Start work on doing path MSAA using intermediate texture

This commit is contained in:
Max Brunsfeld 2025-07-28 17:45:35 -07:00
parent 92b0a7e760
commit e9697e4639
2 changed files with 317 additions and 158 deletions

View file

@ -21,8 +21,8 @@ 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;
// This configuration is used for MSAA rendering on paths only, and it's guaranteed to be supported by DirectX 11.
const PATH_MULTISAMPLE_COUNT: u32 = 4;
pub(crate) struct DirectXRenderer {
hwnd: HWND,
@ -51,8 +51,13 @@ struct DirectXResources {
swap_chain: IDXGISwapChain1,
render_target: ManuallyDrop<ID3D11Texture2D>,
render_target_view: [Option<ID3D11RenderTargetView>; 1],
msaa_target: ID3D11Texture2D,
msaa_view: [Option<ID3D11RenderTargetView>; 1],
// Path intermediate textures (with MSAA)
path_intermediate_texture: ID3D11Texture2D,
path_intermediate_view: [Option<ID3D11RenderTargetView>; 1],
path_intermediate_msaa_texture: ID3D11Texture2D,
path_intermediate_msaa_view: [Option<ID3D11RenderTargetView>; 1],
path_intermediate_srv: Option<ID3D11ShaderResourceView>,
// Cached window size and viewport
width: u32,
@ -63,7 +68,8 @@ struct DirectXResources {
struct DirectXRenderPipelines {
shadow_pipeline: PipelineState<Shadow>,
quad_pipeline: PipelineState<Quad>,
paths_pipeline: PathsPipelineState,
paths_rasterization_pipeline: PathsPipelineState,
paths_sprite_pipeline: PipelineState<PathSprite>,
underline_pipeline: PipelineState<Underline>,
mono_sprites: PipelineState<MonochromeSprite>,
poly_sprites: PipelineState<PolychromeSprite>,
@ -76,13 +82,6 @@ struct DirectXGlobalElements {
}
#[repr(C)]
struct DrawInstancedIndirectArgs {
vertex_count_per_instance: u32,
instance_count: u32,
start_vertex_location: u32,
start_instance_location: u32,
}
#[cfg(not(feature = "enable-renderdoc"))]
struct DirectComposition {
comp_device: IDCompositionDevice,
@ -161,12 +160,13 @@ impl DirectXRenderer {
}],
)?;
unsafe {
self.devices.device_context.ClearRenderTargetView(
self.resources.render_target_view[0].as_ref().unwrap(),
&[0.0; 4],
);
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);
.OMSetRenderTargets(Some(&self.resources.render_target_view), None);
self.devices
.device_context
.RSSetViewports(Some(&self.resources.viewport));
@ -181,16 +181,6 @@ impl DirectXRenderer {
fn present(&mut 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);
let result = self.resources.swap_chain.Present(1, DXGI_PRESENT(0));
// Presenting the swap chain can fail if the DirectX device was removed or reset.
if result == DXGI_ERROR_DEVICE_REMOVED || result == DXGI_ERROR_DEVICE_RESET {
@ -263,7 +253,10 @@ impl DirectXRenderer {
match batch {
PrimitiveBatch::Shadows(shadows) => self.draw_shadows(shadows),
PrimitiveBatch::Quads(quads) => self.draw_quads(quads),
PrimitiveBatch::Paths(paths) => self.draw_paths(paths),
PrimitiveBatch::Paths(paths) => {
self.draw_paths_to_intermediate(paths)?;
self.draw_paths_from_intermediate(paths)
}
PrimitiveBatch::Underlines(underlines) => self.draw_underlines(underlines),
PrimitiveBatch::MonochromeSprites {
texture_id,
@ -369,47 +362,118 @@ impl DirectXRenderer {
)
}
fn draw_paths(&mut self, paths: &[Path<ScaledPixels>]) -> Result<()> {
fn draw_paths_to_intermediate(&mut self, paths: &[Path<ScaledPixels>]) -> Result<()> {
if paths.is_empty() {
return Ok(());
}
let mut vertices = Vec::new();
let mut sprites = Vec::with_capacity(paths.len());
let mut draw_indirect_commands = Vec::with_capacity(paths.len());
let mut start_vertex_location = 0;
for (i, path) in paths.iter().enumerate() {
draw_indirect_commands.push(DrawInstancedIndirectArgs {
vertex_count_per_instance: path.vertices.len() as u32,
instance_count: 1,
start_vertex_location,
start_instance_location: i as u32,
});
start_vertex_location += path.vertices.len() as u32;
// Clear intermediate MSAA texture
unsafe {
self.devices.device_context.ClearRenderTargetView(
self.resources.path_intermediate_msaa_view[0]
.as_ref()
.unwrap(),
&[0.0; 4],
);
// Set intermediate MSAA texture as render target
self.devices
.device_context
.OMSetRenderTargets(Some(&self.resources.path_intermediate_msaa_view), None);
}
// Collect all vertices and sprites for a single draw call
let mut vertices = Vec::new();
let mut sprites = Vec::new();
for (path_index, path) in paths.iter().enumerate() {
vertices.extend(path.vertices.iter().map(|v| DirectXPathVertex {
xy_position: v.xy_position,
content_mask: path.content_mask.bounds,
sprite_index: i as u32,
st_position: v.st_position,
path_index: path_index as u32,
}));
sprites.push(PathSprite {
bounds: path.bounds,
sprites.push(PathRasterizationSprite {
bounds: path.bounds.intersect(&path.content_mask.bounds),
color: path.color,
});
}
self.pipelines.paths_pipeline.update_buffer(
if !vertices.is_empty() {
self.pipelines.paths_rasterization_pipeline.update_buffer(
&self.devices.device,
&self.devices.device_context,
&sprites,
&vertices,
)?;
self.pipelines.paths_rasterization_pipeline.draw(
&self.devices.device_context,
vertices.len() as u32,
&self.resources.viewport,
&self.globals.global_params_buffer,
)?;
}
// Resolve MSAA to non-MSAA intermediate texture
unsafe {
self.devices.device_context.ResolveSubresource(
&self.resources.path_intermediate_texture,
0,
&self.resources.path_intermediate_msaa_texture,
0,
RENDER_TARGET_FORMAT,
);
// Flush to ensure the resolve operation is complete before using the texture
self.devices.device_context.Flush();
// Restore main render target
self.devices
.device_context
.OMSetRenderTargets(Some(&self.resources.render_target_view), None);
}
Ok(())
}
fn draw_paths_from_intermediate(&mut self, paths: &[Path<ScaledPixels>]) -> Result<()> {
let Some(first_path) = paths.first() else {
return Ok(());
};
// 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.bounds,
})
.collect::<Vec<_>>()
} else {
let mut bounds = first_path.bounds;
for path in paths.iter().skip(1) {
bounds = bounds.union(&path.bounds);
}
vec![PathSprite { bounds }]
};
self.pipelines.paths_sprite_pipeline.update_buffer(
&self.devices.device,
&self.devices.device_context,
&sprites,
&vertices,
&draw_indirect_commands,
)?;
self.pipelines.paths_pipeline.draw(
// Draw the sprites with the path texture
self.pipelines.paths_sprite_pipeline.draw_with_texture(
&self.devices.device_context,
paths.len(),
&[self.resources.path_intermediate_srv.clone()],
&self.resources.viewport,
&self.globals.global_params_buffer,
&self.globals.sampler,
sprites.len() as u32,
)
}
@ -528,19 +592,30 @@ impl DirectXResources {
let swap_chain =
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)?;
let (
render_target,
render_target_view,
path_intermediate_texture,
path_intermediate_view,
path_intermediate_msaa_texture,
path_intermediate_msaa_view,
path_intermediate_srv,
viewport,
) = create_resources(devices, &swap_chain, width, height)?;
set_rasterizer_state(&devices.device, &devices.device_context)?;
Ok(ManuallyDrop::new(Self {
swap_chain,
render_target,
render_target_view,
msaa_target,
msaa_view,
path_intermediate_texture,
path_intermediate_view,
path_intermediate_msaa_texture,
path_intermediate_msaa_view,
path_intermediate_srv,
viewport,
width,
height,
viewport,
}))
}
@ -551,12 +626,23 @@ impl DirectXResources {
width: u32,
height: u32,
) -> Result<()> {
let (render_target, render_target_view, msaa_target, msaa_view, viewport) =
create_resources(devices, &self.swap_chain, width, height)?;
let (
render_target,
render_target_view,
path_intermediate_texture,
path_intermediate_view,
path_intermediate_msaa_texture,
path_intermediate_msaa_view,
path_intermediate_srv,
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.path_intermediate_texture = path_intermediate_texture;
self.path_intermediate_view = path_intermediate_view;
self.path_intermediate_msaa_texture = path_intermediate_msaa_texture;
self.path_intermediate_msaa_view = path_intermediate_msaa_view;
self.path_intermediate_srv = path_intermediate_srv;
self.viewport = viewport;
self.width = width;
self.height = height;
@ -569,7 +655,9 @@ impl DirectXRenderPipelines {
let shadow_pipeline =
PipelineState::new(device, "shadow_pipeline", ShaderModule::Shadow, 4)?;
let quad_pipeline = PipelineState::new(device, "quad_pipeline", ShaderModule::Quad, 64)?;
let paths_pipeline = PathsPipelineState::new(device)?;
let paths_rasterization_pipeline = PathsPipelineState::new(device)?;
let paths_sprite_pipeline =
PipelineState::new(device, "paths_sprite_pipeline", ShaderModule::PathSprite, 1)?;
let underline_pipeline =
PipelineState::new(device, "underline_pipeline", ShaderModule::Underline, 4)?;
let mono_sprites = PipelineState::new(
@ -588,7 +676,8 @@ impl DirectXRenderPipelines {
Ok(Self {
shadow_pipeline,
quad_pipeline,
paths_pipeline,
paths_rasterization_pipeline,
paths_sprite_pipeline,
underline_pipeline,
mono_sprites,
poly_sprites,
@ -685,12 +774,10 @@ struct PathsPipelineState {
fragment: ID3D11PixelShader,
buffer: ID3D11Buffer,
buffer_size: usize,
view: [Option<ID3D11ShaderResourceView>; 1],
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> {
@ -809,15 +896,14 @@ impl PathsPipelineState {
let raw_shader = RawShaderBytes::new(ShaderModule::Paths, ShaderTarget::Fragment)?;
create_fragment_shader(device, raw_shader.as_bytes())?
};
let buffer = create_buffer(device, std::mem::size_of::<PathSprite>(), 32)?;
let buffer = create_buffer(device, std::mem::size_of::<PathRasterizationSprite>(), 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 mut layout = None;
device.CreateInputLayout(
@ -843,18 +929,9 @@ impl PathsPipelineState {
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,
AlignedByteOffset: 16,
InputSlotClass: D3D11_INPUT_PER_VERTEX_DATA,
InstanceDataStepRate: 0,
},
@ -870,12 +947,10 @@ impl PathsPipelineState {
fragment,
buffer,
buffer_size: 32,
view,
vertex_buffer,
vertex_buffer_size: 32,
indirect_draw_buffer,
indirect_buffer_size: 32,
input_layout,
view,
})
}
@ -883,24 +958,28 @@ impl PathsPipelineState {
&mut self,
device: &ID3D11Device,
device_context: &ID3D11DeviceContext,
buffer_data: &[PathSprite],
sprites: &[PathRasterizationSprite],
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();
if self.buffer_size < sprites.len() {
let new_buffer_size = sprites.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 buffer = create_buffer(
device,
std::mem::size_of::<PathRasterizationSprite>(),
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)?;
update_buffer(device_context, &self.buffer, sprites)?;
if self.vertex_buffer_size < vertices_data.len() {
let new_vertex_buffer_size = vertices_data.len().next_power_of_two();
log::info!(
@ -921,26 +1000,14 @@ impl PathsPipelineState {
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,
vertex_count: u32,
viewport: &[D3D11_VIEWPORT],
global_params: &[Option<ID3D11Buffer>],
) -> Result<()> {
@ -955,39 +1022,40 @@ impl PathsPipelineState {
);
unsafe {
const STRIDE: u32 = std::mem::size_of::<DirectXPathVertex>() as u32;
const OFFSET: u32 = 0;
device_context.IASetInputLayout(&self.input_layout);
device_context.IASetVertexBuffers(
0,
1,
Some(&self.vertex_buffer),
Some(&STRIDE),
Some(&0),
Some(&OFFSET),
);
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,
);
}
device_context.Draw(vertex_count, 0);
}
Ok(())
}
}
#[derive(Clone, Copy)]
#[repr(C)]
struct DirectXPathVertex {
xy_position: Point<ScaledPixels>,
content_mask: Bounds<ScaledPixels>,
sprite_index: u32,
st_position: Point<f32>,
path_index: u32,
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Clone, Copy)]
#[repr(C)]
struct PathRasterizationSprite {
bounds: Bounds<ScaledPixels>,
color: Background,
}
#[derive(Clone, Copy)]
#[repr(C)]
struct PathSprite {
bounds: Bounds<ScaledPixels>,
color: Background,
}
impl Drop for DirectXRenderer {
@ -1142,17 +1210,26 @@ fn create_resources(
[Option<ID3D11RenderTargetView>; 1],
ID3D11Texture2D,
[Option<ID3D11RenderTargetView>; 1],
ID3D11Texture2D,
[Option<ID3D11RenderTargetView>; 1],
Option<ID3D11ShaderResourceView>,
[D3D11_VIEWPORT; 1],
)> {
let (render_target, render_target_view) =
create_render_target_and_its_view(&swap_chain, &devices.device)?;
let (msaa_target, msaa_view) = create_msaa_target_and_its_view(&devices.device, width, height)?;
let (path_intermediate_texture, path_intermediate_view, path_intermediate_srv) =
create_path_intermediate_texture_and_view(&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,
msaa_target,
msaa_view,
path_intermediate_texture,
path_intermediate_view,
path_intermediate_msaa_texture,
path_intermediate_msaa_view,
path_intermediate_srv,
viewport,
))
}
@ -1175,12 +1252,16 @@ fn create_render_target_and_its_view(
}
#[inline]
fn create_msaa_target_and_its_view(
fn create_path_intermediate_texture_and_view(
device: &ID3D11Device,
width: u32,
height: u32,
) -> Result<(ID3D11Texture2D, [Option<ID3D11RenderTargetView>; 1])> {
let msaa_target = unsafe {
) -> Result<(
ID3D11Texture2D,
[Option<ID3D11RenderTargetView>; 1],
Option<ID3D11ShaderResourceView>,
)> {
let texture = unsafe {
let mut output = None;
let desc = D3D11_TEXTURE2D_DESC {
Width: width,
@ -1189,7 +1270,47 @@ fn create_msaa_target_and_its_view(
ArraySize: 1,
Format: RENDER_TARGET_FORMAT,
SampleDesc: DXGI_SAMPLE_DESC {
Count: MULTISAMPLE_COUNT,
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 render_target_view = None;
unsafe { device.CreateRenderTargetView(&texture, None, Some(&mut render_target_view))? };
let mut shader_resource_view = None;
unsafe { device.CreateShaderResourceView(&texture, None, Some(&mut shader_resource_view))? };
Ok((
texture,
[Some(render_target_view.unwrap())],
shader_resource_view,
))
}
#[inline]
fn create_path_intermediate_msaa_texture_and_view(
device: &ID3D11Device,
width: u32,
height: u32,
) -> Result<(ID3D11Texture2D, [Option<ID3D11RenderTargetView>; 1])> {
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,
@ -1200,12 +1321,9 @@ fn create_msaa_target_and_its_view(
device.CreateTexture2D(&desc, None, Some(&mut output))?;
output.unwrap()
};
let msaa_view = unsafe {
let mut output = None;
device.CreateRenderTargetView(&msaa_target, None, Some(&mut output))?;
output.unwrap()
};
Ok((msaa_target, [Some(msaa_view)]))
let mut msaa_view = None;
unsafe { device.CreateRenderTargetView(&msaa_texture, None, Some(&mut msaa_view))? };
Ok((msaa_texture, [Some(msaa_view.unwrap())]))
}
#[inline]
@ -1318,21 +1436,6 @@ fn create_buffer_view(
Ok([view])
}
#[inline]
fn create_indirect_draw_buffer(device: &ID3D11Device, buffer_size: usize) -> Result<ID3D11Buffer> {
let desc = D3D11_BUFFER_DESC {
ByteWidth: (std::mem::size_of::<DrawInstancedIndirectArgs>() * 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_DRAWINDIRECT_ARGS.0 as u32,
StructureByteStride: std::mem::size_of::<DrawInstancedIndirectArgs>() as u32,
};
let mut buffer = None;
unsafe { device.CreateBuffer(&desc, None, Some(&mut buffer)) }?;
Ok(buffer.unwrap())
}
#[inline]
fn update_buffer<T>(
device_context: &ID3D11DeviceContext,
@ -1390,6 +1493,7 @@ mod shader_resources {
Shadow,
Underline,
Paths,
PathSprite,
MonochromeSprite,
PolychromeSprite,
}
@ -1524,6 +1628,7 @@ mod shader_resources {
ShaderModule::Shadow => "shadow",
ShaderModule::Underline => "underline",
ShaderModule::Paths => "paths",
ShaderModule::PathSprite => "path_sprite",
ShaderModule::MonochromeSprite => "monochrome_sprite",
ShaderModule::PolychromeSprite => "polychrome_sprite",
}

View file

@ -256,7 +256,7 @@ float pick_corner_radius(float2 center_to_point, Corners corner_radii) {
}
}
float4 to_device_position_transformed(float2 unit_vertex, Bounds bounds,
float4 to_device_position_transformed(float2 unit_vertex, Bounds bounds,
TransformationMatrix transformation) {
float2 position = unit_vertex * bounds.size + bounds.origin;
float2 transformed = mul(position, transformation.rotation_scale) + transformation.translation;
@ -878,41 +878,58 @@ float4 shadow_fragment(ShadowFragmentInput input): SV_TARGET {
struct PathVertex {
float2 xy_position: POSITION;
Bounds content_mask: TEXCOORD;
uint idx: GLOBALIDX;
float2 st_position: TEXCOORD0;
uint path_index: TEXCOORD1;
};
struct PathSprite {
struct PathRasterizationSprite {
Bounds bounds;
Background color;
};
struct PathSprite {
Bounds bounds;
};
StructuredBuffer<PathRasterizationSprite> path_rasterization_sprites: register(t1);
struct PathVertexOutput {
float4 position: SV_Position;
nointerpolation uint sprite_id: TEXCOORD0;
float2 st_position: TEXCOORD0;
nointerpolation uint tag: TEXCOORD1;
nointerpolation uint color_space: TEXCOORD2;
nointerpolation float gradient_angle: TEXCOORD3;
nointerpolation float4 solid_color: COLOR0;
nointerpolation float4 color0: COLOR1;
nointerpolation float4 color1: COLOR2;
float4 clip_distance: SV_ClipDistance;
nointerpolation float2 stop_percentages: COLOR3;
nointerpolation Bounds bounds: BOUNDS;
};
struct PathFragmentInput {
float4 position: SV_Position;
nointerpolation uint sprite_id: TEXCOORD0;
float2 st_position: TEXCOORD0;
nointerpolation uint tag: TEXCOORD1;
nointerpolation uint color_space: TEXCOORD2;
nointerpolation float gradient_angle: TEXCOORD3;
nointerpolation float4 solid_color: COLOR0;
nointerpolation float4 color0: COLOR1;
nointerpolation float4 color1: COLOR2;
nointerpolation float2 stop_percentages: COLOR3;
nointerpolation Bounds bounds: BOUNDS;
};
StructuredBuffer<PathSprite> path_sprites: register(t1);
PathVertexOutput paths_vertex(PathVertex input) {
PathSprite sprite = path_sprites[input.idx];
PathRasterizationSprite sprite = path_rasterization_sprites[input.path_index];
PathVertexOutput output;
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;
output.st_position = input.st_position;
output.bounds = sprite.bounds;
output.tag = sprite.color.tag;
output.color_space = sprite.color.color_space;
output.gradient_angle = sprite.color.gradient_angle_or_pattern_height;
output.stop_percentages = float2(sprite.color.colors[0].percentage, sprite.color.colors[1].percentage);
GradientColor gradient = prepare_gradient_color(
sprite.color.tag,
@ -920,21 +937,58 @@ PathVertexOutput paths_vertex(PathVertex input) {
sprite.color.solid,
sprite.color.colors
);
output.solid_color = gradient.solid;
output.color0 = gradient.color0;
output.color1 = gradient.color1;
return output;
}
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,
Background background;
background.tag = input.tag;
background.color_space = input.color_space;
background.solid = (Hsla)0; // Not used when tag != 0
background.gradient_angle_or_pattern_height = input.gradient_angle;
background.colors[0].color = (Hsla)0; // Not used when colors are pre-computed
background.colors[0].percentage = input.stop_percentages.x;
background.colors[1].color = (Hsla)0; // Not used when colors are pre-computed
background.colors[1].percentage = input.stop_percentages.y;
float4 color = gradient_color(background, input.position.xy, input.bounds,
input.solid_color, input.color0, input.color1);
return color;
}
// --- path sprite --- //
struct PathSpriteVertexOutput {
float4 position: SV_Position;
float2 texture_coords: TEXCOORD0;
};
StructuredBuffer<PathSprite> path_sprites: register(t1);
PathSpriteVertexOutput path_sprite_vertex(uint vertex_id: SV_VertexID, uint sprite_id: SV_InstanceID) {
float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
PathSprite sprite = path_sprites[sprite_id];
// Don't apply content mask because it was already accounted for when rasterizing the path
float4 device_position = to_device_position(unit_vertex, sprite.bounds);
float2 screen_position = sprite.bounds.origin + unit_vertex * sprite.bounds.size;
float2 texture_coords = screen_position / global_viewport_size;
PathSpriteVertexOutput output;
output.position = device_position;
output.texture_coords = texture_coords;
return output;
}
float4 path_sprite_fragment(PathSpriteVertexOutput input): SV_Target {
return t_sprite.Sample(s_sprite, input.texture_coords);
}
/*
**
** Underlines
@ -970,7 +1024,7 @@ UnderlineVertexOutput underline_vertex(uint vertex_id: SV_VertexID, uint underli
float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
Underline underline = underlines[underline_id];
float4 device_position = to_device_position(unit_vertex, underline.bounds);
float4 clip_distance = distance_from_clip_rect(unit_vertex, underline.bounds,
float4 clip_distance = distance_from_clip_rect(unit_vertex, underline.bounds,
underline.content_mask);
float4 color = hsla_to_rgba(underline.color);