2571 lines
92 KiB
Rust
2571 lines
92 KiB
Rust
use std::{
|
||
slice,
|
||
sync::{Arc, OnceLock},
|
||
};
|
||
|
||
use ::util::ResultExt;
|
||
use anyhow::{Context, Result};
|
||
use windows::{
|
||
Win32::{
|
||
Foundation::HWND,
|
||
Graphics::{
|
||
Direct3D::*,
|
||
Direct3D11::*,
|
||
DirectComposition::*,
|
||
DirectWrite::*,
|
||
Dxgi::{Common::*, *},
|
||
},
|
||
},
|
||
core::Interface,
|
||
};
|
||
|
||
use crate::directx_renderer::shader_resources::{RawShaderBytes, ShaderModule, ShaderTarget};
|
||
use crate::*;
|
||
use gpui::*;
|
||
|
||
/// The largest blur radius in a scene-space filter chain, in device pixels — used to size the
|
||
/// blur kernel and the dilated region the blur passes are scissored to.
|
||
///
|
||
/// The `match` is exhaustive on purpose: adding a [`ScaledFilter`] variant breaks it here,
|
||
/// forcing this backend to handle (or deliberately ignore) the new filter rather than silently
|
||
/// dropping it.
|
||
fn max_blur_radius(filters: &[ScaledFilter]) -> f32 {
|
||
filters.iter().fold(0.0, |acc, filter| match filter {
|
||
ScaledFilter::Blur(radius) => acc.max(radius.0),
|
||
})
|
||
}
|
||
|
||
pub(crate) const DISABLE_DIRECT_COMPOSITION: &str = "GPUI_DISABLE_DIRECT_COMPOSITION";
|
||
const RENDER_TARGET_FORMAT: DXGI_FORMAT = DXGI_FORMAT_B8G8R8A8_UNORM;
|
||
// This configuration is used for MSAA rendering on paths only, and it's guaranteed to be supported by DirectX 11.
|
||
const PATH_MULTISAMPLE_COUNT: u32 = 4;
|
||
|
||
/// Number of content-filter (`filter`) nesting levels that get their own isolated group target.
|
||
/// Two covers the realistic "a blurred element inside another blurred element" case; deeper nests
|
||
/// render inline (unblurred at the inner level) rather than allocating unbounded VRAM. Must match
|
||
/// the wgpu backend's `MAX_FILTER_DEPTH` so nested blur renders consistently across platforms.
|
||
const MAX_FILTER_DEPTH: usize = 2;
|
||
|
||
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,
|
||
direct_composition: Option<DirectComposition>,
|
||
font_info: &'static FontInfo,
|
||
|
||
width: u32,
|
||
height: u32,
|
||
|
||
/// Whether we want to skip drawing due to device lost events.
|
||
///
|
||
/// In that case we want to discard the first frame that we draw as we got reset in the middle of a frame
|
||
/// meaning we lost all the allocated gpu textures and scene resources.
|
||
skip_draws: bool,
|
||
|
||
/// The render target currently bound for the main scene this frame (the offscreen
|
||
/// `scene_color` when blur filters are present, a content-filter group texture inside such a
|
||
/// group, or the swapchain otherwise). `draw_paths_to_intermediate` restores to this after
|
||
/// its own pass so paths land on the correct target.
|
||
active_render_target: Option<ID3D11RenderTargetView>,
|
||
}
|
||
|
||
/// Direct3D objects
|
||
#[derive(Clone)]
|
||
pub(crate) struct DirectXRendererDevices {
|
||
pub(crate) adapter: IDXGIAdapter1,
|
||
pub(crate) dxgi_factory: IDXGIFactory6,
|
||
pub(crate) device: ID3D11Device,
|
||
pub(crate) device_context: ID3D11DeviceContext,
|
||
dxgi_device: Option<IDXGIDevice>,
|
||
}
|
||
|
||
struct DirectXResources {
|
||
// Direct3D rendering objects
|
||
swap_chain: IDXGISwapChain1,
|
||
render_target: Option<ID3D11Texture2D>,
|
||
render_target_view: Option<ID3D11RenderTargetView>,
|
||
|
||
// Path intermediate textures (with MSAA)
|
||
path_intermediate_texture: ID3D11Texture2D,
|
||
path_intermediate_srv: Option<ID3D11ShaderResourceView>,
|
||
path_intermediate_msaa_texture: ID3D11Texture2D,
|
||
path_intermediate_msaa_view: Option<ID3D11RenderTargetView>,
|
||
|
||
// Offscreen targets for blur filters (each is render-target + shader-resource).
|
||
blur: BlurResources,
|
||
|
||
// Cached viewport
|
||
viewport: D3D11_VIEWPORT,
|
||
}
|
||
|
||
/// Offscreen render targets used by the blur filters. The scene is rendered into `scene_color`
|
||
/// (so filters can sample it), `ping`/`pong` are half-resolution scratch for the separable
|
||
/// gaussian, and `groups` isolate content-filter (`filter`) subtrees — one per nesting level
|
||
/// (indexed by isolation depth), up to [`MAX_FILTER_DEPTH`], so nested content blurs isolate
|
||
/// correctly; deeper nests render inline.
|
||
struct BlurResources {
|
||
#[expect(dead_code)]
|
||
scene_color: ID3D11Texture2D,
|
||
scene_color_rtv: Option<ID3D11RenderTargetView>,
|
||
scene_color_srv: Option<ID3D11ShaderResourceView>,
|
||
#[expect(dead_code)]
|
||
ping: ID3D11Texture2D,
|
||
ping_rtv: Option<ID3D11RenderTargetView>,
|
||
ping_srv: Option<ID3D11ShaderResourceView>,
|
||
#[expect(dead_code)]
|
||
pong: ID3D11Texture2D,
|
||
pong_rtv: Option<ID3D11RenderTargetView>,
|
||
pong_srv: Option<ID3D11ShaderResourceView>,
|
||
// Kept alive for the lifetime of their views; indexed by isolation depth.
|
||
#[expect(dead_code)]
|
||
groups: Vec<ID3D11Texture2D>,
|
||
group_rtvs: Vec<Option<ID3D11RenderTargetView>>,
|
||
group_srvs: Vec<Option<ID3D11ShaderResourceView>>,
|
||
}
|
||
|
||
impl BlurResources {
|
||
fn new(device: &ID3D11Device, width: u32, height: u32) -> Result<Self> {
|
||
let half_w = (width / 2).max(1);
|
||
let half_h = (height / 2).max(1);
|
||
let (scene_color, scene_color_rtv, scene_color_srv) =
|
||
create_color_target(device, width, height)?;
|
||
let (ping, ping_rtv, ping_srv) = create_color_target(device, half_w, half_h)?;
|
||
let (pong, pong_rtv, pong_srv) = create_color_target(device, half_w, half_h)?;
|
||
let mut groups = Vec::with_capacity(MAX_FILTER_DEPTH);
|
||
let mut group_rtvs = Vec::with_capacity(MAX_FILTER_DEPTH);
|
||
let mut group_srvs = Vec::with_capacity(MAX_FILTER_DEPTH);
|
||
for _ in 0..MAX_FILTER_DEPTH {
|
||
let (group, group_rtv, group_srv) = create_color_target(device, width, height)?;
|
||
groups.push(group);
|
||
group_rtvs.push(group_rtv);
|
||
group_srvs.push(group_srv);
|
||
}
|
||
Ok(Self {
|
||
scene_color,
|
||
scene_color_rtv,
|
||
scene_color_srv,
|
||
ping,
|
||
ping_rtv,
|
||
ping_srv,
|
||
pong,
|
||
pong_rtv,
|
||
pong_srv,
|
||
groups,
|
||
group_rtvs,
|
||
group_srvs,
|
||
})
|
||
}
|
||
}
|
||
|
||
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>,
|
||
// Blur (backdrop-filter / filter). These don't use the generic PipelineState since they
|
||
// sample a texture rather than read a structured instance buffer; their parameters live in
|
||
// a dedicated constant buffer at register b1.
|
||
blur_downsample_vertex: ID3D11VertexShader,
|
||
blur_downsample_fragment: ID3D11PixelShader,
|
||
blur_vertex: ID3D11VertexShader,
|
||
blur_fragment: ID3D11PixelShader,
|
||
blur_composite_vertex: ID3D11VertexShader,
|
||
blur_composite_fragment: ID3D11PixelShader,
|
||
blur_params_buffer: ID3D11Buffer,
|
||
blur_blend_replace: ID3D11BlendState,
|
||
blur_blend_composite: ID3D11BlendState,
|
||
}
|
||
|
||
struct DirectXGlobalElements {
|
||
global_params_buffer: Option<ID3D11Buffer>,
|
||
sampler: Option<ID3D11SamplerState>,
|
||
}
|
||
|
||
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,
|
||
active_render_target: None,
|
||
})
|
||
}
|
||
|
||
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)));
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
#[inline]
|
||
fn present(&mut self) -> Result<()> {
|
||
let result = unsafe {
|
||
self.resources
|
||
.as_ref()
|
||
.expect("resources missing")
|
||
.swap_chain
|
||
.Present(0, DXGI_PRESENT(0))
|
||
};
|
||
result.ok().context("Presenting swap chain failed")
|
||
}
|
||
|
||
pub(crate) fn handle_device_lost(&mut self, directx_devices: &DirectXDevices) -> Result<()> {
|
||
try_to_recover_from_device_lost(|| {
|
||
self.handle_device_lost_impl(directx_devices)
|
||
.context("DirectXRenderer handling device lost")
|
||
})
|
||
}
|
||
|
||
fn handle_device_lost_impl(&mut self, directx_devices: &DirectXDevices) -> Result<()> {
|
||
let disable_direct_composition = self.direct_composition.is_none();
|
||
|
||
unsafe {
|
||
#[cfg(debug_assertions)]
|
||
if let Some(devices) = &self.devices {
|
||
report_live_objects(&devices.device)
|
||
.context("Failed to report live objects after device lost")
|
||
.log_err();
|
||
}
|
||
|
||
self.resources.take();
|
||
if let Some(devices) = &self.devices {
|
||
devices.device_context.OMSetRenderTargets(None, None);
|
||
devices.device_context.ClearState();
|
||
devices.device_context.Flush();
|
||
#[cfg(debug_assertions)]
|
||
report_live_objects(&devices.device)
|
||
.context("Failed to report live objects after device lost")
|
||
.log_err();
|
||
}
|
||
|
||
self.direct_composition.take();
|
||
self.devices.take();
|
||
}
|
||
|
||
let devices = DirectXRendererDevices::new(directx_devices, disable_direct_composition)
|
||
.context("Recreating DirectX devices")?;
|
||
let resources = DirectXResources::new(
|
||
&devices,
|
||
self.width,
|
||
self.height,
|
||
self.hwnd,
|
||
disable_direct_composition,
|
||
)
|
||
.context("Creating DirectX resources")?;
|
||
let globals = DirectXGlobalElements::new(&devices.device)
|
||
.context("Creating DirectXGlobalElements")?;
|
||
let pipelines = DirectXRenderPipelines::new(&devices.device)
|
||
.context("Creating DirectXRenderPipelines")?;
|
||
|
||
let direct_composition = if disable_direct_composition {
|
||
None
|
||
} else {
|
||
let composition =
|
||
DirectComposition::new(devices.dxgi_device.as_ref().unwrap(), self.hwnd)?;
|
||
composition.set_swap_chain(&resources.swap_chain)?;
|
||
Some(composition)
|
||
};
|
||
|
||
self.atlas
|
||
.handle_device_lost(&devices.device, &devices.device_context);
|
||
|
||
unsafe {
|
||
devices
|
||
.device_context
|
||
.OMSetRenderTargets(Some(slice::from_ref(&resources.render_target_view)), None);
|
||
}
|
||
self.devices = Some(devices);
|
||
self.resources = Some(resources);
|
||
self.globals = globals;
|
||
self.pipelines = pipelines;
|
||
self.direct_composition = direct_composition;
|
||
self.skip_draws = true;
|
||
Ok(())
|
||
}
|
||
|
||
pub(crate) fn draw(
|
||
&mut self,
|
||
scene: &Scene,
|
||
background_appearance: WindowBackgroundAppearance,
|
||
) -> Result<()> {
|
||
if self.skip_draws {
|
||
// skip drawing this frame, we just recovered from a device lost event
|
||
// and so likely do not have the textures anymore that are required for drawing
|
||
return Ok(());
|
||
}
|
||
self.pre_draw(&match background_appearance {
|
||
WindowBackgroundAppearance::Opaque => [1.0f32; 4],
|
||
_ => [0.0f32; 4],
|
||
})?;
|
||
|
||
self.upload_scene_buffers(scene)?;
|
||
|
||
// Only route through the offscreen scene texture when the scene contains blur filters;
|
||
// otherwise render straight to the swapchain exactly as before.
|
||
let use_offscreen =
|
||
!scene.backdrop_filters.is_empty() || !scene.filter_boundaries.is_empty();
|
||
|
||
// Clone the views we need (AddRef) so the loop can rebind render targets without holding a
|
||
// borrow of `self` across the `&mut self` draw_* calls.
|
||
let (scene_rtv, scene_srv, group_rtvs, group_srvs, swapchain_rtv) = {
|
||
let r = self.resources.as_ref().context("resources missing")?;
|
||
(
|
||
r.blur.scene_color_rtv.clone(),
|
||
r.blur.scene_color_srv.clone(),
|
||
r.blur.group_rtvs.clone(),
|
||
r.blur.group_srvs.clone(),
|
||
r.render_target_view.clone(),
|
||
)
|
||
};
|
||
let ctx = self
|
||
.devices
|
||
.as_ref()
|
||
.context("devices missing")?
|
||
.device_context
|
||
.clone();
|
||
|
||
if use_offscreen {
|
||
unsafe {
|
||
if let Some(rtv) = scene_rtv.as_ref() {
|
||
ctx.ClearRenderTargetView(rtv, &[0.0; 4]);
|
||
}
|
||
ctx.OMSetRenderTargets(Some(slice::from_ref(&scene_rtv)), None);
|
||
}
|
||
self.active_render_target = scene_rtv.clone();
|
||
} else {
|
||
self.active_render_target = swapchain_rtv.clone();
|
||
}
|
||
|
||
// Current target for the main scene + a parent stack for content-filter groups.
|
||
let mut current_rtv = self.active_render_target.clone();
|
||
let mut current_srv = if use_offscreen {
|
||
scene_srv.clone()
|
||
} else {
|
||
None
|
||
};
|
||
// (parent_rtv, parent_srv, isolated)
|
||
let mut filter_stack: Vec<(
|
||
Option<ID3D11RenderTargetView>,
|
||
Option<ID3D11ShaderResourceView>,
|
||
bool,
|
||
)> = Vec::new();
|
||
|
||
for batch in scene.batches() {
|
||
match batch {
|
||
PrimitiveBatch::Shadows(range) => self.draw_shadows(range.start, range.len()),
|
||
PrimitiveBatch::Quads(range) => self.draw_quads(range.start, range.len()),
|
||
PrimitiveBatch::Paths(range) => {
|
||
let paths = &scene.paths[range];
|
||
self.draw_paths_to_intermediate(paths)?;
|
||
self.draw_paths_from_intermediate(paths)
|
||
}
|
||
PrimitiveBatch::Underlines(range) => self.draw_underlines(range.start, range.len()),
|
||
PrimitiveBatch::MonochromeSprites { texture_id, range } => {
|
||
self.draw_monochrome_sprites(texture_id, range.start, range.len())
|
||
}
|
||
PrimitiveBatch::SubpixelSprites { texture_id, range } => {
|
||
self.draw_subpixel_sprites(texture_id, range.start, range.len())
|
||
}
|
||
PrimitiveBatch::PolychromeSprites { texture_id, range } => {
|
||
self.draw_polychrome_sprites(texture_id, range.start, range.len())
|
||
}
|
||
PrimitiveBatch::Surfaces(range) => self.draw_surfaces(&scene.surfaces[range]),
|
||
PrimitiveBatch::BackdropFilters(range) => {
|
||
let result = (|| {
|
||
for filter in &scene.backdrop_filters[range] {
|
||
self.dx_blur_and_composite(
|
||
¤t_srv,
|
||
¤t_rtv,
|
||
filter.bounds,
|
||
filter.content_mask.bounds,
|
||
corner_radii_array(filter.corner_radii),
|
||
max_blur_radius(&filter.filters),
|
||
filter.opacity,
|
||
true,
|
||
)?;
|
||
}
|
||
Ok::<(), anyhow::Error>(())
|
||
})();
|
||
// Restore the current target for subsequent batches.
|
||
unsafe {
|
||
ctx.OMSetRenderTargets(Some(slice::from_ref(¤t_rtv)), None);
|
||
}
|
||
result
|
||
}
|
||
PrimitiveBatch::FilterBoundary(ix) => {
|
||
let boundary = scene.filter_boundaries[ix].clone();
|
||
if boundary.is_start {
|
||
// Each isolated nesting level uses its own group target from the pool
|
||
// (indexed by current isolation depth). Beyond the pool size
|
||
// (MAX_FILTER_DEPTH) deeper filters render inline without isolation rather
|
||
// than corrupting an outer group.
|
||
let depth = filter_stack.iter().filter(|entry| entry.2).count();
|
||
if depth < group_rtvs.len() {
|
||
filter_stack.push((current_rtv.clone(), current_srv.clone(), true));
|
||
current_rtv = group_rtvs[depth].clone();
|
||
current_srv = group_srvs[depth].clone();
|
||
self.active_render_target = current_rtv.clone();
|
||
unsafe {
|
||
if let Some(rtv) = current_rtv.as_ref() {
|
||
ctx.ClearRenderTargetView(rtv, &[0.0; 4]);
|
||
}
|
||
ctx.OMSetRenderTargets(Some(slice::from_ref(¤t_rtv)), None);
|
||
}
|
||
} else {
|
||
filter_stack.push((current_rtv.clone(), current_srv.clone(), false));
|
||
}
|
||
Ok(())
|
||
} else if let Some((parent_rtv, parent_srv, isolated)) = filter_stack.pop() {
|
||
let result = if isolated {
|
||
self.dx_blur_and_composite(
|
||
¤t_srv,
|
||
&parent_rtv,
|
||
boundary.bounds,
|
||
boundary.content_mask.bounds,
|
||
corner_radii_array(boundary.corner_radii),
|
||
max_blur_radius(&boundary.filters),
|
||
boundary.opacity,
|
||
false,
|
||
)
|
||
} else {
|
||
Ok(())
|
||
};
|
||
current_rtv = parent_rtv;
|
||
current_srv = parent_srv;
|
||
self.active_render_target = current_rtv.clone();
|
||
unsafe {
|
||
ctx.OMSetRenderTargets(Some(slice::from_ref(¤t_rtv)), None);
|
||
}
|
||
result
|
||
} else {
|
||
Ok(())
|
||
}
|
||
}
|
||
}
|
||
.context(format!(
|
||
"scene too large:\
|
||
{} paths, {} shadows, {} quads, {} underlines, {} mono, {} subpixel, {} poly, {} surfaces",
|
||
scene.paths.len(),
|
||
scene.shadows.len(),
|
||
scene.quads.len(),
|
||
scene.underlines.len(),
|
||
scene.monochrome_sprites.len(),
|
||
scene.subpixel_sprites.len(),
|
||
scene.polychrome_sprites.len(),
|
||
scene.surfaces.len(),
|
||
))?;
|
||
}
|
||
|
||
// Present the offscreen scene by blitting it into the swapchain.
|
||
if use_offscreen {
|
||
self.dx_blit(&scene_srv, &swapchain_rtv)?;
|
||
}
|
||
self.active_render_target = None;
|
||
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;
|
||
|
||
// Clear the render target before resizing
|
||
let devices = self.devices.as_ref().context("devices missing")?;
|
||
unsafe { devices.device_context.OMSetRenderTargets(None, None) };
|
||
let resources = self.resources.as_mut().context("resources missing")?;
|
||
resources.render_target.take();
|
||
resources.render_target_view.take();
|
||
|
||
// Resizing the swap chain requires a call to the underlying DXGI adapter, which can return the device removed error.
|
||
// The app might have moved to a monitor that's attached to a different graphics device.
|
||
// When a graphics device is removed or reset, the desktop resolution often changes, resulting in a window size change.
|
||
// But here we just return the error, because we are handling device lost scenarios elsewhere.
|
||
unsafe {
|
||
resources
|
||
.swap_chain
|
||
.ResizeBuffers(
|
||
BUFFER_COUNT as u32,
|
||
width,
|
||
height,
|
||
RENDER_TARGET_FORMAT,
|
||
DXGI_SWAP_CHAIN_FLAG(0),
|
||
)
|
||
.context("Failed to resize swap chain")?;
|
||
}
|
||
|
||
resources.recreate_resources(devices, width, height)?;
|
||
|
||
unsafe {
|
||
devices
|
||
.device_context
|
||
.OMSetRenderTargets(Some(slice::from_ref(&resources.render_target_view)), None);
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
fn upload_scene_buffers(&mut self, scene: &Scene) -> Result<()> {
|
||
let devices = self.devices.as_ref().context("devices missing")?;
|
||
|
||
if !scene.shadows.is_empty() {
|
||
self.pipelines.shadow_pipeline.update_buffer(
|
||
&devices.device,
|
||
&devices.device_context,
|
||
&scene.shadows,
|
||
)?;
|
||
}
|
||
|
||
if !scene.quads.is_empty() {
|
||
self.pipelines.quad_pipeline.update_buffer(
|
||
&devices.device,
|
||
&devices.device_context,
|
||
&scene.quads,
|
||
)?;
|
||
}
|
||
|
||
if !scene.underlines.is_empty() {
|
||
self.pipelines.underline_pipeline.update_buffer(
|
||
&devices.device,
|
||
&devices.device_context,
|
||
&scene.underlines,
|
||
)?;
|
||
}
|
||
|
||
if !scene.monochrome_sprites.is_empty() {
|
||
self.pipelines.mono_sprites.update_buffer(
|
||
&devices.device,
|
||
&devices.device_context,
|
||
&scene.monochrome_sprites,
|
||
)?;
|
||
}
|
||
|
||
if !scene.subpixel_sprites.is_empty() {
|
||
self.pipelines.subpixel_sprites.update_buffer(
|
||
&devices.device,
|
||
&devices.device_context,
|
||
&scene.subpixel_sprites,
|
||
)?;
|
||
}
|
||
|
||
if !scene.polychrome_sprites.is_empty() {
|
||
self.pipelines.poly_sprites.update_buffer(
|
||
&devices.device,
|
||
&devices.device_context,
|
||
&scene.polychrome_sprites,
|
||
)?;
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
fn draw_shadows(&mut self, start: usize, len: usize) -> Result<()> {
|
||
if len == 0 {
|
||
return Ok(());
|
||
}
|
||
let devices = self.devices.as_ref().context("devices missing")?;
|
||
self.pipelines.shadow_pipeline.draw_range(
|
||
&devices.device,
|
||
&devices.device_context,
|
||
slice::from_ref(
|
||
&self
|
||
.resources
|
||
.as_ref()
|
||
.context("resources missing")?
|
||
.viewport,
|
||
),
|
||
slice::from_ref(&self.globals.global_params_buffer),
|
||
4,
|
||
start as u32,
|
||
len as u32,
|
||
)
|
||
}
|
||
|
||
fn draw_quads(&mut self, start: usize, len: usize) -> Result<()> {
|
||
if len == 0 {
|
||
return Ok(());
|
||
}
|
||
let devices = self.devices.as_ref().context("devices missing")?;
|
||
self.pipelines.quad_pipeline.draw_range(
|
||
&devices.device,
|
||
&devices.device_context,
|
||
slice::from_ref(
|
||
&self
|
||
.resources
|
||
.as_ref()
|
||
.context("resources missing")?
|
||
.viewport,
|
||
),
|
||
slice::from_ref(&self.globals.global_params_buffer),
|
||
4,
|
||
start as u32,
|
||
len as u32,
|
||
)
|
||
}
|
||
|
||
fn draw_paths_to_intermediate(&mut self, paths: &[Path<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")?;
|
||
// Clear intermediate MSAA texture
|
||
unsafe {
|
||
devices.device_context.ClearRenderTargetView(
|
||
resources.path_intermediate_msaa_view.as_ref().unwrap(),
|
||
&[0.0; 4],
|
||
);
|
||
// Set intermediate MSAA texture as render target
|
||
devices.device_context.OMSetRenderTargets(
|
||
Some(slice::from_ref(&resources.path_intermediate_msaa_view)),
|
||
None,
|
||
);
|
||
}
|
||
|
||
// Collect all vertices and sprites for a single draw call
|
||
let mut vertices = Vec::new();
|
||
|
||
for path in paths {
|
||
vertices.extend(path.vertices.iter().map(|v| PathRasterizationSprite {
|
||
xy_position: v.xy_position,
|
||
st_position: v.st_position,
|
||
color: path.color,
|
||
bounds: path.clipped_bounds(),
|
||
}));
|
||
}
|
||
|
||
self.pipelines.path_rasterization_pipeline.update_buffer(
|
||
&devices.device,
|
||
&devices.device_context,
|
||
&vertices,
|
||
)?;
|
||
|
||
self.pipelines.path_rasterization_pipeline.draw(
|
||
&devices.device_context,
|
||
slice::from_ref(&resources.viewport),
|
||
slice::from_ref(&self.globals.global_params_buffer),
|
||
D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
|
||
vertices.len() as u32,
|
||
1,
|
||
)?;
|
||
|
||
// Resolve MSAA to non-MSAA intermediate texture
|
||
unsafe {
|
||
devices.device_context.ResolveSubresource(
|
||
&resources.path_intermediate_texture,
|
||
0,
|
||
&resources.path_intermediate_msaa_texture,
|
||
0,
|
||
RENDER_TARGET_FORMAT,
|
||
);
|
||
// Restore the active render target (the offscreen scene/group target when blurring,
|
||
// otherwise the swapchain) so the path sprites land on the correct surface.
|
||
let restore_target = if self.active_render_target.is_some() {
|
||
&self.active_render_target
|
||
} else {
|
||
&resources.render_target_view
|
||
};
|
||
devices
|
||
.device_context
|
||
.OMSetRenderTargets(Some(slice::from_ref(restore_target)), None);
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
fn draw_paths_from_intermediate(&mut self, paths: &[Path<ScaledPixels>]) -> Result<()> {
|
||
let Some(first_path) = paths.first() else {
|
||
return Ok(());
|
||
};
|
||
|
||
// When copying paths from the intermediate texture to the drawable,
|
||
// each pixel must only be copied once, in case of transparent paths.
|
||
//
|
||
// If all paths have the same draw order, then their bounds are all
|
||
// disjoint, so we can copy each path's bounds individually. If this
|
||
// batch combines different draw orders, we perform a single copy
|
||
// for a minimal spanning rect.
|
||
let sprites = if paths.last().unwrap().order == first_path.order {
|
||
paths
|
||
.iter()
|
||
.map(|path| PathSprite {
|
||
bounds: path.clipped_bounds(),
|
||
})
|
||
.collect::<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,
|
||
)?;
|
||
|
||
// Draw the sprites with the path texture
|
||
self.pipelines.path_sprite_pipeline.draw_with_texture(
|
||
&devices.device_context,
|
||
slice::from_ref(&resources.path_intermediate_srv),
|
||
slice::from_ref(&resources.viewport),
|
||
slice::from_ref(&self.globals.global_params_buffer),
|
||
slice::from_ref(&self.globals.sampler),
|
||
sprites.len() as u32,
|
||
)
|
||
}
|
||
|
||
fn draw_underlines(&mut self, start: usize, len: usize) -> Result<()> {
|
||
if len == 0 {
|
||
return Ok(());
|
||
}
|
||
let devices = self.devices.as_ref().context("devices missing")?;
|
||
let resources = self.resources.as_ref().context("resources missing")?;
|
||
self.pipelines.underline_pipeline.draw_range(
|
||
&devices.device,
|
||
&devices.device_context,
|
||
slice::from_ref(&resources.viewport),
|
||
slice::from_ref(&self.globals.global_params_buffer),
|
||
4,
|
||
start as u32,
|
||
len as u32,
|
||
)
|
||
}
|
||
|
||
fn draw_monochrome_sprites(
|
||
&mut self,
|
||
texture_id: AtlasTextureId,
|
||
start: usize,
|
||
len: usize,
|
||
) -> Result<()> {
|
||
if len == 0 {
|
||
return Ok(());
|
||
}
|
||
let devices = self.devices.as_ref().context("devices missing")?;
|
||
let resources = self.resources.as_ref().context("resources missing")?;
|
||
let texture_view = self.atlas.get_texture_view(texture_id);
|
||
self.pipelines.mono_sprites.draw_range_with_texture(
|
||
&devices.device,
|
||
&devices.device_context,
|
||
&texture_view,
|
||
slice::from_ref(&resources.viewport),
|
||
slice::from_ref(&self.globals.global_params_buffer),
|
||
slice::from_ref(&self.globals.sampler),
|
||
start as u32,
|
||
len as u32,
|
||
)
|
||
}
|
||
|
||
fn draw_subpixel_sprites(
|
||
&mut self,
|
||
texture_id: AtlasTextureId,
|
||
start: usize,
|
||
len: usize,
|
||
) -> Result<()> {
|
||
if len == 0 {
|
||
return Ok(());
|
||
}
|
||
let devices = self.devices.as_ref().context("devices missing")?;
|
||
let resources = self.resources.as_ref().context("resources missing")?;
|
||
let texture_view = self.atlas.get_texture_view(texture_id);
|
||
self.pipelines.subpixel_sprites.draw_range_with_texture(
|
||
&devices.device,
|
||
&devices.device_context,
|
||
&texture_view,
|
||
slice::from_ref(&resources.viewport),
|
||
slice::from_ref(&self.globals.global_params_buffer),
|
||
slice::from_ref(&self.globals.sampler),
|
||
start as u32,
|
||
len as u32,
|
||
)
|
||
}
|
||
|
||
fn draw_polychrome_sprites(
|
||
&mut self,
|
||
texture_id: AtlasTextureId,
|
||
start: usize,
|
||
len: usize,
|
||
) -> Result<()> {
|
||
if len == 0 {
|
||
return Ok(());
|
||
}
|
||
let devices = self.devices.as_ref().context("devices missing")?;
|
||
let resources = self.resources.as_ref().context("resources missing")?;
|
||
let texture_view = self.atlas.get_texture_view(texture_id);
|
||
self.pipelines.poly_sprites.draw_range_with_texture(
|
||
&devices.device,
|
||
&devices.device_context,
|
||
&texture_view,
|
||
slice::from_ref(&resources.viewport),
|
||
slice::from_ref(&self.globals.global_params_buffer),
|
||
slice::from_ref(&self.globals.sampler),
|
||
start as u32,
|
||
len as u32,
|
||
)
|
||
}
|
||
|
||
fn draw_surfaces(&mut self, surfaces: &[PaintSurface]) -> Result<()> {
|
||
if surfaces.is_empty() {
|
||
return Ok(());
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
/// Run a single blur pass: a full-screen (or composite) draw sampling `source_srv` into
|
||
/// `target_rtv`, with `params` in the blur constant buffer (b1).
|
||
#[allow(clippy::too_many_arguments)]
|
||
fn dx_blur_pass(
|
||
&self,
|
||
vertex: &ID3D11VertexShader,
|
||
fragment: &ID3D11PixelShader,
|
||
blend: &ID3D11BlendState,
|
||
target_rtv: &Option<ID3D11RenderTargetView>,
|
||
source_srv: &Option<ID3D11ShaderResourceView>,
|
||
params: BlurParams,
|
||
viewport: &D3D11_VIEWPORT,
|
||
topology: D3D_PRIMITIVE_TOPOLOGY,
|
||
vertex_count: u32,
|
||
clear: bool,
|
||
) -> Result<()> {
|
||
let devices = self.devices.as_ref().context("devices missing")?;
|
||
let ctx = &devices.device_context;
|
||
update_buffer(ctx, &self.pipelines.blur_params_buffer, &[params])?;
|
||
let null_srv: [Option<ID3D11ShaderResourceView>; 1] = [None];
|
||
let blur_params = [Some(self.pipelines.blur_params_buffer.clone())];
|
||
unsafe {
|
||
// Unbind any SRV at slot 0 so the target texture isn't simultaneously bound as input.
|
||
ctx.PSSetShaderResources(0, Some(&null_srv));
|
||
if clear {
|
||
ctx.ClearRenderTargetView(
|
||
target_rtv.as_ref().context("blur target view missing")?,
|
||
&[0.0; 4],
|
||
);
|
||
}
|
||
ctx.OMSetRenderTargets(Some(slice::from_ref(target_rtv)), None);
|
||
ctx.RSSetViewports(Some(slice::from_ref(viewport)));
|
||
ctx.IASetPrimitiveTopology(topology);
|
||
ctx.VSSetShader(vertex, None);
|
||
ctx.PSSetShader(fragment, None);
|
||
ctx.VSSetConstantBuffers(0, Some(slice::from_ref(&self.globals.global_params_buffer)));
|
||
ctx.PSSetConstantBuffers(0, Some(slice::from_ref(&self.globals.global_params_buffer)));
|
||
ctx.VSSetConstantBuffers(1, Some(&blur_params));
|
||
ctx.PSSetConstantBuffers(1, Some(&blur_params));
|
||
ctx.PSSetSamplers(0, Some(slice::from_ref(&self.globals.sampler)));
|
||
ctx.PSSetShaderResources(0, Some(slice::from_ref(source_srv)));
|
||
ctx.OMSetBlendState(blend, None, 0xFFFFFFFF);
|
||
ctx.DrawInstanced(vertex_count, 1, 0, 0);
|
||
// Unbind the source so the target can be rebound as a render target next.
|
||
ctx.PSSetShaderResources(0, Some(&null_srv));
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
/// Blur `source_srv` (full-resolution) using the half-res ping/pong textures and composite the
|
||
/// result into `target_rtv`, clipped to `bounds`/`corner_radii`/`content_mask` and modulated
|
||
/// by `opacity`. Shared by the backdrop and content-filter paths.
|
||
#[allow(clippy::too_many_arguments)]
|
||
fn dx_blur_and_composite(
|
||
&self,
|
||
source_srv: &Option<ID3D11ShaderResourceView>,
|
||
target_rtv: &Option<ID3D11RenderTargetView>,
|
||
bounds: Bounds<ScaledPixels>,
|
||
content_mask: Bounds<ScaledPixels>,
|
||
corner_radii: [f32; 4],
|
||
blur_radius: f32,
|
||
opacity: f32,
|
||
// Backdrop clips to the rounded rect; content (`filter`) bleeds past its bounds.
|
||
clip_rounded: bool,
|
||
) -> Result<()> {
|
||
// Sigma is halved because the blur runs at half resolution.
|
||
let sigma = (blur_radius * 0.5).max(0.0);
|
||
if sigma <= 0.0 {
|
||
return Ok(());
|
||
}
|
||
// Span ±3σ. If that needs more than 32 taps, spread the taps apart (tap_step > 1) rather
|
||
// than truncating the kernel — keeps very large radii from clipping. Matches wgpu.
|
||
let ideal_taps = (3.0 * sigma).ceil();
|
||
let tap_count = ideal_taps.clamp(1.0, 32.0);
|
||
let tap_step = (ideal_taps / tap_count).max(1.0);
|
||
// Content blur bleeds ~3·radius past the box, so its composite quad covers a dilated rect.
|
||
let composite_bounds = if clip_rounded {
|
||
bounds
|
||
} else {
|
||
bounds.dilate(ScaledPixels(3.0 * blur_radius))
|
||
};
|
||
let half_w = (self.width / 2).max(1);
|
||
let half_h = (self.height / 2).max(1);
|
||
let half_vp = D3D11_VIEWPORT {
|
||
TopLeftX: 0.0,
|
||
TopLeftY: 0.0,
|
||
Width: half_w as f32,
|
||
Height: half_h as f32,
|
||
MinDepth: 0.0,
|
||
MaxDepth: 1.0,
|
||
};
|
||
let (full_vp, ping_rtv, ping_srv, pong_rtv, pong_srv) = {
|
||
let r = self.resources.as_ref().context("resources missing")?;
|
||
(
|
||
r.viewport,
|
||
r.blur.ping_rtv.clone(),
|
||
r.blur.ping_srv.clone(),
|
||
r.blur.pong_rtv.clone(),
|
||
r.blur.pong_srv.clone(),
|
||
)
|
||
};
|
||
|
||
// Downsample source -> ping, then separable gaussian ping -> pong -> ping.
|
||
self.dx_blur_pass(
|
||
&self.pipelines.blur_downsample_vertex,
|
||
&self.pipelines.blur_downsample_fragment,
|
||
&self.pipelines.blur_blend_replace,
|
||
&ping_rtv,
|
||
source_srv,
|
||
BlurParams {
|
||
downsample: 1.0,
|
||
..Default::default()
|
||
},
|
||
&half_vp,
|
||
D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
|
||
3,
|
||
true,
|
||
)?;
|
||
self.dx_blur_pass(
|
||
&self.pipelines.blur_vertex,
|
||
&self.pipelines.blur_fragment,
|
||
&self.pipelines.blur_blend_replace,
|
||
&pong_rtv,
|
||
&ping_srv,
|
||
BlurParams {
|
||
direction: [1.0 / half_w as f32, 0.0],
|
||
sigma,
|
||
tap_count,
|
||
tap_step,
|
||
..Default::default()
|
||
},
|
||
&half_vp,
|
||
D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
|
||
3,
|
||
true,
|
||
)?;
|
||
self.dx_blur_pass(
|
||
&self.pipelines.blur_vertex,
|
||
&self.pipelines.blur_fragment,
|
||
&self.pipelines.blur_blend_replace,
|
||
&ping_rtv,
|
||
&pong_srv,
|
||
BlurParams {
|
||
direction: [0.0, 1.0 / half_h as f32],
|
||
sigma,
|
||
tap_count,
|
||
tap_step,
|
||
..Default::default()
|
||
},
|
||
&half_vp,
|
||
D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
|
||
3,
|
||
true,
|
||
)?;
|
||
// Composite the blurred result into the target (preserving its contents).
|
||
self.dx_blur_pass(
|
||
&self.pipelines.blur_composite_vertex,
|
||
&self.pipelines.blur_composite_fragment,
|
||
&self.pipelines.blur_blend_composite,
|
||
target_rtv,
|
||
&ping_srv,
|
||
BlurParams {
|
||
bounds: composite_bounds,
|
||
content_mask,
|
||
corner_radii,
|
||
opacity,
|
||
clip_rounded: if clip_rounded { 1.0 } else { 0.0 },
|
||
..Default::default()
|
||
},
|
||
&full_vp,
|
||
D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
|
||
4,
|
||
false,
|
||
)?;
|
||
Ok(())
|
||
}
|
||
|
||
/// Copy the offscreen scene texture into the swapchain render target.
|
||
fn dx_blit(
|
||
&self,
|
||
source_srv: &Option<ID3D11ShaderResourceView>,
|
||
target_rtv: &Option<ID3D11RenderTargetView>,
|
||
) -> Result<()> {
|
||
let full_vp = self
|
||
.resources
|
||
.as_ref()
|
||
.context("resources missing")?
|
||
.viewport;
|
||
self.dx_blur_pass(
|
||
&self.pipelines.blur_downsample_vertex,
|
||
&self.pipelines.blur_downsample_fragment,
|
||
&self.pipelines.blur_blend_replace,
|
||
target_rtv,
|
||
source_srv,
|
||
BlurParams::default(),
|
||
&full_vp,
|
||
D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
|
||
3,
|
||
true,
|
||
)
|
||
}
|
||
|
||
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(),
|
||
// For Intel and other vendors, we use the DXGI API to get the driver version.
|
||
_ => dxgi::get_driver_version(&devices.adapter),
|
||
}
|
||
.context("Failed to get gpu driver info")
|
||
.log_err()
|
||
.unwrap_or("Unknown Driver".to_string());
|
||
Ok(GpuSpecs {
|
||
is_software_emulated,
|
||
device_name,
|
||
driver_name,
|
||
driver_info: driver_version,
|
||
})
|
||
}
|
||
|
||
pub(crate) fn get_font_info() -> &'static FontInfo {
|
||
static CACHED_FONT_INFO: OnceLock<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 {
|
||
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)?;
|
||
let blur = BlurResources::new(&devices.device, width, height)?;
|
||
|
||
Ok(Self {
|
||
swap_chain,
|
||
render_target: Some(render_target),
|
||
render_target_view,
|
||
path_intermediate_texture,
|
||
path_intermediate_msaa_texture,
|
||
path_intermediate_msaa_view,
|
||
path_intermediate_srv,
|
||
blur,
|
||
viewport,
|
||
})
|
||
}
|
||
|
||
#[inline]
|
||
fn recreate_resources(
|
||
&mut self,
|
||
devices: &DirectXRendererDevices,
|
||
width: u32,
|
||
height: u32,
|
||
) -> Result<()> {
|
||
let (
|
||
render_target,
|
||
render_target_view,
|
||
path_intermediate_texture,
|
||
path_intermediate_srv,
|
||
path_intermediate_msaa_texture,
|
||
path_intermediate_msaa_view,
|
||
viewport,
|
||
) = create_resources(devices, &self.swap_chain, width, height)?;
|
||
self.render_target = Some(render_target);
|
||
self.render_target_view = render_target_view;
|
||
self.path_intermediate_texture = path_intermediate_texture;
|
||
self.path_intermediate_msaa_texture = path_intermediate_msaa_texture;
|
||
self.path_intermediate_msaa_view = path_intermediate_msaa_view;
|
||
self.path_intermediate_srv = path_intermediate_srv;
|
||
self.blur = BlurResources::new(&devices.device, width, height)?;
|
||
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)?,
|
||
)?;
|
||
|
||
let blur_downsample_vertex = create_vertex_shader(
|
||
device,
|
||
RawShaderBytes::new(ShaderModule::BlurDownsample, ShaderTarget::Vertex)?.as_bytes(),
|
||
)?;
|
||
let blur_downsample_fragment = create_fragment_shader(
|
||
device,
|
||
RawShaderBytes::new(ShaderModule::BlurDownsample, ShaderTarget::Fragment)?.as_bytes(),
|
||
)?;
|
||
let blur_vertex = create_vertex_shader(
|
||
device,
|
||
RawShaderBytes::new(ShaderModule::Blur, ShaderTarget::Vertex)?.as_bytes(),
|
||
)?;
|
||
let blur_fragment = create_fragment_shader(
|
||
device,
|
||
RawShaderBytes::new(ShaderModule::Blur, ShaderTarget::Fragment)?.as_bytes(),
|
||
)?;
|
||
let blur_composite_vertex = create_vertex_shader(
|
||
device,
|
||
RawShaderBytes::new(ShaderModule::BlurComposite, ShaderTarget::Vertex)?.as_bytes(),
|
||
)?;
|
||
let blur_composite_fragment = create_fragment_shader(
|
||
device,
|
||
RawShaderBytes::new(ShaderModule::BlurComposite, ShaderTarget::Fragment)?.as_bytes(),
|
||
)?;
|
||
let blur_params_buffer = create_constant_buffer(device, std::mem::size_of::<BlurParams>())?;
|
||
let blur_blend_replace = create_blend_state_no_blend(device)?;
|
||
// Premultiplied (One / InvSrcAlpha) — the composite outputs a premultiplied blurred sample;
|
||
// straight-alpha blending would darken the faded edges.
|
||
let blur_blend_composite = create_blend_state_for_path_sprite(device)?;
|
||
|
||
Ok(Self {
|
||
shadow_pipeline,
|
||
quad_pipeline,
|
||
path_rasterization_pipeline,
|
||
path_sprite_pipeline,
|
||
underline_pipeline,
|
||
mono_sprites,
|
||
subpixel_sprites,
|
||
poly_sprites,
|
||
blur_downsample_vertex,
|
||
blur_downsample_fragment,
|
||
blur_vertex,
|
||
blur_fragment,
|
||
blur_composite_vertex,
|
||
blur_composite_fragment,
|
||
blur_params_buffer,
|
||
blur_blend_replace,
|
||
blur_blend_composite,
|
||
})
|
||
}
|
||
}
|
||
|
||
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 = unsafe {
|
||
let desc = D3D11_BUFFER_DESC {
|
||
ByteWidth: std::mem::size_of::<GlobalParams>() as u32,
|
||
Usage: D3D11_USAGE_DYNAMIC,
|
||
BindFlags: D3D11_BIND_CONSTANT_BUFFER.0 as u32,
|
||
CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32,
|
||
..Default::default()
|
||
};
|
||
let mut buffer = None;
|
||
device.CreateBuffer(&desc, None, Some(&mut buffer))?;
|
||
buffer
|
||
};
|
||
|
||
let sampler = unsafe {
|
||
let desc = D3D11_SAMPLER_DESC {
|
||
Filter: D3D11_FILTER_MIN_MAG_MIP_LINEAR,
|
||
AddressU: D3D11_TEXTURE_ADDRESS_WRAP,
|
||
AddressV: D3D11_TEXTURE_ADDRESS_WRAP,
|
||
AddressW: D3D11_TEXTURE_ADDRESS_WRAP,
|
||
MipLODBias: 0.0,
|
||
MaxAnisotropy: 1,
|
||
ComparisonFunc: D3D11_COMPARISON_ALWAYS,
|
||
BorderColor: [0.0; 4],
|
||
MinLOD: 0.0,
|
||
MaxLOD: D3D11_FLOAT32_MAX,
|
||
};
|
||
let mut output = None;
|
||
device.CreateSamplerState(&desc, Some(&mut output))?;
|
||
output
|
||
};
|
||
|
||
Ok(Self {
|
||
global_params_buffer,
|
||
sampler,
|
||
})
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Default)]
|
||
#[repr(C)]
|
||
struct GlobalParams {
|
||
gamma_ratios: [f32; 4],
|
||
viewport_size: [f32; 2],
|
||
grayscale_enhanced_contrast: f32,
|
||
subpixel_enhanced_contrast: f32,
|
||
is_bgr: u32,
|
||
_pad: [u32; 3],
|
||
}
|
||
|
||
/// Mirrors the `BlurParams` cbuffer (register b1) in `shaders.hlsl`. 80 bytes (a multiple of 16,
|
||
/// as constant buffers require). Updated per blur pass via `update_buffer`.
|
||
#[repr(C)]
|
||
#[derive(Clone, Copy)]
|
||
struct BlurParams {
|
||
bounds: Bounds<ScaledPixels>,
|
||
content_mask: Bounds<ScaledPixels>,
|
||
corner_radii: [f32; 4],
|
||
direction: [f32; 2],
|
||
sigma: f32,
|
||
opacity: f32,
|
||
tap_count: f32,
|
||
/// 1.0 clips the composite to the rounded rect (backdrop); 0.0 lets content blur bleed past
|
||
/// its bounds like CSS `filter: blur`.
|
||
clip_rounded: f32,
|
||
/// 1.0 = snapped 2:1 box downsample (anchor the half-res grid to a fixed 2px grid at the
|
||
/// origin, so a stationary element blurs identically at every window size); 0.0 = 1:1 copy
|
||
/// (the scene blit, which must not downsample). Downsample pass only.
|
||
downsample: f32,
|
||
/// Spacing between taps in pixels (gaussian passes only); >1 lets `tap_count` taps span very
|
||
/// large radii without truncating the gaussian, matching the wgpu backend.
|
||
tap_step: f32,
|
||
}
|
||
|
||
impl Default for BlurParams {
|
||
fn default() -> Self {
|
||
BlurParams {
|
||
bounds: Bounds::default(),
|
||
content_mask: Bounds::default(),
|
||
corner_radii: [0.0; 4],
|
||
direction: [0.0, 0.0],
|
||
sigma: 0.0,
|
||
opacity: 1.0,
|
||
tap_count: 0.0,
|
||
clip_rounded: 0.0,
|
||
downsample: 0.0,
|
||
tap_step: 0.0,
|
||
}
|
||
}
|
||
}
|
||
|
||
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 new_buffer_size = data.len().next_power_of_two();
|
||
log::debug!(
|
||
"Updating {} buffer size from {} to {}",
|
||
self.label,
|
||
self.buffer_size,
|
||
new_buffer_size
|
||
);
|
||
let buffer = create_buffer(device, std::mem::size_of::<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,
|
||
viewport: &[D3D11_VIEWPORT],
|
||
global_params: &[Option<ID3D11Buffer>],
|
||
topology: D3D_PRIMITIVE_TOPOLOGY,
|
||
vertex_count: u32,
|
||
instance_count: u32,
|
||
) -> Result<()> {
|
||
set_pipeline_state(
|
||
device_context,
|
||
slice::from_ref(&self.view),
|
||
topology,
|
||
viewport,
|
||
&self.vertex,
|
||
&self.fragment,
|
||
global_params,
|
||
&self.blend_state,
|
||
);
|
||
unsafe {
|
||
device_context.DrawInstanced(vertex_count, instance_count, 0, 0);
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
fn draw_with_texture(
|
||
&self,
|
||
device_context: &ID3D11DeviceContext,
|
||
texture: &[Option<ID3D11ShaderResourceView>],
|
||
viewport: &[D3D11_VIEWPORT],
|
||
global_params: &[Option<ID3D11Buffer>],
|
||
sampler: &[Option<ID3D11SamplerState>],
|
||
instance_count: u32,
|
||
) -> Result<()> {
|
||
set_pipeline_state(
|
||
device_context,
|
||
slice::from_ref(&self.view),
|
||
D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
|
||
viewport,
|
||
&self.vertex,
|
||
&self.fragment,
|
||
global_params,
|
||
&self.blend_state,
|
||
);
|
||
unsafe {
|
||
device_context.PSSetSamplers(0, Some(sampler));
|
||
device_context.VSSetShaderResources(0, Some(texture));
|
||
device_context.PSSetShaderResources(0, Some(texture));
|
||
|
||
device_context.DrawInstanced(4, instance_count, 0, 0);
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
fn draw_range(
|
||
&self,
|
||
device: &ID3D11Device,
|
||
device_context: &ID3D11DeviceContext,
|
||
viewport: &[D3D11_VIEWPORT],
|
||
global_params: &[Option<ID3D11Buffer>],
|
||
vertex_count: u32,
|
||
first_instance: u32,
|
||
instance_count: u32,
|
||
) -> Result<()> {
|
||
let view = create_buffer_view_range(device, &self.buffer, first_instance, instance_count)?;
|
||
set_pipeline_state(
|
||
device_context,
|
||
slice::from_ref(&view),
|
||
D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
|
||
viewport,
|
||
&self.vertex,
|
||
&self.fragment,
|
||
global_params,
|
||
&self.blend_state,
|
||
);
|
||
unsafe {
|
||
device_context.DrawInstanced(vertex_count, instance_count, 0, 0);
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
fn draw_range_with_texture(
|
||
&self,
|
||
device: &ID3D11Device,
|
||
device_context: &ID3D11DeviceContext,
|
||
texture: &[Option<ID3D11ShaderResourceView>],
|
||
viewport: &[D3D11_VIEWPORT],
|
||
global_params: &[Option<ID3D11Buffer>],
|
||
sampler: &[Option<ID3D11SamplerState>],
|
||
first_instance: u32,
|
||
instance_count: u32,
|
||
) -> Result<()> {
|
||
let view = create_buffer_view_range(device, &self.buffer, first_instance, instance_count)?;
|
||
set_pipeline_state(
|
||
device_context,
|
||
slice::from_ref(&view),
|
||
D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
|
||
viewport,
|
||
&self.vertex,
|
||
&self.fragment,
|
||
global_params,
|
||
&self.blend_state,
|
||
);
|
||
unsafe {
|
||
device_context.PSSetSamplers(0, Some(sampler));
|
||
device_context.VSSetShaderResources(0, Some(texture));
|
||
device_context.PSSetShaderResources(0, Some(texture));
|
||
device_context.DrawInstanced(4, instance_count, 0, 0);
|
||
}
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, Copy)]
|
||
#[repr(C)]
|
||
struct PathRasterizationSprite {
|
||
xy_position: Point<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,
|
||
// Composition SwapChains only support the DXGI_SCALING_STRETCH Scaling.
|
||
Scaling: DXGI_SCALING_STRETCH,
|
||
SwapEffect: DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL,
|
||
AlphaMode: DXGI_ALPHA_MODE_PREMULTIPLIED,
|
||
Flags: 0,
|
||
};
|
||
Ok(unsafe { dxgi_factory.CreateSwapChainForComposition(device, &desc, None)? })
|
||
}
|
||
|
||
fn create_swap_chain(
|
||
dxgi_factory: &IDXGIFactory6,
|
||
device: &ID3D11Device,
|
||
hwnd: HWND,
|
||
width: u32,
|
||
height: u32,
|
||
) -> Result<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 = set_viewport(&devices.device_context, width as f32, height as f32);
|
||
Ok((
|
||
render_target,
|
||
render_target_view,
|
||
path_intermediate_texture,
|
||
path_intermediate_srv,
|
||
path_intermediate_msaa_texture,
|
||
path_intermediate_msaa_view,
|
||
viewport,
|
||
))
|
||
}
|
||
|
||
#[inline]
|
||
/// Flatten a `Corners` into the `[tl, tr, br, bl]` order expected by the blur composite shader.
|
||
fn corner_radii_array(corners: Corners<ScaledPixels>) -> [f32; 4] {
|
||
[
|
||
corners.top_left.0,
|
||
corners.top_right.0,
|
||
corners.bottom_right.0,
|
||
corners.bottom_left.0,
|
||
]
|
||
}
|
||
|
||
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())))
|
||
}
|
||
|
||
/// Create a color texture usable as both a render target and a shader resource, returning both
|
||
/// views. Used for the blur offscreen targets.
|
||
#[inline]
|
||
fn create_color_target(
|
||
device: &ID3D11Device,
|
||
width: u32,
|
||
height: u32,
|
||
) -> Result<(
|
||
ID3D11Texture2D,
|
||
Option<ID3D11RenderTargetView>,
|
||
Option<ID3D11ShaderResourceView>,
|
||
)> {
|
||
let texture = unsafe {
|
||
let mut output = None;
|
||
let desc = D3D11_TEXTURE2D_DESC {
|
||
Width: width.max(1),
|
||
Height: height.max(1),
|
||
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 rtv = None;
|
||
unsafe { device.CreateRenderTargetView(&texture, None, Some(&mut rtv))? };
|
||
let mut srv = None;
|
||
unsafe { device.CreateShaderResourceView(&texture, None, Some(&mut srv))? };
|
||
Ok((texture, rtv, srv))
|
||
}
|
||
|
||
#[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(())
|
||
}
|
||
|
||
// https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ns-d3d11-d3d11_blend_desc
|
||
#[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;
|
||
// It does not make sense to draw transparent subpixel-rendered text, since it cannot be meaningfully alpha-blended onto anything else.
|
||
desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
|
||
desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
|
||
desc.RenderTarget[0].RenderTargetWriteMask =
|
||
D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8 & !D3D11_COLOR_WRITE_ENABLE_ALPHA.0 as u8;
|
||
|
||
unsafe {
|
||
let mut state = None;
|
||
device.CreateBlendState(&desc, Some(&mut state))?;
|
||
Ok(state.unwrap())
|
||
}
|
||
}
|
||
|
||
#[inline]
|
||
fn create_blend_state_for_path_rasterization(device: &ID3D11Device) -> Result<ID3D11BlendState> {
|
||
// If the feature level is set to greater than D3D_FEATURE_LEVEL_9_3, the display
|
||
// device performs the blend in linear space, which is ideal.
|
||
let mut desc = D3D11_BLEND_DESC::default();
|
||
desc.RenderTarget[0].BlendEnable = true.into();
|
||
desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
|
||
desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
|
||
desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE;
|
||
desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
|
||
desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
|
||
desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA;
|
||
desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8;
|
||
unsafe {
|
||
let mut state = None;
|
||
device.CreateBlendState(&desc, Some(&mut state))?;
|
||
Ok(state.unwrap())
|
||
}
|
||
}
|
||
|
||
#[inline]
|
||
fn create_blend_state_for_path_sprite(device: &ID3D11Device) -> Result<ID3D11BlendState> {
|
||
// If the feature level is set to greater than D3D_FEATURE_LEVEL_9_3, the display
|
||
// device performs the blend in linear space, which is ideal.
|
||
let mut desc = D3D11_BLEND_DESC::default();
|
||
desc.RenderTarget[0].BlendEnable = true.into();
|
||
desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
|
||
desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
|
||
desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE;
|
||
desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
|
||
desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
|
||
desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ONE;
|
||
desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL.0 as u8;
|
||
unsafe {
|
||
let mut state = None;
|
||
device.CreateBlendState(&desc, Some(&mut state))?;
|
||
Ok(state.unwrap())
|
||
}
|
||
}
|
||
|
||
/// Create a CPU-writable dynamic constant buffer of the given byte size (rounded up to 16).
|
||
#[inline]
|
||
fn create_constant_buffer(device: &ID3D11Device, byte_size: usize) -> Result<ID3D11Buffer> {
|
||
let desc = D3D11_BUFFER_DESC {
|
||
ByteWidth: byte_size.next_multiple_of(16) 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;
|
||
unsafe { device.CreateBuffer(&desc, None, Some(&mut buffer)) }?;
|
||
Ok(buffer.unwrap())
|
||
}
|
||
|
||
/// A blend state that overwrites the target (no blending) — used for the blur downsample and
|
||
/// gaussian passes.
|
||
#[inline]
|
||
fn create_blend_state_no_blend(device: &ID3D11Device) -> Result<ID3D11BlendState> {
|
||
let mut desc = D3D11_BLEND_DESC::default();
|
||
desc.RenderTarget[0].BlendEnable = false.into();
|
||
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 create_buffer_view_range(
|
||
device: &ID3D11Device,
|
||
buffer: &ID3D11Buffer,
|
||
first_element: u32,
|
||
num_elements: u32,
|
||
) -> Result<Option<ID3D11ShaderResourceView>> {
|
||
let desc = D3D11_SHADER_RESOURCE_VIEW_DESC {
|
||
Format: DXGI_FORMAT_UNKNOWN,
|
||
ViewDimension: D3D11_SRV_DIMENSION_BUFFER,
|
||
Anonymous: D3D11_SHADER_RESOURCE_VIEW_DESC_0 {
|
||
Buffer: D3D11_BUFFER_SRV {
|
||
Anonymous1: D3D11_BUFFER_SRV_0 {
|
||
FirstElement: first_element,
|
||
},
|
||
Anonymous2: D3D11_BUFFER_SRV_1 {
|
||
NumElements: num_elements,
|
||
},
|
||
},
|
||
},
|
||
};
|
||
let mut view = None;
|
||
unsafe { device.CreateShaderResourceView(buffer, Some(&desc), Some(&mut view)) }?;
|
||
Ok(view)
|
||
}
|
||
|
||
#[inline]
|
||
fn update_buffer<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;
|
||
|
||
#[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,
|
||
BlurDownsample,
|
||
Blur,
|
||
BlurComposite,
|
||
}
|
||
|
||
#[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,
|
||
},
|
||
ShaderModule::BlurDownsample => match target {
|
||
ShaderTarget::Vertex => BLUR_DOWNSAMPLE_VERTEX_BYTES,
|
||
ShaderTarget::Fragment => BLUR_DOWNSAMPLE_FRAGMENT_BYTES,
|
||
},
|
||
ShaderModule::Blur => match target {
|
||
ShaderTarget::Vertex => BLUR_VERTEX_BYTES,
|
||
ShaderTarget::Fragment => BLUR_FRAGMENT_BYTES,
|
||
},
|
||
ShaderModule::BlurComposite => match target {
|
||
ShaderTarget::Vertex => BLUR_COMPOSITE_VERTEX_BYTES,
|
||
ShaderTarget::Fragment => BLUR_COMPOSITE_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());
|
||
|
||
// really dirty trick because winapi bindings are unhappy otherwise
|
||
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",
|
||
ShaderModule::BlurDownsample => "blur_downsample",
|
||
ShaderModule::Blur => "blur",
|
||
ShaderModule::BlurComposite => "blur_composite",
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
mod nvidia {
|
||
use std::{
|
||
ffi::CStr,
|
||
os::raw::{c_char, c_int, c_uint},
|
||
};
|
||
|
||
use anyhow::Result;
|
||
use windows::{Win32::System::LibraryLoader::GetProcAddress, core::s};
|
||
|
||
use crate::with_dll_library;
|
||
|
||
// https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_lite_common.h#L180
|
||
const NVAPI_SHORT_STRING_MAX: usize = 64;
|
||
|
||
// https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_lite_common.h#L235
|
||
#[allow(non_camel_case_types)]
|
||
type NvAPI_ShortString = [c_char; NVAPI_SHORT_STRING_MAX];
|
||
|
||
// https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_lite_common.h#L447
|
||
#[allow(non_camel_case_types)]
|
||
type NvAPI_SYS_GetDriverAndBranchVersion_t = unsafe extern "C" fn(
|
||
driver_version: *mut c_uint,
|
||
build_branch_string: *mut NvAPI_ShortString,
|
||
) -> c_int;
|
||
|
||
pub(super) fn get_driver_version() -> Result<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);
|
||
|
||
// https://github.com/NVIDIA/nvapi/blob/7cb76fce2f52de818b3da497af646af1ec16ce27/nvapi_interface.h#L41
|
||
let nvapi_get_driver_version_ptr = nvapi_query(0x2926aaad);
|
||
if nvapi_get_driver_version_ptr.is_null() {
|
||
anyhow::bail!("Failed to get NVIDIA driver version function pointer");
|
||
}
|
||
let nvapi_get_driver_version: NvAPI_SYS_GetDriverAndBranchVersion_t =
|
||
std::mem::transmute(nvapi_get_driver_version_ptr);
|
||
|
||
let mut driver_version: c_uint = 0;
|
||
let mut build_branch_string: NvAPI_ShortString = [0; NVAPI_SHORT_STRING_MAX];
|
||
let result = nvapi_get_driver_version(
|
||
&mut driver_version as *mut c_uint,
|
||
&mut build_branch_string as *mut NvAPI_ShortString,
|
||
);
|
||
|
||
if result != 0 {
|
||
anyhow::bail!(
|
||
"Failed to get NVIDIA driver version, error code: {}",
|
||
result
|
||
);
|
||
}
|
||
let major = driver_version / 100;
|
||
let minor = driver_version % 100;
|
||
let branch_string = CStr::from_ptr(build_branch_string.as_ptr());
|
||
Ok(format!(
|
||
"{}.{} {}",
|
||
major,
|
||
minor,
|
||
branch_string.to_string_lossy()
|
||
))
|
||
})
|
||
}
|
||
}
|
||
|
||
mod amd {
|
||
use std::os::raw::{c_char, c_int, c_void};
|
||
|
||
use anyhow::Result;
|
||
use windows::{Win32::System::LibraryLoader::GetProcAddress, core::s};
|
||
|
||
use crate::with_dll_library;
|
||
|
||
// https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L145
|
||
const AGS_CURRENT_VERSION: i32 = (6 << 22) | (3 << 12);
|
||
|
||
// https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L204
|
||
// This is an opaque type, using struct to represent it properly for FFI
|
||
#[repr(C)]
|
||
struct AGSContext {
|
||
_private: [u8; 0],
|
||
}
|
||
|
||
#[repr(C)]
|
||
pub struct AGSGPUInfo {
|
||
pub driver_version: *const c_char,
|
||
pub radeon_software_version: *const c_char,
|
||
pub num_devices: c_int,
|
||
pub devices: *mut c_void,
|
||
}
|
||
|
||
// https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L429
|
||
#[allow(non_camel_case_types)]
|
||
type agsInitialize_t = unsafe extern "C" fn(
|
||
version: c_int,
|
||
config: *const c_void,
|
||
context: *mut *mut AGSContext,
|
||
gpu_info: *mut AGSGPUInfo,
|
||
) -> c_int;
|
||
|
||
// https://github.com/GPUOpen-LibrariesAndSDKs/AGS_SDK/blob/5d8812d703d0335741b6f7ffc37838eeb8b967f7/ags_lib/inc/amd_ags.h#L436
|
||
#[allow(non_camel_case_types)]
|
||
type agsDeInitialize_t = unsafe extern "C" fn(context: *mut AGSContext) -> c_int;
|
||
|
||
pub(super) fn get_driver_version() -> Result<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);
|
||
}
|
||
|
||
// Vulkan actually returns this as the driver version
|
||
let software_version = if !gpu_info.radeon_software_version.is_null() {
|
||
std::ffi::CStr::from_ptr(gpu_info.radeon_software_version)
|
||
.to_string_lossy()
|
||
.into_owned()
|
||
} else {
|
||
"Unknown Radeon Software Version".to_string()
|
||
};
|
||
|
||
let driver_version = if !gpu_info.driver_version.is_null() {
|
||
std::ffi::CStr::from_ptr(gpu_info.driver_version)
|
||
.to_string_lossy()
|
||
.into_owned()
|
||
} else {
|
||
"Unknown Radeon Driver Version".to_string()
|
||
};
|
||
|
||
ags_deinitialize(context);
|
||
Ok(format!("{} ({})", software_version, driver_version))
|
||
})
|
||
}
|
||
}
|
||
|
||
mod dxgi {
|
||
use windows::{
|
||
Win32::Graphics::Dxgi::{IDXGIAdapter1, IDXGIDevice},
|
||
core::Interface,
|
||
};
|
||
|
||
pub(super) fn get_driver_version(adapter: &IDXGIAdapter1) -> anyhow::Result<String> {
|
||
let number = unsafe { adapter.CheckInterfaceSupport(&IDXGIDevice::IID as _) }?;
|
||
Ok(format!(
|
||
"{}.{}.{}.{}",
|
||
number >> 48,
|
||
(number >> 32) & 0xFFFF,
|
||
(number >> 16) & 0xFFFF,
|
||
number & 0xFFFF
|
||
))
|
||
}
|
||
}
|