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:
Amp
2026-09-18 05:53:44 +00:00
co-authored by heaust
parent 49c80a209e
commit 31d3ae21ff
15 changed files with 185 additions and 48 deletions
+90 -14
View File
@@ -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)