directx_renderer.rs

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