1use std::{
2 slice,
3 sync::{Arc, OnceLock},
4};
5
6use ::util::ResultExt;
7use anyhow::{Context, Result};
8use windows::{
9 Win32::{
10 Foundation::HWND,
11 Graphics::{
12 Direct3D::*,
13 Direct3D11::*,
14 DirectComposition::*,
15 DirectWrite::*,
16 Dxgi::{Common::*, *},
17 },
18 },
19 core::Interface,
20};
21
22use crate::{
23 platform::windows::directx_renderer::shader_resources::{
24 RawShaderBytes, ShaderModule, ShaderTarget,
25 },
26 *,
27};
28
29pub(crate) const DISABLE_DIRECT_COMPOSITION: &str = "GPUI_DISABLE_DIRECT_COMPOSITION";
30const RENDER_TARGET_FORMAT: DXGI_FORMAT = DXGI_FORMAT_B8G8R8A8_UNORM;
31// This configuration is used for MSAA rendering on paths only, and it's guaranteed to be supported by DirectX 11.
32const PATH_MULTISAMPLE_COUNT: u32 = 4;
33
34pub(crate) struct FontInfo {
35 pub gamma_ratios: [f32; 4],
36 pub grayscale_enhanced_contrast: f32,
37 pub subpixel_enhanced_contrast: f32,
38}
39
40pub(crate) struct DirectXRenderer {
41 hwnd: HWND,
42 atlas: Arc<DirectXAtlas>,
43 devices: Option<DirectXRendererDevices>,
44 resources: Option<DirectXResources>,
45 globals: DirectXGlobalElements,
46 pipelines: DirectXRenderPipelines,
47 direct_composition: Option<DirectComposition>,
48 font_info: &'static FontInfo,
49
50 width: u32,
51 height: u32,
52
53 /// Whether we want to skip drwaing due to device lost events.
54 ///
55 /// In that case we want to discard the first frame that we draw as we got reset in the middle of a frame
56 /// meaning we lost all the allocated gpu textures and scene resources.
57 skip_draws: bool,
58}
59
60/// Direct3D objects
61#[derive(Clone)]
62pub(crate) struct DirectXRendererDevices {
63 pub(crate) adapter: IDXGIAdapter1,
64 pub(crate) dxgi_factory: IDXGIFactory6,
65 pub(crate) device: ID3D11Device,
66 pub(crate) device_context: ID3D11DeviceContext,
67 dxgi_device: Option<IDXGIDevice>,
68}
69
70struct DirectXResources {
71 // Direct3D rendering objects
72 swap_chain: IDXGISwapChain1,
73 render_target: Option<ID3D11Texture2D>,
74 render_target_view: Option<ID3D11RenderTargetView>,
75
76 // Path intermediate textures (with MSAA)
77 path_intermediate_texture: ID3D11Texture2D,
78 path_intermediate_srv: Option<ID3D11ShaderResourceView>,
79 path_intermediate_msaa_texture: ID3D11Texture2D,
80 path_intermediate_msaa_view: Option<ID3D11RenderTargetView>,
81
82 // Cached viewport
83 viewport: D3D11_VIEWPORT,
84}
85
86struct DirectXRenderPipelines {
87 shadow_pipeline: PipelineState<Shadow>,
88 quad_pipeline: PipelineState<Quad>,
89 path_rasterization_pipeline: PipelineState<PathRasterizationSprite>,
90 path_sprite_pipeline: PipelineState<PathSprite>,
91 underline_pipeline: PipelineState<Underline>,
92 mono_sprites: PipelineState<MonochromeSprite>,
93 subpixel_sprites: PipelineState<SubpixelSprite>,
94 poly_sprites: PipelineState<PolychromeSprite>,
95}
96
97struct DirectXGlobalElements {
98 global_params_buffer: Option<ID3D11Buffer>,
99 sampler: Option<ID3D11SamplerState>,
100}
101
102struct DirectComposition {
103 comp_device: IDCompositionDevice,
104 comp_target: IDCompositionTarget,
105 comp_visual: IDCompositionVisual,
106}
107
108impl DirectXRendererDevices {
109 pub(crate) fn new(
110 directx_devices: &DirectXDevices,
111 disable_direct_composition: bool,
112 ) -> Result<Self> {
113 let DirectXDevices {
114 adapter,
115 dxgi_factory,
116 device,
117 device_context,
118 } = directx_devices;
119 let dxgi_device = if disable_direct_composition {
120 None
121 } else {
122 Some(device.cast().context("Creating DXGI device")?)
123 };
124
125 Ok(Self {
126 adapter: adapter.clone(),
127 dxgi_factory: dxgi_factory.clone(),
128 device: device.clone(),
129 device_context: device_context.clone(),
130 dxgi_device,
131 })
132 }
133}
134
135impl DirectXRenderer {
136 pub(crate) fn new(
137 hwnd: HWND,
138 directx_devices: &DirectXDevices,
139 disable_direct_composition: bool,
140 ) -> Result<Self> {
141 if disable_direct_composition {
142 log::info!("Direct Composition is disabled.");
143 }
144
145 let devices = DirectXRendererDevices::new(directx_devices, disable_direct_composition)
146 .context("Creating DirectX devices")?;
147 let atlas = Arc::new(DirectXAtlas::new(&devices.device, &devices.device_context));
148
149 let resources = DirectXResources::new(&devices, 1, 1, hwnd, disable_direct_composition)
150 .context("Creating DirectX resources")?;
151 let globals = DirectXGlobalElements::new(&devices.device)
152 .context("Creating DirectX global elements")?;
153 let pipelines = DirectXRenderPipelines::new(&devices.device)
154 .context("Creating DirectX render pipelines")?;
155
156 let direct_composition = if disable_direct_composition {
157 None
158 } else {
159 let composition = DirectComposition::new(devices.dxgi_device.as_ref().unwrap(), hwnd)
160 .context("Creating DirectComposition")?;
161 composition
162 .set_swap_chain(&resources.swap_chain)
163 .context("Setting swap chain for DirectComposition")?;
164 Some(composition)
165 };
166
167 Ok(DirectXRenderer {
168 hwnd,
169 atlas,
170 devices: Some(devices),
171 resources: Some(resources),
172 globals,
173 pipelines,
174 direct_composition,
175 font_info: Self::get_font_info(),
176 width: 1,
177 height: 1,
178 skip_draws: false,
179 })
180 }
181
182 pub(crate) fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
183 self.atlas.clone()
184 }
185
186 fn pre_draw(&self, clear_color: &[f32; 4]) -> Result<()> {
187 let resources = self.resources.as_ref().expect("resources missing");
188 let device_context = &self
189 .devices
190 .as_ref()
191 .expect("devices missing")
192 .device_context;
193 update_buffer(
194 device_context,
195 self.globals.global_params_buffer.as_ref().unwrap(),
196 &[GlobalParams {
197 gamma_ratios: self.font_info.gamma_ratios,
198 viewport_size: [resources.viewport.Width, resources.viewport.Height],
199 grayscale_enhanced_contrast: self.font_info.grayscale_enhanced_contrast,
200 subpixel_enhanced_contrast: self.font_info.subpixel_enhanced_contrast,
201 }],
202 )?;
203 unsafe {
204 device_context.ClearRenderTargetView(
205 resources
206 .render_target_view
207 .as_ref()
208 .context("missing render target view")?,
209 clear_color,
210 );
211 device_context
212 .OMSetRenderTargets(Some(slice::from_ref(&resources.render_target_view)), None);
213 device_context.RSSetViewports(Some(slice::from_ref(&resources.viewport)));
214 }
215 Ok(())
216 }
217
218 #[inline]
219 fn present(&mut self) -> Result<()> {
220 let result = unsafe {
221 self.resources
222 .as_ref()
223 .expect("resources missing")
224 .swap_chain
225 .Present(0, DXGI_PRESENT(0))
226 };
227 result.ok().context("Presenting swap chain failed")
228 }
229
230 pub(crate) fn handle_device_lost(&mut self, directx_devices: &DirectXDevices) -> Result<()> {
231 try_to_recover_from_device_lost(|| {
232 self.handle_device_lost_impl(directx_devices)
233 .context("DirectXRenderer handling device lost")
234 })
235 }
236
237 fn handle_device_lost_impl(&mut self, directx_devices: &DirectXDevices) -> Result<()> {
238 let disable_direct_composition = self.direct_composition.is_none();
239
240 unsafe {
241 #[cfg(debug_assertions)]
242 if let Some(devices) = &self.devices {
243 report_live_objects(&devices.device)
244 .context("Failed to report live objects after device lost")
245 .log_err();
246 }
247
248 self.resources.take();
249 if let Some(devices) = &self.devices {
250 devices.device_context.OMSetRenderTargets(None, None);
251 devices.device_context.ClearState();
252 devices.device_context.Flush();
253 #[cfg(debug_assertions)]
254 report_live_objects(&devices.device)
255 .context("Failed to report live objects after device lost")
256 .log_err();
257 }
258
259 self.direct_composition.take();
260 self.devices.take();
261 }
262
263 let devices = DirectXRendererDevices::new(directx_devices, disable_direct_composition)
264 .context("Recreating DirectX devices")?;
265 let resources = DirectXResources::new(
266 &devices,
267 self.width,
268 self.height,
269 self.hwnd,
270 disable_direct_composition,
271 )
272 .context("Creating DirectX resources")?;
273 let globals = DirectXGlobalElements::new(&devices.device)
274 .context("Creating DirectXGlobalElements")?;
275 let pipelines = DirectXRenderPipelines::new(&devices.device)
276 .context("Creating DirectXRenderPipelines")?;
277
278 let direct_composition = if disable_direct_composition {
279 None
280 } else {
281 let composition =
282 DirectComposition::new(devices.dxgi_device.as_ref().unwrap(), self.hwnd)?;
283 composition.set_swap_chain(&resources.swap_chain)?;
284 Some(composition)
285 };
286
287 self.atlas
288 .handle_device_lost(&devices.device, &devices.device_context);
289
290 unsafe {
291 devices
292 .device_context
293 .OMSetRenderTargets(Some(slice::from_ref(&resources.render_target_view)), None);
294 }
295 self.devices = Some(devices);
296 self.resources = Some(resources);
297 self.globals = globals;
298 self.pipelines = pipelines;
299 self.direct_composition = direct_composition;
300 self.skip_draws = true;
301 Ok(())
302 }
303
304 pub(crate) fn draw(
305 &mut self,
306 scene: &Scene,
307 background_appearance: WindowBackgroundAppearance,
308 ) -> Result<()> {
309 if self.skip_draws {
310 // skip drawing this frame, we just recovered from a device lost event
311 // and so likely do not have the textures anymore that are required for drawing
312 return Ok(());
313 }
314 self.pre_draw(&match background_appearance {
315 WindowBackgroundAppearance::Opaque => [1.0f32; 4],
316 _ => [0.0f32; 4],
317 })?;
318 for batch in scene.batches() {
319 match batch {
320 PrimitiveBatch::Shadows(shadows) => self.draw_shadows(shadows),
321 PrimitiveBatch::Quads(quads) => self.draw_quads(quads),
322 PrimitiveBatch::Paths(paths) => {
323 self.draw_paths_to_intermediate(paths)?;
324 self.draw_paths_from_intermediate(paths)
325 }
326 PrimitiveBatch::Underlines(underlines) => self.draw_underlines(underlines),
327 PrimitiveBatch::MonochromeSprites {
328 texture_id,
329 sprites,
330 } => self.draw_monochrome_sprites(texture_id, sprites),
331 PrimitiveBatch::SubpixelSprites {
332 texture_id,
333 sprites,
334 } => self.draw_subpixel_sprites(texture_id, sprites),
335 PrimitiveBatch::PolychromeSprites {
336 texture_id,
337 sprites,
338 } => self.draw_polychrome_sprites(texture_id, sprites),
339 PrimitiveBatch::Surfaces(surfaces) => self.draw_surfaces(surfaces),
340 }
341 .context(format!(
342 "scene too large:\
343 {} paths, {} shadows, {} quads, {} underlines, {} mono, {} subpixel, {} poly, {} surfaces",
344 scene.paths.len(),
345 scene.shadows.len(),
346 scene.quads.len(),
347 scene.underlines.len(),
348 scene.monochrome_sprites.len(),
349 scene.subpixel_sprites.len(),
350 scene.polychrome_sprites.len(),
351 scene.surfaces.len(),
352 ))?;
353 }
354 self.present()
355 }
356
357 pub(crate) fn resize(&mut self, new_size: Size<DevicePixels>) -> Result<()> {
358 let width = new_size.width.0.max(1) as u32;
359 let height = new_size.height.0.max(1) as u32;
360 if self.width == width && self.height == height {
361 return Ok(());
362 }
363 self.width = width;
364 self.height = height;
365
366 // Clear the render target before resizing
367 let devices = self.devices.as_ref().context("devices missing")?;
368 unsafe { devices.device_context.OMSetRenderTargets(None, None) };
369 let resources = self.resources.as_mut().context("resources missing")?;
370 resources.render_target.take();
371 resources.render_target_view.take();
372
373 // Resizing the swap chain requires a call to the underlying DXGI adapter, which can return the device removed error.
374 // The app might have moved to a monitor that's attached to a different graphics device.
375 // When a graphics device is removed or reset, the desktop resolution often changes, resulting in a window size change.
376 // But here we just return the error, because we are handling device lost scenarios elsewhere.
377 unsafe {
378 resources
379 .swap_chain
380 .ResizeBuffers(
381 BUFFER_COUNT as u32,
382 width,
383 height,
384 RENDER_TARGET_FORMAT,
385 DXGI_SWAP_CHAIN_FLAG(0),
386 )
387 .context("Failed to resize swap chain")?;
388 }
389
390 resources.recreate_resources(devices, width, height)?;
391
392 unsafe {
393 devices
394 .device_context
395 .OMSetRenderTargets(Some(slice::from_ref(&resources.render_target_view)), None);
396 }
397
398 Ok(())
399 }
400
401 fn draw_shadows(&mut self, shadows: &[Shadow]) -> Result<()> {
402 if shadows.is_empty() {
403 return Ok(());
404 }
405 let devices = self.devices.as_ref().context("devices missing")?;
406 self.pipelines.shadow_pipeline.update_buffer(
407 &devices.device,
408 &devices.device_context,
409 shadows,
410 )?;
411 self.pipelines.shadow_pipeline.draw(
412 &devices.device_context,
413 slice::from_ref(
414 &self
415 .resources
416 .as_ref()
417 .context("resources missing")?
418 .viewport,
419 ),
420 slice::from_ref(&self.globals.global_params_buffer),
421 D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
422 4,
423 shadows.len() as u32,
424 )
425 }
426
427 fn draw_quads(&mut self, quads: &[Quad]) -> Result<()> {
428 if quads.is_empty() {
429 return Ok(());
430 }
431 let devices = self.devices.as_ref().context("devices missing")?;
432 self.pipelines.quad_pipeline.update_buffer(
433 &devices.device,
434 &devices.device_context,
435 quads,
436 )?;
437 self.pipelines.quad_pipeline.draw(
438 &devices.device_context,
439 slice::from_ref(
440 &self
441 .resources
442 .as_ref()
443 .context("resources missing")?
444 .viewport,
445 ),
446 slice::from_ref(&self.globals.global_params_buffer),
447 D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
448 4,
449 quads.len() as u32,
450 )
451 }
452
453 fn draw_paths_to_intermediate(&mut self, paths: &[Path<ScaledPixels>]) -> Result<()> {
454 if paths.is_empty() {
455 return Ok(());
456 }
457
458 let devices = self.devices.as_ref().context("devices missing")?;
459 let resources = self.resources.as_ref().context("resources missing")?;
460 // Clear intermediate MSAA texture
461 unsafe {
462 devices.device_context.ClearRenderTargetView(
463 resources.path_intermediate_msaa_view.as_ref().unwrap(),
464 &[0.0; 4],
465 );
466 // Set intermediate MSAA texture as render target
467 devices.device_context.OMSetRenderTargets(
468 Some(slice::from_ref(&resources.path_intermediate_msaa_view)),
469 None,
470 );
471 }
472
473 // Collect all vertices and sprites for a single draw call
474 let mut vertices = Vec::new();
475
476 for path in paths {
477 vertices.extend(path.vertices.iter().map(|v| PathRasterizationSprite {
478 xy_position: v.xy_position,
479 st_position: v.st_position,
480 color: path.color,
481 bounds: path.clipped_bounds(),
482 }));
483 }
484
485 self.pipelines.path_rasterization_pipeline.update_buffer(
486 &devices.device,
487 &devices.device_context,
488 &vertices,
489 )?;
490
491 self.pipelines.path_rasterization_pipeline.draw(
492 &devices.device_context,
493 slice::from_ref(&resources.viewport),
494 slice::from_ref(&self.globals.global_params_buffer),
495 D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
496 vertices.len() as u32,
497 1,
498 )?;
499
500 // Resolve MSAA to non-MSAA intermediate texture
501 unsafe {
502 devices.device_context.ResolveSubresource(
503 &resources.path_intermediate_texture,
504 0,
505 &resources.path_intermediate_msaa_texture,
506 0,
507 RENDER_TARGET_FORMAT,
508 );
509 // Restore main render target
510 devices
511 .device_context
512 .OMSetRenderTargets(Some(slice::from_ref(&resources.render_target_view)), None);
513 }
514
515 Ok(())
516 }
517
518 fn draw_paths_from_intermediate(&mut self, paths: &[Path<ScaledPixels>]) -> Result<()> {
519 let Some(first_path) = paths.first() else {
520 return Ok(());
521 };
522
523 // When copying paths from the intermediate texture to the drawable,
524 // each pixel must only be copied once, in case of transparent paths.
525 //
526 // If all paths have the same draw order, then their bounds are all
527 // disjoint, so we can copy each path's bounds individually. If this
528 // batch combines different draw orders, we perform a single copy
529 // for a minimal spanning rect.
530 let sprites = if paths.last().unwrap().order == first_path.order {
531 paths
532 .iter()
533 .map(|path| PathSprite {
534 bounds: path.clipped_bounds(),
535 })
536 .collect::<Vec<_>>()
537 } else {
538 let mut bounds = first_path.clipped_bounds();
539 for path in paths.iter().skip(1) {
540 bounds = bounds.union(&path.clipped_bounds());
541 }
542 vec![PathSprite { bounds }]
543 };
544
545 let devices = self.devices.as_ref().context("devices missing")?;
546 let resources = self.resources.as_ref().context("resources missing")?;
547 self.pipelines.path_sprite_pipeline.update_buffer(
548 &devices.device,
549 &devices.device_context,
550 &sprites,
551 )?;
552
553 // Draw the sprites with the path texture
554 self.pipelines.path_sprite_pipeline.draw_with_texture(
555 &devices.device_context,
556 slice::from_ref(&resources.path_intermediate_srv),
557 slice::from_ref(&resources.viewport),
558 slice::from_ref(&self.globals.global_params_buffer),
559 slice::from_ref(&self.globals.sampler),
560 sprites.len() as u32,
561 )
562 }
563
564 fn draw_underlines(&mut self, underlines: &[Underline]) -> Result<()> {
565 if underlines.is_empty() {
566 return Ok(());
567 }
568 let devices = self.devices.as_ref().context("devices missing")?;
569 let resources = self.resources.as_ref().context("resources missing")?;
570 self.pipelines.underline_pipeline.update_buffer(
571 &devices.device,
572 &devices.device_context,
573 underlines,
574 )?;
575 self.pipelines.underline_pipeline.draw(
576 &devices.device_context,
577 slice::from_ref(&resources.viewport),
578 slice::from_ref(&self.globals.global_params_buffer),
579 D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
580 4,
581 underlines.len() as u32,
582 )
583 }
584
585 fn draw_monochrome_sprites(
586 &mut self,
587 texture_id: AtlasTextureId,
588 sprites: &[MonochromeSprite],
589 ) -> Result<()> {
590 if sprites.is_empty() {
591 return Ok(());
592 }
593 let devices = self.devices.as_ref().context("devices missing")?;
594 let resources = self.resources.as_ref().context("resources missing")?;
595
596 self.pipelines.mono_sprites.update_buffer(
597 &devices.device,
598 &devices.device_context,
599 sprites,
600 )?;
601 let texture_view = self.atlas.get_texture_view(texture_id);
602 self.pipelines.mono_sprites.draw_with_texture(
603 &devices.device_context,
604 &texture_view,
605 slice::from_ref(&resources.viewport),
606 slice::from_ref(&self.globals.global_params_buffer),
607 slice::from_ref(&self.globals.sampler),
608 sprites.len() as u32,
609 )
610 }
611
612 fn draw_subpixel_sprites(
613 &mut self,
614 texture_id: AtlasTextureId,
615 sprites: &[SubpixelSprite],
616 ) -> Result<()> {
617 if sprites.is_empty() {
618 return Ok(());
619 }
620 let devices = self.devices.as_ref().context("devices missing")?;
621 let resources = self.resources.as_ref().context("resources missing")?;
622
623 self.pipelines.subpixel_sprites.update_buffer(
624 &devices.device,
625 &devices.device_context,
626 &sprites,
627 )?;
628 let texture_view = self.atlas.get_texture_view(texture_id);
629 self.pipelines.subpixel_sprites.draw_with_texture(
630 &devices.device_context,
631 &texture_view,
632 slice::from_ref(&resources.viewport),
633 slice::from_ref(&self.globals.global_params_buffer),
634 slice::from_ref(&self.globals.sampler),
635 sprites.len() as u32,
636 )
637 }
638
639 fn draw_polychrome_sprites(
640 &mut self,
641 texture_id: AtlasTextureId,
642 sprites: &[PolychromeSprite],
643 ) -> Result<()> {
644 if sprites.is_empty() {
645 return Ok(());
646 }
647
648 let devices = self.devices.as_ref().context("devices missing")?;
649 let resources = self.resources.as_ref().context("resources missing")?;
650 self.pipelines.poly_sprites.update_buffer(
651 &devices.device,
652 &devices.device_context,
653 sprites,
654 )?;
655 let texture_view = self.atlas.get_texture_view(texture_id);
656 self.pipelines.poly_sprites.draw_with_texture(
657 &devices.device_context,
658 &texture_view,
659 slice::from_ref(&resources.viewport),
660 slice::from_ref(&self.globals.global_params_buffer),
661 slice::from_ref(&self.globals.sampler),
662 sprites.len() as u32,
663 )
664 }
665
666 fn draw_surfaces(&mut self, surfaces: &[PaintSurface]) -> Result<()> {
667 if surfaces.is_empty() {
668 return Ok(());
669 }
670 Ok(())
671 }
672
673 pub(crate) fn gpu_specs(&self) -> Result<GpuSpecs> {
674 let devices = self.devices.as_ref().context("devices missing")?;
675 let desc = unsafe { devices.adapter.GetDesc1() }?;
676 let is_software_emulated = (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE.0 as u32) != 0;
677 let device_name = String::from_utf16_lossy(&desc.Description)
678 .trim_matches(char::from(0))
679 .to_string();
680 let driver_name = match desc.VendorId {
681 0x10DE => "NVIDIA Corporation".to_string(),
682 0x1002 => "AMD Corporation".to_string(),
683 0x8086 => "Intel Corporation".to_string(),
684 id => format!("Unknown Vendor (ID: {:#X})", id),
685 };
686 let driver_version = match desc.VendorId {
687 0x10DE => nvidia::get_driver_version(),
688 0x1002 => amd::get_driver_version(),
689 // For Intel and other vendors, we use the DXGI API to get the driver version.
690 _ => dxgi::get_driver_version(&devices.adapter),
691 }
692 .context("Failed to get gpu driver info")
693 .log_err()
694 .unwrap_or("Unknown Driver".to_string());
695 Ok(GpuSpecs {
696 is_software_emulated,
697 device_name,
698 driver_name,
699 driver_info: driver_version,
700 })
701 }
702
703 pub(crate) fn get_font_info() -> &'static FontInfo {
704 static CACHED_FONT_INFO: OnceLock<FontInfo> = OnceLock::new();
705 CACHED_FONT_INFO.get_or_init(|| unsafe {
706 let factory: IDWriteFactory5 = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED).unwrap();
707 let render_params: IDWriteRenderingParams1 =
708 factory.CreateRenderingParams().unwrap().cast().unwrap();
709 FontInfo {
710 gamma_ratios: get_gamma_correction_ratios(render_params.GetGamma()),
711 grayscale_enhanced_contrast: render_params.GetGrayscaleEnhancedContrast(),
712 subpixel_enhanced_contrast: render_params.GetEnhancedContrast(),
713 }
714 })
715 }
716
717 pub(crate) fn mark_drawable(&mut self) {
718 self.skip_draws = false;
719 }
720}
721
722impl DirectXResources {
723 pub fn new(
724 devices: &DirectXRendererDevices,
725 width: u32,
726 height: u32,
727 hwnd: HWND,
728 disable_direct_composition: bool,
729 ) -> Result<Self> {
730 let swap_chain = if disable_direct_composition {
731 create_swap_chain(&devices.dxgi_factory, &devices.device, hwnd, width, height)?
732 } else {
733 create_swap_chain_for_composition(
734 &devices.dxgi_factory,
735 &devices.device,
736 width,
737 height,
738 )?
739 };
740
741 let (
742 render_target,
743 render_target_view,
744 path_intermediate_texture,
745 path_intermediate_srv,
746 path_intermediate_msaa_texture,
747 path_intermediate_msaa_view,
748 viewport,
749 ) = create_resources(devices, &swap_chain, width, height)?;
750 set_rasterizer_state(&devices.device, &devices.device_context)?;
751
752 Ok(Self {
753 swap_chain,
754 render_target: Some(render_target),
755 render_target_view,
756 path_intermediate_texture,
757 path_intermediate_msaa_texture,
758 path_intermediate_msaa_view,
759 path_intermediate_srv,
760 viewport,
761 })
762 }
763
764 #[inline]
765 fn recreate_resources(
766 &mut self,
767 devices: &DirectXRendererDevices,
768 width: u32,
769 height: u32,
770 ) -> Result<()> {
771 let (
772 render_target,
773 render_target_view,
774 path_intermediate_texture,
775 path_intermediate_srv,
776 path_intermediate_msaa_texture,
777 path_intermediate_msaa_view,
778 viewport,
779 ) = create_resources(devices, &self.swap_chain, width, height)?;
780 self.render_target = Some(render_target);
781 self.render_target_view = render_target_view;
782 self.path_intermediate_texture = path_intermediate_texture;
783 self.path_intermediate_msaa_texture = path_intermediate_msaa_texture;
784 self.path_intermediate_msaa_view = path_intermediate_msaa_view;
785 self.path_intermediate_srv = path_intermediate_srv;
786 self.viewport = viewport;
787 Ok(())
788 }
789}
790
791impl DirectXRenderPipelines {
792 pub fn new(device: &ID3D11Device) -> Result<Self> {
793 let shadow_pipeline = PipelineState::new(
794 device,
795 "shadow_pipeline",
796 ShaderModule::Shadow,
797 4,
798 create_blend_state(device)?,
799 )?;
800 let quad_pipeline = PipelineState::new(
801 device,
802 "quad_pipeline",
803 ShaderModule::Quad,
804 64,
805 create_blend_state(device)?,
806 )?;
807 let path_rasterization_pipeline = PipelineState::new(
808 device,
809 "path_rasterization_pipeline",
810 ShaderModule::PathRasterization,
811 32,
812 create_blend_state_for_path_rasterization(device)?,
813 )?;
814 let path_sprite_pipeline = PipelineState::new(
815 device,
816 "path_sprite_pipeline",
817 ShaderModule::PathSprite,
818 4,
819 create_blend_state_for_path_sprite(device)?,
820 )?;
821 let underline_pipeline = PipelineState::new(
822 device,
823 "underline_pipeline",
824 ShaderModule::Underline,
825 4,
826 create_blend_state(device)?,
827 )?;
828 let mono_sprites = PipelineState::new(
829 device,
830 "monochrome_sprite_pipeline",
831 ShaderModule::MonochromeSprite,
832 512,
833 create_blend_state(device)?,
834 )?;
835 let subpixel_sprites = PipelineState::new(
836 device,
837 "subpixel_sprite_pipeline",
838 ShaderModule::SubpixelSprite,
839 512,
840 create_blend_state_for_subpixel_rendering(device)?,
841 )?;
842 let poly_sprites = PipelineState::new(
843 device,
844 "polychrome_sprite_pipeline",
845 ShaderModule::PolychromeSprite,
846 16,
847 create_blend_state(device)?,
848 )?;
849
850 Ok(Self {
851 shadow_pipeline,
852 quad_pipeline,
853 path_rasterization_pipeline,
854 path_sprite_pipeline,
855 underline_pipeline,
856 mono_sprites,
857 subpixel_sprites,
858 poly_sprites,
859 })
860 }
861}
862
863impl DirectComposition {
864 pub fn new(dxgi_device: &IDXGIDevice, hwnd: HWND) -> Result<Self> {
865 let comp_device = get_comp_device(dxgi_device)?;
866 let comp_target = unsafe { comp_device.CreateTargetForHwnd(hwnd, true) }?;
867 let comp_visual = unsafe { comp_device.CreateVisual() }?;
868
869 Ok(Self {
870 comp_device,
871 comp_target,
872 comp_visual,
873 })
874 }
875
876 pub fn set_swap_chain(&self, swap_chain: &IDXGISwapChain1) -> Result<()> {
877 unsafe {
878 self.comp_visual.SetContent(swap_chain)?;
879 self.comp_target.SetRoot(&self.comp_visual)?;
880 self.comp_device.Commit()?;
881 }
882 Ok(())
883 }
884}
885
886impl DirectXGlobalElements {
887 pub fn new(device: &ID3D11Device) -> Result<Self> {
888 let global_params_buffer = unsafe {
889 let desc = D3D11_BUFFER_DESC {
890 ByteWidth: std::mem::size_of::<GlobalParams>() as u32,
891 Usage: D3D11_USAGE_DYNAMIC,
892 BindFlags: D3D11_BIND_CONSTANT_BUFFER.0 as u32,
893 CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32,
894 ..Default::default()
895 };
896 let mut buffer = None;
897 device.CreateBuffer(&desc, None, Some(&mut buffer))?;
898 buffer
899 };
900
901 let sampler = unsafe {
902 let desc = D3D11_SAMPLER_DESC {
903 Filter: D3D11_FILTER_MIN_MAG_MIP_LINEAR,
904 AddressU: D3D11_TEXTURE_ADDRESS_WRAP,
905 AddressV: D3D11_TEXTURE_ADDRESS_WRAP,
906 AddressW: D3D11_TEXTURE_ADDRESS_WRAP,
907 MipLODBias: 0.0,
908 MaxAnisotropy: 1,
909 ComparisonFunc: D3D11_COMPARISON_ALWAYS,
910 BorderColor: [0.0; 4],
911 MinLOD: 0.0,
912 MaxLOD: D3D11_FLOAT32_MAX,
913 };
914 let mut output = None;
915 device.CreateSamplerState(&desc, Some(&mut output))?;
916 output
917 };
918
919 Ok(Self {
920 global_params_buffer,
921 sampler,
922 })
923 }
924}
925
926#[derive(Debug, Default)]
927#[repr(C)]
928struct GlobalParams {
929 gamma_ratios: [f32; 4],
930 viewport_size: [f32; 2],
931 grayscale_enhanced_contrast: f32,
932 subpixel_enhanced_contrast: f32,
933}
934
935struct PipelineState<T> {
936 label: &'static str,
937 vertex: ID3D11VertexShader,
938 fragment: ID3D11PixelShader,
939 buffer: ID3D11Buffer,
940 buffer_size: usize,
941 view: Option<ID3D11ShaderResourceView>,
942 blend_state: ID3D11BlendState,
943 _marker: std::marker::PhantomData<T>,
944}
945
946impl<T> PipelineState<T> {
947 fn new(
948 device: &ID3D11Device,
949 label: &'static str,
950 shader_module: ShaderModule,
951 buffer_size: usize,
952 blend_state: ID3D11BlendState,
953 ) -> Result<Self> {
954 let vertex = {
955 let raw_shader = RawShaderBytes::new(shader_module, ShaderTarget::Vertex)?;
956 create_vertex_shader(device, raw_shader.as_bytes())?
957 };
958 let fragment = {
959 let raw_shader = RawShaderBytes::new(shader_module, ShaderTarget::Fragment)?;
960 create_fragment_shader(device, raw_shader.as_bytes())?
961 };
962 let buffer = create_buffer(device, std::mem::size_of::<T>(), buffer_size)?;
963 let view = create_buffer_view(device, &buffer)?;
964
965 Ok(PipelineState {
966 label,
967 vertex,
968 fragment,
969 buffer,
970 buffer_size,
971 view,
972 blend_state,
973 _marker: std::marker::PhantomData,
974 })
975 }
976
977 fn update_buffer(
978 &mut self,
979 device: &ID3D11Device,
980 device_context: &ID3D11DeviceContext,
981 data: &[T],
982 ) -> Result<()> {
983 if self.buffer_size < data.len() {
984 let new_buffer_size = data.len().next_power_of_two();
985 log::info!(
986 "Updating {} buffer size from {} to {}",
987 self.label,
988 self.buffer_size,
989 new_buffer_size
990 );
991 let buffer = create_buffer(device, std::mem::size_of::<T>(), new_buffer_size)?;
992 let view = create_buffer_view(device, &buffer)?;
993 self.buffer = buffer;
994 self.view = view;
995 self.buffer_size = new_buffer_size;
996 }
997 update_buffer(device_context, &self.buffer, data)
998 }
999
1000 fn draw(
1001 &self,
1002 device_context: &ID3D11DeviceContext,
1003 viewport: &[D3D11_VIEWPORT],
1004 global_params: &[Option<ID3D11Buffer>],
1005 topology: D3D_PRIMITIVE_TOPOLOGY,
1006 vertex_count: u32,
1007 instance_count: u32,
1008 ) -> Result<()> {
1009 set_pipeline_state(
1010 device_context,
1011 slice::from_ref(&self.view),
1012 topology,
1013 viewport,
1014 &self.vertex,
1015 &self.fragment,
1016 global_params,
1017 &self.blend_state,
1018 );
1019 unsafe {
1020 device_context.DrawInstanced(vertex_count, instance_count, 0, 0);
1021 }
1022 Ok(())
1023 }
1024
1025 fn draw_with_texture(
1026 &self,
1027 device_context: &ID3D11DeviceContext,
1028 texture: &[Option<ID3D11ShaderResourceView>],
1029 viewport: &[D3D11_VIEWPORT],
1030 global_params: &[Option<ID3D11Buffer>],
1031 sampler: &[Option<ID3D11SamplerState>],
1032 instance_count: u32,
1033 ) -> Result<()> {
1034 set_pipeline_state(
1035 device_context,
1036 slice::from_ref(&self.view),
1037 D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
1038 viewport,
1039 &self.vertex,
1040 &self.fragment,
1041 global_params,
1042 &self.blend_state,
1043 );
1044 unsafe {
1045 device_context.PSSetSamplers(0, Some(sampler));
1046 device_context.VSSetShaderResources(0, Some(texture));
1047 device_context.PSSetShaderResources(0, Some(texture));
1048
1049 device_context.DrawInstanced(4, instance_count, 0, 0);
1050 }
1051 Ok(())
1052 }
1053}
1054
1055#[derive(Clone, Copy)]
1056#[repr(C)]
1057struct PathRasterizationSprite {
1058 xy_position: Point<ScaledPixels>,
1059 st_position: Point<f32>,
1060 color: Background,
1061 bounds: Bounds<ScaledPixels>,
1062}
1063
1064#[derive(Clone, Copy)]
1065#[repr(C)]
1066struct PathSprite {
1067 bounds: Bounds<ScaledPixels>,
1068}
1069
1070impl Drop for DirectXRenderer {
1071 fn drop(&mut self) {
1072 #[cfg(debug_assertions)]
1073 if let Some(devices) = &self.devices {
1074 report_live_objects(&devices.device).ok();
1075 }
1076 }
1077}
1078
1079#[inline]
1080fn get_comp_device(dxgi_device: &IDXGIDevice) -> Result<IDCompositionDevice> {
1081 Ok(unsafe { DCompositionCreateDevice(dxgi_device)? })
1082}
1083
1084fn create_swap_chain_for_composition(
1085 dxgi_factory: &IDXGIFactory6,
1086 device: &ID3D11Device,
1087 width: u32,
1088 height: u32,
1089) -> Result<IDXGISwapChain1> {
1090 let desc = DXGI_SWAP_CHAIN_DESC1 {
1091 Width: width,
1092 Height: height,
1093 Format: RENDER_TARGET_FORMAT,
1094 Stereo: false.into(),
1095 SampleDesc: DXGI_SAMPLE_DESC {
1096 Count: 1,
1097 Quality: 0,
1098 },
1099 BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT,
1100 BufferCount: BUFFER_COUNT as u32,
1101 // Composition SwapChains only support the DXGI_SCALING_STRETCH Scaling.
1102 Scaling: DXGI_SCALING_STRETCH,
1103 SwapEffect: DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL,
1104 AlphaMode: DXGI_ALPHA_MODE_PREMULTIPLIED,
1105 Flags: 0,
1106 };
1107 Ok(unsafe { dxgi_factory.CreateSwapChainForComposition(device, &desc, None)? })
1108}
1109
1110fn create_swap_chain(
1111 dxgi_factory: &IDXGIFactory6,
1112 device: &ID3D11Device,
1113 hwnd: HWND,
1114 width: u32,
1115 height: u32,
1116) -> Result<IDXGISwapChain1> {
1117 use windows::Win32::Graphics::Dxgi::DXGI_MWA_NO_ALT_ENTER;
1118
1119 let desc = DXGI_SWAP_CHAIN_DESC1 {
1120 Width: width,
1121 Height: height,
1122 Format: RENDER_TARGET_FORMAT,
1123 Stereo: false.into(),
1124 SampleDesc: DXGI_SAMPLE_DESC {
1125 Count: 1,
1126 Quality: 0,
1127 },
1128 BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT,
1129 BufferCount: BUFFER_COUNT as u32,
1130 Scaling: DXGI_SCALING_NONE,
1131 SwapEffect: DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL,
1132 AlphaMode: DXGI_ALPHA_MODE_IGNORE,
1133 Flags: 0,
1134 };
1135 let swap_chain =
1136 unsafe { dxgi_factory.CreateSwapChainForHwnd(device, hwnd, &desc, None, None) }?;
1137 unsafe { dxgi_factory.MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER) }?;
1138 Ok(swap_chain)
1139}
1140
1141#[inline]
1142fn create_resources(
1143 devices: &DirectXRendererDevices,
1144 swap_chain: &IDXGISwapChain1,
1145 width: u32,
1146 height: u32,
1147) -> Result<(
1148 ID3D11Texture2D,
1149 Option<ID3D11RenderTargetView>,
1150 ID3D11Texture2D,
1151 Option<ID3D11ShaderResourceView>,
1152 ID3D11Texture2D,
1153 Option<ID3D11RenderTargetView>,
1154 D3D11_VIEWPORT,
1155)> {
1156 let (render_target, render_target_view) =
1157 create_render_target_and_its_view(swap_chain, &devices.device)?;
1158 let (path_intermediate_texture, path_intermediate_srv) =
1159 create_path_intermediate_texture(&devices.device, width, height)?;
1160 let (path_intermediate_msaa_texture, path_intermediate_msaa_view) =
1161 create_path_intermediate_msaa_texture_and_view(&devices.device, width, height)?;
1162 let viewport = set_viewport(&devices.device_context, width as f32, height as f32);
1163 Ok((
1164 render_target,
1165 render_target_view,
1166 path_intermediate_texture,
1167 path_intermediate_srv,
1168 path_intermediate_msaa_texture,
1169 path_intermediate_msaa_view,
1170 viewport,
1171 ))
1172}
1173
1174#[inline]
1175fn create_render_target_and_its_view(
1176 swap_chain: &IDXGISwapChain1,
1177 device: &ID3D11Device,
1178) -> Result<(ID3D11Texture2D, Option<ID3D11RenderTargetView>)> {
1179 let render_target: ID3D11Texture2D = unsafe { swap_chain.GetBuffer(0) }?;
1180 let mut render_target_view = None;
1181 unsafe { device.CreateRenderTargetView(&render_target, None, Some(&mut render_target_view))? };
1182 Ok((render_target, render_target_view))
1183}
1184
1185#[inline]
1186fn create_path_intermediate_texture(
1187 device: &ID3D11Device,
1188 width: u32,
1189 height: u32,
1190) -> Result<(ID3D11Texture2D, Option<ID3D11ShaderResourceView>)> {
1191 let texture = unsafe {
1192 let mut output = None;
1193 let desc = D3D11_TEXTURE2D_DESC {
1194 Width: width,
1195 Height: height,
1196 MipLevels: 1,
1197 ArraySize: 1,
1198 Format: RENDER_TARGET_FORMAT,
1199 SampleDesc: DXGI_SAMPLE_DESC {
1200 Count: 1,
1201 Quality: 0,
1202 },
1203 Usage: D3D11_USAGE_DEFAULT,
1204 BindFlags: (D3D11_BIND_RENDER_TARGET.0 | D3D11_BIND_SHADER_RESOURCE.0) as u32,
1205 CPUAccessFlags: 0,
1206 MiscFlags: 0,
1207 };
1208 device.CreateTexture2D(&desc, None, Some(&mut output))?;
1209 output.unwrap()
1210 };
1211
1212 let mut shader_resource_view = None;
1213 unsafe { device.CreateShaderResourceView(&texture, None, Some(&mut shader_resource_view))? };
1214
1215 Ok((texture, Some(shader_resource_view.unwrap())))
1216}
1217
1218#[inline]
1219fn create_path_intermediate_msaa_texture_and_view(
1220 device: &ID3D11Device,
1221 width: u32,
1222 height: u32,
1223) -> Result<(ID3D11Texture2D, Option<ID3D11RenderTargetView>)> {
1224 let msaa_texture = unsafe {
1225 let mut output = None;
1226 let desc = D3D11_TEXTURE2D_DESC {
1227 Width: width,
1228 Height: height,
1229 MipLevels: 1,
1230 ArraySize: 1,
1231 Format: RENDER_TARGET_FORMAT,
1232 SampleDesc: DXGI_SAMPLE_DESC {
1233 Count: PATH_MULTISAMPLE_COUNT,
1234 Quality: D3D11_STANDARD_MULTISAMPLE_PATTERN.0 as u32,
1235 },
1236 Usage: D3D11_USAGE_DEFAULT,
1237 BindFlags: D3D11_BIND_RENDER_TARGET.0 as u32,
1238 CPUAccessFlags: 0,
1239 MiscFlags: 0,
1240 };
1241 device.CreateTexture2D(&desc, None, Some(&mut output))?;
1242 output.unwrap()
1243 };
1244 let mut msaa_view = None;
1245 unsafe { device.CreateRenderTargetView(&msaa_texture, None, Some(&mut msaa_view))? };
1246 Ok((msaa_texture, Some(msaa_view.unwrap())))
1247}
1248
1249#[inline]
1250fn set_viewport(device_context: &ID3D11DeviceContext, width: f32, height: f32) -> D3D11_VIEWPORT {
1251 let viewport = [D3D11_VIEWPORT {
1252 TopLeftX: 0.0,
1253 TopLeftY: 0.0,
1254 Width: width,
1255 Height: height,
1256 MinDepth: 0.0,
1257 MaxDepth: 1.0,
1258 }];
1259 unsafe { device_context.RSSetViewports(Some(&viewport)) };
1260 viewport[0]
1261}
1262
1263#[inline]
1264fn set_rasterizer_state(device: &ID3D11Device, device_context: &ID3D11DeviceContext) -> Result<()> {
1265 let desc = D3D11_RASTERIZER_DESC {
1266 FillMode: D3D11_FILL_SOLID,
1267 CullMode: D3D11_CULL_NONE,
1268 FrontCounterClockwise: false.into(),
1269 DepthBias: 0,
1270 DepthBiasClamp: 0.0,
1271 SlopeScaledDepthBias: 0.0,
1272 DepthClipEnable: true.into(),
1273 ScissorEnable: false.into(),
1274 MultisampleEnable: true.into(),
1275 AntialiasedLineEnable: false.into(),
1276 };
1277 let rasterizer_state = unsafe {
1278 let mut state = None;
1279 device.CreateRasterizerState(&desc, Some(&mut state))?;
1280 state.unwrap()
1281 };
1282 unsafe { device_context.RSSetState(&rasterizer_state) };
1283 Ok(())
1284}
1285
1286// https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ns-d3d11-d3d11_blend_desc
1287#[inline]
1288fn create_blend_state(device: &ID3D11Device) -> Result<ID3D11BlendState> {
1289 let mut desc = D3D11_BLEND_DESC::default();
1290 desc.RenderTarget[0].BlendEnable = true.into();
1291 desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
1292 desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
1293 desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
1294 desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
1295 desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
1296 desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ONE;
1297 desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8;
1298 unsafe {
1299 let mut state = None;
1300 device.CreateBlendState(&desc, Some(&mut state))?;
1301 Ok(state.unwrap())
1302 }
1303}
1304
1305#[inline]
1306fn create_blend_state_for_subpixel_rendering(device: &ID3D11Device) -> Result<ID3D11BlendState> {
1307 let mut desc = D3D11_BLEND_DESC::default();
1308 desc.RenderTarget[0].BlendEnable = true.into();
1309 desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
1310 desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
1311 desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC1_COLOR;
1312 desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC1_COLOR;
1313 // It does not make sense to draw transparent subpixel-rendered text, since it cannot be meaningfully alpha-blended onto anything else.
1314 desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
1315 desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
1316 desc.RenderTarget[0].RenderTargetWriteMask =
1317 D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8 & !D3D11_COLOR_WRITE_ENABLE_ALPHA.0 as u8;
1318
1319 unsafe {
1320 let mut state = None;
1321 device.CreateBlendState(&desc, Some(&mut state))?;
1322 Ok(state.unwrap())
1323 }
1324}
1325
1326#[inline]
1327fn create_blend_state_for_path_rasterization(device: &ID3D11Device) -> Result<ID3D11BlendState> {
1328 // If the feature level is set to greater than D3D_FEATURE_LEVEL_9_3, the display
1329 // device performs the blend in linear space, which is ideal.
1330 let mut desc = D3D11_BLEND_DESC::default();
1331 desc.RenderTarget[0].BlendEnable = true.into();
1332 desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
1333 desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
1334 desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE;
1335 desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
1336 desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
1337 desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA;
1338 desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8;
1339 unsafe {
1340 let mut state = None;
1341 device.CreateBlendState(&desc, Some(&mut state))?;
1342 Ok(state.unwrap())
1343 }
1344}
1345
1346#[inline]
1347fn create_blend_state_for_path_sprite(device: &ID3D11Device) -> Result<ID3D11BlendState> {
1348 // If the feature level is set to greater than D3D_FEATURE_LEVEL_9_3, the display
1349 // device performs the blend in linear space, which is ideal.
1350 let mut desc = D3D11_BLEND_DESC::default();
1351 desc.RenderTarget[0].BlendEnable = true.into();
1352 desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
1353 desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
1354 desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE;
1355 desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
1356 desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
1357 desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ONE;
1358 desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8;
1359 unsafe {
1360 let mut state = None;
1361 device.CreateBlendState(&desc, Some(&mut state))?;
1362 Ok(state.unwrap())
1363 }
1364}
1365
1366#[inline]
1367fn create_vertex_shader(device: &ID3D11Device, bytes: &[u8]) -> Result<ID3D11VertexShader> {
1368 unsafe {
1369 let mut shader = None;
1370 device.CreateVertexShader(bytes, None, Some(&mut shader))?;
1371 Ok(shader.unwrap())
1372 }
1373}
1374
1375#[inline]
1376fn create_fragment_shader(device: &ID3D11Device, bytes: &[u8]) -> Result<ID3D11PixelShader> {
1377 unsafe {
1378 let mut shader = None;
1379 device.CreatePixelShader(bytes, None, Some(&mut shader))?;
1380 Ok(shader.unwrap())
1381 }
1382}
1383
1384#[inline]
1385fn create_buffer(
1386 device: &ID3D11Device,
1387 element_size: usize,
1388 buffer_size: usize,
1389) -> Result<ID3D11Buffer> {
1390 let desc = D3D11_BUFFER_DESC {
1391 ByteWidth: (element_size * buffer_size) as u32,
1392 Usage: D3D11_USAGE_DYNAMIC,
1393 BindFlags: D3D11_BIND_SHADER_RESOURCE.0 as u32,
1394 CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32,
1395 MiscFlags: D3D11_RESOURCE_MISC_BUFFER_STRUCTURED.0 as u32,
1396 StructureByteStride: element_size as u32,
1397 };
1398 let mut buffer = None;
1399 unsafe { device.CreateBuffer(&desc, None, Some(&mut buffer)) }?;
1400 Ok(buffer.unwrap())
1401}
1402
1403#[inline]
1404fn create_buffer_view(
1405 device: &ID3D11Device,
1406 buffer: &ID3D11Buffer,
1407) -> Result<Option<ID3D11ShaderResourceView>> {
1408 let mut view = None;
1409 unsafe { device.CreateShaderResourceView(buffer, None, Some(&mut view)) }?;
1410 Ok(view)
1411}
1412
1413#[inline]
1414fn update_buffer<T>(
1415 device_context: &ID3D11DeviceContext,
1416 buffer: &ID3D11Buffer,
1417 data: &[T],
1418) -> Result<()> {
1419 unsafe {
1420 let mut dest = std::mem::zeroed();
1421 device_context.Map(buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, Some(&mut dest))?;
1422 std::ptr::copy_nonoverlapping(data.as_ptr(), dest.pData as _, data.len());
1423 device_context.Unmap(buffer, 0);
1424 }
1425 Ok(())
1426}
1427
1428#[inline]
1429fn set_pipeline_state(
1430 device_context: &ID3D11DeviceContext,
1431 buffer_view: &[Option<ID3D11ShaderResourceView>],
1432 topology: D3D_PRIMITIVE_TOPOLOGY,
1433 viewport: &[D3D11_VIEWPORT],
1434 vertex_shader: &ID3D11VertexShader,
1435 fragment_shader: &ID3D11PixelShader,
1436 global_params: &[Option<ID3D11Buffer>],
1437 blend_state: &ID3D11BlendState,
1438) {
1439 unsafe {
1440 device_context.VSSetShaderResources(1, Some(buffer_view));
1441 device_context.PSSetShaderResources(1, Some(buffer_view));
1442 device_context.IASetPrimitiveTopology(topology);
1443 device_context.RSSetViewports(Some(viewport));
1444 device_context.VSSetShader(vertex_shader, None);
1445 device_context.PSSetShader(fragment_shader, None);
1446 device_context.VSSetConstantBuffers(0, Some(global_params));
1447 device_context.PSSetConstantBuffers(0, Some(global_params));
1448 device_context.OMSetBlendState(blend_state, None, 0xFFFFFFFF);
1449 }
1450}
1451
1452#[cfg(debug_assertions)]
1453fn report_live_objects(device: &ID3D11Device) -> Result<()> {
1454 let debug_device: ID3D11Debug = device.cast()?;
1455 unsafe {
1456 debug_device.ReportLiveDeviceObjects(D3D11_RLDO_DETAIL)?;
1457 }
1458 Ok(())
1459}
1460
1461const BUFFER_COUNT: usize = 3;
1462
1463pub(crate) mod shader_resources {
1464 use anyhow::Result;
1465
1466 #[cfg(debug_assertions)]
1467 use windows::{
1468 Win32::Graphics::Direct3D::{
1469 Fxc::{D3DCOMPILE_DEBUG, D3DCOMPILE_SKIP_OPTIMIZATION, D3DCompileFromFile},
1470 ID3DBlob,
1471 },
1472 core::{HSTRING, PCSTR},
1473 };
1474
1475 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
1476 pub(crate) enum ShaderModule {
1477 Quad,
1478 Shadow,
1479 Underline,
1480 PathRasterization,
1481 PathSprite,
1482 MonochromeSprite,
1483 SubpixelSprite,
1484 PolychromeSprite,
1485 EmojiRasterization,
1486 }
1487
1488 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
1489 pub(crate) enum ShaderTarget {
1490 Vertex,
1491 Fragment,
1492 }
1493
1494 pub(crate) struct RawShaderBytes<'t> {
1495 inner: &'t [u8],
1496
1497 #[cfg(debug_assertions)]
1498 _blob: ID3DBlob,
1499 }
1500
1501 impl<'t> RawShaderBytes<'t> {
1502 pub(crate) fn new(module: ShaderModule, target: ShaderTarget) -> Result<Self> {
1503 #[cfg(not(debug_assertions))]
1504 {
1505 Ok(Self::from_bytes(module, target))
1506 }
1507 #[cfg(debug_assertions)]
1508 {
1509 let blob = build_shader_blob(module, target)?;
1510 let inner = unsafe {
1511 std::slice::from_raw_parts(
1512 blob.GetBufferPointer() as *const u8,
1513 blob.GetBufferSize(),
1514 )
1515 };
1516 Ok(Self { inner, _blob: blob })
1517 }
1518 }
1519
1520 pub(crate) fn as_bytes(&'t self) -> &'t [u8] {
1521 self.inner
1522 }
1523
1524 #[cfg(not(debug_assertions))]
1525 fn from_bytes(module: ShaderModule, target: ShaderTarget) -> Self {
1526 let bytes = match module {
1527 ShaderModule::Quad => match target {
1528 ShaderTarget::Vertex => QUAD_VERTEX_BYTES,
1529 ShaderTarget::Fragment => QUAD_FRAGMENT_BYTES,
1530 },
1531 ShaderModule::Shadow => match target {
1532 ShaderTarget::Vertex => SHADOW_VERTEX_BYTES,
1533 ShaderTarget::Fragment => SHADOW_FRAGMENT_BYTES,
1534 },
1535 ShaderModule::Underline => match target {
1536 ShaderTarget::Vertex => UNDERLINE_VERTEX_BYTES,
1537 ShaderTarget::Fragment => UNDERLINE_FRAGMENT_BYTES,
1538 },
1539 ShaderModule::PathRasterization => match target {
1540 ShaderTarget::Vertex => PATH_RASTERIZATION_VERTEX_BYTES,
1541 ShaderTarget::Fragment => PATH_RASTERIZATION_FRAGMENT_BYTES,
1542 },
1543 ShaderModule::PathSprite => match target {
1544 ShaderTarget::Vertex => PATH_SPRITE_VERTEX_BYTES,
1545 ShaderTarget::Fragment => PATH_SPRITE_FRAGMENT_BYTES,
1546 },
1547 ShaderModule::MonochromeSprite => match target {
1548 ShaderTarget::Vertex => MONOCHROME_SPRITE_VERTEX_BYTES,
1549 ShaderTarget::Fragment => MONOCHROME_SPRITE_FRAGMENT_BYTES,
1550 },
1551 ShaderModule::SubpixelSprite => match target {
1552 ShaderTarget::Vertex => SUBPIXEL_SPRITE_VERTEX_BYTES,
1553 ShaderTarget::Fragment => SUBPIXEL_SPRITE_FRAGMENT_BYTES,
1554 },
1555 ShaderModule::PolychromeSprite => match target {
1556 ShaderTarget::Vertex => POLYCHROME_SPRITE_VERTEX_BYTES,
1557 ShaderTarget::Fragment => POLYCHROME_SPRITE_FRAGMENT_BYTES,
1558 },
1559 ShaderModule::EmojiRasterization => match target {
1560 ShaderTarget::Vertex => EMOJI_RASTERIZATION_VERTEX_BYTES,
1561 ShaderTarget::Fragment => EMOJI_RASTERIZATION_FRAGMENT_BYTES,
1562 },
1563 };
1564 Self { inner: bytes }
1565 }
1566 }
1567
1568 #[cfg(debug_assertions)]
1569 pub(super) fn build_shader_blob(entry: ShaderModule, target: ShaderTarget) -> Result<ID3DBlob> {
1570 unsafe {
1571 use windows::Win32::Graphics::{
1572 Direct3D::ID3DInclude, Hlsl::D3D_COMPILE_STANDARD_FILE_INCLUDE,
1573 };
1574
1575 let shader_name = if matches!(entry, ShaderModule::EmojiRasterization) {
1576 "color_text_raster.hlsl"
1577 } else {
1578 "shaders.hlsl"
1579 };
1580
1581 let entry = format!(
1582 "{}_{}\0",
1583 entry.as_str(),
1584 match target {
1585 ShaderTarget::Vertex => "vertex",
1586 ShaderTarget::Fragment => "fragment",
1587 }
1588 );
1589 let target = match target {
1590 ShaderTarget::Vertex => "vs_4_1\0",
1591 ShaderTarget::Fragment => "ps_4_1\0",
1592 };
1593
1594 let mut compile_blob = None;
1595 let mut error_blob = None;
1596 let shader_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
1597 .join(&format!("src/platform/windows/{}", shader_name))
1598 .canonicalize()?;
1599
1600 let entry_point = PCSTR::from_raw(entry.as_ptr());
1601 let target_cstr = PCSTR::from_raw(target.as_ptr());
1602
1603 // really dirty trick because winapi bindings are unhappy otherwise
1604 let include_handler = &std::mem::transmute::<usize, ID3DInclude>(
1605 D3D_COMPILE_STANDARD_FILE_INCLUDE as usize,
1606 );
1607
1608 let ret = D3DCompileFromFile(
1609 &HSTRING::from(shader_path.to_str().unwrap()),
1610 None,
1611 include_handler,
1612 entry_point,
1613 target_cstr,
1614 D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION,
1615 0,
1616 &mut compile_blob,
1617 Some(&mut error_blob),
1618 );
1619 if ret.is_err() {
1620 let Some(error_blob) = error_blob else {
1621 return Err(anyhow::anyhow!("{ret:?}"));
1622 };
1623
1624 let error_string =
1625 std::ffi::CStr::from_ptr(error_blob.GetBufferPointer() as *const i8)
1626 .to_string_lossy();
1627 log::error!("Shader compile error: {}", error_string);
1628 return Err(anyhow::anyhow!("Compile error: {}", error_string));
1629 }
1630 Ok(compile_blob.unwrap())
1631 }
1632 }
1633
1634 #[cfg(not(debug_assertions))]
1635 include!(concat!(env!("OUT_DIR"), "/shaders_bytes.rs"));
1636
1637 #[cfg(debug_assertions)]
1638 impl ShaderModule {
1639 pub fn as_str(self) -> &'static str {
1640 match self {
1641 ShaderModule::Quad => "quad",
1642 ShaderModule::Shadow => "shadow",
1643 ShaderModule::Underline => "underline",
1644 ShaderModule::PathRasterization => "path_rasterization",
1645 ShaderModule::PathSprite => "path_sprite",
1646 ShaderModule::MonochromeSprite => "monochrome_sprite",
1647 ShaderModule::SubpixelSprite => "subpixel_sprite",
1648 ShaderModule::PolychromeSprite => "polychrome_sprite",
1649 ShaderModule::EmojiRasterization => "emoji_rasterization",
1650 }
1651 }
1652 }
1653}
1654
1655mod nvidia {
1656 use std::{
1657 ffi::CStr,
1658 os::raw::{c_char, c_int, c_uint},
1659 };
1660
1661 use anyhow::Result;
1662 use windows::{Win32::System::LibraryLoader::GetProcAddress, core::s};
1663
1664 use crate::with_dll_library;
1665
1666 // https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_lite_common.h#L180
1667 const NVAPI_SHORT_STRING_MAX: usize = 64;
1668
1669 // https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_lite_common.h#L235
1670 #[allow(non_camel_case_types)]
1671 type NvAPI_ShortString = [c_char; NVAPI_SHORT_STRING_MAX];
1672
1673 // https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_lite_common.h#L447
1674 #[allow(non_camel_case_types)]
1675 type NvAPI_SYS_GetDriverAndBranchVersion_t = unsafe extern "C" fn(
1676 driver_version: *mut c_uint,
1677 build_branch_string: *mut NvAPI_ShortString,
1678 ) -> c_int;
1679
1680 pub(super) fn get_driver_version() -> Result<String> {
1681 #[cfg(target_pointer_width = "64")]
1682 let nvidia_dll_name = s!("nvapi64.dll");
1683 #[cfg(target_pointer_width = "32")]
1684 let nvidia_dll_name = s!("nvapi.dll");
1685
1686 with_dll_library(nvidia_dll_name, |nvidia_dll| unsafe {
1687 let nvapi_query_addr = GetProcAddress(nvidia_dll, s!("nvapi_QueryInterface"))
1688 .ok_or_else(|| anyhow::anyhow!("Failed to get nvapi_QueryInterface address"))?;
1689 let nvapi_query: extern "C" fn(u32) -> *mut () = std::mem::transmute(nvapi_query_addr);
1690
1691 // https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_interface.h#L41
1692 let nvapi_get_driver_version_ptr = nvapi_query(0x2926aaad);
1693 if nvapi_get_driver_version_ptr.is_null() {
1694 anyhow::bail!("Failed to get NVIDIA driver version function pointer");
1695 }
1696 let nvapi_get_driver_version: NvAPI_SYS_GetDriverAndBranchVersion_t =
1697 std::mem::transmute(nvapi_get_driver_version_ptr);
1698
1699 let mut driver_version: c_uint = 0;
1700 let mut build_branch_string: NvAPI_ShortString = [0; NVAPI_SHORT_STRING_MAX];
1701 let result = nvapi_get_driver_version(
1702 &mut driver_version as *mut c_uint,
1703 &mut build_branch_string as *mut NvAPI_ShortString,
1704 );
1705
1706 if result != 0 {
1707 anyhow::bail!(
1708 "Failed to get NVIDIA driver version, error code: {}",
1709 result
1710 );
1711 }
1712 let major = driver_version / 100;
1713 let minor = driver_version % 100;
1714 let branch_string = CStr::from_ptr(build_branch_string.as_ptr());
1715 Ok(format!(
1716 "{}.{} {}",
1717 major,
1718 minor,
1719 branch_string.to_string_lossy()
1720 ))
1721 })
1722 }
1723}
1724
1725mod amd {
1726 use std::os::raw::{c_char, c_int, c_void};
1727
1728 use anyhow::Result;
1729 use windows::{Win32::System::LibraryLoader::GetProcAddress, core::s};
1730
1731 use crate::with_dll_library;
1732
1733 // https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L145
1734 const AGS_CURRENT_VERSION: i32 = (6 << 22) | (3 << 12);
1735
1736 // https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L204
1737 // This is an opaque type, using struct to represent it properly for FFI
1738 #[repr(C)]
1739 struct AGSContext {
1740 _private: [u8; 0],
1741 }
1742
1743 #[repr(C)]
1744 pub struct AGSGPUInfo {
1745 pub driver_version: *const c_char,
1746 pub radeon_software_version: *const c_char,
1747 pub num_devices: c_int,
1748 pub devices: *mut c_void,
1749 }
1750
1751 // https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L429
1752 #[allow(non_camel_case_types)]
1753 type agsInitialize_t = unsafe extern "C" fn(
1754 version: c_int,
1755 config: *const c_void,
1756 context: *mut *mut AGSContext,
1757 gpu_info: *mut AGSGPUInfo,
1758 ) -> c_int;
1759
1760 // https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L436
1761 #[allow(non_camel_case_types)]
1762 type agsDeInitialize_t = unsafe extern "C" fn(context: *mut AGSContext) -> c_int;
1763
1764 pub(super) fn get_driver_version() -> Result<String> {
1765 #[cfg(target_pointer_width = "64")]
1766 let amd_dll_name = s!("amd_ags_x64.dll");
1767 #[cfg(target_pointer_width = "32")]
1768 let amd_dll_name = s!("amd_ags_x86.dll");
1769
1770 with_dll_library(amd_dll_name, |amd_dll| unsafe {
1771 let ags_initialize_addr = GetProcAddress(amd_dll, s!("agsInitialize"))
1772 .ok_or_else(|| anyhow::anyhow!("Failed to get agsInitialize address"))?;
1773 let ags_deinitialize_addr = GetProcAddress(amd_dll, s!("agsDeInitialize"))
1774 .ok_or_else(|| anyhow::anyhow!("Failed to get agsDeInitialize address"))?;
1775
1776 let ags_initialize: agsInitialize_t = std::mem::transmute(ags_initialize_addr);
1777 let ags_deinitialize: agsDeInitialize_t = std::mem::transmute(ags_deinitialize_addr);
1778
1779 let mut context: *mut AGSContext = std::ptr::null_mut();
1780 let mut gpu_info: AGSGPUInfo = AGSGPUInfo {
1781 driver_version: std::ptr::null(),
1782 radeon_software_version: std::ptr::null(),
1783 num_devices: 0,
1784 devices: std::ptr::null_mut(),
1785 };
1786
1787 let result = ags_initialize(
1788 AGS_CURRENT_VERSION,
1789 std::ptr::null(),
1790 &mut context,
1791 &mut gpu_info,
1792 );
1793 if result != 0 {
1794 anyhow::bail!("Failed to initialize AMD AGS, error code: {}", result);
1795 }
1796
1797 // Vulkan actually returns this as the driver version
1798 let software_version = if !gpu_info.radeon_software_version.is_null() {
1799 std::ffi::CStr::from_ptr(gpu_info.radeon_software_version)
1800 .to_string_lossy()
1801 .into_owned()
1802 } else {
1803 "Unknown Radeon Software Version".to_string()
1804 };
1805
1806 let driver_version = if !gpu_info.driver_version.is_null() {
1807 std::ffi::CStr::from_ptr(gpu_info.driver_version)
1808 .to_string_lossy()
1809 .into_owned()
1810 } else {
1811 "Unknown Radeon Driver Version".to_string()
1812 };
1813
1814 ags_deinitialize(context);
1815 Ok(format!("{} ({})", software_version, driver_version))
1816 })
1817 }
1818}
1819
1820mod dxgi {
1821 use windows::{
1822 Win32::Graphics::Dxgi::{IDXGIAdapter1, IDXGIDevice},
1823 core::Interface,
1824 };
1825
1826 pub(super) fn get_driver_version(adapter: &IDXGIAdapter1) -> anyhow::Result<String> {
1827 let number = unsafe { adapter.CheckInterfaceSupport(&IDXGIDevice::IID as _) }?;
1828 Ok(format!(
1829 "{}.{}.{}.{}",
1830 number >> 48,
1831 (number >> 32) & 0xFFFF,
1832 (number >> 16) & 0xFFFF,
1833 number & 0xFFFF
1834 ))
1835 }
1836}