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)
|
||||
|
||||
@@ -85,7 +85,7 @@ pub struct PipelineDeclarations {
|
||||
pub compute: Vec<ComputePipeline>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RenderPipeline {
|
||||
pub id: String,
|
||||
@@ -102,7 +102,7 @@ pub struct RenderPipeline {
|
||||
pub multisample: Value,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
#[derive(Clone, Deserialize, PartialEq, Eq)]
|
||||
pub struct ComputePipeline {
|
||||
pub id: String,
|
||||
pub code: String,
|
||||
@@ -110,7 +110,7 @@ pub struct ComputePipeline {
|
||||
pub entry: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
#[derive(Clone, Deserialize, PartialEq, Eq)]
|
||||
pub struct VertexStage {
|
||||
#[serde(default = "vertex_entry")]
|
||||
pub entry: String,
|
||||
@@ -127,7 +127,7 @@ impl Default for VertexStage {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Deserialize)]
|
||||
#[derive(Clone, Default, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct VertexBuffer {
|
||||
pub array_stride: u64,
|
||||
@@ -137,7 +137,7 @@ pub struct VertexBuffer {
|
||||
pub attributes: Vec<VertexAttribute>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
#[derive(Clone, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct VertexAttribute {
|
||||
pub format: String,
|
||||
@@ -145,7 +145,7 @@ pub struct VertexAttribute {
|
||||
pub shader_location: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
pub struct FragmentStage {
|
||||
#[serde(default = "fragment_entry")]
|
||||
pub entry: String,
|
||||
@@ -162,7 +162,7 @@ impl Default for FragmentStage {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FragmentTarget {
|
||||
pub format: String,
|
||||
@@ -519,7 +519,7 @@ impl RenderGraph {
|
||||
.resources
|
||||
.buffers
|
||||
.iter()
|
||||
.any(|buffer| !matches!(buffer.sync.as_str(), "frame" | "loadout"))
|
||||
.any(|buffer| !matches!(buffer.sync.as_str(), "frame" | "manual"))
|
||||
{
|
||||
return Err("GRAPH_BUFFER_SYNC");
|
||||
}
|
||||
@@ -561,6 +561,24 @@ impl Pass {
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderPipeline {
|
||||
/// Same GPU pipeline regardless of declaration ID.
|
||||
pub(crate) fn same_as(&self, other: &Self) -> bool {
|
||||
self.code == other.code
|
||||
&& self.vertex == other.vertex
|
||||
&& self.fragment == other.fragment
|
||||
&& self.primitive == other.primitive
|
||||
&& self.depth_stencil == other.depth_stencil
|
||||
&& self.multisample == other.multisample
|
||||
}
|
||||
}
|
||||
|
||||
impl ComputePipeline {
|
||||
pub(crate) fn same_as(&self, other: &Self) -> bool {
|
||||
self.code == other.code && self.entry == other.entry
|
||||
}
|
||||
}
|
||||
|
||||
impl Texture {
|
||||
pub(crate) fn key(&self) -> Result<String, &'static str> {
|
||||
let mut usage = self.usage.clone();
|
||||
|
||||
+16
-12
@@ -26,12 +26,7 @@ impl Store {
|
||||
|
||||
pub fn switch(&mut self, id: &str, gpu: &Wgpu, data: &RenderData) -> Result<(), String> {
|
||||
let graph = self.graphs.get(id).ok_or("GRAPH_UNKNOWN")?.clone();
|
||||
let mut resources = GpuResources::activate(
|
||||
&graph,
|
||||
gpu,
|
||||
data,
|
||||
self.active.as_ref().map(|active| &active.resources),
|
||||
)?;
|
||||
let mut resources = GpuResources::activate(&graph, gpu, data, self.active.as_ref())?;
|
||||
for (name, levels) in &self.texture_sources {
|
||||
for (mip_level, image) in levels {
|
||||
if resources.needs_upload(name, *mip_level) {
|
||||
@@ -72,6 +67,20 @@ impl Store {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sync_rows(
|
||||
&self,
|
||||
name: &str,
|
||||
first: u32,
|
||||
count: u32,
|
||||
gpu: &Wgpu,
|
||||
data: &RenderData,
|
||||
) -> Result<(), String> {
|
||||
match &self.active {
|
||||
Some(active) => active.resources.sync_rows(name, first, count, gpu, data),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn active_mut(&mut self) -> Option<&mut Loadout> {
|
||||
self.active.as_mut()
|
||||
}
|
||||
@@ -104,12 +113,7 @@ impl Store {
|
||||
return Ok(());
|
||||
};
|
||||
let graph = active.graph.clone();
|
||||
let mut resources = GpuResources::activate(
|
||||
&graph,
|
||||
gpu,
|
||||
data,
|
||||
self.active.as_ref().map(|active| &active.resources),
|
||||
)?;
|
||||
let mut resources = GpuResources::activate(&graph, gpu, data, self.active.as_ref())?;
|
||||
for (name, levels) in &self.texture_sources {
|
||||
for (mip_level, image) in levels {
|
||||
if resources.needs_upload(name, *mip_level) {
|
||||
|
||||
Reference in New Issue
Block a user