From b0dd2cdf3a922f56064aa0129e72b5eb343347c0 Mon Sep 17 00:00:00 2001 From: FroggyGreen Date: Wed, 22 May 2024 09:57:13 -0500 Subject: [PATCH] Added textures Textures are stored in a global hashtable. Each mesh pipeline has access to a single texture. The texture may be used as an atlas by creating meshes with different texture coordinates. The current system should be fine for text glyphs. However, if you have an atlas where each subdivision is equal in its dimensions, the use of layers and the VK_IMAGE_VIEW_TYPE_2D_ARRAY view type would be best. Some preliminary code for this was completed, but for now inserting textures into the hashmap defaults to VK_IMAGE_VIEW_TYPE_2D. --- .gitmodules | 3 + external/stb | 1 + renderer/CMakeLists.txt | 3 + renderer/shaders/ui-pane-tex.frag | 13 + renderer/shaders/ui-pane-tex.vert | 29 +++ renderer/src/renderer.c | 519 +++++++++++++++++++++++++++++++++++--- renderer/src/renderer.h | 15 ++ resources/textures/arrow.png | Bin 0 -> 129 bytes src/main.lisp | 123 ++++++++- 9 files changed, 661 insertions(+), 45 deletions(-) create mode 160000 external/stb create mode 100644 renderer/shaders/ui-pane-tex.frag create mode 100644 renderer/shaders/ui-pane-tex.vert create mode 100644 resources/textures/arrow.png diff --git a/.gitmodules b/.gitmodules index 6a3c5b3..4200723 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "external/glfw"] path = external/glfw url = https://github.com/glfw/glfw +[submodule "external/stb"] + path = external/stb + url = https://github.com/nothings/stb diff --git a/external/stb b/external/stb new file mode 160000 index 0000000..ae721c5 --- /dev/null +++ b/external/stb @@ -0,0 +1 @@ +Subproject commit ae721c50eaf761660b4f90cc590453cdb0c2acd0 diff --git a/renderer/CMakeLists.txt b/renderer/CMakeLists.txt index 3ddef60..1f64441 100644 --- a/renderer/CMakeLists.txt +++ b/renderer/CMakeLists.txt @@ -44,3 +44,6 @@ target_link_libraries(sofrenderer PRIVATE glfw) # Vulkan target_include_directories(sofrenderer PRIVATE ${Vulkan_INCLUDE_DIRS}) target_link_libraries(sofrenderer PRIVATE ${Vulkan_LIBRARIES}) + +# stb +target_include_directories(sofrenderer PRIVATE "${PROJECT_SOURCE_DIR}/../external/stb") diff --git a/renderer/shaders/ui-pane-tex.frag b/renderer/shaders/ui-pane-tex.frag new file mode 100644 index 0000000..d6a6ae1 --- /dev/null +++ b/renderer/shaders/ui-pane-tex.frag @@ -0,0 +1,13 @@ +#version 460 + +layout(location = 0) in vec4 fragColor; +layout(location = 1) in vec2 fragTex; + +layout(location = 0) out vec4 outColor; + +layout(set = 1, binding = 0) uniform sampler2D texSampler; + +void main() +{ + outColor = texture(texSampler, fragTex); +} diff --git a/renderer/shaders/ui-pane-tex.vert b/renderer/shaders/ui-pane-tex.vert new file mode 100644 index 0000000..5db8773 --- /dev/null +++ b/renderer/shaders/ui-pane-tex.vert @@ -0,0 +1,29 @@ +#version 460 + +// vertex geometry data +layout(location = 0) in vec2 inPos; +layout(location = 1) in vec2 inTex; + +// instance data +layout(location = 2) in vec3 inInstancePos; +layout(location = 3) in vec3 inInstanceScale; +layout(location = 4) in vec4 inInstanceColor; + +layout(location = 0) out vec4 fragColor; +layout(location = 1) out vec2 fragTex; + +layout(binding = 0) uniform Camera +{ + mat4 view; + mat4 perspective; + mat4 orthographic; + +} camera; + +void main() +{ + vec3 pos = vec3(inPos, 0.0) * inInstanceScale + inInstancePos; + gl_Position = camera.orthographic * vec4(pos, 1.0); + fragColor = inInstanceColor; + fragTex = inTex; +} diff --git a/renderer/src/renderer.c b/renderer/src/renderer.c index 6f1fb24..e0ec63f 100644 --- a/renderer/src/renderer.c +++ b/renderer/src/renderer.c @@ -27,6 +27,12 @@ You should have received a copy of the GNU General Public License along with Sha #include #include +#define STB_IMAGE_IMPLEMENTATION +#include + +#define MAX_FRAMES_IN_FLIGHT 2 // must be a power of two! +#define MAX_TEXTURES 256 + typedef struct vulkan_frame_data { VkCommandBuffer command_buffer; @@ -46,6 +52,9 @@ typedef struct vulkan_buffer typedef struct vulkan_image { + u32 width; + u32 height; + u32 layer_count; VkImage handle; VkDeviceMemory memory; VkImageView view_handle; @@ -60,6 +69,15 @@ typedef struct renderer_camera } renderer_camera; +typedef struct texture +{ + vulkan_image image; + VkSampler sampler; + VkFormat format; + VkDescriptorSet descriptor_sets[MAX_FRAMES_IN_FLIGHT]; + +} texture; + typedef struct mesh { vulkan_buffer vertex_buffer; @@ -80,11 +98,11 @@ typedef struct mesh_pipeline vertex_input_type geometry_type; vertex_input_type instance_type; hash_table meshes; + + char texture_name[HASH_TABLE_KEY_LEN]; } mesh_pipeline; - -#define MAX_FRAMES_IN_FLIGHT 2 // must be a power of two! typedef struct vulkan_context { db_list deletion_stack; @@ -95,7 +113,7 @@ typedef struct vulkan_context VkDebugUtilsMessengerEXT debug_messenger; #endif VkSurfaceKHR surface; - + struct { VkPhysicalDevice physical; @@ -139,6 +157,11 @@ typedef struct vulkan_context VkDescriptorSetLayout camera_descriptor_set_layout; VkDescriptorPool camera_descriptor_pool; VkDescriptorSet camera_descriptor_sets[MAX_FRAMES_IN_FLIGHT]; + + VkSampler sampler; + VkDescriptorSetLayout texture_descriptor_set_layout; + VkDescriptorPool texture_descriptor_pool; + hash_table textures; hash_table mesh_pipelines; @@ -172,7 +195,10 @@ static vulkan_context context; #ifdef RENDERER_DEBUG static const char* validation_layers[] = { "VK_LAYER_KHRONOS_validation" }; #endif -static const char* device_extensions[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; +static const char* device_extensions[] = + { + VK_KHR_SWAPCHAIN_EXTENSION_NAME + }; // // Window @@ -247,6 +273,11 @@ void poll_window_input() // Vulkan // +static VkDeviceSize aligned_size(VkDeviceSize old_size, VkDeviceSize alignment) +{ + return (old_size + alignment - 1) & ~(alignment - 1); +} + static void signal_for_shutdown(void (*fn)()) { db_list_pushback(&context.deletion_stack, fn); @@ -389,6 +420,13 @@ static u8 init_vulkan_instance() #ifdef RENDERER_DEBUG if (!check_validation_layer_support()) return 0; #endif + + u32 api_version = VK_API_VERSION_1_3; + if (vkEnumerateInstanceVersion(&api_version) != VK_SUCCESS) + { + trace_log(LOG_FATAL, "Failed to create Vulkan instance; VK_API_VERSION_1_3 is not supported."); + return 0; + } const VkApplicationInfo app_info = { @@ -398,7 +436,7 @@ static u8 init_vulkan_instance() .applicationVersion = VK_MAKE_VERSION(1, 0, 0), .pEngineName = "No Engine", .engineVersion = VK_MAKE_VERSION(1, 0, 0), - .apiVersion = VK_API_VERSION_1_0 + .apiVersion = api_version }; VkInstanceCreateInfo create_info = @@ -655,6 +693,20 @@ static u8 pick_physical_device() // discrete GPUs are more performant than integrated GPUs if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) score += 1000; + // check anisotropic filtering + if (features.samplerAnisotropy != VK_TRUE) + { + score = 0; + trace_log(LOG_WARN, "The physical device '%s' does not support anisotropic filtering.", properties.deviceName); + } + + // check Vulkan 1.3 support + if (properties.apiVersion < VK_API_VERSION_MINOR(VK_API_VERSION_1_3)) + { + score = 0; + trace_log(LOG_WARN, "The physical device '%s' does not support Vulkan 1.3.", properties.deviceName); + } + // check queue requirements const queue_family_indices indices = find_queue_families(physical_devices[i]); if (!indices.is_graphics || !indices.is_present) @@ -663,10 +715,11 @@ static u8 pick_physical_device() trace_log(LOG_WARN, "The physical device '%s' does not meet queue requirements.", properties.deviceName); } - // check swap chain requirements - u8 extensions_supported = check_physical_device_extension_support(physical_devices[i]); + const u8 extensions_supported = check_physical_device_extension_support(physical_devices[i]); + if (extensions_supported) { + // check swap chain requirements swap_chain_support_details support_details; if (!init_swap_chain_support_details(&support_details, physical_devices[i]) || !support_details.format_size || !support_details.present_mode_size) { @@ -732,29 +785,35 @@ static u8 init_logical_device() .queueCount = 1, .pQueuePriorities = &priority }; + + VkPhysicalDeviceFeatures2 physical_device_features = + { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, + .features = {}, + .pNext = NULL + }; + VkPhysicalDeviceFeatures available_features; + vkGetPhysicalDeviceFeatures(context.device.physical, &available_features); + physical_device_features.features.fillModeNonSolid = available_features.fillModeNonSolid; + physical_device_features.features.samplerAnisotropy = VK_TRUE; - VkPhysicalDeviceFeatures device_features = {}; - { - VkPhysicalDeviceFeatures available_features; - vkGetPhysicalDeviceFeatures(context.device.physical, &available_features); - device_features.fillModeNonSolid = available_features.fillModeNonSolid; - } - - VkDeviceCreateInfo device_info = + const VkDeviceCreateInfo device_info = { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pQueueCreateInfos = queue_infos, .queueCreateInfoCount = unique_queue_family_size, - .pEnabledFeatures = &device_features, + .pEnabledFeatures = NULL, .enabledExtensionCount = sizeof(device_extensions) / sizeof(const char*), .ppEnabledExtensionNames = device_extensions, #ifdef RENDERER_DEBUG .enabledLayerCount = sizeof(validation_layers) / sizeof(const char*), - .ppEnabledLayerNames = validation_layers + .ppEnabledLayerNames = validation_layers, #else .enabledLayerCount = 0, - .ppEnabledLayerNames = NULL + .ppEnabledLayerNames = NULL, #endif + .flags = 0, + .pNext = &physical_device_features }; if (vkCreateDevice(context.device.physical, &device_info, context.allocator, &context.device.logical) != VK_SUCCESS) @@ -770,7 +829,6 @@ static u8 init_logical_device() return 1; } - static u8 allocate_command_buffers(VkCommandPool command_pool, VkCommandBuffer* command_buffers, u32 command_buffer_count) { const VkCommandBufferAllocateInfo alloc_info = @@ -791,7 +849,6 @@ static u8 allocate_command_buffers(VkCommandPool command_pool, VkCommandBuffer* return 1; } - static u8 begin_command_recording(VkCommandBuffer command_buffer, VkCommandBufferUsageFlags flags) { const VkCommandBufferBeginInfo info = @@ -1027,8 +1084,8 @@ static u8 copy_vulkan_buffer(vulkan_buffer* dst, vulkan_buffer* src, VkDeviceSiz vkCmdCopyBuffer(command_buffer, src->handle, dst->handle, 1, ©_region); if (!end_single_submit_command_recording(command_buffer)) return 0; + vkFreeCommandBuffers(context.device.logical, context.single_submit_context.command_pool, 1, &command_buffer); - return 1; } @@ -1047,7 +1104,6 @@ static u8 find_vulkan_memory_type(u32* type_index, u32 type_filter, VkMemoryProp return 0; } - static u8 init_image_view(VkImageView* view, VkImage image, VkImageViewType type, VkFormat format, VkImageAspectFlags flags, u32 layer_count) { const VkImageSubresourceRange subresource_range = @@ -1087,7 +1143,10 @@ static u8 init_vulkan_image(vulkan_image* image, u32 width, u32 height, u32 laye VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImageViewType view_type, VkImageAspectFlags aspect_flags) { - VkImageCreateInfo image_info = + image->width = width; + image->height = height; + image->layer_count = layer_count; + const VkImageCreateInfo image_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D, @@ -1153,6 +1212,155 @@ static void shutdown_vulkan_image(vulkan_image* image) vkDestroyImage(context.device.logical, image->handle, context.allocator); } +static u8 transition_image_layout(vulkan_image* image, VkImageLayout old_layout, VkImageLayout new_layout) +{ + VkCommandBuffer command_buffer; + if (!allocate_command_buffers(context.single_submit_context.command_pool, &command_buffer, 1)) return 0; + + if (!begin_single_submit_command_recording(command_buffer)) return 0; + + VkImageMemoryBarrier barrier = + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image->handle, + .subresourceRange = (VkImageSubresourceRange) + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = image->layer_count + }, + .pNext = NULL + }; + + VkPipelineStageFlags src_stage = VK_PIPELINE_STAGE_NONE; + VkPipelineStageFlags dst_stage = VK_PIPELINE_STAGE_NONE; + if (old_layout == VK_IMAGE_LAYOUT_UNDEFINED && + new_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) + { + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + src_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + dst_stage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } + else if (old_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && + new_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) + { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + src_stage = VK_PIPELINE_STAGE_TRANSFER_BIT; + dst_stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } + else + { + trace_log(LOG_ERROR, "Unsupported image layout transition."); + return 0; + } + + vkCmdPipelineBarrier(command_buffer, src_stage, dst_stage, 0, 0, NULL, 0, NULL, 1, &barrier); + + if (!end_single_submit_command_recording(command_buffer)) return 0; + + vkFreeCommandBuffers(context.device.logical, context.single_submit_context.command_pool, 1, &command_buffer); + return 1; +} + +static u8 load_image_2D(vulkan_image* image, u32 offset_x, u32 offset_y, u32 size_x, u32 size_y, const void* pixels, u64 byte_size) +{ + vulkan_buffer staging_buffer; + if (!init_vulkan_staging_buffer(&staging_buffer, byte_size)) return 0; + if (!load_vulkan_buffer(&staging_buffer, 0, byte_size, 0, pixels)) return 0; + + VkCommandBuffer command_buffer; + if (!allocate_command_buffers(context.single_submit_context.command_pool, &command_buffer, 1)) return 0; + + if (!begin_single_submit_command_recording(command_buffer)) return 0; + + if (!transition_image_layout(image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)) + return 0; + + const VkBufferImageCopy copy_region = + { + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = (VkImageSubresourceLayers) + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = image->layer_count + }, + .imageOffset = (VkOffset3D){ offset_x, offset_y, 0 }, + .imageExtent = (VkExtent3D){ size_x, size_y, 1 } + }; + vkCmdCopyBufferToImage(command_buffer, staging_buffer.handle, image->handle, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©_region); + + if (!end_single_submit_command_recording(command_buffer)) return 0; + + if (!transition_image_layout(image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)) + return 0; + + vkFreeCommandBuffers(context.device.logical, context.single_submit_context.command_pool, 1, &command_buffer); + shutdown_vulkan_buffer(&staging_buffer); + + return 1; +} + +static u8 load_image_2D_array(vulkan_image* image, u32 size_x, u32 size_y, const void* pixels, u64 byte_size) +{ + vulkan_buffer staging_buffer; + if (!init_vulkan_staging_buffer(&staging_buffer, byte_size)) return 0; + if (!load_vulkan_buffer(&staging_buffer, 0, byte_size, 0, pixels)) return 0; + + VkCommandBuffer command_buffer; + if (!allocate_command_buffers(context.single_submit_context.command_pool, &command_buffer, 1)) return 0; + + if (!begin_single_submit_command_recording(command_buffer)) return 0; + + if (!transition_image_layout(image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)) + return 0; + + VkBufferImageCopy* copy_regions = malloc(image->layer_count * sizeof(VkBufferImageCopy)); + for (u32 i = 0; i < image->layer_count; i++) + { + copy_regions[i] = (VkBufferImageCopy) + { + .bufferOffset = i * byte_size / image->layer_count, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = (VkImageSubresourceLayers) + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .baseArrayLayer = i, + .layerCount = 1 + }, + .imageOffset = (VkOffset3D){ 0, 0, 0 }, + .imageExtent = (VkExtent3D){ size_x, size_y, 1 } + }; + } + vkCmdCopyBufferToImage(command_buffer, staging_buffer.handle, image->handle, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, image->layer_count, copy_regions); + + if (!transition_image_layout(image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)) + return 0; + + if (!end_single_submit_command_recording(command_buffer)) return 0; + + free(copy_regions); + vkFreeCommandBuffers(context.device.logical, context.single_submit_context.command_pool, 1, &command_buffer); + shutdown_vulkan_buffer(&staging_buffer); + + return 1; +} + static VkSurfaceFormatKHR choose_surface_format(const VkSurfaceFormatKHR* available_formats, u32 available_format_size) { for (u32 i = 0; i < available_format_size; i++) @@ -1593,7 +1801,7 @@ static u8 init_camera() } // descriptor set layout - const VkDescriptorSetLayoutBinding camera_layout_binding = + const VkDescriptorSetLayoutBinding descriptor_layout_binding = { .binding = 0, .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, @@ -1605,7 +1813,7 @@ static u8 init_camera() { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .bindingCount = 1, - .pBindings = &camera_layout_binding, + .pBindings = &descriptor_layout_binding, .flags = 0, .pNext = NULL }; @@ -1650,7 +1858,7 @@ static u8 init_camera() }; if (vkAllocateDescriptorSets(context.device.logical, &alloc_info, context.camera_descriptor_sets) != VK_SUCCESS) { - trace_log(LOG_FATAL, "Failed to create Vulkan camera; failed to allocated descriptor sets"); + trace_log(LOG_FATAL, "Failed to create Vulkan camera; failed to allocate descriptor sets."); return 0; } for (u8 i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) @@ -1702,6 +1910,211 @@ void set_camera_orthographic_projection(const f32* orthographic) context.update_camera_uniform_counter = MAX_FRAMES_IN_FLIGHT; } +static u8 init_texture(texture* t, u32 width, u32 height, VkImageViewType view_type, VkFormat format, u32 layer_count) +{ + if (!init_vulkan_image(&t->image, width, height, layer_count, format, VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, view_type, VK_IMAGE_ASPECT_COLOR_BIT)) + return 0; + + if (!transition_image_layout(&t->image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)) + return 0; + if (!transition_image_layout(&t->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)) + return 0; + + t->sampler = context.sampler; + t->format = format; + + // descriptor sets + VkDescriptorSetLayout* descriptor_set_layouts = malloc(MAX_FRAMES_IN_FLIGHT * sizeof(VkDescriptorSetLayout)); + for (u8 i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + descriptor_set_layouts[i] = context.texture_descriptor_set_layout; + const VkDescriptorSetAllocateInfo alloc_info = + { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .descriptorPool = context.texture_descriptor_pool, + .descriptorSetCount = MAX_FRAMES_IN_FLIGHT, + .pSetLayouts = descriptor_set_layouts, + .pNext = NULL + }; + if (vkAllocateDescriptorSets(context.device.logical, &alloc_info, t->descriptor_sets) != VK_SUCCESS) + { + trace_log(LOG_FATAL, "Failed to create texture; failed to allocate descriptor sets."); + return 0; + } + for (u8 i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) + { + const VkDescriptorImageInfo image_info = + { + .sampler = t->sampler, + .imageView = t->image.view_handle, + .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL + }; + const VkWriteDescriptorSet write = + { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = t->descriptor_sets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = 1, + .pImageInfo = &image_info, + .pBufferInfo = NULL, + .pTexelBufferView = NULL, + .pNext = NULL + }; + vkUpdateDescriptorSets(context.device.logical, 1, &write, 0, NULL); + } + free(descriptor_set_layouts); + + return 1; +} + +static void shutdown_texture(texture* t) +{ + vkFreeDescriptorSets(context.device.logical, context.texture_descriptor_pool, MAX_FRAMES_IN_FLIGHT, t->descriptor_sets); + shutdown_vulkan_image(&t->image); +} + +static void shutdown_textures() +{ + for(u32 i = 0; i < context.textures.slot_count; i++) + for (hash_table_entry* entry = context.textures.entries[i]; entry != NULL; entry = entry->next) + { + texture* t = entry->data; + shutdown_texture(t); + } + shutdown_hash_table(&context.textures); + + vkDestroyDescriptorPool(context.device.logical, context.texture_descriptor_pool, context.allocator); + + vkDestroyDescriptorSetLayout(context.device.logical, context.texture_descriptor_set_layout, context.allocator); + + vkDestroySampler(context.device.logical, context.sampler, context.allocator); + + trace_log(LOG_DEBUG, "Succesfully destroyed textures."); +} + +static u8 init_textures() +{ + VkPhysicalDeviceProperties properties; + vkGetPhysicalDeviceProperties(context.device.physical, &properties); + + VkSamplerCreateInfo sampler_info = + { + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .magFilter = VK_FILTER_LINEAR, + .minFilter = VK_FILTER_LINEAR, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT, + .anisotropyEnable = VK_TRUE, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK, + .unnormalizedCoordinates = VK_FALSE, + .compareOp = VK_COMPARE_OP_ALWAYS, + .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, + .mipLodBias = 0.0f, + .minLod = 0.0f, + .maxLod = 0.0f, + .pNext = NULL + }; + if (vkCreateSampler(context.device.logical, &sampler_info, context.allocator, &context.sampler) != VK_SUCCESS) + { + trace_log(LOG_FATAL, "Failed texture initialization; failed to create sampler."); + return 0; + } + + // descriptor set layout + const VkDescriptorSetLayoutBinding descriptor_layout_binding = + { + .binding = 0, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, + .pImmutableSamplers = NULL + }; + const VkDescriptorSetLayoutCreateInfo descriptor_layout_info = + { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .bindingCount = 1, + .pBindings = &descriptor_layout_binding, + .flags = 0, + .pNext = NULL + }; + if (vkCreateDescriptorSetLayout(context.device.logical, &descriptor_layout_info, context.allocator, &context.texture_descriptor_set_layout) != VK_SUCCESS) + { + trace_log(LOG_FATAL, "Failed texture initialization; failed to create descriptor set layout."); + return 0; + } + + // descriptor pool + const VkDescriptorPoolSize pool_size = + { + .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = MAX_FRAMES_IN_FLIGHT + }; + const VkDescriptorPoolCreateInfo descriptor_pool_info = + { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .poolSizeCount = 1, + .pPoolSizes = &pool_size, + .maxSets = MAX_FRAMES_IN_FLIGHT * MAX_TEXTURES, + .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, + .pNext = NULL + }; + if (vkCreateDescriptorPool(context.device.logical, &descriptor_pool_info, context.allocator, &context.texture_descriptor_pool) != VK_SUCCESS) + { + trace_log(LOG_FATAL, "Failed texture initialization; failed to create descriptor pool."); + return 0; + } + + init_hash_table(&context.textures, 32, sizeof(texture), basic_hash); + + trace_log(LOG_DEBUG, "Textures have successfully initialized."); + signal_for_shutdown(shutdown_textures); + + return 1; +} + +u8 insert_image_2d_texture_from_file(const char* name) +{ + char path[512]; + snprintf(path, 512, "resources/textures/%s.png", name); + + i32 width, height, channels; + stbi_uc* pixels = stbi_load(path, &width, &height, &channels, STBI_rgb_alpha); + if (!pixels) + { + trace_log(LOG_ERROR, "Failed to insert image2D texture at path '%s'; stb failed to load the image data.", path); + return 0; + } + + texture t; + if (!init_texture(&t, width, height, VK_IMAGE_VIEW_TYPE_2D, VK_FORMAT_R8G8B8A8_SRGB, 1) || + !load_image_2D(&t.image, 0, 0, width, height, pixels, width * height * 4)) + { + trace_log(LOG_ERROR, "Failed to insert image2D texture '%s'; failed to setup vulkan texture.", name); + return 0; + } + + return (hash_table_insert(&context.textures, name, &t) != NULL); +} + +u8 remove_texture(const char* name) +{ + texture t; + void* ptexture = &t; + if(!hash_table_remove(&context.textures, name, &ptexture)) + { + trace_log(LOG_ERROR, "Failed to remove texture '%s'; lookup failed.", name); + return 0; + } + shutdown_texture(&t); + + return 1; +} + static u8 init_mesh(mesh* msh, const mesh_geometry* geometry, vertex_input_type geometry_type, const mesh_instance_data* instance_data, vertex_input_type instance_type) { VkDeviceSize geometry_vertices_byte_size = 0, instance_data_byte_size = 0; @@ -1728,6 +2141,9 @@ static u8 init_mesh(mesh* msh, const mesh_geometry* geometry, vertex_input_type case VERTEX_INPUT_TYPE_POS2: *ds = count * sizeof(pos2); break; + case VERTEX_INPUT_TYPE_POS2_TEX2: + *ds = count * sizeof(pos2_tex2); + break; case VERTEX_INPUT_TYPE_POS3_POS3_COLOR4: *ds = count * sizeof(pos3_pos3_color4); break; @@ -1877,6 +2293,23 @@ static u8 init_mesh_pipeline(mesh_pipeline* pipeline, const mesh_pipeline_templa attribute_descriptions = darray_pushback(attribute_descriptions, attributes, 1); } break; + + case VERTEX_INPUT_TYPE_POS2_TEX2: + { + const VkVertexInputBindingDescription binding = + { + .binding = i, .stride = sizeof(pos2_tex2), .inputRate = input_rate + }; + const VkVertexInputAttributeDescription attributes[] = + { + (VkVertexInputAttributeDescription){ .location = idx, .binding = i, .format = VK_FORMAT_R32G32_SFLOAT, .offset = offsetof(pos2_tex2, pos) }, + (VkVertexInputAttributeDescription){ .location = idx + 1, .binding = i, .format = VK_FORMAT_R32G32_SFLOAT, .offset = offsetof(pos2_tex2, tex) } + }; + binding_descriptions = darray_pushback(binding_descriptions, &binding, 1); + attribute_descriptions = darray_pushback(attribute_descriptions, attributes, 2); + } + break; + case VERTEX_INPUT_TYPE_POS3_POS3_COLOR4: { const VkVertexInputBindingDescription binding = @@ -2009,12 +2442,17 @@ static u8 init_mesh_pipeline(mesh_pipeline* pipeline, const mesh_pipeline_templa .pNext = NULL }; - // layout; TO DO probably need something in the pipeline template to make this better + // layout + const VkDescriptorSetLayout descriptor_set_layouts[2] = + { + context.camera_descriptor_set_layout, + context.texture_descriptor_set_layout + }; const VkPipelineLayoutCreateInfo layout_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, - .setLayoutCount = 1, - .pSetLayouts = &context.camera_descriptor_set_layout, + .setLayoutCount = 2, + .pSetLayouts = descriptor_set_layouts, .pushConstantRangeCount = 0, .pPushConstantRanges = NULL, .flags = 0, @@ -2084,6 +2522,12 @@ static u8 init_mesh_pipeline(mesh_pipeline* pipeline, const mesh_pipeline_templa pipeline->instance_type = template->instance_type; init_hash_table(&pipeline->meshes, 8, sizeof(mesh), basic_hash); + // texture + if (template->texture_name != NULL) + strncpy(pipeline->texture_name, template->texture_name, HASH_TABLE_KEY_LEN); + else + pipeline->texture_name[0] = '\0'; + return 1; } @@ -2110,15 +2554,27 @@ u8 draw_mesh_pipeline(const char* name) mesh_pipeline* pipeline = hash_table_lookup(&context.mesh_pipelines, name); if (!pipeline) { - trace_log(LOG_ERROR, "Failed to draw mesh pipeline '%s'; lookup failed." , name); + trace_log(LOG_ERROR, "Failed to draw mesh pipeline '%s'; pipeline lookup failed." , name); return 0; } vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->handle); // bind camera descriptor set (set 0) vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->layout, 0, 1, context.camera_descriptor_sets + context.current_frame, 0, NULL); + + // bind texture descriptor set (set 1) + if (pipeline->texture_name[0] != '\0') + { + texture* t = hash_table_lookup(&context.textures, pipeline->texture_name); + if (!t) + { + trace_log(LOG_ERROR, "Failed to draw mesh pipeline '%s'; texture '%s' lookup failed.", name, pipeline->texture_name); + return 0; + } + vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->layout, 1, 1, t->descriptor_sets + context.current_frame, 0, NULL); + } - for (u32 i = 0; i < pipeline->meshes.slot_count; i++) + for (u32 i = 0; i < pipeline->meshes.slot_count; i++) for (hash_table_entry* entry = pipeline->meshes.entries[i]; entry != NULL; entry = entry->next) { mesh* msh = entry->data; @@ -2250,6 +2706,7 @@ u8 init_vulkan() if (!init_command_pool()) return 0; if (!init_frame_data()) return 0; if (!init_camera()) return 0; + if (!init_textures()) return 0; init_hash_table(&context.mesh_pipelines, 8, sizeof(mesh_pipeline), basic_hash); diff --git a/renderer/src/renderer.h b/renderer/src/renderer.h index bd91a8c..43d6bf7 100644 --- a/renderer/src/renderer.h +++ b/renderer/src/renderer.h @@ -3,6 +3,7 @@ // // vulkan-tutorial.com // vkguide.dev +// saschawillems.de #pragma once @@ -43,6 +44,7 @@ typedef struct window_event typedef enum { VERTEX_INPUT_TYPE_POS2, + VERTEX_INPUT_TYPE_POS2_TEX2, VERTEX_INPUT_TYPE_POS3_POS3_COLOR4 } vertex_input_type; @@ -53,6 +55,13 @@ typedef struct pos2 } pos2; +typedef struct pos2_tex2 +{ + f32 pos[2]; + f32 tex[2]; + +} pos2_tex2; + typedef struct pos3_pos3_color4 { f32 posa[3]; @@ -80,9 +89,12 @@ typedef struct mesh_instance_data typedef struct mesh_pipeline_template { const char* name; + vertex_input_type geometry_type; vertex_input_type instance_type; + const char* texture_name; + } mesh_pipeline_template; typedef enum @@ -109,6 +121,9 @@ void set_camera_view(const f32* view); void set_camera_perspective_projection(const f32* perspective); void set_camera_orthographic_projection(const f32* orthographic); +u8 insert_image_2d_texture_from_file(const char* name); +u8 remove_texture(const char* name); + u8 draw_mesh_pipeline(const char* name); u8 insert_mesh_pipeline(const mesh_pipeline_template* template); u8 remove_mesh_pipeline(const char* name); diff --git a/resources/textures/arrow.png b/resources/textures/arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..04d03b9816ebf49982029ec2f64504fa857b2268 GIT binary patch literal 129 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|jKx9jP7LeL$-D$|tUX;ELo_BP zmt5VDcz~hkXuG6@gxuF#NnVBzgL=;#SkCAxxJ*v%&!rhnJs}^M>kL*gGI?}o?y~4S aCB?vNA|!alF!U+V7zR&QKbLh*2~7Z=o+o_( literal 0 HcmV?d00001 diff --git a/src/main.lisp b/src/main.lisp index 8f60d5c..87eb9e3 100644 --- a/src/main.lisp +++ b/src/main.lisp @@ -41,6 +41,7 @@ You should have received a copy of the GNU General Public License along with Sha (cffi:defcenum vertex-input-type :vertex-input-type-pos2 + :vertex-input-type-pos2-tex2 :vertex-input-type-pos3-pos3-color4) (cffi:defcstruct (%pos2 :class c-pos2) @@ -73,6 +74,46 @@ You should have received a copy of the GNU General Public License along with Sha (cffi:mem-aref pos :float 0) (aref (pos2-pos ,value) 0) (cffi:mem-aref pos :float 1) (aref (pos2-pos ,value) 1)))) +(cffi:defcstruct (%pos2-tex2 :class c-pos2-tex2) + (pos :float :count 2) + (tex :float :count 2)) + +(defstruct pos2-tex2 + (pos (vector 0.0 0.0)) + (tex (vector 0.0 0.0))) + +(defmethod cffi:translate-from-foreign (ptr (type c-pos2-tex2)) + (cffi:with-foreign-slots ((pos tex) ptr (:struct %pos2-tex2)) + (make-pos2-tex2 + :pos (vector (cffi:mem-aref pos :float 0) + (cffi:mem-aref pos :float 1)) + :tex (vector (cffi:mem-aref tex :float 0) + (cffi:mem-aref tex :float 1))))) + +(defmethod cffi:expand-from-foreign (ptr (type c-pos2-tex2)) + `(cffi:with-foreign-slots ((pos tex) ,ptr (:struct %pos2-tex2)) + (make-pos2-tex2 + :pos (vector (cffi:mem-aref pos :float 0) + (cffi:mem-aref pos :float 1)) + :tex (vector (cffi:mem-aref tex :float 0) + (cffi:mem-aref tex :float 1))))) + +(defmethod cffi:translate-into-foreign-memory (value (type c-pos2-tex2) ptr) + (cffi:with-foreign-slots ((pos tex) ptr (:struct %pos2-tex2)) + (setf + (cffi:mem-aref pos :float 0) (aref (pos2-tex2-pos value) 0) + (cffi:mem-aref pos :float 1) (aref (pos2-tex2-pos value) 1) + (cffi:mem-aref tex :float 0) (aref (pos2-tex2-tex value) 0) + (cffi:mem-aref tex :float 1) (aref (pos2-tex2-tex value) 1)))) + +(defmethod cffi:expand-into-foreign-memory (value (type c-pos2-tex2) ptr) + `(cffi:with-foreign-slots ((pos tex) ,ptr (:struct %pos2-tex2)) + (setf + (cffi:mem-aref pos :float 0) (aref (pos2-tex2-pos ,value) 0) + (cffi:mem-aref pos :float 1) (aref (pos2-tex2-pos ,value) 1) + (cffi:mem-aref tex :float 0) (aref (pos2-tex2-tex ,value) 0) + (cffi:mem-aref tex :float 1) (aref (pos2-tex2-tex ,value) 1)))) + (cffi:defcstruct (%pos3-pos3-color4 :class c-pos3-pos3-color4) (posa :float :count 3) (posb :float :count 3) @@ -153,40 +194,46 @@ You should have received a copy of the GNU General Public License along with Sha (cffi:defcstruct (%mesh-pipeline-template :class c-mesh-pipeline-template) (name :string) (geometry-type vertex-input-type) - (instance-type vertex-input-type)) + (instance-type vertex-input-type) + (texture-name :string)) (defstruct mesh-pipeline-template (name "" :type string) (geometry-type 0 :type integer) - (instance-type 0 :type integer)) + (instance-type 0 :type integer) + (texture-name "" :type string)) (defmethod cffi:translate-from-foreign (ptr (type c-mesh-pipeline-template)) - (cffi:with-foreign-slots ((name geometry-type instance-type) ptr (:struct %mesh-pipeline-template)) + (cffi:with-foreign-slots ((name geometry-type instance-type texture-name) ptr (:struct %mesh-pipeline-template)) (make-mesh-pipeline-template :name name :geometry-type geometry-type - :instance-type instance-type))) + :instance-type instance-type + :texture-name texture-name))) (defmethod cffi:expand-from-foreign (ptr (type c-mesh-pipeline-template)) - `(cffi:with-foreign-slots ((name geometry-type instance-type) ,ptr (:struct %mesh-pipeline-template)) + `(cffi:with-foreign-slots ((name geometry-type instance-type texture-name) ,ptr (:struct %mesh-pipeline-template)) (make-mesh-pipeline-template :name name :geometry-type geometry-type - :instance-type instance-type))) + :instance-type instance-type + :texture-name texture-name))) (defmethod cffi:translate-into-foreign-memory (value (type c-mesh-pipeline-template) ptr) - (cffi:with-foreign-slots ((name geometry-type instance-type) ptr (:struct %mesh-pipeline-template)) + (cffi:with-foreign-slots ((name geometry-type instance-type texture-name) ptr (:struct %mesh-pipeline-template)) (setf name (mesh-pipeline-template-name value) geometry-type (mesh-pipeline-template-geometry-type value) - instance-type (mesh-pipeline-template-instance-type value)))) + instance-type (mesh-pipeline-template-instance-type value) + texture-name (mesh-pipeline-template-texture-name value)))) (defmethod cffi:expand-into-foreign-memory (value (type c-mesh-pipeline-template) ptr) `(cffi:with-foreign-slots ((name geometry-type instance-type) ,ptr (:struct %mesh-pipeline-template)) (setf name (mesh-pipeline-template-name ,value) geometry-type (mesh-pipeline-template-geometry-type ,value) - instance-type (mesh-pipeline-template-instance-type ,value)))) + instance-type (mesh-pipeline-template-instance-type ,value) + texture-name (mesh-pipeline-template-texture-name ,value)))) (cffi:defcenum mesh-pipeline-mode :mesh-pipeline-mode-main @@ -234,14 +281,20 @@ You should have received a copy of the GNU General Public License along with Sha (defun set-camera-orthographic-projection (orthographic) (set-camera-helper orthographic #'%set-camera-orthographic-projection)) +(cffi:defcfun ("insert_image_2d_texture_from_file" insert-image-2d-texture-from-file) :boolean + (name :string)) + +(cffi:defcfun ("remove_texture" remove-texture) :boolean + (name :string)) + (cffi:defcfun ("draw_mesh_pipeline" draw-mesh-pipeline) :boolean (name :string)) (cffi:defcfun ("insert_mesh_pipeline" %insert-mesh-pipeline) :boolean (template (:pointer (:struct %mesh-pipeline-template)))) -(defun insert-mesh-pipeline (name geometry-type instance-type) - (let ((template (make-mesh-pipeline-template :name name :geometry-type geometry-type :instance-type instance-type))) +(defun insert-mesh-pipeline (name geometry-type instance-type texture-name) + (let ((template (make-mesh-pipeline-template :name name :geometry-type geometry-type :instance-type instance-type :texture-name texture-name))) (cffi:with-foreign-object (ptr '(:struct %mesh-pipeline-template)) (setf (cffi:mem-aref ptr '(:struct %mesh-pipeline-template)) template) (%insert-mesh-pipeline ptr)))) @@ -301,8 +354,17 @@ You should have received a copy of the GNU General Public License along with Sha (insert-mesh-pipeline "ui-pane" (cffi:foreign-enum-value 'vertex-input-type :vertex-input-type-pos2) - (cffi:foreign-enum-value 'vertex-input-type :vertex-input-type-pos3-pos3-color4)) + (cffi:foreign-enum-value 'vertex-input-type :vertex-input-type-pos3-pos3-color4) + "") + (insert-image-2d-texture-from-file "arrow") + + (insert-mesh-pipeline + "ui-pane-tex" + (cffi:foreign-enum-value 'vertex-input-type :vertex-input-type-pos2-tex2) + (cffi:foreign-enum-value 'vertex-input-type :vertex-input-type-pos3-pos3-color4) + "arrow") + (cffi:with-foreign-object (quad-vertices '(:struct %pos2) 4) (setf (cffi:mem-aref quad-vertices '(:struct %pos2) 0) (make-pos2 :pos (vector 0.0 0.0)) @@ -335,7 +397,39 @@ You should have received a copy of the GNU General Public License along with Sha (setf instances quad-instances instance-count 2) - (insert-mesh "ui-pane" "quad" geometry instance-data))))))))) + (insert-mesh "ui-pane" "quad" geometry instance-data)))))))) + + (cffi:with-foreign-object (quad-vertices '(:struct %pos2-tex2) 4) + (setf + (cffi:mem-aref quad-vertices '(:struct %pos2-tex2) 0) (make-pos2-tex2 :pos (vector 0.0 0.0) :tex (vector (/ 0.5 16) (/ 15.5 16))) + (cffi:mem-aref quad-vertices '(:struct %pos2-tex2) 1) (make-pos2-tex2 :pos (vector 1.0 1.0) :tex (vector (/ 15.5 16) (/ 0.5 16))) + (cffi:mem-aref quad-vertices '(:struct %pos2-tex2) 2) (make-pos2-tex2 :pos (vector 1.0 0.0) :tex (vector (/ 15.5 16) (/ 15.5 16))) + (cffi:mem-aref quad-vertices '(:struct %pos2-tex2) 3) (make-pos2-tex2 :pos (vector 0.0 1.0) :tex (vector (/ 0.5 16) (/ 0.5 16)))) + (cffi:with-foreign-object (quad-indices :uint32 6) + (setf + (cffi:mem-aref quad-indices :uint32 0) 0 + (cffi:mem-aref quad-indices :uint32 1) 1 + (cffi:mem-aref quad-indices :uint32 2) 2 + (cffi:mem-aref quad-indices :uint32 3) 0 + (cffi:mem-aref quad-indices :uint32 4) 3 + (cffi:mem-aref quad-indices :uint32 5) 1) + (cffi:with-foreign-object (geometry '(:struct %mesh-geometry)) + (cffi:with-foreign-slots ((vertices vertex-count indices index-count) geometry (:struct %mesh-geometry)) + (setf + vertices quad-vertices + vertex-count 4 + indices quad-indices + index-count 6) + (cffi:with-foreign-object (quad-instances '(:struct %pos3-pos3-color4) 1) + (setf + (cffi:mem-aref quad-instances '(:struct %pos3-pos3-color4) 0) + (make-pos3-pos3-color4 :posa (vector 300.0 300.0 3.0) :posb (vector 300.0 300.0 1.0) :color (vector 0.7 0.0 0.0 1.0))) + (cffi:with-foreign-object (instance-data '(:struct %mesh-instance-data)) + (cffi:with-foreign-slots ((instances instance-count) instance-data (:struct %mesh-instance-data)) + (setf + instances quad-instances + instance-count 1) + (insert-mesh "ui-pane-tex" "quad" geometry instance-data))))))))) (defun shutdown-game () (shutdown-vulkan) @@ -364,7 +458,8 @@ You should have received a copy of the GNU General Public License along with Sha (declare (ignore l)) (when (begin-render-frame) (when (draw-mesh-pipeline "ui-pane") - (when (end-render-frame) t)))) + (when (draw-mesh-pipeline "ui-pane-tex") + (when (end-render-frame) t))))) (defun run-game ()