[Test] (Pipe): let a test that drives a backend helper directly declare the verb it stands in

- ScopedPipeVerb (MG_Test/ScopedPipeVerb.h): an RAII "as if we were inside verb X" object
  that runs the real MGPipeFillForVerb for the verb it names, so the eleven unit entries
  that construct a GLContext by hand and call a backend helper with no GL entry point in
  between stop reading an empty, unstamped PipeInputs block
- it weakens nothing: it fills exactly that verb class's may-read mask, so a read outside
  it is still Fatal{UnmigratedPipeInput} naming the field and the declared verb; leaving
  the scope re-arms the poison with a one-field kQuery fill, so one case's declaration
  cannot cover a later one when the binary runs as a single process
- placed the way MGP_FILL is placed in production: immediately before the backend call,
  after every frontend mutation it is meant to see; a second call after the test moved
  state is a second verb (Renew()), a helper of another class gets a nested scope
- no-op in the pull build, no test renamed, no test added, no production source touched
This commit is contained in:
2026-09-06 06:01:41 -04:00
parent 9bd6d39403
commit ef6227e19b
4 changed files with 128 additions and 5 deletions
+16 -2
View File
@@ -25,6 +25,7 @@
#include <MG_State/GLState/Core.h>
#include <MG_State/GLState/FramebufferState/FramebufferObject.h>
#include <MG_State/GLState/TextureState/TextureState.h>
#include <MG_Test/ScopedPipeVerb.h>
#include <MG_Backend/DirectVulkan/Renderer/ProgramFactory.h>
#include <MG_Backend/DirectVulkan/Renderer/UniformManager.h>
#include <MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h>
@@ -321,7 +322,10 @@ TEST(DirectGLESSanity, BindsAMultisampleTextureDespiteTheDefaultMipmapFilter) {
backendTexture = MakeShared<DirectGLES::TextureImpl::BackendTextureObject>();
const GLuint backendTextureId = backendTexture->GetBackendTextureId();
// The symptom itself: the per-unit walk has to actually bind it.
// The symptom itself: the per-unit walk has to actually bind it. BindCurrentTextures() is
// the per-draw walk - it reads GetProgramForDraw - so the block it reads is the one a draw
// fills; without saying so, its first read is Fatal{UnmigratedPipeInput} in a push build.
MG_Test::ScopedPipeVerb draw(MG_Pipe::MGPipeVerb::DrawArrays);
DirectGLES::BindCurrentTextures();
ASSERT_EQ(state.bindCalls.size(), 1u)
<< "the multisample texture was not bound; every texelFetch against it reads zero";
@@ -362,6 +366,9 @@ TEST(DirectGLESSanity, BindingZeroClearsPreviousNativeTextureBinding) {
backendTexture = MakeShared<DirectGLES::TextureImpl::BackendTextureObject>();
const GLuint backendTextureId = backendTexture->GetBackendTextureId();
// Each walk below is the texture half of one draw, so each gets its own verb (the second
// and third stand after frontend state moved, exactly as a second entry point's fill would).
MG_Test::ScopedPipeVerb draw(MG_Pipe::MGPipeVerb::DrawArrays);
DirectGLES::BindCurrentTextures();
ASSERT_EQ(state.bindCalls.size(), 1u);
EXPECT_EQ(state.bindCalls[0].target, GL_TEXTURE_2D);
@@ -369,6 +376,7 @@ TEST(DirectGLESSanity, BindingZeroClearsPreviousNativeTextureBinding) {
// The default 1D slot maps to the same native ES target as 2D. It must not clear and force a
// redundant rebind while the real 2D frontend object remains current.
draw.Renew();
DirectGLES::BindCurrentTextures();
EXPECT_EQ(state.bindCalls.size(), 1u);
@@ -379,6 +387,7 @@ TEST(DirectGLESSanity, BindingZeroClearsPreviousNativeTextureBinding) {
.GetBoundObject()
.get()));
draw.Renew();
DirectGLES::BindCurrentTextures();
ASSERT_EQ(state.bindCalls.size(), 2u);
@@ -3015,7 +3024,9 @@ TEST(DirectGLESTextureSync, UnitMemoRefusesToDriveATwinFromAnotherTexture) {
ASSERT_NE(resident, nullptr);
// First sync: builds the memo with unit 0 -> `resident`, and gives `resident`'s twin its
// 16x16 backend storage.
// 16x16 backend storage. The unit walk is the per-draw one, so both syncs below stand in a
// draw - the fill is what a real glDraw* would have done before reaching this helper.
MG_Test::ScopedPipeVerb draw(MG_Pipe::MGPipeVerb::DrawArrays);
MG_Backend::DirectGLES::TextureImpl::SyncNeccessaryTextures();
auto* residentSlot = MG_Backend::DirectGLES::TextureImpl::g_backendTextureObjects.Find(resident.get());
ASSERT_NE(residentSlot, nullptr);
@@ -3031,6 +3042,9 @@ TEST(DirectGLESTextureSync, UnitMemoRefusesToDriveATwinFromAnotherTexture) {
MG_State::pGLContext->GetTextureUnitObject(0).GetBindingSlot(TextureTarget::Texture2D).Bind(foreign);
const SizeT specsBeforeReplay = specs.size();
// The second draw. Its fill re-reads the memo's keys off the live context, which is the
// premise being tested: the silent slot swap moved none of them.
draw.Renew();
MG_Backend::DirectGLES::TextureImpl::SyncNeccessaryTextures();
// `foreign` must have been synced through its OWN twin...