/* ** Vulkan backend ** Copyright (c) 2016-2020 Magnus Norddahl ** ** This software is provided 'as-is', without any express or implied ** warranty. In no event will the authors be held liable for any damages ** arising from the use of this software. ** ** Permission is granted to anyone to use this software for any purpose, ** including commercial applications, and to alter it and redistribute it ** freely, subject to the following restrictions: ** ** 1. The origin of this software must not be misrepresented; you must not ** claim that you wrote the original software. If you use this software ** in a product, an acknowledgment in the product documentation would be ** appreciated but is not required. ** 2. Altered source versions must be plainly marked as such, and must not be ** misrepresented as being the original software. ** 3. This notice may not be removed or altered from any source distribution. ** */ #include "volk/volk.h" #include "v_video.h" #include "m_png.h" #include "templates.h" #include "r_videoscale.h" #include "i_time.h" #include "v_text.h" #include "version.h" #include "v_draw.h" #include "hw_clock.h" #include "hw_vrmodes.h" #include "hw_cvars.h" #include "hw_skydome.h" #include "hwrenderer/data/hw_viewpointbuffer.h" #include "flatvertices.h" #include "hwrenderer/data/shaderuniforms.h" #include "hw_lightbuffer.h" #include "vk_framebuffer.h" #include "vk_buffers.h" #include "vulkan/renderer/vk_renderstate.h" #include "vulkan/renderer/vk_renderpass.h" #include "vulkan/renderer/vk_streambuffer.h" #include "vulkan/renderer/vk_postprocess.h" #include "vulkan/renderer/vk_renderbuffers.h" #include "vulkan/shaders/vk_shader.h" #include "vulkan/textures/vk_samplers.h" #include "vulkan/textures/vk_hwtexture.h" #include "vulkan/system/vk_builders.h" #include "vulkan/system/vk_swapchain.h" #include "engineerrors.h" void Draw2D(F2DDrawer *drawer, FRenderState &state); EXTERN_CVAR(Bool, r_drawvoxels) EXTERN_CVAR(Int, gl_tonemap) EXTERN_CVAR(Int, screenblocks) EXTERN_CVAR(Bool, cl_capfps) extern int rendered_commandbuffers; int current_rendered_commandbuffers; extern bool gpuStatActive; extern bool keepGpuStatActive; extern FString gpuStatOutput; VulkanFrameBuffer::VulkanFrameBuffer(void *hMonitor, bool fullscreen, VulkanDevice *dev) : Super(hMonitor, fullscreen) { device = dev; swapChain = std::make_unique(device); mSwapChainImageAvailableSemaphore.reset(new VulkanSemaphore(device)); mRenderFinishedSemaphore.reset(new VulkanSemaphore(device)); for (auto &semaphore : mSubmitSemaphore) semaphore.reset(new VulkanSemaphore(device)); for (auto &fence : mSubmitFence) fence.reset(new VulkanFence(device)); for (int i = 0; i < maxConcurrentSubmitCount; i++) mSubmitWaitFences[i] = mSubmitFence[i]->fence; } VulkanFrameBuffer::~VulkanFrameBuffer() { vkDeviceWaitIdle(device->device); // make sure the GPU is no longer using any objects before RAII tears them down // screen is already null at this point, but VkHardwareTexture::ResetAll needs it during clean up. Is there a better way we can do this? auto tmp = screen; screen = this; // All descriptors must be destroyed before the descriptor pool in renderpass manager is destroyed VkHardwareTexture::ResetAll(); VKBuffer::ResetAll(); PPResource::ResetAll(); delete MatrixBuffer; delete StreamBuffer; delete mVertexData; delete mSkyData; delete mViewpoints; delete mLights; mShadowMap.Reset(); screen = tmp; DeleteFrameObjects(); } void VulkanFrameBuffer::InitializeState() { static bool first = true; if (first) { PrintStartupLog(); first = false; } // Use the same names here as OpenGL returns. switch (device->PhysicalDevice.Properties.vendorID) { case 0x1002: vendorstring = "ATI Technologies Inc."; break; case 0x10DE: vendorstring = "NVIDIA Corporation"; break; case 0x8086: vendorstring = "Intel"; break; default: vendorstring = "Unknown"; break; } hwcaps = RFL_SHADER_STORAGE_BUFFER | RFL_BUFFER_STORAGE; glslversion = 4.50f; uniformblockalignment = (unsigned int)device->PhysicalDevice.Properties.limits.minUniformBufferOffsetAlignment; maxuniformblock = device->PhysicalDevice.Properties.limits.maxUniformBufferRange; mCommandPool.reset(new VulkanCommandPool(device, device->graphicsFamily)); mScreenBuffers.reset(new VkRenderBuffers()); mSaveBuffers.reset(new VkRenderBuffers()); mActiveRenderBuffers = mScreenBuffers.get(); mPostprocess.reset(new VkPostprocess()); mRenderPassManager.reset(new VkRenderPassManager()); mVertexData = new FFlatVertexBuffer(GetWidth(), GetHeight()); mSkyData = new FSkyVertexBuffer; mViewpoints = new HWViewpointBuffer; mLights = new FLightBuffer(); CreateFanToTrisIndexBuffer(); // To do: move this to HW renderer interface maybe? MatrixBuffer = new VkStreamBuffer(sizeof(MatricesUBO), 50000); StreamBuffer = new VkStreamBuffer(sizeof(StreamUBO), 300); mShaderManager.reset(new VkShaderManager(device)); mSamplerManager.reset(new VkSamplerManager(device)); mRenderPassManager->Init(); #ifdef __APPLE__ mRenderState.reset(new VkRenderStateMolten()); #else mRenderState.reset(new VkRenderState()); #endif if (device->graphicsTimeQueries) { QueryPoolBuilder querybuilder; querybuilder.setQueryType(VK_QUERY_TYPE_TIMESTAMP, MaxTimestampQueries); mTimestampQueryPool = querybuilder.create(device); GetDrawCommands()->resetQueryPool(mTimestampQueryPool.get(), 0, MaxTimestampQueries); } } void VulkanFrameBuffer::Update() { twoD.Reset(); Flush3D.Reset(); Flush3D.Clock(); GetPostprocess()->SetActiveRenderTarget(); Draw2D(); twod->Clear(); mRenderState->EndRenderPass(); mRenderState->EndFrame(); Flush3D.Unclock(); WaitForCommands(true); UpdateGpuStats(); Super::Update(); } void VulkanFrameBuffer::DeleteFrameObjects() { FrameDeleteList.Images.clear(); FrameDeleteList.ImageViews.clear(); FrameDeleteList.Framebuffers.clear(); FrameDeleteList.Buffers.clear(); FrameDeleteList.Descriptors.clear(); FrameDeleteList.DescriptorPools.clear(); FrameDeleteList.CommandBuffers.clear(); } void VulkanFrameBuffer::FlushCommands(VulkanCommandBuffer **commands, size_t count, bool finish, bool lastsubmit) { int currentIndex = mNextSubmit % maxConcurrentSubmitCount; if (mNextSubmit >= maxConcurrentSubmitCount) { vkWaitForFences(device->device, 1, &mSubmitFence[currentIndex]->fence, VK_TRUE, std::numeric_limits::max()); vkResetFences(device->device, 1, &mSubmitFence[currentIndex]->fence); } QueueSubmit submit; for (size_t i = 0; i < count; i++) submit.addCommandBuffer(commands[i]); if (mNextSubmit > 0) submit.addWait(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, mSubmitSemaphore[(mNextSubmit - 1) % maxConcurrentSubmitCount].get()); if (finish && presentImageIndex != 0xffffffff) { submit.addWait(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, mSwapChainImageAvailableSemaphore.get()); submit.addSignal(mRenderFinishedSemaphore.get()); } if (!lastsubmit) submit.addSignal(mSubmitSemaphore[currentIndex].get()); submit.execute(device, device->graphicsQueue, mSubmitFence[currentIndex].get()); mNextSubmit++; } void VulkanFrameBuffer::FlushCommands(bool finish, bool lastsubmit) { mRenderState->EndRenderPass(); if (mDrawCommands || mTransferCommands) { VulkanCommandBuffer *commands[2]; size_t count = 0; if (mTransferCommands) { mTransferCommands->end(); commands[count++] = mTransferCommands.get(); FrameDeleteList.CommandBuffers.push_back(std::move(mTransferCommands)); } if (mDrawCommands) { mDrawCommands->end(); commands[count++] = mDrawCommands.get(); FrameDeleteList.CommandBuffers.push_back(std::move(mDrawCommands)); } FlushCommands(commands, count, finish, lastsubmit); current_rendered_commandbuffers += (int)count; } } void VulkanFrameBuffer::WaitForCommands(bool finish) { if (finish) { Finish.Reset(); Finish.Clock(); presentImageIndex = swapChain->AcquireImage(GetClientWidth(), GetClientHeight(), mSwapChainImageAvailableSemaphore.get()); if (presentImageIndex != 0xffffffff) mPostprocess->DrawPresentTexture(mOutputLetterbox, true, false); } FlushCommands(finish, true); if (finish) { FPSLimit(); if (presentImageIndex != 0xffffffff) swapChain->QueuePresent(presentImageIndex, mRenderFinishedSemaphore.get()); } int numWaitFences = MIN(mNextSubmit, (int)maxConcurrentSubmitCount); if (numWaitFences > 0) { vkWaitForFences(device->device, numWaitFences, mSubmitWaitFences, VK_TRUE, std::numeric_limits::max()); vkResetFences(device->device, numWaitFences, mSubmitWaitFences); } DeleteFrameObjects(); mNextSubmit = 0; if (finish) { Finish.Unclock(); rendered_commandbuffers = current_rendered_commandbuffers; current_rendered_commandbuffers = 0; } } void VulkanFrameBuffer::RenderTextureView(FCanvasTexture* tex, std::function renderFunc) { auto BaseLayer = static_cast(tex->GetHardwareTexture(0, 0)); VkTextureImage *image = BaseLayer->GetImage(tex, 0, 0); VkTextureImage *depthStencil = BaseLayer->GetDepthStencil(tex); mRenderState->EndRenderPass(); VkImageTransition barrier0; barrier0.addImage(image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, true); barrier0.execute(GetDrawCommands()); mRenderState->SetRenderTarget(image, depthStencil->View.get(), image->Image->width, image->Image->height, VK_FORMAT_R8G8B8A8_UNORM, VK_SAMPLE_COUNT_1_BIT); IntRect bounds; bounds.left = bounds.top = 0; bounds.width = std::min(tex->GetWidth(), image->Image->width); bounds.height = std::min(tex->GetHeight(), image->Image->height); renderFunc(bounds); mRenderState->EndRenderPass(); VkImageTransition barrier1; barrier1.addImage(image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, false); barrier1.execute(GetDrawCommands()); mRenderState->SetRenderTarget(&GetBuffers()->SceneColor, GetBuffers()->SceneDepthStencil.View.get(), GetBuffers()->GetWidth(), GetBuffers()->GetHeight(), VK_FORMAT_R16G16B16A16_SFLOAT, GetBuffers()->GetSceneSamples()); tex->SetUpdated(true); } void VulkanFrameBuffer::PostProcessScene(bool swscene, int fixedcm, float flash, const std::function &afterBloomDrawEndScene2D) { if (!swscene) mPostprocess->BlitSceneToPostprocess(); // Copy the resulting scene to the current post process texture mPostprocess->PostProcessScene(fixedcm, flash, afterBloomDrawEndScene2D); } const char* VulkanFrameBuffer::DeviceName() const { const auto &props = device->PhysicalDevice.Properties; return props.deviceName; } void VulkanFrameBuffer::SetVSync(bool vsync) { // This is handled in VulkanSwapChain::AcquireImage. cur_vsync = vsync; } void VulkanFrameBuffer::PrecacheMaterial(FMaterial *mat, int translation) { if (mat->Source()->GetUseType() == ETextureType::SWCanvas) return; MaterialLayerInfo* layer; auto systex = static_cast(mat->GetLayer(0, translation, &layer)); systex->GetImage(layer->layerTexture, translation, layer->scaleFlags); int numLayers = mat->NumLayers(); for (int i = 1; i < numLayers; i++) { auto systex = static_cast(mat->GetLayer(i, 0, &layer)); systex->GetImage(layer->layerTexture, 0, layer->scaleFlags); } } IHardwareTexture *VulkanFrameBuffer::CreateHardwareTexture(int numchannels) { return new VkHardwareTexture(); } FMaterial* VulkanFrameBuffer::CreateMaterial(FGameTexture* tex, int scaleflags) { return new VkMaterial(tex, scaleflags); } IVertexBuffer *VulkanFrameBuffer::CreateVertexBuffer() { return new VKVertexBuffer(); } IIndexBuffer *VulkanFrameBuffer::CreateIndexBuffer() { return new VKIndexBuffer(); } IDataBuffer *VulkanFrameBuffer::CreateDataBuffer(int bindingpoint, bool ssbo, bool needsresize) { auto buffer = new VKDataBuffer(bindingpoint, ssbo, needsresize); auto fb = GetVulkanFrameBuffer(); switch (bindingpoint) { case LIGHTBUF_BINDINGPOINT: LightBufferSSO = buffer; break; case VIEWPOINT_BINDINGPOINT: ViewpointUBO = buffer; break; case LIGHTNODES_BINDINGPOINT: LightNodes = buffer; break; case LIGHTLINES_BINDINGPOINT: LightLines = buffer; break; case LIGHTLIST_BINDINGPOINT: LightList = buffer; break; case POSTPROCESS_BINDINGPOINT: break; default: break; } return buffer; } void VulkanFrameBuffer::SetTextureFilterMode() { if (mSamplerManager) { // Destroy the texture descriptors as they used the old samplers VkMaterial::ResetAllDescriptors(); mSamplerManager->SetTextureFilterMode(); } } void VulkanFrameBuffer::StartPrecaching() { // Destroy the texture descriptors to avoid problems with potentially stale textures. VkMaterial::ResetAllDescriptors(); } void VulkanFrameBuffer::BlurScene(float amount) { if (mPostprocess) mPostprocess->BlurScene(amount); } void VulkanFrameBuffer::UpdatePalette() { if (mPostprocess) mPostprocess->ClearTonemapPalette(); } FTexture *VulkanFrameBuffer::WipeStartScreen() { SetViewportRects(nullptr); auto tex = new FWrapperTexture(mScreenViewport.width, mScreenViewport.height, 1); auto systex = static_cast(tex->GetSystemTexture()); systex->CreateWipeTexture(mScreenViewport.width, mScreenViewport.height, "WipeStartScreen"); return tex; } FTexture *VulkanFrameBuffer::WipeEndScreen() { GetPostprocess()->SetActiveRenderTarget(); Draw2D(); twod->Clear(); auto tex = new FWrapperTexture(mScreenViewport.width, mScreenViewport.height, 1); auto systex = static_cast(tex->GetSystemTexture()); systex->CreateWipeTexture(mScreenViewport.width, mScreenViewport.height, "WipeEndScreen"); return tex; } void VulkanFrameBuffer::CopyScreenToBuffer(int w, int h, uint8_t *data) { VkTextureImage image; // Convert from rgba16f to rgba8 using the GPU: ImageBuilder imgbuilder; imgbuilder.setFormat(VK_FORMAT_R8G8B8A8_UNORM); imgbuilder.setUsage(VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT); imgbuilder.setSize(w, h); image.Image = imgbuilder.create(device); GetPostprocess()->BlitCurrentToImage(&image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); // Staging buffer for download BufferBuilder bufbuilder; bufbuilder.setSize(w * h * 4); bufbuilder.setUsage(VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_GPU_TO_CPU); auto staging = bufbuilder.create(device); // Copy from image to buffer VkBufferImageCopy region = {}; region.imageExtent.width = w; region.imageExtent.height = h; region.imageExtent.depth = 1; region.imageSubresource.layerCount = 1; region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; GetDrawCommands()->copyImageToBuffer(image.Image->image, image.Layout, staging->buffer, 1, ®ion); // Submit command buffers and wait for device to finish the work WaitForCommands(false); // Map and convert from rgba8 to rgb8 uint8_t *dest = (uint8_t*)data; uint8_t *pixels = (uint8_t*)staging->Map(0, w * h * 4); int dindex = 0; for (int y = 0; y < h; y++) { int sindex = (h - y - 1) * w * 4; for (int x = 0; x < w; x++) { dest[dindex] = pixels[sindex]; dest[dindex + 1] = pixels[sindex + 1]; dest[dindex + 2] = pixels[sindex + 2]; dindex += 3; sindex += 4; } } staging->Unmap(); } void VulkanFrameBuffer::SetActiveRenderTarget() { mPostprocess->SetActiveRenderTarget(); } TArray VulkanFrameBuffer::GetScreenshotBuffer(int &pitch, ESSType &color_type, float &gamma) { int w = SCREENWIDTH; int h = SCREENHEIGHT; IntRect box; box.left = 0; box.top = 0; box.width = w; box.height = h; mPostprocess->DrawPresentTexture(box, true, true); TArray ScreenshotBuffer(w * h * 3, true); CopyScreenToBuffer(w, h, ScreenshotBuffer.Data()); pitch = w * 3; color_type = SS_RGB; gamma = 1.0f; return ScreenshotBuffer; } void VulkanFrameBuffer::BeginFrame() { SetViewportRects(nullptr); mScreenBuffers->BeginFrame(screen->mScreenViewport.width, screen->mScreenViewport.height, screen->mSceneViewport.width, screen->mSceneViewport.height); mSaveBuffers->BeginFrame(SAVEPICWIDTH, SAVEPICHEIGHT, SAVEPICWIDTH, SAVEPICHEIGHT); mRenderState->BeginFrame(); mRenderPassManager->UpdateDynamicSet(); if (mNextTimestampQuery > 0) { GetDrawCommands()->resetQueryPool(mTimestampQueryPool.get(), 0, mNextTimestampQuery); mNextTimestampQuery = 0; } } void VulkanFrameBuffer::PushGroup(const FString &name) { if (!gpuStatActive) return; if (mNextTimestampQuery < VulkanFrameBuffer::MaxTimestampQueries && device->graphicsTimeQueries) { TimestampQuery q; q.name = name; q.startIndex = mNextTimestampQuery++; q.endIndex = 0; GetDrawCommands()->writeTimestamp(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, mTimestampQueryPool.get(), q.startIndex); mGroupStack.push_back(timeElapsedQueries.size()); timeElapsedQueries.push_back(q); } } void VulkanFrameBuffer::PopGroup() { if (!gpuStatActive || mGroupStack.empty()) return; TimestampQuery &q = timeElapsedQueries[mGroupStack.back()]; mGroupStack.pop_back(); if (mNextTimestampQuery < VulkanFrameBuffer::MaxTimestampQueries && device->graphicsTimeQueries) { q.endIndex = mNextTimestampQuery++; GetDrawCommands()->writeTimestamp(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, mTimestampQueryPool.get(), q.endIndex); } } void VulkanFrameBuffer::UpdateGpuStats() { uint64_t timestamps[MaxTimestampQueries]; if (mNextTimestampQuery > 0) mTimestampQueryPool->getResults(0, mNextTimestampQuery, sizeof(uint64_t) * mNextTimestampQuery, timestamps, sizeof(uint64_t), VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); double timestampPeriod = device->PhysicalDevice.Properties.limits.timestampPeriod; gpuStatOutput = ""; for (auto &q : timeElapsedQueries) { if (q.endIndex <= q.startIndex) continue; int64_t timeElapsed = std::max(static_cast(timestamps[q.endIndex] - timestamps[q.startIndex]), (int64_t)0); double timeNS = timeElapsed * timestampPeriod; FString out; out.Format("%s=%04.2f ms\n", q.name.GetChars(), timeNS / 1000000.0f); gpuStatOutput += out; } timeElapsedQueries.clear(); mGroupStack.clear(); gpuStatActive = keepGpuStatActive; keepGpuStatActive = false; } void VulkanFrameBuffer::Draw2D() { ::Draw2D(twod, *mRenderState); } VulkanCommandBuffer *VulkanFrameBuffer::GetTransferCommands() { if (!mTransferCommands) { mTransferCommands = mCommandPool->createBuffer(); mTransferCommands->SetDebugName("VulkanFrameBuffer.mTransferCommands"); mTransferCommands->begin(); } return mTransferCommands.get(); } VulkanCommandBuffer *VulkanFrameBuffer::GetDrawCommands() { if (!mDrawCommands) { mDrawCommands = mCommandPool->createBuffer(); mDrawCommands->SetDebugName("VulkanFrameBuffer.mDrawCommands"); mDrawCommands->begin(); } return mDrawCommands.get(); } unsigned int VulkanFrameBuffer::GetLightBufferBlockSize() const { return mLights->GetBlockSize(); } void VulkanFrameBuffer::PrintStartupLog() { const auto &props = device->PhysicalDevice.Properties; FString deviceType; switch (props.deviceType) { case VK_PHYSICAL_DEVICE_TYPE_OTHER: deviceType = "other"; break; case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: deviceType = "integrated gpu"; break; case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: deviceType = "discrete gpu"; break; case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: deviceType = "virtual gpu"; break; case VK_PHYSICAL_DEVICE_TYPE_CPU: deviceType = "cpu"; break; default: deviceType.Format("%d", (int)props.deviceType); break; } FString apiVersion, driverVersion; apiVersion.Format("%d.%d.%d", VK_VERSION_MAJOR(props.apiVersion), VK_VERSION_MINOR(props.apiVersion), VK_VERSION_PATCH(props.apiVersion)); driverVersion.Format("%d.%d.%d", VK_VERSION_MAJOR(props.driverVersion), VK_VERSION_MINOR(props.driverVersion), VK_VERSION_PATCH(props.driverVersion)); Printf("Vulkan device: " TEXTCOLOR_ORANGE "%s\n", props.deviceName); Printf("Vulkan device type: %s\n", deviceType.GetChars()); Printf("Vulkan version: %s (api) %s (driver)\n", apiVersion.GetChars(), driverVersion.GetChars()); Printf(PRINT_LOG, "Vulkan extensions:"); for (const VkExtensionProperties &p : device->PhysicalDevice.Extensions) { Printf(PRINT_LOG, " %s", p.extensionName); } Printf(PRINT_LOG, "\n"); const auto &limits = props.limits; Printf("Max. texture size: %d\n", limits.maxImageDimension2D); Printf("Max. uniform buffer range: %d\n", limits.maxUniformBufferRange); Printf("Min. uniform buffer offset alignment: %llu\n", limits.minUniformBufferOffsetAlignment); } void VulkanFrameBuffer::CreateFanToTrisIndexBuffer() { TArray data; for (int i = 2; i < 1000; i++) { data.Push(0); data.Push(i - 1); data.Push(i); } FanToTrisIndexBuffer.reset(CreateIndexBuffer()); FanToTrisIndexBuffer->SetData(sizeof(uint32_t) * data.Size(), data.Data()); } void VulkanFrameBuffer::UpdateShadowMap() { mPostprocess->UpdateShadowMap(); } void VulkanFrameBuffer::SetSaveBuffers(bool yes) { if (yes) mActiveRenderBuffers = mSaveBuffers.get(); else mActiveRenderBuffers = mScreenBuffers.get(); } void VulkanFrameBuffer::ImageTransitionScene(bool unknown) { mPostprocess->ImageTransitionScene(unknown); } FRenderState* VulkanFrameBuffer::RenderState() { return mRenderState.get(); } void VulkanFrameBuffer::AmbientOccludeScene(float m5) { mPostprocess->AmbientOccludeScene(m5); } void VulkanFrameBuffer::SetSceneRenderTarget(bool useSSAO) { mRenderState->SetRenderTarget(&GetBuffers()->SceneColor, GetBuffers()->SceneDepthStencil.View.get(), GetBuffers()->GetWidth(), GetBuffers()->GetHeight(), VK_FORMAT_R16G16B16A16_SFLOAT, GetBuffers()->GetSceneSamples()); }