directx_renderer.rs

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