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:
Nick Fisher
2024-11-05 10:17:27 +08:00
parent b59cadc061
commit 436873a455
343 changed files with 135936 additions and 2449 deletions

View File

@@ -127,8 +127,7 @@ namespace thermion
FilamentViewer::FilamentViewer(const void *sharedContext, const ResourceLoaderWrapperImpl *const resourceLoader, void *const platform, const char *uberArchivePath)
: _resourceLoaderWrapper(resourceLoader)
{
Log("Creating engine wiht sharedContext %d", sharedContext);
ASSERT_POSTCONDITION(_resourceLoaderWrapper != nullptr, "Resource loader must be non-null");
#if TARGET_OS_IPHONE
@@ -142,11 +141,12 @@ namespace thermion
#elif defined(_WIN32)
Engine::Config config;
config.stereoscopicEyeCount = 1;
_engine = Engine::create(Engine::Backend::OPENGL, (backend::Platform *)platform, (void *)sharedContext, &config);
config.disableHandleUseAfterFreeCheck = true;
_engine = Engine::create(Engine::Backend::VULKAN, (backend::Platform *)platform, (void *)sharedContext, &config);
#else
_engine = Engine::create(Engine::Backend::OPENGL, (backend::Platform *)platform, (void *)sharedContext, nullptr);
#endif
Log("Engine created");
#endif
_engine->setAutomaticInstancingEnabled(true);
_renderer = _engine->createRenderer();
@@ -685,6 +685,7 @@ namespace thermion
SwapChain *swapChain;
swapChain = _engine->createSwapChain(width, height, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE | filament::SwapChain::CONFIG_HAS_STENCIL_BUFFER);
_swapChains.push_back(swapChain);
_engine->flushAndWait();
return swapChain;
}
@@ -767,7 +768,7 @@ namespace thermion
view->setAmbientOcclusionOptions({.enabled = false});
view->setDynamicResolutionOptions({.enabled = false});
#if defined(_WIN32)
view->setStereoscopicOptions({.enabled = true});
view->setStereoscopicOptions({.enabled = false});
#endif
// bloom can be a bit glitchy (some Intel iGPUs won't render when postprocessing is enabled and bloom is disabled,

View File

@@ -0,0 +1,67 @@
cmake_minimum_required(VERSION 3.14)
set(PROJECT_NAME "thermion_vulkan")
project(${PROJECT_NAME} LANGUAGES C CXX)
cmake_policy(VERSION 3.14...3.25)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
set(BUILD_SHARED_LIBS TRUE)
set(CMAKE_ENABLE_EXPORTS TRUE)
add_definitions(-DWIN32_LEAN_AND_MEAN)
add_compile_options(/FS)
# Add Windows-specific compile definitions
add_definitions(-DWIN32)
add_definitions(-D_WINDOWS)
# Ensure UNICODE is defined
add_definitions(-DUNICODE)
add_definitions(-D_UNICODE)
add_library(${PROJECT_NAME} SHARED
"d3d_context.cpp"
"vulkan_context.cpp"
"utils.cpp"
"d3d_texture.cpp"
"vulkan_texture.cpp"
)
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20)
include_directories(${PROJECT_NAME} INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/../../include"
"${CMAKE_CURRENT_SOURCE_DIR}/"
)
target_link_directories(${PROJECT_NAME} PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/../../../.dart_tool/thermion_dart/lib/v1.51.2/windows/debug
)
target_link_libraries(${PROJECT_NAME} PRIVATE
Shlwapi
dxgi
d3d11
bluevk
vulkan-1
)
#add_executable(${PROJECT_NAME}_test
# "main.cpp"
#)
#target_compile_features(${PROJECT_NAME}_test PUBLIC cxx_std_20)
#target_include_directories(${PROJECT_NAME}_test PRIVATE
# E:\\thermion\\thermion_dart\\native\\include\\vulkan
## E:\\thermion\\thermion_dart\\native\\include
# E:\\thermion\\thermion_dart\\native\\include\\filament
#)
#target_link_directories(${PROJECT_NAME}_test PRIVATE
# E:\\thermion\\thermion_dart\\.dart_tool\\thermion_dart\\lib\\v1.51.2\\windows\\debug
# E:\\VulkanSDK\\1.3.296.0\\Lib
#)
#target_link_libraries(${PROJECT_NAME}_test PRIVATE
# ${PROJECT_NAME}
#)
#add_dependencies(${PROJECT_NAME}_test ${PROJECT_NAME})

View File

