[Fix, Test] (MG_Backend, MG_Impl, MG_State): advertise the uniform-block and stencil-texturing strings a 4.0 context hides behind, forward DEPTH_STENCIL_TEXTURE_MODE to both backends, and stop a reserved transform feedback name passing for an object

This commit is contained in:
2026-08-12 15:12:01 -04:00
parent a6e029734b
commit 6b6623ae72
11 changed files with 221 additions and 6 deletions
@@ -940,6 +940,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
// picks a whole different shader for draw_buffers without
// explicit_attrib_location. DirectVulkan advertises both.
E_GL_ARB_explicit_attrib_location, E_GL_ARB_texture_multisample, E_GL_ARB_shader_image_size,
// Core since GL 3.1 and implemented for every version advertised here. The string
// matters because applications gate the ENTRY POINTS on it rather than on the
// version: a caller that finds the extension missing never resolves
// glGetUniformBlockIndex / glUniformBlockBinding, and one that then uses uniform
// blocks anyway calls through a null pointer.
E_GL_ARB_uniform_buffer_object,
// Sampling the stencil aspect through DEPTH_STENCIL_TEXTURE_MODE. Core from 4.3,
// so on a 4.0 context the string is the only way to reach it. The host ES driver
// has had the same texture parameter since ES 3.1, which every device MobileGL
// runs on provides.
E_GL_ARB_stencil_texturing,
// Advertised with GL_NUM_PROGRAM_BINARY_FORMATS = 0, which the
// extension explicitly permits. It is also the only thing that
// exposes glProgramParameteri before GL 4.1.
@@ -3315,6 +3315,29 @@ namespace MobileGL::MG_Backend::DirectGLES {
MGLOG_D("%s(%s:%d) ES error %s", func, file, line, MG_Util::ConvertGLEnumToString(err).c_str());
});
}
// GL_DEPTH_STENCIL_TEXTURE_MODE (GL_ARB_stencil_texturing / ES 3.1 core): which aspect
// of a packed depth/stencil image a sampler reads. Until this was forwarded the
// frontend kept the mode as a pure shadow - glGetTexParameter answered it, sampling
// ignored it - so a usampler2D bound to a D24S8 texture in STENCIL_INDEX mode read the
// depth aspect. Texture state rather than sampler state, so multisample targets take
// it too (ES 3.1 8.10 lists it among the three pnames they accept). It is only sent
// when it has moved, which for the overwhelming majority of textures is never.
const Bool supportsStencilTextureMode =
g_GLESCapabilities.GLESVersion.Major > 3 ||
(g_GLESCapabilities.GLESVersion.Major == 3 && g_GLESCapabilities.GLESVersion.Minor >= 1);
if (supportsStencilTextureMode) {
const GLenum depthStencilTextureMode = stateTextureObject->GetDepthStencilTextureMode();
if (m_cacheDepthStencilTextureMode != depthStencilTextureMode) {
g_GLESFuncs.glTexParameteri(target, GL_DEPTH_STENCIL_TEXTURE_MODE,
static_cast<GLint>(depthStencilTextureMode));
m_cacheDepthStencilTextureMode = depthStencilTextureMode;
DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__, func = __func__](GLenum err) {
MGLOG_D("%s(%s:%d) ES error %s", func, file, line,
MG_Util::ConvertGLEnumToString(err).c_str());
});
}
}
}
void ActivateTextureUnit(Uint unit) {
@@ -699,6 +699,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
FloatVec4 m_cacheBorderColor = {0.0f, 0.0f, 0.0f, 0.0f};
Vec4<TextureSwizzleParam> m_cacheSwizzleParams = {TextureSwizzleParam::Red, TextureSwizzleParam::Green,
TextureSwizzleParam::Blue, TextureSwizzleParam::Alpha};
// GL_DEPTH_STENCIL_TEXTURE_MODE. GL_DEPTH_COMPONENT is the GL and ES default, so a
// texture that never asks for the stencil aspect never emits the call. The
// depth/stencil readback and replicate-blit emulations also write this parameter
// raw, but only ever on their own scratch textures (never on an application
// texture), so they cannot desynchronise this cache.
GLenum m_cacheDepthStencilTextureMode = GL_DEPTH_COMPONENT;
Uint16 m_syncedSamplerVersion = 0;
Uint16 m_syncedTextureParamsVersion = 0;
};
@@ -517,6 +517,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
E_GL_ARB_gpu_shader_int64, E_GL_KHR_debug, E_GL_ARB_gpu_shader5, E_GL_ARB_multi_bind,
E_GL_ARB_shading_language_420pack, E_GL_ARB_vertex_attrib_binding, E_GL_ARB_shader_image_size,
E_GL_ARB_explicit_attrib_location,
// Core since GL 3.1 and implemented for every version advertised here. The string
// matters because applications gate the ENTRY POINTS on it rather than on the
// version: a caller that finds the extension missing never resolves
// glGetUniformBlockIndex / glUniformBlockBinding, and one that then uses uniform
// blocks anyway calls through a null pointer.
E_GL_ARB_uniform_buffer_object,
// Sampling the stencil aspect through DEPTH_STENCIL_TEXTURE_MODE. Core from 4.3,
// so on a 4.0 context the string is the only way to reach it.
E_GL_ARB_stencil_texturing,
// Advertised with GL_NUM_PROGRAM_BINARY_FORMATS = 0, which the
// extension explicitly permits. It is also the only thing that
// exposes glProgramParameteri before GL 4.1.
@@ -950,7 +950,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(texture.GetFormat());
const VkComponentMapping sampledComponents = ResolveSampledViewComponents(texture, formatInfo);
const VkImageAspectFlags sampledAspect = ResolveSampledImageViewAspectMask(resource->aspect);
const VkImageAspectFlags sampledAspect =
ResolveSampledImageViewAspectMask(resource->aspect, texture.GetDepthStencilTextureMode());
perMipSampledView = CreateImageView(resource->image, resource->format, sampledAspect, resource->viewType,
mipLevel, 1, 0, resource->arrayLayers, &sampledComponents);
if (perMipSampledView == VK_NULL_HANDLE) {
@@ -2238,7 +2239,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (resource.fullView == VK_NULL_HANDLE) {
return false;
}
const VkImageAspectFlags sampledAspect = ResolveSampledImageViewAspectMask(resource.aspect);
const VkImageAspectFlags sampledAspect =
ResolveSampledImageViewAspectMask(resource.aspect, texture.GetDepthStencilTextureMode());
resource.sampledView = CreateImageView(resource.image, resource.format, sampledAspect, resource.viewType,
baseMipLevel, levelCount, 0, resource.arrayLayers, &sampledComponents);
if (resource.sampledView == VK_NULL_HANDLE) {
@@ -2860,10 +2862,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
}
VkImageAspectFlags VkTextureManager::ResolveSampledImageViewAspectMask(VkImageAspectFlags imageAspect) {
VkImageAspectFlags VkTextureManager::ResolveSampledImageViewAspectMask(VkImageAspectFlags imageAspect,
GLenum depthStencilTextureMode) {
if ((imageAspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0) {
return VK_IMAGE_ASPECT_COLOR_BIT;
}
// A sampled view of a combined depth/stencil image may name exactly one aspect
// (VUID-VkDescriptorImageInfo-imageView-01976), and GL_DEPTH_STENCIL_TEXTURE_MODE is
// what picks it - the whole content of GL_ARB_stencil_texturing. Depth stays the
// default, so nothing that never sets the mode changes shape. The texture's params
// version moves with the mode, which is what makes the cached views be rebuilt.
if (depthStencilTextureMode == GL_STENCIL_INDEX && (imageAspect & VK_IMAGE_ASPECT_STENCIL_BIT) != 0) {
return VK_IMAGE_ASPECT_STENCIL_BIT;
}
if ((imageAspect & VK_IMAGE_ASPECT_DEPTH_BIT) != 0) {
return VK_IMAGE_ASPECT_DEPTH_BIT;
}
@@ -379,7 +379,11 @@ public:
// true - a false positive merely ends the render pass, a false negative would skip a barrier.
Bool NeedsStorageImagePreparation(MG_State::GLState::ITextureObject& texture) const;
static VkImageAspectFlags ResolveSampledImageViewAspectMask(VkImageAspectFlags imageAspect);
// `depthStencilTextureMode` is the texture's GL_DEPTH_STENCIL_TEXTURE_MODE; it only decides
// anything for an image that carries both aspects. Defaulted so the call sites that have no
// texture in hand keep the depth-aspect answer they have always given.
static VkImageAspectFlags ResolveSampledImageViewAspectMask(VkImageAspectFlags imageAspect,
GLenum depthStencilTextureMode = GL_DEPTH_COMPONENT);
static VkFormat ResolveSampledImageViewFormat(VkFormat imageFormat, SamplerNumericDomain numericDomain);
static Bool AreSampledImageViewFormatsCompatible(VkFormat imageFormat, VkFormat viewFormat);
static Bool AreStorageImageViewFormatsCompatible(VkFormat imageFormat, VkFormat viewFormat);
@@ -1259,7 +1259,15 @@ namespace MobileGL::MG_Impl::GLImpl {
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName, "instancecount must be non-negative."));
return;
}
if (!MG_State::pGLContext->ValidateTransformFeedbackName(id)) {
// "id is not the name of a transform feedback object" has to mean the same thing here
// as it does to glIsTransformFeedback, and the two predicates are not interchangeable:
// a name glGenTransformFeedbacks handed out is only reserved until it is first bound,
// and only the bind turns it into an object (GL 4.6 core 13.2.1). ValidateTransformFeedbackName
// answers the reservation question - the right one for glBindTransformFeedback, which is
// what turns a reserved name into an object - so using it here let a generated-but-unbound
// name through to the completed-span check below and raised INVALID_OPERATION where the
// spec asks for INVALID_VALUE. Name 0 is the default object and always drawable.
if (id != 0 && !MG_State::pGLContext->IsTransformFeedbackObject(id)) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
@@ -117,7 +117,15 @@ namespace MobileGL::MG_State::GLState {
void SetFixedSampleLocations(Bool fixedSampleLocations) override;
Uint64 GetLifetimeId() const override;
GLenum GetDepthStencilTextureMode() const override { return m_depthStencilTextureMode; }
void SetDepthStencilTextureMode(GLenum mode) override { m_depthStencilTextureMode = mode; }
// Bumps the params version like every other backend-visible texture parameter: the mode
// decides which ASPECT of a packed depth/stencil image a sampler reads, which DirectGLES
// forwards as a texture parameter and DirectVulkan bakes into the sampled image view. A
// silent write here would leave both backends showing the aspect they last built.
void SetDepthStencilTextureMode(GLenum mode) override {
if (m_depthStencilTextureMode == mode) return;
m_depthStencilTextureMode = mode;
++m_textureParamsVersion;
}
protected:
static Uint64 AllocateLifetimeId();
+57
View File
@@ -198,6 +198,38 @@ TEST(DirectGLESSanity, AdvertisesDepthTextureForGlmarkShadowScenes) {
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_depth_texture), extensions.end());
}
// Two strings that name capabilities MobileGL has always had, and that were missing from the
// advertised list for as long as it existed.
//
// GL_ARB_uniform_buffer_object is the one with teeth: applications gate the ENTRY POINTS on the
// string rather than on the context version. KHR-GL4x.transform_feedback.draw_xfb_instanced_test
// resolves glGetUniformBlockIndex / glUniformBlockBinding only inside `if (is_arb_ubo)`, then
// calls them unconditionally because the context claims >= 4.2 - so a missing string turned into
// a call through a null pointer and took the whole process down with SIGSEGV. Withdrawing it
// again would restore that crash on both backends.
//
// GL_ARB_stencil_texturing is what makes DEPTH_STENCIL_TEXTURE_MODE = GL_STENCIL_INDEX reachable
// at all before GL 4.3, which is the whole of KHR-GL3x.packed_depth_stencil.stencil_texturing.
TEST(DirectGLESSanity, AdvertisesUniformBufferObjectAndStencilTexturing) {
MobileGL::MG_Backend::DirectGLES::BackendObject_DirectGLES backend;
const auto& extensions = backend.GetRendererInfo().RendererGLInfo.Extensions;
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_uniform_buffer_object),
extensions.end());
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_stencil_texturing),
extensions.end());
}
TEST(DirectVulkanSanity, AdvertisesUniformBufferObjectAndStencilTexturing) {
MobileGL::MG_Backend::DirectVulkan::BackendObject_DirectVulkan backend;
const auto& extensions = backend.GetRendererInfo().RendererGLInfo.Extensions;
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_uniform_buffer_object),
extensions.end());
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_stencil_texturing),
extensions.end());
}
// Voxy only ever needed the extensions, which stay advertised whatever the version is; the version
// assertion just pins what the backend really reports, now that V_OpenGL40 is in the list.
TEST(DirectGLESSanity, AdvertisesVoxyRequiredRenderingExtensions) {
@@ -435,6 +467,31 @@ TEST(DirectVulkanSanity, AdvertisesTextureStorageForDirectStateAccess) {
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_texture_storage), extensions.end());
}
// A sampled view of a combined depth/stencil image may name exactly one aspect, and
// GL_DEPTH_STENCIL_TEXTURE_MODE picks which - the whole of GL_ARB_stencil_texturing on this
// backend. Depth remains the answer for everything that does not ask for stencil, including
// depth-only images asked for the stencil aspect they do not have.
TEST(DirectVulkanSanity, SampledViewAspectFollowsDepthStencilTextureMode) {
using MobileGL::MG_Backend::DirectVulkan::VkTextureManager;
constexpr VkImageAspectFlags kPacked = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
EXPECT_EQ(VkTextureManager::ResolveSampledImageViewAspectMask(kPacked, GL_DEPTH_COMPONENT),
static_cast<VkImageAspectFlags>(VK_IMAGE_ASPECT_DEPTH_BIT));
EXPECT_EQ(VkTextureManager::ResolveSampledImageViewAspectMask(kPacked, GL_STENCIL_INDEX),
static_cast<VkImageAspectFlags>(VK_IMAGE_ASPECT_STENCIL_BIT));
// The default argument is the pre-existing behaviour, for the call sites with no texture.
EXPECT_EQ(VkTextureManager::ResolveSampledImageViewAspectMask(kPacked),
static_cast<VkImageAspectFlags>(VK_IMAGE_ASPECT_DEPTH_BIT));
// Single-aspect images ignore the mode: there is only one aspect to name.
EXPECT_EQ(VkTextureManager::ResolveSampledImageViewAspectMask(VK_IMAGE_ASPECT_DEPTH_BIT, GL_STENCIL_INDEX),
static_cast<VkImageAspectFlags>(VK_IMAGE_ASPECT_DEPTH_BIT));
EXPECT_EQ(VkTextureManager::ResolveSampledImageViewAspectMask(VK_IMAGE_ASPECT_STENCIL_BIT, GL_DEPTH_COMPONENT),
static_cast<VkImageAspectFlags>(VK_IMAGE_ASPECT_STENCIL_BIT));
EXPECT_EQ(VkTextureManager::ResolveSampledImageViewAspectMask(VK_IMAGE_ASPECT_COLOR_BIT, GL_STENCIL_INDEX),
static_cast<VkImageAspectFlags>(VK_IMAGE_ASPECT_COLOR_BIT));
}
TEST(DirectVulkanSanity, RenderPassExtentUsesSwapchainSizeOnlyForDefaultFramebuffer) {
using MobileGL::MG_Backend::DirectVulkan::ResolveRenderPassFramebufferExtent;
@@ -323,6 +323,47 @@ namespace {
static_cast<void>(parameterBuffer);
}
// A transform feedback name has two different truths and glDrawTransformFeedback used to ask
// for the wrong one. glGenTransformFeedbacks only RESERVES a name; the first
// glBindTransformFeedback is what creates the object (GL 4.6 core 13.2.1), and
// glIsTransformFeedback reports exactly that distinction. glDrawTransformFeedback's
// "id is not the name of a transform feedback object" INVALID_VALUE has to agree with
// glIsTransformFeedback, or a caller that picks an unused name the way
// KHR-GL4x.transform_feedback.api_errors_test does - increment until glIsTransformFeedback
// says false - gets a name the draw then accepts, and the draw falls through to a different
// error entirely (INVALID_OPERATION, "glEndTransformFeedback has never been called").
//
// The draw path itself needs a backend and a linked program before it reaches the name, which
// this GPU-free suite has neither of, so what is pinned here is the predicate pair the fix
// turns on: the two must not collapse back into one.
TEST_F(NegativeApiErrorsTest, ReservedTransformFeedbackNameIsNotYetAnObject) {
GLuint name = 0;
GenTransformFeedbacks(1, &name);
ASSERT_NE(name, 0u);
DrainErrors();
// Reserved, so it is a legal argument to glBindTransformFeedback...
EXPECT_TRUE(MG_State::pGLContext->ValidateTransformFeedbackName(name));
// ...but not an object yet, which is what a draw must key off.
EXPECT_FALSE(MG_State::pGLContext->IsTransformFeedbackObject(name));
EXPECT_EQ(IsTransformFeedback(name), GL_FALSE);
BindTransformFeedback(GL_TRANSFORM_FEEDBACK, name);
EXPECT_EQ(GetError(), GL_NO_ERROR);
EXPECT_TRUE(MG_State::pGLContext->ValidateTransformFeedbackName(name));
EXPECT_TRUE(MG_State::pGLContext->IsTransformFeedbackObject(name));
EXPECT_EQ(IsTransformFeedback(name), GL_TRUE);
// The default object is never "an object" by this predicate and is always drawable, so
// the draw path has to special-case it rather than reuse the answer directly.
EXPECT_FALSE(MG_State::pGLContext->IsTransformFeedbackObject(0));
EXPECT_TRUE(MG_State::pGLContext->ValidateTransformFeedbackName(0));
BindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);
DrainErrors();
}
TEST_F(NegativeApiErrorsTest, TexStorage3DRejectsCompressedFormatsOnTexture3D) {
GLuint texture = 0;
GenTextures(1, &texture);
+37
View File
@@ -1651,6 +1651,43 @@ TEST_F(TextureTest, AnUncompressedRespecificationClearsTheCompressedTag) {
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
// GL_DEPTH_STENCIL_TEXTURE_MODE used to be a pure frontend shadow: stored, answered by
// glGetTexParameter, and never shown to a backend. Sampling therefore always read the depth
// aspect however the mode was set, which is the whole of
// KHR-GL3x.packed_depth_stencil.stencil_texturing. Both backends pick the aspect up through the
// texture-params version - DirectGLES re-emits glTexParameteri when it moves, DirectVulkan
// rebuilds the sampled image view - so the version bump is the load-bearing part, and a
// no-op write must not spend one (every bump costs DirectVulkan a view recreation).
TEST_F(TextureTest, DepthStencilTextureModeIsBackendVisibleThroughTheParamsVersion) {
GLuint texture = 0;
MG_Impl::GLImpl::GenTextures(1, &texture);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
MG_Impl::GLImpl::TexStorage2D(GL_TEXTURE_2D, 1, GL_DEPTH24_STENCIL8, 8, 8);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
const auto textureObject = MG_State::pGLContext->GetTextureObject(texture);
ASSERT_NE(textureObject, nullptr);
EXPECT_EQ(textureObject->GetDepthStencilTextureMode(), static_cast<GLenum>(GL_DEPTH_COMPONENT));
const Uint16 initialVersion = textureObject->GetTextureParamsVersion();
MG_Impl::GLImpl::TexParameteri(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_EQ(textureObject->GetDepthStencilTextureMode(), static_cast<GLenum>(GL_STENCIL_INDEX));
EXPECT_NE(textureObject->GetTextureParamsVersion(), initialVersion);
// Re-writing the value already in force is not a change and must not invalidate anything.
const Uint16 settledVersion = textureObject->GetTextureParamsVersion();
MG_Impl::GLImpl::TexParameteri(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_EQ(textureObject->GetTextureParamsVersion(), settledVersion);
// ...and going back to the depth aspect is a change again.
MG_Impl::GLImpl::TexParameteri(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_DEPTH_COMPONENT);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_EQ(textureObject->GetDepthStencilTextureMode(), static_cast<GLenum>(GL_DEPTH_COMPONENT));
EXPECT_NE(textureObject->GetTextureParamsVersion(), settledVersion);
}
namespace {
// 8x8 RGTC1: 2x2 blocks of 8 bytes, so the stored image is 32 bytes and one block row is 16.
constexpr GLsizei kRgtc1Size8x8 = 32;