mirror of
https://github.com/lexmount/moli.git
synced 2026-09-30 16:01:42 +00:00
fix(webgl): expose matrix uniforms and drawing buffer sizes
This commit is contained in:
@@ -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);
|
||||
})()
|
||||
Reference in New Issue
Block a user