Skip to content
Open
68 changes: 68 additions & 0 deletions fixtures/html/webgl-conformance/getparameter-binding-test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebGL getParameter Binding Test</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.pass { color: green; }
.fail { color: red; }
pre { background: #f5f5f5; padding: 10px; border-radius: 4px; }
</style>
</head>
<body>
<h1>WebGL getParameter Binding Test</h1>
<p>This test verifies that context.getParameter() returns null (not undefined) for binding parameters.</p>
<div id="results"></div>
<script>
const results = document.getElementById('results');

function log(message, isPass) {
const div = document.createElement('div');
div.className = isPass ? 'pass' : 'fail';
div.textContent = (isPass ? '✓ ' : '✗ ') + message;
results.appendChild(div);
}

function testParameter(gl, paramName, paramValue) {
const result = gl.getParameter(paramValue);
const isNull = result === null;
const isUndefined = result === undefined;

if (isNull) {
log(`${paramName}: correctly returns null`, true);
return true;
} else if (isUndefined) {
log(`${paramName}: INCORRECTLY returns undefined (should be null)`, false);
return false;
} else {
log(`${paramName}: returns ${result} (expected null or object)`, true);
return true;
}
}

// Create WebGL2 context
const canvas2 = document.createElement('canvas');
const gl2 = navigator.gl || canvas2.getContext('webgl2');

if (gl2) {
results.innerHTML += '<h2>WebGL 2.0 Binding Parameters</h2>';
testParameter(gl2, 'COPY_READ_BUFFER_BINDING', gl2.COPY_READ_BUFFER_BINDING);
testParameter(gl2, 'COPY_WRITE_BUFFER_BINDING', gl2.COPY_WRITE_BUFFER_BINDING);
testParameter(gl2, 'DRAW_FRAMEBUFFER_BINDING', gl2.DRAW_FRAMEBUFFER_BINDING);
testParameter(gl2, 'READ_FRAMEBUFFER_BINDING', gl2.READ_FRAMEBUFFER_BINDING);
testParameter(gl2, 'PIXEL_PACK_BUFFER_BINDING', gl2.PIXEL_PACK_BUFFER_BINDING);
testParameter(gl2, 'PIXEL_UNPACK_BUFFER_BINDING', gl2.PIXEL_UNPACK_BUFFER_BINDING);
testParameter(gl2, 'UNIFORM_BUFFER_BINDING', gl2.UNIFORM_BUFFER_BINDING);
testParameter(gl2, 'VERTEX_ARRAY_BINDING', gl2.VERTEX_ARRAY_BINDING);
testParameter(gl2, 'SAMPLER_BINDING', gl2.SAMPLER_BINDING);
testParameter(gl2, 'TRANSFORM_FEEDBACK_BINDING', gl2.TRANSFORM_FEEDBACK_BINDING);
testParameter(gl2, 'TRANSFORM_FEEDBACK_BUFFER_BINDING', gl2.TRANSFORM_FEEDBACK_BUFFER_BINDING);
testParameter(gl2, 'TEXTURE_BINDING_2D_ARRAY', gl2.TEXTURE_BINDING_2D_ARRAY);
testParameter(gl2, 'TEXTURE_BINDING_3D', gl2.TEXTURE_BINDING_3D);
} else {
log('WebGL 2.0 not supported', false);
}
</script>
</body>
</html>
70 changes: 70 additions & 0 deletions src/client/graphics/webgl_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1466,6 +1466,40 @@ namespace endor
return v;
}

shared_ptr<WebGLBuffer> WebGLContext::getParameter(WebGLBufferBindingParameterName pname)
{
switch (pname)
{
case WebGLBufferBindingParameterName::kArrayBufferBinding:
return clientState_.vertexBuffer.value_or(nullptr);
case WebGLBufferBindingParameterName::kElementArrayBufferBinding:
return clientState_.elementBuffer.value_or(nullptr);
default:
return nullptr;
}
}

shared_ptr<WebGLProgram> WebGLContext::getParameterProgram(WebGLObjectBindingParameterName pname)
{
if (pname == WebGLObjectBindingParameterName::kCurrentProgram)
return clientState_.program.value_or(nullptr);
return nullptr;
}

