mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +09:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37b20f2fca | ||
|
|
ee908aab46 | ||
|
|
a4ccf3a837 |
@@ -15,6 +15,7 @@
|
|||||||
#include <MG_Impl/GLImpl/Texture/ProxyTexture.h>
|
#include <MG_Impl/GLImpl/Texture/ProxyTexture.h>
|
||||||
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
|
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
|
||||||
#include <MG_Impl/GLImpl/Sync/GL_Sync.h>
|
#include <MG_Impl/GLImpl/Sync/GL_Sync.h>
|
||||||
|
#include <MG_Impl/GLImpl/Query/GL_Query.h>
|
||||||
#include <MG_Util/Async/ShaderCompilePool.h>
|
#include <MG_Util/Async/ShaderCompilePool.h>
|
||||||
#include <MG_Util/ShaderTranspiler/ShaderCompiler.h>
|
#include <MG_Util/ShaderTranspiler/ShaderCompiler.h>
|
||||||
|
|
||||||
@@ -51,6 +52,11 @@ namespace MobileGL {
|
|||||||
// before a re-initialized library could pair them with the wrong
|
// before a re-initialized library could pair them with the wrong
|
||||||
// backend's DeleteSync).
|
// backend's DeleteSync).
|
||||||
MG_Impl::GLImpl::DestroyAllSyncObjects();
|
MG_Impl::GLImpl::DestroyAllSyncObjects();
|
||||||
|
// Queries die with their contexts for the same reason, and their registry
|
||||||
|
// is the same shape of process-global map: drain it here too, while the
|
||||||
|
// function table can still pair each backend handle with the backend that
|
||||||
|
// minted it.
|
||||||
|
MG_Impl::GLImpl::DestroyAllQueryObjects();
|
||||||
MG_Backend::pActiveBackendObject.reset();
|
MG_Backend::pActiveBackendObject.reset();
|
||||||
MG_State::pGLContext.reset();
|
MG_State::pGLContext.reset();
|
||||||
MG_State::pEGLContext.reset();
|
MG_State::pEGLContext.reset();
|
||||||
|
|||||||
@@ -1431,6 +1431,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
g_fboSyncedSlotVersions[SizeT(target)] = slotVersion;
|
g_fboSyncedSlotVersions[SizeT(target)] = slotVersion;
|
||||||
g_fboSyncedObjectVersions[SizeT(target)] = objectVersion;
|
g_fboSyncedObjectVersions[SizeT(target)] = objectVersion;
|
||||||
g_fboSyncedObjects[SizeT(target)] = fbo;
|
g_fboSyncedObjects[SizeT(target)] = fbo;
|
||||||
|
g_fboSyncedBackendIdGenerations[SizeT(target)] = g_attachmentBackendIdGeneration;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SyncCurrentFBO() {
|
void SyncCurrentFBO() {
|
||||||
@@ -1461,9 +1462,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
const Uint16 slotVersion = slot.GetVersion();
|
const Uint16 slotVersion = slot.GetVersion();
|
||||||
const Uint16 objectVersion = currentFBO ? currentFBO->GetObjectVersion() : 0;
|
const Uint16 objectVersion = currentFBO ? currentFBO->GetObjectVersion() : 0;
|
||||||
auto* currentPtr = currentFBO.get();
|
auto* currentPtr = currentFBO.get();
|
||||||
|
// The backend-id generation joins the triple: a backend texture re-mint
|
||||||
|
// (RecreateBackendTexture) moves no frontend version, so without it the
|
||||||
|
// early-out would keep the driver FBO on the deleted texture name.
|
||||||
if (slotVersion == g_fboSyncedSlotVersions[SizeT(target)] &&
|
if (slotVersion == g_fboSyncedSlotVersions[SizeT(target)] &&
|
||||||
objectVersion == g_fboSyncedObjectVersions[SizeT(target)] &&
|
objectVersion == g_fboSyncedObjectVersions[SizeT(target)] &&
|
||||||
currentPtr == g_fboSyncedObjects[SizeT(target)]) {
|
currentPtr == g_fboSyncedObjects[SizeT(target)] &&
|
||||||
|
g_fboSyncedBackendIdGenerations[SizeT(target)] == g_attachmentBackendIdGeneration) {
|
||||||
lastUpdatedFBO = currentPtr;
|
lastUpdatedFBO = currentPtr;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -2129,6 +2134,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
static Bool g_broadcastMemoValid = false;
|
static Bool g_broadcastMemoValid = false;
|
||||||
static Uint g_broadcastMemoCount = 1;
|
static Uint g_broadcastMemoCount = 1;
|
||||||
|
|
||||||
|
// The identity+version key above is only monotonic WITHIN one GLContext: a
|
||||||
|
// library teardown + re-init frees every FramebufferObject and restarts the
|
||||||
|
// draw slot's counter at zero, so a recycled FBO address with coinciding
|
||||||
|
// fresh versions would false-hit. Cleared at the same boundaries as the
|
||||||
|
// structurally identical SyncCurrentFBO trio (InvalidateFramebufferBindingCache).
|
||||||
|
void InvalidateBroadcastMemo() {
|
||||||
|
g_broadcastMemoValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
void SyncCurrentProgram(const SharedPtr<MG_State::GLState::ProgramObject>& currentProgram) {
|
void SyncCurrentProgram(const SharedPtr<MG_State::GLState::ProgramObject>& currentProgram) {
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
@@ -2312,6 +2326,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
FramebufferImpl::g_fboSyncedSlotVersions[(SizeT)target] = slot.GetVersion();
|
FramebufferImpl::g_fboSyncedSlotVersions[(SizeT)target] = slot.GetVersion();
|
||||||
FramebufferImpl::g_fboSyncedObjectVersions[(SizeT)target] = fbo ? fbo->GetObjectVersion() : 0;
|
FramebufferImpl::g_fboSyncedObjectVersions[(SizeT)target] = fbo ? fbo->GetObjectVersion() : 0;
|
||||||
FramebufferImpl::g_fboSyncedObjects[(SizeT)target] = fbo.get();
|
FramebufferImpl::g_fboSyncedObjects[(SizeT)target] = fbo.get();
|
||||||
|
FramebufferImpl::g_fboSyncedBackendIdGenerations[(SizeT)target] =
|
||||||
|
FramebufferImpl::g_attachmentBackendIdGeneration;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void BindCurrentProgramWithResources(
|
static void BindCurrentProgramWithResources(
|
||||||
@@ -3950,8 +3966,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
Bool resolved = g_GLESFuncs.glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
|
Bool resolved = g_GLESFuncs.glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
|
||||||
if (resolved) {
|
if (resolved) {
|
||||||
DrainBlitErrors();
|
DrainBlitErrors();
|
||||||
|
// A blit is scissored like a draw (the replicate path's guard documents the
|
||||||
|
// same rule): the application's box would clip this resolve into the
|
||||||
|
// scratch, and the second blit would then copy never-written scratch texels
|
||||||
|
// into the destination - silently, since scissor clipping raises no GL
|
||||||
|
// error. Disable for the staging blit only; the caller-visible blit below
|
||||||
|
// keeps the blit's native scissor semantics. Tracked via the render-state
|
||||||
|
// shadow, exactly like ScopedScissorDisable.
|
||||||
|
const Bool scissorWasEnabled =
|
||||||
|
(RenderStateImpl::g_syncedRenderStateParameters.ScissorTestEnabledMask & 1u) != 0;
|
||||||
|
if (scissorWasEnabled) g_GLESFuncs.glDisable(GL_SCISSOR_TEST);
|
||||||
g_GLESFuncs.glBlitFramebuffer(left, bottom, right, top, 0, 0, width, height, GL_COLOR_BUFFER_BIT,
|
g_GLESFuncs.glBlitFramebuffer(left, bottom, right, top, 0, 0, width, height, GL_COLOR_BUFFER_BIT,
|
||||||
GL_NEAREST);
|
GL_NEAREST);
|
||||||
|
if (scissorWasEnabled) g_GLESFuncs.glEnable(GL_SCISSOR_TEST);
|
||||||
resolved = g_GLESFuncs.glGetError() == GL_NO_ERROR;
|
resolved = g_GLESFuncs.glGetError() == GL_NO_ERROR;
|
||||||
}
|
}
|
||||||
if (resolved) {
|
if (resolved) {
|
||||||
@@ -4097,13 +4124,25 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// The per-draw-buffer colour masks are not covered by the non-indexed
|
// The per-draw-buffer colour masks are not covered by the non-indexed
|
||||||
// glColorMask above.
|
// glColorMask above. Restore what the SYNC actually pushed, not the raw
|
||||||
for (Uint index = 0; index < MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS; ++index) {
|
// application masks: a widened attachment's alpha write is forced off by
|
||||||
const BoolVec4& colorMask = RenderStateImpl::g_syncedRenderStateParameters.ColorMasks[index];
|
// SyncRenderState and memoized in g_syncedColorMaskAlphaWidenMask, and the
|
||||||
if (g_GLESFuncs.glColorMaski) {
|
// next sync early-outs on an unchanged version - restoring the undoctored
|
||||||
g_GLESFuncs.glColorMaski(index, colorMask.x() ? GL_TRUE : GL_FALSE,
|
// mask here would leave alpha writes enabled on the widened buffer with
|
||||||
colorMask.y() ? GL_TRUE : GL_FALSE, colorMask.z() ? GL_TRUE : GL_FALSE,
|
// nothing left to repair it. Same three-way pointer fallback as
|
||||||
colorMask.w() ? GL_TRUE : GL_FALSE);
|
// SyncRenderState's push: gating on the core name alone left EXT/OES-only
|
||||||
|
// devices holding buffer 0's mask broadcast across every buffer.
|
||||||
|
const auto colorMaskiFn = g_GLESFuncs.glColorMaski ? g_GLESFuncs.glColorMaski
|
||||||
|
: g_GLESFuncs.glColorMaskiEXT ? g_GLESFuncs.glColorMaskiEXT
|
||||||
|
: g_GLESFuncs.glColorMaskiOES;
|
||||||
|
if (colorMaskiFn) {
|
||||||
|
for (Uint index = 0; index < MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS; ++index) {
|
||||||
|
BoolVec4 colorMask = RenderStateImpl::g_syncedRenderStateParameters.ColorMasks[index];
|
||||||
|
if (index < 32 && (RenderStateImpl::g_syncedColorMaskAlphaWidenMask & (1u << index)) != 0) {
|
||||||
|
colorMask.w() = false;
|
||||||
|
}
|
||||||
|
colorMaskiFn(index, colorMask.x() ? GL_TRUE : GL_FALSE, colorMask.y() ? GL_TRUE : GL_FALSE,
|
||||||
|
colorMask.z() ? GL_TRUE : GL_FALSE, colorMask.w() ? GL_TRUE : GL_FALSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (m_pausedTransformFeedback && g_GLESFuncs.glResumeTransformFeedback) {
|
if (m_pausedTransformFeedback && g_GLESFuncs.glResumeTransformFeedback) {
|
||||||
@@ -8304,6 +8343,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
// Conservatively drop the redundant-glUseProgram guard: re-issuing one bind
|
// Conservatively drop the redundant-glUseProgram guard: re-issuing one bind
|
||||||
// after a MakeCurrent is cheaper than trusting a possibly-reset context.
|
// after a MakeCurrent is cheaper than trusting a possibly-reset context.
|
||||||
PrgramImpl::g_lastUsedBackendProgramId = 0;
|
PrgramImpl::g_lastUsedBackendProgramId = 0;
|
||||||
|
// The GLContext becoming current may be a fresh one whose slot versions
|
||||||
|
// restarted at zero; the broadcast memo's key is only monotonic within one.
|
||||||
|
PrgramImpl::InvalidateBroadcastMemo();
|
||||||
BufferImpl::InvalidateIndexedBufferBindingCache();
|
BufferImpl::InvalidateIndexedBufferBindingCache();
|
||||||
BufferImpl::InvalidatePixelBufferBindingCaches();
|
BufferImpl::InvalidatePixelBufferBindingCaches();
|
||||||
FramebufferImpl::InvalidateFramebufferBindingCache();
|
FramebufferImpl::InvalidateFramebufferBindingCache();
|
||||||
@@ -8832,6 +8874,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
FramebufferImpl::InvalidateFramebufferBindingCache();
|
FramebufferImpl::InvalidateFramebufferBindingCache();
|
||||||
VertexArrayImpl::InvalidateVAOBindingCache();
|
VertexArrayImpl::InvalidateVAOBindingCache();
|
||||||
PixelStoreImpl::InvalidatePackStateCache();
|
PixelStoreImpl::InvalidatePackStateCache();
|
||||||
|
PrgramImpl::InvalidateBroadcastMemo();
|
||||||
// Texture ids belong to the dying context; wrappers destroyed later must
|
// Texture ids belong to the dying context; wrappers destroyed later must
|
||||||
// not glDeleteTextures a recycled name in a successor context.
|
// not glDeleteTextures a recycled name in a successor context.
|
||||||
++g_backendContextGeneration;
|
++g_backendContextGeneration;
|
||||||
|
|||||||
@@ -577,6 +577,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
// immutable storage, and any prior mutable store is replaced anyway.
|
// immutable storage, and any prior mutable store is replaced anyway.
|
||||||
if (resource->id != 0) {
|
if (resource->id != 0) {
|
||||||
NoteBufferIdDeleted(resource->id);
|
NoteBufferIdDeleted(resource->id);
|
||||||
|
// Driver VAOs may have this id baked into attribute/element bindings
|
||||||
|
// keyed on frontend versions this re-mint does not move.
|
||||||
|
++g_bufferBackendIdGeneration;
|
||||||
g_GLESFuncs.glDeleteBuffers(1, &resource->id);
|
g_GLESFuncs.glDeleteBuffers(1, &resource->id);
|
||||||
resource->id = 0;
|
resource->id = 0;
|
||||||
resource->immutableStorage = false;
|
resource->immutableStorage = false;
|
||||||
@@ -833,6 +836,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
g_bufferMutationEpoch.fetch_add(1, std::memory_order_release);
|
g_bufferMutationEpoch.fetch_add(1, std::memory_order_release);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See the declaration: re-mints of a live resource's driver id. Written only on
|
||||||
|
// the context thread (both re-mint sites run there), read only by the VAO sync.
|
||||||
|
Uint64 g_bufferBackendIdGeneration = 0;
|
||||||
|
|
||||||
void RegisterBufferBackendOps() {
|
void RegisterBufferBackendOps() {
|
||||||
MG_State::GLState::SetBufferBackendOps(&g_glesBufferBackendOps);
|
MG_State::GLState::SetBufferBackendOps(&g_glesBufferBackendOps);
|
||||||
// Frontend writes issued while ops were unregistered advanced change
|
// Frontend writes issued while ops were unregistered advanced change
|
||||||
@@ -960,6 +967,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
// here, on the thread that can, and the id is re-minted below.
|
// here, on the thread that can, and the id is re-minted below.
|
||||||
if (resource->immutableStorage && !resource->persistentMapped && resource->id != 0) {
|
if (resource->immutableStorage && !resource->persistentMapped && resource->id != 0) {
|
||||||
NoteBufferIdDeleted(resource->id);
|
NoteBufferIdDeleted(resource->id);
|
||||||
|
// Same as the persistent-map re-mint: the dying id may be baked into
|
||||||
|
// driver VAO bindings whose frontend versions do not move for this.
|
||||||
|
++g_bufferBackendIdGeneration;
|
||||||
g_GLESFuncs.glDeleteBuffers(1, &resource->id);
|
g_GLESFuncs.glDeleteBuffers(1, &resource->id);
|
||||||
resource->id = 0;
|
resource->id = 0;
|
||||||
resource->immutableStorage = false;
|
resource->immutableStorage = false;
|
||||||
@@ -1640,8 +1650,24 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
// PrepareForDraw's BindCurrentVAO establishes the draw binding regardless.
|
// PrepareForDraw's BindCurrentVAO establishes the draw binding regardless.
|
||||||
const Uint32 currentConfigVersion = stateVAOObject->GetConfigVersion();
|
const Uint32 currentConfigVersion = stateVAOObject->GetConfigVersion();
|
||||||
const Uint16 currentIndexBufferVersion = stateVAOObject->GetIndexBufferBindingSlot().GetVersion();
|
const Uint16 currentIndexBufferVersion = stateVAOObject->GetIndexBufferBindingSlot().GetVersion();
|
||||||
const Bool attributesDirty = !m_hasSyncedConfigVersion || m_syncedConfigVersion != currentConfigVersion;
|
// A live buffer's driver id was re-minted since this twin's last emit
|
||||||
const Bool indexBufferDirty = currentIndexBufferVersion != m_syncedIndexBufferVersion;
|
// (persistent-map adoption / immutable-store retire): every baked binding may
|
||||||
|
// hold the dead id while every frontend version still matches, so force a
|
||||||
|
// full re-emit. Read once; each buffer re-mints at most once per walk (its
|
||||||
|
// first EnsureBufferResource this draw), before its id is baked, so stamping
|
||||||
|
// the entry value at the end is exact - and a stale stamp only costs one
|
||||||
|
// extra full emit.
|
||||||
|
const Uint64 currentBufferIdGeneration = BufferImpl::g_bufferBackendIdGeneration;
|
||||||
|
const Bool bufferIdsRemitted = m_syncedBufferIdGeneration != currentBufferIdGeneration;
|
||||||
|
const Bool attributesDirty =
|
||||||
|
bufferIdsRemitted || !m_hasSyncedConfigVersion || m_syncedConfigVersion != currentConfigVersion;
|
||||||
|
// Identity joins the version compare: the slot version is a wrapping Uint16,
|
||||||
|
// so a wrapped-back count with a different buffer bound must still read dirty.
|
||||||
|
const MG_State::GLState::BufferObject* currentIndexBufferObject =
|
||||||
|
stateVAOObject->GetIndexBufferBindingSlot().GetBoundObject().get();
|
||||||
|
const Bool indexBufferDirty = bufferIdsRemitted ||
|
||||||
|
currentIndexBufferVersion != m_syncedIndexBufferVersion ||
|
||||||
|
currentIndexBufferObject != m_syncedIndexBufferObject;
|
||||||
|
|
||||||
// The baseInstance shift lives in the attribute offsets the driver already holds, so
|
// The baseInstance shift lives in the attribute offsets the driver already holds, so
|
||||||
// a change of baseInstance has to re-emit the divisor'd arrays even when the frontend
|
// a change of baseInstance has to re-emit the divisor'd arrays even when the frontend
|
||||||
@@ -1675,10 +1701,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool needsSyncFormat = allAttributeVersions[attribIndex].FormatVersion !=
|
Bool needsSyncFormat = bufferIdsRemitted || allAttributeVersions[attribIndex].FormatVersion !=
|
||||||
m_syncedAttributeVersions[attribIndex].FormatVersion;
|
m_syncedAttributeVersions[attribIndex].FormatVersion;
|
||||||
Bool needsSyncBuffer = allAttributeVersions[attribIndex].BufferVersion !=
|
Bool needsSyncBuffer = bufferIdsRemitted || allAttributeVersions[attribIndex].BufferVersion !=
|
||||||
m_syncedAttributeVersions[attribIndex].BufferVersion;
|
m_syncedAttributeVersions[attribIndex].BufferVersion;
|
||||||
if (!needsSyncFormat && !needsSyncBuffer && !needsSyncBaseInstance) continue;
|
if (!needsSyncFormat && !needsSyncBuffer && !needsSyncBaseInstance) continue;
|
||||||
|
|
||||||
// Defence in depth. The frontend already declines glVertexAttribLFormat on this
|
// Defence in depth. The frontend already declines glVertexAttribLFormat on this
|
||||||
@@ -1798,6 +1824,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
if (indexBufferSynced) {
|
if (indexBufferSynced) {
|
||||||
m_syncedIndexBufferVersion = currentIndexBufferVersion;
|
m_syncedIndexBufferVersion = currentIndexBufferVersion;
|
||||||
|
m_syncedIndexBufferObject = currentIndexBufferObject;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1809,6 +1836,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
if (emitAttributes) {
|
if (emitAttributes) {
|
||||||
m_syncedFetchBaseInstance = fetchBaseInstance;
|
m_syncedFetchBaseInstance = fetchBaseInstance;
|
||||||
}
|
}
|
||||||
|
m_syncedBufferIdGeneration = currentBufferIdGeneration;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BackendVertexArrayObject::SyncClientSideAttributesForDrawArrays(
|
void BackendVertexArrayObject::SyncClientSideAttributesForDrawArrays(
|
||||||
@@ -1946,6 +1974,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
void BackendTextureObject::RecreateBackendTexture() {
|
void BackendTextureObject::RecreateBackendTexture() {
|
||||||
if (m_backendTextureId != 0) {
|
if (m_backendTextureId != 0) {
|
||||||
ScratchFBOImpl::NoteTextureIdDeleted(m_backendTextureId);
|
ScratchFBOImpl::NoteTextureIdDeleted(m_backendTextureId);
|
||||||
|
// Application FBO twins that attached the dying id memoize on FRONTEND
|
||||||
|
// attachment versions, which this backend-side re-mint does not move;
|
||||||
|
// without this bump their driver FBOs would keep the deleted name
|
||||||
|
// attached forever (see g_attachmentBackendIdGeneration).
|
||||||
|
++FramebufferImpl::g_attachmentBackendIdGeneration;
|
||||||
if (m_contextGeneration == g_backendContextGeneration) {
|
if (m_contextGeneration == g_backendContextGeneration) {
|
||||||
g_GLESFuncs.glDeleteTextures(1, &m_backendTextureId);
|
g_GLESFuncs.glDeleteTextures(1, &m_backendTextureId);
|
||||||
}
|
}
|
||||||
@@ -3517,6 +3550,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
m_backendReadBuffer = GL_NONE;
|
m_backendReadBuffer = GL_NONE;
|
||||||
std::fill(m_syncedFrontendAttachmentVersions.begin(), m_syncedFrontendAttachmentVersions.end(),
|
std::fill(m_syncedFrontendAttachmentVersions.begin(), m_syncedFrontendAttachmentVersions.end(),
|
||||||
static_cast<Uint16>(~0u));
|
static_cast<Uint16>(~0u));
|
||||||
|
// Every attachment version is invalidated above, so the next walk re-attaches
|
||||||
|
// everything regardless; stamp the generation so it does not re-arm twice.
|
||||||
|
m_syncedBackendIdGeneration = g_attachmentBackendIdGeneration;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Bool SyncAttachmentObject(GLenum glFBOTarget,
|
static Bool SyncAttachmentObject(GLenum glFBOTarget,
|
||||||
@@ -4005,6 +4041,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------------------- Attach texture to backend FBO -----------------------
|
// -------------------- Attach texture to backend FBO -----------------------
|
||||||
|
// A backend texture id was re-minted since this twin's last walk
|
||||||
|
// (RecreateBackendTexture): any point here may still hold the dead id while
|
||||||
|
// its frontend attachment version is unchanged, so the memo below would skip
|
||||||
|
// exactly the attachment that needs repair. Re-arm every point first.
|
||||||
|
if (m_syncedBackendIdGeneration != g_attachmentBackendIdGeneration) {
|
||||||
|
std::fill(m_syncedFrontendAttachmentVersions.begin(), m_syncedFrontendAttachmentVersions.end(),
|
||||||
|
static_cast<Uint16>(~0u));
|
||||||
|
m_syncedBackendIdGeneration = g_attachmentBackendIdGeneration;
|
||||||
|
}
|
||||||
const auto& attachments = stateFBOObject->GetAllAttachmentObjects();
|
const auto& attachments = stateFBOObject->GetAllAttachmentObjects();
|
||||||
const auto& attachmentVersions = stateFBOObject->GetAllFramebufferAttachmentVersions();
|
const auto& attachmentVersions = stateFBOObject->GetAllFramebufferAttachmentVersions();
|
||||||
for (SizeT i = 0; i < attachments.size(); ++i) {
|
for (SizeT i = 0; i < attachments.size(); ++i) {
|
||||||
@@ -4093,6 +4138,18 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The walk itself can re-mint an id (SyncAttachmentObject ->
|
||||||
|
// SyncMipmapsToBackend -> RecreateBackendTexture), invalidating points this
|
||||||
|
// walk already attached or version-skipped - e.g. one texture attached at two
|
||||||
|
// points. Re-enter until the generation is quiescent: every pass syncs each
|
||||||
|
// dirty texture clean, so each repeat finds strictly fewer re-mints and the
|
||||||
|
// common case (no re-mint) never takes a second pass. The head's draw/read-
|
||||||
|
// buffer syncs are memoized against their own shadows, so a repeat re-walks
|
||||||
|
// only the attachments.
|
||||||
|
if (m_syncedBackendIdGeneration != g_attachmentBackendIdGeneration) {
|
||||||
|
SyncToBackend(stateFBOObject, asTarget);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GLenum BackendFramebufferObject::GetBackendAttachmentType(FramebufferAttachmentType frontendAtt) const {
|
GLenum BackendFramebufferObject::GetBackendAttachmentType(FramebufferAttachmentType frontendAtt) const {
|
||||||
@@ -4119,6 +4176,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
Array<Uint16, SizeT(FramebufferTarget::FramebufferTargetCount)> g_fboSyncedObjectVersions = {0};
|
Array<Uint16, SizeT(FramebufferTarget::FramebufferTargetCount)> g_fboSyncedObjectVersions = {0};
|
||||||
Array<MG_State::GLState::FramebufferObject*, SizeT(FramebufferTarget::FramebufferTargetCount)>
|
Array<MG_State::GLState::FramebufferObject*, SizeT(FramebufferTarget::FramebufferTargetCount)>
|
||||||
g_fboSyncedObjects = {};
|
g_fboSyncedObjects = {};
|
||||||
|
Uint64 g_attachmentBackendIdGeneration = 0;
|
||||||
|
Array<Uint64, SizeT(FramebufferTarget::FramebufferTargetCount)> g_fboSyncedBackendIdGenerations = {0};
|
||||||
} // namespace FramebufferImpl
|
} // namespace FramebufferImpl
|
||||||
|
|
||||||
namespace ScratchFBOImpl {
|
namespace ScratchFBOImpl {
|
||||||
|
|||||||
@@ -346,6 +346,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
// client-attribute staging buffers): scrub every buffer-binding shadow that
|
// client-attribute staging buffers): scrub every buffer-binding shadow that
|
||||||
// could false-skip when the name is recycled.
|
// could false-skip when the name is recycled.
|
||||||
void NoteBufferIdDeleted(Uint id);
|
void NoteBufferIdDeleted(Uint id);
|
||||||
|
// Bumped whenever a live GLESBufferResource's driver id is retired and re-minted
|
||||||
|
// while its frontend buffer stays alive (persistent-map adoption, immutable-store
|
||||||
|
// retire). The VAO twins' baked glVertexAttribPointer / element-array bindings
|
||||||
|
// key on FRONTEND versions, which a backend-side re-mint does not move - without
|
||||||
|
// this generation the driver VAO would keep fetching through the deleted id (or
|
||||||
|
// its retained store) forever. Compared and stamped by
|
||||||
|
// BackendVertexArrayObject::SyncToBackend.
|
||||||
|
extern Uint64 g_bufferBackendIdGeneration;
|
||||||
// Redundant-bind cache for INDEXED buffer bindings (glBindBufferBase/Range on
|
// Redundant-bind cache for INDEXED buffer bindings (glBindBufferBase/Range on
|
||||||
// GL_UNIFORM_BUFFER / GL_SHADER_STORAGE_BUFFER): skips the GL call when the
|
// GL_UNIFORM_BUFFER / GL_SHADER_STORAGE_BUFFER): skips the GL call when the
|
||||||
// (id, range) already at that index matches, like the array-buffer/texture/
|
// (id, range) already at that index matches, like the array-buffer/texture/
|
||||||
@@ -465,6 +473,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
Array<Uint, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS> m_clientAttributeBufferIds;
|
Array<Uint, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS> m_clientAttributeBufferIds;
|
||||||
Bool m_isInitialized = false;
|
Bool m_isInitialized = false;
|
||||||
Uint16 m_syncedIndexBufferVersion = 0;
|
Uint16 m_syncedIndexBufferVersion = 0;
|
||||||
|
// Identity of the buffer the version above was stamped against. Raw and never
|
||||||
|
// dereferenced: the slot version is a wrapping Uint16 (see the ResolvedDrawBuffers
|
||||||
|
// IBO memo and the packed_pixels postmortem at BindCurrentFBO), so the version
|
||||||
|
// alone would read a wrapped-back count with a different buffer bound as clean.
|
||||||
|
const MG_State::GLState::BufferObject* m_syncedIndexBufferObject = nullptr;
|
||||||
// Aggregate gate over the per-attribute walk below: the frontend bumps its config
|
// Aggregate gate over the per-attribute walk below: the frontend bumps its config
|
||||||
// version on every per-attribute version bump (the three Bump*Version functions are
|
// version on every per-attribute version bump (the three Bump*Version functions are
|
||||||
// its only writers), so an unchanged config version proves every per-attribute
|
// its only writers), so an unchanged config version proves every per-attribute
|
||||||
@@ -480,6 +493,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
// Kept here because it describes what was last EMITTED, which is what the next sync
|
// Kept here because it describes what was last EMITTED, which is what the next sync
|
||||||
// has to correct.
|
// has to correct.
|
||||||
Uint32 m_syncedFetchBaseInstance = 0;
|
Uint32 m_syncedFetchBaseInstance = 0;
|
||||||
|
// BufferImpl::g_bufferBackendIdGeneration as of this twin's last emit. A
|
||||||
|
// mismatch means some live buffer's driver id was re-minted since; the ids
|
||||||
|
// baked into the driver VAO's attribute/element bindings may be dead even
|
||||||
|
// though every frontend version matches, so the next sync re-emits them all.
|
||||||
|
Uint64 m_syncedBufferIdGeneration = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern StateBackendObjectRegistry<MG_State::GLState::VertexArrayObject, BackendVertexArrayObject>
|
extern StateBackendObjectRegistry<MG_State::GLState::VertexArrayObject, BackendVertexArrayObject>
|
||||||
@@ -799,6 +817,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
using FramebufferObject = MG_State::GLState::FramebufferObject;
|
using FramebufferObject = MG_State::GLState::FramebufferObject;
|
||||||
FramebufferObject::FramebufferAttachmentVersionArray m_syncedFrontendAttachmentVersions = {0};
|
FramebufferObject::FramebufferAttachmentVersionArray m_syncedFrontendAttachmentVersions = {0};
|
||||||
|
// g_attachmentBackendIdGeneration as of this twin's last attachment walk. A
|
||||||
|
// mismatch means some backend texture id was re-minted since, and any of this
|
||||||
|
// twin's attachment points may still hold the dead id even though the frontend
|
||||||
|
// attachment versions match - so the walk re-attaches everything first.
|
||||||
|
Uint64 m_syncedBackendIdGeneration = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern StateBackendObjectRegistry<MG_State::GLState::FramebufferObject, BackendFramebufferObject>
|
extern StateBackendObjectRegistry<MG_State::GLState::FramebufferObject, BackendFramebufferObject>
|
||||||
@@ -888,6 +911,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
extern Array<MG_State::GLState::FramebufferObject*, SizeT(FramebufferTarget::FramebufferTargetCount)>
|
extern Array<MG_State::GLState::FramebufferObject*, SizeT(FramebufferTarget::FramebufferTargetCount)>
|
||||||
g_fboSyncedObjects;
|
g_fboSyncedObjects;
|
||||||
|
|
||||||
|
// Bumped whenever a live backend texture's driver id is re-minted while its
|
||||||
|
// frontend texture may still be attached to application FBOs
|
||||||
|
// (BackendTextureObject::RecreateBackendTexture - e.g. a respecify of a texture
|
||||||
|
// whose backend storage went immutable). The FBO twins' attachment memos key on
|
||||||
|
// FRONTEND attachment versions, which a backend-side re-mint does not move, so
|
||||||
|
// the driver FBO would keep the deleted texture name attached forever. The
|
||||||
|
// SyncCurrentFBO gate compares this generation (below) to re-enter the sync,
|
||||||
|
// and each twin re-arms its per-attachment memo on a mismatch (SyncToBackend).
|
||||||
|
extern Uint64 g_attachmentBackendIdGeneration;
|
||||||
|
// What g_attachmentBackendIdGeneration was when SyncCurrentFBO last stamped each
|
||||||
|
// target; part of the synced tuple above.
|
||||||
|
extern Array<Uint64, SizeT(FramebufferTarget::FramebufferTargetCount)> g_fboSyncedBackendIdGenerations;
|
||||||
|
|
||||||
// Driver-level READ/DRAW framebuffer-binding shadow. Every backend
|
// Driver-level READ/DRAW framebuffer-binding shadow. Every backend
|
||||||
// glBindFramebuffer routes through BindFramebufferId so scoped helpers can
|
// glBindFramebuffer routes through BindFramebufferId so scoped helpers can
|
||||||
// save/restore the current binding without a glGetIntegerv round-trip (that
|
// save/restore the current binding without a glGetIntegerv round-trip (that
|
||||||
|
|||||||
@@ -69,6 +69,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// slot's ownership unambiguous.
|
// slot's ownership unambiguous.
|
||||||
Uint64 programLifetimeId = 0;
|
Uint64 programLifetimeId = 0;
|
||||||
Uint32 backendStateVersion = 0;
|
Uint32 backendStateVersion = 0;
|
||||||
|
// glShaderStorageBlockBinding deliberately does NOT bump the backend state
|
||||||
|
// version, and the pipeline composite is unnamed so the in-place patch in
|
||||||
|
// DirectVulkan::ShaderStorageBlockBinding can never reach its slot - the
|
||||||
|
// mirror replay bumps only the program's block-binding version. Without this
|
||||||
|
// key the composite's slot kept serving the pre-rebind block.binding.
|
||||||
|
Uint32 blockBindingVersion = 0;
|
||||||
Vector<StorageBlockResource> storageBlocks;
|
Vector<StorageBlockResource> storageBlocks;
|
||||||
Vector<BufferVariableResource> bufferVariables;
|
Vector<BufferVariableResource> bufferVariables;
|
||||||
GLint computeWorkGroupSize[3] = {1, 1, 1};
|
GLint computeWorkGroupSize[3] = {1, 1, 1};
|
||||||
@@ -156,18 +162,33 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
auto& cache = g_programResourceCaches[program.GetExternalIndex()];
|
auto& cache = g_programResourceCaches[program.GetExternalIndex()];
|
||||||
const Uint64 programLifetimeId = program.GetLifetimeId();
|
const Uint64 programLifetimeId = program.GetLifetimeId();
|
||||||
const Uint32 backendStateVersion = program.GetBackendStateVersion();
|
const Uint32 backendStateVersion = program.GetBackendStateVersion();
|
||||||
|
const Uint32 blockBindingVersion = program.GetBlockBindingVersion();
|
||||||
// The lifetime id must match too: a new program that reuses a deleted
|
// The lifetime id must match too: a new program that reuses a deleted
|
||||||
// program's name and happens to land on the same backendStateVersion (both
|
// program's name and happens to land on the same backendStateVersion (both
|
||||||
// count from zero) would otherwise be served the dead program's reflection.
|
// count from zero) would otherwise be served the dead program's reflection.
|
||||||
if (cache.programLifetimeId == programLifetimeId &&
|
if (cache.programLifetimeId == programLifetimeId &&
|
||||||
cache.backendStateVersion == backendStateVersion &&
|
cache.backendStateVersion == backendStateVersion &&
|
||||||
(!cache.storageBlocks.empty() || !cache.bufferVariables.empty())) {
|
(!cache.storageBlocks.empty() || !cache.bufferVariables.empty())) {
|
||||||
|
if (cache.blockBindingVersion != blockBindingVersion) {
|
||||||
|
// Only the block bindings moved (glShaderStorageBlockBinding, or the
|
||||||
|
// pipeline composite's mirror replay - neither touches the backend
|
||||||
|
// state version): the reflection itself is unchanged, so re-apply the
|
||||||
|
// overrides by name instead of re-running spirv-reflect. Overrides
|
||||||
|
// only ever accumulate, so a block without one still holds its
|
||||||
|
// declared binding.
|
||||||
|
for (auto& block : cache.storageBlocks) {
|
||||||
|
const Int rebound = program.GetShaderStorageBlockBindingOverride(block.name);
|
||||||
|
if (rebound >= 0) block.binding = static_cast<Uint32>(rebound);
|
||||||
|
}
|
||||||
|
cache.blockBindingVersion = blockBindingVersion;
|
||||||
|
}
|
||||||
return cache;
|
return cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
cache = {};
|
cache = {};
|
||||||
cache.programLifetimeId = programLifetimeId;
|
cache.programLifetimeId = programLifetimeId;
|
||||||
cache.backendStateVersion = backendStateVersion;
|
cache.backendStateVersion = backendStateVersion;
|
||||||
|
cache.blockBindingVersion = blockBindingVersion;
|
||||||
|
|
||||||
Vector<SpvReflectShaderModule> modules;
|
Vector<SpvReflectShaderModule> modules;
|
||||||
Vector<Bool> validModules;
|
Vector<Bool> validModules;
|
||||||
|
|||||||
@@ -287,8 +287,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
if (m_frameBoundaryCounter - it->second->lastUsedFrameBoundary > kRetireAgeBoundaries) {
|
if (m_frameBoundaryCounter - it->second->lastUsedFrameBoundary > kRetireAgeBoundaries) {
|
||||||
it = m_cache.erase(it);
|
it = m_cache.erase(it);
|
||||||
// Invalidate every VAO's state-pointer memo: the erased node's
|
// Invalidate every VAO's state-pointer memo: the erased node's
|
||||||
// address may be reused by a future insert.
|
// address may be reused by a future insert. Advance through the
|
||||||
++m_evictionEpoch;
|
// process-wide source so the value stays unique across factory
|
||||||
|
// instances (see the member comment).
|
||||||
|
m_evictionEpoch = ++s_evictionEpochSource;
|
||||||
} else {
|
} else {
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,7 +125,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// construction); a memo is honored only while its recorded epoch
|
// construction); a memo is honored only while its recorded epoch
|
||||||
// matches, so an evicted entry can never be dereferenced through a
|
// matches, so an evicted entry can never be dereferenced through a
|
||||||
// stale memo.
|
// stale memo.
|
||||||
Uint64 m_evictionEpoch = 1;
|
//
|
||||||
|
// Drawn from a process-wide source, never a per-instance counter: the VAO
|
||||||
|
// memos outlive this factory (they live on pGLContext's VAOs, the renderer
|
||||||
|
// is destroyed and recreated on EGL surface release/re-create), so a fresh
|
||||||
|
// factory restarting at a dead factory's epoch value would honor its
|
||||||
|
// dangling entry pointers. The constructor takes a value strictly greater
|
||||||
|
// than anything a predecessor ever stamped, so a dead factory's memo can
|
||||||
|
// never compare equal here - the same never-reused idiom as the lifetime ids.
|
||||||
|
// Single-threaded like the rest of the factory (renderer-thread only).
|
||||||
|
static inline Uint64 s_evictionEpochSource = 0;
|
||||||
|
Uint64 m_evictionEpoch = ++s_evictionEpochSource;
|
||||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||||
};
|
};
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -166,7 +166,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
void VkClearManager::MergeClearPayload(ClearAttachmentPayload& dst, const ClearAttachmentPayload& src) {
|
void VkClearManager::MergeClearPayload(ClearAttachmentPayload& dst, const ClearAttachmentPayload& src) {
|
||||||
dst.mask |= src.mask;
|
dst.mask |= src.mask;
|
||||||
if ((src.mask & GL_COLOR_BUFFER_BIT) != 0) {
|
if ((src.mask & GL_COLOR_BUFFER_BIT) != 0) {
|
||||||
|
// The whole colour story travels together (same rule as
|
||||||
|
// VkRenderPassManager::QueueRenderbufferClear): a glClearBufferiv/uiv
|
||||||
|
// payload carries its value in colorInt/colorUint and its branch selector
|
||||||
|
// in colorEncoding - dropping them here would leave the pending clear
|
||||||
|
// reading as an all-zero float one.
|
||||||
dst.color = src.color;
|
dst.color = src.color;
|
||||||
|
dst.colorEncoding = src.colorEncoding;
|
||||||
|
dst.colorInt = src.colorInt;
|
||||||
|
dst.colorUint = src.colorUint;
|
||||||
}
|
}
|
||||||
if ((src.mask & GL_DEPTH_BUFFER_BIT) != 0) {
|
if ((src.mask & GL_DEPTH_BUFFER_BIT) != 0) {
|
||||||
dst.depth = src.depth;
|
dst.depth = src.depth;
|
||||||
|
|||||||
@@ -831,6 +831,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// recreated since (texture + renderbuffer image epochs), and no pending clear (which alters
|
// recreated since (texture + renderbuffer image epochs), and no pending clear (which alters
|
||||||
// load ops). Any of these differing forces the full recompute below. Portable to VK 1.1.
|
// load ops). Any of these differing forces the full recompute below. Portable to VK 1.1.
|
||||||
if (activeRenderPass != nullptr && m_rpFastValid && m_rpFastFbo == &fbo &&
|
if (activeRenderPass != nullptr && m_rpFastValid && m_rpFastFbo == &fbo &&
|
||||||
|
m_rpFastFboLifetimeId == fbo.GetLifetimeId() &&
|
||||||
m_rpFastFboVersion == fbo.GetObjectVersion() && m_rpFastSwapchainIndex == swapchainImageIndex &&
|
m_rpFastFboVersion == fbo.GetObjectVersion() && m_rpFastSwapchainIndex == swapchainImageIndex &&
|
||||||
m_rpFastTexEpoch == m_textureManager.GetTextureImageEpoch() &&
|
m_rpFastTexEpoch == m_textureManager.GetTextureImageEpoch() &&
|
||||||
m_rpFastRbEpoch == m_renderbufferImageEpoch &&
|
m_rpFastRbEpoch == m_renderbufferImageEpoch &&
|
||||||
@@ -855,6 +856,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// epochs AFTER ComputeHash: its attachment SyncTexture can create an image (bump the epoch).
|
// epochs AFTER ComputeHash: its attachment SyncTexture can create an image (bump the epoch).
|
||||||
m_rpFastValid = true;
|
m_rpFastValid = true;
|
||||||
m_rpFastFbo = &fbo;
|
m_rpFastFbo = &fbo;
|
||||||
|
m_rpFastFboLifetimeId = fbo.GetLifetimeId();
|
||||||
m_rpFastFboVersion = fbo.GetObjectVersion();
|
m_rpFastFboVersion = fbo.GetObjectVersion();
|
||||||
m_rpFastSwapchainIndex = swapchainImageIndex;
|
m_rpFastSwapchainIndex = swapchainImageIndex;
|
||||||
m_rpFastTexEpoch = m_textureManager.GetTextureImageEpoch();
|
m_rpFastTexEpoch = m_textureManager.GetTextureImageEpoch();
|
||||||
@@ -1507,7 +1509,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
ClearAttachmentPayload clearPayload{};
|
ClearAttachmentPayload clearPayload{};
|
||||||
SharedPtr<MG_State::GLState::ITextureObject> liveTexture;
|
SharedPtr<MG_State::GLState::ITextureObject> liveTexture;
|
||||||
if (pending.hasInlinePayload) {
|
if (pending.hasInlinePayload) {
|
||||||
clearPayload = pending.inlinePayload;
|
// The inline payload was snapshotted when the entry was CREATED, but the
|
||||||
|
// clear VALUE is not part of the entry's hash - a cache hit with a newer
|
||||||
|
// glClear would replay the creation-time value and drop the new one (the
|
||||||
|
// texture path below is immune because it re-reads the live payload).
|
||||||
|
// Same defense as ClearAttachmentsOnActiveRenderPass: prefer the live
|
||||||
|
// pending clear, fall back to the snapshot only when none is queued.
|
||||||
|
if (s_renderPassManager != nullptr &&
|
||||||
|
s_renderPassManager->GetPendingRenderbufferClear(pending.renderbuffer, clearPayload)) {
|
||||||
|
if ((clearPayload.mask & GL_COLOR_BUFFER_BIT) != 0 && pending.renderbuffer != nullptr &&
|
||||||
|
MG_Util::GetBaseInternalFormatComponentCount(pending.renderbuffer->GetInternalFormat()) ==
|
||||||
|
3) {
|
||||||
|
// RGB renderbuffers are backed by an RGBA image; the missing alpha reads as 1.
|
||||||
|
ForceOpaqueClearAlpha(clearPayload);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
clearPayload = pending.inlinePayload;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (pending.key.texture == nullptr ||
|
if (pending.key.texture == nullptr ||
|
||||||
!s_clearManager->GetPendingClear(pending.key, clearPayload, liveTexture)) {
|
!s_clearManager->GetPendingClear(pending.key, clearPayload, liveTexture)) {
|
||||||
|
|||||||
@@ -289,6 +289,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// or a pending clear. Portable to Vulkan 1.1 (no dynamic_rendering / imageless FB needed).
|
// or a pending clear. Portable to Vulkan 1.1 (no dynamic_rendering / imageless FB needed).
|
||||||
Bool m_rpFastValid = false;
|
Bool m_rpFastValid = false;
|
||||||
const MG_State::GLState::FramebufferObject* m_rpFastFbo = nullptr;
|
const MG_State::GLState::FramebufferObject* m_rpFastFbo = nullptr;
|
||||||
|
// The FBO's never-reused lifetime id joins the raw pointer + Uint16 version:
|
||||||
|
// a deleted FBO reallocated at the same address whose fresh setup performed
|
||||||
|
// the same number of version bumps would otherwise compare equal (both count
|
||||||
|
// from 0), serving the dead framebuffer's pass to the new object.
|
||||||
|
Uint64 m_rpFastFboLifetimeId = 0;
|
||||||
Uint16 m_rpFastFboVersion = 0;
|
Uint16 m_rpFastFboVersion = 0;
|
||||||
Uint32 m_rpFastSwapchainIndex = 0;
|
Uint32 m_rpFastSwapchainIndex = 0;
|
||||||
Uint64 m_rpFastTexEpoch = 0;
|
Uint64 m_rpFastTexEpoch = 0;
|
||||||
|
|||||||
@@ -1993,6 +1993,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
texture.GetExternalIndex(),
|
texture.GetExternalIndex(),
|
||||||
MG_Util::ConvertTextureUploadTargetToString(uploadTarget).c_str(),
|
MG_Util::ConvertTextureUploadTargetToString(uploadTarget).c_str(),
|
||||||
static_cast<Int>(format), static_cast<Uint32>(imageInfo.usage));
|
static_cast<Int>(format), static_cast<Uint32>(imageInfo.usage));
|
||||||
|
// The preserved image was written by GPU work that may still be in flight
|
||||||
|
// (preserve requires layout != UNDEFINED); park it on the deferred ring
|
||||||
|
// like every other destruction path instead of letting the unique_ptr
|
||||||
|
// destroy it synchronously under the GPU.
|
||||||
|
if (preservedResource) {
|
||||||
|
DeferResourceRelease(Move(*preservedResource));
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2015,6 +2022,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
static_cast<Int>(imageInfo.samples), static_cast<Int>(imageInfo.format));
|
static_cast<Int>(imageInfo.samples), static_cast<Int>(imageInfo.format));
|
||||||
resource.image = VK_NULL_HANDLE;
|
resource.image = VK_NULL_HANDLE;
|
||||||
resource.allocation = nullptr;
|
resource.allocation = nullptr;
|
||||||
|
// Same as the probe failure above: the preserved live image must go through
|
||||||
|
// the deferred ring, never a synchronous destructor while frames that
|
||||||
|
// reference it are still in flight.
|
||||||
|
if (preservedResource) {
|
||||||
|
DeferResourceRelease(Move(*preservedResource));
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
++m_textureImageEpoch; // a new attachment image invalidates cached render passes
|
++m_textureImageEpoch; // a new attachment image invalidates cached render passes
|
||||||
|
|||||||
@@ -3323,6 +3323,11 @@ void main() {
|
|||||||
indexView.indexByteSize > bufferSize - indexView.indexByteOffset) {
|
indexView.indexByteSize > bufferSize - indexView.indexByteOffset) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// Recorded-but-unexecuted GPU writes (XFB capture, SSBO, storage texel
|
||||||
|
// buffer) land in the coherent mapping this scan is about to read;
|
||||||
|
// submit-and-wait first, exactly like the restart-index rewrite does.
|
||||||
|
// A no-op unless the gpu-write flag is set.
|
||||||
|
indexBufferShared->SyncGpuWrites();
|
||||||
indexBufferShared->SyncPersistentMappedRange();
|
indexBufferShared->SyncPersistentMappedRange();
|
||||||
indexBytes = indexBufferShared->MappedData() + indexView.indexByteOffset;
|
indexBytes = indexBufferShared->MappedData() + indexView.indexByteOffset;
|
||||||
} else {
|
} else {
|
||||||
@@ -3560,6 +3565,16 @@ void main() {
|
|||||||
const Uint8* sourceData, SizeT sourceStride,
|
const Uint8* sourceData, SizeT sourceStride,
|
||||||
SizeT elementSize, SizeT elementCount,
|
SizeT elementSize, SizeT elementCount,
|
||||||
BufferSlice& outSlice) -> Bool {
|
BufferSlice& outSlice) -> Bool {
|
||||||
|
// A resolved stride of 0 is the binding model's "never advance" (see the
|
||||||
|
// factory's layout notes): exactly one element is converted and every vertex
|
||||||
|
// reads it. That single element is read at offset 0, so the stride is never
|
||||||
|
// actually used - but both converters reject 0 as a degenerate input, which
|
||||||
|
// made the documented single-element conversion unreachable and silently
|
||||||
|
// dropped every draw using such a binding. Substitute the element's own
|
||||||
|
// size; the caller's cache key still carries the distinct stride 0.
|
||||||
|
if (sourceStride == 0 && elementCount == 1) {
|
||||||
|
sourceStride = elementSize;
|
||||||
|
}
|
||||||
const void* uploadData = nullptr;
|
const void* uploadData = nullptr;
|
||||||
VkDeviceSize uploadSize = 0;
|
VkDeviceSize uploadSize = 0;
|
||||||
switch (conversion) {
|
switch (conversion) {
|
||||||
@@ -3693,6 +3708,12 @@ void main() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A GPU-written source (XFB capture, SSBO, storage texel buffer) has its
|
||||||
|
// bytes produced by commands that are merely RECORDED at this point, and
|
||||||
|
// MappedData() aliases the coherent GPU memory they will write into -
|
||||||
|
// converting now would read pre-write garbage. Submit-and-wait first,
|
||||||
|
// mirroring the restart-index rewrite; a flag-test no-op otherwise.
|
||||||
|
sourceBufferShared->SyncGpuWrites();
|
||||||
sourceBufferShared->SyncPersistentMappedRange();
|
sourceBufferShared->SyncPersistentMappedRange();
|
||||||
const SizeT availableElementCount =
|
const SizeT availableElementCount =
|
||||||
sourceStride == 0 ? 1 : 1 + (sourceSize - baseOffset - elementSize) / sourceStride;
|
sourceStride == 0 ? 1 : 1 + (sourceSize - baseOffset - elementSize) / sourceStride;
|
||||||
@@ -3986,7 +4007,7 @@ void main() {
|
|||||||
// Skips the per-draw GetBackendResource chase into a cold resource object.
|
// Skips the per-draw GetBackendResource chase into a cold resource object.
|
||||||
Bool sliceStillValid = false;
|
Bool sliceStillValid = false;
|
||||||
const Uint64 frameSerial = m_bufferManager.GetFrameSerial();
|
const Uint64 frameSerial = m_bufferManager.GetFrameSerial();
|
||||||
if (indexMemo->indexFrameSerial == frameSerial &&
|
if (indexMemo->indexFrameSerial == frameSerial && !indexMemo->indexBufferMapped &&
|
||||||
indexMemo->indexSliceEpochCounter == m_bufferManager.GetSliceEpochCounter()) {
|
indexMemo->indexSliceEpochCounter == m_bufferManager.GetSliceEpochCounter()) {
|
||||||
sliceStillValid = true;
|
sliceStillValid = true;
|
||||||
}
|
}
|
||||||
@@ -4049,6 +4070,9 @@ void main() {
|
|||||||
indexMemo->indexVkBuffer = slice.buffer;
|
indexMemo->indexVkBuffer = slice.buffer;
|
||||||
indexMemo->indexSliceOffset = slice.offset;
|
indexMemo->indexSliceOffset = slice.offset;
|
||||||
indexMemo->indexFrameSerial = m_bufferManager.GetFrameSerial();
|
indexMemo->indexFrameSerial = m_bufferManager.GetFrameSerial();
|
||||||
|
// A host-mapped EBO can mutate its shadow with no epoch bump; the hit
|
||||||
|
// path declines on this flag (mirror of anyBufferMapped).
|
||||||
|
indexMemo->indexBufferMapped = indexBufferShared->IsMapped();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const VkDeviceSize indexBindOffset =
|
const VkDeviceSize indexBindOffset =
|
||||||
@@ -5718,6 +5742,22 @@ void main() {
|
|||||||
if (program.GetBackendStateVersion() != snap.programVersion) {
|
if (program.GetBackendStateVersion() != snap.programVersion) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// glBegin/EndTransformFeedback moves no key this fast path otherwise observes
|
||||||
|
// (the design makes capture a compile-option FLAG precisely because no version
|
||||||
|
// bumps, VulkanRenderer.h's pipeline-memo note) - but the snapshot bakes that
|
||||||
|
// flag into resolvedTransformFlags and the pipeline. Recompute the one dynamic
|
||||||
|
// bit (the full path's exact predicate) and decline on a mismatch, or the first
|
||||||
|
// captured draw after glBeginTransformFeedback would bind the undecorated
|
||||||
|
// variant and silently capture nothing while the CPU bookkeeping advances.
|
||||||
|
const Bool wantsXfbCapture = m_transformFeedbackFeatureEnabled &&
|
||||||
|
MG_State::pGLContext->IsTransformFeedbackActive() &&
|
||||||
|
program.GetTransformFeedbackVaryingCount() > 0;
|
||||||
|
const Bool snapHasXfbCapture =
|
||||||
|
static_cast<Bool>(ProgramFactory::CompileOptionFlags(snap.resolvedTransformFlags) &
|
||||||
|
ProgramFactory::CompileOptionBit::XfbCapture);
|
||||||
|
if (wantsXfbCapture != snapHasXfbCapture) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// A changed VAO does NOT decline: the VAO only feeds the pipeline's vertex
|
// A changed VAO does NOT decline: the VAO only feeds the pipeline's vertex
|
||||||
// input state (re-resolved below through the layout-keyed memo, so N VAOs
|
// input state (re-resolved below through the layout-keyed memo, so N VAOs
|
||||||
// sharing one attribute layout share one pipeline) and the vertex/index
|
// sharing one attribute layout share one pipeline) and the vertex/index
|
||||||
@@ -5731,6 +5771,7 @@ void main() {
|
|||||||
const auto& drawFbo =
|
const auto& drawFbo =
|
||||||
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
|
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
|
||||||
if (static_cast<const void*>(drawFbo.get()) != snap.drawFbo ||
|
if (static_cast<const void*>(drawFbo.get()) != snap.drawFbo ||
|
||||||
|
drawFbo->GetLifetimeId() != snap.drawFboLifetimeId ||
|
||||||
drawFbo->GetObjectVersion() != snap.fboVersion) {
|
drawFbo->GetObjectVersion() != snap.fboVersion) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -5914,8 +5955,14 @@ void main() {
|
|||||||
}
|
}
|
||||||
const Uint64 samplingResolutionGeneration = MG_State::pGLContext->GetSamplingResolutionGeneration();
|
const Uint64 samplingResolutionGeneration = MG_State::pGLContext->GetSamplingResolutionGeneration();
|
||||||
if (samplingResolutionGeneration != snap.samplingResolutionGeneration) {
|
if (samplingResolutionGeneration != snap.samplingResolutionGeneration) {
|
||||||
snap.samplingResolutionGeneration = samplingResolutionGeneration;
|
// Decline, not re-arm: snap.resolvedTransformFlags bakes the
|
||||||
samplerDescriptorsUnchanged = false;
|
// ExplicitLod0Sampling verdict, which reads the effective sampler's
|
||||||
|
// filters/aniso/LOD range - exactly the state this counter tracks.
|
||||||
|
// Re-arming the stamp here would rebuild the descriptors but keep the
|
||||||
|
// stale SPIR-V variant forever (every later draw compares equal again).
|
||||||
|
// Same shape as the erase-epoch declines above; costs one full-path draw
|
||||||
|
// per sampler/shape change, and the full path's LOD memo re-probes.
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Everything the full path would re-resolve is provably unchanged - or, for
|
// Everything the full path would re-resolve is provably unchanged - or, for
|
||||||
@@ -6095,11 +6142,18 @@ void main() {
|
|||||||
const Uint64 lodProgramLifetimeId = program.GetLifetimeId();
|
const Uint64 lodProgramLifetimeId = program.GetLifetimeId();
|
||||||
const Uint32 lodProgramVersion = program.GetBackendStateVersion();
|
const Uint32 lodProgramVersion = program.GetBackendStateVersion();
|
||||||
const Uint64 lodBindGeneration = MG_State::pGLContext->GetTextureBindGeneration();
|
const Uint64 lodBindGeneration = MG_State::pGLContext->GetTextureBindGeneration();
|
||||||
|
// The probe also reads the EFFECTIVE sampler's filters/aniso/LOD range
|
||||||
|
// (ProgramSamplesOnlySingleLevelTextures), and those setters bump ONLY the
|
||||||
|
// sampling-resolution generation - not the texture params version the sum
|
||||||
|
// below covers. Without this key a filter/aniso change would keep serving
|
||||||
|
// the stale verdict.
|
||||||
|
const Uint64 lodSamplingGeneration = MG_State::pGLContext->GetSamplingResolutionGeneration();
|
||||||
Bool lodMemoHit = false;
|
Bool lodMemoHit = false;
|
||||||
if (m_lastLodDecisionValid && m_lastSampledSetValid &&
|
if (m_lastLodDecisionValid && m_lastSampledSetValid &&
|
||||||
m_lastLodProgramLifetimeId == lodProgramLifetimeId &&
|
m_lastLodProgramLifetimeId == lodProgramLifetimeId &&
|
||||||
m_lastLodProgramVersion == lodProgramVersion &&
|
m_lastLodProgramVersion == lodProgramVersion &&
|
||||||
m_lastLodBindGeneration == lodBindGeneration && m_lastLodBaseFlags == transformFlags &&
|
m_lastLodBindGeneration == lodBindGeneration &&
|
||||||
|
m_lastLodSamplingGeneration == lodSamplingGeneration && m_lastLodBaseFlags == transformFlags &&
|
||||||
m_lastSampledSetProgramLifetimeId == lodProgramLifetimeId &&
|
m_lastSampledSetProgramLifetimeId == lodProgramLifetimeId &&
|
||||||
m_lastSampledSetProgramVersion == lodProgramVersion &&
|
m_lastSampledSetProgramVersion == lodProgramVersion &&
|
||||||
m_lastSampledSetBindGeneration == lodBindGeneration) {
|
m_lastSampledSetBindGeneration == lodBindGeneration) {
|
||||||
@@ -6124,6 +6178,7 @@ void main() {
|
|||||||
m_lastLodProgramLifetimeId = lodProgramLifetimeId;
|
m_lastLodProgramLifetimeId = lodProgramLifetimeId;
|
||||||
m_lastLodProgramVersion = lodProgramVersion;
|
m_lastLodProgramVersion = lodProgramVersion;
|
||||||
m_lastLodBindGeneration = lodBindGeneration;
|
m_lastLodBindGeneration = lodBindGeneration;
|
||||||
|
m_lastLodSamplingGeneration = lodSamplingGeneration;
|
||||||
m_lastLodBaseFlags = baseFlags;
|
m_lastLodBaseFlags = baseFlags;
|
||||||
m_lastLodResultFlags = transformFlags;
|
m_lastLodResultFlags = transformFlags;
|
||||||
m_lastLodParamsSum = 0; // filled below once the sampled set is known
|
m_lastLodParamsSum = 0; // filled below once the sampled set is known
|
||||||
@@ -6467,6 +6522,7 @@ void main() {
|
|||||||
snap.vaoLifetimeId = vao.GetLifetimeId();
|
snap.vaoLifetimeId = vao.GetLifetimeId();
|
||||||
snap.vaoConfigVersion = vao.GetConfigVersion();
|
snap.vaoConfigVersion = vao.GetConfigVersion();
|
||||||
snap.drawFbo = drawFbo.get();
|
snap.drawFbo = drawFbo.get();
|
||||||
|
snap.drawFboLifetimeId = drawFbo->GetLifetimeId();
|
||||||
snap.fboVersion = drawFbo->GetObjectVersion();
|
snap.fboVersion = drawFbo->GetObjectVersion();
|
||||||
snap.drawFboIsDefault = drawFboIsDefault;
|
snap.drawFboIsDefault = drawFboIsDefault;
|
||||||
snap.viewportCount = ResolveDrawViewportCount(programObj.writesViewportIndexBuiltin);
|
snap.viewportCount = ResolveDrawViewportCount(programObj.writesViewportIndexBuiltin);
|
||||||
@@ -13825,6 +13881,15 @@ void main() {
|
|||||||
VkPipeline pipeline = VK_NULL_HANDLE;
|
VkPipeline pipeline = VK_NULL_HANDLE;
|
||||||
VK_VERIFY(vkCreateComputePipelines(m_device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline),
|
VK_VERIFY(vkCreateComputePipelines(m_device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline),
|
||||||
"GetOrCreateComputePipeline, vkCreateComputePipelines");
|
"GetOrCreateComputePipeline, vkCreateComputePipelines");
|
||||||
|
// A failed creation must never be memoized - same contract as
|
||||||
|
// PipelineFactory::GetOrCreatePipeline: caching the null would serve it back
|
||||||
|
// for the rest of the process and every dispatch of this program would be
|
||||||
|
// silently skipped. Retrying costs one failed vkCreateComputePipelines per
|
||||||
|
// dispatch, which is the correct price.
|
||||||
|
if (pipeline == VK_NULL_HANDLE) {
|
||||||
|
MGLOG_E("GetOrCreateComputePipeline: vkCreateComputePipelines failed; not caching the failure");
|
||||||
|
return VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
m_computePipelines.emplace(programObj.hash, pipeline);
|
m_computePipelines.emplace(programObj.hash, pipeline);
|
||||||
return pipeline;
|
return pipeline;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -807,6 +807,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint32 m_lastLodProgramVersion = 0;
|
Uint32 m_lastLodProgramVersion = 0;
|
||||||
Uint64 m_lastLodBindGeneration = 0;
|
Uint64 m_lastLodBindGeneration = 0;
|
||||||
Uint64 m_lastLodParamsSum = 0;
|
Uint64 m_lastLodParamsSum = 0;
|
||||||
|
// Sampling-resolution generation at probe time. The probe reads the effective
|
||||||
|
// sampler's filters/aniso/LOD range, whose setters bump only this counter -
|
||||||
|
// the params-version sum above never moves for them.
|
||||||
|
Uint64 m_lastLodSamplingGeneration = 0;
|
||||||
ProgramFactory::CompileOptionFlags m_lastLodBaseFlags = {};
|
ProgramFactory::CompileOptionFlags m_lastLodBaseFlags = {};
|
||||||
ProgramFactory::CompileOptionFlags m_lastLodResultFlags = {};
|
ProgramFactory::CompileOptionFlags m_lastLodResultFlags = {};
|
||||||
|
|
||||||
@@ -844,6 +848,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint64 vaoLifetimeId = 0;
|
Uint64 vaoLifetimeId = 0;
|
||||||
Uint32 vaoConfigVersion = 0;
|
Uint32 vaoConfigVersion = 0;
|
||||||
const void* drawFbo = nullptr;
|
const void* drawFbo = nullptr;
|
||||||
|
// Never-reused lifetime id beside the raw pointer + Uint16 version: a
|
||||||
|
// deleted FBO recycled at the same address with the same fresh version
|
||||||
|
// count would otherwise compare equal (same ABA as the render-pass
|
||||||
|
// manager's fast-path memo).
|
||||||
|
Uint64 drawFboLifetimeId = 0;
|
||||||
Uint16 fboVersion = 0;
|
Uint16 fboVersion = 0;
|
||||||
Bool drawFboIsDefault = false;
|
Bool drawFboIsDefault = false;
|
||||||
Uint renderStateVersion = 0;
|
Uint renderStateVersion = 0;
|
||||||
@@ -1067,6 +1076,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkBuffer indexVkBuffer = VK_NULL_HANDLE;
|
VkBuffer indexVkBuffer = VK_NULL_HANDLE;
|
||||||
VkDeviceSize indexSliceOffset = 0;
|
VkDeviceSize indexSliceOffset = 0;
|
||||||
Uint64 indexFrameSerial = 0;
|
Uint64 indexFrameSerial = 0;
|
||||||
|
// The EBO carried a host map when the slice was recorded - the mirror of
|
||||||
|
// anyBufferMapped on the vertex half. A shadow-backed (non-adopted)
|
||||||
|
// persistent map mutates its shadow with no API call and no epoch bump, so
|
||||||
|
// the one-compare rescue must decline and re-run the acquire, whose
|
||||||
|
// SyncPersistentMappedRange is the push-down. A map taken AFTER the record
|
||||||
|
// is already covered: AcquirePersistentMap bumps the slice epoch for the
|
||||||
|
// request itself, adopted or declined.
|
||||||
|
Bool indexBufferMapped = false;
|
||||||
|
|
||||||
// Bound per draw (first bindingCount elements).
|
// Bound per draw (first bindingCount elements).
|
||||||
VkBuffer vkBuffers[kMaxBindings] = {};
|
VkBuffer vkBuffers[kMaxBindings] = {};
|
||||||
|
|||||||
@@ -1057,21 +1057,20 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Bool allowVSOnlyPrograms;
|
// Read fresh every link, never latched in a static: the capability is
|
||||||
static Bool initialized = false;
|
// per-backend, and a latch would freeze it across a backend teardown +
|
||||||
if (!initialized) {
|
// re-initialization (the previous function-static memo here never even set
|
||||||
const auto& activeBackendObject = MG_Backend::pActiveBackendObject;
|
// its own initialized flag, so it re-read every call anyway - this makes
|
||||||
if (!activeBackendObject) {
|
// the always-fresh behavior the stated one). A struct-field read per
|
||||||
MGLOG_E_ONCE("activeBackendObject is not initialized!");
|
// glLinkProgram costs nothing.
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto& rendererInfo = activeBackendObject->GetRendererInfo();
|
|
||||||
allowVSOnlyPrograms = (Int)rendererInfo.StaticBackendCapability.AllowVSOnlyPrograms;
|
|
||||||
}
|
|
||||||
const auto& activeBackendObject = MG_Backend::pActiveBackendObject;
|
const auto& activeBackendObject = MG_Backend::pActiveBackendObject;
|
||||||
if (activeBackendObject) {
|
if (!activeBackendObject) {
|
||||||
programObject->SetMaxFragmentOutputColorNumber(activeBackendObject->GetDynamicParameters().MaxDrawBuffers);
|
MGLOG_E_ONCE("activeBackendObject is not initialized!");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
const Bool allowVSOnlyPrograms =
|
||||||
|
activeBackendObject->GetRendererInfo().StaticBackendCapability.AllowVSOnlyPrograms;
|
||||||
|
programObject->SetMaxFragmentOutputColorNumber(activeBackendObject->GetDynamicParameters().MaxDrawBuffers);
|
||||||
programObject->Link(!allowVSOnlyPrograms);
|
programObject->Link(!allowVSOnlyPrograms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -648,4 +648,39 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
if (!ValidateQueryStreamIndex(__FUNCTION__, target, index)) return;
|
if (!ValidateQueryStreamIndex(__FUNCTION__, target, index)) return;
|
||||||
GetQueryiv(target, pname, params);
|
GetQueryiv(target, pname, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DestroyAllQueryObjects() {
|
||||||
|
// Detach the registry under the lock, release outside it - same discipline
|
||||||
|
// (and the same accepted teardown race) as DestroyAllSyncObjects. Without
|
||||||
|
// this drain, every query the app left undeleted survived full library
|
||||||
|
// teardown in the process-global registry: the objects and their backend
|
||||||
|
// wrappers leaked across Destroy/Initialize cycles, stale ids kept
|
||||||
|
// answering IsQuery == GL_TRUE in the re-initialized library, and a later
|
||||||
|
// glDeleteQueries could hand the OLD backend's handle to a DIFFERENT
|
||||||
|
// backend's DeleteBackendQuery, which casts it to the wrong wrapper type.
|
||||||
|
UnorderedMap<GLuint, QueryObject*> orphans;
|
||||||
|
{
|
||||||
|
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||||
|
orphans.swap(g_liveQueryObjects);
|
||||||
|
g_activeTimeElapsedQueryId = 0;
|
||||||
|
g_activePrimitivesWrittenQueryId = 0;
|
||||||
|
g_activePrimitivesGeneratedQueryId = 0;
|
||||||
|
g_activeSamplesPassedQueryId = 0;
|
||||||
|
}
|
||||||
|
if (orphans.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Backend handles must be released by the backend that created them, so
|
||||||
|
// this runs while the function table is still populated. Both backends'
|
||||||
|
// DeleteBackendQuery are generation-guarded, so a handle whose renderer
|
||||||
|
// or ES context is already gone frees only the wrapper.
|
||||||
|
const auto deleteBackendQuery = MG_Backend::gBackendFunctionsTable.GL.DeleteBackendQuery;
|
||||||
|
for (const auto& [_, queryObject] : orphans) {
|
||||||
|
if (deleteBackendQuery && queryObject->backendHandle) {
|
||||||
|
deleteBackendQuery(queryObject->backendHandle);
|
||||||
|
}
|
||||||
|
delete queryObject;
|
||||||
|
}
|
||||||
|
MGLOG_D("DestroyAllQueryObjects: reclaimed %zu query object(s) the app left undeleted", orphans.size());
|
||||||
|
}
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
|
|||||||
@@ -29,4 +29,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void GetQueryBufferObjecti64v(GLuint id, GLuint buffer, GLenum pname, GLintptr offset);
|
void GetQueryBufferObjecti64v(GLuint id, GLuint buffer, GLenum pname, GLintptr offset);
|
||||||
void GetQueryBufferObjectui64v(GLuint id, GLuint buffer, GLenum pname, GLintptr offset);
|
void GetQueryBufferObjectui64v(GLuint id, GLuint buffer, GLenum pname, GLintptr offset);
|
||||||
void QueryCounter(GLuint id, GLenum target);
|
void QueryCounter(GLuint id, GLenum target);
|
||||||
|
// Destroys every still-registered query object exactly as DeleteQueries would.
|
||||||
|
// GL requires queries to die with their context; called only from full library
|
||||||
|
// teardown (DestroyImpl), where no context survives on any thread, so the
|
||||||
|
// process-global registry can be drained wholesale. Must run while the backend
|
||||||
|
// function table is still populated: each backend handle has to be released by
|
||||||
|
// the backend that created it, never by a later re-initialized one (whose
|
||||||
|
// DeleteBackendQuery would cast the wrapper to the wrong backend's type).
|
||||||
|
// Same contract as DestroyAllSyncObjects.
|
||||||
|
void DestroyAllQueryObjects();
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl
|
} // namespace MobileGL::MG_Impl::GLImpl
|
||||||
|
|||||||
@@ -646,9 +646,17 @@ namespace MobileGL::MG_State {
|
|||||||
for (SizeT stage = 0; stage < ProgramPipelineObject::kGraphicsStageCount; ++stage) {
|
for (SizeT stage = 0; stage < ProgramPipelineObject::kGraphicsStageCount; ++stage) {
|
||||||
const auto& stageProgram = pipeline->GetStageProgram(static_cast<ShaderStage>(stage));
|
const auto& stageProgram = pipeline->GetStageProgram(static_cast<ShaderStage>(stage));
|
||||||
if (!stageProgram) continue;
|
if (!stageProgram) continue;
|
||||||
for (const auto& shader : stageProgram->GetAttachedShaders()) {
|
// The stage program contributes the shaders its LAST LINK consumed, never
|
||||||
if (!shader || static_cast<SizeT>(shader->GetShaderStage()) != stage) continue;
|
// its live attach list: per GL 4.6 7.3/7.4 a pipeline stage executes the
|
||||||
composite->AttachShader(shader);
|
// stage program as last linked - glAttachShader and glCompileShader take
|
||||||
|
// effect only at the program's next link - and neither of those moves the
|
||||||
|
// link version this cache keys on, so reading live state here would let a
|
||||||
|
// post-link attach or recompile leak into the composite while the signature
|
||||||
|
// still hits. The pinned (source, node) makes the composite's Link()
|
||||||
|
// consume the very inputs that link consumed.
|
||||||
|
for (const auto& ref : stageProgram->GetLinkedShaderSnapshot()) {
|
||||||
|
if (!ref.shader || static_cast<SizeT>(ref.shader->GetShaderStage()) != stage) continue;
|
||||||
|
composite->AttachShaderWithPinnedLinkInput(ref);
|
||||||
anyStage = true;
|
anyStage = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,18 @@
|
|||||||
#include "FramebufferObject.h"
|
#include "FramebufferObject.h"
|
||||||
#include "MG_Util/Types.h"
|
#include "MG_Util/Types.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
namespace MobileGL::MG_State::GLState {
|
namespace MobileGL::MG_State::GLState {
|
||||||
|
// Starts at 1 so a zero-initialized memo slot can never carry a live object's id.
|
||||||
|
// Atomic for the same reason as the VAO counter: it costs nothing, and a duplicate
|
||||||
|
// id would resurrect exactly the ABA this id exists to kill.
|
||||||
|
static std::atomic<Uint64> s_nextFramebufferLifetimeId{1};
|
||||||
|
|
||||||
|
Uint64 FramebufferObject::AllocateLifetimeId() {
|
||||||
|
return s_nextFramebufferLifetimeId.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
// FramebufferAttachmentObject
|
// FramebufferAttachmentObject
|
||||||
FramebufferAttachmentObject::FramebufferAttachmentObject(
|
FramebufferAttachmentObject::FramebufferAttachmentObject(
|
||||||
const SharedPtr<MG_State::GLState::ITextureObject>& texture, TextureUploadTarget textureUploadTarget, Int level,
|
const SharedPtr<MG_State::GLState::ITextureObject>& texture, TextureUploadTarget textureUploadTarget, Int level,
|
||||||
|
|||||||
@@ -148,13 +148,25 @@ namespace MobileGL {
|
|||||||
|
|
||||||
Uint16 GetObjectVersion() const { return m_objectVersion; }
|
Uint16 GetObjectVersion() const { return m_objectVersion; }
|
||||||
|
|
||||||
|
// Globally-unique, never-reused id for THIS object's lifetime - the same
|
||||||
|
// contract as VertexArrayObject::GetLifetimeId(), and needed for the same
|
||||||
|
// reason: neither the GL name nor the heap address can tell a
|
||||||
|
// deleted-and-recreated framebuffer from the original, and m_objectVersion
|
||||||
|
// starts at 0 for every new object, so a backend memo keyed on
|
||||||
|
// (pointer, version) alone would silently inherit the dead object's entry
|
||||||
|
// (see VkRenderPassManager's per-draw fast-path memo).
|
||||||
|
Uint64 GetLifetimeId() const { return m_lifetimeId; }
|
||||||
|
|
||||||
Uint GetExternalIndex() const;
|
Uint GetExternalIndex() const;
|
||||||
Bool IsDefaultFramebuffer() const { return m_externalIndex == 0; }
|
Bool IsDefaultFramebuffer() const { return m_externalIndex == 0; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static Uint64 AllocateLifetimeId();
|
||||||
|
|
||||||
void BumpAttachmentVersion(FramebufferAttachmentType type);
|
void BumpAttachmentVersion(FramebufferAttachmentType type);
|
||||||
|
|
||||||
const Uint m_externalIndex = 0;
|
const Uint m_externalIndex = 0;
|
||||||
|
const Uint64 m_lifetimeId = AllocateLifetimeId();
|
||||||
FramebufferAttachmentObjectArray m_attachmentObjects;
|
FramebufferAttachmentObjectArray m_attachmentObjects;
|
||||||
FramebufferAttachmentVersionArray m_attachmentVersions;
|
FramebufferAttachmentVersionArray m_attachmentVersions;
|
||||||
|
|
||||||
|
|||||||
@@ -393,6 +393,14 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ProgramObject::AttachShaderWithPinnedLinkInput(const LinkedShaderRef& ref) {
|
||||||
|
if (!AttachShader(ref.shader)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_pinnedLinkInputs[ref.shader.get()] = ref;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
SizeT ProgramObject::DetachShader(const SharedPtr<ShaderObject>& shader) {
|
SizeT ProgramObject::DetachShader(const SharedPtr<ShaderObject>& shader) {
|
||||||
MGLOG_D("DetachShader called for shader %p from ProgramObject %u", shader.get(), m_externalIndex);
|
MGLOG_D("DetachShader called for shader %p from ProgramObject %u", shader.get(), m_externalIndex);
|
||||||
if (!ShaderIsAttached(shader)) {
|
if (!ShaderIsAttached(shader)) {
|
||||||
@@ -475,6 +483,8 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
AddDefaultFragmentShaderIfMissing();
|
AddDefaultFragmentShaderIfMissing();
|
||||||
}
|
}
|
||||||
if (m_shaders.empty()) {
|
if (m_shaders.empty()) {
|
||||||
|
// This IS the last link now, and it consumed nothing.
|
||||||
|
m_linkedShaderSnapshot.clear();
|
||||||
m_artifacts.infoLog = "No shader objects are attached to program.";
|
m_artifacts.infoLog = "No shader objects are attached to program.";
|
||||||
MGLOG_E("ProgramObject %u: Link failed - no shader objects attached.", m_externalIndex);
|
MGLOG_E("ProgramObject %u: Link failed - no shader objects attached.", m_externalIndex);
|
||||||
return;
|
return;
|
||||||
@@ -505,8 +515,19 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
Vector<SharedPtr<ShaderCompileTask>> deps;
|
Vector<SharedPtr<ShaderCompileTask>> deps;
|
||||||
deps.reserve(m_shaders.size());
|
deps.reserve(m_shaders.size());
|
||||||
task->in.shaders.reserve(m_shaders.size());
|
task->in.shaders.reserve(m_shaders.size());
|
||||||
|
m_linkedShaderSnapshot.clear();
|
||||||
|
m_linkedShaderSnapshot.reserve(m_shaders.size());
|
||||||
for (const auto& shader : m_shaders) {
|
for (const auto& shader : m_shaders) {
|
||||||
const SharedPtr<ShaderCompileTask>& node = shader->CompiledNodeForLink();
|
// A pipeline composite pins the (source, node) each stage program's LAST link
|
||||||
|
// consumed (AttachShaderWithPinnedLinkInput); an ordinary program takes the
|
||||||
|
// shader's current ones. Without the pin a post-link recompile would leak a
|
||||||
|
// shader the stage program never linked into the composite.
|
||||||
|
SharedPtr<const String> sourcePtr = shader->GetShaderSourcePtr();
|
||||||
|
SharedPtr<ShaderCompileTask> node = shader->CompiledNodeForLink();
|
||||||
|
if (const auto pinned = m_pinnedLinkInputs.find(shader.get()); pinned != m_pinnedLinkInputs.end()) {
|
||||||
|
sourcePtr = pinned->second.source;
|
||||||
|
node = pinned->second.node;
|
||||||
|
}
|
||||||
if (node) {
|
if (node) {
|
||||||
// This link is now an observer of that node's result, and the ShaderObject is
|
// This link is now an observer of that node's result, and the ShaderObject is
|
||||||
// no longer the only route to it: without the marker, the ordinary
|
// no longer the only route to it: without the marker, the ordinary
|
||||||
@@ -515,7 +536,10 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
node->MarkLinkReferenced();
|
node->MarkLinkReferenced();
|
||||||
if (!node->IsTerminal()) deps.push_back(node);
|
if (!node->IsTerminal()) deps.push_back(node);
|
||||||
}
|
}
|
||||||
task->in.shaders.push_back({shader->GetShaderStage(), shader->GetShaderSourcePtr(), node});
|
task->in.shaders.push_back({shader->GetShaderStage(), sourcePtr, node});
|
||||||
|
// What "as last linked" will mean for this program from now on - the pipeline
|
||||||
|
// composite cache rebuilds from exactly this set (GetProgramForDraw).
|
||||||
|
m_linkedShaderSnapshot.push_back({shader, sourcePtr, node});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase B of the same link: SPIR-V generation, spirv-opt and the global-UBO routing
|
// Phase B of the same link: SPIR-V generation, spirv-opt and the global-UBO routing
|
||||||
|
|||||||
@@ -60,6 +60,26 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
|
|
||||||
Vector<SharedPtr<ShaderObject>>& GetAttachedShaders();
|
Vector<SharedPtr<ShaderObject>>& GetAttachedShaders();
|
||||||
const Vector<SharedPtr<ShaderObject>>& GetAttachedShaders() const;
|
const Vector<SharedPtr<ShaderObject>>& GetAttachedShaders() const;
|
||||||
|
|
||||||
|
// One shader exactly as this program's last Link() consumed it: the object, the
|
||||||
|
// source snapshot, and the compile node taken at that link's enqueue. GL 4.6 7.3/7.4
|
||||||
|
// makes this triple - not the live attach list, not the shader's current compile -
|
||||||
|
// what a program pipeline stage executes ("as last linked"): glAttachShader and
|
||||||
|
// glCompileShader take effect only at the program's next link, yet neither moves
|
||||||
|
// m_linkVersion, so anything keyed on the link generation must consume this
|
||||||
|
// snapshot rather than re-read the live state.
|
||||||
|
struct LinkedShaderRef {
|
||||||
|
SharedPtr<ShaderObject> shader;
|
||||||
|
SharedPtr<const String> source;
|
||||||
|
SharedPtr<ShaderCompileTask> node;
|
||||||
|
};
|
||||||
|
// The last link's full input set; empty when this program has never linked (or its
|
||||||
|
// last link had no shaders attached). GL-thread-owned, rebuilt in Link()'s prologue.
|
||||||
|
const Vector<LinkedShaderRef>& GetLinkedShaderSnapshot() const { return m_linkedShaderSnapshot; }
|
||||||
|
// Pipeline-composite attach: AttachShader plus a pin that makes THIS program's
|
||||||
|
// Link() consume ref's (source, node) instead of the shader's current ones, so a
|
||||||
|
// post-link recompile of the stage program's shader cannot leak into the composite.
|
||||||
|
bool AttachShaderWithPinnedLinkInput(const LinkedShaderRef& ref);
|
||||||
const String& GetInfoLog() const { return Artifacts().infoLog; }
|
const String& GetInfoLog() const { return Artifacts().infoLog; }
|
||||||
// glCreateShaderProgramv folds the shader's compile log into the program's log, which
|
// glCreateShaderProgramv folds the shader's compile log into the program's log, which
|
||||||
// is the only place a caller can read it from once the shader name is gone.
|
// is the only place a caller can read it from once the shader name is gone.
|
||||||
@@ -786,6 +806,13 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
// order - and the name is the only coordinate all three agree on. Absent from the map
|
// order - and the name is the only coordinate all three agree on. Absent from the map
|
||||||
// means "never rebound", and the shader's declared binding still stands.
|
// means "never rebound", and the shader's declared binding still stands.
|
||||||
void SetShaderStorageBlockBinding(const String& blockName, Uint binding) {
|
void SetShaderStorageBlockBinding(const String& blockName, Uint binding) {
|
||||||
|
// Equality bail-out like SetUniformBlockBinding's: the pipeline composite
|
||||||
|
// mirror replays every override each draw, and without this every replay
|
||||||
|
// would churn m_blockBindingVersion and rebuild whatever keys on it.
|
||||||
|
const auto it = Artifacts().shaderStorageBlockBinding.find(blockName);
|
||||||
|
if (it != Artifacts().shaderStorageBlockBinding.end() && it->second == static_cast<Int>(binding)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Artifacts().shaderStorageBlockBinding[blockName] = static_cast<Int>(binding);
|
Artifacts().shaderStorageBlockBinding[blockName] = static_cast<Int>(binding);
|
||||||
// Deliberately NOT m_backendStateVersion: Espryt's entry point never forces a
|
// Deliberately NOT m_backendStateVersion: Espryt's entry point never forces a
|
||||||
// program build off this, and bumping that version would start doing so. The
|
// program build off this, and bumping that version would start doing so. The
|
||||||
@@ -1223,6 +1250,13 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
// glGetAttachedShaders / GL_ATTACHED_SHADERS / the orphan-shader sweep need no join.
|
// glGetAttachedShaders / GL_ATTACHED_SHADERS / the orphan-shader sweep need no join.
|
||||||
Vector<SharedPtr<ShaderObject>> m_shaders;
|
Vector<SharedPtr<ShaderObject>> m_shaders;
|
||||||
Vector<SharedPtr<ShaderObject>> m_detachedShaders; // Store detached shaders and remove on next link
|
Vector<SharedPtr<ShaderObject>> m_detachedShaders; // Store detached shaders and remove on next link
|
||||||
|
// See GetLinkedShaderSnapshot. Holding the SharedPtrs here is deliberate: the
|
||||||
|
// "as last linked" set must survive detach-and-delete of its shaders (the
|
||||||
|
// glCreateShaderProgramv shape) until the next link replaces it.
|
||||||
|
Vector<LinkedShaderRef> m_linkedShaderSnapshot;
|
||||||
|
// See AttachShaderWithPinnedLinkInput. Populated only on pipeline composites,
|
||||||
|
// which never detach, so entries need no removal path. GL-thread-owned.
|
||||||
|
UnorderedMap<const ShaderObject*, LinkedShaderRef> m_pinnedLinkInputs;
|
||||||
|
|
||||||
// Link INPUTS (all "take effect at the next link" per GL): glBindAttribLocation,
|
// Link INPUTS (all "take effect at the next link" per GL): glBindAttribLocation,
|
||||||
// glBindFragDataLocation(Indexed), glTransformFeedbackVaryings, and the draw-buffer
|
// glBindFragDataLocation(Indexed), glTransformFeedbackVaryings, and the draw-buffer
|
||||||
|
|||||||
@@ -140,6 +140,13 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ShaderObject::Compile() {
|
void ShaderObject::Compile() {
|
||||||
|
// The compile-environment snapshot is taken HERE, on the GL thread, and handed to
|
||||||
|
// the job. Everything the pipeline needs to know about the device comes through it,
|
||||||
|
// never through pActiveBackendObject - that is what makes the body movable.
|
||||||
|
// Hoisted above the memo check because the memo must be env-disciplined too (below).
|
||||||
|
const SharedPtr<const MG_Util::ShaderTranspiler::CompileEnv> env =
|
||||||
|
MG_Util::ShaderTranspiler::GetCurrentCompileEnv();
|
||||||
|
|
||||||
// P0b layer 1, as a tri-state: the memo is "the node in m_compiled was built from
|
// P0b layer 1, as a tri-state: the memo is "the node in m_compiled was built from
|
||||||
// the string m_source still points at". SetShaderSource only swaps that pointer when
|
// the string m_source still points at". SetShaderSource only swaps that pointer when
|
||||||
// the text actually differs, so this is a pointer compare, and it covers Pending as
|
// the text actually differs, so this is a pointer compare, and it covers Pending as
|
||||||
@@ -152,7 +159,18 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
// ClaimParsedShader's on-demand re-parse needs - a real recompile would have handed
|
// ClaimParsedShader's on-demand re-parse needs - a real recompile would have handed
|
||||||
// the next link a fresh parse, the no-op hands it a fresh re-parse of the identical
|
// the next link a fresh parse, the no-op hands it a fresh re-parse of the identical
|
||||||
// source instead. Same result, one parse either way.
|
// source instead. Same result, one parse either way.
|
||||||
if (HasMemoizedCompile()) return;
|
//
|
||||||
|
// The environment joins the check (ShaderSourceKey.h's memo-hazard rule: a memo
|
||||||
|
// must never be handed back under an environment other than the one it was
|
||||||
|
// computed against). Layers 2 and 3 key on the fingerprint, but this memo sits
|
||||||
|
// ABOVE both, so without this compare a node computed against a dead environment
|
||||||
|
// - e.g. a compute shader rejected against the pre-capability fallback limits -
|
||||||
|
// would keep answering forever while a fresh object with byte-identical source
|
||||||
|
// compiles fine. The fingerprint is a content hash, so a republish of identical
|
||||||
|
// capabilities still hits.
|
||||||
|
if (HasMemoizedCompile() && m_compiled->env != nullptr && m_compiled->env->fingerprint == env->fingerprint) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Two reasons to stay on this thread, one rule. Without the async flag the whole
|
// Two reasons to stay on this thread, one rule. Without the async flag the whole
|
||||||
// path must be byte-identical to the synchronous implementation, and a cache-less
|
// path must be byte-identical to the synchronous implementation, and a cache-less
|
||||||
@@ -168,12 +186,6 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
// glMaxShaderCompilerThreadsKHR(0) and a flag-off build both bypass sharing exactly
|
// glMaxShaderCompilerThreadsKHR(0) and a flag-off build both bypass sharing exactly
|
||||||
// as they bypass the pool, and their behaviour stays byte-identical to pre-stage-6.
|
// as they bypass the pool, and their behaviour stays byte-identical to pre-stage-6.
|
||||||
const Bool runOnPool = m_preprocessCache && MG_Util::Async::AsyncShaderCompileActive();
|
const Bool runOnPool = m_preprocessCache && MG_Util::Async::AsyncShaderCompileActive();
|
||||||
|
|
||||||
// The compile-environment snapshot is taken HERE, on the GL thread, and handed to
|
|
||||||
// the job. Everything the pipeline needs to know about the device comes through it,
|
|
||||||
// never through pActiveBackendObject - that is what makes the body movable.
|
|
||||||
const SharedPtr<const MG_Util::ShaderTranspiler::CompileEnv> env =
|
|
||||||
MG_Util::ShaderTranspiler::GetCurrentCompileEnv();
|
|
||||||
const Uint64 sourceHash = ShaderPreprocessCache::HashSource(*m_source);
|
const Uint64 sourceHash = ShaderPreprocessCache::HashSource(*m_source);
|
||||||
|
|
||||||
// ---- P1 stage 6: adopt an equivalent compile instead of enqueueing a duplicate ----
|
// ---- P1 stage 6: adopt an equivalent compile instead of enqueueing a duplicate ----
|
||||||
|
|||||||
@@ -91,9 +91,15 @@ namespace MobileGL {
|
|||||||
BlockRelayout(IRContext* irContext, Bool std140)
|
BlockRelayout(IRContext* irContext, Bool std140)
|
||||||
: m_irContext(irContext), m_std140(std140) {}
|
: m_irContext(irContext), m_std140(std140) {}
|
||||||
|
|
||||||
// Size and alignment of `typeId`, applying every stride decoration it implies
|
// Size and alignment of `typeId`, QUEUING every offset/stride decoration it
|
||||||
// on the way down. Zero size means "not a type this layout knows how to
|
// implies on the way down. Zero size means "not a type this layout knows how
|
||||||
// describe"; the caller then leaves the block alone rather than guessing.
|
// to describe"; the caller then leaves the block alone rather than guessing.
|
||||||
|
// The queue is what makes that fallback honest: measurement must be
|
||||||
|
// side-effect-free until it is known to succeed, or a mid-struct failure
|
||||||
|
// would leave the block half-relaid-out - members before the failing one at
|
||||||
|
// compacted 32-bit offsets, members after it at the original 64-bit ones, a
|
||||||
|
// layout matching neither convention. Commit() flushes the queue and is
|
||||||
|
// called only on a successful Measure of the whole block.
|
||||||
struct Extent {
|
struct Extent {
|
||||||
Uint32 size = 0;
|
Uint32 size = 0;
|
||||||
Uint32 alignment = 0;
|
Uint32 alignment = 0;
|
||||||
@@ -108,7 +114,29 @@ namespace MobileGL {
|
|||||||
return extent;
|
return extent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flushes the decoration writes a successful Measure queued. Call exactly
|
||||||
|
// once, only when Measure returned a non-zero size; a failed measurement's
|
||||||
|
// queue dies with this per-block instance, leaving the module untouched.
|
||||||
|
void Commit() {
|
||||||
|
for (const PendingDecoration& pending : m_pendingWrites) {
|
||||||
|
if (pending.member) {
|
||||||
|
ApplyMemberDecoration(pending.targetId, pending.memberIndex, pending.decoration,
|
||||||
|
pending.value);
|
||||||
|
} else {
|
||||||
|
ApplyTypeDecoration(pending.targetId, pending.decoration, pending.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_pendingWrites.clear();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct PendingDecoration {
|
||||||
|
Bool member = false;
|
||||||
|
Uint32 targetId = 0;
|
||||||
|
Uint32 memberIndex = 0;
|
||||||
|
spv::Decoration decoration = spv::Decoration::Offset;
|
||||||
|
Uint32 value = 0;
|
||||||
|
};
|
||||||
Extent MeasureUncached(Uint32 typeId) {
|
Extent MeasureUncached(Uint32 typeId) {
|
||||||
const Instruction* type = m_irContext->get_def_use_mgr()->GetDef(typeId);
|
const Instruction* type = m_irContext->get_def_use_mgr()->GetDef(typeId);
|
||||||
if (type == nullptr) return {};
|
if (type == nullptr) return {};
|
||||||
@@ -204,7 +232,17 @@ namespace MobileGL {
|
|||||||
return length->GetSingleWordInOperand(0);
|
return length->GetSingleWordInOperand(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Queue-only during measurement; the module is mutated in Commit().
|
||||||
void SetTypeDecoration(Uint32 targetId, spv::Decoration decoration, Uint32 value) {
|
void SetTypeDecoration(Uint32 targetId, spv::Decoration decoration, Uint32 value) {
|
||||||
|
m_pendingWrites.push_back({false, targetId, 0, decoration, value});
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetMemberDecoration(Uint32 structId, Uint32 member, spv::Decoration decoration,
|
||||||
|
Uint32 value) {
|
||||||
|
m_pendingWrites.push_back({true, structId, member, decoration, value});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplyTypeDecoration(Uint32 targetId, spv::Decoration decoration, Uint32 value) {
|
||||||
for (Instruction& annotation : m_irContext->annotations()) {
|
for (Instruction& annotation : m_irContext->annotations()) {
|
||||||
if (annotation.opcode() != spv::Op::OpDecorate) continue;
|
if (annotation.opcode() != spv::Op::OpDecorate) continue;
|
||||||
if (annotation.GetSingleWordInOperand(0) != targetId) continue;
|
if (annotation.GetSingleWordInOperand(0) != targetId) continue;
|
||||||
@@ -216,8 +254,8 @@ namespace MobileGL {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetMemberDecoration(Uint32 structId, Uint32 member, spv::Decoration decoration,
|
void ApplyMemberDecoration(Uint32 structId, Uint32 member, spv::Decoration decoration,
|
||||||
Uint32 value) {
|
Uint32 value) {
|
||||||
for (Instruction& annotation : m_irContext->annotations()) {
|
for (Instruction& annotation : m_irContext->annotations()) {
|
||||||
if (annotation.opcode() != spv::Op::OpMemberDecorate) continue;
|
if (annotation.opcode() != spv::Op::OpMemberDecorate) continue;
|
||||||
if (annotation.GetSingleWordInOperand(0) != structId) continue;
|
if (annotation.GetSingleWordInOperand(0) != structId) continue;
|
||||||
@@ -233,6 +271,7 @@ namespace MobileGL {
|
|||||||
IRContext* m_irContext = nullptr;
|
IRContext* m_irContext = nullptr;
|
||||||
Bool m_std140 = true;
|
Bool m_std140 = true;
|
||||||
std::unordered_map<Uint32, Extent> m_extents;
|
std::unordered_map<Uint32, Extent> m_extents;
|
||||||
|
std::vector<PendingDecoration> m_pendingWrites;
|
||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -529,9 +568,12 @@ namespace MobileGL {
|
|||||||
// A member shape the layout rules here do not describe. Leaving the block
|
// A member shape the layout rules here do not describe. Leaving the block
|
||||||
// at its 64-bit offsets keeps the module valid for Vulkan; SPIRV-Cross will
|
// at its 64-bit offsets keeps the module valid for Vulkan; SPIRV-Cross will
|
||||||
// decline it for ESSL, which is the same outcome as before the demotion.
|
// decline it for ESSL, which is the same outcome as before the demotion.
|
||||||
|
// Nothing was written: Measure only queues, and the queue dies here.
|
||||||
MGLOG_D("DemoteFloat64Pass: block %%%u contains a member this pass cannot lay "
|
MGLOG_D("DemoteFloat64Pass: block %%%u contains a member this pass cannot lay "
|
||||||
"out; its 64-bit offsets are left in place",
|
"out; its 64-bit offsets are left in place",
|
||||||
blockType->result_id());
|
blockType->result_id());
|
||||||
|
} else {
|
||||||
|
relayout.Commit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user