#pragma once #include "rend.h" #include "rend_internal.h" #include #include #include #include "rend_vk_internal.h" #include "rend_vk_allocator.c" #include "rend_vk_device.c" #include "rend_vk_arena.c" #include "rend_vk_image.c" #define REND_MIN_FRAMES_IN_FLIGHT 2 // double buffering! #define REND_MAX_FRAMES_IN_FLIGHT 4 // quadruple buffering! #define REND_VK_MAX_PIPELINES 100 typedef struct { VkSwapchainKHR handle; VkSurfaceFormatKHR format; VkExtent2D extent; uint32_t image_count; VkImage *images; VkImageView *views; RendVkImage depth_attachment; } RendVkSwapchain; typedef struct { VkCommandPool command_pool; VkCommandBuffer command_buffer; VkSemaphore image_acquired_semaphore; } RendVkFrameResources; typedef struct { uint8_t push_data[128]; uint32_t size; } RendVkResourceSet; typedef struct RendVkPipeline { void *ctx; VkPipeline handle; VkPipelineLayout layout; uint32_t vertex_count; uint32_t vertex_stride; uint32_t push_constants_range; bool blend_enable; } RendVkPipeline; /* * per renderer context, separate from global vk_ variables * such as the instance, the allocator, the devices etc... */ typedef struct RendVk14Context { P_Window *window; VkSurfaceKHR surface; RendVkPipeline pipelines[REND_VK_MAX_PIPELINES]; RendVkFrameResources frame_resources[REND_MAX_FRAMES_IN_FLIGHT]; RendVkSwapchain swapchain; VkSemaphore timeline_semaphore; VkSemaphore render_complete_semaphores[REND_MAX_FRAMES_IN_FLIGHT]; VkCommandPool upload_command_pool; VkCommandPool graphics_command_pool; RendVkArenaAllocator arena_persistent; // magical arena allocator for every type of memory RendVkArenaAllocator arena_frame; uint64_t frame; uint64_t frame_index; uint64_t signal_value; uint64_t next_signal_value; uint64_t max_frames_in_flight; uint32_t pipeline_count; uint32_t image_index; bool require_swapchain_recreation; bool vsync; bool in_frame; VkDescriptorPool descriptor_pool; VkDescriptorSet desc_set; VkDescriptorSetLayout desc_layout; } RendVk14Context; /* function declarations */ extern bool rend_vk14_init(); extern void rend_vk14_quit(); extern bool rend_vk14_renderer_create(RendRenderer, P_Window *window); extern void rend_vk14_renderer_destroy(RendRenderer); extern bool rend_vk14_renderer_frame_begin(RendRenderer); extern void rend_vk14_renderer_frame_end(RendRenderer, float *delta); static inline void rend_vk14__renderer_render_pass_begin_internal(RendRenderer renderer, float r, float g, float b, float a, uint64_t view_handle, uint64_t depth_attachment_view_handle, uint32_t offset_x, uint32_t offset_y, uint32_t width, uint32_t height); extern void rend_vk14_renderer_render_pass_begin(RendRenderer renderer, float r, float g, float b, float a); extern void rend_vk14_renderer_render_pass_begin_texture(RendRenderer renderer, RendTexture *texture); extern void rend_vk14_renderer_render_pass_end(RendRenderer renderer); extern void rend_vk14_renderer_render_pass_end_texture(RendRenderer renderer, RendTexture *texture); extern void rend_vk14_descriptor_write_buffer(RendRenderer renderer, RendBuffer ubo, uint32_t binding, uint32_t slot, uint32_t offset, uint32_t size, bool is_ubo); extern void rend_vk14_descriptor_write_texture(void *ctx, RendTexture *texture, uint32_t binding, uint32_t slot); extern RendBuffer rend_vk14_buffer_create_lifetime(RendRenderer renderer, size_t size, RendBufferType type, bool gpu, int lifetime); extern void rend_vk14_buffer_destroy(RendBuffer *buffer); extern void rend_vk14_buffer_copy(RendRenderer renderer, RendBuffer *dest, size_t dest_offset, RendBuffer *src, size_t src_offset, size_t bytes); extern RendTexture rend_vk14_texture_create(RendRenderer renderer, uint32_t width, uint32_t height, uint32_t depth, uint32_t mip_levels, uint32_t layers, RendFormat format); extern void rend_vk14_texture_destroy(RendRenderer renderer, RendTexture *tex); extern void rend_vk14_texture_transition_layout(RendRenderer renderer, VkCommandBuffer cmd, RendTexture *texture, VkImageLayout new_layout); extern void rend_vk14_texture_transfer_ownership_release(RendRenderer renderer, VkCommandBuffer cmd, RendTexture *texture, uint32_t src_family, uint32_t dst_family, VkImageLayout new_layout); extern void rend_vk14_texture_transfer_ownership_acquire(RendRenderer renderer, VkCommandBuffer cmd, RendTexture *texture, uint32_t src_family, uint32_t dst_family, VkImageLayout new_layout); extern void rend_vk14_texture_copy_buffer(RendRenderer renderer, RendTexture *texture, RendBuffer *buffer); extern void rend_vk14_texture_blit(RendRenderer renderer, RendTexture *src, RendTexture *dst, uint32_t src_x, uint32_t src_y, uint32_t src_w, uint32_t src_h, uint32_t dst_x, uint32_t dst_y, uint32_t dst_w, uint32_t dst_h); static VkCommandBuffer rend_vk_cmdbuffer_single_use_begin(VkCommandPool pool); static void rend_vk_cmdbuffer_single_use_end(VkCommandPool pool, VkCommandBuffer cmd, VkQueue q); extern bool rend_vk14_pipeline_create(RendRenderer renderer, RendPipeline pipeline, Rend__PipelineConfig config, uint8_t type, const uint8_t *shader1, size_t bytes1, const uint8_t *shader2, size_t bytes2, const uint8_t *shader3, size_t bytes3); extern void rend_vk14_pipeline_bind(RendPipeline); extern void rend_vk14_pipeline_push_constants(RendPipeline pipeline, void *push_data, size_t size); extern void rend_vk14_pipeline_bind_vertex_buffer(RendPipeline pipeline, uint32_t binding, RendBuffer buffer, size_t offset); extern void rend_vk14_pipeline_bind_index_buffer(RendPipeline pipeline, RendBuffer buffer, size_t offset, RendIndexType index_type); extern void rend_vk14_pipeline_dispatch(RendPipeline pipeline, uint32_t x, uint32_t y, uint32_t z); extern void rend_vk14_pipeline_draw(RendPipeline, size_t count, uint32_t instance_count); extern void rend_vk14_pipeline_draw_indexed(RendPipeline pipeline, uint32_t index_count, uint32_t first_index, int32_t vertex_offset, uint32_t instance_count); extern void rend_vk14_pipeline_set_blend(RendPipeline, bool); VKAPI_ATTR VkBool32 VKAPI_CALL rend_vk_debug_func( VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, VkDebugUtilsMessageTypeFlagsEXT message_types, const VkDebugUtilsMessengerCallbackDataEXT *callback_data, void *user_data); static void rend_vk_pipeline_destroy(RendVkPipeline *pipeline); static void rend_vk_swapchain_create(RendVk14Context *ctx, RendVkSwapchain *swapchain); static void rend_vk_swapchain_destroy(RendVk14Context *ctx, RendVkSwapchain *swapchain); static VkShaderModule rend_vk_shader_module_create(const void *data, size_t size); static uint32_t rend_vk_get_heap_index(uint32_t memory_type_bits, uint32_t preferred_index); extern bool rend_vk14_init() { if (vk_instance) { return true; } /* create instance if does not exist */ VkApplicationInfo vk_app_info = {VK_STRUCTURE_TYPE_APPLICATION_INFO}; vk_app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; vk_app_info.pApplicationName = "REND"; vk_app_info.applicationVersion = VK_MAKE_VERSION(REND_MAJOR, REND_MINOR, REND_PATCH); vk_app_info.apiVersion = VK_API_VERSION_1_4; vk_app_info.pEngineName = "REND Renderer"; vk_app_info.engineVersion = VK_MAKE_VERSION(REND_MAJOR, REND_MINOR, REND_PATCH); const char **extensions_darray = (const char **)p_vulkan_get_extensions(); p_darray_push(extensions_darray, VK_KHR_SURFACE_EXTENSION_NAME); #ifdef REND_DEBUG p_darray_push(extensions_darray, VK_EXT_DEBUG_UTILS_EXTENSION_NAME); // p_darray_push(extensions_darray, VK_NV_DEVICE_DIAGNOSTIC_CHECKPOINTS_EXTENSION_NAME); PDEBUG("Vulkan Extensions: "); for (int i = 0; i < p_darray_len(extensions_darray); i++) PDEBUG("%s", extensions_darray[i]); #endif const char **required_validation_layers = NULL; #ifdef REND_DEBUG p_darray_push(required_validation_layers, "VK_LAYER_KHRONOS_validation"); PDEBUG("Required Validation Layers: "); for (uint32_t i = 0; i < p_darray_len(required_validation_layers); i++) { PDEBUG(" - %s", required_validation_layers[i]); } VkValidationFeatureEnableEXT enabled_validation_features[] = { // VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_EXT, // VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_RESERVE_BINDING_SLOT_EXT, }; VkValidationFeaturesEXT validation_features = { .sType = VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT, .pNext = NULL, // .enabledValidationFeatureCount = sizeof(enabled_validation_features) / sizeof(enabled_validation_features[0]), // .pEnabledValidationFeatures = enabled_validation_features, }; #endif VkInstanceCreateInfo vk_create_info = { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; vk_create_info.pApplicationInfo = &vk_app_info; vk_create_info.ppEnabledExtensionNames = extensions_darray; vk_create_info.enabledExtensionCount = p_darray_len(extensions_darray); vk_create_info.pApplicationInfo = &vk_app_info; #ifdef REND_DEBUG vk_create_info.enabledLayerCount = p_darray_len(required_validation_layers); vk_create_info.ppEnabledLayerNames = required_validation_layers; vk_create_info.pNext = &validation_features; #else vk_create_info.enabledLayerCount = 0; vk_create_info.ppEnabledLayerNames = NULL; #endif VkResult res = vkCreateInstance(&vk_create_info, vk_allocator, &vk_instance); CHECK_VK_RESULT(res); PDEBUG("Vulkan instance created!"); p_darray_destroy(extensions_darray); #ifdef REND_DEBUG p_darray_destroy(required_validation_layers); #endif #ifdef REND_DEBUG uint32_t log_severity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT; VkDebugUtilsMessengerCreateInfoEXT debug_create_info = { VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT}; debug_create_info.messageSeverity = log_severity; debug_create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT; debug_create_info.pfnUserCallback = rend_vk_debug_func; PFN_vkCreateDebugUtilsMessengerEXT func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr( vk_instance, "vkCreateDebugUtilsMessengerEXT"); if (!func) { PDEBUG("Failed to create vulkan debug messenger!"); return false; } func(vk_instance, &debug_create_info, vk_allocator, &vk_debug_messenger); #endif return true; } extern void rend_vk14_quit() { PDEBUG("[REND_VK14] Destroying device."); /* we destroy the device on quit */ if (vk_device.logical_device != 0) { vkDeviceWaitIdle(vk_device.logical_device); rend_vk_device_destroy(); vk_device.logical_device = 0; } if (vk_debug_messenger) { PFN_vkDestroyDebugUtilsMessengerEXT func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr( vk_instance, "vkDestroyDebugUtilsMessengerEXT"); func(vk_instance, vk_debug_messenger, vk_allocator); vk_debug_messenger = 0; } if (vk_device.swapchain_support.format) rfree(vk_device.swapchain_support.format); if (vk_device.swapchain_support.present_modes) rfree(vk_device.swapchain_support.present_modes); vkDestroyInstance(vk_instance, vk_allocator); vk_instance = 0; } extern bool rend_vk14_renderer_create(RendRenderer renderer, P_Window *window) { RendVk14Context *ctx = rmalloc(sizeof(*ctx)); memset(ctx, 0, sizeof *ctx); renderer->context = ctx; ctx->window = window; ctx->vsync = renderer->vsync; /* get surface from window */ if (!p_vulkan_create_surface(window, vk_instance, vk_allocator, &ctx->surface)) { PERROR("Failed to create vulkan surface!"); return false; } RendSpecs specs = { .sampler_anisotropy = true, .graphics = true, .transfer = true, .discrete_gpu = false, // even though its not a requirement, I expect discrete gpu to be picked }; /* we lazily create the logical device only after creating the first renderer * because we need the surface first */ if (vk_device.logical_device == 0) { rend_vk_device_create(ctx->surface, specs, &vk_device, rend_vk_device_score_default); } /* we must create arena before swapchain */ ctx->arena_persistent = rend_vk_arena_create( vk_device.logical_device, vk_device.physical_device, vk_device.properties.limits, vk_allocator); ctx->arena_frame = rend_vk_arena_create( vk_device.logical_device, vk_device.physical_device, vk_device.properties.limits, vk_allocator); /* create swapchain */ rend_vk_swapchain_create(ctx, &ctx->swapchain); /* timeline semaphore */ VkSemaphoreTypeCreateInfo timeline_type_info = {VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO}; timeline_type_info.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; timeline_type_info.initialValue = ctx->max_frames_in_flight; VkSemaphoreCreateInfo timeline_semaphore_info = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO}; timeline_semaphore_info.pNext = &timeline_type_info; if (vkCreateSemaphore(vk_device.logical_device, &timeline_semaphore_info, vk_allocator, &ctx->timeline_semaphore) != VK_SUCCESS) { PERROR("Unable to create the timeline semaphore for the renderer!"); return false; } /* create per frame semaphores */ VkSemaphoreCreateInfo frame_semaphore_info = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO}; for (uint32_t u = 0; u < REND_MAX_FRAMES_IN_FLIGHT; ++u) { if (vkCreateSemaphore( vk_device.logical_device, &frame_semaphore_info, vk_allocator, &ctx->frame_resources[u].image_acquired_semaphore) != VK_SUCCESS) { PERROR("Unable to create semaphore for frame #%u!", u); return false; } if (vkCreateSemaphore( vk_device.logical_device, &frame_semaphore_info, vk_allocator, &ctx->render_complete_semaphores[u]) != VK_SUCCESS) { PERROR("Unable to create render complete semaphore #%u!", u); return false; } VkCommandPoolCreateInfo pool_info = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO}; pool_info.queueFamilyIndex = vk_device.graphics_family_index; if (vkCreateCommandPool(vk_device.logical_device, &pool_info, vk_allocator, &ctx->frame_resources[u].command_pool) != VK_SUCCESS) { PERROR("Unable to create command pool for frame #%u!", u); return false; } VkCommandBufferAllocateInfo cmdbuf_info = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO}; cmdbuf_info.commandPool = ctx->frame_resources[u].command_pool; cmdbuf_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cmdbuf_info.commandBufferCount = 1; if (vkAllocateCommandBuffers(vk_device.logical_device, &cmdbuf_info, &ctx->frame_resources[u].command_buffer) != VK_SUCCESS) { PERROR("Unable to create command buffer for frame #%u!", u); return false; } } VkCommandPoolCreateInfo pool_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .queueFamilyIndex = vk_device.transfer_family_index, .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT }; if (vkCreateCommandPool(vk_device.logical_device, &pool_info, vk_allocator, &ctx->upload_command_pool) != VK_SUCCESS) { PERROR("Unable to create upload command pool!"); return false; } pool_info.queueFamilyIndex = vk_device.graphics_family_index; if (vkCreateCommandPool(vk_device.logical_device, &pool_info, vk_allocator, &ctx->graphics_command_pool) != VK_SUCCESS) { PERROR("Unable to create graphics command pool!"); return false; } ctx->frame = 0; ctx->frame_index = 0; ctx->next_signal_value = ctx->max_frames_in_flight + 1; /* start at frame zero */ /* * Descriptor Pool */ { RendBindingInfo bind_info = renderer->bind_info; uint32_t total_ubos = 0; for (uint32_t i = 0; i < bind_info.ubo_binding_count; ++i) { total_ubos += bind_info.ubo_array_sizes[i]; } uint32_t total_ssbos = 0; for (uint32_t i = 0; i < bind_info.ssbo_binding_count; ++i) { total_ssbos += bind_info.ssbo_array_sizes[i]; } uint32_t total_textures = 0; for (uint32_t i = 0; i < bind_info.texture_binding_count; ++i) { total_textures += bind_info.texture_array_sizes[i]; } const uint32_t pool_count = 3; VkDescriptorPoolSize pool_sizes[3] = { { .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = (total_ubos > 0) ? total_ubos : 1 }, { .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, .descriptorCount = (total_ssbos > 0) ? total_ssbos : 1 }, { .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = (total_textures > 0) ? total_textures : 1 } }; VkDescriptorPoolCreateInfo pool_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .flags = 0, .maxSets = 1, .poolSizeCount = pool_count, .pPoolSizes = pool_sizes, .pNext = NULL, }; VkResult result = vkCreateDescriptorPool(vk_device.logical_device, &pool_info, vk_allocator, &ctx->descriptor_pool); if (result != VK_SUCCESS) { REND__CRASH("Failed to create descriptor pool!"); return false; } /* * Descriptor Sets!!!!!! */ const uint32_t binding_count = bind_info.ubo_binding_count + bind_info.ssbo_binding_count + bind_info.texture_binding_count; VkDescriptorSetLayoutBinding binding_array[binding_count]; for (uint32_t i = 0; i < bind_info.ubo_binding_count; ++i) { binding_array[i] = (VkDescriptorSetLayoutBinding) { .binding = bind_info.ubo_bindings[i], .descriptorCount = bind_info.ubo_array_sizes[i], .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .stageFlags = VK_SHADER_STAGE_ALL, .pImmutableSamplers = 0, }; }; uint32_t offset = bind_info.ubo_binding_count; for (uint32_t i = 0; i < bind_info.ssbo_binding_count; ++i) { binding_array[i + offset] = (VkDescriptorSetLayoutBinding) { .binding = bind_info.ssbo_bindings[i], .descriptorCount = bind_info.ssbo_array_sizes[i], .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, .stageFlags = VK_SHADER_STAGE_ALL, .pImmutableSamplers = 0, }; }; offset += bind_info.ssbo_binding_count; for (uint32_t i = 0; i < bind_info.texture_binding_count; ++i) { binding_array[i + offset] = (VkDescriptorSetLayoutBinding) { .binding = bind_info.texture_bindings[i], .descriptorCount = bind_info.texture_array_sizes[i], .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .stageFlags = VK_SHADER_STAGE_ALL, .pImmutableSamplers = 0, }; }; VkDescriptorBindingFlags binding_flags[binding_count]; for (uint32_t u = 0; u < binding_count; ++u) { binding_flags[u] = VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT; } VkDescriptorSetLayoutBindingFlagsCreateInfo desc_flags_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO, .bindingCount = binding_count, .pBindingFlags = binding_flags }; VkDescriptorSetLayoutCreateInfo desc_layout_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .flags = 0, .bindingCount = binding_count, .pBindings = binding_array, .pNext = &desc_flags_info, }; result = vkCreateDescriptorSetLayout(vk_device.logical_device, &desc_layout_info, vk_allocator, &ctx->desc_layout); if (result != VK_SUCCESS) { REND__CRASH("Failed to create descriptor set layout!"); return false; } VkDescriptorSetAllocateInfo alloc_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .descriptorPool = ctx->descriptor_pool, .descriptorSetCount = 1, // ONLY ONE DESCRIPTOR SET .pSetLayouts = &ctx->desc_layout, .pNext = NULL, }; result = vkAllocateDescriptorSets(vk_device.logical_device, &alloc_info, &ctx->desc_set); if (result != VK_SUCCESS) { REND__CRASH("Failed to allocate descriptors!!!"); return false; } } return true; } extern void rend_vk14_renderer_destroy(RendRenderer renderer) { assert(renderer && renderer->context); RendVk14Context *ctx = (RendVk14Context *)renderer->context; VkDevice dev = vk_device.logical_device; PDEBUG("[REND] Waiting for device..."); vkDeviceWaitIdle(dev); PDEBUG("[REND] Destroying renderer..."); vkDestroySemaphore(dev, ctx->timeline_semaphore, vk_allocator); /* destroy per frame semaphores */ for (uint32_t u = 0; u < REND_MAX_FRAMES_IN_FLIGHT; ++u) { vkDestroySemaphore(dev, ctx->frame_resources[u].image_acquired_semaphore, vk_allocator); vkDestroySemaphore(dev, ctx->render_complete_semaphores[u], vk_allocator); vkDestroyCommandPool(dev, ctx->frame_resources[u].command_pool, vk_allocator); ctx->frame_resources[u].image_acquired_semaphore = 0; ctx->frame_resources[u].command_pool = 0; ctx->frame_resources[u].command_buffer = 0; } vkDestroyCommandPool(vk_device.logical_device, ctx->upload_command_pool, vk_allocator); vkDestroyCommandPool(vk_device.logical_device, ctx->graphics_command_pool, vk_allocator); rend_vk_arena_destroy(&ctx->arena_persistent); rend_vk_arena_destroy(&ctx->arena_frame); vkDestroyDescriptorSetLayout(dev, ctx->desc_layout, vk_allocator); vkDestroyDescriptorPool(dev, ctx->descriptor_pool, vk_allocator); PDEBUG("[REND] Destroying pipelines..."); for (uint32_t u = 0; u < ctx->pipeline_count; ++u) { rend_vk_pipeline_destroy(&ctx->pipelines[u]); } PDEBUG("[REND] Destroying swapchain..."); rend_vk_swapchain_destroy(ctx, &ctx->swapchain); ctx->swapchain.handle = 0; PDEBUG("[REND] Destroying surface..."); assert(vk_instance); vkDestroySurfaceKHR(vk_instance, ctx->surface, vk_allocator); ctx->surface = 0; rfree(renderer->context); renderer->context = 0; } extern bool rend_vk14_renderer_frame_begin(RendRenderer renderer) { assert(renderer && (uintptr_t)renderer != 0xffffffff00000000 && "Possible stack corruption"); RendVk14Context *ctx = (RendVk14Context *)renderer->context; /* check if swapchain needs recreation */ if (ctx->require_swapchain_recreation) { PDEBUG("[REND] Awaiting device..."); vkDeviceWaitIdle(vk_device.logical_device); PDEBUG("[REND] Recreating swapchain..."); rend_vk_swapchain_destroy(ctx, &ctx->swapchain); rend_vk_swapchain_create(ctx, &ctx->swapchain); ctx->require_swapchain_recreation = false; } VkDevice dev = vk_device.logical_device; /* wait on timeline semaphore */ const uint64_t frame_res_index = ctx->frame % ctx->max_frames_in_flight; ctx->frame_index = frame_res_index; const uint64_t signal_value = ctx->next_signal_value++; const uint64_t wait_value = signal_value - ctx->max_frames_in_flight; ctx->signal_value = signal_value; VkSemaphoreWaitInfo timeline_wait_info = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO, .semaphoreCount = 1, .pSemaphores = &ctx->timeline_semaphore, .pValues = &wait_value }; vkWaitSemaphores(vk_device.logical_device, &timeline_wait_info, UINT64_MAX); RendVkFrameResources frame_resource = ctx->frame_resources[frame_res_index]; vkResetCommandPool(dev, frame_resource.command_pool, 0); /* acquire next image */ VkResult acquire_image = vkAcquireNextImageKHR( vk_device.logical_device, ctx->swapchain.handle, UINT64_MAX, frame_resource.image_acquired_semaphore, VK_NULL_HANDLE, &ctx->image_index); if (acquire_image == VK_ERROR_OUT_OF_DATE_KHR) { /* out of date images, may need recreating */ ctx->require_swapchain_recreation = true; ctx->frame++; return false; } else if (acquire_image == VK_SUBOPTIMAL_KHR) { /* requires recreation but CAN CONTINUE RENDERING */ ctx->require_swapchain_recreation = true; } VkCommandBufferBeginInfo cmd_begin_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT }; vkBeginCommandBuffer(frame_resource.command_buffer, &cmd_begin_info); static const size_t NUM_LAYOUT_BARRIERS = 2; VkImageMemoryBarrier2 layout_barriers[NUM_LAYOUT_BARRIERS]; layout_barriers[0] = (VkImageMemoryBarrier2) { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, .srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, .srcAccessMask = 0, .dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, .dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT_KHR, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, .image = ctx->swapchain.images[ctx->image_index], .subresourceRange = (VkImageSubresourceRange) { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1 }, // .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, // .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, }; layout_barriers[1] = (VkImageMemoryBarrier2) { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, .srcStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT, .srcAccessMask = 0, .dstStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT, .dstAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, .newLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, .image = ctx->swapchain.depth_attachment.handle, .subresourceRange = (VkImageSubresourceRange) { .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1 }, }; VkMemoryBarrier2 memory_barrier = { .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2, .srcStageMask = VK_PIPELINE_STAGE_2_HOST_BIT, .srcAccessMask = VK_ACCESS_2_HOST_WRITE_BIT, .dstStageMask = VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT, .dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT, }; VkDependencyInfo dep_info = {VK_STRUCTURE_TYPE_DEPENDENCY_INFO}; dep_info.memoryBarrierCount = 1; dep_info.pMemoryBarriers = &memory_barrier; dep_info.imageMemoryBarrierCount = (uint32_t) NUM_LAYOUT_BARRIERS; dep_info.pImageMemoryBarriers = layout_barriers; vkCmdPipelineBarrier2(frame_resource.command_buffer, &dep_info); ctx->in_frame = true; return true; } extern void rend_vk14_renderer_frame_end(RendRenderer renderer, float *delta) { RendVk14Context *ctx = (RendVk14Context *)renderer->context; RendVkFrameResources res = ctx->frame_resources[ctx->frame_index]; VkImageMemoryBarrier2 present_barrier; present_barrier = (VkImageMemoryBarrier2) { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, .srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, .srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, .dstStageMask = VK_PIPELINE_STAGE_2_NONE, .dstAccessMask = 0, .oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, .image = ctx->swapchain.images[ctx->image_index], .subresourceRange = (VkImageSubresourceRange) { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1 } }; VkDependencyInfo dep_info = {VK_STRUCTURE_TYPE_DEPENDENCY_INFO}; dep_info.imageMemoryBarrierCount = 1; dep_info.pImageMemoryBarriers = &present_barrier; vkCmdPipelineBarrier2(res.command_buffer, &dep_info); vkEndCommandBuffer(res.command_buffer); /* ensure swapchain image is available */ VkSemaphoreSubmitInfo image_acquire_await_info = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, .semaphore = res.image_acquired_semaphore, .stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT }; /* signal that the image is presented */ VkSemaphoreSubmitInfo semaphore_signals[2] = { [0] = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, .semaphore = ctx->render_complete_semaphores[ctx->image_index], .stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT }, [1] = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, .semaphore = ctx->timeline_semaphore, .value = ctx->signal_value, .stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT }, }; VkCommandBufferSubmitInfo cmd_submit_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, .commandBuffer = res.command_buffer, }; VkSubmitInfo2 submit_info = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2, .waitSemaphoreInfoCount = 1, .pWaitSemaphoreInfos = &image_acquire_await_info, .commandBufferInfoCount = 1, .pCommandBufferInfos = &cmd_submit_info, .signalSemaphoreInfoCount = 2, .pSignalSemaphoreInfos = semaphore_signals }; vkQueueSubmit2(vk_device.graphics_queue, 1, &submit_info, VK_NULL_HANDLE); VkPresentInfoKHR present_info = { .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .waitSemaphoreCount = 1, .pWaitSemaphores = &ctx->render_complete_semaphores[ctx->image_index], .swapchainCount = 1, .pSwapchains = &ctx->swapchain.handle, .pImageIndices = &ctx->image_index, .pResults = NULL, }; vkQueuePresentKHR(vk_device.graphics_queue, &present_info); ctx->frame++; ctx->in_frame = false; uint64_t f = ctx->frame_index; /* clear host mapped memory */ rend_vk_arena_clear_all(&ctx->arena_frame); rend_vk_arena_clear(&ctx->arena_persistent, vk_device.host_index); } static inline void rend_vk14__renderer_render_pass_begin_internal(RendRenderer renderer, float r, float g, float b, float a, uint64_t view_handle, uint64_t depth_attachment_view_handle, uint32_t offset_x, uint32_t offset_y, uint32_t width, uint32_t height) { RendVk14Context *ctx = (RendVk14Context*) renderer->context; assert(ctx->in_frame && "must begin render pass inside a frame"); RendVkFrameResources frame_resource = ctx->frame_resources[ctx->frame_index]; VkRenderingAttachmentInfo color_attachment = { .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO, .imageView = (VkImageView) view_handle, .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, // clear image .storeOp = VK_ATTACHMENT_STORE_OP_STORE, // store for presentation .clearValue = (VkClearValue) { .color = {r, g, b, a}, } }; VkRenderingAttachmentInfo depth_attachment = { .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO, .imageView = (VkImageView) depth_attachment_view_handle, .imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, // clear depth data .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, // don't care after rendering .clearValue = (VkClearValue) { .depthStencil = (VkClearDepthStencilValue) {1.0f, 0}, }, }; VkRenderingInfo rendering_info = { .sType = VK_STRUCTURE_TYPE_RENDERING_INFO, .renderArea.offset = (VkOffset2D) {offset_x, offset_y}, .renderArea.extent = (VkExtent2D) {width, height}, .layerCount = 1, .colorAttachmentCount = 1, .pColorAttachments = &color_attachment, .pDepthAttachment = &depth_attachment, }; vkCmdBeginRendering(frame_resource.command_buffer, &rendering_info); VkViewport viewport = { .x = offset_x, .y = offset_y, .width = width, .height = height, .minDepth = 0.0f, .maxDepth = 1.0f }; vkCmdSetViewport(frame_resource.command_buffer, 0, 1, &viewport); VkRect2D scissor = {{offset_x, offset_y}, {width, height}}; vkCmdSetScissor(frame_resource.command_buffer, 0, 1, &scissor); } extern void rend_vk14_renderer_render_pass_begin(RendRenderer renderer, float r, float g, float b, float a) { RendVk14Context *ctx = (RendVk14Context*) renderer->context; rend_vk14__renderer_render_pass_begin_internal( renderer, r, g, b, a, (uint64_t) ctx->swapchain.views[ctx->image_index], (uint64_t) ctx->swapchain.depth_attachment.view, 0, 0, ctx->swapchain.extent.width, ctx->swapchain.extent.height ); } extern void rend_vk14_renderer_render_pass_begin_texture(RendRenderer renderer, RendTexture *texture) { RendVk14Context *ctx = (RendVk14Context*) renderer->context; RendVkFrameResources frame_resource = ctx->frame_resources[ctx->frame_index]; rend_vk14_texture_transition_layout(renderer, ctx->frame_resources[ctx->frame_index].command_buffer, texture, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); VkRenderingAttachmentInfo color_attachment = { .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO, .imageView = (VkImageView) texture->view, .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, // load image instead of clearing .storeOp = VK_ATTACHMENT_STORE_OP_STORE, // store for presentation }; VkRenderingAttachmentInfo depth_attachment = { .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO, .imageView = (VkImageView) ctx->swapchain.depth_attachment.view, .imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, // clear depth data .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, // don't care after rendering .clearValue = (VkClearValue) { .depthStencil = (VkClearDepthStencilValue) {1.0f, 0}, }, }; VkRenderingInfo rendering_info = { .sType = VK_STRUCTURE_TYPE_RENDERING_INFO, .renderArea.offset = (VkOffset2D) {0, 0}, .renderArea.extent = (VkExtent2D) {texture->width, texture->height}, .layerCount = 1, .colorAttachmentCount = 1, .pColorAttachments = &color_attachment, .pDepthAttachment = &depth_attachment, }; vkCmdBeginRendering(frame_resource.command_buffer, &rendering_info); VkViewport viewport = { .x = 0, .y = 0, .width = texture->width, .height = texture->height, .minDepth = 0.0f, .maxDepth = 1.0f }; vkCmdSetViewport(frame_resource.command_buffer, 0, 1, &viewport); VkRect2D scissor = {{0, 0}, {texture->width, texture->height}}; vkCmdSetScissor(frame_resource.command_buffer, 0, 1, &scissor); } extern void rend_vk14_renderer_render_pass_end_texture(RendRenderer renderer, RendTexture *texture) { RendVk14Context *ctx = (RendVk14Context*) renderer->context; rend_vk14_texture_transition_layout(renderer, ctx->frame_resources[ctx->frame_index].command_buffer, texture, VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL); rend_vk14_renderer_render_pass_end(renderer); } extern void rend_vk14_renderer_render_pass_end(RendRenderer renderer) { RendVk14Context *ctx = (RendVk14Context*) renderer->context; assert(ctx->in_frame && "must end render pass inside a frame"); RendVkFrameResources res = ctx->frame_resources[ctx->frame_index]; vkCmdEndRendering(res.command_buffer); } extern void rend_vk14_descriptor_write_buffer(RendRenderer renderer, RendBuffer ubo, uint32_t binding, uint32_t slot, uint32_t offset, uint32_t size, bool is_ubo) { RendVk14Context *ctx = renderer->context; VkDescriptorBufferInfo buffer_info = { .buffer = (VkBuffer) ubo.handle, .offset = offset, .range = size, }; VkWriteDescriptorSet descriptor_write = { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = ctx->desc_set, .dstBinding = binding, .dstArrayElement = slot, // write texture to slot .descriptorCount = 1, .descriptorType = (is_ubo) ? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, .pBufferInfo = &buffer_info, }; vkUpdateDescriptorSets(vk_device.logical_device, 1, &descriptor_write, 0, NULL); } extern RendBuffer rend_vk14_buffer_create_lifetime(RendRenderer renderer, size_t size, RendBufferType type, bool gpu, int lifetime) { RendVk14Context *ctx = (RendVk14Context *)renderer->context; int32_t index = (gpu) ? vk_device.device_index : vk_device.host_index; VkBufferUsageFlags vk_usage = 0; switch (type) { case REND_BUFFER_VERTEX: vk_usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; break; case REND_BUFFER_INDEX: vk_usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; break; case REND_BUFFER_UNIFORM: vk_usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; break; case REND_BUFFER_STORAGE: vk_usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT; break; case REND_BUFFER_TRANSFER: vk_usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; break; default: REND__CRASH("Invalid buffer type!"); break; } RendBuffer buffer = {0}; buffer.usage = vk_usage | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; bool is_concurrent = (gpu && (vk_device.graphics_family_index != vk_device.transfer_family_index)); int32_t family[] = { vk_device.graphics_family_index, vk_device.transfer_family_index }; VkBufferCreateInfo buffer_info = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .size = size, .usage = buffer.usage, .sharingMode = is_concurrent ? VK_SHARING_MODE_CONCURRENT : VK_SHARING_MODE_EXCLUSIVE, .queueFamilyIndexCount = is_concurrent ? 2 : 1, .pQueueFamilyIndices = is_concurrent ? family : NULL, }; if (vkCreateBuffer(vk_device.logical_device, &buffer_info, vk_allocator, (VkBuffer*) &buffer.handle) != VK_SUCCESS) { REND__CRASH("Failed to create VkBuffer!"); } VkMemoryRequirements mem_reqs; vkGetBufferMemoryRequirements(vk_device.logical_device, (VkBuffer) buffer.handle, &mem_reqs); assert((mem_reqs.memoryTypeBits & (1u << index)) && "Buffer incompatible with chosen memory type!"); RendVkArenaAllocator *arena = (lifetime == REND_LIFETIME_FRAME) ? &ctx->arena_frame : &ctx->arena_persistent; RendMemory vk_memory = rend_vk_arena_alloc(arena, mem_reqs.size, index); if (vkBindBufferMemory(vk_device.logical_device, (VkBuffer) buffer.handle, (VkDeviceMemory) vk_memory.device_memory, vk_memory.offset) != VK_SUCCESS) { REND__CRASH("Failed to bind VkBuffer memory!"); } buffer.memory = vk_memory; VkBufferDeviceAddressInfo address_info = { .sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, .buffer = (VkBuffer) buffer.handle, }; if (!gpu && vk_memory.host_mapped_memory) { buffer.mapped_memory = vk_memory.host_mapped_memory; } else { buffer.mapped_memory = NULL; } buffer.gpu_address = vkGetBufferDeviceAddress(vk_device.logical_device, &address_info); buffer.size = size; return buffer; } extern void rend_vk14_buffer_destroy(RendBuffer *buffer) { // NOTE: this function is meant to be called by the user // when freeing his buffers mid frame, there may be a better // way using fences perhaps? or by checking the timeline semaphore? vkDeviceWaitIdle(vk_device.logical_device); vkDestroyBuffer(vk_device.logical_device, (VkBuffer) buffer->handle, vk_allocator); memset(buffer, 0xC0FFEE, sizeof *buffer); // fill buffer with coffee } extern void rend_vk14_buffer_copy(RendRenderer renderer, RendBuffer *dest, size_t dest_offset, RendBuffer *src, size_t src_offset, size_t bytes) { assert(src && dest); // check that im not sending null pointers assert(src->usage & VK_BUFFER_USAGE_TRANSFER_SRC_BIT); // source buffer must be marked as transfer src assert(dest->usage & VK_BUFFER_USAGE_TRANSFER_DST_BIT); // dest buffer must be marked as transfer dest RendVk14Context *ctx = (RendVk14Context *)renderer->context; VkBuffer src_vk = (VkBuffer)(uintptr_t)src->handle; VkBuffer dest_vk = (VkBuffer)(uintptr_t)dest->handle; VkCommandBuffer transfer_cmd = VK_NULL_HANDLE; VkCommandBufferAllocateInfo alloc_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandBufferCount = 1, .commandPool = ctx->upload_command_pool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .pNext = NULL, }; if (vkAllocateCommandBuffers(vk_device.logical_device, &alloc_info, &transfer_cmd) != VK_SUCCESS) { REND__CRASH("Failed to allocate transfer command buffer!"); } VkCommandBufferBeginInfo begin_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, }; vkBeginCommandBuffer(transfer_cmd, &begin_info); VkBufferCopy buffer_copy = { .srcOffset = (VkDeviceSize)src_offset, .dstOffset = (VkDeviceSize)dest_offset, .size = (VkDeviceSize)bytes, }; vkCmdCopyBuffer(transfer_cmd, src_vk, dest_vk, 1, &buffer_copy); vkEndCommandBuffer(transfer_cmd); VkCommandBufferSubmitInfo cmd_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, .commandBuffer = transfer_cmd, .deviceMask = 0, }; VkSubmitInfo2 submit_info = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2, .commandBufferInfoCount = 1, .pCommandBufferInfos = &cmd_info, }; vkQueueSubmit2(vk_device.transfer_queue, 1, &submit_info, VK_NULL_HANDLE); vkQueueWaitIdle(vk_device.transfer_queue); vkFreeCommandBuffers(vk_device.logical_device, ctx->upload_command_pool, 1, &transfer_cmd); } extern RendTexture rend_vk14_texture_create(RendRenderer renderer, uint32_t width, uint32_t height, uint32_t depth, uint32_t mip_levels, uint32_t layers, RendFormat format) { assert(format < REND_FORMAT_COUNT && "Invalid format!"); RendVk14Context *ctx = (RendVk14Context*) renderer->context; VkFormat vk_format = vk_format_from_rend_format[format]; uint32_t safe_depth = (depth > 0) ? depth : 1; uint32_t safe_mips = (mip_levels > 0) ? mip_levels : 1; uint32_t safe_layers = (layers > 0) ? layers : 1; RendTexture tex = { .handle = 0, .width = width, .height = height, .depth = safe_depth, .mip_levels = safe_mips, .layers = safe_layers, .format = vk_format, .layout = VK_IMAGE_LAYOUT_UNDEFINED, }; VkImageCreateInfo image_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = (safe_depth > 1) ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D, .extent = { .width = width, .height = height, .depth = safe_depth }, .mipLevels = tex.mip_levels, .arrayLayers = tex.layers, .format = vk_format, .tiling = VK_IMAGE_TILING_OPTIMAL, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .samples = VK_SAMPLE_COUNT_1_BIT, .flags = 0, }; if (vkCreateImage(vk_device.logical_device, &image_info, vk_allocator, (VkImage*)&tex.handle) != VK_SUCCESS) { REND__CRASH("failed to create image!"); return tex; } VkMemoryRequirements mem_requirements; vkGetImageMemoryRequirements(vk_device.logical_device, (VkImage)tex.handle, &mem_requirements); uint32_t index = rend_vk_get_heap_index(mem_requirements.memoryTypeBits, vk_device.device_index); tex.memory = rend_vk_arena_alloc(&ctx->arena_persistent, mem_requirements.size, index); vkBindImageMemory(vk_device.logical_device, (VkImage)tex.handle, (VkDeviceMemory)tex.memory.device_memory, (VkDeviceSize)tex.memory.offset); // Correct ImageView creation using strict VkImageViewType and derived format VkImageViewCreateInfo view_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = (VkImage)tex.handle, .viewType = (safe_depth > 1) ? VK_IMAGE_VIEW_TYPE_3D : VK_IMAGE_VIEW_TYPE_2D, .format = vk_format, .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = tex.mip_levels, .baseArrayLayer = 0, .layerCount = tex.layers, }, }; if (vkCreateImageView(vk_device.logical_device, &view_info, vk_allocator, (VkImageView*)&tex.view) != VK_SUCCESS) { REND__CRASH("failed to create image view!"); return tex; } /* per-texture sampler */ VkSamplerCreateInfo sampler_info = { .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .magFilter = VK_FILTER_LINEAR, // force sharp upscaling .minFilter = VK_FILTER_LINEAR, // TODO: add filter setting to texture .addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT, .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT, .addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT, .mipmapMode = (tex.mip_levels > 1) ? VK_SAMPLER_MIPMAP_MODE_LINEAR : VK_SAMPLER_MIPMAP_MODE_NEAREST, .minLod = 0.0f, .maxLod = (tex.mip_levels > 1) ? (float)tex.mip_levels : 0.0f, .anisotropyEnable = (tex.mip_levels > 1 && vk_device.features.samplerAnisotropy) ? VK_TRUE : VK_FALSE, .maxAnisotropy = 8.0f, }; if (vkCreateSampler(vk_device.logical_device, &sampler_info, vk_allocator, (VkSampler*)&tex.sampler) != VK_SUCCESS) { REND__CRASH("failed to create sampler!"); } return tex; } extern void rend_vk14_texture_destroy(RendRenderer renderer, RendTexture *tex) { assert(renderer && tex); RendVk14Context *ctx = (RendVk14Context*)renderer->context; if (tex->handle) { vkDestroyImage(vk_device.logical_device, (VkImage) tex->handle, vk_allocator); tex->handle = 0; } if (tex->view) { vkDestroyImageView(vk_device.logical_device, (VkImageView) tex->view, vk_allocator); tex->view = 0; } if (tex->sampler) { vkDestroySampler(vk_device.logical_device, (VkSampler)tex->sampler, vk_allocator); tex->sampler = 0; } // memset(tex, 0xBABE, sizeof *tex); } extern void rend_vk14_texture_transition_layout(RendRenderer renderer, VkCommandBuffer cmd, RendTexture *texture, VkImageLayout new_layout) { RendVk14Context *ctx = renderer->context; VkCommandPool pool = ctx->upload_command_pool; VkImageMemoryBarrier2 barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, .oldLayout = texture->layout, .newLayout = new_layout, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = (VkImage) texture->handle, .subresourceRange = (VkImageSubresourceRange) { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = texture->mip_levels, .baseArrayLayer = 0, .layerCount = texture->layers, }, .srcAccessMask = 0, // TODO .dstAccessMask = 0, // TODO }; if (texture->layout == VK_IMAGE_LAYOUT_UNDEFINED && new_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { barrier.srcAccessMask = 0; barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; barrier.srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; barrier.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; } else if (texture->layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && new_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT; barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; } else { barrier.srcAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT; barrier.srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; barrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; } VkDependencyInfo dep = { .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, .pImageMemoryBarriers = &barrier, .imageMemoryBarrierCount = 1, }; texture->layout = new_layout; vkCmdPipelineBarrier2(cmd, &dep); } extern void rend_vk14_texture_transfer_ownership_release(RendRenderer renderer, VkCommandBuffer cmd, RendTexture *texture, uint32_t src_family, uint32_t dst_family, VkImageLayout new_layout) { RendVk14Context *ctx = renderer->context; (void) ctx; // reserved in case you need it for stage/access lookups later VkImageMemoryBarrier2 barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, .oldLayout = texture->layout, .newLayout = new_layout, .srcQueueFamilyIndex = src_family, .dstQueueFamilyIndex = dst_family, .image = (VkImage) texture->handle, .subresourceRange = (VkImageSubresourceRange) { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = texture->mip_levels, .baseArrayLayer = 0, .layerCount = texture->layers, }, }; if (texture->layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && new_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; barrier.dstAccessMask = 0; barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; barrier.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT; } else { barrier.srcAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT; barrier.dstAccessMask = 0; barrier.srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; barrier.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT; } VkDependencyInfo dep = { .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, .pImageMemoryBarriers = &barrier, .imageMemoryBarrierCount = 1, }; texture->layout = new_layout; vkCmdPipelineBarrier2(cmd, &dep); } extern void rend_vk14_texture_transfer_ownership_acquire(RendRenderer renderer, VkCommandBuffer cmd, RendTexture *texture, uint32_t src_family, uint32_t dst_family, VkImageLayout new_layout) { RendVk14Context *ctx = renderer->context; (void) ctx; VkImageMemoryBarrier2 barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, .oldLayout = texture->layout, .newLayout = new_layout, .srcQueueFamilyIndex = src_family, .dstQueueFamilyIndex = dst_family, .image = (VkImage) texture->handle, .subresourceRange = (VkImageSubresourceRange) { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = texture->mip_levels, .baseArrayLayer = 0, .layerCount = texture->layers, }, }; if (texture->layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && new_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { barrier.srcAccessMask = 0; barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT; barrier.srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; } else { barrier.srcAccessMask = 0; barrier.dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT; barrier.srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; barrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; } VkDependencyInfo dep = { .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, .pImageMemoryBarriers = &barrier, .imageMemoryBarrierCount = 1, }; texture->layout = new_layout; vkCmdPipelineBarrier2(cmd, &dep); } extern void rend_vk14_texture_copy_buffer(RendRenderer renderer, RendTexture *texture, RendBuffer *buffer) { RendVk14Context *ctx = renderer->context; VkCommandBuffer cmd_transfer = rend_vk_cmdbuffer_single_use_begin(ctx->upload_command_pool); { rend_vk14_texture_transition_layout(renderer, cmd_transfer, texture, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); VkBufferImageCopy region = { .imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .imageSubresource.layerCount = texture->layers, .imageExtent = (VkExtent3D) { texture->width, texture->height, texture->depth }, }; vkCmdCopyBufferToImage(cmd_transfer, (VkBuffer) buffer->handle, (VkImage) texture->handle, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); rend_vk14_texture_transfer_ownership_release(renderer, cmd_transfer, texture, vk_device.transfer_family_index, vk_device.graphics_family_index, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); } rend_vk_cmdbuffer_single_use_end(ctx->upload_command_pool, cmd_transfer, vk_device.transfer_queue); VkCommandBuffer cmd_graphics = rend_vk_cmdbuffer_single_use_begin(ctx->graphics_command_pool); { rend_vk14_texture_transfer_ownership_acquire(renderer, cmd_graphics, texture, vk_device.transfer_family_index, vk_device.graphics_family_index, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); } rend_vk_cmdbuffer_single_use_end(ctx->graphics_command_pool, cmd_graphics, vk_device.graphics_queue); } extern void rend_vk14_texture_blit(RendRenderer renderer, RendTexture *src, RendTexture *dst, uint32_t src_x, uint32_t src_y, uint32_t src_w, uint32_t src_h, uint32_t dst_x, uint32_t dst_y, uint32_t dst_w, uint32_t dst_h) { RendVk14Context *ctx = (RendVk14Context*) renderer->context; VkCommandBuffer cmd = ctx->frame_resources[ctx->frame_index].command_buffer; rend_vk14_texture_transition_layout(renderer, cmd, src, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); rend_vk14_texture_transition_layout(renderer, cmd, dst, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); VkImageBlit2 blit_region = { .sType = VK_STRUCTURE_TYPE_IMAGE_BLIT_2, .srcSubresource = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1, }, .srcOffsets = { { src_x, src_y, 0 }, { src_x + src_w, src_y + src_h, 1 } }, .dstSubresource = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1, }, .dstOffsets = { { dst_x, dst_y, 0 }, { dst_x + dst_w, dst_y + dst_h, 1 } } }; VkBlitImageInfo2 blit_info = { .sType = VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2, .srcImage = (VkImage) src->handle, .srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, .dstImage = (VkImage) dst->handle, .dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, .regionCount = 1, .pRegions = &blit_region, .filter = VK_FILTER_NEAREST // crispy }; vkCmdBlitImage2(cmd, &blit_info); } static VkCommandBuffer rend_vk_cmdbuffer_single_use_begin(VkCommandPool pool) { VkCommandBufferAllocateInfo alloc_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandPool = pool, .commandBufferCount = 1, }; VkCommandBuffer cmd; vkAllocateCommandBuffers(vk_device.logical_device, &alloc_info, &cmd); VkCommandBufferBeginInfo begin = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, }; vkBeginCommandBuffer(cmd, &begin); return cmd; } static void rend_vk_cmdbuffer_single_use_end(VkCommandPool pool, VkCommandBuffer cmd, VkQueue q) { vkEndCommandBuffer(cmd); VkSubmitInfo submit_info = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1, .pCommandBuffers = &cmd, }; vkQueueSubmit(q, 1, &submit_info, VK_NULL_HANDLE); vkQueueWaitIdle(q); vkFreeCommandBuffers(vk_device.logical_device, pool, 1, &cmd); } extern bool rend_vk14_pipeline_create(RendRenderer renderer, RendPipeline pipeline, Rend__PipelineConfig config, uint8_t type, const uint8_t *shader1, size_t bytes1, const uint8_t *shader2, size_t bytes2, const uint8_t *shader3, size_t bytes3) { RendVk14Context *ctx = (RendVk14Context *)renderer->context; pipeline->idx = ctx->pipeline_count++; pipeline->backend_ctx = ctx; // useful for when we only have RendPipeline as an argument RendVkPipeline *vk_pipeline = &ctx->pipelines[pipeline->idx]; vk_pipeline->blend_enable = false; /* * set color and depth format */ VkFormat color_format = (config.color_format != REND_FORMAT_UNDEFINED) ? vk_format_from_rend_format[config.color_format] : ctx->swapchain.format.format; VkFormat depth_format = (config.depth_format != REND_FORMAT_UNDEFINED) ? vk_format_from_rend_format[config.depth_format] : vk_device.depth_format; /* * Pipeline Layout */ vk_pipeline->push_constants_range = 0; uint32_t total_size = 0; for (uint32_t u = 0; u < config.push_constant_count; ++u) { total_size += config.push_constants[u].size; } vk_pipeline->push_constants_range = total_size; VkPushConstantRange pc_range = { .offset = 0, .size = total_size, .stageFlags = VK_SHADER_STAGE_ALL, }; VkPipelineLayoutCreateInfo layout_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .pSetLayouts = &ctx->desc_layout, .setLayoutCount = 1, .pPushConstantRanges = (total_size > 0) ? &pc_range : NULL, .pushConstantRangeCount = (total_size > 0) ? 1 : 0, }; CHECK_VK_RESULT(vkCreatePipelineLayout(vk_device.logical_device, &layout_info, vk_allocator, &vk_pipeline->layout)); /* * Setup shaders based on pipeline type */ VkPipelineShaderStageCreateInfo shader_stages[3] = {0}; VkShaderModule shader_modules[3] = {0}; uint32_t shader_count = 0; if (type == REND__PIPELINE_GRAPHICS) { shader_count = 2; shader_modules[0] = rend_vk_shader_module_create(shader1, bytes1); shader_modules[1] = rend_vk_shader_module_create(shader2, bytes2); shader_stages[0] = (VkPipelineShaderStageCreateInfo) { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_VERTEX_BIT, .module = shader_modules[0], .pName = "main" }; shader_stages[1] = (VkPipelineShaderStageCreateInfo) { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_FRAGMENT_BIT, .module = shader_modules[1], .pName = "main", }; } else if (type == REND__PIPELINE_MESH) { shader_count = 2; shader_modules[0] = rend_vk_shader_module_create(shader1, bytes1); shader_modules[1] = rend_vk_shader_module_create(shader2, bytes2); shader_stages[0] = (VkPipelineShaderStageCreateInfo) { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_MESH_BIT_EXT, .module = shader_modules[0], .pName = "main" }; shader_stages[1] = (VkPipelineShaderStageCreateInfo) { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_FRAGMENT_BIT, .module = shader_modules[1], .pName = "main", }; } else if (type == REND__PIPELINE_COMPUTE) { shader_count = 1; shader_modules[0] = rend_vk_shader_module_create(shader1, bytes1); shader_stages[0] = (VkPipelineShaderStageCreateInfo) { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_COMPUTE_BIT, .module = shader_modules[0], .pName = "main" }; } else { REND__CRASH("Invalid pipeline type"); } /* * Compute Pipeline Branch */ if (type == REND__PIPELINE_COMPUTE) { VkComputePipelineCreateInfo compute_pipeline_info = { .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, .stage = shader_stages[0], .layout = vk_pipeline->layout, .basePipelineHandle = VK_NULL_HANDLE, .basePipelineIndex = -1, }; CHECK_VK_RESULT(vkCreateComputePipelines(vk_device.logical_device, VK_NULL_HANDLE, 1, &compute_pipeline_info, vk_allocator, &vk_pipeline->handle)); PINFO("Successfully created compute pipeline!"); } /* * Graphics / Mesh Pipeline Branch */ else { VkPipelineRenderingCreateInfo rendering_info = {VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO}; rendering_info.colorAttachmentCount = 1; rendering_info.pColorAttachmentFormats = &color_format; rendering_info.depthAttachmentFormat = depth_format; VkPipelineViewportStateCreateInfo viewport_state = {VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO}; viewport_state.viewportCount = 1; viewport_state.scissorCount = 1; VkPipelineRasterizationStateCreateInfo rasterizer = {VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO}; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; rasterizer.polygonMode = vk_polymode[config.polygon_mode]; rasterizer.lineWidth = 1.0f; rasterizer.cullMode = vk_cullflags[config.cull_mode]; rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; rasterizer.depthBiasEnable = VK_FALSE; VkPipelineMultisampleStateCreateInfo multisampling = {VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO}; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; VkPipelineColorBlendAttachmentState color_blend_attachment = {0}; color_blend_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; color_blend_attachment.blendEnable = VK_FALSE; VkPipelineColorBlendStateCreateInfo color_blending = { VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO}; color_blending.logicOpEnable = VK_FALSE; color_blending.logicOp = VK_LOGIC_OP_COPY; color_blending.attachmentCount = 1; color_blending.pAttachments = &color_blend_attachment; VkPipelineDepthStencilStateCreateInfo depth_stencil = { VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO}; depth_stencil.depthTestEnable = config.depth_test_enable; depth_stencil.depthWriteEnable = VK_TRUE; depth_stencil.depthCompareOp = VK_COMPARE_OP_LESS; depth_stencil.depthBoundsTestEnable = VK_FALSE; depth_stencil.stencilTestEnable = VK_FALSE; VkDynamicState dynamic_states[] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; VkPipelineDynamicStateCreateInfo dynamic_state = { VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO}; dynamic_state.dynamicStateCount = 2; dynamic_state.pDynamicStates = dynamic_states; VkPipelineVertexInputStateCreateInfo vertex_input_info = { VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO}; VkPipelineInputAssemblyStateCreateInfo input_assembly = {VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO}; VkVertexInputBindingDescription* vk_bindings = NULL; VkVertexInputAttributeDescription* vk_attributes = NULL; if (type == REND__PIPELINE_GRAPHICS) { uint32_t binding_count = config.vertex_binding_count; uint32_t attribute_count = config.vertex_attribute_count; /* * Bind Vertex Attributes */ if (binding_count > 0) { vk_bindings = rmalloc(sizeof(VkVertexInputBindingDescription) * binding_count); for (uint32_t i = 0; i < binding_count; i++) { RendVertexBinding rb = config.vertex_bindings[i]; vk_bindings[i].binding = rb.binding; vk_bindings[i].stride = rb.stride; vk_bindings[i].inputRate = (rb.input_rate == REND_INPUT_RATE_INSTANCE) ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX; } } if (attribute_count > 0) { vk_attributes = rmalloc(sizeof(VkVertexInputAttributeDescription) * attribute_count); for (uint32_t i = 0; i < attribute_count; i++) { RendVertexAttributes ra = config.vertex_attributes[i]; vk_attributes[i].binding = ra.binding; vk_attributes[i].location = ra.location; vk_attributes[i].offset = ra.offset; vk_attributes[i].format = vk_format_from_rend_format[ra.format]; } } vertex_input_info.vertexBindingDescriptionCount = binding_count; vertex_input_info.pVertexBindingDescriptions = vk_bindings; vertex_input_info.vertexAttributeDescriptionCount = attribute_count; vertex_input_info.pVertexAttributeDescriptions = vk_attributes; input_assembly.topology = vk_topology[config.topology]; input_assembly.primitiveRestartEnable = VK_FALSE; } VkGraphicsPipelineCreateInfo pipeline_info = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO}; pipeline_info.pNext = &rendering_info; pipeline_info.stageCount = shader_count; pipeline_info.pStages = shader_stages; pipeline_info.pVertexInputState = &vertex_input_info; pipeline_info.pInputAssemblyState = &input_assembly; pipeline_info.pViewportState = &viewport_state; pipeline_info.pRasterizationState = &rasterizer; pipeline_info.pMultisampleState = &multisampling; pipeline_info.pColorBlendState = &color_blending; pipeline_info.pDepthStencilState = &depth_stencil; pipeline_info.pDynamicState = &dynamic_state; pipeline_info.layout = vk_pipeline->layout; pipeline_info.renderPass = VK_NULL_HANDLE; CHECK_VK_RESULT(vkCreateGraphicsPipelines(vk_device.logical_device, VK_NULL_HANDLE, 1, &pipeline_info, vk_allocator, &vk_pipeline->handle)); if (vk_bindings) rfree(vk_bindings); if (vk_attributes) rfree(vk_attributes); } /* destroy shader modules */ for (uint32_t i = 0; i < shader_count; ++i) { if (shader_modules[i] != VK_NULL_HANDLE) { vkDestroyShaderModule(vk_device.logical_device, shader_modules[i], vk_allocator); } } return true; } extern void rend_vk14_pipeline_bind(RendPipeline pipeline) { RendVk14Context *ctx = pipeline->backend_ctx; RendVkPipeline vk_pipeline = ctx->pipelines[pipeline->idx]; VkCommandBuffer cmd = ctx->frame_resources[ctx->frame_index].command_buffer; VkPipelineBindPoint bind_point = (pipeline->type == REND__PIPELINE_COMPUTE) ? VK_PIPELINE_BIND_POINT_COMPUTE : VK_PIPELINE_BIND_POINT_GRAPHICS; vkCmdBindPipeline(cmd, bind_point, vk_pipeline.handle); vkCmdBindDescriptorSets(cmd, bind_point, vk_pipeline.layout, 0, 1, &ctx->desc_set, 0, NULL); } extern void rend_vk14_pipeline_push_constants(RendPipeline pipeline, void *push_data, size_t size) { assert(pipeline && push_data); RendVk14Context *ctx = pipeline->backend_ctx; RendVkPipeline p = ctx->pipelines[pipeline->idx]; assert(size <= p.push_constants_range && "Size exceeds bound push constant range!"); vkCmdPushConstants(ctx->frame_resources[ctx->frame_index].command_buffer, p.layout, VK_SHADER_STAGE_ALL, 0, size, push_data); } extern void rend_vk14_pipeline_bind_vertex_buffer(RendPipeline pipeline, uint32_t binding, RendBuffer buffer, size_t offset) { RendVk14Context *ctx = pipeline->backend_ctx; VkBuffer buf = (VkBuffer)(uintptr_t)buffer.handle; VkCommandBuffer cmd = ctx->frame_resources[ctx->frame_index].command_buffer; VkDeviceSize vk_offset = (VkDeviceSize)offset; vkCmdBindVertexBuffers(cmd, binding, 1, &buf, &vk_offset); } extern void rend_vk14_pipeline_bind_index_buffer(RendPipeline pipeline, RendBuffer buffer, size_t offset, RendIndexType index_type) { RendVk14Context *ctx = pipeline->backend_ctx; VkBuffer buf = (VkBuffer)(uintptr_t)buffer.handle; VkCommandBuffer cmd = ctx->frame_resources[ctx->frame_index].command_buffer; VkIndexType vk_index_type = (index_type == REND_INDEX_UINT16) ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32; vkCmdBindIndexBuffer(cmd, buf, (VkDeviceSize)offset, vk_index_type); } extern void rend_vk14_descriptor_write_texture(void *ctx, RendTexture *texture, uint32_t binding, uint32_t slot) { RendVk14Context *vk_ctx = (RendVk14Context*) ctx; VkDescriptorImageInfo image_info = { .sampler = (VkSampler) texture->sampler, .imageView = (VkImageView) texture->view, .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, }; VkWriteDescriptorSet descriptor_write = { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = vk_ctx->desc_set, .dstBinding = binding, .dstArrayElement = slot, // write texture to slot .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .pImageInfo = &image_info, }; vkUpdateDescriptorSets(vk_device.logical_device, 1, &descriptor_write, 0, NULL); } extern void rend_vk14_pipeline_dispatch(RendPipeline pipeline, uint32_t x, uint32_t y, uint32_t z) { RendVk14Context *ctx = pipeline->backend_ctx; VkCommandBuffer cmd = ctx->frame_resources[ctx->frame_index].command_buffer; vkCmdDispatch(cmd, x, y, z); } extern void rend_vk14_pipeline_draw(RendPipeline pipeline, size_t count, uint32_t instance_count) { RendVk14Context *ctx = pipeline->backend_ctx; VkCommandBuffer cmd = ctx->frame_resources[ctx->frame_index].command_buffer; vkCmdDraw(cmd, count, instance_count, 0, 0); } extern void rend_vk14_pipeline_draw_indexed(RendPipeline pipeline, uint32_t index_count, uint32_t first_index, int32_t vertex_offset, uint32_t instance_count) { RendVk14Context *ctx = pipeline->backend_ctx; VkCommandBuffer cmd = ctx->frame_resources[ctx->frame_index].command_buffer; vkCmdDrawIndexed(cmd, index_count, instance_count, first_index, vertex_offset, 0); } extern void rend_vk14_pipeline_set_blend(RendPipeline pipeline, bool blend) { RendVk14Context *ctx = pipeline->backend_ctx; RendVkPipeline *vk_pipeline = &ctx->pipelines[pipeline->idx]; vk_pipeline->blend_enable = blend; } extern void rend_vk14_pipeline_draw_instanced(RendPipeline pipeline, uint32_t index_count, uint32_t instance_count) { RendVk14Context *ctx = pipeline->backend_ctx; VkCommandBuffer cmd = ctx->frame_resources[ctx->frame_index].command_buffer; vkCmdDraw(cmd, index_count, instance_count, 0, 0); } extern VKAPI_ATTR VkBool32 VKAPI_CALL rend_vk_debug_func(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, VkDebugUtilsMessageTypeFlagsEXT message_types, const VkDebugUtilsMessengerCallbackDataEXT *callback_data, void *user_data) { switch (message_severity) { default: case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: PERROR(callback_data->pMessage); break; case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: PWARN(callback_data->pMessage); break; case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: PINFO(callback_data->pMessage); break; case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: PTRACE(callback_data->pMessage); break; } return VK_FALSE; } static void rend_vk_pipeline_destroy(RendVkPipeline *pipeline) { VkDevice dev = vk_device.logical_device; if (pipeline->handle != VK_NULL_HANDLE) { vkDestroyPipeline(dev, pipeline->handle, vk_allocator); } if (pipeline->layout != VK_NULL_HANDLE) { vkDestroyPipelineLayout(dev, pipeline->layout, vk_allocator); } } static void rend_vk_swapchain_create(RendVk14Context *ctx, RendVkSwapchain *swapchain) { VkSurfaceCapabilitiesKHR surface_caps; vkGetPhysicalDeviceSurfaceCapabilitiesKHR(vk_device.physical_device, ctx->surface, &surface_caps); uint32_t width = surface_caps.currentExtent.width; uint32_t height = surface_caps.currentExtent.height; VkExtent2D swapchain_extent = {width, height}; ctx->max_frames_in_flight = REND_MIN_FRAMES_IN_FLIGHT; if (surface_caps.minImageCount > ctx->max_frames_in_flight) { ctx->max_frames_in_flight = surface_caps.minImageCount; } if (surface_caps.maxImageCount < ctx->max_frames_in_flight) { ctx->max_frames_in_flight = surface_caps.maxImageCount; } bool found = false; for (uint32_t i = 0; i < vk_device.swapchain_support.format_count; i++) { VkSurfaceFormatKHR format = vk_device.swapchain_support.format[i]; /* preferred format */ if (format.format == VK_FORMAT_B8G8R8A8_UNORM && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { swapchain->format = format; found = true; break; } } /* default format */ if (!found) { swapchain->format = vk_device.swapchain_support.format[0]; } /* NOTE: mailbox is probably the best for most applications * but I may want the ability to pick a different mode in * very niche circumstances. * * We also may want immediate mode if we want to disable VSYNC. */ /* default present mode */ VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR; for (uint32_t i = 0; i < vk_device.swapchain_support.present_mode_count; i++) { VkPresentModeKHR pres = vk_device.swapchain_support.present_modes[i]; /* preferred format is mailbox */ if (ctx->vsync) { if (pres == VK_PRESENT_MODE_MAILBOX_KHR) { present_mode = pres; break; } } else { if (pres == VK_PRESENT_MODE_IMMEDIATE_KHR) { present_mode = pres; break; } } } #ifdef REND_DEBUG static const char* present_mode_names[] = { [VK_PRESENT_MODE_IMMEDIATE_KHR] = "IMMEDIATE", [VK_PRESENT_MODE_MAILBOX_KHR] = "MAILBOX", [VK_PRESENT_MODE_FIFO_KHR] = "FIFO", [VK_PRESENT_MODE_FIFO_RELAXED_KHR] = "FIFO_RELAXED" }; PDEBUG("Present mode %s was chosen!", present_mode_names[present_mode]); #endif VkExtent2D min = surface_caps.minImageExtent; VkExtent2D max = surface_caps.maxImageExtent; swapchain_extent.width = (swapchain_extent.width < min.width) ? min.width : swapchain_extent.width; swapchain_extent.width = (swapchain_extent.width > max.width) ? max.width : swapchain_extent.width; swapchain_extent.height = (swapchain_extent.height < min.height) ? min.height : swapchain_extent.height; swapchain_extent.height = (swapchain_extent.height > max.height) ? max.height : swapchain_extent.height; uint32_t img_count = surface_caps.minImageCount + 1; if (surface_caps.maxImageCount > 0 && img_count > surface_caps.maxImageCount) { img_count = surface_caps.maxImageCount; } VkSwapchainCreateInfoKHR swapchain_create_info = { VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR}; swapchain_create_info.surface = ctx->surface; swapchain_create_info.minImageCount = img_count; swapchain_create_info.imageFormat = swapchain->format.format; swapchain_create_info.imageColorSpace = swapchain->format.colorSpace; swapchain_create_info.imageExtent = swapchain_extent; swapchain_create_info.imageArrayLayers = 1; swapchain_create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; /* index sharing */ if (vk_device.graphics_family_index != vk_device.present_family_index) { uint32_t queueFamilyIndices[] = {vk_device.graphics_family_index, vk_device.present_family_index}; swapchain_create_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; swapchain_create_info.queueFamilyIndexCount = 2; swapchain_create_info.pQueueFamilyIndices = queueFamilyIndices; } else { swapchain_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; swapchain_create_info.queueFamilyIndexCount = 0; swapchain_create_info.pQueueFamilyIndices = 0; } swapchain_create_info.preTransform = surface_caps.currentTransform; swapchain_create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; swapchain_create_info.presentMode = present_mode; swapchain_create_info.clipped = VK_TRUE; swapchain_create_info.oldSwapchain = 0; if (vkCreateSwapchainKHR(vk_device.logical_device, &swapchain_create_info, vk_allocator, &swapchain->handle) != VK_SUCCESS) { REND__CRASH("Swapchain creation failed, you are probably trying to create two renderers for the same surface!"); } swapchain->image_count = 0; CHECK_VK_RESULT(vkGetSwapchainImagesKHR(vk_device.logical_device, swapchain->handle, &swapchain->image_count, 0)); if (!swapchain->images) { swapchain->images = rmalloc(swapchain->image_count * sizeof *swapchain->images); } if (!swapchain->views) { swapchain->views = rmalloc(swapchain->image_count * sizeof *swapchain->views); } CHECK_VK_RESULT( vkGetSwapchainImagesKHR(vk_device.logical_device, swapchain->handle, &swapchain->image_count, swapchain->images)); for (uint32_t i = 0; i < swapchain->image_count; i++) { VkImageViewCreateInfo view_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; view_info.image = swapchain->images[i]; view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; view_info.format = swapchain->format.format; view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; view_info.subresourceRange.baseMipLevel = 0; view_info.subresourceRange.levelCount = 1; view_info.subresourceRange.baseArrayLayer = 0; view_info.subresourceRange.layerCount = 1; CHECK_VK_RESULT(vkCreateImageView(vk_device.logical_device, &view_info, vk_allocator, &swapchain->views[i])); } /* depth resources */ if (!rend_vk_device_detect_depth_format(&vk_device)) { vk_device.depth_format = VK_FORMAT_UNDEFINED; PFATAL("Failed to find a supported depth buffer format!"); } swapchain->depth_attachment = rend_vk_image_create( vk_device.logical_device, VK_IMAGE_TYPE_2D, swapchain_extent.width, swapchain_extent.height, vk_device.depth_format, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 1, // depth 1, // mip level 1, // layers VK_SAMPLE_COUNT_1_BIT, VK_SHARING_MODE_EXCLUSIVE ); uint32_t mem_type = rend_vk_image_required_memory_type(&swapchain->depth_attachment); uint32_t depth_index = rend_vk_get_heap_index(mem_type, vk_device.device_index); assert(ctx); RendMemory depth_mem = rend_vk_arena_alloc(&ctx->arena_persistent, swapchain->depth_attachment.requirements.size, depth_index); rend_vk_image_bind_memory(&swapchain->depth_attachment, &depth_mem); rend_vk_image_view_create( &swapchain->depth_attachment, VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_ASPECT_DEPTH_BIT ); swapchain->extent = swapchain_extent; } static void rend_vk_swapchain_destroy(RendVk14Context *ctx, RendVkSwapchain *swapchain) { if (!swapchain || swapchain->handle == VK_NULL_HANDLE) return; assert(vk_device.logical_device); rend_vk_image_destroy(&swapchain->depth_attachment); if (swapchain->views) { for (uint32_t i = 0; i < swapchain->image_count; i++) { vkDestroyImageView(vk_device.logical_device, swapchain->views[i], vk_allocator); } rfree(swapchain->views); swapchain->views = 0; } if (swapchain->images) { rfree(swapchain->images); swapchain->images = 0; } swapchain->image_count = 0; vkDestroySwapchainKHR(vk_device.logical_device, swapchain->handle, vk_allocator); swapchain->handle = VK_NULL_HANDLE; } static VkShaderModule rend_vk_shader_module_create(const void *data, size_t size) { VkShaderModuleCreateInfo create_info = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; create_info.codeSize = size; create_info.pCode = (const uint32_t *)data; VkShaderModule module; VkResult res = vkCreateShaderModule(vk_device.logical_device, &create_info, vk_allocator, &module); if (res != VK_SUCCESS) { PWARN("Failed to create shader module for %p", data); return VK_NULL_HANDLE; } return module; } static uint32_t rend_vk_get_heap_index(uint32_t memory_type_bits, uint32_t preferred_index) { if (memory_type_bits & (1u << preferred_index)) { return preferred_index; } for (uint32_t i = 0; i < 32; i++) { if (memory_type_bits & (1u << i)) { return i; } } return UINT32_MAX; }