#DARE (Danny's Awesome Rendering Engine)
1 messages ยท Page 7 of 1
okay now it's time to lock in
bindless
@devout mesa sorry for pinging again but uh why do you only allocate 1 ssbo descriptor buffer?
the rest is bda
oh wait yeah
i assume you just use that buffer to hold a giant array of bda pointers...?
yes
ok thanks lvstri
dude descriptor sets are pain
AHHH
how did it work again
that free list allocator thingy
NOOOO
the code is gone
whatever ill fucking throw in a slot map allocator and call it a day
nuh uh im throwing a slot map
wait so for bindless
are you making a descriptor set PER each PoolSize
or are you making one massive monolithic descriptor set?
you have too much faith in me
hint: I like massive things
yes
i was led astray by embark studios smh
uh
how did you expand the address buffer...
expand?
with this
ahh wait i found the code
god im so fucking jealous
actually storing pointers in your class without going into compiler error hell
๐ฆ struggles
meme lang
im kinda jealous CPP lets you indicate conditional for your templates
one day you'll come back
wait why did you switch from free list to slot allocation
easier
can you like "unbind" a descriptor set
or is it just not worth it to do that for bindless since at the end of the day, defined behavior is that you should never get a dead descriptor
descriptor or descriptor set
descriptor
What is the difference between free list and slot allocation?
dawg wtfff
thread 'main' panicked at dare\src\main.rs:210:88:
called `Result::unwrap()` on an `Err` value: Requested feature is not available on this device
๐
how does memory allocation
even invoke this error
you know
maybe ill give up on bindless for now
oh yeah wtf does RETINA_FALLTHROUGH mean in a switch case?
like you use the next case below?
it's just [[fallthrough]]
so yes
you know maybe i should bite the bullet and actually use vma's built in allocate buffer but that kinda sucks
since gpu allocator makes no distinction ๐
WAITT
GPU_ALLOCATOR HAS AN UP TO DATE BRANCG
LETS GOOO

