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