@@ -1,65 +1,72 @@
-use std::{collections::HashMap, hash::BuildHasherDefault, sync::Arc};
+use std::{mem::ManuallyDrop, sync::Arc};
use ::util::ResultExt;
use anyhow::{Context, Result};
-use collections::FxHasher;
-// #[cfg(not(feature = "enable-renderdoc"))]
-// use windows::Win32::Graphics::DirectComposition::*;
-use windows::{
- Win32::{
- Foundation::{HMODULE, HWND},
- Graphics::{
- Direct3D::*,
- Direct3D11::*,
- Dxgi::{Common::*, *},
- },
+use windows::Win32::{
+ Foundation::{HMODULE, HWND},
+ Graphics::{
+ Direct3D::*,
+ Direct3D11::*,
+ Dxgi::{Common::*, *},
},
- core::*,
};
+#[cfg(not(feature = "enable-renderdoc"))]
+use windows::{Win32::Graphics::DirectComposition::*, core::Interface};
use crate::*;
+const RENDER_TARGET_FORMAT: DXGI_FORMAT = DXGI_FORMAT_B8G8R8A8_UNORM;
+// This configuration is used for MSAA rendering, and it's guaranteed to be supported by DirectX 11.
+const MULTISAMPLE_COUNT: u32 = 4;
+
pub(crate) struct DirectXRenderer {
atlas: Arc<DirectXAtlas>,
devices: DirectXDevices,
- context: DirectXContext,
+ resources: DirectXResources,
globals: DirectXGlobalElements,
pipelines: DirectXRenderPipelines,
- hwnd: HWND,
- transparent: bool,
+ #[cfg(not(feature = "enable-renderdoc"))]
+ _direct_composition: DirectComposition,
}
+/// Direct3D objects
#[derive(Clone)]
pub(crate) struct DirectXDevices {
+ adapter: IDXGIAdapter1,
dxgi_factory: IDXGIFactory6,
+ #[cfg(not(feature = "enable-renderdoc"))]
dxgi_device: IDXGIDevice,
device: ID3D11Device,
device_context: ID3D11DeviceContext,
}
-struct DirectXContext {
+struct DirectXResources {
+ // Direct3D rendering objects
swap_chain: IDXGISwapChain1,
- back_buffer: [Option<ID3D11RenderTargetView>; 1],
+ render_target: ManuallyDrop<ID3D11Texture2D>,
+ render_target_view: [Option<ID3D11RenderTargetView>; 1],
+ msaa_target: ID3D11Texture2D,
+ msaa_view: [Option<ID3D11RenderTargetView>; 1],
+
+ // Cached window size and viewport
+ width: u32,
+ height: u32,
viewport: [D3D11_VIEWPORT; 1],
- // #[cfg(not(feature = "enable-renderdoc"))]
- // direct_composition: DirectComposition,
}
struct DirectXRenderPipelines {
- shadow_pipeline: PipelineState,
- quad_pipeline: PipelineState,
- paths_pipeline: PipelineState,
- paths_indirect_draw_buffer: ID3D11Buffer,
- underline_pipeline: PipelineState,
- mono_sprites: PipelineState,
- poly_sprites: PipelineState,
+ shadow_pipeline: PipelineState<Shadow>,
+ quad_pipeline: PipelineState<Quad>,
+ paths_pipeline: PathsPipelineState,
+ underline_pipeline: PipelineState<Underline>,
+ mono_sprites: PipelineState<MonochromeSprite>,
+ poly_sprites: PipelineState<PolychromeSprite>,
}
struct DirectXGlobalElements {
global_params_buffer: [Option<ID3D11Buffer>; 1],
sampler: [Option<ID3D11SamplerState>; 1],
blend_state: ID3D11BlendState,
- blend_state_for_pr: ID3D11BlendState,
}
#[repr(C)]
@@ -70,12 +77,12 @@ struct DrawInstancedIndirectArgs {
start_instance_location: u32,
}
-// #[cfg(not(feature = "enable-renderdoc"))]
-// struct DirectComposition {
-// comp_device: IDCompositionDevice,
-// comp_target: IDCompositionTarget,
-// comp_visual: IDCompositionVisual,
-// }
+#[cfg(not(feature = "enable-renderdoc"))]
+struct DirectComposition {
+ comp_device: IDCompositionDevice,
+ comp_target: IDCompositionTarget,
+ comp_visual: IDCompositionVisual,
+}
impl DirectXDevices {
pub(crate) fn new() -> Result<Self> {
@@ -87,10 +94,13 @@ impl DirectXDevices {
get_device(&adapter, Some(&mut device), Some(&mut context))?;
(device.unwrap(), context.unwrap())
};
+ #[cfg(not(feature = "enable-renderdoc"))]
let dxgi_device: IDXGIDevice = device.cast()?;
Ok(Self {
+ adapter,
dxgi_factory,
+ #[cfg(not(feature = "enable-renderdoc"))]
dxgi_device,
device,
device_context,
@@ -99,22 +109,33 @@ impl DirectXDevices {
}
impl DirectXRenderer {
- pub(crate) fn new(devices: &DirectXDevices, hwnd: HWND, transparent: bool) -> Result<Self> {
+ pub(crate) fn new(devices: &DirectXDevices, hwnd: HWND) -> Result<Self> {
let atlas = Arc::new(DirectXAtlas::new(
devices.device.clone(),
devices.device_context.clone(),
));
- let context = DirectXContext::new(devices, hwnd, transparent)?;
+
+ #[cfg(not(feature = "enable-renderdoc"))]
+ let resources = DirectXResources::new(devices)?;
+ #[cfg(feature = "enable-renderdoc")]
+ let resources = DirectXResources::new(devices, hwnd)?;
+
let globals = DirectXGlobalElements::new(&devices.device)?;
let pipelines = DirectXRenderPipelines::new(&devices.device)?;
+
+ #[cfg(not(feature = "enable-renderdoc"))]
+ let direct_composition = DirectComposition::new(&devices.dxgi_device, hwnd)?;
+ #[cfg(not(feature = "enable-renderdoc"))]
+ direct_composition.set_swap_chain(&resources.swap_chain)?;
+
Ok(DirectXRenderer {
atlas,
devices: devices.clone(),
- context,
+ resources,
globals,
pipelines,
- hwnd,
- transparent,
+ #[cfg(not(feature = "enable-renderdoc"))]
+ _direct_composition: direct_composition,
})
}
@@ -122,15 +143,56 @@ impl DirectXRenderer {
self.atlas.clone()
}
- pub(crate) fn draw(&mut self, scene: &Scene) -> Result<()> {
- pre_draw(
+ fn pre_draw(&self) -> Result<()> {
+ update_buffer(
&self.devices.device_context,
- &self.globals.global_params_buffer,
- &self.context.viewport,
- &self.context.back_buffer,
- [0.0, 0.0, 0.0, 0.0],
- &self.globals.blend_state,
+ self.globals.global_params_buffer[0].as_ref().unwrap(),
+ &[GlobalParams {
+ viewport_size: [
+ self.resources.viewport[0].Width,
+ self.resources.viewport[0].Height,
+ ],
+ ..Default::default()
+ }],
)?;
+ unsafe {
+ self.devices
+ .device_context
+ .ClearRenderTargetView(self.resources.msaa_view[0].as_ref().unwrap(), &[0.0; 4]);
+ self.devices
+ .device_context
+ .OMSetRenderTargets(Some(&self.resources.msaa_view), None);
+ self.devices
+ .device_context
+ .RSSetViewports(Some(&self.resources.viewport));
+ self.devices.device_context.OMSetBlendState(
+ &self.globals.blend_state,
+ None,
+ 0xFFFFFFFF,
+ );
+ }
+ Ok(())
+ }
+
+ fn present(&self) -> Result<()> {
+ unsafe {
+ self.devices.device_context.ResolveSubresource(
+ &*self.resources.render_target,
+ 0,
+ &self.resources.msaa_target,
+ 0,
+ RENDER_TARGET_FORMAT,
+ );
+ self.devices
+ .device_context
+ .OMSetRenderTargets(Some(&self.resources.render_target_view), None);
+ self.resources.swap_chain.Present(0, DXGI_PRESENT(0)).ok()?;
+ }
+ Ok(())
+ }
+
+ pub(crate) fn draw(&mut self, scene: &Scene) -> Result<()> {
+ self.pre_draw()?;
for batch in scene.batches() {
match batch {
PrimitiveBatch::Shadows(shadows) => self.draw_shadows(shadows),
@@ -155,98 +217,51 @@ impl DirectXRenderer {
scene.polychrome_sprites.len(),
scene.surfaces.len(),))?;
}
- unsafe { self.context.swap_chain.Present(0, DXGI_PRESENT(0)) }.ok()?;
- Ok(())
+ self.present()
}
pub(crate) fn resize(&mut self, new_size: Size<DevicePixels>) -> Result<()> {
- unsafe { self.devices.device_context.OMSetRenderTargets(None, None) };
- drop(self.context.back_buffer[0].take().unwrap());
+ let width = new_size.width.0.max(1) as u32;
+ let height = new_size.height.0.max(1) as u32;
+ if self.resources.width == width && self.resources.height == height {
+ return Ok(());
+ }
unsafe {
- self.context.swap_chain.ResizeBuffers(
+ // Clear the render target before resizing
+ self.devices.device_context.OMSetRenderTargets(None, None);
+ ManuallyDrop::drop(&mut self.resources.render_target);
+ drop(self.resources.render_target_view[0].take().unwrap());
+
+ self.resources.swap_chain.ResizeBuffers(
BUFFER_COUNT as u32,
- new_size.width.0 as u32,
- new_size.height.0 as u32,
- DXGI_FORMAT_B8G8R8A8_UNORM,
+ width,
+ height,
+ RENDER_TARGET_FORMAT,
DXGI_SWAP_CHAIN_FLAG(0),
)?;
- }
- let backbuffer = set_render_target_view(
- &self.context.swap_chain,
- &self.devices.device,
- &self.devices.device_context,
- )?;
- self.context.back_buffer[0] = Some(backbuffer);
- self.context.viewport = set_viewport(
- &self.devices.device_context,
- new_size.width.0 as f32,
- new_size.height.0 as f32,
- );
- Ok(())
- }
- // #[cfg(not(feature = "enable-renderdoc"))]
- // pub(crate) fn update_transparency(
- // &mut self,
- // background_appearance: WindowBackgroundAppearance,
- // ) -> Result<()> {
- // // We only support setting `Transparent` and `Opaque` for now.
- // match background_appearance {
- // WindowBackgroundAppearance::Opaque => {
- // if self.transparent {
- // return Err(anyhow::anyhow!(
- // "Set opaque backgroud from transparent background, a restart is required. Or, you can open a new window."
- // ));
- // }
- // }
- // WindowBackgroundAppearance::Transparent | WindowBackgroundAppearance::Blurred => {
- // if !self.transparent {
- // return Err(anyhow::anyhow!(
- // "Set transparent backgroud from opaque background, a restart is required. Or, you can open a new window."
- // ));
- // }
- // }
- // }
- // Ok(())
- // }
-
- // #[cfg(feature = "enable-renderdoc")]
- pub(crate) fn update_transparency(
- &mut self,
- background_appearance: WindowBackgroundAppearance,
- ) -> Result<()> {
- if background_appearance != WindowBackgroundAppearance::Opaque {
- Err(anyhow::anyhow!(
- "Set transparent background not supported when feature \"enable-renderdoc\" is enabled."
- ))
- } else {
- Ok(())
+ self.resources
+ .recreate_resources(&self.devices, width, height)?;
+ self.devices
+ .device_context
+ .OMSetRenderTargets(Some(&self.resources.render_target_view), None);
}
+ Ok(())
}
fn draw_shadows(&mut self, shadows: &[Shadow]) -> Result<()> {
if shadows.is_empty() {
return Ok(());
}
- update_buffer_capacity(
- &self.pipelines.shadow_pipeline,
- std::mem::size_of::<Shadow>(),
- shadows.len(),
+ self.pipelines.shadow_pipeline.update_buffer(
&self.devices.device,
- )
- .map(|input| update_pipeline(&mut self.pipelines.shadow_pipeline, input));
- update_buffer(
&self.devices.device_context,
- &self.pipelines.shadow_pipeline.buffer,
shadows,
)?;
- draw_normal(
+ self.pipelines.shadow_pipeline.draw(
&self.devices.device_context,
- &self.pipelines.shadow_pipeline,
- &self.context.viewport,
+ &self.resources.viewport,
&self.globals.global_params_buffer,
- D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
- 4,
shadows.len() as u32,
)
}
@@ -255,25 +270,15 @@ impl DirectXRenderer {
if quads.is_empty() {
return Ok(());
}
- update_buffer_capacity(
- &self.pipelines.quad_pipeline,
- std::mem::size_of::<Quad>(),
- quads.len(),
+ self.pipelines.quad_pipeline.update_buffer(
&self.devices.device,
- )
- .map(|input| update_pipeline(&mut self.pipelines.quad_pipeline, input));
- update_buffer(
&self.devices.device_context,
- &self.pipelines.quad_pipeline.buffer,
quads,
)?;
- draw_normal(
+ self.pipelines.quad_pipeline.draw(
&self.devices.device_context,
- &self.pipelines.quad_pipeline,
- &self.context.viewport,
+ &self.resources.viewport,
&self.globals.global_params_buffer,
- D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
- 4,
quads.len() as u32,
)
}
@@ -282,7 +287,6 @@ impl DirectXRenderer {
if paths.is_empty() {
return Ok(());
}
- println!("Drawing {} paths", paths.len());
let mut vertices = Vec::new();
let mut sprites = Vec::with_capacity(paths.len());
let mut draw_indirect_commands = Vec::with_capacity(paths.len());
@@ -296,11 +300,10 @@ impl DirectXRenderer {
});
start_vertex_location += path.vertices.len() as u32;
- vertices.extend(path.vertices.iter().map(|v| PathVertex {
+ vertices.extend(path.vertices.iter().map(|v| DirectXPathVertex {
xy_position: v.xy_position,
- content_mask: ContentMask {
- bounds: path.content_mask.bounds,
- },
+ content_mask: path.content_mask.bounds,
+ sprite_index: i as u32,
}));
sprites.push(PathSprite {
@@ -309,64 +312,34 @@ impl DirectXRenderer {
});
}
- update_buffer_capacity(
- &self.pipelines.paths_pipeline,
- std::mem::size_of::<PathSprite>(),
- sprites.len(),
+ self.pipelines.paths_pipeline.update_buffer(
&self.devices.device,
- )
- .map(|input| update_pipeline(&mut self.pipelines.paths_pipeline, input));
- update_buffer(
&self.devices.device_context,
- &self.pipelines.paths_pipeline.buffer,
&sprites,
- )?;
- update_indirect_buffer(
- &self.devices.device_context,
- &self.pipelines.paths_indirect_draw_buffer,
+ &vertices,
&draw_indirect_commands,
)?;
- prepare_indirect_draws(
+ self.pipelines.paths_pipeline.draw(
&self.devices.device_context,
- &self.pipelines.paths_pipeline,
- &self.context.viewport,
+ paths.len(),
+ &self.resources.viewport,
&self.globals.global_params_buffer,
- D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
- )?;
-
- for i in 0..paths.len() {
- draw_indirect(
- &self.devices.device_context,
- &self.pipelines.paths_indirect_draw_buffer,
- (i * std::mem::size_of::<DrawInstancedIndirectArgs>()) as u32,
- );
- }
- Ok(())
+ )
}
fn draw_underlines(&mut self, underlines: &[Underline]) -> Result<()> {
if underlines.is_empty() {
return Ok(());
}
- update_buffer_capacity(
- &self.pipelines.underline_pipeline,
- std::mem::size_of::<Underline>(),
- underlines.len(),
+ self.pipelines.underline_pipeline.update_buffer(
&self.devices.device,
- )
- .map(|input| update_pipeline(&mut self.pipelines.underline_pipeline, input));
- update_buffer(
&self.devices.device_context,
- &self.pipelines.underline_pipeline.buffer,
underlines,
)?;
- draw_normal(
+ self.pipelines.underline_pipeline.draw(
&self.devices.device_context,
- &self.pipelines.underline_pipeline,
- &self.context.viewport,
+ &self.resources.viewport,
&self.globals.global_params_buffer,
- D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
- 4,
underlines.len() as u32,
)
}
@@ -379,24 +352,16 @@ impl DirectXRenderer {
if sprites.is_empty() {
return Ok(());
}
- let texture_view = self.atlas.get_texture_view(texture_id);
- update_buffer_capacity(
- &self.pipelines.mono_sprites,
- std::mem::size_of::<MonochromeSprite>(),
- sprites.len(),
+ self.pipelines.mono_sprites.update_buffer(
&self.devices.device,
- )
- .map(|input| update_pipeline(&mut self.pipelines.mono_sprites, input));
- update_buffer(
&self.devices.device_context,
- &self.pipelines.mono_sprites.buffer,
sprites,
)?;
- draw_with_texture(
+ let texture_view = self.atlas.get_texture_view(texture_id);
+ self.pipelines.mono_sprites.draw_with_texture(
&self.devices.device_context,
- &self.pipelines.mono_sprites,
&texture_view,
- &self.context.viewport,
+ &self.resources.viewport,
&self.globals.global_params_buffer,
&self.globals.sampler,
sprites.len() as u32,
@@ -411,24 +376,16 @@ impl DirectXRenderer {
if sprites.is_empty() {
return Ok(());
}
- let texture_view = self.atlas.get_texture_view(texture_id);
- update_buffer_capacity(
- &self.pipelines.poly_sprites,
- std::mem::size_of::<PolychromeSprite>(),
- sprites.len(),
+ self.pipelines.poly_sprites.update_buffer(
&self.devices.device,
- )
- .map(|input| update_pipeline(&mut self.pipelines.poly_sprites, input));
- update_buffer(
&self.devices.device_context,
- &self.pipelines.poly_sprites.buffer,
sprites,
)?;
- draw_with_texture(
+ let texture_view = self.atlas.get_texture_view(texture_id);
+ self.pipelines.poly_sprites.draw_with_texture(
&self.devices.device_context,
- &self.pipelines.poly_sprites,
&texture_view,
- &self.context.viewport,
+ &self.resources.viewport,
&self.globals.global_params_buffer,
&self.globals.sampler,
sprites.len() as u32,
@@ -441,88 +398,125 @@ impl DirectXRenderer {
}
Ok(())
}
+
+ pub(crate) fn gpu_specs(&self) -> Result<GpuSpecs> {
+ let desc = unsafe { self.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(),
+ _ => "Unknown Vendor".to_string(),
+ };
+ let driver_version = match desc.VendorId {
+ 0x10DE => nvidia::get_driver_version(),
+ 0x1002 => Err(anyhow::anyhow!("AMD driver info not implemented yet")),
+ 0x8086 => intel::get_driver_version(&self.devices.adapter),
+ _ => Err(anyhow::anyhow!("Unknown vendor detected.")),
+ }
+ .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,
+ })
+ }
}
-impl DirectXContext {
- pub fn new(devices: &DirectXDevices, hwnd: HWND, transparent: bool) -> Result<Self> {
- // #[cfg(not(feature = "enable-renderdoc"))]
- // let swap_chain = create_swap_chain(&devices.dxgi_factory, &devices.device, transparent)?;
- // #[cfg(feature = "enable-renderdoc")]
+impl DirectXResources {
+ pub fn new(
+ devices: &DirectXDevices,
+ #[cfg(feature = "enable-renderdoc")] hwnd: HWND,
+ ) -> Result<Self> {
+ let width = 1;
+ let height = 1;
+
+ #[cfg(not(feature = "enable-renderdoc"))]
+ let swap_chain = create_swap_chain(&devices.dxgi_factory, &devices.device, width, height)?;
+ #[cfg(feature = "enable-renderdoc")]
let swap_chain =
- create_swap_chain_default(&devices.dxgi_factory, &devices.device, hwnd, transparent)?;
- // #[cfg(not(feature = "enable-renderdoc"))]
- // let direct_composition = DirectComposition::new(&devices.dxgi_device, hwnd)?;
- // #[cfg(not(feature = "enable-renderdoc"))]
- // direct_composition.set_swap_chain(&swap_chain)?;
- let back_buffer = [Some(set_render_target_view(
- &swap_chain,
- &devices.device,
- &devices.device_context,
- )?)];
- let viewport = set_viewport(&devices.device_context, 1.0, 1.0);
+ create_swap_chain(&devices.dxgi_factory, &devices.device, hwnd, width, height)?;
+
+ let (render_target, render_target_view, msaa_target, msaa_view, viewport) =
+ create_resources(devices, &swap_chain, width, height)?;
set_rasterizer_state(&devices.device, &devices.device_context)?;
Ok(Self {
swap_chain,
- back_buffer,
+ render_target,
+ render_target_view,
+ msaa_target,
+ msaa_view,
+ width,
+ height,
viewport,
- // #[cfg(not(feature = "enable-renderdoc"))]
- // direct_composition,
})
}
+
+ #[inline]
+ fn recreate_resources(
+ &mut self,
+ devices: &DirectXDevices,
+ width: u32,
+ height: u32,
+ ) -> Result<()> {
+ let (render_target, render_target_view, msaa_target, msaa_view, viewport) =
+ create_resources(devices, &self.swap_chain, width, height)?;
+ self.render_target = render_target;
+ self.render_target_view = render_target_view;
+ self.msaa_target = msaa_target;
+ self.msaa_view = msaa_view;
+ self.viewport = viewport;
+ self.width = width;
+ self.height = height;
+ Ok(())
+ }
}
impl DirectXRenderPipelines {
pub fn new(device: &ID3D11Device) -> Result<Self> {
- let shadow_pipeline = create_pipieline(
+ let shadow_pipeline = PipelineState::new(
device,
+ "shadow_pipeline",
"shadow_vertex",
"shadow_fragment",
- std::mem::size_of::<Shadow>(),
- 32,
- )?;
- let quad_pipeline = create_pipieline(
- device,
- "quad_vertex",
- "quad_fragment",
- std::mem::size_of::<Quad>(),
- 32,
- )?;
- let paths_pipeline = create_pipieline(
- device,
- "paths_vertex",
- "paths_fragment",
- std::mem::size_of::<PathSprite>(),
- 32,
+ 4,
)?;
- let underline_pipeline = create_pipieline(
+ let quad_pipeline =
+ PipelineState::new(device, "quad_pipeline", "quad_vertex", "quad_fragment", 64)?;
+ let paths_pipeline = PathsPipelineState::new(device)?;
+ let underline_pipeline = PipelineState::new(
device,
+ "underline_pipeline",
"underline_vertex",
"underline_fragment",
- std::mem::size_of::<Underline>(),
- 32,
+ 4,
)?;
- let mono_sprites = create_pipieline(
+ let mono_sprites = PipelineState::new(
device,
+ "monochrome_sprite_pipeline",
"monochrome_sprite_vertex",
"monochrome_sprite_fragment",
- std::mem::size_of::<MonochromeSprite>(),
- 32,
+ 512,
)?;
- let poly_sprites = create_pipieline(
+ let poly_sprites = PipelineState::new(
device,
+ "polychrome_sprite_pipeline",
"polychrome_sprite_vertex",
"polychrome_sprite_fragment",
- std::mem::size_of::<PolychromeSprite>(),
- 32,
+ 16,
)?;
- let paths_indirect_draw_buffer = create_indirect_draw_buffer(device, 32)?;
Ok(Self {
shadow_pipeline,
quad_pipeline,
paths_pipeline,
- paths_indirect_draw_buffer,
underline_pipeline,
mono_sprites,
poly_sprites,
@@ -530,29 +524,29 @@ impl DirectXRenderPipelines {
}
}
-// #[cfg(not(feature = "enable-renderdoc"))]
-// 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(())
-// }
-// }
+#[cfg(not(feature = "enable-renderdoc"))]
+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> {
@@ -588,13 +582,11 @@ impl DirectXGlobalElements {
};
let blend_state = create_blend_state(device)?;
- let blend_state_for_pr = create_blend_state_for_path_raster(device)?;
Ok(Self {
global_params_buffer,
sampler,
blend_state,
- blend_state_for_pr,
})
}
}
@@ -606,14 +598,345 @@ struct GlobalParams {
_pad: u64,
}
-struct PipelineState {
+struct PipelineState<T> {
+ label: &'static str,
+ vertex: ID3D11VertexShader,
+ fragment: ID3D11PixelShader,
+ buffer: ID3D11Buffer,
+ buffer_size: usize,
+ view: [Option<ID3D11ShaderResourceView>; 1],
+ _marker: std::marker::PhantomData<T>,
+}
+
+struct PathsPipelineState {
vertex: ID3D11VertexShader,
fragment: ID3D11PixelShader,
buffer: ID3D11Buffer,
buffer_size: usize,
+ vertex_buffer: Option<ID3D11Buffer>,
+ vertex_buffer_size: usize,
+ indirect_draw_buffer: ID3D11Buffer,
+ indirect_buffer_size: usize,
+ input_layout: ID3D11InputLayout,
view: [Option<ID3D11ShaderResourceView>; 1],
}
+impl<T> PipelineState<T> {
+ fn new(
+ device: &ID3D11Device,
+ label: &'static str,
+ vertex_entry: &str,
+ fragment_entry: &str,
+ buffer_size: usize,
+ ) -> Result<Self> {
+ let vertex = {
+ let shader_blob = shader_resources::build_shader_blob(vertex_entry, "vs_5_0")?;
+ let bytes = unsafe {
+ std::slice::from_raw_parts(
+ shader_blob.GetBufferPointer() as *mut u8,
+ shader_blob.GetBufferSize(),
+ )
+ };
+ create_vertex_shader(device, bytes)?
+ };
+ let fragment = {
+ let shader_blob = shader_resources::build_shader_blob(fragment_entry, "ps_5_0")?;
+ let bytes = unsafe {
+ std::slice::from_raw_parts(
+ shader_blob.GetBufferPointer() as *mut u8,
+ shader_blob.GetBufferSize(),
+ )
+ };
+ create_fragment_shader(device, 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,
+ _marker: std::marker::PhantomData,
+ })
+ }
+
+ fn update_buffer(
+ &mut self,
+ device: &ID3D11Device,
+ device_context: &ID3D11DeviceContext,
+ data: &[T],
+ ) -> Result<()> {
+ if self.buffer_size < data.len() {
+ let new_buffer_size = data.len().next_power_of_two();
+ log::info!(
+ "Updating {} buffer size from {} to {}",
+ self.label,
+ self.buffer_size,
+ new_buffer_size
+ );
+ let buffer = create_buffer(device, std::mem::size_of::<T>(), new_buffer_size)?;
+ let view = create_buffer_view(device, &buffer)?;
+ self.buffer = buffer;
+ self.view = view;
+ self.buffer_size = new_buffer_size;
+ }
+ update_buffer(device_context, &self.buffer, data)
+ }
+
+ fn draw(
+ &self,
+ device_context: &ID3D11DeviceContext,
+ viewport: &[D3D11_VIEWPORT],
+ global_params: &[Option<ID3D11Buffer>],
+ instance_count: u32,
+ ) -> Result<()> {
+ set_pipeline_state(
+ device_context,
+ &self.view,
+ D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
+ viewport,
+ &self.vertex,
+ &self.fragment,
+ global_params,
+ );
+ unsafe {
+ device_context.DrawInstanced(4, 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,
+ &self.view,
+ D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
+ viewport,
+ &self.vertex,
+ &self.fragment,
+ global_params,
+ );
+ 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(())
+ }
+}
+
+impl PathsPipelineState {
+ fn new(device: &ID3D11Device) -> Result<Self> {
+ let (vertex, vertex_shader) = {
+ let shader_blob = shader_resources::build_shader_blob("paths_vertex", "vs_5_0")?;
+ let bytes = unsafe {
+ std::slice::from_raw_parts(
+ shader_blob.GetBufferPointer() as *mut u8,
+ shader_blob.GetBufferSize(),
+ )
+ };
+ (create_vertex_shader(device, bytes)?, shader_blob)
+ };
+ let fragment = {
+ let shader_blob = shader_resources::build_shader_blob("paths_fragment", "ps_5_0")?;
+ let bytes = unsafe {
+ std::slice::from_raw_parts(
+ shader_blob.GetBufferPointer() as *mut u8,
+ shader_blob.GetBufferSize(),
+ )
+ };
+ create_fragment_shader(device, bytes)?
+ };
+ let buffer = create_buffer(device, std::mem::size_of::<PathSprite>(), 32)?;
+ let view = create_buffer_view(device, &buffer)?;
+ let vertex_buffer = Some(create_buffer(
+ device,
+ std::mem::size_of::<DirectXPathVertex>(),
+ 32,
+ )?);
+ let indirect_draw_buffer = create_indirect_draw_buffer(device, 32)?;
+ // Create input layout
+ let input_layout = unsafe {
+ let shader_bytes = std::slice::from_raw_parts(
+ vertex_shader.GetBufferPointer() as *const u8,
+ vertex_shader.GetBufferSize(),
+ );
+ let mut layout = None;
+ device.CreateInputLayout(
+ &[
+ D3D11_INPUT_ELEMENT_DESC {
+ SemanticName: windows::core::s!("POSITION"),
+ SemanticIndex: 0,
+ Format: DXGI_FORMAT_R32G32_FLOAT,
+ InputSlot: 0,
+ AlignedByteOffset: 0,
+ InputSlotClass: D3D11_INPUT_PER_VERTEX_DATA,
+ InstanceDataStepRate: 0,
+ },
+ D3D11_INPUT_ELEMENT_DESC {
+ SemanticName: windows::core::s!("TEXCOORD"),
+ SemanticIndex: 0,
+ Format: DXGI_FORMAT_R32G32_FLOAT,
+ InputSlot: 0,
+ AlignedByteOffset: 8,
+ InputSlotClass: D3D11_INPUT_PER_VERTEX_DATA,
+ InstanceDataStepRate: 0,
+ },
+ D3D11_INPUT_ELEMENT_DESC {
+ SemanticName: windows::core::s!("TEXCOORD"),
+ SemanticIndex: 1,
+ Format: DXGI_FORMAT_R32G32_FLOAT,
+ InputSlot: 0,
+ AlignedByteOffset: 16,
+ InputSlotClass: D3D11_INPUT_PER_VERTEX_DATA,
+ InstanceDataStepRate: 0,
+ },
+ D3D11_INPUT_ELEMENT_DESC {
+ SemanticName: windows::core::s!("GLOBALIDX"),
+ SemanticIndex: 0,
+ Format: DXGI_FORMAT_R32_UINT,
+ InputSlot: 0,
+ AlignedByteOffset: 24,
+ InputSlotClass: D3D11_INPUT_PER_VERTEX_DATA,
+ InstanceDataStepRate: 0,
+ },
+ ],
+ shader_bytes,
+ Some(&mut layout),
+ )?;
+ layout.unwrap()
+ };
+
+ Ok(Self {
+ vertex,
+ fragment,
+ buffer,
+ buffer_size: 32,
+ vertex_buffer,
+ vertex_buffer_size: 32,
+ indirect_draw_buffer,
+ indirect_buffer_size: 32,
+ input_layout,
+ view,
+ })
+ }
+
+ fn update_buffer(
+ &mut self,
+ device: &ID3D11Device,
+ device_context: &ID3D11DeviceContext,
+ buffer_data: &[PathSprite],
+ vertices_data: &[DirectXPathVertex],
+ draw_commands: &[DrawInstancedIndirectArgs],
+ ) -> Result<()> {
+ if self.buffer_size < buffer_data.len() {
+ let new_buffer_size = buffer_data.len().next_power_of_two();
+ log::info!(
+ "Updating Paths Pipeline buffer size from {} to {}",
+ self.buffer_size,
+ new_buffer_size
+ );
+ let buffer = create_buffer(device, std::mem::size_of::<PathSprite>(), 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, buffer_data)?;
+ if self.vertex_buffer_size < vertices_data.len() {
+ let new_vertex_buffer_size = vertices_data.len().next_power_of_two();
+ log::info!(
+ "Updating Paths Pipeline vertex buffer size from {} to {}",
+ self.vertex_buffer_size,
+ new_vertex_buffer_size
+ );
+ let vertex_buffer = create_buffer(
+ device,
+ std::mem::size_of::<DirectXPathVertex>(),
+ new_vertex_buffer_size,
+ )?;
+ self.vertex_buffer = Some(vertex_buffer);
+ self.vertex_buffer_size = new_vertex_buffer_size;
+ }
+ update_buffer(
+ device_context,
+ self.vertex_buffer.as_ref().unwrap(),
+ vertices_data,
+ )?;
+ if self.indirect_buffer_size < draw_commands.len() {
+ let new_indirect_buffer_size = draw_commands.len().next_power_of_two();
+ log::info!(
+ "Updating Paths Pipeline indirect buffer size from {} to {}",
+ self.indirect_buffer_size,
+ new_indirect_buffer_size
+ );
+ let indirect_draw_buffer =
+ create_indirect_draw_buffer(device, new_indirect_buffer_size)?;
+ self.indirect_draw_buffer = indirect_draw_buffer;
+ self.indirect_buffer_size = new_indirect_buffer_size;
+ }
+ update_buffer(device_context, &self.indirect_draw_buffer, draw_commands)?;
+ Ok(())
+ }
+
+ fn draw(
+ &self,
+ device_context: &ID3D11DeviceContext,
+ count: usize,
+ viewport: &[D3D11_VIEWPORT],
+ global_params: &[Option<ID3D11Buffer>],
+ ) -> Result<()> {
+ set_pipeline_state(
+ device_context,
+ &self.view,
+ D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
+ viewport,
+ &self.vertex,
+ &self.fragment,
+ global_params,
+ );
+ unsafe {
+ const STRIDE: u32 = std::mem::size_of::<DirectXPathVertex>() as u32;
+ device_context.IASetVertexBuffers(
+ 0,
+ 1,
+ Some(&self.vertex_buffer),
+ Some(&STRIDE),
+ Some(&0),
+ );
+ device_context.IASetInputLayout(&self.input_layout);
+ }
+ for i in 0..count {
+ unsafe {
+ device_context.DrawInstancedIndirect(
+ &self.indirect_draw_buffer,
+ (i * std::mem::size_of::<DrawInstancedIndirectArgs>()) as u32,
+ );
+ }
+ }
+ Ok(())
+ }
+}
+
+#[repr(C)]
+struct DirectXPathVertex {
+ xy_position: Point<ScaledPixels>,
+ content_mask: Bounds<ScaledPixels>,
+ sprite_index: u32,
+}
+
#[derive(Clone, Debug, Eq, PartialEq)]
#[repr(C)]
struct PathSprite {
@@ -93,10 +93,9 @@ float4 to_device_position(float2 unit_vertex, Bounds bounds) {
}
float4 distance_from_clip_rect_impl(float2 position, Bounds clip_bounds) {
- return float4(position.x - clip_bounds.origin.x,
- clip_bounds.origin.x + clip_bounds.size.x - position.x,
- position.y - clip_bounds.origin.y,
- clip_bounds.origin.y + clip_bounds.size.y - position.y);
+ float2 tl = position - clip_bounds.origin;
+ float2 br = clip_bounds.origin + clip_bounds.size - position;
+ return float4(tl.x, br.x, tl.y, br.y);
}
float4 distance_from_clip_rect(float2 unit_vertex, Bounds bounds, Bounds clip_bounds) {
@@ -240,6 +239,23 @@ float2 to_tile_position(float2 unit_vertex, AtlasTile tile) {
return (float2(tile.bounds.origin) + unit_vertex * float2(tile.bounds.size)) / atlas_size;
}
+// Selects corner radius based on quadrant.
+float pick_corner_radius(float2 center_to_point, Corners corner_radii) {
+ if (center_to_point.x < 0.) {
+ if (center_to_point.y < 0.) {
+ return corner_radii.top_left;
+ } else {
+ return corner_radii.bottom_left;
+ }
+ } else {
+ if (center_to_point.y < 0.) {
+ return corner_radii.top_right;
+ } else {
+ return corner_radii.bottom_right;
+ }
+ }
+}
+
float4 to_device_position_transformed(float2 unit_vertex, Bounds bounds,
TransformationMatrix transformation) {
float2 position = unit_vertex * bounds.size + bounds.origin;
@@ -248,48 +264,48 @@ float4 to_device_position_transformed(float2 unit_vertex, Bounds bounds,
return float4(device_position, 0.0, 1.0);
}
+// Implementation of quad signed distance field
+float quad_sdf_impl(float2 corner_center_to_point, float corner_radius) {
+ if (corner_radius == 0.0) {
+ // Fast path for unrounded corners
+ return max(corner_center_to_point.x, corner_center_to_point.y);
+ } else {
+ // Signed distance of the point from a quad that is inset by corner_radius
+ // It is negative inside this quad, and positive outside
+ float signed_distance_to_inset_quad =
+ // 0 inside the inset quad, and positive outside
+ length(max(float2(0.0, 0.0), corner_center_to_point)) +
+ // 0 outside the inset quad, and negative inside
+ min(0.0, max(corner_center_to_point.x, corner_center_to_point.y));
+
+ return signed_distance_to_inset_quad - corner_radius;
+ }
+}
+
float quad_sdf(float2 pt, Bounds bounds, Corners corner_radii) {
float2 half_size = bounds.size / 2.;
float2 center = bounds.origin + half_size;
float2 center_to_point = pt - center;
- float corner_radius;
- if (center_to_point.x < 0.) {
- if (center_to_point.y < 0.) {
- corner_radius = corner_radii.top_left;
- } else {
- corner_radius = corner_radii.bottom_left;
- }
- } else {
- if (center_to_point.y < 0.) {
- corner_radius = corner_radii.top_right;
- } else {
- corner_radius = corner_radii.bottom_right;
- }
- }
-
- float2 rounded_edge_to_point = abs(center_to_point) - half_size + corner_radius;
- float distance =
- length(max(0., rounded_edge_to_point)) +
- min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
- corner_radius;
-
- return distance;
+ float corner_radius = pick_corner_radius(center_to_point, corner_radii);
+ float2 corner_to_point = abs(center_to_point) - half_size;
+ float2 corner_center_to_point = corner_to_point + corner_radius;
+ return quad_sdf_impl(corner_center_to_point, corner_radius);
}
-GradientColor prepare_gradient_color(uint tag, uint color_space, Hsla solid, Hsla color0, Hsla color1) {
+GradientColor prepare_gradient_color(uint tag, uint color_space, Hsla solid, LinearColorStop colors[2]) {
GradientColor output;
- if (tag == 0) {
+ if (tag == 0 || tag == 2) {
output.solid = hsla_to_rgba(solid);
} else if (tag == 1) {
- output.color0 = hsla_to_rgba(color0);
- output.color1 = hsla_to_rgba(color1);
+ output.color0 = hsla_to_rgba(colors[0].color);
+ output.color1 = hsla_to_rgba(colors[1].color);
// Prepare color space in vertex for avoid conversion
// in fragment shader for performance reasons
if (color_space == 1) {
- // Oklab
- output.color0 = srgb_to_oklab(output.color0);
- output.color1 = srgb_to_oklab(output.color1);
+ // Oklab
+ output.color0 = srgb_to_oklab(output.color0);
+ output.color1 = srgb_to_oklab(output.color1);
}
}
@@ -326,8 +342,8 @@ float4 gradient_color(Background background,
}
// Get the t value for the linear gradient with the color stop percentages.
- float2 half_size = float2(bounds.size.x, bounds.size.y) / 2.;
- float2 center = float2(bounds.origin.x, bounds.origin.y) + half_size;
+ float2 half_size = bounds.size * 0.5;
+ float2 center = bounds.origin + half_size;
float2 center_to_point = position - center;
float t = dot(center_to_point, direction) / length(direction);
// Check the direct to determine the use x or y
@@ -376,97 +392,55 @@ float4 gradient_color(Background background,
return color;
}
-/*
-**
-** Shadows
-**
-*/
-
-struct ShadowVertexOutput {
- float4 position: SV_Position;
- float4 color: COLOR;
- uint shadow_id: FLAT;
- float4 clip_distance: SV_ClipDistance;
-};
-
-struct ShadowFragmentInput {
- float4 position: SV_Position;
- float4 color: COLOR;
- uint shadow_id: FLAT;
-};
-
-struct Shadow {
- uint order;
- float blur_radius;
- Bounds bounds;
- Corners corner_radii;
- Bounds content_mask;
- Hsla color;
-};
-
-StructuredBuffer<Shadow> shadows: register(t1);
-
-ShadowVertexOutput shadow_vertex(uint vertex_id: SV_VertexID, uint shadow_id: SV_InstanceID) {
- float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
- Shadow shadow = shadows[shadow_id];
-
- float margin = 3.0 * shadow.blur_radius;
- Bounds bounds = shadow.bounds;
- bounds.origin -= margin;
- bounds.size += 2.0 * margin;
-
- float4 device_position = to_device_position(unit_vertex, bounds);
- float4 clip_distance = distance_from_clip_rect(unit_vertex, bounds, shadow.content_mask);
- float4 color = hsla_to_rgba(shadow.color);
-
- ShadowVertexOutput output;
- output.position = device_position;
- output.color = color;
- output.shadow_id = shadow_id;
- output.clip_distance = clip_distance;
-
- return output;
-}
-
-float4 shadow_fragment(ShadowFragmentInput input): SV_TARGET {
- Shadow shadow = shadows[input.shadow_id];
-
- float2 half_size = shadow.bounds.size / 2.;
- float2 center = shadow.bounds.origin + half_size;
- float2 point0 = input.position.xy - center;
- float corner_radius;
- if (point0.x < 0.) {
- if (point0.y < 0.) {
- corner_radius = shadow.corner_radii.top_left;
- } else {
- corner_radius = shadow.corner_radii.bottom_left;
- }
+// Returns the dash velocity of a corner given the dash velocity of the two
+// sides, by returning the slower velocity (larger dashes).
+//
+// Since 0 is used for dash velocity when the border width is 0 (instead of
+// +inf), this returns the other dash velocity in that case.
+//
+// An alternative to this might be to appropriately interpolate the dash
+// velocity around the corner, but that seems overcomplicated.
+float corner_dash_velocity(float dv1, float dv2) {
+ if (dv1 == 0.0) {
+ return dv2;
+ } else if (dv2 == 0.0) {
+ return dv1;
} else {
- if (point0.y < 0.) {
- corner_radius = shadow.corner_radii.top_right;
- } else {
- corner_radius = shadow.corner_radii.bottom_right;
- }
+ return min(dv1, dv2);
}
+}
- // The signal is only non-zero in a limited range, so don't waste samples
- float low = point0.y - half_size.y;
- float high = point0.y + half_size.y;
- float start = clamp(-3. * shadow.blur_radius, low, high);
- float end = clamp(3. * shadow.blur_radius, low, high);
-
- // Accumulate samples (we can get away with surprisingly few samples)
- float step = (end - start) / 4.;
- float y = start + step * 0.5;
- float alpha = 0.;
- for (int i = 0; i < 4; i++) {
- alpha += blur_along_x(point0.x, point0.y - y, shadow.blur_radius,
- corner_radius, half_size) *
- gaussian(y, shadow.blur_radius) * step;
- y += step;
- }
+// Returns alpha used to render antialiased dashes.
+// `t` is within the dash when `fmod(t, period) < length`.
+float dash_alpha(
+ float t, float period, float length, float dash_velocity,
+ float antialias_threshold
+) {
+ float half_period = period / 2.0;
+ float half_length = length / 2.0;
+ // Value in [-half_period, half_period]
+ // The dash is in [-half_length, half_length]
+ float centered = fmod(t + half_period - half_length, period) - half_period;
+ // Signed distance for the dash, negative values are inside the dash
+ float signed_distance = abs(centered) - half_length;
+ // Antialiased alpha based on the signed distance
+ return saturate(antialias_threshold - signed_distance / dash_velocity);
+}
- return input.color * float4(1., 1., 1., alpha);
+// This approximates distance to the nearest point to a quarter ellipse in a way
+// that is sufficient for anti-aliasing when the ellipse is not very eccentric.
+// The components of `point` are expected to be positive.
+//
+// Negative on the outside and positive on the inside.
+float quarter_ellipse_sdf(float2 pt, float2 radii) {
+ // Scale the space to treat the ellipse like a unit circle
+ float2 circle_vec = pt / radii;
+ float unit_circle_sdf = length(circle_vec) - 1.0;
+ // Approximate up-scaling of the length by using the average of the radii.
+ //
+ // TODO: A better solution would be to use the gradient of the implicit
+ // function for an ellipse to approximate a scaling factor.
+ return unit_circle_sdf * (radii.x + radii.y) * -0.5;
}
/*
@@ -477,7 +451,7 @@ float4 shadow_fragment(ShadowFragmentInput input): SV_TARGET {
struct Quad {
uint order;
- uint pad;
+ uint border_style;
Bounds bounds;
Bounds content_mask;
Background background;
@@ -487,9 +461,9 @@ struct Quad {
};
struct QuadVertexOutput {
+ nointerpolation uint quad_id: TEXCOORD0;
float4 position: SV_Position;
nointerpolation float4 border_color: COLOR0;
- nointerpolation uint quad_id: TEXCOORD0;
nointerpolation float4 background_solid: COLOR1;
nointerpolation float4 background_color0: COLOR2;
nointerpolation float4 background_color1: COLOR3;
@@ -511,16 +485,15 @@ QuadVertexOutput quad_vertex(uint vertex_id: SV_VertexID, uint quad_id: SV_Insta
float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
Quad quad = quads[quad_id];
float4 device_position = to_device_position(unit_vertex, quad.bounds);
- float4 clip_distance = distance_from_clip_rect(unit_vertex, quad.bounds, quad.content_mask);
- float4 border_color = hsla_to_rgba(quad.border_color);
GradientColor gradient = prepare_gradient_color(
quad.background.tag,
quad.background.color_space,
quad.background.solid,
- quad.background.colors[0].color,
- quad.background.colors[1].color
+ quad.background.colors
);
+ float4 clip_distance = distance_from_clip_rect(unit_vertex, quad.bounds, quad.content_mask);
+ float4 border_color = hsla_to_rgba(quad.border_color);
QuadVertexOutput output;
output.position = device_position;
@@ -535,79 +508,380 @@ QuadVertexOutput quad_vertex(uint vertex_id: SV_VertexID, uint quad_id: SV_Insta
float4 quad_fragment(QuadFragmentInput input): SV_Target {
Quad quad = quads[input.quad_id];
- float2 half_size = quad.bounds.size / 2.;
- float2 center = quad.bounds.origin + half_size;
- float2 center_to_point = input.position.xy - center;
- float4 color = gradient_color(quad.background, input.position.xy, quad.bounds,
+ float4 background_color = gradient_color(quad.background, input.position.xy, quad.bounds,
input.background_solid, input.background_color0, input.background_color1);
- // Fast path when the quad is not rounded and doesn't have any border.
- if (quad.corner_radii.top_left == 0. && quad.corner_radii.bottom_left == 0. &&
- quad.corner_radii.top_right == 0. &&
- quad.corner_radii.bottom_right == 0. && quad.border_widths.top == 0. &&
- quad.border_widths.left == 0. && quad.border_widths.right == 0. &&
- quad.border_widths.bottom == 0.) {
- return color;
+ bool unrounded = quad.corner_radii.top_left == 0.0 &&
+ quad.corner_radii.top_right == 0.0 &&
+ quad.corner_radii.bottom_left == 0.0 &&
+ quad.corner_radii.bottom_right == 0.0;
+
+ // Fast path when the quad is not rounded and doesn't have any border
+ if (quad.border_widths.top == 0.0 &&
+ quad.border_widths.left == 0.0 &&
+ quad.border_widths.right == 0.0 &&
+ quad.border_widths.bottom == 0.0 &&
+ unrounded) {
+ return background_color;
}
- float corner_radius;
- if (center_to_point.x < 0.) {
- if (center_to_point.y < 0.) {
- corner_radius = quad.corner_radii.top_left;
- } else {
- corner_radius = quad.corner_radii.bottom_left;
- }
- } else {
- if (center_to_point.y < 0.) {
- corner_radius = quad.corner_radii.top_right;
- } else {
- corner_radius = quad.corner_radii.bottom_right;
- }
+ float2 size = quad.bounds.size;
+ float2 half_size = size / 2.;
+ float2 the_point = input.position.xy - quad.bounds.origin;
+ float2 center_to_point = the_point - half_size;
+
+ // Signed distance field threshold for inclusion of pixels. 0.5 is the
+ // minimum distance between the center of the pixel and the edge.
+ const float antialias_threshold = 0.5;
+
+ // Radius of the nearest corner
+ float corner_radius = pick_corner_radius(center_to_point, quad.corner_radii);
+
+ float2 border = float2(
+ center_to_point.x < 0.0 ? quad.border_widths.left : quad.border_widths.right,
+ center_to_point.y < 0.0 ? quad.border_widths.top : quad.border_widths.bottom
+ );
+
+ // 0-width borders are reduced so that `inner_sdf >= antialias_threshold`.
+ // The purpose of this is to not draw antialiasing pixels in this case.
+ float2 reduced_border = float2(
+ border.x == 0.0 ? -antialias_threshold : border.x,
+ border.y == 0.0 ? -antialias_threshold : border.y
+ );
+
+ // Vector from the corner of the quad bounds to the point, after mirroring
+ // the point into the bottom right quadrant. Both components are <= 0.
+ float2 corner_to_point = abs(center_to_point) - half_size;
+
+ // Vector from the point to the center of the rounded corner's circle, also
+ // mirrored into bottom right quadrant.
+ float2 corner_center_to_point = corner_to_point + corner_radius;
+
+ // Whether the nearest point on the border is rounded
+ bool is_near_rounded_corner =
+ corner_center_to_point.x >= 0.0 &&
+ corner_center_to_point.y >= 0.0;
+
+ // Vector from straight border inner corner to point.
+ //
+ // 0-width borders are turned into width -1 so that inner_sdf is > 1.0 near
+ // the border. Without this, antialiasing pixels would be drawn.
+ float2 straight_border_inner_corner_to_point = corner_to_point + reduced_border;
+
+ // Whether the point is beyond the inner edge of the straight border
+ bool is_beyond_inner_straight_border =
+ straight_border_inner_corner_to_point.x > 0.0 ||
+ straight_border_inner_corner_to_point.y > 0.0;
+
+ // Whether the point is far enough inside the quad, such that the pixels are
+ // not affected by the straight border.
+ bool is_within_inner_straight_border =
+ straight_border_inner_corner_to_point.x < -antialias_threshold &&
+ straight_border_inner_corner_to_point.y < -antialias_threshold;
+
+ // Fast path for points that must be part of the background
+ if (is_within_inner_straight_border && !is_near_rounded_corner) {
+ return background_color;
}
- float2 rounded_edge_to_point = abs(center_to_point) - half_size + corner_radius;
- float distance =
- length(max(0., rounded_edge_to_point)) +
- min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
- corner_radius;
-
- float vertical_border = center_to_point.x <= 0. ? quad.border_widths.left
- : quad.border_widths.right;
- float horizontal_border = center_to_point.y <= 0. ? quad.border_widths.top
- : quad.border_widths.bottom;
- float2 inset_size = half_size - corner_radius - float2(vertical_border, horizontal_border);
- float2 point_to_inset_corner = abs(center_to_point) - inset_size;
- float border_width;
- if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
- border_width = 0.;
- } else if (point_to_inset_corner.y > point_to_inset_corner.x) {
- border_width = horizontal_border;
+ // Signed distance of the point to the outside edge of the quad's border
+ float outer_sdf = quad_sdf_impl(corner_center_to_point, corner_radius);
+
+ // Approximate signed distance of the point to the inside edge of the quad's
+ // border. It is negative outside this edge (within the border), and
+ // positive inside.
+ //
+ // This is not always an accurate signed distance:
+ // * The rounded portions with varying border width use an approximation of
+ // nearest-point-on-ellipse.
+ // * When it is quickly known to be outside the edge, -1.0 is used.
+ float inner_sdf = 0.0;
+ if (corner_center_to_point.x <= 0.0 || corner_center_to_point.y <= 0.0) {
+ // Fast paths for straight borders
+ inner_sdf = -max(straight_border_inner_corner_to_point.x,
+ straight_border_inner_corner_to_point.y);
+ } else if (is_beyond_inner_straight_border) {
+ // Fast path for points that must be outside the inner edge
+ inner_sdf = -1.0;
+ } else if (reduced_border.x == reduced_border.y) {
+ // Fast path for circular inner edge.
+ inner_sdf = -(outer_sdf + reduced_border.x);
} else {
- border_width = vertical_border;
+ float2 ellipse_radii = max(float2(0.0, 0.0), float2(corner_radius, corner_radius) - reduced_border);
+ inner_sdf = quarter_ellipse_sdf(corner_center_to_point, ellipse_radii);
}
- if (border_width != 0.) {
- float inset_distance = distance + border_width;
+ // Negative when inside the border
+ float border_sdf = max(inner_sdf, outer_sdf);
+
+ float4 color = background_color;
+ if (border_sdf < antialias_threshold) {
+ float4 border_color = input.border_color;
+ // Dashed border logic when border_style == 1
+ if (quad.border_style == 1) {
+ // Position along the perimeter in "dash space", where each dash
+ // period has length 1
+ float t = 0.0;
+
+ // Total number of dash periods, so that the dash spacing can be
+ // adjusted to evenly divide it
+ float max_t = 0.0;
+
+ // Border width is proportional to dash size. This is the behavior
+ // used by browsers, but also avoids dashes from different segments
+ // overlapping when dash size is smaller than the border width.
+ //
+ // Dash pattern: (2 * border width) dash, (1 * border width) gap
+ const float dash_length_per_width = 2.0;
+ const float dash_gap_per_width = 1.0;
+ const float dash_period_per_width = dash_length_per_width + dash_gap_per_width;
+
+ // Since the dash size is determined by border width, the density of
+ // dashes varies. Multiplying a pixel distance by this returns a
+ // position in dash space - it has units (dash period / pixels). So
+ // a dash velocity of (1 / 10) is 1 dash every 10 pixels.
+ float dash_velocity = 0.0;
+
+ // Dividing this by the border width gives the dash velocity
+ const float dv_numerator = 1.0 / dash_period_per_width;
+
+ if (unrounded) {
+ // When corners aren't rounded, the dashes are separately laid
+ // out on each straight line, rather than around the whole
+ // perimeter. This way each line starts and ends with a dash.
+ bool is_horizontal = corner_center_to_point.x < corner_center_to_point.y;
+ float border_width = is_horizontal ? border.x : border.y;
+ dash_velocity = dv_numerator / border_width;
+ t = is_horizontal ? the_point.x : the_point.y;
+ t *= dash_velocity;
+ max_t = is_horizontal ? size.x : size.y;
+ max_t *= dash_velocity;
+ } else {
+ // When corners are rounded, the dashes are laid out clockwise
+ // around the whole perimeter.
+
+ float r_tr = quad.corner_radii.top_right;
+ float r_br = quad.corner_radii.bottom_right;
+ float r_bl = quad.corner_radii.bottom_left;
+ float r_tl = quad.corner_radii.top_left;
+
+ float w_t = quad.border_widths.top;
+ float w_r = quad.border_widths.right;
+ float w_b = quad.border_widths.bottom;
+ float w_l = quad.border_widths.left;
+
+ // Straight side dash velocities
+ float dv_t = w_t <= 0.0 ? 0.0 : dv_numerator / w_t;
+ float dv_r = w_r <= 0.0 ? 0.0 : dv_numerator / w_r;
+ float dv_b = w_b <= 0.0 ? 0.0 : dv_numerator / w_b;
+ float dv_l = w_l <= 0.0 ? 0.0 : dv_numerator / w_l;
+
+ // Straight side lengths in dash space
+ float s_t = (size.x - r_tl - r_tr) * dv_t;
+ float s_r = (size.y - r_tr - r_br) * dv_r;
+ float s_b = (size.x - r_br - r_bl) * dv_b;
+ float s_l = (size.y - r_bl - r_tl) * dv_l;
+
+ float corner_dash_velocity_tr = corner_dash_velocity(dv_t, dv_r);
+ float corner_dash_velocity_br = corner_dash_velocity(dv_b, dv_r);
+ float corner_dash_velocity_bl = corner_dash_velocity(dv_b, dv_l);
+ float corner_dash_velocity_tl = corner_dash_velocity(dv_t, dv_l);
+
+ // Corner lengths in dash space
+ float c_tr = r_tr * (M_PI_F / 2.0) * corner_dash_velocity_tr;
+ float c_br = r_br * (M_PI_F / 2.0) * corner_dash_velocity_br;
+ float c_bl = r_bl * (M_PI_F / 2.0) * corner_dash_velocity_bl;
+ float c_tl = r_tl * (M_PI_F / 2.0) * corner_dash_velocity_tl;
+
+ // Cumulative dash space upto each segment
+ float upto_tr = s_t;
+ float upto_r = upto_tr + c_tr;
+ float upto_br = upto_r + s_r;
+ float upto_b = upto_br + c_br;
+ float upto_bl = upto_b + s_b;
+ float upto_l = upto_bl + c_bl;
+ float upto_tl = upto_l + s_l;
+ max_t = upto_tl + c_tl;
+
+ if (is_near_rounded_corner) {
+ float radians = atan2(corner_center_to_point.y, corner_center_to_point.x);
+ float corner_t = radians * corner_radius;
+
+ if (center_to_point.x >= 0.0) {
+ if (center_to_point.y < 0.0) {
+ dash_velocity = corner_dash_velocity_tr;
+ // Subtracted because radians is pi/2 to 0 when
+ // going clockwise around the top right corner,
+ // since the y axis has been flipped
+ t = upto_r - corner_t * dash_velocity;
+ } else {
+ dash_velocity = corner_dash_velocity_br;
+ // Added because radians is 0 to pi/2 when going
+ // clockwise around the bottom-right corner
+ t = upto_br + corner_t * dash_velocity;
+ }
+ } else {
+ if (center_to_point.y >= 0.0) {
+ dash_velocity = corner_dash_velocity_bl;
+ // Subtracted because radians is pi/1 to 0 when
+ // going clockwise around the bottom-left corner,
+ // since the x axis has been flipped
+ t = upto_l - corner_t * dash_velocity;
+ } else {
+ dash_velocity = corner_dash_velocity_tl;
+ // Added because radians is 0 to pi/2 when going
+ // clockwise around the top-left corner, since both
+ // axis were flipped
+ t = upto_tl + corner_t * dash_velocity;
+ }
+ }
+ } else {
+ // Straight borders
+ bool is_horizontal = corner_center_to_point.x < corner_center_to_point.y;
+ if (is_horizontal) {
+ if (center_to_point.y < 0.0) {
+ dash_velocity = dv_t;
+ t = (the_point.x - r_tl) * dash_velocity;
+ } else {
+ dash_velocity = dv_b;
+ t = upto_bl - (the_point.x - r_bl) * dash_velocity;
+ }
+ } else {
+ if (center_to_point.x < 0.0) {
+ dash_velocity = dv_l;
+ t = upto_tl - (the_point.y - r_tl) * dash_velocity;
+ } else {
+ dash_velocity = dv_r;
+ t = upto_r + (the_point.y - r_tr) * dash_velocity;
+ }
+ }
+ }
+ }
+ float dash_length = dash_length_per_width / dash_period_per_width;
+ float desired_dash_gap = dash_gap_per_width / dash_period_per_width;
+
+ // Straight borders should start and end with a dash, so max_t is
+ // reduced to cause this.
+ max_t -= unrounded ? dash_length : 0.0;
+ if (max_t >= 1.0) {
+ // Adjust dash gap to evenly divide max_t
+ float dash_count = floor(max_t);
+ float dash_period = max_t / dash_count;
+ border_color.a *= dash_alpha(t, dash_period, dash_length, dash_velocity, antialias_threshold);
+ } else if (unrounded) {
+ // When there isn't enough space for the full gap between the
+ // two start / end dashes of a straight border, reduce gap to
+ // make them fit.
+ float dash_gap = max_t - dash_length;
+ if (dash_gap > 0.0) {
+ float dash_period = dash_length + dash_gap;
+ border_color.a *= dash_alpha(t, dash_period, dash_length, dash_velocity, antialias_threshold);
+ }
+ }
+ }
+
// Blend the border on top of the background and then linearly interpolate
// between the two as we slide inside the background.
- float4 blended_border = over(color, input.border_color);
- color = lerp(blended_border, color, saturate(0.5 - inset_distance));
+ float4 blended_border = over(background_color, border_color);
+ color = lerp(background_color, blended_border,
+ saturate(antialias_threshold - inner_sdf));
}
- return color * float4(1., 1., 1., saturate(0.5 - distance));
+ return color * float4(1.0, 1.0, 1.0, saturate(antialias_threshold - outer_sdf));
}
-struct PathVertex {
- float2 xy_position;
+/*
+**
+** Shadows
+**
+*/
+
+struct Shadow {
+ uint order;
+ float blur_radius;
+ Bounds bounds;
+ Corners corner_radii;
Bounds content_mask;
+ Hsla color;
};
+struct ShadowVertexOutput {
+ nointerpolation uint shadow_id: TEXCOORD0;
+ float4 position: SV_Position;
+ nointerpolation float4 color: COLOR;
+ float4 clip_distance: SV_ClipDistance;
+};
+
+struct ShadowFragmentInput {
+ nointerpolation uint shadow_id: TEXCOORD0;
+ float4 position: SV_Position;
+ nointerpolation float4 color: COLOR;
+};
+
+StructuredBuffer<Shadow> shadows: register(t1);
+
+ShadowVertexOutput shadow_vertex(uint vertex_id: SV_VertexID, uint shadow_id: SV_InstanceID) {
+ float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
+ Shadow shadow = shadows[shadow_id];
+
+ float margin = 3.0 * shadow.blur_radius;
+ Bounds bounds = shadow.bounds;
+ bounds.origin -= margin;
+ bounds.size += 2.0 * margin;
+
+ float4 device_position = to_device_position(unit_vertex, bounds);
+ float4 clip_distance = distance_from_clip_rect(unit_vertex, bounds, shadow.content_mask);
+ float4 color = hsla_to_rgba(shadow.color);
+
+ ShadowVertexOutput output;
+ output.position = device_position;
+ output.color = color;
+ output.shadow_id = shadow_id;
+ output.clip_distance = clip_distance;
+
+ return output;
+}
+
+float4 shadow_fragment(ShadowFragmentInput input): SV_TARGET {
+ Shadow shadow = shadows[input.shadow_id];
+
+ float2 half_size = shadow.bounds.size / 2.;
+ float2 center = shadow.bounds.origin + half_size;
+ float2 point0 = input.position.xy - center;
+ float corner_radius = pick_corner_radius(point0, shadow.corner_radii);
+
+ // The signal is only non-zero in a limited range, so don't waste samples
+ float low = point0.y - half_size.y;
+ float high = point0.y + half_size.y;
+ float start = clamp(-3. * shadow.blur_radius, low, high);
+ float end = clamp(3. * shadow.blur_radius, low, high);
+
+ // Accumulate samples (we can get away with surprisingly few samples)
+ float step = (end - start) / 4.;
+ float y = start + step * 0.5;
+ float alpha = 0.;
+ for (int i = 0; i < 4; i++) {
+ alpha += blur_along_x(point0.x, point0.y - y, shadow.blur_radius,
+ corner_radius, half_size) *
+ gaussian(y, shadow.blur_radius) * step;
+ y += step;
+ }
+
+ return input.color * float4(1., 1., 1., alpha);
+}
+
/*
**
** Paths
**
*/
+struct PathVertex {
+ float2 xy_position: POSITION;
+ Bounds content_mask: TEXCOORD;
+ uint idx: GLOBALIDX;
+};
+
struct PathSprite {
Bounds bounds;
Background color;
@@ -615,31 +889,36 @@ struct PathSprite {
struct PathVertexOutput {
float4 position: SV_Position;
+ nointerpolation uint sprite_id: TEXCOORD0;
+ nointerpolation float4 solid_color: COLOR0;
+ nointerpolation float4 color0: COLOR1;
+ nointerpolation float4 color1: COLOR2;
float4 clip_distance: SV_ClipDistance;
+};
+
+struct PathFragmentInput {
+ float4 position: SV_Position;
nointerpolation uint sprite_id: TEXCOORD0;
nointerpolation float4 solid_color: COLOR0;
nointerpolation float4 color0: COLOR1;
nointerpolation float4 color1: COLOR2;
};
-StructuredBuffer<PathVertex> path_vertices: register(t1);
-StructuredBuffer<PathSprite> path_sprites: register(t2);
+StructuredBuffer<PathSprite> path_sprites: register(t1);
-PathVertexOutput paths_vertex(uint vertex_id: SV_VertexID, uint instance_id: SV_InstanceID) {
- PathVertex v = path_vertices[vertex_id];
- PathSprite sprite = path_sprites[instance_id];
+PathVertexOutput paths_vertex(PathVertex input) {
+ PathSprite sprite = path_sprites[input.idx];
PathVertexOutput output;
- output.position = to_device_position_impl(v.xy_position);
- output.clip_distance = distance_from_clip_rect_impl(v.xy_position, v.content_mask);
- output.sprite_id = instance_id;
+ output.position = to_device_position_impl(input.xy_position);
+ output.clip_distance = distance_from_clip_rect_impl(input.xy_position, input.content_mask);
+ output.sprite_id = input.idx;
GradientColor gradient = prepare_gradient_color(
sprite.color.tag,
sprite.color.color_space,
sprite.color.solid,
- sprite.color.colors[0].color,
- sprite.color.colors[1].color
+ sprite.color.colors
);
output.solid_color = gradient.solid;
@@ -648,12 +927,7 @@ PathVertexOutput paths_vertex(uint vertex_id: SV_VertexID, uint instance_id: SV_
return output;
}
-float4 paths_fragment(PathVertexOutput input): SV_Target {
- float4 zero = 0.0;
- if (any(input.clip_distance < zero)) {
- return zero;
- }
-
+float4 paths_fragment(PathFragmentInput input): SV_Target {
PathSprite sprite = path_sprites[input.sprite_id];
Background background = sprite.color;
float4 color = gradient_color(background, input.position.xy, sprite.bounds,
@@ -678,16 +952,16 @@ struct Underline {
};
struct UnderlineVertexOutput {
+ nointerpolation uint underline_id: TEXCOORD0;
float4 position: SV_Position;
- float4 color: COLOR;
- uint underline_id: FLAT;
+ nointerpolation float4 color: COLOR;
float4 clip_distance: SV_ClipDistance;
};
struct UnderlineFragmentInput {
+ nointerpolation uint underline_id: TEXCOORD0;
float4 position: SV_Position;
- float4 color: COLOR;
- uint underline_id: FLAT;
+ nointerpolation float4 color: COLOR;
};
StructuredBuffer<Underline> underlines: register(t1);
@@ -712,10 +986,8 @@ float4 underline_fragment(UnderlineFragmentInput input): SV_Target {
Underline underline = underlines[input.underline_id];
if (underline.wavy) {
float half_thickness = underline.thickness * 0.5;
- float2 origin =
- float2(underline.bounds.origin.x, underline.bounds.origin.y);
- float2 st = ((input.position.xy - origin) / underline.bounds.size.y) -
- float2(0., 0.5);
+ float2 origin = underline.bounds.origin;
+ float2 st = ((input.position.xy - origin) / underline.bounds.size.y) - float2(0., 0.5);
float frequency = (M_PI_F * (3. * underline.thickness)) / 8.;
float amplitude = 1. / (2. * underline.thickness);
float sine = sin(st.x * frequency) * amplitude;
@@ -751,14 +1023,14 @@ struct MonochromeSprite {
struct MonochromeSpriteVertexOutput {
float4 position: SV_Position;
float2 tile_position: POSITION;
- float4 color: COLOR;
+ nointerpolation float4 color: COLOR;
float4 clip_distance: SV_ClipDistance;
};
struct MonochromeSpriteFragmentInput {
float4 position: SV_Position;
float2 tile_position: POSITION;
- float4 color: COLOR;
+ nointerpolation float4 color: COLOR;
};
StructuredBuffer<MonochromeSprite> mono_sprites: register(t1);
@@ -795,7 +1067,9 @@ float4 monochrome_sprite_fragment(MonochromeSpriteFragmentInput input): SV_Targe
struct PolychromeSprite {
uint order;
+ uint pad;
uint grayscale;
+ float opacity;
Bounds bounds;
Bounds content_mask;
Corners corner_radii;
@@ -803,16 +1077,16 @@ struct PolychromeSprite {
};
struct PolychromeSpriteVertexOutput {
+ nointerpolation uint sprite_id: TEXCOORD0;
float4 position: SV_Position;
float2 tile_position: POSITION;
- uint sprite_id: FLAT;
float4 clip_distance: SV_ClipDistance;
};
struct PolychromeSpriteFragmentInput {
+ nointerpolation uint sprite_id: TEXCOORD0;
float4 position: SV_Position;
float2 tile_position: POSITION;
- uint sprite_id: FLAT;
};
StructuredBuffer<PolychromeSprite> poly_sprites: register(t1);
@@ -843,6 +1117,6 @@ float4 polychrome_sprite_fragment(PolychromeSpriteFragmentInput input): SV_Targe
float3 grayscale = dot(color.rgb, GRAYSCALE_FACTORS);
color = float4(grayscale, sample.a);
}
- color.a *= saturate(0.5 - distance);
+ color.a *= sprite.opacity * saturate(0.5 - distance);
return color;
}