layout(set = 0, binding = 3) readonly buffer BDA {
uint64_t buffer_addresses[];
};
HMMMMM
why is this syntax error
unexpected identifier uhh
WAIT
FUCK LKMAOO
i forgot to enable the 64 uint extensions
AHHH
IT WORKS
BINDLESS
AHAHAHAA
IT WORKS
#version 450
#extension GL_EXT_buffer_reference : require
#extension GL_EXT_nonuniform_qualifier : require
#extension GL_ARB_gpu_shader_int64 : enable
layout(set = 0, binding = 0) uniform sampler samplers;
layout(set = 0, binding = 1) uniform texture2D sampled_images[];
layout(set = 0, binding = 2, r32f) uniform image2D storage_images[];
layout(set = 0, binding = 3) readonly buffer BDA {
uint64_t buffer_addresses[];
};
layout (location = 0) out vec3 outColor;
layout (location = 1) out vec2 outUV;
struct Vertex {
vec3 position;
float uv_x;
vec3 normal;
float uv_y;
vec4 color;
};
layout(buffer_reference, std430) readonly buffer VertexBuffer{
Vertex vertices[];
};
//push constants block
layout( push_constant ) uniform constants
{
mat4 render_matrix;
uint vertex_buffer_id;
} pc;
void main()
{
//load vertex data from device adress
VertexBuffer vertex_buffer = VertexBuffer(buffer_addresses[pc.vertex_buffer_id]);
Vertex v = vertex_buffer.vertices[gl_VertexIndex];
//output data
gl_Position = pc.render_matrix * vec4(v.position, 1.0f);
outColor = v.color.xyz;
outUV.x = v.uv_x;
outUV.y = v.uv_y;
}
LETS FUCKING GOO
LAHAHAHAHA
tears in my eyes
i should probably render out deccer's thingies
@devout mesa is it fine if i literally yoink your descriptor abstractions
sorry for pinging btw
my license is "you can literally claim my work as yours if you want"
yes
mmm
ok
hm in glsl can you not redefine your types 
#define u64 uint64_t
pain.
layout(set = 0, binding = 3) readonly buffer BDA {
u64 buffer_addresses[];
};
Does not work :(
lol
now i need a good glsl linter

i forgot there is none
everyday we inch closer to making c ffi bindings for slang
slang is a c api yeah?
it does have one
use a superior language? 
nuh uh
rust may make me cry
but i won't have to cry searching for some stupid double free or memory bug
also i wanna finish the cpp guidelines book so i can code as the way god, Bjarne Stroustrup, intended
also cpp is so fucking annoying as literally there is so much implicit fucking behavior ๐
no. Use slang
no hes uuuuuuuuuuuh nah no no bad
๐
use c++ like c and use c++ features with restraint
yea and everything is convoluted
and undefined
stick with rust
you will live like 30 years longer then us

C++ makes you younger 
you are biologically 80
sorry
it wont be much longer
thank god
Waut
i might've fucked up ๐
When making bindless handles
should they literally also hide the resource behind them?
or should they just be handles strictly for bindless and are not reliant on the resource being hidden behind them?
seems like lvstri handles bindless as a massive storage system for all resources
ahh alright

/// Represents a bindless resource held
///
/// T: The resource type
/// A: The resource that is being aliased by T
///
/// This dual type system allows us to effectively alias the return type of the free list.
#[derive(Debug, Clone)]
pub struct GPUResourceTableHandle<T: Clone + Destructible, A: Clone + Destructible = T> {
handle: Handle<T>,
free_list: FreeList<A>,
}
GPUResourceTableHandle<TypedBuffer<T>, Buffer>
this seems so
uh
unergonomic
maybe i just say skill issue and just make all handles untyped 
why does the handle have a free list wtf
so i can do GPUResourceTableHandle.destroy() 
probably makes more sense to not do that
but like
???
?
each handle has its own free list?
ok
#[derive(Debug)]
struct FreeListInner<T> {
resources: Vec<Option<T>>,
free_ids: Vec<u64>,
_marker: PhantomData<T>
}
#[derive(Debug, Clone)]
pub struct FreeList<T> {
inner: Arc<RwLock<FreeListInner<T>>>,
}
there's no &
yeah because FreeList is literally just a pointer
I'll allow it
but table.destroy(handle) is better
yeah probably since it'll be easier to handle when they get deallocated
storing anything other than a u64 in a handle is dum
but that defeats the purpose of having handles
just have objects if you want that
mmm i hate rust
i think i'm just doing this wrong 
god dammit do i just have to live with
not being able to throw everything into a deletion stack
fml
this has nothing to do with rust
no it doesn't i just have a skill issue-ish
it's just annoying if i wanna reference stuff
i need to hail marry wrap it around some shared pointer and interior mutability/sync locking
MMMMMM
discovered my slot map implementation was fucked
hence causing a 50% chance of my program to crash
nvm
man i uh kinda
forgotten how to get my sampler2D again
layout(set = 0, binding = 0) uniform sampler samplers[];
layout(set = 0, binding = 1) uniform texture2D sampled_images[];
layout(set = 0, binding = 2, r32f) uniform image2D storage_images[];
layout(set = 0, binding = 3) readonly buffer BDA {
uint64_t buffer_addresses[];
}

I FUCKING DID
IT
AHAHAAHHA
i can see it
i can hear the frames
all while being 100% bindless
man
it seems like the architecture of the vkguide engine is uh
not that nice in rust 
its not bad
i wanted to roll my own thingy but its not really ecs
its tied into the event system thing i wrote
not sure if i wanna do an event system
i mean iirc it's very easy to quickly implement ecs in it
so is adding event sys to ecs
Now that I think of it
how do you even implement a renderer in ecs
Nvm make a RenderDevice context which handles all interactions the gpu. Have a command submission system which runs last to all โRenderableโ entities and submits all commands on a single thread
penguin you think it's advisable to like wrap my allocator in Arc<RwLock<A>>?
#[derive(Debug, Clone)]
struct GPUAllocatorImpl {
allocator: Arc<RwLock<GPUAllocator>>
}
impl Allocator for GPUAllocatorImpl {...}
So I can do something like:
#[derive(Debug, Clone)]
struct ArcAllocation<A: Allocator> {
allocator: A,
allocation: Arc<RwLock<Option<A::Allocation>>>
}
impl<A: Allocator> Drop for ArcAllocator<A> {
fn drop(&mut self) {
self.allocator.free(self.allocation.take().unwrap()); // not 100% right but you get the point
}
}
@tropic vigil sorry for pinging
allocator sure, but I wouldnt wrap the actual allocation
you dont have shared mutable access to an allocation
RefCell?
wait mutex works
hmm i mean yeah but i liked being handle to clone my vk resources 
#[derive(Clone)]
struct Image {
allocation: Option<ArcAllocation<...>>
}
no your resources should be owned uniquely
Or you get a mess
Alternatively add Arc<Image> or something but keep Image itself clean of refcounters
why not
my entire engine was built around the idea i could abuse the opaque pointers to clone them ininfitely
it was built with C++ brain 
i yearn for the unsafe pointers
maybe lvstri was right
no i want to be sane im staying in rust 
i mean i could like "daxa" it
and like wrap everything inside an Option<>
to prevent use after free issues
FUCK
even wgpu uses arc
kms
nuh uh
safety is premature optimization if you think about it
therefore i will not optimize
im sorry penguin
nah now im stuck bikeshedding i can't pick
ok wait so
theres a legitimate reason to Arc sometimes
For example your global caches or allocators need to be passed around everywhere and referenced in multiple places, totally fine to Arc
Your entire application shares ownership of this resource
When you use Arc<>, you're using std::shared_ptr. You're saying "Everyone owns this resource!"
The problem with this is that it becomes hard (impossible) to reason about lifetimes, because anywhere in the program you might still have a reference to your object
For a global allocator this is fine because its lifetime is tied to the program lifetime
For an image or a buffer however, it needs to be allocated and freed more commonly
If you use shared ownership, it should only be freed when no references exist anymore, but this is pretty hard to guarantee
If you don't guarantee that and just .destroy() or whatever, you get use-after-free errors
Structuring your program around proper unique ownership can be a bit tricky, but it's very clean and useful once it's done and the compiler will help a lot with it
Ah alright I'll probably have a try at making a nicer ownership system
Where exactly do you run into issues with it rn
Main reason I'm clinging onto cloning so much is that it's so nice having a deletion queue
You can still do deletion queues with unique ownership
Right now my deletion queue just holds a clone of the underlying resource and a ref count to the device then deletes it once it is flushed
It's just a little different
Instead of that, you transfer ownership of the resource to the deletion queue
It then holds on to it for a few frames, and deletes it
impl DeletionQueue<T> {
fn push(resource: T) {
self.queue.push(resource);
}
}
struct Image {}
fn main() {
let image = Image::new();
// do some stuff with image
// ok im done
// image moved into queue, now no longer accessible.
// it is owned by the queue instead of by main()
deletion_queue.push(image);
}
Hmmm
What if I need to reuse said image? Would I not end up in a weird situation where all my resources are only cleaned up until the very end?
If you want to reuse it you shouldn't delete it ๐
You can keep the underlying allocation, gpu-allocator or vk-mem-rs already do this
ill think about it more
It takes a little conscious effort at first but it's really one of the things that makes Rust worth it
Hm guess I've kinda been treating the deletion queue as this giant "Throw this resoruce into it" and forget about how you manage said lifetime 
What I use it for is just as a way to delay running Drop for some frames until the GPU is done with it, because there's no way to know that in Rust. For all other cases the compiler can know about it
Ah your deferred deletor kinda makes sense now 
man I kinda liked the old way though since it wouldn't be hard at all to deal with order of deletion
It still isn't really, it's the same as C++
If you need to delete A before B, either
- Store A inside B
- Store A alongside B so the
Droporder is right
yeah true i just gotta handle the organization of data more carefully

seems like wgpu
uses refcounting
apperantly that's what most wgpu apps are bounded by 
feel so bad for them 
But it makes sense since web support
Yeah
I mean deferred deletion works but what if I need an object still once it's been pushed into a deletion queue?
i.e. an image view to create an attachment for dynamic rendering or would i just delete that last
eh i guess i delete it last
i'll just organize my device to be last in the struct so everything gets deleted before it
what if I need an object still once it's been pushed into a deletion queue?
Then it shouldn't be pushed into the queue
oh god
my free list allocators are all broken 
probably should've returned a reference
You can't return a reference to something inside an object in rust
something something self referential struct
Theres a reasonable explanation for it but every attempt to get around it the compiler will slap you
nah wait i guess in this case the context it's being used (bindless descriptors)
it probably makes sense to keep it as an arc
Probably
Here's a typical example
struct S {
thing: Mutex<i32>
}
impl S {
fn lock<'a>(&'a self) -> LockGuard<'a, i32> {
return thing.lock(); // error, return value may outlive struct
}
}
i mean could you not use lifetime'ism?
A good attempt, but the rust compiler does not like being tricked
Not all hope is lost however
There is a nice solution
struct S {
thing: Mutex<i32>
}
impl S {
fn with_lock<R>(&self, f: impl FnOnce(&mut i32) -> R) -> R {
let value = thing.lock(); // LockGuard<i32>
f(*value)
}
}
fn test() {
let s = S::new();
let value = s.with_lock(|value| {
value += 1;
value
});
}
This is how I deal with assets btw
oh 
yeah that works but uh seems like that will run me back into cloning hell with all the vk resources i would need to copy,clone,etc. into that closure
You don't need to move or copy them
You can just pass by ref
/// Calls the provided callback function with the asset corresponding to the given handle and returns its result.
/// Does not call the function if the asset was not found, and returns None instead.
pub fn with<A, R, F>(&self, handle: Handle<A>, f: F) -> Option<R>
where
A: Asset + Send + 'static,
F: FnOnce(AssetRef<A>) -> R, {
// To access an asset we only need read access, so acquire a read lock to
// the correct container
self.with_container(|container| {
// Look up the entry in the container, and call the function on a reference
// to it if it exists.
let entry = container.items.get(handle);
entry.map(|entry| f(entry.as_ref()))
})
}
Here is the pattern used with assets
fn load_new_mesh(
old: Handle<Terrain>,
options: TerrainOptions,
bus: EventBus<DI>,
) -> Result<Terrain> {
let di = bus.data().read().unwrap();
let assets = di.get::<AssetStorage>().unwrap();
assets
.with_when_ready(old, |terrain| {
let di = bus.data().read().unwrap();
let assets = di.get::<AssetStorage>().unwrap();
let mesh = assets.load(options);
Ok(Terrain {
height_map: terrain.height_map,
normal_map: terrain.normal_map,
diffuse_map: terrain.diffuse_map,
mesh,
})
})
.ok_or_else(|| anyhow!("error creating terrain from old terrain: old terrain is invalid"))?
}
Example usage
with_when_ready is like with but it waits for the asset to load if it was still loading
I kinda shot myself in the foot by cloning my vk handles
It's by far the cleanest implementation of an asset store I've done over the years
its uh
hmmm
i mean i could go down that route with my bindless descriptor manager
since it kinda becomes a glorified resource holder
Should say it's a little more high level than raw vk stuff but yeah the same principles can apply
It also does some fun bits of type erasure
ok so free list arcing probably isn't gonna work 
man im too deep into the engine however this is like a giant headache 
how is a AssetRef different from a regular ref
pub unsafe fn untyped_with_handle<A, R, F: FnOnce(&T) -> R>(&self, handle: &Handle<A>, f: F) -> Result<R> {
if unsafe { !self.untyped_is_valid(handle)? } {
return Err(anyhow::Error::from(errors::Errors::InvalidHandle));
}
self
.inner
.read()
.map_err(|_| {
anyhow::Error::from(crate::DagalError::PoisonError)
})?
.resources.get(handle.id as usize)
.unwrap()
.as_ref()
.map_or(Err(anyhow::Error::from(crate::DagalError::PoisonError)), |data| Ok(f(data)))
}
man
you know this sounds cursed
but in a free list i realistically do not care for the contents of a data: Vec<T> and just that they exist
is there a way to seperate the mutexes?
so like if i were to modify an element in T, it wouldn't end up locking up the entire Vec<T>
nah wait that's a horrible idea
what if i commit to the resource stack meme
and instead offer
push_handle_with_free_list()
holds a copy of the handle, and a ref of the free list allocator
then deletes it ๐
Why do you have an unsafe block in an unsafe function
Either don't mark the function as unsafe or remove the block
natural instincts from calling ash device 
are there like
any resources
on lifetime management so i can watch them
you know
struct TypedBuffer<'a, T> {
handle: &'a mut T,
_marker: PhantomDat<T>,
}
I just wanna be able to quickly wrap a Buffer over and have some type safety but this seems a little too niche
nah i should have it so i can seamlessly convert between and from TypedBuffers to regular Buffers rather than needlessly restricting TypedBuffers like this
bro
FUCK
i'm gonna end up with a monolithic app Drop function 
handle
reference
shush
yeah
Alright
What I've found is that trying to make views like ImageViews somehow have a lifetime reference to their owning Image gets really clunky sadly
I'd love to do it but I haven't found a good way
A typed buffer is not too different, it's just another view
yeah hmmm
ah
But you could totally have like
struct TypedBuffer<T> {
buffer: vk::Buffer
_marker: PhantomData<T>
}
impl Buffer {
pub fn into_typed<T>(self) -> TypedBuffer<T> {
return TypedBuffer {
buffer: self.buffer,
// include metadata etc
_marker: PhantomData::new()
}
}
}
Just having the typed variant own the buffer works
But then it's easier to just make your buffers typed by default and then alias Buffer to TypedBuffer<()>
Yeah
since aliasing a typedbuffer is gonna be hell
I mean it's just type Buffer = TypedBuffer<()>
The only thing you have to make sure is that things that require your type to be Sized or have other properties need to be in a different impl block
impl<T> TypedBuffer<T> where T: Sized {
// Stuff that needs a 'valid' type T
}
impl<T> TypedBuffer<T> {
// Things that work on Buffers even if T = ()
}
Though you can't specialize functions this way
ahh fuck it i guess i could force a typed buffer. it's just nicer to get that type safety
It is
accidentially submitting indices to my vertex buffer 
Template specialization is something I'd love to have in Rust but sadly it's not that close
it was also my fault too as i ended up hail marrying it since i wanted to suballocate it all
so i started throwing out u8 
More type safety is always good
dude i hate PhantomData
It's 100% a skill issue but for some fucking reason despite it literally being marked as phantom data, rust will complain the generic does not implement x,y,z when it's only used in the phantom data
yeah probably which is why im saying it's a skill issue 
i forgot rust
is really rigid

