mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
[Fix] (MG_State, MG_Impl, MG_Test): enforce strict GL 3.3 core rules only on contexts that explicitly request a core profile - texture deleted-name reservation keep and VAO-0 draws relax otherwise or under MOBILEGL_RELAXED_SEMANTICS, and GL_CONTEXT_PROFILE_MASK reports the requested profile
This commit is contained in:
@@ -61,6 +61,12 @@ namespace MobileGL::MG_Config {
|
|||||||
// per-draw glBufferSubData path instead of the persistent-mapped ring allocator
|
// per-draw glBufferSubData path instead of the persistent-mapped ring allocator
|
||||||
// (negative control / driver-bug escape hatch).
|
// (negative control / driver-bug escape hatch).
|
||||||
Bool DisableUboRing = false;
|
Bool DisableUboRing = false;
|
||||||
|
// MOBILEGL_RELAXED_SEMANTICS: relax strict core-profile rules (e.g. VAO-0 draws,
|
||||||
|
// texture-name reuse after delete) even on contexts that explicitly requested a core
|
||||||
|
// profile. Without it, relaxed semantics still apply to every context that did not
|
||||||
|
// explicitly request a core profile via EGL_CONTEXT_OPENGL_PROFILE_MASK / a >=3.1
|
||||||
|
// version request.
|
||||||
|
Bool RelaxedSemantics = false;
|
||||||
};
|
};
|
||||||
extern FeaturesTable Features;
|
extern FeaturesTable Features;
|
||||||
} // namespace MobileGL::MG_Config
|
} // namespace MobileGL::MG_Config
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ namespace MobileGL::MG_ConfigLoader {
|
|||||||
features.CoherentAsFlush = QueryEnvFlag("MOBILEGL_COHERENT_AS_FLUSH");
|
features.CoherentAsFlush = QueryEnvFlag("MOBILEGL_COHERENT_AS_FLUSH");
|
||||||
features.TraceSkipAutodestroy = QueryEnvFlag("MOBILEGL_TRACE_SKIP_AUTODESTROY");
|
features.TraceSkipAutodestroy = QueryEnvFlag("MOBILEGL_TRACE_SKIP_AUTODESTROY");
|
||||||
features.DisableUboRing = QueryEnvFlag("MOBILEGL_DISABLE_UBO_RING");
|
features.DisableUboRing = QueryEnvFlag("MOBILEGL_DISABLE_UBO_RING");
|
||||||
|
features.RelaxedSemantics = QueryEnvFlag("MOBILEGL_RELAXED_SEMANTICS");
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void InitBackendType() {
|
inline void InitBackendType() {
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto& vao = MG_State::pGLContext->GetBoundVertexArray();
|
const auto& vao = MG_State::pGLContext->GetBoundVertexArray();
|
||||||
if (MG_State::pEGLContext->IsCurrentContextOpenGLCoreProfile() && vao && vao->GetExternalIndex() == 0) {
|
if (vao && vao->GetExternalIndex() == 0 && !MG_State::IsRelaxedSemanticsActive()) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidOperation,
|
ErrorCode::InvalidOperation,
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||||
|
|||||||
@@ -1734,7 +1734,11 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
*params = 1024 * 1024; // TODO
|
*params = 1024 * 1024; // TODO
|
||||||
return;
|
return;
|
||||||
case GL_CONTEXT_PROFILE_MASK:
|
case GL_CONTEXT_PROFILE_MASK:
|
||||||
*params = GL_CONTEXT_CORE_PROFILE_BIT;
|
// Reports the requested context profile (EGL defaults 3.x contexts to core);
|
||||||
|
// MOBILEGL_RELAXED_SEMANTICS loosens behavior without changing the identity.
|
||||||
|
*params = MG_State::pEGLContext && MG_State::pEGLContext->IsCurrentContextOpenGLCompatibilityProfile()
|
||||||
|
? GL_CONTEXT_COMPATIBILITY_PROFILE_BIT
|
||||||
|
: GL_CONTEXT_CORE_PROFILE_BIT;
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -764,6 +764,20 @@ namespace MobileGL {
|
|||||||
ctx->MajorVersion > 3 || (ctx->MajorVersion == 3 && ctx->MinorVersion >= 1);
|
ctx->MajorVersion > 3 || (ctx->MajorVersion == 3 && ctx->MinorVersion >= 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bool EGLContext::IsCurrentContextOpenGLCompatibilityProfile() const {
|
||||||
|
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||||
|
auto currentIt = m_threadCurrents.find(CurrentThreadKey());
|
||||||
|
if (currentIt == m_threadCurrents.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto* ctx = TryGetContext(currentIt->second.Context);
|
||||||
|
// Affirmative check: true only when the host explicitly requested the
|
||||||
|
// compatibility bit. Attrib-less contexts report false and thus read as core
|
||||||
|
// for GL_CONTEXT_PROFILE_MASK (EGL defaults 3.x contexts to the core profile).
|
||||||
|
return ctx && ctx->ClientAPI == EGL_OPENGL_API &&
|
||||||
|
(ctx->OpenGLProfileMask & EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
EGLint EGLContext::GetCurrentContextFlags() const {
|
EGLint EGLContext::GetCurrentContextFlags() const {
|
||||||
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||||
auto currentIt = m_threadCurrents.find(CurrentThreadKey());
|
auto currentIt = m_threadCurrents.find(CurrentThreadKey());
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ namespace MobileGL {
|
|||||||
Bool ValidateContext(EGLContextHandle context) const;
|
Bool ValidateContext(EGLContextHandle context) const;
|
||||||
Bool ValidateContextOnDisplay(EGLDisplayHandle display, EGLContextHandle context) const;
|
Bool ValidateContextOnDisplay(EGLDisplayHandle display, EGLContextHandle context) const;
|
||||||
Bool IsCurrentContextOpenGLCoreProfile() const;
|
Bool IsCurrentContextOpenGLCoreProfile() const;
|
||||||
|
Bool IsCurrentContextOpenGLCompatibilityProfile() const;
|
||||||
EGLint GetCurrentContextFlags() const;
|
EGLint GetCurrentContextFlags() const;
|
||||||
|
|
||||||
// Surface
|
// Surface
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "Core.h"
|
#include "Core.h"
|
||||||
#include "MG_State/GLState/RenderbufferState/RenderbufferObject.h"
|
#include "MG_State/GLState/RenderbufferState/RenderbufferObject.h"
|
||||||
#include "MG_State/EGLState/Core.h"
|
#include "MG_State/EGLState/Core.h"
|
||||||
|
#include <Config.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_State {
|
namespace MobileGL::MG_State {
|
||||||
void Init() {
|
void Init() {
|
||||||
@@ -17,6 +18,11 @@ namespace MobileGL::MG_State {
|
|||||||
pEGLContext = MakeUnique<EGLState::EGLContext>();
|
pEGLContext = MakeUnique<EGLState::EGLContext>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bool IsRelaxedSemanticsActive() {
|
||||||
|
return MG_Config::Features.RelaxedSemantics ||
|
||||||
|
!(pEGLContext && pEGLContext->IsCurrentContextOpenGLCoreProfile());
|
||||||
|
}
|
||||||
|
|
||||||
namespace GLState {
|
namespace GLState {
|
||||||
// Error
|
// Error
|
||||||
void GLContext::RecordError(ErrorCode code, UniquePtr<ErrorInfo> info) {
|
void GLContext::RecordError(ErrorCode code, UniquePtr<ErrorInfo> info) {
|
||||||
@@ -235,7 +241,7 @@ namespace MobileGL::MG_State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GLContext::MarkTextureObjectForDeletion(Uint index) {
|
void GLContext::MarkTextureObjectForDeletion(Uint index) {
|
||||||
m_textureState.MarkTextureObjectForDeletion(index);
|
m_textureState.MarkTextureObjectForDeletion(index, IsRelaxedSemanticsActive());
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureUnit& GLContext::GetTextureUnitObject(Int unit) {
|
TextureUnit& GLContext::GetTextureUnitObject(Int unit) {
|
||||||
|
|||||||
@@ -252,5 +252,12 @@ namespace MobileGL {
|
|||||||
} // namespace GLState
|
} // namespace GLState
|
||||||
|
|
||||||
extern UniquePtr<GLState::GLContext> pGLContext;
|
extern UniquePtr<GLState::GLContext> pGLContext;
|
||||||
|
|
||||||
|
// True when relaxed GL semantics apply. Strict core rules are enforced only when the
|
||||||
|
// current EGL context explicitly requested a core profile (core bit in
|
||||||
|
// EGL_CONTEXT_OPENGL_PROFILE_MASK, or a >=3.1 version request without the compatibility
|
||||||
|
// bit) and MOBILEGL_RELAXED_SEMANTICS is off; no current context, legacy version
|
||||||
|
// requests, and the compatibility bit all relax.
|
||||||
|
Bool IsRelaxedSemanticsActive();
|
||||||
} // namespace MG_State
|
} // namespace MG_State
|
||||||
} // namespace MobileGL
|
} // namespace MobileGL
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
return textureObject;
|
return textureObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureState::MarkTextureObjectForDeletion(Uint index) {
|
void TextureState::MarkTextureObjectForDeletion(Uint index, Bool keepUnboundReservation) {
|
||||||
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()) {
|
||||||
@@ -123,10 +123,15 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
BumpTextureBindGeneration();
|
BumpTextureBindGeneration();
|
||||||
m_textureObjects.erase(index);
|
m_textureObjects.erase(index);
|
||||||
m_indexGenerator.Delete(index);
|
m_indexGenerator.Delete(index);
|
||||||
|
} else if (!keepUnboundReservation) {
|
||||||
|
// GL 3.3 core 3.8.1 makes a deleted name unused again even when GenTextures only
|
||||||
|
// reserved it and no bind ever instantiated an object (so a later bind of it must
|
||||||
|
// fail), and the reservation has to return to the free list.
|
||||||
|
m_indexGenerator.Delete(index);
|
||||||
}
|
}
|
||||||
// Compatibility: legacy Minecraft may delete a generated name before its first bind,
|
// Relaxed semantics: legacy apps may delete a generated name before its first
|
||||||
// then bind and populate that same name. Keep such a reservation alive; only a real
|
// bind, then bind and populate that same name. Keep such a reservation alive there;
|
||||||
// texture object reaching deletion releases its index above.
|
// a real texture object reaching deletion still releases its index above.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
const ImageTextureBinding& GetImageTextureBinding(Int unit) const;
|
const ImageTextureBinding& GetImageTextureBinding(Int unit) const;
|
||||||
Int GetActiveTextureUnit() const;
|
Int GetActiveTextureUnit() const;
|
||||||
void SetActiveTextureUnit(Int unit);
|
void SetActiveTextureUnit(Int unit);
|
||||||
void MarkTextureObjectForDeletion(Uint index);
|
void MarkTextureObjectForDeletion(Uint index, Bool keepUnboundReservation);
|
||||||
Bool ValidateName(Uint index) const;
|
Bool ValidateName(Uint index) const;
|
||||||
Bool ValidateTextureObject(Uint index) const;
|
Bool ValidateTextureObject(Uint index) const;
|
||||||
|
|
||||||
|
|||||||
@@ -102,3 +102,33 @@ TEST(EGLStateMakeCurrent, SameThreadRepeatedAttachReleaseDoesNotLeaveStaleOwner)
|
|||||||
EXPECT_TRUE(fixture->State.MakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
|
EXPECT_TRUE(fixture->State.MakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
|
||||||
EXPECT_EQ(fixture->State.ConsumeError(), EGL_SUCCESS);
|
EXPECT_EQ(fixture->State.ConsumeError(), EGL_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The compatibility-profile accessor is affirmative-only (it backs GL_CONTEXT_PROFILE_MASK
|
||||||
|
// reporting): attrib-less contexts (profile mask 0) and released threads both answer "not
|
||||||
|
// compat" and therefore read as core-profile contexts.
|
||||||
|
TEST(EGLStateProfile, CompatibilityProfileRequiresExplicitCompatBit) {
|
||||||
|
auto fixture = CreateFixture();
|
||||||
|
|
||||||
|
EXPECT_FALSE(fixture->State.IsCurrentContextOpenGLCompatibilityProfile());
|
||||||
|
|
||||||
|
EXPECT_TRUE(fixture->State.MakeCurrent(fixture->Display, fixture->Surface, fixture->Surface, fixture->Context));
|
||||||
|
EXPECT_FALSE(fixture->State.IsCurrentContextOpenGLCompatibilityProfile());
|
||||||
|
|
||||||
|
const EGLint compatAttribs[] = {EGL_CONTEXT_MAJOR_VERSION,
|
||||||
|
3,
|
||||||
|
EGL_CONTEXT_MINOR_VERSION,
|
||||||
|
3,
|
||||||
|
EGL_CONTEXT_OPENGL_PROFILE_MASK,
|
||||||
|
EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT,
|
||||||
|
EGL_NONE};
|
||||||
|
const auto compatContext =
|
||||||
|
fixture->State.CreateContext(fixture->Display, fixture->Config, EGL_NO_CONTEXT, compatAttribs);
|
||||||
|
ASSERT_NE(compatContext, EGL_NO_CONTEXT);
|
||||||
|
EXPECT_TRUE(fixture->State.MakeCurrent(fixture->Display, fixture->Surface, fixture->Surface, compatContext));
|
||||||
|
EXPECT_TRUE(fixture->State.IsCurrentContextOpenGLCompatibilityProfile());
|
||||||
|
EXPECT_FALSE(fixture->State.IsCurrentContextOpenGLCoreProfile());
|
||||||
|
|
||||||
|
EXPECT_TRUE(fixture->State.MakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
|
||||||
|
EXPECT_FALSE(fixture->State.IsCurrentContextOpenGLCompatibilityProfile());
|
||||||
|
EXPECT_EQ(fixture->State.ConsumeError(), EGL_SUCCESS);
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,12 +12,14 @@
|
|||||||
|
|
||||||
#include "Includes.h"
|
#include "Includes.h"
|
||||||
#include "Init.h"
|
#include "Init.h"
|
||||||
|
#include <Config.h>
|
||||||
#include <MG_Backend/BackendObjects.h>
|
#include <MG_Backend/BackendObjects.h>
|
||||||
#include <MG_Backend/DirectGLES/Managers.h>
|
#include <MG_Backend/DirectGLES/Managers.h>
|
||||||
#include <MG_Impl/GLImpl/Getter/GL_Getter.h>
|
#include <MG_Impl/GLImpl/Getter/GL_Getter.h>
|
||||||
#include <MG_Impl/GLImpl/RenderState/GL_RenderState.h>
|
#include <MG_Impl/GLImpl/RenderState/GL_RenderState.h>
|
||||||
#include <MG_Impl/GLImpl/Sampler/GL_Sampler.h>
|
#include <MG_Impl/GLImpl/Sampler/GL_Sampler.h>
|
||||||
#include <MG_Impl/GLImpl/Texture/GL_Texture.h>
|
#include <MG_Impl/GLImpl/Texture/GL_Texture.h>
|
||||||
|
#include <MG_State/EGLState/Core.h>
|
||||||
#include <MG_State/GLState/Core.h>
|
#include <MG_State/GLState/Core.h>
|
||||||
#include <MG_State/GLState/TextureState/TextureObject.h>
|
#include <MG_State/GLState/TextureState/TextureObject.h>
|
||||||
#include <MG_Util/Converters/GLToMG/TextureEnumConverter.h>
|
#include <MG_Util/Converters/GLToMG/TextureEnumConverter.h>
|
||||||
@@ -380,10 +382,106 @@ TEST_F(TextureTest, GenThenBindCreatesObjectForUnsizedPackedBgraSubImageUpload)
|
|||||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy Minecraft reserves a texture name, deletes it before first bind, then reuses the same
|
namespace {
|
||||||
// name for the atlas upload. Preserve that generated reservation so the later bind can instantiate
|
// Strict core rules only apply when the current EGL context explicitly requested a core
|
||||||
// the object and subsequent sub-image uploads target it instead of the default texture.
|
// profile; the suite's default (no current context) runs with relaxed semantics. RAII so
|
||||||
TEST_F(TextureTest, DeleteGeneratedReservationThenBindCreatesObjectForSubImageUpload) {
|
// a failed ASSERT cannot leave the context current for the rest of the suite.
|
||||||
|
struct ScopedCoreProfileContext {
|
||||||
|
ScopedCoreProfileContext() {
|
||||||
|
auto& egl = *MG_State::pEGLContext;
|
||||||
|
m_display = egl.GetDisplay(EGL_DEFAULT_DISPLAY);
|
||||||
|
EXPECT_NE(m_display, EGL_NO_DISPLAY);
|
||||||
|
EXPECT_TRUE(egl.InitializeDisplay(m_display, nullptr, nullptr));
|
||||||
|
EGLint configCount = 0;
|
||||||
|
EXPECT_TRUE(egl.ChooseConfig(m_display, nullptr, &m_config, 1, &configCount));
|
||||||
|
const EGLint surfaceAttribs[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE};
|
||||||
|
m_surface = egl.CreatePbufferSurface(m_display, m_config, surfaceAttribs);
|
||||||
|
EXPECT_NE(m_surface, EGL_NO_SURFACE);
|
||||||
|
const EGLint contextAttribs[] = {EGL_CONTEXT_MAJOR_VERSION,
|
||||||
|
3,
|
||||||
|
EGL_CONTEXT_MINOR_VERSION,
|
||||||
|
3,
|
||||||
|
EGL_CONTEXT_OPENGL_PROFILE_MASK,
|
||||||
|
EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
|
||||||
|
EGL_NONE};
|
||||||
|
m_context = egl.CreateContext(m_display, m_config, EGL_NO_CONTEXT, contextAttribs);
|
||||||
|
EXPECT_NE(m_context, EGL_NO_CONTEXT);
|
||||||
|
EXPECT_TRUE(egl.MakeCurrent(m_display, m_surface, m_surface, m_context));
|
||||||
|
}
|
||||||
|
~ScopedCoreProfileContext() {
|
||||||
|
auto& egl = *MG_State::pEGLContext;
|
||||||
|
egl.MakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||||
|
if (m_context != EGL_NO_CONTEXT) egl.DestroyContext(m_display, m_context);
|
||||||
|
if (m_surface != EGL_NO_SURFACE) egl.DestroySurface(m_display, m_surface);
|
||||||
|
}
|
||||||
|
ScopedCoreProfileContext(const ScopedCoreProfileContext&) = delete;
|
||||||
|
ScopedCoreProfileContext& operator=(const ScopedCoreProfileContext&) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
EGLDisplay m_display = EGL_NO_DISPLAY;
|
||||||
|
EGLConfig m_config = nullptr;
|
||||||
|
EGLSurface m_surface = EGL_NO_SURFACE;
|
||||||
|
MG_State::EGLState::EGLContext::EGLContextHandle m_context = EGL_NO_CONTEXT;
|
||||||
|
};
|
||||||
|
|
||||||
|
// MOBILEGL_RELAXED_SEMANTICS loosens strict core rules even on explicit core-profile
|
||||||
|
// contexts. RAII so a failed ASSERT cannot leak the flag into the rest of the suite.
|
||||||
|
struct ScopedRelaxedSemantics {
|
||||||
|
ScopedRelaxedSemantics(): m_previous(MG_Config::Features.RelaxedSemantics) {
|
||||||
|
MG_Config::Features.RelaxedSemantics = true;
|
||||||
|
}
|
||||||
|
~ScopedRelaxedSemantics() {
|
||||||
|
MG_Config::Features.RelaxedSemantics = m_previous;
|
||||||
|
}
|
||||||
|
ScopedRelaxedSemantics(const ScopedRelaxedSemantics&) = delete;
|
||||||
|
ScopedRelaxedSemantics& operator=(const ScopedRelaxedSemantics&) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Bool m_previous;
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// GL 3.3 core 3.8.1: on an explicit core-profile context, DeleteTextures makes the name unused
|
||||||
|
// again whether or not a bind ever instantiated an object, so the reservation must go back to
|
||||||
|
// the generator's free list rather than leaking, and binding the dead name afterwards must fail.
|
||||||
|
TEST_F(TextureTest, DeleteGeneratedButUnboundNameReleasesReservationAndBindFails) {
|
||||||
|
ScopedCoreProfileContext coreContext;
|
||||||
|
ASSERT_FALSE(MG_State::IsRelaxedSemanticsActive());
|
||||||
|
|
||||||
|
GLuint texture = 0;
|
||||||
|
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||||
|
ASSERT_NE(texture, 0u);
|
||||||
|
ASSERT_TRUE(MG_State::pGLContext->ValidateTextureName(texture));
|
||||||
|
ASSERT_FALSE(MG_State::pGLContext->ValidateTextureObject(texture));
|
||||||
|
|
||||||
|
MG_Impl::GLImpl::DeleteTextures(1, &texture);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
EXPECT_FALSE(MG_State::pGLContext->ValidateTextureName(texture));
|
||||||
|
EXPECT_FALSE(MG_State::pGLContext->ValidateTextureObject(texture));
|
||||||
|
// IsTexture answers about a dead name without raising anything (GL 3.3 core 6.1.4).
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::IsTexture(texture), GL_FALSE);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
ExpectSingleGlError(GL_INVALID_OPERATION);
|
||||||
|
EXPECT_FALSE(MG_State::pGLContext->ValidateTextureObject(texture));
|
||||||
|
|
||||||
|
// The freed reservation is recycled (the generator's free list is LIFO, so the very same
|
||||||
|
// name comes back) - a delete that skipped the release would hand out a fresh name here.
|
||||||
|
GLuint recycled = 0;
|
||||||
|
MG_Impl::GLImpl::GenTextures(1, &recycled);
|
||||||
|
EXPECT_EQ(recycled, texture);
|
||||||
|
EXPECT_TRUE(MG_State::pGLContext->ValidateTextureName(recycled));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relaxed semantics - the default whenever the context did not explicitly request a core
|
||||||
|
// profile: legacy Minecraft reserves a texture name, deletes it before first bind, then reuses
|
||||||
|
// the same name for the atlas upload. Preserve that generated reservation so the later bind can
|
||||||
|
// instantiate the object and subsequent sub-image uploads target it instead of the default
|
||||||
|
// texture. Explicit core contexts keep the strict delete semantics asserted above.
|
||||||
|
TEST_F(TextureTest, RelaxedDefaultDeleteGeneratedReservationThenBindCreatesObjectForSubImageUpload) {
|
||||||
|
ASSERT_TRUE(MG_State::IsRelaxedSemanticsActive());
|
||||||
|
|
||||||
GLuint texture = 0;
|
GLuint texture = 0;
|
||||||
MG_Impl::GLImpl::GenTextures(1, &texture);
|
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||||
ASSERT_NE(texture, 0u);
|
ASSERT_NE(texture, 0u);
|
||||||
@@ -424,6 +522,28 @@ TEST_F(TextureTest, DeleteGeneratedReservationThenBindCreatesObjectForSubImageUp
|
|||||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MOBILEGL_RELAXED_SEMANTICS wins even on an explicit core-profile context: the deleted
|
||||||
|
// reservation survives and the name stays bindable.
|
||||||
|
TEST_F(TextureTest, RelaxedSemanticsOverrideKeepsDeletedReservationOnCoreProfileContext) {
|
||||||
|
ScopedCoreProfileContext coreContext;
|
||||||
|
ScopedRelaxedSemantics relaxedSemantics;
|
||||||
|
ASSERT_TRUE(MG_State::IsRelaxedSemanticsActive());
|
||||||
|
|
||||||
|
GLuint texture = 0;
|
||||||
|
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||||
|
ASSERT_NE(texture, 0u);
|
||||||
|
MG_Impl::GLImpl::DeleteTextures(1, &texture);
|
||||||
|
EXPECT_TRUE(MG_State::pGLContext->ValidateTextureName(texture));
|
||||||
|
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
EXPECT_TRUE(MG_State::pGLContext->ValidateTextureObject(texture));
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
MG_Impl::GLImpl::DeleteTextures(1, &texture);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(TextureTest, DeleteInstantiatedTextureInvalidatesNameUntilRegenerated) {
|
TEST_F(TextureTest, DeleteInstantiatedTextureInvalidatesNameUntilRegenerated) {
|
||||||
GLuint textures[2] = {};
|
GLuint textures[2] = {};
|
||||||
MG_Impl::GLImpl::GenTextures(2, textures);
|
MG_Impl::GLImpl::GenTextures(2, textures);
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ val pluginRendererConfig = buildJsonValue {
|
|||||||
customizable("MOBILEGL_MAGMA_FRAMESINFLIGHT", "3", RendererConfig.MetaString("mobilegl_magma_frames_inflight_title"))
|
customizable("MOBILEGL_MAGMA_FRAMESINFLIGHT", "3", RendererConfig.MetaString("mobilegl_magma_frames_inflight_title"))
|
||||||
toggleable("MOBILEGL_AVOID_SAMPLER_MIPMAP_MIN_FILTER", "1", false, RendererConfig.MetaString("mobilegl_avoid_sampler_mipmap_min_filter_title"))
|
toggleable("MOBILEGL_AVOID_SAMPLER_MIPMAP_MIN_FILTER", "1", false, RendererConfig.MetaString("mobilegl_avoid_sampler_mipmap_min_filter_title"))
|
||||||
toggleable("MOBILEGL_COHERENT_AS_FLUSH", "1", false, RendererConfig.MetaString("mobilegl_coherent_as_flush_title"))
|
toggleable("MOBILEGL_COHERENT_AS_FLUSH", "1", false, RendererConfig.MetaString("mobilegl_coherent_as_flush_title"))
|
||||||
|
toggleable("MOBILEGL_RELAXED_SEMANTICS", "1", false, RendererConfig.MetaString("mobilegl_relaxed_semantics_title"))
|
||||||
toggleable("MOBILEGL_USE_ANGLE", "1", false, RendererConfig.MetaString("mobilegl_use_angle_title"))
|
toggleable("MOBILEGL_USE_ANGLE", "1", false, RendererConfig.MetaString("mobilegl_use_angle_title"))
|
||||||
},
|
},
|
||||||
minMCVer = null,
|
minMCVer = null,
|
||||||
|
|||||||
@@ -9,5 +9,6 @@
|
|||||||
<string name="mobilegl_avoid_sampler_mipmap_min_filter_title">Avoid sampler mipmap minification filters</string>
|
<string name="mobilegl_avoid_sampler_mipmap_min_filter_title">Avoid sampler mipmap minification filters</string>
|
||||||
<string name="mobilegl_coherent_as_flush_title">Treat explicit-flush persistent maps as coherent</string>
|
<string name="mobilegl_coherent_as_flush_title">Treat explicit-flush persistent maps as coherent</string>
|
||||||
<string name="mobilegl_use_angle_title">Use ANGLE GLES libraries</string>
|
<string name="mobilegl_use_angle_title">Use ANGLE GLES libraries</string>
|
||||||
|
<string name="mobilegl_relaxed_semantics_title">Relaxed GL semantics (legacy app leniencies)</string>
|
||||||
<string name="mobilegl_default_backend" translatable="false">DirectGLES</string>
|
<string name="mobilegl_default_backend" translatable="false">DirectGLES</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user