mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
[Feat] (MG_Backend/DirectVulkan): inital FBO implementation, and blit framebuffer
This commit is contained in:
@@ -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());
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user