to test on windows

This commit is contained in:
Vicente Ferrari Smith 2026-02-20 14:35:31 +01:00
parent b83207e652
commit a015aa00ef
10 changed files with 102 additions and 81 deletions

BIN
.DS_Store vendored

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 685 KiB

BIN
assets/boy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 MiB

View File

@ -51,7 +51,7 @@ int main() {
Renderer renderer(window); Renderer renderer(window);
texture_manager.load("assets/boy.jpg", renderer); texture_manager.load("assets/boy.png", renderer);
uint64_t current_time = glfwGetTimerValue(); uint64_t current_time = glfwGetTimerValue();
@ -72,8 +72,8 @@ int main() {
t += dt; t += dt;
updates++; updates++;
} }
std::println("Updates: {}", updates); // std::println("Updates: {}", updates);
std::println("frame time: {}", ((double) (frame_time) / (double) glfwGetTimerFrequency())); // std::println("frame time: {}", ((double) (frame_time) / (double) glfwGetTimerFrequency()));
renderer.begin_frame(); renderer.begin_frame();

View File

@ -102,39 +102,33 @@ void Renderer::submit_sprite(glm::vec2 pos, const sprite_t &sprite) {
} }
void Renderer::create_pipeline_layout() { void Renderer::create_pipeline_layout() {
VkDescriptorSetLayoutBinding bindings[2]; std::array<VkDescriptorSetLayoutBinding, 1> bindings = {
{
bindings[0] = VkDescriptorSetLayoutBinding{ {
.binding = 0, .binding = 0,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.descriptorCount = 1, .descriptorCount = nextTextureSlot,
.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT
}
}
}; };
bindings[1] = { VkDescriptorBindingFlags flags[1] = {
.binding = 1, VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT | VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT
.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
}; };
VkDescriptorSetLayoutBindingFlagsCreateInfo layoutFlags{ VkDescriptorSetLayoutBindingFlagsCreateInfo layoutFlags{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO,
.bindingCount = 2, .bindingCount = 1,
.pBindingFlags = flags .pBindingFlags = flags
}; };
VkDescriptorSetLayoutCreateInfo dslci{ VkDescriptorSetLayoutCreateInfo dslci{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.pNext = &layoutFlags, .pNext = &layoutFlags,
.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT, // .flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT,
.bindingCount = 2, .bindingCount = bindings.size(),
.pBindings = bindings .pBindings = bindings.data()
}; };
vkCreateDescriptorSetLayout(device, &dslci, nullptr, &descriptor_set_layout); vkCreateDescriptorSetLayout(device, &dslci, nullptr, &descriptor_set_layout);
@ -316,15 +310,14 @@ VkPipeline Renderer::get_pipeline(PipelineType type) const {
void Renderer::create_descriptor_pool() { void Renderer::create_descriptor_pool() {
VkDescriptorPoolSize pool_sizes[] = { VkDescriptorPoolSize pool_sizes[] = {
{ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 10 }, { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nextTextureSlot },
{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1 },
}; };
VkDescriptorPoolCreateInfo pool_info{ VkDescriptorPoolCreateInfo pool_info{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
.flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT, .flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT,
.maxSets = 1, .maxSets = 1,
.poolSizeCount = 2, .poolSizeCount = 1,
.pPoolSizes = pool_sizes .pPoolSizes = pool_sizes
}; };
@ -340,7 +333,7 @@ void Renderer::create_descriptor_pool() {
vkAllocateDescriptorSets(device, &alloc_info, &set); 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{ VkDescriptorImageInfo image_info{
.sampler = sampler, .sampler = sampler,
.imageView = view, .imageView = view,
@ -350,7 +343,7 @@ void Renderer::update_bindless_slot(uint32_t slot, VkImageView view, VkSampler s
VkWriteDescriptorSet write{ VkWriteDescriptorSet write{
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstSet = set, .dstSet = set,
.dstArrayElement = slot, // Index in the 1000-size array .dstArrayElement = slot,
.descriptorCount = 1, .descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.pImageInfo = &image_info .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); 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; VkDeviceSize imageSize = w * h * 4;
Texture res{};
// --- 1. Create Staging Buffer (CPU Visible) --- // --- 1. Create Staging Buffer (CPU Visible) ---
VkBuffer stagingBuffer; VkBuffer stagingBuffer;
@ -371,9 +371,10 @@ Texture Renderer::upload_texture(int w, int h, void* pixels) {
stagingBufferInfo.size = imageSize; stagingBufferInfo.size = imageSize;
stagingBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; stagingBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
VmaAllocationCreateInfo stagingAllocCreateInfo = {}; VmaAllocationCreateInfo stagingAllocCreateInfo = {
stagingAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; .flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT,
stagingAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; .usage = VMA_MEMORY_USAGE_AUTO,
};
VmaAllocationInfo stagingResultInfo; VmaAllocationInfo stagingResultInfo;
vmaCreateBuffer(allocator, &stagingBufferInfo, &stagingAllocCreateInfo, &stagingBuffer, &stagingAlloc, &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) --- // --- 2. Create GPU Image (Device Local / Tiled) ---
VkExtent3D imageExtent = { (uint32_t) w, (uint32_t) h, 1 }; VkExtent3D imageExtent = { (uint32_t) w, (uint32_t) h, 1 };
VkImageCreateInfo imageInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; VkImageCreateInfo imageInfo = {
imageInfo.imageType = VK_IMAGE_TYPE_2D; .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; .imageType = VK_IMAGE_TYPE_2D,
imageInfo.extent = imageExtent; .format = VK_FORMAT_R8G8B8A8_UNORM,
imageInfo.mipLevels = 1; .extent = imageExtent,
imageInfo.arrayLayers = 1; .mipLevels = 1,
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; .arrayLayers = 1,
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; .samples = VK_SAMPLE_COUNT_1_BIT,
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_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 = {}; VmaAllocationCreateInfo imageAllocCreateInfo = {
imageAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; .usage = VMA_MEMORY_USAGE_AUTO,
imageAllocCreateInfo.priority = 1.0f; // High priority for textures .priority = 1.0f,
};
vmaCreateImage(allocator, &imageInfo, &imageAllocCreateInfo, &res.image, &res.allocation, nullptr); vmaCreateImage(allocator, &imageInfo, &imageAllocCreateInfo, image, allocation, nullptr);
// --- 3. The Transfer --- // --- 3. The Transfer ---
immediate_submit([&](VkCommandBuffer cmd) { immediate_submit([&](VkCommandBuffer cmd) {
// Transition image from UNDEFINED to TRANSFER_DST // 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 = {}; VkBufferImageCopy copyRegion = {};
copyRegion.bufferOffset = 0; copyRegion.bufferOffset = 0;
@ -415,23 +420,21 @@ Texture Renderer::upload_texture(int w, int h, void* pixels) {
copyRegion.imageSubresource.layerCount = 1; copyRegion.imageSubresource.layerCount = 1;
copyRegion.imageExtent = imageExtent; copyRegion.imageExtent = imageExtent;
vkCmdCopyBufferToImage(cmd, stagingBuffer, res.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copyRegion); vkCmdCopyBufferToImage(cmd, stagingBuffer, *image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copyRegion);
// Transition image from TRANSFER_DST to SHADER_READ_ONLY // 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 // Clean up temporary staging resources
vmaDestroyBuffer(allocator, stagingBuffer, stagingAlloc); vmaDestroyBuffer(allocator, stagingBuffer, stagingAlloc);
// --- 4. Finalize Handles --- // --- 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) // Register in your Bindless Array (Set 0, Binding 0, Index N)
res.descriptor_index = nextTextureSlot++; *descriptor_index = nextTextureSlot++;
update_bindless_slot(res.descriptor_index, res.view, defaultSampler); update_bindless_slot(*descriptor_index, *view, defaultSampler);
return res;
} }
void Renderer::immediate_submit(std::function<void(VkCommandBuffer)>&& func) const { void Renderer::immediate_submit(std::function<void(VkCommandBuffer)>&& func) const {
@ -513,25 +516,27 @@ VkImageView Renderer::create_image_view(VkImage image, VkFormat format) const {
} }
void Renderer::create_default_sampler() { 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. .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST,
samplerInfo.magFilter = VK_FILTER_NEAREST;
samplerInfo.minFilter = VK_FILTER_NEAREST;
// How to handle "out of bounds" UVs // How to handle "out of bounds" UVs
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
samplerInfo.addressModeW = 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 // Optimization: turn off things we don't need for simple 2D
samplerInfo.anisotropyEnable = VK_FALSE; .anisotropyEnable = VK_FALSE,
samplerInfo.maxAnisotropy = 1.0f; .maxAnisotropy = 1.0f,
samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; .compareEnable = VK_FALSE,
samplerInfo.unnormalizedCoordinates = VK_FALSE; .compareOp = VK_COMPARE_OP_ALWAYS,
samplerInfo.compareEnable = VK_FALSE; .borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK,
samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; .unnormalizedCoordinates = VK_FALSE,
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; };
vkCreateSampler(device, &samplerInfo, nullptr, &defaultSampler); vkCreateSampler(device, &samplerInfo, nullptr, &defaultSampler);
} }

View File

@ -203,13 +203,20 @@ struct Renderer {
const Frame &frame, const Frame &frame,
std::span<const vertex_p2_s2_st2_col4_a1_u32> vertices) const; std::span<const vertex_p2_s2_st2_col4_a1_u32> vertices) const;
VkPipeline get_pipeline(PipelineType type) const; [[nodiscard]] VkPipeline get_pipeline(PipelineType type) const;
// void bind_material(VkCommandBuffer cmd, uint16_t materialID); // void bind_material(VkCommandBuffer cmd, uint16_t materialID);
void create_descriptor_pool(); 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 // 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 <typename T> template <typename T>
VkPipeline create_graphics_pipeline( VkPipeline create_graphics_pipeline(

View File

@ -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! // Dedup: Don't load the same file twice!
// if (path_to_id.contains(path)) return path_to_id[path]; // 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); unsigned char* data = stbi_load(path.c_str(), &w, &h, &ch, STBI_rgb_alpha);
// Tell the renderer to make the GPU version // 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); stbi_image_free(data);
uint32_t id = static_cast<uint32_t>(textures.size()); res.id = path;
res.path = path;
res.uploaded = true;
textures[path] = res; textures[path] = res;
// path_to_id[path] = id; // path_to_id[path] = id;
return id; // This is the textureID for your sprites return res; // This is the textureID for your sprites
} }

View File

@ -17,7 +17,6 @@ typedef std::string texture_id;
struct Texture { struct Texture {
texture_id id; texture_id id;
std::string path; std::string path;
uint32_t index;
// NOTE: (vfs) stb_image wants s32 values for the width and height. // NOTE: (vfs) stb_image wants s32 values for the width and height.
int32_t width; int32_t width;
@ -37,7 +36,7 @@ struct TextureManager {
std::unordered_map<texture_id, Texture> textures; std::unordered_map<texture_id, Texture> textures;
TextureManager(); TextureManager();
uint32_t load(const std::string& path, Renderer &renderer); Texture load(const std::string& path, Renderer &renderer);
}; };
inline TextureManager texture_manager; inline TextureManager texture_manager;

View File

@ -2,4 +2,4 @@
// Created by Vicente Ferrari Smith on 14.02.26. // Created by Vicente Ferrari Smith on 14.02.26.
// //
#include "texture_sheet.h" #include "texture_sheet.h"

View File

@ -17,6 +17,8 @@ struct VSOutput {
uint32_t tex_id; uint32_t tex_id;
}; };
Sampler2D textures[];
static const float2 square[6] = { static const float2 square[6] = {
float2(-0.5, -0.5), // Top-left float2(-0.5, -0.5), // Top-left
float2(-0.5, 0.5), // Bottom-left float2(-0.5, 0.5), // Bottom-left