use std::{
collections::HashMap,
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::{
platform::windows::directx_renderer::shader_resources::{
RawShaderBytes, ShaderModule, ShaderTarget,
},
*,
};
pub(crate) const DISABLE_DIRECT_COMPOSITION: &str = "GPUI_DISABLE_DIRECT_COMPOSITION";
const RENDER_TARGET_FORMAT: DXGI_FORMAT = DXGI_FORMAT_B8G8R8A8_UNORM;
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<DirectXAtlas>,
devices: Option<DirectXRendererDevices>,
resources: Option<DirectXResources>,
globals: DirectXGlobalElements<GlobalParams>,
pipelines: DirectXRenderPipelines,
direct_composition: Option<DirectComposition>,
font_info: &'static FontInfo,
width: u32,
height: u32,
skip_draws: bool,
}
#[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<IDXGIDevice>,
}
struct DirectXResources {
swap_chain: IDXGISwapChain1,
render_target: Option<ID3D11Texture2D>,
render_target_view: Option<ID3D11RenderTargetView>,
backdrop_texture: ID3D11Texture2D,
backdrop_srv: Option<ID3D11ShaderResourceView>,
path_intermediate_texture: ID3D11Texture2D,
path_intermediate_srv: Option<ID3D11ShaderResourceView>,
path_intermediate_msaa_texture: ID3D11Texture2D,
path_intermediate_msaa_view: Option<ID3D11RenderTargetView>,
viewport: D3D11_VIEWPORT,
}
struct DirectXRenderPipelines {
shadow_pipeline: PipelineState<Shadow>,
quad_pipeline: PipelineState<Quad>,
backdrop_blur_pipeline: PipelineState<BackdropBlur>,
path_rasterization_pipeline: PipelineState<PathRasterizationSprite>,
path_sprite_pipeline: PipelineState<PathSprite>,
underline_pipeline: PipelineState<Underline>,
mono_sprites: PipelineState<MonochromeSprite>,
subpixel_sprites: PipelineState<SubpixelSprite>,
poly_sprites: PipelineState<PolychromeSprite>,
}
struct DirectXGlobalElements<T> {
global_params_buffer: Option<ID3D11Buffer>,
sampler: Option<ID3D11SamplerState>,
_marker: std::marker::PhantomData<T>,
}
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<Self> {
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<Self> {
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<dyn PlatformAtlas> {
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 {
return Ok(());
}
self.pre_draw(&match background_appearance {
WindowBackgroundAppearance::Opaque => [1.0f32; 4],
_ => [0.0f32; 4],
})?;
for batch in scene.batches() {
match batch {
PrimitiveBatch::Shadows(shadows, transforms) => self.draw_shadows(shadows, transforms),
PrimitiveBatch::Quads(quads, transforms) => self.draw_quads(quads, transforms),
PrimitiveBatch::BackdropBlurs(blurs, transforms) => {
self.draw_backdrop_blurs(blurs, transforms)
}
PrimitiveBatch::Paths(paths) => {
self.draw_paths_to_intermediate(paths)?;
self.draw_paths_from_intermediate(paths)
}
PrimitiveBatch::Underlines(underlines, transforms) => {
self.draw_underlines(underlines, transforms)
}
PrimitiveBatch::MonochromeSprites {
texture_id,
sprites,
} => self.draw_monochrome_sprites(texture_id, sprites),
PrimitiveBatch::SubpixelSprites {
texture_id,
sprites,
} => self.draw_subpixel_sprites(texture_id, sprites),
PrimitiveBatch::PolychromeSprites {
texture_id,
sprites,
transforms,
} => self.draw_polychrome_sprites(texture_id, sprites, transforms),
PrimitiveBatch::Surfaces(surfaces) => self.draw_surfaces(surfaces),
}
.context(format!(
"scene too large:\
{} paths, {} shadows, {} quads, {} blurs, {} underlines, {} mono, {} subpixel, {} poly, {} custom, {} surfaces",
scene.paths.len(),
scene.shadows.len(),
scene.quads.len(),
scene.backdrop_blurs.len(),
scene.underlines.len(),
scene.monochrome_sprites.len(),
scene.subpixel_sprites.len(),
scene.polychrome_sprites.len(),
scene.shaders.len(),
scene.surfaces.len(),
))?;
}
self.present()
}
pub(crate) fn resize(&mut self, new_size: Size<DevicePixels>) -> 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;
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();
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 draw_shadows(
&mut self,
shadows: &[Shadow],
shadow_transforms: &[TransformationMatrix],
) -> Result<()> {
if shadows.is_empty() {
return Ok(());
}
debug_assert_eq!(shadows.len(), shadow_transforms.len());
let devices = self.devices.as_ref().context("devices missing")?;
self.pipelines.shadow_pipeline.update_buffer(
&devices.device,
&devices.device_context,
shadows,
)?;
self.pipelines.shadow_pipeline.update_aux_buffer(
&devices.device,
&devices.device_context,
shadow_transforms,
)?;
self.pipelines.shadow_pipeline.draw(
&devices.device_context,
slice::from_ref(
&self
.resources
.as_ref()
.context("resources missing")?
.viewport,
),
slice::from_ref(&self.globals.global_params_buffer),
D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
4,
shadows.len() as u32,
)
}
fn draw_quads(&mut self, quads: &[Quad], quad_transforms: &[TransformationMatrix]) -> Result<()> {
if quads.is_empty() {
return Ok(());
}
debug_assert_eq!(quads.len(), quad_transforms.len());
let devices = self.devices.as_ref().context("devices missing")?;
self.pipelines.quad_pipeline.update_buffer(
&devices.device,
&devices.device_context,
quads,
)?;
self.pipelines.quad_pipeline.update_aux_buffer(
&devices.device,
&devices.device_context,
quad_transforms,
)?;
self.pipelines.quad_pipeline.draw(
&devices.device_context,
slice::from_ref(
&self
.resources
.as_ref()
.context("resources missing")?
.viewport,
),
slice::from_ref(&self.globals.global_params_buffer),
D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
4,
quads.len() as u32,
)
}
fn draw_backdrop_blurs(
&mut self,
blurs: &[BackdropBlur],
blur_transforms: &[TransformationMatrix],
) -> Result<()> {
if blurs.is_empty() {
return Ok(());
}
debug_assert_eq!(blurs.len(), blur_transforms.len());
let devices = self.devices.as_ref().context("devices missing")?;
let resources = self.resources.as_ref().context("resources missing")?;
self.pipelines.backdrop_blur_pipeline.update_buffer(
&devices.device,
&devices.device_context,
blurs,
)?;
self.pipelines.backdrop_blur_pipeline.update_aux_buffer(
&devices.device,
&devices.device_context,
blur_transforms,
)?;
let render_target = resources
.render_target
.as_ref()
.context("missing render target")?;
unsafe {
devices
.device_context
.CopyResource(&resources.backdrop_texture, render_target);
}
self.pipelines.backdrop_blur_pipeline.draw_with_texture(
&devices.device_context,
slice::from_ref(&resources.backdrop_srv),
slice::from_ref(&resources.viewport),
slice::from_ref(&self.globals.global_params_buffer),
slice::from_ref(&self.globals.sampler),
blurs.len() as u32,
)
}
fn draw_paths_to_intermediate(&mut self, paths: &[Path<ScaledPixels>]) -> 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")?;
unsafe {
devices.device_context.ClearRenderTargetView(
resources.path_intermediate_msaa_view.as_ref().unwrap(),
&[0.0; 4],
);
devices.device_context.OMSetRenderTargets(
Some(slice::from_ref(&resources.path_intermediate_msaa_view)),
None,
);
}
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,
)?;
unsafe {
devices.device_context.ResolveSubresource(
&resources.path_intermediate_texture,
0,
&resources.path_intermediate_msaa_texture,
0,
RENDER_TARGET_FORMAT,
);
devices
.device_context
.OMSetRenderTargets(Some(slice::from_ref(&resources.render_target_view)), None);
}
Ok(())
}
fn draw_paths_from_intermediate(&mut self, paths: &[Path<ScaledPixels>]) -> Result<()> {
let Some(first_path) = paths.first() else {
return Ok(());
};
let sprites = if paths.last().unwrap().order == first_path.order {
paths
.iter()
.map(|path| PathSprite {
bounds: path.clipped_bounds(),
})
.collect::<Vec<_>>()
} 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,
)?;
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,
underlines: &[Underline],
underline_transforms: &[TransformationMatrix],
) -> Result<()> {
if underlines.is_empty() {
return Ok(());
}
debug_assert_eq!(underlines.len(), underline_transforms.len());
let devices = self.devices.as_ref().context("devices missing")?;
let resources = self.resources.as_ref().context("resources missing")?;
self.pipelines.underline_pipeline.update_buffer(
&devices.device,
&devices.device_context,
underlines,
)?;
self.pipelines.underline_pipeline.update_aux_buffer(
&devices.device,
&devices.device_context,
underline_transforms,
)?;
self.pipelines.underline_pipeline.draw(
&devices.device_context,
slice::from_ref(&resources.viewport),
slice::from_ref(&self.globals.global_params_buffer),
D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
4,
underlines.len() as u32,
)
}
fn draw_monochrome_sprites(
&mut self,
texture_id: AtlasTextureId,
sprites: &[MonochromeSprite],
) -> Result<()> {
if sprites.is_empty() {
return Ok(());
}
let devices = self.devices.as_ref().context("devices missing")?;
let resources = self.resources.as_ref().context("resources missing")?;
self.pipelines.mono_sprites.update_buffer(
&devices.device,
&devices.device_context,
sprites,
)?;
let texture_view = self.atlas.get_texture_view(texture_id);
self.pipelines.mono_sprites.draw_with_texture(
&devices.device_context,
&texture_view,
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_subpixel_sprites(
&mut self,
texture_id: AtlasTextureId,
sprites: &[SubpixelSprite],
) -> Result<()> {
if sprites.is_empty() {
return Ok(());
}
let devices = self.devices.as_ref().context("devices missing")?;
let resources = self.resources.as_ref().context("resources missing")?;
self.pipelines.subpixel_sprites.update_buffer(
&devices.device,
&devices.device_context,
&sprites,
)?;
let texture_view = self.atlas.get_texture_view(texture_id);
self.pipelines.subpixel_sprites.draw_with_texture(
&devices.device_context,
&texture_view,
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_polychrome_sprites(
&mut self,
texture_id: AtlasTextureId,
sprites: &[PolychromeSprite],
sprite_transforms: &[TransformationMatrix],
) -> Result<()> {
if sprites.is_empty() {
return Ok(());
}
debug_assert_eq!(sprites.len(), sprite_transforms.len());
let devices = self.devices.as_ref().context("devices missing")?;
let resources = self.resources.as_ref().context("resources missing")?;
self.pipelines.poly_sprites.update_buffer(
&devices.device,
&devices.device_context,
sprites,
)?;
self.pipelines.poly_sprites.update_aux_buffer(
&devices.device,
&devices.device_context,
sprite_transforms,
)?;
let texture_view = self.atlas.get_texture_view(texture_id);
self.pipelines.poly_sprites.draw_with_texture(
&devices.device_context,
&texture_view,
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_surfaces(&mut self, surfaces: &[PaintSurface]) -> Result<()> {
if surfaces.is_empty() {
return Ok(());
}
Ok(())
}
pub(crate) fn gpu_specs(&self) -> Result<GpuSpecs> {
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(),
_ => 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<FontInfo> = 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: 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<Self> {
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,
backdrop_texture,
backdrop_srv,
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,
backdrop_texture,
backdrop_srv,
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,
backdrop_texture,
backdrop_srv,
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.backdrop_texture = backdrop_texture;
self.backdrop_srv = backdrop_srv;
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<Self> {
let shadow_pipeline = PipelineState::new_with_aux(
device,
"shadow_pipeline",
ShaderModule::Shadow,
4,
std::mem::size_of::<TransformationMatrix>(),
create_blend_state(device)?,
)?;
let quad_pipeline = PipelineState::new_with_aux(
device,
"quad_pipeline",
ShaderModule::Quad,
64,
std::mem::size_of::<TransformationMatrix>(),
create_blend_state(device)?,
)?;
let backdrop_blur_pipeline = PipelineState::new_with_aux(
device,
"backdrop_blur_pipeline",
ShaderModule::BackdropBlur,
8,
std::mem::size_of::<TransformationMatrix>(),
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_with_aux(
device,
"underline_pipeline",
ShaderModule::Underline,
4,
std::mem::size_of::<TransformationMatrix>(),
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_with_aux(
device,
"polychrome_sprite_pipeline",
ShaderModule::PolychromeSprite,
16,
std::mem::size_of::<TransformationMatrix>(),
create_blend_state(device)?,
)?;
Ok(Self {
shadow_pipeline,
quad_pipeline,
backdrop_blur_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<Self> {
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<T> DirectXGlobalElements<T> {
pub fn new(device: &ID3D11Device) -> Result<Self> {
let global_params_buffer = unsafe {
let desc = D3D11_BUFFER_DESC {
ByteWidth: std::mem::size_of::<T>() 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,
_marker: std::marker::PhantomData,
})
}
}
#[derive(Debug, Default)]
#[repr(C)]
struct GlobalParams {
gamma_ratios: [f32; 4],
viewport_size: [f32; 2],
grayscale_enhanced_contrast: f32,
subpixel_enhanced_contrast: f32,
}
struct AuxBuffer {
buffer: ID3D11Buffer,
buffer_size: usize,
element_size: usize,
view: Option<ID3D11ShaderResourceView>,
}
struct PipelineState<T> {
label: &'static str,
vertex: ID3D11VertexShader,
fragment: ID3D11PixelShader,
buffer: ID3D11Buffer,
buffer_size: usize,
view: Option<ID3D11ShaderResourceView>,
aux: Option<AuxBuffer>,
blend_state: ID3D11BlendState,
_marker: std::marker::PhantomData<T>,
}
impl<T> PipelineState<T> {
fn new(
device: &ID3D11Device,
label: &'static str,
shader_module: ShaderModule,
buffer_size: usize,
blend_state: ID3D11BlendState,
) -> Result<Self> {
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::<T>(), buffer_size)?;
let view = create_buffer_view(device, &buffer)?;
Ok(PipelineState {
label,
vertex,
fragment,
buffer,
buffer_size,
view,
aux: None,
blend_state,
_marker: std::marker::PhantomData,
})
}
fn new_with_aux(
device: &ID3D11Device,
label: &'static str,
shader_module: ShaderModule,
buffer_size: usize,
aux_element_size: usize,
blend_state: ID3D11BlendState,
) -> Result<Self> {
let mut this = Self::new(device, label, shader_module, buffer_size, blend_state)?;
let aux_buffer = create_buffer(device, aux_element_size, buffer_size)?;
let aux_view = create_buffer_view(device, &aux_buffer)?;
this.aux = Some(AuxBuffer {
buffer: aux_buffer,
buffer_size,
element_size: aux_element_size,
view: aux_view,
});
Ok(this)
}
fn new_custom(
device: &ID3D11Device,
hlsl: &str,
buffer_size: usize,
element_size: usize,
blend_state: ID3D11BlendState,
) -> Result<Self> {
let vertex = {
let raw_shader = RawShaderBytes::new_custom(&hlsl, ShaderTarget::Vertex)?;
create_vertex_shader(device, raw_shader.as_bytes())?
};
let fragment = {
let raw_shader = RawShaderBytes::new_custom(&hlsl, ShaderTarget::Fragment)?;
create_fragment_shader(device, raw_shader.as_bytes())?
};
let buffer = create_buffer(device, element_size, buffer_size)?;
let view = create_buffer_view(device, &buffer)?;
Ok(PipelineState {
label: "custom",
vertex,
fragment,
buffer,
buffer_size,
view,
aux: None,
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::info!(
"Updating {} buffer size from {} to {}",
self.label,
self.buffer_size,
new_buffer_size
);
let buffer = create_buffer(device, std::mem::size_of::<T>(), 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 update_aux_buffer<U>(
&mut self,
device: &ID3D11Device,
device_context: &ID3D11DeviceContext,
data: &[U],
) -> Result<()> {
let aux = self.aux.as_mut().context("aux buffer missing")?;
debug_assert_eq!(aux.element_size, std::mem::size_of::<U>());
if aux.buffer_size < data.len() {
let new_buffer_size = data.len().next_power_of_two();
log::info!(
"Updating {} aux buffer size from {} to {}",
self.label,
aux.buffer_size,
new_buffer_size
);
let buffer = create_buffer(device, aux.element_size, new_buffer_size)?;
let view = create_buffer_view(device, &buffer)?;
aux.buffer = buffer;
aux.view = view;
aux.buffer_size = new_buffer_size;
}
update_buffer(device_context, &aux.buffer, data)
}
fn draw(
&self,
device_context: &ID3D11DeviceContext,
viewport: &[D3D11_VIEWPORT],
global_params: &[Option<ID3D11Buffer>],
topology: D3D_PRIMITIVE_TOPOLOGY,
vertex_count: u32,
instance_count: u32,
) -> Result<()> {
let views = [
self.view.clone(),
self.aux.as_ref().and_then(|aux| aux.view.clone()),
];
let view_slice = if self.aux.is_some() { &views[..2] } else { &views[..1] };
set_pipeline_state(
device_context,
view_slice,
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<ID3D11ShaderResourceView>],
viewport: &[D3D11_VIEWPORT],
global_params: &[Option<ID3D11Buffer>],
sampler: &[Option<ID3D11SamplerState>],
instance_count: u32,
) -> Result<()> {
let views = [
self.view.clone(),
self.aux.as_ref().and_then(|aux| aux.view.clone()),
];
let view_slice = if self.aux.is_some() { &views[..2] } else { &views[..1] };
set_pipeline_state(
device_context,
view_slice,
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<ScaledPixels>,
st_position: Point<f32>,
color: Background,
bounds: Bounds<ScaledPixels>,
}
#[derive(Clone, Copy)]
#[repr(C)]
struct PathSprite {
bounds: Bounds<ScaledPixels>,
}
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<IDCompositionDevice> {
Ok(unsafe { DCompositionCreateDevice(dxgi_device)? })
}
fn create_swap_chain_for_composition(
dxgi_factory: &IDXGIFactory6,
device: &ID3D11Device,
width: u32,
height: u32,
) -> Result<IDXGISwapChain1> {
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_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<IDXGISwapChain1> {
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<ID3D11RenderTargetView>,
ID3D11Texture2D,
Option<ID3D11ShaderResourceView>,
ID3D11Texture2D,
Option<ID3D11ShaderResourceView>,
ID3D11Texture2D,
Option<ID3D11RenderTargetView>,
D3D11_VIEWPORT,
)> {
let (render_target, render_target_view) =
create_render_target_and_its_view(swap_chain, &devices.device)?;
let (backdrop_texture, backdrop_srv) = create_backdrop_texture(&devices.device, width, height)?;
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,
backdrop_texture,
backdrop_srv,
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<ID3D11RenderTargetView>)> {
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<ID3D11ShaderResourceView>)> {
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_backdrop_texture(
device: &ID3D11Device,
width: u32,
height: u32,
) -> Result<(ID3D11Texture2D, Option<ID3D11ShaderResourceView>)> {
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_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, shader_resource_view))
}
#[inline]
fn create_path_intermediate_msaa_texture_and_view(
device: &ID3D11Device,
width: u32,
height: u32,
) -> Result<(ID3D11Texture2D, Option<ID3D11RenderTargetView>)> {
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(())
}
#[inline]
fn create_blend_state(device: &ID3D11Device) -> Result<ID3D11BlendState> {
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<ID3D11BlendState> {
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;
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<ID3D11BlendState> {
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<ID3D11BlendState> {
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<ID3D11VertexShader> {
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<ID3D11PixelShader> {
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<ID3D11Buffer> {
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<Option<ID3D11ShaderResourceView>> {
let mut view = None;
unsafe { device.CreateShaderResourceView(buffer, None, Some(&mut view)) }?;
Ok(view)
}
#[inline]
fn update_buffer<T>(
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<ID3D11ShaderResourceView>],
topology: D3D_PRIMITIVE_TOPOLOGY,
viewport: &[D3D11_VIEWPORT],
vertex_shader: &ID3D11VertexShader,
fragment_shader: &ID3D11PixelShader,
global_params: &[Option<ID3D11Buffer>],
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;
use windows::Win32::Graphics::Direct3D::Fxc::D3DCompile;
#[cfg(debug_assertions)]
use windows::{Win32::Graphics::Direct3D::Fxc::D3DCompileFromFile, core::HSTRING};
use windows::{
Win32::Graphics::Direct3D::{
Fxc::{D3DCOMPILE_DEBUG, D3DCOMPILE_SKIP_OPTIMIZATION},
ID3DBlob,
},
core::PCSTR,
};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum ShaderModule {
Quad,
Shadow,
BackdropBlur,
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],
_blob: Option<ID3DBlob>,
}
impl<'t> RawShaderBytes<'t> {
pub(crate) fn new(module: ShaderModule, target: ShaderTarget) -> Result<Self> {
#[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: Some(blob),
})
}
}
pub(crate) fn new_custom(hlsl: &str, target: ShaderTarget) -> Result<Self> {
let mut compile_blob = None;
let mut error_blob = None;
unsafe {
let ret = D3DCompile(
hlsl.as_ptr() as *const _,
hlsl.len(),
PCSTR::null(),
None,
None,
PCSTR::from_raw(
match target {
ShaderTarget::Fragment => "fs\0",
ShaderTarget::Vertex => "vs\0",
}
.as_ptr(),
),
PCSTR::from_raw(
match target {
ShaderTarget::Vertex => "vs_4_1\0",
ShaderTarget::Fragment => "ps_4_1\0",
}
.as_ptr(),
),
if cfg!(debug_assertions) {
D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION
} else {
0
},
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));
}
let inner = std::slice::from_raw_parts(
compile_blob.as_ref().unwrap().GetBufferPointer() as *const u8,
compile_blob.as_ref().unwrap().GetBufferSize(),
);
Ok(Self {
inner,
_blob: compile_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::BackdropBlur => match target {
ShaderTarget::Vertex => BACKDROP_BLUR_VERTEX_BYTES,
ShaderTarget::Fragment => BACKDROP_BLUR_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,
_blob: None,
}
}
}
#[cfg(debug_assertions)]
pub(super) fn build_shader_blob(entry: ShaderModule, target: ShaderTarget) -> Result<ID3DBlob> {
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/platform/windows/{}", shader_name))
.canonicalize()?;
let entry_point = PCSTR::from_raw(entry.as_ptr());
let target_cstr = PCSTR::from_raw(target.as_ptr());
let include_handler = &std::mem::transmute::<usize, ID3DInclude>(
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::BackdropBlur => "backdrop_blur",
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;
const NVAPI_SHORT_STRING_MAX: usize = 64;
#[allow(non_camel_case_types)]
type NvAPI_ShortString = [c_char; NVAPI_SHORT_STRING_MAX];
#[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<String> {
#[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);
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;
const AGS_CURRENT_VERSION: i32 = (6 << 22) | (3 << 12);
#[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,
}
#[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;
#[allow(non_camel_case_types)]
type agsDeInitialize_t = unsafe extern "C" fn(context: *mut AGSContext) -> c_int;
pub(super) fn get_driver_version() -> Result<String> {
#[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);
}
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<String> {
let number = unsafe { adapter.CheckInterfaceSupport(&IDXGIDevice::IID as _) }?;
Ok(format!(
"{}.{}.{}.{}",
number >> 48,
(number >> 32) & 0xFFFF,
(number >> 16) & 0xFFFF,
number & 0xFFFF
))
}
}