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