directx_renderer.rs

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