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