增加freetype支持
This commit is contained in:
parent
e8b9571d25
commit
25e6159176
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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 "========================================"
|
||||
|
|
@ -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, ®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()
|
||||
{
|
||||
// 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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue