mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
[Fix, Test] (MG_Impl, MG_Backend, MG_Util, MG_Test): memory-audit wave 1 - sync/query teardown and GLES GPU-object lifetimes
This commit is contained in:
+7
-5
@@ -14,6 +14,7 @@
|
||||
#include <MG_State/EGLState/Core.h>
|
||||
#include <MG_Impl/GLImpl/Texture/ProxyTexture.h>
|
||||
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
|
||||
#include <MG_Impl/GLImpl/Query/GL_Query.h>
|
||||
#include <MG_Impl/GLImpl/Sync/GL_Sync.h>
|
||||
#include <MG_Util/Async/ShaderCompilePool.h>
|
||||
#include <MG_Util/ShaderTranspiler/ShaderCompiler.h>
|
||||
@@ -45,12 +46,13 @@ namespace MobileGL {
|
||||
// both of which this function is about to destroy. This is the one
|
||||
// cancellation path in the whole design that waits.
|
||||
MG_Util::Async::ShaderCompilePool::Get().StopAndDrain();
|
||||
// GL syncs die with their contexts, and every context is gone by the
|
||||
// time full teardown runs: drain the live-sync registry while the
|
||||
// backend function table can still release the backend handles (and
|
||||
// before a re-initialized library could pair them with the wrong
|
||||
// backend's DeleteSync).
|
||||
// GL syncs and queries die with their contexts, and every context is gone
|
||||
// by the time full teardown runs: drain both live registries while the
|
||||
// backend function table can still release the backend handles (and before
|
||||
// a re-initialized library could pair them with the wrong backend's
|
||||
// DeleteSync / DeleteBackendQuery).
|
||||
MG_Impl::GLImpl::DestroyAllSyncObjects();
|
||||
MG_Impl::GLImpl::DestroyAllQueryObjects();
|
||||
MG_Backend::pActiveBackendObject.reset();
|
||||
MG_State::pGLContext.reset();
|
||||
MG_State::pEGLContext.reset();
|
||||
|
||||
@@ -2205,6 +2205,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// accident - and it never covered the monolithic glUseProgram path at all - so the
|
||||
// dependency is stated here instead.
|
||||
if (!twin->GetBackendProgramId() ||
|
||||
twin->GetContextGeneration() != g_backendContextGeneration ||
|
||||
twin->GetSyncedLinkVersion() != currentProgram->GetLinkVersion() ||
|
||||
twin->GetSyncedImageUnitVersion() != currentProgram->GetImageUnitVersion() ||
|
||||
twin->GetSnormFallbackClampOutputMask() != g_snormFallbackClampOutputMask ||
|
||||
@@ -3054,6 +3055,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw();
|
||||
const auto program = GetCurrentBackendProgram();
|
||||
if (!currentProgram || program == nullptr ||
|
||||
program->GetContextGeneration() != g_backendContextGeneration ||
|
||||
program->GetSyncedLinkVersion() != currentProgram->GetLinkVersion()) {
|
||||
return true;
|
||||
}
|
||||
@@ -5899,6 +5901,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// fallen behind is about to be rebuilt anyway, and its current driver interface is
|
||||
// the PREVIOUS link's - applying to it could land the binding on an unrelated block.
|
||||
if (!backendObj->GetBackendProgramId() ||
|
||||
backendObj->GetContextGeneration() != g_backendContextGeneration ||
|
||||
backendObj->GetSyncedLinkVersion() != programObject->GetLinkVersion()) {
|
||||
return; // SyncToBackend's reseed will carry it
|
||||
}
|
||||
|
||||
@@ -1468,6 +1468,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
m_clientAttributeBufferIds.fill(0);
|
||||
m_contextGeneration = g_backendContextGeneration;
|
||||
g_GLESFuncs.glGenVertexArrays(1, &m_backendVAOId);
|
||||
if (m_backendVAOId == 0) {
|
||||
MGLOG_E_ONCE("Failed to generate vertex array object.");
|
||||
@@ -1481,17 +1482,28 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (InProcessTeardown()) {
|
||||
return; // see InProcessTeardown(): the driver may be unloaded already
|
||||
}
|
||||
const Bool contextCurrent = m_contextGeneration == g_backendContextGeneration;
|
||||
if (m_backendVAOId != 0) {
|
||||
// Scrub the binding shadow whether or not the id can still be
|
||||
// deleted: a recycled name must never satisfy the shadow's dedup.
|
||||
NoteVAOIdDeleted(m_backendVAOId);
|
||||
if (contextCurrent && g_GLESFuncs.glDeleteVertexArrays) {
|
||||
g_GLESFuncs.glDeleteVertexArrays(1, &m_backendVAOId);
|
||||
}
|
||||
m_backendVAOId = 0;
|
||||
}
|
||||
for (auto& bufferId : m_clientAttributeBufferIds) {
|
||||
if (bufferId != 0) {
|
||||
BufferImpl::NoteBufferIdDeleted(bufferId);
|
||||
g_GLESFuncs.glDeleteBuffers(1, &bufferId);
|
||||
bufferId = 0;
|
||||
if (bufferId == 0) {
|
||||
continue;
|
||||
}
|
||||
// Same discipline as the VAO id itself: a buffer id from a dead
|
||||
// context belongs to that context and must never be deleted as a
|
||||
// recycled name in a successor context.
|
||||
BufferImpl::NoteBufferIdDeleted(bufferId);
|
||||
if (contextCurrent && g_GLESFuncs.glDeleteBuffers) {
|
||||
g_GLESFuncs.glDeleteBuffers(1, &bufferId);
|
||||
}
|
||||
bufferId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1635,6 +1647,30 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// PrepareForDraw's BindCurrentVAO establishes the draw binding regardless.
|
||||
const Uint32 currentConfigVersion = stateVAOObject->GetConfigVersion();
|
||||
const Uint16 currentIndexBufferVersion = stateVAOObject->GetIndexBufferBindingSlot().GetVersion();
|
||||
|
||||
// The ES context was recreated since this twin last ran. Its GL names belong to
|
||||
// the dead context and are gone; mint a fresh VAO and force every attribute /
|
||||
// index-binding cache to re-emit. No glDelete* here: the old names are not ours
|
||||
// to delete in the successor context.
|
||||
if (m_contextGeneration != g_backendContextGeneration) {
|
||||
InvalidateVAOBindingCache();
|
||||
m_backendVAOId = 0;
|
||||
m_contextGeneration = g_backendContextGeneration;
|
||||
m_clientAttributeBufferIds.fill(0);
|
||||
m_isInitialized = false;
|
||||
m_resolvedDrawBuffers = {};
|
||||
m_pendingAttribValueMask = {};
|
||||
m_hasSyncedConfigVersion = false;
|
||||
m_syncedConfigVersion = 0;
|
||||
m_syncedIndexBufferVersion = static_cast<Uint16>(currentIndexBufferVersion + 1);
|
||||
m_syncedAttributeVersions.fill({});
|
||||
m_syncedFetchBaseInstance = 0;
|
||||
g_GLESFuncs.glGenVertexArrays(1, &m_backendVAOId);
|
||||
if (m_backendVAOId == 0) {
|
||||
MGLOG_E_ONCE("Failed to recreate vertex array object for a new ES context.");
|
||||
}
|
||||
}
|
||||
|
||||
const Bool attributesDirty = !m_hasSyncedConfigVersion || m_syncedConfigVersion != currentConfigVersion;
|
||||
const Bool indexBufferDirty = currentIndexBufferVersion != m_syncedIndexBufferVersion;
|
||||
|
||||
@@ -1978,6 +2014,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
TextureSwizzleParam::Alpha};
|
||||
m_cacheDepthStencilTextureMode = GL_DEPTH_COMPONENT;
|
||||
m_forceTextureParamsResync = true;
|
||||
m_forceSamplerResync = true;
|
||||
}
|
||||
|
||||
// Sets the backend GL unpack state to MobileGL's upload default for the scope,
|
||||
@@ -2395,6 +2432,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return;
|
||||
}
|
||||
|
||||
// The ES context was recreated since this twin last ran. Recreate the
|
||||
// texture id before any version-based early-out below: those versions are
|
||||
// frontend versions and do not move when only the backend context changed.
|
||||
if (m_contextGeneration != g_backendContextGeneration) {
|
||||
RecreateBackendTexture();
|
||||
}
|
||||
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
@@ -3119,14 +3163,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_contextGeneration != g_backendContextGeneration) {
|
||||
RecreateBackendTexture();
|
||||
}
|
||||
|
||||
auto* samplerObject = stateTextureObject->GetSamplerObject().get();
|
||||
Uint currentSamplerVersion = samplerObject->GetVersion();
|
||||
if (m_syncedSamplerVersion == currentSamplerVersion) {
|
||||
if (m_syncedSamplerVersion == currentSamplerVersion && !m_forceSamplerResync) {
|
||||
MGLOG_D("Sampler parameters have not changed for texture ID: %u, skipping sync.", m_backendTextureId);
|
||||
return;
|
||||
}
|
||||
|
||||
m_syncedSamplerVersion = currentSamplerVersion;
|
||||
m_forceSamplerResync = false;
|
||||
|
||||
MGLOG_D("Syncing texture built-in sampler with backend ID %u to backend for state ID %u",
|
||||
m_backendTextureId, stateTextureObject->GetExternalIndex());
|
||||
@@ -3229,6 +3278,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_contextGeneration != g_backendContextGeneration) {
|
||||
RecreateBackendTexture();
|
||||
}
|
||||
|
||||
Uint16 currentTextureParamsVersion = stateTextureObject->GetTextureParamsVersion();
|
||||
if (m_syncedTextureParamsVersion == currentTextureParamsVersion && !m_forceTextureParamsResync) {
|
||||
MGLOG_D("Texture parameters have not changed for texture ID: %u, skipping sync.", m_backendTextureId);
|
||||
@@ -3902,6 +3955,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
MGLOG_E_ONCE("State FBO object is null, cannot sync to backend.");
|
||||
return;
|
||||
}
|
||||
// Recreate the driver FBO when the ES context has moved on. The old id is
|
||||
// gone with the old context; calling glDeleteFramebuffers on its recycled
|
||||
// numeric value could delete a new live FBO, so simply abandon it.
|
||||
if (m_contextGeneration != g_backendContextGeneration) {
|
||||
m_backendFBOId = 0;
|
||||
m_contextGeneration = g_backendContextGeneration;
|
||||
g_GLESFuncs.glGenFramebuffers(1, &m_backendFBOId);
|
||||
if (m_backendFBOId == 0) {
|
||||
MGLOG_E_ONCE("Failed to recreate framebuffer object for a new ES context.");
|
||||
}
|
||||
InvalidateFramebufferBindingCache();
|
||||
InvalidateSyncedState();
|
||||
}
|
||||
MGLOG_D("Syncing FBO with backend ID %u to backend for state ID %u, as %s FBO", m_backendFBOId,
|
||||
stateFBOObject->GetExternalIndex(), (asTarget == FramebufferTarget::Draw ? "DRAW" : "READ"));
|
||||
GLenum glFBOTarget = MG_Util::ConvertFramebufferTargetToGLEnum(asTarget);
|
||||
@@ -4412,10 +4478,27 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
Uint g_lastUsedBackendProgramId = 0;
|
||||
StateBackendObjectRegistry<MG_State::GLState::ProgramObject, BackendProgramObjectImpl> g_backendProgramObjects;
|
||||
|
||||
void DeleteBackendProgramGlobalUbo(Uint& bufferId, Uint contextGeneration) {
|
||||
if (bufferId == 0) {
|
||||
return;
|
||||
}
|
||||
// Only a buffer that belongs to the LIVE context may be deleted. A stale
|
||||
// generation means the old ES context already reclaimed it; handing its
|
||||
// recycled numeric id to glDeleteBuffers could delete a new live buffer.
|
||||
if (contextGeneration == g_backendContextGeneration) {
|
||||
BufferImpl::NoteBufferIdDeleted(bufferId);
|
||||
if (g_GLESFuncs.glDeleteBuffers) {
|
||||
g_GLESFuncs.glDeleteBuffers(1, &bufferId);
|
||||
}
|
||||
}
|
||||
bufferId = 0;
|
||||
}
|
||||
|
||||
BackendProgramObjectImpl::BackendProgramObjectImpl() {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
m_contextGeneration = g_backendContextGeneration;
|
||||
m_backendProgramId = g_GLESFuncs.glCreateProgram();
|
||||
if (m_backendProgramId == 0) {
|
||||
MGLOG_E_ONCE("Failed to create program object in backend.");
|
||||
@@ -4433,14 +4516,23 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (InProcessTeardown()) {
|
||||
return; // see InProcessTeardown(): the driver may be unloaded already
|
||||
}
|
||||
DeleteBackendProgramGlobalUbo(m_backendGlobalUBOId, m_contextGeneration);
|
||||
if (m_backendProgramId != 0) {
|
||||
// Same generation rule as the global UBO: a program id from a dead
|
||||
// context is gone already and must not be deleted as a recycled name
|
||||
// in a successor context.
|
||||
if (m_contextGeneration == g_backendContextGeneration) {
|
||||
MGLOG_D("Deleting backend program object with ID: %u", m_backendProgramId);
|
||||
if (g_GLESFuncs.glDeleteProgram) {
|
||||
g_GLESFuncs.glDeleteProgram(m_backendProgramId);
|
||||
}
|
||||
}
|
||||
// The driver may recycle this GL name for a future program; a stale
|
||||
// guard entry would then wrongly skip the glUseProgram for it.
|
||||
if (g_lastUsedBackendProgramId == m_backendProgramId) {
|
||||
g_lastUsedBackendProgramId = 0;
|
||||
}
|
||||
m_backendProgramId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4677,6 +4769,31 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return;
|
||||
}
|
||||
|
||||
// The ES context was recreated since this twin last ran. The old program id
|
||||
// and global UBO id belong to the dead context; drop them without GL calls
|
||||
// and mint a fresh program before reusing any cached reflection/version data.
|
||||
if (m_contextGeneration != g_backendContextGeneration) {
|
||||
DeleteBackendProgramGlobalUbo(m_backendGlobalUBOId, m_contextGeneration);
|
||||
m_backendProgramId = 0;
|
||||
m_contextGeneration = g_backendContextGeneration;
|
||||
m_backendProgramId = g_GLESFuncs.glCreateProgram();
|
||||
if (m_backendProgramId == 0) {
|
||||
MGLOG_E_ONCE("Failed to recreate backend program object for a new ES context.");
|
||||
}
|
||||
m_isInitialized = false;
|
||||
m_backendProgramUsable = false;
|
||||
m_syncedLinkVersion = ~0u;
|
||||
m_syncedImageUnitVersion = ~0u;
|
||||
m_lastUploadedGlobalUboVersion = ~0u;
|
||||
m_globalUboBackendBlockIndex = -1;
|
||||
m_globalUboBackendBlockSize = 0;
|
||||
m_uniformBlockBackendIndices.clear();
|
||||
m_samplerUniformBindings.clear();
|
||||
m_formatlessImageUnits.clear();
|
||||
m_imageUnitFormatSignature = 0;
|
||||
m_globalUboRingAllocation = {};
|
||||
}
|
||||
|
||||
MGLOG_D("Syncing program to backend. State program ID: %u, Backend ID: %u",
|
||||
stateProgramObject->GetExternalIndex(), m_backendProgramId);
|
||||
// Every link-derived cache below (incl. m_samplerUniformBindings and its
|
||||
@@ -5154,7 +5271,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
}
|
||||
|
||||
// Create global UBO
|
||||
// Create global UBO. Delete any previous one first: relink reuses this
|
||||
// backend program, and without this every relink leaked the old buffer.
|
||||
DeleteBackendProgramGlobalUbo(m_backendGlobalUBOId, m_contextGeneration);
|
||||
if (stateProgramObject->GetUBOSize() > 0) {
|
||||
g_GLESFuncs.glGenBuffers(1, &m_backendGlobalUBOId);
|
||||
g_GLESFuncs.glBindBuffer(GL_UNIFORM_BUFFER, m_backendGlobalUBOId);
|
||||
@@ -5389,6 +5508,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_contextGeneration != g_backendContextGeneration) {
|
||||
// Old sampler id died with the old context; abandon it and mint a new
|
||||
// one before the version-based early-out below can reuse a dead name.
|
||||
m_backendSamplerId = 0;
|
||||
m_contextGeneration = g_backendContextGeneration;
|
||||
g_GLESFuncs.glGenSamplers(1, &m_backendSamplerId);
|
||||
if (m_backendSamplerId == 0) {
|
||||
MGLOG_E_ONCE("Failed to recreate sampler object for a new ES context.");
|
||||
}
|
||||
m_isInitialized = false;
|
||||
m_cacheSamplerParameters = {};
|
||||
g_boundSamplersCache.fill(nullptr);
|
||||
}
|
||||
|
||||
Uint currentSamplerVersion = stateSamplerObject->GetVersion();
|
||||
if (m_isInitialized && m_syncedSamplerVersion == currentSamplerVersion) {
|
||||
MGLOG_D("Sampler parameters have not changed for sampler ID: %u, skipping sync.",
|
||||
@@ -5533,6 +5666,23 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_contextGeneration != g_backendContextGeneration) {
|
||||
// The old renderbuffer id died with the old context. Abandon it and
|
||||
// force a fresh allocation instead of letting the parameter early-out
|
||||
// below keep using a dead name.
|
||||
m_backendRBOId = 0;
|
||||
m_contextGeneration = g_backendContextGeneration;
|
||||
g_GLESFuncs.glGenRenderbuffers(1, &m_backendRBOId);
|
||||
if (m_backendRBOId == 0) {
|
||||
MGLOG_E_ONCE("Failed to recreate renderbuffer object for a new ES context.");
|
||||
}
|
||||
m_isInitialized = false;
|
||||
m_cacheInternalFormat = TextureInternalFormat::Unknown;
|
||||
m_cacheWidth = -1;
|
||||
m_cacheHeight = -1;
|
||||
m_cacheSamples = -1;
|
||||
}
|
||||
|
||||
MGLOG_D("Syncing RBO with backend ID %u to backend for state ID %u", m_backendRBOId,
|
||||
stateRBOObject->GetExternalIndex());
|
||||
|
||||
|
||||
@@ -406,6 +406,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
void SyncClientSideAttributesForDrawArrays(
|
||||
const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject, GLint first, GLsizei count);
|
||||
Uint GetBackendVertexArrayId() const { return m_backendVAOId; }
|
||||
Uint GetContextGeneration() const { return m_contextGeneration; }
|
||||
void Bind() const;
|
||||
|
||||
// Draw-path memo of SyncNeccessaryBuffers' attribute walk for this VAO: the
|
||||
@@ -462,6 +463,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
ResolvedDrawBuffers m_resolvedDrawBuffers;
|
||||
PendingAttribValueMask m_pendingAttribValueMask;
|
||||
Uint m_backendVAOId = 0;
|
||||
// ES context generation the VAO id and client-attribute buffer ids were
|
||||
// created under; ids from a dead context must never be deleted against a
|
||||
// successor context (both contexts restart GL names at 1).
|
||||
Uint m_contextGeneration = 0;
|
||||
Array<Uint, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS> m_clientAttributeBufferIds;
|
||||
Bool m_isInitialized = false;
|
||||
Uint16 m_syncedIndexBufferVersion = 0;
|
||||
@@ -711,6 +716,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// parameter already pushed onto it: the params-version early-out has to be overridden
|
||||
// once, or an unchanged version would skip the re-push forever.
|
||||
Bool m_forceTextureParamsResync = false;
|
||||
// Same latch for the built-in sampler parameters.
|
||||
Bool m_forceSamplerResync = false;
|
||||
};
|
||||
|
||||
void ActivateTextureUnit(Uint unit);
|
||||
@@ -1087,6 +1094,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
Bool ReadsBaseVertex() const { return m_baseVertexUniformLocation >= 0; }
|
||||
Int GetIndirectParamsBinding() const { return m_indirectParamsBinding; }
|
||||
Uint GetBackendProgramId() const { return m_backendProgramId; }
|
||||
Uint GetContextGeneration() const { return m_contextGeneration; }
|
||||
// False when the last SyncToBackend could not produce a usable program (a
|
||||
// shader failed to transpile or compile, or the link itself failed). Use()
|
||||
// must not leave the previously bound program current in that case.
|
||||
@@ -1149,6 +1157,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
void CacheResourceLocations(const SharedPtr<MG_State::GLState::ProgramObject>& stateProgramObject);
|
||||
|
||||
Uint m_backendProgramId = 0;
|
||||
// ES context generation the backend program and its global UBO were created
|
||||
// under. A stale twin must be recreated, never deleted against a successor
|
||||
// context (both contexts restart GL names at 1).
|
||||
Uint m_contextGeneration = 0;
|
||||
// GL name of the frontend program this was last synced from; diagnostics only, so
|
||||
// an unusable backend program can be traced back to the glCreateProgram id the app
|
||||
// knows it by.
|
||||
@@ -1196,6 +1208,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// skip redundant rebinds. Reset to 0 wherever glUseProgram(0) is issued or the
|
||||
// ES context is recreated.
|
||||
extern Uint g_lastUsedBackendProgramId;
|
||||
// Deletes `bufferId` only while it still belongs to the live ES context. Stale
|
||||
// generations are abandoned without a GL call: the old context already reclaimed
|
||||
// the buffer, and its numeric id may now name a live buffer in a successor context.
|
||||
void DeleteBackendProgramGlobalUbo(Uint& bufferId, Uint contextGeneration);
|
||||
extern StateBackendObjectRegistry<MG_State::GLState::ProgramObject, BackendProgramObjectImpl>
|
||||
g_backendProgramObjects;
|
||||
|
||||
|
||||
@@ -194,54 +194,54 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
PipelineFactory::HashType PipelineFactory::ComputeHash(const PipelineCreatePayload& payload) const {
|
||||
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.programHash, sizeof(payload.programHash)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.vertexInputHash, sizeof(payload.vertexInputHash)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.pipelineLayout, sizeof(payload.pipelineLayout)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.renderPass, sizeof(payload.renderPass)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.colorAttachmentCount, sizeof(payload.colorAttachmentCount)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.rasterizationSamples, sizeof(payload.rasterizationSamples)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.subpass, sizeof(payload.subpass)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.topology, sizeof(payload.topology)));
|
||||
XXHASH_VERIFY(XXH64_reset(m_hashState.Get(), m_config.CacheVersion));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.programHash, sizeof(payload.programHash)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.vertexInputHash, sizeof(payload.vertexInputHash)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.pipelineLayout, sizeof(payload.pipelineLayout)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.renderPass, sizeof(payload.renderPass)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.colorAttachmentCount, sizeof(payload.colorAttachmentCount)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.rasterizationSamples, sizeof(payload.rasterizationSamples)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.subpass, sizeof(payload.subpass)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.topology, sizeof(payload.topology)));
|
||||
XXHASH_VERIFY(
|
||||
XXH64_update(m_hashState, &payload.primitiveRestartEnable, sizeof(payload.primitiveRestartEnable)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.patchControlPoints, sizeof(payload.patchControlPoints)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.viewportCount, sizeof(payload.viewportCount)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.polygonMode, sizeof(payload.polygonMode)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.cullMode, sizeof(payload.cullMode)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.frontFace, sizeof(payload.frontFace)));
|
||||
XXH64_update(m_hashState.Get(), &payload.primitiveRestartEnable, sizeof(payload.primitiveRestartEnable)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.patchControlPoints, sizeof(payload.patchControlPoints)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.viewportCount, sizeof(payload.viewportCount)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.polygonMode, sizeof(payload.polygonMode)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.cullMode, sizeof(payload.cullMode)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.frontFace, sizeof(payload.frontFace)));
|
||||
XXHASH_VERIFY(
|
||||
XXH64_update(m_hashState, &payload.provokingVertexMode, sizeof(payload.provokingVertexMode)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthTestEnable, sizeof(payload.depthTestEnable)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthWriteEnable, sizeof(payload.depthWriteEnable)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthBiasEnable, sizeof(payload.depthBiasEnable)));
|
||||
XXH64_update(m_hashState.Get(), &payload.provokingVertexMode, sizeof(payload.provokingVertexMode)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.depthTestEnable, sizeof(payload.depthTestEnable)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.depthWriteEnable, sizeof(payload.depthWriteEnable)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.depthBiasEnable, sizeof(payload.depthBiasEnable)));
|
||||
XXHASH_VERIFY(
|
||||
XXH64_update(m_hashState, &payload.rasterizerDiscardEnable, sizeof(payload.rasterizerDiscardEnable)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.logicOpEnable, sizeof(payload.logicOpEnable)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.stencilTestEnable, sizeof(payload.stencilTestEnable)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthCompareOp, sizeof(payload.depthCompareOp)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.logicOp, sizeof(payload.logicOp)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.frontStencilFailOp, sizeof(payload.frontStencilFailOp)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.frontStencilPassOp, sizeof(payload.frontStencilPassOp)));
|
||||
XXH64_update(m_hashState.Get(), &payload.rasterizerDiscardEnable, sizeof(payload.rasterizerDiscardEnable)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.logicOpEnable, sizeof(payload.logicOpEnable)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.stencilTestEnable, sizeof(payload.stencilTestEnable)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.depthCompareOp, sizeof(payload.depthCompareOp)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.logicOp, sizeof(payload.logicOp)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.frontStencilFailOp, sizeof(payload.frontStencilFailOp)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.frontStencilPassOp, sizeof(payload.frontStencilPassOp)));
|
||||
XXHASH_VERIFY(
|
||||
XXH64_update(m_hashState, &payload.frontStencilDepthFailOp, sizeof(payload.frontStencilDepthFailOp)));
|
||||
XXH64_update(m_hashState.Get(), &payload.frontStencilDepthFailOp, sizeof(payload.frontStencilDepthFailOp)));
|
||||
XXHASH_VERIFY(
|
||||
XXH64_update(m_hashState, &payload.frontStencilCompareOp, sizeof(payload.frontStencilCompareOp)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.backStencilFailOp, sizeof(payload.backStencilFailOp)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.backStencilPassOp, sizeof(payload.backStencilPassOp)));
|
||||
XXH64_update(m_hashState.Get(), &payload.frontStencilCompareOp, sizeof(payload.frontStencilCompareOp)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.backStencilFailOp, sizeof(payload.backStencilFailOp)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.backStencilPassOp, sizeof(payload.backStencilPassOp)));
|
||||
XXHASH_VERIFY(
|
||||
XXH64_update(m_hashState, &payload.backStencilDepthFailOp, sizeof(payload.backStencilDepthFailOp)));
|
||||
XXH64_update(m_hashState.Get(), &payload.backStencilDepthFailOp, sizeof(payload.backStencilDepthFailOp)));
|
||||
XXHASH_VERIFY(
|
||||
XXH64_update(m_hashState, &payload.backStencilCompareOp, sizeof(payload.backStencilCompareOp)));
|
||||
XXH64_update(m_hashState.Get(), &payload.backStencilCompareOp, sizeof(payload.backStencilCompareOp)));
|
||||
XXHASH_VERIFY(
|
||||
XXH64_update(m_hashState, &payload.fragmentReplacesDepth, sizeof(payload.fragmentReplacesDepth)));
|
||||
XXH64_update(m_hashState.Get(), &payload.fragmentReplacesDepth, sizeof(payload.fragmentReplacesDepth)));
|
||||
if (payload.colorAttachmentCount > 0) {
|
||||
XXHASH_VERIFY(XXH64_update(
|
||||
m_hashState,
|
||||
m_hashState.Get(),
|
||||
payload.colorBlendAttachments.data(),
|
||||
sizeof(payload.colorBlendAttachments[0]) * payload.colorAttachmentCount));
|
||||
}
|
||||
return XXH64_digest(m_hashState);
|
||||
return XXH64_digest(m_hashState.Get());
|
||||
}
|
||||
|
||||
VkPipeline PipelineFactory::GetOrCreatePipeline(const PipelineCreatePayload& payload) {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "../VkIncludes.h"
|
||||
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
|
||||
#include <Includes.h>
|
||||
#include <MG_Util/Types.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// Enough of a fingerprint to identify the exact module the driver rejected without keeping the
|
||||
@@ -165,7 +166,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
UnorderedMap<HashType, PipelineCacheEntry> m_cache;
|
||||
// Monotonic frame-boundary counter (bumped in OnFrameBoundary) for cache aging.
|
||||
Uint64 m_frameCounter = 0;
|
||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||
static inline MobileGL::XXH64State m_hashState;
|
||||
static inline Bool s_suppressBlendedDepthWrite = false;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
@@ -2156,26 +2156,26 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
ProgramFactory::HashType ProgramFactory::ComputeHash(const MG_State::GLState::ProgramObject& program,
|
||||
CompileOptionFlags flags) const {
|
||||
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion));
|
||||
XXHASH_VERIFY(XXH64_reset(m_hashState.Get(), m_config.CacheVersion));
|
||||
// We expect shader stages in program object are sorted
|
||||
const auto& spirvs = program.GetGeneratedSpirv();
|
||||
for (const auto& spv : spirvs) {
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, spv.data(), spv.size() * sizeof(Uint)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), spv.data(), spv.size() * sizeof(Uint)));
|
||||
}
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &flags, sizeof(CompileOptionFlags)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &flags, sizeof(CompileOptionFlags)));
|
||||
// Only FragCoordYFlip variants bake the height in, so mixing it unconditionally would
|
||||
// re-key every program in the cache on a resize for no reason.
|
||||
if (flags & CompileOptionBit::FragCoordYFlip) {
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &m_defaultFramebufferHeight,
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &m_defaultFramebufferHeight,
|
||||
sizeof(m_defaultFramebufferHeight)));
|
||||
}
|
||||
|
||||
// Include UBO block bindings in hash so different binding configurations produce different entries
|
||||
const Uint32 blockCount = static_cast<Uint32>(program.GetActiveUniformBlocksCount());
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &blockCount, sizeof(blockCount)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &blockCount, sizeof(blockCount)));
|
||||
for (Uint32 i = 0; i < blockCount; ++i) {
|
||||
const Uint32 binding = program.GetUniformBlockBinding(i);
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &binding, sizeof(binding)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &binding, sizeof(binding)));
|
||||
}
|
||||
|
||||
// The transform feedback capture layout is baked into the modules by
|
||||
@@ -2186,18 +2186,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// hashed for a capturing compile, so nothing else changes key.
|
||||
if (flags & CompileOptionBit::XfbCapture) {
|
||||
for (const auto& varying : program.GetTransformFeedbackVaryings()) {
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, varying.name.data(), varying.name.size()));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &varying.bufferIndex, sizeof(varying.bufferIndex)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &varying.offsetBytes, sizeof(varying.offsetBytes)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), varying.name.data(), varying.name.size()));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &varying.bufferIndex, sizeof(varying.bufferIndex)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &varying.offsetBytes, sizeof(varying.offsetBytes)));
|
||||
}
|
||||
const SizeT bufferCount = program.GetTransformFeedbackBufferCount();
|
||||
for (SizeT i = 0; i < bufferCount; ++i) {
|
||||
const Uint32 stride = program.GetTransformFeedbackStride(static_cast<Uint32>(i));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &stride, sizeof(stride)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &stride, sizeof(stride)));
|
||||
}
|
||||
}
|
||||
|
||||
HashType hash = XXH64_digest(m_hashState);
|
||||
HashType hash = XXH64_digest(m_hashState.Get());
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "MG_State/GLState/TextureState/TextureEnum.h"
|
||||
|
||||
#include <Includes.h>
|
||||
#include <MG_Util/Types.h>
|
||||
#include <spirv_reflect.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
@@ -489,6 +490,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// ever built from one keeps referencing its module. A failed build is cached as
|
||||
// VK_NULL_HANDLE so a broken generator costs one compile, not one per draw.
|
||||
UnorderedMap<Uint32, VkPipelineShaderStageCreateInfo> m_passthroughTessControlStages;
|
||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||
static inline MobileGL::XXH64State m_hashState;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
@@ -13,25 +13,25 @@
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VertexInputStateFactory::HashType VertexInputStateFactory::ComputeHash(
|
||||
const MG_State::GLState::VertexArrayObject& vao) const {
|
||||
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion));
|
||||
XXHASH_VERIFY(XXH64_reset(m_hashState.Get(), m_config.CacheVersion));
|
||||
|
||||
for (Int i = 0; i < MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS; ++i) {
|
||||
const auto& attr = vao.GetAttribute(i);
|
||||
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Enabled, sizeof(attr.Enabled)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attr.Enabled, sizeof(attr.Enabled)));
|
||||
if (!attr.Enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Size, sizeof(attr.Size)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Type, sizeof(attr.Type)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Normalized, sizeof(attr.Normalized)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Stride, sizeof(attr.Stride)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Offset, sizeof(attr.Offset)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.IsInteger, sizeof(attr.IsInteger)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.IsLong, sizeof(attr.IsLong)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.IsBgra, sizeof(attr.IsBgra)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Divisor, sizeof(attr.Divisor)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attr.Size, sizeof(attr.Size)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attr.Type, sizeof(attr.Type)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attr.Normalized, sizeof(attr.Normalized)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attr.Stride, sizeof(attr.Stride)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attr.Offset, sizeof(attr.Offset)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attr.IsInteger, sizeof(attr.IsInteger)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attr.IsLong, sizeof(attr.IsLong)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attr.IsBgra, sizeof(attr.IsBgra)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attr.Divisor, sizeof(attr.Divisor)));
|
||||
|
||||
// The bound buffer's IDENTITY is a component of the key, and it has to be the
|
||||
// buffer's never-reused lifetime id - NOT its heap address, which this used to
|
||||
@@ -45,10 +45,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// test's positions) instead of its own.
|
||||
// Zero for client memory (no buffer), which is a distinct identity of its own.
|
||||
const Uint64 bufferKey = attr.Buffer ? attr.Buffer->GetLifetimeId() : 0;
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &bufferKey, sizeof(bufferKey)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &bufferKey, sizeof(bufferKey)));
|
||||
}
|
||||
|
||||
return XXH64_digest(m_hashState);
|
||||
return XXH64_digest(m_hashState.Get());
|
||||
}
|
||||
|
||||
VertexInputStateFactory::HashType VertexInputStateFactory::GetOrComputeHash(
|
||||
@@ -225,24 +225,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
entry.attributes = builder.GetAttributes();
|
||||
// See the layoutHash declaration: hash only the resolved layout, never
|
||||
// buffer identities, so identical layouts across VAOs/buffers agree.
|
||||
XXHASH_VERIFY(XXH64_reset(m_hashState, 0));
|
||||
XXHASH_VERIFY(XXH64_reset(m_hashState.Get(), 0));
|
||||
for (const auto& binding : entry.bindings) {
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &binding.binding, sizeof(binding.binding)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &binding.stride, sizeof(binding.stride)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &binding.inputRate, sizeof(binding.inputRate)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &binding.binding, sizeof(binding.binding)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &binding.stride, sizeof(binding.stride)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &binding.inputRate, sizeof(binding.inputRate)));
|
||||
}
|
||||
for (const auto& attribute : entry.attributes) {
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attribute.location, sizeof(attribute.location)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attribute.binding, sizeof(attribute.binding)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attribute.format, sizeof(attribute.format)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attribute.offset, sizeof(attribute.offset)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attribute.location, sizeof(attribute.location)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attribute.binding, sizeof(attribute.binding)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attribute.format, sizeof(attribute.format)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attribute.offset, sizeof(attribute.offset)));
|
||||
}
|
||||
for (const auto& divisor : entry.bindingDivisors) {
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &divisor.binding, sizeof(divisor.binding)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &divisor.divisor, sizeof(divisor.divisor)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &divisor.binding, sizeof(divisor.binding)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &divisor.divisor, sizeof(divisor.divisor)));
|
||||
}
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &unsupportedAttribMask, sizeof(unsupportedAttribMask)));
|
||||
entry.layoutHash = XXH64_digest(m_hashState);
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &unsupportedAttribMask, sizeof(unsupportedAttribMask)));
|
||||
entry.layoutHash = XXH64_digest(m_hashState.Get());
|
||||
entry.attributeLocationMask = 0;
|
||||
for (const auto& attribute : entry.attributes) {
|
||||
if (attribute.location < 32u) {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "VertexInputStateBuilder.h"
|
||||
#include "MG_State/GLState/VertexArrayState/VertexArrayObject.h"
|
||||
#include <Includes.h>
|
||||
#include <MG_Util/Types.h>
|
||||
#include "../VkIncludes.h"
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
@@ -126,6 +127,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// matches, so an evicted entry can never be dereferenced through a
|
||||
// stale memo.
|
||||
Uint64 m_evictionEpoch = 1;
|
||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||
static inline MobileGL::XXH64State m_hashState;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
@@ -594,27 +594,27 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
VkRenderPassManager::HashType VkRenderPassManager::ComputeHash(
|
||||
const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex, Bool includePendingClear,
|
||||
Bool includeDefaultFboDepthStencil) {
|
||||
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion));
|
||||
XXHASH_VERIFY(XXH64_reset(m_hashState.Get(), m_config.CacheVersion));
|
||||
const Bool isDefaultFbo = fbo.IsDefaultFramebuffer();
|
||||
if (isDefaultFbo) {
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &swapchainImageIndex, sizeof(swapchainImageIndex)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &swapchainImageIndex, sizeof(swapchainImageIndex)));
|
||||
}
|
||||
// sRGB attachments switch between their sRGB and UNORM-twin views with this
|
||||
// capability (ResolveSrgbAttachmentWriteFormat), changing the render pass formats.
|
||||
const Bool framebufferSrgbEnabled =
|
||||
MG_State::pGLContext->IsCapabilityEnabled(MobileGL::CapabilityInput::FramebufferSrgb);
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &framebufferSrgbEnabled, sizeof(framebufferSrgbEnabled)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &framebufferSrgbEnabled, sizeof(framebufferSrgbEnabled)));
|
||||
auto& drawBuffers = fbo.GetDrawBuffers();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, drawBuffers.data(), drawBuffers.size() * sizeof(drawBuffers[0])));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), drawBuffers.data(), drawBuffers.size() * sizeof(drawBuffers[0])));
|
||||
auto readBuffer = fbo.GetReadBuffer();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &readBuffer, sizeof(FramebufferAttachmentType)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &readBuffer, sizeof(FramebufferAttachmentType)));
|
||||
Int validDrawBufCount = 0;
|
||||
for (Int i = 0; i < drawBuffers.size(); ++i) {
|
||||
auto drawbuf = drawBuffers[i];
|
||||
if (drawbuf != FramebufferAttachmentType::None)
|
||||
validDrawBufCount = std::max(validDrawBufCount, i + 1);
|
||||
}
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &validDrawBufCount, sizeof(validDrawBufCount)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &validDrawBufCount, sizeof(validDrawBufCount)));
|
||||
|
||||
auto combineFramebufferAttachmentObjHash = [&](FramebufferAttachmentType attachment) {
|
||||
auto& att = fbo.GetAttachment(attachment);
|
||||
@@ -623,49 +623,49 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
if (att.IsEmpty()) type = 0;
|
||||
else if (att.IsTexture()) type = 1;
|
||||
else if (att.IsRenderbuffer()) type = 2;
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &type, sizeof(type)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &type, sizeof(type)));
|
||||
void* contentPtr = nullptr;
|
||||
if (att.IsTexture())
|
||||
contentPtr = att.GetTexture().get();
|
||||
else if (att.IsRenderbuffer())
|
||||
contentPtr = att.GetRenderbuffer().get();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &contentPtr, sizeof(contentPtr)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &contentPtr, sizeof(contentPtr)));
|
||||
if (att.IsTexture()) {
|
||||
const Uint64 textureLifetimeId = att.GetTexture()->GetLifetimeId();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &textureLifetimeId, sizeof(textureLifetimeId)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &textureLifetimeId, sizeof(textureLifetimeId)));
|
||||
const Int textureLevel = att.GetTextureLevel();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &textureLevel, sizeof(textureLevel)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &textureLevel, sizeof(textureLevel)));
|
||||
const TextureUploadTarget textureUploadTarget = att.GetTextureUploadTarget();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &textureUploadTarget, sizeof(textureUploadTarget)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &textureUploadTarget, sizeof(textureUploadTarget)));
|
||||
const Int textureLayer = att.GetTextureLayer();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &textureLayer, sizeof(textureLayer)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &textureLayer, sizeof(textureLayer)));
|
||||
const Bool textureLayered = att.IsLayered();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &textureLayered, sizeof(textureLayered)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &textureLayered, sizeof(textureLayered)));
|
||||
|
||||
Uint64 imageIdentity = 0;
|
||||
auto* texture = att.GetTexture().get();
|
||||
auto* resource = m_textureManager.SyncTextureAndGetDescriptor(*texture);
|
||||
if (resource != nullptr) {
|
||||
imageIdentity = reinterpret_cast<Uint64>(resource->image);
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &resource->sampleCount, sizeof(resource->sampleCount)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &resource->sampleCount, sizeof(resource->sampleCount)));
|
||||
} else {
|
||||
const VkSampleCountFlagBits fallbackSampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &fallbackSampleCount, sizeof(fallbackSampleCount)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &fallbackSampleCount, sizeof(fallbackSampleCount)));
|
||||
}
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &imageIdentity, sizeof(imageIdentity)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &imageIdentity, sizeof(imageIdentity)));
|
||||
}
|
||||
|
||||
if (includePendingClear && att.IsTexture()) {
|
||||
auto* texture = att.GetTexture().get();
|
||||
const auto pendingClearKey = VkClearManager::MakePendingClearKey(att);
|
||||
auto hasClear = m_clearManager.HasPendingClear(pendingClearKey);
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &hasClear, sizeof(hasClear)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &hasClear, sizeof(hasClear)));
|
||||
if (hasClear) {
|
||||
ClearAttachmentPayload clearPayload{};
|
||||
Bool hasPayload = m_clearManager.GetPendingClear(pendingClearKey, clearPayload);
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &hasPayload, sizeof(hasPayload)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &hasPayload, sizeof(hasPayload)));
|
||||
if (hasPayload) {
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &clearPayload.mask, sizeof(clearPayload.mask)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &clearPayload.mask, sizeof(clearPayload.mask)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -695,7 +695,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
currentLayout = textureResource->layout;
|
||||
}
|
||||
}
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, ¤tLayout, sizeof(currentLayout)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), ¤tLayout, sizeof(currentLayout)));
|
||||
}
|
||||
if (att.IsRenderbuffer() && att.GetRenderbuffer()) {
|
||||
const auto& renderbuffer = att.GetRenderbuffer();
|
||||
@@ -703,10 +703,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
const Int width = renderbuffer->GetWidth();
|
||||
const Int height = renderbuffer->GetHeight();
|
||||
const Int samples = renderbuffer->GetSamples();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &internalFormat, sizeof(internalFormat)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &width, sizeof(width)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &height, sizeof(height)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &samples, sizeof(samples)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &internalFormat, sizeof(internalFormat)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &width, sizeof(width)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &height, sizeof(height)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &samples, sizeof(samples)));
|
||||
|
||||
Uint64 imageIdentity = 0;
|
||||
VkImageLayout currentLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
@@ -714,25 +714,25 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
if (resource != nullptr) {
|
||||
imageIdentity = reinterpret_cast<Uint64>(resource->image);
|
||||
currentLayout = resource->layout;
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &resource->sampleCount, sizeof(resource->sampleCount)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &resource->sampleCount, sizeof(resource->sampleCount)));
|
||||
} else {
|
||||
const VkSampleCountFlagBits fallbackSampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &fallbackSampleCount, sizeof(fallbackSampleCount)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &fallbackSampleCount, sizeof(fallbackSampleCount)));
|
||||
}
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &imageIdentity, sizeof(imageIdentity)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &imageIdentity, sizeof(imageIdentity)));
|
||||
|
||||
if (includePendingClear) {
|
||||
const Bool hasClear = HasPendingRenderbufferClear(att);
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &hasClear, sizeof(hasClear)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &hasClear, sizeof(hasClear)));
|
||||
if (hasClear) {
|
||||
ClearAttachmentPayload clearPayload{};
|
||||
const Bool hasPayload = GetPendingRenderbufferClear(renderbuffer.get(), clearPayload);
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &hasPayload, sizeof(hasPayload)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &hasPayload, sizeof(hasPayload)));
|
||||
if (hasPayload) {
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &clearPayload.mask, sizeof(clearPayload.mask)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &clearPayload.mask, sizeof(clearPayload.mask)));
|
||||
}
|
||||
}
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, ¤tLayout, sizeof(currentLayout)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), ¤tLayout, sizeof(currentLayout)));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -745,13 +745,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// The depth-less default-FBO flavor omits the depth/stencil attachment
|
||||
// entirely, so it must hash differently from the depth-full flavor.
|
||||
const Bool depthStencilIncluded = !isDefaultFbo || includeDefaultFboDepthStencil;
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &depthStencilIncluded, sizeof(depthStencilIncluded)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &depthStencilIncluded, sizeof(depthStencilIncluded)));
|
||||
if (depthStencilIncluded) {
|
||||
combineFramebufferAttachmentObjHash(FramebufferAttachmentType::Depth);
|
||||
combineFramebufferAttachmentObjHash(FramebufferAttachmentType::Stencil);
|
||||
}
|
||||
|
||||
return XXH64_digest(m_hashState);
|
||||
return XXH64_digest(m_hashState.Get());
|
||||
}
|
||||
|
||||
RenderPassEntry& VkRenderPassManager::GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo,
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
|
||||
|
||||
#include <Includes.h>
|
||||
#include <MG_Util/Types.h>
|
||||
#include <unordered_map>
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
@@ -391,7 +392,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void DeferRenderbufferBackingRelease(RenderbufferResource& resource);
|
||||
void CollectDeferredRenderbufferReleases(Bool destroyAll);
|
||||
|
||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||
static inline MobileGL::XXH64State m_hashState;
|
||||
static inline ActiveRenderPassInfo s_activeRenderPass{};
|
||||
static inline Bool s_hasActiveRenderPass = false;
|
||||
static inline VkClearManager* s_clearManager = nullptr;
|
||||
|
||||
@@ -134,41 +134,41 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
const MG_State::GLState::ITextureObject& texture,
|
||||
Bool forceNearestFiltering, Bool singleLevelView) const {
|
||||
MOBILEGL_ASSERT(m_config != nullptr, "VkSamplerManager::BuildSamplerKey: m_config is null");
|
||||
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config->CacheVersion));
|
||||
XXHASH_VERIFY(XXH64_reset(m_hashState.Get(), m_config->CacheVersion));
|
||||
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &forceNearestFiltering, sizeof(forceNearestFiltering)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &singleLevelView, sizeof(singleLevelView)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &forceNearestFiltering, sizeof(forceNearestFiltering)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &singleLevelView, sizeof(singleLevelView)));
|
||||
|
||||
const auto minFilter = sampler.GetMinFilter();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &minFilter, sizeof(minFilter)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &minFilter, sizeof(minFilter)));
|
||||
const auto magFilter = sampler.GetMagFilter();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &magFilter, sizeof(magFilter)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &magFilter, sizeof(magFilter)));
|
||||
const auto mipmapMode = sampler.GetMipmapMode();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &mipmapMode, sizeof(mipmapMode)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &mipmapMode, sizeof(mipmapMode)));
|
||||
const auto wrapS = sampler.GetWrapS();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &wrapS, sizeof(wrapS)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &wrapS, sizeof(wrapS)));
|
||||
const auto wrapT = sampler.GetWrapT();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &wrapT, sizeof(wrapT)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &wrapT, sizeof(wrapT)));
|
||||
const auto wrapR = sampler.GetWrapR();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &wrapR, sizeof(wrapR)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &wrapR, sizeof(wrapR)));
|
||||
const auto maxLod = ResolveSingleLevelMaxLod(sampler, singleLevelView);
|
||||
const auto minLod = ResolveEffectiveMinLod(sampler, maxLod);
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &minLod, sizeof(minLod)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &maxLod, sizeof(maxLod)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &minLod, sizeof(minLod)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &maxLod, sizeof(maxLod)));
|
||||
const auto lodBias = sampler.GetLodBias();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &lodBias, sizeof(lodBias)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &lodBias, sizeof(lodBias)));
|
||||
// The RESOLVED value, not the GL request: samplers that only differ in an anisotropy Vulkan
|
||||
// will not apply (NEAREST filtering, or requests past the device limit) must still share one
|
||||
// VkSampler, while two samplers that really do differ must not collide onto the first one's.
|
||||
const auto maxAnisotropy = ResolveEffectiveMaxAnisotropy(sampler, forceNearestFiltering);
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &maxAnisotropy, sizeof(maxAnisotropy)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &maxAnisotropy, sizeof(maxAnisotropy)));
|
||||
const auto compareMode = sampler.GetCompareMode();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &compareMode, sizeof(compareMode)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &compareMode, sizeof(compareMode)));
|
||||
const auto compareFunc = sampler.GetSamplerCompareFunc();
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &compareFunc, sizeof(compareFunc)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &compareFunc, sizeof(compareFunc)));
|
||||
const auto borderColor = ResolveVkBorderColor(sampler, texture);
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &borderColor, sizeof(borderColor)));
|
||||
return XXH64_digest(m_hashState);
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &borderColor, sizeof(borderColor)));
|
||||
return XXH64_digest(m_hashState.Get());
|
||||
}
|
||||
|
||||
VkSampler VkSamplerManager::GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler,
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "../VkIncludes.h"
|
||||
#include "../VulkanRendererConfig.h"
|
||||
#include <Includes.h>
|
||||
#include <MG_Util/Types.h>
|
||||
#include <MG_State/GLState/SamplerState/SamplerObject.h>
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
@@ -85,6 +86,6 @@ private:
|
||||
UnorderedMap<Uint64, SamplerCacheEntry> m_samplers;
|
||||
// Monotonic frame-boundary counter (bumped in OnFrameBoundary) for cache aging.
|
||||
Uint64 m_frameBoundaryCounter = 0;
|
||||
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||
static inline MobileGL::XXH64State m_hashState;
|
||||
};
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
@@ -1994,7 +1994,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// Bound the idle pool: a one-off giant upload (initial atlas define)
|
||||
// must not pin its staging memory forever.
|
||||
constexpr VkDeviceSize kMaxFreeUploadStagingBytes = 32u * 1024u * 1024u;
|
||||
if (m_allocator == nullptr || m_freeUploadStagingBytes + block.capacity > kMaxFreeUploadStagingBytes) {
|
||||
if (m_allocator == nullptr) {
|
||||
// The normal shutdown path destroys the free list through
|
||||
// DestroyUploadPools while the allocator is still valid, so this is a
|
||||
// defensive backstop only. Never pass a null allocator to VMA.
|
||||
MGLOG_W_ONCE("VkTextureManager::RecycleUploadStagingBlock called with a null allocator");
|
||||
return;
|
||||
}
|
||||
if (m_freeUploadStagingBytes + block.capacity > kMaxFreeUploadStagingBytes) {
|
||||
vmaDestroyBuffer(m_allocator, block.buffer, block.allocation);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -344,6 +344,41 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
}
|
||||
|
||||
void DestroyAllQueryObjects() {
|
||||
// Detach the registry under the lock and release it outside. Entries the app
|
||||
// already deleted were erased by DeleteQueries, so nothing here double-frees;
|
||||
// a DeleteQueries racing this sweep finds an empty registry and ignores the
|
||||
// names. The active-query slots and the name allocator are reset under the
|
||||
// same lock: query names are context-owned state, so a fresh context must
|
||||
// start clean instead of inheriting the dead context's allocator cursor or
|
||||
// a stale "a query is already active on this target" latch.
|
||||
UnorderedMap<GLuint, QueryObject*> orphans;
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||
orphans.swap(g_liveQueryObjects);
|
||||
g_nextQueryId = 1;
|
||||
g_activeTimeElapsedQueryId = 0;
|
||||
g_activePrimitivesWrittenQueryId = 0;
|
||||
g_activePrimitivesGeneratedQueryId = 0;
|
||||
g_activeSamplesPassedQueryId = 0;
|
||||
}
|
||||
if (orphans.empty()) {
|
||||
return;
|
||||
}
|
||||
// Both backends' DeleteBackendQuery only free the heap wrapper once their GL
|
||||
// context/renderer is gone (generation/current-thread guards), so this is
|
||||
// safe after the backend has released its EGL resources - but not after the
|
||||
// function table itself is cleared.
|
||||
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());
|
||||
}
|
||||
|
||||
GLboolean IsQuery(GLuint id) {
|
||||
if (id == 0) {
|
||||
return GL_FALSE;
|
||||
|
||||
@@ -13,6 +13,15 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
void GenQueries(GLsizei n, GLuint* ids);
|
||||
void CreateQueries(GLenum target, GLsizei n, GLuint* ids);
|
||||
void DeleteQueries(GLsizei n, const GLuint* ids);
|
||||
// Destroys every still-registered query object exactly as DeleteQueries would.
|
||||
// Query objects are context-owned, and MobileGL::Destroy() tears every context
|
||||
// down, so the process-global registry has to be drained there: without this the
|
||||
// QueryObject and any backend timer-query wrapper leaked across every
|
||||
// eglTerminate/eglInitialize cycle, and the active-query/name-allocator state
|
||||
// from the dead context survived into the next one. Must run while the backend
|
||||
// function table is still populated, and before a re-initialized library could
|
||||
// pair the handles with the wrong backend's DeleteBackendQuery.
|
||||
void DestroyAllQueryObjects();
|
||||
GLboolean IsQuery(GLuint id);
|
||||
void BeginQuery(GLenum target, GLuint id);
|
||||
void EndQuery(GLenum target);
|
||||
|
||||
@@ -14,10 +14,29 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
// Frontend sync object: wraps an optional backend fence handle. A null
|
||||
// backend handle (backend has no fence support, or could not create a
|
||||
// fence at call time) keeps the legacy always-signaled behavior.
|
||||
//
|
||||
// SharedPtr-owned, not raw: DeleteSync can remove the registry entry while
|
||||
// another thread is inside ClientWaitSync/GetSynciv. Those callers hold a
|
||||
// SharedPtr copy, so the object stays alive until the last reader leaves.
|
||||
// `mutex` then serializes backend-handle reads against the one-time
|
||||
// backend-handle release performed by DeleteSync / DestroyAllSyncObjects.
|
||||
struct SyncObject {
|
||||
std::mutex mutex;
|
||||
MG_Backend::BackendSyncHandle backendHandle = nullptr;
|
||||
GLenum condition = GL_SYNC_GPU_COMMANDS_COMPLETE;
|
||||
GLbitfield flags = 0;
|
||||
|
||||
void ReleaseBackendHandle() {
|
||||
const std::lock_guard<std::mutex> lock(mutex);
|
||||
if (backendHandle == nullptr) {
|
||||
return;
|
||||
}
|
||||
const auto backendDeleteSync = MG_Backend::gBackendFunctionsTable.GL.DeleteSync;
|
||||
if (backendDeleteSync) {
|
||||
backendDeleteSync(backendHandle);
|
||||
}
|
||||
backendHandle = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
// Sync calls may arrive from any thread (launchers migrate the context
|
||||
@@ -25,9 +44,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
// Entries left at process shutdown are simply dropped; their backend
|
||||
// handles die with the backend.
|
||||
std::mutex g_syncObjectsMutex;
|
||||
UnorderedMap<GLsync, SyncObject*> g_liveSyncObjects;
|
||||
UnorderedMap<GLsync, SharedPtr<SyncObject>> g_liveSyncObjects;
|
||||
|
||||
SyncObject* FindSyncObject(GLsync sync) {
|
||||
SharedPtr<SyncObject> FindSyncObject(GLsync sync) {
|
||||
const std::lock_guard<std::mutex> lock(g_syncObjectsMutex);
|
||||
const auto it = g_liveSyncObjects.find(sync);
|
||||
return it != g_liveSyncObjects.end() ? it->second : nullptr;
|
||||
@@ -35,13 +54,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
} // namespace
|
||||
|
||||
GLsync FenceSync(GLenum condition, GLbitfield flags) {
|
||||
auto* syncObject = new SyncObject;
|
||||
auto syncObject = MakeShared<SyncObject>();
|
||||
syncObject->condition = condition;
|
||||
syncObject->flags = flags;
|
||||
if (const auto backendFenceSync = MG_Backend::gBackendFunctionsTable.GL.FenceSync) {
|
||||
syncObject->backendHandle = backendFenceSync();
|
||||
}
|
||||
const GLsync handle = reinterpret_cast<GLsync>(syncObject);
|
||||
const GLsync handle = reinterpret_cast<GLsync>(syncObject.get());
|
||||
const std::lock_guard<std::mutex> lock(g_syncObjectsMutex);
|
||||
g_liveSyncObjects[handle] = syncObject;
|
||||
return handle;
|
||||
@@ -52,24 +71,31 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
|
||||
GLenum ClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) {
|
||||
const auto* syncObject = FindSyncObject(sync);
|
||||
const SharedPtr<SyncObject> syncObject = FindSyncObject(sync);
|
||||
if (!syncObject) {
|
||||
return GL_WAIT_FAILED;
|
||||
}
|
||||
const auto backendClientWaitSync = MG_Backend::gBackendFunctionsTable.GL.ClientWaitSync;
|
||||
if (!backendClientWaitSync || !syncObject->backendHandle) {
|
||||
// Hold the per-object lock across the backend call: a concurrent
|
||||
// DeleteSync may already have removed this object from the registry, but
|
||||
// it cannot free the backend handle (or the wrapper) until this reader
|
||||
// finishes. ClientWaitSync can block for `timeout`; that blocks only this
|
||||
// sync object, never the registry or unrelated syncs.
|
||||
const std::lock_guard<std::mutex> lock(syncObject->mutex);
|
||||
if (!backendClientWaitSync || syncObject->backendHandle == nullptr) {
|
||||
return GL_ALREADY_SIGNALED; // legacy always-signaled fallback
|
||||
}
|
||||
return backendClientWaitSync(syncObject->backendHandle, flags, timeout);
|
||||
}
|
||||
|
||||
void WaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) {
|
||||
const auto* syncObject = FindSyncObject(sync);
|
||||
const SharedPtr<SyncObject> syncObject = FindSyncObject(sync);
|
||||
if (!syncObject) {
|
||||
return;
|
||||
}
|
||||
const auto backendWaitSync = MG_Backend::gBackendFunctionsTable.GL.WaitSync;
|
||||
if (backendWaitSync && syncObject->backendHandle) {
|
||||
const std::lock_guard<std::mutex> lock(syncObject->mutex);
|
||||
if (backendWaitSync && syncObject->backendHandle != nullptr) {
|
||||
backendWaitSync(syncObject->backendHandle, flags, timeout);
|
||||
}
|
||||
}
|
||||
@@ -78,7 +104,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
if (sync == nullptr) {
|
||||
return; // glDeleteSync(0) is silently ignored
|
||||
}
|
||||
SyncObject* syncObject = nullptr;
|
||||
SharedPtr<SyncObject> syncObject;
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(g_syncObjectsMutex);
|
||||
const auto it = g_liveSyncObjects.find(sync);
|
||||
@@ -88,15 +114,14 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
syncObject = it->second;
|
||||
g_liveSyncObjects.erase(it);
|
||||
}
|
||||
const auto backendDeleteSync = MG_Backend::gBackendFunctionsTable.GL.DeleteSync;
|
||||
if (backendDeleteSync && syncObject->backendHandle) {
|
||||
backendDeleteSync(syncObject->backendHandle);
|
||||
}
|
||||
delete syncObject;
|
||||
// Release the backend handle under the object lock. The local SharedPtr
|
||||
// (and any reader's SharedPtr) keeps the wrapper itself alive until every
|
||||
// in-flight backend call has returned.
|
||||
syncObject->ReleaseBackendHandle();
|
||||
}
|
||||
|
||||
void GetSynciv(GLsync sync, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* values) {
|
||||
const auto* syncObject = FindSyncObject(sync);
|
||||
const SharedPtr<SyncObject> syncObject = FindSyncObject(sync);
|
||||
if (!syncObject) {
|
||||
if (length) {
|
||||
*length = 0;
|
||||
@@ -111,7 +136,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
break;
|
||||
case GL_SYNC_STATUS: {
|
||||
const auto backendGetSyncStatus = MG_Backend::gBackendFunctionsTable.GL.GetSyncStatus;
|
||||
const Bool signaled = !backendGetSyncStatus || !syncObject->backendHandle ||
|
||||
const std::lock_guard<std::mutex> lock(syncObject->mutex);
|
||||
const Bool signaled = !backendGetSyncStatus || syncObject->backendHandle == nullptr ||
|
||||
backendGetSyncStatus(syncObject->backendHandle);
|
||||
value = signaled ? GL_SIGNALED : GL_UNSIGNALED;
|
||||
break;
|
||||
@@ -137,11 +163,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
void DestroyAllSyncObjects() {
|
||||
// Detach the registry under the lock, release outside it. Entries the app
|
||||
// already deleted were erased by DeleteSync, so nothing here double-frees;
|
||||
// a DeleteSync racing this sweep finds an empty registry and returns. A
|
||||
// thread still blocked inside ClientWaitSync/GetSynciv during teardown
|
||||
// holds a raw SyncObject* these deletes invalidate - the same undefined
|
||||
// race an app-driven DeleteSync already has.
|
||||
UnorderedMap<GLsync, SyncObject*> orphans;
|
||||
// a DeleteSync racing this sweep finds an empty registry and returns.
|
||||
// Readers racing this sweep keep their SharedPtr copy alive, and each
|
||||
// object's own lock makes the backend-handle release wait for them.
|
||||
UnorderedMap<GLsync, SharedPtr<SyncObject>> orphans;
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(g_syncObjectsMutex);
|
||||
orphans.swap(g_liveSyncObjects);
|
||||
@@ -153,12 +178,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
// context/renderer is gone (generation/current-thread guards), so this is
|
||||
// safe after the backend has released its EGL resources - but not after
|
||||
// the function table itself is cleared.
|
||||
const auto backendDeleteSync = MG_Backend::gBackendFunctionsTable.GL.DeleteSync;
|
||||
for (const auto& [_, syncObject] : orphans) {
|
||||
if (backendDeleteSync && syncObject->backendHandle) {
|
||||
backendDeleteSync(syncObject->backendHandle);
|
||||
if (syncObject) {
|
||||
syncObject->ReleaseBackendHandle();
|
||||
}
|
||||
delete syncObject;
|
||||
}
|
||||
MGLOG_D("DestroyAllSyncObjects: reclaimed %zu sync object(s) the app left undeleted", orphans.size());
|
||||
}
|
||||
|
||||
@@ -448,6 +448,39 @@ TEST_F(QueryTest, BackendResultsPropagateThroughFrontend) {
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
}
|
||||
|
||||
TEST_F(QueryTest, DestroyAllQueryObjectsReclaimsRegistryAndResetsContextState) {
|
||||
const ScopedFeaturesOverride featuresGuard;
|
||||
const ScopedBackendFunctionsOverride backendGuard;
|
||||
InstallStubBackendTimerQueries();
|
||||
MG_Config::Features.DisableTimerQuery = false;
|
||||
|
||||
GLuint id = 0;
|
||||
MG_Impl::GLImpl::GenQueries(1, &id);
|
||||
ASSERT_NE(id, 0u);
|
||||
MG_Impl::GLImpl::BeginQuery(GL_TIME_ELAPSED, id);
|
||||
|
||||
GLint currentQuery = -1;
|
||||
MG_Impl::GLImpl::GetQueryiv(GL_TIME_ELAPSED, GL_CURRENT_QUERY, ¤tQuery);
|
||||
EXPECT_EQ(currentQuery, static_cast<GLint>(id));
|
||||
|
||||
// Full teardown drains the registry through this function while the backend
|
||||
// table is still valid. The unread backend handle must be released, the query
|
||||
// must disappear, and a fresh context must restart with no active query and a
|
||||
// fresh name allocator.
|
||||
MG_Impl::GLImpl::DestroyAllQueryObjects();
|
||||
EXPECT_EQ(g_stubDeleteCount, 1);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsQuery(id), GL_FALSE);
|
||||
|
||||
MG_Impl::GLImpl::GetQueryiv(GL_TIME_ELAPSED, GL_CURRENT_QUERY, ¤tQuery);
|
||||
EXPECT_EQ(currentQuery, 0);
|
||||
|
||||
GLuint freshId = 0;
|
||||
MG_Impl::GLImpl::GenQueries(1, &freshId);
|
||||
EXPECT_EQ(freshId, 1u);
|
||||
MG_Impl::GLImpl::DeleteQueries(1, &freshId);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
}
|
||||
|
||||
// Environment-agnostic property test for the env -> ConfigLoader -> Features
|
||||
// chain: whatever MOBILEGL_DISABLE_TIMERQUERY is set to in the environment of
|
||||
// this test process, MG_ConfigLoader::Init must have parsed it with the
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h>
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
#include <MG_Impl/GLImpl/Getter/GL_Getter.h>
|
||||
#include <MG_Impl/GLImpl/Sync/GL_Sync.h>
|
||||
#include <MG_Impl/GLImpl/RenderState/GL_RenderState.h>
|
||||
#include <MG_Impl/GLImpl/Texture/GL_Texture.h>
|
||||
#include <MG_Impl/GLImpl/VertexArray/Validators.h>
|
||||
@@ -1971,6 +1972,9 @@ namespace {
|
||||
MobileGL::Vector<GLuint> framebuffers;
|
||||
MobileGL::Vector<GLuint> renderbuffers;
|
||||
MobileGL::Vector<GLuint> samplers;
|
||||
MobileGL::Vector<GLuint> vertexArrays;
|
||||
MobileGL::Vector<GLuint> programs;
|
||||
MobileGL::Vector<GLuint> buffers;
|
||||
};
|
||||
|
||||
TwinDeletionSinks* g_twinDeletionSinks = nullptr;
|
||||
@@ -1997,6 +2001,24 @@ namespace {
|
||||
if (!g_twinDeletionSinks) return;
|
||||
for (GLsizei i = 0; i < count; ++i) g_twinDeletionSinks->samplers.push_back(ids[i]);
|
||||
}
|
||||
void TW_GenVertexArrays(GLsizei count, GLuint* ids) {
|
||||
for (GLsizei i = 0; i < count; ++i) ids[i] = g_nextTwinDriverId++;
|
||||
}
|
||||
void TW_DeleteVertexArrays(GLsizei count, const GLuint* ids) {
|
||||
if (!g_twinDeletionSinks) return;
|
||||
for (GLsizei i = 0; i < count; ++i) g_twinDeletionSinks->vertexArrays.push_back(ids[i]);
|
||||
}
|
||||
GLuint TW_CreateProgram() { return g_nextTwinDriverId++; }
|
||||
void TW_DeleteProgram(GLuint program) {
|
||||
if (g_twinDeletionSinks) g_twinDeletionSinks->programs.push_back(program);
|
||||
}
|
||||
void TW_GenBuffers(GLsizei count, GLuint* ids) {
|
||||
for (GLsizei i = 0; i < count; ++i) ids[i] = g_nextTwinDriverId++;
|
||||
}
|
||||
void TW_DeleteBuffers(GLsizei count, const GLuint* ids) {
|
||||
if (!g_twinDeletionSinks) return;
|
||||
for (GLsizei i = 0; i < count; ++i) g_twinDeletionSinks->buffers.push_back(ids[i]);
|
||||
}
|
||||
void TW_BindFramebuffer(GLenum target, GLuint framebuffer) {
|
||||
SG_Log("BindFramebuffer:" + std::to_string(target) + ":" + std::to_string(framebuffer));
|
||||
}
|
||||
@@ -2018,6 +2040,12 @@ namespace {
|
||||
functions.glGenSamplers = TW_GenSamplers;
|
||||
functions.glDeleteSamplers = TW_DeleteSamplers;
|
||||
functions.glBindSampler = TW_BindSampler;
|
||||
functions.glGenVertexArrays = TW_GenVertexArrays;
|
||||
functions.glDeleteVertexArrays = TW_DeleteVertexArrays;
|
||||
functions.glCreateProgram = TW_CreateProgram;
|
||||
functions.glDeleteProgram = TW_DeleteProgram;
|
||||
functions.glGenBuffers = TW_GenBuffers;
|
||||
functions.glDeleteBuffers = TW_DeleteBuffers;
|
||||
functions.glGetError = SG_NoError;
|
||||
MobileGL::MG_Backend::DirectGLES::SetGLESFuncsTable(functions);
|
||||
g_twinDeletionSinks = &sinks;
|
||||
@@ -2117,6 +2145,145 @@ TEST(DirectGLESBackendSampler, DestructorDeletesIdAndScrubsUnitCache) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DirectGLESBackendVertexArray, DestructorDeletesIdAndHonorsContextGeneration) {
|
||||
using namespace MobileGL::MG_Backend::DirectGLES;
|
||||
ScopedBackendTwinMocks mocks;
|
||||
|
||||
GLuint id = 0;
|
||||
{
|
||||
auto backendVao = MobileGL::MakeShared<VertexArrayImpl::BackendVertexArrayObject>();
|
||||
id = backendVao->GetBackendVertexArrayId();
|
||||
ASSERT_NE(id, 0u);
|
||||
}
|
||||
ASSERT_EQ(mocks.sinks.vertexArrays.size(), 1u);
|
||||
EXPECT_EQ(mocks.sinks.vertexArrays[0], id);
|
||||
|
||||
// A twin whose context died must NOT delete a VAO name a successor context
|
||||
// may already have recycled (both contexts restart GL names at 1).
|
||||
{
|
||||
auto backendVao = MobileGL::MakeShared<VertexArrayImpl::BackendVertexArrayObject>();
|
||||
++g_backendContextGeneration;
|
||||
backendVao.reset();
|
||||
--g_backendContextGeneration; // restore for later tests
|
||||
EXPECT_EQ(mocks.sinks.vertexArrays.size(), 1u);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DirectGLESBackendProgram, DestructorDeletesIdAndHonorsContextGeneration) {
|
||||
using namespace MobileGL::MG_Backend::DirectGLES;
|
||||
ScopedBackendTwinMocks mocks;
|
||||
|
||||
GLuint id = 0;
|
||||
{
|
||||
auto backendProgram = MobileGL::MakeShared<PrgramImpl::BackendProgramObjectImpl>();
|
||||
id = backendProgram->GetBackendProgramId();
|
||||
ASSERT_NE(id, 0u);
|
||||
}
|
||||
ASSERT_EQ(mocks.sinks.programs.size(), 1u);
|
||||
EXPECT_EQ(mocks.sinks.programs[0], id);
|
||||
|
||||
{
|
||||
auto backendProgram = MobileGL::MakeShared<PrgramImpl::BackendProgramObjectImpl>();
|
||||
++g_backendContextGeneration;
|
||||
backendProgram.reset();
|
||||
--g_backendContextGeneration;
|
||||
EXPECT_EQ(mocks.sinks.programs.size(), 1u);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DirectGLESBackendProgram, GlobalUboDeletionHonorsContextGeneration) {
|
||||
using namespace MobileGL::MG_Backend::DirectGLES;
|
||||
ScopedBackendTwinMocks mocks;
|
||||
|
||||
MobileGL::Uint id = 123;
|
||||
PrgramImpl::DeleteBackendProgramGlobalUbo(id, g_backendContextGeneration);
|
||||
EXPECT_EQ(id, 0u);
|
||||
ASSERT_EQ(mocks.sinks.buffers.size(), 1u);
|
||||
EXPECT_EQ(mocks.sinks.buffers[0], 123u);
|
||||
|
||||
// A buffer belonging to a dead context must be abandoned, never deleted as a
|
||||
// recycled name in the successor context.
|
||||
id = 124;
|
||||
PrgramImpl::DeleteBackendProgramGlobalUbo(id, g_backendContextGeneration - 1);
|
||||
EXPECT_EQ(id, 0u);
|
||||
EXPECT_EQ(mocks.sinks.buffers.size(), 1u);
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct SyncDeleteRacePayload {
|
||||
std::atomic<MobileGL::Bool> alive{true};
|
||||
};
|
||||
std::atomic<MobileGL::Int> g_syncRaceDeleteCount{0};
|
||||
|
||||
MobileGL::MG_Backend::BackendSyncHandle SyncRaceFenceSync() {
|
||||
return new SyncDeleteRacePayload();
|
||||
}
|
||||
|
||||
GLenum SyncRaceClientWaitSync(MobileGL::MG_Backend::BackendSyncHandle handle, GLbitfield, GLuint64) {
|
||||
auto* payload = static_cast<SyncDeleteRacePayload*>(handle);
|
||||
// Keep the backend call in flight while the GL thread runs DeleteSync. The
|
||||
// frontend must not release the backend handle (or the SyncObject wrapper)
|
||||
// until this call has returned.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
return payload->alive.load(std::memory_order_acquire) ? GL_ALREADY_SIGNALED : GL_WAIT_FAILED;
|
||||
}
|
||||
|
||||
void SyncRaceWaitSync(MobileGL::MG_Backend::BackendSyncHandle, GLbitfield, GLuint64) {}
|
||||
|
||||
void SyncRaceDeleteSync(MobileGL::MG_Backend::BackendSyncHandle handle) {
|
||||
auto* payload = static_cast<SyncDeleteRacePayload*>(handle);
|
||||
payload->alive.store(false, std::memory_order_release);
|
||||
delete payload;
|
||||
g_syncRaceDeleteCount.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
MobileGL::Bool SyncRaceGetSyncStatus(MobileGL::MG_Backend::BackendSyncHandle) { return true; }
|
||||
|
||||
struct ScopedSyncRaceBackend {
|
||||
ScopedSyncRaceBackend(): previous(MobileGL::MG_Backend::gBackendFunctionsTable) {
|
||||
MobileGL::MG_Backend::GlobalBackendFunctionsTable functions{};
|
||||
functions.GL.FenceSync = SyncRaceFenceSync;
|
||||
functions.GL.ClientWaitSync = SyncRaceClientWaitSync;
|
||||
functions.GL.WaitSync = SyncRaceWaitSync;
|
||||
functions.GL.DeleteSync = SyncRaceDeleteSync;
|
||||
functions.GL.GetSyncStatus = SyncRaceGetSyncStatus;
|
||||
MobileGL::MG_Backend::gBackendFunctionsTable = functions;
|
||||
g_syncRaceDeleteCount.store(0, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
~ScopedSyncRaceBackend() {
|
||||
MobileGL::MG_Impl::GLImpl::DestroyAllSyncObjects();
|
||||
MobileGL::MG_Backend::gBackendFunctionsTable = previous;
|
||||
}
|
||||
|
||||
ScopedSyncRaceBackend(const ScopedSyncRaceBackend&) = delete;
|
||||
ScopedSyncRaceBackend& operator=(const ScopedSyncRaceBackend&) = delete;
|
||||
|
||||
MobileGL::MG_Backend::GlobalBackendFunctionsTable previous;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST(SyncLifetime, DeleteWaitsForInFlightClientWait) {
|
||||
ScopedSyncRaceBackend backend;
|
||||
|
||||
const GLsync sync = MobileGL::MG_Impl::GLImpl::FenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
ASSERT_NE(sync, nullptr);
|
||||
|
||||
GLenum clientResult = GL_WAIT_FAILED;
|
||||
std::thread waiter([sync, &clientResult] {
|
||||
clientResult = MobileGL::MG_Impl::GLImpl::ClientWaitSync(sync, 0, 0);
|
||||
});
|
||||
|
||||
// Give the worker a head start so ClientWaitSync is already inside the stub
|
||||
// (and therefore holds the per-object lock) when DeleteSync runs.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
MobileGL::MG_Impl::GLImpl::DeleteSync(sync);
|
||||
waiter.join();
|
||||
|
||||
EXPECT_EQ(clientResult, GL_ALREADY_SIGNALED);
|
||||
EXPECT_EQ(g_syncRaceDeleteCount.load(std::memory_order_relaxed), 1);
|
||||
}
|
||||
|
||||
TEST(DirectGLESStateGuards, DefaultFramebufferBindGoesThroughShadow) {
|
||||
using namespace MobileGL::MG_Backend::DirectGLES;
|
||||
ScopedStateGuardMocks mocks;
|
||||
|
||||
@@ -52,6 +52,22 @@ namespace MobileGL {
|
||||
inline UniquePtr<T> MakeUnique(Args&&... args) {
|
||||
return std::make_unique<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
// RAII owner for the one-shot XXH64 state used by the Vulkan cache hashers.
|
||||
// The previous `static inline XXH64_state_t*` form allocated five states per
|
||||
// process and never called XXH64_freeState; a destructor here is independent of
|
||||
// Vulkan/glslang teardown, so it is safe at static destruction time.
|
||||
class XXH64State {
|
||||
public:
|
||||
XXH64State() : m_state(XXH64_createState()) {}
|
||||
~XXH64State() { XXH64_freeState(m_state); }
|
||||
XXH64State(const XXH64State&) = delete;
|
||||
XXH64State& operator=(const XXH64State&) = delete;
|
||||
|
||||
XXH64_state_t* Get() const { return m_state; }
|
||||
|
||||
private:
|
||||
XXH64_state_t* m_state = nullptr;
|
||||
};
|
||||
using SizeT = std::size_t;
|
||||
template <typename T, SizeT N>
|
||||
using Array = std::array<T, N>;
|
||||
|
||||
Reference in New Issue
Block a user