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