shared_ptr<WebGLFramebuffer> WebGLContext::getParameterFramebuffer(WebGLObjectBindingParameterName pname)
{
if (pname == WebGLObjectBindingParameterName::kFramebufferBinding)
return clientState_.framebuffer.value_or(nullptr);
return nullptr;
}

shared_ptr<WebGLRenderbuffer> WebGLContext::getParameterRenderbuffer(WebGLObjectBindingParameterName pname)
{
if (pname == WebGLObjectBindingParameterName::kRenderbufferBinding)
return clientState_.renderbuffer.value_or(nullptr);
return nullptr;
}

WebGLShaderPrecisionFormat WebGLContext::getShaderPrecisionFormat(int shadertype, int precisiontype)
{
if (shadertype != WEBGL_VERTEX_SHADER && shadertype != WEBGL_FRAGMENT_SHADER)
Expand Down Expand Up @@ -2038,6 +2072,42 @@ namespace endor
return v;
}

shared_ptr<WebGLBuffer> WebGL2Context::getParameterV2(WebGL2BufferBindingParameterName pname)
{
// These buffer bindings are not yet tracked in clientState
// TODO: Extend WebGLState to track additional buffer binding points
switch (pname)
{
case WebGL2BufferBindingParameterName::kCopyReadBufferBinding:
case WebGL2BufferBindingParameterName::kCopyWriteBufferBinding:
case WebGL2BufferBindingParameterName::kPixelPackBufferBinding:
case WebGL2BufferBindingParameterName::kPixelUnpackBufferBinding:
case WebGL2BufferBindingParameterName::kTransformFeedbackBufferBinding:
case WebGL2BufferBindingParameterName::kUniformBufferBinding:
default:
return nullptr;
}
Comment on lines +2087 to +2089
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The switch statement lists all enum cases but includes a 'default' case that does the same thing as all listed cases. This pattern is confusing and makes it unclear if this is intentional (all return nullptr) or incomplete. Either remove the 'default' label and just have a single 'return nullptr;' after all cases, or explicitly document that these bindings are not yet implemented.

Suggested change
default:
return nullptr;
}
break;
}
return nullptr;

Copilot uses AI. Check for mistakes.
}

shared_ptr<WebGLFramebuffer> WebGL2Context::getParameterFramebufferV2(WebGL2ObjectBindingParameterName pname)
{
switch (pname)
{
case WebGL2ObjectBindingParameterName::kDrawFramebufferBinding:
case WebGL2ObjectBindingParameterName::kReadFramebufferBinding:
return clientState_.framebuffer.value_or(nullptr);
default:
return nullptr;
}
}

shared_ptr<WebGLVertexArray> WebGL2Context::getParameterVertexArrayV2(WebGL2ObjectBindingParameterName pname)
{
if (pname == WebGL2ObjectBindingParameterName::kVertexArrayBinding)
return clientState_.vertexArray.value_or(nullptr);
return nullptr;
}

shared_ptr<WebGLQuery> WebGL2Context::getQuery(WebGLQueryTarget target, int pname)
{
NOT_IMPLEMENTED();
Expand Down
45 changes: 44 additions & 1 deletion src/client/graphics/webgl_context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,41 @@ namespace endor
kExtMaxViewsOvr = WEBGL2_EXT_MAX_VIEWS_OVR,
};

/**
* Enum for buffer/object binding parameters that return WebGL objects.
*/
enum class WebGLBufferBindingParameterName
{
kArrayBufferBinding = WEBGL_ARRAY_BUFFER_BINDING,
kElementArrayBufferBinding = WEBGL_ELEMENT_ARRAY_BUFFER_BINDING,
};

enum class WebGLObjectBindingParameterName
{
kCurrentProgram = WEBGL_CURRENT_PROGRAM,
kFramebufferBinding = WEBGL_FRAMEBUFFER_BINDING,
kRenderbufferBinding = WEBGL_RENDERBUFFER_BINDING,
};

