Tweak renderer resource management (#62069)

Factoring some changes out of
https://github.com/zed-industries/zed/pull/61461:
- On Metal and wgpu, bulk-upload instance buffers at the start of the
frame rather than uploading chunks as we draw them. The depth buffer
patch needs this architecture since all opaque quads in the scene are
painted before anything else. This also lets us get rid of the awkward
retry loop when the GPU buffer is too small for the scene.
- On Windows, use a constant buffer for the instance offset rather than
creating SRVs with different `FirstElement`s. `FirstElement` is buggy on
some vendors' drivers, and reusing the SRV object should reduce D3D
runtime overhead.
- Misc. refactoring: remove redundant parameters, deduplicate code,
create fewer bind groups on wgpu, etc.

Fixes https://github.com/zed-industries/zed/issues/49588
Fixes https://github.com/zed-industries/zed/issues/59192

Release Notes:

- Fixed an issue where UI elements would flicker on some Windows
devices.
This commit is contained in:
John Tur authored and GitHub committed 2026-08-03 14:37:21 +00:00
1 parent d0f96ff4e0
commit ec5ddcf55e
6 files changed
+951 -1131

No files matched your search

+42 -22
View File
@@ -3967,6 +3967,45 @@ impl Window {
}
}
fn largest_border_interior(quad: &Quad) -> Bounds<ScaledPixels> {
let radii = &quad.corner_radii;
let widths = &quad.border_widths;
let edge_radii = Edges {
top: radii.top_left.max(radii.top_right),
right: radii.top_right.max(radii.bottom_right),
bottom: radii.bottom_left.max(radii.bottom_right),
left: radii.top_left.max(radii.bottom_left),
};
let antialias_inset = point(ScaledPixels(1.0), ScaledPixels(1.0));
let inset_bounds = |top_left_inset, bottom_right_inset| {
Bounds::from_corners(
quad.bounds.origin + top_left_inset + antialias_inset,
quad.bounds.bottom_right() - bottom_right_inset - antialias_inset,
)
};
// Rounded corners need only be excluded on one axis. Either candidate
// is empty of border pixels, so use the larger interior.
let horizontal_band = inset_bounds(
point(widths.left, widths.top.max(edge_radii.top)),
point(widths.right, widths.bottom.max(edge_radii.bottom)),
);
let vertical_band = inset_bounds(
point(widths.left.max(edge_radii.left), widths.top),
point(widths.right.max(edge_radii.right), widths.bottom),
);
let area = |bounds: &Bounds<ScaledPixels>| {
bounds.size.width.0.max(0.) * bounds.size.height.0.max(0.)
};
if area(&horizontal_band) >= area(&vertical_band) {
horizontal_band
} else {
vertical_band
}
}
/// Paint one or more quads into the scene for the next frame at the current stacking context.
/// Quads are colored rectangular regions with an optional background, border, and corner radius.
/// see [`fill`], [`outline`], and [`quad`] to construct this type.
@@ -3998,29 +4037,10 @@ impl Window {
return;
}
// We're drawing a quad with a border but no fill color. Painting this quad would run the quad shader for every
// transparent interior pixel, which is especially costly when the quad is large.
// Instead, split it into four non-overlapping strips that cover the regions where borders are painted:
// the side strips own the straight left and right edges, while the top and bottom strips own the horizontal
// edges and the rounded corners.
let radii = &quad.corner_radii;
let widths = &quad.border_widths;
let antialias_slack = point(ScaledPixels(1.0), ScaledPixels(1.0));
let top_left_inset = point(
widths.left,
widths.top.max(radii.top_left).max(radii.top_right),
) + antialias_slack;
let bottom_right_inset = point(
widths.right,
widths.bottom.max(radii.bottom_left).max(radii.bottom_right),
) + antialias_slack;
// Splitting a border-only quad around its empty interior avoids shading
// every transparent pixel inside large outlines.
let outer_bounds = quad.bounds;
let inner_bounds = Bounds::from_corners(
outer_bounds.origin + top_left_inset,
outer_bounds.bottom_right() - bottom_right_inset,
);
let inner_bounds = Self::largest_border_interior(&quad);
if inner_bounds.is_empty() {
self.next_frame.scene.insert_primitive(quad);
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -93,8 +93,8 @@ struct GammaParams {
@group(0) @binding(0) var<uniform> globals: GlobalParams;
@group(0) @binding(1) var<uniform> gamma_params: GammaParams;
@group(1) @binding(1) var t_sprite: texture_2d<f32>;
@group(1) @binding(2) var s_sprite: sampler;
@group(2) @binding(0) var t_sprite: texture_2d<f32>;
@group(2) @binding(1) var s_sprite: sampler;
const M_PI_F: f32 = 3.1415926;
const GRAYSCALE_FACTORS: vec3<f32> = vec3<f32>(0.2126, 0.7152, 0.0722);
+361 -386
View File
@@ -1,18 +1,21 @@
use crate::{CompositorGpuHint, WgpuAtlas, WgpuContext};
use anyhow::{Context as _, Result};
use bytemuck::{Pod, Zeroable};
use gpui::{
AtlasTextureId, Background, Bounds, DevicePixels, GpuSpecs, MonochromeSprite, Path, Point,
PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, SubpixelSprite,
Underline, get_gamma_correction_ratios,
AtlasTextureId, Background, Bounds, DevicePixels, GpuSpecs, Path, Point, PrimitiveBatch,
ScaledPixels, Scene, Size, get_gamma_correction_ratios,
};
use log::warn;
#[cfg(not(target_family = "wasm"))]
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
use std::cell::RefCell;
use std::num::NonZeroU64;
use std::ops::Range;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
const MAX_INSTANCE_BUFFER_SIZE: u64 = 256 * 1024 * 1024;
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct GlobalParams {
@@ -94,10 +97,19 @@ struct WgpuPipelines {
surfaces: wgpu::RenderPipeline,
}
struct InstanceBindings {
quads: wgpu::BindGroup,
shadows: wgpu::BindGroup,
underlines: wgpu::BindGroup,
monochrome_sprites: wgpu::BindGroup,
subpixel_sprites: wgpu::BindGroup,
polychrome_sprites: wgpu::BindGroup,
}
struct WgpuBindGroupLayouts {
globals: wgpu::BindGroupLayout,
instances: wgpu::BindGroupLayout,
instances_with_texture: wgpu::BindGroupLayout,
texture: wgpu::BindGroupLayout,
surfaces: wgpu::BindGroupLayout,
}
@@ -381,9 +393,15 @@ impl WgpuRenderer {
mapped_at_creation: false,
});
let max_buffer_size = device.limits().max_buffer_size;
// Every frame allocation is exposed as one storage-buffer binding, so
// its backing buffer must satisfy both the allocation and binding limits.
let max_buffer_size = device
.limits()
.max_buffer_size
.min(device.limits().max_storage_buffer_binding_size)
.min(MAX_INSTANCE_BUFFER_SIZE);
let storage_buffer_alignment = device.limits().min_storage_buffer_offset_alignment as u64;
let initial_instance_buffer_capacity = 2 * 1024 * 1024;
let initial_instance_buffer_capacity = (2 * 1024 * 1024).min(max_buffer_size);
let instance_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("instance_buffer"),
size: initial_instance_buffer_capacity,
@@ -539,29 +557,27 @@ impl WgpuRenderer {
entries: &[storage_buffer_entry(0)],
});
let instances_with_texture =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("instances_with_texture_layout"),
entries: &[
storage_buffer_entry(0),
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
let texture = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("texture_layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
let surfaces = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("surfaces_layout"),
@@ -610,7 +626,7 @@ impl WgpuRenderer {
WgpuBindGroupLayouts {
globals,
instances,
instances_with_texture,
texture,
surfaces,
}
}
@@ -682,13 +698,16 @@ impl WgpuRenderer {
fs_entry: &str,
globals_layout: &wgpu::BindGroupLayout,
data_layout: &wgpu::BindGroupLayout,
texture_layout: Option<&wgpu::BindGroupLayout>,
topology: wgpu::PrimitiveTopology,
color_targets: &[Option<wgpu::ColorTargetState>],
sample_count: u32,
module: &wgpu::ShaderModule| {
let mut bind_group_layouts = vec![Some(globals_layout), Some(data_layout)];
bind_group_layouts.extend(texture_layout.map(Some));
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some(&format!("{name}_layout")),
bind_group_layouts: &[Some(globals_layout), Some(data_layout)],
bind_group_layouts: &bind_group_layouts,
immediate_size: 0,
});
@@ -733,6 +752,7 @@ impl WgpuRenderer {
"fs_quad",
&layouts.globals,
&layouts.instances,
None,
wgpu::PrimitiveTopology::TriangleStrip,
&[Some(color_target.clone())],
1,
@@ -745,6 +765,7 @@ impl WgpuRenderer {
"fs_shadow",
&layouts.globals,
&layouts.instances,
None,
wgpu::PrimitiveTopology::TriangleStrip,
&[Some(color_target.clone())],
1,
@@ -757,6 +778,7 @@ impl WgpuRenderer {
"fs_path_rasterization",
&layouts.globals,
&layouts.instances,
None,
wgpu::PrimitiveTopology::TriangleList,
&[Some(wgpu::ColorTargetState {
format: surface_format,
@@ -785,7 +807,8 @@ impl WgpuRenderer {
"vs_path",
"fs_path",
&layouts.globals,
&layouts.instances_with_texture,
&layouts.instances,
Some(&layouts.texture),
wgpu::PrimitiveTopology::TriangleStrip,
&[Some(wgpu::ColorTargetState {
format: surface_format,
@@ -802,6 +825,7 @@ impl WgpuRenderer {
"fs_underline",
&layouts.globals,
&layouts.instances,
None,
wgpu::PrimitiveTopology::TriangleStrip,
&[Some(color_target.clone())],
1,
@@ -813,7 +837,8 @@ impl WgpuRenderer {
"vs_mono_sprite",
"fs_mono_sprite",
&layouts.globals,
&layouts.instances_with_texture,
&layouts.instances,
Some(&layouts.texture),
wgpu::PrimitiveTopology::TriangleStrip,
&[Some(color_target.clone())],
1,
@@ -839,7 +864,8 @@ impl WgpuRenderer {
"vs_subpixel_sprite",
"fs_subpixel_sprite",
&layouts.globals,
&layouts.instances_with_texture,
&layouts.instances,
Some(&layouts.texture),
wgpu::PrimitiveTopology::TriangleStrip,
&[Some(wgpu::ColorTargetState {
format: surface_format,
@@ -858,7 +884,8 @@ impl WgpuRenderer {
"vs_poly_sprite",
"fs_poly_sprite",
&layouts.globals,
&layouts.instances_with_texture,
&layouts.instances,
Some(&layouts.texture),
wgpu::PrimitiveTopology::TriangleStrip,
&[Some(color_target.clone())],
1,
@@ -871,6 +898,7 @@ impl WgpuRenderer {
"fs_surface",
&layouts.globals,
&layouts.surfaces,
None,
wgpu::PrimitiveTopology::TriangleStrip,
&[Some(color_target)],
1,
@@ -1202,327 +1230,249 @@ impl WgpuRenderer {
);
}
loop {
let mut instance_offset: u64 = 0;
let mut overflow = false;
if let Err(error) = self.record_frame(scene, &frame_view) {
log::error!("{error:#}");
self.resources().queue.submit(std::iter::empty());
return false;
}
let mut encoder =
self.resources()
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("main_encoder"),
});
frame.present();
true
}
{
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("main_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &frame_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: None,
..Default::default()
fn record_frame(&mut self, scene: &Scene, frame_view: &wgpu::TextureView) -> Result<()> {
let mut instance_offset = 0;
let instance_bindings = self
.write_instances(scene, &mut instance_offset)
.with_context(|| {
format!(
"scene too large: {} paths, {} shadows, {} quads, {} underlines, {} monochrome sprites, {} subpixel sprites, {} polychrome sprites",
scene.paths.len(),
scene.shadows.len(),
scene.quads.len(),
scene.underlines.len(),
scene.monochrome_sprites.len(),
scene.subpixel_sprites.len(),
scene.polychrome_sprites.len(),
)
})?;
let mut encoder =
self.resources()
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("main_encoder"),
});
for batch in scene.batches() {
let ok = match batch {
PrimitiveBatch::Quads(range) => {
self.draw_quads(&scene.quads[range], &mut instance_offset, &mut pass)
{
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("main_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: frame_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: None,
..Default::default()
});
for batch in scene.batches() {
match batch {
PrimitiveBatch::Quads(range) => self.draw_instances(
&instance_bindings.quads,
&self.resources().pipelines.quads,
instance_range(range),
&mut pass,
),
PrimitiveBatch::Shadows(range) => self.draw_instances(
&instance_bindings.shadows,
&self.resources().pipelines.shadows,
instance_range(range),
&mut pass,
),
PrimitiveBatch::Paths(range) => {
let paths = &scene.paths[range];
if paths.is_empty() {
continue;
}
PrimitiveBatch::Shadows(range) => self.draw_shadows(
&scene.shadows[range],
drop(pass);
let rasterized = self.draw_paths_to_intermediate(
&mut encoder,
paths,
&mut instance_offset,
&mut pass,
),
PrimitiveBatch::Paths(range) => {
let paths = &scene.paths[range];
if paths.is_empty() {
continue;
}
)?;
drop(pass);
pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("main_pass_continued"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: frame_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: None,
..Default::default()
});
let did_draw = self.draw_paths_to_intermediate(
&mut encoder,
if rasterized {
self.draw_paths_from_intermediate(
paths,
&mut instance_offset,
);
pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("main_pass_continued"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &frame_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: None,
..Default::default()
});
if did_draw {
self.draw_paths_from_intermediate(
paths,
&mut instance_offset,
&mut pass,
)
} else {
false
}
&mut pass,
)?;
}
PrimitiveBatch::Underlines(range) => self.draw_underlines(
&scene.underlines[range],
&mut instance_offset,
&mut pass,
),
PrimitiveBatch::MonochromeSprites { texture_id, range } => self
.draw_monochrome_sprites(
&scene.monochrome_sprites[range],
texture_id,
&mut instance_offset,
&mut pass,
),
PrimitiveBatch::SubpixelSprites { texture_id, range } => self
.draw_subpixel_sprites(
&scene.subpixel_sprites[range],
texture_id,
&mut instance_offset,
&mut pass,
),
PrimitiveBatch::PolychromeSprites { texture_id, range } => self
.draw_polychrome_sprites(
&scene.polychrome_sprites[range],
texture_id,
&mut instance_offset,
&mut pass,
),
PrimitiveBatch::Surfaces(_surfaces) => {
// Surfaces are macOS-only for video playback
// Not implemented for Linux/wgpu
true
}
};
if !ok {
overflow = true;
break;
}
PrimitiveBatch::Underlines(range) => self.draw_instances(
&instance_bindings.underlines,
&self.resources().pipelines.underlines,
instance_range(range),
&mut pass,
),
PrimitiveBatch::MonochromeSprites { texture_id, range } => self.draw_sprites(
&instance_bindings.monochrome_sprites,
texture_id,
&self.resources().pipelines.mono_sprites,
instance_range(range),
&mut pass,
),
PrimitiveBatch::SubpixelSprites { texture_id, range } => {
let resources = self.resources();
self.draw_sprites(
&instance_bindings.subpixel_sprites,
texture_id,
resources
.pipelines
.subpixel_sprites
.as_ref()
.unwrap_or(&resources.pipelines.mono_sprites),
instance_range(range),
&mut pass,
);
}
PrimitiveBatch::PolychromeSprites { texture_id, range } => self.draw_sprites(
&instance_bindings.polychrome_sprites,
texture_id,
&self.resources().pipelines.poly_sprites,
instance_range(range),
&mut pass,
),
// Surfaces are macOS-only for video playback and are not
// implemented by the WGPU renderer.
PrimitiveBatch::Surfaces(_surfaces) => {}
}
}
if overflow {
drop(encoder);
if self.instance_buffer_capacity >= self.max_buffer_size {
log::error!(
"instance buffer size grew too large: {}",
self.instance_buffer_capacity
);
frame.present();
return true;
}
self.grow_instance_buffer();
continue;
}
self.resources()
.queue
.submit(std::iter::once(encoder.finish()));
frame.present();
return true;
}
self.resources()
.queue
.submit(std::iter::once(encoder.finish()));
Ok(())
}
fn draw_quads(
&self,
quads: &[Quad],
fn write_instances(
&mut self,
scene: &Scene,
instance_offset: &mut u64,
pass: &mut wgpu::RenderPass<'_>,
) -> bool {
let data = unsafe { Self::instance_bytes(quads) };
self.draw_instances(
data,
quads.len() as u32,
&self.resources().pipelines.quads,
instance_offset,
pass,
)
) -> Result<InstanceBindings> {
Ok(InstanceBindings {
quads: self.write_instance_binding("quads_bind_group", instance_offset, unsafe {
Self::instance_bytes(&scene.quads)
})?,
shadows: self.write_instance_binding(
"shadows_bind_group",
instance_offset,
unsafe { Self::instance_bytes(&scene.shadows) },
)?,
underlines: self.write_instance_binding(
"underlines_bind_group",
instance_offset,
unsafe { Self::instance_bytes(&scene.underlines) },
)?,
monochrome_sprites: self.write_instance_binding(
"monochrome_sprites_bind_group",
instance_offset,
unsafe { Self::instance_bytes(&scene.monochrome_sprites) },
)?,
subpixel_sprites: self.write_instance_binding(
"subpixel_sprites_bind_group",
instance_offset,
unsafe { Self::instance_bytes(&scene.subpixel_sprites) },
)?,
polychrome_sprites: self.write_instance_binding(
"polychrome_sprites_bind_group",
instance_offset,
unsafe { Self::instance_bytes(&scene.polychrome_sprites) },
)?,
})
}
fn draw_shadows(
fn create_texture_bind_group(
&self,
shadows: &[Shadow],
instance_offset: &mut u64,
pass: &mut wgpu::RenderPass<'_>,
) -> bool {
let data = unsafe { Self::instance_bytes(shadows) };
self.draw_instances(
data,
shadows.len() as u32,
&self.resources().pipelines.shadows,
instance_offset,
pass,
)
}
fn draw_underlines(
&self,
underlines: &[Underline],
instance_offset: &mut u64,
pass: &mut wgpu::RenderPass<'_>,
) -> bool {
let data = unsafe { Self::instance_bytes(underlines) };
self.draw_instances(
data,
underlines.len() as u32,
&self.resources().pipelines.underlines,
instance_offset,
pass,
)
}
fn draw_monochrome_sprites(
&self,
sprites: &[MonochromeSprite],
texture_id: AtlasTextureId,
instance_offset: &mut u64,
pass: &mut wgpu::RenderPass<'_>,
) -> bool {
let tex_info = self.atlas.get_texture_info(texture_id);
let data = unsafe { Self::instance_bytes(sprites) };
self.draw_instances_with_texture(
data,
sprites.len() as u32,
&tex_info.view,
&self.resources().pipelines.mono_sprites,
instance_offset,
pass,
)
}
fn draw_subpixel_sprites(
&self,
sprites: &[SubpixelSprite],
texture_id: AtlasTextureId,
instance_offset: &mut u64,
pass: &mut wgpu::RenderPass<'_>,
) -> bool {
let tex_info = self.atlas.get_texture_info(texture_id);
let data = unsafe { Self::instance_bytes(sprites) };
label: &str,
texture_view: &wgpu::TextureView,
) -> wgpu::BindGroup {
let resources = self.resources();
let pipeline = resources
.pipelines
.subpixel_sprites
.as_ref()
.unwrap_or(&resources.pipelines.mono_sprites);
self.draw_instances_with_texture(
data,
sprites.len() as u32,
&tex_info.view,
pipeline,
instance_offset,
pass,
)
}
fn draw_polychrome_sprites(
&self,
sprites: &[PolychromeSprite],
texture_id: AtlasTextureId,
instance_offset: &mut u64,
pass: &mut wgpu::RenderPass<'_>,
) -> bool {
let tex_info = self.atlas.get_texture_info(texture_id);
let data = unsafe { Self::instance_bytes(sprites) };
self.draw_instances_with_texture(
data,
sprites.len() as u32,
&tex_info.view,
&self.resources().pipelines.poly_sprites,
instance_offset,
pass,
)
resources
.device
.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(label),
layout: &resources.bind_group_layouts.texture,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(texture_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&resources.atlas_sampler),
},
],
})
}
fn draw_instances(
&self,
data: &[u8],
instance_count: u32,
bind_group: &wgpu::BindGroup,
pipeline: &wgpu::RenderPipeline,
instance_offset: &mut u64,
instances: Range<u32>,
pass: &mut wgpu::RenderPass<'_>,
) -> bool {
if instance_count == 0 {
return true;
) {
if instances.is_empty() {
return;
}
let Some((offset, size)) = self.write_to_instance_buffer(instance_offset, data) else {
return false;
};
let resources = self.resources();
let bind_group = resources
.device
.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &resources.bind_group_layouts.instances,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: self.instance_binding(offset, size),
}],
});
pass.set_pipeline(pipeline);
pass.set_bind_group(0, &resources.globals_bind_group, &[]);
pass.set_bind_group(1, &bind_group, &[]);
pass.draw(0..4, 0..instance_count);
true
pass.set_bind_group(0, &self.resources().globals_bind_group, &[]);
pass.set_bind_group(1, bind_group, &[]);
pass.draw(0..4, instances);
}
fn draw_instances_with_texture(
fn draw_sprites(
&self,
data: &[u8],
instance_count: u32,
texture_view: &wgpu::TextureView,
sprite_instances: &wgpu::BindGroup,
texture_id: AtlasTextureId,
pipeline: &wgpu::RenderPipeline,
instance_offset: &mut u64,
instances: Range<u32>,
pass: &mut wgpu::RenderPass<'_>,
) -> bool {
if instance_count == 0 {
return true;
) {
if instances.is_empty() {
return;
}
let Some((offset, size)) = self.write_to_instance_buffer(instance_offset, data) else {
return false;
};
let resources = self.resources();
let bind_group = resources
.device
.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &resources.bind_group_layouts.instances_with_texture,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: self.instance_binding(offset, size),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(texture_view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&resources.atlas_sampler),
},
],
});
let texture_info = self.atlas.get_texture_info(texture_id);
let texture =
self.create_texture_bind_group("atlas_texture_bind_group", &texture_info.view);
pass.set_pipeline(pipeline);
pass.set_bind_group(0, &resources.globals_bind_group, &[]);
pass.set_bind_group(1, &bind_group, &[]);
pass.draw(0..4, 0..instance_count);
true
pass.set_bind_group(0, &self.resources().globals_bind_group, &[]);
pass.set_bind_group(1, sprite_instances, &[]);
pass.set_bind_group(2, &texture, &[]);
pass.draw(0..4, instances);
}
unsafe fn instance_bytes<T>(instances: &[T]) -> &[u8] {
@@ -1535,11 +1485,11 @@ impl WgpuRenderer {
}
fn draw_paths_from_intermediate(
&self,
&mut self,
paths: &[Path<ScaledPixels>],
instance_offset: &mut u64,
pass: &mut wgpu::RenderPass<'_>,
) -> bool {
) -> Result<()> {
let first_path = &paths[0];
let sprites: Vec<PathSprite> = if paths.last().map(|p| &p.order) == Some(&first_path.order)
{
@@ -1557,28 +1507,32 @@ impl WgpuRenderer {
vec![PathSprite { bounds }]
};
let resources = self.resources();
let Some(path_intermediate_view) = resources.path_intermediate_view.as_ref() else {
return true;
let Some(path_intermediate_view) = self.resources().path_intermediate_view.clone() else {
return Ok(());
};
let sprite_data = unsafe { Self::instance_bytes(&sprites) };
self.draw_instances_with_texture(
sprite_data,
sprites.len() as u32,
path_intermediate_view,
&resources.pipelines.paths,
instance_offset,
pass,
)
let instances =
self.write_instance_binding("path_sprites_bind_group", instance_offset, unsafe {
Self::instance_bytes(&sprites)
})?;
let texture = self.create_texture_bind_group(
"path_intermediate_texture_bind_group",
&path_intermediate_view,
);
let resources = self.resources();
pass.set_pipeline(&resources.pipelines.paths);
pass.set_bind_group(0, &resources.globals_bind_group, &[]);
pass.set_bind_group(1, &instances, &[]);
pass.set_bind_group(2, &texture, &[]);
pass.draw(0..4, 0..sprites.len() as u32);
Ok(())
}
fn draw_paths_to_intermediate(
&self,
&mut self,
encoder: &mut wgpu::CommandEncoder,
paths: &[Path<ScaledPixels>],
instance_offset: &mut u64,
) -> bool {
) -> Result<bool> {
let mut vertices = Vec::new();
for path in paths {
let bounds = path.clipped_bounds();
@@ -1591,30 +1545,18 @@ impl WgpuRenderer {
}
if vertices.is_empty() {
return true;
return Ok(false);
}
let vertex_data = unsafe { Self::instance_bytes(&vertices) };
let Some((vertex_offset, vertex_size)) =
self.write_to_instance_buffer(instance_offset, vertex_data)
else {
return false;
};
let data_bind_group = self.write_instance_binding(
"path_rasterization_bind_group",
instance_offset,
unsafe { Self::instance_bytes(&vertices) },
)?;
let resources = self.resources();
let data_bind_group = resources
.device
.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("path_rasterization_bind_group"),
layout: &resources.bind_group_layouts.instances,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: self.instance_binding(vertex_offset, vertex_size),
}],
});
let Some(path_intermediate_view) = resources.path_intermediate_view.as_ref() else {
return true;
return Ok(false);
};
let (target_view, resolve_target) = if let Some(ref msaa_view) = resources.path_msaa_view {
@@ -1645,46 +1587,75 @@ impl WgpuRenderer {
pass.draw(0..vertices.len() as u32, 0..1);
}
true
Ok(true)
}
fn grow_instance_buffer(&mut self) {
let new_capacity = (self.instance_buffer_capacity * 2).min(self.max_buffer_size);
log::info!("increased instance buffer size to {}", new_capacity);
fn write_instance_binding(
&mut self,
label: &str,
instance_offset: &mut u64,
data: &[u8],
) -> Result<wgpu::BindGroup> {
// wgpu rejects zero-sized bindings, so empty primitive arrays still
// reserve the 16-byte minimum.
let size = (data.len() as u64).max(16);
let mut offset = (*instance_offset).next_multiple_of(self.storage_buffer_alignment.max(1));
if offset + size > self.instance_buffer_capacity {
self.grow_instance_buffer(size)?;
offset = 0;
}
*instance_offset = offset + size;
let resources = self.resources();
if !data.is_empty() {
resources
.queue
.write_buffer(&resources.instance_buffer, offset, data);
}
let bind_group = resources
.device
.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(label),
layout: &resources.bind_group_layouts.instances,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: &resources.instance_buffer,
offset,
size: NonZeroU64::new(size),
}),
}],
});
Ok(bind_group)
}
fn grow_instance_buffer(&mut self, required: u64) -> Result<()> {
let capacity = (self.instance_buffer_capacity * 2)
.max(required.next_power_of_two())
.min(self.max_buffer_size);
anyhow::ensure!(
capacity >= required,
"instance buffer needs {required} bytes, above the device maximum of {}",
self.max_buffer_size
);
anyhow::ensure!(
capacity > self.instance_buffer_capacity,
"frame instance data exceeds the {}-byte maximum",
self.max_buffer_size
);
log::debug!(
"instance buffer grown from {} to {capacity}",
self.instance_buffer_capacity
);
let resources = self.resources_mut();
resources.instance_buffer = resources.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("instance_buffer"),
size: new_capacity,
size: capacity,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
self.instance_buffer_capacity = new_capacity;
}
fn write_to_instance_buffer(
&self,
instance_offset: &mut u64,
data: &[u8],
) -> Option<(u64, NonZeroU64)> {
let offset = (*instance_offset).next_multiple_of(self.storage_buffer_alignment);
let size = (data.len() as u64).max(16);
if offset + size > self.instance_buffer_capacity {
return None;
}
let resources = self.resources();
resources
.queue
.write_buffer(&resources.instance_buffer, offset, data);
*instance_offset = offset + size;
Some((offset, NonZeroU64::new(size).expect("size is at least 16")))
}
fn instance_binding(&self, offset: u64, size: NonZeroU64) -> wgpu::BindingResource<'_> {
wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: &self.resources().instance_buffer,
offset,
size: Some(size),
})
self.instance_buffer_capacity = capacity;
Ok(())
}
/// Mark the surface as unconfigured so rendering is skipped until a new
@@ -1851,6 +1822,10 @@ impl WgpuRenderer {
}
}
fn instance_range(range: Range<usize>) -> Range<u32> {
range.start as u32..range.end as u32
}
#[cfg(not(target_family = "wasm"))]
fn create_surface(
instance: &wgpu::Instance,
+112 -124
View File
@@ -27,6 +27,7 @@ pub(crate) const DISABLE_DIRECT_COMPOSITION: &str = "GPUI_DISABLE_DIRECT_COMPOSI
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;
const MAX_INSTANCE_BUFFER_SIZE: usize = 256 * 1024 * 1024;
pub(crate) struct FontInfo {
pub gamma_ratios: [f32; 4],
@@ -95,6 +96,7 @@ struct DirectXRenderPipelines {
struct DirectXGlobalElements {
global_params_buffer: Option<ID3D11Buffer>,
batch_params_buffer: Option<ID3D11Buffer>,
sampler: Option<ID3D11SamplerState>,
}
@@ -229,6 +231,12 @@ impl DirectXRenderer {
device_context
.OMSetRenderTargets(Some(slice::from_ref(&resources.render_target_view)), None);
device_context.RSSetViewports(Some(slice::from_ref(&resources.viewport)));
device_context
.VSSetConstantBuffers(0, Some(slice::from_ref(&self.globals.global_params_buffer)));
device_context
.VSSetConstantBuffers(1, Some(slice::from_ref(&self.globals.batch_params_buffer)));
device_context
.PSSetConstantBuffers(0, Some(slice::from_ref(&self.globals.global_params_buffer)));
}
Ok(())
}
@@ -487,17 +495,11 @@ impl DirectXRenderer {
}
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,
self.globals
.batch_params_buffer
.as_ref()
.context("batch params buffer missing")?,
start as u32,
len as u32,
)
@@ -509,17 +511,11 @@ impl DirectXRenderer {
}
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,
self.globals
.batch_params_buffer
.as_ref()
.context("batch params buffer missing")?,
start as u32,
len as u32,
)
@@ -565,8 +561,6 @@ impl DirectXRenderer {
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,
@@ -629,8 +623,6 @@ impl DirectXRenderer {
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,
)
@@ -641,13 +633,12 @@ impl DirectXRenderer {
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,
self.globals
.batch_params_buffer
.as_ref()
.context("batch params buffer missing")?,
start as u32,
len as u32,
)
@@ -663,14 +654,14 @@ impl DirectXRenderer {
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),
self.globals
.batch_params_buffer
.as_ref()
.context("batch params buffer missing")?,
slice::from_ref(&self.globals.sampler),
start as u32,
len as u32,
@@ -687,14 +678,14 @@ impl DirectXRenderer {
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),
self.globals
.batch_params_buffer
.as_ref()
.context("batch params buffer missing")?,
slice::from_ref(&self.globals.sampler),
start as u32,
len as u32,
@@ -711,14 +702,14 @@ impl DirectXRenderer {
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),
self.globals
.batch_params_buffer
.as_ref()
.context("batch params buffer missing")?,
slice::from_ref(&self.globals.sampler),
start as u32,
len as u32,
@@ -948,18 +939,8 @@ impl DirectComposition {
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 global_params_buffer = create_constant_buffer::<GlobalParams>(device)?;
let batch_params_buffer = create_constant_buffer::<BatchParams>(device)?;
let sampler = unsafe {
let desc = D3D11_SAMPLER_DESC {
@@ -981,6 +962,7 @@ impl DirectXGlobalElements {
Ok(Self {
global_params_buffer,
batch_params_buffer,
sampler,
})
}
@@ -997,6 +979,15 @@ struct GlobalParams {
_pad: [u32; 3],
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(C, align(16))]
struct BatchParams {
start_index: u32,
_padding: [u32; 3],
}
const _: () = assert!(std::mem::size_of::<BatchParams>() == 16);
struct PipelineState<T> {
label: &'static str,
vertex: ID3D11VertexShader,
@@ -1046,7 +1037,17 @@ impl<T> PipelineState<T> {
data: &[T],
) -> Result<()> {
if self.buffer_size < data.len() {
let new_buffer_size = data.len().next_power_of_two();
let element_size = std::mem::size_of::<T>();
let required_size = std::mem::size_of_val(data);
anyhow::ensure!(
required_size <= MAX_INSTANCE_BUFFER_SIZE,
"{} buffer needs {required_size} bytes, above the maximum of {MAX_INSTANCE_BUFFER_SIZE}",
self.label
);
let new_buffer_size = data
.len()
.next_power_of_two()
.min(MAX_INSTANCE_BUFFER_SIZE / element_size);
log::debug!(
"Updating {} buffer size from {} to {}",
self.label,
@@ -1065,8 +1066,6 @@ impl<T> PipelineState<T> {
fn draw(
&self,
device_context: &ID3D11DeviceContext,
viewport: &[D3D11_VIEWPORT],
global_params: &[Option<ID3D11Buffer>],
topology: D3D_PRIMITIVE_TOPOLOGY,
vertex_count: u32,
instance_count: u32,
@@ -1075,10 +1074,8 @@ impl<T> PipelineState<T> {
device_context,
slice::from_ref(&self.view),
topology,
viewport,
&self.vertex,
&self.fragment,
global_params,
&self.blend_state,
);
unsafe {
@@ -1091,8 +1088,6 @@ impl<T> PipelineState<T> {
&self,
device_context: &ID3D11DeviceContext,
texture: &[Option<ID3D11ShaderResourceView>],
viewport: &[D3D11_VIEWPORT],
global_params: &[Option<ID3D11Buffer>],
sampler: &[Option<ID3D11SamplerState>],
instance_count: u32,
) -> Result<()> {
@@ -1100,10 +1095,8 @@ impl<T> PipelineState<T> {
device_context,
slice::from_ref(&self.view),
D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
viewport,
&self.vertex,
&self.fragment,
global_params,
&self.blend_state,
);
unsafe {
@@ -1118,51 +1111,52 @@ impl<T> PipelineState<T> {
fn draw_range(
&self,
device: &ID3D11Device,
device_context: &ID3D11DeviceContext,
viewport: &[D3D11_VIEWPORT],
global_params: &[Option<ID3D11Buffer>],
vertex_count: u32,
batch_params_buffer: &ID3D11Buffer,
first_instance: u32,
instance_count: u32,
) -> Result<()> {
let view = create_buffer_view_range(device, &self.buffer, first_instance, instance_count)?;
anyhow::ensure!(
first_instance as usize + instance_count as usize <= self.buffer_size,
"DirectX instance range exceeds the {} buffer",
self.label
);
update_batch_start(device_context, batch_params_buffer, first_instance)?;
set_pipeline_state(
device_context,
slice::from_ref(&view),
slice::from_ref(&self.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);
device_context.DrawInstanced(4, 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>],
batch_params_buffer: &ID3D11Buffer,
sampler: &[Option<ID3D11SamplerState>],
first_instance: u32,
instance_count: u32,
) -> Result<()> {
let view = create_buffer_view_range(device, &self.buffer, first_instance, instance_count)?;
anyhow::ensure!(
first_instance as usize + instance_count as usize <= self.buffer_size,
"DirectX instance range exceeds the {} buffer",
self.label
);
update_batch_start(device_context, batch_params_buffer, first_instance)?;
set_pipeline_state(
device_context,
slice::from_ref(&view),
slice::from_ref(&self.view),
D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP,
viewport,
&self.vertex,
&self.fragment,
global_params,
&self.blend_state,
);
unsafe {
@@ -1282,7 +1276,14 @@ fn create_resources(
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);
let viewport = D3D11_VIEWPORT {
TopLeftX: 0.0,
TopLeftY: 0.0,
Width: width as f32,
Height: height as f32,
MinDepth: 0.0,
MaxDepth: 1.0,
};
Ok((
render_target,
render_target_view,
@@ -1369,20 +1370,6 @@ fn create_path_intermediate_msaa_texture_and_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 {
@@ -1504,6 +1491,22 @@ fn create_fragment_shader(device: &ID3D11Device, bytes: &[u8]) -> Result<ID3D11P
}
}
#[inline]
fn create_constant_buffer<T>(device: &ID3D11Device) -> Result<Option<ID3D11Buffer>> {
const { assert!(std::mem::size_of::<T>() != 0 && std::mem::size_of::<T>().is_multiple_of(16)) };
let desc = D3D11_BUFFER_DESC {
ByteWidth: std::mem::size_of::<T>() as u32,
Usage: D3D11_USAGE_DYNAMIC,
BindFlags: D3D11_BIND_CONSTANT_BUFFER.0 as u32,
CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32,
MiscFlags: 0,
StructureByteStride: 0,
};
let mut buffer = None;
unsafe { device.CreateBuffer(&desc, None, Some(&mut buffer)) }?;
Ok(buffer)
}
#[inline]
fn create_buffer(
device: &ID3D11Device,
@@ -1533,32 +1536,6 @@ fn create_buffer_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,
@@ -1574,26 +1551,37 @@ fn update_buffer<T>(
Ok(())
}
#[inline]
fn update_batch_start(
device_context: &ID3D11DeviceContext,
buffer: &ID3D11Buffer,
first_instance: u32,
) -> Result<()> {
update_buffer(
device_context,
buffer,
&[BatchParams {
start_index: first_instance,
_padding: [0; 3],
}],
)
}
#[inline]
fn set_pipeline_state(
device_context: &ID3D11DeviceContext,
buffer_view: &[Option<ID3D11ShaderResourceView>],
topology: D3D_PRIMITIVE_TOPOLOGY,
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);
}
}
+17 -7
View File
@@ -9,6 +9,11 @@ cbuffer GlobalParams: register(b0) {
uint3 global_pad;
};
cbuffer BatchParams: register(b1) {
uint batch_start_index;
uint3 batch_pad;
};
Texture2D<float4> t_sprite: register(t0);
SamplerState s_sprite: register(s0);
@@ -525,8 +530,9 @@ struct QuadFragmentInput {
StructuredBuffer<Quad> quads: register(t1);
QuadVertexOutput quad_vertex(uint vertex_id: SV_VertexID, uint quad_id: SV_InstanceID) {
QuadVertexOutput quad_vertex(uint vertex_id: SV_VertexID, uint instance_id: SV_InstanceID) {
float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
uint quad_id = batch_start_index + instance_id;
Quad quad = quads[quad_id];
float4 device_position = to_device_position(unit_vertex, quad.bounds);
@@ -875,8 +881,9 @@ struct ShadowFragmentInput {
StructuredBuffer<Shadow> shadows: register(t1);
ShadowVertexOutput shadow_vertex(uint vertex_id: SV_VertexID, uint shadow_id: SV_InstanceID) {
ShadowVertexOutput shadow_vertex(uint vertex_id: SV_VertexID, uint instance_id: SV_InstanceID) {
float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
uint shadow_id = batch_start_index + instance_id;
Shadow shadow = shadows[shadow_id];
Bounds bounds;
@@ -1080,8 +1087,9 @@ struct UnderlineFragmentInput {
StructuredBuffer<Underline> underlines: register(t1);
UnderlineVertexOutput underline_vertex(uint vertex_id: SV_VertexID, uint underline_id: SV_InstanceID) {
UnderlineVertexOutput underline_vertex(uint vertex_id: SV_VertexID, uint instance_id: SV_InstanceID) {
float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
uint underline_id = batch_start_index + instance_id;
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,
@@ -1155,8 +1163,9 @@ struct MonochromeSpriteFragmentInput {
StructuredBuffer<MonochromeSprite> mono_sprites: register(t1);
MonochromeSpriteVertexOutput monochrome_sprite_vertex(uint vertex_id: SV_VertexID, uint sprite_id: SV_InstanceID) {
MonochromeSpriteVertexOutput monochrome_sprite_vertex(uint vertex_id: SV_VertexID, uint instance_id: SV_InstanceID) {
float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
uint sprite_id = batch_start_index + instance_id;
MonochromeSprite sprite = mono_sprites[sprite_id];
float4 device_position =
to_device_position_transformed(unit_vertex, sprite.bounds, sprite.transformation);
@@ -1178,8 +1187,8 @@ float4 monochrome_sprite_fragment(MonochromeSpriteFragmentInput input): SV_Targe
return float4(input.color.rgb, input.color.a * alpha_corrected);
}
MonochromeSpriteVertexOutput subpixel_sprite_vertex(uint vertex_id: SV_VertexID, uint sprite_id: SV_InstanceID) {
return monochrome_sprite_vertex(vertex_id, sprite_id);
MonochromeSpriteVertexOutput subpixel_sprite_vertex(uint vertex_id: SV_VertexID, uint instance_id: SV_InstanceID) {
return monochrome_sprite_vertex(vertex_id, instance_id);
}
SubpixelSpriteFragmentOutput subpixel_sprite_fragment(MonochromeSpriteFragmentInput input) {
@@ -1227,8 +1236,9 @@ struct PolychromeSpriteFragmentInput {
StructuredBuffer<PolychromeSprite> poly_sprites: register(t1);
PolychromeSpriteVertexOutput polychrome_sprite_vertex(uint vertex_id: SV_VertexID, uint sprite_id: SV_InstanceID) {
PolychromeSpriteVertexOutput polychrome_sprite_vertex(uint vertex_id: SV_VertexID, uint instance_id: SV_InstanceID) {
float2 unit_vertex = float2(float(vertex_id & 1u), 0.5 * float(vertex_id & 2u));
uint sprite_id = batch_start_index + instance_id;
PolychromeSprite sprite = poly_sprites[sprite_id];
float4 device_position = to_device_position(unit_vertex, sprite.bounds);
float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,