directx_renderer.rs

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