diff --git a/.DS_Store b/.DS_Store index 5f12cd8..37a822e 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/assets/boy.jpg b/assets/boy.jpg deleted file mode 100644 index c7741df..0000000 Binary files a/assets/boy.jpg and /dev/null differ diff --git a/assets/boy.png b/assets/boy.png new file mode 100644 index 0000000..b66ee6d Binary files /dev/null and b/assets/boy.png differ diff --git a/main.cpp b/main.cpp index af79f6e..c9f7f84 100644 --- a/main.cpp +++ b/main.cpp @@ -51,7 +51,7 @@ int main() { Renderer renderer(window); - texture_manager.load("assets/boy.jpg", renderer); + texture_manager.load("assets/boy.png", renderer); uint64_t current_time = glfwGetTimerValue(); @@ -72,8 +72,8 @@ int main() { t += dt; updates++; } - std::println("Updates: {}", updates); - std::println("frame time: {}", ((double) (frame_time) / (double) glfwGetTimerFrequency())); + // std::println("Updates: {}", updates); + // std::println("frame time: {}", ((double) (frame_time) / (double) glfwGetTimerFrequency())); renderer.begin_frame(); diff --git a/renderer/renderer.cpp b/renderer/renderer.cpp index 1b7ea49..f3642fd 100644 --- a/renderer/renderer.cpp +++ b/renderer/renderer.cpp @@ -102,39 +102,33 @@ void Renderer::submit_sprite(glm::vec2 pos, const sprite_t &sprite) { } void Renderer::create_pipeline_layout() { - VkDescriptorSetLayoutBinding bindings[2]; - - bindings[0] = VkDescriptorSetLayoutBinding{ - .binding = 0, - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT + std::array bindings = { + { + { + .binding = 0, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = nextTextureSlot, + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT + } + } }; - bindings[1] = { - .binding = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_VERTEX_BIT - }; - - VkDescriptorBindingFlags flags[2] = { - VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT | VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT, - 0 // Standard behavior for the buffer + VkDescriptorBindingFlags flags[1] = { + VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT | VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT }; VkDescriptorSetLayoutBindingFlagsCreateInfo layoutFlags{ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO, - .bindingCount = 2, + .bindingCount = 1, .pBindingFlags = flags }; VkDescriptorSetLayoutCreateInfo dslci{ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .pNext = &layoutFlags, - .flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT, - .bindingCount = 2, - .pBindings = bindings + // .flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT, + .bindingCount = bindings.size(), + .pBindings = bindings.data() }; vkCreateDescriptorSetLayout(device, &dslci, nullptr, &descriptor_set_layout); @@ -316,15 +310,14 @@ VkPipeline Renderer::get_pipeline(PipelineType type) const { void Renderer::create_descriptor_pool() { VkDescriptorPoolSize pool_sizes[] = { - { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 10 }, - { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1 }, + { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nextTextureSlot }, }; VkDescriptorPoolCreateInfo pool_info{ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT, .maxSets = 1, - .poolSizeCount = 2, + .poolSizeCount = 1, .pPoolSizes = pool_sizes }; @@ -340,7 +333,7 @@ void Renderer::create_descriptor_pool() { vkAllocateDescriptorSets(device, &alloc_info, &set); } -void Renderer::update_bindless_slot(uint32_t slot, VkImageView view, VkSampler sampler) { +void Renderer::update_bindless_slot(uint32_t slot, VkImageView view, VkSampler sampler) const { VkDescriptorImageInfo image_info{ .sampler = sampler, .imageView = view, @@ -350,7 +343,7 @@ void Renderer::update_bindless_slot(uint32_t slot, VkImageView view, VkSampler s VkWriteDescriptorSet write{ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = set, - .dstArrayElement = slot, // Index in the 1000-size array + .dstArrayElement = slot, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .pImageInfo = &image_info @@ -359,9 +352,16 @@ void Renderer::update_bindless_slot(uint32_t slot, VkImageView view, VkSampler s vkUpdateDescriptorSets(device, 1, &write, 0, nullptr); } -Texture Renderer::upload_texture(int w, int h, void* pixels) { +void Renderer::upload_texture( + const int w, + const int h, + const void* pixels, + VkImage *image, + VmaAllocation *allocation, + VkImageView *view, + uint32_t *descriptor_index) +{ VkDeviceSize imageSize = w * h * 4; - Texture res{}; // --- 1. Create Staging Buffer (CPU Visible) --- VkBuffer stagingBuffer; @@ -371,9 +371,10 @@ Texture Renderer::upload_texture(int w, int h, void* pixels) { stagingBufferInfo.size = imageSize; stagingBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - VmaAllocationCreateInfo stagingAllocCreateInfo = {}; - stagingAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; - stagingAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + VmaAllocationCreateInfo stagingAllocCreateInfo = { + .flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT, + .usage = VMA_MEMORY_USAGE_AUTO, + }; VmaAllocationInfo stagingResultInfo; vmaCreateBuffer(allocator, &stagingBufferInfo, &stagingAllocCreateInfo, &stagingBuffer, &stagingAlloc, &stagingResultInfo); @@ -384,26 +385,30 @@ Texture Renderer::upload_texture(int w, int h, void* pixels) { // --- 2. Create GPU Image (Device Local / Tiled) --- VkExtent3D imageExtent = { (uint32_t) w, (uint32_t) h, 1 }; - VkImageCreateInfo imageInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; - imageInfo.imageType = VK_IMAGE_TYPE_2D; - imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; - imageInfo.extent = imageExtent; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; - imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + VkImageCreateInfo imageInfo = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .imageType = VK_IMAGE_TYPE_2D, + .format = VK_FORMAT_R8G8B8A8_UNORM, + .extent = imageExtent, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED + }; - VmaAllocationCreateInfo imageAllocCreateInfo = {}; - imageAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; - imageAllocCreateInfo.priority = 1.0f; // High priority for textures + VmaAllocationCreateInfo imageAllocCreateInfo = { + .usage = VMA_MEMORY_USAGE_AUTO, + .priority = 1.0f, + }; - vmaCreateImage(allocator, &imageInfo, &imageAllocCreateInfo, &res.image, &res.allocation, nullptr); + vmaCreateImage(allocator, &imageInfo, &imageAllocCreateInfo, image, allocation, nullptr); // --- 3. The Transfer --- immediate_submit([&](VkCommandBuffer cmd) { // Transition image from UNDEFINED to TRANSFER_DST - transition_image_layout(cmd, res.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + transition_image_layout(cmd, *image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); VkBufferImageCopy copyRegion = {}; copyRegion.bufferOffset = 0; @@ -415,23 +420,21 @@ Texture Renderer::upload_texture(int w, int h, void* pixels) { copyRegion.imageSubresource.layerCount = 1; copyRegion.imageExtent = imageExtent; - vkCmdCopyBufferToImage(cmd, stagingBuffer, res.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©Region); + vkCmdCopyBufferToImage(cmd, stagingBuffer, *image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©Region); // Transition image from TRANSFER_DST to SHADER_READ_ONLY - transition_image_layout(cmd, res.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + transition_image_layout(cmd, *image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); }); // Clean up temporary staging resources vmaDestroyBuffer(allocator, stagingBuffer, stagingAlloc); // --- 4. Finalize Handles --- - res.view = create_image_view(res.image, imageInfo.format); + *view = create_image_view(*image, imageInfo.format); // Register in your Bindless Array (Set 0, Binding 0, Index N) - res.descriptor_index = nextTextureSlot++; - update_bindless_slot(res.descriptor_index, res.view, defaultSampler); - - return res; + *descriptor_index = nextTextureSlot++; + update_bindless_slot(*descriptor_index, *view, defaultSampler); } void Renderer::immediate_submit(std::function&& func) const { @@ -513,25 +516,27 @@ VkImageView Renderer::create_image_view(VkImage image, VkFormat format) const { } void Renderer::create_default_sampler() { - VkSamplerCreateInfo samplerInfo{ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO }; + VkSamplerCreateInfo samplerInfo{ + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + // For crisp pixel art, use NEAREST. For smooth textures, use LINEAR. + .magFilter = VK_FILTER_NEAREST, + .minFilter = VK_FILTER_NEAREST, - // For crisp pixel art, use NEAREST. For smooth textures, use LINEAR. - samplerInfo.magFilter = VK_FILTER_NEAREST; - samplerInfo.minFilter = VK_FILTER_NEAREST; + .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST, - // How to handle "out of bounds" UVs - samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + // How to handle "out of bounds" UVs + .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, - // Optimization: turn off things we don't need for simple 2D - samplerInfo.anisotropyEnable = VK_FALSE; - samplerInfo.maxAnisotropy = 1.0f; - samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; - samplerInfo.unnormalizedCoordinates = VK_FALSE; - samplerInfo.compareEnable = VK_FALSE; - samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; - samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + // Optimization: turn off things we don't need for simple 2D + .anisotropyEnable = VK_FALSE, + .maxAnisotropy = 1.0f, + .compareEnable = VK_FALSE, + .compareOp = VK_COMPARE_OP_ALWAYS, + .borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK, + .unnormalizedCoordinates = VK_FALSE, + }; vkCreateSampler(device, &samplerInfo, nullptr, &defaultSampler); } diff --git a/renderer/renderer.h b/renderer/renderer.h index 1d4a46d..7df6462 100644 --- a/renderer/renderer.h +++ b/renderer/renderer.h @@ -203,13 +203,20 @@ struct Renderer { const Frame &frame, std::span vertices) const; - VkPipeline get_pipeline(PipelineType type) const; + [[nodiscard]] VkPipeline get_pipeline(PipelineType type) const; // void bind_material(VkCommandBuffer cmd, uint16_t materialID); void create_descriptor_pool(); - void update_bindless_slot(uint32_t slot, VkImageView view, VkSampler sampler); + void update_bindless_slot(uint32_t slot, VkImageView view, VkSampler sampler) const; // Returns the resource info so the Manager can store it - Texture upload_texture(int w, int h, void* pixels); + void upload_texture( + int w, + int h, + const void* pixels, + VkImage *image, + VmaAllocation *allocation, + VkImageView *view, + uint32_t *descriptor_index); template VkPipeline create_graphics_pipeline( diff --git a/renderer/texture.cpp b/renderer/texture.cpp index 4367ddc..eac67bf 100644 --- a/renderer/texture.cpp +++ b/renderer/texture.cpp @@ -10,7 +10,7 @@ TextureManager::TextureManager() { } -uint32_t TextureManager::load(const std::string& path, Renderer &renderer) { +Texture TextureManager::load(const std::string& path, Renderer &renderer) { // Dedup: Don't load the same file twice! // if (path_to_id.contains(path)) return path_to_id[path]; @@ -18,13 +18,21 @@ uint32_t TextureManager::load(const std::string& path, Renderer &renderer) { unsigned char* data = stbi_load(path.c_str(), &w, &h, &ch, STBI_rgb_alpha); // Tell the renderer to make the GPU version - Texture res = renderer.upload_texture(w, h, data); + Texture res; + res.width = w; + res.height = h; + res.channels = STBI_rgb_alpha; + res.srgb = true; + renderer.upload_texture(w, h, data, &res.image, &res.allocation, &res.view, &res.descriptor_index); stbi_image_free(data); - uint32_t id = static_cast(textures.size()); + res.id = path; + res.path = path; + res.uploaded = true; + textures[path] = res; // path_to_id[path] = id; - return id; // This is the textureID for your sprites + return res; // This is the textureID for your sprites } diff --git a/renderer/texture.h b/renderer/texture.h index 0f69b53..ad9f0ee 100644 --- a/renderer/texture.h +++ b/renderer/texture.h @@ -17,7 +17,6 @@ typedef std::string texture_id; struct Texture { texture_id id; std::string path; - uint32_t index; // NOTE: (vfs) stb_image wants s32 values for the width and height. int32_t width; @@ -37,7 +36,7 @@ struct TextureManager { std::unordered_map textures; TextureManager(); - uint32_t load(const std::string& path, Renderer &renderer); + Texture load(const std::string& path, Renderer &renderer); }; inline TextureManager texture_manager; diff --git a/renderer/texture_sheet.cpp b/renderer/texture_sheet.cpp index 1d4075c..54342e6 100644 --- a/renderer/texture_sheet.cpp +++ b/renderer/texture_sheet.cpp @@ -2,4 +2,4 @@ // Created by Vicente Ferrari Smith on 14.02.26. // -#include "texture_sheet.h" \ No newline at end of file +#include "texture_sheet.h" diff --git a/shaders/shader.slang b/shaders/shader.slang index b06c2e1..03f4032 100644 --- a/shaders/shader.slang +++ b/shaders/shader.slang @@ -17,6 +17,8 @@ struct VSOutput { uint32_t tex_id; }; +Sampler2D textures[]; + static const float2 square[6] = { float2(-0.5, -0.5), // Top-left float2(-0.5, 0.5), // Bottom-left