mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 22:28:32 +09:00
[Perf] (MG_Backend/DirectGLES, MG_Impl): cache link-time lookups, bound unit scans, honor eglSwapInterval
- BackendProgramObjectImpl::CacheResourceLocations resolves every glGetUniformBlockIndex / glGetUniformLocation string query once per link and establishes the block binding points there. Per draw, BindCurrentProgramWithResources now uses the cached indices, re-issues glUniform1i only when a sampler's unit actually changed (program state persists), uploads the global UBO only when its content version moved, and skips redundant glUseProgram binds (guard reset on program-name reuse, MakeCurrent, and every explicit glUseProgram(0)). The caches are invalidated through ProgramObject's link version, which also makes a relinked program finally re-sync its backend program. - Track a texture-unit high-water mark (fed by glBindTexture / glBindTextureUnit / glBindSampler / glBindImageTexture) so the two per-draw unit scans (MAX_TEXTURE_IMAGE_UNITS is 192) and the texture-deletion unbind loop only walk units that were ever touched. - Forward the app's eglSwapInterval to the native EGL surface through a new BackendObject::SetEGLSwapInterval hook (applied immediately when the surface exists, otherwise deferred to surface creation / MakeCurrent). "VSync off" finally reaches the hardware - DirectGLES was hard-locked to the display refresh before. The driver-side cost of the per-draw string lookups was about half of a 30% Adreno driver hotspot; libMobileGL's share of the vanilla render thread fell from 22% to 9% (simpleperf, Adreno 830). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -409,6 +409,13 @@ namespace MobileGL::MG_Backend {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BackendObject::SetEGLSwapInterval(Int interval) {
|
||||||
|
const auto& backendFunctions = GetBackendFunctions();
|
||||||
|
if (backendFunctions.SetSwapInterval) {
|
||||||
|
backendFunctions.SetSwapInterval(interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Bool BackendObject::IsEGLSurfaceCurrent(EGLSurface surface) const {
|
Bool BackendObject::IsEGLSurfaceCurrent(EGLSurface surface) const {
|
||||||
if (surface == EGL_NO_SURFACE) {
|
if (surface == EGL_NO_SURFACE) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -175,6 +175,9 @@ namespace MobileGL {
|
|||||||
struct GlobalBackendFunctionsTable {
|
struct GlobalBackendFunctionsTable {
|
||||||
GLFunctionsTable GL;
|
GLFunctionsTable GL;
|
||||||
void (*Present)();
|
void (*Present)();
|
||||||
|
// Optional: applies the app-requested eglSwapInterval to the native
|
||||||
|
// presentation path (null = backend keeps its own pacing policy).
|
||||||
|
void (*SetSwapInterval)(Int interval);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DynamicBackendParameters {
|
struct DynamicBackendParameters {
|
||||||
@@ -264,6 +267,9 @@ namespace MobileGL {
|
|||||||
virtual Bool CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height);
|
virtual Bool CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height);
|
||||||
virtual Bool MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx);
|
virtual Bool MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx);
|
||||||
virtual Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw);
|
virtual Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw);
|
||||||
|
// Forwards the app-requested eglSwapInterval to the backend's native
|
||||||
|
// presentation path (no-op for backends without a SetSwapInterval hook).
|
||||||
|
virtual void SetEGLSwapInterval(Int interval);
|
||||||
virtual void ReleaseEGLSurface(EGLSurface surface);
|
virtual void ReleaseEGLSurface(EGLSurface surface);
|
||||||
virtual void ReleaseEGLResources();
|
virtual void ReleaseEGLResources();
|
||||||
|
|
||||||
|
|||||||
@@ -818,6 +818,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
static Bool funcsTableInitialized = false;
|
static Bool funcsTableInitialized = false;
|
||||||
if (!funcsTableInitialized) {
|
if (!funcsTableInitialized) {
|
||||||
funcsTable.Present = DirectGLES::Present;
|
funcsTable.Present = DirectGLES::Present;
|
||||||
|
funcsTable.SetSwapInterval = DirectGLES::SetSwapInterval;
|
||||||
funcsTable.GL.DrawArrays = DrawArrays;
|
funcsTable.GL.DrawArrays = DrawArrays;
|
||||||
funcsTable.GL.DrawElements = DrawElements;
|
funcsTable.GL.DrawElements = DrawElements;
|
||||||
funcsTable.GL.DrawElementsBaseVertex = DrawElementsBaseVertex;
|
funcsTable.GL.DrawElementsBaseVertex = DrawElementsBaseVertex;
|
||||||
|
|||||||
@@ -355,7 +355,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
// 2. textures used in current FBO
|
// 2. textures used in current FBO
|
||||||
// 3. textures bound to image units (TODO)
|
// 3. textures bound to image units (TODO)
|
||||||
|
|
||||||
for (int index = 0; index < MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS; ++index) {
|
// Units past the frontend's high-water mark have provably-empty slots.
|
||||||
|
const Int maxTouchedUnit = MG_State::pGLContext->GetMaxTouchedTextureUnit();
|
||||||
|
for (Int index = 0; index <= maxTouchedUnit; ++index) {
|
||||||
auto& unit = MG_State::pGLContext->GetTextureUnitObject(index);
|
auto& unit = MG_State::pGLContext->GetTextureUnitObject(index);
|
||||||
for (const auto& bindingSlot : unit.GetAllBindingSlots()) {
|
for (const auto& bindingSlot : unit.GetAllBindingSlots()) {
|
||||||
auto& textureObject = bindingSlot.GetBoundObject();
|
auto& textureObject = bindingSlot.GetBoundObject();
|
||||||
@@ -748,6 +750,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
auto& currentProgram = MG_State::pGLContext->GetCurrentProgram();
|
auto& currentProgram = MG_State::pGLContext->GetCurrentProgram();
|
||||||
if (!currentProgram || !currentProgram->GetLinkStatus()) {
|
if (!currentProgram || !currentProgram->GetLinkStatus()) {
|
||||||
g_GLESFuncs.glUseProgram(0);
|
g_GLESFuncs.glUseProgram(0);
|
||||||
|
g_lastUsedBackendProgramId = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto& backendProgramIt = g_backendProgramObjects.find(currentProgram.get());
|
const auto& backendProgramIt = g_backendProgramObjects.find(currentProgram.get());
|
||||||
@@ -757,7 +760,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
backendObj = MakeShared<BackendProgramObjectImpl>();
|
backendObj = MakeShared<BackendProgramObjectImpl>();
|
||||||
backendObj->SyncToBackend(currentProgram);
|
backendObj->SyncToBackend(currentProgram);
|
||||||
} else {
|
} else {
|
||||||
|
// A link-version mismatch means the program was relinked: the backend
|
||||||
|
// shaders and every cache built by CacheResourceLocations (block
|
||||||
|
// indices, sampler locations, UBO upload gate) are stale.
|
||||||
if (!backendObj->GetBackendProgramId() ||
|
if (!backendObj->GetBackendProgramId() ||
|
||||||
|
backendObj->GetSyncedLinkVersion() != currentProgram->GetLinkVersion() ||
|
||||||
backendObj->GetSnormFallbackClampOutputMask() != g_snormFallbackClampOutputMask ||
|
backendObj->GetSnormFallbackClampOutputMask() != g_snormFallbackClampOutputMask ||
|
||||||
backendObj->GetUnormFallbackClampOutputMask() != g_unormFallbackClampOutputMask) {
|
backendObj->GetUnormFallbackClampOutputMask() != g_unormFallbackClampOutputMask) {
|
||||||
backendObj->SyncToBackend(currentProgram);
|
backendObj->SyncToBackend(currentProgram);
|
||||||
@@ -827,32 +834,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
static void BindCurrentProgramWithResources();
|
static void BindCurrentProgramWithResources();
|
||||||
static void BindCurrentTextures();
|
static void BindCurrentTextures();
|
||||||
|
|
||||||
// Image uniforms take their unit from the layout(binding=N) qualifier baked into
|
|
||||||
// the transpiled ESSL; unlike samplers they must not (and in ES cannot) be
|
|
||||||
// assigned through glUniform1i.
|
|
||||||
static Bool IsImageUniformType(GLenum type) {
|
|
||||||
switch (type) {
|
|
||||||
case 0x904D: /*GL_IMAGE_2D*/
|
|
||||||
case 0x904E: /*GL_IMAGE_3D*/
|
|
||||||
case 0x9050: /*GL_IMAGE_CUBE*/
|
|
||||||
case 0x9051: /*GL_IMAGE_BUFFER*/
|
|
||||||
case 0x9053: /*GL_IMAGE_2D_ARRAY*/
|
|
||||||
case 0x9058: /*GL_INT_IMAGE_2D*/
|
|
||||||
case 0x9059: /*GL_INT_IMAGE_3D*/
|
|
||||||
case 0x905B: /*GL_INT_IMAGE_CUBE*/
|
|
||||||
case 0x905C: /*GL_INT_IMAGE_BUFFER*/
|
|
||||||
case 0x905E: /*GL_INT_IMAGE_2D_ARRAY*/
|
|
||||||
case 0x9063: /*GL_UNSIGNED_INT_IMAGE_2D*/
|
|
||||||
case 0x9064: /*GL_UNSIGNED_INT_IMAGE_3D*/
|
|
||||||
case 0x9066: /*GL_UNSIGNED_INT_IMAGE_CUBE*/
|
|
||||||
case 0x9067: /*GL_UNSIGNED_INT_IMAGE_BUFFER*/
|
|
||||||
case 0x9069: /*GL_UNSIGNED_INT_IMAGE_2D_ARRAY*/
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrepareForDraw(DrawSyncBit syncBit) {
|
void PrepareForDraw(DrawSyncBit syncBit) {
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
@@ -896,8 +877,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
ZoneScopedNC("BindCurrentTextures", TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedNC("BindCurrentTextures", TRACY_ZONECOLOR_BACKEND);
|
||||||
#endif
|
#endif
|
||||||
Int maxTextureUnits = MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS;
|
// Units past the frontend's high-water mark have provably-empty slots.
|
||||||
for (Int unit = 0; unit < maxTextureUnits; ++unit) {
|
const Int maxTouchedUnit = MG_State::pGLContext->GetMaxTouchedTextureUnit();
|
||||||
|
for (Int unit = 0; unit <= maxTouchedUnit; ++unit) {
|
||||||
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit);
|
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit);
|
||||||
|
|
||||||
for (const auto& bindingSlot : textureUnit.GetAllBindingSlots()) {
|
for (const auto& bindingSlot : textureUnit.GetAllBindingSlots()) {
|
||||||
@@ -945,56 +927,45 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
#endif
|
#endif
|
||||||
const auto& backendProgramIt = PrgramImpl::g_backendProgramObjects.find(currentProgram.get());
|
const auto& backendProgramIt = PrgramImpl::g_backendProgramObjects.find(currentProgram.get());
|
||||||
if (backendProgramIt != PrgramImpl::g_backendProgramObjects.end()) {
|
if (backendProgramIt != PrgramImpl::g_backendProgramObjects.end()) {
|
||||||
backendProgramIt->second->Use();
|
auto& backendProgram = *backendProgramIt->second;
|
||||||
auto backendProgramId = backendProgramIt->second->GetBackendProgramId();
|
backendProgram.Use();
|
||||||
|
|
||||||
// Global UBO
|
// Global UBO: block index and binding-point assignment are cached at
|
||||||
if (currentProgram->GetUBOSize() > 0) {
|
// link time (CacheResourceLocations); re-upload only when the CPU shadow
|
||||||
|
// actually changed since the last upload for this program.
|
||||||
|
if (currentProgram->GetUBOSize() > 0 && backendProgram.HasGlobalUboBlock()) {
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
ZoneScopedNC("UpdateGlobalUBO", TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedNC("UpdateGlobalUBO", TRACY_ZONECOLOR_BACKEND);
|
||||||
#endif
|
#endif
|
||||||
g_GLESFuncs.glBindBuffer(GL_UNIFORM_BUFFER, backendProgramIt->second->GetBackendGlobalUBOId());
|
const Uint32 uboContentVersion = currentProgram->GetUBOContentVersion();
|
||||||
|
if (backendProgram.GetLastUploadedGlobalUboVersion() != uboContentVersion) {
|
||||||
|
g_GLESFuncs.glBindBuffer(GL_UNIFORM_BUFFER, backendProgram.GetBackendGlobalUBOId());
|
||||||
g_GLESFuncs.glBufferSubData(GL_UNIFORM_BUFFER, 0, currentProgram->GetUBOSize(),
|
g_GLESFuncs.glBufferSubData(GL_UNIFORM_BUFFER, 0, currentProgram->GetUBOSize(),
|
||||||
currentProgram->MapUBO());
|
currentProgram->MapUBO());
|
||||||
g_GLESFuncs.glBindBuffer(GL_UNIFORM_BUFFER, 0);
|
g_GLESFuncs.glBindBuffer(GL_UNIFORM_BUFFER, 0);
|
||||||
|
backendProgram.SetLastUploadedGlobalUboVersion(uboContentVersion);
|
||||||
Uint blockIndex = g_GLESFuncs.glGetUniformBlockIndex(backendProgramId,
|
|
||||||
MG_Util::ShaderTranspiler::GLOBAL_UBO_NAME);
|
|
||||||
if (blockIndex == GL_INVALID_INDEX) {
|
|
||||||
MGLOG_W("Program %u has frontend global UBO storage, but backend has no %s block.",
|
|
||||||
currentProgram->GetExternalIndex(), MG_Util::ShaderTranspiler::GLOBAL_UBO_NAME);
|
|
||||||
} else {
|
|
||||||
g_GLESFuncs.glUniformBlockBinding(backendProgramId, blockIndex, 0);
|
|
||||||
|
|
||||||
g_GLESFuncs.glBindBufferBase(GL_UNIFORM_BUFFER, 0,
|
|
||||||
backendProgramIt->second->GetBackendGlobalUBOId());
|
|
||||||
}
|
}
|
||||||
|
g_GLESFuncs.glBindBufferBase(GL_UNIFORM_BUFFER, 0, backendProgram.GetBackendGlobalUBOId());
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
ZoneScopedNC("UpdateUBO", TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedNC("UpdateUBO", TRACY_ZONECOLOR_BACKEND);
|
||||||
#endif
|
#endif
|
||||||
// Normal UBO
|
// Normal UBOs: backend block indices and glUniformBlockBinding
|
||||||
auto uboCount = currentProgram->GetActiveUniformBlocksCount();
|
// assignments are cached at link time; per draw only the buffer
|
||||||
Uint lastUBOBinding = 0; // to prevent overlapping bindings between global UBO and normal UBOs
|
// bindings are re-established (they follow frontend binding points).
|
||||||
|
const auto& blockBackendIndices = backendProgram.GetUniformBlockBackendIndices();
|
||||||
|
const auto uboCount = static_cast<Int>(blockBackendIndices.size());
|
||||||
|
Uint lastUBOBinding = 0; // binding 0 is reserved for the global UBO
|
||||||
for (Int i = 0; i < uboCount; ++i) {
|
for (Int i = 0; i < uboCount; ++i) {
|
||||||
++lastUBOBinding;
|
++lastUBOBinding;
|
||||||
// program state binding index == backend binding index
|
if (blockBackendIndices[i] < 0) {
|
||||||
|
|
||||||
// Connect program ubo index to backend binding point
|
|
||||||
auto binding = currentProgram->GetUniformBlockBinding(i);
|
|
||||||
auto& name = currentProgram->GetUniformBlockName(i);
|
|
||||||
GLuint backendBlkIdx = g_GLESFuncs.glGetUniformBlockIndex(backendProgramId, name.c_str());
|
|
||||||
if (backendBlkIdx == GL_INVALID_INDEX) {
|
|
||||||
// Not a uniform block in the backend program: either eliminated as
|
|
||||||
// unused, or an SSBO block (the frontend's reflection lists those
|
|
||||||
// among uniform blocks); SSBO bindings are baked into the ESSL.
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
g_GLESFuncs.glUniformBlockBinding(backendProgramId, backendBlkIdx, lastUBOBinding);
|
|
||||||
|
|
||||||
// Connect buffer to backend binding point
|
// Connect buffer to backend binding point
|
||||||
|
auto binding = currentProgram->GetUniformBlockBinding(i);
|
||||||
auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::Uniform, binding);
|
auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::Uniform, binding);
|
||||||
auto& bufferObj = point.GetBoundObject();
|
auto& bufferObj = point.GetBoundObject();
|
||||||
auto range = point.GetRange();
|
auto range = point.GetRange();
|
||||||
@@ -1022,23 +993,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
ZoneScopedNC("BindSamplerUnit", TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedNC("BindSamplerUnit", TRACY_ZONECOLOR_BACKEND);
|
||||||
#endif
|
#endif
|
||||||
// Sampler unit binding
|
// Sampler unit binding: backend locations are cached at link time;
|
||||||
auto maxUniformLoc = currentProgram->GetMaxUniformLocation();
|
// glUniform1i is program state, so it is only re-issued when the
|
||||||
for (Uint loc = 0; loc <= maxUniformLoc; ++loc) {
|
// frontend-assigned unit differs from what this program last saw.
|
||||||
auto& name = currentProgram->GetUniformName(loc);
|
for (auto& samplerBinding : backendProgram.GetSamplerUniformBindings()) {
|
||||||
if (name.empty()) continue;
|
const auto unit =
|
||||||
auto unit = currentProgram->GetUniformSamplerOrImageUnitIndex(loc);
|
currentProgram->GetUniformSamplerOrImageUnitIndex(samplerBinding.frontendLocation);
|
||||||
if (unit == -1) continue;
|
if (unit == -1) continue;
|
||||||
const auto uniformType = currentProgram->GetUniformType(loc);
|
if (samplerBinding.lastAssignedUnit != unit) {
|
||||||
if (IsImageUniformType(uniformType)) {
|
g_GLESFuncs.glUniform1i(samplerBinding.backendLocation, unit);
|
||||||
// ES image units come exclusively from the layout(binding=N)
|
samplerBinding.lastAssignedUnit = unit;
|
||||||
// qualifier (preserved in the transpiled ESSL); glUniform1i on an
|
|
||||||
// image uniform is an INVALID_OPERATION.
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
auto locAtBackend = g_GLESFuncs.glGetUniformLocation(
|
|
||||||
backendProgramIt->second->GetBackendProgramId(), name.c_str());
|
|
||||||
g_GLESFuncs.glUniform1i(locAtBackend, unit);
|
|
||||||
|
|
||||||
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit);
|
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit);
|
||||||
auto& samplerObject = textureUnit.GetSamplerObject();
|
auto& samplerObject = textureUnit.GetSamplerObject();
|
||||||
@@ -1048,10 +1013,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
rawDepthSamplerObject = &texture2D->GetSamplerObject();
|
rawDepthSamplerObject = &texture2D->GetSamplerObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uniformType == GL_SAMPLER_2D && texture2D &&
|
if (samplerBinding.uniformType == GL_SAMPLER_2D && texture2D &&
|
||||||
NeedsRawDepthFetchSampler(*rawDepthSamplerObject, texture2D->GetFormat())) {
|
NeedsRawDepthFetchSampler(*rawDepthSamplerObject, texture2D->GetFormat())) {
|
||||||
GetRawDepthFetchSampler()->Bind(unit);
|
GetRawDepthFetchSampler()->Bind(unit);
|
||||||
MGLOG_D("Using raw depth fetch sampler for uniform %s on unit %d.", name.c_str(), unit);
|
MGLOG_D("Using raw depth fetch sampler on unit %d.", unit);
|
||||||
} else if (samplerObject) {
|
} else if (samplerObject) {
|
||||||
const auto& backendSamplerIt =
|
const auto& backendSamplerIt =
|
||||||
SamplerImpl::g_backendSamplerObjects.find(samplerObject.get());
|
SamplerImpl::g_backendSamplerObjects.find(samplerObject.get());
|
||||||
@@ -1069,6 +1034,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
g_GLESFuncs.glUseProgram(0);
|
g_GLESFuncs.glUseProgram(0);
|
||||||
|
PrgramImpl::g_lastUsedBackendProgramId = 0;
|
||||||
MGLOG_E("No backend program found (maybe not synced) for current program, cannot use program.");
|
MGLOG_E("No backend program found (maybe not synced) for current program, cannot use program.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1222,6 +1188,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
const auto& currentProgram = MG_State::pGLContext->GetCurrentProgram();
|
const auto& currentProgram = MG_State::pGLContext->GetCurrentProgram();
|
||||||
if (!currentProgram || !currentProgram->GetLinkStatus()) {
|
if (!currentProgram || !currentProgram->GetLinkStatus()) {
|
||||||
g_GLESFuncs.glUseProgram(0);
|
g_GLESFuncs.glUseProgram(0);
|
||||||
|
PrgramImpl::g_lastUsedBackendProgramId = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3594,6 +3561,26 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return g_Context != EGL_NO_CONTEXT;
|
return g_Context != EGL_NO_CONTEXT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// Last swap interval the app requested through eglSwapInterval; -1 = never
|
||||||
|
// requested (keep the EGL default of 1). Re-applied when the window surface
|
||||||
|
// is (re)created since interval is per-surface state.
|
||||||
|
Int g_requestedSwapInterval = -1;
|
||||||
|
|
||||||
|
void ApplyRequestedSwapInterval() {
|
||||||
|
if (g_requestedSwapInterval < 0) return;
|
||||||
|
if (!g_EGLFuncs.eglSwapInterval || g_Display == EGL_NO_DISPLAY || g_Surface == EGL_NO_SURFACE) return;
|
||||||
|
const EGLBoolean ok = g_EGLFuncs.eglSwapInterval(g_Display, g_requestedSwapInterval);
|
||||||
|
MGLOG_I("DirectGLES: applied native swap interval %d (%s)", g_requestedSwapInterval,
|
||||||
|
ok ? "ok" : "failed");
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void SetSwapInterval(Int interval) {
|
||||||
|
g_requestedSwapInterval = interval;
|
||||||
|
ApplyRequestedSwapInterval();
|
||||||
|
}
|
||||||
|
|
||||||
Bool InitWindowSurface(NativeWindowType window) {
|
Bool InitWindowSurface(NativeWindowType window) {
|
||||||
if (!window) return false;
|
if (!window) return false;
|
||||||
|
|
||||||
@@ -3604,6 +3591,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
if (!MakeCurrent()) return false;
|
if (!MakeCurrent()) return false;
|
||||||
|
|
||||||
|
ApplyRequestedSwapInterval();
|
||||||
|
|
||||||
MGLOG_D("EGL context created successfully: display=%p, surface=%p, context=%p. window=%p", g_Display, g_Surface,
|
MGLOG_D("EGL context created successfully: display=%p, surface=%p, context=%p. window=%p", g_Display, g_Surface,
|
||||||
g_Context, window);
|
g_Context, window);
|
||||||
return true;
|
return true;
|
||||||
@@ -3663,6 +3652,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
// The ops table may have been unregistered when a previous ES context was
|
// The ops table may have been unregistered when a previous ES context was
|
||||||
// destroyed (e.g. a probe context); re-register now that GL is usable.
|
// destroyed (e.g. a probe context); re-register now that GL is usable.
|
||||||
BufferImpl::RegisterBufferBackendOps();
|
BufferImpl::RegisterBufferBackendOps();
|
||||||
|
// Conservatively drop the redundant-glUseProgram guard: re-issuing one bind
|
||||||
|
// after a MakeCurrent is cheaper than trusting a possibly-reset context.
|
||||||
|
PrgramImpl::g_lastUsedBackendProgramId = 0;
|
||||||
|
// eglSwapInterval requires a current context; a request made while none was
|
||||||
|
// current (and dropped by the driver) is retried here.
|
||||||
|
ApplyRequestedSwapInterval();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -110,6 +110,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
void DeleteSync(BackendSyncHandle sync);
|
void DeleteSync(BackendSyncHandle sync);
|
||||||
Bool GetSyncStatus(BackendSyncHandle sync);
|
Bool GetSyncStatus(BackendSyncHandle sync);
|
||||||
void Present();
|
void Present();
|
||||||
|
// Applies (or defers until the window surface exists) the app-requested
|
||||||
|
// eglSwapInterval on the native EGL surface.
|
||||||
|
void SetSwapInterval(Int interval);
|
||||||
void SetEGLFuncsTable(const MG_External::EGLFunctionsTable& eglFuncs);
|
void SetEGLFuncsTable(const MG_External::EGLFunctionsTable& eglFuncs);
|
||||||
void SetGLESFuncsTable(const MG_External::GLESFunctionsTable& glesFuncs);
|
void SetGLESFuncsTable(const MG_External::GLESFunctionsTable& glesFuncs);
|
||||||
void SetGLESCapabilities(const MG_External::GLESCapabilities& capabilities);
|
void SetGLESCapabilities(const MG_External::GLESCapabilities& capabilities);
|
||||||
|
|||||||
@@ -1957,6 +1957,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
namespace PrgramImpl {
|
namespace PrgramImpl {
|
||||||
Uint32 g_snormFallbackClampOutputMask = 0;
|
Uint32 g_snormFallbackClampOutputMask = 0;
|
||||||
Uint32 g_unormFallbackClampOutputMask = 0;
|
Uint32 g_unormFallbackClampOutputMask = 0;
|
||||||
|
Uint g_lastUsedBackendProgramId = 0;
|
||||||
StateBackendObjectRegistry<MG_State::GLState::ProgramObject, BackendProgramObjectImpl> g_backendProgramObjects;
|
StateBackendObjectRegistry<MG_State::GLState::ProgramObject, BackendProgramObjectImpl> g_backendProgramObjects;
|
||||||
|
|
||||||
BackendProgramObjectImpl::BackendProgramObjectImpl() {
|
BackendProgramObjectImpl::BackendProgramObjectImpl() {
|
||||||
@@ -1980,6 +1981,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
if (m_backendProgramId != 0) {
|
if (m_backendProgramId != 0) {
|
||||||
MGLOG_D("Deleting backend program object with ID: %u", m_backendProgramId);
|
MGLOG_D("Deleting backend program object with ID: %u", m_backendProgramId);
|
||||||
g_GLESFuncs.glDeleteProgram(m_backendProgramId);
|
g_GLESFuncs.glDeleteProgram(m_backendProgramId);
|
||||||
|
// The driver may recycle this GL name for a future program; a stale
|
||||||
|
// guard entry would then wrongly skip the glUseProgram for it.
|
||||||
|
if (g_lastUsedBackendProgramId == m_backendProgramId) {
|
||||||
|
g_lastUsedBackendProgramId = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2168,16 +2174,82 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
m_backendGlobalUBOId = 0;
|
m_backendGlobalUBOId = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CacheResourceLocations(stateProgramObject);
|
||||||
|
m_syncedLinkVersion = stateProgramObject->GetLinkVersion();
|
||||||
|
|
||||||
m_isInitialized = true;
|
m_isInitialized = true;
|
||||||
MGLOG_D("Program sync completed. backend ID %u", m_backendProgramId);
|
MGLOG_D("Program sync completed. backend ID %u", m_backendProgramId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolves every name-based resource lookup once per link so the per-draw path
|
||||||
|
// (BindCurrentProgramWithResources) never issues glGetUniformBlockIndex /
|
||||||
|
// glGetUniformLocation string queries; block-to-binding-point assignments are
|
||||||
|
// program state and only need to be established here.
|
||||||
|
void BackendProgramObjectImpl::CacheResourceLocations(
|
||||||
|
const SharedPtr<MG_State::GLState::ProgramObject>& stateProgramObject) {
|
||||||
|
m_globalUboBackendBlockIndex = -1;
|
||||||
|
m_lastUploadedGlobalUboVersion = ~0u;
|
||||||
|
if (stateProgramObject->GetUBOSize() > 0) {
|
||||||
|
const Uint blockIndex =
|
||||||
|
g_GLESFuncs.glGetUniformBlockIndex(m_backendProgramId, MG_Util::ShaderTranspiler::GLOBAL_UBO_NAME);
|
||||||
|
if (blockIndex != GL_INVALID_INDEX) {
|
||||||
|
m_globalUboBackendBlockIndex = static_cast<Int>(blockIndex);
|
||||||
|
g_GLESFuncs.glUniformBlockBinding(m_backendProgramId, blockIndex, 0);
|
||||||
|
} else {
|
||||||
|
MGLOG_W("Program %u has frontend global UBO storage, but backend has no %s block.",
|
||||||
|
stateProgramObject->GetExternalIndex(), MG_Util::ShaderTranspiler::GLOBAL_UBO_NAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Int uboCount = stateProgramObject->GetActiveUniformBlocksCount();
|
||||||
|
m_uniformBlockBackendIndices.assign(static_cast<SizeT>(std::max(uboCount, 0)), -1);
|
||||||
|
Uint lastUBOBinding = 0; // binding 0 is reserved for the global UBO
|
||||||
|
for (Int i = 0; i < uboCount; ++i) {
|
||||||
|
++lastUBOBinding;
|
||||||
|
const auto& name = stateProgramObject->GetUniformBlockName(static_cast<Uint>(i));
|
||||||
|
const GLuint backendBlkIdx = g_GLESFuncs.glGetUniformBlockIndex(m_backendProgramId, name.c_str());
|
||||||
|
if (backendBlkIdx == GL_INVALID_INDEX) {
|
||||||
|
// Either eliminated as unused, or an SSBO block (frontend reflection
|
||||||
|
// lists those among uniform blocks); SSBO bindings are baked into the ESSL.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
m_uniformBlockBackendIndices[static_cast<SizeT>(i)] = static_cast<Int>(backendBlkIdx);
|
||||||
|
g_GLESFuncs.glUniformBlockBinding(m_backendProgramId, backendBlkIdx, lastUBOBinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_samplerUniformBindings.clear();
|
||||||
|
const Uint maxUniformLoc = stateProgramObject->GetMaxUniformLocation();
|
||||||
|
for (Uint loc = 0; loc <= maxUniformLoc; ++loc) {
|
||||||
|
const auto& name = stateProgramObject->GetUniformName(loc);
|
||||||
|
if (name.empty()) continue;
|
||||||
|
const GLenum uniformType = stateProgramObject->GetUniformType(loc);
|
||||||
|
if (IsImageUniformType(uniformType)) {
|
||||||
|
// ES image units come exclusively from the layout(binding=N) qualifier
|
||||||
|
// (preserved in the transpiled ESSL); glUniform1i on an image uniform
|
||||||
|
// is an INVALID_OPERATION.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const Int backendLoc = g_GLESFuncs.glGetUniformLocation(m_backendProgramId, name.c_str());
|
||||||
|
if (backendLoc < 0) continue;
|
||||||
|
SamplerUniformBinding binding;
|
||||||
|
binding.frontendLocation = loc;
|
||||||
|
binding.backendLocation = backendLoc;
|
||||||
|
binding.uniformType = uniformType;
|
||||||
|
binding.lastAssignedUnit = -1;
|
||||||
|
m_samplerUniformBindings.push_back(binding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void BackendProgramObjectImpl::Use() const {
|
void BackendProgramObjectImpl::Use() const {
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
#endif
|
#endif
|
||||||
|
if (g_lastUsedBackendProgramId == m_backendProgramId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
MGLOG_D("Using program %u", m_backendProgramId);
|
MGLOG_D("Using program %u", m_backendProgramId);
|
||||||
g_GLESFuncs.glUseProgram(m_backendProgramId);
|
g_GLESFuncs.glUseProgram(m_backendProgramId);
|
||||||
|
g_lastUsedBackendProgramId = m_backendProgramId;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BackendProgramObjectImpl::SetBaseInstance(Uint32 baseInstance) const {
|
void BackendProgramObjectImpl::SetBaseInstance(Uint32 baseInstance) const {
|
||||||
|
|||||||
@@ -315,9 +315,46 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
extern Array<Uint16, SizeT(FramebufferTarget::FramebufferTargetCount)> g_fboBindVersions;
|
extern Array<Uint16, SizeT(FramebufferTarget::FramebufferTargetCount)> g_fboBindVersions;
|
||||||
} // namespace FramebufferImpl
|
} // namespace FramebufferImpl
|
||||||
|
|
||||||
|
// Image uniforms take their unit from the layout(binding=N) qualifier baked into
|
||||||
|
// the transpiled ESSL; unlike samplers they must not (and in ES cannot) be
|
||||||
|
// assigned through glUniform1i.
|
||||||
|
inline Bool IsImageUniformType(GLenum type) {
|
||||||
|
switch (type) {
|
||||||
|
case 0x904D: /*GL_IMAGE_2D*/
|
||||||
|
case 0x904E: /*GL_IMAGE_3D*/
|
||||||
|
case 0x9050: /*GL_IMAGE_CUBE*/
|
||||||
|
case 0x9051: /*GL_IMAGE_BUFFER*/
|
||||||
|
case 0x9053: /*GL_IMAGE_2D_ARRAY*/
|
||||||
|
case 0x9058: /*GL_INT_IMAGE_2D*/
|
||||||
|
case 0x9059: /*GL_INT_IMAGE_3D*/
|
||||||
|
case 0x905B: /*GL_INT_IMAGE_CUBE*/
|
||||||
|
case 0x905C: /*GL_INT_IMAGE_BUFFER*/
|
||||||
|
case 0x905E: /*GL_INT_IMAGE_2D_ARRAY*/
|
||||||
|
case 0x9063: /*GL_UNSIGNED_INT_IMAGE_2D*/
|
||||||
|
case 0x9064: /*GL_UNSIGNED_INT_IMAGE_3D*/
|
||||||
|
case 0x9066: /*GL_UNSIGNED_INT_IMAGE_CUBE*/
|
||||||
|
case 0x9067: /*GL_UNSIGNED_INT_IMAGE_BUFFER*/
|
||||||
|
case 0x9069: /*GL_UNSIGNED_INT_IMAGE_2D_ARRAY*/
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace PrgramImpl {
|
namespace PrgramImpl {
|
||||||
class BackendProgramObjectImpl {
|
class BackendProgramObjectImpl {
|
||||||
public:
|
public:
|
||||||
|
// Per-link cache of a sampler-style uniform's backend location: built once in
|
||||||
|
// SyncToBackend so draws stop issuing glGetUniformLocation string queries.
|
||||||
|
// lastAssignedUnit mirrors the program-state value set through glUniform1i
|
||||||
|
// (program state persists across binds, so caching per program is exact).
|
||||||
|
struct SamplerUniformBinding {
|
||||||
|
Uint frontendLocation = 0;
|
||||||
|
Int backendLocation = -1;
|
||||||
|
GLenum uniformType = 0;
|
||||||
|
Int lastAssignedUnit = -1;
|
||||||
|
};
|
||||||
|
|
||||||
BackendProgramObjectImpl();
|
BackendProgramObjectImpl();
|
||||||
~BackendProgramObjectImpl();
|
~BackendProgramObjectImpl();
|
||||||
void SyncToBackend(const SharedPtr<MG_State::GLState::ProgramObject>& stateProgramObject);
|
void SyncToBackend(const SharedPtr<MG_State::GLState::ProgramObject>& stateProgramObject);
|
||||||
@@ -331,7 +368,18 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
Uint32 GetSnormFallbackClampOutputMask() const { return m_snormFallbackClampOutputMask; }
|
Uint32 GetSnormFallbackClampOutputMask() const { return m_snormFallbackClampOutputMask; }
|
||||||
Uint32 GetUnormFallbackClampOutputMask() const { return m_unormFallbackClampOutputMask; }
|
Uint32 GetUnormFallbackClampOutputMask() const { return m_unormFallbackClampOutputMask; }
|
||||||
|
|
||||||
|
Bool HasGlobalUboBlock() const { return m_globalUboBackendBlockIndex >= 0; }
|
||||||
|
const Vector<Int>& GetUniformBlockBackendIndices() const { return m_uniformBlockBackendIndices; }
|
||||||
|
Vector<SamplerUniformBinding>& GetSamplerUniformBindings() { return m_samplerUniformBindings; }
|
||||||
|
Uint32 GetLastUploadedGlobalUboVersion() const { return m_lastUploadedGlobalUboVersion; }
|
||||||
|
void SetLastUploadedGlobalUboVersion(Uint32 version) { m_lastUploadedGlobalUboVersion = version; }
|
||||||
|
// Frontend link version this backend program (and its resource caches) was
|
||||||
|
// built from; a mismatch means every link-derived cache here is stale.
|
||||||
|
Uint32 GetSyncedLinkVersion() const { return m_syncedLinkVersion; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void CacheResourceLocations(const SharedPtr<MG_State::GLState::ProgramObject>& stateProgramObject);
|
||||||
|
|
||||||
Uint m_backendProgramId = 0;
|
Uint m_backendProgramId = 0;
|
||||||
Uint m_backendGlobalUBOId = 0;
|
Uint m_backendGlobalUBOId = 0;
|
||||||
Int m_baseInstanceUniformLocation = -1;
|
Int m_baseInstanceUniformLocation = -1;
|
||||||
@@ -341,10 +389,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
Uint32 m_snormFallbackClampOutputMask = 0;
|
Uint32 m_snormFallbackClampOutputMask = 0;
|
||||||
Uint32 m_unormFallbackClampOutputMask = 0;
|
Uint32 m_unormFallbackClampOutputMask = 0;
|
||||||
Bool m_isInitialized = false;
|
Bool m_isInitialized = false;
|
||||||
|
|
||||||
|
Int m_globalUboBackendBlockIndex = -1;
|
||||||
|
Vector<Int> m_uniformBlockBackendIndices; // frontend block index -> backend index (-1 = absent)
|
||||||
|
Vector<SamplerUniformBinding> m_samplerUniformBindings;
|
||||||
|
Uint32 m_lastUploadedGlobalUboVersion = ~0u;
|
||||||
|
Uint32 m_syncedLinkVersion = ~0u;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Uint32 g_snormFallbackClampOutputMask;
|
extern Uint32 g_snormFallbackClampOutputMask;
|
||||||
extern Uint32 g_unormFallbackClampOutputMask;
|
extern Uint32 g_unormFallbackClampOutputMask;
|
||||||
|
// Backend id of the last glUseProgram issued through this backend; lets Use()
|
||||||
|
// skip redundant rebinds. Reset to 0 wherever glUseProgram(0) is issued or the
|
||||||
|
// ES context is recreated.
|
||||||
|
extern Uint g_lastUsedBackendProgramId;
|
||||||
extern StateBackendObjectRegistry<MG_State::GLState::ProgramObject, BackendProgramObjectImpl>
|
extern StateBackendObjectRegistry<MG_State::GLState::ProgramObject, BackendProgramObjectImpl>
|
||||||
g_backendProgramObjects;
|
g_backendProgramObjects;
|
||||||
} // namespace PrgramImpl
|
} // namespace PrgramImpl
|
||||||
|
|||||||
@@ -415,7 +415,17 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
|||||||
if (!state) {
|
if (!state) {
|
||||||
return EGL_FALSE;
|
return EGL_FALSE;
|
||||||
}
|
}
|
||||||
return state->SwapInterval(dpy, interval) ? EGL_TRUE : EGL_FALSE;
|
if (!state->SwapInterval(dpy, interval)) {
|
||||||
|
return EGL_FALSE;
|
||||||
|
}
|
||||||
|
// Forward the request to the backend's native presentation path; without this
|
||||||
|
// the app's vsync setting only ever reaches MobileGL's shadow state and the
|
||||||
|
// native surface stays at the driver default (interval 1 = always vsynced).
|
||||||
|
auto* backendObject = GetBackendObject(state);
|
||||||
|
if (backendObject) {
|
||||||
|
backendObject->SetEGLSwapInterval(static_cast<Int>(interval));
|
||||||
|
}
|
||||||
|
return EGL_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
EGLSurface CreatePbufferSurface(EGLDisplay dpy, EGLConfig config, const EGLint* attrib_list) {
|
EGLSurface CreatePbufferSurface(EGLDisplay dpy, EGLConfig config, const EGLint* attrib_list) {
|
||||||
|
|||||||
@@ -812,6 +812,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
MGLOG_D("%s: program = %d, location = %d, byteOffset = %d", __func__, programObject.GetExternalIndex(),
|
MGLOG_D("%s: program = %d, location = %d, byteOffset = %d", __func__, programObject.GetExternalIndex(),
|
||||||
location, offset + byteOffsetInsideUniform);
|
location, offset + byteOffsetInsideUniform);
|
||||||
Memcpy((char*)programObject.MapUBO() + offset + byteOffsetInsideUniform, value, ItemCount * sizeof(T));
|
Memcpy((char*)programObject.MapUBO() + offset + byteOffsetInsideUniform, value, ItemCount * sizeof(T));
|
||||||
|
programObject.MarkUBOContentDirty();
|
||||||
} else {
|
} else {
|
||||||
auto* ttype = programObject.GetUniformTType(location);
|
auto* ttype = programObject.GetUniformTType(location);
|
||||||
if (!ttype->isTexture() && !ttype->isImage()) return;
|
if (!ttype->isTexture() && !ttype->isImage()) return;
|
||||||
|
|||||||
@@ -202,6 +202,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject((Int)unit);
|
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject((Int)unit);
|
||||||
|
MG_State::pGLContext->NoteTextureUnitTouched((Int)unit);
|
||||||
if (sampler == 0) {
|
if (sampler == 0) {
|
||||||
textureUnit.SetSamplerObject(nullptr);
|
textureUnit.SetSamplerObject(nullptr);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -2495,6 +2495,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
auto& currentUnit = MG_State::pGLContext->GetTextureUnitObject(activeUnit);
|
auto& currentUnit = MG_State::pGLContext->GetTextureUnitObject(activeUnit);
|
||||||
auto& bindingSlot = currentUnit.GetBindingSlot(textureTarget);
|
auto& bindingSlot = currentUnit.GetBindingSlot(textureTarget);
|
||||||
bindingSlot.Bind(nullptr);
|
bindingSlot.Bind(nullptr);
|
||||||
|
MG_State::pGLContext->NoteTextureUnitTouched(activeUnit);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2528,6 +2529,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
auto& currentUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
auto& currentUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
|
||||||
auto& bindingSlot = currentUnit.GetBindingSlot(textureTarget);
|
auto& bindingSlot = currentUnit.GetBindingSlot(textureTarget);
|
||||||
bindingSlot.Bind(textureObject);
|
bindingSlot.Bind(textureObject);
|
||||||
|
MG_State::pGLContext->NoteTextureUnitTouched(MG_State::pGLContext->GetActiveTextureUnit());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActiveTexture_State(GLenum texture) {
|
void ActiveTexture_State(GLenum texture) {
|
||||||
@@ -3112,6 +3114,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(static_cast<Int>(unit));
|
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(static_cast<Int>(unit));
|
||||||
|
MG_State::pGLContext->NoteTextureUnitTouched(static_cast<Int>(unit));
|
||||||
if (texture == 0) {
|
if (texture == 0) {
|
||||||
for (auto& slot : textureUnit.GetAllBindingSlots()) {
|
for (auto& slot : textureUnit.GetAllBindingSlots()) {
|
||||||
slot.Bind(nullptr);
|
slot.Bind(nullptr);
|
||||||
@@ -3274,6 +3277,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
MG_State::pGLContext->GetImageTextureBinding(static_cast<Int>(unit))
|
MG_State::pGLContext->GetImageTextureBinding(static_cast<Int>(unit))
|
||||||
.Bind(textureObject, level, layered, layer, access, format);
|
.Bind(textureObject, level, layered, layer, access, format);
|
||||||
|
MG_State::pGLContext->NoteTextureUnitTouched(static_cast<Int>(unit));
|
||||||
bindImageTexture(unit, texture, level, layered, layer, access, format);
|
bindImageTexture(unit, texture, level, layered, layer, access, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,8 @@ namespace MobileGL {
|
|||||||
TextureUnit& GetTextureUnitObject(Int unit);
|
TextureUnit& GetTextureUnitObject(Int unit);
|
||||||
ImageTextureBinding& GetImageTextureBinding(Int unit);
|
ImageTextureBinding& GetImageTextureBinding(Int unit);
|
||||||
const ImageTextureBinding& GetImageTextureBinding(Int unit) const;
|
const ImageTextureBinding& GetImageTextureBinding(Int unit) const;
|
||||||
|
void NoteTextureUnitTouched(Int unit) { m_textureState.NoteUnitTouched(unit); }
|
||||||
|
Int GetMaxTouchedTextureUnit() const { return m_textureState.GetMaxTouchedUnit(); }
|
||||||
Bool ValidateTextureName(Uint index) const;
|
Bool ValidateTextureName(Uint index) const;
|
||||||
Bool ValidateTextureObject(Uint index) const;
|
Bool ValidateTextureObject(Uint index) const;
|
||||||
Int GetActiveTextureUnit() const;
|
Int GetActiveTextureUnit() const;
|
||||||
|
|||||||
@@ -89,20 +89,22 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
if (m_indexGenerator.IsValid(index)) {
|
if (m_indexGenerator.IsValid(index)) {
|
||||||
auto it = m_textureObjects.find(index);
|
auto it = m_textureObjects.find(index);
|
||||||
if (it != m_textureObjects.end()) {
|
if (it != m_textureObjects.end()) {
|
||||||
for (auto& unit : m_textureUnits) {
|
// Units past the touched high-water mark can never reference a texture.
|
||||||
auto& bindingSlots = unit.GetAllBindingSlots();
|
for (Int unit = 0; unit <= m_maxTouchedUnit; ++unit) {
|
||||||
|
auto& bindingSlots = m_textureUnits[unit].GetAllBindingSlots();
|
||||||
for (auto& bindingSlot : bindingSlots) {
|
for (auto& bindingSlot : bindingSlots) {
|
||||||
if (bindingSlot.GetBoundObject() == it->second) {
|
if (bindingSlot.GetBoundObject() == it->second) {
|
||||||
bindingSlot.Bind(nullptr);
|
bindingSlot.Bind(nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (auto& imageBinding : m_imageTextureBindings) {
|
for (Int unit = 0; unit <= m_maxTouchedUnit; ++unit) {
|
||||||
|
auto& imageBinding = m_imageTextureBindings[unit];
|
||||||
if (imageBinding.Texture == it->second) {
|
if (imageBinding.Texture == it->second) {
|
||||||
imageBinding.Bind(nullptr, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R8);
|
imageBinding.Bind(nullptr, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_textureObjects.erase(it);
|
m_textureObjects.erase(index);
|
||||||
}
|
}
|
||||||
m_indexGenerator.Delete(index);
|
m_indexGenerator.Delete(index);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,16 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
Bool ValidateName(Uint index) const;
|
Bool ValidateName(Uint index) const;
|
||||||
Bool ValidateTextureObject(Uint index) const;
|
Bool ValidateTextureObject(Uint index) const;
|
||||||
|
|
||||||
|
// High-water mark of texture units ever touched by a texture or sampler bind.
|
||||||
|
// Units above it have provably-empty binding slots, so per-draw backend scans
|
||||||
|
// can stop there instead of walking all MAX_TEXTURE_IMAGE_UNITS units.
|
||||||
|
void NoteUnitTouched(Int unit) {
|
||||||
|
if (unit > m_maxTouchedUnit && unit < MAX_TEXTURE_IMAGE_UNITS) m_maxTouchedUnit = unit;
|
||||||
|
}
|
||||||
|
Int GetMaxTouchedUnit() const { return m_maxTouchedUnit; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Int m_maxTouchedUnit = -1;
|
||||||
Int m_activeTextureUnit = 0;
|
Int m_activeTextureUnit = 0;
|
||||||
Array<TextureUnit, MAX_TEXTURE_IMAGE_UNITS> m_textureUnits;
|
Array<TextureUnit, MAX_TEXTURE_IMAGE_UNITS> m_textureUnits;
|
||||||
Array<ImageTextureBinding, MAX_TEXTURE_IMAGE_UNITS> m_imageTextureBindings;
|
Array<ImageTextureBinding, MAX_TEXTURE_IMAGE_UNITS> m_imageTextureBindings;
|
||||||
|
|||||||
Reference in New Issue
Block a user