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