| git.druid.rocks | index | militantprogrammer | astral_canvas | src/ | render/ | render.c |
src/render/render.c
#include "render/render.h"
#include "render/font.h"
#include "lib/error.h"
#include "hud.vert.spv.h"
#include "hud.frag.spv.h"
#include "text.vert.spv.h"
#include "text.frag.spv.h"
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
static uint32_t packRgba(uint32_t argb)
{
uint32_t alpha = (argb >> 24) & 0xffu;
uint32_t red = (argb >> 16) & 0xffu;
uint32_t green = (argb >> 8) & 0xffu;
uint32_t blue = argb & 0xffu;
if (alpha == 0)
alpha = 0xffu;
return red | (green << 8) | (blue << 16) | (alpha << 24);
}
static VkShaderModule createShader(VkDevice device, const uint32_t *code, size_t bytes)
{
VkShaderModule shaderModule = VK_NULL_HANDLE;
VkShaderModuleCreateInfo shaderCreateInfo = {
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.codeSize = bytes,
.pCode = code,
};
if (vkCreateShaderModule(device, &shaderCreateInfo, NULL, &shaderModule) != VK_SUCCESS)
return VK_NULL_HANDLE;
return shaderModule;
}
static int makePipeline(Renderer *renderer, VkPipeline *outPipeline,
const uint32_t *vertexCode, size_t vertexSize,
const uint32_t *fragmentCode, size_t fragmentSize,
const VkPipelineVertexInputStateCreateInfo *vertexInput,
bool blend)
{
VkDevice device = renderer->vulkan.device;
VkShaderModule vertexModule = createShader(device, vertexCode, vertexSize);
VkShaderModule fragmentModule = createShader(device, fragmentCode, fragmentSize);
if (!vertexModule || !fragmentModule) {
fprintf(stderr, "vulkan: shader module failed\n");
if (vertexModule)
vkDestroyShaderModule(device, vertexModule, NULL);
if (fragmentModule)
vkDestroyShaderModule(device, fragmentModule, NULL);
return -1;
}
VkPipelineShaderStageCreateInfo stages[2] = {
{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_VERTEX_BIT,
.module = vertexModule,
.pName = "main",
},
{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
.module = fragmentModule,
.pName = "main",
},
};
VkPipelineVertexInputStateCreateInfo emptyVertexInput = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
};
VkPipelineInputAssemblyStateCreateInfo inputAssembly = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
};
VkPipelineViewportStateCreateInfo viewportState = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.viewportCount = 1,
.scissorCount = 1,
};
VkPipelineRasterizationStateCreateInfo rasterization = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
.polygonMode = VK_POLYGON_MODE_FILL,
.cullMode = VK_CULL_MODE_NONE,
.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
.lineWidth = 1.0f,
};
VkPipelineMultisampleStateCreateInfo multisample = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
};
VkPipelineColorBlendAttachmentState blendAttachment = {
.blendEnable = blend ? 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_ONE_MINUS_SRC_ALPHA,
.alphaBlendOp = VK_BLEND_OP_ADD,
.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
};
VkPipelineColorBlendStateCreateInfo colorBlend = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
.attachmentCount = 1,
.pAttachments = &blendAttachment,
};
VkDynamicState dynamicStates[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
VkPipelineDynamicStateCreateInfo dynamicState = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
.dynamicStateCount = 2,
.pDynamicStates = dynamicStates,
};
VkGraphicsPipelineCreateInfo graphicsPipelineCreateInfo = {
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.stageCount = 2,
.pStages = stages,
.pVertexInputState = vertexInput ? vertexInput : &emptyVertexInput,
.pInputAssemblyState = &inputAssembly,
.pViewportState = &viewportState,
.pRasterizationState = &rasterization,
.pMultisampleState = &multisample,
.pColorBlendState = &colorBlend,
.pDynamicState = &dynamicState,
.layout = renderer->pipelineLayout,
.renderPass = renderer->vulkan.renderPass,
.subpass = 0,
};
VkResult result = vkCreateGraphicsPipelines(
device, VK_NULL_HANDLE, 1, &graphicsPipelineCreateInfo, NULL, outPipeline);
vkDestroyShaderModule(device, vertexModule, NULL);
vkDestroyShaderModule(device, fragmentModule, NULL);
if (result != VK_SUCCESS) {
fprintf(stderr, "vulkan: vkCreateGraphicsPipelines failed (%d)\n", (int)result);
return -1;
}
return 0;
}
static void destroyPipelines(Renderer *renderer)
{
VkDevice device = renderer->vulkan.device;
if (!device)
return;
if (renderer->hudPipeline) {
vkDestroyPipeline(device, renderer->hudPipeline, NULL);
renderer->hudPipeline = VK_NULL_HANDLE;
}
if (renderer->textPipeline) {
vkDestroyPipeline(device, renderer->textPipeline, NULL);
renderer->textPipeline = VK_NULL_HANDLE;
}
}
static int createPipelines(Renderer *renderer)
{
destroyPipelines(renderer);
if (makePipeline(renderer, &renderer->hudPipeline, hud_vert_spv, sizeof(hud_vert_spv),
hud_frag_spv, sizeof(hud_frag_spv), NULL, false) < 0)
return -1;
VkVertexInputBindingDescription bindingDescription = {
.binding = 0,
.stride = sizeof(GlyphVertex),
.inputRate = VK_VERTEX_INPUT_RATE_INSTANCE,
};
VkVertexInputAttributeDescription attributes[3] = {
{ .location = 0, .binding = 0, .format = VK_FORMAT_R32G32_SFLOAT,
.offset = offsetof(GlyphVertex, x) },
{ .location = 1, .binding = 0, .format = VK_FORMAT_R8G8B8A8_UNORM,
.offset = offsetof(GlyphVertex, rgba) },
{ .location = 2, .binding = 0, .format = VK_FORMAT_R32_UINT,
.offset = offsetof(GlyphVertex, character) },
};
VkPipelineVertexInputStateCreateInfo vertexInput = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
.vertexBindingDescriptionCount = 1,
.pVertexBindingDescriptions = &bindingDescription,
.vertexAttributeDescriptionCount = 3,
.pVertexAttributeDescriptions = attributes,
};
if (makePipeline(renderer, &renderer->textPipeline, text_vert_spv, sizeof(text_vert_spv),
text_frag_spv, sizeof(text_frag_spv), &vertexInput, true) < 0)
return -1;
return 0;
}
static int recreateForResize(Renderer *renderer, Platform *platform)
{
if (vk_recreate_swapchain(&renderer->vulkan, platform) < 0)
return -1;
return createPipelines(renderer);
}
static int createFontAtlas(Renderer *renderer)
{
unsigned char pixels[FONT_ATLAS_W * FONT_ATLAS_H];
memset(pixels, 0, sizeof(pixels));
for (int glyphIndex = 0; glyphIndex < FONT_COUNT; glyphIndex++) {
const unsigned char *glyph = font_glyph((unsigned char)(FONT_FIRST + glyphIndex));
int column = glyphIndex % FONT_ATLAS_COLS;
int row = glyphIndex / FONT_ATLAS_COLS;
for (int y = 0; y < FONT_GLYPH_H; y++) {
unsigned char bits = glyph[y];
for (int x = 0; x < FONT_GLYPH_W; x++) {
if (bits & (1u << x))
pixels[(row * FONT_GLYPH_H + y) * FONT_ATLAS_W + column * FONT_GLYPH_W + x] = 255;
}
}
}
VkContext *vulkan = &renderer->vulkan;
VkBuffer stagingBuffer = VK_NULL_HANDLE;
VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
void *mapped = NULL;
if (vk_create_host_buffer(vulkan, sizeof(pixels), VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
&stagingBuffer, &stagingMemory, &mapped) < 0)
return -1;
memcpy(mapped, pixels, sizeof(pixels));
VkImageCreateInfo imageCreateInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = VK_FORMAT_R8_UNORM,
.extent = { FONT_ATLAS_W, FONT_ATLAS_H, 1 },
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
};
VK_TRY(vkCreateImage(vulkan->device, &imageCreateInfo, NULL, &renderer->fontImage));
VkMemoryRequirements memoryRequirements;
vkGetImageMemoryRequirements(vulkan->device, renderer->fontImage, &memoryRequirements);
int memoryTypeIndex = vk_find_memory_type(
vulkan->physicalDevice, memoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
if (memoryTypeIndex < 0)
memoryTypeIndex = vk_find_memory_type(vulkan->physicalDevice, memoryRequirements.memoryTypeBits, 0);
if (memoryTypeIndex < 0)
return -1;
VkMemoryAllocateInfo allocateInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memoryRequirements.size,
.memoryTypeIndex = (uint32_t)memoryTypeIndex,
};
VK_TRY(vkAllocateMemory(vulkan->device, &allocateInfo, NULL, &renderer->fontMemory));
VK_TRY(vkBindImageMemory(vulkan->device, renderer->fontImage, renderer->fontMemory, 0));
VkImageViewCreateInfo imageViewCreateInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = renderer->fontImage,
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = VK_FORMAT_R8_UNORM,
.subresourceRange = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.levelCount = 1,
.layerCount = 1,
},
};
VK_TRY(vkCreateImageView(vulkan->device, &imageViewCreateInfo, NULL, &renderer->fontView));
VkCommandBufferAllocateInfo commandBufferAllocateInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = vulkan->commandPool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1,
};
VkCommandBuffer commandBuffer;
VK_TRY(vkAllocateCommandBuffers(vulkan->device, &commandBufferAllocateInfo, &commandBuffer));
VkCommandBufferBeginInfo beginInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
};
VK_TRY(vkBeginCommandBuffer(commandBuffer, &beginInfo));
vk_image_barrier(commandBuffer, renderer->fontImage, VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
0, VK_ACCESS_TRANSFER_WRITE_BIT,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
VkBufferImageCopy copy = {
.imageSubresource = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .layerCount = 1 },
.imageExtent = { FONT_ATLAS_W, FONT_ATLAS_H, 1 },
};
vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, renderer->fontImage,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©);
vk_image_barrier(commandBuffer, renderer->fontImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
VK_TRY(vkEndCommandBuffer(commandBuffer));
VkSubmitInfo submitInfo = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.commandBufferCount = 1,
.pCommandBuffers = &commandBuffer,
};
VK_TRY(vkQueueSubmit(vulkan->graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE));
VK_TRY(vkQueueWaitIdle(vulkan->graphicsQueue));
vkFreeCommandBuffers(vulkan->device, vulkan->commandPool, 1, &commandBuffer);
vkUnmapMemory(vulkan->device, stagingMemory);
vkDestroyBuffer(vulkan->device, stagingBuffer, NULL);
vkFreeMemory(vulkan->device, stagingMemory, NULL);
return 0;
}
static int createRenderResources(Renderer *renderer)
{
VkContext *vulkan = &renderer->vulkan;
VkSamplerCreateInfo samplerCreateInfo = {
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.magFilter = VK_FILTER_NEAREST,
.minFilter = VK_FILTER_NEAREST,
.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST,
.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
.maxLod = 0.0f,
};
VK_TRY(vkCreateSampler(vulkan->device, &samplerCreateInfo, NULL, &renderer->sampler));
VkDescriptorSetLayoutBinding bindings[2] = {
{
.binding = 0,
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
},
{
.binding = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
},
};
VkDescriptorSetLayoutCreateInfo setLayoutCreateInfo = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.bindingCount = 2,
.pBindings = bindings,
};
VK_TRY(vkCreateDescriptorSetLayout(
vulkan->device, &setLayoutCreateInfo, NULL, &renderer->descriptorSetLayout));
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.setLayoutCount = 1,
.pSetLayouts = &renderer->descriptorSetLayout,
};
VK_TRY(vkCreatePipelineLayout(
vulkan->device, &pipelineLayoutCreateInfo, NULL, &renderer->pipelineLayout));
VkDescriptorPoolSize poolSizes[2] = {
{ .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = VK_MAX_FRAMES },
{ .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = VK_MAX_FRAMES },
};
VkDescriptorPoolCreateInfo descriptorPoolCreateInfo = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
.maxSets = VK_MAX_FRAMES,
.poolSizeCount = 2,
.pPoolSizes = poolSizes,
};
VK_TRY(vkCreateDescriptorPool(
vulkan->device, &descriptorPoolCreateInfo, NULL, &renderer->descriptorPool));
if (createFontAtlas(renderer) < 0)
return -1;
if (createPipelines(renderer) < 0)
return -1;
VkDescriptorSetLayout layouts[VK_MAX_FRAMES];
for (int i = 0; i < VK_MAX_FRAMES; i++)
layouts[i] = renderer->descriptorSetLayout;
VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
.descriptorPool = renderer->descriptorPool,
.descriptorSetCount = VK_MAX_FRAMES,
.pSetLayouts = layouts,
};
VkDescriptorSet descriptorSets[VK_MAX_FRAMES];
VK_TRY(vkAllocateDescriptorSets(vulkan->device, &descriptorSetAllocateInfo, descriptorSets));
for (int i = 0; i < VK_MAX_FRAMES; i++) {
renderer->frames[i].descriptorSet = descriptorSets[i];
if (vk_create_host_buffer(vulkan, sizeof(SceneUniform), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
&renderer->frames[i].uniformBuffer,
&renderer->frames[i].uniformBufferMemory,
&renderer->frames[i].uniformBufferMapped) < 0)
return -1;
if (vk_create_host_buffer(vulkan, sizeof(GlyphVertex) * HUD_MAX_GLYPHS,
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
&renderer->frames[i].glyphBuffer,
&renderer->frames[i].glyphBufferMemory,
&renderer->frames[i].glyphBufferMapped) < 0)
return -1;
VkDescriptorBufferInfo uniformBufferInfo = {
.buffer = renderer->frames[i].uniformBuffer,
.range = sizeof(SceneUniform),
};
VkDescriptorImageInfo imageInfo = {
.sampler = renderer->sampler,
.imageView = renderer->fontView,
.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
};
VkWriteDescriptorSet writes[2] = {
{
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstSet = renderer->frames[i].descriptorSet,
.dstBinding = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
.pBufferInfo = &uniformBufferInfo,
},
{
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstSet = renderer->frames[i].descriptorSet,
.dstBinding = 1,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.pImageInfo = &imageInfo,
},
};
vkUpdateDescriptorSets(vulkan->device, 2, writes, 0, NULL);
}
return 0;
}
static int presentVulkan(Renderer *renderer, Platform *platform, const Hud *hud)
{
VkContext *vulkan = &renderer->vulkan;
if (!vulkan->ready || !hud)
return 0;
uint32_t wantedWidth = (uint32_t)(platform->bufferWidth > 0 ? platform->bufferWidth : 1);
uint32_t wantedHeight = (uint32_t)(platform->bufferHeight > 0 ? platform->bufferHeight : 1);
VkSurfaceCapabilitiesKHR surfaceCapabilities;
if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
vulkan->physicalDevice, vulkan->surface, &surfaceCapabilities) == VK_SUCCESS &&
surfaceCapabilities.currentExtent.width != UINT32_MAX) {
wantedWidth = surfaceCapabilities.currentExtent.width;
wantedHeight = surfaceCapabilities.currentExtent.height;
}
if (wantedWidth != vulkan->extent.width || wantedHeight != vulkan->extent.height) {
if (recreateForResize(renderer, platform) < 0)
return -1;
}
VkFrame *frame = &vulkan->frames[vulkan->frameIndex];
RenderFrame *renderFrame = &renderer->frames[vulkan->frameIndex];
VK_TRY(vkWaitForFences(vulkan->device, 1, &frame->inFlight, VK_TRUE, UINT64_MAX));
uint32_t imageIndex = 0;
VkResult acquireResult = vkAcquireNextImageKHR(
vulkan->device, vulkan->swapchain, UINT64_MAX,
frame->imageAvailable, VK_NULL_HANDLE, &imageIndex);
if (acquireResult == VK_ERROR_OUT_OF_DATE_KHR)
return recreateForResize(renderer, platform);
if (acquireResult != VK_SUCCESS && acquireResult != VK_SUBOPTIMAL_KHR) {
fprintf(stderr, "vulkan: vkAcquireNextImageKHR failed (%d)\n", (int)acquireResult);
return -1;
}
if (imageIndex >= vulkan->swapchainImageCount)
return -1;
if (vulkan->swapchainBusyFences[imageIndex] != VK_NULL_HANDLE)
VK_TRY(vkWaitForFences(vulkan->device, 1,
&vulkan->swapchainBusyFences[imageIndex], VK_TRUE, UINT64_MAX));
vulkan->swapchainBusyFences[imageIndex] = frame->inFlight;
VK_TRY(vkResetFences(vulkan->device, 1, &frame->inFlight));
SceneUniform uniform = {
.resolution = { (float)vulkan->extent.width, (float)vulkan->extent.height },
.scale = platform->scale,
.hudHeight = hud->hudHeight,
.cellWidth = hud->cellWidth,
.cellHeight = hud->cellHeight,
.columns = hud->columns,
.rows = hud->rows,
.hoverColumn = hud->hoverColumn,
.hoverRow = hud->hoverRow,
.hover = hud->hover,
};
memcpy(uniform.background, hud->background, sizeof(uniform.background));
memcpy(uniform.grid, hud->grid, sizeof(uniform.grid));
memcpy(uniform.hoverColor, hud->hoverColor, sizeof(uniform.hoverColor));
memcpy(uniform.bar, hud->bar, sizeof(uniform.bar));
memcpy(renderFrame->uniformBufferMapped, &uniform, sizeof(uniform));
int glyphCount = hud->glyphCount;
if (glyphCount < 0)
glyphCount = 0;
if (glyphCount > HUD_MAX_GLYPHS)
glyphCount = HUD_MAX_GLYPHS;
GlyphVertex *glyphVertices = (GlyphVertex *)renderFrame->glyphBufferMapped;
for (int i = 0; i < glyphCount; i++) {
glyphVertices[i].x = hud->glyphs[i].x;
glyphVertices[i].y = hud->glyphs[i].y;
glyphVertices[i].rgba = packRgba(hud->glyphs[i].color);
glyphVertices[i].character = hud->glyphs[i].character;
}
VK_TRY(vkResetCommandBuffer(frame->commandBuffer, 0));
VkCommandBufferBeginInfo beginInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
};
VK_TRY(vkBeginCommandBuffer(frame->commandBuffer, &beginInfo));
VkClearValue clearValue = { .color = { { 0.0f, 0.0f, 0.0f, 1.0f } } };
VkRenderPassBeginInfo renderPassBeginInfo = {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
.renderPass = vulkan->renderPass,
.framebuffer = vulkan->swapchainFramebuffers[imageIndex],
.renderArea = { .extent = vulkan->extent },
.clearValueCount = 1,
.pClearValues = &clearValue,
};
vkCmdBeginRenderPass(frame->commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
VkViewport viewport = {
.width = (float)vulkan->extent.width,
.height = (float)vulkan->extent.height,
.minDepth = 0.0f,
.maxDepth = 1.0f,
};
VkRect2D scissor = { .extent = vulkan->extent };
vkCmdSetViewport(frame->commandBuffer, 0, 1, &viewport);
vkCmdSetScissor(frame->commandBuffer, 0, 1, &scissor);
vkCmdBindPipeline(frame->commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, renderer->hudPipeline);
vkCmdBindDescriptorSets(frame->commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
renderer->pipelineLayout, 0, 1, &renderFrame->descriptorSet, 0, NULL);
vkCmdDraw(frame->commandBuffer, 3, 1, 0, 0);
if (glyphCount > 0) {
VkDeviceSize offset = 0;
vkCmdBindPipeline(frame->commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, renderer->textPipeline);
vkCmdBindDescriptorSets(frame->commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
renderer->pipelineLayout, 0, 1, &renderFrame->descriptorSet, 0, NULL);
vkCmdBindVertexBuffers(frame->commandBuffer, 0, 1, &renderFrame->glyphBuffer, &offset);
vkCmdDraw(frame->commandBuffer, 6, (uint32_t)glyphCount, 0, 0);
}
vkCmdEndRenderPass(frame->commandBuffer);
VK_TRY(vkEndCommandBuffer(frame->commandBuffer));
VkPipelineStageFlags waitStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
VkSubmitInfo submitInfo = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.waitSemaphoreCount = 1,
.pWaitSemaphores = &frame->imageAvailable,
.pWaitDstStageMask = &waitStage,
.commandBufferCount = 1,
.pCommandBuffers = &frame->commandBuffer,
.signalSemaphoreCount = 1,
.pSignalSemaphores = &frame->renderFinished,
};
VK_TRY(vkQueueSubmit(vulkan->graphicsQueue, 1, &submitInfo, frame->inFlight));
VkPresentInfoKHR presentInfo = {
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
.waitSemaphoreCount = 1,
.pWaitSemaphores = &frame->renderFinished,
.swapchainCount = 1,
.pSwapchains = &vulkan->swapchain,
.pImageIndices = &imageIndex,
};
VkResult presentResult = vkQueuePresentKHR(vulkan->presentQueue, &presentInfo);
if (presentResult == VK_ERROR_OUT_OF_DATE_KHR || presentResult == VK_SUBOPTIMAL_KHR) {
if (recreateForResize(renderer, platform) < 0)
return -1;
} else if (presentResult != VK_SUCCESS) {
fprintf(stderr, "vulkan: vkQueuePresentKHR failed (%d)\n", (int)presentResult);
return -1;
}
vulkan->frameIndex = (vulkan->frameIndex + 1) % VK_MAX_FRAMES;
return 0;
}
int rendererInit(Renderer *renderer, Platform *platform)
{
memset(renderer, 0, sizeof(*renderer));
if (vk_init(&renderer->vulkan, platform) != 0) {
canvasErrorSet("Vulkan instance failed within lib");
return -1;
}
if (createRenderResources(renderer) != 0) {
canvasErrorSet("Vulkan pipeline failed within lib");
vk_destroy(&renderer->vulkan);
memset(renderer, 0, sizeof(*renderer));
platform->gpuPresent = false;
return -1;
}
renderer->useVulkan = true;
return 0;
}
int rendererFrame(Renderer *renderer, Platform *platform, const Hud *hud)
{
return presentVulkan(renderer, platform, hud);
}
void rendererDestroy(Renderer *renderer)
{
if (!renderer)
return;
if (renderer->useVulkan && renderer->vulkan.device) {
vkDeviceWaitIdle(renderer->vulkan.device);
destroyPipelines(renderer);
for (int i = 0; i < VK_MAX_FRAMES; i++) {
if (renderer->frames[i].glyphBufferMapped && renderer->frames[i].glyphBufferMemory)
vkUnmapMemory(renderer->vulkan.device, renderer->frames[i].glyphBufferMemory);
if (renderer->frames[i].uniformBufferMapped && renderer->frames[i].uniformBufferMemory)
vkUnmapMemory(renderer->vulkan.device, renderer->frames[i].uniformBufferMemory);
if (renderer->frames[i].glyphBuffer)
vkDestroyBuffer(renderer->vulkan.device, renderer->frames[i].glyphBuffer, NULL);
if (renderer->frames[i].glyphBufferMemory)
vkFreeMemory(renderer->vulkan.device, renderer->frames[i].glyphBufferMemory, NULL);
if (renderer->frames[i].uniformBuffer)
vkDestroyBuffer(renderer->vulkan.device, renderer->frames[i].uniformBuffer, NULL);
if (renderer->frames[i].uniformBufferMemory)
vkFreeMemory(renderer->vulkan.device, renderer->frames[i].uniformBufferMemory, NULL);
}
if (renderer->fontView)
vkDestroyImageView(renderer->vulkan.device, renderer->fontView, NULL);
if (renderer->fontImage)
vkDestroyImage(renderer->vulkan.device, renderer->fontImage, NULL);
if (renderer->fontMemory)
vkFreeMemory(renderer->vulkan.device, renderer->fontMemory, NULL);
if (renderer->descriptorPool)
vkDestroyDescriptorPool(renderer->vulkan.device, renderer->descriptorPool, NULL);
if (renderer->pipelineLayout)
vkDestroyPipelineLayout(renderer->vulkan.device, renderer->pipelineLayout, NULL);
if (renderer->descriptorSetLayout)
vkDestroyDescriptorSetLayout(renderer->vulkan.device, renderer->descriptorSetLayout, NULL);
if (renderer->sampler)
vkDestroySampler(renderer->vulkan.device, renderer->sampler, NULL);
vk_destroy(&renderer->vulkan);
}
memset(renderer, 0, sizeof(*renderer));
}
void rendererFillClear(Platform *platform, const Style *theme, Mode mode, Hud *hud)
{
float alpha;
memset(hud, 0, sizeof(*hud));
hud->scale = platform && platform->scale > 0 ? (float)platform->scale : 1.0f;
if (!theme)
return;
alpha = theme->backgroundColor.a / 255.0f;
if (mode != MODE_COMPLEX)
alpha = 1.0f;
hud->background[0] = theme->backgroundColor.r / 255.0f;
hud->background[1] = theme->backgroundColor.g / 255.0f;
hud->background[2] = theme->backgroundColor.b / 255.0f;
hud->background[3] = alpha;
}