Compare commits

...

15 commits

Author SHA1 Message Date
Janis dbc4294c09 rendering module 2025-01-10 00:19:46 +01:00
Janis 9242d44755 tracing: debug->trace, remove present_pass
present_pass can be done by simply tagging as output with read: present()
2025-01-09 22:27:47 +01:00
Janis 2cad8adb55 optional pass record function for pure layout/transition passes 2025-01-09 22:27:47 +01:00
Janis 6274b6e5a8 rendergraph resolves faster
Access: layout is nolonger stored in option
rendergraph resolved with linear vectors instead of maps
2025-01-09 22:27:47 +01:00
Janis 3332e59453 i genuinely believe this is close to working 2025-01-09 22:27:47 +01:00
Janis 107c43ee77 it works (magic) 2025-01-09 22:27:47 +01:00
Janis efd73fce43 waiting..... 2025-01-09 22:27:47 +01:00
Janis 131887b633 quick save #2 2025-01-09 22:27:47 +01:00
Janis 146ffa654f quick save 2025-01-09 22:27:47 +01:00
Janis 3deca28391 transition imported resources at the beginning of rendergraph 2025-01-09 22:27:47 +01:00
Janis 5a1ed9340e measure rendergraph time 2025-01-09 22:27:47 +01:00
Janis 30269f7bd2 mold + debug-release profile 2025-01-09 21:52:36 +01:00
Janis 003d507573 warnings 2025-01-05 18:23:54 +01:00
Janis 0f96689079 images+commands: sync
- lock pool when allocating command buffers
- store image parent as weak arc in order to not create cycles
2025-01-05 18:22:50 +01:00
Janis 260275d694 rendergraph: barriers against write-after-read 2025-01-05 16:10:30 +01:00
10 changed files with 1033 additions and 464 deletions

3
.cargo/config.toml Normal file
View file

@ -0,0 +1,3 @@
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=/usr/bin/mold"]

View file

@ -7,6 +7,11 @@ members = [
"crates/game" "crates/game"
] ]
[profile.debug-release]
inherits = "release"
opt-level = 2
debug = true
[workspace.dependencies] [workspace.dependencies]
anyhow = "1.0.89" anyhow = "1.0.89"
ash = "0.38.0" ash = "0.38.0"

View file

