mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53c39d2421 | ||
|
|
9d1b280375 | ||
|
|
8acd885594 | ||
|
|
10ff5e2b18 | ||
|
|
a6e52476f3 | ||
|
|
0deff52a1b | ||
|
|
50fefca959 |
@@ -182,6 +182,7 @@ set(ENABLE_SPVREMAPPER OFF CACHE BOOL "Enable SPVRemapper" FORCE)
|
||||
set(ENABLE_OPT ON CACHE BOOL "Enable SPIRV-Tools opt usage in glslang" FORCE)
|
||||
set(BUILD_EXTERNAL ON CACHE BOOL "Build external deps in External/" FORCE)
|
||||
set(ENABLE_GLSLANG_INSTALL OFF CACHE BOOL "Install glslang targets" FORCE)
|
||||
set(SPIRV_SKIP_EXECUTABLES ON CACHE BOOL "Skip building SPIRV-Tools executables" FORCE)
|
||||
|
||||
set(SPIRV_CROSS_C_API ON CACHE BOOL "Enable C API" FORCE)
|
||||
set(SPIRV_CROSS_ENABLE_GLSL ON CACHE BOOL "Enable GLSL backend" FORCE)
|
||||
|
||||
+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();
|
||||
|
||||
@@ -712,9 +712,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
{
|
||||
.TargetGLVersion = {4, 0, 0}, // GL target version
|
||||
.TargetGLSLVersion = {4, 6, 0}, // Target Shading Language Version
|
||||
// Baseline advertisement (no timer queries / anisotropy yet); reconciled
|
||||
// once the ES capabilities exist, see UpdateAdvertisedCapabilityExtensions.
|
||||
.Extensions = BuildAdvertisedExtensions(false, false),
|
||||
// Baseline advertisement (no runtime capabilities yet); reconciled once
|
||||
// the ES capabilities exist, see UpdateAdvertisedCapabilityExtensions.
|
||||
.Extensions = BuildAdvertisedExtensions(false, false, false, false),
|
||||
.IsCompatibilityProfile = false // Is Compatibility Profile
|
||||
},
|
||||
.StaticBackendCapability = {.AllowVSOnlyPrograms = false} // Backend Capability
|
||||
@@ -734,9 +734,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// thread can only observe the extension string after the
|
||||
// advertisement for its context has settled; rebuilding the whole
|
||||
// list keeps the re-run after a context recreation idempotent.
|
||||
void UpdateAdvertisedCapabilityExtensions(Bool anisotropicFilteringSupported) {
|
||||
MutableRendererInfo().RendererGLInfo.Extensions =
|
||||
BuildAdvertisedExtensions(AreTimerQueriesSupported(), anisotropicFilteringSupported);
|
||||
void UpdateAdvertisedCapabilityExtensions(const MG_External::GLESCapabilities& capabilities) {
|
||||
MutableRendererInfo().RendererGLInfo.Extensions = BuildAdvertisedExtensions(
|
||||
AreTimerQueriesSupported(), capabilities.SupportsTextureFilterAnisotropy,
|
||||
capabilities.SupportsDrawIndirect,
|
||||
capabilities.SupportsDrawIndirect && capabilities.SupportsBaseInstance);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -779,11 +781,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return false;
|
||||
}
|
||||
DirectGLES::SetGLESCapabilities(m_GLESCapabilities);
|
||||
// Now that g_GLESCapabilities knows about GL_EXT_disjoint_timer_query and
|
||||
// GL_EXT_texture_filter_anisotropic, reconcile the advertisement (see the comment on
|
||||
// UpdateAdvertisedCapabilityExtensions for why it cannot happen when the extension
|
||||
// list is first built).
|
||||
UpdateAdvertisedCapabilityExtensions(m_GLESCapabilities.SupportsTextureFilterAnisotropy);
|
||||
// Now that g_GLESCapabilities knows the host extensions, entry points, and ES version,
|
||||
// reconcile every runtime-gated advertisement (see the comment on
|
||||
// UpdateAdvertisedCapabilityExtensions for why this cannot happen when the list is first
|
||||
// built).
|
||||
UpdateAdvertisedCapabilityExtensions(m_GLESCapabilities);
|
||||
UpdateDynamicBackendParameters();
|
||||
PopulateFormatCapabilities(m_GLESFunctions, m_GLESCapabilities, MutableFormatCapabilities());
|
||||
PrintFormatCapabilities(GetFormatCapabilities());
|
||||
@@ -924,7 +926,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return MutableRendererInfo();
|
||||
}
|
||||
|
||||
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported) {
|
||||
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported,
|
||||
Bool drawIndirectSupported,
|
||||
Bool nonZeroIndirectBaseInstanceSupported) {
|
||||
Vector<GLExtension> extensions = {
|
||||
V_OpenGL30, V_OpenGL31, V_OpenGL32, V_OpenGL33, V_OpenGL40, E_GL_ARB_draw_buffers_blend,
|
||||
E_GL_ARB_compute_shader, E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store,
|
||||
@@ -955,6 +959,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// extension explicitly permits. It is also the only thing that
|
||||
// exposes glProgramParameteri before GL 4.1.
|
||||
E_GL_ARB_get_program_binary};
|
||||
// Minecraft 26.3 checks this prerequisite before it even considers
|
||||
// GL_ARB_multi_draw_indirect. ES 3.1 supplies both single-draw entry points; the loader
|
||||
// folds the version and pointer checks into SupportsDrawIndirect.
|
||||
if (drawIndirectSupported) {
|
||||
extensions.push_back(E_GL_ARB_draw_indirect);
|
||||
}
|
||||
// ARB_base_instance also defines the last word of an indirect command. Direct calls are
|
||||
// emulated on every Espryt device, but without host GL_EXT_base_instance a native indirect
|
||||
// draw cannot shift divisor attributes by a GPU-authored non-zero value, so do not promise
|
||||
// that incomplete case.
|
||||
if (drawIndirectSupported && nonZeroIndirectBaseInstanceSupported) {
|
||||
extensions.push_back(E_GL_ARB_base_instance);
|
||||
}
|
||||
// GL_KHR_parallel_shader_compile is MobileGL's own capability, not the host ES
|
||||
// driver's: the compiler threads are MobileGL's, and glCompileShader/glLinkProgram
|
||||
// are serviced entirely inside the frontend. Whether the device driver advertises
|
||||
|
||||
@@ -67,9 +67,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
const RendererInfo& GetRendererIdentity();
|
||||
|
||||
// The full OpenGL extension list Espryt advertises (glGetString(GL_EXTENSIONS))
|
||||
// for a device whose timer queries / anisotropic filtering are (or are not) usable.
|
||||
// for a device whose timer queries / anisotropic filtering / native indirect draws /
|
||||
// non-zero indirect baseInstance semantics are (or are not) usable.
|
||||
// The MOBILEGL_DISABLE_TIMERQUERY escape hatch is applied inside.
|
||||
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported);
|
||||
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported,
|
||||
Bool drawIndirectSupported,
|
||||
Bool nonZeroIndirectBaseInstanceSupported);
|
||||
|
||||
// Format: <OpenGL ES Renderer>, OpenGL ES <Major>.<Minor> — the exact string an
|
||||
// initialized backend returns from GetBackendAPIVersionString (and that ends up
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -3061,10 +3063,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
|
||||
static Bool SupportsNativeIndirectDraws() {
|
||||
const auto& version = g_GLESCapabilities.GLESVersion;
|
||||
const Bool esVersionOk = version.Major > 3 || (version.Major == 3 && version.Minor >= 1);
|
||||
return esVersionOk && g_GLESFuncs.glDrawElementsIndirect != nullptr &&
|
||||
g_GLESFuncs.glDrawArraysIndirect != nullptr;
|
||||
return g_GLESCapabilities.SupportsDrawIndirect;
|
||||
}
|
||||
|
||||
// Runs an (indexed) indirect multi-draw. When a GL_DRAW_INDIRECT_BUFFER is bound the draws
|
||||
@@ -5902,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);
|
||||
g_GLESFuncs.glDeleteVertexArrays(1, &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) {
|
||||
MGLOG_D("Deleting backend program object with ID: %u", m_backendProgramId);
|
||||
g_GLESFuncs.glDeleteProgram(m_backendProgramId);
|
||||
// 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;
|
||||
|
||||
|
||||
@@ -497,20 +497,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
.ExtraVendor = Nullopt,
|
||||
.RendererGLInfo = {.TargetGLVersion = {4, 0, 0},
|
||||
.TargetGLSLVersion = {4, 6, 0},
|
||||
// Baseline advertisement (no shader subgroup, no timer queries); a
|
||||
// live backend reconciles its copy in UpdateAdvertisedExtensions.
|
||||
.Extensions = BuildAdvertisedExtensions(false, false, false),
|
||||
// Baseline advertisement (no runtime-gated capabilities); a live
|
||||
// backend reconciles its copy in UpdateAdvertisedExtensions.
|
||||
.Extensions = BuildAdvertisedExtensions(false, false, false, false),
|
||||
.IsCompatibilityProfile = false},
|
||||
.StaticBackendCapability = {.AllowVSOnlyPrograms = false}};
|
||||
return rendererInfo;
|
||||
}
|
||||
|
||||
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported,
|
||||
Bool anisotropicFilteringSupported) {
|
||||
Bool anisotropicFilteringSupported,
|
||||
Bool nonZeroIndirectBaseInstanceSupported) {
|
||||
Vector<GLExtension> extensions = {
|
||||
V_OpenGL30, V_OpenGL31, V_OpenGL32, V_OpenGL33, V_OpenGL40, E_GL_ARB_draw_buffers_blend,
|
||||
E_GL_ARB_compute_shader, E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store,
|
||||
E_GL_ARB_program_interface_query, E_GL_ARB_framebuffer_object, E_GL_ARB_multi_draw_indirect,
|
||||
E_GL_ARB_program_interface_query, E_GL_ARB_framebuffer_object, E_GL_ARB_draw_indirect,
|
||||
E_GL_ARB_multi_draw_indirect,
|
||||
E_GL_ARB_indirect_parameters, E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage,
|
||||
E_GL_ARB_texture_storage, E_GL_ARB_texture_storage_multisample, E_GL_ARB_texture_multisample,
|
||||
E_GL_ARB_clear_texture, E_GL_ARB_direct_state_access, E_GL_ARB_shader_draw_parameters,
|
||||
@@ -530,6 +532,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// extension explicitly permits. It is also the only thing that
|
||||
// exposes glProgramParameteri before GL 4.1.
|
||||
E_GL_ARB_get_program_binary};
|
||||
// Vulkan's drawIndirectFirstInstance feature is optional. Direct base-instance calls work
|
||||
// without it, but ARB_base_instance also promises non-zero firstInstance in GPU indirect
|
||||
// commands; the renderer supplies true only when that word is legal and gl_InstanceID can
|
||||
// be rebased to OpenGL's zero-based semantics.
|
||||
if (nonZeroIndirectBaseInstanceSupported) {
|
||||
extensions.push_back(E_GL_ARB_base_instance);
|
||||
}
|
||||
if (shaderSubgroupSupported && !MG_Config::Features.DisableSubgroup) {
|
||||
extensions.push_back(E_GL_KHR_shader_subgroup);
|
||||
}
|
||||
@@ -690,7 +699,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// the whole list keeps re-runs idempotent.
|
||||
m_rendererInfo.RendererGLInfo.Extensions = BuildAdvertisedExtensions(
|
||||
m_vulkanCaps.SupportsShaderSubgroup, pVulkanRenderer && pVulkanRenderer->IsTimerQuerySupported(),
|
||||
pVulkanRenderer && pVulkanRenderer->IsSamplerAnisotropySupported());
|
||||
pVulkanRenderer && pVulkanRenderer->IsSamplerAnisotropySupported(),
|
||||
pVulkanRenderer && pVulkanRenderer->IsNonZeroIndirectBaseInstanceSupported());
|
||||
}
|
||||
|
||||
void BackendObject_DirectVulkan::UpdateDynamicBackendParameters() {
|
||||
|
||||
@@ -62,8 +62,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// POST screen shows.
|
||||
|
||||
// Static identity of the Magma renderer (renderer/backend names, target GL/GLSL
|
||||
// versions, ExtraVendor) with the baseline extension advertisement (no shader
|
||||
// subgroup, no timer queries). A live backend copies this in its constructor and
|
||||
// versions, ExtraVendor) with the baseline extension advertisement (no runtime-gated
|
||||
// capabilities). A live backend copies this in its constructor and
|
||||
// reconciles the Extensions in UpdateAdvertisedExtensions once real capabilities
|
||||
// exist; callers that need the advertised list for a known capability set must
|
||||
// use BuildAdvertisedExtensions instead.
|
||||
@@ -74,7 +74,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// MOBILEGL_DISABLE_TIMERQUERY escape hatches are applied inside, so callers pass
|
||||
// the detected device support (passing an already-gated value is harmless).
|
||||
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported,
|
||||
Bool anisotropicFilteringSupported);
|
||||
Bool anisotropicFilteringSupported,
|
||||
Bool nonZeroIndirectBaseInstanceSupported);
|
||||
|
||||
// Format: <GPU Name>, Vulkan <Vulkan Version>, Driver <Driver Version> — the exact
|
||||
// string an initialized backend returns from GetBackendAPIVersionString (and that
|
||||
|
||||
@@ -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 {
|
||||
@@ -224,6 +225,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
fragmentInputComponentCount = other.fragmentInputComponentCount;
|
||||
fragmentReplacesDepth = other.fragmentReplacesDepth;
|
||||
readsBaseVertexBuiltin = other.readsBaseVertexBuiltin;
|
||||
writesViewportIndexBuiltin = other.writesViewportIndexBuiltin;
|
||||
needsPassthroughTessControl = other.needsPassthroughTessControl;
|
||||
passthroughTessControlEmulatable = other.passthroughTessControlEmulatable;
|
||||
lastUsedFrame = other.lastUsedFrame;
|
||||
@@ -240,6 +242,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
other.fragmentInputComponentCount = 0;
|
||||
other.fragmentReplacesDepth = false;
|
||||
other.readsBaseVertexBuiltin = false;
|
||||
other.writesViewportIndexBuiltin = false;
|
||||
other.needsPassthroughTessControl = false;
|
||||
other.passthroughTessControlEmulatable = false;
|
||||
other.lastUsedFrame = 0;
|
||||
@@ -282,6 +285,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
fragmentInputComponentCount = other.fragmentInputComponentCount;
|
||||
fragmentReplacesDepth = other.fragmentReplacesDepth;
|
||||
readsBaseVertexBuiltin = other.readsBaseVertexBuiltin;
|
||||
writesViewportIndexBuiltin = other.writesViewportIndexBuiltin;
|
||||
needsPassthroughTessControl = other.needsPassthroughTessControl;
|
||||
passthroughTessControlEmulatable = other.passthroughTessControlEmulatable;
|
||||
lastUsedFrame = other.lastUsedFrame;
|
||||
@@ -298,6 +302,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
other.fragmentInputComponentCount = 0;
|
||||
other.fragmentReplacesDepth = false;
|
||||
other.readsBaseVertexBuiltin = false;
|
||||
other.writesViewportIndexBuiltin = false;
|
||||
other.needsPassthroughTessControl = false;
|
||||
other.passthroughTessControlEmulatable = false;
|
||||
other.lastUsedFrame = 0;
|
||||
@@ -485,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
|
||||
|
||||
@@ -305,7 +305,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// texture/sampler resolution, completeness probe, sync, layout handling, sampler
|
||||
// and view lookups - would recompute the identical descriptor.
|
||||
if (trustUnchangedHint && descriptorMemoUsable && binding < m_samplerResolveMemo.size() &&
|
||||
m_samplerResolveMemo[binding].infoValid) {
|
||||
m_samplerResolveMemo[binding].infoValid &&
|
||||
m_samplerResolveMemo[binding].infoProgramLifetimeId == program.GetLifetimeId()) {
|
||||
outImageInfo = m_samplerResolveMemo[binding].info;
|
||||
return true;
|
||||
}
|
||||
@@ -504,6 +505,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
if (binding < m_samplerResolveMemo.size()) {
|
||||
if (descriptorMemoUsable) {
|
||||
m_samplerResolveMemo[binding].info = outImageInfo;
|
||||
m_samplerResolveMemo[binding].infoProgramLifetimeId = program.GetLifetimeId();
|
||||
m_samplerResolveMemo[binding].infoValid = true;
|
||||
} else {
|
||||
// An arrayed binding publishes nothing here, and clears what a previous program
|
||||
|
||||
@@ -341,8 +341,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// lifetime id, so a freed-and-reallocated sampler or texture at the same heap address
|
||||
// always gets a fresh id and misses (a raw pointer would false-hit that ABA) - so a
|
||||
// stale guess can only miss and fall through to the hash, never resolve wrong. Still
|
||||
// reset each frame alongside the descriptor-set cache. Indexed by binding.
|
||||
// reset each frame alongside the descriptor-set cache. Indexed by binding, but the
|
||||
// whole-descriptor entry is additionally keyed by program lifetime: Vulkan binding
|
||||
// numbers are layout-local and unrelated programs routinely reuse binding 0/1.
|
||||
struct SamplerResolveMemo {
|
||||
Uint64 infoProgramLifetimeId = 0;
|
||||
Uint64 samplerLifetimeId = 0;
|
||||
Uint64 textureLifetimeId = 0;
|
||||
VkSampler sampler = VK_NULL_HANDLE;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -238,11 +238,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// nothing else across all of gl33.
|
||||
//
|
||||
// The mapping below is derived from - and at full extent exactly reproduces - the pixel
|
||||
// mapping RemapDefaultFboReadbackToGLOrientation has always used:
|
||||
// mapping VulkanRenderer::RemapDefaultFramebufferReadback uses:
|
||||
// identity : image(x, H-1-y) -> flip Y
|
||||
// 180 : image(W-1-x, y) -> mirror X (the rotation already flips the rows)
|
||||
// Quarter turns swap the axes; nothing in this renderer models that (the readback declines to
|
||||
// remap them and the viewport path only rescales), so they are left exactly as they were.
|
||||
// Quarter turns swap the axes and are handled by MapDefaultFramebufferReadbackRect rather than
|
||||
// this same-axis helper.
|
||||
struct DefaultFramebufferRectMapping {
|
||||
Bool flipY = false;
|
||||
Bool mirrorX = false;
|
||||
@@ -2151,47 +2151,6 @@ void main() {
|
||||
return static_cast<Uint8>(value * 255.0f + 0.5f);
|
||||
}
|
||||
|
||||
// Re-order the copied BLOCK - not the whole image - from the default framebuffer's stored
|
||||
// orientation into GL's. The caller has already aimed the copy at the right place with
|
||||
// MapDefaultFramebufferRectAxis, so what arrives here is exactly the requested
|
||||
// rectWidth x rectHeight rect, and all that is left is the order of rows (identity) or of
|
||||
// columns (180) WITHIN it.
|
||||
//
|
||||
// This used to iterate the full swapchain extent and index both sides with that stride,
|
||||
// which is why its caller could only use it on an exact full-extent read - and why every
|
||||
// partial glReadPixels of the default framebuffer came back in Vulkan row order. Only
|
||||
// identity/180 share the swapchain extent with the default framebuffer; 90/270 swap
|
||||
// extents and are still declined.
|
||||
static Bool RemapDefaultFboReadbackToGLOrientation(const Uint8* rawPixels,
|
||||
Uint32 rectWidth,
|
||||
Uint32 rectHeight,
|
||||
VkSurfaceTransformFlagBitsKHR preTransform,
|
||||
SizeT texelSize,
|
||||
Uint8* outPixels) {
|
||||
if (IsQuarterTurnPreTransform(preTransform)) {
|
||||
return false;
|
||||
}
|
||||
if (rectWidth == 0 || rectHeight == 0 || texelSize == 0) {
|
||||
return false;
|
||||
}
|
||||
const DefaultFramebufferRectMapping mapping = GetDefaultFramebufferRectMapping(preTransform);
|
||||
const SizeT rowBytes = static_cast<SizeT>(rectWidth) * texelSize;
|
||||
for (Uint32 outY = 0; outY < rectHeight; ++outY) {
|
||||
const Uint32 srcY = mapping.flipY ? (rectHeight - 1 - outY) : outY;
|
||||
const Uint8* srcRow = rawPixels + static_cast<SizeT>(srcY) * rowBytes;
|
||||
Uint8* dstRow = outPixels + static_cast<SizeT>(outY) * rowBytes;
|
||||
if (!mapping.mirrorX) {
|
||||
Memcpy(dstRow, srcRow, rowBytes);
|
||||
continue;
|
||||
}
|
||||
for (Uint32 outX = 0; outX < rectWidth; ++outX) {
|
||||
Memcpy(dstRow + static_cast<SizeT>(outX) * texelSize,
|
||||
srcRow + static_cast<SizeT>(rectWidth - 1 - outX) * texelSize, texelSize);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static SizeT AlignPixelRow(SizeT rowBytes, Int alignment) {
|
||||
const SizeT resolvedAlignment = static_cast<SizeT>(std::max(alignment, 1));
|
||||
return (rowBytes + resolvedAlignment - 1) & ~(resolvedAlignment - 1);
|
||||
@@ -2738,6 +2697,95 @@ void main() {
|
||||
return formatInfo.texel_block_size;
|
||||
}
|
||||
|
||||
Bool VulkanRenderer::MapDefaultFramebufferReadbackRect(
|
||||
GLint x, GLint y, GLsizei width, GLsizei height, VkExtent2D imageExtent,
|
||||
VkSurfaceTransformFlagBitsKHR preTransform, VkOffset2D* imageOffset,
|
||||
VkExtent2D* imageCopyExtent) {
|
||||
if (width <= 0 || height <= 0 || imageOffset == nullptr || imageCopyExtent == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Int imageWidth = static_cast<Int>(imageExtent.width);
|
||||
const Int imageHeight = static_cast<Int>(imageExtent.height);
|
||||
Int mappedX = x;
|
||||
Int mappedY = y;
|
||||
Uint32 mappedWidth = static_cast<Uint32>(width);
|
||||
Uint32 mappedHeight = static_cast<Uint32>(height);
|
||||
|
||||
// InsertPositionFixup first flips GL Y and then applies the surface transform. In pixel
|
||||
// coordinates that gives these half-open rectangle mappings into the stored image:
|
||||
// identity: (x, H-y-h), 90: (y, x), 180: (W-x-w, y), 270: (H-y-h, W-x-w).
|
||||
// Quarter turns also transpose the copied block's extent.
|
||||
switch (preTransform) {
|
||||
case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
|
||||
mappedX = y;
|
||||
mappedY = x;
|
||||
mappedWidth = static_cast<Uint32>(height);
|
||||
mappedHeight = static_cast<Uint32>(width);
|
||||
break;
|
||||
case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
|
||||
mappedX = imageWidth - x - width;
|
||||
mappedY = y;
|
||||
break;
|
||||
case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
|
||||
mappedX = imageWidth - y - height;
|
||||
mappedY = imageHeight - x - width;
|
||||
mappedWidth = static_cast<Uint32>(height);
|
||||
mappedHeight = static_cast<Uint32>(width);
|
||||
break;
|
||||
default:
|
||||
mappedY = imageHeight - y - height;
|
||||
break;
|
||||
}
|
||||
|
||||
if (mappedX < 0 || mappedY < 0 || mappedWidth > imageExtent.width ||
|
||||
mappedHeight > imageExtent.height ||
|
||||
static_cast<Uint64>(mappedX) + mappedWidth > imageExtent.width ||
|
||||
static_cast<Uint64>(mappedY) + mappedHeight > imageExtent.height) {
|
||||
return false;
|
||||
}
|
||||
*imageOffset = {mappedX, mappedY};
|
||||
*imageCopyExtent = {mappedWidth, mappedHeight};
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VulkanRenderer::RemapDefaultFramebufferReadback(
|
||||
const Uint8* rawPixels, Uint32 logicalWidth, Uint32 logicalHeight,
|
||||
VkSurfaceTransformFlagBitsKHR preTransform, SizeT texelSize, Uint8* outPixels) {
|
||||
if (rawPixels == nullptr || outPixels == nullptr || logicalWidth == 0 || logicalHeight == 0 ||
|
||||
texelSize == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Uint32 rawWidth = IsQuarterTurnPreTransform(preTransform) ? logicalHeight : logicalWidth;
|
||||
for (Uint32 outY = 0; outY < logicalHeight; ++outY) {
|
||||
for (Uint32 outX = 0; outX < logicalWidth; ++outX) {
|
||||
Uint32 srcX = outX;
|
||||
Uint32 srcY = outY;
|
||||
switch (preTransform) {
|
||||
case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
|
||||
srcX = outY;
|
||||
srcY = outX;
|
||||
break;
|
||||
case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
|
||||
srcX = logicalWidth - 1 - outX;
|
||||
break;
|
||||
case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
|
||||
srcX = logicalHeight - 1 - outY;
|
||||
srcY = logicalWidth - 1 - outX;
|
||||
break;
|
||||
default:
|
||||
srcY = logicalHeight - 1 - outY;
|
||||
break;
|
||||
}
|
||||
Memcpy(outPixels + (static_cast<SizeT>(outY) * logicalWidth + outX) * texelSize,
|
||||
rawPixels + (static_cast<SizeT>(srcY) * rawWidth + srcX) * texelSize,
|
||||
texelSize);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VulkanRenderer::ConvertReadbackPixels(const Uint8* sourcePixels, VkFormat sourceFormat,
|
||||
GLsizei width, GLsizei height, GLenum destinationFormat,
|
||||
GLenum destinationType, SizeT destinationRowStride,
|
||||
@@ -9138,19 +9186,18 @@ void main() {
|
||||
// The GL rect, aimed at the default framebuffer's stored orientation. Using the GL y
|
||||
// verbatim copied rows [y, y+h) counted from the TOP of the image, i.e. the wrong band for
|
||||
// every read that was not full-height.
|
||||
Int32 copyOffsetX = x;
|
||||
Int32 copyOffsetY = y;
|
||||
VkOffset2D copyOffset{x, y};
|
||||
VkExtent2D copyExtent{static_cast<Uint32>(width), static_cast<Uint32>(height)};
|
||||
if (readIsDefaultFbo) {
|
||||
const VkExtent2D defaultFboExtent = m_swapchainObject.GetExtent();
|
||||
const DefaultFramebufferRectMapping mapping =
|
||||
GetDefaultFramebufferRectMapping(m_swapchainObject.GetPreTransform());
|
||||
copyOffsetX = MapDefaultFramebufferRectAxis(x, width, static_cast<Int>(defaultFboExtent.width),
|
||||
mapping.mirrorX);
|
||||
copyOffsetY = MapDefaultFramebufferRectAxis(y, height, static_cast<Int>(defaultFboExtent.height),
|
||||
mapping.flipY);
|
||||
const Bool mapped = MapDefaultFramebufferReadbackRect(
|
||||
x, y, width, height, defaultFboExtent, m_swapchainObject.GetPreTransform(), ©Offset,
|
||||
©Extent);
|
||||
MOBILEGL_ASSERT(mapped, "ReadPixels: default framebuffer read rectangle is out of bounds");
|
||||
if (!mapped) return;
|
||||
}
|
||||
copyRegion.imageOffset = {copyOffsetX, copyOffsetY, static_cast<Int32>(srcBinding.depthOffset)};
|
||||
copyRegion.imageExtent = {static_cast<Uint32>(width), static_cast<Uint32>(height), 1};
|
||||
copyRegion.imageOffset = {copyOffset.x, copyOffset.y, static_cast<Int32>(srcBinding.depthOffset)};
|
||||
copyRegion.imageExtent = {copyExtent.width, copyExtent.height, 1};
|
||||
vkCmdCopyImageToBuffer(frame.commandBuffer, srcBinding.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
readback.GetHandle(), 1, ©Region);
|
||||
|
||||
@@ -9192,16 +9239,14 @@ void main() {
|
||||
// already aimed with the same mapping. The gate is exactly what made every partial
|
||||
// read of the default framebuffer come back in Vulkan row order.
|
||||
Vector<Uint8> remapped(static_cast<SizeT>(width) * static_cast<SizeT>(height) * sourceTexelSize);
|
||||
if (RemapDefaultFboReadbackToGLOrientation(mapped, static_cast<Uint32>(width),
|
||||
static_cast<Uint32>(height), preTransform, sourceTexelSize,
|
||||
remapped.data())) {
|
||||
if (RemapDefaultFramebufferReadback(mapped, static_cast<Uint32>(width),
|
||||
static_cast<Uint32>(height), preTransform, sourceTexelSize,
|
||||
remapped.data())) {
|
||||
PackReadbackToClientOrPbo(remapped.data(), srcFormat, width, height, 1, format, type, pixels,
|
||||
/*applyPackImageParams=*/false, /*applyReadColorClamp=*/true);
|
||||
return;
|
||||
}
|
||||
// Only a quarter-turn pre-transform reaches this, and nothing in this renderer models
|
||||
// one. MGLOG_I because the INFO builds are the ones that run conformance.
|
||||
MGLOG_D("DirectVulkan::ReadPixels: default-FBO remap declined (w=%d h=%d preTransform=%d); falling back "
|
||||
MGLOG_D("DirectVulkan::ReadPixels: default-FBO remap failed (w=%d h=%d preTransform=%d); falling back "
|
||||
"to raw readback",
|
||||
width, height, static_cast<Int>(preTransform));
|
||||
}
|
||||
@@ -9582,16 +9627,15 @@ void main() {
|
||||
// The swapchain's depth/stencil image is stored display-side-up like its colour twin, so
|
||||
// the GL rect has to be mapped into that space before the copy and the copied rows
|
||||
// re-oriented afterwards - the same two halves the colour ReadPixels path applies.
|
||||
Int32 copyOffsetX = x;
|
||||
Int32 copyOffsetY = y;
|
||||
VkOffset2D copyOffset{x, y};
|
||||
VkExtent2D copyExtent{static_cast<Uint32>(width), static_cast<Uint32>(height)};
|
||||
if (defaultFramebufferOrientation) {
|
||||
const VkExtent2D defaultFboExtent = m_swapchainObject.GetExtent();
|
||||
const DefaultFramebufferRectMapping mapping =
|
||||
GetDefaultFramebufferRectMapping(m_swapchainObject.GetPreTransform());
|
||||
copyOffsetX = MapDefaultFramebufferRectAxis(x, width, static_cast<Int>(defaultFboExtent.width),
|
||||
mapping.mirrorX);
|
||||
copyOffsetY = MapDefaultFramebufferRectAxis(y, height, static_cast<Int>(defaultFboExtent.height),
|
||||
mapping.flipY);
|
||||
const Bool mapped = MapDefaultFramebufferReadbackRect(
|
||||
x, y, width, height, defaultFboExtent, m_swapchainObject.GetPreTransform(), ©Offset,
|
||||
©Extent);
|
||||
MOBILEGL_ASSERT(mapped, "ReadDepthStencilPixels: default framebuffer read rectangle is out of bounds");
|
||||
if (!mapped) return;
|
||||
}
|
||||
|
||||
VkBufferImageCopy regions[2]{};
|
||||
@@ -9603,8 +9647,8 @@ void main() {
|
||||
region.imageSubresource.mipLevel = mipLevel;
|
||||
region.imageSubresource.baseArrayLayer = baseArrayLayer;
|
||||
region.imageSubresource.layerCount = 1;
|
||||
region.imageOffset = {copyOffsetX, copyOffsetY, 0};
|
||||
region.imageExtent = {static_cast<Uint32>(width), static_cast<Uint32>(height), 1};
|
||||
region.imageOffset = {copyOffset.x, copyOffset.y, 0};
|
||||
region.imageExtent = {copyExtent.width, copyExtent.height, 1};
|
||||
}
|
||||
if (wantStencil) {
|
||||
auto& region = regions[regionCount++];
|
||||
@@ -9613,8 +9657,8 @@ void main() {
|
||||
region.imageSubresource.mipLevel = mipLevel;
|
||||
region.imageSubresource.baseArrayLayer = baseArrayLayer;
|
||||
region.imageSubresource.layerCount = 1;
|
||||
region.imageOffset = {copyOffsetX, copyOffsetY, 0};
|
||||
region.imageExtent = {static_cast<Uint32>(width), static_cast<Uint32>(height), 1};
|
||||
region.imageOffset = {copyOffset.x, copyOffset.y, 0};
|
||||
region.imageExtent = {copyExtent.width, copyExtent.height, 1};
|
||||
}
|
||||
vkCmdCopyImageToBuffer(frame.commandBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, readback.GetHandle(),
|
||||
regionCount, regions);
|
||||
@@ -9648,23 +9692,21 @@ void main() {
|
||||
Bool remapped = true;
|
||||
if (wantDepth && depthCopyBytes > 0) {
|
||||
remappedDepth.resize(pixelCount * depthCopyBytes);
|
||||
remapped = RemapDefaultFboReadbackToGLOrientation(depthSrc, static_cast<Uint32>(width),
|
||||
static_cast<Uint32>(height), preTransform,
|
||||
depthCopyBytes, remappedDepth.data());
|
||||
remapped = RemapDefaultFramebufferReadback(depthSrc, static_cast<Uint32>(width),
|
||||
static_cast<Uint32>(height), preTransform,
|
||||
depthCopyBytes, remappedDepth.data());
|
||||
}
|
||||
if (remapped && wantStencil) {
|
||||
remappedStencil.resize(pixelCount);
|
||||
remapped = RemapDefaultFboReadbackToGLOrientation(stencilSrc, static_cast<Uint32>(width),
|
||||
static_cast<Uint32>(height), preTransform, 1,
|
||||
remappedStencil.data());
|
||||
remapped = RemapDefaultFramebufferReadback(stencilSrc, static_cast<Uint32>(width),
|
||||
static_cast<Uint32>(height), preTransform, 1,
|
||||
remappedStencil.data());
|
||||
}
|
||||
if (remapped) {
|
||||
if (!remappedDepth.empty()) depthSrc = remappedDepth.data();
|
||||
if (!remappedStencil.empty()) stencilSrc = remappedStencil.data();
|
||||
} else {
|
||||
// Only a quarter-turn pre-transform reaches this, and nothing in this renderer
|
||||
// models one. MGLOG_I because the INFO builds are the ones that run conformance.
|
||||
MGLOG_D("DirectVulkan::ReadDepthStencilPixels: default-FBO remap declined (w=%d h=%d "
|
||||
MGLOG_D("DirectVulkan::ReadDepthStencilPixels: default-FBO remap failed (w=%d h=%d "
|
||||
"preTransform=%d); falling back to raw readback",
|
||||
width, height, static_cast<Int>(preTransform));
|
||||
}
|
||||
|
||||
@@ -229,6 +229,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
GLint dstY, GLint width, GLint height, VkImageLayout srcRestoreLayout,
|
||||
VkImageLayout dstRestoreLayout, Bool stencilAspect);
|
||||
static SizeT GetReadbackTexelSize(VkFormat sourceFormat);
|
||||
// Map a GL bottom-left-origin rectangle into the display-oriented swapchain image.
|
||||
// Quarter-turn surface transforms swap the copy extent's axes.
|
||||
static Bool MapDefaultFramebufferReadbackRect(GLint x, GLint y, GLsizei width, GLsizei height,
|
||||
VkExtent2D imageExtent,
|
||||
VkSurfaceTransformFlagBitsKHR preTransform,
|
||||
VkOffset2D* imageOffset, VkExtent2D* imageCopyExtent);
|
||||
// Reorder a tightly packed block copied with MapDefaultFramebufferReadbackRect back into
|
||||
// GL row order. The input block has swapped dimensions for 90/270 degree transforms.
|
||||
static Bool RemapDefaultFramebufferReadback(const Uint8* rawPixels, Uint32 logicalWidth,
|
||||
Uint32 logicalHeight,
|
||||
VkSurfaceTransformFlagBitsKHR preTransform,
|
||||
SizeT texelSize, Uint8* outPixels);
|
||||
static Bool ConvertReadbackPixels(const Uint8* sourcePixels, VkFormat sourceFormat,
|
||||
GLsizei width, GLsizei height, GLenum destinationFormat,
|
||||
GLenum destinationType, SizeT destinationRowStride,
|
||||
@@ -298,6 +310,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// The samplerAnisotropy device feature was granted, so GL_TEXTURE_MAX_ANISOTROPY_EXT is
|
||||
// honored rather than accepted-and-ignored.
|
||||
Bool IsSamplerAnisotropySupported() const { return m_samplerAnisotropyFeatureEnabled; }
|
||||
// ARB_base_instance extends indirect command records with a non-zero firstInstance and
|
||||
// requires gl_InstanceID to remain zero-based. Vulkan needs both features to honor that
|
||||
// complete contract: one legalizes the command word, the other enables the shader rebase.
|
||||
Bool IsNonZeroIndirectBaseInstanceSupported() const {
|
||||
return m_drawIndirectFirstInstanceFeatureEnabled && m_shaderDrawParametersFeatureEnabled;
|
||||
}
|
||||
// Ensures the frame command buffer is recording (same lazy pattern as
|
||||
// SetupDraw) and writes a bottom-of-pipe timestamp into the current
|
||||
// frame's pool. Null when unsupported or the pool is exhausted.
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -8,9 +8,28 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <MG_Backend/DirectVulkan/Renderer/ProgramFactory.h>
|
||||
|
||||
TEST(DirectVulkanSanity, ProgramMovePreservesViewportIndexUsage) {
|
||||
using VkProgramObject = MobileGL::MG_Backend::DirectVulkan::ProgramFactory::VkProgramObject;
|
||||
|
||||
VkProgramObject moveConstructedSource;
|
||||
moveConstructedSource.writesViewportIndexBuiltin = true;
|
||||
VkProgramObject moveConstructed(std::move(moveConstructedSource));
|
||||
EXPECT_TRUE(moveConstructed.writesViewportIndexBuiltin);
|
||||
EXPECT_FALSE(moveConstructedSource.writesViewportIndexBuiltin);
|
||||
|
||||
VkProgramObject moveAssignedSource;
|
||||
moveAssignedSource.writesViewportIndexBuiltin = true;
|
||||
VkProgramObject moveAssigned;
|
||||
moveAssigned = std::move(moveAssignedSource);
|
||||
EXPECT_TRUE(moveAssigned.writesViewportIndexBuiltin);
|
||||
EXPECT_FALSE(moveAssignedSource.writesViewportIndexBuiltin);
|
||||
}
|
||||
|
||||
TEST(DirectVulkanSanity, ExtensionEnumeration) {
|
||||
uint32_t extensionCount = 0;
|
||||
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
|
||||
|
||||
@@ -725,22 +725,57 @@ TEST(TextureAnisotropyCapabilities, ExtensionIsAdvertisedOnlyWhenTheHostDriverSu
|
||||
return std::find(extensions.begin(), extensions.end(), wanted) != extensions.end();
|
||||
};
|
||||
|
||||
const auto without = MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false);
|
||||
const auto without = MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false);
|
||||
EXPECT_FALSE(contains(without, MobileGL::E_GL_EXT_texture_filter_anisotropic));
|
||||
EXPECT_FALSE(contains(without, MobileGL::E_GL_ARB_texture_filter_anisotropic));
|
||||
|
||||
const auto with = MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, true);
|
||||
const auto with = MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, true, false, false);
|
||||
EXPECT_TRUE(contains(with, MobileGL::E_GL_EXT_texture_filter_anisotropic));
|
||||
EXPECT_TRUE(contains(with, MobileGL::E_GL_ARB_texture_filter_anisotropic));
|
||||
|
||||
// Same rule on the Vulkan backend, where the gate is the samplerAnisotropy device feature.
|
||||
const auto vkWithout = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false);
|
||||
const auto vkWithout = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false);
|
||||
EXPECT_FALSE(contains(vkWithout, MobileGL::E_GL_EXT_texture_filter_anisotropic));
|
||||
const auto vkWith = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, true);
|
||||
const auto vkWith = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, true, false);
|
||||
EXPECT_TRUE(contains(vkWith, MobileGL::E_GL_EXT_texture_filter_anisotropic));
|
||||
EXPECT_TRUE(contains(vkWith, MobileGL::E_GL_ARB_texture_filter_anisotropic));
|
||||
}
|
||||
|
||||
// Minecraft 26.3 checks ARB_draw_indirect before it considers the already-advertised
|
||||
// ARB_multi_draw_indirect, then separately requires ARB_base_instance before enabling its terrain
|
||||
// indirect path. Pin both strings and, just as importantly, the non-zero firstInstance gate.
|
||||
TEST(IndirectDrawAdvertisement, MatchesEachBackendsUsableCommandSemantics) {
|
||||
const auto contains = [](const MobileGL::Vector<MobileGL::GLExtension>& extensions,
|
||||
MobileGL::GLExtension wanted) {
|
||||
return std::find(extensions.begin(), extensions.end(), wanted) != extensions.end();
|
||||
};
|
||||
|
||||
const auto esWithoutIndirect =
|
||||
MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false);
|
||||
EXPECT_FALSE(contains(esWithoutIndirect, MobileGL::E_GL_ARB_draw_indirect));
|
||||
EXPECT_FALSE(contains(esWithoutIndirect, MobileGL::E_GL_ARB_base_instance));
|
||||
|
||||
const auto esWithoutBaseInstance =
|
||||
MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, true, false);
|
||||
EXPECT_TRUE(contains(esWithoutBaseInstance, MobileGL::E_GL_ARB_draw_indirect));
|
||||
EXPECT_FALSE(contains(esWithoutBaseInstance, MobileGL::E_GL_ARB_base_instance));
|
||||
|
||||
const auto esWithBoth =
|
||||
MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, true, true);
|
||||
EXPECT_TRUE(contains(esWithBoth, MobileGL::E_GL_ARB_draw_indirect));
|
||||
EXPECT_TRUE(contains(esWithBoth, MobileGL::E_GL_ARB_base_instance));
|
||||
|
||||
const auto vkWithoutBaseInstance =
|
||||
MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false);
|
||||
EXPECT_TRUE(contains(vkWithoutBaseInstance, MobileGL::E_GL_ARB_draw_indirect));
|
||||
EXPECT_FALSE(contains(vkWithoutBaseInstance, MobileGL::E_GL_ARB_base_instance));
|
||||
|
||||
const auto vkWithBoth =
|
||||
MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, true);
|
||||
EXPECT_TRUE(contains(vkWithBoth, MobileGL::E_GL_ARB_draw_indirect));
|
||||
EXPECT_TRUE(contains(vkWithBoth, MobileGL::E_GL_ARB_base_instance));
|
||||
}
|
||||
|
||||
TEST(TextureAnisotropyCapabilities, MaxAnisotropyIsQueriedOnlyWhenTheExtensionIsPresent) {
|
||||
ResetFakeDriver();
|
||||
g_fake.maxVertexSsboBlocks = 0;
|
||||
@@ -836,3 +871,63 @@ TEST(MultiDrawCapabilities, ExtensionWithoutResolvedPointerIsNotSupport) {
|
||||
EXPECT_FALSE(caps.SupportsMultiDrawIndirect);
|
||||
EXPECT_FALSE(caps.SupportsMultiDrawElementsBaseVertex);
|
||||
}
|
||||
|
||||
TEST(DrawIndirectCapabilities, RequiresEs31AndBothCoreEntryPoints) {
|
||||
ResetFakeDriver();
|
||||
g_fake.maxVertexSsboBlocks = 0;
|
||||
auto funcs = MakeFakeGLESFunctions();
|
||||
funcs.glDrawElementsIndirect = [](GLenum, GLenum, const void*) {};
|
||||
|
||||
MobileGL::MG_External::GLESCapabilities supportedCaps;
|
||||
ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(supportedCaps, funcs));
|
||||
EXPECT_TRUE(supportedCaps.SupportsDrawIndirect);
|
||||
|
||||
// The same pointers on an ES 3.0 context are not core entry points and cannot back the
|
||||
// desktop extension contract.
|
||||
ResetFakeDriver();
|
||||
g_fake.maxVertexSsboBlocks = 0;
|
||||
g_fake.glesMinorVersion = 0;
|
||||
MobileGL::MG_External::GLESCapabilities es30Caps;
|
||||
ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(es30Caps, funcs));
|
||||
EXPECT_FALSE(es30Caps.SupportsDrawIndirect);
|
||||
|
||||
ResetFakeDriver();
|
||||
g_fake.maxVertexSsboBlocks = 0;
|
||||
const auto missingElements = MakeFakeGLESFunctions();
|
||||
MobileGL::MG_External::GLESCapabilities missingEntryPointCaps;
|
||||
ASSERT_TRUE(
|
||||
MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(missingEntryPointCaps, missingElements));
|
||||
EXPECT_FALSE(missingEntryPointCaps.SupportsDrawIndirect);
|
||||
}
|
||||
|
||||
TEST(BaseInstanceCapabilities, RequiresTheExtensionAndAllThreeEntryPoints) {
|
||||
ResetFakeDriver();
|
||||
g_fake.maxVertexSsboBlocks = 0;
|
||||
auto funcs = MakeFakeGLESFunctions();
|
||||
funcs.glDrawArraysInstancedBaseInstanceEXT = [](GLenum, GLint, GLsizei, GLsizei, GLuint) {};
|
||||
funcs.glDrawElementsInstancedBaseInstanceEXT =
|
||||
[](GLenum, GLsizei, GLenum, const void*, GLsizei, GLuint) {};
|
||||
funcs.glDrawElementsInstancedBaseVertexBaseInstanceEXT =
|
||||
[](GLenum, GLsizei, GLenum, const void*, GLsizei, GLint, GLuint) {};
|
||||
|
||||
// Resolved stubs alone must never make the capability true.
|
||||
MobileGL::MG_External::GLESCapabilities pointersOnlyCaps;
|
||||
ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(pointersOnlyCaps, funcs));
|
||||
EXPECT_FALSE(pointersOnlyCaps.SupportsBaseInstance);
|
||||
|
||||
ResetFakeDriver();
|
||||
g_fake.maxVertexSsboBlocks = 0;
|
||||
g_fake.extensions.emplace_back("GL_EXT_base_instance");
|
||||
MobileGL::MG_External::GLESCapabilities supportedCaps;
|
||||
ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(supportedCaps, funcs));
|
||||
EXPECT_TRUE(supportedCaps.SupportsBaseInstance);
|
||||
|
||||
ResetFakeDriver();
|
||||
g_fake.maxVertexSsboBlocks = 0;
|
||||
g_fake.extensions.emplace_back("GL_EXT_base_instance");
|
||||
funcs.glDrawElementsInstancedBaseInstanceEXT = nullptr;
|
||||
MobileGL::MG_External::GLESCapabilities missingEntryPointCaps;
|
||||
ASSERT_TRUE(
|
||||
MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(missingEntryPointCaps, funcs));
|
||||
EXPECT_FALSE(missingEntryPointCaps.SupportsBaseInstance);
|
||||
}
|
||||
|
||||
@@ -516,17 +516,17 @@ TEST_F(ParallelShaderCompileTest, MaxShaderCompilerThreadsIgnoresTheCurrentBudge
|
||||
TEST_F(ParallelShaderCompileTest, BothBackendsAdvertiseTheExtensionIffAsyncIsEnabled) {
|
||||
{
|
||||
const AsyncModeScope async(true);
|
||||
EXPECT_TRUE(Advertises(MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false),
|
||||
EXPECT_TRUE(Advertises(MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false),
|
||||
E_GL_KHR_parallel_shader_compile));
|
||||
EXPECT_TRUE(Advertises(MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false),
|
||||
EXPECT_TRUE(Advertises(MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false),
|
||||
E_GL_KHR_parallel_shader_compile));
|
||||
}
|
||||
{
|
||||
const AsyncModeScope async(false);
|
||||
EXPECT_FALSE(Advertises(MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false),
|
||||
EXPECT_FALSE(Advertises(MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false),
|
||||
E_GL_KHR_parallel_shader_compile))
|
||||
<< "MOBILEGL_ASYNC_SHADER_COMPILE=0 must withdraw the extension, not only the threading";
|
||||
EXPECT_FALSE(Advertises(MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false),
|
||||
EXPECT_FALSE(Advertises(MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false),
|
||||
E_GL_KHR_parallel_shader_compile))
|
||||
<< "MOBILEGL_ASYNC_SHADER_COMPILE=0 must withdraw the extension, not only the threading";
|
||||
}
|
||||
|
||||
@@ -2692,11 +2692,13 @@ out vec4 fragColor;
|
||||
float fma
|
||||
(float a, float b, float c) { return a * b + c; }
|
||||
float sinh(float x, float y) { return x * y; }
|
||||
float length_squared(vec3 value) { return dot(value, value); }
|
||||
float round(float x) { return floor(x + 0.5); }
|
||||
float min3(float a, float b, float c) { return min(min(a, b), c); }
|
||||
|
||||
void main() {
|
||||
fragColor = vec4(fma(0.1, 0.2, 0.3), sinh(0.4, 2.0), round(1.25), min3(0.1, 0.2, 0.3));
|
||||
fragColor = vec4(fma(0.1, 0.2, 0.3), sinh(0.4, 2.0), round(1.25),
|
||||
min3(0.1, 0.2, 0.3) + length_squared(vec3(0.1, 0.2, 0.3)));
|
||||
}
|
||||
)";
|
||||
GLuint vs = CompileShaderChecked(GL_VERTEX_SHADER, vsSource);
|
||||
@@ -2707,6 +2709,7 @@ void main() {
|
||||
if (essl.find("fragColor") == String::npos) continue; // fragment module only
|
||||
EXPECT_NE(essl.find("mg_fma("), String::npos) << essl;
|
||||
EXPECT_NE(essl.find("mg_sinh("), String::npos) << essl;
|
||||
EXPECT_NE(essl.find("mg_length_squared("), String::npos) << essl;
|
||||
EXPECT_NE(essl.find("mg_round("), String::npos) << essl;
|
||||
EXPECT_NE(essl.find("mg_min3("), String::npos) << essl;
|
||||
EXPECT_EQ(essl.find("float fma("), String::npos) << essl;
|
||||
|
||||
@@ -52,20 +52,24 @@ TEST_F(ProgramUtilTest, RenameSamplerFunctionParameterInSpirvPass) {
|
||||
OpEntryPoint Fragment %main "main" %outColor
|
||||
OpExecutionMode %main OriginUpperLeft
|
||||
OpName %globalSampler "sampler"
|
||||
OpName %globalNew "new"
|
||||
OpName %paramSampler "sampler"
|
||||
OpName %paramNew "new"
|
||||
OpName %main "main"
|
||||
OpDecorate %outColor Location 0
|
||||
%void = OpTypeVoid
|
||||
%float = OpTypeFloat 32
|
||||
%v4float = OpTypeVector %float 4
|
||||
%mainFn = OpTypeFunction %void
|
||||
%paramFn = OpTypeFunction %void %float
|
||||
%paramFn = OpTypeFunction %void %float %float
|
||||
%outV4Ptr = OpTypePointer Output %v4float
|
||||
%privatePtr = OpTypePointer Private %float
|
||||
%outColor = OpVariable %outV4Ptr Output
|
||||
%globalSampler = OpVariable %privatePtr Private
|
||||
%globalNew = OpVariable %privatePtr Private
|
||||
%helper = OpFunction %void None %paramFn
|
||||
%paramSampler = OpFunctionParameter %float
|
||||
%paramNew = OpFunctionParameter %float
|
||||
%helperBody = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
@@ -91,6 +95,7 @@ TEST_F(ProgramUtilTest, RenameSamplerFunctionParameterInSpirvPass) {
|
||||
ASSERT_TRUE(tools.Disassemble(outputBinary, &outputText));
|
||||
|
||||
EXPECT_NE(outputText.find("\"MGL_COMPAT_sampler\""), String::npos);
|
||||
EXPECT_NE(outputText.find("\"MGL_COMPAT_new\""), String::npos);
|
||||
|
||||
SizeT exactSamplerNameCount = 0;
|
||||
SizeT searchOffset = 0;
|
||||
@@ -99,6 +104,14 @@ TEST_F(ProgramUtilTest, RenameSamplerFunctionParameterInSpirvPass) {
|
||||
searchOffset += std::strlen("\"sampler\"");
|
||||
}
|
||||
EXPECT_EQ(exactSamplerNameCount, 1u);
|
||||
|
||||
SizeT exactNewNameCount = 0;
|
||||
searchOffset = 0;
|
||||
while ((searchOffset = outputText.find("\"new\"", searchOffset)) != String::npos) {
|
||||
++exactNewNameCount;
|
||||
searchOffset += std::strlen("\"new\"");
|
||||
}
|
||||
EXPECT_EQ(exactNewNameCount, 1u);
|
||||
}
|
||||
|
||||
TEST_F(ProgramUtilTest, UnformattedFloatStorageImagesKeepIntegerAtomicImagesTyped) {
|
||||
|
||||
@@ -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>
|
||||
@@ -936,6 +937,45 @@ TEST(DirectVulkanSanity, ReadbackUsesTheSourceFormatTexelSize) {
|
||||
EXPECT_EQ(VulkanRenderer::GetReadbackTexelSize(VK_FORMAT_R32G32B32A32_SFLOAT), 16u);
|
||||
}
|
||||
|
||||
TEST(DirectVulkanSanity, DefaultFramebufferQuarterTurnReadbackMapsRectAndPixels) {
|
||||
using MobileGL::MG_Backend::DirectVulkan::VulkanRenderer;
|
||||
using MobileGL::Uint8;
|
||||
|
||||
VkOffset2D offset{};
|
||||
VkExtent2D copyExtent{};
|
||||
ASSERT_TRUE(VulkanRenderer::MapDefaultFramebufferReadbackRect(
|
||||
1, 0, 2, 1, VkExtent2D{2, 3}, VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR,
|
||||
&offset, ©Extent));
|
||||
EXPECT_EQ(offset.x, 0);
|
||||
EXPECT_EQ(offset.y, 1);
|
||||
EXPECT_EQ(copyExtent.width, 1u);
|
||||
EXPECT_EQ(copyExtent.height, 2u);
|
||||
|
||||
ASSERT_TRUE(VulkanRenderer::MapDefaultFramebufferReadbackRect(
|
||||
1, 0, 2, 1, VkExtent2D{2, 3}, VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR,
|
||||
&offset, ©Extent));
|
||||
EXPECT_EQ(offset.x, 1);
|
||||
EXPECT_EQ(offset.y, 0);
|
||||
EXPECT_EQ(copyExtent.width, 1u);
|
||||
EXPECT_EQ(copyExtent.height, 2u);
|
||||
|
||||
// Logical GL rows, bottom to top, are abc / def. The display-oriented swapchain blocks are
|
||||
// transposed in opposite directions for 90 and 270 degrees.
|
||||
const Uint8 raw90[] = {'a', 'd', 'b', 'e', 'c', 'f'};
|
||||
const Uint8 raw270[] = {'f', 'c', 'e', 'b', 'd', 'a'};
|
||||
const Uint8 expected[] = {'a', 'b', 'c', 'd', 'e', 'f'};
|
||||
Uint8 result[sizeof(expected)]{};
|
||||
|
||||
ASSERT_TRUE(VulkanRenderer::RemapDefaultFramebufferReadback(
|
||||
raw90, 3, 2, VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR, 1, result));
|
||||
EXPECT_TRUE(std::equal(std::begin(expected), std::end(expected), std::begin(result)));
|
||||
|
||||
std::fill(std::begin(result), std::end(result), 0);
|
||||
ASSERT_TRUE(VulkanRenderer::RemapDefaultFramebufferReadback(
|
||||
raw270, 3, 2, VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR, 1, result));
|
||||
EXPECT_TRUE(std::equal(std::begin(expected), std::end(expected), std::begin(result)));
|
||||
}
|
||||
|
||||
TEST(DirectVulkanSanity, ReadbackConvertsRgba8AndRgba16fPixels) {
|
||||
using MobileGL::MG_Backend::DirectVulkan::VulkanRenderer;
|
||||
using MobileGL::MG_Util::EncodeFloatToHalfBits;
|
||||
@@ -1932,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;
|
||||
@@ -1958,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));
|
||||
}
|
||||
@@ -1979,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;
|
||||
@@ -2078,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;
|
||||
|
||||
@@ -955,6 +955,8 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
(caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 2);
|
||||
const Bool esAtLeast31 = caps.GLESVersion.Major > 3 ||
|
||||
(caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 1);
|
||||
caps.SupportsDrawIndirect = esAtLeast31 && glesFuncs.glDrawArraysIndirect != nullptr &&
|
||||
glesFuncs.glDrawElementsIndirect != nullptr;
|
||||
caps.SupportsDrawElementsBaseVertex = (esAtLeast32 || hasDrawElementsBaseVertexExtension) &&
|
||||
glesFuncs.glDrawElementsBaseVertex != nullptr;
|
||||
caps.SupportsComputeShader = esAtLeast31 && glesFuncs.glDispatchCompute != nullptr &&
|
||||
@@ -976,6 +978,7 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
MGLOG_I(" indexed glColorMaski: %s", caps.SupportsIndexedColorMask ? "yes" : "no");
|
||||
MGLOG_I(" dual-source blend (EXT_blend_func_extended): %s",
|
||||
caps.SupportsDualSourceBlend ? "yes" : "no");
|
||||
MGLOG_I(" draw indirect (ES 3.1 core): %s", caps.SupportsDrawIndirect ? "yes" : "no");
|
||||
MGLOG_I(" multi-draw indirect (EXT_multi_draw_indirect): %s",
|
||||
caps.SupportsMultiDrawIndirect ? "yes" : "no");
|
||||
MGLOG_I(" multi-draw base vertex (EXT/OES_draw_elements_base_vertex + EXT_multi_draw_arrays): %s",
|
||||
|
||||
@@ -1149,6 +1149,10 @@ namespace MobileGL {
|
||||
// GLES 3.2 core or GL_OES_shader_multisample_interpolation exposes
|
||||
// interpolateAtOffset and the three fragment-offset limit queries.
|
||||
Bool SupportsShaderMultisampleInterpolation = false;
|
||||
// ES 3.1+ exposes glDrawArraysIndirect / glDrawElementsIndirect in core. Keep the
|
||||
// version and both entry-point checks together so extension advertisement and the
|
||||
// DirectGLES dispatch path cannot disagree on whether native indirect draws exist.
|
||||
Bool SupportsDrawIndirect = false;
|
||||
// GL_EXT_multi_draw_indirect is present AND glMultiDrawArraysIndirectEXT /
|
||||
// glMultiDrawElementsIndirectEXT both resolved. Multi-draw is not core in any ES
|
||||
// version, and eglGetProcAddress may return a live-looking stub on drivers without
|
||||
|
||||
@@ -1168,7 +1168,9 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
backendApiVersionString = MG_Backend::DirectGLES::FormatBackendAPIVersionString(
|
||||
summary.caps.GLESRendererString, summary.caps.GLESVersion.Major, summary.caps.GLESVersion.Minor);
|
||||
advertisedExtensions = JoinAdvertisedExtensions(MG_Backend::DirectGLES::BuildAdvertisedExtensions(
|
||||
summary.caps.SupportsDisjointTimerQuery, summary.caps.SupportsTextureFilterAnisotropy));
|
||||
summary.caps.SupportsDisjointTimerQuery, summary.caps.SupportsTextureFilterAnisotropy,
|
||||
summary.caps.SupportsDrawIndirect,
|
||||
summary.caps.SupportsDrawIndirect && summary.caps.SupportsBaseInstance));
|
||||
}
|
||||
AppendMobileGLReportedRows(builder, MG_Backend::DirectGLES::GetRendererIdentity(), backendApiVersionString,
|
||||
advertisedExtensions);
|
||||
@@ -1461,6 +1463,8 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
Bool shaderSubgroupUsable = false;
|
||||
Bool timerQueriesSupported = false;
|
||||
Bool samplerAnisotropySupported = false;
|
||||
Bool drawIndirectFirstInstanceSupported = false;
|
||||
Bool shaderDrawParametersSupported = false;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
@@ -1744,6 +1748,7 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
VkPhysicalDeviceFeatures features{};
|
||||
vkGetPhysicalDeviceFeaturesFn(physicalDevice, &features);
|
||||
summary.samplerAnisotropySupported = features.samplerAnisotropy == VK_TRUE;
|
||||
summary.drawIndirectFirstInstanceSupported = features.drawIndirectFirstInstance == VK_TRUE;
|
||||
if (features.multiDrawIndirect == VK_TRUE) {
|
||||
builder.Pass("multiDrawIndirect", "indirect multi-draw batches run as single native commands");
|
||||
} else {
|
||||
@@ -1910,6 +1915,7 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
builder.Warn("shaderDrawParameters",
|
||||
"unavailable; shaders using gl_DrawID/gl_BaseInstance will not work");
|
||||
}
|
||||
summary.shaderDrawParametersSupported = shaderDrawParameters;
|
||||
|
||||
Bool provokingVertexLast = false;
|
||||
Bool transformFeedbackPreservesProvokingVertex = false;
|
||||
@@ -2108,7 +2114,8 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
backendApiVersionString = MG_Backend::DirectVulkan::FormatBackendAPIVersionString(
|
||||
summary.deviceName, summary.apiVersionString, summary.driverVersionString);
|
||||
advertisedExtensions = JoinAdvertisedExtensions(MG_Backend::DirectVulkan::BuildAdvertisedExtensions(
|
||||
summary.shaderSubgroupUsable, summary.timerQueriesSupported, summary.samplerAnisotropySupported));
|
||||
summary.shaderSubgroupUsable, summary.timerQueriesSupported, summary.samplerAnisotropySupported,
|
||||
summary.drawIndirectFirstInstanceSupported && summary.shaderDrawParametersSupported));
|
||||
}
|
||||
AppendMobileGLReportedRows(builder, MG_Backend::DirectVulkan::GetRendererIdentity(), backendApiVersionString,
|
||||
advertisedExtensions);
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace MobileGL {
|
||||
"imageAtomicXor", "imageLoad", "imageSize", "imageStore", "imulExtended",
|
||||
"intBitsToFloat", "interpolateAtCentroid", "interpolateAtOffset",
|
||||
"interpolateAtSample", "inverse", "inversesqrt", "isinf", "isnan",
|
||||
"ldexp", "length", "lessThan", "lessThanEqual", "log", "log2",
|
||||
"ldexp", "length", "length_squared", "lessThan", "lessThanEqual", "log", "log2",
|
||||
"matrixCompMult", "max", "max3", "memoryBarrier",
|
||||
"memoryBarrierAtomicCounter", "memoryBarrierBuffer", "memoryBarrierImage",
|
||||
"memoryBarrierShared", "mid3", "min", "min3", "mix", "mod", "modf",
|
||||
|
||||
+15
-10
@@ -19,23 +19,27 @@ namespace MobileGL {
|
||||
namespace MG_Util {
|
||||
namespace ShaderTranspiler {
|
||||
namespace {
|
||||
constexpr const char* kConflictingName = "sampler";
|
||||
constexpr const char* kCompatName = "MGL_COMPAT_sampler";
|
||||
const char* GetCompatName(StringView name) {
|
||||
if (name == "sampler") return "MGL_COMPAT_sampler";
|
||||
if (name == "new") return "MGL_COMPAT_new";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Bool IsNamedSamplerFunctionParameter(spvtools::opt::IRContext* context,
|
||||
spvtools::opt::Instruction& nameInst) {
|
||||
const char* GetConflictingFunctionParameterCompatName(spvtools::opt::IRContext* context,
|
||||
spvtools::opt::Instruction& nameInst) {
|
||||
if (nameInst.opcode() != spv::Op::OpName || nameInst.NumInOperands() < 2) {
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (nameInst.GetInOperand(1).AsString() != kConflictingName) {
|
||||
return false;
|
||||
const char* compatName = GetCompatName(nameInst.GetInOperand(1).AsString());
|
||||
if (compatName == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto* defUseMgr = context->get_def_use_mgr();
|
||||
const Uint32 targetId = nameInst.GetSingleWordInOperand(0);
|
||||
const auto* target = defUseMgr->GetDef(targetId);
|
||||
return target != nullptr && target->opcode() == spv::Op::OpFunctionParameter;
|
||||
return target != nullptr && target->opcode() == spv::Op::OpFunctionParameter ? compatName : nullptr;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -44,12 +48,13 @@ namespace MobileGL {
|
||||
auto* irContext = context();
|
||||
|
||||
for (auto& debugInst : irContext->debugs2()) {
|
||||
if (!IsNamedSamplerFunctionParameter(irContext, debugInst)) {
|
||||
const char* compatName = GetConflictingFunctionParameterCompatName(irContext, debugInst);
|
||||
if (compatName == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
debugInst.SetInOperand(
|
||||
1, spvtools::utils::MakeVector<spvtools::opt::Operand::OperandData>(kCompatName));
|
||||
1, spvtools::utils::MakeVector<spvtools::opt::Operand::OperandData>(compatName));
|
||||
modified = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -11,6 +11,8 @@ The bundled fixtures cover:
|
||||

|
||||
- minecraft-1.21.4-main-menu: captured from Minecraft 1.21.4's main menu.
|
||||

|
||||
- minecraft-1.21.11-main-menu: captured from Minecraft 1.21.11's main menu on a Pixel 8 Pro through FCL MobileGL.
|
||||

|
||||
- minecraft-1.17-main-menu-854: captured from Minecraft 1.17's 854x480 main menu through FCL MobileGL capture.
|
||||

|
||||
- minecraft-1.21.4-in-world: captured from Minecraft 1.21.4 after entering a singleplayer world.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -40,6 +40,13 @@
|
||||
"target_call": 481787,
|
||||
"timeout_seconds": 180
|
||||
},
|
||||
{
|
||||
"name": "minecraft-1.21.11-main-menu",
|
||||
"trace_archive": "minecraft-1.21.11-main-menu.tgz",
|
||||
"golden": "minecraft-1.21.11-main-menu.0000205347.png",
|
||||
"target_call": 205347,
|
||||
"timeout_seconds": 180
|
||||
},
|
||||
{
|
||||
"name": "minecraft-1.17-main-menu-854",
|
||||
"trace_archive": "minecraft-1.17-main-menu-854.tgz",
|
||||
|
||||
Reference in New Issue
Block a user