copium rust's new borrow checker will help
iirc polonious or smth like that
Is that in stable nowadays
i have no clue
yeah i checked on it seems like development is halted since it relies on other improvements
since it's like being refactored to work at a lower/higher level (i think mir??)
Makes sense
god i need it
since rust doesn't have partial borrowing at all
which is so fucking annoying
maybe the solution is another programming language

The problem as always with Rust features is that even the smallest change has a million edge cases that need to be considered

yeah and also it's just slow as hell to iterate with
Which makes it take a long time for new things to be standardized (see for example const generics)
Or for a more recent thing, async fn in traits
like rust is nowhere close to ever being used for game dev 
It seems so harmless but has a huge heap of implications
oh yeah true
uh
/// Write to a mapped pointer if one exists
pub fn write(&mut self, offset: vk::DeviceSize, data: &[T]) -> Result<()> {
if offset + (mem::size_of_val(data) as vk::DeviceSize) < self.size {
return Err(anyhow::Error::from(crate::DagalError::InsufficientSpace));
}
if let Some(mapped_ptr) = self.mapped_ptr() {
unsafe {
let data_ptr = data.as_ptr() as *const _ as *const c_void;
let mapped_ptr = mapped_ptr.as_ptr().add(offset as usize);
ptr::copy_nonoverlapping(data_ptr, mapped_ptr, data.len());
}
Ok(())
} else {
Err(anyhow::Error::from(crate::DagalError::NoMappedPointer))
}
}
so like wtf would
&[()] do 
nothing
makes sense im just more concerned about type coercion since () is supposed to represent no-type
although maybe it's a good idea i force you to use unsafe to arbitrarily upload any data then
btw add some //SAFETY: comments to explain why what you're doing is safe
Standard practice is to add a big comment to any unsafe block which details why what you're doing is completely safe in every scenario
It's also a good way to make sure for yourself
ohh gotcha will do thanks
tbh, I would probably just remove the write function and instead do
pub fn mapped_slice<T>(&mut self) -> Result<&mut [T]> {
if let Some(pointer) = self.pointer {
Ok(unsafe {
std::slice::from_raw_parts_mut(
pointer.cast::<T>().as_ptr(),
self.size as usize / std::mem::size_of::<T>(),
)
})
} else {
Err(anyhow::Error::from(Error::UnmappableBuffer))
}
}
//SAFETY: this is never safe, fuck you```
average C++ program
real

