/
redgpu
/
ezEngine
Обзор
Документация
Войти
/
redgpu
/
ezEngine
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
dev
Code/Engine/RendererVulkan/Device/Implementation/SwapChainVulkan.cpp
469 строк
20 KB
Sanakan8472
Vulkan Validation Fixes (#1977)
06 июл 2026, 20:49
Не верифицирован
06 июл 2026, 20:49
b1f37ac
Код
Авторство
О чём код?
#include <RendererVulkan/RendererVulkanPCH.h> #include <Core/System/Window.h> #include <Foundation/Profiling/Profiling.h> #include <Foundation/Reflection/ReflectionUtils.h> #include <RendererFoundation/RendererReflection.h> #include <RendererFoundation/Resources/RenderTargetView.h> #include <RendererVulkan/Device/DeviceVulkan.h> #include <RendererVulkan/Device/InitContext.h> #include <RendererVulkan/Device/SwapChainVulkan.h> #include <RendererVulkan/Pools/SemaphorePoolVulkan.h> #include <RendererVulkan/Resources/TextureVulkan.h> #include <RendererVulkan/Utils/BarrierUtilsVulkan.h> #include <RendererVulkan/Utils/ConversionUtilsVulkan.h> #if EZ_ENABLED(EZ_SUPPORTS_GLFW) # include <GLFW/glfw3.h> #endif #if EZ_ENABLED(EZ_PLATFORM_LINUX) # include <xcb/xcb.h> #endif namespace { ezResult GetAlternativeFormat(ezGALResourceFormat::Enum& ref_format) { switch (ref_format) { case ezGALResourceFormat::RGBAUByteNormalizedsRGB: ref_format = ezGALResourceFormat::BGRAUByteNormalizedsRGB; return EZ_SUCCESS; case ezGALResourceFormat::RGBAUByteNormalized: ref_format = ezGALResourceFormat::BGRAUByteNormalized; return EZ_SUCCESS; case ezGALResourceFormat::BGRAUByteNormalizedsRGB: ref_format = ezGALResourceFormat::RGBAUByteNormalizedsRGB; return EZ_SUCCESS; case ezGALResourceFormat::BGRAUByteNormalized: ref_format = ezGALResourceFormat::RGBAUByteNormalized; return EZ_SUCCESS; default: return EZ_FAILURE; } } ezGALResourceFormat::Enum GetResourceFormat(vk::Format& ref_format) { switch (ref_format) { case vk::Format::eR8G8B8A8Srgb: return ezGALResourceFormat::RGBAUByteNormalizedsRGB; case vk::Format::eR8G8B8A8Unorm: return ezGALResourceFormat::RGBAUByteNormalized; case vk::Format::eB8G8R8A8Srgb: return ezGALResourceFormat::BGRAUByteNormalizedsRGB; case vk::Format::eB8G8R8A8Unorm: return ezGALResourceFormat::BGRAUByteNormalized; default: return ezGALResourceFormat::ENUM_COUNT; } } } // namespace void ezGALSwapChainVulkan::AcquireNextRenderTarget(ezGALDevice* pDevice) { EZ_PROFILE_SCOPE("AcquireNextRenderTarget"); auto pVulkanDevice = static_cast<ezGALDeviceVulkan*>(pDevice); EZ_ASSERT_DEV(!m_CurrentPipelineImageAvailableSemaphore, "Pipeline semaphores leaked"); m_CurrentPipelineImageAvailableSemaphore = ezSemaphorePoolVulkan::RequestSemaphore(); // Check if the surface extent has changed before acquiring. If it has, recreate the swapchain first. // This avoids a Vulkan spec violation: when minImageCount equals the total swapchain image count, // forward progress can't be guaranteed, and using UINT64_MAX as timeout is not allowed. // I *think* that this is no longer needed now that we don't SPAM the resize in the editor to the engine. /* { const vk::SurfaceCapabilitiesKHR surfaceCapabilities = m_pVulkanDevice->GetVulkanPhysicalDevice().getSurfaceCapabilitiesKHR(m_VulkanSurface); if (surfaceCapabilities.currentExtent.width != m_CurrentSize.width || surfaceCapabilities.currentExtent.height != m_CurrentSize.height) { if (CreateSwapChainInternal().Failed()) { ezLog::Error("Failed to recreate swapchain after surface resize"); } } } */ int retryCount = 0; while (true) { // #TODO_VULKAN We leave the fence parameter blank as we do not care on the CPU whether a swap chain image is still in use or not. Currently, we daisy-chain each frame to the previous frame's semaphore. Not sure if this won't break if we stop doing that. If we call acquireNextImageKHR, can we be sure that the image is no longer in use? vk::Result result = m_pVulkanDevice->GetVulkanDevice().acquireNextImageKHR(m_VulkanSwapChain, std::numeric_limits<uint64_t>::max(), m_CurrentPipelineImageAvailableSemaphore, nullptr, &m_uiCurrentSwapChainImage); if (result == vk::Result::eSuboptimalKHR) { const vk::SurfaceCapabilitiesKHR surfaceCapabilities = m_pVulkanDevice->GetVulkanPhysicalDevice().getSurfaceCapabilitiesKHR(m_VulkanSurface); if ((surfaceCapabilities.currentExtent.width != m_CurrentSize.width || surfaceCapabilities.currentExtent.height != m_CurrentSize.height)) { ezLog::Warning("Swap-chain does not match the target window size and should be recreated. Expected size {0}x{1}, current size {2}x{3}.", surfaceCapabilities.currentExtent.width, surfaceCapabilities.currentExtent.height, m_CurrentSize.width, m_CurrentSize.height); } break; } else if (result == vk::Result::eErrorOutOfDateKHR) { if (retryCount > 0) { ezLog::Warning("Automatic swap-chain re-creation didn't have an effect, retrying"); } if (retryCount >= 4) { EZ_REPORT_FAILURE("Automatic swap-chain re-creation didn't have an effect 4 times in a row, application can't be recovered."); } while (retryCount < 4) { // It is not a size issue, re-create automatically if (CreateSwapChainInternal().Failed()) { ezLog::Error("Failed automatic swapchain re-creation"); } else { ezLog::Debug("Automatic swapchain re-creation succeeded"); break; } retryCount++; } } else { break; } } #ifdef VK_LOG_LAYOUT_CHANGES ezLog::Warning("AcquireNextRenderTarget {}", ezArgP(static_cast<void*>(m_SwapChainImages[m_uiCurrentSwapChainImage]))); #endif m_RenderTargets.m_hRTs[0] = m_SwapChainTextures[m_uiCurrentSwapChainImage]; if (!m_DefaultLayoutApplied.IsBitSet(m_uiCurrentSwapChainImage)) { m_DefaultLayoutApplied.SetBit(m_uiCurrentSwapChainImage); ezBarrierUtilsVulkan barriers(*pVulkanDevice, pVulkanDevice->GetCurrentCommandBuffer()); const ezGALTextureVulkan* pTexture = static_cast<const ezGALTextureVulkan*>(pVulkanDevice->GetTexture(m_SwapChainTextures[m_uiCurrentSwapChainImage])); barriers.TextureBarrier(pTexture->GetImage(), pTexture->GetFullRange(), ezGALResourceState::Unknown, pTexture->GetDescription().GetDefaultState()); } pVulkanDevice->AddWaitSemaphore(ezGALDeviceVulkan::SemaphoreInfo::MakeWaitSemaphore(m_CurrentPipelineImageAvailableSemaphore, vk::PipelineStageFlagBits::eColorAttachmentOutput)); pVulkanDevice->ReclaimLater(m_CurrentPipelineImageAvailableSemaphore); } void ezGALSwapChainVulkan::PresentRenderTarget(ezGALDevice* pDevice) { EZ_PROFILE_SCOPE("PresentRenderTarget"); if (m_RenderTargets.m_hRTs[0].IsInvalidated()) return; auto pVulkanDevice = static_cast<ezGALDeviceVulkan*>(pDevice); // Submit command buffer vk::Semaphore currentPipelineRenderFinishedSemaphore = m_ImageRenderFinishedSemaphores[m_uiCurrentSwapChainImage]; pVulkanDevice->AddSignalSemaphore(ezGALDeviceVulkan::SemaphoreInfo::MakeSignalSemaphore(currentPipelineRenderFinishedSemaphore)); pVulkanDevice->Submit(); { // Present image vk::PresentInfoKHR presentInfo; presentInfo.waitSemaphoreCount = 1; presentInfo.pWaitSemaphores = ¤tPipelineRenderFinishedSemaphore; presentInfo.swapchainCount = 1; presentInfo.pSwapchains = &m_VulkanSwapChain; presentInfo.pImageIndices = &m_uiCurrentSwapChainImage; m_pVulkanDevice->GetGraphicsQueue().m_queue.presentKHR(&presentInfo); #ifdef VK_LOG_LAYOUT_CHANGES ezLog::Warning("PresentInfoKHR {}", ezArgP(m_SwapChainImages[m_uiCurrentSwapChainImage])); #endif } } ezResult ezGALSwapChainVulkan::UpdateSwapChain(ezGALDevice* pDevice, ezEnum<ezGALPresentMode> newPresentMode) { EZ_ASSERT_DEBUG(!m_CurrentPipelineImageAvailableSemaphore, "UpdateSwapChain must not be called between AcquireNextRenderTarget and PresentRenderTarget."); m_CurrentPresentMode = newPresentMode; return CreateSwapChainInternal(); } ezGALSwapChainVulkan::ezGALSwapChainVulkan(const ezGALWindowSwapChainCreationDescription& Description) : ezGALWindowSwapChain(Description) , m_VulkanSwapChain(nullptr) { } ezGALSwapChainVulkan::~ezGALSwapChainVulkan() = default; ezResult ezGALSwapChainVulkan::InitPlatform(ezGALDevice* pDevice) { m_pVulkanDevice = static_cast<ezGALDeviceVulkan*>(pDevice); m_CurrentPresentMode = m_WindowDesc.m_InitialPresentMode; #if defined(VK_USE_PLATFORM_WIN32_KHR) vk::Win32SurfaceCreateInfoKHR surfaceCreateInfo = {}; surfaceCreateInfo.hinstance = GetModuleHandle(nullptr); surfaceCreateInfo.hwnd = (HWND)m_WindowDesc.m_pWindow->GetNativeWindowHandle(); m_VulkanSurface = m_pVulkanDevice->GetVulkanInstance().createWin32SurfaceKHR(surfaceCreateInfo); #elif EZ_ENABLED(EZ_PLATFORM_LINUX) ezWindowHandle windowHandle = m_WindowDesc.m_pWindow->GetNativeWindowHandle(); switch (windowHandle.type) { case ezWindowHandle::Type::Invalid: ezLog::Error("Invalid native window handle for window \"{}\"", m_WindowDesc.m_pWindow); return EZ_FAILURE; case ezWindowHandle::Type::GLFW: { VkSurfaceKHR glfwSurface = VK_NULL_HANDLE; VK_SUCCEED_OR_RETURN_EZ_FAILURE(glfwCreateWindowSurface(m_pVulkanDevice->GetVulkanInstance(), windowHandle.glfwWindow, nullptr, &glfwSurface)); m_VulkanSurface = glfwSurface; } break; case ezWindowHandle::Type::XCB: { if (m_pVulkanDevice->GetExtensions().m_bSurfaceXcb) { vk::XcbSurfaceCreateInfoKHR surfaceCreateInfo = {}; EZ_ASSERT_DEV(windowHandle.xcbWindow.m_pConnection != nullptr && windowHandle.xcbWindow.m_Window != 0, "Invalid xcb handle"); surfaceCreateInfo.connection = windowHandle.xcbWindow.m_pConnection; surfaceCreateInfo.window = windowHandle.xcbWindow.m_Window; m_VulkanSurface = m_pVulkanDevice->GetVulkanInstance().createXcbSurfaceKHR(surfaceCreateInfo); } else { ezLog::Error("VK_KHR_xcb_surface extension is not supported!"); } } break; } #elif EZ_ENABLED(EZ_SUPPORTS_GLFW) VkSurfaceKHR glfwSurface = VK_NULL_HANDLE; VK_SUCCEED_OR_RETURN_EZ_FAILURE(glfwCreateWindowSurface(m_pVulkanDevice->GetVulkanInstance(), m_WindowDesc.m_pWindow->GetNativeWindowHandle(), nullptr, &glfwSurface)); m_VulkanSurface = glfwSurface; #elif EZ_ENABLED(EZ_PLATFORM_ANDROID) vk::AndroidSurfaceCreateInfoKHR info; info.window = reinterpret_cast<ANativeWindow*>(m_WindowDesc.m_pWindow->GetNativeWindowHandle()); VK_SUCCEED_OR_RETURN_EZ_FAILURE(m_pVulkanDevice->GetVulkanInstance().createAndroidSurfaceKHR(&info, nullptr, &m_VulkanSurface)); #else # error Platform not supported #endif if (!m_VulkanSurface) { ezLog::Error("Failed to create Vulkan surface for window \"{}\"", m_WindowDesc.m_pWindow); return EZ_FAILURE; } // We have created a surface on a window, the window must not be destroyed while the surface is still alive. m_WindowDesc.m_pWindow->AddReference(); vk::Bool32 surfaceSupported = false; VK_SUCCEED_OR_RETURN_EZ_FAILURE(m_pVulkanDevice->GetVulkanPhysicalDevice().getSurfaceSupportKHR(m_pVulkanDevice->GetGraphicsQueue().m_uiQueueFamily, m_VulkanSurface, &surfaceSupported)); if (!surfaceSupported) { ezLog::Error("Vulkan device does not support surfaces"); m_pVulkanDevice->DeleteLater(m_VulkanSurface); return EZ_FAILURE; } return CreateSwapChainInternal(); } ezResult ezGALSwapChainVulkan::CreateSwapChainInternal() { uint32_t uiPresentModes = 0; VK_SUCCEED_OR_RETURN_EZ_FAILURE(m_pVulkanDevice->GetVulkanPhysicalDevice().getSurfacePresentModesKHR(m_VulkanSurface, &uiPresentModes, nullptr)); ezHybridArray<vk::PresentModeKHR, 4> presentModes; presentModes.SetCount(uiPresentModes); VK_SUCCEED_OR_RETURN_EZ_FAILURE(m_pVulkanDevice->GetVulkanPhysicalDevice().getSurfacePresentModesKHR(m_VulkanSurface, &uiPresentModes, presentModes.GetData())); vk::SurfaceCapabilitiesKHR surfaceCapabilities; vk::Result res = m_pVulkanDevice->GetVulkanPhysicalDevice().getSurfaceCapabilitiesKHR(m_VulkanSurface, &surfaceCapabilities); if (res != vk::Result::eSuccess) { return EZ_FAILURE; } uint32_t uiNumSurfaceFormats = 0; VK_SUCCEED_OR_RETURN_EZ_FAILURE(m_pVulkanDevice->GetVulkanPhysicalDevice().getSurfaceFormatsKHR(m_VulkanSurface, &uiNumSurfaceFormats, nullptr)); std::vector<vk::SurfaceFormatKHR> supportedFormats; supportedFormats.resize(uiNumSurfaceFormats); VK_SUCCEED_OR_RETURN_EZ_FAILURE(m_pVulkanDevice->GetVulkanPhysicalDevice().getSurfaceFormatsKHR(m_VulkanSurface, &uiNumSurfaceFormats, supportedFormats.data())); vk::Format desiredFormat = m_pVulkanDevice->GetFormatLookupTable().GetFormatInfo(m_WindowDesc.m_BackBufferFormat).m_format; vk::ColorSpaceKHR desiredColorSpace = vk::ColorSpaceKHR::eSrgbNonlinear; vk::ComponentMapping backBufferComponentMapping; bool formatFound = false; for (vk::SurfaceFormatKHR& supportedFormat : supportedFormats) { if (supportedFormat.format == desiredFormat && supportedFormat.colorSpace == desiredColorSpace) { formatFound = true; break; } } if (!formatFound && GetAlternativeFormat(m_WindowDesc.m_BackBufferFormat).Succeeded()) { desiredFormat = m_pVulkanDevice->GetFormatLookupTable().GetFormatInfo(m_WindowDesc.m_BackBufferFormat).m_format; for (vk::SurfaceFormatKHR& supportedFormat : supportedFormats) { if (supportedFormat.format == desiredFormat && supportedFormat.colorSpace == desiredColorSpace) { formatFound = true; break; } } } if (!formatFound) { ezStringBuilder backBufferFormatNice = "<unknown>"; ezReflectionUtils::EnumerationToString(ezGetStaticRTTI<ezGALResourceFormat>(), m_WindowDesc.m_BackBufferFormat, backBufferFormatNice); ezLog::Error("The requested back buffer format {} mapping to the vulkan format {} is not supported on this system.", backBufferFormatNice, vk::to_string(desiredFormat).c_str()); ezLog::Info("Available formats are:"); for (vk::SurfaceFormatKHR& supportedFormat : supportedFormats) { ezLog::Info(" format: {} color space: {}", vk::to_string(supportedFormat.format).c_str(), vk::to_string(supportedFormat.colorSpace).c_str()); } return EZ_FAILURE; } // Does the device support RGBA textures or only BRGA? // Do we need to store somewhere if the texture is swizzeled? vk::SwapchainCreateInfoKHR swapChainCreateInfo = {}; swapChainCreateInfo.clipped = VK_FALSE; vk::CompositeAlphaFlagBitsKHR preferredCompositeMode[] = {vk::CompositeAlphaFlagBitsKHR::eOpaque, vk::CompositeAlphaFlagBitsKHR::eInherit, vk::CompositeAlphaFlagBitsKHR::ePreMultiplied, vk::CompositeAlphaFlagBitsKHR::ePostMultiplied}; for (auto cm : preferredCompositeMode) { if (surfaceCapabilities.supportedCompositeAlpha & cm) { swapChainCreateInfo.compositeAlpha = cm; break; } } swapChainCreateInfo.imageArrayLayers = 1; swapChainCreateInfo.imageColorSpace = desiredColorSpace; swapChainCreateInfo.imageExtent.width = m_WindowDesc.m_pWindow->GetClientAreaSize().width; swapChainCreateInfo.imageExtent.height = m_WindowDesc.m_pWindow->GetClientAreaSize().height; swapChainCreateInfo.imageExtent.width = ezMath::Clamp(swapChainCreateInfo.imageExtent.width, surfaceCapabilities.minImageExtent.width, surfaceCapabilities.maxImageExtent.width); swapChainCreateInfo.imageExtent.height = ezMath::Clamp(swapChainCreateInfo.imageExtent.height, surfaceCapabilities.minImageExtent.height, surfaceCapabilities.maxImageExtent.height); swapChainCreateInfo.imageFormat = desiredFormat; // We need eTransferDst to be able to resolve msaa textures into the backbuffer. swapChainCreateInfo.imageUsage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferDst; // eTransferSrc is needed for debugging and screenshot purposes. swapChainCreateInfo.imageUsage |= vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eSampled; // #TODO_VULKAN Using only 2 images in the swapchain may trigger the following validation error when resizing the window. To prevent this we use 3 images instead. Technically m_bDoubleBuffered now means triple buffering - a problem for another time and creating a swapchain with only 1 texture is impossible anyways on most platforms. // https://vulkan.lunarg.com/doc/view/1.3.239.0/windows/1.3-extensions/vkspec.html#VUID-vkAcquireNextImageKHR-surface-07783 swapChainCreateInfo.minImageCount = ezMath::Max(m_WindowDesc.m_bDoubleBuffered ? 3u : 2u, surfaceCapabilities.minImageCount); if (surfaceCapabilities.maxImageCount != 0) swapChainCreateInfo.minImageCount = ezMath::Min(swapChainCreateInfo.minImageCount, surfaceCapabilities.maxImageCount); swapChainCreateInfo.presentMode = ezConversionUtilsVulkan::GetPresentMode(m_CurrentPresentMode, presentModes); swapChainCreateInfo.preTransform = vk::SurfaceTransformFlagBitsKHR::eIdentity; swapChainCreateInfo.surface = m_VulkanSurface; swapChainCreateInfo.imageSharingMode = vk::SharingMode::eExclusive; swapChainCreateInfo.pQueueFamilyIndices = nullptr; swapChainCreateInfo.queueFamilyIndexCount = 0; // If the swapchain maintenance extension is available, request stretch scaling so the // presentation engine scales the image to the surface size instead of returning // VK_ERROR_OUT_OF_DATE_KHR when dimensions mismatch. vk::SwapchainPresentScalingCreateInfoKHR scalingInfo; if (m_pVulkanDevice->GetExtensions().m_bSwapchainMaintenance1) { scalingInfo.scalingBehavior = vk::PresentScalingFlagBitsKHR::eStretch; scalingInfo.pNext = swapChainCreateInfo.pNext; swapChainCreateInfo.pNext = &scalingInfo; } // We must pass in the old swap chain or NVidia will crash. swapChainCreateInfo.oldSwapchain = m_VulkanSwapChain; DestroySwapChainInternal(m_pVulkanDevice); vk::SwapchainKHR newSwapChain; VK_SUCCEED_OR_RETURN_EZ_FAILURE(m_pVulkanDevice->GetVulkanDevice().createSwapchainKHR(&swapChainCreateInfo, nullptr, &newSwapChain)); if (!newSwapChain) { ezLog::Error("Failed to create Vulkan swap chain!"); return EZ_FAILURE; } m_VulkanSwapChain = newSwapChain; ezUInt32 uiSwapChainImages = 0; VK_SUCCEED_OR_RETURN_EZ_FAILURE(m_pVulkanDevice->GetVulkanDevice().getSwapchainImagesKHR(m_VulkanSwapChain, &uiSwapChainImages, nullptr)); m_SwapChainImages.SetCount(uiSwapChainImages); VK_SUCCEED_OR_RETURN_EZ_FAILURE(m_pVulkanDevice->GetVulkanDevice().getSwapchainImagesKHR(m_VulkanSwapChain, &uiSwapChainImages, m_SwapChainImages.GetData())); // We can't use our pool here as the frame fence is not sufficient to reclaim swapchain semaphores. Thus, we create unique ones for every image. m_ImageRenderFinishedSemaphores.SetCount(uiSwapChainImages); for (ezUInt32 i = 0; i < uiSwapChainImages; ++i) { vk::SemaphoreCreateInfo semaphoreCreateInfo; VK_ASSERT_DEV(m_pVulkanDevice->GetVulkanDevice().createSemaphore(&semaphoreCreateInfo, nullptr, &m_ImageRenderFinishedSemaphores[i])); } for (ezUInt32 i = 0; i < uiSwapChainImages; i++) { m_pVulkanDevice->SetDebugName("SwapChainImage", m_SwapChainImages[i]); ezGALTextureCreationDescription TexDesc; TexDesc.m_Format = GetResourceFormat(desiredFormat); TexDesc.m_uiWidth = swapChainCreateInfo.imageExtent.width; TexDesc.m_uiHeight = swapChainCreateInfo.imageExtent.height; TexDesc.m_SampleCount = m_WindowDesc.m_SampleCount; TexDesc.m_pExisitingNativeObject = m_SwapChainImages[i]; TexDesc.m_TextureFlags = ezGALTextureUsageFlags::RenderTarget | ezGALTextureUsageFlags::ShaderResource | ezGALTextureUsageFlags::Presentable; TexDesc.m_ResourceAccess.m_bImmutable = false; m_SwapChainTextures.PushBack(m_pVulkanDevice->CreateTextureInternal(TexDesc, ezArrayPtr<ezGALSystemMemoryDescription>())); } m_DefaultLayoutApplied.SetCount(uiSwapChainImages, false); m_CurrentSize = ezSizeU32(swapChainCreateInfo.imageExtent.width, swapChainCreateInfo.imageExtent.height); m_RenderTargets.m_hRTs[0] = m_SwapChainTextures[0]; m_pVulkanDevice->s_SwapChainUpdatedEvent.Broadcast(this); return EZ_SUCCESS; } void ezGALSwapChainVulkan::DestroySwapChainInternal(ezGALDeviceVulkan* pVulkanDevice) { ezUInt32 uiSwapChainImages = m_SwapChainTextures.GetCount(); for (ezUInt32 i = 0; i < uiSwapChainImages; i++) { pVulkanDevice->DestroyTexture(m_SwapChainTextures[i]); } m_SwapChainTextures.Clear(); m_DefaultLayoutApplied.Clear(); for (vk::Semaphore& sem : m_ImageRenderFinishedSemaphores) { pVulkanDevice->DeleteLater(sem); } m_ImageRenderFinishedSemaphores.Clear(); if (m_VulkanSwapChain) { pVulkanDevice->DeleteLater(m_VulkanSwapChain); } } ezResult ezGALSwapChainVulkan::DeInitPlatform(ezGALDevice* pDevice) { ezGALDeviceVulkan* pVulkanDevice = static_cast<ezGALDeviceVulkan*>(pDevice); DestroySwapChainInternal(pVulkanDevice); if (m_VulkanSurface) { pVulkanDevice->DeleteLater(m_VulkanSurface, (void*)m_WindowDesc.m_pWindow); } return EZ_SUCCESS; }