enum class WebGL2BufferBindingParameterName
{
kCopyReadBufferBinding = WEBGL2_COPY_READ_BUFFER_BINDING,
kCopyWriteBufferBinding = WEBGL2_COPY_WRITE_BUFFER_BINDING,
kPixelPackBufferBinding = WEBGL2_PIXEL_PACK_BUFFER_BINDING,
kPixelUnpackBufferBinding = WEBGL2_PIXEL_UNPACK_BUFFER_BINDING,
kTransformFeedbackBufferBinding = WEBGL2_TRANSFORM_FEEDBACK_BUFFER_BINDING,
kUniformBufferBinding = WEBGL2_UNIFORM_BUFFER_BINDING,
};

enum class WebGL2ObjectBindingParameterName
{
kDrawFramebufferBinding = WEBGL2_DRAW_FRAMEBUFFER_BINDING,
kReadFramebufferBinding = WEBGL2_READ_FRAMEBUFFER_BINDING,
kVertexArrayBinding = WEBGL2_VERTEX_ARRAY_BINDING,
kSamplerBinding = WEBGL2_SAMPLER_BINDING,
kTransformFeedbackBinding = WEBGL2_TRANSFORM_FEEDBACK_BINDING,
};

class ContextAttributes final
{
public:
Expand Down Expand Up @@ -464,6 +499,10 @@ namespace endor
bool getParameter(WebGLBooleanIndexedParameterName pname, int index);
float getParameter(WebGLFloatArrayParameterName pname, int index);
std::string getParameter(WebGLStringParameterName pname);
std::shared_ptr<WebGLBuffer> getParameter(WebGLBufferBindingParameterName pname);
std::shared_ptr<WebGLProgram> getParameterProgram(WebGLObjectBindingParameterName pname);
std::shared_ptr<WebGLFramebuffer> getParameterFramebuffer(WebGLObjectBindingParameterName pname);
std::shared_ptr<WebGLRenderbuffer> getParameterRenderbuffer(WebGLObjectBindingParameterName pname);
WebGLShaderPrecisionFormat getShaderPrecisionFormat(int shadertype, int precisiontype);
int getError();
std::vector<std::string> &getSupportedExtensions();
Expand Down Expand Up @@ -728,7 +767,8 @@ namespace endor
/**
* @returns the client state of the WebGL context.
*/
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing clientState() to return a const reference breaks compilation at line 1118 where WebGLProgramScope expects a non-const reference (auto &clientState = glContext->clientState()). The PR description mentions 'Restored original clientState() method signature' but this change makes it const-only. Either restore the non-const overload or update line 1118 to use const auto &clientState.

Suggested change
*/
*/
// Non-const accessor
WebGLState &clientState()
{
return clientState_;
}

Copilot uses AI. Check for mistakes.
WebGLState &clientState()
// Read-only accessor
const WebGLState &clientState() const
{
return clientState_;
}
Expand Down Expand Up @@ -917,6 +957,9 @@ namespace endor
std::optional<int> length = std::nullopt);
int getFragDataLocation(std::shared_ptr<WebGLProgram> program, const std::string &name);
int getParameterV2(WebGL2IntegerParameterName pname);
std::shared_ptr<WebGLBuffer> getParameterV2(WebGL2BufferBindingParameterName pname);
std::shared_ptr<WebGLFramebuffer> getParameterFramebufferV2(WebGL2ObjectBindingParameterName pname);
std::shared_ptr<WebGLVertexArray> getParameterVertexArrayV2(WebGL2ObjectBindingParameterName pname);
std::shared_ptr<WebGLQuery> getQuery(WebGLQueryTarget target, int pname);
int getUniformBlockIndex(std::shared_ptr<WebGLProgram> program, const std::string &uniformBlockName);

