mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 14:18:31 +09:00
[Perf] (MG_Backend/DirectVulkan): skip the per-draw sampled-texture walk when the bound set is unchanged (texture-bind generation + program state version); CollectSampledTextures 5.0%->0.2%, fps 228->249
This commit is contained in:
@@ -3389,6 +3389,9 @@ void main() {
|
|||||||
// Begin command recording if not yet
|
// Begin command recording if not yet
|
||||||
if (!frame.isCommandRecording) {
|
if (!frame.isCommandRecording) {
|
||||||
m_frameContext.BeginCommandRecording();
|
m_frameContext.BeginCommandRecording();
|
||||||
|
// New command buffer: a program/FBO address from a previous frame may have been
|
||||||
|
// recycled, so start the sampled-set skip cache fresh this frame.
|
||||||
|
m_lastSampledSetValid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* activeRenderPass = VkRenderPassManager::GetActiveRenderPass();
|
auto* activeRenderPass = VkRenderPassManager::GetActiveRenderPass();
|
||||||
@@ -3397,9 +3400,31 @@ void main() {
|
|||||||
// which probably indicates it's been gone through codepath like `fbo attach` -> `clear` -> `fbo detach`, and
|
// which probably indicates it's been gone through codepath like `fbo attach` -> `clear` -> `fbo detach`, and
|
||||||
// without draws in between to give it a chance to materialize such clear.
|
// without draws in between to give it a chance to materialize such clear.
|
||||||
// Deal with this situation here.
|
// Deal with this situation here.
|
||||||
auto& sampledTextures = m_sampledTexturesScratch; // cleared by CollectSampledTextures
|
// Reuse the previous draw's sampled-texture list when the set is provably unchanged (same
|
||||||
Bool hasSampledTextures = m_uniformManager->CollectSampledTextures(program, programObj, sampledTextures);
|
// program+state+transform and no bind/unbind/delete since), skipping the per-draw GL walk.
|
||||||
MOBILEGL_ASSERT(hasSampledTextures, "%s: CollectSampledTextures failed", __func__);
|
// The layout/feedback/transition loops below still run on the list every draw, so this only
|
||||||
|
// elides re-resolving *which* textures are sampled, never their layout handling.
|
||||||
|
auto& sampledTextures = m_sampledTexturesScratch;
|
||||||
|
{
|
||||||
|
const Uint programIndex = program.GetExternalIndex();
|
||||||
|
const Uint32 programVersion = program.GetBackendStateVersion();
|
||||||
|
const Uint64 bindGeneration = MG_State::pGLContext->GetTextureBindGeneration();
|
||||||
|
const Bool sampledSetUnchanged =
|
||||||
|
m_lastSampledSetValid && m_lastSampledSetProgramIndex == programIndex &&
|
||||||
|
m_lastSampledSetProgramVersion == programVersion &&
|
||||||
|
m_lastSampledSetTransformFlags == transformFlags &&
|
||||||
|
m_lastSampledSetBindGeneration == bindGeneration;
|
||||||
|
if (!sampledSetUnchanged) {
|
||||||
|
const Bool hasSampledTextures =
|
||||||
|
m_uniformManager->CollectSampledTextures(program, programObj, sampledTextures);
|
||||||
|
MOBILEGL_ASSERT(hasSampledTextures, "%s: CollectSampledTextures failed", __func__);
|
||||||
|
m_lastSampledSetValid = true;
|
||||||
|
m_lastSampledSetProgramIndex = programIndex;
|
||||||
|
m_lastSampledSetProgramVersion = programVersion;
|
||||||
|
m_lastSampledSetTransformFlags = transformFlags;
|
||||||
|
m_lastSampledSetBindGeneration = bindGeneration;
|
||||||
|
}
|
||||||
|
}
|
||||||
MGLOG_D("SetupDraw: program=%u drawFbo=%u sampledTextureCount=%zu activeRenderPass=%s",
|
MGLOG_D("SetupDraw: program=%u drawFbo=%u sampledTextureCount=%zu activeRenderPass=%s",
|
||||||
program.GetExternalIndex(), drawFbo ? drawFbo->GetExternalIndex() : 0u, sampledTextures.size(),
|
program.GetExternalIndex(), drawFbo ? drawFbo->GetExternalIndex() : 0u, sampledTextures.size(),
|
||||||
activeRenderPass ? "true" : "false");
|
activeRenderPass ? "true" : "false");
|
||||||
|
|||||||
@@ -403,6 +403,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
DepthMipmapResources m_depthMipmapResources;
|
DepthMipmapResources m_depthMipmapResources;
|
||||||
Vector<DeferredDepthMipmapCleanup> m_deferredDepthMipmapCleanup;
|
Vector<DeferredDepthMipmapCleanup> m_deferredDepthMipmapCleanup;
|
||||||
|
|
||||||
|
// Skip the per-draw CollectSampledTextures walk (~5% of the render thread) when the sampled
|
||||||
|
// texture SET is provably unchanged from the previous draw: same program (external index +
|
||||||
|
// backend-state version, which covers sampler-uniform reassignment / relink) and transform
|
||||||
|
// flags, and no texture bind/unbind/delete since (GetTextureBindGeneration). On a hit,
|
||||||
|
// m_sampledTexturesScratch still holds the previous draw's list and steps 2-4 (feedback /
|
||||||
|
// layout probe / transition) re-run on it, so layout correctness is unaffected - only the GL
|
||||||
|
// walk is skipped. Reset per command-buffer recording so a reused program/FBO address can't
|
||||||
|
// outlive a frame.
|
||||||
|
Bool m_lastSampledSetValid = false;
|
||||||
|
Uint m_lastSampledSetProgramIndex = 0;
|
||||||
|
Uint32 m_lastSampledSetProgramVersion = 0;
|
||||||
|
ProgramFactory::CompileOptionFlags m_lastSampledSetTransformFlags = {};
|
||||||
|
Uint64 m_lastSampledSetBindGeneration = 0;
|
||||||
|
|
||||||
// Per-draw scratch buffers (clear keeps capacity) — these paths run for every
|
// Per-draw scratch buffers (clear keeps capacity) — these paths run for every
|
||||||
// draw call and must not allocate.
|
// draw call and must not allocate.
|
||||||
Vector<MG_State::GLState::ITextureObject*> m_sampledTexturesScratch;
|
Vector<MG_State::GLState::ITextureObject*> m_sampledTexturesScratch;
|
||||||
|
|||||||
@@ -102,6 +102,10 @@ namespace MobileGL {
|
|||||||
const ImageTextureBinding& GetImageTextureBinding(Int unit) const;
|
const ImageTextureBinding& GetImageTextureBinding(Int unit) const;
|
||||||
void NoteTextureUnitTouched(Int unit) { m_textureState.NoteUnitTouched(unit); }
|
void NoteTextureUnitTouched(Int unit) { m_textureState.NoteUnitTouched(unit); }
|
||||||
Int GetMaxTouchedTextureUnit() const { return m_textureState.GetMaxTouchedUnit(); }
|
Int GetMaxTouchedTextureUnit() const { return m_textureState.GetMaxTouchedUnit(); }
|
||||||
|
// Monotonic counter bumped whenever a texture bind/unbind/delete changes which
|
||||||
|
// texture is bound at a unit; lets a backend skip re-resolving an unchanged
|
||||||
|
// per-draw sampled-texture set.
|
||||||
|
Uint64 GetTextureBindGeneration() const { return m_textureState.GetTextureBindGeneration(); }
|
||||||
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;
|
||||||
|
|||||||
@@ -104,6 +104,10 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
imageBinding.Bind(nullptr, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R8);
|
imageBinding.Bind(nullptr, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Deleting a texture unbinds it from every unit above; treat that as a binding
|
||||||
|
// change so a cached sampled-texture set (which may hold this raw pointer) is
|
||||||
|
// re-resolved instead of dangling.
|
||||||
|
BumpTextureBindGeneration();
|
||||||
m_textureObjects.erase(index);
|
m_textureObjects.erase(index);
|
||||||
}
|
}
|
||||||
m_indexGenerator.Delete(index);
|
m_indexGenerator.Delete(index);
|
||||||
|
|||||||
@@ -63,10 +63,20 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
// can stop there instead of walking all MAX_TEXTURE_IMAGE_UNITS units.
|
// can stop there instead of walking all MAX_TEXTURE_IMAGE_UNITS units.
|
||||||
void NoteUnitTouched(Int unit) {
|
void NoteUnitTouched(Int unit) {
|
||||||
if (unit > m_maxTouchedUnit && unit < MAX_TEXTURE_IMAGE_UNITS) m_maxTouchedUnit = unit;
|
if (unit > m_maxTouchedUnit && unit < MAX_TEXTURE_IMAGE_UNITS) m_maxTouchedUnit = unit;
|
||||||
|
// Every texture/sampler bind entry point (glBindTexture / glBindTextureUnit /
|
||||||
|
// glBindTextures / glBindSampler) routes through here, so bumping the generation here
|
||||||
|
// - plus in MarkTextureObjectForDeletion for delete-unbind - covers every change to
|
||||||
|
// which texture is bound at which unit. A backend that has cached the per-draw
|
||||||
|
// sampled-texture set can compare this against a snapshot to skip re-resolving it when
|
||||||
|
// no bind changed (the block atlas + lightmap stay bound across a whole terrain batch).
|
||||||
|
++m_textureBindGeneration;
|
||||||
}
|
}
|
||||||
Int GetMaxTouchedUnit() const { return m_maxTouchedUnit; }
|
Int GetMaxTouchedUnit() const { return m_maxTouchedUnit; }
|
||||||
|
Uint64 GetTextureBindGeneration() const { return m_textureBindGeneration; }
|
||||||
|
void BumpTextureBindGeneration() { ++m_textureBindGeneration; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Uint64 m_textureBindGeneration = 0;
|
||||||
Int m_maxTouchedUnit = -1;
|
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;
|
||||||
|
|||||||
Reference in New Issue
Block a user