mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Fix] (GL): report GL_SAMPLE_POSITION from the backend's sample locations and check the index against the draw framebuffer's GL_SAMPLES
This commit is contained in:
@@ -573,6 +573,14 @@ namespace MobileGL {
|
||||
Uint32 SubgroupSize = 0;
|
||||
Uint32 SubgroupSupportedStages = 0;
|
||||
Uint32 SubgroupSupportedFeatures = 0;
|
||||
// GL_SAMPLE_POSITION (GL 4.6 core 14.3.1.1) as the device actually places the samples
|
||||
// of a 2-, 4-, 8- and 16-sample pixel: (x, y) pairs in [0, 1], the N-sample row starting
|
||||
// at pair N - 2. Bit log2(N) of SamplePositionCountMask says row N was filled in; a row
|
||||
// the backend could not determine stays unset rather than being guessed. Read and
|
||||
// written through Get/SetSamplePositions only.
|
||||
static constexpr Int kMaxSamplePositionCount = 16;
|
||||
Float SamplePositions[60] = {}; // 2 * (2 + 4 + 8 + 16)
|
||||
Uint32 SamplePositionCountMask = 0;
|
||||
Bool SubgroupQuadOperationsInAllStages = false;
|
||||
GpuVendorKind GpuVendor = GpuVendorKind::Unknown;
|
||||
// GL_EXT_fragment_shading_rate and its two companions. Each is a statement about the
|
||||
@@ -606,6 +614,29 @@ namespace MobileGL {
|
||||
Int MaxFragmentShadingRateAttachmentTexelHeight = 0;
|
||||
Int MaxFragmentShadingRateAttachmentTexelAspectRatio = 0;
|
||||
Int MaxFragmentShadingRateAttachmentLayers = 0;
|
||||
|
||||
|
||||
static constexpr Bool IsSamplePositionCount(Int samples) {
|
||||
return samples >= 2 && samples <= kMaxSamplePositionCount && (samples & (samples - 1)) == 0;
|
||||
}
|
||||
static constexpr Uint32 SamplePositionCountBit(Int samples) {
|
||||
return 1u << static_cast<Uint32>(std::countr_zero(static_cast<Uint32>(samples)));
|
||||
}
|
||||
// `xy` holds `samples` (x, y) pairs.
|
||||
void SetSamplePositions(Int samples, const Float* xy) {
|
||||
if (!IsSamplePositionCount(samples)) return;
|
||||
for (Int i = 0; i < 2 * samples; ++i) SamplePositions[2 * (samples - 2) + i] = xy[i];
|
||||
SamplePositionCountMask |= SamplePositionCountBit(samples);
|
||||
}
|
||||
Bool GetSamplePosition(Int samples, Int index, Float* outXY) const {
|
||||
if (!IsSamplePositionCount(samples) || index < 0 || index >= samples ||
|
||||
(SamplePositionCountMask & SamplePositionCountBit(samples)) == 0) {
|
||||
return false;
|
||||
}
|
||||
outXY[0] = SamplePositions[2 * (samples - 2 + index)];
|
||||
outXY[1] = SamplePositions[2 * (samples - 2 + index) + 1];
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
enum class WindowBackend {
|
||||
|
||||
@@ -453,6 +453,59 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return supported;
|
||||
}
|
||||
|
||||
// GL_SAMPLE_POSITION is a property of the multisample framebuffer the driver builds, so it
|
||||
// is asked once per power-of-two sample count, of a throwaway RGBA8 renderbuffer framebuffer,
|
||||
// through the driver's own glGetMultisamplefv (ES 3.1 core). A count the driver rounds up
|
||||
// reports the first positions of the pattern it really uses - the samples gl_SampleID
|
||||
// 0..count-1 address in a shader. Counts that cannot be built are left unset.
|
||||
void ProbeSamplePositions(const MG_External::GLESFunctionsTable& gl,
|
||||
const MG_External::GLESCapabilities& capabilities,
|
||||
DynamicBackendParameters& dynamicParameters) {
|
||||
const auto& version = capabilities.GLESVersion;
|
||||
if (version.Major < 3 || (version.Major == 3 && version.Minor < 1)) return;
|
||||
if (!gl.glGetMultisamplefv || !gl.glGenFramebuffers || !gl.glBindFramebuffer ||
|
||||
!gl.glFramebufferRenderbuffer || !gl.glCheckFramebufferStatus || !gl.glDeleteFramebuffers ||
|
||||
!gl.glGenRenderbuffers || !gl.glBindRenderbuffer || !gl.glRenderbufferStorageMultisample ||
|
||||
!gl.glDeleteRenderbuffers || !gl.glGetIntegerv) {
|
||||
return;
|
||||
}
|
||||
|
||||
GLint prevFramebuffer = 0, prevRenderbuffer = 0;
|
||||
gl.glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prevFramebuffer);
|
||||
gl.glGetIntegerv(GL_RENDERBUFFER_BINDING, &prevRenderbuffer);
|
||||
for (Int samples = 2; samples <= DynamicBackendParameters::kMaxSamplePositionCount &&
|
||||
samples <= capabilities.MaxSamples;
|
||||
samples *= 2) {
|
||||
GLuint framebuffer = 0, renderbuffer = 0;
|
||||
gl.glGenFramebuffers(1, &framebuffer);
|
||||
gl.glGenRenderbuffers(1, &renderbuffer);
|
||||
gl.glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
|
||||
gl.glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_RGBA8, 4, 4);
|
||||
gl.glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
|
||||
gl.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer);
|
||||
GLint actualSamples = 0;
|
||||
if (gl.glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) {
|
||||
gl.glGetIntegerv(GL_SAMPLES, &actualSamples);
|
||||
}
|
||||
if (actualSamples >= samples) {
|
||||
Float positions[2 * DynamicBackendParameters::kMaxSamplePositionCount] = {};
|
||||
for (Int index = 0; index < samples; ++index) {
|
||||
gl.glGetMultisamplefv(GL_SAMPLE_POSITION, static_cast<GLuint>(index), &positions[2 * index]);
|
||||
}
|
||||
dynamicParameters.SetSamplePositions(samples, positions);
|
||||
}
|
||||
gl.glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(prevFramebuffer));
|
||||
gl.glBindRenderbuffer(GL_RENDERBUFFER, static_cast<GLuint>(prevRenderbuffer));
|
||||
gl.glDeleteFramebuffers(1, &framebuffer);
|
||||
gl.glDeleteRenderbuffers(1, &renderbuffer);
|
||||
}
|
||||
// A count the driver refused leaves its error behind; it must not reach the application.
|
||||
if (gl.glGetError) {
|
||||
for (Int drained = 0; drained < 16 && gl.glGetError() != GL_NO_ERROR; ++drained) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Bool ProbeFramebufferCompletenessForRenderbuffer(const MG_External::GLESFunctionsTable& gl, GLuint renderbuffer,
|
||||
TextureInternalFormat format) {
|
||||
GLuint framebuffer = 0;
|
||||
@@ -1868,6 +1921,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
clampStageImageUniforms(m_GLESCapabilities.MaxComputeImageUniforms);
|
||||
m_dynamicParameters.SupportsDistinctDepthStencilAttachments =
|
||||
ProbeDistinctDepthStencilAttachments(DirectGLES::g_GLESFuncs);
|
||||
ProbeSamplePositions(DirectGLES::g_GLESFuncs, m_GLESCapabilities, m_dynamicParameters);
|
||||
// SyncAttachmentObject routes a layered upload target to glFramebufferTextureLayer with the
|
||||
// attachment's layer passed through, so this backend really does render to the layer it was
|
||||
// given - provided the driver resolved the entry point at all.
|
||||
|
||||
@@ -1201,6 +1201,28 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
m_dynamicParameters.MaxIntegerSamples = m_vulkanCaps.MaxIntegerSamples;
|
||||
m_dynamicParameters.MaxSamples = m_vulkanCaps.MaxSamples;
|
||||
m_dynamicParameters.MaxSampleMaskWords = m_vulkanCaps.MaxSampleMaskWords;
|
||||
// GL_SAMPLE_POSITION. A device reporting standardSampleLocations places the samples exactly
|
||||
// at the Vulkan specification's table (Multisampling, "Standard sample locations"), which
|
||||
// is also what gl_SamplePosition reads back in a shader. Vulkan's sample y grows with the
|
||||
// framebuffer row, and a framebuffer object's row 0 is GL's y = 0, so the table needs no
|
||||
// flip to be GL's answer. Without the flag the positions are unknown and stay unset.
|
||||
if (m_vulkanCaps.StandardSampleLocations) {
|
||||
static constexpr Float kStandard2[] = {0.75f, 0.75f, 0.25f, 0.25f};
|
||||
static constexpr Float kStandard4[] = {0.375f, 0.125f, 0.875f, 0.375f, 0.125f, 0.625f, 0.625f, 0.875f};
|
||||
static constexpr Float kStandard8[] = {0.5625f, 0.3125f, 0.4375f, 0.6875f, 0.8125f, 0.5625f,
|
||||
0.3125f, 0.1875f, 0.1875f, 0.8125f, 0.0625f, 0.4375f,
|
||||
0.6875f, 0.9375f, 0.9375f, 0.0625f};
|
||||
static constexpr Float kStandard16[] = {
|
||||
0.5625f, 0.5625f, 0.4375f, 0.3125f, 0.3125f, 0.625f, 0.75f, 0.4375f, 0.1875f, 0.375f, 0.625f,
|
||||
0.8125f, 0.8125f, 0.6875f, 0.6875f, 0.1875f, 0.375f, 0.875f, 0.5f, 0.0625f, 0.25f, 0.125f,
|
||||
0.125f, 0.75f, 0.0f, 0.5f, 0.9375f, 0.25f, 0.875f, 0.9375f, 0.0625f, 0.0f};
|
||||
const Float* const tables[] = {kStandard2, kStandard4, kStandard8, kStandard16};
|
||||
for (Int samples = 2, row = 0; samples <= decltype(m_dynamicParameters)::kMaxSamplePositionCount &&
|
||||
samples <= m_vulkanCaps.MaxSamples;
|
||||
samples *= 2, ++row) {
|
||||
m_dynamicParameters.SetSamplePositions(samples, tables[row]);
|
||||
}
|
||||
}
|
||||
const Int maxSupportedTextureUnits = static_cast<Int>(MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS);
|
||||
// GL_MAX_TEXTURE_IMAGE_UNITS is a *per-stage* sampler limit. Adreno/Qualcomm report a huge
|
||||
// maxPerStageDescriptorSampledImages (descriptor-indexing scale), so clamping it only to our
|
||||
|
||||
@@ -7208,19 +7208,27 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
return;
|
||||
}
|
||||
|
||||
const Int maxSamples = MG_Backend::pActiveBackendObject != nullptr
|
||||
? std::max(MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxSamples, 1)
|
||||
: 1;
|
||||
if (static_cast<Int>(index) >= maxSamples) {
|
||||
// GL 4.6 core 14.3.1.1: a sample of the DRAW framebuffer, which has GL_SAMPLES of them - so
|
||||
// a single-sample framebuffer has none to ask about.
|
||||
const GLint samples = ResolveDrawFramebufferSampleCount();
|
||||
if (static_cast<Int64>(index) >= samples) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "Sample index is out of range."));
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"Sample index is not below the draw framebuffer's GL_SAMPLES."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep sample positions deterministic even before the backend exposes vendor-specific patterns.
|
||||
val[0] = 0.5f;
|
||||
val[1] = 0.5f;
|
||||
// The backend publishes where its device really places the samples of each count (a
|
||||
// shader's gl_SamplePosition reads the same places). A count it could not determine keeps
|
||||
// answering the pixel centre.
|
||||
Float position[2] = {0.5f, 0.5f};
|
||||
if (MG_Backend::pActiveBackendObject != nullptr) {
|
||||
MG_Backend::pActiveBackendObject->GetDynamicParameters().GetSamplePosition(
|
||||
samples, static_cast<Int>(index), position);
|
||||
}
|
||||
val[0] = position[0];
|
||||
val[1] = position[1];
|
||||
}
|
||||
|
||||
void TexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width,
|
||||
|
||||
@@ -390,7 +390,8 @@
|
||||
F(SupportsDistinctDepthStencilAttachments) F(PerLayerFramebufferAttachmentTargets) \
|
||||
F(SupportsShaderFloat64) F(SupportsFloat64VertexAttributes) F(SupportsTessellationPointSize) \
|
||||
F(SupportsGeometryPointSize) F(MaxShaderStorageBlockSize) F(SubgroupSize) \
|
||||
F(SubgroupSupportedStages) F(SubgroupSupportedFeatures) F(SubgroupQuadOperationsInAllStages) \
|
||||
F(SubgroupSupportedStages) F(SubgroupSupportedFeatures) F(SamplePositions) \
|
||||
F(SamplePositionCountMask) F(SubgroupQuadOperationsInAllStages) \
|
||||
F(GpuVendor) F(SupportsFragmentShadingRate) F(SupportsPrimitiveFragmentShadingRate) \
|
||||
F(SupportsAttachmentFragmentShadingRate) F(FragmentShadingRateNonTrivialCombiners) \
|
||||
F(FragmentShadingRateWithShaderDepthStencilWrites) F(FragmentShadingRateWithSampleMask) \
|
||||
|
||||
@@ -664,6 +664,8 @@ inline constexpr WireLayoutMember kMGPipeWireLayoutMembers[] = {
|
||||
{"DynamicBackendParameters.SubgroupSize", offsetof(DynamicBackendParameters, SubgroupSize), sizeof(((DynamicBackendParameters*)nullptr)->SubgroupSize)},
|
||||
{"DynamicBackendParameters.SubgroupSupportedStages", offsetof(DynamicBackendParameters, SubgroupSupportedStages), sizeof(((DynamicBackendParameters*)nullptr)->SubgroupSupportedStages)},
|
||||
{"DynamicBackendParameters.SubgroupSupportedFeatures", offsetof(DynamicBackendParameters, SubgroupSupportedFeatures), sizeof(((DynamicBackendParameters*)nullptr)->SubgroupSupportedFeatures)},
|
||||
{"DynamicBackendParameters.SamplePositions", offsetof(DynamicBackendParameters, SamplePositions), sizeof(((DynamicBackendParameters*)nullptr)->SamplePositions)},
|
||||
{"DynamicBackendParameters.SamplePositionCountMask", offsetof(DynamicBackendParameters, SamplePositionCountMask), sizeof(((DynamicBackendParameters*)nullptr)->SamplePositionCountMask)},
|
||||
{"DynamicBackendParameters.SubgroupQuadOperationsInAllStages", offsetof(DynamicBackendParameters, SubgroupQuadOperationsInAllStages), sizeof(((DynamicBackendParameters*)nullptr)->SubgroupQuadOperationsInAllStages)},
|
||||
{"DynamicBackendParameters.GpuVendor", offsetof(DynamicBackendParameters, GpuVendor), sizeof(((DynamicBackendParameters*)nullptr)->GpuVendor)},
|
||||
{"DynamicBackendParameters.SupportsFragmentShadingRate", offsetof(DynamicBackendParameters, SupportsFragmentShadingRate), sizeof(((DynamicBackendParameters*)nullptr)->SupportsFragmentShadingRate)},
|
||||
|
||||
@@ -414,6 +414,48 @@ TEST_F(TextureTest, ClearTexImageRejectsCompressedTextures) {
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
}
|
||||
|
||||
// GL 4.6 core 14.3.1.1 (KHR-GL4x sample_variables.position.fixed): GL_SAMPLE_POSITION answers where
|
||||
// the backend places the samples of the draw framebuffer's sample count, and an index at or past
|
||||
// that framebuffer's GL_SAMPLES is INVALID_VALUE - every index, for a single-sample framebuffer.
|
||||
TEST_F(TextureTest, SamplePositionComesFromTheBackendForTheDrawFramebufferSampleCount) {
|
||||
auto& params = FormatCapabilityBackend::MutableDynamicParameters();
|
||||
const auto savedParams = params;
|
||||
params.MaxSamples = params.MaxColorTextureSamples = params.MaxDepthTextureSamples = 4;
|
||||
params.MaxIntegerSamples = params.MaxFramebufferSamples = 4;
|
||||
const Float fourSamples[] = {0.375f, 0.125f, 0.875f, 0.375f, 0.125f, 0.625f, 0.625f, 0.875f};
|
||||
params.SetSamplePositions(4, fourSamples);
|
||||
ScopedBackendOverride override(MakeUnique<FormatCapabilityBackend>());
|
||||
|
||||
GLuint texture = 0;
|
||||
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D_MULTISAMPLE, 1, &texture);
|
||||
MG_Impl::GLImpl::TextureStorage2DMultisample(texture, 4, GL_RGBA8, 8, 8, GL_TRUE);
|
||||
GLuint framebuffer = 0;
|
||||
MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer);
|
||||
MG_Impl::GLImpl::NamedFramebufferTexture(framebuffer, GL_COLOR_ATTACHMENT0, texture, 0);
|
||||
MG_Impl::GLImpl::BindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
|
||||
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
|
||||
for (GLuint index = 0; index < 4; ++index) {
|
||||
GLfloat position[2] = {-1.0f, -1.0f};
|
||||
MG_Impl::GLImpl::GetMultisamplefv(GL_SAMPLE_POSITION, index, position);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
EXPECT_EQ(position[0], fourSamples[2 * index]) << "sample " << index;
|
||||
EXPECT_EQ(position[1], fourSamples[2 * index + 1]) << "sample " << index;
|
||||
}
|
||||
GLfloat position[2] = {};
|
||||
MG_Impl::GLImpl::GetMultisamplefv(GL_SAMPLE_POSITION, 4, position);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
|
||||
MG_Impl::GLImpl::BindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
MG_Impl::GLImpl::GetMultisamplefv(GL_SAMPLE_POSITION, 0, position);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
|
||||
MG_Impl::GLImpl::DeleteFramebuffers(1, &framebuffer);
|
||||
MG_Impl::GLImpl::DeleteTextures(1, &texture);
|
||||
params = savedParams;
|
||||
DrainPendingGlErrors();
|
||||
}
|
||||
|
||||
// GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT is float state that must answer every numeric query: GetFloatv
|
||||
// is authoritative and GetIntegerv would otherwise fall through to its INVALID_ENUM default.
|
||||
TEST_F(TextureTest, MaxTextureMaxAnisotropyIsAnsweredFromTheBackendLimit) {
|
||||
|
||||
@@ -187,6 +187,7 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
// reproduced on this backend. One word is the spec minimum and therefore always legal.
|
||||
// It is also what PipelineCreatePayload::sampleMask is sized for.
|
||||
caps.MaxSampleMaskWords = std::min(SaturateToInt(p.limits.maxSampleMaskWords), 1);
|
||||
caps.StandardSampleLocations = p.limits.standardSampleLocations == VK_TRUE;
|
||||
caps.MaxTextureImageUnits = SaturateToInt(p.limits.maxPerStageDescriptorSampledImages);
|
||||
caps.MaxVertexTextureImageUnits = SaturateToInt(p.limits.maxPerStageDescriptorSampledImages);
|
||||
caps.MaxComputeTextureImageUnits = SaturateToInt(p.limits.maxPerStageDescriptorSampledImages);
|
||||
@@ -329,6 +330,7 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
// reproduced on this backend. One word is the spec minimum and therefore always legal.
|
||||
// It is also what PipelineCreatePayload::sampleMask is sized for.
|
||||
caps.MaxSampleMaskWords = std::min(SaturateToInt(properties.limits.maxSampleMaskWords), 1);
|
||||
caps.StandardSampleLocations = properties.limits.standardSampleLocations == VK_TRUE;
|
||||
caps.MaxTextureImageUnits = SaturateToInt(properties.limits.maxPerStageDescriptorSampledImages);
|
||||
caps.MaxVertexTextureImageUnits = SaturateToInt(properties.limits.maxPerStageDescriptorSampledImages);
|
||||
caps.MaxComputeTextureImageUnits = SaturateToInt(properties.limits.maxPerStageDescriptorSampledImages);
|
||||
|
||||
@@ -47,6 +47,9 @@ namespace MobileGL {
|
||||
Int MaxIntegerSamples = 1;
|
||||
Int MaxSamples = 1;
|
||||
Int MaxSampleMaskWords = 1;
|
||||
// VkPhysicalDeviceLimits::standardSampleLocations: the device places the samples of
|
||||
// every supported count at the standard locations the Vulkan specification tabulates.
|
||||
Bool StandardSampleLocations = false;
|
||||
Int MaxTextureImageUnits = 32;
|
||||
Int MaxVertexTextureImageUnits = 32;
|
||||
Int MaxComputeTextureImageUnits = 32;
|
||||
|
||||
Reference in New Issue
Block a user