From 114c8a0c5d67ae5a128d68ac1d4a8d30e95d7f6a Mon Sep 17 00:00:00 2001 From: jacekpoz Date: Sun, 6 Oct 2024 13:29:47 +0200 Subject: [PATCH] images (kinda) working again the image example shows :3 but it errors a lot and shit --- Makefile | 4 +- default.nix | 2 + examples/:3.png | Bin 0 -> 27247 bytes examples/image.c | 15 ++ include/ptk.h | 8 + shaders/shader.frag.glsl | 9 +- src/ptk.c | 5 + src/ptk_vk/backend.c | 16 ++ src/ptk_vk/buffer.c | 63 ++----- src/ptk_vk/buffer.h | 4 + src/ptk_vk/command_pool.c | 61 ++++++ src/ptk_vk/command_pool.h | 8 + src/ptk_vk/components.c | 36 ++++ src/ptk_vk/components.h | 1 + src/ptk_vk/descriptors.c | 81 ++++++-- src/ptk_vk/device.c | 5 +- src/ptk_vk/image.c | 385 ++++++++++++++++++++++++++++++++++++++ src/ptk_vk/image.h | 25 +++ src/ptk_vk/init.c | 7 + src/ptk_vk/pipeline.c | 24 ++- src/ptk_vk/swapchain.c | 40 ++-- 21 files changed, 694 insertions(+), 105 deletions(-) create mode 100644 examples/:3.png create mode 100644 examples/image.c create mode 100644 src/ptk_vk/image.c create mode 100644 src/ptk_vk/image.h diff --git a/Makefile b/Makefile index 4acde4e..3376a5f 100644 --- a/Makefile +++ b/Makefile @@ -4,8 +4,8 @@ CC = clang CFLAGS = -std=c23 -Wall -Wextra -Wpedantic CFLAGS += -Werror -Wfloat-equal -Wshadow CFLAGS += -fPIC -fstrict-aliasing -CFLAGS += $(shell pkg-config --cflags glfw3 vulkan) -LDFLAGS = $(shell pkg-config --libs glfw3 vulkan) +CFLAGS += $(shell pkg-config --cflags glfw3 stb vulkan) +LDFLAGS = $(shell pkg-config --libs glfw3 stb vulkan) ifdef DEBUG CFLAGS += -DDEBUG -g diff --git a/default.nix b/default.nix index b8b5caa..8bb0cdd 100644 --- a/default.nix +++ b/default.nix @@ -6,6 +6,7 @@ gnumake, pkg-config, shaderc, + stb, vulkan-headers, vulkan-loader, vulkan-validation-layers, @@ -20,6 +21,7 @@ in stdenv.mkDerivation { buildInputs = [ glfw + stb vulkan-headers vulkan-loader vulkan-validation-layers diff --git a/examples/:3.png b/examples/:3.png new file mode 100644 index 0000000000000000000000000000000000000000..df738b914ed0a09e68595d548978ce3375792687 GIT binary patch literal 27247 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 +#include + +int main(void) { + if (!ptk_init(800, 600, "image", (PtkVersion){ .major = 0, .minor = 1, .patch = 0 })) { + return EXIT_FAILURE; + } + + return ptk_run(/*PTK_BOX(*/ + // ptk_image("examples/papiezWasap.png", (PtkPos){ .x = 200.0f, .y = 200.0f }, (PtkSize){ .w = 128.0f, .h = 128.0f }), + ptk_image("examples/:3.png", (PtkPos){ .x = 200.0f, .y = 400.0f }, (PtkSize){ .w = 128.0f, .h = 128.0f }) + /*)*/); +} diff --git a/include/ptk.h b/include/ptk.h index 6daa640..b9c22df 100644 --- a/include/ptk.h +++ b/include/ptk.h @@ -25,6 +25,7 @@ typedef enum { PTK_COMPONENT_TYPE_RECT = 2, PTK_COMPONENT_TYPE_ELLIPSE = 3, PTK_COMPONENT_TYPE_CLICKABLE = 4, + PTK_COMPONENT_TYPE_IMAGE = 5, } PtkComponentType; const char *ptk_component_type_to_str(PtkComponentType type); @@ -66,6 +67,12 @@ PTK_COMPONENT_DEFINE(PtkClickable, MouseButtonCallback on_press; ); +PTK_COMPONENT_DEFINE(PtkImage, + const char *path; + PtkPos top_left; + PtkSize size; +); + PtkHandle ptk_box(const size_t child_count, PtkHandle *children); PtkHandle ptk_triangle(const PtkPos vertices[3], const PtkRGB color); PtkHandle ptk_rect(const PtkPos top_left, const PtkSize size, const PtkRGB color); @@ -73,6 +80,7 @@ PtkHandle ptk_square(const PtkPos top_left, const float size, const PtkRGB color PtkHandle ptk_ellipse(const PtkPos center, const PtkSize radii, const PtkRGB color); PtkHandle ptk_circle(const PtkPos center, const float radius, const PtkRGB color); PtkHandle ptk_clickable(PtkHandle hitbox, const MouseButtonCallback on_press); +PtkHandle ptk_image(const char *path, const PtkPos top_left, const PtkSize size); #define PTK_BOX(...) ptk_box(sizeof((PtkHandle []){ __VA_ARGS__ }) / sizeof(PtkHandle), (PtkHandle []) { __VA_ARGS__ }) diff --git a/shaders/shader.frag.glsl b/shaders/shader.frag.glsl index cf5fb02..3f2a2f1 100644 --- a/shaders/shader.frag.glsl +++ b/shaders/shader.frag.glsl @@ -3,6 +3,7 @@ #version 450 layout(constant_id = 0) const int PTK_COMPONENT_TYPE_ELLIPSE = 0; +layout(constant_id = 1) const int PTK_COMPONENT_TYPE_IMAGE = 0; layout(location = 0) in vec3 fragColor; layout(location = 1) flat in int shapeType; @@ -10,11 +11,17 @@ layout(location = 2) in vec2 uv; layout(location = 0) out vec4 outColor; +layout(binding = 1) uniform sampler2D textureSampler; + void main() { if (shapeType == PTK_COMPONENT_TYPE_ELLIPSE) { if (length(uv - vec2(0.5)) > 0.5) { discard; } + outColor = vec4(fragColor, 1.0); + } else if (shapeType == PTK_COMPONENT_TYPE_IMAGE) { + outColor = texture(textureSampler, uv); + } else { + outColor = vec4(fragColor, 1.0); } - outColor = vec4(fragColor, 1.0); } diff --git a/src/ptk.c b/src/ptk.c index 2eeb8a9..e1df636 100644 --- a/src/ptk.c +++ b/src/ptk.c @@ -118,6 +118,7 @@ const char *ptk_component_type_to_str(PtkComponentType type) { case PTK_COMPONENT_TYPE_RECT: res = "rect"; break; case PTK_COMPONENT_TYPE_ELLIPSE: res = "ellipse"; break; case PTK_COMPONENT_TYPE_CLICKABLE: res = "clickable"; break; + case PTK_COMPONENT_TYPE_IMAGE: res = "image"; break; default: res = "unknown"; break; } return res; @@ -159,6 +160,10 @@ PtkHandle ptk_clickable(PtkHandle hitbox, const MouseButtonCallback on_press) { return clickable_component(get_component_id(), hitbox, on_press); } +PtkHandle ptk_image(const char *path, const PtkPos top_left, const PtkSize size) { + return image_component(get_component_id(), path, top_left, size); +} + int ptk_run(PtkHandle root) { vk_init_components(root); diff --git a/src/ptk_vk/backend.c b/src/ptk_vk/backend.c index 7b9c3e8..f28429a 100644 --- a/src/ptk_vk/backend.c +++ b/src/ptk_vk/backend.c @@ -8,6 +8,7 @@ #include "ptk_vk/components.h" #include "ptk_vk/descriptors.h" #include "ptk_vk/device.h" +#include "ptk_vk/image.h" #include "ptk_vk/init.h" #include "ptk_vk/instance.h" #include "ptk_vk/physical_device.h" @@ -174,6 +175,21 @@ bool vk_init(GLFWwindow *window, const size_t width, const size_t height, const return false; } + if (!vk_create_texture_image("examples/:3.png")) { + PTK_ERR("failed creating texture image"); + return false; + } + + if (!vk_create_texture_image_view()) { + PTK_ERR("failed creating texture image view"); + return false; + } + + if (!vk_create_texture_sampler()) { + PTK_ERR("failed creating texture sampler"); + return false; + } + const VkDeviceSize buffer_size = 65536; PTK_OPTION(BufferStuff) vertex_buffer_stuff_opt = vk_create_buffer( diff --git a/src/ptk_vk/buffer.c b/src/ptk_vk/buffer.c index 4f97918..1eae338 100644 --- a/src/ptk_vk/buffer.c +++ b/src/ptk_vk/buffer.c @@ -3,15 +3,14 @@ #include "ptk_vk/buffer.h" #include "ptk_vk/command_pool.h" -#include "ptk_vk/device.h" #include "ptk_vk/physical_device.h" #include "ptk_vk/utils.h" #include -PTK_OPTION(uint32_t) find_memory_type(VkPhysicalDevice physical_dev, const uint32_t type_filter, const VkMemoryPropertyFlags props) { +PTK_OPTION(uint32_t) vk_find_memory_type(const uint32_t type_filter, const VkMemoryPropertyFlags props) { VkPhysicalDeviceMemoryProperties mem_props; - vkGetPhysicalDeviceMemoryProperties(physical_dev, &mem_props); + vkGetPhysicalDeviceMemoryProperties(g_physical_dev, &mem_props); for (uint32_t i = 0; i < mem_props.memoryTypeCount; ++i) { if (type_filter & (1 << i) && (mem_props.memoryTypes[i].propertyFlags & props) == props) { @@ -46,7 +45,7 @@ PTK_OPTION(BufferStuff) vk_create_buffer(const VkDeviceSize size, const VkBuffer VkMemoryRequirements mem_reqs; vkGetBufferMemoryRequirements(g_dev, ret.buffer, &mem_reqs); - const PTK_OPTION(uint32_t) memory_type = find_memory_type(g_physical_dev, mem_reqs.memoryTypeBits, props); + const PTK_OPTION(uint32_t) memory_type = vk_find_memory_type(mem_reqs.memoryTypeBits, props); if (!memory_type.exists) { PTK_ERR("failed to find suitable memory type"); @@ -73,30 +72,14 @@ PTK_OPTION(BufferStuff) vk_create_buffer(const VkDeviceSize size, const VkBuffer } bool copy_buffer(const VkBuffer src, const VkBuffer dst, const VkDeviceSize size) { - VkCommandBuffer command_buffer; + PTK_OPTION(VkCommandBuffer) command_buffer_opt = vk_begin_single_time_commands(); - VK_TRY(false, - vkAllocateCommandBuffers( - g_dev, - &(VkCommandBufferAllocateInfo){ - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, - .pNext = NULL, - .commandPool = g_command_pool, - .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, - .commandBufferCount = 1, - }, - &command_buffer - ) - ); + if (!command_buffer_opt.exists) { + PTK_ERR("failed to create command buffer"); + return false; + } - VK_TRY(false, - vkBeginCommandBuffer(command_buffer, &(VkCommandBufferBeginInfo){ - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, - .pNext = NULL, - .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, - .pInheritanceInfo = NULL, - }) - ); + VkCommandBuffer command_buffer = command_buffer_opt.value; vkCmdCopyBuffer(command_buffer, src, dst, 1, &(VkBufferCopy){ .srcOffset = 0, @@ -104,30 +87,10 @@ bool copy_buffer(const VkBuffer src, const VkBuffer dst, const VkDeviceSize size .size = size, }); - VK_TRY(false, - vkEndCommandBuffer(command_buffer) - ); - - vkQueueSubmit( - g_graphics_queue, - 1, - &(VkSubmitInfo){ - .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, - .pNext = NULL, - .waitSemaphoreCount = 0, - .pWaitSemaphores = NULL, - .pWaitDstStageMask = NULL, - .commandBufferCount = 1, - .pCommandBuffers = &command_buffer, - .signalSemaphoreCount = 0, - .pSignalSemaphores = NULL, - }, - VK_NULL_HANDLE - ); - - vkQueueWaitIdle(g_graphics_queue); - - vkFreeCommandBuffers(g_dev, g_command_pool, 1, &command_buffer); + if (!vk_end_single_time_commands(command_buffer)) { + PTK_ERR("failed to end command buffer"); + return false; + } return true; } diff --git a/src/ptk_vk/buffer.h b/src/ptk_vk/buffer.h index bc1960b..90fdc24 100644 --- a/src/ptk_vk/buffer.h +++ b/src/ptk_vk/buffer.h @@ -3,10 +3,14 @@ #ifndef PTK_PTK_VK_BUFFER_H_ #define PTK_PTK_VK_BUFFER_H_ +#include "ptk_vk/device.h" + #include "ptk_option.h" #include +PTK_OPTION(uint32_t) vk_find_memory_type(const uint32_t type_filter, const VkMemoryPropertyFlags props); + typedef struct { VkBuffer buffer; VkDeviceMemory buffer_memory; diff --git a/src/ptk_vk/command_pool.c b/src/ptk_vk/command_pool.c index 71e2ea1..3503718 100644 --- a/src/ptk_vk/command_pool.c +++ b/src/ptk_vk/command_pool.c @@ -5,6 +5,67 @@ #include "ptk_vk/device.h" #include "ptk_vk/utils.h" +PTK_OPTION(VkCommandBuffer) vk_begin_single_time_commands(void) { + VkCommandBuffer command_buffer; + + VK_TRY(PTK_OPTION_NONE(VkCommandBuffer), + vkAllocateCommandBuffers( + g_dev, + &(VkCommandBufferAllocateInfo){ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .pNext = NULL, + .commandPool = g_command_pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1, + }, + &command_buffer + ) + ); + + VK_TRY(PTK_OPTION_NONE(VkCommandBuffer), + vkBeginCommandBuffer( + command_buffer, + &(VkCommandBufferBeginInfo){ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .pNext = NULL, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + .pInheritanceInfo = NULL, + } + ) + ); + + return PTK_OPTION_SOME(VkCommandBuffer, command_buffer); +} + +bool vk_end_single_time_commands(VkCommandBuffer command_buffer) { + VK_TRY(false, vkEndCommandBuffer(command_buffer)); + + VK_TRY(false, + vkQueueSubmit( + g_graphics_queue, + 1, + &(VkSubmitInfo){ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .pNext = NULL, + .waitSemaphoreCount = 0, + .pWaitSemaphores = NULL, + .pWaitDstStageMask = NULL, + .commandBufferCount = 1, + .pCommandBuffers = &command_buffer, + .signalSemaphoreCount = 0, + .pSignalSemaphores = NULL, + }, + VK_NULL_HANDLE + ); + ); + + VK_TRY(false, vkQueueWaitIdle(g_graphics_queue)); + + vkFreeCommandBuffers(g_dev, g_command_pool, 1, &command_buffer); + + return true; +} + VkCommandPool g_command_pool; bool vk_create_command_pool(void) { diff --git a/src/ptk_vk/command_pool.h b/src/ptk_vk/command_pool.h index 330fe71..95c58fb 100644 --- a/src/ptk_vk/command_pool.h +++ b/src/ptk_vk/command_pool.h @@ -3,8 +3,16 @@ #ifndef PTK_PTK_VK_COMMAND_POOL_H_ #define PTK_PTK_VK_COMMAND_POOL_H_ +#include "ptk_option.h" + #include +PTK_OPTION_DEFINE(VkCommandBuffer); + +PTK_OPTION(VkCommandBuffer) vk_begin_single_time_commands(void); + +bool vk_end_single_time_commands(VkCommandBuffer command_buffer); + extern VkCommandPool g_command_pool; bool vk_create_command_pool(void); diff --git a/src/ptk_vk/components.c b/src/ptk_vk/components.c index df7c22d..3b35dcb 100644 --- a/src/ptk_vk/components.c +++ b/src/ptk_vk/components.c @@ -1,6 +1,7 @@ // Copyright (jacekpoz 2024). Licensed under the EUPL-1.2 or later. #include "ptk_vk/components.h" +#include "ptk_vk/image.h" #include "ptk_vk/init.h" #include "ptk.h" @@ -113,6 +114,19 @@ PtkHandle clickable_component(const uint64_t id, PtkHandle hitbox, const MouseBu return (PtkHandle)ret; } +PtkHandle image_component(const uint64_t id, const char *path, const PtkPos top_left, const PtkSize size) { + PtkImage *ret = malloc(sizeof(PtkImage)); + *ret = (PtkImage){ + .id = id, + .type = PTK_COMPONENT_TYPE_IMAGE, + .path = path, + .top_left = top_left, + .size = size, + }; + + return (PtkHandle)ret; +} + void triangle(const PtkTriangle *triangle) { PTK_LIST_SET(Vertices, m_vertices_cache, triangle->id, PTK_LIST_NEW(Vertex, 3)); PTK_LIST_SET(Indices, m_indices_cache, triangle->id, PTK_LIST_NEW(uint32_t, 3)); @@ -224,7 +238,20 @@ void ellipse(const PtkEllipse *ellipse) { rect(r); } +void image(const PtkImage *image) { + vk_create_texture_image(image->path); + + PtkRect *r = (PtkRect *)rect_component(image->id, image->top_left, image->size, (PtkRGB){ .r = 0.0f, .g = 0.0f, .b = 0.0f }); + r->type = image->type; + + rect(r); +} + void component(PtkHandle c) { + if (c == PTK_NULL_HANDLE) { + return; + } + if (!m_update.data[c->id]) { Vertices vcache = m_vertices_cache.data[c->id]; Indices icache = m_indices_cache.data[c->id]; @@ -245,6 +272,9 @@ void component(PtkHandle c) { case PTK_COMPONENT_TYPE_ELLIPSE: { ellipse((PtkEllipse *)c); } break; + case PTK_COMPONENT_TYPE_IMAGE: { + image((PtkImage *)c); + } break; default: break; } } @@ -255,6 +285,10 @@ void component(PtkHandle c) { } void init_component(PtkHandle c) { + if (c == PTK_NULL_HANDLE) { + return; + } + PTK_LIST_SET(bool, m_update, c->id, true); PTK_LIST_FOR_EACH(PtkHandle, c->children, child, { init_component(child); @@ -323,6 +357,8 @@ bool intersects(const PtkHandle component, const PtkPos point) { return ellipse_intersects(*(PtkEllipse *)component, point); case PTK_COMPONENT_TYPE_CLICKABLE: return intersects(component->children.data[0], point); + case PTK_COMPONENT_TYPE_IMAGE: + return rect_intersects(*(PtkRect *)rect_component(component->id, ((PtkImage *)component)->top_left, ((PtkImage *)component)->size, (PtkRGB){ .r = 0.0f, .g = 0.0f, .b = 0.0f }), point); } } diff --git a/src/ptk_vk/components.h b/src/ptk_vk/components.h index 81d8e8a..4201f4c 100644 --- a/src/ptk_vk/components.h +++ b/src/ptk_vk/components.h @@ -28,6 +28,7 @@ PtkHandle square_component(const uint64_t id, const PtkPos top_left, const float PtkHandle ellipse_component(const uint64_t id, const PtkPos center, const PtkSize radii, const PtkRGB color); PtkHandle circle_component(const uint64_t id, const PtkPos center, const float radius, const PtkRGB color); PtkHandle clickable_component(const uint64_t id, PtkHandle hitbox, const MouseButtonCallback on_press); +PtkHandle image_component(const uint64_t id, const char *path, const PtkPos top_left, const PtkSize size); void vk_init_components(PtkHandle root); diff --git a/src/ptk_vk/descriptors.c b/src/ptk_vk/descriptors.c index f22a63d..d3973ba 100644 --- a/src/ptk_vk/descriptors.c +++ b/src/ptk_vk/descriptors.c @@ -3,15 +3,37 @@ #include "ptk_vk/descriptors.h" #include "ptk_vk/device.h" +#include "ptk_vk/image.h" #include "ptk_vk/sync_objects.h" #include "ptk_vk/uniform_buffers.h" #include "ptk_vk/utils.h" +#include "ptk_array.h" + VkDescriptorSetLayout g_descriptor_set_layout; VkDescriptorPool g_descriptor_pool; PTK_LIST(VkDescriptorSet) g_descriptor_sets; +PTK_ARRAY_DEFINE(VkDescriptorSetLayoutBinding); + bool vk_create_descriptor_set_layout(void) { + PTK_ARRAY(VkDescriptorSetLayoutBinding) bindings = PTK_ARRAY_NEW(VkDescriptorSetLayoutBinding, { + (VkDescriptorSetLayoutBinding){ + .binding = 0, + .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, + .pImmutableSamplers = NULL, + }, + (VkDescriptorSetLayoutBinding){ + .binding = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, + .pImmutableSamplers = NULL, + }, + }); + VK_TRY(false, vkCreateDescriptorSetLayout( g_dev, @@ -19,14 +41,8 @@ bool vk_create_descriptor_set_layout(void) { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .pNext = NULL, .flags = 0, - .bindingCount = 1, - .pBindings = &(VkDescriptorSetLayoutBinding){ - .binding = 0, - .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, - .pImmutableSamplers = NULL, - }, + .bindingCount = bindings.size, + .pBindings = bindings.data, }, NULL, &g_descriptor_set_layout @@ -36,7 +52,20 @@ bool vk_create_descriptor_set_layout(void) { return true; } +PTK_ARRAY_DEFINE(VkDescriptorPoolSize); + bool vk_create_descriptor_pool(void) { + PTK_ARRAY(VkDescriptorPoolSize) pool_sizes = PTK_ARRAY_NEW(VkDescriptorPoolSize, { + (VkDescriptorPoolSize){ + .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .descriptorCount = g_max_frames_in_flight, + }, + (VkDescriptorPoolSize){ + .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = g_max_frames_in_flight, + }, + }); + VK_TRY(false, vkCreateDescriptorPool( g_dev, @@ -45,11 +74,8 @@ bool vk_create_descriptor_pool(void) { .pNext = NULL, .flags = 0, .maxSets = g_max_frames_in_flight, - .poolSizeCount = 1, - .pPoolSizes = &(VkDescriptorPoolSize){ - .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - .descriptorCount = g_max_frames_in_flight, - }, + .poolSizeCount = pool_sizes.size, + .pPoolSizes = pool_sizes.data, }, NULL, &g_descriptor_pool @@ -60,6 +86,7 @@ bool vk_create_descriptor_pool(void) { } PTK_LIST_DEFINE(VkDescriptorSetLayout); +PTK_ARRAY_DEFINE(VkWriteDescriptorSet); bool vk_create_descriptor_sets(void) { PTK_LIST(VkDescriptorSetLayout) layouts = PTK_LIST_NEW(VkDescriptorSetLayout, g_max_frames_in_flight); @@ -84,10 +111,8 @@ bool vk_create_descriptor_sets(void) { ); for (size_t i = 0; i < g_max_frames_in_flight; ++i) { - vkUpdateDescriptorSets( - g_dev, - 1, - &(VkWriteDescriptorSet){ + PTK_ARRAY(VkWriteDescriptorSet) descriptor_writes = PTK_ARRAY_NEW(VkWriteDescriptorSet, { + (VkWriteDescriptorSet){ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .pNext = NULL, .dstSet = g_descriptor_sets.data[i], @@ -103,6 +128,28 @@ bool vk_create_descriptor_sets(void) { }, .pTexelBufferView = NULL, }, + (VkWriteDescriptorSet){ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .pNext = NULL, + .dstSet = g_descriptor_sets.data[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = &(VkDescriptorImageInfo){ + .sampler = g_texture_sampler, + .imageView = g_texture_image_view, + .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + }, + .pBufferInfo = NULL, + .pTexelBufferView = NULL, + }, + }); + + vkUpdateDescriptorSets( + g_dev, + descriptor_writes.size, + descriptor_writes.data, 0, NULL ); diff --git a/src/ptk_vk/device.c b/src/ptk_vk/device.c index 99a4acb..99efd47 100644 --- a/src/ptk_vk/device.c +++ b/src/ptk_vk/device.c @@ -88,7 +88,10 @@ static bool is_device_suitable(const VkPhysicalDevice physical_dev, const VkSurf PTK_LIST_FREE(queue_families); - return indices_found && extensions_supported && swapchain_adequate; + VkPhysicalDeviceFeatures supported_features; + vkGetPhysicalDeviceFeatures(physical_dev, &supported_features); + + return indices_found && extensions_supported && swapchain_adequate && supported_features.samplerAnisotropy; } bool vk_create_logical_dev(void) { diff --git a/src/ptk_vk/image.c b/src/ptk_vk/image.c new file mode 100644 index 0000000..d9693c9 --- /dev/null +++ b/src/ptk_vk/image.c @@ -0,0 +1,385 @@ +#include "ptk_vk/image.h" + +#include "ptk_vk/buffer.h" +#include "ptk_vk/command_pool.h" +#include "ptk_vk/device.h" +#include "ptk_vk/physical_device.h" +#include "ptk_vk/utils.h" + +#include "ptk_log.h" +#include "ptk_option.h" + +#include "vulkan/vulkan_core.h" + +#define STB_IMAGE_IMPLEMENTATION +#include + +VkImage g_texture_image; +VkDeviceMemory g_texture_image_memory; + +VkImageView g_texture_image_view; + +VkSampler g_texture_sampler; + +bool create_image(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags props, VkImage *image, VkDeviceMemory *image_memory) { + VK_TRY(false, + vkCreateImage( + g_dev, + &(VkImageCreateInfo){ + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .imageType = VK_IMAGE_TYPE_2D, + .format = format, + .extent = (VkExtent3D){ + .width = width, + .height = height, + .depth = 1, + }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = tiling, + .usage = usage, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = NULL, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + }, + NULL, + image + ) + ); + + VkMemoryRequirements mem_reqs; + vkGetImageMemoryRequirements(g_dev, *image, &mem_reqs); + + PTK_OPTION(uint32_t) mem_type = vk_find_memory_type(mem_reqs.memoryTypeBits, props); + + if (!mem_type.exists) { + PTK_ERR("failed to find suitable memory type"); + return false; + } + + VK_TRY(false, + vkAllocateMemory( + g_dev, + &(VkMemoryAllocateInfo){ + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = NULL, + .allocationSize = mem_reqs.size, + .memoryTypeIndex = mem_type.value, + }, + NULL, + image_memory + ) + ); + + VK_TRY(false, + vkBindImageMemory( + g_dev, + *image, + *image_memory, + 0 + ) + ); + + return true; +} + +bool transition_image_layout(VkImage image, VkFormat format, VkImageLayout old_layout, VkImageLayout new_layout) { + (void)format; + PTK_OPTION(VkCommandBuffer) command_buffer_opt = vk_begin_single_time_commands(); + + if (!command_buffer_opt.exists) { + PTK_ERR("failed to create command buffer"); + return false; + } + + VkCommandBuffer command_buffer = command_buffer_opt.value; + + VkAccessFlags source_access_mask; + VkAccessFlags destination_access_mask; + VkPipelineStageFlags source_stage; + VkPipelineStageFlags destination_stage; + + if ( + old_layout == VK_IMAGE_LAYOUT_UNDEFINED + && new_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL + ) { + source_access_mask = 0; + destination_access_mask = VK_ACCESS_TRANSFER_WRITE_BIT; + + source_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destination_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 + ) { + source_access_mask = VK_ACCESS_TRANSFER_WRITE_BIT; + destination_access_mask = VK_ACCESS_SHADER_READ_BIT; + + source_stage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destination_stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } else { + PTK_ERR("unsupported layer transition"); + return false; + } + + vkCmdPipelineBarrier( + command_buffer, + source_stage, + destination_stage, + 0, + 0, + NULL, + 0, + NULL, + 1, + &(VkImageMemoryBarrier){ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = NULL, + .srcAccessMask = source_access_mask, + .dstAccessMask = destination_access_mask, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange = (VkImageSubresourceRange){ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + } + ); + + if (!vk_end_single_time_commands(command_buffer)) { + PTK_ERR("failed to end command buffer"); + return false; + } + + return true; +} + +bool copy_buffer_to_image(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { + PTK_OPTION(VkCommandBuffer) command_buffer_opt = vk_begin_single_time_commands(); + + if (!command_buffer_opt.exists) { + PTK_ERR("failed to create command buffer"); + return false; + } + + VkCommandBuffer command_buffer = command_buffer_opt.value; + + vkCmdCopyBufferToImage( + command_buffer, + buffer, + image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + &(VkBufferImageCopy){ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = (VkImageSubresourceLayers){ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1, + }, + .imageOffset = (VkOffset3D){ + .x = 0, + .y = 0, + .z = 0, + }, + .imageExtent = (VkExtent3D){ + .width = width, + .height = height, + .depth = 1, + }, + } + ); + + if (!vk_end_single_time_commands(command_buffer)) { + PTK_ERR("failed to end command buffer"); + return false; + } + + return true; +} + +bool vk_create_texture_image(const char *path) { + int texture_width; + int texture_height; + int texture_channels; + + stbi_uc *pixels = stbi_load(path, &texture_width, &texture_height, &texture_channels, STBI_rgb_alpha); + VkDeviceSize image_size = texture_width * texture_height * 4; + + if (pixels == NULL) { + PTK_ERR("failed to load image at %s", path); + return false; + } + + PTK_OPTION(BufferStuff) staging_buffer_stuff_opt = vk_create_buffer( + image_size, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT + ); + + if (!staging_buffer_stuff_opt.exists) { + PTK_ERR("failed creating staging image buffer"); + return false; + } + + VkBuffer staging_buffer = staging_buffer_stuff_opt.value.buffer; + VkDeviceMemory staging_buffer_memory = staging_buffer_stuff_opt.value.buffer_memory; + + void *data; + vkMapMemory(g_dev, staging_buffer_memory, 0, image_size, 0, &data); + memcpy(data, pixels, image_size); + vkUnmapMemory(g_dev, staging_buffer_memory); + + stbi_image_free(pixels); + + if ( + !create_image( + texture_width, + texture_height, + VK_FORMAT_R8G8B8A8_SRGB, + VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + &g_texture_image, + &g_texture_image_memory + ) + ) { + PTK_ERR("failed creating image"); + return false; + } + + if ( + !transition_image_layout( + g_texture_image, + VK_FORMAT_R8G8B8A8_SRGB, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL + ) + ) { + PTK_ERR("failed transitioning image layout to optimal"); + return false; + } + + if (!copy_buffer_to_image(staging_buffer, g_texture_image, texture_width, texture_height)) { + PTK_ERR("failed copying buffer to image"); + return false; + } + + if ( + !transition_image_layout( + g_texture_image, + VK_FORMAT_R8G8B8A8_SRGB, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL + ) + ) { + PTK_ERR("failed transitioning image layout to shader optimal"); + return false; + } + + vkDestroyBuffer(g_dev, staging_buffer, NULL); + vkFreeMemory(g_dev, staging_buffer_memory, NULL); + + if (!vk_create_texture_image_view()) { + PTK_ERR("failed creating texture image view"); + return false; + } + + return true; +} + +PTK_OPTION(VkImageView) vk_create_image_view(VkImage image, VkFormat format) { + VkImageView ret; + + VK_TRY(PTK_OPTION_NONE(VkImageView), + vkCreateImageView( + g_dev, + &(VkImageViewCreateInfo){ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .image = image, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = format, + .components = (VkComponentMapping){ + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = (VkImageSubresourceRange){ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }, + NULL, + &ret + ) + ); + + return PTK_OPTION_SOME(VkImageView, ret); +} + +bool vk_create_texture_image_view(void) { + PTK_OPTION(VkImageView) image_view_opt = vk_create_image_view(g_texture_image, VK_FORMAT_R8G8B8A8_SRGB); + + if (!image_view_opt.exists) { + PTK_ERR("failed to create texture image view"); + return false; + } + + g_texture_image_view = image_view_opt.value; + + return true; +} + +bool vk_create_texture_sampler(void) { + VkPhysicalDeviceProperties props; + vkGetPhysicalDeviceProperties(g_physical_dev, &props); + + VK_TRY(false, + vkCreateSampler( + g_dev, + &(VkSamplerCreateInfo){ + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .pNext = NULL, + .flags = 0, + .magFilter = VK_FILTER_LINEAR, + .minFilter = VK_FILTER_LINEAR, + .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, + .addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT, + .mipLodBias = 0.0f, + .anisotropyEnable = VK_FALSE, + .maxAnisotropy = props.limits.maxSamplerAnisotropy, + .compareEnable = VK_FALSE, + .compareOp = VK_COMPARE_OP_ALWAYS, + .minLod = 0.0f, + .maxLod = 0.0f, + .borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK, + .unnormalizedCoordinates = VK_FALSE, + }, + NULL, + &g_texture_sampler + ) + ); + + return true; +} diff --git a/src/ptk_vk/image.h b/src/ptk_vk/image.h new file mode 100644 index 0000000..8314e12 --- /dev/null +++ b/src/ptk_vk/image.h @@ -0,0 +1,25 @@ +#ifndef PTK_PTK_VK_IMAGE_H_ +#define PTK_PTK_VK_IMAGE_H_ + +#include "ptk_option.h" + +#include "vulkan/vulkan_core.h" + +extern VkImage g_texture_image; +extern VkDeviceMemory g_texture_image_memory; + +extern VkImageView g_texture_image_view; + +extern VkSampler g_texture_sampler; + +bool vk_create_texture_image(const char *path); + +PTK_OPTION_DEFINE(VkImageView); + +PTK_OPTION(VkImageView) vk_create_image_view(VkImage image, VkFormat format); + +bool vk_create_texture_image_view(void); + +bool vk_create_texture_sampler(void); + +#endif // PTK_PTK_VK_IMAGE_H_ diff --git a/src/ptk_vk/init.c b/src/ptk_vk/init.c index d0e8d9b..4085af1 100644 --- a/src/ptk_vk/init.c +++ b/src/ptk_vk/init.c @@ -8,6 +8,7 @@ #include "ptk_vk/components.h" #include "ptk_vk/descriptors.h" #include "ptk_vk/device.h" +#include "ptk_vk/image.h" #include "ptk_vk/instance.h" #include "ptk_vk/pipeline.h" #include "ptk_vk/render_pass.h" @@ -55,6 +56,12 @@ bool vk_transfer_vertex_data(void) { void vk_cleanup(void) { vk_cleanup_swapchain(); + vkDestroySampler(g_dev, g_texture_sampler, NULL); + vkDestroyImageView(g_dev, g_texture_image_view, NULL); + + vkDestroyImage(g_dev, g_texture_image, NULL); + vkFreeMemory(g_dev, g_texture_image_memory, NULL); + vkDestroyPipeline(g_dev, g_pipeline, NULL); vkDestroyPipelineLayout(g_dev, g_pipeline_layout, NULL); diff --git a/src/ptk_vk/pipeline.c b/src/ptk_vk/pipeline.c index 6cb702e..64df189 100644 --- a/src/ptk_vk/pipeline.c +++ b/src/ptk_vk/pipeline.c @@ -115,6 +115,8 @@ PTK_OPTION(VkShaderModule) create_shader_module(VkDevice dev, const PTK_STRING c return PTK_OPTION_SOME(VkShaderModule, shader_module); } +PTK_ARRAY_DEFINE(VkSpecializationMapEntry); + bool vk_create_pipeline(void) { const PTK_STRING vert_shader_code = read_spv("target/shaders/shader.vert.spv"); const PTK_STRING frag_shader_code = read_spv("target/shaders/shader.frag.spv"); @@ -133,15 +135,27 @@ bool vk_create_pipeline(void) { return false; } - const VkSpecializationInfo spec_info = { - .mapEntryCount = 1, - .pMapEntries = &(VkSpecializationMapEntry){ + PTK_ARRAY(VkSpecializationMapEntry) map_entries = PTK_ARRAY_NEW(VkSpecializationMapEntry, { + (VkSpecializationMapEntry){ .constantID = 0, .offset = 0, .size = sizeof(int), }, - .dataSize = sizeof(int), - .pData = &(int){PTK_COMPONENT_TYPE_ELLIPSE}, + (VkSpecializationMapEntry){ + .constantID = 1, + .offset = sizeof(int), + .size = sizeof(int), + }, + }); + + const VkSpecializationInfo spec_info = { + .mapEntryCount = map_entries.size, + .pMapEntries = map_entries.data, + .dataSize = sizeof(int) * map_entries.size, + .pData = (int []){ + PTK_COMPONENT_TYPE_ELLIPSE, + PTK_COMPONENT_TYPE_IMAGE, + }, }; const VkPipelineShaderStageCreateInfo shader_stages[] = { diff --git a/src/ptk_vk/swapchain.c b/src/ptk_vk/swapchain.c index 0208f1f..3b31c5a 100644 --- a/src/ptk_vk/swapchain.c +++ b/src/ptk_vk/swapchain.c @@ -4,6 +4,7 @@ #include "ptk_vk/backend.h" #include "ptk_vk/device.h" +#include "ptk_vk/image.h" #include "ptk_vk/physical_device.h" #include "ptk_vk/render_pass.h" #include "ptk_vk/utils.h" @@ -183,35 +184,16 @@ bool vk_create_image_views(void) { m_swapchain_image_views = PTK_LIST_NEW(VkImageView, m_swapchain_images.size); PTK_LIST_FOR_EACH_E(VkImage, m_swapchain_images, i, swapchain_image, { - VK_TRY(false, - vkCreateImageView( - g_dev, - &(VkImageViewCreateInfo){ - .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, - .pNext = NULL, - .flags = 0, - .image = swapchain_image, - .viewType = VK_IMAGE_VIEW_TYPE_2D, - .format = g_swapchain_image_format, - .components = (VkComponentMapping){ - .r = VK_COMPONENT_SWIZZLE_IDENTITY, - .g = VK_COMPONENT_SWIZZLE_IDENTITY, - .b = VK_COMPONENT_SWIZZLE_IDENTITY, - .a = VK_COMPONENT_SWIZZLE_IDENTITY, - }, - .subresourceRange = (VkImageSubresourceRange){ - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }, - }, - NULL, - &m_swapchain_image_views.data[i] - ) - ); - m_swapchain_image_views.size += 1; + PTK_OPTION(VkImageView) image_view_opt = vk_create_image_view(swapchain_image, g_swapchain_image_format); + + if (!image_view_opt.exists) { + PTK_ERR("failed to create swapchain image view #%d", i); + return false; + } + + VkImageView image_view = image_view_opt.value; + + PTK_LIST_ADD(VkImageView, m_swapchain_image_views, image_view); }) return true;