directx_renderer.rs

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