[Feat] (MG_Impl, MG_State): implement the framebuffer parameter getters and setters

glFramebufferParameteri, glGetFramebufferParameteriv and their two by-name
siblings were all export stubs - the GL_ARB_framebuffer_no_attachments entry
points. The stub raises no error and writes nothing, so
direct_state_access.framebuffers_get_parameter_errors saw GL_NO_ERROR for all
three conditions it checks.

FramebufferObject gains the five DEFAULT_* parameters as real state, initialised
to GL 4.6 core table 23.24 and bumping the object version on a write like the
read buffer does. The getter answers those plus the six derived names -
GL_SAMPLES and GL_SAMPLE_BUFFERS from the attachments' sample counts,
GL_IMPLEMENTATION_COLOR_READ_FORMAT/_TYPE from the read buffer's internal format,
GL_DOUBLEBUFFER true only for the window-system framebuffer, GL_STEREO false
because stereo surfaces are not exposed - which is what glGetIntegerv already
reports for the bound framebuffer.

The pname rules live in ValidateFramebufferParameterPname, and their ORDER is
load-bearing: a name outside the table is INVALID_ENUM, and only a name that IS
in the table but that the default framebuffer cannot answer is INVALID_OPERATION.
Testing the framebuffer kind first would answer INVALID_ENUM for
GL_FRAMEBUFFER_DEFAULT_WIDTH on framebuffer zero, which is exactly the third
thing the case checks. The by-name forms take zero as the default framebuffer,
like the other DSA framebuffer entry points.

Rendering to a framebuffer with no attachments is deliberately NOT enabled by
this: CheckCompleteness still reports INCOMPLETE_MISSING_ATTACHMENT, because no
backend can rasterize one. The state is real and the queries are honest; the
draw path is a separate piece of work.

