mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-13 06:38:31 +09:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3794f4e6a | ||
|
|
14d3901d30 | ||
|
|
d4766513e4 | ||
|
|
72dc7aa6aa |
+3
-5
@@ -66,14 +66,12 @@ namespace MobileGL::MG_Config {
|
|||||||
// - DISPLAY: X11 session variable, not MobileGL configuration.
|
// - DISPLAY: X11 session variable, not MobileGL configuration.
|
||||||
// - MOBILEGL_LOG_FILE_PATH: log-file init runs before MG_ConfigLoader::Init
|
// - MOBILEGL_LOG_FILE_PATH: log-file init runs before MG_ConfigLoader::Init
|
||||||
// (see MG_Util/Debug/Log.cpp).
|
// (see MG_Util/Debug/Log.cpp).
|
||||||
// - MOBILEGL_VALIDATE_SPIRV: test suites like SpirvPassTest exercise
|
|
||||||
// ShaderCompiler without ever running MobileGL::Initialize(), and every
|
|
||||||
// Initialize() re-runs MG_ConfigLoader::Init, which would clobber a
|
|
||||||
// programmatic override stored here (see ShaderCompiler.cpp,
|
|
||||||
// SpirvValidationEnabled).
|
|
||||||
struct FeaturesTable {
|
struct FeaturesTable {
|
||||||
// MOBILEGL_DISABLE_TIMERQUERY: do not advertise or use GPU timer queries.
|
// MOBILEGL_DISABLE_TIMERQUERY: do not advertise or use GPU timer queries.
|
||||||
Bool DisableTimerQuery = false;
|
Bool DisableTimerQuery = false;
|
||||||
|
// MOBILEGL_ENABLE_SPIRV_VALIDATION: validate generated and transformed SPIR-V.
|
||||||
|
// Disabled by default because validation is a diagnostics-only cost.
|
||||||
|
Bool EnableSpirvValidation = false;
|
||||||
// MOBILEGL_USE_ANGLE: load ANGLE EGL/GLES libraries.
|
// MOBILEGL_USE_ANGLE: load ANGLE EGL/GLES libraries.
|
||||||
Bool UseAngle = false;
|
Bool UseAngle = false;
|
||||||
#if defined(MOBILEGL_TRACE_ANGLE_VARIANTS)
|
#if defined(MOBILEGL_TRACE_ANGLE_VARIANTS)
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ namespace MobileGL::MG_ConfigLoader {
|
|||||||
inline void InitFeatures() {
|
inline void InitFeatures() {
|
||||||
auto& features = MG_Config::Features;
|
auto& features = MG_Config::Features;
|
||||||
features.DisableTimerQuery = QueryEnvFlag("MOBILEGL_DISABLE_TIMERQUERY");
|
features.DisableTimerQuery = QueryEnvFlag("MOBILEGL_DISABLE_TIMERQUERY");
|
||||||
|
features.EnableSpirvValidation = QueryEnvFlag("MOBILEGL_ENABLE_SPIRV_VALIDATION");
|
||||||
features.UseAngle = QueryEnvFlag("MOBILEGL_USE_ANGLE");
|
features.UseAngle = QueryEnvFlag("MOBILEGL_USE_ANGLE");
|
||||||
#if defined(MOBILEGL_TRACE_ANGLE_VARIANTS)
|
#if defined(MOBILEGL_TRACE_ANGLE_VARIANTS)
|
||||||
QueryEnvVariable("MOBILEGL_TRACE_ANGLE_VARIANT", features.TraceAngleVariant, "");
|
QueryEnvVariable("MOBILEGL_TRACE_ANGLE_VARIANT", features.TraceAngleVariant, "");
|
||||||
|
|||||||
+5
-7
@@ -14,7 +14,6 @@
|
|||||||
#include <MG_State/EGLState/Core.h>
|
#include <MG_State/EGLState/Core.h>
|
||||||
#include <MG_Impl/GLImpl/Texture/ProxyTexture.h>
|
#include <MG_Impl/GLImpl/Texture/ProxyTexture.h>
|
||||||
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
|
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
|
||||||
#include <MG_Impl/GLImpl/Query/GL_Query.h>
|
|
||||||
#include <MG_Impl/GLImpl/Sync/GL_Sync.h>
|
#include <MG_Impl/GLImpl/Sync/GL_Sync.h>
|
||||||
#include <MG_Util/Async/ShaderCompilePool.h>
|
#include <MG_Util/Async/ShaderCompilePool.h>
|
||||||
#include <MG_Util/ShaderTranspiler/ShaderCompiler.h>
|
#include <MG_Util/ShaderTranspiler/ShaderCompiler.h>
|
||||||
@@ -46,13 +45,12 @@ namespace MobileGL {
|
|||||||
// both of which this function is about to destroy. This is the one
|
// both of which this function is about to destroy. This is the one
|
||||||
// cancellation path in the whole design that waits.
|
// cancellation path in the whole design that waits.
|
||||||
MG_Util::Async::ShaderCompilePool::Get().StopAndDrain();
|
MG_Util::Async::ShaderCompilePool::Get().StopAndDrain();
|
||||||
// GL syncs and queries die with their contexts, and every context is gone
|
// GL syncs die with their contexts, and every context is gone by the
|
||||||
// by the time full teardown runs: drain both live registries while the
|
// time full teardown runs: drain the live-sync registry while the
|
||||||
// backend function table can still release the backend handles (and before
|
// backend function table can still release the backend handles (and
|
||||||
// a re-initialized library could pair them with the wrong backend's
|
// before a re-initialized library could pair them with the wrong
|
||||||
// DeleteSync / DeleteBackendQuery).
|
// backend's DeleteSync).
|
||||||
MG_Impl::GLImpl::DestroyAllSyncObjects();
|
MG_Impl::GLImpl::DestroyAllSyncObjects();
|
||||||
MG_Impl::GLImpl::DestroyAllQueryObjects();
|
|
||||||
MG_Backend::pActiveBackendObject.reset();
|
MG_Backend::pActiveBackendObject.reset();
|
||||||
MG_State::pGLContext.reset();
|
MG_State::pGLContext.reset();
|
||||||
MG_State::pEGLContext.reset();
|
MG_State::pEGLContext.reset();
|
||||||
|
|||||||
@@ -932,7 +932,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
Vector<GLExtension> extensions = {
|
Vector<GLExtension> extensions = {
|
||||||
V_OpenGL30, V_OpenGL31, V_OpenGL32, V_OpenGL33, V_OpenGL40, E_GL_ARB_draw_buffers_blend,
|
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_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_EXT_framebuffer_object,
|
E_GL_ARB_clear_buffer_object, E_GL_ARB_program_interface_query, E_GL_ARB_framebuffer_object, E_GL_EXT_framebuffer_object,
|
||||||
E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage, E_GL_ARB_texture_storage,
|
E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage, E_GL_ARB_texture_storage,
|
||||||
E_GL_ARB_texture_storage_multisample, E_GL_ARB_clear_texture, E_GL_ARB_direct_state_access,
|
E_GL_ARB_texture_storage_multisample, E_GL_ARB_clear_texture, E_GL_ARB_direct_state_access,
|
||||||
E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters, E_GL_ARB_shader_draw_parameters,
|
E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters, E_GL_ARB_shader_draw_parameters,
|
||||||
|
|||||||
@@ -2205,7 +2205,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
// accident - and it never covered the monolithic glUseProgram path at all - so the
|
// accident - and it never covered the monolithic glUseProgram path at all - so the
|
||||||
// dependency is stated here instead.
|
// dependency is stated here instead.
|
||||||
if (!twin->GetBackendProgramId() ||
|
if (!twin->GetBackendProgramId() ||
|
||||||
twin->GetContextGeneration() != g_backendContextGeneration ||
|
|
||||||
twin->GetSyncedLinkVersion() != currentProgram->GetLinkVersion() ||
|
twin->GetSyncedLinkVersion() != currentProgram->GetLinkVersion() ||
|
||||||
twin->GetSyncedImageUnitVersion() != currentProgram->GetImageUnitVersion() ||
|
twin->GetSyncedImageUnitVersion() != currentProgram->GetImageUnitVersion() ||
|
||||||
twin->GetSnormFallbackClampOutputMask() != g_snormFallbackClampOutputMask ||
|
twin->GetSnormFallbackClampOutputMask() != g_snormFallbackClampOutputMask ||
|
||||||
@@ -3055,7 +3054,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw();
|
const auto& currentProgram = MG_State::pGLContext->GetProgramForDraw();
|
||||||
const auto program = GetCurrentBackendProgram();
|
const auto program = GetCurrentBackendProgram();
|
||||||
if (!currentProgram || program == nullptr ||
|
if (!currentProgram || program == nullptr ||
|
||||||
program->GetContextGeneration() != g_backendContextGeneration ||
|
|
||||||
program->GetSyncedLinkVersion() != currentProgram->GetLinkVersion()) {
|
program->GetSyncedLinkVersion() != currentProgram->GetLinkVersion()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -5901,7 +5899,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
// fallen behind is about to be rebuilt anyway, and its current driver interface is
|
// 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.
|
// the PREVIOUS link's - applying to it could land the binding on an unrelated block.
|
||||||
if (!backendObj->GetBackendProgramId() ||
|
if (!backendObj->GetBackendProgramId() ||
|
||||||
backendObj->GetContextGeneration() != g_backendContextGeneration ||
|
|
||||||
backendObj->GetSyncedLinkVersion() != programObject->GetLinkVersion()) {
|
backendObj->GetSyncedLinkVersion() != programObject->GetLinkVersion()) {
|
||||||
return; // SyncToBackend's reseed will carry it
|
return; // SyncToBackend's reseed will carry it
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -729,8 +729,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
void Ops_ReadbackFromGpu(BufferObject& bufferObject) {
|
void Ops_ReadbackFromGpu(BufferObject& bufferObject) {
|
||||||
auto* resource = ResourceOf(bufferObject);
|
auto* resource = ResourceOf(bufferObject);
|
||||||
if (!resource || resource->id == 0 || !resource->storageInitialized) return;
|
if (!resource || resource->id == 0 || !resource->storageInitialized) return;
|
||||||
if (resource->persistentMapped) return; // shadow already IS the GPU storage
|
|
||||||
if (!CanTouchGLNow() || resource->contextGeneration != g_bufferContextGeneration) return;
|
if (!CanTouchGLNow() || resource->contextGeneration != g_bufferContextGeneration) return;
|
||||||
|
if (resource->persistentMapped) {
|
||||||
|
// Host writes to a persistent map must not race shader writes already queued
|
||||||
|
// on this context. There is no backend copy to read back in this case.
|
||||||
|
if (g_GLESFuncs.glFinish) g_GLESFuncs.glFinish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!g_GLESFuncs.glMapBufferRange || !g_GLESFuncs.glUnmapBuffer) return;
|
if (!g_GLESFuncs.glMapBufferRange || !g_GLESFuncs.glUnmapBuffer) return;
|
||||||
const SizeT size = std::min<SizeT>(bufferObject.GetSize(), resource->storageSize);
|
const SizeT size = std::min<SizeT>(bufferObject.GetSize(), resource->storageSize);
|
||||||
if (size == 0) return;
|
if (size == 0) return;
|
||||||
@@ -1468,7 +1473,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
#endif
|
#endif
|
||||||
m_clientAttributeBufferIds.fill(0);
|
m_clientAttributeBufferIds.fill(0);
|
||||||
m_contextGeneration = g_backendContextGeneration;
|
|
||||||
g_GLESFuncs.glGenVertexArrays(1, &m_backendVAOId);
|
g_GLESFuncs.glGenVertexArrays(1, &m_backendVAOId);
|
||||||
if (m_backendVAOId == 0) {
|
if (m_backendVAOId == 0) {
|
||||||
MGLOG_E_ONCE("Failed to generate vertex array object.");
|
MGLOG_E_ONCE("Failed to generate vertex array object.");
|
||||||
@@ -1482,30 +1486,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
if (InProcessTeardown()) {
|
if (InProcessTeardown()) {
|
||||||
return; // see InProcessTeardown(): the driver may be unloaded already
|
return; // see InProcessTeardown(): the driver may be unloaded already
|
||||||
}
|
}
|
||||||
const Bool contextCurrent = m_contextGeneration == g_backendContextGeneration;
|
|
||||||
if (m_backendVAOId != 0) {
|
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);
|
NoteVAOIdDeleted(m_backendVAOId);
|
||||||
if (contextCurrent && g_GLESFuncs.glDeleteVertexArrays) {
|
|
||||||
g_GLESFuncs.glDeleteVertexArrays(1, &m_backendVAOId);
|
g_GLESFuncs.glDeleteVertexArrays(1, &m_backendVAOId);
|
||||||
}
|
|
||||||
m_backendVAOId = 0;
|
m_backendVAOId = 0;
|
||||||
}
|
}
|
||||||
for (auto& bufferId : m_clientAttributeBufferIds) {
|
for (auto& bufferId : m_clientAttributeBufferIds) {
|
||||||
if (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);
|
BufferImpl::NoteBufferIdDeleted(bufferId);
|
||||||
if (contextCurrent && g_GLESFuncs.glDeleteBuffers) {
|
|
||||||
g_GLESFuncs.glDeleteBuffers(1, &bufferId);
|
g_GLESFuncs.glDeleteBuffers(1, &bufferId);
|
||||||
}
|
|
||||||
bufferId = 0;
|
bufferId = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
Uint g_boundBackendVAOId = 0;
|
Uint g_boundBackendVAOId = 0;
|
||||||
@@ -1647,30 +1640,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
// PrepareForDraw's BindCurrentVAO establishes the draw binding regardless.
|
// PrepareForDraw's BindCurrentVAO establishes the draw binding regardless.
|
||||||
const Uint32 currentConfigVersion = stateVAOObject->GetConfigVersion();
|
const Uint32 currentConfigVersion = stateVAOObject->GetConfigVersion();
|
||||||
const Uint16 currentIndexBufferVersion = stateVAOObject->GetIndexBufferBindingSlot().GetVersion();
|
const Uint16 currentIndexBufferVersion = stateVAOObject->GetIndexBufferBindingSlot().GetVersion();
|
||||||
|
|
||||||
// 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 attributesDirty = !m_hasSyncedConfigVersion || m_syncedConfigVersion != currentConfigVersion;
|
||||||
const Bool indexBufferDirty = currentIndexBufferVersion != m_syncedIndexBufferVersion;
|
const Bool indexBufferDirty = currentIndexBufferVersion != m_syncedIndexBufferVersion;
|
||||||
|
|
||||||
@@ -2014,7 +1983,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
TextureSwizzleParam::Alpha};
|
TextureSwizzleParam::Alpha};
|
||||||
m_cacheDepthStencilTextureMode = GL_DEPTH_COMPONENT;
|
m_cacheDepthStencilTextureMode = GL_DEPTH_COMPONENT;
|
||||||
m_forceTextureParamsResync = true;
|
m_forceTextureParamsResync = true;
|
||||||
m_forceSamplerResync = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the backend GL unpack state to MobileGL's upload default for the scope,
|
// Sets the backend GL unpack state to MobileGL's upload default for the scope,
|
||||||
@@ -2432,13 +2400,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return;
|
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
|
#ifdef TRACY_ENABLE
|
||||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
#endif
|
#endif
|
||||||
@@ -3163,19 +3124,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_contextGeneration != g_backendContextGeneration) {
|
|
||||||
RecreateBackendTexture();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* samplerObject = stateTextureObject->GetSamplerObject().get();
|
auto* samplerObject = stateTextureObject->GetSamplerObject().get();
|
||||||
Uint currentSamplerVersion = samplerObject->GetVersion();
|
Uint currentSamplerVersion = samplerObject->GetVersion();
|
||||||
if (m_syncedSamplerVersion == currentSamplerVersion && !m_forceSamplerResync) {
|
if (m_syncedSamplerVersion == currentSamplerVersion) {
|
||||||
MGLOG_D("Sampler parameters have not changed for texture ID: %u, skipping sync.", m_backendTextureId);
|
MGLOG_D("Sampler parameters have not changed for texture ID: %u, skipping sync.", m_backendTextureId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_syncedSamplerVersion = currentSamplerVersion;
|
m_syncedSamplerVersion = currentSamplerVersion;
|
||||||
m_forceSamplerResync = false;
|
|
||||||
|
|
||||||
MGLOG_D("Syncing texture built-in sampler with backend ID %u to backend for state ID %u",
|
MGLOG_D("Syncing texture built-in sampler with backend ID %u to backend for state ID %u",
|
||||||
m_backendTextureId, stateTextureObject->GetExternalIndex());
|
m_backendTextureId, stateTextureObject->GetExternalIndex());
|
||||||
@@ -3278,10 +3234,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_contextGeneration != g_backendContextGeneration) {
|
|
||||||
RecreateBackendTexture();
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint16 currentTextureParamsVersion = stateTextureObject->GetTextureParamsVersion();
|
Uint16 currentTextureParamsVersion = stateTextureObject->GetTextureParamsVersion();
|
||||||
if (m_syncedTextureParamsVersion == currentTextureParamsVersion && !m_forceTextureParamsResync) {
|
if (m_syncedTextureParamsVersion == currentTextureParamsVersion && !m_forceTextureParamsResync) {
|
||||||
MGLOG_D("Texture parameters have not changed for texture ID: %u, skipping sync.", m_backendTextureId);
|
MGLOG_D("Texture parameters have not changed for texture ID: %u, skipping sync.", m_backendTextureId);
|
||||||
@@ -3955,19 +3907,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
MGLOG_E_ONCE("State FBO object is null, cannot sync to backend.");
|
MGLOG_E_ONCE("State FBO object is null, cannot sync to backend.");
|
||||||
return;
|
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,
|
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"));
|
stateFBOObject->GetExternalIndex(), (asTarget == FramebufferTarget::Draw ? "DRAW" : "READ"));
|
||||||
GLenum glFBOTarget = MG_Util::ConvertFramebufferTargetToGLEnum(asTarget);
|
GLenum glFBOTarget = MG_Util::ConvertFramebufferTargetToGLEnum(asTarget);
|
||||||
@@ -4478,27 +4417,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
Uint g_lastUsedBackendProgramId = 0;
|
Uint g_lastUsedBackendProgramId = 0;
|
||||||
StateBackendObjectRegistry<MG_State::GLState::ProgramObject, BackendProgramObjectImpl> g_backendProgramObjects;
|
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() {
|
BackendProgramObjectImpl::BackendProgramObjectImpl() {
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
#endif
|
#endif
|
||||||
m_contextGeneration = g_backendContextGeneration;
|
|
||||||
m_backendProgramId = g_GLESFuncs.glCreateProgram();
|
m_backendProgramId = g_GLESFuncs.glCreateProgram();
|
||||||
if (m_backendProgramId == 0) {
|
if (m_backendProgramId == 0) {
|
||||||
MGLOG_E_ONCE("Failed to create program object in backend.");
|
MGLOG_E_ONCE("Failed to create program object in backend.");
|
||||||
@@ -4516,23 +4438,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
if (InProcessTeardown()) {
|
if (InProcessTeardown()) {
|
||||||
return; // see InProcessTeardown(): the driver may be unloaded already
|
return; // see InProcessTeardown(): the driver may be unloaded already
|
||||||
}
|
}
|
||||||
DeleteBackendProgramGlobalUbo(m_backendGlobalUBOId, m_contextGeneration);
|
|
||||||
if (m_backendProgramId != 0) {
|
if (m_backendProgramId != 0) {
|
||||||
// Same generation rule as the global UBO: a program id from a dead
|
|
||||||
// context is gone already and must not be deleted as a recycled name
|
|
||||||
// in a successor context.
|
|
||||||
if (m_contextGeneration == g_backendContextGeneration) {
|
|
||||||
MGLOG_D("Deleting backend program object with ID: %u", m_backendProgramId);
|
MGLOG_D("Deleting backend program object with ID: %u", m_backendProgramId);
|
||||||
if (g_GLESFuncs.glDeleteProgram) {
|
|
||||||
g_GLESFuncs.glDeleteProgram(m_backendProgramId);
|
g_GLESFuncs.glDeleteProgram(m_backendProgramId);
|
||||||
}
|
|
||||||
}
|
|
||||||
// The driver may recycle this GL name for a future program; a stale
|
// The driver may recycle this GL name for a future program; a stale
|
||||||
// guard entry would then wrongly skip the glUseProgram for it.
|
// guard entry would then wrongly skip the glUseProgram for it.
|
||||||
if (g_lastUsedBackendProgramId == m_backendProgramId) {
|
if (g_lastUsedBackendProgramId == m_backendProgramId) {
|
||||||
g_lastUsedBackendProgramId = 0;
|
g_lastUsedBackendProgramId = 0;
|
||||||
}
|
}
|
||||||
m_backendProgramId = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4769,31 +4682,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return;
|
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",
|
MGLOG_D("Syncing program to backend. State program ID: %u, Backend ID: %u",
|
||||||
stateProgramObject->GetExternalIndex(), m_backendProgramId);
|
stateProgramObject->GetExternalIndex(), m_backendProgramId);
|
||||||
// Every link-derived cache below (incl. m_samplerUniformBindings and its
|
// Every link-derived cache below (incl. m_samplerUniformBindings and its
|
||||||
@@ -4858,6 +4746,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
MGLOG_D("%s:", src.empty() ? "" : src.c_str());
|
MGLOG_D("%s:", src.empty() ? "" : src.c_str());
|
||||||
}
|
}
|
||||||
auto& shaderSpirvs = stateProgramObject->GetGeneratedSpirv();
|
auto& shaderSpirvs = stateProgramObject->GetGeneratedSpirv();
|
||||||
|
const Bool enableSpirvValidation = stateProgramObject->GetSpirvValidationEnabled();
|
||||||
|
|
||||||
// Blocks a transform-feedback capture request names a member of ("StageData" of
|
// Blocks a transform-feedback capture request names a member of ("StageData" of
|
||||||
// "StageData.attrib[0]"). The Adreno ES driver accepts such a request, links, and
|
// "StageData.attrib[0]"). The Adreno ES driver accepts such a request, links, and
|
||||||
@@ -4911,7 +4800,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
Vector<unsigned int> loweredSpirv;
|
Vector<unsigned int> loweredSpirv;
|
||||||
const Vector<unsigned int>* effectiveSpirv = &spirvCode;
|
const Vector<unsigned int>* effectiveSpirv = &spirvCode;
|
||||||
if (glShaderType == GL_VERTEX_SHADER &&
|
if (glShaderType == GL_VERTEX_SHADER &&
|
||||||
MG_Util::ShaderTranspiler::ShaderCompiler::LowerDrawParametersForEssl(spirvCode, loweredSpirv) &&
|
MG_Util::ShaderTranspiler::ShaderCompiler::LowerDrawParametersForEssl(spirvCode, loweredSpirv, enableSpirvValidation) &&
|
||||||
!loweredSpirv.empty()) {
|
!loweredSpirv.empty()) {
|
||||||
effectiveSpirv = &loweredSpirv;
|
effectiveSpirv = &loweredSpirv;
|
||||||
}
|
}
|
||||||
@@ -4921,7 +4810,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
Vector<unsigned int> splitArrayInputSpirv;
|
Vector<unsigned int> splitArrayInputSpirv;
|
||||||
if (glShaderType == GL_VERTEX_SHADER &&
|
if (glShaderType == GL_VERTEX_SHADER &&
|
||||||
MG_Util::ShaderTranspiler::ShaderCompiler::SplitArrayVertexInputsForEssl(
|
MG_Util::ShaderTranspiler::ShaderCompiler::SplitArrayVertexInputsForEssl(
|
||||||
*effectiveSpirv, splitArrayInputSpirv) &&
|
*effectiveSpirv, splitArrayInputSpirv, enableSpirvValidation) &&
|
||||||
!splitArrayInputSpirv.empty() && splitArrayInputSpirv != *effectiveSpirv) {
|
!splitArrayInputSpirv.empty() && splitArrayInputSpirv != *effectiveSpirv) {
|
||||||
// Only when the pass ACTUALLY split something. The optimizer hands back a
|
// Only when the pass ACTUALLY split something. The optimizer hands back a
|
||||||
// re-serialised copy either way, and adopting that copy for every vertex
|
// re-serialised copy either way, and adopting that copy for every vertex
|
||||||
@@ -4943,7 +4832,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
if (!xfbCaptureBlockNames.empty() &&
|
if (!xfbCaptureBlockNames.empty() &&
|
||||||
MG_Util::ShaderTranspiler::ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(
|
MG_Util::ShaderTranspiler::ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(
|
||||||
*effectiveSpirv, xfbCaptureBlockNames, stageFlattenedXfbBlockNames,
|
*effectiveSpirv, xfbCaptureBlockNames, stageFlattenedXfbBlockNames,
|
||||||
flattenedXfbSpirv) &&
|
flattenedXfbSpirv, enableSpirvValidation) &&
|
||||||
!flattenedXfbSpirv.empty() && !stageFlattenedXfbBlockNames.empty()) {
|
!flattenedXfbSpirv.empty() && !stageFlattenedXfbBlockNames.empty()) {
|
||||||
effectiveSpirv = &flattenedXfbSpirv;
|
effectiveSpirv = &flattenedXfbSpirv;
|
||||||
flattenedXfbBlockNames.insert(stageFlattenedXfbBlockNames.begin(),
|
flattenedXfbBlockNames.insert(stageFlattenedXfbBlockNames.begin(),
|
||||||
@@ -4959,7 +4848,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
// declare the member highp; nothing else about emission changes.
|
// declare the member highp; nothing else about emission changes.
|
||||||
Vector<unsigned int> uboPrecisionSpirv;
|
Vector<unsigned int> uboPrecisionSpirv;
|
||||||
if (MG_Util::ShaderTranspiler::ShaderCompiler::StripUboMemberRelaxedPrecisionForEssl(
|
if (MG_Util::ShaderTranspiler::ShaderCompiler::StripUboMemberRelaxedPrecisionForEssl(
|
||||||
*effectiveSpirv, uboPrecisionSpirv) &&
|
*effectiveSpirv, uboPrecisionSpirv, enableSpirvValidation) &&
|
||||||
!uboPrecisionSpirv.empty()) {
|
!uboPrecisionSpirv.empty()) {
|
||||||
effectiveSpirv = &uboPrecisionSpirv;
|
effectiveSpirv = &uboPrecisionSpirv;
|
||||||
}
|
}
|
||||||
@@ -4974,7 +4863,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
Vector<unsigned int> noperspectiveSpirv;
|
Vector<unsigned int> noperspectiveSpirv;
|
||||||
if (!g_GLESCapabilities.SupportsNoperspectiveInterpolation &&
|
if (!g_GLESCapabilities.SupportsNoperspectiveInterpolation &&
|
||||||
MG_Util::ShaderTranspiler::ShaderCompiler::EmulateNoPerspectiveForEssl(
|
MG_Util::ShaderTranspiler::ShaderCompiler::EmulateNoPerspectiveForEssl(
|
||||||
*effectiveSpirv, noperspectiveSpirv) &&
|
*effectiveSpirv, noperspectiveSpirv, enableSpirvValidation) &&
|
||||||
!noperspectiveSpirv.empty()) {
|
!noperspectiveSpirv.empty()) {
|
||||||
effectiveSpirv = &noperspectiveSpirv;
|
effectiveSpirv = &noperspectiveSpirv;
|
||||||
}
|
}
|
||||||
@@ -4984,7 +4873,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
// divides the coordinate of every normalized-coordinate lookup by the texture
|
// divides the coordinate of every normalized-coordinate lookup by the texture
|
||||||
// size, which is the whole of the difference between the two.
|
// size, which is the whole of the difference between the two.
|
||||||
Vector<unsigned int> rectLoweredSpirv;
|
Vector<unsigned int> rectLoweredSpirv;
|
||||||
if (MG_Util::ShaderTranspiler::ShaderCompiler::LowerRectImages(*effectiveSpirv, rectLoweredSpirv) &&
|
if (MG_Util::ShaderTranspiler::ShaderCompiler::LowerRectImages(*effectiveSpirv, rectLoweredSpirv, enableSpirvValidation) &&
|
||||||
!rectLoweredSpirv.empty()) {
|
!rectLoweredSpirv.empty()) {
|
||||||
effectiveSpirv = &rectLoweredSpirv;
|
effectiveSpirv = &rectLoweredSpirv;
|
||||||
}
|
}
|
||||||
@@ -4998,7 +4887,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
// coordinate to (u, 0, layer) - before SPIRV-Cross can apply its own.
|
// coordinate to (u, 0, layer) - before SPIRV-Cross can apply its own.
|
||||||
Vector<unsigned int> arrayImageSpirv;
|
Vector<unsigned int> arrayImageSpirv;
|
||||||
if (MG_Util::ShaderTranspiler::ShaderCompiler::Lower1DArrayImagesForEssl(*effectiveSpirv,
|
if (MG_Util::ShaderTranspiler::ShaderCompiler::Lower1DArrayImagesForEssl(*effectiveSpirv,
|
||||||
arrayImageSpirv) &&
|
arrayImageSpirv, enableSpirvValidation) &&
|
||||||
!arrayImageSpirv.empty()) {
|
!arrayImageSpirv.empty()) {
|
||||||
effectiveSpirv = &arrayImageSpirv;
|
effectiveSpirv = &arrayImageSpirv;
|
||||||
}
|
}
|
||||||
@@ -5017,7 +4906,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
if (!imageFormatBake.glFormatByUniformName.empty() &&
|
if (!imageFormatBake.glFormatByUniformName.empty() &&
|
||||||
MG_Util::ShaderTranspiler::ShaderCompiler::DeclaresFormatlessStorageImage(*effectiveSpirv) &&
|
MG_Util::ShaderTranspiler::ShaderCompiler::DeclaresFormatlessStorageImage(*effectiveSpirv) &&
|
||||||
MG_Util::ShaderTranspiler::ShaderCompiler::BakeImageFormatsForEssl(
|
MG_Util::ShaderTranspiler::ShaderCompiler::BakeImageFormatsForEssl(
|
||||||
*effectiveSpirv, imageFormatBake.glFormatByUniformName, imageFormatSpirv) &&
|
*effectiveSpirv, imageFormatBake.glFormatByUniformName, imageFormatSpirv,
|
||||||
|
enableSpirvValidation) &&
|
||||||
!imageFormatSpirv.empty()) {
|
!imageFormatSpirv.empty()) {
|
||||||
effectiveSpirv = &imageFormatSpirv;
|
effectiveSpirv = &imageFormatSpirv;
|
||||||
}
|
}
|
||||||
@@ -5033,7 +4923,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
Vector<unsigned int> outputIndexSpirv;
|
Vector<unsigned int> outputIndexSpirv;
|
||||||
if (glShaderType == GL_FRAGMENT_SHADER &&
|
if (glShaderType == GL_FRAGMENT_SHADER &&
|
||||||
MG_Util::ShaderTranspiler::ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(
|
MG_Util::ShaderTranspiler::ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(
|
||||||
*effectiveSpirv, outputIndexSpirv) &&
|
*effectiveSpirv, outputIndexSpirv, enableSpirvValidation) &&
|
||||||
!outputIndexSpirv.empty()) {
|
!outputIndexSpirv.empty()) {
|
||||||
effectiveSpirv = &outputIndexSpirv;
|
effectiveSpirv = &outputIndexSpirv;
|
||||||
}
|
}
|
||||||
@@ -5271,9 +5161,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create global UBO. Delete any previous one first: relink reuses this
|
// Create global UBO
|
||||||
// backend program, and without this every relink leaked the old buffer.
|
|
||||||
DeleteBackendProgramGlobalUbo(m_backendGlobalUBOId, m_contextGeneration);
|
|
||||||
if (stateProgramObject->GetUBOSize() > 0) {
|
if (stateProgramObject->GetUBOSize() > 0) {
|
||||||
g_GLESFuncs.glGenBuffers(1, &m_backendGlobalUBOId);
|
g_GLESFuncs.glGenBuffers(1, &m_backendGlobalUBOId);
|
||||||
g_GLESFuncs.glBindBuffer(GL_UNIFORM_BUFFER, m_backendGlobalUBOId);
|
g_GLESFuncs.glBindBuffer(GL_UNIFORM_BUFFER, m_backendGlobalUBOId);
|
||||||
@@ -5508,20 +5396,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return;
|
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();
|
Uint currentSamplerVersion = stateSamplerObject->GetVersion();
|
||||||
if (m_isInitialized && m_syncedSamplerVersion == currentSamplerVersion) {
|
if (m_isInitialized && m_syncedSamplerVersion == currentSamplerVersion) {
|
||||||
MGLOG_D("Sampler parameters have not changed for sampler ID: %u, skipping sync.",
|
MGLOG_D("Sampler parameters have not changed for sampler ID: %u, skipping sync.",
|
||||||
@@ -5666,23 +5540,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return;
|
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,
|
MGLOG_D("Syncing RBO with backend ID %u to backend for state ID %u", m_backendRBOId,
|
||||||
stateRBOObject->GetExternalIndex());
|
stateRBOObject->GetExternalIndex());
|
||||||
|
|
||||||
|
|||||||
@@ -406,7 +406,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
void SyncClientSideAttributesForDrawArrays(
|
void SyncClientSideAttributesForDrawArrays(
|
||||||
const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject, GLint first, GLsizei count);
|
const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject, GLint first, GLsizei count);
|
||||||
Uint GetBackendVertexArrayId() const { return m_backendVAOId; }
|
Uint GetBackendVertexArrayId() const { return m_backendVAOId; }
|
||||||
Uint GetContextGeneration() const { return m_contextGeneration; }
|
|
||||||
void Bind() const;
|
void Bind() const;
|
||||||
|
|
||||||
// Draw-path memo of SyncNeccessaryBuffers' attribute walk for this VAO: the
|
// Draw-path memo of SyncNeccessaryBuffers' attribute walk for this VAO: the
|
||||||
@@ -463,10 +462,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
ResolvedDrawBuffers m_resolvedDrawBuffers;
|
ResolvedDrawBuffers m_resolvedDrawBuffers;
|
||||||
PendingAttribValueMask m_pendingAttribValueMask;
|
PendingAttribValueMask m_pendingAttribValueMask;
|
||||||
Uint m_backendVAOId = 0;
|
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;
|
Array<Uint, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS> m_clientAttributeBufferIds;
|
||||||
Bool m_isInitialized = false;
|
Bool m_isInitialized = false;
|
||||||
Uint16 m_syncedIndexBufferVersion = 0;
|
Uint16 m_syncedIndexBufferVersion = 0;
|
||||||
@@ -716,8 +711,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
// parameter already pushed onto it: the params-version early-out has to be overridden
|
// 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.
|
// once, or an unchanged version would skip the re-push forever.
|
||||||
Bool m_forceTextureParamsResync = false;
|
Bool m_forceTextureParamsResync = false;
|
||||||
// Same latch for the built-in sampler parameters.
|
|
||||||
Bool m_forceSamplerResync = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void ActivateTextureUnit(Uint unit);
|
void ActivateTextureUnit(Uint unit);
|
||||||
@@ -1094,7 +1087,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
Bool ReadsBaseVertex() const { return m_baseVertexUniformLocation >= 0; }
|
Bool ReadsBaseVertex() const { return m_baseVertexUniformLocation >= 0; }
|
||||||
Int GetIndirectParamsBinding() const { return m_indirectParamsBinding; }
|
Int GetIndirectParamsBinding() const { return m_indirectParamsBinding; }
|
||||||
Uint GetBackendProgramId() const { return m_backendProgramId; }
|
Uint GetBackendProgramId() const { return m_backendProgramId; }
|
||||||
Uint GetContextGeneration() const { return m_contextGeneration; }
|
|
||||||
// False when the last SyncToBackend could not produce a usable program (a
|
// False when the last SyncToBackend could not produce a usable program (a
|
||||||
// shader failed to transpile or compile, or the link itself failed). Use()
|
// shader failed to transpile or compile, or the link itself failed). Use()
|
||||||
// must not leave the previously bound program current in that case.
|
// must not leave the previously bound program current in that case.
|
||||||
@@ -1157,10 +1149,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
void CacheResourceLocations(const SharedPtr<MG_State::GLState::ProgramObject>& stateProgramObject);
|
void CacheResourceLocations(const SharedPtr<MG_State::GLState::ProgramObject>& stateProgramObject);
|
||||||
|
|
||||||
Uint m_backendProgramId = 0;
|
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
|
// 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
|
// an unusable backend program can be traced back to the glCreateProgram id the app
|
||||||
// knows it by.
|
// knows it by.
|
||||||
@@ -1208,10 +1196,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
// skip redundant rebinds. Reset to 0 wherever glUseProgram(0) is issued or the
|
// skip redundant rebinds. Reset to 0 wherever glUseProgram(0) is issued or the
|
||||||
// ES context is recreated.
|
// ES context is recreated.
|
||||||
extern Uint g_lastUsedBackendProgramId;
|
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>
|
extern StateBackendObjectRegistry<MG_State::GLState::ProgramObject, BackendProgramObjectImpl>
|
||||||
g_backendProgramObjects;
|
g_backendProgramObjects;
|
||||||
|
|
||||||
|
|||||||
@@ -511,7 +511,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Vector<GLExtension> extensions = {
|
Vector<GLExtension> extensions = {
|
||||||
V_OpenGL30, V_OpenGL31, V_OpenGL32, V_OpenGL33, V_OpenGL40, E_GL_ARB_draw_buffers_blend,
|
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_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_draw_indirect,
|
E_GL_ARB_clear_buffer_object, 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_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_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_texture_storage, E_GL_ARB_texture_storage_multisample, E_GL_ARB_texture_multisample,
|
||||||
|
|||||||
@@ -194,54 +194,54 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PipelineFactory::HashType PipelineFactory::ComputeHash(const PipelineCreatePayload& payload) const {
|
PipelineFactory::HashType PipelineFactory::ComputeHash(const PipelineCreatePayload& payload) const {
|
||||||
XXHASH_VERIFY(XXH64_reset(m_hashState.Get(), m_config.CacheVersion));
|
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.programHash, sizeof(payload.programHash)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.programHash, sizeof(payload.programHash)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.vertexInputHash, sizeof(payload.vertexInputHash)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.vertexInputHash, sizeof(payload.vertexInputHash)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.pipelineLayout, sizeof(payload.pipelineLayout)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.pipelineLayout, sizeof(payload.pipelineLayout)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.renderPass, sizeof(payload.renderPass)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.renderPass, sizeof(payload.renderPass)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.colorAttachmentCount, sizeof(payload.colorAttachmentCount)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.colorAttachmentCount, sizeof(payload.colorAttachmentCount)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.rasterizationSamples, sizeof(payload.rasterizationSamples)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.rasterizationSamples, sizeof(payload.rasterizationSamples)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.subpass, sizeof(payload.subpass)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.subpass, sizeof(payload.subpass)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.topology, sizeof(payload.topology)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.topology, sizeof(payload.topology)));
|
||||||
XXHASH_VERIFY(
|
XXHASH_VERIFY(
|
||||||
XXH64_update(m_hashState.Get(), &payload.primitiveRestartEnable, sizeof(payload.primitiveRestartEnable)));
|
XXH64_update(m_hashState, &payload.primitiveRestartEnable, sizeof(payload.primitiveRestartEnable)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.patchControlPoints, sizeof(payload.patchControlPoints)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.patchControlPoints, sizeof(payload.patchControlPoints)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.viewportCount, sizeof(payload.viewportCount)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.viewportCount, sizeof(payload.viewportCount)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.polygonMode, sizeof(payload.polygonMode)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.polygonMode, sizeof(payload.polygonMode)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.cullMode, sizeof(payload.cullMode)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.cullMode, sizeof(payload.cullMode)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.frontFace, sizeof(payload.frontFace)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.frontFace, sizeof(payload.frontFace)));
|
||||||
XXHASH_VERIFY(
|
XXHASH_VERIFY(
|
||||||
XXH64_update(m_hashState.Get(), &payload.provokingVertexMode, sizeof(payload.provokingVertexMode)));
|
XXH64_update(m_hashState, &payload.provokingVertexMode, sizeof(payload.provokingVertexMode)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.depthTestEnable, sizeof(payload.depthTestEnable)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthTestEnable, sizeof(payload.depthTestEnable)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.depthWriteEnable, sizeof(payload.depthWriteEnable)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthWriteEnable, sizeof(payload.depthWriteEnable)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.depthBiasEnable, sizeof(payload.depthBiasEnable)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthBiasEnable, sizeof(payload.depthBiasEnable)));
|
||||||
XXHASH_VERIFY(
|
XXHASH_VERIFY(
|
||||||
XXH64_update(m_hashState.Get(), &payload.rasterizerDiscardEnable, sizeof(payload.rasterizerDiscardEnable)));
|
XXH64_update(m_hashState, &payload.rasterizerDiscardEnable, sizeof(payload.rasterizerDiscardEnable)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.logicOpEnable, sizeof(payload.logicOpEnable)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.logicOpEnable, sizeof(payload.logicOpEnable)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.stencilTestEnable, sizeof(payload.stencilTestEnable)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.stencilTestEnable, sizeof(payload.stencilTestEnable)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.depthCompareOp, sizeof(payload.depthCompareOp)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.depthCompareOp, sizeof(payload.depthCompareOp)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.logicOp, sizeof(payload.logicOp)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.logicOp, sizeof(payload.logicOp)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.frontStencilFailOp, sizeof(payload.frontStencilFailOp)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.frontStencilFailOp, sizeof(payload.frontStencilFailOp)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.frontStencilPassOp, sizeof(payload.frontStencilPassOp)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.frontStencilPassOp, sizeof(payload.frontStencilPassOp)));
|
||||||
XXHASH_VERIFY(
|
XXHASH_VERIFY(
|
||||||
XXH64_update(m_hashState.Get(), &payload.frontStencilDepthFailOp, sizeof(payload.frontStencilDepthFailOp)));
|
XXH64_update(m_hashState, &payload.frontStencilDepthFailOp, sizeof(payload.frontStencilDepthFailOp)));
|
||||||
XXHASH_VERIFY(
|
XXHASH_VERIFY(
|
||||||
XXH64_update(m_hashState.Get(), &payload.frontStencilCompareOp, sizeof(payload.frontStencilCompareOp)));
|
XXH64_update(m_hashState, &payload.frontStencilCompareOp, sizeof(payload.frontStencilCompareOp)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.backStencilFailOp, sizeof(payload.backStencilFailOp)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.backStencilFailOp, sizeof(payload.backStencilFailOp)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &payload.backStencilPassOp, sizeof(payload.backStencilPassOp)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &payload.backStencilPassOp, sizeof(payload.backStencilPassOp)));
|
||||||
XXHASH_VERIFY(
|
XXHASH_VERIFY(
|
||||||
XXH64_update(m_hashState.Get(), &payload.backStencilDepthFailOp, sizeof(payload.backStencilDepthFailOp)));
|
XXH64_update(m_hashState, &payload.backStencilDepthFailOp, sizeof(payload.backStencilDepthFailOp)));
|
||||||
XXHASH_VERIFY(
|
XXHASH_VERIFY(
|
||||||
XXH64_update(m_hashState.Get(), &payload.backStencilCompareOp, sizeof(payload.backStencilCompareOp)));
|
XXH64_update(m_hashState, &payload.backStencilCompareOp, sizeof(payload.backStencilCompareOp)));
|
||||||
XXHASH_VERIFY(
|
XXHASH_VERIFY(
|
||||||
XXH64_update(m_hashState.Get(), &payload.fragmentReplacesDepth, sizeof(payload.fragmentReplacesDepth)));
|
XXH64_update(m_hashState, &payload.fragmentReplacesDepth, sizeof(payload.fragmentReplacesDepth)));
|
||||||
if (payload.colorAttachmentCount > 0) {
|
if (payload.colorAttachmentCount > 0) {
|
||||||
XXHASH_VERIFY(XXH64_update(
|
XXHASH_VERIFY(XXH64_update(
|
||||||
m_hashState.Get(),
|
m_hashState,
|
||||||
payload.colorBlendAttachments.data(),
|
payload.colorBlendAttachments.data(),
|
||||||
sizeof(payload.colorBlendAttachments[0]) * payload.colorAttachmentCount));
|
sizeof(payload.colorBlendAttachments[0]) * payload.colorAttachmentCount));
|
||||||
}
|
}
|
||||||
return XXH64_digest(m_hashState.Get());
|
return XXH64_digest(m_hashState);
|
||||||
}
|
}
|
||||||
|
|
||||||
VkPipeline PipelineFactory::GetOrCreatePipeline(const PipelineCreatePayload& payload) {
|
VkPipeline PipelineFactory::GetOrCreatePipeline(const PipelineCreatePayload& payload) {
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
#include "../VkIncludes.h"
|
#include "../VkIncludes.h"
|
||||||
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
|
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
#include <MG_Util/Types.h>
|
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
// Enough of a fingerprint to identify the exact module the driver rejected without keeping the
|
// Enough of a fingerprint to identify the exact module the driver rejected without keeping the
|
||||||
@@ -166,7 +165,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
UnorderedMap<HashType, PipelineCacheEntry> m_cache;
|
UnorderedMap<HashType, PipelineCacheEntry> m_cache;
|
||||||
// Monotonic frame-boundary counter (bumped in OnFrameBoundary) for cache aging.
|
// Monotonic frame-boundary counter (bumped in OnFrameBoundary) for cache aging.
|
||||||
Uint64 m_frameCounter = 0;
|
Uint64 m_frameCounter = 0;
|
||||||
static inline MobileGL::XXH64State m_hashState;
|
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||||
static inline Bool s_suppressBlendedDepthWrite = false;
|
static inline Bool s_suppressBlendedDepthWrite = false;
|
||||||
};
|
};
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -2156,26 +2156,26 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
ProgramFactory::HashType ProgramFactory::ComputeHash(const MG_State::GLState::ProgramObject& program,
|
ProgramFactory::HashType ProgramFactory::ComputeHash(const MG_State::GLState::ProgramObject& program,
|
||||||
CompileOptionFlags flags) const {
|
CompileOptionFlags flags) const {
|
||||||
XXHASH_VERIFY(XXH64_reset(m_hashState.Get(), m_config.CacheVersion));
|
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion));
|
||||||
// We expect shader stages in program object are sorted
|
// We expect shader stages in program object are sorted
|
||||||
const auto& spirvs = program.GetGeneratedSpirv();
|
const auto& spirvs = program.GetGeneratedSpirv();
|
||||||
for (const auto& spv : spirvs) {
|
for (const auto& spv : spirvs) {
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), spv.data(), spv.size() * sizeof(Uint)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, spv.data(), spv.size() * sizeof(Uint)));
|
||||||
}
|
}
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &flags, sizeof(CompileOptionFlags)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &flags, sizeof(CompileOptionFlags)));
|
||||||
// Only FragCoordYFlip variants bake the height in, so mixing it unconditionally would
|
// 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.
|
// re-key every program in the cache on a resize for no reason.
|
||||||
if (flags & CompileOptionBit::FragCoordYFlip) {
|
if (flags & CompileOptionBit::FragCoordYFlip) {
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &m_defaultFramebufferHeight,
|
XXHASH_VERIFY(XXH64_update(m_hashState, &m_defaultFramebufferHeight,
|
||||||
sizeof(m_defaultFramebufferHeight)));
|
sizeof(m_defaultFramebufferHeight)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include UBO block bindings in hash so different binding configurations produce different entries
|
// Include UBO block bindings in hash so different binding configurations produce different entries
|
||||||
const Uint32 blockCount = static_cast<Uint32>(program.GetActiveUniformBlocksCount());
|
const Uint32 blockCount = static_cast<Uint32>(program.GetActiveUniformBlocksCount());
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &blockCount, sizeof(blockCount)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &blockCount, sizeof(blockCount)));
|
||||||
for (Uint32 i = 0; i < blockCount; ++i) {
|
for (Uint32 i = 0; i < blockCount; ++i) {
|
||||||
const Uint32 binding = program.GetUniformBlockBinding(i);
|
const Uint32 binding = program.GetUniformBlockBinding(i);
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &binding, sizeof(binding)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &binding, sizeof(binding)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// The transform feedback capture layout is baked into the modules by
|
// 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.
|
// hashed for a capturing compile, so nothing else changes key.
|
||||||
if (flags & CompileOptionBit::XfbCapture) {
|
if (flags & CompileOptionBit::XfbCapture) {
|
||||||
for (const auto& varying : program.GetTransformFeedbackVaryings()) {
|
for (const auto& varying : program.GetTransformFeedbackVaryings()) {
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), varying.name.data(), varying.name.size()));
|
XXHASH_VERIFY(XXH64_update(m_hashState, varying.name.data(), varying.name.size()));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &varying.bufferIndex, sizeof(varying.bufferIndex)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &varying.bufferIndex, sizeof(varying.bufferIndex)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &varying.offsetBytes, sizeof(varying.offsetBytes)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &varying.offsetBytes, sizeof(varying.offsetBytes)));
|
||||||
}
|
}
|
||||||
const SizeT bufferCount = program.GetTransformFeedbackBufferCount();
|
const SizeT bufferCount = program.GetTransformFeedbackBufferCount();
|
||||||
for (SizeT i = 0; i < bufferCount; ++i) {
|
for (SizeT i = 0; i < bufferCount; ++i) {
|
||||||
const Uint32 stride = program.GetTransformFeedbackStride(static_cast<Uint32>(i));
|
const Uint32 stride = program.GetTransformFeedbackStride(static_cast<Uint32>(i));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &stride, sizeof(stride)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &stride, sizeof(stride)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HashType hash = XXH64_digest(m_hashState.Get());
|
HashType hash = XXH64_digest(m_hashState);
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2973,8 +2973,73 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
bindings.push_back(layoutBinding);
|
bindings.push_back(layoutBinding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UPDATE_AFTER_BIND is strictly an optional per-layout acceleration. The GL
|
||||||
|
// descriptor model still resolves every sampler uniform element independently
|
||||||
|
// (including its texture-unit sampler-object override); selecting this path
|
||||||
|
// changes neither that resolution nor the set versioning in UniformManager.
|
||||||
|
// A conservative count keeps a layout on ordinary descriptors whenever any
|
||||||
|
// relevant update-after-bind limit is not large enough, rather than asking a
|
||||||
|
// driver to reject it during vkCreateDescriptorSetLayout.
|
||||||
|
Uint32 updateAfterBindSamplers = 0;
|
||||||
|
Uint32 updateAfterBindUniformBuffers = 0;
|
||||||
|
Uint32 updateAfterBindStorageBuffers = 0;
|
||||||
|
Uint32 updateAfterBindSampledImages = 0;
|
||||||
|
Uint32 updateAfterBindStorageImages = 0;
|
||||||
|
for (Uint32 binding = 0; binding < m_maxBindings; ++binding) {
|
||||||
|
const Uint32 count = entry.bindingDescriptorCounts[binding];
|
||||||
|
switch (entry.bindingKinds[binding]) {
|
||||||
|
case DescriptorBindingKind::UniformBufferDynamic:
|
||||||
|
updateAfterBindUniformBuffers += count;
|
||||||
|
break;
|
||||||
|
case DescriptorBindingKind::CombinedImageSampler:
|
||||||
|
updateAfterBindSamplers += count;
|
||||||
|
updateAfterBindSampledImages += count;
|
||||||
|
break;
|
||||||
|
case DescriptorBindingKind::UniformTexelBuffer:
|
||||||
|
updateAfterBindSampledImages += count;
|
||||||
|
break;
|
||||||
|
case DescriptorBindingKind::StorageBuffer:
|
||||||
|
case DescriptorBindingKind::StorageTexelBuffer:
|
||||||
|
updateAfterBindStorageBuffers += count;
|
||||||
|
break;
|
||||||
|
case DescriptorBindingKind::StorageImage:
|
||||||
|
updateAfterBindStorageImages += count;
|
||||||
|
break;
|
||||||
|
case DescriptorBindingKind::None:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const Uint32 updateAfterBindResources = updateAfterBindUniformBuffers + updateAfterBindStorageBuffers +
|
||||||
|
updateAfterBindSampledImages + updateAfterBindStorageImages;
|
||||||
|
const auto& uab = m_updateAfterBindLimits;
|
||||||
|
entry.usesUpdateAfterBind =
|
||||||
|
uab.enabled && updateAfterBindSamplers <= uab.maxPerStageSamplers &&
|
||||||
|
updateAfterBindUniformBuffers <= uab.maxPerStageUniformBuffers &&
|
||||||
|
updateAfterBindStorageBuffers <= uab.maxPerStageStorageBuffers &&
|
||||||
|
updateAfterBindSampledImages <= uab.maxPerStageSampledImages &&
|
||||||
|
updateAfterBindStorageImages <= uab.maxPerStageStorageImages &&
|
||||||
|
updateAfterBindResources <= uab.maxPerStageResources &&
|
||||||
|
updateAfterBindSamplers <= uab.maxSetSamplers &&
|
||||||
|
updateAfterBindUniformBuffers <= uab.maxSetUniformBuffers &&
|
||||||
|
updateAfterBindUniformBuffers <= uab.maxSetUniformBuffersDynamic &&
|
||||||
|
updateAfterBindStorageBuffers <= uab.maxSetStorageBuffers &&
|
||||||
|
updateAfterBindStorageBuffers <= uab.maxSetStorageBuffersDynamic &&
|
||||||
|
updateAfterBindSampledImages <= uab.maxSetSampledImages &&
|
||||||
|
updateAfterBindStorageImages <= uab.maxSetStorageImages;
|
||||||
|
|
||||||
|
Vector<VkDescriptorBindingFlags> bindingFlags;
|
||||||
|
VkDescriptorSetLayoutBindingFlagsCreateInfo bindingFlagsInfo{};
|
||||||
|
if (entry.usesUpdateAfterBind) {
|
||||||
|
bindingFlags.assign(bindings.size(), VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT);
|
||||||
|
bindingFlagsInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO;
|
||||||
|
bindingFlagsInfo.bindingCount = static_cast<Uint32>(bindingFlags.size());
|
||||||
|
bindingFlagsInfo.pBindingFlags = bindingFlags.data();
|
||||||
|
}
|
||||||
|
|
||||||
VkDescriptorSetLayoutCreateInfo setLayoutInfo{};
|
VkDescriptorSetLayoutCreateInfo setLayoutInfo{};
|
||||||
setLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
setLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
||||||
|
setLayoutInfo.flags = entry.usesUpdateAfterBind ? VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT : 0;
|
||||||
|
setLayoutInfo.pNext = entry.usesUpdateAfterBind ? &bindingFlagsInfo : nullptr;
|
||||||
setLayoutInfo.bindingCount = static_cast<Uint32>(bindings.size());
|
setLayoutInfo.bindingCount = static_cast<Uint32>(bindings.size());
|
||||||
setLayoutInfo.pBindings = bindings.data();
|
setLayoutInfo.pBindings = bindings.data();
|
||||||
VK_VERIFY(vkCreateDescriptorSetLayout(m_device, &setLayoutInfo, nullptr, &entry.descriptorSetLayout),
|
VK_VERIFY(vkCreateDescriptorSetLayout(m_device, &setLayoutInfo, nullptr, &entry.descriptorSetLayout),
|
||||||
@@ -3054,6 +3119,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
auto& shaders = program.GetAttachedShaders();
|
auto& shaders = program.GetAttachedShaders();
|
||||||
auto& spirv = program.GetGeneratedSpirv();
|
auto& spirv = program.GetGeneratedSpirv();
|
||||||
Vector<Vector<Uint>> moduleSpirvs(spirv.size());
|
Vector<Vector<Uint>> moduleSpirvs(spirv.size());
|
||||||
|
const Bool enableSpirvValidation = program.GetSpirvValidationEnabled();
|
||||||
|
if (enableSpirvValidation) {
|
||||||
|
MG_Util::ShaderTranspiler::ShaderCompiler::PrepareSpirvValidation();
|
||||||
|
}
|
||||||
|
|
||||||
const ShaderStage fixupStage = PickClipFixupStage(shaders);
|
const ShaderStage fixupStage = PickClipFixupStage(shaders);
|
||||||
|
|
||||||
@@ -3099,7 +3168,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// stored as - which addresses [0,1] where the application addressed texels.
|
// stored as - which addresses [0,1] where the application addressed texels.
|
||||||
{
|
{
|
||||||
Vector<Uint> rectLoweredSpirv;
|
Vector<Uint> rectLoweredSpirv;
|
||||||
if (MG_Util::ShaderTranspiler::ShaderCompiler::LowerRectImages(moduleSpirvs[i], rectLoweredSpirv) &&
|
if (MG_Util::ShaderTranspiler::ShaderCompiler::LowerRectImages(moduleSpirvs[i], rectLoweredSpirv, enableSpirvValidation) &&
|
||||||
!rectLoweredSpirv.empty()) {
|
!rectLoweredSpirv.empty()) {
|
||||||
moduleSpirvs[i] = Move(rectLoweredSpirv);
|
moduleSpirvs[i] = Move(rectLoweredSpirv);
|
||||||
}
|
}
|
||||||
@@ -3112,7 +3181,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
{
|
{
|
||||||
Vector<Uint> invariantSpirv;
|
Vector<Uint> invariantSpirv;
|
||||||
if (MG_Util::ShaderTranspiler::ShaderCompiler::DecoratePositionInvariantForVulkan(
|
if (MG_Util::ShaderTranspiler::ShaderCompiler::DecoratePositionInvariantForVulkan(
|
||||||
moduleSpirvs[i], invariantSpirv)) {
|
moduleSpirvs[i], invariantSpirv, enableSpirvValidation)) {
|
||||||
moduleSpirvs[i] = std::move(invariantSpirv);
|
moduleSpirvs[i] = std::move(invariantSpirv);
|
||||||
} else {
|
} else {
|
||||||
// The pass round-trips through SPIRV-Tools IR, so an unparseable module
|
// The pass round-trips through SPIRV-Tools IR, so an unparseable module
|
||||||
@@ -3136,7 +3205,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_shaderDrawParametersEnabled) {
|
m_shaderDrawParametersEnabled) {
|
||||||
Vector<Uint> rebasedSpirv;
|
Vector<Uint> rebasedSpirv;
|
||||||
if (MG_Util::ShaderTranspiler::ShaderCompiler::RebaseInstanceIndexForVulkan(moduleSpirvs[i],
|
if (MG_Util::ShaderTranspiler::ShaderCompiler::RebaseInstanceIndexForVulkan(moduleSpirvs[i],
|
||||||
rebasedSpirv)) {
|
rebasedSpirv, enableSpirvValidation)) {
|
||||||
moduleSpirvs[i] = std::move(rebasedSpirv);
|
moduleSpirvs[i] = std::move(rebasedSpirv);
|
||||||
} else {
|
} else {
|
||||||
MGLOG_E("ProgramFactory: failed to rebase gl_InstanceID for program %u; "
|
MGLOG_E("ProgramFactory: failed to rebase gl_InstanceID for program %u; "
|
||||||
@@ -3154,7 +3223,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
(flags & CompileOptionBit::ZeroBaseVertex)) {
|
(flags & CompileOptionBit::ZeroBaseVertex)) {
|
||||||
Vector<Uint> zeroedSpirv;
|
Vector<Uint> zeroedSpirv;
|
||||||
if (MG_Util::ShaderTranspiler::ShaderCompiler::ZeroBaseVertexForVulkan(moduleSpirvs[i],
|
if (MG_Util::ShaderTranspiler::ShaderCompiler::ZeroBaseVertexForVulkan(moduleSpirvs[i],
|
||||||
zeroedSpirv)) {
|
zeroedSpirv, enableSpirvValidation)) {
|
||||||
moduleSpirvs[i] = std::move(zeroedSpirv);
|
moduleSpirvs[i] = std::move(zeroedSpirv);
|
||||||
} else {
|
} else {
|
||||||
// Failing open keeps the native builtin, which is the pre-fix behavior:
|
// Failing open keeps the native builtin, which is the pre-fix behavior:
|
||||||
@@ -3177,7 +3246,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
if (shaders[i] && shaders[i]->GetShaderStage() == ShaderStage::Vertex) {
|
if (shaders[i] && shaders[i]->GetShaderStage() == ShaderStage::Vertex) {
|
||||||
Vector<Uint> packedSpirv;
|
Vector<Uint> packedSpirv;
|
||||||
const Bool packOk = MG_Util::ShaderTranspiler::ShaderCompiler::PackDoubleVertexInputsForVulkan(
|
const Bool packOk = MG_Util::ShaderTranspiler::ShaderCompiler::PackDoubleVertexInputsForVulkan(
|
||||||
moduleSpirvs[i], packedSpirv);
|
moduleSpirvs[i], packedSpirv, enableSpirvValidation);
|
||||||
MOBILEGL_ASSERT(packOk,
|
MOBILEGL_ASSERT(packOk,
|
||||||
"ProgramFactory: 64-bit vertex input packing failed for program %u; the "
|
"ProgramFactory: 64-bit vertex input packing failed for program %u; the "
|
||||||
"vertex-input format and the shader input type now disagree",
|
"vertex-input format and the shader input type now disagree",
|
||||||
@@ -3201,7 +3270,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
if (m_unformattedFloatStorageImagesEnabled) {
|
if (m_unformattedFloatStorageImagesEnabled) {
|
||||||
Vector<Uint> unformattedSpirv;
|
Vector<Uint> unformattedSpirv;
|
||||||
if (MG_Util::ShaderTranspiler::ShaderCompiler::UseUnformattedFloatStorageImagesForVulkan(
|
if (MG_Util::ShaderTranspiler::ShaderCompiler::UseUnformattedFloatStorageImagesForVulkan(
|
||||||
moduleSpirvs[i], unformattedSpirv)) {
|
moduleSpirvs[i], unformattedSpirv, enableSpirvValidation)) {
|
||||||
moduleSpirvs[i] = std::move(unformattedSpirv);
|
moduleSpirvs[i] = std::move(unformattedSpirv);
|
||||||
} else {
|
} else {
|
||||||
MGLOG_E("ProgramFactory: failed to make float storage images unformatted for program %u",
|
MGLOG_E("ProgramFactory: failed to make float storage images unformatted for program %u",
|
||||||
@@ -3222,7 +3291,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
#else
|
#else
|
||||||
// Final module the driver receives; also checked in the INFO-level CI/test
|
// Final module the driver receives; also checked in the INFO-level CI/test
|
||||||
// lanes, where the DEBUG gate above is compiled out.
|
// lanes, where the DEBUG gate above is compiled out.
|
||||||
if (MG_Util::ShaderTranspiler::ShaderCompiler::SpirvValidationEnabled()) {
|
if (enableSpirvValidation) {
|
||||||
ValidateTransformedSpirv(moduleSpv, shaders[i]->GetShaderStage(), program.GetExternalIndex());
|
ValidateTransformedSpirv(moduleSpv, shaders[i]->GetShaderStage(), program.GetExternalIndex());
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -3299,14 +3368,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const VkDescriptorSetLayout descriptorSetLayout = it->second.descriptorSetLayout;
|
const VkDescriptorSetLayout descriptorSetLayout = it->second.descriptorSetLayout;
|
||||||
MGLOG_D("ProgramFactory::OnFrameBoundary: evicting idle program entry hash=0x%llx",
|
MGLOG_D("ProgramFactory::OnFrameBoundary: evicting idle program entry hash=0x%llx",
|
||||||
static_cast<unsigned long long>(hash));
|
static_cast<unsigned long long>(hash));
|
||||||
// erase runs ~VkProgramObject (modules/layouts destroyed); notify after
|
// The observer destroys dependent pipelines and frees descriptor sets while
|
||||||
// so an observer never observes a half-destroyed entry through a lookup.
|
// this entry still owns its layout. Vulkan requires every descriptor set to be
|
||||||
// Observers only need the handle values to purge their keyed caches.
|
// freed before its VkDescriptorSetLayout is destroyed.
|
||||||
++m_cacheStructureEpoch; // erase moves/kills entries: memoised pointers die
|
|
||||||
it = m_cache.erase(it);
|
|
||||||
if (m_evictionObserver != nullptr) {
|
if (m_evictionObserver != nullptr) {
|
||||||
m_evictionObserver->OnProgramEvicted(hash, descriptorSetLayout);
|
m_evictionObserver->OnProgramEvicted(hash, descriptorSetLayout);
|
||||||
}
|
}
|
||||||
|
++m_cacheStructureEpoch; // erase moves/kills entries: memoised pointers die
|
||||||
|
it = m_cache.erase(it);
|
||||||
} else {
|
} else {
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
@@ -3440,7 +3509,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG
|
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG
|
||||||
ValidateTransformedSpirv(spirv, ShaderStage::TessControl, 0);
|
ValidateTransformedSpirv(spirv, ShaderStage::TessControl, 0);
|
||||||
#else
|
#else
|
||||||
if (MG_Util::ShaderTranspiler::ShaderCompiler::SpirvValidationEnabled()) {
|
if (m_enableSpirvValidation) {
|
||||||
|
MG_Util::ShaderTranspiler::ShaderCompiler::PrepareSpirvValidation();
|
||||||
ValidateTransformedSpirv(spirv, ShaderStage::TessControl, 0);
|
ValidateTransformedSpirv(spirv, ShaderStage::TessControl, 0);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
#include "MG_State/GLState/TextureState/TextureEnum.h"
|
#include "MG_State/GLState/TextureState/TextureEnum.h"
|
||||||
|
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
#include <MG_Util/Types.h>
|
|
||||||
#include <spirv_reflect.h>
|
#include <spirv_reflect.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
@@ -77,6 +76,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
using CompileOptionFlags = Flags<CompileOptionBit>;
|
using CompileOptionFlags = Flags<CompileOptionBit>;
|
||||||
using HashType = Uint64;
|
using HashType = Uint64;
|
||||||
|
|
||||||
|
struct UpdateAfterBindLimits {
|
||||||
|
Bool enabled = false;
|
||||||
|
Uint32 maxPerStageSamplers = 0;
|
||||||
|
Uint32 maxPerStageUniformBuffers = 0;
|
||||||
|
Uint32 maxPerStageStorageBuffers = 0;
|
||||||
|
Uint32 maxPerStageSampledImages = 0;
|
||||||
|
Uint32 maxPerStageStorageImages = 0;
|
||||||
|
Uint32 maxPerStageResources = 0;
|
||||||
|
Uint32 maxSetSamplers = 0;
|
||||||
|
Uint32 maxSetUniformBuffers = 0;
|
||||||
|
Uint32 maxSetUniformBuffersDynamic = 0;
|
||||||
|
Uint32 maxSetStorageBuffers = 0;
|
||||||
|
Uint32 maxSetStorageBuffersDynamic = 0;
|
||||||
|
Uint32 maxSetSampledImages = 0;
|
||||||
|
Uint32 maxSetStorageImages = 0;
|
||||||
|
};
|
||||||
|
|
||||||
struct VkProgramObject {
|
struct VkProgramObject {
|
||||||
static constexpr Uint32 kMaxVertexInputLocations = 32;
|
static constexpr Uint32 kMaxVertexInputLocations = 32;
|
||||||
|
|
||||||
@@ -89,6 +105,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
// Layout data (previously in separate VkProgramLayout)
|
// Layout data (previously in separate VkProgramLayout)
|
||||||
VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE;
|
VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE;
|
||||||
|
// True only when this layout passed every descriptor-indexing feature and
|
||||||
|
// update-after-bind limit gate at reflection time. It controls both the
|
||||||
|
// layout/binding flags and the pool class used by UniformManager.
|
||||||
|
Bool usesUpdateAfterBind = false;
|
||||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||||
Vector<DescriptorBindingKind> bindingKinds;
|
Vector<DescriptorBindingKind> bindingKinds;
|
||||||
// The bindings this program actually declares, ascending. bindingKinds is sized to the
|
// The bindings this program actually declares, ascending. bindingKinds is sized to the
|
||||||
@@ -197,6 +217,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// a pipeline failure would be reported against the wrong SPIR-V.
|
// a pipeline failure would be reported against the wrong SPIR-V.
|
||||||
stageSpirvDigests = std::move(other.stageSpirvDigests);
|
stageSpirvDigests = std::move(other.stageSpirvDigests);
|
||||||
descriptorSetLayout = other.descriptorSetLayout;
|
descriptorSetLayout = other.descriptorSetLayout;
|
||||||
|
usesUpdateAfterBind = other.usesUpdateAfterBind;
|
||||||
pipelineLayout = other.pipelineLayout;
|
pipelineLayout = other.pipelineLayout;
|
||||||
bindingKinds = std::move(other.bindingKinds);
|
bindingKinds = std::move(other.bindingKinds);
|
||||||
activeBindings = std::move(other.activeBindings);
|
activeBindings = std::move(other.activeBindings);
|
||||||
@@ -231,6 +252,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
lastUsedFrame = other.lastUsedFrame;
|
lastUsedFrame = other.lastUsedFrame;
|
||||||
other.hash = 0;
|
other.hash = 0;
|
||||||
other.descriptorSetLayout = VK_NULL_HANDLE;
|
other.descriptorSetLayout = VK_NULL_HANDLE;
|
||||||
|
other.usesUpdateAfterBind = false;
|
||||||
other.pipelineLayout = VK_NULL_HANDLE;
|
other.pipelineLayout = VK_NULL_HANDLE;
|
||||||
other.hasStorageImages = false;
|
other.hasStorageImages = false;
|
||||||
other.declinedDescriptors = false;
|
other.declinedDescriptors = false;
|
||||||
@@ -257,6 +279,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
modules = std::move(other.modules);
|
modules = std::move(other.modules);
|
||||||
stageSpirvDigests = std::move(other.stageSpirvDigests); // travels with `modules` - see the move ctor
|
stageSpirvDigests = std::move(other.stageSpirvDigests); // travels with `modules` - see the move ctor
|
||||||
descriptorSetLayout = other.descriptorSetLayout;
|
descriptorSetLayout = other.descriptorSetLayout;
|
||||||
|
usesUpdateAfterBind = other.usesUpdateAfterBind;
|
||||||
pipelineLayout = other.pipelineLayout;
|
pipelineLayout = other.pipelineLayout;
|
||||||
bindingKinds = std::move(other.bindingKinds);
|
bindingKinds = std::move(other.bindingKinds);
|
||||||
activeBindings = std::move(other.activeBindings);
|
activeBindings = std::move(other.activeBindings);
|
||||||
@@ -291,6 +314,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
lastUsedFrame = other.lastUsedFrame;
|
lastUsedFrame = other.lastUsedFrame;
|
||||||
other.hash = 0;
|
other.hash = 0;
|
||||||
other.descriptorSetLayout = VK_NULL_HANDLE;
|
other.descriptorSetLayout = VK_NULL_HANDLE;
|
||||||
|
other.usesUpdateAfterBind = false;
|
||||||
other.pipelineLayout = VK_NULL_HANDLE;
|
other.pipelineLayout = VK_NULL_HANDLE;
|
||||||
other.hasStorageImages = false;
|
other.hasStorageImages = false;
|
||||||
other.declinedDescriptors = false;
|
other.declinedDescriptors = false;
|
||||||
@@ -348,12 +372,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
virtual void OnProgramEvicted(HashType programHash, VkDescriptorSetLayout descriptorSetLayout) = 0;
|
virtual void OnProgramEvicted(HashType programHash, VkDescriptorSetLayout descriptorSetLayout) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config, Uint32 maxBindings = 16,
|
explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config, Uint32 maxBindings,
|
||||||
Bool shaderDrawParametersEnabled = false,
|
Bool shaderDrawParametersEnabled,
|
||||||
Bool unformattedFloatStorageImagesEnabled = false)
|
Bool unformattedFloatStorageImagesEnabled,
|
||||||
|
Bool enableSpirvValidation,
|
||||||
|
UpdateAfterBindLimits updateAfterBindLimits)
|
||||||
: m_device(device), m_maxBindings(maxBindings), m_config(config),
|
: m_device(device), m_maxBindings(maxBindings), m_config(config),
|
||||||
m_shaderDrawParametersEnabled(shaderDrawParametersEnabled),
|
m_shaderDrawParametersEnabled(shaderDrawParametersEnabled),
|
||||||
m_unformattedFloatStorageImagesEnabled(unformattedFloatStorageImagesEnabled) {
|
m_unformattedFloatStorageImagesEnabled(unformattedFloatStorageImagesEnabled),
|
||||||
|
m_enableSpirvValidation(enableSpirvValidation),
|
||||||
|
m_updateAfterBindLimits(updateAfterBindLimits) {
|
||||||
VkProgramObject::s_device = device;
|
VkProgramObject::s_device = device;
|
||||||
}
|
}
|
||||||
// Destroys the pass-through tessellation control modules. Runs while the device is
|
// Destroys the pass-through tessellation control modules. Runs while the device is
|
||||||
@@ -476,6 +504,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// True only when the logical device enabled both
|
// True only when the logical device enabled both
|
||||||
// shaderStorageImageReadWithoutFormat and shaderStorageImageWriteWithoutFormat.
|
// shaderStorageImageReadWithoutFormat and shaderStorageImageWriteWithoutFormat.
|
||||||
Bool m_unformattedFloatStorageImagesEnabled = false;
|
Bool m_unformattedFloatStorageImagesEnabled = false;
|
||||||
|
// Startup snapshot used only by internally synthesized shader modules, which do not
|
||||||
|
// originate from a ProgramLinkTask.
|
||||||
|
Bool m_enableSpirvValidation = false;
|
||||||
|
// Device feature and limit gate resolved before vkCreateDevice. Keeping it in
|
||||||
|
// the factory lets each reflected layout choose ordinary descriptors when its
|
||||||
|
// own counts would exceed the update-after-bind budget.
|
||||||
|
UpdateAfterBindLimits m_updateAfterBindLimits{};
|
||||||
// See SetDefaultFramebufferHeight. 0 means "not known yet"; the FragCoordYFlip bit is
|
// See SetDefaultFramebufferHeight. 0 means "not known yet"; the FragCoordYFlip bit is
|
||||||
// never set before the swapchain exists, so no variant can be compiled against it.
|
// never set before the swapchain exists, so no variant can be compiled against it.
|
||||||
Uint32 m_defaultFramebufferHeight = 0;
|
Uint32 m_defaultFramebufferHeight = 0;
|
||||||
@@ -490,6 +525,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// ever built from one keeps referencing its module. A failed build is cached as
|
// 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.
|
// VK_NULL_HANDLE so a broken generator costs one compile, not one per draw.
|
||||||
UnorderedMap<Uint32, VkPipelineShaderStageCreateInfo> m_passthroughTessControlStages;
|
UnorderedMap<Uint32, VkPipelineShaderStageCreateInfo> m_passthroughTessControlStages;
|
||||||
static inline MobileGL::XXH64State m_hashState;
|
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||||
};
|
};
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -156,13 +156,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
frame.descriptorPools.clear();
|
frame.descriptorPools.clear();
|
||||||
|
|
||||||
VkDescriptorPool initialPool = VK_NULL_HANDLE;
|
VkDescriptorPool initialPool = VK_NULL_HANDLE;
|
||||||
if (!CreateDescriptorPool(m_setsPerFrame, initialPool)) {
|
if (!CreateDescriptorPool(m_setsPerFrame, false, initialPool)) {
|
||||||
MGLOG_E_ONCE("UniformDescriptorBinder::Initialize failed: cannot create frame descriptor pool %u",
|
MGLOG_E_ONCE("UniformDescriptorBinder::Initialize failed: cannot create frame descriptor pool %u",
|
||||||
frameIndex);
|
frameIndex);
|
||||||
Shutdown();
|
Shutdown();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
frame.descriptorPools.push_back({initialPool, m_setsPerFrame, 0});
|
frame.descriptorPools.push_back({initialPool, m_setsPerFrame, 0, false});
|
||||||
MGLOG_D("UniformDescriptorBinder: frame %u descriptor pool created (maxSets=%u)", frameIndex,
|
MGLOG_D("UniformDescriptorBinder: frame %u descriptor pool created (maxSets=%u)", frameIndex,
|
||||||
m_setsPerFrame);
|
m_setsPerFrame);
|
||||||
}
|
}
|
||||||
@@ -1390,7 +1390,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool UniformManager::CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const {
|
Bool UniformManager::CreateDescriptorPool(Uint32 maxSets, Bool updateAfterBind, VkDescriptorPool& outPool) const {
|
||||||
outPool = VK_NULL_HANDLE;
|
outPool = VK_NULL_HANDLE;
|
||||||
if (m_device == VK_NULL_HANDLE || maxSets == 0 || m_maxBindings == 0) {
|
if (m_device == VK_NULL_HANDLE || maxSets == 0 || m_maxBindings == 0) {
|
||||||
return false;
|
return false;
|
||||||
@@ -1433,7 +1433,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// (OnDescriptorSetLayoutDestroyed) so program churn recycles pool capacity.
|
// (OnDescriptorSetLayoutDestroyed) so program churn recycles pool capacity.
|
||||||
// The cost is on set allocation only, which happens when a layout's per-frame
|
// The cost is on set allocation only, which happens when a layout's per-frame
|
||||||
// cache grows - never on the per-draw reuse path.
|
// cache grows - never on the per-draw reuse path.
|
||||||
poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
|
poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT |
|
||||||
|
(updateAfterBind ? VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT : 0);
|
||||||
poolInfo.maxSets = maxSets;
|
poolInfo.maxSets = maxSets;
|
||||||
poolInfo.poolSizeCount = static_cast<Uint32>(std::size(poolSizes));
|
poolInfo.poolSizeCount = static_cast<Uint32>(std::size(poolSizes));
|
||||||
poolInfo.pPoolSizes = poolSizes;
|
poolInfo.pPoolSizes = poolSizes;
|
||||||
@@ -1447,24 +1448,28 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool UniformManager::GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex) {
|
Bool UniformManager::GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex, Bool updateAfterBind) {
|
||||||
if (frame.descriptorPools.empty()) {
|
if (frame.descriptorPools.empty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& currentBucket = frame.descriptorPools[frame.activeDescriptorPoolIndex];
|
const auto matchingBucket = std::find_if(
|
||||||
const Uint32 currentMaxSets = std::max<Uint32>(1, currentBucket.maxSets);
|
frame.descriptorPools.begin(), frame.descriptorPools.end(),
|
||||||
|
[updateAfterBind](const DescriptorPoolBucket& candidate) { return candidate.updateAfterBind == updateAfterBind; });
|
||||||
|
const Uint32 currentMaxSets = matchingBucket != frame.descriptorPools.end()
|
||||||
|
? std::max<Uint32>(1, matchingBucket->maxSets)
|
||||||
|
: m_setsPerFrame;
|
||||||
const Uint32 grownMaxSets = currentMaxSets <= (std::numeric_limits<Uint32>::max() / 2) ? (currentMaxSets * 2)
|
const Uint32 grownMaxSets = currentMaxSets <= (std::numeric_limits<Uint32>::max() / 2) ? (currentMaxSets * 2)
|
||||||
: currentMaxSets;
|
: currentMaxSets;
|
||||||
|
|
||||||
VkDescriptorPool grownPool = VK_NULL_HANDLE;
|
VkDescriptorPool grownPool = VK_NULL_HANDLE;
|
||||||
if (!CreateDescriptorPool(grownMaxSets, grownPool)) {
|
if (!CreateDescriptorPool(grownMaxSets, updateAfterBind, grownPool)) {
|
||||||
MGLOG_E_ONCE("UniformDescriptorBinder::GrowFrameDescriptorPool failed: cannot create grown pool (%u -> %u sets)",
|
MGLOG_E_ONCE("UniformDescriptorBinder::GrowFrameDescriptorPool failed: cannot create grown pool (%u -> %u sets)",
|
||||||
currentMaxSets, grownMaxSets);
|
currentMaxSets, grownMaxSets);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
frame.descriptorPools.push_back({grownPool, grownMaxSets, 0});
|
frame.descriptorPools.push_back({grownPool, grownMaxSets, 0, updateAfterBind});
|
||||||
frame.activeDescriptorPoolIndex = static_cast<Uint32>(frame.descriptorPools.size() - 1);
|
frame.activeDescriptorPoolIndex = static_cast<Uint32>(frame.descriptorPools.size() - 1);
|
||||||
MGLOG_D(
|
MGLOG_D(
|
||||||
"UniformDescriptorBinder: frame %u descriptor pool exhausted, grew pool (%u -> %u sets), poolCount=%zu",
|
"UniformDescriptorBinder: frame %u descriptor pool exhausted, grew pool (%u -> %u sets), poolCount=%zu",
|
||||||
@@ -1474,14 +1479,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
|
|
||||||
VkResult UniformManager::AllocateDescriptorSetsFromActivePool(Uint32 frameIndex, const ProgramFactory::VkProgramObject& programObj, VkDescriptorSet& outDescriptorSet) {
|
VkResult UniformManager::AllocateDescriptorSetsFromActivePool(Uint32 frameIndex, const ProgramFactory::VkProgramObject& programObj, VkDescriptorSet& outDescriptorSet) {
|
||||||
auto& frame = m_frames[frameIndex];
|
auto& frame = m_frames[frameIndex];
|
||||||
if (frame.activeDescriptorPoolIndex >= frame.descriptorPools.size()) {
|
const Bool updateAfterBind = programObj.usesUpdateAfterBind;
|
||||||
frame.activeDescriptorPoolIndex = 0;
|
if (frame.activeDescriptorPoolIndex >= frame.descriptorPools.size() ||
|
||||||
}
|
frame.descriptorPools[frame.activeDescriptorPoolIndex].updateAfterBind != updateAfterBind ||
|
||||||
if (frame.descriptorPools[frame.activeDescriptorPoolIndex].allocatedSets >=
|
frame.descriptorPools[frame.activeDescriptorPoolIndex].allocatedSets >=
|
||||||
frame.descriptorPools[frame.activeDescriptorPoolIndex].maxSets) {
|
frame.descriptorPools[frame.activeDescriptorPoolIndex].maxSets) {
|
||||||
const auto availableBucket = std::find_if(
|
const auto availableBucket = std::find_if(
|
||||||
frame.descriptorPools.begin(), frame.descriptorPools.end(),
|
frame.descriptorPools.begin(), frame.descriptorPools.end(),
|
||||||
[](const DescriptorPoolBucket& candidate) { return candidate.allocatedSets < candidate.maxSets; });
|
[updateAfterBind](const DescriptorPoolBucket& candidate) {
|
||||||
|
return candidate.updateAfterBind == updateAfterBind && candidate.allocatedSets < candidate.maxSets;
|
||||||
|
});
|
||||||
if (availableBucket == frame.descriptorPools.end()) {
|
if (availableBucket == frame.descriptorPools.end()) {
|
||||||
outDescriptorSet = VK_NULL_HANDLE;
|
outDescriptorSet = VK_NULL_HANDLE;
|
||||||
return VK_ERROR_OUT_OF_POOL_MEMORY;
|
return VK_ERROR_OUT_OF_POOL_MEMORY;
|
||||||
@@ -1517,7 +1524,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
} else {
|
} else {
|
||||||
VkResult allocResult = AllocateDescriptorSetsFromActivePool(frameIndex, programObj, outDescriptorSet);
|
VkResult allocResult = AllocateDescriptorSetsFromActivePool(frameIndex, programObj, outDescriptorSet);
|
||||||
if (allocResult == VK_ERROR_OUT_OF_POOL_MEMORY || allocResult == VK_ERROR_FRAGMENTED_POOL) {
|
if (allocResult == VK_ERROR_OUT_OF_POOL_MEMORY || allocResult == VK_ERROR_FRAGMENTED_POOL) {
|
||||||
if (!GrowFrameDescriptorPool(frame, frameIndex)) {
|
if (!GrowFrameDescriptorPool(frame, frameIndex, programObj.usesUpdateAfterBind)) {
|
||||||
MGLOG_E_ONCE("UniformDescriptorBinder::AcquireDescriptorSet failed: descriptor pool growth failed");
|
MGLOG_E_ONCE("UniformDescriptorBinder::AcquireDescriptorSet failed: descriptor pool growth failed");
|
||||||
return allocResult;
|
return allocResult;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkDescriptorPool handle = VK_NULL_HANDLE;
|
VkDescriptorPool handle = VK_NULL_HANDLE;
|
||||||
Uint32 maxSets = 0;
|
Uint32 maxSets = 0;
|
||||||
Uint32 allocatedSets = 0;
|
Uint32 allocatedSets = 0;
|
||||||
|
Bool updateAfterBind = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// A cached descriptor set together with the pool it was allocated from, so a
|
// A cached descriptor set together with the pool it was allocated from, so a
|
||||||
@@ -223,8 +224,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
void BindDescriptorSetDeduped(VkCommandBuffer commandBuffer, VkPipelineBindPoint bindPoint,
|
void BindDescriptorSetDeduped(VkCommandBuffer commandBuffer, VkPipelineBindPoint bindPoint,
|
||||||
VkPipelineLayout pipelineLayout, VkDescriptorSet descriptorSet,
|
VkPipelineLayout pipelineLayout, VkDescriptorSet descriptorSet,
|
||||||
const Vector<Uint32>& dynamicOffsets);
|
const Vector<Uint32>& dynamicOffsets);
|
||||||
Bool CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const;
|
Bool CreateDescriptorPool(Uint32 maxSets, Bool updateAfterBind, VkDescriptorPool& outPool) const;
|
||||||
Bool GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex);
|
Bool GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex, Bool updateAfterBind);
|
||||||
VkResult AllocateDescriptorSetsFromActivePool(
|
VkResult AllocateDescriptorSetsFromActivePool(
|
||||||
Uint32 frameIndex, const ProgramFactory::VkProgramObject& programObj, VkDescriptorSet& outDescriptorSet);
|
Uint32 frameIndex, const ProgramFactory::VkProgramObject& programObj, VkDescriptorSet& outDescriptorSet);
|
||||||
VkResult AcquireDescriptorSet(Uint32 frameIndex,
|
VkResult AcquireDescriptorSet(Uint32 frameIndex,
|
||||||
|
|||||||
@@ -13,25 +13,25 @@
|
|||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
VertexInputStateFactory::HashType VertexInputStateFactory::ComputeHash(
|
VertexInputStateFactory::HashType VertexInputStateFactory::ComputeHash(
|
||||||
const MG_State::GLState::VertexArrayObject& vao) const {
|
const MG_State::GLState::VertexArrayObject& vao) const {
|
||||||
XXHASH_VERIFY(XXH64_reset(m_hashState.Get(), m_config.CacheVersion));
|
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion));
|
||||||
|
|
||||||
for (Int i = 0; i < MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS; ++i) {
|
for (Int i = 0; i < MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS; ++i) {
|
||||||
const auto& attr = vao.GetAttribute(i);
|
const auto& attr = vao.GetAttribute(i);
|
||||||
|
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attr.Enabled, sizeof(attr.Enabled)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Enabled, sizeof(attr.Enabled)));
|
||||||
if (!attr.Enabled) {
|
if (!attr.Enabled) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attr.Size, sizeof(attr.Size)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Size, sizeof(attr.Size)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attr.Type, sizeof(attr.Type)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Type, sizeof(attr.Type)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attr.Normalized, sizeof(attr.Normalized)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Normalized, sizeof(attr.Normalized)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attr.Stride, sizeof(attr.Stride)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Stride, sizeof(attr.Stride)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attr.Offset, sizeof(attr.Offset)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Offset, sizeof(attr.Offset)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attr.IsInteger, sizeof(attr.IsInteger)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.IsInteger, sizeof(attr.IsInteger)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attr.IsLong, sizeof(attr.IsLong)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.IsLong, sizeof(attr.IsLong)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attr.IsBgra, sizeof(attr.IsBgra)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.IsBgra, sizeof(attr.IsBgra)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attr.Divisor, sizeof(attr.Divisor)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Divisor, sizeof(attr.Divisor)));
|
||||||
|
|
||||||
// The bound buffer's IDENTITY is a component of the key, and it has to be the
|
// 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
|
// 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.
|
// test's positions) instead of its own.
|
||||||
// Zero for client memory (no buffer), which is a distinct identity 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;
|
const Uint64 bufferKey = attr.Buffer ? attr.Buffer->GetLifetimeId() : 0;
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &bufferKey, sizeof(bufferKey)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &bufferKey, sizeof(bufferKey)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return XXH64_digest(m_hashState.Get());
|
return XXH64_digest(m_hashState);
|
||||||
}
|
}
|
||||||
|
|
||||||
VertexInputStateFactory::HashType VertexInputStateFactory::GetOrComputeHash(
|
VertexInputStateFactory::HashType VertexInputStateFactory::GetOrComputeHash(
|
||||||
@@ -225,24 +225,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
entry.attributes = builder.GetAttributes();
|
entry.attributes = builder.GetAttributes();
|
||||||
// See the layoutHash declaration: hash only the resolved layout, never
|
// See the layoutHash declaration: hash only the resolved layout, never
|
||||||
// buffer identities, so identical layouts across VAOs/buffers agree.
|
// buffer identities, so identical layouts across VAOs/buffers agree.
|
||||||
XXHASH_VERIFY(XXH64_reset(m_hashState.Get(), 0));
|
XXHASH_VERIFY(XXH64_reset(m_hashState, 0));
|
||||||
for (const auto& binding : entry.bindings) {
|
for (const auto& binding : entry.bindings) {
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &binding.binding, sizeof(binding.binding)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &binding.binding, sizeof(binding.binding)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &binding.stride, sizeof(binding.stride)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &binding.stride, sizeof(binding.stride)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &binding.inputRate, sizeof(binding.inputRate)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &binding.inputRate, sizeof(binding.inputRate)));
|
||||||
}
|
}
|
||||||
for (const auto& attribute : entry.attributes) {
|
for (const auto& attribute : entry.attributes) {
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attribute.location, sizeof(attribute.location)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attribute.location, sizeof(attribute.location)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attribute.binding, sizeof(attribute.binding)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attribute.binding, sizeof(attribute.binding)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attribute.format, sizeof(attribute.format)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attribute.format, sizeof(attribute.format)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &attribute.offset, sizeof(attribute.offset)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &attribute.offset, sizeof(attribute.offset)));
|
||||||
}
|
}
|
||||||
for (const auto& divisor : entry.bindingDivisors) {
|
for (const auto& divisor : entry.bindingDivisors) {
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &divisor.binding, sizeof(divisor.binding)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &divisor.binding, sizeof(divisor.binding)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &divisor.divisor, sizeof(divisor.divisor)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &divisor.divisor, sizeof(divisor.divisor)));
|
||||||
}
|
}
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &unsupportedAttribMask, sizeof(unsupportedAttribMask)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &unsupportedAttribMask, sizeof(unsupportedAttribMask)));
|
||||||
entry.layoutHash = XXH64_digest(m_hashState.Get());
|
entry.layoutHash = XXH64_digest(m_hashState);
|
||||||
entry.attributeLocationMask = 0;
|
entry.attributeLocationMask = 0;
|
||||||
for (const auto& attribute : entry.attributes) {
|
for (const auto& attribute : entry.attributes) {
|
||||||
if (attribute.location < 32u) {
|
if (attribute.location < 32u) {
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
#include "VertexInputStateBuilder.h"
|
#include "VertexInputStateBuilder.h"
|
||||||
#include "MG_State/GLState/VertexArrayState/VertexArrayObject.h"
|
#include "MG_State/GLState/VertexArrayState/VertexArrayObject.h"
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
#include <MG_Util/Types.h>
|
|
||||||
#include "../VkIncludes.h"
|
#include "../VkIncludes.h"
|
||||||
|
|
||||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||||
@@ -127,6 +126,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// matches, so an evicted entry can never be dereferenced through a
|
// matches, so an evicted entry can never be dereferenced through a
|
||||||
// stale memo.
|
// stale memo.
|
||||||
Uint64 m_evictionEpoch = 1;
|
Uint64 m_evictionEpoch = 1;
|
||||||
static inline MobileGL::XXH64State m_hashState;
|
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||||
};
|
};
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -594,27 +594,27 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
VkRenderPassManager::HashType VkRenderPassManager::ComputeHash(
|
VkRenderPassManager::HashType VkRenderPassManager::ComputeHash(
|
||||||
const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex, Bool includePendingClear,
|
const MG_State::GLState::FramebufferObject& fbo, Uint32 swapchainImageIndex, Bool includePendingClear,
|
||||||
Bool includeDefaultFboDepthStencil) {
|
Bool includeDefaultFboDepthStencil) {
|
||||||
XXHASH_VERIFY(XXH64_reset(m_hashState.Get(), m_config.CacheVersion));
|
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion));
|
||||||
const Bool isDefaultFbo = fbo.IsDefaultFramebuffer();
|
const Bool isDefaultFbo = fbo.IsDefaultFramebuffer();
|
||||||
if (isDefaultFbo) {
|
if (isDefaultFbo) {
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &swapchainImageIndex, sizeof(swapchainImageIndex)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &swapchainImageIndex, sizeof(swapchainImageIndex)));
|
||||||
}
|
}
|
||||||
// sRGB attachments switch between their sRGB and UNORM-twin views with this
|
// sRGB attachments switch between their sRGB and UNORM-twin views with this
|
||||||
// capability (ResolveSrgbAttachmentWriteFormat), changing the render pass formats.
|
// capability (ResolveSrgbAttachmentWriteFormat), changing the render pass formats.
|
||||||
const Bool framebufferSrgbEnabled =
|
const Bool framebufferSrgbEnabled =
|
||||||
MG_State::pGLContext->IsCapabilityEnabled(MobileGL::CapabilityInput::FramebufferSrgb);
|
MG_State::pGLContext->IsCapabilityEnabled(MobileGL::CapabilityInput::FramebufferSrgb);
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &framebufferSrgbEnabled, sizeof(framebufferSrgbEnabled)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &framebufferSrgbEnabled, sizeof(framebufferSrgbEnabled)));
|
||||||
auto& drawBuffers = fbo.GetDrawBuffers();
|
auto& drawBuffers = fbo.GetDrawBuffers();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), drawBuffers.data(), drawBuffers.size() * sizeof(drawBuffers[0])));
|
XXHASH_VERIFY(XXH64_update(m_hashState, drawBuffers.data(), drawBuffers.size() * sizeof(drawBuffers[0])));
|
||||||
auto readBuffer = fbo.GetReadBuffer();
|
auto readBuffer = fbo.GetReadBuffer();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &readBuffer, sizeof(FramebufferAttachmentType)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &readBuffer, sizeof(FramebufferAttachmentType)));
|
||||||
Int validDrawBufCount = 0;
|
Int validDrawBufCount = 0;
|
||||||
for (Int i = 0; i < drawBuffers.size(); ++i) {
|
for (Int i = 0; i < drawBuffers.size(); ++i) {
|
||||||
auto drawbuf = drawBuffers[i];
|
auto drawbuf = drawBuffers[i];
|
||||||
if (drawbuf != FramebufferAttachmentType::None)
|
if (drawbuf != FramebufferAttachmentType::None)
|
||||||
validDrawBufCount = std::max(validDrawBufCount, i + 1);
|
validDrawBufCount = std::max(validDrawBufCount, i + 1);
|
||||||
}
|
}
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &validDrawBufCount, sizeof(validDrawBufCount)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &validDrawBufCount, sizeof(validDrawBufCount)));
|
||||||
|
|
||||||
auto combineFramebufferAttachmentObjHash = [&](FramebufferAttachmentType attachment) {
|
auto combineFramebufferAttachmentObjHash = [&](FramebufferAttachmentType attachment) {
|
||||||
auto& att = fbo.GetAttachment(attachment);
|
auto& att = fbo.GetAttachment(attachment);
|
||||||
@@ -623,49 +623,49 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
if (att.IsEmpty()) type = 0;
|
if (att.IsEmpty()) type = 0;
|
||||||
else if (att.IsTexture()) type = 1;
|
else if (att.IsTexture()) type = 1;
|
||||||
else if (att.IsRenderbuffer()) type = 2;
|
else if (att.IsRenderbuffer()) type = 2;
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &type, sizeof(type)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &type, sizeof(type)));
|
||||||
void* contentPtr = nullptr;
|
void* contentPtr = nullptr;
|
||||||
if (att.IsTexture())
|
if (att.IsTexture())
|
||||||
contentPtr = att.GetTexture().get();
|
contentPtr = att.GetTexture().get();
|
||||||
else if (att.IsRenderbuffer())
|
else if (att.IsRenderbuffer())
|
||||||
contentPtr = att.GetRenderbuffer().get();
|
contentPtr = att.GetRenderbuffer().get();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &contentPtr, sizeof(contentPtr)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &contentPtr, sizeof(contentPtr)));
|
||||||
if (att.IsTexture()) {
|
if (att.IsTexture()) {
|
||||||
const Uint64 textureLifetimeId = att.GetTexture()->GetLifetimeId();
|
const Uint64 textureLifetimeId = att.GetTexture()->GetLifetimeId();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &textureLifetimeId, sizeof(textureLifetimeId)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &textureLifetimeId, sizeof(textureLifetimeId)));
|
||||||
const Int textureLevel = att.GetTextureLevel();
|
const Int textureLevel = att.GetTextureLevel();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &textureLevel, sizeof(textureLevel)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &textureLevel, sizeof(textureLevel)));
|
||||||
const TextureUploadTarget textureUploadTarget = att.GetTextureUploadTarget();
|
const TextureUploadTarget textureUploadTarget = att.GetTextureUploadTarget();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &textureUploadTarget, sizeof(textureUploadTarget)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &textureUploadTarget, sizeof(textureUploadTarget)));
|
||||||
const Int textureLayer = att.GetTextureLayer();
|
const Int textureLayer = att.GetTextureLayer();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &textureLayer, sizeof(textureLayer)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &textureLayer, sizeof(textureLayer)));
|
||||||
const Bool textureLayered = att.IsLayered();
|
const Bool textureLayered = att.IsLayered();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &textureLayered, sizeof(textureLayered)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &textureLayered, sizeof(textureLayered)));
|
||||||
|
|
||||||
Uint64 imageIdentity = 0;
|
Uint64 imageIdentity = 0;
|
||||||
auto* texture = att.GetTexture().get();
|
auto* texture = att.GetTexture().get();
|
||||||
auto* resource = m_textureManager.SyncTextureAndGetDescriptor(*texture);
|
auto* resource = m_textureManager.SyncTextureAndGetDescriptor(*texture);
|
||||||
if (resource != nullptr) {
|
if (resource != nullptr) {
|
||||||
imageIdentity = reinterpret_cast<Uint64>(resource->image);
|
imageIdentity = reinterpret_cast<Uint64>(resource->image);
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &resource->sampleCount, sizeof(resource->sampleCount)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &resource->sampleCount, sizeof(resource->sampleCount)));
|
||||||
} else {
|
} else {
|
||||||
const VkSampleCountFlagBits fallbackSampleCount = VK_SAMPLE_COUNT_1_BIT;
|
const VkSampleCountFlagBits fallbackSampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &fallbackSampleCount, sizeof(fallbackSampleCount)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &fallbackSampleCount, sizeof(fallbackSampleCount)));
|
||||||
}
|
}
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &imageIdentity, sizeof(imageIdentity)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &imageIdentity, sizeof(imageIdentity)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (includePendingClear && att.IsTexture()) {
|
if (includePendingClear && att.IsTexture()) {
|
||||||
auto* texture = att.GetTexture().get();
|
auto* texture = att.GetTexture().get();
|
||||||
const auto pendingClearKey = VkClearManager::MakePendingClearKey(att);
|
const auto pendingClearKey = VkClearManager::MakePendingClearKey(att);
|
||||||
auto hasClear = m_clearManager.HasPendingClear(pendingClearKey);
|
auto hasClear = m_clearManager.HasPendingClear(pendingClearKey);
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &hasClear, sizeof(hasClear)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &hasClear, sizeof(hasClear)));
|
||||||
if (hasClear) {
|
if (hasClear) {
|
||||||
ClearAttachmentPayload clearPayload{};
|
ClearAttachmentPayload clearPayload{};
|
||||||
Bool hasPayload = m_clearManager.GetPendingClear(pendingClearKey, clearPayload);
|
Bool hasPayload = m_clearManager.GetPendingClear(pendingClearKey, clearPayload);
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &hasPayload, sizeof(hasPayload)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &hasPayload, sizeof(hasPayload)));
|
||||||
if (hasPayload) {
|
if (hasPayload) {
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &clearPayload.mask, sizeof(clearPayload.mask)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &clearPayload.mask, sizeof(clearPayload.mask)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -695,7 +695,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
currentLayout = textureResource->layout;
|
currentLayout = textureResource->layout;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), ¤tLayout, sizeof(currentLayout)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, ¤tLayout, sizeof(currentLayout)));
|
||||||
}
|
}
|
||||||
if (att.IsRenderbuffer() && att.GetRenderbuffer()) {
|
if (att.IsRenderbuffer() && att.GetRenderbuffer()) {
|
||||||
const auto& renderbuffer = att.GetRenderbuffer();
|
const auto& renderbuffer = att.GetRenderbuffer();
|
||||||
@@ -703,10 +703,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const Int width = renderbuffer->GetWidth();
|
const Int width = renderbuffer->GetWidth();
|
||||||
const Int height = renderbuffer->GetHeight();
|
const Int height = renderbuffer->GetHeight();
|
||||||
const Int samples = renderbuffer->GetSamples();
|
const Int samples = renderbuffer->GetSamples();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &internalFormat, sizeof(internalFormat)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &internalFormat, sizeof(internalFormat)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &width, sizeof(width)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &width, sizeof(width)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &height, sizeof(height)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &height, sizeof(height)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &samples, sizeof(samples)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &samples, sizeof(samples)));
|
||||||
|
|
||||||
Uint64 imageIdentity = 0;
|
Uint64 imageIdentity = 0;
|
||||||
VkImageLayout currentLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
VkImageLayout currentLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
@@ -714,25 +714,25 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
if (resource != nullptr) {
|
if (resource != nullptr) {
|
||||||
imageIdentity = reinterpret_cast<Uint64>(resource->image);
|
imageIdentity = reinterpret_cast<Uint64>(resource->image);
|
||||||
currentLayout = resource->layout;
|
currentLayout = resource->layout;
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &resource->sampleCount, sizeof(resource->sampleCount)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &resource->sampleCount, sizeof(resource->sampleCount)));
|
||||||
} else {
|
} else {
|
||||||
const VkSampleCountFlagBits fallbackSampleCount = VK_SAMPLE_COUNT_1_BIT;
|
const VkSampleCountFlagBits fallbackSampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &fallbackSampleCount, sizeof(fallbackSampleCount)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &fallbackSampleCount, sizeof(fallbackSampleCount)));
|
||||||
}
|
}
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &imageIdentity, sizeof(imageIdentity)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &imageIdentity, sizeof(imageIdentity)));
|
||||||
|
|
||||||
if (includePendingClear) {
|
if (includePendingClear) {
|
||||||
const Bool hasClear = HasPendingRenderbufferClear(att);
|
const Bool hasClear = HasPendingRenderbufferClear(att);
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &hasClear, sizeof(hasClear)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &hasClear, sizeof(hasClear)));
|
||||||
if (hasClear) {
|
if (hasClear) {
|
||||||
ClearAttachmentPayload clearPayload{};
|
ClearAttachmentPayload clearPayload{};
|
||||||
const Bool hasPayload = GetPendingRenderbufferClear(renderbuffer.get(), clearPayload);
|
const Bool hasPayload = GetPendingRenderbufferClear(renderbuffer.get(), clearPayload);
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &hasPayload, sizeof(hasPayload)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &hasPayload, sizeof(hasPayload)));
|
||||||
if (hasPayload) {
|
if (hasPayload) {
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &clearPayload.mask, sizeof(clearPayload.mask)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &clearPayload.mask, sizeof(clearPayload.mask)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), ¤tLayout, sizeof(currentLayout)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, ¤tLayout, sizeof(currentLayout)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -745,13 +745,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// The depth-less default-FBO flavor omits the depth/stencil attachment
|
// The depth-less default-FBO flavor omits the depth/stencil attachment
|
||||||
// entirely, so it must hash differently from the depth-full flavor.
|
// entirely, so it must hash differently from the depth-full flavor.
|
||||||
const Bool depthStencilIncluded = !isDefaultFbo || includeDefaultFboDepthStencil;
|
const Bool depthStencilIncluded = !isDefaultFbo || includeDefaultFboDepthStencil;
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &depthStencilIncluded, sizeof(depthStencilIncluded)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &depthStencilIncluded, sizeof(depthStencilIncluded)));
|
||||||
if (depthStencilIncluded) {
|
if (depthStencilIncluded) {
|
||||||
combineFramebufferAttachmentObjHash(FramebufferAttachmentType::Depth);
|
combineFramebufferAttachmentObjHash(FramebufferAttachmentType::Depth);
|
||||||
combineFramebufferAttachmentObjHash(FramebufferAttachmentType::Stencil);
|
combineFramebufferAttachmentObjHash(FramebufferAttachmentType::Stencil);
|
||||||
}
|
}
|
||||||
|
|
||||||
return XXH64_digest(m_hashState.Get());
|
return XXH64_digest(m_hashState);
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderPassEntry& VkRenderPassManager::GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo,
|
RenderPassEntry& VkRenderPassManager::GetOrCreateRenderPass(const MG_State::GLState::FramebufferObject& fbo,
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
|
#include "MG_State/GLState/FramebufferState/FramebufferObject.h"
|
||||||
|
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
#include <MG_Util/Types.h>
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vk_mem_alloc.h>
|
#include <vk_mem_alloc.h>
|
||||||
|
|
||||||
@@ -392,7 +391,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
void DeferRenderbufferBackingRelease(RenderbufferResource& resource);
|
void DeferRenderbufferBackingRelease(RenderbufferResource& resource);
|
||||||
void CollectDeferredRenderbufferReleases(Bool destroyAll);
|
void CollectDeferredRenderbufferReleases(Bool destroyAll);
|
||||||
|
|
||||||
static inline MobileGL::XXH64State m_hashState;
|
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||||
static inline ActiveRenderPassInfo s_activeRenderPass{};
|
static inline ActiveRenderPassInfo s_activeRenderPass{};
|
||||||
static inline Bool s_hasActiveRenderPass = false;
|
static inline Bool s_hasActiveRenderPass = false;
|
||||||
static inline VkClearManager* s_clearManager = nullptr;
|
static inline VkClearManager* s_clearManager = nullptr;
|
||||||
|
|||||||
@@ -134,41 +134,41 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const MG_State::GLState::ITextureObject& texture,
|
const MG_State::GLState::ITextureObject& texture,
|
||||||
Bool forceNearestFiltering, Bool singleLevelView) const {
|
Bool forceNearestFiltering, Bool singleLevelView) const {
|
||||||
MOBILEGL_ASSERT(m_config != nullptr, "VkSamplerManager::BuildSamplerKey: m_config is null");
|
MOBILEGL_ASSERT(m_config != nullptr, "VkSamplerManager::BuildSamplerKey: m_config is null");
|
||||||
XXHASH_VERIFY(XXH64_reset(m_hashState.Get(), m_config->CacheVersion));
|
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config->CacheVersion));
|
||||||
|
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &forceNearestFiltering, sizeof(forceNearestFiltering)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &forceNearestFiltering, sizeof(forceNearestFiltering)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &singleLevelView, sizeof(singleLevelView)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &singleLevelView, sizeof(singleLevelView)));
|
||||||
|
|
||||||
const auto minFilter = sampler.GetMinFilter();
|
const auto minFilter = sampler.GetMinFilter();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &minFilter, sizeof(minFilter)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &minFilter, sizeof(minFilter)));
|
||||||
const auto magFilter = sampler.GetMagFilter();
|
const auto magFilter = sampler.GetMagFilter();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &magFilter, sizeof(magFilter)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &magFilter, sizeof(magFilter)));
|
||||||
const auto mipmapMode = sampler.GetMipmapMode();
|
const auto mipmapMode = sampler.GetMipmapMode();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &mipmapMode, sizeof(mipmapMode)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &mipmapMode, sizeof(mipmapMode)));
|
||||||
const auto wrapS = sampler.GetWrapS();
|
const auto wrapS = sampler.GetWrapS();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &wrapS, sizeof(wrapS)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &wrapS, sizeof(wrapS)));
|
||||||
const auto wrapT = sampler.GetWrapT();
|
const auto wrapT = sampler.GetWrapT();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &wrapT, sizeof(wrapT)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &wrapT, sizeof(wrapT)));
|
||||||
const auto wrapR = sampler.GetWrapR();
|
const auto wrapR = sampler.GetWrapR();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &wrapR, sizeof(wrapR)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &wrapR, sizeof(wrapR)));
|
||||||
const auto maxLod = ResolveSingleLevelMaxLod(sampler, singleLevelView);
|
const auto maxLod = ResolveSingleLevelMaxLod(sampler, singleLevelView);
|
||||||
const auto minLod = ResolveEffectiveMinLod(sampler, maxLod);
|
const auto minLod = ResolveEffectiveMinLod(sampler, maxLod);
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &minLod, sizeof(minLod)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &minLod, sizeof(minLod)));
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &maxLod, sizeof(maxLod)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &maxLod, sizeof(maxLod)));
|
||||||
const auto lodBias = sampler.GetLodBias();
|
const auto lodBias = sampler.GetLodBias();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &lodBias, sizeof(lodBias)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &lodBias, sizeof(lodBias)));
|
||||||
// The RESOLVED value, not the GL request: samplers that only differ in an anisotropy Vulkan
|
// 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
|
// 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.
|
// VkSampler, while two samplers that really do differ must not collide onto the first one's.
|
||||||
const auto maxAnisotropy = ResolveEffectiveMaxAnisotropy(sampler, forceNearestFiltering);
|
const auto maxAnisotropy = ResolveEffectiveMaxAnisotropy(sampler, forceNearestFiltering);
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &maxAnisotropy, sizeof(maxAnisotropy)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &maxAnisotropy, sizeof(maxAnisotropy)));
|
||||||
const auto compareMode = sampler.GetCompareMode();
|
const auto compareMode = sampler.GetCompareMode();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &compareMode, sizeof(compareMode)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &compareMode, sizeof(compareMode)));
|
||||||
const auto compareFunc = sampler.GetSamplerCompareFunc();
|
const auto compareFunc = sampler.GetSamplerCompareFunc();
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &compareFunc, sizeof(compareFunc)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &compareFunc, sizeof(compareFunc)));
|
||||||
const auto borderColor = ResolveVkBorderColor(sampler, texture);
|
const auto borderColor = ResolveVkBorderColor(sampler, texture);
|
||||||
XXHASH_VERIFY(XXH64_update(m_hashState.Get(), &borderColor, sizeof(borderColor)));
|
XXHASH_VERIFY(XXH64_update(m_hashState, &borderColor, sizeof(borderColor)));
|
||||||
return XXH64_digest(m_hashState.Get());
|
return XXH64_digest(m_hashState);
|
||||||
}
|
}
|
||||||
|
|
||||||
VkSampler VkSamplerManager::GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler,
|
VkSampler VkSamplerManager::GetOrCreateSampler(const MG_State::GLState::SamplerObject& sampler,
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
#include "../VkIncludes.h"
|
#include "../VkIncludes.h"
|
||||||
#include "../VulkanRendererConfig.h"
|
#include "../VulkanRendererConfig.h"
|
||||||
#include <Includes.h>
|
#include <Includes.h>
|
||||||
#include <MG_Util/Types.h>
|
|
||||||
#include <MG_State/GLState/SamplerState/SamplerObject.h>
|
#include <MG_State/GLState/SamplerState/SamplerObject.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_State::GLState {
|
namespace MobileGL::MG_State::GLState {
|
||||||
@@ -86,6 +85,6 @@ private:
|
|||||||
UnorderedMap<Uint64, SamplerCacheEntry> m_samplers;
|
UnorderedMap<Uint64, SamplerCacheEntry> m_samplers;
|
||||||
// Monotonic frame-boundary counter (bumped in OnFrameBoundary) for cache aging.
|
// Monotonic frame-boundary counter (bumped in OnFrameBoundary) for cache aging.
|
||||||
Uint64 m_frameBoundaryCounter = 0;
|
Uint64 m_frameBoundaryCounter = 0;
|
||||||
static inline MobileGL::XXH64State m_hashState;
|
static inline XXH64_state_t* m_hashState = XXH64_createState();
|
||||||
};
|
};
|
||||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||||
|
|||||||
@@ -1994,14 +1994,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// Bound the idle pool: a one-off giant upload (initial atlas define)
|
// Bound the idle pool: a one-off giant upload (initial atlas define)
|
||||||
// must not pin its staging memory forever.
|
// must not pin its staging memory forever.
|
||||||
constexpr VkDeviceSize kMaxFreeUploadStagingBytes = 32u * 1024u * 1024u;
|
constexpr VkDeviceSize kMaxFreeUploadStagingBytes = 32u * 1024u * 1024u;
|
||||||
if (m_allocator == nullptr) {
|
if (m_allocator == nullptr || m_freeUploadStagingBytes + block.capacity > kMaxFreeUploadStagingBytes) {
|
||||||
// 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);
|
vmaDestroyBuffer(m_allocator, block.buffer, block.allocation);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1480,7 +1480,8 @@ void main() {
|
|||||||
const char* label = nullptr;
|
const char* label = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
static Uint32 ComputeMaxProgramBindings(const VkPhysicalDeviceProperties& properties) {
|
static Uint32 ComputeMaxProgramBindings(const VkPhysicalDeviceProperties& properties,
|
||||||
|
const ProgramFactory::UpdateAfterBindLimits& updateAfterBindLimits) {
|
||||||
const auto& limits = properties.limits;
|
const auto& limits = properties.limits;
|
||||||
static constexpr Uint32 kMinProgramBindings = 16;
|
static constexpr Uint32 kMinProgramBindings = 16;
|
||||||
static constexpr Uint32 kMaxProgramBindingsCap = 256;
|
static constexpr Uint32 kMaxProgramBindingsCap = 256;
|
||||||
@@ -1495,6 +1496,21 @@ void main() {
|
|||||||
maxBindings = std::min(maxBindings, maxCombinedImageSamplers);
|
maxBindings = std::min(maxBindings, maxCombinedImageSamplers);
|
||||||
maxBindings = std::min(maxBindings, maxSampledImages + maxDynamicUniformBuffers);
|
maxBindings = std::min(maxBindings, maxSampledImages + maxDynamicUniformBuffers);
|
||||||
|
|
||||||
|
if (updateAfterBindLimits.enabled) {
|
||||||
|
const Uint32 updateAfterBindSamplers = std::min(updateAfterBindLimits.maxPerStageSamplers,
|
||||||
|
updateAfterBindLimits.maxSetSamplers);
|
||||||
|
const Uint32 updateAfterBindSampledImages = std::min(updateAfterBindLimits.maxPerStageSampledImages,
|
||||||
|
updateAfterBindLimits.maxSetSampledImages);
|
||||||
|
const Uint32 updateAfterBindDynamicUniformBuffers =
|
||||||
|
std::min(updateAfterBindLimits.maxPerStageUniformBuffers,
|
||||||
|
updateAfterBindLimits.maxSetUniformBuffersDynamic);
|
||||||
|
Uint32 updateAfterBindBindings = updateAfterBindLimits.maxPerStageResources;
|
||||||
|
updateAfterBindBindings = std::min(updateAfterBindBindings, updateAfterBindSamplers);
|
||||||
|
updateAfterBindBindings =
|
||||||
|
std::min(updateAfterBindBindings, updateAfterBindSampledImages + updateAfterBindDynamicUniformBuffers);
|
||||||
|
maxBindings = std::max(maxBindings, updateAfterBindBindings);
|
||||||
|
}
|
||||||
|
|
||||||
maxBindings = std::max(kMinProgramBindings, maxBindings);
|
maxBindings = std::max(kMinProgramBindings, maxBindings);
|
||||||
maxBindings = std::min(kMaxProgramBindingsCap, maxBindings);
|
maxBindings = std::min(kMaxProgramBindingsCap, maxBindings);
|
||||||
return maxBindings;
|
return maxBindings;
|
||||||
@@ -3010,7 +3026,7 @@ void main() {
|
|||||||
succeeded = m_renderPassManager->Initialize();
|
succeeded = m_renderPassManager->Initialize();
|
||||||
MOBILEGL_ASSERT(succeeded, "VkRenderPassManager initialization failed.");
|
MOBILEGL_ASSERT(succeeded, "VkRenderPassManager initialization failed.");
|
||||||
|
|
||||||
const Uint32 maxProgramBindings = ComputeMaxProgramBindings(m_physicalDevice.properties);
|
const Uint32 maxProgramBindings = ComputeMaxProgramBindings(m_physicalDevice.properties, m_updateAfterBindLimits);
|
||||||
MGLOG_I("DirectVulkan: using %u program descriptor bindings", maxProgramBindings);
|
MGLOG_I("DirectVulkan: using %u program descriptor bindings", maxProgramBindings);
|
||||||
if (IsPowerVRDevice(m_physicalDevice.properties)) {
|
if (IsPowerVRDevice(m_physicalDevice.properties)) {
|
||||||
m_config.DisablePipelineCache = true;
|
m_config.DisablePipelineCache = true;
|
||||||
@@ -3044,7 +3060,9 @@ void main() {
|
|||||||
}
|
}
|
||||||
m_programFactory = MakeUnique<ProgramFactory>(m_device, m_config, maxProgramBindings,
|
m_programFactory = MakeUnique<ProgramFactory>(m_device, m_config, maxProgramBindings,
|
||||||
m_shaderDrawParametersFeatureEnabled,
|
m_shaderDrawParametersFeatureEnabled,
|
||||||
m_unformattedFloatStorageImagesEnabled);
|
m_unformattedFloatStorageImagesEnabled,
|
||||||
|
MG_Config::Features.EnableSpirvValidation,
|
||||||
|
m_updateAfterBindLimits);
|
||||||
MOBILEGL_ASSERT(m_programFactory != nullptr, "ProgramFactory creation failed.");
|
MOBILEGL_ASSERT(m_programFactory != nullptr, "ProgramFactory creation failed.");
|
||||||
// The swapchain already exists at this point (Initialize creates it first), so seed the
|
// The swapchain already exists at this point (Initialize creates it first), so seed the
|
||||||
// height the factory could not be told about from CreateSwapchain.
|
// height the factory could not be told about from CreateSwapchain.
|
||||||
@@ -11899,6 +11917,17 @@ void main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanRenderer::CreateInstance() {
|
void VulkanRenderer::CreateInstance() {
|
||||||
|
#if defined(VK_USE_PLATFORM_METAL_EXT)
|
||||||
|
// MoltenVK snapshots its configuration when the loader first discovers the ICD. Set
|
||||||
|
// this before instance-extension enumeration, while preserving an explicit user value.
|
||||||
|
if (std::getenv("MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS") == nullptr) {
|
||||||
|
if (::setenv("MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS", "1", 0) == 0) {
|
||||||
|
MGLOG_I("MoltenVK: enabling Metal argument buffers");
|
||||||
|
} else {
|
||||||
|
MGLOG_W("MoltenVK: could not enable Metal argument buffers before ICD discovery");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
m_extensions = EnumerateInstanceExtensions();
|
m_extensions = EnumerateInstanceExtensions();
|
||||||
MGLOG_I("Got %d Vulkan instance extensions: ", m_extensions.size());
|
MGLOG_I("Got %d Vulkan instance extensions: ", m_extensions.size());
|
||||||
for (auto& extension : m_extensions) {
|
for (auto& extension : m_extensions) {
|
||||||
@@ -12040,17 +12069,19 @@ void main() {
|
|||||||
|
|
||||||
auto debugMessengerCreateInfo = PopulateDebugMessengerCreateInfo();
|
auto debugMessengerCreateInfo = PopulateDebugMessengerCreateInfo();
|
||||||
// Layers
|
// Layers
|
||||||
|
const void* instanceCreatePNext = nullptr;
|
||||||
if (m_validationLayersEnabled) {
|
if (m_validationLayersEnabled) {
|
||||||
MGLOG_I("Enabling validation layer...");
|
MGLOG_I("Enabling validation layer...");
|
||||||
instanceInfo.enabledLayerCount = static_cast<uint32_t>(std::size(s_validationLayerNames));
|
instanceInfo.enabledLayerCount = static_cast<uint32_t>(std::size(s_validationLayerNames));
|
||||||
instanceInfo.ppEnabledLayerNames = s_validationLayerNames;
|
instanceInfo.ppEnabledLayerNames = s_validationLayerNames;
|
||||||
// Chaining the messenger create-info is only legal with the extension on.
|
// Chaining the messenger create-info is only legal with the extension on.
|
||||||
instanceInfo.pNext = debugUtilsAvailable ? &debugMessengerCreateInfo : nullptr;
|
instanceCreatePNext = debugUtilsAvailable ? &debugMessengerCreateInfo : nullptr;
|
||||||
} else {
|
} else {
|
||||||
instanceInfo.enabledLayerCount = 0;
|
instanceInfo.enabledLayerCount = 0;
|
||||||
instanceInfo.pNext = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
instanceInfo.pNext = instanceCreatePNext;
|
||||||
|
|
||||||
VK_VERIFY(vkCreateInstance(&instanceInfo, nullptr, &m_instance), "vkCreateInstance failed");
|
VK_VERIFY(vkCreateInstance(&instanceInfo, nullptr, &m_instance), "vkCreateInstance failed");
|
||||||
|
|
||||||
if (debugUtilsAvailable) {
|
if (debugUtilsAvailable) {
|
||||||
@@ -12476,6 +12507,75 @@ void main() {
|
|||||||
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceFeatures2KHR"));
|
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceFeatures2KHR"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_updateAfterBindLimits = {};
|
||||||
|
VkPhysicalDeviceDescriptorIndexingFeatures descriptorIndexingFeatures{};
|
||||||
|
descriptorIndexingFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES;
|
||||||
|
VkPhysicalDeviceDescriptorIndexingProperties descriptorIndexingProperties{};
|
||||||
|
descriptorIndexingProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_PROPERTIES;
|
||||||
|
const Bool descriptorIndexingCore = m_physicalDevice.properties.apiVersion >= VK_API_VERSION_1_2;
|
||||||
|
const Bool descriptorIndexingExtension =
|
||||||
|
IsExtensionSupported(availableExtensions, VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME);
|
||||||
|
auto getPhysicalDeviceProperties2 = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2>(
|
||||||
|
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceProperties2"));
|
||||||
|
if (getPhysicalDeviceProperties2 == nullptr) {
|
||||||
|
getPhysicalDeviceProperties2 = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2>(
|
||||||
|
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceProperties2KHR"));
|
||||||
|
}
|
||||||
|
if ((descriptorIndexingCore || descriptorIndexingExtension) && getPhysicalDeviceFeatures2 != nullptr &&
|
||||||
|
getPhysicalDeviceProperties2 != nullptr) {
|
||||||
|
VkPhysicalDeviceFeatures2 featureQuery{};
|
||||||
|
featureQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
||||||
|
featureQuery.pNext = &descriptorIndexingFeatures;
|
||||||
|
getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery);
|
||||||
|
VkPhysicalDeviceProperties2 propertyQuery{};
|
||||||
|
propertyQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
|
||||||
|
propertyQuery.pNext = &descriptorIndexingProperties;
|
||||||
|
getPhysicalDeviceProperties2(m_physicalDevice.handle, &propertyQuery);
|
||||||
|
|
||||||
|
// This renderer emits every descriptor category listed below, including
|
||||||
|
// dynamic UBOs and combined image samplers. Do not enable a partial
|
||||||
|
// descriptor-indexing contract: it would make a later reflected program
|
||||||
|
// fail in the driver instead of choosing its ordinary descriptor layout.
|
||||||
|
const Bool allUpdateAfterBindFeatures =
|
||||||
|
descriptorIndexingFeatures.descriptorBindingUniformBufferUpdateAfterBind == VK_TRUE &&
|
||||||
|
descriptorIndexingFeatures.descriptorBindingSampledImageUpdateAfterBind == VK_TRUE &&
|
||||||
|
descriptorIndexingFeatures.descriptorBindingStorageImageUpdateAfterBind == VK_TRUE &&
|
||||||
|
descriptorIndexingFeatures.descriptorBindingStorageBufferUpdateAfterBind == VK_TRUE &&
|
||||||
|
descriptorIndexingFeatures.descriptorBindingUniformTexelBufferUpdateAfterBind == VK_TRUE &&
|
||||||
|
descriptorIndexingFeatures.descriptorBindingStorageTexelBufferUpdateAfterBind == VK_TRUE &&
|
||||||
|
(!deviceFeatures.robustBufferAccess || descriptorIndexingProperties.robustBufferAccessUpdateAfterBind);
|
||||||
|
if (allUpdateAfterBindFeatures) {
|
||||||
|
if (!descriptorIndexingCore && !IsExtensionAlreadyEnabled(
|
||||||
|
enabledDeviceExtensions,
|
||||||
|
VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME)) {
|
||||||
|
enabledDeviceExtensions.push_back(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME);
|
||||||
|
}
|
||||||
|
descriptorIndexingFeatures.pNext = const_cast<void*>(deviceCreateInfo.pNext);
|
||||||
|
deviceCreateInfo.pNext = &descriptorIndexingFeatures;
|
||||||
|
m_updateAfterBindLimits = {
|
||||||
|
true,
|
||||||
|
descriptorIndexingProperties.maxPerStageDescriptorUpdateAfterBindSamplers,
|
||||||
|
descriptorIndexingProperties.maxPerStageDescriptorUpdateAfterBindUniformBuffers,
|
||||||
|
descriptorIndexingProperties.maxPerStageDescriptorUpdateAfterBindStorageBuffers,
|
||||||
|
descriptorIndexingProperties.maxPerStageDescriptorUpdateAfterBindSampledImages,
|
||||||
|
descriptorIndexingProperties.maxPerStageDescriptorUpdateAfterBindStorageImages,
|
||||||
|
descriptorIndexingProperties.maxPerStageUpdateAfterBindResources,
|
||||||
|
descriptorIndexingProperties.maxDescriptorSetUpdateAfterBindSamplers,
|
||||||
|
descriptorIndexingProperties.maxDescriptorSetUpdateAfterBindUniformBuffers,
|
||||||
|
descriptorIndexingProperties.maxDescriptorSetUpdateAfterBindUniformBuffersDynamic,
|
||||||
|
descriptorIndexingProperties.maxDescriptorSetUpdateAfterBindStorageBuffers,
|
||||||
|
descriptorIndexingProperties.maxDescriptorSetUpdateAfterBindStorageBuffersDynamic,
|
||||||
|
descriptorIndexingProperties.maxDescriptorSetUpdateAfterBindSampledImages,
|
||||||
|
descriptorIndexingProperties.maxDescriptorSetUpdateAfterBindStorageImages};
|
||||||
|
MGLOG_I("Vulkan: update-after-bind descriptor layouts enabled");
|
||||||
|
} else {
|
||||||
|
MGLOG_I("Vulkan: descriptor indexing is present but lacks the complete update-after-bind feature set; "
|
||||||
|
"using ordinary descriptor layouts");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MGLOG_I("Vulkan: descriptor indexing unavailable; using ordinary descriptor layouts");
|
||||||
|
}
|
||||||
|
|
||||||
VkPhysicalDeviceIndexTypeUint8Features indexTypeUint8Features{};
|
VkPhysicalDeviceIndexTypeUint8Features indexTypeUint8Features{};
|
||||||
indexTypeUint8Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES;
|
indexTypeUint8Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES;
|
||||||
if (indexTypeUint8ExtensionName != nullptr) {
|
if (indexTypeUint8ExtensionName != nullptr) {
|
||||||
|
|||||||
@@ -555,6 +555,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Bool m_shaderDrawParametersExtensionEnabled = false;
|
Bool m_shaderDrawParametersExtensionEnabled = false;
|
||||||
Bool m_shaderDrawParametersFeatureEnabled = false;
|
Bool m_shaderDrawParametersFeatureEnabled = false;
|
||||||
Bool m_unformattedFloatStorageImagesEnabled = false;
|
Bool m_unformattedFloatStorageImagesEnabled = false;
|
||||||
|
// Set only after descriptor-indexing feature AND property queries prove that
|
||||||
|
// update-after-bind is legal for every descriptor category this renderer emits.
|
||||||
|
ProgramFactory::UpdateAfterBindLimits m_updateAfterBindLimits{};
|
||||||
// fillModeNonSolid gates VK_POLYGON_MODE_LINE/_POINT (glPolygonMode); independentBlend gates
|
// fillModeNonSolid gates VK_POLYGON_MODE_LINE/_POINT (glPolygonMode); independentBlend gates
|
||||||
// per-draw-buffer color write masks (glColorMaski). Both are cached at device creation and
|
// per-draw-buffer color write masks (glColorMaski). Both are cached at device creation and
|
||||||
// drive a runtime fallback when the device lacks them.
|
// drive a runtime fallback when the device lacks them.
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
||||||
#include <MG_Util/Converters/GLToMG/BufferEnumConverter.h>
|
#include <MG_Util/Converters/GLToMG/BufferEnumConverter.h>
|
||||||
#include <MG_Util/Converters/MGToGL/BufferEnumConverter.h>
|
#include <MG_Util/Converters/MGToGL/BufferEnumConverter.h>
|
||||||
|
#include <MG_Util/Texture/PixelStoreProcessor.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl {
|
namespace MobileGL::MG_Impl::GLImpl {
|
||||||
namespace {
|
namespace {
|
||||||
@@ -31,6 +32,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
NamedBufferData,
|
NamedBufferData,
|
||||||
NamedBufferSubData,
|
NamedBufferSubData,
|
||||||
CopyNamedBufferSubData,
|
CopyNamedBufferSubData,
|
||||||
|
ClearBufferData,
|
||||||
|
ClearBufferSubData,
|
||||||
ClearNamedBufferData,
|
ClearNamedBufferData,
|
||||||
ClearNamedBufferSubData,
|
ClearNamedBufferSubData,
|
||||||
MapBufferRange,
|
MapBufferRange,
|
||||||
@@ -65,6 +68,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return "NamedBufferSubData";
|
return "NamedBufferSubData";
|
||||||
case BufferOp::CopyNamedBufferSubData:
|
case BufferOp::CopyNamedBufferSubData:
|
||||||
return "CopyNamedBufferSubData";
|
return "CopyNamedBufferSubData";
|
||||||
|
case BufferOp::ClearBufferData:
|
||||||
|
return "ClearBufferData";
|
||||||
|
case BufferOp::ClearBufferSubData:
|
||||||
|
return "ClearBufferSubData";
|
||||||
case BufferOp::ClearNamedBufferData:
|
case BufferOp::ClearNamedBufferData:
|
||||||
return "ClearNamedBufferData";
|
return "ClearNamedBufferData";
|
||||||
case BufferOp::ClearNamedBufferSubData:
|
case BufferOp::ClearNamedBufferSubData:
|
||||||
@@ -143,16 +150,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The pattern is replicated verbatim, which is only the whole story while the client
|
|
||||||
// layout already matches the internal format - the case every entry point in practice
|
|
||||||
// uses, and the only one the conversion machinery here can express. Say so rather than
|
|
||||||
// quietly writing a differently-sized pattern.
|
|
||||||
const SizeT sourceSize = MG_Util::GetInputBytesPerPixel(inputFormat, pixelType);
|
|
||||||
if (sourceSize != elementSize) {
|
|
||||||
MGLOG_W_ONCE("%s: clear pattern is %zu bytes but internalformat 0x%X stores %zu; "
|
|
||||||
"converting between them is not implemented",
|
|
||||||
GetBufferOpName(op), sourceSize, internalformat, elementSize);
|
|
||||||
}
|
|
||||||
return elementSize;
|
return elementSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,27 +191,59 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClearNamedBufferRange_State(GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size,
|
Bool BuildClearPattern(GLenum internalformat, GLenum format, GLenum type, const void* data,
|
||||||
|
SizeT patternSize, BufferOp op, Vector<Uint8>& pattern) {
|
||||||
|
const TextureInternalFormat internal = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat);
|
||||||
|
const TextureInputFormat inputFormat = MG_Util::ConvertGLEnumToTextureInputFormat(format);
|
||||||
|
const TexturePixelDataType inputType = MG_Util::ConvertGLEnumToTexturePixelDataType(type);
|
||||||
|
|
||||||
|
Vector<Uint8> zeroInput;
|
||||||
|
const void* inputPixel = data;
|
||||||
|
if (inputPixel == nullptr) {
|
||||||
|
const SizeT inputSize = MG_Util::GetInputBytesPerPixel(inputFormat, inputType);
|
||||||
|
if (inputSize == 0) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", GetBufferOpName(op),
|
||||||
|
"format and type do not describe a source pixel."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
zeroInput.resize(inputSize);
|
||||||
|
inputPixel = zeroInput.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!MG_Util::PixelStoreProcessor::ConvertOnePixelToInternal(
|
||||||
|
internal, inputFormat, inputType, inputPixel, pattern)) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", GetBufferOpName(op),
|
||||||
|
std::format("Cannot convert one ({}, {}) pixel into internalformat 0x{:X}.",
|
||||||
|
MG_Util::ConvertGLEnumToString(format), MG_Util::ConvertGLEnumToString(type),
|
||||||
|
internalformat)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data == nullptr) {
|
||||||
|
// GL defines a null clear value as all zero bits in the destination store, while
|
||||||
|
// retaining the format/type validation above.
|
||||||
|
pattern.assign(patternSize, 0);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClearBufferRange_State(const SharedPtr<MG_State::GLState::BufferObject>& bufferObject,
|
||||||
|
GLenum internalformat, GLintptr offset, GLsizeiptr size,
|
||||||
GLenum format, GLenum type, const void* data, BufferOp op) {
|
GLenum format, GLenum type, const void* data, BufferOp op) {
|
||||||
const SizeT patternSize = GetClearPatternSize(internalformat, format, type, op);
|
const SizeT patternSize = GetClearPatternSize(internalformat, format, type, op);
|
||||||
if (patternSize == 0) return;
|
if (patternSize == 0) return;
|
||||||
|
|
||||||
auto bufferObject = GetNamedBufferObject(buffer, op);
|
|
||||||
if (!bufferObject) return;
|
|
||||||
if (!ValidateBufferClearRange(bufferObject, offset, size, patternSize, op)) return;
|
if (!ValidateBufferClearRange(bufferObject, offset, size, patternSize, op)) return;
|
||||||
if (size == 0) return;
|
if (size == 0) return;
|
||||||
|
|
||||||
Vector<Uint8> clearData(static_cast<SizeT>(size));
|
Vector<Uint8> pattern;
|
||||||
if (data) {
|
if (!BuildClearPattern(internalformat, format, type, data, patternSize, op, pattern)) return;
|
||||||
const auto* pattern = static_cast<const Uint8*>(data);
|
bufferObject->FillSubData({pattern.data(), pattern.size()}, static_cast<SizeT>(offset),
|
||||||
for (SizeT at = 0; at < clearData.size(); at += patternSize) {
|
static_cast<SizeT>(size));
|
||||||
Memcpy(clearData.data() + at, pattern, patternSize);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Memset(clearData.data(), 0, clearData.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
bufferObject->UploadSubData({clearData.data(), clearData.size()}, static_cast<SizeT>(offset));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& GetBufferBindingSlot(BufferTarget target) {
|
auto& GetBufferBindingSlot(BufferTarget target) {
|
||||||
@@ -1197,16 +1226,33 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
static_cast<SizeT>(writeOffset), static_cast<SizeT>(size));
|
static_cast<SizeT>(writeOffset), static_cast<SizeT>(size));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ClearBufferData_State(GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) {
|
||||||
|
auto bufferObject = GetBoundBufferObject(target, BufferOp::ClearBufferData);
|
||||||
|
if (!bufferObject) return;
|
||||||
|
ClearBufferRange_State(bufferObject, internalformat, 0, static_cast<GLsizeiptr>(bufferObject->GetSize()), format,
|
||||||
|
type, data, BufferOp::ClearBufferData);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClearBufferSubData_State(GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size,
|
||||||
|
GLenum format, GLenum type, const void* data) {
|
||||||
|
auto bufferObject = GetBoundBufferObject(target, BufferOp::ClearBufferSubData);
|
||||||
|
if (!bufferObject) return;
|
||||||
|
ClearBufferRange_State(bufferObject, internalformat, offset, size, format, type, data,
|
||||||
|
BufferOp::ClearBufferSubData);
|
||||||
|
}
|
||||||
|
|
||||||
void ClearNamedBufferData_State(GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void* data) {
|
void ClearNamedBufferData_State(GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void* data) {
|
||||||
auto bufferObject = GetNamedBufferObject(buffer, BufferOp::ClearNamedBufferData);
|
auto bufferObject = GetNamedBufferObject(buffer, BufferOp::ClearNamedBufferData);
|
||||||
if (!bufferObject) return;
|
if (!bufferObject) return;
|
||||||
ClearNamedBufferRange_State(buffer, internalformat, 0, static_cast<GLsizeiptr>(bufferObject->GetSize()), format,
|
ClearBufferRange_State(bufferObject, internalformat, 0, static_cast<GLsizeiptr>(bufferObject->GetSize()), format,
|
||||||
type, data, BufferOp::ClearNamedBufferData);
|
type, data, BufferOp::ClearNamedBufferData);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClearNamedBufferSubData_State(GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size,
|
void ClearNamedBufferSubData_State(GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size,
|
||||||
GLenum format, GLenum type, const void* data) {
|
GLenum format, GLenum type, const void* data) {
|
||||||
ClearNamedBufferRange_State(buffer, internalformat, offset, size, format, type, data,
|
auto bufferObject = GetNamedBufferObject(buffer, BufferOp::ClearNamedBufferSubData);
|
||||||
|
if (!bufferObject) return;
|
||||||
|
ClearBufferRange_State(bufferObject, internalformat, offset, size, format, type, data,
|
||||||
BufferOp::ClearNamedBufferSubData);
|
BufferOp::ClearNamedBufferSubData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1662,6 +1708,15 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
CopyNamedBufferSubData_State(readBuffer, writeBuffer, readOffset, writeOffset, size);
|
CopyNamedBufferSubData_State(readBuffer, writeBuffer, readOffset, writeOffset, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ClearBufferData(GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) {
|
||||||
|
ClearBufferData_State(target, internalformat, format, type, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClearBufferSubData(GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format,
|
||||||
|
GLenum type, const void* data) {
|
||||||
|
ClearBufferSubData_State(target, internalformat, offset, size, format, type, data);
|
||||||
|
}
|
||||||
|
|
||||||
void ClearNamedBufferData(GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void* data) {
|
void ClearNamedBufferData(GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void* data) {
|
||||||
ClearNamedBufferData_State(buffer, internalformat, format, type, data);
|
ClearNamedBufferData_State(buffer, internalformat, format, type, data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void NamedBufferSubData(GLuint buffer, GLintptr offset, GLsizeiptr size, const void* data);
|
void NamedBufferSubData(GLuint buffer, GLintptr offset, GLsizeiptr size, const void* data);
|
||||||
void CopyNamedBufferSubData(GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset,
|
void CopyNamedBufferSubData(GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset,
|
||||||
GLsizeiptr size);
|
GLsizeiptr size);
|
||||||
|
void ClearBufferData(GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data);
|
||||||
|
void ClearBufferSubData(GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format,
|
||||||
|
GLenum type, const void* data);
|
||||||
void ClearNamedBufferData(GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void* data);
|
void ClearNamedBufferData(GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void* data);
|
||||||
void ClearNamedBufferSubData(GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format,
|
void ClearNamedBufferSubData(GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format,
|
||||||
GLenum type, const void* data);
|
GLenum type, const void* data);
|
||||||
|
|||||||
@@ -985,8 +985,8 @@ DECLARE_GL_FUNCTION_HEAD(void, DrawElementsInstancedBaseVertexBaseInstance, GLen
|
|||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetActiveAtomicCounterBufferiv, GLuint program, GLuint bufferIndex, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetActiveAtomicCounterBufferiv, program, bufferIndex, pname, params)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetActiveAtomicCounterBufferiv, GLuint program, GLuint bufferIndex, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetActiveAtomicCounterBufferiv, program, bufferIndex, pname, params)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, DrawTransformFeedbackInstanced, GLenum mode, GLuint id, GLsizei instancecount) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawTransformFeedbackInstanced, mode, id, instancecount)
|
DECLARE_GL_FUNCTION_HEAD(void, DrawTransformFeedbackInstanced, GLenum mode, GLuint id, GLsizei instancecount) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawTransformFeedbackInstanced, mode, id, instancecount)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, DrawTransformFeedbackStreamInstanced, GLenum mode, GLuint id, GLuint stream, GLsizei instancecount) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawTransformFeedbackStreamInstanced, mode, id, stream, instancecount)
|
DECLARE_GL_FUNCTION_HEAD(void, DrawTransformFeedbackStreamInstanced, GLenum mode, GLuint id, GLuint stream, GLsizei instancecount) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawTransformFeedbackStreamInstanced, mode, id, stream, instancecount)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearBufferData, GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearBufferData, target, internalformat, format, type, data)
|
DECLARE_GL_FUNCTION_HEAD(void, ClearBufferData, GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearBufferData, target, internalformat, format, type, data)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearBufferSubData, GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearBufferSubData, target, internalformat, offset, size, format, type, data)
|
DECLARE_GL_FUNCTION_HEAD(void, ClearBufferSubData, GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearBufferSubData, target, internalformat, offset, size, format, type, data)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetInternalformati64v, GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint64* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetInternalformati64v, target, internalformat, pname, count, params)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetInternalformati64v, GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint64* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetInternalformati64v, target, internalformat, pname, count, params)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, InvalidateTexSubImage, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, InvalidateTexSubImage, texture, level, xoffset, yoffset, zoffset, width, height, depth)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, InvalidateTexSubImage, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, InvalidateTexSubImage, texture, level, xoffset, yoffset, zoffset, width, height, depth)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, InvalidateTexImage, GLuint texture, GLint level) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, InvalidateTexImage, texture, level)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, InvalidateTexImage, GLuint texture, GLint level) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, InvalidateTexImage, texture, level)
|
||||||
|
|||||||
@@ -344,41 +344,6 @@ 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) {
|
GLboolean IsQuery(GLuint id) {
|
||||||
if (id == 0) {
|
if (id == 0) {
|
||||||
return GL_FALSE;
|
return GL_FALSE;
|
||||||
|
|||||||
@@ -13,15 +13,6 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void GenQueries(GLsizei n, GLuint* ids);
|
void GenQueries(GLsizei n, GLuint* ids);
|
||||||
void CreateQueries(GLenum target, GLsizei n, GLuint* ids);
|
void CreateQueries(GLenum target, GLsizei n, GLuint* ids);
|
||||||
void DeleteQueries(GLsizei n, const 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);
|
GLboolean IsQuery(GLuint id);
|
||||||
void BeginQuery(GLenum target, GLuint id);
|
void BeginQuery(GLenum target, GLuint id);
|
||||||
void EndQuery(GLenum target);
|
void EndQuery(GLenum target);
|
||||||
|
|||||||
@@ -14,29 +14,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
// Frontend sync object: wraps an optional backend fence handle. A null
|
// Frontend sync object: wraps an optional backend fence handle. A null
|
||||||
// backend handle (backend has no fence support, or could not create a
|
// backend handle (backend has no fence support, or could not create a
|
||||||
// fence at call time) keeps the legacy always-signaled behavior.
|
// 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 {
|
struct SyncObject {
|
||||||
std::mutex mutex;
|
|
||||||
MG_Backend::BackendSyncHandle backendHandle = nullptr;
|
MG_Backend::BackendSyncHandle backendHandle = nullptr;
|
||||||
GLenum condition = GL_SYNC_GPU_COMMANDS_COMPLETE;
|
GLenum condition = GL_SYNC_GPU_COMMANDS_COMPLETE;
|
||||||
GLbitfield flags = 0;
|
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
|
// Sync calls may arrive from any thread (launchers migrate the context
|
||||||
@@ -44,9 +25,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
// Entries left at process shutdown are simply dropped; their backend
|
// Entries left at process shutdown are simply dropped; their backend
|
||||||
// handles die with the backend.
|
// handles die with the backend.
|
||||||
std::mutex g_syncObjectsMutex;
|
std::mutex g_syncObjectsMutex;
|
||||||
UnorderedMap<GLsync, SharedPtr<SyncObject>> g_liveSyncObjects;
|
UnorderedMap<GLsync, SyncObject*> g_liveSyncObjects;
|
||||||
|
|
||||||
SharedPtr<SyncObject> FindSyncObject(GLsync sync) {
|
SyncObject* FindSyncObject(GLsync sync) {
|
||||||
const std::lock_guard<std::mutex> lock(g_syncObjectsMutex);
|
const std::lock_guard<std::mutex> lock(g_syncObjectsMutex);
|
||||||
const auto it = g_liveSyncObjects.find(sync);
|
const auto it = g_liveSyncObjects.find(sync);
|
||||||
return it != g_liveSyncObjects.end() ? it->second : nullptr;
|
return it != g_liveSyncObjects.end() ? it->second : nullptr;
|
||||||
@@ -54,13 +35,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
GLsync FenceSync(GLenum condition, GLbitfield flags) {
|
GLsync FenceSync(GLenum condition, GLbitfield flags) {
|
||||||
auto syncObject = MakeShared<SyncObject>();
|
auto* syncObject = new SyncObject;
|
||||||
syncObject->condition = condition;
|
syncObject->condition = condition;
|
||||||
syncObject->flags = flags;
|
syncObject->flags = flags;
|
||||||
if (const auto backendFenceSync = MG_Backend::gBackendFunctionsTable.GL.FenceSync) {
|
if (const auto backendFenceSync = MG_Backend::gBackendFunctionsTable.GL.FenceSync) {
|
||||||
syncObject->backendHandle = backendFenceSync();
|
syncObject->backendHandle = backendFenceSync();
|
||||||
}
|
}
|
||||||
const GLsync handle = reinterpret_cast<GLsync>(syncObject.get());
|
const GLsync handle = reinterpret_cast<GLsync>(syncObject);
|
||||||
const std::lock_guard<std::mutex> lock(g_syncObjectsMutex);
|
const std::lock_guard<std::mutex> lock(g_syncObjectsMutex);
|
||||||
g_liveSyncObjects[handle] = syncObject;
|
g_liveSyncObjects[handle] = syncObject;
|
||||||
return handle;
|
return handle;
|
||||||
@@ -71,31 +52,24 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GLenum ClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) {
|
GLenum ClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) {
|
||||||
const SharedPtr<SyncObject> syncObject = FindSyncObject(sync);
|
const auto* syncObject = FindSyncObject(sync);
|
||||||
if (!syncObject) {
|
if (!syncObject) {
|
||||||
return GL_WAIT_FAILED;
|
return GL_WAIT_FAILED;
|
||||||
}
|
}
|
||||||
const auto backendClientWaitSync = MG_Backend::gBackendFunctionsTable.GL.ClientWaitSync;
|
const auto backendClientWaitSync = MG_Backend::gBackendFunctionsTable.GL.ClientWaitSync;
|
||||||
// Hold the per-object lock across the backend call: a concurrent
|
if (!backendClientWaitSync || !syncObject->backendHandle) {
|
||||||
// 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 GL_ALREADY_SIGNALED; // legacy always-signaled fallback
|
||||||
}
|
}
|
||||||
return backendClientWaitSync(syncObject->backendHandle, flags, timeout);
|
return backendClientWaitSync(syncObject->backendHandle, flags, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) {
|
void WaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) {
|
||||||
const SharedPtr<SyncObject> syncObject = FindSyncObject(sync);
|
const auto* syncObject = FindSyncObject(sync);
|
||||||
if (!syncObject) {
|
if (!syncObject) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto backendWaitSync = MG_Backend::gBackendFunctionsTable.GL.WaitSync;
|
const auto backendWaitSync = MG_Backend::gBackendFunctionsTable.GL.WaitSync;
|
||||||
const std::lock_guard<std::mutex> lock(syncObject->mutex);
|
if (backendWaitSync && syncObject->backendHandle) {
|
||||||
if (backendWaitSync && syncObject->backendHandle != nullptr) {
|
|
||||||
backendWaitSync(syncObject->backendHandle, flags, timeout);
|
backendWaitSync(syncObject->backendHandle, flags, timeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,7 +78,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
if (sync == nullptr) {
|
if (sync == nullptr) {
|
||||||
return; // glDeleteSync(0) is silently ignored
|
return; // glDeleteSync(0) is silently ignored
|
||||||
}
|
}
|
||||||
SharedPtr<SyncObject> syncObject;
|
SyncObject* syncObject = nullptr;
|
||||||
{
|
{
|
||||||
const std::lock_guard<std::mutex> lock(g_syncObjectsMutex);
|
const std::lock_guard<std::mutex> lock(g_syncObjectsMutex);
|
||||||
const auto it = g_liveSyncObjects.find(sync);
|
const auto it = g_liveSyncObjects.find(sync);
|
||||||
@@ -114,14 +88,15 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
syncObject = it->second;
|
syncObject = it->second;
|
||||||
g_liveSyncObjects.erase(it);
|
g_liveSyncObjects.erase(it);
|
||||||
}
|
}
|
||||||
// Release the backend handle under the object lock. The local SharedPtr
|
const auto backendDeleteSync = MG_Backend::gBackendFunctionsTable.GL.DeleteSync;
|
||||||
// (and any reader's SharedPtr) keeps the wrapper itself alive until every
|
if (backendDeleteSync && syncObject->backendHandle) {
|
||||||
// in-flight backend call has returned.
|
backendDeleteSync(syncObject->backendHandle);
|
||||||
syncObject->ReleaseBackendHandle();
|
}
|
||||||
|
delete syncObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetSynciv(GLsync sync, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* values) {
|
void GetSynciv(GLsync sync, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* values) {
|
||||||
const SharedPtr<SyncObject> syncObject = FindSyncObject(sync);
|
const auto* syncObject = FindSyncObject(sync);
|
||||||
if (!syncObject) {
|
if (!syncObject) {
|
||||||
if (length) {
|
if (length) {
|
||||||
*length = 0;
|
*length = 0;
|
||||||
@@ -136,8 +111,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
break;
|
break;
|
||||||
case GL_SYNC_STATUS: {
|
case GL_SYNC_STATUS: {
|
||||||
const auto backendGetSyncStatus = MG_Backend::gBackendFunctionsTable.GL.GetSyncStatus;
|
const auto backendGetSyncStatus = MG_Backend::gBackendFunctionsTable.GL.GetSyncStatus;
|
||||||
const std::lock_guard<std::mutex> lock(syncObject->mutex);
|
const Bool signaled = !backendGetSyncStatus || !syncObject->backendHandle ||
|
||||||
const Bool signaled = !backendGetSyncStatus || syncObject->backendHandle == nullptr ||
|
|
||||||
backendGetSyncStatus(syncObject->backendHandle);
|
backendGetSyncStatus(syncObject->backendHandle);
|
||||||
value = signaled ? GL_SIGNALED : GL_UNSIGNALED;
|
value = signaled ? GL_SIGNALED : GL_UNSIGNALED;
|
||||||
break;
|
break;
|
||||||
@@ -163,10 +137,11 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void DestroyAllSyncObjects() {
|
void DestroyAllSyncObjects() {
|
||||||
// Detach the registry under the lock, release outside it. Entries the app
|
// Detach the registry under the lock, release outside it. Entries the app
|
||||||
// already deleted were erased by DeleteSync, so nothing here double-frees;
|
// already deleted were erased by DeleteSync, so nothing here double-frees;
|
||||||
// a DeleteSync racing this sweep finds an empty registry and returns.
|
// a DeleteSync racing this sweep finds an empty registry and returns. A
|
||||||
// Readers racing this sweep keep their SharedPtr copy alive, and each
|
// thread still blocked inside ClientWaitSync/GetSynciv during teardown
|
||||||
// object's own lock makes the backend-handle release wait for them.
|
// holds a raw SyncObject* these deletes invalidate - the same undefined
|
||||||
UnorderedMap<GLsync, SharedPtr<SyncObject>> orphans;
|
// race an app-driven DeleteSync already has.
|
||||||
|
UnorderedMap<GLsync, SyncObject*> orphans;
|
||||||
{
|
{
|
||||||
const std::lock_guard<std::mutex> lock(g_syncObjectsMutex);
|
const std::lock_guard<std::mutex> lock(g_syncObjectsMutex);
|
||||||
orphans.swap(g_liveSyncObjects);
|
orphans.swap(g_liveSyncObjects);
|
||||||
@@ -178,10 +153,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
// context/renderer is gone (generation/current-thread guards), so this is
|
// context/renderer is gone (generation/current-thread guards), so this is
|
||||||
// safe after the backend has released its EGL resources - but not after
|
// safe after the backend has released its EGL resources - but not after
|
||||||
// the function table itself is cleared.
|
// the function table itself is cleared.
|
||||||
|
const auto backendDeleteSync = MG_Backend::gBackendFunctionsTable.GL.DeleteSync;
|
||||||
for (const auto& [_, syncObject] : orphans) {
|
for (const auto& [_, syncObject] : orphans) {
|
||||||
if (syncObject) {
|
if (backendDeleteSync && syncObject->backendHandle) {
|
||||||
syncObject->ReleaseBackendHandle();
|
backendDeleteSync(syncObject->backendHandle);
|
||||||
}
|
}
|
||||||
|
delete syncObject;
|
||||||
}
|
}
|
||||||
MGLOG_D("DestroyAllSyncObjects: reclaimed %zu sync object(s) the app left undeleted", orphans.size());
|
MGLOG_D("DestroyAllSyncObjects: reclaimed %zu sync object(s) the app left undeleted", orphans.size());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -250,6 +250,34 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
NotifyContentWrite(atOffset, data.size);
|
NotifyContentWrite(atOffset, data.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BufferObject::FillSubData(DataPtr pattern, SizeT atOffset, SizeT size) {
|
||||||
|
MOBILEGL_ASSERT(pattern.data != nullptr && pattern.size > 0,
|
||||||
|
"FillSubData requires a non-empty pattern.");
|
||||||
|
MOBILEGL_ASSERT(size % pattern.size == 0,
|
||||||
|
"FillSubData size (%zu) must be a multiple of pattern size (%zu).", size, pattern.size);
|
||||||
|
MOBILEGL_ASSERT(atOffset <= m_size && size <= m_size - atOffset,
|
||||||
|
"FillSubData out of bounds: atOffset (%zu) + size (%zu) > m_size (%zu)", atOffset, size,
|
||||||
|
m_size);
|
||||||
|
MOBILEGL_ASSERT(!m_isMapped || (m_mappingAccess & BufferMappingAccessBit::Persistent),
|
||||||
|
"Cannot fill data while buffer is non-persistently mapped.");
|
||||||
|
if (size == 0) return;
|
||||||
|
|
||||||
|
// A clear is ordered after all earlier GPU writes. Partial clears additionally need the
|
||||||
|
// retained shadow bytes; whole-store clears need the same synchronization before writing
|
||||||
|
// an adopted persistent mapping that the GPU may still be accessing.
|
||||||
|
SyncGpuWrites();
|
||||||
|
|
||||||
|
Uint8* dst = m_resource.Bytes() + atOffset;
|
||||||
|
if (pattern.size == 1) {
|
||||||
|
Memset(dst, *static_cast<const Uint8*>(pattern.data), size);
|
||||||
|
} else {
|
||||||
|
for (SizeT at = 0; at < size; at += pattern.size) {
|
||||||
|
Memcpy(dst + at, pattern.data, pattern.size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NotifyContentWrite(atOffset, size);
|
||||||
|
}
|
||||||
|
|
||||||
void BufferObject::DownloadSubData(void* dst, SizeT atOffset, SizeT size) const {
|
void BufferObject::DownloadSubData(void* dst, SizeT atOffset, SizeT size) const {
|
||||||
MOBILEGL_ASSERT(atOffset + size <= m_size,
|
MOBILEGL_ASSERT(atOffset + size <= m_size,
|
||||||
"DownloadSubData out of bounds: atOffset (%zu) + size (%zu) > m_size (%zu)", atOffset, size,
|
"DownloadSubData out of bounds: atOffset (%zu) + size (%zu) > m_size (%zu)", atOffset, size,
|
||||||
|
|||||||
@@ -132,6 +132,9 @@ namespace MobileGL {
|
|||||||
|
|
||||||
void UploadData(DataPtr data, SizeT atOffset);
|
void UploadData(DataPtr data, SizeT atOffset);
|
||||||
void UploadSubData(DataPtr data, SizeT atOffset);
|
void UploadSubData(DataPtr data, SizeT atOffset);
|
||||||
|
// Repeats one already-converted element through [atOffset, atOffset + size) and
|
||||||
|
// publishes the range as one content mutation.
|
||||||
|
void FillSubData(DataPtr pattern, SizeT atOffset, SizeT size);
|
||||||
// Reads `size` bytes from the CPU shadow at `atOffset` into `dst` (glGetBufferSubData).
|
// Reads `size` bytes from the CPU shadow at `atOffset` into `dst` (glGetBufferSubData).
|
||||||
// The shadow reflects CPU writes (BufferData/SubData/maps) and backend write-backs, but not
|
// The shadow reflects CPU writes (BufferData/SubData/maps) and backend write-backs, but not
|
||||||
// arbitrary GPU-side writes.
|
// arbitrary GPU-side writes.
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
Uint externalIndex = 0; // logs only
|
Uint externalIndex = 0; // logs only
|
||||||
Vector<LinkShaderInput> shaders; // already stage-sorted
|
Vector<LinkShaderInput> shaders; // already stage-sorted
|
||||||
SharedPtr<const MG_Util::ShaderTranspiler::CompileEnv> env;
|
SharedPtr<const MG_Util::ShaderTranspiler::CompileEnv> env;
|
||||||
|
// Startup configuration copied with the task, never read from worker code.
|
||||||
|
Bool enableSpirvValidation = false;
|
||||||
// The four "takes effect at the next link" request maps. Snapshotted rather than
|
// The four "takes effect at the next link" request maps. Snapshotted rather than
|
||||||
// referenced, which is precisely what makes glBindAttribLocation and friends
|
// referenced, which is precisely what makes glBindAttribLocation and friends
|
||||||
// legal to call over a pending link without cancelling it: the pending link keeps
|
// legal to call over a pending link without cancelling it: the pending link keeps
|
||||||
|
|||||||
@@ -494,6 +494,7 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
auto task = MakeShared<ProgramLinkTask>();
|
auto task = MakeShared<ProgramLinkTask>();
|
||||||
task->in.externalIndex = m_externalIndex;
|
task->in.externalIndex = m_externalIndex;
|
||||||
task->in.env = MG_Util::ShaderTranspiler::GetCurrentCompileEnv();
|
task->in.env = MG_Util::ShaderTranspiler::GetCurrentCompileEnv();
|
||||||
|
task->in.enableSpirvValidation = MG_Config::Features.EnableSpirvValidation;
|
||||||
task->in.explicitAttribLocations = m_explicitAttribLocations;
|
task->in.explicitAttribLocations = m_explicitAttribLocations;
|
||||||
task->in.explicitFragDataLocation = m_explicitFragDataLocation;
|
task->in.explicitFragDataLocation = m_explicitFragDataLocation;
|
||||||
task->in.explicitFragDataIndex = m_explicitFragDataIndex;
|
task->in.explicitFragDataIndex = m_explicitFragDataIndex;
|
||||||
|
|||||||
@@ -819,6 +819,9 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
// backend asks this exactly where it used to ask GetLinkStatus(), i.e. right before
|
// backend asks this exactly where it used to ask GetLinkStatus(), i.e. right before
|
||||||
// it builds or draws with the program.
|
// it builds or draws with the program.
|
||||||
Bool GetSpirvStatus() const { return Spirv().spirvStatus; }
|
Bool GetSpirvStatus() const { return Spirv().spirvStatus; }
|
||||||
|
// Copied from the link task that generated this program's SPIR-V. Backends use it for
|
||||||
|
// their final transforms, which must honor the same diagnostic setting as phase B.
|
||||||
|
Bool GetSpirvValidationEnabled() const { return Spirv().enableSpirvValidation; }
|
||||||
|
|
||||||
// The linked glslang reflection itself, for the ONE consumer that needs resource
|
// The linked glslang reflection itself, for the ONE consumer that needs resource
|
||||||
// lists no typed getter above exposes: the GL program-interface query layer
|
// lists no typed getter above exposes: the GL program-interface query layer
|
||||||
@@ -985,6 +988,7 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
// cannot be lifted out of glslang's reflection instead.
|
// cannot be lifted out of glslang's reflection instead.
|
||||||
struct SpirvArtifacts {
|
struct SpirvArtifacts {
|
||||||
Vector<Vector<unsigned>> generatedSpirv;
|
Vector<Vector<unsigned>> generatedSpirv;
|
||||||
|
Bool enableSpirvValidation = false;
|
||||||
// Byte offset of each uniform location inside globalUboScratch, or
|
// Byte offset of each uniform location inside globalUboScratch, or
|
||||||
// kInvalidUniformOffset. Sized maxUniformLocation + 1 by the routing pass.
|
// kInvalidUniformOffset. Sized maxUniformLocation + 1 by the routing pass.
|
||||||
Vector<Uint> uniformOffsets;
|
Vector<Uint> uniformOffsets;
|
||||||
|
|||||||
@@ -102,7 +102,11 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MGLOG_D("ProgramObject %u: Starting SPIR-V generation", externalIndex);
|
MGLOG_D("ProgramObject %u: Starting SPIR-V generation", externalIndex);
|
||||||
GenerateSpirv(handoff, externalIndex);
|
const Bool deferOutputValidationForDirectVulkan =
|
||||||
|
m_phaseA->in.env != nullptr && m_phaseA->in.env->backend == BackendType::DirectVulkan;
|
||||||
|
const Bool enableSpirvValidation = m_phaseA->in.enableSpirvValidation;
|
||||||
|
artifacts.enableSpirvValidation = enableSpirvValidation;
|
||||||
|
GenerateSpirv(handoff, externalIndex, deferOutputValidationForDirectVulkan, enableSpirvValidation);
|
||||||
// GlslangToSpv was the only consumer of the parsed ASTs; everything after this point
|
// GlslangToSpv was the only consumer of the parsed ASTs; everything after this point
|
||||||
// works on the SPIR-V and on the TProgram's own self-contained reflection pool. Drop
|
// works on the SPIR-V and on the TProgram's own self-contained reflection pool. Drop
|
||||||
// them here rather than at the end of the body, which is ~87% of this node's runtime
|
// them here rather than at the end of the body, which is ~87% of this node's runtime
|
||||||
@@ -137,7 +141,9 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
artifacts.generatedSpirv.size());
|
artifacts.generatedSpirv.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProgramSpirvTask::GenerateSpirv(const ProgramLinkTask::SpirvHandoff& handoff, const Uint externalIndex) {
|
void ProgramSpirvTask::GenerateSpirv(const ProgramLinkTask::SpirvHandoff& handoff, const Uint externalIndex,
|
||||||
|
const Bool deferOutputValidationForDirectVulkan,
|
||||||
|
const Bool enableSpirvValidation) {
|
||||||
/* As we passed first stage compilation/linking,
|
/* As we passed first stage compilation/linking,
|
||||||
* we'll assume all the operations here should
|
* we'll assume all the operations here should
|
||||||
* pass. We may be able to employ some optimizations
|
* pass. We may be able to employ some optimizations
|
||||||
@@ -169,7 +175,8 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
Bool allOptimized = true;
|
Bool allOptimized = true;
|
||||||
{
|
{
|
||||||
for (auto& spv : artifacts.generatedSpirv) {
|
for (auto& spv : artifacts.generatedSpirv) {
|
||||||
auto success = ShaderCompiler::SanitizeAndOptimizeBinary(spv, spv);
|
auto success = ShaderCompiler::SanitizeAndOptimizeBinary(
|
||||||
|
spv, spv, !deferOutputValidationForDirectVulkan, enableSpirvValidation);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
// The one genuine phase-B failure mode: one of the seven optimizer passes
|
// The one genuine phase-B failure mode: one of the seven optimizer passes
|
||||||
// reported failure, so `spv` is whatever the run left behind. A fordebug
|
// reported failure, so `spv` is whatever the run left behind. A fordebug
|
||||||
|
|||||||
@@ -65,7 +65,8 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
private:
|
private:
|
||||||
void RunBody() override;
|
void RunBody() override;
|
||||||
|
|
||||||
void GenerateSpirv(const ProgramLinkTask::SpirvHandoff& handoff, Uint externalIndex);
|
void GenerateSpirv(const ProgramLinkTask::SpirvHandoff& handoff, Uint externalIndex,
|
||||||
|
Bool deferOutputValidationForDirectVulkan, Bool enableSpirvValidation);
|
||||||
void BuildGlobalUboRouting(const ProgramLinkTask::SpirvHandoff& handoff, Uint externalIndex);
|
void BuildGlobalUboRouting(const ProgramLinkTask::SpirvHandoff& handoff, Uint externalIndex);
|
||||||
|
|
||||||
// Worker-side MGLOG replacement, replayed by the join on the GL thread. Same reason as
|
// Worker-side MGLOG replacement, replayed by the join on the GL thread. Same reason as
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include <MG_State/GLState/Core.h>
|
#include <MG_State/GLState/Core.h>
|
||||||
|
|
||||||
#include <MG_Impl/GLImpl/Buffer/GL_Buffer.h>
|
#include <MG_Impl/GLImpl/Buffer/GL_Buffer.h>
|
||||||
|
#include <MG_Impl/GetProcAddress.h>
|
||||||
#include <MG_Impl/GLImpl/Getter/GL_Getter.h>
|
#include <MG_Impl/GLImpl/Getter/GL_Getter.h>
|
||||||
|
|
||||||
using namespace MobileGL;
|
using namespace MobileGL;
|
||||||
@@ -599,6 +600,117 @@ TEST_F(BufferTest, ClearNamedBufferSubDataRepeatsPattern) {
|
|||||||
EXPECT_EQ(actual, (Vector<Uint32>{0, pattern, pattern, pattern, 0}));
|
EXPECT_EQ(actual, (Vector<Uint32>{0, pattern, pattern, pattern, 0}));
|
||||||
EXPECT_EQ(MobileGL::MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
EXPECT_EQ(MobileGL::MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
}
|
}
|
||||||
|
TEST_F(BufferTest, ClearBufferSubDataInitializesIrisStaticSsboRange) {
|
||||||
|
GLuint buffer = 0;
|
||||||
|
MobileGL::MG_Impl::GLImpl::GenBuffers(1, &buffer);
|
||||||
|
MobileGL::MG_Impl::GLImpl::BindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
|
||||||
|
|
||||||
|
Vector<Uint8> initial(32, 0x7F);
|
||||||
|
MobileGL::MG_Impl::GLImpl::BufferData(
|
||||||
|
GL_SHADER_STORAGE_BUFFER, initial.size(), initial.data(), GL_STATIC_DRAW);
|
||||||
|
const GLbyte zero = 0;
|
||||||
|
const auto clear = reinterpret_cast<PFNGLCLEARBUFFERSUBDATAPROC>(
|
||||||
|
MobileGL::MG_Impl::GetProcAddress("glClearBufferSubData"));
|
||||||
|
ASSERT_NE(clear, nullptr);
|
||||||
|
clear(GL_SHADER_STORAGE_BUFFER, GL_R8, 4, 24, GL_RED, GL_BYTE, &zero);
|
||||||
|
|
||||||
|
Vector<Uint8> actual(initial.size());
|
||||||
|
auto bufferObject = MobileGL::MG_State::pGLContext->GetBufferObject(buffer);
|
||||||
|
ASSERT_NE(bufferObject, nullptr);
|
||||||
|
Memcpy(actual.data(), bufferObject->AcquireMemory(false, true, false), actual.size());
|
||||||
|
EXPECT_EQ(actual, (Vector<Uint8>{0x7F, 0x7F, 0x7F, 0x7F,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0x7F, 0x7F, 0x7F, 0x7F}));
|
||||||
|
EXPECT_EQ(MobileGL::MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
|
||||||
|
MobileGL::MG_Impl::GLImpl::BindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
|
||||||
|
MobileGL::MG_Impl::GLImpl::DeleteBuffers(1, &buffer);
|
||||||
|
DrainPendingGlErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(BufferTest, ClearBufferSubDataInitializesCompleteIrisStaticSsbo) {
|
||||||
|
constexpr SizeT irisStaticSsboSize = 5'000'192;
|
||||||
|
GLuint buffer = 0;
|
||||||
|
MobileGL::MG_Impl::GLImpl::GenBuffers(1, &buffer);
|
||||||
|
MobileGL::MG_Impl::GLImpl::BindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
|
||||||
|
|
||||||
|
Vector<Uint8> initial(irisStaticSsboSize, 0x7F);
|
||||||
|
MobileGL::MG_Impl::GLImpl::BufferData(
|
||||||
|
GL_SHADER_STORAGE_BUFFER, initial.size(), initial.data(), GL_STATIC_DRAW);
|
||||||
|
const GLbyte zero = 0;
|
||||||
|
MobileGL::MG_Impl::GLImpl::ClearBufferSubData(
|
||||||
|
GL_SHADER_STORAGE_BUFFER, GL_R8, 0, irisStaticSsboSize, GL_RED, GL_BYTE, &zero);
|
||||||
|
|
||||||
|
Vector<Uint8> actual(irisStaticSsboSize);
|
||||||
|
auto bufferObject = MobileGL::MG_State::pGLContext->GetBufferObject(buffer);
|
||||||
|
ASSERT_NE(bufferObject, nullptr);
|
||||||
|
Memcpy(actual.data(), bufferObject->AcquireMemory(false, true, false), actual.size());
|
||||||
|
EXPECT_EQ(actual, Vector<Uint8>(irisStaticSsboSize, 0));
|
||||||
|
EXPECT_EQ(MobileGL::MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
|
||||||
|
MobileGL::MG_Impl::GLImpl::BindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
|
||||||
|
MobileGL::MG_Impl::GLImpl::DeleteBuffers(1, &buffer);
|
||||||
|
DrainPendingGlErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(BufferTest, ClearBufferDataConvertsOneClientPixelBeforeRepeatingIt) {
|
||||||
|
GLuint buffer = 0;
|
||||||
|
MobileGL::MG_Impl::GLImpl::GenBuffers(1, &buffer);
|
||||||
|
MobileGL::MG_Impl::GLImpl::BindBuffer(GL_ARRAY_BUFFER, buffer);
|
||||||
|
|
||||||
|
Vector<Uint32> initial(4, 0u);
|
||||||
|
MobileGL::MG_Impl::GLImpl::BufferData(GL_ARRAY_BUFFER, initial.size() * sizeof(Uint32), initial.data(),
|
||||||
|
GL_STATIC_DRAW);
|
||||||
|
const Uint8 value = 0xAB;
|
||||||
|
MobileGL::MG_Impl::GLImpl::ClearBufferData(
|
||||||
|
GL_ARRAY_BUFFER, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, &value);
|
||||||
|
|
||||||
|
Vector<Uint32> actual(initial.size());
|
||||||
|
auto bufferObject = MobileGL::MG_State::pGLContext->GetBufferObject(buffer);
|
||||||
|
ASSERT_NE(bufferObject, nullptr);
|
||||||
|
Memcpy(actual.data(), bufferObject->AcquireMemory(false, true, false), actual.size() * sizeof(Uint32));
|
||||||
|
EXPECT_EQ(actual, Vector<Uint32>(initial.size(), value));
|
||||||
|
EXPECT_EQ(MobileGL::MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
|
||||||
|
MobileGL::MG_Impl::GLImpl::BindBuffer(GL_ARRAY_BUFFER, 0);
|
||||||
|
MobileGL::MG_Impl::GLImpl::DeleteBuffers(1, &buffer);
|
||||||
|
DrainPendingGlErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(BufferTest, ClearBufferSubDataRejectsUnboundTarget) {
|
||||||
|
MobileGL::MG_Impl::GLImpl::BindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
|
||||||
|
const GLbyte zero = 0;
|
||||||
|
MobileGL::MG_Impl::GLImpl::ClearBufferSubData(
|
||||||
|
GL_SHADER_STORAGE_BUFFER, GL_R8, 0, 1, GL_RED, GL_BYTE, &zero);
|
||||||
|
ExpectSingleGlError(GL_INVALID_OPERATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(BufferTest, ClearBufferDataRejectsInvalidPixelFormatTypePairs) {
|
||||||
|
GLuint buffer = 0;
|
||||||
|
MobileGL::MG_Impl::GLImpl::GenBuffers(1, &buffer);
|
||||||
|
MobileGL::MG_Impl::GLImpl::BindBuffer(GL_ARRAY_BUFFER, buffer);
|
||||||
|
|
||||||
|
const Vector<Uint8> initial{0x7F, 0x7F};
|
||||||
|
MobileGL::MG_Impl::GLImpl::BufferData(GL_ARRAY_BUFFER, initial.size(), initial.data(), GL_STATIC_DRAW);
|
||||||
|
const Uint16 packed = 0;
|
||||||
|
MobileGL::MG_Impl::GLImpl::ClearBufferData(
|
||||||
|
GL_ARRAY_BUFFER, GL_R16, GL_RED, GL_UNSIGNED_SHORT_5_6_5, &packed);
|
||||||
|
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||||
|
MobileGL::MG_Impl::GLImpl::ClearBufferData(
|
||||||
|
GL_ARRAY_BUFFER, GL_R16, GL_RED, GL_UNSIGNED_SHORT_5_6_5, nullptr);
|
||||||
|
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||||
|
|
||||||
|
Vector<Uint8> actual(initial.size());
|
||||||
|
auto bufferObject = MobileGL::MG_State::pGLContext->GetBufferObject(buffer);
|
||||||
|
ASSERT_NE(bufferObject, nullptr);
|
||||||
|
Memcpy(actual.data(), bufferObject->AcquireMemory(false, true, false), actual.size());
|
||||||
|
EXPECT_EQ(actual, initial);
|
||||||
|
|
||||||
|
MobileGL::MG_Impl::GLImpl::BindBuffer(GL_ARRAY_BUFFER, 0);
|
||||||
|
MobileGL::MG_Impl::GLImpl::DeleteBuffers(1, &buffer);
|
||||||
|
DrainPendingGlErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// GL 4.6 core 6.5: glBufferSubData fails only when the written range OVERLAPS the mapped range.
|
// GL 4.6 core 6.5: glBufferSubData fails only when the written range OVERLAPS the mapped range.
|
||||||
|
|||||||
@@ -2849,16 +2849,6 @@ vec4 helperTint() { return vec4(1.0); }
|
|||||||
return binaryResult->front();
|
return binaryResult->front();
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SpirvValidationScope {
|
|
||||||
bool previous;
|
|
||||||
explicit SpirvValidationScope(bool enabled)
|
|
||||||
: previous(MG_Util::ShaderTranspiler::ShaderCompiler::SpirvValidationEnabled()) {
|
|
||||||
MG_Util::ShaderTranspiler::ShaderCompiler::SetSpirvValidationEnabled(enabled);
|
|
||||||
}
|
|
||||||
~SpirvValidationScope() {
|
|
||||||
MG_Util::ShaderTranspiler::ShaderCompiler::SetSpirvValidationEnabled(previous);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_F(ProgramUtilTest, DeadPrivateChainVertexInputIsEliminatedFromOptimizedBinary) {
|
TEST_F(ProgramUtilTest, DeadPrivateChainVertexInputIsEliminatedFromOptimizedBinary) {
|
||||||
@@ -2880,7 +2870,7 @@ TEST_F(ProgramUtilTest, DeadPrivateChainVertexInputIsEliminatedFromOptimizedBina
|
|||||||
<< "entry-point-with-calls shape it exists for";
|
<< "entry-point-with-calls shape it exists for";
|
||||||
|
|
||||||
Vector<Uint32> optimized;
|
Vector<Uint32> optimized;
|
||||||
ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, optimized));
|
ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, optimized, true, true));
|
||||||
|
|
||||||
const SpirvVariableCensus after = TakeVariableCensus(optimized);
|
const SpirvVariableCensus after = TakeVariableCensus(optimized);
|
||||||
EXPECT_EQ(after.inputCount, 1u)
|
EXPECT_EQ(after.inputCount, 1u)
|
||||||
@@ -2918,7 +2908,7 @@ void main() {
|
|||||||
ASSERT_GE(before.outputCount, 3u);
|
ASSERT_GE(before.outputCount, 3u);
|
||||||
|
|
||||||
Vector<Uint32> optimized;
|
Vector<Uint32> optimized;
|
||||||
ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, optimized));
|
ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, optimized, true, true));
|
||||||
EXPECT_EQ(TakeVariableCensus(optimized).outputCount, before.outputCount)
|
EXPECT_EQ(TakeVariableCensus(optimized).outputCount, before.outputCount)
|
||||||
<< "a declared-but-unwritten output was deleted; a fragment stage reading it now "
|
<< "a declared-but-unwritten output was deleted; a fragment stage reading it now "
|
||||||
<< "fails to link (ES) or breaks the Vulkan stage interface";
|
<< "fails to link (ES) or breaks the Vulkan stage interface";
|
||||||
@@ -2977,17 +2967,15 @@ void main() {
|
|||||||
// succeeds - fail-open call sites downstream must not see a different world),
|
// succeeds - fail-open call sites downstream must not see a different world),
|
||||||
// and the failure latch is the signal. This is the catch that took a device
|
// and the failure latch is the signal. This is the catch that took a device
|
||||||
// bisect to find when the validator was off everywhere.
|
// bisect to find when the validator was off everywhere.
|
||||||
SpirvValidationScope validationOn(true);
|
|
||||||
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
||||||
EXPECT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, optimized));
|
EXPECT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, optimized, true, true));
|
||||||
EXPECT_GT(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore)
|
EXPECT_GT(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore)
|
||||||
<< "an invalid optimized module must bump the validation-failure latch";
|
<< "an invalid optimized module must bump the validation-failure latch";
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// The shipping configuration: same result, no validation, latch untouched.
|
// The shipping configuration: same result, no validation, latch untouched.
|
||||||
SpirvValidationScope validationOff(false);
|
|
||||||
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
||||||
EXPECT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, optimized));
|
EXPECT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, optimized, true, false));
|
||||||
EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore);
|
EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3057,10 +3045,9 @@ void main() {
|
|||||||
ASSERT_FALSE(raw.empty());
|
ASSERT_FALSE(raw.empty());
|
||||||
ASSERT_GE(CountRectImageTypes(raw), 1u) << "glslang no longer emits Dim::Rect for sampler2DRect";
|
ASSERT_GE(CountRectImageTypes(raw), 1u) << "glslang no longer emits Dim::Rect for sampler2DRect";
|
||||||
|
|
||||||
SpirvValidationScope validationOn(true);
|
|
||||||
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
||||||
Vector<Uint32> optimized;
|
Vector<Uint32> optimized;
|
||||||
ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, optimized));
|
ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, optimized, true, true));
|
||||||
EXPECT_EQ(CountRectImageTypes(optimized), 0u);
|
EXPECT_EQ(CountRectImageTypes(optimized), 0u);
|
||||||
EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore)
|
EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore)
|
||||||
<< "a rectangle module must leave the chain valid, not latched as a failure";
|
<< "a rectangle module must leave the chain valid, not latched as a failure";
|
||||||
@@ -3085,10 +3072,9 @@ void main() {
|
|||||||
ASSERT_TRUE(AnyLocationOnUniformStorage(raw))
|
ASSERT_TRUE(AnyLocationOnUniformStorage(raw))
|
||||||
<< "glslang no longer keeps the explicit uniform location; the strip pass may be obsolete";
|
<< "glslang no longer keeps the explicit uniform location; the strip pass may be obsolete";
|
||||||
|
|
||||||
SpirvValidationScope validationOn(true);
|
|
||||||
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
||||||
Vector<Uint32> optimized;
|
Vector<Uint32> optimized;
|
||||||
ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, optimized));
|
ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, optimized, true, true));
|
||||||
EXPECT_FALSE(AnyLocationOnUniformStorage(optimized));
|
EXPECT_FALSE(AnyLocationOnUniformStorage(optimized));
|
||||||
EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore)
|
EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore)
|
||||||
<< "the stripped module must validate clean";
|
<< "the stripped module must validate clean";
|
||||||
@@ -3185,11 +3171,10 @@ void main() {
|
|||||||
<< "the fixture must reproduce the defect before the fix is asked to remove it:\n"
|
<< "the fixture must reproduce the defect before the fix is asked to remove it:\n"
|
||||||
<< DisassembleSpirv(raw);
|
<< DisassembleSpirv(raw);
|
||||||
|
|
||||||
SpirvValidationScope validationOn(true);
|
|
||||||
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
||||||
|
|
||||||
Vector<Uint32> legalized;
|
Vector<Uint32> legalized;
|
||||||
ASSERT_TRUE(ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(raw, legalized));
|
ASSERT_TRUE(ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(raw, legalized, true));
|
||||||
ASSERT_FALSE(legalized.empty());
|
ASSERT_FALSE(legalized.empty());
|
||||||
|
|
||||||
const String disassembly = DisassembleSpirv(legalized);
|
const String disassembly = DisassembleSpirv(legalized);
|
||||||
@@ -3226,11 +3211,10 @@ void main() {
|
|||||||
ASSERT_TRUE(LegalizeFragmentOutputIndexPass::BinaryHasDynamicOutputIndexing(raw))
|
ASSERT_TRUE(LegalizeFragmentOutputIndexPass::BinaryHasDynamicOutputIndexing(raw))
|
||||||
<< DisassembleSpirv(raw);
|
<< DisassembleSpirv(raw);
|
||||||
|
|
||||||
SpirvValidationScope validationOn(true);
|
|
||||||
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
||||||
|
|
||||||
Vector<Uint32> legalized;
|
Vector<Uint32> legalized;
|
||||||
ASSERT_TRUE(ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(raw, legalized));
|
ASSERT_TRUE(ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(raw, legalized, true));
|
||||||
ASSERT_FALSE(legalized.empty());
|
ASSERT_FALSE(legalized.empty());
|
||||||
|
|
||||||
const String disassembly = DisassembleSpirv(legalized);
|
const String disassembly = DisassembleSpirv(legalized);
|
||||||
@@ -3270,11 +3254,10 @@ void main() {
|
|||||||
ASSERT_FALSE(raw.empty());
|
ASSERT_FALSE(raw.empty());
|
||||||
ASSERT_TRUE(LegalizeFragmentOutputIndexPass::BinaryHasDynamicOutputIndexing(raw));
|
ASSERT_TRUE(LegalizeFragmentOutputIndexPass::BinaryHasDynamicOutputIndexing(raw));
|
||||||
|
|
||||||
SpirvValidationScope validationOn(true);
|
|
||||||
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
||||||
|
|
||||||
Vector<Uint32> legalized;
|
Vector<Uint32> legalized;
|
||||||
ASSERT_TRUE(ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(raw, legalized));
|
ASSERT_TRUE(ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(raw, legalized, true));
|
||||||
ASSERT_FALSE(legalized.empty());
|
ASSERT_FALSE(legalized.empty());
|
||||||
|
|
||||||
const String disassembly = DisassembleSpirv(legalized);
|
const String disassembly = DisassembleSpirv(legalized);
|
||||||
@@ -3313,7 +3296,7 @@ void main() {
|
|||||||
ASSERT_FALSE(LegalizeFragmentOutputIndexPass::BinaryHasDynamicOutputIndexing(raw));
|
ASSERT_FALSE(LegalizeFragmentOutputIndexPass::BinaryHasDynamicOutputIndexing(raw));
|
||||||
|
|
||||||
Vector<Uint32> legalized;
|
Vector<Uint32> legalized;
|
||||||
ASSERT_TRUE(ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(raw, legalized));
|
ASSERT_TRUE(ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(raw, legalized, true));
|
||||||
EXPECT_EQ(legalized, raw) << "the module must not be rewritten - not even re-serialized - when "
|
EXPECT_EQ(legalized, raw) << "the module must not be rewritten - not even re-serialized - when "
|
||||||
"nothing indexes a fragment output dynamically";
|
"nothing indexes a fragment output dynamically";
|
||||||
}
|
}
|
||||||
@@ -3563,11 +3546,10 @@ TEST_F(ProgramUtilTest, Lower1DArrayImagesRewritesTheTypeAndWidensTheCoordinate)
|
|||||||
ASSERT_EQ(Count1DArrayStorageImageTypes(spirv), 1u)
|
ASSERT_EQ(Count1DArrayStorageImageTypes(spirv), 1u)
|
||||||
<< "the shared chain must leave the 1D-array image for this pass to handle";
|
<< "the shared chain must leave the 1D-array image for this pass to handle";
|
||||||
|
|
||||||
SpirvValidationScope validationOn(true);
|
|
||||||
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
||||||
|
|
||||||
Vector<Uint32> lowered;
|
Vector<Uint32> lowered;
|
||||||
ASSERT_TRUE(ShaderCompiler::Lower1DArrayImagesForEssl(spirv, lowered));
|
ASSERT_TRUE(ShaderCompiler::Lower1DArrayImagesForEssl(spirv, lowered, true));
|
||||||
ASSERT_FALSE(lowered.empty());
|
ASSERT_FALSE(lowered.empty());
|
||||||
|
|
||||||
EXPECT_EQ(Count1DArrayStorageImageTypes(lowered), 0u)
|
EXPECT_EQ(Count1DArrayStorageImageTypes(lowered), 0u)
|
||||||
@@ -3615,11 +3597,10 @@ void main() { ssb.sum = imageLoad(i0, ivec2(2, 3)).r + imageLoad(i1, ivec3(1, 1,
|
|||||||
ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, spirv));
|
ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, spirv));
|
||||||
ASSERT_EQ(Count1DArrayStorageImageTypes(spirv), 1u);
|
ASSERT_EQ(Count1DArrayStorageImageTypes(spirv), 1u);
|
||||||
|
|
||||||
SpirvValidationScope validationOn(true);
|
|
||||||
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
||||||
|
|
||||||
Vector<Uint32> lowered;
|
Vector<Uint32> lowered;
|
||||||
ASSERT_TRUE(ShaderCompiler::Lower1DArrayImagesForEssl(spirv, lowered));
|
ASSERT_TRUE(ShaderCompiler::Lower1DArrayImagesForEssl(spirv, lowered, true));
|
||||||
ASSERT_FALSE(lowered.empty());
|
ASSERT_FALSE(lowered.empty());
|
||||||
|
|
||||||
EXPECT_EQ(Count1DArrayStorageImageTypes(lowered), 0u) << DisassembleSpirv(lowered);
|
EXPECT_EQ(Count1DArrayStorageImageTypes(lowered), 0u) << DisassembleSpirv(lowered);
|
||||||
@@ -3649,7 +3630,7 @@ void main() { ssb.sum = imageLoad(i0, 2).r; }
|
|||||||
ASSERT_FALSE(spirv.empty());
|
ASSERT_FALSE(spirv.empty());
|
||||||
|
|
||||||
Vector<Uint32> lowered;
|
Vector<Uint32> lowered;
|
||||||
ASSERT_TRUE(ShaderCompiler::Lower1DArrayImagesForEssl(spirv, lowered));
|
ASSERT_TRUE(ShaderCompiler::Lower1DArrayImagesForEssl(spirv, lowered, true));
|
||||||
EXPECT_EQ(lowered, spirv) << "a non-arrayed 1D storage image must pass through byte for byte";
|
EXPECT_EQ(lowered, spirv) << "a non-arrayed 1D storage image must pass through byte for byte";
|
||||||
|
|
||||||
const String essl = DecompileToEssl(lowered);
|
const String essl = DecompileToEssl(lowered);
|
||||||
@@ -3673,7 +3654,7 @@ void main() { fragColor = texture(uTex, vUv); }
|
|||||||
ASSERT_FALSE(spirv.empty());
|
ASSERT_FALSE(spirv.empty());
|
||||||
|
|
||||||
Vector<Uint32> lowered;
|
Vector<Uint32> lowered;
|
||||||
ASSERT_TRUE(ShaderCompiler::Lower1DArrayImagesForEssl(spirv, lowered));
|
ASSERT_TRUE(ShaderCompiler::Lower1DArrayImagesForEssl(spirv, lowered, true));
|
||||||
EXPECT_EQ(lowered, spirv) << "a sampled 1D-array image must pass through byte for byte";
|
EXPECT_EQ(lowered, spirv) << "a sampled 1D-array image must pass through byte for byte";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3697,7 +3678,7 @@ void main() { ssb.sum = uint(imageSize(i0).x) + imageLoad(i0, ivec2(0, 0)).r; }
|
|||||||
<< "the fixture must contain the shape the pass declines";
|
<< "the fixture must contain the shape the pass declines";
|
||||||
|
|
||||||
Vector<Uint32> lowered;
|
Vector<Uint32> lowered;
|
||||||
ASSERT_TRUE(ShaderCompiler::Lower1DArrayImagesForEssl(spirv, lowered));
|
ASSERT_TRUE(ShaderCompiler::Lower1DArrayImagesForEssl(spirv, lowered, true));
|
||||||
EXPECT_EQ(lowered, spirv) << "a declined module must be handed back untouched, not partly rewritten";
|
EXPECT_EQ(lowered, spirv) << "a declined module must be handed back untouched, not partly rewritten";
|
||||||
EXPECT_EQ(Count1DArrayStorageImageTypes(lowered), 1u)
|
EXPECT_EQ(Count1DArrayStorageImageTypes(lowered), 1u)
|
||||||
<< "declining means the 1D-array type is still there for the driver to reject";
|
<< "declining means the 1D-array type is still there for the driver to reject";
|
||||||
@@ -3748,11 +3729,10 @@ void main() { imageStore(uni_image, ivec2(gl_GlobalInvocationID.xy), uvec4(15u,
|
|||||||
// Precondition: SPIRV-Cross prints no format for it, which is the ESSL the driver refuses.
|
// Precondition: SPIRV-Cross prints no format for it, which is the ESSL the driver refuses.
|
||||||
EXPECT_EQ(DecompileToEssl(spirv).find("r32ui"), String::npos);
|
EXPECT_EQ(DecompileToEssl(spirv).find("r32ui"), String::npos);
|
||||||
|
|
||||||
SpirvValidationScope validationOn(true);
|
|
||||||
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
||||||
|
|
||||||
Vector<Uint32> baked;
|
Vector<Uint32> baked;
|
||||||
ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_image", kGlR32ui}}, baked));
|
ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_image", kGlR32ui}}, baked, true));
|
||||||
ASSERT_FALSE(baked.empty());
|
ASSERT_FALSE(baked.empty());
|
||||||
EXPECT_FALSE(ShaderCompiler::DeclaresFormatlessStorageImage(baked)) << DisassembleSpirv(baked);
|
EXPECT_FALSE(ShaderCompiler::DeclaresFormatlessStorageImage(baked)) << DisassembleSpirv(baked);
|
||||||
EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore)
|
EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore)
|
||||||
@@ -3813,7 +3793,7 @@ void main() { imageStore(uni_image, ivec2(0), uvec4(1u)); }
|
|||||||
|
|
||||||
Vector<Uint32> baked;
|
Vector<Uint32> baked;
|
||||||
// Even asked to, with a format of the right component class.
|
// Even asked to, with a format of the right component class.
|
||||||
ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_image", kGlR32ui}}, baked));
|
ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_image", kGlR32ui}}, baked, true));
|
||||||
EXPECT_EQ(baked, spirv) << "a module with nothing format-less must pass through byte for byte";
|
EXPECT_EQ(baked, spirv) << "a module with nothing format-less must pass through byte for byte";
|
||||||
EXPECT_NE(DecompileToEssl(baked).find("rgba32ui"), String::npos);
|
EXPECT_NE(DecompileToEssl(baked).find("rgba32ui"), String::npos);
|
||||||
}
|
}
|
||||||
@@ -3835,11 +3815,10 @@ void main() { writeIt(uni_image); }
|
|||||||
ASSERT_FALSE(spirv.empty());
|
ASSERT_FALSE(spirv.empty());
|
||||||
ASSERT_TRUE(ShaderCompiler::DeclaresFormatlessStorageImage(spirv));
|
ASSERT_TRUE(ShaderCompiler::DeclaresFormatlessStorageImage(spirv));
|
||||||
|
|
||||||
SpirvValidationScope validationOn(true);
|
|
||||||
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
||||||
|
|
||||||
Vector<Uint32> baked;
|
Vector<Uint32> baked;
|
||||||
ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_image", kGlR32ui}}, baked));
|
ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_image", kGlR32ui}}, baked, true));
|
||||||
EXPECT_EQ(baked, spirv) << "a shape the retype cannot follow must leave the module untouched, "
|
EXPECT_EQ(baked, spirv) << "a shape the retype cannot follow must leave the module untouched, "
|
||||||
"not partly rewritten:\n"
|
"not partly rewritten:\n"
|
||||||
<< DisassembleSpirv(baked);
|
<< DisassembleSpirv(baked);
|
||||||
@@ -3861,18 +3840,17 @@ void main() { imageStore(uni_image, ivec2(0), vec4(1.0)); }
|
|||||||
GL_COMPUTE_SHADER);
|
GL_COMPUTE_SHADER);
|
||||||
ASSERT_FALSE(spirv.empty());
|
ASSERT_FALSE(spirv.empty());
|
||||||
|
|
||||||
SpirvValidationScope validationOn(true);
|
|
||||||
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
||||||
|
|
||||||
Vector<Uint32> baked;
|
Vector<Uint32> baked;
|
||||||
ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_image", kGlR32ui}}, baked));
|
ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_image", kGlR32ui}}, baked, true));
|
||||||
EXPECT_EQ(baked, spirv) << "a declined module must be handed back untouched, not partly rewritten";
|
EXPECT_EQ(baked, spirv) << "a declined module must be handed back untouched, not partly rewritten";
|
||||||
EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore);
|
EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore);
|
||||||
|
|
||||||
// ...and the same image with a float bind format is baked, so the decline above is about the
|
// ...and the same image with a float bind format is baked, so the decline above is about the
|
||||||
// class and not about the pass refusing float images.
|
// class and not about the pass refusing float images.
|
||||||
Vector<Uint32> bakedFloat;
|
Vector<Uint32> bakedFloat;
|
||||||
ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_image", kGlR32f}}, bakedFloat));
|
ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_image", kGlR32f}}, bakedFloat, true));
|
||||||
EXPECT_NE(DecompileToEssl(bakedFloat).find("r32f"), String::npos) << DisassembleSpirv(bakedFloat);
|
EXPECT_NE(DecompileToEssl(bakedFloat).find("r32f"), String::npos) << DisassembleSpirv(bakedFloat);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3896,12 +3874,11 @@ void main() {
|
|||||||
ASSERT_EQ(CountSpirvOpcode(DisassembleSpirv(spirv), "OpTypeImage"), 1u)
|
ASSERT_EQ(CountSpirvOpcode(DisassembleSpirv(spirv), "OpTypeImage"), 1u)
|
||||||
<< "the fixture must have the two images sharing one type:\n" << DisassembleSpirv(spirv);
|
<< "the fixture must have the two images sharing one type:\n" << DisassembleSpirv(spirv);
|
||||||
|
|
||||||
SpirvValidationScope validationOn(true);
|
|
||||||
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
||||||
|
|
||||||
Vector<Uint32> baked;
|
Vector<Uint32> baked;
|
||||||
ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(
|
ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(
|
||||||
spirv, {{"imgA", kGlR32ui}, {"imgB", kGlRgba32ui}}, baked));
|
spirv, {{"imgA", kGlR32ui}, {"imgB", kGlRgba32ui}}, baked, true));
|
||||||
ASSERT_FALSE(baked.empty());
|
ASSERT_FALSE(baked.empty());
|
||||||
EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore)
|
EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore)
|
||||||
<< "splitting the shared type must not leave a dangling or duplicate declaration:\n"
|
<< "splitting the shared type must not leave a dangling or duplicate declaration:\n"
|
||||||
@@ -3934,11 +3911,10 @@ void main() {
|
|||||||
ASSERT_EQ(CountSpirvOpcode(DisassembleSpirv(spirv), "OpTypeImage"), 2u)
|
ASSERT_EQ(CountSpirvOpcode(DisassembleSpirv(spirv), "OpTypeImage"), 2u)
|
||||||
<< "the fixture needs one Unknown-format and one r32ui image type:\n" << DisassembleSpirv(spirv);
|
<< "the fixture needs one Unknown-format and one r32ui image type:\n" << DisassembleSpirv(spirv);
|
||||||
|
|
||||||
SpirvValidationScope validationOn(true);
|
|
||||||
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
||||||
|
|
||||||
Vector<Uint32> baked;
|
Vector<Uint32> baked;
|
||||||
ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"formatless", kGlR32ui}}, baked));
|
ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"formatless", kGlR32ui}}, baked, true));
|
||||||
ASSERT_FALSE(baked.empty());
|
ASSERT_FALSE(baked.empty());
|
||||||
EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore)
|
EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore)
|
||||||
<< "the baked image collided with the module's own r32ui image and left a duplicate type:\n"
|
<< "the baked image collided with the module's own r32ui image and left a duplicate type:\n"
|
||||||
@@ -3963,11 +3939,10 @@ void main() {
|
|||||||
ASSERT_FALSE(spirv.empty());
|
ASSERT_FALSE(spirv.empty());
|
||||||
ASSERT_TRUE(ShaderCompiler::DeclaresFormatlessStorageImage(spirv));
|
ASSERT_TRUE(ShaderCompiler::DeclaresFormatlessStorageImage(spirv));
|
||||||
|
|
||||||
SpirvValidationScope validationOn(true);
|
|
||||||
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount();
|
||||||
|
|
||||||
Vector<Uint32> baked;
|
Vector<Uint32> baked;
|
||||||
ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"imgs", kGlR32ui}}, baked));
|
ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"imgs", kGlR32ui}}, baked, true));
|
||||||
ASSERT_FALSE(baked.empty());
|
ASSERT_FALSE(baked.empty());
|
||||||
EXPECT_FALSE(ShaderCompiler::DeclaresFormatlessStorageImage(baked)) << DisassembleSpirv(baked);
|
EXPECT_FALSE(ShaderCompiler::DeclaresFormatlessStorageImage(baked)) << DisassembleSpirv(baked);
|
||||||
EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore)
|
EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore)
|
||||||
@@ -3993,7 +3968,7 @@ void main() { fragColor = texture(uni_sampler, vUv); }
|
|||||||
<< "a sampled image must not read as a format-less STORAGE image:\n" << DisassembleSpirv(spirv);
|
<< "a sampled image must not read as a format-less STORAGE image:\n" << DisassembleSpirv(spirv);
|
||||||
|
|
||||||
Vector<Uint32> baked;
|
Vector<Uint32> baked;
|
||||||
ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_sampler", kGlR32ui}}, baked));
|
ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_sampler", kGlR32ui}}, baked, true));
|
||||||
EXPECT_EQ(baked, spirv) << "a sampled image must pass through byte for byte";
|
EXPECT_EQ(baked, spirv) << "a sampled image must pass through byte for byte";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -448,39 +448,6 @@ TEST_F(QueryTest, BackendResultsPropagateThroughFrontend) {
|
|||||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
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
|
// Environment-agnostic property test for the env -> ConfigLoader -> Features
|
||||||
// chain: whatever MOBILEGL_DISABLE_TIMERQUERY is set to in the environment of
|
// chain: whatever MOBILEGL_DISABLE_TIMERQUERY is set to in the environment of
|
||||||
// this test process, MG_ConfigLoader::Init must have parsed it with the
|
// this test process, MG_ConfigLoader::Init must have parsed it with the
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
#include <MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h>
|
#include <MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h>
|
||||||
#include <MG_Backend/BackendObjects.h>
|
#include <MG_Backend/BackendObjects.h>
|
||||||
#include <MG_Impl/GLImpl/Getter/GL_Getter.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/RenderState/GL_RenderState.h>
|
||||||
#include <MG_Impl/GLImpl/Texture/GL_Texture.h>
|
#include <MG_Impl/GLImpl/Texture/GL_Texture.h>
|
||||||
#include <MG_Impl/GLImpl/VertexArray/Validators.h>
|
#include <MG_Impl/GLImpl/VertexArray/Validators.h>
|
||||||
@@ -1972,9 +1971,6 @@ namespace {
|
|||||||
MobileGL::Vector<GLuint> framebuffers;
|
MobileGL::Vector<GLuint> framebuffers;
|
||||||
MobileGL::Vector<GLuint> renderbuffers;
|
MobileGL::Vector<GLuint> renderbuffers;
|
||||||
MobileGL::Vector<GLuint> samplers;
|
MobileGL::Vector<GLuint> samplers;
|
||||||
MobileGL::Vector<GLuint> vertexArrays;
|
|
||||||
MobileGL::Vector<GLuint> programs;
|
|
||||||
MobileGL::Vector<GLuint> buffers;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
TwinDeletionSinks* g_twinDeletionSinks = nullptr;
|
TwinDeletionSinks* g_twinDeletionSinks = nullptr;
|
||||||
@@ -2001,24 +1997,6 @@ namespace {
|
|||||||
if (!g_twinDeletionSinks) return;
|
if (!g_twinDeletionSinks) return;
|
||||||
for (GLsizei i = 0; i < count; ++i) g_twinDeletionSinks->samplers.push_back(ids[i]);
|
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) {
|
void TW_BindFramebuffer(GLenum target, GLuint framebuffer) {
|
||||||
SG_Log("BindFramebuffer:" + std::to_string(target) + ":" + std::to_string(framebuffer));
|
SG_Log("BindFramebuffer:" + std::to_string(target) + ":" + std::to_string(framebuffer));
|
||||||
}
|
}
|
||||||
@@ -2040,12 +2018,6 @@ namespace {
|
|||||||
functions.glGenSamplers = TW_GenSamplers;
|
functions.glGenSamplers = TW_GenSamplers;
|
||||||
functions.glDeleteSamplers = TW_DeleteSamplers;
|
functions.glDeleteSamplers = TW_DeleteSamplers;
|
||||||
functions.glBindSampler = TW_BindSampler;
|
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;
|
functions.glGetError = SG_NoError;
|
||||||
MobileGL::MG_Backend::DirectGLES::SetGLESFuncsTable(functions);
|
MobileGL::MG_Backend::DirectGLES::SetGLESFuncsTable(functions);
|
||||||
g_twinDeletionSinks = &sinks;
|
g_twinDeletionSinks = &sinks;
|
||||||
@@ -2145,145 +2117,6 @@ 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) {
|
TEST(DirectGLESStateGuards, DefaultFramebufferBindGoesThroughShadow) {
|
||||||
using namespace MobileGL::MG_Backend::DirectGLES;
|
using namespace MobileGL::MG_Backend::DirectGLES;
|
||||||
ScopedStateGuardMocks mocks;
|
ScopedStateGuardMocks mocks;
|
||||||
|
|||||||
@@ -154,7 +154,6 @@ class DemoteFloat64Test : public ::testing::Test {
|
|||||||
protected:
|
protected:
|
||||||
void SetUp() override {
|
void SetUp() override {
|
||||||
MobileGL::Initialize();
|
MobileGL::Initialize();
|
||||||
ShaderCompiler::SetSpirvValidationEnabled(true);
|
|
||||||
m_validationFailuresAtStart = ShaderCompiler::SpirvValidationFailureCount();
|
m_validationFailuresAtStart = ShaderCompiler::SpirvValidationFailureCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +174,7 @@ TEST_F(DemoteFloat64Test, DemotesEveryWidthAndDropsTheCapability) {
|
|||||||
ASSERT_TRUE(DeclaresFloat64Capability(input));
|
ASSERT_TRUE(DeclaresFloat64Capability(input));
|
||||||
|
|
||||||
Vector<Uint32> output;
|
Vector<Uint32> output;
|
||||||
ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output));
|
ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output, true));
|
||||||
|
|
||||||
EXPECT_EQ(CountFloatTypesOfWidth(output, 64), 0u) << Disassemble(output);
|
EXPECT_EQ(CountFloatTypesOfWidth(output, 64), 0u) << Disassemble(output);
|
||||||
// And exactly one 32-bit float type survives: the merge has to happen, or spirv-val rejects
|
// And exactly one 32-bit float type survives: the merge has to happen, or spirv-val rejects
|
||||||
@@ -210,7 +209,7 @@ void main() {
|
|||||||
<< Disassemble(input);
|
<< Disassemble(input);
|
||||||
|
|
||||||
Vector<Uint32> output;
|
Vector<Uint32> output;
|
||||||
ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output));
|
ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output, true));
|
||||||
|
|
||||||
// std140 for the demoted members: float at 4, vec2 at 8, vec3 at 16 (aligned like a vec4),
|
// std140 for the demoted members: float at 4, vec2 at 8, vec3 at 16 (aligned like a vec4),
|
||||||
// vec4 at 32, mat4 at 48 with a 16-byte column stride, the array at 112 with the std140
|
// vec4 at 32, mat4 at 48 with a 16-byte column stride, the array at 112 with the std140
|
||||||
@@ -241,7 +240,7 @@ void main() {
|
|||||||
EXPECT_EQ(CollectOffsetsOf(input, "Ssbo"), (Vector<Uint32>{0, 32, 64})) << Disassemble(input);
|
EXPECT_EQ(CollectOffsetsOf(input, "Ssbo"), (Vector<Uint32>{0, 32, 64})) << Disassemble(input);
|
||||||
|
|
||||||
Vector<Uint32> output;
|
Vector<Uint32> output;
|
||||||
ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output));
|
ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output, true));
|
||||||
|
|
||||||
// std430, so the array packs at its element size rather than being rounded to 16: float at 0,
|
// std430, so the array packs at its element size rather than being rounded to 16: float at 0,
|
||||||
// vec4 at 16, float[4] at 32 with a 4-byte stride. A storage block must NOT come out std140,
|
// vec4 at 16, float[4] at 32 with a 4-byte stride. A storage block must NOT come out std140,
|
||||||
@@ -273,7 +272,7 @@ void main() {
|
|||||||
ASSERT_FALSE(before.empty());
|
ASSERT_FALSE(before.empty());
|
||||||
|
|
||||||
Vector<Uint32> output;
|
Vector<Uint32> output;
|
||||||
ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output));
|
ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output, true));
|
||||||
|
|
||||||
// Only the block that actually narrowed is re-laid-out. Touching the other one would be
|
// Only the block that actually narrowed is re-laid-out. Touching the other one would be
|
||||||
// churn at best, and a disagreement with glslang's own layout at worst.
|
// churn at best, and a disagreement with glslang's own layout at worst.
|
||||||
@@ -287,7 +286,7 @@ TEST_F(DemoteFloat64Test, FoldsTheConversionsThatBecameIdentities) {
|
|||||||
ASSERT_GT(CountFConverts(input), 0u) << "the fixture no longer converts between the two widths";
|
ASSERT_GT(CountFConverts(input), 0u) << "the fixture no longer converts between the two widths";
|
||||||
|
|
||||||
Vector<Uint32> output;
|
Vector<Uint32> output;
|
||||||
ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output));
|
ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output, true));
|
||||||
|
|
||||||
// SPIR-V requires the two component widths of an OpFConvert to differ, so every one of them
|
// SPIR-V requires the two component widths of an OpFConvert to differ, so every one of them
|
||||||
// has to be gone: both sides are 32 bits now.
|
// has to be gone: both sides are 32 bits now.
|
||||||
@@ -307,7 +306,7 @@ void main() {
|
|||||||
ASSERT_FALSE(input.empty());
|
ASSERT_FALSE(input.empty());
|
||||||
|
|
||||||
Vector<Uint32> output;
|
Vector<Uint32> output;
|
||||||
ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output));
|
ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output, true));
|
||||||
|
|
||||||
// A 64-bit literal is two words wide and a 32-bit one is a single word, so a constant left
|
// A 64-bit literal is two words wide and a 32-bit one is a single word, so a constant left
|
||||||
// unconverted is not merely imprecise - it is an unparseable instruction. Disassembling both
|
// unconverted is not merely imprecise - it is an unparseable instruction. Disassembling both
|
||||||
@@ -326,7 +325,7 @@ void main() { gl_Position = inPos; }
|
|||||||
ASSERT_FALSE(input.empty());
|
ASSERT_FALSE(input.empty());
|
||||||
|
|
||||||
Vector<Uint32> output;
|
Vector<Uint32> output;
|
||||||
ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output));
|
ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output, true));
|
||||||
// The pass reports SuccessWithoutChange here, and SPIRV-Tools asserts (in assert-enabled
|
// The pass reports SuccessWithoutChange here, and SPIRV-Tools asserts (in assert-enabled
|
||||||
// builds) that such a run round-trips byte-identically.
|
// builds) that such a run round-trips byte-identically.
|
||||||
EXPECT_EQ(output, input);
|
EXPECT_EQ(output, input);
|
||||||
@@ -351,7 +350,7 @@ void main() {
|
|||||||
ASSERT_EQ(CountFloatTypesOfWidth(input, 64), 1u) << Disassemble(input);
|
ASSERT_EQ(CountFloatTypesOfWidth(input, 64), 1u) << Disassemble(input);
|
||||||
|
|
||||||
Vector<Uint32> output;
|
Vector<Uint32> output;
|
||||||
ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output));
|
ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output, true));
|
||||||
EXPECT_EQ(output, input) << Disassemble(output);
|
EXPECT_EQ(output, input) << Disassemble(output);
|
||||||
EXPECT_TRUE(ShaderCompiler::ModuleDeclaresFloat64(output));
|
EXPECT_TRUE(ShaderCompiler::ModuleDeclaresFloat64(output));
|
||||||
}
|
}
|
||||||
@@ -362,7 +361,7 @@ TEST_F(DemoteFloat64Test, ModuleDeclaresFloat64AnswersBothWays) {
|
|||||||
EXPECT_TRUE(ShaderCompiler::ModuleDeclaresFloat64(wide));
|
EXPECT_TRUE(ShaderCompiler::ModuleDeclaresFloat64(wide));
|
||||||
|
|
||||||
Vector<Uint32> demoted;
|
Vector<Uint32> demoted;
|
||||||
ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(wide, demoted));
|
ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(wide, demoted, true));
|
||||||
EXPECT_FALSE(ShaderCompiler::ModuleDeclaresFloat64(demoted));
|
EXPECT_FALSE(ShaderCompiler::ModuleDeclaresFloat64(demoted));
|
||||||
|
|
||||||
EXPECT_FALSE(ShaderCompiler::ModuleDeclaresFloat64({}));
|
EXPECT_FALSE(ShaderCompiler::ModuleDeclaresFloat64({}));
|
||||||
@@ -375,7 +374,7 @@ TEST_F(DemoteFloat64Test, TheSharedChainDemotesToo) {
|
|||||||
ASSERT_FALSE(input.empty());
|
ASSERT_FALSE(input.empty());
|
||||||
|
|
||||||
Vector<Uint32> output;
|
Vector<Uint32> output;
|
||||||
ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(input, output));
|
ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(input, output, true, true));
|
||||||
EXPECT_FALSE(ShaderCompiler::ModuleDeclaresFloat64(output)) << Disassemble(output);
|
EXPECT_FALSE(ShaderCompiler::ModuleDeclaresFloat64(output)) << Disassemble(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,7 +458,7 @@ TEST_P(DemoteFloat64EsslTest, TheDemotedModuleCanBeEmittedAsEssl) {
|
|||||||
ASSERT_FALSE(input.empty());
|
ASSERT_FALSE(input.empty());
|
||||||
|
|
||||||
Vector<Uint32> output;
|
Vector<Uint32> output;
|
||||||
ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(input, output));
|
ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(input, output, true, true));
|
||||||
|
|
||||||
SpvcSession session(output, SessionUsageBit::Transpile);
|
SpvcSession session(output, SessionUsageBit::Transpile);
|
||||||
spvc_compiler_options options;
|
spvc_compiler_options options;
|
||||||
@@ -482,7 +481,7 @@ TEST_P(DemoteFloat64EsslTest, TheDemotedModuleCanBeEmittedAsEssl) {
|
|||||||
TEST_F(DemoteFloat64Test, RejectsGarbageInput) {
|
TEST_F(DemoteFloat64Test, RejectsGarbageInput) {
|
||||||
const Vector<Uint32> notSpirv{0xdeadbeefu, 0u, 0u, 0u, 0u};
|
const Vector<Uint32> notSpirv{0xdeadbeefu, 0u, 0u, 0u, 0u};
|
||||||
Vector<Uint32> output;
|
Vector<Uint32> output;
|
||||||
EXPECT_FALSE(ShaderCompiler::DemoteFloat64ToFloat32(notSpirv, output));
|
EXPECT_FALSE(ShaderCompiler::DemoteFloat64ToFloat32(notSpirv, output, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
// EliminateFloatEqualsZeroPass turns a comparison against 0.0 into an epsilon test, a
|
// EliminateFloatEqualsZeroPass turns a comparison against 0.0 into an epsilon test, a
|
||||||
@@ -502,7 +501,7 @@ namespace {
|
|||||||
EXPECT_FALSE(input.empty());
|
EXPECT_FALSE(input.empty());
|
||||||
if (input.empty()) return false;
|
if (input.empty()) return false;
|
||||||
Vector<Uint32> output;
|
Vector<Uint32> output;
|
||||||
EXPECT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(input, output));
|
EXPECT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(input, output, true, true));
|
||||||
return Disassemble(output).find("FAbs") != String::npos;
|
return Disassemble(output).find("FAbs") != String::npos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,6 @@ class FlattenXfbInterfaceBlocksTest : public ::testing::Test {
|
|||||||
protected:
|
protected:
|
||||||
void SetUp() override {
|
void SetUp() override {
|
||||||
MobileGL::Initialize();
|
MobileGL::Initialize();
|
||||||
ShaderCompiler::SetSpirvValidationEnabled(true);
|
|
||||||
m_validationFailuresAtStart = ShaderCompiler::SpirvValidationFailureCount();
|
m_validationFailuresAtStart = ShaderCompiler::SpirvValidationFailureCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +115,7 @@ TEST_F(FlattenXfbInterfaceBlocksTest, FlattensACapturedBlockIntoOneVariablePerMe
|
|||||||
|
|
||||||
std::set<String> flattened;
|
std::set<String> flattened;
|
||||||
Vector<Uint32> output;
|
Vector<Uint32> output;
|
||||||
ASSERT_TRUE(ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(input, {"StageData"}, flattened, output));
|
ASSERT_TRUE(ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(input, {"StageData"}, flattened, output, true));
|
||||||
ASSERT_FALSE(output.empty());
|
ASSERT_FALSE(output.empty());
|
||||||
EXPECT_EQ(flattened, (std::set<String>{"StageData"}));
|
EXPECT_EQ(flattened, (std::set<String>{"StageData"}));
|
||||||
|
|
||||||
@@ -145,7 +144,7 @@ TEST_F(FlattenXfbInterfaceBlocksTest, TheEmittedDeclarationIsAPlainArrayNotABloc
|
|||||||
|
|
||||||
std::set<String> flattened;
|
std::set<String> flattened;
|
||||||
Vector<Uint32> output;
|
Vector<Uint32> output;
|
||||||
ASSERT_TRUE(ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(input, {"StageData"}, flattened, output));
|
ASSERT_TRUE(ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(input, {"StageData"}, flattened, output, true));
|
||||||
|
|
||||||
const String after = Transpile(output);
|
const String after = Transpile(output);
|
||||||
EXPECT_NE(after.find("StageData_attrib[16]"), String::npos) << after;
|
EXPECT_NE(after.find("StageData_attrib[16]"), String::npos) << after;
|
||||||
@@ -162,7 +161,7 @@ TEST_F(FlattenXfbInterfaceBlocksTest, GivesEachMemberItsOwnConsecutiveLocations)
|
|||||||
|
|
||||||
std::set<String> flattened;
|
std::set<String> flattened;
|
||||||
Vector<Uint32> output;
|
Vector<Uint32> output;
|
||||||
ASSERT_TRUE(ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(input, {"StageData"}, flattened, output));
|
ASSERT_TRUE(ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(input, {"StageData"}, flattened, output, true));
|
||||||
ASSERT_FALSE(output.empty());
|
ASSERT_FALSE(output.empty());
|
||||||
|
|
||||||
const String dis = Disassemble(output);
|
const String dis = Disassemble(output);
|
||||||
@@ -184,7 +183,7 @@ TEST_F(FlattenXfbInterfaceBlocksTest, LeavesABlockNoCaptureNamesAlone) {
|
|||||||
std::set<String> flattened;
|
std::set<String> flattened;
|
||||||
Vector<Uint32> output;
|
Vector<Uint32> output;
|
||||||
ASSERT_TRUE(
|
ASSERT_TRUE(
|
||||||
ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(input, {"SomeOtherBlock"}, flattened, output));
|
ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(input, {"SomeOtherBlock"}, flattened, output, true));
|
||||||
EXPECT_TRUE(flattened.empty());
|
EXPECT_TRUE(flattened.empty());
|
||||||
|
|
||||||
const String after = Transpile(output);
|
const String after = Transpile(output);
|
||||||
@@ -200,7 +199,7 @@ TEST_F(FlattenXfbInterfaceBlocksTest, DeclinesAnEmptyRequestWithoutRewriting) {
|
|||||||
|
|
||||||
std::set<String> flattened;
|
std::set<String> flattened;
|
||||||
Vector<Uint32> output;
|
Vector<Uint32> output;
|
||||||
EXPECT_FALSE(ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(input, {}, flattened, output));
|
EXPECT_FALSE(ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(input, {}, flattened, output, true));
|
||||||
EXPECT_TRUE(flattened.empty());
|
EXPECT_TRUE(flattened.empty());
|
||||||
EXPECT_TRUE(output.empty());
|
EXPECT_TRUE(output.empty());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -369,12 +369,6 @@ namespace MobileGL {
|
|||||||
return allSpirv;
|
return allSpirv;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -1 unresolved, 0 off, 1 on. Resolved once from MOBILEGL_VALIDATE_SPIRV on first
|
|
||||||
// use. A live getenv rather than an MG_Config::Features field, for the same reason
|
|
||||||
// Config.h already exempts MOBILEGL_LOG_FILE_PATH: suites like SpirvPassTest never
|
|
||||||
// run MobileGL::Initialize(), and every Initialize() re-runs MG_ConfigLoader::Init,
|
|
||||||
// which would clobber a programmatic override stored in the feature table.
|
|
||||||
static std::atomic<int> g_validateSpirv{-1};
|
|
||||||
// Total validation failures observed this process. This latch - not the wrappers'
|
// Total validation failures observed this process. This latch - not the wrappers'
|
||||||
// return values - is the test-lane signal: validation must never change what a
|
// return values - is the test-lane signal: validation must never change what a
|
||||||
// wrapper returns, or the validating lanes would render differently from the
|
// wrapper returns, or the validating lanes would render differently from the
|
||||||
@@ -383,28 +377,6 @@ namespace MobileGL {
|
|||||||
static std::atomic<Uint64> g_spirvValidationFailures{0};
|
static std::atomic<Uint64> g_spirvValidationFailures{0};
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// Test lanes (desktop/CI/WSL) validate by default; device builds do not -
|
|
||||||
// validation costs real time per module, and on device the driver is the
|
|
||||||
// final validator anyway. MOBILEGL_VALIDATE_SPIRV overrides in either
|
|
||||||
// direction, using the ConfigLoader truthy rule.
|
|
||||||
constexpr bool kValidateSpirvDefault =
|
|
||||||
#if defined(__ANDROID__)
|
|
||||||
false;
|
|
||||||
#else
|
|
||||||
true;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool IsTruthySpirvEnvValue(const char* value) {
|
|
||||||
if (value == nullptr || value[0] == '\0') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
String lowered(value);
|
|
||||||
for (auto& c : lowered) {
|
|
||||||
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
||||||
}
|
|
||||||
return lowered != "0" && lowered != "false";
|
|
||||||
}
|
|
||||||
|
|
||||||
// spirv-tools' validator lazily constructs function-local static tables on
|
// spirv-tools' validator lazily constructs function-local static tables on
|
||||||
// its first run, which on this codebase happens on a ShaderCompilePool
|
// its first run, which on this codebase happens on a ShaderCompilePool
|
||||||
// worker. Function-local statics are destroyed in reverse construction
|
// worker. Function-local statics are destroyed in reverse construction
|
||||||
@@ -445,11 +417,6 @@ namespace MobileGL {
|
|||||||
tools.Validate(warmup);
|
tools.Validate(warmup);
|
||||||
}
|
}
|
||||||
std::atexit(+[] {
|
std::atexit(+[] {
|
||||||
// Flip validation off first: a validator table this warmup does
|
|
||||||
// not know about (a future spirv-tools bump) would still be
|
|
||||||
// destroyed before this handler, and workers must stop entering
|
|
||||||
// Validate before the drain waits for them.
|
|
||||||
g_validateSpirv.store(0, std::memory_order_release);
|
|
||||||
Async::ShaderCompilePool::StopAndDrainProcessPoolAtExit();
|
Async::ShaderCompilePool::StopAndDrainProcessPoolAtExit();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -478,10 +445,10 @@ namespace MobileGL {
|
|||||||
// Validation is decoupled from control flow on purpose: a failure logs and
|
// Validation is decoupled from control flow on purpose: a failure logs and
|
||||||
// bumps the latch, and the caller proceeds exactly as the shipping (non-
|
// bumps the latch, and the caller proceeds exactly as the shipping (non-
|
||||||
// validating) configuration would. Tests assert on the latch delta.
|
// validating) configuration would. Tests assert on the latch delta.
|
||||||
void ValidateOrLatch(const char* site, const Vector<Uint32>& binary) {
|
void ValidateOrLatch(const char* site, const Vector<Uint32>& binary,
|
||||||
if (!ShaderCompiler::SpirvValidationEnabled()) {
|
const bool enableSpirvValidation) {
|
||||||
return;
|
if (!enableSpirvValidation) return;
|
||||||
}
|
PinValidatorTablesForProcessExit();
|
||||||
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1);
|
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1);
|
||||||
tools.SetMessageConsumer(MakeSpirvMessageConsumer(site));
|
tools.SetMessageConsumer(MakeSpirvMessageConsumer(site));
|
||||||
if (!tools.Validate(binary)) {
|
if (!tools.Validate(binary)) {
|
||||||
@@ -502,40 +469,24 @@ namespace MobileGL {
|
|||||||
// spirv-tools drops pass diagnostics on the floor.
|
// spirv-tools drops pass diagnostics on the floor.
|
||||||
bool RunOptimizerChecked(const char* site, spvtools::Optimizer& optimizer,
|
bool RunOptimizerChecked(const char* site, spvtools::Optimizer& optimizer,
|
||||||
const Vector<Uint32>& inputBinary,
|
const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary) {
|
Vector<uint32_t>& outputBinary, const bool validateOutput,
|
||||||
|
const bool enableSpirvValidation) {
|
||||||
spvtools::OptimizerOptions options;
|
spvtools::OptimizerOptions options;
|
||||||
options.set_run_validator(false);
|
options.set_run_validator(false);
|
||||||
optimizer.SetMessageConsumer(MakeSpirvMessageConsumer(site));
|
optimizer.SetMessageConsumer(MakeSpirvMessageConsumer(site));
|
||||||
if (!optimizer.Run(inputBinary.data(), inputBinary.size(), &outputBinary, options)) {
|
if (!optimizer.Run(inputBinary.data(), inputBinary.size(), &outputBinary, options)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ValidateOrLatch(site, outputBinary);
|
if (validateOutput) {
|
||||||
|
ValidateOrLatch(site, outputBinary, enableSpirvValidation);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool ShaderCompiler::SpirvValidationEnabled() {
|
void ShaderCompiler::PrepareSpirvValidation() {
|
||||||
int state = g_validateSpirv.load(std::memory_order_acquire);
|
|
||||||
if (state < 0) {
|
|
||||||
const char* env = std::getenv("MOBILEGL_VALIDATE_SPIRV");
|
|
||||||
const bool resolved = env != nullptr ? IsTruthySpirvEnvValue(env) : kValidateSpirvDefault;
|
|
||||||
int expected = -1;
|
|
||||||
g_validateSpirv.compare_exchange_strong(expected, resolved ? 1 : 0,
|
|
||||||
std::memory_order_acq_rel);
|
|
||||||
state = g_validateSpirv.load(std::memory_order_acquire);
|
|
||||||
if (state == 1) {
|
|
||||||
PinValidatorTablesForProcessExit();
|
PinValidatorTablesForProcessExit();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return state == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderCompiler::SetSpirvValidationEnabled(bool enabled) {
|
|
||||||
g_validateSpirv.store(enabled ? 1 : 0, std::memory_order_release);
|
|
||||||
if (enabled) {
|
|
||||||
PinValidatorTablesForProcessExit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint64 ShaderCompiler::NoteSpirvValidationFailure() {
|
Uint64 ShaderCompiler::NoteSpirvValidationFailure() {
|
||||||
return g_spirvValidationFailures.fetch_add(1, std::memory_order_relaxed) + 1;
|
return g_spirvValidationFailures.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||||
@@ -604,16 +555,19 @@ namespace MobileGL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ShaderCompiler::DemoteFloat64ToFloat32(const Vector<Uint32>& inputBinary,
|
bool ShaderCompiler::DemoteFloat64ToFloat32(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary) {
|
Vector<uint32_t>& outputBinary,
|
||||||
|
const bool enableSpirvValidation) {
|
||||||
using namespace spvtools;
|
using namespace spvtools;
|
||||||
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
||||||
optimizer.RegisterPass(DemoteFloat64Pass::CreateDemoteFloat64Pass());
|
optimizer.RegisterPass(DemoteFloat64Pass::CreateDemoteFloat64Pass());
|
||||||
|
|
||||||
return RunOptimizerChecked("DemoteFloat64ToFloat32", optimizer, inputBinary, outputBinary);
|
return RunOptimizerChecked("DemoteFloat64ToFloat32", optimizer, inputBinary, outputBinary, true, enableSpirvValidation);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShaderCompiler::SanitizeAndOptimizeBinary(const Vector<Uint32>& inputBinary,
|
bool ShaderCompiler::SanitizeAndOptimizeBinary(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary) {
|
Vector<uint32_t>& outputBinary,
|
||||||
|
const bool validateOutput,
|
||||||
|
const bool enableSpirvValidation) {
|
||||||
using namespace spvtools;
|
using namespace spvtools;
|
||||||
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
||||||
|
|
||||||
@@ -663,38 +617,41 @@ namespace MobileGL {
|
|||||||
optimizer.RegisterPass(DemoteFloat64Pass::CreateDemoteFloat64Pass());
|
optimizer.RegisterPass(DemoteFloat64Pass::CreateDemoteFloat64Pass());
|
||||||
|
|
||||||
return RunOptimizerChecked("SanitizeAndOptimizeBinary", optimizer, inputBinary,
|
return RunOptimizerChecked("SanitizeAndOptimizeBinary", optimizer, inputBinary,
|
||||||
outputBinary);
|
outputBinary, validateOutput, enableSpirvValidation);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShaderCompiler::LowerDrawParametersForEssl(const Vector<Uint32>& inputBinary,
|
bool ShaderCompiler::LowerDrawParametersForEssl(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary) {
|
Vector<uint32_t>& outputBinary,
|
||||||
|
const bool enableSpirvValidation) {
|
||||||
using namespace spvtools;
|
using namespace spvtools;
|
||||||
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
||||||
optimizer.RegisterPass(LowerDrawParametersPass::CreateLowerDrawParametersPass());
|
optimizer.RegisterPass(LowerDrawParametersPass::CreateLowerDrawParametersPass());
|
||||||
|
|
||||||
return RunOptimizerChecked("LowerDrawParametersForEssl", optimizer, inputBinary,
|
return RunOptimizerChecked("LowerDrawParametersForEssl", optimizer, inputBinary,
|
||||||
outputBinary);
|
outputBinary, true, enableSpirvValidation);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShaderCompiler::SplitArrayVertexInputsForEssl(const Vector<Uint32>& inputBinary,
|
bool ShaderCompiler::SplitArrayVertexInputsForEssl(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary) {
|
Vector<uint32_t>& outputBinary,
|
||||||
|
const bool enableSpirvValidation) {
|
||||||
using namespace spvtools;
|
using namespace spvtools;
|
||||||
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
||||||
optimizer.RegisterPass(SplitArrayVertexInputsPass::CreateSplitArrayVertexInputsPass());
|
optimizer.RegisterPass(SplitArrayVertexInputsPass::CreateSplitArrayVertexInputsPass());
|
||||||
|
|
||||||
return RunOptimizerChecked("SplitArrayVertexInputsForEssl", optimizer, inputBinary,
|
return RunOptimizerChecked("SplitArrayVertexInputsForEssl", optimizer, inputBinary,
|
||||||
outputBinary);
|
outputBinary, true, enableSpirvValidation);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShaderCompiler::BakeImageFormatsForEssl(const Vector<Uint32>& inputBinary,
|
bool ShaderCompiler::BakeImageFormatsForEssl(const Vector<Uint32>& inputBinary,
|
||||||
const UnorderedMap<String, Uint>& glFormatByName,
|
const UnorderedMap<String, Uint>& glFormatByName,
|
||||||
Vector<uint32_t>& outputBinary) {
|
Vector<uint32_t>& outputBinary,
|
||||||
|
const bool enableSpirvValidation) {
|
||||||
using namespace spvtools;
|
using namespace spvtools;
|
||||||
if (glFormatByName.empty()) return false;
|
if (glFormatByName.empty()) return false;
|
||||||
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
||||||
optimizer.RegisterPass(BakeImageFormatsPass::CreateBakeImageFormatsPass(glFormatByName));
|
optimizer.RegisterPass(BakeImageFormatsPass::CreateBakeImageFormatsPass(glFormatByName));
|
||||||
|
|
||||||
return RunOptimizerChecked("BakeImageFormatsForEssl", optimizer, inputBinary, outputBinary);
|
return RunOptimizerChecked("BakeImageFormatsForEssl", optimizer, inputBinary, outputBinary, true, enableSpirvValidation);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShaderCompiler::DeclaresFormatlessStorageImage(const Vector<Uint32>& binary) {
|
bool ShaderCompiler::DeclaresFormatlessStorageImage(const Vector<Uint32>& binary) {
|
||||||
@@ -718,7 +675,8 @@ namespace MobileGL {
|
|||||||
bool ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(const Vector<Uint32>& inputBinary,
|
bool ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(const Vector<Uint32>& inputBinary,
|
||||||
const std::set<String>& blockNames,
|
const std::set<String>& blockNames,
|
||||||
std::set<String>& flattenedBlockNames,
|
std::set<String>& flattenedBlockNames,
|
||||||
Vector<uint32_t>& outputBinary) {
|
Vector<uint32_t>& outputBinary,
|
||||||
|
const bool enableSpirvValidation) {
|
||||||
using namespace spvtools;
|
using namespace spvtools;
|
||||||
if (blockNames.empty()) return false;
|
if (blockNames.empty()) return false;
|
||||||
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
||||||
@@ -726,7 +684,7 @@ namespace MobileGL {
|
|||||||
blockNames, &flattenedBlockNames));
|
blockNames, &flattenedBlockNames));
|
||||||
|
|
||||||
return RunOptimizerChecked("FlattenXfbInterfaceBlocksForEssl", optimizer, inputBinary,
|
return RunOptimizerChecked("FlattenXfbInterfaceBlocksForEssl", optimizer, inputBinary,
|
||||||
outputBinary);
|
outputBinary, true, enableSpirvValidation);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShaderCompiler::RewriteXfbCaptureNameForFlattenedBlock(
|
bool ShaderCompiler::RewriteXfbCaptureNameForFlattenedBlock(
|
||||||
@@ -736,48 +694,53 @@ namespace MobileGL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ShaderCompiler::PackDoubleVertexInputsForVulkan(const Vector<Uint32>& inputBinary,
|
bool ShaderCompiler::PackDoubleVertexInputsForVulkan(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary) {
|
Vector<uint32_t>& outputBinary,
|
||||||
|
const bool enableSpirvValidation) {
|
||||||
using namespace spvtools;
|
using namespace spvtools;
|
||||||
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
||||||
optimizer.RegisterPass(PackDoubleVertexInputsPass::CreatePackDoubleVertexInputsPass());
|
optimizer.RegisterPass(PackDoubleVertexInputsPass::CreatePackDoubleVertexInputsPass());
|
||||||
|
|
||||||
return RunOptimizerChecked("PackDoubleVertexInputsForVulkan", optimizer, inputBinary,
|
return RunOptimizerChecked("PackDoubleVertexInputsForVulkan", optimizer, inputBinary,
|
||||||
outputBinary);
|
outputBinary, true, enableSpirvValidation);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShaderCompiler::StripUboMemberRelaxedPrecisionForEssl(const Vector<Uint32>& inputBinary,
|
bool ShaderCompiler::StripUboMemberRelaxedPrecisionForEssl(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary) {
|
Vector<uint32_t>& outputBinary,
|
||||||
|
const bool enableSpirvValidation) {
|
||||||
using namespace spvtools;
|
using namespace spvtools;
|
||||||
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
||||||
optimizer.RegisterPass(
|
optimizer.RegisterPass(
|
||||||
StripUboMemberRelaxedPrecisionPass::CreateStripUboMemberRelaxedPrecisionPass());
|
StripUboMemberRelaxedPrecisionPass::CreateStripUboMemberRelaxedPrecisionPass());
|
||||||
|
|
||||||
return RunOptimizerChecked("StripUboMemberRelaxedPrecisionForEssl", optimizer,
|
return RunOptimizerChecked("StripUboMemberRelaxedPrecisionForEssl", optimizer,
|
||||||
inputBinary, outputBinary);
|
inputBinary, outputBinary, true, enableSpirvValidation);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShaderCompiler::StripNoPerspectiveForEssl(const Vector<Uint32>& inputBinary,
|
bool ShaderCompiler::StripNoPerspectiveForEssl(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary) {
|
Vector<uint32_t>& outputBinary,
|
||||||
|
const bool enableSpirvValidation) {
|
||||||
using namespace spvtools;
|
using namespace spvtools;
|
||||||
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
||||||
optimizer.RegisterPass(StripNoPerspectivePass::CreateStripNoPerspectivePass());
|
optimizer.RegisterPass(StripNoPerspectivePass::CreateStripNoPerspectivePass());
|
||||||
|
|
||||||
return RunOptimizerChecked("StripNoPerspectiveForEssl", optimizer, inputBinary,
|
return RunOptimizerChecked("StripNoPerspectiveForEssl", optimizer, inputBinary,
|
||||||
outputBinary);
|
outputBinary, true, enableSpirvValidation);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShaderCompiler::EmulateNoPerspectiveForEssl(const Vector<Uint32>& inputBinary,
|
bool ShaderCompiler::EmulateNoPerspectiveForEssl(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary) {
|
Vector<uint32_t>& outputBinary,
|
||||||
|
const bool enableSpirvValidation) {
|
||||||
using namespace spvtools;
|
using namespace spvtools;
|
||||||
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
||||||
optimizer.RegisterPass(EmulateNoPerspectivePass::CreateEmulateNoPerspectivePass());
|
optimizer.RegisterPass(EmulateNoPerspectivePass::CreateEmulateNoPerspectivePass());
|
||||||
|
|
||||||
return RunOptimizerChecked("EmulateNoPerspectiveForEssl", optimizer, inputBinary,
|
return RunOptimizerChecked("EmulateNoPerspectiveForEssl", optimizer, inputBinary,
|
||||||
outputBinary);
|
outputBinary, true, enableSpirvValidation);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(const Vector<Uint32>& inputBinary,
|
bool ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary) {
|
Vector<uint32_t>& outputBinary,
|
||||||
|
const bool enableSpirvValidation) {
|
||||||
using namespace spvtools;
|
using namespace spvtools;
|
||||||
|
|
||||||
// Detection gates everything: a module with no dynamically indexed fragment
|
// Detection gates everything: a module with no dynamically indexed fragment
|
||||||
@@ -810,7 +773,7 @@ namespace MobileGL {
|
|||||||
|
|
||||||
Vector<uint32_t> folded;
|
Vector<uint32_t> folded;
|
||||||
if (!RunOptimizerChecked("LegalizeFragmentOutputIndexingForEssl.fold", folder, inputBinary,
|
if (!RunOptimizerChecked("LegalizeFragmentOutputIndexingForEssl.fold", folder, inputBinary,
|
||||||
folded) ||
|
folded, true, enableSpirvValidation) ||
|
||||||
folded.empty()) {
|
folded.empty()) {
|
||||||
// Fail open onto the fallback rather than onto the illegal module.
|
// Fail open onto the fallback rather than onto the illegal module.
|
||||||
folded = inputBinary;
|
folded = inputBinary;
|
||||||
@@ -829,7 +792,7 @@ namespace MobileGL {
|
|||||||
lowerer.RegisterPass(CreateAggressiveDCEPass(false));
|
lowerer.RegisterPass(CreateAggressiveDCEPass(false));
|
||||||
|
|
||||||
if (!RunOptimizerChecked("LegalizeFragmentOutputIndexingForEssl.lower", lowerer, folded,
|
if (!RunOptimizerChecked("LegalizeFragmentOutputIndexingForEssl.lower", lowerer, folded,
|
||||||
outputBinary) ||
|
outputBinary, true, enableSpirvValidation) ||
|
||||||
outputBinary.empty()) {
|
outputBinary.empty()) {
|
||||||
outputBinary = folded;
|
outputBinary = folded;
|
||||||
return true;
|
return true;
|
||||||
@@ -846,16 +809,17 @@ namespace MobileGL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ShaderCompiler::LowerRectImages(const Vector<Uint32>& inputBinary,
|
bool ShaderCompiler::LowerRectImages(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary) {
|
Vector<uint32_t>& outputBinary,
|
||||||
|
const bool enableSpirvValidation) {
|
||||||
using namespace spvtools;
|
using namespace spvtools;
|
||||||
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
||||||
optimizer.RegisterPass(NormalizeRectCoordinatesPass::CreateNormalizeRectCoordinatesPass());
|
optimizer.RegisterPass(NormalizeRectCoordinatesPass::CreateNormalizeRectCoordinatesPass());
|
||||||
|
|
||||||
return RunOptimizerChecked("LowerRectImages", optimizer, inputBinary, outputBinary);
|
return RunOptimizerChecked("LowerRectImages", optimizer, inputBinary, outputBinary, true, enableSpirvValidation);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShaderCompiler::Lower1DArrayImagesForEssl(const Vector<Uint32>& inputBinary,
|
bool ShaderCompiler::Lower1DArrayImagesForEssl(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary) {
|
Vector<uint32_t>& outputBinary, const bool enableSpirvValidation) {
|
||||||
using namespace spvtools;
|
using namespace spvtools;
|
||||||
|
|
||||||
// Declined rather than half-translated: after the rewrite the image is a 2D
|
// Declined rather than half-translated: after the rewrite the image is a 2D
|
||||||
@@ -897,40 +861,41 @@ namespace MobileGL {
|
|||||||
// second Shader. Deduplicating afterwards collapses all three at once.
|
// second Shader. Deduplicating afterwards collapses all three at once.
|
||||||
optimizer.RegisterPass(CreateRemoveDuplicatesPass());
|
optimizer.RegisterPass(CreateRemoveDuplicatesPass());
|
||||||
|
|
||||||
return RunOptimizerChecked("Lower1DArrayImagesForEssl", optimizer, inputBinary, outputBinary);
|
return RunOptimizerChecked("Lower1DArrayImagesForEssl", optimizer, inputBinary, outputBinary, true, enableSpirvValidation);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShaderCompiler::RebaseInstanceIndexForVulkan(const Vector<Uint32>& inputBinary,
|
bool ShaderCompiler::RebaseInstanceIndexForVulkan(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary) {
|
Vector<uint32_t>& outputBinary, const bool enableSpirvValidation) {
|
||||||
using namespace spvtools;
|
using namespace spvtools;
|
||||||
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
||||||
optimizer.RegisterPass(RebaseInstanceIndexPass::CreateRebaseInstanceIndexPass());
|
optimizer.RegisterPass(RebaseInstanceIndexPass::CreateRebaseInstanceIndexPass());
|
||||||
|
|
||||||
return RunOptimizerChecked("RebaseInstanceIndexForVulkan", optimizer, inputBinary,
|
return RunOptimizerChecked("RebaseInstanceIndexForVulkan", optimizer, inputBinary,
|
||||||
outputBinary);
|
outputBinary, true, enableSpirvValidation);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShaderCompiler::ZeroBaseVertexForVulkan(const Vector<Uint32>& inputBinary,
|
bool ShaderCompiler::ZeroBaseVertexForVulkan(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary) {
|
Vector<uint32_t>& outputBinary, const bool enableSpirvValidation) {
|
||||||
using namespace spvtools;
|
using namespace spvtools;
|
||||||
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
||||||
optimizer.RegisterPass(ZeroBaseVertexPass::CreateZeroBaseVertexPass());
|
optimizer.RegisterPass(ZeroBaseVertexPass::CreateZeroBaseVertexPass());
|
||||||
|
|
||||||
return RunOptimizerChecked("ZeroBaseVertexForVulkan", optimizer, inputBinary, outputBinary);
|
return RunOptimizerChecked("ZeroBaseVertexForVulkan", optimizer, inputBinary, outputBinary, true, enableSpirvValidation);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShaderCompiler::DecoratePositionInvariantForVulkan(const Vector<Uint32>& inputBinary,
|
bool ShaderCompiler::DecoratePositionInvariantForVulkan(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary) {
|
Vector<uint32_t>& outputBinary, const bool enableSpirvValidation) {
|
||||||
using namespace spvtools;
|
using namespace spvtools;
|
||||||
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
||||||
optimizer.RegisterPass(DecoratePositionInvariantPass::CreateDecoratePositionInvariantPass());
|
optimizer.RegisterPass(DecoratePositionInvariantPass::CreateDecoratePositionInvariantPass());
|
||||||
|
|
||||||
return RunOptimizerChecked("DecoratePositionInvariantForVulkan", optimizer, inputBinary,
|
return RunOptimizerChecked("DecoratePositionInvariantForVulkan", optimizer, inputBinary,
|
||||||
outputBinary);
|
outputBinary, true, enableSpirvValidation);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShaderCompiler::UseUnformattedFloatStorageImagesForVulkan(
|
bool ShaderCompiler::UseUnformattedFloatStorageImagesForVulkan(
|
||||||
const Vector<Uint32>& inputBinary, Vector<uint32_t>& outputBinary) {
|
const Vector<Uint32>& inputBinary, Vector<uint32_t>& outputBinary,
|
||||||
|
const bool enableSpirvValidation) {
|
||||||
constexpr SizeT kSpirvHeaderWordCount = 5;
|
constexpr SizeT kSpirvHeaderWordCount = 5;
|
||||||
outputBinary.clear();
|
outputBinary.clear();
|
||||||
if (inputBinary.size() < kSpirvHeaderWordCount || inputBinary[0] != spv::MagicNumber) {
|
if (inputBinary.size() < kSpirvHeaderWordCount || inputBinary[0] != spv::MagicNumber) {
|
||||||
@@ -1058,7 +1023,8 @@ namespace MobileGL {
|
|||||||
addedCapabilities.begin(), addedCapabilities.end());
|
addedCapabilities.begin(), addedCapabilities.end());
|
||||||
// Hand-rolled word walk, so no Optimizer wrapper ever sees this rewrite;
|
// Hand-rolled word walk, so no Optimizer wrapper ever sees this rewrite;
|
||||||
// check the modified module explicitly in validating lanes.
|
// check the modified module explicitly in validating lanes.
|
||||||
ValidateOrLatch("UseUnformattedFloatStorageImagesForVulkan", outputBinary);
|
ValidateOrLatch("UseUnformattedFloatStorageImagesForVulkan", outputBinary,
|
||||||
|
enableSpirvValidation);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,19 +23,23 @@ namespace MobileGL {
|
|||||||
static Result<SharedPtr<glslang::TProgram>> LinkProgram(const ProgramAttrib& attrib);
|
static Result<SharedPtr<glslang::TProgram>> LinkProgram(const ProgramAttrib& attrib);
|
||||||
static Result<Vector<Vector<unsigned>>> GetSpirvBinaryFromProgram(const ProgramBinaryAttrib& attrib);
|
static Result<Vector<Vector<unsigned>>> GetSpirvBinaryFromProgram(const ProgramBinaryAttrib& attrib);
|
||||||
static bool SanitizeAndOptimizeBinary(const Vector<Uint32>& inputBinary,
|
static bool SanitizeAndOptimizeBinary(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary);
|
Vector<uint32_t>& outputBinary,
|
||||||
|
bool validateOutput = true,
|
||||||
|
bool enableSpirvValidation = false);
|
||||||
// Demotes DrawIndex/BaseInstance/BaseVertex builtins to plain Private globals
|
// Demotes DrawIndex/BaseInstance/BaseVertex builtins to plain Private globals
|
||||||
// (mg_DrawID/mg_BaseInstance/mg_BaseVertex) so SPIRV-Cross can emit ESSL.
|
// (mg_DrawID/mg_BaseInstance/mg_BaseVertex) so SPIRV-Cross can emit ESSL.
|
||||||
// Only for backends without native draw-parameter support (DirectGLES).
|
// Only for backends without native draw-parameter support (DirectGLES).
|
||||||
static bool LowerDrawParametersForEssl(const Vector<Uint32>& inputBinary,
|
static bool LowerDrawParametersForEssl(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary);
|
Vector<uint32_t>& outputBinary,
|
||||||
|
bool enableSpirvValidation = false);
|
||||||
// Replaces an ARRAY vertex input with one input per element at consecutive
|
// Replaces an ARRAY vertex input with one input per element at consecutive
|
||||||
// locations, seeding a Private copy of the array so indexed reads still work.
|
// locations, seeding a Private copy of the array so indexed reads still work.
|
||||||
// GLSL ES has no array vertex inputs and SPIRV-Cross refuses the whole module
|
// GLSL ES has no array vertex inputs and SPIRV-Cross refuses the whole module
|
||||||
// rather than emulating them, so without this the stage never reaches the
|
// rather than emulating them, so without this the stage never reaches the
|
||||||
// driver. Only for the DirectGLES transpile path.
|
// driver. Only for the DirectGLES transpile path.
|
||||||
static bool SplitArrayVertexInputsForEssl(const Vector<Uint32>& inputBinary,
|
static bool SplitArrayVertexInputsForEssl(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary);
|
Vector<uint32_t>& outputBinary,
|
||||||
|
bool enableSpirvValidation = false);
|
||||||
// Replaces the named interface BLOCKS with one variable per member, named
|
// Replaces the named interface BLOCKS with one variable per member, named
|
||||||
// "<Block>_<member>", shadowing the block itself so the body is untouched. The
|
// "<Block>_<member>", shadowing the block itself so the body is untouched. The
|
||||||
// Adreno ES driver silently captures NOTHING for a transform-feedback varying
|
// Adreno ES driver silently captures NOTHING for a transform-feedback varying
|
||||||
@@ -46,7 +50,8 @@ namespace MobileGL {
|
|||||||
static bool FlattenXfbInterfaceBlocksForEssl(const Vector<Uint32>& inputBinary,
|
static bool FlattenXfbInterfaceBlocksForEssl(const Vector<Uint32>& inputBinary,
|
||||||
const std::set<String>& blockNames,
|
const std::set<String>& blockNames,
|
||||||
std::set<String>& flattenedBlockNames,
|
std::set<String>& flattenedBlockNames,
|
||||||
Vector<uint32_t>& outputBinary);
|
Vector<uint32_t>& outputBinary,
|
||||||
|
bool enableSpirvValidation = false);
|
||||||
// The capture request "StageData.attrib[0]" as the pass above renamed it,
|
// The capture request "StageData.attrib[0]" as the pass above renamed it,
|
||||||
// "StageData_attrib[0]", or false when it does not name a member of a block
|
// "StageData_attrib[0]", or false when it does not name a member of a block
|
||||||
// that was flattened.
|
// that was flattened.
|
||||||
@@ -58,17 +63,20 @@ namespace MobileGL {
|
|||||||
// drivers reject cross-stage uniform blocks whose member precisions differ.
|
// drivers reject cross-stage uniform blocks whose member precisions differ.
|
||||||
// Only for the DirectGLES transpile path.
|
// Only for the DirectGLES transpile path.
|
||||||
static bool StripUboMemberRelaxedPrecisionForEssl(const Vector<Uint32>& inputBinary,
|
static bool StripUboMemberRelaxedPrecisionForEssl(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary);
|
Vector<uint32_t>& outputBinary,
|
||||||
|
bool enableSpirvValidation = false);
|
||||||
// Removes NoPerspective decorations so SPIRV-Cross emits plain (smooth) ESSL varyings.
|
// Removes NoPerspective decorations so SPIRV-Cross emits plain (smooth) ESSL varyings.
|
||||||
// DirectGLES fallback only, for devices lacking GL_NV_shader_noperspective_interpolation
|
// DirectGLES fallback only, for devices lacking GL_NV_shader_noperspective_interpolation
|
||||||
// (SPIRV-Cross would otherwise require that extension and the driver would reject it).
|
// (SPIRV-Cross would otherwise require that extension and the driver would reject it).
|
||||||
static bool StripNoPerspectiveForEssl(const Vector<Uint32>& inputBinary,
|
static bool StripNoPerspectiveForEssl(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary);
|
Vector<uint32_t>& outputBinary,
|
||||||
|
bool enableSpirvValidation = false);
|
||||||
// Emulates noperspective (screen-linear) interpolation via gl_Position.w / gl_FragCoord.w
|
// Emulates noperspective (screen-linear) interpolation via gl_Position.w / gl_FragCoord.w
|
||||||
// so no NV extension is needed; strips what it cannot emulate. DirectGLES fallback for
|
// so no NV extension is needed; strips what it cannot emulate. DirectGLES fallback for
|
||||||
// devices lacking GL_NV_shader_noperspective_interpolation. See EmulateNoPerspectivePass.
|
// devices lacking GL_NV_shader_noperspective_interpolation. See EmulateNoPerspectivePass.
|
||||||
static bool EmulateNoPerspectiveForEssl(const Vector<Uint32>& inputBinary,
|
static bool EmulateNoPerspectiveForEssl(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary);
|
Vector<uint32_t>& outputBinary,
|
||||||
|
bool enableSpirvValidation = false);
|
||||||
// Makes every index into a fragment-output array a constant integral
|
// Makes every index into a fragment-output array a constant integral
|
||||||
// expression, which is what GLSL ES requires and SPIR-V does not. Runs the
|
// expression, which is what GLSL ES requires and SPIR-V does not. Runs the
|
||||||
// stock folding chain first (loop unrolling folds the loop-derived indices
|
// stock folding chain first (loop unrolling folds the loop-derived indices
|
||||||
@@ -79,7 +87,8 @@ namespace MobileGL {
|
|||||||
// dynamically, which is every shader but a handful.
|
// dynamically, which is every shader but a handful.
|
||||||
// See LegalizeFragmentOutputIndexPass.
|
// See LegalizeFragmentOutputIndexPass.
|
||||||
static bool LegalizeFragmentOutputIndexingForEssl(const Vector<Uint32>& inputBinary,
|
static bool LegalizeFragmentOutputIndexingForEssl(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary);
|
Vector<uint32_t>& outputBinary,
|
||||||
|
bool enableSpirvValidation = false);
|
||||||
// Rebases loads of the InstanceIndex builtin to (InstanceIndex - BaseInstance) so
|
// Rebases loads of the InstanceIndex builtin to (InstanceIndex - BaseInstance) so
|
||||||
// shaders see GL's zero-based gl_InstanceID. Vertex shaders only; DirectVulkan
|
// shaders see GL's zero-based gl_InstanceID. Vertex shaders only; DirectVulkan
|
||||||
// backend only (glslang's relaxed mode aliases gl_InstanceID to gl_InstanceIndex,
|
// backend only (glslang's relaxed mode aliases gl_InstanceID to gl_InstanceIndex,
|
||||||
@@ -88,7 +97,8 @@ namespace MobileGL {
|
|||||||
// divides the coordinate of each normalized-coordinate lookup by the texture
|
// divides the coordinate of each normalized-coordinate lookup by the texture
|
||||||
// size and rewrites the image type to 2D. See NormalizeRectCoordinatesPass for
|
// size and rewrites the image type to 2D. See NormalizeRectCoordinatesPass for
|
||||||
// what it declines and why.
|
// what it declines and why.
|
||||||
static bool LowerRectImages(const Vector<Uint32>& inputBinary, Vector<uint32_t>& outputBinary);
|
static bool LowerRectImages(const Vector<Uint32>& inputBinary, Vector<uint32_t>& outputBinary,
|
||||||
|
bool enableSpirvValidation = false);
|
||||||
// GL_TEXTURE_1D_ARRAY storage images rewritten to the 2D-array shape the texture
|
// GL_TEXTURE_1D_ARRAY storage images rewritten to the 2D-array shape the texture
|
||||||
// is actually stored in on ES, with the layer moved from the coordinate's second
|
// is actually stored in on ES, with the layer moved from the coordinate's second
|
||||||
// component to its third. DirectGLES transpile path only - Vulkan binds a real
|
// component to its third. DirectGLES transpile path only - Vulkan binds a real
|
||||||
@@ -96,7 +106,8 @@ namespace MobileGL {
|
|||||||
// through untouched when the module declares no such image, which is every shader
|
// through untouched when the module declares no such image, which is every shader
|
||||||
// but a handful. See Lower1DArrayImagesPass for what it declines and why.
|
// but a handful. See Lower1DArrayImagesPass for what it declines and why.
|
||||||
static bool Lower1DArrayImagesForEssl(const Vector<Uint32>& inputBinary,
|
static bool Lower1DArrayImagesForEssl(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary);
|
Vector<uint32_t>& outputBinary,
|
||||||
|
bool enableSpirvValidation = false);
|
||||||
// Gives each format-less storage image the format bound to its image unit, so
|
// Gives each format-less storage image the format bound to its image unit, so
|
||||||
// the emitted ESSL can carry the format layout qualifier GLSL ES requires of
|
// the emitted ESSL can carry the format layout qualifier GLSL ES requires of
|
||||||
// every image and desktop GLSL lets a writeonly declaration omit. `glFormatByName`
|
// every image and desktop GLSL lets a writeonly declaration omit. `glFormatByName`
|
||||||
@@ -105,7 +116,8 @@ namespace MobileGL {
|
|||||||
// natively. See BakeImageFormatsPass for what it declines and why.
|
// natively. See BakeImageFormatsPass for what it declines and why.
|
||||||
static bool BakeImageFormatsForEssl(const Vector<Uint32>& inputBinary,
|
static bool BakeImageFormatsForEssl(const Vector<Uint32>& inputBinary,
|
||||||
const UnorderedMap<String, Uint>& glFormatByName,
|
const UnorderedMap<String, Uint>& glFormatByName,
|
||||||
Vector<uint32_t>& outputBinary);
|
Vector<uint32_t>& outputBinary,
|
||||||
|
bool enableSpirvValidation = false);
|
||||||
// Whether the module declares a storage image with no format qualifier at all,
|
// Whether the module declares a storage image with no format qualifier at all,
|
||||||
// i.e. whether BakeImageFormatsForEssl could change anything. One module parse,
|
// i.e. whether BakeImageFormatsForEssl could change anything. One module parse,
|
||||||
// so the ~every shader that declares none pays no optimizer run.
|
// so the ~every shader that declares none pays no optimizer run.
|
||||||
@@ -124,27 +136,31 @@ namespace MobileGL {
|
|||||||
// emitted text instead.
|
// emitted text instead.
|
||||||
static bool SpirvCrossCanPrintEsslImageFormat(Uint glInternalFormat);
|
static bool SpirvCrossCanPrintEsslImageFormat(Uint glInternalFormat);
|
||||||
static bool RebaseInstanceIndexForVulkan(const Vector<Uint32>& inputBinary,
|
static bool RebaseInstanceIndexForVulkan(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary);
|
Vector<uint32_t>& outputBinary,
|
||||||
|
bool enableSpirvValidation = false);
|
||||||
// Builds the non-indexed-draw variant of a vertex shader: every gl_BaseVertex
|
// Builds the non-indexed-draw variant of a vertex shader: every gl_BaseVertex
|
||||||
// read becomes zero, which is what GL defines for a command carrying no
|
// read becomes zero, which is what GL defines for a command carrying no
|
||||||
// baseVertex parameter while Vulkan's builtin would report firstVertex.
|
// baseVertex parameter while Vulkan's builtin would report firstVertex.
|
||||||
// See ZeroBaseVertexPass.
|
// See ZeroBaseVertexPass.
|
||||||
static bool ZeroBaseVertexForVulkan(const Vector<Uint32>& inputBinary,
|
static bool ZeroBaseVertexForVulkan(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary);
|
Vector<uint32_t>& outputBinary,
|
||||||
|
bool enableSpirvValidation = false);
|
||||||
// Re-declares 64-bit float vertex inputs as their 32-bit unsigned word pair
|
// Re-declares 64-bit float vertex inputs as their 32-bit unsigned word pair
|
||||||
// (double -> uvec2, dvec2 -> uvec4) and bitcasts them back to double at entry, so no
|
// (double -> uvec2, dvec2 -> uvec4) and bitcasts them back to double at entry, so no
|
||||||
// VK_FORMAT_R64*_SFLOAT is needed - lavapipe advertises none of them for vertex
|
// VK_FORMAT_R64*_SFLOAT is needed - lavapipe advertises none of them for vertex
|
||||||
// buffers. Vertex stage, DirectVulkan only; pairs with the Float64 case in
|
// buffers. Vertex stage, DirectVulkan only; pairs with the Float64 case in
|
||||||
// VertexInputStateFactory::ToVkVertexFormat.
|
// VertexInputStateFactory::ToVkVertexFormat.
|
||||||
static bool PackDoubleVertexInputsForVulkan(const Vector<Uint32>& inputBinary,
|
static bool PackDoubleVertexInputsForVulkan(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary);
|
Vector<uint32_t>& outputBinary,
|
||||||
|
bool enableSpirvValidation = false);
|
||||||
// Adds the Invariant decoration to every Position builtin output. GL apps
|
// Adds the Invariant decoration to every Position builtin output. GL apps
|
||||||
// routinely rely on cross-program position invariance for multi-pass
|
// routinely rely on cross-program position invariance for multi-pass
|
||||||
// equality depth tests (e.g. GEQUAL re-draws of the same geometry), and
|
// equality depth tests (e.g. GEQUAL re-draws of the same geometry), and
|
||||||
// mobile drivers that optimize per-pipeline break that without the
|
// mobile drivers that optimize per-pipeline break that without the
|
||||||
// decoration. DirectVulkan only.
|
// decoration. DirectVulkan only.
|
||||||
static bool DecoratePositionInvariantForVulkan(const Vector<Uint32>& inputBinary,
|
static bool DecoratePositionInvariantForVulkan(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary);
|
Vector<uint32_t>& outputBinary,
|
||||||
|
bool enableSpirvValidation = false);
|
||||||
// Replaces the declared format of float storage images with Unknown and adds the
|
// Replaces the declared format of float storage images with Unknown and adds the
|
||||||
// matching SPIR-V capabilities. DirectVulkan uses this only when both Vulkan
|
// matching SPIR-V capabilities. DirectVulkan uses this only when both Vulkan
|
||||||
// shaderStorageImage*WithoutFormat features are enabled, allowing the
|
// shaderStorageImage*WithoutFormat features are enabled, allowing the
|
||||||
@@ -152,13 +168,15 @@ namespace MobileGL {
|
|||||||
// storage images deliberately keep their declared format for GL-compatible bit
|
// storage images deliberately keep their declared format for GL-compatible bit
|
||||||
// reinterpretation paths (for example, R32F storage accessed as r32ui).
|
// reinterpretation paths (for example, R32F storage accessed as r32ui).
|
||||||
static bool UseUnformattedFloatStorageImagesForVulkan(
|
static bool UseUnformattedFloatStorageImagesForVulkan(
|
||||||
const Vector<Uint32>& inputBinary, Vector<uint32_t>& outputBinary);
|
const Vector<Uint32>& inputBinary, Vector<uint32_t>& outputBinary,
|
||||||
|
bool enableSpirvValidation = false);
|
||||||
// Rewrites every 64-bit float in the module to a 32-bit one, preserving every
|
// Rewrites every 64-bit float in the module to a 32-bit one, preserving every
|
||||||
// block offset and stride exactly (see DemoteFloat64Pass). Already part of
|
// block offset and stride exactly (see DemoteFloat64Pass). Already part of
|
||||||
// SanitizeAndOptimizeBinary, which is where production reaches it; exposed
|
// SanitizeAndOptimizeBinary, which is where production reaches it; exposed
|
||||||
// separately so a test can drive the demotion on its own.
|
// separately so a test can drive the demotion on its own.
|
||||||
static bool DemoteFloat64ToFloat32(const Vector<Uint32>& inputBinary,
|
static bool DemoteFloat64ToFloat32(const Vector<Uint32>& inputBinary,
|
||||||
Vector<uint32_t>& outputBinary);
|
Vector<uint32_t>& outputBinary,
|
||||||
|
bool enableSpirvValidation = false);
|
||||||
static Result<String> DecompileShader(SpvcSession& session);
|
static Result<String> DecompileShader(SpvcSession& session);
|
||||||
|
|
||||||
// Parses one trivial shader in each configuration the production path can
|
// Parses one trivial shader in each configuration the production path can
|
||||||
@@ -185,18 +203,16 @@ namespace MobileGL {
|
|||||||
// no way left to warm it.
|
// no way left to warm it.
|
||||||
static void ResetPrewarmLatch();
|
static void ResetPrewarmLatch();
|
||||||
|
|
||||||
// Test-environment SPIR-V validation. When enabled, every Optimizer wrapper
|
// Validation is an explicit immutable option of each compiler operation. The
|
||||||
// in this file validates its OUTPUT binary - the bytes a driver can actually
|
// program-link task snapshots MOBILEGL_ENABLE_SPIRV_VALIDATION before it can run
|
||||||
// receive - and a failure logs the VUID (via MGLOG_I; see the consumer for
|
// on a worker; standalone callers pass true directly. A failure logs the VUID and
|
||||||
// why not MGLOG_E) and bumps the failure latch below WITHOUT changing the
|
// bumps the latch below WITHOUT changing a wrapper's return value, so validating
|
||||||
// wrapper's return value: control flow must stay identical between the
|
// and shipping configurations preserve identical rendering control flow.
|
||||||
// validating and shipping configurations, or fail-open call sites would make
|
|
||||||
// the two render differently. Resolved lazily from MOBILEGL_VALIDATE_SPIRV;
|
// Makes validator table lifetime safe before an external final-module validator
|
||||||
// defaults on for desktop/CI/WSL builds and off for device (__ANDROID__)
|
// runs. This has no configuration state; callers invoke it only for an enabled
|
||||||
// builds. The setter wins over the environment and is safe to call from test
|
// task-local validation option.
|
||||||
// fixtures at any time.
|
static void PrepareSpirvValidation();
|
||||||
static bool SpirvValidationEnabled();
|
|
||||||
static void SetSpirvValidationEnabled(bool enabled);
|
|
||||||
|
|
||||||
// The test-lane enforcement signal: total validation failures observed this
|
// The test-lane enforcement signal: total validation failures observed this
|
||||||
// process. Tests snapshot it, run the operation under scrutiny, and assert
|
// process. Tests snapshot it, run the operation under scrutiny, and assert
|
||||||
|
|||||||
@@ -423,6 +423,26 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
|
|||||||
InternalPackedLayout internalPacked;
|
InternalPackedLayout internalPacked;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Bool IsValidUnpackPixelPair(TextureInputFormat format, TexturePixelDataType type) {
|
||||||
|
UnpackChannelMapping mapping{};
|
||||||
|
if (!GetUnpackChannelMapping(format, mapping)) return false;
|
||||||
|
|
||||||
|
PackedTypeLayout packed{};
|
||||||
|
if (GetPackedTypeLayout(type, packed)) {
|
||||||
|
return packed.fieldCount == mapping.channelCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case TexturePixelDataType::UnsignedInt5999Rev:
|
||||||
|
case TexturePixelDataType::UnsignedInt101111Rev:
|
||||||
|
return !mapping.isInteger && mapping.channelCount == 3;
|
||||||
|
default: {
|
||||||
|
ShadowComponent component{};
|
||||||
|
return GetDirectShadowComponentForType(type, mapping.isInteger, component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Returns true when the (format, type) -> internal-format upload needs a per-texel conversion;
|
// Returns true when the (format, type) -> internal-format upload needs a per-texel conversion;
|
||||||
// returns false both for layouts that already match the shadow bytes (memcpy fast path) and for
|
// returns false both for layouts that already match the shadow bytes (memcpy fast path) and for
|
||||||
// combinations the converter does not support (legacy copy behavior).
|
// combinations the converter does not support (legacy copy behavior).
|
||||||
@@ -964,6 +984,32 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
|
|||||||
return outputPixels;
|
return outputPixels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bool ConvertOnePixelToInternal(TextureInternalFormat targetInternalFormat,
|
||||||
|
TextureInputFormat textureInputFormat,
|
||||||
|
TexturePixelDataType inputDataType,
|
||||||
|
const void* inputPixel,
|
||||||
|
Vector<Uint8>& outputPixel) {
|
||||||
|
outputPixel.clear();
|
||||||
|
if (inputPixel == nullptr || !IsValidUnpackPixelPair(textureInputFormat, inputDataType)) return false;
|
||||||
|
|
||||||
|
PixelStoreParameters params{};
|
||||||
|
params.Alignment = 1;
|
||||||
|
SizeT convertedSize = 0;
|
||||||
|
void* converted = ProcessTexturePixelsDataUnpack(
|
||||||
|
inputPixel, params, targetInternalFormat, textureInputFormat, inputDataType, {1, 1, 1}, false,
|
||||||
|
convertedSize);
|
||||||
|
const SizeT expectedSize = MG_Util::GetSizedInternalFormatSizeInBytes(targetInternalFormat);
|
||||||
|
if (converted == nullptr || convertedSize != expectedSize || expectedSize == 0) {
|
||||||
|
if (converted != nullptr) free(converted);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputPixel.resize(convertedSize);
|
||||||
|
Memcpy(outputPixel.data(), converted, convertedSize);
|
||||||
|
free(converted);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void* ProcessTexturePixelsDataPack(const void* inputPixels, const PixelStoreParameters& params,
|
void* ProcessTexturePixelsDataPack(const void* inputPixels, const PixelStoreParameters& params,
|
||||||
TextureInternalFormat srcInternalFormat, TexturePixelDataType srcDataType,
|
TextureInternalFormat srcInternalFormat, TexturePixelDataType srcDataType,
|
||||||
TextureInputFormat dstInputFormat, TexturePixelDataType dstDataType,
|
TextureInputFormat dstInputFormat, TexturePixelDataType dstDataType,
|
||||||
|
|||||||
@@ -21,6 +21,12 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
|
|||||||
TextureInternalFormat srcInternalFormat, TexturePixelDataType srcDataType,
|
TextureInternalFormat srcInternalFormat, TexturePixelDataType srcDataType,
|
||||||
TextureInputFormat dstInputFormat, TexturePixelDataType dstDataType,
|
TextureInputFormat dstInputFormat, TexturePixelDataType dstDataType,
|
||||||
IntVec3 dimension, Bool isBitmap, SizeT& outSize);
|
IntVec3 dimension, Bool isBitmap, SizeT& outSize);
|
||||||
|
Bool ConvertOnePixelToInternal(TextureInternalFormat targetInternalFormat,
|
||||||
|
TextureInputFormat textureInputFormat,
|
||||||
|
TexturePixelDataType inputDataType,
|
||||||
|
const void* inputPixel,
|
||||||
|
Vector<Uint8>& outputPixel);
|
||||||
|
|
||||||
void ProcessColorSwizzle(void* data, SizeT pixelCount, const Vector<TextureSwizzleParam>& swizzle);
|
void ProcessColorSwizzle(void* data, SizeT pixelCount, const Vector<TextureSwizzleParam>& swizzle);
|
||||||
|
|
||||||
// True when a packed internal format's 32-bit storage word IS the client (format, type) word,
|
// True when a packed internal format's 32-bit storage word IS the client (format, type) word,
|
||||||
|
|||||||
@@ -52,22 +52,6 @@ namespace MobileGL {
|
|||||||
inline UniquePtr<T> MakeUnique(Args&&... args) {
|
inline UniquePtr<T> MakeUnique(Args&&... args) {
|
||||||
return std::make_unique<T>(std::forward<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;
|
using SizeT = std::size_t;
|
||||||
template <typename T, SizeT N>
|
template <typename T, SizeT N>
|
||||||
using Array = std::array<T, N>;
|
using Array = std::array<T, N>;
|
||||||
|
|||||||
Reference in New Issue
Block a user