[Fix, Test] (clientsp, MG_Pipe, Espryt): produce kMGPipeBindSampler and kMGPipeBindShaderImage where D-A4 places them - nothing set either bit, so ImageBindableHint was always 0, the metadata respecify had no live trigger and the remint pull the hint prevents was neither prevented nor counted (final review M-A); the sampler-view resolution notes SAMPLER, glBindImageTexture's state setter notes SHADER_IMAGE at the bind (so the hint precedes the first sync) and the image walk notes it too, both through a contract door since neither may include TextureEmit.h, and Espryt counts every re-mint of storage it already held as tex-remint-pulls (trp= on the stats line, ROADMAP open question 2's number)

This commit is contained in:
rereview
2026-09-09 00:09:30 -04:00
parent a690032f85
commit 9f60aadc1d
14 changed files with 366 additions and 5 deletions
@@ -5252,6 +5252,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (m_imageBindableStorageRequired) {
return;
}
#if MOBILEGL_PIPE_PUSH
// Whether this transition re-mints storage that ALREADY EXISTED on the backend: that
// is the remint PULL (the levels below are replayed from the client's shadow to fill
// the new carrier), and it is what ROADMAP open question 2 counts. A texture reaching
// here uninitialised is allocated image-bindable up front and pulls nothing.
const Bool hadBackendStorage = m_isInitialized;
#endif
m_imageBindableStorageRequired = true;
m_isInitialized = false;
// Every level this object has ALREADY uploaded has to be replayed, because the
@@ -5326,6 +5333,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (!markedRemintPull) {
MG_Pipe::MGPipeUnmigratedEmulation("texture-remint-pull");
markedRemintPull = true;
// THE COUNTER BEHIND ROADMAP OPEN QUESTION 2 (final review M-A): one per
// transition that replays a level of storage the backend already held.
if (hadBackendStorage && MG_Util::PipeStats::Enabled()) {
MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::TextureRemintPulls, 1);
}
}
if (!MG_Pipe::MGPipeHandleIsNull(rearmRes)) {
const MG_Pipe::MGPBox wholeLevel{0,
+8
View File
@@ -38,6 +38,7 @@
#include <MG_Impl/Pipe/SlotAllocator.h>
#include <MG_Pipe/MGPipe.h>
#include <MG_Pipe/PipeApply.h>
#include <MG_Pipe/PipeMutation.h>
#include <MG_State/GLState/Core.h>
#include <MG_State/GLState/TextureState/TextureState.h>
#include <MG_Util/Metrics/PipeStats.h>
@@ -97,6 +98,13 @@ namespace MobileGL::MG_Pipe {
entry.Res = binding.Texture ? MGPipeSlots().Acquire(MGPipeKind::Texture,
binding.Texture->GetLifetimeId())
: kMGPipeNullHandle;
// D-A4: a texture named in an emitted MGPImageView is SHADER-IMAGE-bound from
// then on - the bit ImageBindableHint is derived from. The bind itself noted it
// first (TextureState.h, so the hint precedes the first sync); this is the
// letter of the rule and a one-compare early-out once the bit is set.
if (!MGPipeHandleIsNull(entry.Res)) {
MGPipeNoteTextureBoundAs(entry.Res, static_cast<Uint32>(kMGPipeBindShaderImage));
}
// THE APPLICATION's format and access, verbatim. The bind-format recast and the
// buffer-texture split view are server-side and stay there; so does
// SupportsLayeredImageBinding's rule, which asks the BACKEND target after
+15
View File
@@ -1290,6 +1290,21 @@ namespace MobileGL::MG_Pipe {
[&](auto& emitter) { emitter.EmitRenderbufferRespecify(renderbuffer); });
}
void MGPipeNoteTextureBoundAs(MGPipeHandle texture, Uint32 bindBit) {
// Not gated on FamilyIsLive: the mask is client state (see the declaration), and the
// emitter gates the emission it causes.
ForwardWhenWired<kMGPipeWiredTextureSubsystem>(
MGPipeTextureEmitterInstance(),
[&](auto& emitter) { emitter.NoteTextureBoundAs(texture, static_cast<Uint16>(bindBit)); });
}
void MGPipeNoteTextureImageBound(ITextureObject& texture) {
ForwardWhenWired<kMGPipeWiredTextureSubsystem>(MGPipeTextureEmitterInstance(), [&](auto& emitter) {
emitter.NoteTextureBoundAs(emitter.AcquireTexture(texture.GetLifetimeId(), &texture),
static_cast<Uint16>(kMGPipeBindShaderImage));
});
}
void MGPipeEmitSamplerCsoCreate(SamplerObject& sampler) {
if (!FamilyIsLive(kMGPipeSubsystemSamplers, kMGPipeWiredSamplerSubsystem)) return;
ForwardWhenWired<kMGPipeWiredSamplerSubsystem>(
+5
View File
@@ -767,6 +767,11 @@ namespace MobileGL::MG_Pipe {
if (MG_State::GLState::SamplesAsIncompleteTexture(texture.get(), effective)) continue;
entry.Texture = MGPipeSlots().Acquire(MGPipeKind::Texture, texture->GetLifetimeId());
// D-A4: a texture the sampler-view resolution names in an emitted MGPBoundView
// is SAMPLER-bound from then on (sticky; the texture emitter's contract door,
// since this header is included BY TextureEmit.h). One early-out per unit per
// pass once the bit is set.
MGPipeNoteTextureBoundAs(entry.Texture, static_cast<Uint32>(kMGPipeBindSampler));
entry.View = AcquireSamplerView(*texture, entry.Texture, bytes);
}
+5 -2
View File
@@ -519,8 +519,11 @@ namespace MobileGL::MG_Pipe {
// ORed, never cleared, and emitted on BOTH resource_create and every
// resource_respecify, exactly as P3a's buffer mask is. The four bits nothing set before
// P4a get their producers here and in the framebuffer emitter: RENDER_TARGET and
// DEPTH_STENCIL from an attachment point, SAMPLER from a resolved sampler view and
// SHADER_IMAGE from a resolved image unit (the sampler package's two).
// DEPTH_STENCIL from an attachment point (FramebufferEmit.h), SAMPLER from a resolved
// sampler view (SamplerEmit.h) and SHADER_IMAGE from glBindImageTexture's state setter
// and the resolved image unit (TextureState.h, ImageEmit.h) - the last two through the
// contract's MGPipeNoteTextureBoundAs door, since neither may include this header
// (final review M-A: before the fix round nothing produced them and the hint was dead).
// A MASK CHANGE AFTER THE ALLOCATION IS A METADATA RESPECIFY (ID-18 M4), and without it
// the sticky half of D-A4 is a no-op for exactly the textures it was written for. The
// mask rides resource_create and every resource_respecify - and an IMMUTABLE texture has
@@ -13,6 +13,7 @@
#if MOBILEGL_PIPE_PUSH
#include <MG_Pipe/MGPipeTypes.h>
#include <MG_Pipe/PipeApply.h>
#include <MG_Util/Metrics/PipeStats.h>
#define MGITEST_P4A_FINALFIX_PEEK_LIVE 1
#endif
#endif
@@ -45,8 +46,26 @@ namespace MGITest {
}
return false;
}
bool PeekPipeStatsTextureRemintPulls(unsigned long long* out) {
if (out == nullptr) return false;
namespace Stats = MobileGL::MG_Util::PipeStats;
if (!Stats::Enabled()) Stats::SetEnabledForTesting(true);
*out = static_cast<unsigned long long>(Stats::TotalCalls(Stats::CallClass::TextureRemintPulls));
return true;
}
bool PeekPipeStatsTextureUploadEmissions(unsigned long long* out) {
if (out == nullptr) return false;
namespace Stats = MobileGL::MG_Util::PipeStats;
if (!Stats::Enabled()) Stats::SetEnabledForTesting(true);
*out = static_cast<unsigned long long>(Stats::TotalCalls(Stats::CallClass::TextureUploadEmissions));
return true;
}
#else
bool PeekPipeTextureResourceRecord(unsigned, PipeTextureResourceRecordPeek*) { return false; }
bool PeekPipeStatsTextureRemintPulls(unsigned long long*) { return false; }
bool PeekPipeStatsTextureUploadEmissions(unsigned long long*) { return false; }
#endif
} // namespace MGITest
@@ -29,4 +29,13 @@ namespace MGITest {
};
bool PeekPipeTextureResourceRecord(unsigned glTextureName, PipeTextureResourceRecordPeek* out);
// The process-wide texture-remint pull count (PipeStats "tex-remint-pulls", `trp=` on the
// summary line; ROADMAP open question 2). Arms the PipeStats counters for this process on
// the first call, which is what lets a case read the number without a stats-enabled lane.
bool PeekPipeStatsTextureRemintPulls(unsigned long long* out);
// Espryt's count of texture uploads it actually issued (PipeStats "tex-upload-emissions"):
// what tells a CONSUMED pending upload apart from a DROPPED one, since the record's set is
// empty either way. Arms the counters the same way.
bool PeekPipeStatsTextureUploadEmissions(unsigned long long* out);
} // namespace MGITest
@@ -487,5 +487,172 @@ void main() { oColor = texture(uTex, vUv); }
glDeleteTextures(1, &cleanup);
}
// ======================================================================================
// M-A: an image bind after the allocation is a metadata respecify with the hint set
// ======================================================================================
// glTexStorage2D (immutable: no later respecify to ride), a red upload consumed by a draw,
// then a blue upload drained by a verb the texture is not reached by (accepted, standing
// in the applier's pending set), then glBindImageTexture. The bind must reach the record
// as a metadata update - ImageBindableHint 1, the pending upload still standing - and the
// draw after it must show the blue that upload carried through the widened carrier the
// hint schedules.
TEST_F(P4aFinalFixScenario, AnImageBindAfterAllocationReachesTheApplierAsAMetadataRespecify) {
if (!Ready()) return;
SkipUnlessEspryt("M-A's image-bindable hint");
if (IsSkipped()) return;
GLint maxImageUnits = 0;
glGetIntegerv(GL_MAX_IMAGE_UNITS, &maxImageUnits);
while (glGetError() != GL_NO_ERROR) {
}
if (maxImageUnits < 1) {
GTEST_SKIP() << "no image units";
return;
}
// THE NUMBER ROADMAP OPEN QUESTION 2 ASKS FOR: a texture Espryt allocated BEFORE the
// hint reached it is re-minted image-bindable at the bind and its levels replayed
// from the client's shadow - one remint pull, counted. Arming the counter here is
// what makes it readable without a stats-enabled lane.
unsigned long long pullsBefore = 0;
const bool pullsReadable = PeekPipeStatsTextureRemintPulls(&pullsBefore);
GLuint texture = 0;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 4, 4);
const std::vector<std::uint8_t> red = Solid(4, 255, 0, 0);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, red.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glBindTexture(GL_TEXTURE_2D, 0);
const Image before = DrawSampled(texture); // allocated and consumed, NOT image-bindable
EXPECT_TRUE(Mostly(before, "red", "the immutable texture before the image bind"));
PipeTextureResourceRecordPeek record{};
const bool readable = RecordIsReadable(texture, "M-A's image-bindable hint", &record);
if (readable) {
EXPECT_EQ(record.ImageBindableHint, 0u) << "nothing has image-bound this texture yet";
EXPECT_EQ(record.PendingUploads, 0u) << "the red upload was consumed by the draw";
}
// A blue upload, drained by a verb that does not reach T: accepted, unconsumed.
const std::vector<std::uint8_t> blue = Solid(4, 0, 0, 255);
glBindTexture(GL_TEXTURE_2D, texture);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, blue.data());
glBindTexture(GL_TEXTURE_2D, 0);
const Image unrelated = DrawSampled(m_other);
EXPECT_TRUE(Mostly(unrelated, "white", "the unrelated draw"));
if (readable) {
ASSERT_TRUE(PeekPipeTextureResourceRecord(texture, &record));
EXPECT_EQ(record.PendingUploads, 1u) << "the blue upload was not drained into the applier";
}
const unsigned long long serialBeforeBind = record.Serial;
unsigned long long uploadsBeforeBind = 0;
const bool uploadsReadable = PeekPipeStatsTextureUploadEmissions(&uploadsBeforeBind);
// THE TRANSITION. An immutable texture has no storage-defining respecify left, so the
// hint can only arrive as a metadata update (ID-18 M4). Espryt syncs the texture
// eagerly inside glBindImageTexture and the widening re-mints its storage, replaying
// every defined level from the shadow (the remint pull the counter below counts), so
// the standing upload is consumed by that regeneration here and the picture that
// follows is blue whatever the metadata respecify did to the record - the KEPT
// property is proved further down, on a texture no remint stands in front of.
(void)uploadsBeforeBind;
(void)uploadsReadable;
glBindImageTexture(0, texture, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8);
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
if (readable) {
ASSERT_TRUE(PeekPipeTextureResourceRecord(texture, &record));
EXPECT_EQ(record.ImageBindableHint, 1u)
<< "glBindImageTexture did not reach the applier's record as ImageBindableHint";
EXPECT_NE(record.BindMask & (1u << 6), 0u) << "kMGPipeBindShaderImage was not produced";
EXPECT_GT(record.Serial, serialBeforeBind) << "the metadata respecify moved no serial";
}
const Image image = DrawSampled(texture);
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
Report("AnImageBindAfterAllocationReachesTheApplierAsAMetadataRespecify", image);
EXPECT_TRUE(Mostly(image, "blue", "the texture after the image bind that followed an unconsumed upload"));
glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8);
unsigned long long pullsAfter = 0;
if (pullsReadable && readable && PeekPipeStatsTextureRemintPulls(&pullsAfter)) {
EXPECT_EQ(pullsAfter, pullsBefore + 1)
<< "the re-mint of a texture allocated before its hint was not counted as a remint pull "
"(trp= on the stats line is ROADMAP open question 2's number)";
}
// THE PREVENTION HALF, measured the other way round: a texture whose hint arrives at
// the bind, BEFORE its first sync, is allocated image-bindable up front and pulls
// nothing - the counter does not move.
GLuint early = 0;
glGenTextures(1, &early);
glBindTexture(GL_TEXTURE_2D, early);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 4, 4);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, red.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glBindTexture(GL_TEXTURE_2D, 0);
glBindImageTexture(0, early, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8); // before any sync
const Image earlyImage = DrawSampled(early);
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
EXPECT_TRUE(Mostly(earlyImage, "red", "a texture image-bound before its first sync"));
glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8);
unsigned long long pullsEarly = 0;
if (pullsReadable && readable && PeekPipeStatsTextureRemintPulls(&pullsEarly)) {
EXPECT_EQ(pullsEarly, pullsAfter)
<< "a texture whose hint preceded its first sync was still re-minted (the prevention "
"half of the hint did not fire)";
}
// THE METADATA RESPECIFY KEEPS A STANDING UPLOAD, end to end and with no remint in the
// way: `early` is image-bindable already, so a NEW sticky bit reaching it - the
// RENDER_TARGET bit a DSA attachment produces at its setter (a Named record, ID-19(c)),
// with no sync of the texture in between - is a pure metadata update. The blue upload
// drained before it must still stand in the record afterwards (or, if a sync did run,
// have been uploaded rather than dropped) and reach the driver at the next draw.
glBindTexture(GL_TEXTURE_2D, early);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, blue.data());
glBindTexture(GL_TEXTURE_2D, 0);
const Image unrelatedAgain = DrawSampled(m_other);
EXPECT_TRUE(Mostly(unrelatedAgain, "white", "the unrelated draw"));
PipeTextureResourceRecordPeek earlyRecord{};
const bool earlyReadable = PeekPipeTextureResourceRecord(early, &earlyRecord);
if (earlyReadable) {
EXPECT_EQ(earlyRecord.PendingUploads, 1u) << "the blue upload was not drained into the applier";
}
const unsigned long long earlySerialBefore = earlyRecord.Serial;
unsigned long long uploadsBeforeAttach = 0;
const bool uploadsCounted = PeekPipeStatsTextureUploadEmissions(&uploadsBeforeAttach);
GLuint namedFbo = 0;
glCreateFramebuffers(1, &namedFbo);
glNamedFramebufferTexture(namedFbo, GL_COLOR_ATTACHMENT0, early, 0);
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
if (earlyReadable) {
ASSERT_TRUE(PeekPipeTextureResourceRecord(early, &earlyRecord));
EXPECT_NE(earlyRecord.BindMask & (1u << 7), 0u)
<< "the DSA attachment did not produce kMGPipeBindRenderTarget";
EXPECT_GT(earlyRecord.Serial, earlySerialBefore) << "the mask move reached the record as no respecify";
unsigned long long uploadsAfterAttach = 0;
if (earlyRecord.PendingUploads == 0 && uploadsCounted &&
PeekPipeStatsTextureUploadEmissions(&uploadsAfterAttach)) {
EXPECT_GT(uploadsAfterAttach, uploadsBeforeAttach)
<< "the standing upload vanished from the record without Espryt uploading anything: "
"the metadata respecify dropped it";
} else {
EXPECT_EQ(earlyRecord.PendingUploads, 1u)
<< "the metadata respecify dropped the pending upload standing beside it";
}
}
const Image earlyAfter = DrawSampled(early);
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
EXPECT_TRUE(Mostly(earlyAfter, "blue", "the upload that stood across a metadata respecify"));
glDeleteFramebuffers(1, &namedFbo);
GLuint cleanup = texture;
glDeleteTextures(1, &cleanup);
GLuint cleanupEarly = early;
glDeleteTextures(1, &cleanupEarly);
}
} // namespace
} // namespace MGITest
+23
View File
@@ -361,6 +361,29 @@ namespace MobileGL::MG_Pipe {
void MGPipeEmitRenderbufferResourceCreate(MG_State::GLState::RenderbufferObject& renderbuffer);
void MGPipeEmitRenderbufferResourceRespecify(MG_State::GLState::RenderbufferObject& renderbuffer);
// ---- D-A4's two sticky bind-mask producers (P4a final review M-A) ----
//
// kMGPipeBindSampler is "any texture the sampler-view resolution names in an emitted
// MGPBoundView" and kMGPipeBindShaderImage "any texture named in an emitted MGPImageView"
// - both the SAMPLER package's emitters (SamplerEmit.h, ImageEmit.h), which the texture
// emitter's header includes and which therefore cannot include it back - and, earliest of
// all, glBindImageTexture's state setter (TextureState.h, MG_State), which may include no
// emit header at all. So the note goes through this door, exactly as the birth hooks do.
// Nothing produced either bit before the fix round: ImageBindableHint was always 0, the
// metadata respecify (ID-18 M4) had no live trigger, and the remint pull the hint exists to
// prevent was neither prevented nor counted.
//
// UNCONDITIONAL IN A PUSH BUILD, like the mints: the mask is CLIENT state the framebuffer
// emitter ORs into whether or not the texture family is on, and the emission a mask move
// causes (the metadata respecify) is gated inside the emitter on the family's own pair.
void MGPipeNoteTextureBoundAs(MGPipeHandle texture, Uint32 bindBit);
// glBindImageTexture. The hint is the PREVENTION half of the texture-remint stall class -
// a texture the server knows may be image-bound is allocated image-bindable up front - so it
// has to reach the applier before the texture's first sync, i.e. at the bind itself, not at
// the validate point's image walk (which notes it as well, D-A4's letter).
void MGPipeNoteTextureImageBound(MG_State::GLState::ITextureObject& texture);
// ---- sampler CSOs and sampler views: MG_Impl/Pipe/SamplerEmit.h, package C ----
//
// Entry points MGPipeSamplerEmitter must provide, returning void:
@@ -33,6 +33,15 @@ namespace MobileGL::MG_State::GLState {
Access = access;
Format = format;
++Version;
#if MOBILEGL_PIPE_PUSH
// P4a D-A4 / final review M-A: the EARLIEST producer of kMGPipeBindShaderImage. The
// ImageBindableHint the bit feeds is the prevention half of the texture-remint stall
// class (a texture the server knows may be image-bound is allocated image-bindable
// up front), so it has to reach the applier before the texture's first sync - at
// the bind, not at the next validate point's image walk. Push-only through the
// contract's door, like every other hook in this directory (G1).
if (Texture) MG_Pipe::MGPipeNoteTextureImageBound(*Texture);
#endif
}
};
+40 -1
View File
@@ -48,6 +48,7 @@
#include <MG_Impl/GLImpl/Program/GL_Program.h>
#include <MG_Impl/GLImpl/Texture/GL_Texture.h>
#include <MG_Impl/Pipe/ImageEmit.h>
#include <MG_Impl/Pipe/TextureEmit.h>
#include <MG_Impl/Pipe/SetHashSuppressor.h>
#include <MG_Impl/Pipe/SlotAllocator.h>
#include <MG_Pipe/PipeApply.h>
@@ -347,7 +348,8 @@ TEST(ImageEmit, AMakeCurrentClearsTheImageSetAndAdvancesItsSerial) {
X(ImageEmit, AZeroHighWaterMarkEmitsNothingWithoutHashing) \
X(ImageEmit, AnAccessModeChangeAloneStillEmitsTheSet) \
X(ImageEmit, AnInternalFormatChangeAloneStillEmitsTheSet) \
X(ImageEmit, TheApplicationsFormatAndAccessTravelUnrecast)
X(ImageEmit, TheApplicationsFormatAndAccessTravelUnrecast) \
X(ImageEmit, AnImageBoundTextureIsMarkedShaderImageBoundAtTheBind)
#define MGL_DECLARE_PULL_SKIP(Suite, Name) \
TEST(Suite, Name) { GTEST_SKIP() << "compiled only under MOBILEGL_PIPE_PUSH"; }
@@ -516,6 +518,43 @@ void main() { imageStore(img, ivec2(0), vec4(1.0)); }
EXPECT_EQ(Emitter().LastImageViews()[1].InternalFormat, static_cast<Uint32>(GL_RGBA8UI));
GL::UseProgram(0);
}
// FINAL REVIEW M-A: glBindImageTexture IS THE EARLIEST PRODUCER OF kMGPipeBindShaderImage -
// the bit the ImageBindableHint is derived from - and the emitted image set's walk is D-A4's
// (any texture named in an emitted MGPImageView). The hint is the PREVENTION half of the
// texture-remint stall class: a texture the server knows may be image-bound is allocated
// image-bindable up front, so it has to arrive before the first sync, i.e. at the bind.
// Nothing produced the bit before the fix round.
TEST(ImageEmit, AnImageBoundTextureIsMarkedShaderImageBoundAtTheBind) {
EmitterScope scope;
MGPipeTextureEmitterInstance().ResetForTest();
const GLuint name = MakeImageTexture();
const auto& texture = Ctx().GetTextureObject(name);
ASSERT_TRUE(texture);
const MGPipeHandle handle = MGPipeSlots().FindByLifetimeId(MGPipeKind::Texture, texture->GetLifetimeId());
ASSERT_FALSE(MGPipeHandleIsNull(handle));
EXPECT_EQ(MGPipeTextureEmitterInstance().TextureBindMask(handle) & kMGPipeBindShaderImage, 0)
<< "nothing has image-bound this texture yet";
GL::BindImageTexture(0, name, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8);
EXPECT_NE(MGPipeTextureEmitterInstance().TextureBindMask(handle) & kMGPipeBindShaderImage, 0)
<< "glBindImageTexture did not mark the texture image-bound";
static const char* kOneImage = R"(#version 430 core
layout(local_size_x = 1) in;
layout(binding = 0, rgba8) uniform image2D img;
void main() { imageStore(img, ivec2(0, 0), vec4(1.0)); }
)";
const GLuint program = MakeComputeProgram(kOneImage);
GL::UseProgram(program);
Emitter().EmitShaderImages(Ctx());
ASSERT_GE(Emitter().Window(), 1u);
EXPECT_TRUE(Emitter().LastImageViews()[0].Res == handle);
EXPECT_NE(MGPipeTextureEmitterInstance().TextureBindMask(handle) & kMGPipeBindShaderImage, 0)
<< "the emitted image set's walk does not carry the bit either";
GL::UseProgram(0);
GL::BindImageTexture(0, 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8);
}
} // namespace
#endif // MOBILEGL_PIPE_PUSH
+41 -1
View File
@@ -58,6 +58,7 @@
#include "Init.h"
#include <MG_Impl/GLImpl/Program/GL_Program.h>
#include <MG_Impl/GLImpl/Texture/GL_Texture.h>
#include <MG_Impl/Pipe/TextureEmit.h>
#include <MG_Impl/Pipe/SamplerEmit.h>
#include <MG_Impl/Pipe/SetHashSuppressor.h>
#include <MG_Impl/Pipe/SlotAllocator.h>
@@ -566,7 +567,8 @@ TEST(SamplerEmit, AMakeCurrentTakesTheUnitSetsAndLeavesTheCsoAndViewRecordsStand
X(SamplerEmit, ABoundSamplerStateHoldsItsCsoUntilTheUnitMoves) \
X(SamplerEmit, AReferencedCsoIsNeverTheLruVictim) \
X(SamplerEmit, AFullyPinnedCacheMintsBeyondItsCapacityAndCountsIt) \
X(SamplerEmit, AReleaseThisCacheNeverHandedOutIsCountedRatherThanAbsorbed)
X(SamplerEmit, AReleaseThisCacheNeverHandedOutIsCountedRatherThanAbsorbed) \
X(SamplerEmit, AResolvedSamplerViewMarksItsTextureAsSamplerBound)
#define MGL_DECLARE_PULL_SKIP(Suite, Name) \
TEST(Suite, Name) { GTEST_SKIP() << "compiled only under MOBILEGL_PIPE_PUSH"; }
@@ -1088,6 +1090,44 @@ namespace {
// turned from a compiled-out assert into a number.
EXPECT_EQ(Cache().GetCounters().ReferencedEvictions, 0u);
}
// FINAL REVIEW M-A: THE SAMPLER-VIEW RESOLUTION IS D-A4's PRODUCER OF kMGPipeBindSampler.
// "Any texture the sampler-view resolution names in an emitted MGPBoundView" carries the
// sticky bit from then on; a texture bound to a unit no sampler uniform resolves does not.
// Nothing produced the bit before the fix round.
TEST(SamplerEmit, AResolvedSamplerViewMarksItsTextureAsSamplerBound) {
EmitterScope scope;
MGPipeTextureEmitterInstance().ResetForTest();
namespace GL = MobileGL::MG_Impl::GLImpl;
const Uint program = MakeSamplerProgram();
GLint linked = 0;
GL::GetProgramiv(program, GL_LINK_STATUS, &linked);
ASSERT_EQ(linked, GL_TRUE);
GL::UseProgram(program);
const GLint location = GL::GetUniformLocation(program, "sampled");
ASSERT_GE(location, 0);
GL::Uniform1i(location, 3);
GLuint sampledName = 0;
GLuint unsampledName = 0;
const SharedPtr<ITextureObject> sampled = MakeCompleteTexture(sampledName, 4);
const SharedPtr<ITextureObject> unsampled = MakeCompleteTexture(unsampledName, 4);
BindTextureToUnit(3, sampled);
BindTextureToUnit(5, unsampled);
ASSERT_GT(Emitter().EmitSamplerViews(Ctx()), 0u);
const MGPBoundView& resolved = Emitter().LastBoundViews()[3];
ASSERT_FALSE(MGPipeHandleIsNull(resolved.Texture));
EXPECT_NE(MGPipeTextureEmitterInstance().TextureBindMask(resolved.Texture) & kMGPipeBindSampler, 0)
<< "the texture a sampler view was resolved for does not carry kMGPipeBindSampler";
const MGPipeHandle unsampledHandle =
MGPipeSlots().FindByLifetimeId(MGPipeKind::Texture, unsampled->GetLifetimeId());
if (!MGPipeHandleIsNull(unsampledHandle)) {
EXPECT_EQ(MGPipeTextureEmitterInstance().TextureBindMask(unsampledHandle) & kMGPipeBindSampler, 0)
<< "a texture no sampler uniform resolves to was marked sampler-bound";
}
GL::UseProgram(0);
}
} // namespace
#endif // MOBILEGL_PIPE_PUSH
+5 -1
View File
@@ -179,7 +179,7 @@ namespace MobileGL::MG_Util::PipeStats {
#if MOBILEGL_PIPE_PUSH
"render-state-cso-mints", "render-state-cso-binds", "map-persistent-roundtrips",
"framebuffer-emissions", "sampler-view-emissions", "sampler-state-emissions",
"shader-image-emissions", "client-tex-upload-emissions",
"shader-image-emissions", "client-tex-upload-emissions", "tex-remint-pulls",
#endif
};
const char* const kGateNames[kGateCount] = {
@@ -453,6 +453,10 @@ namespace MobileGL::MG_Util::PipeStats {
line += " sie=" + std::to_string(calls[static_cast<Uint32>(CallClass::ShaderImageEmissions)]);
line += " ctu=" +
std::to_string(calls[static_cast<Uint32>(CallClass::ClientTextureUploadEmissions)]);
// trp is the texture-remint pull count (ROADMAP open question 2): every one is a texture
// Espryt had already allocated and then had to re-mint image-bindable, replaying its
// levels from the client's shadow, because ImageBindableHint reached it too late.
line += " trp=" + std::to_string(calls[static_cast<Uint32>(CallClass::TextureRemintPulls)]);
#endif
line += "] gates[";
for (Uint32 i = 0; i < kGateCount; ++i) {
+8
View File
@@ -156,6 +156,14 @@ namespace MobileGL::MG_Util::PipeStats {
// hides is ~+6 ms/frame, so an emission-shape divergence has to be a difference of two
// numbers rather than something only a GPU can see.
ClientTextureUploadEmissions,
// THE TEXTURE-REMINT PULL RATE (ROADMAP open question 2; P4a final review M-A). Counted
// by Espryt once per transition in which a texture that ALREADY HAD backend storage is
// re-minted image-bindable and its defined levels are replayed from the client's shadow
// (RequireImageBindableStorage) - the reach-back a split cannot make (D-M) and the one
// ImageBindableHint exists to prevent. A texture whose hint arrived before its first
// sync is allocated image-bindable up front and never counts. `trp=` on the summary
// line; the number that decides MOBILEGL_PIPE_TEXEL_RETAIN_MB's default.
TextureRemintPulls,
#endif
Count
};