diff --git a/MobileGL/MG_Backend/BackendObject.h b/MobileGL/MG_Backend/BackendObject.h index 59642841..e6037847 100644 --- a/MobileGL/MG_Backend/BackendObject.h +++ b/MobileGL/MG_Backend/BackendObject.h @@ -384,6 +384,20 @@ namespace MobileGL { // hand glslang a workable gl_MaxClipDistances. Int MaxClipDistances = 8; Int MaxViewports = 16; + // GL_LAYER_PROVOKING_VERTEX / GL_VIEWPORT_INDEX_PROVOKING_VERTEX: which vertex of a + // primitive supplies gl_Layer and gl_ViewportIndex. GL 4.6 table 23.65 makes + // GL_UNDEFINED_VERTEX a legal answer for both, and it is the honest default - naming + // a convention is a statement about behaviour, so a backend that does not pin one + // must not claim it does. DirectGLES fills the layer one from the ES 3.2 query and + // the viewport one from GL_OES_viewport_array, and leaves UNDEFINED where the + // capability is absent: without the viewport array extension only viewport 0 is ever + // rasterized, so no convention selects anything. DirectVulkan keeps UNDEFINED for + // both - which vertex provokes is decided per pipeline by + // VulkanRenderer::SelectProvokingVertexMode out of VK_EXT_provoking_vertex, + // provokingVertexModePerPipeline and the topology, so no single convention is true + // of the backend. + GLenum LayerProvokingVertex = GL_UNDEFINED_VERTEX; + GLenum ViewportIndexProvokingVertex = GL_UNDEFINED_VERTEX; Int MaxViewportWidth = 16384; Int MaxViewportHeight = 16384; Float ViewportBoundsRangeMin = 0.0f; diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index 36f450f5..3195b9f4 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -1336,6 +1336,13 @@ namespace MobileGL::MG_Backend::DirectGLES { m_dynamicParameters.MaxColorAttachments = m_GLESCapabilities.MaxColorAttachments; m_dynamicParameters.MaxClipDistances = m_GLESCapabilities.MaxClipDistances; m_dynamicParameters.MaxViewports = m_GLESCapabilities.MaxViewports; + // Whatever the driver said about which vertex supplies gl_Layer, and GL_UNDEFINED_VERTEX + // for gl_ViewportIndex on every driver without GL_OES_viewport_array - which is both test + // devices. That is not a shortfall being hidden: without the extension only viewport 0 is + // ever rasterized, so no vertex "selects" a viewport index and naming a convention would + // describe behaviour this backend does not implement. + m_dynamicParameters.LayerProvokingVertex = m_GLESCapabilities.LayerProvokingVertex; + m_dynamicParameters.ViewportIndexProvokingVertex = m_GLESCapabilities.ViewportIndexProvokingVertex; m_dynamicParameters.MaxViewportWidth = m_GLESCapabilities.MaxViewportWidth; m_dynamicParameters.MaxViewportHeight = m_GLESCapabilities.MaxViewportHeight; m_dynamicParameters.ViewportBoundsRangeMin = m_GLESCapabilities.ViewportBoundsRangeMin; diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp index 7c32172b..855d3aaf 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp @@ -912,6 +912,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_dynamicParameters.MaxClipDistances = m_vulkanCaps.SupportsShaderClipDistance ? std::max(m_vulkanCaps.MaxClipDistances, 0) : 0; m_dynamicParameters.MaxViewports = m_vulkanCaps.MaxViewports; + // Assigned explicitly rather than left to the struct's defaults, like every other + // parameter here, so a second fill cannot inherit a stale value. GL_UNDEFINED_VERTEX is + // the truthful answer for DirectVulkan and a legal one (GL 4.6 table 23.65): which vertex + // provokes is chosen per pipeline by VulkanRenderer::SelectProvokingVertexMode out of + // VK_EXT_provoking_vertex, provokingVertexModePerPipeline and the topology, so there is no + // one convention to name. Vulkan's own default is FIRST, which is the opposite of the + // GL_LAST_VERTEX_CONVENTION this used to claim unconditionally. + m_dynamicParameters.LayerProvokingVertex = GL_UNDEFINED_VERTEX; + m_dynamicParameters.ViewportIndexProvokingVertex = GL_UNDEFINED_VERTEX; m_dynamicParameters.MaxViewportWidth = m_vulkanCaps.MaxViewportWidth; m_dynamicParameters.MaxViewportHeight = m_vulkanCaps.MaxViewportHeight; m_dynamicParameters.ViewportBoundsRangeMin = m_vulkanCaps.ViewportBoundsRangeMin; diff --git a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp index 7f0ac27f..dd4de29f 100644 --- a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp +++ b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp @@ -1572,9 +1572,6 @@ namespace MobileGL::MG_Impl::GLImpl { case GL_LINE_WIDTH: *params = static_cast(MG_State::pGLContext->GetLineWidth()); return; - case GL_LAYER_PROVOKING_VERTEX: - *params = GL_LAST_VERTEX_CONVENTION; - return; case GL_LOGIC_OP_MODE: *params = static_cast(MG_Util::ConvertLogicOperationToGLEnum(MG_State::pGLContext->GetLogicOp())); return; @@ -2117,9 +2114,6 @@ namespace MobileGL::MG_Impl::GLImpl { params[3] = vp.w(); return; } - case GL_VIEWPORT_INDEX_PROVOKING_VERTEX: - *params = GL_LAST_VERTEX_CONVENTION; - return; case GL_MAX_ELEMENT_INDEX: *params = 1024 * 1024; // TODO return; @@ -2207,6 +2201,20 @@ namespace MobileGL::MG_Impl::GLImpl { case GL_MAX_CLIP_DISTANCES: *params = dynamicParameters.MaxClipDistances; break; + // Both were a hard-coded GL_LAST_VERTEX_CONVENTION, derived from nothing. GL 4.6 table + // 23.65 permits GL_UNDEFINED_VERTEX for either, and that is what the backends report + // wherever they do not actually pin a convention - claiming one is a statement about + // which vertex of a primitive supplies gl_Layer / gl_ViewportIndex, and DirectGLES + // rasterizes only viewport 0 on a driver without GL_OES_viewport_array while + // DirectVulkan picks its provoking mode per pipeline. KHR-GLxx.viewport_array.query + // accepts all four values, and .provoking_vertex - which failed on both devices, in + // OPPOSITE directions - stops verifying as soon as either answer is undefined. + case GL_LAYER_PROVOKING_VERTEX: + *params = static_cast(dynamicParameters.LayerProvokingVertex); + break; + case GL_VIEWPORT_INDEX_PROVOKING_VERTEX: + *params = static_cast(dynamicParameters.ViewportIndexProvokingVertex); + break; case GL_MAX_COLOR_TEXTURE_SAMPLES: *params = std::max(dynamicParameters.MaxColorTextureSamples, GetAdvertisedMaxSamples()); break; diff --git a/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp b/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp index 63ba1755..4b35266d 100644 --- a/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp +++ b/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp @@ -57,6 +57,12 @@ namespace { GLint maxViewports = 32; GLint viewportSubpixelBits = 8; bool viewportArrayLimitsQueried = false; + // GL_LAYER_PROVOKING_VERTEX is ES 3.2 core; GL_VIEWPORT_INDEX_PROVOKING_VERTEX comes with + // GL_OES_viewport_array. Both must go unasked where they do not exist, and a driver answer + // outside the four legal conventions must not be forwarded as one. + GLint layerProvokingVertex = GL_FIRST_VERTEX_CONVENTION; + GLint viewportIndexProvokingVertex = GL_LAST_VERTEX_CONVENTION; + bool layerProvokingVertexQueried = false; // A driver rejecting one of the UNCONDITIONAL probes. GL_SMOOTH_LINE_WIDTH_RANGE is the // realistic one - it is desktop-only state that every GLES driver refuses - and it stands // in for the whole run: whatever it leaves behind must not reach the application. @@ -195,6 +201,14 @@ namespace { g_fake.viewportArrayLimitsQueried = true; *data = g_fake.viewportSubpixelBits; break; + case GL_VIEWPORT_INDEX_PROVOKING_VERTEX: + g_fake.viewportArrayLimitsQueried = true; + *data = g_fake.viewportIndexProvokingVertex; + break; + case GL_LAYER_PROVOKING_VERTEX: + g_fake.layerProvokingVertexQueried = true; + *data = g_fake.layerProvokingVertex; + break; case GL_MAX_COLOR_TEXTURE_SAMPLES: case GL_MAX_DEPTH_TEXTURE_SAMPLES: case GL_MAX_FRAMEBUFFER_SAMPLES: @@ -782,6 +796,62 @@ TEST(ViewportArrayCapabilities, TheLimitsAreOnlyAskedForWhenTheExtensionIsPresen EXPECT_EQ(withCaps.ViewportSubpixelBits, g_fake.viewportSubpixelBits); } +// GL_LAYER_PROVOKING_VERTEX and GL_VIEWPORT_INDEX_PROVOKING_VERTEX name which vertex of a +// primitive supplies gl_Layer and gl_ViewportIndex. MobileGL used to answer a hard-coded +// GL_LAST_VERTEX_CONVENTION for both, derived from nothing, and got it wrong on both test devices +// in OPPOSITE directions. GL_UNDEFINED_VERTEX is a legal answer (GL 4.6 table 23.65) and it is +// the honest one wherever the capability that would give the convention meaning is absent. +TEST(ProvokingVertexConventions, AreTakenFromTheDriverOnlyWhereThePnameExists) { + const auto funcs = MakeFakeGLESFunctions(); + + // ES 3.1, no viewport array: neither pname exists, so neither is asked for. + ResetFakeDriver(); + g_fake.maxVertexSsboBlocks = 0; + MobileGL::MG_External::GLESCapabilities es31Caps; + ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(es31Caps, funcs)); + EXPECT_FALSE(g_fake.layerProvokingVertexQueried); + EXPECT_EQ(es31Caps.LayerProvokingVertex, static_cast(GL_UNDEFINED_VERTEX)); + EXPECT_EQ(es31Caps.ViewportIndexProvokingVertex, static_cast(GL_UNDEFINED_VERTEX)); + + // ES 3.2 with the viewport array: both exist and both driver answers come through verbatim. + ResetFakeDriver(); + g_fake.maxVertexSsboBlocks = 0; + g_fake.glesMinorVersion = 2; + g_fake.extensions.emplace_back("GL_OES_viewport_array"); + MobileGL::MG_External::GLESCapabilities es32Caps; + ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(es32Caps, funcs)); + EXPECT_TRUE(g_fake.layerProvokingVertexQueried); + EXPECT_EQ(es32Caps.LayerProvokingVertex, static_cast(GL_FIRST_VERTEX_CONVENTION)); + EXPECT_EQ(es32Caps.ViewportIndexProvokingVertex, static_cast(GL_LAST_VERTEX_CONVENTION)); + + // ES 3.2 WITHOUT the viewport array - the shape of both test devices. The layer convention is + // real and comes from the driver; the viewport-index one describes a selection that never + // happens, because only viewport 0 is ever rasterized, and stays undefined. + ResetFakeDriver(); + g_fake.maxVertexSsboBlocks = 0; + g_fake.glesMinorVersion = 2; + MobileGL::MG_External::GLESCapabilities deviceLikeCaps; + ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(deviceLikeCaps, funcs)); + EXPECT_EQ(deviceLikeCaps.LayerProvokingVertex, static_cast(GL_FIRST_VERTEX_CONVENTION)); + EXPECT_EQ(deviceLikeCaps.ViewportIndexProvokingVertex, static_cast(GL_UNDEFINED_VERTEX)); +} + +// A driver answering something that is not one of the four legal conventions must not have it +// forwarded as one: GL_UNDEFINED_VERTEX describes "MobileGL cannot tell you" exactly. +TEST(ProvokingVertexConventions, AnIllegalDriverAnswerBecomesUndefined) { + ResetFakeDriver(); + g_fake.maxVertexSsboBlocks = 0; + g_fake.glesMinorVersion = 2; + g_fake.layerProvokingVertex = 0x1234; + const auto funcs = MakeFakeGLESFunctions(); + + MobileGL::MG_External::GLESCapabilities caps; + ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(caps, funcs)); + + EXPECT_TRUE(g_fake.layerProvokingVertexQueried); + EXPECT_EQ(caps.LayerProvokingVertex, static_cast(GL_UNDEFINED_VERTEX)); +} + // The multisample ceilings are ES 3.1 state; a driver that answers zero - or an older context // that answers nothing - must not have that reach GL_Getter, which would then reject the sample // count it just advertised. diff --git a/MobileGL/MG_Test/SanityTest.cpp b/MobileGL/MG_Test/SanityTest.cpp index beea5f01..c0ea526e 100644 --- a/MobileGL/MG_Test/SanityTest.cpp +++ b/MobileGL/MG_Test/SanityTest.cpp @@ -665,6 +665,33 @@ TEST(DirectVulkanSanity, GatesClipDistancesOnTheShaderClipDistanceFeature) { EXPECT_EQ(backend.GetDynamicParameters().MaxClipDistances, 8); } +// GL_LAYER_PROVOKING_VERTEX / GL_VIEWPORT_INDEX_PROVOKING_VERTEX were a hard-coded +// GL_LAST_VERTEX_CONVENTION for both backends, derived from nothing, and wrong on both test +// devices in opposite directions. DirectGLES now forwards what its loader resolved; DirectVulkan +// reports GL_UNDEFINED_VERTEX, which GL 4.6 table 23.65 permits and which is what the backend +// honestly implements - the provoking mode is chosen per pipeline out of VK_EXT_provoking_vertex, +// provokingVertexModePerPipeline and the topology. +TEST(ProvokingVertexConventions, EachBackendReportsWhatItActuallyPins) { + using namespace MobileGL; + + MG_Backend::DirectGLES::BackendObject_DirectGLES glesBackend; + MG_External::GLESCapabilities glesCaps; + glesCaps.LayerProvokingVertex = GL_FIRST_VERTEX_CONVENTION; + glesCaps.ViewportIndexProvokingVertex = GL_UNDEFINED_VERTEX; + glesBackend.ApplyGLESCapabilitiesForTesting(glesCaps); + EXPECT_EQ(glesBackend.GetDynamicParameters().LayerProvokingVertex, + static_cast(GL_FIRST_VERTEX_CONVENTION)); + EXPECT_EQ(glesBackend.GetDynamicParameters().ViewportIndexProvokingVertex, + static_cast(GL_UNDEFINED_VERTEX)); + + MG_Backend::DirectVulkan::BackendObject_DirectVulkan vkBackend; + MG_External::VulkanCapabilities vkCaps; + vkBackend.ApplyVulkanCapabilitiesForTesting(vkCaps); + EXPECT_EQ(vkBackend.GetDynamicParameters().LayerProvokingVertex, static_cast(GL_UNDEFINED_VERTEX)); + EXPECT_EQ(vkBackend.GetDynamicParameters().ViewportIndexProvokingVertex, + static_cast(GL_UNDEFINED_VERTEX)); +} + TEST(FragmentInterpolationCapabilities, PlumbsGLESAndBothVulkanPropertyPaths) { using namespace MobileGL; diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp index 38cd1d24..e4d3a4b7 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp @@ -831,6 +831,35 @@ namespace MobileGL::MG_Util::BackendLoader { return includesBase; } + // GL 4.6 table 23.65 admits exactly four answers for GL_LAYER_PROVOKING_VERTEX and + // GL_VIEWPORT_INDEX_PROVOKING_VERTEX. Anything else means the driver wrote something MobileGL + // cannot forward as a convention, and GL_UNDEFINED_VERTEX - a legal answer, not a placeholder + // - is the accurate thing to say about it. + static GLenum NormalizeProvokingVertexConvention(GLint driverValue) { + switch (static_cast(driverValue)) { + case GL_FIRST_VERTEX_CONVENTION: + case GL_LAST_VERTEX_CONVENTION: + case GL_PROVOKING_VERTEX: + case GL_UNDEFINED_VERTEX: + return static_cast(driverValue); + default: + return GL_UNDEFINED_VERTEX; + } + } + + static const char* ProvokingVertexConventionName(GLenum convention) { + switch (convention) { + case GL_FIRST_VERTEX_CONVENTION: + return "GL_FIRST_VERTEX_CONVENTION"; + case GL_LAST_VERTEX_CONVENTION: + return "GL_LAST_VERTEX_CONVENTION"; + case GL_PROVOKING_VERTEX: + return "GL_PROVOKING_VERTEX"; + default: + return "GL_UNDEFINED_VERTEX"; + } + } + Bool FillInGLESCapabilities(MG_External::GLESCapabilities& caps, const MG_External::GLESFunctionsTable& glesFuncs) { if (!glesFuncs.glGetString || !glesFuncs.glGetIntegerv) { MGLOG_E("Required GLES functions are not loaded, cannot query capabilities"); @@ -1073,6 +1102,11 @@ namespace MobileGL::MG_Util::BackendLoader { // clipping program silently rendered nothing. The guarded probe below only ever widens it. GLint maxClipDistances = 0; GLint maxViewports = 16; + // GL_UNDEFINED_VERTEX is what stands when the probes below cannot run, and it is a legal + // answer rather than a placeholder: with neither geometry shaders nor a viewport array + // there is no layered or multi-viewport draw for a convention to describe. + GLenum layerProvokingVertex = GL_UNDEFINED_VERTEX; + GLenum viewportIndexProvokingVertex = GL_UNDEFINED_VERTEX; GLfloat minFragmentInterpolationOffset = -0.5f; GLfloat maxFragmentInterpolationOffset = 0.4375f; GLint fragmentInterpolationOffsetBits = 4; @@ -1263,6 +1297,21 @@ namespace MobileGL::MG_Util::BackendLoader { } } glesFuncs.glGetIntegerv(GL_MAX_VIEWPORT_DIMS, maxViewportDims); + // GL_LAYER_PROVOKING_VERTEX is ES 3.2 core (it arrives with geometry shaders, which is + // what gl_Layer needs). Ask the driver where the pname exists rather than asserting a + // convention: it is a statement about which vertex of a primitive supplies gl_Layer, and + // MobileGL forwards the geometry stage to the driver rather than implementing the + // selection itself, so the driver's answer IS MobileGL's answer. Below ES 3.2 there are + // no layered draws to have a convention for and GL_UNDEFINED_VERTEX stands, which GL 4.6 + // table 23.65 explicitly permits. + if (esAtLeast32) { + GLint driverLayerConvention = static_cast(GL_UNDEFINED_VERTEX); + drainErrors(); + glesFuncs.glGetIntegerv(GL_LAYER_PROVOKING_VERTEX, &driverLayerConvention); + if (!drainErrors()) { + layerProvokingVertex = NormalizeProvokingVertexConvention(driverLayerConvention); + } + } // GL_MAX_VIEWPORTS (0x825B), GL_VIEWPORT_SUBPIXEL_BITS (0x825C) and GL_VIEWPORT_BOUNDS_RANGE // (0x825D) all arrive with GL_OES_viewport_array and exist nowhere in ES core, so on the // drivers DirectGLES actually runs on all three raise GL_INVALID_ENUM. The values MobileGL @@ -1272,9 +1321,11 @@ namespace MobileGL::MG_Util::BackendLoader { // hold), floors GL_SUBPIXEL_BITS at its own 4, and the bounds range is clamped to the core // minimum below. What changes is that the errors stop being manufactured. if (caps.SupportsViewportArray) { + GLint driverViewportIndexConvention = static_cast(GL_UNDEFINED_VERTEX); drainErrors(); glesFuncs.glGetIntegerv(GL_MAX_VIEWPORTS, &maxViewports); glesFuncs.glGetIntegerv(GL_VIEWPORT_SUBPIXEL_BITS, &viewportSubpixelBits); + glesFuncs.glGetIntegerv(GL_VIEWPORT_INDEX_PROVOKING_VERTEX, &driverViewportIndexConvention); if (glesFuncs.glGetFloatv) { glesFuncs.glGetFloatv(GL_VIEWPORT_BOUNDS_RANGE, viewportBoundsRange); } @@ -1285,6 +1336,9 @@ namespace MobileGL::MG_Util::BackendLoader { viewportSubpixelBits = 0; viewportBoundsRange[0] = -32768.0f; viewportBoundsRange[1] = 32767.0f; + } else { + viewportIndexProvokingVertex = + NormalizeProvokingVertexConvention(driverViewportIndexConvention); } } if (caps.SupportsShaderMultisampleInterpolation && glesFuncs.glGetFloatv) { @@ -1452,6 +1506,8 @@ namespace MobileGL::MG_Util::BackendLoader { // extension the probe above never ran at all - so the flag, not the local, decides. caps.MaxClipDistances = caps.SupportsClipDistance ? std::max(maxClipDistances, 0) : 0; caps.MaxViewports = maxViewports; + caps.LayerProvokingVertex = layerProvokingVertex; + caps.ViewportIndexProvokingVertex = viewportIndexProvokingVertex; caps.MaxViewportWidth = maxViewportDims[0]; caps.MaxViewportHeight = maxViewportDims[1]; // Only ever WIDER than the core minimum: a driver that answered the query is allowed to @@ -1543,6 +1599,9 @@ namespace MobileGL::MG_Util::BackendLoader { MGLOG_I(" GL_VIEWPORT_BOUNDS_RANGE: [%.3f, %.3f]", caps.ViewportBoundsRangeMin, caps.ViewportBoundsRangeMax); MGLOG_I(" GL_VIEWPORT_SUBPIXEL_BITS: %d", caps.ViewportSubpixelBits); + MGLOG_I(" GL_LAYER_PROVOKING_VERTEX: %s", ProvokingVertexConventionName(caps.LayerProvokingVertex)); + MGLOG_I(" GL_VIEWPORT_INDEX_PROVOKING_VERTEX: %s", + ProvokingVertexConventionName(caps.ViewportIndexProvokingVertex)); caps.IndirectDrawInstanceIdIncludesBaseInstance = ProbeIndirectInstanceIdIncludesBaseInstance(caps, glesFuncs); diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h index d495738c..e5c806a1 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h @@ -1280,6 +1280,11 @@ namespace MobileGL { // in FillInGLESCapabilities. Int MaxClipDistances = 0; Int MaxViewports = 16; + // GL_LAYER_PROVOKING_VERTEX (ES 3.2 core) and GL_VIEWPORT_INDEX_PROVOKING_VERTEX + // (GL_OES_viewport_array). GL_UNDEFINED_VERTEX is a legal answer for both and is what + // a driver that has neither is honestly saying. + GLenum LayerProvokingVertex = GL_UNDEFINED_VERTEX; + GLenum ViewportIndexProvokingVertex = GL_UNDEFINED_VERTEX; Int MaxViewportWidth = 16384; Int MaxViewportHeight = 16384; Float ViewportBoundsRangeMin = 0.0f;