mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-18 00:58:30 +09:00
[Merge] (P5b): integrate i1 alongside f1 and r2, preserving the combined slot partition
This commit is contained in:
@@ -8988,6 +8988,27 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
auto* srcMipmap = MG_State::GLState::AsMipmapTexture(srcEndpoint.Texture.get());
|
auto* srcMipmap = MG_State::GLState::AsMipmapTexture(srcEndpoint.Texture.get());
|
||||||
auto* dstMipmap = MG_State::GLState::AsMipmapTexture(dstEndpoint.Texture.get());
|
auto* dstMipmap = MG_State::GLState::AsMipmapTexture(dstEndpoint.Texture.get());
|
||||||
if (!srcMipmap || !dstMipmap) return;
|
if (!srcMipmap || !dstMipmap) return;
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
// P5b package i1's ONE backend edit, and the contract names this site
|
||||||
|
// (MG_Remote/CONTRACT-P5B.md §2 i1 "the copy-image-shadow-mirror emulation", ruling
|
||||||
|
// §6.7). Migrating glCopyImageSubData moves the first blocker off the client's
|
||||||
|
// Fatal{UnmigratedVerb} and onto the Fatal below, on every Espryt copy between two
|
||||||
|
// textures with CPU shadows - so the ruling is: UNDER A REAL TRANSPORT THE SERVER SKIPS
|
||||||
|
// THE MIRROR, and the client-side mirror ROADMAP P8 names ("CopyImage 镜像搬到 client")
|
||||||
|
// stays P8's.
|
||||||
|
//
|
||||||
|
// WHAT THE SKIP LOSES IS BOUNDED BY TWO FATALS, which is the whole reason it is allowed
|
||||||
|
// to be a skip rather than a port: a later glGetTexImage of the destination served from
|
||||||
|
// the shadow is class C wave 3 (Fatal{UnmigratedVerb, "GetTexImage"}, P9) and a texture
|
||||||
|
// re-mint that re-uploads the level is Fatal{UnmigratedEmulation, "texture-remint-pull"}
|
||||||
|
// (Managers.cpp:5634). Neither can silently read the un-mirrored shadow.
|
||||||
|
//
|
||||||
|
// BEHIND #if MOBILEGL_BUILD_DISAGGREGATED so the pull build's code does not move (G1),
|
||||||
|
// and the arm is the TRANSPORT and not the build - build-split runs its unit and
|
||||||
|
// integration-gpu lanes under MOBILEGL_TRANSPORT=monolith, where this mirror is on an
|
||||||
|
// ordinary correct path and must still run.
|
||||||
|
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) return;
|
||||||
|
#endif
|
||||||
#if MOBILEGL_PIPE_PUSH
|
#if MOBILEGL_PIPE_PUSH
|
||||||
// P4a (D-M). glCopyImageSubData's CPU-shadow mirror copies the source level's shadow
|
// P4a (D-M). glCopyImageSubData's CPU-shadow mirror copies the source level's shadow
|
||||||
// rows into the DESTINATION's shadow so a later readback of the destination sees what
|
// rows into the DESTINATION's shadow so a later readback of the destination sees what
|
||||||
|
|||||||
@@ -1935,6 +1935,55 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
|||||||
ENVIRONMENT "${MGL_ITEST_GLES_SPLIT_ENVIRONMENT}"
|
ENVIRONMENT "${MGL_ITEST_GLES_SPLIT_ENVIRONMENT}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# ---- P5b package i1 (MG_Remote/CONTRACT-P5B.md §2 i1) --------------------------------
|
||||||
|
#
|
||||||
|
# THE LANE CASE THAT PROVES THE RECORDS CROSSED, and it is a RESULT and not a probe (R-16:
|
||||||
|
# a probe may not arm against a stub). Each of these four scenarios asserts a value that
|
||||||
|
# only exists if the verb ran on the apply thread - the texel a compute shader stored
|
||||||
|
# through an image unit, the word glCopyImageSubData moved, the counter an atomic add left
|
||||||
|
# behind, the block a program pipeline rebound - so a sink that DECLINED, or a client that
|
||||||
|
# fell through to the driver, is red by the number it reads back and not by a tally.
|
||||||
|
#
|
||||||
|
# Before i1 every one of these aborted at the client's Fatal{UnmigratedVerb} on the first
|
||||||
|
# glBindImageTexture / glDispatchCompute / glCopyImageSubData / glShaderStorageBlockBinding;
|
||||||
|
# all four are green under inproc now (the census: 13 + 6 + 11 + 3 DirectGLES entries).
|
||||||
|
# RED ONCE BY DOING X: return false from ServerVerbSink::OnLaunchGrid before the
|
||||||
|
# gl.DispatchCompute call and UnboundImageDescriptor / AtomicCounter / ProgramPipeline all
|
||||||
|
# read back their initial values.
|
||||||
|
#
|
||||||
|
# THE FILTERS ARE NARROWED TO THE CASES THAT ACTUALLY EMIT, and that is ScenarioFixture's
|
||||||
|
# rule rather than a convenience: an armed `DirectGLES.Split.` case must move the client
|
||||||
|
# encoder's record ordinal (ScenarioFixture.h:86), because a lane whose workload produced no
|
||||||
|
# record is the "resolved the transport and then fell through to the driver" shape that
|
||||||
|
# every pixel assertion is blind to. ProgramPipelineScenario's other nine cases are pure
|
||||||
|
# name/state cases that draw nothing, so the whole-scenario filter would arm them and they
|
||||||
|
# would be red for having nothing to say. The two named here are the ones that were
|
||||||
|
# Fatal{UnmigratedVerb, "ShaderStorageBlockBinding"} on the c0b head.
|
||||||
|
#
|
||||||
|
# One block per scenario, following the three above: a `:`-separated multi-pattern
|
||||||
|
# TEST_FILTER is unproven through gtest_discover_tests' flat PROPERTIES forwarding.
|
||||||
|
set(MGL_SPLIT_I1_SCENARIOS
|
||||||
|
UnboundImageDescriptorScenario # bind_shader_image (72) + launch_grid (60), 13 cases
|
||||||
|
CopyImageLayeredScenario # resource_copy_region (53), 6 cases
|
||||||
|
AtomicCounterScenario # launch_grid (60) + memory_barrier (61), 3 cases
|
||||||
|
ProgramPipelineScenario) # set_storage_block_binding (75), the 2 storage-block cases
|
||||||
|
set(MGL_SPLIT_I1_FILTER_UnboundImageDescriptorScenario "UnboundImageDescriptorScenario.*")
|
||||||
|
set(MGL_SPLIT_I1_FILTER_CopyImageLayeredScenario "CopyImageLayeredScenario.*")
|
||||||
|
set(MGL_SPLIT_I1_FILTER_AtomicCounterScenario "AtomicCounterScenario.*")
|
||||||
|
set(MGL_SPLIT_I1_FILTER_ProgramPipelineScenario "ProgramPipelineScenario.*StorageBlock*")
|
||||||
|
foreach(mglItestI1Scenario IN LISTS MGL_SPLIT_I1_SCENARIOS)
|
||||||
|
gtest_discover_tests(MobileGLIntegrationTest
|
||||||
|
TEST_PREFIX "DirectGLES.Split."
|
||||||
|
TEST_LIST "MGL_SPLIT_I1_${mglItestI1Scenario}_TESTS"
|
||||||
|
TEST_FILTER "${MGL_SPLIT_I1_FILTER_${mglItestI1Scenario}}"
|
||||||
|
DISCOVERY_TIMEOUT 30
|
||||||
|
PROPERTIES
|
||||||
|
LABELS "integration-gpu\;integration-split"
|
||||||
|
TIMEOUT ${MGL_ITEST_TIMEOUT}
|
||||||
|
ENVIRONMENT "${MGL_ITEST_GLES_SPLIT_ENVIRONMENT}"
|
||||||
|
)
|
||||||
|
endforeach()
|
||||||
|
|
||||||
# The split half of the counting pair. MGITEST_PERSISTENT_MAP_ARM=emulated is R-6: under split
|
# The split half of the counting pair. MGITEST_PERSISTENT_MAP_ARM=emulated is R-6: under split
|
||||||
# the adopt tier is pinned at T2, the resource owner declines every acquisition and the client
|
# the adopt tier is pinned at T2, the resource owner declines every acquisition and the client
|
||||||
# pushes the mapping's dirty blocks - so pmap must be non-zero and mpr must be the monolith
|
# pushes the mapping's dirty blocks - so pmap must be non-zero and mpr must be the monolith
|
||||||
|
|||||||
@@ -11,4 +11,13 @@ foreach(scenario @MGL_SPLIT_SMALL_RING_SCENARIOS@)
|
|||||||
"MOBILEGL_LOG_FILE_PATH=@CMAKE_CURRENT_BINARY_DIR@/split-logs/${entry}.log")
|
"MOBILEGL_LOG_FILE_PATH=@CMAKE_CURRENT_BINARY_DIR@/split-logs/${entry}.log")
|
||||||
endforeach()
|
endforeach()
|
||||||
endforeach()
|
endforeach()
|
||||||
|
# P5b package i1's four scenarios, the same shape as the small-ring loop above: every
|
||||||
|
# DirectGLES.Split. entry owns exactly one absolute log path and shares it with nobody, which
|
||||||
|
# is what SplitLogPaths.PrivateAndDistinct checks over the discovered set.
|
||||||
|
foreach(scenario @MGL_SPLIT_I1_SCENARIOS@)
|
||||||
|
foreach(entry IN LISTS MGL_SPLIT_I1_${scenario}_TESTS)
|
||||||
|
set_tests_properties("${entry}" PROPERTIES ENVIRONMENT
|
||||||
|
"MOBILEGL_LOG_FILE_PATH=@CMAKE_CURRENT_BINARY_DIR@/split-logs/${entry}.log")
|
||||||
|
endforeach()
|
||||||
|
endforeach()
|
||||||
# PersistentMapArm retains its existing private path and RESOURCE_LOCK: b1 reads it.
|
# PersistentMapArm retains its existing private path and RESOURCE_LOCK: b1 reads it.
|
||||||
|
|||||||
@@ -105,6 +105,14 @@
|
|||||||
/* DirectGLES.cpp:4497, PrepareForDraw, the second unconditional pointer read of every draw. */ \
|
/* DirectGLES.cpp:4497, PrepareForDraw, the second unconditional pointer read of every draw. */ \
|
||||||
X(GetProgramForDraw, BARRIER_PULLED, "P8 (Espryt), P7 (Magma)", \
|
X(GetProgramForDraw, BARRIER_PULLED, "P8 (Espryt), P7 (Magma)", \
|
||||||
"frontend SharedPtr<ProgramObject>; the record carries a handle") \
|
"frontend SharedPtr<ProgramObject>; the record carries a handle") \
|
||||||
|
/* P5b package i1 (CONTRACT-P5B.md §6.9, the one row the contract grants a package). This */ \
|
||||||
|
/* was FATAL "reachable only from kDispatch; there is no compute on the reduced path" - and */ \
|
||||||
|
/* i1 IS what puts compute on the path: launch_grid (60) now crosses and the backend's */ \
|
||||||
|
/* PrepareForCompute pulls this inside it (DirectGLES.cpp:5779, VulkanRenderer.cpp:7327). */ \
|
||||||
|
/* It is GetProgramForDraw's twin in every respect, so it takes its class and its retiring */ \
|
||||||
|
/* phases. Overturned by: nothing in P5b; P7/P8 retire both rows together. */ \
|
||||||
|
X(GetProgramForDispatch, BARRIER_PULLED, "P7 (Magma), P8 (Espryt)", \
|
||||||
|
"frontend SharedPtr<ProgramObject>; the record carries a handle") \
|
||||||
/* The XFB six. XFB itself is off the reduced path, but kDraw's may-read mask carries all */ \
|
/* The XFB six. XFB itself is off the reduced path, but kDraw's may-read mask carries all */ \
|
||||||
/* six and the draw walk reads them regardless - which is exactly the case a field census */ \
|
/* six and the draw walk reads them regardless - which is exactly the case a field census */ \
|
||||||
/* taken from "what the scenario does" rather than from the mask would miss. */ \
|
/* taken from "what the scenario does" rather than from the mask would miss. */ \
|
||||||
@@ -132,13 +140,13 @@
|
|||||||
X(GetPixelStoreParameters, APPLIER_DERIVED, "-", \
|
X(GetPixelStoreParameters, APPLIER_DERIVED, "-", \
|
||||||
"set_pixel_pack_state; the applier writes m_pixelStore[0] (PipeApply.cpp:1373)") \
|
"set_pixel_pack_state; the applier writes m_pixelStore[0] (PipeApply.cpp:1373)") \
|
||||||
\
|
\
|
||||||
/* ---- FATAL: three non-sticky fields, each off the reduced path for a checkable reason --- */ \
|
/* ---- FATAL: two non-sticky fields, each off the reduced path for a checkable reason ----- */ \
|
||||||
|
/* Three until P5b: GetProgramForDispatch moved up to BARRIER_PULLED when package i1 put */ \
|
||||||
|
/* compute on the path (CONTRACT-P5B.md §6.9). */ \
|
||||||
X(GetBoundTransformFeedbackName, FATAL, "-", \
|
X(GetBoundTransformFeedbackName, FATAL, "-", \
|
||||||
"DEAD: read by no backend since the D21 rekey (PipeInputs.h:232-234)") \
|
"DEAD: read by no backend since the D21 rekey (PipeInputs.h:232-234)") \
|
||||||
X(GetTransformFeedbackPausedPrimitiveCounter, FATAL, "-", \
|
X(GetTransformFeedbackPausedPrimitiveCounter, FATAL, "-", \
|
||||||
"reachable only from class kQuery, which the reduced path never enters") \
|
"reachable only from class kQuery, which the reduced path never enters") \
|
||||||
X(GetProgramForDispatch, FATAL, "-", \
|
|
||||||
"reachable only from kDispatch; there is no compute on the reduced path") \
|
|
||||||
\
|
\
|
||||||
/* ---- the seven sticky forwards, as FIELD rows ---- */ \
|
/* ---- the seven sticky forwards, as FIELD rows ---- */ \
|
||||||
/* They have no storage, so a read of the FIELD is a call of the FORWARD; the field row and */ \
|
/* They have no storage, so a read of the FIELD is a call of the FORWARD; the field row and */ \
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ inline constexpr MGPipeFieldOwnership kMGPipeFieldOwnership[kMGPipeInputFieldCou
|
|||||||
MGPipeFieldOwnership::kRecordSupplied, // GetPolygonOffsetFactor
|
MGPipeFieldOwnership::kRecordSupplied, // GetPolygonOffsetFactor
|
||||||
MGPipeFieldOwnership::kRecordSupplied, // GetPolygonOffsetUnits
|
MGPipeFieldOwnership::kRecordSupplied, // GetPolygonOffsetUnits
|
||||||
MGPipeFieldOwnership::kRecordSupplied, // GetPrimitiveRestartIndex
|
MGPipeFieldOwnership::kRecordSupplied, // GetPrimitiveRestartIndex
|
||||||
MGPipeFieldOwnership::kFatal, // GetProgramForDispatch
|
MGPipeFieldOwnership::kBarrierPulled, // GetProgramForDispatch
|
||||||
MGPipeFieldOwnership::kBarrierPulled, // GetProgramForDraw
|
MGPipeFieldOwnership::kBarrierPulled, // GetProgramForDraw
|
||||||
MGPipeFieldOwnership::kBarrierPulled, // GetProgramObject
|
MGPipeFieldOwnership::kBarrierPulled, // GetProgramObject
|
||||||
MGPipeFieldOwnership::kRecordSupplied, // GetProvokingVertexMode
|
MGPipeFieldOwnership::kRecordSupplied, // GetProvokingVertexMode
|
||||||
@@ -136,7 +136,7 @@ inline constexpr const char* kMGPipeFieldRetiringPhase[kMGPipeInputFieldCount] =
|
|||||||
"-", // GetPolygonOffsetFactor
|
"-", // GetPolygonOffsetFactor
|
||||||
"-", // GetPolygonOffsetUnits
|
"-", // GetPolygonOffsetUnits
|
||||||
"-", // GetPrimitiveRestartIndex
|
"-", // GetPrimitiveRestartIndex
|
||||||
"-", // GetProgramForDispatch
|
"P7 (Magma), P8 (Espryt)", // GetProgramForDispatch
|
||||||
"P8 (Espryt), P7 (Magma)", // GetProgramForDraw
|
"P8 (Espryt), P7 (Magma)", // GetProgramForDraw
|
||||||
"P9", // GetProgramObject
|
"P9", // GetProgramObject
|
||||||
"-", // GetProvokingVertexMode
|
"-", // GetProvokingVertexMode
|
||||||
@@ -298,6 +298,6 @@ inline constexpr SizeT kMGPipeVerbBoundaryExemptCount = 3;
|
|||||||
// The class sizes, as constants a test can pin without recounting the table.
|
// The class sizes, as constants a test can pin without recounting the table.
|
||||||
inline constexpr SizeT kMGPipeRecordSuppliedFieldCount = 32;
|
inline constexpr SizeT kMGPipeRecordSuppliedFieldCount = 32;
|
||||||
inline constexpr SizeT kMGPipeApplierDerivedFieldCount = 1;
|
inline constexpr SizeT kMGPipeApplierDerivedFieldCount = 1;
|
||||||
inline constexpr SizeT kMGPipeBarrierPulledFieldCount = 27;
|
inline constexpr SizeT kMGPipeBarrierPulledFieldCount = 28;
|
||||||
inline constexpr SizeT kMGPipeFatalFieldCount = 3;
|
inline constexpr SizeT kMGPipeFatalFieldCount = 2;
|
||||||
static_assert(kMGPipeRecordSuppliedFieldCount + kMGPipeApplierDerivedFieldCount + kMGPipeBarrierPulledFieldCount + kMGPipeFatalFieldCount == kMGPipeInputFieldCount, "the four class sizes do not partition the field set");
|
static_assert(kMGPipeRecordSuppliedFieldCount + kMGPipeApplierDerivedFieldCount + kMGPipeBarrierPulledFieldCount + kMGPipeFatalFieldCount == kMGPipeInputFieldCount, "the four class sizes do not partition the field set");
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
//
|
//
|
||||||
// THE PARTITION IS CONTRACT-P5.md §7's AND IS NOT RE-DERIVED HERE (R-15, ID-12):
|
// THE PARTITION IS CONTRACT-P5.md §7's AND IS NOT RE-DERIVED HERE (R-15, ID-12):
|
||||||
// class A 2 slots answered locally from the caps mirror, never emitted, never Fatal
|
// class A 2 slots answered locally from the caps mirror, never emitted, never Fatal
|
||||||
// class B 5 slots emitted
|
// class B 5 slots emitted (+ 7 flipped by P5b package i1, below)
|
||||||
// class C 64 slots Fatal{UnmigratedVerb, "<slot>"}
|
// class C 64 slots Fatal{UnmigratedVerb, "<slot>"} (- the same 7)
|
||||||
// The three counts are static_asserted to sum to kRemoteEmitSlotCount below, so a slot that
|
// The three counts are static_asserted to sum to kRemoteEmitSlotCount below, so a slot that
|
||||||
// changes class without changing the arithmetic is a build break rather than a behaviour
|
// changes class without changing the arithmetic is a build break rather than a behaviour
|
||||||
// change nobody reviewed.
|
// change nobody reviewed.
|
||||||
@@ -35,6 +35,13 @@
|
|||||||
|
|
||||||
#include <MG_State/GLState/BufferState/BufferObject.h>
|
#include <MG_State/GLState/BufferState/BufferObject.h>
|
||||||
#include <MG_State/GLState/Core.h>
|
#include <MG_State/GLState/Core.h>
|
||||||
|
// P5b i1: the emitters below name a texture's, a buffer's and a program's HANDLE beside the GL
|
||||||
|
// arguments (rule D). The handle comes from the client's own slot allocator, which is
|
||||||
|
// MG_Impl/Pipe's - the same table MG_Impl/Pipe/ImageEmit.h's set_shader_images reads, so the
|
||||||
|
// two records name one identity rather than two.
|
||||||
|
#include <MG_Impl/Pipe/SlotAllocator.h>
|
||||||
|
#include <MG_State/GLState/ProgramState/ProgramObject.h>
|
||||||
|
#include <MG_State/GLState/TextureState/TextureState.h>
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -665,6 +672,231 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
LogE2ControlLine(g_presentOrdinal);
|
LogE2ControlLine(g_presentOrdinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// CLASS B - P5b package i1: image bind, compute, barriers, copy-image, SSBO block
|
||||||
|
// (MG_Remote/CONTRACT-P5B.md §2 i1). Seven slots, five wire rows.
|
||||||
|
// =============================================================================
|
||||||
|
//
|
||||||
|
// RULE D, WHICH IS WHY THESE ARE SHORT. A P5b verb crosses AS THE CALL: the record
|
||||||
|
// carries the GL arguments verbatim - the enums as tokens, the GL names the backend
|
||||||
|
// keys on - beside the handle the P7/P8 form will dispatch on instead, and the server's
|
||||||
|
// ServerVerbSink reproduces the backend call the monolith makes. The backend keeps
|
||||||
|
// reading the frontend state it reads today through the BARRIER-PULLED fields of its
|
||||||
|
// verb class, which the record's own verb stamp is what makes legal. So a migration is
|
||||||
|
// one emitter here plus one sink body there, and nothing in the backend moves.
|
||||||
|
//
|
||||||
|
// THE HANDLES ARE LOOKED UP, NEVER MINTED, and that is a ruling (i1-v1 §4). The sinks
|
||||||
|
// below dispatch on the GL NAME - that is the whole point of carrying it - so a handle
|
||||||
|
// is carried for P7's sake only. FindByLifetimeId answers the handle the resource
|
||||||
|
// subsystem has already published for this object and kMGPipeNullHandle when it has
|
||||||
|
// published none; Acquire would MINT one here instead, at a call site that emits no
|
||||||
|
// create record, and the server would then be handed an identity it has never seen.
|
||||||
|
// A null Res/Src/Dst/ShaderCso therefore means "no handle published yet", which is a
|
||||||
|
// true statement, rather than a slot nobody allocated.
|
||||||
|
|
||||||
|
MG_Pipe::MGPipeHandle PublishedTextureHandle(
|
||||||
|
const SharedPtr<MG_State::GLState::ITextureObject>& texture) {
|
||||||
|
if (!texture) return MG_Pipe::kMGPipeNullHandle;
|
||||||
|
return MG_Pipe::MGPipeSlots().FindByLifetimeId(MG_Pipe::MGPipeKind::Texture,
|
||||||
|
texture->GetLifetimeId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// glBindImageTexture. Emitted AT THE CALL, after the frontend has written the unit's
|
||||||
|
// ImageTextureBinding and MGP_FILL(BindImageTexture) has run - the record's verb
|
||||||
|
// boundary is what makes the server's read of that binding legal. set_shader_images
|
||||||
|
// (the draw-prep set) still travels at the next validate, untouched: that record
|
||||||
|
// describes a resolved unit for the draw, this one reproduces a call.
|
||||||
|
//
|
||||||
|
// NO PRE-VERB HOOK, AND THAT IS DELIBERATE. b1's two hooks describe "the work the
|
||||||
|
// record is ABOUT TO START" - PushPersistentMapsBeforeVerb publishes bytes an
|
||||||
|
// application wrote through a coherent map, MarkGpuWrites* builds the GPU-write set.
|
||||||
|
// A bind starts no shader and reads no buffer; the dispatch that later reads this image
|
||||||
|
// is the verb that carries both hooks, and running them here as well would push the
|
||||||
|
// same maps twice per dispatch and inflate b1's per-row counters.
|
||||||
|
void EmitBindImageTexture(GLuint unit, GLuint texture, GLint level, GLboolean layered,
|
||||||
|
GLint layer, GLenum access, GLenum format) {
|
||||||
|
ClientSession& session = RequireSession("BindImageTexture");
|
||||||
|
|
||||||
|
MG_Pipe::MGPImageBind record{};
|
||||||
|
// The unit's binding is the frontend's and has just been written by the caller, so
|
||||||
|
// the texture this record names is the one the server's SyncImageTextureBinding
|
||||||
|
// will pull for the same unit. Read from MG_State::pGLContext and NOT through
|
||||||
|
// MGB_CTX: on this side of a split MGB_CTX is gPipeInputs, which is the SERVER's
|
||||||
|
// view, and the client asking it a question is how the two halves come to disagree.
|
||||||
|
if (MG_State::pGLContext != nullptr) {
|
||||||
|
record.Res = PublishedTextureHandle(
|
||||||
|
MG_State::pGLContext->GetImageTextureBinding(static_cast<Int>(unit)).Texture);
|
||||||
|
}
|
||||||
|
record.Unit = static_cast<Uint32>(unit);
|
||||||
|
// The GL name the application passed, verbatim - what the ES slot is handed as
|
||||||
|
// `texture` and currently ignores. Never an identity (ARCHITECTURE 4.2.1).
|
||||||
|
record.GlName = static_cast<Uint32>(texture);
|
||||||
|
record.Level = static_cast<Int32>(level);
|
||||||
|
record.Layer = static_cast<Int32>(layer);
|
||||||
|
// THE GL ACCESS TOKEN, not MGPImageView::Access's three-value encoding (table 0's
|
||||||
|
// MGPImageBind::Access row). Two records, two jobs.
|
||||||
|
record.Access = static_cast<Uint32>(access);
|
||||||
|
record.Format = static_cast<Uint32>(format);
|
||||||
|
record.Layered = layered != GL_FALSE ? 1 : 0;
|
||||||
|
session.EmitAndWait(MG_Pipe::MGPWireOp::BindShaderImage, &record, sizeof(record),
|
||||||
|
nullptr, 0, nullptr, 0, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// glDispatchCompute. THE HOOK ORDER IS b1's AND IS INHERITED FROM THE CLASS-C STUB
|
||||||
|
// VERBATIM: push the persistent maps (they produce resource_subdata records that must
|
||||||
|
// precede the verb on SEG_CMD), then the dispatch mark walk, then the record. The stub
|
||||||
|
// carried both calls before its Fatal precisely so that the package which flipped this
|
||||||
|
// slot would inherit a call site that was already correct.
|
||||||
|
void EmitDispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ) {
|
||||||
|
ClientSession& session = RequireSession("DispatchCompute");
|
||||||
|
PushPersistentMapsBeforeVerb();
|
||||||
|
MarkGpuWritesForDispatch();
|
||||||
|
|
||||||
|
MG_Pipe::MGPGridInfo record{};
|
||||||
|
record.GridX = static_cast<Uint32>(numGroupsX);
|
||||||
|
record.GridY = static_cast<Uint32>(numGroupsY);
|
||||||
|
record.GridZ = static_cast<Uint32>(numGroupsZ);
|
||||||
|
// Block* STAY 0 IN P5b (contract i1): the local size is a link artifact the backend
|
||||||
|
// reads from its own program, and a client-minted copy would be a second statement
|
||||||
|
// of it. P7's Magma may fill it from the reflection archive.
|
||||||
|
record.IndirectBuffer = MG_Pipe::kMGPipeNullHandle;
|
||||||
|
record.IndirectOffset = 0;
|
||||||
|
record.IsIndirect = 0;
|
||||||
|
session.EmitAndWait(MG_Pipe::MGPWireOp::LaunchGrid, &record, sizeof(record), nullptr, 0,
|
||||||
|
nullptr, 0, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitDispatchComputeIndirect(GLintptr indirect) {
|
||||||
|
ClientSession& session = RequireSession("DispatchComputeIndirect");
|
||||||
|
PushPersistentMapsBeforeVerb();
|
||||||
|
MarkGpuWritesForDispatch();
|
||||||
|
|
||||||
|
MG_Pipe::MGPGridInfo record{};
|
||||||
|
// The counts come from the GL_DISPATCH_INDIRECT_BUFFER, so the three grid fields are
|
||||||
|
// 0 and IsIndirect is what says so; the sink dispatches on it and calls
|
||||||
|
// glDispatchComputeIndirect with the offset the application spelled.
|
||||||
|
record.IsIndirect = 1;
|
||||||
|
record.IndirectOffset = static_cast<Uint64>(indirect);
|
||||||
|
record.IndirectBuffer = MG_Pipe::kMGPipeNullHandle;
|
||||||
|
if (MG_State::pGLContext != nullptr) {
|
||||||
|
const auto& bound =
|
||||||
|
MG_State::pGLContext
|
||||||
|
->GetBufferBindingSlot(::MobileGL::BufferTarget::DispatchIndirect)
|
||||||
|
.GetBoundObject();
|
||||||
|
if (bound) {
|
||||||
|
record.IndirectBuffer = MG_Pipe::MGPipeSlots().FindByLifetimeId(
|
||||||
|
MG_Pipe::MGPipeKind::Buffer, bound->GetLifetimeId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
session.EmitAndWait(MG_Pipe::MGPWireOp::LaunchGrid, &record, sizeof(record), nullptr, 0,
|
||||||
|
nullptr, 0, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// glMemoryBarrier / glMemoryBarrierByRegion. The bits cross VERBATIM: the frontend has
|
||||||
|
// already validated them and already folds glTextureBarrier onto the same field
|
||||||
|
// (GL_Drawing.cpp:920-937), and Espryt's atomic-counter lowering - the counter bit
|
||||||
|
// implying the storage bit - stays inside the backend where the reason for it lives
|
||||||
|
// (DirectGLES.cpp:8837). A client that pre-lowered would be answering a driver question
|
||||||
|
// from the wrong side and the two arms would stop being byte-identical.
|
||||||
|
//
|
||||||
|
// No pre-verb hook: a barrier orders memory the GPU already holds. It starts no shader
|
||||||
|
// and reads no mapped buffer.
|
||||||
|
void EmitMemoryBarrier(GLbitfield barriers) {
|
||||||
|
ClientSession& session = RequireSession("MemoryBarrier");
|
||||||
|
MG_Pipe::MGPMemoryBarrier record{};
|
||||||
|
record.Bits = static_cast<Uint32>(barriers);
|
||||||
|
record.ByRegion = 0;
|
||||||
|
session.EmitAndWait(MG_Pipe::MGPWireOp::MemoryBarrier, &record, sizeof(record), nullptr,
|
||||||
|
0, nullptr, 0, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitMemoryBarrierByRegion(GLbitfield barriers) {
|
||||||
|
ClientSession& session = RequireSession("MemoryBarrierByRegion");
|
||||||
|
MG_Pipe::MGPMemoryBarrier record{};
|
||||||
|
record.Bits = static_cast<Uint32>(barriers);
|
||||||
|
record.ByRegion = 1;
|
||||||
|
session.EmitAndWait(MG_Pipe::MGPWireOp::MemoryBarrier, &record, sizeof(record), nullptr,
|
||||||
|
0, nullptr, 0, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// glCopyImageSubData -> resource_copy_region (53), which P5b rules is glCopyImageSubData
|
||||||
|
// ONLY (contract §6.4; the framebuffer-sourced copies are f1's row 76).
|
||||||
|
void EmitCopyImageSubData(const MG_Backend::CopyImageEndpoint& src, GLenum srcTarget,
|
||||||
|
GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ,
|
||||||
|
const MG_Backend::CopyImageEndpoint& dst, GLenum dstTarget,
|
||||||
|
GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
|
||||||
|
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) {
|
||||||
|
ClientSession& session = RequireSession("CopyImageSubData");
|
||||||
|
|
||||||
|
// ID-57's SHAPE: REFUSED BY NAME, BEFORE ANY EMISSION. GL 4.6 core 18.3.2 accepts
|
||||||
|
// GL_RENDERBUFFER as either endpoint, and an endpoint is a sum type for exactly that
|
||||||
|
// reason - but no sticky forward hands out a renderbuffer object, so the sink has no
|
||||||
|
// way to rebuild one from a name, and none was measured. P7 is where the backend
|
||||||
|
// takes handles and this arm becomes ordinary. The sink refuses the same shape by
|
||||||
|
// the same name if a record ever reaches it (defence on both sides of one wire).
|
||||||
|
if (src.IsRenderbuffer() || dst.IsRenderbuffer()) {
|
||||||
|
UnmigratedVerbFatal("CopyImageSubData+RENDERBUFFER");
|
||||||
|
}
|
||||||
|
|
||||||
|
MG_Pipe::MGPCopyRegion record{};
|
||||||
|
record.Src = PublishedTextureHandle(src.Texture);
|
||||||
|
record.Dst = PublishedTextureHandle(dst.Texture);
|
||||||
|
// The GL names beside the handles: the key MGB_CTX->GetTextureObject(name) takes on
|
||||||
|
// the far side (a BARRIER-PULLED sticky forward, counted in `rsp`, retired by P7).
|
||||||
|
record.SrcGlName =
|
||||||
|
src.Texture ? static_cast<Uint32>(src.Texture->GetExternalIndex()) : 0u;
|
||||||
|
record.DstGlName =
|
||||||
|
dst.Texture ? static_cast<Uint32>(dst.Texture->GetExternalIndex()) : 0u;
|
||||||
|
// The GL targets verbatim, in a Uint16 - every GL texture target fits one. NOT
|
||||||
|
// MGPipeResourceTarget: the sink only ever forwards these to a slot that takes GL
|
||||||
|
// enums, and the tree has no resource-target -> GL-enum inverse to spend on them.
|
||||||
|
record.SrcTarget = static_cast<Uint16>(srcTarget);
|
||||||
|
record.DstTarget = static_cast<Uint16>(dstTarget);
|
||||||
|
record.SrcLevel = static_cast<Uint16>(srcLevel);
|
||||||
|
record.DstLevel = static_cast<Uint16>(dstLevel);
|
||||||
|
// SrcBox is {srcX, srcY, srcZ, w, h, d}: the source origin AND the extent, which is
|
||||||
|
// one extent for both endpoints (GL spells the copy's size once).
|
||||||
|
record.SrcBox = MG_Pipe::MGPBox{srcX, srcY, srcZ, static_cast<Uint32>(srcWidth),
|
||||||
|
static_cast<Uint32>(srcHeight),
|
||||||
|
static_cast<Uint32>(srcDepth)};
|
||||||
|
record.DstX = dstX;
|
||||||
|
record.DstY = dstY;
|
||||||
|
record.DstZ = dstZ;
|
||||||
|
session.EmitAndWait(MG_Pipe::MGPWireOp::ResourceCopyRegion, &record, sizeof(record),
|
||||||
|
nullptr, 0, nullptr, 0, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// glShaderStorageBlockBinding -> set_storage_block_binding (75), the ONE content-carrying
|
||||||
|
// row P5b adds. The block is NAMED, not indexed, because the application's index is the
|
||||||
|
// frontend interface-query enumeration's and no backend shares that index space
|
||||||
|
// (BackendObject.h:216-221) - the name is the one coordinate all three agree on.
|
||||||
|
void EmitShaderStorageBlockBinding(GLuint program, const GLchar* storageBlockName,
|
||||||
|
GLuint storageBlockBinding) {
|
||||||
|
ClientSession& session = RequireSession("ShaderStorageBlockBinding");
|
||||||
|
// The backend slot's own first line (DirectGLES.cpp:9201), kept here so a null name
|
||||||
|
// never becomes a zero-size blob - which rule A forbids spelling at all.
|
||||||
|
if (storageBlockName == nullptr) return;
|
||||||
|
|
||||||
|
MG_Pipe::MGPStorageBlockBinding record{};
|
||||||
|
record.GlName = static_cast<Uint32>(program);
|
||||||
|
record.Binding = static_cast<Uint32>(storageBlockBinding);
|
||||||
|
record.ShaderCso = MG_Pipe::kMGPipeNullHandle;
|
||||||
|
if (MG_State::pGLContext != nullptr) {
|
||||||
|
const auto& programObject = MG_State::pGLContext->GetProgramObject(program);
|
||||||
|
if (programObject) {
|
||||||
|
record.ShaderCso = MG_Pipe::MGPipeSlots().FindByLifetimeId(
|
||||||
|
MG_Pipe::MGPipeKind::ShaderCso, programObject->GetLifetimeId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Size = strlen + 1: THE NUL TRAVELS (contract table 0's block-name row). The
|
||||||
|
// decoder re-terminates into a bounded local and refuses a run whose last byte is
|
||||||
|
// not NUL, so the two sides agree on where the name ends.
|
||||||
|
const Uint64 nameBytes = static_cast<Uint64>(std::strlen(storageBlockName)) + 1ull;
|
||||||
|
record.Name = session.Encoder().StageBytes(storageBlockName, nameBytes);
|
||||||
|
session.EmitAndWait(MG_Pipe::MGPWireOp::SetStorageBlockBinding, &record, sizeof(record),
|
||||||
|
nullptr, 0, nullptr, 0, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// CLASS A - answered locally from the caps mirror (R-15). NO RECORD, EVER.
|
// CLASS A - answered locally from the caps mirror (R-15). NO RECORD, EVER.
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -704,7 +936,8 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// CLASS C - Fatal{UnmigratedVerb}. 64 slots: 63 in GLFunctionsTable + SetSwapInterval.
|
// CLASS C - Fatal{UnmigratedVerb}. 57 slots: 56 in GLFunctionsTable + SetSwapInterval
|
||||||
|
// (64 at the P5b contract commit, less the seven package i1 flipped to class B).
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
//
|
//
|
||||||
// PARTITIONED BY THE P5b PACKAGE THAT OWNS THE FLIP (MG_Remote/CONTRACT-P5B.md,
|
// PARTITIONED BY THE P5b PACKAGE THAT OWNS THE FLIP (MG_Remote/CONTRACT-P5B.md,
|
||||||
@@ -755,17 +988,12 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
X(DrawElementsIndirect, void, (GLenum, GLenum, const void*)) \
|
X(DrawElementsIndirect, void, (GLenum, GLenum, const void*)) \
|
||||||
X(DrawArraysIndirect, void, (GLenum, const void*))
|
X(DrawArraysIndirect, void, (GLenum, const void*))
|
||||||
|
|
||||||
// DispatchCompute and DispatchComputeIndirect are i1's too; they are hand-written below
|
// i1 HAS LANDED: the list is EMPTY and all seven slots are class B (the five emitters
|
||||||
// because they carry b1's dispatch hook before the Fatal.
|
// above plus the two compute ones). It is kept as an empty macro rather than deleted so
|
||||||
#define MGR_UNMIGRATED_I1_SLOTS(X) \
|
// that MGR_UNMIGRATED_GL_SLOTS' union, kUnmigratedI1's arithmetic and the ownership
|
||||||
X(BindImageTexture, void, (GLuint, GLuint, GLint, GLboolean, GLint, GLenum, GLenum)) \
|
// static_assert below all keep their shape - and so the next package to need a row here
|
||||||
X(CopyImageSubData, void, \
|
// (a P5b wave-3 image/compute slot) has the partition to put it in.
|
||||||
(const MG_Backend::CopyImageEndpoint&, GLenum, GLint, GLint, GLint, GLint, \
|
#define MGR_UNMIGRATED_I1_SLOTS(X)
|
||||||
const MG_Backend::CopyImageEndpoint&, GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, \
|
|
||||||
GLsizei)) \
|
|
||||||
X(MemoryBarrier, void, (GLbitfield)) \
|
|
||||||
X(MemoryBarrierByRegion, void, (GLbitfield)) \
|
|
||||||
X(ShaderStorageBlockBinding, void, (GLuint, const GLchar*, GLuint))
|
|
||||||
|
|
||||||
#define MGR_UNMIGRATED_T2_SLOTS(X) \
|
#define MGR_UNMIGRATED_T2_SLOTS(X) \
|
||||||
X(PatchParameteri, void, (GLenum, GLint)) \
|
X(PatchParameteri, void, (GLenum, GLint)) \
|
||||||
@@ -829,31 +1057,18 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
MGR_UNMIGRATED_GL_VALUE_SLOTS(MGR_DEFINE_UNMIGRATED)
|
MGR_UNMIGRATED_GL_VALUE_SLOTS(MGR_DEFINE_UNMIGRATED)
|
||||||
#undef MGR_DEFINE_UNMIGRATED
|
#undef MGR_DEFINE_UNMIGRATED
|
||||||
|
|
||||||
// THE TWO COMPUTE SLOTS CARRY b1's DISPATCH HOOK BEFORE THE FATAL, and this is stated
|
// THE TWO COMPUTE SLOTS' class-C stubs are GONE (P5b i1): they carried b1's dispatch
|
||||||
// rather than hidden. MarkGpuWritesForDispatch() belongs immediately before the
|
// hook before their Fatal so that the package flipping them would inherit a call site
|
||||||
// dispatch record, and the dispatch record is class C until i1 lands - so the call site
|
// that was already correct, and EmitDispatchCompute / EmitDispatchComputeIndirect above
|
||||||
// is here, in the right place, and is UNREACHABLE-IN-EFFECT: the abort follows it. The
|
// are that inheritance - same two calls, same order, the record where the Fatal was.
|
||||||
// package that moves DispatchCompute into class B (i1: launch_grid, opcode 60) replaces
|
|
||||||
// the Fatal and inherits a call site that is already correct rather than discovering
|
|
||||||
// that the mark walk was never wired.
|
|
||||||
void DispatchCompute_Unmigrated(GLuint, GLuint, GLuint) {
|
|
||||||
PushPersistentMapsBeforeVerb();
|
|
||||||
MarkGpuWritesForDispatch();
|
|
||||||
UnmigratedVerbFatal("DispatchCompute");
|
|
||||||
}
|
|
||||||
void DispatchComputeIndirect_Unmigrated(GLintptr) {
|
|
||||||
PushPersistentMapsBeforeVerb();
|
|
||||||
MarkGpuWritesForDispatch();
|
|
||||||
UnmigratedVerbFatal("DispatchComputeIndirect");
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetSwapInterval_Unmigrated(Int) { UnmigratedVerbFatal("SetSwapInterval"); }
|
void SetSwapInterval_Unmigrated(Int) { UnmigratedVerbFatal("SetSwapInterval"); }
|
||||||
|
|
||||||
// The counts, as arithmetic. MGR_COUNT_ONE expands to `+ 1` per row.
|
// The counts, as arithmetic. MGR_COUNT_ONE expands to `+ 1` per row.
|
||||||
#define MGR_COUNT_ONE(Name, Ret, Sig) +1
|
#define MGR_COUNT_ONE(Name, Ret, Sig) +1
|
||||||
constexpr Uint32 kUnmigratedD1 = 0 MGR_UNMIGRATED_D1_SLOTS(MGR_COUNT_ONE);
|
constexpr Uint32 kUnmigratedD1 = 0 MGR_UNMIGRATED_D1_SLOTS(MGR_COUNT_ONE);
|
||||||
// + DispatchCompute, DispatchComputeIndirect, written out by hand.
|
// i1 landed: the list is empty and the two hand-written compute stubs are gone with it.
|
||||||
constexpr Uint32 kUnmigratedI1 = 0 MGR_UNMIGRATED_I1_SLOTS(MGR_COUNT_ONE) + 2;
|
constexpr Uint32 kUnmigratedI1 = 0 MGR_UNMIGRATED_I1_SLOTS(MGR_COUNT_ONE);
|
||||||
constexpr Uint32 kUnmigratedT2 = 0 MGR_UNMIGRATED_T2_SLOTS(MGR_COUNT_ONE);
|
constexpr Uint32 kUnmigratedT2 = 0 MGR_UNMIGRATED_T2_SLOTS(MGR_COUNT_ONE);
|
||||||
constexpr Uint32 kUnmigratedF1 = 0 MGR_UNMIGRATED_F1_SLOTS(MGR_COUNT_ONE);
|
constexpr Uint32 kUnmigratedF1 = 0 MGR_UNMIGRATED_F1_SLOTS(MGR_COUNT_ONE);
|
||||||
// + SetSwapInterval, written out by hand.
|
// + SetSwapInterval, written out by hand.
|
||||||
@@ -866,7 +1081,9 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
// The emitted counts, PER OWNER. P5's five are c1's; each P5b package raises its own.
|
// The emitted counts, PER OWNER. P5's five are c1's; each P5b package raises its own.
|
||||||
constexpr Uint32 kEmittedSlotsP5 = 5; // Clear, DrawArrays, ReadPixels, Blit, Present
|
constexpr Uint32 kEmittedSlotsP5 = 5; // Clear, DrawArrays, ReadPixels, Blit, Present
|
||||||
constexpr Uint32 kEmittedSlotsD1 = 0;
|
constexpr Uint32 kEmittedSlotsD1 = 0;
|
||||||
constexpr Uint32 kEmittedSlotsI1 = 0;
|
// P5b i1: BindImageTexture, DispatchCompute, DispatchComputeIndirect, MemoryBarrier,
|
||||||
|
// MemoryBarrierByRegion, CopyImageSubData, ShaderStorageBlockBinding.
|
||||||
|
constexpr Uint32 kEmittedSlotsI1 = 7;
|
||||||
constexpr Uint32 kEmittedSlotsT2 = 0;
|
constexpr Uint32 kEmittedSlotsT2 = 0;
|
||||||
constexpr Uint32 kEmittedSlotsF1 = 11;
|
constexpr Uint32 kEmittedSlotsF1 = 11;
|
||||||
constexpr Uint32 kEmittedSlots =
|
constexpr Uint32 kEmittedSlots =
|
||||||
@@ -899,8 +1116,6 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
MGR_UNMIGRATED_GL_SLOTS(MGR_ASSIGN_UNMIGRATED)
|
MGR_UNMIGRATED_GL_SLOTS(MGR_ASSIGN_UNMIGRATED)
|
||||||
MGR_UNMIGRATED_GL_VALUE_SLOTS(MGR_ASSIGN_UNMIGRATED)
|
MGR_UNMIGRATED_GL_VALUE_SLOTS(MGR_ASSIGN_UNMIGRATED)
|
||||||
#undef MGR_ASSIGN_UNMIGRATED
|
#undef MGR_ASSIGN_UNMIGRATED
|
||||||
table.GL.DispatchCompute = &DispatchCompute_Unmigrated;
|
|
||||||
table.GL.DispatchComputeIndirect = &DispatchComputeIndirect_Unmigrated;
|
|
||||||
table.SetSwapInterval = &SetSwapInterval_Unmigrated;
|
table.SetSwapInterval = &SetSwapInterval_Unmigrated;
|
||||||
|
|
||||||
// ---- class A
|
// ---- class A
|
||||||
@@ -920,6 +1135,7 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
table.GL.BlitFramebuffer = &EmitBlitFramebuffer;
|
table.GL.BlitFramebuffer = &EmitBlitFramebuffer;
|
||||||
table.Present = &EmitPresent;
|
table.Present = &EmitPresent;
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
// ---- f1 ----
|
// ---- f1 ----
|
||||||
table.GL.ClearBufferfi = &EmitClearBufferfi;
|
table.GL.ClearBufferfi = &EmitClearBufferfi;
|
||||||
table.GL.ClearBufferfv = &EmitClearBufferfv;
|
table.GL.ClearBufferfv = &EmitClearBufferfv;
|
||||||
@@ -932,6 +1148,16 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
table.GL.CopyTexImage2D = &EmitCopyTexImage2D;
|
table.GL.CopyTexImage2D = &EmitCopyTexImage2D;
|
||||||
table.GL.CopyTexSubImage2D = &EmitCopyTexSubImage2D;
|
table.GL.CopyTexSubImage2D = &EmitCopyTexSubImage2D;
|
||||||
table.GL.GenerateMipmap = &EmitGenerateMipmap;
|
table.GL.GenerateMipmap = &EmitGenerateMipmap;
|
||||||
|
=======
|
||||||
|
// ---- class B, P5b package i1 (kEmittedSlotsI1 = 7)
|
||||||
|
table.GL.BindImageTexture = &EmitBindImageTexture;
|
||||||
|
table.GL.DispatchCompute = &EmitDispatchCompute;
|
||||||
|
table.GL.DispatchComputeIndirect = &EmitDispatchComputeIndirect;
|
||||||
|
table.GL.MemoryBarrier = &EmitMemoryBarrier;
|
||||||
|
table.GL.MemoryBarrierByRegion = &EmitMemoryBarrierByRegion;
|
||||||
|
table.GL.CopyImageSubData = &EmitCopyImageSubData;
|
||||||
|
table.GL.ShaderStorageBlockBinding = &EmitShaderStorageBlockBinding;
|
||||||
|
>>>>>>> p5b/i1
|
||||||
|
|
||||||
return table;
|
return table;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -357,31 +357,144 @@ namespace MobileGL::MG_Remote::Server {
|
|||||||
// in the same words), the call, and a tally the lane can assert moved.
|
// in the same words), the call, and a tally the lane can assert moved.
|
||||||
// -----------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------
|
||||||
|
|
||||||
// ---- i1 ----
|
// ---- i1 ---- (MG_Remote/CONTRACT-P5B.md §2 i1; landed by package p5b/i1)
|
||||||
|
//
|
||||||
|
// RULE D IN FIVE BODIES. Each reproduces the backend call the monolith makes, from the
|
||||||
|
// record and from server state, and NOTHING ELSE: the backend goes on reading the frontend
|
||||||
|
// fields it reads today through the BARRIER-PULLED entries of its verb class, which the
|
||||||
|
// verb stamp PipeApplier::ApplyOne put up before this sink ran is exactly what makes legal.
|
||||||
|
// That is why none of these touches a backend file and why the monolith path is byte
|
||||||
|
// identical - and it is also the honest statement of the debt, which `rsp` counts.
|
||||||
|
|
||||||
Bool ServerVerbSink::OnLaunchGrid(const MG_Pipe::MGPGridInfo& grid) {
|
Bool ServerVerbSink::OnLaunchGrid(const MG_Pipe::MGPGridInfo& grid) {
|
||||||
(void)grid;
|
const MG_Backend::GlobalBackendFunctionsTable* table = Table("launch_grid");
|
||||||
ServerUnmigratedVerbFatal(grid.IsIndirect ? "DispatchComputeIndirect" : "DispatchCompute");
|
if (table == nullptr) return false;
|
||||||
|
const MG_Backend::GLFunctionsTable& gl = table->GL;
|
||||||
|
// The compute program is NOT named by this record and must not be: it is
|
||||||
|
// GetProgramForDispatch, GetProgramForDraw's twin, which the backend pulls inside its
|
||||||
|
// own PrepareForCompute (DirectGLES.cpp:5779). i1 is what puts compute on the path, so
|
||||||
|
// the field moves FATAL -> BARRIER_PULLED in FieldOwnership.def (contract §6.9, the
|
||||||
|
// one row this package is granted). Block* are 0 on the wire for the same reason: the
|
||||||
|
// local size is a link artifact the backend reads from its own program.
|
||||||
|
if (grid.IsIndirect != 0) {
|
||||||
|
if (gl.DispatchComputeIndirect == nullptr) return false;
|
||||||
|
// IndirectBuffer travels for P7's sake; the BINDING is server state, put there by
|
||||||
|
// the set_buffer_bindings record that preceded this one, exactly as OnClear's Fbo
|
||||||
|
// is not re-resolved here. glDispatchComputeIndirect takes only the offset.
|
||||||
|
gl.DispatchComputeIndirect(static_cast<GLintptr>(grid.IndirectOffset));
|
||||||
|
} else {
|
||||||
|
if (gl.DispatchCompute == nullptr) return false;
|
||||||
|
gl.DispatchCompute(static_cast<GLuint>(grid.GridX), static_cast<GLuint>(grid.GridY),
|
||||||
|
static_cast<GLuint>(grid.GridZ));
|
||||||
|
}
|
||||||
|
++m_dispatches;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool ServerVerbSink::OnMemoryBarrier(const MG_Pipe::MGPMemoryBarrier& barrier) {
|
Bool ServerVerbSink::OnMemoryBarrier(const MG_Pipe::MGPMemoryBarrier& barrier) {
|
||||||
ServerUnmigratedVerbFatal(barrier.ByRegion ? "MemoryBarrierByRegion" : "MemoryBarrier");
|
const MG_Backend::GlobalBackendFunctionsTable* table = Table("memory_barrier");
|
||||||
|
if (table == nullptr) return false;
|
||||||
|
const MG_Backend::GLFunctionsTable& gl = table->GL;
|
||||||
|
// THE BITS GO OVER VERBATIM AND ARE LOWERED HERE BY NOBODY. Espryt's atomic-counter
|
||||||
|
// lowering - the counter bit implying the storage bit, because glslang lowers every
|
||||||
|
// atomic_uint onto a storage block - lives inside its own MemoryBarrier
|
||||||
|
// (DirectGLES.cpp:8837) and is a statement about the DRIVER. Repeating it on this side
|
||||||
|
// would make the split arm and the monolith arm two different calls.
|
||||||
|
if (barrier.ByRegion != 0) {
|
||||||
|
if (gl.MemoryBarrierByRegion == nullptr) return false;
|
||||||
|
gl.MemoryBarrierByRegion(static_cast<GLbitfield>(barrier.Bits));
|
||||||
|
} else {
|
||||||
|
if (gl.MemoryBarrier == nullptr) return false;
|
||||||
|
gl.MemoryBarrier(static_cast<GLbitfield>(barrier.Bits));
|
||||||
|
}
|
||||||
|
++m_memoryBarriers;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool ServerVerbSink::OnResourceCopyRegion(const MG_Pipe::MGPCopyRegion& copy) {
|
Bool ServerVerbSink::OnResourceCopyRegion(const MG_Pipe::MGPCopyRegion& copy) {
|
||||||
(void)copy;
|
const MG_Backend::GlobalBackendFunctionsTable* table = Table("resource_copy_region");
|
||||||
ServerUnmigratedVerbFatal("CopyImageSubData");
|
if (table == nullptr) return false;
|
||||||
|
if (table->GL.CopyImageSubData == nullptr) return false;
|
||||||
|
|
||||||
|
// REFUSED BY NAME ON BOTH SIDES OF ONE WIRE. The client refuses a renderbuffer endpoint
|
||||||
|
// before it emits (ID-57's shape, EmitTables.cpp), and this is the same refusal for a
|
||||||
|
// record that reached here anyway: no sticky forward hands out a RenderbufferObject, so
|
||||||
|
// there is no honest way to build the endpoint, and guessing an empty one would copy
|
||||||
|
// nothing and say it copied.
|
||||||
|
if (copy.SrcTarget == GL_RENDERBUFFER || copy.DstTarget == GL_RENDERBUFFER) {
|
||||||
|
ServerUnmigratedVerbFatal("CopyImageSubData+RENDERBUFFER");
|
||||||
|
}
|
||||||
|
|
||||||
|
// THE TWO ENDPOINTS ARE REBUILT FROM THE GL NAMES, through the BARRIER-PULLED sticky
|
||||||
|
// forward GetTextureObject(name) - `rsp` counts every one of these and P7 is what
|
||||||
|
// retires them by making the backend take the handles that travel beside the names.
|
||||||
|
MG_Backend::CopyImageEndpoint src{};
|
||||||
|
MG_Backend::CopyImageEndpoint dst{};
|
||||||
|
src.Texture = MG_Pipe::gPipeInputs.GetTextureObject(static_cast<Uint>(copy.SrcGlName));
|
||||||
|
dst.Texture = MG_Pipe::gPipeInputs.GetTextureObject(static_cast<Uint>(copy.DstGlName));
|
||||||
|
if (!src.Exists() || !dst.Exists()) {
|
||||||
|
// The monolith's own answer to this, in its own words (DirectGLES.cpp:9067
|
||||||
|
// "source or destination image failed to sync; declining the copy"): the frontend
|
||||||
|
// validator is what keeps it unreachable and what reports the INVALID_VALUE the
|
||||||
|
// application is owed. A decline here is a real answer, not a silent success.
|
||||||
|
MGLOG_E_ONCE("MG_Remote server: resource_copy_region named texture(s) %u -> %u that "
|
||||||
|
"the frontend no longer holds; declining the copy",
|
||||||
|
static_cast<unsigned>(copy.SrcGlName),
|
||||||
|
static_cast<unsigned>(copy.DstGlName));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SrcBox is {origin, extent} and the extent is the copy's, spelled once by GL for both
|
||||||
|
// endpoints; the destination contributes only its origin.
|
||||||
|
table->GL.CopyImageSubData(src, static_cast<GLenum>(copy.SrcTarget),
|
||||||
|
static_cast<GLint>(copy.SrcLevel), copy.SrcBox.X, copy.SrcBox.Y,
|
||||||
|
copy.SrcBox.Z, dst, static_cast<GLenum>(copy.DstTarget),
|
||||||
|
static_cast<GLint>(copy.DstLevel), copy.DstX, copy.DstY,
|
||||||
|
copy.DstZ, static_cast<GLsizei>(copy.SrcBox.W),
|
||||||
|
static_cast<GLsizei>(copy.SrcBox.H),
|
||||||
|
static_cast<GLsizei>(copy.SrcBox.D));
|
||||||
|
++m_imageCopies;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool ServerVerbSink::OnBindShaderImage(const MG_Pipe::MGPImageBind& bind) {
|
Bool ServerVerbSink::OnBindShaderImage(const MG_Pipe::MGPImageBind& bind) {
|
||||||
(void)bind;
|
const MG_Backend::GlobalBackendFunctionsTable* table = Table("bind_shader_image");
|
||||||
ServerUnmigratedVerbFatal("BindImageTexture");
|
if (table == nullptr) return false;
|
||||||
|
if (table->GL.BindImageTexture == nullptr) return false;
|
||||||
|
// THE SAME CALL IS RIGHT FOR BOTH BACKENDS, which is why the record carries the whole
|
||||||
|
// argument list although neither reads all of it today: Espryt ignores everything but
|
||||||
|
// Unit and syncs that unit from the barrier-pulled GetImageTextureBinding
|
||||||
|
// (DirectGLES.cpp:9154, :2471), and Magma's slot is a no-op (DirectVulkan.cpp:665). The
|
||||||
|
// arguments travel because rule D says a verb crosses as the CALL, and because P7 is
|
||||||
|
// what makes the backend read them instead of pulling.
|
||||||
|
table->GL.BindImageTexture(static_cast<GLuint>(bind.Unit), static_cast<GLuint>(bind.GlName),
|
||||||
|
static_cast<GLint>(bind.Level),
|
||||||
|
bind.Layered != 0 ? GL_TRUE : GL_FALSE,
|
||||||
|
static_cast<GLint>(bind.Layer),
|
||||||
|
static_cast<GLenum>(bind.Access),
|
||||||
|
static_cast<GLenum>(bind.Format));
|
||||||
|
++m_imageBinds;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool ServerVerbSink::OnSetStorageBlockBinding(const MG_Pipe::MGPStorageBlockBinding& binding,
|
Bool ServerVerbSink::OnSetStorageBlockBinding(const MG_Pipe::MGPStorageBlockBinding& binding,
|
||||||
const char* name) {
|
const char* name) {
|
||||||
(void)binding;
|
const MG_Backend::GlobalBackendFunctionsTable* table = Table("set_storage_block_binding");
|
||||||
(void)name;
|
if (table == nullptr) return false;
|
||||||
ServerUnmigratedVerbFatal("ShaderStorageBlockBinding");
|
if (table->GL.ShaderStorageBlockBinding == nullptr) return false;
|
||||||
|
if (name == nullptr) return false;
|
||||||
|
// The NAME is the one coordinate the application, the frontend and both backends agree
|
||||||
|
// on (BackendObject.h:216-221), which is why the row carries a blob rather than the
|
||||||
|
// application's block INDEX. `name` points into the decoder's bounded local and is
|
||||||
|
// valid for this call only (rule C); the backend slot copies what it needs.
|
||||||
|
//
|
||||||
|
// Both backends resolve the PROGRAM through the barrier-pulled GetProgramObject(GlName)
|
||||||
|
// / TryGetDirectVulkanProgram - `rsp` again, retired by P9. ShaderCso travels beside the
|
||||||
|
// name for the phase that dispatches on it.
|
||||||
|
table->GL.ShaderStorageBlockBinding(static_cast<GLuint>(binding.GlName), name,
|
||||||
|
static_cast<GLuint>(binding.Binding));
|
||||||
|
++m_storageBlockBindings;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- t2 ----
|
// ---- t2 ----
|
||||||
|
|||||||
@@ -153,6 +153,16 @@ namespace MobileGL::MG_Remote::Server {
|
|||||||
Uint64 Presents() const { return m_presents; }
|
Uint64 Presents() const { return m_presents; }
|
||||||
Uint64 LastPresentSerial() const { return m_lastPresentSerial; }
|
Uint64 LastPresentSerial() const { return m_lastPresentSerial; }
|
||||||
Uint64 ReadbackBytes() const { return m_readbackBytes; }
|
Uint64 ReadbackBytes() const { return m_readbackBytes; }
|
||||||
|
// ---- P5b package i1's tallies. R-16: a probe may not arm against a stub, and on a
|
||||||
|
// split build "the scenario passed" is also what a scenario that never left the
|
||||||
|
// monolith path looks like - so the lane asserts the number that only this sink can
|
||||||
|
// move. One per wire ROW, not per GL slot: launch_grid carries both dispatch entry
|
||||||
|
// points and memory_barrier both barrier ones, and the sink is where they separate.
|
||||||
|
Uint64 ImageBinds() const { return m_imageBinds; }
|
||||||
|
Uint64 Dispatches() const { return m_dispatches; }
|
||||||
|
Uint64 MemoryBarriers() const { return m_memoryBarriers; }
|
||||||
|
Uint64 ImageCopies() const { return m_imageCopies; }
|
||||||
|
Uint64 StorageBlockBindings() const { return m_storageBlockBindings; }
|
||||||
// ID-49's tight-size control reads this: the scratch a read_pixels grew to. It must equal
|
// ID-49's tight-size control reads this: the scratch a read_pixels grew to. It must equal
|
||||||
// the tight w*h*bpp extent of the read, never the client's DstSize - a scratch sized from
|
// the tight w*h*bpp extent of the read, never the client's DstSize - a scratch sized from
|
||||||
// DstSize is exactly the heap overflow codex 1 found, one field over.
|
// DstSize is exactly the heap overflow codex 1 found, one field over.
|
||||||
@@ -169,6 +179,11 @@ namespace MobileGL::MG_Remote::Server {
|
|||||||
Uint64 m_presents = 0;
|
Uint64 m_presents = 0;
|
||||||
Uint64 m_lastPresentSerial = 0;
|
Uint64 m_lastPresentSerial = 0;
|
||||||
Uint64 m_readbackBytes = 0;
|
Uint64 m_readbackBytes = 0;
|
||||||
|
Uint64 m_imageBinds = 0;
|
||||||
|
Uint64 m_dispatches = 0;
|
||||||
|
Uint64 m_memoryBarriers = 0;
|
||||||
|
Uint64 m_imageCopies = 0;
|
||||||
|
Uint64 m_storageBlockBindings = 0;
|
||||||
// ReadPixels' destination. The pixels go into the reply slot, but GLFunctionsTable::
|
// ReadPixels' destination. The pixels go into the reply slot, but GLFunctionsTable::
|
||||||
// ReadPixels writes into a caller buffer, so one staging vector per session sits
|
// ReadPixels writes into a caller buffer, so one staging vector per session sits
|
||||||
// between them. Grown, never shrunk, and never handed out past the call.
|
// between them. Grown, never shrunk, and never handed out past the call.
|
||||||
|
|||||||
@@ -254,13 +254,23 @@ TEST_F(FieldOwnershipTest, TheReducedPathsUnmigratedFieldsAreAllAccountedFor) {
|
|||||||
EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetPixelStoreParameters, 1u),
|
EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetPixelStoreParameters, 1u),
|
||||||
MGPipeFieldOwnership::kFatal);
|
MGPipeFieldOwnership::kFatal);
|
||||||
|
|
||||||
// The three off the reduced path, each for its own checkable reason.
|
// The two off the reduced path, each for its own checkable reason. THREE UNTIL P5b: the
|
||||||
|
// third was GetProgramForDispatch, FATAL because "there is no compute on the reduced path",
|
||||||
|
// and package i1 is what put compute on the path (CONTRACT-P5B.md §6.9). It is asserted
|
||||||
|
// below in its new class rather than deleted from this case, because a field that quietly
|
||||||
|
// left the FATAL list is exactly what this case exists to catch.
|
||||||
EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetBoundTransformFeedbackName),
|
EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetBoundTransformFeedbackName),
|
||||||
MGPipeFieldOwnership::kFatal);
|
MGPipeFieldOwnership::kFatal);
|
||||||
EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetTransformFeedbackPausedPrimitiveCounter),
|
EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetTransformFeedbackPausedPrimitiveCounter),
|
||||||
MGPipeFieldOwnership::kFatal);
|
MGPipeFieldOwnership::kFatal);
|
||||||
|
// P5b i1: launch_grid (60) crosses, the backend's PrepareForCompute pulls the compute
|
||||||
|
// program inside it (DirectGLES.cpp:5779), and the field takes GetProgramForDraw's class
|
||||||
|
// and its retiring phases - so it is a measured DEBT now, not a defect.
|
||||||
EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetProgramForDispatch),
|
EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetProgramForDispatch),
|
||||||
MGPipeFieldOwnership::kFatal);
|
MGPipeFieldOwnership::kBarrierPulled);
|
||||||
|
EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetProgramForDispatch),
|
||||||
|
MGPipeFieldOwnershipOf(MGPipeInputField::GetProgramForDraw))
|
||||||
|
<< "GetProgramForDispatch is GetProgramForDraw's twin and must share its class";
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(FieldOwnershipTest, TheSevenStickyForwardsAgreeWithTheirFieldRows) {
|
TEST_F(FieldOwnershipTest, TheSevenStickyForwardsAgreeWithTheirFieldRows) {
|
||||||
@@ -516,17 +526,43 @@ TEST_F(FieldOwnershipTest, StrictErrorsAlsoPromotesTheStickyForwards) {
|
|||||||
|
|
||||||
// A FATAL-class read aborts whatever the knob says: no carrier, and the reduced path never
|
// A FATAL-class read aborts whatever the knob says: no carrier, and the reduced path never
|
||||||
// reads it, so it is a real defect rather than a debt.
|
// reads it, so it is a real defect rather than a debt.
|
||||||
|
// P5b i1: the exemplar MOVED. This case used GetProgramForDispatch, which is BARRIER-PULLED
|
||||||
|
// from i1 on (a debt the server serves, not an abort), so it would now assert that a served
|
||||||
|
// read aborts - green for the wrong reason at best. GetTransformFeedbackPausedPrimitiveCounter
|
||||||
|
// is the same statement with a field that is still FATAL: reachable only from class kQuery,
|
||||||
|
// which the reduced path never enters.
|
||||||
|
// RED ONCE BY DOING X: put GetProgramForDispatch back in the FATAL block of FieldOwnership.def
|
||||||
|
// and TheFieldOwnershipTableIsTheContractsTableRow's new kBarrierPulled expectation goes red by
|
||||||
|
// name; swap the field below for GetProgramForDispatch and THIS case goes red instead, because
|
||||||
|
// a barrier-pulled read under a stamp does not abort.
|
||||||
TEST_F(FieldOwnershipTest, AFatalClassReadAbortsEvenWithoutStrictErrors) {
|
TEST_F(FieldOwnershipTest, AFatalClassReadAbortsEvenWithoutStrictErrors) {
|
||||||
const ChildResult r = RunInChild([] {
|
const ChildResult r = RunInChild([] {
|
||||||
MGPipeServerStampVerbBoundary(MGPipeVerb::DrawArrays);
|
MGPipeServerStampVerbBoundary(MGPipeVerb::DrawArrays);
|
||||||
(void)gPipeInputs.GetProgramForDispatch();
|
(void)gPipeInputs.GetTransformFeedbackPausedPrimitiveCounter();
|
||||||
});
|
});
|
||||||
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
||||||
EXPECT_NE(r.Log.find("Fatal{UnmigratedPipeInput, \"GetProgramForDispatch@DrawArrays\"}"),
|
EXPECT_NE(r.Log.find("Fatal{UnmigratedPipeInput, "
|
||||||
|
"\"GetTransformFeedbackPausedPrimitiveCounter@DrawArrays\"}"),
|
||||||
std::string::npos)
|
std::string::npos)
|
||||||
<< r.Log;
|
<< r.Log;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// And the field that LEFT the FATAL class is served rather than fatal, under the verb that made
|
||||||
|
// it reachable. This is i1's half of the §6.9 grant made checkable: a dispatch stamp plus a read
|
||||||
|
// of the compute program must NOT abort, which is precisely the statement "compute is on the
|
||||||
|
// path now". RED ONCE BY DOING X: revert the FieldOwnership.def row to FATAL and this child
|
||||||
|
// aborts with Fatal{UnmigratedPipeInput, "GetProgramForDispatch@DispatchCompute"}.
|
||||||
|
TEST_F(FieldOwnershipTest, TheComputeProgramIsServedUnderADispatchStampFromP5bOn) {
|
||||||
|
const ChildResult r = RunInChild([] {
|
||||||
|
MGPipeServerStampVerbBoundary(MGPipeVerb::DispatchCompute);
|
||||||
|
(void)gPipeInputs.GetProgramForDispatch();
|
||||||
|
MGPipeServerClearVerbBoundary();
|
||||||
|
});
|
||||||
|
EXPECT_TRUE(ExitedWith(r, 0)) << DescribeStatus(r) << "\n" << r.Log;
|
||||||
|
EXPECT_EQ(r.Log.find("Fatal{UnmigratedPipeInput, \"GetProgramForDispatch"), std::string::npos)
|
||||||
|
<< r.Log;
|
||||||
|
}
|
||||||
|
|
||||||
// The argument-keyed row: the pack half is answerable and the unpack half is not, and the
|
// The argument-keyed row: the pack half is answerable and the unpack half is not, and the
|
||||||
// difference is the accessor's own argument rather than a second field id. Splitting the field
|
// difference is the accessor's own argument rather than a second field id. Splitting the field
|
||||||
// into two ids would have made the unpack half BARRIER-PULLED - silently served - which is
|
// into two ids would have made the unpack half BARRIER-PULLED - silently served - which is
|
||||||
|
|||||||
@@ -171,12 +171,21 @@ namespace {
|
|||||||
// =====================================================================================
|
// =====================================================================================
|
||||||
|
|
||||||
TEST(RemoteEmitTable, TheThreeClassesPartitionAllSeventyOneSlots) {
|
TEST(RemoteEmitTable, TheThreeClassesPartitionAllSeventyOneSlots) {
|
||||||
// CONTRACT-P5.md §7: 2 answered locally + 5 emitted + 64 Fatal. Read from the functions the
|
// CONTRACT-P5.md §7: 2 answered locally + 5 emitted + 64 Fatal at the P5b contract commit.
|
||||||
// table itself reports with - which is also what t1's arming condition reads - rather than
|
// Read from the functions the table itself reports with - which is also what t1's arming
|
||||||
// recomputed here, so a table that lost an emitter cannot look like one that never had it.
|
// condition reads - rather than recomputed here, so a table that lost an emitter cannot look
|
||||||
|
// like one that never had it.
|
||||||
|
//
|
||||||
|
// P5b package i1 (CONTRACT-P5B.md §2 i1) flipped SEVEN slots C -> B: BindImageTexture,
|
||||||
|
// DispatchCompute, DispatchComputeIndirect, MemoryBarrier, MemoryBarrierByRegion,
|
||||||
|
// CopyImageSubData, ShaderStorageBlockBinding. So the two numbers that move are 5 -> 12 and
|
||||||
|
// 64 -> 57, and the SUM below is the invariant that does not move whichever package lands
|
||||||
|
// next. RED ONCE BY DOING X: comment out `table.GL.MemoryBarrier = &EmitMemoryBarrier;` in
|
||||||
|
// BuildRemoteEmitTable and this case stays green while NoSlotIsNull goes red - which is why
|
||||||
|
// the per-package count is asserted here and the null walk is a separate case.
|
||||||
EXPECT_EQ(LocallyAnsweredSlotCount(), 2u);
|
EXPECT_EQ(LocallyAnsweredSlotCount(), 2u);
|
||||||
EXPECT_EQ(ImplementedVerbCount(), 16u);
|
EXPECT_EQ(ImplementedVerbCount(), 23u);
|
||||||
EXPECT_EQ(UnmigratedSlotCount(), 53u);
|
EXPECT_EQ(UnmigratedSlotCount(), 46u);
|
||||||
EXPECT_EQ(LocallyAnsweredSlotCount() + ImplementedVerbCount() + UnmigratedSlotCount(),
|
EXPECT_EQ(LocallyAnsweredSlotCount() + ImplementedVerbCount() + UnmigratedSlotCount(),
|
||||||
kRemoteEmitSlotCount);
|
kRemoteEmitSlotCount);
|
||||||
}
|
}
|
||||||
@@ -257,6 +266,49 @@ TEST(RemoteEmitTable, SetSwapIntervalIsClassCAndSaysSo) {
|
|||||||
EXPECT_NE(r.Log.find("Fatal{UnmigratedVerb, \"SetSwapInterval\"}"), std::string::npos) << r.Log;
|
EXPECT_NE(r.Log.find("Fatal{UnmigratedVerb, \"SetSwapInterval\"}"), std::string::npos) << r.Log;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif // MGTEST_HAVE_FORK
|
||||||
|
|
||||||
|
// ---- P5b package i1 (MG_Remote/CONTRACT-P5B.md §2 i1) -------------------------------------
|
||||||
|
|
||||||
|
TEST(RemoteEmitTable, TheSevenI1SlotsAreClassBAndAreNotTheFatalThunk) {
|
||||||
|
// The census's five measured slots plus the two companions that share their rows. Named
|
||||||
|
// rather than counted, so a table that flipped a DIFFERENT seven is red here and not only
|
||||||
|
// in the arithmetic. The comparison is against a slot that is still class C: a flipped slot
|
||||||
|
// and an unflipped one cannot be the same pointer, which is what a forgotten class-B
|
||||||
|
// assignment would look like (class C is assigned FIRST in BuildRemoteEmitTable precisely so
|
||||||
|
// that the mistake is loud rather than null).
|
||||||
|
const MG_Backend::GlobalBackendFunctionsTable& table = RemoteEmitTable();
|
||||||
|
const void* fatal = reinterpret_cast<const void*>(table.GL.GetTexImage); // wave-3 tail, class C
|
||||||
|
ASSERT_NE(fatal, nullptr);
|
||||||
|
const void* const i1[] = {
|
||||||
|
reinterpret_cast<const void*>(table.GL.BindImageTexture),
|
||||||
|
reinterpret_cast<const void*>(table.GL.DispatchCompute),
|
||||||
|
reinterpret_cast<const void*>(table.GL.DispatchComputeIndirect),
|
||||||
|
reinterpret_cast<const void*>(table.GL.MemoryBarrier),
|
||||||
|
reinterpret_cast<const void*>(table.GL.MemoryBarrierByRegion),
|
||||||
|
reinterpret_cast<const void*>(table.GL.CopyImageSubData),
|
||||||
|
reinterpret_cast<const void*>(table.GL.ShaderStorageBlockBinding),
|
||||||
|
};
|
||||||
|
static const char* const kNames[] = {"BindImageTexture", "DispatchCompute",
|
||||||
|
"DispatchComputeIndirect", "MemoryBarrier",
|
||||||
|
"MemoryBarrierByRegion", "CopyImageSubData",
|
||||||
|
"ShaderStorageBlockBinding"};
|
||||||
|
for (SizeT i = 0; i < sizeof(i1) / sizeof(i1[0]); ++i) {
|
||||||
|
EXPECT_NE(i1[i], nullptr) << kNames[i] << " is null";
|
||||||
|
EXPECT_NE(i1[i], fatal) << kNames[i]
|
||||||
|
<< " still points at an UnmigratedVerbFatal thunk; i1 flipped it "
|
||||||
|
"to class B";
|
||||||
|
}
|
||||||
|
// The two barrier slots and the two dispatch slots share a WIRE ROW but not an emitter: the
|
||||||
|
// discriminant (ByRegion / IsIndirect) is set by the emitter, so one thunk for both would
|
||||||
|
// carry the wrong one.
|
||||||
|
EXPECT_NE(i1[3], i1[4]) << "MemoryBarrier and MemoryBarrierByRegion share memory_barrier (61) "
|
||||||
|
"but must set opposite ByRegion values";
|
||||||
|
EXPECT_NE(i1[1], i1[2]) << "DispatchCompute and DispatchComputeIndirect share launch_grid (60) "
|
||||||
|
"but must set opposite IsIndirect values";
|
||||||
|
}
|
||||||
|
|
||||||
|
#if MGTEST_HAVE_FORK
|
||||||
TEST(RemoteEmitTable, AClassBSlotWithNoSessionAbortsRatherThanFallingThrough) {
|
TEST(RemoteEmitTable, AClassBSlotWithNoSessionAbortsRatherThanFallingThrough) {
|
||||||
// The other half of "no slot may fall through to the driver". With no ClientSession the
|
// The other half of "no slot may fall through to the driver". With no ClientSession the
|
||||||
// emitter has nowhere to put the record, and the one thing it may not do is return quietly:
|
// emitter has nowhere to put the record, and the one thing it may not do is return quietly:
|
||||||
@@ -265,6 +317,53 @@ TEST(RemoteEmitTable, AClassBSlotWithNoSessionAbortsRatherThanFallingThrough) {
|
|||||||
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
||||||
EXPECT_NE(r.Log.find("Fatal{NoClientSession, \"Clear\"}"), std::string::npos) << r.Log;
|
EXPECT_NE(r.Log.find("Fatal{NoClientSession, \"Clear\"}"), std::string::npos) << r.Log;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(RemoteEmitTable, EachI1SlotReachesRequireSessionUnderItsOwnName) {
|
||||||
|
// The behavioural half: an i1 slot is class B, so with no ClientSession it reaches
|
||||||
|
// RequireSession and aborts Fatal{NoClientSession, "<slot>"} - NOT Fatal{UnmigratedVerb}
|
||||||
|
// (which would mean the flip never happened) and NOT quietly (which is the split lane
|
||||||
|
// running monolith and going green, R-4). The name in the message is the slot's own, which
|
||||||
|
// is the half a single case could not state.
|
||||||
|
//
|
||||||
|
// RED ONCE BY DOING X: put `X(MemoryBarrier, void, (GLbitfield))` back in
|
||||||
|
// MGR_UNMIGRATED_I1_SLOTS and drop `table.GL.MemoryBarrier = &EmitMemoryBarrier;` - the
|
||||||
|
// MemoryBarrier arm below then finds Fatal{UnmigratedVerb, "MemoryBarrier"} instead.
|
||||||
|
const ChildResult barrier = RunInChild([] { RemoteEmitTable().GL.MemoryBarrier(0x2000); });
|
||||||
|
ASSERT_TRUE(DiedOfAbort(barrier)) << DescribeStatus(barrier) << "\n" << barrier.Log;
|
||||||
|
EXPECT_NE(barrier.Log.find("Fatal{NoClientSession, \"MemoryBarrier\"}"), std::string::npos)
|
||||||
|
<< barrier.Log;
|
||||||
|
EXPECT_EQ(barrier.Log.find("Fatal{UnmigratedVerb"), std::string::npos)
|
||||||
|
<< "MemoryBarrier is class B from P5b i1 on:\n"
|
||||||
|
<< barrier.Log;
|
||||||
|
|
||||||
|
const ChildResult dispatch = RunInChild([] { RemoteEmitTable().GL.DispatchCompute(1, 1, 1); });
|
||||||
|
ASSERT_TRUE(DiedOfAbort(dispatch)) << DescribeStatus(dispatch) << "\n" << dispatch.Log;
|
||||||
|
EXPECT_NE(dispatch.Log.find("Fatal{NoClientSession, \"DispatchCompute\"}"), std::string::npos)
|
||||||
|
<< dispatch.Log;
|
||||||
|
|
||||||
|
const ChildResult bind = RunInChild(
|
||||||
|
[] { RemoteEmitTable().GL.BindImageTexture(0, 1, 0, GL_FALSE, 0, 0x88BA, 0x8058); });
|
||||||
|
ASSERT_TRUE(DiedOfAbort(bind)) << DescribeStatus(bind) << "\n" << bind.Log;
|
||||||
|
EXPECT_NE(bind.Log.find("Fatal{NoClientSession, \"BindImageTexture\"}"), std::string::npos)
|
||||||
|
<< bind.Log;
|
||||||
|
|
||||||
|
const ChildResult ssbo = RunInChild(
|
||||||
|
[] { RemoteEmitTable().GL.ShaderStorageBlockBinding(1, "Blk", 2); });
|
||||||
|
ASSERT_TRUE(DiedOfAbort(ssbo)) << DescribeStatus(ssbo) << "\n" << ssbo.Log;
|
||||||
|
EXPECT_NE(ssbo.Log.find("Fatal{NoClientSession, \"ShaderStorageBlockBinding\"}"),
|
||||||
|
std::string::npos)
|
||||||
|
<< ssbo.Log;
|
||||||
|
|
||||||
|
const ChildResult copy = RunInChild([] {
|
||||||
|
const MG_Backend::CopyImageEndpoint src{};
|
||||||
|
const MG_Backend::CopyImageEndpoint dst{};
|
||||||
|
RemoteEmitTable().GL.CopyImageSubData(src, 0x0DE1, 0, 0, 0, 0, dst, 0x0DE1, 0, 0, 0, 0, 1,
|
||||||
|
1, 1);
|
||||||
|
});
|
||||||
|
ASSERT_TRUE(DiedOfAbort(copy)) << DescribeStatus(copy) << "\n" << copy.Log;
|
||||||
|
EXPECT_NE(copy.Log.find("Fatal{NoClientSession, \"CopyImageSubData\"}"), std::string::npos)
|
||||||
|
<< copy.Log;
|
||||||
|
}
|
||||||
#endif // MGTEST_HAVE_FORK
|
#endif // MGTEST_HAVE_FORK
|
||||||
|
|
||||||
// =====================================================================================
|
// =====================================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user