directx_renderer.rs

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