fix(webgl): expose matrix uniforms and drawing buffer sizes

This commit is contained in:
ldm0
2026-09-29 16:41:50 +08:00
parent 324659f7f5
commit 83ab7fa4bf
10 changed files with 345 additions and 44 deletions
+6 -3
View File
@@ -359,9 +359,12 @@ Its current intentional boundaries include:
- Media capability queries use a Chromium-style software compatibility profile;
advertised support and smoothness do not imply implemented media playback or
hardware acceleration.
- WebGL renderer queries use generic compatibility labels (`WebKit` / `WebKit
WebGL`), including enabled debug-renderer queries. These labels do not identify
a physical GPU or an implemented graphics driver.
- WebGL queries use a declared Windows/ANGLE compatibility profile, with
`WebKit` / `WebKit WebGL` as masked labels. Debug identity, limits and shader
precision do not identify a physical GPU or an implemented graphics driver.
Drawing-buffer dimensions reflect the native canvas size; matrix-uniform
capability shims validate arguments but do not execute shaders or upload
uniforms to a GPU. Drawing and pixel readback remain incomplete.
- `--layout` supports software screenshots and raster-backed CDP PDF
generation, but not every Chrome screenshot or print mode is implemented.
@@ -213,15 +213,15 @@ pub(crate) use webgl::{
webgl_check_framebuffer_status_callback, webgl_create_buffer_callback,
webgl_create_framebuffer_callback, webgl_create_program_callback,
webgl_create_renderbuffer_callback, webgl_create_shader_callback,
webgl_get_attrib_location_callback, webgl_get_context_attributes_callback,
webgl_get_error_callback, webgl_get_extension_callback, webgl_get_parameter_callback,
webgl_get_shader_info_log_callback, webgl_get_shader_precision_format_callback,
webgl_get_supported_extensions_callback, webgl_is_context_lost_callback,
webgl_lose_context_noop_callback, webgl_noop_callback, webgl_uniform_location_callback,
webgl_viewport_callback, webgl2_color_space_getter_callback,
webgl2_color_space_setter_callback, webgl2_get_extension_callback,
webgl2_get_internalformat_parameter_callback, webgl2_get_parameter_callback,
webgl2_get_supported_extensions_callback,
webgl_drawing_buffer_size_callback, webgl_get_attrib_location_callback,
webgl_get_context_attributes_callback, webgl_get_error_callback, webgl_get_extension_callback,
webgl_get_parameter_callback, webgl_get_shader_info_log_callback,
webgl_get_shader_precision_format_callback, webgl_get_supported_extensions_callback,
webgl_is_context_lost_callback, webgl_lose_context_noop_callback, webgl_noop_callback,
webgl_uniform_location_callback, webgl_uniform_matrix_callback, webgl_viewport_callback,
webgl2_color_space_getter_callback, webgl2_color_space_setter_callback,
webgl2_get_extension_callback, webgl2_get_internalformat_parameter_callback,
webgl2_get_parameter_callback, webgl2_get_supported_extensions_callback,
};
pub(super) fn install_canvas_template_bindings<'s>(
@@ -1,7 +1,6 @@
use super::super::image_data::new_uint8_clamped_array_from_bytes;
use super::{OFFSCREEN_CANVAS_HEIGHT_SLOT, OFFSCREEN_CANVAS_WIDTH_SLOT};
use crate::util::{get_private_object, get_private_value, set_private_value};
use crate::webidl;
use crate::{
document_runtime::DomHandle,
native_bridge::{JsContextHost, node_runtime_and_handle_from_object_or_detached},
@@ -221,24 +220,21 @@ pub(super) fn canvas_like_dimensions<'s>(
scope: &mut v8::PinScope<'s, '_>,
canvas: v8::Local<'s, v8::Object>,
) -> Option<(u32, u32)> {
let width = canvas_like_dimension(scope, canvas, OFFSCREEN_CANVAS_WIDTH_SLOT, "width")?;
let height = canvas_like_dimension(scope, canvas, OFFSCREEN_CANVAS_HEIGHT_SLOT, "height")?;
// Backing dimensions are native state, not JS-visible property lookups.
// A page's own width/height getter must not resize a context or execute
// while its drawingBufferWidth/Height (or pixels) are being queried.
if html_canvas_identity(scope, canvas).is_some() {
return Some(crate::native_bridge::element::html_canvas_dimensions(
scope, canvas,
));
}
let width =
get_private_value(scope, canvas, OFFSCREEN_CANVAS_WIDTH_SLOT)?.uint32_value(scope)?;
let height =
get_private_value(scope, canvas, OFFSCREEN_CANVAS_HEIGHT_SLOT)?.uint32_value(scope)?;
Some((width, height))
}
fn canvas_like_dimension<'s>(
scope: &mut v8::PinScope<'s, '_>,
canvas: v8::Local<'s, v8::Object>,
slot: &str,
public_name: &'static str,
) -> Option<u32> {
let value = get_private_value(scope, canvas, slot)
.and_then(|value| value.number_value(scope))
.or_else(|| webidl::optional_number_property(scope, canvas, public_name))
.unwrap_or(0.0);
Some(value.max(0.0).trunc() as u32)
}
fn write_bytes_to_view<'s>(
scope: &mut v8::PinScope<'s, '_>,
view: v8::Local<'s, v8::Uint8ClampedArray>,
@@ -14,8 +14,16 @@ const WEBGL_EXTENSIONS_SLOT: &str = "__moliWebGlExtensions";
const WEBGL_VIEWPORT: u32 = 0x0BA2;
const WEBGL_INVALID_ENUM: u32 = 0x0500;
const WEBGL_INVALID_VALUE: u32 = 0x0501;
const WEBGL_INVALID_OPERATION: u32 = 0x0502;
const WEBGL_MAX_VIEWPORT_DIMS: [i32; 2] = [32767, 32767];
const WEBGL_MAX_TEXTURE_SIZE: u32 = 16384;
const WEBGL_MAX_TEXTURE_IMAGE_UNITS: i32 = 16;
const WEBGL_UNIFORM_CONTEXT_SLOT: &str = "__moliWebGlUniformContext";
const WEBGL_MATRIX_METHODS: &[(&str, usize)] = &[
("uniformMatrix2fv", 4),
("uniformMatrix3fv", 9),
("uniformMatrix4fv", 16),
];
const WEBGL_MASKED_VENDOR: &str = "WebKit";
const WEBGL_MASKED_RENDERER: &str = "WebKit WebGL";
// A declared Windows/ANGLE compatibility identity, not physical GPU discovery.
@@ -144,7 +152,10 @@ struct WebGlShaderHandleDeclaration {}
interface = web_api_interfaces::WebGLUniformLocation,
fallback_to_string_tag = "WebGLUniformLocation"
)]
struct WebGlUniformLocationHandleDeclaration {}
struct WebGlUniformLocationHandleDeclaration<'s> {
#[webapi(slot = WEBGL_UNIFORM_CONTEXT_SLOT)]
context: v8::Local<'s, v8::Object>,
}
#[derive(WebApiObject)]
#[webapi(
@@ -495,7 +506,9 @@ fn webgl_resource_limit<'s>(
// ANGLE renderer11_utils.cpp: feature level 11 resource caps, shared by
// WebGL1/2. Querying these compatibility limits does not allocate resources.
Some(match pname {
0x0D33 | 0x84E8 | 0x851C => v8::Integer::new(scope, 16_384).into(),
0x0D33 | 0x84E8 | 0x851C => {
v8::Integer::new_from_unsigned(scope, WEBGL_MAX_TEXTURE_SIZE).into()
}
0x0D3A => webgl_int32_array(scope, &WEBGL_MAX_VIEWPORT_DIMS),
0x846D => webgl_float32_array(scope, &[1.0, 1024.0]),
0x8872 | 0x8B4C => v8::Integer::new(scope, WEBGL_MAX_TEXTURE_IMAGE_UNITS).into(),
@@ -647,17 +660,137 @@ pub(crate) fn webgl_create_shader_callback(
rv.set(value.into());
}
pub(crate) fn webgl_uniform_location_callback(
scope: &mut v8::PinScope<'_, '_>,
_args: v8::FunctionCallbackArguments<'_>,
pub(crate) fn webgl_uniform_location_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let value = WebGlUniformLocationHandleDeclaration::new()
if webgl_extensions(scope, args.this()).is_none() {
return;
}
let value = WebGlUniformLocationHandleDeclaration::new(args.this())
.bind(scope)
.expect("WebGLUniformLocation handle declaration should bind");
rv.set(value.into());
}
pub(crate) fn webgl_drawing_buffer_size_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
if webgl_extensions(scope, args.this()).is_none() {
return;
}
let Some(axis) = callback_data_item(scope, &args, &[0usize, 1], "WebGL drawing buffer axis")
else {
return;
};
let dimensions = super::backing_store::canvas_owner_from_context(scope, args.this())
.and_then(|canvas| super::backing_store::canvas_like_dimensions(scope, canvas));
let Some((width, height)) = dimensions else {
rv.set_uint32(0);
return;
};
// A zero canvas dimension still has a one-pixel drawing buffer in Blink.
// This is the compatibility buffer extent, not a physical GPU allocation.
rv.set_uint32([width, height][axis].clamp(1, WEBGL_MAX_TEXTURE_SIZE));
}
pub(crate) fn webgl_uniform_matrix_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
_rv: v8::ReturnValue<'_, v8::Value>,
) {
if webgl_extensions(scope, args.this()).is_none() {
return;
}
let Some((method, elements)) =
callback_data_item(scope, &args, WEBGL_MATRIX_METHODS, "WebGL matrix uniform")
else {
return;
};
if args.length() < 3 {
throw_type_error(scope, &format!("{method}: 3 arguments required"));
return;
}
let location = args.get(0);
if !location.is_null_or_undefined()
&& !v8::Local::<v8::Object>::try_from(location).is_ok_and(|object| {
web_api_interfaces::WebGLUniformLocation::is_instance(scope, object)
})
{
throw_type_error(
scope,
&format!("{method}: location is not a WebGLUniformLocation"),
);
return;
}
let transpose = args.get(1).boolean_value(scope);
// Float32List's typed-array arm precedes sequence conversion: neither a
// custom iterator nor indexed accessors may run on a Float32Array.
let length = if let Ok(array) = v8::Local::<v8::Float32Array>::try_from(args.get(2)) {
array.length()
} else {
match webidl::argument::<webidl::Sequence<webidl::UnrestrictedDouble>>(
scope,
&args,
2,
webidl::Context::argument(method, 3),
) {
Ok(values) => values.0.len(),
Err(error) => {
webidl::throw_error(scope, &error);
return;
}
}
};
let webgl2 = web_api_interfaces::WebGL2RenderingContext::is_instance(scope, args.this());
let mut range = [0u32; 2];
if webgl2 {
for (i, value) in range.iter_mut().enumerate() {
if let Err(error) = webidl::argument::<webidl::UnsignedLong>(
scope,
&args,
i as i32 + 3,
webidl::Context::argument(method, i + 4),
)
.map(|parsed| *value = parsed.0)
{
webidl::throw_error(scope, &error);
return;
}
}
}
if length == 0 {
record_webgl_error(scope, args.this(), WEBGL_INVALID_VALUE);
return;
}
if location.is_null_or_undefined() {
return;
}
let location = v8::Local::<v8::Object>::try_from(location).expect("validated uniform location");
if !get_private_value(scope, location, WEBGL_UNIFORM_CONTEXT_SLOT)
.is_some_and(|context| context.strict_equals(args.this().into()))
{
record_webgl_error(scope, args.this(), WEBGL_INVALID_OPERATION);
return;
}
let [offset, requested] = range.map(|value| value as usize);
let available = length.saturating_sub(offset);
let count = if requested == 0 { available } else { requested };
if (transpose && !webgl2)
|| offset >= length
|| count > available
|| count < elements
|| !count.is_multiple_of(elements)
{
record_webgl_error(scope, args.this(), WEBGL_INVALID_VALUE);
}
// Accept the validated capability call. Shader/program execution and GPU
// uniform uploads remain outside this shim, like the existing draw methods.
}
pub(crate) fn webgl_get_attrib_location_callback(
scope: &mut v8::PinScope<'_, '_>,
_args: v8::FunctionCallbackArguments<'_>,
@@ -36,15 +36,15 @@ use super::canvas::{
webgl_boolean_callback, webgl_check_framebuffer_status_callback, webgl_create_buffer_callback,
webgl_create_framebuffer_callback, webgl_create_program_callback,
webgl_create_renderbuffer_callback, webgl_create_shader_callback,
webgl_get_attrib_location_callback, webgl_get_context_attributes_callback,
webgl_get_error_callback, webgl_get_extension_callback, webgl_get_parameter_callback,
webgl_get_shader_info_log_callback, webgl_get_shader_precision_format_callback,
webgl_get_supported_extensions_callback, webgl_is_context_lost_callback,
webgl_lose_context_noop_callback, webgl_noop_callback, webgl_uniform_location_callback,
webgl_viewport_callback, webgl2_color_space_getter_callback,
webgl2_color_space_setter_callback, webgl2_get_extension_callback,
webgl2_get_internalformat_parameter_callback, webgl2_get_parameter_callback,
webgl2_get_supported_extensions_callback,
webgl_drawing_buffer_size_callback, webgl_get_attrib_location_callback,
webgl_get_context_attributes_callback, webgl_get_error_callback, webgl_get_extension_callback,
webgl_get_parameter_callback, webgl_get_shader_info_log_callback,
webgl_get_shader_precision_format_callback, webgl_get_supported_extensions_callback,
webgl_is_context_lost_callback, webgl_lose_context_noop_callback, webgl_noop_callback,
webgl_uniform_location_callback, webgl_uniform_matrix_callback, webgl_viewport_callback,
webgl2_color_space_getter_callback, webgl2_color_space_setter_callback,
webgl2_get_extension_callback, webgl2_get_internalformat_parameter_callback,
webgl2_get_parameter_callback, webgl2_get_supported_extensions_callback,
};
use super::file_api::{
file_list_item_callback, file_reader_abort_callback, file_reader_add_event_listener_callback,
@@ -423,6 +423,14 @@ struct CanvasGradientTemplateMethodsDeclaration {
#[derive(WebApiFunctionTemplate)]
#[webapi(interface = web_api_interfaces::WebGLRenderingContext, enumerable)]
struct WebGlRenderingContextTemplateMethodsDeclaration {
#[webapi(accessor_property = "drawingBufferWidth", getter = webgl_drawing_buffer_size_callback,
data = crate::util::callback_data_index_value(scope, 0))]
drawing_buffer_width: (),
#[webapi(accessor_property = "drawingBufferHeight", getter = webgl_drawing_buffer_size_callback,
data = crate::util::callback_data_index_value(scope, 1))]
drawing_buffer_height: (),
#[webapi(method = "viewport", length = 4, callback = webgl_viewport_callback)]
viewport: (),
@@ -511,6 +519,18 @@ struct WebGlRenderingContextTemplateMethodsDeclaration {
#[webapi(method = "uniform2fv", length = 2, callback = webgl_noop_callback)]
uniform2fv: (),
#[webapi(method = "uniformMatrix2fv", length = 3, callback = webgl_uniform_matrix_callback,
data = crate::util::callback_data_index_value(scope, 0))]
uniform_matrix2fv: (),
#[webapi(method = "uniformMatrix3fv", length = 3, callback = webgl_uniform_matrix_callback,
data = crate::util::callback_data_index_value(scope, 1))]
uniform_matrix3fv: (),
#[webapi(method = "uniformMatrix4fv", length = 3, callback = webgl_uniform_matrix_callback,
data = crate::util::callback_data_index_value(scope, 2))]
uniform_matrix4fv: (),
#[webapi(method = "drawArrays", length = 3, callback = webgl_noop_callback)]
draw_arrays: (),
@@ -224,7 +224,7 @@ pub(super) use attributes::{
};
pub(crate) use canvas::{
canvas_get_context_callback, canvas_to_data_url_callback,
canvas_transfer_control_to_offscreen_callback,
canvas_transfer_control_to_offscreen_callback, html_canvas_dimensions,
};
pub(crate) use canvas::{
html_canvas_height_getter_callback, html_canvas_height_setter_callback,
@@ -112,6 +112,16 @@ fn set_canvas_dimension_attribute<'s>(
true
}
pub(crate) fn html_canvas_dimensions<'s>(
scope: &mut v8::PinScope<'s, '_>,
canvas: v8::Local<'s, v8::Object>,
) -> (u32, u32) {
(
canvas_dimension_value(scope, canvas, "width", 300),
canvas_dimension_value(scope, canvas, "height", 150),
)
}
fn canvas_dimension_value<'s>(
scope: &mut v8::PinScope<'s, '_>,
object: v8::Local<'s, v8::Object>,
@@ -1,5 +1,19 @@
use super::*;
#[test]
fn webgl_drawing_capabilities_preserve_native_sizes_and_matrix_arguments() {
let mut vm = new_storage_test_vm("https://webgl-drawing-capabilities.test/");
let result = vm
.eval(include_str!(
"../../../tests/fixtures/webgl-drawing-capabilities.js"
))
.expect("WebGL drawing capability contract should hold");
assert_eq!(
result,
r#"["html:webgl","offscreen:webgl","html:webgl2","offscreen:webgl2"]"#
);
}
#[test]
fn webgl_compatibility_profile_uses_d3d11_identity_and_precision() {
let mut vm = new_storage_test_vm("https://webgl-compatibility-profile.test/");
@@ -2133,6 +2133,26 @@ async fn worker_media_capabilities_matches_the_software_chromium_profile() {
assert_eq!(expect_post_json(msg), "[3,3,3,0,3,3,3,3]");
}
#[tokio::test]
async fn worker_webgl_drawing_capabilities_match_the_window_contract() {
ensure_v8();
let mut handle = spawn_worker(
format!(
"try {{ postMessage(JSON.parse({})); }} catch (error) {{ postMessage(String(error)); }} close();",
include_str!("../../../../tests/fixtures/webgl-drawing-capabilities.js")
),
"https://webgl-drawing-capabilities.test/worker.js".into(),
);
let message = timeout(TIMEOUT, handle.recv())
.await
.expect("worker WebGL drawing capability task must settle")
.expect("worker must return the drawing capability result");
assert_eq!(
expect_post_json(message),
r#"["offscreen:webgl","offscreen:webgl2"]"#
);
}
#[tokio::test]
async fn worker_webgl_compatibility_profile_matches_the_window_profile() {
ensure_v8();
@@ -0,0 +1,105 @@
(() => {
const results = [];
const check = (value, message) => { if (!value) throw new Error(message); };
const throws = (callback, message) => {
let name; try { callback(); } catch (error) { name = error.name; }
check(name === 'TypeError', message);
};
const make = offscreen => offscreen ? new OffscreenCanvas(300, 150) : document.createElement('canvas');
for (const kind of ['webgl', 'webgl2']) {
for (const offscreen of typeof document === 'object' ? [false, true] : [true]) {
const label = `${offscreen ? 'offscreen' : 'html'}:${kind}`;
const canvas = make(offscreen), gl = canvas.getContext(kind);
check(!!gl, `${label}: context available`);
const proto = Object.getPrototypeOf(gl);
for (const field of ['drawingBufferWidth', 'drawingBufferHeight']) {
const d = Object.getOwnPropertyDescriptor(proto, field);
check(d && typeof d.get === 'function' && d.set === undefined && d.enumerable && d.configurable,
`${label}: native readonly ${field}`);
throws(() => d.get.call({}), `${label}: ${field} receiver`);
throws(() => d.get.call(Object.create(proto)), `${label}: ${field} forged receiver`);
}
check(gl.drawingBufferWidth === 300 && gl.drawingBufferHeight === 150, `${label}: default extent`);
for (const [width, height] of [[319, 173], [0, 173], [0, 0], [23, 17]]) {
canvas.width = width; canvas.height = height;
check(gl.drawingBufferWidth === Math.max(1, width) && gl.drawingBufferHeight === Math.max(1, height),
`${label}: resize extent`);
}
Object.defineProperty(canvas, 'width', {configurable: true, get(){ throw new Error('page width getter'); }});
Object.defineProperty(canvas, 'height', {configurable: true, get(){ throw new Error('page height getter'); }});
Object.defineProperty(gl, 'canvas', {configurable: true, value: {width: 999, height: 999}});
check(gl.drawingBufferWidth === 23 && gl.drawingBufferHeight === 17, `${label}: extent uses native owner and size`);
delete canvas.width; delete canvas.height; delete gl.canvas;
// A minimal real shader pair keeps uniform locations valid on Chromium.
const program = gl.createProgram();
for (const [type, source] of [
[gl.VERTEX_SHADER, 'attribute vec4 p; uniform mat2 m2; uniform mat3 m3; uniform mat4 m4; void main(){ gl_Position=m4*vec4(m3*vec3(m2*p.xy,p.z),p.w); }'],
[gl.FRAGMENT_SHADER, 'precision mediump float; void main(){gl_FragColor=vec4(1.0);}'],
]) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source); gl.compileShader(shader);
check(gl.getShaderParameter(shader, gl.COMPILE_STATUS), `${label}: shader compile`);
gl.attachShader(program, shader);
}
gl.linkProgram(program); gl.useProgram(program);
check(gl.getProgramParameter(program, gl.LINK_STATUS), `${label}: program linked`);
const other = make(offscreen).getContext(kind);
for (const size of [2, 3, 4]) {
const name = `uniformMatrix${size}fv`, method = gl[name];
const d = Object.getOwnPropertyDescriptor(proto, name), count = size * size;
check(d?.value === method && method.name === name && method.length === 3 && d.enumerable && d.configurable && d.writable,
`${label}: matrix method descriptor`);
const location = gl.getUniformLocation(program, `m${size}`);
check(location !== null, `${label}: live matrix location`);
other[name](location, false, new Float32Array(count));
check(other.getError() === 0x0502, `${label}: location belongs to its context (INVALID_OPERATION)`);
throws(() => method.call({}, null, false, []), `${label}: matrix receiver`);
throws(() => method.call(Object.create(proto), null, false, []), `${label}: forged matrix receiver`);
throws(() => method.call(gl), `${label}: required arguments`);
let iterated = 0;
const values = {[Symbol.iterator](){iterated++; return Array(count).fill(0)[Symbol.iterator]();}};
throws(() => method.call(gl, {}, false, values), `${label}: invalid location`);
check(iterated === 0, `${label}: validate location before data conversion`);
check(method.call(gl, location, false, values) === undefined && gl.getError() === gl.NO_ERROR && iterated === 1,
`${label}: iterable values`);
const typed = new Float32Array(count);
Object.defineProperty(typed, Symbol.iterator, {get(){throw new Error('typed-array iterator must not be consulted');}});
method.call(gl, location, false, typed);
check(gl.getError() === gl.NO_ERROR, `${label}: typed-array union branch`);
method.call(gl, location, false, new Float64Array(count));
check(gl.getError() === gl.NO_ERROR, `${label}: other typed array uses iterable branch`);
throws(() => method.call(gl, location, false, null), `${label}: data cannot be null`);
throws(() => method.call(gl, location, false, [1n]), `${label}: numeric element conversion`);
const sentinel = new Error('iterator failure');
let caught;
try {method.call(gl, location, false, {[Symbol.iterator](){throw sentinel;}});}
catch (error) {caught = error;}
check(caught === sentinel, `${label}: preserve iterator exception`);
method.call(gl, location, false, []);
check(gl.getError() === gl.INVALID_VALUE, `${label}: empty matrix error`);
method.call(gl, null, false, new Float32Array(0));
check(gl.getError() === gl.INVALID_VALUE, `${label}: empty data precedes null-location no-op`);
method.call(gl, null, true, [1]);
check(gl.getError() === gl.NO_ERROR, `${label}: converted nonempty null-location call ignored`);
method.call(gl, location, false, Array(count - 1).fill(0));
check(gl.getError() === gl.INVALID_VALUE, `${label}: incomplete matrix`);
method.call(gl, location, true, Array(count).fill(0));
check(gl.getError() === (kind === 'webgl2' ? gl.NO_ERROR : gl.INVALID_VALUE), `${label}: transpose version policy`);
method.call(gl, location, false, Array(count + 1).fill(0), 1);
check(gl.getError() === (kind === 'webgl2' ? gl.NO_ERROR : gl.INVALID_VALUE), `${label}: srcOffset overload`);
method.call(gl, location, false, Array(count + 2).fill(0), 1, count);
check(gl.getError() === (kind === 'webgl2' ? gl.NO_ERROR : gl.INVALID_VALUE), `${label}: srcLength overload`);
method.call(gl, location, false, Array(count).fill(0), count);
check(gl.getError() === (kind === 'webgl2' ? gl.INVALID_VALUE : gl.NO_ERROR), `${label}: offset past end`);
const offset = {valueOf(){throw new TypeError('offset conversion');}};
if (kind === 'webgl2') throws(() => method.call(gl, null, false, typed, offset), `${label}: offset WebIDL`);
else method.call(gl, null, false, typed, offset);
check(gl.getError() === gl.NO_ERROR, `${label}: WebIDL exceptions are not GL errors`);
}
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
check(Array.from(gl.getParameter(gl.VIEWPORT)).join(',') === '0,0,23,17', `${label}: native extents feed viewport`);
results.push(label);
}
}
return JSON.stringify(results);
})()