/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "DeviceCommandContext.h"
#include "lib/bits.h"
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/containers/Span.h"
#include "ps/containers/StaticVector.h"
#include "renderer/backend/vulkan/Buffer.h"
#include "renderer/backend/vulkan/DescriptorManager.h"
#include "renderer/backend/vulkan/Device.h"
#include "renderer/backend/vulkan/Framebuffer.h"
#include "renderer/backend/vulkan/PipelineState.h"
#include "renderer/backend/vulkan/RingCommandContext.h"
#include "renderer/backend/vulkan/ShaderProgram.h"
#include "renderer/backend/vulkan/Texture.h"
#include "renderer/backend/vulkan/Utilities.h"
#include
#include
#include
namespace Renderer
{
namespace Backend
{
namespace Vulkan
{
namespace
{
constexpr uint32_t UNIFORM_BUFFER_INITIAL_SIZE = 1024 * 1024;
constexpr uint32_t FRAME_INPLACE_BUFFER_INITIAL_SIZE = 128 * 1024;
struct SBaseImageState
{
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
VkAccessFlags accessMask = 0;
VkPipelineStageFlags stageMask = 0;
};
SBaseImageState GetBaseImageState(CTexture* texture)
{
if (texture->GetUsage() & ITexture::Usage::SAMPLED)
{
return {
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT};
}
else if (texture->GetUsage() & ITexture::Usage::COLOR_ATTACHMENT)
{
return {
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
}
else if (texture->GetUsage() & ITexture::Usage::DEPTH_STENCIL_ATTACHMENT)
{
return {
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
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 {};
}
class ScopedImageLayoutTransition
{
public:
ScopedImageLayoutTransition(
CRingCommandContext& commandContext, const PS::span textures,
const VkImageLayout layout, const VkAccessFlags accessMask, const VkPipelineStageFlags stageMask)
: m_CommandContext(commandContext), m_Textures(textures), m_Layout(layout),
m_AccessMask(accessMask), m_StageMask(stageMask)
{
for (CTexture* const texture : m_Textures)
{
const auto state = GetBaseImageState(texture);
VkImageLayout oldLayout = state.layout;
if (!texture->IsInitialized())
oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
Utilities::SetTextureLayout(
m_CommandContext.GetCommandBuffer(), texture,
oldLayout, m_Layout,
state.accessMask, m_AccessMask, state.stageMask, m_StageMask);
}
}
~ScopedImageLayoutTransition()
{
for (CTexture* const texture : m_Textures)
{
const auto state = GetBaseImageState(texture);
Utilities::SetTextureLayout(
m_CommandContext.GetCommandBuffer(), texture,
m_Layout, state.layout,
m_AccessMask, state.accessMask, m_StageMask, state.stageMask);
}
}
private:
CRingCommandContext& m_CommandContext;
const PS::span m_Textures;
const VkImageLayout m_Layout = VK_IMAGE_LAYOUT_UNDEFINED;
const VkAccessFlags m_AccessMask = 0;
const VkPipelineStageFlags m_StageMask = 0;
};
template
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.
// We use a buffer in the device memory and NUMBER_OF_FRAMES_IN_FLIGHT
// times bigger buffer in the host memory.
class CDeviceCommandContext::CUploadRing
{
public:
CUploadRing(
CDevice* device, const IBuffer::Type type, const uint32_t initialCapacity);
CBuffer* GetBuffer() { return m_Buffer.get(); }
uint32_t ScheduleUpload(
VkCommandBuffer commandBuffer, const PS::span data,
const uint32_t alignment);
void ExecuteUploads(VkCommandBuffer commandBuffer);
private:
void ResizeIfNeeded(
VkCommandBuffer commandBuffer, const uint32_t dataSize);
CDevice* m_Device = nullptr;
IBuffer::Type m_Type = IBuffer::Type::VERTEX;
uint32_t m_Capacity = 0;
uint32_t m_BlockIndex = 0, m_BlockOffset = 0;
std::unique_ptr m_Buffer, m_StagingBuffer;
std::byte* m_StagingBufferMappedData = nullptr;
};
CDeviceCommandContext::CUploadRing::CUploadRing(
CDevice* device, const IBuffer::Type type, const uint32_t initialCapacity)
: m_Device(device), m_Type(type)
{
ResizeIfNeeded(VK_NULL_HANDLE, initialCapacity);
}
void CDeviceCommandContext::CUploadRing::ResizeIfNeeded(
VkCommandBuffer commandBuffer, const uint32_t dataSize)
{
const bool resizeNeeded = !m_Buffer || m_BlockOffset + dataSize > m_Capacity;
if (!resizeNeeded)
return;
if (m_Buffer && m_BlockOffset > 0)
{
ENSURE(commandBuffer != VK_NULL_HANDLE);
ExecuteUploads(commandBuffer);
}
m_Capacity = std::max(m_Capacity * 2, round_up_to_pow2(dataSize));
m_Buffer = m_Device->CreateCBuffer(
"UploadRingBuffer", m_Type, m_Capacity, IBuffer::Usage::DYNAMIC | IBuffer::Usage::TRANSFER_DST);
m_StagingBuffer = m_Device->CreateCBuffer(
"UploadRingStagingBuffer", IBuffer::Type::UPLOAD, NUMBER_OF_FRAMES_IN_FLIGHT * m_Capacity, IBuffer::Usage::DYNAMIC | IBuffer::Usage::TRANSFER_SRC);
ENSURE(m_Buffer && m_StagingBuffer);
m_StagingBufferMappedData = static_cast(m_StagingBuffer->GetMappedData());
ENSURE(m_StagingBufferMappedData);
m_BlockIndex = 0;
m_BlockOffset = 0;
}
uint32_t CDeviceCommandContext::CUploadRing::ScheduleUpload(
VkCommandBuffer commandBuffer, const PS::span data,
const uint32_t alignment)
{
ENSURE(data.size() > 0);
ENSURE(is_pow2(alignment));
m_BlockOffset = (m_BlockOffset + alignment - 1) & ~(alignment - 1);
ResizeIfNeeded(commandBuffer, data.size());
const uint32_t destination = m_BlockIndex * m_Capacity + m_BlockOffset;
const uint32_t offset = m_BlockOffset;
m_BlockOffset += data.size();
std::memcpy(m_StagingBufferMappedData + destination, data.data(), data.size());
return offset;
}
void CDeviceCommandContext::CUploadRing::ExecuteUploads(
VkCommandBuffer commandBuffer)
{
if (m_BlockOffset == 0)
return;
const VkPipelineStageFlags stageMask =
m_Type == IBuffer::Type::UNIFORM
? VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT
: VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
Utilities::SubmitBufferMemoryBarrier(
commandBuffer, m_Buffer.get(), 0, VK_WHOLE_SIZE,
VK_ACCESS_MEMORY_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
stageMask, VK_PIPELINE_STAGE_TRANSFER_BIT);
VkBufferCopy region{};
region.srcOffset = m_BlockIndex * m_Capacity;
region.dstOffset = 0;
region.size = m_BlockOffset;
vkCmdCopyBuffer(
commandBuffer,
m_StagingBuffer->GetVkBuffer(), m_Buffer->GetVkBuffer(),
1, ®ion);
Utilities::SubmitBufferMemoryBarrier(
commandBuffer, m_Buffer.get(), 0, VK_WHOLE_SIZE,
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_MEMORY_READ_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, stageMask);
m_BlockIndex = (m_BlockIndex + 1) % NUMBER_OF_FRAMES_IN_FLIGHT;
m_BlockOffset = 0;
}
// static
std::unique_ptr CDeviceCommandContext::Create(CDevice* device)
{
std::unique_ptr deviceCommandContext(new CDeviceCommandContext());
deviceCommandContext->m_Device = device;
deviceCommandContext->m_DebugScopedLabels = device->GetCapabilities().debugScopedLabels;
deviceCommandContext->m_PrependCommandContext =
device->CreateRingCommandContext(NUMBER_OF_FRAMES_IN_FLIGHT);
deviceCommandContext->m_CommandContext =
device->CreateRingCommandContext(NUMBER_OF_FRAMES_IN_FLIGHT);
deviceCommandContext->m_VertexUploadRing = std::make_unique(
device, IBuffer::Type::VERTEX, FRAME_INPLACE_BUFFER_INITIAL_SIZE);
deviceCommandContext->m_IndexUploadRing = std::make_unique(
device, IBuffer::Type::INDEX, FRAME_INPLACE_BUFFER_INITIAL_SIZE);
deviceCommandContext->m_UniformUploadRing = std::make_unique(
device, IBuffer::Type::UNIFORM, UNIFORM_BUFFER_INITIAL_SIZE);
// TODO: reduce the code duplication.
VkDescriptorPoolSize descriptorPoolSize{};
descriptorPoolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
descriptorPoolSize.descriptorCount = 1;
VkDescriptorPoolCreateInfo descriptorPoolCreateInfo{};
descriptorPoolCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
descriptorPoolCreateInfo.poolSizeCount = 1;
descriptorPoolCreateInfo.pPoolSizes = &descriptorPoolSize;
descriptorPoolCreateInfo.maxSets = 1;
ENSURE_VK_SUCCESS(vkCreateDescriptorPool(
device->GetVkDevice(), &descriptorPoolCreateInfo, nullptr, &deviceCommandContext->m_UniformDescriptorPool));
VkDescriptorSetAllocateInfo descriptorSetAllocateInfo{};
descriptorSetAllocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
descriptorSetAllocateInfo.descriptorPool = deviceCommandContext->m_UniformDescriptorPool;
descriptorSetAllocateInfo.descriptorSetCount = 1;
descriptorSetAllocateInfo.pSetLayouts = &device->GetDescriptorManager().GetUniformDescriptorSetLayout();
ENSURE_VK_SUCCESS(vkAllocateDescriptorSets(
device->GetVkDevice(), &descriptorSetAllocateInfo, &deviceCommandContext->m_UniformDescriptorSet));
CBuffer* uniformBuffer = deviceCommandContext->m_UniformUploadRing->GetBuffer();
ENSURE(uniformBuffer);
// TODO: fix the hard-coded size.
const VkDescriptorBufferInfo descriptorBufferInfos[1] =
{
{uniformBuffer->GetVkBuffer(), 0u, 512u}
};
VkWriteDescriptorSet writeDescriptorSet{};
writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
writeDescriptorSet.dstSet = deviceCommandContext->m_UniformDescriptorSet;
writeDescriptorSet.dstBinding = 0;
writeDescriptorSet.dstArrayElement = 0;
writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
writeDescriptorSet.descriptorCount = 1;
writeDescriptorSet.pBufferInfo = descriptorBufferInfos;
vkUpdateDescriptorSets(
device->GetVkDevice(), 1, &writeDescriptorSet, 0, nullptr);
CFG_GET_VAL(
"renderer.backend.vulkan.debugbarrierafterframebufferpass",
deviceCommandContext->m_DebugBarrierAfterFramebufferPass);
return deviceCommandContext;
}
CDeviceCommandContext::CDeviceCommandContext() = default;
CDeviceCommandContext::~CDeviceCommandContext()
{
VkDevice device = m_Device->GetVkDevice();
vkDeviceWaitIdle(device);
if (m_UniformDescriptorPool != VK_NULL_HANDLE)
vkDestroyDescriptorPool(device, m_UniformDescriptorPool, nullptr);
}
IDevice* CDeviceCommandContext::GetDevice()
{
return m_Device;
}
void CDeviceCommandContext::SetGraphicsPipelineState(
IGraphicsPipelineState* pipelineState)
{
ENSURE(pipelineState);
m_GraphicsPipelineState = pipelineState->As();
CShaderProgram* shaderProgram = m_GraphicsPipelineState->GetShaderProgram()->As();
if (m_ShaderProgram != shaderProgram)
{
if (m_ShaderProgram)
m_ShaderProgram->Unbind();
m_ShaderProgram = shaderProgram;
}
m_IsPipelineStateDirty = true;
}
void CDeviceCommandContext::SetComputePipelineState(
IComputePipelineState* pipelineState)
{
if (m_ShaderProgram)
m_ShaderProgram->Unbind();
ENSURE(pipelineState);
CComputePipelineState* computePipelineState = pipelineState->As();
m_ShaderProgram = computePipelineState->GetShaderProgram()->As();
m_ShaderProgram->Bind();
vkCmdBindPipeline(
m_CommandContext->GetCommandBuffer(), m_ShaderProgram->GetPipelineBindPoint(), computePipelineState->GetPipeline());
if (m_Device->GetDescriptorManager().UseDescriptorIndexing())
{
vkCmdBindDescriptorSets(
m_CommandContext->GetCommandBuffer(), m_ShaderProgram->GetPipelineBindPoint(),
m_ShaderProgram->GetPipelineLayout(), 0,
1, &m_Device->GetDescriptorManager().GetDescriptorIndexingSet(), 0, nullptr);
}
}
void CDeviceCommandContext::BlitFramebuffer(
IFramebuffer* sourceFramebuffer, IFramebuffer* destinationFramebuffer,
const Rect& sourceRegion, const Rect& destinationRegion,
const Sampler::Filter filter)
{
ENSURE(!m_InsideFramebufferPass);
ENSURE(sourceRegion.x >= 0 && sourceRegion.x + sourceRegion.width <= static_cast(sourceFramebuffer->GetWidth()));
ENSURE(sourceRegion.y >= 0 && sourceRegion.y + sourceRegion.height <= static_cast(sourceFramebuffer->GetHeight()));
ENSURE(destinationRegion.x >= 0 && destinationRegion.x + destinationRegion.width <= static_cast(destinationFramebuffer->GetWidth()));
ENSURE(destinationRegion.y >= 0 && destinationRegion.y + destinationRegion.height <= static_cast(destinationFramebuffer->GetHeight()));
TransferForEachFramebufferAttachmentPair(
*m_CommandContext,
sourceFramebuffer->As(), destinationFramebuffer->As(),
[&sourceRegion, &destinationRegion, filter](
VkCommandBuffer commandBuffer, CTexture* sourceColorAttachment, CTexture* destinationColorAttachment)
{
// 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{};
// 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 = destinationRegion.x + destinationRegion.width;
region.dstOffsets[1].y = destinationHeight - destinationRegion.y;
region.dstOffsets[1].z = 1;
region.srcSubresource.aspectMask = aspectMask;
region.srcSubresource.mipLevel = 0;
region.srcSubresource.baseArrayLayer = 0;
region.srcSubresource.layerCount = 1;
region.dstSubresource.aspectMask = aspectMask;
region.dstSubresource.mipLevel = 0;
region.dstSubresource.baseArrayLayer = 0;
region.dstSubresource.layerCount = 1;
vkCmdBlitImage(
commandBuffer,
sourceColorAttachment->GetImage(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
destinationColorAttachment->GetImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
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()->GetSampleCount() > 1);
ENSURE(destinationFramebuffer->As()->GetSampleCount() == 1);
ENSURE(sourceFramebuffer->As()->GetWidth() == destinationFramebuffer->As()->GetWidth());
ENSURE(sourceFramebuffer->As()->GetHeight() == destinationFramebuffer->As()->GetHeight());
TransferForEachFramebufferAttachmentPair(
*m_CommandContext,
sourceFramebuffer->As(), destinationFramebuffer->As(),
[](VkCommandBuffer commandBuffer, CTexture* sourceColorAttachment, CTexture* destinationColorAttachment)
{
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 = aspectMask;
region.srcSubresource.mipLevel = 0;
region.srcSubresource.baseArrayLayer = 0;
region.srcSubresource.layerCount = 1;
region.dstSubresource.aspectMask = aspectMask;
region.dstSubresource.mipLevel = 0;
region.dstSubresource.baseArrayLayer = 0;
region.dstSubresource.layerCount = 1;
vkCmdResolveImage(
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)
{
ENSURE(m_InsideFramebufferPass);
ENSURE(m_Framebuffer);
PS::StaticVector clearAttachments;
if (color)
{
ENSURE(!m_Framebuffer->GetColorAttachments().empty());
for (size_t index = 0; index < m_Framebuffer->GetColorAttachments().size(); ++index)
{
VkClearAttachment clearAttachment{};
clearAttachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
const CColor& clearColor = m_Framebuffer->GetClearColor();
clearAttachment.clearValue.color.float32[0] = clearColor.r;
clearAttachment.clearValue.color.float32[1] = clearColor.g;
clearAttachment.clearValue.color.float32[2] = clearColor.b;
clearAttachment.clearValue.color.float32[3] = clearColor.a;
clearAttachment.colorAttachment = index;
clearAttachments.emplace_back(std::move(clearAttachment));
}
}
if (depth || stencil)
{
ENSURE(m_Framebuffer->GetDepthStencilAttachment());
if (stencil)
{
const Format depthStencilFormat =
m_Framebuffer->GetDepthStencilAttachment()->GetFormat();
ENSURE(depthStencilFormat == Format::D24_UNORM_S8_UINT ||
depthStencilFormat == Format::D32_SFLOAT_S8_UINT);
}
VkClearAttachment clearAttachment{};
if (depth)
clearAttachment.aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
if (stencil)
clearAttachment.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
clearAttachment.clearValue.depthStencil.depth = 1.0f;
clearAttachment.clearValue.depthStencil.stencil = 0;
clearAttachments.emplace_back(std::move(clearAttachment));
}
VkClearRect clearRect{};
clearRect.layerCount = 1;
clearRect.rect.offset.x = 0;
clearRect.rect.offset.y = 0;
clearRect.rect.extent.width = m_Framebuffer->GetWidth();
clearRect.rect.extent.height = m_Framebuffer->GetHeight();
vkCmdClearAttachments(
m_CommandContext->GetCommandBuffer(),
clearAttachments.size(), clearAttachments.data(),
1, &clearRect);
}
void CDeviceCommandContext::BeginFramebufferPass(IFramebuffer* framebuffer)
{
ENSURE(!m_InsideFramebufferPass);
ENSURE(!m_InsideComputePass);
ENSURE(framebuffer);
m_IsPipelineStateDirty = true;
m_Framebuffer = framebuffer->As();
m_GraphicsPipelineState = nullptr;
m_VertexInputLayout = nullptr;
SetScissors(0, nullptr);
for (CTexture* colorAttachment : m_Framebuffer->GetColorAttachments())
{
if (!(colorAttachment->GetUsage() & ITexture::Usage::SAMPLED) && colorAttachment->IsInitialized())
continue;
VkImageLayout oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
if (!colorAttachment->IsInitialized())
oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
Utilities::SetTextureLayout(
m_CommandContext->GetCommandBuffer(), colorAttachment,
oldLayout,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_ACCESS_SHADER_READ_BIT,
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT);
}
CTexture* depthStencilAttachment = m_Framebuffer->GetDepthStencilAttachment();
if (depthStencilAttachment && ((depthStencilAttachment->GetUsage() & ITexture::Usage::SAMPLED) || !depthStencilAttachment->IsInitialized()))
{
VkImageLayout oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
if (!depthStencilAttachment->IsInitialized())
oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
Utilities::SetTextureLayout(
m_CommandContext->GetCommandBuffer(), depthStencilAttachment, oldLayout,
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
VK_ACCESS_SHADER_READ_BIT,
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT);
}
m_InsideFramebufferPass = true;
VkRenderPassBeginInfo renderPassBeginInfo{};
renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassBeginInfo.renderPass = m_Framebuffer->GetRenderPass();
renderPassBeginInfo.framebuffer = m_Framebuffer->GetFramebuffer();
renderPassBeginInfo.renderArea.offset = { 0, 0 };
renderPassBeginInfo.renderArea.extent = { m_Framebuffer->GetWidth(), m_Framebuffer->GetHeight() };
PS::StaticVector clearValues;
const bool needsClearValues =
m_Framebuffer->GetColorAttachmentLoadOp() == AttachmentLoadOp::CLEAR ||
(m_Framebuffer->GetDepthStencilAttachment() &&
m_Framebuffer->GetDepthStencilAttachmentLoadOp() == AttachmentLoadOp::CLEAR);
if (needsClearValues)
{
for (CTexture* colorAttachment : m_Framebuffer->GetColorAttachments())
{
UNUSED2(colorAttachment);
const CColor& clearColor = m_Framebuffer->GetClearColor();
// The four array elements of the clear color map to R, G, B, and A
// components of image formats, in order.
clearValues.emplace_back();
clearValues.back().color.float32[0] = clearColor.r;
clearValues.back().color.float32[1] = clearColor.g;
clearValues.back().color.float32[2] = clearColor.b;
clearValues.back().color.float32[3] = clearColor.a;
}
if (m_Framebuffer->GetDepthStencilAttachment())
{
clearValues.emplace_back();
clearValues.back().depthStencil.depth = 1.0f;
clearValues.back().depthStencil.stencil = 0;
}
renderPassBeginInfo.clearValueCount = clearValues.size();
renderPassBeginInfo.pClearValues = clearValues.data();
}
vkCmdBeginRenderPass(m_CommandContext->GetCommandBuffer(), &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
}
void CDeviceCommandContext::EndFramebufferPass()
{
ENSURE(m_InsideFramebufferPass);
vkCmdEndRenderPass(m_CommandContext->GetCommandBuffer());
m_InsideFramebufferPass = false;
m_BoundIndexBuffer = nullptr;
ENSURE(m_Framebuffer);
for (CTexture* colorAttachment : m_Framebuffer->GetColorAttachments())
{
if (!(colorAttachment->GetUsage() & ITexture::Usage::SAMPLED))
continue;
Utilities::SetTextureLayout(
m_CommandContext->GetCommandBuffer(), colorAttachment,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
}
CTexture* depthStencilAttachment = m_Framebuffer->GetDepthStencilAttachment();
if (depthStencilAttachment && (depthStencilAttachment->GetUsage() & ITexture::Usage::SAMPLED))
{
Utilities::SetTextureLayout(
m_CommandContext->GetCommandBuffer(), depthStencilAttachment,
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
}
m_LastBoundPipeline = VK_NULL_HANDLE;
if (m_ShaderProgram)
m_ShaderProgram->Unbind();
m_ShaderProgram = nullptr;
if (m_DebugBarrierAfterFramebufferPass)
Utilities::SubmitDebugSyncMemoryBarrier(m_CommandContext->GetCommandBuffer());
}
void CDeviceCommandContext::ReadbackFramebufferSync(
const uint32_t x, const uint32_t y, const uint32_t width, const uint32_t height,
void* data)
{
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,
const void* data, const size_t dataSize,
const uint32_t level, const uint32_t layer)
{
(m_InsideFramebufferPass ? m_PrependCommandContext : m_CommandContext)->ScheduleUpload(
texture->As(), dataFormat, data, dataSize, level, layer);
}
void CDeviceCommandContext::UploadTextureRegion(ITexture* texture, const Format dataFormat,
const void* data, const size_t dataSize,
const uint32_t xOffset, const uint32_t yOffset,
const uint32_t width, const uint32_t height,
const uint32_t level, const uint32_t layer)
{
(m_InsideFramebufferPass ? m_PrependCommandContext : m_CommandContext)->ScheduleUpload(
texture->As(), dataFormat, data, dataSize, xOffset, yOffset, width, height, level, layer);
}
void CDeviceCommandContext::UploadBuffer(IBuffer* buffer, const void* data, const uint32_t dataSize)
{
ENSURE(!m_InsideFramebufferPass);
m_CommandContext->ScheduleUpload(
buffer->As(), data, 0, dataSize);
}
void CDeviceCommandContext::UploadBuffer(IBuffer* buffer, const UploadBufferFunction& uploadFunction)
{
ENSURE(!m_InsideFramebufferPass);
m_CommandContext->ScheduleUpload(
buffer->As(), 0, buffer->As()->GetSize(), uploadFunction);
}
void CDeviceCommandContext::UploadBufferRegion(
IBuffer* buffer, const void* data, const uint32_t dataOffset, const uint32_t dataSize)
{
ENSURE(!m_InsideFramebufferPass);
m_CommandContext->ScheduleUpload(
buffer->As(), data, dataOffset, dataSize);
}
void CDeviceCommandContext::UploadBufferRegion(
IBuffer* buffer, const uint32_t dataOffset, const uint32_t dataSize,
const UploadBufferFunction& uploadFunction)
{
m_CommandContext->ScheduleUpload(
buffer->As(), dataOffset, dataSize, uploadFunction);
}
void CDeviceCommandContext::SetScissors(const uint32_t scissorCount, const Rect* scissors)
{
ENSURE(m_Framebuffer);
ENSURE(scissorCount <= 1);
VkRect2D scissor{};
if (scissorCount == 1)
{
// the x and y members of offset member of any element of pScissors must be
// greater than or equal to 0.
int32_t x = scissors[0].x;
int32_t y = m_Framebuffer->GetHeight() - scissors[0].y - scissors[0].height;
int32_t width = scissors[0].width;
int32_t height = scissors[0].height;
if (x < 0)
{
width = std::max(0, width + x);
x = 0;
}
if (y < 0)
{
height = std::max(0, height + y);
y = 0;
}
scissor.offset.x = x;
scissor.offset.y = y;
scissor.extent.width = width;
scissor.extent.height = height;
}
else
{
scissor.extent.width = m_Framebuffer->GetWidth();
scissor.extent.height = m_Framebuffer->GetHeight();
}
vkCmdSetScissor(m_CommandContext->GetCommandBuffer(), 0, 1, &scissor);
}
void CDeviceCommandContext::SetViewports(const uint32_t viewportCount, const Rect* viewports)
{
ENSURE(m_Framebuffer);
ENSURE(viewportCount == 1);
VkViewport viewport{};
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
viewport.x = static_cast(viewports[0].x);
viewport.y = static_cast(static_cast(m_Framebuffer->GetHeight()) - viewports[0].y - viewports[0].height);
viewport.width = static_cast(viewports[0].width);
viewport.height = static_cast(viewports[0].height);
vkCmdSetViewport(m_CommandContext->GetCommandBuffer(), 0, 1, &viewport);
}
void CDeviceCommandContext::SetVertexInputLayout(
IVertexInputLayout* vertexInputLayout)
{
ENSURE(vertexInputLayout);
if (m_VertexInputLayout != vertexInputLayout->As())
{
m_IsPipelineStateDirty = true;
m_VertexInputLayout = vertexInputLayout->As();
}
}
void CDeviceCommandContext::SetVertexBuffer(
const uint32_t bindingSlot, IBuffer* buffer, const uint32_t offset)
{
BindVertexBuffer(bindingSlot, buffer->As(), offset);
}
void CDeviceCommandContext::SetVertexBufferData(
const uint32_t bindingSlot, const void* data, const uint32_t dataSize)
{
// TODO: check vertex buffer alignment.
const uint32_t ALIGNMENT = 32;
const uint32_t offset = m_VertexUploadRing->ScheduleUpload(
m_PrependCommandContext->GetCommandBuffer(),
PS::span{static_cast(data), dataSize}, ALIGNMENT);
BindVertexBuffer(bindingSlot, m_VertexUploadRing->GetBuffer(), offset);
}
void CDeviceCommandContext::SetIndexBuffer(IBuffer* buffer)
{
BindIndexBuffer(buffer->As(), 0);
}
void CDeviceCommandContext::SetIndexBufferData(
const void* data, const uint32_t dataSize)
{
// TODO: check index buffer alignment.
const uint32_t ALIGNMENT = 32;
const uint32_t offset = m_IndexUploadRing->ScheduleUpload(
m_PrependCommandContext->GetCommandBuffer(),
PS::span{static_cast(data), dataSize}, ALIGNMENT);
BindIndexBuffer(m_IndexUploadRing->GetBuffer(), offset);
}
void CDeviceCommandContext::BeginPass()
{
ENSURE(m_InsideFramebufferPass);
m_InsidePass = true;
}
void CDeviceCommandContext::EndPass()
{
ENSURE(m_InsidePass);
m_InsidePass = false;
}
void CDeviceCommandContext::Draw(const uint32_t firstVertex, const uint32_t vertexCount)
{
PreDraw();
vkCmdDraw(m_CommandContext->GetCommandBuffer(), vertexCount, 1, firstVertex, 0);
}
void CDeviceCommandContext::DrawIndexed(
const uint32_t firstIndex, const uint32_t indexCount, const int32_t vertexOffset)
{
ENSURE(vertexOffset == 0);
PreDraw();
vkCmdDrawIndexed(m_CommandContext->GetCommandBuffer(), indexCount, 1, firstIndex, 0, 0);
}
void CDeviceCommandContext::DrawInstanced(
const uint32_t firstVertex, const uint32_t vertexCount,
const uint32_t firstInstance, const uint32_t instanceCount)
{
PreDraw();
vkCmdDraw(
m_CommandContext->GetCommandBuffer(), vertexCount, instanceCount, firstVertex, firstInstance);
}
void CDeviceCommandContext::DrawIndexedInstanced(
const uint32_t firstIndex, const uint32_t indexCount,
const uint32_t firstInstance, const uint32_t instanceCount,
const int32_t vertexOffset)
{
PreDraw();
vkCmdDrawIndexed(
m_CommandContext->GetCommandBuffer(), indexCount, instanceCount, firstIndex, vertexOffset, firstInstance);
}
void CDeviceCommandContext::DrawIndexedInRange(
const uint32_t firstIndex, const uint32_t indexCount,
const uint32_t UNUSED(start), const uint32_t UNUSED(end))
{
DrawIndexed(firstIndex, indexCount, 0);
}
void CDeviceCommandContext::BeginComputePass()
{
ENSURE(!m_InsideFramebufferPass);
ENSURE(!m_InsideComputePass);
m_InsideComputePass = true;
}
void CDeviceCommandContext::EndComputePass()
{
if (m_ShaderProgram)
{
m_ShaderProgram->Unbind();
m_ShaderProgram = nullptr;
}
ENSURE(m_InsideComputePass);
m_InsideComputePass = false;
}
void CDeviceCommandContext::Dispatch(
const uint32_t groupCountX,
const uint32_t groupCountY,
const uint32_t groupCountZ)
{
ENSURE(m_InsideComputePass);
m_ShaderProgram->PreDispatch(*m_CommandContext);
UpdateOutdatedConstants();
vkCmdDispatch(
m_CommandContext->GetCommandBuffer(), groupCountX, groupCountY, groupCountZ);
m_ShaderProgram->PostDispatch(*m_CommandContext);
}
void CDeviceCommandContext::SetTexture(const int32_t bindingSlot, ITexture* texture)
{
if (bindingSlot < 0)
return;
ENSURE(m_InsidePass || m_InsideComputePass);
ENSURE(texture);
CTexture* textureToBind = texture->As();
ENSURE(textureToBind->GetUsage() & ITexture::Usage::SAMPLED);
if (!m_Device->GetDescriptorManager().UseDescriptorIndexing() && m_InsidePass)
{
// We can't bind textures which are used as attachments.
const auto& colorAttachments = m_Framebuffer->GetColorAttachments();
ENSURE(std::find(
colorAttachments.begin(), colorAttachments.end(), textureToBind) == colorAttachments.end());
ENSURE(m_Framebuffer->GetDepthStencilAttachment() != textureToBind);
ENSURE(textureToBind->IsInitialized());
}
m_ShaderProgram->SetTexture(bindingSlot, textureToBind);
}
void CDeviceCommandContext::SetStorageTexture(const int32_t bindingSlot, ITexture* texture)
{
ENSURE(m_InsidePass || m_InsideComputePass);
ENSURE(texture);
CTexture* textureToBind = texture->As();
ENSURE(textureToBind->GetUsage() & ITexture::Usage::STORAGE);
m_ShaderProgram->SetStorageTexture(bindingSlot, textureToBind);
}
void CDeviceCommandContext::SetUniform(
const int32_t bindingSlot,
const float value)
{
ENSURE(m_InsidePass || m_InsideComputePass);
m_ShaderProgram->SetUniform(bindingSlot, value);
}
void CDeviceCommandContext::SetUniform(
const int32_t bindingSlot,
const float valueX, const float valueY)
{
ENSURE(m_InsidePass || m_InsideComputePass);
m_ShaderProgram->SetUniform(bindingSlot, valueX, valueY);
}
void CDeviceCommandContext::SetUniform(
const int32_t bindingSlot,
const float valueX, const float valueY,
const float valueZ)
{
ENSURE(m_InsidePass || m_InsideComputePass);
m_ShaderProgram->SetUniform(bindingSlot, valueX, valueY, valueZ);
}
void CDeviceCommandContext::SetUniform(
const int32_t bindingSlot,
const float valueX, const float valueY,
const float valueZ, const float valueW)
{
ENSURE(m_InsidePass || m_InsideComputePass);
m_ShaderProgram->SetUniform(bindingSlot, valueX, valueY, valueZ, valueW);
}
void CDeviceCommandContext::SetUniform(
const int32_t bindingSlot, PS::span values)
{
ENSURE(m_InsidePass || m_InsideComputePass);
m_ShaderProgram->SetUniform(bindingSlot, values);
}
void CDeviceCommandContext::BeginScopedLabel(const char* name)
{
if (!m_DebugScopedLabels)
return;
VkDebugUtilsLabelEXT label{};
label.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT;
label.pLabelName = name;
vkCmdBeginDebugUtilsLabelEXT(m_CommandContext->GetCommandBuffer(), &label);
}
void CDeviceCommandContext::EndScopedLabel()
{
if (!m_DebugScopedLabels)
return;
vkCmdEndDebugUtilsLabelEXT(m_CommandContext->GetCommandBuffer());
}
void CDeviceCommandContext::Flush()
{
ENSURE(!m_InsideFramebufferPass);
// TODO: fix unsafe copying when overlaping flushes/frames.
m_VertexUploadRing->ExecuteUploads(m_PrependCommandContext->GetCommandBuffer());
m_IndexUploadRing->ExecuteUploads(m_PrependCommandContext->GetCommandBuffer());
m_UniformUploadRing->ExecuteUploads(m_PrependCommandContext->GetCommandBuffer());
m_IsPipelineStateDirty = true;
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(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(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()
{
ENSURE(m_InsidePass);
ApplyPipelineStateIfDirty();
m_ShaderProgram->PreDraw(*m_CommandContext);
UpdateOutdatedConstants();
}
void CDeviceCommandContext::UpdateOutdatedConstants()
{
if (m_ShaderProgram->IsMaterialConstantsDataOutdated())
{
const VkDeviceSize alignment =
std::max(static_cast(16), m_Device->GetChoosenPhysicalDevice().properties.limits.minUniformBufferOffsetAlignment);
const uint32_t offset = m_UniformUploadRing->ScheduleUpload(
m_PrependCommandContext->GetCommandBuffer(),
PS::span{
m_ShaderProgram->GetMaterialConstantsData(),
m_ShaderProgram->GetMaterialConstantsDataSize()}, alignment);
m_ShaderProgram->UpdateMaterialConstantsData();
// TODO: maybe move inside shader program to reduce the # of bind calls.
vkCmdBindDescriptorSets(
m_CommandContext->GetCommandBuffer(), m_ShaderProgram->GetPipelineBindPoint(),
m_ShaderProgram->GetPipelineLayout(), m_Device->GetDescriptorManager().GetUniformSet(),
1, &m_UniformDescriptorSet, 1, &offset);
}
}
void CDeviceCommandContext::ApplyPipelineStateIfDirty()
{
if (!m_IsPipelineStateDirty)
return;
m_IsPipelineStateDirty = false;
ENSURE(m_GraphicsPipelineState);
ENSURE(m_VertexInputLayout);
ENSURE(m_Framebuffer);
VkPipeline pipeline = m_GraphicsPipelineState->GetOrCreatePipeline(
m_VertexInputLayout, m_Framebuffer);
ENSURE(pipeline != VK_NULL_HANDLE);
if (m_LastBoundPipeline != pipeline)
{
m_LastBoundPipeline = pipeline;
vkCmdBindPipeline(m_CommandContext->GetCommandBuffer(), m_ShaderProgram->GetPipelineBindPoint(), pipeline);
m_ShaderProgram->Bind();
if (m_Device->GetDescriptorManager().UseDescriptorIndexing())
{
vkCmdBindDescriptorSets(
m_CommandContext->GetCommandBuffer(), m_ShaderProgram->GetPipelineBindPoint(),
m_ShaderProgram->GetPipelineLayout(), 0,
1, &m_Device->GetDescriptorManager().GetDescriptorIndexingSet(), 0, nullptr);
}
}
}
void CDeviceCommandContext::BindVertexBuffer(
const uint32_t bindingSlot, CBuffer* buffer, uint32_t offset)
{
VkBuffer vertexBuffers[] = { buffer->GetVkBuffer() };
VkDeviceSize offsets[] = { offset };
vkCmdBindVertexBuffers(
m_CommandContext->GetCommandBuffer(), bindingSlot, std::size(vertexBuffers), vertexBuffers, offsets);
}
void CDeviceCommandContext::BindIndexBuffer(CBuffer* buffer, uint32_t offset)
{
if (buffer == m_BoundIndexBuffer && offset == m_BoundIndexBufferOffset)
return;
m_BoundIndexBuffer = buffer;
m_BoundIndexBufferOffset = offset;
vkCmdBindIndexBuffer(
m_CommandContext->GetCommandBuffer(), buffer->GetVkBuffer(), offset, VK_INDEX_TYPE_UINT16);
}
} // namespace Vulkan
} // namespace Backend
} // namespace Renderer