successfully creating D3D texture with D3D11_RESOURCE_MISC_SHARED_NTHANDLE;
successfully allocating with VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT working copying vulkan texture successfully passing D3D texture back to Flutter chore: Dart/Windows sample project: remove unnnecessary InvalidateRect from update() chore: Dart/Windows sample project: add generated bindings successfully blitting from Vulkan swapchain to D3D texture working Vulkan texture integration with Flutter refactor to allow disposal of resources in destructors handle destroyTexture correctly correctly implement surface resizing/destruction move Windows engine to Vulkan backend and flush after creating swapchain add vulkan + vkshaders to Windows libs update materials with Vulkan move Vulkan implementation to thermion_Dart remove extras folder thermion_flutter plugin updates update build hook to copy .lib file to output directory and use -vulkan lib zip file thermion_flutter cleanup reinstate stereoscopic on Windows add dxgi and d3d11.lib to windows header pragma update cli_windows sample project copy filament/vulkan headers to output directory. This was originally added to facilitate linking on Windows (where thermion_flutter_plugin.cpp needs the Vulkan-related headers), but this doesn't actually solve the problem because there's no way that I've found to get the directory structure correct in the Dart native_assets build directory unless you explicitly address each inidivual file. The current approach is therefore to just keep a permanent copy of the headers in the thermion_filament directory (meaning these will need to be updated manually if the Filament version changes). However, I decided to keep the changes to build.dart because it doesn't have much negative impact and may be helpful in future. disable stereoscopic on Windows and disable handle use after free checks use filament headers for thermion_flutter throw Exception for MSAA on Windows (note that passing msaa:true for setAntiAliasing doesn't actually set MSAA on other platforms, but at least it won't cause the engine to crash) change header include path for Windows/Vulkan change header include path for Windows/Vulkan add filament/vulkan headers for flutter (Windows) ensure destroyTexture platform methods accept an integer rather than a list handle Android/Windows swapchain creation separately
This commit is contained in:
773
thermion_dart/native/src/windows/vulkan/utils.cpp
Normal file
773
thermion_dart/native/src/windows/vulkan/utils.cpp
Normal file
@@ -0,0 +1,773 @@
|
||||
#include "utils.h"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#include "ThermionWin32.h"
|
||||
#include <Windows.h>
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
// Helper function to convert VkResult to string for error reporting
|
||||
const char *VkResultToString(VkResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case VK_SUCCESS:
|
||||
return "VK_SUCCESS";
|
||||
case VK_ERROR_OUT_OF_HOST_MEMORY:
|
||||
return "VK_ERROR_OUT_OF_HOST_MEMORY";
|
||||
case VK_ERROR_OUT_OF_DEVICE_MEMORY:
|
||||
return "VK_ERROR_OUT_OF_DEVICE_MEMORY";
|
||||
case VK_ERROR_INITIALIZATION_FAILED:
|
||||
return "VK_ERROR_INITIALIZATION_FAILED";
|
||||
case VK_ERROR_LAYER_NOT_PRESENT:
|
||||
return "VK_ERROR_LAYER_NOT_PRESENT";
|
||||
case VK_ERROR_EXTENSION_NOT_PRESENT:
|
||||
return "VK_ERROR_EXTENSION_NOT_PRESENT";
|
||||
default:
|
||||
return "UNKNOWN_ERROR";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// bool checkD3D11VulkanInterop(VkPhysicalDevice physicalDevice, ID3D11Device *d3dDevice)
|
||||
// {
|
||||
// std::cout << "\n=== Checking D3D11-Vulkan Interop Support in QEMU ===" << std::endl;
|
||||
|
||||
// // Check Vulkan external memory capabilities
|
||||
// VkPhysicalDeviceExternalImageFormatInfo externFormatInfo = {
|
||||
// .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO,
|
||||
// .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_BIT};
|
||||
|
||||
// VkPhysicalDeviceImageFormatInfo2 formatInfo = {
|
||||
// .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2,
|
||||
// .pNext = &externFormatInfo,
|
||||
// .format = VK_FORMAT_R8G8B8A8_UNORM,
|
||||
// .type = VK_IMAGE_TYPE_2D,
|
||||
// .tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
// .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||
// .flags = 0};
|
||||
|
||||
// VkExternalImageFormatProperties externFormatProps = {
|
||||
// .sType = VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES};
|
||||
|
||||
// VkImageFormatProperties2 formatProps = {
|
||||
// .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2,
|
||||
// .pNext = &externFormatProps};
|
||||
|
||||
// // Check device properties
|
||||
// VkPhysicalDeviceProperties deviceProps;
|
||||
// vkGetPhysicalDeviceProperties(physicalDevice, &deviceProps);
|
||||
|
||||
// std::cout << "Vulkan Device: " << deviceProps.deviceName << std::endl;
|
||||
// std::cout << "Driver Version: " << deviceProps.driverVersion << std::endl;
|
||||
// std::cout << "API Version: " << VK_VERSION_MAJOR(deviceProps.apiVersion) << "." << VK_VERSION_MINOR(deviceProps.apiVersion) << "." << VK_VERSION_PATCH(deviceProps.apiVersion) << std::endl;
|
||||
|
||||
// // Check D3D11 device capabilities
|
||||
// D3D11_FEATURE_DATA_D3D11_OPTIONS3 featureData = {};
|
||||
// HRESULT hr = d3dDevice->CheckFeatureSupport(
|
||||
// D3D11_FEATURE_D3D11_OPTIONS3,
|
||||
// &featureData,
|
||||
// sizeof(featureData));
|
||||
|
||||
// std::cout << "\nChecking D3D11 Device:" << std::endl;
|
||||
|
||||
// // Get D3D11 device information
|
||||
// IDXGIDevice *dxgiDevice = nullptr;
|
||||
// hr = d3dDevice->QueryInterface(__uuidof(IDXGIDevice), (void **)&dxgiDevice);
|
||||
// if (SUCCEEDED(hr))
|
||||
// {
|
||||
// IDXGIAdapter *adapter = nullptr;
|
||||
// hr = dxgiDevice->GetAdapter(&adapter);
|
||||
// if (SUCCEEDED(hr))
|
||||
// {
|
||||
// DXGI_ADAPTER_DESC desc;
|
||||
// adapter->GetDesc(&desc);
|
||||
// std::wcout << L"D3D11 Adapter: " << desc.Description << std::endl;
|
||||
// adapter->Release();
|
||||
// }
|
||||
// dxgiDevice->Release();
|
||||
// }
|
||||
|
||||
// // Check for external memory support
|
||||
// VkResult result = vkGetPhysicalDeviceImageFormatProperties2(
|
||||
// physicalDevice,
|
||||
// &formatInfo,
|
||||
// &formatProps);
|
||||
|
||||
// std::cout << "\nInterop Support Details:" << std::endl;
|
||||
|
||||
// // Check external memory extension
|
||||
// uint32_t extensionCount = 0;
|
||||
// vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extensionCount, nullptr);
|
||||
// std::vector<VkExtensionProperties> extensions(extensionCount);
|
||||
// vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extensionCount, extensions.data());
|
||||
|
||||
// bool hasExternalMemoryExt = false;
|
||||
// bool hasWin32Ext = false;
|
||||
|
||||
// for (const auto &ext : extensions)
|
||||
// {
|
||||
// if (strcmp(ext.extensionName, VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME) == 0)
|
||||
// {
|
||||
// hasExternalMemoryExt = true;
|
||||
// }
|
||||
// if (strcmp(ext.extensionName, VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME) == 0)
|
||||
// {
|
||||
// hasWin32Ext = true;
|
||||
// }
|
||||
// }
|
||||
|
||||
// std::cout << "External Memory Extension: " << (hasExternalMemoryExt ? "Yes" : "No") << std::endl;
|
||||
// std::cout << "Win32 External Memory Extension: " << (hasWin32Ext ? "Yes" : "No") << std::endl;
|
||||
// std::cout << "Format Properties Check: " << (result == VK_SUCCESS ? "Passed" : "Failed") << std::endl;
|
||||
|
||||
// // Check memory properties
|
||||
// VkPhysicalDeviceMemoryProperties memProps;
|
||||
// vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProps);
|
||||
|
||||
// std::cout << "\nMemory Types Available:" << std::endl;
|
||||
// for (uint32_t i = 0; i < memProps.memoryTypeCount; i++)
|
||||
// {
|
||||
// VkMemoryPropertyFlags flags = memProps.memoryTypes[i].propertyFlags;
|
||||
// std::cout << "Type " << i << ": ";
|
||||
// if (flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)
|
||||
// std::cout << "Device Local ";
|
||||
// if (flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)
|
||||
// std::cout << "Host Visible ";
|
||||
// if (flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
|
||||
// std::cout << "Host Coherent ";
|
||||
// if (flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT)
|
||||
// std::cout << "Host Cached ";
|
||||
// std::cout << std::endl;
|
||||
// }
|
||||
|
||||
// // Check if all required features are available
|
||||
// bool supportsInterop =
|
||||
// hasExternalMemoryExt &&
|
||||
// hasWin32Ext &&
|
||||
// result == VK_SUCCESS;
|
||||
|
||||
// std::cout << "\nFinal Result: " << (supportsInterop ? "Interop Supported" : "Interop Not Supported") << std::endl;
|
||||
// std::cout << "================================================" << std::endl;
|
||||
|
||||
// return supportsInterop;
|
||||
// }
|
||||
|
||||
// Helper function to find suitable memory type
|
||||
uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties, VkPhysicalDevice physicalDevice) {
|
||||
VkPhysicalDeviceMemoryProperties memProperties;
|
||||
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
|
||||
|
||||
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
|
||||
if ((typeFilter & (1 << i)) &&
|
||||
(memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error("Failed to find suitable memory type");
|
||||
}
|
||||
|
||||
|
||||
// Modified memory type selection function with more detailed requirements checking
|
||||
uint32_t findOptimalMemoryType(VkPhysicalDevice physicalDevice,
|
||||
uint32_t typeFilter,
|
||||
VkMemoryPropertyFlags requiredProperties,
|
||||
VkMemoryPropertyFlags preferredProperties) {
|
||||
VkPhysicalDeviceMemoryProperties memProperties;
|
||||
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
|
||||
|
||||
// First try to find memory type with all preferred properties
|
||||
if (preferredProperties != 0) {
|
||||
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
|
||||
if ((typeFilter & (1 << i)) &&
|
||||
(memProperties.memoryTypes[i].propertyFlags & (requiredProperties | preferredProperties)) ==
|
||||
(requiredProperties | preferredProperties)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to just required properties
|
||||
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
|
||||
if ((typeFilter & (1 << i)) &&
|
||||
(memProperties.memoryTypes[i].propertyFlags & requiredProperties) == requiredProperties) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
// Consolidated function for creating Vulkan instance
|
||||
VkResult createVulkanInstance(VkInstance *instance)
|
||||
{
|
||||
std::vector<const char *> instanceExtensions = {
|
||||
VK_KHR_SURFACE_EXTENSION_NAME,
|
||||
VK_KHR_WIN32_SURFACE_EXTENSION_NAME,
|
||||
VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME,
|
||||
VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME};
|
||||
|
||||
VkApplicationInfo appInfo = {};
|
||||
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
||||
appInfo.pApplicationName = "Vulkan-D3D11 Interop";
|
||||
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_1;
|
||||
|
||||
VkInstanceCreateInfo createInfo = {};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
||||
createInfo.pApplicationInfo = &appInfo;
|
||||
createInfo.enabledExtensionCount = static_cast<uint32_t>(instanceExtensions.size());
|
||||
createInfo.ppEnabledExtensionNames = instanceExtensions.data();
|
||||
|
||||
return vkCreateInstance(&createInfo, nullptr, instance);
|
||||
}
|
||||
|
||||
// Helper function to find a queue family that supports graphics operations
|
||||
uint32_t findGraphicsQueueFamily(VkPhysicalDevice physicalDevice) {
|
||||
uint32_t queueFamilyCount = 0;
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, nullptr);
|
||||
|
||||
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilies.data());
|
||||
|
||||
// Find a queue family that supports graphics operations
|
||||
for (uint32_t i = 0; i < queueFamilyCount; i++) {
|
||||
if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error("Failed to find graphics queue family");
|
||||
}
|
||||
|
||||
|
||||
CommandResources createCommandResources(VkDevice device, VkPhysicalDevice physicalDevice) {
|
||||
CommandResources resources{};
|
||||
|
||||
// 1. Find a suitable queue family
|
||||
resources.queueFamilyIndex = findGraphicsQueueFamily(physicalDevice);
|
||||
|
||||
// 2. Get the queue handle
|
||||
vkGetDeviceQueue(device,
|
||||
resources.queueFamilyIndex,
|
||||
0, // First queue in family
|
||||
&resources.queue);
|
||||
|
||||
// 3. Create command pool
|
||||
VkCommandPoolCreateInfo poolInfo{};
|
||||
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
||||
poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; // Allow resetting individual command buffers
|
||||
poolInfo.queueFamilyIndex = resources.queueFamilyIndex;
|
||||
|
||||
if (vkCreateCommandPool(device, &poolInfo, nullptr, &resources.commandPool) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to create command pool");
|
||||
}
|
||||
|
||||
return resources;
|
||||
}
|
||||
|
||||
void readVkImageToBitmap(
|
||||
VkPhysicalDevice physicalDevice,
|
||||
VkDevice device,
|
||||
VkCommandPool commandPool,
|
||||
VkQueue queue,
|
||||
VkImage sourceImage,
|
||||
uint32_t width,
|
||||
uint32_t height,
|
||||
const char* outputPath
|
||||
) {
|
||||
// Create staging buffer for reading pixel data
|
||||
VkBufferCreateInfo bufferInfo{};
|
||||
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
bufferInfo.size = width * height * 4; // Assuming RGBA8 format
|
||||
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
|
||||
VkBuffer stagingBuffer;
|
||||
VkResult result = vkCreateBuffer(device, &bufferInfo, nullptr, &stagingBuffer);
|
||||
if (result != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to create staging buffer");
|
||||
}
|
||||
|
||||
// Get memory requirements and properties
|
||||
VkMemoryRequirements memRequirements;
|
||||
vkGetBufferMemoryRequirements(device, stagingBuffer, &memRequirements);
|
||||
|
||||
// Get physical device memory properties
|
||||
VkPhysicalDeviceMemoryProperties memProperties;
|
||||
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
|
||||
|
||||
// Find suitable memory type index
|
||||
uint32_t memoryTypeIndex = -1;
|
||||
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
|
||||
if ((memRequirements.memoryTypeBits & (1 << i)) &&
|
||||
(memProperties.memoryTypes[i].propertyFlags &
|
||||
(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) ==
|
||||
(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) {
|
||||
memoryTypeIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (memoryTypeIndex == -1) {
|
||||
vkDestroyBuffer(device, stagingBuffer, nullptr);
|
||||
throw std::runtime_error("Failed to find suitable memory type");
|
||||
}
|
||||
|
||||
// Allocate memory
|
||||
VkMemoryAllocateInfo allocInfo{};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
allocInfo.allocationSize = memRequirements.size;
|
||||
allocInfo.memoryTypeIndex = memoryTypeIndex;
|
||||
|
||||
VkDeviceMemory stagingMemory;
|
||||
result = vkAllocateMemory(device, &allocInfo, nullptr, &stagingMemory);
|
||||
if (result != VK_SUCCESS) {
|
||||
vkDestroyBuffer(device, stagingBuffer, nullptr);
|
||||
throw std::runtime_error("Failed to allocate staging memory");
|
||||
}
|
||||
|
||||
// Bind memory to buffer
|
||||
result = vkBindBufferMemory(device, stagingBuffer, stagingMemory, 0);
|
||||
if (result != VK_SUCCESS) {
|
||||
vkFreeMemory(device, stagingMemory, nullptr);
|
||||
vkDestroyBuffer(device, stagingBuffer, nullptr);
|
||||
throw std::runtime_error("Failed to bind buffer memory");
|
||||
}
|
||||
|
||||
// Create command buffer
|
||||
VkCommandBufferAllocateInfo cmdBufInfo{};
|
||||
cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
cmdBufInfo.commandPool = commandPool;
|
||||
cmdBufInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
cmdBufInfo.commandBufferCount = 1;
|
||||
|
||||
VkCommandBuffer cmdBuffer;
|
||||
result = vkAllocateCommandBuffers(device, &cmdBufInfo, &cmdBuffer);
|
||||
if (result != VK_SUCCESS) {
|
||||
vkFreeMemory(device, stagingMemory, nullptr);
|
||||
vkDestroyBuffer(device, stagingBuffer, nullptr);
|
||||
throw std::runtime_error("Failed to allocate command buffer");
|
||||
}
|
||||
|
||||
// Begin command buffer
|
||||
VkCommandBufferBeginInfo beginInfo{};
|
||||
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||
|
||||
result = vkBeginCommandBuffer(cmdBuffer, &beginInfo);
|
||||
if (result != VK_SUCCESS) {
|
||||
vkFreeCommandBuffers(device, commandPool, 1, &cmdBuffer);
|
||||
vkFreeMemory(device, stagingMemory, nullptr);
|
||||
vkDestroyBuffer(device, stagingBuffer, nullptr);
|
||||
throw std::runtime_error("Failed to begin command buffer");
|
||||
}
|
||||
|
||||
// Transition image layout for transfer
|
||||
VkImageMemoryBarrier imageBarrier{};
|
||||
imageBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
imageBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; // Adjust based on current layout
|
||||
imageBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
imageBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
imageBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
imageBarrier.image = sourceImage;
|
||||
imageBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
imageBarrier.subresourceRange.baseMipLevel = 0;
|
||||
imageBarrier.subresourceRange.levelCount = 1;
|
||||
imageBarrier.subresourceRange.baseArrayLayer = 0;
|
||||
imageBarrier.subresourceRange.layerCount = 1;
|
||||
imageBarrier.srcAccessMask = 0;
|
||||
imageBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
|
||||
vkCmdPipelineBarrier(
|
||||
cmdBuffer,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
0,
|
||||
0, nullptr,
|
||||
0, nullptr,
|
||||
1, &imageBarrier
|
||||
);
|
||||
|
||||
// Copy image to buffer
|
||||
VkBufferImageCopy region{};
|
||||
region.bufferOffset = 0;
|
||||
region.bufferRowLength = 0;
|
||||
region.bufferImageHeight = 0;
|
||||
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
region.imageSubresource.mipLevel = 0;
|
||||
region.imageSubresource.baseArrayLayer = 0;
|
||||
region.imageSubresource.layerCount = 1;
|
||||
region.imageOffset = { 0, 0, 0 };
|
||||
region.imageExtent = { width, height, 1 };
|
||||
|
||||
vkCmdCopyImageToBuffer(
|
||||
cmdBuffer,
|
||||
sourceImage,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
stagingBuffer,
|
||||
1,
|
||||
®ion
|
||||
);
|
||||
|
||||
// Add memory barrier to ensure the transfer is complete before reading
|
||||
VkMemoryBarrier memBarrier{};
|
||||
memBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
|
||||
memBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
memBarrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
|
||||
|
||||
vkCmdPipelineBarrier(
|
||||
cmdBuffer,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_HOST_BIT,
|
||||
0,
|
||||
1, &memBarrier,
|
||||
0, nullptr,
|
||||
0, nullptr
|
||||
);
|
||||
|
||||
// End command buffer
|
||||
result = vkEndCommandBuffer(cmdBuffer);
|
||||
if (result != VK_SUCCESS) {
|
||||
vkFreeCommandBuffers(device, commandPool, 1, &cmdBuffer);
|
||||
vkFreeMemory(device, stagingMemory, nullptr);
|
||||
vkDestroyBuffer(device, stagingBuffer, nullptr);
|
||||
throw std::runtime_error("Failed to end command buffer");
|
||||
}
|
||||
|
||||
// Submit command buffer
|
||||
VkSubmitInfo submitInfo{};
|
||||
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submitInfo.commandBufferCount = 1;
|
||||
submitInfo.pCommandBuffers = &cmdBuffer;
|
||||
|
||||
// Create fence to ensure command buffer has finished executing
|
||||
VkFenceCreateInfo fenceInfo{};
|
||||
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
||||
|
||||
VkFence fence;
|
||||
result = vkCreateFence(device, &fenceInfo, nullptr, &fence);
|
||||
if (result != VK_SUCCESS) {
|
||||
vkFreeCommandBuffers(device, commandPool, 1, &cmdBuffer);
|
||||
vkFreeMemory(device, stagingMemory, nullptr);
|
||||
vkDestroyBuffer(device, stagingBuffer, nullptr);
|
||||
throw std::runtime_error("Failed to create fence");
|
||||
}
|
||||
|
||||
// Submit with fence
|
||||
result = vkQueueSubmit(queue, 1, &submitInfo, fence);
|
||||
if (result != VK_SUCCESS) {
|
||||
vkDestroyFence(device, fence, nullptr);
|
||||
vkFreeCommandBuffers(device, commandPool, 1, &cmdBuffer);
|
||||
vkFreeMemory(device, stagingMemory, nullptr);
|
||||
vkDestroyBuffer(device, stagingBuffer, nullptr);
|
||||
throw std::runtime_error("Failed to submit queue");
|
||||
}
|
||||
|
||||
// Wait for the command buffer to complete execution
|
||||
result = vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
|
||||
if (result != VK_SUCCESS) {
|
||||
vkDestroyFence(device, fence, nullptr);
|
||||
vkFreeCommandBuffers(device, commandPool, 1, &cmdBuffer);
|
||||
vkFreeMemory(device, stagingMemory, nullptr);
|
||||
vkDestroyBuffer(device, stagingBuffer, nullptr);
|
||||
throw std::runtime_error("Failed to wait for fence");
|
||||
}
|
||||
|
||||
// Now safe to map memory and read data
|
||||
void* data;
|
||||
result = vkMapMemory(device, stagingMemory, 0, bufferInfo.size, 0, &data);
|
||||
if (result != VK_SUCCESS) {
|
||||
vkDestroyFence(device, fence, nullptr);
|
||||
vkFreeCommandBuffers(device, commandPool, 1, &cmdBuffer);
|
||||
vkFreeMemory(device, stagingMemory, nullptr);
|
||||
vkDestroyBuffer(device, stagingBuffer, nullptr);
|
||||
throw std::runtime_error("Failed to map memory");
|
||||
}
|
||||
|
||||
// Create bitmap header
|
||||
BMPHeader header{};
|
||||
header.signature = 0x4D42; // "BM"
|
||||
header.fileSize = sizeof(BMPHeader) + width * height * 3; // 3 bytes per pixel (BGR)
|
||||
header.dataOffset = sizeof(BMPHeader);
|
||||
header.headerSize = 40;
|
||||
header.width = width;
|
||||
header.height = height;
|
||||
header.planes = 1;
|
||||
header.bitsPerPixel = 24;
|
||||
header.compression = 0;
|
||||
header.imageSize = width * height * 3;
|
||||
|
||||
|
||||
//// Write to file
|
||||
std::ofstream file(outputPath, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
vkUnmapMemory(device, stagingMemory);
|
||||
vkDestroyFence(device, fence, nullptr);
|
||||
vkFreeCommandBuffers(device, commandPool, 1, &cmdBuffer);
|
||||
vkFreeMemory(device, stagingMemory, nullptr);
|
||||
vkDestroyBuffer(device, stagingBuffer, nullptr);
|
||||
throw std::runtime_error("Failed to open output file");
|
||||
}
|
||||
|
||||
file.write(reinterpret_cast<char*>(&header), sizeof(header));
|
||||
|
||||
// Convert RGBA to BGR and write pixel data
|
||||
uint8_t* pixels = reinterpret_cast<uint8_t*>(data);
|
||||
std::vector<uint8_t> bgrData(width * height * 3);
|
||||
|
||||
for (uint32_t y = 0; y < height; y++) {
|
||||
for (uint32_t x = 0; x < width; x++) {
|
||||
uint32_t srcIdx = (y * width + x) * 4; // RGBA has 4 components
|
||||
uint32_t dstIdx = ((height - 1 - y) * width + x) * 3; // Flip vertically
|
||||
|
||||
// RGBA to BGR conversion
|
||||
bgrData[dstIdx + 0] = pixels[srcIdx + 0];
|
||||
bgrData[dstIdx + 1] = pixels[srcIdx + 1];
|
||||
bgrData[dstIdx + 2] = pixels[srcIdx + 2];
|
||||
}
|
||||
}
|
||||
|
||||
file.write(reinterpret_cast<char*>(bgrData.data()), bgrData.size());
|
||||
file.close();
|
||||
|
||||
// Cleanup
|
||||
vkUnmapMemory(device, stagingMemory);
|
||||
vkDestroyFence(device, fence, nullptr);
|
||||
vkFreeCommandBuffers(device, commandPool, 1, &cmdBuffer);
|
||||
vkFreeMemory(device, stagingMemory, nullptr);
|
||||
vkDestroyBuffer(device, stagingBuffer, nullptr);
|
||||
}
|
||||
|
||||
// Consolidated function for creating logical device
|
||||
VkResult createLogicalDevice(VkInstance instance, VkPhysicalDevice *physicalDevice, VkDevice *device)
|
||||
{
|
||||
uint32_t deviceCount = 0;
|
||||
bluevk::vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
|
||||
std::vector<VkPhysicalDevice> physicalDevices(deviceCount);
|
||||
bluevk::vkEnumeratePhysicalDevices(instance, &deviceCount, physicalDevices.data());
|
||||
|
||||
if (deviceCount == 0)
|
||||
{
|
||||
return VK_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
|
||||
*physicalDevice = physicalDevices[0];
|
||||
|
||||
std::vector<const char *> deviceExtensions = {
|
||||
VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME,
|
||||
VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME};
|
||||
|
||||
float queuePriority = 1.0f;
|
||||
VkDeviceQueueCreateInfo queueCreateInfo = {};
|
||||
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
||||
queueCreateInfo.queueFamilyIndex = 0;
|
||||
queueCreateInfo.queueCount = 1;
|
||||
queueCreateInfo.pQueuePriorities = &queuePriority;
|
||||
|
||||
VkDeviceCreateInfo deviceCreateInfo = {};
|
||||
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
||||
deviceCreateInfo.queueCreateInfoCount = 1;
|
||||
deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo;
|
||||
deviceCreateInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
|
||||
deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions.data();
|
||||
|
||||
return vkCreateDevice(*physicalDevice, &deviceCreateInfo, nullptr, device);
|
||||
}
|
||||
|
||||
// Example usage with device creation
|
||||
void createDeviceWithGraphicsQueue(VkPhysicalDevice physicalDevice, uint32_t& queueFamilyIndex, VkDevice* device) {
|
||||
// Find queue family index
|
||||
queueFamilyIndex = findGraphicsQueueFamily(physicalDevice);
|
||||
|
||||
// Specify queue creation
|
||||
float queuePriority = 1.0f;
|
||||
VkDeviceQueueCreateInfo queueCreateInfo{};
|
||||
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
||||
queueCreateInfo.queueFamilyIndex = queueFamilyIndex;
|
||||
queueCreateInfo.queueCount = 1;
|
||||
queueCreateInfo.pQueuePriorities = &queuePriority;
|
||||
|
||||
// Specify device features
|
||||
VkPhysicalDeviceFeatures deviceFeatures{};
|
||||
|
||||
// Create logical device
|
||||
VkDeviceCreateInfo createInfo{};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
||||
createInfo.pQueueCreateInfos = &queueCreateInfo;
|
||||
createInfo.queueCreateInfoCount = 1;
|
||||
createInfo.pEnabledFeatures = &deviceFeatures;
|
||||
|
||||
if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device) != VK_SUCCESS) {
|
||||
throw std::runtime_error("Failed to create logical device");
|
||||
}
|
||||
}
|
||||
|
||||
void fillImageWithColor(
|
||||
VkDevice device,
|
||||
VkCommandPool commandPool,
|
||||
VkQueue queue,
|
||||
VkImage image,
|
||||
VkFormat format,
|
||||
VkImageLayout currentLayout,
|
||||
VkExtent3D extent,
|
||||
float r, float g, float b, float a
|
||||
) {
|
||||
// Create command buffer
|
||||
VkCommandBufferAllocateInfo allocInfo = {};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
allocInfo.commandPool = commandPool;
|
||||
allocInfo.commandBufferCount = 1;
|
||||
|
||||
VkCommandBuffer commandBuffer;
|
||||
vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer);
|
||||
|
||||
// Begin command buffer
|
||||
VkCommandBufferBeginInfo beginInfo = {};
|
||||
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||
vkBeginCommandBuffer(commandBuffer, &beginInfo);
|
||||
|
||||
// Transition image layout to TRANSFER_DST_OPTIMAL if needed
|
||||
if (currentLayout != VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
|
||||
VkImageMemoryBarrier barrier = {};
|
||||
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
barrier.oldLayout = currentLayout;
|
||||
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = image;
|
||||
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
barrier.subresourceRange.baseMipLevel = 0;
|
||||
barrier.subresourceRange.levelCount = 1;
|
||||
barrier.subresourceRange.baseArrayLayer = 0;
|
||||
barrier.subresourceRange.layerCount = 1;
|
||||
barrier.srcAccessMask = 0;
|
||||
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
|
||||
vkCmdPipelineBarrier(
|
||||
commandBuffer,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
0,
|
||||
0, nullptr,
|
||||
0, nullptr,
|
||||
1, &barrier
|
||||
);
|
||||
}
|
||||
|
||||
// Clear the image
|
||||
VkClearColorValue clearColor = {{r, g, b, a}};
|
||||
VkImageSubresourceRange range = {};
|
||||
range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
range.baseMipLevel = 0;
|
||||
range.levelCount = 1;
|
||||
range.baseArrayLayer = 0;
|
||||
range.layerCount = 1;
|
||||
|
||||
vkCmdClearColorImage(
|
||||
commandBuffer,
|
||||
image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
&clearColor,
|
||||
1,
|
||||
&range
|
||||
);
|
||||
|
||||
// Transition back to original layout if needed
|
||||
if (currentLayout != VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
|
||||
VkImageMemoryBarrier barrier = {};
|
||||
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
barrier.newLayout = currentLayout;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = image;
|
||||
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
barrier.subresourceRange.baseMipLevel = 0;
|
||||
barrier.subresourceRange.levelCount = 1;
|
||||
barrier.subresourceRange.baseArrayLayer = 0;
|
||||
barrier.subresourceRange.layerCount = 1;
|
||||
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
barrier.dstAccessMask = 0;
|
||||
|
||||
vkCmdPipelineBarrier(
|
||||
commandBuffer,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
|
||||
0,
|
||||
0, nullptr,
|
||||
0, nullptr,
|
||||
1, &barrier
|
||||
);
|
||||
}
|
||||
|
||||
// End and submit command buffer
|
||||
vkEndCommandBuffer(commandBuffer);
|
||||
|
||||
VkSubmitInfo submitInfo = {};
|
||||
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submitInfo.commandBufferCount = 1;
|
||||
submitInfo.pCommandBuffers = &commandBuffer;
|
||||
|
||||
vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE);
|
||||
vkQueueWaitIdle(queue);
|
||||
|
||||
// Cleanup
|
||||
vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
|
||||
}
|
||||
|
||||
bool SavePixelsAsBMP(uint8_t* pixels, uint32_t width, uint32_t height, int rowPitch, const char* filename) {
|
||||
// Create and fill header
|
||||
BMPHeader header = {};
|
||||
header.signature = 0x4D42; // 'BM'
|
||||
header.fileSize = sizeof(BMPHeader) + width * height * 4;
|
||||
header.dataOffset = sizeof(BMPHeader);
|
||||
header.headerSize = 40;
|
||||
header.width = width;
|
||||
header.height = height;
|
||||
header.planes = 1;
|
||||
header.bitsPerPixel = 32;
|
||||
header.compression = 0;
|
||||
header.imageSize = width * height * 4;
|
||||
header.xPixelsPerMeter = 2835; // 72 DPI
|
||||
header.yPixelsPerMeter = 2835; // 72 DPI
|
||||
|
||||
// Write to file
|
||||
FILE* file = nullptr;
|
||||
fopen_s(&file, filename, "wb");
|
||||
|
||||
if (!file) {
|
||||
std::cout << "Couldn't open file for pixels" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
fwrite(&header, sizeof(header), 1, file);
|
||||
|
||||
// Write pixel data (need to flip rows as BMP is bottom-up)
|
||||
for (int y = height - 1; y >= 0; y--) {
|
||||
uint8_t* rowData = pixels + y * rowPitch;
|
||||
fwrite(rowData, width * 4, 1, file);
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
return true;
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user