This would crash big time if T = () because of a division by zero
Actually
Your code probably would too
let data_ptr = data.as_ptr() as *const _ as *const c_void;
let mapped_ptr = mapped_ptr.as_ptr().add(offset as usize);
ptr::copy_nonoverlapping(data_ptr, mapped_ptr, data.len());
If T = (), then the total size in bytes of the array might be zero bytes, but data.len() won't return 0
(It's also wrong because data.len() is just the amount of items to be copied, but you are copying bytes (c_voids but okay) so your count is off)
tldr it bogus
Yeah probably
I like my function because it returns an actual slice
Which you can do easy and safe operations on
hm i have no clue how to deal with the TypedBuffer<()> issue
do i just do some unimplemented! panic
man all roads point back to just doing a .mapped_ptr::<T>()
You simply do not implement the method for TypedBuffer<()>
wait
() doesn't implement Sized
It does
Well
There is T: !MyTrait but you can only use it on builtin traits
Otherwise you could just use
trait Unit {}
impl Unit for () {}
fn foo<T: !Unit>(arr: &[T]) {
}
fn main() {
foo(&[(), (), ()]);
}
If you could write this it would but sadly you can't
Yeah that's not possible, only with builtin traits like Sized, Send, Sync
grabbing a slice seems better then
yeah probably
Since a slice of () is valid, though you'd never actually call that because it's nonsense
"hmm yes I'd like a view of this GPU buffer of nothing please"
Oh yeah you can do that in the function
I was just hoping there'd be a nice way of expressing that as a trait bound
just throw some if size_of::<T> == 0 { unimplemented!() } expept for unsafe fn write_arbitrary
Which would trigger a compiler error
yeah me too
i'm probably not gonna do that tbfh
but atp i might as well mapped_ptr::<T>
dude i fucking overengineered it
LMAOOO
i just realized
.into_typed::<T>
.into_untyped()

boom
no lifetime bullshit
nah wait
i'm stupid
there are situations i wish to simply view into a buffer but cannot take ownership
thatโs what I was originally suggesting yeah lol
But yes
Sometimes you just need a view
Though the only time you care about its type is when you are writing or reading data from the mapped pointer
So itโs pretty natural to return a typed slice, which is exactly that
A typed view
you know what
im calling it for a day
my brain hurts
im emotionally hurt knowing all of my progress was a lie

yeah true can't learn without failing
but also a part of me thinks i'm probably fine just clonining my handles for now as i make through vkguide
lower the number of things i need to juggle for now basically
cuz like figuring lifetimes + learning vulkan 
a pretty good rule of thumb is that if youโre using lifetime parameters youโre probably overcomplicating things
of course you need them sometimes though
i mean i think this case is a pretty good use of them probably
there are no lifetimes in C++ btw 
lvstri your propaganda won't get me
it's not mere propaganda, simply the truth 
mmm
use core::marker::PhantomData;
pub struct my_generic<T> { _marker: PhantomData<T> }
pub struct not_clone {}
#[derive(Clone)]
pub struct my_stuff {
my_data: my_generic<not_clone>
}
fn main() {
println!("Hello, world!");
}

curse you phantomdata
this probably makes sense but why can you not just be normal and not force your generic onto the entire type's requirements since it quite literally does not need them
ah- i could technically do ::size_of::<T>() 
ahhh
is there a phantomdata that uh removes that
This doesnโt compile right
yeah it doesn't
It has nothing to do with PhantomData
it shou;dn't
Your generic doesnโt implement Clone
yeah i realized that 
You can do impl<T: Clone> Clone for my_generic<T> though
yeah that's what i did
let sampler = self.with_sampler(&sampler_handle, |sampler| {
sampler.handle()
})?;

(Derive sadly does not include those bounds)
Why is that needed here
average rust user
Im confused
Yeah
so you offered to just do with_...
Whatโs the type of sampler here
And sampler_handle?
Handle<Sampler>
Whatโs that
A free list handle
Hmm
aka u64
I would just expose a method in Handle<T> to grab the T if youโre returning a copy anyway
Can guard it behind a T: Copy bound
Maybe make it unsafe because it allows concurrent usage without that being checked
In Phobos everything that returns a raw vk object is unsafe
contain no ref back to their free list as that kinda defeats their purpose
So then free_list.get(handle) or something
so tiny
eh well
impl<T: Resource then T::HandleType: Copy>
tbf i could just restrict handletype to copy only
/// If you're simply acquiring handle of a resource
pub fn get_resource_handle(&self, handle: &Handle<T>) -> Result<T::HandleType> {
if !self.is_valid(handle)? {
return Err(anyhow::Error::from(errors::Errors::InvalidHandle));
}
Ok(self.inner
.read()
.map_err(|_| Err(anyhow::Error::from(crate::DagalError::PoisonError)))?
.resources
.get(handle.id as usize)
.unwrap()
.as_ref()
.unwrap()
.handle())
}
sadge it only works for .handle
in theory i could shove all copyable data in resources
to something like type Copyable or like type Metadata
then do like
get_resource_metadata()
although i wouldn't say metadata makes sense 
i think i cooked
now
time for the final boss: how the fuck do i manage lifetimes
ah yeah rust drops items in order
Iโm really determined to get deletion stacks working ๐ญ
Maybe I created an Inner struct which can be freely copied around. This copying is restricted to deletion stacks only and they store them to be deleted
Penguin is this a good idea
unwrap in a function that returns Result 
Lmao
We do checking prior
Data: Vec<Option<T>>
we already checked that Option<T> is Some(T)
hm true
im gonna lose it
Okay, we're gonna use Inner structs which can be copied
deletion stacks can accept this inner
and use this to delete them
this will keep me sane
and not end up with me wanting to commit henious crimes against memory lifetimes
nuh uh
99% of rust programmers quit before the hit it big (lifetimes)
real
MR PENGUIN
WE DID
WE FUCKING DID IT
NO CLONING OF THOSE NASTY VULKAN HANDLES
ME? USE AFTER FREE ISSUES? NAAHHHHH
I DON'T FW THAT
i also figured out i could use manuallydrop on my swapchain images to never drop them
dude i forgot rust dropped stuff in order of their declaration 
so i had a massive drop function for no reason
type is trivial so no destruction takes place
non trivial types have destruction in reverse order of initialization
Trivially copyable class
A trivially copyable class is a class that
has at least one eligible copy constructor, move constructor, copy assignment operator, or move assignment operator,
each eligible copy constructor is trivial
each eligible move constructor is trivial
each eligible copy assignment operator is trivial
each eligible move assignment operator is trivial, and
has a non-deleted trivial destructor.
Trivial class
A trivial class is a class that
is trivially copyable, and
has one or more eligible default constructors such that each is trivial.
Standard-layout class
A standard-layout class is a class that
has no non-static data members of type non-standard-layout class (or array of such types) or reference,
has no virtual functions and no virtual base classes,
has the same access control for all non-static data members,
has no non-standard-layout base classes,
only one class in the hierarchy has non-static data members, and
Informally, none of the base classes has the same type as the first non-static data member. Or, formally: given the class as S, has no element of the set M(S) of types as a base class, where M(X) for a type X is defined as:
If X is a non-union class type with no (possibly inherited) non-static data members, the set M(X) is empty.
If X is a non-union class type whose first non-static data member has type X0 (where said member may be an anonymous union), the set M(X) consists of X0 and the elements of M(X0).
If X is a union type, the set M(X) is the union of all M(Ui) and the set containing all Ui, where each Ui is the type of the ith non-static data member of X.
If X is an array type with element type Xe, the set M(X) consists of Xe and the elements of M(Xe).
If X is a non-class, non-array type, the set M(X) is empty.```
the TL;DR version of that is:
- constructor is trivial or defaulted inline trivial
- destructor is trivial or defaulted inline trivial
- trivial copy constructors
- every subobject is trivial and or primitive
- no virtual functions
- no non-trivial base classes (this still introduces padding
- same access specifiers for all subobjects
- no references
this is why learning C++ is valuable btw
it trains your brain to this

it does train your brain to deal with a lot of implicit behavior
and also multiple compilers
nothing is implicit if you know the standard
true
but i like rust being very explicit in every move/copy/clone you do
makes it harder for me to go braindead and accidentially doing a bunch of copies i never meant to do
on the contrary, your trust in the compiler means that if something goes wrong it's joever
true
meanwhile I trust nothing, therefore I can't be hurt in any way
i just like how cpp has multiple implementations
stuff going wrong is the default behavior
forcing you to rely wholly on spec
and makes cpp spec just more rigirious since more people are implementing it
real
the only reason why cpp has a spec is because you can't understand it otherwise
mhm
anyways go back to c++

it's good for you
one day
trust
nuh uh
one day
soon(tm)
i need good cpp resources anyways
eh i guess i could use cppcon back to basics
it's very much a speedrun of cpp for those who are "familiar" with it so i guess i would fit in
anyways
AHAHAHA
it worky now
no i forbit it
stay strong
brain workout
rust is more weight
you ll get stronger then c++ people
real and factual
my renderer no longer uses unsafe pointers
all objects are now exclusively owned by their own class and no longer duplicate their vk pointer handles
im one step closer to the most safe code
OH FUCK
MY TESTS
ALL MY TESTS
Okay here is an insane idea
pub trait ResourceInner: Copy + Debug {
type HandleType;
pub fn get_handle(&self) -> Self::HandleType
...
}
pub trait Resource: Debug {
type HandleType;
type ResourceIn: ResourceInner;
pub fn inner(&self) -> ResourceIn;
}
we reimplement resource onto deletion stack element
and force everything to have a Result<...> return
then we can in theory
use a deletion stack still
fake weight
mmm rust requires me to think harder
ray tracing or meshlets
๐ค
rust does have a metis lib
i guess i could start with meshlets for now since they can be applied in both rt + rasterization
aw
come back in one year
ok
mmm alright
uh how does one bind multiple push constants
egui
yeah but like the integrations require me to annihilate my entire engine 
you dont
yeah i wrote my own based on the existing one
that just uses phobos apis
why do that
what
layout(set = 0, binding = 0) uniform SceneData{
mat4 view;
mat4 proj;
mat4 viewproj;
vec4 ambientColor;
vec4 sunlightDirection; //w for sun power
vec4 sunlightColor;
} sceneData;
layout(set = 1, binding = 0) uniform GLTFMaterialData{
vec4 colorFactors;
vec4 metal_rough_factors;
} materialData;
why are these in two different sets
didn't you achieve bindless
AH
give address to shader
yeah
be happy
thanks lvstri
uh
for a material buffer yeah?
should i just have it so when i render i re-upload that data
or should i just make a some really big material buffer

Start by just copying every frame
Penguin
uh do you have idea if it's a good idea to group my vk::PipelineLayout with my vk::Pipeline?
My only issue is that it makes it hard to reuse
without resorting to Arc<>'ing it
Eh I went with just seperatikg them
Bro I need to do graphics
I'm too addicted to modded Minecraft

i refuse to gregify because it will be an unstoppable addiction
Eearslya has already joined us 
nuh uh im too deep into create mod
gregification will wait until i graduate from uni
but MMMMM
The G in graphics programming stands for Gregtech
create sucks
yeah but moving contraption makes me goo oo oo ee ee aa aa
Ok but you could have AE going brrr in GTNH
guys i got a split keyboard
this is so com confusing to use
now i just need to learn vim kekw
but my wpm is so badd
how do i even code in this condition
OH MY HOR
GOD
I CAN ACTUALLY DO VKGUIDE WITHOUT KILLING MYSELF
THIS IS ACTUALLY AMAZIJG
HOLY SHIT
OH MY HOD
THIS IS ACTUALLY AMAZING

oh my god my school's student formula team allowed me to rewrite some of their shit in rust
I'm gonna steal ur code next year vro...
kekw
wanna join gryphon fsae
we deadass just make cares n shit
im trying to gaslight them to rewriting the entire car in rust
it's the most funded one ๐
we literally suck all of the engineering's club money
"yes sir we need 10k to print our carbon fibre"
we'll be there at the engineering section of the club faire
Holy based lol
there are 2 during o-week. the first is the general one. the second is the engineering one
first is if you wanna touch grass and join social shit
second is if you wanna do activism or not touch grass and make cars and aerospace shit
wait fuck first is wrong, there was an anime club
they fucking meow'd when i went there

also vkguide is designed with that in mind 
dude vkguide is getting confusing now
vkguide doesn't clone anything sir
no like clone my vulkan handles
mmmm
i somehow fucked up me matrices
4

it seems like the mesh is only visible from the inside
fml
i mixed up view and proj
materials
well sorta
close enough
i really dislike how vkguide does it
hmm is there a nice way to handle mesh resource lifetimes
cuz i have a weird issue with my vertex buffers and index buffers
since they're all hidden behind a freelist allocator, i can't just throw arc on them
fellas
Do I make a seperate MeshBuffer (contains the addresses of all my vertices + indices) and it contains an index into my MaterialBuffer which contains ids to textures + samplers
or should I just throw it into one massive mesh info buffer then just provide an id to index into said buffer
will i need to ever worry about more than 1 texcoords
@tropic vigil sorry for pinging, but what would make the most sense for suballocations of a buffer? Should I just have:
pub struct BufferView<A: Allocation> {
buffer: Arc<Buffer<A>>,
offset: vk::DeviceSize,
length: vk::DeviceSize,
stride: vk::DeviceSize
}
?
That seems sensible yeah
Well
idk why the stride would be useful
since its a single contiguous view
feels wrong ye
I still dont really get where you run into major issues not using arcs though
it's mainly if 2 meshes reference the same buffer
cuz the other solution is to throw into some slot map and have a pseudo reference counting system but atp, i might as well throw on rc/arc
Is that because the buffer is suballocated or do they actually reference the same data
same data
In this case why are they not the same mesh
To me the real problem sounds like your mesh is too concrete
yeah i mean i could make that assumption
If two meshes hold the same data, they are the same - if you need something to build differences on top that means that thing shouldn't be a mesh but an abstraction above it (that refers to a mesh)
hmm how to make sure allocations are valid throughout a suballocations' lifetimes
hmmm
tbfh i could just yolo it and throw it into a giant slot map and hope the logic just works out
Ok so the big problem here is that suppose you write some kind of wrapper SuballocatedBuffer that can handle this and hand out slices of your buffer to the caller, you run into issues quickly (lifetime needs to be ensured, you can't just store references to things inside SuballocatedBuffer since you're then borrowing it, etc).
I would probably handle this by letting SuballocatedBuffer return handles or strong ids that refer to a unique range in the buffer (could simply be offset/size packed into a u64), and then have another method that allows getting the actual view when you need it
So for storage purposes you just have this BufferSliceID or something, but when you need to actually access the buffer you make a quick query to the backing buffer
If you want to make your id fancy and hold a reference to the storage, you could consider std::sync::Weak to store alongside it
I feel like this would be handled at a lower level than the level that makes buffers a handle
So if your buffers are something like Handle<RawBuffer>, then your SuballocatedBuffer holds a RawBuffer
If thats what your handles were for, probably
I mean the id/handle that your suballocatedbuffer would return is effectively what your handles from before were doing
Except now you keep the possibility of using full buffers that are not suballocated by simply using whatever buffer type SuballocatedBuffer is abstracting over
kekw it's probably easier to just have a seperate buffer position/normal/tangents per mesh
i mean isn't that what ue5 literally does
someone needs to remind to stop overthinking stuff
WAIIIIIIT DRAW INDIRECT FUCK

??
you know how could i handle uploading mesh data in a nicer way
cuz i feel like there it isn't a good idea to literally double my gpu memory for a:
GpuOnly buffer as well a CpuToGpu buffer
reminder
thanks lvstri
one thing that pisses me off rn is that
my surface structs only hold handles since i submit all my buffers to the gpu resource table
-2000 elo icon
real
List of my work
Your pfp seems good
okay hear me out
nvm don't
Dude I don't wanna end up having to do this:
struct Material {
handle: Handle<render::Material>,
freelist_suballocator: Arc<RwLock<FreeListSuballocator>>,
}
impl Drop for Material {
fn drop(&mut self) {
self.freelist_suballocator.free(self.handle);
}
}
This literally makes handles redundant
penguin pls help how do i make a material buffer i am once again overthinking
this seems like an antipattern
if you have something that is referred to by handles
then something else owns it
so that something else should be responsible for removing it from the freelist (or should be the freelist entirely)
What I used to do in my GL era was just hook onto the Handle's Drop and simply destroy the item then
For vulkan I'd assume you'd have to wait until the GPU no longer uses it so something like phobos's delete queue works nicely imo
wdym penguinm?
hmmm deletion queue is fine? but at the end of the day, the resource still needs to be deleted regardless
there really is no nice way to deal with this 
rust moment (tm)
it's weird cuz my materials can be referenced by multiple meshes
do i need to make sure they don't get deleted then
but also make sure they get destroyed if not needed
all my resources are effectively owned by my bindless gpu resource table which just hands out handles when inserted into
maybe i make it no longer own them?