[Fix] (MG_Backend/DirectVulkan): fix prerotated default framebuffer

This commit is contained in:
2026-06-23 20:00:43 +08:00
parent 18d19a9a8b
commit f0f6d1e5fa
2 changed files with 150 additions and 32 deletions
@@ -184,6 +184,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
swapchainCaps.minImageExtent.height,
swapchainCaps.maxImageExtent.height);
}
const VkExtent2D defaultFramebufferExtent = createInfo.imageExtent;
if (swapchainCaps.currentTransform == VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR ||
swapchainCaps.currentTransform == VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) {
std::swap(createInfo.imageExtent.width, createInfo.imageExtent.height);
@@ -254,10 +255,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Properly initialize Default FBO here
auto& defaultFBOInfo = MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo;
const Int extentWidth = static_cast<Int>(createInfo.imageExtent.width);
const Int extentHeight = static_cast<Int>(createInfo.imageExtent.height);
const Int extentWidth = static_cast<Int>(defaultFramebufferExtent.width);
const Int extentHeight = static_cast<Int>(defaultFramebufferExtent.height);
const SizeT defaultAttachmentByteSize =
static_cast<SizeT>(createInfo.imageExtent.width) * static_cast<SizeT>(createInfo.imageExtent.height) * 4;
static_cast<SizeT>(defaultFramebufferExtent.width) *
static_cast<SizeT>(defaultFramebufferExtent.height) * 4;
auto* colorTex = static_cast<MG_State::GLState::TextureObject2D*>(defaultFBOInfo->colorAttachment.get());
colorTex->AllocateStorage(
@@ -90,17 +90,53 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return requestedAlpha;
}
static void ApplyGLViewportState(VkCommandBuffer commandBuffer, const IntVec2& fallbackExtent) {
static Bool IsQuarterTurnPreTransform(VkSurfaceTransformFlagBitsKHR preTransform) {
return preTransform == VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR ||
preTransform == VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR;
}
static IntVec2 ResolveDefaultFramebufferLogicalExtent(VkSurfaceTransformFlagBitsKHR preTransform,
const IntVec2& rawExtent) {
if (IsQuarterTurnPreTransform(preTransform)) {
return {rawExtent.y(), rawExtent.x()};
}
return rawExtent;
}
static Int ScaleFramebufferCoordinate(Int value, Int fromExtent, Int toExtent) {
if (fromExtent <= 0 || toExtent <= 0) {
return value;
}
return static_cast<Int>((static_cast<Int64>(value) * toExtent + fromExtent / 2) / fromExtent);
}
static void ApplyGLViewportState(VkCommandBuffer commandBuffer,
const IntVec2& framebufferExtent,
VkSurfaceTransformFlagBitsKHR preTransform,
Bool isDefaultFramebuffer) {
const IntVec4& viewportState = MG_State::pGLContext->GetViewport();
const FloatVec2& depthRange = MG_State::pGLContext->GetDepthRange();
const IntVec2 logicalExtent = isDefaultFramebuffer
? ResolveDefaultFramebufferLogicalExtent(preTransform, framebufferExtent)
: framebufferExtent;
Int viewportX = viewportState.x();
Int viewportY = viewportState.y();
Int viewportWidth = viewportState.z() > 0 ? viewportState.z() : logicalExtent.x();
Int viewportHeight = viewportState.w() > 0 ? viewportState.w() : logicalExtent.y();
if (isDefaultFramebuffer && IsQuarterTurnPreTransform(preTransform)) {
viewportX = ScaleFramebufferCoordinate(viewportX, logicalExtent.x(), framebufferExtent.x());
viewportY = ScaleFramebufferCoordinate(viewportY, logicalExtent.y(), framebufferExtent.y());
viewportWidth = ScaleFramebufferCoordinate(viewportWidth, logicalExtent.x(), framebufferExtent.x());
viewportHeight = ScaleFramebufferCoordinate(viewportHeight, logicalExtent.y(), framebufferExtent.y());
}
VkViewport viewport{};
viewport.x = static_cast<float>(viewportState.x());
viewport.y = static_cast<float>(viewportState.y());
viewport.width =
static_cast<float>(viewportState.z() > 0 ? viewportState.z() : fallbackExtent.x());
viewport.height =
static_cast<float>(viewportState.w() > 0 ? viewportState.w() : fallbackExtent.y());
viewport.x = static_cast<float>(viewportX);
viewport.y = static_cast<float>(viewportY);
viewport.width = static_cast<float>(viewportWidth);
viewport.height = static_cast<float>(viewportHeight);
viewport.minDepth = depthRange.x();
viewport.maxDepth = depthRange.y();
vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
@@ -163,6 +199,33 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return scissor;
}
static VkRect2D MakeDefaultFramebufferScissorRect(const IntVec4& scissorBox,
const IntVec2& framebufferExtent,
VkSurfaceTransformFlagBitsKHR preTransform) {
if (!IsQuarterTurnPreTransform(preTransform)) {
return MakeClampedScissorRect(scissorBox, framebufferExtent);
}
const IntVec2 logicalExtent = ResolveDefaultFramebufferLogicalExtent(preTransform, framebufferExtent);
const Int logicalX0 = std::max<Int>(0, scissorBox.x());
const Int logicalY0 = std::max<Int>(0, scissorBox.y());
const Int logicalX1 = std::min<Int>(logicalExtent.x(), scissorBox.x() + std::max<Int>(0, scissorBox.z()));
const Int logicalY1 = std::min<Int>(logicalExtent.y(), scissorBox.y() + std::max<Int>(0, scissorBox.w()));
const Int rawX0 = ScaleFramebufferCoordinate(logicalX0, logicalExtent.x(), framebufferExtent.x());
const Int rawY0 = ScaleFramebufferCoordinate(logicalY0, logicalExtent.y(), framebufferExtent.y());
const Int rawX1 = ScaleFramebufferCoordinate(logicalX1, logicalExtent.x(), framebufferExtent.x());
const Int rawY1 = ScaleFramebufferCoordinate(logicalY1, logicalExtent.y(), framebufferExtent.y());
VkRect2D scissor{};
scissor.offset = {std::max<Int>(0, rawX0), std::max<Int>(0, rawY0)};
scissor.extent = {
static_cast<Uint32>(std::max<Int>(0, rawX1 - rawX0)),
static_cast<Uint32>(std::max<Int>(0, rawY1 - rawY0)),
};
return scissor;
}
static void ApplyStencilState(VkCommandBuffer commandBuffer) {
const StencilFaceState& frontStencil = MG_State::pGLContext->GetStencilState(StencilFace::Front);
const StencilFaceState& backStencil = MG_State::pGLContext->GetStencilState(StencilFace::Back);
@@ -1410,12 +1473,76 @@ void main() {
case VK_FORMAT_B8G8R8A8_UNORM:
case VK_FORMAT_B8G8R8A8_SNORM:
case VK_FORMAT_B8G8R8A8_SRGB:
case VK_FORMAT_B8G8R8A8_USCALED:
case VK_FORMAT_B8G8R8A8_SSCALED:
return true;
default:
return false;
}
}
static VkExtent2D GetPresentedDumpExtent(VkExtent2D rawExtent,
VkSurfaceTransformFlagBitsKHR preTransform) {
if (IsQuarterTurnPreTransform(preTransform)) {
return {rawExtent.height, rawExtent.width};
}
return rawExtent;
}
static const Uint8* GetPresentedDumpPixel(const Uint8* rawPixels,
VkExtent2D rawExtent,
VkSurfaceTransformFlagBitsKHR preTransform,
Uint32 presentedX,
Uint32 presentedY) {
Uint32 rawX = presentedX;
Uint32 rawY = presentedY;
switch (preTransform) {
case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
rawX = rawExtent.width - 1 - presentedY;
rawY = presentedX;
break;
case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
rawX = rawExtent.width - 1 - presentedX;
rawY = rawExtent.height - 1 - presentedY;
break;
case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
rawX = presentedY;
rawY = rawExtent.height - 1 - presentedX;
break;
default:
break;
}
return rawPixels + (static_cast<SizeT>(rawY) * rawExtent.width + rawX) * 4;
}
static Bool WritePresentedDumpPpm(const char* path,
const Uint8* rawPixels,
VkExtent2D rawExtent,
VkFormat rawFormat,
VkSurfaceTransformFlagBitsKHR preTransform) {
FILE* dump = std::fopen(path, "wb");
if (dump == nullptr) {
return false;
}
const VkExtent2D presentedExtent = GetPresentedDumpExtent(rawExtent, preTransform);
const Bool presentIsBgra = IsBgraVkFormat(rawFormat);
std::fprintf(dump, "P6\n%u %u\n255\n", presentedExtent.width, presentedExtent.height);
for (Uint32 y = 0; y < presentedExtent.height; ++y) {
for (Uint32 x = 0; x < presentedExtent.width; ++x) {
const Uint8* p = GetPresentedDumpPixel(rawPixels, rawExtent, preTransform, x, y);
const Uint8 rgb[3] = {
presentIsBgra ? p[2] : p[0],
p[1],
presentIsBgra ? p[0] : p[2],
};
std::fwrite(rgb, 1, sizeof(rgb), dump);
}
}
std::fclose(dump);
return true;
}
static SizeT AlignPixelRow(SizeT rowBytes, Int alignment) {
const SizeT resolvedAlignment = static_cast<SizeT>(std::max(alignment, 1));
return (rowBytes + resolvedAlignment - 1) & ~(resolvedAlignment - 1);
@@ -3045,7 +3172,8 @@ void main() {
MOBILEGL_ASSERT(idxUploadOk, "SetupDraw skipped: failed to upload index buffer");
}
ApplyGLViewportState(frame.commandBuffer, renderPassEntry->extent);
ApplyGLViewportState(frame.commandBuffer, renderPassEntry->extent,
m_swapchainObject.GetPreTransform(), drawFbo->IsDefaultFramebuffer());
ApplyBlendConstants(frame.commandBuffer);
ApplyPolygonOffsetState(frame.commandBuffer);
ApplyLineWidthState(frame.commandBuffer);
@@ -3055,7 +3183,10 @@ void main() {
VkRect2D scissor{};
if (scissorEnabled) {
const auto& scissorBox = MG_State::pGLContext->GetScissorBox();
scissor = MakeClampedScissorRect(scissorBox, renderPassEntry->extent);
scissor = drawFbo->IsDefaultFramebuffer()
? MakeDefaultFramebufferScissorRect(scissorBox, renderPassEntry->extent,
m_swapchainObject.GetPreTransform())
: MakeClampedScissorRect(scissorBox, renderPassEntry->extent);
} else {
scissor.offset = {0, 0};
scissor.extent = { (Uint)renderPassEntry->extent.x(), (Uint)renderPassEntry->extent.y() };
@@ -3523,7 +3654,8 @@ void main() {
const Bool ok = VkRenderPassManager::BeginRenderPass(frame.commandBuffer, renderPassEntry);
MOBILEGL_ASSERT(ok, "%s: BeginRenderPass failed", __func__);
ApplyGLViewportState(frame.commandBuffer, renderPassEntry.extent);
ApplyGLViewportState(frame.commandBuffer, renderPassEntry.extent,
m_swapchainObject.GetPreTransform(), drawIsDefaultFbo);
VkRect2D scissor{};
scissor.offset = {0, 0};
@@ -5102,25 +5234,9 @@ void main() {
presentStatsExtent.width, presentStatsExtent.height);
}
if (shouldDumpPresent) {
FILE* dump = std::fopen(presentDumpPath, "wb");
if (dump != nullptr) {
const VkFormat presentFormat = m_swapchainObject.GetSurfaceFormat().format;
const Bool presentIsBgra = presentFormat == VK_FORMAT_B8G8R8A8_UNORM ||
presentFormat == VK_FORMAT_B8G8R8A8_SRGB ||
presentFormat == VK_FORMAT_B8G8R8A8_SNORM ||
presentFormat == VK_FORMAT_B8G8R8A8_USCALED ||
presentFormat == VK_FORMAT_B8G8R8A8_SSCALED;
std::fprintf(dump, "P6\n%u %u\n255\n", presentStatsExtent.width, presentStatsExtent.height);
for (SizeT i = 0; i < pixelCount; ++i) {
const Uint8* p = pixels + i * 4;
const Uint8 rgb[3] = {
presentIsBgra ? p[2] : p[0],
p[1],
presentIsBgra ? p[0] : p[2],
};
std::fwrite(rgb, 1, sizeof(rgb), dump);
}
std::fclose(dump);
const VkFormat presentFormat = m_swapchainObject.GetSurfaceFormat().format;
if (WritePresentedDumpPpm(presentDumpPath, pixels, presentStatsExtent, presentFormat,
m_swapchainObject.GetPreTransform())) {
if (PresentStatsEnabled()) {
std::fprintf(stderr, "MOBILEGL_PRESENT_DUMP_WRITTEN path=%s bytes=%zu\n",
presentDumpPath, static_cast<SizeT>(pixelCount * 3));