core: manual buffer sync with sync-rows, reuse unchanged buffers and pipelines across activations
Also lets the screenshot suite point at another reference directory and settle longer. 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:
@@ -9,6 +9,7 @@ use serde_json::Value;
|
||||
use crate::gpu::Wgpu;
|
||||
use crate::graph::{Binding, Execution, Extent, Pass, RenderGraph, RenderPipeline, Texture};
|
||||
use crate::render_data::RenderData;
|
||||
use crate::store::Loadout;
|
||||
|
||||
pub struct GpuResources {
|
||||
pub buffers: HashMap<String, GpuBuffer>,
|
||||
@@ -38,6 +39,7 @@ pub struct ProfileMap {
|
||||
pub struct GpuBuffer {
|
||||
pub buffer: wgpu::Buffer,
|
||||
pub source: String,
|
||||
pub usage: wgpu::BufferUsages,
|
||||
pub sync_each_frame: bool,
|
||||
}
|
||||
|
||||
@@ -65,28 +67,48 @@ pub enum GpuPass {
|
||||
}
|
||||
|
||||
impl GpuResources {
|
||||
/// Builds the GPU side of `graph`. Buffers whose source array, size, and usage are unchanged
|
||||
/// and pipelines whose declaration is unchanged are taken over from `previous` instead of being
|
||||
/// recreated and re-uploaded; bind groups and bundles are always rebuilt.
|
||||
pub fn activate(
|
||||
graph: &RenderGraph,
|
||||
gpu: &Wgpu,
|
||||
data: &RenderData,
|
||||
previous: Option<&Self>,
|
||||
previous: Option<&Loadout>,
|
||||
) -> Result<Self, String> {
|
||||
let mut buffers = HashMap::new();
|
||||
for source in &graph.resources.buffers {
|
||||
let rows = data.rows(&source.array).ok_or("GRAPH_ARRAY_UNKNOWN")?;
|
||||
let buffer = gpu.device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some(&source.id),
|
||||
size: u64::from(rows.bytes.max(4)),
|
||||
usage: buffer_usage(&source.usage)?,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
gpu.queue
|
||||
.write_buffer(&buffer, 0, data.bytes(&source.array).unwrap());
|
||||
let size = u64::from(rows.bytes.max(4));
|
||||
let usage = buffer_usage(&source.usage)?;
|
||||
let reused = previous
|
||||
.and_then(|previous| previous.resources.buffers.get(&source.id))
|
||||
.filter(|buffer| {
|
||||
buffer.source == source.array
|
||||
&& buffer.usage == usage
|
||||
&& buffer.buffer.size() == size
|
||||
})
|
||||
.map(|buffer| buffer.buffer.clone());
|
||||
let buffer = match reused {
|
||||
Some(buffer) => buffer,
|
||||
None => {
|
||||
let buffer = gpu.device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some(&source.id),
|
||||
size,
|
||||
usage,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
gpu.queue
|
||||
.write_buffer(&buffer, 0, data.bytes(&source.array).unwrap());
|
||||
buffer
|
||||
}
|
||||
};
|
||||
buffers.insert(
|
||||
source.id.clone(),
|
||||
GpuBuffer {
|
||||
buffer,
|
||||
source: source.array.clone(),
|
||||
usage,
|
||||
sync_each_frame: source.sync == "frame",
|
||||
},
|
||||
);
|
||||
@@ -102,11 +124,12 @@ impl GpuResources {
|
||||
let key = source.key()?;
|
||||
if !source.transient {
|
||||
if let Some(texture) = previous
|
||||
.and_then(|resources| {
|
||||
resources
|
||||
.and_then(|previous| {
|
||||
previous
|
||||
.resources
|
||||
.texture_slots
|
||||
.get(&source.id)
|
||||
.map(|slot| &resources.textures[*slot])
|
||||
.map(|slot| &previous.resources.textures[*slot])
|
||||
})
|
||||
.filter(|texture| texture.key == key)
|
||||
{
|
||||
@@ -147,7 +170,23 @@ impl GpuResources {
|
||||
.render
|
||||
.iter()
|
||||
.map(|source| {
|
||||
create_render_pipeline(source, gpu).map(|pipeline| (source.id.clone(), pipeline))
|
||||
let reused = previous.and_then(|previous| {
|
||||
previous
|
||||
.graph
|
||||
.pipelines
|
||||
.render
|
||||
.iter()
|
||||
.find(|candidate| candidate.same_as(source))
|
||||
.and_then(|candidate| {
|
||||
previous.resources.render_pipelines.get(&candidate.id)
|
||||
})
|
||||
.cloned()
|
||||
});
|
||||
match reused {
|
||||
Some(pipeline) => Ok(pipeline),
|
||||
None => create_render_pipeline(source, gpu),
|
||||
}
|
||||
.map(|pipeline| (source.id.clone(), pipeline))
|
||||
})
|
||||
.collect::<Result<HashMap<_, _>, _>>()?;
|
||||
let compute_pipelines = graph
|
||||
@@ -155,6 +194,21 @@ impl GpuResources {
|
||||
.compute
|
||||
.iter()
|
||||
.map(|source| {
|
||||
let reused = previous.and_then(|previous| {
|
||||
previous
|
||||
.graph
|
||||
.pipelines
|
||||
.compute
|
||||
.iter()
|
||||
.find(|candidate| candidate.same_as(source))
|
||||
.and_then(|candidate| {
|
||||
previous.resources.compute_pipelines.get(&candidate.id)
|
||||
})
|
||||
.cloned()
|
||||
});
|
||||
if let Some(pipeline) = reused {
|
||||
return Ok::<_, String>((source.id.clone(), pipeline));
|
||||
}
|
||||
let module = gpu
|
||||
.device
|
||||
.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
@@ -171,7 +225,7 @@ impl GpuResources {
|
||||
compilation_options: Default::default(),
|
||||
cache: None,
|
||||
});
|
||||
Ok::<_, String>((source.id.clone(), pipeline))
|
||||
Ok((source.id.clone(), pipeline))
|
||||
})
|
||||
.collect::<Result<HashMap<_, _>, _>>()?;
|
||||
|
||||
@@ -251,6 +305,28 @@ impl GpuResources {
|
||||
Ok(resources)
|
||||
}
|
||||
|
||||
/// Copies rows `first .. first + count` of a source array into every GPU buffer bound to it.
|
||||
pub fn sync_rows(
|
||||
&self,
|
||||
name: &str,
|
||||
first: u32,
|
||||
count: u32,
|
||||
gpu: &Wgpu,
|
||||
data: &RenderData,
|
||||
) -> Result<(), String> {
|
||||
let rows = data.rows(name).ok_or("ROWS_UNKNOWN")?;
|
||||
let end = first
|
||||
.checked_add(count)
|
||||
.filter(|end| *end <= rows.rows)
|
||||
.ok_or("ROW_RANGE")?;
|
||||
let start = (first * rows.stride) as usize;
|
||||
let bytes = &data.bytes(name).unwrap()[start..(end * rows.stride) as usize];
|
||||
for buffer in self.buffers.values().filter(|buffer| buffer.source == name) {
|
||||
gpu.queue.write_buffer(&buffer.buffer, start as u64, bytes);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn texture_view(&self, id: &str) -> Option<&wgpu::TextureView> {
|
||||
self.texture_slots
|
||||
.get(id)
|
||||
|
||||
Reference in New Issue
Block a user