[Feat] (MG_Backend/DirectVulkan): new VkRenderPassManager and a bunch of changes accordingly

This commit is contained in:
2026-02-28 17:37:45 +08:00
parent 8adf1d3b8a
commit 6ff97a9bb1
14 changed files with 857 additions and 1048 deletions
+2 -2
View File
@@ -162,6 +162,7 @@ set(SOURCE_FILES
MobileGL/MG_Util/Converters/GLToMG/RenderStateEnumConverter.cpp
MobileGL/MG_Util/Converters/GLToMG/ProgramEnumConverter.cpp
MobileGL/MG_Util/Converters/MGToMG/TextureEnumConverter.cpp
MobileGL/MG_Util/Converters/MGToVk/TextureEnumConverter.cpp
MobileGL/MG_Util/Classifiers/TextureEnumClassifier.cpp
@@ -225,11 +226,10 @@ set(SOURCE_FILES
MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.cpp
MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp
MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp
MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderTargetManager.cpp
MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp
MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp
MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.cpp
MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp
MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp
MobileGL/MG_State/GLState/Core.cpp
MobileGL/MG_State/GLState/ErrorState/Error.cpp
@@ -8,7 +8,6 @@
#include "UniformDescriptorBinder.h"
#include "VkRenderTargetManager.h"
#include "MG_State/GLState/Core.h"
#include "MG_State/GLState/ProgramState/ProgramObject.h"
#include "MG_Util/ShaderTranspiler/Types.h"
@@ -138,8 +137,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool UniformDescriptorBinder::Initialize(VkDevice device, VmaAllocator allocator,
VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount,
Uint32 maxBindings, Uint32 setsPerFrame, VkDeviceSize perFrameUploadBytes,
VkTextureManager* textureManager, VkSamplerManager* samplerManager,
VkRenderTargetManager* framebufferManager) {
VkTextureManager* textureManager, VkSamplerManager* samplerManager) {
Shutdown();
MOBILEGL_ASSERT(device != VK_NULL_HANDLE, "UniformDescriptorBinder::Initialize requires valid VkDevice");
@@ -151,8 +149,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
"UniformDescriptorBinder::Initialize requires valid texture manager");
MOBILEGL_ASSERT(samplerManager != nullptr,
"UniformDescriptorBinder::Initialize requires valid sampler manager");
MOBILEGL_ASSERT(framebufferManager != nullptr,
"UniformDescriptorBinder::Initialize requires valid framebuffer manager");
m_device = device;
m_allocator = allocator;
@@ -164,7 +160,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_peakDescriptorSetsObserved = 0;
m_textureManager = textureManager;
m_samplerManager = samplerManager;
m_framebufferManager = framebufferManager;
m_frames.resize(m_frameCount);
for (Uint32 frameIndex = 0; frameIndex < m_frameCount; ++frameIndex) {
@@ -228,7 +223,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_peakDescriptorSetsObserved = 0;
m_textureManager = nullptr;
m_samplerManager = nullptr;
m_framebufferManager = nullptr;
}
void UniformDescriptorBinder::BeginFrame(Uint32 frameIndex) {
@@ -451,6 +445,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const MG_State::GLState::ProgramObject& program,
const ProgramLayout& layout, Uint32 binding,
VkDescriptorImageInfo& outImageInfo) const {
(void)commandBuffer;
MOBILEGL_ASSERT(m_textureManager != nullptr, "ResolveSamplerDescriptor: texture manager is null");
MOBILEGL_ASSERT(m_samplerManager != nullptr, "ResolveSamplerDescriptor: sampler manager is null");
if (!MG_State::pGLContext || binding >= layout.samplerUniformLocationByBinding.size()) {
@@ -481,36 +476,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return false;
}
VkImageView offscreenView = VK_NULL_HANDLE;
VkImage offscreenImage = VK_NULL_HANDLE;
VkImageLayout* offscreenLayout = nullptr;
if (m_framebufferManager->GetOffscreenColorTargetStateByTexture(texture->GetExternalIndex(), offscreenView,
offscreenImage, offscreenLayout) &&
offscreenView != VK_NULL_HANDLE && offscreenLayout != nullptr) {
const Bool fromUndefined = (*offscreenLayout == VK_IMAGE_LAYOUT_UNDEFINED);
if (VkTextureManager::TransitionImageLayout(
commandBuffer, offscreenImage, *offscreenLayout, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
fromUndefined
? VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT
: (VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT),
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
fromUndefined ? 0 : (VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT),
VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_ASPECT_COLOR_BIT)) {
VkDescriptorImageInfo sampledInfo{};
if (!m_textureManager->SyncTextureAndGetDescriptor(*texture, sampledInfo)) {
return false;
}
sampledInfo.sampler = m_samplerManager->GetOrCreateSampler(*samplerToUse);
if (sampledInfo.sampler == VK_NULL_HANDLE) {
return false;
}
sampledInfo.imageView = offscreenView;
sampledInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
outImageInfo = sampledInfo;
return true;
}
}
if (!m_textureManager->SyncTextureAndGetDescriptor(*texture, outImageInfo)) {
return false;
}
@@ -20,8 +20,6 @@ namespace MobileGL::MG_State::GLState {
}
namespace MobileGL::MG_Backend::DirectVulkan {
class VkRenderTargetManager;
class UniformDescriptorBinder {
public:
enum class BindingKind : Uint8 {
@@ -33,8 +31,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool Initialize(VkDevice device, VmaAllocator allocator, VkDeviceSize minUniformBufferOffsetAlignment,
Uint32 frameCount, Uint32 maxBindings = 16, Uint32 setsPerFrame = 64,
VkDeviceSize perFrameUploadBytes = 4 * 1024 * 1024,
VkTextureManager* textureManager = nullptr, VkSamplerManager* samplerManager = nullptr,
VkRenderTargetManager* framebufferManager = nullptr);
VkTextureManager* textureManager = nullptr, VkSamplerManager* samplerManager = nullptr);
void Shutdown();
void BeginFrame(Uint32 frameIndex);
@@ -100,7 +97,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint32 m_peakDescriptorSetsObserved = 0;
VkTextureManager* m_textureManager = nullptr;
VkSamplerManager* m_samplerManager = nullptr;
VkRenderTargetManager* m_framebufferManager = nullptr;
};
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -61,14 +61,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_pendingClears[pTexture] = clearPayload;
}
Bool VkClearManager::DequeueClear(MG_State::GLState::ITextureObject* texture, ClearAttachmentPayload& outPayload) {
if (m_aliveObjects.find(texture) == m_aliveObjects.end())
Bool VkClearManager::HasPendingClear(MG_State::GLState::ITextureObject* texture) {
return m_pendingClears.find(texture) != m_pendingClears.end();
}
Bool VkClearManager::GetPendingClear(MG_State::GLState::ITextureObject* texture, ClearAttachmentPayload& outPayload) {
if (m_aliveObjects.find(texture) == m_aliveObjects.end() ||
m_pendingClears.find(texture) == m_pendingClears.end()) {
return false;
}
outPayload = m_pendingClears[texture];
return true;
}
void VkClearManager::PopPendingClear(MG_State::GLState::ITextureObject* texture) {
m_aliveObjects.erase(texture);
m_pendingClears.erase(texture);
return true;
}
SizeT VkClearManager::CollectGarbage() {
@@ -40,7 +40,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void QueueClear(
const ClearAttachmentPayload& clearPayload,
const SharedPtr<MG_State::GLState::ITextureObject>& texture);
Bool DequeueClear(MG_State::GLState::ITextureObject* texture, ClearAttachmentPayload& outPayload);
Bool HasPendingClear(MG_State::GLState::ITextureObject* texture);
Bool GetPendingClear(MG_State::GLState::ITextureObject* texture, ClearAttachmentPayload& outPayload);
void PopPendingClear(MG_State::GLState::ITextureObject* texture);
SizeT CollectGarbage();
private:
UnorderedMap<MG_State::GLState::ITextureObject*, ClearAttachmentPayload> m_pendingClears;
@@ -8,327 +8,234 @@
#include "VkRenderPassManager.h"
#include "MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h"
#include "MG_State/GLState/TextureState/TextureObject2D.h"
#include "MG_Util/Converters/MGToVk/TextureEnumConverter.h"
namespace MobileGL::MG_Backend::DirectVulkan {
Bool VkRenderPassManager::Initialize(const InitInfo& initInfo) {
Shutdown();
VkRenderPassManager::VkRenderPassManager(VkDevice device,
const VulkanRendererConfig& config, VkClearManager& clearManager, VkTextureManager& textureManager):
m_device(device), m_config(config), m_clearManager(clearManager), m_textureManager(textureManager) {
RenderPassEntry::s_device = m_device;
}
if (initInfo.device == VK_NULL_HANDLE || initInfo.colorFormat == VK_FORMAT_UNDEFINED ||
initInfo.depthStencilFormat == VK_FORMAT_UNDEFINED) {
return false;
}
VkRenderPassManager::~VkRenderPassManager() {}
m_device = initInfo.device;
m_colorFormat = initInfo.colorFormat;
m_depthStencilFormat = initInfo.depthStencilFormat;
m_renderPassLoad = CreateDefaultRenderPass(VK_ATTACHMENT_LOAD_OP_LOAD);
MGLOG_D("VkRenderPassManager: RenderPasses created (LOAD/CLEAR).");
return m_renderPassLoad != VK_NULL_HANDLE;
Bool VkRenderPassManager::Initialize() {
return true;
}
void VkRenderPassManager::Shutdown() {
DestroyDefaultFramebuffers();
for (auto& [_, target] : m_offscreenRenderTargets) {
DestroyOffscreenRenderTarget(target);
}
m_offscreenRenderTargets.clear();
}
if (m_device != VK_NULL_HANDLE) {
if (m_renderPassLoad != VK_NULL_HANDLE) {
vkDestroyRenderPass(m_device, m_renderPassLoad, nullptr);
VkRenderPassManager::HashType VkRenderPassManager::ComputeHash(
const MG_State::GLState::FramebufferObject& fbo) const {
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion));
auto& drawBuffers = fbo.GetDrawBuffers();
XXHASH_VERIFY(XXH64_update(m_hashState, drawBuffers.data(), drawBuffers.size() * sizeof(drawBuffers[0])));
auto readBuffer = fbo.GetReadBuffer();
XXHASH_VERIFY(XXH64_update(m_hashState, &readBuffer, sizeof(FramebufferAttachmentType)));
Int validDrawBufCount = 0;
for (Int i = 0; i < drawBuffers.size(); ++i) {
auto drawbuf = drawBuffers[i];
if (drawbuf != FramebufferAttachmentType::None)
validDrawBufCount = std::max(validDrawBufCount, i + 1);
}
XXHASH_VERIFY(XXH64_update(m_hashState, &validDrawBufCount, sizeof(validDrawBufCount)));
auto combineFramebufferAttachmentObjHash = [&](FramebufferAttachmentType attachment) {
auto& att = fbo.GetAttachment(attachment);
Int type = 0;
if (att.IsEmpty()) type = 0;
else if (att.IsTexture()) type = 1;
else if (att.IsRenderbuffer()) type = 2;
XXHASH_VERIFY(XXH64_update(m_hashState, &type, sizeof(type)));
void* contentPtr = nullptr;
if (att.IsTexture())
contentPtr = att.GetTexture().get();
else if (att.IsRenderbuffer())
contentPtr = att.GetRenderbuffer().get();
XXHASH_VERIFY(XXH64_update(m_hashState, &contentPtr, sizeof(contentPtr)));
if (att.IsTexture()) {
auto hasClear = m_clearManager.HasPendingClear(att.GetTexture().get());
XXHASH_VERIFY(XXH64_update(m_hashState, &hasClear, sizeof(hasClear)));
}
};
for (Int i = 0; i < validDrawBufCount; ++i) {
auto drawbuf = drawBuffers[i];
combineFramebufferAttachmentObjHash(drawbuf);
}
m_renderPassLoad = VK_NULL_HANDLE;
m_colorFormat = VK_FORMAT_UNDEFINED;
m_depthStencilFormat = VK_FORMAT_UNDEFINED;
m_defaultExtent = {0, 0};
m_device = VK_NULL_HANDLE;
combineFramebufferAttachmentObjHash(FramebufferAttachmentType::Depth);
combineFramebufferAttachmentObjHash(FramebufferAttachmentType::Stencil);
return XXH64_digest(m_hashState);
}
Bool VkRenderPassManager::RecreateDefaultFramebuffers(const Vector<VkImageView>& colorViews,
const Vector<VkImageView>& depthStencilViews,
VkExtent2D extent) {
DestroyDefaultFramebuffers();
RenderPassEntry& VkRenderPassManager::GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo) {
// retrieve from cache first
auto hash = ComputeHash(fbo);
auto it = m_renderPasses.find(hash);
if (it != m_renderPasses.end())
return it->second;
if (m_device == VK_NULL_HANDLE || m_renderPassLoad == VK_NULL_HANDLE || extent.width == 0 ||
extent.height == 0 || colorViews.empty() || colorViews.size() != depthStencilViews.size()) {
return false;
Bool isDefaultFbo = (&fbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO.get());
// Color attachment
auto& drawbufs = fbo.GetDrawBuffers();
Int validDrawBufCount = 0;
for (Int i = 0; i < drawbufs.size(); ++i) {
auto drawbuf = drawbufs[i];
if (drawbuf != FramebufferAttachmentType::None)
validDrawBufCount = std::max(validDrawBufCount, i + 1);
}
m_defaultFramebuffers.reserve(colorViews.size());
for (SizeT i = 0; i < colorViews.size(); ++i) {
if (colorViews[i] == VK_NULL_HANDLE || depthStencilViews[i] == VK_NULL_HANDLE) {
DestroyDefaultFramebuffers();
return false;
Int width = 0;
Int height = 0;
Vector<VkAttachmentDescription> colorAttachmentDescriptions(validDrawBufCount);
Vector<VkAttachmentReference> colorAttachmentRefs(validDrawBufCount);
Vector<VkDescriptorImageInfo> descriptorImageInfo(validDrawBufCount);
// This should automatically work on default & offscreen FBO
// assuming default FBO has the right param
for (Int i = 0; i < validDrawBufCount; ++i) {
auto drawbuf = drawbufs[i];
if (drawbuf == FramebufferAttachmentType::None ||
fbo.GetAttachment(drawbuf).IsRenderbuffer())
continue;
auto& att = fbo.GetAttachment(drawbuf);
auto* texture = att.GetTexture().get();
const auto textureTarget = texture->GetTarget();
// Color attachment description
VkAttachmentDescription& desc = colorAttachmentDescriptions[i];
switch (textureTarget) {
case TextureTarget::Texture2D: {
auto* texture2d =
static_cast<MG_State::GLState::TextureObject2D*>(texture);
desc.format =
MG_Util::ConvertTextureInternalFormatToVkEnum(
texture2d->GetFormat());
desc.samples = VK_SAMPLE_COUNT_1_BIT;
Bool hasClear = m_clearManager.HasPendingClear(texture);
desc.loadOp = hasClear ?
VK_ATTACHMENT_LOAD_OP_CLEAR :
VK_ATTACHMENT_LOAD_OP_LOAD;
desc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
desc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
desc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
desc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
desc.finalLayout = isDefaultFbo ?
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR :
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
if (width == 0)
width = texture2d->GetBaseSize().x();
if (height == 0)
height = texture2d->GetBaseSize().y();
Bool ok = m_textureManager.SyncTextureAndGetDescriptor(*texture, descriptorImageInfo[i]);
MOBILEGL_ASSERT(ok, "GetOrCreateRenderPass: SyncTextureAndGetDescriptor failed");
break;
}
default:
MOBILEGL_ASSERT(false, "Unsupported texture target");
}
VkImageView attachments[] = {colorViews[i], depthStencilViews[i]};
VkFramebufferCreateInfo createInfo{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
createInfo.renderPass = m_renderPassLoad;
createInfo.attachmentCount = static_cast<Uint32>(std::size(attachments));
createInfo.pAttachments = attachments;
createInfo.width = extent.width;
createInfo.height = extent.height;
createInfo.layers = 1;
VkFramebuffer framebuffer = VK_NULL_HANDLE;
if (vkCreateFramebuffer(m_device, &createInfo, nullptr, &framebuffer) != VK_SUCCESS) {
DestroyDefaultFramebuffers();
return false;
}
m_defaultFramebuffers.push_back(framebuffer);
}
m_defaultExtent = extent;
return true;
}
Bool VkRenderPassManager::GetDefaultRenderTarget(Uint32 imageIndex, VkRenderPass& outRenderPass,
VkFramebuffer& outFramebuffer, VkExtent2D& outExtent,
VkFormat& outDepthStencilFormat) const {
if (imageIndex >= m_defaultFramebuffers.size() || m_renderPassLoad == VK_NULL_HANDLE) {
return false;
}
outRenderPass = m_renderPassLoad;
outFramebuffer = m_defaultFramebuffers[imageIndex];
outExtent = m_defaultExtent;
outDepthStencilFormat = m_depthStencilFormat;
return outFramebuffer != VK_NULL_HANDLE;
}
Bool VkRenderPassManager::EnsureOffscreenRenderTarget(const OffscreenRenderTargetInfo& targetInfo) {
if (m_device == VK_NULL_HANDLE || targetInfo.colorView == VK_NULL_HANDLE ||
targetInfo.colorFormat == VK_FORMAT_UNDEFINED || targetInfo.extent.width == 0 ||
targetInfo.extent.height == 0) {
return false;
// Attachment reference
VkAttachmentReference& attachmentRef = colorAttachmentRefs[i];
attachmentRef.attachment = i;
attachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
}
const Bool hasDepthStencil =
targetInfo.depthStencilView != VK_NULL_HANDLE && targetInfo.depthStencilFormat != VK_FORMAT_UNDEFINED;
auto& target = m_offscreenRenderTargets[targetInfo.targetExternalIndex];
if (target.framebuffer != VK_NULL_HANDLE && target.renderPassLoad != VK_NULL_HANDLE &&
target.targetVersion == targetInfo.targetVersion && target.colorView == targetInfo.colorView &&
target.colorFormat == targetInfo.colorFormat && target.depthStencilView == targetInfo.depthStencilView &&
target.depthStencilFormat == targetInfo.depthStencilFormat &&
target.extent.width == targetInfo.extent.width && target.extent.height == targetInfo.extent.height) {
return true;
}
DestroyOffscreenRenderTarget(target);
// Depth attachment description
VkAttachmentDescription depthAttachmentDescription;
auto& depthAtt = fbo.GetAttachment(FramebufferAttachmentType::Depth);
depthAttachmentDescription.format =
MG_Util::ConvertTextureInternalFormatToVkEnum(depthAtt.GetTexture()->GetFormat());
depthAttachmentDescription.samples = VK_SAMPLE_COUNT_1_BIT;
depthAttachmentDescription.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
depthAttachmentDescription.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
depthAttachmentDescription.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
depthAttachmentDescription.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
depthAttachmentDescription.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
depthAttachmentDescription.finalLayout = isDefaultFbo ?
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR :
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
const VkFormat depthStencilFormat = hasDepthStencil ? targetInfo.depthStencilFormat : VK_FORMAT_UNDEFINED;
target.renderPassLoad = CreateRenderPass(targetInfo.colorFormat, depthStencilFormat, VK_ATTACHMENT_LOAD_OP_LOAD,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
if (target.renderPassLoad == VK_NULL_HANDLE) {
return false;
}
// Depth attachment ref
VkAttachmentReference depthAttachmentRef;
depthAttachmentRef.attachment = 1;
depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
Array<VkImageView, 2> attachments{};
attachments[0] = targetInfo.colorView;
Uint32 attachmentCount = 1;
if (hasDepthStencil) {
attachments[1] = targetInfo.depthStencilView;
attachmentCount = 2;
}
// Subpass
VkSubpassDescription subpassDesc;
subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpassDesc.colorAttachmentCount = colorAttachmentRefs.size();
subpassDesc.pColorAttachments = colorAttachmentRefs.data();
subpassDesc.pDepthStencilAttachment = &depthAttachmentRef;
VkFramebufferCreateInfo framebufferInfo{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
framebufferInfo.renderPass = target.renderPassLoad;
framebufferInfo.attachmentCount = attachmentCount;
framebufferInfo.pAttachments = attachments.data();
framebufferInfo.width = targetInfo.extent.width;
framebufferInfo.height = targetInfo.extent.height;
framebufferInfo.layers = 1;
VK_VERIFY(vkCreateFramebuffer(m_device, &framebufferInfo, nullptr, &target.framebuffer),
"vkCreateFramebuffer(offscreen)");
target.targetVersion = targetInfo.targetVersion;
target.colorView = targetInfo.colorView;
target.colorFormat = targetInfo.colorFormat;
target.depthStencilView = targetInfo.depthStencilView;
target.depthStencilFormat = targetInfo.depthStencilFormat;
target.extent = targetInfo.extent;
return true;
}
Bool VkRenderPassManager::GetOffscreenRenderTarget(Uint targetExternalIndex, VkRenderPass& outRenderPass,
VkFramebuffer& outFramebuffer, VkExtent2D& outExtent,
VkFormat& outDepthStencilFormat) const {
auto it = m_offscreenRenderTargets.find(targetExternalIndex);
if (it == m_offscreenRenderTargets.end() || it->second.renderPassLoad == VK_NULL_HANDLE ||
it->second.framebuffer == VK_NULL_HANDLE) {
return false;
}
outRenderPass = it->second.renderPassLoad;
outFramebuffer = it->second.framebuffer;
outExtent = it->second.extent;
outDepthStencilFormat = it->second.depthStencilFormat;
return true;
}
void VkRenderPassManager::RemoveOffscreenRenderTarget(Uint targetExternalIndex) {
auto it = m_offscreenRenderTargets.find(targetExternalIndex);
if (it == m_offscreenRenderTargets.end()) {
return;
}
DestroyOffscreenRenderTarget(it->second);
m_offscreenRenderTargets.erase(it);
}
void VkRenderPassManager::BeginRenderPass(VkCommandBuffer commandBuffer, VkRenderPass renderPass,
VkFramebuffer framebuffer, VkExtent2D extent) const {
VkRenderPassBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
beginInfo.renderPass = renderPass;
beginInfo.framebuffer = framebuffer;
beginInfo.renderArea.offset = {0, 0};
beginInfo.renderArea.extent = extent;
beginInfo.clearValueCount = 0;
beginInfo.pClearValues = nullptr;
vkCmdBeginRenderPass(commandBuffer, &beginInfo, VK_SUBPASS_CONTENTS_INLINE);
}
void VkRenderPassManager::EndRenderPass(VkCommandBuffer commandBuffer) const {
vkCmdEndRenderPass(commandBuffer);
}
void VkRenderPassManager::RecordColorClear(VkCommandBuffer commandBuffer, VkExtent2D extent,
const VkClearColorValue& clearColor) const {
VkClearAttachment clearAttachment{};
clearAttachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
clearAttachment.colorAttachment = 0;
clearAttachment.clearValue.color = clearColor;
VkClearRect clearRect{};
clearRect.rect.offset = {0, 0};
clearRect.rect.extent = extent;
clearRect.baseArrayLayer = 0;
clearRect.layerCount = 1;
vkCmdClearAttachments(commandBuffer, 1, &clearAttachment, 1, &clearRect);
}
void VkRenderPassManager::RecordDepthStencilClear(VkCommandBuffer commandBuffer, VkExtent2D extent, GLbitfield mask,
Float depth, Uint32 stencil, VkFormat depthStencilFormat) const {
if (depthStencilFormat == VK_FORMAT_UNDEFINED) {
return;
}
VkImageAspectFlags aspectMask = 0;
if ((mask & GL_DEPTH_BUFFER_BIT) != 0) {
aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
}
if ((mask & GL_STENCIL_BUFFER_BIT) != 0 && HasStencilComponent(depthStencilFormat)) {
aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
}
if (aspectMask == 0) {
return;
}
VkClearAttachment clearAttachment{};
clearAttachment.aspectMask = aspectMask;
clearAttachment.clearValue.depthStencil.depth = depth;
clearAttachment.clearValue.depthStencil.stencil = stencil;
VkClearRect clearRect{};
clearRect.rect.offset = {0, 0};
clearRect.rect.extent = extent;
clearRect.baseArrayLayer = 0;
clearRect.layerCount = 1;
vkCmdClearAttachments(commandBuffer, 1, &clearAttachment, 1, &clearRect);
}
VkRenderPass VkRenderPassManager::CreateDefaultRenderPass(VkAttachmentLoadOp colorLoadOp) const {
MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE, "VkRenderPassManager: device is null");
MOBILEGL_ASSERT(m_colorFormat != VK_FORMAT_UNDEFINED, "VkRenderPassManager: color format is undefined");
MOBILEGL_ASSERT(m_depthStencilFormat != VK_FORMAT_UNDEFINED,
"VkRenderPassManager: depth/stencil format is undefined");
return CreateRenderPass(m_colorFormat, m_depthStencilFormat, colorLoadOp, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
}
VkRenderPass VkRenderPassManager::CreateRenderPass(VkFormat colorFormat, VkFormat depthStencilFormat,
VkAttachmentLoadOp colorLoadOp,
VkImageLayout colorFinalLayout) const {
VkAttachmentDescription color{};
color.format = colorFormat;
color.samples = VK_SAMPLE_COUNT_1_BIT;
color.loadOp = colorLoadOp;
color.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
color.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
color.finalLayout = colorFinalLayout;
color.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
color.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
const Bool hasDepthStencil = depthStencilFormat != VK_FORMAT_UNDEFINED;
VkAttachmentDescription depthStencil{};
if (hasDepthStencil) {
depthStencil.format = depthStencilFormat;
depthStencil.samples = VK_SAMPLE_COUNT_1_BIT;
depthStencil.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
depthStencil.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
depthStencil.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
depthStencil.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
depthStencil.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
depthStencil.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
}
VkAttachmentReference colorRef{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
VkAttachmentReference depthStencilRef{1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL};
VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorRef;
if (hasDepthStencil) {
subpass.pDepthStencilAttachment = &depthStencilRef;
}
Array<VkAttachmentDescription, 2> attachments{};
attachments[0] = color;
Uint32 attachmentCount = 1;
if (hasDepthStencil) {
attachments[1] = depthStencil;
attachmentCount = 2;
}
VkRenderPassCreateInfo createInfo{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO};
createInfo.attachmentCount = attachmentCount;
createInfo.pAttachments = attachments.data();
createInfo.subpassCount = 1;
createInfo.pSubpasses = &subpass;
// Render Pass
VkRenderPassCreateInfo renderPassCreateInfo;
renderPassCreateInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassCreateInfo.attachmentCount = colorAttachmentDescriptions.size();
renderPassCreateInfo.pAttachments = colorAttachmentDescriptions.data();
renderPassCreateInfo.subpassCount = 1;
renderPassCreateInfo.pSubpasses = &subpassDesc;
VkRenderPass renderPass = VK_NULL_HANDLE;
VK_VERIFY(vkCreateRenderPass(m_device, &createInfo, nullptr, &renderPass), "vkCreateRenderPass");
return renderPass;
VK_VERIFY(vkCreateRenderPass(m_device, &renderPassCreateInfo, nullptr, &renderPass));
Vector<VkImageView> attachmentViews(descriptorImageInfo.size(), VK_NULL_HANDLE);
for (Int i = 0; i < descriptorImageInfo.size(); i++) {
attachmentViews[i] = descriptorImageInfo[i].imageView;
}
// Framebuffer
VkFramebufferCreateInfo framebufferCreateInfo;
framebufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebufferCreateInfo.renderPass = renderPass;
framebufferCreateInfo.attachmentCount = attachmentViews.size();
framebufferCreateInfo.pAttachments = attachmentViews.data();
framebufferCreateInfo.width = width;
framebufferCreateInfo.height = height;
VkFramebuffer framebuffer = VK_NULL_HANDLE;
VK_VERIFY(vkCreateFramebuffer(m_device, &framebufferCreateInfo, nullptr, &framebuffer));
IntVec2 extent = {width, height};
m_renderPasses[hash] = { renderPass, framebuffer, Move(descriptorImageInfo), extent };
return m_renderPasses[hash];
}
void VkRenderPassManager::DestroyDefaultFramebuffers() {
for (auto framebuffer : m_defaultFramebuffers) {
if (framebuffer != VK_NULL_HANDLE) {
vkDestroyFramebuffer(m_device, framebuffer, nullptr);
}
}
m_defaultFramebuffers.clear();
m_defaultExtent = {0, 0};
Bool VkRenderPassManager::StartRenderPass(VkCommandBuffer commandBuffer, RenderPassEntry& renderPassEntry) {
if (m_activeRenderPass != nullptr)
return false;
m_activeRenderPass = &renderPassEntry;
VkRenderPassBeginInfo renderPassBeginInfo;
renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassBeginInfo.renderPass = m_activeRenderPass->renderPass;
renderPassBeginInfo.framebuffer = m_activeRenderPass->framebuffer;
renderPassBeginInfo.renderArea.offset = { 0, 0 };
renderPassBeginInfo.renderArea.extent = {
(Uint32)m_activeRenderPass->extent.x(), (Uint32)m_activeRenderPass->extent.y() };
// TODO: should query proper clear color
VkClearValue clearValue;
clearValue.color = { 0.0f, 0.0f, 0.0f, 1.0f };
clearValue.depthStencil = { 1.0f, 0 };
vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
return true;
}
void VkRenderPassManager::DestroyOffscreenRenderTarget(OffscreenRenderTarget& target) {
if (target.framebuffer != VK_NULL_HANDLE) {
vkDestroyFramebuffer(m_device, target.framebuffer, nullptr);
target.framebuffer = VK_NULL_HANDLE;
}
if (target.renderPassLoad != VK_NULL_HANDLE) {
vkDestroyRenderPass(m_device, target.renderPassLoad, nullptr);
target.renderPassLoad = VK_NULL_HANDLE;
}
target.targetVersion = 0;
target.colorView = VK_NULL_HANDLE;
target.colorFormat = VK_FORMAT_UNDEFINED;
target.depthStencilView = VK_NULL_HANDLE;
target.depthStencilFormat = VK_FORMAT_UNDEFINED;
target.extent = {0, 0};
}
Bool VkRenderPassManager::HasStencilComponent(VkFormat format) {
return format == VK_FORMAT_D24_UNORM_S8_UINT || format == VK_FORMAT_D32_SFLOAT_S8_UINT;
Bool VkRenderPassManager::EndRenderPass(VkCommandBuffer commandBuffer) {
if (m_activeRenderPass == nullptr)
return false;
vkCmdEndRenderPass(commandBuffer);
return true;
}
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -8,75 +8,54 @@
#pragma once
#include "VkClearManager.h"
#include "VkTextureManager.h"
#include "../VkIncludes.h"
#include "../VulkanRendererConfig.h"
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
#include <Includes.h>
namespace MobileGL::MG_Backend::DirectVulkan {
struct RenderPassEntry {
static inline VkDevice s_device;
VkRenderPass renderPass = VK_NULL_HANDLE;
VkFramebuffer framebuffer = VK_NULL_HANDLE;
Vector<VkDescriptorImageInfo> descriptorImageInfo;
IntVec2 extent = {0, 0};
~RenderPassEntry() {
if (renderPass != VK_NULL_HANDLE) {
vkDestroyRenderPass(s_device, renderPass, nullptr);
}
if (framebuffer != VK_NULL_HANDLE) {
vkDestroyFramebuffer(s_device, framebuffer, nullptr);
}
}
};
class VkRenderPassManager {
public:
struct InitInfo {
VkDevice device = VK_NULL_HANDLE;
VkFormat colorFormat = VK_FORMAT_UNDEFINED;
VkFormat depthStencilFormat = VK_FORMAT_UNDEFINED;
};
using HashType = Uint64;
VkRenderPassManager(VkDevice device,
const VulkanRendererConfig& config, VkClearManager& clearManager, VkTextureManager& textureManager);
~VkRenderPassManager();
struct OffscreenRenderTargetInfo {
Uint targetExternalIndex = 0;
Uint16 targetVersion = 0;
VkImageView colorView = VK_NULL_HANDLE;
VkFormat colorFormat = VK_FORMAT_UNDEFINED;
VkImageView depthStencilView = VK_NULL_HANDLE;
VkFormat depthStencilFormat = VK_FORMAT_UNDEFINED;
VkExtent2D extent = {0, 0};
};
Bool Initialize(const InitInfo& initInfo);
Bool Initialize();
void Shutdown();
Bool RecreateDefaultFramebuffers(const Vector<VkImageView>& colorViews,
const Vector<VkImageView>& depthStencilViews, VkExtent2D extent);
Bool GetDefaultRenderTarget(Uint32 imageIndex, VkRenderPass& outRenderPass, VkFramebuffer& outFramebuffer,
VkExtent2D& outExtent, VkFormat& outDepthStencilFormat) const;
Bool EnsureOffscreenRenderTarget(const OffscreenRenderTargetInfo& targetInfo);
Bool GetOffscreenRenderTarget(Uint targetExternalIndex, VkRenderPass& outRenderPass,
VkFramebuffer& outFramebuffer, VkExtent2D& outExtent,
VkFormat& outDepthStencilFormat) const;
void RemoveOffscreenRenderTarget(Uint targetExternalIndex);
void BeginRenderPass(VkCommandBuffer commandBuffer, VkRenderPass renderPass, VkFramebuffer framebuffer,
VkExtent2D extent) const;
void EndRenderPass(VkCommandBuffer commandBuffer) const;
void RecordColorClear(VkCommandBuffer commandBuffer, VkExtent2D extent,
const VkClearColorValue& clearColor) const;
void RecordDepthStencilClear(VkCommandBuffer commandBuffer, VkExtent2D extent, GLbitfield mask, Float depth,
Uint32 stencil, VkFormat depthStencilFormat) const;
HashType ComputeHash(const MG_State::GLState::FramebufferObject& fbo) const;
RenderPassEntry& GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo);
RenderPassEntry* GetActiveRenderPass() const { return m_activeRenderPass; }
Bool StartRenderPass(VkCommandBuffer commandBuffer, RenderPassEntry& renderPassEntry);
Bool EndRenderPass(VkCommandBuffer commandBuffer);
private:
struct OffscreenRenderTarget {
Uint16 targetVersion = 0;
VkImageView colorView = VK_NULL_HANDLE;
VkFormat colorFormat = VK_FORMAT_UNDEFINED;
VkImageView depthStencilView = VK_NULL_HANDLE;
VkFormat depthStencilFormat = VK_FORMAT_UNDEFINED;
VkExtent2D extent = {0, 0};
VkRenderPass renderPassLoad = VK_NULL_HANDLE;
VkFramebuffer framebuffer = VK_NULL_HANDLE;
};
VkRenderPass CreateDefaultRenderPass(VkAttachmentLoadOp colorLoadOp) const;
VkRenderPass CreateRenderPass(VkFormat colorFormat, VkFormat depthStencilFormat, VkAttachmentLoadOp colorLoadOp,
VkImageLayout colorFinalLayout) const;
void DestroyDefaultFramebuffers();
void DestroyOffscreenRenderTarget(OffscreenRenderTarget& target);
static Bool HasStencilComponent(VkFormat format);
VkDevice m_device = VK_NULL_HANDLE;
VkFormat m_colorFormat = VK_FORMAT_UNDEFINED;
VkFormat m_depthStencilFormat = VK_FORMAT_UNDEFINED;
VkRenderPass m_renderPassLoad = VK_NULL_HANDLE;
Vector<VkFramebuffer> m_defaultFramebuffers;
VkExtent2D m_defaultExtent = {0, 0};
UnorderedMap<Uint, OffscreenRenderTarget> m_offscreenRenderTargets;
const VulkanRendererConfig& m_config;
VkClearManager& m_clearManager;
VkTextureManager& m_textureManager;
UnorderedMap<Uint64, RenderPassEntry> m_renderPasses;
RenderPassEntry* m_activeRenderPass = nullptr;
static inline XXH64_state_t* m_hashState = XXH64_createState();
};
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -1,325 +0,0 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.cpp
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
#include "VkRenderTargetManager.h"
#include <MG_State/GLState/RenderbufferState/RenderbufferObject.h>
#include <MG_State/GLState/TextureState/TextureEnum.h>
namespace MobileGL::MG_Backend::DirectVulkan {
Bool VkRenderTargetManager::Initialize(const InitInfo& initInfo) {
m_device = initInfo.device;
m_physicalDevice = initInfo.physicalDevice;
m_allocator = initInfo.allocator;
return m_device != VK_NULL_HANDLE && m_physicalDevice != VK_NULL_HANDLE && m_allocator != nullptr;
}
void VkRenderTargetManager::Shutdown() {
for (auto& [_, target] : m_offscreenColorTargets) {
DestroyOffscreenColorTarget(target);
}
m_offscreenColorTargets.clear();
m_device = VK_NULL_HANDLE;
m_physicalDevice = VK_NULL_HANDLE;
m_allocator = nullptr;
}
Bool VkRenderTargetManager::EnsureOffscreenColorTarget(Uint glFboExternalIndex,
const MG_State::GLState::FramebufferObject& glFbo) {
const auto& colorAttachment = glFbo.GetAttachment(FramebufferAttachmentType::Color0);
if (!colorAttachment.IsValid() || colorAttachment.IsEmpty()) {
MGLOG_W("VkFramebufferManager: FBO %u has no valid COLOR0 attachment", glFboExternalIndex);
return false;
}
const auto objectVersion = glFbo.GetObjectVersion();
auto& target = m_offscreenColorTargets[glFboExternalIndex];
if (target.image != VK_NULL_HANDLE && target.glObjectVersion == objectVersion) {
return true;
}
return RecreateOffscreenColorTarget(target, glFbo, colorAttachment, objectVersion);
}
Bool VkRenderTargetManager::GetOffscreenRenderSurfaceState(Uint glFboExternalIndex, VkImage& outColorImage,
VkImageLayout*& outColorLayout,
VkImage& outDepthStencilImage,
VkImageLayout*& outDepthStencilLayout,
VkFormat& outDepthStencilFormat) {
auto it = m_offscreenColorTargets.find(glFboExternalIndex);
if (it == m_offscreenColorTargets.end()) {
return false;
}
auto& target = it->second;
if (target.image == VK_NULL_HANDLE) {
return false;
}
outColorImage = target.image;
outColorLayout = &target.layout;
outDepthStencilImage = target.depthStencilImage;
outDepthStencilLayout = &target.depthStencilLayout;
outDepthStencilFormat = target.depthStencilFormat;
return true;
}
Bool VkRenderTargetManager::GetOffscreenColorTargetStateByTexture(Uint textureExternalIndex,
VkImageView& outImageView, VkImage& outImage,
VkImageLayout*& outLayout) {
for (auto& [_, target] : m_offscreenColorTargets) {
if (target.colorTextureExternalIndex != textureExternalIndex || target.image == VK_NULL_HANDLE ||
target.imageView == VK_NULL_HANDLE) {
continue;
}
outImageView = target.imageView;
outImage = target.image;
outLayout = &target.layout;
return true;
}
return false;
}
Bool VkRenderTargetManager::GetOffscreenRenderSurface(Uint glFboExternalIndex, VkImageView& outColorView,
VkFormat& outColorFormat, VkImageView& outDepthStencilView,
VkFormat& outDepthStencilFormat, VkExtent2D& outExtent) const {
auto it = m_offscreenColorTargets.find(glFboExternalIndex);
if (it == m_offscreenColorTargets.end() || it->second.imageView == VK_NULL_HANDLE) {
return false;
}
outColorView = it->second.imageView;
outColorFormat = it->second.format;
outDepthStencilView = it->second.depthStencilImageView;
outExtent = it->second.extent;
outDepthStencilFormat = it->second.depthStencilFormat;
return true;
}
Bool VkRenderTargetManager::RecreateOffscreenColorTarget(
OffscreenColorTarget& target, const MG_State::GLState::FramebufferObject& glFbo,
const MG_State::GLState::FramebufferAttachmentObject& colorAttachment, Uint16 glObjectVersion) {
DestroyOffscreenColorTarget(target);
const auto size = colorAttachment.GetSize();
if (size.x() <= 0 || size.y() <= 0) {
MGLOG_W("VkFramebufferManager: COLOR0 attachment size is invalid (%d, %d)", size.x(), size.y());
return false;
}
const VkFormat format = ResolveColorFormat(colorAttachment);
if (format == VK_FORMAT_UNDEFINED) {
MGLOG_W("VkFramebufferManager: COLOR0 attachment format is unsupported for Vulkan clear");
return false;
}
VkImageCreateInfo imageInfo{};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.extent.width = static_cast<Uint32>(size.x());
imageInfo.extent.height = static_cast<Uint32>(size.y());
imageInfo.extent.depth = 1;
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.format = format;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VmaAllocationCreateInfo colorAllocationInfo{};
colorAllocationInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
colorAllocationInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
VK_VERIFY(vmaCreateImage(m_allocator, &imageInfo, &colorAllocationInfo, &target.image, &target.allocation, nullptr),
"vmaCreateImage(offscreen color)");
VkImageViewCreateInfo viewInfo{};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = target.image;
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = format;
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
viewInfo.subresourceRange.baseMipLevel = 0;
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = 1;
VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &target.imageView),
"vkCreateImageView(offscreen color)");
const auto& depthAttachment = glFbo.GetAttachment(FramebufferAttachmentType::Depth);
const auto& stencilAttachment = glFbo.GetAttachment(FramebufferAttachmentType::Stencil);
const Bool requestedDepthStencil = (depthAttachment.IsValid() && !depthAttachment.IsEmpty()) ||
(stencilAttachment.IsValid() && !stencilAttachment.IsEmpty());
VkFormat depthStencilFormat = ResolveDepthStencilFormat(depthAttachment, stencilAttachment);
if (depthStencilFormat == VK_FORMAT_D24_UNORM_S8_UINT) {
depthStencilFormat =
FindSupportedDepthStencilFormat({VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D32_SFLOAT_S8_UINT});
} else if (depthStencilFormat == VK_FORMAT_D32_SFLOAT) {
depthStencilFormat = FindSupportedDepthStencilFormat({VK_FORMAT_D32_SFLOAT, VK_FORMAT_D16_UNORM});
}
const Bool hasDepthStencil = (depthStencilFormat != VK_FORMAT_UNDEFINED);
if (requestedDepthStencil && !hasDepthStencil) {
MGLOG_W("VkFramebufferManager: FBO %u depth/stencil attachment exists but format is unsupported",
glFbo.GetExternalIndex());
}
if (hasDepthStencil) {
VkImageCreateInfo depthImageInfo{};
depthImageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
depthImageInfo.imageType = VK_IMAGE_TYPE_2D;
depthImageInfo.extent.width = static_cast<Uint32>(size.x());
depthImageInfo.extent.height = static_cast<Uint32>(size.y());
depthImageInfo.extent.depth = 1;
depthImageInfo.mipLevels = 1;
depthImageInfo.arrayLayers = 1;
depthImageInfo.format = depthStencilFormat;
depthImageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
depthImageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
depthImageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
depthImageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
depthImageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VmaAllocationCreateInfo depthAllocationInfo{};
depthAllocationInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
depthAllocationInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
VK_VERIFY(vmaCreateImage(m_allocator, &depthImageInfo, &depthAllocationInfo, &target.depthStencilImage,
&target.depthStencilAllocation, nullptr),
"vmaCreateImage(offscreen depth/stencil)");
VkImageAspectFlags depthAspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
if (depthStencilFormat == VK_FORMAT_D24_UNORM_S8_UINT ||
depthStencilFormat == VK_FORMAT_D32_SFLOAT_S8_UINT) {
depthAspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
}
VkImageViewCreateInfo depthViewInfo{};
depthViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
depthViewInfo.image = target.depthStencilImage;
depthViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
depthViewInfo.format = depthStencilFormat;
depthViewInfo.subresourceRange.aspectMask = depthAspectMask;
depthViewInfo.subresourceRange.baseMipLevel = 0;
depthViewInfo.subresourceRange.levelCount = 1;
depthViewInfo.subresourceRange.baseArrayLayer = 0;
depthViewInfo.subresourceRange.layerCount = 1;
VK_VERIFY(vkCreateImageView(m_device, &depthViewInfo, nullptr, &target.depthStencilImageView),
"vkCreateImageView(offscreen depth/stencil)");
}
target.layout = VK_IMAGE_LAYOUT_UNDEFINED;
target.extent = {static_cast<Uint32>(size.x()), static_cast<Uint32>(size.y())};
target.format = format;
target.depthStencilLayout = VK_IMAGE_LAYOUT_UNDEFINED;
target.depthStencilFormat = depthStencilFormat;
target.glObjectVersion = glObjectVersion;
target.colorTextureExternalIndex = (colorAttachment.IsTexture() && colorAttachment.GetTexture())
? colorAttachment.GetTexture()->GetExternalIndex()
: 0;
return true;
}
void VkRenderTargetManager::DestroyOffscreenColorTarget(OffscreenColorTarget& target) {
if (target.imageView != VK_NULL_HANDLE) {
vkDestroyImageView(m_device, target.imageView, nullptr);
target.imageView = VK_NULL_HANDLE;
}
if (target.depthStencilImageView != VK_NULL_HANDLE) {
vkDestroyImageView(m_device, target.depthStencilImageView, nullptr);
target.depthStencilImageView = VK_NULL_HANDLE;
}
if (target.image != VK_NULL_HANDLE) {
vmaDestroyImage(m_allocator, target.image, target.allocation);
target.image = VK_NULL_HANDLE;
}
if (target.depthStencilImage != VK_NULL_HANDLE) {
vmaDestroyImage(m_allocator, target.depthStencilImage, target.depthStencilAllocation);
target.depthStencilImage = VK_NULL_HANDLE;
}
target.allocation = nullptr;
target.depthStencilAllocation = nullptr;
target.layout = VK_IMAGE_LAYOUT_UNDEFINED;
target.depthStencilLayout = VK_IMAGE_LAYOUT_UNDEFINED;
target.extent = {0, 0};
target.format = VK_FORMAT_UNDEFINED;
target.depthStencilFormat = VK_FORMAT_UNDEFINED;
target.glObjectVersion = 0;
target.colorTextureExternalIndex = 0;
}
VkFormat VkRenderTargetManager::ResolveColorFormat(
const MG_State::GLState::FramebufferAttachmentObject& colorAttachment) {
TextureInternalFormat internalFormat = TextureInternalFormat::Unknown;
if (colorAttachment.IsTexture()) {
const auto texture = colorAttachment.GetTexture();
internalFormat = texture ? texture->GetFormat() : TextureInternalFormat::Unknown;
} else if (colorAttachment.IsRenderbuffer()) {
const auto renderbuffer = colorAttachment.GetRenderbuffer();
internalFormat = renderbuffer ? renderbuffer->GetInternalFormat() : TextureInternalFormat::Unknown;
}
switch (internalFormat) {
case TextureInternalFormat::RGBA:
case TextureInternalFormat::RGBA8:
case TextureInternalFormat::SRGB8Alpha8:
return VK_FORMAT_R8G8B8A8_UNORM;
default:
return VK_FORMAT_UNDEFINED;
}
}
VkFormat VkRenderTargetManager::ResolveDepthStencilFormat(
const MG_State::GLState::FramebufferAttachmentObject& depthAttachment,
const MG_State::GLState::FramebufferAttachmentObject& stencilAttachment) {
const auto resolveAttachmentFormat = [](const MG_State::GLState::FramebufferAttachmentObject& attachment) {
TextureInternalFormat internalFormat = TextureInternalFormat::Unknown;
if (attachment.IsTexture()) {
const auto texture = attachment.GetTexture();
internalFormat = texture ? texture->GetFormat() : TextureInternalFormat::Unknown;
} else if (attachment.IsRenderbuffer()) {
const auto renderbuffer = attachment.GetRenderbuffer();
internalFormat = renderbuffer ? renderbuffer->GetInternalFormat() : TextureInternalFormat::Unknown;
}
return internalFormat;
};
const auto depthFormat = resolveAttachmentFormat(depthAttachment);
const auto stencilFormat = resolveAttachmentFormat(stencilAttachment);
switch (depthFormat) {
case TextureInternalFormat::Depth24Stencil8:
case TextureInternalFormat::Depth32FStencil8:
case TextureInternalFormat::DepthStencil:
return VK_FORMAT_D24_UNORM_S8_UINT;
case TextureInternalFormat::DepthComponent16:
return VK_FORMAT_D16_UNORM;
case TextureInternalFormat::DepthComponent24:
case TextureInternalFormat::DepthComponent32:
case TextureInternalFormat::DepthComponent32F:
case TextureInternalFormat::DepthComponent:
return VK_FORMAT_D32_SFLOAT;
default:
break;
}
switch (stencilFormat) {
case TextureInternalFormat::Depth24Stencil8:
case TextureInternalFormat::Depth32FStencil8:
case TextureInternalFormat::DepthStencil:
return VK_FORMAT_D24_UNORM_S8_UINT;
default:
return VK_FORMAT_UNDEFINED;
}
}
VkFormat VkRenderTargetManager::FindSupportedDepthStencilFormat(const Vector<VkFormat>& candidates) const {
for (auto format : candidates) {
VkFormatProperties properties{};
vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &properties);
if ((properties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0) {
return format;
}
}
return VK_FORMAT_UNDEFINED;
}
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -1,75 +0,0 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.h
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
#pragma once
#include "../VkIncludes.h"
#include <Includes.h>
#include <MG_State/GLState/FramebufferState/FramebufferObject.h>
#include <vk_mem_alloc.h>
namespace MobileGL::MG_Backend::DirectVulkan {
class VkRenderTargetManager {
public:
struct InitInfo {
VkDevice device = VK_NULL_HANDLE;
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
VmaAllocator allocator = nullptr;
};
VkRenderTargetManager() = default;
~VkRenderTargetManager() = default;
Bool Initialize(const InitInfo& initInfo);
void Shutdown();
Bool EnsureOffscreenColorTarget(Uint glFboExternalIndex, const MG_State::GLState::FramebufferObject& glFbo);
Bool GetOffscreenRenderSurfaceState(Uint glFboExternalIndex, VkImage& outColorImage,
VkImageLayout*& outColorLayout, VkImage& outDepthStencilImage,
VkImageLayout*& outDepthStencilLayout,
VkFormat& outDepthStencilFormat);
Bool GetOffscreenColorTargetStateByTexture(Uint textureExternalIndex, VkImageView& outImageView,
VkImage& outImage, VkImageLayout*& outLayout);
Bool GetOffscreenRenderSurface(Uint glFboExternalIndex, VkImageView& outColorView, VkFormat& outColorFormat,
VkImageView& outDepthStencilView, VkFormat& outDepthStencilFormat,
VkExtent2D& outExtent) const;
private:
struct OffscreenColorTarget {
VkImage image = VK_NULL_HANDLE;
VmaAllocation allocation = nullptr;
VkImageView imageView = VK_NULL_HANDLE;
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
VkExtent2D extent = {0, 0};
VkFormat format = VK_FORMAT_UNDEFINED;
VkImage depthStencilImage = VK_NULL_HANDLE;
VmaAllocation depthStencilAllocation = nullptr;
VkImageView depthStencilImageView = VK_NULL_HANDLE;
VkImageLayout depthStencilLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VkFormat depthStencilFormat = VK_FORMAT_UNDEFINED;
Uint16 glObjectVersion = 0;
Uint colorTextureExternalIndex = 0;
};
Bool RecreateOffscreenColorTarget(OffscreenColorTarget& target,
const MG_State::GLState::FramebufferObject& glFbo,
const MG_State::GLState::FramebufferAttachmentObject& colorAttachment,
Uint16 glObjectVersion);
void DestroyOffscreenColorTarget(OffscreenColorTarget& target);
static VkFormat ResolveColorFormat(const MG_State::GLState::FramebufferAttachmentObject& colorAttachment);
static VkFormat ResolveDepthStencilFormat(
const MG_State::GLState::FramebufferAttachmentObject& depthAttachment,
const MG_State::GLState::FramebufferAttachmentObject& stencilAttachment);
VkFormat FindSupportedDepthStencilFormat(const Vector<VkFormat>& candidates) const;
VkDevice m_device = VK_NULL_HANDLE;
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
VmaAllocator m_allocator = nullptr;
UnorderedMap<Uint, OffscreenColorTarget> m_offscreenColorTargets;
};
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -62,7 +62,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
outImageInfo.sampler = VK_NULL_HANDLE;
outImageInfo.imageView = it->second.view;
outImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
// Will be VK_IMAGE_LAYOUT_UNDEFINED or VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
// Shouldn't hardcode VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL here
// outImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
return true;
}
Bool VkTextureManager::TransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image,
@@ -298,6 +300,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VK_ACCESS_TRANSFER_READ_BIT,
VK_IMAGE_ASPECT_COLOR_BIT);
MOBILEGL_ASSERT(ok, "TransitionImageLayout to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL failed");
outResource.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
return ok;
});
@@ -205,8 +205,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
CreateAllocator();
CreateCommandPool();
m_renderPassManager = MakeUnique<VkRenderPassManager>();
m_textureManager = MakeUnique<VkTextureManager>();
MOBILEGL_ASSERT(m_textureManager != nullptr, "VkTextureManager creation failed.");
auto succeeded = false;
succeeded = m_textureManager->Initialize(
{m_device, m_physicalDevice.handle, m_allocator, m_commandPool, m_graphicsQueue});
MOBILEGL_ASSERT(succeeded, "VkTextureManager initialization failed.");
m_clearManager = MakeUnique<VkClearManager>();
MOBILEGL_ASSERT(m_clearManager != nullptr, "VkClearManager creation failed.");
succeeded = m_clearManager->Initialize();
MOBILEGL_ASSERT(succeeded, "VkClearManager initialization failed.");
m_renderPassManager = MakeUnique<VkRenderPassManager>(m_device, m_config, *m_clearManager, *m_textureManager);
MOBILEGL_ASSERT(m_renderPassManager != nullptr, "VkRenderPassManager creation failed.");
succeeded = m_renderPassManager->Initialize();
MOBILEGL_ASSERT(succeeded, "VkRenderPassManager initialization failed.");
RecreateSwapchain();
@@ -214,30 +226,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MOBILEGL_ASSERT(m_pipelineFactory != nullptr, "PipelineFactory creation failed.");
m_programFactory = MakeUnique<ProgramFactory>(m_device, m_config);
MOBILEGL_ASSERT(m_programFactory != nullptr, "ProgramFactory creation failed.");
m_textureManager = MakeUnique<VkTextureManager>();
MOBILEGL_ASSERT(m_textureManager != nullptr, "VkTextureManager creation failed.");
auto succeeded = false;
succeeded = m_textureManager->Initialize(
{m_device, m_physicalDevice.handle, m_allocator, m_commandPool, m_graphicsQueue});
MOBILEGL_ASSERT(succeeded, "VkTextureManager initialization failed.");
m_samplerManager = MakeUnique<VkSamplerManager>();
MOBILEGL_ASSERT(m_samplerManager != nullptr, "VkSamplerManager creation failed.");
succeeded = m_samplerManager->Initialize({m_device, &m_config});
MOBILEGL_ASSERT(succeeded, "VkSamplerManager initialization failed.");
m_framebufferManager = MakeUnique<VkRenderTargetManager>();
MOBILEGL_ASSERT(m_framebufferManager != nullptr, "VkFramebufferManager creation failed.");
succeeded = m_framebufferManager->Initialize({m_device, m_physicalDevice.handle, m_allocator});
MOBILEGL_ASSERT(succeeded, "VkFramebufferManager initialization failed.");
m_uniformDescriptorBinder = MakeUnique<UniformDescriptorBinder>();
MOBILEGL_ASSERT(m_uniformDescriptorBinder != nullptr, "UniformDescriptorBinder creation failed.");
succeeded = m_uniformDescriptorBinder->Initialize(m_device, m_allocator,
m_physicalDevice.properties.limits.minUniformBufferOffsetAlignment,
m_config.MaxFramesInFlight, 16, 64, 4 * 1024 * 1024,
m_textureManager.get(), m_samplerManager.get(),
m_framebufferManager.get());
m_textureManager.get(), m_samplerManager.get());
MOBILEGL_ASSERT(succeeded, "UniformDescriptorBinder initialization failed.");
m_vertexInputStateFactory = MakeUnique<VertexInputStateFactory>(m_config);
MOBILEGL_ASSERT(m_vertexInputStateFactory != nullptr, "VertexInputStateFactory creation failed.");
@@ -292,11 +292,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
ShutdownSwapchain();
m_renderPassManager.reset();
if (m_framebufferManager) {
m_framebufferManager->Shutdown();
m_framebufferManager.reset();
if (m_clearManager) {
m_clearManager->Shutdown();
m_clearManager.reset();
}
if (m_commandPool != VK_NULL_HANDLE) {
vkDestroyCommandPool(m_device, m_commandPool, nullptr);
m_commandPool = VK_NULL_HANDLE;
@@ -353,6 +352,29 @@ namespace MobileGL::MG_Backend::DirectVulkan {
pending.drawFboExternalIndex = drawFboExternalIndex;
pending.targetsDefaultFramebuffer = isDefaultFramebufferTarget;
pending.mask |= requestMask;
if (m_clearManager == nullptr) {
return;
}
const auto* defaultFboInfo = MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo;
const auto* defaultFbo = defaultFboInfo ? defaultFboInfo->defaultFBO.get() : nullptr;
const MG_State::GLState::FramebufferObject* drawFbo = nullptr;
if (isDefaultFramebufferTarget) {
drawFbo = defaultFbo;
} else {
drawFbo = MG_State::pGLContext->GetFramebufferObject(drawFboExternalIndex).get();
}
if (drawFbo == nullptr) {
return;
}
m_clearManager->QueueClear(
requestMask,
{
.color = color,
.depth = depth,
.stencil = stencil,
},
*drawFbo);
}
Bool VulkanRenderer::ConsumePendingColorClear(VkClearColorValue& outClearColor) {
@@ -370,6 +392,51 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return false;
}
void VulkanRenderer::ApplyPendingClearsForActiveTarget(VkCommandBuffer commandBuffer, Uint64 drawTargetKey) {
if (m_activeRenderExtent.width == 0 || m_activeRenderExtent.height == 0) {
return;
}
auto pendingIt = m_pendingClears.find(drawTargetKey);
if (pendingIt == m_pendingClears.end()) {
return;
}
VkClearRect clearRect{};
clearRect.rect.offset = {0, 0};
clearRect.rect.extent = m_activeRenderExtent;
clearRect.baseArrayLayer = 0;
clearRect.layerCount = 1;
if ((pendingIt->second.mask & GL_COLOR_BUFFER_BIT) != 0) {
VkClearAttachment colorAttachment{};
colorAttachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
colorAttachment.colorAttachment = 0;
colorAttachment.clearValue.color = pendingIt->second.color;
vkCmdClearAttachments(commandBuffer, 1, &colorAttachment, 1, &clearRect);
pendingIt->second.mask &= ~GL_COLOR_BUFFER_BIT;
}
if ((pendingIt->second.mask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) != 0) {
VkClearAttachment depthStencilAttachment{};
depthStencilAttachment.aspectMask = 0;
if ((pendingIt->second.mask & GL_DEPTH_BUFFER_BIT) != 0) {
depthStencilAttachment.aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
}
if ((pendingIt->second.mask & GL_STENCIL_BUFFER_BIT) != 0) {
depthStencilAttachment.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
}
depthStencilAttachment.colorAttachment = 0;
depthStencilAttachment.clearValue.depthStencil.depth = pendingIt->second.depth;
depthStencilAttachment.clearValue.depthStencil.stencil = pendingIt->second.stencil;
vkCmdClearAttachments(commandBuffer, 1, &depthStencilAttachment, 1, &clearRect);
pendingIt->second.mask &= ~(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
if (pendingIt->second.mask == 0) {
m_pendingClears.erase(pendingIt);
}
}
void VulkanRenderer::TransitionSwapchainImageToColorAttachment(VkCommandBuffer commandBuffer, Uint32 imageIndex) {
auto layout = m_swapchainObject.GetImageLayout(imageIndex);
Bool ok = VkTextureManager::TransitionImageLayout(commandBuffer, m_swapchainObject.GetImage(imageIndex),
@@ -469,119 +536,42 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_swapchainObject.SetImageLayout(m_imageIndexAcquired, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
}
VkRenderPass offscreenRenderPass = VK_NULL_HANDLE;
VkFramebuffer offscreenFramebuffer = VK_NULL_HANDLE;
VkExtent2D offscreenExtent{};
VkFormat offscreenDepthStencilFormat = VK_FORMAT_UNDEFINED;
if (!EnsureOffscreenRenderTarget(drawFboExternalIndex, *drawFbo, offscreenRenderPass, offscreenFramebuffer,
offscreenExtent, offscreenDepthStencilFormat)) {
MGLOG_D("EnsureFrameRecordingStarted skipped: offscreen render target for FBO %u is unavailable",
drawFboExternalIndex);
return;
}
VkImage offscreenColorImage = VK_NULL_HANDLE;
VkImageLayout* offscreenColorLayout = nullptr;
VkImage offscreenDepthStencilImage = VK_NULL_HANDLE;
VkImageLayout* offscreenDepthStencilLayout = nullptr;
VkFormat offscreenDepthStencilStateFormat = VK_FORMAT_UNDEFINED;
if (!m_framebufferManager->GetOffscreenRenderSurfaceState(
drawFboExternalIndex, offscreenColorImage, offscreenColorLayout, offscreenDepthStencilImage,
offscreenDepthStencilLayout, offscreenDepthStencilStateFormat) ||
offscreenColorLayout == nullptr) {
MGLOG_D("EnsureFrameRecordingStarted skipped: failed to transition offscreen FBO %u for attachment",
drawFboExternalIndex);
return;
}
if (!VkTextureManager::TransitionImageLayout(
commandBuffer, offscreenColorImage, *offscreenColorLayout, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0,
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_IMAGE_ASPECT_COLOR_BIT)) {
MGLOG_D("EnsureFrameRecordingStarted skipped: failed to transition offscreen color for FBO %u",
drawFboExternalIndex);
return;
}
if (offscreenDepthStencilImage != VK_NULL_HANDLE && offscreenDepthStencilLayout != nullptr) {
VkImageAspectFlags depthStencilAspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
if (offscreenDepthStencilStateFormat == VK_FORMAT_D24_UNORM_S8_UINT ||
offscreenDepthStencilStateFormat == VK_FORMAT_D32_SFLOAT_S8_UINT) {
depthStencilAspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
}
if (!VkTextureManager::TransitionImageLayout(
commandBuffer, offscreenDepthStencilImage, *offscreenDepthStencilLayout,
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, 0,
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
depthStencilAspectMask)) {
MGLOG_D("EnsureFrameRecordingStarted skipped: failed to transition offscreen depth/stencil for FBO %u",
drawFboExternalIndex);
return;
}
}
m_renderPassManager->BeginRenderPass(commandBuffer, offscreenRenderPass, offscreenFramebuffer,
offscreenExtent);
MOBILEGL_ASSERT(m_renderPassManager != nullptr, "EnsureFrameRecordingStarted: manager is null");
auto& renderPassEntry = m_renderPassManager->GetOrCreateRenderPass(*drawFbo);
Bool started = m_renderPassManager->StartRenderPass(commandBuffer, renderPassEntry);
MOBILEGL_ASSERT(started, "EnsureFrameRecordingStarted: StartRenderPass for offscreen target failed");
m_isMainRenderPassActive = true;
m_activeRenderPass = offscreenRenderPass;
m_activeRenderExtent = offscreenExtent;
m_activeDepthStencilFormat = offscreenDepthStencilFormat;
m_activeRenderPass = renderPassEntry.renderPass;
m_activeRenderExtent = {
static_cast<Uint32>(renderPassEntry.extent.x() > 0 ? renderPassEntry.extent.x() : 0),
static_cast<Uint32>(renderPassEntry.extent.y() > 0 ? renderPassEntry.extent.y() : 0)};
m_activeDepthStencilFormat = VK_FORMAT_UNDEFINED;
m_activeRenderTargetIsDefault = false;
m_activeDrawFboExternalIndex = drawFboExternalIndex;
auto pendingIt = m_pendingClears.find(drawTargetKey);
if (pendingIt != m_pendingClears.end()) {
if ((pendingIt->second.mask & GL_COLOR_BUFFER_BIT) != 0) {
m_renderPassManager->RecordColorClear(commandBuffer, m_activeRenderExtent, pendingIt->second.color);
pendingIt->second.mask &= ~GL_COLOR_BUFFER_BIT;
}
if ((pendingIt->second.mask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) != 0) {
m_renderPassManager->RecordDepthStencilClear(commandBuffer, m_activeRenderExtent,
pendingIt->second.mask, pendingIt->second.depth,
pendingIt->second.stencil, m_activeDepthStencilFormat);
pendingIt->second.mask &= ~(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
if (pendingIt->second.mask == 0) {
m_pendingClears.erase(pendingIt);
}
}
ApplyPendingClearsForActiveTarget(commandBuffer, drawTargetKey);
return;
}
TransitionSwapchainImageToColorAttachment(commandBuffer, m_imageIndexAcquired);
TransitionDepthStencilImageToAttachment(commandBuffer, m_imageIndexAcquired);
VkRenderPass defaultRenderPass = VK_NULL_HANDLE;
VkFramebuffer defaultFramebuffer = VK_NULL_HANDLE;
VkExtent2D defaultExtent{};
VkFormat defaultDepthStencilFormat = VK_FORMAT_UNDEFINED;
if (!GetDefaultRenderTargetForCurrentImage(defaultRenderPass, defaultFramebuffer, defaultExtent,
defaultDepthStencilFormat)) {
if (!defaultFbo) {
MGLOG_D("EnsureFrameRecordingStarted skipped: default render target unavailable");
return;
}
MOBILEGL_ASSERT(m_renderPassManager != nullptr, "EnsureFrameRecordingStarted: manager is null");
m_renderPassManager->BeginRenderPass(commandBuffer, defaultRenderPass, defaultFramebuffer, defaultExtent);
auto& renderPassEntry = m_renderPassManager->GetOrCreateRenderPass(*defaultFbo);
Bool started = m_renderPassManager->StartRenderPass(commandBuffer, renderPassEntry);
MOBILEGL_ASSERT(started, "EnsureFrameRecordingStarted: StartRenderPass for default target failed");
m_isMainRenderPassActive = true;
m_activeRenderPass = defaultRenderPass;
m_activeRenderExtent = defaultExtent;
m_activeDepthStencilFormat = defaultDepthStencilFormat;
m_activeRenderPass = renderPassEntry.renderPass;
m_activeRenderExtent = {
static_cast<Uint32>(renderPassEntry.extent.x() > 0 ? renderPassEntry.extent.x() : 0),
static_cast<Uint32>(renderPassEntry.extent.y() > 0 ? renderPassEntry.extent.y() : 0)};
m_activeDepthStencilFormat = m_swapchainObject.GetDepthStencilFormat();
m_activeRenderTargetIsDefault = true;
m_activeDrawFboExternalIndex = drawFboExternalIndex;
auto pendingIt = m_pendingClears.find(drawTargetKey);
if (pendingIt != m_pendingClears.end()) {
if ((pendingIt->second.mask & GL_COLOR_BUFFER_BIT) != 0) {
m_renderPassManager->RecordColorClear(commandBuffer, m_activeRenderExtent, pendingIt->second.color);
pendingIt->second.mask &= ~GL_COLOR_BUFFER_BIT;
}
if ((pendingIt->second.mask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) != 0) {
m_renderPassManager->RecordDepthStencilClear(commandBuffer, m_activeRenderExtent,
pendingIt->second.mask, pendingIt->second.depth,
pendingIt->second.stencil, m_activeDepthStencilFormat);
pendingIt->second.mask &= ~(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
if (pendingIt->second.mask == 0) {
m_pendingClears.erase(pendingIt);
}
}
ApplyPendingClearsForActiveTarget(commandBuffer, drawTargetKey);
}
void VulkanRenderer::EndFrameRecordingIfNeeded() {
@@ -1227,71 +1217,31 @@ namespace MobileGL::MG_Backend::DirectVulkan {
TransitionSwapchainImageToColorAttachment(commandBuffer, m_imageIndexAcquired);
TransitionDepthStencilImageToAttachment(commandBuffer, m_imageIndexAcquired);
VkRenderPass renderPass = VK_NULL_HANDLE;
VkFramebuffer framebuffer = VK_NULL_HANDLE;
VkExtent2D extent{};
VkFormat depthStencilFormat = VK_FORMAT_UNDEFINED;
if (!GetDefaultRenderTargetForCurrentImage(renderPass, framebuffer, extent, depthStencilFormat)) {
const auto* defaultFboInfo = MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo;
const auto* defaultFbo = defaultFboInfo ? defaultFboInfo->defaultFBO.get() : nullptr;
if (defaultFbo == nullptr) {
return false;
}
auto& renderPassEntry = m_renderPassManager->GetOrCreateRenderPass(*defaultFbo);
const Bool started = m_renderPassManager->StartRenderPass(commandBuffer, renderPassEntry);
if (!started) {
return false;
}
m_renderPassManager->BeginRenderPass(commandBuffer, renderPass, framebuffer, extent);
m_isMainRenderPassActive = true;
m_activeRenderPass = renderPass;
m_activeRenderExtent = extent;
m_activeDepthStencilFormat = depthStencilFormat;
m_activeRenderPass = renderPassEntry.renderPass;
m_activeRenderExtent = {
static_cast<Uint32>(renderPassEntry.extent.x() > 0 ? renderPassEntry.extent.x() : 0),
static_cast<Uint32>(renderPassEntry.extent.y() > 0 ? renderPassEntry.extent.y() : 0)};
m_activeDepthStencilFormat = m_swapchainObject.GetDepthStencilFormat();
m_activeRenderTargetIsDefault = true;
m_activeDrawFboExternalIndex = 0;
return true;
}
MOBILEGL_ASSERT(m_framebufferManager != nullptr, "Present: framebuffer manager is null");
const auto pendingFbo = MG_State::pGLContext->GetFramebufferObject(targetFboExternalIndex);
if (!pendingFbo) {
return false;
}
VkRenderPass offscreenRenderPass = VK_NULL_HANDLE;
VkFramebuffer offscreenFramebuffer = VK_NULL_HANDLE;
VkExtent2D offscreenExtent{};
VkFormat offscreenDepthStencilFormat = VK_FORMAT_UNDEFINED;
if (!EnsureOffscreenRenderTarget(targetFboExternalIndex, *pendingFbo, offscreenRenderPass,
offscreenFramebuffer, offscreenExtent, offscreenDepthStencilFormat)) {
return false;
}
VkImage offscreenColorImage = VK_NULL_HANDLE;
VkImageLayout* offscreenColorLayout = nullptr;
VkImage offscreenDepthStencilImage = VK_NULL_HANDLE;
VkImageLayout* offscreenDepthStencilLayout = nullptr;
VkFormat offscreenDepthStencilStateFormat = VK_FORMAT_UNDEFINED;
if (!m_framebufferManager->GetOffscreenRenderSurfaceState(
targetFboExternalIndex, offscreenColorImage, offscreenColorLayout, offscreenDepthStencilImage,
offscreenDepthStencilLayout, offscreenDepthStencilStateFormat) ||
offscreenColorLayout == nullptr) {
return false;
}
if (!VkTextureManager::TransitionImageLayout(
commandBuffer, offscreenColorImage, *offscreenColorLayout,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0,
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_IMAGE_ASPECT_COLOR_BIT)) {
return false;
}
if (offscreenDepthStencilImage != VK_NULL_HANDLE && offscreenDepthStencilLayout != nullptr) {
VkImageAspectFlags depthStencilAspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
if (offscreenDepthStencilStateFormat == VK_FORMAT_D24_UNORM_S8_UINT ||
offscreenDepthStencilStateFormat == VK_FORMAT_D32_SFLOAT_S8_UINT) {
depthStencilAspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
}
if (!VkTextureManager::TransitionImageLayout(
commandBuffer, offscreenDepthStencilImage, *offscreenDepthStencilLayout,
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, 0,
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
depthStencilAspectMask)) {
return false;
}
}
const auto swapchainOldLayout = m_swapchainObject.GetImageLayout(m_imageIndexAcquired);
if (swapchainOldLayout != VK_IMAGE_LAYOUT_PRESENT_SRC_KHR &&
@@ -1316,12 +1266,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_swapchainObject.SetImageLayout(m_imageIndexAcquired, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
}
m_renderPassManager->BeginRenderPass(commandBuffer, offscreenRenderPass, offscreenFramebuffer,
offscreenExtent);
auto& renderPassEntry = m_renderPassManager->GetOrCreateRenderPass(*pendingFbo);
const Bool started = m_renderPassManager->StartRenderPass(commandBuffer, renderPassEntry);
if (!started) {
return false;
}
m_isMainRenderPassActive = true;
m_activeRenderPass = offscreenRenderPass;
m_activeRenderExtent = offscreenExtent;
m_activeDepthStencilFormat = offscreenDepthStencilFormat;
m_activeRenderPass = renderPassEntry.renderPass;
m_activeRenderExtent = {
static_cast<Uint32>(renderPassEntry.extent.x() > 0 ? renderPassEntry.extent.x() : 0),
static_cast<Uint32>(renderPassEntry.extent.y() > 0 ? renderPassEntry.extent.y() : 0)};
m_activeDepthStencilFormat = VK_FORMAT_UNDEFINED;
m_activeRenderTargetIsDefault = false;
m_activeDrawFboExternalIndex = targetFboExternalIndex;
return true;
@@ -1353,20 +1308,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
continue;
}
if ((activePendingIt->second.mask & GL_COLOR_BUFFER_BIT) != 0) {
m_renderPassManager->RecordColorClear(frame.commandBuffer, m_activeRenderExtent,
activePendingIt->second.color);
activePendingIt->second.mask &= ~GL_COLOR_BUFFER_BIT;
}
if ((activePendingIt->second.mask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) != 0) {
m_renderPassManager->RecordDepthStencilClear(
frame.commandBuffer, m_activeRenderExtent, activePendingIt->second.mask,
activePendingIt->second.depth, activePendingIt->second.stencil, m_activeDepthStencilFormat);
activePendingIt->second.mask &= ~(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
if (activePendingIt->second.mask == 0) {
m_pendingClears.erase(activePendingIt);
}
ApplyPendingClearsForActiveTarget(frame.commandBuffer, activeKey);
}
}
EndFrameRecordingIfNeeded();
@@ -1777,51 +1719,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MGLOG_I("Command pool created");
}
Bool VulkanRenderer::GetDefaultRenderTargetForCurrentImage(VkRenderPass& outRenderPass,
VkFramebuffer& outFramebuffer, VkExtent2D& outExtent,
VkFormat& outDepthStencilFormat) const {
MOBILEGL_ASSERT(m_renderPassManager != nullptr, "GetDefaultRenderTargetForCurrentImage: manager is null");
return m_renderPassManager->GetDefaultRenderTarget(m_imageIndexAcquired, outRenderPass, outFramebuffer,
outExtent, outDepthStencilFormat);
}
Bool VulkanRenderer::EnsureOffscreenRenderTarget(Uint glFboExternalIndex,
const MG_State::GLState::FramebufferObject& glFbo,
VkRenderPass& outRenderPass, VkFramebuffer& outFramebuffer,
VkExtent2D& outExtent, VkFormat& outDepthStencilFormat) {
MOBILEGL_ASSERT(m_framebufferManager != nullptr, "EnsureOffscreenRenderTarget: framebuffer manager is null");
MOBILEGL_ASSERT(m_renderPassManager != nullptr, "EnsureOffscreenRenderTarget: render pass manager is null");
if (!m_framebufferManager->EnsureOffscreenColorTarget(glFboExternalIndex, glFbo)) {
return false;
}
VkImageView colorView = VK_NULL_HANDLE;
VkFormat colorFormat = VK_FORMAT_UNDEFINED;
VkImageView depthStencilView = VK_NULL_HANDLE;
VkFormat depthStencilFormat = VK_FORMAT_UNDEFINED;
VkExtent2D extent{};
if (!m_framebufferManager->GetOffscreenRenderSurface(glFboExternalIndex, colorView, colorFormat,
depthStencilView, depthStencilFormat, extent)) {
return false;
}
VkRenderPassManager::OffscreenRenderTargetInfo targetInfo{};
targetInfo.targetExternalIndex = glFboExternalIndex;
targetInfo.targetVersion = glFbo.GetObjectVersion();
targetInfo.colorView = colorView;
targetInfo.colorFormat = colorFormat;
targetInfo.depthStencilView = depthStencilView;
targetInfo.depthStencilFormat = depthStencilFormat;
targetInfo.extent = extent;
if (!m_renderPassManager->EnsureOffscreenRenderTarget(targetInfo)) {
return false;
}
return m_renderPassManager->GetOffscreenRenderTarget(glFboExternalIndex, outRenderPass, outFramebuffer,
outExtent, outDepthStencilFormat);
}
void VulkanRenderer::CreateSurface() {
#if defined VK_USE_PLATFORM_ANDROID_KHR
auto* nativeWindow = static_cast<ANativeWindow*>(m_window);
@@ -1990,16 +1887,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
static_cast<Uint32>(m_swapchainObject.GetImageCount())),
"RecreateSwapchain, InitializeSwapchainSemaphores");
MOBILEGL_ASSERT(m_renderPassManager != nullptr, "RecreateSwapchain: render pass manager is null");
VkRenderPassManager::InitInfo renderPassInitInfo{};
renderPassInitInfo.device = m_device;
renderPassInitInfo.colorFormat = m_swapchainObject.GetSurfaceFormat().format;
renderPassInitInfo.depthStencilFormat = m_swapchainObject.GetDepthStencilFormat();
Bool ok = m_renderPassManager->Initialize(renderPassInitInfo);
Bool ok = m_renderPassManager->Initialize();
MOBILEGL_ASSERT(ok, "RecreateSwapchain: render pass manager initialization failed");
ok = m_renderPassManager->RecreateDefaultFramebuffers(m_swapchainObject.GetImageViews(),
m_swapchainObject.GetDepthStencilImageViews(),
m_swapchainObject.GetExtent());
MOBILEGL_ASSERT(ok, "RecreateSwapchain: default framebuffers initialization failed");
if (m_pipelineFactory) {
m_pipelineFactory->DestroyAll();
}
@@ -15,7 +15,7 @@
#include "UniformDescriptorBinder.h"
#include "VertexInputStateFactory.h"
#include "VkBufferObject.h"
#include "VkRenderTargetManager.h"
#include "VkClearManager.h"
#include "VkRenderPassManager.h"
#include "VkSamplerManager.h"
#include "VkTextureManager.h"
@@ -143,7 +143,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
UniquePtr<ProgramFactory> m_programFactory;
UniquePtr<UniformDescriptorBinder> m_uniformDescriptorBinder;
UniquePtr<VertexInputStateFactory> m_vertexInputStateFactory;
UniquePtr<VkRenderTargetManager> m_framebufferManager;
UniquePtr<VkClearManager> m_clearManager;
UniquePtr<VkRenderPassManager> m_renderPassManager;
UniquePtr<VkTextureManager> m_textureManager;
UniquePtr<VkSamplerManager> m_samplerManager;
@@ -160,11 +160,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void CreateSwapchain();
void CreateCommandPool();
void CreateFrameContexts();
Bool GetDefaultRenderTargetForCurrentImage(VkRenderPass& outRenderPass, VkFramebuffer& outFramebuffer,
VkExtent2D& outExtent, VkFormat& outDepthStencilFormat) const;
Bool EnsureOffscreenRenderTarget(Uint glFboExternalIndex, const MG_State::GLState::FramebufferObject& glFbo,
VkRenderPass& outRenderPass, VkFramebuffer& outFramebuffer,
VkExtent2D& outExtent, VkFormat& outDepthStencilFormat);
VkPipeline GetOrCreatePipeline(const MG_State::GLState::ProgramObject& program, VkPipelineLayout pipelineLayout,
Uint64 vertexInputHash,
const VkPipelineVertexInputStateCreateInfo& vertexInputState);
@@ -177,6 +172,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkDeviceSize minCapacity, VkBufferUsageFlags usage);
Bool UploadAndBindVertexStreams(const VertexInputStateFactory::BackendVertexInputState& vertexInputState,
const DrawArrayPayload& payload, VkCommandBuffer commandBuffer);
void ApplyPendingClearsForActiveTarget(VkCommandBuffer commandBuffer, Uint64 drawTargetKey);
void ShutdownSwapchain();
@@ -0,0 +1,436 @@
// MobileGL - MobileGL/MG_Util/Converters/MGToVk/TextureEnumConverter.cpp
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
#include "TextureEnumConverter.h"
#include "MG_Util/Types.h"
namespace MobileGL {
namespace MG_Util {
VkImageViewType ConvertTextureTargetToVkEnum(TextureTarget target) {
switch (target) {
case TextureTarget::Texture1D:
return VK_IMAGE_VIEW_TYPE_1D;
case TextureTarget::Texture2D:
return VK_IMAGE_VIEW_TYPE_2D;
case TextureTarget::Texture3D:
return VK_IMAGE_VIEW_TYPE_3D;
case TextureTarget::TextureCubeMap:
return VK_IMAGE_VIEW_TYPE_CUBE;
case TextureTarget::Texture2DArray:
return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
case TextureTarget::Texture2DMultisample:
return VK_IMAGE_VIEW_TYPE_2D;
case TextureTarget::TextureCubeMapArray:
return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
case TextureTarget::Texture1DArray:
return VK_IMAGE_VIEW_TYPE_1D_ARRAY;
case TextureTarget::TextureRectangle:
return VK_IMAGE_VIEW_TYPE_2D;
case TextureTarget::Texture2DMultisampleArray:
return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
case TextureTarget::TextureBuffer:
default:
return VK_IMAGE_VIEW_TYPE_MAX_ENUM;
}
}
VkFormat ConvertTextureInputFormatToVkEnum(TextureInputFormat format) {
switch (format) {
case TextureInputFormat::Red:
return VK_FORMAT_R8_UNORM;
case TextureInputFormat::RG:
return VK_FORMAT_R8G8_UNORM;
case TextureInputFormat::RGB:
return VK_FORMAT_R8G8B8_UNORM;
case TextureInputFormat::BGR:
return VK_FORMAT_B8G8R8_UNORM;
case TextureInputFormat::RGBA:
return VK_FORMAT_R8G8B8A8_UNORM;
case TextureInputFormat::BGRA:
return VK_FORMAT_B8G8R8A8_UNORM;
case TextureInputFormat::RInteger:
return VK_FORMAT_R8_UINT;
case TextureInputFormat::RGInteger:
return VK_FORMAT_R8G8_UINT;
case TextureInputFormat::RGBInteger:
return VK_FORMAT_R8G8B8_UINT;
case TextureInputFormat::BGRInteger:
return VK_FORMAT_B8G8R8_UINT;
case TextureInputFormat::RGBAInteger:
return VK_FORMAT_R8G8B8A8_UINT;
case TextureInputFormat::BGRAInteger:
return VK_FORMAT_B8G8R8A8_UINT;
case TextureInputFormat::StencilIndex:
return VK_FORMAT_S8_UINT;
case TextureInputFormat::DepthComponent:
return VK_FORMAT_D32_SFLOAT;
case TextureInputFormat::DepthStencil:
return VK_FORMAT_D24_UNORM_S8_UINT;
default:
return VK_FORMAT_UNDEFINED;
}
}
VkFormat ConvertTextureInternalFormatToVkEnum(TextureInternalFormat format) {
switch (format) {
case TextureInternalFormat::R8:
return VK_FORMAT_R8_UNORM;
case TextureInternalFormat::R8Snorm:
return VK_FORMAT_R8_SNORM;
case TextureInternalFormat::R16:
return VK_FORMAT_R16_UNORM;
case TextureInternalFormat::R16Snorm:
return VK_FORMAT_R16_SNORM;
case TextureInternalFormat::RG8:
return VK_FORMAT_R8G8_UNORM;
case TextureInternalFormat::RG8Snorm:
return VK_FORMAT_R8G8_SNORM;
case TextureInternalFormat::RG16:
return VK_FORMAT_R16G16_UNORM;
case TextureInternalFormat::RG16Snorm:
return VK_FORMAT_R16G16_SNORM;
case TextureInternalFormat::R3G3B2:
return VK_FORMAT_UNDEFINED;
case TextureInternalFormat::RGB4:
return VK_FORMAT_UNDEFINED;
case TextureInternalFormat::RGB5:
return VK_FORMAT_UNDEFINED;
case TextureInternalFormat::RGB8:
return VK_FORMAT_R8G8B8_UNORM;
case TextureInternalFormat::RGB8Snorm:
return VK_FORMAT_R8G8B8_SNORM;
case TextureInternalFormat::RGB10:
return VK_FORMAT_UNDEFINED;
case TextureInternalFormat::RGB12:
return VK_FORMAT_UNDEFINED;
case TextureInternalFormat::RGB16:
return VK_FORMAT_UNDEFINED;
case TextureInternalFormat::RGB16Snorm:
return VK_FORMAT_UNDEFINED;
case TextureInternalFormat::RGBA2:
return VK_FORMAT_UNDEFINED;
case TextureInternalFormat::RGBA4:
return VK_FORMAT_R4G4B4A4_UNORM_PACK16;
case TextureInternalFormat::RGB5A1:
return VK_FORMAT_R5G5B5A1_UNORM_PACK16;
case TextureInternalFormat::RGBA8:
return VK_FORMAT_R8G8B8A8_UNORM;
case TextureInternalFormat::RGBA8Snorm:
return VK_FORMAT_R8G8B8A8_SNORM;
case TextureInternalFormat::RGB10A2:
return VK_FORMAT_A2R10G10B10_UNORM_PACK32;
case TextureInternalFormat::RGB10A2UI:
return VK_FORMAT_A2R10G10B10_UINT_PACK32;
case TextureInternalFormat::RGBA12:
return VK_FORMAT_UNDEFINED;
case TextureInternalFormat::RGBA16:
return VK_FORMAT_R16G16B16A16_UNORM;
case TextureInternalFormat::SRGB8:
return VK_FORMAT_R8G8B8_SRGB;
case TextureInternalFormat::SRGB8Alpha8:
return VK_FORMAT_R8G8B8A8_SRGB;
case TextureInternalFormat::R16F:
return VK_FORMAT_R16_SFLOAT;
case TextureInternalFormat::RG16F:
return VK_FORMAT_R16G16_SFLOAT;
case TextureInternalFormat::RGB16F:
return VK_FORMAT_R16G16B16_SFLOAT;
case TextureInternalFormat::RGBA16F:
return VK_FORMAT_R16G16B16A16_SFLOAT;
case TextureInternalFormat::R32F:
return VK_FORMAT_R32_SFLOAT;
case TextureInternalFormat::RG32F:
return VK_FORMAT_R32G32_SFLOAT;
case TextureInternalFormat::RGB32F:
return VK_FORMAT_R32G32B32_SFLOAT;
case TextureInternalFormat::RGBA32F:
return VK_FORMAT_R32G32B32A32_SFLOAT;
case TextureInternalFormat::R11FG11FB10F:
return VK_FORMAT_B10G11R11_UFLOAT_PACK32;
case TextureInternalFormat::RGB9E5:
return VK_FORMAT_E5B9G9R9_UFLOAT_PACK32;
case TextureInternalFormat::R8I:
return VK_FORMAT_R8_SINT;
case TextureInternalFormat::R8UI:
return VK_FORMAT_R8_UINT;
case TextureInternalFormat::R16I:
return VK_FORMAT_R16_SINT;
case TextureInternalFormat::R16UI:
return VK_FORMAT_R16_UINT;
case TextureInternalFormat::R32I:
return VK_FORMAT_R32_SINT;
case TextureInternalFormat::R32UI:
return VK_FORMAT_R32_UINT;
case TextureInternalFormat::RG8I:
return VK_FORMAT_R8G8_SINT;
case TextureInternalFormat::RG8UI:
return VK_FORMAT_R8G8_UINT;
case TextureInternalFormat::RG16I:
return VK_FORMAT_R16G16_SINT;
case TextureInternalFormat::RG16UI:
return VK_FORMAT_R16G16_UINT;
case TextureInternalFormat::RG32I:
return VK_FORMAT_R32G32_SINT;
case TextureInternalFormat::RG32UI:
return VK_FORMAT_R32G32_UINT;
case TextureInternalFormat::RGB8I:
return VK_FORMAT_R8G8B8_SINT;
case TextureInternalFormat::RGB8UI:
return VK_FORMAT_R8G8B8_UINT;
case TextureInternalFormat::RGB16I:
return VK_FORMAT_R16G16B16_SINT;
case TextureInternalFormat::RGB16UI:
return VK_FORMAT_R16G16B16_UINT;
case TextureInternalFormat::RGB32I:
return VK_FORMAT_R32G32B32_SINT;
case TextureInternalFormat::RGB32UI:
return VK_FORMAT_R32G32B32_UINT;
case TextureInternalFormat::RGBA8I:
return VK_FORMAT_R8G8B8A8_SINT;
case TextureInternalFormat::RGBA8UI:
return VK_FORMAT_R8G8B8A8_UINT;
case TextureInternalFormat::RGBA16I:
return VK_FORMAT_R16G16B16A16_SINT;
case TextureInternalFormat::RGBA16UI:
return VK_FORMAT_R16G16B16A16_UINT;
case TextureInternalFormat::RGBA32I:
return VK_FORMAT_R32G32B32A32_SINT;
case TextureInternalFormat::RGBA32UI:
return VK_FORMAT_R32G32B32A32_UINT;
case TextureInternalFormat::DepthComponent:
return VK_FORMAT_D32_SFLOAT;
case TextureInternalFormat::DepthComponent16:
return VK_FORMAT_D16_UNORM;
case TextureInternalFormat::DepthComponent24:
return VK_FORMAT_X8_D24_UNORM_PACK32;
case TextureInternalFormat::DepthComponent32F:
return VK_FORMAT_D32_SFLOAT;
case TextureInternalFormat::Depth24Stencil8:
return VK_FORMAT_D24_UNORM_S8_UINT;
case TextureInternalFormat::Depth32FStencil8:
return VK_FORMAT_D32_SFLOAT_S8_UINT;
case TextureInternalFormat::DepthComponent32:
return VK_FORMAT_D32_SFLOAT;
case TextureInternalFormat::DepthStencil:
return VK_FORMAT_D24_UNORM_S8_UINT;
case TextureInternalFormat::Red:
return VK_FORMAT_R8_UNORM;
case TextureInternalFormat::RG:
return VK_FORMAT_R8G8_UNORM;
case TextureInternalFormat::RGB:
return VK_FORMAT_R8G8B8_UNORM;
case TextureInternalFormat::RGBA:
return VK_FORMAT_R8G8B8A8_UNORM;
default:
return VK_FORMAT_UNDEFINED;
}
}
VkFormat ConvertTexturePixelDataTypeToVkEnum(TexturePixelDataType type) {
switch (type) {
case TexturePixelDataType::UnsignedByte:
return VK_FORMAT_R8_UINT;
case TexturePixelDataType::Byte:
return VK_FORMAT_R8_SINT;
case TexturePixelDataType::UnsignedShort:
return VK_FORMAT_R16_UINT;
case TexturePixelDataType::Short:
return VK_FORMAT_R16_SINT;
case TexturePixelDataType::UnsignedInt:
return VK_FORMAT_R32_UINT;
case TexturePixelDataType::Int:
return VK_FORMAT_R32_SINT;
case TexturePixelDataType::Float:
return VK_FORMAT_R32_SFLOAT;
case TexturePixelDataType::HalfFloat:
return VK_FORMAT_R16_SFLOAT;
case TexturePixelDataType::UnsignedInt248:
return VK_FORMAT_D24_UNORM_S8_UINT;
case TexturePixelDataType::UnsignedByte332:
return VK_FORMAT_R8_UINT;
case TexturePixelDataType::UnsignedByte233Rev:
return VK_FORMAT_R8_UINT;
case TexturePixelDataType::UnsignedShort565:
return VK_FORMAT_R5G6B5_UNORM_PACK16;
case TexturePixelDataType::UnsignedShort565Rev:
return VK_FORMAT_R5G6B5_UNORM_PACK16;
case TexturePixelDataType::UnsignedShort4444:
return VK_FORMAT_R4G4B4A4_UNORM_PACK16;
case TexturePixelDataType::UnsignedShort4444Rev:
return VK_FORMAT_R4G4B4A4_UNORM_PACK16;
case TexturePixelDataType::UnsignedShort5551:
return VK_FORMAT_R5G5B5A1_UNORM_PACK16;
case TexturePixelDataType::UnsignedShort1555Rev:
return VK_FORMAT_R5G5B5A1_UNORM_PACK16;
case TexturePixelDataType::UnsignedInt8888:
return VK_FORMAT_R8G8B8A8_UINT;
case TexturePixelDataType::UnsignedInt8888Rev:
return VK_FORMAT_R8G8B8A8_UINT;
case TexturePixelDataType::UnsignedInt1010102:
return VK_FORMAT_A2R10G10B10_UINT_PACK32;
case TexturePixelDataType::UnsignedInt2101010Rev:
return VK_FORMAT_A2R10G10B10_UINT_PACK32;
case TexturePixelDataType::UnsignedInt101111Rev:
return VK_FORMAT_B10G11R11_UFLOAT_PACK32;
case TexturePixelDataType::UnsignedInt5999Rev:
return VK_FORMAT_E5B9G9R9_UFLOAT_PACK32;
case TexturePixelDataType::Float32UnsignedInt248Rev:
return VK_FORMAT_D32_SFLOAT_S8_UINT;
default:
return VK_FORMAT_UNDEFINED;
}
}
VkImageViewType ConvertTextureUploadTargetToVkEnum(TextureUploadTarget target) {
switch (target) {
case TextureUploadTarget::Texture2D:
return VK_IMAGE_VIEW_TYPE_2D;
case TextureUploadTarget::Texture1D:
return VK_IMAGE_VIEW_TYPE_1D;
case TextureUploadTarget::Texture3D:
return VK_IMAGE_VIEW_TYPE_3D;
case TextureUploadTarget::ProxyTexture2D:
return VK_IMAGE_VIEW_TYPE_2D;
case TextureUploadTarget::Texture1DArray:
return VK_IMAGE_VIEW_TYPE_1D_ARRAY;
case TextureUploadTarget::ProxyTexture1DArray:
return VK_IMAGE_VIEW_TYPE_1D_ARRAY;
case TextureUploadTarget::TextureRectangle:
return VK_IMAGE_VIEW_TYPE_2D;
case TextureUploadTarget::ProxyTextureRectangle:
return VK_IMAGE_VIEW_TYPE_2D;
case TextureUploadTarget::CubeMapPositiveX:
return VK_IMAGE_VIEW_TYPE_CUBE;
case TextureUploadTarget::CubeMapNegativeX:
return VK_IMAGE_VIEW_TYPE_CUBE;
case TextureUploadTarget::CubeMapPositiveY:
return VK_IMAGE_VIEW_TYPE_CUBE;
case TextureUploadTarget::CubeMapNegativeY:
return VK_IMAGE_VIEW_TYPE_CUBE;
case TextureUploadTarget::CubeMapPositiveZ:
return VK_IMAGE_VIEW_TYPE_CUBE;
case TextureUploadTarget::CubeMapNegativeZ:
return VK_IMAGE_VIEW_TYPE_CUBE;
case TextureUploadTarget::ProxyCubeMap:
return VK_IMAGE_VIEW_TYPE_CUBE;
case TextureUploadTarget::Texture2DMultisample:
return VK_IMAGE_VIEW_TYPE_2D;
case TextureUploadTarget::ProxyTexture2DMultisample:
return VK_IMAGE_VIEW_TYPE_2D;
case TextureUploadTarget::CubeMapArray:
return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
case TextureUploadTarget::ProxyCubeMapArray:
return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
case TextureUploadTarget::Texture2DArray:
return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
case TextureUploadTarget::ProxyTexture2DArray:
return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
case TextureUploadTarget::Texture2DMultisampleArray:
return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
case TextureUploadTarget::ProxyTexture2DMultisampleArray:
return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
default:
return VK_IMAGE_VIEW_TYPE_MAX_ENUM;
}
}
VkFilter ConvertSamplerFilterModeToVkEnum(SamplerFilterMode filter, SamplerMipmapMode mipmap) {
(void)mipmap;
switch (filter) {
case SamplerFilterMode::Nearest:
return VK_FILTER_NEAREST;
case SamplerFilterMode::Linear:
return VK_FILTER_LINEAR;
default:
return VK_FILTER_NEAREST;
}
}
VkSamplerMipmapMode ConvertSamplerMipmapModeToVkEnum(SamplerMipmapMode v) {
switch (v) {
case SamplerMipmapMode::Nearest:
return VK_SAMPLER_MIPMAP_MODE_NEAREST;
case SamplerMipmapMode::Linear:
return VK_SAMPLER_MIPMAP_MODE_LINEAR;
case SamplerMipmapMode::None:
default:
return VK_SAMPLER_MIPMAP_MODE_NEAREST;
}
}
VkSamplerAddressMode ConvertSamplerWrapModeToVkEnum(SamplerWrapMode v) {
switch (v) {
case SamplerWrapMode::ClampToEdge:
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
case SamplerWrapMode::MirroredRepeat:
return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
case SamplerWrapMode::Repeat:
return VK_SAMPLER_ADDRESS_MODE_REPEAT;
case SamplerWrapMode::ClampToBorder:
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
case SamplerWrapMode::MirrorClampToEdge:
return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE;
default:
return VK_SAMPLER_ADDRESS_MODE_REPEAT;
}
}
VkBool32 ConvertSamplerCompareModeToVkEnum(SamplerCompareMode v) {
switch (v) {
case SamplerCompareMode::None:
return VK_FALSE;
case SamplerCompareMode::CompareToTexture:
return VK_TRUE;
default:
return VK_FALSE;
}
}
VkCompareOp ConvertSamplerCompareFuncToVkEnum(SamplerCompareFunc v) {
switch (v) {
case SamplerCompareFunc::Never:
return VK_COMPARE_OP_NEVER;
case SamplerCompareFunc::Less:
return VK_COMPARE_OP_LESS;
case SamplerCompareFunc::Equal:
return VK_COMPARE_OP_EQUAL;
case SamplerCompareFunc::LessEqual:
return VK_COMPARE_OP_LESS_OR_EQUAL;
case SamplerCompareFunc::Greater:
return VK_COMPARE_OP_GREATER;
case SamplerCompareFunc::NotEqual:
return VK_COMPARE_OP_NOT_EQUAL;
case SamplerCompareFunc::GreaterEqual:
return VK_COMPARE_OP_GREATER_OR_EQUAL;
case SamplerCompareFunc::Always:
return VK_COMPARE_OP_ALWAYS;
default:
return VK_COMPARE_OP_ALWAYS;
}
}
VkComponentSwizzle ConvertTextureSwizzleParamToVkEnum(TextureSwizzleParam v) {
switch (v) {
case TextureSwizzleParam::Red:
return VK_COMPONENT_SWIZZLE_R;
case TextureSwizzleParam::Green:
return VK_COMPONENT_SWIZZLE_G;
case TextureSwizzleParam::Blue:
return VK_COMPONENT_SWIZZLE_B;
case TextureSwizzleParam::Alpha:
return VK_COMPONENT_SWIZZLE_A;
case TextureSwizzleParam::Zero:
return VK_COMPONENT_SWIZZLE_ZERO;
case TextureSwizzleParam::One:
return VK_COMPONENT_SWIZZLE_ONE;
default:
return VK_COMPONENT_SWIZZLE_IDENTITY;
}
}
} // namespace MG_Util
} // namespace MobileGL
@@ -0,0 +1,27 @@
// MobileGL - MobileGL/MG_Util/Converters/MGToVk/TextureEnumConverter.h
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
#pragma once
#include <Includes.h>
#include <MG_State/GLState/TextureState/TextureObject.h>
namespace MobileGL {
namespace MG_Util {
VkImageViewType ConvertTextureTargetToVkEnum(TextureTarget target);
VkFormat ConvertTextureInputFormatToVkEnum(TextureInputFormat format);
VkFormat ConvertTextureInternalFormatToVkEnum(TextureInternalFormat internalformat);
VkFormat ConvertTexturePixelDataTypeToVkEnum(TexturePixelDataType type);
VkImageViewType ConvertTextureUploadTargetToVkEnum(TextureUploadTarget target);
VkFilter ConvertSamplerFilterModeToVkEnum(SamplerFilterMode filter, SamplerMipmapMode mipmap);
VkSamplerMipmapMode ConvertSamplerMipmapModeToVkEnum(SamplerMipmapMode value);
VkSamplerAddressMode ConvertSamplerWrapModeToVkEnum(SamplerWrapMode value);
VkBool32 ConvertSamplerCompareModeToVkEnum(SamplerCompareMode value);
VkCompareOp ConvertSamplerCompareFuncToVkEnum(SamplerCompareFunc value);
VkComponentSwizzle ConvertTextureSwizzleParamToVkEnum(TextureSwizzleParam value);
} // namespace MG_Util
} // namespace MobileGL