Expand Down
109 changes: 109 additions & 0 deletions src/client/script_bindings/webgl/webgl_rendering_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4385,6 +4385,61 @@ namespace endor
jsValue = String::NewFromUtf8(isolate, value.c_str()).ToLocalChecked();
break;
}
/**
* WebGL object bindings (return null when nothing bound, object when bound)
*/
case WEBGL_ARRAY_BUFFER_BINDING:
{
auto value = handle()->getParameter(static_cast<client_graphics::WebGLBufferBindingParameterName>(pname));
if (value != nullptr)
jsValue = WebGLBuffer::NewInstance(isolate, value);
else
jsValue = Null(isolate);
break;
}
case WEBGL_ELEMENT_ARRAY_BUFFER_BINDING:
{
auto value = handle()->getParameter(static_cast<client_graphics::WebGLBufferBindingParameterName>(pname));
if (value != nullptr)
jsValue = WebGLBuffer::NewInstance(isolate, value);
else
jsValue = Null(isolate);
break;
}
case WEBGL_FRAMEBUFFER_BINDING:
{
auto value = handle()->getParameterFramebuffer(static_cast<client_graphics::WebGLObjectBindingParameterName>(pname));
if (value != nullptr)
jsValue = WebGLFramebuffer::NewInstance(isolate, value);
else
jsValue = Null(isolate);
break;
}
case WEBGL_RENDERBUFFER_BINDING:
{
auto value = handle()->getParameterRenderbuffer(static_cast<client_graphics::WebGLObjectBindingParameterName>(pname));
if (value != nullptr)
jsValue = WebGLRenderbuffer::NewInstance(isolate, value);
else
jsValue = Null(isolate);
break;
}
case WEBGL_CURRENT_PROGRAM:
{
auto value = handle()->getParameterProgram(static_cast<client_graphics::WebGLObjectBindingParameterName>(pname));
if (value != nullptr)
jsValue = WebGLProgram::NewInstance(isolate, value);
else
jsValue = Null(isolate);
break;
}
case WEBGL_TEXTURE_BINDING_2D:
case WEBGL_TEXTURE_BINDING_CUBE_MAP:
{
// TODO: Texture bindings not yet tracked in clientState
jsValue = Null(isolate);
break;
}
default:
cerr << "WebGLRenderingContext::GetParameter: Unhandled pname " << pname << endl;
break;
Expand Down Expand Up @@ -4429,6 +4484,60 @@ namespace endor
jsValue = Integer::New(isolate, value);
break;
}
/**
* WebGL2 object bindings (return null when nothing bound, object when bound)
*/
case WEBGL2_VERTEX_ARRAY_BINDING:
{
auto value = handle<client_graphics::WebGL2Context>()
->getParameterVertexArrayV2(static_cast<client_graphics::WebGL2ObjectBindingParameterName>(pname));
if (value != nullptr)
jsValue = WebGLVertexArray::NewInstance(isolate, value);
else
jsValue = Null(isolate);
break;
}
case WEBGL2_DRAW_FRAMEBUFFER_BINDING:
case WEBGL2_READ_FRAMEBUFFER_BINDING:
{
auto value = handle<client_graphics::WebGL2Context>()
->getParameterFramebufferV2(static_cast<client_graphics::WebGL2ObjectBindingParameterName>(pname));
if (value != nullptr)
jsValue = WebGLFramebuffer::NewInstance(isolate, value);
else
jsValue = Null(isolate);
break;
}
case WEBGL2_COPY_READ_BUFFER_BINDING:
case WEBGL2_COPY_WRITE_BUFFER_BINDING:
case WEBGL2_PIXEL_PACK_BUFFER_BINDING:
case WEBGL2_PIXEL_UNPACK_BUFFER_BINDING:
case WEBGL2_UNIFORM_BUFFER_BINDING:
case WEBGL2_TRANSFORM_FEEDBACK_BUFFER_BINDING:
{
auto value = handle<client_graphics::WebGL2Context>()
->getParameterV2(static_cast<client_graphics::WebGL2BufferBindingParameterName>(pname));
if (value != nullptr)
jsValue = WebGLBuffer::NewInstance(isolate, value);
else
jsValue = Null(isolate);
break;
}
case WEBGL2_SAMPLER_BINDING:
case WEBGL2_TRANSFORM_FEEDBACK_BINDING:
{
// TODO: Sampler and transform feedback bindings not yet tracked
// Would require creating WebGLSampler and WebGLTransformFeedback JavaScript bindings
jsValue = Null(isolate);
break;
}
case WEBGL2_TEXTURE_BINDING_2D_ARRAY:
case WEBGL2_TEXTURE_BINDING_3D:
{
// TODO: Texture bindings not yet tracked in clientState
jsValue = Null(isolate);
break;
}
default:
break;
}
Expand Down