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