@@ -0,0 +1,134 @@
#include "d3d_context.h"
#include <iostream>
namespace thermion::windows::d3d
{
D3DContext::D3DContext()
{
IDXGIAdapter *adapter_ = nullptr;
auto feature_levels = {
D3D_FEATURE_LEVEL_12_0,
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
};
IDXGIFactory1 *dxgi = nullptr;
HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void **)&dxgi);
if (FAILED(hr))
{
std::cout << "Failed to create DXGI 1.1 factory" << std::endl;
return;
}
dxgi->EnumAdapters(0, &adapter_);
dxgi->Release();
if (!adapter_)
{
std::cout << "Failed to locate default D3D adapter" << std::endl;
return;
}
Microsoft::WRL::ComPtr<IDXGIFactory2> factory2;
if (SUCCEEDED(dxgi->QueryInterface(IID_PPV_ARGS(&factory2))))
{
std::cout << "DXGI 1.2 or higher supported" << std::endl;
}
DXGI_ADAPTER_DESC adapter_desc_;
adapter_->GetDesc(&adapter_desc_);
std::wcout << L"D3D adapter description: " << adapter_desc_.Description
<< std::endl;
hr = ::D3D11CreateDevice(
adapter_, D3D_DRIVER_TYPE_UNKNOWN, 0, D3D11_CREATE_DEVICE_BGRA_SUPPORT, feature_levels.begin(),
static_cast<UINT>(feature_levels.size()), D3D11_SDK_VERSION,
&_D3D11Device, 0, &_D3D11DeviceContext);
if (FAILED(hr))
{
std::cout << "Failed to create D3D device" << std::endl;
return;
}
Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device = nullptr;
auto dxgi_device_success = _D3D11Device->QueryInterface(
__uuidof(IDXGIDevice), (void **)&dxgi_device);
if (SUCCEEDED(dxgi_device_success) && dxgi_device != nullptr)
{
dxgi_device->SetGPUThreadPriority(5); // Must be in interval [-7, 7].
}
auto level = _D3D11Device->GetFeatureLevel();
std::cout << "Direct3D Feature Level: "
<< (((unsigned)level) >> 12) << "_"
<< ((((unsigned)level) >> 8) & 0xf) << std::endl;
}
D3DContext::~D3DContext() {
if (_D3D11DeviceContext) {
_D3D11DeviceContext->Release();
}
if (_D3D11Device) {
_D3D11Device->Release();
}
}
std::unique_ptr<D3DTexture> D3DContext::CreateTexture(uint32_t width, uint32_t height)
{
// Create texture
auto d3d11_texture2D_desc = D3D11_TEXTURE2D_DESC{0};
d3d11_texture2D_desc.Width = width;
d3d11_texture2D_desc.Height = height;
d3d11_texture2D_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
d3d11_texture2D_desc.MipLevels = 1;
d3d11_texture2D_desc.ArraySize = 1;
d3d11_texture2D_desc.SampleDesc.Count = 1;
d3d11_texture2D_desc.SampleDesc.Quality = 0;
d3d11_texture2D_desc.Usage = D3D11_USAGE_DEFAULT;
d3d11_texture2D_desc.BindFlags =
D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
d3d11_texture2D_desc.CPUAccessFlags = 0;
d3d11_texture2D_desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED | D3D11_RESOURCE_MISC_SHARED_NTHANDLE;
Microsoft::WRL::ComPtr<ID3D11Texture2D> _d3dTexture2D;
auto hr = _D3D11Device->CreateTexture2D(&d3d11_texture2D_desc, nullptr, &_d3dTexture2D);
if FAILED (hr)
{
std::cout << "Failed to create D3D texture (" << hr << ")" << std::endl;
return nullptr;
}
auto resource = Microsoft::WRL::ComPtr<IDXGIResource1>{};
hr = _d3dTexture2D.As(&resource);
if FAILED (hr)
{
std::cout << "Failed to create D3D texture" << std::endl;
return nullptr;
}
HANDLE _d3dTexture2DHandle = nullptr;
// hr = resource->GetSharedHandle(&_d3dTexture2DHandle);
hr = resource->CreateSharedHandle(nullptr, GENERIC_ALL, nullptr, &_d3dTexture2DHandle);
if FAILED (hr)
{
std::cout << "Failed to get shared handle to external D3D texture" << std::endl;
return nullptr;
}
_d3dTexture2D->AddRef();
std::cout << "Created external D3D texture " << width << "x" << height << std::endl;
return std::make_unique<D3DTexture>(_d3dTexture2D, _d3dTexture2DHandle, width, height);
}
void D3DContext::Flush()
{
_D3D11DeviceContext->Flush();
}
}

