mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Refactor] (P8-F): reclassify what our client cannot send - the multi-draw client-indices/instanced latches and Magma's client-vertex-array become ProtocolCorruption, Magma's mipmap-shader-format-or-shape becomes a named decline decided before the chain grows (with a knob-forced 3D case on split and spawn), and the three unpublished cap bits and the index-mirror sentinel are marked reserved
This commit is contained in:
@@ -104,3 +104,9 @@ MGL_WIRE_DECLINE(ShapeCreateImageFailed)
|
||||
// Appended by the B3 review round: the preserve-copy arm of a reallocation was the one exit in
|
||||
// SyncWireTextureShape still leaving no trace at all.
|
||||
MGL_WIRE_DECLINE(ShapePreserveCopyFailed)
|
||||
|
||||
// ---- VulkanRenderer::GenerateWireMipmap (P8-F) -----------------------------------------
|
||||
// `mipmap-shader-format-or-shape` was a Fatal on this arm and a decline on the monolith arm; it
|
||||
// is the monolith's decline now. Counted once per refused glGenerateMipmap, whose chain is left
|
||||
// as it was.
|
||||
MGL_WIRE_DECLINE(MipmapShaderFormatOrShape)
|
||||
|
||||
@@ -294,7 +294,14 @@
|
||||
const Uint32 location = input.bindingAttributeLocations[binding];
|
||||
const auto& attr = vao.Attributes[location];
|
||||
const auto& resource = state.VertexBuffers[location];
|
||||
if (MG_Pipe::MGPipeHandleIsNull(resource.Res)) MagmaWireFatal("client-vertex-array@P8");
|
||||
// P8-F: A RECORD INVARIANT, NOT AN UNMIGRATED VERB (it carried a P8 phase tag).
|
||||
// The client turns every enabled client-memory array into an owned buffer before the
|
||||
// draw record is built, or refuses the draw itself (MG_Impl/Pipe/OwnedDrawInputs.h),
|
||||
// so an attribute the program reads with no buffer handle is a foreign peer's record.
|
||||
if (MG_Pipe::MGPipeHandleIsNull(resource.Res))
|
||||
MG_Pipe::MGPipeSessionFail(MG_Pipe::MGPipeFatalFamily::ProtocolCorruption,
|
||||
"MGPipe: Fatal{ProtocolCorruption, \"Magma:client-vertex-array\"} location=%u: an "
|
||||
"attribute the program reads carries no buffer handle", location);
|
||||
BufferSlice slice{};
|
||||
if (!m_bufferManager.AcquireWireSlice(BufferKind::Vertex, resource.Res, slice))
|
||||
MagmaWireFatal("vertex-buffer-resource");
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
// missing WHICH feature bit - was the one thing it did not. The census, the retrace
|
||||
// refusal tally and every log grep key on the leading word, so it is unchanged and
|
||||
// unprefixed; everything after the first space is for the reader of the next device run.
|
||||
// P8-F: the refusal is a named decline now (WireDeclines.def MipmapShaderFormatOrShape);
|
||||
// the line it logs keeps this word and this detail.
|
||||
static String MagmaWireShapeDetail(const char* name, VkFormat format, VkFormat storageFormat,
|
||||
const VkFormatProperties& props, VkImageAspectFlags aspect,
|
||||
VkImageViewType viewType, VkImageUsageFlags usage,
|
||||
@@ -1400,8 +1402,10 @@
|
||||
base + 1, base + count - 1);
|
||||
return;
|
||||
}
|
||||
if (!m_textureManager->GrowWireTextureMipChain(handle,base+count,frame.commandBuffer))
|
||||
MagmaWireFatal("mipmap-growth");
|
||||
// P8-F: WHICH ARM TAKES THIS CHAIN IS DECIDED BEFORE THE CHAIN IS GROWN, for the
|
||||
// depth-stencil decline's reason above. Nothing here depends on the growth: the format,
|
||||
// its features, the knob tier, the aspect, view type, usage and layer window are the
|
||||
// same before and after it (GrowWireTextureMipChain carries every one of them over).
|
||||
VkFormatProperties props{};
|
||||
const VkFormat format = viewFormat == VK_FORMAT_UNDEFINED ? resource->format : viewFormat;
|
||||
vkGetPhysicalDeviceFormatProperties(m_physicalDevice.handle,format,&props);
|
||||
@@ -1411,6 +1415,50 @@
|
||||
(props.optimalTilingFeatures & required) == required;
|
||||
const VkFilter filter = props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT ?
|
||||
VK_FILTER_LINEAR : VK_FILTER_NEAREST;
|
||||
const Bool layersInRange = baseLayer < resource->arrayLayers &&
|
||||
layerCount <= resource->arrayLayers - baseLayer;
|
||||
// The depth arm's shape: the layer window, and what WireDepthMipmap.inc's pass refuses
|
||||
// for an image this function hands it - a format that does not both sample and attach
|
||||
// as depth (its other refusals, 3D / multisample / a default image, are never built here).
|
||||
const auto depthPassFeatures =
|
||||
VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||
const Bool depthArmTakesShape =
|
||||
layersInRange && (props.optimalTilingFeatures & depthPassFeatures) == depthPassFeatures;
|
||||
// The colour arm's shape (the two shader shapes are explained at the colour arm below).
|
||||
const Bool colour = resource->aspect == VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
const Bool planar = resource->viewType != VK_IMAGE_VIEW_TYPE_1D &&
|
||||
resource->viewType != VK_IMAGE_VIEW_TYPE_1D_ARRAY &&
|
||||
resource->viewType != VK_IMAGE_VIEW_TYPE_3D;
|
||||
// The shader arm samples through a sampler2D and writes a vec4, which is what
|
||||
// BlitWireColorImage's own float check enforces; naming it here rather than letting the
|
||||
// blit return false is what puts the reason in the decline line.
|
||||
const Bool sampledFloat = vkuFormatIsSampledFloat(format);
|
||||
const Bool renderableFormat =
|
||||
(props.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) != 0;
|
||||
const Bool attachable = forcedShaderTier < 2 &&
|
||||
(resource->usageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) != 0;
|
||||
const Bool viaScratch = !attachable && renderableFormat &&
|
||||
(resource->usageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) != 0;
|
||||
const Bool colourArmTakesShape =
|
||||
colour && planar && layersInRange && sampledFloat && (attachable || viaScratch);
|
||||
// P8-F: `mipmap-shader-format-or-shape` IS A NAMED DECLINE, NOT A FATAL (rule I (a)).
|
||||
// The monolith arm has always answered this chain with a decline (VulkanRenderer.cpp's
|
||||
// GenerateMipmap: a format without blit support is skipped and the chain left unwritten),
|
||||
// and a shape no shader arm takes is a coverage gap in this backend, not a broken record.
|
||||
// The leading word stays, so every device-log grep keyed on it still finds the line.
|
||||
const Bool shaderArmTakesShape =
|
||||
resource->aspect == VK_IMAGE_ASPECT_DEPTH_BIT ? depthArmTakesShape : colourArmTakesShape;
|
||||
if (!nativeBlit && !shaderArmTakesShape) {
|
||||
const String detail = MagmaWireShapeDetail("mipmap-shader-format-or-shape", format,
|
||||
resource->format, props, resource->aspect, resource->viewType, resource->usageFlags,
|
||||
resource->extent, resource->depth, resource->mipLevels, baseLayer, layerCount,
|
||||
resource->arrayLayers);
|
||||
MGL_WIRE_DECLINE_AT(MipmapShaderFormatOrShape, "%s; no mip arm takes this shape, levels %u..%u left as they are",
|
||||
detail.c_str(), base + 1, base + count - 1);
|
||||
return;
|
||||
}
|
||||
if (!m_textureManager->GrowWireTextureMipChain(handle,base+count,frame.commandBuffer))
|
||||
MagmaWireFatal("mipmap-growth");
|
||||
if (!nativeBlit && resource->aspect == VK_IMAGE_ASPECT_DEPTH_BIT) {
|
||||
// P7 wave 2-B, CONTRACT-P7 §5.1 (A): THE DEPTH ARM, and it is first because a
|
||||
// depth format without BLIT_DST is the ordinary case, not the exotic one -
|
||||
@@ -1422,12 +1470,7 @@
|
||||
// The per-level pass is WireDepthMipmap.inc's, which is the monolith's fragment
|
||||
// shader with the monolith's arithmetic on a baked pipeline that owns no frontend
|
||||
// object - §3.2's "observably the monolith's" without the GL program the monolith
|
||||
// needs to get there.
|
||||
if (baseLayer >= resource->arrayLayers || layerCount > resource->arrayLayers - baseLayer)
|
||||
MagmaWireFatal(MagmaWireShapeDetail("mipmap-shader-format-or-shape", format,
|
||||
resource->format, props, resource->aspect, resource->viewType,
|
||||
resource->usageFlags, resource->extent, resource->depth, resource->mipLevels,
|
||||
baseLayer, layerCount, resource->arrayLayers).c_str());
|
||||
// needs to get there. Its layer window and format were checked before the growth.
|
||||
for (Uint32 level = base + 1; level < base + count; ++level) {
|
||||
for (Uint32 layer = baseLayer; layer < baseLayer + layerCount; ++layer) {
|
||||
WireImage source{};
|
||||
@@ -1445,15 +1488,19 @@
|
||||
destination.level = level;
|
||||
destination.extent = {std::max(resource->extent.width >> level, 1u),
|
||||
std::max(resource->extent.height >> level, 1u)};
|
||||
// The pass reports the SHAPE it cannot take (1D, 3D, multisample, a
|
||||
// format without sampled or depth-attachment support), so the refusal
|
||||
// keeps its name and gains the reason.
|
||||
if (!GenerateWireDepthMipLevel(source, destination))
|
||||
MagmaWireFatal(MagmaWireShapeDetail("mipmap-shader-format-or-shape", format,
|
||||
// The pass reports the SHAPE it cannot take. depthArmTakesShape above asks
|
||||
// its questions first, so this is reached only if the two lists drift apart
|
||||
// - and then it is the same named decline (P8-F), not a session's end. It
|
||||
// can only refuse the first level: the shape does not change down the chain.
|
||||
if (!GenerateWireDepthMipLevel(source, destination)) {
|
||||
const String detail = MagmaWireShapeDetail("mipmap-shader-format-or-shape", format,
|
||||
resource->format, props, resource->aspect, resource->viewType,
|
||||
resource->usageFlags, resource->extent, resource->depth,
|
||||
resource->mipLevels, baseLayer, layerCount,
|
||||
resource->arrayLayers).c_str());
|
||||
resource->mipLevels, baseLayer, layerCount, resource->arrayLayers);
|
||||
MGL_WIRE_DECLINE_AT(MipmapShaderFormatOrShape, "%s; the depth pass refused level %u",
|
||||
detail.c_str(), level);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!nativeBlit) {
|
||||
@@ -1470,27 +1517,9 @@
|
||||
// cannot, an owned scratch attachment of the destination level's size renders
|
||||
// the mip and vkCmdCopyImage moves it in: a raw texel move, no conversion, and
|
||||
// the texels still come from GPU contents rather than from a staged shadow.
|
||||
const Bool colour = resource->aspect == VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
const Bool planar = resource->viewType != VK_IMAGE_VIEW_TYPE_1D &&
|
||||
resource->viewType != VK_IMAGE_VIEW_TYPE_1D_ARRAY &&
|
||||
resource->viewType != VK_IMAGE_VIEW_TYPE_3D;
|
||||
const Bool layersInRange = baseLayer < resource->arrayLayers &&
|
||||
layerCount <= resource->arrayLayers - baseLayer;
|
||||
// The shader arm samples through a sampler2D and writes a vec4, which is what
|
||||
// BlitWireColorImage's own float check enforces; naming it here rather than
|
||||
// letting the blit return false is what puts the reason in the fatal line.
|
||||
const Bool sampledFloat = vkuFormatIsSampledFloat(format);
|
||||
const Bool renderableFormat =
|
||||
(props.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) != 0;
|
||||
const Bool attachable = forcedShaderTier < 2 &&
|
||||
(resource->usageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) != 0;
|
||||
const Bool viaScratch = !attachable && renderableFormat &&
|
||||
(resource->usageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) != 0;
|
||||
if (!colour || !planar || !layersInRange || !sampledFloat || (!attachable && !viaScratch))
|
||||
MagmaWireFatal(MagmaWireShapeDetail("mipmap-shader-format-or-shape", format,
|
||||
resource->format, props, resource->aspect, resource->viewType,
|
||||
resource->usageFlags, resource->extent, resource->depth, resource->mipLevels,
|
||||
baseLayer, layerCount, resource->arrayLayers).c_str());
|
||||
//
|
||||
// Which of the two (`attachable` or `viaScratch`), and whether either takes the
|
||||
// shape at all, was decided before the growth (colourArmTakesShape, P8-F).
|
||||
for (Uint32 level = base + 1; level < base + count; ++level) {
|
||||
for (Uint32 layer = baseLayer; layer < baseLayer + layerCount; ++layer) {
|
||||
WireImage source{};
|
||||
|
||||
@@ -186,9 +186,9 @@ namespace MobileGL::MG_Backend {
|
||||
// ZERO IS THE EXPLICIT ANSWER FOR P5, not an omission (ServerSession.h's block):
|
||||
// every optional capability bit belongs to the package that owns its question, and
|
||||
// withholding one leaves the legacy path running, which is the safe direction.
|
||||
// kCapNeedsHostIndexBytes and kCapNeedsHostUboBytes must be 0 for the whole of P5
|
||||
// by ruling - they are the only two things that ask for an MGHostSpan, and 0 is
|
||||
// what keeps every one of them out of the first IPC frame (contract table 0).
|
||||
// kCapNeedsHostIndexBytes, kCapNeedsHostUboBytes and kCapViewportArray are RESERVED
|
||||
// (P8-F) and never set here: the first two are the only things that ask for an
|
||||
// MGHostSpan, and 0 keeps every one of them off the wire (contract table 0).
|
||||
//
|
||||
// P5b t2 (CONTRACT-P5B.md §6.5) PUBLISHES THE ONE BIT P5b ADDS, and this is the
|
||||
// only place that can: the question kCapBackendOwnsXfbCapture answers is "does the
|
||||
|
||||
@@ -247,10 +247,10 @@ namespace MobileGL::MG_Pipe {
|
||||
for (Uint32 w = 0; w < kMGPipeShaderBufferWritableMaskWords; ++w) {
|
||||
header.WritableMask[w] = writableMask[w];
|
||||
}
|
||||
// HostSpanCount IS 0 ALWAYS on Espryt, and that is a ruling rather than an omission:
|
||||
// the second var-tail exists for kCapNeedsHostUboBytes (Magma's named-UBO ring) and
|
||||
// that bit is 0 for the whole of P5 (CONTRACT-P5.md table 0). The codec's host-span
|
||||
// honesty pass is the guard that says so out loud if a backend ever publishes it.
|
||||
// HostSpanCount IS 0 ALWAYS, and that is a ruling rather than an omission: the
|
||||
// second var-tail exists for kCapNeedsHostUboBytes (a Magma named-UBO ring that was
|
||||
// never built) and that bit is RESERVED (P8-F): no server publishes it. The codec's
|
||||
// host-span honesty pass is the guard that says so out loud if a peer sends one.
|
||||
header.HostSpanCount = 0;
|
||||
header.ContentHash = hash;
|
||||
MGPipeRouteSetShaderBuffers(header, m_entries.data());
|
||||
|
||||
@@ -263,8 +263,8 @@ namespace MobileGL::MG_Pipe {
|
||||
if (vao) {
|
||||
if (const auto& bound = vao->GetIndexBufferBindingSlot().GetBoundObject()) {
|
||||
m_lastIndex.Res = MGPipeSlots().Acquire(MGPipeKind::Buffer, bound->GetLifetimeId());
|
||||
// The ELEMENT_ARRAY bit, and it is the one the split path keys on
|
||||
// (kCapNeedsHostIndexBytes -> restart rewriting, multi-draw flattening).
|
||||
// The INDEX|ELEMENT_ARRAY bind bits. ELEMENT_ARRAY's D-B7 reader
|
||||
// (kCapNeedsHostIndexBytes) is RESERVED (P8-F) and was never built.
|
||||
// Noted at every draw for RefreshBindMask's reason: an EBO defined through
|
||||
// DSA and unbound before its last respecify would otherwise never publish
|
||||
// it, and getting that bit wrong is invisible in monolith.
|
||||
|
||||
@@ -3354,6 +3354,26 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Harness/split_log_paths.py"
|
||||
check "${CMAKE_CTEST_COMMAND}" "${CMAKE_BINARY_DIR}")
|
||||
set_tests_properties(SplitLogPaths.PrivateAndDistinct PROPERTIES LABELS "integration-split")
|
||||
|
||||
# P8-F: `Magma:mipmap-shader-format-or-shape` is a named DECLINE on the wire arm now
|
||||
# (WireDeclines.def MipmapShaderFormatOrShape), as the monolith arm's GenerateMipmap already
|
||||
# declines. The shape is a 3D chain with the native blit forced off - neither shader mip arm
|
||||
# takes it - and the knob is read BY THE SERVER, so split + spawn only (the ShaderMip entries'
|
||||
# tcp note below). The `.ShaderMip1.` tail is already MAGMA_SERVER_ENV_KNOB_NO_TCP in
|
||||
# scripts/ci/spawn_lane_parity.py. Environment: the Fm lane's flavour plus the arm.
|
||||
foreach(mglItestArm IN ITEMS Split Spawn)
|
||||
mgl_itest_magma_arm_shape(${mglItestArm})
|
||||
mgl_itest_join_environment(mglItestP8fMipEnvironment
|
||||
"MOBILEGL_BACKEND_TYPE=DirectVulkan" "MOBILEGL_ITEST_REQUIRE_GPU=1" "MGITEST_SPLIT_LANE=1"
|
||||
${MGL_ITEST_CAPABILITY_ENV} ${MGL_ITEST_VULKAN_ENV} ${mglItestArmTransportEnv})
|
||||
gtest_discover_tests(MobileGLIntegrationTest
|
||||
TEST_PREFIX "DirectVulkan.${mglItestArm}.ShaderMip1."
|
||||
TEST_FILTER "F1WireScenario.GenerateMipmapWithoutAShaderArmShapeDeclinesAndKeepsTheSession"
|
||||
DISCOVERY_TIMEOUT 30
|
||||
PROPERTIES LABELS "${mglItestMagmaLabels}" TIMEOUT ${MGL_ITEST_TIMEOUT}
|
||||
${mglItestArmProperties}
|
||||
ENVIRONMENT "${mglItestP8fMipEnvironment}\;MGITEST_MAGMA_FORCE_SHADER_MIPMAP=1\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p8f-shadermip-decline${mglItestArmLogTag}.log")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# f1: split-only source keeps the monolith registry unchanged.
|
||||
|
||||
@@ -675,6 +675,65 @@ TEST_F(F1WireScenario, GenerateMipmapDepthStencilDeclinesAndKeepsTheSession) {
|
||||
glClearDepth(1.0);
|
||||
}
|
||||
|
||||
// P8-F: `Magma:mipmap-shader-format-or-shape` IS A NAMED DECLINE ON THE WIRE ARM NOW, which is
|
||||
// the monolith arm's answer for a chain it cannot build (VulkanRenderer.cpp's GenerateMipmap
|
||||
// declines a format without blit support and leaves the chain unwritten). It used to end the
|
||||
// session. A 3D texture is a shape neither wire shader arm takes (the colour pass samples a
|
||||
// sampler2D), so with the native blit forced off (MGITEST_MAGMA_FORCE_SHADER_MIPMAP) it is what
|
||||
// reaches the refusal on this host - lavapipe blits a 3D RGBA8 chain natively otherwise.
|
||||
//
|
||||
// What this case pins: no GL error, the session still answering (two readback round trips), level
|
||||
// 0 untouched, and level 1 still holding the application's own texels - the decline wrote nothing.
|
||||
// Magma only, and knob-gated, like GenerateMipmapWithoutNativeBlitPixels above.
|
||||
// Red once (executed, reverted): restore the MagmaWireFatal and this case dies with
|
||||
// Fatal{UnmigratedVerb, "Magma:mipmap-shader-format-or-shape ..."}.
|
||||
TEST_F(F1WireScenario, GenerateMipmapWithoutAShaderArmShapeDeclinesAndKeepsTheSession) {
|
||||
if (!Ready()) return;
|
||||
if (Gl().BackendName() != "DirectVulkan")
|
||||
GTEST_SKIP() << "Magma wire-arm decline (P8-F, mipmap-shader-format-or-shape): the knob and "
|
||||
"the shader mip arms it forces are Magma's; Espryt ("
|
||||
<< Gl().BackendName() << ") generates this chain itself";
|
||||
const std::string tier = SplitLane::MarkerValue("MGITEST_MAGMA_FORCE_SHADER_MIPMAP");
|
||||
if (tier.empty())
|
||||
GTEST_SKIP() << "MGITEST_MAGMA_FORCE_SHADER_MIPMAP is unset: this driver blits a 3D RGBA8 "
|
||||
"chain natively, so the decline under test is not entered";
|
||||
GLuint volume = 0;
|
||||
glGenTextures(1, &volume);
|
||||
glBindTexture(GL_TEXTURE_3D, volume);
|
||||
glTexStorage3D(GL_TEXTURE_3D, 3, GL_RGBA8, 4, 4, 4);
|
||||
const std::array<GLubyte, 4> base{200, 40, 80, 255};
|
||||
const std::array<GLubyte, 4> sentinel{10, 220, 30, 255};
|
||||
const auto fill = [](int side, const std::array<GLubyte, 4>& texel) {
|
||||
std::vector<GLubyte> pixels(static_cast<size_t>(side * side * side * 4));
|
||||
for (size_t at = 0; at < pixels.size(); at += 4) std::copy(texel.begin(), texel.end(), pixels.begin() + at);
|
||||
return pixels;
|
||||
};
|
||||
const auto level0 = fill(4, base);
|
||||
const auto level1 = fill(2, sentinel);
|
||||
glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 4, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, level0.data());
|
||||
glTexSubImage3D(GL_TEXTURE_3D, 1, 0, 0, 0, 2, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, level1.data());
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ShaderMipDecline.setup";
|
||||
const auto before = PeekSplitRuntime().emitSeq;
|
||||
glGenerateMipmap(GL_TEXTURE_3D);
|
||||
ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ShaderMipDecline.wire";
|
||||
EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ShaderMipDecline.error";
|
||||
for (const int level : {0, 1}) {
|
||||
const int side = 4 >> level;
|
||||
std::vector<GLubyte> pixels(static_cast<size_t>(side * side * side * 4), 0x5a);
|
||||
glGetTexImage(GL_TEXTURE_3D, level, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ShaderMipDecline.readback level " << level;
|
||||
const std::array<GLubyte, 4>& expected = level == 0 ? base : sentinel;
|
||||
size_t wrong = 0;
|
||||
for (size_t at = 0; at < pixels.size(); at += 4)
|
||||
if (!std::equal(expected.begin(), expected.end(), pixels.begin() + at)) ++wrong;
|
||||
EXPECT_EQ(wrong, 0u) << "F1.ShaderMipDecline.level" << level << " tier=" << tier
|
||||
<< ": a declined mip must leave every level as the application wrote it";
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_3D, 0);
|
||||
glDeleteTextures(1, &volume);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
}
|
||||
|
||||
TEST_F(F1WireScenario, GenerateMipmapHonorsMutableBaseAndMaxWithoutChangingOtherLevels) {
|
||||
if (!Ready()) return;
|
||||
const std::array<GLubyte, 4> colors[5] = {
|
||||
|
||||
@@ -13,16 +13,16 @@
|
||||
//
|
||||
// Monolith: Ptr addresses the frontend shadow or the application's own memory and the
|
||||
// accessor is one predictable branch. Split: Ptr is null and the bytes live in a staging
|
||||
// segment named by Seg/Offset, or - for the index bytes a server-side primitive-restart
|
||||
// rewrite or multi-draw flattening consumes - in the server's own index host mirror, which
|
||||
// costs no wire traffic at all (D-B7).
|
||||
// segment named by Seg/Offset. D-B7's third shape - index bytes a server-side restart
|
||||
// rewrite reads out of its own index host mirror, at no wire cost - was never built; its
|
||||
// sentinel below is RESERVED (P8-F).
|
||||
namespace MobileGL::MG_Pipe {
|
||||
// Seg sentinels. Anything else is a real SEG_STAGE id assigned by the transport.
|
||||
inline constexpr Uint32 kMGHostSpanSegNone = 0;
|
||||
// "The bytes are already on your side": the server reads them out of the index host
|
||||
// mirror it maintains for every resource created with the ELEMENT_ARRAY bind bit while
|
||||
// kCapNeedsHostIndexBytes is set. When the mirror is over budget the tracker degrades
|
||||
// to per-draw staging and counts the bytes in index-bytes-shipped.
|
||||
// RESERVED (P8-F). It was to mean "the bytes are already on your side", read out of an
|
||||
// index host mirror armed by kCapNeedsHostIndexBytes. Neither was built (client indices
|
||||
// travel as owned element buffers; the cap is reserved), so no encoder writes this value
|
||||
// and SegmentTable::Resolve answers it "unknown". Kept so the value is never reused.
|
||||
inline constexpr Uint32 kMGHostSpanSegFromServerIndexMirror = 0xFFFFFFFFu;
|
||||
|
||||
struct MGHostSpan {
|
||||
|
||||
@@ -107,19 +107,19 @@ namespace MobileGL::MG_Pipe {
|
||||
// flattening - is not expressible as a capability (D-B7).
|
||||
enum MGPCapBit : Uint64 {
|
||||
kCapNone = 0,
|
||||
kCapViewportArray = 1ull << 0,
|
||||
kCapViewportArray = 1ull << 0, // RESERVED (P8-F): no producer, no consumer; never renumber
|
||||
kCapFloat64VertexAttrib = 1ull << 1,
|
||||
kCapResidentSubData = 1ull << 2,
|
||||
kCapCpuXfbPrimitiveAccounting = 1ull << 3,
|
||||
kCapTimerQuery = 1ull << 4,
|
||||
kCapOcclusionQuery = 1ull << 5,
|
||||
kCapXfbPrimitivesQuery = 1ull << 6,
|
||||
// The server rewrites restart indices / flattens multi-draws itself and therefore
|
||||
// needs the index bytes on its side: under split this arms the index host mirror
|
||||
// (D-B7).
|
||||
// RESERVED (P8-F): never published, never read. The index host mirror it would arm
|
||||
// (D-B7) was not built: client indices travel as owned element buffers and the server
|
||||
// reads its own staged bytes. The bit position is wire format: never reuse/renumber it.
|
||||
kCapNeedsHostIndexBytes = 1ull << 7,
|
||||
// The server packs named uniform blocks into its own ring and therefore needs the
|
||||
// host bytes of a set_shader_buffers(Uniform) range (D-B8).
|
||||
// RESERVED (P8-F): never published, never read - the named-UBO host span (D-B8) has
|
||||
// no producer. The bit position is wire format: never reuse or renumber it.
|
||||
kCapNeedsHostUboBytes = 1ull << 8,
|
||||
// P5b t2 (CONTRACT-P5B.md §6.5), the one cap bit P5b adds. The SERVER's backend owns
|
||||
// the transform-feedback capture, i.e. its own table registers EndTransformFeedback.
|
||||
@@ -312,11 +312,11 @@ namespace MobileGL::MG_Pipe {
|
||||
kMGPipeBindDepthStencil = 1u << 8,
|
||||
kMGPipeBindStreamOutput = 1u << 9,
|
||||
kMGPipeBindAtomic = 1u << 10,
|
||||
// THE D-B7 SWITCH. With kCapNeedsHostIndexBytes set the server mirrors this
|
||||
// resource's bytes so it can rewrite restart indices and flatten multi-draws
|
||||
// (ARCHITECTURE.md 10.3). Getting it wrong is invisible in monolith and silently
|
||||
// disables both under split, which is why it is set from a table rather than from a
|
||||
// special case at the emission site.
|
||||
// Set beside INDEX on every element-array buffer, from a table (ResourceTracker.h).
|
||||
// It was THE D-B7 SWITCH: with kCapNeedsHostIndexBytes the server would mirror this
|
||||
// resource's bytes for restart rewriting and multi-draw flattening (ARCHITECTURE.md
|
||||
// 10.3). That cap is RESERVED (P8-F) and the mirror was never built, so no reader
|
||||
// consumes this bit today; it stays on the wire, at this position.
|
||||
kMGPipeBindElementArray = 1u << 11,
|
||||
};
|
||||
|
||||
@@ -331,8 +331,8 @@ namespace MobileGL::MG_Pipe {
|
||||
Uint8 StorageKind; // == TextureStorageType (Mipmap | Buffer)
|
||||
// MGPipeBindBit, above: VERTEX|INDEX|CONSTANT|SHADER_BUFFER|INDIRECT|SAMPLER|
|
||||
// SHADER_IMAGE|RENDER_TARGET|DEPTH_STENCIL|STREAM_OUTPUT|ATOMIC|ELEMENT_ARRAY. The
|
||||
// ELEMENT_ARRAY bit is the D-B7 switch: with kCapNeedsHostIndexBytes set the server
|
||||
// mirrors this resource.
|
||||
// ELEMENT_ARRAY bit was the D-B7 switch; kCapNeedsHostIndexBytes is RESERVED (P8-F)
|
||||
// and no server mirrors this resource.
|
||||
Uint16 BindMask;
|
||||
Uint32 InternalFormat; // already resolved to an uncompressed fallback by the client
|
||||
Uint32 Width, Height, Depth;
|
||||
@@ -992,11 +992,11 @@ namespace MobileGL::MG_Pipe {
|
||||
MGP_ASSERT_POD(MGPShaderImages, 16);
|
||||
|
||||
// One bound buffer range: 24 bytes, no inline host span. The named-UBO host bytes a
|
||||
// backend needs under kCapNeedsHostUboBytes (D-B8) travel as an OPTIONAL second var-tail,
|
||||
// MGHostSpan[HostSpanCount] behind the ranges, announced by MGPShaderBuffers below. An
|
||||
// inline span would have cost every SSBO, atomic-counter and XFB range 32 dead bytes, and
|
||||
// D-B8 says not to freeze that payload's shape before the stage-ubo-named counter has
|
||||
// produced numbers.
|
||||
// backend would need under kCapNeedsHostUboBytes (D-B8; that cap is RESERVED since P8-F
|
||||
// and nothing sends the tail) travel as an OPTIONAL second var-tail, MGHostSpan[HostSpanCount]
|
||||
// behind the ranges, announced by MGPShaderBuffers below. An inline span would have cost
|
||||
// every SSBO, atomic-counter and XFB range 32 dead bytes, and D-B8 says not to freeze that
|
||||
// payload's shape before the stage-ubo-named counter has produced numbers.
|
||||
struct MGPBufferRange {
|
||||
MGPipeHandle Res;
|
||||
Uint64 Offset;
|
||||
@@ -1005,8 +1005,8 @@ namespace MobileGL::MG_Pipe {
|
||||
MGP_ASSERT_POD(MGPBufferRange, 24);
|
||||
|
||||
// Var-tail header: MGPBufferRange[Count], then MGHostSpan[HostSpanCount]. HostSpanCount is
|
||||
// 0, or Count for the Uniform class under kCapNeedsHostUboBytes (a range with nothing to
|
||||
// ship carries an empty span, so the two arrays stay index-aligned).
|
||||
// 0, or Count for the Uniform class under kCapNeedsHostUboBytes (RESERVED, P8-F: 0 today;
|
||||
// a range with nothing to ship carries an empty span, so the two arrays stay aligned).
|
||||
// MGPShaderBuffers::Class's three values, and the ONLY spelling of them (P5e,
|
||||
// MG_Remote/CONTRACT-P5E.md §1). The payload's comment has named them since P4a and
|
||||
// nothing numbered them, so the emitter and the applier were one literal each away from
|
||||
|
||||
@@ -44,9 +44,9 @@ namespace MobileGL::MG_Remote {
|
||||
// (MGPipeTypes.h:108-124) and has no per-family bit at all, so "read the CallMask" was
|
||||
// not yet an implementable instruction. It is now:
|
||||
//
|
||||
// bits 0..8 MGPCapBit, unchanged. kCapNeedsHostIndexBytes and kCapNeedsHostUboBytes
|
||||
// are BOTH ZERO in P5 by ruling (table 0), which is what keeps every
|
||||
// MGHostSpan out of the first IPC frame.
|
||||
// bits 0..8 MGPCapBit, unchanged. kCapViewportArray (0), kCapNeedsHostIndexBytes (7)
|
||||
// and kCapNeedsHostUboBytes (8) are RESERVED (P8-F): never published, and
|
||||
// their positions are never reused, which keeps every MGHostSpan off the wire.
|
||||
// bits 9..31 reserved for further MGPCapBits.
|
||||
// bits 32..47 THE CONSUMER MASK: bit (32 + n) means "the server has a consumer for
|
||||
// MGPipe subsystem bit n" - i.e. the server's own subsystem mask, shifted.
|
||||
|
||||
@@ -251,7 +251,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// P5e (sb, CONTRACT-P5E.md §5.6): the indexed buffer binding points, one record per
|
||||
// class. The generic tail wrapper carries the FIRST tail only, which is the whole of
|
||||
// the record on Espryt - the optional MGHostSpan tail exists for kCapNeedsHostUboBytes
|
||||
// and that bit is 0 for the whole of P5.
|
||||
// and that bit is RESERVED (P8-F): no server publishes it.
|
||||
MGP_WIRE_TAIL(SetShaderBuffers, MGPShaderBuffers, MGPBufferRange)
|
||||
MGP_WIRE_TAIL(SetVertexAttribDefaults, MGPVertexAttribDefaults, MGPAttribValue)
|
||||
|
||||
|
||||
@@ -766,17 +766,20 @@ namespace MobileGL::MG_Remote::Server {
|
||||
return LandReadbackInBuffer(record, bytes.data(), bpp);
|
||||
}
|
||||
|
||||
// P5b's server-side stub shape (CONTRACT-P5B.md): the same line the client's class-C table
|
||||
// raises (EmitTables.cpp UnmigratedVerbFatal) and the same family the census greps, so a
|
||||
// slot flipped on the client ahead of its server half aborts BY NAME on the apply thread
|
||||
// rather than rendering nothing. Named "(server sink)" in the message so a log reader can
|
||||
// tell which half is missing.
|
||||
// P8-F: A PROTOCOL GUARD, NOT AN UNMIGRATED VERB. This was P5b's server-side stub shape
|
||||
// (`Fatal{UnmigratedVerb, ...}`, "ServerVerbSink has no body for it yet"), but no package is
|
||||
// going to give these two multi-draw shapes a body, because our client can no longer send
|
||||
// either: client index arrays become one owned element buffer before the record is built
|
||||
// (EmitTables.cpp's multi-draw arm, OwnedDrawInputs.h clears kDrawHasUserIndices), and no GL
|
||||
// entry point produces an instanced multi-draw. A record that carries one came from a
|
||||
// foreign peer, so the family is the one every other malformed-record check uses.
|
||||
//
|
||||
// PH-1 (3): the shape is the PEER's (a multi-draw record it chose to send), so in an armed
|
||||
// session child the refusal latches and the verb returns false; unarmed it still dies.
|
||||
static Bool ServerUnmigratedVerbLatch(const char* slot) {
|
||||
return SessionLatch(MGFatalFamily::UnmigratedVerb, "MGPipe: Fatal{UnmigratedVerb, \"%s\"} (server sink: the record crossed and "
|
||||
"ServerVerbSink has no body for it yet - CONTRACT-P5B.md names the package)",
|
||||
static Bool MultiDrawShapeLatch(const char* slot) {
|
||||
return SessionLatch(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s\"} a multi-draw "
|
||||
"record with a shape this protocol does not carry: client indices travel as an owned "
|
||||
"element buffer and no GL entry point produces an instanced multi-draw",
|
||||
slot);
|
||||
}
|
||||
|
||||
@@ -802,10 +805,10 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// of 1 and no base instance is the plain draw it is equivalent to, and a count of 0 must
|
||||
// NOT collapse onto the plain draw (it draws nothing, the plain draw would draw once).
|
||||
//
|
||||
// What is still refused by name (the census's own grep family): a multi-draw that arrived
|
||||
// with a span (the client refuses "MultiDrawElements+CLIENT_INDICES" first; P8's
|
||||
// HostResolve.cpp flattens it) and a multi-draw that claims instancing (no GL entry point
|
||||
// produces one; the client never sends it).
|
||||
// What is still refused by name, as protocol corruption (P8-F): a multi-draw that arrived
|
||||
// with a span (our client stages client indices into an owned element buffer and never sets
|
||||
// kDrawHasUserIndices) and a multi-draw that claims instancing (no GL entry point produces
|
||||
// one; the client never sends it). Only a foreign peer reaches either.
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
namespace {
|
||||
// P5e (tx2), CONTRACT-P5E §5.3 / ruling 19 (ID-95, A8 closed). THE TWO UNIT WINDOWS MUST
|
||||
@@ -970,12 +973,12 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// ---- the multi-draws: the two arrays rebuilt from the ranges (rule C) --------------
|
||||
if (info.NumDraws != 1) {
|
||||
if (userIndices != nullptr) {
|
||||
return ServerUnmigratedVerbLatch(info.IndexSize == 0 ? "MultiDrawArrays+CLIENT_INDICES"
|
||||
: "MultiDrawElements+CLIENT_INDICES");
|
||||
return MultiDrawShapeLatch(info.IndexSize == 0 ? "MultiDrawArrays+CLIENT_INDICES"
|
||||
: "MultiDrawElements+CLIENT_INDICES");
|
||||
}
|
||||
if (instanced) {
|
||||
return ServerUnmigratedVerbLatch(info.IndexSize == 0 ? "MultiDrawArrays+INSTANCED"
|
||||
: "MultiDrawElements+INSTANCED");
|
||||
return MultiDrawShapeLatch(info.IndexSize == 0 ? "MultiDrawArrays+INSTANCED"
|
||||
: "MultiDrawElements+INSTANCED");
|
||||
}
|
||||
const auto n = static_cast<SizeT>(info.NumDraws);
|
||||
m_multiCounts.resize(n);
|
||||
|
||||
@@ -162,9 +162,9 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// that owns the question (kCapTimerQuery to the query family, kCapResidentSubData to
|
||||
// b1, and so on). `SetCapabilityBits(0)` is a legitimate and explicit answer - "this
|
||||
// server offers no optional capability" - and is the right call while those packages
|
||||
// land. kCapNeedsHostIndexBytes / kCapNeedsHostUboBytes must stay 0 for the whole of
|
||||
// P5 by ruling (CONTRACT-P5 table 0): they are the only two things that ask for an
|
||||
// MGHostSpan, and 0 is what keeps every one of them out of the first IPC frame.
|
||||
// land. kCapNeedsHostIndexBytes / kCapNeedsHostUboBytes / kCapViewportArray are RESERVED
|
||||
// (P8-F; CONTRACT-P5 table 0 first held the two host-byte bits at 0): the first two are
|
||||
// the only things that ask for an MGHostSpan, and 0 keeps every one of them off the wire.
|
||||
void SetCapabilityBits(Uint64 capBits);
|
||||
|
||||
// BITS 32..47, THE CONSUMER MASK (R-8 / C-4): which MGPipe subsystems this server has
|
||||
|
||||
@@ -456,8 +456,9 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
}
|
||||
|
||||
const void* SegmentTable::Resolve(Uint32 seg, Uint64 offset, Uint64 size) const {
|
||||
// 0 is "no segment", ALWAYS (table 0), and 0xFFFFFFFF is P8's index-mirror sentinel
|
||||
// which this phase does not resolve. Both land here as "unknown", and the CALLER
|
||||
// 0 is "no segment", ALWAYS (table 0), and 0xFFFFFFFF is the RESERVED index-mirror
|
||||
// sentinel (P8-F: the mirror was never built, so nothing resolves it and no client of
|
||||
// ours sends it). Both land here as "unknown", and the CALLER
|
||||
// escalates to Fatal - a unit case has to be able to exercise this arithmetic without
|
||||
// dying.
|
||||
if (seg == kSegNone || seg > static_cast<Uint32>(kSegAdopt)) {
|
||||
@@ -2121,7 +2122,7 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// entire point is that the server stops walking the client's binding-point table.
|
||||
//
|
||||
// THE HOST-SPAN PASS STILL RUNS FIRST, and it runs whether or not the record is
|
||||
// applied: kCapNeedsHostUboBytes is 0 for the whole of P5 (table 0), so the second
|
||||
// applied: kCapNeedsHostUboBytes is RESERVED (P8-F, never published), so the second
|
||||
// tail is always absent here, and this pass is what says so out loud if it ever is
|
||||
// not. A span that was going to be refused must be refused BEFORE the applier has
|
||||
// stored the window it rides with.
|
||||
|
||||
@@ -148,9 +148,9 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// declared. Fatal on an absent blob, then CheckBlobIsHonest on a present one.
|
||||
Bool RequireDeclaredBlob(MG_Pipe::MGPWireOp op, const MG_Pipe::MGPBlobRef& blob,
|
||||
const SegmentTable& segments);
|
||||
// R-2.3 arms 1 and 3 for MGHostSpan. P5's reduced path should produce ZERO host spans
|
||||
// (kCapNeedsHostIndexBytes / kCapNeedsHostUboBytes are both 0 in P5, table 0), so this
|
||||
// firing at all is a finding, not just a corruption check.
|
||||
// R-2.3 arms 1 and 3 for MGHostSpan. Our client produces ZERO host spans
|
||||
// (kCapNeedsHostIndexBytes / kCapNeedsHostUboBytes are RESERVED since P8-F, never set), so
|
||||
// this firing at all is a finding, not just a corruption check.
|
||||
//
|
||||
// IT CANNOT DO ARM 4 - it has no segment table - so a span that names a real segment and a
|
||||
// run PAST THE END OF IT passes this function. Use the overload below on any path that has
|
||||
|
||||
@@ -1159,8 +1159,10 @@ namespace {
|
||||
p.FrameSerial = 0;
|
||||
return Forge(c, r, P::MGPWireOp::Present, Bytes(p));
|
||||
}},
|
||||
// P8-F: ProtocolCorruption, no longer UnmigratedVerb - our client cannot send this shape
|
||||
// (nor +CLIENT_INDICES, the same site), so only a foreign peer like this one reaches it.
|
||||
{"MultiDrawInstanced", "PipeApplier.cpp", "MultiDraw*+INSTANCED", Outcome::Latched,
|
||||
"Fatal{UnmigratedVerb, \"MultiDrawArrays+INSTANCED\"}", false,
|
||||
"Fatal{ProtocolCorruption, \"MultiDrawArrays+INSTANCED\"}", false,
|
||||
[](Client::ClientSession& c, PeerReport& r) {
|
||||
P::MGPDrawInfo info{};
|
||||
info.Mode = GL_TRIANGLES;
|
||||
|
||||
Reference in New Issue
Block a user