feat: new draw command with arguments and resource tracking
This commit is contained in:
parent
d896a207de
commit
d901fe250b
|
|
@ -3,7 +3,9 @@ use ash::vk;
|
|||
use crate::{
|
||||
recorder::{CommandIndex, CommandRecorder, RecordError, RecordedCommand, SideEffectMap},
|
||||
resources::{
|
||||
Buffer, BufferRange, ResourceId, SubresourceScope, SubresourceScopeInner, Texture,
|
||||
Buffer, BufferRange, DescriptorResourceAccess, GraphDescriptorSet, IndexBinding,
|
||||
IndexFormat, IndirectBinding, PushConstantData, ResourceId, SubresourceScope,
|
||||
SubresourceScopeInner, Texture, VertexBinding,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -120,28 +122,206 @@ impl RecordedCommand for ClearTexture {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum DrawArgs {
|
||||
Direct {
|
||||
vertex_count: u32,
|
||||
instance_count: u32,
|
||||
first_vertex: u32,
|
||||
first_instance: u32,
|
||||
},
|
||||
Indexed {
|
||||
index_count: u32,
|
||||
instance_count: u32,
|
||||
first_index: u32,
|
||||
vertex_offset: i32,
|
||||
first_instance: u32,
|
||||
},
|
||||
}
|
||||
|
||||
/// A self-contained graphics draw packet. Every graph resource consumed by
|
||||
/// vertex/index/indirect input or descriptor sets is declared by this packet.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Draw {
|
||||
pub vertex_count: u32,
|
||||
pub instance_count: u32,
|
||||
pub first_vertex: u32,
|
||||
pub first_instance: u32,
|
||||
pub args: DrawArgs,
|
||||
/// Layout compatible with the bound pipeline, descriptor sets, and push constants.
|
||||
pub pipeline_layout: vk::PipelineLayout,
|
||||
pub vertices: Vec<VertexBinding>,
|
||||
pub index: Option<IndexBinding>,
|
||||
pub indirect: Option<IndirectBinding>,
|
||||
pub descriptor_sets: Vec<GraphDescriptorSet>,
|
||||
pub push_constants: Vec<PushConstantData>,
|
||||
}
|
||||
|
||||
impl Command for Draw {
|
||||
fn side_effects(&self, _command_index: CommandIndex, _map: &mut SideEffectMap) {}
|
||||
fn side_effects(&self, command_index: CommandIndex, map: &mut SideEffectMap) {
|
||||
for vertex in &self.vertices {
|
||||
map.insert(
|
||||
command_index,
|
||||
vertex.slice.buffer.id(),
|
||||
access(
|
||||
vk::PipelineStageFlags2::VERTEX_INPUT,
|
||||
vk::AccessFlags2::VERTEX_ATTRIBUTE_READ,
|
||||
SubresourceScopeInner::Buffer(vertex.slice.range),
|
||||
),
|
||||
);
|
||||
}
|
||||
if let Some(index) = self.index {
|
||||
map.insert(
|
||||
command_index,
|
||||
index.slice.buffer.id(),
|
||||
access(
|
||||
vk::PipelineStageFlags2::INDEX_INPUT,
|
||||
vk::AccessFlags2::INDEX_READ,
|
||||
SubresourceScopeInner::Buffer(index.slice.range),
|
||||
),
|
||||
);
|
||||
}
|
||||
if let Some(indirect) = self.indirect {
|
||||
map.insert(
|
||||
command_index,
|
||||
indirect.slice.buffer.id(),
|
||||
access(
|
||||
vk::PipelineStageFlags2::DRAW_INDIRECT,
|
||||
vk::AccessFlags2::INDIRECT_COMMAND_READ,
|
||||
SubresourceScopeInner::Buffer(indirect.slice.range),
|
||||
),
|
||||
);
|
||||
}
|
||||
for set in &self.descriptor_sets {
|
||||
for declared in &set.accesses {
|
||||
let scope = match *declared {
|
||||
DescriptorResourceAccess::Buffer {
|
||||
range,
|
||||
stage,
|
||||
access: mask,
|
||||
..
|
||||
} => access(stage, mask, SubresourceScopeInner::Buffer(range)),
|
||||
DescriptorResourceAccess::Texture {
|
||||
range,
|
||||
layout,
|
||||
stage,
|
||||
access: mask,
|
||||
..
|
||||
} => access(
|
||||
stage,
|
||||
mask,
|
||||
SubresourceScopeInner::Texture { range, layout },
|
||||
),
|
||||
};
|
||||
let resource = match *declared {
|
||||
DescriptorResourceAccess::Buffer { buffer, .. } => buffer.id(),
|
||||
DescriptorResourceAccess::Texture { texture, .. } => texture.id(),
|
||||
};
|
||||
map.insert(command_index, resource, scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RecordedCommand for Draw {
|
||||
fn apply(&mut self, recorder: &mut CommandRecorder<'_>) -> Result<(), RecordError> {
|
||||
for vertex in &self.vertices {
|
||||
let buffers = [recorder.buffer(vertex.slice.buffer.id())?];
|
||||
let offsets = [vertex.slice.range.offset];
|
||||
unsafe {
|
||||
recorder.device.cmd_draw(
|
||||
recorder.device.cmd_bind_vertex_buffers(
|
||||
recorder.command_buffer,
|
||||
self.vertex_count,
|
||||
self.instance_count,
|
||||
self.first_vertex,
|
||||
self.first_instance,
|
||||
vertex.binding,
|
||||
&buffers,
|
||||
&offsets,
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(index) = self.index {
|
||||
let index_type = match index.format {
|
||||
IndexFormat::Uint8 => vk::IndexType::UINT8_KHR,
|
||||
IndexFormat::Uint16 => vk::IndexType::UINT16,
|
||||
IndexFormat::Uint32 => vk::IndexType::UINT32,
|
||||
};
|
||||
unsafe {
|
||||
recorder.device.cmd_bind_index_buffer(
|
||||
recorder.command_buffer,
|
||||
recorder.buffer(index.slice.buffer.id())?,
|
||||
index.slice.range.offset,
|
||||
index_type,
|
||||
);
|
||||
}
|
||||
}
|
||||
for constants in &self.push_constants {
|
||||
unsafe {
|
||||
recorder.device.cmd_push_constants(
|
||||
recorder.command_buffer,
|
||||
self.pipeline_layout,
|
||||
constants.stage_flags,
|
||||
constants.offset,
|
||||
&constants.data,
|
||||
);
|
||||
}
|
||||
}
|
||||
let descriptor_sets: Vec<_> = self.descriptor_sets.iter().map(|set| set.raw).collect();
|
||||
if !descriptor_sets.is_empty() {
|
||||
unsafe {
|
||||
recorder.device.cmd_bind_descriptor_sets(
|
||||
recorder.command_buffer,
|
||||
vk::PipelineBindPoint::GRAPHICS,
|
||||
self.pipeline_layout,
|
||||
0,
|
||||
&descriptor_sets,
|
||||
&[],
|
||||
);
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
if let Some(indirect) = self.indirect {
|
||||
let buffer = recorder.buffer(indirect.slice.buffer.id())?;
|
||||
if matches!(self.args, DrawArgs::Indexed { .. }) {
|
||||
recorder.device.cmd_draw_indexed_indirect(
|
||||
recorder.command_buffer,
|
||||
buffer,
|
||||
indirect.slice.range.offset,
|
||||
indirect.draw_count,
|
||||
indirect.stride,
|
||||
);
|
||||
} else {
|
||||
recorder.device.cmd_draw_indirect(
|
||||
recorder.command_buffer,
|
||||
buffer,
|
||||
indirect.slice.range.offset,
|
||||
indirect.draw_count,
|
||||
indirect.stride,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
match self.args {
|
||||
DrawArgs::Direct {
|
||||
vertex_count,
|
||||
instance_count,
|
||||
first_vertex,
|
||||
first_instance,
|
||||
} => recorder.device.cmd_draw(
|
||||
recorder.command_buffer,
|
||||
vertex_count,
|
||||
instance_count,
|
||||
first_vertex,
|
||||
first_instance,
|
||||
),
|
||||
DrawArgs::Indexed {
|
||||
index_count,
|
||||
instance_count,
|
||||
first_index,
|
||||
vertex_offset,
|
||||
first_instance,
|
||||
} => recorder.device.cmd_draw_indexed(
|
||||
recorder.command_buffer,
|
||||
index_count,
|
||||
instance_count,
|
||||
first_index,
|
||||
vertex_offset,
|
||||
first_instance,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,9 @@ use renderer_device::QueueFamilyIndex;
|
|||
use crate::{
|
||||
recorder::{CommandIndex, CommandList, CommandRecorder, RecordError, scope_range},
|
||||
resources::{
|
||||
Buffer, GraphBufferDesc, GraphTextureDesc, ResourceDesc, ResourceId, Subresource,
|
||||
SubresourceScope, SubresourceScopeInner, Texture, access_flag_is_write,
|
||||
Buffer, DescriptorResourceAccess, GraphBufferDesc, GraphDescriptorSet, GraphTextureDesc,
|
||||
ResourceDesc, ResourceId, Subresource, SubresourceScope, SubresourceScopeInner, Texture,
|
||||
access_flag_is_write,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -244,6 +245,19 @@ impl RenderGraph {
|
|||
}))
|
||||
}
|
||||
|
||||
/// Associate graph-visible resource accesses with a raw Vulkan descriptor set.
|
||||
///
|
||||
/// The caller is responsible for declaring every resource referenced by the
|
||||
/// set accurately; compilation validates that each declared resource belongs
|
||||
/// to this graph, but cannot inspect the raw descriptor contents.
|
||||
pub fn track_descriptor_set(
|
||||
&self,
|
||||
raw: vk::DescriptorSet,
|
||||
accesses: Vec<DescriptorResourceAccess>,
|
||||
) -> GraphDescriptorSet {
|
||||
GraphDescriptorSet { raw, accesses }
|
||||
}
|
||||
|
||||
pub fn push<C: crate::commands::Command + 'static>(&mut self, command: C) -> CommandIndex {
|
||||
self.commands.push(command)
|
||||
}
|
||||
|
|
@ -923,8 +937,11 @@ fn texture_size_estimate(desc: &GraphTextureDesc) -> u64 {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
commands::{ClearTexture, CopyBuffer},
|
||||
resources::{BufferRange, GraphBufferDesc, GraphTextureDesc, ResourceDesc},
|
||||
commands::{ClearTexture, CopyBuffer, Draw, DrawArgs},
|
||||
resources::{
|
||||
BufferRange, BufferSlice, GraphBufferDesc, GraphTextureDesc, IndexBinding, IndexFormat,
|
||||
IndirectBinding, ResourceDesc, VertexBinding,
|
||||
},
|
||||
};
|
||||
|
||||
#[test]
|
||||
|
|
@ -1079,6 +1096,237 @@ mod tests {
|
|||
assert!(compiled.interference().interferes(src.id(), dst.id()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn draw_vertex_index_and_indirect_reads_follow_copies_and_derive_usage() {
|
||||
let mut graph = RenderGraph::new();
|
||||
let source = graph.create_buffer(GraphBufferDesc {
|
||||
size: 64,
|
||||
..Default::default()
|
||||
});
|
||||
let vertex = graph.create_buffer(GraphBufferDesc {
|
||||
size: 64,
|
||||
..Default::default()
|
||||
});
|
||||
let index = graph.create_buffer(GraphBufferDesc {
|
||||
size: 64,
|
||||
..Default::default()
|
||||
});
|
||||
let indirect = graph.create_buffer(GraphBufferDesc {
|
||||
size: 64,
|
||||
..Default::default()
|
||||
});
|
||||
let range = BufferRange {
|
||||
offset: 0,
|
||||
size: 64,
|
||||
};
|
||||
for dst in [vertex, index, indirect] {
|
||||
graph.push(CopyBuffer {
|
||||
src: source,
|
||||
src_range: range,
|
||||
dst,
|
||||
dst_range: range,
|
||||
});
|
||||
}
|
||||
graph.push(Draw {
|
||||
args: DrawArgs::Indexed {
|
||||
index_count: 3,
|
||||
instance_count: 1,
|
||||
first_index: 0,
|
||||
vertex_offset: 0,
|
||||
first_instance: 0,
|
||||
},
|
||||
pipeline_layout: vk::PipelineLayout::null(),
|
||||
vertices: vec![VertexBinding {
|
||||
binding: 0,
|
||||
slice: BufferSlice {
|
||||
buffer: vertex,
|
||||
range,
|
||||
},
|
||||
}],
|
||||
index: Some(IndexBinding {
|
||||
slice: BufferSlice {
|
||||
buffer: index,
|
||||
range,
|
||||
},
|
||||
format: IndexFormat::Uint16,
|
||||
}),
|
||||
indirect: Some(IndirectBinding {
|
||||
slice: BufferSlice {
|
||||
buffer: indirect,
|
||||
range,
|
||||
},
|
||||
draw_count: 1,
|
||||
stride: 20,
|
||||
}),
|
||||
descriptor_sets: Vec::new(),
|
||||
push_constants: Vec::new(),
|
||||
});
|
||||
|
||||
let accesses = graph.commands.side_effects.command_accesses();
|
||||
assert_eq!(
|
||||
derive_buffer_usage(
|
||||
&accesses_for_resource(vertex.id(), accesses),
|
||||
vk::BufferUsageFlags::empty()
|
||||
),
|
||||
vk::BufferUsageFlags::TRANSFER_DST | vk::BufferUsageFlags::VERTEX_BUFFER
|
||||
);
|
||||
assert_eq!(
|
||||
derive_buffer_usage(
|
||||
&accesses_for_resource(index.id(), accesses),
|
||||
vk::BufferUsageFlags::empty()
|
||||
),
|
||||
vk::BufferUsageFlags::TRANSFER_DST | vk::BufferUsageFlags::INDEX_BUFFER
|
||||
);
|
||||
assert_eq!(
|
||||
derive_buffer_usage(
|
||||
&accesses_for_resource(indirect.id(), accesses),
|
||||
vk::BufferUsageFlags::empty()
|
||||
),
|
||||
vk::BufferUsageFlags::TRANSFER_DST | vk::BufferUsageFlags::INDIRECT_BUFFER
|
||||
);
|
||||
|
||||
let compiled = graph.compile().expect("graph compiles");
|
||||
assert_eq!(compiled.batches().len(), 2);
|
||||
assert_eq!(
|
||||
compiled.batches()[0],
|
||||
[CommandIndex(0), CommandIndex(1), CommandIndex(2)]
|
||||
);
|
||||
assert_eq!(compiled.batches()[1], [CommandIndex(3)]);
|
||||
let draw_barrier = compiled
|
||||
.barriers()
|
||||
.iter()
|
||||
.find(|barrier| barrier.before_command == CommandIndex(3))
|
||||
.expect("draw input barriers are generated");
|
||||
assert_eq!(draw_barrier.resources.len(), 3);
|
||||
assert!(
|
||||
draw_barrier
|
||||
.resources
|
||||
.iter()
|
||||
.any(|resource| resource.id == vertex.id())
|
||||
);
|
||||
assert!(
|
||||
draw_barrier
|
||||
.resources
|
||||
.iter()
|
||||
.any(|resource| resource.id == index.id())
|
||||
);
|
||||
assert!(
|
||||
draw_barrier
|
||||
.resources
|
||||
.iter()
|
||||
.any(|resource| resource.id == indirect.id())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn descriptor_set_declarations_create_buffer_and_texture_accesses() {
|
||||
let mut graph = RenderGraph::new();
|
||||
let source = graph.create_buffer(GraphBufferDesc {
|
||||
size: 32,
|
||||
..Default::default()
|
||||
});
|
||||
let uniform = graph.create_buffer(GraphBufferDesc {
|
||||
size: 32,
|
||||
..Default::default()
|
||||
});
|
||||
let texture = graph.create_texture(GraphTextureDesc {
|
||||
format: vk::Format::R8G8B8A8_UNORM,
|
||||
..Default::default()
|
||||
});
|
||||
let buffer_range = BufferRange {
|
||||
offset: 0,
|
||||
size: 32,
|
||||
};
|
||||
graph.push(CopyBuffer {
|
||||
src: source,
|
||||
src_range: buffer_range,
|
||||
dst: uniform,
|
||||
dst_range: buffer_range,
|
||||
});
|
||||
graph.push(ClearTexture {
|
||||
texture,
|
||||
range: texture_range(vk::ImageAspectFlags::COLOR, 0, 1, 0, 1),
|
||||
layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
|
||||
color: [0.0; 4],
|
||||
});
|
||||
let set = graph.track_descriptor_set(
|
||||
vk::DescriptorSet::null(),
|
||||
vec![
|
||||
DescriptorResourceAccess::Buffer {
|
||||
buffer: uniform,
|
||||
range: buffer_range,
|
||||
stage: vk::PipelineStageFlags2::VERTEX_SHADER,
|
||||
access: vk::AccessFlags2::UNIFORM_READ,
|
||||
},
|
||||
DescriptorResourceAccess::Texture {
|
||||
texture,
|
||||
range: texture_range(vk::ImageAspectFlags::COLOR, 0, 1, 0, 1),
|
||||
layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
||||
stage: vk::PipelineStageFlags2::FRAGMENT_SHADER,
|
||||
access: vk::AccessFlags2::SHADER_SAMPLED_READ,
|
||||
},
|
||||
],
|
||||
);
|
||||
graph.push(Draw {
|
||||
args: DrawArgs::Direct {
|
||||
vertex_count: 3,
|
||||
instance_count: 1,
|
||||
first_vertex: 0,
|
||||
first_instance: 0,
|
||||
},
|
||||
pipeline_layout: vk::PipelineLayout::null(),
|
||||
vertices: Vec::new(),
|
||||
index: None,
|
||||
indirect: None,
|
||||
descriptor_sets: vec![set],
|
||||
push_constants: Vec::new(),
|
||||
});
|
||||
|
||||
let accesses = graph.commands.side_effects.command_accesses();
|
||||
assert!(
|
||||
derive_buffer_usage(
|
||||
&accesses_for_resource(uniform.id(), accesses),
|
||||
vk::BufferUsageFlags::empty()
|
||||
)
|
||||
.contains(vk::BufferUsageFlags::UNIFORM_BUFFER)
|
||||
);
|
||||
assert!(
|
||||
derive_image_usage(
|
||||
&accesses_for_resource(texture.id(), accesses),
|
||||
vk::ImageUsageFlags::empty()
|
||||
)
|
||||
.contains(vk::ImageUsageFlags::SAMPLED)
|
||||
);
|
||||
|
||||
let compiled = graph.compile().expect("graph compiles");
|
||||
let draw_barrier = compiled
|
||||
.barriers()
|
||||
.iter()
|
||||
.find(|barrier| barrier.before_command == CommandIndex(2))
|
||||
.expect("descriptor accesses produce barriers");
|
||||
assert_eq!(draw_barrier.resources.len(), 2);
|
||||
let texture_barrier = draw_barrier
|
||||
.resources
|
||||
.iter()
|
||||
.find(|resource| resource.id == texture.id())
|
||||
.expect("sampled texture barrier is present");
|
||||
assert_eq!(
|
||||
texture_barrier.new_layout,
|
||||
Some(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
|
||||
);
|
||||
assert_eq!(
|
||||
texture_barrier.dst_access,
|
||||
vk::AccessFlags2::SHADER_SAMPLED_READ
|
||||
);
|
||||
assert!(compiled.resource_lifetimes().contains_key(&uniform.id()));
|
||||
assert!(compiled.resource_lifetimes().contains_key(&texture.id()));
|
||||
assert!(
|
||||
compiled
|
||||
.interference()
|
||||
.interferes(uniform.id(), texture.id())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_then_read_creates_order_and_barrier() {
|
||||
let mut graph = RenderGraph::new();
|
||||
|
|
|
|||
|
|
@ -8,4 +8,8 @@ pub mod resources;
|
|||
|
||||
pub use graph::{CompiledGraph, GraphError, RenderGraph, ResourceInterference};
|
||||
pub use recorder::{CommandIndex, CommandList, CommandRecorder, SideEffectMap};
|
||||
pub use resources::{Buffer, GraphBufferDesc, GraphTextureDesc, ResourceId, Texture};
|
||||
pub use resources::{
|
||||
Buffer, BufferRange, BufferSlice, DescriptorResourceAccess, GraphBufferDesc,
|
||||
GraphDescriptorSet, GraphTextureDesc, IndexBinding, IndexFormat, IndirectBinding,
|
||||
PushConstantData, ResourceId, Texture, VertexBinding,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -130,6 +130,79 @@ impl BufferRange {
|
|||
}
|
||||
}
|
||||
|
||||
/// A graph buffer together with the byte range used by a command.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct BufferSlice {
|
||||
pub buffer: Buffer,
|
||||
pub range: BufferRange,
|
||||
}
|
||||
|
||||
/// A vertex-input binding. `binding` is the Vulkan vertex binding number.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct VertexBinding {
|
||||
pub binding: u32,
|
||||
pub slice: BufferSlice,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum IndexFormat {
|
||||
Uint8,
|
||||
Uint16,
|
||||
Uint32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct IndexBinding {
|
||||
pub slice: BufferSlice,
|
||||
pub format: IndexFormat,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct IndirectBinding {
|
||||
pub slice: BufferSlice,
|
||||
/// Number of draw commands in the indirect buffer.
|
||||
pub draw_count: u32,
|
||||
/// Stride in bytes between draw commands. Must satisfy Vulkan's alignment
|
||||
/// and minimum-size requirements for the corresponding draw command.
|
||||
pub stride: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum DescriptorResourceAccess {
|
||||
Buffer {
|
||||
buffer: Buffer,
|
||||
range: BufferRange,
|
||||
stage: vk::PipelineStageFlags2,
|
||||
access: vk::AccessFlags2,
|
||||
},
|
||||
Texture {
|
||||
texture: Texture,
|
||||
range: vk::ImageSubresourceRange,
|
||||
layout: vk::ImageLayout,
|
||||
stage: vk::PipelineStageFlags2,
|
||||
access: vk::AccessFlags2,
|
||||
},
|
||||
}
|
||||
|
||||
/// A Vulkan descriptor set accompanied by graph-visible resource accesses.
|
||||
///
|
||||
/// Callers must list every resource used by the set with its accurate range,
|
||||
/// pipeline stage, access mask, and (for textures) image layout. The graph can
|
||||
/// track these declarations, but cannot inspect or verify raw descriptor-set
|
||||
/// contents.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GraphDescriptorSet {
|
||||
pub raw: vk::DescriptorSet,
|
||||
pub accesses: Vec<DescriptorResourceAccess>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PushConstantData {
|
||||
pub stage_flags: vk::ShaderStageFlags,
|
||||
pub offset: u32,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Subresource {
|
||||
Buffer(BufferRange),
|
||||
|
|
|
|||
Loading…
Reference in a new issue