directx_renderer.rs

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