#include "vulkanrenderer.h" #define VK_NO_PROTOTYPES #include #include #include #include #include #ifdef ENABLE_FREETYPE #include #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(m_width); m_ubo.resolution[1] = static_cast(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 bgVertices; std::vector 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 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(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(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(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(m_width); m_ubo.resolution[1] = static_cast(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 circleVertices, waveVertices; std::vector 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(frameCount); m_ubo.resolution[0] = static_cast(m_width); m_ubo.resolution[1] = static_cast(m_height); m_ubo.rotation = static_cast(rotationAngle); m_ubo.wavePhase = static_cast(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(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(m_width); viewport.height = static_cast(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(frameCount); m_ubo.rotation = static_cast(rotation); m_ubo.wavePhase = static_cast(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 attachments; if (m_msaaSamples > VK_SAMPLE_COUNT_1_BIT) { // MSAA color attachment VkAttachmentDescription colorAttachment = {}; colorAttachment.format = static_cast(m_swapchainFormat); colorAttachment.samples = static_cast(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(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(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(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& code) { VkShaderModuleCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); createInfo.pCode = reinterpret_cast(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(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(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(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 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(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 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 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 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 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& 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& 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& vertices, std::vector& 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& vertices, std::vector& 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& vertices, std::vector& 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& vertices, std::vector& 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 vertices; std::vector 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; } }