mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-10 05:08:31 +09:00
[Test] (Espryt): take G9's reading while the texture is still attachment-only - the applier's params record, Espryt's applied value and the absence of a sampler view, before the sample that would repair all three
This commit is contained in:
@@ -53,6 +53,7 @@ add_executable(MobileGLIntegrationTest
|
||||
Harness/HeadlessGL.cpp
|
||||
Harness/BackendCapsPeek.cpp
|
||||
Harness/PipeSlotPeek.cpp
|
||||
Harness/PipeApplyPeek.cpp
|
||||
Scenarios/OrientationScenario.cpp
|
||||
Scenarios/CrossFrameBufferScenario.cpp
|
||||
Scenarios/ResidentIndexScenario.cpp
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
// MobileGL - MobileGL/MG_IntegrationTest/Harness/PipeApplyPeek.cpp
|
||||
// Copyright (c) 2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "PipeApplyPeek.h"
|
||||
|
||||
#if !defined(__ANDROID__)
|
||||
#include <MG_Pipe/MGPipe.h>
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
#include <MG_Pipe/PipeApply.h>
|
||||
#include <MG_Pipe/MGPipeTypes.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
|
||||
#include <MG_Backend/DirectGLES/Managers.h>
|
||||
#include <MG_Backend/DirectGLES/DirectGLES.h>
|
||||
#define MGITEST_PIPE_APPLY_PEEK_LIVE 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace MGITest {
|
||||
|
||||
#if defined(MGITEST_PIPE_APPLY_PEEK_LIVE)
|
||||
namespace {
|
||||
namespace MGP = MobileGL::MG_Pipe;
|
||||
namespace MGB = MobileGL::MG_Backend::DirectGLES;
|
||||
|
||||
// The frontend texture object a GL name denotes in the CURRENT context, or null. This is
|
||||
// a LOOKUP KEY and nothing else: every value this file reports comes from the applier or
|
||||
// from Espryt, never from the object found here. (Reading the frontend's own parameter
|
||||
// state would answer the question the scenario is asking with the input to it.)
|
||||
MobileGL::MG_State::GLState::ITextureObject* FrontendTexture(unsigned glTextureName) {
|
||||
if (!MobileGL::MG_State::pGLContext) return nullptr;
|
||||
const auto& object = MobileGL::MG_State::pGLContext->GetTextureObject(
|
||||
static_cast<MobileGL::Uint>(glTextureName));
|
||||
return object ? object.get() : nullptr;
|
||||
}
|
||||
|
||||
// Espryt's twin for that texture, or null - which is also this file's "is Espryt even the
|
||||
// backend running" answer. On Magma no Espryt twin was ever built, so every entry point
|
||||
// below stops here rather than reaching for g_GLESFuncs, whose members are null there.
|
||||
MGB::TextureImpl::BackendTextureObject* EsprytTwin(unsigned glTextureName) {
|
||||
MobileGL::MG_State::GLState::ITextureObject* const object = FrontendTexture(glTextureName);
|
||||
if (object == nullptr) return nullptr;
|
||||
auto* const found = MGB::TextureImpl::g_backendTextureObjects.Find(object);
|
||||
if (found == nullptr || !*found) return nullptr;
|
||||
return found->get();
|
||||
}
|
||||
|
||||
int SwizzleToGLEnum(MobileGL::Uint8 encoded) {
|
||||
return static_cast<int>(MobileGL::MG_Util::ConvertTextureSwizzleParamToGLEnum(
|
||||
static_cast<MobileGL::TextureSwizzleParam>(encoded)));
|
||||
}
|
||||
|
||||
// MGPipeTypes.h owns the two numbers and says why depth is 0 (a zeroed record must decode
|
||||
// to what an untouched texture already has). This is that decode, and nothing else in
|
||||
// this module may open-code it.
|
||||
int DepthStencilModeToGLEnum(MobileGL::Uint8 encoded) {
|
||||
return encoded == MGP::kMGPipeDepthStencilModeStencil ? GL_STENCIL_INDEX
|
||||
: GL_DEPTH_COMPONENT;
|
||||
}
|
||||
|
||||
// The GL_TEXTURE_BINDING_* query for a target, or 0 where this file has no answer. A
|
||||
// guess would be worse than a refusal: the binding is what gets RESTORED, so a wrong
|
||||
// pname would leave the driver bound to this test's texture.
|
||||
int BindingQueryFor(unsigned glTarget) {
|
||||
switch (glTarget) {
|
||||
case GL_TEXTURE_2D: return GL_TEXTURE_BINDING_2D;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool PeekPipeTextureParamsRecord(unsigned glTextureName, PipeTextureParamsRecordPeek* out) {
|
||||
if (out == nullptr) return false;
|
||||
const MGP::MGPipeApplierState& applier = MGP::MGPipeApplier();
|
||||
// Slot 0 is the reserved null handle and is never live (MGPipeHandles.h), so the scan
|
||||
// starts at 1 and a match at 0 is impossible rather than merely unlikely.
|
||||
for (MobileGL::SizeT slot = 1; slot < applier.TextureResources.size(); ++slot) {
|
||||
const MGP::MGPipeResourceRecord& record = applier.TextureResources[slot];
|
||||
if (!record.Live) continue;
|
||||
if (record.Desc.GlNameForDiag != static_cast<MobileGL::Uint32>(glTextureName)) continue;
|
||||
out->Slot = static_cast<unsigned>(slot);
|
||||
out->Gen = static_cast<unsigned>(record.Gen);
|
||||
out->ParamsSerial = static_cast<unsigned long long>(record.ParamsSerial);
|
||||
for (int channel = 0; channel < 4; ++channel) {
|
||||
out->Swizzle[channel] = SwizzleToGLEnum(record.Params.Swizzle[channel]);
|
||||
}
|
||||
out->DepthStencilMode = DepthStencilModeToGLEnum(record.Params.DepthStencilMode);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PeekEsprytAppliedTextureParams(unsigned glTextureName, unsigned glTarget,
|
||||
EsprytAppliedTextureParamsPeek* out) {
|
||||
if (out == nullptr) return false;
|
||||
const int bindingQuery = BindingQueryFor(glTarget);
|
||||
if (bindingQuery == 0) return false;
|
||||
MGB::TextureImpl::BackendTextureObject* const twin = EsprytTwin(glTextureName);
|
||||
if (twin == nullptr) return false;
|
||||
const MobileGL::Uint backendId = twin->GetBackendTextureId();
|
||||
if (backendId == 0) return false;
|
||||
if (MGB::g_GLESFuncs.glGetTexParameteriv == nullptr ||
|
||||
MGB::g_GLESFuncs.glBindTexture == nullptr || MGB::g_GLESFuncs.glGetIntegerv == nullptr ||
|
||||
MGB::g_GLESFuncs.glGetError == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// SAVE / QUERY / RESTORE ON THE UNIT THAT IS ALREADY ACTIVE. No glActiveTexture, so the
|
||||
// only driver state this touches is one unit's binding, and it is put back byte for byte
|
||||
// - which is what keeps Espryt's own g_boundTexturesCache true rather than merely
|
||||
// consistent. (Binding through the twin's own Bind() would update that shadow and would
|
||||
// therefore CHANGE what the scenario measures next; this does not.)
|
||||
GLint previousBinding = 0;
|
||||
MGB::g_GLESFuncs.glGetIntegerv(static_cast<GLenum>(bindingQuery), &previousBinding);
|
||||
MGB::g_GLESFuncs.glBindTexture(static_cast<GLenum>(glTarget), backendId);
|
||||
|
||||
out->BackendTextureId = static_cast<unsigned>(backendId);
|
||||
static const GLenum kSwizzlePnames[4] = {GL_TEXTURE_SWIZZLE_R, GL_TEXTURE_SWIZZLE_G,
|
||||
GL_TEXTURE_SWIZZLE_B, GL_TEXTURE_SWIZZLE_A};
|
||||
for (int channel = 0; channel < 4; ++channel) {
|
||||
GLint value = 0;
|
||||
MGB::g_GLESFuncs.glGetTexParameteriv(static_cast<GLenum>(glTarget),
|
||||
kSwizzlePnames[channel], &value);
|
||||
out->Swizzle[channel] = static_cast<int>(value);
|
||||
}
|
||||
|
||||
// The depth/stencil aspect mode is ES 3.1 and is INVALID_ENUM on a driver without it, so
|
||||
// it is asked for last and its own error decides whether the answer is usable. The queue
|
||||
// is drained first because a stale error from anywhere else would be indistinguishable
|
||||
// from this call's - Espryt drains it the same way at every one of its own sync sites
|
||||
// (DebugImpl::ErrorLopper), and this module's own GL errors are read from the FRONTEND
|
||||
// state (ScenarioTest::FirstGLError), which none of this touches.
|
||||
while (MGB::g_GLESFuncs.glGetError() != GL_NO_ERROR) {
|
||||
}
|
||||
GLint mode = 0;
|
||||
MGB::g_GLESFuncs.glGetTexParameteriv(static_cast<GLenum>(glTarget),
|
||||
GL_DEPTH_STENCIL_TEXTURE_MODE, &mode);
|
||||
out->DepthStencilModeIsReadable = MGB::g_GLESFuncs.glGetError() == GL_NO_ERROR;
|
||||
out->DepthStencilMode = static_cast<int>(mode);
|
||||
|
||||
MGB::g_GLESFuncs.glBindTexture(static_cast<GLenum>(glTarget),
|
||||
static_cast<GLuint>(previousBinding));
|
||||
while (MGB::g_GLESFuncs.glGetError() != GL_NO_ERROR) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PeekEsprytHasSamplerViewForTexture(unsigned glTextureName, bool* outExists) {
|
||||
if (outExists == nullptr) return false;
|
||||
MobileGL::MG_State::GLState::ITextureObject* const object = FrontendTexture(glTextureName);
|
||||
if (object == nullptr) return false;
|
||||
// Espryt must be the backend running, or "no view" would be true of every texture on
|
||||
// every other backend and the assertion would be vacuous where it is loudest.
|
||||
if (EsprytTwin(glTextureName) == nullptr) return false;
|
||||
// HandleOfSamplerViewForTexture is the monolith glue that derives the view's handle from
|
||||
// the TEXTURE's lifetime id (D-F2: one view per ITextureObject), so this asks Espryt's
|
||||
// own table the same way Espryt asks it - it does not consult the applier record's
|
||||
// ViewCso, which is the client's statement about the same fact and would make one side
|
||||
// of the seam vouch for the other.
|
||||
const MGP::MGPipeHandle view = MGB::SamplerViewImpl::HandleOfSamplerViewForTexture(object);
|
||||
if (MGP::MGPipeHandleIsNull(view)) {
|
||||
*outExists = false;
|
||||
return true;
|
||||
}
|
||||
*outExists = MGB::SamplerViewImpl::FindSamplerViewForHandle(view) != nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PeekPipeApplierRefusedNoConsumer(unsigned long long* outCount) {
|
||||
if (outCount == nullptr) return false;
|
||||
*outCount = static_cast<unsigned long long>(MGP::MGPipeApplier().RefusedNoConsumer);
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
bool PeekPipeTextureParamsRecord(unsigned, PipeTextureParamsRecordPeek*) { return false; }
|
||||
bool PeekEsprytAppliedTextureParams(unsigned, unsigned, EsprytAppliedTextureParamsPeek*) {
|
||||
return false;
|
||||
}
|
||||
bool PeekEsprytHasSamplerViewForTexture(unsigned, bool*) { return false; }
|
||||
bool PeekPipeApplierRefusedNoConsumer(unsigned long long*) { return false; }
|
||||
#endif
|
||||
|
||||
} // namespace MGITest
|
||||
@@ -0,0 +1,110 @@
|
||||
// MobileGL - MobileGL/MG_IntegrationTest/Harness/PipeApplyPeek.h
|
||||
// Copyright (c) 2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
//
|
||||
// The APPLIER's texture-parameter record, ESPRYT's applied value for the same texture, and
|
||||
// whether that texture has a sampler view yet. Three readings taken from a scenario, for gate
|
||||
// G9's WHITE-BOX half.
|
||||
//
|
||||
// WHY A WHITE-BOX HALF EXISTS AT ALL (ID-19, brief section F, gates review R1). G9's public-GL
|
||||
// cases in TextureParamsWithoutASamplerViewScenario.cpp catch "the parameter never reached the
|
||||
// driver". They CANNOT catch "the parameter reached the driver LATE", because a texture
|
||||
// parameter's only public-GL observable is a SAMPLE and the sample is itself what repairs an
|
||||
// unsynced parameter: it puts the texture on the unit list, and that walk pushes the parameters
|
||||
// for anything whose params serial moved. A backend that deferred every attachment-only
|
||||
// texture's parameters to the first sampler view would be green on all four of those cases,
|
||||
// forever, on every tree. The distinction only exists on the inside, so the reading has to be
|
||||
// taken there - while the texture is still attachment-only, before any sample.
|
||||
//
|
||||
// A SEPARATE TRANSLATION UNIT for PipeSlotPeek.h's reason, verbatim: the scenario sources
|
||||
// include the GL headers with prototypes and MobileGL's umbrella header is not meant to meet
|
||||
// them in one file. This one goes further than PipeSlotPeek and includes Espryt's own
|
||||
// Managers.h, which is exactly why it may not be anywhere near a scenario's GL headers.
|
||||
//
|
||||
// EVERY ENTRY POINT RETURNS false, TOUCHING NOTHING, WHERE IT CANNOT LOOK, and a caller that
|
||||
// gets false has learned NOTHING - "could not look" is not "was applied". Out of reach means:
|
||||
// a PULL build (there is no applier: it is `#if MOBILEGL_PIPE_PUSH`); Android, where this module
|
||||
// links the shipping libMobileGL.so built -fvisibility=hidden and no internal symbol resolves;
|
||||
// a backend that is not DirectGLES (Espryt is the subject; Magma answers the same GL question
|
||||
// through P7's own paths); and, for the record peek, a mask whose texture-resource bit is off,
|
||||
// where no record exists to find because nothing was ever emitted.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace MGITest {
|
||||
|
||||
// ---- the applier's set_texture_params record for one GL texture name ------------------
|
||||
//
|
||||
// ADDRESSED BY GL NAME, and the search key is MGPResourceDesc::GlNameForDiag. That field is
|
||||
// diagnostics-only by contract - never an identity, never a memo key (MGPipeTypes.h) - and
|
||||
// this is a diagnostic: a test harness looking for the record a named GL object produced.
|
||||
// The alternative would be to ask the CLIENT emitter for the texture's handle, and the
|
||||
// review is explicit that this probe must arm on package D's applier/backend state and not
|
||||
// on the emitter markers B and C set: they are different questions, and a shared marker
|
||||
// would re-create the shape review F-M5 was raised about.
|
||||
struct PipeTextureParamsRecordPeek {
|
||||
// The handle the record sits at, so a caller can print it.
|
||||
unsigned Slot;
|
||||
unsigned Gen;
|
||||
// set_texture_params' own serial. 0 means the record exists (the resource was created)
|
||||
// but NO set_texture_params has ever been applied to it - which is a different finding
|
||||
// from "no record", and the two must not be merged.
|
||||
unsigned long long ParamsSerial;
|
||||
// MGPTextureParams::Swizzle[4], translated to the GL enums the application passed to
|
||||
// glTextureParameteri (GL_ZERO / GL_ONE / GL_RED / GL_GREEN / GL_BLUE / GL_ALPHA), so
|
||||
// the scenario compares what it set against what the record carries in ONE vocabulary
|
||||
// and neither side has to know the other's encoding.
|
||||
int Swizzle[4];
|
||||
// MGPTextureParams::DepthStencilMode, translated the same way: GL_DEPTH_COMPONENT or
|
||||
// GL_STENCIL_INDEX.
|
||||
int DepthStencilMode;
|
||||
};
|
||||
|
||||
bool PeekPipeTextureParamsRecord(unsigned glTextureName, PipeTextureParamsRecordPeek* out);
|
||||
|
||||
// ---- Espryt's APPLIED value for the same texture --------------------------------------
|
||||
//
|
||||
// Read from the DRIVER, through the twin's own ES name, because "applied" means the driver
|
||||
// was told - the same thing package D's white-box unit probe asserts against its mocked
|
||||
// driver (esprytobj-v2 (9)). The current binding on the ACTIVE unit is saved and restored
|
||||
// around the query and no unit is switched, so Espryt's binding shadow still describes
|
||||
// reality afterwards: nothing is perturbed for it to be stale about.
|
||||
//
|
||||
// `glTarget` is the texture's GL target (only GL_TEXTURE_2D is supported today; any other
|
||||
// target returns false rather than guessing a binding query).
|
||||
struct EsprytAppliedTextureParamsPeek {
|
||||
// The driver name Espryt minted for this texture, for the caller's message.
|
||||
unsigned BackendTextureId;
|
||||
int Swizzle[4];
|
||||
int DepthStencilMode;
|
||||
// False when the driver rejected the depth/stencil query - a non-depth texture, or an ES
|
||||
// level without GL_DEPTH_STENCIL_TEXTURE_MODE. The swizzle half is still valid.
|
||||
bool DepthStencilModeIsReadable;
|
||||
};
|
||||
|
||||
bool PeekEsprytAppliedTextureParams(unsigned glTextureName, unsigned glTarget,
|
||||
EsprytAppliedTextureParamsPeek* out);
|
||||
|
||||
// ---- and the claim that makes the two above mean anything ------------------------------
|
||||
//
|
||||
// Whether Espryt holds a SAMPLER VIEW twin for this texture. This is the assertion the
|
||||
// public-GL cases cannot make, because making it there would create the view. `*outExists`
|
||||
// is written only on true.
|
||||
bool PeekEsprytHasSamplerViewForTexture(unsigned glTextureName, bool* outExists);
|
||||
|
||||
// ---- c0f's belt, for the ObjectSubsystemControl arms -----------------------------------
|
||||
//
|
||||
// MGPipeApplierState::RefusedNoConsumer: the number of P4a-family entry points that were
|
||||
// refused because no backend had registered MGPipeResourceOps. On a backend WITH a consumer
|
||||
// it must never move; on one without (Magma, ID-39/ID-40) the client's own gate is supposed
|
||||
// to stop the emission before the belt is reached, so it must never move there either. A
|
||||
// non-zero delta says the gate and the belt disagreed, which is the whole point of having
|
||||
// both. Reset by MGPipeApplierReset, so a caller reads it as a DELTA and treats a value that
|
||||
// went DOWN as "the applier was reset, count everything since as `after`".
|
||||
bool PeekPipeApplierRefusedNoConsumer(unsigned long long* outCount);
|
||||
|
||||
} // namespace MGITest
|
||||
+235
-5
@@ -48,11 +48,46 @@
|
||||
// in particular they would go RED if any of the four reachability paths were ever made to
|
||||
// depend on the texture having a sampler VIEW - which is exactly the coupling P4a's
|
||||
// resource-addressed set_texture_params removes and the thing a later phase could reintroduce;
|
||||
// * the "落地前必须红" artefact ROADMAP.md:20 asks for is NOT produced by this file, and no
|
||||
// public-GL integration scenario on a monolith tree can produce it. Producing it needs an
|
||||
// observation of the DRIVER's texture object taken while the texture is still
|
||||
// read-attachment-only, which means a probe inside MG_Backend/DirectGLES - package D's files
|
||||
// (C.7), not this package's. The integrator's ruling is recorded in the gates result document.
|
||||
// * the "落地前必须红" artefact ROADMAP.md:20 asks for is NOT produced by the public-GL half
|
||||
// of this file, and no public-GL integration scenario on a monolith tree can produce it.
|
||||
// Producing it needs an observation of the DRIVER's texture object taken while the texture is
|
||||
// still read-attachment-only. ID-19 rules that G9 is therefore a WHITE-BOX assertion, and this
|
||||
// file now carries the SCENARIO half of it (the unit half is package D's,
|
||||
// MG_Test/SanityTest.cpp's DirectGLESTextureSync.AnAttachmentOnlyTexturesParametersReachThe
|
||||
// DriverWithNoSamplerView).
|
||||
//
|
||||
// THE WHITE-BOX HALF, and what it adds to the four cases below. Each case, at the point where its
|
||||
// texture is reachable ONLY its own way and BEFORE the observing sample, takes three readings
|
||||
// through MG_IntegrationTest/Harness/PipeApplyPeek.h and asserts all three:
|
||||
//
|
||||
// (a) the APPLIER holds a set_texture_params record for this texture, at a non-zero ParamsSerial,
|
||||
// carrying the field the case moved;
|
||||
// (b) ESPRYT's applied value for the same texture - read back from the DRIVER, through the twin's
|
||||
// own ES name - is that value ALREADY, not after the first sampler view;
|
||||
// (c) Espryt holds NO SAMPLER VIEW for this texture yet, which is what turns (b) from "applied"
|
||||
// into "applied WITHOUT one" and is the whole claim D10 makes.
|
||||
//
|
||||
// (c) is the assertion the public-GL half structurally cannot make: making it there would create
|
||||
// the view. (b) is the half that goes red on a backend that DEFERS - a tree where the parameter
|
||||
// push is gated on a sampler view existing is green on all four public-GL cases forever, because
|
||||
// the sample that observes the parameter is also what mints the view and repairs the state.
|
||||
//
|
||||
// WHAT THE THREE READINGS DO **NOT** COVER, so the next reader does not over-trust them
|
||||
// (esprytobj re-review N-9, carried here by request). D's unit probe drives
|
||||
// SyncTextureParamsToBackend directly, so the only deferral shape IT can see is one INSIDE that
|
||||
// function. These three run through the real per-frame paths and therefore also see a deferral
|
||||
// introduced ABOVE it - in SyncNeccessaryTextures, in the attachment walk, or in E's per-unit walk.
|
||||
// Between them the two halves cover both, and neither covers both alone.
|
||||
//
|
||||
// A READING THAT CANNOT BE TAKEN IS DECLINED BY NAME AND THE CASE CONTINUES - it is not a
|
||||
// GTEST_SKIP, and that is a deliberate departure from the shape the review sketched. These four
|
||||
// cases are dual-purpose: they are also the END-TO-END regression net around D10, and that net is
|
||||
// the ONLY thing measuring D10 on exactly the arms where the peek cannot look (the pull build,
|
||||
// which has no applier at all; the 0x1ff and 0 lanes, where the texture family is switched off;
|
||||
// Magma, which has no Espryt twin). Skipping the case there would delete the one verdict those
|
||||
// lanes carry in order to report the absence of a second one. The decline is printed, recorded as
|
||||
// a test property and named, so a lane that silently stopped taking the reading is visible in the
|
||||
// log rather than in a count.
|
||||
//
|
||||
// WHY THE SECOND ONE IS THE RED, mechanically (scout-espryt-framebuffer.md 2.6, re-opened at the
|
||||
// base ref). Today a texture's parameters ride on the UNIT BINDING and on the DRAW attachment set:
|
||||
@@ -102,6 +137,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "../Harness/HeadlessGL.h"
|
||||
#include "../Harness/PipeApplyPeek.h"
|
||||
#include "../Harness/ScenarioFixture.h"
|
||||
|
||||
#ifdef GLAPI
|
||||
@@ -271,11 +307,172 @@ void main() {
|
||||
glTextureParameteri(texture, GL_TEXTURE_SWIZZLE_B, GL_ZERO);
|
||||
}
|
||||
|
||||
// Which of the two parameters a case moved, and therefore which one the white-box
|
||||
// reading has to find on both sides of the seam. Two, because they are the two the
|
||||
// four cases use and because a peek that reported "some parameter" would be green for
|
||||
// a backend that applied the wrong one.
|
||||
enum class MovedParameter { Swizzle, DepthStencilMode };
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
// G9's WHITE-BOX READING (ID-19). Called by every case at the point where its
|
||||
// texture is reachable only its own way and BEFORE the observing sample - which is
|
||||
// the whole of the design, because the sample repairs what it observes.
|
||||
//
|
||||
// `expectedSwizzle` is the four GL enums the case set (or left at their defaults);
|
||||
// `expectedDepthStencilMode` is GL_DEPTH_COMPONENT or GL_STENCIL_INDEX. Both are
|
||||
// always passed and `moved` says which one is the case's subject, so a reader of a
|
||||
// failure can see the untouched half beside the moved one.
|
||||
void TakeTheWhiteBoxReadingBeforeAnySample(GLuint texture, GLenum target,
|
||||
MovedParameter moved,
|
||||
const GLint expectedSwizzle[4],
|
||||
GLint expectedDepthStencilMode,
|
||||
const char* whatMadeItReachable) {
|
||||
const char* const movedName =
|
||||
moved == MovedParameter::Swizzle ? "GL_TEXTURE_SWIZZLE_*"
|
||||
: "GL_DEPTH_STENCIL_TEXTURE_MODE";
|
||||
|
||||
PipeTextureParamsRecordPeek record{};
|
||||
if (!PeekPipeTextureParamsRecord(static_cast<unsigned>(texture), &record)) {
|
||||
DeclineTheWhiteBoxReading(
|
||||
"no set_texture_params record for this texture in the applier. Either "
|
||||
"there is no applier here (a PULL build: MGPipeApplierState is "
|
||||
"#if MOBILEGL_PIPE_PUSH), or this lane's MOBILEGL_PIPE_PUSH leaves "
|
||||
"kMGPipeSubsystemTextureResources (bit 10) clear, or no backend "
|
||||
"registered MGPipeResourceOps so the client never emitted (c0f). The "
|
||||
"end-to-end half of this case below is unaffected and still decides it.");
|
||||
return;
|
||||
}
|
||||
|
||||
// From here the reading WAS taken, so everything is a hard assertion: a record
|
||||
// that exists and does not carry the parameter is exactly the finding.
|
||||
EXPECT_NE(record.ParamsSerial, 0u)
|
||||
<< "the applier holds a resource record for texture " << texture
|
||||
<< " at handle {" << record.Slot << ", " << record.Gen
|
||||
<< "} but its ParamsSerial is 0, i.e. NO set_texture_params has ever been "
|
||||
"applied to it - and this case moved " << movedName << " while the texture "
|
||||
"was " << whatMadeItReachable
|
||||
<< ". A parameter change on a texture with no sampler view has to produce a "
|
||||
"record addressed BY RESOURCE (D10, D-E1); a zero here means the client "
|
||||
"never emitted one, which is the coupling P4a exists to remove reappearing "
|
||||
"on the emitter's side of the seam.";
|
||||
|
||||
if (moved == MovedParameter::Swizzle) {
|
||||
for (int channel = 0; channel < 4; ++channel) {
|
||||
EXPECT_EQ(record.Swizzle[channel], static_cast<int>(expectedSwizzle[channel]))
|
||||
<< "the applier's set_texture_params record for texture " << texture
|
||||
<< " carries the wrong swizzle in channel " << channel
|
||||
<< " (record 0x" << std::hex << record.Swizzle[channel] << ", expected 0x"
|
||||
<< expectedSwizzle[channel] << std::dec
|
||||
<< "). The record is what Espryt reads, so a wrong value here is a "
|
||||
"wrong value everywhere downstream of it.";
|
||||
}
|
||||
} else {
|
||||
EXPECT_EQ(record.DepthStencilMode, static_cast<int>(expectedDepthStencilMode))
|
||||
<< "the applier's set_texture_params record for texture " << texture
|
||||
<< " carries GL_DEPTH_STENCIL_TEXTURE_MODE 0x" << std::hex
|
||||
<< record.DepthStencilMode << ", expected 0x" << expectedDepthStencilMode
|
||||
<< std::dec << ".";
|
||||
}
|
||||
|
||||
// (c) - and it is checked BEFORE (b) is read, because (b) reads the driver and a
|
||||
// reader of a failure needs to know the view question was answered on the state
|
||||
// this case built rather than on anything the peek did.
|
||||
bool hasSamplerView = true;
|
||||
if (!PeekEsprytHasSamplerViewForTexture(static_cast<unsigned>(texture),
|
||||
&hasSamplerView)) {
|
||||
DeclineTheWhiteBoxReading(
|
||||
"Espryt holds no twin for this texture, so neither the sampler-view "
|
||||
"question nor the applied-value one can be asked here. On a backend other "
|
||||
"than DirectGLES that is the designed state (P4a touches no DirectVulkan "
|
||||
"source but MagmaPipeArms.h, D-Q).");
|
||||
return;
|
||||
}
|
||||
EXPECT_FALSE(hasSamplerView)
|
||||
<< "Espryt already holds a SAMPLER VIEW for texture " << texture
|
||||
<< ", which was " << whatMadeItReachable
|
||||
<< " and has never been bound to a sampler unit in this case. The whole claim "
|
||||
"of D10 is that a texture reached this way has no view, so if one exists "
|
||||
"the reading below cannot separate 'applied by resource' from 'applied "
|
||||
"through the view' and this case has stopped measuring G9.";
|
||||
|
||||
EsprytAppliedTextureParamsPeek applied{};
|
||||
if (!PeekEsprytAppliedTextureParams(static_cast<unsigned>(texture),
|
||||
static_cast<unsigned>(target), &applied)) {
|
||||
DeclineTheWhiteBoxReading(
|
||||
"Espryt's applied value could not be read back from the driver (no twin, "
|
||||
"no ES name yet, or a target this peek has no binding query for).");
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "[ TextureParamsWithoutASamplerView ] white-box: texture " << texture
|
||||
<< " -> applier handle {" << record.Slot << ", " << record.Gen
|
||||
<< "} paramsSerial " << record.ParamsSerial << ", Espryt ES name "
|
||||
<< applied.BackendTextureId << ", sampler view: none, " << movedName
|
||||
<< " applied before any sample" << std::endl;
|
||||
|
||||
if (moved == MovedParameter::Swizzle) {
|
||||
for (int channel = 0; channel < 4; ++channel) {
|
||||
EXPECT_EQ(applied.Swizzle[channel], static_cast<int>(expectedSwizzle[channel]))
|
||||
<< "ESPRYT HAS NOT APPLIED THE SWIZZLE YET. Channel " << channel
|
||||
<< " of the driver texture (ES name " << applied.BackendTextureId
|
||||
<< ") reads 0x" << std::hex << applied.Swizzle[channel] << ", the "
|
||||
<< "application set 0x" << expectedSwizzle[channel] << std::dec
|
||||
<< ", and the applier's record already carries the right value - so "
|
||||
"the record reached the server and the server has not pushed it. "
|
||||
"The texture was " << whatMadeItReachable
|
||||
<< " and has NO sampler view (asserted above), which makes this "
|
||||
"exactly the deferred-to-first-view shape G9 exists to catch: the "
|
||||
"sample at the end of this case would repair it, and the "
|
||||
"end-to-end assertion below would then pass on a driver that was "
|
||||
"told late. That is the half no public-GL case can see.";
|
||||
}
|
||||
} else {
|
||||
if (!applied.DepthStencilModeIsReadable) {
|
||||
DeclineTheWhiteBoxReading(
|
||||
"this driver would not answer glGetTexParameteriv("
|
||||
"GL_DEPTH_STENCIL_TEXTURE_MODE), so the applied aspect mode cannot be "
|
||||
"read back. The record half above was still asserted.");
|
||||
return;
|
||||
}
|
||||
EXPECT_EQ(applied.DepthStencilMode, static_cast<int>(expectedDepthStencilMode))
|
||||
<< "ESPRYT HAS NOT APPLIED THE DEPTH/STENCIL ASPECT MODE YET. The driver "
|
||||
"texture (ES name " << applied.BackendTextureId << ") reads 0x"
|
||||
<< std::hex << applied.DepthStencilMode << ", the application set 0x"
|
||||
<< expectedDepthStencilMode << std::dec
|
||||
<< ", and the applier's record already carries the right value. The "
|
||||
"texture was " << whatMadeItReachable
|
||||
<< " and has no sampler view, so this is D-E3's gap measured directly "
|
||||
"rather than through a sample that would repair it: a driver left at "
|
||||
"GL_DEPTH_COMPONENT samples the DEPTH bits where the application asked "
|
||||
"for stencil.";
|
||||
}
|
||||
}
|
||||
|
||||
// Printed, recorded and named, never silent - a lane that stopped taking the reading
|
||||
// must be visible in the log. See this file's header for why it is not a GTEST_SKIP.
|
||||
void DeclineTheWhiteBoxReading(const std::string& why) {
|
||||
std::cout << "[ TextureParamsWithoutASamplerView ] white-box reading DECLINED: "
|
||||
<< why << std::endl;
|
||||
RecordProperty("g9_white_box", "declined");
|
||||
RecordProperty("g9_white_box_reason", why.c_str());
|
||||
}
|
||||
|
||||
GLuint m_fetchProgram = 0;
|
||||
GLuint m_vao = 0;
|
||||
GLuint m_quadBuffer = 0;
|
||||
};
|
||||
|
||||
// The swizzle SwizzleRedIntoGreen leaves behind, as GL enums: R -> ZERO, G -> ONE,
|
||||
// B -> ZERO and A untouched at its GL default. Written once here because both the
|
||||
// applier record and the driver read-back are compared against it.
|
||||
constexpr GLint kRedIntoGreenSwizzle[4] = {GL_ZERO, GL_ONE, GL_ZERO, GL_ALPHA};
|
||||
// A texture whose aspect mode was never touched, i.e. the GL initial value - which is
|
||||
// also what a zeroed MGPTextureParams::DepthStencilMode decodes to (MGPipeTypes.h).
|
||||
constexpr GLint kUntouchedDepthStencilMode = GL_DEPTH_COMPONENT;
|
||||
// ...and the identity swizzle, for the case whose subject is the aspect mode: the moved
|
||||
// half is asserted, and the untouched half is carried so a failure prints both.
|
||||
constexpr GLint kUntouchedSwizzle[4] = {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA};
|
||||
|
||||
// ------------------------------------------------------------------------------------
|
||||
// 1. DRAW ATTACHMENT ONLY. Green today (SyncNeccessaryTextures' FBO list walks the draw
|
||||
// slot and calls SyncTextureObjectToBackend, which syncs parameters) and green after.
|
||||
@@ -307,6 +504,13 @@ void main() {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glDeleteFramebuffers(1, &fbo);
|
||||
|
||||
// G9's white-box reading, taken here: the texture has been a draw attachment and
|
||||
// nothing else, and the sample below has not happened yet.
|
||||
TakeTheWhiteBoxReadingBeforeAnySample(texture, GL_TEXTURE_2D, MovedParameter::Swizzle,
|
||||
kRedIntoGreenSwizzle, kUntouchedDepthStencilMode,
|
||||
"an attachment of the DRAW framebuffer and "
|
||||
"nothing else");
|
||||
|
||||
const Image image = SampleAndRead(m_fetchProgram, texture);
|
||||
EXPECT_TRUE(WholeViewportIs(image, "green",
|
||||
"a texture that was only ever a DRAW attachment, sampled "
|
||||
@@ -406,6 +610,16 @@ void main() {
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "the read-attachment frame left a GL error";
|
||||
|
||||
// G9's white-box reading, and this is the case it matters most for: the aspect
|
||||
// mode was set while the texture was reachable ONLY as a read attachment, and the
|
||||
// observation below is a sample that would repair an unsynced parameter on its way
|
||||
// to reporting it.
|
||||
TakeTheWhiteBoxReadingBeforeAnySample(texture, GL_TEXTURE_2D,
|
||||
MovedParameter::DepthStencilMode,
|
||||
kUntouchedSwizzle, GL_STENCIL_INDEX,
|
||||
"an attachment of the READ framebuffer and "
|
||||
"nothing else");
|
||||
|
||||
// ---- the observation ----
|
||||
BindDefaultFramebuffer();
|
||||
glViewport(0, 0, Gl().Width(), Gl().Height());
|
||||
@@ -484,6 +698,15 @@ void main() {
|
||||
Gl().EndFrame();
|
||||
glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8);
|
||||
|
||||
// G9's white-box reading. RGBA8 is a core image format, so no widening carrier is
|
||||
// minted and the driver's swizzle is the application's own - the composition
|
||||
// RecreateBackendTexture applies for a NON-core format would show up here as a
|
||||
// legitimate difference and this case deliberately does not use one.
|
||||
TakeTheWhiteBoxReadingBeforeAnySample(texture, GL_TEXTURE_2D, MovedParameter::Swizzle,
|
||||
kRedIntoGreenSwizzle, kUntouchedDepthStencilMode,
|
||||
"an image-unit binding and nothing else, across "
|
||||
"a RequireImageBindableStorage re-mint");
|
||||
|
||||
const Image image = SampleAndRead(m_fetchProgram, texture);
|
||||
EXPECT_TRUE(WholeViewportIs(image, "green",
|
||||
"a texture that was only ever an image-unit binding, sampled "
|
||||
@@ -525,6 +748,13 @@ void main() {
|
||||
}
|
||||
Gl().EndFrame();
|
||||
|
||||
// G9's white-box reading, on the DESTINATION - the endpoint whose parameters moved.
|
||||
TakeTheWhiteBoxReadingBeforeAnySample(destination, GL_TEXTURE_2D,
|
||||
MovedParameter::Swizzle, kRedIntoGreenSwizzle,
|
||||
kUntouchedDepthStencilMode,
|
||||
"a glCopyImageSubData destination and nothing "
|
||||
"else");
|
||||
|
||||
// ...and the destination now holds the source's RED texel, which the swizzle must turn
|
||||
// into GREEN when it is finally sampled.
|
||||
const Image image = SampleAndRead(m_fetchProgram, destination);
|
||||
|
||||
Reference in New Issue
Block a user