View File

@@ -0,0 +1,128 @@
#include "d3d_texture.h"
#include "utils.h"
#include <functional>
#include <iostream>
#include <memory>
#include <thread>
namespace thermion::windows::d3d
{
bool IsNTHandleSupported(ID3D11Device *device)
{
Microsoft::WRL::ComPtr<ID3D11Device5> device5;
return SUCCEEDED(device->QueryInterface(IID_PPV_ARGS(&device5)));
}
D3DTexture::D3DTexture(
Microsoft::WRL::ComPtr<ID3D11Texture2D> d3dTexture2D,
HANDLE d3dTexture2DHandle, uint32_t width, uint32_t height) : _d3dTexture2D(d3dTexture2D), _d3dTexture2DHandle(d3dTexture2DHandle), _width(width), _height(height)
{
}
D3DTexture::~D3DTexture() {
if (_d3dTexture2DHandle) {
CloseHandle(_d3dTexture2DHandle);
_d3dTexture2DHandle = nullptr;
}
if (_d3dTexture2D) {
_d3dTexture2D->Release();
_d3dTexture2D = nullptr;
}
}
HANDLE D3DTexture::GetTextureHandle() {
return _d3dTexture2DHandle;
}
void D3DTexture::SaveToBMP(const char *filename)
{
// // Create render target view of the texture
// ID3D11RenderTargetView* rtv = nullptr;
// D3D11_RENDER_TARGET_VIEW_DESC rtvDesc = {};
// rtvDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
// rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
// rtvDesc.Texture2D.MipSlice = 0;
// HRESULT hr = _D3D11Device->CreateRenderTargetView(_d3dTexture2D.Get(), &rtvDesc, &rtv);
// if (FAILED(hr)) {
// std::cout << "Failed to create render target view" << std::endl;
// return;
// }
// // Create staging texture for CPU read access
// D3D11_TEXTURE2D_DESC stagingDesc = {};
// _d3dTexture2D->GetDesc(&stagingDesc);
// stagingDesc.Usage = D3D11_USAGE_STAGING;
// stagingDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
// stagingDesc.BindFlags = 0;
// stagingDesc.MiscFlags = 0;
// ID3D11Texture2D* stagingTexture = nullptr;
// hr = _D3D11Device->CreateTexture2D(&stagingDesc, nullptr, &stagingTexture);
// if (FAILED(hr)) {
// rtv->Release();
// std::cout << "Failed to create staging texture" << std::endl;
// return;
// }
// // Copy to staging texture
// _D3D11DeviceContext->CopyResource(stagingTexture, _d3dTexture2D.Get());
// // Save to BMP
// bool success = SaveTextureAsBMP(stagingTexture, filename);
// // Cleanup
// stagingTexture->Release();
// rtv->Release();
// if (success) {
// std::cout << "Successfully saved texture to " << filename << std::endl;
// } else {
// std::cout << "Texture save failed to " << filename << std::endl;
// }
}
bool D3DTexture::SaveTextureAsBMP(ID3D11Texture2D *texture, const char *filename)
{
return false;
// D3D11_TEXTURE2D_DESC desc;
// texture->GetDesc(&desc);
// // Map texture to get pixel data
// D3D11_MAPPED_SUBRESOURCE mappedResource;
// HRESULT hr = _D3D11DeviceContext->Map(texture, 0, D3D11_MAP_READ, 0, &mappedResource);
// if (FAILED(hr)) {
// std::cout << "Failed to map texture" << std::endl;
// return false;
// }
// auto success = SavePixelsAsBMP(reinterpret_cast<uint8_t*>(mappedResource.pData), desc.Width, desc.Height, mappedResource.RowPitch, filename);
// if(!success) {
// std::cout << "BMP write failed" << std::endl;
// }
// _D3D11DeviceContext->Unmap(texture, 0);
// return success;
}
} // namespace thermion_flutter
// Create render target view of the texture
// ID3D11RenderTargetView* rtv = nullptr;
// D3D11_RENDER_TARGET_VIEW_DESC rtvDesc = {};
// rtvDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
// rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
// rtvDesc.Texture2D.MipSlice = 0;
// hr = _D3D11Device->CreateRenderTargetView(_d3dTexture2D.Get(), &rtvDesc, &rtv);
// if (FAILED(hr)) {
// std::cout << "Failed to create render target view" << std::endl;
// return;
// }
// // Clear the texture to blue
// float blueColor[4] = { 0.0f, 0.0f, 1.0f, 1.0f }; // RGBA
// _D3D11DeviceContext->ClearRenderTargetView(rtv, blueColor);

