Files
MobileGL/MobileGL/MG_State/GLState/BufferState/BufferState.h
T
swung0x48 1ebe6abeb2 [MG_Pipe, MG_Impl, MG_State, MG_Remote, MG_Backend, MG_Test] (Disaggregated): P5e sb - set_shader_buffers is emitted, routed and applied
- MGPShaderBuffers::WritableMask is Uint32[3] and the POD grows 32 -> 40 (ID-104). One
  Uint32 could describe only the first 32 of an 84-point window, so a storage buffer bound
  at point 32 or above read as unwritable with nothing able to see it; the ruling widens the
  field rather than narrowing the window, which would change what an application may bind.
  MGPipeShaderBufferMaskHas / ...Set are the one spelling of the mask, so the emitter, the
  applier and the four backend sites cannot disagree about which word a point lives in.
- MGPipeApplySetShaderBuffers has its body: per-class window, Start/Count validated against
  the capacity, the mask copied by word, ONE ShaderBuffersSerial advanced on every applied
  record and ADVANCED - never zeroed - by both resets, which also clear the three windows.
- Route rows for opcode 38 on both arms (Mono_ and Wire_) and the server sink in the wire
  codec's decode switch, which is where a kCtxState row's sink lives - PipeApplier.cpp's
  ServerVerbSink has no method for it and the brief's "OnSetShaderBuffers" names no code.
  The codec also bounds Class and Start+Count where Count becomes a byte length.
- New MG_Impl/Pipe/ShaderBufferEmit.h: one record per class, window = the touched
  high-water mark clamped to 84, zero early-out before any hash, a BASE binding travelling
  as kMGPipeWholeBuffer so the server re-resolves the extent at use, three suppressor slots
  (ruling 11), and the writable mask that replaces the backend's GPU-write walk.
- BufferState gains a per-target bind-point generation (push-only, G1) and dirty bits
  15/16/17 shutter on it instead of the buffer CONTENT aggregate. That aggregate answered
  the wrong question in both directions: any glBufferSubData fired all three, and
  glBindBufferBase fired none of them, because it mutates a binding point through a returned
  reference. Same defect class as P4a's glBindSampler hole.
- Init.cpp publishes consumer bit 13 for DirectGLES and not for Magma (ID-106), in the same
  commit as the wired constant, and PipeFill's liveness gate asks R-8 for bit 13 in
  particular plus bit 13-requires-bit-7.
- `Touch` joins the dirty-surface scanner's mutator prefixes; TouchBufferBindingPoint gets
  its row and SetNamedTransformFeedbackBinding's is rewritten now that bit 17 reads a
  generation a binding moves. Negative control 6 is re-pointed: the pair it used to refute
  is a live answer, and a control that has quietly become positive is what the self-test is
  for.
2026-09-18 08:08:04 -04:00

123 lines
7.3 KiB
C++

