directx_renderer.rs

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