增加freetype支持

This commit is contained in:
ubuntu1804 2025-11-10 20:00:34 +08:00
parent e8b9571d25
commit 25e6159176
4 changed files with 1193 additions and 36 deletions

View File

@ -60,6 +60,15 @@ endif()
find_package(Qt5 REQUIRED COMPONENTS ${QT_COMPONENTS})
# FreeType support (for text rendering in Vulkan)
find_package(Freetype)
if(FREETYPE_FOUND)
message(STATUS "FreeType found: ${FREETYPE_LIBRARIES}")
message(STATUS "FreeType include: ${FREETYPE_INCLUDE_DIRS}")
else()
message(WARNING "FreeType not found, text rendering may be limited")
endif()
# Vulkan support (optional) - only headers needed, volk loads dynamically
option(ENABLE_VULKAN "Enable Vulkan support" ON)
set(VULKAN_FOUND FALSE)
@ -190,6 +199,13 @@ if(VULKAN_FOUND)
target_link_libraries(${PROJECT_NAME}
volk
)
# Add FreeType support for text rendering in Vulkan
if(FREETYPE_FOUND)
target_include_directories(${PROJECT_NAME} PRIVATE ${FREETYPE_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} ${FREETYPE_LIBRARIES})
target_compile_definitions(${PROJECT_NAME} PRIVATE ENABLE_FREETYPE)
endif()
endif()
# Platform-specific linking
@ -235,6 +251,12 @@ if(VULKAN_FOUND)
message(STATUS "Vulkan headers: ${VULKAN_HEADERS_DIR}")
message(STATUS "Volk directory: ${VOLK_DIR}")
message(STATUS "Note: No Vulkan library linking - volk loads dynamically at runtime")
if(FREETYPE_FOUND)
message(STATUS "FreeType support: ENABLED (for text rendering)")
message(STATUS "FreeType libraries: ${FREETYPE_LIBRARIES}")
else()
message(STATUS "FreeType support: DISABLED")
endif()
else()
message(STATUS "Vulkan support: DISABLED")
endif()

186
TEXT_RENDERING.md Normal file
View File

