#![allow(clippy::reversed_empty_ranges)]
use crate::{
binding_model::{BindError, BindGroup, PipelineLayout},
command::{
BasePass, BindGroupStateChange, ColorAttachmentError, DrawError, MapPassErr,
PassErrorScope, RenderCommandError, StateChange,
},
device::{
AttachmentData, Device, DeviceError, MissingDownlevelFlags, RenderPassContext,
SHADER_STAGE_COUNT,
},
hal_api::HalApi,
hub::Hub,
id,
init_tracker::{BufferInitTrackerAction, MemoryInitKind, TextureInitTrackerAction},
pipeline::{PipelineFlags, RenderPipeline, VertexStep},
resource::{Buffer, DestroyedResourceError, Labeled, ParentDevice, TrackingData},
resource_log,
snatch::SnatchGuard,
track::RenderBundleScope,
Label, LabelHelpers,
};
use arrayvec::ArrayVec;
use std::{borrow::Cow, mem, num::NonZeroU32, ops::Range, sync::Arc};
use thiserror::Error;
use hal::CommandEncoder as _;
use super::{
render_command::{ArcRenderCommand, RenderCommand},
DrawKind,
};
fn validate_draw<A: HalApi>(
vertex: &[Option<VertexState<A>>],
step: &[VertexStep],
first_vertex: u32,
vertex_count: u32,
first_instance: u32,
instance_count: u32,
) -> Result<(), DrawError> {
let vertices_end = first_vertex as u64 + vertex_count as u64;
let instances_end = first_instance as u64 + instance_count as u64;
for (idx, (vbs, step)) in vertex.iter().zip(step).enumerate() {
let Some(vbs) = vbs else {
continue;
};
let stride_count = match step.mode {
wgt::VertexStepMode::Vertex => vertices_end,
wgt::VertexStepMode::Instance => instances_end,
};
if stride_count == 0 {
continue;
}
let offset = (stride_count - 1) * step.stride + step.last_stride;
let limit = vbs.range.end - vbs.range.start;
if offset > limit {
return Err(DrawError::VertexOutOfBounds {
step_mode: step.mode,
offset,
limit,
slot: idx as u32,
});
}
}
Ok(())
}
fn validate_indexed_draw<A: HalApi>(
vertex: &[Option<VertexState<A>>],
step: &[VertexStep],
index_state: &IndexState<A>,
first_index: u32,
index_count: u32,
first_instance: u32,
instance_count: u32,
) -> Result<(), DrawError> {
let last_index = first_index as u64 + index_count as u64;
let index_limit = index_state.limit();
if last_index > index_limit {
return Err(DrawError::IndexBeyondLimit {
last_index,
index_limit,
});
}
let stride_count = first_instance as u64 + instance_count as u64;
for (idx, (vbs, step)) in vertex.iter().zip(step).enumerate() {
let Some(vbs) = vbs else {
continue;
};
if stride_count == 0 || step.mode != wgt::VertexStepMode::Instance {
continue;
}
let offset = (stride_count - 1) * step.stride + step.last_stride;
let limit = vbs.range.end - vbs.range.start;
if offset > limit {
return Err(DrawError::VertexOutOfBounds {
step_mode: step.mode,
offset,
limit,
slot: idx as u32,
});
}
}
Ok(())
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RenderBundleEncoderDescriptor<'a> {
pub label: Label<'a>,
pub color_formats: Cow<'a, [Option<wgt::TextureFormat>]>,
pub depth_stencil: Option<wgt::RenderBundleDepthStencil>,
pub sample_count: u32,
pub multiview: Option<NonZeroU32>,
}
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct RenderBundleEncoder {
base: BasePass<RenderCommand>,
parent_id: id::DeviceId,
pub(crate) context: RenderPassContext,
pub(crate) is_depth_read_only: bool,
pub(crate) is_stencil_read_only: bool,
#[cfg_attr(feature = "serde", serde(skip))]
current_bind_groups: BindGroupStateChange,
#[cfg_attr(feature = "serde", serde(skip))]
current_pipeline: StateChange<id::RenderPipelineId>,
}
impl RenderBundleEncoder {
pub fn new(
desc: &RenderBundleEncoderDescriptor,
parent_id: id::DeviceId,
base: Option<BasePass<RenderCommand>>,
) -> Result<Self, CreateRenderBundleError> {
let (is_depth_read_only, is_stencil_read_only) = match desc.depth_stencil {
Some(ds) => {
let aspects = hal::FormatAspects::from(ds.format);
(
!aspects.contains(hal::FormatAspects::DEPTH) || ds.depth_read_only,
!aspects.contains(hal::FormatAspects::STENCIL) || ds.stencil_read_only,
)
}
None => (true, true),
};
let max_color_attachments = hal::MAX_COLOR_ATTACHMENTS;
Ok(Self {
base: base.unwrap_or_else(|| BasePass::new(&desc.label)),
parent_id,
context: RenderPassContext {
attachments: AttachmentData {
colors: if desc.color_formats.len() > max_color_attachments {
return Err(CreateRenderBundleError::ColorAttachment(
ColorAttachmentError::TooMany {
given: desc.color_formats.len(),
limit: max_color_attachments,
},
));
} else {
desc.color_formats.iter().cloned().collect()
},
resolves: ArrayVec::new(),
depth_stencil: desc.depth_stencil.map(|ds| ds.format),
},
sample_count: {
let sc = desc.sample_count;
if sc == 0 || sc > 32 || !sc.is_power_of_two() {
return Err(CreateRenderBundleError::InvalidSampleCount(sc));
}
sc
},
multiview: desc.multiview,
},
is_depth_read_only,
is_stencil_read_only,
current_bind_groups: BindGroupStateChange::new(),
current_pipeline: StateChange::new(),
})
}
pub fn dummy(parent_id: id::DeviceId) -> Self {
Self {
base: BasePass::new(&None),
parent_id,
context: RenderPassContext {
attachments: AttachmentData {
colors: ArrayVec::new(),
resolves: ArrayVec::new(),
depth_stencil: None,
},
sample_count: 0,
multiview: None,
},
is_depth_read_only: false,
is_stencil_read_only: false,
current_bind_groups: BindGroupStateChange::new(),
current_pipeline: StateChange::new(),
}
}
#[cfg(feature = "trace")]
pub(crate) fn to_base_pass(&self) -> BasePass<RenderCommand> {
self.base.clone()
}
pub fn parent(&self) -> id::DeviceId {
self.parent_id
}
pub(crate) fn finish<A: HalApi>(
self,
desc: &RenderBundleDescriptor,
device: &Arc<Device<A>>,
hub: &Hub<A>,
) -> Result<Arc<RenderBundle<A>>, RenderBundleError> {
let scope = PassErrorScope::Bundle;
device.check_is_valid().map_pass_err(scope)?;
let bind_group_guard = hub.bind_groups.read();
let pipeline_guard = hub.render_pipelines.read();
let buffer_guard = hub.buffers.read();
let mut state = State {
trackers: RenderBundleScope::new(),
pipeline: None,
bind: (0..hal::MAX_BIND_GROUPS).map(|_| None).collect(),
vertex: (0..hal::MAX_VERTEX_BUFFERS).map(|_| None).collect(),
index: None,
flat_dynamic_offsets: Vec::new(),
device: device.clone(),
commands: Vec::new(),
buffer_memory_init_actions: Vec::new(),
texture_memory_init_actions: Vec::new(),
next_dynamic_offset: 0,
};
let indices = &state.device.tracker_indices;
state
.trackers
.buffers
.write()
.set_size(indices.buffers.size());
state
.trackers
.textures
.write()
.set_size(indices.textures.size());
state
.trackers
.bind_groups
.write()
.set_size(indices.bind_groups.size());
state
.trackers
.render_pipelines
.write()
.set_size(indices.render_pipelines.size());
state
.trackers
.query_sets
.write()
.set_size(indices.query_sets.size());
let base = &self.base;
for &command in &base.commands {
match command {
RenderCommand::SetBindGroup {
index,
num_dynamic_offsets,
bind_group_id,
} => {
let scope = PassErrorScope::SetBindGroup;
set_bind_group(
&mut state,
&bind_group_guard,
&base.dynamic_offsets,
index,
num_dynamic_offsets,
bind_group_id,
)
.map_pass_err(scope)?;
}
RenderCommand::SetPipeline(pipeline_id) => {
let scope = PassErrorScope::SetPipelineRender;
set_pipeline(
&mut state,
&pipeline_guard,
&self.context,
self.is_depth_read_only,
self.is_stencil_read_only,
pipeline_id,
)
.map_pass_err(scope)?;
}
RenderCommand::SetIndexBuffer {
buffer_id,
index_format,
offset,
size,
} => {
let scope = PassErrorScope::SetIndexBuffer;
set_index_buffer(
&mut state,
&buffer_guard,
buffer_id,
index_format,
offset,
size,
)
.map_pass_err(scope)?;
}
RenderCommand::SetVertexBuffer {
slot,
buffer_id,
offset,
size,
} => {
let scope = PassErrorScope::SetVertexBuffer;
set_vertex_buffer(&mut state, &buffer_guard, slot, buffer_id, offset, size)
.map_pass_err(scope)?;
}
RenderCommand::SetPushConstant {
stages,
offset,
size_bytes,
values_offset,
} => {
let scope = PassErrorScope::SetPushConstant;
set_push_constant(&mut state, stages, offset, size_bytes, values_offset)
.map_pass_err(scope)?;
}
RenderCommand::Draw {
vertex_count,
instance_count,
first_vertex,
first_instance,
} => {
let scope = PassErrorScope::Draw {
kind: DrawKind::Draw,
indexed: false,
};
draw(
&mut state,
&base.dynamic_offsets,
vertex_count,
instance_count,
first_vertex,
first_instance,
)
.map_pass_err(scope)?;
}
RenderCommand::DrawIndexed {
index_count,
instance_count,
first_index,
base_vertex,
first_instance,
} => {
let scope = PassErrorScope::Draw {
kind: DrawKind::Draw,
indexed: true,
};
draw_indexed(
&mut state,
&base.dynamic_offsets,
index_count,
instance_count,
first_index,
base_vertex,
first_instance,
)
.map_pass_err(scope)?;
}
RenderCommand::MultiDrawIndirect {
buffer_id,
offset,
count: None,
indexed,
} => {
let scope = PassErrorScope::Draw {
kind: DrawKind::DrawIndirect,
indexed,
};
multi_draw_indirect(
&mut state,
&base.dynamic_offsets,
&buffer_guard,
buffer_id,
offset,
indexed,
)
.map_pass_err(scope)?;
}
RenderCommand::MultiDrawIndirect { .. }
| RenderCommand::MultiDrawIndirectCount { .. } => unimplemented!(),
RenderCommand::PushDebugGroup { color: _, len: _ } => unimplemented!(),
RenderCommand::InsertDebugMarker { color: _, len: _ } => unimplemented!(),
RenderCommand::PopDebugGroup => unimplemented!(),
RenderCommand::WriteTimestamp { .. }
| RenderCommand::BeginOcclusionQuery { .. }
| RenderCommand::EndOcclusionQuery
| RenderCommand::BeginPipelineStatisticsQuery { .. }
| RenderCommand::EndPipelineStatisticsQuery => unimplemented!(),
RenderCommand::ExecuteBundle(_)
| RenderCommand::SetBlendConstant(_)
| RenderCommand::SetStencilReference(_)
| RenderCommand::SetViewport { .. }
| RenderCommand::SetScissor(_) => unreachable!("not supported by a render bundle"),
}
}
let State {
trackers,
flat_dynamic_offsets,
device,
commands,
buffer_memory_init_actions,
texture_memory_init_actions,
..
} = state;
let tracker_indices = device.tracker_indices.bundles.clone();
let discard_hal_labels = device
.instance_flags
.contains(wgt::InstanceFlags::DISCARD_HAL_LABELS);
let render_bundle = RenderBundle {
base: BasePass {
label: desc.label.as_ref().map(|cow| cow.to_string()),
commands,
dynamic_offsets: flat_dynamic_offsets,
string_data: Vec::new(),
push_constant_data: Vec::new(),
},
is_depth_read_only: self.is_depth_read_only,
is_stencil_read_only: self.is_stencil_read_only,
device: device.clone(),
used: trackers,
buffer_memory_init_actions,
texture_memory_init_actions,
context: self.context,
label: desc.label.to_string(),
tracking_data: TrackingData::new(tracker_indices),
discard_hal_labels,
};
let render_bundle = Arc::new(render_bundle);
Ok(render_bundle)
}
pub fn set_index_buffer(
&mut self,
buffer_id: id::BufferId,
index_format: wgt::IndexFormat,
offset: wgt::BufferAddress,
size: Option<wgt::BufferSize>,
) {
self.base.commands.push(RenderCommand::SetIndexBuffer {
buffer_id,
index_format,
offset,
size,
});
}
}
fn set_bind_group<A: HalApi>(
state: &mut State<A>,
bind_group_guard: &crate::lock::RwLockReadGuard<crate::storage::Storage<BindGroup<A>>>,
dynamic_offsets: &[u32],
index: u32,
num_dynamic_offsets: usize,
bind_group_id: id::Id<id::markers::BindGroup>,
) -> Result<(), RenderBundleErrorInner> {
let bind_group = bind_group_guard
.get_owned(bind_group_id)
.map_err(|_| RenderCommandError::InvalidBindGroupId(bind_group_id))?;
bind_group.same_device(&state.device)?;
let max_bind_groups = state.device.limits.max_bind_groups;
if index >= max_bind_groups {
return Err(RenderCommandError::BindGroupIndexOutOfRange {
index,
max: max_bind_groups,
}
.into());
}
let offsets_range = state.next_dynamic_offset..state.next_dynamic_offset + num_dynamic_offsets;
state.next_dynamic_offset = offsets_range.end;
let offsets = &dynamic_offsets[offsets_range.clone()];
bind_group.validate_dynamic_bindings(index, offsets)?;
state
.buffer_memory_init_actions
.extend_from_slice(&bind_group.used_buffer_ranges);
state
.texture_memory_init_actions
.extend_from_slice(&bind_group.used_texture_ranges);
state.set_bind_group(index, &bind_group, offsets_range);
unsafe { state.trackers.merge_bind_group(&bind_group.used)? };
state.trackers.bind_groups.write().insert_single(bind_group);
Ok(())
}
fn set_pipeline<A: HalApi>(
state: &mut State<A>,
pipeline_guard: &crate::lock::RwLockReadGuard<crate::storage::Storage<RenderPipeline<A>>>,
context: &RenderPassContext,
is_depth_read_only: bool,
is_stencil_read_only: bool,
pipeline_id: id::Id<id::markers::RenderPipeline>,
) -> Result<(), RenderBundleErrorInner> {
let pipeline = pipeline_guard
.get_owned(pipeline_id)
.map_err(|_| RenderCommandError::InvalidPipelineId(pipeline_id))?;
pipeline.same_device(&state.device)?;
context
.check_compatible(&pipeline.pass_context, pipeline.as_ref())
.map_err(RenderCommandError::IncompatiblePipelineTargets)?;
if pipeline.flags.contains(PipelineFlags::WRITES_DEPTH) && is_depth_read_only {
return Err(RenderCommandError::IncompatibleDepthAccess(pipeline.error_ident()).into());
}
if pipeline.flags.contains(PipelineFlags::WRITES_STENCIL) && is_stencil_read_only {
return Err(RenderCommandError::IncompatibleStencilAccess(pipeline.error_ident()).into());
}
let pipeline_state = PipelineState::new(&pipeline);
state
.commands
.push(ArcRenderCommand::SetPipeline(pipeline.clone()));
if let Some(iter) = pipeline_state.zero_push_constants() {
state.commands.extend(iter)
}
state.invalidate_bind_groups(&pipeline_state, &pipeline.layout);
state.pipeline = Some(pipeline_state);
state
.trackers
.render_pipelines
.write()
.insert_single(pipeline);
Ok(())
}
fn set_index_buffer<A: HalApi>(
state: &mut State<A>,
buffer_guard: &crate::lock::RwLockReadGuard<crate::storage::Storage<Buffer<A>>>,
buffer_id: id::Id<id::markers::Buffer>,
index_format: wgt::IndexFormat,
offset: u64,
size: Option<std::num::NonZeroU64>,
) -> Result<(), RenderBundleErrorInner> {
let buffer = buffer_guard
.get_owned(buffer_id)
.map_err(|_| RenderCommandError::InvalidBufferId(buffer_id))?;
state
.trackers
.buffers
.write()
.merge_single(&buffer, hal::BufferUses::INDEX)?;
buffer.same_device(&state.device)?;
buffer.check_usage(wgt::BufferUsages::INDEX)?;
let end = match size {
Some(s) => offset + s.get(),
None => buffer.size,
};
state
.buffer_memory_init_actions
.extend(buffer.initialization_status.read().create_action(
&buffer,
offset..end,
MemoryInitKind::NeedsInitializedMemory,
));
state.set_index_buffer(buffer, index_format, offset..end);
Ok(())
}
fn set_vertex_buffer<A: HalApi>(
state: &mut State<A>,
buffer_guard: &crate::lock::RwLockReadGuard<crate::storage::Storage<Buffer<A>>>,
slot: u32,
buffer_id: id::Id<id::markers::Buffer>,
offset: u64,
size: Option<std::num::NonZeroU64>,
) -> Result<(), RenderBundleErrorInner> {
let max_vertex_buffers = state.device.limits.max_vertex_buffers;
if slot >= max_vertex_buffers {
return Err(RenderCommandError::VertexBufferIndexOutOfRange {
index: slot,
max: max_vertex_buffers,
}
.into());
}
let buffer = buffer_guard
.get_owned(buffer_id)
.map_err(|_| RenderCommandError::InvalidBufferId(buffer_id))?;
state
.trackers
.buffers
.write()
.merge_single(&buffer, hal::BufferUses::VERTEX)?;
buffer.same_device(&state.device)?;
buffer.check_usage(wgt::BufferUsages::VERTEX)?;
let end = match size {
Some(s) => offset + s.get(),
None => buffer.size,
};
state
.buffer_memory_init_actions
.extend(buffer.initialization_status.read().create_action(
&buffer,
offset..end,
MemoryInitKind::NeedsInitializedMemory,
));
state.vertex[slot as usize] = Some(VertexState::new(buffer, offset..end));
Ok(())
}
fn set_push_constant<A: HalApi>(
state: &mut State<A>,
stages: wgt::ShaderStages,
offset: u32,
size_bytes: u32,
values_offset: Option<u32>,
) -> Result<(), RenderBundleErrorInner> {
let end_offset = offset + size_bytes;
let pipeline_state = state.pipeline()?;
pipeline_state
.pipeline
.layout
.validate_push_constant_ranges(stages, offset, end_offset)?;
state.commands.push(ArcRenderCommand::SetPushConstant {
stages,
offset,
size_bytes,
values_offset,
});
Ok(())
}
fn draw<A: HalApi>(
state: &mut State<A>,
dynamic_offsets: &[u32],
vertex_count: u32,
instance_count: u32,
first_vertex: u32,
first_instance: u32,
) -> Result<(), RenderBundleErrorInner> {
let pipeline = state.pipeline()?;
let used_bind_groups = pipeline.used_bind_groups;
validate_draw(
&state.vertex[..],
&pipeline.steps,
first_vertex,
vertex_count,
first_instance,
instance_count,
)?;
if instance_count > 0 && vertex_count > 0 {
state.flush_vertices();
state.flush_binds(used_bind_groups, dynamic_offsets);
state.commands.push(ArcRenderCommand::Draw {
vertex_count,
instance_count,
first_vertex,
first_instance,
});
}
Ok(())
}
fn draw_indexed<A: HalApi>(
state: &mut State<A>,
dynamic_offsets: &[u32],
index_count: u32,
instance_count: u32,
first_index: u32,
base_vertex: i32,
first_instance: u32,
) -> Result<(), RenderBundleErrorInner> {
let pipeline = state.pipeline()?;
let used_bind_groups = pipeline.used_bind_groups;
let index = match state.index {
Some(ref index) => index,
None => return Err(DrawError::MissingIndexBuffer.into()),
};
validate_indexed_draw(
&state.vertex[..],
&pipeline.steps,
index,
first_index,
index_count,
first_instance,
instance_count,
)?;
if instance_count > 0 && index_count > 0 {
state.flush_index();
state.flush_vertices();
state.flush_binds(used_bind_groups, dynamic_offsets);
state.commands.push(ArcRenderCommand::DrawIndexed {
index_count,
instance_count,
first_index,
base_vertex,
first_instance,
});
}
Ok(())
}
fn multi_draw_indirect<A: HalApi>(
state: &mut State<A>,
dynamic_offsets: &[u32],
buffer_guard: &crate::lock::RwLockReadGuard<crate::storage::Storage<Buffer<A>>>,
buffer_id: id::Id<id::markers::Buffer>,
offset: u64,
indexed: bool,
) -> Result<(), RenderBundleErrorInner> {
state
.device
.require_downlevel_flags(wgt::DownlevelFlags::INDIRECT_EXECUTION)?;
let pipeline = state.pipeline()?;
let used_bind_groups = pipeline.used_bind_groups;
let buffer = buffer_guard
.get_owned(buffer_id)
.map_err(|_| RenderCommandError::InvalidBufferId(buffer_id))?;
state
.trackers
.buffers
.write()
.merge_single(&buffer, hal::BufferUses::INDIRECT)?;
buffer.same_device(&state.device)?;
buffer.check_usage(wgt::BufferUsages::INDIRECT)?;
state
.buffer_memory_init_actions
.extend(buffer.initialization_status.read().create_action(
&buffer,
offset..(offset + mem::size_of::<wgt::DrawIndirectArgs>() as u64),
MemoryInitKind::NeedsInitializedMemory,
));
if indexed {
let index = match state.index {
Some(ref mut index) => index,
None => return Err(DrawError::MissingIndexBuffer.into()),
};
state.commands.extend(index.flush());
}
state.flush_vertices();
state.flush_binds(used_bind_groups, dynamic_offsets);
state.commands.push(ArcRenderCommand::MultiDrawIndirect {
buffer,
offset,
count: None,
indexed,
});
Ok(())
}
#[derive(Clone, Debug, Error)]
#[non_exhaustive]
pub enum CreateRenderBundleError {
#[error(transparent)]
ColorAttachment(#[from] ColorAttachmentError),
#[error("Invalid number of samples {0}")]
InvalidSampleCount(u32),
}
#[derive(Clone, Debug, Error)]
#[non_exhaustive]
pub enum ExecutionError {
#[error(transparent)]
DestroyedResource(#[from] DestroyedResourceError),
#[error("Using {0} in a render bundle is not implemented")]
Unimplemented(&'static str),
}
pub type RenderBundleDescriptor<'a> = wgt::RenderBundleDescriptor<Label<'a>>;
#[derive(Debug)]
pub struct RenderBundle<A: HalApi> {
base: BasePass<ArcRenderCommand<A>>,
pub(super) is_depth_read_only: bool,
pub(super) is_stencil_read_only: bool,
pub(crate) device: Arc<Device<A>>,
pub(crate) used: RenderBundleScope<A>,
pub(super) buffer_memory_init_actions: Vec<BufferInitTrackerAction<A>>,
pub(super) texture_memory_init_actions: Vec<TextureInitTrackerAction<A>>,
pub(super) context: RenderPassContext,
label: String,
pub(crate) tracking_data: TrackingData,
discard_hal_labels: bool,
}
impl<A: HalApi> Drop for RenderBundle<A> {
fn drop(&mut self) {
resource_log!("Drop {}", self.error_ident());
}
}
#[cfg(send_sync)]
unsafe impl<A: HalApi> Send for RenderBundle<A> {}
#[cfg(send_sync)]
unsafe impl<A: HalApi> Sync for RenderBundle<A> {}
impl<A: HalApi> RenderBundle<A> {
pub(super) unsafe fn execute(
&self,
raw: &mut A::CommandEncoder,
snatch_guard: &SnatchGuard,
) -> Result<(), ExecutionError> {
let mut offsets = self.base.dynamic_offsets.as_slice();
let mut pipeline_layout = None::<Arc<PipelineLayout<A>>>;
if !self.discard_hal_labels {
if let Some(ref label) = self.base.label {
unsafe { raw.begin_debug_marker(label) };
}
}
use ArcRenderCommand as Cmd;
for command in self.base.commands.iter() {
match command {
Cmd::SetBindGroup {
index,
num_dynamic_offsets,
bind_group,
} => {
let raw_bg = bind_group.try_raw(snatch_guard)?;
unsafe {
raw.set_bind_group(
pipeline_layout.as_ref().unwrap().raw(),
*index,
raw_bg,
&offsets[..*num_dynamic_offsets],
)
};
offsets = &offsets[*num_dynamic_offsets..];
}
Cmd::SetPipeline(pipeline) => {
unsafe { raw.set_render_pipeline(pipeline.raw()) };
pipeline_layout = Some(pipeline.layout.clone());
}
Cmd::SetIndexBuffer {
buffer,
index_format,
offset,
size,
} => {
let buffer: &A::Buffer = buffer.try_raw(snatch_guard)?;
let bb = hal::BufferBinding {
buffer,
offset: *offset,
size: *size,
};
unsafe { raw.set_index_buffer(bb, *index_format) };
}
Cmd::SetVertexBuffer {
slot,
buffer,
offset,
size,
} => {
let buffer = buffer.try_raw(snatch_guard)?;
let bb = hal::BufferBinding {
buffer,
offset: *offset,
size: *size,
};
unsafe { raw.set_vertex_buffer(*slot, bb) };
}
Cmd::SetPushConstant {
stages,
offset,
size_bytes,
values_offset,
} => {
let pipeline_layout = pipeline_layout.as_ref().unwrap();
if let Some(values_offset) = *values_offset {
let values_end_offset =
(values_offset + size_bytes / wgt::PUSH_CONSTANT_ALIGNMENT) as usize;
let data_slice = &self.base.push_constant_data
[(values_offset as usize)..values_end_offset];
unsafe {
raw.set_push_constants(
pipeline_layout.raw(),
*stages,
*offset,
data_slice,
)
}
} else {
super::push_constant_clear(
*offset,
*size_bytes,
|clear_offset, clear_data| {
unsafe {
raw.set_push_constants(
pipeline_layout.raw(),
*stages,
clear_offset,
clear_data,
)
};
},
);
}
}
Cmd::Draw {
vertex_count,
instance_count,
first_vertex,
first_instance,
} => {
unsafe {
raw.draw(
*first_vertex,
*vertex_count,
*first_instance,
*instance_count,
)
};
}
Cmd::DrawIndexed {
index_count,
instance_count,
first_index,
base_vertex,
first_instance,
} => {
unsafe {
raw.draw_indexed(
*first_index,
*index_count,
*base_vertex,
*first_instance,
*instance_count,
)
};
}
Cmd::MultiDrawIndirect {
buffer,
offset,
count: None,
indexed: false,
} => {
let buffer = buffer.try_raw(snatch_guard)?;
unsafe { raw.draw_indirect(buffer, *offset, 1) };
}
Cmd::MultiDrawIndirect {
buffer,
offset,
count: None,
indexed: true,
} => {
let buffer = buffer.try_raw(snatch_guard)?;
unsafe { raw.draw_indexed_indirect(buffer, *offset, 1) };
}
Cmd::MultiDrawIndirect { .. } | Cmd::MultiDrawIndirectCount { .. } => {
return Err(ExecutionError::Unimplemented("multi-draw-indirect"))
}
Cmd::PushDebugGroup { .. } | Cmd::InsertDebugMarker { .. } | Cmd::PopDebugGroup => {
return Err(ExecutionError::Unimplemented("debug-markers"))
}
Cmd::WriteTimestamp { .. }
| Cmd::BeginOcclusionQuery { .. }
| Cmd::EndOcclusionQuery
| Cmd::BeginPipelineStatisticsQuery { .. }
| Cmd::EndPipelineStatisticsQuery => {
return Err(ExecutionError::Unimplemented("queries"))
}
Cmd::ExecuteBundle(_)
| Cmd::SetBlendConstant(_)
| Cmd::SetStencilReference(_)
| Cmd::SetViewport { .. }
| Cmd::SetScissor(_) => unreachable!(),
}
}
if !self.discard_hal_labels {
if let Some(_) = self.base.label {
unsafe { raw.end_debug_marker() };
}
}
Ok(())
}
}
crate::impl_resource_type!(RenderBundle);
crate::impl_labeled!(RenderBundle);
crate::impl_parent_device!(RenderBundle);
crate::impl_storage_item!(RenderBundle);
crate::impl_trackable!(RenderBundle);
#[derive(Debug)]
struct IndexState<A: HalApi> {
buffer: Arc<Buffer<A>>,
format: wgt::IndexFormat,
range: Range<wgt::BufferAddress>,
is_dirty: bool,
}
impl<A: HalApi> IndexState<A> {
fn limit(&self) -> u64 {
let bytes_per_index = match self.format {
wgt::IndexFormat::Uint16 => 2,
wgt::IndexFormat::Uint32 => 4,
};
(self.range.end - self.range.start) / bytes_per_index
}
fn flush(&mut self) -> Option<ArcRenderCommand<A>> {
if self.is_dirty {
self.is_dirty = false;
Some(ArcRenderCommand::SetIndexBuffer {
buffer: self.buffer.clone(),
index_format: self.format,
offset: self.range.start,
size: wgt::BufferSize::new(self.range.end - self.range.start),
})
} else {
None
}
}
}
#[derive(Debug)]
struct VertexState<A: HalApi> {
buffer: Arc<Buffer<A>>,
range: Range<wgt::BufferAddress>,
is_dirty: bool,
}
impl<A: HalApi> VertexState<A> {
fn new(buffer: Arc<Buffer<A>>, range: Range<wgt::BufferAddress>) -> Self {
Self {
buffer,
range,
is_dirty: true,
}
}
fn flush(&mut self, slot: u32) -> Option<ArcRenderCommand<A>> {
if self.is_dirty {
self.is_dirty = false;
Some(ArcRenderCommand::SetVertexBuffer {
slot,
buffer: self.buffer.clone(),
offset: self.range.start,
size: wgt::BufferSize::new(self.range.end - self.range.start),
})
} else {
None
}
}
}
#[derive(Debug)]
struct BindState<A: HalApi> {
bind_group: Arc<BindGroup<A>>,
dynamic_offsets: Range<usize>,
is_dirty: bool,
}
struct PipelineState<A: HalApi> {
pipeline: Arc<RenderPipeline<A>>,
steps: Vec<VertexStep>,
push_constant_ranges: ArrayVec<wgt::PushConstantRange, { SHADER_STAGE_COUNT }>,
used_bind_groups: usize,
}
impl<A: HalApi> PipelineState<A> {
fn new(pipeline: &Arc<RenderPipeline<A>>) -> Self {
Self {
pipeline: pipeline.clone(),
steps: pipeline.vertex_steps.to_vec(),
push_constant_ranges: pipeline
.layout
.push_constant_ranges
.iter()
.cloned()
.collect(),
used_bind_groups: pipeline.layout.bind_group_layouts.len(),
}
}
fn zero_push_constants(&self) -> Option<impl Iterator<Item = ArcRenderCommand<A>>> {
if !self.push_constant_ranges.is_empty() {
let nonoverlapping_ranges =
super::bind::compute_nonoverlapping_ranges(&self.push_constant_ranges);
Some(
nonoverlapping_ranges
.into_iter()
.map(|range| ArcRenderCommand::SetPushConstant {
stages: range.stages,
offset: range.range.start,
size_bytes: range.range.end - range.range.start,
values_offset: None, }),
)
} else {
None
}
}
}
struct State<A: HalApi> {
trackers: RenderBundleScope<A>,
pipeline: Option<PipelineState<A>>,
bind: ArrayVec<Option<BindState<A>>, { hal::MAX_BIND_GROUPS }>,
vertex: ArrayVec<Option<VertexState<A>>, { hal::MAX_VERTEX_BUFFERS }>,
index: Option<IndexState<A>>,
flat_dynamic_offsets: Vec<wgt::DynamicOffset>,
device: Arc<Device<A>>,
commands: Vec<ArcRenderCommand<A>>,
buffer_memory_init_actions: Vec<BufferInitTrackerAction<A>>,
texture_memory_init_actions: Vec<TextureInitTrackerAction<A>>,
next_dynamic_offset: usize,
}
impl<A: HalApi> State<A> {
fn pipeline(&self) -> Result<&PipelineState<A>, RenderBundleErrorInner> {
self.pipeline
.as_ref()
.ok_or(DrawError::MissingPipeline.into())
}
fn invalidate_bind_group_from(&mut self, index: usize) {
for contents in self.bind[index..].iter_mut().flatten() {
contents.is_dirty = true;
}
}
fn set_bind_group(
&mut self,
slot: u32,
bind_group: &Arc<BindGroup<A>>,
dynamic_offsets: Range<usize>,
) {
if dynamic_offsets.is_empty() {
if let Some(ref contents) = self.bind[slot as usize] {
if contents.bind_group.is_equal(bind_group) {
return;
}
}
}
self.bind[slot as usize] = Some(BindState {
bind_group: bind_group.clone(),
dynamic_offsets,
is_dirty: true,
});
self.invalidate_bind_group_from(slot as usize + 1);
}
fn invalidate_bind_groups(&mut self, new: &PipelineState<A>, layout: &PipelineLayout<A>) {
match self.pipeline {
None => {
self.invalidate_bind_group_from(0);
}
Some(ref old) => {
if old.pipeline.is_equal(&new.pipeline) {
return;
}
if old.push_constant_ranges != new.push_constant_ranges {
self.invalidate_bind_group_from(0);
} else {
let first_changed = self.bind.iter().zip(&layout.bind_group_layouts).position(
|(entry, layout)| match *entry {
Some(ref contents) => !contents.bind_group.layout.is_equal(layout),
None => false,
},
);
if let Some(slot) = first_changed {
self.invalidate_bind_group_from(slot);
}
}
}
}
}
fn set_index_buffer(
&mut self,
buffer: Arc<Buffer<A>>,
format: wgt::IndexFormat,
range: Range<wgt::BufferAddress>,
) {
match self.index {
Some(ref current)
if current.buffer.is_equal(&buffer)
&& current.format == format
&& current.range == range =>
{
return
}
_ => (),
}
self.index = Some(IndexState {
buffer,
format,
range,
is_dirty: true,
});
}
fn flush_index(&mut self) {
let commands = self.index.as_mut().and_then(|index| index.flush());
self.commands.extend(commands);
}
fn flush_vertices(&mut self) {
let commands = self
.vertex
.iter_mut()
.enumerate()
.flat_map(|(i, vs)| vs.as_mut().and_then(|vs| vs.flush(i as u32)));
self.commands.extend(commands);
}
fn flush_binds(&mut self, used_bind_groups: usize, dynamic_offsets: &[wgt::DynamicOffset]) {
for contents in self.bind[..used_bind_groups].iter().flatten() {
if contents.is_dirty {
self.flat_dynamic_offsets
.extend_from_slice(&dynamic_offsets[contents.dynamic_offsets.clone()]);
}
}
let commands = self.bind[..used_bind_groups]
.iter_mut()
.enumerate()
.flat_map(|(i, entry)| {
if let Some(ref mut contents) = *entry {
if contents.is_dirty {
contents.is_dirty = false;
let offsets = &contents.dynamic_offsets;
return Some(ArcRenderCommand::SetBindGroup {
index: i.try_into().unwrap(),
bind_group: contents.bind_group.clone(),
num_dynamic_offsets: offsets.end - offsets.start,
});
}
}
None
});
self.commands.extend(commands);
}
}
#[derive(Clone, Debug, Error)]
pub(super) enum RenderBundleErrorInner {
#[error(transparent)]
Device(#[from] DeviceError),
#[error(transparent)]
RenderCommand(RenderCommandError),
#[error(transparent)]
Draw(#[from] DrawError),
#[error(transparent)]
MissingDownlevelFlags(#[from] MissingDownlevelFlags),
#[error(transparent)]
Bind(#[from] BindError),
}
impl<T> From<T> for RenderBundleErrorInner
where
T: Into<RenderCommandError>,
{
fn from(t: T) -> Self {
Self::RenderCommand(t.into())
}
}
#[derive(Clone, Debug, Error)]
#[error("{scope}")]
pub struct RenderBundleError {
pub scope: PassErrorScope,
#[source]
inner: RenderBundleErrorInner,
}
impl RenderBundleError {
pub fn from_device_error(e: DeviceError) -> Self {
Self {
scope: PassErrorScope::Bundle,
inner: e.into(),
}
}
}
impl<T, E> MapPassErr<T, RenderBundleError> for Result<T, E>
where
E: Into<RenderBundleErrorInner>,
{
fn map_pass_err(self, scope: PassErrorScope) -> Result<T, RenderBundleError> {
self.map_err(|inner| RenderBundleError {
scope,
inner: inner.into(),
})
}
}
pub mod bundle_ffi {
use super::{RenderBundleEncoder, RenderCommand};
use crate::{id, RawString};
use std::{convert::TryInto, slice};
use wgt::{BufferAddress, BufferSize, DynamicOffset, IndexFormat};
#[no_mangle]
pub unsafe extern "C" fn wgpu_render_bundle_set_bind_group(
bundle: &mut RenderBundleEncoder,
index: u32,
bind_group_id: id::BindGroupId,
offsets: *const DynamicOffset,
offset_length: usize,
) {
let offsets = unsafe { slice::from_raw_parts(offsets, offset_length) };
let redundant = bundle.current_bind_groups.set_and_check_redundant(
bind_group_id,
index,
&mut bundle.base.dynamic_offsets,
offsets,
);
if redundant {
return;
}
bundle.base.commands.push(RenderCommand::SetBindGroup {
index,
num_dynamic_offsets: offset_length,
bind_group_id,
});
}
#[no_mangle]
pub extern "C" fn wgpu_render_bundle_set_pipeline(
bundle: &mut RenderBundleEncoder,
pipeline_id: id::RenderPipelineId,
) {
if bundle.current_pipeline.set_and_check_redundant(pipeline_id) {
return;
}
bundle
.base
.commands
.push(RenderCommand::SetPipeline(pipeline_id));
}
#[no_mangle]
pub extern "C" fn wgpu_render_bundle_set_vertex_buffer(
bundle: &mut RenderBundleEncoder,
slot: u32,
buffer_id: id::BufferId,
offset: BufferAddress,
size: Option<BufferSize>,
) {
bundle.base.commands.push(RenderCommand::SetVertexBuffer {
slot,
buffer_id,
offset,
size,
});
}
#[no_mangle]
pub extern "C" fn wgpu_render_bundle_set_index_buffer(
encoder: &mut RenderBundleEncoder,
buffer: id::BufferId,
index_format: IndexFormat,
offset: BufferAddress,
size: Option<BufferSize>,
) {
encoder.set_index_buffer(buffer, index_format, offset, size);
}
#[no_mangle]
pub unsafe extern "C" fn wgpu_render_bundle_set_push_constants(
pass: &mut RenderBundleEncoder,
stages: wgt::ShaderStages,
offset: u32,
size_bytes: u32,
data: *const u8,
) {
assert_eq!(
offset & (wgt::PUSH_CONSTANT_ALIGNMENT - 1),
0,
"Push constant offset must be aligned to 4 bytes."
);
assert_eq!(
size_bytes & (wgt::PUSH_CONSTANT_ALIGNMENT - 1),
0,
"Push constant size must be aligned to 4 bytes."
);
let data_slice = unsafe { slice::from_raw_parts(data, size_bytes as usize) };
let value_offset = pass.base.push_constant_data.len().try_into().expect(
"Ran out of push constant space. Don't set 4gb of push constants per RenderBundle.",
);
pass.base.push_constant_data.extend(
data_slice
.chunks_exact(wgt::PUSH_CONSTANT_ALIGNMENT as usize)
.map(|arr| u32::from_ne_bytes([arr[0], arr[1], arr[2], arr[3]])),
);
pass.base.commands.push(RenderCommand::SetPushConstant {
stages,
offset,
size_bytes,
values_offset: Some(value_offset),
});
}
#[no_mangle]
pub extern "C" fn wgpu_render_bundle_draw(
bundle: &mut RenderBundleEncoder,
vertex_count: u32,
instance_count: u32,
first_vertex: u32,
first_instance: u32,
) {
bundle.base.commands.push(RenderCommand::Draw {
vertex_count,
instance_count,
first_vertex,
first_instance,
});
}
#[no_mangle]
pub extern "C" fn wgpu_render_bundle_draw_indexed(
bundle: &mut RenderBundleEncoder,
index_count: u32,
instance_count: u32,
first_index: u32,
base_vertex: i32,
first_instance: u32,
) {
bundle.base.commands.push(RenderCommand::DrawIndexed {
index_count,
instance_count,
first_index,
base_vertex,
first_instance,
});
}
#[no_mangle]
pub extern "C" fn wgpu_render_bundle_draw_indirect(
bundle: &mut RenderBundleEncoder,
buffer_id: id::BufferId,
offset: BufferAddress,
) {
bundle.base.commands.push(RenderCommand::MultiDrawIndirect {
buffer_id,
offset,
count: None,
indexed: false,
});
}
#[no_mangle]
pub extern "C" fn wgpu_render_bundle_draw_indexed_indirect(
bundle: &mut RenderBundleEncoder,
buffer_id: id::BufferId,
offset: BufferAddress,
) {
bundle.base.commands.push(RenderCommand::MultiDrawIndirect {
buffer_id,
offset,
count: None,
indexed: true,
});
}
#[no_mangle]
pub unsafe extern "C" fn wgpu_render_bundle_push_debug_group(
_bundle: &mut RenderBundleEncoder,
_label: RawString,
) {
}
#[no_mangle]
pub extern "C" fn wgpu_render_bundle_pop_debug_group(_bundle: &mut RenderBundleEncoder) {
}
#[no_mangle]
pub unsafe extern "C" fn wgpu_render_bundle_insert_debug_marker(
_bundle: &mut RenderBundleEncoder,
_label: RawString,
) {
}
}