2830 lines
113 KiB
C++
2830 lines
113 KiB
C++
#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, ©Region);
|
||
|
||
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, ®ion);
|
||
|
||
// 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;
|
||
}
|
||
}
|