fake frames (and cleaning up swapchain code)

This commit is contained in:
ecker 2026-04-25 00:37:19 -05:00
parent 8803034a60
commit 2c3da15a4d
15 changed files with 633 additions and 715 deletions

View File

@ -90,8 +90,8 @@ ifneq (,$(findstring ffx:sdk,$(REQ_DEPS)))
ifneq (,$(findstring vulkan,$(REQ_DEPS)))
FLAGS += -DUF_USE_FFX_SDK=3
#INCS += -I./dep/include/ffx_fsr2/
#DEPS += -lffx_fsr3_x64drel -lffx_fsr2_x64drel -lffx_fsr3upscaler_x64drel -lffx_frameinterpolation_x64drel -lffx_opticalflow_x64drel -lffx_backend_vk_x64drel -lamd_fidelityfx_vkdrel
DEPS += -lffx_fsr3_x64 -lffx_fsr2_x64 -lffx_fsr3upscaler_x64 -lffx_frameinterpolation_x64 -lffx_opticalflow_x64 -lffx_backend_vk_x64 -lamd_fidelityfx_vk
DEPS += -lffx_fsr3_x64drel -lffx_fsr2_x64drel -lffx_fsr3upscaler_x64drel -lffx_frameinterpolation_x64drel -lffx_opticalflow_x64drel -lffx_backend_vk_x64drel -lamd_fidelityfx_vkdrel
#DEPS += -lffx_fsr3_x64 -lffx_fsr2_x64 -lffx_fsr3upscaler_x64 -lffx_frameinterpolation_x64 -lffx_opticalflow_x64 -lffx_backend_vk_x64 -lamd_fidelityfx_vk
endif
endif
ifneq (,$(findstring vulkan,$(REQ_DEPS)))

View File

