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