View File

@@ -0,0 +1,95 @@
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
#include <string>
#include <fstream>
#include <functional>
#include <iostream>
#include <memory>
#include <thread>
#include "ThermionWin32.h"
#include "vulkan_context.h"
#include "d3d_texture.h"
#include "filament/backend/platforms/VulkanPlatform.h"
#include "filament/Engine.h"
#include "filament/Renderer.h"
#include "filament/View.h"
#include "filament/Viewport.h"
#include "filament/Scene.h"
#include "filament/SwapChain.h"
#include "filament/Texture.h"
#include "utils/EntityManager.h"
int main()
{
auto ctx = new thermion::windows::vulkan::ThermionVulkanContext();
uint32_t width = 100;
uint32_t height = 100;
auto handle = ctx->CreateRenderingSurface(width, height, 0, 0);
std::cout << "CREATED" << std::endl;
ctx->DestroyRenderingSurface(handle);
std::cout << "FINISHED" << std::endl;
// auto *engine = filament::Engine::create(filament::Engine::Backend::VULKAN, ctx->GetPlatform(), nullptr, nullptr);
// auto *swapChain = engine->createSwapChain(width,height, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE | filament::SwapChain::CONFIG_HAS_STENCIL_BUFFER);
// engine->flushAndWait();
// if(engine->isValid(reinterpret_cast<filament::SwapChain*>(swapChain))) {
// std::cout << "VALID SWAPCHIAN" << std::endl;
// } else {
// std::cout << "INVALID SWAPCHIAN" << std::endl;
// }
// auto renderer = engine->createRenderer();
// filament::Renderer::ClearOptions clearOptions;
// clearOptions.clear = true;
// clearOptions.clearColor = { 1.0f, 0.0f, 0.5f, 1.0f };
// renderer->setClearOptions(clearOptions);
// auto scene = engine->createScene();
// auto *view = engine->createView();
// view->setViewport(filament::Viewport {0,0, width,height});
// view->setBlendMode(filament::View::BlendMode::TRANSLUCENT);
// view->setScene(scene);
// auto camera = engine->createCamera(utils::EntityManager::get().create());
// view->setCamera(camera);
// engine->flushAndWait();
// size_t pixelBufferSize = width * height * 4;
// auto out = new uint8_t[pixelBufferSize];
// auto pbd = filament::Texture::PixelBufferDescriptor(
// out, pixelBufferSize,
// filament::Texture::Format::RGBA,
// filament::Texture::Type::UBYTE, nullptr, nullptr, nullptr);
// renderer->beginFrame(swapChain);
// renderer->render(view);
// renderer->readPixels(0, 0, width, height, std::move(pbd));
// renderer->endFrame();
// engine->flushAndWait();
// std::cout << "FLUSHED" << std::endl;
// if(!SavePixelsAsBMP(out, width, height, width, "savepixels.bmp")) {
// std::cout << "FAILED TO SAVE PIXELS" << std::endl;
// }
// // ctx->GetTexture()->Flush();
// ctx->BlitFromSwapchain(width,height);
// ctx->GetTexture()->SaveToBMP("d3d_texture.bmp");
// std::vector<uint8_t> outPixels(width * height * 4);
// ctx->readPixelsFromImage(width, height, outPixels);
// std::cout << "READBACK FROM VULKAN COMPLETE " << std::endl;
// thermion::windows::d3d::D3DTexture::SavePixelsAsBMP(outPixels.data(), width, height, width, "vulkan_readback.bmp");
}

View 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,
&region
);
// 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;
}

View File

