[Feat] (MG_Backend/DirectVulkan): inital FBO implementation, and blit framebuffer

This commit is contained in:
2026-02-17 19:39:10 +08:00
parent 49a18f68e1
commit e132e9edc1
10 changed files with 717 additions and 38 deletions
+1
View File
@@ -219,6 +219,7 @@ set(SOURCE_FILES
MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.cpp
MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp
MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp
MobileGL/MG_Backend/DirectVulkan/Renderer/VkFramebufferManager.cpp
MobileGL/MG_State/GLState/Core.cpp MobileGL/MG_State/GLState/Core.cpp
MobileGL/MG_State/GLState/ErrorState/Error.cpp MobileGL/MG_State/GLState/ErrorState/Error.cpp
@@ -8,6 +8,7 @@
#include "DirectVulkan.h" #include "DirectVulkan.h"
#include "MG_State/GLState/Core.h" #include "MG_State/GLState/Core.h"
#include "MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h"
namespace MobileGL::MG_Backend::DirectVulkan { namespace MobileGL::MG_Backend::DirectVulkan {
UniquePtr<VulkanRenderer> pVulkanRenderer = nullptr; UniquePtr<VulkanRenderer> pVulkanRenderer = nullptr;
@@ -16,10 +17,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::Clear called with null VulkanRenderer"); MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::Clear called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::Clear called with null GL context"); MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::Clear called with null GL context");
const auto& drawFboSlot = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw);
const auto drawFbo = drawFboSlot.GetBoundObject();
const auto defaultFboInfo = MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo;
const auto defaultFbo = defaultFboInfo ? defaultFboInfo->defaultFBO : nullptr;
const Bool isDefaultFboTarget = (drawFbo == defaultFbo) || (drawFbo == nullptr && defaultFbo != nullptr);
Uint drawFboExternalIndex = 0;
if (drawFbo) {
drawFboExternalIndex = drawFbo->GetExternalIndex();
} else if (defaultFbo) {
drawFboExternalIndex = defaultFbo->GetExternalIndex();
}
const auto& clearColor = MG_State::pGLContext->GetClearColor(); const auto& clearColor = MG_State::pGLContext->GetClearColor();
const auto clearDepth = MG_State::pGLContext->GetClearDepth(); const auto clearDepth = MG_State::pGLContext->GetClearDepth();
const auto clearStencil = static_cast<Uint32>(MG_State::pGLContext->GetClearStencil()); const auto clearStencil = static_cast<Uint32>(MG_State::pGLContext->GetClearStencil());
pVulkanRenderer->RequestClear(mask, clearColor, clearDepth, clearStencil); pVulkanRenderer->RequestClear(mask, clearColor, clearDepth, clearStencil, drawFboExternalIndex,
isDefaultFboTarget);
} }
void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) { void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices) {
@@ -177,5 +191,34 @@ namespace MobileGL::MG_Backend::DirectVulkan {
pVulkanRenderer->DrawArrays(payload); pVulkanRenderer->DrawArrays(payload);
} }
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
GLint dstY1, GLbitfield mask, GLenum filter) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::BlitFramebuffer called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::BlitFramebuffer called with null GL context");
const auto readFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject();
const auto drawFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
const auto defaultFboInfo = MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo;
const auto defaultFbo = defaultFboInfo ? defaultFboInfo->defaultFBO : nullptr;
const Bool readIsDefault = (readFbo == defaultFbo) || (readFbo == nullptr && defaultFbo != nullptr);
const Bool drawIsDefault = (drawFbo == defaultFbo) || (drawFbo == nullptr && defaultFbo != nullptr);
Uint readFboExternalIndex = 0;
Uint drawFboExternalIndex = 0;
if (readFbo) {
readFboExternalIndex = readFbo->GetExternalIndex();
} else if (defaultFbo) {
readFboExternalIndex = defaultFbo->GetExternalIndex();
}
if (drawFbo) {
drawFboExternalIndex = drawFbo->GetExternalIndex();
} else if (defaultFbo) {
drawFboExternalIndex = defaultFbo->GetExternalIndex();
}
pVulkanRenderer->BlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter,
readFboExternalIndex, drawFboExternalIndex, readIsDefault, drawIsDefault);
}
} // namespace MobileGL::MG_Backend::DirectVulkan } // namespace MobileGL::MG_Backend::DirectVulkan
@@ -128,7 +128,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
createInfo.imageColorSpace = pickedSurfaceFormat.colorSpace; createInfo.imageColorSpace = pickedSurfaceFormat.colorSpace;
createInfo.imageExtent = swapchainCaps.currentExtent; createInfo.imageExtent = swapchainCaps.currentExtent;
createInfo.imageArrayLayers = 1; createInfo.imageArrayLayers = 1;
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; const VkImageUsageFlags requiredImageUsage =
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
MOBILEGL_ASSERT((swapchainCaps.supportedUsageFlags & requiredImageUsage) == requiredImageUsage,
"Swapchain does not support required usage flags (COLOR_ATTACHMENT | TRANSFER_DST). "
"supportedUsageFlags=0x%x",
static_cast<Uint32>(swapchainCaps.supportedUsageFlags));
VkImageUsageFlags imageUsage = requiredImageUsage;
if ((swapchainCaps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) != 0) {
imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
}
createInfo.imageUsage = imageUsage;
MGLOG_I("Swapchain imageUsage = 0x%x (supportedUsageFlags = 0x%x)", static_cast<Uint32>(createInfo.imageUsage),
static_cast<Uint32>(swapchainCaps.supportedUsageFlags));
Uint32 queueFamilyIndices[] = {graphicsQueueFamily, presentQueueFamily}; Uint32 queueFamilyIndices[] = {graphicsQueueFamily, presentQueueFamily};
if (graphicsQueueFamily != presentQueueFamily) { if (graphicsQueueFamily != presentQueueFamily) {
createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
@@ -0,0 +1,258 @@
// 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 "VkFramebufferManager.h"
#include <MG_State/GLState/RenderbufferState/RenderbufferObject.h>
#include <MG_State/GLState/TextureState/TextureEnum.h>
namespace MobileGL::MG_Backend::DirectVulkan {
Bool VkFramebufferManager::Initialize(const InitInfo& initInfo) {
m_device = initInfo.device;
m_physicalDevice = initInfo.physicalDevice;
return m_device != VK_NULL_HANDLE && m_physicalDevice != VK_NULL_HANDLE;
}
void VkFramebufferManager::Shutdown() {
for (auto& [_, target] : m_offscreenColorTargets) {
DestroyOffscreenColorTarget(target);
}
m_offscreenColorTargets.clear();
m_device = VK_NULL_HANDLE;
m_physicalDevice = VK_NULL_HANDLE;
}
Bool VkFramebufferManager::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, colorAttachment, objectVersion);
}
Bool VkFramebufferManager::ClearColor(VkCommandBuffer commandBuffer, Uint glFboExternalIndex,
const VkClearColorValue& clearColor) {
auto it = m_offscreenColorTargets.find(glFboExternalIndex);
if (it == m_offscreenColorTargets.end()) {
MGLOG_W("VkFramebufferManager::ClearColor skipped: no offscreen target for FBO %u", glFboExternalIndex);
return false;
}
auto& target = it->second;
if (!TransitionColorTargetForClear(commandBuffer, target)) {
return false;
}
VkImageSubresourceRange subresourceRange{};
subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
subresourceRange.baseMipLevel = 0;
subresourceRange.levelCount = 1;
subresourceRange.baseArrayLayer = 0;
subresourceRange.layerCount = 1;
vkCmdClearColorImage(commandBuffer, target.image, target.layout, &clearColor, 1, &subresourceRange);
return true;
}
Bool VkFramebufferManager::TransitionOffscreenColorToTransferSrc(VkCommandBuffer commandBuffer,
Uint glFboExternalIndex) {
auto it = m_offscreenColorTargets.find(glFboExternalIndex);
if (it == m_offscreenColorTargets.end()) {
MGLOG_W("VkFramebufferManager::TransitionOffscreenColorToTransferSrc skipped: FBO %u not found",
glFboExternalIndex);
return false;
}
return TransitionColorTargetForBlitSrc(commandBuffer, it->second);
}
Bool VkFramebufferManager::GetOffscreenColorImage(Uint glFboExternalIndex, VkImage& outImage,
VkExtent2D& outExtent) const {
auto it = m_offscreenColorTargets.find(glFboExternalIndex);
if (it == m_offscreenColorTargets.end() || it->second.image == VK_NULL_HANDLE) {
return false;
}
outImage = it->second.image;
outExtent = it->second.extent;
return true;
}
Bool VkFramebufferManager::RecreateOffscreenColorTarget(
OffscreenColorTarget& target, 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;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VK_VERIFY(vkCreateImage(m_device, &imageInfo, nullptr, &target.image), "vkCreateImage(offscreen color)");
VkMemoryRequirements memoryRequirements{};
vkGetImageMemoryRequirements(m_device, target.image, &memoryRequirements);
VkMemoryAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memoryRequirements.size;
allocInfo.memoryTypeIndex = FindMemoryType(memoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
VK_VERIFY(vkAllocateMemory(m_device, &allocInfo, nullptr, &target.memory), "vkAllocateMemory(offscreen color)");
VK_VERIFY(vkBindImageMemory(m_device, target.image, target.memory, 0), "vkBindImageMemory(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)");
target.layout = VK_IMAGE_LAYOUT_UNDEFINED;
target.extent = {static_cast<Uint32>(size.x()), static_cast<Uint32>(size.y())};
target.format = format;
target.glObjectVersion = glObjectVersion;
return true;
}
void VkFramebufferManager::DestroyOffscreenColorTarget(OffscreenColorTarget& target) {
if (target.imageView != VK_NULL_HANDLE) {
vkDestroyImageView(m_device, target.imageView, nullptr);
target.imageView = VK_NULL_HANDLE;
}
if (target.image != VK_NULL_HANDLE) {
vkDestroyImage(m_device, target.image, nullptr);
target.image = VK_NULL_HANDLE;
}
if (target.memory != VK_NULL_HANDLE) {
vkFreeMemory(m_device, target.memory, nullptr);
target.memory = VK_NULL_HANDLE;
}
target.layout = VK_IMAGE_LAYOUT_UNDEFINED;
target.extent = {0, 0};
target.format = VK_FORMAT_UNDEFINED;
target.glObjectVersion = 0;
}
Bool VkFramebufferManager::TransitionColorTargetForClear(VkCommandBuffer commandBuffer, OffscreenColorTarget& target) {
if (target.layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
return true;
}
VkImageMemoryBarrier barrier{};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.oldLayout = target.layout;
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = target.image;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1;
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
0, 0, nullptr, 0, nullptr, 1, &barrier);
target.layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
return true;
}
Bool VkFramebufferManager::TransitionColorTargetForBlitSrc(VkCommandBuffer commandBuffer,
OffscreenColorTarget& target) {
if (target.layout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) {
return true;
}
VkImageMemoryBarrier barrier{};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barrier.oldLayout = target.layout;
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = target.image;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1;
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
0, 0, nullptr, 0, nullptr, 1, &barrier);
target.layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
return true;
}
Uint32 VkFramebufferManager::FindMemoryType(Uint32 typeFilter, VkMemoryPropertyFlags properties) const {
VkPhysicalDeviceMemoryProperties memoryProperties{};
vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memoryProperties);
for (Uint32 i = 0; i < memoryProperties.memoryTypeCount; ++i) {
if ((typeFilter & (1U << i)) &&
(memoryProperties.memoryTypes[i].propertyFlags & properties) == properties) {
return i;
}
}
MOBILEGL_ASSERT(false, "VkFramebufferManager::FindMemoryType failed");
return 0;
}
VkFormat VkFramebufferManager::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;
}
}
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -0,0 +1,58 @@
// 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>
namespace MobileGL::MG_Backend::DirectVulkan {
class VkFramebufferManager {
public:
struct InitInfo {
VkDevice device = VK_NULL_HANDLE;
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
};
VkFramebufferManager() = default;
~VkFramebufferManager() = default;
Bool Initialize(const InitInfo& initInfo);
void Shutdown();
Bool EnsureOffscreenColorTarget(Uint glFboExternalIndex, const MG_State::GLState::FramebufferObject& glFbo);
Bool ClearColor(VkCommandBuffer commandBuffer, Uint glFboExternalIndex, const VkClearColorValue& clearColor);
Bool TransitionOffscreenColorToTransferSrc(VkCommandBuffer commandBuffer, Uint glFboExternalIndex);
Bool GetOffscreenColorImage(Uint glFboExternalIndex, VkImage& outImage, VkExtent2D& outExtent) const;
private:
struct OffscreenColorTarget {
VkImage image = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
VkImageView imageView = VK_NULL_HANDLE;
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
VkExtent2D extent = {0, 0};
VkFormat format = VK_FORMAT_UNDEFINED;
Uint16 glObjectVersion = 0;
};
Bool RecreateOffscreenColorTarget(OffscreenColorTarget& target,
const MG_State::GLState::FramebufferAttachmentObject& colorAttachment,
Uint16 glObjectVersion);
void DestroyOffscreenColorTarget(OffscreenColorTarget& target);
Bool TransitionColorTargetForClear(VkCommandBuffer commandBuffer, OffscreenColorTarget& target);
Bool TransitionColorTargetForBlitSrc(VkCommandBuffer commandBuffer, OffscreenColorTarget& target);
Uint32 FindMemoryType(Uint32 typeFilter, VkMemoryPropertyFlags properties) const;
static VkFormat ResolveColorFormat(const MG_State::GLState::FramebufferAttachmentObject& colorAttachment);
VkDevice m_device = VK_NULL_HANDLE;
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
UnorderedMap<Uint, OffscreenColorTarget> m_offscreenColorTargets;
};
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -10,6 +10,7 @@
#include "VertexInputStateFactory.h" #include "VertexInputStateFactory.h"
#include "VertexInputStateBuilder.h" #include "VertexInputStateBuilder.h"
#include "MG_State/GLState/Core.h"
#include "MG_State/GLState/ProgramState/ProgramObject.h" #include "MG_State/GLState/ProgramState/ProgramObject.h"
namespace MobileGL::MG_Backend::DirectVulkan { namespace MobileGL::MG_Backend::DirectVulkan {
@@ -130,6 +131,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_uniformDescriptorBinder.reset(); m_uniformDescriptorBinder.reset();
} }
m_vertexInputStateFactory = MakeUnique<VertexInputStateFactory>(m_config); m_vertexInputStateFactory = MakeUnique<VertexInputStateFactory>(m_config);
m_framebufferManager = MakeUnique<VkFramebufferManager>();
if (!m_framebufferManager->Initialize({m_device, m_physicalDevice.handle})) {
MGLOG_E("VkFramebufferManager initialization failed. Offscreen FBO clear path is disabled.");
m_framebufferManager.reset();
}
PrepareDemoPipeline(); PrepareDemoPipeline();
CreateFrameContexts(); CreateFrameContexts();
@@ -147,6 +153,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_pipelineFactory.reset(); m_pipelineFactory.reset();
m_programFactory.reset(); m_programFactory.reset();
m_vertexInputStateFactory.reset(); m_vertexInputStateFactory.reset();
if (m_framebufferManager) {
m_framebufferManager->Shutdown();
m_framebufferManager.reset();
}
for (auto& vertexBuffer : m_vertexBuffers) { for (auto& vertexBuffer : m_vertexBuffers) {
if (vertexBuffer) { if (vertexBuffer) {
vertexBuffer->Destroy(); vertexBuffer->Destroy();
@@ -197,13 +207,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MGLOG_I("VulkanRenderer shut down completed"); MGLOG_I("VulkanRenderer shut down completed");
} }
void VulkanRenderer::RequestClear(GLbitfield mask, const FloatVec4& color, Float depth, Uint32 stencil) { void VulkanRenderer::RequestClear(GLbitfield mask, const FloatVec4& color, Float depth, Uint32 stencil,
Uint drawFboExternalIndex, Bool isDefaultFramebufferTarget) {
const GLbitfield supportedMask = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; const GLbitfield supportedMask = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
const GLbitfield requestMask = (mask & supportedMask); const GLbitfield requestMask = (mask & supportedMask);
if (requestMask == 0) { if (requestMask == 0) {
return; return;
} }
if (m_pendingClearMask != 0 &&
(m_pendingClearDrawFboExternalIndex != drawFboExternalIndex ||
m_pendingClearTargetsDefaultFramebuffer != isDefaultFramebufferTarget)) {
MGLOG_W("Pending clear target changed from FBO %u to FBO %u before execution; dropping previous pending clear",
m_pendingClearDrawFboExternalIndex, drawFboExternalIndex);
m_pendingClearMask = 0;
}
if ((requestMask & GL_COLOR_BUFFER_BIT) != 0) { if ((requestMask & GL_COLOR_BUFFER_BIT) != 0) {
m_pendingClearColor.float32[0] = color.x(); m_pendingClearColor.float32[0] = color.x();
m_pendingClearColor.float32[1] = color.y(); m_pendingClearColor.float32[1] = color.y();
@@ -216,6 +235,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if ((requestMask & GL_STENCIL_BUFFER_BIT) != 0) { if ((requestMask & GL_STENCIL_BUFFER_BIT) != 0) {
m_pendingClearStencil = stencil; m_pendingClearStencil = stencil;
} }
m_pendingClearDrawFboExternalIndex = drawFboExternalIndex;
m_pendingClearTargetsDefaultFramebuffer = isDefaultFramebufferTarget;
m_pendingClearMask |= requestMask; m_pendingClearMask |= requestMask;
} }
@@ -330,6 +351,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_depthStencilImageLayouts[imageIndex] = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; m_depthStencilImageLayouts[imageIndex] = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
} }
Bool VulkanRenderer::RecordOffscreenColorClear(VkCommandBuffer commandBuffer) {
if (!m_framebufferManager || !MG_State::pGLContext) {
return false;
}
const auto fbo = MG_State::pGLContext->GetFramebufferObject(m_pendingClearDrawFboExternalIndex);
if (!fbo) {
MGLOG_W("RecordOffscreenColorClear skipped: draw FBO %u not found", m_pendingClearDrawFboExternalIndex);
return false;
}
if (!m_framebufferManager->EnsureOffscreenColorTarget(m_pendingClearDrawFboExternalIndex, *fbo)) {
return false;
}
return m_framebufferManager->ClearColor(commandBuffer, m_pendingClearDrawFboExternalIndex, m_pendingClearColor);
}
void VulkanRenderer::EnsureFrameRecordingStarted() { void VulkanRenderer::EnsureFrameRecordingStarted() {
auto& frame = m_frameContext.GetCurrent(); auto& frame = m_frameContext.GetCurrent();
if (frame.isCommandRecording) { if (frame.isCommandRecording) {
@@ -345,6 +382,47 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (m_uniformDescriptorBinder) { if (m_uniformDescriptorBinder) {
m_uniformDescriptorBinder->BeginFrame(m_frameContext.GetCurrentFrameIndex()); m_uniformDescriptorBinder->BeginFrame(m_frameContext.GetCurrentFrameIndex());
} }
if (m_pendingClearMask != 0 && !m_pendingClearTargetsDefaultFramebuffer) {
if ((m_pendingClearMask & GL_COLOR_BUFFER_BIT) != 0) {
if (!RecordOffscreenColorClear(commandBuffer)) {
MGLOG_W("Failed to clear non-default FBO %u color attachment",
m_pendingClearDrawFboExternalIndex);
}
m_pendingClearMask &= ~GL_COLOR_BUFFER_BIT;
}
if ((m_pendingClearMask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) != 0) {
MGLOG_W("Depth/stencil clear on non-default FBO is not implemented yet (FBO %u)",
m_pendingClearDrawFboExternalIndex);
m_pendingClearMask &= ~(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
// This frame touched only offscreen resources. Present still requires
// the acquired swapchain image to be in PRESENT layout.
const auto swapchainOldLayout = m_swapchainObject.GetImageLayout(m_imageIndexAcquired);
if (swapchainOldLayout != VK_IMAGE_LAYOUT_PRESENT_SRC_KHR &&
swapchainOldLayout != VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR) {
VkImageMemoryBarrier presentBarrier{};
presentBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
presentBarrier.srcAccessMask = 0;
presentBarrier.dstAccessMask = 0;
presentBarrier.oldLayout = swapchainOldLayout;
presentBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
presentBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
presentBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
presentBarrier.image = m_swapchainObject.GetImage(m_imageIndexAcquired);
presentBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
presentBarrier.subresourceRange.baseMipLevel = 0;
presentBarrier.subresourceRange.levelCount = 1;
presentBarrier.subresourceRange.baseArrayLayer = 0;
presentBarrier.subresourceRange.layerCount = 1;
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
0, 0, nullptr, 0, nullptr, 1, &presentBarrier);
m_swapchainObject.SetImageLayout(m_imageIndexAcquired, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
}
return;
}
TransitionSwapchainImageToColorAttachment(commandBuffer, m_imageIndexAcquired); TransitionSwapchainImageToColorAttachment(commandBuffer, m_imageIndexAcquired);
TransitionDepthStencilImageToAttachment(commandBuffer, m_imageIndexAcquired); TransitionDepthStencilImageToAttachment(commandBuffer, m_imageIndexAcquired);
@@ -648,6 +726,124 @@ namespace MobileGL::MG_Backend::DirectVulkan {
vkCmdDrawIndexed(commandBuffer, static_cast<Uint32>(payload.drawArray.count), 1, 0, 0, 0); vkCmdDrawIndexed(commandBuffer, static_cast<Uint32>(payload.drawArray.count), 1, 0, 0, 0);
} }
Bool VulkanRenderer::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0,
GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter,
Uint readFboExternalIndex, Uint drawFboExternalIndex,
Bool readIsDefaultFramebuffer, Bool drawIsDefaultFramebuffer) {
if ((mask & GL_COLOR_BUFFER_BIT) == 0 || (mask & ~GL_COLOR_BUFFER_BIT) != 0) {
MGLOG_W("BlitFramebuffer skipped: only GL_COLOR_BUFFER_BIT is supported in Vulkan backend for now");
return false;
}
if (readIsDefaultFramebuffer || !drawIsDefaultFramebuffer) {
MGLOG_W("BlitFramebuffer skipped: currently only read=offscreen FBO and draw=default FBO is supported");
return false;
}
auto& frame = m_frameContext.GetCurrent();
if (frame.hasCommandBufferRecorded) {
MGLOG_W("BlitFramebuffer skipped: current frame command buffer is already finalized");
return false;
}
if (!frame.isCommandRecording) {
m_frameContext.BeginCommandRecording();
if (m_uniformDescriptorBinder) {
m_uniformDescriptorBinder->BeginFrame(m_frameContext.GetCurrentFrameIndex());
}
}
VkCommandBuffer commandBuffer = frame.commandBuffer;
if (m_isMainRenderPassActive) {
vkCmdEndRenderPass(commandBuffer);
m_isMainRenderPassActive = false;
}
if (!m_framebufferManager || !MG_State::pGLContext) {
MGLOG_W("BlitFramebuffer skipped: framebuffer manager is unavailable");
return false;
}
(void)drawFboExternalIndex;
const auto readFbo = MG_State::pGLContext->GetFramebufferObject(readFboExternalIndex);
if (!readFbo) {
MGLOG_W("BlitFramebuffer skipped: read FBO %u not found", readFboExternalIndex);
return false;
}
if (!m_framebufferManager->EnsureOffscreenColorTarget(readFboExternalIndex, *readFbo)) {
return false;
}
if (!m_framebufferManager->TransitionOffscreenColorToTransferSrc(commandBuffer, readFboExternalIndex)) {
return false;
}
VkImage srcImage = VK_NULL_HANDLE;
VkExtent2D srcExtent{};
if (!m_framebufferManager->GetOffscreenColorImage(readFboExternalIndex, srcImage, srcExtent)) {
MGLOG_W("BlitFramebuffer skipped: offscreen image for FBO %u is unavailable", readFboExternalIndex);
return false;
}
const auto swapchainOldLayout = m_swapchainObject.GetImageLayout(m_imageIndexAcquired);
if (swapchainOldLayout != VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
VkImageMemoryBarrier toTransferDstBarrier{};
toTransferDstBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
toTransferDstBarrier.srcAccessMask = 0;
toTransferDstBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
toTransferDstBarrier.oldLayout = swapchainOldLayout;
toTransferDstBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
toTransferDstBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
toTransferDstBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
toTransferDstBarrier.image = m_swapchainObject.GetImage(m_imageIndexAcquired);
toTransferDstBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
toTransferDstBarrier.subresourceRange.baseMipLevel = 0;
toTransferDstBarrier.subresourceRange.levelCount = 1;
toTransferDstBarrier.subresourceRange.baseArrayLayer = 0;
toTransferDstBarrier.subresourceRange.layerCount = 1;
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
0, 0, nullptr, 0, nullptr, 1, &toTransferDstBarrier);
m_swapchainObject.SetImageLayout(m_imageIndexAcquired, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
}
VkImageBlit blitRegion{};
blitRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
blitRegion.srcSubresource.mipLevel = 0;
blitRegion.srcSubresource.baseArrayLayer = 0;
blitRegion.srcSubresource.layerCount = 1;
blitRegion.srcOffsets[0] = {srcX0, srcY0, 0};
blitRegion.srcOffsets[1] = {srcX1, srcY1, 1};
blitRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
blitRegion.dstSubresource.mipLevel = 0;
blitRegion.dstSubresource.baseArrayLayer = 0;
blitRegion.dstSubresource.layerCount = 1;
blitRegion.dstOffsets[0] = {dstX0, dstY0, 0};
blitRegion.dstOffsets[1] = {dstX1, dstY1, 1};
vkCmdBlitImage(commandBuffer, srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
m_swapchainObject.GetImage(m_imageIndexAcquired), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1, &blitRegion, (filter == GL_LINEAR ? VK_FILTER_LINEAR : VK_FILTER_NEAREST));
VkImageMemoryBarrier toPresentBarrier{};
toPresentBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
toPresentBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
toPresentBarrier.dstAccessMask = 0;
toPresentBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
toPresentBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
toPresentBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
toPresentBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
toPresentBarrier.image = m_swapchainObject.GetImage(m_imageIndexAcquired);
toPresentBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
toPresentBarrier.subresourceRange.baseMipLevel = 0;
toPresentBarrier.subresourceRange.levelCount = 1;
toPresentBarrier.subresourceRange.baseArrayLayer = 0;
toPresentBarrier.subresourceRange.layerCount = 1;
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
0, 0, nullptr, 0, nullptr, 1, &toPresentBarrier);
m_swapchainObject.SetImageLayout(m_imageIndexAcquired, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
(void)srcExtent;
return true;
}
void VulkanRenderer::Render() { void VulkanRenderer::Render() {
// Route test rendering through the same frame-start logic used by draw calls, // Route test rendering through the same frame-start logic used by draw calls,
// so pending glClear() state can be consumed consistently. // so pending glClear() state can be consumed consistently.
@@ -669,7 +865,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MOBILEGL_ASSERT(m_imageIndexAcquired < m_swapchainObject.GetImageCount(), MOBILEGL_ASSERT(m_imageIndexAcquired < m_swapchainObject.GetImageCount(),
"Present, acquired image index out of range"); "Present, acquired image index out of range");
auto& frame = m_frameContext.GetCurrent(); auto& frame = m_frameContext.GetCurrent();
if (m_pendingClearMask != 0 && frame.isCommandRecording && m_isMainRenderPassActive) { if (m_pendingClearMask != 0 && frame.hasCommandBufferRecorded) {
MGLOG_W("Dropping pending clear for current frame because command buffer is already finalized");
m_pendingClearMask = 0;
} else if (m_pendingClearMask != 0 && frame.isCommandRecording) {
if (m_pendingClearTargetsDefaultFramebuffer && m_isMainRenderPassActive) {
if ((m_pendingClearMask & GL_COLOR_BUFFER_BIT) != 0) { if ((m_pendingClearMask & GL_COLOR_BUFFER_BIT) != 0) {
RecordColorClear(frame.commandBuffer, m_pendingClearColor); RecordColorClear(frame.commandBuffer, m_pendingClearColor);
} }
@@ -677,9 +877,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
RecordDepthStencilClear(frame.commandBuffer, m_pendingClearMask, m_pendingClearDepth, m_pendingClearStencil); RecordDepthStencilClear(frame.commandBuffer, m_pendingClearMask, m_pendingClearDepth, m_pendingClearStencil);
} }
m_pendingClearMask = 0; m_pendingClearMask = 0;
} else if (m_pendingClearMask != 0 && frame.hasCommandBufferRecorded) { } else if (!m_pendingClearTargetsDefaultFramebuffer) {
MGLOG_W("Dropping pending clear for current frame because command buffer is already finalized"); if ((m_pendingClearMask & GL_COLOR_BUFFER_BIT) != 0) {
if (!RecordOffscreenColorClear(frame.commandBuffer)) {
MGLOG_W("Present: failed to clear non-default FBO %u color attachment",
m_pendingClearDrawFboExternalIndex);
}
m_pendingClearMask &= ~GL_COLOR_BUFFER_BIT;
}
if ((m_pendingClearMask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) != 0) {
MGLOG_W("Present: depth/stencil clear on non-default FBO is not implemented yet (FBO %u)",
m_pendingClearDrawFboExternalIndex);
m_pendingClearMask &= ~(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
} else {
MGLOG_W("Present: pending default-FBO clear cannot execute because no render pass is active");
m_pendingClearMask = 0; m_pendingClearMask = 0;
}
} else if (m_pendingClearMask != 0) { } else if (m_pendingClearMask != 0) {
EnsureFrameRecordingStarted(); EnsureFrameRecordingStarted();
} }
@@ -15,6 +15,7 @@
#include "UniformDescriptorBinder.h" #include "UniformDescriptorBinder.h"
#include "VertexInputStateFactory.h" #include "VertexInputStateFactory.h"
#include "VkBufferObject.h" #include "VkBufferObject.h"
#include "VkFramebufferManager.h"
#include "MG_Util/Math/VectorTypes.h" #include "MG_Util/Math/VectorTypes.h"
#include <Includes.h> #include <Includes.h>
#include <vk_mem_alloc.h> #include <vk_mem_alloc.h>
@@ -58,11 +59,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void Initialize(); void Initialize();
void Shutdown(); void Shutdown();
void RequestClear(GLbitfield mask, const FloatVec4& color, Float depth, Uint32 stencil); void RequestClear(GLbitfield mask, const FloatVec4& color, Float depth, Uint32 stencil,
Uint drawFboExternalIndex, Bool isDefaultFramebufferTarget);
Bool ConsumePendingColorClear(VkClearColorValue& outClearColor); Bool ConsumePendingColorClear(VkClearColorValue& outClearColor);
void EnsureFrameRecordingStarted(); void EnsureFrameRecordingStarted();
void DrawArrays(const DrawArrayPayload& payload); void DrawArrays(const DrawArrayPayload& payload);
void DrawElements(const DrawElementPayload& payload); void DrawElements(const DrawElementPayload& payload);
Bool BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
GLint dstY1, GLbitfield mask, GLenum filter, Uint readFboExternalIndex,
Uint drawFboExternalIndex, Bool readIsDefaultFramebuffer, Bool drawIsDefaultFramebuffer);
void Render(); void Render();
void Present(); void Present();
@@ -128,12 +133,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkClearColorValue m_pendingClearColor = {{0.0f, 0.0f, 0.0f, 1.0f}}; VkClearColorValue m_pendingClearColor = {{0.0f, 0.0f, 0.0f, 1.0f}};
Float m_pendingClearDepth = 1.0f; Float m_pendingClearDepth = 1.0f;
Uint32 m_pendingClearStencil = 0; Uint32 m_pendingClearStencil = 0;
Uint m_pendingClearDrawFboExternalIndex = 0;
Bool m_pendingClearTargetsDefaultFramebuffer = true;
Bool m_isMainRenderPassActive = false; Bool m_isMainRenderPassActive = false;
UniquePtr<PipelineFactory> m_pipelineFactory; UniquePtr<PipelineFactory> m_pipelineFactory;
UniquePtr<ProgramFactory> m_programFactory; UniquePtr<ProgramFactory> m_programFactory;
UniquePtr<UniformDescriptorBinder> m_uniformDescriptorBinder; UniquePtr<UniformDescriptorBinder> m_uniformDescriptorBinder;
UniquePtr<VertexInputStateFactory> m_vertexInputStateFactory; UniquePtr<VertexInputStateFactory> m_vertexInputStateFactory;
UniquePtr<VkFramebufferManager> m_framebufferManager;
void CreateInstance(); void CreateInstance();
VkResult SetupDebugMessenger(); VkResult SetupDebugMessenger();
@@ -159,6 +167,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void TransitionDepthStencilImageToAttachment(VkCommandBuffer commandBuffer, Uint32 imageIndex); void TransitionDepthStencilImageToAttachment(VkCommandBuffer commandBuffer, Uint32 imageIndex);
void RecordColorClear(VkCommandBuffer commandBuffer, const VkClearColorValue& clearColor); void RecordColorClear(VkCommandBuffer commandBuffer, const VkClearColorValue& clearColor);
void RecordDepthStencilClear(VkCommandBuffer commandBuffer, GLbitfield mask, Float depth, Uint32 stencil); void RecordDepthStencilClear(VkCommandBuffer commandBuffer, GLbitfield mask, Float depth, Uint32 stencil);
Bool RecordOffscreenColorClear(VkCommandBuffer commandBuffer);
void EndFrameRecordingIfNeeded(); void EndFrameRecordingIfNeeded();
Bool UploadAndBindVertexStreams( Bool UploadAndBindVertexStreams(
const VertexInputStateFactory::BackendVertexInputState& vertexInputState, const VertexInputStateFactory::BackendVertexInputState& vertexInputState,
@@ -19,6 +19,9 @@
#if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES #if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES
#include <MG_Backend/DirectGLES/DirectGLES.h> #include <MG_Backend/DirectGLES/DirectGLES.h>
#endif #endif
#if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_VULKAN
#include <MG_Backend/DirectVulkan/DirectVulkan.h>
#endif
namespace MobileGL { namespace MobileGL {
namespace MG_Impl::GLImpl { namespace MG_Impl::GLImpl {
@@ -27,9 +30,69 @@ namespace MobileGL {
#if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES #if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES
MG_Backend::DirectGLES::BlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, MG_Backend::DirectGLES::BlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask,
filter); filter);
#elif MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_VULKAN
MG_Backend::DirectVulkan::BlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask,
filter);
#endif #endif
} }
Bool BlitFramebuffer_State(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0,
GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) {
(void)srcX0;
(void)srcY0;
(void)srcX1;
(void)srcY1;
(void)dstX0;
(void)dstY0;
(void)dstX1;
(void)dstY1;
constexpr GLbitfield kSupportedMask = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
if ((mask & kSupportedMask) == 0 || (mask & ~kSupportedMask) != 0) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeShared<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"`mask` must contain COLOR/DEPTH/STENCIL bits only."));
return false;
}
if (filter != GL_NEAREST && filter != GL_LINEAR) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeShared<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"`filter` must be GL_NEAREST or GL_LINEAR."));
return false;
}
if ((mask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) != 0 && filter != GL_NEAREST) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeShared<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"Depth/stencil blit requires GL_NEAREST filter."));
return false;
}
const auto readFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject();
const auto drawFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
if (!readFbo || !drawFbo) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeShared<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"Read/draw framebuffer binding is null."));
return false;
}
if (!readFbo->CheckCompleteness() || !drawFbo->CheckCompleteness()) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidFramebufferOperation,
MakeShared<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"Read/draw framebuffer is incomplete."));
return false;
}
return true;
}
void SampleMaski_State(GLuint maskNumber, GLbitfield mask) { void SampleMaski_State(GLuint maskNumber, GLbitfield mask) {
// TODO: implement // TODO: implement
} }
@@ -748,6 +811,9 @@ namespace MobileGL {
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
GLint dstY1, GLbitfield mask, GLenum filter) { GLint dstY1, GLbitfield mask, GLenum filter) {
if (!BlitFramebuffer_State(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter)) {
return;
}
BlitFramebuffer_Backend(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); BlitFramebuffer_Backend(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
} }
@@ -132,6 +132,22 @@ int main() {
MobileGL::MG_Initialize(); MobileGL::MG_Initialize();
GLuint offscreenTex = 0;
GLuint offscreenFbo = 0;
glGenTextures(1, &offscreenTex);
glBindTexture(GL_TEXTURE_2D, offscreenTex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glBindTexture(GL_TEXTURE_2D, 0);
glGenFramebuffers(1, &offscreenFbo);
glBindFramebuffer(GL_FRAMEBUFFER, offscreenFbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, offscreenTex, 0);
const GLenum offscreenFboStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
std::cout << "Offscreen FBO status = 0x" << std::hex << offscreenFboStatus << std::dec << std::endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
GLuint vao = 0; GLuint vao = 0;
glGenVertexArrays(1, &vao); glGenVertexArrays(1, &vao);
glBindVertexArray(vao); glBindVertexArray(vao);
@@ -246,30 +262,30 @@ void main() {
glBindBufferBase(GL_UNIFORM_BUFFER, 1, colorUbo); glBindBufferBase(GL_UNIFORM_BUFFER, 1, colorUbo);
glBindBuffer(GL_UNIFORM_BUFFER, 0); glBindBuffer(GL_UNIFORM_BUFFER, 0);
const auto startTime = std::chrono::steady_clock::now();
int i = 0; int i = 0;
while(!glfwWindowShouldClose(window)) { while(!glfwWindowShouldClose(window)) {
glfwPollEvents(); glfwPollEvents();
if (i % 1000 > 500) const bool useOffscreenPath = ((i / 100) % 2) == 0;
if (useOffscreenPath) {
glBindFramebuffer(GL_FRAMEBUFFER, offscreenFbo);
glClearColor(1.0f, 0.0f, 0.0f, 1.0f); glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
else glClear(GL_COLOR_BUFFER_BIT);
glBindFramebuffer(GL_READ_FRAMEBUFFER, offscreenFbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBlitFramebuffer(0, 0, 256, 256, 0, 0, 800, 600, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
} else {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClearColor(0.0f, 1.0f, 0.0f, 1.0f); glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
if (i % 500 > 250) { }
glUseProgram(program);
const auto now = std::chrono::steady_clock::now();
const float t = std::chrono::duration<float>(now - startTime).count();
glUniform1f(iTimeLocation, t);
uboColorData[0] = 0.5f + 0.5f * std::sin(t * 0.7f); if (i % 100 == 0) {
uboColorData[1] = 0.5f + 0.5f * std::sin(t * 1.1f + 1.2f); std::cout << "frame=" << i
uboColorData[2] = 0.5f + 0.5f * std::sin(t * 1.5f + 2.4f); << " path=" << (useOffscreenPath ? "offscreen-clear+blit" : "default-clear")
glBindBuffer(GL_UNIFORM_BUFFER, colorUbo); << std::endl;
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(uboColorData), uboColorData);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr);
} }
eglSwapBuffers(display, surface); eglSwapBuffers(display, surface);
++i; ++i;
@@ -281,6 +297,8 @@ void main() {
glDeleteBuffers(1, &colorVbo); glDeleteBuffers(1, &colorVbo);
glDeleteBuffers(1, &ebo); glDeleteBuffers(1, &ebo);
glDeleteVertexArrays(1, &vao); glDeleteVertexArrays(1, &vao);
glDeleteFramebuffers(1, &offscreenFbo);
glDeleteTextures(1, &offscreenTex);
glfwDestroyWindow(window); glfwDestroyWindow(window);
@@ -147,14 +147,13 @@ namespace MobileGL {
if (attrib.flags & ShaderCompileBits::CompileForOpenGL) { if (attrib.flags & ShaderCompileBits::CompileForOpenGL) {
tshader->setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientVulkan, 450); tshader->setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientVulkan, 450);
tshader->setEnvClient(glslang::EShClientOpenGL, glslang::EShTargetOpenGL_450); tshader->setEnvClient(glslang::EShClientOpenGL, glslang::EShTargetOpenGL_450);
tshader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_5); tshader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_3);
} else { } else {
tshader->setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientVulkan, 450); tshader->setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientVulkan, 450);
tshader->setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_3); // MobileGL runtime currently creates Vulkan 1.1 instance/device on Android path,
tshader->setEnvTarget(glslang::EShTargetSpv, // so generated SPIR-V must not exceed SPIR-V 1.3.
((attrib.flags & ShaderCompileBits::EmitDiscardAsDemote) tshader->setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_1);
? glslang::EShTargetSpv_1_6 tshader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_3);
: glslang::EShTargetSpv_1_5));
tshader->setEnvInputVulkanRulesRelaxed(); // using EXT_vulkan_glsl_relaxed for gl_VertexID and tshader->setEnvInputVulkanRulesRelaxed(); // using EXT_vulkan_glsl_relaxed for gl_VertexID and
// gl_InstanceID? // gl_InstanceID?
} }
@@ -233,7 +232,7 @@ namespace MobileGL {
OptimizerOptions options; OptimizerOptions options;
options.set_run_validator(false); options.set_run_validator(false);
Optimizer optimizer(SPV_ENV_UNIVERSAL_1_5); Optimizer optimizer(SPV_ENV_VULKAN_1_1);
optimizer.RegisterPass(EliminateFloatEqualsZeroPass::CreateEliminateFloatEqualsZeroPass()); optimizer.RegisterPass(EliminateFloatEqualsZeroPass::CreateEliminateFloatEqualsZeroPass());