mod color_presentation;
mod wide_sdr;
use std::{
slice,
sync::{Arc, OnceLock},
};
use anyhow::{Context, Result};
use gpui_util::ResultExt;
use windows::{
Win32::{
Foundation::HWND,
Graphics::{
Direct3D::*,
Direct3D11::*,
DirectComposition::*,
DirectWrite::*,
Dxgi::{Common::*, *},
},
},
core::{HSTRING, 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;
const PATH_MULTISAMPLE_COUNT: u32 = 4;
const MAX_INSTANCE_BUFFER_SIZE: usize = 256 * 1024 * 1024;
pub(crate) struct FontInfo {
pub gamma_ratios: [f32; 4],
pub grayscale_enhanced_contrast: f32,
pub subpixel_enhanced_contrast: f32,
pub is_bgr: bool,
}
pub(crate) struct DirectXRenderer {
hwnd: HWND,
atlas: Arc<DirectXAtlas>,
devices: Option<DirectXRendererDevices>,
resources: Option<DirectXResources>,
globals: DirectXGlobalElements,
pipelines: DirectXRenderPipelines,
custom_compositor: Option<crate::custom_draw::CustomCompositor>,
custom_retirement: Vec<(ID3D11Query, Vec<Arc<dyn gpui::CustomContent>>)>,
shaders: Option<crate::custom_draw::ShaderRenderer>,
backdrops: Option<crate::custom_draw::Backdrops>,
wide_sdr: Option<wide_sdr::Compositor>,
color_mode: WindowColorMode,
color_display: color_presentation::DisplayState,
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>,
annotation: Option<ID3DUserDefinedAnnotation>,
}
struct DirectXResources {
swap_chain: Option<IDXGISwapChain1>,
render_target: Option<ID3D11Texture2D>,
render_target_view: Option<ID3D11RenderTargetView>,
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>,
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 {
global_params_buffer: Option<ID3D11Buffer>,
batch_params_buffer: Option<ID3D11Buffer>,
sampler: Option<ID3D11SamplerState>,
}
struct Annotation<'a>(&'a ID3DUserDefinedAnnotation);
impl<'a> Annotation<'a> {
fn new(annotation: &'a ID3DUserDefinedAnnotation, label: HSTRING) -> Self {
unsafe { annotation.BeginEvent(&label) };
Self(annotation)
}
}
impl Drop for Annotation<'_> {
fn drop(&mut self) {
unsafe { self.0.EndEvent() };
}
}
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")?)
};
let annotation = device_context.cast().ok();
Ok(Self {
adapter: adapter.clone(),
dxgi_factory: dxgi_factory.clone(),
device: device.clone(),
device_context: device_context.clone(),
dxgi_device,
annotation,
})
}
}
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
.as_ref()
.context("missing swap chain")?,
)
.context("Setting swap chain for DirectComposition")?;
Some(composition)
};
Ok(DirectXRenderer {
hwnd,
atlas,
devices: Some(devices),
resources: Some(resources),
globals,
pipelines,
custom_compositor: None,
custom_retirement: Vec::new(),
backdrops: None,
shaders: None,
wide_sdr: None,
color_mode: WindowColorMode::Default,
color_display: Default::default(),
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,
is_bgr: self.font_info.is_bgr as u32,
_pad: [0; 3],
}],
)?;
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)));
device_context
.VSSetConstantBuffers(0, Some(slice::from_ref(&self.globals.global_params_buffer)));
device_context
.VSSetConstantBuffers(1, Some(slice::from_ref(&self.globals.batch_params_buffer)));
device_context
.PSSetConstantBuffers(0, Some(slice::from_ref(&self.globals.global_params_buffer)));
}
Ok(())
}
#[inline]
fn present(&mut self) -> Result<()> {
let result = unsafe {
self.resources
.as_ref()
.expect("resources missing")
.swap_chain
.as_ref()
.context("headless target cannot present")?
.Present(0, DXGI_PRESENT(0))
};
result.ok().context("Presenting swap chain failed")
}
pub(crate) fn handle_device_lost(&mut self, directx_devices: &DirectXDevices) -> Result<()> {
let disable_direct_composition = self.direct_composition.is_none();
try_to_recover_from_device_lost(|| {
self.handle_device_lost_impl(directx_devices, disable_direct_composition)
.context("DirectXRenderer handling device lost")
})
}
fn handle_device_lost_impl(
&mut self,
directx_devices: &DirectXDevices,
disable_direct_composition: bool,
) -> Result<()> {
let requested_color = self.color_mode;
self.wide_sdr = None;
self.custom_compositor = None;
self.shaders = None;
self.backdrops = 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();
if let Some(devices) = &self.devices {
devices.device_context.Flush();
}
self.devices.take();
self.custom_retirement.clear();
}
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
.as_ref()
.context("missing 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.color_mode = WindowColorMode::Default;
self.skip_draws = true;
if let Err(error) = self.set_color_mode(requested_color, WindowBackgroundAppearance::Opaque)
{
self.color_mode = requested_color;
return Err(error);
}
Ok(())
}
pub(crate) fn draw(
&mut self,
scene: &Scene,
background_appearance: WindowBackgroundAppearance,
) -> Result<()> {
if self.skip_draws {
return Ok(());
}
self.refresh_color_profile()?;
self.ensure_color_ready()?;
self.render(scene, background_appearance)?;
if let Some(wide) = &self.wide_sdr {
let devices = self.devices.as_ref().context("devices missing")?;
let target = self
.resources
.as_ref()
.context("resources missing")?
.render_target_view
.as_ref()
.context("back buffer missing")?;
wide.present(devices, target, self.color_display.white_scale)?;
}
self.present()
}
fn render(&mut self, scene: &Scene, background: WindowBackgroundAppearance) -> Result<()> {
if let Some(devices) = &self.devices {
self.custom_retirement.retain(|(query, _)| {
let mut complete = 0u32;
let result = unsafe {
devices.device_context.GetData(
query,
Some((&mut complete as *mut u32).cast()),
4,
D3D11_ASYNC_GETDATA_DONOTFLUSH.0 as u32,
)
};
result.is_err() || complete == 0
});
}
let fence = if !scene.custom_draws.is_empty() {
let devices = self.devices.as_ref().context("devices missing")?;
let mut query = None;
unsafe {
devices.device.CreateQuery(
&D3D11_QUERY_DESC {
Query: D3D11_QUERY_EVENT,
MiscFlags: 0,
},
Some(&mut query),
)?;
}
Some((
query.context("custom retirement query missing")?,
scene
.custom_draws
.iter()
.map(|d| d.content.clone())
.collect::<Vec<_>>(),
))
} else {
None
};
let result = self.render_inner(scene, background);
if let Some((query, content)) = fence {
if let Some(devices) = &self.devices {
unsafe {
devices.device_context.End(&query);
}
self.custom_retirement.push((query, content));
}
}
result
}
fn render_inner(
&mut self,
scene: &Scene,
background_appearance: WindowBackgroundAppearance,
) -> Result<()> {
if scene.backdrops.is_empty() && scene.layer_boundaries.is_empty() {
if let Some(effects) = &mut self.backdrops {
effects.clear();
}
}
anyhow::ensure!(self.wide_sdr.is_some() || scene.shader_draws.iter().all(|d|
d.program.output_encoding() == gpui::ShaderOutputEncoding::EncodedSrgb),
"linear P3 shaders require WideGamutSdr");
anyhow::ensure!(
self.wide_sdr.is_none()
|| (scene.backdrops.is_empty()
&& scene.layer_boundaries.is_empty()
&& scene.shader_draws.iter().all(|d| d.path_mask.is_none())),
"captured effects require SDR presentation"
);
if let Some(program) = scene
.backdrops
.first()
.map(|d| &d.program)
.or_else(|| scene.layer_boundaries.first().map(|b| &b.capture.program))
{
let devices = self.devices.as_ref().context("devices missing")?;
if self.backdrops.is_none() {
self.backdrops = Some(crate::custom_draw::Backdrops::new(
&devices.device,
program,
)?);
}
self.backdrops.as_mut().unwrap().prepare(
&devices.device,
scene,
size(
DevicePixels(self.width as i32),
DevicePixels(self.height as i32),
),
&[
matches!(background_appearance, WindowBackgroundAppearance::Opaque) as u64,
self.font_info.gamma_ratios[0].to_bits() as u64,
self.font_info.gamma_ratios[1].to_bits() as u64,
self.font_info.gamma_ratios[2].to_bits() as u64,
self.font_info.gamma_ratios[3].to_bits() as u64,
self.font_info.grayscale_enhanced_contrast.to_bits() as u64,
self.font_info.subpixel_enhanced_contrast.to_bits() as u64,
self.font_info.is_bgr as u64,
],
)?;
}
if let Some(wide) = self.wide_sdr.as_mut() {
wide.resize(
&self.devices.as_ref().context("devices missing")?.device,
size(
DevicePixels(self.width as i32),
DevicePixels(self.height as i32),
),
)?;
}
if !scene.shader_draws.is_empty() || self.shaders.is_some() {
let devices = self.devices.as_ref().context("devices missing")?;
if self.shaders.is_none() {
self.shaders = Some(crate::custom_draw::ShaderRenderer::new(&devices.device)?);
}
self.shaders.as_mut().unwrap().prepare(
&devices.device,
&devices.device_context,
scene,
)?;
}
let custom_outputs = self.prepare_custom_draws(scene)?;
self.pre_draw(&match background_appearance {
WindowBackgroundAppearance::Opaque => [1.0f32; 4],
_ => [0.0f32; 4],
})?;
self.upload_scene_buffers(scene)?;
if let Some(mut wide) = self.wide_sdr.take() {
let result = wide.render_scene(self, scene, &custom_outputs, background_appearance);
self.wide_sdr = Some(wide);
return result;
}
let mut executed_backdrops = Vec::new();
let mut executed_groups = Vec::new();
let mut skip_group_end = None;
for batch in scene.batches() {
if let Some(end) = skip_group_end {
if !matches!(&batch,PrimitiveBatch::LayerBoundary(index) if *index==end) {
continue;
}
skip_group_end = None;
}
if let PrimitiveBatch::LayerBoundary(index) = &batch {
let effects = self.backdrops.as_mut().unwrap();
let (slot, begin) = effects.groups.boundary(*index);
if begin {
effects.groups.start(slot);
}
if begin {
executed_groups.push(slot);
if effects.groups.hit(slot) {
skip_group_end = Some(effects.groups.end(slot));
}
}
}
if let PrimitiveBatch::Backdrop(index) = &batch {
executed_backdrops.push(*index);
}
self.render_batch(scene, &custom_outputs, batch)?;
}
if let Some(effects) = &mut self.backdrops {
effects.groups.commit(&executed_groups);
effects.cache.commit(&executed_backdrops);
}
Ok(())
}
fn render_batch(
&mut self,
scene: &Scene,
custom_outputs: &[crate::custom_draw::PreparedDraw],
batch: PrimitiveBatch,
) -> Result<()> {
let annotation = self
.devices
.as_ref()
.and_then(|d| d.annotation.clone())
.filter(|a| unsafe { a.GetStatus().as_bool() });
let _annotation = annotation
.as_ref()
.map(|a| Annotation::new(a, HSTRING::from(batch.label())));
match batch {
PrimitiveBatch::Shaders { program_id, range } => {
if scene.shader_draws[range.start].path_mask.is_some() {
let paths: Vec<_> = scene.shader_draws[range.clone()]
.iter()
.map(|draw| draw.path_mask.as_ref().unwrap().as_ref().clone())
.collect();
self.draw_paths_to_intermediate(&paths)?;
}
let devices = self.devices.as_ref().context("devices missing")?;
let resources = self.resources.as_ref().context("resources missing")?;
self.shaders
.as_ref()
.context("shader renderer missing")?
.draw(
&devices.device_context,
resources
.render_target_view
.as_ref()
.context("render target missing")?,
size(
DevicePixels(resources.viewport.Width as i32),
DevicePixels(resources.viewport.Height as i32),
),
program_id,
range.clone(),
if scene.shader_draws[range.start].path_mask.is_some() {
Some([resources.path_intermediate_srv.clone()])
} else {
scene.shader_draws[range.start]
.glyph_mask
.and_then(|tile| self.atlas.get_texture_view(tile.texture_id))
},
)
}
PrimitiveBatch::LayerBoundary(index) => {
let devices = self.devices.as_ref().context("devices missing")?;
let resources = self.resources.as_ref().context("resources missing")?;
self.backdrops
.as_ref()
.context("effects missing")?
.group_boundary(
&devices.device_context,
resources.render_target.as_ref().context("target missing")?,
resources
.render_target_view
.as_ref()
.context("target view missing")?,
size(
DevicePixels(resources.viewport.Width as i32),
DevicePixels(resources.viewport.Height as i32),
),
index,
)
}
PrimitiveBatch::Backdrop(index) => {
let devices = self.devices.as_ref().context("devices missing")?;
let resources = self.resources.as_ref().context("resources missing")?;
self.backdrops
.as_mut()
.context("backdrop renderer missing")?
.draw(
&devices.device_context,
resources
.render_target
.as_ref()
.context("backdrop target missing")?,
resources
.render_target_view
.as_ref()
.context("backdrop target view missing")?,
size(
DevicePixels(resources.viewport.Width as i32),
DevicePixels(resources.viewport.Height as i32),
),
index,
)
}
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::Custom(range) => {
let devices = self.devices.as_ref().context("devices missing")?;
let resources = self.resources.as_ref().context("resources missing")?;
let target = resources
.render_target_view
.as_ref()
.context("render target missing")?;
let viewport = size(
DevicePixels(resources.viewport.Width as i32),
DevicePixels(resources.viewport.Height as i32),
);
self.custom_compositor
.as_ref()
.context("custom compositor missing")?
.draw(
&devices.device_context,
target,
viewport,
&scene.custom_draws[range.clone()],
&custom_outputs[range],
)
}
PrimitiveBatch::Surfaces(range) => self.draw_surfaces(&scene.surfaces[range]),
}
}
fn prepare_custom_draws(
&mut self,
scene: &Scene,
) -> Result<Vec<crate::custom_draw::PreparedDraw>> {
use crate::custom_draw::*;
if scene.custom_draws.is_empty() {
return Ok(Vec::new());
}
let devices = self.devices.as_ref().context("devices missing")?.clone();
let mut batch =
PreparationBatch::new(devices.device.clone(), devices.device_context.clone());
let mut compositor = match self.custom_compositor.take() {
Some(compositor) => compositor,
None => CustomCompositor::new(&devices.device)?,
};
let result = (|| {
let mut prepared = Vec::with_capacity(scene.custom_draws.len());
let preparation_frame = gpui::PreparationFrame::default();
for draw in &scene.custom_draws {
let prepared_draw = batch.prepare(|context| {
let extent = extent(draw)?;
let request =
PreparationRequest::new(draw.bounds.size.map(|v| px(v.0)), extent, 1.)?;
let output =
draw.content
.prepare_in_frame(context, &request, &preparation_frame)?;
let native = output.is::<Background>();
let typed_wide = output.is::<Dx11WideSdrOutput>();
let wide = typed_wide
|| draw.content.image_color_mode() == gpui::ImageColorMode::LinearP3Wide;
anyhow::ensure!(
!wide || self.wide_sdr.is_some(),
"wide image requires WideGamutSdr presentation"
);
let source = if typed_wide {
anyhow::ensure!(
self.wide_sdr.is_some(),
"wide output requires WideGamutSdr presentation"
);
output
.downcast::<Dx11WideSdrOutput>()
.unwrap()
.validate(context, extent)?
} else if native {
let background = *output.downcast::<Background>().unwrap();
let bounds = Bounds::new(
point(ScaledPixels(0.), ScaledPixels(0.)),
size(
ScaledPixels(extent.width.0 as f32),
ScaledPixels(extent.height.0 as f32),
),
);
let mut scene = Scene::default();
scene.insert_primitive(Quad {
bounds,
content_mask: ContentMask { bounds },
background,
..Default::default()
});
scene.finish();
self.render_custom_mask(&scene, extent)?
} else {
output
.downcast::<Dx11CustomOutput>()
.map_err(|_| anyhow::anyhow!("producer returned a non-DirectX output"))?
.validate(context, extent)?
};
if draw.content.image_color_mode() != gpui::ImageColorMode::EncodedSrgb {
let mut description = D3D11_TEXTURE2D_DESC::default();
unsafe {
source.texture.GetDesc(&mut description);
}
anyhow::ensure!(
description.Format == DXGI_FORMAT_R16G16B16A16_FLOAT,
"linear P3 image metadata requires RGBA16F texture"
);
}
let coverage = if draw.coverage.is_some() {
let key = CoverageKey::new(draw)?;
if let Some(mask) = compositor.masks.get(&key) {
Some(mask.clone())
} else {
let mask =
self.render_custom_mask(&coverage_scene(draw, extent)?, extent)?;
let bytes = extent.width.0 as usize * extent.height.0 as usize * 4
+ key.byte_len();
if compositor.mask_bytes + bytes > 64 * 1024 * 1024 {
compositor.masks.clear();
compositor.mask_bytes = 0;
}
if bytes <= 64 * 1024 * 1024 {
compositor.mask_bytes += bytes;
compositor.masks.insert(&key, mask.clone());
}
Some(mask)
}
} else {
None
};
Ok(PreparedDraw {
source,
coverage,
native,
wide,
})
})?;
prepared.push(prepared_draw);
}
batch.submit()?;
Ok(prepared)
})();
self.custom_compositor = Some(compositor);
result
}
fn render_custom_mask(
&mut self,
scene: &Scene,
extent: Size<DevicePixels>,
) -> Result<crate::custom_draw::TextureBundle> {
let devices = self.devices.as_ref().context("devices missing")?.clone();
let target = crate::custom_draw::allocate_texture(&devices.device, extent)?;
let (path_intermediate_texture, path_intermediate_srv) = create_path_intermediate_texture(
&devices.device,
extent.width.0 as u32,
extent.height.0 as u32,
)?;
let (path_intermediate_msaa_texture, path_intermediate_msaa_view) =
create_path_intermediate_msaa_texture_and_view(
&devices.device,
extent.width.0 as u32,
extent.height.0 as u32,
)?;
let original = self.resources.take().context("resources missing")?;
self.resources = Some(DirectXResources {
swap_chain: original.swap_chain.clone(),
render_target: Some(target.texture.clone()),
render_target_view: Some(target.rtv.clone()),
path_intermediate_texture,
path_intermediate_srv,
path_intermediate_msaa_texture,
path_intermediate_msaa_view,
viewport: crate::custom_draw::viewport(extent),
});
let wide = self.wide_sdr.take();
let effects = self.backdrops.take();
let result = self.render(scene, WindowBackgroundAppearance::Transparent);
self.backdrops = effects;
self.wide_sdr = wide;
unsafe {
devices
.device_context
.OMSetRenderTargets(Some(slice::from_ref(&original.render_target_view)), None);
devices
.device_context
.RSSetViewports(Some(slice::from_ref(&original.viewport)));
}
self.resources = Some(original);
unsafe {
devices
.device_context
.PSSetShaderResources(0, Some(&[None, None]));
}
result?;
Ok(target)
}
#[cfg(any(test, feature = "test-support"))]
pub(crate) fn render_to_image(
&mut self,
scene: &Scene,
background_appearance: WindowBackgroundAppearance,
) -> Result<image::RgbaImage> {
anyhow::ensure!(
!self.skip_draws,
"render_to_image unavailable while recovering from a lost device"
);
self.render(scene, background_appearance)?;
let devices = self.devices.as_ref().context("devices missing")?;
let device = &devices.device;
let context = &devices.device_context;
let resources = self.resources.as_ref().context("resources missing")?;
let preview;
let render_target = if let Some(wide) = &self.wide_sdr {
preview = wide.preview(devices)?;
&preview
} else {
resources
.render_target
.as_ref()
.context("render target missing")?
};
let mut desc = D3D11_TEXTURE2D_DESC::default();
unsafe { render_target.GetDesc(&mut desc) };
let width = desc.Width;
let height = desc.Height;
let staging_desc = D3D11_TEXTURE2D_DESC {
Usage: D3D11_USAGE_STAGING,
BindFlags: 0,
CPUAccessFlags: D3D11_CPU_ACCESS_READ.0 as u32,
MiscFlags: 0,
MipLevels: 1,
ArraySize: 1,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
..desc
};
let mut staging: Option<ID3D11Texture2D> = None;
unsafe { device.CreateTexture2D(&staging_desc, None, Some(&mut staging))? };
let staging = staging.context("creating staging texture")?;
unsafe { context.CopyResource(&staging, render_target) };
let mut mapped = D3D11_MAPPED_SUBRESOURCE::default();
unsafe { context.Map(&staging, 0, D3D11_MAP_READ, 0, Some(&mut mapped))? };
let row_bytes = (width as usize) * 4;
let mut pixels = vec![0u8; row_bytes * height as usize];
unsafe {
let src = mapped.pData as *const u8;
for row in 0..height as usize {
let s = src.add(row * mapped.RowPitch as usize);
let d = pixels.as_mut_ptr().add(row * row_bytes);
std::ptr::copy_nonoverlapping(s, d, row_bytes);
}
context.Unmap(&staging, 0);
}
for px in pixels.chunks_exact_mut(4) {
px.swap(0, 2);
}
image::RgbaImage::from_raw(width, height, pixels)
.context("Failed to build RgbaImage from staging readback")
}
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(());
}
let buffer_format = self.buffer_format();
if self.wide_sdr.is_some() {
wide_sdr::validate_extent(size(
DevicePixels(width as i32),
DevicePixels(height as i32),
))?;
}
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")?;
if resources.swap_chain.is_none() {
*resources = DirectXResources::offscreen(devices, width, height)?;
self.width = width;
self.height = height;
return Ok(());
}
resources.render_target.take();
resources.render_target_view.take();
unsafe {
resources
.swap_chain
.as_ref()
.context("missing swap chain")?
.ResizeBuffers(
BUFFER_COUNT as u32,
width,
height,
buffer_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);
}
self.width = width;
self.height = height;
self.resize_color_space()?;
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_context,
self.globals
.batch_params_buffer
.as_ref()
.context("batch params buffer missing")?,
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_context,
self.globals
.batch_params_buffer
.as_ref()
.context("batch params buffer missing")?,
start as u32,
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,
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(&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")?;
self.pipelines.underline_pipeline.draw_range(
&devices.device_context,
self.globals
.batch_params_buffer
.as_ref()
.context("batch params buffer missing")?,
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 Some(texture_view) = self.atlas.get_texture_view(texture_id) else {
return Ok(());
};
self.pipelines.mono_sprites.draw_range_with_texture(
&devices.device_context,
&texture_view,
self.globals
.batch_params_buffer
.as_ref()
.context("batch params buffer missing")?,
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 Some(texture_view) = self.atlas.get_texture_view(texture_id) else {
return Ok(());
};
self.pipelines.subpixel_sprites.draw_range_with_texture(
&devices.device_context,
&texture_view,
self.globals
.batch_params_buffer
.as_ref()
.context("batch params buffer missing")?,
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 Some(texture_view) = self.atlas.get_texture_view(texture_id) else {
return Ok(());
};
self.pipelines.poly_sprites.draw_range_with_texture(
&devices.device_context,
&texture_view,
self.globals
.batch_params_buffer
.as_ref()
.context("batch params buffer missing")?,
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<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: gpui::get_gamma_correction_ratios(render_params.GetGamma()),
grayscale_enhanced_contrast: render_params.GetGrayscaleEnhancedContrast(),
subpixel_enhanced_contrast: render_params.GetEnhancedContrast(),
is_bgr: render_params.GetPixelGeometry() == DWRITE_PIXEL_GEOMETRY_BGR,
}
})
}
pub(crate) fn mark_drawable(&mut self) {
self.skip_draws = false;
}
}
impl DirectXResources {
fn offscreen(devices: &DirectXRendererDevices, width: u32, height: u32) -> Result<Self> {
let extent = size(DevicePixels(width as i32), DevicePixels(height as i32));
let target = crate::custom_draw::allocate_texture(&devices.device, extent)?;
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)?;
Ok(Self {
swap_chain: None,
render_target: Some(target.texture),
render_target_view: Some(target.rtv),
path_intermediate_texture,
path_intermediate_srv,
path_intermediate_msaa_texture,
path_intermediate_msaa_view,
viewport: crate::custom_draw::viewport(extent),
})
}
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,
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: Some(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.as_ref().context("missing 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<Self> {
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<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 DirectXGlobalElements {
pub fn new(device: &ID3D11Device) -> Result<Self> {
let global_params_buffer = create_constant_buffer::<GlobalParams>(device)?;
let batch_params_buffer = create_constant_buffer::<BatchParams>(device)?;
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,
batch_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,
is_bgr: u32,
_pad: [u32; 3],
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(C, align(16))]
struct BatchParams {
start_index: u32,
_padding: [u32; 3],
}
const _: () = assert!(std::mem::size_of::<BatchParams>() == 16);
struct PipelineState<T> {
label: &'static str,
vertex: ID3D11VertexShader,
fragment: ID3D11PixelShader,
buffer: ID3D11Buffer,
buffer_size: usize,
view: Option<ID3D11ShaderResourceView>,
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,
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 element_size = std::mem::size_of::<T>();
let required_size = std::mem::size_of_val(data);
anyhow::ensure!(
required_size <= MAX_INSTANCE_BUFFER_SIZE,
"{} buffer needs {required_size} bytes, above the maximum of {MAX_INSTANCE_BUFFER_SIZE}",
self.label
);
let new_buffer_size = data
.len()
.next_power_of_two()
.min(MAX_INSTANCE_BUFFER_SIZE / element_size);
log::debug!(
"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 draw(
&self,
device_context: &ID3D11DeviceContext,
topology: D3D_PRIMITIVE_TOPOLOGY,
vertex_count: u32,
instance_count: u32,
) -> Result<()> {
set_pipeline_state(
device_context,
slice::from_ref(&self.view),
topology,
&self.vertex,
&self.fragment,
&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>],
sampler: &[Option<ID3D11SamplerState>],
instance_count: u32,
) -> Result<()> {
set_pipeline_state(
device_context,
slice::from_ref(&self.view),
D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
&self.vertex,
&self.fragment,
&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_context: &ID3D11DeviceContext,
batch_params_buffer: &ID3D11Buffer,
first_instance: u32,
instance_count: u32,
) -> Result<()> {
anyhow::ensure!(
first_instance as usize + instance_count as usize <= self.buffer_size,
"DirectX instance range exceeds the {} buffer",
self.label
);
update_batch_start(device_context, batch_params_buffer, first_instance)?;
set_pipeline_state(
device_context,
slice::from_ref(&self.view),
D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
&self.vertex,
&self.fragment,
&self.blend_state,
);
unsafe {
device_context.DrawInstanced(4, instance_count, 0, 0);
}
Ok(())
}
fn draw_range_with_texture(
&self,
device_context: &ID3D11DeviceContext,
texture: &[Option<ID3D11ShaderResourceView>],
batch_params_buffer: &ID3D11Buffer,
sampler: &[Option<ID3D11SamplerState>],
first_instance: u32,
instance_count: u32,
) -> Result<()> {
anyhow::ensure!(
first_instance as usize + instance_count as usize <= self.buffer_size,
"DirectX instance range exceeds the {} buffer",
self.label
);
update_batch_start(device_context, batch_params_buffer, first_instance)?;
set_pipeline_state(
device_context,
slice::from_ref(&self.view),
D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
&self.vertex,
&self.fragment,
&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<ID3D11RenderTargetView>,
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 = D3D11_VIEWPORT {
TopLeftX: 0.0,
TopLeftY: 0.0,
Width: width as f32,
Height: height as f32,
MinDepth: 0.0,
MaxDepth: 1.0,
};
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<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_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_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_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_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_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_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_constant_buffer<T>(device: &ID3D11Device) -> Result<Option<ID3D11Buffer>> {
const { assert!(std::mem::size_of::<T>() != 0 && std::mem::size_of::<T>().is_multiple_of(16)) };
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,
MiscFlags: 0,
StructureByteStride: 0,
};
let mut buffer = None;
unsafe { device.CreateBuffer(&desc, None, Some(&mut buffer)) }?;
Ok(buffer)
}
#[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 update_batch_start(
device_context: &ID3D11DeviceContext,
buffer: &ID3D11Buffer,
first_instance: u32,
) -> Result<()> {
update_buffer(
device_context,
buffer,
&[BatchParams {
start_index: first_instance,
_padding: [0; 3],
}],
)
}
#[inline]
fn set_pipeline_state(
device_context: &ID3D11DeviceContext,
buffer_view: &[Option<ID3D11ShaderResourceView>],
topology: D3D_PRIMITIVE_TOPOLOGY,
vertex_shader: &ID3D11VertexShader,
fragment_shader: &ID3D11PixelShader,
blend_state: &ID3D11BlendState,
) {
unsafe {
device_context.VSSetShaderResources(1, Some(buffer_view));
device_context.PSSetShaderResources(1, Some(buffer_view));
device_context.IASetPrimitiveTopology(topology);
device_context.VSSetShader(vertex_shader, None);
device_context.PSSetShader(fragment_shader, None);
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<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: 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<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/{}", 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::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
))
}
}
#[cfg(any(test, feature = "test-support"))]
pub struct Dx11HeadlessRenderer(DirectXRenderer);
#[cfg(any(test, feature = "test-support"))]
impl Dx11HeadlessRenderer {
pub fn layer_texture_bytes(&self) -> u64 {
self.0
.backdrops
.as_ref()
.map(|effects| effects.groups.texture_bytes())
.unwrap_or(0)
}
pub fn effect_texture_count(&self) -> usize {
self.0
.backdrops
.as_ref()
.map(|effects| effects.texture_count())
.unwrap_or(0)
}
pub fn backdrop_cache_stats(&self) -> gpui::BackdropCacheStats {
self.0
.backdrops
.as_ref()
.map(|e| e.cache.stats)
.unwrap_or_default()
}
pub fn layer_cache_stats(&self) -> gpui::LayerCacheStats {
self.0
.backdrops
.as_ref()
.map(|e| e.groups.stats)
.unwrap_or_default()
}
pub fn set_color_mode(&mut self, mode: WindowColorMode) -> Result<gpui::WindowColorStatus> {
self.0
.set_color_mode(mode, WindowBackgroundAppearance::Opaque)
}
pub fn new() -> Result<Self> {
let (mut device, mut immediate) = (None, None);
unsafe {
D3D11CreateDevice(
None,
D3D_DRIVER_TYPE_WARP,
windows::Win32::Foundation::HMODULE::default(),
D3D11_CREATE_DEVICE_BGRA_SUPPORT,
Some(&[D3D_FEATURE_LEVEL_11_0]),
D3D11_SDK_VERSION,
Some(&mut device),
None,
Some(&mut immediate),
)?;
}
let device = device.context("missing WARP device")?;
let device_context = immediate.context("missing WARP context")?;
let dxgi: IDXGIDevice = device.cast()?;
let adapter: IDXGIAdapter1 = unsafe { dxgi.GetAdapter()? }.cast()?;
let dxgi_factory: IDXGIFactory6 = unsafe { adapter.GetParent()? };
let devices = DirectXRendererDevices {
adapter,
dxgi_factory,
device,
device_context,
dxgi_device: None,
annotation: None,
};
let atlas = Arc::new(DirectXAtlas::new(&devices.device, &devices.device_context));
set_rasterizer_state(&devices.device, &devices.device_context)?;
let resources = DirectXResources::offscreen(&devices, 1, 1)?;
let globals = DirectXGlobalElements::new(&devices.device)?;
let pipelines = DirectXRenderPipelines::new(&devices.device)?;
Ok(Self(DirectXRenderer {
hwnd: HWND::default(),
atlas,
devices: Some(devices),
resources: Some(resources),
globals,
pipelines,
custom_compositor: None,
custom_retirement: Vec::new(),
backdrops: None,
shaders: None,
wide_sdr: None,
color_mode: WindowColorMode::Default,
color_display: Default::default(),
direct_composition: None,
font_info: DirectXRenderer::get_font_info(),
width: 1,
height: 1,
skip_draws: false,
}))
}
}
#[cfg(any(test, feature = "test-support"))]
impl PlatformHeadlessRenderer for Dx11HeadlessRenderer {
fn render_scene_to_image(
&mut self,
scene: &Scene,
size: Size<DevicePixels>,
) -> Result<image::RgbaImage> {
self.0.resize(size)?;
self.0
.render_to_image(scene, WindowBackgroundAppearance::Transparent)
}
fn render_scene(&mut self, scene: &Scene, size: Size<DevicePixels>) -> Result<()> {
self.0.resize(size)?;
self.0
.render(scene, WindowBackgroundAppearance::Transparent)
}
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
self.0.sprite_atlas()
}
}