mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 04:38:30 +09:00
[Feat] (Pipe): apply sampler states, sampler views and the three unit sets into the server's own working state
This commit is contained in:
+213
-11
@@ -862,6 +862,53 @@ namespace MobileGL::MG_Pipe {
|
||||
// record was accumulated, so the texels are the server's now.
|
||||
++stored->Serial;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// P4a: the three kVarTail unit sets share one body.
|
||||
//
|
||||
// They differ in exactly one thing - what an entry IS - and in nothing else: the same
|
||||
// window rule, the same bound, the same "the entries outside the window are not
|
||||
// cleared", the same serial discipline. set_vertex_buffers wrote this arithmetic once
|
||||
// already; a second, third and fourth copy of it would be three more places to get the
|
||||
// Start + Count overflow wrong.
|
||||
//
|
||||
// THE WINDOW IS THE BOUND AND ENTRIES OUTSIDE IT ARE NOT CLEARED. The record is "the
|
||||
// last set as received": a set that names four units has said nothing about the other
|
||||
// 188, and clearing them would unbind textures the client never mentioned.
|
||||
//
|
||||
// THE ENTRY'S OWN Unit FIELD IS NOT POLICED, deliberately and for MGPVertexBuffer::
|
||||
// BindingIndex's reason: the DESTINATION is Start + i, which is the window this
|
||||
// function has already bounded, and the field beside it is the client's own label for
|
||||
// the entry. Policing it would hand the emitter a contract this applier cannot justify
|
||||
// - the two agree by construction or the emitter is broken in a way a unit test on the
|
||||
// emitter's side is the right place to catch.
|
||||
template <class Entry, class ArrayT>
|
||||
Bool ApplyUnitWindow(const char* call, Uint32 start, Uint32 count, Uint64 contentHash,
|
||||
const Entry* tail, ArrayT& destination, Uint32& destinationStart,
|
||||
Uint32& destinationCount) {
|
||||
const Uint64 capacity = destination.size();
|
||||
const Uint64 end = Uint64{start} + Uint64{count};
|
||||
const char* fault = nullptr;
|
||||
if (end > capacity) {
|
||||
fault = "the window runs past the merged texture-unit space";
|
||||
} else if (count != 0 && tail == nullptr) {
|
||||
fault = "a non-empty set carries no entries";
|
||||
}
|
||||
if (fault != nullptr) {
|
||||
MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption")
|
||||
" %s {start=%u, count=%u, hash=%llu}: %s (the applier holds %llu "
|
||||
"entries)",
|
||||
call, start, count, static_cast<unsigned long long>(contentHash),
|
||||
fault, static_cast<unsigned long long>(capacity));
|
||||
return false;
|
||||
}
|
||||
for (Uint32 i = 0; i < count; ++i) {
|
||||
destination[start + i] = tail[i];
|
||||
}
|
||||
destinationStart = start;
|
||||
destinationCount = count;
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
MGPipeApplierState& MGPipeApplier() { return g_applier; }
|
||||
@@ -1828,16 +1875,144 @@ namespace MobileGL::MG_Pipe {
|
||||
++g_applier.FramebufferSerial;
|
||||
}
|
||||
|
||||
// ================================================================================
|
||||
// w2: sampler CSOs, sampler views and the three unit sets
|
||||
// ================================================================================
|
||||
|
||||
void MGPipeApplyCreateSamplerState(const MGPSamplerDesc& desc, const SamplerParameters* parameters) {
|
||||
(void)desc;
|
||||
(void)parameters;
|
||||
MOBILEGL_ASSERT(desc.Cso.Slot >= kMGPipeFirstAllocatableSlot,
|
||||
"create_sampler_state named the reserved slot 0");
|
||||
if (desc.Cso.Slot < kMGPipeFirstAllocatableSlot) return;
|
||||
|
||||
// THE ONE BLOB RULE, on the family's own blob: a non-zero Parameters.Size must be
|
||||
// exactly one SamplerParameters, and a zero means "this record does not declare its
|
||||
// blob" - which is what a monolith emission is, because the value rides beside the
|
||||
// record through the companion pointer. Either way the bytes read are bounded by the
|
||||
// TYPE and not by the declared length, so the length is a cross-check and never the
|
||||
// safety property.
|
||||
const char* fault = nullptr;
|
||||
if (desc.Parameters.Size != 0 && desc.Parameters.Size != sizeof(SamplerParameters)) {
|
||||
fault = "the declared blob length is not one SamplerParameters";
|
||||
} else if (parameters == nullptr) {
|
||||
fault = "the record declares no parameters and carries none";
|
||||
}
|
||||
if (fault != nullptr) {
|
||||
MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption")
|
||||
" create_sampler_state {slot=%u, gen=%u}: %s (one SamplerParameters is "
|
||||
"%llu bytes, the blob declares %llu)",
|
||||
desc.Cso.Slot, desc.Cso.Gen, fault,
|
||||
static_cast<unsigned long long>(sizeof(SamplerParameters)),
|
||||
static_cast<unsigned long long>(desc.Parameters.Size));
|
||||
return;
|
||||
}
|
||||
|
||||
MGPipeSamplerCsoRecord* recordAt =
|
||||
RecordAt(g_applier.SamplerCsos, desc.Cso.Slot, kMGPipeMaxSamplerCsoSlots);
|
||||
if (recordAt == nullptr) {
|
||||
MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption")
|
||||
" create_sampler_state {slot=%u, gen=%u}: the slot is outside the record "
|
||||
"table's bound (%u)",
|
||||
desc.Cso.Slot, desc.Cso.Gen, kMGPipeMaxSamplerCsoSlots);
|
||||
return;
|
||||
}
|
||||
MGPipeSamplerCsoRecord& record = *recordAt;
|
||||
// A CREATE STARTS THE RECORD OVER AND LEAVES Serial AT 0; A RE-ISSUE ON A LIVE IDENTITY
|
||||
// COUNTS UP. The first half is what stops a recycled slot contributing one field of its
|
||||
// predecessor, and it is why a fresh backend twin starting its own synced serial at 0
|
||||
// agrees with a fresh record without either side publishing anything. The second is how
|
||||
// a value change travels on a handle whose Gen moves only on slot reuse - and it is a
|
||||
// mutation, so it moves the serial.
|
||||
if (!record.Live || record.Gen != desc.Cso.Gen) {
|
||||
record = MGPipeSamplerCsoRecord{};
|
||||
record.Gen = desc.Cso.Gen;
|
||||
} else {
|
||||
++record.Serial;
|
||||
}
|
||||
record.Live = true;
|
||||
// BY VALUE, INCLUDING borderColorForm. All three border representations are always
|
||||
// numerically populated, so the value alone cannot say which driver entry point to use,
|
||||
// and the backend's redundancy filter compares all four. The three trailing padding
|
||||
// bytes are why MOBILEGL_PIPE_VERIFY compares this FIELD BY FIELD through
|
||||
// PipeFields.def's MGP_FIELDS_SamplerParameters rather than as bytes.
|
||||
record.Params = *parameters;
|
||||
}
|
||||
|
||||
void MGPipeApplyDeleteSamplerState(const MGPHandleOnly& handle) { (void)handle; }
|
||||
void MGPipeApplyDeleteSamplerState(const MGPHandleOnly& handle) {
|
||||
MOBILEGL_ASSERT(handle.Kind == static_cast<Uint32>(MGPipeKind::SamplerCso),
|
||||
"delete_sampler_state on kind %u", handle.Kind);
|
||||
MGPipeSamplerCsoRecord* record =
|
||||
ResolveObject(g_applier.SamplerCsos, "delete_sampler_state", handle.Handle);
|
||||
if (record == nullptr) return;
|
||||
|
||||
void MGPipeApplyCreateSamplerView(const MGPSamplerView& view) { (void)view; }
|
||||
// Dropped whole, generation kept, for resource_destroy's reason: the client allocator
|
||||
// owns the Gen bump and takes it on the next handout of the slot, so a server-side bump
|
||||
// here would put the two identities out of step.
|
||||
//
|
||||
// AND THE UNIT SET IS NOT SWEPT. bind_sampler_states is "the last set as received" and
|
||||
// the client re-emits the whole window from its own resolved set at the next verb, so
|
||||
// walking 192 entries here to blank a handle the next set is about to overwrite would
|
||||
// buy nothing and would break the one rule the window has.
|
||||
const Uint32 gen = record->Gen;
|
||||
*record = MGPipeSamplerCsoRecord{};
|
||||
record->Gen = gen;
|
||||
}
|
||||
|
||||
void MGPipeApplyDeleteSamplerView(const MGPHandleOnly& handle) { (void)handle; }
|
||||
void MGPipeApplyCreateSamplerView(const MGPSamplerView& view) {
|
||||
MOBILEGL_ASSERT(view.Cso.Slot >= kMGPipeFirstAllocatableSlot,
|
||||
"create_sampler_view named the reserved slot 0");
|
||||
if (view.Cso.Slot < kMGPipeFirstAllocatableSlot) return;
|
||||
|
||||
MGPipeSamplerViewRecord* recordAt =
|
||||
RecordAt(g_applier.SamplerViewCsos, view.Cso.Slot, kMGPipeMaxSamplerViewSlots);
|
||||
if (recordAt == nullptr) {
|
||||
MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption")
|
||||
" create_sampler_view {slot=%u, gen=%u}: the slot is outside the record "
|
||||
"table's bound (%u)",
|
||||
view.Cso.Slot, view.Cso.Gen, kMGPipeMaxSamplerViewSlots);
|
||||
return;
|
||||
}
|
||||
MGPipeSamplerViewRecord& record = *recordAt;
|
||||
// RE-ISSUING ON THE SAME HANDLE IS HOW A RESTRICTION CHANGE TRAVELS - a view is
|
||||
// identity-addressed one per texture object, minted off that object's lifetime id, and
|
||||
// Gen moves only on slot reuse - so it bumps the serial and starts nothing over. AND IT
|
||||
// DOES NOT REBIND: a re-issue on a view some unit is holding changes what that unit
|
||||
// sees, which the serial already says; it must not make some other unit see it.
|
||||
if (!record.Live || record.Gen != view.Cso.Gen) {
|
||||
record = MGPipeSamplerViewRecord{};
|
||||
record.Gen = view.Cso.Gen;
|
||||
} else {
|
||||
++record.Serial;
|
||||
}
|
||||
record.Live = true;
|
||||
record.View = view;
|
||||
|
||||
// THE TEXTURE'S OWN ViewCso IS WRITTEN HERE, and it is a SILENT lookup rather than a
|
||||
// resolved one: the texture-resource bit and the sampler bit are independent, so a view
|
||||
// arriving before - or without - the texture record is an ordering fact and not a
|
||||
// refusal. When the record is there this is the back-pointer that lets a sync reach a
|
||||
// texture's view without walking every view the applier holds.
|
||||
MGPipeResourceRecord* texture = FindIn(g_applier.TextureResources, view.Texture);
|
||||
if (texture != nullptr) texture->ViewCso = view.Cso;
|
||||
}
|
||||
|
||||
void MGPipeApplyDeleteSamplerView(const MGPHandleOnly& handle) {
|
||||
MOBILEGL_ASSERT(handle.Kind == static_cast<Uint32>(MGPipeKind::SamplerViewCso),
|
||||
"delete_sampler_view on kind %u", handle.Kind);
|
||||
MGPipeSamplerViewRecord* record =
|
||||
ResolveObject(g_applier.SamplerViewCsos, "delete_sampler_view", handle.Handle);
|
||||
if (record == nullptr) return;
|
||||
|
||||
// The back-pointer goes first, while the record that names the texture still exists,
|
||||
// and only if that texture still names THIS view - a texture whose view has already
|
||||
// been re-minted must not have the new handle cleared out from under it.
|
||||
MGPipeResourceRecord* texture = FindIn(g_applier.TextureResources, record->View.Texture);
|
||||
if (texture != nullptr && texture->ViewCso == handle.Handle) {
|
||||
texture->ViewCso = kMGPipeNullHandle;
|
||||
}
|
||||
const Uint32 gen = record->Gen;
|
||||
*record = MGPipeSamplerViewRecord{};
|
||||
record->Gen = gen;
|
||||
}
|
||||
|
||||
void MGPipeApplySetTextureParams(const MGPTextureParams& params) {
|
||||
// ADDRESSED BY RESOURCE AND BY NOTHING ELSE, which is the whole point of the call: a
|
||||
@@ -1877,19 +2052,46 @@ namespace MobileGL::MG_Pipe {
|
||||
++record->ParamsSerial;
|
||||
}
|
||||
|
||||
// The three of them, and NO STAGE DIMENSION on any of them: MobileGL's texture-unit space
|
||||
// is one merged array of 192, the same unit may be sampled from two stages, and stage is
|
||||
// derived server-side from the reflection archive only where the target API needs it.
|
||||
//
|
||||
// A NULL HANDLE IN A TAIL ENTRY IS LEGAL EVERYWHERE HERE and is not a refusal: a unit the
|
||||
// program does not resolve carries a null view, a unit with no sampler object carries a
|
||||
// null sampler CSO (the texture's built-in sampler applies then, exactly as today), and a
|
||||
// unit with no texture carries a null resource. None of the three is resolved against a
|
||||
// record either - a set is WORKING STATE, the records it names are OBJECT state, and the
|
||||
// backend resolves the pair at its own sync point where both are current.
|
||||
void MGPipeApplySetSamplerViews(const MGPSamplerViews& hdr, const MGPBoundView* tail) {
|
||||
(void)hdr;
|
||||
(void)tail;
|
||||
if (!ApplyUnitWindow("set_sampler_views", hdr.Start, hdr.Count, hdr.ContentHash, tail,
|
||||
g_applier.BoundSamplerViews, g_applier.SamplerViewStart,
|
||||
g_applier.SamplerViewCount)) {
|
||||
return;
|
||||
}
|
||||
++g_applier.SamplerViewsSerial;
|
||||
}
|
||||
|
||||
void MGPipeApplyBindSamplerStates(const MGPSamplerStates& hdr, const MGPipeHandle* tail) {
|
||||
(void)hdr;
|
||||
(void)tail;
|
||||
if (!ApplyUnitWindow("bind_sampler_states", hdr.Start, hdr.Count, hdr.ContentHash, tail,
|
||||
g_applier.BoundSamplerStates, g_applier.SamplerStateStart,
|
||||
g_applier.SamplerStateCount)) {
|
||||
return;
|
||||
}
|
||||
++g_applier.SamplerStatesSerial;
|
||||
}
|
||||
|
||||
void MGPipeApplySetShaderImages(const MGPShaderImages& hdr, const MGPImageView* tail) {
|
||||
(void)hdr;
|
||||
(void)tail;
|
||||
// The image set carries InternalFormat and Access per entry and BOTH are live
|
||||
// glBindImageTexture state the format-less image bake keys on, so they are stored as
|
||||
// sent and recast on the server - the record carries the application's format, and the
|
||||
// bind-format recast that turns a GL_RG32F bind into something 19 of 26 non-core
|
||||
// formats on Adreno will accept is the backend's, not this applier's.
|
||||
if (!ApplyUnitWindow("set_shader_images", hdr.Start, hdr.Count, hdr.ContentHash, tail,
|
||||
g_applier.BoundShaderImages, g_applier.ShaderImageStart,
|
||||
g_applier.ShaderImageCount)) {
|
||||
return;
|
||||
}
|
||||
++g_applier.ShaderImagesSerial;
|
||||
}
|
||||
|
||||
void MGPipeApplyCreateShaderState(const MGPProgramDesc& desc,
|
||||
|
||||
Reference in New Issue
Block a user