mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 12:48:32 +09:00
- Plan B section 4 makes the frontend/backend boundary explicit and gives the backend its
own state machine. This is the P0 deliverable of section 11: the whole catalogue exists
from day one, placeholders included, because the wire opcode is a call's position in
PipeCalls.def and record numbering must never churn.
- MG_Pipe/PipeCalls.def is the single source of truth: 68 unique calls as
X(Name, Payload, Class, Flags). Its header reconciles that number with the plan's
headline counts (section 4.4, appendix A), which double count - "CSO 15" names
bind_sampler_states and set_sampler_views that the "set_* 17" list also names, "screen
14" tabulates the query family that section 4.3 assigns to the context, and the
"transfer 12" row enumerates 11 calls. Each reconciliation is written down next to the
count rather than resolved silently.
- MGPipeHandles.h: the 8-byte {slot, gen} pair, dense per-kind slots, the reserved null
and default-framebuffer handles, and the ShaderCso composite band (sections 4.2, 5.6.3).
The two generations are documented as strictly separate, with the interface rule that no
call may require the client to know MGGen.
- MGPipeTypes.h: every payload of section 4.5 as a flat POD with explicit padding, a
trivial-copyability assertion and an exact sizeof assertion, because the wire records are
memcpy'd and a field silently changing width is a protocol break no test would see.
MGPCaps embeds DynamicBackendParameters by inclusion so a caps field added there needs no
second edit here; its assertion is stated as a composition because that struct still
carries SizeT. ResidualValueBlock is pinned at MGL_RESIDUAL_BLOCK_SIZE 1248, the ratchet
that only ever goes down and reaches static_assert(... == 0) in P13 (section 6.3).
- MGPipeHostSpan.h keeps the one shape that changes with the transport isolated behind one
predictable branch, with the kFromServerIndexMirror sentinel D-B7 needs.
- MGPipeCallbacks.h names the reverse channel as ten callbacks plus the forward terminator
in the context table, replacing 95 poke sites across 17 methods (section 7.1).
- scripts/gen_pipe.py runs G1-G7 off those .def files. G1 asserts each table is EXACTLY its
call count of function pointers; G3 pads every wire record to the stream's 8-byte
granularity and checks size >= sizeof && size <= remaining && size % 8 == 0 before
dispatch, fatally; G4 compares field by field (padding excluded, floats by bits) because
a comparator with false positives is one nobody reads - DirectGLES.cpp says the same
thing about its own memcmp of RenderStateParameters; G5 turns the accessor list into
per-verb poison generations rather than a written-once bitmap, which is the only version
that can see a field left over from the previous draw (section 6.2.2); G6 joins the 477
read points of the vendored backend_read_inventory.md against Coverage.def and reports
0 UNMAPPED (299 to a call, 167 signatures that become handle parameters, 6 reverse
channel, 5 client-resolved); G7 pins the pipeline subset BY MEMBER NAME from what
VulkanRenderer::ComputePipelineStateHash hashes today, computing no offsets in python.
- The generated files are committed so the build never depends on python; CI regenerates
and diffs them.
99 lines
4.3 KiB
C++
99 lines
4.3 KiB
C++
// MobileGL - MobileGL/MG_Pipe/MGPipeHandles.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>
|
|
|
|
// MGPipe object identity (plan B section 4.2).
|
|
//
|
|
// A handle is a {slot, gen} pair minted by the CLIENT and never by the server: no create_*
|
|
// call in the catalogue returns a server-cast handle, which is the deliberate deviation
|
|
// from gallium (D1) that lets the whole catalogue be remoted with ZERO creation round
|
|
// trips.
|
|
//
|
|
// Slots are dense and allocated PER KIND, so the server's object table is an array rather
|
|
// than a hash map. The allocator is a free list plus a high-water mark and has nothing to
|
|
// do with MG_State's IndexGenerator - that container's LIFO name reuse is the very problem
|
|
// {slot, gen} exists to close.
|
|
namespace MobileGL::MG_Pipe {
|
|
enum class MGPipeKind : Uint8 {
|
|
None = 0,
|
|
Buffer = 1,
|
|
Texture,
|
|
Renderbuffer,
|
|
Framebuffer,
|
|
Xfb,
|
|
RenderStateCso,
|
|
VertexElementsCso,
|
|
SamplerCso,
|
|
SamplerViewCso,
|
|
ShaderCso,
|
|
Fence,
|
|
Query,
|
|
Context,
|
|
KindCount,
|
|
};
|
|
|
|
// 8 bytes, POD, passed by value in a register pair.
|
|
//
|
|
// Gen increments only when a SLOT IS REUSED - never on a respecify - so {slot, gen} is
|
|
// unique until the same slot has been recycled 2^32 times. That bound is documented
|
|
// rather than defended at runtime in release builds: at one recycle per frame at
|
|
// 1000 fps a single slot would take ~50 days of continuous churn to wrap, and the
|
|
// debug allocator asserts on the wrap.
|
|
//
|
|
// Two generations exist in this design and they are strictly separate (section 4.2.2):
|
|
// this one is the CLIENT's answer to "is this still the same GL object", while MGGen is
|
|
// the SERVER's own epoch for "did I recast my driver object". Interface rule: no MGPipe
|
|
// call may require the client to supply or know MGGen.
|
|
struct MGPipeHandle {
|
|
Uint32 Slot;
|
|
Uint32 Gen;
|
|
|
|
friend constexpr Bool operator==(const MGPipeHandle& a, const MGPipeHandle& b) {
|
|
return a.Slot == b.Slot && a.Gen == b.Gen;
|
|
}
|
|
};
|
|
|
|
static_assert(sizeof(MGPipeHandle) == 8, "MGPipeHandle is the 8-byte {slot, gen} pair");
|
|
static_assert(alignof(MGPipeHandle) == 4, "MGPipeHandle must not gain padding on the wire");
|
|
static_assert(std::is_trivially_copyable_v<MGPipeHandle>);
|
|
|
|
// Reserved handles (section 4.2.1).
|
|
// {0, 0} is null for every kind.
|
|
// {0, 1} of kind Framebuffer is the DEFAULT framebuffer. It exists so the four
|
|
// pDefaultFramebufferInfo->defaultFBO identity comparisons in DirectGLES retire into
|
|
// an ordinary handle compare.
|
|
inline constexpr MGPipeHandle kMGPipeNullHandle{0, 0};
|
|
inline constexpr MGPipeHandle kMGPipeDefaultFramebuffer{0, 1};
|
|
|
|
inline constexpr Bool MGPipeHandleIsNull(const MGPipeHandle& handle) {
|
|
return handle.Slot == 0 && handle.Gen == 0;
|
|
}
|
|
|
|
// Slot 0 of every kind is reserved (null, and the default framebuffer for kind
|
|
// Framebuffer), so a real allocation starts at 1.
|
|
inline constexpr Uint32 kMGPipeFirstAllocatableSlot = 1;
|
|
|
|
// ShaderCso slot space. The top 1/16 of it is reserved for PROGRAM PIPELINE COMPOSITES
|
|
// (section 5.6.3): a composite is minted client-side out of the stage programs bound to
|
|
// a pipeline object, and the server never learns it is a composite - it is just another
|
|
// ShaderCso. Reserving a band rather than a flag keeps the composite resolver's
|
|
// lifetime bookkeeping out of the ordinary program slot allocator.
|
|
inline constexpr Uint32 kMGPipeShaderCsoSlotLimit = 1u << 20;
|
|
inline constexpr Uint32 kMGPipeShaderCsoCompositeSlotBase =
|
|
kMGPipeShaderCsoSlotLimit - (kMGPipeShaderCsoSlotLimit >> 4);
|
|
|
|
inline constexpr Bool MGPipeIsCompositeShaderSlot(Uint32 slot) {
|
|
return slot >= kMGPipeShaderCsoCompositeSlotBase && slot < kMGPipeShaderCsoSlotLimit;
|
|
}
|
|
|
|
static_assert(kMGPipeShaderCsoCompositeSlotBase > kMGPipeFirstAllocatableSlot,
|
|
"the composite band must not swallow the ordinary program slots");
|
|
} // namespace MobileGL::MG_Pipe
|