Make TrackedRenderPass::set_vertex_buffer aware of slice size (#14916)

# Objective

- Fixes #14841

## Solution

- Compute BufferSlice size manually and use it for comparison in
`TrackedRenderPass`

## Testing

- Gizmo example does not crash with #14721 (without system ordering),
and `slice` computes correct size there

---

## Migration Guide

- `TrackedRenderPass::set_vertex_buffer` function has been modified to
update vertex buffers when the same buffer with the same offset is
provided, but its size has changed. Some existing code may rely on the
previous behavior, which did not update the vertex buffer in this
scenario.

---------

Co-authored-by: Zachary Harrold <zac@harrold.com.au>
This commit is contained in:
akimakinai 2024-08-28 20:41:42 +09:00 committed by GitHub
parent d93b78a66e
commit 4648f7bf72
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 50 additions and 37 deletions

View file

@ -54,9 +54,7 @@ impl Plugin for LineGizmo2dPlugin {
) )
.add_systems( .add_systems(
Render, Render,
// FIXME: added `chain()` to workaround vertex buffer being not updated when sliced size changed
(queue_line_gizmos_2d, queue_line_joint_gizmos_2d) (queue_line_gizmos_2d, queue_line_joint_gizmos_2d)
.chain()
.in_set(GizmoRenderSystem::QueueLineGizmos2d) .in_set(GizmoRenderSystem::QueueLineGizmos2d)
.after(prepare_assets::<GpuLineGizmo>), .after(prepare_assets::<GpuLineGizmo>),
); );

View file

@ -53,9 +53,7 @@ impl Plugin for LineGizmo3dPlugin {
) )
.add_systems( .add_systems(
Render, Render,
// FIXME: added `chain()` to workaround vertex buffer being not updated when sliced size changed
(queue_line_gizmos_3d, queue_line_joint_gizmos_3d) (queue_line_gizmos_3d, queue_line_joint_gizmos_3d)
.chain()
.in_set(GizmoRenderSystem::QueueLineGizmos3d) .in_set(GizmoRenderSystem::QueueLineGizmos3d)
.after(prepare_assets::<GpuLineGizmo>), .after(prepare_assets::<GpuLineGizmo>),
); );

View file

