ScreenLockDetector/src/vulkanrenderer.cpp

2830 lines
113 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "vulkanrenderer.h"
#define VK_NO_PROTOTYPES
#include <volk.h>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
#ifdef ENABLE_FREETYPE
#include <ft2build.h>
#include FT_FREETYPE_H
#endif
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
VulkanRenderer::VulkanRenderer()
: m_device(VK_NULL_HANDLE)
, m_physicalDevice(VK_NULL_HANDLE)
, m_graphicsQueue(VK_NULL_HANDLE)
, m_queueFamilyIndex(0)
, m_transferCommandPool(VK_NULL_HANDLE)
, m_renderPass(VK_NULL_HANDLE)
, m_backgroundPipeline(VK_NULL_HANDLE)
, m_backgroundPipelineLayout(VK_NULL_HANDLE)
, m_geometryPipeline(VK_NULL_HANDLE)
, m_geometryPipelineLayout(VK_NULL_HANDLE)
, m_linePipeline(VK_NULL_HANDLE)
, m_linePipelineLayout(VK_NULL_HANDLE)
, m_textPipeline(VK_NULL_HANDLE)
, m_textPipelineLayout(VK_NULL_HANDLE)
, m_descriptorSetLayout(VK_NULL_HANDLE)
, m_descriptorPool(VK_NULL_HANDLE)
, m_backgroundVertexBuffer(VK_NULL_HANDLE)
, m_backgroundVertexMemory(VK_NULL_HANDLE)
, m_backgroundIndexBuffer(VK_NULL_HANDLE)
, m_backgroundIndexMemory(VK_NULL_HANDLE)
, m_backgroundIndexCount(0)
, m_circleVertexBuffer(VK_NULL_HANDLE)
, m_circleVertexMemory(VK_NULL_HANDLE)
, m_circleIndexBuffer(VK_NULL_HANDLE)
, m_circleIndexMemory(VK_NULL_HANDLE)
, m_circleIndexCount(0)
, m_waveVertexBuffer(VK_NULL_HANDLE)
, m_waveVertexMemory(VK_NULL_HANDLE)
, m_waveIndexBuffer(VK_NULL_HANDLE)
, m_waveIndexMemory(VK_NULL_HANDLE)
, m_waveIndexCount(0)
, m_fontTexture(VK_NULL_HANDLE)
, m_fontTextureMemory(VK_NULL_HANDLE)
, m_fontTextureView(VK_NULL_HANDLE)
, m_fontSampler(VK_NULL_HANDLE)
, m_textVertexBuffer(VK_NULL_HANDLE)
, m_textVertexMemory(VK_NULL_HANDLE)
, m_textIndexBuffer(VK_NULL_HANDLE)
, m_textIndexMemory(VK_NULL_HANDLE)
, m_textIndexCount(0)
, m_msaaImage(VK_NULL_HANDLE)
, m_msaaImageMemory(VK_NULL_HANDLE)
, m_msaaImageView(VK_NULL_HANDLE)
, m_msaaSamples(VK_SAMPLE_COUNT_1_BIT)
, m_width(0)
, m_height(0)
, m_swapchainFormat(0)
, m_initialized(false)
, m_errorCallback(nullptr)
{
memset(&m_ubo, 0, sizeof(m_ubo));
}
VulkanRenderer::~VulkanRenderer()
{
cleanup();
}
bool VulkanRenderer::initialize(VkDevice device, VkPhysicalDevice physicalDevice,
VkQueue graphicsQueue, uint32_t queueFamilyIndex,
uint32_t swapchainFormat, uint32_t width, uint32_t height)
{
m_device = device;
m_physicalDevice = physicalDevice;
m_graphicsQueue = graphicsQueue;
m_queueFamilyIndex = queueFamilyIndex;
m_swapchainFormat = swapchainFormat;
m_width = width;
m_height = height;
m_transferCommandPool = VK_NULL_HANDLE;
// Determine best MSAA sample count
m_msaaSamples = getMaxUsableSampleCount();
std::cout << "Using MSAA with " << m_msaaSamples << " samples" << std::endl;
// Create MSAA resources if MSAA is enabled
if (m_msaaSamples > VK_SAMPLE_COUNT_1_BIT) {
if (!createMSAAResources()) {
logError("Failed to create MSAA resources, falling back to no MSAA");
m_msaaSamples = VK_SAMPLE_COUNT_1_BIT;
}
}
if (!createRenderPass()) {
logError("Failed to create render pass");
return false;
}
if (!createDescriptorSetLayout()) {
logError("Failed to create descriptor set layout");
return false;
}
if (!createBackgroundPipeline()) {
logError("Failed to create background pipeline");
return false;
}
if (!createGeometryPipeline()) {
logError("Failed to create geometry pipeline");
return false;
}
if (!createLinePipeline()) {
logError("Failed to create line pipeline");
return false;
}
if (!createUniformBuffers()) {
logError("Failed to create uniform buffers");
return false;
}
if (!createDescriptorPool()) {
logError("Failed to create descriptor pool");
return false;
}
if (!createDescriptorSets()) {
logError("Failed to create descriptor sets");
return false;
}
// Initialize UBO with current dimensions
m_ubo.time = 0.0f;
m_ubo.resolution[0] = static_cast<float>(m_width);
m_ubo.resolution[1] = static_cast<float>(m_height);
m_ubo.rotation = 0.0f;
m_ubo.wavePhase = 0.0f;
// Update all uniform buffers with initial values
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
if (m_uniformBuffersMapped[i] != nullptr) {
memcpy(m_uniformBuffersMapped[i], &m_ubo, sizeof(m_ubo));
}
}
// Create background geometry
std::vector<Vertex> bgVertices;
std::vector<uint16_t> bgIndices;
generateBackgroundQuad(bgVertices, bgIndices);
m_backgroundIndexCount = bgIndices.size();
if (!createVertexBuffer(bgVertices, m_backgroundVertexBuffer, m_backgroundVertexMemory)) {
logError("Failed to create background vertex buffer");
return false;
}
if (!createIndexBuffer(bgIndices, m_backgroundIndexBuffer, m_backgroundIndexMemory)) {
logError("Failed to create background index buffer");
return false;
}
// Initialize text rendering (optional)
if (initializeTextRendering()) {
std::cout << "Creating text pipeline..." << std::endl;
if (!createTextPipeline()) {
logError("Failed to create text pipeline - text rendering will be disabled");
} else {
// Update descriptor sets to include font texture
std::cout << "Updating descriptor sets with font texture..." << std::endl;
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
VkDescriptorBufferInfo bufferInfo = {};
bufferInfo.buffer = m_uniformBuffers[i];
bufferInfo.offset = 0;
bufferInfo.range = sizeof(UniformBufferObject);
VkDescriptorImageInfo imageInfo = {};
imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
imageInfo.imageView = m_fontTextureView;
imageInfo.sampler = m_fontSampler;
std::vector<VkWriteDescriptorSet> descriptorWrites(2);
// UBO descriptor
descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[0].dstSet = m_descriptorSets[i];
descriptorWrites[0].dstBinding = 0;
descriptorWrites[0].dstArrayElement = 0;
descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptorWrites[0].descriptorCount = 1;
descriptorWrites[0].pBufferInfo = &bufferInfo;
// Font texture descriptor
descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[1].dstSet = m_descriptorSets[i];
descriptorWrites[1].dstBinding = 1;
descriptorWrites[1].dstArrayElement = 0;
descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
descriptorWrites[1].descriptorCount = 1;
descriptorWrites[1].pImageInfo = &imageInfo;
vkUpdateDescriptorSets(m_device, descriptorWrites.size(), descriptorWrites.data(), 0, nullptr);
}
std::cout << "Descriptor sets updated with font texture" << std::endl;
}
} else {
std::cout << "Text rendering initialization failed - text will be disabled" << std::endl;
}
m_initialized = true;
return true;
}
uint32_t VulkanRenderer::getMaxUsableSampleCount()
{
VkPhysicalDeviceProperties physicalDeviceProperties;
vkGetPhysicalDeviceProperties(m_physicalDevice, &physicalDeviceProperties);
VkSampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts &
physicalDeviceProperties.limits.framebufferDepthSampleCounts;
std::cout << "Available MSAA sample counts: ";
if (counts & VK_SAMPLE_COUNT_64_BIT) std::cout << "64 ";
if (counts & VK_SAMPLE_COUNT_32_BIT) std::cout << "32 ";
if (counts & VK_SAMPLE_COUNT_16_BIT) std::cout << "16 ";
if (counts & VK_SAMPLE_COUNT_8_BIT) std::cout << "8 ";
if (counts & VK_SAMPLE_COUNT_4_BIT) std::cout << "4 ";
if (counts & VK_SAMPLE_COUNT_2_BIT) std::cout << "2 ";
std::cout << std::endl;
// Prefer higher sample counts for better quality
if (counts & VK_SAMPLE_COUNT_16_BIT) {
std::cout << "Selected: 16x MSAA" << std::endl;
return VK_SAMPLE_COUNT_16_BIT;
}
if (counts & VK_SAMPLE_COUNT_8_BIT) {
std::cout << "Selected: 8x MSAA" << std::endl;
return VK_SAMPLE_COUNT_8_BIT;
}
if (counts & VK_SAMPLE_COUNT_4_BIT) {
std::cout << "Selected: 4x MSAA" << std::endl;
return VK_SAMPLE_COUNT_4_BIT;
}
if (counts & VK_SAMPLE_COUNT_2_BIT) {
std::cout << "Selected: 2x MSAA" << std::endl;
return VK_SAMPLE_COUNT_2_BIT;
}
std::cout << "No MSAA support available" << std::endl;
return VK_SAMPLE_COUNT_1_BIT;
}
bool VulkanRenderer::createMSAAResources()
{
std::cout << "Creating MSAA resources: " << m_width << "x" << m_height
<< " with " << m_msaaSamples << " samples" << std::endl;
VkImageCreateInfo imageInfo = {};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.extent.width = m_width;
imageInfo.extent.height = m_height;
imageInfo.extent.depth = 1;
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.format = static_cast<VkFormat>(m_swapchainFormat);
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
imageInfo.usage = VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
imageInfo.samples = static_cast<VkSampleCountFlagBits>(m_msaaSamples);
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VkResult result = vkCreateImage(m_device, &imageInfo, nullptr, &m_msaaImage);
if (result != VK_SUCCESS) {
std::cout << "Failed to create MSAA image, error: " << result << std::endl;
logError("Failed to create MSAA image");
return false;
}
std::cout << "MSAA image created successfully" << std::endl;
VkMemoryRequirements memRequirements;
vkGetImageMemoryRequirements(m_device, m_msaaImage, &memRequirements);
std::cout << "MSAA memory requirements: size=" << memRequirements.size
<< " alignment=" << memRequirements.alignment << std::endl;
VkMemoryAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size;
allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
result = vkAllocateMemory(m_device, &allocInfo, nullptr, &m_msaaImageMemory);
if (result != VK_SUCCESS) {
std::cout << "Failed to allocate MSAA image memory, error: " << result << std::endl;
logError("Failed to allocate MSAA image memory");
vkDestroyImage(m_device, m_msaaImage, nullptr);
m_msaaImage = VK_NULL_HANDLE;
return false;
}
std::cout << "MSAA memory allocated successfully" << std::endl;
result = vkBindImageMemory(m_device, m_msaaImage, m_msaaImageMemory, 0);
if (result != VK_SUCCESS) {
std::cout << "Failed to bind MSAA image memory, error: " << result << std::endl;
logError("Failed to bind MSAA image memory");
vkFreeMemory(m_device, m_msaaImageMemory, nullptr);
vkDestroyImage(m_device, m_msaaImage, nullptr);
m_msaaImage = VK_NULL_HANDLE;
m_msaaImageMemory = VK_NULL_HANDLE;
return false;
}
std::cout << "MSAA memory bound successfully" << std::endl;
// Create image view
VkImageViewCreateInfo viewInfo = {};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = m_msaaImage;
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = static_cast<VkFormat>(m_swapchainFormat);
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
viewInfo.subresourceRange.baseMipLevel = 0;
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = 1;
result = vkCreateImageView(m_device, &viewInfo, nullptr, &m_msaaImageView);
if (result != VK_SUCCESS) {
std::cout << "Failed to create MSAA image view, error: " << result << std::endl;
logError("Failed to create MSAA image view");
vkFreeMemory(m_device, m_msaaImageMemory, nullptr);
vkDestroyImage(m_device, m_msaaImage, nullptr);
m_msaaImage = VK_NULL_HANDLE;
m_msaaImageMemory = VK_NULL_HANDLE;
return false;
}
std::cout << "MSAA image view created successfully" << std::endl;
std::cout << "MSAA resources created: image=" << m_msaaImage
<< " view=" << m_msaaImageView << std::endl;
return true;
}
void VulkanRenderer::cleanupMSAAResources()
{
if (m_msaaImageView != VK_NULL_HANDLE) {
vkDestroyImageView(m_device, m_msaaImageView, nullptr);
m_msaaImageView = VK_NULL_HANDLE;
}
if (m_msaaImage != VK_NULL_HANDLE) {
vkDestroyImage(m_device, m_msaaImage, nullptr);
m_msaaImage = VK_NULL_HANDLE;
}
if (m_msaaImageMemory != VK_NULL_HANDLE) {
vkFreeMemory(m_device, m_msaaImageMemory, nullptr);
m_msaaImageMemory = VK_NULL_HANDLE;
}
}
void VulkanRenderer::cleanup()
{
cleanupMSAAResources();
if (!m_device) return;
// Clean up buffers
if (m_backgroundVertexBuffer) vkDestroyBuffer(m_device, m_backgroundVertexBuffer, nullptr);
if (m_backgroundVertexMemory) vkFreeMemory(m_device, m_backgroundVertexMemory, nullptr);
if (m_backgroundIndexBuffer) vkDestroyBuffer(m_device, m_backgroundIndexBuffer, nullptr);
if (m_backgroundIndexMemory) vkFreeMemory(m_device, m_backgroundIndexMemory, nullptr);
if (m_textVertexBuffer) vkDestroyBuffer(m_device, m_textVertexBuffer, nullptr);
if (m_textVertexMemory) vkFreeMemory(m_device, m_textVertexMemory, nullptr);
if (m_textIndexBuffer) vkDestroyBuffer(m_device, m_textIndexBuffer, nullptr);
if (m_textIndexMemory) vkFreeMemory(m_device, m_textIndexMemory, nullptr);
if (m_circleVertexBuffer) vkDestroyBuffer(m_device, m_circleVertexBuffer, nullptr);
if (m_circleVertexMemory) vkFreeMemory(m_device, m_circleVertexMemory, nullptr);
if (m_circleIndexBuffer) vkDestroyBuffer(m_device, m_circleIndexBuffer, nullptr);
if (m_circleIndexMemory) vkFreeMemory(m_device, m_circleIndexMemory, nullptr);
if (m_waveVertexBuffer) vkDestroyBuffer(m_device, m_waveVertexBuffer, nullptr);
if (m_waveVertexMemory) vkFreeMemory(m_device, m_waveVertexMemory, nullptr);
if (m_waveIndexBuffer) vkDestroyBuffer(m_device, m_waveIndexBuffer, nullptr);
if (m_waveIndexMemory) vkFreeMemory(m_device, m_waveIndexMemory, nullptr);
// Clean up line pipeline
if (m_linePipeline) vkDestroyPipeline(m_device, m_linePipeline, nullptr);
if (m_linePipelineLayout) vkDestroyPipelineLayout(m_device, m_linePipelineLayout, nullptr);
// Clean up uniform buffers
for (size_t i = 0; i < m_uniformBuffers.size(); i++) {
if (m_uniformBuffers[i]) vkDestroyBuffer(m_device, m_uniformBuffers[i], nullptr);
if (m_uniformBuffersMemory[i]) vkFreeMemory(m_device, m_uniformBuffersMemory[i], nullptr);
}
m_uniformBuffers.clear();
m_uniformBuffersMemory.clear();
m_uniformBuffersMapped.clear();
// Clean up text resources
if (m_fontSampler) vkDestroySampler(m_device, m_fontSampler, nullptr);
if (m_fontTextureView) vkDestroyImageView(m_device, m_fontTextureView, nullptr);
if (m_fontTexture) vkDestroyImage(m_device, m_fontTexture, nullptr);
if (m_fontTextureMemory) vkFreeMemory(m_device, m_fontTextureMemory, nullptr);
// Clean up descriptors
if (m_descriptorPool) vkDestroyDescriptorPool(m_device, m_descriptorPool, nullptr);
if (m_descriptorSetLayout) vkDestroyDescriptorSetLayout(m_device, m_descriptorSetLayout, nullptr);
// Clean up transfer command pool
if (m_transferCommandPool) vkDestroyCommandPool(m_device, m_transferCommandPool, nullptr);
// Clean up pipelines
if (m_backgroundPipeline) vkDestroyPipeline(m_device, m_backgroundPipeline, nullptr);
if (m_backgroundPipelineLayout) vkDestroyPipelineLayout(m_device, m_backgroundPipelineLayout, nullptr);
if (m_geometryPipeline) vkDestroyPipeline(m_device, m_geometryPipeline, nullptr);
if (m_geometryPipelineLayout) vkDestroyPipelineLayout(m_device, m_geometryPipelineLayout, nullptr);
if (m_textPipeline) vkDestroyPipeline(m_device, m_textPipeline, nullptr);
if (m_textPipelineLayout) vkDestroyPipelineLayout(m_device, m_textPipelineLayout, nullptr);
// Clean up framebuffers
for (auto fb : m_framebuffers) {
if (fb) vkDestroyFramebuffer(m_device, fb, nullptr);
}
m_framebuffers.clear();
// Clean up render pass
if (m_renderPass) vkDestroyRenderPass(m_device, m_renderPass, nullptr);
m_device = VK_NULL_HANDLE;
m_initialized = false;
}
bool VulkanRenderer::resize(uint32_t width, uint32_t height)
{
std::cout << "VulkanRenderer::resize called: " << width << "x" << height
<< " (previous: " << m_width << "x" << m_height << ")" << std::endl;
m_width = width;
m_height = height;
// Update UBO resolution immediately
m_ubo.resolution[0] = static_cast<float>(m_width);
m_ubo.resolution[1] = static_cast<float>(m_height);
// Update all uniform buffers with new resolution
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
if (i < m_uniformBuffersMapped.size() && m_uniformBuffersMapped[i] != nullptr) {
memcpy(m_uniformBuffersMapped[i], &m_ubo, sizeof(m_ubo));
}
}
std::cout << " Updated UBO resolution to: (" << m_ubo.resolution[0] << ", " << m_ubo.resolution[1] << ")" << std::endl;
// Recreate framebuffers with new size
for (auto fb : m_framebuffers) {
if (fb) vkDestroyFramebuffer(m_device, fb, nullptr);
}
m_framebuffers.clear();
// Recreate MSAA resources if MSAA is enabled
if (m_msaaSamples > VK_SAMPLE_COUNT_1_BIT) {
cleanupMSAAResources();
if (!createMSAAResources()) {
logError("Failed to recreate MSAA resources during resize");
return false;
}
}
return createFramebuffers();
}
void VulkanRenderer::recordCommandBuffer(VkCommandBuffer commandBuffer,
uint32_t imageIndex,
VkImageView imageView,
int frameCount,
double rotationAngle,
double wavePhase,
bool paintingEnabled,
const std::string& lockInfo)
{
if (!m_initialized) {
return;
}
// Check for valid dimensions
if (m_width < 100 || m_height < 100) {
paintingEnabled = false;
}
// 根本性修复:使用 host-visible buffers 进行动态更新
// 这样可以直接映射内存更新,无需 staging buffer 和命令队列同步
if (paintingEnabled) {
std::vector<Vertex> circleVertices, waveVertices;
std::vector<uint16_t> circleIndices, waveIndices;
// 生成动态几何体(使用当前动画参数)
generateRotatingCircles(circleVertices, circleIndices, rotationAngle);
generateWaveEffect(waveVertices, waveIndices, wavePhase);
// 首次创建 buffers使用 host-visible 内存)
if (m_circleVertexBuffer == VK_NULL_HANDLE) {
VkDeviceSize circleVertexSize = sizeof(Vertex) * 1024; // 预分配足够空间
VkDeviceSize circleIndexSize = sizeof(uint16_t) * 2048;
VkDeviceSize waveVertexSize = sizeof(Vertex) * 1024;
VkDeviceSize waveIndexSize = sizeof(uint16_t) * 2048;
if (!createDynamicVertexBuffer(circleVertexSize, m_circleVertexBuffer, m_circleVertexMemory)) {
logError("Failed to create dynamic circle vertex buffer");
return;
}
if (!createDynamicIndexBuffer(circleIndexSize, m_circleIndexBuffer, m_circleIndexMemory)) {
logError("Failed to create dynamic circle index buffer");
return;
}
if (!createDynamicVertexBuffer(waveVertexSize, m_waveVertexBuffer, m_waveVertexMemory)) {
logError("Failed to create dynamic wave vertex buffer");
return;
}
if (!createDynamicIndexBuffer(waveIndexSize, m_waveIndexBuffer, m_waveIndexMemory)) {
logError("Failed to create dynamic wave index buffer");
return;
}
std::cout << "Dynamic geometry buffers created (host-visible, no staging)" << std::endl;
}
// 直接更新 buffer 内容(通过内存映射,无需命令队列)
if (!circleVertices.empty() && !circleIndices.empty()) {
updateDynamicBuffer(m_circleVertexMemory, circleVertices.data(),
sizeof(Vertex) * circleVertices.size());
updateDynamicBuffer(m_circleIndexMemory, circleIndices.data(),
sizeof(uint16_t) * circleIndices.size());
m_circleIndexCount = circleIndices.size();
}
if (!waveVertices.empty() && !waveIndices.empty()) {
updateDynamicBuffer(m_waveVertexMemory, waveVertices.data(),
sizeof(Vertex) * waveVertices.size());
updateDynamicBuffer(m_waveIndexMemory, waveIndices.data(),
sizeof(uint16_t) * waveIndices.size());
m_waveIndexCount = waveIndices.size();
}
}
// Update uniform buffer
m_ubo.time = static_cast<float>(frameCount);
m_ubo.resolution[0] = static_cast<float>(m_width);
m_ubo.resolution[1] = static_cast<float>(m_height);
m_ubo.rotation = static_cast<float>(rotationAngle);
m_ubo.wavePhase = static_cast<float>(wavePhase);
m_ubo.paintingEnabled = paintingEnabled ? 1.0f : 0.0f;
// CRITICAL FIX: Update ALL uniform buffers every frame!
// Each descriptor set points to a different buffer, so all must have current data
// Otherwise, alternating frames will use stale data from the other buffer
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
updateUniformBuffer(i);
}
// Use consistent frame index for descriptor set binding
uint32_t frameIndex = static_cast<uint32_t>(frameCount) % MAX_FRAMES_IN_FLIGHT;
// Begin command buffer
VkCommandBufferBeginInfo beginInfo = {};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
VkResult result = vkBeginCommandBuffer(commandBuffer, &beginInfo);
if (result != VK_SUCCESS) {
return;
}
// Make sure we have enough image views
while (m_imageViews.size() <= imageIndex) {
m_imageViews.push_back(VK_NULL_HANDLE);
}
// Make sure we have enough framebuffers
while (m_framebuffers.size() <= imageIndex) {
m_framebuffers.push_back(VK_NULL_HANDLE);
}
// Check if we need to create or recreate framebuffer
bool needsRecreate = false;
if (m_framebuffers[imageIndex] == VK_NULL_HANDLE) {
needsRecreate = true;
} else if (m_imageViews[imageIndex] != imageView) {
// Image view changed - destroy old framebuffer
vkDestroyFramebuffer(m_device, m_framebuffers[imageIndex], nullptr);
m_framebuffers[imageIndex] = VK_NULL_HANDLE;
needsRecreate = true;
}
// Update image view
m_imageViews[imageIndex] = imageView;
// Create framebuffer if needed
if (needsRecreate) {
VkFramebufferCreateInfo fbInfo = {};
fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
fbInfo.renderPass = m_renderPass;
fbInfo.width = m_width;
fbInfo.height = m_height;
fbInfo.layers = 1;
if (m_msaaSamples > VK_SAMPLE_COUNT_1_BIT) {
// MSAA enabled: attach MSAA image and resolve image
VkImageView attachments[2] = {
m_msaaImageView,
imageView
};
fbInfo.attachmentCount = 2;
fbInfo.pAttachments = attachments;
static int fbCreateCount = 0;
std::cout << "Creating framebuffer " << imageIndex << " (#" << fbCreateCount++
<< ") with MSAA (msaa_view=" << m_msaaImageView
<< ", resolve=" << imageView << ")" << std::endl;
} else {
// No MSAA: single attachment
fbInfo.attachmentCount = 1;
fbInfo.pAttachments = &imageView;
}
VkResult result = vkCreateFramebuffer(m_device, &fbInfo, nullptr, &m_framebuffers[imageIndex]);
if (result != VK_SUCCESS) {
std::cout << "ERROR: Failed to create framebuffer " << imageIndex
<< ", error code: " << result << std::endl;
return;
}
}
// Begin render pass
VkRenderPassBeginInfo renderPassInfo = {};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = m_renderPass;
renderPassInfo.framebuffer = m_framebuffers[imageIndex];
renderPassInfo.renderArea.offset = {0, 0};
renderPassInfo.renderArea.extent = {m_width, m_height};
VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
renderPassInfo.clearValueCount = 1;
renderPassInfo.pClearValues = &clearColor;
vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
// Set dynamic viewport and scissor
VkViewport viewport = {};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = static_cast<float>(m_width);
viewport.height = static_cast<float>(m_height);
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
VkRect2D scissor = {};
scissor.offset = {0, 0};
scissor.extent = {m_width, m_height};
vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
// Draw background
drawBackground(commandBuffer, frameCount);
if (paintingEnabled) {
// Draw geometry (circles and waves)
drawGeometry(commandBuffer, frameCount, rotationAngle, wavePhase);
}
// Draw text (status info)
drawText(commandBuffer, frameCount, paintingEnabled, lockInfo);
vkCmdEndRenderPass(commandBuffer);
vkEndCommandBuffer(commandBuffer);
}
void VulkanRenderer::updateAnimationParams(int frameCount, double rotation, double wavePhase)
{
m_ubo.time = static_cast<float>(frameCount);
m_ubo.rotation = static_cast<float>(rotation);
m_ubo.wavePhase = static_cast<float>(wavePhase);
}
VkFramebuffer VulkanRenderer::getFramebuffer(uint32_t index) const
{
if (index < m_framebuffers.size()) {
return m_framebuffers[index];
}
return VK_NULL_HANDLE;
}
bool VulkanRenderer::createRenderPass()
{
std::vector<VkAttachmentDescription> attachments;
if (m_msaaSamples > VK_SAMPLE_COUNT_1_BIT) {
// MSAA color attachment
VkAttachmentDescription colorAttachment = {};
colorAttachment.format = static_cast<VkFormat>(m_swapchainFormat);
colorAttachment.samples = static_cast<VkSampleCountFlagBits>(m_msaaSamples);
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
colorAttachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachments.push_back(colorAttachment);
// Resolve attachment (for presenting)
VkAttachmentDescription resolveAttachment = {};
resolveAttachment.format = static_cast<VkFormat>(m_swapchainFormat);
resolveAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
resolveAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
resolveAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
resolveAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
resolveAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
resolveAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
resolveAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
attachments.push_back(resolveAttachment);
VkAttachmentReference colorAttachmentRef = {};
colorAttachmentRef.attachment = 0;
colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkAttachmentReference resolveAttachmentRef = {};
resolveAttachmentRef.attachment = 1;
resolveAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef;
subpass.pResolveAttachments = &resolveAttachmentRef;
VkSubpassDependency dependency = {};
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
dependency.dstSubpass = 0;
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.srcAccessMask = 0;
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
VkRenderPassCreateInfo renderPassInfo = {};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
renderPassInfo.pAttachments = attachments.data();
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpass;
renderPassInfo.dependencyCount = 1;
renderPassInfo.pDependencies = &dependency;
return vkCreateRenderPass(m_device, &renderPassInfo, nullptr, &m_renderPass) == VK_SUCCESS;
} else {
// No MSAA - single color attachment
VkAttachmentDescription colorAttachment = {};
colorAttachment.format = static_cast<VkFormat>(m_swapchainFormat);
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
VkAttachmentReference colorAttachmentRef = {};
colorAttachmentRef.attachment = 0;
colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef;
VkSubpassDependency dependency = {};
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
dependency.dstSubpass = 0;
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.srcAccessMask = 0;
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
VkRenderPassCreateInfo renderPassInfo = {};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount = 1;
renderPassInfo.pAttachments = &colorAttachment;
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpass;
renderPassInfo.dependencyCount = 1;
renderPassInfo.pDependencies = &dependency;
return vkCreateRenderPass(m_device, &renderPassInfo, nullptr, &m_renderPass) == VK_SUCCESS;
}
}
bool VulkanRenderer::createFramebuffers()
{
m_framebuffers.resize(m_imageViews.size());
for (size_t i = 0; i < m_imageViews.size(); i++) {
VkFramebufferCreateInfo framebufferInfo = {};
framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebufferInfo.renderPass = m_renderPass;
framebufferInfo.width = m_width;
framebufferInfo.height = m_height;
framebufferInfo.layers = 1;
if (m_msaaSamples > VK_SAMPLE_COUNT_1_BIT) {
// MSAA enabled: attach MSAA image and resolve image
if (m_msaaImageView == VK_NULL_HANDLE) {
std::cout << "ERROR: MSAA image view is null when creating framebuffer " << i << std::endl;
return false;
}
VkImageView attachments[2] = {
m_msaaImageView,
m_imageViews[i]
};
framebufferInfo.attachmentCount = 2;
framebufferInfo.pAttachments = attachments;
std::cout << "Creating framebuffer " << i << " with MSAA (view=" << m_msaaImageView
<< ", resolve=" << m_imageViews[i] << ")" << std::endl;
} else {
// No MSAA: single attachment
framebufferInfo.attachmentCount = 1;
framebufferInfo.pAttachments = &m_imageViews[i];
}
VkResult result = vkCreateFramebuffer(m_device, &framebufferInfo, nullptr, &m_framebuffers[i]);
if (result != VK_SUCCESS) {
std::cout << "Failed to create framebuffer " << i << ", error: " << result << std::endl;
return false;
}
}
return true;
}
VkShaderModule VulkanRenderer::createShaderModule(const std::vector<char>& code)
{
VkShaderModuleCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
createInfo.codeSize = code.size();
createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data());
VkShaderModule shaderModule;
if (vkCreateShaderModule(m_device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) {
return VK_NULL_HANDLE;
}
return shaderModule;
}
bool VulkanRenderer::createBackgroundPipeline()
{
// For now, use simple shaders embedded in code
// In production, load from compiled SPIR-V files
// Simple vertex shader (SPIR-V bytecode for passthrough with UBO)
static const uint32_t bgVertCode[] = {
#include "shaders_spirv/background_vert.inc"
};
static const uint32_t bgFragCode[] = {
#include "shaders_spirv/background_frag.inc"
};
// Create shader modules
VkShaderModuleCreateInfo vertModuleInfo = {};
vertModuleInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
vertModuleInfo.codeSize = sizeof(bgVertCode);
vertModuleInfo.pCode = bgVertCode;
VkShaderModule vertShaderModule;
if (vkCreateShaderModule(m_device, &vertModuleInfo, nullptr, &vertShaderModule) != VK_SUCCESS) {
// Fallback to simple pipeline without shader includes
return createBackgroundPipelineSimple();
}
VkShaderModuleCreateInfo fragModuleInfo = {};
fragModuleInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
fragModuleInfo.codeSize = sizeof(bgFragCode);
fragModuleInfo.pCode = bgFragCode;
VkShaderModule fragShaderModule;
if (vkCreateShaderModule(m_device, &fragModuleInfo, nullptr, &fragShaderModule) != VK_SUCCESS) {
vkDestroyShaderModule(m_device, vertShaderModule, nullptr);
return createBackgroundPipelineSimple();
}
// Shader stages
VkPipelineShaderStageCreateInfo vertStageInfo = {};
vertStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
vertStageInfo.module = vertShaderModule;
vertStageInfo.pName = "main";
VkPipelineShaderStageCreateInfo fragStageInfo = {};
fragStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
fragStageInfo.module = fragShaderModule;
fragStageInfo.pName = "main";
VkPipelineShaderStageCreateInfo shaderStages[] = {vertStageInfo, fragStageInfo};
// Vertex input
VkVertexInputBindingDescription bindingDescription = {};
bindingDescription.binding = 0;
bindingDescription.stride = sizeof(Vertex);
bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
VkVertexInputAttributeDescription attributeDescriptions[3] = {};
// Position
attributeDescriptions[0].binding = 0;
attributeDescriptions[0].location = 0;
attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
attributeDescriptions[0].offset = offsetof(Vertex, pos);
// Color
attributeDescriptions[1].binding = 0;
attributeDescriptions[1].location = 1;
attributeDescriptions[1].format = VK_FORMAT_R32G32B32A32_SFLOAT;
attributeDescriptions[1].offset = offsetof(Vertex, color);
// TexCoord
attributeDescriptions[2].binding = 0;
attributeDescriptions[2].location = 2;
attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT;
attributeDescriptions[2].offset = offsetof(Vertex, texCoord);
VkPipelineVertexInputStateCreateInfo vertexInputInfo = {};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo.vertexBindingDescriptionCount = 1;
vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
vertexInputInfo.vertexAttributeDescriptionCount = 3;
vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions;
// Input assembly
VkPipelineInputAssemblyStateCreateInfo inputAssembly = {};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
inputAssembly.primitiveRestartEnable = VK_FALSE;
// Viewport and scissor - use dynamic state
VkPipelineViewportStateCreateInfo viewportState = {};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = 1;
viewportState.pViewports = nullptr; // Will be set dynamically
viewportState.scissorCount = 1;
viewportState.pScissors = nullptr; // Will be set dynamically
// Rasterizer
VkPipelineRasterizationStateCreateInfo rasterizer = {};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthClampEnable = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE;
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
rasterizer.lineWidth = 1.0f;
rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
rasterizer.depthBiasEnable = VK_FALSE;
// Multisampling
VkPipelineMultisampleStateCreateInfo multisampling = {};
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.sampleShadingEnable = VK_TRUE; // Enable sample shading for better quality
multisampling.minSampleShading = 0.5f; // Minimum fraction for sample shading
multisampling.rasterizationSamples = static_cast<VkSampleCountFlagBits>(m_msaaSamples);
// Color blending
VkPipelineColorBlendAttachmentState colorBlendAttachment = {};
colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
colorBlendAttachment.blendEnable = VK_FALSE;
VkPipelineColorBlendStateCreateInfo colorBlending = {};
colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
colorBlending.logicOpEnable = VK_FALSE;
colorBlending.attachmentCount = 1;
colorBlending.pAttachments = &colorBlendAttachment;
// Pipeline layout
VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 1;
pipelineLayoutInfo.pSetLayouts = &m_descriptorSetLayout;
if (vkCreatePipelineLayout(m_device, &pipelineLayoutInfo, nullptr, &m_backgroundPipelineLayout) != VK_SUCCESS) {
vkDestroyShaderModule(m_device, vertShaderModule, nullptr);
vkDestroyShaderModule(m_device, fragShaderModule, nullptr);
return false;
}
// Dynamic state - viewport and scissor
VkDynamicState dynamicStates[] = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR
};
VkPipelineDynamicStateCreateInfo dynamicState = {};
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamicState.dynamicStateCount = 2;
dynamicState.pDynamicStates = dynamicStates;
// Create graphics pipeline
VkGraphicsPipelineCreateInfo pipelineInfo = {};
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineInfo.stageCount = 2;
pipelineInfo.pStages = shaderStages;
pipelineInfo.pVertexInputState = &vertexInputInfo;
pipelineInfo.pInputAssemblyState = &inputAssembly;
pipelineInfo.pViewportState = &viewportState;
pipelineInfo.pRasterizationState = &rasterizer;
pipelineInfo.pMultisampleState = &multisampling;
pipelineInfo.pColorBlendState = &colorBlending;
pipelineInfo.pDynamicState = &dynamicState;
pipelineInfo.layout = m_backgroundPipelineLayout;
pipelineInfo.renderPass = m_renderPass;
pipelineInfo.subpass = 0;
VkResult result = vkCreateGraphicsPipelines(m_device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &m_backgroundPipeline);
vkDestroyShaderModule(m_device, vertShaderModule, nullptr);
vkDestroyShaderModule(m_device, fragShaderModule, nullptr);
return result == VK_SUCCESS;
}
bool VulkanRenderer::createBackgroundPipelineSimple()
{
// Fallback: create a simple pipeline without external shaders
// This is a placeholder implementation
logError("Shader files not found, using simple color-only rendering");
return true;
}
bool VulkanRenderer::createGeometryPipeline()
{
// Similar to background pipeline but with different blend mode for overlays
// For now, reuse the same structure
static const uint32_t geomVertCode[] = {
#include "shaders_spirv/geometry_vert.inc"
};
static const uint32_t geomFragCode[] = {
#include "shaders_spirv/geometry_frag.inc"
};
VkShaderModuleCreateInfo vertModuleInfo = {};
vertModuleInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
vertModuleInfo.codeSize = sizeof(geomVertCode);
vertModuleInfo.pCode = geomVertCode;
VkShaderModule vertShaderModule;
if (vkCreateShaderModule(m_device, &vertModuleInfo, nullptr, &vertShaderModule) != VK_SUCCESS) {
return false;
}
VkShaderModuleCreateInfo fragModuleInfo = {};
fragModuleInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
fragModuleInfo.codeSize = sizeof(geomFragCode);
fragModuleInfo.pCode = geomFragCode;
VkShaderModule fragShaderModule;
if (vkCreateShaderModule(m_device, &fragModuleInfo, nullptr, &fragShaderModule) != VK_SUCCESS) {
vkDestroyShaderModule(m_device, vertShaderModule, nullptr);
return false;
}
VkPipelineShaderStageCreateInfo shaderStages[2] = {};
shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
shaderStages[0].module = vertShaderModule;
shaderStages[0].pName = "main";
shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
shaderStages[1].module = fragShaderModule;
shaderStages[1].pName = "main";
// Vertex input
VkVertexInputBindingDescription bindingDescription = {};
bindingDescription.binding = 0;
bindingDescription.stride = sizeof(Vertex);
bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
VkVertexInputAttributeDescription attributeDescriptions[3] = {};
attributeDescriptions[0].binding = 0;
attributeDescriptions[0].location = 0;
attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
attributeDescriptions[0].offset = offsetof(Vertex, pos);
attributeDescriptions[1].binding = 0;
attributeDescriptions[1].location = 1;
attributeDescriptions[1].format = VK_FORMAT_R32G32B32A32_SFLOAT;
attributeDescriptions[1].offset = offsetof(Vertex, color);
attributeDescriptions[2].binding = 0;
attributeDescriptions[2].location = 2;
attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT;
attributeDescriptions[2].offset = offsetof(Vertex, texCoord);
VkPipelineVertexInputStateCreateInfo vertexInputInfo = {};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo.vertexBindingDescriptionCount = 1;
vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
vertexInputInfo.vertexAttributeDescriptionCount = 3;
vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions;
VkPipelineInputAssemblyStateCreateInfo inputAssembly = {};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
inputAssembly.primitiveRestartEnable = VK_FALSE;
// Viewport and scissor - use dynamic state
VkPipelineViewportStateCreateInfo viewportState = {};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = 1;
viewportState.pViewports = nullptr; // Will be set dynamically
viewportState.scissorCount = 1;
viewportState.pScissors = nullptr; // Will be set dynamically
VkPipelineRasterizationStateCreateInfo rasterizer = {};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthClampEnable = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE;
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
rasterizer.lineWidth = 1.0f;
rasterizer.cullMode = VK_CULL_MODE_NONE;
rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
rasterizer.depthBiasEnable = VK_FALSE;
VkPipelineMultisampleStateCreateInfo multisampling = {};
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.sampleShadingEnable = VK_TRUE; // Enable sample shading for better quality
multisampling.minSampleShading = 0.5f; // Minimum fraction for sample shading
multisampling.rasterizationSamples = static_cast<VkSampleCountFlagBits>(m_msaaSamples);
// Enable alpha blending
VkPipelineColorBlendAttachmentState colorBlendAttachment = {};
colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
colorBlendAttachment.blendEnable = VK_TRUE;
colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
VkPipelineColorBlendStateCreateInfo colorBlending = {};
colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
colorBlending.logicOpEnable = VK_FALSE;
colorBlending.attachmentCount = 1;
colorBlending.pAttachments = &colorBlendAttachment;
VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 1;
pipelineLayoutInfo.pSetLayouts = &m_descriptorSetLayout;
if (vkCreatePipelineLayout(m_device, &pipelineLayoutInfo, nullptr, &m_geometryPipelineLayout) != VK_SUCCESS) {
vkDestroyShaderModule(m_device, vertShaderModule, nullptr);
vkDestroyShaderModule(m_device, fragShaderModule, nullptr);
return false;
}
// Dynamic state - viewport and scissor
VkDynamicState dynamicStates[] = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR
};
VkPipelineDynamicStateCreateInfo dynamicState = {};
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamicState.dynamicStateCount = 2;
dynamicState.pDynamicStates = dynamicStates;
VkGraphicsPipelineCreateInfo pipelineInfo = {};
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineInfo.stageCount = 2;
pipelineInfo.pStages = shaderStages;
pipelineInfo.pVertexInputState = &vertexInputInfo;
pipelineInfo.pInputAssemblyState = &inputAssembly;
pipelineInfo.pViewportState = &viewportState;
pipelineInfo.pRasterizationState = &rasterizer;
pipelineInfo.pMultisampleState = &multisampling;
pipelineInfo.pColorBlendState = &colorBlending;
pipelineInfo.pDynamicState = &dynamicState;
pipelineInfo.layout = m_geometryPipelineLayout;
pipelineInfo.renderPass = m_renderPass;
pipelineInfo.subpass = 0;
VkResult result = vkCreateGraphicsPipelines(m_device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &m_geometryPipeline);
vkDestroyShaderModule(m_device, vertShaderModule, nullptr);
vkDestroyShaderModule(m_device, fragShaderModule, nullptr);
return (result == VK_SUCCESS);
}
bool VulkanRenderer::createLinePipeline()
{
// Create a pipeline for line rendering (waves)
static const uint32_t geomVertCode[] = {
#include "shaders_spirv/geometry_vert.inc"
};
static const uint32_t geomFragCode[] = {
#include "shaders_spirv/geometry_frag.inc"
};
VkShaderModuleCreateInfo vertModuleInfo = {};
vertModuleInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
vertModuleInfo.codeSize = sizeof(geomVertCode);
vertModuleInfo.pCode = geomVertCode;
VkShaderModule vertShaderModule;
if (vkCreateShaderModule(m_device, &vertModuleInfo, nullptr, &vertShaderModule) != VK_SUCCESS) {
return false;
}
VkShaderModuleCreateInfo fragModuleInfo = {};
fragModuleInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
fragModuleInfo.codeSize = sizeof(geomFragCode);
fragModuleInfo.pCode = geomFragCode;
VkShaderModule fragShaderModule;
if (vkCreateShaderModule(m_device, &fragModuleInfo, nullptr, &fragShaderModule) != VK_SUCCESS) {
vkDestroyShaderModule(m_device, vertShaderModule, nullptr);
return false;
}
VkPipelineShaderStageCreateInfo shaderStages[2] = {};
shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
shaderStages[0].module = vertShaderModule;
shaderStages[0].pName = "main";
shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
shaderStages[1].module = fragShaderModule;
shaderStages[1].pName = "main";
// Vertex input
VkVertexInputBindingDescription bindingDescription = {};
bindingDescription.binding = 0;
bindingDescription.stride = sizeof(Vertex);
bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
VkVertexInputAttributeDescription attributeDescriptions[3] = {};
attributeDescriptions[0].binding = 0;
attributeDescriptions[0].location = 0;
attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
attributeDescriptions[0].offset = offsetof(Vertex, pos);
attributeDescriptions[1].binding = 0;
attributeDescriptions[1].location = 1;
attributeDescriptions[1].format = VK_FORMAT_R32G32B32A32_SFLOAT;
attributeDescriptions[1].offset = offsetof(Vertex, color);
attributeDescriptions[2].binding = 0;
attributeDescriptions[2].location = 2;
attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT;
attributeDescriptions[2].offset = offsetof(Vertex, texCoord);
VkPipelineVertexInputStateCreateInfo vertexInputInfo = {};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo.vertexBindingDescriptionCount = 1;
vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
vertexInputInfo.vertexAttributeDescriptionCount = 3;
vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions;
VkPipelineInputAssemblyStateCreateInfo inputAssembly = {};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST; // Line rendering!
inputAssembly.primitiveRestartEnable = VK_FALSE;
// Viewport and scissor - use dynamic state
VkPipelineViewportStateCreateInfo viewportState = {};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = 1;
viewportState.pViewports = nullptr; // Will be set dynamically
viewportState.scissorCount = 1;
viewportState.pScissors = nullptr; // Will be set dynamically
VkPipelineRasterizationStateCreateInfo rasterizer = {};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthClampEnable = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE;
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
rasterizer.lineWidth = 2.0f; // Thicker lines for visibility
rasterizer.cullMode = VK_CULL_MODE_NONE;
rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
rasterizer.depthBiasEnable = VK_FALSE;
VkPipelineMultisampleStateCreateInfo multisampling = {};
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.sampleShadingEnable = VK_TRUE; // Enable sample shading for smoother lines
multisampling.minSampleShading = 0.75f; // Higher quality for lines
multisampling.rasterizationSamples = static_cast<VkSampleCountFlagBits>(m_msaaSamples);
// Enable alpha blending
VkPipelineColorBlendAttachmentState colorBlendAttachment = {};
colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
colorBlendAttachment.blendEnable = VK_TRUE;
colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
VkPipelineColorBlendStateCreateInfo colorBlending = {};
colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
colorBlending.logicOpEnable = VK_FALSE;
colorBlending.attachmentCount = 1;
colorBlending.pAttachments = &colorBlendAttachment;
VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 1;
pipelineLayoutInfo.pSetLayouts = &m_descriptorSetLayout;
if (vkCreatePipelineLayout(m_device, &pipelineLayoutInfo, nullptr, &m_linePipelineLayout) != VK_SUCCESS) {
vkDestroyShaderModule(m_device, vertShaderModule, nullptr);
vkDestroyShaderModule(m_device, fragShaderModule, nullptr);
return false;
}
// Dynamic state - viewport and scissor
VkDynamicState dynamicStates[] = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR
};
VkPipelineDynamicStateCreateInfo dynamicState = {};
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamicState.dynamicStateCount = 2;
dynamicState.pDynamicStates = dynamicStates;
VkGraphicsPipelineCreateInfo pipelineInfo = {};
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineInfo.stageCount = 2;
pipelineInfo.pStages = shaderStages;
pipelineInfo.pVertexInputState = &vertexInputInfo;
pipelineInfo.pInputAssemblyState = &inputAssembly;
pipelineInfo.pViewportState = &viewportState;
pipelineInfo.pRasterizationState = &rasterizer;
pipelineInfo.pMultisampleState = &multisampling;
pipelineInfo.pColorBlendState = &colorBlending;
pipelineInfo.pDynamicState = &dynamicState;
pipelineInfo.layout = m_linePipelineLayout;
pipelineInfo.renderPass = m_renderPass;
pipelineInfo.subpass = 0;
VkResult result = vkCreateGraphicsPipelines(m_device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &m_linePipeline);
vkDestroyShaderModule(m_device, vertShaderModule, nullptr);
vkDestroyShaderModule(m_device, fragShaderModule, nullptr);
return (result == VK_SUCCESS);
}
bool VulkanRenderer::createTextPipeline()
{
// Load text shaders from embedded SPIR-V
static const uint32_t textVertCode[] = {
#include "shaders_spirv/text_vert.inc"
};
static const uint32_t textFragCode[] = {
#include "shaders_spirv/text_frag.inc"
};
VkShaderModuleCreateInfo vertModuleInfo = {};
vertModuleInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
vertModuleInfo.codeSize = sizeof(textVertCode);
vertModuleInfo.pCode = textVertCode;
VkShaderModule vertShaderModule = VK_NULL_HANDLE;
if (vkCreateShaderModule(m_device, &vertModuleInfo, nullptr, &vertShaderModule) != VK_SUCCESS) {
logError("Failed to create text vertex shader module");
return false;
}
VkShaderModuleCreateInfo fragModuleInfo = {};
fragModuleInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
fragModuleInfo.codeSize = sizeof(textFragCode);
fragModuleInfo.pCode = textFragCode;
VkShaderModule fragShaderModule = VK_NULL_HANDLE;
if (vkCreateShaderModule(m_device, &fragModuleInfo, nullptr, &fragShaderModule) != VK_SUCCESS) {
vkDestroyShaderModule(m_device, vertShaderModule, nullptr);
logError("Failed to create text fragment shader module");
return false;
}
VkPipelineShaderStageCreateInfo vertShaderStageInfo = {};
vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
vertShaderStageInfo.module = vertShaderModule;
vertShaderStageInfo.pName = "main";
VkPipelineShaderStageCreateInfo fragShaderStageInfo = {};
fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
fragShaderStageInfo.module = fragShaderModule;
fragShaderStageInfo.pName = "main";
VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo};
// Vertex input
VkVertexInputBindingDescription bindingDescription = {};
bindingDescription.binding = 0;
bindingDescription.stride = sizeof(Vertex);
bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
std::vector<VkVertexInputAttributeDescription> attributeDescriptions(3);
attributeDescriptions[0].binding = 0;
attributeDescriptions[0].location = 0;
attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
attributeDescriptions[0].offset = offsetof(Vertex, pos);
attributeDescriptions[1].binding = 0;
attributeDescriptions[1].location = 1;
attributeDescriptions[1].format = VK_FORMAT_R32G32B32A32_SFLOAT;
attributeDescriptions[1].offset = offsetof(Vertex, color);
attributeDescriptions[2].binding = 0;
attributeDescriptions[2].location = 2;
attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT;
attributeDescriptions[2].offset = offsetof(Vertex, texCoord);
VkPipelineVertexInputStateCreateInfo vertexInputInfo = {};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo.vertexBindingDescriptionCount = 1;
vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
vertexInputInfo.vertexAttributeDescriptionCount = attributeDescriptions.size();
vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();
VkPipelineInputAssemblyStateCreateInfo inputAssembly = {};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
inputAssembly.primitiveRestartEnable = VK_FALSE;
VkViewport viewport = {};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = (float)m_width;
viewport.height = (float)m_height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
VkRect2D scissor = {};
scissor.offset = {0, 0};
scissor.extent = {m_width, m_height};
VkPipelineViewportStateCreateInfo viewportState = {};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = 1;
viewportState.pViewports = &viewport;
viewportState.scissorCount = 1;
viewportState.pScissors = &scissor;
VkPipelineRasterizationStateCreateInfo rasterizer = {};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthClampEnable = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE;
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
rasterizer.lineWidth = 1.0f;
rasterizer.cullMode = VK_CULL_MODE_NONE;
rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
rasterizer.depthBiasEnable = VK_FALSE;
VkPipelineMultisampleStateCreateInfo multisampling = {};
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.sampleShadingEnable = VK_TRUE; // Enable sample shading for smoother text
multisampling.minSampleShading = 1.0f; // Maximum quality for text rendering
multisampling.rasterizationSamples = static_cast<VkSampleCountFlagBits>(m_msaaSamples);
// Enable alpha blending for text
VkPipelineColorBlendAttachmentState colorBlendAttachment = {};
colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
colorBlendAttachment.blendEnable = VK_TRUE;
colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
VkPipelineColorBlendStateCreateInfo colorBlending = {};
colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
colorBlending.logicOpEnable = VK_FALSE;
colorBlending.attachmentCount = 1;
colorBlending.pAttachments = &colorBlendAttachment;
// Create descriptor set layout for text (with texture sampler)
VkDescriptorSetLayoutBinding bindings[2];
// UBO binding
bindings[0].binding = 0;
bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
bindings[0].descriptorCount = 1;
bindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
bindings[0].pImmutableSamplers = nullptr;
// Texture sampler binding
bindings[1].binding = 1;
bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
bindings[1].descriptorCount = 1;
bindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
bindings[1].pImmutableSamplers = nullptr;
VkDescriptorSetLayoutCreateInfo layoutInfo = {};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layoutInfo.bindingCount = 2;
layoutInfo.pBindings = bindings;
VkDescriptorSetLayout textDescriptorSetLayout;
if (vkCreateDescriptorSetLayout(m_device, &layoutInfo, nullptr, &textDescriptorSetLayout) != VK_SUCCESS) {
vkDestroyShaderModule(m_device, vertShaderModule, nullptr);
vkDestroyShaderModule(m_device, fragShaderModule, nullptr);
return false;
}
VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 1;
pipelineLayoutInfo.pSetLayouts = &textDescriptorSetLayout;
if (vkCreatePipelineLayout(m_device, &pipelineLayoutInfo, nullptr, &m_textPipelineLayout) != VK_SUCCESS) {
vkDestroyDescriptorSetLayout(m_device, textDescriptorSetLayout, nullptr);
vkDestroyShaderModule(m_device, vertShaderModule, nullptr);
vkDestroyShaderModule(m_device, fragShaderModule, nullptr);
return false;
}
VkGraphicsPipelineCreateInfo pipelineInfo = {};
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineInfo.stageCount = 2;
pipelineInfo.pStages = shaderStages;
pipelineInfo.pVertexInputState = &vertexInputInfo;
pipelineInfo.pInputAssemblyState = &inputAssembly;
pipelineInfo.pViewportState = &viewportState;
pipelineInfo.pRasterizationState = &rasterizer;
pipelineInfo.pMultisampleState = &multisampling;
pipelineInfo.pColorBlendState = &colorBlending;
pipelineInfo.layout = m_textPipelineLayout;
pipelineInfo.renderPass = m_renderPass;
pipelineInfo.subpass = 0;
VkResult result = vkCreateGraphicsPipelines(m_device, VK_NULL_HANDLE, 1, &pipelineInfo,
nullptr, &m_textPipeline);
vkDestroyShaderModule(m_device, vertShaderModule, nullptr);
vkDestroyShaderModule(m_device, fragShaderModule, nullptr);
vkDestroyDescriptorSetLayout(m_device, textDescriptorSetLayout, nullptr);
if (result != VK_SUCCESS) {
logError("Failed to create text graphics pipeline");
return false;
}
std::cout << "Text pipeline created successfully" << std::endl;
std::cout << "Text pipeline handle: " << m_textPipeline << std::endl;
std::cout << "Text pipeline layout: " << m_textPipelineLayout << std::endl;
return true;
}
bool VulkanRenderer::createDescriptorSetLayout()
{
std::vector<VkDescriptorSetLayoutBinding> bindings(2);
// UBO binding
bindings[0].binding = 0;
bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
bindings[0].descriptorCount = 1;
bindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
bindings[0].pImmutableSamplers = nullptr;
// Image sampler binding
bindings[1].binding = 1;
bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
bindings[1].descriptorCount = 1;
bindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
bindings[1].pImmutableSamplers = nullptr;
VkDescriptorSetLayoutCreateInfo layoutInfo = {};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layoutInfo.bindingCount = bindings.size();
layoutInfo.pBindings = bindings.data();
if (vkCreateDescriptorSetLayout(m_device, &layoutInfo, nullptr, &m_descriptorSetLayout) != VK_SUCCESS) {
return false;
}
return true;
}
bool VulkanRenderer::createDescriptorPool()
{
std::vector<VkDescriptorPoolSize> poolSizes(2);
// UBO pool size
poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
poolSizes[0].descriptorCount = MAX_FRAMES_IN_FLIGHT;
// Image sampler pool size
poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
poolSizes[1].descriptorCount = MAX_FRAMES_IN_FLIGHT;
VkDescriptorPoolCreateInfo poolInfo = {};
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
poolInfo.poolSizeCount = poolSizes.size();
poolInfo.pPoolSizes = poolSizes.data();
poolInfo.maxSets = MAX_FRAMES_IN_FLIGHT;
if (vkCreateDescriptorPool(m_device, &poolInfo, nullptr, &m_descriptorPool) != VK_SUCCESS) {
return false;
}
return true;
}
bool VulkanRenderer::createDescriptorSets()
{
std::vector<VkDescriptorSetLayout> layouts(MAX_FRAMES_IN_FLIGHT, m_descriptorSetLayout);
VkDescriptorSetAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.descriptorPool = m_descriptorPool;
allocInfo.descriptorSetCount = MAX_FRAMES_IN_FLIGHT;
allocInfo.pSetLayouts = layouts.data();
m_descriptorSets.resize(MAX_FRAMES_IN_FLIGHT);
if (vkAllocateDescriptorSets(m_device, &allocInfo, m_descriptorSets.data()) != VK_SUCCESS) {
return false;
}
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
VkDescriptorBufferInfo bufferInfo = {};
bufferInfo.buffer = m_uniformBuffers[i];
bufferInfo.offset = 0;
bufferInfo.range = sizeof(UniformBufferObject);
std::vector<VkWriteDescriptorSet> descriptorWrites;
// UBO descriptor
VkWriteDescriptorSet uboWrite = {};
uboWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
uboWrite.dstSet = m_descriptorSets[i];
uboWrite.dstBinding = 0;
uboWrite.dstArrayElement = 0;
uboWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
uboWrite.descriptorCount = 1;
uboWrite.pBufferInfo = &bufferInfo;
descriptorWrites.push_back(uboWrite);
// Font texture descriptor (if available)
VkDescriptorImageInfo imageInfo = {};
if (m_fontTextureView != VK_NULL_HANDLE && m_fontSampler != VK_NULL_HANDLE) {
imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
imageInfo.imageView = m_fontTextureView;
imageInfo.sampler = m_fontSampler;
VkWriteDescriptorSet imageWrite = {};
imageWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
imageWrite.dstSet = m_descriptorSets[i];
imageWrite.dstBinding = 1;
imageWrite.dstArrayElement = 0;
imageWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
imageWrite.descriptorCount = 1;
imageWrite.pImageInfo = &imageInfo;
descriptorWrites.push_back(imageWrite);
}
vkUpdateDescriptorSets(m_device, descriptorWrites.size(), descriptorWrites.data(), 0, nullptr);
}
return true;
}
bool VulkanRenderer::createUniformBuffers()
{
VkDeviceSize bufferSize = sizeof(UniformBufferObject);
m_uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT);
m_uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT);
m_uniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT);
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
if (!createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
m_uniformBuffers[i], m_uniformBuffersMemory[i])) {
logError("Failed to create uniform buffer");
return false;
}
VkResult result = vkMapMemory(m_device, m_uniformBuffersMemory[i], 0, bufferSize, 0, &m_uniformBuffersMapped[i]);
if (result != VK_SUCCESS) {
logError("Failed to map uniform buffer memory");
return false;
}
if (m_uniformBuffersMapped[i] == nullptr) {
logError("Uniform buffer mapped pointer is null");
return false;
}
}
return true;
}
void VulkanRenderer::updateUniformBuffer(uint32_t currentImage)
{
if (currentImage < m_uniformBuffersMapped.size()) {
if (m_uniformBuffersMapped[currentImage] != nullptr) {
memcpy(m_uniformBuffersMapped[currentImage], &m_ubo, sizeof(m_ubo));
} else {
logError("Uniform buffer mapped pointer is null");
}
} else {
std::cerr << "VulkanRenderer::updateUniformBuffer - currentImage " << currentImage
<< " out of range (size: " << m_uniformBuffersMapped.size() << ")" << std::endl;
}
}
bool VulkanRenderer::createVertexBuffer(const std::vector<Vertex>& vertices,
VkBuffer& buffer, VkDeviceMemory& memory)
{
if (vertices.empty()) {
logError("Cannot create vertex buffer from empty vertices");
return false;
}
VkDeviceSize bufferSize = sizeof(Vertex) * vertices.size();
VkBuffer stagingBuffer;
VkDeviceMemory stagingBufferMemory;
if (!createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
stagingBuffer, stagingBufferMemory)) {
logError("Failed to create staging buffer for vertex buffer");
return false;
}
void* data = nullptr;
VkResult result = vkMapMemory(m_device, stagingBufferMemory, 0, bufferSize, 0, &data);
if (result != VK_SUCCESS || data == nullptr) {
logError("Failed to map staging buffer memory for vertex buffer");
vkDestroyBuffer(m_device, stagingBuffer, nullptr);
vkFreeMemory(m_device, stagingBufferMemory, nullptr);
return false;
}
memcpy(data, vertices.data(), bufferSize);
vkUnmapMemory(m_device, stagingBufferMemory);
if (!createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, buffer, memory)) {
logError("Failed to create device local vertex buffer");
vkDestroyBuffer(m_device, stagingBuffer, nullptr);
vkFreeMemory(m_device, stagingBufferMemory, nullptr);
return false;
}
if (!copyBuffer(stagingBuffer, buffer, bufferSize)) {
logError("Failed to copy staging buffer to vertex buffer");
// 清理已创建的目标 buffer 和 staging buffer
vkDestroyBuffer(m_device, buffer, nullptr);
vkFreeMemory(m_device, memory, nullptr);
vkDestroyBuffer(m_device, stagingBuffer, nullptr);
vkFreeMemory(m_device, stagingBufferMemory, nullptr);
buffer = VK_NULL_HANDLE;
memory = VK_NULL_HANDLE;
return false;
}
vkDestroyBuffer(m_device, stagingBuffer, nullptr);
vkFreeMemory(m_device, stagingBufferMemory, nullptr);
return true;
}
bool VulkanRenderer::createIndexBuffer(const std::vector<uint16_t>& indices,
VkBuffer& buffer, VkDeviceMemory& memory)
{
if (indices.empty()) {
logError("Cannot create index buffer from empty indices");
return false;
}
VkDeviceSize bufferSize = sizeof(uint16_t) * indices.size();
VkBuffer stagingBuffer;
VkDeviceMemory stagingBufferMemory;
if (!createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
stagingBuffer, stagingBufferMemory)) {
logError("Failed to create staging buffer for index buffer");
return false;
}
void* data = nullptr;
VkResult result = vkMapMemory(m_device, stagingBufferMemory, 0, bufferSize, 0, &data);
if (result != VK_SUCCESS || data == nullptr) {
logError("Failed to map staging buffer memory for index buffer");
vkDestroyBuffer(m_device, stagingBuffer, nullptr);
vkFreeMemory(m_device, stagingBufferMemory, nullptr);
return false;
}
memcpy(data, indices.data(), bufferSize);
vkUnmapMemory(m_device, stagingBufferMemory);
if (!createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, buffer, memory)) {
logError("Failed to create device local index buffer");
vkDestroyBuffer(m_device, stagingBuffer, nullptr);
vkFreeMemory(m_device, stagingBufferMemory, nullptr);
return false;
}
if (!copyBuffer(stagingBuffer, buffer, bufferSize)) {
logError("Failed to copy staging buffer to index buffer");
// 清理已创建的目标 buffer 和 staging buffer
vkDestroyBuffer(m_device, buffer, nullptr);
vkFreeMemory(m_device, memory, nullptr);
vkDestroyBuffer(m_device, stagingBuffer, nullptr);
vkFreeMemory(m_device, stagingBufferMemory, nullptr);
buffer = VK_NULL_HANDLE;
memory = VK_NULL_HANDLE;
return false;
}
vkDestroyBuffer(m_device, stagingBuffer, nullptr);
vkFreeMemory(m_device, stagingBufferMemory, nullptr);
return true;
}
bool VulkanRenderer::createDynamicVertexBuffer(uint64_t size, VkBuffer& buffer, VkDeviceMemory& memory)
{
// 创建 host-visible 的 vertex buffer可以直接映射更新无需 staging buffer
return createBuffer(size,
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
buffer, memory);
}
bool VulkanRenderer::createDynamicIndexBuffer(uint64_t size, VkBuffer& buffer, VkDeviceMemory& memory)
{
// 创建 host-visible 的 index buffer可以直接映射更新无需 staging buffer
return createBuffer(size,
VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
buffer, memory);
}
bool VulkanRenderer::updateDynamicBuffer(VkDeviceMemory memory, const void* data, uint64_t size)
{
// 直接映射内存并更新,无需命令队列和同步
void* mappedData = nullptr;
VkResult result = vkMapMemory(m_device, memory, 0, size, 0, &mappedData);
if (result != VK_SUCCESS || mappedData == nullptr) {
logError("Failed to map dynamic buffer memory");
return false;
}
memcpy(mappedData, data, size);
vkUnmapMemory(m_device, memory);
return true;
}
bool VulkanRenderer::createBuffer(uint64_t size, uint32_t usage, uint32_t properties,
VkBuffer& buffer, VkDeviceMemory& memory)
{
VkBufferCreateInfo bufferInfo = {};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = size;
bufferInfo.usage = usage;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
if (vkCreateBuffer(m_device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) {
return false;
}
VkMemoryRequirements memRequirements;
vkGetBufferMemoryRequirements(m_device, buffer, &memRequirements);
VkMemoryAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size;
allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
if (vkAllocateMemory(m_device, &allocInfo, nullptr, &memory) != VK_SUCCESS) {
vkDestroyBuffer(m_device, buffer, nullptr);
return false;
}
vkBindBufferMemory(m_device, buffer, memory, 0);
return true;
}
bool VulkanRenderer::copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, uint64_t size)
{
if (srcBuffer == VK_NULL_HANDLE || dstBuffer == VK_NULL_HANDLE) {
logError("Cannot copy buffer: source or destination is null");
return false;
}
if (m_graphicsQueue == VK_NULL_HANDLE) {
logError("Cannot copy buffer: graphics queue is null");
return false;
}
// Note: 移除 vkDeviceWaitIdle改用 fence 精确同步
// vkDeviceWaitIdle 在三缓冲场景下会导致等待失败
// 因为双缓冲的 fence 机制只能确保 2 帧的同步
// Create a one-time command pool if not exists
if (m_transferCommandPool == VK_NULL_HANDLE) {
VkCommandPoolCreateInfo poolInfo = {};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
poolInfo.queueFamilyIndex = m_queueFamilyIndex;
VkResult result = vkCreateCommandPool(m_device, &poolInfo, nullptr, &m_transferCommandPool);
if (result != VK_SUCCESS) {
logError("Failed to create transfer command pool");
return false;
}
}
// Allocate a one-time command buffer
VkCommandBufferAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandPool = m_transferCommandPool;
allocInfo.commandBufferCount = 1;
VkCommandBuffer commandBuffer;
VkResult result = vkAllocateCommandBuffers(m_device, &allocInfo, &commandBuffer);
if (result != VK_SUCCESS) {
logError("Failed to allocate command buffer for copy");
return false;
}
// Begin recording
VkCommandBufferBeginInfo beginInfo = {};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
result = vkBeginCommandBuffer(commandBuffer, &beginInfo);
if (result != VK_SUCCESS) {
logError("Failed to begin command buffer for copy");
vkFreeCommandBuffers(m_device, m_transferCommandPool, 1, &commandBuffer);
return false;
}
// Copy command
VkBufferCopy copyRegion = {};
copyRegion.srcOffset = 0;
copyRegion.dstOffset = 0;
copyRegion.size = size;
vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, &copyRegion);
result = vkEndCommandBuffer(commandBuffer);
if (result != VK_SUCCESS) {
logError("Failed to end command buffer for copy");
vkFreeCommandBuffers(m_device, m_transferCommandPool, 1, &commandBuffer);
return false;
}
// Create a fence for synchronization
VkFenceCreateInfo fenceInfo = {};
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceInfo.flags = 0;
VkFence fence;
result = vkCreateFence(m_device, &fenceInfo, nullptr, &fence);
if (result != VK_SUCCESS) {
logError("Failed to create fence for buffer copy");
vkFreeCommandBuffers(m_device, m_transferCommandPool, 1, &commandBuffer);
return false;
}
// Submit and wait
VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;
result = vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, fence);
if (result != VK_SUCCESS) {
std::cerr << "vkQueueSubmit failed with error code: " << result << std::endl;
logError("Failed to submit copy command");
vkDestroyFence(m_device, fence, nullptr);
vkFreeCommandBuffers(m_device, m_transferCommandPool, 1, &commandBuffer);
return false;
}
// Wait for the fence with short timeout (100ms) to avoid blocking
// 使用短超时快速失败,避免长时间阻塞渲染循环导致设备丢失
result = vkWaitForFences(m_device, 1, &fence, VK_TRUE, 100000000ULL); // 100 ms in nanoseconds
if (result == VK_TIMEOUT) {
std::cerr << "TIMEOUT: vkWaitForFences exceeded 100ms - GPU busy, will retry later" << std::endl;
logError("Timeout waiting for fence after copy (100ms) - skipping this buffer update");
vkDestroyFence(m_device, fence, nullptr);
vkFreeCommandBuffers(m_device, m_transferCommandPool, 1, &commandBuffer);
return false;
} else if (result != VK_SUCCESS) {
std::cerr << "vkWaitForFences failed with error code: " << result << std::endl;
logError("Failed to wait for fence after copy");
vkDestroyFence(m_device, fence, nullptr);
vkFreeCommandBuffers(m_device, m_transferCommandPool, 1, &commandBuffer);
return false;
}
// Cleanup fence
vkDestroyFence(m_device, fence, nullptr);
// Free command buffer
vkFreeCommandBuffers(m_device, m_transferCommandPool, 1, &commandBuffer);
return true;
}
uint32_t VulkanRenderer::findMemoryType(uint32_t typeFilter, uint32_t properties)
{
VkPhysicalDeviceMemoryProperties memProperties;
vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memProperties);
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
if ((typeFilter & (1 << i)) &&
(memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
return i;
}
}
return 0;
}
bool VulkanRenderer::initializeTextRendering()
{
#ifdef ENABLE_FREETYPE
std::cout << "========================================" << std::endl;
std::cout << "Initializing text rendering with FreeType..." << std::endl;
// Initialize FreeType library
FT_Library ft;
if (FT_Init_FreeType(&ft)) {
logError("Failed to initialize FreeType library");
return false;
}
// Load font face - try multiple common font paths
FT_Face face;
const char* fontPaths[] = {
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
"/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
"/usr/share/fonts/TTF/DejaVuSans.ttf",
"/System/Library/Fonts/Helvetica.ttc",
"/usr/local/share/fonts/DejaVuSans.ttf"
};
bool fontLoaded = false;
for (const char* fontPath : fontPaths) {
if (FT_New_Face(ft, fontPath, 0, &face) == 0) {
std::cout << "Loaded font: " << fontPath << std::endl;
fontLoaded = true;
break;
}
}
if (!fontLoaded) {
logError("Failed to load any font");
FT_Done_FreeType(ft);
return false;
}
// Set pixel size
FT_Set_Pixel_Sizes(face, 0, 48);
// Create font atlas
if (!createFontAtlas()) {
logError("Failed to create font atlas");
FT_Done_Face(face);
FT_Done_FreeType(ft);
return false;
}
// Generate character textures for ASCII printable characters
const int atlasWidth = 512;
const int atlasHeight = 512;
unsigned char* atlasData = new unsigned char[atlasWidth * atlasHeight];
memset(atlasData, 0, atlasWidth * atlasHeight);
int penX = 0;
int penY = 0;
int rowHeight = 0;
// Generate glyphs for ASCII 32-126
for (unsigned char c = 32; c < 127; c++) {
if (FT_Load_Char(face, c, FT_LOAD_RENDER)) {
std::cerr << "Failed to load glyph: " << c << std::endl;
continue;
}
FT_GlyphSlot g = face->glyph;
// Check if we need to move to next row
if (penX + g->bitmap.width >= atlasWidth) {
penX = 0;
penY += rowHeight;
rowHeight = 0;
}
// Check if we have space
if (penY + g->bitmap.rows >= atlasHeight) {
std::cerr << "Font atlas too small!" << std::endl;
break;
}
// Copy glyph bitmap to atlas
for (unsigned int row = 0; row < g->bitmap.rows; row++) {
for (unsigned int col = 0; col < g->bitmap.width; col++) {
int x = penX + col;
int y = penY + row;
atlasData[y * atlasWidth + x] = g->bitmap.buffer[row * g->bitmap.width + col];
}
}
// Store character info
CharInfo charInfo;
charInfo.texCoords[0] = (float)penX / atlasWidth;
charInfo.texCoords[1] = (float)penY / atlasHeight;
charInfo.texCoords[2] = (float)g->bitmap.width / atlasWidth;
charInfo.texCoords[3] = (float)g->bitmap.rows / atlasHeight;
charInfo.size[0] = (float)g->bitmap.width;
charInfo.size[1] = (float)g->bitmap.rows;
charInfo.bearing[0] = (float)g->bitmap_left;
charInfo.bearing[1] = (float)g->bitmap_top;
charInfo.advance = (float)(g->advance.x >> 6);
m_charInfoMap[c] = charInfo;
// Update position
penX += g->bitmap.width + 1; // 1 pixel padding
rowHeight = std::max(rowHeight, (int)g->bitmap.rows);
}
// Create Vulkan texture from atlas
VkDeviceSize imageSize = atlasWidth * atlasHeight;
// Create staging buffer
VkBuffer stagingBuffer;
VkDeviceMemory stagingBufferMemory;
if (!createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
stagingBuffer, stagingBufferMemory)) {
delete[] atlasData;
FT_Done_Face(face);
FT_Done_FreeType(ft);
return false;
}
// Copy atlas data to staging buffer
void* data;
vkMapMemory(m_device, stagingBufferMemory, 0, imageSize, 0, &data);
memcpy(data, atlasData, imageSize);
vkUnmapMemory(m_device, stagingBufferMemory);
delete[] atlasData;
// Create image
VkImageCreateInfo imageInfo = {};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.extent.width = atlasWidth;
imageInfo.extent.height = atlasHeight;
imageInfo.extent.depth = 1;
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.format = VK_FORMAT_R8_UNORM;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
if (vkCreateImage(m_device, &imageInfo, nullptr, &m_fontTexture) != VK_SUCCESS) {
vkDestroyBuffer(m_device, stagingBuffer, nullptr);
vkFreeMemory(m_device, stagingBufferMemory, nullptr);
FT_Done_Face(face);
FT_Done_FreeType(ft);
return false;
}
// Allocate image memory
VkMemoryRequirements memRequirements;
vkGetImageMemoryRequirements(m_device, m_fontTexture, &memRequirements);
VkMemoryAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size;
allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
if (vkAllocateMemory(m_device, &allocInfo, nullptr, &m_fontTextureMemory) != VK_SUCCESS) {
vkDestroyImage(m_device, m_fontTexture, nullptr);
vkDestroyBuffer(m_device, stagingBuffer, nullptr);
vkFreeMemory(m_device, stagingBufferMemory, nullptr);
FT_Done_Face(face);
FT_Done_FreeType(ft);
return false;
}
vkBindImageMemory(m_device, m_fontTexture, m_fontTextureMemory, 0);
// Transition image layout and copy buffer to image
// (This requires a command buffer - simplified version)
VkCommandPoolCreateInfo poolInfo = {};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.queueFamilyIndex = m_queueFamilyIndex;
poolInfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
if (m_transferCommandPool == VK_NULL_HANDLE) {
vkCreateCommandPool(m_device, &poolInfo, nullptr, &m_transferCommandPool);
}
VkCommandBufferAllocateInfo allocInfoCmd = {};
allocInfoCmd.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfoCmd.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfoCmd.commandPool = m_transferCommandPool;
allocInfoCmd.commandBufferCount = 1;
VkCommandBuffer commandBuffer;
vkAllocateCommandBuffers(m_device, &allocInfoCmd, &commandBuffer);
VkCommandBufferBeginInfo beginInfo = {};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkBeginCommandBuffer(commandBuffer, &beginInfo);
// Transition to transfer dst
VkImageMemoryBarrier barrier = {};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = m_fontTexture;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1;
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
// Copy buffer to image
VkBufferImageCopy region = {};
region.bufferOffset = 0;
region.bufferRowLength = 0;
region.bufferImageHeight = 0;
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.mipLevel = 0;
region.imageSubresource.baseArrayLayer = 0;
region.imageSubresource.layerCount = 1;
region.imageOffset = {0, 0, 0};
region.imageExtent = {(uint32_t)atlasWidth, (uint32_t)atlasHeight, 1};
vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, m_fontTexture,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
// Transition to shader read
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
vkEndCommandBuffer(commandBuffer);
VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;
vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
vkQueueWaitIdle(m_graphicsQueue);
vkFreeCommandBuffers(m_device, m_transferCommandPool, 1, &commandBuffer);
vkDestroyBuffer(m_device, stagingBuffer, nullptr);
vkFreeMemory(m_device, stagingBufferMemory, nullptr);
// Create image view
VkImageViewCreateInfo viewInfo = {};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = m_fontTexture;
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = VK_FORMAT_R8_UNORM;
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
viewInfo.subresourceRange.baseMipLevel = 0;
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = 1;
if (vkCreateImageView(m_device, &viewInfo, nullptr, &m_fontTextureView) != VK_SUCCESS) {
FT_Done_Face(face);
FT_Done_FreeType(ft);
return false;
}
// Create sampler
VkSamplerCreateInfo samplerInfo = {};
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
samplerInfo.magFilter = VK_FILTER_LINEAR;
samplerInfo.minFilter = VK_FILTER_LINEAR;
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;
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_LINEAR;
if (vkCreateSampler(m_device, &samplerInfo, nullptr, &m_fontSampler) != VK_SUCCESS) {
FT_Done_Face(face);
FT_Done_FreeType(ft);
return false;
}
// Cleanup FreeType
FT_Done_Face(face);
FT_Done_FreeType(ft);
std::cout << "Text rendering initialized successfully with " << m_charInfoMap.size() << " characters" << std::endl;
std::cout << "Font texture: " << (m_fontTexture != VK_NULL_HANDLE ? "OK" : "NULL") << std::endl;
std::cout << "Font texture view: " << (m_fontTextureView != VK_NULL_HANDLE ? "OK" : "NULL") << std::endl;
std::cout << "Font sampler: " << (m_fontSampler != VK_NULL_HANDLE ? "OK" : "NULL") << std::endl;
std::cout << "========================================" << std::endl;
return true;
#else
std::cout << "Text rendering not available (FreeType not enabled)" << std::endl;
return false;
#endif
}
bool VulkanRenderer::createFontAtlas()
{
// Font atlas creation is now handled in initializeTextRendering
return true;
}
void VulkanRenderer::generateBackgroundQuad(std::vector<Vertex>& vertices,
std::vector<uint16_t>& indices)
{
// Full-screen quad in NDC space
vertices = {
{{-1.0f, -1.0f}, {1.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f}},
{{ 1.0f, -1.0f}, {0.0f, 1.0f, 0.0f, 1.0f}, {1.0f, 0.0f}},
{{ 1.0f, 1.0f}, {0.0f, 0.0f, 1.0f, 1.0f}, {1.0f, 1.0f}},
{{-1.0f, 1.0f}, {1.0f, 1.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}
};
indices = {0, 1, 2, 2, 3, 0};
}
void VulkanRenderer::generateRotatingCircles(std::vector<Vertex>& vertices,
std::vector<uint16_t>& indices,
double rotation)
{
vertices.clear();
indices.clear();
// Safety check for dimensions
if (m_width == 0 || m_height == 0) {
std::cout << "WARNING: generateRotatingCircles called with zero dimensions!" << std::endl;
return;
}
// Use screen coordinates (Y-down): center is at middle of screen
float centerX = m_width / 2.0f;
float centerY = m_height / 2.0f;
int numCircles = 8;
float orbitRadius = 80.0f; // Match CustomWidget
float circleRadius = 15.0f; // Match CustomWidget
for (int i = 0; i < numCircles; i++) {
float angle = (i * 2.0f * M_PI / numCircles) + rotation * M_PI / 180.0f;
float x = centerX + orbitRadius * cos(angle);
// Y-down screen coords: positive sin moves down
float y = centerY + orbitRadius * sin(angle);
// HSV to RGB for color - more vibrant colors
float hue = fmod((i * 360.0f / numCircles + rotation), 360.0f) / 360.0f;
float r = std::abs(sin(hue * 6.28318f)) * 0.8f + 0.2f;
float g = std::abs(sin((hue + 0.33f) * 6.28318f)) * 0.8f + 0.2f;
float b = std::abs(sin((hue + 0.66f) * 6.28318f)) * 0.8f + 0.2f;
// Create circle as triangles
int segments = 16;
uint16_t centerIdx = vertices.size();
// Center vertex with more opaque alpha
vertices.push_back({{x, y}, {r, g, b, 0.9f}, {0.5f, 0.5f}});
for (int j = 0; j <= segments; j++) {
float segAngle = j * 2.0f * M_PI / segments;
float vx = x + circleRadius * cos(segAngle);
float vy = y + circleRadius * sin(segAngle);
vertices.push_back({{vx, vy}, {r, g, b, 0.9f}, {0.0f, 0.0f}});
if (j > 0) {
indices.push_back(centerIdx);
indices.push_back(centerIdx + j);
indices.push_back(centerIdx + j + 1);
}
}
}
}
void VulkanRenderer::generateWaveEffect(std::vector<Vertex>& vertices,
std::vector<uint16_t>& indices,
double wavePhase)
{
vertices.clear();
indices.clear();
// Safety check for dimensions
if (m_width == 0 || m_height == 0) {
std::cout << "WARNING: generateWaveEffect called with zero dimensions!" << std::endl;
return;
}
int numPoints = m_width / 5 + 1;
float waveY = m_height * 0.7f;
float amplitude = 30.0f;
// First wave - screen coordinates (Y-down)
for (int i = 0; i < numPoints; i++) {
float x = i * 5.0f;
float y = waveY + amplitude * sin(wavePhase + x * 0.02);
vertices.push_back({{x, y}, {1.0f, 1.0f, 1.0f, 0.6f}, {0.0f, 0.0f}});
}
// Line list indices for first wave (pairs of points)
for (int i = 0; i < numPoints - 1; i++) {
indices.push_back(i);
indices.push_back(i + 1);
}
// Second wave (phase shifted) - screen coordinates (Y-down)
uint16_t offset = vertices.size();
for (int i = 0; i < numPoints; i++) {
float x = i * 5.0f;
float y = waveY + amplitude * sin(wavePhase + M_PI + x * 0.02);
vertices.push_back({{x, y}, {1.0f, 1.0f, 0.4f, 0.5f}, {0.0f, 0.0f}});
}
// Line list indices for second wave (pairs of points)
for (int i = 0; i < numPoints - 1; i++) {
indices.push_back(offset + i);
indices.push_back(offset + i + 1);
}
}
float VulkanRenderer::calculateTextWidth(const std::string& text, float scale)
{
if (m_charInfoMap.empty()) {
return 0.0f;
}
float width = 0.0f;
for (size_t i = 0; i < text.length(); i++) {
char c = text[i];
// Skip newlines and characters not in atlas
if (c == '\n' || m_charInfoMap.find(c) == m_charInfoMap.end()) {
continue;
}
const CharInfo& ch = m_charInfoMap[c];
width += ch.advance * scale;
}
return width;
}
void VulkanRenderer::generateTextQuads(const std::string& text, float x, float y,
float scale, const float color[4],
std::vector<Vertex>& vertices,
std::vector<uint16_t>& indices)
{
if (m_charInfoMap.empty()) {
return; // No font loaded
}
float currentX = x;
float currentY = y;
for (size_t i = 0; i < text.length(); i++) {
char c = text[i];
// Handle newline
if (c == '\n') {
currentX = x;
currentY += 60.0f * scale; // Line height
continue;
}
// Skip characters not in atlas
if (m_charInfoMap.find(c) == m_charInfoMap.end()) {
continue;
}
const CharInfo& ch = m_charInfoMap[c];
float xpos = currentX + ch.bearing[0] * scale;
float ypos = currentY - ch.bearing[1] * scale;
float w = ch.size[0] * scale;
float h = ch.size[1] * scale;
// Use pixel coordinates (shader will convert to NDC)
float x0 = xpos;
float y0 = ypos;
float x1 = xpos + w;
float y1 = ypos + h;
// Texture coordinates
float u0 = ch.texCoords[0];
float v0 = ch.texCoords[1];
float u1 = ch.texCoords[0] + ch.texCoords[2];
float v1 = ch.texCoords[1] + ch.texCoords[3];
uint16_t startIdx = vertices.size();
// Create quad
Vertex v0_vert = {{x0, y0}, {color[0], color[1], color[2], color[3]}, {u0, v0}};
Vertex v1_vert = {{x1, y0}, {color[0], color[1], color[2], color[3]}, {u1, v0}};
Vertex v2_vert = {{x1, y1}, {color[0], color[1], color[2], color[3]}, {u1, v1}};
Vertex v3_vert = {{x0, y1}, {color[0], color[1], color[2], color[3]}, {u0, v1}};
vertices.push_back(v0_vert);
vertices.push_back(v1_vert);
vertices.push_back(v2_vert);
vertices.push_back(v3_vert);
// Two triangles
indices.push_back(startIdx + 0);
indices.push_back(startIdx + 1);
indices.push_back(startIdx + 2);
indices.push_back(startIdx + 2);
indices.push_back(startIdx + 3);
indices.push_back(startIdx + 0);
currentX += ch.advance * scale;
}
}
void VulkanRenderer::drawBackground(VkCommandBuffer commandBuffer, int frameCount)
{
if (m_backgroundPipeline == VK_NULL_HANDLE || m_backgroundVertexBuffer == VK_NULL_HANDLE ||
m_backgroundPipelineLayout == VK_NULL_HANDLE || m_descriptorSets.empty()) {
return;
}
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_backgroundPipeline);
// Use the same frameIndex that was used to update the uniform buffer
uint32_t frameIndex = frameCount % MAX_FRAMES_IN_FLIGHT;
if (frameIndex >= m_descriptorSets.size()) {
return;
}
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
m_backgroundPipelineLayout, 0, 1, &m_descriptorSets[frameIndex], 0, nullptr);
VkBuffer vertexBuffers[] = {m_backgroundVertexBuffer};
VkDeviceSize offsets[] = {0};
vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);
vkCmdBindIndexBuffer(commandBuffer, m_backgroundIndexBuffer, 0, VK_INDEX_TYPE_UINT16);
vkCmdDrawIndexed(commandBuffer, m_backgroundIndexCount, 1, 0, 0, 0);
}
void VulkanRenderer::drawGeometry(VkCommandBuffer commandBuffer, int frameCount, double rotation, double wavePhase)
{
if (m_geometryPipeline == VK_NULL_HANDLE || m_geometryPipelineLayout == VK_NULL_HANDLE ||
m_descriptorSets.empty()) {
return;
}
// Use the same frameIndex that was used to update the uniform buffer
uint32_t frameIndex = frameCount % MAX_FRAMES_IN_FLIGHT;
if (frameIndex >= m_descriptorSets.size()) {
return;
}
// Draw circles with geometry pipeline
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_geometryPipeline);
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
m_geometryPipelineLayout, 0, 1, &m_descriptorSets[frameIndex], 0, nullptr);
if (m_circleVertexBuffer != VK_NULL_HANDLE && m_circleIndexBuffer != VK_NULL_HANDLE && m_circleIndexCount > 0) {
VkBuffer vertexBuffers[] = {m_circleVertexBuffer};
VkDeviceSize offsets[] = {0};
vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);
vkCmdBindIndexBuffer(commandBuffer, m_circleIndexBuffer, 0, VK_INDEX_TYPE_UINT16);
vkCmdDrawIndexed(commandBuffer, m_circleIndexCount, 1, 0, 0, 0);
}
// Draw waves with line pipeline
if (m_linePipeline != VK_NULL_HANDLE && m_linePipelineLayout != VK_NULL_HANDLE &&
m_waveVertexBuffer != VK_NULL_HANDLE && m_waveIndexBuffer != VK_NULL_HANDLE && m_waveIndexCount > 0) {
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_linePipeline);
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
m_linePipelineLayout, 0, 1, &m_descriptorSets[frameIndex], 0, nullptr);
VkBuffer waveBuffers[] = {m_waveVertexBuffer};
VkDeviceSize offsets[] = {0};
vkCmdBindVertexBuffers(commandBuffer, 0, 1, waveBuffers, offsets);
vkCmdBindIndexBuffer(commandBuffer, m_waveIndexBuffer, 0, VK_INDEX_TYPE_UINT16);
vkCmdDrawIndexed(commandBuffer, m_waveIndexCount, 1, 0, 0, 0);
}
}
void VulkanRenderer::drawText(VkCommandBuffer commandBuffer, int frameCount,
bool paintingEnabled, const std::string& lockInfo)
{
static int textDebugCounter = 0;
if (m_charInfoMap.empty() || m_textPipeline == VK_NULL_HANDLE) {
if (textDebugCounter % 300 == 1) {
std::cout << "Text rendering not available - skipping" << std::endl;
}
return; // Text rendering not available
}
// Bind text pipeline
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_textPipeline);
// Bind descriptor sets for text rendering (if using textures)
if (!m_descriptorSets.empty()) {
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
m_textPipelineLayout, 0, 1,
&m_descriptorSets[0], 0, nullptr);
}
std::vector<Vertex> vertices;
std::vector<uint16_t> indices;
float white[4] = {1.0f, 1.0f, 1.0f, 1.0f};
float green[4] = {0.78f, 1.0f, 0.78f, 1.0f};
float yellow[4] = {1.0f, 1.0f, 0.59f, 1.0f};
float magenta[4] = {1.0f, 0.78f, 1.0f, 1.0f};
float gray[4] = {0.78f, 0.78f, 0.78f, 1.0f};
// Use reference resolution (1920x1080) for consistent text size across different window sizes
const float refWidth = m_width;
const float refHeight = m_height;
// Calculate uniform scale factor based on height to avoid text distortion
// Use height as the primary factor since vertical space is usually more constrained
float scale = m_height / refHeight;
// For positioning, we still need to account for the actual window dimensions
// But use the same scale factor for both X and Y to prevent distortion
float scaleX = scale;
float scaleY = scale;
if (!paintingEnabled) {
// Screen locked state
std::string title = "PAINTING DISABLED";
std::string subtitle = "(Screen Locked)";
// Calculate centered positions
float titleScale = 0.6f;
float subtitleScale = 0.5f;
float titleWidth = calculateTextWidth(title, titleScale);
float subtitleWidth = calculateTextWidth(subtitle, subtitleScale);
float titleX = (refWidth - titleWidth) / 2.0f * scaleX;
float titleY = (refHeight / 2.0f - 30.0f) * scaleY;
float subtitleX = (refWidth - subtitleWidth) / 2.0f * scaleX;
float subtitleY = (refHeight / 2.0f + 30.0f) * scaleY;
generateTextQuads(title, titleX, titleY, titleScale, white, vertices, indices);
generateTextQuads(subtitle, subtitleX, subtitleY, subtitleScale, gray, vertices, indices);
// Stats
std::string stats = "Total Frames Painted: " + std::to_string(frameCount);
generateTextQuads(stats, 20.0f * scaleX, m_height - 60.0f * scaleY, 0.3f, gray, vertices, indices);
} else {
// Active rendering state
std::string title = "Screen Lock Detector - Painting Active";
// Calculate centered position for title
float titleScale = 0.7f;
float titleWidth = calculateTextWidth(title, titleScale);
float titleX = (refWidth - titleWidth) / 2.0f * scaleX;
generateTextQuads(title, titleX - 10.0f * scaleX, 60.0f * scaleY, titleScale, white, vertices, indices);
// Stats info box
std::string frameStr = "Frame Count: " + std::to_string(frameCount);
std::string fpsStr = "FPS: ~60";
std::string rotStr = "Rotation: " + std::to_string((int)m_ubo.rotation) + "°";
std::string timeStr = "Running Time: " + std::to_string((int)m_ubo.time) + "s";
generateTextQuads(frameStr, 20.0f * scaleX, 130.0f * scaleY, 0.3f, green, vertices, indices);
generateTextQuads(fpsStr, 20.0f * scaleX, 150.0f * scaleY, 0.3f, green, vertices, indices);
generateTextQuads(rotStr, 20.0f * scaleX, 170.0f * scaleY, 0.3f, green, vertices, indices);
generateTextQuads(timeStr, 20.0f * scaleX, 190.0f * scaleY, 0.3f, green, vertices, indices);
// Lock info (if available)
if (!lockInfo.empty()) {
std::string lockTitle = "Last Lock Info:";
generateTextQuads(lockTitle, 20.0f * scaleX, 220.0f * scaleY, 0.4f, magenta, vertices, indices);
// Parse lock info (assuming format from lockInfo string)
size_t pos = 0;
int line = 0;
std::string info = lockInfo;
while ((pos = info.find('\n')) != std::string::npos) {
std::string token = info.substr(0, pos);
if (!token.empty()) {
generateTextQuads(token, 20.0f * scaleX, (240.0f + line * 20.0f) * scaleY, 0.3f, magenta, vertices, indices);
line++;
}
info.erase(0, pos + 1);
}
if (!info.empty()) {
generateTextQuads(info, 20.0f * scaleX, (240.0f + line * 20.0f) * scaleY, 0.3f, magenta, vertices, indices);
}
}
// Hint at bottom - centered
std::string hint = "Lock your screen to see the painting stop automatically!";
float hintScale = 0.5f;
float hintWidth = calculateTextWidth(hint, hintScale);
float hintX = (refWidth - hintWidth) / 2.0f * scaleX;
generateTextQuads(hint, hintX, m_height - 50.0f * scaleY, hintScale, yellow, vertices, indices);
}
// Create/update text buffers
if (vertices.empty() || indices.empty()) {
if (textDebugCounter % 300 == 1) {
std::cout << "No text vertices generated - skipping" << std::endl;
}
return;
}
// 首次创建 text buffers使用 host-visible 内存)
if (m_textVertexBuffer == VK_NULL_HANDLE) {
VkDeviceSize textVertexSize = sizeof(Vertex) * 4096; // 预分配足够空间
VkDeviceSize textIndexSize = sizeof(uint16_t) * 6144;
if (!createDynamicVertexBuffer(textVertexSize, m_textVertexBuffer, m_textVertexMemory)) {
logError("Failed to create dynamic text vertex buffer");
return;
}
if (!createDynamicIndexBuffer(textIndexSize, m_textIndexBuffer, m_textIndexMemory)) {
logError("Failed to create dynamic text index buffer");
vkDestroyBuffer(m_device, m_textVertexBuffer, nullptr);
vkFreeMemory(m_device, m_textVertexMemory, nullptr);
m_textVertexBuffer = VK_NULL_HANDLE;
m_textVertexMemory = VK_NULL_HANDLE;
return;
}
std::cout << "Dynamic text buffers created (host-visible, no staging)" << std::endl;
}
// 每帧直接更新 buffer 内容(通过内存映射,无需命令队列)
// 使用 host-visible buffers 后,更新成本极低,无需缓存
if (!vertices.empty() && !indices.empty()) {
if (!updateDynamicBuffer(m_textVertexMemory, vertices.data(),
sizeof(Vertex) * vertices.size())) {
logError("Failed to update text vertex buffer");
return;
}
if (!updateDynamicBuffer(m_textIndexMemory, indices.data(),
sizeof(uint16_t) * indices.size())) {
logError("Failed to update text index buffer");
return;
}
m_textIndexCount = indices.size();
}
// Draw text with newly created buffers
VkBuffer vertexBuffers[] = {m_textVertexBuffer};
VkDeviceSize offsets[] = {0};
vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);
vkCmdBindIndexBuffer(commandBuffer, m_textIndexBuffer, 0, VK_INDEX_TYPE_UINT16);
vkCmdDrawIndexed(commandBuffer, m_textIndexCount, 1, 0, 0, 0);
}
void VulkanRenderer::logError(const char* message)
{
if (m_errorCallback) {
m_errorCallback(message);
} else {
std::cerr << "VulkanRenderer Error: " << message << std::endl;
}
}