diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp index c079678d..4a25b2aa 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp @@ -768,14 +768,48 @@ namespace MobileGL::MG_Backend::DirectVulkan { // the Uint32 attribute masks the draw path passes around are both bounded by MAX_VERTEX_ATTRIBS. m_dynamicParameters.MaxVertexAttribs = std::min( m_vulkanCaps.MaxVertexAttribs, static_cast(MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS)); - m_dynamicParameters.MaxComputeShaderStorageBlocks = m_vulkanCaps.MaxComputeShaderStorageBlocks; - m_dynamicParameters.MaxCombinedShaderStorageBlocks = m_vulkanCaps.MaxCombinedShaderStorageBlocks; - m_dynamicParameters.MaxComputeUniformBlocks = m_vulkanCaps.MaxComputeUniformBlocks; + // Vulkan descriptor limits are not GL limits, and a GL application reads an advertised + // limit as an amount it may actually USE. Adreno answers the per-stage/per-set descriptor + // queries at descriptor-indexing scale - the same driver whose + // GL_MAX_SHADER_STORAGE_BLOCK_SIZE is clamped from 2147483647 further down - so + // KHR-GL44.multi_bind.dispatch_bind_buffers_base read GL_MAX_COMPUTE_UNIFORM_BLOCKS, + // created that many buffers and spliced that many UBO declarations into a single compute + // shader: ~14 s of allocation, then death on std::bad_alloc. Its sibling + // dispatch_bind_buffers_range hard-codes 4 buffers and passes, which is the clean + // discriminator. Every ceiling below is far above what any desktop driver advertises for + // these (84-96 for the binding families) and far below a descriptor-indexing count, so it + // can only lower a limit that was never usable in the first place. The zero floor is not + // decoration: a driver reporting UINT32_MAX used to arrive here as -1. + const auto clampLimit = [](const char* name, Int reported, Int ceiling) { + const Int clamped = std::min(std::max(reported, 0), ceiling); + if (clamped != reported) { + MGLOG_I("DirectVulkan: clamped %s from %d to %d", name, reported, clamped); + } + return clamped; + }; + // GL 4.6 required minimums, for the record: MAX_COMPUTE_UNIFORM_BLOCKS 12, + // MAX_COMPUTE/COMBINED_SHADER_STORAGE_BLOCKS 8, MAX_SHADER_STORAGE_BUFFER_BINDINGS 8, + // MAX_UNIFORM_BUFFER_BINDINGS 84, MAX_TEXTURE_BUFFER_SIZE 65536. + constexpr Int kMaxAdvertisedBufferBlocks = 256; + constexpr Int kMaxAdvertisedTextureBufferSize = 1 << 27; // texels; what desktop GL reports + m_dynamicParameters.MaxComputeShaderStorageBlocks = + clampLimit("GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS", m_vulkanCaps.MaxComputeShaderStorageBlocks, + kMaxAdvertisedBufferBlocks); + m_dynamicParameters.MaxCombinedShaderStorageBlocks = + clampLimit("GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS", m_vulkanCaps.MaxCombinedShaderStorageBlocks, + kMaxAdvertisedBufferBlocks); + m_dynamicParameters.MaxComputeUniformBlocks = + clampLimit("GL_MAX_COMPUTE_UNIFORM_BLOCKS", m_vulkanCaps.MaxComputeUniformBlocks, + kMaxAdvertisedBufferBlocks); m_dynamicParameters.MaxComputeWorkGroupInvocations = m_vulkanCaps.MaxComputeWorkGroupInvocations; - m_dynamicParameters.MaxShaderStorageBufferBindings = m_vulkanCaps.MaxShaderStorageBufferBindings; - m_dynamicParameters.MaxTextureBufferSize = m_vulkanCaps.MaxTextureBufferSize; + m_dynamicParameters.MaxShaderStorageBufferBindings = + clampLimit("GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS", m_vulkanCaps.MaxShaderStorageBufferBindings, + kMaxAdvertisedBufferBlocks); + m_dynamicParameters.MaxTextureBufferSize = clampLimit( + "GL_MAX_TEXTURE_BUFFER_SIZE", m_vulkanCaps.MaxTextureBufferSize, kMaxAdvertisedTextureBufferSize); m_dynamicParameters.TextureBufferOffsetAlignment = m_vulkanCaps.TextureBufferOffsetAlignment; - m_dynamicParameters.MaxUniformBufferBindings = m_vulkanCaps.MaxUniformBufferBindings; + m_dynamicParameters.MaxUniformBufferBindings = clampLimit( + "GL_MAX_UNIFORM_BUFFER_BINDINGS", m_vulkanCaps.MaxUniformBufferBindings, kMaxAdvertisedBufferBlocks); m_dynamicParameters.MaxUniformBlockSize = m_vulkanCaps.MaxUniformBlockSize; m_dynamicParameters.MaxImageUnits = std::max(std::min(m_vulkanCaps.MaxImageUnits, maxSupportedTextureUnits), 0); m_dynamicParameters.MaxCombinedImageUniforms = std::max(m_vulkanCaps.MaxCombinedImageUniforms, 0); diff --git a/MobileGL/MG_IntegrationTest/CMakeLists.txt b/MobileGL/MG_IntegrationTest/CMakeLists.txt index 2112a071..371d0ad7 100644 --- a/MobileGL/MG_IntegrationTest/CMakeLists.txt +++ b/MobileGL/MG_IntegrationTest/CMakeLists.txt @@ -54,6 +54,7 @@ add_executable(MobileGLIntegrationTest Scenarios/XfbAfterClipDistanceScenario.cpp Scenarios/ThreeChannelAttachmentScenario.cpp Scenarios/PipelineFailureScenario.cpp + Scenarios/AdvertisedLimitsScenario.cpp ) target_include_directories(MobileGLIntegrationTest PRIVATE diff --git a/MobileGL/MG_IntegrationTest/Scenarios/AdvertisedLimitsScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/AdvertisedLimitsScenario.cpp new file mode 100644 index 00000000..2e74ffbd --- /dev/null +++ b/MobileGL/MG_IntegrationTest/Scenarios/AdvertisedLimitsScenario.cpp @@ -0,0 +1,120 @@ +// MobileGL - MobileGL/MG_IntegrationTest/Scenarios/AdvertisedLimitsScenario.cpp +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header +// +// "The limit we advertise is a promise, and an application will hold us to it." +// +// DirectVulkan copied Vulkan descriptor limits straight into the GL limit table. Those are not +// the same quantity: Adreno answers maxPerStageDescriptorUniformBuffers at descriptor-indexing +// scale, and GL_MAX_COMPUTE_UNIFORM_BLOCKS is a count an app will allocate. KHR-GL44.multi_bind +// .dispatch_bind_buffers_base does exactly that - createsO(limit) buffers and splices O(limit) +// UBO declarations into one compute shader - and spent ~14 s allocating before dying on +// std::bad_alloc. Its sibling dispatch_bind_buffers_range hard-codes 4 buffers and passes. +// +// Two failure modes, one table: +// - too LARGE: an unusable promise (the OOM above). +// - too SMALL or negative: a uint32 limit that lost its top bit on the way to a signed Int - +// UINT32_MAX arrived as -1, which every downstream std::min then accepted as "small enough". +// A conformant GL 4.x implementation may never advertise below the spec minimum either. +// +// Every bound below is checked on BOTH backends, because the loader casts are shared and the +// DirectGLES lane is the control: it takes its limits from a driver that already reports GL +// quantities, so an entry that only fails on DirectVulkan is a translation bug and one that +// fails on both is a table bug. + +#include +#include + +#include "../Harness/HeadlessGL.h" +#include "../Harness/ScenarioFixture.h" + +#ifdef GLAPI +#undef GLAPI +#endif +#define GL_GLEXT_PROTOTYPES +#include +#include +#undef GL_GLEXT_PROTOTYPES + +namespace MGITest { + namespace { + + struct LimitBound { + GLenum pname; + const char* name; + // The GL 4.x required minimum. A value below this is a conformance failure in its own + // right, and is what a sign-flipped uint32 looks like. + int minimum; + // The largest value this implementation is willing to promise. Chosen well above every + // desktop driver's answer, so it can only catch a descriptor-scale number. + int ceiling; + }; + + const std::vector& BufferLimitTable() { + static const std::vector table = { + {GL_MAX_UNIFORM_BUFFER_BINDINGS, "GL_MAX_UNIFORM_BUFFER_BINDINGS", 36, 256}, + {GL_MAX_COMPUTE_UNIFORM_BLOCKS, "GL_MAX_COMPUTE_UNIFORM_BLOCKS", 12, 256}, + {GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS, "GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS", 8, 256}, + {GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS, "GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS", 8, 256}, + {GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, "GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS", 8, 256}, + {GL_MAX_TEXTURE_BUFFER_SIZE, "GL_MAX_TEXTURE_BUFFER_SIZE", 65536, 1 << 27}, + {GL_MAX_UNIFORM_BLOCK_SIZE, "GL_MAX_UNIFORM_BLOCK_SIZE", 16384, 1 << 30}, + // Already clamped before this campaign; in the table so a regression there is + // caught by the same case. + {GL_MAX_SHADER_STORAGE_BLOCK_SIZE, "GL_MAX_SHADER_STORAGE_BLOCK_SIZE", 1 << 24, 512 * 1024 * 1024}, + {GL_MAX_TEXTURE_IMAGE_UNITS, "GL_MAX_TEXTURE_IMAGE_UNITS", 16, 32}, + {GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, "GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS", 48, 192}, + }; + return table; + } + + class AdvertisedLimitsScenario : public ScenarioTest {}; + + TEST_F(AdvertisedLimitsScenario, EveryBufferLimitIsWithinItsAdvertisedRange) { + for (const LimitBound& bound : BufferLimitTable()) { + GLint value = -424242; + glGetIntegerv(bound.pname, &value); + const unsigned int error = FirstGLError(); + EXPECT_EQ(error, GLenum(GL_NO_ERROR)) + << bound.name << " is not answerable: " << GLErrorName(error); + if (error != GL_NO_ERROR) continue; + + EXPECT_GE(value, bound.minimum) + << bound.name << " = " << value << " is below the GL required minimum " + << bound.minimum << " (a negative or tiny value here is a uint32 limit that lost " + "its top bit on the way to a signed Int)"; + EXPECT_LE(value, bound.ceiling) + << bound.name << " = " << value << " exceeds the ceiling " << bound.ceiling + << " this implementation is willing to promise - an application that allocates " + "what we advertise will run out of memory"; + } + } + + // The OOM case in isolation, because it is the one with a known CTS victim and the one a + // future refactor is most likely to reintroduce by copying the Vulkan limit back. + TEST_F(AdvertisedLimitsScenario, ComputeUniformBlocksIsAnAmountAnApplicationCouldActuallyAllocate) { + GLint blocks = -1; + glGetIntegerv(GL_MAX_COMPUTE_UNIFORM_BLOCKS, &blocks); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + EXPECT_GE(blocks, 12); + EXPECT_LE(blocks, 256) << "KHR-GL44.multi_bind.dispatch_bind_buffers_base creates one GL buffer " + "and one UBO declaration per advertised block"; + + GLint blockSize = -1; + glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &blockSize); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + EXPECT_GT(blockSize, 0); + // GL_MAX_COMBINED_COMPUTE_UNIFORM_COMPONENTS is derived from the product of these two, + // so their product has to stay representable. + EXPECT_LE(static_cast(blocks) * blockSize, + static_cast(2147483647)) + << "blocks(" << blocks << ") * blockSize(" << blockSize << ") overflows the GLint the " + "derived component limits are computed in"; + } + + } // namespace +} // namespace MGITest diff --git a/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp index f5579433..872d6775 100644 --- a/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp @@ -10,9 +10,20 @@ #include #include +#include namespace MobileGL::MG_Util::BackendLoader { namespace { + // A Vulkan limit is an unsigned 32-bit count; a GL limit is a signed Int. Drivers do report + // values with the top bit set (UINT32_MAX is the idiomatic "effectively unlimited"), and a + // plain static_cast turned those into small negatives - which every downstream std::min or + // ceiling comparison then accepted as "already small enough". Saturate instead, so a clamp + // above this can be trusted to be the only thing that lowers a limit. + Int SaturateToInt(Uint32 value) { + constexpr Uint32 kMaxInt = static_cast(std::numeric_limits::max()); + return static_cast(std::min(value, kMaxInt)); + } + struct VulkanDynamicFunctions { PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties = nullptr; PFN_vkGetPhysicalDeviceProperties2 vkGetPhysicalDeviceProperties2 = nullptr; @@ -152,47 +163,47 @@ namespace MobileGL::MG_Util::BackendLoader { caps.PointSizeRangeMin = p.limits.pointSizeRange[0]; caps.PointSizeRangeMax = p.limits.pointSizeRange[1]; caps.PointSizeGranularity = p.limits.pointSizeGranularity; - caps.Max3DTextureSize = static_cast(p.limits.maxImageDimension3D); - caps.MaxArrayTextureLayers = static_cast(p.limits.maxImageArrayLayers); - caps.MaxCubeMapTextureSize = static_cast(p.limits.maxImageDimensionCube); - caps.MaxFramebufferWidth = static_cast(p.limits.maxFramebufferWidth); - caps.MaxFramebufferHeight = static_cast(p.limits.maxFramebufferHeight); - caps.MaxFramebufferLayers = static_cast(p.limits.maxFramebufferLayers); + caps.Max3DTextureSize = SaturateToInt(p.limits.maxImageDimension3D); + caps.MaxArrayTextureLayers = SaturateToInt(p.limits.maxImageArrayLayers); + caps.MaxCubeMapTextureSize = SaturateToInt(p.limits.maxImageDimensionCube); + caps.MaxFramebufferWidth = SaturateToInt(p.limits.maxFramebufferWidth); + caps.MaxFramebufferHeight = SaturateToInt(p.limits.maxFramebufferHeight); + caps.MaxFramebufferLayers = SaturateToInt(p.limits.maxFramebufferLayers); caps.MaxRenderbufferSize = ResolveMaxRenderbufferSize(p.limits); - caps.MaxTextureSize = static_cast(p.limits.maxImageDimension2D); + caps.MaxTextureSize = SaturateToInt(p.limits.maxImageDimension2D); caps.MaxColorTextureSamples = MaxSampleCountFromFlags(p.limits.sampledImageColorSampleCounts); caps.MaxDepthTextureSamples = MaxSampleCountFromFlags(p.limits.sampledImageDepthSampleCounts); caps.MaxFramebufferSamples = ResolveConservativeFramebufferSampleLimit(p.limits); caps.MaxIntegerSamples = MaxSampleCountFromFlags(p.limits.sampledImageIntegerSampleCounts); caps.MaxSamples = caps.MaxFramebufferSamples; - caps.MaxSampleMaskWords = static_cast(p.limits.maxSampleMaskWords); - caps.MaxTextureImageUnits = static_cast(p.limits.maxPerStageDescriptorSampledImages); - caps.MaxVertexTextureImageUnits = static_cast(p.limits.maxPerStageDescriptorSampledImages); - caps.MaxComputeTextureImageUnits = static_cast(p.limits.maxPerStageDescriptorSampledImages); - caps.MaxCombinedTextureImageUnits = static_cast(p.limits.maxDescriptorSetSampledImages); - caps.MaxVertexAttribs = static_cast(p.limits.maxVertexInputAttributes); - caps.MaxComputeShaderStorageBlocks = static_cast(p.limits.maxPerStageDescriptorStorageBuffers); - caps.MaxCombinedShaderStorageBlocks = static_cast(p.limits.maxDescriptorSetStorageBuffers); - caps.MaxComputeUniformBlocks = static_cast(p.limits.maxPerStageDescriptorUniformBuffers); - caps.MaxComputeWorkGroupInvocations = static_cast(p.limits.maxComputeWorkGroupInvocations); - caps.MaxShaderStorageBufferBindings = static_cast(p.limits.maxDescriptorSetStorageBuffers); - caps.MaxTextureBufferSize = static_cast(p.limits.maxTexelBufferElements); + caps.MaxSampleMaskWords = SaturateToInt(p.limits.maxSampleMaskWords); + caps.MaxTextureImageUnits = SaturateToInt(p.limits.maxPerStageDescriptorSampledImages); + caps.MaxVertexTextureImageUnits = SaturateToInt(p.limits.maxPerStageDescriptorSampledImages); + caps.MaxComputeTextureImageUnits = SaturateToInt(p.limits.maxPerStageDescriptorSampledImages); + caps.MaxCombinedTextureImageUnits = SaturateToInt(p.limits.maxDescriptorSetSampledImages); + caps.MaxVertexAttribs = SaturateToInt(p.limits.maxVertexInputAttributes); + caps.MaxComputeShaderStorageBlocks = SaturateToInt(p.limits.maxPerStageDescriptorStorageBuffers); + caps.MaxCombinedShaderStorageBlocks = SaturateToInt(p.limits.maxDescriptorSetStorageBuffers); + caps.MaxComputeUniformBlocks = SaturateToInt(p.limits.maxPerStageDescriptorUniformBuffers); + caps.MaxComputeWorkGroupInvocations = SaturateToInt(p.limits.maxComputeWorkGroupInvocations); + caps.MaxShaderStorageBufferBindings = SaturateToInt(p.limits.maxDescriptorSetStorageBuffers); + caps.MaxTextureBufferSize = SaturateToInt(p.limits.maxTexelBufferElements); caps.TextureBufferOffsetAlignment = static_cast(std::max(1, p.limits.minTexelBufferOffsetAlignment)); - caps.MaxUniformBufferBindings = static_cast(p.limits.maxDescriptorSetUniformBuffers); - caps.MaxUniformBlockSize = static_cast(p.limits.maxUniformBufferRange); - caps.MaxImageUnits = static_cast(p.limits.maxPerStageDescriptorStorageImages); - caps.MaxCombinedImageUniforms = static_cast(p.limits.maxDescriptorSetStorageImages); - caps.MaxComputeImageUniforms = static_cast(p.limits.maxPerStageDescriptorStorageImages); - caps.MaxDrawBuffers = static_cast(p.limits.maxFragmentOutputAttachments); - caps.MaxColorAttachments = static_cast(p.limits.maxColorAttachments); - caps.MaxClipDistances = static_cast(p.limits.maxClipDistances); - caps.MaxViewports = static_cast(p.limits.maxViewports); - caps.MaxViewportWidth = static_cast(p.limits.maxViewportDimensions[0]); - caps.MaxViewportHeight = static_cast(p.limits.maxViewportDimensions[1]); + caps.MaxUniformBufferBindings = SaturateToInt(p.limits.maxDescriptorSetUniformBuffers); + caps.MaxUniformBlockSize = SaturateToInt(p.limits.maxUniformBufferRange); + caps.MaxImageUnits = SaturateToInt(p.limits.maxPerStageDescriptorStorageImages); + caps.MaxCombinedImageUniforms = SaturateToInt(p.limits.maxDescriptorSetStorageImages); + caps.MaxComputeImageUniforms = SaturateToInt(p.limits.maxPerStageDescriptorStorageImages); + caps.MaxDrawBuffers = SaturateToInt(p.limits.maxFragmentOutputAttachments); + caps.MaxColorAttachments = SaturateToInt(p.limits.maxColorAttachments); + caps.MaxClipDistances = SaturateToInt(p.limits.maxClipDistances); + caps.MaxViewports = SaturateToInt(p.limits.maxViewports); + caps.MaxViewportWidth = SaturateToInt(p.limits.maxViewportDimensions[0]); + caps.MaxViewportHeight = SaturateToInt(p.limits.maxViewportDimensions[1]); caps.ViewportBoundsRangeMin = p.limits.viewportBoundsRange[0]; caps.ViewportBoundsRangeMax = p.limits.viewportBoundsRange[1]; - caps.ViewportSubpixelBits = static_cast(p.limits.viewportSubPixelBits); + caps.ViewportSubpixelBits = SaturateToInt(p.limits.viewportSubPixelBits); FillFragmentInterpolationLimits(caps, p.limits); VkPhysicalDeviceFeatures supportedFeatures{}; @@ -269,47 +280,47 @@ namespace MobileGL::MG_Util::BackendLoader { caps.PointSizeRangeMin = properties.limits.pointSizeRange[0]; caps.PointSizeRangeMax = properties.limits.pointSizeRange[1]; caps.PointSizeGranularity = properties.limits.pointSizeGranularity; - caps.Max3DTextureSize = static_cast(properties.limits.maxImageDimension3D); - caps.MaxArrayTextureLayers = static_cast(properties.limits.maxImageArrayLayers); - caps.MaxCubeMapTextureSize = static_cast(properties.limits.maxImageDimensionCube); - caps.MaxFramebufferWidth = static_cast(properties.limits.maxFramebufferWidth); - caps.MaxFramebufferHeight = static_cast(properties.limits.maxFramebufferHeight); - caps.MaxFramebufferLayers = static_cast(properties.limits.maxFramebufferLayers); + caps.Max3DTextureSize = SaturateToInt(properties.limits.maxImageDimension3D); + caps.MaxArrayTextureLayers = SaturateToInt(properties.limits.maxImageArrayLayers); + caps.MaxCubeMapTextureSize = SaturateToInt(properties.limits.maxImageDimensionCube); + caps.MaxFramebufferWidth = SaturateToInt(properties.limits.maxFramebufferWidth); + caps.MaxFramebufferHeight = SaturateToInt(properties.limits.maxFramebufferHeight); + caps.MaxFramebufferLayers = SaturateToInt(properties.limits.maxFramebufferLayers); caps.MaxRenderbufferSize = ResolveMaxRenderbufferSize(properties.limits); - caps.MaxTextureSize = static_cast(properties.limits.maxImageDimension2D); + caps.MaxTextureSize = SaturateToInt(properties.limits.maxImageDimension2D); caps.MaxColorTextureSamples = MaxSampleCountFromFlags(properties.limits.sampledImageColorSampleCounts); caps.MaxDepthTextureSamples = MaxSampleCountFromFlags(properties.limits.sampledImageDepthSampleCounts); caps.MaxFramebufferSamples = ResolveConservativeFramebufferSampleLimit(properties.limits); caps.MaxIntegerSamples = MaxSampleCountFromFlags(properties.limits.sampledImageIntegerSampleCounts); caps.MaxSamples = caps.MaxFramebufferSamples; - caps.MaxSampleMaskWords = static_cast(properties.limits.maxSampleMaskWords); - caps.MaxTextureImageUnits = static_cast(properties.limits.maxPerStageDescriptorSampledImages); - caps.MaxVertexTextureImageUnits = static_cast(properties.limits.maxPerStageDescriptorSampledImages); - caps.MaxComputeTextureImageUnits = static_cast(properties.limits.maxPerStageDescriptorSampledImages); - caps.MaxCombinedTextureImageUnits = static_cast(properties.limits.maxDescriptorSetSampledImages); - caps.MaxVertexAttribs = static_cast(properties.limits.maxVertexInputAttributes); - caps.MaxComputeShaderStorageBlocks = static_cast(properties.limits.maxPerStageDescriptorStorageBuffers); - caps.MaxCombinedShaderStorageBlocks = static_cast(properties.limits.maxDescriptorSetStorageBuffers); - caps.MaxComputeUniformBlocks = static_cast(properties.limits.maxPerStageDescriptorUniformBuffers); - caps.MaxComputeWorkGroupInvocations = static_cast(properties.limits.maxComputeWorkGroupInvocations); - caps.MaxShaderStorageBufferBindings = static_cast(properties.limits.maxDescriptorSetStorageBuffers); - caps.MaxTextureBufferSize = static_cast(properties.limits.maxTexelBufferElements); + caps.MaxSampleMaskWords = SaturateToInt(properties.limits.maxSampleMaskWords); + caps.MaxTextureImageUnits = SaturateToInt(properties.limits.maxPerStageDescriptorSampledImages); + caps.MaxVertexTextureImageUnits = SaturateToInt(properties.limits.maxPerStageDescriptorSampledImages); + caps.MaxComputeTextureImageUnits = SaturateToInt(properties.limits.maxPerStageDescriptorSampledImages); + caps.MaxCombinedTextureImageUnits = SaturateToInt(properties.limits.maxDescriptorSetSampledImages); + caps.MaxVertexAttribs = SaturateToInt(properties.limits.maxVertexInputAttributes); + caps.MaxComputeShaderStorageBlocks = SaturateToInt(properties.limits.maxPerStageDescriptorStorageBuffers); + caps.MaxCombinedShaderStorageBlocks = SaturateToInt(properties.limits.maxDescriptorSetStorageBuffers); + caps.MaxComputeUniformBlocks = SaturateToInt(properties.limits.maxPerStageDescriptorUniformBuffers); + caps.MaxComputeWorkGroupInvocations = SaturateToInt(properties.limits.maxComputeWorkGroupInvocations); + caps.MaxShaderStorageBufferBindings = SaturateToInt(properties.limits.maxDescriptorSetStorageBuffers); + caps.MaxTextureBufferSize = SaturateToInt(properties.limits.maxTexelBufferElements); caps.TextureBufferOffsetAlignment = static_cast(std::max(1, properties.limits.minTexelBufferOffsetAlignment)); - caps.MaxUniformBufferBindings = static_cast(properties.limits.maxDescriptorSetUniformBuffers); - caps.MaxUniformBlockSize = static_cast(properties.limits.maxUniformBufferRange); - caps.MaxImageUnits = static_cast(properties.limits.maxPerStageDescriptorStorageImages); - caps.MaxCombinedImageUniforms = static_cast(properties.limits.maxDescriptorSetStorageImages); - caps.MaxComputeImageUniforms = static_cast(properties.limits.maxPerStageDescriptorStorageImages); - caps.MaxDrawBuffers = static_cast(properties.limits.maxFragmentOutputAttachments); - caps.MaxColorAttachments = static_cast(properties.limits.maxColorAttachments); - caps.MaxClipDistances = static_cast(properties.limits.maxClipDistances); - caps.MaxViewports = static_cast(properties.limits.maxViewports); - caps.MaxViewportWidth = static_cast(properties.limits.maxViewportDimensions[0]); - caps.MaxViewportHeight = static_cast(properties.limits.maxViewportDimensions[1]); + caps.MaxUniformBufferBindings = SaturateToInt(properties.limits.maxDescriptorSetUniformBuffers); + caps.MaxUniformBlockSize = SaturateToInt(properties.limits.maxUniformBufferRange); + caps.MaxImageUnits = SaturateToInt(properties.limits.maxPerStageDescriptorStorageImages); + caps.MaxCombinedImageUniforms = SaturateToInt(properties.limits.maxDescriptorSetStorageImages); + caps.MaxComputeImageUniforms = SaturateToInt(properties.limits.maxPerStageDescriptorStorageImages); + caps.MaxDrawBuffers = SaturateToInt(properties.limits.maxFragmentOutputAttachments); + caps.MaxColorAttachments = SaturateToInt(properties.limits.maxColorAttachments); + caps.MaxClipDistances = SaturateToInt(properties.limits.maxClipDistances); + caps.MaxViewports = SaturateToInt(properties.limits.maxViewports); + caps.MaxViewportWidth = SaturateToInt(properties.limits.maxViewportDimensions[0]); + caps.MaxViewportHeight = SaturateToInt(properties.limits.maxViewportDimensions[1]); caps.ViewportBoundsRangeMin = properties.limits.viewportBoundsRange[0]; caps.ViewportBoundsRangeMax = properties.limits.viewportBoundsRange[1]; - caps.ViewportSubpixelBits = static_cast(properties.limits.viewportSubPixelBits); + caps.ViewportSubpixelBits = SaturateToInt(properties.limits.viewportSubPixelBits); FillFragmentInterpolationLimits(caps, properties.limits); caps.SupportsWideLines = false; caps.SupportsShaderFloat64 = false;