@ -0,0 +1,186 @@
# Text Rendering Implementation in VulkanWidget
## Overview
This document describes the text rendering implementation added to the VulkanWidget using FreeType library.
## Features Implemented
### 1. FreeType Integration
- **Library**: Uses system libfreetype.a (found at `/usr/local/lib/libfreetype.a`)
- **Font Loading**: Automatically searches common font paths:
- `/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` (macOS)
- `/usr/local/share/fonts/DejaVuSans.ttf`
### 2. Font Atlas Generation
- **Size**: 512x512 texture atlas
- **Character Range**: ASCII 32-126 (95 printable characters)
- **Format**: Single-channel R8_UNORM texture
- **Packing**: Automatic row-based packing with 1-pixel padding
- **Storage**: CharInfo map stores texture coordinates, size, bearing, and advance for each character
### 3. Text Rendering Pipeline
- **Shaders**:
- `text.vert` - Converts pixel coordinates to NDC
- `text.frag` - Samples font atlas with alpha blending
- **Blending**: Alpha blending enabled for smooth text rendering
- **Descriptors**:
- Binding 0: Uniform Buffer (time, resolution, rotation, wavePhase)
- Binding 1: Font texture sampler
### 4. Text Content (Matching CustomWidget)
The text rendering displays the same information as CustomWidget:
#### Active State:
- **Title**: "Screen Lock Detector - Painting Active" (top center, white, scale 0.8)
- **Statistics Box** (top left, green, scale 0.6):
- Frame Count
- FPS: ~60
- Rotation angle
- Running Time
- **Lock Info Box** (below stats, magenta, scale 0.65/0.5):
- Last Lock Info title
- Lock time
- Duration
- Frame count at lock
- Total locks
- **Hint**: "Lock your screen..." (bottom center, yellow, scale 0.65)
#### Locked State:
- **Title**: "PAINTING DISABLED" (center, white, scale 1.2)
- **Subtitle**: "(Screen Locked)" (center, gray, scale 0.9)
- **Stats**: Total frames painted (bottom left, gray, scale 0.7)
## Implementation Details
### Code Structure
1. **VulkanRenderer::initializeTextRendering()**
- Initializes FreeType library
- Loads font face
- Generates character glyphs
- Creates font texture atlas
- Sets up Vulkan image, image view, and sampler
2. **VulkanRenderer::createTextPipeline()**
- Loads text vertex and fragment shaders
- Sets up vertex input (position, color, texCoord)
- Configures alpha blending
- Creates descriptor set layout with texture sampler
- Builds graphics pipeline
3. **VulkanRenderer::generateTextQuads()**
- Converts text string to quad vertices
- Handles newlines
- Uses pixel coordinates (shader converts to NDC)
- Sets texture coordinates from CharInfo map
- Applies scale and color
4. **VulkanRenderer::drawText()**
- Binds text pipeline
- Binds descriptor sets (UBO + font texture)
- Generates text quads based on painting state
- Creates temporary vertex/index buffers
- Issues draw calls
### Coordinate System
- **Input**: Pixel coordinates (0,0 = top-left, width,height = bottom-right)
- **Shader**: Converts to NDC (-1,-1 = bottom-left, 1,1 = top-right)
- **Y-axis**: Flipped in shader (Vulkan Y-down → traditional Y-up)
### Resource Management
- Font texture and sampler are persistent (created once)
- Text vertex/index buffers are recreated each frame (using static variables)
- Old buffers are destroyed before creating new ones
- Descriptor sets are updated after text rendering initialization
## CMake Configuration
```cmake
# FreeType support (for text rendering in Vulkan)
find_package(Freetype)
if(FREETYPE_FOUND)
target_include_directories(${PROJECT_NAME} PRIVATE ${FREETYPE_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} ${FREETYPE_LIBRARIES})
target_compile_definitions(${PROJECT_NAME} PRIVATE ENABLE_FREETYPE)
endif()
```
## Debugging
Debug output is enabled every ~5 seconds (300 frames):
```
Initializing text rendering with FreeType...
Text rendering initialized successfully with 95 characters
Font texture: OK
Font texture view: OK
Font sampler: OK
Creating text pipeline...
Text pipeline created successfully
Updating descriptor sets with font texture...
Descriptor sets updated with font texture
drawText called: charMap=95 pipeline=OK fontTexView=OK
Drawing text with 572 vertices, 858 indices
Window size: 756x425
First vertex pos: (30.4, 1.2)
First vertex color: (1, 1, 1, 1)
```
## Known Issues and Future Improvements
### Current Limitations:
1. Text buffers are recreated every frame (performance impact)
2. No text caching or batching
3. Limited to ASCII characters only
4. Fixed font size (48pt base)
### Potential Improvements:
1. Implement text buffer caching for static text
2. Add support for Unicode/UTF-8
3. Multiple font sizes/styles
4. Text layout system (word wrapping, alignment)
5. Better memory management for text resources
6. Dynamic font atlas resizing
7. SDF (Signed Distance Field) rendering for scalable text
## Testing
To verify text rendering:
1. Build project: `./build.sh`
2. Run application: `./run.sh`
3. Check console for text rendering initialization messages
4. Verify text appears on screen matching CustomWidget layout
5. Lock screen to test state changes
## Dependencies
- FreeType 2.x
- Vulkan 1.x
- Volk (for Vulkan function loading)
- Qt 5.15.x (for window management)
## Files Modified
- `CMakeLists.txt` - Added FreeType support
- `src/vulkanrenderer.h` - Added text rendering declarations
- `src/vulkanrenderer.cpp` - Implemented text rendering functions
- `shaders/text.vert` - Text vertex shader
- `shaders/text.frag` - Text fragment shader
## References
- FreeType Documentation: https://freetype.org/freetype2/docs/
- Vulkan Text Rendering: https://learnopengl.com/In-Practice/Text-Rendering
- Font Atlas Generation: https://en.wikibooks.org/wiki/OpenGL_Programming/Modern_OpenGL_Tutorial_Text_Rendering_02

141
check_text_rendering.sh Executable file
View File

