mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +09:00
[Fix, Test] (DirectVulkan, trace-replay): replay quarter-turn surfaces correctly
This commit is contained in:
@@ -305,7 +305,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// texture/sampler resolution, completeness probe, sync, layout handling, sampler
|
// texture/sampler resolution, completeness probe, sync, layout handling, sampler
|
||||||
// and view lookups - would recompute the identical descriptor.
|
// and view lookups - would recompute the identical descriptor.
|
||||||
if (trustUnchangedHint && descriptorMemoUsable && binding < m_samplerResolveMemo.size() &&
|
if (trustUnchangedHint && descriptorMemoUsable && binding < m_samplerResolveMemo.size() &&
|
||||||
m_samplerResolveMemo[binding].infoValid) {
|
m_samplerResolveMemo[binding].infoValid &&
|
||||||
|
m_samplerResolveMemo[binding].infoProgramLifetimeId == program.GetLifetimeId()) {
|
||||||
outImageInfo = m_samplerResolveMemo[binding].info;
|
outImageInfo = m_samplerResolveMemo[binding].info;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -504,6 +505,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
if (binding < m_samplerResolveMemo.size()) {
|
if (binding < m_samplerResolveMemo.size()) {
|
||||||
if (descriptorMemoUsable) {
|
if (descriptorMemoUsable) {
|
||||||
m_samplerResolveMemo[binding].info = outImageInfo;
|
m_samplerResolveMemo[binding].info = outImageInfo;
|
||||||
|
m_samplerResolveMemo[binding].infoProgramLifetimeId = program.GetLifetimeId();
|
||||||
m_samplerResolveMemo[binding].infoValid = true;
|
m_samplerResolveMemo[binding].infoValid = true;
|
||||||
} else {
|
} else {
|
||||||
// An arrayed binding publishes nothing here, and clears what a previous program
|
// An arrayed binding publishes nothing here, and clears what a previous program
|
||||||
|
|||||||
@@ -341,8 +341,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// lifetime id, so a freed-and-reallocated sampler or texture at the same heap address
|
// lifetime id, so a freed-and-reallocated sampler or texture at the same heap address
|
||||||
// always gets a fresh id and misses (a raw pointer would false-hit that ABA) - so a
|
// always gets a fresh id and misses (a raw pointer would false-hit that ABA) - so a
|
||||||
// stale guess can only miss and fall through to the hash, never resolve wrong. Still
|
// stale guess can only miss and fall through to the hash, never resolve wrong. Still
|
||||||
// reset each frame alongside the descriptor-set cache. Indexed by binding.
|
// reset each frame alongside the descriptor-set cache. Indexed by binding, but the
|
||||||
|
// whole-descriptor entry is additionally keyed by program lifetime: Vulkan binding
|
||||||
|
// numbers are layout-local and unrelated programs routinely reuse binding 0/1.
|
||||||
struct SamplerResolveMemo {
|
struct SamplerResolveMemo {
|
||||||
|
Uint64 infoProgramLifetimeId = 0;
|
||||||
Uint64 samplerLifetimeId = 0;
|
Uint64 samplerLifetimeId = 0;
|
||||||
Uint64 textureLifetimeId = 0;
|
Uint64 textureLifetimeId = 0;
|
||||||
VkSampler sampler = VK_NULL_HANDLE;
|
VkSampler sampler = VK_NULL_HANDLE;
|
||||||
|
|||||||
@@ -238,11 +238,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// nothing else across all of gl33.
|
// nothing else across all of gl33.
|
||||||
//
|
//
|
||||||
// The mapping below is derived from - and at full extent exactly reproduces - the pixel
|
// The mapping below is derived from - and at full extent exactly reproduces - the pixel
|
||||||
// mapping RemapDefaultFboReadbackToGLOrientation has always used:
|
// mapping VulkanRenderer::RemapDefaultFramebufferReadback uses:
|
||||||
// identity : image(x, H-1-y) -> flip Y
|
// identity : image(x, H-1-y) -> flip Y
|
||||||
// 180 : image(W-1-x, y) -> mirror X (the rotation already flips the rows)
|
// 180 : image(W-1-x, y) -> mirror X (the rotation already flips the rows)
|
||||||
// Quarter turns swap the axes; nothing in this renderer models that (the readback declines to
|
// Quarter turns swap the axes and are handled by MapDefaultFramebufferReadbackRect rather than
|
||||||
// remap them and the viewport path only rescales), so they are left exactly as they were.
|
// this same-axis helper.
|
||||||
struct DefaultFramebufferRectMapping {
|
struct DefaultFramebufferRectMapping {
|
||||||
Bool flipY = false;
|
Bool flipY = false;
|
||||||
Bool mirrorX = false;
|
Bool mirrorX = false;
|
||||||
@@ -2151,47 +2151,6 @@ void main() {
|
|||||||
return static_cast<Uint8>(value * 255.0f + 0.5f);
|
return static_cast<Uint8>(value * 255.0f + 0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-order the copied BLOCK - not the whole image - from the default framebuffer's stored
|
|
||||||
// orientation into GL's. The caller has already aimed the copy at the right place with
|
|
||||||
// MapDefaultFramebufferRectAxis, so what arrives here is exactly the requested
|
|
||||||
// rectWidth x rectHeight rect, and all that is left is the order of rows (identity) or of
|
|
||||||
// columns (180) WITHIN it.
|
|
||||||
//
|
|
||||||
// This used to iterate the full swapchain extent and index both sides with that stride,
|
|
||||||
// which is why its caller could only use it on an exact full-extent read - and why every
|
|
||||||
// partial glReadPixels of the default framebuffer came back in Vulkan row order. Only
|
|
||||||
// identity/180 share the swapchain extent with the default framebuffer; 90/270 swap
|
|
||||||
// extents and are still declined.
|
|
||||||
static Bool RemapDefaultFboReadbackToGLOrientation(const Uint8* rawPixels,
|
|
||||||
Uint32 rectWidth,
|
|
||||||
Uint32 rectHeight,
|
|
||||||
VkSurfaceTransformFlagBitsKHR preTransform,
|
|
||||||
SizeT texelSize,
|
|
||||||
Uint8* outPixels) {
|
|
||||||
if (IsQuarterTurnPreTransform(preTransform)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (rectWidth == 0 || rectHeight == 0 || texelSize == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const DefaultFramebufferRectMapping mapping = GetDefaultFramebufferRectMapping(preTransform);
|
|
||||||
const SizeT rowBytes = static_cast<SizeT>(rectWidth) * texelSize;
|
|
||||||
for (Uint32 outY = 0; outY < rectHeight; ++outY) {
|
|
||||||
const Uint32 srcY = mapping.flipY ? (rectHeight - 1 - outY) : outY;
|
|
||||||
const Uint8* srcRow = rawPixels + static_cast<SizeT>(srcY) * rowBytes;
|
|
||||||
Uint8* dstRow = outPixels + static_cast<SizeT>(outY) * rowBytes;
|
|
||||||
if (!mapping.mirrorX) {
|
|
||||||
Memcpy(dstRow, srcRow, rowBytes);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (Uint32 outX = 0; outX < rectWidth; ++outX) {
|
|
||||||
Memcpy(dstRow + static_cast<SizeT>(outX) * texelSize,
|
|
||||||
srcRow + static_cast<SizeT>(rectWidth - 1 - outX) * texelSize, texelSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static SizeT AlignPixelRow(SizeT rowBytes, Int alignment) {
|
static SizeT AlignPixelRow(SizeT rowBytes, Int alignment) {
|
||||||
const SizeT resolvedAlignment = static_cast<SizeT>(std::max(alignment, 1));
|
const SizeT resolvedAlignment = static_cast<SizeT>(std::max(alignment, 1));
|
||||||
return (rowBytes + resolvedAlignment - 1) & ~(resolvedAlignment - 1);
|
return (rowBytes + resolvedAlignment - 1) & ~(resolvedAlignment - 1);
|
||||||
@@ -2738,6 +2697,95 @@ void main() {
|
|||||||
return formatInfo.texel_block_size;
|
return formatInfo.texel_block_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bool VulkanRenderer::MapDefaultFramebufferReadbackRect(
|
||||||
|
GLint x, GLint y, GLsizei width, GLsizei height, VkExtent2D imageExtent,
|
||||||
|
VkSurfaceTransformFlagBitsKHR preTransform, VkOffset2D* imageOffset,
|
||||||
|
VkExtent2D* imageCopyExtent) {
|
||||||
|
if (width <= 0 || height <= 0 || imageOffset == nullptr || imageCopyExtent == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Int imageWidth = static_cast<Int>(imageExtent.width);
|
||||||
|
const Int imageHeight = static_cast<Int>(imageExtent.height);
|
||||||
|
Int mappedX = x;
|
||||||
|
Int mappedY = y;
|
||||||
|
Uint32 mappedWidth = static_cast<Uint32>(width);
|
||||||
|
Uint32 mappedHeight = static_cast<Uint32>(height);
|
||||||
|
|
||||||
|
// InsertPositionFixup first flips GL Y and then applies the surface transform. In pixel
|
||||||
|
// coordinates that gives these half-open rectangle mappings into the stored image:
|
||||||
|
// identity: (x, H-y-h), 90: (y, x), 180: (W-x-w, y), 270: (H-y-h, W-x-w).
|
||||||
|
// Quarter turns also transpose the copied block's extent.
|
||||||
|
switch (preTransform) {
|
||||||
|
case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
|
||||||
|
mappedX = y;
|
||||||
|
mappedY = x;
|
||||||
|
mappedWidth = static_cast<Uint32>(height);
|
||||||
|
mappedHeight = static_cast<Uint32>(width);
|
||||||
|
break;
|
||||||
|
case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
|
||||||
|
mappedX = imageWidth - x - width;
|
||||||
|
mappedY = y;
|
||||||
|
break;
|
||||||
|
case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
|
||||||
|
mappedX = imageWidth - y - height;
|
||||||
|
mappedY = imageHeight - x - width;
|
||||||
|
mappedWidth = static_cast<Uint32>(height);
|
||||||
|
mappedHeight = static_cast<Uint32>(width);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mappedY = imageHeight - y - height;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mappedX < 0 || mappedY < 0 || mappedWidth > imageExtent.width ||
|
||||||
|
mappedHeight > imageExtent.height ||
|
||||||
|
static_cast<Uint64>(mappedX) + mappedWidth > imageExtent.width ||
|
||||||
|
static_cast<Uint64>(mappedY) + mappedHeight > imageExtent.height) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*imageOffset = {mappedX, mappedY};
|
||||||
|
*imageCopyExtent = {mappedWidth, mappedHeight};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool VulkanRenderer::RemapDefaultFramebufferReadback(
|
||||||
|
const Uint8* rawPixels, Uint32 logicalWidth, Uint32 logicalHeight,
|
||||||
|
VkSurfaceTransformFlagBitsKHR preTransform, SizeT texelSize, Uint8* outPixels) {
|
||||||
|
if (rawPixels == nullptr || outPixels == nullptr || logicalWidth == 0 || logicalHeight == 0 ||
|
||||||
|
texelSize == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Uint32 rawWidth = IsQuarterTurnPreTransform(preTransform) ? logicalHeight : logicalWidth;
|
||||||
|
for (Uint32 outY = 0; outY < logicalHeight; ++outY) {
|
||||||
|
for (Uint32 outX = 0; outX < logicalWidth; ++outX) {
|
||||||
|
Uint32 srcX = outX;
|
||||||
|
Uint32 srcY = outY;
|
||||||
|
switch (preTransform) {
|
||||||
|
case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
|
||||||
|
srcX = outY;
|
||||||
|
srcY = outX;
|
||||||
|
break;
|
||||||
|
case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
|
||||||
|
srcX = logicalWidth - 1 - outX;
|
||||||
|
break;
|
||||||
|
case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
|
||||||
|
srcX = logicalHeight - 1 - outY;
|
||||||
|
srcY = logicalWidth - 1 - outX;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
srcY = logicalHeight - 1 - outY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Memcpy(outPixels + (static_cast<SizeT>(outY) * logicalWidth + outX) * texelSize,
|
||||||
|
rawPixels + (static_cast<SizeT>(srcY) * rawWidth + srcX) * texelSize,
|
||||||
|
texelSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Bool VulkanRenderer::ConvertReadbackPixels(const Uint8* sourcePixels, VkFormat sourceFormat,
|
Bool VulkanRenderer::ConvertReadbackPixels(const Uint8* sourcePixels, VkFormat sourceFormat,
|
||||||
GLsizei width, GLsizei height, GLenum destinationFormat,
|
GLsizei width, GLsizei height, GLenum destinationFormat,
|
||||||
GLenum destinationType, SizeT destinationRowStride,
|
GLenum destinationType, SizeT destinationRowStride,
|
||||||
@@ -9138,19 +9186,18 @@ void main() {
|
|||||||
// The GL rect, aimed at the default framebuffer's stored orientation. Using the GL y
|
// The GL rect, aimed at the default framebuffer's stored orientation. Using the GL y
|
||||||
// verbatim copied rows [y, y+h) counted from the TOP of the image, i.e. the wrong band for
|
// verbatim copied rows [y, y+h) counted from the TOP of the image, i.e. the wrong band for
|
||||||
// every read that was not full-height.
|
// every read that was not full-height.
|
||||||
Int32 copyOffsetX = x;
|
VkOffset2D copyOffset{x, y};
|
||||||
Int32 copyOffsetY = y;
|
VkExtent2D copyExtent{static_cast<Uint32>(width), static_cast<Uint32>(height)};
|
||||||
if (readIsDefaultFbo) {
|
if (readIsDefaultFbo) {
|
||||||
const VkExtent2D defaultFboExtent = m_swapchainObject.GetExtent();
|
const VkExtent2D defaultFboExtent = m_swapchainObject.GetExtent();
|
||||||
const DefaultFramebufferRectMapping mapping =
|
const Bool mapped = MapDefaultFramebufferReadbackRect(
|
||||||
GetDefaultFramebufferRectMapping(m_swapchainObject.GetPreTransform());
|
x, y, width, height, defaultFboExtent, m_swapchainObject.GetPreTransform(), ©Offset,
|
||||||
copyOffsetX = MapDefaultFramebufferRectAxis(x, width, static_cast<Int>(defaultFboExtent.width),
|
©Extent);
|
||||||
mapping.mirrorX);
|
MOBILEGL_ASSERT(mapped, "ReadPixels: default framebuffer read rectangle is out of bounds");
|
||||||
copyOffsetY = MapDefaultFramebufferRectAxis(y, height, static_cast<Int>(defaultFboExtent.height),
|
if (!mapped) return;
|
||||||
mapping.flipY);
|
|
||||||
}
|
}
|
||||||
copyRegion.imageOffset = {copyOffsetX, copyOffsetY, static_cast<Int32>(srcBinding.depthOffset)};
|
copyRegion.imageOffset = {copyOffset.x, copyOffset.y, static_cast<Int32>(srcBinding.depthOffset)};
|
||||||
copyRegion.imageExtent = {static_cast<Uint32>(width), static_cast<Uint32>(height), 1};
|
copyRegion.imageExtent = {copyExtent.width, copyExtent.height, 1};
|
||||||
vkCmdCopyImageToBuffer(frame.commandBuffer, srcBinding.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
vkCmdCopyImageToBuffer(frame.commandBuffer, srcBinding.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||||
readback.GetHandle(), 1, ©Region);
|
readback.GetHandle(), 1, ©Region);
|
||||||
|
|
||||||
@@ -9192,16 +9239,14 @@ void main() {
|
|||||||
// already aimed with the same mapping. The gate is exactly what made every partial
|
// already aimed with the same mapping. The gate is exactly what made every partial
|
||||||
// read of the default framebuffer come back in Vulkan row order.
|
// read of the default framebuffer come back in Vulkan row order.
|
||||||
Vector<Uint8> remapped(static_cast<SizeT>(width) * static_cast<SizeT>(height) * sourceTexelSize);
|
Vector<Uint8> remapped(static_cast<SizeT>(width) * static_cast<SizeT>(height) * sourceTexelSize);
|
||||||
if (RemapDefaultFboReadbackToGLOrientation(mapped, static_cast<Uint32>(width),
|
if (RemapDefaultFramebufferReadback(mapped, static_cast<Uint32>(width),
|
||||||
static_cast<Uint32>(height), preTransform, sourceTexelSize,
|
static_cast<Uint32>(height), preTransform, sourceTexelSize,
|
||||||
remapped.data())) {
|
remapped.data())) {
|
||||||
PackReadbackToClientOrPbo(remapped.data(), srcFormat, width, height, 1, format, type, pixels,
|
PackReadbackToClientOrPbo(remapped.data(), srcFormat, width, height, 1, format, type, pixels,
|
||||||
/*applyPackImageParams=*/false, /*applyReadColorClamp=*/true);
|
/*applyPackImageParams=*/false, /*applyReadColorClamp=*/true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Only a quarter-turn pre-transform reaches this, and nothing in this renderer models
|
MGLOG_D("DirectVulkan::ReadPixels: default-FBO remap failed (w=%d h=%d preTransform=%d); falling back "
|
||||||
// one. MGLOG_I because the INFO builds are the ones that run conformance.
|
|
||||||
MGLOG_D("DirectVulkan::ReadPixels: default-FBO remap declined (w=%d h=%d preTransform=%d); falling back "
|
|
||||||
"to raw readback",
|
"to raw readback",
|
||||||
width, height, static_cast<Int>(preTransform));
|
width, height, static_cast<Int>(preTransform));
|
||||||
}
|
}
|
||||||
@@ -9582,16 +9627,15 @@ void main() {
|
|||||||
// The swapchain's depth/stencil image is stored display-side-up like its colour twin, so
|
// The swapchain's depth/stencil image is stored display-side-up like its colour twin, so
|
||||||
// the GL rect has to be mapped into that space before the copy and the copied rows
|
// the GL rect has to be mapped into that space before the copy and the copied rows
|
||||||
// re-oriented afterwards - the same two halves the colour ReadPixels path applies.
|
// re-oriented afterwards - the same two halves the colour ReadPixels path applies.
|
||||||
Int32 copyOffsetX = x;
|
VkOffset2D copyOffset{x, y};
|
||||||
Int32 copyOffsetY = y;
|
VkExtent2D copyExtent{static_cast<Uint32>(width), static_cast<Uint32>(height)};
|
||||||
if (defaultFramebufferOrientation) {
|
if (defaultFramebufferOrientation) {
|
||||||
const VkExtent2D defaultFboExtent = m_swapchainObject.GetExtent();
|
const VkExtent2D defaultFboExtent = m_swapchainObject.GetExtent();
|
||||||
const DefaultFramebufferRectMapping mapping =
|
const Bool mapped = MapDefaultFramebufferReadbackRect(
|
||||||
GetDefaultFramebufferRectMapping(m_swapchainObject.GetPreTransform());
|
x, y, width, height, defaultFboExtent, m_swapchainObject.GetPreTransform(), ©Offset,
|
||||||
copyOffsetX = MapDefaultFramebufferRectAxis(x, width, static_cast<Int>(defaultFboExtent.width),
|
©Extent);
|
||||||
mapping.mirrorX);
|
MOBILEGL_ASSERT(mapped, "ReadDepthStencilPixels: default framebuffer read rectangle is out of bounds");
|
||||||
copyOffsetY = MapDefaultFramebufferRectAxis(y, height, static_cast<Int>(defaultFboExtent.height),
|
if (!mapped) return;
|
||||||
mapping.flipY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VkBufferImageCopy regions[2]{};
|
VkBufferImageCopy regions[2]{};
|
||||||
@@ -9603,8 +9647,8 @@ void main() {
|
|||||||
region.imageSubresource.mipLevel = mipLevel;
|
region.imageSubresource.mipLevel = mipLevel;
|
||||||
region.imageSubresource.baseArrayLayer = baseArrayLayer;
|
region.imageSubresource.baseArrayLayer = baseArrayLayer;
|
||||||
region.imageSubresource.layerCount = 1;
|
region.imageSubresource.layerCount = 1;
|
||||||
region.imageOffset = {copyOffsetX, copyOffsetY, 0};
|
region.imageOffset = {copyOffset.x, copyOffset.y, 0};
|
||||||
region.imageExtent = {static_cast<Uint32>(width), static_cast<Uint32>(height), 1};
|
region.imageExtent = {copyExtent.width, copyExtent.height, 1};
|
||||||
}
|
}
|
||||||
if (wantStencil) {
|
if (wantStencil) {
|
||||||
auto& region = regions[regionCount++];
|
auto& region = regions[regionCount++];
|
||||||
@@ -9613,8 +9657,8 @@ void main() {
|
|||||||
region.imageSubresource.mipLevel = mipLevel;
|
region.imageSubresource.mipLevel = mipLevel;
|
||||||
region.imageSubresource.baseArrayLayer = baseArrayLayer;
|
region.imageSubresource.baseArrayLayer = baseArrayLayer;
|
||||||
region.imageSubresource.layerCount = 1;
|
region.imageSubresource.layerCount = 1;
|
||||||
region.imageOffset = {copyOffsetX, copyOffsetY, 0};
|
region.imageOffset = {copyOffset.x, copyOffset.y, 0};
|
||||||
region.imageExtent = {static_cast<Uint32>(width), static_cast<Uint32>(height), 1};
|
region.imageExtent = {copyExtent.width, copyExtent.height, 1};
|
||||||
}
|
}
|
||||||
vkCmdCopyImageToBuffer(frame.commandBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, readback.GetHandle(),
|
vkCmdCopyImageToBuffer(frame.commandBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, readback.GetHandle(),
|
||||||
regionCount, regions);
|
regionCount, regions);
|
||||||
@@ -9648,23 +9692,21 @@ void main() {
|
|||||||
Bool remapped = true;
|
Bool remapped = true;
|
||||||
if (wantDepth && depthCopyBytes > 0) {
|
if (wantDepth && depthCopyBytes > 0) {
|
||||||
remappedDepth.resize(pixelCount * depthCopyBytes);
|
remappedDepth.resize(pixelCount * depthCopyBytes);
|
||||||
remapped = RemapDefaultFboReadbackToGLOrientation(depthSrc, static_cast<Uint32>(width),
|
remapped = RemapDefaultFramebufferReadback(depthSrc, static_cast<Uint32>(width),
|
||||||
static_cast<Uint32>(height), preTransform,
|
static_cast<Uint32>(height), preTransform,
|
||||||
depthCopyBytes, remappedDepth.data());
|
depthCopyBytes, remappedDepth.data());
|
||||||
}
|
}
|
||||||
if (remapped && wantStencil) {
|
if (remapped && wantStencil) {
|
||||||
remappedStencil.resize(pixelCount);
|
remappedStencil.resize(pixelCount);
|
||||||
remapped = RemapDefaultFboReadbackToGLOrientation(stencilSrc, static_cast<Uint32>(width),
|
remapped = RemapDefaultFramebufferReadback(stencilSrc, static_cast<Uint32>(width),
|
||||||
static_cast<Uint32>(height), preTransform, 1,
|
static_cast<Uint32>(height), preTransform, 1,
|
||||||
remappedStencil.data());
|
remappedStencil.data());
|
||||||
}
|
}
|
||||||
if (remapped) {
|
if (remapped) {
|
||||||
if (!remappedDepth.empty()) depthSrc = remappedDepth.data();
|
if (!remappedDepth.empty()) depthSrc = remappedDepth.data();
|
||||||
if (!remappedStencil.empty()) stencilSrc = remappedStencil.data();
|
if (!remappedStencil.empty()) stencilSrc = remappedStencil.data();
|
||||||
} else {
|
} else {
|
||||||
// Only a quarter-turn pre-transform reaches this, and nothing in this renderer
|
MGLOG_D("DirectVulkan::ReadDepthStencilPixels: default-FBO remap failed (w=%d h=%d "
|
||||||
// models one. MGLOG_I because the INFO builds are the ones that run conformance.
|
|
||||||
MGLOG_D("DirectVulkan::ReadDepthStencilPixels: default-FBO remap declined (w=%d h=%d "
|
|
||||||
"preTransform=%d); falling back to raw readback",
|
"preTransform=%d); falling back to raw readback",
|
||||||
width, height, static_cast<Int>(preTransform));
|
width, height, static_cast<Int>(preTransform));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -229,6 +229,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
GLint dstY, GLint width, GLint height, VkImageLayout srcRestoreLayout,
|
GLint dstY, GLint width, GLint height, VkImageLayout srcRestoreLayout,
|
||||||
VkImageLayout dstRestoreLayout, Bool stencilAspect);
|
VkImageLayout dstRestoreLayout, Bool stencilAspect);
|
||||||
static SizeT GetReadbackTexelSize(VkFormat sourceFormat);
|
static SizeT GetReadbackTexelSize(VkFormat sourceFormat);
|
||||||
|
// Map a GL bottom-left-origin rectangle into the display-oriented swapchain image.
|
||||||
|
// Quarter-turn surface transforms swap the copy extent's axes.
|
||||||
|
static Bool MapDefaultFramebufferReadbackRect(GLint x, GLint y, GLsizei width, GLsizei height,
|
||||||
|
VkExtent2D imageExtent,
|
||||||
|
VkSurfaceTransformFlagBitsKHR preTransform,
|
||||||
|
VkOffset2D* imageOffset, VkExtent2D* imageCopyExtent);
|
||||||
|
// Reorder a tightly packed block copied with MapDefaultFramebufferReadbackRect back into
|
||||||
|
// GL row order. The input block has swapped dimensions for 90/270 degree transforms.
|
||||||
|
static Bool RemapDefaultFramebufferReadback(const Uint8* rawPixels, Uint32 logicalWidth,
|
||||||
|
Uint32 logicalHeight,
|
||||||
|
VkSurfaceTransformFlagBitsKHR preTransform,
|
||||||
|
SizeT texelSize, Uint8* outPixels);
|
||||||
static Bool ConvertReadbackPixels(const Uint8* sourcePixels, VkFormat sourceFormat,
|
static Bool ConvertReadbackPixels(const Uint8* sourcePixels, VkFormat sourceFormat,
|
||||||
GLsizei width, GLsizei height, GLenum destinationFormat,
|
GLsizei width, GLsizei height, GLenum destinationFormat,
|
||||||
GLenum destinationType, SizeT destinationRowStride,
|
GLenum destinationType, SizeT destinationRowStride,
|
||||||
|
|||||||
@@ -936,6 +936,45 @@ TEST(DirectVulkanSanity, ReadbackUsesTheSourceFormatTexelSize) {
|
|||||||
EXPECT_EQ(VulkanRenderer::GetReadbackTexelSize(VK_FORMAT_R32G32B32A32_SFLOAT), 16u);
|
EXPECT_EQ(VulkanRenderer::GetReadbackTexelSize(VK_FORMAT_R32G32B32A32_SFLOAT), 16u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(DirectVulkanSanity, DefaultFramebufferQuarterTurnReadbackMapsRectAndPixels) {
|
||||||
|
using MobileGL::MG_Backend::DirectVulkan::VulkanRenderer;
|
||||||
|
using MobileGL::Uint8;
|
||||||
|
|
||||||
|
VkOffset2D offset{};
|
||||||
|
VkExtent2D copyExtent{};
|
||||||
|
ASSERT_TRUE(VulkanRenderer::MapDefaultFramebufferReadbackRect(
|
||||||
|
1, 0, 2, 1, VkExtent2D{2, 3}, VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR,
|
||||||
|
&offset, ©Extent));
|
||||||
|
EXPECT_EQ(offset.x, 0);
|
||||||
|
EXPECT_EQ(offset.y, 1);
|
||||||
|
EXPECT_EQ(copyExtent.width, 1u);
|
||||||
|
EXPECT_EQ(copyExtent.height, 2u);
|
||||||
|
|
||||||
|
ASSERT_TRUE(VulkanRenderer::MapDefaultFramebufferReadbackRect(
|
||||||
|
1, 0, 2, 1, VkExtent2D{2, 3}, VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR,
|
||||||
|
&offset, ©Extent));
|
||||||
|
EXPECT_EQ(offset.x, 1);
|
||||||
|
EXPECT_EQ(offset.y, 0);
|
||||||
|
EXPECT_EQ(copyExtent.width, 1u);
|
||||||
|
EXPECT_EQ(copyExtent.height, 2u);
|
||||||
|
|
||||||
|
// Logical GL rows, bottom to top, are abc / def. The display-oriented swapchain blocks are
|
||||||
|
// transposed in opposite directions for 90 and 270 degrees.
|
||||||
|
const Uint8 raw90[] = {'a', 'd', 'b', 'e', 'c', 'f'};
|
||||||
|
const Uint8 raw270[] = {'f', 'c', 'e', 'b', 'd', 'a'};
|
||||||
|
const Uint8 expected[] = {'a', 'b', 'c', 'd', 'e', 'f'};
|
||||||
|
Uint8 result[sizeof(expected)]{};
|
||||||
|
|
||||||
|
ASSERT_TRUE(VulkanRenderer::RemapDefaultFramebufferReadback(
|
||||||
|
raw90, 3, 2, VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR, 1, result));
|
||||||
|
EXPECT_TRUE(std::equal(std::begin(expected), std::end(expected), std::begin(result)));
|
||||||
|
|
||||||
|
std::fill(std::begin(result), std::end(result), 0);
|
||||||
|
ASSERT_TRUE(VulkanRenderer::RemapDefaultFramebufferReadback(
|
||||||
|
raw270, 3, 2, VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR, 1, result));
|
||||||
|
EXPECT_TRUE(std::equal(std::begin(expected), std::end(expected), std::begin(result)));
|
||||||
|
}
|
||||||
|
|
||||||
TEST(DirectVulkanSanity, ReadbackConvertsRgba8AndRgba16fPixels) {
|
TEST(DirectVulkanSanity, ReadbackConvertsRgba8AndRgba16fPixels) {
|
||||||
using MobileGL::MG_Backend::DirectVulkan::VulkanRenderer;
|
using MobileGL::MG_Backend::DirectVulkan::VulkanRenderer;
|
||||||
using MobileGL::MG_Util::EncodeFloatToHalfBits;
|
using MobileGL::MG_Util::EncodeFloatToHalfBits;
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ The bundled fixtures cover:
|
|||||||

|

|
||||||
- minecraft-1.21.4-main-menu: captured from Minecraft 1.21.4's main menu.
|
- minecraft-1.21.4-main-menu: captured from Minecraft 1.21.4's main menu.
|
||||||

|

|
||||||
|
- minecraft-1.21.11-main-menu: captured from Minecraft 1.21.11's main menu on a Pixel 8 Pro through FCL MobileGL.
|
||||||
|

|
||||||
- minecraft-1.17-main-menu-854: captured from Minecraft 1.17's 854x480 main menu through FCL MobileGL capture.
|
- minecraft-1.17-main-menu-854: captured from Minecraft 1.17's 854x480 main menu through FCL MobileGL capture.
|
||||||

|

|
||||||
- minecraft-1.21.4-in-world: captured from Minecraft 1.21.4 after entering a singleplayer world.
|
- minecraft-1.21.4-in-world: captured from Minecraft 1.21.4 after entering a singleplayer world.
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -40,6 +40,13 @@
|
|||||||
"target_call": 481787,
|
"target_call": 481787,
|
||||||
"timeout_seconds": 180
|
"timeout_seconds": 180
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "minecraft-1.21.11-main-menu",
|
||||||
|
"trace_archive": "minecraft-1.21.11-main-menu.tgz",
|
||||||
|
"golden": "minecraft-1.21.11-main-menu.0000205347.png",
|
||||||
|
"target_call": 205347,
|
||||||
|
"timeout_seconds": 180
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "minecraft-1.17-main-menu-854",
|
"name": "minecraft-1.17-main-menu-854",
|
||||||
"trace_archive": "minecraft-1.17-main-menu-854.tgz",
|
"trace_archive": "minecraft-1.17-main-menu-854.tgz",
|
||||||
|
|||||||
Reference in New Issue
Block a user