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