Optimize forward rendering and add benchmark

Amp-Thread-ID: https://ampcode.com/threads/T-01a01380-b478-77d0-84a0-102880a5c5ae
Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
Amp
2026-08-20 14:36:51 +00:00
co-authored by heaust
parent 000bfd63bf
commit abfa428464
18 changed files with 390 additions and 97 deletions
+65 -30
View File
@@ -1,4 +1,4 @@
use std::collections::{BTreeMap, HashMap};
use std::collections::{BTreeMap, HashMap, HashSet};
use std::num::NonZeroU64;
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::Arc;
@@ -46,7 +46,7 @@ pub struct GpuTexture {
pub texture: wgpu::Texture,
pub view: wgpu::TextureView,
key: String,
uploaded: bool,
uploaded_mips: HashSet<u32>,
}
pub enum GpuPass {
@@ -124,7 +124,7 @@ impl GpuResources {
view: texture.create_view(&wgpu::TextureViewDescriptor::default()),
texture,
key,
uploaded: false,
uploaded_mips: HashSet::new(),
});
physical_slots.insert(source.slot, physical);
physical
@@ -211,21 +211,29 @@ impl GpuResources {
};
for execution in &graph.executions {
let compiled = match execution {
Execution::Render(passes) => 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)", passes.len())
} else {
format!("Render ({} draws)", passes.len())
},
first: passes[0],
last: *passes.last().unwrap(),
bundle: resources.render_bundle(graph, passes, gpu)?,
},
Execution::Render(passes) => {
let (bundle, draws) = resources.render_bundle(graph, passes, gpu)?;
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)")
} else {
format!("Render ({draws} draws)")
},
first: passes[0],
last: *passes.last().unwrap(),
bundle,
}
}
Execution::Compute(index) => {
let pass = &graph.passes[*index];
let pipeline = resources
@@ -261,6 +269,7 @@ impl GpuResources {
pub fn upload_texture(
&mut self,
id: &str,
mip_level: u32,
image: &web_sys::ImageBitmap,
gpu: &Wgpu,
) -> Result<(), String> {
@@ -277,7 +286,7 @@ impl GpuResources {
},
wgpu::TexelCopyTextureInfo {
texture: &texture.texture,
mip_level: 0,
mip_level,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
}
@@ -288,15 +297,15 @@ impl GpuResources {
depth_or_array_layers: 1,
},
);
texture.uploaded = true;
texture.uploaded_mips.insert(mip_level);
Ok(())
}
pub fn needs_upload(&self, id: &str) -> bool {
pub fn needs_upload(&self, id: &str, mip_level: u32) -> bool {
self.texture_slots
.get(id)
.and_then(|slot| self.textures.get(*slot))
.is_some_and(|texture| !texture.uploaded)
.is_some_and(|texture| !texture.uploaded_mips.contains(&mip_level))
}
pub fn render_timestamps(&self, pass: usize) -> Option<wgpu::RenderPassTimestampWrites<'_>> {
@@ -396,7 +405,7 @@ impl GpuResources {
graph: &RenderGraph,
passes: &[usize],
gpu: &Wgpu,
) -> Result<wgpu::RenderBundle, String> {
) -> Result<(wgpu::RenderBundle, usize), String> {
let pass = &graph.passes[passes[0]];
let declaration = graph
.pipelines
@@ -431,8 +440,10 @@ impl GpuResources {
});
let mut previous_pipeline = None;
let mut previous_bindings: Option<&[Binding]> = None;
for index in passes {
let pass = &graph.passes[*index];
let mut draws = 0;
let mut at = 0;
while at < passes.len() {
let pass = &graph.passes[passes[at]];
let pipeline = self
.render_pipelines
.get(&pass.pipeline)
@@ -468,10 +479,17 @@ 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 + pass.draw.instances,
pass.draw.first_instance..pass.draw.first_instance + instances,
);
} else {
encoder.draw(
@@ -479,10 +497,15 @@ impl GpuResources {
pass.draw.first_instance..pass.draw.first_instance + pass.draw.instances,
);
}
draws += 1;
at += 1;
}
Ok(encoder.finish(&wgpu::RenderBundleDescriptor {
label: Some(&pass.id),
}))
Ok((
encoder.finish(&wgpu::RenderBundleDescriptor {
label: Some(&pass.id),
}),
draws,
))
}
fn bind_groups(
@@ -531,6 +554,18 @@ 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 {
@@ -621,7 +656,7 @@ fn create_render_pipeline(
primitive: primitive(&source.primitive)?,
depth_stencil: depth_stencil(&source.depth_stencil)?,
multisample: multisample(&source.multisample)?,
fragment: Some(wgpu::FragmentState {
fragment: (!targets.is_empty()).then_some(wgpu::FragmentState {
module: &module,
entry_point: Some(&source.fragment.entry),
compilation_options: Default::default(),
+3 -3
View File
@@ -229,7 +229,7 @@ pub struct DepthAttachment {
pub store: String,
}
#[derive(Clone, Deserialize)]
#[derive(Clone, Deserialize, PartialEq, Eq)]
pub struct VertexBinding {
#[serde(default)]
pub slot: u32,
@@ -238,7 +238,7 @@ pub struct VertexBinding {
pub offset: u64,
}
#[derive(Clone, Deserialize)]
#[derive(Clone, Deserialize, PartialEq, Eq)]
pub struct IndexBinding {
pub resource: String,
#[serde(default = "index_format")]
@@ -247,7 +247,7 @@ pub struct IndexBinding {
pub offset: u64,
}
#[derive(Clone, Deserialize)]
#[derive(Clone, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Draw {
#[serde(default = "three")]
+21 -11
View File
@@ -13,7 +13,7 @@ pub struct Loadout {
#[derive(Default)]
pub struct Store {
graphs: HashMap<String, RenderGraph>,
texture_sources: HashMap<String, web_sys::ImageBitmap>,
texture_sources: HashMap<String, HashMap<u32, web_sys::ImageBitmap>>,
active: Option<Loadout>,
}
@@ -32,9 +32,11 @@ impl Store {
data,
self.active.as_ref().map(|active| &active.resources),
)?;
for (name, image) in &self.texture_sources {
if resources.needs_upload(name) {
resources.upload_texture(name, image, gpu)?;
for (name, levels) in &self.texture_sources {
for (mip_level, image) in levels {
if resources.needs_upload(name, *mip_level) {
resources.upload_texture(name, *mip_level, image, gpu)?;
}
}
}
self.active = Some(Loadout { graph, resources });
@@ -44,23 +46,29 @@ impl Store {
pub fn upload_texture(
&mut self,
name: String,
mip_level: u32,
image: web_sys::ImageBitmap,
gpu: &Wgpu,
) -> Result<(), String> {
if let Some(active) = &mut self.active {
if active.resources.texture_slots.contains_key(&name) {
active.resources.upload_texture(&name, &image, gpu)?;
active
.resources
.upload_texture(&name, mip_level, &image, gpu)?;
}
}
if let Some(previous) = self.texture_sources.insert(name, image) {
let levels = self.texture_sources.entry(name).or_default();
if let Some(previous) = levels.insert(mip_level, image) {
previous.close();
}
Ok(())
}
pub fn delete_texture(&mut self, name: &str) {
if let Some(image) = self.texture_sources.remove(name) {
image.close();
if let Some(levels) = self.texture_sources.remove(name) {
for image in levels.into_values() {
image.close();
}
}
}
@@ -102,9 +110,11 @@ impl Store {
data,
self.active.as_ref().map(|active| &active.resources),
)?;
for (name, image) in &self.texture_sources {
if resources.needs_upload(name) {
resources.upload_texture(name, image, gpu)?;
for (name, levels) in &self.texture_sources {
for (mip_level, image) in levels {
if resources.needs_upload(name, *mip_level) {
resources.upload_texture(name, *mip_level, image, gpu)?;
}
}
}
self.active = Some(Loadout { graph, resources });