directx_renderer.rs

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