Compare commits
No commits in common. "d901fe250bc603633cd2e42c087a189b7cf868f9" and "f9c18bcd3c2a94e013fdb1d7ff1e1e4d23cb2c99" have entirely different histories.
d901fe250b
...
f9c18bcd3c
|
|
@ -2,24 +2,20 @@
|
|||
|
||||
extern crate self as renderer_device;
|
||||
|
||||
use crate::instance::InstanceInner;
|
||||
use ash::{
|
||||
ext,
|
||||
vk::{self, SurfaceCapabilitiesKHR},
|
||||
};
|
||||
use dyn_clone::DynClone;
|
||||
use std::{fmt::Debug, path::PathBuf};
|
||||
use ash::{ext, vk::{self, SurfaceCapabilitiesKHR}};
|
||||
use dyn_clone::DynClone;
|
||||
use crate::instance::InstanceInner;
|
||||
|
||||
mod debug;
|
||||
pub mod device;
|
||||
pub mod instance;
|
||||
pub mod pipeline_cache;
|
||||
pub mod queue;
|
||||
pub mod sync;
|
||||
mod debug;
|
||||
|
||||
pub use device::{Device, DeviceOwned, DeviceRef, Extension};
|
||||
pub use instance::{DebugUtils, DebugUtilsCreateInfo, Instance};
|
||||
pub use queue::QueueFamilyIndex;
|
||||
|
||||
#[cfg(test)]
|
||||
mod device_ref_derive_tests {
|
||||
|
|
|
|||
|
|
@ -4,8 +4,3 @@ version = "0.1.0"
|
|||
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" }
|
||||
|
|
|
|||
|
|
@ -1,331 +0,0 @@
|
|||
use ash::vk;
|
||||
|
||||
use crate::{
|
||||
recorder::{CommandIndex, CommandRecorder, RecordError, RecordedCommand, SideEffectMap},
|
||||
resources::{
|
||||
Buffer, BufferRange, DescriptorResourceAccess, GraphDescriptorSet, IndexBinding,
|
||||
IndexFormat, IndirectBinding, PushConstantData, ResourceId, SubresourceScope,
|
||||
SubresourceScopeInner, Texture, VertexBinding,
|
||||
},
|
||||
};
|
||||
|
||||
/// A recorded operation declares its accesses before compilation and applies
|
||||
/// itself to a Vulkan command buffer after resource binding.
|
||||
pub trait Command: RecordedCommand {
|
||||
fn side_effects(&self, command_index: CommandIndex, map: &mut SideEffectMap);
|
||||
}
|
||||
|
||||
fn access(
|
||||
stage: vk::PipelineStageFlags2,
|
||||
access: vk::AccessFlags2,
|
||||
subresource: SubresourceScopeInner,
|
||||
) -> SubresourceScope {
|
||||
SubresourceScope {
|
||||
stage,
|
||||
access,
|
||||
subresource,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CopyBuffer {
|
||||
pub src: Buffer,
|
||||
pub src_range: BufferRange,
|
||||
pub dst: Buffer,
|
||||
pub dst_range: BufferRange,
|
||||
}
|
||||
|
||||
impl Command for CopyBuffer {
|
||||
fn side_effects(&self, command_index: CommandIndex, map: &mut SideEffectMap) {
|
||||
map.insert(
|
||||
command_index,
|
||||
self.src.id(),
|
||||
access(
|
||||
vk::PipelineStageFlags2::COPY,
|
||||
vk::AccessFlags2::TRANSFER_READ,
|
||||
SubresourceScopeInner::Buffer(self.src_range),
|
||||
),
|
||||
);
|
||||
map.insert(
|
||||
command_index,
|
||||
self.dst.id(),
|
||||
access(
|
||||
vk::PipelineStageFlags2::COPY,
|
||||
vk::AccessFlags2::TRANSFER_WRITE,
|
||||
SubresourceScopeInner::Buffer(self.dst_range),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl RecordedCommand for CopyBuffer {
|
||||
fn apply(&mut self, recorder: &mut CommandRecorder<'_>) -> Result<(), RecordError> {
|
||||
let region = vk::BufferCopy2::default()
|
||||
.src_offset(self.src_range.offset)
|
||||
.dst_offset(self.dst_range.offset)
|
||||
.size(self.src_range.size.min(self.dst_range.size));
|
||||
let regions = [region];
|
||||
let info = vk::CopyBufferInfo2::default()
|
||||
.src_buffer(recorder.buffer(self.src.id())?)
|
||||
.dst_buffer(recorder.buffer(self.dst.id())?)
|
||||
.regions(®ions);
|
||||
unsafe {
|
||||
recorder
|
||||
.device
|
||||
.cmd_copy_buffer2(recorder.command_buffer, &info)
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ClearTexture {
|
||||
pub texture: Texture,
|
||||
pub range: vk::ImageSubresourceRange,
|
||||
pub layout: vk::ImageLayout,
|
||||
pub color: [f32; 4],
|
||||
}
|
||||
|
||||
impl Command for ClearTexture {
|
||||
fn side_effects(&self, command_index: CommandIndex, map: &mut SideEffectMap) {
|
||||
map.insert(
|
||||
command_index,
|
||||
self.texture.id(),
|
||||
access(
|
||||
vk::PipelineStageFlags2::CLEAR,
|
||||
vk::AccessFlags2::TRANSFER_WRITE,
|
||||
SubresourceScopeInner::Texture {
|
||||
range: self.range,
|
||||
layout: self.layout,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl RecordedCommand for ClearTexture {
|
||||
fn apply(&mut self, recorder: &mut CommandRecorder<'_>) -> Result<(), RecordError> {
|
||||
let value = vk::ClearColorValue {
|
||||
float32: self.color,
|
||||
};
|
||||
unsafe {
|
||||
recorder.device.cmd_clear_color_image(
|
||||
recorder.command_buffer,
|
||||
recorder.image(self.texture.id())?,
|
||||
self.layout,
|
||||
&value,
|
||||
&[self.range],
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[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 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) {
|
||||
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_bind_vertex_buffers(
|
||||
recorder.command_buffer,
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resource_id(buffer: Buffer) -> ResourceId {
|
||||
buffer.id()
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,15 +0,0 @@
|
|||
//! A small render-graph skeleton following the design in `rendergraph.org`.
|
||||
#![allow(dead_code)]
|
||||
|
||||
pub mod commands;
|
||||
pub mod graph;
|
||||
pub mod recorder;
|
||||
pub mod resources;
|
||||
|
||||
pub use graph::{CompiledGraph, GraphError, RenderGraph, ResourceInterference};
|
||||
pub use recorder::{CommandIndex, CommandList, CommandRecorder, SideEffectMap};
|
||||
pub use resources::{
|
||||
Buffer, BufferRange, BufferSlice, DescriptorResourceAccess, GraphBufferDesc,
|
||||
GraphDescriptorSet, GraphTextureDesc, IndexBinding, IndexFormat, IndirectBinding,
|
||||
PushConstantData, ResourceId, Texture, VertexBinding,
|
||||
};
|
||||
3
crates/renderer_rendergraph/src/main.rs
Normal file
3
crates/renderer_rendergraph/src/main.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
|
|
@ -1,413 +0,0 @@
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
use ash::vk;
|
||||
|
||||
use crate::resources::{
|
||||
BufferRange, ResourceId, Subresource, SubresourceScope, SubresourceScopeInner,
|
||||
};
|
||||
|
||||
/// The position of a command in its command list.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct CommandIndex(pub usize);
|
||||
|
||||
impl CommandIndex {
|
||||
pub fn index(self) -> usize {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for CommandIndex {
|
||||
fn from(index: usize) -> Self {
|
||||
Self(index)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CommandIndex> for usize {
|
||||
fn from(index: CommandIndex) -> Self {
|
||||
index.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ResourceAccess {
|
||||
pub resource: ResourceId,
|
||||
pub scope: SubresourceScope,
|
||||
}
|
||||
|
||||
/// Stores every declared access together with the command that declared it.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct SideEffectMap {
|
||||
accesses: Vec<Vec<ResourceAccess>>,
|
||||
}
|
||||
|
||||
impl SideEffectMap {
|
||||
pub fn begin_command(&mut self) -> CommandIndex {
|
||||
let index = self.accesses.len();
|
||||
self.accesses.push(Vec::new());
|
||||
index.into()
|
||||
}
|
||||
|
||||
/// Record an access scope for a command and resource.
|
||||
///
|
||||
/// Each scope must describe the command's complete use for that access,
|
||||
/// not merely an enclosing bound, because `SideEffectMap::insert` may merge
|
||||
/// contained scopes for the same command and resource.
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
command_index: CommandIndex,
|
||||
resource: ResourceId,
|
||||
scope: SubresourceScope,
|
||||
) {
|
||||
if self.accesses.len() <= command_index.0 {
|
||||
self.accesses.resize_with(command_index.0 + 1, Vec::new);
|
||||
}
|
||||
let accesses = &mut self.accesses[command_index.0];
|
||||
for existing in accesses.iter_mut() {
|
||||
if existing.resource == resource {
|
||||
if existing.scope.try_merge(&scope) {
|
||||
return;
|
||||
}
|
||||
if scope.has_same_access_polarity(&existing.scope)
|
||||
&& scope.contains_subresource(&existing.scope)
|
||||
{
|
||||
let mut merged = scope;
|
||||
merged.stage |= existing.scope.stage;
|
||||
merged.access |= existing.scope.access;
|
||||
existing.scope = merged;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
accesses.push(ResourceAccess { resource, scope });
|
||||
}
|
||||
|
||||
pub fn accesses(&self, command_index: CommandIndex) -> &[ResourceAccess] {
|
||||
self.accesses
|
||||
.get(command_index.0)
|
||||
.map_or(&[], Vec::as_slice)
|
||||
}
|
||||
|
||||
pub fn command_accesses(&self) -> &[Vec<ResourceAccess>] {
|
||||
&self.accesses
|
||||
}
|
||||
}
|
||||
|
||||
/// A safe, type-erased temporary command buffer. Each command is owned until
|
||||
/// compilation/recording, while its access declarations are stored separately.
|
||||
pub struct CommandList {
|
||||
pub(crate) commands: Vec<Box<dyn RecordedCommand>>,
|
||||
pub side_effects: SideEffectMap,
|
||||
}
|
||||
|
||||
pub trait RecordedCommand {
|
||||
fn apply(&mut self, recorder: &mut CommandRecorder<'_>) -> Result<(), RecordError>;
|
||||
}
|
||||
|
||||
impl CommandList {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
commands: Vec::new(),
|
||||
side_effects: SideEffectMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push<C: crate::commands::Command + 'static>(&mut self, command: C) -> CommandIndex {
|
||||
let index = self.side_effects.begin_command();
|
||||
command.side_effects(index, &mut self.side_effects);
|
||||
self.commands.push(Box::new(command));
|
||||
index
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.commands.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.commands.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CommandList {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum RecordError {
|
||||
MissingBuffer(ResourceId),
|
||||
MissingImage(ResourceId),
|
||||
UnresolvedCreatedBuffer(ResourceId),
|
||||
UnresolvedCreatedImage(ResourceId),
|
||||
}
|
||||
|
||||
/// Vulkan recording context. Handle maps are populated by the compiled graph.
|
||||
pub struct CommandRecorder<'a> {
|
||||
pub device: &'a ash::Device,
|
||||
pub command_buffer: vk::CommandBuffer,
|
||||
pub buffers: BTreeMap<ResourceId, vk::Buffer>,
|
||||
pub images: BTreeMap<ResourceId, vk::Image>,
|
||||
}
|
||||
|
||||
impl<'a> CommandRecorder<'a> {
|
||||
pub fn new(device: &'a ash::Device, command_buffer: vk::CommandBuffer) -> Self {
|
||||
Self {
|
||||
device,
|
||||
command_buffer,
|
||||
buffers: BTreeMap::new(),
|
||||
images: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn buffer(&self, id: ResourceId) -> Result<vk::Buffer, RecordError> {
|
||||
self.buffers
|
||||
.get(&id)
|
||||
.copied()
|
||||
.ok_or(RecordError::MissingBuffer(id))
|
||||
}
|
||||
|
||||
pub fn image(&self, id: ResourceId) -> Result<vk::Image, RecordError> {
|
||||
self.images
|
||||
.get(&id)
|
||||
.copied()
|
||||
.ok_or(RecordError::MissingImage(id))
|
||||
}
|
||||
|
||||
pub fn pipeline_barrier(&self, barrier: &super::graph::PipelineBarrier) {
|
||||
let buffer_barriers: Vec<_> = barrier
|
||||
.resources
|
||||
.iter()
|
||||
.filter_map(|resource| match resource.range {
|
||||
Subresource::Buffer(BufferRange { offset, size }) => {
|
||||
self.buffers.get(&resource.id).map(|&buffer| {
|
||||
let (src, dst) = if let Some(queue_transfer) = resource.queue_transfer {
|
||||
(queue_transfer.src.index(), queue_transfer.dst.index())
|
||||
} else {
|
||||
(vk::QUEUE_FAMILY_IGNORED, vk::QUEUE_FAMILY_IGNORED)
|
||||
};
|
||||
|
||||
vk::BufferMemoryBarrier2::default()
|
||||
.src_stage_mask(resource.src_stage)
|
||||
.src_access_mask(resource.src_access)
|
||||
.dst_stage_mask(resource.dst_stage)
|
||||
.dst_access_mask(resource.dst_access)
|
||||
.src_queue_family_index(src)
|
||||
.dst_queue_family_index(dst)
|
||||
.buffer(buffer)
|
||||
.offset(offset)
|
||||
.size(size)
|
||||
})
|
||||
}
|
||||
Subresource::Texture(_) => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let image_barriers: Vec<_> = barrier
|
||||
.resources
|
||||
.iter()
|
||||
.filter_map(|resource| match resource.range {
|
||||
Subresource::Texture(range) => self.images.get(&resource.id).map(|&image| {
|
||||
vk::ImageMemoryBarrier2::default()
|
||||
.src_stage_mask(resource.src_stage)
|
||||
.src_access_mask(resource.src_access)
|
||||
.dst_stage_mask(resource.dst_stage)
|
||||
.dst_access_mask(resource.dst_access)
|
||||
.old_layout(resource.old_layout.unwrap_or(vk::ImageLayout::UNDEFINED))
|
||||
.new_layout(resource.new_layout.unwrap_or(vk::ImageLayout::GENERAL))
|
||||
.image(image)
|
||||
.subresource_range(range)
|
||||
}),
|
||||
Subresource::Buffer(_) => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let dependency = vk::DependencyInfo::default()
|
||||
.buffer_memory_barriers(&buffer_barriers)
|
||||
.image_memory_barriers(&image_barriers);
|
||||
unsafe {
|
||||
self.device
|
||||
.cmd_pipeline_barrier2(self.command_buffer, &dependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn scope_range(scope: SubresourceScope) -> Subresource {
|
||||
match scope.subresource {
|
||||
SubresourceScopeInner::Buffer(range) => Subresource::Buffer(range),
|
||||
SubresourceScopeInner::Texture { range, .. } => Subresource::Texture(range),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn read_scope(
|
||||
offset: u64,
|
||||
size: u64,
|
||||
stage: vk::PipelineStageFlags2,
|
||||
access: vk::AccessFlags2,
|
||||
) -> SubresourceScope {
|
||||
SubresourceScope {
|
||||
access,
|
||||
stage,
|
||||
subresource: SubresourceScopeInner::Buffer(BufferRange { offset, size }),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn side_effect_map_folds_same_command_scopes_in_both_containment_directions() {
|
||||
let mut map = SideEffectMap::default();
|
||||
let command = map.begin_command();
|
||||
map.insert(
|
||||
command,
|
||||
ResourceId(0),
|
||||
read_scope(
|
||||
0,
|
||||
64,
|
||||
vk::PipelineStageFlags2::VERTEX_SHADER,
|
||||
vk::AccessFlags2::SHADER_READ,
|
||||
),
|
||||
);
|
||||
map.insert(
|
||||
command,
|
||||
ResourceId(0),
|
||||
read_scope(
|
||||
16,
|
||||
16,
|
||||
vk::PipelineStageFlags2::FRAGMENT_SHADER,
|
||||
vk::AccessFlags2::UNIFORM_READ,
|
||||
),
|
||||
);
|
||||
|
||||
let accesses = map.accesses(command);
|
||||
assert_eq!(accesses.len(), 1);
|
||||
assert_eq!(
|
||||
accesses[0].scope.stage,
|
||||
vk::PipelineStageFlags2::VERTEX_SHADER | vk::PipelineStageFlags2::FRAGMENT_SHADER
|
||||
);
|
||||
assert_eq!(
|
||||
accesses[0].scope.access,
|
||||
vk::AccessFlags2::SHADER_READ | vk::AccessFlags2::UNIFORM_READ
|
||||
);
|
||||
assert!(matches!(
|
||||
accesses[0].scope.subresource,
|
||||
SubresourceScopeInner::Buffer(BufferRange {
|
||||
offset: 0,
|
||||
size: 64
|
||||
})
|
||||
));
|
||||
|
||||
let mut reverse = SideEffectMap::default();
|
||||
let command = reverse.begin_command();
|
||||
reverse.insert(
|
||||
command,
|
||||
ResourceId(1),
|
||||
read_scope(
|
||||
16,
|
||||
16,
|
||||
vk::PipelineStageFlags2::VERTEX_SHADER,
|
||||
vk::AccessFlags2::SHADER_READ,
|
||||
),
|
||||
);
|
||||
reverse.insert(
|
||||
command,
|
||||
ResourceId(1),
|
||||
read_scope(
|
||||
0,
|
||||
64,
|
||||
vk::PipelineStageFlags2::FRAGMENT_SHADER,
|
||||
vk::AccessFlags2::UNIFORM_READ,
|
||||
),
|
||||
);
|
||||
|
||||
let accesses = reverse.accesses(command);
|
||||
assert_eq!(accesses.len(), 1);
|
||||
assert_eq!(
|
||||
accesses[0].scope.stage,
|
||||
vk::PipelineStageFlags2::VERTEX_SHADER | vk::PipelineStageFlags2::FRAGMENT_SHADER
|
||||
);
|
||||
assert_eq!(
|
||||
accesses[0].scope.access,
|
||||
vk::AccessFlags2::SHADER_READ | vk::AccessFlags2::UNIFORM_READ
|
||||
);
|
||||
assert!(matches!(
|
||||
accesses[0].scope.subresource,
|
||||
SubresourceScopeInner::Buffer(BufferRange {
|
||||
offset: 0,
|
||||
size: 64
|
||||
})
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn side_effect_map_keeps_overlapping_non_subset_scopes_separate() {
|
||||
let mut map = SideEffectMap::default();
|
||||
let command = map.begin_command();
|
||||
map.insert(
|
||||
command,
|
||||
ResourceId(0),
|
||||
read_scope(
|
||||
0,
|
||||
16,
|
||||
vk::PipelineStageFlags2::VERTEX_SHADER,
|
||||
vk::AccessFlags2::SHADER_READ,
|
||||
),
|
||||
);
|
||||
map.insert(
|
||||
command,
|
||||
ResourceId(0),
|
||||
read_scope(
|
||||
8,
|
||||
16,
|
||||
vk::PipelineStageFlags2::FRAGMENT_SHADER,
|
||||
vk::AccessFlags2::UNIFORM_READ,
|
||||
),
|
||||
);
|
||||
|
||||
assert_eq!(map.accesses(command).len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn side_effect_map_does_not_fold_opposite_polarity_scopes() {
|
||||
let mut map = SideEffectMap::default();
|
||||
let command = map.begin_command();
|
||||
map.insert(
|
||||
command,
|
||||
ResourceId(0),
|
||||
read_scope(
|
||||
0,
|
||||
64,
|
||||
vk::PipelineStageFlags2::COMPUTE_SHADER,
|
||||
vk::AccessFlags2::SHADER_READ,
|
||||
),
|
||||
);
|
||||
map.insert(
|
||||
command,
|
||||
ResourceId(0),
|
||||
read_scope(
|
||||
16,
|
||||
16,
|
||||
vk::PipelineStageFlags2::COMPUTE_SHADER,
|
||||
vk::AccessFlags2::SHADER_WRITE,
|
||||
),
|
||||
);
|
||||
|
||||
let accesses = map.accesses(command);
|
||||
assert_eq!(accesses.len(), 2);
|
||||
assert!(matches!(
|
||||
accesses[0].scope.subresource,
|
||||
SubresourceScopeInner::Buffer(BufferRange {
|
||||
offset: 0,
|
||||
size: 64
|
||||
})
|
||||
));
|
||||
assert!(matches!(
|
||||
accesses[1].scope.subresource,
|
||||
SubresourceScopeInner::Buffer(BufferRange {
|
||||
offset: 16,
|
||||
size: 16
|
||||
})
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,397 +0,0 @@
|
|||
use std::{borrow::Cow, sync::Arc};
|
||||
|
||||
use ash::vk;
|
||||
use renderer_resources::{
|
||||
buffers::Buffer as BufferResource,
|
||||
images::{Image as ImageResource, ImageSubresourceExt},
|
||||
};
|
||||
|
||||
/// 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);
|
||||
|
||||
impl ResourceId {
|
||||
pub(crate) fn index(self) -> usize {
|
||||
self.0 as usize
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Buffer(pub ResourceId);
|
||||
|
||||
impl Buffer {
|
||||
pub fn id(self) -> ResourceId {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Texture(pub ResourceId);
|
||||
|
||||
impl Texture {
|
||||
pub fn id(self) -> ResourceId {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct BufferRange {
|
||||
pub offset: u64,
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
impl BufferRange {
|
||||
pub const fn full() -> Self {
|
||||
Self {
|
||||
offset: 0,
|
||||
size: vk::WHOLE_SIZE,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end(self) -> u64 {
|
||||
if self.size == vk::WHOLE_SIZE {
|
||||
u64::MAX
|
||||
} else {
|
||||
self.offset.saturating_add(self.size)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn overlaps(self, other: Self) -> bool {
|
||||
self.offset < other.end() && other.offset < self.end()
|
||||
}
|
||||
|
||||
pub fn contains(self, other: &Self) -> bool {
|
||||
self.offset <= other.offset && self.end() >= other.end()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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),
|
||||
Texture(vk::ImageSubresourceRange),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum SubresourceScopeInner {
|
||||
Buffer(BufferRange),
|
||||
Texture {
|
||||
range: vk::ImageSubresourceRange,
|
||||
layout: vk::ImageLayout,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SubresourceScope {
|
||||
pub access: vk::AccessFlags2,
|
||||
pub stage: vk::PipelineStageFlags2,
|
||||
pub subresource: SubresourceScopeInner,
|
||||
}
|
||||
|
||||
impl SubresourceScope {
|
||||
/// Whether this scope contains the other's subresource range.
|
||||
/// Texture ranges are compatible only when their layouts match.
|
||||
pub fn contains_subresource(&self, other: &Self) -> bool {
|
||||
match (self.subresource, other.subresource) {
|
||||
(SubresourceScopeInner::Buffer(a), SubresourceScopeInner::Buffer(b)) => a.contains(&b),
|
||||
(
|
||||
SubresourceScopeInner::Texture {
|
||||
range: a,
|
||||
layout: layout_a,
|
||||
},
|
||||
SubresourceScopeInner::Texture {
|
||||
range: b,
|
||||
layout: layout_b,
|
||||
},
|
||||
) => layout_a == layout_b && a.contains(&b),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether both scopes have the same read/write polarity.
|
||||
pub fn has_same_access_polarity(&self, other: &Self) -> bool {
|
||||
access_flag_is_read(self.access) == access_flag_is_read(other.access)
|
||||
&& access_flag_is_write(self.access) == access_flag_is_write(other.access)
|
||||
}
|
||||
|
||||
/// Merge sub-ranges with compatible access polarity into one scope.
|
||||
pub fn try_merge(&mut self, other: &Self) -> bool {
|
||||
if access_flag_is_read(self.access) != access_flag_is_read(other.access)
|
||||
|| access_flag_is_write(self.access) != access_flag_is_write(other.access)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let can_merge = match (self.subresource, other.subresource) {
|
||||
(SubresourceScopeInner::Buffer(a), SubresourceScopeInner::Buffer(b)) => a.contains(&b),
|
||||
(
|
||||
SubresourceScopeInner::Texture {
|
||||
range: a,
|
||||
layout: layout_a,
|
||||
},
|
||||
SubresourceScopeInner::Texture {
|
||||
range: b,
|
||||
layout: layout_b,
|
||||
},
|
||||
) => layout_a == layout_b && a.contains(&b),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if can_merge {
|
||||
self.stage |= other.stage;
|
||||
self.access |= other.access;
|
||||
}
|
||||
|
||||
can_merge
|
||||
}
|
||||
|
||||
/// Whether accesses overlap and require ordering (a write or layout change).
|
||||
pub fn conflicts(&self, other: &Self) -> bool {
|
||||
// Read-after-read accesses do not conflict, even if they overlap..
|
||||
if access_flag_is_read(self.access)
|
||||
&& !access_flag_is_write(self.access)
|
||||
&& access_flag_is_read(other.access)
|
||||
&& !access_flag_is_write(other.access)
|
||||
{
|
||||
// ..unless the layouts differ for overlapping texture subresources.
|
||||
return match (self.subresource, other.subresource) {
|
||||
(
|
||||
SubresourceScopeInner::Texture {
|
||||
layout: layout_a,
|
||||
range: a,
|
||||
},
|
||||
SubresourceScopeInner::Texture {
|
||||
layout: layout_b,
|
||||
range: b,
|
||||
},
|
||||
) => layout_a != layout_b && a.intersects(&b),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
// for accesses involving writes:
|
||||
use SubresourceScopeInner::*;
|
||||
match (self.subresource, other.subresource) {
|
||||
(Buffer(a), Buffer(b)) => a.overlaps(b),
|
||||
(Texture { range: a, .. }, Texture { range: b, .. }) => a.intersects(&b),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// True when an access mask contains a Vulkan read access.
|
||||
pub fn access_flag_is_read(access: vk::AccessFlags2) -> bool {
|
||||
access.intersects(
|
||||
vk::AccessFlags2::INDIRECT_COMMAND_READ
|
||||
| vk::AccessFlags2::INDEX_READ
|
||||
| vk::AccessFlags2::VERTEX_ATTRIBUTE_READ
|
||||
| vk::AccessFlags2::UNIFORM_READ
|
||||
| vk::AccessFlags2::INPUT_ATTACHMENT_READ
|
||||
| vk::AccessFlags2::SHADER_READ
|
||||
| vk::AccessFlags2::COLOR_ATTACHMENT_READ
|
||||
| vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_READ
|
||||
| vk::AccessFlags2::TRANSFER_READ
|
||||
| vk::AccessFlags2::HOST_READ
|
||||
| vk::AccessFlags2::MEMORY_READ
|
||||
| vk::AccessFlags2::SHADER_SAMPLED_READ
|
||||
| vk::AccessFlags2::SHADER_STORAGE_READ
|
||||
| vk::AccessFlags2::ACCELERATION_STRUCTURE_READ_KHR,
|
||||
)
|
||||
}
|
||||
|
||||
/// True when an access mask contains a Vulkan write access.
|
||||
pub fn access_flag_is_write(access: vk::AccessFlags2) -> bool {
|
||||
access.intersects(
|
||||
vk::AccessFlags2::SHADER_WRITE
|
||||
| vk::AccessFlags2::COLOR_ATTACHMENT_WRITE
|
||||
| vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_WRITE
|
||||
| vk::AccessFlags2::TRANSFER_WRITE
|
||||
| vk::AccessFlags2::HOST_WRITE
|
||||
| vk::AccessFlags2::MEMORY_WRITE
|
||||
| vk::AccessFlags2::SHADER_STORAGE_WRITE
|
||||
| vk::AccessFlags2::ACCELERATION_STRUCTURE_WRITE_KHR,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ResourceState {
|
||||
pub stage: vk::PipelineStageFlags2,
|
||||
pub access: vk::AccessFlags2,
|
||||
pub layout: Option<vk::ImageLayout>,
|
||||
}
|
||||
|
||||
impl Default for ResourceState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
stage: vk::PipelineStageFlags2::NONE,
|
||||
access: vk::AccessFlags2::NONE,
|
||||
layout: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ResourceDesc {
|
||||
CreatedBuffer(GraphBufferDesc),
|
||||
CreatedTexture(GraphTextureDesc),
|
||||
ImportedBuffer {
|
||||
resource: Arc<BufferResource>,
|
||||
initial: ResourceState,
|
||||
},
|
||||
ImportedTexture {
|
||||
resource: Arc<ImageResource>,
|
||||
initial: ResourceState,
|
||||
},
|
||||
}
|
||||
|
||||
impl ResourceDesc {
|
||||
pub fn initial_state(&self) -> ResourceState {
|
||||
match self {
|
||||
Self::ImportedBuffer { initial, .. } | Self::ImportedTexture { initial, .. } => {
|
||||
*initial
|
||||
}
|
||||
Self::CreatedBuffer(_) | Self::CreatedTexture(_) => ResourceState::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_created(&self) -> bool {
|
||||
matches!(self, Self::CreatedBuffer(_) | Self::CreatedTexture(_))
|
||||
}
|
||||
}
|
||||
|
|
@ -1027,110 +1027,3 @@ pub const SUBRESOURCERANGE_COLOR_ALL: vk::ImageSubresourceRange = vk::ImageSubre
|
|||
base_array_layer: 0,
|
||||
layer_count: vk::REMAINING_ARRAY_LAYERS,
|
||||
};
|
||||
|
||||
pub trait IntoImageSubresourceLayers {
|
||||
fn as_layers(&self) -> vk::ImageSubresourceLayers;
|
||||
}
|
||||
|
||||
pub trait IntoImageSubresourceRange {
|
||||
fn as_range(&self) -> vk::ImageSubresourceRange;
|
||||
}
|
||||
|
||||
impl IntoImageSubresourceLayers for vk::ImageSubresource {
|
||||
fn as_layers(&self) -> vk::ImageSubresourceLayers {
|
||||
vk::ImageSubresourceLayers {
|
||||
aspect_mask: self.aspect_mask,
|
||||
mip_level: self.mip_level,
|
||||
base_array_layer: self.array_layer,
|
||||
layer_count: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoImageSubresourceRange for T
|
||||
where
|
||||
T: IntoImageSubresourceLayers,
|
||||
{
|
||||
fn as_range(&self) -> vk::ImageSubresourceRange {
|
||||
self.as_layers().as_range()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoImageSubresourceRange for vk::ImageSubresourceLayers {
|
||||
fn as_range(&self) -> vk::ImageSubresourceRange {
|
||||
vk::ImageSubresourceRange {
|
||||
aspect_mask: self.aspect_mask,
|
||||
base_mip_level: self.mip_level,
|
||||
level_count: 1,
|
||||
base_array_layer: self.base_array_layer,
|
||||
layer_count: self.layer_count,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoImageSubresourceRange for vk::ImageSubresourceRange {
|
||||
fn as_range(&self) -> vk::ImageSubresourceRange {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ImageSubresourceExt {
|
||||
fn aspects(&self) -> vk::ImageAspectFlags;
|
||||
fn layers(&self) -> MipRange;
|
||||
fn mip_levels(&self) -> MipRange;
|
||||
fn contains(&self, other: &Self) -> bool {
|
||||
self.aspects().contains(other.aspects())
|
||||
&& self.layers().contains(other.layers())
|
||||
&& self.mip_levels().contains(other.mip_levels())
|
||||
}
|
||||
fn intersects(&self, other: &Self) -> bool {
|
||||
self.aspects().intersects(other.aspects())
|
||||
&& self.layers().intersects(other.layers())
|
||||
&& self.mip_levels().intersects(other.mip_levels())
|
||||
}
|
||||
}
|
||||
|
||||
impl ImageSubresourceExt for vk::ImageSubresourceLayers {
|
||||
fn aspects(&self) -> vk::ImageAspectFlags {
|
||||
self.aspect_mask
|
||||
}
|
||||
fn layers(&self) -> MipRange {
|
||||
MipRange::new(
|
||||
self.base_array_layer,
|
||||
self.base_array_layer + self.layer_count,
|
||||
)
|
||||
}
|
||||
|
||||
fn mip_levels(&self) -> MipRange {
|
||||
MipRange::single(self.mip_level)
|
||||
}
|
||||
}
|
||||
|
||||
impl ImageSubresourceExt for vk::ImageSubresourceRange {
|
||||
fn aspects(&self) -> vk::ImageAspectFlags {
|
||||
self.aspect_mask
|
||||
}
|
||||
fn layers(&self) -> MipRange {
|
||||
MipRange::new(
|
||||
self.base_array_layer,
|
||||
self.base_array_layer + self.layer_count,
|
||||
)
|
||||
}
|
||||
|
||||
fn mip_levels(&self) -> MipRange {
|
||||
MipRange::new(self.base_mip_level, self.base_mip_level + self.level_count)
|
||||
}
|
||||
}
|
||||
|
||||
impl ImageSubresourceExt for vk::ImageSubresource {
|
||||
fn aspects(&self) -> vk::ImageAspectFlags {
|
||||
self.aspect_mask
|
||||
}
|
||||
fn layers(&self) -> MipRange {
|
||||
MipRange::single(self.array_layer)
|
||||
}
|
||||
|
||||
fn mip_levels(&self) -> MipRange {
|
||||
MipRange::single(self.mip_level)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue