directx_renderer.rs

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