@ -21,13 +21,14 @@ use wgpu::{IndexFormat, QuerySet, RenderPass};
struct DrawState { struct DrawState {
pipeline: Option<RenderPipelineId>, pipeline: Option<RenderPipelineId>,
bind_groups: Vec<(Option<BindGroupId>, Vec<u32>)>, bind_groups: Vec<(Option<BindGroupId>, Vec<u32>)>,
vertex_buffers: Vec<Option<(BufferId, u64)>>, /// List of vertex buffers by [`BufferId`], offset, and size. See [`DrawState::buffer_slice_key`]
vertex_buffers: Vec<Option<(BufferId, u64, u64)>>,
index_buffer: Option<(BufferId, u64, IndexFormat)>, index_buffer: Option<(BufferId, u64, IndexFormat)>,
} }
impl DrawState { impl DrawState {
/// Marks the `pipeline` as bound. /// Marks the `pipeline` as bound.
pub fn set_pipeline(&mut self, pipeline: RenderPipelineId) { fn set_pipeline(&mut self, pipeline: RenderPipelineId) {
// TODO: do these need to be cleared? // TODO: do these need to be cleared?
// self.bind_groups.clear(); // self.bind_groups.clear();
// self.vertex_buffers.clear(); // self.vertex_buffers.clear();
@ -36,17 +37,12 @@ impl DrawState {
} }
/// Checks, whether the `pipeline` is already bound. /// Checks, whether the `pipeline` is already bound.
pub fn is_pipeline_set(&self, pipeline: RenderPipelineId) -> bool { fn is_pipeline_set(&self, pipeline: RenderPipelineId) -> bool {
self.pipeline == Some(pipeline) self.pipeline == Some(pipeline)
} }
/// Marks the `bind_group` as bound to the `index`. /// Marks the `bind_group` as bound to the `index`.
pub fn set_bind_group( fn set_bind_group(&mut self, index: usize, bind_group: BindGroupId, dynamic_indices: &[u32]) {
&mut self,
index: usize,
bind_group: BindGroupId,
dynamic_indices: &[u32],
) {
let group = &mut self.bind_groups[index]; let group = &mut self.bind_groups[index];
group.0 = Some(bind_group); group.0 = Some(bind_group);
group.1.clear(); group.1.clear();
@ -54,7 +50,7 @@ impl DrawState {
} }
/// Checks, whether the `bind_group` is already bound to the `index`. /// Checks, whether the `bind_group` is already bound to the `index`.
pub fn is_bind_group_set( fn is_bind_group_set(
&self, &self,
index: usize, index: usize,
bind_group: BindGroupId, bind_group: BindGroupId,
@ -68,26 +64,35 @@ impl DrawState {
} }
/// Marks the vertex `buffer` as bound to the `index`. /// Marks the vertex `buffer` as bound to the `index`.
pub fn set_vertex_buffer(&mut self, index: usize, buffer: BufferId, offset: u64) { fn set_vertex_buffer(&mut self, index: usize, buffer_slice: BufferSlice) {
self.vertex_buffers[index] = Some((buffer, offset)); self.vertex_buffers[index] = Some(self.buffer_slice_key(&buffer_slice));
} }
/// Checks, whether the vertex `buffer` is already bound to the `index`. /// Checks, whether the vertex `buffer` is already bound to the `index`.
pub fn is_vertex_buffer_set(&self, index: usize, buffer: BufferId, offset: u64) -> bool { fn is_vertex_buffer_set(&self, index: usize, buffer_slice: &BufferSlice) -> bool {
if let Some(current) = self.vertex_buffers.get(index) { if let Some(current) = self.vertex_buffers.get(index) {
*current == Some((buffer, offset)) *current == Some(self.buffer_slice_key(buffer_slice))
} else { } else {
false false
} }
} }
/// Returns the value used for checking whether `BufferSlice`s are equivalent.
fn buffer_slice_key(&self, buffer_slice: &BufferSlice) -> (BufferId, u64, u64) {
(
buffer_slice.id(),
buffer_slice.offset(),
buffer_slice.size(),
)
}
/// Marks the index `buffer` as bound. /// Marks the index `buffer` as bound.
pub fn set_index_buffer(&mut self, buffer: BufferId, offset: u64, index_format: IndexFormat) { fn set_index_buffer(&mut self, buffer: BufferId, offset: u64, index_format: IndexFormat) {
self.index_buffer = Some((buffer, offset, index_format)); self.index_buffer = Some((buffer, offset, index_format));
} }
/// Checks, whether the index `buffer` is already bound. /// Checks, whether the index `buffer` is already bound.
pub fn is_index_buffer_set( fn is_index_buffer_set(
&self, &self,
buffer: BufferId, buffer: BufferId,
offset: u64, offset: u64,
@ -188,30 +193,27 @@ impl<'a> TrackedRenderPass<'a> {
/// [`draw`]: TrackedRenderPass::draw /// [`draw`]: TrackedRenderPass::draw
/// [`draw_indexed`]: TrackedRenderPass::draw_indexed /// [`draw_indexed`]: TrackedRenderPass::draw_indexed
pub fn set_vertex_buffer(&mut self, slot_index: usize, buffer_slice: BufferSlice<'a>) { pub fn set_vertex_buffer(&mut self, slot_index: usize, buffer_slice: BufferSlice<'a>) {
let offset = buffer_slice.offset(); if self.state.is_vertex_buffer_set(slot_index, &buffer_slice) {
if self
.state
.is_vertex_buffer_set(slot_index, buffer_slice.id(), offset)
{
detailed_trace!( detailed_trace!(
"set vertex buffer {} (already set): {:?} ({})", "set vertex buffer {} (already set): {:?} (offset = {}, size = {})",
slot_index, slot_index,
buffer_slice.id(), buffer_slice.id(),
offset buffer_slice.offset(),
buffer_slice.size(),
); );
return; return;
} }
detailed_trace!( detailed_trace!(
"set vertex buffer {}: {:?} ({})", "set vertex buffer {}: {:?} (offset = {}, size = {})",
slot_index, slot_index,
buffer_slice.id(), buffer_slice.id(),
offset buffer_slice.offset(),
buffer_slice.size(),
); );
self.pass self.pass
.set_vertex_buffer(slot_index as u32, *buffer_slice); .set_vertex_buffer(slot_index as u32, *buffer_slice);
self.state self.state.set_vertex_buffer(slot_index, buffer_slice);
.set_vertex_buffer(slot_index, buffer_slice.id(), offset);
} }
/// Sets the active index buffer. /// Sets the active index buffer.

View file

@ -8,6 +8,7 @@ render_resource_wrapper!(ErasedBuffer, wgpu::Buffer);
pub struct Buffer { pub struct Buffer {
id: BufferId, id: BufferId,
value: ErasedBuffer, value: ErasedBuffer,
size: wgpu::BufferAddress,
} }
impl Buffer { impl Buffer {
@ -17,14 +18,21 @@ impl Buffer {
} }
pub fn slice(&self, bounds: impl RangeBounds<wgpu::BufferAddress>) -> BufferSlice { pub fn slice(&self, bounds: impl RangeBounds<wgpu::BufferAddress>) -> BufferSlice {
// need to compute and store this manually because wgpu doesn't export offset and size on wgpu::BufferSlice
let offset = match bounds.start_bound() {
Bound::Included(&bound) => bound,
Bound::Excluded(&bound) => bound + 1,
Bound::Unbounded => 0,
};
let size = match bounds.end_bound() {
Bound::Included(&bound) => bound + 1,
Bound::Excluded(&bound) => bound,
Bound::Unbounded => self.size,
} - offset;
BufferSlice { BufferSlice {
id: self.id, id: self.id,
// need to compute and store this manually because wgpu doesn't export offset on wgpu::BufferSlice offset,
offset: match bounds.start_bound() { size,
Bound::Included(&bound) => bound,
Bound::Excluded(&bound) => bound + 1,
Bound::Unbounded => 0,
},
value: self.value.slice(bounds), value: self.value.slice(bounds),
} }
} }
@ -39,6 +47,7 @@ impl From<wgpu::Buffer> for Buffer {
fn from(value: wgpu::Buffer) -> Self { fn from(value: wgpu::Buffer) -> Self {
Buffer { Buffer {
id: BufferId::new(), id: BufferId::new(),
size: value.size(),
value: ErasedBuffer::new(value), value: ErasedBuffer::new(value),
} }
} }
@ -58,6 +67,7 @@ pub struct BufferSlice<'a> {
id: BufferId, id: BufferId,
offset: wgpu::BufferAddress, offset: wgpu::BufferAddress,
value: wgpu::BufferSlice<'a>, value: wgpu::BufferSlice<'a>,
size: wgpu::BufferAddress,
} }
impl<'a> BufferSlice<'a> { impl<'a> BufferSlice<'a> {
@ -70,6 +80,11 @@ impl<'a> BufferSlice<'a> {
pub fn offset(&self) -> wgpu::BufferAddress { pub fn offset(&self) -> wgpu::BufferAddress {
self.offset self.offset
} }
#[inline]
pub fn size(&self) -> wgpu::BufferAddress {
self.size
}
} }
impl<'a> Deref for BufferSlice<'a> { impl<'a> Deref for BufferSlice<'a> {