[Feat] (MG_Backend/DirectVulkan): implements BlitFramebuffer

This commit is contained in:
2026-03-07 18:22:38 +08:00
parent 876cb7bcd3
commit f3b0b242dd
3 changed files with 613 additions and 0 deletions
@@ -108,6 +108,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
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) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::BlitFramebuffer called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::BlitFramebuffer called with null GL context");
pVulkanRenderer->BlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
} }
void Present() { void Present() {
@@ -12,6 +12,7 @@
#include "MG_State/GLState/Core.h" #include "MG_State/GLState/Core.h"
#include "MG_State/GLState/ProgramState/ProgramObject.h" #include "MG_State/GLState/ProgramState/ProgramObject.h"
#include "MG_Util/ShaderTranspiler/ShaderCompiler.h"
#include "MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h" #include "MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h"
#include "MG_Util/Converters/MGToVk/RenderStateEnumConverter.h" #include "MG_Util/Converters/MGToVk/RenderStateEnumConverter.h"
#include <vulkan/vulkan_core.h> #include <vulkan/vulkan_core.h>
@@ -72,6 +73,126 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
} }
namespace {
enum class BlitSurfaceTransform : Uint32 {
Identity = 0,
Rotate90 = 1,
Rotate180 = 2,
Rotate270 = 3,
};
struct BlitImageBinding {
VkImage image = VK_NULL_HANDLE;
VkImageLayout* trackedLayout = nullptr;
VkImageAspectFlags aspectMask = VK_IMAGE_ASPECT_NONE;
IntVec2 extent = {0, 0};
const char* label = nullptr;
};
static void GetImageTransitionSourceState(VkImageLayout oldLayout, VkPipelineStageFlags& outSrcStageMask,
VkAccessFlags& outSrcAccessMask) {
switch (oldLayout) {
case VK_IMAGE_LAYOUT_UNDEFINED:
case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:
outSrcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
outSrcAccessMask = 0;
break;
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
outSrcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
outSrcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
outSrcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
outSrcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
break;
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
outSrcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
outSrcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
outSrcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
outSrcAccessMask = VK_ACCESS_SHADER_READ_BIT;
break;
default:
outSrcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
outSrcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
break;
}
}
static Bool ResolveColorBlitBinding(MG_State::GLState::FramebufferObject& fbo, Bool isReadFramebuffer,
Uint32 swapchainImageIndex, SwapchainObject& swapchainObject,
VkTextureManager& textureManager, BlitImageBinding& outBinding) {
const Bool isDefaultFbo = (&fbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO.get());
const FramebufferAttachmentType attachmentType =
isReadFramebuffer ? fbo.GetReadBuffer() : fbo.GetDrawBuffers()[0];
if (attachmentType < FramebufferAttachmentType::Color0 || attachmentType > FramebufferAttachmentType::Color31) {
MGLOG_E("BlitFramebuffer only supports color attachments right now (attachment=%d)",
static_cast<Int>(attachmentType));
return false;
}
const auto& attachment = fbo.GetAttachment(attachmentType);
if (!attachment.IsComplete()) {
MGLOG_E("BlitFramebuffer skipped: %s framebuffer color attachment is incomplete",
isReadFramebuffer ? "read" : "draw");
return false;
}
if (attachment.IsRenderbuffer()) {
MGLOG_E("BlitFramebuffer skipped: renderbuffer attachments are not supported yet");
return false;
}
if (!attachment.IsTexture()) {
MGLOG_E("BlitFramebuffer skipped: unsupported framebuffer attachment type");
return false;
}
auto* texture = attachment.GetTexture().get();
MOBILEGL_ASSERT(texture != nullptr, "ResolveColorBlitBinding: texture attachment is null");
outBinding.label = isReadFramebuffer ? "read" : "draw";
if (isDefaultFbo) {
outBinding.image = swapchainObject.GetImage(swapchainImageIndex);
outBinding.trackedLayout = nullptr;
outBinding.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
const auto extent = swapchainObject.GetExtent();
outBinding.extent = {static_cast<Int>(extent.width), static_cast<Int>(extent.height)};
return true;
}
auto* resource = textureManager.SyncTextureAndGetDescriptor(*texture);
if (resource == nullptr) {
MGLOG_E("BlitFramebuffer skipped: failed to sync %s framebuffer textureId=%d",
outBinding.label, texture->GetExternalIndex());
return false;
}
if ((resource->aspect & VK_IMAGE_ASPECT_COLOR_BIT) == 0) {
MGLOG_E("BlitFramebuffer skipped: %s framebuffer attachment textureId=%d is not a color image",
outBinding.label, texture->GetExternalIndex());
return false;
}
outBinding.image = resource->image;
outBinding.trackedLayout = &resource->layout;
outBinding.aspectMask = resource->aspect;
outBinding.extent = {static_cast<Int>(resource->extent.width), static_cast<Int>(resource->extent.height)};
return true;
}
static BlitSurfaceTransform ToBlitSurfaceTransform(VkSurfaceTransformFlagBitsKHR preTransform) {
switch (preTransform) {
case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
return BlitSurfaceTransform::Rotate90;
case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
return BlitSurfaceTransform::Rotate180;
case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
return BlitSurfaceTransform::Rotate270;
default:
return BlitSurfaceTransform::Identity;
}
}
} // namespace
VkBool32 VulkanRenderer::DebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkBool32 VulkanRenderer::DebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType, VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) {
@@ -185,6 +306,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MOBILEGL_ASSERT(m_samplerManager != nullptr, "VkSamplerManager creation failed."); MOBILEGL_ASSERT(m_samplerManager != nullptr, "VkSamplerManager creation failed.");
succeeded = m_samplerManager->Initialize({m_device, &m_config}); succeeded = m_samplerManager->Initialize({m_device, &m_config});
MOBILEGL_ASSERT(succeeded, "VkSamplerManager initialization failed."); MOBILEGL_ASSERT(succeeded, "VkSamplerManager initialization failed.");
succeeded = InitializeBlitResources();
MOBILEGL_ASSERT(succeeded, "Blit pipeline resource initialization failed.");
m_uniformDescriptorBinder = MakeUnique<UniformDescriptorBinder>(); m_uniformDescriptorBinder = MakeUnique<UniformDescriptorBinder>();
MOBILEGL_ASSERT(m_uniformDescriptorBinder != nullptr, "UniformDescriptorBinder creation failed."); MOBILEGL_ASSERT(m_uniformDescriptorBinder != nullptr, "UniformDescriptorBinder creation failed.");
@@ -216,6 +339,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_pipelineFactory.reset(); m_pipelineFactory.reset();
m_programFactory.reset(); m_programFactory.reset();
ShutdownBlitResources();
if (m_samplerManager) { if (m_samplerManager) {
m_samplerManager->Shutdown(); m_samplerManager->Shutdown();
m_samplerManager.reset(); m_samplerManager.reset();
@@ -386,6 +510,217 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return true; return true;
} }
Bool VulkanRenderer::InitializeBlitResources() {
ShutdownBlitResources();
using namespace MG_Util::ShaderTranspiler;
static constexpr StringView kVertexShaderSource = R"(#version 450
layout(push_constant) uniform BlitPushConstants {
vec4 srcRect;
vec4 dstRect;
uint surfaceTransform;
} pc;
layout(location = 0) out vec2 vTexCoord;
vec2 ApplySurfaceTransform(vec2 position, uint transform) {
vec2 p = position;
p.y = -p.y;
if (transform == 1u) {
p = vec2(-p.y, p.x);
} else if (transform == 2u) {
p = -p;
} else if (transform == 3u) {
p = vec2(p.y, -p.x);
}
return p;
}
void main() {
const vec2 uvTri[3] = vec2[](
vec2(0.0, 0.0),
vec2(2.0, 0.0),
vec2(0.0, 2.0)
);
vec2 uv = uvTri[gl_VertexIndex];
vec2 dst = pc.dstRect.xy + uv * pc.dstRect.zw;
vec2 clip = dst * 2.0 - 1.0;
clip = ApplySurfaceTransform(clip, pc.surfaceTransform);
gl_Position = vec4(clip, 0.0, 1.0);
vTexCoord = pc.srcRect.xy + uv * pc.srcRect.zw;
}
)";
static constexpr StringView kFragmentShaderSource = R"(#version 450
layout(set = 0, binding = 0) uniform sampler2D uSource;
layout(location = 0) in vec2 vTexCoord;
layout(location = 0) out vec4 outColor;
void main() {
outColor = texture(uSource, vTexCoord);
}
)";
auto vs = ShaderCompiler::CompileShader({GL_VERTEX_SHADER, kVertexShaderSource, {}});
if (!vs) {
MGLOG_E("InitializeBlitResources failed: vertex shader compile error: %s", vs.error().log.c_str());
return false;
}
auto fs = ShaderCompiler::CompileShader({GL_FRAGMENT_SHADER, kFragmentShaderSource, {}});
if (!fs) {
MGLOG_E("InitializeBlitResources failed: fragment shader compile error: %s", fs.error().log.c_str());
return false;
}
auto program = ShaderCompiler::LinkProgram({{vs.value(), fs.value()}, {}, {}});
if (!program) {
MGLOG_E("InitializeBlitResources failed: link error: %s", program.error().log.c_str());
return false;
}
auto spirv = ShaderCompiler::GetSpirvBinaryFromProgram({{GL_VERTEX_SHADER, GL_FRAGMENT_SHADER}, *program.value()});
if (!spirv || spirv->size() != 2) {
MGLOG_E("InitializeBlitResources failed: SPIR-V generation failed");
return false;
}
for (SizeT i = 0; i < spirv->size(); ++i) {
const auto& moduleSpv = spirv.value()[i];
VkShaderModuleCreateInfo smci{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
smci.codeSize = moduleSpv.size() * sizeof(Uint);
smci.pCode = moduleSpv.data();
VkShaderModule module = VK_NULL_HANDLE;
VK_VERIFY(vkCreateShaderModule(m_device, &smci, nullptr, &module), "InitializeBlitResources, vkCreateShaderModule");
m_blitPipelineResources.shaderModules.push_back(module);
VkPipelineShaderStageCreateInfo stage{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
stage.stage = (i == 0) ? VK_SHADER_STAGE_VERTEX_BIT : VK_SHADER_STAGE_FRAGMENT_BIT;
stage.module = module;
stage.pName = "main";
m_blitPipelineResources.shaderStages.push_back(stage);
}
VkDescriptorSetLayoutBinding binding{};
binding.binding = 0;
binding.descriptorCount = 1;
binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
VkDescriptorSetLayoutCreateInfo setLayoutInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
setLayoutInfo.bindingCount = 1;
setLayoutInfo.pBindings = &binding;
VK_VERIFY(vkCreateDescriptorSetLayout(m_device, &setLayoutInfo, nullptr, &m_blitPipelineResources.descriptorSetLayout),
"InitializeBlitResources, vkCreateDescriptorSetLayout");
VkPushConstantRange pushConstantRange{};
pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
pushConstantRange.offset = 0;
pushConstantRange.size = sizeof(BlitPushConstants);
VkPipelineLayoutCreateInfo pipelineLayoutInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
pipelineLayoutInfo.setLayoutCount = 1;
pipelineLayoutInfo.pSetLayouts = &m_blitPipelineResources.descriptorSetLayout;
pipelineLayoutInfo.pushConstantRangeCount = 1;
pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
VK_VERIFY(vkCreatePipelineLayout(m_device, &pipelineLayoutInfo, nullptr, &m_blitPipelineResources.pipelineLayout),
"InitializeBlitResources, vkCreatePipelineLayout");
VkDescriptorPoolSize poolSize{};
poolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
poolSize.descriptorCount = m_frameContext.GetFrameCount() > 0 ? m_frameContext.GetFrameCount() : m_config.MaxFramesInFlight;
VkDescriptorPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
poolInfo.maxSets = poolSize.descriptorCount;
poolInfo.poolSizeCount = 1;
poolInfo.pPoolSizes = &poolSize;
VK_VERIFY(vkCreateDescriptorPool(m_device, &poolInfo, nullptr, &m_blitPipelineResources.descriptorPool),
"InitializeBlitResources, vkCreateDescriptorPool");
const Uint32 setCount = std::max<Uint32>(1, m_config.MaxFramesInFlight);
m_blitPipelineResources.descriptorSets.resize(setCount, VK_NULL_HANDLE);
Vector<VkDescriptorSetLayout> layouts(setCount, m_blitPipelineResources.descriptorSetLayout);
VkDescriptorSetAllocateInfo allocInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
allocInfo.descriptorPool = m_blitPipelineResources.descriptorPool;
allocInfo.descriptorSetCount = setCount;
allocInfo.pSetLayouts = layouts.data();
VK_VERIFY(vkAllocateDescriptorSets(m_device, &allocInfo, m_blitPipelineResources.descriptorSets.data()),
"InitializeBlitResources, vkAllocateDescriptorSets");
auto createSampler = [&](VkFilter filter, VkSampler& outSampler) {
VkSamplerCreateInfo samplerInfo{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
samplerInfo.magFilter = filter;
samplerInfo.minFilter = filter;
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerInfo.minLod = 0.f;
samplerInfo.maxLod = 0.f;
samplerInfo.maxAnisotropy = 1.f;
samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK;
VK_VERIFY(vkCreateSampler(m_device, &samplerInfo, nullptr, &outSampler),
"InitializeBlitResources, vkCreateSampler");
};
createSampler(VK_FILTER_NEAREST, m_blitPipelineResources.nearestSampler);
createSampler(VK_FILTER_LINEAR, m_blitPipelineResources.linearSampler);
return true;
}
void VulkanRenderer::ShutdownBlitResources() {
if (m_device == VK_NULL_HANDLE) {
m_blitPipelineResources = {};
return;
}
if (m_blitPipelineResources.nearestSampler != VK_NULL_HANDLE) {
vkDestroySampler(m_device, m_blitPipelineResources.nearestSampler, nullptr);
}
if (m_blitPipelineResources.linearSampler != VK_NULL_HANDLE) {
vkDestroySampler(m_device, m_blitPipelineResources.linearSampler, nullptr);
}
if (m_blitPipelineResources.descriptorPool != VK_NULL_HANDLE) {
vkDestroyDescriptorPool(m_device, m_blitPipelineResources.descriptorPool, nullptr);
}
if (m_blitPipelineResources.pipelineLayout != VK_NULL_HANDLE) {
vkDestroyPipelineLayout(m_device, m_blitPipelineResources.pipelineLayout, nullptr);
}
if (m_blitPipelineResources.descriptorSetLayout != VK_NULL_HANDLE) {
vkDestroyDescriptorSetLayout(m_device, m_blitPipelineResources.descriptorSetLayout, nullptr);
}
for (auto module : m_blitPipelineResources.shaderModules) {
if (module != VK_NULL_HANDLE) {
vkDestroyShaderModule(m_device, module, nullptr);
}
}
m_blitPipelineResources = {};
}
VkPipeline VulkanRenderer::GetOrCreateBlitPipeline(const RenderPassEntry& renderPassEntry) {
static const VkPipelineVertexInputStateCreateInfo kEmptyVertexInputState {
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
};
PipelineFactory::PipelineCreatePayload payload{
.programHash = 0xB17FB17FULL,
.vertexInputHash = 0,
.pipelineLayout = m_blitPipelineResources.pipelineLayout,
.renderPass = renderPassEntry.renderPass,
.subpass = 0,
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
.cullMode = VK_CULL_MODE_NONE,
.frontFace = VK_FRONT_FACE_CLOCKWISE,
.depthTestEnable = false,
.depthWriteEnable = false,
.depthCompareOp = VK_COMPARE_OP_ALWAYS,
.blendEnable = false,
.srcColorBlendFactor = VK_BLEND_FACTOR_ONE,
.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO,
.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
.stages = &m_blitPipelineResources.shaderStages,
.vertexInputState = &kEmptyVertexInputState
};
return m_pipelineFactory->GetOrCreatePipeline(payload);
}
VkPipeline VulkanRenderer::GetOrCreatePipeline( VkPipeline VulkanRenderer::GetOrCreatePipeline(
GLenum mode, GLenum mode,
const MG_State::GLState::ProgramObject& program, const MG_State::GLState::ProgramObject& program,
@@ -443,6 +778,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
void VulkanRenderer::SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects) { void VulkanRenderer::SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects) {
m_textureManager->CollectGarbage();
const auto& drawFbo = const auto& drawFbo =
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
const auto& vao = *MG_State::pGLContext->GetBoundVertexArray(); const auto& vao = *MG_State::pGLContext->GetBoundVertexArray();
@@ -579,6 +915,249 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_clearManager->QueueClear(mask, payload, *fbo); m_clearManager->QueueClear(mask, payload, *fbo);
} }
Bool VulkanRenderer::TryBlitToDefaultFramebufferWithShader(FrameContext::FrameData& frame,
MG_State::GLState::FramebufferObject& readFbo,
MG_State::GLState::FramebufferObject& drawFbo,
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
GLenum filter) {
const Bool drawIsDefaultFbo =
(&drawFbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO.get());
if (!drawIsDefaultFbo) {
return false;
}
BlitImageBinding srcBinding{};
BlitImageBinding dstBinding{};
if (!ResolveColorBlitBinding(readFbo, true, m_imageIndexAcquired, m_swapchainObject, *m_textureManager, srcBinding) ||
!ResolveColorBlitBinding(drawFbo, false, m_imageIndexAcquired, m_swapchainObject, *m_textureManager, dstBinding)) {
return false;
}
if (srcBinding.trackedLayout == nullptr) {
MGLOG_E("BlitFramebuffer skipped: shader blit to default framebuffer requires a texture-backed source framebuffer");
return false;
}
auto* activeRenderPass = VkRenderPassManager::GetActiveRenderPass();
if (activeRenderPass != nullptr) {
VkRenderPassManager::EndRenderPass(frame.commandBuffer);
}
const auto& attachment = readFbo.GetAttachment(readFbo.GetReadBuffer());
auto sourceTexture = attachment.GetTexture();
MOBILEGL_ASSERT(sourceTexture != nullptr, "TryBlitToDefaultFramebufferWithShader: source texture is null");
const Bool ready = m_textureManager->TransitionTextureForSampling(frame.commandBuffer, *sourceTexture);
if (!ready) {
MGLOG_E("BlitFramebuffer skipped: failed to transition source textureId=%d for sampling",
sourceTexture->GetExternalIndex());
return false;
}
auto* sourceResource = m_textureManager->SyncTextureAndGetDescriptor(*sourceTexture);
if (sourceResource == nullptr) {
MGLOG_E("BlitFramebuffer skipped: failed to resolve source textureId=%d after sampling transition",
sourceTexture->GetExternalIndex());
return false;
}
auto& renderPassEntry = m_renderPassManager->GetOrCreateRenderPass(drawFbo, m_imageIndexAcquired);
const Bool ok = VkRenderPassManager::BeginRenderPass(frame.commandBuffer, renderPassEntry);
MOBILEGL_ASSERT(ok, "%s: BeginRenderPass failed", __func__);
VkViewport viewport{};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = static_cast<float>(renderPassEntry.extent.x());
viewport.height = static_cast<float>(renderPassEntry.extent.y());
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
vkCmdSetViewport(frame.commandBuffer, 0, 1, &viewport);
VkRect2D scissor{};
scissor.offset = {0, 0};
scissor.extent = {static_cast<Uint32>(renderPassEntry.extent.x()), static_cast<Uint32>(renderPassEntry.extent.y())};
vkCmdSetScissor(frame.commandBuffer, 0, 1, &scissor);
const VkPipeline pipeline = GetOrCreateBlitPipeline(renderPassEntry);
vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
const Uint32 frameIndex = m_frameContext.GetCurrentFrameIndex() % static_cast<Uint32>(m_blitPipelineResources.descriptorSets.size());
const VkSampler sampler = (filter == GL_LINEAR) ? m_blitPipelineResources.linearSampler
: m_blitPipelineResources.nearestSampler;
VkDescriptorImageInfo imageInfo{
.sampler = sampler,
.imageView = sourceResource->view,
.imageLayout = sourceResource->layout,
};
VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
write.dstSet = m_blitPipelineResources.descriptorSets[frameIndex];
write.dstBinding = 0;
write.descriptorCount = 1;
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
write.pImageInfo = &imageInfo;
vkUpdateDescriptorSets(m_device, 1, &write, 0, nullptr);
vkCmdBindDescriptorSets(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
m_blitPipelineResources.pipelineLayout, 0, 1,
&m_blitPipelineResources.descriptorSets[frameIndex], 0, nullptr);
BlitPushConstants pushConstants{};
const float srcWidth = static_cast<float>(srcBinding.extent.x());
const float srcHeight = static_cast<float>(srcBinding.extent.y());
const float dstWidth = static_cast<float>(dstBinding.extent.x());
const float dstHeight = static_cast<float>(dstBinding.extent.y());
float dstNormWidth = dstWidth;
float dstNormHeight = dstHeight;
switch (m_swapchainObject.GetPreTransform()) {
case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
dstNormWidth = dstHeight;
dstNormHeight = dstWidth;
break;
default:
break;
}
pushConstants.srcRect[0] = static_cast<float>(srcX0) / srcWidth;
pushConstants.srcRect[1] = static_cast<float>(srcY0) / srcHeight;
pushConstants.srcRect[2] = static_cast<float>(srcX1 - srcX0) / srcWidth;
pushConstants.srcRect[3] = static_cast<float>(srcY1 - srcY0) / srcHeight;
pushConstants.dstRect[0] = static_cast<float>(dstX0) / dstNormWidth;
pushConstants.dstRect[1] = static_cast<float>(dstY0) / dstNormHeight;
pushConstants.dstRect[2] = static_cast<float>(dstX1 - dstX0) / dstNormWidth;
pushConstants.dstRect[3] = static_cast<float>(dstY1 - dstY0) / dstNormHeight;
pushConstants.surfaceTransform = static_cast<Uint32>(ToBlitSurfaceTransform(m_swapchainObject.GetPreTransform()));
vkCmdPushConstants(frame.commandBuffer, m_blitPipelineResources.pipelineLayout,
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0,
sizeof(pushConstants), &pushConstants);
vkCmdDraw(frame.commandBuffer, 3, 1, 0, 0);
return true;
}
void VulkanRenderer::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
GLbitfield mask, GLenum filter) {
if ((mask & ~GL_COLOR_BUFFER_BIT) != 0) {
MGLOG_E("BlitFramebuffer skipped: only GL_COLOR_BUFFER_BIT is supported right now (mask=0x%x)",
static_cast<Uint32>(mask));
return;
}
if ((mask & GL_COLOR_BUFFER_BIT) == 0) {
return;
}
if (filter != GL_NEAREST && filter != GL_LINEAR) {
MGLOG_E("BlitFramebuffer skipped: unsupported filter=0x%x", static_cast<Uint32>(filter));
return;
}
auto readFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject();
auto drawFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
MOBILEGL_ASSERT(readFbo != nullptr, "VulkanRenderer::BlitFramebuffer: read framebuffer is null");
MOBILEGL_ASSERT(drawFbo != nullptr, "VulkanRenderer::BlitFramebuffer: draw framebuffer is null");
auto& frame = m_frameContext.GetCurrent();
if (!frame.isCommandRecording) {
m_frameContext.BeginCommandRecording();
m_uniformDescriptorBinder->BeginFrame(m_frameContext.GetCurrentFrameIndex());
}
auto* activeRenderPass = VkRenderPassManager::GetActiveRenderPass();
if (activeRenderPass != nullptr) {
VkRenderPassManager::EndRenderPass(frame.commandBuffer);
}
const Bool readIsDefaultFbo =
(readFbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO);
const Bool drawIsDefaultFbo =
(drawFbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO);
if (drawIsDefaultFbo && m_swapchainObject.GetPreTransform() != VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) {
if (TryBlitToDefaultFramebufferWithShader(frame, *readFbo, *drawFbo,
srcX0, srcY0, srcX1, srcY1,
dstX0, dstY0, dstX1, dstY1, filter)) {
return;
}
MGLOG_E("BlitFramebuffer skipped: rotated blit to default framebuffer requires a texture-backed source framebuffer");
return;
}
BlitImageBinding srcBinding{};
BlitImageBinding dstBinding{};
if (!ResolveColorBlitBinding(*readFbo, true, m_imageIndexAcquired, m_swapchainObject, *m_textureManager, srcBinding) ||
!ResolveColorBlitBinding(*drawFbo, false, m_imageIndexAcquired, m_swapchainObject, *m_textureManager, dstBinding)) {
return;
}
VkImageLayout srcLayout = readIsDefaultFbo
? m_swapchainObject.GetImageLayout(m_imageIndexAcquired)
: *srcBinding.trackedLayout;
VkImageLayout dstLayout = drawIsDefaultFbo
? m_swapchainObject.GetImageLayout(m_imageIndexAcquired)
: *dstBinding.trackedLayout;
if (readIsDefaultFbo && srcLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
MGLOG_E("BlitFramebuffer skipped: swapchain source image layout is undefined");
return;
}
if (srcLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
MGLOG_E("BlitFramebuffer skipped: source image layout is undefined");
return;
}
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
VkAccessFlags srcAccessMask = 0;
GetImageTransitionSourceState(srcLayout, srcStageMask, srcAccessMask);
if (readIsDefaultFbo) {
Bool ok = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, srcBinding.image, srcLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT,
srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, srcBinding.aspectMask);
MOBILEGL_ASSERT(ok, "%s: failed to transition swapchain source image", __func__);
m_swapchainObject.SetImageLayout(m_imageIndexAcquired, srcLayout);
} else {
Bool ok = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, srcBinding.image, *srcBinding.trackedLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT,
srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, srcBinding.aspectMask);
MOBILEGL_ASSERT(ok, "%s: failed to transition source image", __func__);
}
VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
VkAccessFlags dstAccessMask = 0;
GetImageTransitionSourceState(dstLayout, dstStageMask, dstAccessMask);
if (drawIsDefaultFbo) {
Bool ok = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, dstBinding.image, dstLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
dstStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT,
dstAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, dstBinding.aspectMask);
MOBILEGL_ASSERT(ok, "%s: failed to transition swapchain destination image", __func__);
m_swapchainObject.SetImageLayout(m_imageIndexAcquired, dstLayout);
} else {
Bool ok = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, dstBinding.image, *dstBinding.trackedLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
dstStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT,
dstAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, dstBinding.aspectMask);
MOBILEGL_ASSERT(ok, "%s: failed to transition destination image", __func__);
}
VkImageBlit blitRegion{};
blitRegion.srcSubresource.aspectMask = srcBinding.aspectMask;
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 = dstBinding.aspectMask;
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(frame.commandBuffer,
srcBinding.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
dstBinding.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1, &blitRegion, filter == GL_LINEAR ? VK_FILTER_LINEAR : VK_FILTER_NEAREST);
}
void VulkanRenderer::DrawArrays(const DrawArrayCmd& payload) { void VulkanRenderer::DrawArrays(const DrawArrayCmd& payload) {
auto& frame = m_frameContext.GetCurrent(); auto& frame = m_frameContext.GetCurrent();
@@ -82,6 +82,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const RenderPassEntry& compatibleRenderPassEntry); const RenderPassEntry& compatibleRenderPassEntry);
void Clear(GLbitfield mask); void Clear(GLbitfield mask);
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
GLbitfield mask, GLenum filter);
void DrawArrays(const DrawArrayCmd& payload); void DrawArrays(const DrawArrayCmd& payload);
void DrawElements(const DrawElementCmd& payload); void DrawElements(const DrawElementCmd& payload);
void MultiDrawElements(const Vector<DrawElementCmd>& payloads); void MultiDrawElements(const Vector<DrawElementCmd>& payloads);
@@ -93,6 +96,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void RecreateSwapchain(); void RecreateSwapchain();
private: private:
struct BlitPushConstants {
float srcRect[4] = {0.f, 0.f, 1.f, 1.f};
float dstRect[4] = {0.f, 0.f, 1.f, 1.f};
Uint32 surfaceTransform = 0;
Uint32 padding[3] = {0, 0, 0};
};
struct BlitPipelineResources {
VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE;
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
Vector<VkDescriptorSet> descriptorSets;
Vector<VkPipelineShaderStageCreateInfo> shaderStages;
Vector<VkShaderModule> shaderModules;
VkSampler nearestSampler = VK_NULL_HANDLE;
VkSampler linearSampler = VK_NULL_HANDLE;
};
NativeWindowType m_window = 0; NativeWindowType m_window = 0;
VulkanRendererConfig m_config; VulkanRendererConfig m_config;
@@ -135,6 +156,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
UniquePtr<VkRenderPassManager> m_renderPassManager; UniquePtr<VkRenderPassManager> m_renderPassManager;
UniquePtr<VkTextureManager> m_textureManager; UniquePtr<VkTextureManager> m_textureManager;
UniquePtr<VkSamplerManager> m_samplerManager; UniquePtr<VkSamplerManager> m_samplerManager;
BlitPipelineResources m_blitPipelineResources;
void CreateInstance(); void CreateInstance();
VkResult SetupDebugMessenger(); VkResult SetupDebugMessenger();
@@ -160,6 +182,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool EnsureFrameUploadBufferCapacity(Uint32 frameIndex, Bool isIndexBuffer, VkDeviceSize requiredEndOffset, Bool EnsureFrameUploadBufferCapacity(Uint32 frameIndex, Bool isIndexBuffer, VkDeviceSize requiredEndOffset,
VkDeviceSize minCapacity, VkBufferUsageFlags usage); VkDeviceSize minCapacity, VkBufferUsageFlags usage);
Bool UploadAndBindVertexStreams(VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao); Bool UploadAndBindVertexStreams(VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao);
Bool InitializeBlitResources();
void ShutdownBlitResources();
Bool TryBlitToDefaultFramebufferWithShader(FrameContext::FrameData& frame,
MG_State::GLState::FramebufferObject& readFbo,
MG_State::GLState::FramebufferObject& drawFbo,
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
GLenum filter);
VkPipeline GetOrCreateBlitPipeline(const RenderPassEntry& renderPassEntry);
void ShutdownSwapchain(); void ShutdownSwapchain();