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