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:
@@ -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(),
|
||||
|
||||
@@ -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
@@ -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 });
|
||||
|
||||
Reference in New Issue
Block a user