directx_renderer.rs

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