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