initial commit

This commit is contained in:
vfs committed 2026-02-16 11:41:13 +01:00
commit 34789e806b
31 files changed
+2244

No files matched your search

+249
View File
@@ -0,0 +1,249 @@
//
// Created by Vicente Ferrari Smith on 12.02.26.
//
#include "init.h"
#include <print>
#include <vector>
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<unsigned long long>(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<const char*> 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<uint32_t>(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<VkPhysicalDevice> 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<VkQueueFamilyProperties> 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);
}
+29
View File
@@ -0,0 +1,29 @@
//
// Created by Vicente Ferrari Smith on 12.02.26.
//
#ifndef V_INIT_H
#define V_INIT_H
#include <volk/volk.h>
#include <GLFW/glfw3.h>
#include <vma/vk_mem_alloc.h>
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
+723
View File
@@ -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<vertex_p2_st2_col4_a1_u32>(
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<VkDescriptorSet> 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, &copyRegion);
// 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<void(VkCommandBuffer)>&& 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!");
}
}
+371
View File
@@ -0,0 +1,371 @@
//
// Created by Vicente Ferrari Smith on 13.02.26.
//
#ifndef V_RENDERER_H
#define V_RENDERER_H
#include <volk/volk.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <vma/vk_mem_alloc.h>
#include "sprite.h"
#include "texture.h"
#include <misc.h>
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<VkVertexInputAttributeDescription> 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<RenderCommand> 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<void(VkCommandBuffer)>&& 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<VkDescriptorSet> 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 <typename T>
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<uint32_t*>(vsCode.data());
VkShaderModule vs;
vkCreateShaderModule(device, &smci, nullptr, &vs);
smci.codeSize = fsCode.size();
smci.pCode = reinterpret_cast<uint32_t*>(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
+5
View File
@@ -0,0 +1,5 @@
//
// Created by Vicente Ferrari Smith on 14.02.26.
//
#include "sprite.h"
+24
View File
@@ -0,0 +1,24 @@
//
// Created by Vicente Ferrari Smith on 14.02.26.
//
#ifndef V_SPRITE_H
#define V_SPRITE_H
#include <glm/glm.hpp>
#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
+64
View File
@@ -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<uint32_t>(fbWidth),
static_cast<uint32_t>(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<VkImage>(imgCount);
vkGetSwapchainImagesKHR(device, swapchain, &imgCount, images.data());
imageLayouts = std::vector<VkImageLayout>(
imgCount,
VK_IMAGE_LAYOUT_UNDEFINED
);
imageViews = std::vector<VkImageView>(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]);
}
}
+24
View File
@@ -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 <vector>
inline VkSwapchainKHR swapchain;
inline VkExtent2D swapchain_extent;
inline VkSurfaceFormatKHR swapchain_format{
VK_FORMAT_B8G8R8A8_UNORM,
VK_COLOR_SPACE_SRGB_NONLINEAR_KHR
};
inline std::vector<VkImage> images;
inline std::vector<VkImageView> imageViews;
inline std::vector<VkImageLayout> imageLayouts;
void createSwapchain(GLFWwindow* window);
#endif //V_SWAPCHAIN_H
+30
View File
@@ -0,0 +1,30 @@
//
// Created by Vicente Ferrari Smith on 14.02.26.
//
#include "texture.h"
#include <stb_image.h>
#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<uint32_t>(textures.size());
textures[path] = res;
// path_to_id[path] = id;
return id; // This is the textureID for your sprites
}
+45
View File
@@ -0,0 +1,45 @@
//
// Created by Vicente Ferrari Smith on 14.02.26.
//
#ifndef V_TEXTURE_H
#define V_TEXTURE_H
#include <string>
#include <unordered_map>
#include <volk/volk.h>
#include <vma/vk_mem_alloc.h>
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<texture_id, Texture> textures;
TextureManager();
uint32_t load(const std::string& path, Renderer &renderer);
};
inline TextureManager texture_manager;
#endif //V_TEXTURE_H
+5
View File
@@ -0,0 +1,5 @@
//
// Created by Vicente Ferrari Smith on 14.02.26.
//
#include "texture_sheet.h"
+52
View File
@@ -0,0 +1,52 @@
//
// Created by Vicente Ferrari Smith on 14.02.26.
//
#ifndef V_TEXTURE_SHEET_H
#define V_TEXTURE_SHEET_H
#include <string>
#include <glm/glm.hpp>
#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<TextureCell> cells;
};
struct TextureSheetManager {
std::unordered_map<texture_sheet_id, TextureSheet> sheets;
};
inline TextureSheetManager texture_sheet_manager;
#endif //V_TEXTURE_SHEET_H