commit 34789e806bbc924d850745a54b4699f2b58b9abc Author: Vicente Ferrari Smith Date: Mon Feb 16 11:41:13 2026 +0100 initial commit diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..5f12cd8 Binary files /dev/null and b/.DS_Store differ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/editor.xml b/.idea/editor.xml new file mode 100644 index 0000000..33921db --- /dev/null +++ b/.idea/editor.xml @@ -0,0 +1,267 @@ + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..146ab09 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..0b76fe5 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..6543a01 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/v.iml b/.idea/v.iml new file mode 100644 index 0000000..4c94235 --- /dev/null +++ b/.idea/v.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..440e867 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9e52fa6 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,84 @@ +cmake_minimum_required(VERSION 4.1) +project(v) + +set(CMAKE_CXX_STANDARD 23) + +include(FetchContent) + +find_package(Vulkan REQUIRED) + +FetchContent_Declare( + glfw + GIT_REPOSITORY https://github.com/glfw/glfw.git + GIT_TAG 232164f62b0edbf667cba37c91bab92ffbb020d0 +) +FetchContent_MakeAvailable(glfw) + +FetchContent_Declare( + glm + GIT_REPOSITORY https://github.com/g-truc/glm.git + GIT_TAG 1.0.3 +) +FetchContent_MakeAvailable(glm) + +FetchContent_Declare( + stb + GIT_REPOSITORY https://github.com/nothings/stb.git + GIT_TAG master +) +FetchContent_MakeAvailable(stb) + +add_library(stb INTERFACE) +target_include_directories(stb INTERFACE ${stb_SOURCE_DIR}) + +add_executable(v main.cpp + renderer/init.cpp + renderer/init.h + renderer/swapchain.cpp + renderer/swapchain.h + renderer/renderer.cpp + renderer/renderer.h + misc.cpp + misc.h + renderer/sprite.cpp + renderer/sprite.h + renderer/texture.cpp + renderer/texture.h + renderer/texture_sheet.cpp + renderer/texture_sheet.h) + +target_include_directories(v + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_link_libraries(v PRIVATE glfw Vulkan::Vulkan glm stb) + +set(SHADER_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/shaders") +set(SHADER_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/shaders") +file(GLOB_RECURSE SHADER_SOURCES + "${SHADER_SOURCE_DIR}/*.vert" + "${SHADER_SOURCE_DIR}/*.frag" + "${SHADER_SOURCE_DIR}/*.comp" +) + +set(SPIRV_BINARY_FILES "") + +foreach(SHADER ${SHADER_SOURCES}) + + get_filename_component(FILENAME ${SHADER} NAME) + set(OUTPUT_SPV "${SHADER_SOURCE_DIR}/${FILENAME}.spv") + + add_custom_command( + OUTPUT ${OUTPUT_SPV} + COMMAND Vulkan::glslc ${SHADER} -o ${OUTPUT_SPV} + DEPENDS ${SHADER} + COMMENT "Compiling shader: ${FILENAME}" + ) + + list(APPEND SPIRV_BINARY_FILES ${OUTPUT_SPV}) +endforeach() + +add_custom_target(compile_shaders ALL DEPENDS ${SPIRV_BINARY_FILES}) + +add_dependencies(v compile_shaders) diff --git a/assets/.DS_Store b/assets/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/assets/.DS_Store differ diff --git a/assets/boy.jpg b/assets/boy.jpg new file mode 100644 index 0000000..c7741df Binary files /dev/null and b/assets/boy.jpg differ diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..7aefef7 --- /dev/null +++ b/main.cpp @@ -0,0 +1,65 @@ +#include +#include +#include +#include + +#define VOLK_IMPLEMENTATION +#include +#define VMA_IMPLEMENTATION +#include +#include + +#include "renderer/init.h" +#include "renderer/swapchain.h" +#include "renderer/renderer.h" +#include "renderer/texture.h" + +#define STB_IMAGE_IMPLEMENTATION +#include + +GLFWwindow *window; + +int main() { + std::print("Hello, Sailor!"); + + if (!glfwInit()) + return -1; + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + window = glfwCreateWindow(640, 480, "Hello World", nullptr, nullptr); + if (!window) { + glfwTerminate(); + return -1; + } + + createInstance(window); + createSurface(window); + createDevice(); + + createSwapchain(window); + + Renderer renderer(window); + + texture_manager.load("assets/boy.jpg", renderer); + + while (!glfwWindowShouldClose(window)) { + + glfwPollEvents(); + + renderer.begin_frame(); + + renderer.submit_quad(); + + // renderer.submit_sprite(); + + renderer.end_frame(); + } + + vkDeviceWaitIdle(device); + + + glfwDestroyWindow(window); + glfwTerminate(); + + return 0; +} diff --git a/misc.cpp b/misc.cpp new file mode 100644 index 0000000..b7b211f --- /dev/null +++ b/misc.cpp @@ -0,0 +1,19 @@ +// +// Created by Vicente Ferrari Smith on 13.02.26. +// + +#include "misc.h" +#include + +std::vector loadFile(const char* path) { + std::ifstream f(path, std::ios::binary | std::ios::ate); + if (f.good()) { + const uint32_t size = f.tellg(); + std::vector data(size); + f.seekg(0); + f.read(data.data(), size); + return data; + } + + return {}; +} diff --git a/misc.h b/misc.h new file mode 100644 index 0000000..e009cfc --- /dev/null +++ b/misc.h @@ -0,0 +1,83 @@ +// +// Created by Vicente Ferrari Smith on 13.02.26. +// + +#ifndef V_MISC_H +#define V_MISC_H + +#include + +std::vector loadFile(const char* path); + + +template +std::vector counting_sort(const std::vector &array, F f) { + int64_t n = array.size(); + if (n == 0) return {}; + std::vector output(n); + + int64_t max_val = 0; + int64_t min_val = 0; + + for (const auto& it : array) { + const int64_t ssy = f(it); + if (ssy > max_val) max_val = ssy; + if (ssy < min_val) min_val = ssy; + } + + const size_t range = max_val - min_val + 1; + std::vector count(range, 0); + + for (const auto& it : array) { + count[f(it) - min_val] += 1; + } + + for (int64_t i = 1; i < range; ++i) { + count[i] += count[i - 1]; + } + + for (int64_t i = n - 1; i >= 0; --i) { + const int64_t ssy = f(array[i]); + output[count[ssy - min_val] - 1] = array[i]; + count[ssy - min_val] -= 1; + } + + return output; +} + +template +std::vector counting_sort_descending(const std::vector& array, F f) { + int64_t n = array.size(); + if (n == 0) return {}; + std::vector output(n); + + int64_t max_val = 0; + int64_t min_val = 0; + + for (const auto& it : array) { + const int64_t ssy = f(it); + if (ssy > max_val) max_val = ssy; + if (ssy < min_val) min_val = ssy; + } + + const int64_t range = max_val - min_val + 1; + std::vector count(range, 0); + + for (const auto& it : array) { + count[f(it) - min_val] += 1; + } + + for (int64_t i = range - 2; i >= 0; --i) { + count[i] += count[i + 1]; + } + + for (int64_t i = n - 1; i >= 0; --i) { + const int64_t ssy = f(array[i]); + output[count[ssy - min_val] - 1] = array[i]; + count[ssy - min_val] -= 1; + } + + return output; +} + +#endif //V_MISC_H diff --git a/renderer/init.cpp b/renderer/init.cpp new file mode 100644 index 0000000..ec392fd --- /dev/null +++ b/renderer/init.cpp @@ -0,0 +1,249 @@ +// +// Created by Vicente Ferrari Smith on 12.02.26. +// + +#include "init.h" +#include +#include + +VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT severity, + VkDebugUtilsMessageTypeFlagsEXT type, + const VkDebugUtilsMessengerCallbackDataEXT* callbackData, + void* userData) { + const char* severityStr = "UNKNOWN"; + if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) + severityStr = "VERBOSE"; + else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) + severityStr = "INFO"; + else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) + severityStr = "WARNING"; + else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) + severityStr = "ERROR"; + + const char* typeStr = ""; + if (type & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) + typeStr = "GENERAL"; + else if (type & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) + typeStr = "VALIDATION"; + else if (type & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) + typeStr = "PERFORMANCE"; + + std::fprintf(stderr, "\n[VULKAN %s][%s]\n%s\n", + severityStr, + typeStr, + callbackData->pMessage); + + // Print associated objects (very useful on MoltenVK) + for (uint32_t i = 0; i < callbackData->objectCount; i++) { + const VkDebugUtilsObjectNameInfoEXT& obj = + callbackData->pObjects[i]; + + std::fprintf(stderr, + " Object %u: type=%d handle=0x%llx name=%s\n", + i, + obj.objectType, + static_cast(obj.objectHandle), + obj.pObjectName ? obj.pObjectName : "null"); + } + + return VK_FALSE; // Do not abort Vulkan call +} + +int createInstance(GLFWwindow *window) { + volkInitialize(); + + uint32_t extensions_count; + const char** extensions = glfwGetRequiredInstanceExtensions(&extensions_count); + std::vector instance_exts; + instance_exts.reserve(extensions_count + 1); + // Copy GLFW-required extensions + for (uint32_t i = 0; i < extensions_count; i++) { + instance_exts.push_back(extensions[i]); + } + // Append portability enumeration (MANDATORY on macOS) + instance_exts.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + instance_exts.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + + for (auto& ext : instance_exts) { + std::print("extension {}\n", ext); + } + + constexpr VkApplicationInfo app{ + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .apiVersion = VK_API_VERSION_1_4, + }; + + const char* layers[] = { + "VK_LAYER_KHRONOS_validation" + }; + + VkDebugUtilsMessengerCreateInfoEXT dbg{}; + dbg.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + dbg.messageSeverity = + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + dbg.messageType = + VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + dbg.pfnUserCallback = debugCallback; + + + const VkInstanceCreateInfo ici{ + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pNext = &dbg, + .flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR, + .pApplicationInfo = &app, + .enabledLayerCount = 1, + .ppEnabledLayerNames = layers, + .enabledExtensionCount = static_cast(instance_exts.size()), + .ppEnabledExtensionNames = instance_exts.data(), + }; + + VkResult res = vkCreateInstance(&ici, nullptr, &instance); + if (res != VK_SUCCESS) { + printf("vkCreateInstance failed: %d\n", res); + return -1; + } + + volkLoadInstance(instance); + + VkDebugUtilsMessengerEXT messenger; + vkCreateDebugUtilsMessengerEXT(instance, &dbg, nullptr, &messenger); + + + uint32_t layerCount = 0; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + return 0; +} + +void createSurface(GLFWwindow *window) { + glfwCreateWindowSurface(instance, window, nullptr, &surface); +} + +void createDevice() { + uint32_t deviceCount = 0; + uint32_t res = vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + if (res != VK_SUCCESS || deviceCount == 0) { + printf("vkEnumeratePhysicalDevices failed or found no devices (%d)\n", res); + } + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + printf("Found %u physical device(s)\n", deviceCount); + for (uint32_t i = 0; i < deviceCount; i++) { + VkPhysicalDeviceProperties props{}; + vkGetPhysicalDeviceProperties(devices[i], &props); + + printf("Device %u:\n", i); + printf(" Name: %s\n", props.deviceName); + printf(" Type: %u\n", props.deviceType); + printf(" API version: %u.%u.%u\n", + VK_VERSION_MAJOR(props.apiVersion), + VK_VERSION_MINOR(props.apiVersion), + VK_VERSION_PATCH(props.apiVersion)); + } + + physicalDevice = devices[0]; + + queueFamily = 0; + uint32_t qCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &qCount, nullptr); + std::vector qprops(qCount); + vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &qCount, qprops.data()); + + for (uint32_t i = 0; i < qCount; i++) { + VkBool32 present = VK_FALSE; + vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, i, surface, &present); + if ((qprops[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) && present) { + queueFamily = i; + break; + } + } + + std::println("{}", queueFamily); + + const char* devExts[] = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, + VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME, + VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME, + VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME, + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME, + "VK_KHR_portability_subset" + }; + + float prio = 1.0f; + VkDeviceQueueCreateInfo qci{ + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .queueFamilyIndex = queueFamily, + .queueCount = 1, + .pQueuePriorities = &prio + }; + + VkPhysicalDeviceDescriptorIndexingFeatures indexingFeatures{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES, + .pNext = nullptr, + .shaderSampledImageArrayNonUniformIndexing = VK_TRUE, + .descriptorBindingSampledImageUpdateAfterBind = VK_TRUE, + .descriptorBindingPartiallyBound = VK_TRUE, + .runtimeDescriptorArray = VK_TRUE + }; + + VkPhysicalDeviceTimelineSemaphoreFeatures timeline{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES, + .pNext = &indexingFeatures, + .timelineSemaphore = VK_TRUE + }; + + VkPhysicalDeviceSynchronization2Features sync2{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES, + .pNext = &timeline, + .synchronization2 = VK_TRUE + }; + + VkPhysicalDeviceDynamicRenderingFeatures dyn{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES, + .pNext = &sync2, + .dynamicRendering = VK_TRUE + }; + + + const VkDeviceCreateInfo dci{ + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pNext = &dyn, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &qci, + .enabledExtensionCount = 7, + .ppEnabledExtensionNames = devExts + }; + + vkCreateDevice(physicalDevice, &dci, nullptr, &device); + + volkLoadDevice(device); + + vkGetDeviceQueue(device, queueFamily, 0, &graphics_queue); + + VmaAllocatorCreateInfo allocatorCreateInfo = { + .flags = 0, + .physicalDevice = physicalDevice, + .device = device, + .preferredLargeHeapBlockSize = 0, + .pAllocationCallbacks = nullptr, + .pDeviceMemoryCallbacks = nullptr, + .pHeapSizeLimit = nullptr, + .pVulkanFunctions = nullptr, + .instance = instance, + .vulkanApiVersion = VK_API_VERSION_1_4, + .pTypeExternalMemoryHandleTypes = nullptr, + }; + + VmaVulkanFunctions vulkan_functions = {}; + res = vmaImportVulkanFunctionsFromVolk(&allocatorCreateInfo, &vulkan_functions); + + allocatorCreateInfo.pVulkanFunctions = &vulkan_functions; + + res = vmaCreateAllocator(&allocatorCreateInfo, &allocator); +} diff --git a/renderer/init.h b/renderer/init.h new file mode 100644 index 0000000..2ffc028 --- /dev/null +++ b/renderer/init.h @@ -0,0 +1,29 @@ +// +// Created by Vicente Ferrari Smith on 12.02.26. +// + +#ifndef V_INIT_H +#define V_INIT_H + +#include +#include +#include + +inline VkInstance instance; +inline VkPhysicalDevice physicalDevice; +inline VkDevice device; +inline VkQueue graphics_queue; +inline uint32_t queueFamily; + +inline VkSurfaceKHR surface; +inline VkDebugUtilsMessengerEXT debugMessenger; + +inline VmaAllocator allocator; + +int createInstance(GLFWwindow* window); +void createSurface(GLFWwindow* window); +void pickPhysicalDevice(); +void createDevice(); + + +#endif //V_INIT_H diff --git a/renderer/renderer.cpp b/renderer/renderer.cpp new file mode 100644 index 0000000..c6df79e --- /dev/null +++ b/renderer/renderer.cpp @@ -0,0 +1,723 @@ +// +// Created by Vicente Ferrari Smith on 13.02.26. +// + +#include "renderer.h" +#include "init.h" +#include "sprite.h" +#include "swapchain.h" + +bool SortKey::operator<(const SortKey& b) const { + if (depth != b.depth) return depth < b.depth; + if (pipeline != b.pipeline) return pipeline < b.pipeline; + return materialID < b.materialID; +} + +Renderer::Renderer(GLFWwindow *window) { + + VkCommandPoolCreateInfo cpci{ + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + .queueFamilyIndex = queueFamily + }; + + vkCreateCommandPool(device, &cpci, nullptr, &commandPool); + + create_pipeline_layout(); + colored_quad_pipeline = create_graphics_pipeline( + device, + pipelineLayout, + swapchain_format.format, + VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + true + ); + create_default_sampler(); + create_descriptor_pool(); + createFrameResources(); +} + +void Renderer::begin_frame() { + uint64_t waitValue = 0; + + if (frameValue >= MAX_FRAMES_IN_FLIGHT) { + waitValue = frameValue - MAX_FRAMES_IN_FLIGHT + 1; + + VkSemaphoreWaitInfo waitInfo{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO, + .semaphoreCount = 1, + .pSemaphores = &timelineSemaphore, + .pValues = &waitValue + }; + + vkWaitSemaphores(device, &waitInfo, UINT64_MAX); + } +} + +void Renderer::flush() { + +} + +void Renderer::submit_quad() { + glm::vec2 pos = {0, 0}; + RenderCommand cmd {}; + cmd.pipeline = PipelineType::ColoredQuad; + cmd.key = { + (uint16_t) pos.y, + 0, + (uint8_t) PipelineType::ColoredQuad + }; + + cmd.colored_quad = { + .position = pos, + .size = {0.25, 0.25}, + .color = {1, 1, 1, 1}, + }; + + commands.push_back(cmd); +} + +void Renderer::submit_sprite(glm::vec2 pos, const sprite_t &sprite) { + RenderCommand cmd {}; + cmd.pipeline = PipelineType::TexturedQuad; + cmd.key = { + (uint16_t) pos.y, + 0, + (uint8_t) PipelineType::TexturedQuad + }; + + cmd.textured_quad = { + .position = pos, + .size = {0, 0}, + .uvMin = {0, 0}, + .uvMax = {0, 0}, + .color = {1, 1, 1, 1}, + .textureID = 0, + }; + + commands.push_back(cmd); + + // assert(started == true, "You can't submit without having started the renderer first."); + // renderable : Renderable; + // renderable.type = .Sprite; + // + // if sprite.window_space + // renderable.projection_type = .ORTHOGRAPHIC_WINDOW; + // else + // renderable.projection_type = .ORTHOGRAPHIC_WORLD; + // + // renderable.pos = pos; + // renderable.sprite.texture_sheet = sprite.texture_sheet; + // renderable.sprite.texture_cell = sprite.texture_cell; + // renderable.sprite.origin = sprite.origin; + // renderable.sprite.scale = sprite.scale; + // renderable.sprite.colour = sprite.colour; + // renderable.sprite.alpha = alpha; + // + // array_add(*renderer.renderable_list, renderable); +} + +void Renderer::create_pipeline_layout() { + VkDescriptorSetLayoutBinding bindings[2]; + + bindings[0] = VkDescriptorSetLayoutBinding{ + .binding = 0, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = 1000, + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT + }; + + bindings[1] = { + .binding = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_VERTEX_BIT // The vertex shader "pulls" from here + }; + + VkDescriptorBindingFlags flags[2] = { + VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT | VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT, + 0 // Standard behavior for the buffer + }; + + VkDescriptorSetLayoutBindingFlagsCreateInfo layoutFlags{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO, + .bindingCount = 2, + .pBindingFlags = flags + }; + + VkDescriptorSetLayoutCreateInfo dslci{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + .pNext = &layoutFlags, // Attach the flags + .flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT, + .bindingCount = 2, + .pBindings = bindings + }; + + vkCreateDescriptorSetLayout(device, &dslci, nullptr, &descriptor_set_layout); + + // 2. Create the Shared Pipeline Layout + VkPushConstantRange push_constant{ + .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, + .offset = 0, + .size = sizeof(glm::mat4) // Camera Projection + }; + + VkPipelineLayoutCreateInfo plci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .setLayoutCount = 1, + .pSetLayouts = &descriptor_set_layout, + .pushConstantRangeCount = 1, + .pPushConstantRanges = &push_constant, + }; + + vkCreatePipelineLayout(device, &plci, nullptr, &pipelineLayout); +} + +void Renderer::createFrameResources() { + VkSemaphoreTypeCreateInfo typeInfo{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO, + .semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE, + .initialValue = 0 + }; + + const VkSemaphoreCreateInfo semaphoreci{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = &typeInfo + }; + + vkCreateSemaphore(device, &semaphoreci, nullptr, &timelineSemaphore); + + const VkSemaphoreCreateInfo seci{ VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; + + for (auto & frame : frames) { + vkCreateSemaphore(device, &seci, nullptr, &frame.imageAvailable); + vkCreateSemaphore(device, &seci, nullptr, &frame.renderFinished); + } + + const VkCommandBufferAllocateInfo cbai{ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = commandPool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1 + }; + + for (auto &frame : frames) { + vkAllocateCommandBuffers(device, &cbai, &frame.command_buffer); + } + + VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + bufferInfo.size = 1024 * 1024 * 4; + bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; + + VmaAllocationCreateInfo allocInfo = {}; + allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; + allocInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; + + vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &vertexBuffer, &vertexAllocation, &vertexAllocInfo); + + VkDescriptorBufferInfo vertexBufferInfo{ + .buffer = vertexBuffer, + .offset = 0, + .range = VK_WHOLE_SIZE + }; + + VkWriteDescriptorSet bufferWrite{ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = set, + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .pBufferInfo = &vertexBufferInfo + }; + + vkUpdateDescriptorSets(device, 1, &bufferWrite, 0, nullptr); +} + +void Renderer::end_frame() { + + commands = counting_sort_descending(commands, [](const RenderCommand &cmd){ + return cmd.key.depth; + }); + + vertex_p2_st2_col4_a1_u32 *vPtr = (vertex_p2_st2_col4_a1_u32 *) vertexAllocInfo.pMappedData; + vertex_p2_st2_col4_a1_u32 *currentFrameStart = vPtr + (currentFrame * MAX_VERTICES_PER_FRAME); + + uint32_t totalVertices = 0; + for (auto& cmd : commands) { + + vPtr = currentFrameStart + totalVertices; + + switch (cmd.pipeline) { + case PipelineType::ColoredQuad: { + const auto &q = cmd.textured_quad; + + // Calculate spatial corners + float x0 = q.position.x; + float y0 = q.position.y; + float x1 = q.position.x + q.size.x; + float y1 = q.position.y + q.size.y; + + // Calculate UV corners + float u0 = q.uvMin.x; + float v0 = q.uvMin.y; + float u1 = q.uvMax.x; + float v1 = q.uvMax.y; + + // Define the 4 corners of the quad + vertex_p2_st2_col4_a1_u32 vTL = { {x0, y0}, {u0, v0}, q.color, 1, q.textureID }; + vertex_p2_st2_col4_a1_u32 vTR = { {x1, y0}, {u1, v0}, q.color, 1, q.textureID }; + vertex_p2_st2_col4_a1_u32 vBL = { {x0, y1}, {u0, v1}, q.color, 1, q.textureID }; + vertex_p2_st2_col4_a1_u32 vBR = { {x1, y1}, {u1, v1}, q.color, 1, q.textureID }; + + // --- Triangle 1 (TL, TR, BL) --- + vPtr[0] = vTL; + vPtr[1] = vTR; + vPtr[2] = vBL; + + // --- Triangle 2 (TR, BR, BL) --- + vPtr[3] = vTR; + vPtr[4] = vBR; + vPtr[5] = vBL; + + break; + } + default: + break; + } + + totalVertices += 6; + } + + uint32_t imageIndex; + vkAcquireNextImageKHR( + device, + swapchain, + UINT64_MAX, + frames[currentFrame].imageAvailable, + VK_NULL_HANDLE, + &imageIndex + ); + + VkCommandBuffer command_buffer = frames[currentFrame].command_buffer; + vkResetCommandBuffer(command_buffer, 0); + + recordCommandBuffer( + command_buffer, + images[imageIndex], + imageViews[imageIndex], + swapchain_extent, + imageLayouts[imageIndex] + ); + + commands.clear(); + + imageLayouts[imageIndex] = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + frameValue++; + + VkSemaphoreSubmitInfo waitBinary{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .semaphore = frames[currentFrame].imageAvailable, + .stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT + }; + + VkSemaphoreSubmitInfo signalBinary{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .semaphore = frames[currentFrame].renderFinished, + .stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT + }; + + VkSemaphoreSubmitInfo signalTimeline{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .semaphore = timelineSemaphore, + .value = frameValue, + .stageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT + }; + + VkSemaphoreSubmitInfo signals[] = { signalBinary, signalTimeline }; + + VkCommandBufferSubmitInfo cmdInfo{ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, + .commandBuffer = command_buffer, + }; + + const VkSubmitInfo2 submit{ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2, + .waitSemaphoreInfoCount = 1, + .pWaitSemaphoreInfos = &waitBinary, + .commandBufferInfoCount = 1, + .pCommandBufferInfos = &cmdInfo, + .signalSemaphoreInfoCount = 2, + .pSignalSemaphoreInfos = signals, + }; + + vkQueueSubmit2(graphics_queue, 1, &submit, VK_NULL_HANDLE); + + VkPresentInfoKHR present{ + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &frames[currentFrame].renderFinished, + .swapchainCount = 1, + .pSwapchains = &swapchain, + .pImageIndices = &imageIndex, + }; + + vkQueuePresentKHR(graphics_queue, &present); + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; +} + +void Renderer::recordCommandBuffer( + VkCommandBuffer command_buffer, + VkImage image, + VkImageView imageView, + VkExtent2D extent, + VkImageLayout oldLayout) const +{ + VkCommandBufferBeginInfo begin{ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; + vkBeginCommandBuffer(command_buffer, &begin); + + { + VkImageMemoryBarrier2 toColor{ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2 }; + toColor.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + toColor.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + toColor.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + toColor.oldLayout = oldLayout; + toColor.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + toColor.image = image; + toColor.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; + + VkDependencyInfo dep{ + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &toColor + }; + vkCmdPipelineBarrier2(command_buffer, &dep); + } + + VkClearValue clearColor = {{{0.1f, 0.1f, 0.2f, 1.0f}}}; + VkRenderingAttachmentInfo colorAttach{ + .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO, + .imageView = imageView, + .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .clearValue = clearColor + }; + + VkRenderingInfo ri{ + .sType = VK_STRUCTURE_TYPE_RENDERING_INFO, + .renderArea = {{0,0}, extent}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttach + }; + + vkCmdBeginRendering(command_buffer, &ri); + + vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &set, 0, nullptr); + + VkViewport vp{0.0f, 0.0f, (float)extent.width, (float)extent.height, 0.0f, 1.0f}; + VkRect2D sc{{0, 0}, extent}; + vkCmdSetViewport(command_buffer, 0, 1, &vp); + vkCmdSetScissor(command_buffer, 0, 1, &sc); + + PipelineType lastPipeline = PipelineType::None; // Track current state + uint32_t vertexOffset = currentFrame * MAX_VERTICES_PER_FRAME; + uint32_t currentBatchVertices = 0; + + for (const auto & cmd : commands) { + // Only switch pipelines if we have to + if (cmd.pipeline != lastPipeline) { + // If we were mid-batch, draw what we have before switching + if (currentBatchVertices > 0) { + vkCmdDraw(command_buffer, currentBatchVertices, 1, vertexOffset, 0); + vertexOffset += currentBatchVertices; + currentBatchVertices = 0; + } + + vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, get_pipeline(cmd.pipeline)); + lastPipeline = cmd.pipeline; + } + + currentBatchVertices += 6; + } + + // Draw the final batch + if (currentBatchVertices > 0) { + vkCmdDraw(command_buffer, currentBatchVertices, 1, vertexOffset, 0); + } + + vkCmdEndRendering(command_buffer); + + // 3. Transition back to Present + { + VkImageMemoryBarrier2 toPresent{ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2 }; + toPresent.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + toPresent.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + toPresent.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT; + toPresent.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + toPresent.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + toPresent.image = image; + toPresent.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; + + VkDependencyInfo dep{ .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &toPresent }; + vkCmdPipelineBarrier2(command_buffer, &dep); + } + + vkEndCommandBuffer(command_buffer); +} + +VkPipeline Renderer::get_pipeline(PipelineType type) const { + switch (type) { + case PipelineType::TexturedQuad: return textured_quad_pipeline; + case PipelineType::ColoredQuad: return colored_quad_pipeline; + case PipelineType::Line: return line_pipeline; + default: return {}; + } +} + +void Renderer::bind_material(VkCommandBuffer cmd, uint16_t materialID) { + // In a real app, you'd have an array/map: std::vector textureSets; + VkDescriptorSet set = textureSets[materialID]; + + vkCmdBindDescriptorSets( + cmd, + VK_PIPELINE_BIND_POINT_GRAPHICS, + pipelineLayout, // Our shared layout + 0, // Starting at Set 0 + 1, // Binding 1 set + &set, + 0, nullptr + ); +} + +void Renderer::create_descriptor_pool() { + VkDescriptorPoolSize pool_sizes[] = { + { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 }, + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1 }, + }; + + VkDescriptorPoolCreateInfo pool_info{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT, + .maxSets = 1000, + .poolSizeCount = 2, + .pPoolSizes = pool_sizes + }; + + vkCreateDescriptorPool(device, &pool_info, nullptr, &descriptorPool); + + VkDescriptorSetAllocateInfo alloc_info{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .descriptorPool = descriptorPool, + .descriptorSetCount = 1, + .pSetLayouts = &descriptor_set_layout + }; + + if (vkAllocateDescriptorSets(device, &alloc_info, &set) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate bindless descriptor set!"); + } +} + +void Renderer::update_bindless_slot(uint32_t slot, VkImageView view, VkSampler sampler) { + VkDescriptorImageInfo image_info{ + .sampler = sampler, + .imageView = view, + .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL + }; + + VkWriteDescriptorSet write{ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = set, + .dstArrayElement = slot, // Index in the 1000-size array + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .pImageInfo = &image_info + }; + + vkUpdateDescriptorSets(device, 1, &write, 0, nullptr); +} + +Texture Renderer::upload_texture(int w, int h, void* pixels) { + VkDeviceSize imageSize = w * h * 4; + Texture res{}; + + // --- 1. Create Staging Buffer (CPU Visible) --- + VkBuffer stagingBuffer; + VmaAllocation stagingAlloc; + + VkBufferCreateInfo stagingBufferInfo = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + 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; + + VmaAllocationInfo stagingResultInfo; + vmaCreateBuffer(allocator, &stagingBufferInfo, &stagingAllocCreateInfo, &stagingBuffer, &stagingAlloc, &stagingResultInfo); + + // Copy raw pixels into the mapped memory provided by VMA + memcpy(stagingResultInfo.pMappedData, pixels, imageSize); + + // --- 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; + + VmaAllocationCreateInfo imageAllocCreateInfo = {}; + imageAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; + imageAllocCreateInfo.priority = 1.0f; // High priority for textures + + vmaCreateImage(allocator, &imageInfo, &imageAllocCreateInfo, &res.image, &res.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); + + VkBufferImageCopy copyRegion = {}; + copyRegion.bufferOffset = 0; + copyRegion.bufferRowLength = 0; + copyRegion.bufferImageHeight = 0; + copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copyRegion.imageSubresource.mipLevel = 0; + copyRegion.imageSubresource.baseArrayLayer = 0; + copyRegion.imageSubresource.layerCount = 1; + copyRegion.imageExtent = imageExtent; + + vkCmdCopyBufferToImage(cmd, stagingBuffer, res.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); + }); + + // Clean up temporary staging resources + vmaDestroyBuffer(allocator, stagingBuffer, stagingAlloc); + + // --- 4. Finalize Handles --- + res.view = create_image_view(res.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; +} + +void Renderer::immediate_submit(std::function&& func) const { + VkCommandBufferAllocateInfo allocInfo{ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO }; + allocInfo.commandPool = commandPool; // Use a pool created with VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer cmd; + vkAllocateCommandBuffers(device, &allocInfo, &cmd); + + VkCommandBufferBeginInfo beginInfo{ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(cmd, &beginInfo); + + // Execute the code passed in the lambda + func(cmd); + + vkEndCommandBuffer(cmd); + + VkSubmitInfo submit{ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO }; + submit.commandBufferCount = 1; + submit.pCommandBuffers = &cmd; + + // Submit and wait + vkQueueSubmit(graphics_queue, 1, &submit, VK_NULL_HANDLE); + vkQueueWaitIdle(graphics_queue); + + vkFreeCommandBuffers(device, commandPool, 1, &cmd); +} + +void Renderer::transition_image_layout(VkCommandBuffer cmd, VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout) const { + VkImageMemoryBarrier2 barrier{ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2 }; + + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.image = image; + barrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; + + // Simple synchronization: wait for all previous commands, and block all future ones + // You can optimize these masks later, but this is safe for a 2D engine + barrier.srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; + barrier.srcAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; + barrier.dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT; + + VkDependencyInfo dep{ .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO }; + dep.imageMemoryBarrierCount = 1; + dep.pImageMemoryBarriers = &barrier; + + vkCmdPipelineBarrier2(cmd, &dep); +} + +VkImageView Renderer::create_image_view(VkImage image, VkFormat format) const { + VkImageViewCreateInfo viewInfo{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = image, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = format, + }; + + // Default component mapping (R,G,B,A) + viewInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + viewInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + viewInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + viewInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + + // Which part of the image to look at (Mip 0, Layer 0) + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + + VkImageView view; + if (vkCreateImageView(device, &viewInfo, nullptr, &view) != VK_SUCCESS) { + throw std::runtime_error("failed to create image view!"); + } + return view; +} + +void Renderer::create_default_sampler() { + VkSamplerCreateInfo samplerInfo{ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO }; + + // For crisp pixel art, use NEAREST. For smooth textures, use LINEAR. + samplerInfo.magFilter = VK_FILTER_NEAREST; + samplerInfo.minFilter = VK_FILTER_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; + + // 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; + + if (vkCreateSampler(device, &samplerInfo, nullptr, &defaultSampler) != VK_SUCCESS) { + throw std::runtime_error("failed to create texture sampler!"); + } +} diff --git a/renderer/renderer.h b/renderer/renderer.h new file mode 100644 index 0000000..9cd9689 --- /dev/null +++ b/renderer/renderer.h @@ -0,0 +1,371 @@ +// +// Created by Vicente Ferrari Smith on 13.02.26. +// + +#ifndef V_RENDERER_H +#define V_RENDERER_H + +#include +#include +#include +#include +#include "sprite.h" +#include "texture.h" +#include + +enum class PROJECTION_TYPE : uint8_t { + NONE, + ORTHOGRAPHIC_WORLD, + ORTHOGRAPHIC_WINDOW, + PERSPECTIVE_WORLD, + PERSPECTIVE_WINDOW, + COUNT, +}; + +struct vertex_p2_col4 { + glm::vec2 pos; + glm::vec4 col; +}; + +struct vertex_p2_st2_col4 { + glm::vec2 pos; + glm::vec2 st; + glm::vec4 col; +}; + +struct vertex_p2_st2_col4_a1_u32 { + glm::vec2 pos; + glm::vec2 st; + glm::vec4 col; + float alpha; + uint32_t textureID; + + static VkVertexInputBindingDescription getBindingDescription() { + return {0, sizeof(vertex_p2_st2_col4_a1_u32), VK_VERTEX_INPUT_RATE_VERTEX}; + } + + static std::vector getAttributeDescriptions() { + return { + {0, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(vertex_p2_st2_col4_a1_u32, pos)}, + {1, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(vertex_p2_st2_col4_a1_u32, st)}, + {2, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(vertex_p2_st2_col4_a1_u32, col)}, + {3, 0, VK_FORMAT_R32_SFLOAT, offsetof(vertex_p2_st2_col4_a1_u32, alpha)}, + {4, 0, VK_FORMAT_R32_UINT, offsetof(vertex_p2_st2_col4_a1_u32, textureID)}, + }; + } +}; + +struct vertex_st2 { + glm::vec2 st; +}; + +struct vertex_p2_scale2_rot1_st2 { + glm::vec2 pos; + glm::vec2 scale; + float rot; + glm::vec2 st; +}; + +typedef vertex_st2 quad_st2[6]; + +typedef vertex_p2_scale2_rot1_st2 quad_pos2_scale2_rot1_st2[6]; + +// commands + +enum class PipelineType : uint8_t { + None, + TexturedQuad, + ColoredQuad, + Line, + Text, + Chunk +}; + +struct TexturedQuadCmd { + glm::vec2 position; + glm::vec2 size; + glm::vec2 uvMin; + glm::vec2 uvMax; + glm::vec4 color; + uint16_t textureID; +}; + +struct ColoredQuadCmd { + glm::vec2 position; + glm::vec2 size; + glm::vec4 color; +}; + +struct LineCmd { + glm::vec2 start; + glm::vec2 end; + glm::vec4 color; +}; + +// struct TextCmd { +// Font* font; +// std::string text; +// glm::vec2 position; +// glm::vec4 color; +// }; + +struct ChunkCmd { + VkBuffer vertexBuffer; + VkBuffer indexBuffer; + uint32_t indexCount; +}; + +struct SortKey { + uint16_t depth; // world Z or Y-sorted depth + uint16_t materialID; // texture sheet, font atlas, etc. + uint8_t pipeline; // PipelineType + + bool operator<(const SortKey& b) const; +}; + +struct RenderCommand { + + SortKey key; + PipelineType pipeline; + + union { + TexturedQuadCmd textured_quad; + ColoredQuadCmd colored_quad; + LineCmd line; + // TextCmd text; + ChunkCmd chunk; + }; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////// + +struct Renderer { + std::vector commands; + + void begin_frame(); + void end_frame(); + void flush(); + + void submit_sprite(glm::vec2 pos, const sprite_t &sprite); + void submit_quad(); + + explicit Renderer(GLFWwindow *window); + void create_pipeline_layout(); + void createFrameResources(); + void create_default_sampler(); + void recordCommandBuffer( + VkCommandBuffer cmd, + VkImage image, + VkImageView imageView, + VkExtent2D extent, + VkImageLayout oldLayout) const; + void immediate_submit(std::function&& func) const; + void transition_image_layout(VkCommandBuffer cmd, VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout) const; + VkImageView create_image_view(VkImage image, VkFormat format) const; + + VkDescriptorSetLayout descriptor_set_layout; + VkPipelineLayout pipelineLayout; + VkPipeline textured_quad_pipeline; + VkPipeline colored_quad_pipeline; + VkPipeline line_pipeline; + VkPipeline text_pipeline; + VkPipeline chunk_pipeline; + VkDescriptorSet set; + + VkSampler defaultSampler; + + uint32_t nextTextureSlot = 0; + + VkCommandPool commandPool; + + static constexpr uint32_t MAX_FRAMES_IN_FLIGHT = 2; + static constexpr uint32_t MAX_VERTICES_PER_FRAME = 65536; + + struct Frame { + VkCommandBuffer command_buffer{}; + VkSemaphore imageAvailable{}; + VkSemaphore renderFinished{}; + }; + + Frame frames[MAX_FRAMES_IN_FLIGHT]; + uint32_t currentFrame = 0; + + VkSemaphore timelineSemaphore{}; + uint64_t frameValue = 0; + + VkBuffer vertexBuffer; + VmaAllocation vertexAllocation; + VmaAllocationInfo vertexAllocInfo; + + VkDescriptorPool descriptorPool; + std::vector textureSets; + + VkPipeline get_pipeline(PipelineType type) const; + void bind_material(VkCommandBuffer cmd, uint16_t materialID); + void create_descriptor_pool(); + VkDescriptorSet create_texture_descriptor(VkImageView imageView, VkSampler sampler); + void update_bindless_slot(uint32_t slot, VkImageView view, VkSampler sampler); + + // Returns the resource info so the Manager can store it + Texture upload_texture(int w, int h, void* pixels); + + template + VkPipeline create_graphics_pipeline( + VkDevice device, + VkPipelineLayout layout, + VkFormat colorFormat, + // VkShaderModule vertShader, + // VkShaderModule fragShader, + VkPrimitiveTopology topology, + bool enableBlending) + { + + auto vsCode = loadFile("shaders/triangle.vert.spv"); + auto fsCode = loadFile("shaders/triangle.frag.spv"); + + VkShaderModuleCreateInfo smci{ + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO + }; + + smci.codeSize = vsCode.size(); + smci.pCode = reinterpret_cast(vsCode.data()); + VkShaderModule vs; + vkCreateShaderModule(device, &smci, nullptr, &vs); + + smci.codeSize = fsCode.size(); + smci.pCode = reinterpret_cast(fsCode.data()); + VkShaderModule fs; + vkCreateShaderModule(device, &smci, nullptr, &fs); + + // --- Shaders --- + VkPipelineShaderStageCreateInfo stages[2] = { + { + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + nullptr, + 0, + VK_SHADER_STAGE_VERTEX_BIT, + vs, + "main" + }, + { + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + nullptr, + 0, + VK_SHADER_STAGE_FRAGMENT_BIT, + fs, + "main" + } + }; + + // --- Vertex Input (Generic) --- + auto binding = T::getBindingDescription(); + auto attrs = T::getAttributeDescriptions(); + + // --- Vertex Input (Matching our vertex_p2_st2_col4 struct) --- + VkPipelineVertexInputStateCreateInfo vi{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .vertexBindingDescriptionCount = 0, + .pVertexBindingDescriptions = nullptr, + .vertexAttributeDescriptionCount = 0, + .pVertexAttributeDescriptions = nullptr, + }; + + // --- Input Assembly (Changes based on Topology parameter) --- + VkPipelineInputAssemblyStateCreateInfo ia{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO}; + ia.topology = topology; + + // --- Blending (Changes based on enableBlending parameter) --- + VkPipelineColorBlendAttachmentState colorBlend{ + .blendEnable = enableBlending ? VK_TRUE : VK_FALSE, + .srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA, + .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + .colorBlendOp = VK_BLEND_OP_ADD, + .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, + .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, + .alphaBlendOp = VK_BLEND_OP_ADD, + .colorWriteMask = 0xF + }; + + // --- Boilerplate (Standard 2D Defaults) --- + VkPipelineViewportStateCreateInfo vp{ + VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + nullptr, + 0, + 1, + nullptr, + 1, + nullptr + }; + + VkPipelineRasterizationStateCreateInfo rs{ + VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + nullptr, + 0, + 0, + 0, + VK_POLYGON_MODE_FILL, + VK_CULL_MODE_NONE, + VK_FRONT_FACE_CLOCKWISE, + 0, + 0, + 0, + 0, + 1.0f + }; + + VkPipelineMultisampleStateCreateInfo ms{ + VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + nullptr, + 0, + VK_SAMPLE_COUNT_1_BIT + }; + + VkPipelineColorBlendStateCreateInfo cb{ + VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + nullptr, + 0, + 0, + VK_LOGIC_OP_AND, + 1, + &colorBlend + }; + + VkDynamicState dyns[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + VkPipelineDynamicStateCreateInfo ds{ + VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, + nullptr, + 0, + 2, + dyns + }; + + VkPipelineRenderingCreateInfo rci{ + VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO, + nullptr, + 0, + 1, + &colorFormat + }; + + VkGraphicsPipelineCreateInfo gpci{ + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = &rci, + .stageCount = 2, + .pStages = stages, + .pVertexInputState = &vi, + .pInputAssemblyState = &ia, + .pViewportState = &vp, + .pRasterizationState = &rs, + .pMultisampleState = &ms, + .pColorBlendState = &cb, + .pDynamicState = &ds, + .layout = layout + }; + + VkPipeline pipeline; + vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &gpci, nullptr, &pipeline); + return pipeline; + } +}; + +#endif //V_RENDERER_H diff --git a/renderer/sprite.cpp b/renderer/sprite.cpp new file mode 100644 index 0000000..148b117 --- /dev/null +++ b/renderer/sprite.cpp @@ -0,0 +1,5 @@ +// +// Created by Vicente Ferrari Smith on 14.02.26. +// + +#include "sprite.h" \ No newline at end of file diff --git a/renderer/sprite.h b/renderer/sprite.h new file mode 100644 index 0000000..a272c77 --- /dev/null +++ b/renderer/sprite.h @@ -0,0 +1,24 @@ +// +// Created by Vicente Ferrari Smith on 14.02.26. +// + +#ifndef V_SPRITE_H +#define V_SPRITE_H + +#include +#include "texture_sheet.h" + +struct sprite_t { + glm::vec2 origin; + glm::vec2 scale; + float rotation; + glm::vec4 colour; + float alpha; + bool window_space; + bool maintain_ar; + + texture_sheet_id texture_sheet; + texture_cell_id texture_cell; +}; + +#endif //V_SPRITE_H \ No newline at end of file diff --git a/renderer/swapchain.cpp b/renderer/swapchain.cpp new file mode 100644 index 0000000..fb653cc --- /dev/null +++ b/renderer/swapchain.cpp @@ -0,0 +1,64 @@ +// +// Created by Vicente Ferrari Smith on 13.02.26. +// + +#include "swapchain.h" + +void createSwapchain(GLFWwindow* window) { + int fbWidth, fbHeight; + glfwGetFramebufferSize(window, &fbWidth, &fbHeight); + + swapchain_extent = { + static_cast(fbWidth), + static_cast(fbHeight) + }; + + const VkSwapchainCreateInfoKHR sci{ + .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .surface = surface, + .minImageCount = 2, + .imageFormat = swapchain_format.format, + .imageColorSpace = swapchain_format.colorSpace, + .imageExtent = swapchain_extent, + .imageArrayLayers = 1, + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, + .preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR, + .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + .presentMode = VK_PRESENT_MODE_FIFO_KHR, + .clipped = VK_TRUE + }; + + vkCreateSwapchainKHR(device, &sci, nullptr, &swapchain); + + uint32_t imgCount; + vkGetSwapchainImagesKHR(device, swapchain, &imgCount, nullptr); + images = std::vector(imgCount); + vkGetSwapchainImagesKHR(device, swapchain, &imgCount, images.data()); + + imageLayouts = std::vector( + imgCount, + VK_IMAGE_LAYOUT_UNDEFINED + ); + + imageViews = std::vector(imgCount); + + for (uint32_t i = 0; i < imgCount; i++) { + VkImageViewCreateInfo ivci{}; + ivci.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + ivci.image = images[i]; + ivci.viewType = VK_IMAGE_VIEW_TYPE_2D; + ivci.format = swapchain_format.format; // must match swapchain format + ivci.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + ivci.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + ivci.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + ivci.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + ivci.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + ivci.subresourceRange.baseMipLevel = 0; + ivci.subresourceRange.levelCount = 1; + ivci.subresourceRange.baseArrayLayer = 0; + ivci.subresourceRange.layerCount = 1; + + vkCreateImageView(device, &ivci, nullptr, &imageViews[i]); + } +} diff --git a/renderer/swapchain.h b/renderer/swapchain.h new file mode 100644 index 0000000..7ce5cef --- /dev/null +++ b/renderer/swapchain.h @@ -0,0 +1,24 @@ +// +// Created by Vicente Ferrari Smith on 13.02.26. +// + +#ifndef V_SWAPCHAIN_H +#define V_SWAPCHAIN_H + +#include "init.h" +#include + +inline VkSwapchainKHR swapchain; +inline VkExtent2D swapchain_extent; +inline VkSurfaceFormatKHR swapchain_format{ + VK_FORMAT_B8G8R8A8_UNORM, + VK_COLOR_SPACE_SRGB_NONLINEAR_KHR +}; + +inline std::vector images; +inline std::vector imageViews; +inline std::vector imageLayouts; + +void createSwapchain(GLFWwindow* window); + +#endif //V_SWAPCHAIN_H \ No newline at end of file diff --git a/renderer/texture.cpp b/renderer/texture.cpp new file mode 100644 index 0000000..4367ddc --- /dev/null +++ b/renderer/texture.cpp @@ -0,0 +1,30 @@ +// +// Created by Vicente Ferrari Smith on 14.02.26. +// + +#include "texture.h" +#include +#include "renderer.h" + +TextureManager::TextureManager() { + +} + +uint32_t 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]; + + int w, h, ch; + 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); + + stbi_image_free(data); + + uint32_t id = static_cast(textures.size()); + textures[path] = res; + // path_to_id[path] = id; + + return id; // This is the textureID for your sprites +} diff --git a/renderer/texture.h b/renderer/texture.h new file mode 100644 index 0000000..0f69b53 --- /dev/null +++ b/renderer/texture.h @@ -0,0 +1,45 @@ +// +// Created by Vicente Ferrari Smith on 14.02.26. +// + +#ifndef V_TEXTURE_H +#define V_TEXTURE_H + +#include +#include +#include +#include + +struct Renderer; + +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; + int32_t height; + int32_t channels; + + bool srgb; + bool uploaded; + + VkImage image; + VmaAllocation allocation; + VkImageView view; + uint32_t descriptor_index; +}; + +struct TextureManager { + std::unordered_map textures; + + TextureManager(); + uint32_t load(const std::string& path, Renderer &renderer); +}; + +inline TextureManager texture_manager; + +#endif //V_TEXTURE_H \ No newline at end of file diff --git a/renderer/texture_sheet.cpp b/renderer/texture_sheet.cpp new file mode 100644 index 0000000..1d4075c --- /dev/null +++ b/renderer/texture_sheet.cpp @@ -0,0 +1,5 @@ +// +// Created by Vicente Ferrari Smith on 14.02.26. +// + +#include "texture_sheet.h" \ No newline at end of file diff --git a/renderer/texture_sheet.h b/renderer/texture_sheet.h new file mode 100644 index 0000000..8decc73 --- /dev/null +++ b/renderer/texture_sheet.h @@ -0,0 +1,52 @@ +// +// Created by Vicente Ferrari Smith on 14.02.26. +// + +#ifndef V_TEXTURE_SHEET_H +#define V_TEXTURE_SHEET_H + +#include +#include + +#include "texture.h" + +constexpr std::string TEXTURE_SHEETS_PATH = "data/texture_sheets"; + +typedef std::string texture_cell_id; +typedef std::string texture_sheet_id; + +struct sheet_cell_id { + texture_sheet_id sheet_id; + texture_cell_id cell_id; +}; + +struct TextureCell { + texture_cell_id name; + + int64_t cell_x; + int64_t cell_y; + + glm::vec2 st0; + glm::vec2 st1 = {1.0, 1.0}; +}; + +struct TextureSheet { + texture_sheet_id name; + texture_id texture; + + int64_t cell_width; + int64_t cell_height; + + int64_t columns; + int64_t rows; + + std::vector cells; +}; + +struct TextureSheetManager { + std::unordered_map sheets; +}; + +inline TextureSheetManager texture_sheet_manager; + +#endif //V_TEXTURE_SHEET_H \ No newline at end of file diff --git a/shaders/triangle.frag b/shaders/triangle.frag new file mode 100644 index 0000000..85fb0ad --- /dev/null +++ b/shaders/triangle.frag @@ -0,0 +1,15 @@ +#version 460 +#extension GL_EXT_nonuniform_qualifier : require + +layout(set = 0, binding = 0) uniform sampler2D texSamplers[]; + +layout(location = 0) in vec2 uv; +layout(location = 1) in vec4 color; +layout(location = 2) in flat uint tex_id; + +layout(location = 0) out vec4 out_color; + +void main() { +// outColor = texture(texSamplers[nonuniformEXT(tex_id)], uv) * color; + out_color = color; +} diff --git a/shaders/triangle.frag.spv b/shaders/triangle.frag.spv new file mode 100644 index 0000000..9cefaee Binary files /dev/null and b/shaders/triangle.frag.spv differ diff --git a/shaders/triangle.vert b/shaders/triangle.vert new file mode 100644 index 0000000..4c24852 --- /dev/null +++ b/shaders/triangle.vert @@ -0,0 +1,40 @@ +#version 460 +#extension GL_EXT_nonuniform_qualifier : require + +struct Vertex { + vec2 pos; + vec2 uv; + vec4 color; + float alpha; + uint textureID; +}; + +layout(set = 0, binding = 0) uniform sampler2D globalTextures[]; + +layout(std430, set = 0, binding = 1) readonly buffer VertexBuffer { + Vertex vertices[]; +} vBuf; + +layout(location = 0) out vec2 uv; +layout(location = 1) out vec4 color; +layout(location = 2) out flat uint tex_id; + +void main() { + + Vertex testVertices[6] = Vertex[]( + Vertex(vec2(-0.5, -0.5), vec2(0.0, 0.0), vec4(1.0, 0.0, 0.0, 1.0), 1.0, 0), + Vertex(vec2( 0.5, -0.5), vec2(1.0, 0.0), vec4(0.0, 1.0, 0.0, 1.0), 1.0, 0), + Vertex(vec2(-0.5, 0.5), vec2(0.0, 1.0), vec4(0.0, 0.0, 1.0, 1.0), 1.0, 0), + + Vertex(vec2( 0.5, 0.5), vec2(1.0, 1.0), vec4(1.0, 1.0, 1.0, 1.0), 1.0, 0), + Vertex(vec2(-0.5, 0.5), vec2(0.0, 1.0), vec4(0.0, 0.0, 1.0, 1.0), 1.0, 0), + Vertex(vec2( 0.5, -0.5), vec2(1.0, 1.0), vec4(0.0, 1.0, 0.0, 1.0), 1.0, 0) + ); + + Vertex v = vBuf.vertices[gl_VertexIndex]; + + uv = v.uv; + color = v.color; + tex_id = v.textureID; + gl_Position = vec4(v.pos, 0.0, 1.0); +} diff --git a/shaders/triangle.vert.spv b/shaders/triangle.vert.spv new file mode 100644 index 0000000..062e3ad Binary files /dev/null and b/shaders/triangle.vert.spv differ