directx_renderer.rs

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