mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 18:13:23 +09:00
[Perf] (MG_Pipe): a vertex array keeps its buffer and index bindings on the server, a small uniform change crosses as a span, and unchanged context values skip their record (rd12 split arms +5-10%).
This commit is contained in:
@@ -59,6 +59,7 @@ namespace MobileGL::MG_Pipe {
|
|||||||
binding.Res = info.IndexResource;
|
binding.Res = info.IndexResource;
|
||||||
binding.IndexSize = info.IndexSize;
|
binding.IndexSize = info.IndexSize;
|
||||||
MGPipeRouteSetIndexBuffer(binding);
|
MGPipeRouteSetIndexBuffer(binding);
|
||||||
|
MGPipeVertexInputEmitterInstance().NoteIndexBufferRoutedElsewhere(m_context);
|
||||||
m_indexBindingChanged = true;
|
m_indexBindingChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1422,6 +1422,7 @@ namespace MobileGL::MG_Pipe {
|
|||||||
// refusal, and the refusal asserts (PipeApply.cpp's ResolveVertexElements), i.e. it
|
// refusal, and the refusal asserts (PipeApply.cpp's ResolveVertexElements), i.e. it
|
||||||
// stops a verify build.
|
// stops a verify build.
|
||||||
MGPipeVertexInputEmitter& emitter = MGPipeVertexInputEmitterInstance();
|
MGPipeVertexInputEmitter& emitter = MGPipeVertexInputEmitterInstance();
|
||||||
|
emitter.ForgetVao(lifetimeId);
|
||||||
const Bool published = emitter.RecordIsPublished(handle);
|
const Bool published = emitter.RecordIsPublished(handle);
|
||||||
if (published) {
|
if (published) {
|
||||||
MGPHandleOnly only{};
|
MGPHandleOnly only{};
|
||||||
@@ -3152,6 +3153,18 @@ namespace MobileGL::MG_Pipe {
|
|||||||
// server-role-only fixture) - nothing is emitted and the fields keep being pulled, byte
|
// server-role-only fixture) - nothing is emitted and the fields keep being pulled, byte
|
||||||
// for byte as before (G1).
|
// for byte as before (G1).
|
||||||
Uint64 EmitContextValues(GLContext& ctx) {
|
Uint64 EmitContextValues(GLContext& ctx) {
|
||||||
|
// P14: nothing the record reads has moved since the record whose hash the suppressor
|
||||||
|
// still holds was built (same context, same input generation): it would be dropped.
|
||||||
|
static MGPContextValues s_lastValues{};
|
||||||
|
static Uint64 s_lastHash = 0;
|
||||||
|
static Uint64 s_lastContextId = 0;
|
||||||
|
static Uint64 s_lastGeneration = 0;
|
||||||
|
auto& suppressor = MGPipeSetHashSuppressorInstance();
|
||||||
|
const Uint64 contextId = ctx.GetTextureContextId();
|
||||||
|
const Uint64 generation = ctx.GetContextValuesGeneration();
|
||||||
|
if (s_lastHash != 0 && contextId == s_lastContextId && generation == s_lastGeneration &&
|
||||||
|
suppressor.LastEmitted(MGPipeSuppressorSlot::SetContextValues) == s_lastHash)
|
||||||
|
return 0;
|
||||||
MGPContextValues values{};
|
MGPContextValues values{};
|
||||||
values.ActiveTextureUnit = static_cast<Uint32>(ctx.GetActiveTextureUnit());
|
values.ActiveTextureUnit = static_cast<Uint32>(ctx.GetActiveTextureUnit());
|
||||||
values.MaxTouchedTextureUnit = static_cast<Uint32>(ctx.GetMaxTouchedTextureUnit());
|
values.MaxTouchedTextureUnit = static_cast<Uint32>(ctx.GetMaxTouchedTextureUnit());
|
||||||
@@ -3174,9 +3187,8 @@ namespace MobileGL::MG_Pipe {
|
|||||||
// is byte-equal to the record whose hash the suppressor still holds, the suppressor
|
// is byte-equal to the record whose hash the suppressor still holds, the suppressor
|
||||||
// would drop it: skip the hash. (An InvalidateAll changes what the suppressor holds,
|
// would drop it: skip the hash. (An InvalidateAll changes what the suppressor holds,
|
||||||
// so a reset still re-emits.)
|
// so a reset still re-emits.)
|
||||||
static MGPContextValues s_lastValues{};
|
s_lastContextId = contextId;
|
||||||
static Uint64 s_lastHash = 0;
|
s_lastGeneration = generation;
|
||||||
auto& suppressor = MGPipeSetHashSuppressorInstance();
|
|
||||||
if (s_lastHash != 0 && std::memcmp(&values, &s_lastValues, sizeof(values)) == 0 &&
|
if (s_lastHash != 0 && std::memcmp(&values, &s_lastValues, sizeof(values)) == 0 &&
|
||||||
suppressor.LastEmitted(MGPipeSuppressorSlot::SetContextValues) == s_lastHash)
|
suppressor.LastEmitted(MGPipeSuppressorSlot::SetContextValues) == s_lastHash)
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -218,6 +218,37 @@ namespace MobileGL::MG_Pipe {
|
|||||||
m_lastConstants = MGPGlobalConstants{};
|
m_lastConstants = MGPGlobalConstants{};
|
||||||
m_lastConstants.ShaderCso = cso;
|
m_lastConstants.ShaderCso = cso;
|
||||||
m_lastConstants.Version = version;
|
m_lastConstants.Version = version;
|
||||||
|
// P14: the server holds this program's previous image while the latch names it (every
|
||||||
|
// path that could drop or replace the applier's block clears the latch), so only the
|
||||||
|
// span that moved goes out - when it is small enough to be worth it.
|
||||||
|
const Uint8* image = static_cast<const Uint8*>(program->GetUBOData());
|
||||||
|
if (cso == m_constantsCso && m_constantsShadow.size() == size && size <= 0xffffu) {
|
||||||
|
Uint32 first = 0;
|
||||||
|
while (first < size && image[first] == m_constantsShadow[first]) ++first;
|
||||||
|
Uint32 last = size;
|
||||||
|
while (last > first && image[last - 1] == m_constantsShadow[last - 1]) --last;
|
||||||
|
// Nothing moved but the version: one byte carries the new version.
|
||||||
|
if (first == size) {
|
||||||
|
first = 0;
|
||||||
|
last = 1;
|
||||||
|
}
|
||||||
|
const Uint32 length = last - first;
|
||||||
|
if (length * 2 <= size) {
|
||||||
|
m_lastConstants.Range = (first << 16) | length;
|
||||||
|
m_lastConstants.Blob.Seg = kMGHostSpanSegNone;
|
||||||
|
m_lastConstants.Blob.Offset = reinterpret_cast<Uint64>(image + first);
|
||||||
|
m_lastConstants.Blob.Size = 0;
|
||||||
|
MGPipeRouteSetGlobalConstants(m_lastConstants, image + first, static_cast<Uint64>(length));
|
||||||
|
std::memcpy(m_constantsShadow.data() + first, image + first, length);
|
||||||
|
m_constantsVersion = version;
|
||||||
|
++m_constantSets;
|
||||||
|
++m_constantSpans;
|
||||||
|
if (MG_Util::PipeStats::Enabled()) {
|
||||||
|
MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::CsoBlobBytes, length);
|
||||||
|
}
|
||||||
|
return bytes + sizeof(MGPGlobalConstants) + length;
|
||||||
|
}
|
||||||
|
}
|
||||||
// THE ONE BLOB RULE: Size 0 means "this record does not declare its blob" - which
|
// THE ONE BLOB RULE: Size 0 means "this record does not declare its blob" - which
|
||||||
// is what a monolith emission is - and the bytes ride beside it as a companion
|
// is what a monolith emission is - and the bytes ride beside it as a companion
|
||||||
// pointer. Offset carries the staging address for diagnostics only; nothing reads
|
// pointer. Offset carries the staging address for diagnostics only; nothing reads
|
||||||
@@ -233,6 +264,7 @@ namespace MobileGL::MG_Pipe {
|
|||||||
static_cast<Uint64>(size));
|
static_cast<Uint64>(size));
|
||||||
m_constantsCso = cso;
|
m_constantsCso = cso;
|
||||||
m_constantsVersion = version;
|
m_constantsVersion = version;
|
||||||
|
m_constantsShadow.assign(image, image + size);
|
||||||
++m_constantSets;
|
++m_constantSets;
|
||||||
if (MG_Util::PipeStats::Enabled()) {
|
if (MG_Util::PipeStats::Enabled()) {
|
||||||
MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::CsoBlobBytes, size);
|
MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::CsoBlobBytes, size);
|
||||||
@@ -705,6 +737,10 @@ namespace MobileGL::MG_Pipe {
|
|||||||
MGPipeHandle m_dispatchCso = kMGPipeNullHandle;
|
MGPipeHandle m_dispatchCso = kMGPipeNullHandle;
|
||||||
MGPipeHandle m_constantsCso = kMGPipeNullHandle;
|
MGPipeHandle m_constantsCso = kMGPipeNullHandle;
|
||||||
Uint32 m_constantsVersion = kMGPipeGlobalConstantsNeverUploaded;
|
Uint32 m_constantsVersion = kMGPipeGlobalConstantsNeverUploaded;
|
||||||
|
// The image last sent for m_constantsCso (whole or patched by spans): what the server
|
||||||
|
// holds while the latch names it.
|
||||||
|
Vector<Uint8> m_constantsShadow;
|
||||||
|
Uint64 m_constantSpans = 0;
|
||||||
|
|
||||||
Uint64 m_creates = 0;
|
Uint64 m_creates = 0;
|
||||||
Uint64 m_binds = 0;
|
Uint64 m_binds = 0;
|
||||||
|
|||||||
@@ -103,6 +103,13 @@ namespace MobileGL::MG_Pipe {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The server now holds the set `contentHash` names without this slot having emitted it
|
||||||
|
// (P14: bind_vertex_elements re-applies the array's kept set). Same 0 -> 1 remap as
|
||||||
|
// ShouldEmit, so the next ShouldEmit of the same hash answers false.
|
||||||
|
void Latch(MGPipeSuppressorSlot slot, Uint64 contentHash) {
|
||||||
|
m_lastEmitted[static_cast<SizeT>(slot)] = contentHash == 0 ? 1 : contentHash;
|
||||||
|
}
|
||||||
|
|
||||||
// A context change or a server reset: what the server has is no longer what this
|
// A context change or a server reset: what the server has is no longer what this
|
||||||
// slot last emitted, so the next resolved set must go out whatever it hashes to.
|
// slot last emitted, so the next resolved set must go out whatever it hashes to.
|
||||||
void Invalidate(MGPipeSuppressorSlot slot) { m_lastEmitted[static_cast<SizeT>(slot)] = 0; }
|
void Invalidate(MGPipeSuppressorSlot slot) { m_lastEmitted[static_cast<SizeT>(slot)] = 0; }
|
||||||
|
|||||||
@@ -181,13 +181,26 @@ namespace MobileGL::MG_Pipe {
|
|||||||
Uint64 bytes = 0;
|
Uint64 bytes = 0;
|
||||||
const Bool configMoved = !latch.Published || latch.ConfigVersion != configVersion ||
|
const Bool configMoved = !latch.Published || latch.ConfigVersion != configVersion ||
|
||||||
latch.Gen != handle.Gen;
|
latch.Gen != handle.Gen;
|
||||||
if (configMoved) bytes += EmitCreate(*vao, handle, latch, configVersion);
|
if (configMoved) {
|
||||||
|
bytes += EmitCreate(*vao, handle, latch, configVersion);
|
||||||
|
// The applier drops the record's kept bindings at the same create.
|
||||||
|
memo.StoredBuffersValid = false;
|
||||||
|
memo.StoredIndexValid = false;
|
||||||
|
}
|
||||||
if (lifetimeId != m_boundLifetimeId || m_boundHandle != handle) {
|
if (lifetimeId != m_boundLifetimeId || m_boundHandle != handle) {
|
||||||
MGPipeRouteBindVertexElements(HandleOnly(handle));
|
MGPipeRouteBindVertexElements(HandleOnly(handle));
|
||||||
++m_binds;
|
++m_binds;
|
||||||
bytes += sizeof(MGPHandleOnly);
|
bytes += sizeof(MGPHandleOnly);
|
||||||
m_boundHandle = handle;
|
m_boundHandle = handle;
|
||||||
m_boundLifetimeId = lifetimeId;
|
m_boundLifetimeId = lifetimeId;
|
||||||
|
// P14: the bind re-applies whatever the record keeps (PipeApply.h,
|
||||||
|
// MGPipeVertexElementsRecord). The mirror below says what that is; where it does
|
||||||
|
// not know, the next set goes out whatever it hashes to.
|
||||||
|
auto& suppressor = MGPipeSetHashSuppressorInstance();
|
||||||
|
if (memo.StoredBuffersValid) suppressor.Latch(MGPipeSuppressorSlot::SetVertexBuffers, memo.StoredBuffersHash);
|
||||||
|
else suppressor.Invalidate(MGPipeSuppressorSlot::SetVertexBuffers);
|
||||||
|
m_serverIndexKnown = memo.StoredIndexValid;
|
||||||
|
m_serverIndexRes = memo.StoredIndexRes;
|
||||||
}
|
}
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
@@ -287,6 +300,14 @@ namespace MobileGL::MG_Pipe {
|
|||||||
m_lastBuffers.ContentHash = hash;
|
m_lastBuffers.ContentHash = hash;
|
||||||
MGPipeRouteSetVertexBuffers(m_lastBuffers, m_entries.data());
|
MGPipeRouteSetVertexBuffers(m_lastBuffers, m_entries.data());
|
||||||
++m_bufferSets;
|
++m_bufferSets;
|
||||||
|
// The bound array now keeps this set (the applier's rule, mirrored): a draw-time
|
||||||
|
// client-array set is the array's too, but it names private buffers, so the mirror
|
||||||
|
// forgets rather than remembers it.
|
||||||
|
if (vao && vao->GetLifetimeId() == m_boundLifetimeId) {
|
||||||
|
VaoMemo& bound = MemoFor(*vao);
|
||||||
|
bound.StoredBuffersValid = ownedClientBuffers == nullptr && count <= kMGPipeVaoStoredVertexBuffers;
|
||||||
|
bound.StoredBuffersHash = hash;
|
||||||
|
}
|
||||||
return sizeof(MGPVertexBuffers) + static_cast<Uint64>(count) * sizeof(MGPVertexBuffer);
|
return sizeof(MGPVertexBuffers) + static_cast<Uint64>(count) * sizeof(MGPVertexBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,11 +337,29 @@ namespace MobileGL::MG_Pipe {
|
|||||||
MGPipeResourceTrackerInstance().NoteBoundAs(m_lastIndex.Res, BufferTarget::Index);
|
MGPipeResourceTrackerInstance().NoteBoundAs(m_lastIndex.Res, BufferTarget::Index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// P14: the server already holds this binding - re-applied by the bind from the
|
||||||
|
// array's kept one, or sent last time - so there is nothing to send.
|
||||||
|
if (m_serverIndexKnown && m_serverIndexRes == m_lastIndex.Res) return 0;
|
||||||
MGPipeRouteSetIndexBuffer(m_lastIndex);
|
MGPipeRouteSetIndexBuffer(m_lastIndex);
|
||||||
++m_indexSets;
|
++m_indexSets;
|
||||||
|
m_serverIndexKnown = true;
|
||||||
|
m_serverIndexRes = m_lastIndex.Res;
|
||||||
|
if (vao && vao->GetLifetimeId() == m_boundLifetimeId) {
|
||||||
|
VaoMemo& bound = MemoFor(*vao);
|
||||||
|
bound.StoredIndexValid = true;
|
||||||
|
bound.StoredIndexRes = m_lastIndex.Res;
|
||||||
|
}
|
||||||
return sizeof(MGPIndexBuffer);
|
return sizeof(MGPIndexBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A set_index_buffer this emitter did not send (OwnedDrawInputs' private element
|
||||||
|
// buffer): the server's binding and the bound array's kept one are no longer known.
|
||||||
|
void NoteIndexBufferRoutedElsewhere(GLContext& ctx) {
|
||||||
|
m_serverIndexKnown = false;
|
||||||
|
const auto& vao = ctx.GetBoundVertexArray();
|
||||||
|
if (vao && vao->GetLifetimeId() == m_boundLifetimeId) MemoFor(*vao).StoredIndexValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
// ---- what a unit case reads. None of it costs a copy: the emitter builds INTO
|
// ---- what a unit case reads. None of it costs a copy: the emitter builds INTO
|
||||||
// these and hands the applier the same pointers. ----
|
// these and hands the applier the same pointers. ----
|
||||||
const Array<MGPVertexAttribWire, kMGPipeMaxVertexAttribs>& LastAttributes() const { return m_attributes; }
|
const Array<MGPVertexAttribWire, kMGPipeMaxVertexAttribs>& LastAttributes() const { return m_attributes; }
|
||||||
@@ -394,16 +433,19 @@ namespace MobileGL::MG_Pipe {
|
|||||||
}
|
}
|
||||||
m_boundHandle = kMGPipeNullHandle;
|
m_boundHandle = kMGPipeNullHandle;
|
||||||
m_boundLifetimeId = 0;
|
m_boundLifetimeId = 0;
|
||||||
|
m_serverIndexKnown = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ResetCounters() { m_creates = m_binds = m_bufferSets = m_indexSets = 0; }
|
void ResetCounters() { m_creates = m_binds = m_bufferSets = m_indexSets = 0; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// P14: what EmitVertexElements / EmitVertexBuffers / EmitIndexBuffer resolve from a VAO,
|
// P14: what EmitVertexElements / EmitVertexBuffers / EmitIndexBuffer resolve from a VAO,
|
||||||
// direct-mapped on the VAO's lifetime id (monotonic, never reused) and dropped whole at an
|
// one entry per live VAO keyed on its lifetime id (monotonic, never reused), erased at the
|
||||||
// allocator reset. A colliding VAO simply takes the entry over. It changes no record: the
|
// VAO's death and re-initialised after an allocator reset. A direct-mapped table was tried
|
||||||
// same handles and entries are emitted, only found without the per-draw walk and lookups.
|
// first and lost most entries to collisions: a Minecraft frame draws ~1,500 chunk arrays
|
||||||
static constexpr SizeT kVaoMemoEntries = 1024;
|
// whose ids spread well past any fixed table. The handles and entries it keeps change no
|
||||||
|
// record; the kept-binding mirror below does decide what is sent, and is only ever
|
||||||
|
// believed when the server provably holds the same.
|
||||||
static constexpr SizeT kVaoMemoBuffers = 8;
|
static constexpr SizeT kVaoMemoBuffers = 8;
|
||||||
struct VaoMemo {
|
struct VaoMemo {
|
||||||
Uint64 LifetimeId = 0;
|
Uint64 LifetimeId = 0;
|
||||||
@@ -418,11 +460,29 @@ namespace MobileGL::MG_Pipe {
|
|||||||
Uint32 HashBaseInstance = 0;
|
Uint32 HashBaseInstance = 0;
|
||||||
Uint64 Hash = 0;
|
Uint64 Hash = 0;
|
||||||
Array<MGPVertexBuffer, kVaoMemoBuffers> Buffers{};
|
Array<MGPVertexBuffer, kVaoMemoBuffers> Buffers{};
|
||||||
|
// The mirror of what the applier's record for this array keeps (PipeApply.h's
|
||||||
|
// MGPipeVertexElementsRecord): valid only while the server provably keeps exactly
|
||||||
|
// this. A memo entry taken over by another array starts invalid, which only costs a
|
||||||
|
// re-send.
|
||||||
|
Bool StoredBuffersValid = false;
|
||||||
|
Uint64 StoredBuffersHash = 0;
|
||||||
|
Bool StoredIndexValid = false;
|
||||||
|
MGPipeHandle StoredIndexRes = kMGPipeNullHandle;
|
||||||
};
|
};
|
||||||
|
// What the server's index binding is, when known (P14).
|
||||||
|
Bool m_serverIndexKnown = false;
|
||||||
|
MGPipeHandle m_serverIndexRes = kMGPipeNullHandle;
|
||||||
|
// The returned reference stays valid until the next MemoFor of a DIFFERENT array (an
|
||||||
|
// insert may grow the table) or ForgetVao.
|
||||||
VaoMemo& MemoFor(const VertexArrayObject& vao) {
|
VaoMemo& MemoFor(const VertexArrayObject& vao) {
|
||||||
const Uint64 lifetimeId = vao.GetLifetimeId();
|
const Uint64 lifetimeId = vao.GetLifetimeId();
|
||||||
const Uint64 epoch = MGPipeSlots().ResetEpoch();
|
const Uint64 epoch = MGPipeSlots().ResetEpoch();
|
||||||
VaoMemo& memo = m_vaoMemo[lifetimeId % kVaoMemoEntries];
|
if (m_vaoMemo.empty() || (m_vaoMemoUsed + 1) * 2 > m_vaoMemo.size()) GrowVaoMemo();
|
||||||
|
const SizeT mask = m_vaoMemo.size() - 1;
|
||||||
|
SizeT i = VaoMemoHome(lifetimeId, mask);
|
||||||
|
while (m_vaoMemo[i].LifetimeId != 0 && m_vaoMemo[i].LifetimeId != lifetimeId) i = (i + 1) & mask;
|
||||||
|
VaoMemo& memo = m_vaoMemo[i];
|
||||||
|
if (memo.LifetimeId == 0) ++m_vaoMemoUsed;
|
||||||
if (memo.LifetimeId != lifetimeId || memo.SlotEpoch != epoch) {
|
if (memo.LifetimeId != lifetimeId || memo.SlotEpoch != epoch) {
|
||||||
memo = VaoMemo{};
|
memo = VaoMemo{};
|
||||||
memo.LifetimeId = lifetimeId;
|
memo.LifetimeId = lifetimeId;
|
||||||
@@ -430,7 +490,52 @@ namespace MobileGL::MG_Pipe {
|
|||||||
}
|
}
|
||||||
return memo;
|
return memo;
|
||||||
}
|
}
|
||||||
Array<VaoMemo, kVaoMemoEntries> m_vaoMemo{};
|
static SizeT VaoMemoHome(Uint64 lifetimeId, SizeT mask) {
|
||||||
|
return static_cast<SizeT>((lifetimeId * 0x9E3779B97F4A7C15ull) >> 32) & mask;
|
||||||
|
}
|
||||||
|
void GrowVaoMemo() {
|
||||||
|
Vector<VaoMemo> old;
|
||||||
|
old.swap(m_vaoMemo);
|
||||||
|
m_vaoMemo.resize(old.empty() ? 1024 : old.size() * 2);
|
||||||
|
m_vaoMemoUsed = 0;
|
||||||
|
const SizeT mask = m_vaoMemo.size() - 1;
|
||||||
|
for (const VaoMemo& entry : old) {
|
||||||
|
if (entry.LifetimeId == 0) continue;
|
||||||
|
SizeT i = VaoMemoHome(entry.LifetimeId, mask);
|
||||||
|
while (m_vaoMemo[i].LifetimeId != 0) i = (i + 1) & mask;
|
||||||
|
m_vaoMemo[i] = entry;
|
||||||
|
++m_vaoMemoUsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Vector<VaoMemo> m_vaoMemo;
|
||||||
|
SizeT m_vaoMemoUsed = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// The VAO died (MGPipeEmitVertexElementsDestroyAndFree): its entry goes, by backward-shift
|
||||||
|
// deletion so every other entry stays reachable from its home.
|
||||||
|
void ForgetVao(Uint64 lifetimeId) {
|
||||||
|
if (m_vaoMemo.empty() || lifetimeId == 0) return;
|
||||||
|
const SizeT mask = m_vaoMemo.size() - 1;
|
||||||
|
SizeT i = VaoMemoHome(lifetimeId, mask);
|
||||||
|
while (m_vaoMemo[i].LifetimeId != lifetimeId) {
|
||||||
|
if (m_vaoMemo[i].LifetimeId == 0) return;
|
||||||
|
i = (i + 1) & mask;
|
||||||
|
}
|
||||||
|
SizeT hole = i;
|
||||||
|
for (SizeT j = (hole + 1) & mask; m_vaoMemo[j].LifetimeId != 0; j = (j + 1) & mask) {
|
||||||
|
const SizeT home = VaoMemoHome(m_vaoMemo[j].LifetimeId, mask);
|
||||||
|
// Move j into the hole when its home is not strictly inside (hole, j].
|
||||||
|
const Bool between = hole <= j ? (home > hole && home <= j) : (home > hole || home <= j);
|
||||||
|
if (!between) {
|
||||||
|
m_vaoMemo[hole] = m_vaoMemo[j];
|
||||||
|
hole = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_vaoMemo[hole] = VaoMemo{};
|
||||||
|
--m_vaoMemoUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
struct Latch {
|
struct Latch {
|
||||||
// The PER-CONTEXT half: "has this emitter told THIS server about this handle's
|
// The PER-CONTEXT half: "has this emitter told THIS server about this handle's
|
||||||
|
|||||||
@@ -1140,10 +1140,16 @@ namespace MobileGL::MG_Pipe {
|
|||||||
MGP_ASSERT_POD(MGPProgramBindings, 32);
|
MGP_ASSERT_POD(MGPProgramBindings, 32);
|
||||||
|
|
||||||
// Covers the DEFAULT UNIFORM BLOCK only (D6).
|
// Covers the DEFAULT UNIFORM BLOCK only (D6).
|
||||||
|
//
|
||||||
|
// Range (P14, was Pad0 and the size did not move): 0 means the bytes are the whole block.
|
||||||
|
// Otherwise it is (offset << 16) | length, and the bytes are only that span of the block,
|
||||||
|
// written over the image the program's record already holds - which the client sends only
|
||||||
|
// while it knows the server holds its previous image for the same ShaderCso (the latch that
|
||||||
|
// suppresses an unchanged block). A Minecraft chunk draw moves one vec3 of a ~230-byte block.
|
||||||
struct MGPGlobalConstants {
|
struct MGPGlobalConstants {
|
||||||
MGPipeHandle ShaderCso;
|
MGPipeHandle ShaderCso;
|
||||||
Uint32 Version;
|
Uint32 Version;
|
||||||
Uint32 Pad0;
|
Uint32 Range;
|
||||||
MGPBlobRef Blob;
|
MGPBlobRef Blob;
|
||||||
};
|
};
|
||||||
MGP_ASSERT_POD(MGPGlobalConstants, 40);
|
MGP_ASSERT_POD(MGPGlobalConstants, 40);
|
||||||
|
|||||||
@@ -2951,6 +2951,10 @@ namespace MobileGL::MG_Pipe {
|
|||||||
record.AttributeCount = desc.AttributeCount;
|
record.AttributeCount = desc.AttributeCount;
|
||||||
record.BindingPointCount = desc.BindingPointCount;
|
record.BindingPointCount = desc.BindingPointCount;
|
||||||
record.Live = true;
|
record.Live = true;
|
||||||
|
// A new configuration starts without kept bindings (see the record): the client drops
|
||||||
|
// its mirror of them at the same create.
|
||||||
|
record.StoredBuffersValid = false;
|
||||||
|
record.StoredIndexValid = false;
|
||||||
// Serial 0 means "never created", so the first create of an identity lands on 1 and a
|
// Serial 0 means "never created", so the first create of an identity lands on 1 and a
|
||||||
// backend twin that has synced nothing can never accidentally match a live record.
|
// backend twin that has synced nothing can never accidentally match a live record.
|
||||||
++record.ContentSerial;
|
++record.ContentSerial;
|
||||||
@@ -2973,6 +2977,14 @@ namespace MobileGL::MG_Pipe {
|
|||||||
const MGPipeVertexElementsRecord* record = ResolveVertexElements("bind_vertex_elements", handle.Handle);
|
const MGPipeVertexElementsRecord* record = ResolveVertexElements("bind_vertex_elements", handle.Handle);
|
||||||
if (record == nullptr) return;
|
if (record == nullptr) return;
|
||||||
MGPipeApplier().BoundVertexElements = handle.Handle;
|
MGPipeApplier().BoundVertexElements = handle.Handle;
|
||||||
|
// The array's kept bindings come back with it, through the same two applies their
|
||||||
|
// records take, so every serial and field moves exactly as it would for the records.
|
||||||
|
// (The apply writes the same entries back over themselves: harmless, and no copy.)
|
||||||
|
if (record->StoredBuffersValid) {
|
||||||
|
const MGPVertexBuffers header = record->StoredBuffersHeader;
|
||||||
|
MGPipeApplySetVertexBuffers(header, record->StoredBuffers.data());
|
||||||
|
}
|
||||||
|
if (record->StoredIndexValid) MGPipeApplySetIndexBuffer(record->StoredIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MGPipeApplyDeleteVertexElements(const MGPHandleOnly& handle) {
|
void MGPipeApplyDeleteVertexElements(const MGPHandleOnly& handle) {
|
||||||
@@ -3043,6 +3055,15 @@ namespace MobileGL::MG_Pipe {
|
|||||||
// would keep the previous shift.
|
// would keep the previous shift.
|
||||||
MGPipeApplier().VertexFetchBaseInstance = hdr.BaseInstance;
|
MGPipeApplier().VertexFetchBaseInstance = hdr.BaseInstance;
|
||||||
++MGPipeApplier().VertexBuffersSerial;
|
++MGPipeApplier().VertexBuffersSerial;
|
||||||
|
// Kept by the bound array (MGPipeVertexElementsRecord's P14 note). A re-apply from the
|
||||||
|
// bind writes back what it read, which is harmless.
|
||||||
|
if (MGPipeVertexElementsRecord* bound = FindVertexElements(MGPipeApplier().BoundVertexElements)) {
|
||||||
|
bound->StoredBuffersValid = hdr.Start == 0 && hdr.Count <= kMGPipeVaoStoredVertexBuffers;
|
||||||
|
if (bound->StoredBuffersValid) {
|
||||||
|
bound->StoredBuffersHeader = hdr;
|
||||||
|
for (Uint32 i = 0; i < hdr.Count; ++i) bound->StoredBuffers[i] = tail[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MGPipeApplySetIndexBuffer(const MGPIndexBuffer& record) {
|
void MGPipeApplySetIndexBuffer(const MGPIndexBuffer& record) {
|
||||||
@@ -3058,6 +3079,11 @@ namespace MobileGL::MG_Pipe {
|
|||||||
// configuration's, which is exactly what the backend's two separate compares need.
|
// configuration's, which is exactly what the backend's two separate compares need.
|
||||||
MGPipeApplier().IndexBuffer = record;
|
MGPipeApplier().IndexBuffer = record;
|
||||||
++MGPipeApplier().IndexBufferSerial;
|
++MGPipeApplier().IndexBufferSerial;
|
||||||
|
// Kept by the bound array (MGPipeVertexElementsRecord's P14 note).
|
||||||
|
if (MGPipeVertexElementsRecord* bound = FindVertexElements(MGPipeApplier().BoundVertexElements)) {
|
||||||
|
bound->StoredIndex = record;
|
||||||
|
bound->StoredIndexValid = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MGPipeDeriveRenderStateFields(PipeInputs& inputs) {
|
void MGPipeDeriveRenderStateFields(PipeInputs& inputs) {
|
||||||
@@ -3949,10 +3975,21 @@ namespace MobileGL::MG_Pipe {
|
|||||||
// bounded there - so this call can only ever allocate what the create already declared,
|
// bounded there - so this call can only ever allocate what the create already declared,
|
||||||
// and the blob rule has a real field to cross-check against for once.
|
// and the blob rule has a real field to cross-check against for once.
|
||||||
const Uint32 size = stored->Desc.GlobalUboSize;
|
const Uint32 size = stored->Desc.GlobalUboSize;
|
||||||
|
// A span (MGPGlobalConstants::Range) lands on the block the record already holds.
|
||||||
|
const Uint32 spanOffset = record.Range >> 16;
|
||||||
|
const Uint32 spanLength = record.Range & 0xffffu;
|
||||||
const char* fault = nullptr;
|
const char* fault = nullptr;
|
||||||
if (record.Version == ~Uint32{0}) {
|
if (record.Version == ~Uint32{0}) {
|
||||||
fault = "the version is the backends' never-uploaded sentinel, which no record may carry";
|
fault = "the version is the backends' never-uploaded sentinel, which no record may carry";
|
||||||
} else if (record.Blob.Size != 0 && record.Blob.Size != size) {
|
} else if (record.Range != 0 && (spanLength == 0 || spanOffset + spanLength > size)) {
|
||||||
|
fault = "the span runs outside the program's default uniform block";
|
||||||
|
} else if (record.Range != 0 && stored->GlobalConstants.size() != size) {
|
||||||
|
fault = "a span arrived for a block the record does not hold";
|
||||||
|
} else if (record.Range != 0 && record.Blob.Size != 0 && record.Blob.Size != spanLength) {
|
||||||
|
fault = "the declared blob length is not the span's length";
|
||||||
|
} else if (record.Range != 0 && bytes == nullptr) {
|
||||||
|
fault = "a span carries no bytes";
|
||||||
|
} else if (record.Range == 0 && record.Blob.Size != 0 && record.Blob.Size != size) {
|
||||||
fault = "the declared blob length is not the program's own default uniform block size";
|
fault = "the declared blob length is not the program's own default uniform block size";
|
||||||
} else if (size > kMGPipeMaxGlobalConstantsBytes) {
|
} else if (size > kMGPipeMaxGlobalConstantsBytes) {
|
||||||
fault = "the program's default uniform block is larger than any program may declare";
|
fault = "the program's default uniform block is larger than any program may declare";
|
||||||
@@ -3968,7 +4005,9 @@ namespace MobileGL::MG_Pipe {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (size == 0) {
|
if (record.Range != 0) {
|
||||||
|
std::memcpy(stored->GlobalConstants.data() + spanOffset, bytes, spanLength);
|
||||||
|
} else if (size == 0) {
|
||||||
stored->GlobalConstants.clear();
|
stored->GlobalConstants.clear();
|
||||||
} else {
|
} else {
|
||||||
const Uint8* image = static_cast<const Uint8*>(bytes);
|
const Uint8* image = static_cast<const Uint8*>(bytes);
|
||||||
|
|||||||
@@ -500,6 +500,11 @@ namespace MobileGL::MG_Pipe {
|
|||||||
// The 32 is GL's MAX_VERTEX_ATTRIBS as MobileGL advertises it (kMGPipeMaxVertexAttribs,
|
// The 32 is GL's MAX_VERTEX_ATTRIBS as MobileGL advertises it (kMGPipeMaxVertexAttribs,
|
||||||
// MGPipeTypes.h), which is also the bound the record's two declared counts are checked
|
// MGPipeTypes.h), which is also the bound the record's two declared counts are checked
|
||||||
// against before the blob is unpacked.
|
// against before the blob is unpacked.
|
||||||
|
// The widest vertex-buffer set a vertex-elements record keeps as its own (see below). A
|
||||||
|
// wider set is not kept, and the client, which mirrors this rule, then re-sends it after
|
||||||
|
// every bind of that array.
|
||||||
|
inline constexpr Uint32 kMGPipeVaoStoredVertexBuffers = 16;
|
||||||
|
|
||||||
struct MGPipeVertexElementsRecord {
|
struct MGPipeVertexElementsRecord {
|
||||||
Uint32 Gen = 0;
|
Uint32 Gen = 0;
|
||||||
Bool Live = false;
|
Bool Live = false;
|
||||||
@@ -511,6 +516,25 @@ namespace MobileGL::MG_Pipe {
|
|||||||
// including a RE-create on the same handle, which is how a configuration change
|
// including a RE-create on the same handle, which is how a configuration change
|
||||||
// travels (the handle is minted per frontend VAO and Gen moves only on slot reuse).
|
// travels (the handle is minted per frontend VAO and Gen moves only on slot reuse).
|
||||||
Uint64 ContentSerial = 0;
|
Uint64 ContentSerial = 0;
|
||||||
|
// P14: THE ARRAY'S OWN BUFFER AND ELEMENT BINDINGS, as GL keeps them on the vertex array
|
||||||
|
// object. The last set_vertex_buffers (window starting at 0, at most
|
||||||
|
// kMGPipeVaoStoredVertexBuffers entries) and the last set_index_buffer applied while this
|
||||||
|
// record was bound are kept here, and bind_vertex_elements re-applies them, exactly as if
|
||||||
|
// the two records had arrived again - so a client switching between arrays whose
|
||||||
|
// bindings did not move sends the bind alone. create_vertex_elements on the handle drops
|
||||||
|
// both (the client drops its mirror at the same create), so a kept set never outlives the
|
||||||
|
// configuration it was sent for. The client's mirror (VertexInputEmit.h) only ever
|
||||||
|
// believes a kept set the server also keeps; where it is unsure it re-sends.
|
||||||
|
Bool StoredBuffersValid = false;
|
||||||
|
Bool StoredIndexValid = false;
|
||||||
|
MGPVertexBuffers StoredBuffersHeader{};
|
||||||
|
Array<MGPVertexBuffer, kMGPipeVaoStoredVertexBuffers> StoredBuffers{};
|
||||||
|
MGPIndexBuffer StoredIndex{};
|
||||||
|
// P14: a backend's own derivation from this record (Magma's resolved vertex input
|
||||||
|
// layout), opaque here. It lives and dies with the record - a re-created or recycled
|
||||||
|
// record starts without it - and the backend keys it on ContentSerial plus the inputs it
|
||||||
|
// read, so it is never believed for a configuration it was not built from.
|
||||||
|
mutable SharedPtr<void> BackendMemo;
|
||||||
};
|
};
|
||||||
|
|
||||||
// set_framebuffer_state's record, HELD PER FRAMEBUFFER OBJECT and indexed by the handle's
|
// set_framebuffer_state's record, HELD PER FRAMEBUFFER OBJECT and indexed by the handle's
|
||||||
|
|||||||
@@ -159,7 +159,7 @@
|
|||||||
F(Name) F(Binding)
|
F(Name) F(Binding)
|
||||||
|
|
||||||
#define MGP_FIELDS_MGPGlobalConstants(F) \
|
#define MGP_FIELDS_MGPGlobalConstants(F) \
|
||||||
F(ShaderCso) F(Version) F(Blob)
|
F(ShaderCso) F(Version) F(Range) F(Blob)
|
||||||
|
|
||||||
#define MGP_FIELDS_MGPAttribValue(F) \
|
#define MGP_FIELDS_MGPAttribValue(F) \
|
||||||
F(Location) F(ValueClass) F(FloatView) F(IntView) F(UintView)
|
F(Location) F(ValueClass) F(FloatView) F(IntView) F(UintView)
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ namespace MobileGL::MG_Pipe {
|
|||||||
kMGPipeWireLayoutMembers, sizeof(kMGPipeWireLayoutMembers) / sizeof(WireLayoutMember));
|
kMGPipeWireLayoutMembers, sizeof(kMGPipeWireLayoutMembers) / sizeof(WireLayoutMember));
|
||||||
// Bump when a fixed-layout pipe carrier keeps its bytes but changes their meaning. The
|
// Bump when a fixed-layout pipe carrier keeps its bytes but changes their meaning. The
|
||||||
// mutable-level extent carrier reuses BufOffset/BufSize only on named image respecifies.
|
// mutable-level extent carrier reuses BufOffset/BufSize only on named image respecifies.
|
||||||
inline constexpr Uint32 kMGPipeResourceRespecifyExtentCarrierRevision = 1;
|
// 2 (P14): bind_vertex_elements re-applies the array's kept vertex-buffer and index
|
||||||
|
// bindings (PipeApply.h, MGPipeVertexElementsRecord), which a revision-1 peer would not.
|
||||||
|
inline constexpr Uint32 kMGPipeResourceRespecifyExtentCarrierRevision = 2;
|
||||||
|
|
||||||
constexpr Uint64 WireCatalogueDigest() {
|
constexpr Uint64 WireCatalogueDigest() {
|
||||||
Uint64 hash = 1469598103934665603ull;
|
Uint64 hash = 1469598103934665603ull;
|
||||||
|
|||||||
@@ -221,6 +221,7 @@ inline constexpr WireLayoutMember kMGPipeWireLayoutMembers[] = {
|
|||||||
{"MGPGlobalConstants", 0, sizeof(MGPGlobalConstants)},
|
{"MGPGlobalConstants", 0, sizeof(MGPGlobalConstants)},
|
||||||
{"MGPGlobalConstants.ShaderCso", offsetof(MGPGlobalConstants, ShaderCso), sizeof(((MGPGlobalConstants*)nullptr)->ShaderCso)},
|
{"MGPGlobalConstants.ShaderCso", offsetof(MGPGlobalConstants, ShaderCso), sizeof(((MGPGlobalConstants*)nullptr)->ShaderCso)},
|
||||||
{"MGPGlobalConstants.Version", offsetof(MGPGlobalConstants, Version), sizeof(((MGPGlobalConstants*)nullptr)->Version)},
|
{"MGPGlobalConstants.Version", offsetof(MGPGlobalConstants, Version), sizeof(((MGPGlobalConstants*)nullptr)->Version)},
|
||||||
|
{"MGPGlobalConstants.Range", offsetof(MGPGlobalConstants, Range), sizeof(((MGPGlobalConstants*)nullptr)->Range)},
|
||||||
{"MGPGlobalConstants.Blob", offsetof(MGPGlobalConstants, Blob), sizeof(((MGPGlobalConstants*)nullptr)->Blob)},
|
{"MGPGlobalConstants.Blob", offsetof(MGPGlobalConstants, Blob), sizeof(((MGPGlobalConstants*)nullptr)->Blob)},
|
||||||
{"MGPAttribValue", 0, sizeof(MGPAttribValue)},
|
{"MGPAttribValue", 0, sizeof(MGPAttribValue)},
|
||||||
{"MGPAttribValue.Location", offsetof(MGPAttribValue, Location), sizeof(((MGPAttribValue*)nullptr)->Location)},
|
{"MGPAttribValue.Location", offsetof(MGPAttribValue, Location), sizeof(((MGPAttribValue*)nullptr)->Location)},
|
||||||
|
|||||||
@@ -673,10 +673,18 @@ namespace MobileGL::MG_Remote::Transport {
|
|||||||
auto& x = *m_impl;
|
auto& x = *m_impl;
|
||||||
if (!x.Client() || span.Offset > x.memory->StageBytes() || span.Size > x.memory->StageBytes() - span.Offset)
|
if (!x.Client() || span.Offset > x.memory->StageBytes() || span.Size > x.memory->StageBytes() - span.Offset)
|
||||||
Corrupt("staged span outside window");
|
Corrupt("staged span outside window");
|
||||||
if (!x.stage.empty() && x.stage.back().Offset + x.stage.back().Size == span.Offset)
|
// Adjacent once the producer's 8-byte allocation granule is counted (StageAllocate rounds
|
||||||
x.stage.back().Size += span.Size;
|
// every blob up): one span then carries both, padding included, instead of one Stage
|
||||||
else
|
// message per small blob - a Minecraft chunk draw stages a 12-byte uniform span.
|
||||||
x.stage.push_back(span);
|
if (!x.stage.empty()) {
|
||||||
|
auto& back = x.stage.back();
|
||||||
|
const std::uint64_t end = back.Offset + back.Size;
|
||||||
|
if (span.Offset >= end && span.Offset - end < 8) {
|
||||||
|
back.Size = span.Offset + span.Size - back.Offset;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x.stage.push_back(span);
|
||||||
}
|
}
|
||||||
MobileGLResult StreamLink::Flush() {
|
MobileGLResult StreamLink::Flush() {
|
||||||
auto& x = *m_impl;
|
auto& x = *m_impl;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
const auto target = static_cast<SizeT>(GlobalBufferTargets[i]);
|
const auto target = static_cast<SizeT>(GlobalBufferTargets[i]);
|
||||||
if (target < m_bindingSlotIndex.size()) m_bindingSlotIndex[target] = static_cast<Int16>(i);
|
if (target < m_bindingSlotIndex.size()) m_bindingSlotIndex[target] = static_cast<Int16>(i);
|
||||||
}
|
}
|
||||||
|
++m_contextValuesGeneration;
|
||||||
for (SizeT i = 0; i < m_touchedBindPointCount.size(); ++i) {
|
for (SizeT i = 0; i < m_touchedBindPointCount.size(); ++i) {
|
||||||
m_touchedBindPointCount[i] = 0;
|
m_touchedBindPointCount[i] = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,9 +51,14 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
auto it = std::find(BufferBindPointTargets.begin(), BufferBindPointTargets.end(), target);
|
auto it = std::find(BufferBindPointTargets.begin(), BufferBindPointTargets.end(), target);
|
||||||
if (it == BufferBindPointTargets.end()) return;
|
if (it == BufferBindPointTargets.end()) return;
|
||||||
auto slot = std::distance(BufferBindPointTargets.begin(), it);
|
auto slot = std::distance(BufferBindPointTargets.begin(), it);
|
||||||
if (static_cast<SizeT>(index) + 1 > m_touchedBindPointCount[slot])
|
if (static_cast<SizeT>(index) + 1 > m_touchedBindPointCount[slot]) {
|
||||||
m_touchedBindPointCount[slot] = static_cast<SizeT>(index) + 1;
|
m_touchedBindPointCount[slot] = static_cast<SizeT>(index) + 1;
|
||||||
|
++m_contextValuesGeneration;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// Moves whenever a touched binding-point count does: the buffer half of
|
||||||
|
// GLContext::GetContextValuesGeneration.
|
||||||
|
Uint64 GetContextValuesGeneration() const { return m_contextValuesGeneration; }
|
||||||
SizeT GetTouchedBindPointCount(const BufferTarget target) const {
|
SizeT GetTouchedBindPointCount(const BufferTarget target) const {
|
||||||
auto it = std::find(BufferBindPointTargets.begin(), BufferBindPointTargets.end(), target);
|
auto it = std::find(BufferBindPointTargets.begin(), BufferBindPointTargets.end(), target);
|
||||||
if (it == BufferBindPointTargets.end()) return 0;
|
if (it == BufferBindPointTargets.end()) return 0;
|
||||||
@@ -113,5 +118,6 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
Array<Array<BindingSlotRange1D<BufferObject>, BufferBindingPointCount>, BufferBindPointTargets.size()>
|
Array<Array<BindingSlotRange1D<BufferObject>, BufferBindingPointCount>, BufferBindPointTargets.size()>
|
||||||
m_bufferBindPointTargets;
|
m_bufferBindPointTargets;
|
||||||
Array<SizeT, BufferBindPointTargets.size()> m_touchedBindPointCount;
|
Array<SizeT, BufferBindPointTargets.size()> m_touchedBindPointCount;
|
||||||
|
Uint64 m_contextValuesGeneration = 0;
|
||||||
};
|
};
|
||||||
} // namespace MobileGL::MG_State::GLState
|
} // namespace MobileGL::MG_State::GLState
|
||||||
|
|||||||
@@ -1436,6 +1436,7 @@ namespace MobileGL::MG_State {
|
|||||||
point.ClearRange();
|
point.ClearRange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
++m_transformFeedbackValuesGeneration;
|
||||||
m_transformFeedbackActive = object.active;
|
m_transformFeedbackActive = object.active;
|
||||||
m_transformFeedbackPaused = object.paused;
|
m_transformFeedbackPaused = object.paused;
|
||||||
m_transformFeedbackPrimitiveMode = object.primitiveMode;
|
m_transformFeedbackPrimitiveMode = object.primitiveMode;
|
||||||
|
|||||||
@@ -437,6 +437,7 @@ namespace MobileGL {
|
|||||||
// feedback object currently bound to GL_TRANSFORM_FEEDBACK; see the object
|
// feedback object currently bound to GL_TRANSFORM_FEEDBACK; see the object
|
||||||
// block further down for how a bind swaps them.
|
// block further down for how a bind swaps them.
|
||||||
void BeginTransformFeedback(GLenum primitiveMode, const SharedPtr<ProgramObject>& program) {
|
void BeginTransformFeedback(GLenum primitiveMode, const SharedPtr<ProgramObject>& program) {
|
||||||
|
++m_transformFeedbackValuesGeneration;
|
||||||
m_transformFeedbackActive = true;
|
m_transformFeedbackActive = true;
|
||||||
m_transformFeedbackPaused = false;
|
m_transformFeedbackPaused = false;
|
||||||
m_transformFeedbackPrimitiveMode = primitiveMode;
|
m_transformFeedbackPrimitiveMode = primitiveMode;
|
||||||
@@ -446,6 +447,7 @@ namespace MobileGL {
|
|||||||
m_transformFeedbackInputPrimitives = 0;
|
m_transformFeedbackInputPrimitives = 0;
|
||||||
}
|
}
|
||||||
void EndTransformFeedback() {
|
void EndTransformFeedback() {
|
||||||
|
++m_transformFeedbackValuesGeneration;
|
||||||
m_transformFeedbackActive = false;
|
m_transformFeedbackActive = false;
|
||||||
m_transformFeedbackPaused = false;
|
m_transformFeedbackPaused = false;
|
||||||
m_transformFeedbackProgram.reset();
|
m_transformFeedbackProgram.reset();
|
||||||
@@ -456,7 +458,10 @@ namespace MobileGL {
|
|||||||
}
|
}
|
||||||
Bool IsTransformFeedbackActive() const { return m_transformFeedbackActive; }
|
Bool IsTransformFeedbackActive() const { return m_transformFeedbackActive; }
|
||||||
Bool IsTransformFeedbackPaused() const { return m_transformFeedbackPaused; }
|
Bool IsTransformFeedbackPaused() const { return m_transformFeedbackPaused; }
|
||||||
void SetTransformFeedbackPaused(Bool paused) { m_transformFeedbackPaused = paused; }
|
void SetTransformFeedbackPaused(Bool paused) {
|
||||||
|
++m_transformFeedbackValuesGeneration;
|
||||||
|
m_transformFeedbackPaused = paused;
|
||||||
|
}
|
||||||
GLenum GetTransformFeedbackPrimitiveMode() const { return m_transformFeedbackPrimitiveMode; }
|
GLenum GetTransformFeedbackPrimitiveMode() const { return m_transformFeedbackPrimitiveMode; }
|
||||||
const SharedPtr<ProgramObject>& GetTransformFeedbackProgram() const {
|
const SharedPtr<ProgramObject>& GetTransformFeedbackProgram() const {
|
||||||
return m_transformFeedbackProgram;
|
return m_transformFeedbackProgram;
|
||||||
@@ -484,8 +489,17 @@ namespace MobileGL {
|
|||||||
// Vertices already captured since BeginTransformFeedback (drives the
|
// Vertices already captured since BeginTransformFeedback (drives the
|
||||||
// buffer-capacity clamp on the primitives-written accounting).
|
// buffer-capacity clamp on the primitives-written accounting).
|
||||||
void AddTransformFeedbackCapturedVertices(Uint64 vertices) {
|
void AddTransformFeedbackCapturedVertices(Uint64 vertices) {
|
||||||
|
++m_transformFeedbackValuesGeneration;
|
||||||
m_transformFeedbackCapturedVertices += vertices;
|
m_transformFeedbackCapturedVertices += vertices;
|
||||||
}
|
}
|
||||||
|
// P14: moves whenever any input of set_context_values does (the active texture
|
||||||
|
// unit, the two touched high-water marks, the transform feedback state of the bound
|
||||||
|
// object). Each half only ever counts up, so the sum moves exactly when one does;
|
||||||
|
// the emitter skips rebuilding the record while it holds.
|
||||||
|
Uint64 GetContextValuesGeneration() const {
|
||||||
|
return m_textureState.GetContextValuesGeneration() + m_bufferState.GetContextValuesGeneration() +
|
||||||
|
m_transformFeedbackValuesGeneration;
|
||||||
|
}
|
||||||
Uint64 GetTransformFeedbackCapturedVertices() const { return m_transformFeedbackCapturedVertices; }
|
Uint64 GetTransformFeedbackCapturedVertices() const { return m_transformFeedbackCapturedVertices; }
|
||||||
// Raw assembled input primitives fed to the capture stage since Begin
|
// Raw assembled input primitives fed to the capture stage since Begin
|
||||||
// (pre-clamp; drives the GS strip capture-order fixup at EndTF).
|
// (pre-clamp; drives the GS strip capture-order fixup at EndTF).
|
||||||
@@ -655,6 +669,7 @@ namespace MobileGL {
|
|||||||
Array<CurrentVertexAttributeValue, VertexArrayObject::MAX_VERTEX_ATTRIBS> m_currentVertexAttributes{};
|
Array<CurrentVertexAttributeValue, VertexArrayObject::MAX_VERTEX_ATTRIBS> m_currentVertexAttributes{};
|
||||||
Bool m_transformFeedbackActive = false;
|
Bool m_transformFeedbackActive = false;
|
||||||
Bool m_transformFeedbackPaused = false;
|
Bool m_transformFeedbackPaused = false;
|
||||||
|
Uint64 m_transformFeedbackValuesGeneration = 0;
|
||||||
GLenum m_transformFeedbackPrimitiveMode = GL_POINTS;
|
GLenum m_transformFeedbackPrimitiveMode = GL_POINTS;
|
||||||
SharedPtr<ProgramObject> m_transformFeedbackProgram;
|
SharedPtr<ProgramObject> m_transformFeedbackProgram;
|
||||||
Uint64 m_anyVertexAttribDefaultGeneration = 0;
|
Uint64 m_anyVertexAttribDefaultGeneration = 0;
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TextureState::SetActiveTextureUnit(Int unit) {
|
void TextureState::SetActiveTextureUnit(Int unit) {
|
||||||
|
if (unit != m_activeTextureUnit) ++m_contextValuesGeneration;
|
||||||
m_activeTextureUnit = unit;
|
m_activeTextureUnit = unit;
|
||||||
}
|
}
|
||||||
} // namespace MobileGL::MG_State::GLState
|
} // namespace MobileGL::MG_State::GLState
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
void NoteUnitTouched(Int unit, Bool bindingChanged = true) {
|
void NoteUnitTouched(Int unit, Bool bindingChanged = true) {
|
||||||
if (unit > m_maxTouchedUnit && unit < MAX_TEXTURE_IMAGE_UNITS) {
|
if (unit > m_maxTouchedUnit && unit < MAX_TEXTURE_IMAGE_UNITS) {
|
||||||
m_maxTouchedUnit = unit;
|
m_maxTouchedUnit = unit;
|
||||||
|
++m_contextValuesGeneration;
|
||||||
// Push-on-mutation (MG_Pipe/PipeMutation.h): the high-water mark is a pushed
|
// Push-on-mutation (MG_Pipe/PipeMutation.h): the high-water mark is a pushed
|
||||||
// PipeInputs field, and a bind reached from inside a verb - a backend binding
|
// PipeInputs field, and a bind reached from inside a verb - a backend binding
|
||||||
// its own synthesised fallback texture - would otherwise leave the block
|
// its own synthesised fallback texture - would otherwise leave the block
|
||||||
@@ -105,6 +106,9 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Int GetMaxTouchedUnit() const { return m_maxTouchedUnit; }
|
Int GetMaxTouchedUnit() const { return m_maxTouchedUnit; }
|
||||||
|
// Moves whenever the active unit or the touched high-water mark does: the texture half of
|
||||||
|
// GLContext::GetContextValuesGeneration.
|
||||||
|
Uint64 GetContextValuesGeneration() const { return m_contextValuesGeneration; }
|
||||||
// High-water mark of IMAGE units ever bound by glBindImageTexture, and it is a SECOND
|
// High-water mark of IMAGE units ever bound by glBindImageTexture, and it is a SECOND
|
||||||
// mark rather than a widening of the one above because the two index different
|
// mark rather than a widening of the one above because the two index different
|
||||||
// namespaces: GL 4.6 core 8.22's image units are their own array, and folding them
|
// namespaces: GL 4.6 core 8.22's image units are their own array, and folding them
|
||||||
@@ -198,6 +202,7 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
Uint64 m_samplingResolutionGeneration = 0;
|
Uint64 m_samplingResolutionGeneration = 0;
|
||||||
Int m_maxTouchedUnit = -1;
|
Int m_maxTouchedUnit = -1;
|
||||||
Int m_activeTextureUnit = 0;
|
Int m_activeTextureUnit = 0;
|
||||||
|
Uint64 m_contextValuesGeneration = 0;
|
||||||
Array<TextureUnit, MAX_TEXTURE_IMAGE_UNITS> m_textureUnits;
|
Array<TextureUnit, MAX_TEXTURE_IMAGE_UNITS> m_textureUnits;
|
||||||
Array<ImageTextureBinding, MAX_TEXTURE_IMAGE_UNITS> m_imageTextureBindings;
|
Array<ImageTextureBinding, MAX_TEXTURE_IMAGE_UNITS> m_imageTextureBindings;
|
||||||
// One default texture object (external name 0) per target, created with the context and
|
// One default texture object (external name 0) per target, created with the context and
|
||||||
|
|||||||
@@ -852,6 +852,59 @@ namespace {
|
|||||||
EXPECT_TRUE(MGPipeHandleIsNull(MGPipeApplier().BoundVertexElements));
|
EXPECT_TRUE(MGPipeHandleIsNull(MGPipeApplier().BoundVertexElements));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// P14: a vertex-elements record keeps the last vertex-buffer set and index binding applied
|
||||||
|
// while it was bound, and bind_vertex_elements re-applies them through the same two applies
|
||||||
|
// - every field and both serials move as if the records had arrived again. A re-create of
|
||||||
|
// the handle drops what it kept (the client drops its mirror at the same create).
|
||||||
|
TEST(ResourceEmit, AVertexElementsRecordKeepsItsBindingsAndABindReappliesThem) {
|
||||||
|
ApplierGuard guard;
|
||||||
|
const MGPipeHandle resA{7, 3};
|
||||||
|
const MGPipeHandle resB{8, 1};
|
||||||
|
const MGPipeHandle csoA{2, 1};
|
||||||
|
const MGPipeHandle csoB{3, 1};
|
||||||
|
const ElementsBlob elementsA = MakeElements(csoA, 2, 1, true);
|
||||||
|
const ElementsBlob elementsB = MakeElements(csoB, 2, 1, true);
|
||||||
|
MGPipeApplyCreateVertexElements(elementsA.Desc, elementsA.Data());
|
||||||
|
MGPipeApplyCreateVertexElements(elementsB.Desc, elementsB.Data());
|
||||||
|
|
||||||
|
const auto setBuffers = [](MGPipeHandle res, Uint32 stride, Uint32 baseInstance) {
|
||||||
|
MGPVertexBuffers hdr{};
|
||||||
|
hdr.Count = 1;
|
||||||
|
hdr.BaseInstance = baseInstance;
|
||||||
|
MGPVertexBuffer entry{};
|
||||||
|
entry.Res = res;
|
||||||
|
entry.Stride = stride;
|
||||||
|
MGPipeApplySetVertexBuffers(hdr, &entry);
|
||||||
|
};
|
||||||
|
MGPipeApplyBindVertexElements(ElementsHandle(csoA));
|
||||||
|
setBuffers(resA, 12, 0);
|
||||||
|
MGPipeApplySetIndexBuffer(MGPIndexBuffer{resA, 0, 0, 0});
|
||||||
|
MGPipeApplyBindVertexElements(ElementsHandle(csoB));
|
||||||
|
setBuffers(resB, 20, 0);
|
||||||
|
MGPipeApplySetIndexBuffer(MGPIndexBuffer{resB, 0, 0, 0});
|
||||||
|
|
||||||
|
// Back to A with nothing sent but the bind: A's own bindings come back.
|
||||||
|
const Uint64 vertexSerial = MGPipeApplier().VertexBuffersSerial;
|
||||||
|
const Uint64 indexSerial = MGPipeApplier().IndexBufferSerial;
|
||||||
|
MGPipeApplyBindVertexElements(ElementsHandle(csoA));
|
||||||
|
EXPECT_EQ(MGPipeApplier().VertexBufferCount, 1u);
|
||||||
|
EXPECT_EQ(MGPipeApplier().VertexBuffers[0].Res, resA);
|
||||||
|
EXPECT_EQ(MGPipeApplier().VertexBuffers[0].Stride, 12u);
|
||||||
|
EXPECT_EQ(MGPipeApplier().IndexBuffer.Res, resA);
|
||||||
|
EXPECT_GT(MGPipeApplier().VertexBuffersSerial, vertexSerial) << "a re-applied set must move its serial";
|
||||||
|
EXPECT_GT(MGPipeApplier().IndexBufferSerial, indexSerial);
|
||||||
|
MGPipeApplyBindVertexElements(ElementsHandle(csoB));
|
||||||
|
EXPECT_EQ(MGPipeApplier().VertexBuffers[0].Res, resB);
|
||||||
|
EXPECT_EQ(MGPipeApplier().IndexBuffer.Res, resB);
|
||||||
|
|
||||||
|
// A re-create drops what A kept: binding it again leaves B's bindings in place.
|
||||||
|
MGPipeApplyCreateVertexElements(elementsA.Desc, elementsA.Data());
|
||||||
|
MGPipeApplyBindVertexElements(ElementsHandle(csoA));
|
||||||
|
EXPECT_EQ(MGPipeApplier().VertexBuffers[0].Res, resB);
|
||||||
|
EXPECT_EQ(MGPipeApplier().IndexBuffer.Res, resB);
|
||||||
|
EXPECT_EQ(MGPipeApplier().RefusedVertexInputCalls, 0u);
|
||||||
|
}
|
||||||
|
|
||||||
// C1's observable. A call that names a record this applier does not have is a DEFINED
|
// C1's observable. A call that names a record this applier does not have is a DEFINED
|
||||||
// no-op - nothing stored, nothing dispatched, no serial moved - because the teardown order
|
// no-op - nothing stored, nothing dispatched, no serial moved - because the teardown order
|
||||||
// makes exactly one such sequence legal (release the records, then every ~BufferObject
|
// makes exactly one such sequence legal (release the records, then every ~BufferObject
|
||||||
|
|||||||
@@ -557,6 +557,60 @@ namespace {
|
|||||||
EXPECT_EQ(Emitter().CreateCount(), 3u);
|
EXPECT_EQ(Emitter().CreateCount(), 3u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// P14: GL keeps a vertex array's buffer and element bindings ON THE ARRAY, and so does the
|
||||||
|
// applier's record (MGPipeVertexElementsRecord): a switch back to an array whose bindings did
|
||||||
|
// not move sends the bind alone - no set_vertex_buffers, no set_index_buffer. A binding that
|
||||||
|
// did move still goes out after the bind.
|
||||||
|
TEST(VertexInputEmit, ASwitchBetweenArraysWithUnchangedBindingsSendsOnlyTheBind) {
|
||||||
|
EmitterScope scope;
|
||||||
|
const SharedPtr<BufferObject> vertices = Ctx().CreateBufferObject(1);
|
||||||
|
vertices->Respecify(256, nullptr);
|
||||||
|
const SharedPtr<BufferObject> elements = Ctx().CreateBufferObject(2);
|
||||||
|
elements->Respecify(64, nullptr);
|
||||||
|
const SharedPtr<BufferObject> other = Ctx().CreateBufferObject(3);
|
||||||
|
other->Respecify(256, nullptr);
|
||||||
|
const auto configure = [&](Uint name, const SharedPtr<BufferObject>& buffer) {
|
||||||
|
const SharedPtr<VertexArrayObject> vao = MakeVao(name);
|
||||||
|
vao->SetAttributeFormat(0, 4, DataType::Float32, false, 16, 0, false);
|
||||||
|
vao->BindAttributeBuffer(0, buffer);
|
||||||
|
vao->EnableAttribute(0);
|
||||||
|
vao->GetIndexBufferBindingSlot().Bind(elements);
|
||||||
|
return vao;
|
||||||
|
};
|
||||||
|
const auto validate = [&] {
|
||||||
|
Uint64 bytes = Emitter().EmitVertexElements(Ctx());
|
||||||
|
bytes += Emitter().EmitVertexBuffers(Ctx(), 0);
|
||||||
|
bytes += Emitter().EmitIndexBuffer(Ctx());
|
||||||
|
return bytes;
|
||||||
|
};
|
||||||
|
const SharedPtr<VertexArrayObject> a = configure(1, vertices);
|
||||||
|
ASSERT_GT(validate(), 0u);
|
||||||
|
const SharedPtr<VertexArrayObject> b = configure(2, other);
|
||||||
|
ASSERT_GT(validate(), 0u);
|
||||||
|
const Uint64 sets = Emitter().VertexBufferSetCount();
|
||||||
|
const Uint64 indexSets = Emitter().IndexBufferSetCount();
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
Ctx().BindVertexArray(1);
|
||||||
|
EXPECT_EQ(validate(), sizeof(MGPHandleOnly)) << "a switch to an unchanged array sent more than its bind";
|
||||||
|
Ctx().BindVertexArray(2);
|
||||||
|
EXPECT_EQ(validate(), sizeof(MGPHandleOnly));
|
||||||
|
}
|
||||||
|
EXPECT_EQ(Emitter().VertexBufferSetCount(), sets);
|
||||||
|
EXPECT_EQ(Emitter().IndexBufferSetCount(), indexSets);
|
||||||
|
|
||||||
|
// A moved binding is sent after the bind, and kept from then on.
|
||||||
|
Ctx().BindVertexArray(1);
|
||||||
|
a->BindAttributeBuffer(0, other);
|
||||||
|
EXPECT_GT(validate(), sizeof(MGPHandleOnly));
|
||||||
|
EXPECT_EQ(Emitter().VertexBufferSetCount(), sets + 1);
|
||||||
|
Ctx().BindVertexArray(2);
|
||||||
|
EXPECT_EQ(validate(), sizeof(MGPHandleOnly));
|
||||||
|
Ctx().BindVertexArray(1);
|
||||||
|
EXPECT_EQ(validate(), sizeof(MGPHandleOnly));
|
||||||
|
EXPECT_EQ(Emitter().VertexBufferSetCount(), sets + 1);
|
||||||
|
}
|
||||||
|
|
||||||
// ============================ C-1: the death path ============================
|
// ============================ C-1: the death path ============================
|
||||||
//
|
//
|
||||||
// THE LEAK THIS RULES OUT, and why it is a client case rather than a backend one. Every
|
// THE LEAK THIS RULES OUT, and why it is a client case rather than a backend one. Every
|
||||||
|
|||||||
Reference in New Issue
Block a user