[Merge] (GLImpl, DirectGLES, DirectVulkan): take the extension advertisements under the DSA completion

This commit is contained in:
2026-08-22 22:42:47 -04:00
13 changed files with 353 additions and 44 deletions
@@ -753,7 +753,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
.TargetGLSLVersion = {4, 6, 0}, // Target Shading Language Version .TargetGLSLVersion = {4, 6, 0}, // Target Shading Language Version
// Baseline advertisement (no runtime capabilities yet); reconciled once // Baseline advertisement (no runtime capabilities yet); reconciled once
// the ES capabilities exist, see UpdateAdvertisedCapabilityExtensions. // the ES capabilities exist, see UpdateAdvertisedCapabilityExtensions.
.Extensions = BuildAdvertisedExtensions(false, false, false, false, false), .Extensions = BuildAdvertisedExtensions(false, false, false, false, false, false),
.IsCompatibilityProfile = false // Is Compatibility Profile .IsCompatibilityProfile = false // Is Compatibility Profile
}, },
.StaticBackendCapability = {.AllowVSOnlyPrograms = false} // Backend Capability .StaticBackendCapability = {.AllowVSOnlyPrograms = false} // Backend Capability
@@ -778,7 +778,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
AreTimerQueriesSupported(), capabilities.SupportsTextureFilterAnisotropy, AreTimerQueriesSupported(), capabilities.SupportsTextureFilterAnisotropy,
capabilities.SupportsDrawIndirect, capabilities.SupportsDrawIndirect,
capabilities.SupportsDrawIndirect && capabilities.SupportsBaseInstance, capabilities.SupportsDrawIndirect && capabilities.SupportsBaseInstance,
capabilities.SupportsTextureView); capabilities.SupportsTextureView, capabilities.SupportsTextureCubeMapArray);
} }
} // namespace } // namespace
@@ -992,7 +992,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported, Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported,
Bool drawIndirectSupported, Bool drawIndirectSupported,
Bool nonZeroIndirectBaseInstanceSupported, Bool nonZeroIndirectBaseInstanceSupported,
Bool textureViewSupported) { Bool textureViewSupported, Bool cubeMapArraySupported) {
Vector<GLExtension> extensions = { Vector<GLExtension> extensions = {
// The version tokens have to reach the version the backend actually claims: // The version tokens have to reach the version the backend actually claims:
// TargetGLVersion is {4,3,0}, and a list that stopped at OpenGL40 told an // TargetGLVersion is {4,3,0}, and a list that stopped at OpenGL40 told an
@@ -1030,6 +1030,22 @@ namespace MobileGL::MG_Backend::DirectGLES {
// was simply never emitted, which left KHR-GL4*.draw_elements_base_vertex_tests // was simply never emitted, which left KHR-GL4*.draw_elements_base_vertex_tests
// NotSupported on a feature that works. // NotSupported on a feature that works.
E_GL_ARB_draw_elements_base_vertex, E_GL_ARB_draw_elements_base_vertex,
// The whole sync-object family is real and core since 3.2: glFenceSync, glIsSync,
// glDeleteSync, glClientWaitSync, glWaitSync and glGetSynciv all live in GLImpl over a
// backend fence (a host GLsync here, a VkFence on DirectVulkan), and glGetInteger64v
// answers GL_MAX_SERVER_WAIT_TIMEOUT. The string matters for the same reason
// ARB_uniform_buffer_object's does: LWJGL builds GLCapabilities from the extension
// list, and a caller that finds GL_ARB_sync missing never resolves the entry points -
// then calls through null if it uses fences anyway. Nothing in the CTS gates on this
// string, so it is advertised on the strength of the implementation, not a test unlock.
E_GL_ARB_sync,
// Atomic counters, core since 4.2. glGetActiveAtomicCounterBufferiv and the whole
// GL_ATOMIC_COUNTER_BUFFER_* query family are real in GLImpl, and SyncAtomicCounterBuffers
// re-issues the counter buffer as an SSBO binding in the range reserved at the top of
// the ES driver's shader-storage points, so a counter dispatch reads and writes the
// buffer the application bound. DirectVulkan reaches the same place through its own
// descriptor resolution, so the string is symmetric.
E_GL_ARB_shader_atomic_counters,
// glVertexAttribDivisor, core since 3.3 and real on both backends. Applications // glVertexAttribDivisor, core since 3.3 and real on both backends. Applications
// (Better Clouds' GLCompat among them) accept the extension string as an // (Better Clouds' GLCompat among them) accept the extension string as an
// ALTERNATIVE to a 3.3 context when deciding whether instanced rendering is // ALTERNATIVE to a 3.3 context when deciding whether instanced rendering is
@@ -1039,6 +1055,46 @@ namespace MobileGL::MG_Backend::DirectGLES {
// object-label table are MobileGL's own state, not the host driver's - so it is as // object-label table are MobileGL's own state, not the host driver's - so it is as
// available here as it is on DirectVulkan, which has advertised it all along. // available here as it is on DirectVulkan, which has advertised it all along.
E_GL_KHR_debug, E_GL_KHR_debug,
// Core GL 3.0-4.3 plumbing that has been real here for as long as the backend has
// existed, and that was simply never named. None of these unlocks a single CTS case -
// the conformance suite reaches all of them through the version - so they are
// advertised for the OTHER consumer of this list: LWJGL builds GLCapabilities from the
// string set, and an application that gates its ENTRY POINTS on the string rather than
// on the version never resolves them and then calls through null. Each is backed by
// the entry points named beside it.
//
// glBindVertexArray / glGenVertexArrays / glDeleteVertexArrays / glIsVertexArray.
E_GL_ARB_vertex_array_object,
// The 14 glSamplerParameter* / glGetSamplerParameter* entry points, including the
// integer-valued Iiv/Iuiv forms.
E_GL_ARB_sampler_objects,
// glMapBufferRange + glFlushMappedBufferRange, which ARB_buffer_storage's persistent
// maps are already built on top of.
E_GL_ARB_map_buffer_range,
// glCopyBufferSubData plus the GL_COPY_READ_BUFFER / GL_COPY_WRITE_BUFFER targets.
E_GL_ARB_copy_buffer,
// glCopyImageSubData, wired to a real backend hook on both backends.
E_GL_ARB_copy_image,
// GL_TEXTURE_SWIZZLE_{R,G,B,A,RGBA}, which this backend syncs through to the ES
// driver's identical parameters.
E_GL_ARB_texture_swizzle,
// GL_INT_2_10_10_10_REV / GL_UNSIGNED_INT_2_10_10_10_REV on glVertexAttribPointer plus
// the eight glVertexAttribP* entry points.
E_GL_ARB_vertex_type_2_10_10_10_rev,
// The R/RG internal formats. Named separately from the float ones because an
// application may check either.
E_GL_ARB_texture_rg,
// GL_DEPTH_COMPONENT32F and GL_DEPTH32F_STENCIL8.
E_GL_ARB_depth_buffer_float,
// The floating-point colour formats. Unlike the rest of this block this string DOES
// gate CTS cases - KHR-GL4*.internalformat.texture2d.*{16f,32f} is keyed on it with no
// core-version fallback, so eight cases per version list were NotSupported on formats
// the backend has always had.
E_GL_ARB_texture_float,
// glViewportArrayv / glViewportIndexedf{,v} / glScissorArrayv / glScissorIndexed{,v} /
// glDepthRangeArrayv / glDepthRangeIndexed / glGetFloati_v / glGetDoublei_v, over the
// 16 viewports GL_MAX_VIEWPORTS reports and the per-viewport routing emulation.
E_GL_ARB_viewport_array,
// Advertised with GL_NUM_PROGRAM_BINARY_FORMATS = 0, which the // Advertised with GL_NUM_PROGRAM_BINARY_FORMATS = 0, which the
// extension explicitly permits. It is also the only thing that // extension explicitly permits. It is also the only thing that
// exposes glProgramParameteri before GL 4.1. // exposes glProgramParameteri before GL 4.1.
@@ -1087,6 +1143,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (timerQueriesSupported && !MG_Config::Features.DisableTimerQuery) { if (timerQueriesSupported && !MG_Config::Features.DisableTimerQuery) {
extensions.push_back(E_GL_ARB_timer_query); extensions.push_back(E_GL_ARB_timer_query);
} }
// Cube map arrays are core from GL 4.0 and from ES 3.2, but on a pre-ES-3.2 driver without
// EXT/OES_texture_cube_map_array there is nothing underneath: the texture gets no storage
// and a samplerCubeArray shader does not even compile, which is exactly what the POST
// reports. So the string follows the host capability rather than the version.
//
// Named for the application's benefit rather than the suite's: measured on Adreno 830,
// KHR-GL43.texture_gather.plain-gather-*-cube-array already passed without the string, so
// this unlocks no conformance case. It is advertised because the feature is real and
// because an application that feature-detects cube map arrays off the string (rather than
// off the 4.0 version) would otherwise decline a path this backend serves.
if (cubeMapArraySupported) {
extensions.push_back(E_GL_ARB_texture_cube_map_array);
}
// Only advertised when the host ES driver has EXT/OES_texture_view. ES has no core // Only advertised when the host ES driver has EXT/OES_texture_view. ES has no core
// texture views at any version and no honest emulation exists: a view is a SECOND NAME // texture views at any version and no honest emulation exists: a view is a SECOND NAME
// over the SAME storage, so that writes through either are visible through the other and // over the SAME storage, so that writes through either are visible through the other and
@@ -83,7 +83,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported, Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported,
Bool drawIndirectSupported, Bool drawIndirectSupported,
Bool nonZeroIndirectBaseInstanceSupported, Bool nonZeroIndirectBaseInstanceSupported,
Bool textureViewSupported); Bool textureViewSupported, Bool cubeMapArraySupported);
// Format: <OpenGL ES Renderer>, OpenGL ES <Major>.<Minor> — the exact string an // Format: <OpenGL ES Renderer>, OpenGL ES <Major>.<Minor> — the exact string an
// initialized backend returns from GetBackendAPIVersionString (and that ends up // initialized backend returns from GetBackendAPIVersionString (and that ends up
@@ -504,7 +504,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
.TargetGLSLVersion = {4, 6, 0}, .TargetGLSLVersion = {4, 6, 0},
// Baseline advertisement (no runtime-gated capabilities); a live // Baseline advertisement (no runtime-gated capabilities); a live
// backend reconciles its copy in UpdateAdvertisedExtensions. // backend reconciles its copy in UpdateAdvertisedExtensions.
.Extensions = BuildAdvertisedExtensions(false, false, false, false), .Extensions = BuildAdvertisedExtensions(false, false, false, false, false),
.IsCompatibilityProfile = false}, .IsCompatibilityProfile = false},
.StaticBackendCapability = {.AllowVSOnlyPrograms = false}}; .StaticBackendCapability = {.AllowVSOnlyPrograms = false}};
return rendererInfo; return rendererInfo;
@@ -512,7 +512,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported, Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported,
Bool anisotropicFilteringSupported, Bool anisotropicFilteringSupported,
Bool nonZeroIndirectBaseInstanceSupported) { Bool nonZeroIndirectBaseInstanceSupported,
Bool cubeMapArraySupported) {
Vector<GLExtension> extensions = { Vector<GLExtension> extensions = {
// The version tokens have to reach the version the backend actually claims: // The version tokens have to reach the version the backend actually claims:
// TargetGLVersion is {4,3,0}, and a list that stopped at OpenGL40 told an // TargetGLVersion is {4,3,0}, and a list that stopped at OpenGL40 told an
@@ -550,11 +551,67 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// was simply never emitted, which left KHR-GL4*.draw_elements_base_vertex_tests // was simply never emitted, which left KHR-GL4*.draw_elements_base_vertex_tests
// NotSupported on a feature that works. // NotSupported on a feature that works.
E_GL_ARB_draw_elements_base_vertex, E_GL_ARB_draw_elements_base_vertex,
// The whole sync-object family is real and core since 3.2: glFenceSync, glIsSync,
// glDeleteSync, glClientWaitSync, glWaitSync and glGetSynciv all live in GLImpl over a
// backend fence (a VkFence here, an EGLSync/GLsync on DirectGLES), and glGetInteger64v
// answers GL_MAX_SERVER_WAIT_TIMEOUT. The string matters for the same reason
// ARB_uniform_buffer_object's does: LWJGL builds GLCapabilities from the extension
// list, and a caller that finds GL_ARB_sync missing never resolves the entry points -
// then calls through null if it uses fences anyway. Nothing in the CTS gates on this
// string, so it is advertised on the strength of the implementation, not a test unlock.
E_GL_ARB_sync,
// Atomic counters, core since 4.2. glGetActiveAtomicCounterBufferiv and the whole
// GL_ATOMIC_COUNTER_BUFFER_* query family are real in GLImpl, and the counter buffer
// now reaches the shader on BOTH backends - Magma resolves the lowered
// gl_AtomicCounterBlock_<N> from the atomic-counter binding points rather than the
// shader-storage ones (see ResolveStorageBufferDescriptor). Withheld here until that
// landed, because the counter silently read whatever was bound as SSBO N instead.
E_GL_ARB_shader_atomic_counters,
// glVertexAttribDivisor, core since 3.3 and real on both backends. Applications // glVertexAttribDivisor, core since 3.3 and real on both backends. Applications
// (Better Clouds' GLCompat among them) accept the extension string as an // (Better Clouds' GLCompat among them) accept the extension string as an
// ALTERNATIVE to a 3.3 context when deciding whether instanced rendering is // ALTERNATIVE to a 3.3 context when deciding whether instanced rendering is
// available, so withholding it makes MobileGL look less capable than it is. // available, so withholding it makes MobileGL look less capable than it is.
E_GL_ARB_instanced_arrays, E_GL_ARB_instanced_arrays,
// Core GL 3.0-4.3 plumbing that has been real here for as long as the backend has
// existed, and that was simply never named. None of these unlocks a single CTS case -
// the conformance suite reaches all of them through the version - so they are
// advertised for the OTHER consumer of this list: LWJGL builds GLCapabilities from the
// string set, and an application that gates its ENTRY POINTS on the string rather than
// on the version never resolves them and then calls through null. Each is backed by
// the entry points named beside it. Kept identical to the DirectGLES block so the two
// backends do not disagree about what MobileGL is.
//
// glBindVertexArray / glGenVertexArrays / glDeleteVertexArrays / glIsVertexArray.
E_GL_ARB_vertex_array_object,
// The 14 glSamplerParameter* / glGetSamplerParameter* entry points, including the
// integer-valued Iiv/Iuiv forms.
E_GL_ARB_sampler_objects,
// glMapBufferRange + glFlushMappedBufferRange, which ARB_buffer_storage's persistent
// maps are already built on top of.
E_GL_ARB_map_buffer_range,
// glCopyBufferSubData plus the GL_COPY_READ_BUFFER / GL_COPY_WRITE_BUFFER targets.
E_GL_ARB_copy_buffer,
// glCopyImageSubData, wired to a real backend hook on both backends.
E_GL_ARB_copy_image,
// GL_TEXTURE_SWIZZLE_{R,G,B,A,RGBA}, which map onto a VkImageView's component swizzle.
E_GL_ARB_texture_swizzle,
// GL_INT_2_10_10_10_REV / GL_UNSIGNED_INT_2_10_10_10_REV on glVertexAttribPointer plus
// the eight glVertexAttribP* entry points.
E_GL_ARB_vertex_type_2_10_10_10_rev,
// The R/RG internal formats. Named separately from the float ones because an
// application may check either.
E_GL_ARB_texture_rg,
// GL_DEPTH_COMPONENT32F and GL_DEPTH32F_STENCIL8.
E_GL_ARB_depth_buffer_float,
// The floating-point colour formats. Unlike the rest of this block this string DOES
// gate CTS cases - KHR-GL4*.internalformat.texture2d.*{16f,32f} is keyed on it with no
// core-version fallback, so eight cases per version list were NotSupported on formats
// the backend has always had.
E_GL_ARB_texture_float,
// glViewportArrayv / glViewportIndexedf{,v} / glScissorArrayv / glScissorIndexed{,v} /
// glDepthRangeArrayv / glDepthRangeIndexed / glGetFloati_v / glGetDoublei_v, over the
// 16 viewports GL_MAX_VIEWPORTS reports.
E_GL_ARB_viewport_array,
// Advertised with GL_NUM_PROGRAM_BINARY_FORMATS = 0, which the // Advertised with GL_NUM_PROGRAM_BINARY_FORMATS = 0, which the
// extension explicitly permits. It is also the only thing that // extension explicitly permits. It is also the only thing that
// exposes glProgramParameteri before GL 4.1. // exposes glProgramParameteri before GL 4.1.
@@ -607,6 +664,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
extensions.push_back(E_GL_EXT_texture_filter_anisotropic); extensions.push_back(E_GL_EXT_texture_filter_anisotropic);
extensions.push_back(E_GL_ARB_texture_filter_anisotropic); extensions.push_back(E_GL_ARB_texture_filter_anisotropic);
} }
// A cube map array is a 6n-layer VkImage viewed as VK_IMAGE_VIEW_TYPE_CUBE_ARRAY, and that
// view type cannot be created without the imageCubeArray device feature - so the string
// follows the feature, not the version, exactly as the per-layer attachment bit does.
//
// Named for the application's benefit rather than the suite's: measured on Adreno 830,
// KHR-GL43.texture_gather.plain-gather-*-cube-array already passed without the string, so
// this unlocks no conformance case. It is advertised because the feature is real and
// because an application that feature-detects cube map arrays off the string (rather than
// off the 4.0 version) would otherwise decline a path this backend serves.
if (cubeMapArraySupported) {
extensions.push_back(E_GL_ARB_texture_cube_map_array);
}
return extensions; return extensions;
} }
@@ -737,7 +806,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_rendererInfo.RendererGLInfo.Extensions = BuildAdvertisedExtensions( m_rendererInfo.RendererGLInfo.Extensions = BuildAdvertisedExtensions(
subgroupSupportAdvertised, pVulkanRenderer && pVulkanRenderer->IsTimerQuerySupported(), subgroupSupportAdvertised, pVulkanRenderer && pVulkanRenderer->IsTimerQuerySupported(),
pVulkanRenderer && pVulkanRenderer->IsSamplerAnisotropySupported(), pVulkanRenderer && pVulkanRenderer->IsSamplerAnisotropySupported(),
pVulkanRenderer && pVulkanRenderer->IsNonZeroIndirectBaseInstanceSupported()); pVulkanRenderer && pVulkanRenderer->IsNonZeroIndirectBaseInstanceSupported(),
m_vulkanCaps.SupportsImageCubeArray);
} }
void BackendObject_DirectVulkan::UpdateDynamicBackendParameters() { void BackendObject_DirectVulkan::UpdateDynamicBackendParameters() {
@@ -75,7 +75,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// the detected device support (passing an already-gated value is harmless). // the detected device support (passing an already-gated value is harmless).
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported, Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported,
Bool anisotropicFilteringSupported, Bool anisotropicFilteringSupported,
Bool nonZeroIndirectBaseInstanceSupported); Bool nonZeroIndirectBaseInstanceSupported,
Bool cubeMapArraySupported);
// Format: <GPU Name>, Vulkan <Vulkan Version>, Driver <Driver Version> — the exact // Format: <GPU Name>, Vulkan <Vulkan Version>, Driver <Driver Version> — the exact
// string an initialized backend returns from GetBackendAPIVersionString (and that // string an initialized backend returns from GetBackendAPIVersionString (and that
@@ -17,6 +17,7 @@
#include "MG_Util/Converters/MGToStr/FramebufferEnumConverter.h" #include "MG_Util/Converters/MGToStr/FramebufferEnumConverter.h"
#include "MG_Util/Converters/MGToVk/TextureEnumConverter.h" #include "MG_Util/Converters/MGToVk/TextureEnumConverter.h"
#include "MG_Util/Metrics/TextureMetrics.h" #include "MG_Util/Metrics/TextureMetrics.h"
#include "MG_Util/ShaderTranspiler/Types.h"
#include <Config.h> #include <Config.h>
#include <algorithm> #include <algorithm>
#include <cstdio> #include <cstdio>
@@ -923,22 +924,46 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Int blockIndex = programObj.storageBlockIndexByBinding[binding]; const Int blockIndex = programObj.storageBlockIndexByBinding[binding];
MOBILEGL_ASSERT(blockIndex >= 0, "ResolveStorageBufferDescriptor: no SSBO block mapped to binding %u", MOBILEGL_ASSERT(blockIndex >= 0, "ResolveStorageBufferDescriptor: no SSBO block mapped to binding %u",
binding); binding);
// An atomic counter is not an SSBO the application ever declared: glslang lowers every
// atomic_uint onto a synthesized gl_AtomicCounterBlock_<N> storage block, where N is the
// GL ATOMIC-COUNTER binding. That block arrives here auto-mapped to an arbitrary
// storage-block slot, so resolving it the SSBO way looked up GL_SHADER_STORAGE_BUFFER
// point N' - which is never where glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, N, ...) put
// the buffer. The counter therefore never reached the shader (KHR-GL43
// shader_atomic_counters.advanced-usage-*), and when the application also bound an SSBO at
// the colliding slot the descriptor silently aliased it, so the dispatch wrote over the
// application's own buffer. DirectGLES has always taken this branch explicitly
// (SyncAtomicCounterBuffers); this is the same rule in Magma's descriptor resolution.
//
// Only the SOURCE of the handle differs. The per-counter layout(offset=) is already folded
// into the block's SPIR-V member offsets on this path (FlattenAtomicCounterBlockPass is
// DirectGLES-only), so everything below - residency, the glBindBufferRange window, the
// descriptor fill - is target-agnostic and stays exactly as it was.
const String& blockName = programObj.storageBlockNameByBinding[binding];
const Int atomicCounterBinding = MG_Util::ShaderTranspiler::AtomicCounterBlockGlBinding(blockName);
const Bool isAtomicCounterBlock = atomicCounterBinding >= 0;
const BufferTarget bufferTarget =
isAtomicCounterBlock ? BufferTarget::AtomicCounter : BufferTarget::ShaderStorage;
// A block instance array declares one block whose elements take consecutive GL binding // A block instance array declares one block whose elements take consecutive GL binding
// points from the declared one (GL 4.6 core 7.8), and the reflection collapses the whole // points from the declared one (GL 4.6 core 7.8), and the reflection collapses the whole
// array to that one block - so the element index IS the offset from its binding. // array to that one block - so the element index IS the offset from its binding. glslang
// synthesizes one counter block per GL binding, so a counter block is never an instance
// array and `element` is always 0 there; the +element rule stays with the SSBO case.
const GLuint frontendBinding = const GLuint frontendBinding =
GetShaderStorageBlockBinding(program, static_cast<GLuint>(blockIndex)) + element; isAtomicCounterBlock
? static_cast<GLuint>(atomicCounterBinding)
: GetShaderStorageBlockBinding(program, static_cast<GLuint>(blockIndex)) + element;
const Uint32 bindingPointCount = const Uint32 bindingPointCount =
static_cast<Uint32>(MG_State::pGLContext->GetBufferBindingPointCount(BufferTarget::ShaderStorage)); static_cast<Uint32>(MG_State::pGLContext->GetBufferBindingPointCount(bufferTarget));
MOBILEGL_ASSERT(frontendBinding < bindingPointCount, MOBILEGL_ASSERT(frontendBinding < bindingPointCount,
"ResolveStorageBufferDescriptor: frontend SSBO binding %u out of range for block '%s'", "ResolveStorageBufferDescriptor: frontend binding %u out of range for block '%s'",
frontendBinding, programObj.storageBlockNameByBinding[binding].c_str()); frontendBinding, blockName.c_str());
auto& bindingPoint = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::ShaderStorage, frontendBinding); auto& bindingPoint = MG_State::pGLContext->GetBufferBindingPoint(bufferTarget, frontendBinding);
const auto& bufferObject = bindingPoint.GetBoundObject(); const auto& bufferObject = bindingPoint.GetBoundObject();
if (bufferObject == nullptr) { if (bufferObject == nullptr) {
MGLOG_E_ONCE("ResolveStorageBufferDescriptor: no SSBO bound at frontend binding %u for block '%s'", MGLOG_E_ONCE("ResolveStorageBufferDescriptor: no buffer bound at frontend binding %u for block '%s'",
frontendBinding, programObj.storageBlockNameByBinding[binding].c_str()); frontendBinding, blockName.c_str());
return false; return false;
} }
@@ -713,7 +713,34 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
} }
namespace {
// GL 4.6 core 7.11.2 (and ARB_shader_image_load_store, which introduced the call): the
// barrier bitfield is INVALID_VALUE unless every bit is one of the defined ones, with
// GL_ALL_BARRIER_BITS - which is 0xFFFFFFFF, not the union of the list - accepted whole.
// Forwarding an undefined bit to the host driver let a caller that had computed its mask
// wrongly (or reused an ES-only bit) get silence instead of the error the spec promises.
constexpr GLbitfield kAllDefinedBarrierBits =
GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_ELEMENT_ARRAY_BARRIER_BIT | GL_UNIFORM_BARRIER_BIT |
GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_COMMAND_BARRIER_BIT |
GL_PIXEL_BUFFER_BARRIER_BIT | GL_TEXTURE_UPDATE_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT |
GL_FRAMEBUFFER_BARRIER_BIT | GL_TRANSFORM_FEEDBACK_BARRIER_BIT | GL_ATOMIC_COUNTER_BARRIER_BIT |
GL_SHADER_STORAGE_BARRIER_BIT | GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT | GL_QUERY_BUFFER_BARRIER_BIT;
Bool ValidateMemoryBarrierBits(const char* function, GLbitfield barriers) {
if (barriers == GL_ALL_BARRIER_BITS) return true;
if ((barriers & ~kAllDefinedBarrierBits) != 0) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", function,
"barriers contains bits that are not defined barrier bits."));
return false;
}
return true;
}
} // namespace
void MemoryBarrier(GLbitfield barriers) { void MemoryBarrier(GLbitfield barriers) {
if (!ValidateMemoryBarrierBits(__func__, barriers)) return;
auto memoryBarrier = MG_Backend::gBackendFunctionsTable.GL.MemoryBarrier; auto memoryBarrier = MG_Backend::gBackendFunctionsTable.GL.MemoryBarrier;
if (!memoryBarrier) { if (!memoryBarrier) {
MG_State::pGLContext->RecordError( MG_State::pGLContext->RecordError(
@@ -725,6 +752,7 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
void MemoryBarrierByRegion(GLbitfield barriers) { void MemoryBarrierByRegion(GLbitfield barriers) {
if (!ValidateMemoryBarrierBits(__func__, barriers)) return;
auto memoryBarrierByRegion = MG_Backend::gBackendFunctionsTable.GL.MemoryBarrierByRegion; auto memoryBarrierByRegion = MG_Backend::gBackendFunctionsTable.GL.MemoryBarrierByRegion;
if (!memoryBarrierByRegion) { if (!memoryBarrierByRegion) {
MG_State::pGLContext->RecordError( MG_State::pGLContext->RecordError(
+43 -1
View File
@@ -69,8 +69,25 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
GLenum ClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) { GLenum ClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) {
// GL 4.6 core 4.1.1: GL_SYNC_FLUSH_COMMANDS_BIT is the only bit this call accepts, and
// any other bit is INVALID_VALUE. Silently ignoring the stray bits used to make a caller
// that passed, say, GL_SYNC_GPU_COMMANDS_COMPLETE by mistake think it had asked for a
// flush it never got.
if ((flags & ~static_cast<GLbitfield>(GL_SYNC_FLUSH_COMMANDS_BIT)) != 0) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"flags must be zero or GL_SYNC_FLUSH_COMMANDS_BIT."));
return GL_WAIT_FAILED;
}
const auto* syncObject = FindSyncObject(sync); const auto* syncObject = FindSyncObject(sync);
if (!syncObject) { if (!syncObject) {
// The spec pairs the GL_WAIT_FAILED return with a recorded INVALID_VALUE; returning
// the enum alone left glGetError() clean and the failure indistinguishable from a
// genuine wait failure on a live sync.
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "sync is not the name of a sync object."));
return GL_WAIT_FAILED; return GL_WAIT_FAILED;
} }
const auto backendClientWaitSync = MG_Backend::gBackendFunctionsTable.GL.ClientWaitSync; const auto backendClientWaitSync = MG_Backend::gBackendFunctionsTable.GL.ClientWaitSync;
@@ -95,6 +112,9 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
const auto* syncObject = FindSyncObject(sync); const auto* syncObject = FindSyncObject(sync);
if (!syncObject) { if (!syncObject) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "sync is not the name of a sync object."));
return; return;
} }
const auto backendWaitSync = MG_Backend::gBackendFunctionsTable.GL.WaitSync; const auto backendWaitSync = MG_Backend::gBackendFunctionsTable.GL.WaitSync;
@@ -125,8 +145,22 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
void GetSynciv(GLsync sync, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* values) { void GetSynciv(GLsync sync, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* values) {
// GL 4.6 core 4.1: a negative bufSize is INVALID_VALUE, an unnamed sync is INVALID_VALUE
// and an unrecognised pname is INVALID_ENUM. All three used to leave glGetError() clean
// and write a plausible-looking zero, which is the one failure mode a caller cannot tell
// apart from a real answer - GL_SYNC_STATUS legitimately answers GL_UNSIGNALED (0x9118),
// but a mistyped pname answered a bare 0 that no query ever returns.
if (bufSize < 0) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "bufSize must not be negative."));
return;
}
const auto* syncObject = FindSyncObject(sync); const auto* syncObject = FindSyncObject(sync);
if (!syncObject) { if (!syncObject) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "sync is not the name of a sync object."));
if (length) { if (length) {
*length = 0; *length = 0;
} }
@@ -152,7 +186,15 @@ namespace MobileGL::MG_Impl::GLImpl {
value = static_cast<GLint>(syncObject->flags); value = static_cast<GLint>(syncObject->flags);
break; break;
default: default:
break; MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"pname must be GL_OBJECT_TYPE, GL_SYNC_STATUS, GL_SYNC_CONDITION or "
"GL_SYNC_FLAGS."));
if (length) {
*length = 0;
}
return;
} }
if (length) { if (length) {
@@ -87,11 +87,6 @@ void main() {
<< " and GL_MAX_COMPUTE_ATOMIC_COUNTER_BUFFERS is " << buffers << " and GL_MAX_COMPUTE_ATOMIC_COUNTER_BUFFERS is " << buffers
<< "; this needs 3 and 2"; << "; this needs 3 and 2";
} }
if (!AtomicCountersAreWired()) {
GTEST_SKIP() << "atomic counter buffers are not wired up on " << Gl().BackendName()
<< " yet: glslang lowers them onto a storage block and that block's descriptor "
<< "is still resolved from the shader-storage binding points";
}
m_program = CompileComputeProgram(kCounterComputeSource); m_program = CompileComputeProgram(kCounterComputeSource);
ASSERT_NE(m_program, 0u) << m_buildLog; ASSERT_NE(m_program, 0u) << m_buildLog;
} }
@@ -105,12 +100,6 @@ void main() {
m_program = 0; m_program = 0;
} }
// Magma binds the lowered block as an ordinary storage-buffer descriptor resolved
// from GL_SHADER_STORAGE_BUFFER point N, so the counter buffer never reaches it. The
// frontend half (limits, reflection queries, the link-time offset rules) is
// backend-agnostic and is covered by the unit suites; only the VALUE is scoped here.
bool AtomicCountersAreWired() const { return Gl().BackendName() != "DirectVulkan"; }
unsigned int CompileComputeProgram(const char* source) { unsigned int CompileComputeProgram(const char* source) {
const GLuint shader = glCreateShader(GL_COMPUTE_SHADER); const GLuint shader = glCreateShader(GL_COMPUTE_SHADER);
glShaderSource(shader, 1, &source, nullptr); glShaderSource(shader, 1, &source, nullptr);
@@ -118,7 +118,13 @@ namespace MobileGL::MG_State::GLState {
// record that so backends skip uploading the stale shadow bytes. // record that so backends skip uploading the stale shadow bytes.
m_hasDefinedContent = (data != nullptr) || size == 0; m_hasDefinedContent = (data != nullptr) || size == 0;
m_isImmutableStorage = false; m_isImmutableStorage = false;
m_storageFlags = 0; // GL 4.6 core 6.2 defines glBufferData as glBufferStorage with
// DYNAMIC_STORAGE_BIT | MAP_READ_BIT | MAP_WRITE_BIT, so GL_BUFFER_STORAGE_FLAGS has to
// report those three afterwards. Reporting 0 - the value that belongs to a buffer whose
// store has never been specified - told an application that a perfectly writable
// glBufferData buffer accepted neither glBufferSubData nor a map. Only the IMMUTABLE flag
// distinguishes the two cases, and it is cleared just above.
m_storageFlags = GL_DYNAMIC_STORAGE_BIT | GL_MAP_READ_BIT | GL_MAP_WRITE_BIT;
NotifyRespecify(); NotifyRespecify();
} }
@@ -1087,22 +1087,76 @@ TEST(TextureAnisotropyCapabilities, ExtensionIsAdvertisedOnlyWhenTheHostDriverSu
return std::find(extensions.begin(), extensions.end(), wanted) != extensions.end(); return std::find(extensions.begin(), extensions.end(), wanted) != extensions.end();
}; };
const auto without = MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false); const auto without = MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false, false);
EXPECT_FALSE(contains(without, MobileGL::E_GL_EXT_texture_filter_anisotropic)); EXPECT_FALSE(contains(without, MobileGL::E_GL_EXT_texture_filter_anisotropic));
EXPECT_FALSE(contains(without, MobileGL::E_GL_ARB_texture_filter_anisotropic)); EXPECT_FALSE(contains(without, MobileGL::E_GL_ARB_texture_filter_anisotropic));
const auto with = MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, true, false, false, false); const auto with = MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, true, false, false, false, false);
EXPECT_TRUE(contains(with, MobileGL::E_GL_EXT_texture_filter_anisotropic)); EXPECT_TRUE(contains(with, MobileGL::E_GL_EXT_texture_filter_anisotropic));
EXPECT_TRUE(contains(with, MobileGL::E_GL_ARB_texture_filter_anisotropic)); EXPECT_TRUE(contains(with, MobileGL::E_GL_ARB_texture_filter_anisotropic));
// Same rule on the Vulkan backend, where the gate is the samplerAnisotropy device feature. // Same rule on the Vulkan backend, where the gate is the samplerAnisotropy device feature.
const auto vkWithout = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false); const auto vkWithout = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false, false);
EXPECT_FALSE(contains(vkWithout, MobileGL::E_GL_EXT_texture_filter_anisotropic)); EXPECT_FALSE(contains(vkWithout, MobileGL::E_GL_EXT_texture_filter_anisotropic));
const auto vkWith = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, true, false); const auto vkWith = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, true, false, false);
EXPECT_TRUE(contains(vkWith, MobileGL::E_GL_EXT_texture_filter_anisotropic)); EXPECT_TRUE(contains(vkWith, MobileGL::E_GL_EXT_texture_filter_anisotropic));
EXPECT_TRUE(contains(vkWith, MobileGL::E_GL_ARB_texture_filter_anisotropic)); EXPECT_TRUE(contains(vkWith, MobileGL::E_GL_ARB_texture_filter_anisotropic));
} }
// Cube map arrays are core at the version MobileGL claims, but there is nothing underneath on a
// pre-ES-3.2 driver without EXT/OES_texture_cube_map_array, and no VK_IMAGE_VIEW_TYPE_CUBE_ARRAY
// without the imageCubeArray feature. The string has to follow the capability on both backends -
// and it has to BE there when the capability is, because KHR-GL4*.texture_gather.*-cube-array
// gates on the string with no core-version fallback.
TEST(CubeMapArrayAdvertisement, FollowsTheHostCapabilityOnBothBackends) {
const auto contains = [](const MobileGL::Vector<MobileGL::GLExtension>& extensions,
MobileGL::GLExtension wanted) {
return std::find(extensions.begin(), extensions.end(), wanted) != extensions.end();
};
const auto esWithout =
MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false, false);
EXPECT_FALSE(contains(esWithout, MobileGL::E_GL_ARB_texture_cube_map_array));
const auto esWith =
MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false, true);
EXPECT_TRUE(contains(esWith, MobileGL::E_GL_ARB_texture_cube_map_array));
const auto vkWithout = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false,
false);
EXPECT_FALSE(contains(vkWithout, MobileGL::E_GL_ARB_texture_cube_map_array));
const auto vkWith = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false, true);
EXPECT_TRUE(contains(vkWith, MobileGL::E_GL_ARB_texture_cube_map_array));
}
// The core-plumbing strings carry no capability gate: they name entry points that have been real
// on both backends for as long as the backends have existed, and an application that gates its
// entry-point resolution on the string (LWJGL does) would otherwise call through null. Pinned
// together so a future edit cannot quietly drop one, and pinned on BOTH backends so the two
// cannot disagree about what MobileGL is.
TEST(CorePlumbingAdvertisement, IsUnconditionalAndIdenticalOnBothBackends) {
const auto contains = [](const MobileGL::Vector<MobileGL::GLExtension>& extensions,
MobileGL::GLExtension wanted) {
return std::find(extensions.begin(), extensions.end(), wanted) != extensions.end();
};
const MobileGL::GLExtension expected[] = {
MobileGL::E_GL_ARB_sync, MobileGL::E_GL_ARB_shader_atomic_counters,
MobileGL::E_GL_ARB_vertex_array_object, MobileGL::E_GL_ARB_sampler_objects,
MobileGL::E_GL_ARB_map_buffer_range, MobileGL::E_GL_ARB_copy_buffer,
MobileGL::E_GL_ARB_copy_image, MobileGL::E_GL_ARB_texture_swizzle,
MobileGL::E_GL_ARB_vertex_type_2_10_10_10_rev, MobileGL::E_GL_ARB_texture_rg,
MobileGL::E_GL_ARB_depth_buffer_float, MobileGL::E_GL_ARB_texture_float,
MobileGL::E_GL_ARB_viewport_array};
// Every gate off: none of these may depend on one.
const auto es = MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false,
false);
const auto vk = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false, false);
for (const auto extension : expected) {
EXPECT_TRUE(contains(es, extension)) << "DirectGLES stopped advertising extension " << extension;
EXPECT_TRUE(contains(vk, extension)) << "DirectVulkan stopped advertising extension " << extension;
}
}
// Minecraft 26.3 checks ARB_draw_indirect before it considers the already-advertised // Minecraft 26.3 checks ARB_draw_indirect before it considers the already-advertised
// ARB_multi_draw_indirect, then separately requires ARB_base_instance before enabling its terrain // ARB_multi_draw_indirect, then separately requires ARB_base_instance before enabling its terrain
// indirect path. Pin both strings and, just as importantly, the non-zero firstInstance gate. // indirect path. Pin both strings and, just as importantly, the non-zero firstInstance gate.
@@ -1113,27 +1167,27 @@ TEST(IndirectDrawAdvertisement, MatchesEachBackendsUsableCommandSemantics) {
}; };
const auto esWithoutIndirect = const auto esWithoutIndirect =
MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false); MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false, false);
EXPECT_FALSE(contains(esWithoutIndirect, MobileGL::E_GL_ARB_draw_indirect)); EXPECT_FALSE(contains(esWithoutIndirect, MobileGL::E_GL_ARB_draw_indirect));
EXPECT_FALSE(contains(esWithoutIndirect, MobileGL::E_GL_ARB_base_instance)); EXPECT_FALSE(contains(esWithoutIndirect, MobileGL::E_GL_ARB_base_instance));
const auto esWithoutBaseInstance = const auto esWithoutBaseInstance =
MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, true, false, false); MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, true, false, false, false);
EXPECT_TRUE(contains(esWithoutBaseInstance, MobileGL::E_GL_ARB_draw_indirect)); EXPECT_TRUE(contains(esWithoutBaseInstance, MobileGL::E_GL_ARB_draw_indirect));
EXPECT_FALSE(contains(esWithoutBaseInstance, MobileGL::E_GL_ARB_base_instance)); EXPECT_FALSE(contains(esWithoutBaseInstance, MobileGL::E_GL_ARB_base_instance));
const auto esWithBoth = const auto esWithBoth =
MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, true, true, false); MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, true, true, false, false);
EXPECT_TRUE(contains(esWithBoth, MobileGL::E_GL_ARB_draw_indirect)); EXPECT_TRUE(contains(esWithBoth, MobileGL::E_GL_ARB_draw_indirect));
EXPECT_TRUE(contains(esWithBoth, MobileGL::E_GL_ARB_base_instance)); EXPECT_TRUE(contains(esWithBoth, MobileGL::E_GL_ARB_base_instance));
const auto vkWithoutBaseInstance = const auto vkWithoutBaseInstance =
MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false); MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false, false);
EXPECT_TRUE(contains(vkWithoutBaseInstance, MobileGL::E_GL_ARB_draw_indirect)); EXPECT_TRUE(contains(vkWithoutBaseInstance, MobileGL::E_GL_ARB_draw_indirect));
EXPECT_FALSE(contains(vkWithoutBaseInstance, MobileGL::E_GL_ARB_base_instance)); EXPECT_FALSE(contains(vkWithoutBaseInstance, MobileGL::E_GL_ARB_base_instance));
const auto vkWithBoth = const auto vkWithBoth =
MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, true); MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, true, false);
EXPECT_TRUE(contains(vkWithBoth, MobileGL::E_GL_ARB_draw_indirect)); EXPECT_TRUE(contains(vkWithBoth, MobileGL::E_GL_ARB_draw_indirect));
EXPECT_TRUE(contains(vkWithBoth, MobileGL::E_GL_ARB_base_instance)); EXPECT_TRUE(contains(vkWithBoth, MobileGL::E_GL_ARB_base_instance));
} }
@@ -516,17 +516,17 @@ TEST_F(ParallelShaderCompileTest, MaxShaderCompilerThreadsIgnoresTheCurrentBudge
TEST_F(ParallelShaderCompileTest, BothBackendsAdvertiseTheExtensionIffAsyncIsEnabled) { TEST_F(ParallelShaderCompileTest, BothBackendsAdvertiseTheExtensionIffAsyncIsEnabled) {
{ {
const AsyncModeScope async(true); const AsyncModeScope async(true);
EXPECT_TRUE(Advertises(MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false), EXPECT_TRUE(Advertises(MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false, false),
E_GL_KHR_parallel_shader_compile)); E_GL_KHR_parallel_shader_compile));
EXPECT_TRUE(Advertises(MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false), EXPECT_TRUE(Advertises(MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false, false),
E_GL_KHR_parallel_shader_compile)); E_GL_KHR_parallel_shader_compile));
} }
{ {
const AsyncModeScope async(false); const AsyncModeScope async(false);
EXPECT_FALSE(Advertises(MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false), EXPECT_FALSE(Advertises(MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false, false),
E_GL_KHR_parallel_shader_compile)) E_GL_KHR_parallel_shader_compile))
<< "MOBILEGL_ASYNC_SHADER_COMPILE=0 must withdraw the extension, not only the threading"; << "MOBILEGL_ASYNC_SHADER_COMPILE=0 must withdraw the extension, not only the threading";
EXPECT_FALSE(Advertises(MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false), EXPECT_FALSE(Advertises(MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false, false),
E_GL_KHR_parallel_shader_compile)) E_GL_KHR_parallel_shader_compile))
<< "MOBILEGL_ASYNC_SHADER_COMPILE=0 must withdraw the extension, not only the threading"; << "MOBILEGL_ASYNC_SHADER_COMPILE=0 must withdraw the extension, not only the threading";
} }
+5 -2
View File
@@ -1269,7 +1269,7 @@ namespace MobileGL::MG_Util::SelfTest {
summary.caps.SupportsDisjointTimerQuery, summary.caps.SupportsTextureFilterAnisotropy, summary.caps.SupportsDisjointTimerQuery, summary.caps.SupportsTextureFilterAnisotropy,
summary.caps.SupportsDrawIndirect, summary.caps.SupportsDrawIndirect,
summary.caps.SupportsDrawIndirect && summary.caps.SupportsBaseInstance, summary.caps.SupportsDrawIndirect && summary.caps.SupportsBaseInstance,
summary.caps.SupportsTextureView)); summary.caps.SupportsTextureView, summary.caps.SupportsTextureCubeMapArray));
} }
AppendMobileGLReportedRows(builder, MG_Backend::DirectGLES::GetRendererIdentity(), backendApiVersionString, AppendMobileGLReportedRows(builder, MG_Backend::DirectGLES::GetRendererIdentity(), backendApiVersionString,
advertisedExtensions); advertisedExtensions);
@@ -2009,6 +2009,7 @@ namespace MobileGL::MG_Util::SelfTest {
Bool samplerAnisotropySupported = false; Bool samplerAnisotropySupported = false;
Bool drawIndirectFirstInstanceSupported = false; Bool drawIndirectFirstInstanceSupported = false;
Bool shaderDrawParametersSupported = false; Bool shaderDrawParametersSupported = false;
Bool imageCubeArraySupported = false;
}; };
} // namespace } // namespace
@@ -2300,6 +2301,7 @@ namespace MobileGL::MG_Util::SelfTest {
VkPhysicalDeviceFeatures features{}; VkPhysicalDeviceFeatures features{};
vkGetPhysicalDeviceFeaturesFn(physicalDevice, &features); vkGetPhysicalDeviceFeaturesFn(physicalDevice, &features);
summary.samplerAnisotropySupported = features.samplerAnisotropy == VK_TRUE; summary.samplerAnisotropySupported = features.samplerAnisotropy == VK_TRUE;
summary.imageCubeArraySupported = features.imageCubeArray == VK_TRUE;
summary.drawIndirectFirstInstanceSupported = features.drawIndirectFirstInstance == VK_TRUE; summary.drawIndirectFirstInstanceSupported = features.drawIndirectFirstInstance == VK_TRUE;
if (features.multiDrawIndirect == VK_TRUE) { if (features.multiDrawIndirect == VK_TRUE) {
builder.Pass("multiDrawIndirect", "indirect multi-draw batches run as single native commands"); builder.Pass("multiDrawIndirect", "indirect multi-draw batches run as single native commands");
@@ -2721,7 +2723,8 @@ namespace MobileGL::MG_Util::SelfTest {
summary.deviceName, summary.apiVersionString, summary.driverVersionString); summary.deviceName, summary.apiVersionString, summary.driverVersionString);
advertisedExtensions = JoinAdvertisedExtensions(MG_Backend::DirectVulkan::BuildAdvertisedExtensions( advertisedExtensions = JoinAdvertisedExtensions(MG_Backend::DirectVulkan::BuildAdvertisedExtensions(
summary.shaderSubgroupUsable, summary.timerQueriesSupported, summary.samplerAnisotropySupported, summary.shaderSubgroupUsable, summary.timerQueriesSupported, summary.samplerAnisotropySupported,
summary.drawIndirectFirstInstanceSupported && summary.shaderDrawParametersSupported)); summary.drawIndirectFirstInstanceSupported && summary.shaderDrawParametersSupported,
summary.imageCubeArraySupported));
} }
AppendMobileGLReportedRows(builder, MG_Backend::DirectVulkan::GetRendererIdentity(), backendApiVersionString, AppendMobileGLReportedRows(builder, MG_Backend::DirectVulkan::GetRendererIdentity(), backendApiVersionString,
advertisedExtensions); advertisedExtensions);
+22
View File
@@ -20,6 +20,28 @@ namespace MobileGL {
// buffer, and the trailing number is the only place the GL binding survives. // buffer, and the trailing number is the only place the GL binding survives.
inline constexpr const char* ATOMIC_COUNTER_BLOCK_PREFIX = "gl_AtomicCounterBlock"; inline constexpr const char* ATOMIC_COUNTER_BLOCK_PREFIX = "gl_AtomicCounterBlock";
// "gl_AtomicCounterBlock_5" -> 5; -1 for any name that is not one of these blocks.
// Recovering N from the NAME is not a shortcut, it is the only way: the block reaches
// a backend auto-mapped to whatever storage-block slot the IO mapper had free, and
// that number has no relation to the GL atomic-counter binding the application asked
// for (see TMglGlslIoResolver). A backend that resolves the block from the
// shader-storage binding points therefore binds the wrong buffer - or, worse, the
// application's own SSBO at the same slot.
inline Int AtomicCounterBlockGlBinding(StringView name) {
const SizeT prefixLength = StringView(ATOMIC_COUNTER_BLOCK_PREFIX).size();
// Needs the prefix, the '_' and at least one digit.
if (name.size() <= prefixLength + 1) return -1;
if (name.compare(0, prefixLength, ATOMIC_COUNTER_BLOCK_PREFIX) != 0) return -1;
if (name[prefixLength] != '_') return -1;
Int binding = 0;
for (SizeT i = prefixLength + 1; i < name.size(); ++i) {
if (name[i] < '0' || name[i] > '9') return -1;
binding = binding * 10 + (name[i] - '0');
if (binding > 0x0FFFFFFF) return -1; // absurd suffix; treat as not-a-counter
}
return binding;
}
// Atomic-counter limits, in ONE place because GL 4.6 requires glGetIntegerv and the // Atomic-counter limits, in ONE place because GL 4.6 requires glGetIntegerv and the
// shading language's gl_MaxAtomicCounter* constants to report the same numbers // shading language's gl_MaxAtomicCounter* constants to report the same numbers
// (KHR-GL43.shader_atomic_counters.basic-glsl-built-in compares them directly). // (KHR-GL43.shader_atomic_counters.basic-glsl-built-in compares them directly).