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