mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 20:28:32 +09:00
[Feat] (State): give the frontend six aggregate generations so a tracker can answer "did any bound texture, buffer, attachment or attribute move" with one Uint64 compare
- MGP_NOTE_AGGREGATE(Aggregate) next to MGP_NOTE_MUTATION in MG_Pipe/PipeMutation.h, ((void)0) in the pull build for the same reason and with the same shape. It answers a DIFFERENT question from MGP_NOTE_MUTATION - "did any object of this class move since the tracker last looked", not "did a backend move a frontend value inside its own verb" - which is why it is a second macro rather than an overload. - The counters are members of the owning MG_State container (VertexArrayState, FramebufferState, TextureState x2, BufferState) and are reached through a push-only GLContext facade, because the bump points sit on OBJECTS and an object has no back-pointer to the state that owns it. That is the free-function form P2 brief D4 allows, and it costs a global load on a path that has just written object state. - A SIXTH aggregate, VertexAttribDefault on GLContext, which D4 does not list. Its bit (NEW_VERTEX_ATTRIB_DEFAULTS) is specified there with a ContentHash over all 32 CurrentVertexAttributeValues, and hashing 768 bytes on every draw does not fit inside the T1 ceiling the same brief pins. The hash still decides whether to EMIT (D11's set-hash suppressor); the generation decides whether to hash at all. - 30 bump points: 3 VertexArrayObject config-version sites, 3 FramebufferObject object-version sites (one of them inside MOBILEGL_DEFINE_FRAMEBUFFER_DEFAULT_SETTER, so the statement carries its own line continuation), 5 texture content-version sites, 12 texture params-version sites, SamplerObject::BumpVersion as the sampler choke point, 7 BufferObject change-serial sites and the 3 glVertexAttrib* defaults. - Every counter is deliberately COARSER than the state it guards: over-firing costs one extra push, under-firing renders stale, and under-firing is the direction ARCHITECTURE.md 13.2 names as the dangerous one and the P1 verify comparator cannot see for object-class state. - TrackerTest: each bump point moves ITS aggregate and no other, plus a null-context note. - G1: the pull build is 0 added / 0 removed / 0 renamed and the four resized symbols are the contract commit's own, unchanged by this commit.
This commit is contained in:
@@ -490,6 +490,39 @@ namespace MobileGL::MG_Pipe {
|
||||
MGPipeFillAccess::CopyField(inputs, *ctx, field);
|
||||
}
|
||||
|
||||
// ---- the aggregate generations (P2 brief D4) ----
|
||||
// MGP_NOTE_AGGREGATE lands here. The bump points are on OBJECTS, which have no
|
||||
// back-pointer to the state container that owns them, so the note finds the live
|
||||
// context - the same shape, and for the same reason, as MGPipeNoteFrontendMutation
|
||||
// above. No verb has to be in flight and no field is stamped: an aggregate generation
|
||||
// is not a PipeInputs field, it is what the tracker's shutter compares against.
|
||||
void MGPipeNoteAggregate(MGPipeAggregate aggregate) {
|
||||
auto* ctx = LiveContext();
|
||||
if (ctx == nullptr) return;
|
||||
switch (aggregate) {
|
||||
case MGPipeAggregate::VaoAttribute:
|
||||
ctx->NoteVaoAttributeChanged();
|
||||
break;
|
||||
case MGPipeAggregate::FramebufferAttachment:
|
||||
ctx->NoteFramebufferAttachmentChanged();
|
||||
break;
|
||||
case MGPipeAggregate::TextureContent:
|
||||
ctx->NoteTextureContentChanged();
|
||||
break;
|
||||
case MGPipeAggregate::TextureParams:
|
||||
ctx->NoteTextureParamsChanged();
|
||||
break;
|
||||
case MGPipeAggregate::BufferChange:
|
||||
ctx->NoteBufferChanged();
|
||||
break;
|
||||
case MGPipeAggregate::VertexAttribDefault:
|
||||
ctx->NoteVertexAttribDefaultChanged();
|
||||
break;
|
||||
case MGPipeAggregate::Count:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MGPipeSetPoisonOmission(const char* verb, const char* field) {
|
||||
if (verb == nullptr || field == nullptr) {
|
||||
g_omission = PoisonOmission{};
|
||||
|
||||
@@ -33,10 +33,55 @@ namespace MobileGL::MG_Pipe {
|
||||
// A no-op unless a context is live, a verb has been filled, and `field` is in that verb
|
||||
// class's may-read mask; a forwarded (sticky) field has no storage and is never copied.
|
||||
void MGPipeNoteFrontendMutation(MGPipeInputField field);
|
||||
|
||||
// ---- the aggregate generations (P2 brief D4, ARCHITECTURE.md 5.2) ----
|
||||
//
|
||||
// MGP_NOTE_MUTATION answers "a backend moved a frontend value INSIDE its own verb".
|
||||
// MGP_NOTE_AGGREGATE answers a different question, which is why it is a second macro
|
||||
// and not an overload: "did ANY object of this class move since the last time the
|
||||
// tracker looked", collapsed onto one monotonic Uint64 per class so a per-verb dirty
|
||||
// walk is a handful of compares rather than a scan over 32 attributes, 16 attachments,
|
||||
// 32 texture units and 84 binding points.
|
||||
//
|
||||
// The counters are members of the owning MG_State container, all guarded by
|
||||
// MOBILEGL_PIPE_PUSH so the pull build's state objects do not change size (G1). The
|
||||
// bump points sit on OBJECTS, which have no back-pointer to their state, so the macro
|
||||
// goes through a free function that finds the live GLContext - the same shape, and for
|
||||
// the same reason, as MGP_NOTE_MUTATION (MG_Impl/Pipe/PipeFill.cpp). It costs a global
|
||||
// load on a path that has just written object state.
|
||||
//
|
||||
// Monotonic and never reset: the tracker widens and compares, it never subtracts.
|
||||
// Over-firing is free (one extra re-push); under-firing renders stale, which is why
|
||||
// every counter here is deliberately COARSER than the state it guards.
|
||||
enum class MGPipeAggregate : Uint32 {
|
||||
// VertexArrayState: any VAO attribute format / buffer / enable moved.
|
||||
VaoAttribute = 0,
|
||||
// FramebufferState: any FBO attachment or default-geometry write, or a bind.
|
||||
FramebufferAttachment,
|
||||
// TextureState: any texture object CONTENT moved (an upload, a dirty region).
|
||||
TextureContent,
|
||||
// TextureState: any texture object or sampler object PARAMETER moved.
|
||||
TextureParams,
|
||||
// BufferState: any buffer object contents moved.
|
||||
BufferChange,
|
||||
// GLContext: a glVertexAttrib* default value moved. Not one of D4 five: the bit it
|
||||
// shutters (NEW_VERTEX_ATTRIB_DEFAULTS) is specified there as a ContentHash over
|
||||
// all 32 CurrentVertexAttributeValues, and hashing 768 bytes on EVERY draw does not
|
||||
// fit inside the T1 ceiling. The hash still decides whether to EMIT (D11 set-hash
|
||||
// suppressor); this decides whether to hash at all.
|
||||
VertexAttribDefault,
|
||||
Count,
|
||||
};
|
||||
|
||||
// MG_Impl/Pipe/PipeFill.cpp. A no-op unless a context is live.
|
||||
void MGPipeNoteAggregate(MGPipeAggregate aggregate);
|
||||
} // namespace MobileGL::MG_Pipe
|
||||
#define MGP_NOTE_MUTATION(Field) \
|
||||
::MobileGL::MG_Pipe::MGPipeNoteFrontendMutation(::MobileGL::MG_Pipe::MGPipeInputField::Field)
|
||||
#define MGP_NOTE_AGGREGATE(Aggregate) \
|
||||
::MobileGL::MG_Pipe::MGPipeNoteAggregate(::MobileGL::MG_Pipe::MGPipeAggregate::Aggregate)
|
||||
#else
|
||||
#define MGP_NOTE_MUTATION(Field) ((void)0)
|
||||
#define MGP_NOTE_AGGREGATE(Aggregate) ((void)0)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <Config.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <MG_Pipe/PipeMutation.h>
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
namespace {
|
||||
@@ -43,6 +44,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
|
||||
void BufferObject::NotifyRespecify() {
|
||||
++m_changeSerial;
|
||||
MGP_NOTE_AGGREGATE(BufferChange);
|
||||
if (g_bufferBackendOps && g_bufferBackendOps->Respecify) {
|
||||
g_bufferBackendOps->Respecify(*this);
|
||||
}
|
||||
@@ -50,6 +52,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
|
||||
void BufferObject::NotifySubData(SizeT offset, SizeT size) {
|
||||
++m_changeSerial;
|
||||
MGP_NOTE_AGGREGATE(BufferChange);
|
||||
if (size == 0) return;
|
||||
m_hasDefinedContent = true;
|
||||
if (g_bufferBackendOps && g_bufferBackendOps->SubData) {
|
||||
@@ -59,6 +62,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
|
||||
void BufferObject::NotifyFlushMappedRange(Range1D range, Flags<BufferMappingAccessBit> appAccess) {
|
||||
++m_changeSerial;
|
||||
MGP_NOTE_AGGREGATE(BufferChange);
|
||||
if (range.start >= range.end) return;
|
||||
m_hasDefinedContent = true;
|
||||
if (g_bufferBackendOps && g_bufferBackendOps->FlushMappedRange) {
|
||||
@@ -73,6 +77,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
// undefined store to "has content" - that would cost the next orphaning
|
||||
// respecification a full-size upload of bytes the application never wrote.
|
||||
++m_changeSerial;
|
||||
MGP_NOTE_AGGREGATE(BufferChange);
|
||||
return;
|
||||
}
|
||||
m_hasDefinedContent = true;
|
||||
@@ -80,6 +85,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
// The write already landed in coherent GPU memory; the backend has no separate
|
||||
// copy to sync. Only bump the serial so cached transient slices invalidate.
|
||||
++m_changeSerial;
|
||||
MGP_NOTE_AGGREGATE(BufferChange);
|
||||
return;
|
||||
}
|
||||
NotifySubData(offset, size);
|
||||
@@ -302,6 +308,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
data.size, m_size);
|
||||
Memcpy(m_resource.Bytes() + atOffset, data.data, data.size);
|
||||
++m_changeSerial;
|
||||
MGP_NOTE_AGGREGATE(BufferChange);
|
||||
}
|
||||
|
||||
void BufferObject::MarkGpuWritten() {
|
||||
@@ -366,6 +373,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
g_bufferBackendOps->ResidentSubData(*this, offset, bytes);
|
||||
m_hasDefinedContent = true;
|
||||
++m_changeSerial;
|
||||
MGP_NOTE_AGGREGATE(BufferChange);
|
||||
m_gpuWritePending = true;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,19 @@ namespace MobileGL::MG_State::GLState {
|
||||
Bool ValidateName(Uint index) const;
|
||||
Bool ValidateBufferObject(Uint index) const;
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P2 brief D4: "did the contents of ANY buffer object move". One counter for every
|
||||
// BufferObject ++m_changeSerial site, which is what NEW_VERTEX_BUFFERS /
|
||||
// NEW_INDEX_BUFFER / NEW_CONST_BUFFERS / NEW_SHADER_BUFFERS / NEW_SO_TARGETS all
|
||||
// shutter on in P2 - five bits over one aggregate until P3b splits them.
|
||||
void NoteBufferChanged() { ++m_anyBufferChangeGeneration; }
|
||||
Uint64 GetAnyBufferChangeGeneration() const { return m_anyBufferChangeGeneration; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
Uint64 m_anyBufferChangeGeneration = 0;
|
||||
#endif
|
||||
UnorderedMap<Uint, SharedPtr<BufferObject>> m_bufferObjects;
|
||||
IndexGenerator<Uint> m_indexGenerator;
|
||||
Array<BindingSlot<BufferObject>, GlobalBufferTargets.size()> m_bindingSlots;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <Config.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <MG_Pipe/PipeMutation.h>
|
||||
|
||||
namespace MobileGL::MG_State {
|
||||
void Init() {
|
||||
@@ -213,6 +214,7 @@ namespace MobileGL::MG_State {
|
||||
current.intValue[component] = static_cast<Int32>(value[component]);
|
||||
current.uintValue[component] = static_cast<Uint32>(value[component]);
|
||||
}
|
||||
MGP_NOTE_AGGREGATE(VertexAttribDefault);
|
||||
}
|
||||
|
||||
void GLContext::SetCurrentVertexAttributeInt(Uint index, const Array<Int32, 4>& value) {
|
||||
@@ -227,6 +229,7 @@ namespace MobileGL::MG_State {
|
||||
current.floatValue[component] = static_cast<Float>(value[component]);
|
||||
current.uintValue[component] = static_cast<Uint32>(value[component]);
|
||||
}
|
||||
MGP_NOTE_AGGREGATE(VertexAttribDefault);
|
||||
}
|
||||
|
||||
void GLContext::SetCurrentVertexAttributeUint(Uint index, const Array<Uint32, 4>& value) {
|
||||
@@ -241,6 +244,7 @@ namespace MobileGL::MG_State {
|
||||
current.floatValue[component] = static_cast<Float>(value[component]);
|
||||
current.intValue[component] = static_cast<Int32>(value[component]);
|
||||
}
|
||||
MGP_NOTE_AGGREGATE(VertexAttribDefault);
|
||||
}
|
||||
|
||||
const CurrentVertexAttributeValue& GLContext::GetCurrentVertexAttribute(Uint index) const {
|
||||
|
||||
@@ -198,6 +198,43 @@ namespace MobileGL {
|
||||
Uint GetBoundProgramPipelineName() const { return m_boundProgramPipeline; }
|
||||
const SharedPtr<ProgramPipelineObject>& GetBoundProgramPipeline() const;
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// ---- the aggregate generations (P2 brief D4) ----
|
||||
//
|
||||
// The bump points sit on OBJECTS - a VertexArrayObject, a TextureObject, a
|
||||
// BufferObject - which have no back-pointer to the state container that owns
|
||||
// them, so MGP_NOTE_AGGREGATE goes through MGPipeNoteAggregate, which finds
|
||||
// the live context and lands here. This facade is the whole reason the
|
||||
// objects need no back-pointer, and it is push-only so the pull build's
|
||||
// GLContext is byte-identical (G1).
|
||||
void NoteVaoAttributeChanged() { m_vertexArrayState.NoteAttributeChanged(); }
|
||||
Uint64 GetAnyVaoAttributeGeneration() const {
|
||||
return m_vertexArrayState.GetAnyAttributeGeneration();
|
||||
}
|
||||
void NoteFramebufferAttachmentChanged() { m_framebufferState.NoteAttachmentChanged(); }
|
||||
Uint64 GetAnyFramebufferAttachmentGeneration() const {
|
||||
return m_framebufferState.GetAnyAttachmentGeneration();
|
||||
}
|
||||
void NoteTextureContentChanged() { m_textureState.NoteTextureContentChanged(); }
|
||||
Uint64 GetAnyTextureContentGeneration() const {
|
||||
return m_textureState.GetAnyTextureContentGeneration();
|
||||
}
|
||||
void NoteTextureParamsChanged() { m_textureState.NoteTextureParamsChanged(); }
|
||||
Uint64 GetAnyTextureParamsGeneration() const {
|
||||
return m_textureState.GetAnyTextureParamsGeneration();
|
||||
}
|
||||
void NoteBufferChanged() { m_bufferState.NoteBufferChanged(); }
|
||||
Uint64 GetAnyBufferChangeGeneration() const {
|
||||
return m_bufferState.GetAnyBufferChangeGeneration();
|
||||
}
|
||||
// The sixth aggregate lives here rather than on a state container because
|
||||
// the values it guards do too (m_currentVertexAttributes).
|
||||
void NoteVertexAttribDefaultChanged() { ++m_anyVertexAttribDefaultGeneration; }
|
||||
Uint64 GetAnyVertexAttribDefaultGeneration() const {
|
||||
return m_anyVertexAttribDefaultGeneration;
|
||||
}
|
||||
#endif
|
||||
|
||||
// RenderState
|
||||
Uint GetRenderStateParametersVersion() const;
|
||||
// Only the pipeline-relevant subset - see RenderState::m_pipelineStateVersion.
|
||||
@@ -515,6 +552,9 @@ namespace MobileGL {
|
||||
Bool m_transformFeedbackPaused = false;
|
||||
GLenum m_transformFeedbackPrimitiveMode = GL_POINTS;
|
||||
SharedPtr<ProgramObject> m_transformFeedbackProgram;
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
Uint64 m_anyVertexAttribDefaultGeneration = 0;
|
||||
#endif
|
||||
Uint64 m_transformFeedbackGeneration = 0;
|
||||
// Source of the per-span ids above; never rolls back with an object switch.
|
||||
Uint64 m_transformFeedbackNextGeneration = 0;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "MG_Util/Types.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <MG_Pipe/PipeMutation.h>
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
// Starts at 1 so a zero-initialized memo slot can never carry a live object's id.
|
||||
@@ -205,6 +206,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
if (m_readBuffer == buf) return;
|
||||
m_readBuffer = buf;
|
||||
++m_objectVersion;
|
||||
MGP_NOTE_AGGREGATE(FramebufferAttachment);
|
||||
}
|
||||
|
||||
Uint FramebufferObject::GetExternalIndex() const {
|
||||
@@ -216,6 +218,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
if (member == value) return; \
|
||||
member = value; \
|
||||
++m_objectVersion; \
|
||||
MGP_NOTE_AGGREGATE(FramebufferAttachment); \
|
||||
}
|
||||
|
||||
MOBILEGL_DEFINE_FRAMEBUFFER_DEFAULT_SETTER(DefaultWidth, m_defaultWidth, Int)
|
||||
@@ -228,5 +231,6 @@ namespace MobileGL::MG_State::GLState {
|
||||
void FramebufferObject::BumpAttachmentVersion(FramebufferAttachmentType type) {
|
||||
++m_attachmentVersions[static_cast<SizeT>(type)];
|
||||
++m_objectVersion;
|
||||
MGP_NOTE_AGGREGATE(FramebufferAttachment);
|
||||
}
|
||||
} // namespace MobileGL::MG_State::GLState
|
||||
|
||||
@@ -25,7 +25,19 @@ namespace MobileGL::MG_State::GLState {
|
||||
Bool ValidateName(Uint index) const;
|
||||
Bool ValidateFramebufferObject(Uint index) const;
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P2 brief D4: "did the attachment set or the default geometry of ANY framebuffer
|
||||
// move". It does NOT cover a BIND - a bind writes a BindingSlot, not the object - so
|
||||
// MGPipeTracker pairs this counter with the bound draw framebuffer identity, which
|
||||
// is one extra load and keeps the bump points on the object where they belong.
|
||||
void NoteAttachmentChanged() { ++m_anyAttachmentGeneration; }
|
||||
Uint64 GetAnyAttachmentGeneration() const { return m_anyAttachmentGeneration; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
Uint64 m_anyAttachmentGeneration = 0;
|
||||
#endif
|
||||
UnorderedMap<Uint, SharedPtr<FramebufferObject>> m_framebufferObjects;
|
||||
IndexGenerator<Uint> m_indexGenerator;
|
||||
Array<BindingSlot<FramebufferObject>, static_cast<SizeT>(FramebufferTarget::FramebufferTargetCount)>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <MG_State/GLState/StateObjectDeathNotice.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <MG_Pipe/PipeMutation.h>
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_State {
|
||||
@@ -47,6 +48,9 @@ namespace MobileGL {
|
||||
// bindings must never miss an invalidation, and over-invalidating on a wrap-mode
|
||||
// write costs one re-resolve.
|
||||
if (pGLContext) pGLContext->BumpSamplingResolutionGeneration();
|
||||
// Every sampler parameter is a texture PARAMETER as far as the dirty walk is
|
||||
// concerned, and BumpVersion is the one choke point every setter reaches.
|
||||
MGP_NOTE_AGGREGATE(TextureParams);
|
||||
}
|
||||
|
||||
void SamplerObject::SetWrapS(SamplerWrapMode mode) {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "MG_State/GLState/StateObjectDeathNotice.h"
|
||||
#include "MG_Util/Types.h"
|
||||
#include <MG_Util/Metrics/TextureMetrics.h>
|
||||
#include <MG_Pipe/PipeMutation.h>
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_State {
|
||||
@@ -114,6 +115,7 @@ namespace MobileGL {
|
||||
m_internalFormat = format;
|
||||
BumpShapeVersion();
|
||||
++m_textureParamsVersion;
|
||||
MGP_NOTE_AGGREGATE(TextureParams);
|
||||
}
|
||||
|
||||
Uint TextureObjectBase::GetExternalIndex() const {
|
||||
@@ -139,6 +141,7 @@ namespace MobileGL {
|
||||
|
||||
m_sampler->SetBorderColor(color);
|
||||
++m_textureParamsVersion;
|
||||
MGP_NOTE_AGGREGATE(TextureParams);
|
||||
}
|
||||
|
||||
const IntVec4& TextureObjectBase::GetBorderColorI() const {
|
||||
@@ -153,6 +156,7 @@ namespace MobileGL {
|
||||
|
||||
m_sampler->SetBorderColorI(color);
|
||||
++m_textureParamsVersion;
|
||||
MGP_NOTE_AGGREGATE(TextureParams);
|
||||
}
|
||||
|
||||
const UintVec4& TextureObjectBase::GetBorderColorUI() const {
|
||||
@@ -167,6 +171,7 @@ namespace MobileGL {
|
||||
|
||||
m_sampler->SetBorderColorUI(color);
|
||||
++m_textureParamsVersion;
|
||||
MGP_NOTE_AGGREGATE(TextureParams);
|
||||
}
|
||||
|
||||
BorderColorForm TextureObjectBase::GetBorderColorForm() const {
|
||||
@@ -216,6 +221,7 @@ namespace MobileGL {
|
||||
break;
|
||||
}
|
||||
++m_textureParamsVersion;
|
||||
MGP_NOTE_AGGREGATE(TextureParams);
|
||||
}
|
||||
|
||||
void TextureObjectBase::SetSwizzleParamRGBA(const Vec4<TextureSwizzleParam>& values) {
|
||||
@@ -223,6 +229,7 @@ namespace MobileGL {
|
||||
|
||||
m_swizzleParams = values;
|
||||
++m_textureParamsVersion;
|
||||
MGP_NOTE_AGGREGATE(TextureParams);
|
||||
}
|
||||
|
||||
const UintVec2& TextureObjectBase::GetLevelRange() const {
|
||||
@@ -240,6 +247,7 @@ namespace MobileGL {
|
||||
m_levelRange.y() = m_levelRange.x();
|
||||
}
|
||||
++m_textureParamsVersion;
|
||||
MGP_NOTE_AGGREGATE(TextureParams);
|
||||
BumpShapeVersion();
|
||||
}
|
||||
|
||||
@@ -251,6 +259,7 @@ namespace MobileGL {
|
||||
|
||||
m_levelRange.y() = maxLevel;
|
||||
++m_textureParamsVersion;
|
||||
MGP_NOTE_AGGREGATE(TextureParams);
|
||||
BumpShapeVersion();
|
||||
}
|
||||
|
||||
@@ -271,6 +280,7 @@ namespace MobileGL {
|
||||
m_levelRange.y() = std::min(std::max(m_levelRange.y(), m_levelRange.x()), m_immutableLevels - 1);
|
||||
}
|
||||
++m_textureParamsVersion;
|
||||
MGP_NOTE_AGGREGATE(TextureParams);
|
||||
}
|
||||
|
||||
Uint16 TextureObjectBase::GetTextureParamsVersion() const {
|
||||
@@ -298,6 +308,7 @@ namespace MobileGL {
|
||||
|
||||
void TextureObjectBase::BumpContentVersion() {
|
||||
++m_contentVersion;
|
||||
MGP_NOTE_AGGREGATE(TextureContent);
|
||||
}
|
||||
|
||||
Int TextureObjectBase::GetSamples() const {
|
||||
@@ -307,6 +318,7 @@ namespace MobileGL {
|
||||
void TextureObjectBase::SetSamples(Int samples) {
|
||||
m_samples = samples;
|
||||
++m_textureParamsVersion;
|
||||
MGP_NOTE_AGGREGATE(TextureParams);
|
||||
}
|
||||
|
||||
Bool TextureObjectBase::HasFixedSampleLocations() const {
|
||||
@@ -316,6 +328,7 @@ namespace MobileGL {
|
||||
void TextureObjectBase::SetFixedSampleLocations(Bool fixedSampleLocations) {
|
||||
m_fixedSampleLocations = fixedSampleLocations;
|
||||
++m_textureParamsVersion;
|
||||
MGP_NOTE_AGGREGATE(TextureParams);
|
||||
}
|
||||
|
||||
Uint64 TextureObjectBase::GetLifetimeId() const {
|
||||
@@ -367,6 +380,7 @@ namespace MobileGL {
|
||||
Bool dirty) {
|
||||
if (dirty) {
|
||||
++m_contentVersion;
|
||||
MGP_NOTE_AGGREGATE(TextureContent);
|
||||
}
|
||||
m_textureStorage.MarkDirty(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, dirty);
|
||||
}
|
||||
@@ -378,6 +392,7 @@ namespace MobileGL {
|
||||
void TextureObjectWithOneMipmap::MarkStorageDirtyRegion(TextureUploadTarget uploadTarget, Uint mipmapLevel,
|
||||
IntVec3 offset, IntVec3 size) {
|
||||
++m_contentVersion;
|
||||
MGP_NOTE_AGGREGATE(TextureContent);
|
||||
m_textureStorage.MarkDirtyRegion(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, offset,
|
||||
size);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "../SamplerState/SamplerObject.h"
|
||||
#include <Includes.h>
|
||||
#include <MG_Util/Math/VectorTypes.h>
|
||||
#include <MG_Pipe/PipeMutation.h>
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
// Texture objects are always SharedPtr-owned (TextureState creates every instance via
|
||||
@@ -188,6 +189,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
if (m_depthStencilTextureMode == mode) return;
|
||||
m_depthStencilTextureMode = mode;
|
||||
++m_textureParamsVersion;
|
||||
MGP_NOTE_AGGREGATE(TextureParams);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
// End of Source File Header
|
||||
|
||||
#include "TextureObject2DCube.h"
|
||||
#include <MG_Pipe/PipeMutation.h>
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_State {
|
||||
@@ -49,6 +50,7 @@ namespace MobileGL {
|
||||
void TextureObject2DCube::MarkStorageDirty(TextureUploadTarget uploadTarget, Uint mipmapLevel, bool dirty) {
|
||||
if (dirty) {
|
||||
++m_contentVersion;
|
||||
MGP_NOTE_AGGREGATE(TextureContent);
|
||||
}
|
||||
m_textureStorage.MarkDirty(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, dirty);
|
||||
}
|
||||
@@ -60,6 +62,7 @@ namespace MobileGL {
|
||||
void TextureObject2DCube::MarkStorageDirtyRegion(TextureUploadTarget uploadTarget, Uint mipmapLevel,
|
||||
IntVec3 offset, IntVec3 size) {
|
||||
++m_contentVersion;
|
||||
MGP_NOTE_AGGREGATE(TextureContent);
|
||||
m_textureStorage.MarkDirtyRegion(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, offset,
|
||||
size);
|
||||
}
|
||||
|
||||
@@ -140,7 +140,22 @@ namespace MobileGL::MG_State::GLState {
|
||||
// again (the unit tests do exactly that between cases).
|
||||
Uint64 GetContextId() const { return m_contextId; }
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P2 brief D4, the two texture aggregates. CONTENT is an upload or a dirty region;
|
||||
// PARAMS is a glTexParameter or a glSamplerParameter. They are separate because
|
||||
// NEW_SAMPLER_VIEWS and NEW_SAMPLERS are separate dirty bits and a Minecraft frame
|
||||
// moves them at wildly different rates.
|
||||
void NoteTextureContentChanged() { ++m_anyTextureContentGeneration; }
|
||||
Uint64 GetAnyTextureContentGeneration() const { return m_anyTextureContentGeneration; }
|
||||
void NoteTextureParamsChanged() { ++m_anyTextureParamsGeneration; }
|
||||
Uint64 GetAnyTextureParamsGeneration() const { return m_anyTextureParamsGeneration; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
Uint64 m_anyTextureContentGeneration = 0;
|
||||
Uint64 m_anyTextureParamsGeneration = 0;
|
||||
#endif
|
||||
static Uint64 AllocateContextId();
|
||||
|
||||
const Uint64 m_contextId;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <MG_State/GLState/StateObjectDeathNotice.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <MG_Pipe/PipeMutation.h>
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
// Starts at 1 so a zero-initialized memo slot can never carry a live object's id.
|
||||
@@ -313,18 +314,21 @@ namespace MobileGL::MG_State::GLState {
|
||||
if (index >= MAX_VERTEX_ATTRIBS) return;
|
||||
++m_attributeVersions[index].FormatVersion;
|
||||
++m_configVersion;
|
||||
MGP_NOTE_AGGREGATE(VaoAttribute);
|
||||
}
|
||||
|
||||
void VertexArrayObject::BumpAttributeBufferVersion(Uint index) {
|
||||
if (index >= MAX_VERTEX_ATTRIBS) return;
|
||||
++m_attributeVersions[index].BufferVersion;
|
||||
++m_configVersion;
|
||||
MGP_NOTE_AGGREGATE(VaoAttribute);
|
||||
}
|
||||
|
||||
void VertexArrayObject::BumpAttributeSwitchVersion(Uint index) {
|
||||
if (index >= MAX_VERTEX_ATTRIBS) return;
|
||||
++m_attributeVersions[index].SwitchVersion;
|
||||
++m_configVersion;
|
||||
MGP_NOTE_AGGREGATE(VaoAttribute);
|
||||
}
|
||||
|
||||
const VertexAttributeVersion& VertexArrayObject::GetAttributeVersion(Uint index) const {
|
||||
|
||||
@@ -28,7 +28,20 @@ namespace MobileGL {
|
||||
const SharedPtr<VertexArrayObject>& GetBoundVertexArray();
|
||||
Vector<SharedPtr<VertexArrayObject>>& GetAllVertexArrays();
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P2 brief D4: "did the attribute configuration of ANY vertex array move".
|
||||
// Bumped from every VertexArrayObject::BumpAttribute*Version through
|
||||
// MGP_NOTE_AGGREGATE(VaoAttribute), which is coarser than the per-object
|
||||
// m_configVersion on purpose - the tracker wants one compare, and an extra
|
||||
// re-push costs a push while a missed one renders stale.
|
||||
void NoteAttributeChanged() { ++m_anyVaoAttributeGeneration; }
|
||||
Uint64 GetAnyAttributeGeneration() const { return m_anyVaoAttributeGeneration; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
Uint64 m_anyVaoAttributeGeneration = 0;
|
||||
#endif
|
||||
// "Nothing bound" (an out-of-range or never-created name was bound). Distinct from
|
||||
// being bound to a live slot so that a slot filled AFTER such a bind does not
|
||||
// retroactively become the bound VAO.
|
||||
|
||||
@@ -6,27 +6,168 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
// The frontend state tracker: the dirty walk, the five aggregate generations, the per-bit fire counters (P2 brief D4).
|
||||
// The frontend state tracker: the dirty walk, the aggregate generations, the per-bit fire
|
||||
// counters (P2 brief D4). Owned by P2 package B (p2/tracker); the file and its CMake
|
||||
// registration are the contract commit's.
|
||||
//
|
||||
// STUB, and deliberately one. It is created by the P2 CONTRACT commit together with its
|
||||
// CMakeLists.txt registration, so that the package which owns its CONTENTS
|
||||
// (P2 package B, p2/tracker) never has to touch MG_Test/Pipe/CMakeLists.txt - no two P2
|
||||
// packages edit the same file, which is what keeps the integrator's rebases clean.
|
||||
//
|
||||
// The placeholder case is not decoration: without it the binary has no test, and
|
||||
// gtest_discover_tests on a binary with no test is a silently green lane.
|
||||
// Needs the push sources, so every case is a visible SKIP in a pull build rather than a
|
||||
// vanishing test - the shape PipeInputsTest.cpp established.
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "Includes.h"
|
||||
#include <MG_Pipe/MGPipe.h>
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
#include <MG_Pipe/PipeMutation.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#endif
|
||||
|
||||
using namespace MobileGL;
|
||||
using namespace MobileGL::MG_Pipe;
|
||||
|
||||
namespace {
|
||||
// The tracker does not exist yet; what is true in every build is that the subsystem
|
||||
// bitmask it dispatches on is allocated and does not overlap the behaviour bit.
|
||||
TEST(Tracker, PlaceholderUntilTheOwningPackageFillsThisIn) {
|
||||
// The subsystem bitmask the tracker dispatches on is allocated in every build.
|
||||
TEST(Tracker, SubsystemBitsDoNotOverlapTheBehaviourBit) {
|
||||
EXPECT_EQ(kMGPipeSubsystemsMigratedAtP2 & kMGPipeBehaviourNoCsoContentAddressing, 0ull);
|
||||
}
|
||||
|
||||
#if !MOBILEGL_PIPE_PUSH
|
||||
TEST(Tracker, SkippedInAPullBuild) {
|
||||
GTEST_SKIP() << "the tracker is compiled only under MOBILEGL_PIPE_PUSH";
|
||||
}
|
||||
#else
|
||||
using GLContext = MG_State::GLState::GLContext;
|
||||
|
||||
using MG_State::GLState::TextureObjectBase;
|
||||
using MobileGL::TextureTarget;
|
||||
|
||||
constexpr SizeT kAggregateCount = static_cast<SizeT>(MGPipeAggregate::Count);
|
||||
|
||||
// A live frontend context for the bump points to find, restored on the way out so the
|
||||
// cases stay independent (PipeInputsTest's idiom).
|
||||
class TrackerAggregates : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
m_previous = Move(MG_State::pGLContext);
|
||||
MG_State::pGLContext = MakeUnique<GLContext>();
|
||||
}
|
||||
void TearDown() override { MG_State::pGLContext = Move(m_previous); }
|
||||
|
||||
static GLContext& Ctx() { return *MG_State::pGLContext; }
|
||||
|
||||
struct Snapshot {
|
||||
Uint64 Values[kAggregateCount];
|
||||
Uint64 operator[](MGPipeAggregate a) const { return Values[static_cast<SizeT>(a)]; }
|
||||
};
|
||||
|
||||
static Snapshot Snap() {
|
||||
GLContext& c = Ctx();
|
||||
Snapshot s{};
|
||||
s.Values[static_cast<SizeT>(MGPipeAggregate::VaoAttribute)] = c.GetAnyVaoAttributeGeneration();
|
||||
s.Values[static_cast<SizeT>(MGPipeAggregate::FramebufferAttachment)] =
|
||||
c.GetAnyFramebufferAttachmentGeneration();
|
||||
s.Values[static_cast<SizeT>(MGPipeAggregate::TextureContent)] = c.GetAnyTextureContentGeneration();
|
||||
s.Values[static_cast<SizeT>(MGPipeAggregate::TextureParams)] = c.GetAnyTextureParamsGeneration();
|
||||
s.Values[static_cast<SizeT>(MGPipeAggregate::BufferChange)] = c.GetAnyBufferChangeGeneration();
|
||||
s.Values[static_cast<SizeT>(MGPipeAggregate::VertexAttribDefault)] =
|
||||
c.GetAnyVertexAttribDefaultGeneration();
|
||||
return s;
|
||||
}
|
||||
|
||||
// The whole contract of an aggregate generation in one assertion: the bump point
|
||||
// moved ITS counter and moved NO other. The second half is what stops a bump point
|
||||
// being wired to the wrong aggregate, which would over-fire one dirty bit and
|
||||
// under-fire another - and under-firing is the direction that renders stale.
|
||||
static void ExpectOnly(MGPipeAggregate moved, const Snapshot& before, const Snapshot& after) {
|
||||
for (SizeT i = 0; i < kAggregateCount; ++i) {
|
||||
const auto which = static_cast<MGPipeAggregate>(i);
|
||||
if (which == moved) {
|
||||
EXPECT_GT(after.Values[i], before.Values[i]) << "aggregate " << i << " did not move";
|
||||
} else {
|
||||
EXPECT_EQ(after.Values[i], before.Values[i]) << "aggregate " << i << " moved and must not";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UniquePtr<GLContext> m_previous;
|
||||
};
|
||||
|
||||
TEST_F(TrackerAggregates, EveryAggregateStartsAtZero) {
|
||||
const Snapshot s = Snap();
|
||||
for (SizeT i = 0; i < kAggregateCount; ++i) EXPECT_EQ(s.Values[i], 0ull);
|
||||
}
|
||||
|
||||
TEST_F(TrackerAggregates, AVertexArrayAttributeMovesOnlyTheVaoAggregate) {
|
||||
const auto& vao = Ctx().CreateVertexArrayObject(1);
|
||||
ASSERT_TRUE(vao != nullptr);
|
||||
const Snapshot before = Snap();
|
||||
vao->EnableAttribute(3);
|
||||
ExpectOnly(MGPipeAggregate::VaoAttribute, before, Snap());
|
||||
}
|
||||
|
||||
TEST_F(TrackerAggregates, AFramebufferObjectWriteMovesOnlyTheFramebufferAggregate) {
|
||||
const auto& fbo = Ctx().CreateFramebufferObject(1);
|
||||
ASSERT_TRUE(fbo != nullptr);
|
||||
fbo->SetReadBuffer(FramebufferAttachmentType::Color0);
|
||||
const Snapshot before = Snap();
|
||||
fbo->SetReadBuffer(FramebufferAttachmentType::Color1);
|
||||
ExpectOnly(MGPipeAggregate::FramebufferAttachment, before, Snap());
|
||||
}
|
||||
|
||||
TEST_F(TrackerAggregates, AFramebufferDefaultSetterMovesOnlyTheFramebufferAggregate) {
|
||||
const auto& fbo = Ctx().CreateFramebufferObject(2);
|
||||
ASSERT_TRUE(fbo != nullptr);
|
||||
const Snapshot before = Snap();
|
||||
// The five MOBILEGL_DEFINE_FRAMEBUFFER_DEFAULT_SETTER bodies are one macro, so the
|
||||
// bump statement inside it has to carry its own line continuation or the macro
|
||||
// silently swallows the next line. This case is what says it did not.
|
||||
fbo->SetDefaultWidth(64);
|
||||
ExpectOnly(MGPipeAggregate::FramebufferAttachment, before, Snap());
|
||||
}
|
||||
|
||||
TEST_F(TrackerAggregates, ATextureContentWriteMovesOnlyTheContentAggregate) {
|
||||
const auto& tex = Ctx().CreateTextureObject(1, TextureTarget::Texture2D);
|
||||
ASSERT_TRUE(tex != nullptr);
|
||||
const Snapshot before = Snap();
|
||||
static_cast<TextureObjectBase*>(tex.get())->BumpContentVersion();
|
||||
ExpectOnly(MGPipeAggregate::TextureContent, before, Snap());
|
||||
}
|
||||
|
||||
TEST_F(TrackerAggregates, ATextureParameterMovesOnlyTheParamsAggregate) {
|
||||
const auto& tex = Ctx().CreateTextureObject(2, TextureTarget::Texture2D);
|
||||
ASSERT_TRUE(tex != nullptr);
|
||||
const Snapshot before = Snap();
|
||||
tex->SetMaxLevel(4);
|
||||
ExpectOnly(MGPipeAggregate::TextureParams, before, Snap());
|
||||
}
|
||||
|
||||
TEST_F(TrackerAggregates, ASamplerParameterMovesOnlyTheParamsAggregate) {
|
||||
const auto& sampler = Ctx().CreateSamplerObject(1);
|
||||
ASSERT_TRUE(sampler != nullptr);
|
||||
const Snapshot before = Snap();
|
||||
sampler->SetWrapS(MobileGL::SamplerWrapMode::ClampToEdge);
|
||||
ExpectOnly(MGPipeAggregate::TextureParams, before, Snap());
|
||||
}
|
||||
|
||||
TEST_F(TrackerAggregates, ABufferRespecifyMovesOnlyTheBufferAggregate) {
|
||||
const auto& buffer = Ctx().CreateBufferObject(1);
|
||||
ASSERT_TRUE(buffer != nullptr);
|
||||
const Snapshot before = Snap();
|
||||
buffer->Respecify(64, nullptr);
|
||||
ExpectOnly(MGPipeAggregate::BufferChange, before, Snap());
|
||||
}
|
||||
|
||||
TEST_F(TrackerAggregates, AVertexAttribDefaultMovesOnlyItsOwnAggregate) {
|
||||
const Snapshot before = Snap();
|
||||
Ctx().SetCurrentVertexAttributeFloat(2, Array<Float, 4>{1.0f, 2.0f, 3.0f, 4.0f});
|
||||
ExpectOnly(MGPipeAggregate::VertexAttribDefault, before, Snap());
|
||||
}
|
||||
|
||||
TEST_F(TrackerAggregates, ANoteWithoutALiveContextIsANoOp) {
|
||||
UniquePtr<GLContext> held = Move(MG_State::pGLContext);
|
||||
MGP_NOTE_AGGREGATE(BufferChange); // must not dereference a null context
|
||||
MG_State::pGLContext = Move(held);
|
||||
SUCCEED();
|
||||
}
|
||||
#endif // MOBILEGL_PIPE_PUSH
|
||||
} // namespace
|
||||
|
||||
Reference in New Issue
Block a user