@@ -0,0 +1,511 @@
#define THERMION_WIN32_KHR_BUILD
#include "vulkan_context.h"
#include "ThermionWin32.h"
#include <functional>
#include <vector>
#include <chrono>
#include <string>
#include <fstream>
#include <iostream>
#include <memory>
#include <thread>
#include "filament/backend/platforms/VulkanPlatform.h"
#include "filament/Engine.h"
#include "filament/Renderer.h"
#include "filament/View.h"
#include "filament/Viewport.h"
#include "filament/Scene.h"
#include "filament/SwapChain.h"
#include "filament/Texture.h"
#include "Log.hpp"
namespace thermion::windows::vulkan {
using namespace bluevk;
ThermionVulkanContext::ThermionVulkanContext() {
bluevk::initialize();
// Create Vulkan instance
VkResult result = createVulkanInstance(&instance);
if (result != VK_SUCCESS)
{
std::cout << "[ERROR] Failed to create Vulkan instance! Error: " << VkResultToString(result) << std::endl;
return;
}
bluevk::bindInstance(instance);
result = createLogicalDevice(instance, &physicalDevice, &device);
if (result != VK_SUCCESS)
{
std::cout << "[ERROR] Failed to create logical device! Error: " << VkResultToString(result) << std::endl;
vkDestroyInstance(instance, nullptr);
return;
}
uint32_t queueFamilyIndex;
createDeviceWithGraphicsQueue(physicalDevice,queueFamilyIndex, &device);
CommandResources cmdResources = createCommandResources(device, physicalDevice);
commandPool = cmdResources.commandPool;
queue = cmdResources.queue;
VkPhysicalDeviceExternalImageFormatInfo externFormatInfo = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO,
.pNext = nullptr,
.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, // Changed to LINEAR for VM compatibility
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, // Simplified usage flags
.flags = 0
};
VkExternalImageFormatProperties externFormatProps = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES
};
VkImageFormatProperties2 formatProps = {
.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2,
.pNext = &externFormatProps};
// Query supported features
result = vkGetPhysicalDeviceImageFormatProperties2(
physicalDevice,
&formatInfo,
&formatProps);
if (result != VK_SUCCESS)
{
std::cout << "VM environment may not support required external memory features" << std::endl;
return;
}
_platform = new TVulkanPlatform();
_d3dContext = std::make_unique<thermion::windows::d3d::D3DContext>();
}
HANDLE ThermionVulkanContext::CreateRenderingSurface(uint32_t width, uint32_t height, uint32_t left, uint32_t top) {
Log("Creating Vulkan texture %dx%d", width, height);
// creates the D3D texture
auto d3dTexture = _d3dContext->CreateTexture(width, height);
auto d3dTextureHandle = d3dTexture->GetTextureHandle();
auto vkTexture = VulkanTexture::create(device, physicalDevice, width, height, d3dTextureHandle);
// fillImageWithColor(device, commandPool, queue, image, VK_FORMAT_B8G8R8A8_UNORM, VK_IMAGE_LAYOUT_UNDEFINED, // Current image layout
// { width, height, 1 }, // Image extent
// 0.0f, 1.0f, 0.0f, 1.0f); // Red color (RGBA))
_d3dTextures.push_back(std::move(d3dTexture));
_vulkanTextures.push_back(std::move(vkTexture));
return d3dTextureHandle;
}
void ThermionVulkanContext::ResizeRenderingSurface(uint32_t width, uint32_t height, uint32_t left, uint32_t top) {
}
void ThermionVulkanContext::DestroyRenderingSurface(HANDLE handle) {
_vulkanTextures.erase(std::remove_if(_vulkanTextures.begin(), _vulkanTextures.end(), [=](auto&& vkTexture) {
return vkTexture->GetD3DTextureHandle() == handle;
}));
_d3dTextures.erase(std::remove_if(_d3dTextures.begin(), _d3dTextures.end(), [=](auto&& d3dTexture) {
return d3dTexture->GetTextureHandle() == handle;
}));
}
void ThermionVulkanContext::Flush() {
// ?? what to do here
}
// Function to perform the blit operation
void ThermionVulkanContext::BlitFromSwapchain() {
std::lock_guard lock(_platform->mutex);
if(!_platform->_current || _d3dTextures.size() == 0) {
return;
}
auto&& vkTexture = _vulkanTextures.back();
auto image = vkTexture->GetImage();
auto&& texture = _d3dTextures.back();
auto height = texture->GetHeight();
auto width = texture->GetWidth();
auto bundle = _platform->getSwapChainBundle(_platform->_current);
VkImage swapchainImage = bundle.colors[_platform->_currentColorIndex];
// Command buffer allocation
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 cmd;
VkResult result = bluevk::vkAllocateCommandBuffers(device, &cmdBufInfo, &cmd);
if (result != VK_SUCCESS) {
std::cout << "Failed to allocate command buffer: " << result << std::endl;
return;
}
// 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 = bluevk::vkBeginCommandBuffer(cmd, &beginInfo);
if (result != VK_SUCCESS) {
std::cout << "Failed to begin command buffer: " << result << std::endl;
return;
}
// std::cout << "Starting blit operation..." << std::endl;
// Pre-transition barriers
VkImageMemoryBarrier srcBarrier{};
srcBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
srcBarrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
srcBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
srcBarrier.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
srcBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
srcBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
srcBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
srcBarrier.image = swapchainImage;
srcBarrier.subresourceRange = {
VK_IMAGE_ASPECT_COLOR_BIT,
0, 1, 0, 1
};
VkImageMemoryBarrier dstBarrier{};
dstBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
dstBarrier.srcAccessMask = 0;
dstBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
dstBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
dstBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
dstBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
dstBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
dstBarrier.image = image;
dstBarrier.subresourceRange = {
VK_IMAGE_ASPECT_COLOR_BIT,
0, 1, 0, 1
};
// std::cout << "Transitioning images to transfer layouts..." << std::endl;
// Pre-blit barriers
VkImageMemoryBarrier preBlitBarriers[] = {srcBarrier, dstBarrier};
vkCmdPipelineBarrier(
cmd,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, // Changed from TRANSFER_BIT for better sync
VK_PIPELINE_STAGE_TRANSFER_BIT,
0,
0, nullptr,
0, nullptr,
2, preBlitBarriers
);
// Define blit region with bounds checking
VkImageBlit blit{};
blit.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
blit.srcOffsets[0] = {0, 0, 0};
blit.srcOffsets[1] = {static_cast<int32_t>(width), static_cast<int32_t>(height), 1};
blit.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
blit.dstOffsets[0] = {0, 0, 0};
blit.dstOffsets[1] = {static_cast<int32_t>(width), static_cast<int32_t>(height), 1};
// std::cout << "Executing blit command..." << std::endl;
// std::cout << "Source dimensions: " << width << "x" << height << std::endl;
// std::cout << "Destination dimensions: " << width << "x" << height << std::endl;
// Perform blit with validation
bluevk::vkCmdBlitImage(
cmd,
swapchainImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1, &blit,
VK_FILTER_NEAREST // Changed to NEAREST for debugging
);
// Post-transition barriers
srcBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
srcBarrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
srcBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
srcBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
dstBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
dstBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
dstBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
dstBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
// std::cout << "Transitioning images back to original layouts..." << std::endl;
// Post-blit barriers
VkImageMemoryBarrier postBlitBarriers[] = {srcBarrier, dstBarrier};
bluevk::vkCmdPipelineBarrier(
cmd,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, // Changed for better sync
0,
0, nullptr,
0, nullptr,
2, postBlitBarriers
);
// End command buffer
result = bluevk::vkEndCommandBuffer(cmd);
if (result != VK_SUCCESS) {
std::cout << "Failed to end command buffer: " << result << std::endl;
return;
}
// Create fence for synchronization
// VkFenceCreateInfo fenceInfo{};
// fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
// VkFence fence;
// result = bluevk::vkCreateFence(device, &fenceInfo, nullptr, &fence);
// if (result != VK_SUCCESS) {
// std::cout << "Failed to create fence: " << result << std::endl;
// return;
// }
// // Submit with fence
VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &cmd;
result = bluevk::vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); //fence);
if (result != VK_SUCCESS) {
std::cout << "Failed to submit queue: " << result << std::endl;
// bluevk::vkDestroyFence(device, fence, nullptr);
return;
}
// // Wait for fence with timeout
// result = bluevk::vkWaitForFences(device, 1, &fence, VK_TRUE, 5000000000); // 5 second timeout
// if (result != VK_SUCCESS) {
// std::cout << "Failed to wait for fence: " << result << std::endl;
// vkDestroyFence(device, fence, nullptr);
// return;
// }
// std::cout << "Blit operation completed successfully" << std::endl;
// // Cleanup
// bluevk::vkDestroyFence(device, fence, nullptr);
// bluevk::vkFreeCommandBuffers(device, commandPool, 1, &cmd);
}
void ThermionVulkanContext::readPixelsFromImage(
uint32_t width,
uint32_t height,
std::vector<uint8_t>& outPixels
) {
auto&& vkTexture = _vulkanTextures.back();
auto image = vkTexture->GetImage();
VkDeviceSize bufferSize = width * height * 4; // RGBA8 format
// Create staging buffer
VkBuffer stagingBuffer;
VkDeviceMemory stagingBufferMemory;
VkBufferCreateInfo bufferInfo{};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = bufferSize;
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VkResult result = bluevk::vkCreateBuffer(device, &bufferInfo, nullptr, &stagingBuffer);
if (result != VK_SUCCESS) {
throw std::runtime_error("Failed to create staging buffer");
}
// Get memory requirements and allocate
VkMemoryRequirements memRequirements;
bluevk::vkGetBufferMemoryRequirements(device, stagingBuffer, &memRequirements);
VkMemoryAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size;
allocInfo.memoryTypeIndex = findMemoryType(
memRequirements.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
physicalDevice
);
result = bluevk::vkAllocateMemory(device, &allocInfo, nullptr, &stagingBufferMemory);
if (result != VK_SUCCESS) {
bluevk::vkDestroyBuffer(device, stagingBuffer, nullptr);
throw std::runtime_error("Failed to allocate staging buffer memory");
}
result = bluevk::vkBindBufferMemory(device, stagingBuffer, stagingBufferMemory, 0);
if (result != VK_SUCCESS) {
vkFreeMemory(device, stagingBufferMemory, nullptr);
vkDestroyBuffer(device, stagingBuffer, nullptr);
throw std::runtime_error("Failed to bind buffer memory");
}
// Create command buffer
VkCommandBufferAllocateInfo cmdBufAllocInfo{};
cmdBufAllocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
cmdBufAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
cmdBufAllocInfo.commandPool = commandPool;
cmdBufAllocInfo.commandBufferCount = 1;
VkCommandBuffer commandBuffer;
result = bluevk::vkAllocateCommandBuffers(device, &cmdBufAllocInfo, &commandBuffer);
if (result != VK_SUCCESS) {
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 = bluevk::vkBeginCommandBuffer(commandBuffer, &beginInfo);
if (result != VK_SUCCESS) {
throw std::runtime_error("Failed to begin command buffer");
}
// Transition image layout for transfer with proper sync
VkImageMemoryBarrier barrier{};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barrier.oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; // Assuming this is the current layout
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_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;
bluevk::vkCmdPipelineBarrier(
commandBuffer,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
0,
0, nullptr,
0, nullptr,
1, &barrier
);
// 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};
bluevk::vkCmdCopyImageToBuffer(
commandBuffer,
image,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
stagingBuffer,
1,
&region
);
// Transition image layout back
barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
bluevk::vkCmdPipelineBarrier(
commandBuffer,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0,
0, nullptr,
0, nullptr,
1, &barrier
);
result = bluevk::vkEndCommandBuffer(commandBuffer);
if (result != VK_SUCCESS) {
throw std::runtime_error("Failed to end command buffer");
}
// Submit command buffer with fence for synchronization
VkFenceCreateInfo fenceInfo{};
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
VkFence fence;
result = bluevk::vkCreateFence(device, &fenceInfo, nullptr, &fence);
if (result != VK_SUCCESS) {
throw std::runtime_error("Failed to create fence");
}
VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;
result = bluevk::vkQueueSubmit(queue, 1, &submitInfo, fence);
if (result != VK_SUCCESS) {
bluevk::vkDestroyFence(device, fence, nullptr);
throw std::runtime_error("Failed to submit queue");
}
// Wait for the command buffer to complete with timeout
result = bluevk::vkWaitForFences(device, 1, &fence, VK_TRUE, 5000000000); // 5 second timeout
if (result != VK_SUCCESS) {
bluevk::vkDestroyFence(device, fence, nullptr);
throw std::runtime_error("Failed to wait for fence");
}
// Map memory and copy data
void* data;
result = bluevk::vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data);
if (result != VK_SUCCESS) {
throw std::runtime_error("Failed to map memory");
}
outPixels.resize(bufferSize);
memcpy(outPixels.data(), data, bufferSize);
bluevk::vkUnmapMemory(device, stagingBufferMemory);
// Cleanup
bluevk::vkDestroyFence(device, fence, nullptr);
vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
vkDestroyBuffer(device, stagingBuffer, nullptr);
vkFreeMemory(device, stagingBufferMemory, nullptr);
std::cout << "Successfully completed readPixelsFromImage" << std::endl;
}
}

