Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T05:05:17.829Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

directx_renderer.rs

1986 lines · 69.2 KB · rust
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
Served at tenant.openagents/omega Member data and write actions are omitted.