mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[MG_Backend, MG_IntegrationTest] (P7): copy 1:1 single-sample depth/stencil blits on the Magma wire arm
This commit is contained in:
@@ -517,6 +517,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void ResolveWireDepthStencil(WireImage source, WireImage destination,
|
||||
GLint sx0, GLint sy0, GLint sx1, GLint sy1,
|
||||
GLint dx0, GLint dy0, GLint dx1, GLint dy1);
|
||||
// One aspect of a depth/stencil image through a buffer (source in TRANSFER_SRC_OPTIMAL,
|
||||
// destination in TRANSFER_DST_OPTIMAL, one format). The returned buffer is named by the
|
||||
// recorded commands and must be retired with them.
|
||||
UniquePtr<VkBufferObject> CopyWireAspectThroughBuffer(VkImage srcImage, Uint32 srcLevel, Uint32 srcLayer,
|
||||
GLint sx, GLint sy, const WireImage& destination,
|
||||
GLint dx, GLint dy, Uint32 width, Uint32 height,
|
||||
VkImageAspectFlags aspect, VkFormat format,
|
||||
Bool mirrorY, const char* fatalDetail);
|
||||
// A single-sample depth/stencil blit whose rectangles make it a plain copy, performed
|
||||
// without vkCmdBlitImage (P7 gate 5). False when the shape is not a plain copy.
|
||||
Bool CopyWireDepthStencilBlit(WireImage& source, WireImage& destination,
|
||||
GLint sx0, GLint sy0, GLint sx1, GLint sy1,
|
||||
GLint dx0, GLint dy0, GLint dx1, GLint dy1, VkImageAspectFlags aspect);
|
||||
// True when the rectangles give a multisample depth/stencil resolve a shape it declines
|
||||
// (a scale, an X mirror, a Y mirror across two formats); the decline has then been
|
||||
// logged and its INVALID_OPERATION recorded. BlitWireFramebuffers asks it BEFORE any
|
||||
|
||||
@@ -116,6 +116,11 @@
|
||||
return forced;
|
||||
}
|
||||
|
||||
static Bool WireFormatIsPackedDepthStencil(VkFormat format) {
|
||||
return format == VK_FORMAT_D16_UNORM_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT ||
|
||||
format == VK_FORMAT_D32_SFLOAT_S8_UINT;
|
||||
}
|
||||
|
||||
VulkanRenderer::WireImage VulkanRenderer::ResolveWireImage(
|
||||
const MG_Pipe::MGPFramebufferState& fbo, const MG_Pipe::MGPSurface& surface,
|
||||
VkImageAspectFlags aspect, Bool isWriteTarget) {
|
||||
@@ -494,6 +499,136 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
// ONE ASPECT OF A DEPTH/STENCIL IMAGE, MOVED THROUGH A BUFFER. The source image must be in
|
||||
// TRANSFER_SRC_OPTIMAL and the destination in TRANSFER_DST_OPTIMAL, both of `format`. A
|
||||
// buffer copy names exactly one aspect and moves exactly its texels, which an image copy of a
|
||||
// packed depth-stencil format does not promise (some drivers move the whole native word, so
|
||||
// a depth-only region would replace the destination's stencil). The multisample resolve's
|
||||
// copy-out and the single-sample copy arm share this body; the returned buffer is still named
|
||||
// by the recorded commands and must be retired with them.
|
||||
UniquePtr<VkBufferObject> VulkanRenderer::CopyWireAspectThroughBuffer(
|
||||
VkImage srcImage, Uint32 srcLevel, Uint32 srcLayer, GLint sx, GLint sy,
|
||||
const WireImage& destination, GLint dx, GLint dy, Uint32 width, Uint32 height,
|
||||
VkImageAspectFlags aspect, VkFormat format, Bool mirrorY, const char* fatalDetail) {
|
||||
auto& frame = m_frameContext.GetCurrent();
|
||||
const VkDeviceSize texelSize = aspect == VK_IMAGE_ASPECT_STENCIL_BIT ? 1 :
|
||||
(format == VK_FORMAT_D16_UNORM || format == VK_FORMAT_D16_UNORM_S8_UINT ? 2 : 4);
|
||||
// THE ROW STRIDE IS PADDED TO 4 BYTES. A depth/stencil buffer<->image copy wants
|
||||
// every bufferOffset on a multiple of 4 (VUID-vkCmdCopyBufferToImage-dstImage-07978,
|
||||
// pRegions-07978 in older layers), and the mirror below starts a region on every
|
||||
// row - so a tight row of 1-byte stencil texels whose width is not a multiple of 4,
|
||||
// or of 2-byte D16 texels with an odd width, put every row after the first on an
|
||||
// illegal offset. The scissor clip in BlitWireFramebuffers produces exactly such
|
||||
// widths. Both copies name the padded length as bufferRowLength, so the buffer
|
||||
// layout is one layout whichever way it is walked.
|
||||
const VkDeviceSize texelsPer4Bytes = 4 / texelSize;
|
||||
const VkDeviceSize paddedWidth =
|
||||
(static_cast<VkDeviceSize>(width) + texelsPer4Bytes - 1) / texelsPer4Bytes * texelsPer4Bytes;
|
||||
const VkDeviceSize bytes = paddedWidth * height * texelSize;
|
||||
auto aspectBuffer = MakeUnique<VkBufferObject>();
|
||||
if (!aspectBuffer->Create({.allocator = m_allocator, .size = bytes,
|
||||
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||
.memoryUsage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE}))
|
||||
MagmaWireFatal(fatalDetail);
|
||||
VkBufferImageCopy copy{};
|
||||
copy.bufferRowLength = static_cast<Uint32>(paddedWidth);
|
||||
copy.imageSubresource = {aspect, srcLevel, srcLayer, 1};
|
||||
copy.imageOffset = {sx, sy, 0};
|
||||
copy.imageExtent = {width, height, 1};
|
||||
vkCmdCopyImageToBuffer(frame.commandBuffer, srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
aspectBuffer->GetHandle(), 1, ©);
|
||||
VkBufferMemoryBarrier barrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER};
|
||||
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
barrier.srcQueueFamilyIndex = barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.buffer = aspectBuffer->GetHandle();
|
||||
barrier.size = bytes;
|
||||
vkCmdPipelineBarrier(frame.commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
0, 0, nullptr, 1, &barrier, 0, nullptr);
|
||||
copy.imageSubresource = {aspect, destination.level, destination.layer, 1};
|
||||
copy.imageOffset = {dx, dy, 0};
|
||||
if (!mirrorY) {
|
||||
vkCmdCopyBufferToImage(frame.commandBuffer, aspectBuffer->GetHandle(), destination.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©);
|
||||
} else {
|
||||
// THE VERTICAL MIRROR, and it costs one region per row rather than a pass. The
|
||||
// buffer holds the band bottom row first (the image copy above wrote it from sy
|
||||
// upwards, one padded stride per row); writing row r of that buffer to destination
|
||||
// row dy + height - 1 - r reverses the band. Each region starts on a padded row,
|
||||
// which is what keeps its bufferOffset a multiple of 4.
|
||||
Vector<VkBufferImageCopy> rows(static_cast<SizeT>(height), copy);
|
||||
const VkDeviceSize rowBytes = paddedWidth * texelSize;
|
||||
for (Uint32 row = 0; row < height; ++row) {
|
||||
auto& region = rows[static_cast<SizeT>(row)];
|
||||
region.bufferOffset = static_cast<VkDeviceSize>(row) * rowBytes;
|
||||
region.imageOffset = {dx, static_cast<Int32>(static_cast<Int64>(dy) + height - 1 - row), 0};
|
||||
region.imageExtent = {width, 1, 1};
|
||||
}
|
||||
vkCmdCopyBufferToImage(frame.commandBuffer, aspectBuffer->GetHandle(), destination.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
static_cast<Uint32>(rows.size()), rows.data());
|
||||
}
|
||||
return aspectBuffer;
|
||||
}
|
||||
|
||||
// P7 gate 5 (g5-msrbo): A SINGLE-SAMPLE DEPTH/STENCIL BLIT THAT IS A PLAIN COPY IS A COPY.
|
||||
// Same size, same orientation on both axes, same native format, two distinct non-default,
|
||||
// non-3D images, and the rectangle inside both: nothing a blit adds (scale, filter, flip,
|
||||
// format conversion) is asked for, so the aspect moves with vkCmdCopyImage - or, for one
|
||||
// aspect of a packed depth-stencil format, through CopyWireAspectThroughBuffer so the
|
||||
// destination's other aspect survives. That is what the monolith arm does
|
||||
// (VulkanRenderer::BlitNamedFramebuffer: vkCmdCopyImage at 1:1, CopyDepthStencilAspectThrough-
|
||||
// Buffer for one aspect of a packed format), and neither command needs BLIT_SRC/BLIT_DST,
|
||||
// which a depth/stencil format does not have to carry (the Adreno 830 reports BLIT_SRC and no
|
||||
// BLIT_DST for every depth format). The wire arm sent every depth/stencil blit to
|
||||
// vkCmdBlitImage, and for one aspect of a packed format that moved the whole native word:
|
||||
// lavapipe replaced the destination's stencil on a depth-only blit. Returns false when the
|
||||
// shape is not a plain copy; the caller then takes the vkCmdBlitImage arm as before.
|
||||
Bool VulkanRenderer::CopyWireDepthStencilBlit(WireImage& source, WireImage& destination,
|
||||
GLint sx0, GLint sy0, GLint sx1, GLint sy1, GLint dx0, GLint dy0, GLint dx1, GLint dy1,
|
||||
VkImageAspectFlags aspect) {
|
||||
const Int64 spanX = static_cast<Int64>(sx1) - sx0;
|
||||
const Int64 spanY = static_cast<Int64>(sy1) - sy0;
|
||||
if (spanX == 0 || spanY == 0 || spanX != static_cast<Int64>(dx1) - dx0 ||
|
||||
spanY != static_cast<Int64>(dy1) - dy0)
|
||||
return false;
|
||||
if (source.isDefault || destination.isDefault || source.is3D || destination.is3D ||
|
||||
source.image == destination.image || source.format != destination.format)
|
||||
return false;
|
||||
const Int64 width = spanX < 0 ? -spanX : spanX;
|
||||
const Int64 height = spanY < 0 ? -spanY : spanY;
|
||||
const Int64 sxMin = std::min(sx0, sx1), syMin = std::min(sy0, sy1);
|
||||
const Int64 dxMin = std::min(dx0, dx1), dyMin = std::min(dy0, dy1);
|
||||
if (sxMin < 0 || syMin < 0 || dxMin < 0 || dyMin < 0 ||
|
||||
sxMin + width > static_cast<Int64>(source.extent.width) ||
|
||||
syMin + height > static_cast<Int64>(source.extent.height) ||
|
||||
dxMin + width > static_cast<Int64>(destination.extent.width) ||
|
||||
dyMin + height > static_cast<Int64>(destination.extent.height))
|
||||
return false;
|
||||
TransitionWireImage(source, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
TransitionWireImage(destination, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
if (WireFormatIsPackedDepthStencil(source.format)) {
|
||||
WireRetiredObjects retired{};
|
||||
retired.buffers.push_back(CopyWireAspectThroughBuffer(
|
||||
source.image, source.level, source.layer, static_cast<GLint>(sxMin),
|
||||
static_cast<GLint>(syMin), destination, static_cast<GLint>(dxMin),
|
||||
static_cast<GLint>(dyMin), static_cast<Uint32>(width), static_cast<Uint32>(height),
|
||||
aspect, source.format, /*mirrorY=*/false, "depth-stencil-copy-buffer"));
|
||||
RetireWireObjects(Move(retired));
|
||||
return true;
|
||||
}
|
||||
VkImageCopy copy{};
|
||||
copy.srcSubresource = {aspect, source.level, source.layer, 1};
|
||||
copy.srcOffset = {static_cast<Int32>(sxMin), static_cast<Int32>(syMin), 0};
|
||||
copy.dstSubresource = {aspect, destination.level, destination.layer, 1};
|
||||
copy.dstOffset = {static_cast<Int32>(dxMin), static_cast<Int32>(dyMin), 0};
|
||||
copy.extent = {static_cast<Uint32>(width), static_cast<Uint32>(height), 1};
|
||||
vkCmdCopyImage(m_frameContext.GetCurrent().commandBuffer, source.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, destination.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©);
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanRenderer::ResolveWireDepthStencil(WireImage source, WireImage destination,
|
||||
GLint sx0, GLint sy0, GLint sx1, GLint sy1, GLint dx0, GLint dy0, GLint dx1, GLint dy1) {
|
||||
// P7 wave 2-B2 (review round): A REVERSED RECTANGLE IS NOT A MALFORMED ONE. This guard
|
||||
@@ -690,63 +825,11 @@
|
||||
// depth/stencil image-copy paths copy the complete native word even
|
||||
// for a single-aspect region. A stencil byte or depth-only word in
|
||||
// this GPU buffer cannot replace the destination's other aspect.
|
||||
const VkDeviceSize texelSize = source.aspect == VK_IMAGE_ASPECT_STENCIL_BIT ? 1 :
|
||||
(source.format == VK_FORMAT_D16_UNORM || source.format == VK_FORMAT_D16_UNORM_S8_UINT ? 2 : 4);
|
||||
// THE ROW STRIDE IS PADDED TO 4 BYTES. A depth/stencil buffer<->image copy wants
|
||||
// every bufferOffset on a multiple of 4 (VUID-vkCmdCopyBufferToImage-dstImage-07978,
|
||||
// pRegions-07978 in older layers), and the mirror below starts a region on every
|
||||
// row - so a tight row of 1-byte stencil texels whose width is not a multiple of 4,
|
||||
// or of 2-byte D16 texels with an odd width, put every row after the first on an
|
||||
// illegal offset. The scissor clip in BlitWireFramebuffers produces exactly such
|
||||
// widths. Both copies name the padded length as bufferRowLength, so the buffer
|
||||
// layout is one layout whichever way it is walked.
|
||||
const VkDeviceSize texelsPer4Bytes = 4 / texelSize;
|
||||
const VkDeviceSize paddedWidth =
|
||||
(static_cast<VkDeviceSize>(width) + texelsPer4Bytes - 1) / texelsPer4Bytes * texelsPer4Bytes;
|
||||
const VkDeviceSize bytes = paddedWidth * height * texelSize;
|
||||
aspectBuffer = MakeUnique<VkBufferObject>();
|
||||
if (!aspectBuffer->Create({.allocator = m_allocator, .size = bytes,
|
||||
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||
.memoryUsage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE}))
|
||||
MagmaWireFatal("multisample-depth-resolve-buffer");
|
||||
VkBufferImageCopy copy{};
|
||||
copy.bufferRowLength = static_cast<Uint32>(paddedWidth);
|
||||
copy.imageSubresource = {source.aspect, 0, 0, 1};
|
||||
copy.imageOffset = {sxMin, syMin, 0};
|
||||
copy.imageExtent = {static_cast<Uint32>(width), static_cast<Uint32>(height), 1};
|
||||
vkCmdCopyImageToBuffer(frame.commandBuffer, resolved.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
aspectBuffer->GetHandle(), 1, ©);
|
||||
VkBufferMemoryBarrier barrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER};
|
||||
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
barrier.srcQueueFamilyIndex = barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.buffer = aspectBuffer->GetHandle();
|
||||
barrier.size = bytes;
|
||||
vkCmdPipelineBarrier(frame.commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
0, 0, nullptr, 1, &barrier, 0, nullptr);
|
||||
copy.imageSubresource = {source.aspect, destination.level, destination.layer, 1};
|
||||
copy.imageOffset = {dxMin, dyMin, 0};
|
||||
if (!mirrorY) {
|
||||
vkCmdCopyBufferToImage(frame.commandBuffer, aspectBuffer->GetHandle(), destination.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©);
|
||||
} else {
|
||||
// THE VERTICAL MIRROR, and it costs one region per row rather than a pass. The
|
||||
// buffer holds the resolved band bottom row first (the image copy above wrote it
|
||||
// from syMin upwards, one padded stride per row); writing row r of that buffer
|
||||
// to destination row dyMin + height - 1 - r reverses the band. Each region
|
||||
// starts on a padded row, which is what keeps its bufferOffset a multiple of 4.
|
||||
Vector<VkBufferImageCopy> rows(static_cast<SizeT>(height), copy);
|
||||
const VkDeviceSize rowBytes = paddedWidth * texelSize;
|
||||
for (Int64 row = 0; row < height; ++row) {
|
||||
auto& region = rows[static_cast<SizeT>(row)];
|
||||
region.bufferOffset = static_cast<VkDeviceSize>(row) * rowBytes;
|
||||
region.imageOffset = {dxMin, static_cast<Int32>(dyMin + height - 1 - row), 0};
|
||||
region.imageExtent = {static_cast<Uint32>(width), 1, 1};
|
||||
}
|
||||
vkCmdCopyBufferToImage(frame.commandBuffer, aspectBuffer->GetHandle(), destination.image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
static_cast<Uint32>(rows.size()), rows.data());
|
||||
}
|
||||
aspectBuffer = CopyWireAspectThroughBuffer(resolved.image, 0, 0, sxMin, syMin, destination,
|
||||
dxMin, dyMin, static_cast<Uint32>(width),
|
||||
static_cast<Uint32>(height), source.aspect,
|
||||
source.format, mirrorY,
|
||||
"multisample-depth-resolve-buffer");
|
||||
} else {
|
||||
// A logical GL depth format may use different native renderbuffer
|
||||
// and texture formats. Re-encode only after producing real resolved
|
||||
@@ -962,6 +1045,39 @@
|
||||
RetireWireObjects(Move(retired));
|
||||
return;
|
||||
}
|
||||
// P7 gate 5 (g5-msrbo): A SINGLE-SAMPLE DEPTH/STENCIL BLIT THAT IS A PLAIN COPY IS A COPY
|
||||
// (CopyWireDepthStencilBlit): no blit feature, and one aspect of a packed format moves
|
||||
// without touching the other. Anything else - a scale, a flip, the default framebuffer,
|
||||
// a format change - still takes vkCmdBlitImage below, exactly as it did and as the
|
||||
// monolith arm does. On a format without BLIT_SRC/BLIT_DST that command is outside the
|
||||
// spec, and the Adreno 830 (which has no BLIT_DST on any depth format) executes it
|
||||
// anyway: declining would have turned a working picture on the target device into a
|
||||
// missing one. So it is named once, for the next device reading, and not refused.
|
||||
if (aspect != VK_IMAGE_ASPECT_COLOR_BIT && source.samples == VK_SAMPLE_COUNT_1_BIT &&
|
||||
destination.samples == VK_SAMPLE_COUNT_1_BIT) {
|
||||
if (CopyWireDepthStencilBlit(source, destination, sx0, sy0, sx1, sy1, dx0, dy0, dx1, dy1, aspect)) {
|
||||
if (dst.Kind == MG_Pipe::kMGPipeSurfaceKindTexture)
|
||||
m_textureManager->MarkWireTextureGpuWritten(destination.storage, destination.level,
|
||||
destination.layer, 1);
|
||||
return;
|
||||
}
|
||||
VkFormatProperties sourceProperties{};
|
||||
VkFormatProperties destinationProperties{};
|
||||
vkGetPhysicalDeviceFormatProperties(m_physicalDevice.handle, source.format, &sourceProperties);
|
||||
vkGetPhysicalDeviceFormatProperties(m_physicalDevice.handle, destination.format,
|
||||
&destinationProperties);
|
||||
if (!(sourceProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT) ||
|
||||
!(destinationProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT)) {
|
||||
MGLOG_W_ONCE("DirectVulkan::BlitWireFramebuffers: aspect 0x%x from format %d (features 0x%x) "
|
||||
"to format %d (features 0x%x) is not a plain copy and takes vkCmdBlitImage "
|
||||
"without BLIT_SRC/BLIT_DST - the monolith arm's command, outside the spec; the "
|
||||
"driver decides the picture",
|
||||
static_cast<Uint32>(aspect), static_cast<Int>(source.format),
|
||||
static_cast<Uint32>(sourceProperties.optimalTilingFeatures),
|
||||
static_cast<Int>(destination.format),
|
||||
static_cast<Uint32>(destinationProperties.optimalTilingFeatures));
|
||||
}
|
||||
}
|
||||
// GL names logical window coordinates. Android's native swapchain may
|
||||
// be quarter-turned (e.g. logical 2620x1280 over an image 1280x2620).
|
||||
// An axis-aligned vkCmdBlitImage cannot express that rotation; issuing
|
||||
|
||||
@@ -3364,6 +3364,21 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
${mglItestArmProperties}
|
||||
ENVIRONMENT "${mglItestMagmaWireEnvironment}\;MGITEST_MAGMA_FORCE_SHADER_DEPTH_RESOLVE=${shaderDepthResolve}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p7-msresolve${mglItestArmLogTag}-shader${shaderDepthResolve}.log")
|
||||
endforeach()
|
||||
# ---- P7 gate 5 (g5-msrbo): the SINGLE-SAMPLED depth/stencil blit ----------------------
|
||||
#
|
||||
# KHR-GL46.direct_state_access.renderbuffers_storage_multisample's samples == 0 leg, every
|
||||
# depth/stencil format: the wire arm moved every single-sample depth/stencil aspect with
|
||||
# vkCmdBlitImage where the monolith copies at 1:1, and for one aspect of a packed format
|
||||
# that moved the whole native word (a depth-only blit replaced the destination's stencil
|
||||
# on lavapipe). NO KNOB: the packed-aspect leg is red on every host without the copy arm,
|
||||
# so all three arms carry it.
|
||||
gtest_discover_tests(MobileGLIntegrationTest
|
||||
TEST_PREFIX "DirectVulkan.${mglItestArm}.DsBlit."
|
||||
TEST_FILTER "DepthStencilReadbackMatrixScenario.ASingleSampledDepthStencilBlitCopiesEveryAspect"
|
||||
DISCOVERY_TIMEOUT 30
|
||||
PROPERTIES LABELS "${mglItestMagmaLabels}" TIMEOUT ${MGL_ITEST_TIMEOUT}
|
||||
${mglItestArmProperties}
|
||||
ENVIRONMENT "${mglItestMagmaWireEnvironment}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p7-dsblit${mglItestArmLogTag}.log")
|
||||
# P7 wave 2-B2 (review round): a FLIPPED multisample depth/stencil resolve is a legal blit
|
||||
# that used to end the session, and a SCALED one is INVALID_OPERATION that used to end it
|
||||
# too. Both shapes are decided on the rectangles, before any capability question, so this
|
||||
|
||||
@@ -682,6 +682,181 @@ namespace MGITest {
|
||||
Gl().EndFrame();
|
||||
}
|
||||
|
||||
// P7 gate 5 (g5-msrbo): A SINGLE-SAMPLED DEPTH/STENCIL BLIT, EVERY FORMAT, EVERY ASPECT.
|
||||
//
|
||||
// KHR-GL46.direct_state_access.renderbuffers_storage_multisample reduced to its samples == 0
|
||||
// leg: two renderbuffers of one depth/stencil format (glNamedRenderbufferStorageMultisample
|
||||
// with samples 0, the conformance case's own call), clear the first, glBlitFramebuffer
|
||||
// COLOR|DEPTH|STENCIL into the second at 1:1, read both aspects back from the second. The
|
||||
// wire arm moved every depth/stencil aspect with vkCmdBlitImage, which needs BLIT_SRC/BLIT_DST
|
||||
// (optional for depth/stencil formats: the Adreno 830 has no BLIT_DST on any of them) where
|
||||
// the monolith arm copies at 1:1 - and for ONE aspect of a packed format the blit moved the
|
||||
// whole native word.
|
||||
//
|
||||
// THE SUB-RECTANGLE LEG is the red one without the copy arm, on any host: a depth-only blit
|
||||
// of a packed DEPTH24_STENCIL8 replaced the destination's stencil on lavapipe. It also pins
|
||||
// the min corners on both sides and a 1-byte stencil row whose width is not a multiple of 4
|
||||
// (the buffer round trip's padded stride). THE SCALED LEG is the shape that still takes
|
||||
// vkCmdBlitImage, so the copy arm must not have swallowed it.
|
||||
TEST_F(DepthStencilReadbackMatrixScenario, ASingleSampledDepthStencilBlitCopiesEveryAspect) {
|
||||
if (!Ready()) return;
|
||||
struct BlitFormat {
|
||||
const char* name;
|
||||
GLenum internalFormat;
|
||||
};
|
||||
const BlitFormat formats[] = {
|
||||
{"GL_DEPTH_COMPONENT16", GL_DEPTH_COMPONENT16}, {"GL_DEPTH_COMPONENT24", GL_DEPTH_COMPONENT24},
|
||||
{"GL_DEPTH_COMPONENT32F", GL_DEPTH_COMPONENT32F}, {"GL_DEPTH24_STENCIL8", GL_DEPTH24_STENCIL8},
|
||||
{"GL_DEPTH32F_STENCIL8", GL_DEPTH32F_STENCIL8}, {"GL_STENCIL_INDEX8", GL_STENCIL_INDEX8},
|
||||
};
|
||||
struct Extent {
|
||||
int width, height;
|
||||
};
|
||||
// The conformance case's three shapes ({1,1}, {max/2,1}, {1,max/2}), with strips short
|
||||
// enough for a CPU rasterizer, plus the matrix's own rectangle.
|
||||
const Extent extents[] = {{1, 1}, {256, 1}, {1, 256}, {kWidth, kHeight}};
|
||||
|
||||
const auto makePair = [](GLenum internalFormat, int width, int height, GLuint (&fbo)[2], GLuint (&rbo)[2]) {
|
||||
glGenFramebuffers(2, fbo);
|
||||
glCreateRenderbuffers(2, rbo);
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
glNamedRenderbufferStorageMultisample(rbo[i], 0, internalFormat, width, height);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo[i]);
|
||||
if (FormatHasDepth(internalFormat))
|
||||
glNamedFramebufferRenderbuffer(fbo[i], GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo[i]);
|
||||
if (FormatHasStencil(internalFormat))
|
||||
glNamedFramebufferRenderbuffer(fbo[i], GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo[i]);
|
||||
}
|
||||
};
|
||||
const auto clearBound = [](GLenum internalFormat, int width, int height, float depth, int stencil) {
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glViewport(0, 0, width, height);
|
||||
glDepthMask(GL_TRUE);
|
||||
glStencilMask(0xFFu);
|
||||
glClearDepth(depth);
|
||||
glClearStencil(stencil);
|
||||
GLbitfield mask = 0;
|
||||
if (FormatHasDepth(internalFormat)) mask |= GL_DEPTH_BUFFER_BIT;
|
||||
if (FormatHasStencil(internalFormat)) mask |= GL_STENCIL_BUFFER_BIT;
|
||||
glClear(mask);
|
||||
};
|
||||
|
||||
int exercised = 0;
|
||||
for (const BlitFormat& format : formats) {
|
||||
for (const Extent& extent : extents) {
|
||||
SCOPED_TRACE(std::string(format.name) + " " + std::to_string(extent.width) + "x" +
|
||||
std::to_string(extent.height));
|
||||
GLuint fbo[2] = {0, 0};
|
||||
GLuint rbo[2] = {0, 0};
|
||||
makePair(format.internalFormat, extent.width, extent.height, fbo, rbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo[0]);
|
||||
const bool usable = SourceIsUsable();
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo[1]);
|
||||
if (!usable || !SourceIsUsable()) {
|
||||
glDeleteFramebuffers(2, fbo);
|
||||
glDeleteRenderbuffers(2, rbo);
|
||||
FirstGLError();
|
||||
continue;
|
||||
}
|
||||
ASSERT_EQ(FirstGLError(), 0u) << "creating the renderbuffer pair";
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo[1]);
|
||||
clearBound(format.internalFormat, extent.width, extent.height, kDepthPoison, kStencilPoison);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo[0]);
|
||||
clearBound(format.internalFormat, extent.width, extent.height, 0.5f, 7);
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo[0]);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[1]);
|
||||
glBlitFramebuffer(0, 0, extent.width, extent.height, 0, 0, extent.width, extent.height,
|
||||
GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_NEAREST);
|
||||
EXPECT_EQ(FirstGLError(), 0u) << "a 1:1 single-sampled depth/stencil blit";
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo[1]);
|
||||
if (FormatHasDepth(format.internalFormat))
|
||||
ExpectAllDepth(ReadDepthFloat(0, 0, extent.width, extent.height), 0.5f, "blitted depth");
|
||||
if (FormatHasStencil(format.internalFormat))
|
||||
ExpectAllStencil(ReadStencilInt(0, 0, extent.width, extent.height), 7, "blitted stencil");
|
||||
EXPECT_EQ(FirstGLError(), 0u);
|
||||
++exercised;
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glDeleteFramebuffers(2, fbo);
|
||||
glDeleteRenderbuffers(2, rbo);
|
||||
}
|
||||
}
|
||||
// Six formats times four extents; a machine that hosts fewer than half is not a matrix.
|
||||
EXPECT_GE(exercised, 12) << "too few depth/stencil renderbuffer pairs were usable";
|
||||
|
||||
// THE SUB-RECTANGLE LEG, packed DEPTH24_STENCIL8: a depth-only band, then a stencil-only
|
||||
// band, each offset from the origin on both sides and narrow enough that a 1-byte stencil
|
||||
// row is not a multiple of 4.
|
||||
{
|
||||
GLuint fbo[2] = {0, 0};
|
||||
GLuint rbo[2] = {0, 0};
|
||||
makePair(GL_DEPTH24_STENCIL8, kWidth, kHeight, fbo, rbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo[0]);
|
||||
ASSERT_TRUE(SourceIsUsable());
|
||||
clearBound(GL_DEPTH24_STENCIL8, kWidth, kHeight, 0.75f, 99);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo[1]);
|
||||
clearBound(GL_DEPTH24_STENCIL8, kWidth, kHeight, 0.25f, 11);
|
||||
ASSERT_EQ(FirstGLError(), 0u);
|
||||
constexpr int kSx = 5, kSy = 8, kDx = 20, kDy = 4, kW = 29, kH = 32;
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo[0]);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[1]);
|
||||
glBlitFramebuffer(kSx, kSy, kSx + kW, kSy + kH, kDx, kDy, kDx + kW, kDy + kH, GL_DEPTH_BUFFER_BIT,
|
||||
GL_NEAREST);
|
||||
EXPECT_EQ(FirstGLError(), 0u) << "a depth-only sub-rectangle blit";
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo[1]);
|
||||
const std::vector<float> depth = ReadDepthFloat(0, 0, kWidth, kHeight);
|
||||
const std::vector<int> stencil = ReadStencilInt(0, 0, kWidth, kHeight);
|
||||
size_t badDepth = 0, badStencil = 0;
|
||||
for (int y = 0; y < kHeight; ++y) {
|
||||
for (int x = 0; x < kWidth; ++x) {
|
||||
const bool inside = x >= kDx && x < kDx + kW && y >= kDy && y < kDy + kH;
|
||||
const size_t at = static_cast<size_t>(y) * kWidth + x;
|
||||
if (std::fabs(depth[at] - (inside ? 0.75f : 0.25f)) > 1.0f / 4096.0f) ++badDepth;
|
||||
if (stencil[at] != 11) ++badStencil;
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(badDepth, 0u) << "the depth-only band landed somewhere other than its destination rectangle";
|
||||
EXPECT_EQ(badStencil, 0u) << "a depth-only blit replaced the destination's stencil";
|
||||
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo[0]);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[1]);
|
||||
glBlitFramebuffer(kSx, kSy, kSx + kW, kSy + kH, kDx, kDy, kDx + kW, kDy + kH, GL_STENCIL_BUFFER_BIT,
|
||||
GL_NEAREST);
|
||||
EXPECT_EQ(FirstGLError(), 0u) << "a stencil-only sub-rectangle blit";
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo[1]);
|
||||
const std::vector<float> depthAfter = ReadDepthFloat(0, 0, kWidth, kHeight);
|
||||
const std::vector<int> stencilAfter = ReadStencilInt(0, 0, kWidth, kHeight);
|
||||
badDepth = badStencil = 0;
|
||||
for (int y = 0; y < kHeight; ++y) {
|
||||
for (int x = 0; x < kWidth; ++x) {
|
||||
const bool inside = x >= kDx && x < kDx + kW && y >= kDy && y < kDy + kH;
|
||||
const size_t at = static_cast<size_t>(y) * kWidth + x;
|
||||
if (std::fabs(depthAfter[at] - (inside ? 0.75f : 0.25f)) > 1.0f / 4096.0f) ++badDepth;
|
||||
if (stencilAfter[at] != (inside ? 99 : 11)) ++badStencil;
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(badStencil, 0u) << "the stencil-only band landed somewhere other than its destination rectangle";
|
||||
EXPECT_EQ(badDepth, 0u) << "a stencil-only blit replaced the destination's depth";
|
||||
|
||||
// THE SCALED LEG: half the source onto the whole destination - the shape that still
|
||||
// takes vkCmdBlitImage.
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo[1]);
|
||||
clearBound(GL_DEPTH24_STENCIL8, kWidth, kHeight, 0.25f, 11);
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo[0]);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[1]);
|
||||
glBlitFramebuffer(0, 0, kWidth / 2, kHeight / 2, 0, 0, kWidth, kHeight, GL_DEPTH_BUFFER_BIT, GL_NEAREST);
|
||||
glFinish();
|
||||
EXPECT_EQ(FirstGLError(), 0u) << "a scaled depth blit with NEAREST is legal GL";
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo[1]);
|
||||
ExpectAllDepth(ReadDepthFloat(0, 0, kWidth, kHeight), 0.75f, "a scaled depth blit");
|
||||
EXPECT_EQ(FirstGLError(), 0u) << "the session survived the scaled leg";
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glDeleteFramebuffers(2, fbo);
|
||||
glDeleteRenderbuffers(2, rbo);
|
||||
}
|
||||
Gl().EndFrame();
|
||||
}
|
||||
|
||||
// A read whose rectangle is NOT the whole attachment. The staging copy has to carry
|
||||
// the requested rect (not the origin) and hand back its rows bottom-up, which a
|
||||
// full-extent uniform read is a fixed point of and therefore cannot see.
|
||||
|
||||
Reference in New Issue
Block a user