mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 20:28:32 +09:00
[Test] (Pipe): pin the base instance across a make-current and both of the validate point's exits, the record surviving one, the draw-time bind mask, the create-destroy pairing latch, and the bind-mask table against literals
This commit is contained in:
@@ -58,6 +58,7 @@
|
||||
#include <Config.h>
|
||||
#include <MG_Impl/Pipe/ResourceTracker.h>
|
||||
#include <MG_Impl/Pipe/SlotAllocator.h>
|
||||
#include <MG_Impl/Pipe/VertexInputEmit.h>
|
||||
#include <MG_Pipe/PipeApply.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
|
||||
@@ -1331,7 +1332,10 @@ namespace {
|
||||
X(ResourceEmit, EveryBufferTargetSetsItsBindMaskBit) \
|
||||
X(ResourceEmit, ABindMaskBitIsStickyAcrossARespecifyThatDoesNotRebind) \
|
||||
X(ResourceEmit, ADestroyedBufferReleasesItsSlotAndAStaleHandleResolvesToNothing) \
|
||||
X(ResourceEmit, AWholeBufferSubDataBeyondTheRecordBoundIsSplitIntoContiguousRecords)
|
||||
X(ResourceEmit, AWholeBufferSubDataBeyondTheRecordBoundIsSplitIntoContiguousRecords) \
|
||||
X(ResourceEmit, ABufferCreatedBeforeAMakeCurrentStillLandsItsSubDataAfterOne) \
|
||||
X(ResourceEmit, ADrawTimeIndexBindingPublishesElementArrayEvenWhenTheRespecifyCannotSeeIt) \
|
||||
X(ResourceEmit, ADestroyFollowsTheCreateEvenIfTheOpTableWasUnregisteredMeanwhile)
|
||||
|
||||
#define MGL_DECLARE_PULL_SKIP(Suite, Name) \
|
||||
TEST(Suite, Name) { GTEST_SKIP() << "compiled only under MOBILEGL_PIPE_PUSH"; }
|
||||
@@ -1399,6 +1403,49 @@ namespace {
|
||||
return false;
|
||||
}
|
||||
|
||||
// THE ORACLE FOR THE BIND-MASK TABLE, spelled out here as raw bit positions rather than
|
||||
// by calling MGPipeBindMaskForBufferTarget: comparing the emitted mask against the table
|
||||
// under test pins the plumbing and not the table, and the risk register calls this table
|
||||
// the one P3a deliverable whose only real gate is a unit case. The numbers are
|
||||
// MGPipeTypes.h's documented order - VERTEX|INDEX|CONSTANT|SHADER_BUFFER|INDIRECT|
|
||||
// SAMPLER|SHADER_IMAGE|RENDER_TARGET|DEPTH_STENCIL|STREAM_OUTPUT|ATOMIC|ELEMENT_ARRAY -
|
||||
// read off that list and not off the enum, so a renumbering of MGPipeBindBit that the
|
||||
// table follows still fails here.
|
||||
//
|
||||
// No `default:`, for the table's own reason: a new BufferTarget must be a build break in
|
||||
// both places rather than a bit that quietly stops being published.
|
||||
Uint32 LiteralBindMaskFor(BufferTarget target) {
|
||||
switch (target) {
|
||||
case BufferTarget::Vertex:
|
||||
return 1u << 0; // VERTEX
|
||||
case BufferTarget::Index:
|
||||
return (1u << 1) | (1u << 11); // INDEX | ELEMENT_ARRAY
|
||||
case BufferTarget::Uniform:
|
||||
return 1u << 2; // CONSTANT
|
||||
case BufferTarget::ShaderStorage:
|
||||
return 1u << 3; // SHADER_BUFFER
|
||||
case BufferTarget::DispatchIndirect:
|
||||
case BufferTarget::DrawIndirect:
|
||||
case BufferTarget::Parameter:
|
||||
return 1u << 4; // INDIRECT
|
||||
case BufferTarget::Texture:
|
||||
return 1u << 5; // SAMPLER
|
||||
case BufferTarget::TransformFeedback:
|
||||
return 1u << 9; // STREAM_OUTPUT
|
||||
case BufferTarget::AtomicCounter:
|
||||
return 1u << 10; // ATOMIC
|
||||
case BufferTarget::CopyRead:
|
||||
case BufferTarget::CopyWrite:
|
||||
case BufferTarget::PixelPack:
|
||||
case BufferTarget::PixelUnpack:
|
||||
case BufferTarget::Query:
|
||||
case BufferTarget::BufferTargetCount:
|
||||
case BufferTarget::Unknown:
|
||||
return 0u; // transfer and query targets bind nothing
|
||||
}
|
||||
return 0xFFFFFFFFu;
|
||||
}
|
||||
|
||||
// D-A3, and the risk register calls this the one P3a deliverable whose only real gate is
|
||||
// a unit test: a wrong ELEMENT_ARRAY bit silently disables restart rewriting and
|
||||
// multi-draw flattening under split and is invisible in monolith.
|
||||
@@ -1433,7 +1480,12 @@ namespace {
|
||||
buffer->Respecify(64, nullptr);
|
||||
|
||||
const MGPResourceDesc respecified = tracker.LastDesc();
|
||||
const auto expected = static_cast<Uint16>(MGPipeBindMaskForBufferTarget(target));
|
||||
// AGAINST THE LITERAL, not against the table this case exists to police.
|
||||
const auto expected = static_cast<Uint16>(LiteralBindMaskFor(target));
|
||||
EXPECT_EQ(MGPipeBindMaskForBufferTarget(target), LiteralBindMaskFor(target))
|
||||
<< "the BufferTarget -> bit table disagrees with MGPipeTypes.h's documented bit "
|
||||
"order for target "
|
||||
<< i;
|
||||
EXPECT_EQ(respecified.BindMask, expected)
|
||||
<< "BindMask for BufferTarget " << i << " (" << respecified.BindMask << " vs " << expected << ")";
|
||||
EXPECT_EQ(respecified.Resource, created.Resource) << "a respecify keeps the handle";
|
||||
@@ -1449,6 +1501,15 @@ namespace {
|
||||
// table edit that moved it is a failure here rather than a silent P8 regression.
|
||||
EXPECT_EQ(MGPipeBindMaskForBufferTarget(BufferTarget::Index),
|
||||
static_cast<Uint32>(kMGPipeBindIndex | kMGPipeBindElementArray));
|
||||
// Every enumerator, including the ones the loop above skips because no entry point
|
||||
// binds them through a global slot: the TABLE is the thing P8 keys on, and a row that
|
||||
// moved for an unbindable target is exactly as silent as one that moved for a
|
||||
// bindable one.
|
||||
for (SizeT i = 0; i < static_cast<SizeT>(BufferTarget::BufferTargetCount); ++i) {
|
||||
const auto target = static_cast<BufferTarget>(i);
|
||||
EXPECT_EQ(MGPipeBindMaskForBufferTarget(target), LiteralBindMaskFor(target))
|
||||
<< "the bind-mask row for BufferTarget " << i << " is not the documented bit";
|
||||
}
|
||||
}
|
||||
|
||||
// Sticky means ORed and never cleared, exactly like the image-bindable hint. A buffer
|
||||
@@ -1609,6 +1670,127 @@ namespace {
|
||||
kSegment * 4, collect, kSegment));
|
||||
EXPECT_TRUE(pieces.empty());
|
||||
}
|
||||
|
||||
// B-C2 FROM THE CLIENT'S SIDE, with a real BufferObject rather than a synthetic handle.
|
||||
// ResourceEmit.TheObjectRecordsSurviveAMakeCurrentAndOnlyTheWorkingStateIsReset drives the
|
||||
// applier's half; this drives the CLIENT's: the record's only producer is the buffer's
|
||||
// CONSTRUCTOR, which a context switch does not re-run, so if a make-current dropped the
|
||||
// record there would be nothing to re-publish it and the next glBufferSubData on a
|
||||
// share-group buffer would resolve to nothing and be refused - a lost upload, in a build
|
||||
// where the refusal's assertion has compiled out.
|
||||
//
|
||||
// THE RULE THIS PINS is the one ResourceTracker.h states beside ResetForTest: a handle and
|
||||
// its record are share-group object state, and the only things that drop a record are the
|
||||
// object's own death signal and the served context's teardown. The client therefore has NO
|
||||
// re-publication path on a fresh context and must not grow one.
|
||||
TEST(ResourceEmit, ABufferCreatedBeforeAMakeCurrentStillLandsItsSubDataAfterOne) {
|
||||
PushArm arm;
|
||||
MGPipeResourceTracker& tracker = MGPipeResourceTrackerInstance();
|
||||
|
||||
// ctxA: the buffer exists and has a store.
|
||||
const SharedPtr<BufferObject> shared = MakeBuffer(1);
|
||||
shared->Respecify(256, nullptr);
|
||||
const MGPipeHandle handle = tracker.Find(*shared);
|
||||
ASSERT_FALSE(MGPipeHandleIsNull(handle));
|
||||
ASSERT_TRUE(RecordOf(handle.Slot).Live) << "the constructor's create never reached the applier";
|
||||
ASSERT_EQ(RecordOf(handle.Slot).Desc.Width, 256u);
|
||||
const Uint64 serialBefore = RecordOf(handle.Slot).Serial;
|
||||
|
||||
// The make-current, exactly as MGPipeValidateForVerb's FreshlyPrimed arm performs it.
|
||||
// The buffer is held by this case, which is what a share group is: the context went
|
||||
// away, the object did not.
|
||||
MGPipeApplierReset();
|
||||
MG_State::pGLContext = MakeUnique<GLContext>();
|
||||
|
||||
ASSERT_TRUE(RecordOf(handle.Slot).Live)
|
||||
<< "a make-current dropped the record of a buffer the switch did not destroy";
|
||||
EXPECT_EQ(tracker.Find(*shared), handle) << "the handle is client state and does not move";
|
||||
|
||||
Array<Uint8, 32> bytes{};
|
||||
shared->UploadSubData(DataPtr{bytes.data(), bytes.size()}, 0);
|
||||
|
||||
EXPECT_GT(RecordOf(handle.Slot).Serial, serialBefore)
|
||||
<< "the first write after a make-current moved no serial - it was dropped";
|
||||
EXPECT_EQ(MGPipeApplier().RefusedResourceCalls, 0u)
|
||||
<< "the write was refused because the applier had no record for it";
|
||||
EXPECT_EQ(tracker.CreateCount(), 1u)
|
||||
<< "the client re-published a create for a record the applier still had";
|
||||
}
|
||||
|
||||
// THE DSA HOLE, which is what makes the sampled mask insufficient on its own: an element
|
||||
// buffer bound once, drawn with, unbound, and then defined through glNamedBuffer* has no
|
||||
// binding at all at the moment the respecify samples - and MC 26.3 streams with exactly
|
||||
// that idiom (TryAdoptLargeStorage's comment names glNamedBufferSubData). The ELEMENT_ARRAY
|
||||
// bit is the split path's kCapNeedsHostIndexBytes switch, so losing it silently disables
|
||||
// restart rewriting and multi-draw flattening and is invisible in monolith.
|
||||
//
|
||||
// What closes it is NoteBoundAs from the draw-time emitters: any buffer ever fetched from
|
||||
// carries its bit for the rest of its life.
|
||||
TEST(ResourceEmit, ADrawTimeIndexBindingPublishesElementArrayEvenWhenTheRespecifyCannotSeeIt) {
|
||||
PushArm arm;
|
||||
MGPipeResourceTracker& tracker = MGPipeResourceTrackerInstance();
|
||||
MGPipeVertexInputEmitterInstance().Reset();
|
||||
|
||||
const SharedPtr<BufferObject> indices = MakeBuffer(1);
|
||||
const SharedPtr<BufferObject> vertices = MakeBuffer(2);
|
||||
|
||||
// The negative control FIRST, so the assertion below cannot pass because the bit is
|
||||
// set for everything: a respecify with nothing bound publishes an empty mask.
|
||||
indices->Respecify(64, nullptr);
|
||||
ASSERT_EQ(tracker.LastDesc().BindMask, 0u)
|
||||
<< "a respecify with no binding at all published one";
|
||||
|
||||
// Bind, draw, unbind - the transient the sampler cannot see afterwards.
|
||||
Ctx().GetBoundVertexArray()->GetIndexBufferBindingSlot().Bind(indices);
|
||||
Ctx().GetBoundVertexArray()->BindAttributeBuffer(0, vertices);
|
||||
Ctx().GetBoundVertexArray()->EnableAttribute(0);
|
||||
MGPipeVertexInputEmitterInstance().EmitIndexBuffer(Ctx());
|
||||
MGPipeVertexInputEmitterInstance().EmitVertexBuffers(Ctx(), 0);
|
||||
Ctx().GetBoundVertexArray()->GetIndexBufferBindingSlot().Bind(nullptr);
|
||||
Ctx().GetBoundVertexArray()->BindAttributeBuffer(0, nullptr);
|
||||
|
||||
// The DSA respecify: nothing is bound now, and the bit still goes out.
|
||||
indices->Respecify(128, nullptr);
|
||||
EXPECT_EQ(tracker.LastDesc().BindMask & kMGPipeBindElementArray, kMGPipeBindElementArray)
|
||||
<< "the element-array bit was lost because the buffer was not bound at the respecify";
|
||||
EXPECT_EQ(tracker.LastDesc().BindMask & kMGPipeBindIndex, kMGPipeBindIndex);
|
||||
|
||||
vertices->Respecify(128, nullptr);
|
||||
EXPECT_EQ(tracker.LastDesc().BindMask & kMGPipeBindVertex, kMGPipeBindVertex)
|
||||
<< "a buffer drawn from as a vertex array published no ARRAY_BUFFER bit";
|
||||
}
|
||||
|
||||
// CREATE AND DESTROY ARE GATED AT TWO DIFFERENT MOMENTS - the create at its call site in
|
||||
// the constructor, the destroy inside the emit-then-free helper - so asking
|
||||
// MGPipeResourceSubsystemEnabled() twice pairs an emission taken under one registration
|
||||
// with a decision taken under another. A buffer that outlives its backend's table would
|
||||
// then free its slot with the applier's record still Live, and the allocator is about to
|
||||
// hand that slot out again; a stale generation is the only thing between that and a
|
||||
// cross-buffer id mix-up on the backend's side. The answer is latched at the create.
|
||||
TEST(ResourceEmit, ADestroyFollowsTheCreateEvenIfTheOpTableWasUnregisteredMeanwhile) {
|
||||
PushArm arm;
|
||||
MGPipeResourceTracker& tracker = MGPipeResourceTrackerInstance();
|
||||
// Owned by the case, so "the last reference drops" is one line below and not a chain
|
||||
// of unbinds through a context that is about to be torn down anyway.
|
||||
SharedPtr<BufferObject> buffer = MakeShared<BufferObject>(1);
|
||||
const MGPipeHandle handle = tracker.Find(*buffer);
|
||||
ASSERT_FALSE(MGPipeHandleIsNull(handle));
|
||||
ASSERT_TRUE(RecordOf(handle.Slot).Live) << "the constructor's create never reached the applier";
|
||||
|
||||
// The backend goes away while the buffer is still alive.
|
||||
MGPipeSetResourceOps(nullptr);
|
||||
ASSERT_FALSE(MGPipeResourceSubsystemEnabled());
|
||||
|
||||
const Uint64 destroysBefore = tracker.DestroyCount();
|
||||
buffer.reset();
|
||||
|
||||
EXPECT_EQ(tracker.DestroyCount(), destroysBefore + 1)
|
||||
<< "the destroy was gated on the live predicate rather than on the create's own latch";
|
||||
EXPECT_FALSE(RecordOf(handle.Slot).Live)
|
||||
<< "the applier's record outlived the object, on a slot the allocator will hand out again";
|
||||
EXPECT_FALSE(MGPipeSlots().IsLive(MGPipeKind::Buffer, handle));
|
||||
EXPECT_EQ(MGPipeApplier().RefusedResourceCalls, 0u);
|
||||
}
|
||||
#endif // MOBILEGL_PIPE_PUSH
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <MG_Impl/Pipe/PipeFill.h>
|
||||
#include <MG_Impl/Pipe/SetHashSuppressor.h>
|
||||
#include <MG_Impl/Pipe/Tracker.h>
|
||||
#include <MG_Impl/Pipe/VertexInputEmit.h>
|
||||
#include <MG_Pipe/MGPipeRenderStateSpans.h>
|
||||
#include <MG_Pipe/PipeApply.h>
|
||||
#include <MG_Pipe/PipeMutation.h>
|
||||
@@ -74,6 +75,7 @@ namespace {
|
||||
X(TrackerWalk, TheFireTalliesOnlyRunWhilePipeStatsIsOn) \
|
||||
X(TrackerWalk, TheIndexBufferBitDoesNotFireOnAnUnrelatedBufferWrite) \
|
||||
X(TrackerWalk, TheIndexBufferBitFiresWhenTheSlotVersionWrapsOntoADifferentBuffer) \
|
||||
X(TrackerWalk, ABaseInstanceSurvivesTheFirstWalkOnAFreshContext) \
|
||||
X(TrackerAttribPayload, AFloatWriteCarriesTheFloatBitsAndNamesItsClass) \
|
||||
X(TrackerAttribPayload, AnIntWriteCarriesTheIntWordsAndNamesItsClass) \
|
||||
X(TrackerAttribPayload, AUintWriteCarriesTheUintWordsAndNamesItsClass) \
|
||||
@@ -84,7 +86,8 @@ namespace {
|
||||
X(TrackerShippedEmitter, AViewportThroughTheValidatePointMintsNoCso) \
|
||||
X(TrackerShippedEmitter, AClipDistanceEnableReArmsTheResidualBlock) \
|
||||
X(TrackerShippedEmitter, AFreshContextRepublishesEveryVertexAttributeDefault) \
|
||||
X(TrackerShippedEmitter, AFreshContextResetsTheApplierWithTheRenderStateSubsystemOff)
|
||||
X(TrackerShippedEmitter, AFreshContextResetsTheApplierWithTheRenderStateSubsystemOff) \
|
||||
X(TrackerShippedEmitter, ABaseInstancedDrawAfterAMakeCurrentPublishesItsOwnBaseInstance)
|
||||
|
||||
#define MGL_DECLARE_PULL_SKIP(Suite, Name) \
|
||||
TEST(Suite, Name) { GTEST_SKIP() << "compiled only under MOBILEGL_PIPE_PUSH"; }
|
||||
@@ -238,12 +241,21 @@ namespace {
|
||||
// the next bind_render_state naming a CSO the applier no longer has - it asserts and
|
||||
// returns, leaving m_renderState unwritten. The three are one state, so they are reset
|
||||
// together, here and in TrackerShippedEmitter.
|
||||
// P3a adds two more pieces to that one state. MGPipeApplierReset is a MAKE-CURRENT and
|
||||
// deliberately keeps the object records now, so a fixture that means "this applier is
|
||||
// going away" has to say the other verb as well (PipeApply.h); and the vertex-input
|
||||
// emitter's latches say "this handle has already published this configuration" about an
|
||||
// applier that is about to be empty, so a bind would be suppressed against a record that
|
||||
// is no longer there.
|
||||
void ResetTheServerSideSingletons() {
|
||||
MGPipeApplierReset();
|
||||
MGPipeApplierReleaseObjectRecords();
|
||||
MGPipeCsoCacheInstance().Reset();
|
||||
MGPipeCsoCacheInstance().ResetCounters();
|
||||
MGPipeTrackerInstance().Reset();
|
||||
MGPipeSetHashSuppressorInstance().InvalidateAll();
|
||||
MGPipeVertexInputEmitterInstance().Reset();
|
||||
MGPipeVertexInputEmitterInstance().ResetCounters();
|
||||
}
|
||||
|
||||
class TrackerWalk : public ::testing::Test {
|
||||
@@ -544,6 +556,34 @@ namespace {
|
||||
m_cache.Reset();
|
||||
}
|
||||
|
||||
// THE PENDING BASE INSTANCE IS THIS CALL'S ARGUMENT, NOT A LATCH, and Update() is where
|
||||
// the difference bites: it calls Reset() from inside itself whenever the current
|
||||
// GLContext pointer moves, and the draw entry point wrote the value one statement EARLIER
|
||||
// (D-H2.1 puts MGP_SET_BASE_INSTANCE immediately above MGP_FILL, and Update is inside
|
||||
// MGP_FILL). So `eglMakeCurrent(ctxB); glDrawArraysInstancedBaseInstance(..., 7)` used to
|
||||
// put a BaseInstance of 0 on the wire - one silently mis-shifted instanced draw per
|
||||
// context switch, on the emulation path, invisible to a single-context retrace corpus and
|
||||
// to every case that drives the emitter directly.
|
||||
//
|
||||
// A FRESH TRACKER IS EXACTLY THAT SWITCH: m_context starts null, so the first Walk() takes
|
||||
// the same `m_context != &ctx` branch a make-current does.
|
||||
TEST_F(TrackerWalk, ABaseInstanceSurvivesTheFirstWalkOnAFreshContext) {
|
||||
m_tracker.SetPendingBaseInstance(7);
|
||||
const Uint32 dirty = Walk();
|
||||
EXPECT_EQ(m_tracker.PendingBaseInstance(), 7u)
|
||||
<< "the walk that follows a make-current cleared the base instance the same call set";
|
||||
EXPECT_NE(dirty & MGPipeDirtyBit(MGPipeDirty::NewVertexBuffers), 0u)
|
||||
<< "NEW_VERTEX_BUFFERS did not fire on the first walk of a fresh context";
|
||||
|
||||
// And the control, so the assertion above is about Reset() and not about a value that
|
||||
// is never cleared at all: the verb that consumes it clears it, and the next walk on
|
||||
// the SAME context then sees 0.
|
||||
m_tracker.ClearPendingBaseInstance();
|
||||
Walk();
|
||||
EXPECT_EQ(m_tracker.PendingBaseInstance(), 0u);
|
||||
m_cache.Reset();
|
||||
}
|
||||
|
||||
// ===================================================================================
|
||||
// set_vertex_attrib_defaults' payload (P2 brief D10)
|
||||
// ===================================================================================
|
||||
@@ -810,5 +850,68 @@ namespace {
|
||||
EXPECT_EQ(Cso().Binds, 0u) << "glViewport reached the CSO cache";
|
||||
}
|
||||
|
||||
// B-C1's scenario END TO END, through the shipped entry point and read back off the real
|
||||
// applier: the setter runs, THEN a make-current happens inside the same fill (the fresh
|
||||
// GLContext below is the switch), and what set_vertex_buffers carries has to be the 7 the
|
||||
// draw entry point passed - not the 0 the tracker's context Reset used to leave behind.
|
||||
//
|
||||
// It also pins the two halves D-H2.3 makes one property: the applier's raw
|
||||
// VertexFetchBaseInstance, and the ContentHash the suppressor keys on - which has to mix
|
||||
// BaseInstance in, or the SECOND draw at a different base instance over the same buffer
|
||||
// set would be suppressed as unchanged and the server would keep the first one's shift.
|
||||
TEST_F(TrackerShippedEmitter, ABaseInstancedDrawAfterAMakeCurrentPublishesItsOwnBaseInstance) {
|
||||
MG_Config::Features.PipePush = kMGPipeSubsystemsMigratedAtP2 | kMGPipeSubsystemResources |
|
||||
kMGPipeSubsystemVertexInput;
|
||||
Draw(); // prime this context, so the switch below is a real make-current
|
||||
ASSERT_EQ(MGPipeApplier().VertexFetchBaseInstance, 0u);
|
||||
|
||||
// The make-current, then the entry point's one line, then the fill - in that order,
|
||||
// which is the order GL_Drawing.cpp has.
|
||||
MG_State::pGLContext = MakeUnique<GLContext>();
|
||||
const SharedPtr<MG_State::GLState::BufferObject> vertices = Ctx().CreateBufferObject(1);
|
||||
vertices->Respecify(4096, nullptr);
|
||||
Ctx().GetBoundVertexArray()->SetAttributeFormat(0, 4, DataType::Float32, false, 16, 0, false, false, -1);
|
||||
Ctx().GetBoundVertexArray()->BindAttributeBuffer(0, vertices);
|
||||
Ctx().GetBoundVertexArray()->EnableAttribute(0);
|
||||
|
||||
MGPipeSetPendingBaseInstance(7);
|
||||
ASSERT_EQ(MGPipePendingBaseInstance(), 7u) << "the setter did not take";
|
||||
Draw();
|
||||
|
||||
EXPECT_EQ(MGPipeApplier().VertexFetchBaseInstance, 7u)
|
||||
<< "the make-current between the setter and the fill ate the base instance";
|
||||
EXPECT_EQ(MGPipeApplier().VertexBufferCount, 1u);
|
||||
const Uint64 hashAtSeven = MGPipeVertexInputEmitterInstance().LastVertexBuffers().ContentHash;
|
||||
EXPECT_EQ(hashAtSeven,
|
||||
MGPipeVertexBufferSetContentHash(MGPipeVertexInputEmitterInstance().LastEntries().data(), 0,
|
||||
1, 7))
|
||||
<< "the emitted set's ContentHash does not include the base instance it went out with";
|
||||
|
||||
// CONSUMED by the verb that carried it: the next plain draw sees 0 again, and the
|
||||
// clear that makes that true is the validate point's, not MGPipeLeaveVerb's (no GL
|
||||
// entry point calls that one).
|
||||
EXPECT_EQ(MGPipePendingBaseInstance(), 0u);
|
||||
const Uint64 setsAtSeven = MGPipeVertexInputEmitterInstance().VertexBufferSetCount();
|
||||
Draw();
|
||||
EXPECT_EQ(MGPipeApplier().VertexFetchBaseInstance, 0u)
|
||||
<< "a plain draw after a base-instanced one kept the previous fetch shift";
|
||||
EXPECT_GT(MGPipeVertexInputEmitterInstance().VertexBufferSetCount(), setsAtSeven)
|
||||
<< "the buffer set was suppressed on a base-instance-only change - the hash or the "
|
||||
"bit-9 shutter is missing it";
|
||||
EXPECT_NE(MGPipeVertexInputEmitterInstance().LastVertexBuffers().ContentHash, hashAtSeven);
|
||||
|
||||
// THE OTHER EXIT. MGPipeValidateForVerb returns early when there is no live context,
|
||||
// which skips step 3 and therefore skips step 3's clear - and since Reset() no longer
|
||||
// clears it either, that exit is the only path left on which a base instance could
|
||||
// stand into the next verb. A draw with no context is a no-op; its argument must not
|
||||
// outlive it.
|
||||
MGPipeSetPendingBaseInstance(11);
|
||||
UniquePtr<GLContext> parked = Move(MG_State::pGLContext);
|
||||
Draw();
|
||||
EXPECT_EQ(MGPipePendingBaseInstance(), 0u)
|
||||
<< "the no-live-context exit left the draw's base instance standing for the next verb";
|
||||
MG_State::pGLContext = Move(parked);
|
||||
}
|
||||
|
||||
#endif // MOBILEGL_PIPE_PUSH
|
||||
} // namespace
|
||||
|
||||
Reference in New Issue
Block a user