I would absolutely love to write my GPU memory allocation algorithm, but unfortunately calculus colloquium is a thing. Yes, holydays are fucking over, I spent them watching Hermitcraft 9, now 228 deadlines/second are back on my ass

This commit is contained in:
Андреев Григорий 2025-10-29 02:24:42 +03:00
parent 6527ee98e0
commit 941a6da1d7
7 changed files with 490 additions and 27 deletions

View File

@ -38,8 +38,8 @@ add_compile_options(-fno-trapping-math)
#add_executable(codegen_l1_5 src/l1_5/anne/codegen.c)
#add_executable(0_render_test src/l2/tests/r0/r0.c gen/l_wl_protocols/xdg-shell-private.c)
#target_link_libraries(0_render_test -lvulkan -lwayland-client -lm -lxkbcommon -lpng)
add_executable(0_render_test src/l2/tests/r0/r0.c gen/l_wl_protocols/xdg-shell-private.c)
target_link_libraries(0_render_test -lvulkan -lwayland-client -lm -lxkbcommon -lpng)
#add_executable(0r_tex_init_prep src/l2/tests/r0/r0_tex_init_prep.c)
#target_link_libraries(0r_tex_init_prep -lm -lpng)
@ -53,15 +53,11 @@ add_compile_options(-fno-trapping-math)
#add_executable(3_render_test src/l2/tests/r3/r3.c gen/l_wl_protocols/xdg-shell-private.c)
#target_link_libraries(3_render_test -lwayland-client -lm -lvulkan -lxkbcommon)
#add_executable(0_play_test src/l3/tests/p0.c)
#target_link_libraries(0_play_test -lncurses)
#
add_executable(l2t2 src/l2/tests/data_structures/t2.c)
add_compile_definitions(RUNNING_HERE)
add_executable(l2t0 src/l2/tests/data_structures/t0.c)
add_executable(l2t1 src/l2/tests/data_structures/t1.c)
add_executable(H src/l2/tests/r_alg/H.c)
add_executable(I src/l2/tests/r_alg/I.c)
add_executable(J src/l2/tests/r_alg/J.c)
#add_executable(l2t0 src/l2/tests/data_structures/t0.c)
#add_executable(l2t1 src/l2/tests/data_structures/t1.c)
#add_executable(H src/l2/tests/r_alg/H.c)
#add_executable(I src/l2/tests/r_alg/I.c)
#add_executable(J src/l2/tests/r_alg/J.c)

View File

@ -11,6 +11,9 @@ void generate_margaret_eve_for_vulkan_utils() {
.T = cstr("MargaretScoredPhysicalDevice"), .t_primitive = true, .vec = true, .span = true,
.mut_span = true, .collab_vec_span = true, .span_sort = true
});
/* Под снос */
generate_eve_span_company_for_primitive(l, ns, cstr("MargaretBufferInMemoryInfo"), true, false);
generate_util_templ_inst_eve_header(l, ns, (util_templates_instantiation_options){
.T = cstr("PtrMargaretBufferInMemoryInfo"), .t_primitive = true, .vec = true, .span = true, .mut_span = true,
@ -21,7 +24,14 @@ void generate_margaret_eve_for_vulkan_utils() {
.T = cstr("PtrMargaretImageInMemoryInfo"), .t_primitive = true, .vec = true, .span = true, .mut_span = true,
.collab_vec_span = true
});
generate_eve_span_company_for_primitive(l, ns, cstr("MargaretCommandForImageCopying"), true, true);
generate_eve_span_company_for_primitive(l, ns, cstr("MargaretMemAllocatorRequestAllocBuffer"), true, true);
generate_eve_span_company_for_primitive(l, ns, cstr("MargaretMemAllocatorRequestResizeBuffer"), true, true);
generate_eve_span_company_for_primitive(l, ns, cstr("MargaretMemAllocatorRequestFreeBuffer"), true, true);
generate_eve_span_company_for_primitive(l, ns, cstr("MargaretMemAllocatorRequestAllocImage"), true, true);
generate_eve_span_company_for_primitive(l, ns, cstr("MargaretMemAllocatorRequestFreeImage"), true, true);
}