Takes framebuffers_get_parameter_errors from failing to passing on both backends,
with framebuffers_get_parameters - which passed only because both getters were
stubs leaving the CTS's zero-initialised comparands untouched - still passing.
This commit is contained in:
BZLZHH
2026-08-05 05:33:31 -04:00
parent 817091641c
commit 1f1a331a44
7 changed files with 282 additions and 4 deletions
@@ -8,6 +8,7 @@
#include "GL_Framebuffer.h"
#include "Validators.h"
#include <MG_Util/Texture/TextureFormatProcessor.h>
#include "Config.h"
#include <MG_Backend/BackendObjects.h>
#include <MG_Util/Metrics/TextureMetrics.h>
@@ -2131,6 +2132,124 @@ namespace MobileGL::MG_Impl::GLImpl {
return GL_FRAMEBUFFER_COMPLETE;
}
// GL_ARB_framebuffer_no_attachments plus the queryable framebuffer state of GL 4.6 core 9.2.3.
// The DEFAULT_* half is real state on the framebuffer object; the rest is derived from the
// attachments, and matches what glGetIntegerv answers for the bound framebuffer.
void GetFramebufferParameteriv_Object(const SharedPtr<MG_State::GLState::FramebufferObject>& framebufferObject,
GLenum pname, GLint* params, const char* caller) {
if (params == nullptr) return;
if (!FramebufferImpl::ValidateFramebufferParameterPname(pname, framebufferObject->IsDefaultFramebuffer(),
/*forSetter=*/false, caller)) {
return;
}
const auto resolveSampleCount = [&]() {
GLint maxSamples = 0;
for (const auto& attachment : framebufferObject->GetAllAttachmentObjects()) {
if (attachment.IsRenderbuffer() && attachment.GetRenderbuffer()) {
maxSamples = std::max(maxSamples, static_cast<GLint>(attachment.GetRenderbuffer()->GetSamples()));
} else if (attachment.IsTexture() && attachment.GetTexture()) {
maxSamples = std::max(maxSamples, static_cast<GLint>(attachment.GetTexture()->GetSamples()));
}
}
return maxSamples;
};
switch (pname) {
case GL_FRAMEBUFFER_DEFAULT_WIDTH:
*params = framebufferObject->GetDefaultWidth();
break;
case GL_FRAMEBUFFER_DEFAULT_HEIGHT:
*params = framebufferObject->GetDefaultHeight();
break;
case GL_FRAMEBUFFER_DEFAULT_LAYERS:
*params = framebufferObject->GetDefaultLayers();
break;
case GL_FRAMEBUFFER_DEFAULT_SAMPLES:
*params = framebufferObject->GetDefaultSamples();
break;
case GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS:
*params = framebufferObject->GetDefaultFixedSampleLocations() ? GL_TRUE : GL_FALSE;
break;
case GL_SAMPLES:
*params = resolveSampleCount();
break;
case GL_SAMPLE_BUFFERS:
*params = resolveSampleCount() > 0 ? 1 : 0;
break;
case GL_IMPLEMENTATION_COLOR_READ_FORMAT:
case GL_IMPLEMENTATION_COLOR_READ_TYPE: {
const auto readBuffer = framebufferObject->GetReadBuffer();
TextureInternalFormat internalFormat = TextureInternalFormat::Unknown;
if (readBuffer != FramebufferAttachmentType::None) {
const auto& attachment = framebufferObject->GetAttachment(readBuffer);
if (attachment.IsTexture() && attachment.GetTexture()) {
internalFormat = attachment.GetTexture()->GetFormat();
} else if (attachment.IsRenderbuffer() && attachment.GetRenderbuffer()) {
internalFormat = attachment.GetRenderbuffer()->GetInternalFormat();
}
}
if (internalFormat == TextureInternalFormat::Unknown) {
*params = 0;
break;
}
const GLenum glInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(internalFormat);
GLenum normalizedInternalFormat = glInternalFormat;
GLenum format = GL_RGBA;
GLenum type = GL_UNSIGNED_BYTE;
MG_Util::TextureFormatProcessor::NormalizePixelFormat(glInternalFormat, PixelFormatNormalizeOptionBit::None,
&normalizedInternalFormat, &format, &type);
*params = static_cast<GLint>(pname == GL_IMPLEMENTATION_COLOR_READ_FORMAT ? format : type);
break;
}
case GL_DOUBLEBUFFER:
// Only the window-system surface is ever double buffered; a framebuffer object never is.
*params = framebufferObject->IsDefaultFramebuffer() ? GL_TRUE : GL_FALSE;
break;
case GL_STEREO:
// Stereo surfaces are not exposed, matching what glGetIntegerv reports.
*params = GL_FALSE;
break;
default:
break;
}
}
void FramebufferParameteri_Object(const SharedPtr<MG_State::GLState::FramebufferObject>& framebufferObject,
GLenum pname, GLint param, const char* caller) {
if (!FramebufferImpl::ValidateFramebufferParameterPname(pname, framebufferObject->IsDefaultFramebuffer(),
/*forSetter=*/true, caller)) {
return;
}
if (pname != GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS && param < 0) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
"Framebuffer default parameters must be non-negative."));
return;
}
switch (pname) {
case GL_FRAMEBUFFER_DEFAULT_WIDTH:
framebufferObject->SetDefaultWidth(param);
break;
case GL_FRAMEBUFFER_DEFAULT_HEIGHT:
framebufferObject->SetDefaultHeight(param);
break;
case GL_FRAMEBUFFER_DEFAULT_LAYERS:
framebufferObject->SetDefaultLayers(param);
break;
case GL_FRAMEBUFFER_DEFAULT_SAMPLES:
framebufferObject->SetDefaultSamples(param);
break;
case GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS:
framebufferObject->SetDefaultFixedSampleLocations(param != GL_FALSE);
break;
default:
break;
}
}
void GetFramebufferAttachmentParameteriv_Object(
const SharedPtr<MG_State::GLState::FramebufferObject>& framebufferObject, GLenum attachment, GLenum pname,
GLint* params, const char* caller) {
@@ -2250,6 +2369,54 @@ namespace MobileGL::MG_Impl::GLImpl {
"GetNamedFramebufferAttachmentParameteriv_State");
}
// The by-target forms resolve the binding; the by-name forms take zero as the default
// framebuffer, exactly as the other DSA framebuffer entry points do (GL 4.6 core 9.2.3).
void GetFramebufferParameteriv_State(GLenum target, GLenum pname, GLint* params) {
if (target == GL_FRAMEBUFFER) target = GL_DRAW_FRAMEBUFFER;
const auto framebufferTarget = MG_Util::ConvertGLEnumToFramebufferTarget(target);
if (!FramebufferImpl::ValidateFramebufferTarget(framebufferTarget)) return;
auto& framebufferObject = MG_State::pGLContext->GetFramebufferBindingSlot(framebufferTarget).GetBoundObject();
if (!framebufferObject) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetFramebufferParameteriv_State",
"Framebuffer target is bound to no framebuffer object."));
return;
}
GetFramebufferParameteriv_Object(framebufferObject, pname, params, "GetFramebufferParameteriv_State");
}
void FramebufferParameteri_State(GLenum target, GLenum pname, GLint param) {
if (target == GL_FRAMEBUFFER) target = GL_DRAW_FRAMEBUFFER;
const auto framebufferTarget = MG_Util::ConvertGLEnumToFramebufferTarget(target);
if (!FramebufferImpl::ValidateFramebufferTarget(framebufferTarget)) return;
auto& framebufferObject = MG_State::pGLContext->GetFramebufferBindingSlot(framebufferTarget).GetBoundObject();
if (!framebufferObject) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "FramebufferParameteri_State",
"Framebuffer target is bound to no framebuffer object."));
return;
}
FramebufferParameteri_Object(framebufferObject, pname, param, "FramebufferParameteri_State");
}
void GetNamedFramebufferParameteriv_State(GLuint framebuffer, GLenum pname, GLint* params) {
auto framebufferObject = framebuffer == 0
? FramebufferImpl::pDefaultFramebufferInfo->defaultFBO
: GetNamedFramebufferObject_State(framebuffer, "GetNamedFramebufferParameteriv_State");
if (!framebufferObject) return;
GetFramebufferParameteriv_Object(framebufferObject, pname, params, "GetNamedFramebufferParameteriv_State");
}
void NamedFramebufferParameteri_State(GLuint framebuffer, GLenum pname, GLint param) {
auto framebufferObject = framebuffer == 0
? FramebufferImpl::pDefaultFramebufferInfo->defaultFBO
: GetNamedFramebufferObject_State(framebuffer, "NamedFramebufferParameteri_State");
if (!framebufferObject) return;
FramebufferParameteri_Object(framebufferObject, pname, param, "NamedFramebufferParameteri_State");
}
void BlitNamedFramebuffer_State(GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, GLint srcY0,
GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
GLbitfield mask, GLenum filter) {
@@ -2896,6 +3063,22 @@ namespace MobileGL::MG_Impl::GLImpl {
return CheckNamedFramebufferStatus_State(framebuffer, target);
}
void GetFramebufferParameteriv(GLenum target, GLenum pname, GLint* params) {
GetFramebufferParameteriv_State(target, pname, params);
}
void FramebufferParameteri(GLenum target, GLenum pname, GLint param) {
FramebufferParameteri_State(target, pname, param);
}
void GetNamedFramebufferParameteriv(GLuint framebuffer, GLenum pname, GLint* params) {
GetNamedFramebufferParameteriv_State(framebuffer, pname, params);
}
void NamedFramebufferParameteri(GLuint framebuffer, GLenum pname, GLint param) {
NamedFramebufferParameteri_State(framebuffer, pname, param);
}
void GetNamedFramebufferAttachmentParameteriv(GLuint framebuffer, GLenum attachment, GLenum pname, GLint* params) {
GetNamedFramebufferAttachmentParameteriv_State(framebuffer, attachment, pname, params);
}