custom resource descriptors for graph
This commit is contained in:
parent
d367b4d05b
commit
5f7bbba49d
|
|
@ -5,5 +5,7 @@ edition = "2024"
|
|||
|
||||
[dependencies]
|
||||
ash.workspace = true
|
||||
gpu-allocator.workspace = true
|
||||
renderer_device = { path = "../renderer_device" }
|
||||
renderer_resources = { path = "../renderer_resources" }
|
||||
ash-formats = { git = "https://github.com/janis-bhm/ash-formats", branch = "main" }
|
||||
|
|
|
|||
|
|
@ -6,14 +6,17 @@ use std::{
|
|||
use ash::vk;
|
||||
use ash_formats::FormatExt;
|
||||
use renderer_resources::{
|
||||
Device, buffers::Buffer as BufferResource, images::Image as ImageResource,
|
||||
Device,
|
||||
buffers::{Buffer as BufferResource, BufferDesc},
|
||||
device::{AllocationStrategy, QueueFlags},
|
||||
images::{Image as ImageResource, ImageDesc},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
recorder::{CommandIndex, CommandList, CommandRecorder, RecordError, scope_range},
|
||||
resources::{
|
||||
Buffer, ResourceDesc, ResourceId, Subresource, SubresourceScope, SubresourceScopeInner,
|
||||
Texture, access_flag_is_write,
|
||||
Buffer, GraphBufferDesc, GraphTextureDesc, ResourceDesc, ResourceId, Subresource,
|
||||
SubresourceScope, SubresourceScopeInner, Texture, access_flag_is_write,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -72,11 +75,11 @@ impl RenderGraph {
|
|||
id
|
||||
}
|
||||
|
||||
pub fn create_buffer(&mut self, desc: renderer_resources::buffers::BufferDesc) -> Buffer {
|
||||
pub fn create_buffer(&mut self, desc: GraphBufferDesc) -> Buffer {
|
||||
Buffer(self.add_resource(ResourceDesc::CreatedBuffer(desc)))
|
||||
}
|
||||
|
||||
pub fn create_texture(&mut self, desc: renderer_resources::images::ImageDesc) -> Texture {
|
||||
pub fn create_texture(&mut self, desc: GraphTextureDesc) -> Texture {
|
||||
Texture(self.add_resource(ResourceDesc::CreatedTexture(desc)))
|
||||
}
|
||||
|
||||
|
|
@ -207,13 +210,51 @@ impl CompiledGraph {
|
|||
if self.resolved.contains_key(&id) {
|
||||
continue;
|
||||
}
|
||||
let accesses = accesses_for_resource(id, self.commands.side_effects.command_accesses());
|
||||
let resource = match desc {
|
||||
ResourceDesc::CreatedBuffer(desc) => ResolvedResource::Buffer(Arc::new(
|
||||
BufferResource::new(device.clone(), desc.clone())?,
|
||||
)),
|
||||
ResourceDesc::CreatedTexture(desc) => ResolvedResource::Image(Arc::new(
|
||||
ImageResource::new(device.clone(), desc.clone())?,
|
||||
)),
|
||||
ResourceDesc::CreatedBuffer(desc) => {
|
||||
let usage = derive_buffer_usage(&accesses, desc.extra_usage);
|
||||
let queue_families =
|
||||
actual_queue_families(device, derive_queue_capabilities(&accesses));
|
||||
let resource_size = desc.size;
|
||||
ResolvedResource::Buffer(Arc::new(BufferResource::new(
|
||||
device.clone(),
|
||||
BufferDesc {
|
||||
flags: vk::BufferCreateFlags::empty(),
|
||||
name: desc.name.clone(),
|
||||
size: desc.size,
|
||||
usage,
|
||||
queue_families,
|
||||
mem_location: desc.mem_location,
|
||||
alloc_scheme: allocation_strategy(resource_size),
|
||||
},
|
||||
)?))
|
||||
}
|
||||
ResourceDesc::CreatedTexture(desc) => {
|
||||
let usage = derive_image_usage(&accesses, desc.extra_usage);
|
||||
let queue_families =
|
||||
actual_queue_families(device, derive_queue_capabilities(&accesses));
|
||||
let resource_size = texture_size_estimate(desc);
|
||||
ResolvedResource::Image(Arc::new(ImageResource::new(
|
||||
device.clone(),
|
||||
ImageDesc {
|
||||
flags: vk::ImageCreateFlags::empty(),
|
||||
name: desc.name.clone(),
|
||||
format: desc.format,
|
||||
kind: desc.kind,
|
||||
mip_levels: desc.mip_levels,
|
||||
array_layers: desc.array_layers,
|
||||
samples: desc.samples,
|
||||
extent: desc.extent,
|
||||
tiling: desc.tiling,
|
||||
usage,
|
||||
queue_families,
|
||||
layout: desc.layout,
|
||||
mem_location: desc.mem_location,
|
||||
alloc_scheme: allocation_strategy(resource_size),
|
||||
},
|
||||
)?))
|
||||
}
|
||||
ResourceDesc::ImportedBuffer { .. } | ResourceDesc::ImportedTexture { .. } => {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -270,6 +311,226 @@ impl CompiledGraph {
|
|||
}
|
||||
}
|
||||
|
||||
/// The skeleton switches to a dedicated allocation at 64 MiB. This is a
|
||||
/// placeholder heuristic until resource lifetime/aliasing allocation is real.
|
||||
const DEDICATED_ALLOCATION_THRESHOLD: u64 = 64 * 1024 * 1024;
|
||||
|
||||
fn allocation_strategy(size: u64) -> AllocationStrategy {
|
||||
if size >= DEDICATED_ALLOCATION_THRESHOLD {
|
||||
AllocationStrategy::Dedicated
|
||||
} else {
|
||||
AllocationStrategy::AllocatorManaged
|
||||
}
|
||||
}
|
||||
|
||||
fn accesses_for_resource(
|
||||
id: ResourceId,
|
||||
command_accesses: &[Vec<crate::recorder::ResourceAccess>],
|
||||
) -> Vec<crate::recorder::ResourceAccess> {
|
||||
command_accesses
|
||||
.iter()
|
||||
.flatten()
|
||||
.filter(|access| access.resource == id)
|
||||
.copied()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Derive buffer usages from declared access masks. Unknown access bits add no
|
||||
/// usage; callers can use `extra_usage` for uses outside the graph's knowledge.
|
||||
pub fn derive_buffer_usage(
|
||||
accesses: &[crate::recorder::ResourceAccess],
|
||||
extra_usage: vk::BufferUsageFlags,
|
||||
) -> vk::BufferUsageFlags {
|
||||
accesses.iter().fold(extra_usage, |usage, access| {
|
||||
let mask = access.scope.access;
|
||||
let mut usage = usage;
|
||||
if mask.intersects(vk::AccessFlags2::TRANSFER_READ) {
|
||||
usage |= vk::BufferUsageFlags::TRANSFER_SRC;
|
||||
}
|
||||
if mask.intersects(vk::AccessFlags2::TRANSFER_WRITE) {
|
||||
usage |= vk::BufferUsageFlags::TRANSFER_DST;
|
||||
}
|
||||
if mask.intersects(
|
||||
vk::AccessFlags2::SHADER_READ
|
||||
| vk::AccessFlags2::SHADER_STORAGE_READ
|
||||
| vk::AccessFlags2::SHADER_STORAGE_WRITE,
|
||||
) {
|
||||
// Shader reads are conservatively treated as storage-buffer reads;
|
||||
// add UNIFORM_BUFFER explicitly when the shader uses uniform data.
|
||||
usage |= vk::BufferUsageFlags::STORAGE_BUFFER;
|
||||
}
|
||||
if mask.intersects(vk::AccessFlags2::UNIFORM_READ) {
|
||||
usage |= vk::BufferUsageFlags::UNIFORM_BUFFER;
|
||||
}
|
||||
if mask.intersects(vk::AccessFlags2::SHADER_WRITE | vk::AccessFlags2::SHADER_STORAGE_WRITE)
|
||||
{
|
||||
usage |= vk::BufferUsageFlags::STORAGE_BUFFER;
|
||||
}
|
||||
if mask.intersects(vk::AccessFlags2::VERTEX_ATTRIBUTE_READ) {
|
||||
usage |= vk::BufferUsageFlags::VERTEX_BUFFER;
|
||||
}
|
||||
if mask.intersects(vk::AccessFlags2::INDEX_READ) {
|
||||
usage |= vk::BufferUsageFlags::INDEX_BUFFER;
|
||||
}
|
||||
if mask.intersects(vk::AccessFlags2::INDIRECT_COMMAND_READ) {
|
||||
usage |= vk::BufferUsageFlags::INDIRECT_BUFFER;
|
||||
}
|
||||
usage
|
||||
})
|
||||
}
|
||||
|
||||
/// Derive image usages from declared access masks. Shader reads require a
|
||||
/// sampled or storage image descriptor, so both flags are enabled; unknown
|
||||
/// access bits add no usage and may be supplied through `extra_usage`.
|
||||
pub fn derive_image_usage(
|
||||
accesses: &[crate::recorder::ResourceAccess],
|
||||
extra_usage: vk::ImageUsageFlags,
|
||||
) -> vk::ImageUsageFlags {
|
||||
accesses.iter().fold(extra_usage, |usage, access| {
|
||||
let mask = access.scope.access;
|
||||
let mut usage = usage;
|
||||
if mask.intersects(vk::AccessFlags2::TRANSFER_READ) {
|
||||
usage |= vk::ImageUsageFlags::TRANSFER_SRC;
|
||||
}
|
||||
if mask.intersects(vk::AccessFlags2::TRANSFER_WRITE) {
|
||||
usage |= vk::ImageUsageFlags::TRANSFER_DST;
|
||||
}
|
||||
if mask.intersects(
|
||||
vk::AccessFlags2::SHADER_READ
|
||||
| vk::AccessFlags2::SHADER_SAMPLED_READ
|
||||
| vk::AccessFlags2::SHADER_STORAGE_READ,
|
||||
) {
|
||||
usage |= vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::STORAGE;
|
||||
}
|
||||
if mask.intersects(vk::AccessFlags2::SHADER_WRITE | vk::AccessFlags2::SHADER_STORAGE_WRITE)
|
||||
{
|
||||
usage |= vk::ImageUsageFlags::STORAGE;
|
||||
}
|
||||
if mask.intersects(vk::AccessFlags2::INPUT_ATTACHMENT_READ) {
|
||||
usage |= vk::ImageUsageFlags::INPUT_ATTACHMENT;
|
||||
}
|
||||
if mask.intersects(
|
||||
vk::AccessFlags2::COLOR_ATTACHMENT_READ | vk::AccessFlags2::COLOR_ATTACHMENT_WRITE,
|
||||
) {
|
||||
usage |= vk::ImageUsageFlags::COLOR_ATTACHMENT;
|
||||
}
|
||||
if mask.intersects(
|
||||
vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_READ
|
||||
| vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_WRITE,
|
||||
) {
|
||||
usage |= vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT;
|
||||
}
|
||||
usage
|
||||
})
|
||||
}
|
||||
|
||||
/// Map pipeline-stage usage to desired queue capabilities. Graphics stages
|
||||
/// imply GRAPHICS, compute stages imply ASYNC_COMPUTE, and copy/clear/transfer
|
||||
/// stages imply TRANSFER. NONE and unrecognized stages contribute nothing.
|
||||
pub fn derive_queue_capabilities(accesses: &[crate::recorder::ResourceAccess]) -> QueueFlags {
|
||||
accesses
|
||||
.iter()
|
||||
.fold(QueueFlags::empty(), |mut flags, access| {
|
||||
let stages = access.scope.stage;
|
||||
if stages.intersects(
|
||||
vk::PipelineStageFlags2::DRAW_INDIRECT
|
||||
| vk::PipelineStageFlags2::VERTEX_INPUT
|
||||
| vk::PipelineStageFlags2::VERTEX_SHADER
|
||||
| vk::PipelineStageFlags2::TESSELLATION_CONTROL_SHADER
|
||||
| vk::PipelineStageFlags2::TESSELLATION_EVALUATION_SHADER
|
||||
| vk::PipelineStageFlags2::GEOMETRY_SHADER
|
||||
| vk::PipelineStageFlags2::FRAGMENT_SHADER
|
||||
| vk::PipelineStageFlags2::EARLY_FRAGMENT_TESTS
|
||||
| vk::PipelineStageFlags2::LATE_FRAGMENT_TESTS
|
||||
| vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT
|
||||
| vk::PipelineStageFlags2::ALL_GRAPHICS
|
||||
| vk::PipelineStageFlags2::PRE_RASTERIZATION_SHADERS,
|
||||
) {
|
||||
flags |= QueueFlags::GRAPHICS;
|
||||
}
|
||||
if stages.intersects(vk::PipelineStageFlags2::COMPUTE_SHADER) {
|
||||
flags |= QueueFlags::ASYNC_COMPUTE;
|
||||
}
|
||||
if stages.intersects(
|
||||
vk::PipelineStageFlags2::TRANSFER
|
||||
| vk::PipelineStageFlags2::COPY
|
||||
| vk::PipelineStageFlags2::CLEAR,
|
||||
) {
|
||||
flags |= QueueFlags::TRANSFER;
|
||||
}
|
||||
flags
|
||||
})
|
||||
}
|
||||
|
||||
/// Convert inferred capabilities into the device's queue-role selectors,
|
||||
/// retaining only roles whose selected family actually supports the inferred
|
||||
/// capability. If no role matches (or nothing was inferred), use graphics.
|
||||
fn actual_queue_families(device: &Device, capabilities: QueueFlags) -> QueueFlags {
|
||||
use renderer_device::queue::QueueFlags as FamilyFlags;
|
||||
|
||||
let mut selected = QueueFlags::empty();
|
||||
if capabilities.contains(QueueFlags::GRAPHICS)
|
||||
&& device
|
||||
.queues
|
||||
.graphics()
|
||||
.family
|
||||
.flags
|
||||
.contains(FamilyFlags::GRAPHICS)
|
||||
{
|
||||
selected |= QueueFlags::GRAPHICS;
|
||||
}
|
||||
if capabilities.contains(QueueFlags::ASYNC_COMPUTE)
|
||||
&& device
|
||||
.queues
|
||||
.compute()
|
||||
.family
|
||||
.flags
|
||||
.contains(FamilyFlags::COMPUTE)
|
||||
{
|
||||
selected |= QueueFlags::ASYNC_COMPUTE;
|
||||
}
|
||||
if capabilities.contains(QueueFlags::TRANSFER)
|
||||
&& device
|
||||
.queues
|
||||
.transfer()
|
||||
.family
|
||||
.flags
|
||||
.contains(FamilyFlags::TRANSFER)
|
||||
{
|
||||
selected |= QueueFlags::TRANSFER;
|
||||
}
|
||||
if selected.is_empty() {
|
||||
QueueFlags::GRAPHICS
|
||||
} else {
|
||||
selected
|
||||
}
|
||||
}
|
||||
|
||||
fn texture_size_estimate(desc: &GraphTextureDesc) -> u64 {
|
||||
let texels_per_block = u64::from(desc.format.texels_per_block()).max(1);
|
||||
let block_size = u64::from(desc.format.block_size());
|
||||
let mut size = 0u64;
|
||||
let (mut width, mut height, mut depth) = (
|
||||
u64::from(desc.extent.width),
|
||||
u64::from(desc.extent.height),
|
||||
u64::from(desc.extent.depth),
|
||||
);
|
||||
for _ in 0..desc.mip_levels {
|
||||
size = size.saturating_add(
|
||||
width
|
||||
.saturating_mul(height)
|
||||
.saturating_mul(depth)
|
||||
.div_ceil(texels_per_block)
|
||||
.saturating_mul(block_size),
|
||||
);
|
||||
width = (width / 2).max(1);
|
||||
height = (height / 2).max(1);
|
||||
depth = (depth / 2).max(1);
|
||||
}
|
||||
size.saturating_mul(u64::from(desc.array_layers))
|
||||
.saturating_mul(u64::from(desc.samples.as_raw().max(1)))
|
||||
}
|
||||
|
||||
fn build_barriers(
|
||||
order: &[CommandIndex],
|
||||
accesses: &[Vec<crate::recorder::ResourceAccess>],
|
||||
|
|
@ -387,17 +648,7 @@ fn bind_resources(
|
|||
let lifetime = lifetimes.get_mut(&id).expect("lifetime key exists");
|
||||
let size = match &resources[&id] {
|
||||
ResourceDesc::CreatedBuffer(desc) => desc.size,
|
||||
ResourceDesc::CreatedTexture(desc) => {
|
||||
let texels_per_block = u64::from(desc.format.texels_per_block()).max(1);
|
||||
let block_size = u64::from(desc.format.block_size());
|
||||
u64::from(desc.extent.width)
|
||||
.saturating_mul(u64::from(desc.extent.height))
|
||||
.saturating_mul(u64::from(desc.extent.depth))
|
||||
.div_ceil(texels_per_block)
|
||||
.saturating_mul(block_size)
|
||||
.saturating_mul(u64::from(desc.array_layers))
|
||||
.saturating_mul(u64::from(desc.samples.as_raw().max(1)))
|
||||
}
|
||||
ResourceDesc::CreatedTexture(desc) => texture_size_estimate(desc),
|
||||
ResourceDesc::ImportedBuffer { .. } | ResourceDesc::ImportedTexture { .. } => 0,
|
||||
};
|
||||
if resources[&id].is_created()
|
||||
|
|
@ -424,20 +675,18 @@ mod tests {
|
|||
use super::*;
|
||||
use crate::{
|
||||
commands::{ClearTexture, CopyBuffer},
|
||||
resources::BufferRange,
|
||||
resources::{BufferRange, GraphBufferDesc, GraphTextureDesc},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn copy_buffer_compiles_with_dependency_and_barrier() {
|
||||
let mut graph = RenderGraph::new();
|
||||
let src = graph.create_buffer(renderer_resources::buffers::BufferDesc {
|
||||
let src = graph.create_buffer(GraphBufferDesc {
|
||||
size: 64,
|
||||
usage: vk::BufferUsageFlags::TRANSFER_SRC,
|
||||
..Default::default()
|
||||
});
|
||||
let dst = graph.create_buffer(renderer_resources::buffers::BufferDesc {
|
||||
let dst = graph.create_buffer(GraphBufferDesc {
|
||||
size: 64,
|
||||
usage: vk::BufferUsageFlags::TRANSFER_DST,
|
||||
..Default::default()
|
||||
});
|
||||
graph.push(CopyBuffer {
|
||||
|
|
@ -462,19 +711,16 @@ mod tests {
|
|||
#[test]
|
||||
fn write_then_read_creates_order_and_barrier() {
|
||||
let mut graph = RenderGraph::new();
|
||||
let a = graph.create_buffer(renderer_resources::buffers::BufferDesc {
|
||||
let a = graph.create_buffer(GraphBufferDesc {
|
||||
size: 32,
|
||||
usage: vk::BufferUsageFlags::TRANSFER_SRC | vk::BufferUsageFlags::TRANSFER_DST,
|
||||
..Default::default()
|
||||
});
|
||||
let b = graph.create_buffer(renderer_resources::buffers::BufferDesc {
|
||||
let b = graph.create_buffer(GraphBufferDesc {
|
||||
size: 32,
|
||||
usage: vk::BufferUsageFlags::TRANSFER_DST,
|
||||
..Default::default()
|
||||
});
|
||||
let c = graph.create_buffer(renderer_resources::buffers::BufferDesc {
|
||||
let c = graph.create_buffer(GraphBufferDesc {
|
||||
size: 32,
|
||||
usage: vk::BufferUsageFlags::TRANSFER_DST,
|
||||
..Default::default()
|
||||
});
|
||||
graph.push(CopyBuffer {
|
||||
|
|
@ -515,19 +761,17 @@ mod tests {
|
|||
#[test]
|
||||
fn texture_aliasing_size_uses_format_block_size() {
|
||||
let mut graph = RenderGraph::new();
|
||||
let texture = graph.create_texture(renderer_resources::images::ImageDesc {
|
||||
let texture = graph.create_texture(GraphTextureDesc {
|
||||
format: vk::Format::R8G8B8A8_UNORM,
|
||||
extent: vk::Extent3D {
|
||||
width: 4,
|
||||
height: 4,
|
||||
depth: 1,
|
||||
},
|
||||
usage: vk::ImageUsageFlags::TRANSFER_DST,
|
||||
..Default::default()
|
||||
});
|
||||
let buffer = graph.create_buffer(renderer_resources::buffers::BufferDesc {
|
||||
let buffer = graph.create_buffer(GraphBufferDesc {
|
||||
size: 8,
|
||||
usage: vk::BufferUsageFlags::TRANSFER_DST,
|
||||
..Default::default()
|
||||
});
|
||||
graph.push(ClearTexture {
|
||||
|
|
@ -554,13 +798,157 @@ mod tests {
|
|||
);
|
||||
|
||||
let compiled = graph.compile().expect("graph compiles");
|
||||
assert_eq!(
|
||||
compiled.resource_lifetimes()[&texture.id()].memory_offset,
|
||||
0
|
||||
);
|
||||
assert_eq!(
|
||||
compiled.resource_lifetimes()[&buffer.id()].memory_offset,
|
||||
64
|
||||
let texture_offset = compiled.resource_lifetimes()[&texture.id()].memory_offset;
|
||||
let buffer_offset = compiled.resource_lifetimes()[&buffer.id()].memory_offset;
|
||||
// Equal first-use indices may be assigned in either order by the
|
||||
// lifetime map, but the image's estimated 64-byte size is respected.
|
||||
assert!(
|
||||
texture_offset == 0 && buffer_offset == 64 || buffer_offset == 0 && texture_offset == 8
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn copy_buffer_derives_transfer_usages_for_source_and_destination() {
|
||||
let mut graph = RenderGraph::new();
|
||||
let src = graph.create_buffer(GraphBufferDesc {
|
||||
size: 64,
|
||||
..Default::default()
|
||||
});
|
||||
let dst = graph.create_buffer(GraphBufferDesc {
|
||||
size: 64,
|
||||
..Default::default()
|
||||
});
|
||||
graph.push(CopyBuffer {
|
||||
src,
|
||||
src_range: BufferRange {
|
||||
offset: 0,
|
||||
size: 64,
|
||||
},
|
||||
dst,
|
||||
dst_range: BufferRange {
|
||||
offset: 0,
|
||||
size: 64,
|
||||
},
|
||||
});
|
||||
graph.push(CopyBuffer {
|
||||
src: dst,
|
||||
src_range: BufferRange {
|
||||
offset: 0,
|
||||
size: 64,
|
||||
},
|
||||
dst: src,
|
||||
dst_range: BufferRange {
|
||||
offset: 0,
|
||||
size: 64,
|
||||
},
|
||||
});
|
||||
let accesses = graph.commands.side_effects.command_accesses();
|
||||
let src_accesses = accesses_for_resource(src.id(), accesses);
|
||||
let dst_accesses = accesses_for_resource(dst.id(), accesses);
|
||||
|
||||
assert_eq!(
|
||||
derive_buffer_usage(&src_accesses, vk::BufferUsageFlags::empty()),
|
||||
vk::BufferUsageFlags::TRANSFER_SRC | vk::BufferUsageFlags::TRANSFER_DST
|
||||
);
|
||||
assert_eq!(
|
||||
derive_buffer_usage(&dst_accesses, vk::BufferUsageFlags::empty()),
|
||||
vk::BufferUsageFlags::TRANSFER_SRC | vk::BufferUsageFlags::TRANSFER_DST
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_texture_derives_transfer_destination_usage() {
|
||||
let mut graph = RenderGraph::new();
|
||||
let texture = graph.create_texture(GraphTextureDesc {
|
||||
format: vk::Format::R8G8B8A8_UNORM,
|
||||
..Default::default()
|
||||
});
|
||||
graph.push(ClearTexture {
|
||||
texture,
|
||||
range: vk::ImageSubresourceRange {
|
||||
aspect_mask: vk::ImageAspectFlags::COLOR,
|
||||
base_mip_level: 0,
|
||||
level_count: 1,
|
||||
base_array_layer: 0,
|
||||
layer_count: 1,
|
||||
},
|
||||
layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||
color: [0.0; 4],
|
||||
});
|
||||
let accesses =
|
||||
accesses_for_resource(texture.id(), graph.commands.side_effects.command_accesses());
|
||||
|
||||
assert_eq!(
|
||||
derive_image_usage(&accesses, vk::ImageUsageFlags::empty()),
|
||||
vk::ImageUsageFlags::TRANSFER_DST
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extra_usage_is_preserved_and_combined_with_derived_usage() {
|
||||
let mut graph = RenderGraph::new();
|
||||
let buffer = graph.create_buffer(GraphBufferDesc {
|
||||
size: 16,
|
||||
extra_usage: vk::BufferUsageFlags::UNIFORM_BUFFER,
|
||||
..Default::default()
|
||||
});
|
||||
graph.push(CopyBuffer {
|
||||
src: buffer,
|
||||
src_range: BufferRange {
|
||||
offset: 0,
|
||||
size: 16,
|
||||
},
|
||||
dst: buffer,
|
||||
dst_range: BufferRange {
|
||||
offset: 0,
|
||||
size: 16,
|
||||
},
|
||||
});
|
||||
let accesses =
|
||||
accesses_for_resource(buffer.id(), graph.commands.side_effects.command_accesses());
|
||||
|
||||
assert_eq!(
|
||||
derive_buffer_usage(&accesses, vk::BufferUsageFlags::UNIFORM_BUFFER),
|
||||
vk::BufferUsageFlags::TRANSFER_SRC
|
||||
| vk::BufferUsageFlags::TRANSFER_DST
|
||||
| vk::BufferUsageFlags::UNIFORM_BUFFER
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pipeline_stages_map_to_queue_capabilities() {
|
||||
let accesses = [
|
||||
resource_access(
|
||||
vk::PipelineStageFlags2::VERTEX_SHADER,
|
||||
vk::AccessFlags2::SHADER_READ,
|
||||
),
|
||||
resource_access(
|
||||
vk::PipelineStageFlags2::COMPUTE_SHADER,
|
||||
vk::AccessFlags2::SHADER_WRITE,
|
||||
),
|
||||
resource_access(
|
||||
vk::PipelineStageFlags2::COPY | vk::PipelineStageFlags2::CLEAR,
|
||||
vk::AccessFlags2::TRANSFER_WRITE,
|
||||
),
|
||||
resource_access(vk::PipelineStageFlags2::NONE, vk::AccessFlags2::NONE),
|
||||
];
|
||||
assert_eq!(
|
||||
derive_queue_capabilities(&accesses),
|
||||
QueueFlags::GRAPHICS | QueueFlags::ASYNC_COMPUTE | QueueFlags::TRANSFER
|
||||
);
|
||||
}
|
||||
|
||||
fn resource_access(
|
||||
stage: vk::PipelineStageFlags2,
|
||||
access: vk::AccessFlags2,
|
||||
) -> crate::recorder::ResourceAccess {
|
||||
crate::recorder::ResourceAccess {
|
||||
resource: ResourceId(0),
|
||||
scope: SubresourceScope {
|
||||
stage,
|
||||
access,
|
||||
subresource: SubresourceScopeInner::Buffer(BufferRange::full()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,4 +8,4 @@ pub mod resources;
|
|||
|
||||
pub use graph::{CompiledGraph, GraphError, RenderGraph};
|
||||
pub use recorder::{CommandIndex, CommandList, CommandRecorder, SideEffectMap};
|
||||
pub use resources::{Buffer, ResourceId, Texture};
|
||||
pub use resources::{Buffer, GraphBufferDesc, GraphTextureDesc, ResourceId, Texture};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,73 @@
|
|||
use std::sync::Arc;
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
|
||||
use ash::vk;
|
||||
use renderer_resources::{buffers::Buffer as BufferResource, images::Image as ImageResource};
|
||||
|
||||
/// Minimal graph-level buffer description. Buffer usage, queue sharing, and
|
||||
/// allocation strategy are inferred from recorded command accesses at resolve.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GraphBufferDesc {
|
||||
pub size: u64,
|
||||
pub name: Option<Cow<'static, str>>,
|
||||
pub mem_location: gpu_allocator::MemoryLocation,
|
||||
/// Additional Vulkan usages not visible in the graph's recorded commands.
|
||||
pub extra_usage: vk::BufferUsageFlags,
|
||||
}
|
||||
|
||||
impl Default for GraphBufferDesc {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
size: 0,
|
||||
name: None,
|
||||
// Transient graph resources default to device-local memory.
|
||||
mem_location: gpu_allocator::MemoryLocation::GpuOnly,
|
||||
extra_usage: vk::BufferUsageFlags::empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Minimal graph-level texture description. Usage, queue sharing, and
|
||||
/// allocation strategy are inferred from recorded command accesses at resolve.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GraphTextureDesc {
|
||||
pub format: vk::Format,
|
||||
pub kind: vk::ImageType,
|
||||
pub extent: vk::Extent3D,
|
||||
pub mip_levels: u32,
|
||||
pub array_layers: u32,
|
||||
pub samples: vk::SampleCountFlags,
|
||||
pub tiling: vk::ImageTiling,
|
||||
pub layout: vk::ImageLayout,
|
||||
pub name: Option<Cow<'static, str>>,
|
||||
pub mem_location: gpu_allocator::MemoryLocation,
|
||||
/// Additional Vulkan usages not visible in the graph's recorded commands.
|
||||
pub extra_usage: vk::ImageUsageFlags,
|
||||
}
|
||||
|
||||
impl Default for GraphTextureDesc {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
// Callers must provide a concrete format before resolving an image.
|
||||
format: vk::Format::UNDEFINED,
|
||||
kind: vk::ImageType::TYPE_2D,
|
||||
extent: vk::Extent3D {
|
||||
width: 1,
|
||||
height: 1,
|
||||
depth: 1,
|
||||
},
|
||||
mip_levels: 1,
|
||||
array_layers: 1,
|
||||
samples: vk::SampleCountFlags::TYPE_1,
|
||||
// Optimal tiling is the usual choice for graph-managed GPU images.
|
||||
tiling: vk::ImageTiling::OPTIMAL,
|
||||
layout: vk::ImageLayout::UNDEFINED,
|
||||
name: None,
|
||||
mem_location: gpu_allocator::MemoryLocation::GpuOnly,
|
||||
extra_usage: vk::ImageUsageFlags::empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct ResourceId(pub u64);
|
||||
|
|
@ -212,8 +277,8 @@ impl Default for ResourceState {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ResourceDesc {
|
||||
CreatedBuffer(renderer_resources::buffers::BufferDesc),
|
||||
CreatedTexture(renderer_resources::images::ImageDesc),
|
||||
CreatedBuffer(GraphBufferDesc),
|
||||
CreatedTexture(GraphTextureDesc),
|
||||
ImportedBuffer {
|
||||
resource: Arc<BufferResource>,
|
||||
initial: ResourceState,
|
||||
|
|
|
|||
Loading…
Reference in a new issue