mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-16 06:23:23 +09:00
[MG_Backend, MG_IntegrationTest, docs] (P7): a same-image copy takes one GENERAL transition instead of the session
CONTRACT-P7 §3.2's `copy-image-in-place@P7`, retired.
glCopyImageSubData between two subresources of ONE texture is ordinary, defined GL -
4.6 core 18.3.2 spells out that a texture and a view of it are two objects over one
image and may be copied between. The wire arm took the session down for it, and the
reason it gave was true of the code beneath it and of nothing else: the
TRANSFER_SRC/TRANSFER_DST pair cannot describe one image, because both endpoints share
one tracked VkImageLayout and the second transition undoes the first.
VK_IMAGE_LAYOUT_GENERAL can describe it - both sides of vkCmdCopyImage accept it. So a
same-image copy now takes ONE transition of the whole image to GENERAL (the source level
and the destination level are both inside it), GENERAL on both sides of the command, and
ONE restore, with an access mask carrying both directions because that one barrier is
the edge for both. The monolith arm still declines every in-place copy a few lines up,
so the wire arm is the one that performs this - which is what §3.2 asks for rather than
parity. `inPlace` is constexpr false in the pull build so every branch it guards folds:
this function is in the monolith image and G1 pins that image's .text.
The one shape that stays a decline is same image + same level + overlapping slices +
overlapping rectangle, all three at once. GL leaves that undefined, and GENERAL makes
the command legal to RECORD rather than meaningful to execute - vkCmdCopyImage orders
nothing between its own reads and writes inside a region, so a tiler produces whichever
half of each texel it reached first. Level 0 to level 1, disjoint layers, and disjoint
rectangles of one level are all defined, and all performed.
Two split-only cases on the three Magma arms, over one 8x8x2 two-level array texture
whose every touched subresource is a different colour - a flat fill would let "copied to
the wrong level", "copied to the wrong layer" and "did not copy" all read as correct.
CopyImageInPlaceAcrossLevelsAndLayers does level 0 -> level 1 and then layer 0 -> layer 1
and checks the untouched subresources too; CopyImageInPlaceOverlapDeclinesAndLeavesTheLevelAlone
copies (0,0)-(4,4) onto (2,2) and pins every texel unchanged - an executed copy would
turn the texel at (4,4) from yellow to red, so the assertion can tell.
Red once (executed, reverted): with MagmaWireFatal("copy-image-in-place@P7") restored,
both entries die with Fatal{UnmigratedVerb, "Magma:copy-image-in-place@P7"}; without it,
2/2 green.
Gates: integration-magma-split 79 (+2), -spawn 58 (+2), -tcp 60 (+2), 0 red;
ctest -L unit 2410; informational integration-magma-full-split 515, 0 red, 59 skip;
fatal_census green (79 sites / 43 words / 0 unmarked); link_ratchet --assert-monotone
unchanged at 186, p7-magma 101; G1 pull .text 0xa52203 and the defined-symbol name list
identical (30570) - the constexpr-false branches fold as intended.
This tree's integration TCP endpoint moved to 127.0.0.1:40913: a sibling worktree's
libMobileGLServer was already listening on pipe's default 40613 and took the whole tcp
lane down at TcpServer.Start. It is a local configure option, not a lane semantic.
This commit is contained in:
@@ -10386,10 +10386,24 @@ void main() {
|
||||
const Bool srcResolved = resolveImage(srcEndpoint, srcImage);
|
||||
const Bool dstResolved = resolveImage(dstEndpoint, dstImage);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (wire) {
|
||||
if (srcImage.image == dstImage.image) MagmaWireFatal("copy-image-in-place@P7");
|
||||
m_textureManager->FlushPendingUploads();
|
||||
}
|
||||
// P7 wave 2-B, CONTRACT-P7 §3.2: `copy-image-in-place@P7` RETIRES.
|
||||
//
|
||||
// What the Fatal here used to say was true of the code below it and of nothing else: the
|
||||
// TRANSFER_SRC/TRANSFER_DST pair cannot describe one image, because both endpoints share
|
||||
// one VkImageLayout and the second transition undoes the first. VK_IMAGE_LAYOUT_GENERAL
|
||||
// can describe it - it is a legal layout for both sides of vkCmdCopyImage - so a
|
||||
// same-image copy takes ONE transition of the whole image to GENERAL, GENERAL on both
|
||||
// sides of the command, and ONE restore. The monolith arm declines this shape outright
|
||||
// (a few lines up, `!wire &&`); the wire arm is now the one that performs it, which is
|
||||
// what §3.2 asks for and more than parity would give.
|
||||
//
|
||||
// `inPlace` is constexpr false in the pull build so that every branch it guards folds
|
||||
// away: this function is in the monolith image too and G1 pins that image's .text.
|
||||
const Bool inPlace = wire && srcImage.image != VK_NULL_HANDLE &&
|
||||
srcImage.image == dstImage.image;
|
||||
if (wire) m_textureManager->FlushPendingUploads();
|
||||
#else
|
||||
constexpr Bool inPlace = false;
|
||||
#endif
|
||||
// Real checks, not MOBILEGL_ASSERT: the assertions this replaces compile to nothing in
|
||||
// a release build, which is where both observed failures happened - a null resource
|
||||
@@ -10535,6 +10549,34 @@ void main() {
|
||||
__func__, srcZ, srcSlices.availableSlices, dstZ, dstSlices.availableSlices, srcDepth);
|
||||
return;
|
||||
}
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// THE ONE IN-PLACE SHAPE THAT STAYS A DECLINE (§3.2). GL 4.6 core 18.3.2: if the source
|
||||
// and destination name the same image AND the same level AND the same layers, the result
|
||||
// is UNDEFINED where the regions overlap. GENERAL makes the command legal to record, not
|
||||
// meaningful to execute - vkCmdCopyImage has no ordering between its own reads and writes
|
||||
// within one region, so what a tiler produces is whichever half of each texel it reached
|
||||
// first. Declining says that, once, instead of shipping it.
|
||||
//
|
||||
// The test is ALL THREE dimensions at once: same level, overlapping slice range, and
|
||||
// overlapping rectangle. A copy from level 0 to level 1, or between disjoint layers, or
|
||||
// between disjoint rectangles of one level, is perfectly defined and is performed.
|
||||
if (inPlace && srcMipLevel == dstMipLevel) {
|
||||
const Uint32 srcSliceBegin = srcSlices.baseSlice, dstSliceBegin = dstSlices.baseSlice;
|
||||
const Bool slicesOverlap = srcSliceBegin < dstSliceBegin + copySliceCount &&
|
||||
dstSliceBegin < srcSliceBegin + copySliceCount;
|
||||
const Bool rectOverlaps = srcX < dstX + srcWidth && dstX < srcX + srcWidth &&
|
||||
srcY < dstY + srcHeight && dstY < srcY + srcHeight;
|
||||
if (slicesOverlap && rectOverlaps) {
|
||||
MGLOG_E_ONCE("%s: in-place copy on objectId=%u overlaps itself at level %u "
|
||||
"(src %dx%d+%d+%d slice %u, dst +%d+%d slice %u, %u slice(s)); GL leaves "
|
||||
"the result undefined, so the copy is declined rather than recorded",
|
||||
__func__, CopyImageEndpointName(srcEndpoint), srcMipLevel,
|
||||
srcWidth, srcHeight, srcX, srcY, srcSliceBegin, dstX, dstY,
|
||||
dstSliceBegin, copySliceCount);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
const auto materializeClear = [this, &frame](const CopyImageEndpoint& endpoint) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
@@ -10579,6 +10621,19 @@ void main() {
|
||||
const VkImageLayout srcRestoreLayout = resolveRestoreLayout(srcOriginalLayout, srcImage.isRenderbuffer);
|
||||
const VkImageLayout dstRestoreLayout = resolveRestoreLayout(dstOriginalLayout, dstImage.isRenderbuffer);
|
||||
|
||||
// ONE IMAGE, ONE LAYOUT, ONE TRANSITION. For a same-image copy both endpoints are the same
|
||||
// VkImage behind the same tracked layout, so the two transitions below would describe one
|
||||
// subresource twice and the second would undo the first. GENERAL is the layout both sides
|
||||
// of vkCmdCopyImage accept at once, the barrier covers the WHOLE image (both the source
|
||||
// level and the destination level are in it), and the access mask carries both directions
|
||||
// because this one barrier is the edge for both.
|
||||
const VkImageLayout copySourceLayout =
|
||||
inPlace ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
const VkImageLayout copyDestinationLayout =
|
||||
inPlace ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
const VkAccessFlags copySourceAccess =
|
||||
inPlace ? (VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT)
|
||||
: VK_ACCESS_TRANSFER_READ_BIT;
|
||||
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||
VkAccessFlags srcAccessMask = 0;
|
||||
GetImageTransitionSourceState(srcOriginalLayout, srcStageMask, srcAccessMask);
|
||||
@@ -10588,17 +10643,19 @@ void main() {
|
||||
// [baseSlice, baseSlice + depth) the slice mapping above hands the copy.
|
||||
if (srcOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
|
||||
Bool srcReady = VkTextureManager::TransitionImageLayout(
|
||||
frame.commandBuffer, srcImage.image, *srcImage.trackedLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
frame.commandBuffer, srcImage.image, *srcImage.trackedLayout, copySourceLayout,
|
||||
srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT,
|
||||
srcAccessMask, copySourceAccess,
|
||||
srcImage.aspect, 0, srcImage.mipLevels);
|
||||
MOBILEGL_ASSERT(srcReady, "%s: failed to transition undefined source image", __func__);
|
||||
srcCopyLayout = *srcImage.trackedLayout;
|
||||
} else {
|
||||
Bool srcReady = VkTextureManager::TransitionImageLayout(
|
||||
frame.commandBuffer, srcImage.image, srcCopyLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
frame.commandBuffer, srcImage.image, srcCopyLayout, copySourceLayout,
|
||||
srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, copyAspectMask, srcMipLevel, 1);
|
||||
srcAccessMask, copySourceAccess,
|
||||
inPlace ? srcImage.aspect : copyAspectMask,
|
||||
inPlace ? 0u : srcMipLevel, inPlace ? srcImage.mipLevels : 1u);
|
||||
MOBILEGL_ASSERT(srcReady, "%s: failed to transition source image", __func__);
|
||||
}
|
||||
|
||||
@@ -10606,7 +10663,10 @@ void main() {
|
||||
VkAccessFlags dstAccessMask = 0;
|
||||
GetImageTransitionSourceState(dstOriginalLayout, dstStageMask, dstAccessMask);
|
||||
VkImageLayout dstCopyLayout = dstOriginalLayout;
|
||||
if (dstOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
|
||||
if (inPlace) {
|
||||
// Already moved, by the barrier above: trackedLayout is the same field.
|
||||
dstCopyLayout = *dstImage.trackedLayout;
|
||||
} else if (dstOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
|
||||
Bool dstReady = VkTextureManager::TransitionImageLayout(
|
||||
frame.commandBuffer, dstImage.image, *dstImage.trackedLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
dstStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||
@@ -10648,8 +10708,8 @@ void main() {
|
||||
copyRegion.dstSubresource.baseArrayLayer, copyRegion.dstSubresource.layerCount,
|
||||
copyRegion.dstOffset.z, srcWidth, srcHeight, copyRegion.extent.depth);
|
||||
vkCmdCopyImage(frame.commandBuffer,
|
||||
srcImage.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
dstImage.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
srcImage.image, copySourceLayout,
|
||||
dstImage.image, copyDestinationLayout,
|
||||
1, ©Region);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// The copy coordinates above are already storage-relative. Name that
|
||||
@@ -10665,21 +10725,27 @@ void main() {
|
||||
Bool srcRestored = VkTextureManager::TransitionImageLayout(
|
||||
frame.commandBuffer, srcImage.image, *srcImage.trackedLayout, srcRestoreLayout,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, srcRestoreStageMask,
|
||||
VK_ACCESS_TRANSFER_READ_BIT, srcRestoreAccessMask,
|
||||
copySourceAccess, srcRestoreAccessMask,
|
||||
srcImage.aspect, 0, srcImage.mipLevels);
|
||||
MOBILEGL_ASSERT(srcRestored, "%s: failed to restore undefined source image layout", __func__);
|
||||
} else {
|
||||
Bool srcRestored = VkTextureManager::TransitionImageLayout(
|
||||
frame.commandBuffer, srcImage.image, srcCopyLayout, srcRestoreLayout,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, srcRestoreStageMask,
|
||||
VK_ACCESS_TRANSFER_READ_BIT, srcRestoreAccessMask, copyAspectMask, srcMipLevel, 1);
|
||||
copySourceAccess, srcRestoreAccessMask,
|
||||
inPlace ? srcImage.aspect : copyAspectMask,
|
||||
inPlace ? 0u : srcMipLevel, inPlace ? srcImage.mipLevels : 1u);
|
||||
MOBILEGL_ASSERT(srcRestored, "%s: failed to restore source image layout", __func__);
|
||||
}
|
||||
|
||||
VkPipelineStageFlags dstRestoreStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||
VkAccessFlags dstRestoreAccessMask = 0;
|
||||
GetImageTransitionDestinationState(dstRestoreLayout, dstRestoreStageMask, dstRestoreAccessMask);
|
||||
if (dstOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
|
||||
if (inPlace) {
|
||||
// The restore above already moved the whole image off GENERAL, and the destination
|
||||
// is that same image: a second restore would barrier a layout the tracker has
|
||||
// already left, which is the mirror of the bug the retired Fatal was guarding.
|
||||
} else if (dstOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
|
||||
Bool dstRestored = VkTextureManager::TransitionImageLayout(
|
||||
frame.commandBuffer, dstImage.image, *dstImage.trackedLayout, dstRestoreLayout,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, dstRestoreStageMask,
|
||||
|
||||
@@ -2730,6 +2730,20 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
PROPERTIES LABELS "${mglItestMagmaLabels}" TIMEOUT ${MGL_ITEST_TIMEOUT}
|
||||
${mglItestArmProperties}
|
||||
ENVIRONMENT "${mglItestMagmaWireEnvironment}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p7-dsmipdecline${mglItestArmLogTag}.log")
|
||||
# `copy-image-in-place@P7`'s retirement and its one surviving decline. SPLIT-ONLY in the
|
||||
# strongest sense: the monolith arm declines every in-place copy, so there is no
|
||||
# DirectVulkan. entry to register these beside - the wire arm is the one that performs
|
||||
# them, which is what §3.2 asks for.
|
||||
foreach(inPlaceCase CopyImageInPlaceAcrossLevelsAndLayers
|
||||
CopyImageInPlaceOverlapDeclinesAndLeavesTheLevelAlone)
|
||||
gtest_discover_tests(MobileGLIntegrationTest
|
||||
TEST_PREFIX "DirectVulkan.${mglItestArm}.CopyInPlace."
|
||||
TEST_FILTER "F1WireScenario.${inPlaceCase}"
|
||||
DISCOVERY_TIMEOUT 30
|
||||
PROPERTIES LABELS "${mglItestMagmaLabels}" TIMEOUT ${MGL_ITEST_TIMEOUT}
|
||||
${mglItestArmProperties}
|
||||
ENVIRONMENT "${mglItestMagmaWireEnvironment}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p7-copyinplace${mglItestArmLogTag}-${inPlaceCase}.log")
|
||||
endforeach()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
|
||||
@@ -1554,4 +1554,153 @@ TEST_F(F1WireScenario, ColorBlitToDefaultHonorsScissorAndReversedRect) {
|
||||
Gl().EndFrame();
|
||||
}
|
||||
|
||||
namespace {
|
||||
// One 8x8x2 RGBA8 array texture with two levels, filled so that EVERY subresource this pair
|
||||
// of cases touches is distinguishable from every other one. A flat fill would let a copy that
|
||||
// went to the wrong level, the wrong layer or nowhere at all read as correct.
|
||||
// level 0 layer 0: four quadrants (GL row order: red/green on the bottom, blue/yellow up)
|
||||
// level 0 layer 1: magenta
|
||||
// level 1 layer 0: cyan level 1 layer 1: white
|
||||
constexpr std::array<GLubyte, 4> kInPlaceRed{255, 0, 0, 255};
|
||||
constexpr std::array<GLubyte, 4> kInPlaceGreen{0, 255, 0, 255};
|
||||
constexpr std::array<GLubyte, 4> kInPlaceBlue{0, 0, 255, 255};
|
||||
constexpr std::array<GLubyte, 4> kInPlaceYellow{255, 255, 0, 255};
|
||||
constexpr std::array<GLubyte, 4> kInPlaceMagenta{255, 0, 255, 255};
|
||||
constexpr std::array<GLubyte, 4> kInPlaceCyan{0, 255, 255, 255};
|
||||
constexpr std::array<GLubyte, 4> kInPlaceWhite{255, 255, 255, 255};
|
||||
|
||||
std::array<GLubyte, 4> InPlaceQuadrant(int x, int y) {
|
||||
if (y < 4) return x < 4 ? kInPlaceRed : kInPlaceGreen;
|
||||
return x < 4 ? kInPlaceBlue : kInPlaceYellow;
|
||||
}
|
||||
|
||||
GLuint MakeInPlaceCopyTexture() {
|
||||
GLuint name = 0;
|
||||
glGenTextures(1, &name);
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, name);
|
||||
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 2, GL_RGBA8, 8, 8, 2);
|
||||
std::vector<GLubyte> level0(8 * 8 * 2 * 4);
|
||||
for (int layer = 0; layer < 2; ++layer)
|
||||
for (int y = 0; y < 8; ++y)
|
||||
for (int x = 0; x < 8; ++x) {
|
||||
const auto colour = layer == 0 ? InPlaceQuadrant(x, y) : kInPlaceMagenta;
|
||||
const size_t at = (static_cast<size_t>(layer) * 64 + y * 8 + x) * 4;
|
||||
std::copy(colour.begin(), colour.end(), level0.begin() + at);
|
||||
}
|
||||
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 8, 8, 2, GL_RGBA, GL_UNSIGNED_BYTE, level0.data());
|
||||
std::vector<GLubyte> level1(4 * 4 * 2 * 4);
|
||||
for (int layer = 0; layer < 2; ++layer)
|
||||
for (int at = 0; at < 16; ++at) {
|
||||
const auto colour = layer == 0 ? kInPlaceCyan : kInPlaceWhite;
|
||||
std::copy(colour.begin(), colour.end(),
|
||||
level1.begin() + (static_cast<size_t>(layer) * 16 + at) * 4);
|
||||
}
|
||||
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 1, 0, 0, 0, 4, 4, 2, GL_RGBA, GL_UNSIGNED_BYTE, level1.data());
|
||||
return name;
|
||||
}
|
||||
|
||||
// (level, side) -> the whole level, every layer, as glGetTexImage hands it back.
|
||||
std::vector<GLubyte> ReadInPlaceLevel(GLint level, int side) {
|
||||
std::vector<GLubyte> pixels(static_cast<size_t>(side) * side * 2 * 4, 17);
|
||||
glGetTexImage(GL_TEXTURE_2D_ARRAY, level, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
|
||||
return pixels;
|
||||
}
|
||||
|
||||
::testing::AssertionResult InPlaceTexelIs(const std::vector<GLubyte>& pixels, int side, int layer,
|
||||
int x, int y, const std::array<GLubyte, 4>& expected) {
|
||||
const size_t at = ((static_cast<size_t>(layer) * side * side) + static_cast<size_t>(y) * side + x) * 4;
|
||||
if (at + 4 > pixels.size()) return ::testing::AssertionFailure() << "texel out of range";
|
||||
for (int channel = 0; channel < 4; ++channel)
|
||||
if (pixels[at + channel] != expected[channel])
|
||||
return ::testing::AssertionFailure()
|
||||
<< "layer " << layer << " (" << x << "," << y << ") channel " << channel
|
||||
<< " is " << int(pixels[at + channel]) << ", expected " << int(expected[channel]);
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// P7 wave 2-B, CONTRACT-P7 §3.2: `copy-image-in-place@P7` RETIRES.
|
||||
//
|
||||
// glCopyImageSubData between two subresources of ONE texture is ordinary, defined GL (4.6 core
|
||||
// 18.3.2 even spells out that a texture and a view of it are two objects over one image and may
|
||||
// be copied between). The wire arm took the session down for it, because the TRANSFER_SRC /
|
||||
// TRANSFER_DST pair below it cannot describe one image: both endpoints share one tracked
|
||||
// VkImageLayout and the second transition undoes the first. GENERAL can describe it.
|
||||
//
|
||||
// The monolith arm still DECLINES this shape outright, so this case is split-only in the
|
||||
// strongest sense - there is no monolith reading of it to compare against, and that asymmetry
|
||||
// is deliberate rather than an oversight (§3.2 asks the wire arm to perform the copy).
|
||||
//
|
||||
// Red once (executed, reverted): restore MagmaWireFatal("copy-image-in-place@P7") and this case
|
||||
// dies by that name.
|
||||
TEST_F(F1WireScenario, CopyImageInPlaceAcrossLevelsAndLayers) {
|
||||
if (!Ready()) return;
|
||||
const GLuint array = MakeInPlaceCopyTexture();
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyInPlace.setup";
|
||||
|
||||
// LEVEL 0 -> LEVEL 1, same layer: the destination level is a different subresource of the
|
||||
// same image, so one GENERAL transition has to cover both of them.
|
||||
const auto before = PeekSplitRuntime().emitSeq;
|
||||
glCopyImageSubData(array, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0,
|
||||
array, GL_TEXTURE_2D_ARRAY, 1, 0, 0, 0, 4, 4, 1);
|
||||
ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.CopyInPlace.wire";
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyInPlace.levelCopy.error";
|
||||
{
|
||||
const auto level1 = ReadInPlaceLevel(1, 4);
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyInPlace.levelCopy.readback";
|
||||
for (int y = 0; y < 4; ++y)
|
||||
for (int x = 0; x < 4; ++x) {
|
||||
// The source rectangle is level 0's bottom-left quadrant, which is all red.
|
||||
EXPECT_TRUE(InPlaceTexelIs(level1, 4, 0, x, y, kInPlaceRed))
|
||||
<< "F1.CopyInPlace.level1.layer0";
|
||||
EXPECT_TRUE(InPlaceTexelIs(level1, 4, 1, x, y, kInPlaceWhite))
|
||||
<< "F1.CopyInPlace.level1.layer1 must not be touched";
|
||||
}
|
||||
}
|
||||
|
||||
// LAYER 0 -> LAYER 1 at level 0: the same level, disjoint slices. Defined GL, and the
|
||||
// overlap decline must NOT catch it.
|
||||
glCopyImageSubData(array, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0,
|
||||
array, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 1, 8, 8, 1);
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyInPlace.layerCopy.error";
|
||||
{
|
||||
const auto level0 = ReadInPlaceLevel(0, 8);
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyInPlace.layerCopy.readback";
|
||||
for (int y = 0; y < 8; ++y)
|
||||
for (int x = 0; x < 8; ++x) {
|
||||
EXPECT_TRUE(InPlaceTexelIs(level0, 8, 0, x, y, InPlaceQuadrant(x, y)))
|
||||
<< "F1.CopyInPlace.level0.layer0 is the source and must be unchanged";
|
||||
EXPECT_TRUE(InPlaceTexelIs(level0, 8, 1, x, y, InPlaceQuadrant(x, y)))
|
||||
<< "F1.CopyInPlace.level0.layer1 must carry the source's quadrants";
|
||||
}
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
|
||||
glDeleteTextures(1, &array);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
}
|
||||
|
||||
// The one in-place shape that stays a DECLINE (§3.2): same image, same level, same layer,
|
||||
// overlapping rectangles. GL 4.6 core 18.3.2 leaves that undefined, and GENERAL makes the
|
||||
// command legal to RECORD rather than meaningful to execute - vkCmdCopyImage orders nothing
|
||||
// between its own reads and writes inside one region. The case pins that nothing is recorded:
|
||||
// no GL error, and the level still holds exactly what it held.
|
||||
TEST_F(F1WireScenario, CopyImageInPlaceOverlapDeclinesAndLeavesTheLevelAlone) {
|
||||
if (!Ready()) return;
|
||||
const GLuint array = MakeInPlaceCopyTexture();
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyInPlaceOverlap.setup";
|
||||
// (0,0)-(4,4) onto (2,2): four of the sixteen texels are in both rectangles.
|
||||
glCopyImageSubData(array, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0,
|
||||
array, GL_TEXTURE_2D_ARRAY, 0, 2, 2, 0, 4, 4, 1);
|
||||
EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyInPlaceOverlap.error";
|
||||
const auto level0 = ReadInPlaceLevel(0, 8);
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyInPlaceOverlap.readback";
|
||||
for (int y = 0; y < 8; ++y)
|
||||
for (int x = 0; x < 8; ++x)
|
||||
EXPECT_TRUE(InPlaceTexelIs(level0, 8, 0, x, y, InPlaceQuadrant(x, y)))
|
||||
<< "F1.CopyInPlaceOverlap.level0 must be untouched by a declined copy";
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
|
||||
glDeleteTextures(1, &array);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
}
|
||||
|
||||
} // namespace MGITest
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|---|---|---|---|
|
||||
| 1 | `mipmap-shader-format-or-shape` | 诊断 + 颜色 shader mip 回落(**退役**一半:可渲染格式的不可附着图像) | 见 §1 |
|
||||
| 2 | `depth-stencil-mipmap@P7` + 烘焙 (A)(D) | DEPTH-only **退役**(烘焙深度 mip 程序);D\|S **decline** | 见 §2 |
|
||||
| 3 | `copy-image-in-place@P7` | **退役**(单次 GENERAL 转换);同级同层矩形重叠 **decline** | 见 §3 |
|
||||
|
||||
---
|
||||
|
||||
@@ -197,9 +198,67 @@ glslang **库**、烘焙器 shell 出一个 glslangValidator **二进制**,就
|
||||
`ctest -L unit` **2410**(+8 = 新鲜度门的八行)全绿;信息档 `integration-magma-full-split` **513**,
|
||||
0 红 / 59 skip(基线 510,0 红 / 57 skip——两条新增用例在无旋钮的全量档自 skip)。
|
||||
|
||||
**G1 实测过了,而且这一条事先不是显然的**:`DriverPostIterationRPWitnessSpv.h` 是
|
||||
**G1 实测过了,而且这一条事先不是显然的**(片 2):`DriverPostIterationRPWitnessSpv.h` 是
|
||||
`MG_Util/SelfTest` 的头,pull 构建也编它,重烘改了 2142 → 2143 个字。pull `.text` 仍是 **`0xa52203`**,
|
||||
`nm --defined-only` 的名字表与 `~/w7/p7-before/pull-syms.txt` 逐行相同(30570 行)——字长的变化落在
|
||||
`.rodata`,没有改动任何一条指令的编码宽度。`fatal_census` 绿(79 abort 站点 / 43 家族词 / 0 无标记,
|
||||
本片**退掉一处** `Fatal{}` 用法但不动 abort 站点数);`link_ratchet --assert-monotone` 186 不变,
|
||||
`p7-magma` 桶 101。
|
||||
|
||||
---
|
||||
|
||||
## 3. 片 3:`copy-image-in-place@P7`
|
||||
|
||||
### 3.1 那条 Fatal 说的是真话,但只对它下面的代码成立
|
||||
|
||||
`glCopyImageSubData` 在**同一个纹理的两个子资源**之间拷是普通的、有定义的 GL(4.6 core 18.3.2 还专门
|
||||
写明纹理与它的 view 是同一 image 上的两个对象、允许互拷)。wire 臂为它带走整个会话,理由写在注释里:
|
||||
下面那对 `TRANSFER_SRC` / `TRANSFER_DST` **描述不了一个 image**——两端共用同一个被跟踪的
|
||||
`VkImageLayout`,第二次转换会把第一次撤销。
|
||||
|
||||
`VK_IMAGE_LAYOUT_GENERAL` 能描述它(`vkCmdCopyImage` 两侧都接受 GENERAL)。于是:**一次**把整个 image
|
||||
转成 GENERAL(源级与目标级都在里面)、命令两侧都 GENERAL、**一次**还原;access mask 带上读写两个方向,
|
||||
因为这一道 barrier 是两边共同的边。
|
||||
|
||||
monolith 臂至今仍把所有 in-place 拷贝整体 decline(同函数上方的 `!wire &&` 分支),所以这一片**让 wire
|
||||
臂比 monolith 臂多做一件事**——这正是 §3.2 要求的形状,而不是对齐。
|
||||
|
||||
`inPlace` 在 pull 构建里是 `constexpr false`,它守的每个分支都会折掉:这个函数也编进 monolith 镜像,
|
||||
而 G1 钉着那个镜像的 `.text`。
|
||||
|
||||
### 3.2 留下来的那一种 decline
|
||||
|
||||
同 image + 同 level + 同 layer + **矩形重叠**:GL 4.6 core 18.3.2 说重叠处结果未定义。GENERAL 让这条
|
||||
命令**可记录**,不让它**有意义**——`vkCmdCopyImage` 对自己在同一 region 内的读写不排序,tiler 产出的是
|
||||
每个 texel 先到的那一半。所以 `MGLOG_E_ONCE` + return,把这句话说一次,而不是把它发出去。
|
||||
|
||||
判据是**三个维度同时成立**:同 level、slice 区间相交、矩形相交。level 0→1、层与层不相交、同一级上两个
|
||||
不相交的矩形,都是有定义的,都照做。
|
||||
|
||||
### 3.3 场景
|
||||
|
||||
`F1WireScenario` 里两条(split-only,因为 monolith 没有可对照的读数),一张 8×8×2、两级的
|
||||
`GL_TEXTURE_2D_ARRAY`,**每一个被碰到的子资源都填成互不相同的颜色**(level 0 layer 0 四象限、
|
||||
level 0 layer 1 洋红、level 1 layer 0 青、level 1 layer 1 白)——铺平色会让「拷到了错的 level / 错的
|
||||
layer / 根本没拷」都读成对的。
|
||||
|
||||
- `CopyImageInPlaceAcrossLevelsAndLayers`:level 0 → level 1(同层)后 level 1 layer 0 变成源矩形那个
|
||||
全红象限、layer 1 仍是白;再 level 0 layer 0 → layer 1(同级、层不相交)后两层都是四象限,源不变。
|
||||
- `CopyImageInPlaceOverlapDeclinesAndLeavesTheLevelAlone`:同级同层 (0,0)-(4,4) → (2,2),无 GL 错,
|
||||
level 0 layer 0 逐 texel 不变(若真执行,(4,4) 会从黄变红,断言分得出来)。
|
||||
|
||||
三臂各注册两条。
|
||||
|
||||
### 3.4 red-once(R-16,已执行并还原)
|
||||
|
||||
把 `MagmaWireFatal("copy-image-in-place@P7")` 放回去 → 两条都死于
|
||||
`Fatal{UnmigratedVerb, "Magma:copy-image-in-place@P7"}`;还原后 2/2 绿。
|
||||
|
||||
### 3.5 门
|
||||
|
||||
`integration-magma-split` **79**(+2)、`-spawn` **58**(+2)、`-tcp` **60**(+2),三臂 0 红。
|
||||
|
||||
> **本树的 tcp 端口改成了 `40913`。** 跑本片的门时 `TcpServer.Start` 整条 tcp 车道红了 59/60,
|
||||
> 原因是 `40613`(`~/w7/pipe/build-split` 的默认值)上已经有一个兄弟树的 `libMobileGLServer` 在监听。
|
||||
> 这是本地 configure 选项,不影响车道语义;`magma-two-process-first-run.md` 里 wave 0 也为同一原因
|
||||
> 用了专用端口。
|
||||
|
||||
Reference in New Issue
Block a user