@ -29,8 +29,8 @@
}
},
"vxgi": {
"limiter": 0,
// "limiter": 0.0125,
// "limiter": 0,
"limiter": 0.0125,
"size": 256,
"dispatch": 16,
"cascades": 3,
@ -101,7 +101,7 @@
"experimental": {
"rebuild on tick begin": false,
"batch queue submissions": true,
"dedicated thread": false, // mostly works
"dedicated thread": true, // mostly works
"memory budget": false,
"register render modes": true,
"skip render on rebuild": false
@ -122,7 +122,7 @@
"bloom": true,
"dof": true,
"rt": false,
"fsr": true,
"fsr": false,
"postProcess": false // "postProcess.chromab" // false
},
"formats": {
@ -228,7 +228,8 @@
"descriptorBindingVariableDescriptorCount",
"shaderOutputViewportIndex",
"shaderOutputLayer"
"shaderOutputLayer",
"timelineSemaphore"
],
"featureChain": [
"physicalDeviceVulkan12"
@ -303,7 +304,7 @@
"fsr": {
"enabled": true,
"upscale": true,
"interpolation": false, // it works but it's naively using the interpolated frame
"interpolation": true, // it works but i don't think it's presenting the frame
"sharpness": 0,
"jitter scale": 0.0625,
"preset": "native" // native (1x), quality (1.5x), balanced (1.7x), performance (2.0x), ultra (3.0x)

View File

@ -0,0 +1,24 @@
#version 450
#pragma shader_stage(compute)
layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in;
layout(binding = 0) uniform sampler2D samplerA;
layout(binding = 1) uniform sampler2D samplerB;
layout(binding = 2, rgba16f) uniform writeonly image2D imageOutput;
void main() {
ivec2 texCoords = ivec2(gl_GlobalInvocationID.xy);
ivec2 size = imageSize(imageOutput);
if(texCoords.x >= size.x || texCoords.y >= size.y) return;
vec2 uv = (vec2(texCoords) + vec2(0.5)) / vec2(size);
vec4 colorA = texture(samplerA, uv);
vec4 colorB = texture(samplerB, uv);
vec4 finalColor = vec4(mix(colorA.rgb, colorB.rgb, colorB.a), 1.0);
imageStore(imageOutput, texCoords, finalColor);
}

View File

@ -21,10 +21,18 @@ namespace ext {
void UF_API initialize();
void UF_API tick();
void UF_API render();
void UF_API render( VkCommandBuffer );
void UF_API terminate();
uf::renderer::Texture& getRenderTarget();
VkResult acquireNextImage( uint32_t*, VkSemaphore, VkFence = VK_NULL_HANDLE );
VkResult queuePresent( VkQueue, uint32_t, VkSemaphore = VK_NULL_HANDLE );
VkResult createSwapchain( VkDevice, VkSwapchainCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSwapchainKHR* pSwapchain );
void destroySwapchain( VkDevice, VkSwapchainKHR swapchain, const VkAllocationCallbacks* pAllocator );
VkResult getSwapchainImages( VkDevice, VkSwapchainKHR swapchain, uint32_t* pSwapchainImageCount, VkImage* pSwapchainImages );
VkSwapchainKHR getSwapchain( VkSwapchainKHR swapchain );
uf::renderer::Texture& getRenderTarget();
pod::Matrix4f UF_API getJitterMatrix();
}
}

View File

@ -15,6 +15,7 @@ namespace ext {
PRESENT,
COMPUTE,
TRANSFER,
ACQUIRE,
};
struct CommandBuffer {
bool immediate{true};
@ -76,6 +77,7 @@ namespace ext {
uf::ThreadUnique<VkQueue> present;
uf::ThreadUnique<VkQueue> compute;
uf::ThreadUnique<VkQueue> transfer;
uf::ThreadUnique<VkQueue> acquire;
} queues;
struct {
@ -104,7 +106,8 @@ namespace ext {
uint32_t present;
uint32_t compute;
uint32_t transfer;
} queueFamilyIndices;
uint32_t acquire;
} queueFamilyIndices, queueIndices;
operator VkDevice() { return this->logicalDevice; };
// helpers

View File

@ -14,9 +14,10 @@ namespace ext {
uint32_t buffers = {};
uf::stl::vector<VkSemaphore> presentCompleteSemaphores;
uf::stl::vector<VkImage> images;
// helpers
VkResult acquireNextImage( uint32_t* imageIndex, VkSemaphore, VkFence = nullptr );
VkResult acquireNextImage( uint32_t* imageIndex, VkSemaphore, VkFence = VK_NULL_HANDLE );
VkResult queuePresent( VkQueue queue, uint32_t imageIndex, VkSemaphore waitSemaphore = VK_NULL_HANDLE );
// RAII

View File

@ -286,6 +286,14 @@ void UF_API uf::load( ext::json::Value& json ) {
ext::fsr::frameUpscale = json["engine"]["ext"]["fsr"]["upscale"].as(ext::fsr::frameUpscale);
ext::fsr::frameInterpolation = json["engine"]["ext"]["fsr"]["interpolation"].as(ext::fsr::frameInterpolation);
{
bool enabled = json["engine"]["ext"]["fsr"]["enabled"].as(true);
if ( !enabled ) {
ext::fsr::frameUpscale = false;
ext::fsr::frameInterpolation = false;
}
}
#endif
if ( uf::renderer::hasRenderMode("", true) ) {

View File

@ -190,6 +190,11 @@ namespace {
FfxContextDescription contextDescription;
#if UF_USE_FFX_SDR_FRAME_INTERP && UF_USE_FFX_SDK == FFX_SDK_3_1
FfxSwapchain ffxSwapchain = nullptr;
FfxSwapchainReplacementFunctions ffxSwapchainFuncs = {};
uf::renderer::Graphic compositor;
uf::stl::vector<uint8_t> scratchBufferFG;
uf::stl::vector<uint8_t> scratchBufferOF;
@ -203,7 +208,7 @@ namespace {
uf::renderer::Texture empty;
uf::renderer::Texture output;
#if UF_USE_FFX_SDK == FFX_SDK_3_1
uf::renderer::Texture outputFG;
uf::renderer::Texture outputComposited;
uf::renderer::Texture dilatedMotionVectors;
uf::renderer::Texture dilatedDepth;
uf::renderer::Texture reconstructedPrevNearestDepth;
@ -224,7 +229,7 @@ namespace {
width, height,
1,
1,
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | usage,
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | usage,
layout
);
}
@ -399,6 +404,12 @@ namespace {
auto& camera = controller.getComponent<uf::Camera>();
auto& projection = camera.getProjection();
// composite GUI onto output
{
::compositor.record( commandBuffer );
barrier(commandBuffer, ::resources.outputComposited.image);
}
#if UF_USE_FFX_SDK == FFX_SDK_3
FfxFsr3DispatchFrameGenerationPrepareDescription dispatchParameters = {};
dispatchParameters.commandList = ffxGetCommandListVK(commandBuffer);
@ -407,6 +418,16 @@ namespace {
FFX_ERROR_CHECK(ffxFsr3ContextDispatchFrameGenerationPrepare(&::context, &dispatchParameters));
#elif UF_USE_FFX_SDK == FFX_SDK_3_1
FfxFrameGenerationConfig fgConfig = {};
fgConfig.swapChain = ffxGetSwapchainVK(uf::renderer::swapchain.swapChain);
fgConfig.frameGenerationEnabled = true;
fgConfig.allowAsyncWorkloads = true;
FFX_ERROR_CHECK(ffxSetFrameGenerationConfigToSwapchainVK(&fgConfig));
FfxCommandList interpolationCommandList;
ffxGetFrameinterpolationCommandlistVK(fgConfig.swapChain, interpolationCommandList);
FfxFrameInterpolationDispatchDescription dispatchParameters = {};
dispatchParameters.commandList = ffxGetCommandListVK(commandBuffer);
@ -416,15 +437,17 @@ namespace {
dispatchParameters.renderSize.height = displaySize.y;
// use output from rendermode
if ( ext::fsr::frameUpscale ) {
if ( !ext::fsr::frameUpscale ) {
if ( !renderMode.hasAttachment("output") && !renderMode.hasAttachment("color") ) return;
auto& attachmentColor = renderMode.hasAttachment("output") ? renderMode.getAttachment("output") : renderMode.getAttachment("color");
dispatchParameters.currentBackBuffer = createFfxResource(attachmentColor, L"FSR3_InterpolationSource");
dispatchParameters.currentBackBuffer_HUDLess = createFfxResource(attachmentColor, L"FSR3_InterpolationSource_HUDLess");
} else {
dispatchParameters.currentBackBuffer = createFfxResource(::resources.output, L"FSR3_InterpolationSource");
dispatchParameters.currentBackBuffer_HUDLess = createFfxResource(::resources.output, L"FSR3_InterpolationSource_HUDLess");
}
dispatchParameters.output = createFfxResource(::resources.outputFG, L"FSR3_InterpolatedOutput");
// attach HUD'd image
dispatchParameters.currentBackBuffer = createFfxResource(::resources.outputComposited, L"FSR3_InterpolationSource");
dispatchParameters.output = ffxGetFrameinterpolationTextureVK( ffxGetSwapchainVK(uf::renderer::swapchain.swapChain) );
dispatchParameters.cameraNear = projection(2,3);
dispatchParameters.cameraFar = FLT_MAX;
@ -450,9 +473,9 @@ namespace {
dispatchParameters.opticalFlowScale.x = 1.0f;
dispatchParameters.opticalFlowScale.y = 1.0f;
dispatchParameters.opticalFlowBlockSize = FFX_FSR_BLOCK_SIZE;
dispatchParameters.commandList = interpolationCommandList;
FFX_ERROR_CHECK(ffxFrameInterpolationDispatch(&::contextFG, &dispatchParameters));
barrier(commandBuffer, ::resources.outputFG.image);
#endif
}
#endif
@ -577,6 +600,8 @@ void ext::fsr::initialize() {
FFX_ERROR_CHECK(ffxGetInterfaceVK( &::contextDescriptionFG.backendInterface, ffxDevice, ::scratchBufferFG.data(), ::scratchBufferFG.size(), 1 ));
FFX_ERROR_CHECK(ffxFrameInterpolationContextCreate( &::contextFG, &::contextDescriptionFG ));
FFX_ERROR_CHECK(ffxGetSwapchainReplacementFunctionsVK(ffxDevice, &::ffxSwapchainFuncs));
}
// setup optical flow context
@ -594,9 +619,7 @@ void ext::fsr::initialize() {
::resources.output.format = uf::renderer::settings::pipelines::hdr ? uf::renderer::enums::Format::HDR : uf::renderer::enums::Format::SDR;
::initializeResource( ::resources.output );
#if UF_USE_FFX_SDK == FFX_SDK_3_1
::resources.outputFG.viewComponentMapping.a = VK_COMPONENT_SWIZZLE_ONE; // for some reason framegen has the alpha set to 1 (or never writes it)
::resources.outputFG.format = uf::renderer::settings::pipelines::hdr ? uf::renderer::enums::Format::HDR : uf::renderer::enums::Format::SDR;
::resources.outputComposited.format = uf::renderer::settings::pipelines::hdr ? uf::renderer::enums::Format::HDR : uf::renderer::enums::Format::SDR;
::resources.dilatedMotionVectors.format = uf::renderer::enums::Format::R16G16_SFLOAT;
::resources.dilatedDepth.format = uf::renderer::enums::Format::R32_SFLOAT;
::resources.reconstructedPrevNearestDepth.format = uf::renderer::enums::Format::R32_UINT;
@ -607,7 +630,7 @@ void ext::fsr::initialize() {
uint32_t ofWidth = (uf::renderer::settings::width + block_size) / block_size;
uint32_t ofHeight = (uf::renderer::settings::height + block_size) / block_size;
::initializeResource( ::resources.outputFG );
::initializeResource( ::resources.outputComposited );
::initializeResource( ::resources.dilatedMotionVectors );
::initializeResource( ::resources.dilatedDepth );
::initializeResource( ::resources.reconstructedPrevNearestDepth );
@ -616,6 +639,37 @@ void ext::fsr::initialize() {
#endif
}
// setup compositor
{
uf::Mesh mesh;
mesh.vertex.count = 3;
auto& blitter = ::compositor;
blitter.device = &uf::renderer::device;
blitter.material.device = &uf::renderer::device;
blitter.descriptor.renderMode = "Swapchain";
blitter.descriptor.subpass = -1;
blitter.initializeMesh( mesh );
blitter.material.attachShader(uf::io::resolveURI(uf::io::root+"/shaders/display/compositor/comp.spv"), ext::vulkan::enums::Shader::COMPUTE);
blitter.material.textures.clear();
blitter.material.textures.emplace_back().aliasTexture(::resources.output);
blitter.material.textures.emplace_back().aliasTexture(uf::renderer::Texture2D::empty);
blitter.material.textures.emplace_back().aliasTexture(::resources.outputComposited);
blitter.descriptor.bind.width = uf::renderer::settings::width;
blitter.descriptor.bind.height = uf::renderer::settings::height;
blitter.descriptor.bind.point = VK_PIPELINE_BIND_POINT_COMPUTE;
if ( !blitter.hasPipeline( blitter.descriptor ) ) {
blitter.initializePipeline( blitter.descriptor );
} else if ( blitter.hasPipeline( blitter.descriptor ) ){
blitter.getPipeline( blitter.descriptor ).update( blitter, blitter.descriptor );
}
}
ext::fsr::initialized = true;
}
void ext::fsr::tick() {
@ -684,7 +738,7 @@ void ext::fsr::tick() {
uint32_t ofWidth = (displaySize.x + block_size) / block_size;
uint32_t ofHeight = (displaySize.y + block_size) / block_size;
::initializeResource( ::resources.outputFG, displaySize.x, displaySize.y );
::initializeResource( ::resources.outputComposited, displaySize.x, displaySize.y );
::initializeResource( ::resources.dilatedMotionVectors, displaySize.x, displaySize.y );
::initializeResource( ::resources.dilatedDepth, displaySize.x, displaySize.y );
::initializeResource( ::resources.reconstructedPrevNearestDepth, displaySize.x, displaySize.y );
@ -692,6 +746,29 @@ void ext::fsr::tick() {
::initializeResource( ::resources.opticalFlowSceneChangeDetection, ofWidth, ofHeight );
#endif
}
{
auto& blitter = ::compositor;
blitter.material.textures.clear();
blitter.material.textures.emplace_back().aliasTexture(::resources.output);
if ( uf::renderer::hasRenderMode("Gui", true) ) {
auto& renderMode = uf::renderer::getRenderMode("Gui", true);
auto& attachment = renderMode.getAttachment("color");
blitter.material.textures.emplace_back().aliasAttachment( attachment );
} else {
blitter.material.textures.emplace_back().aliasTexture( uf::renderer::Texture2D::empty );
}
blitter.material.textures.emplace_back().aliasTexture(::resources.outputComposited);
blitter.descriptor.bind.width = displaySize.x;
blitter.descriptor.bind.height = displaySize.y;
if ( !blitter.hasPipeline( blitter.descriptor ) ) {
blitter.initializePipeline( blitter.descriptor );
} else if ( blitter.hasPipeline( blitter.descriptor ) ){
blitter.getPipeline( blitter.descriptor ).update( blitter, blitter.descriptor );
}
}
}
// update jitter
@ -713,8 +790,12 @@ void ext::fsr::tick() {
}
void ext::fsr::render() {
if ( !ext::fsr::initialized ) return;
auto commandBuffer = uf::renderer::device.fetchCommandBuffer(uf::renderer::QueueEnum::GRAPHICS, true); // immediately flush
render( commandBuffer );
uf::renderer::device.flushCommandBuffer(commandBuffer);
}
void ext::fsr::render( VkCommandBuffer commandBuffer ) {
if ( !ext::fsr::initialized ) return;
if ( ext::fsr::frameUpscale ) {
upscale( commandBuffer );
}
@ -726,7 +807,6 @@ void ext::fsr::render() {
framegen( commandBuffer );
#endif
}
uf::renderer::device.flushCommandBuffer(commandBuffer);
}
void ext::fsr::terminate() {
if ( !ext::fsr::initialized ) return;
@ -746,7 +826,7 @@ void ext::fsr::terminate() {
{
::resources.output.destroy();
#if UF_USE_FFX_SDK == FFX_SDK_3_1
::resources.outputFG.destroy();
::resources.outputComposited.destroy();
::resources.dilatedMotionVectors.destroy();
::resources.dilatedDepth.destroy();
::resources.reconstructedPrevNearestDepth.destroy();
@ -754,6 +834,99 @@ void ext::fsr::terminate() {
::resources.opticalFlowSceneChangeDetection.destroy();
#endif
}
// destroy compositor
{
::compositor.destroy();
}
}
VkResult ext::fsr::acquireNextImage( uint32_t* imageIndex, VkSemaphore presentCompleteSemaphore, VkFence acquireFence ) {
#if UF_USE_FFX_SDR_FRAME_INTERP && UF_USE_FFX_SDK == FFX_SDK_3_1
if ( ext::fsr::frameInterpolation && ::ffxSwapchainFuncs.acquireNextImageKHR ) {
return ::ffxSwapchainFuncs.acquireNextImageKHR( uf::renderer::device, uf::renderer::swapchain.swapChain, VK_DEFAULT_FENCE_TIMEOUT, presentCompleteSemaphore, acquireFence, imageIndex );
}
#endif
return vkAcquireNextImageKHR( uf::renderer::device, uf::renderer::swapchain.swapChain, VK_DEFAULT_FENCE_TIMEOUT, presentCompleteSemaphore, acquireFence, imageIndex );
}
VkResult ext::fsr::queuePresent( VkQueue queue, uint32_t imageIndex, VkSemaphore waitSemaphore ) {
VkPresentInfoKHR presentInfo = {};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.pNext = NULL;
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = &uf::renderer::swapchain.swapChain;
presentInfo.pImageIndices = &imageIndex;
if ( waitSemaphore != VK_NULL_HANDLE ) {
presentInfo.pWaitSemaphores = &waitSemaphore;
presentInfo.waitSemaphoreCount = 1;
}
#if UF_USE_FFX_SDR_FRAME_INTERP && UF_USE_FFX_SDK == FFX_SDK_3_1
if ( ext::fsr::frameInterpolation && ::ffxSwapchainFuncs.queuePresentKHR ) {
return ::ffxSwapchainFuncs.queuePresentKHR(queue, &presentInfo);
}
#endif
return vkQueuePresentKHR(queue, &presentInfo);
}
VkResult ext::fsr::createSwapchain( VkDevice device, VkSwapchainCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSwapchainKHR* pSwapchain ) {
#if UF_USE_FFX_SDR_FRAME_INTERP && UF_USE_FFX_SDK == FFX_SDK_3_1
if ( ext::fsr::frameInterpolation && !::ffxSwapchainFuncs.createSwapchainFFX ) {
VkDeviceContext deviceContextVK = {};
deviceContextVK.vkDevice = device;
deviceContextVK.vkPhysicalDevice = uf::renderer::device.physicalDevice;
deviceContextVK.vkDeviceProcAddr = vkGetDeviceProcAddr;
FfxDevice ffxDevice = ffxGetDeviceVK(&deviceContextVK);
FFX_ERROR_CHECK(ffxGetSwapchainReplacementFunctionsVK(ffxDevice, &::ffxSwapchainFuncs));
}
if ( ext::fsr::frameInterpolation && ::ffxSwapchainFuncs.createSwapchainFFX ) {
VkFrameInterpolationInfoFFX fiInfo = {};
fiInfo.device = device;
fiInfo.physicalDevice = uf::renderer::device.physicalDevice;
fiInfo.pAllocator = pAllocator;
fiInfo.compositionMode = VK_COMPOSITION_MODE_GAME_QUEUE_FFX; // Standard mode
fiInfo.gameQueue.queue = uf::renderer::device.getQueue( uf::renderer::QueueEnum::GRAPHICS );
fiInfo.gameQueue.familyIndex = uf::renderer::device.queueFamilyIndices.graphics;
fiInfo.gameQueue.submitFunc = nullptr;
fiInfo.presentQueue.queue = uf::renderer::device.getQueue( uf::renderer::QueueEnum::PRESENT );
fiInfo.presentQueue.familyIndex = uf::renderer::device.queueFamilyIndices.present;
fiInfo.presentQueue.submitFunc = nullptr;
fiInfo.asyncComputeQueue.queue = uf::renderer::device.getQueue( uf::renderer::QueueEnum::COMPUTE );
fiInfo.asyncComputeQueue.familyIndex = uf::renderer::device.queueFamilyIndices.compute;
fiInfo.asyncComputeQueue.submitFunc = nullptr;
fiInfo.imageAcquireQueue.queue = uf::renderer::device.getQueue( uf::renderer::QueueEnum::ACQUIRE );
fiInfo.imageAcquireQueue.familyIndex = uf::renderer::device.queueFamilyIndices.acquire;
fiInfo.imageAcquireQueue.submitFunc = nullptr;
return ::ffxSwapchainFuncs.createSwapchainFFX( device, pCreateInfo, pAllocator, pSwapchain, &fiInfo );
}
#endif
return vkCreateSwapchainKHR( device, pCreateInfo, pAllocator, pSwapchain );
}
void ext::fsr::destroySwapchain( VkDevice device, VkSwapchainKHR swapchain, const VkAllocationCallbacks* pAllocator ) {
#if UF_USE_FFX_SDR_FRAME_INTERP && UF_USE_FFX_SDK == FFX_SDK_3_1
if ( ext::fsr::frameInterpolation && ::ffxSwapchainFuncs.destroySwapchainKHR ) {
::ffxSwapchainFuncs.destroySwapchainKHR(device, swapchain, pAllocator);
return;
}
#endif
vkDestroySwapchainKHR( device, swapchain, pAllocator );
}
VkResult ext::fsr::getSwapchainImages( VkDevice device, VkSwapchainKHR swapchain, uint32_t* pSwapchainImageCount, VkImage* pSwapchainImages ) {
#if UF_USE_FFX_SDR_FRAME_INTERP && UF_USE_FFX_SDK == FFX_SDK_3_1
if ( ext::fsr::frameInterpolation && ::ffxSwapchainFuncs.getSwapchainImagesKHR ) {
return ::ffxSwapchainFuncs.getSwapchainImagesKHR(device, swapchain, pSwapchainImageCount, pSwapchainImages);
}
#endif
return vkGetSwapchainImagesKHR( device, swapchain, pSwapchainImageCount, pSwapchainImages );
}
pod::Matrix4f ext::fsr::getJitterMatrix() {
@ -761,12 +934,7 @@ pod::Matrix4f ext::fsr::getJitterMatrix() {
}
uf::renderer::Texture& ext::fsr::getRenderTarget() {
#if UF_USE_FFX_SDR_FRAME_INTERP
if ( ext::fsr::frameInterpolation ) return ::resources.outputFG;
#endif
return ::resources.output;
}
// to-do: add functions to get framegen swapchain functions
#endif

View File

@ -787,24 +787,34 @@ VkCommandPool ext::vulkan::Device::getCommandPool( ext::vulkan::QueueEnum queueE
VkQueue ext::vulkan::Device::getQueue( ext::vulkan::QueueEnum queueEnum, std::thread::id id ) {
auto& device = *this;
uint32_t familyIndex = 0;
uint32_t index = 0;
uf::ThreadUnique<VkQueue>* commandPool{NULL};
switch ( queueEnum ) {
case QueueEnum::GRAPHICS:
index = device.queueFamilyIndices.graphics;
familyIndex = device.queueFamilyIndices.graphics;
commandPool = &queues.graphics;
index = device.queueIndices.graphics;
break;
case QueueEnum::PRESENT:
index = device.queueFamilyIndices.present;
familyIndex = device.queueFamilyIndices.present;
commandPool = &queues.present;
index = device.queueIndices.present;
break;
case QueueEnum::COMPUTE:
index = device.queueFamilyIndices.compute;
familyIndex = device.queueFamilyIndices.compute;
commandPool = &queues.compute;
index = device.queueIndices.compute;
break;
case QueueEnum::TRANSFER:
index = device.queueFamilyIndices.transfer;
familyIndex = device.queueFamilyIndices.transfer;
commandPool = &queues.transfer;
index = device.queueIndices.transfer;
break;
case QueueEnum::ACQUIRE:
familyIndex = device.queueFamilyIndices.acquire;
commandPool = &queues.acquire;
index = device.queueIndices.acquire;
break;
}
UF_ASSERT( commandPool );
@ -812,7 +822,7 @@ VkQueue ext::vulkan::Device::getQueue( ext::vulkan::QueueEnum queueEnum, std::th
bool exists = commandPool->has(id);
VkQueue& queue = commandPool->get(id);
if ( !exists ) {
vkGetDeviceQueue( device, index, 0, &queue );
vkGetDeviceQueue( device, familyIndex, index, &queue );
}
return queue;
}
@ -1070,37 +1080,24 @@ void ext::vulkan::Device::initialize() {
extensions.enabled.device[s] = true;
}
// Desired queues need to be requested upon logical device creation
// Due to differing queue family configurations of Vulkan implementations this can be a bit tricky, especially if the application
// requests different queue types
uf::stl::vector<VkDeviceQueueCreateInfo> queueCreateInfos{};
// Get queue family indices for the requested queue family types
// Note that the indices may overlap depending on the implementation
const float defaultQueuePriority(0.0f);
std::vector<std::vector<float>> queuePriorities;
// Graphics queue
if ( requestedQueueTypes & VK_QUEUE_GRAPHICS_BIT ) {
queueFamilyIndices.graphics = getQueueFamilyIndex(VK_QUEUE_GRAPHICS_BIT);
VkDeviceQueueCreateInfo queueInfo{};
queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueInfo.queueFamilyIndex = queueFamilyIndices.graphics;
queueInfo.queueCount = 1;
queueInfo.pQueuePriorities = &defaultQueuePriority;
queueCreateInfos.push_back(queueInfo);
} else {
queueFamilyIndices.graphics = 0; // VK_NULL_HANDLE;
}
// Dedicated compute queue
if ( requestedQueueTypes & VK_QUEUE_COMPUTE_BIT ) {
queueFamilyIndices.compute = getQueueFamilyIndex(VK_QUEUE_COMPUTE_BIT);
if ( queueFamilyIndices.compute != queueFamilyIndices.graphics ) {
// If compute family index differs, we need an additional queue create info for the compute queue
VkDeviceQueueCreateInfo queueInfo{};
queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueInfo.queueFamilyIndex = queueFamilyIndices.compute;
queueInfo.queueCount = 1;
queueInfo.pQueuePriorities = &defaultQueuePriority;
queueCreateInfos.push_back(queueInfo);
}
} else {
// Else we use the same queue
queueFamilyIndices.compute = queueFamilyIndices.graphics;
@ -1108,19 +1105,50 @@ void ext::vulkan::Device::initialize() {
// Dedicated transfer queue
if ( requestedQueueTypes & VK_QUEUE_TRANSFER_BIT ) {
queueFamilyIndices.transfer = getQueueFamilyIndex(VK_QUEUE_TRANSFER_BIT);
if ((queueFamilyIndices.transfer != queueFamilyIndices.graphics) && (queueFamilyIndices.transfer != queueFamilyIndices.compute)) {
// If compute family index differs, we need an additional queue create info for the compute queue
VkDeviceQueueCreateInfo queueInfo{};
queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueInfo.queueFamilyIndex = queueFamilyIndices.transfer;
queueInfo.queueCount = 1;
queueInfo.pQueuePriorities = &defaultQueuePriority;
queueCreateInfos.emplace_back(queueInfo);
}
} else {
// Else we use the same queue
queueFamilyIndices.transfer = queueFamilyIndices.graphics;
}
// Dedicated acquire queue
{
queueFamilyIndices.acquire = queueFamilyIndices.present;
}
// tally up how many queues we need
std::map<uint32_t, uint32_t> requestedQueuesPerFamily;
requestedQueuesPerFamily[queueFamilyIndices.graphics]++;
requestedQueuesPerFamily[queueFamilyIndices.compute]++;
requestedQueuesPerFamily[queueFamilyIndices.transfer]++;
requestedQueuesPerFamily[queueFamilyIndices.present]++;
requestedQueuesPerFamily[queueFamilyIndices.acquire]++;
for ( const auto& [ familyIndex, requestedCount ] : requestedQueuesPerFamily ) {
uint32_t maxSupported = queueFamilyProperties[familyIndex].queueCount;
uint32_t actualCount = std::min( requestedCount, maxSupported );
queuePriorities.emplace_back(std::vector<float>(actualCount, 1.0f));
VkDeviceQueueCreateInfo queueInfo{};
queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueInfo.queueFamilyIndex = familyIndex;
queueInfo.queueCount = actualCount;
queueInfo.pQueuePriorities = queuePriorities.back().data();
queueCreateInfos.emplace_back(queueInfo);
}
{
std::map<uint32_t, uint32_t> familyIndexCounters;
auto assignQueueIndex = [&](uint32_t family) -> uint32_t {
return familyIndexCounters[family]++;
};
device.queueIndices.graphics = assignQueueIndex( device.queueFamilyIndices.graphics );
device.queueIndices.compute = assignQueueIndex( device.queueFamilyIndices.compute );
device.queueIndices.transfer = assignQueueIndex( device.queueFamilyIndices.transfer );
device.queueIndices.present = assignQueueIndex( device.queueFamilyIndices.present );
device.queueIndices.acquire = assignQueueIndex( device.queueFamilyIndices.acquire );
}
// Create the logical device representation
if ( useSwapChain ) {
@ -1311,35 +1339,41 @@ void ext::vulkan::Device::initialize() {
int i = 0;
for (const auto& queueFamily : queueFamilyProperties) {
if ( queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT )
if ( queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT ) {
graphicsQueueNodeIndex = i;
}
if ( queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_COMPUTE_BIT )
if ( queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_COMPUTE_BIT ) {
computeQueueNodeIndex = i;
}
if ( queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_TRANSFER_BIT )
if ( queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_TRANSFER_BIT ) {
transferQueueNodeIndex = i;
}
VkBool32 presentSupport = false;
vkGetPhysicalDeviceSurfaceSupportKHR( this->physicalDevice, i, surface, &presentSupport );
if ( queueFamily.queueCount > 0 && presentSupport )
if ( queueFamily.queueCount > 0 && presentSupport ) {
presentQueueNodeIndex = i;
}
if ( graphicsQueueNodeIndex != UINT32_MAX && presentQueueNodeIndex != UINT32_MAX && computeQueueNodeIndex != UINT32_MAX ) break;
i++;
}
VK_VALIDATION_MESSAGE("Graphics queue: {}", device.queueFamilyIndices.graphics);
VK_VALIDATION_MESSAGE("Compute queue: {}", device.queueFamilyIndices.compute);
VK_VALIDATION_MESSAGE("Transfer queue: {}", device.queueFamilyIndices.transfer);
VK_VALIDATION_MESSAGE("Present queue: {}", device.queueFamilyIndices.present);
VK_VALIDATION_MESSAGE("Graphics queue: family={}, index={}", device.queueFamilyIndices.graphics, device.queueIndices.graphics );
VK_VALIDATION_MESSAGE("Compute queue: family={}, index={}", device.queueFamilyIndices.compute, device.queueIndices.compute );
VK_VALIDATION_MESSAGE("Transfer queue: family={}, index={}", device.queueFamilyIndices.transfer, device.queueIndices.transfer );
VK_VALIDATION_MESSAGE("Present queue: family={}, index={}", device.queueFamilyIndices.present, device.queueIndices.present );
VK_VALIDATION_MESSAGE("Acquire queue: family={}, index={}", device.queueFamilyIndices.acquire, device.queueIndices.acquire );
device.queueFamilyIndices.present = presentQueueNodeIndex;
getQueue( QueueEnum::GRAPHICS );
getQueue( QueueEnum::PRESENT );
getQueue( QueueEnum::COMPUTE );
getQueue( QueueEnum::TRANSFER );
getQueue( QueueEnum::ACQUIRE );
}
// Set formats
{

View File

@ -454,8 +454,7 @@ void ext::vulkan::Pipeline::record( const Graphic& graphic, const GraphicDescrip
// no matching bind point for shaders, skip
if ( !bound ) {
UF_MSG_DEBUG("No shaders found to bind...");
return;
UF_MSG_DEBUG("No shaders found to bind...: {} | {}", shaders.size(), descriptor.pipeline);
}
// Bind descriptor sets describing shader binding points

View File

@ -9,13 +9,173 @@
#include <uf/utils/graphic/graphic.h>
#include <uf/utils/io/fmt.h>
namespace {
uf::stl::vector<VkImage> images;
}
const uf::stl::string ext::vulkan::BaseRenderMode::getType() const {
return "Swapchain";
}
void ext::vulkan::BaseRenderMode::build( bool resized ) {
}
void ext::vulkan::BaseRenderMode::initialize( Device& device ) {
this->metadata.name = "Swapchain";
auto windowSize = device.window->getSize();
this->width = windowSize.x;
this->height = windowSize.y;
renderTarget.width = this->width;
renderTarget.height = this->height;
ext::vulkan::RenderMode::initialize( device );
swapchain.initialize( device );
renderTarget.device = &device;
renderTarget.passes.clear();
renderTarget.attachments.clear();
struct {
size_t color, depth;
} attachments = {};
attachments.color = renderTarget.attach(RenderTarget::Attachment::Descriptor{
/*.format = */ext::vulkan::settings::formats::color,
/*.layout = */VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
/*.usage = */VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
/*.blend = */false,
/*.samples = */1,
});
attachments.depth = renderTarget.attach(RenderTarget::Attachment::Descriptor{
/*.format = */ext::vulkan::settings::formats::depth,
/*.layout = */VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
/*.usage = */VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
/*.blend = */false,
/*.samples = */1,
});
metadata.attachments["color"] = attachments.color;
metadata.attachments["depth"] = attachments.depth;
renderTarget.addPass(
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
{ attachments.color }, {}, {}, attachments.depth, 0, true
);
renderTarget.initialize( device );
// set sync objects
for ( auto i = 0; i < ext::vulkan::swapchain.buffers; ++i ) {
auto& presentCompleteSemaphore = swapchain.presentCompleteSemaphores.emplace_back();
VkSemaphoreCreateInfo semaphoreCreateInfo = {};
semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
semaphoreCreateInfo.pNext = nullptr;
VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &presentCompleteSemaphore));
VK_REGISTER_HANDLE(presentCompleteSemaphore);
}
}
void ext::vulkan::BaseRenderMode::tick() {
ext::vulkan::RenderMode::tick();
bool resized = (this->width == 0 && this->height == 0) || ext::vulkan::states::resized || this->resized;
bool rebuild = resized || ext::vulkan::states::rebuild || this->rebuild;
auto windowSize = device->window->getSize();
if ( windowSize.x == this->width && windowSize.y == this->height ) {
resized = false;
}
// rebuild rendertarget
if ( resized ) {
this->destroy();
this->initialize( *this->device );
/*
this->width = windowSize.x;
this->height = windowSize.y;
this->resized = false;
rebuild = true;
renderTarget.width = this->width;
renderTarget.height = this->height;
swapchain.initialize( *swapchain.device );
renderTarget.initialize( *renderTarget.device );
*/
}
// update blitter descriptor set
if ( rebuild && blitter.initialized ) {
this->build( resized );
}
}
void ext::vulkan::BaseRenderMode::render() {
// if ( ext::vulkan::states::frameSkip ) return;
// if ( ext::vulkan::renderModes.size() > 1 ) return;
// if ( ext::vulkan::renderModes.back() != this ) return;
if ( this->commands.container().empty() ) return;
//lockMutex( this->mostRecentCommandPoolId );
auto& commands = getCommands( this->mostRecentCommandPoolId );
VK_CHECK_RESULT(swapchain.acquireNextImage(&states::currentBuffer, swapchain.presentCompleteSemaphores[0]));
VK_CHECK_RESULT(vkWaitForFences(*device, 1, &fences[states::currentBuffer], VK_TRUE, VK_DEFAULT_FENCE_TIMEOUT));
VK_CHECK_RESULT(vkResetFences(*device, 1, &fences[states::currentBuffer]));
VkPipelineStageFlags waitStageMask[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.pWaitDstStageMask = waitStageMask;
submitInfo.pWaitSemaphores = &swapchain.presentCompleteSemaphores[0];
submitInfo.waitSemaphoreCount = 1;
submitInfo.pSignalSemaphores = &renderCompleteSemaphores[states::currentBuffer];
submitInfo.signalSemaphoreCount = 1;
submitInfo.pCommandBuffers = &commands[states::currentBuffer];
submitInfo.commandBufferCount = 1;
{
VkQueue queue = device->getQueue( QueueEnum::GRAPHICS );
VkResult res = vkQueueSubmit( queue, 1, &submitInfo, fences[states::currentBuffer]);
VK_CHECK_QUEUE_CHECKPOINT( queue, res );
}
VK_CHECK_RESULT(swapchain.queuePresent(device->getQueue( QueueEnum::PRESENT ), states::currentBuffer, renderCompleteSemaphores[states::currentBuffer]));
#if 0
{
VkQueue queue = device->getQueue( QueueEnum::PRESENT );
VkResult res = vkQueueWaitIdle(device->getQueue( QueueEnum::PRESENT ));
VK_CHECK_QUEUE_CHECKPOINT( queue, res );
}
#endif
this->executed = true;
//unlockMutex( this->mostRecentCommandPoolId );
}
void ext::vulkan::BaseRenderMode::destroy() {
ext::vulkan::RenderMode::destroy();
for ( auto& presentCompleteSemaphore : swapchain.presentCompleteSemaphores ) {
vkDestroySemaphore( *device, presentCompleteSemaphore, nullptr);
VK_UNREGISTER_HANDLE( presentCompleteSemaphore );
}
swapchain.presentCompleteSemaphores.clear();
}
ext::vulkan::GraphicDescriptor ext::vulkan::BaseRenderMode::bindGraphicDescriptor( const ext::vulkan::GraphicDescriptor& reference, size_t pass ) {
ext::vulkan::GraphicDescriptor descriptor = ext::vulkan::RenderMode::bindGraphicDescriptor(reference, pass);
descriptor.depth.test = false;
descriptor.depth.write = false;
return descriptor;
}
void ext::vulkan::BaseRenderMode::createCommandBuffers( const uf::stl::vector<ext::vulkan::Graphic*>& graphics ) {
// if ( ext::vulkan::renderModes.size() > 1 ) return;
@ -94,40 +254,11 @@ void ext::vulkan::BaseRenderMode::createCommandBuffers( const uf::stl::vector<ex
auto& commandBuffer = commands[frame];
renderPassBeginInfo.framebuffer = renderTarget.framebuffers[frame];
VK_CHECK_RESULT(vkBeginCommandBuffer(commandBuffer, &cmdBufInfo));
device->UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::BEGIN, "begin" );
{
size_t currentSubpass = 0;
{
VkImageMemoryBarrier imageMemoryBarrier = {};
imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
imageMemoryBarrier.srcAccessMask = 0;
imageMemoryBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
imageMemoryBarrier.oldLayout = renderTarget.attachments[frame].descriptor.layout;
imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
// explicitly transfer queue-ownership
if ( ext::vulkan::device.queueFamilyIndices.graphics != ext::vulkan::device.queueFamilyIndices.present ) {
imageMemoryBarrier.srcQueueFamilyIndex = ext::vulkan::device.queueFamilyIndices.present;
imageMemoryBarrier.dstQueueFamilyIndex = ext::vulkan::device.queueFamilyIndices.graphics;
} else {
imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; // ext::vulkan::device.queueFamilyIndices.present;
imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; // ext::vulkan::device.queueFamilyIndices.graphics;
}
imageMemoryBarrier.image = renderTarget.attachments[frame].image;
imageMemoryBarrier.subresourceRange.baseMipLevel = 0;
imageMemoryBarrier.subresourceRange.levelCount = 1;
imageMemoryBarrier.subresourceRange.baseArrayLayer = 0;
imageMemoryBarrier.subresourceRange.layerCount = 1;
imageMemoryBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
renderTarget.attachments[frame].descriptor.layout = imageMemoryBarrier.newLayout;
device->UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::GENERIC, "setImageLayout" );
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier);
}
// pre-renderpass commands
VK_COMMAND_BUFFER_CALLBACK( CALLBACK_BEGIN, commandBuffer, frame, {
@ -160,29 +291,6 @@ void ext::vulkan::BaseRenderMode::createCommandBuffers( const uf::stl::vector<ex
VK_COMMAND_BUFFER_CALLBACK( CALLBACK_END, commandBuffer, frame, {
device->UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::GENERIC, "callback[end]" );
} );
// need to transfer it back, if they differ
if ( ext::vulkan::device.queueFamilyIndices.graphics != ext::vulkan::device.queueFamilyIndices.present ) {
VkImageMemoryBarrier imageMemoryBarrier = {};
imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
imageMemoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
imageMemoryBarrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
imageMemoryBarrier.oldLayout = renderTarget.attachments[frame].descriptor.layout;
imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
imageMemoryBarrier.srcQueueFamilyIndex = ext::vulkan::device.queueFamilyIndices.graphics;
imageMemoryBarrier.dstQueueFamilyIndex = ext::vulkan::device.queueFamilyIndices.present;
imageMemoryBarrier.image = renderTarget.attachments[frame].image;
imageMemoryBarrier.subresourceRange.baseMipLevel = 0;
imageMemoryBarrier.subresourceRange.levelCount = 1;
imageMemoryBarrier.subresourceRange.baseArrayLayer = 0;
imageMemoryBarrier.subresourceRange.layerCount = 1;
imageMemoryBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
renderTarget.attachments[frame].descriptor.layout = imageMemoryBarrier.newLayout;
device->UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::GENERIC, "setImageLayout" );
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier);
}
}
device->UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::END, "end" );
@ -190,501 +298,4 @@ void ext::vulkan::BaseRenderMode::createCommandBuffers( const uf::stl::vector<ex
}
}
void ext::vulkan::BaseRenderMode::build( bool resized ) {
}
void ext::vulkan::BaseRenderMode::tick() {
ext::vulkan::RenderMode::tick();
if ( ext::vulkan::states::resized ) {
this->destroy();
this->initialize( *this->device );
}
}
void ext::vulkan::BaseRenderMode::render() {
// if ( ext::vulkan::states::frameSkip ) return;
// if ( ext::vulkan::renderModes.size() > 1 ) return;
// if ( ext::vulkan::renderModes.back() != this ) return;
if ( this->commands.container().empty() ) return;
//lockMutex( this->mostRecentCommandPoolId );
auto& commands = getCommands( this->mostRecentCommandPoolId );
// Get next image in the swap chain (back/front buffer)
VK_CHECK_RESULT(swapchain.acquireNextImage(&states::currentBuffer, swapchain.presentCompleteSemaphores[0]));
// Use a fence to wait until the command buffer has finished execution before using it again
VK_CHECK_RESULT(vkWaitForFences(*device, 1, &fences[states::currentBuffer], VK_TRUE, VK_DEFAULT_FENCE_TIMEOUT));
VK_CHECK_RESULT(vkResetFences(*device, 1, &fences[states::currentBuffer]));
// Pipeline stage at which the queue submission will wait (via pWaitSemaphores)
VkPipelineStageFlags waitStageMask[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
// The submit info structure specifices a command buffer queue submission batch
VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.pWaitDstStageMask = waitStageMask; // Pointer to the list of pipeline stages that the semaphore waits will occur at
submitInfo.pWaitSemaphores = &swapchain.presentCompleteSemaphores[0]; // Semaphore(s) to wait upon before the submitted command buffer starts executing
submitInfo.waitSemaphoreCount = 1; // One wait semaphore
submitInfo.pSignalSemaphores = &renderCompleteSemaphores[states::currentBuffer]; // Semaphore(s) to be signaled when command buffers have completed
submitInfo.signalSemaphoreCount = 1; // One signal semaphore
submitInfo.pCommandBuffers = &commands[states::currentBuffer]; // Command buffers(s) to execute in this batch (submission)
submitInfo.commandBufferCount = 1;
// Submit to the graphics queue passing a wait fence
#if 1
VK_CHECK_RESULT(vkQueueSubmit( device->getQueue( QueueEnum::GRAPHICS ), 1, &submitInfo, fences[states::currentBuffer]));
#else
{
VkQueue queue = device->getQueue( QueueEnum::GRAPHICS );
VkResult res = vkQueueSubmit( queue, 1, &submitInfo, fences[states::currentBuffer]);
VK_CHECK_QUEUE_CHECKPOINT( queue, res );
}
#endif
// Present the current buffer to the swap chain
// Pass the semaphore signaled by the command buffer submission from the submit info as the wait semaphore for swap chain presentation
// This ensures that the image is not presented to the windowing system until all commands have been submitted
VK_CHECK_RESULT(swapchain.queuePresent(device->getQueue( QueueEnum::PRESENT ), states::currentBuffer, renderCompleteSemaphores[states::currentBuffer]));
#if 1
//VK_CHECK_RESULT(vkQueueWaitIdle(device->getQueue( QueueEnum::PRESENT )));
#else
{
VkQueue queue = device->getQueue( QueueEnum::PRESENT );
VkResult res = vkQueueWaitIdle(device->getQueue( QueueEnum::PRESENT ));
VK_CHECK_QUEUE_CHECKPOINT( queue, res );
}
#endif
this->executed = true;
//unlockMutex( this->mostRecentCommandPoolId );
}
void ext::vulkan::BaseRenderMode::initialize( Device& device ) {
this->metadata.name = "Swapchain";
auto windowSize = device.window->getSize();
this->width = windowSize.x;
this->height = windowSize.y;
ext::vulkan::RenderMode::initialize( device );
// manual initialization
// recreate swapchain
// destroy any existing imageviews
// attachments marked as aliased are actually from the swapchain
// swapchain.destroy();
swapchain.initialize( device );
// bind swapchain images
::images.resize( ext::vulkan::swapchain.buffers );
VK_CHECK_RESULT(vkGetSwapchainImagesKHR( device, swapchain.swapChain, &swapchain.buffers, ::images.data()));
// create image views for swapchain images
renderTarget.attachments.clear();
renderTarget.attachments.resize( ext::vulkan::swapchain.buffers );
// uint32_t width = windowSize.x; //this->width > 0 ? this->width : windowSize.x;
// uint32_t height = windowSize.y; //this->height > 0 ? this->height : windowSize.y;
size_t attachmentIndex = 0;
for ( size_t frame = 0; frame < ext::vulkan::swapchain.buffers; ++frame ) {
VkImageViewCreateInfo colorAttachmentView = {};
colorAttachmentView.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
colorAttachmentView.pNext = NULL;
colorAttachmentView.format = ext::vulkan::settings::formats::color;
colorAttachmentView.components = {
VK_COMPONENT_SWIZZLE_R,
VK_COMPONENT_SWIZZLE_G,
VK_COMPONENT_SWIZZLE_B,
VK_COMPONENT_SWIZZLE_A
};
colorAttachmentView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
colorAttachmentView.subresourceRange.baseMipLevel = 0;
colorAttachmentView.subresourceRange.levelCount = 1;
colorAttachmentView.subresourceRange.baseArrayLayer = 0;
colorAttachmentView.subresourceRange.layerCount = 1;
colorAttachmentView.viewType = VK_IMAGE_VIEW_TYPE_2D;
colorAttachmentView.flags = 0;
colorAttachmentView.image = ::images[frame];
VK_CHECK_RESULT(vkCreateImageView( device, &colorAttachmentView, nullptr, &renderTarget.attachments[frame].view));
VK_REGISTER_HANDLE( renderTarget.attachments[frame].view );
renderTarget.attachments[frame].descriptor.format = ext::vulkan::settings::formats::color;
// renderTarget.attachments[frame].descriptor.layout = VK_IMAGE_LAYOUT_UNDEFINED;
renderTarget.attachments[frame].descriptor.layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
renderTarget.attachments[frame].descriptor.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
renderTarget.attachments[frame].descriptor.aliased = true;
renderTarget.attachments[frame].image = ::images[frame];
renderTarget.attachments[frame].mem = VK_NULL_HANDLE;
metadata.attachments["color["+std::to_string((int) frame)+"]"] = attachmentIndex++;
}
{
// Create depth
auto& attachment = renderTarget.attachments.emplace_back();
metadata.attachments["depth"] = attachmentIndex++;
// Create an optimal image used as the depth stencil attachment
VkImageCreateInfo imageCreateInfo = {};
imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
imageCreateInfo.format = ext::vulkan::settings::formats::depth;
// Use example's height and width
imageCreateInfo.extent = { width, height, 1 };
imageCreateInfo.mipLevels = 1;
imageCreateInfo.arrayLayers = 1;
imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageCreateInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VmaAllocationCreateInfo allocInfo = {};
allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
VK_CHECK_RESULT(vmaCreateImage(allocator, &imageCreateInfo, &allocInfo, &attachment.image, &attachment.allocation, &attachment.allocationInfo));
VK_REGISTER_HANDLE( attachment.image );
attachment.descriptor.format = ext::vulkan::settings::formats::depth;
// attachment.descriptor.layout = VK_IMAGE_LAYOUT_UNDEFINED;
attachment.descriptor.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
attachment.descriptor.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
// attachment.descriptor.aliased = true;
attachment.mem = attachment.allocationInfo.deviceMemory;
VkImageViewCreateInfo depthStencilView = {};
depthStencilView.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
depthStencilView.viewType = VK_IMAGE_VIEW_TYPE_2D;
depthStencilView.format = ext::vulkan::settings::formats::depth;
depthStencilView.subresourceRange = {};
depthStencilView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
depthStencilView.subresourceRange.baseMipLevel = 0;
depthStencilView.subresourceRange.levelCount = 1;
depthStencilView.subresourceRange.baseArrayLayer = 0;
depthStencilView.subresourceRange.layerCount = 1;
depthStencilView.image = attachment.image;
VK_CHECK_RESULT(vkCreateImageView(device, &depthStencilView, nullptr, &attachment.view));
VK_REGISTER_HANDLE(attachment.view);
}
#if 1
auto commandBuffer = device.fetchCommandBuffer(uf::renderer::QueueEnum::GRAPHICS);
for ( auto& attachment : renderTarget.attachments ) {
bool isDepth = attachment.descriptor.usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
if ( isDepth ) {
// transition attachments to general attachments for imageStore
VkImageSubresourceRange subresourceRange;
subresourceRange.baseMipLevel = 0;
subresourceRange.levelCount = 1;
subresourceRange.baseArrayLayer = 0;
subresourceRange.layerCount = 1;
subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
subresourceRange.layerCount = 1;
uf::renderer::Texture::setImageLayout( commandBuffer, attachment.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, subresourceRange );
} else {
// transition attachments to general attachments for imageStore
VkImageSubresourceRange subresourceRange;
subresourceRange.baseMipLevel = 0;
subresourceRange.levelCount = 1;
subresourceRange.baseArrayLayer = 0;
subresourceRange.layerCount = 1;
subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
subresourceRange.layerCount = 1;
uf::renderer::Texture::setImageLayout( commandBuffer, attachment.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, subresourceRange );
}
}
device.flushCommandBuffer(commandBuffer, uf::renderer::QueueEnum::GRAPHICS);
#endif
// Create FSR dump
/*
if ( settings::pipelines::fsr ) {
auto& attachment = renderTarget.attachments.emplace_back();
VkImageCreateInfo imageCreateInfo = {};
imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
imageCreateInfo.format = ext::vulkan::settings::pipelines::hdr ? enums::Format::HDR : enums::Format::SDR;
imageCreateInfo.extent = { width, height, 1 };
imageCreateInfo.mipLevels = 1;
imageCreateInfo.arrayLayers = 1;
imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageCreateInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VmaAllocationCreateInfo allocInfo = {};
allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
VK_CHECK_RESULT(vmaCreateImage(allocator, &imageCreateInfo, &allocInfo, &attachment.image, &attachment.allocation, &attachment.allocationInfo));
VK_REGISTER_HANDLE( attachment.image );
attachment.mem = attachment.allocationInfo.deviceMemory;
VkImageViewCreateInfo imageViewCreateInfo = {};
imageViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
imageViewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
imageViewCreateInfo.format = ext::vulkan::settings::pipelines::hdr ? enums::Format::HDR : enums::Format::SDR;
imageViewCreateInfo.subresourceRange = {};
imageViewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
imageViewCreateInfo.subresourceRange.baseMipLevel = 0;
imageViewCreateInfo.subresourceRange.levelCount = 1;
imageViewCreateInfo.subresourceRange.baseArrayLayer = 0;
imageViewCreateInfo.subresourceRange.layerCount = 1;
imageViewCreateInfo.image = attachment.image;
VK_CHECK_RESULT(vkCreateImageView(device, &imageViewCreateInfo, nullptr, &attachment.view));
VK_REGISTER_HANDLE(attachment.view);
metadata.attachments["fsr"] = attachmentIndex++;
}
*/
// Create renderpass
if ( !renderTarget.renderPass ) {// Create render pass
// This example will use a single render pass with one subpass
// Descriptors for the attachments used by this renderpass
std::array<VkAttachmentDescription, 2> attachments = {};
// Color attachment
attachments[0].format = ext::vulkan::settings::formats::color; // Use the color format selected by the swapchain
attachments[0].samples = VK_SAMPLE_COUNT_1_BIT; // We don't use multi sampling in this example
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; // Clear this attachment at the start of the render pass
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE; // Keep it's contents after the render pass is finished (for displaying it)
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; // We don't use stencil, so don't care for load
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; // Same for store
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; // Layout at render pass start. Initial doesn't matter, so we use undefined
attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; // Layout to which the attachment is transitioned when the render pass is finished
// As we want to present the color buffer to the swapchain, we transition to PRESENT_KHR
// Depth attachment
attachments[1].format = ext::vulkan::settings::formats::depth; // A proper depth format is selected in the example base
attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; // Clear depth at start of first subpass
attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; // We don't need depth after render pass has finished (DONT_CARE may result in better performance)
attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; // No stencil
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; // No Stencil
attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; // Layout at render pass start. Initial doesn't matter, so we use undefined
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; // Transition to depth/stencil attachment
// Setup attachment references
VkAttachmentReference colorReference = {};
colorReference.attachment = 0; // Attachment 0 is color
colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; // Attachment layout used as color during the subpass
VkAttachmentReference depthReference = {};
depthReference.attachment = 1; // Attachment 1 is color
depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; // Attachment used as depth/stemcil used during the subpass
// Setup a single subpass reference
VkSubpassDescription subpassDescription = {};
subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpassDescription.colorAttachmentCount = 1; // Subpass uses one color attachment
subpassDescription.pColorAttachments = &colorReference; // Reference to the color attachment in slot 0
subpassDescription.pDepthStencilAttachment = &depthReference; // Reference to the depth attachment in slot 1
subpassDescription.inputAttachmentCount = 0; // Input attachments can be used to sample from contents of a previous subpass
subpassDescription.pInputAttachments = nullptr; // (Input attachments not used by this example)
subpassDescription.preserveAttachmentCount = 0; // Preserved attachments can be used to loop (and preserve) attachments through subpasses
subpassDescription.pPreserveAttachments = nullptr; // (Preserve attachments not used by this example)
subpassDescription.pResolveAttachments = nullptr; // Resolve attachments are resolved at the end of a sub pass and can be used for e.g. multi sampling
// Setup subpass dependencies
// These will add the implicit ttachment layout transitionss specified by the attachment descriptions
// The actual usage layout is preserved through the layout specified in the attachment reference
// Each subpass dependency will introduce a memory and execution dependency between the source and dest subpass described by
// srcStageMask, dstStageMask, srcAccessMask, dstAccessMask (and dependencyFlags is set)
// Note: VK_SUBPASS_EXTERNAL is a special constant that refers to all commands executed outside of the actual renderpass)
std::array<VkSubpassDependency, 2> dependencies;
// First dependency at the start of the renderpass
// Does the transition from final to initial layout
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL; // Producer of the dependency
dependencies[0].dstSubpass = 0; // Consumer is our single subpass that will wait for the execution depdendency
dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
// Second dependency at the end the renderpass
// Does the transition from the initial to the final layout
dependencies[1].srcSubpass = 0; // Producer of the dependency is our single subpass
dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL; // Consumer are all commands outside of the renderpass
dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
// Create the actual renderpass
VkRenderPassCreateInfo renderPassInfo = {};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size()); // Number of attachments used by this render pass
renderPassInfo.pAttachments = attachments.data(); // Descriptions of the attachments used by the render pass
renderPassInfo.subpassCount = 1; // We only use one subpass in this example
renderPassInfo.pSubpasses = &subpassDescription; // Description of that subpass
renderPassInfo.dependencyCount = static_cast<uint32_t>(dependencies.size()); // Number of subpass dependencies
renderPassInfo.pDependencies = dependencies.data(); // Subpass dependencies used by the render pass
VK_CHECK_RESULT(vkCreateRenderPass( device, &renderPassInfo, nullptr, &renderTarget.renderPass));
VK_REGISTER_HANDLE(renderTarget.renderPass);
}
// Create framebuffer
{
// Create a frame buffer for every image in the swapchain
renderTarget.framebuffers.resize(::images.size());
for (size_t frame = 0; frame < renderTarget.framebuffers.size(); frame++)
{
std::array<VkImageView, 2> attachments;
attachments[0] = renderTarget.attachments[frame].view; // Color attachment is the view of the swapchain image
attachments[1] = renderTarget.attachments[metadata.attachments["depth"]].view; // Depth/Stencil attachment is the same for all frame buffers
VkFramebufferCreateInfo frameBufferCreateInfo = {};
frameBufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
// All frame buffers use the same renderpass setup
frameBufferCreateInfo.renderPass = renderTarget.renderPass;
frameBufferCreateInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
frameBufferCreateInfo.pAttachments = attachments.data();
frameBufferCreateInfo.width = width;
frameBufferCreateInfo.height = height;
frameBufferCreateInfo.layers = 1;
// Create the framebuffer
VK_CHECK_RESULT(vkCreateFramebuffer( device, &frameBufferCreateInfo, nullptr, &renderTarget.framebuffers[frame]));
VK_REGISTER_HANDLE(renderTarget.framebuffers[frame]);
}
}
#if 0
if ( true ) {
auto commandBuffer = device.fetchCommandBuffer(uf::renderer::QueueEnum::TRANSFER);
for ( size_t frame = 0; frame < ::images.size(); ++frame ) {
VkImageMemoryBarrier imageMemoryBarrier = {};
imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
imageMemoryBarrier.srcAccessMask = 0;
imageMemoryBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
imageMemoryBarrier.oldLayout = renderTarget.attachments[frame].descriptor.layout;
imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; // ext::vulkan::device.queueFamilyIndices.present;
imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; // ext::vulkan::device.queueFamilyIndices.graphics;
imageMemoryBarrier.image = renderTarget.attachments[frame].image;
imageMemoryBarrier.subresourceRange.baseMipLevel = 0;
imageMemoryBarrier.subresourceRange.levelCount = 1;
imageMemoryBarrier.subresourceRange.baseArrayLayer = 0;
imageMemoryBarrier.subresourceRange.layerCount = 1;
imageMemoryBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
renderTarget.attachments[frame].descriptor.layout = imageMemoryBarrier.newLayout;
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier);
}
device.flushCommandBuffer(uf::renderer::QueueEnum::TRANSFER);
}
#endif
/*
{
renderTarget.device = &device;
// attach targets
struct {
size_t color, depth;
} attachments;
attachments.color = 0; {
}
attachments.depth = renderTarget.attach( ext::vulkan::settings::formats::depth, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL ); // depth
// First pass: fill the G-Buffer
{
renderTarget.addPass(
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
{ attachments.color },
{},
attachments.depth
);
}
}
renderTarget.initialize( device );
*/
// Set sync objects
for ( auto i = 0; i < ext::vulkan::swapchain.buffers; ++i ) {
auto& presentCompleteSemaphore = swapchain.presentCompleteSemaphores.emplace_back();
// Semaphores (Used for correct command ordering)
VkSemaphoreCreateInfo semaphoreCreateInfo = {};
semaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
semaphoreCreateInfo.pNext = nullptr;
// Semaphore used to ensures that image presentation is complete before starting to submit again
VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &presentCompleteSemaphore));
VK_REGISTER_HANDLE(presentCompleteSemaphore);
}
}
void ext::vulkan::BaseRenderMode::destroy() {
if ( renderTarget.renderPass != VK_NULL_HANDLE ) {
vkDestroyRenderPass( *device, renderTarget.renderPass, nullptr );
VK_UNREGISTER_HANDLE( renderTarget.renderPass );
renderTarget.renderPass = VK_NULL_HANDLE;
}
for ( uint32_t frame = 0; frame < renderTarget.framebuffers.size(); frame++ ) {
if ( renderTarget.framebuffers[frame] != VK_NULL_HANDLE ) {
vkDestroyFramebuffer( *device, renderTarget.framebuffers[frame], nullptr );
VK_UNREGISTER_HANDLE( renderTarget.framebuffers[frame] );
renderTarget.framebuffers[frame] = VK_NULL_HANDLE;
}
}
for ( auto& attachment : renderTarget.attachments ) {
if ( attachment.view != VK_NULL_HANDLE ) {
vkDestroyImageView( *device, attachment.view, nullptr);
VK_UNREGISTER_HANDLE( attachment.view );
attachment.view = VK_NULL_HANDLE;
}
if ( attachment.descriptor.aliased ) continue;
if ( attachment.image != VK_NULL_HANDLE ) {
// vkDestroyImage( *device, attachment.image, nullptr );
vmaDestroyImage( allocator, attachment.image, attachment.allocation );
VK_UNREGISTER_HANDLE( attachment.image );
attachment.image = VK_NULL_HANDLE;
}
if ( attachment.mem != VK_NULL_HANDLE ) {
// vkFreeMemory( *device, attachment.mem, nullptr );
attachment.mem = VK_NULL_HANDLE;
}
}
for ( auto& image : ::images ) {
// vkDestroyImage( *device, image, nullptr ); // destroyed via vkDestroySwapchainKHR
VK_UNREGISTER_HANDLE( image );
image = VK_NULL_HANDLE;
}
::images.clear();
ext::vulkan::RenderMode::destroy();
for ( auto& presentCompleteSemaphore : swapchain.presentCompleteSemaphores ) {
vkDestroySemaphore( *device, presentCompleteSemaphore, nullptr);
VK_UNREGISTER_HANDLE( presentCompleteSemaphore );
}
swapchain.presentCompleteSemaphores.clear();
}
ext::vulkan::GraphicDescriptor ext::vulkan::BaseRenderMode::bindGraphicDescriptor( const ext::vulkan::GraphicDescriptor& reference, size_t pass ) {
ext::vulkan::GraphicDescriptor descriptor = ext::vulkan::RenderMode::bindGraphicDescriptor(reference, pass);
/*
descriptor.parse(metadata.json["descriptor"]);
// invalidate
if ( metadata.target != "" && descriptor.renderMode != this->getName() && descriptor.renderMode != metadata.target ) {
descriptor.invalidated = true;
} else {
descriptor.renderMode = this->getName();
}
*/
descriptor.depth.test = false;
descriptor.depth.write = false;
return descriptor;
}
#endif

View File

@ -280,7 +280,7 @@ void ext::vulkan::DeferredRenderMode::initialize( Device& device ) {
});
auto& shader = blitter.material.getShader("fragment");
if ( !settings::pipelines::fsr ) {
if ( !settings::pipelines::fsr || !ext::fsr::frameUpscale ) {
shader.aliasAttachment("output", this);
}
}
@ -456,7 +456,7 @@ void ext::vulkan::DeferredRenderMode::build( bool resized ) {
// if resized (or initialized)
if ( resized ) {
#if UF_USE_FFX_FSR || UF_USE_FFX_SDK
if ( settings::pipelines::fsr ) {
if ( settings::pipelines::fsr && ext::fsr::frameUpscale ) {
auto& shader = blitter.material.getShader("fragment");
shader.textures.clear();
shader.textures.emplace_back().aliasTexture( ext::fsr::getRenderTarget() );
@ -1109,6 +1109,16 @@ void ext::vulkan::DeferredRenderMode::createCommandBuffers( const uf::stl::vecto
#endif
}
/*
#if UF_USE_FFX_FSR || UF_USE_FFX_SDK
{
device->UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::END, "fsr:start" );
ext::fsr::render( commandBuffer );
device->UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::END, "fsr:end" );
}
#endif
*/
device->UF_CHECKPOINT_MARK( commandBuffer, pod::Checkpoint::END, "end" );
VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer));
}

View File

@ -211,10 +211,10 @@ void ext::vulkan::RenderTargetRenderMode::initialize( Device& device ) {
} else {
for ( size_t currentPass = 0; currentPass < metadata.subpasses; ++currentPass ) {
struct {
size_t albedo, depth;
size_t color, depth;
} attachments = {};
attachments.albedo = renderTarget.attach(RenderTarget::Attachment::Descriptor{
attachments.color = renderTarget.attach(RenderTarget::Attachment::Descriptor{
/*.format = */VK_FORMAT_R8G8B8A8_UNORM,
/*.layout = */VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
/*.usage = */VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT,
@ -230,7 +230,7 @@ void ext::vulkan::RenderTargetRenderMode::initialize( Device& device ) {
});
renderTarget.addPass(
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
{ attachments.albedo },
{ attachments.color },
{},
{},
attachments.depth,
@ -238,7 +238,7 @@ void ext::vulkan::RenderTargetRenderMode::initialize( Device& device ) {
true
);
metadata.attachments["albedo"] = attachments.albedo;
metadata.attachments["color"] = attachments.color;
metadata.attachments["depth"] = attachments.depth;
}
}
@ -537,7 +537,7 @@ void ext::vulkan::RenderTargetRenderMode::createCommandBuffers( const uf::stl::v
// VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL
// VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL
#if 1
#if 0
for ( auto& attachment : renderTarget.attachments ) {
// transition attachments to general attachments for imageStore
VkImageSubresourceRange subresourceRange;

View File

@ -34,7 +34,7 @@ size_t ext::vulkan::RenderTarget::attach( const Attachment::Descriptor& descript
VK_UNREGISTER_HANDLE( view );
}
attachment->views.clear();
if ( attachment->image ) {
if ( attachment->image && attachment->descriptor.layout != VK_IMAGE_LAYOUT_PRESENT_SRC_KHR ) {
vmaDestroyImage( allocator, attachment->image, attachment->allocation );
attachment->image = VK_NULL_HANDLE;
}
@ -73,8 +73,51 @@ size_t ext::vulkan::RenderTarget::attach( const Attachment::Descriptor& descript
}
attachment->views.resize(this->views * attachment->descriptor.mips);
bool isSwapchain = attachment->descriptor.layout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
bool isDepth = attachment->descriptor.usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
// swapchain specialization
if ( isSwapchain ) {
attachment->descriptor.aliased = true;
attachment->views.resize(ext::vulkan::swapchain.buffers);
VkImageSubresourceRange subresourceRange;
subresourceRange.baseMipLevel = 0;
subresourceRange.levelCount = 1;
subresourceRange.baseArrayLayer = 0;
subresourceRange.layerCount = 1;
subresourceRange.aspectMask = isDepth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
subresourceRange.layerCount = 1;
auto commandBuffer = device->fetchCommandBuffer(uf::renderer::QueueEnum::GRAPHICS);
for ( size_t i = 0; i < ext::vulkan::swapchain.buffers; ++i ) {
auto& image = ext::vulkan::swapchain.images[i];
VkImageViewCreateInfo imageView = {};
imageView.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
imageView.viewType = VK_IMAGE_VIEW_TYPE_2D;
imageView.format = attachment->descriptor.format;
imageView.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A };
imageView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
imageView.subresourceRange.baseMipLevel = 0;
imageView.subresourceRange.levelCount = 1;
imageView.subresourceRange.baseArrayLayer = 0;
imageView.subresourceRange.layerCount = 1;
imageView.image = image;
VK_CHECK_RESULT(vkCreateImageView(*device, &imageView, nullptr, &attachment->views[i]));
VK_REGISTER_HANDLE( attachment->views[i] );
uf::renderer::Texture::setImageLayout( commandBuffer, image, VK_IMAGE_LAYOUT_UNDEFINED, attachment->descriptor.layout, subresourceRange );
}
device->flushCommandBuffer(commandBuffer, uf::renderer::QueueEnum::GRAPHICS);
attachment->image = ext::vulkan::swapchain.images[0];
attachment->view = attachment->views[0];
attachment->mem = VK_NULL_HANDLE;
return index;
}
VkImageCreateInfo imageCreateInfo = {};
imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
@ -158,27 +201,13 @@ size_t ext::vulkan::RenderTarget::attach( const Attachment::Descriptor& descript
if ( false ) {
auto commandBuffer = device->fetchCommandBuffer(uf::renderer::QueueEnum::GRAPHICS);
if ( isDepth ) {
// transition attachments to general attachments for imageStore
VkImageSubresourceRange subresourceRange;
subresourceRange.baseMipLevel = 0;
subresourceRange.baseArrayLayer = 0;
subresourceRange.levelCount = attachment->descriptor.mips;
subresourceRange.layerCount = this->views;
subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
uf::renderer::Texture::setImageLayout( commandBuffer, attachment->image, VK_IMAGE_LAYOUT_UNDEFINED, attachment->descriptor.layout, subresourceRange );
} else {
// transition attachments to general attachments for imageStore
VkImageSubresourceRange subresourceRange;
subresourceRange.baseMipLevel = 0;
subresourceRange.baseArrayLayer = 0;
subresourceRange.levelCount = attachment->descriptor.mips;
subresourceRange.layerCount = this->views;
subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
uf::renderer::Texture::setImageLayout( commandBuffer, attachment->image, VK_IMAGE_LAYOUT_UNDEFINED, attachment->descriptor.layout, subresourceRange );
}
VkImageSubresourceRange subresourceRange;
subresourceRange.baseMipLevel = 0;
subresourceRange.baseArrayLayer = 0;
subresourceRange.levelCount = attachment->descriptor.mips;
subresourceRange.layerCount = this->views;
subresourceRange.aspectMask = isDepth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
uf::renderer::Texture::setImageLayout( commandBuffer, attachment->image, VK_IMAGE_LAYOUT_UNDEFINED, attachment->descriptor.layout, subresourceRange );
device->flushCommandBuffer(commandBuffer, uf::renderer::QueueEnum::GRAPHICS);
}
@ -201,6 +230,11 @@ void ext::vulkan::RenderTarget::initialize( Device& device ) {
assert( this->attachments.size() > 0 );
// Create render pass
bool isSwapchain = false;
for ( auto& attachment : this->attachments ) {
if ( attachment.descriptor.layout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR ) isSwapchain = true;
}
if ( !renderPass ) {
uf::stl::vector<VkAttachmentDescription> attachments; attachments.reserve( this->attachments.size() );
for ( size_t i = 0; i < this->views; ++i ) {
@ -212,7 +246,7 @@ void ext::vulkan::RenderTarget::initialize( Device& device ) {
description.storeOp = attachment.descriptor.usage & VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE;
description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
description.initialLayout = attachment.descriptor.layout; // VK_IMAGE_LAYOUT_UNDEFINED;
description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; // isSwapchain ? VK_IMAGE_LAYOUT_UNDEFINED : attachment.descriptor.layout; // VK_IMAGE_LAYOUT_UNDEFINED;
description.finalLayout = ext::vulkan::Texture::remapRenderpassLayout( attachment.descriptor.layout );
description.flags = 0;
@ -220,7 +254,6 @@ void ext::vulkan::RenderTarget::initialize( Device& device ) {
}
}
// ensure that the subpasses are already described
auto passes = this->passes;
assert( passes.size() > 0 );
@ -316,6 +349,26 @@ void ext::vulkan::RenderTarget::initialize( Device& device ) {
dependencies.emplace_back(dependency);
}
// crunge
if ( isSwapchain ) {
dependencies.resize(2);
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
dependencies[0].dstSubpass = 0;
dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
dependencies[1].srcSubpass = 0;
dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
}
VkRenderPassCreateInfo renderPassInfo = {};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
@ -336,30 +389,20 @@ void ext::vulkan::RenderTarget::initialize( Device& device ) {
vkDestroyFramebuffer( device, framebuffer, nullptr );
VK_UNREGISTER_HANDLE( framebuffer );
}
RenderMode& base = ext::vulkan::getRenderMode( "Swapchain", false );
framebuffers.clear();
framebuffers.resize(ext::vulkan::swapchain.buffers);
for ( size_t i = 0; i < framebuffers.size(); ++i ) {
for ( size_t frame = 0; frame < framebuffers.size(); ++frame ) {
uf::stl::vector<VkImageView> attachmentViews;
for ( size_t view = 0; view < this->views; ++view ) {
for ( auto view = 0; view < this->views; ++view ) {
for ( auto& attachment : this->attachments ) {
if ( attachment.descriptor.aliased && attachment.descriptor.layout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR ) {
attachmentViews.emplace_back(base.renderTarget.attachments[i].view);
continue;
if ( attachment.descriptor.layout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR ) {
attachmentViews.emplace_back(attachment.views[frame]);
} else {
attachmentViews.emplace_back(attachment.views[view * attachment.descriptor.mips]);
}
attachmentViews.emplace_back(attachment.views[view * attachment.descriptor.mips]);
}
}
#if 0
for ( size_t j = 0; j < this->views; ++j ) {
for ( auto& attachment : this->attachments ) {
if ( attachment.descriptor.aliased && attachment.descriptor.layout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR ) {
attachmentViews.emplace_back(base.renderTarget.attachments[i].view);
} else attachmentViews.emplace_back(attachment.views[j]);
}
}
#endif
VkFramebufferCreateInfo frameBufferCreateInfo = {};
frameBufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
@ -371,45 +414,11 @@ void ext::vulkan::RenderTarget::initialize( Device& device ) {
frameBufferCreateInfo.height = height;
frameBufferCreateInfo.layers = 1;
// Create the framebuffer
VK_CHECK_RESULT(vkCreateFramebuffer( device, &frameBufferCreateInfo, nullptr, &framebuffers[i]));
VK_REGISTER_HANDLE( framebuffers[i] );
VK_CHECK_RESULT(vkCreateFramebuffer( device, &frameBufferCreateInfo, nullptr, &framebuffers[frame]));
VK_REGISTER_HANDLE( framebuffers[frame] );
}
}
#if 0
{
auto commandBuffer = device.fetchCommandBuffer(uf::renderer::QueueEnum::TRANSFER);
for ( auto& attachment : attachments ) {
bool isDepth = attachment.descriptor.usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
if ( isDepth ) {
// transition attachments to general attachments for imageStore
VkImageSubresourceRange subresourceRange;
subresourceRange.baseMipLevel = 0;
subresourceRange.levelCount = 1;
subresourceRange.baseArrayLayer = 0;
subresourceRange.layerCount = 1;
subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
subresourceRange.layerCount = 1;
uf::renderer::Texture::setImageLayout( commandBuffer, attachment.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, subresourceRange );
} else {
// transition attachments to general attachments for imageStore
VkImageSubresourceRange subresourceRange;
subresourceRange.baseMipLevel = 0;
subresourceRange.levelCount = 1;
subresourceRange.baseArrayLayer = 0;
subresourceRange.layerCount = 1;
subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
subresourceRange.layerCount = 1;
uf::renderer::Texture::setImageLayout( commandBuffer, attachment.image, VK_IMAGE_LAYOUT_UNDEFINED, attachment.descriptor.layout, subresourceRange );
}
}
device.flushCommandBuffer(commandBuffer);
}
#endif
initialized = true;
}
@ -421,6 +430,7 @@ void ext::vulkan::RenderTarget::destroy() {
vkDestroyFramebuffer( *device, framebuffer, nullptr );
VK_UNREGISTER_HANDLE( framebuffer );
}
framebuffers.clear();
for ( auto& attachment : attachments ) {
if ( attachment.descriptor.aliased ) continue;

View File

@ -4,14 +4,24 @@
#include <uf/ext/vulkan/swapchain.h>
#include <uf/ext/vulkan/initializers.h>
#include <uf/utils/window/window.h>
#include <uf/ext/ffx/fsr.h>
VkResult ext::vulkan::Swapchain::acquireNextImage( uint32_t* imageIndex, VkSemaphore presentCompleteSemaphore, VkFence acquireFence ) {
// By setting timeout to UINT64_MAX we will always wait until the next image has been acquired or an actual error is thrown
// With that we don't have to handle VK_NOT_READY
#if UF_USE_FFX_SDK
if ( ext::fsr::frameInterpolation ) {
return ext::fsr::acquireNextImage( imageIndex, presentCompleteSemaphore, acquireFence );
}
#endif
return vkAcquireNextImageKHR( *device, swapChain, VK_DEFAULT_FENCE_TIMEOUT, presentCompleteSemaphore, acquireFence, imageIndex );
}
VkResult ext::vulkan::Swapchain::queuePresent( VkQueue queue, uint32_t imageIndex, VkSemaphore waitSemaphore ) {
#if UF_USE_FFX_SDK
if ( ext::fsr::frameInterpolation ) {
return ext::fsr::queuePresent( queue, imageIndex, waitSemaphore );
}
#endif
VkPresentInfoKHR presentInfo = {};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.pNext = NULL;
@ -168,24 +178,55 @@ void ext::vulkan::Swapchain::initialize( Device& device ) {
swapchainCI.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
}
VK_CHECK_RESULT(vkCreateSwapchainKHR( device.logicalDevice, &swapchainCI, nullptr, &swapChain));
#if UF_USE_FFX_SDK
VK_CHECK_RESULT(ext::fsr::createSwapchain( device.logicalDevice, &swapchainCI, nullptr, &swapChain ));
#else
VK_CHECK_RESULT(vkCreateSwapchainKHR( device.logicalDevice, &swapchainCI, nullptr, &swapChain ));
#endif
// VK_REGISTER_HANDLE( swapchainCI );
// If an existing swap chain is re-created, destroy the old swap chain
// This also cleans up all the presentable images
if (oldSwapchain != VK_NULL_HANDLE) {
vkDestroySwapchainKHR( device.logicalDevice, oldSwapchain, nullptr);
if ( oldSwapchain != VK_NULL_HANDLE ) {
#if UF_USE_FFX_SDK
ext::fsr::destroySwapchain( device.logicalDevice, oldSwapchain, nullptr);
#else
vkDestroySwapchainKHR( device.logicalDevice, oldSwapchain, nullptr);
#endif
// VK_UNREGISTER_HANDLE( oldSwapchain );
}
#if UF_USE_FFX_SDK
VK_CHECK_RESULT(ext::fsr::getSwapchainImages( device.logicalDevice, swapChain, &buffers, NULL));
#else
VK_CHECK_RESULT(vkGetSwapchainImagesKHR( device.logicalDevice, swapChain, &buffers, NULL));
#endif
}
// Bind images
{
images.resize( buffers );
#if UF_USE_FFX_SDK
VK_CHECK_RESULT(ext::fsr::getSwapchainImages( device, swapChain, &buffers, images.data()));
#else
VK_CHECK_RESULT(vkGetSwapchainImagesKHR( device, swapChain, &buffers, images.data()));
#endif
}
}
void ext::vulkan::Swapchain::destroy() {
if ( !device ) return;
for ( auto& image : swapchain.images ) {
// vkDestroyImage( *device, image, nullptr ); // destroyed via vkDestroySwapchainKHR
//VK_UNREGISTER_HANDLE( image );
image = VK_NULL_HANDLE;
}
swapchain.images.clear();
if ( swapChain != VK_NULL_HANDLE ) {
#if UF_USE_FFX_SDK
ext::fsr::destroySwapchain( *device, swapChain, nullptr );
#else
vkDestroySwapchainKHR( *device, swapChain, nullptr);
#endif
// VK_UNREGISTER_HANDLE( swapChain )
}