// MobileGL - MobileGL/MG_State/GLState/BufferState/BufferState.h
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
#pragma once
#include <Includes.h>
#include <MG_Util/Miscellany/IndexGenerator.h>
#include "BufferObject.h"
namespace MobileGL::MG_State::GLState {
constexpr const auto GlobalBufferTargets =
ToArray(BufferTarget::Vertex, BufferTarget::Uniform, BufferTarget::CopyRead, BufferTarget::CopyWrite,
BufferTarget::PixelPack, BufferTarget::PixelUnpack, BufferTarget::Query, BufferTarget::Texture,
BufferTarget::TransformFeedback, BufferTarget::AtomicCounter, BufferTarget::DispatchIndirect,
BufferTarget::DrawIndirect, BufferTarget::Parameter, BufferTarget::ShaderStorage);
constexpr const auto BufferBindPointTargets = ToArray(BufferTarget::Uniform, BufferTarget::TransformFeedback,
BufferTarget::AtomicCounter, BufferTarget::ShaderStorage);
// How many indexed binding points each of BufferBindPointTargets gets. 84 is the GL 4.5 core
// minimum for GL_MAX_UNIFORM_BUFFER_BINDINGS (table 23.64) and this array is the capacity
// that limit is clamped against - at 36 the clamp in GL_Getter was degenerate (lo == hi) and
// no application could ever be told about, or bind to, a binding point past the 36th. The
// other three targets advertise their own, smaller ceilings out of
// GetIndexedBufferQueryPointCount, so widening this does not widen what they promise; it only
// costs the unused tail of three arrays.
constexpr SizeT BufferBindingPointCount = 84;
class BufferState {
public:
BufferState();
const SharedPtr<BufferObject>& GetBufferObject(Uint index);
void GenerateNames(Uint number, Vector<Uint>& buffers);
const SharedPtr<BufferObject>& CreateBufferObject(Uint index);
BindingSlot<BufferObject>& GetBindingSlot(BufferTarget target);
// For glBindBufferBase / glBindBufferRange
BindingSlotRange1D<BufferObject>& GetBindingPoint(BufferTarget target, Uint index);
const BindingSlotRange1D<BufferObject>& GetBindingPoint(BufferTarget target, Uint index) const {
return const_cast<BufferState*>(this)->GetBindingPoint(target, index);
}
constexpr SizeT GetBindingPointCount(const BufferTarget target) const {
auto it = std::find(BufferBindPointTargets.begin(), BufferBindPointTargets.end(), target);
auto index = std::distance(BufferBindPointTargets.begin(), it);
return m_bufferBindPointTargets[index].size();
}
// High-water mark of app-touched binding points per target (highest index + 1, 0 if none).
// Lets the backend skip syncing the never-touched tail of the fixed 36-point array each draw.
void TouchBindPoint(const BufferTarget target, Uint index) {
auto it = std::find(BufferBindPointTargets.begin(), BufferBindPointTargets.end(), target);
if (it == BufferBindPointTargets.end()) return;
auto slot = std::distance(BufferBindPointTargets.begin(), it);
if (static_cast<SizeT>(index) + 1 > m_touchedBindPointCount[slot])
m_touchedBindPointCount[slot] = static_cast<SizeT>(index) + 1;
}
SizeT GetTouchedBindPointCount(const BufferTarget target) const {
auto it = std::find(BufferBindPointTargets.begin(), BufferBindPointTargets.end(), target);
if (it == BufferBindPointTargets.end()) return 0;
return m_touchedBindPointCount[std::distance(BufferBindPointTargets.begin(), it)];
}
void MarkBufferObjectForDeletion(Uint index);
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; }
// P5e (sb, MG_Remote/CONTRACT-P5E.md §1, §5.6). "DID ANY INDEXED BINDING POINT OF THIS
// TARGET MOVE" - one counter per BufferBindPointTargets entry, and it is the P4b hole
// closed rather than a new convenience.
//
// WHAT WAS WRONG. Dirty bits 15/16/17 shuttered on GetAnyBufferChangeGeneration() - the
// CONTENT aggregate - while glBindBufferBase / glBindBufferRange mutate a binding point
// through a returned reference (BindingSlotRange1D::Bind / SetRange), which moves the
// slot's own Uint16 version and nothing the tracker reads. So
// `glBindBufferBase(UNIFORM,1,A); draw; glBindBufferBase(UNIFORM,1,B); draw` fired no bit
// at all: harmless while nothing was emitted for those bits, and an UNDER-FIRE the moment
// set_shader_buffers is, because the server would go on binding A. It is the same defect
// class as P4a's glBindSampler hole (c0d) and it is closed the same way - the shutter
// reads the generation the mutator moves.
//
// A COUNTER AND NOT THE SLOT'S OWN VERSION: the slot version is a WRAPPING Uint16 per
// point, and a shutter over 84 of them would have to observe all 84 through the widened
// counter every draw. One monotone Uint64 per target answers "could this target's window
// have moved" in one read, which is what a per-draw shutter can afford.
//
// #if MOBILEGL_PIPE_PUSH so the pull build's BufferState does not resize (G1) - the same
// licence TextureState::NoteImageUnitTouched took at P5d r3.
void NoteBindPointChanged(const BufferTarget target) {
auto it = std::find(BufferBindPointTargets.begin(), BufferBindPointTargets.end(), target);
if (it == BufferBindPointTargets.end()) return;
++m_bindPointGeneration[std::distance(BufferBindPointTargets.begin(), it)];
}
Uint64 GetBindPointGeneration(const BufferTarget target) const {
auto it = std::find(BufferBindPointTargets.begin(), BufferBindPointTargets.end(), target);
if (it == BufferBindPointTargets.end()) return 0;
return m_bindPointGeneration[std::distance(BufferBindPointTargets.begin(), it)];
}
#endif
private:
#if MOBILEGL_PIPE_PUSH
Uint64 m_anyBufferChangeGeneration = 0;
Array<Uint64, BufferBindPointTargets.size()> m_bindPointGeneration{};
#endif
UnorderedMap<Uint, SharedPtr<BufferObject>> m_bufferObjects;
IndexGenerator<Uint> m_indexGenerator;
Array<BindingSlot<BufferObject>, GlobalBufferTargets.size()> m_bindingSlots;
// TODO: query the count somewhere globally?
// For glBindBufferBase / glBindBufferRange
Array<Array<BindingSlotRange1D<BufferObject>, BufferBindingPointCount>, BufferBindPointTargets.size()>
m_bufferBindPointTargets;
Array<SizeT, BufferBindPointTargets.size()> m_touchedBindPointCount;
};
} // namespace MobileGL::MG_State::GLState