@ -26,6 +26,17 @@ pub struct BufferDesc {
pub alloc_flags: vk_mem::AllocationCreateFlags, pub alloc_flags: vk_mem::AllocationCreateFlags,
} }
impl std::hash::Hash for BufferDesc {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.flags.hash(state);
self.size.hash(state);
self.usage.hash(state);
self.queue_families.hash(state);
self.mem_usage.hash(state);
self.alloc_flags.bits().hash(state);
}
}
impl std::fmt::Debug for BufferDesc { impl std::fmt::Debug for BufferDesc {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BufferDesc") f.debug_struct("BufferDesc")
@ -49,6 +60,20 @@ impl std::fmt::Debug for BufferDesc {
} }
} }
impl Eq for BufferDesc {}
impl PartialEq for BufferDesc {
fn eq(&self, other: &Self) -> bool {
self.flags == other.flags
// for hashmaps, `Eq` may be more strict than `Hash`
&& self.name == other.name
&& self.size == other.size
&& self.usage == other.usage
&& self.queue_families == other.queue_families
&& self.mem_usage == other.mem_usage
&& self.alloc_flags.bits() == other.alloc_flags.bits()
}
}
impl Default for BufferDesc { impl Default for BufferDesc {
fn default() -> Self { fn default() -> Self {
Self { Self {

View file

@ -64,6 +64,7 @@ impl SingleUseCommandPool {
} }
/// get the underlying pool, bypassing the mutex /// get the underlying pool, bypassing the mutex
#[allow(dead_code)]
pub unsafe fn pool(&self) -> vk::CommandPool { pub unsafe fn pool(&self) -> vk::CommandPool {
self.pool.data_ptr().read() self.pool.data_ptr().read()
} }
@ -129,6 +130,8 @@ pub enum CommandBufferState {
#[derive(Debug)] #[derive(Debug)]
#[repr(transparent)] #[repr(transparent)]
struct CommandBufferState2(AtomicU8); struct CommandBufferState2(AtomicU8);
#[allow(dead_code)]
impl CommandBufferState2 { impl CommandBufferState2 {
fn initial() -> Self { fn initial() -> Self {
Self(AtomicU8::new(CommandBufferState::Initial as u8)) Self(AtomicU8::new(CommandBufferState::Initial as u8))
@ -188,11 +191,13 @@ impl !Sync for SingleUseCommand {}
impl SingleUseCommand { impl SingleUseCommand {
pub fn new(device: Device, pool: Arc<SingleUseCommandPool>) -> VkResult<Self> { pub fn new(device: Device, pool: Arc<SingleUseCommandPool>) -> VkResult<Self> {
let buffer = unsafe { let buffer = unsafe {
let alloc_info = vk::CommandBufferAllocateInfo::default() let buffer = pool.pool.with_locked(|pool| {
.command_buffer_count(1) let alloc_info = vk::CommandBufferAllocateInfo::default()
.command_pool(pool.pool()) .command_buffer_count(1)
.level(vk::CommandBufferLevel::PRIMARY); .command_pool(*pool)
let buffer = device.dev().allocate_command_buffers(&alloc_info)?[0]; .level(vk::CommandBufferLevel::PRIMARY);
Ok(device.dev().allocate_command_buffers(&alloc_info)?[0])
})?;
device.dev().begin_command_buffer( device.dev().begin_command_buffer(
buffer, buffer,

View file

@ -184,7 +184,7 @@ pub fn egui_pre_pass(
rg.import_image( rg.import_image(
img, img,
Access { Access {
layout: Some(vk::ImageLayout::GENERAL), layout: vk::ImageLayout::GENERAL,
..Access::undefined() ..Access::undefined()
}, },
), ),
@ -229,12 +229,12 @@ pub fn egui_pre_pass(
Access { Access {
stage: vk::PipelineStageFlags2::NONE, stage: vk::PipelineStageFlags2::NONE,
mask: vk::AccessFlags2::empty(), mask: vk::AccessFlags2::empty(),
layout: Some(vk::ImageLayout::UNDEFINED), layout: vk::ImageLayout::UNDEFINED,
}, },
Access { Access {
stage: vk::PipelineStageFlags2::TRANSFER, stage: vk::PipelineStageFlags2::TRANSFER,
mask: vk::AccessFlags2::TRANSFER_WRITE, mask: vk::AccessFlags2::TRANSFER_WRITE,
layout: Some(vk::ImageLayout::TRANSFER_DST_OPTIMAL), layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
}, },
None, None,
) )
@ -270,12 +270,12 @@ pub fn egui_pre_pass(
Access { Access {
stage: vk::PipelineStageFlags2::TRANSFER, stage: vk::PipelineStageFlags2::TRANSFER,
mask: vk::AccessFlags2::TRANSFER_WRITE, mask: vk::AccessFlags2::TRANSFER_WRITE,
layout: Some(vk::ImageLayout::TRANSFER_DST_OPTIMAL), layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
}, },
Access { Access {
stage: vk::PipelineStageFlags2::TRANSFER, stage: vk::PipelineStageFlags2::TRANSFER,
mask: vk::AccessFlags2::TRANSFER_READ, mask: vk::AccessFlags2::TRANSFER_READ,
layout: Some(vk::ImageLayout::TRANSFER_SRC_OPTIMAL), layout: vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
}, },
None, None,
); );
@ -285,12 +285,12 @@ pub fn egui_pre_pass(
Access { Access {
stage: vk::PipelineStageFlags2::NONE, stage: vk::PipelineStageFlags2::NONE,
mask: vk::AccessFlags2::empty(), mask: vk::AccessFlags2::empty(),
layout: Some(vk::ImageLayout::GENERAL), layout: vk::ImageLayout::GENERAL,
}, },
Access { Access {
stage: vk::PipelineStageFlags2::TRANSFER, stage: vk::PipelineStageFlags2::TRANSFER,
mask: vk::AccessFlags2::TRANSFER_WRITE, mask: vk::AccessFlags2::TRANSFER_WRITE,
layout: Some(vk::ImageLayout::TRANSFER_DST_OPTIMAL), layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
}, },
None, None,
); );
@ -334,12 +334,12 @@ pub fn egui_pre_pass(
Access { Access {
stage: vk::PipelineStageFlags2::TRANSFER, stage: vk::PipelineStageFlags2::TRANSFER,
mask: vk::AccessFlags2::TRANSFER_WRITE, mask: vk::AccessFlags2::TRANSFER_WRITE,
layout: Some(vk::ImageLayout::TRANSFER_DST_OPTIMAL), layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
}, },
Access { Access {
stage: vk::PipelineStageFlags2::ALL_COMMANDS, stage: vk::PipelineStageFlags2::ALL_COMMANDS,
mask: vk::AccessFlags2::empty(), mask: vk::AccessFlags2::empty(),
layout: Some(vk::ImageLayout::GENERAL), layout: vk::ImageLayout::GENERAL,
}, },
None, None,
) )
@ -374,13 +374,13 @@ pub fn egui_pre_pass(
( (
*id, *id,
Access { Access {
layout: Some(vk::ImageLayout::GENERAL), layout: vk::ImageLayout::GENERAL,
..Access::undefined() ..Access::undefined()
}, },
) )
}) })
.collect(), .collect(),
record, record: Some(record),
}); });
Ok(()) Ok(())
} }
@ -730,7 +730,7 @@ pub fn egui_pass(
rg.add_pass(PassDesc { rg.add_pass(PassDesc {
reads, reads,
writes, writes,
record, record: Some(record),
}); });
Ok(to_remove_tex_ids) Ok(to_remove_tex_ids)

View file

@ -1,4 +1,8 @@
use std::{borrow::Cow, collections::HashMap, sync::Arc}; use std::{
borrow::Cow,
collections::HashMap,
sync::{Arc, Weak},
};
use crate::{ use crate::{
define_device_owned_handle, define_device_owned_handle,
@ -33,7 +37,6 @@ pub struct ImageDesc {
impl std::hash::Hash for ImageDesc { impl std::hash::Hash for ImageDesc {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.flags.hash(state); self.flags.hash(state);
self.name.hash(state);
self.format.hash(state); self.format.hash(state);
self.kind.hash(state); self.kind.hash(state);
self.mip_levels.hash(state); self.mip_levels.hash(state);
@ -128,8 +131,8 @@ define_device_owned_handle! {
format: vk::Format, format: vk::Format,
views: Mutex<HashMap<ImageViewDesc, vk::ImageView>>, views: Mutex<HashMap<ImageViewDesc, vk::ImageView>>,
aliases: Mutex<HashMap<ImageDesc, Arc<Image>>>, aliases: Mutex<HashMap<ImageDesc, Arc<Image>>>,
parent: Option<Arc<Image>>, parent: Option<Weak<Image>>,
is_swapchain_image:bool, is_swapchain_image: bool,
} => |this| if !this.is_swapchain_image { } => |this| if !this.is_swapchain_image {
unsafe { unsafe {
for &view in this.views.lock().values() { for &view in this.views.lock().values() {
@ -266,20 +269,19 @@ impl Image {
self.size.depth self.size.depth
} }
fn get_parent(self: &Arc<Self>) -> Arc<Image> { fn get_parent_or_self(self: &Arc<Self>) -> Arc<Image> {
self.parent.clone().unwrap_or_else(|| self.clone()) self.parent
}
fn get_alloc(&self) -> Option<&vk_mem::Allocation> {
self.alloc
.as_ref() .as_ref()
.or(self.parent.as_ref().and_then(|image| image.get_alloc())) .map(|weak| weak.upgrade().unwrap())
.unwrap_or_else(|| self.clone())
} }
pub unsafe fn get_alias(self: &Arc<Self>, desc: ImageDesc) -> VkResult<Arc<Self>> { pub unsafe fn get_alias(self: &Arc<Self>, desc: ImageDesc) -> VkResult<Arc<Self>> {
self.get_parent().get_alias_inner(desc) self.get_parent_or_self().get_alias_inner(desc)
} }
/// must only be called on the primogenitor of an image.
/// get the primogenitor with [`Self::get_parent_or_self()`]
unsafe fn get_alias_inner(self: Arc<Self>, desc: ImageDesc) -> VkResult<Arc<Image>> { unsafe fn get_alias_inner(self: Arc<Self>, desc: ImageDesc) -> VkResult<Arc<Image>> {
use std::collections::hash_map::Entry::*; use std::collections::hash_map::Entry::*;
match self.aliases.lock().entry(desc.clone()) { match self.aliases.lock().entry(desc.clone()) {
@ -327,7 +329,8 @@ impl Image {
.mip_levels(mip_levels); .mip_levels(mip_levels);
let alloc = self let alloc = self
.get_alloc() .alloc
.as_ref()
.expect("no alloc associated with image. is this the framebuffer?"); .expect("no alloc associated with image. is this the framebuffer?");
let image = unsafe { let image = unsafe {
@ -342,7 +345,6 @@ impl Image {
image image
}; };
let parent = self.parent.clone().unwrap_or(self.clone());
let alias = Self::construct( let alias = Self::construct(
self.device().clone(), self.device().clone(),
image, image,
@ -352,8 +354,8 @@ impl Image {
format, format,
Mutex::new(HashMap::new()), Mutex::new(HashMap::new()),
Mutex::new(HashMap::new()), Mutex::new(HashMap::new()),
Some(parent.clone()), Some(Arc::downgrade(&self)),
parent.is_swapchain_image, self.is_swapchain_image,
)?; )?;
Ok(vacant.insert(Arc::new(alias)).clone()) Ok(vacant.insert(Arc::new(alias)).clone())
} }

View file

@ -43,6 +43,7 @@ mod egui_pass;
mod images; mod images;
mod pipeline; mod pipeline;
mod render_graph; mod render_graph;
mod rendering;
mod sync; mod sync;
mod util; mod util;
@ -441,6 +442,7 @@ impl PhysicalDeviceProperties {
pub struct PhysicalDevice { pub struct PhysicalDevice {
pdev: vk::PhysicalDevice, pdev: vk::PhysicalDevice,
queue_families: DeviceQueueFamilies, queue_families: DeviceQueueFamilies,
#[allow(dead_code)]
properties: PhysicalDeviceProperties, properties: PhysicalDeviceProperties,
} }
@ -729,7 +731,7 @@ impl Swapchain {
format, format,
) )
.inspect(|img| { .inspect(|img| {
img.get_view(images::ImageViewDesc { _ = img.get_view(images::ImageViewDesc {
name: Some(format!("swapchain-{swapchain:?}-image-{i}-view").into()), name: Some(format!("swapchain-{swapchain:?}-image-{i}-view").into()),
kind: vk::ImageViewType::TYPE_2D, kind: vk::ImageViewType::TYPE_2D,
format, format,
@ -2534,7 +2536,6 @@ impl<W> Renderer<W> {
let mut rg = render_graph::RenderGraph::new(); let mut rg = render_graph::RenderGraph::new();
let (textures_to_remove, cmds) = util::timed("record command buffer", || { let (textures_to_remove, cmds) = util::timed("record command buffer", || {
let framebuffer = rg.import_image(frame.image.clone(), Access::undefined()); let framebuffer = rg.import_image(frame.image.clone(), Access::undefined());
rg.mark_as_output(framebuffer);
render_graph::clear_pass(&mut rg, clear_color, framebuffer); render_graph::clear_pass(&mut rg, clear_color, framebuffer);
egui_pass::egui_pre_pass( egui_pass::egui_pre_pass(
@ -2555,7 +2556,8 @@ impl<W> Renderer<W> {
output, output,
framebuffer, framebuffer,
)?; )?;
render_graph::present_pass(&mut rg, framebuffer);
rg.mark_as_output(framebuffer, Access::present());
Result::Ok((textures_to_remove, rg.resolve(dev.clone())?)) Result::Ok((textures_to_remove, rg.resolve(dev.clone())?))
})?; })?;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@

View file

@ -414,3 +414,100 @@ impl<'a, T: 'a> DerefMut for WithLifetime<'a, T> {
&mut self.0 &mut self.0
} }
} }
bitflags::bitflags! {
pub struct PipelineAccess: u32 {
const TRANSFER = 1 << 0;
const VERTEX_ATTRIBUTE_INPUT = 1 << 1;
const DRAW_INDIRECT = 1 << 2;
const VERTEX_INPUT = 1 << 3;
}
}
#[derive(Debug, Clone)]
pub struct BitIter<'a> {
bits: &'a [u64],
num_bits: usize,
bit_offset: usize,
bit_index: usize,
}
impl<'a> std::fmt::Display for BitIter<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BitIter")
.field_with("bits", |f| {
write!(f, "[")?;
for bit in self.clone() {
write!(f, "{bit}, ")?;
}
write!(f, " ]")
})
.finish()
}
}
impl<'a> BitIter<'a> {
pub fn new(bits: &'a [u64], num_bits: usize) -> Self {
Self {
bits,
num_bits,
bit_index: 0,
bit_offset: 0,
}
}
pub fn chunks(self, chunk_size: usize) -> ChunkedBitIter<'a> {
ChunkedBitIter {
inner: self,
chunk_size,
pos: 0,
}
}
}
impl Iterator for BitIter<'_> {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
loop {
if self.bit_index >= self.num_bits {
return None;
}
let bit_index = self.bit_index + self.bit_offset;
let byte_idx = bit_index / 64;
let byte_offset = bit_index % 64;
self.bit_index += 1;
if (self.bits[byte_idx] >> byte_offset) & 1 == 1 {
return Some(self.bit_index - 1);
}
}
}
}
#[derive(Debug)]
pub struct ChunkedBitIter<'a> {
inner: BitIter<'a>,
chunk_size: usize,
pos: usize,
}
impl<'a> Iterator for ChunkedBitIter<'a> {
type Item = BitIter<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.pos >= self.inner.num_bits {
return None;
}
let bits = (self.inner.num_bits - self.pos).min(self.chunk_size);
let iter = BitIter {
bits: &self.inner.bits[self.pos / 64..],
bit_offset: self.pos % 64,
bit_index: 0,
num_bits: bits,
};
self.pos += bits;
Some(iter)
}
}