#![allow(clippy::reversed_empty_ranges)]
use crate::{
command::{
BasePass, DrawError, MapPassErr, PassErrorScope, RenderCommand, RenderCommandError,
StateChange,
},
conv,
device::{
AttachmentData, Device, DeviceError, RenderPassContext, MAX_VERTEX_BUFFERS,
SHADER_STAGE_COUNT,
},
hub::{GfxBackend, GlobalIdentityHandlerFactory, Hub, Resource, Storage, Token},
id,
resource::BufferUse,
span,
track::{TrackerSet, UsageConflict},
validation::check_buffer_usage,
Label, LabelHelpers, LifeGuard, Stored, MAX_BIND_GROUPS,
};
use arrayvec::ArrayVec;
use std::{borrow::Cow, iter, ops::Range};
use thiserror::Error;
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "trace", derive(serde::Serialize))]
#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
pub struct RenderBundleEncoderDescriptor<'a> {
pub label: Label<'a>,
pub color_formats: Cow<'a, [wgt::TextureFormat]>,
pub depth_stencil_format: Option<wgt::TextureFormat>,
pub sample_count: u32,
}
#[derive(Debug)]
#[cfg_attr(feature = "serial-pass", derive(serde::Deserialize, serde::Serialize))]
pub struct RenderBundleEncoder {
base: BasePass<RenderCommand>,
parent_id: id::DeviceId,
pub(crate) context: RenderPassContext,
}
impl RenderBundleEncoder {
pub fn new(
desc: &RenderBundleEncoderDescriptor,
parent_id: id::DeviceId,
base: Option<BasePass<RenderCommand>>,
) -> Result<Self, CreateRenderBundleError> {
span!(_guard, INFO, "RenderBundleEncoder::new");
Ok(Self {
base: base.unwrap_or_else(|| BasePass::new(&desc.label)),
parent_id,
context: RenderPassContext {
attachments: AttachmentData {
colors: desc.color_formats.iter().cloned().collect(),
resolves: ArrayVec::new(),
depth_stencil: desc.depth_stencil_format,
},
sample_count: {
let sc = desc.sample_count;
if sc == 0 || sc > 32 || !conv::is_power_of_two(sc) {
return Err(CreateRenderBundleError::InvalidSampleCount(sc));
}
sc as u8
},
},
})
}
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,
},
}
}
pub fn parent(&self) -> id::DeviceId {
self.parent_id
}
pub(crate) fn finish<B: hal::Backend, G: GlobalIdentityHandlerFactory>(
self,
desc: &RenderBundleDescriptor,
device: &Device<B>,
hub: &Hub<B, G>,
token: &mut Token<Device<B>>,
) -> Result<RenderBundle, RenderBundleError> {
let (pipeline_layout_guard, mut token) = hub.pipeline_layouts.read(token);
let (bind_group_guard, mut token) = hub.bind_groups.read(&mut token);
let (pipeline_guard, mut token) = hub.render_pipelines.read(&mut token);
let (buffer_guard, _) = hub.buffers.read(&mut token);
let mut state = State {
trackers: TrackerSet::new(self.parent_id.backend()),
index: IndexState::new(),
vertex: (0..MAX_VERTEX_BUFFERS)
.map(|_| VertexState::new())
.collect(),
bind: (0..MAX_BIND_GROUPS).map(|_| BindState::new()).collect(),
push_constant_ranges: PushConstantState::new(),
raw_dynamic_offsets: Vec::new(),
flat_dynamic_offsets: Vec::new(),
used_bind_groups: 0,
pipeline: StateChange::new(),
};
let mut commands = Vec::new();
let mut base = self.base.as_ref();
let mut pipeline_layout_id = None::<id::Valid<id::PipelineLayoutId>>;
for &command in base.commands {
match command {
RenderCommand::SetBindGroup {
index,
num_dynamic_offsets,
bind_group_id,
} => {
let scope = PassErrorScope::SetBindGroup(bind_group_id);
let max_bind_groups = device.limits.max_bind_groups;
if (index as u32) >= max_bind_groups {
return Err(RenderCommandError::BindGroupIndexOutOfRange {
index,
max: max_bind_groups,
})
.map_pass_err(scope);
}
let offsets = &base.dynamic_offsets[..num_dynamic_offsets as usize];
base.dynamic_offsets = &base.dynamic_offsets[num_dynamic_offsets as usize..];
if let Some(offset) = offsets
.iter()
.map(|offset| *offset as wgt::BufferAddress)
.find(|offset| offset % wgt::BIND_BUFFER_ALIGNMENT != 0)
{
return Err(RenderCommandError::UnalignedBufferOffset(offset))
.map_pass_err(scope);
}
let bind_group = state
.trackers
.bind_groups
.use_extend(&*bind_group_guard, bind_group_id, (), ())
.map_err(|_| RenderCommandError::InvalidBindGroup(bind_group_id))
.map_pass_err(scope)?;
if bind_group.dynamic_binding_info.len() != offsets.len() {
return Err(RenderCommandError::InvalidDynamicOffsetCount {
actual: offsets.len(),
expected: bind_group.dynamic_binding_info.len(),
})
.map_pass_err(scope);
}
state.set_bind_group(index, bind_group_id, bind_group.layout_id, offsets);
state
.trackers
.merge_extend(&bind_group.used)
.map_pass_err(scope)?;
}
RenderCommand::SetPipeline(pipeline_id) => {
let scope = PassErrorScope::SetPipelineRender(pipeline_id);
if state.pipeline.set_and_check_redundant(pipeline_id) {
continue;
}
let pipeline = state
.trackers
.render_pipes
.use_extend(&*pipeline_guard, pipeline_id, (), ())
.unwrap();
self.context
.check_compatible(&pipeline.pass_context)
.map_err(RenderCommandError::IncompatiblePipeline)
.map_pass_err(scope)?;
let layout = &pipeline_layout_guard[pipeline.layout_id.value];
pipeline_layout_id = Some(pipeline.layout_id.value);
state.set_pipeline(
pipeline.strip_index_format,
&pipeline.vertex_strides,
&layout.bind_group_layout_ids,
&layout.push_constant_ranges,
);
commands.push(command);
if let Some(iter) = state.flush_push_constants() {
commands.extend(iter)
}
}
RenderCommand::SetIndexBuffer {
buffer_id,
index_format,
offset,
size,
} => {
let scope = PassErrorScope::SetIndexBuffer(buffer_id);
let buffer = state
.trackers
.buffers
.use_extend(&*buffer_guard, buffer_id, (), BufferUse::INDEX)
.unwrap();
check_buffer_usage(buffer.usage, wgt::BufferUsage::INDEX)
.map_pass_err(scope)?;
let end = match size {
Some(s) => offset + s.get(),
None => buffer.size,
};
state.index.set_format(index_format);
state.index.set_buffer(buffer_id, offset..end);
}
RenderCommand::SetVertexBuffer {
slot,
buffer_id,
offset,
size,
} => {
let scope = PassErrorScope::SetVertexBuffer(buffer_id);
let buffer = state
.trackers
.buffers
.use_extend(&*buffer_guard, buffer_id, (), BufferUse::VERTEX)
.unwrap();
check_buffer_usage(buffer.usage, wgt::BufferUsage::VERTEX)
.map_pass_err(scope)?;
let end = match size {
Some(s) => offset + s.get(),
None => buffer.size,
};
state.vertex[slot as usize].set_buffer(buffer_id, offset..end);
}
RenderCommand::SetPushConstant {
stages,
offset,
size_bytes,
values_offset: _,
} => {
let scope = PassErrorScope::SetPushConstant;
let end_offset = offset + size_bytes;
let pipeline_layout_id = pipeline_layout_id
.ok_or(DrawError::MissingPipeline)
.map_pass_err(scope)?;
let pipeline_layout = &pipeline_layout_guard[pipeline_layout_id];
pipeline_layout
.validate_push_constant_ranges(stages, offset, end_offset)
.map_pass_err(scope)?;
commands.push(command);
}
RenderCommand::Draw {
vertex_count,
instance_count,
first_vertex,
first_instance,
} => {
let scope = PassErrorScope::Draw {
indexed: false,
indirect: false,
pipeline: state.pipeline.last_state,
};
let vertex_limits = state.vertex_limits();
let last_vertex = first_vertex + vertex_count;
if last_vertex > vertex_limits.vertex_limit {
return Err(DrawError::VertexBeyondLimit {
last_vertex,
vertex_limit: vertex_limits.vertex_limit,
slot: vertex_limits.vertex_limit_slot,
})
.map_pass_err(scope);
}
let last_instance = first_instance + instance_count;
if last_instance > vertex_limits.instance_limit {
return Err(DrawError::InstanceBeyondLimit {
last_instance,
instance_limit: vertex_limits.instance_limit,
slot: vertex_limits.instance_limit_slot,
})
.map_pass_err(scope);
}
commands.extend(state.flush_vertices());
commands.extend(state.flush_binds());
commands.push(command);
}
RenderCommand::DrawIndexed {
index_count,
instance_count,
first_index,
base_vertex: _,
first_instance,
} => {
let scope = PassErrorScope::Draw {
indexed: true,
indirect: false,
pipeline: state.pipeline.last_state,
};
let vertex_limits = state.vertex_limits();
let index_limit = state.index.limit();
let last_index = first_index + index_count;
if last_index > index_limit {
return Err(DrawError::IndexBeyondLimit {
last_index,
index_limit,
})
.map_pass_err(scope);
}
let last_instance = first_instance + instance_count;
if last_instance > vertex_limits.instance_limit {
return Err(DrawError::InstanceBeyondLimit {
last_instance,
instance_limit: vertex_limits.instance_limit,
slot: vertex_limits.instance_limit_slot,
})
.map_pass_err(scope);
}
commands.extend(state.index.flush());
commands.extend(state.flush_vertices());
commands.extend(state.flush_binds());
commands.push(command);
}
RenderCommand::MultiDrawIndirect {
buffer_id,
offset: _,
count: None,
indexed: false,
} => {
let scope = PassErrorScope::Draw {
indexed: false,
indirect: true,
pipeline: state.pipeline.last_state,
};
let buffer = state
.trackers
.buffers
.use_extend(&*buffer_guard, buffer_id, (), BufferUse::INDIRECT)
.unwrap();
check_buffer_usage(buffer.usage, wgt::BufferUsage::INDIRECT)
.map_pass_err(scope)?;
commands.extend(state.flush_vertices());
commands.extend(state.flush_binds());
commands.push(command);
}
RenderCommand::MultiDrawIndirect {
buffer_id,
offset: _,
count: None,
indexed: true,
} => {
let scope = PassErrorScope::Draw {
indexed: true,
indirect: true,
pipeline: state.pipeline.last_state,
};
let buffer = state
.trackers
.buffers
.use_extend(&*buffer_guard, buffer_id, (), BufferUse::INDIRECT)
.map_err(|err| RenderCommandError::Buffer(buffer_id, err))
.map_pass_err(scope)?;
check_buffer_usage(buffer.usage, wgt::BufferUsage::INDIRECT)
.map_pass_err(scope)?;
commands.extend(state.index.flush());
commands.extend(state.flush_vertices());
commands.extend(state.flush_binds());
commands.push(command);
}
RenderCommand::MultiDrawIndirect { .. }
| RenderCommand::MultiDrawIndirectCount { .. } => unimplemented!(),
RenderCommand::PushDebugGroup { color: _, len: _ } => unimplemented!(),
RenderCommand::InsertDebugMarker { color: _, len: _ } => unimplemented!(),
RenderCommand::PopDebugGroup => unimplemented!(),
RenderCommand::WriteTimestamp { .. }
| RenderCommand::BeginPipelineStatisticsQuery { .. }
| RenderCommand::EndPipelineStatisticsQuery => unimplemented!(),
RenderCommand::ExecuteBundle(_)
| RenderCommand::SetBlendColor(_)
| RenderCommand::SetStencilReference(_)
| RenderCommand::SetViewport { .. }
| RenderCommand::SetScissor(_) => unreachable!("not supported by a render bundle"),
}
}
Ok(RenderBundle {
base: BasePass {
label: desc.label.as_ref().map(|cow| cow.to_string()),
commands,
dynamic_offsets: state.flat_dynamic_offsets,
string_data: Vec::new(),
push_constant_data: Vec::new(),
},
device_id: Stored {
value: id::Valid(self.parent_id),
ref_count: device.life_guard.add_ref(),
},
used: state.trackers,
context: self.context,
life_guard: LifeGuard::new(desc.label.borrow_or_default()),
})
}
pub fn set_index_buffer(
&mut self,
buffer_id: id::BufferId,
index_format: wgt::IndexFormat,
offset: wgt::BufferAddress,
size: Option<wgt::BufferSize>,
) {
span!(_guard, DEBUG, "RenderBundle::set_index_buffer");
self.base.commands.push(RenderCommand::SetIndexBuffer {
buffer_id,
index_format,
offset,
size,
});
}
}
#[derive(Clone, Debug, Error)]
pub enum CreateRenderBundleError {
#[error("invalid number of samples {0}")]
InvalidSampleCount(u32),
}
#[derive(Clone, Debug, Error)]
pub enum ExecutionError {
#[error("buffer {0:?} is destroyed")]
DestroyedBuffer(id::BufferId),
}
pub type RenderBundleDescriptor<'a> = wgt::RenderBundleDescriptor<Label<'a>>;
#[derive(Debug)]
pub struct RenderBundle {
base: BasePass<RenderCommand>,
pub(crate) device_id: Stored<id::DeviceId>,
pub(crate) used: TrackerSet,
pub(crate) context: RenderPassContext,
pub(crate) life_guard: LifeGuard,
}
unsafe impl Send for RenderBundle {}
unsafe impl Sync for RenderBundle {}
impl RenderBundle {
#[cfg(feature = "trace")]
pub(crate) fn to_base_pass(&self) -> BasePass<RenderCommand> {
BasePass::from_ref(self.base.as_ref())
}
pub(crate) unsafe fn execute<B: GfxBackend>(
&self,
cmd_buf: &mut B::CommandBuffer,
pipeline_layout_guard: &Storage<
crate::binding_model::PipelineLayout<B>,
id::PipelineLayoutId,
>,
bind_group_guard: &Storage<crate::binding_model::BindGroup<B>, id::BindGroupId>,
pipeline_guard: &Storage<crate::pipeline::RenderPipeline<B>, id::RenderPipelineId>,
buffer_guard: &Storage<crate::resource::Buffer<B>, id::BufferId>,
) -> Result<(), ExecutionError> {
use hal::command::CommandBuffer as _;
let mut offsets = self.base.dynamic_offsets.as_slice();
let mut pipeline_layout_id = None::<id::Valid<id::PipelineLayoutId>>;
if let Some(ref label) = self.base.label {
cmd_buf.begin_debug_marker(label, 0);
}
for command in self.base.commands.iter() {
match *command {
RenderCommand::SetBindGroup {
index,
num_dynamic_offsets,
bind_group_id,
} => {
let bind_group = bind_group_guard.get(bind_group_id).unwrap();
cmd_buf.bind_graphics_descriptor_sets(
&pipeline_layout_guard[pipeline_layout_id.unwrap()].raw,
index as usize,
iter::once(bind_group.raw.raw()),
offsets.iter().take(num_dynamic_offsets as usize).cloned(),
);
offsets = &offsets[num_dynamic_offsets as usize..];
}
RenderCommand::SetPipeline(pipeline_id) => {
let pipeline = pipeline_guard.get(pipeline_id).unwrap();
cmd_buf.bind_graphics_pipeline(&pipeline.raw);
pipeline_layout_id = Some(pipeline.layout_id.value);
}
RenderCommand::SetIndexBuffer {
buffer_id,
index_format,
offset,
size,
} => {
let index_type = conv::map_index_format(index_format);
let &(ref buffer, _) = buffer_guard
.get(buffer_id)
.unwrap()
.raw
.as_ref()
.ok_or(ExecutionError::DestroyedBuffer(buffer_id))?;
let range = hal::buffer::SubRange {
offset,
size: size.map(|s| s.get()),
};
cmd_buf.bind_index_buffer(buffer, range, index_type);
}
RenderCommand::SetVertexBuffer {
slot,
buffer_id,
offset,
size,
} => {
let &(ref buffer, _) = buffer_guard
.get(buffer_id)
.unwrap()
.raw
.as_ref()
.ok_or(ExecutionError::DestroyedBuffer(buffer_id))?;
let range = hal::buffer::SubRange {
offset,
size: size.map(|s| s.get()),
};
cmd_buf.bind_vertex_buffers(slot, iter::once((buffer, range)));
}
RenderCommand::SetPushConstant {
stages,
offset,
size_bytes,
values_offset,
} => {
let pipeline_layout_id = pipeline_layout_id.unwrap();
let pipeline_layout = &pipeline_layout_guard[pipeline_layout_id];
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];
cmd_buf.push_graphics_constants(
&pipeline_layout.raw,
conv::map_shader_stage_flags(stages),
offset,
&data_slice,
)
} else {
super::push_constant_clear(
offset,
size_bytes,
|clear_offset, clear_data| {
cmd_buf.push_graphics_constants(
&pipeline_layout.raw,
conv::map_shader_stage_flags(stages),
clear_offset,
clear_data,
);
},
);
}
}
RenderCommand::Draw {
vertex_count,
instance_count,
first_vertex,
first_instance,
} => {
cmd_buf.draw(
first_vertex..first_vertex + vertex_count,
first_instance..first_instance + instance_count,
);
}
RenderCommand::DrawIndexed {
index_count,
instance_count,
first_index,
base_vertex,
first_instance,
} => {
cmd_buf.draw_indexed(
first_index..first_index + index_count,
base_vertex,
first_instance..first_instance + instance_count,
);
}
RenderCommand::MultiDrawIndirect {
buffer_id,
offset,
count: None,
indexed: false,
} => {
let &(ref buffer, _) = buffer_guard
.get(buffer_id)
.unwrap()
.raw
.as_ref()
.ok_or(ExecutionError::DestroyedBuffer(buffer_id))?;
cmd_buf.draw_indirect(buffer, offset, 1, 0);
}
RenderCommand::MultiDrawIndirect {
buffer_id,
offset,
count: None,
indexed: true,
} => {
let &(ref buffer, _) = buffer_guard
.get(buffer_id)
.unwrap()
.raw
.as_ref()
.ok_or(ExecutionError::DestroyedBuffer(buffer_id))?;
cmd_buf.draw_indexed_indirect(buffer, offset, 1, 0);
}
RenderCommand::MultiDrawIndirect { .. }
| RenderCommand::MultiDrawIndirectCount { .. } => unimplemented!(),
RenderCommand::PushDebugGroup { color: _, len: _ } => unimplemented!(),
RenderCommand::InsertDebugMarker { color: _, len: _ } => unimplemented!(),
RenderCommand::PopDebugGroup => unimplemented!(),
RenderCommand::WriteTimestamp { .. }
| RenderCommand::BeginPipelineStatisticsQuery { .. }
| RenderCommand::EndPipelineStatisticsQuery => unimplemented!(),
RenderCommand::ExecuteBundle(_)
| RenderCommand::SetBlendColor(_)
| RenderCommand::SetStencilReference(_)
| RenderCommand::SetViewport { .. }
| RenderCommand::SetScissor(_) => unreachable!(),
}
}
if let Some(_) = self.base.label {
cmd_buf.end_debug_marker();
}
Ok(())
}
}
impl Resource for RenderBundle {
const TYPE: &'static str = "RenderBundle";
fn life_guard(&self) -> &LifeGuard {
&self.life_guard
}
}
#[derive(Debug)]
struct IndexState {
buffer: Option<id::BufferId>,
format: wgt::IndexFormat,
pipeline_format: Option<wgt::IndexFormat>,
range: Range<wgt::BufferAddress>,
is_dirty: bool,
}
impl IndexState {
fn new() -> Self {
Self {
buffer: None,
format: wgt::IndexFormat::default(),
pipeline_format: None,
range: 0..0,
is_dirty: false,
}
}
fn limit(&self) -> u32 {
assert!(self.buffer.is_some());
let bytes_per_index = match self.format {
wgt::IndexFormat::Uint16 => 2,
wgt::IndexFormat::Uint32 => 4,
};
((self.range.end - self.range.start) / bytes_per_index) as u32
}
fn flush(&mut self) -> Option<RenderCommand> {
if self.is_dirty {
self.is_dirty = false;
Some(RenderCommand::SetIndexBuffer {
buffer_id: self.buffer.unwrap(),
index_format: self.format,
offset: self.range.start,
size: wgt::BufferSize::new(self.range.end - self.range.start),
})
} else {
None
}
}
fn set_format(&mut self, format: wgt::IndexFormat) {
if self.format != format {
self.format = format;
self.is_dirty = true;
}
}
fn set_buffer(&mut self, id: id::BufferId, range: Range<wgt::BufferAddress>) {
self.buffer = Some(id);
self.range = range;
self.is_dirty = true;
}
}
#[derive(Debug)]
struct VertexState {
buffer: Option<id::BufferId>,
range: Range<wgt::BufferAddress>,
stride: wgt::BufferAddress,
rate: wgt::InputStepMode,
is_dirty: bool,
}
impl VertexState {
fn new() -> Self {
Self {
buffer: None,
range: 0..0,
stride: 0,
rate: wgt::InputStepMode::Vertex,
is_dirty: false,
}
}
fn set_buffer(&mut self, buffer_id: id::BufferId, range: Range<wgt::BufferAddress>) {
self.buffer = Some(buffer_id);
self.range = range;
self.is_dirty = true;
}
fn flush(&mut self, slot: u32) -> Option<RenderCommand> {
if self.is_dirty {
self.is_dirty = false;
Some(RenderCommand::SetVertexBuffer {
slot,
buffer_id: self.buffer.unwrap(),
offset: self.range.start,
size: wgt::BufferSize::new(self.range.end - self.range.start),
})
} else {
None
}
}
}
#[derive(Debug)]
struct BindState {
bind_group: Option<(id::BindGroupId, id::BindGroupLayoutId)>,
dynamic_offsets: Range<usize>,
is_dirty: bool,
}
impl BindState {
fn new() -> Self {
Self {
bind_group: None,
dynamic_offsets: 0..0,
is_dirty: false,
}
}
fn set_group(
&mut self,
bind_group_id: id::BindGroupId,
layout_id: id::BindGroupLayoutId,
dyn_offset: usize,
dyn_count: usize,
) -> bool {
match self.bind_group {
Some((bg_id, _)) if bg_id == bind_group_id && dyn_count == 0 => false,
_ => {
self.bind_group = Some((bind_group_id, layout_id));
self.dynamic_offsets = dyn_offset..dyn_offset + dyn_count;
self.is_dirty = true;
true
}
}
}
}
#[derive(Debug)]
struct PushConstantState {
ranges: ArrayVec<[wgt::PushConstantRange; SHADER_STAGE_COUNT]>,
is_dirty: bool,
}
impl PushConstantState {
fn new() -> Self {
Self {
ranges: ArrayVec::new(),
is_dirty: false,
}
}
fn set_push_constants(&mut self, new_ranges: &[wgt::PushConstantRange]) -> bool {
if &*self.ranges != new_ranges {
self.ranges = new_ranges.iter().cloned().collect();
self.is_dirty = true;
true
} else {
false
}
}
}
#[derive(Debug)]
struct VertexLimitState {
vertex_limit: u32,
vertex_limit_slot: u32,
instance_limit: u32,
instance_limit_slot: u32,
}
#[derive(Debug)]
struct State {
trackers: TrackerSet,
index: IndexState,
vertex: ArrayVec<[VertexState; MAX_VERTEX_BUFFERS]>,
bind: ArrayVec<[BindState; MAX_BIND_GROUPS]>,
push_constant_ranges: PushConstantState,
raw_dynamic_offsets: Vec<wgt::DynamicOffset>,
flat_dynamic_offsets: Vec<wgt::DynamicOffset>,
used_bind_groups: usize,
pipeline: StateChange<id::RenderPipelineId>,
}
impl State {
fn vertex_limits(&self) -> VertexLimitState {
let mut vert_state = VertexLimitState {
vertex_limit: u32::MAX,
vertex_limit_slot: 0,
instance_limit: u32::MAX,
instance_limit_slot: 0,
};
for (idx, vbs) in self.vertex.iter().enumerate() {
if vbs.stride == 0 {
continue;
}
let limit = ((vbs.range.end - vbs.range.start) / vbs.stride) as u32;
match vbs.rate {
wgt::InputStepMode::Vertex => {
if limit < vert_state.vertex_limit {
vert_state.vertex_limit = limit;
vert_state.vertex_limit_slot = idx as _;
}
}
wgt::InputStepMode::Instance => {
if limit < vert_state.instance_limit {
vert_state.instance_limit = limit;
vert_state.instance_limit_slot = idx as _;
}
}
}
}
vert_state
}
fn invalidate_group_from(&mut self, slot: usize) {
for bind in self.bind[slot..].iter_mut() {
if bind.bind_group.is_some() {
bind.is_dirty = true;
}
}
}
fn set_bind_group(
&mut self,
slot: u8,
bind_group_id: id::BindGroupId,
layout_id: id::Valid<id::BindGroupLayoutId>,
offsets: &[wgt::DynamicOffset],
) {
if self.bind[slot as usize].set_group(
bind_group_id,
layout_id.0,
self.raw_dynamic_offsets.len(),
offsets.len(),
) {
self.invalidate_group_from(slot as usize + 1);
}
self.raw_dynamic_offsets.extend(offsets);
}
fn set_pipeline(
&mut self,
index_format: Option<wgt::IndexFormat>,
vertex_strides: &[(wgt::BufferAddress, wgt::InputStepMode)],
layout_ids: &[id::Valid<id::BindGroupLayoutId>],
push_constant_layouts: &[wgt::PushConstantRange],
) {
self.index.pipeline_format = index_format;
for (vs, &(stride, step_mode)) in self.vertex.iter_mut().zip(vertex_strides) {
if vs.stride != stride || vs.rate != step_mode {
vs.stride = stride;
vs.rate = step_mode;
vs.is_dirty = true;
}
}
let push_constants_changed = self
.push_constant_ranges
.set_push_constants(push_constant_layouts);
self.used_bind_groups = layout_ids.len();
let invalid_from = if push_constants_changed {
Some(0)
} else {
self.bind
.iter()
.zip(layout_ids)
.position(|(bs, layout_id)| match bs.bind_group {
Some((_, bgl_id)) => bgl_id != layout_id.0,
None => false,
})
};
if let Some(slot) = invalid_from {
self.invalidate_group_from(slot);
}
}
fn flush_push_constants(&mut self) -> Option<impl Iterator<Item = RenderCommand>> {
let is_dirty = self.push_constant_ranges.is_dirty;
if is_dirty {
let nonoverlapping_ranges =
super::bind::compute_nonoverlapping_ranges(&self.push_constant_ranges.ranges);
Some(
nonoverlapping_ranges
.into_iter()
.map(|range| RenderCommand::SetPushConstant {
stages: range.stages,
offset: range.range.start,
size_bytes: range.range.end - range.range.start,
values_offset: None,
}),
)
} else {
None
}
}
fn flush_vertices(&mut self) -> impl Iterator<Item = RenderCommand> + '_ {
self.vertex
.iter_mut()
.enumerate()
.flat_map(|(i, vs)| vs.flush(i as u32))
}
fn flush_binds(&mut self) -> impl Iterator<Item = RenderCommand> + '_ {
for bs in self.bind[..self.used_bind_groups].iter() {
if bs.is_dirty {
self.flat_dynamic_offsets
.extend_from_slice(&self.raw_dynamic_offsets[bs.dynamic_offsets.clone()]);
}
}
self.bind
.iter_mut()
.take(self.used_bind_groups)
.enumerate()
.flat_map(|(i, bs)| {
if bs.is_dirty {
bs.is_dirty = false;
Some(RenderCommand::SetBindGroup {
index: i as u8,
bind_group_id: bs.bind_group.unwrap().0,
num_dynamic_offsets: (bs.dynamic_offsets.end - bs.dynamic_offsets.start)
as u8,
})
} else {
None
}
})
}
}
#[derive(Clone, Debug, Error)]
pub(super) enum RenderBundleErrorInner {
#[error(transparent)]
Device(#[from] DeviceError),
#[error(transparent)]
RenderCommand(RenderCommandError),
#[error(transparent)]
ResourceUsageConflict(#[from] UsageConflict),
#[error(transparent)]
Draw(#[from] DrawError),
}
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(crate) const INVALID_DEVICE: Self = RenderBundleError {
scope: PassErrorScope::Bundle,
inner: RenderBundleErrorInner::Device(DeviceError::Invalid),
};
}
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, span, RawString};
use std::{convert::TryInto, slice};
use wgt::{BufferAddress, BufferSize, DynamicOffset};
#[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,
) {
span!(_guard, DEBUG, "RenderBundle::set_bind_group");
bundle.base.commands.push(RenderCommand::SetBindGroup {
index: index.try_into().unwrap(),
num_dynamic_offsets: offset_length.try_into().unwrap(),
bind_group_id,
});
if offset_length != 0 {
bundle
.base
.dynamic_offsets
.extend_from_slice(slice::from_raw_parts(offsets, offset_length));
}
}
#[no_mangle]
pub extern "C" fn wgpu_render_bundle_set_pipeline(
bundle: &mut RenderBundleEncoder,
pipeline_id: id::RenderPipelineId,
) {
span!(_guard, DEBUG, "RenderBundle::set_pipeline");
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>,
) {
span!(_guard, DEBUG, "RenderBundle::set_vertex_buffer");
bundle.base.commands.push(RenderCommand::SetVertexBuffer {
slot,
buffer_id,
offset,
size,
});
}
#[no_mangle]
pub unsafe extern "C" fn wgpu_render_bundle_set_push_constants(
pass: &mut RenderBundleEncoder,
stages: wgt::ShaderStage,
offset: u32,
size_bytes: u32,
data: *const u8,
) {
span!(_guard, DEBUG, "RenderBundle::set_push_constants");
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 = 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,
) {
span!(_guard, DEBUG, "RenderBundle::draw");
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,
) {
span!(_guard, DEBUG, "RenderBundle::draw_indexed");
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,
) {
span!(_guard, DEBUG, "RenderBundle::draw_indirect");
bundle.base.commands.push(RenderCommand::MultiDrawIndirect {
buffer_id,
offset,
count: None,
indexed: false,
});
}
#[no_mangle]
pub extern "C" fn wgpu_render_pass_bundle_indexed_indirect(
bundle: &mut RenderBundleEncoder,
buffer_id: id::BufferId,
offset: BufferAddress,
) {
span!(_guard, DEBUG, "RenderBundle::draw_indexed_indirect");
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,
) {
span!(_guard, DEBUG, "RenderBundle::push_debug_group");
}
#[no_mangle]
pub unsafe extern "C" fn wgpu_render_bundle_pop_debug_group(_bundle: &mut RenderBundleEncoder) {
span!(_guard, DEBUG, "RenderBundle::pop_debug_group");
}
#[no_mangle]
pub unsafe extern "C" fn wgpu_render_bundle_insert_debug_marker(
_bundle: &mut RenderBundleEncoder,
_label: RawString,
) {
span!(_guard, DEBUG, "RenderBundle::insert_debug_marker");
}
}