mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
[Test] (Pipe): pin the push-on-mutation shape - a frontend write inside a verb refreshes the pushed field, and only its value
- AFrontendMutationInsideAVerbRefreshesThePushedField: fills DrawArrays, then
does what UniformManager's fallback path does mid-draw (a SamplerObject filter
change) and what a bind reached from inside a verb does
(NoteTextureUnitTouched), and asserts the block still equals the live context
for GetSamplingResolutionGeneration, GetTextureBindGeneration and
GetMaxTouchedTextureUnit.
- AFrontendMutationInsideAVerbDoesNotDivergeAtRead: the lane failure end to end,
under the armed comparator in a forked child - the read after the mutation
must complete and the log must carry no Fatal{.
- TheMutationNoticeRefreshesTheValueButNotTheStamp: the notice must not restamp
a field whose stamp the fill withheld (negative control B), and must not stamp
a field the verb class never fills (the generation under a kQuery verb).
- Falsified: with the notice's body short-circuited, the first two fail (the
read-side one by SIGABRT on Fatal{PipeVerifyDiffer,
"GetSamplingResolutionGeneration@DrawArrays", where=read}) and the third
stays green, which is what a guard case should do.
- The three names also exist as visible GTEST_SKIPs in the pull build, as every
other case in this file does.
This commit is contained in:
@@ -170,6 +170,15 @@ TEST(PipeInputsTest,BadVerifyCorruptKnobIsFatalNamingTheKnob) {
|
||||
TEST(PipeInputsTest,VerifyFatalOffLogsTheDivergenceAndContinues) {
|
||||
GTEST_SKIP() << "push not compiled in (MOBILEGL_PIPE_PUSH=OFF)";
|
||||
}
|
||||
TEST(PipeInputsTest,AFrontendMutationInsideAVerbRefreshesThePushedField) {
|
||||
GTEST_SKIP() << "push not compiled in (MOBILEGL_PIPE_PUSH=OFF)";
|
||||
}
|
||||
TEST(PipeInputsTest,TheMutationNoticeRefreshesTheValueButNotTheStamp) {
|
||||
GTEST_SKIP() << "push not compiled in (MOBILEGL_PIPE_PUSH=OFF)";
|
||||
}
|
||||
TEST(PipeInputsTest,AFrontendMutationInsideAVerbDoesNotDivergeAtRead) {
|
||||
GTEST_SKIP() << "push not compiled in (MOBILEGL_PIPE_PUSH=OFF)";
|
||||
}
|
||||
|
||||
#else // MOBILEGL_PIPE_PUSH
|
||||
|
||||
@@ -515,6 +524,102 @@ TEST_F(PipeInputsTest, EveryVerbFillsItsClassAndNothingElse) {
|
||||
#endif
|
||||
}
|
||||
|
||||
// Push on mutation (P1 lane finding F2, MG_Pipe/PipeMutation.h). The backends write into
|
||||
// frontend objects inside their own verb - Magma synthesises a fallback texture for an
|
||||
// unbound sampler and overrides a unit's sampler filter (VulkanRenderer/UniformManager) -
|
||||
// and every such write moves a counter the verb boundary already copied. The frontend
|
||||
// mutator refreshes that field, so the block still equals the live context at every read.
|
||||
// Without the notice the two reads below differ and the pushed value is the stale one.
|
||||
TEST_F(PipeInputsTest, AFrontendMutationInsideAVerbRefreshesThePushedField) {
|
||||
auto& ctx = *MG_State::pGLContext;
|
||||
MGPipeFillForVerb(MGPipeVerb::DrawArrays);
|
||||
ASSERT_EQ(gPipeInputs.GetSamplingResolutionGeneration(), ctx.GetSamplingResolutionGeneration());
|
||||
ASSERT_EQ(gPipeInputs.GetTextureBindGeneration(), ctx.GetTextureBindGeneration());
|
||||
ASSERT_EQ(gPipeInputs.GetMaxTouchedTextureUnit(), ctx.GetMaxTouchedTextureUnit());
|
||||
|
||||
// What UniformManager's fallback path does mid-draw: change a sampler object's filter,
|
||||
// which bumps the context-wide sampling-resolution generation (SamplerObject.cpp).
|
||||
const Uint64 samplingBefore = ctx.GetSamplingResolutionGeneration();
|
||||
auto sampler = MakeShared<MG_State::GLState::SamplerObject>(0u);
|
||||
sampler->SetMinFilter(sampler->GetMinFilter() == SamplerFilterMode::Nearest ? SamplerFilterMode::Linear
|
||||
: SamplerFilterMode::Nearest);
|
||||
ASSERT_NE(ctx.GetSamplingResolutionGeneration(), samplingBefore) << "the mutation did not move the counter";
|
||||
EXPECT_EQ(gPipeInputs.GetSamplingResolutionGeneration(), ctx.GetSamplingResolutionGeneration());
|
||||
|
||||
// And a bind reached from inside a verb moves both the bind generation and the
|
||||
// high-water mark of touched units (TextureState::NoteUnitTouched).
|
||||
const Uint64 bindBefore = ctx.GetTextureBindGeneration();
|
||||
const Int unit = ctx.GetMaxTouchedTextureUnit() + 1;
|
||||
ctx.NoteTextureUnitTouched(unit);
|
||||
ASSERT_NE(ctx.GetTextureBindGeneration(), bindBefore) << "the bind did not move the counter";
|
||||
ASSERT_EQ(ctx.GetMaxTouchedTextureUnit(), unit);
|
||||
EXPECT_EQ(gPipeInputs.GetTextureBindGeneration(), ctx.GetTextureBindGeneration());
|
||||
EXPECT_EQ(gPipeInputs.GetMaxTouchedTextureUnit(), ctx.GetMaxTouchedTextureUnit());
|
||||
}
|
||||
|
||||
// The notice refreshes the VALUE and never the stamp: a field this verb withheld the stamp
|
||||
// of (negative control B) must stay stale, and a field outside the verb class's mask must
|
||||
// stay unfilled rather than be healed by an unrelated frontend write.
|
||||
TEST_F(PipeInputsTest, TheMutationNoticeRefreshesTheValueButNotTheStamp) {
|
||||
#if !MOBILEGL_PIPE_POISON
|
||||
GTEST_SKIP() << "poison not compiled in (MOBILEGL_PIPE_POISON=0)";
|
||||
#else
|
||||
auto& ctx = *MG_State::pGLContext;
|
||||
MGPipeSetPoisonOmission("DrawArrays", "GetSamplingResolutionGeneration");
|
||||
MGPipeFillForVerb(MGPipeVerb::DrawArrays);
|
||||
ASSERT_FALSE(Fresh(MGPipeInputField::GetSamplingResolutionGeneration));
|
||||
ctx.BumpSamplingResolutionGeneration();
|
||||
EXPECT_FALSE(Fresh(MGPipeInputField::GetSamplingResolutionGeneration))
|
||||
<< "the notice restamped a field whose stamp the fill withheld";
|
||||
|
||||
// FenceSync is a kQuery verb: its mask holds no texture field at all, so the notice must
|
||||
// leave the generation unfilled and a read of it Fatal{UnmigratedPipeInput}.
|
||||
MGPipeSetPoisonOmission(nullptr, nullptr);
|
||||
MGPipeFillForVerb(MGPipeVerb::FenceSync);
|
||||
ASSERT_FALSE(Fresh(MGPipeInputField::GetSamplingResolutionGeneration));
|
||||
ctx.BumpSamplingResolutionGeneration();
|
||||
EXPECT_FALSE(Fresh(MGPipeInputField::GetSamplingResolutionGeneration))
|
||||
<< "the notice stamped a field the verb class never fills";
|
||||
#endif
|
||||
}
|
||||
|
||||
// The lane failure itself (six DirectVulkan.Verify.UnboundImageDescriptorScenario entries,
|
||||
// two SampledSetStalenessScenario, two retrace cases): a frontend write made inside a draw
|
||||
// used to make the very next read of the pushed block differ from the live context. The
|
||||
// child reproduces it end to end under the armed comparator and must survive; without the
|
||||
// notice it dies of Fatal{PipeVerifyDiffer, "GetSamplingResolutionGeneration@DrawArrays",
|
||||
// where=read}.
|
||||
TEST_F(PipeInputsTest, AFrontendMutationInsideAVerbDoesNotDivergeAtRead) {
|
||||
#if !MOBILEGL_PIPE_VERIFY
|
||||
GTEST_SKIP() << "verify not compiled in (MOBILEGL_PIPE_VERIFY=OFF)";
|
||||
#elif !MGTEST_HAVE_FORK
|
||||
GTEST_SKIP() << "no fork() on this platform";
|
||||
#else
|
||||
ASSERT_FALSE(g_logPath.empty()) << "main() did not set MOBILEGL_LOG_FILE_PATH";
|
||||
const ChildResult r = RunInChild([] {
|
||||
MG_Config::Features.PipeVerify = true;
|
||||
MGPipeFillForVerb(MGPipeVerb::DrawArrays);
|
||||
(void)gPipeInputs.GetSamplingResolutionGeneration(); // boundary == live: completes
|
||||
auto& ctx = *MG_State::pGLContext;
|
||||
const Uint64 before = ctx.GetSamplingResolutionGeneration();
|
||||
auto sampler = MakeShared<MG_State::GLState::SamplerObject>(0u);
|
||||
sampler->SetMinFilter(sampler->GetMinFilter() == SamplerFilterMode::Nearest ? SamplerFilterMode::Linear
|
||||
: SamplerFilterMode::Nearest);
|
||||
if (ctx.GetSamplingResolutionGeneration() == before) {
|
||||
::_exit(7); // the mutation did not take: the case would pass for the wrong reason
|
||||
}
|
||||
(void)gPipeInputs.GetSamplingResolutionGeneration(); // Fatal{PipeVerifyDiffer} without the notice
|
||||
ctx.NoteTextureUnitTouched(ctx.GetMaxTouchedTextureUnit() + 1);
|
||||
(void)gPipeInputs.GetTextureBindGeneration();
|
||||
(void)gPipeInputs.GetMaxTouchedTextureUnit();
|
||||
});
|
||||
ASSERT_TRUE(ExitedWith(r, 0)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_NE(r.Log.find("MGPipe: verify armed"), std::string::npos) << r.Log;
|
||||
EXPECT_EQ(r.Log.find("Fatal{"), std::string::npos) << r.Log;
|
||||
EXPECT_EQ(r.Log.find("PipeVerifyDiffer"), std::string::npos) << r.Log;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // MOBILEGL_PIPE_PUSH
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
Reference in New Issue
Block a user