diff --git a/docs/Disaggregated/notes/perf-p15/PARITY-SEED.md b/docs/Disaggregated/notes/perf-p15/PARITY-SEED.md new file mode 100644 index 00000000..8b5389b0 --- /dev/null +++ b/docs/Disaggregated/notes/perf-p15/PARITY-SEED.md @@ -0,0 +1,617 @@ +# Deleted-symbol inventory: 88a11b81 (OLD) -> a0f4aa83 (NEW) + +Seed for the stage 0.1 parity audit (PLAN-P15.md). Regex-based extraction, not compiler-accurate. See method notes at the end. + +## Summary + +| file | present at NEW | OLD bytes | NEW bytes | deleted names | of which gone | +|---|---|---|---|---|---| +| `MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp` | yes | 24665 | 4092 | 29 | 29 | +| `MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.h` | yes | 8751 | 2769 | 11 | 11 | +| `MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp` | yes | 102628 | 6349 | 23 | 22 | +| `MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h` | yes | 24416 | 8866 | 8 | 7 | +| `MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp` | yes | 1009784 | 534405 | 85 | 85 | +| `MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h` | yes | 148622 | 108551 | 10 | 10 | +| `MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp` | yes | 224566 | 120448 | 25 | 23 | +| `MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h` | yes | 46216 | 38007 | 2 | 2 | +| `MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp` | yes | 291971 | 138980 | 51 | 50 | +| `MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h` | yes | 61972 | 45328 | 12 | 12 | +| `MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp` | yes | 116229 | 84585 | 25 | 25 | +| `MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h` | yes | 28699 | 22339 | 3 | 3 | +| `MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp` | yes | 49266 | 26249 | 7 | 7 | +| `MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h` | yes | 13262 | 8134 | 3 | 2 | +| `MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp` | yes | 1149763 | 898589 | 53 | 50 | +| `MobileGL/MG_Backend/DirectGLES/Managers.cpp` | yes | 1063201 | 912235 | 49 | 48 | +| `MobileGL/MG_Backend/DirectGLES/Managers.h` | yes | 254434 | 229808 | 14 | 14 | + +## `MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp` + +OLD defs: 32, NEW defs: 3, deleted: 29 (gone 29, params differ 0). + +| name | body lines | status | HEAD elsewhere (token) | comment | +|---|---|---|---|---| +| `VkClearManager::QueueClear(GLbitfield mask, const ClearFramebufferPayload& clearPayload, const MG_State::GLState::Fra...` | 48 | gone | 0 | | +| `VkClearManager::GetPendingClear(const PendingClearKey& key, ClearAttachmentPayload& outPayload, SharedPtr& outEntries)` | 23 | gone | 0 | | +| `VkClearManager::QueueClear(const ClearAttachmentPayload& clearPayload, const MG_State::GLState::FramebufferAttachment...` | 22 | gone | 0 | | +| `VkClearManager::LockTextureIdentityLocked(const TextureIdentity& identity, SharedPtr& outTexture)` | 6 | gone | 0 | | +| `VkClearManager::PopPendingClear(const MG_State::GLState::FramebufferAttachmentObject& attachment)` | 6 | gone | 0 | | +| `VkClearManager::Shutdown()` | 6 | gone | 26: BackendObjects.h, BufferArena.cpp, ... | | +| `VkClearManager::GetPendingClear(const PendingClearKey& key, ClearAttachmentPayload& outPayload)` | 4 | gone | 0 | | +| `PendingClearMatchesTextureIdentity(const PendingClearKey& key, const TextureIdentity& identity)` | 3 | gone | 0 | | +| `VkClearManager::Initialize()` | 3 | gone | 32: BackendObject.h, BackendObjects.h, ... | | + +## `MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.h` + +OLD defs: 13, NEW defs: 2, deleted: 11 (gone 11, params differ 0). + +| name | body lines | status | HEAD elsewhere (token) | comment | +|---|---|---|---|---| +| `class VkClearManager` | 55 | gone | 5: VkClearManager.cpp, VkRenderPassManager.cpp, ... | | +| `class PendingClearKeyHash` | 15 | gone | 0 | | +| `PendingClearKeyHash::operator()(const PendingClearKey& key)` | 13 | gone | 0 | | +| `class PendingClearKey` | 13 | gone | 0 | | +| `class TextureIdentity` | 8 | gone | 0 | | +| `class TextureIdentityHash` | 7 | gone | 0 | | +| `PendingClearKey::operator==(const PendingClearKey& other)` | 5 | gone | 0 | | +| `TextureIdentityHash::operator()(const TextureIdentity& key)` | 5 | gone | 0 | | +| `class PendingClearEntry` | 4 | gone | 0 | | +| `TextureIdentity::operator==(const TextureIdentity& other)` | 3 | gone | 0 | | +| `VkClearManager::HasAnyPendingClears()` | 1 | gone | 0 | Lock-free probe for the consecutive-draw fast path: any pending clear | + +## `MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp` + +OLD defs: 35, NEW defs: 13, deleted: 23 (gone 22, params differ 1). + +| name | body lines | status | HEAD elsewhere (token) | comment | +|---|---|---|---|---| +| `VkRenderPassManager::GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageInde...` | 760 | gone | 2: VkRenderPassManager.h, VulkanRenderer.cpp | | +| `VkRenderPassManager::GetOrCreateRenderbufferResource(const SharedPtr& renderbu...` | 169 | gone | 0 | | +| `VkRenderPassManager::ComputeHash(const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex, Bool in...` | 163 | gone | 5: PipelineFactory.cpp, PipelineFactory.h, ... | | +| `VkRenderPassManager::OnPresent()` | 43 | gone | 6: DirectGLES.cpp, DirectVulkan.cpp, ... | | +| `ResolveCompleteColorAttachmentTexture(const MG_State::GLState::FramebufferObject& fbo, FramebufferAttachmentType atta...` | 35 | gone | 0 | | +| `ResolveAttachmentViewType(const MG_State::GLState::FramebufferAttachmentObject& attachment, const VkTextureManager::T...` | 30 | gone | 0 | VUID-VkFramebufferCreateInfo-flags-04113: every view handed to vkCreateFramebuffer must have | +| `VkRenderPassManager::CollectRenderbufferGarbage()` | 30 | gone | 0 | | +| `VkRenderPassManager::QueueRenderbufferClear(const ClearAttachmentPayload& clearPayload, const MG_State::GLState::Fram...` | 27 | gone | 0 | | +| `VkRenderPassManager::QueueRenderbufferClear(GLbitfield mask, const ClearFramebufferPayload& clearPayload, const MG_St...` | 24 | gone | 0 | | +| `VkRenderPassManager::RenderbufferResource::Destroy(VkDevice device, VmaAllocator allocator)` | 24 | gone | 17: BackendObjects.h, DirectGLES.cpp, ... | | +| `VkRenderPassManager::CollectDeferredRenderbufferReleases(Bool destroyAll)` | 21 | gone | 0 | | +| `ResolveAttachmentBaseArrayLayer(const MG_State::GLState::FramebufferAttachmentObject& attachment)` | 19 | gone | 1: BackendObject_DirectVulkan.cpp | | +| `TryResolveSampleCountFlagBits(Int requestedSamples, VkSampleCountFlagBits& outSampleCount)` | 17 | gone | 1: VkTextureManager.cpp | | +| `ResolveImageAspectMaskForFormat(VkFormat format)` | 16 | gone | 0 | | +| `VkRenderPassManager::GetPendingRenderbufferClear(MG_State::GLState::RenderbufferObject* renderbuffer, ClearAttachment...` | 15 | gone | 0 | | +| `VkRenderPassManager::DeferRenderbufferBackingRelease(RenderbufferResource& resource)` | 14 | gone | 0 | | +| `VkRenderPassManager::PurgeRenderPasses()` | 10 | gone | 0 | | +| `VkRenderPassManager::RetireAgeFrames()` | 8 | gone | 0 | | +| `VkRenderPassManager::HasPendingRenderbufferClear(const MG_State::GLState::FramebufferAttachmentObject& attachment)` | 7 | gone | 0 | | +| `VkRenderPassManager::VkRenderPassManager(VkDevice device, VkPhysicalDevice physicalDevice, VmaAllocator allocator, co...` | 7 | params differ | 12: PipelineFactory.cpp, PipelineFactory.h, ... | | +| `VkRenderPassManager::PopPendingRenderbufferClear(MG_State::GLState::RenderbufferObject* renderbuffer)` | 5 | gone | 0 | | +| `IsCubeMapFaceUploadTarget(TextureUploadTarget target)` | 4 | gone | 2: VkClearManager.cpp, VkTextureManager.cpp | | +| `ColorFormatLacksAlpha(const MG_State::GLState::ITextureObject* texture)` | 3 | gone | 0 | | + +## `MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h` + +OLD defs: 21, NEW defs: 13, deleted: 8 (gone 7, params differ 1). + +| name | body lines | status | HEAD elsewhere (token) | comment | +|---|---|---|---|---| +| `class RenderbufferResource` | 26 | gone | 0 | | +| `class TrackedAttachmentLayoutInfo` | 13 | gone | 0 | | +| `class PendingClearAttachmentInfo` | 12 | gone | 0 | | +| `class DeferredRenderbufferRelease` | 7 | gone | 0 | A superseded renderbuffer backing (glRenderbufferStorage respecify) parked | +| `class IEvictionObserver` | 5 | gone | 2: ProgramFactory.h, VulkanRenderer.h | Notified once per OnPresent sweep with every aged-out entry's VkRenderPass | +| `class PendingRenderbufferClear` | 4 | gone | 0 | | +| `RenderPassEntry::RenderPassEntry(Uint64 hash, VkRenderPass renderpass, VkFramebuffer framebuffer, Uint64 compatibilit...` | 1 | params differ | 4: VkRenderPassManager.cpp, VulkanRenderer.cpp, ... | | +| `VkRenderPassManager::SetEvictionObserver(IEvictionObserver* observer)` | 1 | gone | 2: ProgramFactory.h, VulkanRenderer.cpp | Observer may be null (no notifications). Not owned. | + +## `MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp` + +OLD defs: 301, NEW defs: 216, deleted: 85 (gone 85, params differ 0). + +| name | body lines | status | HEAD elsewhere (token) | comment | +|---|---|---|---|---| +| `VulkanRenderer::BlitNamedFramebuffer(const SharedPtr& readFbo, const SharedPtr<...` | 624 | gone | 5: BackendObject.h, DirectGLES.cpp, ... | | +| `VulkanRenderer::UploadAndBindVertexBuffers(VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject&...` | 417 | gone | 0 | | +| `VulkanRenderer::TrySetupDrawFastPath(FrameContext::FrameData& frame, GLenum mode, Flags aspects, con...` | 401 | gone | 0 | | +| `VulkanRenderer::GenerateDepthMipmapWithShader(FrameContext::FrameData& frame, MG_State::GLState::ITextureObject& text...` | 351 | gone | 2: VulkanRenderer.h, WireDepthMipmap.inc | | +| `VulkanRenderer::GetTextureImage(const SharedPtr& textureObject, TextureUploadTarge...` | 235 | gone | 2: BackendObject.h, RecordVerbSink.cpp | | +| `VulkanRenderer::TryBlitToDefaultFramebufferWithShader(FrameContext::FrameData& frame, MG_State::GLState::FramebufferO...` | 195 | gone | 0 | | +| `VulkanRenderer::UploadAndBindIndexBuffer(FrameContext::FrameData& frame, const MG_State::GLState::VertexArrayObject& ...` | 179 | gone | 2: DirectVulkan.cpp, VulkanRenderer.h | | +| `VulkanRenderer::MaterializePendingClearForTexture(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& t...` | 175 | gone | 2: VkClearManager.cpp, VkTextureManager.h | | +| `VulkanRenderer::QueueClearBufferPayloadForFramebuffer(const MG_State::GLState::FramebufferObject& framebuffer, GLenum...` | 132 | gone | 0 | | +| `VulkanRenderer::ReadDepthStencilPixels(MG_State::GLState::FramebufferObject& readFbo, GLint x, GLint y, GLsizei width...` | 118 | gone | 0 | | +| `VulkanRenderer::LookupVaoDrawMemo(const MG_State::GLState::VertexArrayObject* vao)` | 114 | gone | 0 | | +| `ResolveFramebufferBlitBinding(MG_State::GLState::FramebufferObject& fbo, Bool isReadFramebuffer, Uint32 swapchainImag...` | 113 | gone | 0 | | +| `ResolveTextureCopySourceBinding(MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex, SwapchainObjec...` | 108 | gone | 0 | | +| `ResolveColorBlitBinding(MG_State::GLState::FramebufferObject& fbo, Bool isReadFramebuffer, Uint32 swapchainImageIndex...` | 103 | gone | 0 | | +| `VulkanRenderer::MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride)` | 101 | gone | 8: BackendObject.h, BackendObject_DirectGLES.cpp, ... | | +| `VulkanRenderer::MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount, GL...` | 99 | gone | 9: BackendObject.h, BackendObject_DirectGLES.cpp, ... | | +| `VulkanRenderer::PrepareStorageImageTextures(FrameContext::FrameData& frame, const MagmaProgramSource& program, const ...` | 93 | gone | 0 | | +| `VulkanRenderer::MaterializePendingClearForRenderbuffer(VkCommandBuffer commandBuffer, const SharedPtr& framebuffer, GLenum bu...` | 20 | gone | 1: BackendObject.h | The integer clears carry the same payload as their target-based siblings; only the | +| `ConvertIntegerVertexStreamToFloat32(const MG_State::GLState::VertexAttribute& attribute, const Uint8* sourceData, Siz...` | 19 | gone | 0 | | +| `HasUnsupportedCompleteRenderbufferAttachment(const MG_State::GLState::FramebufferObject& framebufferObject)` | 19 | gone | 0 | | +| `VulkanRenderer::ClearNamedFramebufferfv(const SharedPtr& framebuffer, GLenum bu...` | 19 | gone | 1: BackendObject.h | | +| `ConvertScaledIntegerVertexStreamToFloat32(const MG_State::GLState::VertexAttribute& attribute, const Uint8* sourceDat...` | 18 | gone | 0 | | +| `GetSupportedColorWriteMaskForComponentCount(SizeT componentCount)` | 18 | gone | 0 | | +| `HasDistinctCompleteDepthStencilTextureAttachments(const MG_State::GLState::FramebufferObject& framebufferObject)` | 17 | gone | 0 | | +| `GetDepthStencilAspectMaskForFormat(VkFormat format)` | 16 | gone | 0 | The aspects a depth/stencil format actually carries. VkTextureManager keeps its own copy of | +| `ShouldUseTransientVertexIndexBuffer(const MG_State::GLState::BufferObject& bufferObject)` | 16 | gone | 0 | | +| `ActiveRenderPassUsesTexture(const ActiveRenderPassInfo& activeRenderPass, const MG_State::GLState::ITextureObject& te...` | 15 | gone | 0 | | +| `ApplyNativeBlitDefaultFramebufferSourceTransform(VkSurfaceTransformFlagBitsKHR preTransform, const BlitImageBinding& ...` | 14 | gone | 0 | The same conversion on the READ side, which never had one: a blit whose source is the | +| `ApplyNativeBlitDefaultFramebufferTransform(VkSurfaceTransformFlagBitsKHR preTransform, const BlitImageBinding& dstBin...` | 14 | gone | 0 | | +| `VulkanRenderer::SettleTexelsBeforeQueuedClear(const MG_State::GLState::FramebufferObject& framebuffer, const MG_State...` | 14 | gone | 0 | | +| `ConvertIntegerVertexComponentToFloat(ComponentT value, Bool normalized)` | 13 | gone | 0 | | +| `ResolveAttachmentBaseArrayLayer(const MG_State::GLState::FramebufferAttachmentObject& attachment)` | 13 | gone | 1: BackendObject_DirectVulkan.cpp | | +| `GetSwapchainDepthStencilAspectMask(const SwapchainObject& swapchainObject)` | 12 | gone | 0 | | +| `IsValidSampledImageLayout(VkImageLayout layout)` | 12 | gone | 0 | | +| `RepackVertexStream(const Uint8* sourceData, SizeT sourceStride, SizeT elementSize, SizeT elementCount, Vector&...` | 12 | gone | 0 | | +| `ResolveFramebufferCopyAttachmentType(const MG_State::GLState::FramebufferObject& fbo, Bool isReadFramebuffer, VkImage...` | 12 | gone | 0 | | +| `VulkanRenderer::ClearNamedFramebufferuiv(const SharedPtr& framebuffer, GLenum b...` | 12 | gone | 1: BackendObject.h | | +| `VulkanRenderer::OnRenderPassesDestroyed(const Vector& renderPasses)` | 12 | gone | 0 | | +| `ComputeFullMipLevelCount(const IntVec3& baseTexelSize)` | 11 | gone | 1: VkTextureManager.cpp | | +| `MipShrinkingComponentCount(TextureTarget target)` | 11 | gone | 0 | How many components of a GL-space texel size actually halve down the mip chain. An array | +| `VulkanRenderer::ClearNamedFramebufferfi(const SharedPtr& framebuffer, GLenum bu...` | 11 | gone | 1: BackendObject.h | | +| `VulkanRenderer::FinishPendingGpuWork()` | 11 | gone | 0 | | +| `RequiresShaderBlitToDefaultFramebuffer(VkSurfaceTransformFlagBitsKHR preTransform)` | 9 | gone | 0 | | +| `ComputeMipTexelSizeWithFixedComponents(const IntVec3& baseTexelSize, Uint32 relativeMipLevel, Int shrinkingComponents)` | 7 | gone | 0 | | +| `RecordUnsupportedFramebufferError(const char* func)` | 7 | gone | 0 | | +| `VulkanRenderer::GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels)` | 7 | gone | 6: BackendObject.h, DirectGLES.cpp, ... | | +| `ComputeMipTexelSize(const IntVec3& baseTexelSize, Uint32 relativeMipLevel)` | 6 | gone | 0 | | +| `IsUnsupportedFramebufferForDirectVulkan(const MG_State::GLState::FramebufferObject& framebufferObject)` | 5 | gone | 0 | | +| `ResolveGenerateMipmapFinalLayout(VkImageAspectFlags aspectMask)` | 5 | gone | 0 | | +| `VulkanRenderer::GetOrCreatePipeline(GLenum mode, const MagmaProgramSource& program, const ProgramFactory::VkProgramOb...` | 5 | gone | 4: MagmaPipeArms.h, PipelineFactory.cpp, ... | | +| `class SnapshotCacheEntry` | 5 | gone | 0 | | +| `constexpr(std::is_signed_v)` | 5 | gone | 47: BackendObject.h, DirectGLES.cpp, ... | | +| `AlignPixelRow(SizeT rowBytes, Int alignment)` | 4 | gone | 1: DirectGLES.cpp | | +| `AttachmentIsDepthSlice(const MG_State::GLState::FramebufferAttachmentObject& attachment)` | 4 | gone | 0 | A 3D image has arrayLayers == 1: its "layer" is a z slice, which has to travel as an | +| `IsColorAttachment(FramebufferAttachmentType attachmentType)` | 4 | gone | 0 | | +| `IsCubeMapFaceUploadTarget(TextureUploadTarget target)` | 4 | gone | 2: VkClearManager.cpp, VkTextureManager.cpp | | +| `ColorFormatLacksAlpha(const MG_State::GLState::ITextureObject* texture)` | 3 | gone | 0 | GL 4.6 core 15.2.3: a colour format with no alpha channel reads as if alpha were one. | +| `CombinePipelineStateWord(Uint64 hash, Uint64 word)` | 3 | gone | 0 | Boost-style hash combine. The inputs are tiny enum ordinals and bit masks, so | +| `RecordClearBufferError(const char* func, ErrorCode code, const char* message)` | 3 | gone | 0 | | +| `VulkanRenderer::IsDrawIndirectCountExtensionEnabled()` | 3 | gone | 0 | | + +## `MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h` + +OLD defs: 55, NEW defs: 52, deleted: 10 (gone 10, params differ 0). + +| name | body lines | status | HEAD elsewhere (token) | comment | +|---|---|---|---|---| +| `class SetupDrawSnapshot` | 99 | gone | 1: VulkanRenderer.cpp | Snapshot behind TrySetupDrawFastPath. Values only: the program and | +| `class ResolvedVertexBindings` | 73 | gone | 0 | One VAO's resolved vkCmdBindVertexBuffers arguments, reusable by a later draw | +| `class ConvertedVertexStreamKey` | 19 | gone | 0 | | +| `class ConvertedVertexStreamKeyHash` | 17 | gone | 0 | | +| `ConvertedVertexStreamKeyHash::operator()(const ConvertedVertexStreamKey& key)` | 15 | gone | 0 | | +| `class ConvertedVertexStream` | 11 | gone | 0 | | +| `ConvertedVertexStreamKey::operator==(const ConvertedVertexStreamKey& other)` | 6 | gone | 0 | | +| `VulkanRenderer::InvalidateSetupDrawSnapshots()` | 5 | gone | 0 | | +| `VulkanRenderer::ResolveVaoHandle(const MG_State::GLState::VertexArrayObject& vao)` | 4 | gone | 0 | The VAO's {slot, gen}. A one-entry memo hit for every acquisition after a draw's | +| `VulkanRenderer::VaoContentHashIfKnown(const MG_State::GLState::VertexArrayObject& vao, Uint64& outHash)` | 3 | gone | 0 | "Is this VAO's content hash already memoized?", asked of whichever side owns the | + +## `MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp` + +OLD defs: 63, NEW defs: 41, deleted: 25 (gone 23, params differ 2). + +| name | body lines | status | HEAD elsewhere (token) | comment | +|---|---|---|---|---| +| `UniformManager::BindProgramUniformBuffers(VkCommandBuffer commandBuffer, const MagmaProgramSource& program, const Pro...` | 410 | params differ | 5: ProgramFactory.cpp, ProgramFactory.h, ... | | +| `UniformManager::ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, const MagmaProgramSource& program, const Prog...` | 244 | gone | 1: UniformManager.h | | +| `UniformManager::ResolveWireImageDescriptor(VkCommandBuffer commandBuffer, const MagmaProgramSource& program, const Pr...` | 167 | params differ | 1: UniformManager.h | | +| `UniformManager::CollectSamplerImageFeedback(const MagmaProgramSource& program, const ProgramFactory::VkProgramObject&...` | 78 | gone | 0 | | +| `UniformManager::CollectStorageImageTextures(const MagmaProgramSource& program, const ProgramFactory::VkProgramObject&...` | 76 | gone | 0 | | +| `UniformManager::GetFallbackMultisampleTexture(TextureTarget target, SamplerNumericDomain numericDomain)` | 75 | gone | 0 | | +| `UniformManager::ResolveSampledBinding(const MagmaProgramSource& program, const ProgramFactory::VkProgramObject& progr...` | 64 | gone | 0 | | +| `UniformManager::ProgramSamplesOnlySingleLevelTextures(const MagmaProgramSource& program, const ProgramFactory::VkProg...` | 59 | gone | 0 | | +| `UniformManager::CollectSampledTextures(const MagmaProgramSource& program, const ProgramFactory::VkProgramObject& prog...` | 46 | gone | 0 | | +| `UniformManager::GetUnboundStorageImageTexture(TextureTarget target, VkFormat format)` | 43 | gone | 1: ProgramFactory.h | | +| `UniformManager::SampledBindingsUnchanged(const MagmaProgramSource& program, const ProgramFactory::VkProgramObject& pr...` | 42 | gone | 0 | | +| `UniformManager::GetFallbackTexture(TextureTarget target, SamplerNumericDomain numericDomain)` | 39 | gone | 0 | | +| `PlaceholderShapeForTarget(TextureTarget target)` | 37 | gone | 0 | | +| `UniformManager::ResolveSamplerDescriptorOverride(const SamplerBindingOverride& samplerBindingOverride, VkDescriptorIm...` | 37 | gone | 0 | | +| `UniformManager::ResolveSamplerTextureRaw(const MagmaProgramSource& program, const ProgramFactory::VkProgramObject& pr...` | 25 | gone | 0 | | +| `FindFramebufferAttachmentForTexture(const MG_State::GLState::FramebufferObject& framebuffer, const MG_State::GLState:...` | 24 | gone | 0 | | +| `UniformManager::ResolveSamplerTexture(const MagmaProgramSource& program, const ProgramFactory::VkProgramObject& progr...` | 23 | gone | 0 | | +| `MakePlaceholderTextureObject(TextureTarget target, Uint index)` | 22 | gone | 0 | TextureObjectMipmap, not ITextureObject: AllocateStorage and MarkStorageDirty live | +| `UniformManager::ResolveUnboundStorageImagePlaceholder(const ProgramFactory::VkProgramObject& programObj, Uint32 bindi...` | 16 | gone | 0 | | +| `InternalFormatForVkFormat(VkFormat format)` | 13 | gone | 0 | Reverse of MG_Util::ConvertTextureInternalFormatToVkEnum. A placeholder texture is | +| `IsValidSampledImageLayout(VkImageLayout layout)` | 12 | gone | 0 | | +| `ResolveSamplerUnitIndex(const MagmaProgramSource& program, Int location, Uint32 binding)` | 11 | gone | 0 | | +| `class PlaceholderShape` | 8 | gone | 0 | What a 1x1 placeholder of a given target has to allocate for the backend to give it the | +| `IsPlaceholderTexture(const MG_State::GLState::ITextureObject* texture)` | 7 | gone | 0 | MobileGL's own stand-in textures, by the reserved ids above. Nothing an application can | +| `ResolveDescriptorElementLocation(const MagmaProgramSource& program, Int baseLocation, Uint32 element)` | 7 | gone | 0 | Uniform location of ELEMENT `element` of the opaque-uniform array at `baseLocation`, or | + +## `MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h` + +OLD defs: 19, NEW defs: 19, deleted: 2 (gone 2, params differ 0). + +| name | body lines | status | HEAD elsewhere (token) | comment | +|---|---|---|---|---| +| `class FastRebindMemo` | 13 | gone | 0 | Dynamic-offset-only rebind (see BindProgramUniformBuffers): records the | +| `class SamplerBindingOverride` | 9 | gone | 0 | | + +## `MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp` + +OLD defs: 104, NEW defs: 55, deleted: 51 (gone 50, params differ 1). + +| name | body lines | status | HEAD elsewhere (token) | comment | +|---|---|---|---|---| +| `VkTextureManager::UploadDirtyMipLevels(MG_State::GLState::TextureObjectMipmap &mipmapTexture, TextureUploadTarget upl...` | 524 | gone | 0 | | +| `VkTextureManager::SyncTextureResource(const MG_State::GLState::ITextureObject &texture, TextureUploadTarget uploadTar...` | 416 | gone | 4: BackendObject_DirectVulkan.cpp, VkTextureManager.h, ... | | +| `VkTextureManager::SnapshotTextureForSampling(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& textur...` | 148 | gone | 0 | | +| `VkTextureManager::SyncTexture(MG_State::GLState::ITextureObject &texture, TextureResource &outResource)` | 117 | gone | 1: VkTextureManager.h | | +| `VkTextureManager::GetOrCreateStorageImageView(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel, VkFormat f...` | 112 | gone | 0 | | +| `VkTextureManager::SyncTextureAndGetDescriptor(MG_State::GLState::ITextureObject& textureOrView)` | 108 | gone | 2: VkTextureManager.h, VulkanRenderer.cpp | | +| `PreserveTextureContentsOnRecreate(VkDevice device, VkCommandPool commandPool, VkQueue graphicsQueue, const VkTextureM...` | 106 | params differ | 0 | | +| `TryResolveTextureShapeInfo(const MG_State::GLState::ITextureObject& texture, TextureUploadTarget uploadTarget, const ...` | 102 | gone | 1: VulkanRenderer.cpp | | +| `VkTextureManager::GetOrCreateAttachmentViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel, Ui...` | 91 | gone | 0 | | +| `VkTextureManager::GetOrCreateSampledImageView(MG_State::GLState::ITextureObject& texture, VkFormat format)` | 83 | gone | 0 | | +| `VkTextureManager::SyncTextureViews(const MG_State::GLState::ITextureObject& texture, TextureResource& resource)` | 56 | gone | 1: VkTextureManager.h | | +| `VkTextureManager::ResolveTextureViewWindow(MG_State::GLState::ITextureObject& texture, const TextureResource& resource)` | 55 | gone | 0 | | +| `VkTextureManager::UpdateTrackedImageLayoutAfterAttachmentWrite(VkCommandBuffer commandBuffer, MG_State::GLState::ITex...` | 52 | gone | 1: VkTextureManager.h | | +| `VkTextureManager::TransitionTextureForSampling(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& text...` | 49 | gone | 0 | | +| `VkTextureManager::GetOrCreateSampledViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel)` | 44 | gone | 0 | | +| `VkTextureManager::GetOrCreateWindowedSampledView(MG_State::GLState::ITextureObject& texture, TextureResource& resourc...` | 41 | gone | 0 | Builds (and caches) one sampled VkImageView over `resource`'s image for an arbitrary | +| `VkTextureManager::NoteTextureViewImageRequirements(MG_State::GLState::ITextureObject& viewTexture, MG_State::GLState:...` | 41 | gone | 0 | The extra VkImageCreateFlags a GL texture view needs on the image it views. Recorded | +| `VkTextureManager::CheckMipmapCompleteness(const MG_State::GLState::ITextureObject& texture, TextureUploadTarget& outT...` | 36 | gone | 0 | | +| `VkTextureManager::GetOrCreateViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel)` | 36 | gone | 0 | | +| `VkTextureManager::TransitionTextureForStorageImage(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& ...` | 30 | gone | 0 | | +| `VkTextureManager::ResolveViewMipRange(const MG_State::GLState::ITextureObject& texture, Uint32 mipLevels, Uint32& out...` | 28 | gone | 0 | | +| `VkTextureManager::CreateImageView(VkImage image, VkFormat format, VkImageAspectFlags aspect, VkImageViewType viewType...` | 27 | gone | 0 | | +| `VkTextureManager::NeedsStorageImagePreparation(MG_State::GLState::ITextureObject& texture)` | 25 | gone | 1: VkTextureManager.h | | +| `class UploadItem` | 24 | gone | 0 | | +| `VkTextureManager::MakeTextureIdentity(MG_State::GLState::ITextureObject* texture)` | 22 | gone | 0 | | +| `ResolveTextureViewImageViewType(TextureTarget target, VkImageViewType storageViewType)` | 21 | gone | 0 | The VkImageViewType a GL texture view's own target asks for. Deliberately derived from the | +| `VkTextureManager::PruneStaleTextureAliases(MG_State::GLState::ITextureObject* texture)` | 20 | gone | 0 | | +| `ToVkComponentSwizzle(TextureSwizzleParam swizzle)` | 19 | gone | 0 | | +| `VkTextureManager::GetUploadMipLevelCount(const MG_State::GLState::TextureObjectMipmap& texture, TextureUploadTarget t...` | 17 | gone | 0 | | +| `VkTextureManager::DeferViewRelease(VkImageView view)` | 15 | gone | 0 | | +| `VkTextureManager::EraseTrackedTexture(const TextureIdentity& identity)` | 14 | gone | 0 | | +| `VkTextureManager::NeedsMipChainGrowth(MG_State::GLState::ITextureObject& texture)` | 13 | gone | 0 | | +| `IsValidSampledImageLayout(VkImageLayout layout)` | 12 | gone | 0 | | +| `VkTextureManager::AppendViewRequestedFormats(const MG_State::GLState::ITextureObject& storageTexture, Vector& bufferObject...` | 85 | gone | 0 | | +| `VkBufferManager::AcquirePersistentMap(MG_State::GLState::BufferObject& bufferObject)` | 56 | gone | 2: Managers.cpp, Managers.h | | +| `VkBufferManager::AcquireResidentSlice(BufferKind kind, const SharedPtr& bufferObject...` | 49 | gone | 0 | | +| `VkBufferManager::StagedRangeCopy(VkBufferResource& resource, const void* data, SizeT offset, SizeT size)` | 46 | gone | 0 | | +| `VkBufferManager::OnRespecify(MG_State::GLState::BufferObject& bufferObject)` | 43 | gone | 1: VkBufferManager.h | | +| `VkBufferManager::OnSubData(MG_State::GLState::BufferObject& bufferObject, SizeT offset, SizeT size)` | 36 | gone | 0 | | +| `VkBufferManager::OnFlushMappedRange(MG_State::GLState::BufferObject& bufferObject, Range1D range, Flags&& resource)` | 18 | gone | 0 | | +| `VkBufferManager::ReleaseAllLiveResources()` | 15 | gone | 0 | | +| `VkBufferManager::DeferRelease(VkBufferObject&& buffer)` | 14 | gone | 1: VkBufferManager.h | | +| `VkBufferManager::GetOrCreateResource(const SharedPtr& bufferObject)` | 14 | gone | 0 | | +| `VkBufferManager::TrackLiveResource(const SharedPtr& resource)` | 13 | gone | 0 | | +| `Ops_OnDestroy(SharedPtr&& resource)` | 9 | gone | 1: Managers.cpp | | +| `Ops_ReadbackFromGpu(BufferObject& bufferObject)` | 7 | gone | 0 | The CPU is about to read a buffer a shader wrote. Its bytes live in coherent | +| `Ops_AcquirePersistentMap(BufferObject& bufferObject)` | 6 | gone | 1: Managers.cpp | | +| `Ops_FlushMappedRange(BufferObject& bufferObject, Range1D range, Flags appAccess)` | 5 | gone | 0 | | +| `Ops_ResidentSubData(BufferObject& bufferObject, SizeT offset, DataPtr data)` | 5 | gone | 0 | | +| `Ops_Respecify(BufferObject& bufferObject)` | 5 | gone | 0 | | +| `Ops_SubData(BufferObject& bufferObject, SizeT offset, SizeT size)` | 5 | gone | 0 | | +| `VkBufferManager::IsResourceBusy(const VkBufferResource& resource)` | 3 | gone | 1: VkBufferManager.h | | +| `VkBufferManager::ResourceOf(MG_State::GLState::BufferObject& bufferObject)` | 3 | gone | 0 | | + +## `MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h` + +OLD defs: 13, NEW defs: 13, deleted: 3 (gone 3, params differ 0). + +| name | body lines | status | HEAD elsewhere (token) | comment | +|---|---|---|---|---| +| `class VkBufferResource` | 46 | gone | 2: BufferArena.cpp, VkBufferManager.cpp | The DirectVulkan storage behind one frontend buffer (pipe_resource analogue). | +| `VkBufferManager::BumpSliceEpoch(VkBufferResource& resource)` | 1 | gone | 1: VkBufferManager.cpp | See VkBufferResource::sliceEpoch. | +| `VkBufferManager::GetSliceEpochCounter()` | 1 | gone | 0 | Highest value handed to any VkBufferResource::sliceEpoch. Unchanged since a | + +## `MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp` + +OLD defs: 14, NEW defs: 10, deleted: 7 (gone 7, params differ 0). + +| name | body lines | status | HEAD elsewhere (token) | comment | +|---|---|---|---|---| +| `VertexInputStateFactory::GetOrCreateVertexInputState(const MG_State::GLState::VertexArrayObject& vao, HashType hash)` | 218 | gone | 0 | | +| `VertexInputStateFactory::ComputeHash(const MG_State::GLState::VertexArrayObject& vao)` | 72 | gone | 5: PipelineFactory.cpp, PipelineFactory.h, ... | | +| `VertexInputStateFactory::GetOrCreateVertexInputState(const MG_State::GLState::VertexArrayObject& vao)` | 32 | gone | 0 | | +| `VertexInputStateFactory::OnFrameBoundary()` | 30 | gone | 8: PipelineFactory.cpp, PipelineFactory.h, ... | | +| `VertexInputStateFactory::MemosFor(const MG_State::GLState::VertexArrayObject& vao)` | 18 | gone | 0 | | +| `VertexInputStateFactory::GetOrComputeHash(const MG_State::GLState::VertexArrayObject& vao)` | 15 | gone | 0 | | +| `VertexInputStateFactory::TryGetMemoizedHash(const MG_State::GLState::VertexArrayObject& vao, Uint64& outHash)` | 9 | gone | 0 | | + +## `MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h` + +OLD defs: 5, NEW defs: 4, deleted: 3 (gone 2, params differ 1). + +| name | body lines | status | HEAD elsewhere (token) | comment | +|---|---|---|---|---| +| `class VaoBackendMemos` | 11 | gone | 0 | ---- P2 D12.5: the backend's memos, off the frontend VAO and into the backend ---- | +| `VertexInputStateFactory::PackVertexInputAuxMasks(Uint32 unsupportedAttribMask, Uint32 attributeLocationMask)` | 3 | gone | 0 | The VAO aux-memo payload GetOrCreateVertexInputState(vao) stamps: aux0 is the | +| `VertexInputStateFactory::VertexInputStateFactory(const VulkanRendererConfig& config, VkPhysicalDevice physicalDevice,...` | 1 | params differ | 6: BackendObject.h, ProgramFactory.cpp, ... | The mint is the RENDERER's (MagmaPipeIdentityTables), not a process-global and not | + +## `MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp` + +OLD defs: 510, NEW defs: 458, deleted: 53 (gone 50, params differ 3). + +| name | body lines | status | HEAD elsewhere (token) | comment | +|---|---|---|---|---| +| `GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, void* pixels)` | 417 | gone | 6: BackendObject.h, Managers.h, ... | | +| `ScopedDetachedTextureFramebufferAttachments::ScopedDetachedTextureFramebufferAttachments(const SharedPtr& readFramebuffer, const SharedPtr...` | 136 | params differ | 0 | ---- glBlitFramebuffer onto a non-zero array layer ------------------------------------- | +| `SyncCurrentProgram(const SharedPtr& currentProgram)` | 116 | gone | 2: Managers.cpp, Utils.h | | +| `GetTexImageViaShadowConversion(MG_State::GLState::TextureObjectMipmap* textureMipmapObject, TextureUploadTarget uploa...` | 100 | gone | 1: WireTextureReadback.inc | GetTexImage fallback for internal formats the ES driver cannot attach to a framebuffer | +| `SyncVaoAttributeBuffersByHandle(const SharedPtr& currentVAOObject, VertexArrayI...` | 86 | gone | 0 | The handle arm of the resolved-draw-buffers memo (D-G4). Two substitutions and | +| `SyncTextureObjectToBackend(const SharedPtr& textureObject, Bool imageBindableStora...` | 80 | gone | 3: Managers.cpp, Managers.h, ... | | +| `SyncReadFramebufferTextureAttachments(const DrawTextureSyncKeys& keys, const MG_State::GLState::FramebufferObject* dr...` | 74 | gone | 0 | | +| `GenerateThreeChannelFloatMipmapOnCpu(const SharedPtr& texture)` | 69 | gone | 1: Managers.cpp | ES has no colour-renderable three-channel float format, and its glGenerateMipmap | +| `SamplerUniformTextureTarget(GLenum uniformType)` | 59 | gone | 0 | Frontend texture target a GLSL sampler uniform samples from. Only used to find | +| `SyncClientSideVertexArraysForFetch(Uint8 indexSize, Uint32 elementStart, GLsizei count, GLsizei instanceCount, GLint ...` | 53 | gone | 0 | | +| `GenerateMipmapThroughViewWindowForTexture(const SharedPtr& texture)` | 47 | gone | 0 | The monolith twin of the record arm's view-window rule. Same window, read off the frontend | +| `SyncBufferBindingPoints(BufferTarget target, GLenum glTarget)` | 45 | gone | 1: Managers.cpp | | +| `ResolveUnitSamplerBackend(Int unit, const SharedPtr& samplerObject)` | 44 | gone | 0 | | +| `BlitNamedFramebuffer(const SharedPtr& readFramebuffer, const SharedPtr& framebuffer, FramebufferTarget ta...` | 32 | gone | 0 | | +| `MarkShaderStorageBuffersGpuWritten()` | 31 | gone | 0 | Called once the storage-buffer points are bound and the draw/dispatch is about to | +| `GenerateDepthTexture2DMipmap(const SharedPtr& texture, const SharedPtr& texture, const SharedPtr& framebuffer, GLenum buffer, GLint draw...` | 18 | gone | 1: BackendObject.h | | +| `ClearNamedFramebufferiv(const SharedPtr& framebuffer, GLenum buffer, GLint draw...` | 18 | gone | 1: BackendObject.h | | +| `ClearNamedFramebufferuiv(const SharedPtr& framebuffer, GLenum buffer, GLint dra...` | 18 | gone | 1: BackendObject.h | | +| `ResolveVaoTwin(const SharedPtr& vao)` | 18 | gone | 2: Managers.cpp, Managers.h | Resolve-or-create the VAO's backend twin, once per draw: PrepareForDraw passes | +| `SyncBoundBuffer(BufferTarget target, GLenum glTarget)` | 17 | gone | 0 | | +| `ClearNamedFramebufferfi(const SharedPtr& framebuffer, GLenum buffer, GLint draw...` | 15 | gone | 1: BackendObject.h | | +| `RefuseElementArrayBufferFromTheFrontend(const char* entry)` | 15 | gone | 1: MultiDraw.cpp | MONOLITH GLUE, AND LOUD ABOUT IT AS OF P5e (vi). CONTRACT-P5E §5.1 makes the restart | +| `SyncRenderbufferObjectToBackend(const SharedPtr& renderbufferObject)` | 15 | gone | 0 | The renderbuffer twin of TextureImpl::SyncTextureObjectToBackend: the same | +| `TextureLayerCount(const SharedPtr& texture)` | 15 | gone | 0 | Where a target keeps its LAYER count, the state-side twin of the wire descriptor's | +| `EnsureGenerateMipmapStorageAllocated(const SharedPtr& texture)` | 14 | gone | 0 | | +| `IsNativeGetTexImagePair(GLenum format, GLenum type)` | 14 | gone | 0 | Combinations the ES driver has always handled directly for GetTexImage; everything else that maps | +| `ResolveGlobalConstantsRecord(const MG_State::GLState::ProgramObject* program)` | 14 | gone | 0 | THE MONOLITH-GLUE HALF (ruling 1 / ID-81), unchanged in what it does: resolve the | +| `IsWidenedNamedDrawBuffer(const SharedPtr& framebuffer, GLenum buffer, GLint dra...` | 13 | gone | 0 | The same question for an explicitly named framebuffer (the DSA clears), which is NOT | +| `SyncCurrentVAO(const SharedPtr& currentVAOObject, BackendVertexArrayObject* vao...` | 13 | gone | 1: Managers.cpp | | +| `ComputeFullMipmapLevelCount(const IntVec3& baseTexelSize)` | 11 | gone | 0 | | +| `ReadClientSnapshotBytes(void* user, MG_Pipe::MGPipeClientBufferKind kind, Uint64 offset, SizeT size, void* destination)` | 10 | gone | 0 | | +| `VaoHasEnabledClientArray(const MG_State::GLState::VertexArrayObject& vao, Bool* vertexRate = nullptr)` | 10 | gone | 0 | The draw's own fetch, spelled as the range record spells it: `indexSize` 0 is arrays, | +| `BoundElementArrayBuffer()` | 7 | gone | 0 | | +| `BoundElementArrayBufferId()` | 7 | gone | 0 | The GL name PrepareForDraw left on GL_ELEMENT_ARRAY_BUFFER, i.e. what the | +| `ComputeMipmapTexelSize(const IntVec3& baseTexelSize, Uint relativeLevel)` | 7 | gone | 0 | | +| `DrawProgramFromRecords()` | 7 | gone | 0 | P5e (pa), CONTRACT-P5E §5.5 / §5.8, ruling ID-81: "IS THERE ANYBODY LEFT WHO NEEDS THE | +| `MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, GLsizei drawcount)` | 6 | gone | 5: BackendObject.h, DirectVulkan.cpp, ... | Both glMultiDrawElements entry points are emulated - ES has neither in core - by the | +| `PairingsIntact(const Vector& list)` | 6 | gone | 0 | True while every entry's borrowed slot still holds the texture the entry was paired | +| `StampSyncedFBO(FramebufferTarget target, Uint16 slotVersion, Uint16 objectVersion, MG_State::GLState::FramebufferObje...` | 6 | gone | 1: Managers.h | Record that `target` now reflects this exact (binding, object, revision) triple. | +| `BindingPointsComeFromRecords() (x2)` | 4 | gone | 0 | FILE-LOCAL AND INLINE-MEMOISED, for EsprytSlotTablesEnabled's reason: this is | +| `IsWritableImageBufferTexture(const MG_State::GLState::ImageTextureBinding& binding)` | 4 | gone | 0 | | +| `UnitTexturesByHandle()` | 4 | gone | 0 | P5e (tx2), CONTRACT-P5E §5.8 / ruling 1: THE ARM SELECTOR for every by-handle site in | +| `FramebufferRecordArmIsMandatory()` | 3 | gone | 0 | P5e (fb, CONTRACT-P5E.md §5.4 and §0's rule F). THE DECLINE BECOMES THE DETECTOR. | +| `AreOcclusionQueriesSupported()` | 1 | gone | 0 | | + +## `MobileGL/MG_Backend/DirectGLES/Managers.cpp` + +OLD defs: 398, NEW defs: 354, deleted: 49 (gone 48, params differ 1). + +| name | body lines | status | HEAD elsewhere (token) | comment | +|---|---|---|---|---| +| `BackendFramebufferObject::SyncToBackend(const SharedPtr& stateFBOObject, Frameb...` | 383 | gone | 2: DirectGLES.cpp, Managers.h | | +| `BackendTextureObject::RequireImageBindableStorage(const SharedPtr& stateTextureObj...` | 158 | gone | 1: Managers.h | | +| `EnsureBufferResource(const SharedPtr& bufferObject)` | 137 | gone | 3: DirectGLES.cpp, Managers.h, ... | | +| `OnFrontendStateObjectDestroyed(MG_Pipe::MGPipeKind kind, Uint64 lifetimeId)` | 119 | params differ | 0 | P2 step e2's dispatcher: the frontend told us an object died, so free its slot and | +| `BackendRenderbufferObject::SyncToBackend(const SharedPtr& stateRBOObject)` | 117 | gone | 2: DirectGLES.cpp, Managers.h | | +| `BackendTextureObject::SyncTextureViewToBackend(const SharedPtr& stateTextureObject)` | 91 | gone | 1: BackendObject_DirectGLES.cpp | | +| `SyncAttachmentObject(GLenum glFBOTarget, const MG_State::GLState::FramebufferAttachmentObject& attachmentObject, GLen...` | 86 | gone | 2: BackendObject_DirectGLES.cpp, DirectGLES.cpp | | +| `BackendVertexArrayObject::SyncClientSideAttributesForDraw(const SharedPtr& stat...` | 83 | gone | 1: Managers.h | THE CLIENT-ARRAY UPLOAD FOR A DRAW WHOSE FETCHED ELEMENTS (first, count) DOES NOT | +| `Ops_AcquirePersistentMap(BufferObject& bufferObject)` | 78 | gone | 0 | Zero-copy persistent map: back the buffer with real immutable, | +| `BackendFramebufferObject::SyncReadBufferToBackend(const SharedPtr& stateFBOObject)` | 63 | gone | 2: DirectGLES.cpp, Managers.h | | +| `Ops_FlushMappedRange(BufferObject& bufferObject, Range1D range, Flags appAccess)` | 63 | gone | 0 | | +| `Ops_Respecify(BufferObject& bufferObject)` | 55 | gone | 0 | | +| `ResolveVertexInputSubsystemArm()` | 47 | gone | 1: Managers.h | | +| `BackendProgramObjectImpl::SyncToBackend(const SharedPtr& stateProgramObject)` | 42 | gone | 2: DirectGLES.cpp, Managers.h | ---- the two public heads (P5e pg) ----------------------------------------------- | +| `Ops_ReadbackFromGpu(BufferObject& bufferObject)` | 37 | gone | 0 | A shader wrote this buffer through a storage/atomic-counter binding, so the ES | +| `Ops_SubData(BufferObject& bufferObject, SizeT offset, SizeT size)` | 34 | gone | 0 | | +| `MarkBufferGpuWritten(const SharedPtr& bufferObject)` | 32 | gone | 2: DirectGLES.cpp, Managers.h | | +| `SyncZeroStrideAttribute(Uint attribIndex, const MG_State::GLState::VertexAttribute& attrib)` | 31 | gone | 0 | Declares one attribute through the ES binding-point API, the only spelling that can | +| `HandleOfBuffer(const MG_State::GLState::BufferObject* bufferObject)` | 29 | gone | 1: DirectGLES.cpp | | +| `IsBufferDrawClean(const MG_State::GLState::BufferObject* frontend, const GLESBufferResource* resource)` | 25 | gone | 2: DirectGLES.cpp, Managers.h | | +| `ProgramArchiveSource::FromFrontend(const MG_State::GLState::ProgramObject& program)` | 24 | gone | 0 | | +| `GetOrCreateSamplerViewForHandle(MG_Pipe::MGPipeHandle view)` | 23 | gone | 0 | | +| `ComputeShaderStorageBlockBindingSignatureOf(const MG_State::GLState::ProgramObject& program)` | 22 | gone | 0 | The COMPUTATION, still here and still the only one on this side: FromFrontend seeds a | +| `BackendVertexArrayObject::SyncToBackend(const SharedPtr& stateVAOObject)` | 21 | gone | 2: DirectGLES.cpp, Managers.h | | +| `ResolveResourceSubsystemArm()` | 21 | gone | 0 | | +| `BindAttributeBuffer(const MG_State::GLState::VertexAttribute& attrib)` | 16 | gone | 0 | | +| `ComputeAlphaWidenedDrawBufferMask(const MG_State::GLState::FramebufferObject& fbo)` | 16 | gone | 0 | | +| `IsSnormFallbackAttachment(const MG_State::GLState::FramebufferAttachmentObject& attachmentObject)` | 14 | gone | 0 | | +| `IsUnormFallbackAttachment(const MG_State::GLState::FramebufferAttachmentObject& attachmentObject)` | 14 | gone | 0 | | +| `NoteDriverSideTextureWrite(const SharedPtr& textureObject)` | 14 | gone | 1: Managers.h | | +| `IsAlphaWidenedColorAttachment(const MG_State::GLState::FramebufferAttachmentObject& attachmentObject)` | 13 | gone | 0 | | +| `GetReadColorAttachment()` | 12 | gone | 0 | The colour attachment glReadPixels/glGetTexImage would read from, or nullptr when the | +| `HandleOfSamplerViewForTexture(const MG_State::GLState::ITextureObject* textureObject)` | 12 | gone | 0 | | +| `IsIntegerColorAttachment(const MG_State::GLState::FramebufferAttachmentObject& attachmentObject)` | 11 | gone | 0 | | +| `Ops_ResidentSubData(BufferObject& bufferObject, SizeT offset, DataPtr data)` | 9 | gone | 0 | App bytes for an ADOPTED store: queue them untouched-by-the-mapping; the | +| `EnsureNoColorAttachment(ScratchFramebuffer& fb, GLenum fbTarget)` | 7 | gone | 0 | | +| `EnsureNoDepthAttachment(ScratchFramebuffer& fb, GLenum fbTarget)` | 7 | gone | 0 | | +| `MGPipeP5eSeamNotLanded(const char* name, const char* owner, MG_Pipe::MGPipeHandle handle)` | 7 | gone | 0 | | +| `Ops_AcquirePersistentMapTracked(BufferObject& bufferObject)` | 7 | gone | 0 | | +| `FramebuffersAttachingTexture(MG_Pipe::MGPipeHandle texture)` | 6 | gone | 0 | BY VALUE, and deliberately: the caller re-binds framebuffers while it walks the | +| `PushedFramebufferIsBoundTo(FramebufferTarget target, MG_Pipe::MGPipeHandle fbo)` | 6 | gone | 0 | | +| `GetBufferResource(MG_State::GLState::BufferObject* bufferObject)` | 4 | gone | 0 | | +| `Ops_FlushMappedRangeTracked(BufferObject& bufferObject, Range1D range, Flags appAccess)` | 4 | gone | 0 | | +| `Ops_OnDestroyTracked(SharedPtr&& resource)` | 4 | gone | 0 | | +| `Ops_ReadbackFromGpuTracked(BufferObject& bufferObject)` | 4 | gone | 0 | | +| `Ops_ResidentSubDataTracked(BufferObject& bufferObject, SizeT offset, DataPtr data)` | 4 | gone | 0 | | +| `Ops_RespecifyTracked(BufferObject& bufferObject)` | 4 | gone | 0 | Epoch-tracking wrappers: every op bumps the buffer-mutation epoch AFTER | +| `Ops_SubDataTracked(BufferObject& bufferObject, SizeT offset, SizeT size)` | 4 | gone | 0 | | +| `ResourceOf(BufferObject& bufferObject)` | 3 | gone | 0 | | + +## `MobileGL/MG_Backend/DirectGLES/Managers.h` + +OLD defs: 138, NEW defs: 124, deleted: 14 (gone 14, params differ 0). + +| name | body lines | status | HEAD elsewhere (token) | comment | +|---|---|---|---|---| +| `StateBackendObjectRegistry::GetOrCreate(const StatePtr& stateObj)` | 50 | gone | 4: DirectGLES.cpp, Managers.cpp, ... | | +| `StateBackendObjectRegistry::CollectGarbage()` | 21 | gone | 3: VkTextureManager.cpp, VkTextureManager.h, ... | | +| `StateBackendObjectRegistry::Find(StateObject* stateObj) (x2)` | 19 | gone | 5: DirectGLES.cpp, Managers.cpp, ... | Null when no live state object owns this key. | +| `VertexInputReadsRecords()` | 8 | gone | 2: DirectGLES.cpp, MultiDraw.cpp | P5e (vi), CONTRACT-P5E §5.1 + §5.8 (ruling 1 / ID-81): THE ARM SELECTOR for this | +| `StateBackendObjectRegistry::CollectGarbageNow()` | 6 | gone | 0 | Pre-P2 API, kept for the legacy arm. On the handle arm there is nothing it could | +| `StateBackendObjectRegistry::DestroyByLifetimeId(Uint64 lifetimeId)` | 6 | gone | 0 | P2 step e2. STATIC, because a death notice is about an object and not about a | +| `StateBackendObjectRegistry::HandleOf(const StateObject* stateObj)` | 6 | gone | 3: DirectGLES.cpp, Managers.cpp, ... | The {slot, gen} this object's twin is keyed on, or the null handle. This is what a | +| `StateBackendObjectRegistry::StateForHandle(MG_Pipe::MGPipeHandle handle)` | 6 | gone | 1: Managers.cpp | | +| `StateBackendObjectRegistry::begin() (x2)` | 6 | gone | 35: BackendObject_DirectGLES.cpp, DirectGLES.cpp, ... | | +| `StateBackendObjectRegistry::CollectGarbageIfNeeded()` | 5 | gone | 0 | The seven DirectGLES.cpp call sites drive the LEGACY arm and nothing else. On the | +| `StateBackendObjectRegistry::NoteStateForHandle(MG_Pipe::MGPipeHandle handle, const StatePtr& stateObj)` | 5 | gone | 1: DirectGLES.cpp | P5c (hd): the two halves of SlotTables.h's state note, forwarded. A caller holding | +| `ResourceSubsystemEnabled()` | 4 | gone | 0 | INLINE for the reason SlotTables.h spells out at EsprytSlotTablesEnabled: both are | +| `TextureResourceSubsystemEnabled()` | 4 | gone | 2: DirectGLES.cpp, Managers.cpp | | +| `VertexInputSubsystemEnabled()` | 4 | gone | 0 | | + +## git log 88a11b81..HEAD -- listed paths (first 80 of 47) + +```text +ec973fba [Fix] (Magma): a hang-watch marker slot is reused once its submission's fence completed, not once the host read its event. +553eb86d [Perf] (Magma): texture level uploads stop waiting for the GPU to drain; queue order and the upload barrier already order them (rd12 monolith 70.4 -> 83.4, openra 367 -> 745 fps). +785df78d [Perf] (P14): one UBO-ring lookup per draw, split residual copy/stamp loops, one context read for GPU-write marks, Magma slice stamps from the owner's counter (rd12 monolith Espryt 88.8 -> 90.3, Magma 69.8 -> 70.3 fps). +81bdfb1f [Perf] (Espryt): twin lookups skip the bucket SharedPtr copy and the native-context state is one thread-local block (rd12 monolith 81.0 -> 84.1 fps). +032dcd24 [Perf] (Magma): the wire vertex-input layout is kept on its vertex-elements record and buffer handles resolve through a slot-indexed front. +0158a115 [Perf] (Magma): a wire draw that provably continues the open pass skips the attachment resolve and pass-key walk (rd12 monolith 63.2 -> 65.5, inproc 89.8 -> 92.6 fps). +5788a602 [Perf] (Magma): a wire draw whose image descriptors cannot have moved rebinds its descriptor set with fresh buffer offsets (rd12 monolith 60.5 -> 63.3, inproc 83.7 -> 89.7 fps). +afcf8d96 [Perf] (Espryt): a VAO twin adopts an unchanged buffer set and element buffer instead of re-emitting them (rd12 monolith 75.2 -> 80.5 fps). +e6281b26 [Perf] (Magma): wire texture and buffer lookups get direct-mapped fronts and a continued pass stops re-marking its attachments (rd12 monolith 59.6 -> 60.5 fps). +4ef9705e [Perf] (Magma): wire draws memoise each unit's sampler and skip rebinding an unchanged pipeline, vertex or index binding (rd12 monolith 57.9 -> 60.3 fps). +b556ec31 [Perf] (Espryt): a clean framebuffer attachment skips its per-draw resync behind the unit list's gate (rd12 monolith 74.5 -> 77.0 fps). +a9108e1a [Perf] (Espryt): a VAO twin whose buffer set alone moved re-points enabled arrays without the 32-slot enable walk (rd12 monolith 70.3 -> 73.5 fps). +267211e8 [Perf] (Magma): wire draws stop rehashing program SPIR-V and stop allocating per draw (rd12 monolith 45.7 -> 56.7 fps). +de6ab167 [Refactor] (P13): the legacy BufferBackendOps table, Magma's never-populated texture/render-pass/renderbuffer caches and VkClearManager go, and the buffer mock tests drive the resource op table instead (ratchet 36 -> 27). +fa77b187 [Refactor] (P13): Espryt's frontend-keyed slot-table entries and the apply-thread refusals go, death notices release twins by handle, and the link ratchet refuses any '# P13' line (49 -> 36 symbols). +922507f8 [Refactor] (P13 W6f): Magma's pipeline build reads the draw framebuffer record only, and the frontend texture sync behind it goes; link ratchet 58 -> 49. +dab827d5 [Refactor] (P13 W6e): the backends' frontend-framebuffer clear, blit and readback entries go - the verb port owns those slots in every mode - with Magma's frontend render-pass, pending-clear and renderbuffer halves. +8bc39c79 [Test] (P13 W6d): the unmigrated-emulation list is empty, the dead storage-view format case goes, and Espryt's bring-up check is the colour-attachment cap alone. +8abae647 [Refactor] (P13 W6d): Espryt's family arm predicates fold to the record arm, checked once at capability bring-up, and the frontend-arm walks they selected go. +dc1e95b4 [Refactor] (P13 W6c): Magma's frontend arm goes - the frontend program source, sampled-set walk, placeholder textures, VAO draw memos, identity tables and fast path - leaving the record arm. +e0113503 [Refactor] (P13 W6b): the frontend-arm code after each record-arm return goes, and the transform-feedback state keeps one role. +742d43ae [Refactor] (P13 W6b): the data-arm predicate folds out of every backend branch; frontend-arm unit cases go, three adapted, and two GL sampling rules move to an integration scenario. +7fb18840 [Refactor] (P13 W5 S8): MOBILEGL_BUILD_RECORD_ARM is folded away - the record arm is unconditional - and retired_switches keeps it from coming back. +a0b9ca07 [Fix] (P13 W5 S7): Magma's T0 import and its self-test stay transport-only inside the record arm. +a37212ea [Fix] (P13 W5 S7): Espryt's T0 re-import after a context loss stays transport-only inside the record arm's twin re-arm. +1cd35d39 [Refactor] (P13 W5 S7): Magma's wire-object collection, wire-store teardown, depth-resolve entry point and the fast-rebind destroy epoch follow the record-arm guard. +557975bf [Refactor] (P13 W5 S7): the record verbs' verb-state stamps, the record-reading draw and sampler passes and the image-bindable re-mint follow the record-arm guard. +aa5c12b0 [Fix] (P13 W5 S7): Espryt's shared-image arm in the storage sync stays on the transport guard inside the record arm. +7ac3ce03 [Fix] (P13 W5 S7): Espryt's shared-image sync stays on the transport guard. +956e3847 [Refactor] (P13 W5 S7): the record arm's remaining transport-guarded blocks (XFB capture landing, staged-texture follows, view-window mips, tracker membership, unit-window checks) follow the record-arm guard. +4ec65e80 [Fix] (P13 W5 S7): Espryt's copy-image store follow and its driver-write mark build with the record arm. +f4be8c38 [Style] (P13 W5): VertexInputStateFactory.cpp includes its own header first. +7f194a85 [Refactor] (P13 W5 S3): the apply side's backend comes through MG_Backend's ApplyRoleBackend hook, which ServerLoop installs. +bb45648c [Refactor] (P13 W5 S3): Magma's wire texture and vertex-input builders and the buffer shadow size follow the record-arm guard. +17e36589 [Refactor] (P13 W5 S3): the latch twin and Magma's shared-image binding take their guards; the dual-block diagnostics stay transport. +b13af729 [Refactor] (P13 W5 S5): the record arm's three knobs move from IpcTable to RecordArmTable, which every record-arm build parses. +cb81610b [Refactor] (P13 W5 S3): the record arm's definitions and its depth-resolve probe source follow the record-arm guard. +1956d6cd [Refactor] (P13 W5 S3): transport-only hooks get no-op twins where the record arm calls them, and their guards return to the transport. +c879776a [Refactor] (P13 W5 S3): Espryt's shared-image checks stay on the transport guard. +8a2ddc81 [Refactor] (P13 W5 S3): Magma's funnels, the texture ops and the staged-texture macros follow the record-arm guard; shared images stay transport. +d00d5c94 [Refactor] (P13 W5 S3): the record stores' includes and the slot tables' refusals follow the record-arm guard. +b26167a0 [Refactor] (P13 W5 S3): Espryt's sampler-view refusal latches through the record seam. +73a185f6 [Refactor] (P13 W5 S4): the staged stores move to MG_Backend/Record as MG_Record and die through the record fail seam. +e544121e [Refactor] (P13 W5 S3): record-arm declarations, role-guard stubs and the T0 split in the backends. +22739a03 [Refactor] (P13 W5 S2): MG_Backend/DirectVulkan's record-arm guards name MOBILEGL_BUILD_RECORD_ARM. +036d1ac3 [Refactor] (P13 W5 S2): MG_Backend/DirectGLES's record-arm guards name MOBILEGL_BUILD_RECORD_ARM. +cfd6c873 [Fix] (P13 W4b): the aliasing texture store follows glCopyImageSubData into a server-owned destination. +``` + +## Method notes + +- Definitions: function and class/struct bodies found by regex on comment- and literal-stripped text, then brace matching. Declarations without a body are not counted. +- Header inline members are qualified by their innermost enclosing class. Out-of-class definitions keep their own qualified name. +- Match key = qualified name + whitespace-stripped parameter list. Status 'params differ' means the qualified name is still defined in NEW with a different parameter list. +- Body lines = brace-matched span from the opening brace line to the closing brace line, inclusive. +- Comment = topmost line of the contiguous comment block directly above the definition (a template line is skipped), truncated to 160 chars. +- 'HEAD elsewhere (token)' = number of other MobileGL/MG_Backend source files at HEAD containing the bare identifier as a token (not the same file). Approximate; common names will over-match. Indicates possible relocation, not proof. +- Skipped: unqualified names shorter than 4 characters and control keywords (if/for/while/switch/return/catch/...). +- Duplicate keys (overloads with identical parameter text) are merged and shown with a count. diff --git a/docs/Disaggregated/notes/perf-p15/PLAN-P15.md b/docs/Disaggregated/notes/perf-p15/PLAN-P15.md new file mode 100644 index 00000000..a9b0a4b0 --- /dev/null +++ b/docs/Disaggregated/notes/perf-p15/PLAN-P15.md @@ -0,0 +1,543 @@ +# P15 plan: the FCL gap first, then a draw-delta fast path + +Written 2026-10-07 on `feat/disaggregated` at `a0f4aa83`. Device: Lenovo Y700 (Adreno 750, +`HA27Q3LQ`), rooted, clocks pinned. Baseline: `dev` at `08124c99` (the pull monolith). + +This is the plan, plus the measurements it rests on (section 2). Nothing here is implemented yet. + +## 0. Summary + +1. **FCL's "unexplained ~3 ms/frame wait" is display pacing, and dev has it too.** + - On every build, the presenting thread spends about 2.0 ms sleeping and 0.4 ms runnable per + frame. That holds for dev and feat, on both backends. + - The sleep is in `queueBuffer`. There the render thread waits for the `BLASTBufferQueue` + mutex, which the app's binder thread holds across a synchronous `setTransactionState`. That + call is phase-locked to the 165 Hz display. + - The whole FCL fps gap vs dev is presenting-thread CPU: + - Magma: 3.25 vs 1.85 ms/frame; + - Espryt: 2.7 vs 2.05 ms/frame. +2. **FCL's Magma CPU gap is mostly non-draw verbs on the wire arm.** These are mechanisms dev's + frontend arm had and the wire arm lost: + - every `glClear` builds and destroys its own image view, render pass and framebuffer; + - every blit creates a descriptor pool; + - `glBufferSubData` takes a slow staged path; + - the retired-object garbage collection and command-buffer reset grow to match. + This explains about 1.0 of the 1.4 ms. rd12 Magma pays the clear and churn part too, about + 0.45 ms/frame. +3. **The (VAO, program) draw-packet table as sketched would never hit on rd12.** + - rd12 draws 1471 times a frame, with 1466 distinct (VAO, program) pairs per frame. A pair + repeats only across frames, and something outside the whitelist always runs between frames. + So a packet stamped with a context epoch hits 0.0 % of the time. + - What does hold on rd12 is the **draw-to-draw delta**: before 99.0 % of draws, the only calls + since the previous draw are `glBindVertexArray` and a float uniform. + - P15 therefore builds a draw-delta fast path: + - "nothing context-global moved since the last draw", checked once per draw against two + epochs (a client one and a server one); + - per-VAO and per-program slices that persist across frames. + It does not build a pair-keyed packet table. + +Stage order: 0 (parity audit + wire non-draw fast path), 0b (an FCL 1.21.5 trace fixture), 1 +(client draw-delta prototype), 2 (per-VAO / per-program slices), 3 (applier + backend +draw-delta), 4 (split arms, verify, default-on). + +## 1. Measurement rules for P15 + +- **FCL:** + - The primary metric is the presenting-thread CPU in ms/frame, which MobileGL's fps log already + prints. Report fps beside it, never alone. + - Above ~165 fps, frame time ≈ CPU + ~2.4 ms of display-coupled wait (section 2.1). So fps + ratios understate CPU ratios and are not linear in CPU saved. + - Take readings only from in-world steady windows. The probe waits for MC's `Time elapsed:` + line and then four fps-log windows of more than 150 frames each. +- **Trace (rd12, openra, and the FCL 1.21.5 fixture from stage 0b):** + - fps, best of 3. + - Per-layer CPU in ms/frame from `profile_p14.sh` / `layers.py`. Remember that rd12 carries + ~6.9 ms/frame of harness parse. +- **Method:** + - `FINISH=false`, pinned clocks, best of 3. + - Re-run any surprising result before believing it. + - Every probe needs proof it engaged: a marker line, a counter, or a knob that visibly changes + behaviour. + - Every optimisation needs a negative control that can go red. +- **Device hygiene:** + - One device job at a time, under the `devjob.sh` lock. + - Restore FCL's library, config and properties afterwards, md5-checked (`fcl_p14.sh restore`). + - Stop the anland desktop during runs. + - FCL's SIGRTMIN+2 start-up death is known (DEBTS). The probe relaunches up to 4 times. +- **Gates for each landing:** + - SSIM 16/16 (`matrix_p14.sh ssim`); + - the retrace CI; + - the run-ahead and syncval gates on any Magma change that touches barriers or pass structure; + - an FCL soak before default-on. + +## 2. Measurements behind the plan (2026-10-07) + +Tools are in the session scratchpad `p15/`, to be landed under `tools/device_bench/disagg/` +(open question 4): +- `fcl_probe.sh`: waits for in-world frames, then runs 4 s of ftrace (sched, kgsl cmdbatch, atrace + gfx) and 6 s of `simpleperf --trace-offcpu -p`; +- `offwait.py`: off-CPU time by open atrace slice and waker; +- `sfalign.py`: SurfaceFlinger transaction phase; +- `fclsplit.py` / `incl.py` / `drvcaller.py`: layer split, inclusive functions, and driver time + by caller; +- `P15Count.inl` (measurement-only patch): per-draw pair and gap statistics. + +### 2.1 Item 1: where FCL's wait is + +FCL MC 1.21.5, in world, view distance 2, monolith. The figures for the presenting thread +(`Thread-14`) come from the ftrace window. The off-CPU breakdown comes from the simpleperf window. + +| build | frame ms | CPU ms | sleep ms | runnable ms | biggest sleep | +|---|---|---|---|---|---| +| dev Magma (×2) | 4.27 / 4.46 | 1.77 | 2.05 / 2.07 | 0.35 / 0.41 | QueuePresentKHR > queueBuffer, woken by the app's binder thread | +| feat Magma (×2) | 5.67 / 5.79 | 3.43 | 1.98 / 2.10 | 0.44 / 0.47 | same | +| dev Espryt | 4.54 | 2.10 | 1.92 | 0.40 | eglSwapBuffers > queueBuffer, same waker | +| feat Espryt | 5.03 | 2.73 | 1.81 | 0.39 | same | + +- **The off-CPU stack is the same on all four builds:** + - The render thread: `BLASTBufferQueue::onFrameAvailable` → `std::mutex::lock`, 1.4-1.7 + ms/frame. A further 0.2-0.34 ms/frame goes to `Fence::waitForever` in `queueBuffer`. + - The lock holder is the app binder thread: `releaseBufferCallback` → + `acquireNextBufferLocked` → `Transaction::apply` → `BpSurfaceComposer::setTransactionState`, + synchronous and under the BBQ mutex. +- **Inside SurfaceFlinger, `setTransactionState` has a median of 3.4 ms** (p10-p90 2.7-3.8). It + returns **4.95 ms after SF's `flushTransactions`** (p10-p90 4.78-5.2). The flush period is 6.1 + ms (165 Hz, `NextFrameInterval 165_Hz`), with about one call per refresh. So the BBQ binder thread is + paced by the display. Above refresh rate, the render thread's present waits for it through the + mutex. +- **Other checks:** + - GPU busy is ~14 % (union of kgsl `adreno_cmdbatch_retired`). + - The swapchain is MAILBOX with 6 images, so present mode and image count are not the cause. +- **Consequence:** this is not a MobileGL defect and not a feat regression. Making the present + asynchronous would raise every build's FCL fps above 165, but only by producing frames SF drops. + It is not proposed as a perf item; open question 2 covers it. + +### 2.2 FCL CPU by function (render thread, on-CPU ms/frame, inclusive) + +**Magma monolith: feat 3.43 vs dev 1.77.** Repeat run: 3.25 vs 1.85 by the fps log. + +| item | feat | dev | note | +|---|---|---|---| +| glClear → ClearWireFramebuffer | 0.42-0.46 | ~0.05 (Clear + ClearAttachmentsOnActiveRenderPass) | per clear: RetireWireDrawPass, vkCreateImageView + vkCreateRenderPass + vkCreateFramebuffer, its own pass, retire. 6 clears/frame, ~70 µs each | +| glBufferSubData → WriteWireBuffer / StagedWireRangeCopy | 0.28 | ~0 | 11 calls/frame, ~25 µs each; check whether it also ends the pass | +| CollectWireObjects (vkDestroyFramebuffer / DescriptorPool / ImageView) | 0.18 | none | the GC of the objects above | +| glBlitNamedFramebuffer → BlitWireColorImage | 0.13 | 0.02 | vkCreateDescriptorPool per blit (0.08) | +| TransitionWireImage | 0.14 | small | | +| BeginCommandRecording (vkResetCommandBuffer) + EndCommandRecording | 0.24 | 0.08 | larger, fragmented command buffers | +| glDrawElements (whole draw) | 0.93 | 0.56 | +0.37: client validate/emit ~0.27; SetupWireDraw 0.55 vs dev SetupDraw 0.54 | +| Present | 0.37 | 0.25 | | +| driver layer total | 1.46 | 0.49 | almost all the create/destroy above; not cache pressure | + +**Espryt monolith: feat 2.73 vs dev 2.10.** The +0.62 splits as: +- client half +0.39; +- backend +0.09; +- driver +0.04; +- applier +0.035. + +The clear/blit/sub-data path costs about the same as dev's: clear 0.16 vs 0.13, blit 0.125 vs +0.11, sub-data 0.024 vs 0.007. So Espryt has no churn of this kind (at most 0.07 ms/frame). Its +gap is the draw path: glDrawElements 1.01 vs 0.60, which is +4.7 µs per draw over 87 draws. + +**rd12 Magma** (P14 `prof-fin` profiles, scaled to ms/frame): +- ClearWireFramebuffer ≈ 0.27, CollectWireObjects ≈ 0.18, BeginCommandRecording 0.12 (dev 0.02), + blit 0.09 (dev 0.06). +- So stage 0 also pays on rd12: about 0.4-0.5 ms/frame. +- Dev's deferred-clear lookups cost about 0.6 ms/frame on rd12: `VkClearManager::HasPendingClear` + is probed per draw, plus the key find. A naive port would be a regression there. Pending clears + must be resolved once per pass begin, never per draw. + +### 2.3 Draw-pair and gap statistics (P15Count, frontend entry level) + +What each column measures: +- **pairs:** distinct (VAO lifetime id, program lifetime id) pairs per frame. +- **same as prev:** the previous draw had the same pair. +- **pair epoch hit:** the pair has drawn before, and no non-whitelisted GL entry has run since its + last draw. This is the packet-table hit rate. +- **prev-draw gap clean:** no non-whitelisted entry since the previous draw, whichever pair it + had. This is the draw-delta hit rate. + +The whitelist is draws, `glBindVertexArray`, float uniforms and `glUseProgram`. "+buffer" adds +buffer binds and vertex-buffer binds. + +| workload | draws/frame | GL calls/frame | pairs/frame (ever) | same as prev | pair epoch hit, whitelist / +buffer | prev-draw gap clean, whitelist / +buffer | +|---|---|---|---|---|---|---| +| rd12 (steady window) | 1471 | 4856 | 1466 (1473) | 0.3 % | 0.0 % / 0.0 % | **99.0 %** / 99.0 % | +| FCL MC 1.21.5 (10 windows, both backends) | 88 | 873 | 15.1 (27) | 82.9 % | 0.0 % / 80.6 % | 0.0 % / **80.6 %** | +| openra | 29 | 593 | 2 (2) | 93 % | 0 % / 0 % | 0 % / 0 % | + +Engagement and controls: +- Allowing every category gives 99.9-100 %, which is the positive control. +- The FCL numbers are identical on Espryt and Magma, as expected for frontend counts. + +What sits between draws: +- **rd12:** per draw, just BindVertexArray + Uniform3fv + DrawElements. The ~1 % of other gaps + carry Enable/Disable, BlendFunc, BindTexture, Uniform1i and similar. +- **FCL:** + - `BindBuffer` comes before 98.9 % of draws and `BindVertexBuffer` before 88.5 %. Both are + VAO-scoped state: the element buffer and the vertex binding. + - 19 % of draws also follow `BindFramebuffer` / `Viewport` / `PolygonMode`. + - MC issues 96 `BindFramebuffer` calls per frame, mostly redundant. +- **openra:** every draw changes blend, texture, attrib pointers and sub-data. No per-draw + shortcut applies, and none is needed: openra is not where the gap is. + +## 3. Stages + +### Stage 0: parity audit, then the wire non-draw fast path (Magma first) + +**0.1 Parity audit (first step, judgement work).** +- **What to list:** every performance mechanism of dev's frontend arm that the record and wire + arms lost when P13 W4 moved monolith onto the wire arm and W6e / `de6ab167` deleted the old + code as dead. The split arm (the anland desktop) has always paid for them. +- **References:** `git show origin/dev:` and the pre-W6 feat tree `88a11b81`. +- **Seed inventory:** [PARITY-SEED.md](PARITY-SEED.md) lists the definitions in OLD but not NEW + for 17 backend files (regex-extracted, approximate). The audit's verdicts go in + `perf-p15/PARITY-AUDIT.md`. +- **For each mechanism, record:** + - what it did and its key; + - whether the wire/record arm has an equivalent, and where; + - whether FCL or rd12 shows its absence (section 2.2 numbers); + - porting cost and correctness risk; + - a verdict. + +Seed rows (to be verified by the audit): + +| mechanism (dev / 88a11b81) | wire/record arm today | shows in profiles | first estimate | +|---|---|---|---| +| `VkClearManager`: glClear queues a pending clear per attachment (texture + lifetime id + level/layer); the next pass on that attachment starts with LOAD_OP_CLEAR; materialised before reads, blits and copies | none: ClearWireFramebuffer runs a standalone pass with fresh objects | FCL 0.42, rd12 0.27 ms | **port, per pass begin**: dev's per-draw probe cost 0.6 ms on rd12 | +| `VkRenderPassManager::GetOrCreateRenderPass` hash cache of render pass + framebuffer, with OnPresent GC | partial: wire pass continuation, RenderPassEntry; framebuffers still created in SetupWireDraw (0.04) and in clears | FCL 0.04-0.19 (create) + 0.12 (destroy) | port a cache keyed on attachment lifetime ids + image epoch | +| shader blit to default FB (`TryBlitToDefaultFramebufferWithShader`), persistent blit resources (`InitializeBlitResources`) | BlitWireColorImage creates a descriptor pool per blit | FCL 0.08 + GC | persistent pool / set cache per (pipeline, source view) | +| `VkBufferManager::OnSubData` / `OnResidentSubData` (writes into resident or streamed slices) | WriteWireBuffer + StagedWireRangeCopy | FCL 0.28 | find out why it is ~25 µs per call; port the resident fast path; check for pass breaks | +| `TrySetupDrawFastPath` / `SetupDrawSnapshot` (4 snapshots keyed on program lifetime id) | P14 memos (pass continuation, unit samplers, pipeline/vertex/index bind skips) | SetupWireDraw ≈ dev SetupDraw on FCL | audit for leftovers only | +| `UniformManager` `FastRebindMemo`, `SampledBindingsUnchanged` | P14 descriptor-set rebind, walk-skip | small | audit | +| `VertexInputStateFactory` per-VAO memos (`VaoBackendMemos`) | vertex-input layout kept on its record (P14) | small | audit | +| `VkTextureManager` `SyncedTextureMemoEntry` / `DrawSyncScope` | P14 direct-mapped texture fronts | small | audit | +| Espryt `StateBackendObjectRegistry` (pointer-keyed twins), `SyncCurrentProgram`, `SyncVaoAttributeBuffersByHandle`, `BackendFramebufferObject::SyncToBackend` | twins keyed on applier serials (P14) | Espryt backend +0.09 on FCL | audit; low priority | + +**0.2 Port, in order of measured value:** + +1. **Deferred clears on the wire arm.** + - Key: image lifetime id + level/layer range + aspect, never a VkImage or a GL name. + - A clear records a pending entry. The next wire pass that targets the image takes it as + LOAD_OP_CLEAR. + - A clear inside an open pass on the same attachments becomes `vkCmdClearAttachments`, without + ending the pass. + - Materialise pending clears before any read: sampling, readback, blit source, copy, mipmap + generation, shared-image export and present. The cost is one check per pass begin and one + per read verb; no per-draw probe. + - The clear-mask cases (partial colour mask, stencil write mask, scissor) keep the current path. +2. **A render-pass + framebuffer cache** for wire passes and clears. + - Keyed on attachment lifetime ids and the image epoch. + - Retired when an attachment's lifetime ends: the store destroy epoch. +3. **Blit descriptor reuse:** a persistent pool, sets keyed on (source view lifetime id, sampler). +4. **The `glBufferSubData` path:** find the ~25 µs per call, then port the resident-slice write + and check whether it ends the pass. +5. **Command-buffer churn:** once 1-4 land, re-measure `vkResetCommandBuffer` / + `EndCommandRecording`. They should fall with the pass count. + +**Espryt:** no churn of this kind measured (≤0.07 ms/frame on FCL). The audit should still cover +Espryt's deleted mechanisms. Port only those that show in profiles. + +**Correctness:** +- Deferred clears are the e8e1521 class of risk: a read that skips materialisation renders stale + content. +- Tests: + - an integration scenario per read path (sample after clear; readback; blit source; copy; mipmap; + present), with negative controls that drop materialisation for one read kind and must go red; + - SSIM 16/16; + - the syncval gates (a LOAD_OP_CLEAR changes the pass's first access). +- Runtime knob, e.g. `MOBILEGL_MAGMA_DEFERRED_CLEARS=0`, kept for one release. + +**Expected gain:** +- FCL Magma: 0.8-1.0 of the 1.4 ms presenting-thread CPU gap. Medium-high confidence: the items + are measured, the removal fraction is estimated. +- rd12 Magma: 0.3-0.5 ms/frame, about +3-5 % fps. Medium confidence. + +### Stage 0b: an FCL MC 1.21.5 trace fixture + +- **Why:** rd12 (MC 1.21.4) never exercised the per-frame clear, blit and BufferSubData pattern + that FCL's MC 1.21.5 runs. The trace matrix must cover what FCL runs. +- **Capture:** + - with the on-device FCL capture wrapper (`tools/trace_replay/skills/trace-fixture-authoring-on-android-fcl`); + - the same world and view distance as the FCL bench, from the in-world steady state; + - trace mode forces 854×480. +- **Trim:** a multi-frame window, like rd12 (`gltrim -f`, about 250 frames), so the benchmark + measures steady frames. The golden is the final frame. +- **Golden:** + - follow `trace-fixture-authoring/SKILL.md`: generate it on a real GPU (Windows `glretrace` on + the RTX 3080, or a known-good Android replay); never on llvmpipe for anything heavy; + - check its content against a live-run screenshot; + - remove alpha; `repack --brotli`; register with `ci: false` like rd12. +- **Use:** + - add to `matrix_p14.sh` `wl_args`; + - A/B every stage on rd12, openra and this fixture; + - check that P15Count on the fixture reproduces section 2.3's FCL row (88 draws, 15 pairs); if + not, the fixture does not represent FCL. +- **Order:** best done before stage 0.2 lands, so stage 0 can be A/B'd on a trace as well as on FCL. + +### Stage 1: client draw-delta prototype (Espryt monolith) + +This is the client half of the old sketch, re-keyed on what section 2.3 shows. + +- **Client context epoch:** + - one `Uint64` per context; + - bumped by every GL/EGL entry except a whitelist; + - share-group-scoped objects (textures, buffers, programs, renderbuffers, samplers) bump a + share-group epoch that every context in the group checks. +- **Whitelist classes,** decided per entry in a checked-in table (section 4.4): + - **free:** draws, `glBindVertexArray`, float `glUniform*` / `glProgramUniform*`, `glUseProgram` + (the program is a key, not context state); + - **VAO-scoped:** `glBindVertexBuffer`, `glVertexAttrib*Pointer`, `glEnable/DisableVertexAttribArray`, + `glBindBuffer(GL_ELEMENT_ARRAY_BUFFER)` and the VAO DSA calls. These bump the bound VAO's own + versions, which the per-VAO slice checks. `glBindBuffer(GL_ARRAY_BUFFER)` changes nothing a draw + reads; + - **program-scoped:** integer uniforms and block bindings. These bump the program's backend-state + / block-binding version; + - **everything else** bumps the epoch. + - **Redundant binds** (same object, same value) may skip the bump only where the entry already + compares. MC issues 96 BindFramebuffer calls per frame and 19 % of draws follow one. +- **At the validate point:** + - if (epoch, share-group epoch, context serial, slot reset epoch) are unchanged since the + previous draw, skip `Tracker::Update`'s context-global shutters, the residual copy and the + context-value emit; + - run only the VAO-derived shutters (through the per-VAO slice once stage 2 lands), the + program-derived ones, and the uniform span; + - also skip `ReadDrawBindings` / `MarkGpuWritesForDraw` when no persistent map is live and no + image or SSBO write is bound. The persistent-map re-sync stays a per-draw input otherwise. +- **Kill switch:** `MOBILEGL_PIPE_DRAW_DELTA=0`, default off until stage 4. +- **Measure** on rd12, FCL and the stage 0b fixture against P14's per-item numbers: Tracker::Update + 0.12, CopyField 0.10, validate self 0.10, ReadDrawBindings 0.06, MarkGpuWritesForDraw 0.05, + thread-local/atomic 0.07 ms/frame. +- **Expected:** 0.3-0.5 ms/frame on rd12 Espryt monolith (91 → ~94-96 fps), and 0.1-0.25 ms on FCL + Espryt. Medium confidence. +- **Decide** on stages 2-3 from this. + +### Stage 2: per-VAO and per-program slices + +- **Per-VAO slice.** This is what the old "packet" becomes. It extends `VaoMemo`, which is already + keyed on VAO lifetime id + slot reset epoch. + - It holds the vertex-elements handle + published config version, the buffer set + index + handles + hash, the stored-on-server mirrors, and the backend's per-VAO answers (stage 3). + - It is reached by an index stored on the frontend VAO object, not a hash probe. + - rd12 rebinds a different VAO for every draw, but each VAO is seen every frame and its config + hardly moves. So the slice turns EmitVertexElements (0.13) / EmitVertexBuffers (0.07) into + a version compare plus a handle reuse. +- **Per-program slice:** the shader CSO handle, link and backend-state versions, the + uniform-span layout and the UBO block size. +- **Expected:** + - rd12: 0.1-0.2 ms on the client half; + - FCL: small, because only 27 pairs exist. But each FCL draw also changes the vertex buffer + binding, which is now a VAO-version bump that the slice re-derives cheaply. + +### Stage 3: applier and backend draw-delta + +- **Server epoch, generated from `PipeCalls.def`:** + - The applier bumps an epoch for every applied record except a whitelist: draw, + bind_vertex_elements, the uniform span, and the per-VAO / per-program record kinds. + - A constexpr table is generated from the record opcode list. A test fails if a record kind is + missing from it. This is the server-side twin of section 4.4. +- **Backend-internal mutations also bump a backend epoch.** These are: + - texture `Serial` / `ParamsSerial` / `PendingUploads`; + - twin force-resync flags; + - the sampler CSO serial; + - shared-image state; + - GPU-written marks; + - on Magma: the image epoch, the resource-erase epoch, an image layout leaving GENERAL, and the + store-destroy epoch. + The rule from the content-version lesson applies: every place that changes a twin's synced + state bumps it, including `glGenerateMipmap` growing mips. P14's Magma memos already check most of these. +- **When both epochs are unchanged since the previous draw,** the backend skips the + context-global walks: + - Espryt: `SyncNeccessaryTextures` / `SyncNeccessaryBuffers`, `AdoptUnchangedBufferSets`, + `BindCurrentProgramWithResources`; + - Magma: whatever of SetupWireDraw is not VAO- or program-derived. +- **Expected:** + - rd12 Espryt backend: 0.15-0.3 ms of the +0.45; + - rd12 Magma: 0.1-0.3 ms (its backend is already below dev); + - driver: 0-0.3 ms, if the cache-pressure reading is right. Measure L1D refills before and + after with `stat_p14.sh`. + +### Stage 4: split arms, verify, default-on + +- **Inproc / spawn / tcp:** + - the client epoch and the per-VAO/program client slices live in the client; + - the server epoch and the backend slices live where the applier runs: the same thread on + monolith, the apply thread on inproc, the server process on spawn and tcp. + - Nothing new crosses the wire. + - Under run-ahead, client validity depends only on client epochs. The server computes its own + epoch from the records it applies, so the two cannot disagree about what moved. +- **Default-on** after: + - CI and SSIM green on all arms; + - verify lanes green with the shadow check (section 4.5); + - an FCL soak on both backends. + Keep the knob for one release. +- **Expected inproc:** + - Espryt rd12 is client-bound, so stages 1-2 give 112 → ~116-119, above dev's 115. + - Magma inproc is apply-bound, so it gains from stages 0 and 3. + +## 4. Design details + +### 4.1 Scope and non-goals + +**In scope:** +- the stage 0 wire non-draw fast path (Magma); +- the client and server draw-delta epochs; +- per-VAO and per-program slices; +- the backend draw-delta. + +**Non-goals:** +- a (VAO, program) pair table: 0 % hit on rd12, and FCL's hits are covered by the draw delta; +- record batching (measured and dropped in P14); +- protocol changes; +- asynchronous present (open question 2); +- native TLS (open question 3); +- openra, whose gaps change state on every draw. + +### 4.2 Per-layer state for one draw on the fast path + +| layer | read per draw on a hit | kept in | +|---|---|---| +| tracker | client epoch + share-group epoch + context serial + slot reset epoch (4 compares); VAO identity (lifetime id + config + attribute generation); program identity (lifetime id + link + backend-state + block-binding versions) | context, per-VAO slice, per-program slice | +| emitter | per-VAO slice: elements / buffers / index handles + versions; uniform span; draw record | per-VAO slice | +| applier | server epoch; vertex-elements record by slot + generation (bind by reference) | applier context | +| backend | backend epoch; Espryt VAO twin + program twin pointers; Magma vertex-input id, pipeline handle, descriptor-set layout, pass-compatibility id | per-VAO / per-program backend slices | + +### 4.3 Keys + +- Use monotonic lifetime ids only: VAO, program, buffer and texture lifetime ids; slot + + generation on the server side. Never a GL name or a pointer. This is the ABA rule, broken three + times so far (sampler, texture, program). +- Slices are invalidated by: + - the context serial; + - `MGPipeSlots().ResetEpoch()`; + - the store-destroy epoch. +- A slice index stored on a frontend object is valid only together with the object's lifetime id. + +### 4.4 Invalidation sources, and how to prove none is missed + +**Client entry-point table** (`MG_Pipe/DrawDeltaClass.def`, new): +- one row per exported GL entry, giving its class (free / VAO-scoped / program-scoped / + share-group / context); +- `DECLARE_GL_FUNCTION_END*` passes `#name` to `GLStreamScope`, which looks up the class at + compile time; +- the hand-written `MOBILEGL_GL_API` definitions (~170 in `Definitions.cpp`, mostly EXT/ARB + aliases plus `glFinish`/`glFlush`) and the EGL entries use the same scope helper. + +**Completeness tests:** +1. A CTest scans `Definitions.cpp` and the EGL exports. It fails if: + - any exported entry is missing from the table; + - a table row names a non-exported entry; + - any `MOBILEGL_GL_API` body neither constructs the scope nor forwards to a macro entry. +2. A runtime self-test: in a debug build, every entry asserts it consulted the table. A fuzz + driver walks all entries. +3. **Negative controls:** a test-only knob `MOBILEGL_PIPE_DRAW_DELTA_DROP=` removes one + entry's bump. Run for a representative entry per class: + - `glBindTexture`; + - `glTexParameteri`; + - `glBindFramebuffer`; + - `glEnable`; + - `glBindBufferBase`; + - `glUniform1i` (program-scoped); + - `glBindVertexBuffer` (VAO-scoped); + - an EXT alias; + - an EGL entry. + Each must turn the shadow check (4.5) and a scenario red. + +**Mutations that bypass GL entries:** +- persistent-mapped writes stay a per-draw input; +- async link completion (link version); +- `eglMakeCurrent` (context serial); +- another context in the share group (share-group epoch); +- server-side backend mutations (backend epoch, 3.3). + +**Server side:** +- the generated table from `PipeCalls.def`, with the same three tests; +- for the backend epoch, a list of every writer of the synced-state fields, enforced by making + those fields private behind a setter that bumps. This is the same chokepoint design as the + Magma layout setter in the whole-draw fast-path note. + +### 4.5 The verify comparator + +`MOBILEGL_PIPE_VERIFY` compares the residual block with a live snapshot at the verb. + +Under verify: +- the fast path is armed but always takes the slow path; +- it asserts that the fast path would have hit with byte-identical residual, record and + vertex-element output (a **shadow check**). +So every verify lane cross-checks epoch coverage for free, and the negative controls in 4.4 +redden it. + +A non-verify build has a sampled shadow mode, `MOBILEGL_PIPE_DRAW_DELTA_SHADOW=N`, which checks +every Nth hit. It is for FCL soaks. + +POISON stamps (trace builds): on a hit, the stamp set recorded at the last slow draw is applied in +one bulk write. Freshness checks then keep working. + +### 4.6 Tests + +- Unit tests for the epoch classes; the table tests (4.4). +- Integration scenarios: + - draw-delta hit/miss per class; + - share-group mutation from a second context; + - persistent-map write between two hit draws; + - async link completing between draws; + - VAO rebind with the same lifetime id after delete + gen (ABA); + - context switch; + - slot reset. + Each runs with a negative control. +- Stage 0: deferred-clear read-path scenarios with drop-materialisation controls; syncval. +- All: SSIM 16/16, the retrace CI, FCL soak. + +## 5. Expected gains + +ms/frame of CPU on the bound thread. + +| stage | rd12 Espryt mono | rd12 Magma mono | FCL Espryt mono (presenting CPU) | FCL Magma mono (presenting CPU) | confidence | +|---|---|---|---|---|---| +| 0 wire non-draw | 0-0.07 | 0.3-0.5 | ≤0.07 | 0.8-1.0 | medium-high (FCL), medium (rd12) | +| 1 client draw-delta | 0.3-0.5 | 0.3-0.5 | 0.1-0.25 | 0.1-0.25 | medium | +| 2 slices | 0.1-0.2 | 0.1-0.2 | ~0.05 | ~0.05 | low-medium | +| 3 backend draw-delta | 0.15-0.3 (+ driver 0-0.3) | 0.1-0.3 | ~0.05 | 0.05-0.1 | low-medium; driver unproven | +| total | 0.55-1.0 (+driver), gap 2.5 | 0.8-1.5, gap 0.76 | 0.25-0.4, gap 0.62 | 1.0-1.4, gap 1.4 | | + +What the totals mean: +- **rd12 Espryt monolith:** 91 → roughly 96-101 fps. Short of dev's 115 unless the driver term + moves. +- **rd12 Magma monolith:** 84 → about dev's 87 or above. +- **FCL Magma:** CPU gap mostly closed. Espryt about halved. +- **Inproc:** above dev on Espryt rd12. Magma inproc gains stages 0 + 3. +- **Revision:** these are below the old sketch's 1.0-1.6 ms because the pair table does not hit on + rd12. + +## 6. Risks + +- **A missed epoch bump renders stale state.** This is the dominant risk. Mitigations: + - the generated tables and their completeness tests; + - the verify shadow check; + - per-class negative controls; + - SSIM and the retrace CI; + - default-off until stage 4. +- **Deferred clears skipping a materialisation.** Same class as the reverted e8e1521. Mitigations: + per-read-path scenarios with drop controls, syncval, SSIM. +- **ABA on slice keys.** Mitigations: lifetime ids, slot + generation, the reset and destroy + epochs. +- **Share groups and multiple sessions:** per-context epochs miss a mutation from another context + in the group. Mitigation: the share-group epoch. The multi-session server hazards also apply on + the split arms. +- **Gains below estimate:** the stage 1 prototype is the go/no-go for stages 2-3. +- **FCL fixture fidelity:** stage 0b's P15Count check. + +## 7. Open questions for the user + +1. **The FCL metric.** Do you agree to make presenting-thread CPU ms/frame the primary FCL metric? + fps above refresh rate is display-coupled (section 2.1). +2. **Asynchronous present.** Presenting from a separate thread would cut the ~2 ms/frame BBQ wait + from the render thread on every build, dev included. It would raise FCL fps above refresh, but + the extra frames are ones SF drops; at or below refresh nothing changes. Leave it out (my + recommendation), or plan it as a separate item? +3. **Native TLS** (minSdk 29, +1.7 % Espryt): still your call from P14. +4. **The P15 measurement tools.** May I commit `fcl_probe.sh`, `offwait.py`, `sfalign.py`, + `fclsplit.py`, `drvcaller.py` and `incl.py` under `tools/device_bench/disagg/` with the plan? + They are tooling, not product code. This assignment was docs-only, so they are not in this + commit. +5. **Magma deferred clears, Espryt side:** the wire arm's Espryt clears cost the same as dev's, so + no port is planned there. Confirm.