View File

@@ -0,0 +1,158 @@
#include "vulkan_texture.h"
#include "bluevk/BlueVK.h"
#include "utils.h"
#include <iostream>
namespace thermion::windows::vulkan
{
VulkanTexture::VulkanTexture(VkImage image, VkDevice device, VkDeviceMemory imageMemory, uint32_t width, uint32_t height, HANDLE d3dTextureHandle) : _image(image), _device(device), _imageMemory(imageMemory), _width(width), _height(height), _d3dTextureHandle(d3dTextureHandle) {};
VulkanTexture::~VulkanTexture() {
bluevk::vkDeviceWaitIdle(_device);
if(_image != VK_NULL_HANDLE) {
bluevk::vkDestroyImage(_device, _image, nullptr);
} else {
std::cout << "Warning : no vkImage found" << std::endl;
}
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkImportMemoryWin32HandleInfoKHR.html
// imageMemory has been imported from D3D without transferring ownership
// therefore we don't need to release
}
std::unique_ptr<VulkanTexture> VulkanTexture::create(VkDevice device, VkPhysicalDevice physicalDevice, uint32_t width, uint32_t height, HANDLE d3dTextureHandle)
{
// Create image with external memory support
VkExternalMemoryImageCreateInfo extImageInfo = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_BIT};
VkImageCreateInfo imageInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = &extImageInfo,
.flags = 0,
.imageType = VK_IMAGE_TYPE_2D,
.format = VK_FORMAT_B8G8R8A8_UNORM,
.extent = {width, height, 1},
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED};
VkImage image;
VkResult result = bluevk::vkCreateImage(device, &imageInfo, nullptr, &image);
if (result != VK_SUCCESS)
{
std::cout << "Failed to create iamge " << std::endl;
return nullptr;
}
std::cout << "Created vkImage " << (int64_t)image << std::endl;
VkMemoryDedicatedRequirements MemoryDedicatedRequirements{
.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS,
.pNext = nullptr};
VkMemoryRequirements2 MemoryRequirements2{
.sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2,
.pNext = &MemoryDedicatedRequirements};
const VkImageMemoryRequirementsInfo2 ImageMemoryRequirementsInfo2{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2,
.pNext = nullptr,
.image = image};
// WARN: Memory access violation unless validation instance layer is enabled, otherwise success but...
bluevk::vkGetImageMemoryRequirements2(device, &ImageMemoryRequirementsInfo2, &MemoryRequirements2);
// ... if we happen to be here, MemoryRequirements2 is empty
VkMemoryRequirements &MemoryRequirements = MemoryRequirements2.memoryRequirements;
const VkMemoryDedicatedAllocateInfo MemoryDedicatedAllocateInfo{
.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO,
.pNext = nullptr,
.image = image,
.buffer = VK_NULL_HANDLE};
const VkImportMemoryWin32HandleInfoKHR ImportMemoryWin32HandleInfo{
.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_KHR,
.pNext = &MemoryDedicatedAllocateInfo,
.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_BIT,
.handle = d3dTextureHandle,
.name = nullptr};
// Find suitable memory type
uint32_t memoryTypeIndex = findOptimalMemoryType(
physicalDevice,
MemoryRequirements.memoryTypeBits,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
);
VkMemoryAllocateInfo allocInfo{
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = &ImportMemoryWin32HandleInfo,
.allocationSize = MemoryRequirements.size,
.memoryTypeIndex = memoryTypeIndex
};
VkDeviceMemory imageMemory = VK_NULL_HANDLE;
VkResult allocResult = bluevk::vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory);
if (allocResult != VK_SUCCESS || imageMemory == VK_NULL_HANDLE)
{
std::cout << "IMAGE MEMORY ALLOCATION FAILED:" << std::endl;
std::cout << " Allocation size: " << MemoryRequirements.size << " bytes" << std::endl;
std::cout << " Memory type index: " << allocInfo.memoryTypeIndex << std::endl;
std::cout << " Error code: " << allocResult << std::endl;
// Get more detailed error message based on VkResult
const char *errorMsg;
switch (allocResult)
{
case VK_ERROR_OUT_OF_HOST_MEMORY:
errorMsg = "VK_ERROR_OUT_OF_HOST_MEMORY: Out of host memory";
break;
case VK_ERROR_OUT_OF_DEVICE_MEMORY:
errorMsg = "VK_ERROR_OUT_OF_DEVICE_MEMORY: Out of device memory";
break;
case VK_ERROR_INVALID_EXTERNAL_HANDLE:
errorMsg = "VK_ERROR_INVALID_EXTERNAL_HANDLE: The external handle is invalid";
break;
case VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS:
errorMsg = "VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS: The requested address is not available";
break;
default:
errorMsg = "Unknown error";
}
std::cout << " Error message: " << errorMsg << std::endl;
// Print memory requirements
std::cout << " Memory requirements:" << std::endl;
std::cout << " Size: " << MemoryRequirements.size << std::endl;
std::cout << " Alignment: " << MemoryRequirements.alignment << std::endl;
std::cout << " Memory type bits: 0x" << std::hex << MemoryRequirements.memoryTypeBits << std::dec << std::endl;
return nullptr;
}
const VkBindImageMemoryInfo bindImageMemoryInfo{
.sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO,
.pNext = nullptr,
.image = image,
.memory = imageMemory,
.memoryOffset = 0};
result = bluevk::vkBindImageMemory2(device, 1, &bindImageMemoryInfo);
if (result != VK_SUCCESS)
{
std::cout << "vkBindImageMemory2 failed" << std::endl;
return nullptr;
}
return std::make_unique<VulkanTexture>(image, device, imageMemory, width, height, d3dTextureHandle);
}
}