mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-13 14:48:32 +09:00
[Test] (Pipe): pin every P4a record's lifecycle, its bounds gate and what a make-current does and does not clear
This commit is contained in:
@@ -39,13 +39,19 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <process.h>
|
||||
#define MGTEST_HAVE_FORK 0
|
||||
#else
|
||||
#include <csignal>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#define MGTEST_HAVE_FORK 1
|
||||
#endif
|
||||
|
||||
#include "Includes.h"
|
||||
@@ -68,6 +74,92 @@ namespace {
|
||||
return static_cast<int>(getpid());
|
||||
#endif
|
||||
}
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
std::string ReadLog() {
|
||||
std::ifstream in(g_logPath, std::ios::binary);
|
||||
std::ostringstream ss;
|
||||
ss << in.rdbuf();
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
// A fresh applier per case, BOTH SCOPES, and it takes both because there are two: a reset
|
||||
// is a make-current and deliberately KEEPS the object records, so a fixture that wants a
|
||||
// genuinely empty applier has to say the other one as well. Every case is its own process
|
||||
// under ctest, so this is belt and braces - but running the binary by hand must give the
|
||||
// same answers as running it under ctest.
|
||||
struct ApplierGuard {
|
||||
ApplierGuard() {
|
||||
MGPipeApplierReset();
|
||||
MGPipeApplierReleaseObjectRecords();
|
||||
}
|
||||
~ApplierGuard() {
|
||||
MGPipeApplierReset();
|
||||
MGPipeApplierReleaseObjectRecords();
|
||||
}
|
||||
};
|
||||
|
||||
#if MGTEST_HAVE_FORK
|
||||
struct ChildResult {
|
||||
int Status = -1;
|
||||
std::string Log;
|
||||
};
|
||||
|
||||
template <class Body>
|
||||
ChildResult RunInChild(Body body) {
|
||||
ChildResult result;
|
||||
std::error_code ec;
|
||||
std::filesystem::remove(g_logPath, ec);
|
||||
std::fflush(nullptr);
|
||||
const pid_t pid = ::fork();
|
||||
if (pid < 0) return result;
|
||||
if (pid == 0) {
|
||||
body();
|
||||
::_exit(0);
|
||||
}
|
||||
int status = 0;
|
||||
if (::waitpid(pid, &status, 0) != pid) return result;
|
||||
result.Status = status;
|
||||
result.Log = ReadLog();
|
||||
return result;
|
||||
}
|
||||
|
||||
Bool DiedOfAbort(const ChildResult& r) { return WIFSIGNALED(r.Status) && WTERMSIG(r.Status) == SIGABRT; }
|
||||
std::string DescribeStatus(const ChildResult& r) {
|
||||
if (r.Status < 0) return "fork/waitpid failed";
|
||||
if (WIFEXITED(r.Status)) return "exited " + std::to_string(WEXITSTATUS(r.Status));
|
||||
if (WIFSIGNALED(r.Status)) return "signal " + std::to_string(WTERMSIG(r.Status));
|
||||
return "status " + std::to_string(r.Status);
|
||||
}
|
||||
#endif // MGTEST_HAVE_FORK
|
||||
|
||||
// Drives a call a trip wire must REFUSE, and asserts the wire NAMED what it refused. The
|
||||
// two arms differ by design: a poison or verify build stops the process, so the drive is a
|
||||
// forked child and the parent reads SIGABRT plus the line out of the log; a shipped push
|
||||
// build logs and carries on from a defined state, so there the line is read back in process
|
||||
// and the caller goes on to assert that nothing moved.
|
||||
template <class Body>
|
||||
void ExpectRefusedNaming(const char* needle, Body body) {
|
||||
#if MOBILEGL_PIPE_POISON || MOBILEGL_PIPE_VERIFY
|
||||
#if MGTEST_HAVE_FORK
|
||||
const std::string tagged = std::string("Fatal{ProtocolCorruption} ") + needle;
|
||||
const ChildResult child = RunInChild(body);
|
||||
EXPECT_TRUE(DiedOfAbort(child)) << DescribeStatus(child) << "; log: " << child.Log;
|
||||
EXPECT_NE(child.Log.find(tagged), std::string::npos)
|
||||
<< "the gate fired without naming what it refused; wanted \"" << tagged << "\"; log: " << child.Log;
|
||||
#else
|
||||
(void)needle;
|
||||
(void)body; // no fork on this platform; the verdict here is std::abort()
|
||||
#endif
|
||||
#else
|
||||
const std::string tagged = std::string("ProtocolCorruption ") + needle;
|
||||
const std::string before = ReadLog();
|
||||
body();
|
||||
EXPECT_NE(ReadLog().substr(before.size()).find(tagged), std::string::npos)
|
||||
<< "the gate refused without saying what it refused; wanted \"" << tagged << "\"";
|
||||
#endif
|
||||
}
|
||||
#endif // MOBILEGL_PIPE_PUSH
|
||||
} // namespace
|
||||
|
||||
// See FramebufferEmitTest's twin for why this is a shape pin rather than a placeholder: a
|
||||
@@ -83,6 +175,325 @@ TEST(TextureEmit, TheEmitterIsOneNeverDestroyedProcessSingleton) {
|
||||
#endif
|
||||
}
|
||||
|
||||
// =========================================================================================
|
||||
// The APPLIER's half of the texture family (the wire commits'): set_texture_params on the
|
||||
// texture's own record, and the sub-data validator plus the pending-upload set that replaces
|
||||
// the frontend dirty flags the client clears at emission. The emitter's half - the descriptor
|
||||
// builder for every target, the sticky bind mask, the drain list, the level-shadow strides -
|
||||
// is the client package's and lands beside these.
|
||||
// =========================================================================================
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
namespace {
|
||||
constexpr Uint16 kTex2D = static_cast<Uint16>(MGPipeResourceTarget::Tex2D);
|
||||
|
||||
MGPResourceDesc TextureDesc(MGPipeHandle res, Uint32 width, Uint32 glName) {
|
||||
MGPResourceDesc desc{};
|
||||
desc.Resource = res;
|
||||
desc.Target = static_cast<Uint8>(MGPipeResourceTarget::Tex2D);
|
||||
desc.Width = width;
|
||||
desc.Height = width;
|
||||
desc.GlNameForDiag = glName;
|
||||
return desc;
|
||||
}
|
||||
|
||||
// Every field carries a value of its own so a body that stored the wrong one is visible BY
|
||||
// FIELD, which is what the family's descriptor-consistency control needs of it.
|
||||
MGPTextureParams TextureParams(MGPipeHandle res, MGPipeHandle builtinSampler, Uint16 baseLevel) {
|
||||
MGPTextureParams params{};
|
||||
params.Res = res;
|
||||
params.BuiltinSampler = builtinSampler;
|
||||
params.BaseLevel = baseLevel;
|
||||
params.MaxLevel = 7;
|
||||
params.Swizzle[0] = 1;
|
||||
params.Swizzle[1] = 2;
|
||||
params.Swizzle[2] = 3;
|
||||
params.Swizzle[3] = 4;
|
||||
params.DepthStencilMode = 5;
|
||||
params.ForceResync = 1;
|
||||
params.SamplerResync = 1;
|
||||
params.MinLod = -2.0f;
|
||||
params.MaxLod = 9.0f;
|
||||
params.LodBias = 0.5f;
|
||||
return params;
|
||||
}
|
||||
|
||||
MGPSubData TextureUpload(MGPipeHandle res, Uint16 level, const MGPBox& box, Uint32 regionCount) {
|
||||
MGPSubData record{};
|
||||
record.Res = res;
|
||||
record.Target = kTex2D;
|
||||
record.Level = level;
|
||||
record.SourceIsVerbatimLevelShadow = 1;
|
||||
record.UnionBox = box;
|
||||
record.RegionCount = regionCount;
|
||||
return record;
|
||||
}
|
||||
|
||||
MGPSubRegion Region(Int32 x, Int32 y, Uint32 w, Uint32 h) {
|
||||
MGPSubRegion region{};
|
||||
region.X = x;
|
||||
region.Y = y;
|
||||
region.Z = 0;
|
||||
region.W = w;
|
||||
region.H = h;
|
||||
region.D = 1;
|
||||
region.SrcOffset = static_cast<Uint64>(y) * 64 + static_cast<Uint64>(x) * 4;
|
||||
region.SrcRowStride = 256;
|
||||
region.SrcSliceStride = 0;
|
||||
return region;
|
||||
}
|
||||
|
||||
const MGPipeResourceRecord& TextureRecordOf(Uint32 slot) {
|
||||
EXPECT_GT(MGPipeApplier().TextureResources.size(), static_cast<SizeT>(slot));
|
||||
return MGPipeApplier().TextureResources[slot];
|
||||
}
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
// set_texture_params IS ADDRESSED BY RESOURCE AND BY NOTHING ELSE, which is the whole reason
|
||||
// the call exists: a texture that is only an FBO attachment, only an image-unit binding or
|
||||
// only a glCopyImageSubData endpoint has no sampler view to hang its parameters on. Deleting
|
||||
// the store or the ParamsSerial bump leaves this red.
|
||||
TEST(TextureEmit, ATexturesParametersLandOnItsOwnRecordAndMoveOnlyTheirOwnSerial) {
|
||||
#if !MOBILEGL_PIPE_PUSH
|
||||
GTEST_SKIP() << "MOBILEGL_PIPE_PUSH is off: there is no applier in this build";
|
||||
#else
|
||||
ApplierGuard guard;
|
||||
const MGPipeHandle texture{7, 3};
|
||||
const MGPipeHandle sampler{2, 1};
|
||||
MGPipeApplyResourceCreate(TextureDesc(texture, 0, 41));
|
||||
MGPipeApplyResourceRespecify(TextureDesc(texture, 64, 41), nullptr);
|
||||
ASSERT_EQ(TextureRecordOf(7).ParamsSerial, 0u) << "a create and a respecify are not a parameter push";
|
||||
|
||||
MGPipeApplySetTextureParams(TextureParams(texture, sampler, 2));
|
||||
|
||||
const MGPipeResourceRecord& record = TextureRecordOf(7);
|
||||
EXPECT_EQ(record.Params.BuiltinSampler, sampler);
|
||||
EXPECT_EQ(record.Params.BaseLevel, 2u);
|
||||
EXPECT_EQ(record.Params.MaxLevel, 7u);
|
||||
EXPECT_EQ(record.Params.Swizzle[2], 3u);
|
||||
EXPECT_EQ(record.Params.DepthStencilMode, 5u);
|
||||
EXPECT_EQ(record.Params.ForceResync, 1u);
|
||||
EXPECT_EQ(record.Params.SamplerResync, 1u) << "the second resync bit is carried, not dropped";
|
||||
EXPECT_FLOAT_EQ(record.Params.MinLod, -2.0f);
|
||||
EXPECT_FLOAT_EQ(record.Params.LodBias, 0.5f);
|
||||
EXPECT_EQ(record.ParamsSerial, 1u);
|
||||
EXPECT_EQ(record.Serial, 1u) << "a parameter push is not a storage mutation and must not move Serial";
|
||||
|
||||
MGPipeApplySetTextureParams(TextureParams(texture, sampler, 3));
|
||||
EXPECT_EQ(TextureRecordOf(7).Params.BaseLevel, 3u);
|
||||
EXPECT_EQ(TextureRecordOf(7).ParamsSerial, 2u);
|
||||
|
||||
// A stale generation resolves to nothing: the call is a DEFINED no-op and it is COUNTED,
|
||||
// because MOBILEGL_ASSERT compiles out at INFO and a no-op nobody can see is a dropped
|
||||
// parameter push nobody can see.
|
||||
const Uint64 refusedBefore = MGPipeApplier().RefusedObjectCalls;
|
||||
MGPipeApplySetTextureParams(TextureParams(MGPipeHandle{7, 4}, sampler, 6));
|
||||
EXPECT_EQ(MGPipeApplier().RefusedObjectCalls, refusedBefore + 1);
|
||||
EXPECT_EQ(TextureRecordOf(7).Params.BaseLevel, 3u) << "a stale handle wrote the live record";
|
||||
EXPECT_EQ(TextureRecordOf(7).ParamsSerial, 2u);
|
||||
|
||||
// And a BUFFER of the same slot is not a texture: the two tables are independent, so this
|
||||
// is a refusal rather than a parameter push onto somebody else's record.
|
||||
MGPipeApplySetTextureParams(TextureParams(MGPipeHandle{9, 1}, sampler, 1));
|
||||
EXPECT_EQ(MGPipeApplier().RefusedObjectCalls, refusedBefore + 2);
|
||||
#endif
|
||||
}
|
||||
|
||||
// EVERY ITextureObject OWNS A SamplerObject, so a null built-in sampler CSO is not "no
|
||||
// sampler" - it is a record that would have the backend sample with whatever filter and wrap
|
||||
// state the unit last left behind. It is the corrupt-record verdict rather than the dropped-
|
||||
// call one, so it must NOT be counted as a refusal.
|
||||
TEST(TextureEmit, ARecordWithNoBuiltinSamplerCsoIsRefusedNamingTheTexture) {
|
||||
#if !MOBILEGL_PIPE_PUSH
|
||||
GTEST_SKIP() << "MOBILEGL_PIPE_PUSH is off: there is no applier in this build";
|
||||
#else
|
||||
ApplierGuard guard;
|
||||
const MGPipeHandle texture{6, 2};
|
||||
MGPipeApplyResourceCreate(TextureDesc(texture, 0, 77));
|
||||
MGPipeApplySetTextureParams(TextureParams(texture, MGPipeHandle{2, 1}, 1));
|
||||
ASSERT_EQ(TextureRecordOf(6).ParamsSerial, 1u);
|
||||
const Uint64 refusedBefore = MGPipeApplier().RefusedObjectCalls;
|
||||
|
||||
const MGPTextureParams noSampler = TextureParams(texture, kMGPipeNullHandle, 4);
|
||||
ExpectRefusedNaming("set_texture_params {slot=6, gen=2, glName=77}: the record names no built-in "
|
||||
"sampler CSO",
|
||||
[&noSampler]() { MGPipeApplySetTextureParams(noSampler); });
|
||||
EXPECT_EQ(TextureRecordOf(6).Params.BaseLevel, 1u) << "a refused record was stored anyway";
|
||||
EXPECT_EQ(TextureRecordOf(6).ParamsSerial, 1u);
|
||||
EXPECT_EQ(MGPipeApplier().RefusedObjectCalls, refusedBefore)
|
||||
<< "a corrupt record is not a dropped call and must not be counted as one";
|
||||
#endif
|
||||
}
|
||||
|
||||
// D-D5's safety net. The client clears its own dirty flags AT EMISSION and the backend's
|
||||
// upload loop has bail arms that would otherwise lose exactly those texels, so the emitted
|
||||
// shape accumulates SERVER-SIDE: boxes union, rect lists concatenate, and the moment either
|
||||
// side says "box only" the entry becomes box only - which is the frontend's own model, where
|
||||
// zero rects means "upload the union box instead" and covers every reason at once.
|
||||
TEST(TextureEmit, AnAccumulatedUploadUnionsItsBoxesAndCollapsesToTheBoxWhenARectListCannotDescribeIt) {
|
||||
#if !MOBILEGL_PIPE_PUSH
|
||||
GTEST_SKIP() << "MOBILEGL_PIPE_PUSH is off: there is no applier in this build";
|
||||
#else
|
||||
ApplierGuard guard;
|
||||
const MGPipeHandle texture{4, 1};
|
||||
const Uint8 texels[4096] = {};
|
||||
MGPipeApplyResourceCreate(TextureDesc(texture, 0, 88));
|
||||
MGPipeApplyResourceRespecify(TextureDesc(texture, 64, 88), nullptr);
|
||||
|
||||
const MGPSubRegion first[2] = {Region(0, 0, 4, 4), Region(8, 8, 4, 4)};
|
||||
MGPipeApplyResourceSubData(TextureUpload(texture, 0, MGPBox{0, 0, 0, 12, 12, 1}, 2), texels, first);
|
||||
ASSERT_EQ(TextureRecordOf(4).PendingUploads.size(), 1u);
|
||||
EXPECT_EQ(TextureRecordOf(4).PendingUploads[0].UploadTarget, kTex2D);
|
||||
EXPECT_EQ(TextureRecordOf(4).PendingUploads[0].Level, 0u);
|
||||
EXPECT_EQ(TextureRecordOf(4).PendingUploads[0].UnionBox.W, 12u);
|
||||
ASSERT_EQ(TextureRecordOf(4).PendingUploads[0].Regions.size(), 2u);
|
||||
EXPECT_EQ(TextureRecordOf(4).PendingUploads[0].Regions[1].X, 8);
|
||||
EXPECT_EQ(TextureRecordOf(4).PendingUploads[0].Regions[1].SrcRowStride, 256u)
|
||||
<< "the strides are CARRIED, never inferred: the pointer comparison they replace cannot "
|
||||
"survive a split";
|
||||
EXPECT_EQ(TextureRecordOf(4).Serial, 2u) << "an accepted upload moves the record's serial";
|
||||
|
||||
// A second emission behind a backend bail: the boxes union and the lists concatenate.
|
||||
const MGPSubRegion second[1] = {Region(16, 0, 8, 8)};
|
||||
MGPipeApplyResourceSubData(TextureUpload(texture, 0, MGPBox{16, 0, 0, 8, 8, 1}, 1), texels, second);
|
||||
ASSERT_EQ(TextureRecordOf(4).PendingUploads.size(), 1u) << "the (target, level) key split in two";
|
||||
EXPECT_EQ(TextureRecordOf(4).PendingUploads[0].UnionBox.X, 0);
|
||||
EXPECT_EQ(TextureRecordOf(4).PendingUploads[0].UnionBox.W, 24u);
|
||||
EXPECT_EQ(TextureRecordOf(4).PendingUploads[0].UnionBox.H, 12u);
|
||||
EXPECT_EQ(TextureRecordOf(4).PendingUploads[0].Regions.size(), 3u);
|
||||
|
||||
// A contribution with NO regions means "the box is the whole story", and the accumulated
|
||||
// entry has to say the same thing afterwards or the box would cover texels the rect list
|
||||
// does not name.
|
||||
MGPipeApplyResourceSubData(TextureUpload(texture, 0, MGPBox{0, 0, 0, 64, 64, 1}, 0), texels);
|
||||
ASSERT_EQ(TextureRecordOf(4).PendingUploads.size(), 1u);
|
||||
EXPECT_TRUE(TextureRecordOf(4).PendingUploads[0].Regions.empty())
|
||||
<< "a box-only contribution left a rect list that no longer covers the box";
|
||||
EXPECT_EQ(TextureRecordOf(4).PendingUploads[0].UnionBox.W, 64u);
|
||||
|
||||
// A different level is a different key, and a different upload target would be too.
|
||||
MGPipeApplyResourceSubData(TextureUpload(texture, 3, MGPBox{0, 0, 0, 8, 8, 1}, 0), texels);
|
||||
ASSERT_EQ(TextureRecordOf(4).PendingUploads.size(), 2u);
|
||||
EXPECT_EQ(TextureRecordOf(4).PendingUploads[1].Level, 3u);
|
||||
EXPECT_EQ(TextureRecordOf(4).PendingUploads[0].UnionBox.W, 64u) << "level 3 rewrote level 0's box";
|
||||
#endif
|
||||
}
|
||||
|
||||
// The texture half of the sub-data validator. Each of its four statements is about a record
|
||||
// that would make the server upload texels it was never told about, or read a tail it was not
|
||||
// given; removing any one of them leaves this red.
|
||||
TEST(TextureEmit, TheSubDataValidatorRefusesALevelABoxAndARegionTheRecordCannotDescribe) {
|
||||
#if !MOBILEGL_PIPE_PUSH
|
||||
GTEST_SKIP() << "MOBILEGL_PIPE_PUSH is off: there is no applier in this build";
|
||||
#else
|
||||
ApplierGuard guard;
|
||||
const MGPipeHandle texture{5, 2};
|
||||
const Uint8 texels[4096] = {};
|
||||
MGPipeApplyResourceCreate(TextureDesc(texture, 0, 99));
|
||||
MGPipeApplyResourceRespecify(TextureDesc(texture, 64, 99), nullptr);
|
||||
|
||||
// Positive control: the last legal level, a whole-level box and a region exactly filling
|
||||
// it are all fine, so what follows is refusing the value and not the arithmetic round it.
|
||||
const MGPSubRegion exact[1] = {Region(0, 0, 8, 8)};
|
||||
MGPipeApplyResourceSubData(TextureUpload(texture, 31, MGPBox{0, 0, 0, 8, 8, 1}, 1), texels, exact);
|
||||
ASSERT_EQ(TextureRecordOf(5).PendingUploads.size(), 1u);
|
||||
const Uint64 serialBefore = TextureRecordOf(5).Serial;
|
||||
|
||||
const MGPSubData deepLevel = TextureUpload(texture, 32, MGPBox{0, 0, 0, 8, 8, 1}, 0);
|
||||
ExpectRefusedNaming("resource_subdata {slot=5, gen=2, glName=99}: the level is above the bound any "
|
||||
"texture's storage can have",
|
||||
[&deepLevel, &texels]() { MGPipeApplyResourceSubData(deepLevel, texels); });
|
||||
|
||||
const MGPSubData negative = TextureUpload(texture, 0, MGPBox{-1, 0, 0, 8, 8, 1}, 0);
|
||||
ExpectRefusedNaming("resource_subdata {slot=5, gen=2, glName=99}: the union box has a negative origin "
|
||||
"or runs past the bound one record can encode",
|
||||
[&negative, &texels]() { MGPipeApplyResourceSubData(negative, texels); });
|
||||
|
||||
const MGPSubData missingTail = TextureUpload(texture, 0, MGPBox{0, 0, 0, 8, 8, 1}, 2);
|
||||
ExpectRefusedNaming("resource_subdata {slot=5, gen=2, glName=99}: the record declares sub-regions and "
|
||||
"carries none",
|
||||
[&missingTail, &texels]() { MGPipeApplyResourceSubData(missingTail, texels); });
|
||||
|
||||
// THE ONE INVARIANT THAT MATTERS: the union box IS the union of the regions. The server
|
||||
// picks the upload shape from the pair, so a region outside the box means the box misses
|
||||
// its texels and the region writes where the box never said it would.
|
||||
const MGPSubRegion outside[1] = {Region(16, 0, 4, 4)};
|
||||
const MGPSubData escapes = TextureUpload(texture, 0, MGPBox{0, 0, 0, 8, 8, 1}, 1);
|
||||
ExpectRefusedNaming("resource_subdata {slot=5, gen=2, glName=99}: a sub-region is not inside the union "
|
||||
"box the record declares",
|
||||
[&escapes, &texels, &outside]() {
|
||||
MGPipeApplyResourceSubData(escapes, texels, outside);
|
||||
});
|
||||
|
||||
const MGPSubData nothing = TextureUpload(texture, 0, MGPBox{0, 0, 0, 0, 0, 0}, 0);
|
||||
ExpectRefusedNaming("resource_subdata {slot=5, gen=2, glName=99}: the record describes no texels at all",
|
||||
[¬hing, &texels]() { MGPipeApplyResourceSubData(nothing, texels); });
|
||||
|
||||
EXPECT_EQ(TextureRecordOf(5).PendingUploads.size(), 1u)
|
||||
<< "a refused record was accumulated anyway";
|
||||
EXPECT_EQ(TextureRecordOf(5).Serial, serialBefore) << "not one refusal may move the serial";
|
||||
#endif
|
||||
}
|
||||
|
||||
// A respecify redefines the store, so the boxes and rects that describe the level it replaces
|
||||
// go with it - a box kept across a shrink would have the backend upload past the end of the
|
||||
// new level. Nothing is lost by it: the frontend entry points that respecify a texture re-mark
|
||||
// the levels they define.
|
||||
TEST(TextureEmit, ARespecifyDropsThePendingUploadsAgainstTheStorageItReplaces) {
|
||||
#if !MOBILEGL_PIPE_PUSH
|
||||
GTEST_SKIP() << "MOBILEGL_PIPE_PUSH is off: there is no applier in this build";
|
||||
#else
|
||||
ApplierGuard guard;
|
||||
const MGPipeHandle texture{3, 1};
|
||||
const Uint8 texels[4096] = {};
|
||||
MGPipeApplyResourceCreate(TextureDesc(texture, 0, 55));
|
||||
MGPipeApplyResourceRespecify(TextureDesc(texture, 64, 55), nullptr);
|
||||
MGPipeApplyResourceSubData(TextureUpload(texture, 0, MGPBox{0, 0, 0, 64, 64, 1}, 0), texels);
|
||||
ASSERT_EQ(TextureRecordOf(3).PendingUploads.size(), 1u);
|
||||
|
||||
MGPipeApplyResourceRespecify(TextureDesc(texture, 8, 55), nullptr);
|
||||
EXPECT_TRUE(TextureRecordOf(3).PendingUploads.empty())
|
||||
<< "a 64-wide box survived onto an 8-wide store";
|
||||
EXPECT_EQ(TextureRecordOf(3).Desc.Width, 8u);
|
||||
#endif
|
||||
}
|
||||
|
||||
// D-J4, for the kind that made the rule matter: a TEXTURE lives in a share group exactly as a
|
||||
// buffer does, so its record - and the parameters and the pending uploads that ride on it -
|
||||
// outlives a make-current, and only the applier's own teardown takes it.
|
||||
TEST(TextureEmit, TheTextureRecordAndItsParamsAndPendingUploadsSurviveAMakeCurrent) {
|
||||
#if !MOBILEGL_PIPE_PUSH
|
||||
GTEST_SKIP() << "MOBILEGL_PIPE_PUSH is off: there is no applier in this build";
|
||||
#else
|
||||
ApplierGuard guard;
|
||||
const MGPipeHandle texture{2, 5};
|
||||
const Uint8 texels[4096] = {};
|
||||
MGPipeApplyResourceCreate(TextureDesc(texture, 0, 66));
|
||||
MGPipeApplyResourceRespecify(TextureDesc(texture, 32, 66), nullptr);
|
||||
MGPipeApplySetTextureParams(TextureParams(texture, MGPipeHandle{1, 1}, 2));
|
||||
MGPipeApplyResourceSubData(TextureUpload(texture, 0, MGPBox{0, 0, 0, 32, 32, 1}, 0), texels);
|
||||
|
||||
MGPipeApplierReset(); // the make-current
|
||||
|
||||
ASSERT_TRUE(TextureRecordOf(2).Live) << "a make-current dropped a share-group object's record";
|
||||
EXPECT_EQ(TextureRecordOf(2).Desc.Width, 32u);
|
||||
EXPECT_EQ(TextureRecordOf(2).Params.BaseLevel, 2u);
|
||||
EXPECT_EQ(TextureRecordOf(2).ParamsSerial, 1u);
|
||||
ASSERT_EQ(TextureRecordOf(2).PendingUploads.size(), 1u)
|
||||
<< "the pending uploads are the safety net for a backend bail and cannot be per context";
|
||||
|
||||
// The write that follows the switch still lands, which is the whole point of the rule.
|
||||
MGPipeApplyResourceSubData(TextureUpload(texture, 1, MGPBox{0, 0, 0, 16, 16, 1}, 0), texels);
|
||||
EXPECT_EQ(TextureRecordOf(2).PendingUploads.size(), 2u);
|
||||
EXPECT_EQ(MGPipeApplier().RefusedResourceCalls, 0u);
|
||||
|
||||
// And the teardown scope - the only other thing that clears a record - does take it.
|
||||
MGPipeApplierReleaseObjectRecords();
|
||||
EXPECT_TRUE(MGPipeApplier().TextureResources.empty());
|
||||
#endif
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
namespace fs = std::filesystem;
|
||||
const fs::path path =
|
||||
|
||||
Reference in New Issue
Block a user