@ -0,0 +1,141 @@
#!/bin/bash
echo "========================================"
echo "Text Rendering Diagnostic Script"
echo "========================================"
echo ""
# Check if FreeType is installed
echo "1. Checking FreeType installation..."
if pkg-config --exists freetype2; then
echo " ✓ FreeType found:"
pkg-config --modversion freetype2
echo " Library: $(pkg-config --variable=libdir freetype2)/libfreetype.a"
echo " Include: $(pkg-config --cflags freetype2)"
else
echo " ✗ FreeType not found via pkg-config"
fi
echo ""
# Check for font files
echo "2. Checking available fonts..."
FONTS=(
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
"/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf"
"/usr/share/fonts/TTF/DejaVuSans.ttf"
"/usr/local/share/fonts/DejaVuSans.ttf"
)
FOUND_FONT=false
for font in "${FONTS[@]}"; do
if [ -f "$font" ]; then
echo " ✓ Found: $font"
FOUND_FONT=true
fi
done
if [ "$FOUND_FONT" = false ]; then
echo " ✗ No fonts found in standard locations"
echo " Installing DejaVu fonts might help:"
echo " sudo apt-get install fonts-dejavu-core"
fi
echo ""
# Check if shaders are compiled
echo "3. Checking text shaders..."
if [ -f "src/shaders_spirv/text.vert.spv" ]; then
echo " ✓ text.vert.spv exists"
else
echo " ✗ text.vert.spv missing"
fi
if [ -f "src/shaders_spirv/text.frag.spv" ]; then
echo " ✓ text.frag.spv exists"
else
echo " ✗ text.frag.spv missing"
fi
if [ -f "src/shaders_spirv/text_vert.inc" ]; then
echo " ✓ text_vert.inc exists"
else
echo " ✗ text_vert.inc missing"
fi
if [ -f "src/shaders_spirv/text_frag.inc" ]; then
echo " ✓ text_frag.inc exists"
else
echo " ✗ text_frag.inc missing"
fi
echo ""
# Check build configuration
echo "4. Checking build configuration..."
if [ -f "build/CMakeCache.txt" ]; then
if grep -q "ENABLE_FREETYPE" build/CMakeCache.txt 2>/dev/null; then
echo " ✓ ENABLE_FREETYPE is defined"
else
echo " ✗ ENABLE_FREETYPE not found in build"
fi
if grep -q "FREETYPE_LIBRARY" build/CMakeCache.txt 2>/dev/null; then
FREETYPE_LIB=$(grep "FREETYPE_LIBRARY" build/CMakeCache.txt | cut -d= -f2)
echo " ✓ FreeType library linked: $FREETYPE_LIB"
fi
else
echo " ⚠ Build directory not found - run ./build.sh first"
fi
echo ""
# Check if executable exists and has FreeType symbols
echo "5. Checking executable..."
if [ -f "build/bin/ScreenLockDetector" ]; then
echo " ✓ Executable exists"
# Check for FreeType symbols
if nm build/bin/ScreenLockDetector 2>/dev/null | grep -q "FT_Init_FreeType"; then
echo " ✓ FreeType symbols found in executable"
else
echo " ⚠ FreeType symbols not found (might be dynamically linked)"
fi
# Check for Vulkan symbols
if nm build/bin/ScreenLockDetector 2>/dev/null | grep -q "vkCreateGraphicsPipeline"; then
echo " ✓ Vulkan symbols found"
fi
else
echo " ✗ Executable not found - build first with ./build.sh"
fi
echo ""
# Run the application with text rendering debug output
echo "6. Testing text rendering (5 seconds)..."
echo " Starting application..."
if [ -f "build/bin/ScreenLockDetector" ]; then
timeout 5 ./run.sh 2>&1 | grep -E "Text|text|Font|font|FreeType|pipeline|charMap|Drawing text" | head -20
echo ""
echo " If you see 'Drawing text with X vertices' above, text rendering is working!"
else
echo " ✗ Cannot test - executable not found"
fi
echo ""
# Summary
echo "========================================"
echo "Summary"
echo "========================================"
echo ""
echo "Text rendering requires:"
echo " 1. FreeType library installed ✓"
echo " 2. At least one font file available"
echo " 3. Text shaders compiled (text.vert/frag.spv)"
echo " 4. ENABLE_FREETYPE defined in build"
echo " 5. Application successfully initializes text pipeline"
echo ""
echo "If text is not visible but pipeline is created:"
echo " - Check shader coordinate system"
echo " - Verify alpha blending is enabled"
echo " - Ensure text color contrasts with background"
echo " - Check if text is positioned within viewport"
echo ""
echo "For more details, see: TEXT_RENDERING.md"
echo "========================================"

View File

