custom resource descriptors for graph

This commit is contained in:
janis 2026-10-25 00:13:26 +02:00
parent d367b4d05b
commit 5f7bbba49d
Signed by: janis
SSH key fingerprint: SHA256:bB1qbbqmDXZNT0KKD5c2Dfjg53JGhj7B3CFcLIzSqq8
4 changed files with 503 additions and 48 deletions

View file

@ -5,5 +5,7 @@ edition = "2024"
[dependencies] [dependencies]
ash.workspace = true ash.workspace = true
gpu-allocator.workspace = true
renderer_device = { path = "../renderer_device" }
renderer_resources = { path = "../renderer_resources" } renderer_resources = { path = "../renderer_resources" }
ash-formats = { git = "https://github.com/janis-bhm/ash-formats", branch = "main" } ash-formats = { git = "https://github.com/janis-bhm/ash-formats", branch = "main" }

View file

@ -6,14 +6,17 @@ use std::{
use ash::vk; use ash::vk;
use ash_formats::FormatExt; use ash_formats::FormatExt;
use renderer_resources::{ 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::{ use crate::{
recorder::{CommandIndex, CommandList, CommandRecorder, RecordError, scope_range}, recorder::{CommandIndex, CommandList, CommandRecorder, RecordError, scope_range},
resources::{ resources::{
Buffer, ResourceDesc, ResourceId, Subresource, SubresourceScope, SubresourceScopeInner, Buffer, GraphBufferDesc, GraphTextureDesc, ResourceDesc, ResourceId, Subresource,
Texture, access_flag_is_write, SubresourceScope, SubresourceScopeInner, Texture, access_flag_is_write,
}, },
}; };
@ -72,11 +75,11 @@ impl RenderGraph {
id 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))) 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))) Texture(self.add_resource(ResourceDesc::CreatedTexture(desc)))
} }
@ -207,13 +210,51 @@ impl CompiledGraph {
if self.resolved.contains_key(&id) { if self.resolved.contains_key(&id) {
continue; continue;
} }
let accesses = accesses_for_resource(id, self.commands.side_effects.command_accesses());
let resource = match desc { let resource = match desc {
ResourceDesc::CreatedBuffer(desc) => ResolvedResource::Buffer(Arc::new( ResourceDesc::CreatedBuffer(desc) => {
BufferResource::new(device.clone(), desc.clone())?, let usage = derive_buffer_usage(&accesses, desc.extra_usage);
)), let queue_families =
ResourceDesc::CreatedTexture(desc) => ResolvedResource::Image(Arc::new( actual_queue_families(device, derive_queue_capabilities(&accesses));
ImageResource::new(device.clone(), desc.clone())?, 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 { .. } => { ResourceDesc::ImportedBuffer { .. } | ResourceDesc::ImportedTexture { .. } => {
continue; 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( fn build_barriers(
order: &[CommandIndex], order: &[CommandIndex],
accesses: &[Vec<crate::recorder::ResourceAccess>], accesses: &[Vec<crate::recorder::ResourceAccess>],
@ -387,17 +648,7 @@ fn bind_resources(
let lifetime = lifetimes.get_mut(&id).expect("lifetime key exists"); let lifetime = lifetimes.get_mut(&id).expect("lifetime key exists");
let size = match &resources[&id] { let size = match &resources[&id] {
ResourceDesc::CreatedBuffer(desc) => desc.size, ResourceDesc::CreatedBuffer(desc) => desc.size,
ResourceDesc::CreatedTexture(desc) => { ResourceDesc::CreatedTexture(desc) => texture_size_estimate(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::ImportedBuffer { .. } | ResourceDesc::ImportedTexture { .. } => 0, ResourceDesc::ImportedBuffer { .. } | ResourceDesc::ImportedTexture { .. } => 0,
}; };
if resources[&id].is_created() if resources[&id].is_created()
@ -424,20 +675,18 @@ mod tests {
use super::*; use super::*;
use crate::{ use crate::{
commands::{ClearTexture, CopyBuffer}, commands::{ClearTexture, CopyBuffer},
resources::BufferRange, resources::{BufferRange, GraphBufferDesc, GraphTextureDesc},
}; };
#[test] #[test]
fn copy_buffer_compiles_with_dependency_and_barrier() { fn copy_buffer_compiles_with_dependency_and_barrier() {
let mut graph = RenderGraph::new(); let mut graph = RenderGraph::new();
let src = graph.create_buffer(renderer_resources::buffers::BufferDesc { let src = graph.create_buffer(GraphBufferDesc {
size: 64, size: 64,
usage: vk::BufferUsageFlags::TRANSFER_SRC,
..Default::default() ..Default::default()
}); });
let dst = graph.create_buffer(renderer_resources::buffers::BufferDesc { let dst = graph.create_buffer(GraphBufferDesc {
size: 64, size: 64,
usage: vk::BufferUsageFlags::TRANSFER_DST,
..Default::default() ..Default::default()
}); });
graph.push(CopyBuffer { graph.push(CopyBuffer {
@ -462,19 +711,16 @@ mod tests {
#[test] #[test]
fn write_then_read_creates_order_and_barrier() { fn write_then_read_creates_order_and_barrier() {
let mut graph = RenderGraph::new(); let mut graph = RenderGraph::new();
let a = graph.create_buffer(renderer_resources::buffers::BufferDesc { let a = graph.create_buffer(GraphBufferDesc {
size: 32, size: 32,
usage: vk::BufferUsageFlags::TRANSFER_SRC | vk::BufferUsageFlags::TRANSFER_DST,
..Default::default() ..Default::default()
}); });
let b = graph.create_buffer(renderer_resources::buffers::BufferDesc { let b = graph.create_buffer(GraphBufferDesc {
size: 32, size: 32,
usage: vk::BufferUsageFlags::TRANSFER_DST,
..Default::default() ..Default::default()
}); });
let c = graph.create_buffer(renderer_resources::buffers::BufferDesc { let c = graph.create_buffer(GraphBufferDesc {
size: 32, size: 32,
usage: vk::BufferUsageFlags::TRANSFER_DST,
..Default::default() ..Default::default()
}); });
graph.push(CopyBuffer { graph.push(CopyBuffer {
@ -515,19 +761,17 @@ mod tests {
#[test] #[test]
fn texture_aliasing_size_uses_format_block_size() { fn texture_aliasing_size_uses_format_block_size() {
let mut graph = RenderGraph::new(); 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, format: vk::Format::R8G8B8A8_UNORM,
extent: vk::Extent3D { extent: vk::Extent3D {
width: 4, width: 4,
height: 4, height: 4,
depth: 1, depth: 1,
}, },
usage: vk::ImageUsageFlags::TRANSFER_DST,
..Default::default() ..Default::default()
}); });
let buffer = graph.create_buffer(renderer_resources::buffers::BufferDesc { let buffer = graph.create_buffer(GraphBufferDesc {
size: 8, size: 8,
usage: vk::BufferUsageFlags::TRANSFER_DST,
..Default::default() ..Default::default()
}); });
graph.push(ClearTexture { graph.push(ClearTexture {
@ -554,13 +798,157 @@ mod tests {
); );
let compiled = graph.compile().expect("graph compiles"); let compiled = graph.compile().expect("graph compiles");
assert_eq!( let texture_offset = compiled.resource_lifetimes()[&texture.id()].memory_offset;
compiled.resource_lifetimes()[&texture.id()].memory_offset, let buffer_offset = compiled.resource_lifetimes()[&buffer.id()].memory_offset;
0 // 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_eq!( assert!(
compiled.resource_lifetimes()[&buffer.id()].memory_offset, texture_offset == 0 && buffer_offset == 64 || buffer_offset == 0 && texture_offset == 8
64
); );
} }
#[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()),
},
}
}
} }

View file

@ -8,4 +8,4 @@ pub mod resources;
pub use graph::{CompiledGraph, GraphError, RenderGraph}; pub use graph::{CompiledGraph, GraphError, RenderGraph};
pub use recorder::{CommandIndex, CommandList, CommandRecorder, SideEffectMap}; pub use recorder::{CommandIndex, CommandList, CommandRecorder, SideEffectMap};
pub use resources::{Buffer, ResourceId, Texture}; pub use resources::{Buffer, GraphBufferDesc, GraphTextureDesc, ResourceId, Texture};

View file

@ -1,8 +1,73 @@
use std::sync::Arc; use std::{borrow::Cow, sync::Arc};
use ash::vk; use ash::vk;
use renderer_resources::{buffers::Buffer as BufferResource, images::Image as ImageResource}; 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)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)] #[repr(transparent)]
pub struct ResourceId(pub u64); pub struct ResourceId(pub u64);
@ -212,8 +277,8 @@ impl Default for ResourceState {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ResourceDesc { pub enum ResourceDesc {
CreatedBuffer(renderer_resources::buffers::BufferDesc), CreatedBuffer(GraphBufferDesc),
CreatedTexture(renderer_resources::images::ImageDesc), CreatedTexture(GraphTextureDesc),
ImportedBuffer { ImportedBuffer {
resource: Arc<BufferResource>, resource: Arc<BufferResource>,
initial: ResourceState, initial: ResourceState,