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