forked from 0ad/0ad
Implements framebuffer readback for Vulkan to allow screenshots.
Differential Revision: https://code.wildfiregames.com/D4940 This was SVN commit r27552.
This commit is contained in:
parent
11c9e33d0f
commit
4355c8675b
@ -404,9 +404,17 @@ void CPostprocManager::BlitOutputFramebuffer(
|
||||
|
||||
GPU_SCOPED_LABEL(deviceCommandContext, "Copy postproc to backbuffer");
|
||||
|
||||
Renderer::Backend::IFramebuffer* source =
|
||||
(m_WhichBuffer ? m_PingFramebuffer : m_PongFramebuffer).get();
|
||||
|
||||
// We blit to the backbuffer from the previous active buffer.
|
||||
// We'll have upscaling/downscaling separately.
|
||||
Renderer::Backend::IDeviceCommandContext::Rect region{};
|
||||
region.width = std::min(source->GetWidth(), destination->GetWidth());
|
||||
region.height = std::min(source->GetHeight(), destination->GetHeight());
|
||||
deviceCommandContext->BlitFramebuffer(
|
||||
destination, (m_WhichBuffer ? m_PingFramebuffer : m_PongFramebuffer).get());
|
||||
source, destination, region, region,
|
||||
Renderer::Backend::Sampler::Filter::NEAREST);
|
||||
}
|
||||
|
||||
void CPostprocManager::ApplyEffect(
|
||||
@ -690,6 +698,6 @@ void CPostprocManager::ResolveMultisampleFramebuffer(
|
||||
return;
|
||||
|
||||
GPU_SCOPED_LABEL(deviceCommandContext, "Resolve postproc multisample");
|
||||
deviceCommandContext->BlitFramebuffer(
|
||||
m_PingFramebuffer.get(), m_MultisampleFramebuffer.get());
|
||||
deviceCommandContext->ResolveFramebuffer(
|
||||
m_MultisampleFramebuffer.get(), m_PingFramebuffer.get());
|
||||
}
|
||||
|
@ -488,7 +488,7 @@ void CRenderer::RenderFrameImpl(const bool renderGUI, const bool renderLogger)
|
||||
|
||||
Renderer::Backend::IFramebuffer* framebuffer = nullptr;
|
||||
|
||||
CPostprocManager& postprocManager = g_Renderer.GetPostprocManager();
|
||||
CPostprocManager& postprocManager = GetPostprocManager();
|
||||
if (postprocManager.IsEnabled())
|
||||
{
|
||||
// We have to update the post process manager with real near/far planes
|
||||
@ -515,8 +515,8 @@ void CRenderer::RenderFrameImpl(const bool renderGUI, const bool renderLogger)
|
||||
m->deviceCommandContext->BeginFramebufferPass(framebuffer);
|
||||
|
||||
Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
|
||||
viewportRect.width = framebuffer->GetWidth();
|
||||
viewportRect.height = framebuffer->GetHeight();
|
||||
viewportRect.width = m_Width;
|
||||
viewportRect.height = m_Height;
|
||||
m->deviceCommandContext->SetViewports(1, &viewportRect);
|
||||
|
||||
g_Game->GetView()->Render(m->deviceCommandContext.get());
|
||||
@ -542,8 +542,8 @@ void CRenderer::RenderFrameImpl(const bool renderGUI, const bool renderLogger)
|
||||
m->deviceCommandContext->BeginFramebufferPass(backbuffer);
|
||||
|
||||
Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
|
||||
viewportRect.width = backbuffer->GetWidth();
|
||||
viewportRect.height = backbuffer->GetHeight();
|
||||
viewportRect.width = m_Width;
|
||||
viewportRect.height = m_Height;
|
||||
m->deviceCommandContext->SetViewports(1, &viewportRect);
|
||||
}
|
||||
|
||||
@ -571,8 +571,8 @@ void CRenderer::RenderFrameImpl(const bool renderGUI, const bool renderLogger)
|
||||
m->deviceCommandContext->BeginFramebufferPass(backbuffer);
|
||||
|
||||
Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
|
||||
viewportRect.width = backbuffer->GetWidth();
|
||||
viewportRect.height = backbuffer->GetHeight();
|
||||
viewportRect.width = m_Width;
|
||||
viewportRect.height = m_Height;
|
||||
m->deviceCommandContext->SetViewports(1, &viewportRect);
|
||||
}
|
||||
|
||||
@ -757,7 +757,9 @@ void CRenderer::RenderBigScreenShot(const bool needsPresent)
|
||||
// Adjust the camera to render the appropriate region
|
||||
if (oldCamera.GetProjectionType() == CCamera::ProjectionType::PERSPECTIVE)
|
||||
{
|
||||
projection.SetPerspectiveTile(oldCamera.GetFOV(), aspectRatio, oldCamera.GetNearPlane(), oldCamera.GetFarPlane(), tiles, tileX, tileY);
|
||||
projection.SetPerspectiveTile(
|
||||
oldCamera.GetFOV(), aspectRatio, oldCamera.GetNearPlane(), oldCamera.GetFarPlane(),
|
||||
tiles, tileX, tileY);
|
||||
}
|
||||
g_Game->GetView()->GetCamera()->SetProjection(projection);
|
||||
|
||||
|
@ -33,6 +33,7 @@ enum class Format
|
||||
R8G8B8_UNORM,
|
||||
R8G8B8A8_UNORM,
|
||||
R8G8B8A8_UINT,
|
||||
B8G8R8A8_UNORM,
|
||||
|
||||
// TODO: we need to drop legacy A8 and L8 formats as soon as we have proper
|
||||
// channel swizzling.
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "renderer/backend/Format.h"
|
||||
#include "renderer/backend/IDeviceObject.h"
|
||||
#include "renderer/backend/PipelineState.h"
|
||||
#include "renderer/backend/Sampler.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
@ -46,8 +47,30 @@ public:
|
||||
*/
|
||||
virtual void SetGraphicsPipelineState(IGraphicsPipelineState* pipelineState) = 0;
|
||||
|
||||
// TODO: maybe we should add a more common type, like CRectI.
|
||||
struct Rect
|
||||
{
|
||||
int32_t x, y;
|
||||
int32_t width, height;
|
||||
};
|
||||
/**
|
||||
* Copies source region into destination region automatically applying
|
||||
* compatible format conversion and scaling using a provided filter.
|
||||
* A backbuffer can't be a source.
|
||||
*/
|
||||
virtual void BlitFramebuffer(
|
||||
IFramebuffer* destinationFramebuffer, IFramebuffer* sourceFramebuffer) = 0;
|
||||
IFramebuffer* sourceFramebuffer, IFramebuffer* destinationFramebuffer,
|
||||
const Rect& sourceRegion, const Rect& destinationRegion,
|
||||
const Sampler::Filter filter) = 0;
|
||||
|
||||
/**
|
||||
* Resolves multisample source framebuffer attachments to destination
|
||||
* attachments. Source attachments should have a sample count > 1 and
|
||||
* destination attachments should have a sample count = 1.
|
||||
* A backbuffer can't be a source.
|
||||
*/
|
||||
virtual void ResolveFramebuffer(
|
||||
IFramebuffer* sourceFramebuffer, IFramebuffer* destinationFramebuffer) = 0;
|
||||
|
||||
/**
|
||||
* Starts a framebuffer pass, performs attachment load operations.
|
||||
@ -68,6 +91,15 @@ public:
|
||||
*/
|
||||
virtual void ClearFramebuffer(const bool color, const bool depth, const bool stencil) = 0;
|
||||
|
||||
/**
|
||||
* Readbacks the current backbuffer to data in R8G8B8_UNORM format somewhen
|
||||
* between the function call and Flush (inclusively). Because of that the
|
||||
* data pointer should be valid in that time period and have enough space
|
||||
* to fit the readback result.
|
||||
* @note this operation is very slow and should not be used regularly.
|
||||
* TODO: ideally we should do readback on Present or even asynchronously
|
||||
* but a client doesn't support that yet.
|
||||
*/
|
||||
virtual void ReadbackFramebufferSync(
|
||||
const uint32_t x, const uint32_t y, const uint32_t width, const uint32_t height,
|
||||
void* data) = 0;
|
||||
@ -90,12 +122,6 @@ public:
|
||||
IBuffer* buffer, const uint32_t dataOffset, const uint32_t dataSize,
|
||||
const UploadBufferFunction& uploadFunction) = 0;
|
||||
|
||||
// TODO: maybe we should add a more common type, like CRectI.
|
||||
struct Rect
|
||||
{
|
||||
int32_t x, y;
|
||||
int32_t width, height;
|
||||
};
|
||||
virtual void SetScissors(const uint32_t scissorCount, const Rect* scissors) = 0;
|
||||
virtual void SetViewports(const uint32_t viewportCount, const Rect* viewports) = 0;
|
||||
|
||||
|
@ -99,7 +99,12 @@ void CDeviceCommandContext::Flush()
|
||||
{
|
||||
}
|
||||
|
||||
void CDeviceCommandContext::BlitFramebuffer(IFramebuffer*, IFramebuffer*)
|
||||
void CDeviceCommandContext::BlitFramebuffer(
|
||||
IFramebuffer*, IFramebuffer*, const Rect&, const Rect&, const Sampler::Filter)
|
||||
{
|
||||
}
|
||||
|
||||
void CDeviceCommandContext::ResolveFramebuffer(IFramebuffer*, IFramebuffer*)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,12 @@ public:
|
||||
|
||||
void SetGraphicsPipelineState(IGraphicsPipelineState* pipelineState) override;
|
||||
|
||||
void BlitFramebuffer(IFramebuffer* destinationFramebuffer, IFramebuffer* sourceFramebuffer) override;
|
||||
void BlitFramebuffer(
|
||||
IFramebuffer* sourceFramebuffer, IFramebuffer* destinationFramebuffer,
|
||||
const Rect& sourceRegion, const Rect& destinationRegion,
|
||||
const Sampler::Filter filter) override;
|
||||
void ResolveFramebuffer(
|
||||
IFramebuffer* sourceFramebuffer, IFramebuffer* destinationFramebuffer) override;
|
||||
|
||||
void ClearFramebuffer(const bool color, const bool depth, const bool stencil) override;
|
||||
void BeginFramebufferPass(IFramebuffer* framebuffer) override;
|
||||
|
@ -806,7 +806,9 @@ void CDeviceCommandContext::SetGraphicsPipelineStateImpl(
|
||||
}
|
||||
|
||||
void CDeviceCommandContext::BlitFramebuffer(
|
||||
IFramebuffer* dstFramebuffer, IFramebuffer* srcFramebuffer)
|
||||
IFramebuffer* srcFramebuffer, IFramebuffer* dstFramebuffer,
|
||||
const Rect& sourceRegion, const Rect& destinationRegion,
|
||||
const Sampler::Filter filter)
|
||||
{
|
||||
ENSURE(!m_InsideFramebufferPass);
|
||||
CFramebuffer* destinationFramebuffer = dstFramebuffer->As<CFramebuffer>();
|
||||
@ -814,6 +816,9 @@ void CDeviceCommandContext::BlitFramebuffer(
|
||||
#if CONFIG2_GLES
|
||||
UNUSED2(destinationFramebuffer);
|
||||
UNUSED2(sourceFramebuffer);
|
||||
UNUSED2(destinationRegion);
|
||||
UNUSED2(sourceRegion);
|
||||
UNUSED2(filter);
|
||||
debug_warn("CDeviceCommandContext::BlitFramebuffer is not implemented for GLES");
|
||||
#else
|
||||
// Source framebuffer should not be backbuffer.
|
||||
@ -821,8 +826,34 @@ void CDeviceCommandContext::BlitFramebuffer(
|
||||
ENSURE(destinationFramebuffer != sourceFramebuffer);
|
||||
glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, sourceFramebuffer->GetHandle());
|
||||
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, destinationFramebuffer->GetHandle());
|
||||
// TODO: add more check for internal formats. And currently we don't support
|
||||
// scaling inside blit.
|
||||
// TODO: add more check for internal formats.
|
||||
glBlitFramebufferEXT(
|
||||
sourceRegion.x, sourceRegion.y, sourceRegion.width, sourceRegion.height,
|
||||
destinationRegion.x, destinationRegion.y, destinationRegion.width, destinationRegion.height,
|
||||
(sourceFramebuffer->GetAttachmentMask() & destinationFramebuffer->GetAttachmentMask()),
|
||||
filter == Sampler::Filter::LINEAR ? GL_LINEAR : GL_NEAREST);
|
||||
ogl_WarnIfError();
|
||||
#endif
|
||||
}
|
||||
|
||||
void CDeviceCommandContext::ResolveFramebuffer(
|
||||
IFramebuffer* srcFramebuffer, IFramebuffer* dstFramebuffer)
|
||||
{
|
||||
ENSURE(!m_InsideFramebufferPass);
|
||||
CFramebuffer* destinationFramebuffer = dstFramebuffer->As<CFramebuffer>();
|
||||
CFramebuffer* sourceFramebuffer = srcFramebuffer->As<CFramebuffer>();
|
||||
ENSURE(destinationFramebuffer->GetWidth() == sourceFramebuffer->GetWidth());
|
||||
ENSURE(destinationFramebuffer->GetHeight() == sourceFramebuffer->GetHeight());
|
||||
#if CONFIG2_GLES
|
||||
UNUSED2(destinationFramebuffer);
|
||||
UNUSED2(sourceFramebuffer);
|
||||
debug_warn("CDeviceCommandContext::ResolveFramebuffer is not implemented for GLES");
|
||||
#else
|
||||
// Source framebuffer should not be backbuffer.
|
||||
ENSURE(sourceFramebuffer->GetHandle() != 0);
|
||||
ENSURE(destinationFramebuffer != sourceFramebuffer);
|
||||
glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, sourceFramebuffer->GetHandle());
|
||||
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, destinationFramebuffer->GetHandle());
|
||||
glBlitFramebufferEXT(
|
||||
0, 0, sourceFramebuffer->GetWidth(), sourceFramebuffer->GetHeight(),
|
||||
0, 0, sourceFramebuffer->GetWidth(), sourceFramebuffer->GetHeight(),
|
||||
|
@ -57,7 +57,12 @@ public:
|
||||
|
||||
void SetGraphicsPipelineState(IGraphicsPipelineState* pipelineState) override;
|
||||
|
||||
void BlitFramebuffer(IFramebuffer* destinationFramebuffer, IFramebuffer* sourceFramebuffer) override;
|
||||
void BlitFramebuffer(
|
||||
IFramebuffer* sourceFramebuffer, IFramebuffer* destinationFramebuffer,
|
||||
const Rect& sourceRegion, const Rect& destinationRegion,
|
||||
const Sampler::Filter filter) override;
|
||||
void ResolveFramebuffer(
|
||||
IFramebuffer* sourceFramebuffer, IFramebuffer* destinationFramebuffer) override;
|
||||
|
||||
void BeginFramebufferPass(IFramebuffer* framebuffer) override;
|
||||
void EndFramebufferPass() override;
|
||||
|
@ -640,6 +640,8 @@ CDevice::~CDevice()
|
||||
// The order of destroying does matter to avoid use-after-free and validation
|
||||
// layers complaints.
|
||||
|
||||
m_BackbufferReadbackTexture.reset();
|
||||
|
||||
m_SubmitScheduler.reset();
|
||||
|
||||
ProcessTextureToDestroyQueue(true);
|
||||
@ -927,6 +929,7 @@ std::unique_ptr<CRingCommandContext> CDevice::CreateRingCommandContext(const siz
|
||||
|
||||
void CDevice::RecreateSwapChain()
|
||||
{
|
||||
m_BackbufferReadbackTexture.reset();
|
||||
int surfaceDrawableWidth = 0, surfaceDrawableHeight = 0;
|
||||
SDL_Vulkan_GetDrawableSize(m_Window, &surfaceDrawableWidth, &surfaceDrawableHeight);
|
||||
m_SwapChain = CSwapChain::Create(
|
||||
@ -998,6 +1001,27 @@ void CDevice::ProcessTextureToDestroyQueue(const bool ignoreFrameID)
|
||||
}
|
||||
}
|
||||
|
||||
CTexture* CDevice::GetCurrentBackbufferTexture()
|
||||
{
|
||||
return IsSwapChainValid() ? m_SwapChain->GetCurrentBackbufferTexture() : nullptr;
|
||||
}
|
||||
|
||||
CTexture* CDevice::GetOrCreateBackbufferReadbackTexture()
|
||||
{
|
||||
if (!IsSwapChainValid())
|
||||
return nullptr;
|
||||
if (!m_BackbufferReadbackTexture)
|
||||
{
|
||||
CTexture* currentBackbufferTexture = m_SwapChain->GetCurrentBackbufferTexture();
|
||||
m_BackbufferReadbackTexture = CTexture::CreateReadback(
|
||||
this, "BackbufferReadback",
|
||||
currentBackbufferTexture->GetFormat(),
|
||||
currentBackbufferTexture->GetWidth(),
|
||||
currentBackbufferTexture->GetHeight());
|
||||
}
|
||||
return m_BackbufferReadbackTexture.get();
|
||||
}
|
||||
|
||||
std::unique_ptr<IDevice> CreateDevice(SDL_Window* window)
|
||||
{
|
||||
return Vulkan::CDevice::Create(window);
|
||||
|
@ -159,6 +159,10 @@ public:
|
||||
|
||||
CDescriptorManager& GetDescriptorManager() { return *m_DescriptorManager; }
|
||||
|
||||
CTexture* GetCurrentBackbufferTexture();
|
||||
|
||||
CTexture* GetOrCreateBackbufferReadbackTexture();
|
||||
|
||||
private:
|
||||
CDevice();
|
||||
|
||||
@ -194,6 +198,7 @@ private:
|
||||
uint32_t m_GraphicsQueueFamilyIndex = std::numeric_limits<uint32_t>::max();
|
||||
|
||||
std::unique_ptr<CSwapChain> m_SwapChain;
|
||||
std::unique_ptr<CTexture> m_BackbufferReadbackTexture;
|
||||
|
||||
uint32_t m_FrameID = 0;
|
||||
|
||||
|
@ -36,6 +36,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <tuple>
|
||||
|
||||
namespace Renderer
|
||||
{
|
||||
@ -82,6 +83,14 @@ SBaseImageState GetBaseImageState(CTexture* texture)
|
||||
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
|
||||
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT};
|
||||
}
|
||||
// The only TRANSFER_DST usage means we can do only readbacks.
|
||||
else if (texture->GetUsage() == ITexture::Usage::TRANSFER_DST)
|
||||
{
|
||||
return {
|
||||
VK_IMAGE_LAYOUT_GENERAL,
|
||||
VK_ACCESS_HOST_READ_BIT,
|
||||
VK_PIPELINE_STAGE_HOST_BIT};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -130,6 +139,54 @@ private:
|
||||
const VkPipelineStageFlags m_StageMask = 0;
|
||||
};
|
||||
|
||||
template<typename TransferOp>
|
||||
void TransferForEachFramebufferAttachmentPair(
|
||||
CRingCommandContext& commandContext,
|
||||
CFramebuffer* sourceFramebuffer, CFramebuffer* destinationFramebuffer,
|
||||
TransferOp transferOp)
|
||||
{
|
||||
const auto& sourceColorAttachments =
|
||||
sourceFramebuffer->GetColorAttachments();
|
||||
const auto& destinationColorAttachments =
|
||||
destinationFramebuffer->GetColorAttachments();
|
||||
ENSURE(sourceColorAttachments.size() == destinationColorAttachments.size());
|
||||
|
||||
for (CTexture* sourceColorAttachment : sourceColorAttachments)
|
||||
ENSURE(sourceColorAttachment->GetUsage() & ITexture::Usage::TRANSFER_SRC);
|
||||
for (CTexture* destinationColorAttachment : destinationColorAttachments)
|
||||
ENSURE(destinationColorAttachment->GetUsage() & ITexture::Usage::TRANSFER_DST);
|
||||
|
||||
// TODO: combine barriers, reduce duplication, add depth.
|
||||
ScopedImageLayoutTransition scopedColorAttachmentsTransition{
|
||||
commandContext,
|
||||
{sourceColorAttachments.begin(), sourceColorAttachments.end()},
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
VK_ACCESS_TRANSFER_READ_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT};
|
||||
ScopedImageLayoutTransition destinationColorAttachmentsTransition{
|
||||
commandContext,
|
||||
{destinationColorAttachments.begin(), destinationColorAttachments.end()},
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT};
|
||||
|
||||
for (CFramebuffer::ColorAttachments::size_type index = 0; index < destinationColorAttachments.size(); ++index)
|
||||
{
|
||||
CTexture* sourceColorAttachment = sourceColorAttachments[index];
|
||||
CTexture* destinationColorAttachment = destinationColorAttachments[index];
|
||||
|
||||
transferOp(commandContext.GetCommandBuffer(), sourceColorAttachment, destinationColorAttachment);
|
||||
}
|
||||
|
||||
if (sourceFramebuffer->GetDepthStencilAttachment() && destinationFramebuffer->GetDepthStencilAttachment())
|
||||
{
|
||||
transferOp(
|
||||
commandContext.GetCommandBuffer(),
|
||||
sourceFramebuffer->GetDepthStencilAttachment(),
|
||||
destinationFramebuffer->GetDepthStencilAttachment());
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// A helper class to store consequent uploads to avoid many copy functions.
|
||||
@ -344,107 +401,97 @@ void CDeviceCommandContext::SetGraphicsPipelineState(
|
||||
m_IsPipelineStateDirty = true;
|
||||
}
|
||||
|
||||
void CDeviceCommandContext::BlitFramebuffer(IFramebuffer* destinationFramebuffer, IFramebuffer* sourceFramebuffer)
|
||||
void CDeviceCommandContext::BlitFramebuffer(
|
||||
IFramebuffer* sourceFramebuffer, IFramebuffer* destinationFramebuffer,
|
||||
const Rect& sourceRegion, const Rect& destinationRegion,
|
||||
const Sampler::Filter filter)
|
||||
{
|
||||
ENSURE(!m_InsideFramebufferPass);
|
||||
const auto& sourceColorAttachments =
|
||||
sourceFramebuffer->As<CFramebuffer>()->GetColorAttachments();
|
||||
const auto& destinationColorAttachments =
|
||||
destinationFramebuffer->As<CFramebuffer>()->GetColorAttachments();
|
||||
ENSURE(sourceColorAttachments.size() == destinationColorAttachments.size());
|
||||
// TODO: account depth.
|
||||
//ENSURE(
|
||||
// static_cast<bool>(sourceFramebuffer->As<CFramebuffer>()->GetDepthStencilAttachment()) ==
|
||||
// static_cast<bool>(destinationFramebuffer->As<CFramebuffer>()->GetDepthStencilAttachment()));
|
||||
ENSURE(sourceRegion.x >= 0 && sourceRegion.x + sourceRegion.width <= static_cast<int32_t>(sourceFramebuffer->GetWidth()));
|
||||
ENSURE(sourceRegion.y >= 0 && sourceRegion.y + sourceRegion.height <= static_cast<int32_t>(sourceFramebuffer->GetHeight()));
|
||||
ENSURE(destinationRegion.x >= 0 && destinationRegion.x + destinationRegion.width <= static_cast<int32_t>(destinationFramebuffer->GetWidth()));
|
||||
ENSURE(destinationRegion.y >= 0 && destinationRegion.y + destinationRegion.height <= static_cast<int32_t>(destinationFramebuffer->GetHeight()));
|
||||
|
||||
for (CTexture* sourceColorAttachment : sourceColorAttachments)
|
||||
{
|
||||
ENSURE(sourceColorAttachment->GetUsage() & ITexture::Usage::TRANSFER_SRC);
|
||||
}
|
||||
for (CTexture* destinationColorAttachment : destinationColorAttachments)
|
||||
{
|
||||
ENSURE(destinationColorAttachment->GetUsage() & ITexture::Usage::TRANSFER_DST);
|
||||
}
|
||||
|
||||
// TODO: combine barriers, reduce duplication, add depth.
|
||||
ScopedImageLayoutTransition scopedColorAttachmentsTransition{
|
||||
TransferForEachFramebufferAttachmentPair(
|
||||
*m_CommandContext,
|
||||
{sourceColorAttachments.begin(), sourceColorAttachments.end()},
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
VK_ACCESS_TRANSFER_READ_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT};
|
||||
ScopedImageLayoutTransition destinationColorAttachmentsTransition{
|
||||
*m_CommandContext,
|
||||
{destinationColorAttachments.begin(), destinationColorAttachments.end()},
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT};
|
||||
|
||||
// TODO: split BlitFramebuffer into ResolveFramebuffer and BlitFramebuffer.
|
||||
if (sourceFramebuffer->As<CFramebuffer>()->GetSampleCount() == 1)
|
||||
{
|
||||
// TODO: we need to check for VK_FORMAT_FEATURE_BLIT_*_BIT for used formats.
|
||||
for (CFramebuffer::ColorAttachments::size_type index = 0; index < destinationColorAttachments.size(); ++index)
|
||||
sourceFramebuffer->As<CFramebuffer>(), destinationFramebuffer->As<CFramebuffer>(),
|
||||
[&sourceRegion, &destinationRegion, filter](
|
||||
VkCommandBuffer commandBuffer, CTexture* sourceColorAttachment, CTexture* destinationColorAttachment)
|
||||
{
|
||||
CTexture* sourceColorAttachment = sourceColorAttachments[index];
|
||||
CTexture* destinationColorAttachment = destinationColorAttachments[index];
|
||||
// TODO: we need to check for VK_FORMAT_FEATURE_BLIT_*_BIT for used formats.
|
||||
|
||||
const bool isDepth = IsDepthFormat(sourceColorAttachment->GetFormat());
|
||||
const VkImageAspectFlags aspectMask = isDepth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
|
||||
VkImageBlit region{};
|
||||
region.srcOffsets[1].x = sourceColorAttachment->GetWidth();
|
||||
region.srcOffsets[1].y = sourceColorAttachment->GetHeight();
|
||||
// Currently (0, 0) is the left-bottom corner (legacy from GL), so
|
||||
// we need to adjust the regions.
|
||||
const uint32_t sourceHeight = sourceColorAttachment->GetHeight();
|
||||
const uint32_t destinationHeight = destinationColorAttachment->GetHeight();
|
||||
region.srcOffsets[0].x = sourceRegion.x;
|
||||
region.srcOffsets[0].y = sourceHeight - sourceRegion.y - sourceRegion.height;
|
||||
region.dstOffsets[0].x = destinationRegion.x;
|
||||
region.dstOffsets[0].y = destinationHeight - destinationRegion.y - destinationRegion.height;
|
||||
region.srcOffsets[1].x = sourceRegion.x + sourceRegion.width;
|
||||
region.srcOffsets[1].y = sourceHeight - sourceRegion.y;
|
||||
region.srcOffsets[1].z = 1;
|
||||
region.dstOffsets[1].x = destinationColorAttachment->GetWidth();
|
||||
region.dstOffsets[1].y = destinationColorAttachment->GetHeight();
|
||||
region.dstOffsets[1].x = destinationRegion.x + destinationRegion.width;
|
||||
region.dstOffsets[1].y = destinationHeight - destinationRegion.y;
|
||||
region.dstOffsets[1].z = 1;
|
||||
region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
region.srcSubresource.aspectMask = aspectMask;
|
||||
region.srcSubresource.mipLevel = 0;
|
||||
region.srcSubresource.baseArrayLayer = 0;
|
||||
region.srcSubresource.layerCount = 1;
|
||||
region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
region.dstSubresource.aspectMask = aspectMask;
|
||||
region.dstSubresource.mipLevel = 0;
|
||||
region.dstSubresource.baseArrayLayer = 0;
|
||||
region.dstSubresource.layerCount = 1;
|
||||
|
||||
ENSURE(sourceColorAttachment->GetImage() != VK_NULL_HANDLE);
|
||||
ENSURE(destinationColorAttachment->GetImage() != VK_NULL_HANDLE);
|
||||
vkCmdBlitImage(
|
||||
m_CommandContext->GetCommandBuffer(),
|
||||
commandBuffer,
|
||||
sourceColorAttachment->GetImage(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
destinationColorAttachment->GetImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
1, ®ion, VK_FILTER_NEAREST);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ENSURE(sourceFramebuffer->As<CFramebuffer>()->GetSampleCount() > 1);
|
||||
ENSURE(destinationFramebuffer->As<CFramebuffer>()->GetSampleCount() == 1);
|
||||
ENSURE(sourceFramebuffer->As<CFramebuffer>()->GetWidth() == destinationFramebuffer->As<CFramebuffer>()->GetWidth());
|
||||
ENSURE(sourceFramebuffer->As<CFramebuffer>()->GetHeight() == destinationFramebuffer->As<CFramebuffer>()->GetHeight());
|
||||
for (CFramebuffer::ColorAttachments::size_type index = 0; index < destinationColorAttachments.size(); ++index)
|
||||
1, ®ion, filter == Sampler::Filter::LINEAR ? VK_FILTER_LINEAR : VK_FILTER_NEAREST);
|
||||
});
|
||||
}
|
||||
|
||||
void CDeviceCommandContext::ResolveFramebuffer(
|
||||
IFramebuffer* sourceFramebuffer, IFramebuffer* destinationFramebuffer)
|
||||
{
|
||||
ENSURE(!m_InsideFramebufferPass);
|
||||
ENSURE(sourceFramebuffer->As<CFramebuffer>()->GetSampleCount() > 1);
|
||||
ENSURE(destinationFramebuffer->As<CFramebuffer>()->GetSampleCount() == 1);
|
||||
ENSURE(sourceFramebuffer->As<CFramebuffer>()->GetWidth() == destinationFramebuffer->As<CFramebuffer>()->GetWidth());
|
||||
ENSURE(sourceFramebuffer->As<CFramebuffer>()->GetHeight() == destinationFramebuffer->As<CFramebuffer>()->GetHeight());
|
||||
|
||||
TransferForEachFramebufferAttachmentPair(
|
||||
*m_CommandContext,
|
||||
sourceFramebuffer->As<CFramebuffer>(), destinationFramebuffer->As<CFramebuffer>(),
|
||||
[](VkCommandBuffer commandBuffer, CTexture* sourceColorAttachment, CTexture* destinationColorAttachment)
|
||||
{
|
||||
CTexture* sourceColorAttachment = sourceColorAttachments[index];
|
||||
CTexture* destinationColorAttachment = destinationColorAttachments[index];
|
||||
ENSURE(sourceColorAttachment->GetFormat() == destinationColorAttachment->GetFormat());
|
||||
ENSURE(!IsDepthFormat(sourceColorAttachment->GetFormat()));
|
||||
const VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
|
||||
VkImageResolve region{};
|
||||
region.extent.width = sourceColorAttachment->GetWidth();
|
||||
region.extent.height = sourceColorAttachment->GetHeight();
|
||||
region.extent.depth = 1;
|
||||
region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
region.srcSubresource.aspectMask = aspectMask;
|
||||
region.srcSubresource.mipLevel = 0;
|
||||
region.srcSubresource.baseArrayLayer = 0;
|
||||
region.srcSubresource.layerCount = 1;
|
||||
region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
region.dstSubresource.aspectMask = aspectMask;
|
||||
region.dstSubresource.mipLevel = 0;
|
||||
region.dstSubresource.baseArrayLayer = 0;
|
||||
region.dstSubresource.layerCount = 1;
|
||||
|
||||
vkCmdResolveImage(
|
||||
m_CommandContext->GetCommandBuffer(),
|
||||
commandBuffer,
|
||||
sourceColorAttachment->GetImage(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
destinationColorAttachment->GetImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
1, ®ion);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CDeviceCommandContext::ClearFramebuffer(const bool color, const bool depth, const bool stencil)
|
||||
@ -626,12 +673,18 @@ void CDeviceCommandContext::ReadbackFramebufferSync(
|
||||
const uint32_t x, const uint32_t y, const uint32_t width, const uint32_t height,
|
||||
void* data)
|
||||
{
|
||||
UNUSED2(x);
|
||||
UNUSED2(y);
|
||||
UNUSED2(width);
|
||||
UNUSED2(height);
|
||||
UNUSED2(data);
|
||||
LOGERROR("Vulkan: framebuffer readback is not implemented yet.");
|
||||
CTexture* texture = m_Device->GetCurrentBackbufferTexture();
|
||||
if (!texture)
|
||||
{
|
||||
LOGERROR("Vulkan: backbuffer is unavailable.");
|
||||
return;
|
||||
}
|
||||
if (!(texture->GetUsage() & ITexture::Usage::TRANSFER_SRC))
|
||||
{
|
||||
LOGERROR("Vulkan: backbuffer doesn't support readback.");
|
||||
return;
|
||||
}
|
||||
m_QueuedReadbacks.emplace_back(x, y, width, height, data);
|
||||
}
|
||||
|
||||
void CDeviceCommandContext::UploadTexture(ITexture* texture, const Format dataFormat,
|
||||
@ -922,8 +975,89 @@ void CDeviceCommandContext::Flush()
|
||||
|
||||
m_IsPipelineStateDirty = true;
|
||||
|
||||
m_PrependCommandContext->Flush();
|
||||
m_CommandContext->Flush();
|
||||
CTexture* backbufferReadbackTexture = m_QueuedReadbacks.empty()
|
||||
? nullptr : m_Device->GetOrCreateBackbufferReadbackTexture();
|
||||
const bool needsReadback = backbufferReadbackTexture;
|
||||
if (needsReadback)
|
||||
{
|
||||
CTexture* backbufferTexture = m_Device->GetCurrentBackbufferTexture();
|
||||
|
||||
{
|
||||
// We assume that the readback texture is in linear tiling.
|
||||
ScopedImageLayoutTransition scopedBackbufferTransition{
|
||||
*m_CommandContext,
|
||||
{&backbufferTexture, 1},
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
VK_ACCESS_TRANSFER_READ_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT};
|
||||
ScopedImageLayoutTransition scopedReadbackBackbufferTransition{
|
||||
*m_CommandContext,
|
||||
{&backbufferReadbackTexture, 1},
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT};
|
||||
VkImageCopy region{};
|
||||
region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
region.srcSubresource.layerCount = 1;
|
||||
region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
region.dstSubresource.layerCount = 1;
|
||||
region.extent.width = backbufferTexture->GetWidth();
|
||||
region.extent.height = backbufferTexture->GetHeight();
|
||||
region.extent.depth = 1;
|
||||
vkCmdCopyImage(
|
||||
m_CommandContext->GetCommandBuffer(),
|
||||
backbufferTexture->GetImage(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
backbufferReadbackTexture->GetImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
1, ®ion);
|
||||
}
|
||||
|
||||
Utilities::SubmitMemoryBarrier(
|
||||
m_CommandContext->GetCommandBuffer(),
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT);
|
||||
|
||||
m_PrependCommandContext->Flush();
|
||||
m_CommandContext->FlushAndWait();
|
||||
|
||||
VkImageSubresource subresource{};
|
||||
subresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
VkSubresourceLayout subresourceLayout{};
|
||||
vkGetImageSubresourceLayout(
|
||||
m_Device->GetVkDevice(), backbufferReadbackTexture->GetImage(), &subresource, &subresourceLayout);
|
||||
|
||||
void* mappedData = backbufferReadbackTexture->GetMappedData();
|
||||
ENSURE(mappedData);
|
||||
const uint32_t height = backbufferReadbackTexture->GetHeight();
|
||||
const auto [redOffset, greenOffset, blueOffset] = backbufferReadbackTexture->GetFormat() == Format::B8G8R8A8_UNORM
|
||||
? std::make_tuple(2, 1, 0) : std::make_tuple(0, 1, 2);
|
||||
for (const QueuedReadback& queuedReackback : m_QueuedReadbacks)
|
||||
{
|
||||
const std::byte* data = static_cast<const std::byte*>(mappedData);
|
||||
// Currently the backbuffer (0, 0) is the left-bottom corner (legacy from GL).
|
||||
data += subresourceLayout.offset + subresourceLayout.rowPitch * (height - queuedReackback.height - queuedReackback.y);
|
||||
for (uint32_t y = 0; y < queuedReackback.height; ++y)
|
||||
{
|
||||
const std::byte* row = data;
|
||||
for (uint32_t x = 0; x < queuedReackback.width; ++x)
|
||||
{
|
||||
const uint32_t sourceIndex = (queuedReackback.x + x) * 4;
|
||||
const uint32_t destinationIndex = ((queuedReackback.height - y - 1) * queuedReackback.width + x) * 3;
|
||||
std::byte* destinationPixelData = static_cast<std::byte*>(queuedReackback.data) + destinationIndex;
|
||||
destinationPixelData[0] = row[sourceIndex + redOffset];
|
||||
destinationPixelData[1] = row[sourceIndex + greenOffset];
|
||||
destinationPixelData[2] = row[sourceIndex + blueOffset];
|
||||
}
|
||||
data += subresourceLayout.rowPitch;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_PrependCommandContext->Flush();
|
||||
m_CommandContext->Flush();
|
||||
}
|
||||
|
||||
m_QueuedReadbacks.clear();
|
||||
}
|
||||
|
||||
void CDeviceCommandContext::PreDraw()
|
||||
|
@ -18,6 +18,7 @@
|
||||
#ifndef INCLUDED_RENDERER_VULKAN_DEVICECOMMANDCONTEXT
|
||||
#define INCLUDED_RENDERER_VULKAN_DEVICECOMMANDCONTEXT
|
||||
|
||||
#include "ps/containers/StaticVector.h"
|
||||
#include "renderer/backend/IBuffer.h"
|
||||
#include "renderer/backend/IDeviceCommandContext.h"
|
||||
|
||||
@ -51,7 +52,12 @@ public:
|
||||
|
||||
void SetGraphicsPipelineState(IGraphicsPipelineState* pipelineState) override;
|
||||
|
||||
void BlitFramebuffer(IFramebuffer* destinationFramebuffer, IFramebuffer* sourceFramebuffer) override;
|
||||
void BlitFramebuffer(
|
||||
IFramebuffer* destinationFramebuffer, IFramebuffer* sourceFramebuffer,
|
||||
const Rect& destinationRegion, const Rect& sourceRegion,
|
||||
const Sampler::Filter filter) override;
|
||||
void ResolveFramebuffer(
|
||||
IFramebuffer* destinationFramebuffer, IFramebuffer* sourceFramebuffer) override;
|
||||
|
||||
void ClearFramebuffer(const bool color, const bool depth, const bool stencil) override;
|
||||
void BeginFramebufferPass(IFramebuffer* framebuffer) override;
|
||||
@ -170,6 +176,16 @@ private:
|
||||
|
||||
VkDescriptorPool m_UniformDescriptorPool = VK_NULL_HANDLE;
|
||||
VkDescriptorSet m_UniformDescriptorSet = VK_NULL_HANDLE;
|
||||
|
||||
// Currently we support readbacks only from backbuffer.
|
||||
struct QueuedReadback
|
||||
{
|
||||
uint32_t x = 0, y = 0;
|
||||
uint32_t width = 0, height = 0;
|
||||
// It's a responsibility of the caller to guarantee that data is valid.
|
||||
void* data = nullptr;
|
||||
};
|
||||
PS::StaticVector<QueuedReadback, 2> m_QueuedReadbacks;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
|
@ -170,6 +170,7 @@ VkFormat FromFormat(const Format format)
|
||||
CASE(R8G8_UINT)
|
||||
CASE(R8G8B8A8_UNORM)
|
||||
CASE(R8G8B8A8_UINT)
|
||||
CASE(B8G8R8A8_UNORM)
|
||||
|
||||
CASE(R16_UNORM)
|
||||
CASE(R16_UINT)
|
||||
|
@ -122,6 +122,17 @@ void CRingCommandContext::Flush()
|
||||
m_RingIndex = (m_RingIndex + 1) % m_Ring.size();
|
||||
}
|
||||
|
||||
void CRingCommandContext::FlushAndWait()
|
||||
{
|
||||
RingItem& item = m_Ring[m_RingIndex];
|
||||
ENSURE(item.isBegan);
|
||||
|
||||
End();
|
||||
|
||||
item.handle = m_SubmitScheduler.Submit(item.commandBuffer);
|
||||
WaitUntilFree(item);
|
||||
}
|
||||
|
||||
void CRingCommandContext::ScheduleUpload(
|
||||
CTexture* texture, const Format dataFormat,
|
||||
const void* data, const size_t dataSize,
|
||||
|
@ -63,6 +63,12 @@ public:
|
||||
*/
|
||||
void Flush();
|
||||
|
||||
/**
|
||||
* The same as Flush but also waits until it's completed. It means it
|
||||
* forces SubmitScheduler to submit all previously queued work to GPU.
|
||||
*/
|
||||
void FlushAndWait();
|
||||
|
||||
/**
|
||||
* Schedules uploads until next render pass or flush.
|
||||
* @note doesn't save a command buffer returned by GetCommandBuffer during
|
||||
|
@ -62,9 +62,9 @@ public:
|
||||
|
||||
uint32_t GetFrameID() const { return m_FrameID; }
|
||||
|
||||
private:
|
||||
void Flush();
|
||||
|
||||
private:
|
||||
CDevice* m_Device = nullptr;
|
||||
VkQueue m_Queue = VK_NULL_HANDLE;
|
||||
|
||||
|
@ -371,6 +371,12 @@ CFramebuffer* CSwapChain::GetCurrentBackbuffer(
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
CTexture* CSwapChain::GetCurrentBackbufferTexture()
|
||||
{
|
||||
ENSURE(m_CurrentImageIndex != std::numeric_limits<uint32_t>::max());
|
||||
return m_Textures[m_CurrentImageIndex].get();
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
} // namespace Backend
|
||||
|
@ -62,6 +62,8 @@ public:
|
||||
const AttachmentLoadOp depthStencilAttachmentLoadOp,
|
||||
const AttachmentStoreOp depthStencilAttachmentStoreOp);
|
||||
|
||||
CTexture* GetCurrentBackbufferTexture();
|
||||
|
||||
CTexture* GetDepthTexture() { return m_DepthTexture.get(); }
|
||||
|
||||
private:
|
||||
|
@ -227,7 +227,12 @@ std::unique_ptr<CTexture> CTexture::WrapBackbufferImage(
|
||||
std::unique_ptr<CTexture> texture(new CTexture());
|
||||
texture->m_Device = device;
|
||||
|
||||
texture->m_Format = Format::UNDEFINED;
|
||||
if (format == VK_FORMAT_R8G8B8A8_UNORM)
|
||||
texture->m_Format = Format::R8G8B8A8_UNORM;
|
||||
else if (format == VK_FORMAT_B8G8R8A8_UNORM)
|
||||
texture->m_Format = Format::B8G8R8A8_UNORM;
|
||||
else
|
||||
texture->m_Format = Format::UNDEFINED;
|
||||
texture->m_Type = Type::TEXTURE_2D;
|
||||
if (usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)
|
||||
texture->m_Usage |= Usage::COLOR_ATTACHMENT;
|
||||
@ -268,6 +273,69 @@ std::unique_ptr<CTexture> CTexture::WrapBackbufferImage(
|
||||
return texture;
|
||||
}
|
||||
|
||||
// static
|
||||
std::unique_ptr<CTexture> CTexture::CreateReadback(
|
||||
CDevice* device, const char* name, const Format format,
|
||||
const uint32_t width, const uint32_t height)
|
||||
{
|
||||
std::unique_ptr<CTexture> texture(new CTexture());
|
||||
texture->m_Device = device;
|
||||
|
||||
texture->m_Format = format;
|
||||
texture->m_Type = Type::TEXTURE_2D;
|
||||
texture->m_Usage = Usage::TRANSFER_DST;
|
||||
texture->m_Width = width;
|
||||
texture->m_Height = height;
|
||||
texture->m_MIPLevelCount = 1;
|
||||
texture->m_SampleCount = 1;
|
||||
texture->m_LayerCount = 1;
|
||||
texture->m_VkFormat = Mapping::FromFormat(texture->m_Format);
|
||||
texture->m_AttachmentImageAspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
texture->m_SamplerImageAspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
|
||||
VkImageCreateInfo imageCreateInfo{};
|
||||
imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
imageCreateInfo.extent.width = width;
|
||||
imageCreateInfo.extent.height = height;
|
||||
imageCreateInfo.extent.depth = 1;
|
||||
imageCreateInfo.mipLevels = 1;
|
||||
imageCreateInfo.arrayLayers = 1;
|
||||
imageCreateInfo.format = texture->m_VkFormat;
|
||||
imageCreateInfo.samples = Mapping::FromSampleCount(1);
|
||||
imageCreateInfo.tiling = VK_IMAGE_TILING_LINEAR;
|
||||
imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
|
||||
VmaAllocationCreateInfo allocationCreateInfo{};
|
||||
allocationCreateInfo.flags |= VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;
|
||||
#ifndef NDEBUG
|
||||
allocationCreateInfo.flags |= VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT;
|
||||
allocationCreateInfo.pUserData = const_cast<char*>(name);
|
||||
#endif
|
||||
allocationCreateInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
||||
allocationCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
|
||||
const VkResult createImageResult = vmaCreateImage(
|
||||
device->GetVMAAllocator(), &imageCreateInfo, &allocationCreateInfo,
|
||||
&texture->m_Image, &texture->m_Allocation, &texture->m_AllocationInfo);
|
||||
if (createImageResult != VK_SUCCESS)
|
||||
{
|
||||
LOGERROR("Failed to create VkImage: %d", static_cast<int>(createImageResult));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!texture->m_AllocationInfo.pMappedData)
|
||||
{
|
||||
LOGERROR("Failed to map readback image.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
device->SetObjectName(VK_OBJECT_TYPE_IMAGE, texture->m_Image, name);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
CTexture::CTexture()
|
||||
{
|
||||
static uint32_t m_LastAvailableUID = 1;
|
||||
|
@ -63,6 +63,13 @@ public:
|
||||
VkImageAspectFlags GetAttachmentImageAspectMask() { return m_AttachmentImageAspectMask; }
|
||||
VkImageAspectFlags GetSamplerImageAspectMask() { return m_SamplerImageAspectMask; }
|
||||
|
||||
/**
|
||||
* @return mapped data for readback textures else returns nullptr.
|
||||
*/
|
||||
void* GetMappedData() { return m_AllocationInfo.pMappedData; }
|
||||
|
||||
VkDeviceMemory GetDeviceMemory() { return m_AllocationInfo.deviceMemory; }
|
||||
|
||||
bool IsInitialized() const { return m_Initialized; }
|
||||
void SetInitialized() { m_Initialized = true; }
|
||||
|
||||
@ -91,6 +98,10 @@ private:
|
||||
CDevice* device, const char* name, const VkImage image, const VkFormat format,
|
||||
const VkImageUsageFlags usage, const uint32_t width, const uint32_t height);
|
||||
|
||||
static std::unique_ptr<CTexture> CreateReadback(
|
||||
CDevice* device, const char* name, const Format format,
|
||||
const uint32_t width, const uint32_t height);
|
||||
|
||||
Type m_Type = Type::TEXTURE_2D;
|
||||
uint32_t m_Usage = 0;
|
||||
Format m_Format = Format::UNDEFINED;
|
||||
@ -109,6 +120,7 @@ private:
|
||||
VkSampler m_Sampler = VK_NULL_HANDLE;
|
||||
bool m_IsCompareEnabled = false;
|
||||
VmaAllocation m_Allocation{};
|
||||
VmaAllocationInfo m_AllocationInfo{};
|
||||
|
||||
UID m_UID = 0;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user