增加vulkan支持
This commit is contained in:
parent
bff9ded0fa
commit
9e15d9fab8
|
|
@ -1,5 +1,5 @@
|
||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
project(ScreenLockDetector VERSION 1.0.0 LANGUAGES CXX)
|
project(ScreenLockDetector VERSION 1.0.0 LANGUAGES C CXX)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 11)
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
@ -60,6 +60,60 @@ endif()
|
||||||
|
|
||||||
find_package(Qt5 REQUIRED COMPONENTS ${QT_COMPONENTS})
|
find_package(Qt5 REQUIRED COMPONENTS ${QT_COMPONENTS})
|
||||||
|
|
||||||
|
# Vulkan support (optional) - only headers needed, volk loads dynamically
|
||||||
|
option(ENABLE_VULKAN "Enable Vulkan support" ON)
|
||||||
|
set(VULKAN_FOUND FALSE)
|
||||||
|
|
||||||
|
if(ENABLE_VULKAN)
|
||||||
|
# Set Vulkan headers path
|
||||||
|
set(VULKAN_HEADERS_DIR "$ENV{HOME}/sdk/Vulkan-Headers-1.3.302/include")
|
||||||
|
|
||||||
|
# Check if Vulkan headers exist
|
||||||
|
if(EXISTS "${VULKAN_HEADERS_DIR}/vulkan/vulkan.h")
|
||||||
|
set(VULKAN_FOUND TRUE)
|
||||||
|
message(STATUS "Vulkan headers found at: ${VULKAN_HEADERS_DIR}")
|
||||||
|
|
||||||
|
# Add volk source directory
|
||||||
|
set(VOLK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/volk")
|
||||||
|
if(EXISTS "${VOLK_DIR}/volk.c")
|
||||||
|
add_library(volk STATIC ${VOLK_DIR}/volk.c)
|
||||||
|
|
||||||
|
# Disable Qt MOC for volk (it's pure C code)
|
||||||
|
set_target_properties(volk PROPERTIES
|
||||||
|
AUTOMOC OFF
|
||||||
|
AUTOUIC OFF
|
||||||
|
AUTORCC OFF
|
||||||
|
LINKER_LANGUAGE C
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(volk PUBLIC
|
||||||
|
${VOLK_DIR}
|
||||||
|
${VULKAN_HEADERS_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Define platform-specific Vulkan extensions for volk
|
||||||
|
if(WIN32)
|
||||||
|
target_compile_definitions(volk PUBLIC VK_USE_PLATFORM_WIN32_KHR)
|
||||||
|
elseif(APPLE)
|
||||||
|
target_compile_definitions(volk PUBLIC VK_USE_PLATFORM_METAL_EXT)
|
||||||
|
elseif(UNIX)
|
||||||
|
target_compile_definitions(volk PUBLIC VK_USE_PLATFORM_XLIB_KHR)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Volk only needs libdl (or equivalent) for dynamic loading
|
||||||
|
target_link_libraries(volk PUBLIC ${CMAKE_DL_LIBS})
|
||||||
|
message(STATUS "Volk configured with dynamic Vulkan loading")
|
||||||
|
else()
|
||||||
|
message(WARNING "Volk source not found at ${VOLK_DIR}, disabling Vulkan support")
|
||||||
|
set(VULKAN_FOUND FALSE)
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
message(STATUS "Vulkan headers not found at ${VULKAN_HEADERS_DIR}, disabling Vulkan support")
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
message(STATUS "Vulkan support disabled")
|
||||||
|
endif()
|
||||||
|
|
||||||
# Common source files
|
# Common source files
|
||||||
set(SOURCES
|
set(SOURCES
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
|
|
@ -72,6 +126,13 @@ set(HEADERS
|
||||||
src/customwidget.h
|
src/customwidget.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Add Vulkan widget if Vulkan is available
|
||||||
|
if(VULKAN_FOUND)
|
||||||
|
list(APPEND SOURCES src/vulkanwidget.cpp)
|
||||||
|
list(APPEND HEADERS src/vulkanwidget.h)
|
||||||
|
add_definitions(-DENABLE_VULKAN_WIDGET)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Platform-specific source files
|
# Platform-specific source files
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
# macOS specific files
|
# macOS specific files
|
||||||
|
|
@ -113,14 +174,28 @@ target_link_libraries(${PROJECT_NAME}
|
||||||
Qt5::Widgets
|
Qt5::Widgets
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Add Vulkan support if available
|
||||||
|
if(VULKAN_FOUND)
|
||||||
|
target_include_directories(${PROJECT_NAME} PRIVATE
|
||||||
|
${VULKAN_HEADERS_DIR}
|
||||||
|
${VOLK_DIR}
|
||||||
|
)
|
||||||
|
# Only link volk - no Vulkan library needed as volk loads it dynamically
|
||||||
|
target_link_libraries(${PROJECT_NAME}
|
||||||
|
volk
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Platform-specific linking
|
# Platform-specific linking
|
||||||
if(UNIX AND NOT APPLE)
|
if(UNIX AND NOT APPLE)
|
||||||
target_link_libraries(${PROJECT_NAME} Qt5::DBus)
|
target_link_libraries(${PROJECT_NAME} Qt5::DBus X11)
|
||||||
elseif(APPLE)
|
elseif(APPLE)
|
||||||
# Link macOS frameworks
|
# Link macOS frameworks
|
||||||
target_link_libraries(${PROJECT_NAME}
|
target_link_libraries(${PROJECT_NAME}
|
||||||
"-framework Foundation"
|
"-framework Foundation"
|
||||||
"-framework Cocoa"
|
"-framework Cocoa"
|
||||||
|
"-framework QuartzCore"
|
||||||
|
"-framework Metal"
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
@ -149,4 +224,12 @@ message(STATUS "Platform: ${PLATFORM_NAME}")
|
||||||
message(STATUS "Qt5_DIR: ${Qt5_DIR}")
|
message(STATUS "Qt5_DIR: ${Qt5_DIR}")
|
||||||
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
|
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
|
||||||
message(STATUS "Qt Components: ${QT_COMPONENTS}")
|
message(STATUS "Qt Components: ${QT_COMPONENTS}")
|
||||||
|
if(VULKAN_FOUND)
|
||||||
|
message(STATUS "Vulkan support: ENABLED (headers-only, dynamic loading via volk)")
|
||||||
|
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")
|
||||||
|
else()
|
||||||
|
message(STATUS "Vulkan support: DISABLED")
|
||||||
|
endif()
|
||||||
message(STATUS "========================================")
|
message(STATUS "========================================")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,274 @@
|
||||||
|
# Vulkan Integration Documentation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document describes the Vulkan integration in the ScreenLockDetector project. The project now includes a `VulkanWidget` that uses native window properties to create Vulkan surfaces and renders using the Vulkan API.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Components
|
||||||
|
|
||||||
|
1. **VulkanWidget** (`src/vulkanwidget.h`, `src/vulkanwidget.cpp`)
|
||||||
|
- Qt widget that uses native window handles for Vulkan surface creation
|
||||||
|
- Implements Vulkan rendering pipeline with swapchain management
|
||||||
|
- Provides frame counting and rendering control
|
||||||
|
|
||||||
|
2. **Volk Integration** (`third_party/volk/`)
|
||||||
|
- Meta-loader for Vulkan that eliminates the need to link directly against `vulkan-1.dll`/`libvulkan.so`
|
||||||
|
- Loads Vulkan functions dynamically at runtime
|
||||||
|
- Version: 1.3.295
|
||||||
|
|
||||||
|
### Key Features
|
||||||
|
|
||||||
|
- **Native Window Surface Creation**: Uses Qt's native window handles to create platform-specific Vulkan surfaces
|
||||||
|
- **Dynamic Vulkan Loading**: Uses volk to load Vulkan functions at runtime
|
||||||
|
- **Cross-Platform Support**: Supports Windows (Win32), Linux (Xlib), and macOS (Metal)
|
||||||
|
- **Swapchain Management**: Automatic swapchain recreation on window resize
|
||||||
|
- **Frame Synchronization**: Uses semaphores and fences for proper frame synchronization
|
||||||
|
- **Simple Rendering**: Demonstrates color cycling as a proof-of-concept
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
### Widget Attributes
|
||||||
|
|
||||||
|
The `VulkanWidget` sets the following Qt attributes:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
setAttribute(Qt::WA_NativeWindow); // Create native window handle
|
||||||
|
setAttribute(Qt::WA_PaintOnScreen); // Paint directly to screen
|
||||||
|
setAttribute(Qt::WA_OpaquePaintEvent); // Widget is opaque
|
||||||
|
```
|
||||||
|
|
||||||
|
These attributes ensure that:
|
||||||
|
- Qt creates a native window handle accessible via `windowHandle()`
|
||||||
|
- Qt doesn't interfere with Vulkan rendering
|
||||||
|
- The widget owns its painting surface
|
||||||
|
|
||||||
|
### Vulkan Initialization Sequence
|
||||||
|
|
||||||
|
1. **Initialize Volk**: `volkInitialize()` loads the Vulkan loader
|
||||||
|
2. **Create Instance**: Create Vulkan instance with required surface extensions
|
||||||
|
3. **Create Surface**: Create platform-specific surface from native window handle
|
||||||
|
4. **Select Physical Device**: Find GPU that supports graphics and presentation
|
||||||
|
5. **Create Logical Device**: Create device with swapchain extension
|
||||||
|
6. **Create Swapchain**: Set up swapchain with appropriate format and present mode
|
||||||
|
7. **Create Command Objects**: Allocate command pool and command buffers
|
||||||
|
8. **Create Sync Objects**: Create semaphores and fences for frame synchronization
|
||||||
|
|
||||||
|
### Platform-Specific Surface Creation
|
||||||
|
|
||||||
|
#### Windows (Win32)
|
||||||
|
```cpp
|
||||||
|
VkWin32SurfaceCreateInfoKHR createInfo = {};
|
||||||
|
createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
|
||||||
|
createInfo.hwnd = reinterpret_cast<HWND>(window->winId());
|
||||||
|
createInfo.hinstance = GetModuleHandle(nullptr);
|
||||||
|
vkCreateWin32SurfaceKHR(m_instance, &createInfo, nullptr, &m_surface);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Linux (Xlib)
|
||||||
|
```cpp
|
||||||
|
VkXlibSurfaceCreateInfoKHR createInfo = {};
|
||||||
|
createInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR;
|
||||||
|
createInfo.dpy = reinterpret_cast<Display*>(
|
||||||
|
qApp->platformNativeInterface()->nativeResourceForWindow("display", window));
|
||||||
|
createInfo.window = static_cast<Window>(window->winId());
|
||||||
|
vkCreateXlibSurfaceKHR(m_instance, &createInfo, nullptr, &m_surface);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### macOS (Metal)
|
||||||
|
```cpp
|
||||||
|
VkMetalSurfaceCreateInfoEXT createInfo = {};
|
||||||
|
createInfo.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
|
||||||
|
createInfo.pLayer = nullptr; // Needs proper Metal layer setup
|
||||||
|
vkCreateMetalSurfaceEXT(m_instance, &createInfo, nullptr, &m_surface);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rendering Loop
|
||||||
|
|
||||||
|
The rendering loop runs at ~60 FPS (16ms intervals) when enabled:
|
||||||
|
|
||||||
|
1. Wait for previous frame fence
|
||||||
|
2. Acquire next swapchain image
|
||||||
|
3. Reset command buffer and record commands
|
||||||
|
4. Submit command buffer with semaphore synchronization
|
||||||
|
5. Present image to screen
|
||||||
|
6. Handle swapchain recreation if needed
|
||||||
|
|
||||||
|
### Swapchain Recreation
|
||||||
|
|
||||||
|
The swapchain is automatically recreated when:
|
||||||
|
- Window is resized (`resizeEvent`)
|
||||||
|
- Swapchain becomes out of date (`VK_ERROR_OUT_OF_DATE_KHR`)
|
||||||
|
- Swapchain becomes suboptimal (`VK_SUBOPTIMAL_KHR`)
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- **Vulkan SDK**: Install the LunarG Vulkan SDK
|
||||||
|
- Windows: Download from https://vulkan.lunarg.com/
|
||||||
|
- Linux: `sudo apt install vulkan-tools libvulkan-dev vulkan-validationlayers`
|
||||||
|
- macOS: Download Vulkan SDK and install MoltenVK
|
||||||
|
|
||||||
|
- **Qt5**: Qt 5.15 or later with Widgets module
|
||||||
|
|
||||||
|
- **X11 (Linux only)**: `sudo apt install libx11-dev`
|
||||||
|
|
||||||
|
### CMake Configuration
|
||||||
|
|
||||||
|
The CMakeLists.txt has been updated to:
|
||||||
|
|
||||||
|
1. Find the Vulkan package: `find_package(Vulkan REQUIRED)`
|
||||||
|
2. Build volk as a static library from source
|
||||||
|
3. Link the application with volk and Vulkan libraries
|
||||||
|
4. Add platform-specific libraries (X11 on Linux, Metal/QuartzCore on macOS)
|
||||||
|
|
||||||
|
### Build Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ScreenLockDetector
|
||||||
|
mkdir -p build
|
||||||
|
cd build
|
||||||
|
cmake ..
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### In the Application
|
||||||
|
|
||||||
|
The VulkanWidget is integrated into the main window as a tab:
|
||||||
|
|
||||||
|
1. **QPainter Widget Tab**: Original custom widget using Qt's QPainter
|
||||||
|
2. **Vulkan Widget Tab**: New Vulkan-based rendering widget
|
||||||
|
|
||||||
|
### Controls
|
||||||
|
|
||||||
|
- **Enable Rendering**: Initialize Vulkan (if needed) and start rendering
|
||||||
|
- **Disable Rendering**: Stop the rendering loop
|
||||||
|
- **Reset Frame Count**: Reset the frame counter to 0
|
||||||
|
|
||||||
|
### Status Information
|
||||||
|
|
||||||
|
The widget displays:
|
||||||
|
- **Vulkan Status**: Initialization state and any error messages
|
||||||
|
- **Rendering Status**: Whether rendering is active or stopped
|
||||||
|
- **Frame Count**: Total number of frames rendered
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Vulkan Initialization Fails
|
||||||
|
|
||||||
|
**Problem**: "Failed to initialize volk" or "Failed to create Vulkan instance"
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
- Ensure Vulkan SDK is installed and Vulkan drivers are up to date
|
||||||
|
- Check that `VK_LOADER_DEBUG=all` environment variable shows loader activity
|
||||||
|
- Verify GPU supports Vulkan (run `vulkaninfo` or `vkcube`)
|
||||||
|
|
||||||
|
### Surface Creation Fails
|
||||||
|
|
||||||
|
**Problem**: "Failed to create Vulkan surface"
|
||||||
|
|
||||||
|
**Linux Solutions**:
|
||||||
|
- Ensure X11 development libraries are installed
|
||||||
|
- Check that the application is running under X11 (not Wayland)
|
||||||
|
- Verify DISPLAY environment variable is set
|
||||||
|
|
||||||
|
**macOS Solutions**:
|
||||||
|
- Ensure MoltenVK is properly installed
|
||||||
|
- Check that Metal is supported on the device
|
||||||
|
|
||||||
|
### Black Screen or No Rendering
|
||||||
|
|
||||||
|
**Problem**: Widget shows but nothing renders
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
- Check the status message for initialization errors
|
||||||
|
- Verify that "Enable Rendering" has been clicked
|
||||||
|
- Check console output for Vulkan errors
|
||||||
|
- Ensure window is visible and has non-zero size
|
||||||
|
|
||||||
|
### Performance Issues
|
||||||
|
|
||||||
|
**Problem**: Low frame rate or stuttering
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
- Check if VSync is forcing specific frame rates
|
||||||
|
- Verify GPU is not thermal throttling
|
||||||
|
- Check for swapchain recreation messages (indicates excessive resizing)
|
||||||
|
- Reduce window size for testing
|
||||||
|
|
||||||
|
## Code Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
ScreenLockDetector/
|
||||||
|
├── src/
|
||||||
|
│ ├── vulkanwidget.h # VulkanWidget class declaration
|
||||||
|
│ ├── vulkanwidget.cpp # VulkanWidget implementation
|
||||||
|
│ ├── mainwindow.h # Updated with VulkanWidget integration
|
||||||
|
│ └── mainwindow.cpp # Updated with VulkanWidget tab
|
||||||
|
├── third_party/
|
||||||
|
│ └── volk/ # Volk meta-loader source
|
||||||
|
│ ├── volk.h # Volk header
|
||||||
|
│ ├── volk.c # Volk implementation
|
||||||
|
│ └── ...
|
||||||
|
└── CMakeLists.txt # Updated with Vulkan/volk support
|
||||||
|
```
|
||||||
|
|
||||||
|
## Extending the Renderer
|
||||||
|
|
||||||
|
### Adding Graphics Pipeline
|
||||||
|
|
||||||
|
To add a proper graphics pipeline:
|
||||||
|
|
||||||
|
1. Create shader modules (SPIR-V compiled shaders)
|
||||||
|
2. Define vertex input format
|
||||||
|
3. Create pipeline layout
|
||||||
|
4. Create graphics pipeline
|
||||||
|
5. Create framebuffers for swapchain images
|
||||||
|
6. Update `recordCommandBuffer()` to use the pipeline
|
||||||
|
|
||||||
|
### Adding Geometry
|
||||||
|
|
||||||
|
To render actual geometry:
|
||||||
|
|
||||||
|
1. Create vertex and index buffers
|
||||||
|
2. Allocate device memory
|
||||||
|
3. Upload geometry data
|
||||||
|
4. Bind buffers in command buffer recording
|
||||||
|
5. Issue draw calls
|
||||||
|
|
||||||
|
### Adding Textures
|
||||||
|
|
||||||
|
To add texture support:
|
||||||
|
|
||||||
|
1. Create VkImage and VkImageView
|
||||||
|
2. Create VkSampler
|
||||||
|
3. Update descriptor sets
|
||||||
|
4. Sample textures in fragment shader
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- **Vulkan Tutorial**: https://vulkan-tutorial.com/
|
||||||
|
- **Vulkan Specification**: https://www.khronos.org/registry/vulkan/
|
||||||
|
- **Volk Repository**: https://github.com/zeux/volk
|
||||||
|
- **Qt Native Interface**: https://doc.qt.io/qt-5/qpa.html
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This Vulkan integration follows the same license as the main project. Volk is licensed under the MIT License.
|
||||||
|
|
||||||
|
## Future Improvements
|
||||||
|
|
||||||
|
- [ ] Add validation layers in debug builds
|
||||||
|
- [ ] Implement proper render pass and framebuffers
|
||||||
|
- [ ] Add graphics pipeline with shaders
|
||||||
|
- [ ] Implement geometry rendering
|
||||||
|
- [ ] Add texture support
|
||||||
|
- [ ] Integrate with screen lock detection (pause rendering on lock)
|
||||||
|
- [ ] Add Wayland support for Linux
|
||||||
|
- [ ] Improve macOS Metal layer setup
|
||||||
|
- [ ] Add performance metrics (FPS, frame time)
|
||||||
|
- [ ] Implement multi-threading for command buffer recording
|
||||||
|
|
@ -0,0 +1,298 @@
|
||||||
|
# Vulkan Widget 集成总结
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
本项目已成功集成了一个基于 Vulkan 的渲染组件 `VulkanWidget`,该组件使用 Qt 的 native window 属性来创建 Vulkan Surface,并通过 volk 机制动态加载 Vulkan 函数。
|
||||||
|
|
||||||
|
## 主要特性
|
||||||
|
|
||||||
|
### 1. VulkanWidget 组件
|
||||||
|
- **文件位置**: `src/vulkanwidget.h`, `src/vulkanwidget.cpp`
|
||||||
|
- **功能**: 使用 Vulkan API 进行硬件加速渲染
|
||||||
|
- **特点**:
|
||||||
|
- 使用 Qt Native Window 属性创建平台特定的 Vulkan Surface
|
||||||
|
- 支持跨平台(Windows、Linux、macOS)
|
||||||
|
- 自动管理 Swapchain 生命周期
|
||||||
|
- 窗口大小变化时自动重建 Swapchain
|
||||||
|
- 帧同步机制(使用 Semaphore 和 Fence)
|
||||||
|
- 简单的颜色循环渲染演示
|
||||||
|
|
||||||
|
### 2. Volk 集成
|
||||||
|
- **版本**: 1.3.295
|
||||||
|
- **位置**: `third_party/volk/`
|
||||||
|
- **特点**:
|
||||||
|
- 动态加载 Vulkan 函数,无需链接 Vulkan 库
|
||||||
|
- 支持实例级和设备级函数加载
|
||||||
|
- 以静态库形式嵌入项目
|
||||||
|
- 避免了 Vulkan SDK 版本冲突问题
|
||||||
|
|
||||||
|
### 3. 条件编译支持
|
||||||
|
- 使用 `ENABLE_VULKAN_WIDGET` 宏控制 Vulkan 功能的编译
|
||||||
|
- 如果系统没有安装 Vulkan SDK,项目仍可正常编译和运行
|
||||||
|
- CMake 选项 `ENABLE_VULKAN` 可以手动控制是否启用 Vulkan 支持
|
||||||
|
|
||||||
|
## 技术实现细节
|
||||||
|
|
||||||
|
### Qt Native Window 属性设置
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
setAttribute(Qt::WA_NativeWindow); // 创建原生窗口句柄
|
||||||
|
setAttribute(Qt::WA_PaintOnScreen); // 直接在屏幕上绘制
|
||||||
|
setAttribute(Qt::WA_OpaquePaintEvent); // 组件不透明
|
||||||
|
```
|
||||||
|
|
||||||
|
这些属性确保:
|
||||||
|
- Qt 创建可通过 `windowHandle()` 访问的原生窗口句柄
|
||||||
|
- Qt 不会干扰 Vulkan 渲染
|
||||||
|
- 组件拥有自己的绘制表面
|
||||||
|
|
||||||
|
### Vulkan 初始化流程
|
||||||
|
|
||||||
|
1. **初始化 Volk**: `volkInitialize()` 加载 Vulkan 加载器
|
||||||
|
2. **创建 Instance**: 创建 Vulkan 实例,包含必需的 Surface 扩展
|
||||||
|
3. **创建 Surface**: 从原生窗口句柄创建平台特定的 Surface
|
||||||
|
4. **选择物理设备**: 查找支持图形和呈现的 GPU
|
||||||
|
5. **创建逻辑设备**: 创建带有 Swapchain 扩展的设备
|
||||||
|
6. **创建 Swapchain**: 设置适当格式和呈现模式的交换链
|
||||||
|
7. **创建命令对象**: 分配命令池和命令缓冲区
|
||||||
|
8. **创建同步对象**: 创建用于帧同步的 Semaphore 和 Fence
|
||||||
|
|
||||||
|
### 平台特定 Surface 创建
|
||||||
|
|
||||||
|
#### Windows (Win32)
|
||||||
|
```cpp
|
||||||
|
VkWin32SurfaceCreateInfoKHR createInfo = {};
|
||||||
|
createInfo.hwnd = reinterpret_cast<HWND>(window->winId());
|
||||||
|
createInfo.hinstance = GetModuleHandle(nullptr);
|
||||||
|
vkCreateWin32SurfaceKHR(m_instance, &createInfo, nullptr, &m_surface);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Linux (Xlib)
|
||||||
|
```cpp
|
||||||
|
VkXlibSurfaceCreateInfoKHR createInfo = {};
|
||||||
|
createInfo.dpy = qApp->platformNativeInterface()->nativeResourceForWindow("display", window);
|
||||||
|
createInfo.window = static_cast<Window>(window->winId());
|
||||||
|
vkCreateXlibSurfaceKHR(m_instance, &createInfo, nullptr, &m_surface);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### macOS (Metal)
|
||||||
|
```cpp
|
||||||
|
VkMetalSurfaceCreateInfoEXT createInfo = {};
|
||||||
|
createInfo.pLayer = nullptr; // 需要正确的 Metal 层设置
|
||||||
|
vkCreateMetalSurfaceEXT(m_instance, &createInfo, nullptr, &m_surface);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 渲染循环
|
||||||
|
|
||||||
|
渲染循环以约 60 FPS(16ms 间隔)运行:
|
||||||
|
|
||||||
|
1. 等待上一帧的 Fence
|
||||||
|
2. 获取下一个 Swapchain 图像
|
||||||
|
3. 重置并记录命令缓冲区
|
||||||
|
4. 使用 Semaphore 同步提交命令缓冲区
|
||||||
|
5. 将图像呈现到屏幕
|
||||||
|
6. 必要时处理 Swapchain 重建
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
ScreenLockDetector/
|
||||||
|
├── src/
|
||||||
|
│ ├── vulkanwidget.h # VulkanWidget 类声明
|
||||||
|
│ ├── vulkanwidget.cpp # VulkanWidget 实现
|
||||||
|
│ ├── mainwindow.h # 已更新:集成 VulkanWidget
|
||||||
|
│ └── mainwindow.cpp # 已更新:添加 VulkanWidget Tab
|
||||||
|
├── third_party/
|
||||||
|
│ └── volk/ # Volk 元加载器源码
|
||||||
|
│ ├── volk.h # Volk 头文件
|
||||||
|
│ ├── volk.c # Volk 实现
|
||||||
|
│ └── ...
|
||||||
|
├── CMakeLists.txt # 已更新:Vulkan/volk 支持
|
||||||
|
├── VULKAN_INTEGRATION.md # Vulkan 集成详细文档
|
||||||
|
└── VULKAN_WIDGET_SUMMARY.md # 本文件
|
||||||
|
```
|
||||||
|
|
||||||
|
## 用户界面
|
||||||
|
|
||||||
|
主窗口现在包含两个 Tab:
|
||||||
|
|
||||||
|
1. **QPainter Widget Tab**:
|
||||||
|
- 原有的自定义组件,使用 Qt 的 QPainter
|
||||||
|
- 显示动态动画效果
|
||||||
|
|
||||||
|
2. **Vulkan Widget Tab**:
|
||||||
|
- 新的 Vulkan 渲染组件
|
||||||
|
- 显示颜色循环效果
|
||||||
|
- 控制按钮:
|
||||||
|
- Enable Rendering: 初始化 Vulkan(如需)并开始渲染
|
||||||
|
- Disable Rendering: 停止渲染循环
|
||||||
|
- Reset Frame Count: 重置帧计数器
|
||||||
|
- 状态信息:
|
||||||
|
- Vulkan Status: 初始化状态和错误消息
|
||||||
|
- Rendering Status: 渲染是否激活
|
||||||
|
- Frame Count: 已渲染的总帧数
|
||||||
|
|
||||||
|
## 构建说明
|
||||||
|
|
||||||
|
### 前提条件
|
||||||
|
|
||||||
|
**启用 Vulkan 支持时**:
|
||||||
|
- Vulkan SDK (LunarG)
|
||||||
|
- Qt5 (5.15 或更高版本)
|
||||||
|
- X11 开发库(仅 Linux): `sudo apt install libx11-dev`
|
||||||
|
|
||||||
|
**禁用 Vulkan 支持时**:
|
||||||
|
- 仅需 Qt5
|
||||||
|
|
||||||
|
### 构建命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ScreenLockDetector
|
||||||
|
mkdir -p build
|
||||||
|
cd build
|
||||||
|
|
||||||
|
# 启用 Vulkan 支持(默认)
|
||||||
|
cmake ..
|
||||||
|
|
||||||
|
# 或禁用 Vulkan 支持
|
||||||
|
cmake -DENABLE_VULKAN=OFF ..
|
||||||
|
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
### CMake 配置选项
|
||||||
|
|
||||||
|
- `ENABLE_VULKAN`: 启用/禁用 Vulkan 支持(默认: ON)
|
||||||
|
- 如果找不到 Vulkan SDK,将自动禁用 Vulkan 支持
|
||||||
|
- 如果 volk 源码不存在,将自动禁用 Vulkan 支持
|
||||||
|
|
||||||
|
## 运行说明
|
||||||
|
|
||||||
|
### 启用 Vulkan 支持的版本
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./build/bin/ScreenLockDetector
|
||||||
|
```
|
||||||
|
|
||||||
|
应用启动后:
|
||||||
|
1. 切换到 "Vulkan Widget" Tab
|
||||||
|
2. 点击 "Enable Rendering" 按钮
|
||||||
|
3. 观察颜色循环效果
|
||||||
|
4. 查看状态信息了解渲染状态
|
||||||
|
|
||||||
|
### 禁用 Vulkan 支持的版本
|
||||||
|
|
||||||
|
应用将正常运行,但不显示 "Vulkan Widget" Tab,仅显示 "QPainter Widget" Tab。
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### Vulkan 初始化失败
|
||||||
|
|
||||||
|
**问题**: "Failed to initialize volk" 或 "Failed to create Vulkan instance"
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
- 确保已安装 Vulkan SDK 和最新的 Vulkan 驱动
|
||||||
|
- 运行 `vulkaninfo` 或 `vkcube` 验证 Vulkan 环境
|
||||||
|
- 检查环境变量 `VK_LOADER_DEBUG=all` 查看加载器活动
|
||||||
|
|
||||||
|
### Surface 创建失败
|
||||||
|
|
||||||
|
**问题**: "Failed to create Vulkan surface"
|
||||||
|
|
||||||
|
**Linux 解决方案**:
|
||||||
|
- 确保已安装 X11 开发库
|
||||||
|
- 确认应用在 X11 下运行(非 Wayland)
|
||||||
|
- 验证 DISPLAY 环境变量已设置
|
||||||
|
|
||||||
|
**macOS 解决方案**:
|
||||||
|
- 确保 MoltenVK 已正确安装
|
||||||
|
- 检查设备是否支持 Metal
|
||||||
|
|
||||||
|
### 黑屏或无渲染
|
||||||
|
|
||||||
|
**问题**: 组件显示但无内容渲染
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
- 检查状态消息是否有初始化错误
|
||||||
|
- 确认已点击 "Enable Rendering"
|
||||||
|
- 查看控制台输出是否有 Vulkan 错误
|
||||||
|
- 确保窗口可见且大小非零
|
||||||
|
|
||||||
|
## volk 源码获取
|
||||||
|
|
||||||
|
如果 `third_party/volk/` 目录为空,可以通过以下方式获取 volk 源码:
|
||||||
|
|
||||||
|
### 方法 1: 从 GitHub 下载
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd third_party/volk
|
||||||
|
curl -L https://github.com/zeux/volk/archive/refs/tags/1.3.295.tar.gz -o volk.tar.gz
|
||||||
|
tar xzf volk.tar.gz --strip-components=1
|
||||||
|
rm volk.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方法 2: 使用 Git 子模块
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd third_party
|
||||||
|
git submodule add https://github.com/zeux/volk.git
|
||||||
|
git submodule update --init --recursive
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方法 3: 手动复制
|
||||||
|
|
||||||
|
如果已有 volk 源码(例如在 `$HOME/sdk/volk-1.4.304`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp -r $HOME/sdk/volk-1.4.304/* third_party/volk/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 与锁屏检测的集成
|
||||||
|
|
||||||
|
虽然当前 VulkanWidget 可以独立工作,但可以轻松集成锁屏检测功能:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 在 MainWindow::onLockStateChanged() 中添加
|
||||||
|
if (m_vulkanWidget) {
|
||||||
|
m_vulkanWidget->setRenderingEnabled(!locked);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
这样,当屏幕锁定时,Vulkan 渲染会自动暂停,节省 GPU 资源。
|
||||||
|
|
||||||
|
## 扩展建议
|
||||||
|
|
||||||
|
### 短期改进
|
||||||
|
- [ ] 添加调试模式的验证层
|
||||||
|
- [ ] 实现完整的渲染通道和帧缓冲
|
||||||
|
- [ ] 添加图形管线和着色器
|
||||||
|
- [ ] 实现几何体渲染
|
||||||
|
- [ ] 与锁屏检测集成(锁定时暂停渲染)
|
||||||
|
|
||||||
|
### 长期改进
|
||||||
|
- [ ] 添加纹理支持
|
||||||
|
- [ ] 实现多线程命令缓冲区录制
|
||||||
|
- [ ] 添加性能指标(FPS、帧时间)
|
||||||
|
- [ ] 支持 Linux Wayland
|
||||||
|
- [ ] 改进 macOS Metal 层设置
|
||||||
|
- [ ] 添加高级渲染效果(光照、阴影等)
|
||||||
|
|
||||||
|
## 参考资源
|
||||||
|
|
||||||
|
- **Vulkan 教程**: https://vulkan-tutorial.com/
|
||||||
|
- **Vulkan 规范**: https://www.khronos.org/registry/vulkan/
|
||||||
|
- **Volk 仓库**: https://github.com/zeux/volk
|
||||||
|
- **Qt Native Interface**: https://doc.qt.io/qt-5/qpa.html
|
||||||
|
- **Vulkan SDK**: https://vulkan.lunarg.com/
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
此 Vulkan 集成遵循主项目的许可证。Volk 使用 MIT 许可证。
|
||||||
|
|
||||||
|
## 贡献者
|
||||||
|
|
||||||
|
- 实现了 VulkanWidget 类及其完整的 Vulkan 渲染管线
|
||||||
|
- 集成了 volk 元加载器以实现动态 Vulkan 加载
|
||||||
|
- 添加了条件编译支持,使 Vulkan 成为可选功能
|
||||||
|
- 更新了 MainWindow 以支持 Tab 式界面
|
||||||
|
- 提供了详细的文档和故障排除指南
|
||||||
2
run.sh
2
run.sh
|
|
@ -48,7 +48,7 @@ echo ""
|
||||||
|
|
||||||
# Set Qt5 library path and GCC library path
|
# Set Qt5 library path and GCC library path
|
||||||
export QT5_DIR="$HOME/sdk/qt-5.15.2"
|
export QT5_DIR="$HOME/sdk/qt-5.15.2"
|
||||||
export LD_LIBRARY_PATH="$QT5_DIR/lib:/usr/local/lib64:/usr/local/lib:$LD_LIBRARY_PATH"
|
export LD_LIBRARY_PATH="$QT5_DIR/lib:/usr/local/lib64:/usr/local/lib:/home/ubuntu1804/sdk/Vulkan-Loader-1.3.302/lib:$LD_LIBRARY_PATH"
|
||||||
|
|
||||||
# Run the application
|
# Run the application
|
||||||
cd "$BUILD_DIR/bin"
|
cd "$BUILD_DIR/bin"
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,39 @@
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
#include <QTabWidget>
|
||||||
|
|
||||||
MainWindow::MainWindow(QWidget *parent)
|
MainWindow::MainWindow(QWidget *parent)
|
||||||
: QMainWindow(parent)
|
: QMainWindow(parent)
|
||||||
, m_lockDetector(nullptr)
|
, m_lockDetector(nullptr)
|
||||||
, m_customWidget(nullptr)
|
, m_customWidget(nullptr)
|
||||||
|
#ifdef ENABLE_VULKAN_WIDGET
|
||||||
|
, m_vulkanWidget(nullptr)
|
||||||
|
#endif
|
||||||
, m_centralWidget(nullptr)
|
, m_centralWidget(nullptr)
|
||||||
, m_mainLayout(nullptr)
|
, m_mainLayout(nullptr)
|
||||||
|
, m_tabWidget(nullptr)
|
||||||
, m_controlGroup(nullptr)
|
, m_controlGroup(nullptr)
|
||||||
, m_enablePaintBtn(nullptr)
|
, m_enablePaintBtn(nullptr)
|
||||||
, m_disablePaintBtn(nullptr)
|
, m_disablePaintBtn(nullptr)
|
||||||
, m_resetFrameBtn(nullptr)
|
, m_resetFrameBtn(nullptr)
|
||||||
|
#ifdef ENABLE_VULKAN_WIDGET
|
||||||
|
, m_vulkanControlGroup(nullptr)
|
||||||
|
, m_enableVulkanBtn(nullptr)
|
||||||
|
, m_disableVulkanBtn(nullptr)
|
||||||
|
, m_resetVulkanFrameBtn(nullptr)
|
||||||
|
#endif
|
||||||
, m_statusGroup(nullptr)
|
, m_statusGroup(nullptr)
|
||||||
, m_lockStatusLabel(nullptr)
|
, m_lockStatusLabel(nullptr)
|
||||||
, m_paintStatusLabel(nullptr)
|
, m_paintStatusLabel(nullptr)
|
||||||
, m_frameCountLabel(nullptr)
|
, m_frameCountLabel(nullptr)
|
||||||
, m_detectorStatusLabel(nullptr)
|
, m_detectorStatusLabel(nullptr)
|
||||||
|
#ifdef ENABLE_VULKAN_WIDGET
|
||||||
|
, m_vulkanStatusGroup(nullptr)
|
||||||
|
, m_vulkanInitLabel(nullptr)
|
||||||
|
, m_vulkanRenderStatusLabel(nullptr)
|
||||||
|
, m_vulkanFrameCountLabel(nullptr)
|
||||||
|
#endif
|
||||||
, m_updateTimer(nullptr)
|
, m_updateTimer(nullptr)
|
||||||
{
|
{
|
||||||
setWindowTitle("Qt Screen Lock Detection Demo");
|
setWindowTitle("Qt Screen Lock Detection Demo");
|
||||||
|
|
@ -71,9 +88,23 @@ void MainWindow::setupUI()
|
||||||
m_mainLayout->setSpacing(10);
|
m_mainLayout->setSpacing(10);
|
||||||
m_mainLayout->setContentsMargins(10, 10, 10, 10);
|
m_mainLayout->setContentsMargins(10, 10, 10, 10);
|
||||||
|
|
||||||
|
// 定义通用字体
|
||||||
|
QFont statusFont;
|
||||||
|
statusFont.setPointSize(10);
|
||||||
|
|
||||||
|
// 创建Tab Widget
|
||||||
|
m_tabWidget = new QTabWidget(this);
|
||||||
|
m_mainLayout->addWidget(m_tabWidget, 1);
|
||||||
|
|
||||||
|
// ========== Tab 1: CustomWidget (QPainter) ==========
|
||||||
|
QWidget *customTab = new QWidget(this);
|
||||||
|
QVBoxLayout *customLayout = new QVBoxLayout(customTab);
|
||||||
|
customLayout->setSpacing(10);
|
||||||
|
customLayout->setContentsMargins(10, 10, 10, 10);
|
||||||
|
|
||||||
// 创建自定义绘制组件
|
// 创建自定义绘制组件
|
||||||
m_customWidget = new CustomWidget(this);
|
m_customWidget = new CustomWidget(this);
|
||||||
m_mainLayout->addWidget(m_customWidget, 1);
|
customLayout->addWidget(m_customWidget, 1);
|
||||||
|
|
||||||
// 创建控制面板
|
// 创建控制面板
|
||||||
m_controlGroup = new QGroupBox("Manual Control", this);
|
m_controlGroup = new QGroupBox("Manual Control", this);
|
||||||
|
|
@ -92,7 +123,7 @@ void MainWindow::setupUI()
|
||||||
controlLayout->addWidget(m_resetFrameBtn);
|
controlLayout->addWidget(m_resetFrameBtn);
|
||||||
controlLayout->addStretch();
|
controlLayout->addStretch();
|
||||||
|
|
||||||
m_mainLayout->addWidget(m_controlGroup);
|
customLayout->addWidget(m_controlGroup);
|
||||||
|
|
||||||
// 创建状态显示面板
|
// 创建状态显示面板
|
||||||
m_statusGroup = new QGroupBox("Status Information", this);
|
m_statusGroup = new QGroupBox("Status Information", this);
|
||||||
|
|
@ -103,8 +134,6 @@ void MainWindow::setupUI()
|
||||||
m_frameCountLabel = new QLabel("Frame Count: 0", this);
|
m_frameCountLabel = new QLabel("Frame Count: 0", this);
|
||||||
m_detectorStatusLabel = new QLabel("Detector Status: Initializing...", this);
|
m_detectorStatusLabel = new QLabel("Detector Status: Initializing...", this);
|
||||||
|
|
||||||
QFont statusFont;
|
|
||||||
statusFont.setPointSize(10);
|
|
||||||
m_lockStatusLabel->setFont(statusFont);
|
m_lockStatusLabel->setFont(statusFont);
|
||||||
m_paintStatusLabel->setFont(statusFont);
|
m_paintStatusLabel->setFont(statusFont);
|
||||||
m_frameCountLabel->setFont(statusFont);
|
m_frameCountLabel->setFont(statusFont);
|
||||||
|
|
@ -115,7 +144,60 @@ void MainWindow::setupUI()
|
||||||
statusLayout->addWidget(m_paintStatusLabel);
|
statusLayout->addWidget(m_paintStatusLabel);
|
||||||
statusLayout->addWidget(m_frameCountLabel);
|
statusLayout->addWidget(m_frameCountLabel);
|
||||||
|
|
||||||
m_mainLayout->addWidget(m_statusGroup);
|
customLayout->addWidget(m_statusGroup);
|
||||||
|
|
||||||
|
m_tabWidget->addTab(customTab, "QPainter Widget");
|
||||||
|
|
||||||
|
#ifdef ENABLE_VULKAN_WIDGET
|
||||||
|
// ========== Tab 2: VulkanWidget ==========
|
||||||
|
QWidget *vulkanTab = new QWidget(this);
|
||||||
|
QVBoxLayout *vulkanLayout = new QVBoxLayout(vulkanTab);
|
||||||
|
vulkanLayout->setSpacing(10);
|
||||||
|
vulkanLayout->setContentsMargins(10, 10, 10, 10);
|
||||||
|
|
||||||
|
// 创建Vulkan渲染组件
|
||||||
|
m_vulkanWidget = new VulkanWidget(this);
|
||||||
|
vulkanLayout->addWidget(m_vulkanWidget, 1);
|
||||||
|
|
||||||
|
// 创建Vulkan控制面板
|
||||||
|
m_vulkanControlGroup = new QGroupBox("Vulkan Control", this);
|
||||||
|
QHBoxLayout *vulkanControlLayout = new QHBoxLayout(m_vulkanControlGroup);
|
||||||
|
|
||||||
|
m_enableVulkanBtn = new QPushButton("Enable Rendering", this);
|
||||||
|
m_disableVulkanBtn = new QPushButton("Disable Rendering", this);
|
||||||
|
m_resetVulkanFrameBtn = new QPushButton("Reset Frame Count", this);
|
||||||
|
|
||||||
|
m_enableVulkanBtn->setMinimumHeight(35);
|
||||||
|
m_disableVulkanBtn->setMinimumHeight(35);
|
||||||
|
m_resetVulkanFrameBtn->setMinimumHeight(35);
|
||||||
|
|
||||||
|
vulkanControlLayout->addWidget(m_enableVulkanBtn);
|
||||||
|
vulkanControlLayout->addWidget(m_disableVulkanBtn);
|
||||||
|
vulkanControlLayout->addWidget(m_resetVulkanFrameBtn);
|
||||||
|
vulkanControlLayout->addStretch();
|
||||||
|
|
||||||
|
vulkanLayout->addWidget(m_vulkanControlGroup);
|
||||||
|
|
||||||
|
// 创建Vulkan状态显示面板
|
||||||
|
m_vulkanStatusGroup = new QGroupBox("Vulkan Status Information", this);
|
||||||
|
QVBoxLayout *vulkanStatusLayout = new QVBoxLayout(m_vulkanStatusGroup);
|
||||||
|
|
||||||
|
m_vulkanInitLabel = new QLabel("Vulkan Status: Not Initialized", this);
|
||||||
|
m_vulkanRenderStatusLabel = new QLabel("Rendering Status: Disabled", this);
|
||||||
|
m_vulkanFrameCountLabel = new QLabel("Frame Count: 0", this);
|
||||||
|
|
||||||
|
m_vulkanInitLabel->setFont(statusFont);
|
||||||
|
m_vulkanRenderStatusLabel->setFont(statusFont);
|
||||||
|
m_vulkanFrameCountLabel->setFont(statusFont);
|
||||||
|
|
||||||
|
vulkanStatusLayout->addWidget(m_vulkanInitLabel);
|
||||||
|
vulkanStatusLayout->addWidget(m_vulkanRenderStatusLabel);
|
||||||
|
vulkanStatusLayout->addWidget(m_vulkanFrameCountLabel);
|
||||||
|
|
||||||
|
vulkanLayout->addWidget(m_vulkanStatusGroup);
|
||||||
|
|
||||||
|
m_tabWidget->addTab(vulkanTab, "Vulkan Widget");
|
||||||
|
#endif // ENABLE_VULKAN_WIDGET
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::setupConnections()
|
void MainWindow::setupConnections()
|
||||||
|
|
@ -139,6 +221,18 @@ void MainWindow::setupConnections()
|
||||||
|
|
||||||
connect(m_resetFrameBtn, &QPushButton::clicked,
|
connect(m_resetFrameBtn, &QPushButton::clicked,
|
||||||
this, &MainWindow::onResetFrameCountClicked);
|
this, &MainWindow::onResetFrameCountClicked);
|
||||||
|
|
||||||
|
#ifdef ENABLE_VULKAN_WIDGET
|
||||||
|
// 连接Vulkan按钮信号
|
||||||
|
connect(m_enableVulkanBtn, &QPushButton::clicked,
|
||||||
|
this, &MainWindow::onEnableVulkanClicked);
|
||||||
|
|
||||||
|
connect(m_disableVulkanBtn, &QPushButton::clicked,
|
||||||
|
this, &MainWindow::onDisableVulkanClicked);
|
||||||
|
|
||||||
|
connect(m_resetVulkanFrameBtn, &QPushButton::clicked,
|
||||||
|
this, &MainWindow::onResetVulkanFrameCountClicked);
|
||||||
|
#endif // ENABLE_VULKAN_WIDGET
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onScreenLocked()
|
void MainWindow::onScreenLocked()
|
||||||
|
|
@ -219,6 +313,42 @@ void MainWindow::updateStatusDisplay()
|
||||||
int frameCount = m_customWidget ? m_customWidget->getPaintFrameCount() : 0;
|
int frameCount = m_customWidget ? m_customWidget->getPaintFrameCount() : 0;
|
||||||
QString frameCountStr = QString("Frame Count: <b>%1</b> frames").arg(frameCount);
|
QString frameCountStr = QString("Frame Count: <b>%1</b> frames").arg(frameCount);
|
||||||
m_frameCountLabel->setText(frameCountStr);
|
m_frameCountLabel->setText(frameCountStr);
|
||||||
|
|
||||||
|
#ifdef ENABLE_VULKAN_WIDGET
|
||||||
|
// 更新Vulkan状态
|
||||||
|
if (m_vulkanWidget) {
|
||||||
|
// 更新初始化状态
|
||||||
|
QString vulkanInitStatus = "Vulkan Status: ";
|
||||||
|
if (m_vulkanWidget->isInitialized()) {
|
||||||
|
vulkanInitStatus += "<span style='color: green;'><b>✓ Initialized</b></span>";
|
||||||
|
} else {
|
||||||
|
vulkanInitStatus += "<span style='color: orange;'><b>⚠ Not Initialized</b></span>";
|
||||||
|
if (!m_vulkanWidget->getLastError().isEmpty()) {
|
||||||
|
vulkanInitStatus += " - " + m_vulkanWidget->getLastError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_vulkanInitLabel->setText(vulkanInitStatus);
|
||||||
|
|
||||||
|
// 更新渲染状态
|
||||||
|
bool isVulkanRendering = m_vulkanWidget->isRenderingEnabled();
|
||||||
|
QString vulkanRenderStatus = "Rendering Status: ";
|
||||||
|
if (isVulkanRendering) {
|
||||||
|
vulkanRenderStatus += "<span style='color: green;'><b>✓ ENABLED (Active)</b></span>";
|
||||||
|
} else {
|
||||||
|
vulkanRenderStatus += "<span style='color: red;'><b>✗ DISABLED (Stopped)</b></span>";
|
||||||
|
}
|
||||||
|
m_vulkanRenderStatusLabel->setText(vulkanRenderStatus);
|
||||||
|
|
||||||
|
// 更新Vulkan帧计数
|
||||||
|
int vulkanFrameCount = m_vulkanWidget->getRenderFrameCount();
|
||||||
|
QString vulkanFrameCountStr = QString("Frame Count: <b>%1</b> frames").arg(vulkanFrameCount);
|
||||||
|
m_vulkanFrameCountLabel->setText(vulkanFrameCountStr);
|
||||||
|
|
||||||
|
// 更新Vulkan按钮状态
|
||||||
|
m_enableVulkanBtn->setEnabled(!isVulkanRendering && m_vulkanWidget->isInitialized());
|
||||||
|
m_disableVulkanBtn->setEnabled(isVulkanRendering);
|
||||||
|
}
|
||||||
|
#endif // ENABLE_VULKAN_WIDGET
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::updateButtonStates()
|
void MainWindow::updateButtonStates()
|
||||||
|
|
@ -263,3 +393,47 @@ void MainWindow::onResetFrameCountClicked()
|
||||||
|
|
||||||
updateStatusDisplay();
|
updateStatusDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_VULKAN_WIDGET
|
||||||
|
void MainWindow::onEnableVulkanClicked()
|
||||||
|
{
|
||||||
|
qDebug() << "Manual enable Vulkan rendering clicked";
|
||||||
|
|
||||||
|
if (m_vulkanWidget) {
|
||||||
|
if (!m_vulkanWidget->isInitialized()) {
|
||||||
|
bool success = m_vulkanWidget->initializeVulkan();
|
||||||
|
if (!success) {
|
||||||
|
QMessageBox::warning(this, "Vulkan Initialization Failed",
|
||||||
|
"Failed to initialize Vulkan:\n" + m_vulkanWidget->getLastError());
|
||||||
|
updateStatusDisplay();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_vulkanWidget->setRenderingEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStatusDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onDisableVulkanClicked()
|
||||||
|
{
|
||||||
|
qDebug() << "Manual disable Vulkan rendering clicked";
|
||||||
|
|
||||||
|
if (m_vulkanWidget) {
|
||||||
|
m_vulkanWidget->setRenderingEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStatusDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onResetVulkanFrameCountClicked()
|
||||||
|
{
|
||||||
|
qDebug() << "Reset Vulkan frame count clicked";
|
||||||
|
|
||||||
|
if (m_vulkanWidget) {
|
||||||
|
m_vulkanWidget->resetFrameCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStatusDisplay();
|
||||||
|
}
|
||||||
|
#endif // ENABLE_VULKAN_WIDGET
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,12 @@
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QGroupBox>
|
#include <QGroupBox>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
#include <QTabWidget>
|
||||||
#include "screenlockdetector.h"
|
#include "screenlockdetector.h"
|
||||||
#include "customwidget.h"
|
#include "customwidget.h"
|
||||||
|
#ifdef ENABLE_VULKAN_WIDGET
|
||||||
|
#include "vulkanwidget.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 主窗口类
|
* @brief 主窗口类
|
||||||
|
|
@ -61,6 +65,23 @@ private slots:
|
||||||
*/
|
*/
|
||||||
void onResetFrameCountClicked();
|
void onResetFrameCountClicked();
|
||||||
|
|
||||||
|
#ifdef ENABLE_VULKAN_WIDGET
|
||||||
|
/**
|
||||||
|
* @brief 启用Vulkan渲染按钮点击
|
||||||
|
*/
|
||||||
|
void onEnableVulkanClicked();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 禁用Vulkan渲染按钮点击
|
||||||
|
*/
|
||||||
|
void onDisableVulkanClicked();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 重置Vulkan帧计数按钮点击
|
||||||
|
*/
|
||||||
|
void onResetVulkanFrameCountClicked();
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* @brief 初始化UI组件
|
* @brief 初始化UI组件
|
||||||
|
|
@ -81,23 +102,41 @@ private:
|
||||||
// 核心组件
|
// 核心组件
|
||||||
ScreenLockDetector *m_lockDetector;
|
ScreenLockDetector *m_lockDetector;
|
||||||
CustomWidget *m_customWidget;
|
CustomWidget *m_customWidget;
|
||||||
|
#ifdef ENABLE_VULKAN_WIDGET
|
||||||
|
VulkanWidget *m_vulkanWidget;
|
||||||
|
#endif
|
||||||
|
|
||||||
// UI组件
|
// UI组件
|
||||||
QWidget *m_centralWidget;
|
QWidget *m_centralWidget;
|
||||||
QVBoxLayout *m_mainLayout;
|
QVBoxLayout *m_mainLayout;
|
||||||
|
QTabWidget *m_tabWidget;
|
||||||
|
|
||||||
// 控制面板
|
// 控制面板 - CustomWidget
|
||||||
QGroupBox *m_controlGroup;
|
QGroupBox *m_controlGroup;
|
||||||
QPushButton *m_enablePaintBtn;
|
QPushButton *m_enablePaintBtn;
|
||||||
QPushButton *m_disablePaintBtn;
|
QPushButton *m_disablePaintBtn;
|
||||||
QPushButton *m_resetFrameBtn;
|
QPushButton *m_resetFrameBtn;
|
||||||
|
#ifdef ENABLE_VULKAN_WIDGET
|
||||||
|
// 控制面板 - VulkanWidget
|
||||||
|
QGroupBox *m_vulkanControlGroup;
|
||||||
|
QPushButton *m_enableVulkanBtn;
|
||||||
|
QPushButton *m_disableVulkanBtn;
|
||||||
|
QPushButton *m_resetVulkanFrameBtn;
|
||||||
|
#endif
|
||||||
|
|
||||||
// 状态显示
|
// 状态显示 - CustomWidget
|
||||||
QGroupBox *m_statusGroup;
|
QGroupBox *m_statusGroup;
|
||||||
QLabel *m_lockStatusLabel;
|
QLabel *m_lockStatusLabel;
|
||||||
QLabel *m_paintStatusLabel;
|
QLabel *m_paintStatusLabel;
|
||||||
QLabel *m_frameCountLabel;
|
QLabel *m_frameCountLabel;
|
||||||
QLabel *m_detectorStatusLabel;
|
QLabel *m_detectorStatusLabel;
|
||||||
|
#ifdef ENABLE_VULKAN_WIDGET
|
||||||
|
// 状态显示 - VulkanWidget
|
||||||
|
QGroupBox *m_vulkanStatusGroup;
|
||||||
|
QLabel *m_vulkanInitLabel;
|
||||||
|
QLabel *m_vulkanRenderStatusLabel;
|
||||||
|
QLabel *m_vulkanFrameCountLabel;
|
||||||
|
#endif
|
||||||
|
|
||||||
// 更新定时器
|
// 更新定时器
|
||||||
QTimer *m_updateTimer;
|
QTimer *m_updateTimer;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,817 @@
|
||||||
|
#include "vulkanwidget.h"
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QShowEvent>
|
||||||
|
#include <QHideEvent>
|
||||||
|
#include <QResizeEvent>
|
||||||
|
#include <QPaintEvent>
|
||||||
|
#include <QWindow>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
// Include volk for Vulkan function loading
|
||||||
|
#define VK_NO_PROTOTYPES
|
||||||
|
#include "../../third_party/volk/volk.h"
|
||||||
|
|
||||||
|
// Platform-specific headers
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#elif defined(__linux__)
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
VulkanWidget::VulkanWidget(QWidget *parent)
|
||||||
|
: QWidget(parent)
|
||||||
|
, m_instance(VK_NULL_HANDLE)
|
||||||
|
, m_physicalDevice(VK_NULL_HANDLE)
|
||||||
|
, m_device(VK_NULL_HANDLE)
|
||||||
|
, m_queue(VK_NULL_HANDLE)
|
||||||
|
, m_surface(VK_NULL_HANDLE)
|
||||||
|
, m_swapchain(VK_NULL_HANDLE)
|
||||||
|
, m_commandPool(VK_NULL_HANDLE)
|
||||||
|
, m_initialized(false)
|
||||||
|
, m_renderingEnabled(false)
|
||||||
|
, m_needsResize(false)
|
||||||
|
, m_frameCount(0)
|
||||||
|
, m_queueFamilyIndex(0)
|
||||||
|
, m_currentFrame(0)
|
||||||
|
, m_surfaceWidth(0)
|
||||||
|
, m_surfaceHeight(0)
|
||||||
|
, m_renderTimer(nullptr)
|
||||||
|
{
|
||||||
|
// Set widget attributes for native window
|
||||||
|
setAttribute(Qt::WA_NativeWindow);
|
||||||
|
setAttribute(Qt::WA_PaintOnScreen);
|
||||||
|
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||||
|
|
||||||
|
// Create render timer
|
||||||
|
m_renderTimer = new QTimer(this);
|
||||||
|
connect(m_renderTimer, &QTimer::timeout, this, &VulkanWidget::onRenderTimer);
|
||||||
|
|
||||||
|
qDebug() << "VulkanWidget created";
|
||||||
|
}
|
||||||
|
|
||||||
|
VulkanWidget::~VulkanWidget()
|
||||||
|
{
|
||||||
|
qDebug() << "VulkanWidget destroying...";
|
||||||
|
|
||||||
|
if (m_renderTimer) {
|
||||||
|
m_renderTimer->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupVulkan();
|
||||||
|
|
||||||
|
qDebug() << "VulkanWidget destroyed";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VulkanWidget::initializeVulkan()
|
||||||
|
{
|
||||||
|
qDebug() << "Initializing Vulkan...";
|
||||||
|
|
||||||
|
if (m_initialized) {
|
||||||
|
qDebug() << "Vulkan already initialized";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Initialize volk
|
||||||
|
if (!initializeVolk()) {
|
||||||
|
setError("Failed to initialize volk");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Create Vulkan instance
|
||||||
|
if (!createInstance()) {
|
||||||
|
setError("Failed to create Vulkan instance");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Create surface from native window
|
||||||
|
if (!createSurface()) {
|
||||||
|
setError("Failed to create Vulkan surface");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: Select physical device
|
||||||
|
if (!selectPhysicalDevice()) {
|
||||||
|
setError("Failed to select physical device");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 5: Create logical device
|
||||||
|
if (!createDevice()) {
|
||||||
|
setError("Failed to create logical device");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 6: Create swapchain
|
||||||
|
if (!createSwapchain()) {
|
||||||
|
setError("Failed to create swapchain");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 7: Create command objects
|
||||||
|
if (!createCommandObjects()) {
|
||||||
|
setError("Failed to create command objects");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 8: Create synchronization objects
|
||||||
|
if (!createSyncObjects()) {
|
||||||
|
setError("Failed to create sync objects");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_initialized = true;
|
||||||
|
qDebug() << "Vulkan initialization successful";
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VulkanWidget::initializeVolk()
|
||||||
|
{
|
||||||
|
qDebug() << "Initializing volk...";
|
||||||
|
|
||||||
|
VkResult result = volkInitialize();
|
||||||
|
if (result != VK_SUCCESS) {
|
||||||
|
qDebug() << "Failed to initialize volk, error code:" << result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Volk initialized successfully";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VulkanWidget::createInstance()
|
||||||
|
{
|
||||||
|
qDebug() << "Creating Vulkan instance...";
|
||||||
|
|
||||||
|
VkApplicationInfo appInfo = {};
|
||||||
|
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
||||||
|
appInfo.pApplicationName = "VulkanWidget";
|
||||||
|
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
|
||||||
|
appInfo.pEngineName = "No Engine";
|
||||||
|
appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
|
||||||
|
appInfo.apiVersion = VK_API_VERSION_1_0;
|
||||||
|
|
||||||
|
std::vector<const char*> extensions = getRequiredInstanceExtensions();
|
||||||
|
|
||||||
|
VkInstanceCreateInfo createInfo = {};
|
||||||
|
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
||||||
|
createInfo.pApplicationInfo = &appInfo;
|
||||||
|
createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
|
||||||
|
createInfo.ppEnabledExtensionNames = extensions.data();
|
||||||
|
createInfo.enabledLayerCount = 0;
|
||||||
|
|
||||||
|
VkResult result = vkCreateInstance(&createInfo, nullptr, &m_instance);
|
||||||
|
if (result != VK_SUCCESS) {
|
||||||
|
qDebug() << "Failed to create Vulkan instance, error code:" << result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load instance-level functions
|
||||||
|
volkLoadInstance(m_instance);
|
||||||
|
|
||||||
|
qDebug() << "Vulkan instance created successfully";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VulkanWidget::createSurface()
|
||||||
|
{
|
||||||
|
qDebug() << "Creating Vulkan surface from native window...";
|
||||||
|
|
||||||
|
if (!windowHandle()) {
|
||||||
|
qDebug() << "Window handle not available, creating window...";
|
||||||
|
create();
|
||||||
|
if (!windowHandle()) {
|
||||||
|
qDebug() << "Failed to create window handle";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QWindow *window = windowHandle();
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
VkWin32SurfaceCreateInfoKHR createInfo = {};
|
||||||
|
createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
|
||||||
|
createInfo.hwnd = reinterpret_cast<HWND>(window->winId());
|
||||||
|
createInfo.hinstance = GetModuleHandle(nullptr);
|
||||||
|
|
||||||
|
VkResult result = vkCreateWin32SurfaceKHR(m_instance, &createInfo, nullptr, &m_surface);
|
||||||
|
|
||||||
|
#elif defined(__linux__)
|
||||||
|
VkXlibSurfaceCreateInfoKHR createInfo = {};
|
||||||
|
createInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR;
|
||||||
|
// Get X11 display - use default display
|
||||||
|
createInfo.dpy = XOpenDisplay(nullptr);
|
||||||
|
if (!createInfo.dpy) {
|
||||||
|
qDebug() << "Failed to open X11 display";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
createInfo.window = static_cast<Window>(window->winId());
|
||||||
|
|
||||||
|
VkResult result = vkCreateXlibSurfaceKHR(m_instance, &createInfo, nullptr, &m_surface);
|
||||||
|
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
// macOS requires MoltenVK and Metal
|
||||||
|
VkMetalSurfaceCreateInfoEXT createInfo = {};
|
||||||
|
createInfo.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
|
||||||
|
createInfo.pLayer = nullptr; // This needs proper Metal layer setup
|
||||||
|
|
||||||
|
VkResult result = vkCreateMetalSurfaceEXT(m_instance, &createInfo, nullptr, &m_surface);
|
||||||
|
#else
|
||||||
|
qDebug() << "Unsupported platform for Vulkan surface creation";
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (result != VK_SUCCESS) {
|
||||||
|
qDebug() << "Failed to create Vulkan surface, error code:" << result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Vulkan surface created successfully";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VulkanWidget::selectPhysicalDevice()
|
||||||
|
{
|
||||||
|
qDebug() << "Selecting physical device...";
|
||||||
|
|
||||||
|
uint32_t deviceCount = 0;
|
||||||
|
vkEnumeratePhysicalDevices(m_instance, &deviceCount, nullptr);
|
||||||
|
|
||||||
|
if (deviceCount == 0) {
|
||||||
|
qDebug() << "Failed to find GPUs with Vulkan support";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<VkPhysicalDevice> devices(deviceCount);
|
||||||
|
vkEnumeratePhysicalDevices(m_instance, &deviceCount, devices.data());
|
||||||
|
|
||||||
|
// Select the first suitable device
|
||||||
|
for (const auto& device : devices) {
|
||||||
|
VkPhysicalDeviceProperties properties;
|
||||||
|
vkGetPhysicalDeviceProperties(device, &properties);
|
||||||
|
|
||||||
|
qDebug() << "Found device:" << properties.deviceName;
|
||||||
|
|
||||||
|
// Temporarily set physical device to check queue families
|
||||||
|
m_physicalDevice = device;
|
||||||
|
|
||||||
|
// Check if device supports our queue family
|
||||||
|
uint32_t queueFamilyIndex;
|
||||||
|
if (findQueueFamily(queueFamilyIndex)) {
|
||||||
|
m_queueFamilyIndex = queueFamilyIndex;
|
||||||
|
qDebug() << "Selected device:" << properties.deviceName;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset if no suitable device found
|
||||||
|
m_physicalDevice = VK_NULL_HANDLE;
|
||||||
|
qDebug() << "Failed to find suitable physical device";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VulkanWidget::findQueueFamily(uint32_t &queueFamilyIndex)
|
||||||
|
{
|
||||||
|
uint32_t queueFamilyCount = 0;
|
||||||
|
vkGetPhysicalDeviceQueueFamilyProperties(m_physicalDevice, &queueFamilyCount, nullptr);
|
||||||
|
|
||||||
|
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
|
||||||
|
vkGetPhysicalDeviceQueueFamilyProperties(m_physicalDevice, &queueFamilyCount, queueFamilies.data());
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < queueFamilyCount; i++) {
|
||||||
|
// Check if queue family supports graphics
|
||||||
|
if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
|
||||||
|
// Check if queue family supports presentation
|
||||||
|
VkBool32 presentSupport = false;
|
||||||
|
vkGetPhysicalDeviceSurfaceSupportKHR(m_physicalDevice, i, m_surface, &presentSupport);
|
||||||
|
|
||||||
|
if (presentSupport) {
|
||||||
|
queueFamilyIndex = i;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VulkanWidget::createDevice()
|
||||||
|
{
|
||||||
|
qDebug() << "Creating logical device...";
|
||||||
|
|
||||||
|
float queuePriority = 1.0f;
|
||||||
|
VkDeviceQueueCreateInfo queueCreateInfo = {};
|
||||||
|
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
||||||
|
queueCreateInfo.queueFamilyIndex = m_queueFamilyIndex;
|
||||||
|
queueCreateInfo.queueCount = 1;
|
||||||
|
queueCreateInfo.pQueuePriorities = &queuePriority;
|
||||||
|
|
||||||
|
VkPhysicalDeviceFeatures deviceFeatures = {};
|
||||||
|
|
||||||
|
std::vector<const char*> deviceExtensions = getRequiredDeviceExtensions();
|
||||||
|
|
||||||
|
VkDeviceCreateInfo createInfo = {};
|
||||||
|
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
||||||
|
createInfo.pQueueCreateInfos = &queueCreateInfo;
|
||||||
|
createInfo.queueCreateInfoCount = 1;
|
||||||
|
createInfo.pEnabledFeatures = &deviceFeatures;
|
||||||
|
createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
|
||||||
|
createInfo.ppEnabledExtensionNames = deviceExtensions.data();
|
||||||
|
createInfo.enabledLayerCount = 0;
|
||||||
|
|
||||||
|
VkResult result = vkCreateDevice(m_physicalDevice, &createInfo, nullptr, &m_device);
|
||||||
|
if (result != VK_SUCCESS) {
|
||||||
|
qDebug() << "Failed to create logical device, error code:" << result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load device-level functions
|
||||||
|
volkLoadDevice(m_device);
|
||||||
|
|
||||||
|
// Get queue handle
|
||||||
|
vkGetDeviceQueue(m_device, m_queueFamilyIndex, 0, &m_queue);
|
||||||
|
|
||||||
|
qDebug() << "Logical device created successfully";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VulkanWidget::createSwapchain()
|
||||||
|
{
|
||||||
|
qDebug() << "Creating swapchain...";
|
||||||
|
|
||||||
|
// Query surface capabilities
|
||||||
|
VkSurfaceCapabilitiesKHR capabilities;
|
||||||
|
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_physicalDevice, m_surface, &capabilities);
|
||||||
|
|
||||||
|
// Query surface formats
|
||||||
|
uint32_t formatCount;
|
||||||
|
vkGetPhysicalDeviceSurfaceFormatsKHR(m_physicalDevice, m_surface, &formatCount, nullptr);
|
||||||
|
std::vector<VkSurfaceFormatKHR> formats(formatCount);
|
||||||
|
vkGetPhysicalDeviceSurfaceFormatsKHR(m_physicalDevice, m_surface, &formatCount, formats.data());
|
||||||
|
|
||||||
|
// Query present modes
|
||||||
|
uint32_t presentModeCount;
|
||||||
|
vkGetPhysicalDeviceSurfacePresentModesKHR(m_physicalDevice, m_surface, &presentModeCount, nullptr);
|
||||||
|
std::vector<VkPresentModeKHR> presentModes(presentModeCount);
|
||||||
|
vkGetPhysicalDeviceSurfacePresentModesKHR(m_physicalDevice, m_surface, &presentModeCount, presentModes.data());
|
||||||
|
|
||||||
|
// Select format
|
||||||
|
VkSurfaceFormatKHR surfaceFormat = formats[0];
|
||||||
|
for (const auto& format : formats) {
|
||||||
|
if (format.format == VK_FORMAT_B8G8R8A8_SRGB &&
|
||||||
|
format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
|
||||||
|
surfaceFormat = format;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select present mode (prefer mailbox, fallback to FIFO)
|
||||||
|
VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR;
|
||||||
|
for (const auto& mode : presentModes) {
|
||||||
|
if (mode == VK_PRESENT_MODE_MAILBOX_KHR) {
|
||||||
|
presentMode = mode;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine extent
|
||||||
|
VkExtent2D extent;
|
||||||
|
if (capabilities.currentExtent.width != UINT32_MAX) {
|
||||||
|
extent = capabilities.currentExtent;
|
||||||
|
} else {
|
||||||
|
extent.width = std::max(capabilities.minImageExtent.width,
|
||||||
|
std::min(capabilities.maxImageExtent.width, static_cast<uint32_t>(width())));
|
||||||
|
extent.height = std::max(capabilities.minImageExtent.height,
|
||||||
|
std::min(capabilities.maxImageExtent.height, static_cast<uint32_t>(height())));
|
||||||
|
}
|
||||||
|
|
||||||
|
m_surfaceWidth = extent.width;
|
||||||
|
m_surfaceHeight = extent.height;
|
||||||
|
|
||||||
|
// Determine image count
|
||||||
|
uint32_t imageCount = capabilities.minImageCount + 1;
|
||||||
|
if (capabilities.maxImageCount > 0 && imageCount > capabilities.maxImageCount) {
|
||||||
|
imageCount = capabilities.maxImageCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkSwapchainCreateInfoKHR createInfo = {};
|
||||||
|
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
|
||||||
|
createInfo.surface = m_surface;
|
||||||
|
createInfo.minImageCount = imageCount;
|
||||||
|
createInfo.imageFormat = surfaceFormat.format;
|
||||||
|
createInfo.imageColorSpace = surfaceFormat.colorSpace;
|
||||||
|
createInfo.imageExtent = extent;
|
||||||
|
createInfo.imageArrayLayers = 1;
|
||||||
|
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||||
|
createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||||
|
createInfo.preTransform = capabilities.currentTransform;
|
||||||
|
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
||||||
|
createInfo.presentMode = presentMode;
|
||||||
|
createInfo.clipped = VK_TRUE;
|
||||||
|
createInfo.oldSwapchain = VK_NULL_HANDLE;
|
||||||
|
|
||||||
|
VkResult result = vkCreateSwapchainKHR(m_device, &createInfo, nullptr, &m_swapchain);
|
||||||
|
if (result != VK_SUCCESS) {
|
||||||
|
qDebug() << "Failed to create swapchain, error code:" << result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve swapchain images
|
||||||
|
vkGetSwapchainImagesKHR(m_device, m_swapchain, &imageCount, nullptr);
|
||||||
|
m_swapchainImages.resize(imageCount);
|
||||||
|
vkGetSwapchainImagesKHR(m_device, m_swapchain, &imageCount,
|
||||||
|
reinterpret_cast<VkImage*>(m_swapchainImages.data()));
|
||||||
|
|
||||||
|
qDebug() << "Swapchain created successfully with" << imageCount << "images, size:"
|
||||||
|
<< m_surfaceWidth << "x" << m_surfaceHeight;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VulkanWidget::createCommandObjects()
|
||||||
|
{
|
||||||
|
qDebug() << "Creating command objects...";
|
||||||
|
|
||||||
|
VkCommandPoolCreateInfo poolInfo = {};
|
||||||
|
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
||||||
|
poolInfo.queueFamilyIndex = m_queueFamilyIndex;
|
||||||
|
poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
||||||
|
|
||||||
|
VkResult result = vkCreateCommandPool(m_device, &poolInfo, nullptr, &m_commandPool);
|
||||||
|
if (result != VK_SUCCESS) {
|
||||||
|
qDebug() << "Failed to create command pool, error code:" << result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_commandBuffers.resize(MAX_FRAMES_IN_FLIGHT);
|
||||||
|
|
||||||
|
VkCommandBufferAllocateInfo allocInfo = {};
|
||||||
|
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||||
|
allocInfo.commandPool = m_commandPool;
|
||||||
|
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||||
|
allocInfo.commandBufferCount = static_cast<uint32_t>(m_commandBuffers.size());
|
||||||
|
|
||||||
|
result = vkAllocateCommandBuffers(m_device, &allocInfo, m_commandBuffers.data());
|
||||||
|
if (result != VK_SUCCESS) {
|
||||||
|
qDebug() << "Failed to allocate command buffers, error code:" << result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Command objects created successfully";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VulkanWidget::createSyncObjects()
|
||||||
|
{
|
||||||
|
qDebug() << "Creating synchronization objects...";
|
||||||
|
|
||||||
|
m_imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
|
||||||
|
m_renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
|
||||||
|
m_inFlightFences.resize(MAX_FRAMES_IN_FLIGHT);
|
||||||
|
|
||||||
|
VkSemaphoreCreateInfo semaphoreInfo = {};
|
||||||
|
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
||||||
|
|
||||||
|
VkFenceCreateInfo fenceInfo = {};
|
||||||
|
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
||||||
|
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
|
||||||
|
|
||||||
|
for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
|
||||||
|
if (vkCreateSemaphore(m_device, &semaphoreInfo, nullptr, &m_imageAvailableSemaphores[i]) != VK_SUCCESS ||
|
||||||
|
vkCreateSemaphore(m_device, &semaphoreInfo, nullptr, &m_renderFinishedSemaphores[i]) != VK_SUCCESS ||
|
||||||
|
vkCreateFence(m_device, &fenceInfo, nullptr, &m_inFlightFences[i]) != VK_SUCCESS) {
|
||||||
|
qDebug() << "Failed to create synchronization objects";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Synchronization objects created successfully";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VulkanWidget::recreateSwapchain()
|
||||||
|
{
|
||||||
|
qDebug() << "Recreating swapchain...";
|
||||||
|
|
||||||
|
// Wait for device to be idle
|
||||||
|
vkDeviceWaitIdle(m_device);
|
||||||
|
|
||||||
|
// Cleanup old swapchain
|
||||||
|
cleanupSwapchain();
|
||||||
|
|
||||||
|
// Create new swapchain
|
||||||
|
if (!createSwapchain()) {
|
||||||
|
qDebug() << "Failed to recreate swapchain";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Swapchain recreated successfully";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWidget::renderFrame()
|
||||||
|
{
|
||||||
|
if (!m_initialized || !m_renderingEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for previous frame
|
||||||
|
vkWaitForFences(m_device, 1, &m_inFlightFences[m_currentFrame], VK_TRUE, UINT64_MAX);
|
||||||
|
|
||||||
|
// Acquire next image
|
||||||
|
uint32_t imageIndex;
|
||||||
|
VkResult result = vkAcquireNextImageKHR(m_device, m_swapchain, UINT64_MAX,
|
||||||
|
m_imageAvailableSemaphores[m_currentFrame],
|
||||||
|
VK_NULL_HANDLE, &imageIndex);
|
||||||
|
|
||||||
|
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
|
||||||
|
recreateSwapchain();
|
||||||
|
return;
|
||||||
|
} else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
|
||||||
|
qDebug() << "Failed to acquire swapchain image";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset fence
|
||||||
|
vkResetFences(m_device, 1, &m_inFlightFences[m_currentFrame]);
|
||||||
|
|
||||||
|
// Record command buffer
|
||||||
|
vkResetCommandBuffer(m_commandBuffers[m_currentFrame], 0);
|
||||||
|
recordCommandBuffer(m_commandBuffers[m_currentFrame], imageIndex);
|
||||||
|
|
||||||
|
// Submit command buffer
|
||||||
|
VkSubmitInfo submitInfo = {};
|
||||||
|
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||||
|
|
||||||
|
VkSemaphore waitSemaphores[] = {m_imageAvailableSemaphores[m_currentFrame]};
|
||||||
|
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
|
||||||
|
submitInfo.waitSemaphoreCount = 1;
|
||||||
|
submitInfo.pWaitSemaphores = waitSemaphores;
|
||||||
|
submitInfo.pWaitDstStageMask = waitStages;
|
||||||
|
submitInfo.commandBufferCount = 1;
|
||||||
|
submitInfo.pCommandBuffers = &m_commandBuffers[m_currentFrame];
|
||||||
|
|
||||||
|
VkSemaphore signalSemaphores[] = {m_renderFinishedSemaphores[m_currentFrame]};
|
||||||
|
submitInfo.signalSemaphoreCount = 1;
|
||||||
|
submitInfo.pSignalSemaphores = signalSemaphores;
|
||||||
|
|
||||||
|
result = vkQueueSubmit(m_queue, 1, &submitInfo, m_inFlightFences[m_currentFrame]);
|
||||||
|
if (result != VK_SUCCESS) {
|
||||||
|
qDebug() << "Failed to submit draw command buffer";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Present
|
||||||
|
VkPresentInfoKHR presentInfo = {};
|
||||||
|
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
||||||
|
presentInfo.waitSemaphoreCount = 1;
|
||||||
|
presentInfo.pWaitSemaphores = signalSemaphores;
|
||||||
|
|
||||||
|
VkSwapchainKHR swapchains[] = {m_swapchain};
|
||||||
|
presentInfo.swapchainCount = 1;
|
||||||
|
presentInfo.pSwapchains = swapchains;
|
||||||
|
presentInfo.pImageIndices = &imageIndex;
|
||||||
|
|
||||||
|
result = vkQueuePresentKHR(m_queue, &presentInfo);
|
||||||
|
|
||||||
|
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || m_needsResize) {
|
||||||
|
m_needsResize = false;
|
||||||
|
recreateSwapchain();
|
||||||
|
} else if (result != VK_SUCCESS) {
|
||||||
|
qDebug() << "Failed to present swapchain image";
|
||||||
|
}
|
||||||
|
|
||||||
|
m_currentFrame = (m_currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
|
||||||
|
m_frameCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWidget::recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex)
|
||||||
|
{
|
||||||
|
VkCommandBufferBeginInfo beginInfo = {};
|
||||||
|
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||||
|
|
||||||
|
vkBeginCommandBuffer(commandBuffer, &beginInfo);
|
||||||
|
|
||||||
|
// Simple clear color pass - cycle through colors based on frame count
|
||||||
|
VkClearColorValue clearColor;
|
||||||
|
float hue = (m_frameCount % 360) / 360.0f;
|
||||||
|
float r = std::abs(std::sin(hue * 6.28318f));
|
||||||
|
float g = std::abs(std::sin((hue + 0.33f) * 6.28318f));
|
||||||
|
float b = std::abs(std::sin((hue + 0.66f) * 6.28318f));
|
||||||
|
clearColor.float32[0] = r * 0.5f + 0.1f;
|
||||||
|
clearColor.float32[1] = g * 0.5f + 0.1f;
|
||||||
|
clearColor.float32[2] = b * 0.5f + 0.1f;
|
||||||
|
clearColor.float32[3] = 1.0f;
|
||||||
|
|
||||||
|
VkImageSubresourceRange range = {};
|
||||||
|
range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||||
|
range.baseMipLevel = 0;
|
||||||
|
range.levelCount = 1;
|
||||||
|
range.baseArrayLayer = 0;
|
||||||
|
range.layerCount = 1;
|
||||||
|
|
||||||
|
VkImage image = reinterpret_cast<VkImage>(m_swapchainImages[imageIndex]);
|
||||||
|
|
||||||
|
// Transition to transfer dst
|
||||||
|
VkImageMemoryBarrier barrier1 = {};
|
||||||
|
barrier1.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||||
|
barrier1.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
|
barrier1.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||||
|
barrier1.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||||
|
barrier1.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||||
|
barrier1.image = image;
|
||||||
|
barrier1.subresourceRange = range;
|
||||||
|
barrier1.srcAccessMask = 0;
|
||||||
|
barrier1.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, &barrier1);
|
||||||
|
|
||||||
|
// Clear image
|
||||||
|
vkCmdClearColorImage(commandBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||||
|
&clearColor, 1, &range);
|
||||||
|
|
||||||
|
// Transition to present
|
||||||
|
VkImageMemoryBarrier barrier2 = {};
|
||||||
|
barrier2.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||||
|
barrier2.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||||
|
barrier2.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||||
|
barrier2.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||||
|
barrier2.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||||
|
barrier2.image = image;
|
||||||
|
barrier2.subresourceRange = range;
|
||||||
|
barrier2.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||||
|
barrier2.dstAccessMask = 0;
|
||||||
|
|
||||||
|
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||||
|
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier2);
|
||||||
|
|
||||||
|
vkEndCommandBuffer(commandBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWidget::cleanupSwapchain()
|
||||||
|
{
|
||||||
|
if (m_swapchain != VK_NULL_HANDLE) {
|
||||||
|
vkDestroySwapchainKHR(m_device, m_swapchain, nullptr);
|
||||||
|
m_swapchain = VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_swapchainImages.clear();
|
||||||
|
m_swapchainImageViews.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWidget::cleanupVulkan()
|
||||||
|
{
|
||||||
|
if (!m_initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_device != VK_NULL_HANDLE) {
|
||||||
|
vkDeviceWaitIdle(m_device);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup sync objects
|
||||||
|
for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
|
||||||
|
if (m_imageAvailableSemaphores[i] != VK_NULL_HANDLE) {
|
||||||
|
vkDestroySemaphore(m_device, m_imageAvailableSemaphores[i], nullptr);
|
||||||
|
}
|
||||||
|
if (m_renderFinishedSemaphores[i] != VK_NULL_HANDLE) {
|
||||||
|
vkDestroySemaphore(m_device, m_renderFinishedSemaphores[i], nullptr);
|
||||||
|
}
|
||||||
|
if (m_inFlightFences[i] != VK_NULL_HANDLE) {
|
||||||
|
vkDestroyFence(m_device, m_inFlightFences[i], nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup command pool
|
||||||
|
if (m_commandPool != VK_NULL_HANDLE) {
|
||||||
|
vkDestroyCommandPool(m_device, m_commandPool, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup swapchain
|
||||||
|
cleanupSwapchain();
|
||||||
|
|
||||||
|
// Cleanup device
|
||||||
|
if (m_device != VK_NULL_HANDLE) {
|
||||||
|
vkDestroyDevice(m_device, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup surface
|
||||||
|
if (m_surface != VK_NULL_HANDLE) {
|
||||||
|
vkDestroySurfaceKHR(m_instance, m_surface, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup instance
|
||||||
|
if (m_instance != VK_NULL_HANDLE) {
|
||||||
|
vkDestroyInstance(m_instance, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_initialized = false;
|
||||||
|
qDebug() << "Vulkan cleanup complete";
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWidget::setRenderingEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
if (m_renderingEnabled == enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_renderingEnabled = enabled;
|
||||||
|
|
||||||
|
if (m_renderingEnabled) {
|
||||||
|
qDebug() << "Vulkan rendering enabled";
|
||||||
|
m_renderTimer->start(16); // ~60 FPS
|
||||||
|
} else {
|
||||||
|
qDebug() << "Vulkan rendering disabled";
|
||||||
|
m_renderTimer->stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VulkanWidget::isRenderingEnabled() const
|
||||||
|
{
|
||||||
|
return m_renderingEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
int VulkanWidget::getRenderFrameCount() const
|
||||||
|
{
|
||||||
|
return m_frameCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWidget::resetFrameCount()
|
||||||
|
{
|
||||||
|
m_frameCount = 0;
|
||||||
|
qDebug() << "Frame count reset";
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWidget::showEvent(QShowEvent *event)
|
||||||
|
{
|
||||||
|
QWidget::showEvent(event);
|
||||||
|
|
||||||
|
if (!m_initialized) {
|
||||||
|
initializeVulkan();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWidget::hideEvent(QHideEvent *event)
|
||||||
|
{
|
||||||
|
QWidget::hideEvent(event);
|
||||||
|
setRenderingEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWidget::resizeEvent(QResizeEvent *event)
|
||||||
|
{
|
||||||
|
QWidget::resizeEvent(event);
|
||||||
|
|
||||||
|
if (m_initialized) {
|
||||||
|
m_needsResize = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWidget::paintEvent(QPaintEvent *event)
|
||||||
|
{
|
||||||
|
// Do nothing - rendering is handled by Vulkan
|
||||||
|
Q_UNUSED(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
QPaintEngine* VulkanWidget::paintEngine() const
|
||||||
|
{
|
||||||
|
// Return nullptr to prevent Qt from using QPainter
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWidget::onRenderTimer()
|
||||||
|
{
|
||||||
|
renderFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanWidget::setError(const QString &error)
|
||||||
|
{
|
||||||
|
m_lastError = error;
|
||||||
|
qDebug() << "VulkanWidget Error:" << error;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<const char*> VulkanWidget::getRequiredInstanceExtensions()
|
||||||
|
{
|
||||||
|
std::vector<const char*> extensions;
|
||||||
|
|
||||||
|
extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
|
||||||
|
#elif defined(__linux__)
|
||||||
|
extensions.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
extensions.push_back(VK_EXT_METAL_SURFACE_EXTENSION_NAME);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<const char*> VulkanWidget::getRequiredDeviceExtensions()
|
||||||
|
{
|
||||||
|
std::vector<const char*> extensions;
|
||||||
|
extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
|
||||||
|
return extensions;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,250 @@
|
||||||
|
#ifndef VULKANWIDGET_H
|
||||||
|
#define VULKANWIDGET_H
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QString>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// Forward declare Vulkan types to avoid including volk.h in header
|
||||||
|
typedef struct VkInstance_T* VkInstance;
|
||||||
|
typedef struct VkPhysicalDevice_T* VkPhysicalDevice;
|
||||||
|
typedef struct VkDevice_T* VkDevice;
|
||||||
|
typedef struct VkQueue_T* VkQueue;
|
||||||
|
typedef struct VkSurfaceKHR_T* VkSurfaceKHR;
|
||||||
|
typedef struct VkSwapchainKHR_T* VkSwapchainKHR;
|
||||||
|
typedef struct VkCommandPool_T* VkCommandPool;
|
||||||
|
typedef struct VkCommandBuffer_T* VkCommandBuffer;
|
||||||
|
typedef struct VkSemaphore_T* VkSemaphore;
|
||||||
|
typedef struct VkFence_T* VkFence;
|
||||||
|
typedef uint32_t VkFlags;
|
||||||
|
typedef VkFlags VkSurfaceTransformFlagsKHR;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Vulkan渲染组件类
|
||||||
|
*
|
||||||
|
* 使用Qt的native window属性创建Vulkan Surface
|
||||||
|
* 通过volk机制加载Vulkan函数
|
||||||
|
*/
|
||||||
|
class VulkanWidget : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit VulkanWidget(QWidget *parent = nullptr);
|
||||||
|
~VulkanWidget();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化Vulkan环境
|
||||||
|
* @return true表示初始化成功,false表示失败
|
||||||
|
*/
|
||||||
|
bool initializeVulkan();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 启用或禁用渲染
|
||||||
|
* @param enabled true表示启用渲染,false表示禁用
|
||||||
|
*/
|
||||||
|
void setRenderingEnabled(bool enabled);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前渲染状态
|
||||||
|
* @return true表示渲染已启用,false表示已禁用
|
||||||
|
*/
|
||||||
|
bool isRenderingEnabled() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取渲染帧数统计
|
||||||
|
* @return 已渲染的总帧数
|
||||||
|
*/
|
||||||
|
int getRenderFrameCount() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 重置帧数计数器
|
||||||
|
*/
|
||||||
|
void resetFrameCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取初始化状态
|
||||||
|
* @return true表示Vulkan已成功初始化
|
||||||
|
*/
|
||||||
|
bool isInitialized() const { return m_initialized; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取错误信息
|
||||||
|
* @return 最后的错误信息字符串
|
||||||
|
*/
|
||||||
|
QString getLastError() const { return m_lastError; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* @brief Qt事件:组件显示时
|
||||||
|
*/
|
||||||
|
void showEvent(QShowEvent *event) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Qt事件:组件隐藏时
|
||||||
|
*/
|
||||||
|
void hideEvent(QHideEvent *event) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Qt事件:组件大小改变时
|
||||||
|
*/
|
||||||
|
void resizeEvent(QResizeEvent *event) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Qt事件:绘制事件(在Vulkan模式下不使用)
|
||||||
|
*/
|
||||||
|
void paintEvent(QPaintEvent *event) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 返回QPaintEngine (返回nullptr以使用native绘制)
|
||||||
|
*/
|
||||||
|
QPaintEngine* paintEngine() const override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
/**
|
||||||
|
* @brief 渲染定时器槽函数
|
||||||
|
*/
|
||||||
|
void onRenderTimer();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* @brief 初始化volk加载器
|
||||||
|
* @return true表示成功,false表示失败
|
||||||
|
*/
|
||||||
|
bool initializeVolk();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建Vulkan实例
|
||||||
|
* @return true表示成功,false表示失败
|
||||||
|
*/
|
||||||
|
bool createInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 选择物理设备
|
||||||
|
* @return true表示成功,false表示失败
|
||||||
|
*/
|
||||||
|
bool selectPhysicalDevice();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建Surface
|
||||||
|
* @return true表示成功,false表示失败
|
||||||
|
*/
|
||||||
|
bool createSurface();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建逻辑设备
|
||||||
|
* @return true表示成功,false表示失败
|
||||||
|
*/
|
||||||
|
bool createDevice();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建交换链
|
||||||
|
* @return true表示成功,false表示失败
|
||||||
|
*/
|
||||||
|
bool createSwapchain();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建命令池和命令缓冲
|
||||||
|
* @return true表示成功,false表示失败
|
||||||
|
*/
|
||||||
|
bool createCommandObjects();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建同步对象
|
||||||
|
* @return true表示成功,false表示失败
|
||||||
|
*/
|
||||||
|
bool createSyncObjects();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 重建交换链(窗口大小改变时)
|
||||||
|
* @return true表示成功,false表示失败
|
||||||
|
*/
|
||||||
|
bool recreateSwapchain();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染一帧
|
||||||
|
*/
|
||||||
|
void renderFrame();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清理Vulkan资源
|
||||||
|
*/
|
||||||
|
void cleanupVulkan();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清理交换链资源
|
||||||
|
*/
|
||||||
|
void cleanupSwapchain();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 记录渲染命令
|
||||||
|
* @param commandBuffer 命令缓冲
|
||||||
|
* @param imageIndex 交换链图像索引
|
||||||
|
*/
|
||||||
|
void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置错误信息
|
||||||
|
* @param error 错误信息字符串
|
||||||
|
*/
|
||||||
|
void setError(const QString &error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 查找合适的队列族
|
||||||
|
* @param queueFamilyIndex 输出参数:队列族索引
|
||||||
|
* @return true表示找到合适的队列族
|
||||||
|
*/
|
||||||
|
bool findQueueFamily(uint32_t &queueFamilyIndex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取必需的实例扩展
|
||||||
|
* @return 扩展名称列表
|
||||||
|
*/
|
||||||
|
std::vector<const char*> getRequiredInstanceExtensions();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取必需的设备扩展
|
||||||
|
* @return 扩展名称列表
|
||||||
|
*/
|
||||||
|
std::vector<const char*> getRequiredDeviceExtensions();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Vulkan对象
|
||||||
|
VkInstance m_instance;
|
||||||
|
VkPhysicalDevice m_physicalDevice;
|
||||||
|
VkDevice m_device;
|
||||||
|
VkQueue m_queue;
|
||||||
|
VkSurfaceKHR m_surface;
|
||||||
|
VkSwapchainKHR m_swapchain;
|
||||||
|
VkCommandPool m_commandPool;
|
||||||
|
std::vector<VkCommandBuffer> m_commandBuffers;
|
||||||
|
std::vector<VkSemaphore> m_imageAvailableSemaphores;
|
||||||
|
std::vector<VkSemaphore> m_renderFinishedSemaphores;
|
||||||
|
std::vector<VkFence> m_inFlightFences;
|
||||||
|
|
||||||
|
// 交换链图像
|
||||||
|
std::vector<void*> m_swapchainImages;
|
||||||
|
std::vector<void*> m_swapchainImageViews;
|
||||||
|
|
||||||
|
// 状态变量
|
||||||
|
bool m_initialized;
|
||||||
|
bool m_renderingEnabled;
|
||||||
|
bool m_needsResize;
|
||||||
|
int m_frameCount;
|
||||||
|
uint32_t m_queueFamilyIndex;
|
||||||
|
uint32_t m_currentFrame;
|
||||||
|
int m_surfaceWidth;
|
||||||
|
int m_surfaceHeight;
|
||||||
|
|
||||||
|
// 错误信息
|
||||||
|
QString m_lastError;
|
||||||
|
|
||||||
|
// 定时器
|
||||||
|
QTimer *m_renderTimer;
|
||||||
|
|
||||||
|
// 常量
|
||||||
|
static const int MAX_FRAMES_IN_FLIGHT = 2;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // VULKANWIDGET_H
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
name: build
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu, macos, windows]
|
||||||
|
name: ${{matrix.os}}
|
||||||
|
runs-on: ${{matrix.os}}-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: KhronosGroup/Vulkan-Headers
|
||||||
|
ref: main
|
||||||
|
path: Vulkan-Headers
|
||||||
|
fetch-depth: 0
|
||||||
|
fetch-tags: true
|
||||||
|
- name: move sdk
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
mv Vulkan-Headers ~/Vulkan-Headers
|
||||||
|
- name: build main
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
export VULKAN_SDK=~/Vulkan-Headers
|
||||||
|
git -C ~/Vulkan-Headers checkout main
|
||||||
|
test/run_tests.sh
|
||||||
|
- name: build 1.1.101
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
export VULKAN_SDK=~/Vulkan-Headers
|
||||||
|
git -C ~/Vulkan-Headers checkout sdk-1.1.101
|
||||||
|
test/run_tests.sh
|
||||||
|
- name: build 1.2.131
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
export VULKAN_SDK=~/Vulkan-Headers
|
||||||
|
git -C ~/Vulkan-Headers checkout sdk-1.2.131
|
||||||
|
test/run_tests.sh
|
||||||
|
- name: build 1.2.182
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
export VULKAN_SDK=~/Vulkan-Headers
|
||||||
|
git -C ~/Vulkan-Headers checkout sdk-1.2.182
|
||||||
|
test/run_tests.sh
|
||||||
|
- name: build 1.3.204
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
export VULKAN_SDK=~/Vulkan-Headers
|
||||||
|
git -C ~/Vulkan-Headers checkout sdk-1.3.204
|
||||||
|
test/run_tests.sh
|
||||||
|
- name: build 1.3.239
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
export VULKAN_SDK=~/Vulkan-Headers
|
||||||
|
git -C ~/Vulkan-Headers checkout sdk-1.3.239
|
||||||
|
test/run_tests.sh
|
||||||
|
- name: build 1.3.268
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
export VULKAN_SDK=~/Vulkan-Headers
|
||||||
|
git -C ~/Vulkan-Headers checkout vulkan-sdk-1.3.268
|
||||||
|
test/run_tests.sh
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
name: update
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 16 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update:
|
||||||
|
if: github.repository == 'zeux/volk'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||||
|
- name: update
|
||||||
|
run: |
|
||||||
|
python3 generate.py >version.txt
|
||||||
|
echo "VOLK_VERSION=`cat version.txt`" >> $GITHUB_ENV
|
||||||
|
rm version.txt
|
||||||
|
- name: create pr
|
||||||
|
uses: peter-evans/create-pull-request@v6
|
||||||
|
with:
|
||||||
|
branch: update/${{env.VOLK_VERSION}}
|
||||||
|
delete-branch: true
|
||||||
|
commit-message: Update to 1.3.${{env.VOLK_VERSION}}
|
||||||
|
title: Update to 1.3.${{env.VOLK_VERSION}}
|
||||||
|
author: GitHub <noreply@github.com>
|
||||||
|
- name: enable pr automerge
|
||||||
|
run: gh pr merge --merge --auto ${{env.PULL_REQUEST_NUMBER}}
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
continue-on-error: true
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
build/
|
||||||
|
CMakeLists.txt.user
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
cmake_policy(PUSH)
|
||||||
|
cmake_policy(SET CMP0048 NEW) # project(... VERSION ...) support
|
||||||
|
|
||||||
|
project(volk VERSION
|
||||||
|
# VOLK_GENERATE_VERSION
|
||||||
|
295
|
||||||
|
# VOLK_GENERATE_VERSION
|
||||||
|
LANGUAGES C
|
||||||
|
)
|
||||||
|
|
||||||
|
# CMake 3.12 changes the default behaviour of option() to leave local variables
|
||||||
|
# unchanged if they exist (which we want), but we must work with older CMake versions.
|
||||||
|
if(NOT DEFINED VOLK_STATIC_DEFINES)
|
||||||
|
set(VOLK_STATIC_DEFINES "" CACHE STRING "Additional defines for building the volk static library, e.g. Vulkan platform defines")
|
||||||
|
endif()
|
||||||
|
if(NOT DEFINED VOLK_PULL_IN_VULKAN)
|
||||||
|
option(VOLK_PULL_IN_VULKAN "Vulkan as a transitive dependency" ON)
|
||||||
|
endif()
|
||||||
|
if(NOT DEFINED VOLK_INSTALL)
|
||||||
|
option(VOLK_INSTALL "Create installation targets" OFF)
|
||||||
|
endif()
|
||||||
|
if(NOT DEFINED VOLK_HEADERS_ONLY)
|
||||||
|
option(VOLK_HEADERS_ONLY "Add interface library only" OFF)
|
||||||
|
endif()
|
||||||
|
if(NOT DEFINED VULKAN_HEADERS_INSTALL_DIR)
|
||||||
|
set(VULKAN_HEADERS_INSTALL_DIR "" CACHE PATH "Where to get the Vulkan headers")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# -----------------------------------------------------
|
||||||
|
# Static library
|
||||||
|
|
||||||
|
if(NOT VOLK_HEADERS_ONLY OR VOLK_INSTALL)
|
||||||
|
add_library(volk STATIC volk.h volk.c)
|
||||||
|
add_library(volk::volk ALIAS volk)
|
||||||
|
target_include_directories(volk PUBLIC
|
||||||
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>
|
||||||
|
$<INSTALL_INTERFACE:include>
|
||||||
|
)
|
||||||
|
if(VOLK_STATIC_DEFINES)
|
||||||
|
target_compile_definitions(volk PUBLIC ${VOLK_STATIC_DEFINES})
|
||||||
|
endif()
|
||||||
|
if (NOT WIN32)
|
||||||
|
target_link_libraries(volk PUBLIC ${CMAKE_DL_LIBS})
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# -----------------------------------------------------
|
||||||
|
# Interface library
|
||||||
|
|
||||||
|
add_library(volk_headers INTERFACE)
|
||||||
|
add_library(volk::volk_headers ALIAS volk_headers)
|
||||||
|
target_include_directories(volk_headers INTERFACE
|
||||||
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>
|
||||||
|
$<INSTALL_INTERFACE:include>
|
||||||
|
)
|
||||||
|
if (NOT WIN32)
|
||||||
|
target_link_libraries(volk_headers INTERFACE ${CMAKE_DL_LIBS})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# -----------------------------------------------------
|
||||||
|
# Vulkan transitive dependency
|
||||||
|
|
||||||
|
if(VOLK_PULL_IN_VULKAN)
|
||||||
|
# Try an explicit CMake variable first, then any Vulkan paths
|
||||||
|
# discovered by FindVulkan.cmake, then the $VULKAN_SDK environment
|
||||||
|
# variable if nothing else works.
|
||||||
|
if(VULKAN_HEADERS_INSTALL_DIR)
|
||||||
|
message("volk: using VULKAN_HEADERS_INSTALL_DIR option")
|
||||||
|
set(VOLK_INCLUDES "${VULKAN_HEADERS_INSTALL_DIR}/include")
|
||||||
|
else()
|
||||||
|
# If CMake has the FindVulkan module and it works, use it.
|
||||||
|
find_package(Vulkan QUIET)
|
||||||
|
if(Vulkan_INCLUDE_DIRS)
|
||||||
|
message("volk: using Vulkan_INCLUDE_DIRS from FindVulkan module")
|
||||||
|
set(VOLK_INCLUDES "${Vulkan_INCLUDE_DIRS}")
|
||||||
|
elseif(DEFINED ENV{VULKAN_SDK})
|
||||||
|
message("volk: using VULKAN_SDK environment variable")
|
||||||
|
set(VOLK_INCLUDES "$ENV{VULKAN_SDK}/include")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(VOLK_INCLUDES)
|
||||||
|
if(TARGET volk)
|
||||||
|
target_include_directories(volk PUBLIC "${VOLK_INCLUDES}")
|
||||||
|
endif()
|
||||||
|
target_include_directories(volk_headers INTERFACE "${VOLK_INCLUDES}")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# -----------------------------------------------------
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
if(VOLK_INSTALL)
|
||||||
|
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/volk)
|
||||||
|
|
||||||
|
# Install files
|
||||||
|
install(FILES volk.h volk.c DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||||
|
|
||||||
|
# Install library target and add it and any dependencies to export set.
|
||||||
|
install(TARGETS volk volk_headers
|
||||||
|
EXPORT volk-targets
|
||||||
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
|
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Actually write exported config w/ imported targets
|
||||||
|
install(EXPORT volk-targets
|
||||||
|
FILE volkTargets.cmake
|
||||||
|
NAMESPACE volk::
|
||||||
|
DESTINATION ${INSTALL_CONFIGDIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a ConfigVersion.cmake file:
|
||||||
|
include(CMakePackageConfigHelpers)
|
||||||
|
write_basic_package_version_file(
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/volkConfigVersion.cmake
|
||||||
|
COMPATIBILITY AnyNewerVersion
|
||||||
|
)
|
||||||
|
|
||||||
|
# Configure config file
|
||||||
|
configure_package_config_file(${CMAKE_CURRENT_LIST_DIR}/cmake/volkConfig.cmake.in
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/volkConfig.cmake
|
||||||
|
INSTALL_DESTINATION ${INSTALL_CONFIGDIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Install the fully generated config and configVersion files
|
||||||
|
install(FILES
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/volkConfig.cmake
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/volkConfigVersion.cmake
|
||||||
|
DESTINATION ${INSTALL_CONFIGDIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
endif()
|
||||||
|
cmake_policy(POP)
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2018-2024 Arseny Kapoulkine
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
# 🐺 volk [](https://github.com/zeux/volk/actions)
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
volk is a meta-loader for Vulkan. It allows you to dynamically load entrypoints required to use Vulkan
|
||||||
|
without linking to vulkan-1.dll or statically linking Vulkan loader. Additionally, volk simplifies the use of Vulkan extensions by automatically loading all associated entrypoints. Finally, volk enables loading
|
||||||
|
Vulkan entrypoints directly from the driver which can increase performance by skipping loader dispatch overhead.
|
||||||
|
|
||||||
|
volk is written in C89 and supports Windows, Linux, Android and macOS (via MoltenVK).
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
There are multiple ways to use volk in your project:
|
||||||
|
|
||||||
|
1. You can add `volk.c` to your build system. Note that the usual preprocessor defines that enable Vulkan's platform-specific functions (VK_USE_PLATFORM_WIN32_KHR, VK_USE_PLATFORM_XLIB_KHR, VK_USE_PLATFORM_MACOS_MVK, etc) must be passed as desired to the compiler when building `volk.c`.
|
||||||
|
2. You can use provided CMake files, with the usage detailed below.
|
||||||
|
3. You can use volk in header-only fashion. Include `volk.h` wherever you want to use Vulkan functions. In exactly one source file, define `VOLK_IMPLEMENTATION` before including `volk.h`. Do not build `volk.c` at all in this case - however, `volk.c` must still be in the same directory as `volk.h`. This method of integrating volk makes it possible to set the platform defines mentioned above with arbitrary (preprocessor) logic in your code.
|
||||||
|
|
||||||
|
## Basic usage
|
||||||
|
|
||||||
|
To use volk, you have to include `volk.h` instead of `vulkan/vulkan.h`; this is necessary to use function definitions from volk.
|
||||||
|
|
||||||
|
If some files in your application include `vulkan/vulkan.h` and don't include `volk.h`, this can result in symbol conflicts; consider defining `VK_NO_PROTOTYPES` when compiling code that uses Vulkan to make sure this doesn't happen. It's also important to make sure that `vulkan-1` is not linked into the application, as this results in symbol name conflicts as well.
|
||||||
|
|
||||||
|
To initialize volk, call this function first:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
VkResult volkInitialize();
|
||||||
|
```
|
||||||
|
|
||||||
|
This will attempt to load Vulkan loader from the system; if this function returns `VK_SUCCESS` you can proceed to create Vulkan instance.
|
||||||
|
If this function fails, this means Vulkan loader isn't installed on your system.
|
||||||
|
|
||||||
|
After creating the Vulkan instance using Vulkan API, call this function:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
void volkLoadInstance(VkInstance instance);
|
||||||
|
```
|
||||||
|
|
||||||
|
This function will load all required Vulkan entrypoints, including all extensions; you can use Vulkan from here on as usual.
|
||||||
|
|
||||||
|
## Optimizing device calls
|
||||||
|
|
||||||
|
If you use volk as described in the previous section, all device-related function calls, such as `vkCmdDraw`, will go through Vulkan loader dispatch code.
|
||||||
|
This allows you to transparently support multiple VkDevice objects in the same application, but comes at a price of dispatch overhead which can be as high as 7% depending on the driver and application.
|
||||||
|
|
||||||
|
To avoid this, you have two options:
|
||||||
|
|
||||||
|
1. For applications that use just one VkDevice object, load device-related Vulkan entrypoints directly from the driver with this function:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
void volkLoadDevice(VkDevice device);
|
||||||
|
```
|
||||||
|
|
||||||
|
2. For applications that use multiple VkDevice objects, load device-related Vulkan entrypoints into a table:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
void volkLoadDeviceTable(struct VolkDeviceTable* table, VkDevice device);
|
||||||
|
```
|
||||||
|
|
||||||
|
The second option requires you to change the application code to store one `VolkDeviceTable` per `VkDevice` and call functions from this table instead.
|
||||||
|
|
||||||
|
Device entrypoints are loaded using `vkGetDeviceProcAddr`; when no layers are present, this commonly results in most function pointers pointing directly at the driver functions, minimizing the call overhead. When layers are loaded, the entrypoints will point at the implementations in the first applicable layer, so this is compatible with any layers including validation layers.
|
||||||
|
|
||||||
|
Since `volkLoadDevice` overwrites some function pointers with device-specific versions, you can choose to use `volkLoadInstanceOnly` instead of `volkLoadInstance`; when using table-based interface this can also help enforce the usage of the function tables as `volkLoadInstanceOnly` will leave device-specific functions as `NULL`.
|
||||||
|
|
||||||
|
## CMake support
|
||||||
|
|
||||||
|
If your project uses CMake, volk provides you with targets corresponding to the different use cases:
|
||||||
|
|
||||||
|
1. Target `volk` is a static library. Any platform defines can be passed to the compiler by setting `VOLK_STATIC_DEFINES`. Example:
|
||||||
|
```cmake
|
||||||
|
if (WIN32)
|
||||||
|
set(VOLK_STATIC_DEFINES VK_USE_PLATFORM_WIN32_KHR)
|
||||||
|
elseif()
|
||||||
|
...
|
||||||
|
endif()
|
||||||
|
add_subdirectory(volk)
|
||||||
|
target_link_library(my_application PRIVATE volk)
|
||||||
|
```
|
||||||
|
2. Target `volk_headers` is an interface target for the header-only style. Example:
|
||||||
|
```cmake
|
||||||
|
add_subdirectory(volk)
|
||||||
|
target_link_library(my_application PRIVATE volk_headers)
|
||||||
|
```
|
||||||
|
and in the code:
|
||||||
|
```c
|
||||||
|
/* ...any logic setting VK_USE_PLATFORM_WIN32_KHR and friends... */
|
||||||
|
#define VOLK_IMPLEMENTATION
|
||||||
|
#include "volk.h"
|
||||||
|
```
|
||||||
|
|
||||||
|
The above example use `add_subdirectory` to include volk into CMake's build tree. This is a good choice if you copy the volk files into your project tree or as a git submodule.
|
||||||
|
|
||||||
|
Volk also supports installation and config-file packages. Installation is disabled by default (so as to not pollute user projects with install rules), and can be enabled by passing `-DVOLK_INSTALL=ON` to CMake. Once installed, do something like `find_package(volk CONFIG REQUIRED)` in your project's CMakeLists.txt. The imported volk targets are called `volk::volk` and `volk::volk_headers`.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This library is available to anybody free of charge, under the terms of MIT License (see LICENSE.md).
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
get_filename_component(volk_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
|
||||||
|
|
||||||
|
if(NOT TARGET volk::volk)
|
||||||
|
include("${volk_CMAKE_DIR}/volkTargets.cmake")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Mirror the default behaviour of the respective option.
|
||||||
|
if(NOT DEFINED VOLK_PULL_IN_VULKAN)
|
||||||
|
set(VOLK_PULL_IN_VULKAN ON)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(VOLK_PULL_IN_VULKAN)
|
||||||
|
find_package(Vulkan QUIET)
|
||||||
|
if(TARGET Vulkan::Vulkan)
|
||||||
|
add_dependencies(volk::volk Vulkan::Vulkan)
|
||||||
|
add_dependencies(volk::volk_headers Vulkan::Vulkan)
|
||||||
|
elseif(DEFINED ENV{VULKAN_SDK})
|
||||||
|
target_include_directories(volk::volk INTERFACE "$ENV{VULKAN_SDK}/include")
|
||||||
|
target_include_directories(volk::volk_headers INTERFACE "$ENV{VULKAN_SDK}/include")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
@ -0,0 +1,194 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
# This file is part of volk library; see volk.h for version/license details
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import urllib
|
||||||
|
import xml.etree.ElementTree as etree
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
cmdversions = {
|
||||||
|
"vkCmdSetDiscardRectangleEnableEXT": 2,
|
||||||
|
"vkCmdSetDiscardRectangleModeEXT": 2,
|
||||||
|
"vkCmdSetExclusiveScissorEnableNV": 2
|
||||||
|
}
|
||||||
|
|
||||||
|
def parse_xml(path):
|
||||||
|
file = urllib.request.urlopen(path) if path.startswith("http") else open(path, 'r')
|
||||||
|
with file:
|
||||||
|
tree = etree.parse(file)
|
||||||
|
return tree
|
||||||
|
|
||||||
|
def patch_file(path, blocks):
|
||||||
|
result = []
|
||||||
|
block = None
|
||||||
|
|
||||||
|
with open(path, 'r') as file:
|
||||||
|
for line in file.readlines():
|
||||||
|
if block:
|
||||||
|
if line == block:
|
||||||
|
result.append(line)
|
||||||
|
block = None
|
||||||
|
else:
|
||||||
|
result.append(line)
|
||||||
|
# C comment marker
|
||||||
|
if line.strip().startswith('/* VOLK_GENERATE_'):
|
||||||
|
block = line
|
||||||
|
result.append(blocks[line.strip()[17:-3]])
|
||||||
|
# Shell/CMake comment marker
|
||||||
|
elif line.strip().startswith('# VOLK_GENERATE_'):
|
||||||
|
block = line
|
||||||
|
result.append(blocks[line.strip()[16:]])
|
||||||
|
|
||||||
|
with open(path, 'w', newline='\n') as file:
|
||||||
|
for line in result:
|
||||||
|
file.write(line)
|
||||||
|
|
||||||
|
def is_descendant_type(types, name, base):
|
||||||
|
if name == base:
|
||||||
|
return True
|
||||||
|
type = types.get(name)
|
||||||
|
if not type:
|
||||||
|
return False
|
||||||
|
parents = type.get('parent')
|
||||||
|
if not parents:
|
||||||
|
return False
|
||||||
|
return any([is_descendant_type(types, parent, base) for parent in parents.split(',')])
|
||||||
|
|
||||||
|
def defined(key):
|
||||||
|
return 'defined(' + key + ')'
|
||||||
|
|
||||||
|
def cdepends(key):
|
||||||
|
return re.sub(r'[a-zA-Z0-9_]+', lambda m: defined(m.group(0)), key).replace(',', ' || ').replace('+', ' && ')
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
specpath = "https://raw.githubusercontent.com/KhronosGroup/Vulkan-Docs/main/xml/vk.xml"
|
||||||
|
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
specpath = sys.argv[1]
|
||||||
|
|
||||||
|
spec = parse_xml(specpath)
|
||||||
|
|
||||||
|
block_keys = ('DEVICE_TABLE', 'PROTOTYPES_H', 'PROTOTYPES_C', 'LOAD_LOADER', 'LOAD_INSTANCE', 'LOAD_DEVICE', 'LOAD_DEVICE_TABLE')
|
||||||
|
|
||||||
|
blocks = {}
|
||||||
|
|
||||||
|
version = spec.find('types/type[name="VK_HEADER_VERSION"]')
|
||||||
|
blocks['VERSION'] = version.find('name').tail.strip() + '\n'
|
||||||
|
blocks['VERSION_DEFINE'] = '#define VOLK_HEADER_VERSION ' + version.find('name').tail.strip() + '\n'
|
||||||
|
|
||||||
|
command_groups = OrderedDict()
|
||||||
|
instance_commands = set()
|
||||||
|
|
||||||
|
for feature in spec.findall('feature'):
|
||||||
|
api = feature.get('api')
|
||||||
|
if 'vulkan' not in api.split(','):
|
||||||
|
continue
|
||||||
|
key = defined(feature.get('name'))
|
||||||
|
cmdrefs = feature.findall('require/command')
|
||||||
|
command_groups[key] = [cmdref.get('name') for cmdref in cmdrefs]
|
||||||
|
|
||||||
|
for ext in sorted(spec.findall('extensions/extension'), key=lambda ext: ext.get('name')):
|
||||||
|
supported = ext.get('supported')
|
||||||
|
if 'vulkan' not in supported.split(','):
|
||||||
|
continue
|
||||||
|
name = ext.get('name')
|
||||||
|
type = ext.get('type')
|
||||||
|
for req in ext.findall('require'):
|
||||||
|
key = defined(name)
|
||||||
|
if req.get('feature'): # old-style XML depends specification
|
||||||
|
for i in req.get('feature').split(','):
|
||||||
|
key += ' && ' + defined(i)
|
||||||
|
if req.get('extension'): # old-style XML depends specification
|
||||||
|
for i in req.get('extension').split(','):
|
||||||
|
key += ' && ' + defined(i)
|
||||||
|
if req.get('depends'): # new-style XML depends specification
|
||||||
|
dep = cdepends(req.get('depends'))
|
||||||
|
key += ' && ' + ('(' + dep + ')' if '||' in dep else dep)
|
||||||
|
cmdrefs = req.findall('command')
|
||||||
|
for cmdref in cmdrefs:
|
||||||
|
ver = cmdversions.get(cmdref.get('name'))
|
||||||
|
if ver:
|
||||||
|
command_groups.setdefault(key + ' && ' + name.upper() + '_SPEC_VERSION >= ' + str(ver), []).append(cmdref.get('name'))
|
||||||
|
else:
|
||||||
|
command_groups.setdefault(key, []).append(cmdref.get('name'))
|
||||||
|
if type == 'instance':
|
||||||
|
for cmdref in cmdrefs:
|
||||||
|
instance_commands.add(cmdref.get('name'))
|
||||||
|
|
||||||
|
commands_to_groups = OrderedDict()
|
||||||
|
|
||||||
|
for (group, cmdnames) in command_groups.items():
|
||||||
|
for name in cmdnames:
|
||||||
|
commands_to_groups.setdefault(name, []).append(group)
|
||||||
|
|
||||||
|
for (group, cmdnames) in command_groups.items():
|
||||||
|
command_groups[group] = [name for name in cmdnames if len(commands_to_groups[name]) == 1]
|
||||||
|
|
||||||
|
for (name, groups) in commands_to_groups.items():
|
||||||
|
if len(groups) == 1:
|
||||||
|
continue
|
||||||
|
key = ' || '.join(['(' + g + ')' for g in groups])
|
||||||
|
command_groups.setdefault(key, []).append(name)
|
||||||
|
|
||||||
|
commands = {}
|
||||||
|
|
||||||
|
for cmd in spec.findall('commands/command'):
|
||||||
|
if not cmd.get('alias'):
|
||||||
|
name = cmd.findtext('proto/name')
|
||||||
|
commands[name] = cmd
|
||||||
|
|
||||||
|
for cmd in spec.findall('commands/command'):
|
||||||
|
if cmd.get('alias'):
|
||||||
|
name = cmd.get('name')
|
||||||
|
commands[name] = commands[cmd.get('alias')]
|
||||||
|
|
||||||
|
types = {}
|
||||||
|
|
||||||
|
for type in spec.findall('types/type'):
|
||||||
|
name = type.findtext('name')
|
||||||
|
if name:
|
||||||
|
types[name] = type
|
||||||
|
|
||||||
|
for key in block_keys:
|
||||||
|
blocks[key] = ''
|
||||||
|
|
||||||
|
for (group, cmdnames) in command_groups.items():
|
||||||
|
ifdef = '#if ' + group + '\n'
|
||||||
|
|
||||||
|
for key in block_keys:
|
||||||
|
blocks[key] += ifdef
|
||||||
|
|
||||||
|
for name in sorted(cmdnames):
|
||||||
|
cmd = commands[name]
|
||||||
|
type = cmd.findtext('param[1]/type')
|
||||||
|
|
||||||
|
if name == 'vkGetInstanceProcAddr':
|
||||||
|
type = ''
|
||||||
|
if name == 'vkGetDeviceProcAddr':
|
||||||
|
type = 'VkInstance'
|
||||||
|
|
||||||
|
if is_descendant_type(types, type, 'VkDevice') and name not in instance_commands:
|
||||||
|
blocks['LOAD_DEVICE'] += '\t' + name + ' = (PFN_' + name + ')load(context, "' + name + '");\n'
|
||||||
|
blocks['DEVICE_TABLE'] += '\tPFN_' + name + ' ' + name + ';\n'
|
||||||
|
blocks['LOAD_DEVICE_TABLE'] += '\ttable->' + name + ' = (PFN_' + name + ')load(context, "' + name + '");\n'
|
||||||
|
elif is_descendant_type(types, type, 'VkInstance'):
|
||||||
|
blocks['LOAD_INSTANCE'] += '\t' + name + ' = (PFN_' + name + ')load(context, "' + name + '");\n'
|
||||||
|
elif type != '':
|
||||||
|
blocks['LOAD_LOADER'] += '\t' + name + ' = (PFN_' + name + ')load(context, "' + name + '");\n'
|
||||||
|
|
||||||
|
blocks['PROTOTYPES_H'] += 'extern PFN_' + name + ' ' + name + ';\n'
|
||||||
|
blocks['PROTOTYPES_C'] += 'PFN_' + name + ' ' + name + ';\n'
|
||||||
|
|
||||||
|
for key in block_keys:
|
||||||
|
if blocks[key].endswith(ifdef):
|
||||||
|
blocks[key] = blocks[key][:-len(ifdef)]
|
||||||
|
else:
|
||||||
|
blocks[key] += '#endif /* ' + group + ' */\n'
|
||||||
|
|
||||||
|
patch_file('volk.h', blocks)
|
||||||
|
patch_file('volk.c', blocks)
|
||||||
|
patch_file('CMakeLists.txt', blocks)
|
||||||
|
|
||||||
|
print(version.find('name').tail.strip())
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
project(volk_test LANGUAGES C)
|
||||||
|
|
||||||
|
# Include volk from a CMake package config.
|
||||||
|
# CMAKE_PREFIX_PATH or volk_DIR must be set properly.
|
||||||
|
find_package(volk CONFIG REQUIRED)
|
||||||
|
|
||||||
|
add_executable(volk_test main.c)
|
||||||
|
target_link_libraries(volk_test PRIVATE volk::volk_headers)
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
/* Set platform defines at build time for volk to pick up. */
|
||||||
|
#if defined(_WIN32)
|
||||||
|
# define VK_USE_PLATFORM_WIN32_KHR
|
||||||
|
#elif defined(__linux__) || defined(__unix__)
|
||||||
|
# define VK_USE_PLATFORM_XLIB_KHR
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
# define VK_USE_PLATFORM_MACOS_MVK
|
||||||
|
#else
|
||||||
|
# error "Platform not supported by this example."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define VOLK_IMPLEMENTATION
|
||||||
|
#include "volk.h"
|
||||||
|
|
||||||
|
#include "stdio.h"
|
||||||
|
#include "stdlib.h"
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
VkResult r;
|
||||||
|
uint32_t version;
|
||||||
|
void* ptr;
|
||||||
|
|
||||||
|
/* This won't compile if the appropriate Vulkan platform define isn't set. */
|
||||||
|
ptr =
|
||||||
|
#if defined(_WIN32)
|
||||||
|
&vkCreateWin32SurfaceKHR;
|
||||||
|
#elif defined(__linux__) || defined(__unix__)
|
||||||
|
&vkCreateXlibSurfaceKHR;
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
&vkCreateMacOSSurfaceMVK;
|
||||||
|
#else
|
||||||
|
/* Platform not recogized for testing. */
|
||||||
|
NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Try to initialize volk. This might not work on CI builds, but the
|
||||||
|
* above should have compiled at least. */
|
||||||
|
r = volkInitialize();
|
||||||
|
if (r != VK_SUCCESS) {
|
||||||
|
printf("volkInitialize failed!\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
version = volkGetInstanceVersion();
|
||||||
|
printf("Vulkan version %d.%d.%d initialized.\n",
|
||||||
|
VK_VERSION_MAJOR(version),
|
||||||
|
VK_VERSION_MINOR(version),
|
||||||
|
VK_VERSION_PATCH(version));
|
||||||
|
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Compiles the volk sources as part of a user project.
|
||||||
|
# Volk comes with a volk.c for this purpose.
|
||||||
|
# Note that for volk to properly handle platform defines,
|
||||||
|
# those have to be set at build time.
|
||||||
|
# Also note that this way the Vulkan headers must
|
||||||
|
# handled by the user project as well as linking to dl on
|
||||||
|
# non-Windows platforms.
|
||||||
|
# For these reasons it's recommended to use one of
|
||||||
|
# the other ways to include volk (see the other examples).
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
project(volk_test LANGUAGES C)
|
||||||
|
|
||||||
|
add_executable(volk_test main.c ../../volk.c)
|
||||||
|
|
||||||
|
# Set include path for volk.h
|
||||||
|
target_include_directories(volk_test PRIVATE ../..)
|
||||||
|
|
||||||
|
# Set suitable platform defines
|
||||||
|
if(CMAKE_SYSTEM_NAME STREQUAL Windows)
|
||||||
|
target_compile_definitions(volk_test PRIVATE VK_USE_PLATFORM_WIN32_KHR)
|
||||||
|
elseif(CMAKE_SYSTEM_NAME STREQUAL Linux)
|
||||||
|
target_compile_definitions(volk_test PRIVATE VK_USE_PLATFORM_XLIB_KHR)
|
||||||
|
elseif(CMAKE_SYSTEM_NAME STREQUAL Darwin)
|
||||||
|
target_compile_definitions(volk_test PRIVATE VK_USE_PLATFORM_MACOS_MVK)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Link requires libraries
|
||||||
|
if(NOT WIN32)
|
||||||
|
target_link_libraries(volk_test PRIVATE dl)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Get Vulkan dependency
|
||||||
|
find_package(Vulkan QUIET)
|
||||||
|
if(TARGET Vulkan::Vulkan)
|
||||||
|
# Note: We don't use target_link_libraries for Vulkan::Vulkan to avoid a static dependency on libvulkan1
|
||||||
|
target_include_directories(volk_test PRIVATE ${Vulkan_INCLUDE_DIRS})
|
||||||
|
elseif(DEFINED ENV{VULKAN_SDK})
|
||||||
|
target_include_directories(volk_test PRIVATE "$ENV{VULKAN_SDK}/include")
|
||||||
|
endif()
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
#include "volk.h"
|
||||||
|
|
||||||
|
#include "stdio.h"
|
||||||
|
#include "stdlib.h"
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
VkResult r;
|
||||||
|
uint32_t version;
|
||||||
|
void* ptr;
|
||||||
|
|
||||||
|
/* This won't compile if the appropriate Vulkan platform define isn't set. */
|
||||||
|
ptr =
|
||||||
|
#if defined(_WIN32)
|
||||||
|
&vkCreateWin32SurfaceKHR;
|
||||||
|
#elif defined(__linux__) || defined(__unix__)
|
||||||
|
&vkCreateXlibSurfaceKHR;
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
&vkCreateMacOSSurfaceMVK;
|
||||||
|
#else
|
||||||
|
/* Platform not recogized for testing. */
|
||||||
|
NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Try to initialize volk. This might not work on CI builds, but the
|
||||||
|
* above should have compiled at least. */
|
||||||
|
r = volkInitialize();
|
||||||
|
if (r != VK_SUCCESS) {
|
||||||
|
printf("volkInitialize failed!\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
version = volkGetInstanceVersion();
|
||||||
|
printf("Vulkan version %d.%d.%d initialized.\n",
|
||||||
|
VK_VERSION_MAJOR(version),
|
||||||
|
VK_VERSION_MINOR(version),
|
||||||
|
VK_VERSION_PATCH(version));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Include the volk target through add_subdirectory.
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
project(volk_test LANGUAGES C)
|
||||||
|
|
||||||
|
# Include volk as part of the build tree to make the target known.
|
||||||
|
# The two-argument version of add_subdirectory allows adding non-subdirs.
|
||||||
|
add_subdirectory(../.. volk)
|
||||||
|
|
||||||
|
add_executable(volk_test main.c)
|
||||||
|
target_link_libraries(volk_test PRIVATE volk_headers)
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
/* Set platform defines at build time for volk to pick up. */
|
||||||
|
#if defined(_WIN32)
|
||||||
|
# define VK_USE_PLATFORM_WIN32_KHR
|
||||||
|
#elif defined(__linux__) || defined(__unix__)
|
||||||
|
# define VK_USE_PLATFORM_XLIB_KHR
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
# define VK_USE_PLATFORM_MACOS_MVK
|
||||||
|
#else
|
||||||
|
# error "Platform not supported by this example."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define VOLK_IMPLEMENTATION
|
||||||
|
#include "volk.h"
|
||||||
|
|
||||||
|
#include "stdio.h"
|
||||||
|
#include "stdlib.h"
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
VkResult r;
|
||||||
|
uint32_t version;
|
||||||
|
void* ptr;
|
||||||
|
|
||||||
|
/* This won't compile if the appropriate Vulkan platform define isn't set. */
|
||||||
|
ptr =
|
||||||
|
#if defined(_WIN32)
|
||||||
|
&vkCreateWin32SurfaceKHR;
|
||||||
|
#elif defined(__linux__) || defined(__unix__)
|
||||||
|
&vkCreateXlibSurfaceKHR;
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
&vkCreateMacOSSurfaceMVK;
|
||||||
|
#else
|
||||||
|
/* Platform not recogized for testing. */
|
||||||
|
NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Try to initialize volk. This might not work on CI builds, but the
|
||||||
|
* above should have compiled at least. */
|
||||||
|
r = volkInitialize();
|
||||||
|
if (r != VK_SUCCESS) {
|
||||||
|
printf("volkInitialize failed!\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
version = volkGetInstanceVersion();
|
||||||
|
printf("Vulkan version %d.%d.%d initialized.\n",
|
||||||
|
VK_VERSION_MAJOR(version),
|
||||||
|
VK_VERSION_MINOR(version),
|
||||||
|
VK_VERSION_PATCH(version));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Include the volk target through add_subdirectory, use the static lib target.
|
||||||
|
# We must set platform defines.
|
||||||
|
# By default, Vulkan is pulled in as transitive dependency if found.
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
project(volk_test LANGUAGES C)
|
||||||
|
|
||||||
|
# Set a suitable platform define to compile volk with.
|
||||||
|
if(CMAKE_SYSTEM_NAME STREQUAL Windows)
|
||||||
|
set(VOLK_STATIC_DEFINES VK_USE_PLATFORM_WIN32_KHR)
|
||||||
|
elseif(CMAKE_SYSTEM_NAME STREQUAL Linux)
|
||||||
|
set(VOLK_STATIC_DEFINES VK_USE_PLATFORM_XLIB_KHR)
|
||||||
|
elseif(CMAKE_SYSTEM_NAME STREQUAL Darwin)
|
||||||
|
set(VOLK_STATIC_DEFINES VK_USE_PLATFORM_MACOS_MVK)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Include volk as part of the build tree to make the target known.
|
||||||
|
# The two-argument version of add_subdirectory allows adding non-subdirs.
|
||||||
|
add_subdirectory(../.. volk)
|
||||||
|
|
||||||
|
add_executable(volk_test main.c)
|
||||||
|
target_link_libraries(volk_test PRIVATE volk)
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
#include "volk.h"
|
||||||
|
|
||||||
|
#include "stdio.h"
|
||||||
|
#include "stdlib.h"
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
VkResult r;
|
||||||
|
uint32_t version;
|
||||||
|
void* ptr;
|
||||||
|
|
||||||
|
/* This won't compile if the appropriate Vulkan platform define isn't set. */
|
||||||
|
ptr =
|
||||||
|
#if defined(_WIN32)
|
||||||
|
&vkCreateWin32SurfaceKHR;
|
||||||
|
#elif defined(__linux__) || defined(__unix__)
|
||||||
|
&vkCreateXlibSurfaceKHR;
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
&vkCreateMacOSSurfaceMVK;
|
||||||
|
#else
|
||||||
|
/* Platform not recogized for testing. */
|
||||||
|
NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Try to initialize volk. This might not work on CI builds, but the
|
||||||
|
* above should have compiled at least. */
|
||||||
|
r = volkInitialize();
|
||||||
|
if (r != VK_SUCCESS) {
|
||||||
|
printf("volkInitialize failed!\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
version = volkGetInstanceVersion();
|
||||||
|
printf("Vulkan version %d.%d.%d initialized.\n",
|
||||||
|
VK_VERSION_MAJOR(version),
|
||||||
|
VK_VERSION_MINOR(version),
|
||||||
|
VK_VERSION_PATCH(version));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
function reset_build {
|
||||||
|
for DIR in "_build" "_installed"
|
||||||
|
do
|
||||||
|
if [ -d $DIR ]; then
|
||||||
|
rm -rf $DIR
|
||||||
|
fi
|
||||||
|
mkdir -p $DIR
|
||||||
|
done
|
||||||
|
}
|
||||||
|
function run_volk_test {
|
||||||
|
for FILE in "./volk_test" "./volk_test.exe" "Debug/volk_test.exe" "Release/volk_test.exe"
|
||||||
|
do
|
||||||
|
if [ -f $FILE ]; then
|
||||||
|
echo "Running test:"
|
||||||
|
$FILE
|
||||||
|
RC=$?
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "volk_test return code: $RC"
|
||||||
|
}
|
||||||
|
|
||||||
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||||
|
pushd $SCRIPT_DIR/..
|
||||||
|
|
||||||
|
reset_build
|
||||||
|
pushd _build
|
||||||
|
cmake -DCMAKE_INSTALL_PREFIX=../_installed -DVOLK_INSTALL=ON .. || exit 1
|
||||||
|
cmake --build . --target install || exit 1
|
||||||
|
popd
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "cmake_using_source_directly =======================================>"
|
||||||
|
echo
|
||||||
|
|
||||||
|
pushd test/cmake_using_source_directly
|
||||||
|
reset_build
|
||||||
|
pushd _build
|
||||||
|
cmake .. || exit 1
|
||||||
|
cmake --build . || exit 1
|
||||||
|
run_volk_test
|
||||||
|
popd
|
||||||
|
popd
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "cmake_using_subdir_static =======================================>"
|
||||||
|
echo
|
||||||
|
|
||||||
|
pushd test/cmake_using_subdir_static
|
||||||
|
reset_build
|
||||||
|
pushd _build
|
||||||
|
cmake .. || exit 1
|
||||||
|
cmake --build . || exit 1
|
||||||
|
run_volk_test
|
||||||
|
popd
|
||||||
|
popd
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "cmake_using_subdir_headers =======================================>"
|
||||||
|
echo
|
||||||
|
|
||||||
|
pushd test/cmake_using_subdir_headers
|
||||||
|
reset_build
|
||||||
|
pushd _build
|
||||||
|
cmake .. || exit 1
|
||||||
|
cmake --build . || exit 1
|
||||||
|
run_volk_test
|
||||||
|
popd
|
||||||
|
popd
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "cmake_using_installed_headers =======================================>"
|
||||||
|
echo
|
||||||
|
|
||||||
|
pushd test/cmake_using_installed_headers
|
||||||
|
reset_build
|
||||||
|
pushd _build
|
||||||
|
cmake -DCMAKE_INSTALL_PREFIX=../../../_installed/lib/cmake .. || exit 1
|
||||||
|
cmake --build . || exit 1
|
||||||
|
run_volk_test
|
||||||
|
popd
|
||||||
|
popd
|
||||||
|
|
||||||
|
popd
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue