mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +09:00
DirectGLES re-derived the whole texture binding state for every draw: for each touched unit, two alias-resolution passes over all binding slots, then a third walk to unbind native targets nothing claimed, then the sampler. With the Minecraft-shaped bench that was 13.2% of the render thread in BindCurrentTextures alone, plus 4.6% in SyncNeccessaryTextures deciding which textures to consider. The answer is identical across a whole terrain batch. The resolution is now memoised, and what makes replaying it as a no-op legitimate is that the memo does not merely trust a key: it compares the backend's own bound texture shadow against the one resolution left behind. Every path that binds a texture behind this function's back already maintains that shadow - the scratch bind an upload does on the temp unit, CopyTexSubImage2D and GenerateMipmap binding on the active unit, the glBindTextures fast path, the scrub a backend texture performs when it is destroyed or respecified - so a memcmp catches all of them without having to enumerate them. On top of that the key covers the texture bind generation, the program that arbitrates aliased targets (pointer, lifetime id, backend state version, link status), and the ES context generation. Two invalidation sources had no signal at all and needed one. Mipmap completeness decides whether a texture is bound in the first place, and it moves with texture shape and with the effective sampler's filter - so a sampling-resolution generation now moves with both, routed through single choke points (TextureObjectBase::BumpShapeVersion, SamplerObject::BumpVersion) so a future bump site cannot forget it. A texture context id was needed because both generations restart at zero in a new GLContext, which can land on the old heap address. This also closes a pre-existing hole rather than working around it: glDeleteSamplers unbinds the sampler from every unit straight through TextureUnit::SetSamplerObject, bypassing the touch bookkeeping, so that setter now bumps the bind generation on a real change. The sampler bind step itself stays outside the memo and runs every draw - the program's raw-depth-fetch substitution rewrites unit samplers immediately afterwards, so a memo there could never hit. ns per draw, DriverBench on a GTX 1660 SUPER (native / Espryt): mc_vanilla_draw 253 / 2037->1315, mc_ubo_range 202 / 1684->955, mc_sodium_multidraw 739 / 3939->3150. Espryt goes from 8.3x to 4.7x the native driver on the per-draw uniform-range case. Magma is unaffected (the MG_State additions are counter bumps), and no case regressed. Unit tests 421/421.
235 lines
9.1 KiB
C++
235 lines
9.1 KiB
C++
// MobileGL - MobileGL/MG_State/GLState/SamplerState/SamplerObject.cpp
|
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
|
// Licensed under the GNU Lesser General Public License v3.0:
|
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// End of Source File Header
|
|
|
|
#include "SamplerObject.h"
|
|
|
|
#include <MG_State/GLState/Core.h>
|
|
|
|
#include <atomic>
|
|
|
|
namespace MobileGL {
|
|
namespace MG_State {
|
|
namespace GLState {
|
|
static std::atomic<Uint64> s_nextSamplerLifetimeId = 1;
|
|
|
|
Uint64 SamplerObject::AllocateLifetimeId() {
|
|
return s_nextSamplerLifetimeId.fetch_add(1, std::memory_order_relaxed);
|
|
}
|
|
|
|
SamplerObject::SamplerObject(Uint externalIndex)
|
|
: m_externalIndex(externalIndex), m_lifetimeId(AllocateLifetimeId()) {}
|
|
|
|
void SamplerObject::BumpVersion() {
|
|
++m_version;
|
|
// Every setter early-outs on an unchanged value, so this only runs on a real
|
|
// parameter change. The generation is bumped for ALL parameters, not just filter
|
|
// ones that feed mipmap-completeness: a backend memo of the resolved per-unit
|
|
// bindings must never miss an invalidation, and over-invalidating on a wrap-mode
|
|
// write costs one re-resolve.
|
|
if (pGLContext) pGLContext->BumpSamplingResolutionGeneration();
|
|
}
|
|
|
|
void SamplerObject::SetWrapS(SamplerWrapMode mode) {
|
|
if (mode == m_samplerParameters.wrapS) return;
|
|
|
|
m_samplerParameters.wrapS = mode;
|
|
BumpVersion();
|
|
}
|
|
|
|
void SamplerObject::SetWrapT(SamplerWrapMode mode) {
|
|
if (mode == m_samplerParameters.wrapT) return;
|
|
|
|
m_samplerParameters.wrapT = mode;
|
|
BumpVersion();
|
|
}
|
|
|
|
void SamplerObject::SetWrapR(SamplerWrapMode mode) {
|
|
if (mode == m_samplerParameters.wrapR) return;
|
|
|
|
m_samplerParameters.wrapR = mode;
|
|
BumpVersion();
|
|
}
|
|
|
|
void SamplerObject::SetMinFilter(SamplerFilterMode mode) {
|
|
if (mode == m_samplerParameters.minFilter) return;
|
|
|
|
m_samplerParameters.minFilter = mode;
|
|
BumpVersion();
|
|
}
|
|
|
|
void SamplerObject::SetMagFilter(SamplerFilterMode mode) {
|
|
if (mode == m_samplerParameters.magFilter) return;
|
|
|
|
m_samplerParameters.magFilter = mode;
|
|
BumpVersion();
|
|
}
|
|
|
|
void SamplerObject::SetMipmapMode(SamplerMipmapMode mode) {
|
|
if (mode == m_samplerParameters.mipmapMode) return;
|
|
|
|
m_samplerParameters.mipmapMode = mode;
|
|
BumpVersion();
|
|
}
|
|
|
|
void SamplerObject::SetLodRange(Float minLod, Float maxLod) {
|
|
if (minLod == m_samplerParameters.minLod && maxLod == m_samplerParameters.maxLod) return;
|
|
m_samplerParameters.minLod = minLod;
|
|
m_samplerParameters.maxLod = maxLod;
|
|
BumpVersion();
|
|
}
|
|
|
|
void SamplerObject::SetLodBias(Float bias) {
|
|
if (bias == m_samplerParameters.lodBias) return;
|
|
|
|
m_samplerParameters.lodBias = bias;
|
|
BumpVersion();
|
|
}
|
|
|
|
void SamplerObject::SetMaxAnisotropy(Float maxAnisotropy) {
|
|
if (maxAnisotropy == m_samplerParameters.maxAnisotropy) return;
|
|
|
|
m_samplerParameters.maxAnisotropy = maxAnisotropy;
|
|
BumpVersion();
|
|
}
|
|
|
|
void SamplerObject::SetSamplerCompareFunc(SamplerCompareFunc func) {
|
|
if (func == m_samplerParameters.compareFunc) return;
|
|
|
|
m_samplerParameters.compareFunc = func;
|
|
BumpVersion();
|
|
}
|
|
|
|
void SamplerObject::SetCompareMode(SamplerCompareMode mode) {
|
|
if (mode == m_samplerParameters.compareMode) return;
|
|
|
|
m_samplerParameters.compareMode = mode;
|
|
BumpVersion();
|
|
}
|
|
|
|
SamplerWrapMode SamplerObject::GetWrapS() const {
|
|
return m_samplerParameters.wrapS;
|
|
}
|
|
|
|
SamplerWrapMode SamplerObject::GetWrapT() const {
|
|
return m_samplerParameters.wrapT;
|
|
}
|
|
|
|
SamplerWrapMode SamplerObject::GetWrapR() const {
|
|
return m_samplerParameters.wrapR;
|
|
}
|
|
|
|
SamplerFilterMode SamplerObject::GetMinFilter() const {
|
|
return m_samplerParameters.minFilter;
|
|
}
|
|
|
|
SamplerFilterMode SamplerObject::GetMagFilter() const {
|
|
return m_samplerParameters.magFilter;
|
|
}
|
|
|
|
SamplerMipmapMode SamplerObject::GetMipmapMode() const {
|
|
return m_samplerParameters.mipmapMode;
|
|
}
|
|
|
|
Float SamplerObject::GetMinLod() const {
|
|
return m_samplerParameters.minLod;
|
|
}
|
|
|
|
Float SamplerObject::GetMaxLod() const {
|
|
return m_samplerParameters.maxLod;
|
|
}
|
|
|
|
Float SamplerObject::GetLodBias() const {
|
|
return m_samplerParameters.lodBias;
|
|
}
|
|
|
|
Float SamplerObject::GetMaxAnisotropy() const {
|
|
return m_samplerParameters.maxAnisotropy;
|
|
}
|
|
|
|
// The three border-colour representations are kept in step so a getter of any form has
|
|
// an answer whichever form was written. Integer <-> float uses the plain value, matching
|
|
// what glTexParameterIiv/Iuiv mean: those forms are for integer texture formats, whose
|
|
// border components are the raw integers rather than a normalized fraction.
|
|
void SamplerObject::SetBorderColor(const FloatVec4& color) {
|
|
if (color == m_samplerParameters.borderColor) return;
|
|
|
|
m_samplerParameters.borderColor = color;
|
|
m_samplerParameters.borderColorI =
|
|
IntVec4(static_cast<Int32>(color.x()), static_cast<Int32>(color.y()),
|
|
static_cast<Int32>(color.z()), static_cast<Int32>(color.w()));
|
|
m_samplerParameters.borderColorUI =
|
|
UintVec4(static_cast<Uint32>(color.x()), static_cast<Uint32>(color.y()),
|
|
static_cast<Uint32>(color.z()), static_cast<Uint32>(color.w()));
|
|
BumpVersion();
|
|
}
|
|
|
|
void SamplerObject::SetBorderColorI(const IntVec4& color) {
|
|
if (color == m_samplerParameters.borderColorI) return;
|
|
|
|
m_samplerParameters.borderColorI = color;
|
|
m_samplerParameters.borderColorUI =
|
|
UintVec4(static_cast<Uint32>(color.x()), static_cast<Uint32>(color.y()),
|
|
static_cast<Uint32>(color.z()), static_cast<Uint32>(color.w()));
|
|
m_samplerParameters.borderColor =
|
|
FloatVec4(static_cast<Float>(color.x()), static_cast<Float>(color.y()),
|
|
static_cast<Float>(color.z()), static_cast<Float>(color.w()));
|
|
BumpVersion();
|
|
}
|
|
|
|
void SamplerObject::SetBorderColorUI(const UintVec4& color) {
|
|
if (color == m_samplerParameters.borderColorUI) return;
|
|
|
|
m_samplerParameters.borderColorUI = color;
|
|
m_samplerParameters.borderColorI =
|
|
IntVec4(static_cast<Int32>(color.x()), static_cast<Int32>(color.y()),
|
|
static_cast<Int32>(color.z()), static_cast<Int32>(color.w()));
|
|
m_samplerParameters.borderColor =
|
|
FloatVec4(static_cast<Float>(color.x()), static_cast<Float>(color.y()),
|
|
static_cast<Float>(color.z()), static_cast<Float>(color.w()));
|
|
BumpVersion();
|
|
}
|
|
|
|
const FloatVec4& SamplerObject::GetBorderColor() const {
|
|
return m_samplerParameters.borderColor;
|
|
}
|
|
|
|
const IntVec4& SamplerObject::GetBorderColorI() const {
|
|
return m_samplerParameters.borderColorI;
|
|
}
|
|
|
|
const UintVec4& SamplerObject::GetBorderColorUI() const {
|
|
return m_samplerParameters.borderColorUI;
|
|
}
|
|
|
|
SamplerCompareMode SamplerObject::GetCompareMode() const {
|
|
return m_samplerParameters.compareMode;
|
|
}
|
|
|
|
SamplerCompareFunc SamplerObject::GetSamplerCompareFunc() const {
|
|
return m_samplerParameters.compareFunc;
|
|
}
|
|
|
|
Uint SamplerObject::GetExternalIndex() const {
|
|
return m_externalIndex;
|
|
}
|
|
|
|
const SamplerParameters& SamplerObject::GetAllSamplerParameters() const {
|
|
return m_samplerParameters;
|
|
}
|
|
|
|
Uint16 SamplerObject::GetVersion() const {
|
|
return m_version;
|
|
}
|
|
|
|
Uint64 SamplerObject::GetLifetimeId() const {
|
|
return m_lifetimeId;
|
|
}
|
|
} // namespace GLState
|
|
} // namespace MG_State
|
|
} // namespace MobileGL
|