diff --git a/crates/renderer_rendergraph/Cargo.toml b/crates/renderer_rendergraph/Cargo.toml index e405adf..a08ef86 100644 --- a/crates/renderer_rendergraph/Cargo.toml +++ b/crates/renderer_rendergraph/Cargo.toml @@ -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" } diff --git a/crates/renderer_rendergraph/src/graph.rs b/crates/renderer_rendergraph/src/graph.rs index bfe16cb..b5102be 100644 --- a/crates/renderer_rendergraph/src/graph.rs +++ b/crates/renderer_rendergraph/src/graph.rs @@ -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], +) -> Vec { + 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], @@ -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()), + }, + } + } } diff --git a/crates/renderer_rendergraph/src/lib.rs b/crates/renderer_rendergraph/src/lib.rs index 6cd3c14..f7e4d46 100644 --- a/crates/renderer_rendergraph/src/lib.rs +++ b/crates/renderer_rendergraph/src/lib.rs @@ -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}; diff --git a/crates/renderer_rendergraph/src/resources.rs b/crates/renderer_rendergraph/src/resources.rs index 2e77352..107ce8a 100644 --- a/crates/renderer_rendergraph/src/resources.rs +++ b/crates/renderer_rendergraph/src/resources.rs @@ -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>, + 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>, + 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, initial: ResourceState,