mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
[Feat] (MG_Backend, MG_Impl): expose experimental GL 4.6 CTS limits
This commit is contained in:
@@ -300,6 +300,13 @@ namespace MobileGL {
|
||||
Float ViewportBoundsRangeMin = 0.0f;
|
||||
Float ViewportBoundsRangeMax = 0.0f;
|
||||
Int ViewportSubpixelBits = 0;
|
||||
// GL 4.x fragment-interpolation offset limits. These defaults are the
|
||||
// core minimums and are replaced by live GLES/Vulkan device limits.
|
||||
Float MinFragmentInterpolationOffset = -0.5f;
|
||||
// For four fractional bits the greatest required legal offset is
|
||||
// 0.5 - 2^-4 = 0.4375 (GL 4.6 table 23.70).
|
||||
Float MaxFragmentInterpolationOffset = 0.4375f;
|
||||
Int FragmentInterpolationOffsetBits = 4;
|
||||
Bool SupportsWideLines = false;
|
||||
SizeT MaxShaderStorageBlockSize = 128 * 1024 * 1024;
|
||||
Uint32 SubgroupSize = 0;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <MG_Util/Texture/TextureFormatProcessor.h>
|
||||
#include <Config.h>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <format>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectGLES {
|
||||
@@ -603,7 +604,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
.ExtraVendor = Nullopt, // Extra vendor
|
||||
.RendererGLInfo =
|
||||
{
|
||||
.TargetGLVersion = {3, 3, 0}, // Target OpenGL Version
|
||||
.TargetGLVersion = {4, 6, 0}, // Experimental GL CTS target version
|
||||
.TargetGLSLVersion = {4, 6, 0}, // Target Shading Language Version
|
||||
// Baseline advertisement (no timer queries / anisotropy yet); reconciled
|
||||
// once the ES capabilities exist, see UpdateAdvertisedCapabilityExtensions.
|
||||
@@ -1034,6 +1035,24 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
m_dynamicParameters.ViewportBoundsRangeMin = m_GLESCapabilities.ViewportBoundsRangeMin;
|
||||
m_dynamicParameters.ViewportBoundsRangeMax = m_GLESCapabilities.ViewportBoundsRangeMax;
|
||||
m_dynamicParameters.ViewportSubpixelBits = m_GLESCapabilities.ViewportSubpixelBits;
|
||||
m_dynamicParameters.MinFragmentInterpolationOffset =
|
||||
std::isfinite(m_GLESCapabilities.MinFragmentInterpolationOffset) &&
|
||||
m_GLESCapabilities.MinFragmentInterpolationOffset <= -0.5f
|
||||
? m_GLESCapabilities.MinFragmentInterpolationOffset
|
||||
: -0.5f;
|
||||
m_dynamicParameters.MaxFragmentInterpolationOffset = 0.4375f;
|
||||
m_dynamicParameters.FragmentInterpolationOffsetBits = 4;
|
||||
if (m_GLESCapabilities.FragmentInterpolationOffsetBits >= 4 &&
|
||||
std::isfinite(m_GLESCapabilities.MaxFragmentInterpolationOffset)) {
|
||||
const Float requiredMaxOffset =
|
||||
0.5f - std::ldexp(1.0f, -m_GLESCapabilities.FragmentInterpolationOffsetBits);
|
||||
if (m_GLESCapabilities.MaxFragmentInterpolationOffset >= requiredMaxOffset) {
|
||||
m_dynamicParameters.MaxFragmentInterpolationOffset =
|
||||
m_GLESCapabilities.MaxFragmentInterpolationOffset;
|
||||
m_dynamicParameters.FragmentInterpolationOffsetBits =
|
||||
m_GLESCapabilities.FragmentInterpolationOffsetBits;
|
||||
}
|
||||
}
|
||||
m_dynamicParameters.SupportsWideLines =
|
||||
m_GLESCapabilities.AliasedLineWidthRangeMax > 1.0f || m_GLESCapabilities.SmoothLineWidthRangeMax > 1.0f;
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "MG_Util/Texture/TextureFormatProcessor.h"
|
||||
|
||||
#include <Config.h>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
@@ -505,7 +506,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
.ExtraVendor = Nullopt,
|
||||
.RendererGLInfo =
|
||||
{
|
||||
.TargetGLVersion = {3, 3, 0},
|
||||
.TargetGLVersion = {4, 6, 0}, // Experimental GL CTS target version
|
||||
.TargetGLSLVersion = {4, 6, 0},
|
||||
// Baseline advertisement (no shader subgroup, no timer queries); a
|
||||
// live backend reconciles its copy in UpdateAdvertisedExtensions.
|
||||
@@ -796,6 +797,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
m_dynamicParameters.ViewportBoundsRangeMin = m_vulkanCaps.ViewportBoundsRangeMin;
|
||||
m_dynamicParameters.ViewportBoundsRangeMax = m_vulkanCaps.ViewportBoundsRangeMax;
|
||||
m_dynamicParameters.ViewportSubpixelBits = m_vulkanCaps.ViewportSubpixelBits;
|
||||
m_dynamicParameters.MinFragmentInterpolationOffset =
|
||||
std::isfinite(m_vulkanCaps.MinFragmentInterpolationOffset) &&
|
||||
m_vulkanCaps.MinFragmentInterpolationOffset <= -0.5f
|
||||
? m_vulkanCaps.MinFragmentInterpolationOffset
|
||||
: -0.5f;
|
||||
m_dynamicParameters.MaxFragmentInterpolationOffset = 0.4375f;
|
||||
m_dynamicParameters.FragmentInterpolationOffsetBits = 4;
|
||||
if (m_vulkanCaps.FragmentInterpolationOffsetBits >= 4 &&
|
||||
std::isfinite(m_vulkanCaps.MaxFragmentInterpolationOffset)) {
|
||||
const Float requiredMaxOffset =
|
||||
0.5f - std::ldexp(1.0f, -m_vulkanCaps.FragmentInterpolationOffsetBits);
|
||||
if (m_vulkanCaps.MaxFragmentInterpolationOffset >= requiredMaxOffset) {
|
||||
m_dynamicParameters.MaxFragmentInterpolationOffset = m_vulkanCaps.MaxFragmentInterpolationOffset;
|
||||
m_dynamicParameters.FragmentInterpolationOffsetBits =
|
||||
m_vulkanCaps.FragmentInterpolationOffsetBits;
|
||||
}
|
||||
}
|
||||
m_dynamicParameters.SupportsWideLines = m_vulkanCaps.SupportsWideLines;
|
||||
m_dynamicParameters.MaxShaderStorageBlockSize =
|
||||
std::min(m_vulkanCaps.MaxShaderStorageBlockSize, kMaxAdvertisedShaderStorageBlockSize);
|
||||
|
||||
@@ -465,6 +465,14 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
case GL_STENCIL_TEST:
|
||||
*params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::StencilTest) ? GL_TRUE : GL_FALSE;
|
||||
return;
|
||||
case GL_MIN_FRAGMENT_INTERPOLATION_OFFSET:
|
||||
case GL_MAX_FRAGMENT_INTERPOLATION_OFFSET:
|
||||
case GL_FRAGMENT_INTERPOLATION_OFFSET_BITS: {
|
||||
GLfloat value = 0.0f;
|
||||
GetFloatv(pname, &value);
|
||||
*params = value != 0.0f ? GL_TRUE : GL_FALSE;
|
||||
return;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -520,6 +528,19 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
params[1] = dynamicParameters.ViewportBoundsRangeMax;
|
||||
return;
|
||||
}
|
||||
case GL_MIN_FRAGMENT_INTERPOLATION_OFFSET:
|
||||
case GL_MAX_FRAGMENT_INTERPOLATION_OFFSET:
|
||||
case GL_FRAGMENT_INTERPOLATION_OFFSET_BITS: {
|
||||
const auto& dynamicParameters = MG_Backend::pActiveBackendObject->GetDynamicParameters();
|
||||
if (pname == GL_MIN_FRAGMENT_INTERPOLATION_OFFSET) {
|
||||
params[0] = dynamicParameters.MinFragmentInterpolationOffset;
|
||||
} else if (pname == GL_MAX_FRAGMENT_INTERPOLATION_OFFSET) {
|
||||
params[0] = dynamicParameters.MaxFragmentInterpolationOffset;
|
||||
} else {
|
||||
params[0] = static_cast<GLfloat>(dynamicParameters.FragmentInterpolationOffsetBits);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case GL_DEPTH_CLEAR_VALUE:
|
||||
params[0] = MG_State::pGLContext->GetClearDepth();
|
||||
return;
|
||||
@@ -1957,6 +1978,15 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
case GL_SUBPIXEL_BITS:
|
||||
*params = std::max(dynamicParameters.ViewportSubpixelBits, kFrontendSubpixelBits);
|
||||
break;
|
||||
case GL_MIN_FRAGMENT_INTERPOLATION_OFFSET:
|
||||
*params = static_cast<GLint>(std::lround(dynamicParameters.MinFragmentInterpolationOffset));
|
||||
break;
|
||||
case GL_MAX_FRAGMENT_INTERPOLATION_OFFSET:
|
||||
*params = static_cast<GLint>(std::lround(dynamicParameters.MaxFragmentInterpolationOffset));
|
||||
break;
|
||||
case GL_FRAGMENT_INTERPOLATION_OFFSET_BITS:
|
||||
*params = dynamicParameters.FragmentInterpolationOffsetBits;
|
||||
break;
|
||||
case GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT:
|
||||
*params = static_cast<Int>(dynamicParameters.UniformBufferOffsetAlignment);
|
||||
break;
|
||||
|
||||
@@ -34,6 +34,11 @@ namespace {
|
||||
GLint maxFragmentImageUniforms = 4;
|
||||
GLint maxComputeImageUniforms = 5;
|
||||
bool maxGeometryImageUniformsQueried = false;
|
||||
GLfloat minFragmentInterpolationOffset = -0.75f;
|
||||
GLfloat maxFragmentInterpolationOffset = 0.625f;
|
||||
GLint fragmentInterpolationOffsetBits = 6;
|
||||
bool fragmentInterpolationLimitsQueried = false;
|
||||
bool fragmentInterpolationQueryRaisesError = false;
|
||||
// Emulates ANGLE-on-Vulkan: the draw reads the indirect command's
|
||||
// baseInstance word and exposes it through gl_InstanceID.
|
||||
bool drawLeaksBaseInstanceWord = false;
|
||||
@@ -108,6 +113,14 @@ namespace {
|
||||
case GL_MAX_COMPUTE_IMAGE_UNIFORMS:
|
||||
*data = g_fake.maxComputeImageUniforms;
|
||||
break;
|
||||
case GL_FRAGMENT_INTERPOLATION_OFFSET_BITS:
|
||||
g_fake.fragmentInterpolationLimitsQueried = true;
|
||||
if (g_fake.fragmentInterpolationQueryRaisesError) {
|
||||
g_fake.pendingError = GL_INVALID_ENUM;
|
||||
} else {
|
||||
*data = g_fake.fragmentInterpolationOffsetBits;
|
||||
}
|
||||
break;
|
||||
// FillInGLESCapabilities reads the context version before running the
|
||||
// baseInstance probe, which requires ES >= 3.1.
|
||||
case GL_MAJOR_VERSION:
|
||||
@@ -155,6 +168,22 @@ namespace {
|
||||
g_fake.maxTextureMaxAnisotropyQueried = true;
|
||||
data[0] = g_fake.maxTextureMaxAnisotropy;
|
||||
break;
|
||||
case GL_MIN_FRAGMENT_INTERPOLATION_OFFSET:
|
||||
g_fake.fragmentInterpolationLimitsQueried = true;
|
||||
if (g_fake.fragmentInterpolationQueryRaisesError) {
|
||||
g_fake.pendingError = GL_INVALID_ENUM;
|
||||
} else {
|
||||
data[0] = g_fake.minFragmentInterpolationOffset;
|
||||
}
|
||||
break;
|
||||
case GL_MAX_FRAGMENT_INTERPOLATION_OFFSET:
|
||||
g_fake.fragmentInterpolationLimitsQueried = true;
|
||||
if (g_fake.fragmentInterpolationQueryRaisesError) {
|
||||
g_fake.pendingError = GL_INVALID_ENUM;
|
||||
} else {
|
||||
data[0] = g_fake.maxFragmentInterpolationOffset;
|
||||
}
|
||||
break;
|
||||
// Two-component range queries.
|
||||
case GL_ALIASED_LINE_WIDTH_RANGE:
|
||||
case GL_SMOOTH_LINE_WIDTH_RANGE:
|
||||
@@ -462,6 +491,52 @@ TEST(ImageUniformCapabilities, QueriesRealPerStageLimitsAndConservativelyGatesGe
|
||||
EXPECT_TRUE(g_fake.maxGeometryImageUniformsQueried);
|
||||
}
|
||||
|
||||
TEST(FragmentInterpolationCapabilities, QueriesOnlyWhenSupportedAndPreservesDriverLimits) {
|
||||
const auto funcs = MakeFakeGLESFunctions();
|
||||
|
||||
ResetFakeDriver();
|
||||
g_fake.maxVertexSsboBlocks = 0;
|
||||
MobileGL::MG_External::GLESCapabilities unsupportedCaps;
|
||||
ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(unsupportedCaps, funcs));
|
||||
EXPECT_FALSE(unsupportedCaps.SupportsShaderMultisampleInterpolation);
|
||||
EXPECT_FALSE(g_fake.fragmentInterpolationLimitsQueried);
|
||||
EXPECT_FLOAT_EQ(unsupportedCaps.MinFragmentInterpolationOffset, -0.5f);
|
||||
EXPECT_FLOAT_EQ(unsupportedCaps.MaxFragmentInterpolationOffset, 0.4375f);
|
||||
EXPECT_EQ(unsupportedCaps.FragmentInterpolationOffsetBits, 4);
|
||||
|
||||
ResetFakeDriver();
|
||||
g_fake.maxVertexSsboBlocks = 0;
|
||||
g_fake.extensions.emplace_back("GL_OES_shader_multisample_interpolation");
|
||||
// A stale error from an earlier capability probe must not make the optional
|
||||
// interpolation query look like it failed.
|
||||
g_fake.pendingError = GL_INVALID_OPERATION;
|
||||
MobileGL::MG_External::GLESCapabilities supportedCaps;
|
||||
ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(supportedCaps, funcs));
|
||||
EXPECT_TRUE(supportedCaps.SupportsShaderMultisampleInterpolation);
|
||||
EXPECT_TRUE(g_fake.fragmentInterpolationLimitsQueried);
|
||||
EXPECT_FLOAT_EQ(supportedCaps.MinFragmentInterpolationOffset, g_fake.minFragmentInterpolationOffset);
|
||||
EXPECT_FLOAT_EQ(supportedCaps.MaxFragmentInterpolationOffset, g_fake.maxFragmentInterpolationOffset);
|
||||
EXPECT_EQ(supportedCaps.FragmentInterpolationOffsetBits, g_fake.fragmentInterpolationOffsetBits);
|
||||
EXPECT_EQ(funcs.glGetError(), GL_NO_ERROR);
|
||||
}
|
||||
|
||||
TEST(FragmentInterpolationCapabilities, QueryErrorIsDrainedAndFallsBackToCoreMinimums) {
|
||||
ResetFakeDriver();
|
||||
g_fake.maxVertexSsboBlocks = 0;
|
||||
g_fake.extensions.emplace_back("GL_OES_shader_multisample_interpolation");
|
||||
g_fake.fragmentInterpolationQueryRaisesError = true;
|
||||
const auto funcs = MakeFakeGLESFunctions();
|
||||
|
||||
MobileGL::MG_External::GLESCapabilities caps;
|
||||
ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(caps, funcs));
|
||||
|
||||
EXPECT_TRUE(g_fake.fragmentInterpolationLimitsQueried);
|
||||
EXPECT_FLOAT_EQ(caps.MinFragmentInterpolationOffset, -0.5f);
|
||||
EXPECT_FLOAT_EQ(caps.MaxFragmentInterpolationOffset, 0.4375f);
|
||||
EXPECT_EQ(caps.FragmentInterpolationOffsetBits, 4);
|
||||
EXPECT_EQ(funcs.glGetError(), GL_NO_ERROR);
|
||||
}
|
||||
|
||||
// The extension string is what apps gate on (LWJGL builds GLCapabilities from it), so advertising
|
||||
// it on a driver that cannot filter anisotropically would leave them silently on trilinear.
|
||||
TEST(TextureAnisotropyCapabilities, ExtensionIsAdvertisedOnlyWhenTheHostDriverSupportsIt) {
|
||||
|
||||
@@ -197,13 +197,13 @@ TEST(DirectGLESSanity, AdvertisesDepthTextureForGlmarkShadowScenes) {
|
||||
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_depth_texture), extensions.end());
|
||||
}
|
||||
|
||||
TEST(DirectGLESSanity, AdvertisesVoxyRequiredRenderingExtensionsWithoutRaisingGLVersion) {
|
||||
TEST(DirectGLESSanity, AdvertisesVoxyRequiredRenderingExtensionsAtExperimentalCTSVersion) {
|
||||
MobileGL::MG_Backend::DirectGLES::BackendObject_DirectGLES backend;
|
||||
const auto& rendererInfo = backend.GetRendererInfo().RendererGLInfo;
|
||||
const auto& extensions = rendererInfo.Extensions;
|
||||
|
||||
EXPECT_EQ(rendererInfo.TargetGLVersion.Major, 3);
|
||||
EXPECT_EQ(rendererInfo.TargetGLVersion.Minor, 3);
|
||||
EXPECT_EQ(rendererInfo.TargetGLVersion.Major, 4);
|
||||
EXPECT_EQ(rendererInfo.TargetGLVersion.Minor, 6);
|
||||
EXPECT_EQ(rendererInfo.TargetGLVersion.Patch, 0);
|
||||
|
||||
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_compute_shader),
|
||||
@@ -397,13 +397,13 @@ TEST(DirectVulkanSanity, RenderPassExtentUsesSwapchainSizeOnlyForDefaultFramebuf
|
||||
MobileGL::IntVec2(512, 512));
|
||||
}
|
||||
|
||||
TEST(DirectVulkanSanity, AdvertisesVoxyRequiredRenderingExtensionsWithoutRaisingGLVersion) {
|
||||
TEST(DirectVulkanSanity, AdvertisesVoxyRequiredRenderingExtensionsAtExperimentalCTSVersion) {
|
||||
MobileGL::MG_Backend::DirectVulkan::BackendObject_DirectVulkan backend;
|
||||
const auto& rendererInfo = backend.GetRendererInfo().RendererGLInfo;
|
||||
const auto& extensions = rendererInfo.Extensions;
|
||||
|
||||
EXPECT_EQ(rendererInfo.TargetGLVersion.Major, 3);
|
||||
EXPECT_EQ(rendererInfo.TargetGLVersion.Minor, 3);
|
||||
EXPECT_EQ(rendererInfo.TargetGLVersion.Major, 4);
|
||||
EXPECT_EQ(rendererInfo.TargetGLVersion.Minor, 6);
|
||||
EXPECT_EQ(rendererInfo.TargetGLVersion.Patch, 0);
|
||||
|
||||
EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_compute_shader),
|
||||
@@ -516,6 +516,50 @@ TEST(DirectGLESSanity, PreservesHostPerStageImageUniformLimits) {
|
||||
EXPECT_EQ(params.MaxComputeImageUniforms, 5);
|
||||
}
|
||||
|
||||
TEST(FragmentInterpolationCapabilities, PlumbsGLESAndBothVulkanPropertyPaths) {
|
||||
using namespace MobileGL;
|
||||
|
||||
MG_External::GLESCapabilities glesCaps;
|
||||
glesCaps.MinFragmentInterpolationOffset = -0.75f;
|
||||
glesCaps.MaxFragmentInterpolationOffset = 0.625f;
|
||||
glesCaps.FragmentInterpolationOffsetBits = 6;
|
||||
MG_Backend::DirectGLES::BackendObject_DirectGLES glesBackend;
|
||||
glesBackend.ApplyGLESCapabilitiesForTesting(glesCaps);
|
||||
EXPECT_FLOAT_EQ(glesBackend.GetDynamicParameters().MinFragmentInterpolationOffset, -0.75f);
|
||||
EXPECT_FLOAT_EQ(glesBackend.GetDynamicParameters().MaxFragmentInterpolationOffset, 0.625f);
|
||||
EXPECT_EQ(glesBackend.GetDynamicParameters().FragmentInterpolationOffsetBits, 6);
|
||||
|
||||
VkPhysicalDeviceProperties properties{};
|
||||
// A common Vulkan limit pair: max is one representable 4-bit step below 0.5.
|
||||
properties.limits.minInterpolationOffset = -0.5f;
|
||||
properties.limits.maxInterpolationOffset = 0.4375f;
|
||||
properties.limits.subPixelInterpolationOffsetBits = 4;
|
||||
MG_External::VulkanCapabilities vkCaps;
|
||||
MG_Util::BackendLoader::FillInVulkanCapabilities(vkCaps, properties);
|
||||
EXPECT_FLOAT_EQ(vkCaps.MinFragmentInterpolationOffset, -0.5f);
|
||||
EXPECT_FLOAT_EQ(vkCaps.MaxFragmentInterpolationOffset, 0.4375f);
|
||||
EXPECT_EQ(vkCaps.FragmentInterpolationOffsetBits, 4);
|
||||
|
||||
vkCaps.MinFragmentInterpolationOffset = -0.875f;
|
||||
vkCaps.MaxFragmentInterpolationOffset = 0.75f;
|
||||
vkCaps.FragmentInterpolationOffsetBits = 7;
|
||||
MG_Backend::DirectVulkan::BackendObject_DirectVulkan vkBackend;
|
||||
vkBackend.ApplyVulkanCapabilitiesForTesting(vkCaps);
|
||||
EXPECT_FLOAT_EQ(vkBackend.GetDynamicParameters().MinFragmentInterpolationOffset, -0.875f);
|
||||
EXPECT_FLOAT_EQ(vkBackend.GetDynamicParameters().MaxFragmentInterpolationOffset, 0.75f);
|
||||
EXPECT_EQ(vkBackend.GetDynamicParameters().FragmentInterpolationOffsetBits, 7);
|
||||
|
||||
// Invalid/zero host data cannot under-advertise the OpenGL 4 minimums.
|
||||
MG_External::VulkanCapabilities invalidCaps;
|
||||
invalidCaps.MinFragmentInterpolationOffset = 0.0f;
|
||||
invalidCaps.MaxFragmentInterpolationOffset = 0.0f;
|
||||
invalidCaps.FragmentInterpolationOffsetBits = 0;
|
||||
vkBackend.ApplyVulkanCapabilitiesForTesting(invalidCaps);
|
||||
EXPECT_LE(vkBackend.GetDynamicParameters().MinFragmentInterpolationOffset, -0.5f);
|
||||
EXPECT_FLOAT_EQ(vkBackend.GetDynamicParameters().MaxFragmentInterpolationOffset, 0.4375f);
|
||||
EXPECT_EQ(vkBackend.GetDynamicParameters().FragmentInterpolationOffsetBits, 4);
|
||||
}
|
||||
|
||||
TEST(DirectVulkanSanity, AdvertisesSubgroupOnlyWhenVulkanReportsUsableSupport) {
|
||||
using namespace MobileGL;
|
||||
|
||||
@@ -638,6 +682,48 @@ TEST(GetterSanity, ClampsMaxVertexAttribsToCurrentValueStorageCapacity) {
|
||||
MG_State::pGLContext.reset();
|
||||
}
|
||||
|
||||
TEST(GetterSanity, ReportsFragmentInterpolationLimitsForFloatAndIntegerQueries) {
|
||||
using namespace MobileGL;
|
||||
|
||||
auto previousContext = Move(MG_State::pGLContext);
|
||||
auto previousBackend = Move(MG_Backend::pActiveBackendObject);
|
||||
MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>();
|
||||
|
||||
MG_Backend::DynamicBackendParameters params;
|
||||
params.MinFragmentInterpolationOffset = -0.75f;
|
||||
params.MaxFragmentInterpolationOffset = 0.4375f;
|
||||
params.FragmentInterpolationOffsetBits = 6;
|
||||
MG_Backend::pActiveBackendObject = MakeUnique<DynamicParameterBackend>(params);
|
||||
|
||||
GLfloat floatValue = 0.0f;
|
||||
MG_Impl::GLImpl::GetFloatv(GL_MIN_FRAGMENT_INTERPOLATION_OFFSET, &floatValue);
|
||||
EXPECT_FLOAT_EQ(floatValue, -0.75f);
|
||||
MG_Impl::GLImpl::GetFloatv(GL_MAX_FRAGMENT_INTERPOLATION_OFFSET, &floatValue);
|
||||
EXPECT_FLOAT_EQ(floatValue, 0.4375f);
|
||||
MG_Impl::GLImpl::GetFloatv(GL_FRAGMENT_INTERPOLATION_OFFSET_BITS, &floatValue);
|
||||
EXPECT_FLOAT_EQ(floatValue, 6.0f);
|
||||
|
||||
GLint intValue = 0;
|
||||
MG_Impl::GLImpl::GetIntegerv(GL_MIN_FRAGMENT_INTERPOLATION_OFFSET, &intValue);
|
||||
EXPECT_EQ(intValue, -1);
|
||||
MG_Impl::GLImpl::GetIntegerv(GL_MAX_FRAGMENT_INTERPOLATION_OFFSET, &intValue);
|
||||
EXPECT_EQ(intValue, 0);
|
||||
MG_Impl::GLImpl::GetIntegerv(GL_FRAGMENT_INTERPOLATION_OFFSET_BITS, &intValue);
|
||||
EXPECT_EQ(intValue, 6);
|
||||
|
||||
GLboolean boolValue = GL_FALSE;
|
||||
MG_Impl::GLImpl::GetBooleanv(GL_MIN_FRAGMENT_INTERPOLATION_OFFSET, &boolValue);
|
||||
EXPECT_EQ(boolValue, GL_TRUE);
|
||||
MG_Impl::GLImpl::GetBooleanv(GL_MAX_FRAGMENT_INTERPOLATION_OFFSET, &boolValue);
|
||||
EXPECT_EQ(boolValue, GL_TRUE);
|
||||
MG_Impl::GLImpl::GetBooleanv(GL_FRAGMENT_INTERPOLATION_OFFSET_BITS, &boolValue);
|
||||
EXPECT_EQ(boolValue, GL_TRUE);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
|
||||
MG_Backend::pActiveBackendObject = Move(previousBackend);
|
||||
MG_State::pGLContext = Move(previousContext);
|
||||
}
|
||||
|
||||
TEST(GetterSanity, PerStageImageUniformQueriesMatchShaderCompilerLimits) {
|
||||
using namespace MobileGL;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "Loader.h"
|
||||
#include "MG_Util/Types.h"
|
||||
#include <Config.h>
|
||||
#include <cmath>
|
||||
#if defined(_WIN32)
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN 1
|
||||
@@ -838,8 +839,14 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
if (std::strcmp(extension, "GL_NV_shader_noperspective_interpolation") == 0) {
|
||||
caps.SupportsNoperspectiveInterpolation = true;
|
||||
}
|
||||
if (std::strcmp(extension, "GL_OES_shader_multisample_interpolation") == 0) {
|
||||
caps.SupportsShaderMultisampleInterpolation = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
caps.SupportsShaderMultisampleInterpolation =
|
||||
caps.SupportsShaderMultisampleInterpolation || caps.GLESVersion.Major > 3 ||
|
||||
(caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 2);
|
||||
|
||||
// Detect optional raster/color-mask entry points by whether they loaded. glColorMaski is GLES
|
||||
// 3.2 core (no extension string), so pointer presence is the reliable signal for all of these.
|
||||
@@ -900,6 +907,9 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
GLint maxColorAttachments = 8;
|
||||
GLint maxClipDistances = 8;
|
||||
GLint maxViewports = 16;
|
||||
GLfloat minFragmentInterpolationOffset = -0.5f;
|
||||
GLfloat maxFragmentInterpolationOffset = 0.4375f;
|
||||
GLint fragmentInterpolationOffsetBits = 4;
|
||||
glesFuncs.glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, aliasedLineWidthRange);
|
||||
glesFuncs.glGetFloatv(GL_SMOOTH_LINE_WIDTH_RANGE, smoothLineWidthRange);
|
||||
glesFuncs.glGetFloatv(GL_SMOOTH_LINE_WIDTH_GRANULARITY, &smoothLineWidthGranularity);
|
||||
@@ -950,6 +960,29 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
glesFuncs.glGetIntegerv(GL_MAX_VIEWPORTS, &maxViewports);
|
||||
glesFuncs.glGetIntegerv(GL_MAX_VIEWPORT_DIMS, maxViewportDims);
|
||||
glesFuncs.glGetIntegerv(GL_VIEWPORT_SUBPIXEL_BITS, &viewportSubpixelBits);
|
||||
if (caps.SupportsShaderMultisampleInterpolation && glesFuncs.glGetFloatv) {
|
||||
const auto drainErrors = [&glesFuncs]() {
|
||||
Bool hadError = false;
|
||||
if (glesFuncs.glGetError) {
|
||||
while (glesFuncs.glGetError() != GL_NO_ERROR) hadError = true;
|
||||
}
|
||||
return hadError;
|
||||
};
|
||||
|
||||
// Isolate these optional queries from errors raised by preceding capability
|
||||
// probes, then consume any query error so initialization never leaks it into
|
||||
// the application's first glGetError call.
|
||||
drainErrors();
|
||||
glesFuncs.glGetFloatv(GL_MIN_FRAGMENT_INTERPOLATION_OFFSET, &minFragmentInterpolationOffset);
|
||||
glesFuncs.glGetFloatv(GL_MAX_FRAGMENT_INTERPOLATION_OFFSET, &maxFragmentInterpolationOffset);
|
||||
glesFuncs.glGetIntegerv(GL_FRAGMENT_INTERPOLATION_OFFSET_BITS, &fragmentInterpolationOffsetBits);
|
||||
if (drainErrors()) {
|
||||
MGLOG_W("Fragment interpolation limit query failed; using OpenGL minimums");
|
||||
minFragmentInterpolationOffset = -0.5f;
|
||||
maxFragmentInterpolationOffset = 0.4375f;
|
||||
fragmentInterpolationOffsetBits = 4;
|
||||
}
|
||||
}
|
||||
// Only legal to query once the extension has been seen in the loop above, hence not batched
|
||||
// with the unconditional probes: on a driver without it this raises GL_INVALID_ENUM.
|
||||
if (caps.SupportsTextureFilterAnisotropy) {
|
||||
@@ -1007,6 +1040,19 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
caps.ViewportBoundsRangeMin = viewportBoundsRange[0];
|
||||
caps.ViewportBoundsRangeMax = viewportBoundsRange[1];
|
||||
caps.ViewportSubpixelBits = viewportSubpixelBits;
|
||||
caps.MinFragmentInterpolationOffset =
|
||||
std::isfinite(minFragmentInterpolationOffset) && minFragmentInterpolationOffset <= -0.5f
|
||||
? minFragmentInterpolationOffset
|
||||
: -0.5f;
|
||||
caps.MaxFragmentInterpolationOffset = 0.4375f;
|
||||
caps.FragmentInterpolationOffsetBits = 4;
|
||||
if (fragmentInterpolationOffsetBits >= 4 && std::isfinite(maxFragmentInterpolationOffset)) {
|
||||
const Float requiredMaxOffset = 0.5f - std::ldexp(1.0f, -fragmentInterpolationOffsetBits);
|
||||
if (maxFragmentInterpolationOffset >= requiredMaxOffset) {
|
||||
caps.MaxFragmentInterpolationOffset = maxFragmentInterpolationOffset;
|
||||
caps.FragmentInterpolationOffsetBits = fragmentInterpolationOffsetBits;
|
||||
}
|
||||
}
|
||||
MGLOG_I(" GL_ALIASED_LINE_WIDTH_RANGE: [%.3f, %.3f]", caps.AliasedLineWidthRangeMin,
|
||||
caps.AliasedLineWidthRangeMax);
|
||||
MGLOG_I(" GL_SMOOTH_LINE_WIDTH_RANGE: [%.3f, %.3f]", caps.SmoothLineWidthRangeMin,
|
||||
|
||||
@@ -1055,6 +1055,9 @@ namespace MobileGL {
|
||||
// SPIRV-Cross's `#extension ... : require` would fail to compile and MobileGL falls back
|
||||
// to stripping the NoPerspective decoration (smooth interpolation) via StripNoPerspectivePass.
|
||||
Bool SupportsNoperspectiveInterpolation = false;
|
||||
// GLES 3.2 core or GL_OES_shader_multisample_interpolation exposes
|
||||
// interpolateAtOffset and the three fragment-offset limit queries.
|
||||
Bool SupportsShaderMultisampleInterpolation = false;
|
||||
// GL_RENDERER contains "ANGLE".
|
||||
Bool IsAngleRenderer = false;
|
||||
// GL_RENDERER contains both "ANGLE" and "llvmpipe".
|
||||
@@ -1120,6 +1123,9 @@ namespace MobileGL {
|
||||
Float ViewportBoundsRangeMin = 0.0f;
|
||||
Float ViewportBoundsRangeMax = 0.0f;
|
||||
Int ViewportSubpixelBits = 0;
|
||||
Float MinFragmentInterpolationOffset = -0.5f;
|
||||
Float MaxFragmentInterpolationOffset = 0.4375f;
|
||||
Int FragmentInterpolationOffsetBits = 4;
|
||||
};
|
||||
} // namespace MG_External
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "Loader.h"
|
||||
|
||||
#include <Config.h>
|
||||
#include <cmath>
|
||||
|
||||
namespace MobileGL::MG_Util::BackendLoader {
|
||||
namespace {
|
||||
@@ -40,6 +41,25 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
return MaxSampleCountFromFlags(commonFlags);
|
||||
}
|
||||
|
||||
void FillFragmentInterpolationLimits(MG_External::VulkanCapabilities& caps,
|
||||
const VkPhysicalDeviceLimits& limits) {
|
||||
caps.MinFragmentInterpolationOffset =
|
||||
std::isfinite(limits.minInterpolationOffset) && limits.minInterpolationOffset <= -0.5f
|
||||
? limits.minInterpolationOffset
|
||||
: -0.5f;
|
||||
|
||||
caps.MaxFragmentInterpolationOffset = 0.4375f;
|
||||
caps.FragmentInterpolationOffsetBits = 4;
|
||||
const Int bits = static_cast<Int>(limits.subPixelInterpolationOffsetBits);
|
||||
if (bits >= 4 && std::isfinite(limits.maxInterpolationOffset)) {
|
||||
const Float requiredMaxOffset = 0.5f - std::ldexp(1.0f, -bits);
|
||||
if (limits.maxInterpolationOffset >= requiredMaxOffset) {
|
||||
caps.MaxFragmentInterpolationOffset = limits.maxInterpolationOffset;
|
||||
caps.FragmentInterpolationOffsetBits = bits;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VulkanDynamicFunctions LoadVulkanDynamicFunctions(VkInstance instance) {
|
||||
VulkanDynamicFunctions loaded{};
|
||||
if (instance == VK_NULL_HANDLE) {
|
||||
@@ -171,6 +191,7 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
caps.ViewportBoundsRangeMin = p.limits.viewportBoundsRange[0];
|
||||
caps.ViewportBoundsRangeMax = p.limits.viewportBoundsRange[1];
|
||||
caps.ViewportSubpixelBits = static_cast<Int>(p.limits.viewportSubPixelBits);
|
||||
FillFragmentInterpolationLimits(caps, p.limits);
|
||||
|
||||
VkPhysicalDeviceFeatures supportedFeatures{};
|
||||
vkGetPhysicalDeviceFeatures(physicalDevice, &supportedFeatures);
|
||||
@@ -261,6 +282,7 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
caps.ViewportBoundsRangeMin = properties.limits.viewportBoundsRange[0];
|
||||
caps.ViewportBoundsRangeMax = properties.limits.viewportBoundsRange[1];
|
||||
caps.ViewportSubpixelBits = static_cast<Int>(properties.limits.viewportSubPixelBits);
|
||||
FillFragmentInterpolationLimits(caps, properties.limits);
|
||||
caps.SupportsWideLines = false;
|
||||
// This helper only receives properties, not VkPhysicalDeviceFeatures. Leave optional
|
||||
// stage writes disabled rather than inferring them from descriptor limits alone.
|
||||
|
||||
@@ -68,6 +68,9 @@ namespace MobileGL {
|
||||
Float ViewportBoundsRangeMin = 0.0f;
|
||||
Float ViewportBoundsRangeMax = 0.0f;
|
||||
Int ViewportSubpixelBits = 0;
|
||||
Float MinFragmentInterpolationOffset = -0.5f;
|
||||
Float MaxFragmentInterpolationOffset = 0.4375f;
|
||||
Int FragmentInterpolationOffsetBits = 4;
|
||||
Bool SupportsWideLines = false;
|
||||
// Storage-image descriptors are limited per stage by
|
||||
// maxPerStageDescriptorStorageImages, but writes/atomics outside compute additionally
|
||||
|
||||
Reference in New Issue
Block a user