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 0000000..04d03b9 Binary files /dev/null and b/resources/textures/arrow.png differ 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 ()