core: indirect draw passes, device feature list, drop handles-specific bundle heuristics

Amp-Thread-ID: https://ampcode.com/threads/T-01a0b2dd-db32-712d-99ce-12ce4a86d979
Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
Amp
2026-09-18 05:31:11 +00:00
co-authored by heaust
parent dda602c4a2
commit 49c80a209e
11 changed files with 123 additions and 56 deletions
+54 -47
View File
@@ -212,22 +212,14 @@ impl GpuResources {
for execution in &graph.executions {
let compiled = match execution {
Execution::Render(passes) => {
let (bundle, draws) = resources.render_bundle(graph, passes, gpu)?;
let (bundle, draws) = resources.render_bundle(graph, passes, gpu, data)?;
let first = &graph.passes[passes[0]].id;
let last = &graph.passes[*passes.last().unwrap()].id;
GpuPass::Render {
label: if passes.len() == 1 {
graph.passes[passes[0]].id.clone()
} else if passes
.iter()
.all(|index| graph.passes[*index].id.starts_with("forward-"))
{
format!("Forward ({draws} draws)")
} else if passes
.iter()
.all(|index| graph.passes[*index].id.starts_with("depth-"))
{
format!("Depth ({draws} draws)")
format!("{first} ({draws} draws)")
} else {
format!("Render ({draws} draws)")
format!("{first} … {last} ({draws} draws)")
},
first: passes[0],
last: *passes.last().unwrap(),
@@ -405,6 +397,7 @@ impl GpuResources {
graph: &RenderGraph,
passes: &[usize],
gpu: &Wgpu,
data: &RenderData,
) -> Result<(wgpu::RenderBundle, usize), String> {
let pass = &graph.passes[passes[0]];
let declaration = graph
@@ -441,9 +434,8 @@ impl GpuResources {
let mut previous_pipeline = None;
let mut previous_bindings: Option<&[Binding]> = None;
let mut draws = 0;
let mut at = 0;
while at < passes.len() {
let pass = &graph.passes[passes[at]];
for &at in passes {
let pass = &graph.passes[at];
let pipeline = self
.render_pipelines
.get(&pass.pipeline)
@@ -469,7 +461,7 @@ impl GpuResources {
.buffer;
encoder.set_vertex_buffer(binding.slot, buffer.slice(binding.offset..));
}
if let Some(binding) = &pass.index_buffer {
let indexed = if let Some(binding) = &pass.index_buffer {
let buffer = &self
.buffers
.get(&binding.resource)
@@ -479,26 +471,53 @@ impl GpuResources {
buffer.slice(binding.offset..),
parse(&binding.format, "GRAPH_INDEX_FORMAT")?,
);
let mut instances = pass.draw.instances;
while at + 1 < passes.len()
&& can_instance(pass, &graph.passes[passes[at + 1]], instances)
{
at += 1;
instances += graph.passes[passes[at]].draw.instances;
}
encoder.draw_indexed(
pass.draw.first_index..pass.draw.first_index + pass.draw.indices,
pass.draw.base_vertex,
pass.draw.first_instance..pass.draw.first_instance + instances,
);
true
} else {
encoder.draw(
pass.draw.first_vertex..pass.draw.first_vertex + pass.draw.vertices,
pass.draw.first_instance..pass.draw.first_instance + pass.draw.instances,
);
false
};
match (&pass.indirect, indexed) {
(Some(indirect), indexed) => {
let buffer = self
.buffers
.get(&indirect.resource)
.ok_or("GRAPH_RESOURCE_UNKNOWN")?;
let rows = data.rows(&buffer.source).ok_or("GRAPH_ARRAY_UNKNOWN")?;
let end = match indirect.count {
Some(count) => indirect.first.checked_add(count),
None => Some(rows.rows),
}
.filter(|end| indirect.first <= *end && *end <= rows.rows)
.ok_or("GRAPH_INDIRECT")?;
if rows.stride < if indexed { 20 } else { 16 } {
return Err("GRAPH_INDIRECT".into());
}
let count = end - indirect.first;
for row in indirect.first..end {
let offset = u64::from(row) * u64::from(rows.stride);
if indexed {
encoder.draw_indexed_indirect(&buffer.buffer, offset);
} else {
encoder.draw_indirect(&buffer.buffer, offset);
}
}
draws += count as usize;
}
(None, true) => {
encoder.draw_indexed(
pass.draw.first_index..pass.draw.first_index + pass.draw.indices,
pass.draw.base_vertex,
pass.draw.first_instance..pass.draw.first_instance + pass.draw.instances,
);
draws += 1;
}
(None, false) => {
encoder.draw(
pass.draw.first_vertex..pass.draw.first_vertex + pass.draw.vertices,
pass.draw.first_instance..pass.draw.first_instance + pass.draw.instances,
);
draws += 1;
}
}
draws += 1;
at += 1;
}
Ok((
encoder.finish(&wgpu::RenderBundleDescriptor {
@@ -554,18 +573,6 @@ impl GpuResources {
}
}
fn can_instance(first: &Pass, next: &Pass, instances: u32) -> bool {
first.pipeline == next.pipeline
&& first.bindings == next.bindings
&& first.vertex_buffers == next.vertex_buffers
&& first.index_buffer == next.index_buffer
&& first.draw.indices != 0
&& first.draw.indices == next.draw.indices
&& first.draw.first_index == next.draw.first_index
&& first.draw.base_vertex == next.draw.base_vertex
&& first.draw.first_instance + instances == next.draw.first_instance
}
impl GpuPass {
fn label(&self) -> &str {
match self {
+25
View File
@@ -192,10 +192,23 @@ pub struct Pass {
pub index_buffer: Option<IndexBinding>,
#[serde(default)]
pub draw: Draw,
pub indirect: Option<IndirectBinding>,
#[serde(default = "dispatch")]
pub dispatch: [u32; 3],
}
/// Draw commands read from rows of an `indirect` buffer: one command per row starting at
/// `first`, `count` rows (default: every row to the end of the array). The command occupies the
/// first 16 bytes (non-indexed) or 20 bytes (indexed) of each row; the remainder of the row is
/// free for the caller.
#[derive(Clone, Deserialize, PartialEq, Eq)]
pub struct IndirectBinding {
pub resource: String,
#[serde(default)]
pub first: u32,
pub count: Option<u32>,
}
#[derive(Clone, Deserialize, PartialEq, Eq)]
pub struct Binding {
#[serde(default)]
@@ -344,6 +357,17 @@ impl RenderGraph {
"compute" => _ = compute_pipelines.insert(pass.pipeline.clone()),
_ => return Err("GRAPH_PASS"),
}
if let Some(indirect) = &pass.indirect {
let usable = self
.resources
.buffers
.iter()
.find(|buffer| buffer.id == indirect.resource)
.is_some_and(|buffer| buffer.usage.iter().any(|usage| usage == "indirect"));
if !usable || pass.draw != Draw::default() {
return Err("GRAPH_INDIRECT");
}
}
for id in pass.resources() {
used.insert(id.to_owned());
lifetimes
@@ -532,6 +556,7 @@ impl Pass {
.iter()
.map(|value| value.resource.as_str()),
)
.chain(self.indirect.iter().map(|value| value.resource.as_str()))
.collect()
}
}