use std::{ slice, sync::{Arc, OnceLock}, }; use ::util::ResultExt; use anyhow::{Context, Result}; use windows::{ Win32::{ Foundation::HWND, Graphics::{ Direct3D::*, Direct3D11::*, DirectComposition::*, DirectWrite::*, Dxgi::{Common::*, *}, }, }, core::Interface, }; use crate::directx_renderer::shader_resources::{RawShaderBytes, ShaderModule, ShaderTarget}; use crate::*; use gpui::*; pub(crate) const DISABLE_DIRECT_COMPOSITION: &str = "GPUI_DISABLE_DIRECT_COMPOSITION"; const RENDER_TARGET_FORMAT: DXGI_FORMAT = DXGI_FORMAT_B8G8R8A8_UNORM; // This configuration is used for MSAA rendering on paths only, and it's guaranteed to be supported by DirectX 11. const PATH_MULTISAMPLE_COUNT: u32 = 4; pub(crate) struct FontInfo { pub gamma_ratios: [f32; 4], pub grayscale_enhanced_contrast: f32, pub subpixel_enhanced_contrast: f32, } pub(crate) struct DirectXRenderer { hwnd: HWND, atlas: Arc, devices: Option, resources: Option, globals: DirectXGlobalElements, pipelines: DirectXRenderPipelines, direct_composition: Option, font_info: &'static FontInfo, width: u32, height: u32, /// Whether we want to skip drwaing due to device lost events. /// /// In that case we want to discard the first frame that we draw as we got reset in the middle of a frame /// meaning we lost all the allocated gpu textures and scene resources. skip_draws: bool, } /// Direct3D objects #[derive(Clone)] pub(crate) struct DirectXRendererDevices { pub(crate) adapter: IDXGIAdapter1, pub(crate) dxgi_factory: IDXGIFactory6, pub(crate) device: ID3D11Device, pub(crate) device_context: ID3D11DeviceContext, dxgi_device: Option, } struct DirectXResources { // Direct3D rendering objects swap_chain: IDXGISwapChain1, render_target: Option, render_target_view: Option, // Path intermediate textures (with MSAA) path_intermediate_texture: ID3D11Texture2D, path_intermediate_srv: Option, path_intermediate_msaa_texture: ID3D11Texture2D, path_intermediate_msaa_view: Option, // Cached viewport viewport: D3D11_VIEWPORT, } struct DirectXRenderPipelines { shadow_pipeline: PipelineState, quad_pipeline: PipelineState, path_rasterization_pipeline: PipelineState, path_sprite_pipeline: PipelineState, underline_pipeline: PipelineState, mono_sprites: PipelineState, subpixel_sprites: PipelineState, poly_sprites: PipelineState, } struct DirectXGlobalElements { global_params_buffer: Option, sampler: Option, } struct DirectComposition { comp_device: IDCompositionDevice, comp_target: IDCompositionTarget, comp_visual: IDCompositionVisual, } impl DirectXRendererDevices { pub(crate) fn new( directx_devices: &DirectXDevices, disable_direct_composition: bool, ) -> Result { let DirectXDevices { adapter, dxgi_factory, device, device_context, } = directx_devices; let dxgi_device = if disable_direct_composition { None } else { Some(device.cast().context("Creating DXGI device")?) }; Ok(Self { adapter: adapter.clone(), dxgi_factory: dxgi_factory.clone(), device: device.clone(), device_context: device_context.clone(), dxgi_device, }) } } impl DirectXRenderer { pub(crate) fn new( hwnd: HWND, directx_devices: &DirectXDevices, disable_direct_composition: bool, ) -> Result { if disable_direct_composition { log::info!("Direct Composition is disabled."); } let devices = DirectXRendererDevices::new(directx_devices, disable_direct_composition) .context("Creating DirectX devices")?; let atlas = Arc::new(DirectXAtlas::new(&devices.device, &devices.device_context)); let resources = DirectXResources::new(&devices, 1, 1, hwnd, disable_direct_composition) .context("Creating DirectX resources")?; let globals = DirectXGlobalElements::new(&devices.device) .context("Creating DirectX global elements")?; let pipelines = DirectXRenderPipelines::new(&devices.device) .context("Creating DirectX render pipelines")?; let direct_composition = if disable_direct_composition { None } else { let composition = DirectComposition::new(devices.dxgi_device.as_ref().unwrap(), hwnd) .context("Creating DirectComposition")?; composition .set_swap_chain(&resources.swap_chain) .context("Setting swap chain for DirectComposition")?; Some(composition) }; Ok(DirectXRenderer { hwnd, atlas, devices: Some(devices), resources: Some(resources), globals, pipelines, direct_composition, font_info: Self::get_font_info(), width: 1, height: 1, skip_draws: false, }) } pub(crate) fn sprite_atlas(&self) -> Arc { self.atlas.clone() } fn pre_draw(&self, clear_color: &[f32; 4]) -> Result<()> { let resources = self.resources.as_ref().expect("resources missing"); let device_context = &self .devices .as_ref() .expect("devices missing") .device_context; update_buffer( device_context, self.globals.global_params_buffer.as_ref().unwrap(), &[GlobalParams { gamma_ratios: self.font_info.gamma_ratios, viewport_size: [resources.viewport.Width, resources.viewport.Height], grayscale_enhanced_contrast: self.font_info.grayscale_enhanced_contrast, subpixel_enhanced_contrast: self.font_info.subpixel_enhanced_contrast, }], )?; unsafe { device_context.ClearRenderTargetView( resources .render_target_view .as_ref() .context("missing render target view")?, clear_color, ); device_context .OMSetRenderTargets(Some(slice::from_ref(&resources.render_target_view)), None); device_context.RSSetViewports(Some(slice::from_ref(&resources.viewport))); } Ok(()) } #[inline] fn present(&mut self) -> Result<()> { let result = unsafe { self.resources .as_ref() .expect("resources missing") .swap_chain .Present(0, DXGI_PRESENT(0)) }; result.ok().context("Presenting swap chain failed") } pub(crate) fn handle_device_lost(&mut self, directx_devices: &DirectXDevices) -> Result<()> { try_to_recover_from_device_lost(|| { self.handle_device_lost_impl(directx_devices) .context("DirectXRenderer handling device lost") }) } fn handle_device_lost_impl(&mut self, directx_devices: &DirectXDevices) -> Result<()> { let disable_direct_composition = self.direct_composition.is_none(); unsafe { #[cfg(debug_assertions)] if let Some(devices) = &self.devices { report_live_objects(&devices.device) .context("Failed to report live objects after device lost") .log_err(); } self.resources.take(); if let Some(devices) = &self.devices { devices.device_context.OMSetRenderTargets(None, None); devices.device_context.ClearState(); devices.device_context.Flush(); #[cfg(debug_assertions)] report_live_objects(&devices.device) .context("Failed to report live objects after device lost") .log_err(); } self.direct_composition.take(); self.devices.take(); } let devices = DirectXRendererDevices::new(directx_devices, disable_direct_composition) .context("Recreating DirectX devices")?; let resources = DirectXResources::new( &devices, self.width, self.height, self.hwnd, disable_direct_composition, ) .context("Creating DirectX resources")?; let globals = DirectXGlobalElements::new(&devices.device) .context("Creating DirectXGlobalElements")?; let pipelines = DirectXRenderPipelines::new(&devices.device) .context("Creating DirectXRenderPipelines")?; let direct_composition = if disable_direct_composition { None } else { let composition = DirectComposition::new(devices.dxgi_device.as_ref().unwrap(), self.hwnd)?; composition.set_swap_chain(&resources.swap_chain)?; Some(composition) }; self.atlas .handle_device_lost(&devices.device, &devices.device_context); unsafe { devices .device_context .OMSetRenderTargets(Some(slice::from_ref(&resources.render_target_view)), None); } self.devices = Some(devices); self.resources = Some(resources); self.globals = globals; self.pipelines = pipelines; self.direct_composition = direct_composition; self.skip_draws = true; Ok(()) } pub(crate) fn draw( &mut self, scene: &Scene, background_appearance: WindowBackgroundAppearance, ) -> Result<()> { if self.skip_draws { // skip drawing this frame, we just recovered from a device lost event // and so likely do not have the textures anymore that are required for drawing return Ok(()); } self.pre_draw(&match background_appearance { WindowBackgroundAppearance::Opaque => [1.0f32; 4], _ => [0.0f32; 4], })?; self.upload_scene_buffers(scene)?; for batch in scene.batches() { match batch { PrimitiveBatch::Shadows(range) => self.draw_shadows(range.start, range.len()), PrimitiveBatch::Quads(range) => self.draw_quads(range.start, range.len()), PrimitiveBatch::Paths(range) => { let paths = &scene.paths[range]; self.draw_paths_to_intermediate(paths)?; self.draw_paths_from_intermediate(paths) } PrimitiveBatch::Underlines(range) => self.draw_underlines(range.start, range.len()), PrimitiveBatch::MonochromeSprites { texture_id, range } => { self.draw_monochrome_sprites(texture_id, range.start, range.len()) } PrimitiveBatch::SubpixelSprites { texture_id, range } => { self.draw_subpixel_sprites(texture_id, range.start, range.len()) } PrimitiveBatch::PolychromeSprites { texture_id, range } => { self.draw_polychrome_sprites(texture_id, range.start, range.len()) } PrimitiveBatch::Surfaces(range) => self.draw_surfaces(&scene.surfaces[range]), } .context(format!( "scene too large:\ {} paths, {} shadows, {} quads, {} underlines, {} mono, {} subpixel, {} poly, {} surfaces", scene.paths.len(), scene.shadows.len(), scene.quads.len(), scene.underlines.len(), scene.monochrome_sprites.len(), scene.subpixel_sprites.len(), scene.polychrome_sprites.len(), scene.surfaces.len(), ))?; } self.present() } pub(crate) fn resize(&mut self, new_size: Size) -> Result<()> { let width = new_size.width.0.max(1) as u32; let height = new_size.height.0.max(1) as u32; if self.width == width && self.height == height { return Ok(()); } self.width = width; self.height = height; // Clear the render target before resizing let devices = self.devices.as_ref().context("devices missing")?; unsafe { devices.device_context.OMSetRenderTargets(None, None) }; let resources = self.resources.as_mut().context("resources missing")?; resources.render_target.take(); resources.render_target_view.take(); // Resizing the swap chain requires a call to the underlying DXGI adapter, which can return the device removed error. // The app might have moved to a monitor that's attached to a different graphics device. // When a graphics device is removed or reset, the desktop resolution often changes, resulting in a window size change. // But here we just return the error, because we are handling device lost scenarios elsewhere. unsafe { resources .swap_chain .ResizeBuffers( BUFFER_COUNT as u32, width, height, RENDER_TARGET_FORMAT, DXGI_SWAP_CHAIN_FLAG(0), ) .context("Failed to resize swap chain")?; } resources.recreate_resources(devices, width, height)?; unsafe { devices .device_context .OMSetRenderTargets(Some(slice::from_ref(&resources.render_target_view)), None); } Ok(()) } fn upload_scene_buffers(&mut self, scene: &Scene) -> Result<()> { let devices = self.devices.as_ref().context("devices missing")?; if !scene.shadows.is_empty() { self.pipelines.shadow_pipeline.update_buffer( &devices.device, &devices.device_context, &scene.shadows, )?; } if !scene.quads.is_empty() { self.pipelines.quad_pipeline.update_buffer( &devices.device, &devices.device_context, &scene.quads, )?; } if !scene.underlines.is_empty() { self.pipelines.underline_pipeline.update_buffer( &devices.device, &devices.device_context, &scene.underlines, )?; } if !scene.monochrome_sprites.is_empty() { self.pipelines.mono_sprites.update_buffer( &devices.device, &devices.device_context, &scene.monochrome_sprites, )?; } if !scene.subpixel_sprites.is_empty() { self.pipelines.subpixel_sprites.update_buffer( &devices.device, &devices.device_context, &scene.subpixel_sprites, )?; } if !scene.polychrome_sprites.is_empty() { self.pipelines.poly_sprites.update_buffer( &devices.device, &devices.device_context, &scene.polychrome_sprites, )?; } Ok(()) } fn draw_shadows(&mut self, start: usize, len: usize) -> Result<()> { if len == 0 { return Ok(()); } let devices = self.devices.as_ref().context("devices missing")?; self.pipelines.shadow_pipeline.draw_range( &devices.device, &devices.device_context, slice::from_ref( &self .resources .as_ref() .context("resources missing")? .viewport, ), slice::from_ref(&self.globals.global_params_buffer), 4, start as u32, len as u32, ) } fn draw_quads(&mut self, start: usize, len: usize) -> Result<()> { if len == 0 { return Ok(()); } let devices = self.devices.as_ref().context("devices missing")?; self.pipelines.quad_pipeline.draw_range( &devices.device, &devices.device_context, slice::from_ref( &self .resources .as_ref() .context("resources missing")? .viewport, ), slice::from_ref(&self.globals.global_params_buffer), 4, start as u32, len as u32, ) } fn draw_paths_to_intermediate(&mut self, paths: &[Path]) -> Result<()> { if paths.is_empty() { return Ok(()); } let devices = self.devices.as_ref().context("devices missing")?; let resources = self.resources.as_ref().context("resources missing")?; // Clear intermediate MSAA texture unsafe { devices.device_context.ClearRenderTargetView( resources.path_intermediate_msaa_view.as_ref().unwrap(), &[0.0; 4], ); // Set intermediate MSAA texture as render target devices.device_context.OMSetRenderTargets( Some(slice::from_ref(&resources.path_intermediate_msaa_view)), None, ); } // Collect all vertices and sprites for a single draw call let mut vertices = Vec::new(); for path in paths { vertices.extend(path.vertices.iter().map(|v| PathRasterizationSprite { xy_position: v.xy_position, st_position: v.st_position, color: path.color, bounds: path.clipped_bounds(), })); } self.pipelines.path_rasterization_pipeline.update_buffer( &devices.device, &devices.device_context, &vertices, )?; self.pipelines.path_rasterization_pipeline.draw( &devices.device_context, slice::from_ref(&resources.viewport), slice::from_ref(&self.globals.global_params_buffer), D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST, vertices.len() as u32, 1, )?; // Resolve MSAA to non-MSAA intermediate texture unsafe { devices.device_context.ResolveSubresource( &resources.path_intermediate_texture, 0, &resources.path_intermediate_msaa_texture, 0, RENDER_TARGET_FORMAT, ); // Restore main render target devices .device_context .OMSetRenderTargets(Some(slice::from_ref(&resources.render_target_view)), None); } Ok(()) } fn draw_paths_from_intermediate(&mut self, paths: &[Path]) -> Result<()> { let Some(first_path) = paths.first() else { return Ok(()); }; // When copying paths from the intermediate texture to the drawable, // each pixel must only be copied once, in case of transparent paths. // // If all paths have the same draw order, then their bounds are all // disjoint, so we can copy each path's bounds individually. If this // batch combines different draw orders, we perform a single copy // for a minimal spanning rect. let sprites = if paths.last().unwrap().order == first_path.order { paths .iter() .map(|path| PathSprite { bounds: path.clipped_bounds(), }) .collect::>() } else { let mut bounds = first_path.clipped_bounds(); for path in paths.iter().skip(1) { bounds = bounds.union(&path.clipped_bounds()); } vec![PathSprite { bounds }] }; let devices = self.devices.as_ref().context("devices missing")?; let resources = self.resources.as_ref().context("resources missing")?; self.pipelines.path_sprite_pipeline.update_buffer( &devices.device, &devices.device_context, &sprites, )?; // Draw the sprites with the path texture self.pipelines.path_sprite_pipeline.draw_with_texture( &devices.device_context, slice::from_ref(&resources.path_intermediate_srv), slice::from_ref(&resources.viewport), slice::from_ref(&self.globals.global_params_buffer), slice::from_ref(&self.globals.sampler), sprites.len() as u32, ) } fn draw_underlines(&mut self, start: usize, len: usize) -> Result<()> { if len == 0 { return Ok(()); } let devices = self.devices.as_ref().context("devices missing")?; let resources = self.resources.as_ref().context("resources missing")?; self.pipelines.underline_pipeline.draw_range( &devices.device, &devices.device_context, slice::from_ref(&resources.viewport), slice::from_ref(&self.globals.global_params_buffer), 4, start as u32, len as u32, ) } fn draw_monochrome_sprites( &mut self, texture_id: AtlasTextureId, start: usize, len: usize, ) -> Result<()> { if len == 0 { return Ok(()); } let devices = self.devices.as_ref().context("devices missing")?; let resources = self.resources.as_ref().context("resources missing")?; let texture_view = self.atlas.get_texture_view(texture_id); self.pipelines.mono_sprites.draw_range_with_texture( &devices.device, &devices.device_context, &texture_view, slice::from_ref(&resources.viewport), slice::from_ref(&self.globals.global_params_buffer), slice::from_ref(&self.globals.sampler), start as u32, len as u32, ) } fn draw_subpixel_sprites( &mut self, texture_id: AtlasTextureId, start: usize, len: usize, ) -> Result<()> { if len == 0 { return Ok(()); } let devices = self.devices.as_ref().context("devices missing")?; let resources = self.resources.as_ref().context("resources missing")?; let texture_view = self.atlas.get_texture_view(texture_id); self.pipelines.subpixel_sprites.draw_range_with_texture( &devices.device, &devices.device_context, &texture_view, slice::from_ref(&resources.viewport), slice::from_ref(&self.globals.global_params_buffer), slice::from_ref(&self.globals.sampler), start as u32, len as u32, ) } fn draw_polychrome_sprites( &mut self, texture_id: AtlasTextureId, start: usize, len: usize, ) -> Result<()> { if len == 0 { return Ok(()); } let devices = self.devices.as_ref().context("devices missing")?; let resources = self.resources.as_ref().context("resources missing")?; let texture_view = self.atlas.get_texture_view(texture_id); self.pipelines.poly_sprites.draw_range_with_texture( &devices.device, &devices.device_context, &texture_view, slice::from_ref(&resources.viewport), slice::from_ref(&self.globals.global_params_buffer), slice::from_ref(&self.globals.sampler), start as u32, len as u32, ) } fn draw_surfaces(&mut self, surfaces: &[PaintSurface]) -> Result<()> { if surfaces.is_empty() { return Ok(()); } Ok(()) } pub(crate) fn gpu_specs(&self) -> Result { let devices = self.devices.as_ref().context("devices missing")?; let desc = unsafe { devices.adapter.GetDesc1() }?; let is_software_emulated = (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE.0 as u32) != 0; let device_name = String::from_utf16_lossy(&desc.Description) .trim_matches(char::from(0)) .to_string(); let driver_name = match desc.VendorId { 0x10DE => "NVIDIA Corporation".to_string(), 0x1002 => "AMD Corporation".to_string(), 0x8086 => "Intel Corporation".to_string(), id => format!("Unknown Vendor (ID: {:#X})", id), }; let driver_version = match desc.VendorId { 0x10DE => nvidia::get_driver_version(), 0x1002 => amd::get_driver_version(), // For Intel and other vendors, we use the DXGI API to get the driver version. _ => dxgi::get_driver_version(&devices.adapter), } .context("Failed to get gpu driver info") .log_err() .unwrap_or("Unknown Driver".to_string()); Ok(GpuSpecs { is_software_emulated, device_name, driver_name, driver_info: driver_version, }) } pub(crate) fn get_font_info() -> &'static FontInfo { static CACHED_FONT_INFO: OnceLock = OnceLock::new(); CACHED_FONT_INFO.get_or_init(|| unsafe { let factory: IDWriteFactory5 = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED).unwrap(); let render_params: IDWriteRenderingParams1 = factory.CreateRenderingParams().unwrap().cast().unwrap(); FontInfo { gamma_ratios: gpui::get_gamma_correction_ratios(render_params.GetGamma()), grayscale_enhanced_contrast: render_params.GetGrayscaleEnhancedContrast(), subpixel_enhanced_contrast: render_params.GetEnhancedContrast(), } }) } pub(crate) fn mark_drawable(&mut self) { self.skip_draws = false; } } impl DirectXResources { pub fn new( devices: &DirectXRendererDevices, width: u32, height: u32, hwnd: HWND, disable_direct_composition: bool, ) -> Result { let swap_chain = if disable_direct_composition { create_swap_chain(&devices.dxgi_factory, &devices.device, hwnd, width, height)? } else { create_swap_chain_for_composition( &devices.dxgi_factory, &devices.device, width, height, )? }; let ( render_target, render_target_view, path_intermediate_texture, path_intermediate_srv, path_intermediate_msaa_texture, path_intermediate_msaa_view, viewport, ) = create_resources(devices, &swap_chain, width, height)?; set_rasterizer_state(&devices.device, &devices.device_context)?; Ok(Self { swap_chain, render_target: Some(render_target), render_target_view, path_intermediate_texture, path_intermediate_msaa_texture, path_intermediate_msaa_view, path_intermediate_srv, viewport, }) } #[inline] fn recreate_resources( &mut self, devices: &DirectXRendererDevices, width: u32, height: u32, ) -> Result<()> { let ( render_target, render_target_view, path_intermediate_texture, path_intermediate_srv, path_intermediate_msaa_texture, path_intermediate_msaa_view, viewport, ) = create_resources(devices, &self.swap_chain, width, height)?; self.render_target = Some(render_target); self.render_target_view = render_target_view; self.path_intermediate_texture = path_intermediate_texture; self.path_intermediate_msaa_texture = path_intermediate_msaa_texture; self.path_intermediate_msaa_view = path_intermediate_msaa_view; self.path_intermediate_srv = path_intermediate_srv; self.viewport = viewport; Ok(()) } } impl DirectXRenderPipelines { pub fn new(device: &ID3D11Device) -> Result { let shadow_pipeline = PipelineState::new( device, "shadow_pipeline", ShaderModule::Shadow, 4, create_blend_state(device)?, )?; let quad_pipeline = PipelineState::new( device, "quad_pipeline", ShaderModule::Quad, 64, create_blend_state(device)?, )?; let path_rasterization_pipeline = PipelineState::new( device, "path_rasterization_pipeline", ShaderModule::PathRasterization, 32, create_blend_state_for_path_rasterization(device)?, )?; let path_sprite_pipeline = PipelineState::new( device, "path_sprite_pipeline", ShaderModule::PathSprite, 4, create_blend_state_for_path_sprite(device)?, )?; let underline_pipeline = PipelineState::new( device, "underline_pipeline", ShaderModule::Underline, 4, create_blend_state(device)?, )?; let mono_sprites = PipelineState::new( device, "monochrome_sprite_pipeline", ShaderModule::MonochromeSprite, 512, create_blend_state(device)?, )?; let subpixel_sprites = PipelineState::new( device, "subpixel_sprite_pipeline", ShaderModule::SubpixelSprite, 512, create_blend_state_for_subpixel_rendering(device)?, )?; let poly_sprites = PipelineState::new( device, "polychrome_sprite_pipeline", ShaderModule::PolychromeSprite, 16, create_blend_state(device)?, )?; Ok(Self { shadow_pipeline, quad_pipeline, path_rasterization_pipeline, path_sprite_pipeline, underline_pipeline, mono_sprites, subpixel_sprites, poly_sprites, }) } } impl DirectComposition { pub fn new(dxgi_device: &IDXGIDevice, hwnd: HWND) -> Result { let comp_device = get_comp_device(dxgi_device)?; let comp_target = unsafe { comp_device.CreateTargetForHwnd(hwnd, true) }?; let comp_visual = unsafe { comp_device.CreateVisual() }?; Ok(Self { comp_device, comp_target, comp_visual, }) } pub fn set_swap_chain(&self, swap_chain: &IDXGISwapChain1) -> Result<()> { unsafe { self.comp_visual.SetContent(swap_chain)?; self.comp_target.SetRoot(&self.comp_visual)?; self.comp_device.Commit()?; } Ok(()) } } impl DirectXGlobalElements { pub fn new(device: &ID3D11Device) -> Result { let global_params_buffer = unsafe { let desc = D3D11_BUFFER_DESC { ByteWidth: std::mem::size_of::() as u32, Usage: D3D11_USAGE_DYNAMIC, BindFlags: D3D11_BIND_CONSTANT_BUFFER.0 as u32, CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32, ..Default::default() }; let mut buffer = None; device.CreateBuffer(&desc, None, Some(&mut buffer))?; buffer }; let sampler = unsafe { let desc = D3D11_SAMPLER_DESC { Filter: D3D11_FILTER_MIN_MAG_MIP_LINEAR, AddressU: D3D11_TEXTURE_ADDRESS_WRAP, AddressV: D3D11_TEXTURE_ADDRESS_WRAP, AddressW: D3D11_TEXTURE_ADDRESS_WRAP, MipLODBias: 0.0, MaxAnisotropy: 1, ComparisonFunc: D3D11_COMPARISON_ALWAYS, BorderColor: [0.0; 4], MinLOD: 0.0, MaxLOD: D3D11_FLOAT32_MAX, }; let mut output = None; device.CreateSamplerState(&desc, Some(&mut output))?; output }; Ok(Self { global_params_buffer, sampler, }) } } #[derive(Debug, Default)] #[repr(C)] struct GlobalParams { gamma_ratios: [f32; 4], viewport_size: [f32; 2], grayscale_enhanced_contrast: f32, subpixel_enhanced_contrast: f32, } struct PipelineState { label: &'static str, vertex: ID3D11VertexShader, fragment: ID3D11PixelShader, buffer: ID3D11Buffer, buffer_size: usize, view: Option, blend_state: ID3D11BlendState, _marker: std::marker::PhantomData, } impl PipelineState { fn new( device: &ID3D11Device, label: &'static str, shader_module: ShaderModule, buffer_size: usize, blend_state: ID3D11BlendState, ) -> Result { let vertex = { let raw_shader = RawShaderBytes::new(shader_module, ShaderTarget::Vertex)?; create_vertex_shader(device, raw_shader.as_bytes())? }; let fragment = { let raw_shader = RawShaderBytes::new(shader_module, ShaderTarget::Fragment)?; create_fragment_shader(device, raw_shader.as_bytes())? }; let buffer = create_buffer(device, std::mem::size_of::(), buffer_size)?; let view = create_buffer_view(device, &buffer)?; Ok(PipelineState { label, vertex, fragment, buffer, buffer_size, view, blend_state, _marker: std::marker::PhantomData, }) } fn update_buffer( &mut self, device: &ID3D11Device, device_context: &ID3D11DeviceContext, data: &[T], ) -> Result<()> { if self.buffer_size < data.len() { let new_buffer_size = data.len().next_power_of_two(); log::debug!( "Updating {} buffer size from {} to {}", self.label, self.buffer_size, new_buffer_size ); let buffer = create_buffer(device, std::mem::size_of::(), new_buffer_size)?; let view = create_buffer_view(device, &buffer)?; self.buffer = buffer; self.view = view; self.buffer_size = new_buffer_size; } update_buffer(device_context, &self.buffer, data) } fn draw( &self, device_context: &ID3D11DeviceContext, viewport: &[D3D11_VIEWPORT], global_params: &[Option], topology: D3D_PRIMITIVE_TOPOLOGY, vertex_count: u32, instance_count: u32, ) -> Result<()> { set_pipeline_state( device_context, slice::from_ref(&self.view), topology, viewport, &self.vertex, &self.fragment, global_params, &self.blend_state, ); unsafe { device_context.DrawInstanced(vertex_count, instance_count, 0, 0); } Ok(()) } fn draw_with_texture( &self, device_context: &ID3D11DeviceContext, texture: &[Option], viewport: &[D3D11_VIEWPORT], global_params: &[Option], sampler: &[Option], instance_count: u32, ) -> Result<()> { set_pipeline_state( device_context, slice::from_ref(&self.view), D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP, viewport, &self.vertex, &self.fragment, global_params, &self.blend_state, ); unsafe { device_context.PSSetSamplers(0, Some(sampler)); device_context.VSSetShaderResources(0, Some(texture)); device_context.PSSetShaderResources(0, Some(texture)); device_context.DrawInstanced(4, instance_count, 0, 0); } Ok(()) } fn draw_range( &self, device: &ID3D11Device, device_context: &ID3D11DeviceContext, viewport: &[D3D11_VIEWPORT], global_params: &[Option], vertex_count: u32, first_instance: u32, instance_count: u32, ) -> Result<()> { let view = create_buffer_view_range(device, &self.buffer, first_instance, instance_count)?; set_pipeline_state( device_context, slice::from_ref(&view), D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP, viewport, &self.vertex, &self.fragment, global_params, &self.blend_state, ); unsafe { device_context.DrawInstanced(vertex_count, instance_count, 0, 0); } Ok(()) } fn draw_range_with_texture( &self, device: &ID3D11Device, device_context: &ID3D11DeviceContext, texture: &[Option], viewport: &[D3D11_VIEWPORT], global_params: &[Option], sampler: &[Option], first_instance: u32, instance_count: u32, ) -> Result<()> { let view = create_buffer_view_range(device, &self.buffer, first_instance, instance_count)?; set_pipeline_state( device_context, slice::from_ref(&view), D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP, viewport, &self.vertex, &self.fragment, global_params, &self.blend_state, ); unsafe { device_context.PSSetSamplers(0, Some(sampler)); device_context.VSSetShaderResources(0, Some(texture)); device_context.PSSetShaderResources(0, Some(texture)); device_context.DrawInstanced(4, instance_count, 0, 0); } Ok(()) } } #[derive(Clone, Copy)] #[repr(C)] struct PathRasterizationSprite { xy_position: Point, st_position: Point, color: Background, bounds: Bounds, } #[derive(Clone, Copy)] #[repr(C)] struct PathSprite { bounds: Bounds, } impl Drop for DirectXRenderer { fn drop(&mut self) { #[cfg(debug_assertions)] if let Some(devices) = &self.devices { report_live_objects(&devices.device).ok(); } } } #[inline] fn get_comp_device(dxgi_device: &IDXGIDevice) -> Result { Ok(unsafe { DCompositionCreateDevice(dxgi_device)? }) } fn create_swap_chain_for_composition( dxgi_factory: &IDXGIFactory6, device: &ID3D11Device, width: u32, height: u32, ) -> Result { let desc = DXGI_SWAP_CHAIN_DESC1 { Width: width, Height: height, Format: RENDER_TARGET_FORMAT, Stereo: false.into(), SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0, }, BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT, BufferCount: BUFFER_COUNT as u32, // Composition SwapChains only support the DXGI_SCALING_STRETCH Scaling. Scaling: DXGI_SCALING_STRETCH, SwapEffect: DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, AlphaMode: DXGI_ALPHA_MODE_PREMULTIPLIED, Flags: 0, }; Ok(unsafe { dxgi_factory.CreateSwapChainForComposition(device, &desc, None)? }) } fn create_swap_chain( dxgi_factory: &IDXGIFactory6, device: &ID3D11Device, hwnd: HWND, width: u32, height: u32, ) -> Result { use windows::Win32::Graphics::Dxgi::DXGI_MWA_NO_ALT_ENTER; let desc = DXGI_SWAP_CHAIN_DESC1 { Width: width, Height: height, Format: RENDER_TARGET_FORMAT, Stereo: false.into(), SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0, }, BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT, BufferCount: BUFFER_COUNT as u32, Scaling: DXGI_SCALING_NONE, SwapEffect: DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, AlphaMode: DXGI_ALPHA_MODE_IGNORE, Flags: 0, }; let swap_chain = unsafe { dxgi_factory.CreateSwapChainForHwnd(device, hwnd, &desc, None, None) }?; unsafe { dxgi_factory.MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER) }?; Ok(swap_chain) } #[inline] fn create_resources( devices: &DirectXRendererDevices, swap_chain: &IDXGISwapChain1, width: u32, height: u32, ) -> Result<( ID3D11Texture2D, Option, ID3D11Texture2D, Option, ID3D11Texture2D, Option, D3D11_VIEWPORT, )> { let (render_target, render_target_view) = create_render_target_and_its_view(swap_chain, &devices.device)?; let (path_intermediate_texture, path_intermediate_srv) = create_path_intermediate_texture(&devices.device, width, height)?; let (path_intermediate_msaa_texture, path_intermediate_msaa_view) = create_path_intermediate_msaa_texture_and_view(&devices.device, width, height)?; let viewport = set_viewport(&devices.device_context, width as f32, height as f32); Ok(( render_target, render_target_view, path_intermediate_texture, path_intermediate_srv, path_intermediate_msaa_texture, path_intermediate_msaa_view, viewport, )) } #[inline] fn create_render_target_and_its_view( swap_chain: &IDXGISwapChain1, device: &ID3D11Device, ) -> Result<(ID3D11Texture2D, Option)> { let render_target: ID3D11Texture2D = unsafe { swap_chain.GetBuffer(0) }?; let mut render_target_view = None; unsafe { device.CreateRenderTargetView(&render_target, None, Some(&mut render_target_view))? }; Ok((render_target, render_target_view)) } #[inline] fn create_path_intermediate_texture( device: &ID3D11Device, width: u32, height: u32, ) -> Result<(ID3D11Texture2D, Option)> { let texture = unsafe { let mut output = None; let desc = D3D11_TEXTURE2D_DESC { Width: width, Height: height, MipLevels: 1, ArraySize: 1, Format: RENDER_TARGET_FORMAT, SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0, }, Usage: D3D11_USAGE_DEFAULT, BindFlags: (D3D11_BIND_RENDER_TARGET.0 | D3D11_BIND_SHADER_RESOURCE.0) as u32, CPUAccessFlags: 0, MiscFlags: 0, }; device.CreateTexture2D(&desc, None, Some(&mut output))?; output.unwrap() }; let mut shader_resource_view = None; unsafe { device.CreateShaderResourceView(&texture, None, Some(&mut shader_resource_view))? }; Ok((texture, Some(shader_resource_view.unwrap()))) } #[inline] fn create_path_intermediate_msaa_texture_and_view( device: &ID3D11Device, width: u32, height: u32, ) -> Result<(ID3D11Texture2D, Option)> { let msaa_texture = unsafe { let mut output = None; let desc = D3D11_TEXTURE2D_DESC { Width: width, Height: height, MipLevels: 1, ArraySize: 1, Format: RENDER_TARGET_FORMAT, SampleDesc: DXGI_SAMPLE_DESC { Count: PATH_MULTISAMPLE_COUNT, Quality: D3D11_STANDARD_MULTISAMPLE_PATTERN.0 as u32, }, Usage: D3D11_USAGE_DEFAULT, BindFlags: D3D11_BIND_RENDER_TARGET.0 as u32, CPUAccessFlags: 0, MiscFlags: 0, }; device.CreateTexture2D(&desc, None, Some(&mut output))?; output.unwrap() }; let mut msaa_view = None; unsafe { device.CreateRenderTargetView(&msaa_texture, None, Some(&mut msaa_view))? }; Ok((msaa_texture, Some(msaa_view.unwrap()))) } #[inline] fn set_viewport(device_context: &ID3D11DeviceContext, width: f32, height: f32) -> D3D11_VIEWPORT { let viewport = [D3D11_VIEWPORT { TopLeftX: 0.0, TopLeftY: 0.0, Width: width, Height: height, MinDepth: 0.0, MaxDepth: 1.0, }]; unsafe { device_context.RSSetViewports(Some(&viewport)) }; viewport[0] } #[inline] fn set_rasterizer_state(device: &ID3D11Device, device_context: &ID3D11DeviceContext) -> Result<()> { let desc = D3D11_RASTERIZER_DESC { FillMode: D3D11_FILL_SOLID, CullMode: D3D11_CULL_NONE, FrontCounterClockwise: false.into(), DepthBias: 0, DepthBiasClamp: 0.0, SlopeScaledDepthBias: 0.0, DepthClipEnable: true.into(), ScissorEnable: false.into(), MultisampleEnable: true.into(), AntialiasedLineEnable: false.into(), }; let rasterizer_state = unsafe { let mut state = None; device.CreateRasterizerState(&desc, Some(&mut state))?; state.unwrap() }; unsafe { device_context.RSSetState(&rasterizer_state) }; Ok(()) } // https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ns-d3d11-d3d11_blend_desc #[inline] fn create_blend_state(device: &ID3D11Device) -> Result { let mut desc = D3D11_BLEND_DESC::default(); desc.RenderTarget[0].BlendEnable = true.into(); desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ONE; desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8; unsafe { let mut state = None; device.CreateBlendState(&desc, Some(&mut state))?; Ok(state.unwrap()) } } #[inline] fn create_blend_state_for_subpixel_rendering(device: &ID3D11Device) -> Result { let mut desc = D3D11_BLEND_DESC::default(); desc.RenderTarget[0].BlendEnable = true.into(); desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC1_COLOR; desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC1_COLOR; // It does not make sense to draw transparent subpixel-rendered text, since it cannot be meaningfully alpha-blended onto anything else. desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8 & !D3D11_COLOR_WRITE_ENABLE_ALPHA.0 as u8; unsafe { let mut state = None; device.CreateBlendState(&desc, Some(&mut state))?; Ok(state.unwrap()) } } #[inline] fn create_blend_state_for_path_rasterization(device: &ID3D11Device) -> Result { // If the feature level is set to greater than D3D_FEATURE_LEVEL_9_3, the display // device performs the blend in linear space, which is ideal. let mut desc = D3D11_BLEND_DESC::default(); desc.RenderTarget[0].BlendEnable = true.into(); desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE; desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8; unsafe { let mut state = None; device.CreateBlendState(&desc, Some(&mut state))?; Ok(state.unwrap()) } } #[inline] fn create_blend_state_for_path_sprite(device: &ID3D11Device) -> Result { // If the feature level is set to greater than D3D_FEATURE_LEVEL_9_3, the display // device performs the blend in linear space, which is ideal. let mut desc = D3D11_BLEND_DESC::default(); desc.RenderTarget[0].BlendEnable = true.into(); desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE; desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ONE; desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8; unsafe { let mut state = None; device.CreateBlendState(&desc, Some(&mut state))?; Ok(state.unwrap()) } } #[inline] fn create_vertex_shader(device: &ID3D11Device, bytes: &[u8]) -> Result { unsafe { let mut shader = None; device.CreateVertexShader(bytes, None, Some(&mut shader))?; Ok(shader.unwrap()) } } #[inline] fn create_fragment_shader(device: &ID3D11Device, bytes: &[u8]) -> Result { unsafe { let mut shader = None; device.CreatePixelShader(bytes, None, Some(&mut shader))?; Ok(shader.unwrap()) } } #[inline] fn create_buffer( device: &ID3D11Device, element_size: usize, buffer_size: usize, ) -> Result { let desc = D3D11_BUFFER_DESC { ByteWidth: (element_size * buffer_size) as u32, Usage: D3D11_USAGE_DYNAMIC, BindFlags: D3D11_BIND_SHADER_RESOURCE.0 as u32, CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32, MiscFlags: D3D11_RESOURCE_MISC_BUFFER_STRUCTURED.0 as u32, StructureByteStride: element_size as u32, }; let mut buffer = None; unsafe { device.CreateBuffer(&desc, None, Some(&mut buffer)) }?; Ok(buffer.unwrap()) } #[inline] fn create_buffer_view( device: &ID3D11Device, buffer: &ID3D11Buffer, ) -> Result> { let mut view = None; unsafe { device.CreateShaderResourceView(buffer, None, Some(&mut view)) }?; Ok(view) } #[inline] fn create_buffer_view_range( device: &ID3D11Device, buffer: &ID3D11Buffer, first_element: u32, num_elements: u32, ) -> Result> { let desc = D3D11_SHADER_RESOURCE_VIEW_DESC { Format: DXGI_FORMAT_UNKNOWN, ViewDimension: D3D11_SRV_DIMENSION_BUFFER, Anonymous: D3D11_SHADER_RESOURCE_VIEW_DESC_0 { Buffer: D3D11_BUFFER_SRV { Anonymous1: D3D11_BUFFER_SRV_0 { FirstElement: first_element, }, Anonymous2: D3D11_BUFFER_SRV_1 { NumElements: num_elements, }, }, }, }; let mut view = None; unsafe { device.CreateShaderResourceView(buffer, Some(&desc), Some(&mut view)) }?; Ok(view) } #[inline] fn update_buffer( device_context: &ID3D11DeviceContext, buffer: &ID3D11Buffer, data: &[T], ) -> Result<()> { unsafe { let mut dest = std::mem::zeroed(); device_context.Map(buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, Some(&mut dest))?; std::ptr::copy_nonoverlapping(data.as_ptr(), dest.pData as _, data.len()); device_context.Unmap(buffer, 0); } Ok(()) } #[inline] fn set_pipeline_state( device_context: &ID3D11DeviceContext, buffer_view: &[Option], topology: D3D_PRIMITIVE_TOPOLOGY, viewport: &[D3D11_VIEWPORT], vertex_shader: &ID3D11VertexShader, fragment_shader: &ID3D11PixelShader, global_params: &[Option], blend_state: &ID3D11BlendState, ) { unsafe { device_context.VSSetShaderResources(1, Some(buffer_view)); device_context.PSSetShaderResources(1, Some(buffer_view)); device_context.IASetPrimitiveTopology(topology); device_context.RSSetViewports(Some(viewport)); device_context.VSSetShader(vertex_shader, None); device_context.PSSetShader(fragment_shader, None); device_context.VSSetConstantBuffers(0, Some(global_params)); device_context.PSSetConstantBuffers(0, Some(global_params)); device_context.OMSetBlendState(blend_state, None, 0xFFFFFFFF); } } #[cfg(debug_assertions)] fn report_live_objects(device: &ID3D11Device) -> Result<()> { let debug_device: ID3D11Debug = device.cast()?; unsafe { debug_device.ReportLiveDeviceObjects(D3D11_RLDO_DETAIL)?; } Ok(()) } const BUFFER_COUNT: usize = 3; pub(crate) mod shader_resources { use anyhow::Result; #[cfg(debug_assertions)] use windows::{ Win32::Graphics::Direct3D::{ Fxc::{D3DCOMPILE_DEBUG, D3DCOMPILE_SKIP_OPTIMIZATION, D3DCompileFromFile}, ID3DBlob, }, core::{HSTRING, PCSTR}, }; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub(crate) enum ShaderModule { Quad, Shadow, Underline, PathRasterization, PathSprite, MonochromeSprite, SubpixelSprite, PolychromeSprite, EmojiRasterization, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub(crate) enum ShaderTarget { Vertex, Fragment, } pub(crate) struct RawShaderBytes<'t> { inner: &'t [u8], #[cfg(debug_assertions)] _blob: ID3DBlob, } impl<'t> RawShaderBytes<'t> { pub(crate) fn new(module: ShaderModule, target: ShaderTarget) -> Result { #[cfg(not(debug_assertions))] { Ok(Self::from_bytes(module, target)) } #[cfg(debug_assertions)] { let blob = build_shader_blob(module, target)?; let inner = unsafe { std::slice::from_raw_parts( blob.GetBufferPointer() as *const u8, blob.GetBufferSize(), ) }; Ok(Self { inner, _blob: blob }) } } pub(crate) fn as_bytes(&'t self) -> &'t [u8] { self.inner } #[cfg(not(debug_assertions))] fn from_bytes(module: ShaderModule, target: ShaderTarget) -> Self { let bytes = match module { ShaderModule::Quad => match target { ShaderTarget::Vertex => QUAD_VERTEX_BYTES, ShaderTarget::Fragment => QUAD_FRAGMENT_BYTES, }, ShaderModule::Shadow => match target { ShaderTarget::Vertex => SHADOW_VERTEX_BYTES, ShaderTarget::Fragment => SHADOW_FRAGMENT_BYTES, }, ShaderModule::Underline => match target { ShaderTarget::Vertex => UNDERLINE_VERTEX_BYTES, ShaderTarget::Fragment => UNDERLINE_FRAGMENT_BYTES, }, ShaderModule::PathRasterization => match target { ShaderTarget::Vertex => PATH_RASTERIZATION_VERTEX_BYTES, ShaderTarget::Fragment => PATH_RASTERIZATION_FRAGMENT_BYTES, }, ShaderModule::PathSprite => match target { ShaderTarget::Vertex => PATH_SPRITE_VERTEX_BYTES, ShaderTarget::Fragment => PATH_SPRITE_FRAGMENT_BYTES, }, ShaderModule::MonochromeSprite => match target { ShaderTarget::Vertex => MONOCHROME_SPRITE_VERTEX_BYTES, ShaderTarget::Fragment => MONOCHROME_SPRITE_FRAGMENT_BYTES, }, ShaderModule::SubpixelSprite => match target { ShaderTarget::Vertex => SUBPIXEL_SPRITE_VERTEX_BYTES, ShaderTarget::Fragment => SUBPIXEL_SPRITE_FRAGMENT_BYTES, }, ShaderModule::PolychromeSprite => match target { ShaderTarget::Vertex => POLYCHROME_SPRITE_VERTEX_BYTES, ShaderTarget::Fragment => POLYCHROME_SPRITE_FRAGMENT_BYTES, }, ShaderModule::EmojiRasterization => match target { ShaderTarget::Vertex => EMOJI_RASTERIZATION_VERTEX_BYTES, ShaderTarget::Fragment => EMOJI_RASTERIZATION_FRAGMENT_BYTES, }, }; Self { inner: bytes } } } #[cfg(debug_assertions)] pub(super) fn build_shader_blob(entry: ShaderModule, target: ShaderTarget) -> Result { unsafe { use windows::Win32::Graphics::{ Direct3D::ID3DInclude, Hlsl::D3D_COMPILE_STANDARD_FILE_INCLUDE, }; let shader_name = if matches!(entry, ShaderModule::EmojiRasterization) { "color_text_raster.hlsl" } else { "shaders.hlsl" }; let entry = format!( "{}_{}\0", entry.as_str(), match target { ShaderTarget::Vertex => "vertex", ShaderTarget::Fragment => "fragment", } ); let target = match target { ShaderTarget::Vertex => "vs_4_1\0", ShaderTarget::Fragment => "ps_4_1\0", }; let mut compile_blob = None; let mut error_blob = None; let shader_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join(&format!("src/{}", shader_name)) .canonicalize()?; let entry_point = PCSTR::from_raw(entry.as_ptr()); let target_cstr = PCSTR::from_raw(target.as_ptr()); // really dirty trick because winapi bindings are unhappy otherwise let include_handler = &std::mem::transmute::( D3D_COMPILE_STANDARD_FILE_INCLUDE as usize, ); let ret = D3DCompileFromFile( &HSTRING::from(shader_path.to_str().unwrap()), None, include_handler, entry_point, target_cstr, D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION, 0, &mut compile_blob, Some(&mut error_blob), ); if ret.is_err() { let Some(error_blob) = error_blob else { return Err(anyhow::anyhow!("{ret:?}")); }; let error_string = std::ffi::CStr::from_ptr(error_blob.GetBufferPointer() as *const i8) .to_string_lossy(); log::error!("Shader compile error: {}", error_string); return Err(anyhow::anyhow!("Compile error: {}", error_string)); } Ok(compile_blob.unwrap()) } } #[cfg(not(debug_assertions))] include!(concat!(env!("OUT_DIR"), "/shaders_bytes.rs")); #[cfg(debug_assertions)] impl ShaderModule { pub fn as_str(self) -> &'static str { match self { ShaderModule::Quad => "quad", ShaderModule::Shadow => "shadow", ShaderModule::Underline => "underline", ShaderModule::PathRasterization => "path_rasterization", ShaderModule::PathSprite => "path_sprite", ShaderModule::MonochromeSprite => "monochrome_sprite", ShaderModule::SubpixelSprite => "subpixel_sprite", ShaderModule::PolychromeSprite => "polychrome_sprite", ShaderModule::EmojiRasterization => "emoji_rasterization", } } } } mod nvidia { use std::{ ffi::CStr, os::raw::{c_char, c_int, c_uint}, }; use anyhow::Result; use windows::{Win32::System::LibraryLoader::GetProcAddress, core::s}; use crate::with_dll_library; // https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_lite_common.h#L180 const NVAPI_SHORT_STRING_MAX: usize = 64; // https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_lite_common.h#L235 #[allow(non_camel_case_types)] type NvAPI_ShortString = [c_char; NVAPI_SHORT_STRING_MAX]; // https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_lite_common.h#L447 #[allow(non_camel_case_types)] type NvAPI_SYS_GetDriverAndBranchVersion_t = unsafe extern "C" fn( driver_version: *mut c_uint, build_branch_string: *mut NvAPI_ShortString, ) -> c_int; pub(super) fn get_driver_version() -> Result { #[cfg(target_pointer_width = "64")] let nvidia_dll_name = s!("nvapi64.dll"); #[cfg(target_pointer_width = "32")] let nvidia_dll_name = s!("nvapi.dll"); with_dll_library(nvidia_dll_name, |nvidia_dll| unsafe { let nvapi_query_addr = GetProcAddress(nvidia_dll, s!("nvapi_QueryInterface")) .ok_or_else(|| anyhow::anyhow!("Failed to get nvapi_QueryInterface address"))?; let nvapi_query: extern "C" fn(u32) -> *mut () = std::mem::transmute(nvapi_query_addr); // https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_interface.h#L41 let nvapi_get_driver_version_ptr = nvapi_query(0x2926aaad); if nvapi_get_driver_version_ptr.is_null() { anyhow::bail!("Failed to get NVIDIA driver version function pointer"); } let nvapi_get_driver_version: NvAPI_SYS_GetDriverAndBranchVersion_t = std::mem::transmute(nvapi_get_driver_version_ptr); let mut driver_version: c_uint = 0; let mut build_branch_string: NvAPI_ShortString = [0; NVAPI_SHORT_STRING_MAX]; let result = nvapi_get_driver_version( &mut driver_version as *mut c_uint, &mut build_branch_string as *mut NvAPI_ShortString, ); if result != 0 { anyhow::bail!( "Failed to get NVIDIA driver version, error code: {}", result ); } let major = driver_version / 100; let minor = driver_version % 100; let branch_string = CStr::from_ptr(build_branch_string.as_ptr()); Ok(format!( "{}.{} {}", major, minor, branch_string.to_string_lossy() )) }) } } mod amd { use std::os::raw::{c_char, c_int, c_void}; use anyhow::Result; use windows::{Win32::System::LibraryLoader::GetProcAddress, core::s}; use crate::with_dll_library; // https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L145 const AGS_CURRENT_VERSION: i32 = (6 << 22) | (3 << 12); // https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L204 // This is an opaque type, using struct to represent it properly for FFI #[repr(C)] struct AGSContext { _private: [u8; 0], } #[repr(C)] pub struct AGSGPUInfo { pub driver_version: *const c_char, pub radeon_software_version: *const c_char, pub num_devices: c_int, pub devices: *mut c_void, } // https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L429 #[allow(non_camel_case_types)] type agsInitialize_t = unsafe extern "C" fn( version: c_int, config: *const c_void, context: *mut *mut AGSContext, gpu_info: *mut AGSGPUInfo, ) -> c_int; // https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L436 #[allow(non_camel_case_types)] type agsDeInitialize_t = unsafe extern "C" fn(context: *mut AGSContext) -> c_int; pub(super) fn get_driver_version() -> Result { #[cfg(target_pointer_width = "64")] let amd_dll_name = s!("amd_ags_x64.dll"); #[cfg(target_pointer_width = "32")] let amd_dll_name = s!("amd_ags_x86.dll"); with_dll_library(amd_dll_name, |amd_dll| unsafe { let ags_initialize_addr = GetProcAddress(amd_dll, s!("agsInitialize")) .ok_or_else(|| anyhow::anyhow!("Failed to get agsInitialize address"))?; let ags_deinitialize_addr = GetProcAddress(amd_dll, s!("agsDeInitialize")) .ok_or_else(|| anyhow::anyhow!("Failed to get agsDeInitialize address"))?; let ags_initialize: agsInitialize_t = std::mem::transmute(ags_initialize_addr); let ags_deinitialize: agsDeInitialize_t = std::mem::transmute(ags_deinitialize_addr); let mut context: *mut AGSContext = std::ptr::null_mut(); let mut gpu_info: AGSGPUInfo = AGSGPUInfo { driver_version: std::ptr::null(), radeon_software_version: std::ptr::null(), num_devices: 0, devices: std::ptr::null_mut(), }; let result = ags_initialize( AGS_CURRENT_VERSION, std::ptr::null(), &mut context, &mut gpu_info, ); if result != 0 { anyhow::bail!("Failed to initialize AMD AGS, error code: {}", result); } // Vulkan actually returns this as the driver version let software_version = if !gpu_info.radeon_software_version.is_null() { std::ffi::CStr::from_ptr(gpu_info.radeon_software_version) .to_string_lossy() .into_owned() } else { "Unknown Radeon Software Version".to_string() }; let driver_version = if !gpu_info.driver_version.is_null() { std::ffi::CStr::from_ptr(gpu_info.driver_version) .to_string_lossy() .into_owned() } else { "Unknown Radeon Driver Version".to_string() }; ags_deinitialize(context); Ok(format!("{} ({})", software_version, driver_version)) }) } } mod dxgi { use windows::{ Win32::Graphics::Dxgi::{IDXGIAdapter1, IDXGIDevice}, core::Interface, }; pub(super) fn get_driver_version(adapter: &IDXGIAdapter1) -> anyhow::Result { let number = unsafe { adapter.CheckInterfaceSupport(&IDXGIDevice::IID as _) }?; Ok(format!( "{}.{}.{}.{}", number >> 48, (number >> 32) & 0xFFFF, (number >> 16) & 0xFFFF, number & 0xFFFF )) } }