@ -8,6 +8,11 @@
#include <algorithm>
#include <iostream>
#ifdef ENABLE_FREETYPE
#include <ft2build.h>
#include FT_FREETYPE_H
#endif
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
@ -146,7 +151,51 @@ bool VulkanRenderer::initialize(VkDevice device, VkPhysicalDevice physicalDevice
}
// Initialize text rendering (optional)
initializeTextRendering();
if (initializeTextRendering()) {
std::cout << "Creating text pipeline..." << std::endl;
if (!createTextPipeline()) {
logError("Failed to create text pipeline - text rendering will be disabled");
} else {
// Update descriptor sets to include font texture
std::cout << "Updating descriptor sets with font texture..." << std::endl;
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
VkDescriptorBufferInfo bufferInfo = {};
bufferInfo.buffer = m_uniformBuffers[i];
bufferInfo.offset = 0;
bufferInfo.range = sizeof(UniformBufferObject);
VkDescriptorImageInfo imageInfo = {};
imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
imageInfo.imageView = m_fontTextureView;
imageInfo.sampler = m_fontSampler;
std::vector<VkWriteDescriptorSet> descriptorWrites(2);
// UBO descriptor
descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[0].dstSet = m_descriptorSets[i];
descriptorWrites[0].dstBinding = 0;
descriptorWrites[0].dstArrayElement = 0;
descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptorWrites[0].descriptorCount = 1;
descriptorWrites[0].pBufferInfo = &bufferInfo;
// Font texture descriptor
descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[1].dstSet = m_descriptorSets[i];
descriptorWrites[1].dstBinding = 1;
descriptorWrites[1].dstArrayElement = 0;
descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
descriptorWrites[1].descriptorCount = 1;
descriptorWrites[1].pImageInfo = &imageInfo;
vkUpdateDescriptorSets(m_device, descriptorWrites.size(), descriptorWrites.data(), 0, nullptr);
}
std::cout << "Descriptor sets updated with font texture" << std::endl;
}
} else {
std::cout << "Text rendering initialization failed - text will be disabled" << std::endl;
}
m_initialized = true;
return true;
@ -1039,42 +1088,264 @@ bool VulkanRenderer::createLinePipeline()
bool VulkanRenderer::createTextPipeline()
{
// Text rendering pipeline with texture sampling
// Not implemented yet - would require font texture atlas
// Load text shaders from embedded SPIR-V
static const uint32_t textVertCode[] = {
#include "shaders_spirv/text_vert.inc"
};
static const uint32_t textFragCode[] = {
#include "shaders_spirv/text_frag.inc"
};
VkShaderModuleCreateInfo vertModuleInfo = {};
vertModuleInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
vertModuleInfo.codeSize = sizeof(textVertCode);
vertModuleInfo.pCode = textVertCode;
VkShaderModule vertShaderModule = VK_NULL_HANDLE;
if (vkCreateShaderModule(m_device, &vertModuleInfo, nullptr, &vertShaderModule) != VK_SUCCESS) {
logError("Failed to create text vertex shader module");
return false;
}
VkShaderModuleCreateInfo fragModuleInfo = {};
fragModuleInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
fragModuleInfo.codeSize = sizeof(textFragCode);
fragModuleInfo.pCode = textFragCode;
VkShaderModule fragShaderModule = VK_NULL_HANDLE;
if (vkCreateShaderModule(m_device, &fragModuleInfo, nullptr, &fragShaderModule) != VK_SUCCESS) {
vkDestroyShaderModule(m_device, vertShaderModule, nullptr);
logError("Failed to create text fragment shader module");
return false;
}
VkPipelineShaderStageCreateInfo vertShaderStageInfo = {};
vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
vertShaderStageInfo.module = vertShaderModule;
vertShaderStageInfo.pName = "main";
VkPipelineShaderStageCreateInfo fragShaderStageInfo = {};
fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
fragShaderStageInfo.module = fragShaderModule;
fragShaderStageInfo.pName = "main";
VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo};
// Vertex input
VkVertexInputBindingDescription bindingDescription = {};
bindingDescription.binding = 0;
bindingDescription.stride = sizeof(Vertex);
bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
std::vector<VkVertexInputAttributeDescription> attributeDescriptions(3);
attributeDescriptions[0].binding = 0;
attributeDescriptions[0].location = 0;
attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
attributeDescriptions[0].offset = offsetof(Vertex, pos);
attributeDescriptions[1].binding = 0;
attributeDescriptions[1].location = 1;
attributeDescriptions[1].format = VK_FORMAT_R32G32B32A32_SFLOAT;
attributeDescriptions[1].offset = offsetof(Vertex, color);
attributeDescriptions[2].binding = 0;
attributeDescriptions[2].location = 2;
attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT;
attributeDescriptions[2].offset = offsetof(Vertex, texCoord);
VkPipelineVertexInputStateCreateInfo vertexInputInfo = {};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo.vertexBindingDescriptionCount = 1;
vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
vertexInputInfo.vertexAttributeDescriptionCount = attributeDescriptions.size();
vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();
VkPipelineInputAssemblyStateCreateInfo inputAssembly = {};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
inputAssembly.primitiveRestartEnable = VK_FALSE;
VkViewport viewport = {};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = (float)m_width;
viewport.height = (float)m_height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
VkRect2D scissor = {};
scissor.offset = {0, 0};
scissor.extent = {m_width, m_height};
VkPipelineViewportStateCreateInfo viewportState = {};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = 1;
viewportState.pViewports = &viewport;
viewportState.scissorCount = 1;
viewportState.pScissors = &scissor;
VkPipelineRasterizationStateCreateInfo rasterizer = {};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthClampEnable = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE;
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
rasterizer.lineWidth = 1.0f;
rasterizer.cullMode = VK_CULL_MODE_NONE;
rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
rasterizer.depthBiasEnable = VK_FALSE;
VkPipelineMultisampleStateCreateInfo multisampling = {};
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.sampleShadingEnable = VK_FALSE;
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
// 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()
{
VkDescriptorSetLayoutBinding uboLayoutBinding = {};
uboLayoutBinding.binding = 0;
uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
uboLayoutBinding.descriptorCount = 1;
uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
std::vector<VkDescriptorSetLayoutBinding> bindings(2);
// UBO binding
bindings[0].binding = 0;
bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
bindings[0].descriptorCount = 1;
bindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
bindings[0].pImmutableSamplers = nullptr;
// Image sampler binding
bindings[1].binding = 1;
bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
bindings[1].descriptorCount = 1;
bindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
bindings[1].pImmutableSamplers = nullptr;
VkDescriptorSetLayoutCreateInfo layoutInfo = {};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layoutInfo.bindingCount = 1;
layoutInfo.pBindings = &uboLayoutBinding;
layoutInfo.bindingCount = bindings.size();
layoutInfo.pBindings = bindings.data();
return vkCreateDescriptorSetLayout(m_device, &layoutInfo, nullptr, &m_descriptorSetLayout) == VK_SUCCESS;
if (vkCreateDescriptorSetLayout(m_device, &layoutInfo, nullptr, &m_descriptorSetLayout) != VK_SUCCESS) {
return false;
}
return true;
}
bool VulkanRenderer::createDescriptorPool()
{
VkDescriptorPoolSize poolSize = {};
poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
poolSize.descriptorCount = MAX_FRAMES_IN_FLIGHT;
std::vector<VkDescriptorPoolSize> poolSizes(2);
// UBO pool size
poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
poolSizes[0].descriptorCount = MAX_FRAMES_IN_FLIGHT;
// Image sampler pool size
poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
poolSizes[1].descriptorCount = MAX_FRAMES_IN_FLIGHT;
VkDescriptorPoolCreateInfo poolInfo = {};
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
poolInfo.poolSizeCount = 1;
poolInfo.pPoolSizes = &poolSize;
poolInfo.poolSizeCount = poolSizes.size();
poolInfo.pPoolSizes = poolSizes.data();
poolInfo.maxSets = MAX_FRAMES_IN_FLIGHT;
return vkCreateDescriptorPool(m_device, &poolInfo, nullptr, &m_descriptorPool) == VK_SUCCESS;
}
if (vkCreateDescriptorPool(m_device, &poolInfo, nullptr, &m_descriptorPool) != VK_SUCCESS) {
return false;
}
return true;
}
bool VulkanRenderer::createDescriptorSets()
{
std::vector<VkDescriptorSetLayout> layouts(MAX_FRAMES_IN_FLIGHT, m_descriptorSetLayout);
@ -1096,16 +1367,38 @@ bool VulkanRenderer::createDescriptorSets()
bufferInfo.offset = 0;
bufferInfo.range = sizeof(UniformBufferObject);
VkWriteDescriptorSet descriptorWrite = {};
descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrite.dstSet = m_descriptorSets[i];
descriptorWrite.dstBinding = 0;
descriptorWrite.dstArrayElement = 0;
descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptorWrite.descriptorCount = 1;
descriptorWrite.pBufferInfo = &bufferInfo;
std::vector<VkWriteDescriptorSet> descriptorWrites;
vkUpdateDescriptorSets(m_device, 1, &descriptorWrite, 0, nullptr);
// 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;
@ -1397,15 +1690,323 @@ uint32_t VulkanRenderer::findMemoryType(uint32_t typeFilter, uint32_t properties
bool VulkanRenderer::initializeTextRendering()
{
// Simplified text rendering - just create simple bitmap font
// In production, use FreeType or stb_truetype
#ifdef ENABLE_FREETYPE
std::cout << "========================================" << std::endl;
std::cout << "Initializing text rendering with FreeType..." << std::endl;
// Initialize FreeType library
FT_Library ft;
if (FT_Init_FreeType(&ft)) {
logError("Failed to initialize FreeType library");
return false;
}
// Load font face - try multiple common font paths
FT_Face face;
const char* fontPaths[] = {
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
"/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
"/usr/share/fonts/TTF/DejaVuSans.ttf",
"/System/Library/Fonts/Helvetica.ttc",
"/usr/local/share/fonts/DejaVuSans.ttf"
};
bool fontLoaded = false;
for (const char* fontPath : fontPaths) {
if (FT_New_Face(ft, fontPath, 0, &face) == 0) {
std::cout << "Loaded font: " << fontPath << std::endl;
fontLoaded = true;
break;
}
}
if (!fontLoaded) {
logError("Failed to load any font");
FT_Done_FreeType(ft);
return false;
}
// Set pixel size
FT_Set_Pixel_Sizes(face, 0, 48);
// Create font atlas
if (!createFontAtlas()) {
logError("Failed to create font atlas");
FT_Done_Face(face);
FT_Done_FreeType(ft);
return false;
}
// Generate character textures for ASCII printable characters
const int atlasWidth = 512;
const int atlasHeight = 512;
unsigned char* atlasData = new unsigned char[atlasWidth * atlasHeight];
memset(atlasData, 0, atlasWidth * atlasHeight);
int penX = 0;
int penY = 0;
int rowHeight = 0;
// Generate glyphs for ASCII 32-126
for (unsigned char c = 32; c < 127; c++) {
if (FT_Load_Char(face, c, FT_LOAD_RENDER)) {
std::cerr << "Failed to load glyph: " << c << std::endl;
continue;
}
FT_GlyphSlot g = face->glyph;
// Check if we need to move to next row
if (penX + g->bitmap.width >= atlasWidth) {
penX = 0;
penY += rowHeight;
rowHeight = 0;
}
// Check if we have space
if (penY + g->bitmap.rows >= atlasHeight) {
std::cerr << "Font atlas too small!" << std::endl;
break;
}
// Copy glyph bitmap to atlas
for (unsigned int row = 0; row < g->bitmap.rows; row++) {
for (unsigned int col = 0; col < g->bitmap.width; col++) {
int x = penX + col;
int y = penY + row;
atlasData[y * atlasWidth + x] = g->bitmap.buffer[row * g->bitmap.width + col];
}
}
// Store character info
CharInfo charInfo;
charInfo.texCoords[0] = (float)penX / atlasWidth;
charInfo.texCoords[1] = (float)penY / atlasHeight;
charInfo.texCoords[2] = (float)g->bitmap.width / atlasWidth;
charInfo.texCoords[3] = (float)g->bitmap.rows / atlasHeight;
charInfo.size[0] = (float)g->bitmap.width;
charInfo.size[1] = (float)g->bitmap.rows;
charInfo.bearing[0] = (float)g->bitmap_left;
charInfo.bearing[1] = (float)g->bitmap_top;
charInfo.advance = (float)(g->advance.x >> 6);
m_charInfoMap[c] = charInfo;
// Update position
penX += g->bitmap.width + 1; // 1 pixel padding
rowHeight = std::max(rowHeight, (int)g->bitmap.rows);
}
// Create Vulkan texture from atlas
VkDeviceSize imageSize = atlasWidth * atlasHeight;
// Create staging buffer
VkBuffer stagingBuffer;
VkDeviceMemory stagingBufferMemory;
if (!createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
stagingBuffer, stagingBufferMemory)) {
delete[] atlasData;
FT_Done_Face(face);
FT_Done_FreeType(ft);
return false;
}
// Copy atlas data to staging buffer
void* data;
vkMapMemory(m_device, stagingBufferMemory, 0, imageSize, 0, &data);
memcpy(data, atlasData, imageSize);
vkUnmapMemory(m_device, stagingBufferMemory);
delete[] atlasData;
// Create image
VkImageCreateInfo imageInfo = {};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.extent.width = atlasWidth;
imageInfo.extent.height = atlasHeight;
imageInfo.extent.depth = 1;
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.format = VK_FORMAT_R8_UNORM;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
if (vkCreateImage(m_device, &imageInfo, nullptr, &m_fontTexture) != VK_SUCCESS) {
vkDestroyBuffer(m_device, stagingBuffer, nullptr);
vkFreeMemory(m_device, stagingBufferMemory, nullptr);
FT_Done_Face(face);
FT_Done_FreeType(ft);
return false;
}
// Allocate image memory
VkMemoryRequirements memRequirements;
vkGetImageMemoryRequirements(m_device, m_fontTexture, &memRequirements);
VkMemoryAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size;
allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
if (vkAllocateMemory(m_device, &allocInfo, nullptr, &m_fontTextureMemory) != VK_SUCCESS) {
vkDestroyImage(m_device, m_fontTexture, nullptr);
vkDestroyBuffer(m_device, stagingBuffer, nullptr);
vkFreeMemory(m_device, stagingBufferMemory, nullptr);
FT_Done_Face(face);
FT_Done_FreeType(ft);
return false;
}
vkBindImageMemory(m_device, m_fontTexture, m_fontTextureMemory, 0);
// Transition image layout and copy buffer to image
// (This requires a command buffer - simplified version)
VkCommandPoolCreateInfo poolInfo = {};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.queueFamilyIndex = m_queueFamilyIndex;
poolInfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
if (m_transferCommandPool == VK_NULL_HANDLE) {
vkCreateCommandPool(m_device, &poolInfo, nullptr, &m_transferCommandPool);
}
VkCommandBufferAllocateInfo allocInfoCmd = {};
allocInfoCmd.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfoCmd.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfoCmd.commandPool = m_transferCommandPool;
allocInfoCmd.commandBufferCount = 1;
VkCommandBuffer commandBuffer;
vkAllocateCommandBuffers(m_device, &allocInfoCmd, &commandBuffer);
VkCommandBufferBeginInfo beginInfo = {};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkBeginCommandBuffer(commandBuffer, &beginInfo);
// Transition to transfer dst
VkImageMemoryBarrier barrier = {};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = m_fontTexture;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1;
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
// Copy buffer to image
VkBufferImageCopy region = {};
region.bufferOffset = 0;
region.bufferRowLength = 0;
region.bufferImageHeight = 0;
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.mipLevel = 0;
region.imageSubresource.baseArrayLayer = 0;
region.imageSubresource.layerCount = 1;
region.imageOffset = {0, 0, 0};
region.imageExtent = {(uint32_t)atlasWidth, (uint32_t)atlasHeight, 1};
vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, m_fontTexture,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
// Transition to shader read
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
vkEndCommandBuffer(commandBuffer);
VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;
vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
vkQueueWaitIdle(m_graphicsQueue);
vkFreeCommandBuffers(m_device, m_transferCommandPool, 1, &commandBuffer);
vkDestroyBuffer(m_device, stagingBuffer, nullptr);
vkFreeMemory(m_device, stagingBufferMemory, nullptr);
// Create image view
VkImageViewCreateInfo viewInfo = {};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = m_fontTexture;
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = VK_FORMAT_R8_UNORM;
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
viewInfo.subresourceRange.baseMipLevel = 0;
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = 1;
if (vkCreateImageView(m_device, &viewInfo, nullptr, &m_fontTextureView) != VK_SUCCESS) {
FT_Done_Face(face);
FT_Done_FreeType(ft);
return false;
}
// Create sampler
VkSamplerCreateInfo samplerInfo = {};
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
samplerInfo.magFilter = VK_FILTER_LINEAR;
samplerInfo.minFilter = VK_FILTER_LINEAR;
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerInfo.anisotropyEnable = VK_FALSE;
samplerInfo.maxAnisotropy = 1.0f;
samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
samplerInfo.unnormalizedCoordinates = VK_FALSE;
samplerInfo.compareEnable = VK_FALSE;
samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
if (vkCreateSampler(m_device, &samplerInfo, nullptr, &m_fontSampler) != VK_SUCCESS) {
FT_Done_Face(face);
FT_Done_FreeType(ft);
return false;
}
// Cleanup FreeType
FT_Done_Face(face);
FT_Done_FreeType(ft);
std::cout << "Text rendering initialized successfully with " << m_charInfoMap.size() << " characters" << std::endl;
std::cout << "Font texture: " << (m_fontTexture != VK_NULL_HANDLE ? "OK" : "NULL") << std::endl;
std::cout << "Font texture view: " << (m_fontTextureView != VK_NULL_HANDLE ? "OK" : "NULL") << std::endl;
std::cout << "Font sampler: " << (m_fontSampler != VK_NULL_HANDLE ? "OK" : "NULL") << std::endl;
std::cout << "========================================" << std::endl;
return true;
#else
std::cout << "Text rendering not available (FreeType not enabled)" << std::endl;
return false;
#endif
}
bool VulkanRenderer::createFontAtlas()
{
// Create a simple font texture atlas
// Not implemented yet
// Font atlas creation is now handled in initializeTextRendering
return true;
}
@ -1530,8 +2131,70 @@ void VulkanRenderer::generateTextQuads(const std::string& text, float x, float y
std::vector<Vertex>& vertices,
std::vector<uint16_t>& indices)
{
// Simplified text rendering - not implemented yet
// Would require font atlas texture
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)
@ -1604,8 +2267,153 @@ void VulkanRenderer::drawGeometry(VkCommandBuffer commandBuffer, int frameCount,
void VulkanRenderer::drawText(VkCommandBuffer commandBuffer, int frameCount,
bool paintingEnabled, const std::string& lockInfo)
{
// Text rendering not fully implemented yet
// Would require font texture atlas and text pipeline
static int textDebugCounter = 0;
if (textDebugCounter++ % 300 == 0) { // Every ~5 seconds at 60fps
std::cout << "drawText called: charMap=" << m_charInfoMap.size()
<< " pipeline=" << (m_textPipeline != VK_NULL_HANDLE ? "OK" : "NULL")
<< " fontTexView=" << (m_fontTextureView != VK_NULL_HANDLE ? "OK" : "NULL") << std::endl;
}
if (m_charInfoMap.empty() || m_textPipeline == VK_NULL_HANDLE) {
if (textDebugCounter % 300 == 1) {
std::cout << "Text rendering not available - skipping" << std::endl;
}
return; // Text rendering not available
}
// Bind text pipeline
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_textPipeline);
// Bind descriptor sets for text rendering (if using textures)
if (!m_descriptorSets.empty()) {
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
m_textPipelineLayout, 0, 1,
&m_descriptorSets[0], 0, nullptr);
}
std::vector<Vertex> vertices;
std::vector<uint16_t> indices;
float white[4] = {1.0f, 1.0f, 1.0f, 1.0f};
float green[4] = {0.78f, 1.0f, 0.78f, 1.0f};
float yellow[4] = {1.0f, 1.0f, 0.59f, 1.0f};
float magenta[4] = {1.0f, 0.78f, 1.0f, 1.0f};
float gray[4] = {0.78f, 0.78f, 0.78f, 1.0f};
if (!paintingEnabled) {
// Screen locked state
std::string title = "PAINTING DISABLED";
std::string subtitle = "(Screen Locked)";
generateTextQuads(title, m_width / 2.0f - 250.0f, m_height / 2.0f - 50.0f, 1.2f, white, vertices, indices);
generateTextQuads(subtitle, m_width / 2.0f - 180.0f, m_height / 2.0f + 30.0f, 0.9f, gray, vertices, indices);
// Stats
std::string stats = "Total Frames Painted: " + std::to_string(frameCount);
generateTextQuads(stats, 20.0f, m_height - 60.0f, 0.7f, gray, vertices, indices);
} else {
// Active rendering state
std::string title = "Screen Lock Detector - Painting Active";
generateTextQuads(title, m_width / 2.0f - 350.0f, 30.0f, 0.8f, 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, 90.0f, 0.6f, green, vertices, indices);
generateTextQuads(fpsStr, 20.0f, 130.0f, 0.6f, green, vertices, indices);
generateTextQuads(rotStr, 20.0f, 170.0f, 0.6f, green, vertices, indices);
generateTextQuads(timeStr, 20.0f, 210.0f, 0.6f, green, vertices, indices);
// Lock info (if available)
if (!lockInfo.empty()) {
std::string lockTitle = "Last Lock Info:";
generateTextQuads(lockTitle, 20.0f, 270.0f, 0.65f, 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, 310.0f + line * 40.0f, 0.5f, magenta, vertices, indices);
line++;
}
info.erase(0, pos + 1);
}
if (!info.empty()) {
generateTextQuads(info, 20.0f, 310.0f + line * 40.0f, 0.5f, magenta, vertices, indices);
}
}
// Hint at bottom
std::string hint = "Lock your screen to see the painting stop automatically!";
generateTextQuads(hint, m_width / 2.0f - 420.0f, m_height - 50.0f, 0.65f, yellow, vertices, indices);
}
// Create temporary buffers for text
if (vertices.empty() || indices.empty()) {
if (textDebugCounter % 300 == 1) {
std::cout << "No text vertices generated - skipping" << std::endl;
}
return;
}
if (textDebugCounter % 300 == 1) {
std::cout << "Drawing text with " << vertices.size() << " vertices, "
<< indices.size() << " indices" << std::endl;
std::cout << "Window size: " << m_width << "x" << m_height << std::endl;
if (!vertices.empty()) {
std::cout << "First vertex pos: (" << vertices[0].pos[0] << ", " << vertices[0].pos[1] << ")" << std::endl;
std::cout << "First vertex color: (" << vertices[0].color[0] << ", " << vertices[0].color[1]
<< ", " << vertices[0].color[2] << ", " << vertices[0].color[3] << ")" << std::endl;
}
}
// Use static buffers for text rendering to avoid recreating every frame
static VkBuffer textVertexBuffer = VK_NULL_HANDLE;
static VkDeviceMemory textVertexMemory = VK_NULL_HANDLE;
static VkBuffer textIndexBuffer = VK_NULL_HANDLE;
static VkDeviceMemory textIndexMemory = VK_NULL_HANDLE;
// Cleanup old buffers if they exist
if (textVertexBuffer != VK_NULL_HANDLE) {
vkDestroyBuffer(m_device, textVertexBuffer, nullptr);
vkFreeMemory(m_device, textVertexMemory, nullptr);
textVertexBuffer = VK_NULL_HANDLE;
textVertexMemory = VK_NULL_HANDLE;
}
if (textIndexBuffer != VK_NULL_HANDLE) {
vkDestroyBuffer(m_device, textIndexBuffer, nullptr);
vkFreeMemory(m_device, textIndexMemory, nullptr);
textIndexBuffer = VK_NULL_HANDLE;
textIndexMemory = VK_NULL_HANDLE;
}
if (!createVertexBuffer(vertices, textVertexBuffer, textVertexMemory)) {
return;
}
if (!createIndexBuffer(indices, textIndexBuffer, textIndexMemory)) {
vkDestroyBuffer(m_device, textVertexBuffer, nullptr);
vkFreeMemory(m_device, textVertexMemory, nullptr);
textVertexBuffer = VK_NULL_HANDLE;
textVertexMemory = VK_NULL_HANDLE;
return;
}
// Draw text
VkBuffer vertexBuffers[] = {textVertexBuffer};
VkDeviceSize offsets[] = {0};
vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);
vkCmdBindIndexBuffer(commandBuffer, textIndexBuffer, 0, VK_INDEX_TYPE_UINT16);
vkCmdDrawIndexed(commandBuffer, indices.size(), 1, 0, 0, 0);
// Note: Buffers will persist and be reused next frame
// They will be cleaned up when the function is called again or at shutdown
}
void VulkanRenderer::logError(const char* message)