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