mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-17 08:38:30 +09:00
[Merge] (MGPipe, P5): integrate package w1
This commit is contained in:
@@ -868,233 +868,266 @@ static_assert(sizeof(MGPWireRec_FenceWaitServer) ==
|
|||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
// Returns whether the record was applied. P0 is a SKELETON: every case validates its
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
// bounds and then reports "not applied", because no applier exists until P5 wires
|
// THE DECODER HOOK (P5 w1). MG_Pipe is BELOW MG_Remote and may not include it, so the real
|
||||||
// MG_Remote/Server/PipeApplier.cpp to the real backend tables. The switch and the opcode
|
// 71-arm decoder - MG_Remote/Wire/PipeWireCodec.cpp, which resolves the segments, cross-checks
|
||||||
// enum come from the same list, so a call added to the catalogue cannot be forgotten here;
|
// the variable tails and calls today's MGPipeApply* free functions - installs itself here.
|
||||||
// the default arm is for the opcode that never came from this catalogue at all - a byte
|
// The indirection is the layering, not a policy: gMGPipeSegmentResolver
|
||||||
// off a corrupt stream - and it is fatal for the same reason the bounds check is.
|
// (MGPipeHostSpan.h:47) is the same shape for the same reason.
|
||||||
|
//
|
||||||
|
// It lives behind the build option because G1 admits no symbol movement in a PULL build, and
|
||||||
|
// an inline variable that MGPipeApplyWireRecord odr-uses would be one.
|
||||||
|
using MGPipeWireRecordApplyFn = Bool (*)(MGPWireOp op, const void* record, Uint64 size,
|
||||||
|
Uint64 remaining);
|
||||||
|
inline MGPipeWireRecordApplyFn gMGPipeWireRecordApply = nullptr;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Returns whether the record was applied.
|
||||||
|
//
|
||||||
|
// THE SWITCH IS THE PER-OPCODE BOUNDS GATE AND NOTHING ELSE, AND IT CANNOT SEE THE TAIL.
|
||||||
|
// MGP_WIRE_CHECK_BOUNDS proves `size >= sizeof(MGPWireRec_X)`, `size <= remaining` and
|
||||||
|
// 8-alignment - which is exactly the part a generator can state, because it is the part that
|
||||||
|
// follows from the opcode alone. A kVarTail record declaring Count = 4000 while carrying 8
|
||||||
|
// bytes passes every one of those, so the SECOND check - recompute the total from the
|
||||||
|
// record's own count fields and require it to EQUAL MGPWireRecHeader::Size - belongs to the
|
||||||
|
// decoder, which has the payload (MG_Remote/Wire's MGPipeWireRecordLayout, P5 w1).
|
||||||
|
//
|
||||||
|
// The switch and the opcode enum come from the same list, so a call added to the catalogue
|
||||||
|
// cannot be forgotten here; the default arm is for the opcode that never came from this
|
||||||
|
// catalogue at all - a byte off a corrupt stream - and it is fatal for the same reason the
|
||||||
|
// bounds check is.
|
||||||
|
//
|
||||||
|
// With no decoder installed - every monolith build, and a split build before
|
||||||
|
// ClientSession::Start - this returns false, "this build does not implement it". That is the
|
||||||
|
// P0 skeleton's answer, kept deliberately: a codec that has not been installed must not look
|
||||||
|
// like one that applied the record.
|
||||||
inline Bool MGPipeApplyWireRecord(MGPWireOp op, const void* record, Uint64 size, Uint64 remaining) {
|
inline Bool MGPipeApplyWireRecord(MGPWireOp op, const void* record, Uint64 size, Uint64 remaining) {
|
||||||
(void)record;
|
(void)record;
|
||||||
switch (op) {
|
switch (op) {
|
||||||
case MGPWireOp::GetCaps:
|
case MGPWireOp::GetCaps:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_GetCaps, "GetCaps");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_GetCaps, "GetCaps");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::ResourceCreate:
|
case MGPWireOp::ResourceCreate:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_ResourceCreate, "ResourceCreate");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_ResourceCreate, "ResourceCreate");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::ResourceRespecify:
|
case MGPWireOp::ResourceRespecify:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_ResourceRespecify, "ResourceRespecify");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_ResourceRespecify, "ResourceRespecify");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::ResourceDestroy:
|
case MGPWireOp::ResourceDestroy:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_ResourceDestroy, "ResourceDestroy");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_ResourceDestroy, "ResourceDestroy");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::MapPersistent:
|
case MGPWireOp::MapPersistent:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_MapPersistent, "MapPersistent");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_MapPersistent, "MapPersistent");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::UnmapPersistent:
|
case MGPWireOp::UnmapPersistent:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_UnmapPersistent, "UnmapPersistent");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_UnmapPersistent, "UnmapPersistent");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::FenceCreate:
|
case MGPWireOp::FenceCreate:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_FenceCreate, "FenceCreate");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_FenceCreate, "FenceCreate");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::FenceStatus:
|
case MGPWireOp::FenceStatus:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_FenceStatus, "FenceStatus");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_FenceStatus, "FenceStatus");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::FenceWait:
|
case MGPWireOp::FenceWait:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_FenceWait, "FenceWait");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_FenceWait, "FenceWait");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::FenceDestroy:
|
case MGPWireOp::FenceDestroy:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_FenceDestroy, "FenceDestroy");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_FenceDestroy, "FenceDestroy");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::QueryCreate:
|
case MGPWireOp::QueryCreate:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_QueryCreate, "QueryCreate");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_QueryCreate, "QueryCreate");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::QueryBegin:
|
case MGPWireOp::QueryBegin:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_QueryBegin, "QueryBegin");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_QueryBegin, "QueryBegin");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::QueryEnd:
|
case MGPWireOp::QueryEnd:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_QueryEnd, "QueryEnd");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_QueryEnd, "QueryEnd");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::QueryAvailable:
|
case MGPWireOp::QueryAvailable:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_QueryAvailable, "QueryAvailable");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_QueryAvailable, "QueryAvailable");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::QueryResult:
|
case MGPWireOp::QueryResult:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_QueryResult, "QueryResult");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_QueryResult, "QueryResult");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::QueryDestroy:
|
case MGPWireOp::QueryDestroy:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_QueryDestroy, "QueryDestroy");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_QueryDestroy, "QueryDestroy");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::CreateRenderState:
|
case MGPWireOp::CreateRenderState:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_CreateRenderState, "CreateRenderState");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_CreateRenderState, "CreateRenderState");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::BindRenderState:
|
case MGPWireOp::BindRenderState:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_BindRenderState, "BindRenderState");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_BindRenderState, "BindRenderState");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::DeleteRenderState:
|
case MGPWireOp::DeleteRenderState:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_DeleteRenderState, "DeleteRenderState");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_DeleteRenderState, "DeleteRenderState");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::CreateVertexElements:
|
case MGPWireOp::CreateVertexElements:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_CreateVertexElements, "CreateVertexElements");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_CreateVertexElements, "CreateVertexElements");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::BindVertexElements:
|
case MGPWireOp::BindVertexElements:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_BindVertexElements, "BindVertexElements");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_BindVertexElements, "BindVertexElements");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::DeleteVertexElements:
|
case MGPWireOp::DeleteVertexElements:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_DeleteVertexElements, "DeleteVertexElements");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_DeleteVertexElements, "DeleteVertexElements");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::CreateSamplerState:
|
case MGPWireOp::CreateSamplerState:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_CreateSamplerState, "CreateSamplerState");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_CreateSamplerState, "CreateSamplerState");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::DeleteSamplerState:
|
case MGPWireOp::DeleteSamplerState:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_DeleteSamplerState, "DeleteSamplerState");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_DeleteSamplerState, "DeleteSamplerState");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::CreateSamplerView:
|
case MGPWireOp::CreateSamplerView:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_CreateSamplerView, "CreateSamplerView");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_CreateSamplerView, "CreateSamplerView");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::DeleteSamplerView:
|
case MGPWireOp::DeleteSamplerView:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_DeleteSamplerView, "DeleteSamplerView");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_DeleteSamplerView, "DeleteSamplerView");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::CreateShaderState:
|
case MGPWireOp::CreateShaderState:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_CreateShaderState, "CreateShaderState");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_CreateShaderState, "CreateShaderState");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::BindShaderState:
|
case MGPWireOp::BindShaderState:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_BindShaderState, "BindShaderState");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_BindShaderState, "BindShaderState");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::DeleteShaderState:
|
case MGPWireOp::DeleteShaderState:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_DeleteShaderState, "DeleteShaderState");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_DeleteShaderState, "DeleteShaderState");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::SetDynamicState:
|
case MGPWireOp::SetDynamicState:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetDynamicState, "SetDynamicState");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetDynamicState, "SetDynamicState");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::SetFramebufferState:
|
case MGPWireOp::SetFramebufferState:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetFramebufferState, "SetFramebufferState");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetFramebufferState, "SetFramebufferState");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::SetVertexBuffers:
|
case MGPWireOp::SetVertexBuffers:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetVertexBuffers, "SetVertexBuffers");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetVertexBuffers, "SetVertexBuffers");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::SetIndexBuffer:
|
case MGPWireOp::SetIndexBuffer:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetIndexBuffer, "SetIndexBuffer");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetIndexBuffer, "SetIndexBuffer");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::SetIndirectBuffers:
|
case MGPWireOp::SetIndirectBuffers:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetIndirectBuffers, "SetIndirectBuffers");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetIndirectBuffers, "SetIndirectBuffers");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::SetSamplerViews:
|
case MGPWireOp::SetSamplerViews:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetSamplerViews, "SetSamplerViews");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetSamplerViews, "SetSamplerViews");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::BindSamplerStates:
|
case MGPWireOp::BindSamplerStates:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_BindSamplerStates, "BindSamplerStates");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_BindSamplerStates, "BindSamplerStates");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::SetShaderImages:
|
case MGPWireOp::SetShaderImages:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetShaderImages, "SetShaderImages");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetShaderImages, "SetShaderImages");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::SetShaderBuffers:
|
case MGPWireOp::SetShaderBuffers:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetShaderBuffers, "SetShaderBuffers");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetShaderBuffers, "SetShaderBuffers");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::SetStreamOutputTargets:
|
case MGPWireOp::SetStreamOutputTargets:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetStreamOutputTargets, "SetStreamOutputTargets");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetStreamOutputTargets, "SetStreamOutputTargets");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::SetGlobalConstants:
|
case MGPWireOp::SetGlobalConstants:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetGlobalConstants, "SetGlobalConstants");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetGlobalConstants, "SetGlobalConstants");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::SetVertexAttribDefaults:
|
case MGPWireOp::SetVertexAttribDefaults:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetVertexAttribDefaults, "SetVertexAttribDefaults");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetVertexAttribDefaults, "SetVertexAttribDefaults");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::SetPixelPackState:
|
case MGPWireOp::SetPixelPackState:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetPixelPackState, "SetPixelPackState");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetPixelPackState, "SetPixelPackState");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::SetPatchState:
|
case MGPWireOp::SetPatchState:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetPatchState, "SetPatchState");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetPatchState, "SetPatchState");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::SetDrawProgram:
|
case MGPWireOp::SetDrawProgram:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetDrawProgram, "SetDrawProgram");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetDrawProgram, "SetDrawProgram");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::SetDispatchProgram:
|
case MGPWireOp::SetDispatchProgram:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetDispatchProgram, "SetDispatchProgram");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetDispatchProgram, "SetDispatchProgram");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::SetResidualValueState:
|
case MGPWireOp::SetResidualValueState:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetResidualValueState, "SetResidualValueState");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetResidualValueState, "SetResidualValueState");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::SetTextureParams:
|
case MGPWireOp::SetTextureParams:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetTextureParams, "SetTextureParams");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetTextureParams, "SetTextureParams");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::ResourceSubData:
|
case MGPWireOp::ResourceSubData:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_ResourceSubData, "ResourceSubData");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_ResourceSubData, "ResourceSubData");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::BufferSubDataResident:
|
case MGPWireOp::BufferSubDataResident:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_BufferSubDataResident, "BufferSubDataResident");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_BufferSubDataResident, "BufferSubDataResident");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::ResourceSubDataComplete:
|
case MGPWireOp::ResourceSubDataComplete:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_ResourceSubDataComplete, "ResourceSubDataComplete");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_ResourceSubDataComplete, "ResourceSubDataComplete");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::ResourceFlushRange:
|
case MGPWireOp::ResourceFlushRange:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_ResourceFlushRange, "ResourceFlushRange");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_ResourceFlushRange, "ResourceFlushRange");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::ResourceReadback:
|
case MGPWireOp::ResourceReadback:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_ResourceReadback, "ResourceReadback");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_ResourceReadback, "ResourceReadback");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::ResourceCopyRegion:
|
case MGPWireOp::ResourceCopyRegion:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_ResourceCopyRegion, "ResourceCopyRegion");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_ResourceCopyRegion, "ResourceCopyRegion");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::GenerateMipmap:
|
case MGPWireOp::GenerateMipmap:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_GenerateMipmap, "GenerateMipmap");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_GenerateMipmap, "GenerateMipmap");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::GetTextureImage:
|
case MGPWireOp::GetTextureImage:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_GetTextureImage, "GetTextureImage");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_GetTextureImage, "GetTextureImage");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::Blit:
|
case MGPWireOp::Blit:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_Blit, "Blit");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_Blit, "Blit");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::Clear:
|
case MGPWireOp::Clear:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_Clear, "Clear");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_Clear, "Clear");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::ReadPixels:
|
case MGPWireOp::ReadPixels:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_ReadPixels, "ReadPixels");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_ReadPixels, "ReadPixels");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::DrawVbo:
|
case MGPWireOp::DrawVbo:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_DrawVbo, "DrawVbo");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_DrawVbo, "DrawVbo");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::LaunchGrid:
|
case MGPWireOp::LaunchGrid:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_LaunchGrid, "LaunchGrid");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_LaunchGrid, "LaunchGrid");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::MemoryBarrier:
|
case MGPWireOp::MemoryBarrier:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_MemoryBarrier, "MemoryBarrier");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_MemoryBarrier, "MemoryBarrier");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::BeginStreamOutput:
|
case MGPWireOp::BeginStreamOutput:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_BeginStreamOutput, "BeginStreamOutput");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_BeginStreamOutput, "BeginStreamOutput");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::EndStreamOutput:
|
case MGPWireOp::EndStreamOutput:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_EndStreamOutput, "EndStreamOutput");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_EndStreamOutput, "EndStreamOutput");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::PauseStreamOutput:
|
case MGPWireOp::PauseStreamOutput:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_PauseStreamOutput, "PauseStreamOutput");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_PauseStreamOutput, "PauseStreamOutput");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::ResumeStreamOutput:
|
case MGPWireOp::ResumeStreamOutput:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_ResumeStreamOutput, "ResumeStreamOutput");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_ResumeStreamOutput, "ResumeStreamOutput");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::Flush:
|
case MGPWireOp::Flush:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_Flush, "Flush");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_Flush, "Flush");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::Present:
|
case MGPWireOp::Present:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_Present, "Present");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_Present, "Present");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::SetSwapInterval:
|
case MGPWireOp::SetSwapInterval:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetSwapInterval, "SetSwapInterval");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SetSwapInterval, "SetSwapInterval");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::QueryTimestamp:
|
case MGPWireOp::QueryTimestamp:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_QueryTimestamp, "QueryTimestamp");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_QueryTimestamp, "QueryTimestamp");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::QueryCounter:
|
case MGPWireOp::QueryCounter:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_QueryCounter, "QueryCounter");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_QueryCounter, "QueryCounter");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::FenceWaitServer:
|
case MGPWireOp::FenceWaitServer:
|
||||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_FenceWaitServer, "FenceWaitServer");
|
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_FenceWaitServer, "FenceWaitServer");
|
||||||
return false;
|
break;
|
||||||
case MGPWireOp::kInvalid:
|
case MGPWireOp::kInvalid:
|
||||||
case MGPWireOp::kOpCount:
|
case MGPWireOp::kOpCount:
|
||||||
default:
|
default:
|
||||||
MGPipeWireProtocolFatal("<unknown opcode>", size, remaining);
|
MGPipeWireProtocolFatal("<unknown opcode>", size, remaining);
|
||||||
}
|
}
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
if (gMGPipeWireRecordApply != nullptr) {
|
||||||
|
return gMGPipeWireRecordApply(op, record, size, remaining);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef MGP_WIRE_CHECK_BOUNDS
|
#undef MGP_WIRE_CHECK_BOUNDS
|
||||||
|
|||||||
@@ -6,11 +6,39 @@
|
|||||||
// SPDX-License-Identifier: LGPL-3.0-only
|
// SPDX-License-Identifier: LGPL-3.0-only
|
||||||
// End of Source File Header
|
// End of Source File Header
|
||||||
|
|
||||||
|
// P5 package w1: MGPCaps's two blob serializers, the pair MGPipeTypes.h:134-136 defers to
|
||||||
|
// this phase by name ("Their serializers land with the transport (P5)").
|
||||||
|
//
|
||||||
|
// THE FORMAT, and every part of it is a refusal rather than a guess. It is
|
||||||
|
// ProgramArtifactsCodec's shape on purpose - that codec is the tree's one worked example of a
|
||||||
|
// wire format that has survived a real transport, so copying it is cheaper than being
|
||||||
|
// original and much cheaper than being wrong:
|
||||||
|
//
|
||||||
|
// * a VERSION word first, and the two TABLE DIMENSIONS second, so a peer whose
|
||||||
|
// TextureInternalFormat enum grew is a mismatch AT READ TIME rather than a silent shear
|
||||||
|
// that reads one format's capabilities as another's;
|
||||||
|
// * length-prefixed everything, with the count checked against the bytes that REMAIN before
|
||||||
|
// a single element is reserved, so a corrupt count cannot become a four-billion-element
|
||||||
|
// resize;
|
||||||
|
// * SPARSE for all three capability tables. The dense form is
|
||||||
|
// 2 * targets * formats * 8 bytes plus the sample-count lists - with 13 targets and 77
|
||||||
|
// internal formats that is ~16 KiB of mostly zeroes, PER CONTEXT AND PER CAPS
|
||||||
|
// INVALIDATION, because R-12 makes a re-arriving snapshot the invalidation rather than a
|
||||||
|
// once-per-process cost. The tables are overwhelmingly empty, so what crosses is
|
||||||
|
// (index, value) pairs and the decoder Clear()s first;
|
||||||
|
// * little-endian by memcpy of fixed-width scalars, which is what every other MobileGL wire
|
||||||
|
// struct already assumes and what the ABI fingerprint below makes checkable;
|
||||||
|
// * every decoder returns FALSE on truncation, a bad version, a dimension mismatch, an
|
||||||
|
// out-of-range index or TRAILING BYTES THE FORMAT DOES NOT ACCOUNT FOR. These bytes
|
||||||
|
// arrive over a wire and the outputs are left in a defined, default state on a refusal.
|
||||||
|
|
||||||
#include "CapsCodec.h"
|
#include "CapsCodec.h"
|
||||||
|
|
||||||
|
#include <MGGitHash.h>
|
||||||
#include <MG_Util/Debug/Log.h>
|
#include <MG_Util/Debug/Log.h>
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
namespace MobileGL::MG_Remote {
|
namespace MobileGL::MG_Remote {
|
||||||
|
|
||||||
@@ -29,28 +57,437 @@ namespace MobileGL::MG_Remote {
|
|||||||
static_assert(MG_Pipe::kMGPipeSubsystemsMigratedAtP4a <= 0xFFFFull,
|
static_assert(MG_Pipe::kMGPipeSubsystemsMigratedAtP4a <= 0xFFFFull,
|
||||||
"the subsystem mask no longer fits CallMask's sixteen consumer bits");
|
"the subsystem mask no longer fits CallMask's sixteen consumer bits");
|
||||||
|
|
||||||
#define MGP5_C0_STUB(what) \
|
namespace {
|
||||||
do { \
|
|
||||||
MGLOG_F("MGPipe: Fatal{UnimplementedCapsCodec, \"%s\"} - P5 package w1 has not landed " \
|
|
||||||
"this yet; c0 shipped the signature only", \
|
|
||||||
what); \
|
|
||||||
std::abort(); \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
Bool EncodeFormatCapabilities(const MG_Backend::FormatCapabilityCache&, Vector<Uint8>&) {
|
// Bumped whenever the bytes change in a way a previous reader would misread. A reader
|
||||||
MGP5_C0_STUB("EncodeFormatCapabilities");
|
// that sees a different word REFUSES; it never tries to guess a layout.
|
||||||
|
constexpr Uint32 kFormatCapabilitiesCodecVersion = 1;
|
||||||
|
constexpr Uint32 kRendererInfoCodecVersion = 1;
|
||||||
|
|
||||||
|
// A count is never believed before it is weighed against the bytes that are left. The
|
||||||
|
// largest legal element count in either blob is bounded by the tables' own dimensions,
|
||||||
|
// but a String's length is not, so the reader checks bytes rather than a constant.
|
||||||
|
class Writer {
|
||||||
|
public:
|
||||||
|
explicit Writer(Vector<Uint8>& out) : m_out(out) {}
|
||||||
|
|
||||||
|
void Raw(const void* bytes, SizeT size) {
|
||||||
|
if (size == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto* p = static_cast<const Uint8*>(bytes);
|
||||||
|
m_out.insert(m_out.end(), p, p + size);
|
||||||
|
}
|
||||||
|
void U8(Uint8 v) { Raw(&v, sizeof(v)); }
|
||||||
|
void U32(Uint32 v) { Raw(&v, sizeof(v)); }
|
||||||
|
void U64(Uint64 v) { Raw(&v, sizeof(v)); }
|
||||||
|
void I32(Int32 v) { Raw(&v, sizeof(v)); }
|
||||||
|
void Str(const String& s) {
|
||||||
|
U32(static_cast<Uint32>(s.size()));
|
||||||
|
Raw(s.data(), s.size());
|
||||||
|
}
|
||||||
|
void OptStr(const Optional<String>& s) {
|
||||||
|
U8(s.has_value() ? 1u : 0u);
|
||||||
|
if (s.has_value()) {
|
||||||
|
Str(*s);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool DecodeFormatCapabilities(const void*, Uint64, MG_Backend::FormatCapabilityCache&) {
|
private:
|
||||||
MGP5_C0_STUB("DecodeFormatCapabilities");
|
Vector<Uint8>& m_out;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Reader {
|
||||||
|
public:
|
||||||
|
Reader(const void* bytes, Uint64 size)
|
||||||
|
: m_p(static_cast<const Uint8*>(bytes)), m_left(bytes != nullptr ? size : 0) {}
|
||||||
|
|
||||||
|
Bool Raw(void* out, Uint64 size) {
|
||||||
|
if (!m_ok || size > m_left) {
|
||||||
|
m_ok = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::memcpy(out, m_p, static_cast<SizeT>(size));
|
||||||
|
m_p += size;
|
||||||
|
m_left -= size;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Bool U8(Uint8& v) { return Raw(&v, sizeof(v)); }
|
||||||
|
Bool U32(Uint32& v) { return Raw(&v, sizeof(v)); }
|
||||||
|
Bool U64(Uint64& v) { return Raw(&v, sizeof(v)); }
|
||||||
|
Bool I32(Int32& v) { return Raw(&v, sizeof(v)); }
|
||||||
|
Bool Str(String& s) {
|
||||||
|
Uint32 length = 0;
|
||||||
|
if (!U32(length)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// THE CHECK THAT MATTERS: the count is weighed against the bytes that remain
|
||||||
|
// BEFORE the string is sized, so a corrupt length is a refusal rather than a
|
||||||
|
// four-gigabyte allocation.
|
||||||
|
if (length > m_left) {
|
||||||
|
m_ok = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
s.assign(reinterpret_cast<const char*>(m_p), static_cast<SizeT>(length));
|
||||||
|
m_p += length;
|
||||||
|
m_left -= length;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Bool OptStr(Optional<String>& s) {
|
||||||
|
Uint8 present = 0;
|
||||||
|
if (!U8(present)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (present == 0) {
|
||||||
|
s.reset();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
String value;
|
||||||
|
if (!Str(value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
s = Move(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// How many elements of `elementBytes` could still possibly be there. The gate a
|
||||||
|
// length-prefixed array is held to before it reserves anything.
|
||||||
|
Uint64 RoomFor(Uint64 elementBytes) const {
|
||||||
|
return elementBytes == 0 ? 0 : m_left / elementBytes;
|
||||||
|
}
|
||||||
|
Bool Ok() const { return m_ok; }
|
||||||
|
Uint64 Left() const { return m_left; }
|
||||||
|
// Trailing bytes the format does not account for are a REFUSAL: they mean the
|
||||||
|
// writer and the reader disagree about the shape, and the half that was read is
|
||||||
|
// not trustworthy just because it parsed.
|
||||||
|
Bool Finished() const { return m_ok && m_left == 0; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const Uint8* m_p = nullptr;
|
||||||
|
Uint64 m_left = 0;
|
||||||
|
Bool m_ok = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
using MG_Backend::FormatCapabilityCache;
|
||||||
|
using MG_Backend::FormatCapabilityFlags;
|
||||||
|
|
||||||
|
constexpr Uint64 kFormatCells = static_cast<Uint64>(MG_Backend::kFormatCapabilityTargetCount) *
|
||||||
|
static_cast<Uint64>(MG_Backend::kFormatCapabilityFormatCount);
|
||||||
|
|
||||||
|
// FNV-1a, the same mixer the tree already uses for build-stamp style fingerprints.
|
||||||
|
constexpr Uint64 kFnvOffset = 1469598103934665603ull;
|
||||||
|
constexpr Uint64 kFnvPrime = 1099511628211ull;
|
||||||
|
|
||||||
|
Uint64 FnvBytes(Uint64 hash, const void* bytes, SizeT size) {
|
||||||
|
const auto* p = static_cast<const Uint8*>(bytes);
|
||||||
|
for (SizeT i = 0; i < size; ++i) {
|
||||||
|
hash ^= static_cast<Uint64>(p[i]);
|
||||||
|
hash *= kFnvPrime;
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool EncodeRendererInfo(const RendererInfo&, Vector<Uint8>&) { MGP5_C0_STUB("EncodeRendererInfo"); }
|
Uint64 FnvU64(Uint64 hash, Uint64 value) { return FnvBytes(hash, &value, sizeof(value)); }
|
||||||
|
|
||||||
Bool DecodeRendererInfo(const void*, Uint64, RendererInfo&) { MGP5_C0_STUB("DecodeRendererInfo"); }
|
} // namespace
|
||||||
|
|
||||||
Uint64 CapsAbiFingerprint() { MGP5_C0_STUB("CapsAbiFingerprint"); }
|
// ---------------------------------------------------------------------------------
|
||||||
|
// FormatCapabilityCache
|
||||||
|
// ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
#undef MGP5_C0_STUB
|
Bool EncodeFormatCapabilities(const FormatCapabilityCache& cache, Vector<Uint8>& out) {
|
||||||
|
Writer w(out);
|
||||||
|
w.U32(kFormatCapabilitiesCodecVersion);
|
||||||
|
w.U32(static_cast<Uint32>(MG_Backend::kFormatCapabilityTargetCount));
|
||||||
|
w.U32(static_cast<Uint32>(MG_Backend::kFormatCapabilityFormatCount));
|
||||||
|
w.U32(0); // reserved, keeps the header 16 bytes and 8-aligned for the pairs below
|
||||||
|
|
||||||
|
// The two flag tables, sparse. A cell is written only when it is non-zero, so what
|
||||||
|
// crosses is proportional to what the driver actually supports rather than to the
|
||||||
|
// square of two enum spaces.
|
||||||
|
const auto writeTable = [&](const MG_Backend::FormatCapabilityTable& table) {
|
||||||
|
Uint32 populated = 0;
|
||||||
|
for (SizeT t = 0; t < MG_Backend::kFormatCapabilityTargetCount; ++t) {
|
||||||
|
for (SizeT f = 0; f < MG_Backend::kFormatCapabilityFormatCount; ++f) {
|
||||||
|
if (table[t][f].GetRaw() != 0) {
|
||||||
|
++populated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.U32(populated);
|
||||||
|
for (SizeT t = 0; t < MG_Backend::kFormatCapabilityTargetCount; ++t) {
|
||||||
|
for (SizeT f = 0; f < MG_Backend::kFormatCapabilityFormatCount; ++f) {
|
||||||
|
const Uint64 raw = static_cast<Uint64>(table[t][f].GetRaw());
|
||||||
|
if (raw == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
w.U32(static_cast<Uint32>(t * MG_Backend::kFormatCapabilityFormatCount + f));
|
||||||
|
w.U64(raw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
writeTable(cache.FullCaps);
|
||||||
|
writeTable(cache.CaveatCaps);
|
||||||
|
|
||||||
|
// The sample-count lists: the Vector<Int> that is the reason this cannot be a memcpy.
|
||||||
|
Uint32 populated = 0;
|
||||||
|
for (SizeT t = 0; t < MG_Backend::kFormatCapabilityTargetCount; ++t) {
|
||||||
|
for (SizeT f = 0; f < MG_Backend::kFormatCapabilityFormatCount; ++f) {
|
||||||
|
if (!cache.SampleCounts[t][f].empty()) {
|
||||||
|
++populated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.U32(populated);
|
||||||
|
for (SizeT t = 0; t < MG_Backend::kFormatCapabilityTargetCount; ++t) {
|
||||||
|
for (SizeT f = 0; f < MG_Backend::kFormatCapabilityFormatCount; ++f) {
|
||||||
|
const Vector<Int>& counts = cache.SampleCounts[t][f];
|
||||||
|
if (counts.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
w.U32(static_cast<Uint32>(t * MG_Backend::kFormatCapabilityFormatCount + f));
|
||||||
|
w.U32(static_cast<Uint32>(counts.size()));
|
||||||
|
for (const Int value : counts) {
|
||||||
|
w.I32(static_cast<Int32>(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool DecodeFormatCapabilities(const void* bytes, Uint64 size, FormatCapabilityCache& out) {
|
||||||
|
out.Clear();
|
||||||
|
Reader r(bytes, size);
|
||||||
|
|
||||||
|
Uint32 version = 0;
|
||||||
|
Uint32 targets = 0;
|
||||||
|
Uint32 formats = 0;
|
||||||
|
Uint32 reserved = 0;
|
||||||
|
if (!r.U32(version) || !r.U32(targets) || !r.U32(formats) || !r.U32(reserved)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (version != kFormatCapabilitiesCodecVersion) {
|
||||||
|
MGLOG_E("MG_Remote caps: format-capability blob is version %u, this build reads %u",
|
||||||
|
version, kFormatCapabilitiesCodecVersion);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (targets != MG_Backend::kFormatCapabilityTargetCount ||
|
||||||
|
formats != MG_Backend::kFormatCapabilityFormatCount) {
|
||||||
|
// Not a corrupt stream: a peer whose TextureTarget or TextureInternalFormat enum
|
||||||
|
// is a different size. Reading it anyway shears every cell onto a neighbouring
|
||||||
|
// format, which is exactly the failure the ABI fingerprint exists to make loud.
|
||||||
|
MGLOG_E("MG_Remote caps: format-capability blob is %ux%u, this build is %llux%llu",
|
||||||
|
targets, formats,
|
||||||
|
static_cast<unsigned long long>(MG_Backend::kFormatCapabilityTargetCount),
|
||||||
|
static_cast<unsigned long long>(MG_Backend::kFormatCapabilityFormatCount));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto readTable = [&](MG_Backend::FormatCapabilityTable& table) -> Bool {
|
||||||
|
Uint32 populated = 0;
|
||||||
|
if (!r.U32(populated)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (populated > r.RoomFor(sizeof(Uint32) + sizeof(Uint64))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (Uint32 i = 0; i < populated; ++i) {
|
||||||
|
Uint32 index = 0;
|
||||||
|
Uint64 raw = 0;
|
||||||
|
if (!r.U32(index) || !r.U64(raw)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (static_cast<Uint64>(index) >= kFormatCells) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
table[index / MG_Backend::kFormatCapabilityFormatCount]
|
||||||
|
[index % MG_Backend::kFormatCapabilityFormatCount] =
|
||||||
|
FormatCapabilityFlags(raw);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
if (!readTable(out.FullCaps) || !readTable(out.CaveatCaps)) {
|
||||||
|
out.Clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 populated = 0;
|
||||||
|
if (!r.U32(populated) || populated > r.RoomFor(2 * sizeof(Uint32))) {
|
||||||
|
out.Clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (Uint32 i = 0; i < populated; ++i) {
|
||||||
|
Uint32 index = 0;
|
||||||
|
Uint32 count = 0;
|
||||||
|
if (!r.U32(index) || !r.U32(count)) {
|
||||||
|
out.Clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (static_cast<Uint64>(index) >= kFormatCells || count > r.RoomFor(sizeof(Int32))) {
|
||||||
|
out.Clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Vector<Int>& counts = out.SampleCounts[index / MG_Backend::kFormatCapabilityFormatCount]
|
||||||
|
[index % MG_Backend::kFormatCapabilityFormatCount];
|
||||||
|
counts.resize(static_cast<SizeT>(count));
|
||||||
|
for (Uint32 j = 0; j < count; ++j) {
|
||||||
|
Int32 value = 0;
|
||||||
|
if (!r.I32(value)) {
|
||||||
|
out.Clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
counts[j] = static_cast<Int>(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!r.Finished()) {
|
||||||
|
MGLOG_E("MG_Remote caps: format-capability blob has %llu trailing bytes the format "
|
||||||
|
"does not account for",
|
||||||
|
static_cast<unsigned long long>(r.Left()));
|
||||||
|
out.Clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------------
|
||||||
|
// RendererInfo
|
||||||
|
// ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void WriteVersion(Writer& w, const Version& v) {
|
||||||
|
w.I32(static_cast<Int32>(v.Major));
|
||||||
|
w.I32(static_cast<Int32>(v.Minor));
|
||||||
|
w.I32(static_cast<Int32>(v.Patch));
|
||||||
|
w.OptStr(v.Suffix);
|
||||||
|
w.U8(v.Type.has_value() ? 1u : 0u);
|
||||||
|
w.U8(v.Type.has_value() ? static_cast<Uint8>(*v.Type) : 0u);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool ReadVersion(Reader& r, Version& v) {
|
||||||
|
Int32 major = 0;
|
||||||
|
Int32 minor = 0;
|
||||||
|
Int32 patch = 0;
|
||||||
|
if (!r.I32(major) || !r.I32(minor) || !r.I32(patch)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
v.Major = static_cast<Int>(major);
|
||||||
|
v.Minor = static_cast<Int>(minor);
|
||||||
|
v.Patch = static_cast<Int>(patch);
|
||||||
|
if (!r.OptStr(v.Suffix)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Uint8 hasType = 0;
|
||||||
|
Uint8 type = 0;
|
||||||
|
if (!r.U8(hasType) || !r.U8(type)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (hasType != 0) {
|
||||||
|
v.Type = static_cast<VersionType>(type);
|
||||||
|
} else {
|
||||||
|
v.Type.reset();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Bool EncodeRendererInfo(const RendererInfo& info, Vector<Uint8>& out) {
|
||||||
|
Writer w(out);
|
||||||
|
w.U32(kRendererInfoCodecVersion);
|
||||||
|
w.Str(info.RendererName);
|
||||||
|
w.Str(info.BackendName);
|
||||||
|
w.OptStr(info.ExtraVendor);
|
||||||
|
WriteVersion(w, info.RendererGLInfo.TargetGLVersion);
|
||||||
|
WriteVersion(w, info.RendererGLInfo.TargetGLSLVersion);
|
||||||
|
w.U32(static_cast<Uint32>(info.RendererGLInfo.Extensions.size()));
|
||||||
|
for (const GLExtension extension : info.RendererGLInfo.Extensions) {
|
||||||
|
w.U32(static_cast<Uint32>(extension));
|
||||||
|
}
|
||||||
|
w.U8(info.RendererGLInfo.IsCompatibilityProfile ? 1u : 0u);
|
||||||
|
w.U8(info.StaticBackendCapability.AllowVSOnlyPrograms ? 1u : 0u);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool DecodeRendererInfo(const void* bytes, Uint64 size, RendererInfo& out) {
|
||||||
|
out = RendererInfo{};
|
||||||
|
Reader r(bytes, size);
|
||||||
|
|
||||||
|
Uint32 version = 0;
|
||||||
|
if (!r.U32(version)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (version != kRendererInfoCodecVersion) {
|
||||||
|
MGLOG_E("MG_Remote caps: renderer-info blob is version %u, this build reads %u", version,
|
||||||
|
kRendererInfoCodecVersion);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!r.Str(out.RendererName) || !r.Str(out.BackendName) || !r.OptStr(out.ExtraVendor) ||
|
||||||
|
!ReadVersion(r, out.RendererGLInfo.TargetGLVersion) ||
|
||||||
|
!ReadVersion(r, out.RendererGLInfo.TargetGLSLVersion)) {
|
||||||
|
out = RendererInfo{};
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 extensionCount = 0;
|
||||||
|
if (!r.U32(extensionCount) || extensionCount > r.RoomFor(sizeof(Uint32))) {
|
||||||
|
out = RendererInfo{};
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.RendererGLInfo.Extensions.resize(static_cast<SizeT>(extensionCount));
|
||||||
|
for (Uint32 i = 0; i < extensionCount; ++i) {
|
||||||
|
Uint32 value = 0;
|
||||||
|
if (!r.U32(value)) {
|
||||||
|
out = RendererInfo{};
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.RendererGLInfo.Extensions[i] = static_cast<GLExtension>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8 compatibility = 0;
|
||||||
|
Uint8 vsOnly = 0;
|
||||||
|
if (!r.U8(compatibility) || !r.U8(vsOnly)) {
|
||||||
|
out = RendererInfo{};
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.RendererGLInfo.IsCompatibilityProfile = compatibility != 0;
|
||||||
|
out.StaticBackendCapability.AllowVSOnlyPrograms = vsOnly != 0;
|
||||||
|
|
||||||
|
if (!r.Finished()) {
|
||||||
|
MGLOG_E("MG_Remote caps: renderer-info blob has %llu trailing bytes the format does "
|
||||||
|
"not account for",
|
||||||
|
static_cast<unsigned long long>(r.Left()));
|
||||||
|
out = RendererInfo{};
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------------
|
||||||
|
// The ABI assertion the handshake carries
|
||||||
|
// ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Uint64 CapsAbiFingerprint() {
|
||||||
|
// MGPCaps has only a COMPOSITIONAL size assertion (MGPipeTypes.h:145-146) because
|
||||||
|
// DynamicBackendParameters still carries SizeT and GLenum members - P0.5's fixed-width
|
||||||
|
// rewrite did not happen and P5 does not do it either (table 0's ABI row; the rewrite
|
||||||
|
// is P7's account). So the caps block's literal size IS ABI-dependent, and this
|
||||||
|
// fingerprint is what turns that from a latent hazard into a named refusal.
|
||||||
|
//
|
||||||
|
// The git stamp is in it because two builds of the same sizes can still disagree about
|
||||||
|
// a FIELD ORDER, which no sizeof can see; P6's spawn is same-machine and same-binary,
|
||||||
|
// so it inherits this unchanged rather than needing a looser rule.
|
||||||
|
Uint64 hash = kFnvOffset;
|
||||||
|
hash = FnvU64(hash, sizeof(MG_Backend::DynamicBackendParameters));
|
||||||
|
hash = FnvU64(hash, sizeof(MG_Pipe::MGPCaps));
|
||||||
|
hash = FnvU64(hash, sizeof(MG_Backend::GLFunctionsTable));
|
||||||
|
hash = FnvU64(hash, static_cast<Uint64>(MG_Backend::kFormatCapabilityTargetCount));
|
||||||
|
hash = FnvU64(hash, static_cast<Uint64>(MG_Backend::kFormatCapabilityFormatCount));
|
||||||
|
hash = FnvU64(hash, kFormatCapabilitiesCodecVersion);
|
||||||
|
hash = FnvU64(hash, kRendererInfoCodecVersion);
|
||||||
|
hash = FnvU64(hash, static_cast<Uint64>(MG_Pipe::MGPWireOp::kOpCount));
|
||||||
|
hash = FnvBytes(hash, GIT_COMMIT_HASH_SHORT, std::strlen(GIT_COMMIT_HASH_SHORT));
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace MobileGL::MG_Remote
|
} // namespace MobileGL::MG_Remote
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -111,12 +111,83 @@ namespace MobileGL::MG_Remote::Wire {
|
|||||||
|
|
||||||
// R-2.3 arms 1-4 over one record's blobref. Split only; a monolith emission is exempt by
|
// R-2.3 arms 1-4 over one record's blobref. Split only; a monolith emission is exempt by
|
||||||
// construction because it never reaches this layer.
|
// construction because it never reaches this layer.
|
||||||
|
//
|
||||||
|
// ARMS 3 AND 4 ONLY, PLUS ONE THIS FUNCTION HAD TO INVENT. A blobref is honest when it is
|
||||||
|
// EITHER fully declared - a real Seg, a non-zero Size and an Offset+Size inside that
|
||||||
|
// segment - OR fully absent, which is all three fields zero. The third shape, a Seg or an
|
||||||
|
// Offset with Size == 0, is neither, and it is the shape a monolith emitter produces
|
||||||
|
// today (Seg = None, Offset = a host address, Size = 0), so under split it has to be
|
||||||
|
// Fatal rather than "absent": a decoder that read it as absent would silently drop the
|
||||||
|
// bytes of every record an unconverted emitter sent.
|
||||||
|
//
|
||||||
|
// ARM 2 - "Blob.Size == 0 on a CONTENT record" - is NOT here and cannot be: whether a
|
||||||
|
// record carries content is a property of the record's OTHER fields (ChunkMask, the
|
||||||
|
// destination range, GlobalUboSize, the stage mask), which this signature does not see.
|
||||||
|
// RequireDeclaredBlob below is arm 2, and the decoder's per-op arm calls it exactly where
|
||||||
|
// the payload says content is implied.
|
||||||
void CheckBlobIsHonest(MG_Pipe::MGPWireOp op, const MG_Pipe::MGPBlobRef& blob,
|
void CheckBlobIsHonest(MG_Pipe::MGPWireOp op, const MG_Pipe::MGPBlobRef& blob,
|
||||||
const SegmentTable& segments);
|
const SegmentTable& segments);
|
||||||
// R-2.3 arm for MGHostSpan. P5's reduced path should produce ZERO host spans
|
// R-2.3 arm 2: the record's other fields say it carries content, so the blob must be
|
||||||
|
// declared. Fatal on an absent blob, then CheckBlobIsHonest on a present one.
|
||||||
|
void RequireDeclaredBlob(MG_Pipe::MGPWireOp op, const MG_Pipe::MGPBlobRef& blob,
|
||||||
|
const SegmentTable& segments);
|
||||||
|
// R-2.3 arms 1 and 3 for MGHostSpan. P5's reduced path should produce ZERO host spans
|
||||||
// (kCapNeedsHostIndexBytes / kCapNeedsHostUboBytes are both 0 in P5, table 0), so this
|
// (kCapNeedsHostIndexBytes / kCapNeedsHostUboBytes are both 0 in P5, table 0), so this
|
||||||
// firing at all is a finding, not just a corruption check.
|
// firing at all is a finding, not just a corruption check.
|
||||||
|
//
|
||||||
|
// IT CANNOT DO ARM 4 - it has no segment table - so a span that names a real segment and a
|
||||||
|
// run PAST THE END OF IT passes this function. Use the overload below on any path that has
|
||||||
|
// a table; this one exists because c0 shipped the signature and other packages compile
|
||||||
|
// against it.
|
||||||
void CheckHostSpanIsHonest(const MG_Pipe::MGHostSpan& span);
|
void CheckHostSpanIsHonest(const MG_Pipe::MGHostSpan& span);
|
||||||
|
// All four arms. Arm 4 is the one the signature above cannot express: a span is only
|
||||||
|
// honest if its Offset+Size actually lies inside the segment it names, and the promise
|
||||||
|
// WireVerbSink's header makes - that OnDrawVbo is handed a VALIDATED argument list - is
|
||||||
|
// false without it. Latent in P5 (nothing emits a span) and armed at P8, which is exactly
|
||||||
|
// when nobody will be reading this file.
|
||||||
|
void CheckHostSpanIsHonest(const MG_Pipe::MGHostSpan& span, const SegmentTable& segments);
|
||||||
|
|
||||||
|
// ---- one record's shape, computed ONCE and read by both sides ----------------------
|
||||||
|
//
|
||||||
|
// THE TAIL CROSS-CHECK LIVES HERE AND NOWHERE ELSE (BRIEF §5 w1, contract table 1 group
|
||||||
|
// B). MGP_WIRE_CHECK_BOUNDS only proves `size >= sizeof(MGPWireRec_X)` - IT CANNOT SEE
|
||||||
|
// THE TAIL - so a record declaring Count = 4000 while carrying 8 bytes passes it. The
|
||||||
|
// encoder computes this layout from the payload it is about to write and REFUSES a caller
|
||||||
|
// whose tails disagree; the decoder computes the same layout from the payload it just
|
||||||
|
// received and REFUSES a record whose MGPWireRecHeader::Size disagrees. Two readers, one
|
||||||
|
// arithmetic, so the two sides cannot drift.
|
||||||
|
//
|
||||||
|
// EVERY TAIL STARTS 8-BYTE ALIGNED WITHIN THE RECORD, and the encoder zero-fills the gap.
|
||||||
|
// Eight of the nine kVarTail rows are already aligned by construction (their payload and
|
||||||
|
// element sizes are multiples of 8); DrawVbo is not - MGPDrawRange is TWELVE bytes, so an
|
||||||
|
// odd NumDraws leaves the conditional MGHostSpan on a 4-byte boundary, and MGHostSpan
|
||||||
|
// holds a pointer and two Uint64s. P5 emits no host span at all, so this rule costs
|
||||||
|
// nothing now and is stated now because the phase that arms kDrawHasUserIndices would
|
||||||
|
// otherwise have to discover it as a misaligned load on a device.
|
||||||
|
struct WireRecordLayout {
|
||||||
|
Uint64 PayloadBytes = 0; // sizeof the op's payload struct
|
||||||
|
Uint64 TailOffset[2] = {0, 0}; // from the START of the record, header included
|
||||||
|
Uint64 TailBytes[2] = {0, 0};
|
||||||
|
Uint32 TailCount = 0;
|
||||||
|
Uint64 TotalBytes = 0; // header + payload + gaps + tails, rounded up to 8
|
||||||
|
};
|
||||||
|
|
||||||
|
// `payload` must already be known to hold at least the op's payload struct - that is what
|
||||||
|
// MGP_WIRE_CHECK_BOUNDS proves, and this function is only ever called after it. Returns
|
||||||
|
// false for an opcode outside the catalogue; a count past its own GL bound is Fatal,
|
||||||
|
// because a decoder holding such a record has nothing safe left to do with it.
|
||||||
|
Bool MGPipeWireRecordLayout(MG_Pipe::MGPWireOp op, const void* payload, WireRecordLayout& out);
|
||||||
|
|
||||||
|
// The catalogue's own spelling of an opcode, for a Fatal line. Out of range is "<opcode>".
|
||||||
|
const char* WireOpName(MG_Pipe::MGPWireOp op);
|
||||||
|
|
||||||
|
// One tail array. Two of the 71 rows carry two (SetShaderBuffers, SetStreamOutputTargets)
|
||||||
|
// and DrawVbo carries a conditional second one, which is why EncodeRecord's one-tail form
|
||||||
|
// could not stay the only one.
|
||||||
|
struct WireTail {
|
||||||
|
const void* Bytes = nullptr;
|
||||||
|
Uint64 Size = 0;
|
||||||
|
};
|
||||||
|
|
||||||
// ---- encoder -----------------------------------------------------------------------
|
// ---- encoder -----------------------------------------------------------------------
|
||||||
//
|
//
|
||||||
@@ -150,6 +221,50 @@ namespace MobileGL::MG_Remote::Wire {
|
|||||||
Uint64 EncodeRecord(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes,
|
Uint64 EncodeRecord(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes,
|
||||||
const void* varTail = nullptr, Uint64 varTailBytes = 0);
|
const void* varTail = nullptr, Uint64 varTailBytes = 0);
|
||||||
|
|
||||||
|
// The same call for the three rows that carry TWO tails. The one-tail form above is
|
||||||
|
// this one with tailCount <= 1; nothing is duplicated between them.
|
||||||
|
//
|
||||||
|
// The tails a caller hands over are CROSS-CHECKED against the layout the payload
|
||||||
|
// itself declares (MGPipeWireRecordLayout): a caller whose Count says 4000 while its
|
||||||
|
// tail holds 8 bytes is Fatal HERE, on the producing side, rather than on a peer that
|
||||||
|
// can only report a corrupt stream. That is the same arithmetic the decoder runs, so
|
||||||
|
// the check is real rather than a restatement of the caller's own belief.
|
||||||
|
Uint64 EncodeRecord(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes,
|
||||||
|
const WireTail* tails, Uint32 tailCount);
|
||||||
|
|
||||||
|
// Releases every SEG_STAGE run named by a record the apply side has RETIRED
|
||||||
|
// (RingControl::retiredSeq, R-9, which this class only ever READS). Called from
|
||||||
|
// Publish() and whenever StageBytes runs short, so nothing outside this package has to
|
||||||
|
// remember to; the allocator reclaims behind retiredSeq and nothing else may
|
||||||
|
// (table 1's "retires" column, R-11).
|
||||||
|
//
|
||||||
|
// THE MARK IS HELD ON THIS SIDE, NOT ON THE WIRE. A record does not carry where its
|
||||||
|
// staged bytes end, so the encoder remembers {seq, stage cursor} per record and
|
||||||
|
// reclaims to the newest mark whose seq the server has retired. That is exact, needs
|
||||||
|
// no wire field, and does not depend on the verb barrier - so it keeps working when
|
||||||
|
// R-1's barrier retires family by family.
|
||||||
|
//
|
||||||
|
// SEG_STAGE'S CURSOR TRIPLE IN RingControl IS NOT USED BY EITHER SIDE IN P5, and this
|
||||||
|
// is the reason. Ring.h makes stageAppliedTail / stageRetiredTail CONSUMER-owned, the
|
||||||
|
// server never Pops the stage ring (the decoder resolves by offset), and a producer
|
||||||
|
// that wrote those cursors would be the very shape R-9 forbids one segment over. So
|
||||||
|
// the allocator below is entirely encoder-local and the triple is left at its
|
||||||
|
// InitRingControl values. **s1 must not attach a RingConsumer to
|
||||||
|
// RingCursorSet::Stage**: its m_localTail would never move, and PublishApplied /
|
||||||
|
// PublishRetired would then walk both cursors BACKWARDS under this allocator.
|
||||||
|
void ReclaimStagedBytes();
|
||||||
|
// Staged bytes not yet reclaimed. The number MOBILEGL_IPC_STAGE_MB has to cover.
|
||||||
|
Uint64 StagedBytesInFlight() const;
|
||||||
|
// The {seq, cursor} marks the queue is STILL STORING, consumed ones included - not the
|
||||||
|
// number in flight. Bounded by twice the records in flight, and exposed so a case can
|
||||||
|
// assert that bound rather than trust the comment, because an unbounded queue here is
|
||||||
|
// a steady-state leak no SSIM comparison and no two-scenario lane would ever see.
|
||||||
|
//
|
||||||
|
// It reports the storage deliberately: the in-flight count stays at one or two while
|
||||||
|
// the container behind it grows for ever, so a control written against that number
|
||||||
|
// goes green through exactly the leak it exists to catch.
|
||||||
|
SizeT StageMarksHeld() const;
|
||||||
|
|
||||||
// Release-stores the head cursor, then rings the consumer doorbell IF PARKED. The
|
// Release-stores the head cursor, then rings the consumer doorbell IF PARKED. The
|
||||||
// order is pinned by RingTest.cpp:446 and must not be swapped: notify-then-publish
|
// order is pinned by RingTest.cpp:446 and must not be swapped: notify-then-publish
|
||||||
// loses the wakeup.
|
// loses the wakeup.
|
||||||
@@ -163,12 +278,30 @@ namespace MobileGL::MG_Remote::Wire {
|
|||||||
Uint64 MaxRecordBytesSeen() const;
|
Uint64 MaxRecordBytesSeen() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// {the record's seq, the SEG_STAGE cursor just past everything that record named}.
|
||||||
|
struct StageMark {
|
||||||
|
Uint64 Seq = 0;
|
||||||
|
Uint64 StageCursor = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The SEG_STAGE linear allocator (BRIEF §5 w1's own wording). Monotonic byte counters
|
||||||
|
// over the segment; the in-segment offset is `cursor % capacity` and a run that would
|
||||||
|
// straddle the end skips to the boundary, exactly as a ring does, but WITHOUT touching
|
||||||
|
// RingControl - see ReclaimStagedBytes above.
|
||||||
|
Uint8* StageAllocate(Uint64 size);
|
||||||
|
|
||||||
Transport::RingControl* m_control = nullptr;
|
Transport::RingControl* m_control = nullptr;
|
||||||
Transport::RingProducer* m_cmd = nullptr;
|
Transport::RingProducer* m_cmd = nullptr;
|
||||||
Transport::RingProducer* m_stage = nullptr;
|
Transport::RingProducer* m_stage = nullptr;
|
||||||
SegmentTable* m_segments = nullptr;
|
SegmentTable* m_segments = nullptr;
|
||||||
Uint64 m_emitSeq = kInvalidSeq;
|
Uint64 m_emitSeq = kInvalidSeq;
|
||||||
Uint64 m_maxRecordBytes = 0;
|
Uint64 m_maxRecordBytes = 0;
|
||||||
|
Vector<StageMark> m_stageMarks;
|
||||||
|
SizeT m_stageMarkFront = 0;
|
||||||
|
Uint8* m_stageBase = nullptr;
|
||||||
|
Uint64 m_stageCapacity = 0;
|
||||||
|
Uint64 m_stageHead = 0; // monotonic bytes allocated
|
||||||
|
Uint64 m_stageTail = 0; // monotonic bytes reclaimed
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- decoder -----------------------------------------------------------------------
|
// ---- decoder -----------------------------------------------------------------------
|
||||||
@@ -189,6 +322,61 @@ namespace MobileGL::MG_Remote::Wire {
|
|||||||
virtual void PostReply(Uint64 seq, Int32 status, const void* bytes, Uint64 size) = 0;
|
virtual void PostReply(Uint64 seq, Int32 status, const void* bytes, Uint64 size) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ---- the verb sink: the five rows with no MGPipeApply* to delegate to ---------------
|
||||||
|
//
|
||||||
|
// The decoder owns NO semantics, so every arm ends in an existing MGPipeApply* free
|
||||||
|
// function - except five, and they are exactly contract §7's class B: Clear (57), Blit
|
||||||
|
// (56), ReadPixels (58), DrawVbo (59) and Present (67). Those are GLFunctionsTable VERBS.
|
||||||
|
// MG_Pipe has no applier for any of them (the 37 MGPipeApply* entry points are the object
|
||||||
|
// and state families), so for these five P5 writes the first consumer as well as the first
|
||||||
|
// producer - and the consumer is the SERVER'S backend call, which is v1's, not the codec's.
|
||||||
|
//
|
||||||
|
// So the codec does what it can prove and stops there: it bounds-checks, cross-checks the
|
||||||
|
// tail, resolves the segments and hands over a DECODED, VALIDATED argument list. With no
|
||||||
|
// sink installed those five arms return false ("this build does not implement it"), which
|
||||||
|
// is the same answer the other unimplemented rows give.
|
||||||
|
//
|
||||||
|
// SetShaderBuffers (38) and SetStreamOutputTargets (39) also have no applier entry point,
|
||||||
|
// and they deliberately get NO sink method: both are off P5's reduced path (BRIEF §4's
|
||||||
|
// exclusion list), so inventing a consumer for them would be building a semantics nobody
|
||||||
|
// can test this phase. Their arms validate both tails - which is the part a later phase
|
||||||
|
// must not have to re-derive - and return false.
|
||||||
|
class WireVerbSink {
|
||||||
|
public:
|
||||||
|
virtual ~WireVerbSink() = default;
|
||||||
|
virtual Bool OnClear(const MG_Pipe::MGPClear& clear) {
|
||||||
|
(void)clear;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
virtual Bool OnBlit(const MG_Pipe::MGPBlit& blit) {
|
||||||
|
(void)blit;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
virtual Bool OnPresent(const MG_Pipe::MGPPresent& present) {
|
||||||
|
(void)present;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// The pixels go back in the reply slot (contract table 1 row 23: the destination is
|
||||||
|
// ALWAYS SEG_REPLY in P5, which is why MGPReadbackInfo gains no Seg field), so the
|
||||||
|
// sink is handed the seq and the sink it must answer into.
|
||||||
|
virtual Bool OnReadPixels(const MG_Pipe::MGPReadbackInfo& info, Uint64 seq, ReplySink* replies) {
|
||||||
|
(void)info;
|
||||||
|
(void)seq;
|
||||||
|
(void)replies;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// `ranges` is info.NumDraws entries. `userIndices` is null unless the record set
|
||||||
|
// kDrawHasUserIndices - which P5 never does, because the reduced path draws from a
|
||||||
|
// VBO precisely so no MGHostSpan is produced (table 0's cap-bit row).
|
||||||
|
virtual Bool OnDrawVbo(const MG_Pipe::MGPDrawInfo& info, const MG_Pipe::MGPDrawRange* ranges,
|
||||||
|
const MG_Pipe::MGHostSpan* userIndices) {
|
||||||
|
(void)info;
|
||||||
|
(void)ranges;
|
||||||
|
(void)userIndices;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Not thread safe: one decoder on the apply thread, by construction.
|
// Not thread safe: one decoder on the apply thread, by construction.
|
||||||
class PipeWireDecoder {
|
class PipeWireDecoder {
|
||||||
public:
|
public:
|
||||||
@@ -215,16 +403,106 @@ namespace MobileGL::MG_Remote::Wire {
|
|||||||
// here Fatals, because a pad that reached the decoder has already been counted.
|
// here Fatals, because a pad that reached the decoder has already been counted.
|
||||||
Bool DecodeAndApply(const Transport::RingRecordView& record);
|
Bool DecodeAndApply(const Transport::RingRecordView& record);
|
||||||
|
|
||||||
// Advanced by exactly one per applied non-pad record. P5 FORBIDS BATCHING IT (R-9):
|
// THE DECODER'S OWN TALLY, NOT THE SHARED WATERMARK. Advanced by exactly one per
|
||||||
// the verb barrier's waiter reads it, and a batched watermark makes the client wait
|
// applied non-pad record.
|
||||||
// for records the server has not run.
|
//
|
||||||
|
// RingControl::appliedSeq has exactly ONE writer - s1's SessionConsumer::ApplyOne, +1
|
||||||
|
// per record, pads never counted - and this class writes NO RingControl field at all.
|
||||||
|
// That is deliberate rather than a division of labour: two writers of a watermark is
|
||||||
|
// how a waiter resumes on a record the server has not run, which is what R-9's "never
|
||||||
|
// publish a watermark early" forbids, and there is no checksum on this ring that would
|
||||||
|
// catch it.
|
||||||
|
//
|
||||||
|
// Keeping a private count beside the session's is what makes the batching ban
|
||||||
|
// CHECKABLE instead of merely stated: after every record the two numbers must agree,
|
||||||
|
// and a single counter could not tell a batched publish from an honest one.
|
||||||
Uint64 AppliedSeq() const;
|
Uint64 AppliedSeq() const;
|
||||||
|
|
||||||
|
// v1 installs the backend bridge for contract §7's five class-B verbs. Null - the
|
||||||
|
// default - makes those five arms return false rather than invent a semantics.
|
||||||
|
void SetVerbSink(WireVerbSink* sink);
|
||||||
|
WireVerbSink* VerbSink() const;
|
||||||
|
|
||||||
|
// R-2.5 / rule C's mechanical control: with MOBILEGL_IPC_AUDIT=1 every SEG_STAGE byte
|
||||||
|
// this decoder resolved for a record is overwritten with 0xDD once the applier has
|
||||||
|
// RETURNED, so an applier that kept the pointer reads 0xDD on the next frame instead
|
||||||
|
// of bytes that happen to still be there. Off by default; the run is exact - the
|
||||||
|
// decoder poisons what it resolved, not a conservative window.
|
||||||
|
void SetAuditPoison(Bool enabled);
|
||||||
|
Bool AuditPoison() const;
|
||||||
|
// How many staged bytes this decoder has poisoned. Zero with the audit off, and the
|
||||||
|
// number a t1 lane asserts is non-zero with it on: an instrumentation that cannot be
|
||||||
|
// observed to have run is decoration.
|
||||||
|
//
|
||||||
|
// WHAT IT ACTUALLY COVERS is "the blob runs the arm resolved", which today is AT MOST
|
||||||
|
// ONE per record: tails live in SEG_CMD and are never noted, and CreateShaderState's
|
||||||
|
// six per-stage runs are Fatal rather than resolved, so the one archive is the only
|
||||||
|
// multi-kilobyte run in the catalogue that reaches it. The array is eight deep so a
|
||||||
|
// later phase that declares more can fill it without a code change - and overflowing
|
||||||
|
// it is Fatal rather than a silent drop, because a poison that quietly stopped
|
||||||
|
// covering a run is the same failure as no poison at all.
|
||||||
|
Uint64 PoisonedStageBytes() const;
|
||||||
|
|
||||||
|
// R-5's acceptance answer for the four Bool-returning appliers - ResourceCreate,
|
||||||
|
// ResourceRespecify, ResourceSubData, SetTextureParams.
|
||||||
|
//
|
||||||
|
// IT DOES NOT RIDE A REPLY SLOT, and that is a contract conflict this package could
|
||||||
|
// not settle on its own. CONTRACT-P5.md table 0's reply-slot-header row says DECLINED
|
||||||
|
// "is how the four Bool acceptance entry points say false (R-5)", but PipeCalls.def
|
||||||
|
// gives none of those four `kReplySlot` - and table 0 ALSO says kMGPipeCallFlags is
|
||||||
|
// what "every package" reads, so s1 will size ReplyPool from it. Writing
|
||||||
|
// SEG_REPLY[seq % slots] for a record the pool never reserved a slot for overwrites a
|
||||||
|
// waiter's answer, and because the slot header stamps the writer's seq for self-check,
|
||||||
|
// the waiter's check then fails FOR EVER and the barrier hangs rather than returning
|
||||||
|
// something wrong. So the decoder posts a reply only for rows whose flags say
|
||||||
|
// kReplySlot (PostReply itself Fatals otherwise) and exposes the acceptance here
|
||||||
|
// instead. The integrator rules on which half of the contract moves.
|
||||||
|
Bool LastAcceptanceKnown() const;
|
||||||
|
Bool LastAcceptance() const;
|
||||||
|
Uint64 AcceptedRecords() const;
|
||||||
|
Uint64 DeclinedRecords() const;
|
||||||
|
|
||||||
|
// Points MG_Pipe::gMGPipeWireRecordApply at this layer's thunk. Called once from the
|
||||||
|
// constructor and modelled on SegmentTable::InstallProcessResolver, which is the same
|
||||||
|
// shape for the same reason; Uninstall belongs beside that one at teardown. The thunk
|
||||||
|
// is inert without a decoder on the calling thread, so installing it early changes
|
||||||
|
// nothing for a monolith caller of MGPipeApplyWireRecord.
|
||||||
|
static void InstallApplyHook();
|
||||||
|
static void UninstallApplyHook();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Bool ApplyChecked(MG_Pipe::MGPWireOp op, const void* record, Uint64 size);
|
||||||
|
const void* ResolveOrFatal(MG_Pipe::MGPWireOp op, const MG_Pipe::MGPBlobRef& blob);
|
||||||
|
void NoteResolvedRun(MG_Pipe::MGPWireOp op, const MG_Pipe::MGPBlobRef& blob);
|
||||||
|
void PoisonResolvedRuns();
|
||||||
|
// The ONLY way this class answers a record. Fatals if `op` carries no kReplySlot -
|
||||||
|
// see LastAcceptanceKnown() for why that is a Fatal and not a log line.
|
||||||
|
void PostReply(MG_Pipe::MGPWireOp op, Uint64 seq, Int32 status, const void* bytes,
|
||||||
|
Uint64 size);
|
||||||
|
|
||||||
|
friend Bool MGPipeWireRecordApplyThunk(MG_Pipe::MGPWireOp, const void*, Uint64, Uint64);
|
||||||
|
|
||||||
Transport::RingControl* m_control = nullptr;
|
Transport::RingControl* m_control = nullptr;
|
||||||
SegmentTable* m_segments = nullptr;
|
SegmentTable* m_segments = nullptr;
|
||||||
ReplySink* m_replies = nullptr;
|
ReplySink* m_replies = nullptr;
|
||||||
|
WireVerbSink* m_verbs = nullptr;
|
||||||
Uint64 m_applySeq = kInvalidSeq;
|
Uint64 m_applySeq = kInvalidSeq;
|
||||||
|
Bool m_auditPoison = false;
|
||||||
|
Uint64 m_poisonedBytes = 0;
|
||||||
|
Bool m_lastAcceptanceKnown = false;
|
||||||
|
Bool m_lastAcceptance = false;
|
||||||
|
Uint64 m_accepted = 0;
|
||||||
|
Uint64 m_declined = 0;
|
||||||
|
// The SEG_STAGE runs the record being applied resolved, for the 0xDD fill. Eight deep
|
||||||
|
// so CreateShaderState's seven blob members plus a tail would fit if a later phase
|
||||||
|
// declares them; today at most one run is ever noted (see PoisonedStageBytes).
|
||||||
|
MG_Pipe::MGPBlobRef m_resolved[8];
|
||||||
|
Uint32 m_resolvedCount = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The hook MGPipeApplyWireRecord dispatches to once its generated per-opcode bounds gate
|
||||||
|
// has passed. Inert unless a decoder is active on the calling thread.
|
||||||
|
Bool MGPipeWireRecordApplyThunk(MG_Pipe::MGPWireOp op, const void* record, Uint64 size,
|
||||||
|
Uint64 remaining);
|
||||||
|
|
||||||
} // namespace MobileGL::MG_Remote::Wire
|
} // namespace MobileGL::MG_Remote::Wire
|
||||||
|
|||||||
@@ -34,3 +34,31 @@ foreach (test IN LISTS MOBILEGL_WIRE_TESTS)
|
|||||||
|
|
||||||
gtest_discover_tests(${test} DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
gtest_discover_tests(${test} DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
||||||
endforeach ()
|
endforeach ()
|
||||||
|
|
||||||
|
# P5 w1's G3 codec suite. It is registered on its own rather than in the list above because it
|
||||||
|
# links gtest, NOT gtest_main: the R-2 Fatal arms report through MGLOG_F + std::abort, so the
|
||||||
|
# cases that drive one fork and read the Fatal line back out of a log file the process names
|
||||||
|
# before anything logs - which needs a main() of its own (PipeInputsTest's shape). It also
|
||||||
|
# reaches MG_Pipe and MG_State, so it carries their include paths.
|
||||||
|
add_executable(PipeWireCodecTest PipeWireCodecTest.cpp)
|
||||||
|
|
||||||
|
target_include_directories(PipeWireCodecTest PRIVATE
|
||||||
|
${MGL_ROOT}/include
|
||||||
|
${MGL_ROOT}/MobileGL
|
||||||
|
${MGL_ROOT}/MobileGL/MG_Pipe
|
||||||
|
${MGL_ROOT}/3rdparty/flatbuffers/include
|
||||||
|
${MGL_ROOT}/3rdparty/xxHash
|
||||||
|
${MGL_ROOT}/3rdparty/Vulkan-Headers/include
|
||||||
|
${MGL_ROOT}/3rdparty/SPIRV-Reflect
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(PipeWireCodecTest PRIVATE
|
||||||
|
GTest::gtest
|
||||||
|
${LINK_LIBRARIES}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (MSVC)
|
||||||
|
target_compile_options(PipeWireCodecTest PRIVATE /Zc:preprocessor)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
gtest_discover_tests(PipeWireCodecTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+40
-7
@@ -813,24 +813,57 @@ static_assert((MGPipeCallFlagsFor(MGPWireOp::DrawVbo) &
|
|||||||
} \\
|
} \\
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
// Returns whether the record was applied. P0 is a SKELETON: every case validates its
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
// bounds and then reports "not applied", because no applier exists until P5 wires
|
// THE DECODER HOOK (P5 w1). MG_Pipe is BELOW MG_Remote and may not include it, so the real
|
||||||
// MG_Remote/Server/PipeApplier.cpp to the real backend tables. The switch and the opcode
|
// 71-arm decoder - MG_Remote/Wire/PipeWireCodec.cpp, which resolves the segments, cross-checks
|
||||||
// enum come from the same list, so a call added to the catalogue cannot be forgotten here;
|
// the variable tails and calls today's MGPipeApply* free functions - installs itself here.
|
||||||
// the default arm is for the opcode that never came from this catalogue at all - a byte
|
// The indirection is the layering, not a policy: gMGPipeSegmentResolver
|
||||||
// off a corrupt stream - and it is fatal for the same reason the bounds check is.
|
// (MGPipeHostSpan.h:47) is the same shape for the same reason.
|
||||||
|
//
|
||||||
|
// It lives behind the build option because G1 admits no symbol movement in a PULL build, and
|
||||||
|
// an inline variable that MGPipeApplyWireRecord odr-uses would be one.
|
||||||
|
using MGPipeWireRecordApplyFn = Bool (*)(MGPWireOp op, const void* record, Uint64 size,
|
||||||
|
Uint64 remaining);
|
||||||
|
inline MGPipeWireRecordApplyFn gMGPipeWireRecordApply = nullptr;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Returns whether the record was applied.
|
||||||
|
//
|
||||||
|
// THE SWITCH IS THE PER-OPCODE BOUNDS GATE AND NOTHING ELSE, AND IT CANNOT SEE THE TAIL.
|
||||||
|
// MGP_WIRE_CHECK_BOUNDS proves `size >= sizeof(MGPWireRec_X)`, `size <= remaining` and
|
||||||
|
// 8-alignment - which is exactly the part a generator can state, because it is the part that
|
||||||
|
// follows from the opcode alone. A kVarTail record declaring Count = 4000 while carrying 8
|
||||||
|
// bytes passes every one of those, so the SECOND check - recompute the total from the
|
||||||
|
// record's own count fields and require it to EQUAL MGPWireRecHeader::Size - belongs to the
|
||||||
|
// decoder, which has the payload (MG_Remote/Wire's MGPipeWireRecordLayout, P5 w1).
|
||||||
|
//
|
||||||
|
// The switch and the opcode enum come from the same list, so a call added to the catalogue
|
||||||
|
// cannot be forgotten here; the default arm is for the opcode that never came from this
|
||||||
|
// catalogue at all - a byte off a corrupt stream - and it is fatal for the same reason the
|
||||||
|
// bounds check is.
|
||||||
|
//
|
||||||
|
// With no decoder installed - every monolith build, and a split build before
|
||||||
|
// ClientSession::Start - this returns false, "this build does not implement it". That is the
|
||||||
|
// P0 skeleton's answer, kept deliberately: a codec that has not been installed must not look
|
||||||
|
// like one that applied the record.
|
||||||
inline Bool MGPipeApplyWireRecord(MGPWireOp op, const void* record, Uint64 size, Uint64 remaining) {
|
inline Bool MGPipeApplyWireRecord(MGPWireOp op, const void* record, Uint64 size, Uint64 remaining) {
|
||||||
(void)record;
|
(void)record;
|
||||||
switch (op) {""")
|
switch (op) {""")
|
||||||
for call in calls:
|
for call in calls:
|
||||||
out.append(" case MGPWireOp::%s:" % call.Name)
|
out.append(" case MGPWireOp::%s:" % call.Name)
|
||||||
out.append(" MGP_WIRE_CHECK_BOUNDS(MGPWireRec_%s, \"%s\");" % (call.Name, call.Name))
|
out.append(" MGP_WIRE_CHECK_BOUNDS(MGPWireRec_%s, \"%s\");" % (call.Name, call.Name))
|
||||||
out.append(" return false;")
|
out.append(" break;")
|
||||||
out.append(""" case MGPWireOp::kInvalid:
|
out.append(""" case MGPWireOp::kInvalid:
|
||||||
case MGPWireOp::kOpCount:
|
case MGPWireOp::kOpCount:
|
||||||
default:
|
default:
|
||||||
MGPipeWireProtocolFatal("<unknown opcode>", size, remaining);
|
MGPipeWireProtocolFatal("<unknown opcode>", size, remaining);
|
||||||
}
|
}
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
if (gMGPipeWireRecordApply != nullptr) {
|
||||||
|
return gMGPipeWireRecordApply(op, record, size, remaining);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef MGP_WIRE_CHECK_BOUNDS""")
|
#undef MGP_WIRE_CHECK_BOUNDS""")
|
||||||
|
|||||||
Reference in New Issue
Block a user