View File

@ -5,17 +5,11 @@
void generate_l1_5_template_instantiation_for_base_types(){
SpanU8 l = cstr("l1_5"), ns = cstr("");
generate_rb_tree_Set_templ_inst_guarded_header(l, ns, cstr("#include \"../l1/VecAndSpan_U64.h\""),
generate_rb_tree_Set_templ_inst_guarded_header(l, ns,cstr("#include \"../l1/VecAndSpan_U64.h\""),
(set_instantiation_op){.T = cstr("U64"), .t_integer = true});
generate_rb_tree_Set_templ_inst_guarded_header(l, ns, cstr("#include \"../l1/VecAndSpan_S64.h\""),
(set_instantiation_op){.T = cstr("S64"), .t_integer = true});
generate_rb_tree_Set_templ_inst_guarded_header(l, ns,
cstr("#include \"../l1/VecAndSpan_U64.h\""),
(set_instantiation_op){.T = cstr("U64"), .t_integer = true});
generate_rb_tree_Set_templ_inst_guarded_header(l, ns,
cstr("#include \"../l1/VecAndSpan_S64.h\""),
(set_instantiation_op){.T = cstr("S64"), .t_integer = true});
generate_rb_tree_Set_templ_inst_guarded_header(l, ns, cstr("#include \"../../src/l1/core/uint_segments.h\""),
(set_instantiation_op){
.T = cstr("U64Segment"),

View File

@ -0,0 +1,268 @@
/* This is a Claire header. Do not include it in more that one place.
* This Claire requires vulkan api:
*
*
* typedef integer VkResult
*
* const VkResult VK_SUCCESS
*
* typedef integer VkStructureType
*
* const VkStructureType VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO
* const VkStructureType VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO
* const VkStructureType VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO
*
* typedef integer VkBufferCreateFlags
* typedef integer VkDeviceSize
* typedef integer VkBufferUsageFlags
* typedef integer VkSharingMode
*
* const VkSharingMode VK_SHARING_MODE_EXCLUSIVE
*
*
* typedef handler VkPhysicalDevice
* typedef handler VkDevice
* typedef handler VkBuffer
* typedef handler VkImage
* typedef handler VkDeviceMemory
* typedef handler VkCommandBuffer
*
* typedef struct {
* VkStructureType sType;
* const void* pNext;
* VkBufferCreateFlags flags;
* VkDeviceSize size;
* VkBufferUsageFlags usage;
* VkSharingMode sharingMode;
* uint32_t queueFamilyIndexCount;
* const uint32_t* pQueueFamilyIndices;
* } VkBufferCreateInfo
*
* typedef integer VkMemoryPropertyFlags
*
* const VkMemoryPropertyFlags VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
*
* typedef struct {
* VkMemoryPropertyFlags propertyFlags;
* ...
* } VkMemoryType
*
* #define VK_MAX_MEMORY_TYPES 32
*
* typedef struct {
* uint32_t memoryTypeCount;
* VkMemoryType memoryTypes[VK_MAX_MEMORY_TYPES];
* ...
* } VkPhysicalDeviceMemoryProperties
*
* void vkGetPhysicalDeviceMemoryProperties(
* VkPhysicalDevice physicalDevice,
* VkPhysicalDeviceMemoryProperties* pMemoryProperties)
*
* typedef void VkAllocationCallbacks
* (seriously, this type is only used as a pointer in an exposed api function, and it always takes NULL value)
*
* VkResult vkCreateBuffer(
* VkDevice device,
* const VkBufferCreateInfo* pCreateInfo,
* const VkAllocationCallbacks* pAllocator,
* VkBuffer* pBuffer)
*
* typedef struct {
* VkDeviceSize size;
* VkDeviceSize alignment;
* uint32_t memoryTypeBits;
* } VkMemoryRequirements
*
* void vkGetBufferMemoryRequirements(
* VkDevice device,
* VkBuffer buffer,
* VkMemoryRequirements* pMemoryRequirements)
*
* typedef integer VkImageCreateFlags
* typedef integer VkImageType
*
* const VkImageType VK_IMAGE_TYPE_2D
*
* typedef integer VkFormat
*
* typedef struct {
* uint32_t width;
* uint32_t height;
* uint32_t depth;
* } VkExtent3D
*
* typedef integer VkSampleCountFlagBits
*
* const VkSampleCountFlagBits VK_SAMPLE_COUNT_1_BIT
*
* typedef integer VkImageTiling
*
* const VkImageTiling VK_IMAGE_TILING_LINEAR
*
* const VkImageTiling VK_IMAGE_TILING_OPTIMAL
*
* typedef integer VkImageUsageFlags
* typedef integer VkImageLayout
*
* const VkImageLayout VK_IMAGE_LAYOUT_UNDEFINED
*
* typedef struct {
* VkStructureType sType;
* const void* pNext;
* VkImageCreateFlags flags;
* VkImageType imageType;
* VkFormat format;
* VkExtent3D extent;
* uint32_t mipLevels;
* uint32_t arrayLayers;
* VkSampleCountFlagBits samples;
* VkImageTiling tiling;
* VkImageUsageFlags usage;
* VkSharingMode sharingMode;
* uint32_t queueFamilyIndexCount;
* const uint32_t* pQueueFamilyIndices;
* VkImageLayout initialLayout;
* } VkImageCreateInfo
*
* VkResult vkCreateImage(
* VkDevice device,
* const VkImageCreateInfo* pCreateInfo,
* const VkAllocationCallbacks* pAllocator,
* VkImage* pImage)
*
* void vkGetImageMemoryRequirements(
* VkDevice device,
* VkImage image,
* VkMemoryRequirements* pMemoryRequirements)
*
* typedef struct {
* VkStructureType sType;
* const void* pNext;
* VkDeviceSize allocationSize;
* uint32_t memoryTypeIndex;
* } VkMemoryAllocateInfo
*
* VkResult vkAllocateMemory(
* VkDevice device,
* const VkMemoryAllocateInfo* pAllocateInfo,
* const VkAllocationCallbacks* pAllocator,
* VkDeviceMemory* pMemory)
*
* VkResult vkBindBufferMemory(
* VkDevice device,
* VkBuffer buffer,
* VkDeviceMemory memory,
* VkDeviceSize memoryOffset)
*
* VkResult vkBindImageMemory(
* VkDevice device,
* VkImage image,
* VkDeviceMemory memory,
* VkDeviceSize memoryOffset)
*
* void vkDestroyBuffer(
* VkDevice device,
* VkBuffer buffer,
* const VkAllocationCallbacks* pAllocator)
*
* void vkDestroyImage(
* VkDevice device,
* VkImage image,
* const VkAllocationCallbacks* pAllocator)
*/
#include "../../../l1/core/int_primitives.h"
/* MargaretMemAllocator assumes that your application tolerates existence of several
* 'quiet' time phases, when no frames are in flight and all you do is relocate and defragment your memory */
typedef struct {
VkBufferUsageFlags usage_flags;
VkMemoryPropertyFlags memory_properties;
bool preserve_at_quiet;
U64 inner_alignment;
} MargaretMemAllocatorBufferKindInfo;
typedef struct {
VkBuffer buffer;
U64 offset;
/* Fun fact: Muller dictionary recognizes nubble, but not nubbin, while firefox spellchecker and clion
* recognize nubbin, but not nubble. But I prefer nubble more. */
U64 offset_in_device_memory_nubble;
/* If your buffer kind requested VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, MargaretMemAllocator
* will map all nubbles with it. Use `offset_in_device_memory_nubble` to access this buffer from cpu */
U8 memory_type_id;
U16 memory_allocation_id;
} MargaretMemAllocatorBufferPosition;
typedef struct {
U64 size;
U32 kind;
/* If I were you, I would just store this in heap and did not care */
MargaretMemAllocatorBufferPosition* ans;
} MargaretMemAllocatorRequestAllocBuffer;
typedef struct {
MargaretMemAllocatorBufferPosition* prev_ans;
U64 new_size;
} MargaretMemAllocatorRequestResizeBuffer;
typedef MargaretMemAllocatorBufferPosition* MargaretMemAllocatorRequestFreeBuffer;
typedef struct{
VkImage image;
U64 offset_in_device_memory_nubble;
U8 memory_type_id;
U16 memory_allocation_id;
} MargaretMemAllocatorImagePosition;
typedef struct {
U64 width;
U64 height;
VkFormat format;
VkImageTiling tiling;
VkImageLayout initial_layout;
VkImageUsageFlags usage_flags;
VkMemoryPropertyFlags memory_properties;
bool preserve_at_quiet;
/* If I were you, I would just store this in heap and did not care*/
MargaretMemAllocatorImagePosition* ans;
} MargaretMemAllocatorRequestAllocImage;
typedef MargaretMemAllocatorImagePosition* MargaretMemAllocatorRequestFreeImage;
#include "../../../gen/l1/eve/margaret/VecAndSpan_MargaretMemAllocatorRequestAllocBuffer.h"
#include "../../../gen/l1/eve/margaret/VecAndSpan_MargaretMemAllocatorRequestResizeBuffer.h"
#include "../../../gen/l1/eve/margaret/VecAndSpan_MargaretMemAllocatorRequestFreeBuffer.h"
#include "../../../gen/l1/eve/margaret/VecAndSpan_MargaretMemAllocatorRequestAllocImage.h"
#include "../../../gen/l1/eve/margaret/VecAndSpan_MargaretMemAllocatorRequestFreeImage.h"
typedef struct {
SpanMargaretMemAllocatorRequestAllocBuffer alloc_buffer;
SpanMargaretMemAllocatorRequestResizeBuffer realloc_buffer;
SpanMargaretMemAllocatorRequestFreeBuffer free_buffer;
SpanMargaretMemAllocatorRequestAllocImage alloc_image;
SpanMargaretMemAllocatorRequestFreeImage free_image;
} MargaretMemAllocatorRequest;
/* That is our guy! */
typedef struct {
} MargaretMemAllocator;
/* Vibe check */
bool MargaretMemAllocator_request_needs_silence(MargaretMemAllocator* self, MargaretMemAllocatorRequest req){
return false;
}
/* Appends copying commands into cmd_buff. It may append none. Defragmentation, device memory relocation
* need copying commands, but buffer resize may also require copying.
* If silence is needed, silence flag should be set, otherwise method aborts. You can use
* _request_needs_silence method to check if silence is needed, but if you know for sure that you already have
* silence anyway, you can pass `silence=true`.
* Returned value: true if some_commands were appended to cmd_buff and need to be executed before any further
* actions with memory managed by Self would make any sense */
bool MargaretMemAllocator_carry_out_request(MargaretMemAllocator* self, VkCommandBuffer cmd_buff, bool silence){
return false;
}

View File

@ -28,10 +28,6 @@
#include "../../../gen/l1/vulkan/VecVkSurfaceFormatKHR.h"
#include "../../../gen/l1/vulkan/OptionVkSurfaceFormatKHR.h"
#include <vulkan/vulkan_wayland.h>
// #include <vulkan/vulkan.h>
void margaret_create_debug_utils_messenger_EXT(
VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo,
@ -287,6 +283,30 @@ NODISCARD VecU8 margaret_stringify_device_memory_properties(VkPhysicalDevice phy
return result;
}
NODISCARD VecU8 margaret_stringify_device_memory_properties_2(VkPhysicalDevice physical_device){
VkPhysicalDeviceMaintenance3Properties maintenance3_properties = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_3_PROPERTIES,
};
VkPhysicalDeviceMaintenance4PropertiesKHR maintenance4_properties = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_4_PROPERTIES_KHR,
.pNext = &maintenance3_properties,
};
VkPhysicalDeviceProperties2 properties = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
.pNext = &maintenance4_properties,
};
/* Needs VK_KHR_maintenance4 */
vkGetPhysicalDeviceProperties2(physical_device, &properties);
U64 maxBufferSize = maintenance4_properties.maxBufferSize;
U64 maxMemoryAllocationCount = properties.properties.limits.maxMemoryAllocationCount;
U64 maxMemoryAllocationSize = maintenance3_properties.maxMemoryAllocationSize;
return VecU8_fmt(
"maxMemoryAllocationsCount: %u\n"
"maxMemoryAllocationSize: %u\n"
"maxBufferSize: %u!!!!!!!!!\n",
maxMemoryAllocationCount, maxMemoryAllocationSize, maxBufferSize);
}
VkDevice margaret_create_logical_device(VkPhysicalDevice physical_device, MargaretChosenQueueFamilies queue_fam) {
VkPhysicalDeviceFeatures physical_features;
vkGetPhysicalDeviceFeatures(physical_device, &physical_features);
@ -314,7 +334,7 @@ VkDevice margaret_create_logical_device(VkPhysicalDevice physical_device, Margar
},
};
const char* needed_extensions[2] = {"VK_KHR_swapchain", "VK_KHR_synchronization2"};
const char* needed_extensions[] = {"VK_KHR_swapchain", "VK_KHR_synchronization2", "VK_KHR_maintenance4"};
VkDeviceCreateInfo device_crinfo = {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
@ -556,7 +576,9 @@ MargaretScoredPhysicalDevice margaret_score_physical_device(
return (MargaretScoredPhysicalDevice){dev, -1, queue_families.err};
// Checking device specific extensions (VK_KHR_swapchain required to check swap_chain support details
VecVecU8 dev_extensions = margaret_get_extensions_of_physical_device(dev);
SpanU8 required_dev_extensions[] = {cstr("VK_KHR_swapchain"), cstr("VK_KHR_synchronization2")};
SpanU8 required_dev_extensions[] = {
cstr("VK_KHR_swapchain"), cstr("VK_KHR_synchronization2"),
cstr("VK_KHR_maintenance4")};
for (size_t ei = 0; ei < ARRAY_SIZE(required_dev_extensions); ei++) {
if (!is_string_in_string_vec(required_dev_extensions[ei], &dev_extensions))
return (MargaretScoredPhysicalDevice){dev, -1, cstr("Missing some device extensions")};
@ -1266,7 +1288,7 @@ VkSampler margaret_create_sampler(VkPhysicalDevice physical_device, VkDevice dev
VkDescriptorPool margaret_create_descriptor_set_pool(VkDevice device,
uint32_t ubo_descriptor_count, uint32_t image_sampler_descriptor_count, uint32_t max_sets
) {
VecVkDescriptorPoolSize sizes = VecVkDescriptorPoolSize_new();
VecVkDescriptorPoolSize sizes = VecVkDescriptorPoolSize_new_reserved(2);
if (ubo_descriptor_count > 0)
VecVkDescriptorPoolSize_append(&sizes, (VkDescriptorPoolSize){
.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,

View File

@ -0,0 +1,169 @@
#include <stdint.h>
typedef int VkResult;
const VkResult VK_SUCCESS = 120;
const VkResult VK_ERROR_INVALID_VIDEO_STD_PARAMETERS_KHR = -2;
typedef int VkStructureType;
const VkStructureType VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO = 100;
const VkStructureType VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO = 200;
const VkStructureType VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO = 3000;
typedef int VkBufferCreateFlags;
typedef int VkDeviceSize;
typedef int VkBufferUsageFlags;
typedef int VkSharingMode;
const VkSharingMode VK_SHARING_MODE_EXCLUSIVE = 12;
typedef void* VkPhysicalDevice;
typedef void* VkDevice;
typedef void* VkBuffer;
typedef void* VkImage;
typedef void* VkDeviceMemory;
typedef void* VkCommandBuffer;
typedef struct {
VkStructureType sType;
const void* pNext;
VkBufferCreateFlags flags;
VkDeviceSize size;
VkBufferUsageFlags usage;
VkSharingMode sharingMode;
uint32_t queueFamilyIndexCount;
const uint32_t* pQueueFamilyIndices;
} VkBufferCreateInfo;
typedef int VkMemoryPropertyFlags;
typedef struct {
VkMemoryPropertyFlags propertyFlags;
// ...
} VkMemoryType;
#define VK_MAX_MEMORY_TYPES 32
typedef struct {
uint32_t memoryTypeCount;
VkMemoryType memoryTypes[VK_MAX_MEMORY_TYPES];
// ...
} VkPhysicalDeviceMemoryProperties;
void vkGetPhysicalDeviceMemoryProperties(
VkPhysicalDevice physicalDevice,
VkPhysicalDeviceMemoryProperties* pMemoryProperties);
typedef void VkAllocationCallbacks;
VkResult vkCreateBuffer(
VkDevice device,
const VkBufferCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkBuffer* pBuffer);
typedef struct {
VkDeviceSize size;
VkDeviceSize alignment;
uint32_t memoryTypeBits;
} VkMemoryRequirements;
void vkGetBufferMemoryRequirements(
VkDevice device,
VkBuffer buffer,
VkMemoryRequirements* pMemoryRequirements);
typedef int VkImageCreateFlags;
typedef int VkImageType;
const VkImageType VK_IMAGE_TYPE_2D = 1232;
typedef int VkFormat;
typedef struct {
uint32_t width;
uint32_t height;
uint32_t depth;
} VkExtent3D;
typedef int VkSampleCountFlagBits;
const VkSampleCountFlagBits VK_SAMPLE_COUNT_1_BIT = 1299;
typedef int VkImageTiling;
const VkImageTiling VK_IMAGE_TILING_OPTIMAL = 13115;
const VkImageTiling VK_IMAGE_TILING_LINEAR = 9874;
typedef int VkImageUsageFlags;
typedef int VkImageLayout;
const VkImageLayout VK_IMAGE_LAYOUT_UNDEFINED = 780;
typedef struct {
VkStructureType sType;
const void* pNext;
VkImageCreateFlags flags;
VkImageType imageType;
VkFormat format;
VkExtent3D extent;
uint32_t mipLevels;
uint32_t arrayLayers;
VkSampleCountFlagBits samples;
VkImageTiling tiling;
VkImageUsageFlags usage;
VkSharingMode sharingMode;
uint32_t queueFamilyIndexCount;
const uint32_t* pQueueFamilyIndices;
VkImageLayout initialLayout;
} VkImageCreateInfo;
VkResult vkCreateImage(
VkDevice device,
const VkImageCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkImage* pImage);
void vkGetImageMemoryRequirements(
VkDevice device,
VkImage image,
VkMemoryRequirements* pMemoryRequirements);
typedef struct {
VkStructureType sType;
const void* pNext;
VkDeviceSize allocationSize;
uint32_t memoryTypeIndex;
} VkMemoryAllocateInfo;
VkResult vkAllocateMemory(
VkDevice device,
const VkMemoryAllocateInfo* pAllocateInfo,
const VkAllocationCallbacks* pAllocator,
VkDeviceMemory* pMemory);
VkResult vkBindBufferMemory(
VkDevice device,
VkBuffer buffer,
VkDeviceMemory memory,
VkDeviceSize memoryOffset);
VkResult vkBindImageMemory(
VkDevice device,
VkImage image,
VkDeviceMemory memory,
VkDeviceSize memoryOffset);
void vkDestroyBuffer(
VkDevice device,
VkBuffer buffer,
const VkAllocationCallbacks* pAllocator);
void vkDestroyImage(
VkDevice device,
VkImage image,
const VkAllocationCallbacks* pAllocator);
#include "../../margaret/vulkan_memory_claire.h"

View File

@ -1740,6 +1740,11 @@ int main() {
vk_ctx->physical_device = margaret_select_one_physical_device(
instance, vk_ctx->surface, GPU, bugged_GPU, sane_image_extent_limit);
{ /* Funny vibe check */
VecU8 txt = margaret_stringify_device_memory_properties_2(vk_ctx->physical_device);
SpanU8_print(VecU8_to_span(&txt));
}
// print_physical_device_available_extensions(physical_device);
ResultMargaretChosenQueueFamiliesOrSpanU8 queue_fam_res = margaret_choose_good_queue_families(
@ -2096,7 +2101,6 @@ int main() {
.imageView = M->specular_view,
.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
};
// todo: add a third binding (for specular shading)
VkWriteDescriptorSet writes_in_descriptor_set[] = {
{
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,