mirror of
https://github.com/servo/servo.git
synced 2025-08-05 05:30:08 +01:00
Auto merge of #13309 - anholt:webgl-fbo-prep, r=emilio
webgl: FBO support preparation <!-- Please describe your changes on the following line: --> Sending this PR now so that we can get the webrender patches merged. This is prep for the webgl-fbo series, with one small fix for some conformance tests. --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [ ] These changes fix #__ (github issue number if applicable). <!-- Either: --> - [X] There are tests for these changes OR - [ ] These changes do not require tests because _____ <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/13309) <!-- Reviewable:end -->
This commit is contained in:
commit
7c0dfd07ad
13 changed files with 129 additions and 49 deletions
|
@ -427,9 +427,12 @@ impl WebRenderDisplayItemConverter for DisplayItem {
|
|||
if let Some(id) = item.webrender_image.key {
|
||||
if item.stretch_size.width > Au(0) &&
|
||||
item.stretch_size.height > Au(0) {
|
||||
// TODO(gw): Pass through the tile spacing once the other
|
||||
// changes related to this land (parsing etc).
|
||||
builder.push_image(item.base.bounds.to_rectf(),
|
||||
item.base.clip.to_clip_region(frame_builder),
|
||||
item.stretch_size.to_sizef(),
|
||||
Size2D::zero(),
|
||||
item.image_rendering.to_image_rendering(),
|
||||
id);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
// https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl
|
||||
use canvas_traits::CanvasMsg;
|
||||
use dom::bindings::codegen::Bindings::WebGLFramebufferBinding;
|
||||
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants;
|
||||
use dom::bindings::global::GlobalRef;
|
||||
use dom::bindings::js::Root;
|
||||
use dom::bindings::reflector::reflect_dom_object;
|
||||
|
@ -79,6 +80,12 @@ impl WebGLFramebuffer {
|
|||
self.is_deleted.get()
|
||||
}
|
||||
|
||||
pub fn check_status(&self) -> u32 {
|
||||
// Until we build support for attaching renderbuffers or
|
||||
// textures, all user FBOs are incomplete.
|
||||
return constants::FRAMEBUFFER_UNSUPPORTED;
|
||||
}
|
||||
|
||||
pub fn target(&self) -> Option<u32> {
|
||||
self.target.get()
|
||||
}
|
||||
|
|
|
@ -212,6 +212,38 @@ impl WebGLRenderingContext {
|
|||
}
|
||||
}
|
||||
|
||||
// Helper function for validating framebuffer completeness in
|
||||
// calls touching the framebuffer. From the GLES 2.0.25 spec,
|
||||
// page 119:
|
||||
//
|
||||
// "Effects of Framebuffer Completeness on Framebuffer
|
||||
// Operations
|
||||
//
|
||||
// If the currently bound framebuffer is not framebuffer
|
||||
// complete, then it is an error to attempt to use the
|
||||
// framebuffer for writing or reading. This means that
|
||||
// rendering commands such as DrawArrays and DrawElements, as
|
||||
// well as commands that read the framebuffer such as
|
||||
// ReadPixels and CopyTexSubImage, will generate the error
|
||||
// INVALID_FRAMEBUFFER_OPERATION if called while the
|
||||
// framebuffer is not framebuffer complete."
|
||||
//
|
||||
// The WebGL spec mentions a couple more operations that trigger
|
||||
// this: clear() and getParameter(IMPLEMENTATION_COLOR_READ_*).
|
||||
fn validate_framebuffer_complete(&self) -> bool {
|
||||
match self.bound_framebuffer.get() {
|
||||
Some(fb) => match fb.check_status() {
|
||||
constants::FRAMEBUFFER_COMPLETE => return true,
|
||||
_ => {
|
||||
self.webgl_error(InvalidFramebufferOperation);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
// The default framebuffer is always complete.
|
||||
None => return true,
|
||||
}
|
||||
}
|
||||
|
||||
fn tex_parameter(&self, target: u32, name: u32, value: TexParameterValue) {
|
||||
let texture = match target {
|
||||
constants::TEXTURE_2D => self.bound_texture_2d.get(),
|
||||
|
@ -591,6 +623,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
|
|||
let error_code = if let Some(error) = self.last_error.get() {
|
||||
match error {
|
||||
WebGLError::InvalidEnum => constants::INVALID_ENUM,
|
||||
WebGLError::InvalidFramebufferOperation => constants::INVALID_FRAMEBUFFER_OPERATION,
|
||||
WebGLError::InvalidValue => constants::INVALID_VALUE,
|
||||
WebGLError::InvalidOperation => constants::INVALID_OPERATION,
|
||||
WebGLError::OutOfMemory => constants::OUT_OF_MEMORY,
|
||||
|
@ -748,9 +781,11 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
|
|||
// case: Chromium currently unbinds, and Gecko silently
|
||||
// returns. The conformance tests don't cover this case.
|
||||
Some(renderbuffer) if !renderbuffer.is_deleted() => {
|
||||
renderbuffer.bind(target)
|
||||
self.bound_renderbuffer.set(Some(renderbuffer));
|
||||
renderbuffer.bind(target);
|
||||
}
|
||||
_ => {
|
||||
self.bound_renderbuffer.set(None);
|
||||
// Unbind the currently bound renderbuffer
|
||||
self.ipc_renderer
|
||||
.send(CanvasMsg::WebGL(WebGLCommand::BindRenderbuffer(target, None)))
|
||||
|
@ -773,6 +808,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
|
|||
Err(err) => return self.webgl_error(err),
|
||||
}
|
||||
} else {
|
||||
slot.set(None);
|
||||
// Unbind the currently bound texture
|
||||
self.ipc_renderer
|
||||
.send(CanvasMsg::WebGL(WebGLCommand::BindTexture(target, None)))
|
||||
|
@ -882,6 +918,10 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
|
|||
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
|
||||
fn CopyTexImage2D(&self, target: u32, level: i32, internal_format: u32,
|
||||
x: i32, y: i32, width: i32, height: i32, border: i32) {
|
||||
if !self.validate_framebuffer_complete() {
|
||||
return;
|
||||
}
|
||||
|
||||
let validator = CommonTexImage2DValidator::new(self, target, level,
|
||||
internal_format, width,
|
||||
height, border);
|
||||
|
@ -935,6 +975,10 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
|
|||
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
|
||||
fn CopyTexSubImage2D(&self, target: u32, level: i32, xoffset: i32, yoffset: i32,
|
||||
x: i32, y: i32, width: i32, height: i32) {
|
||||
if !self.validate_framebuffer_complete() {
|
||||
return;
|
||||
}
|
||||
|
||||
// NB: We use a dummy (valid) format and border in order to reuse the
|
||||
// common validations, but this should have its own validator.
|
||||
let validator = CommonTexImage2DValidator::new(self, target, level,
|
||||
|
@ -974,6 +1018,10 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
|
|||
|
||||
// https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.11
|
||||
fn Clear(&self, mask: u32) {
|
||||
if !self.validate_framebuffer_complete() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.ipc_renderer.send(CanvasMsg::WebGL(WebGLCommand::Clear(mask))).unwrap();
|
||||
self.mark_as_dirty();
|
||||
}
|
||||
|
@ -1200,6 +1248,10 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
|
|||
return self.webgl_error(InvalidValue);
|
||||
}
|
||||
|
||||
if !self.validate_framebuffer_complete() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.ipc_renderer
|
||||
.send(CanvasMsg::WebGL(WebGLCommand::DrawArrays(mode, first, count)))
|
||||
.unwrap();
|
||||
|
@ -1236,6 +1288,10 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
|
|||
return self.webgl_error(InvalidOperation);
|
||||
}
|
||||
|
||||
if !self.validate_framebuffer_complete() {
|
||||
return;
|
||||
}
|
||||
|
||||
match mode {
|
||||
constants::POINTS | constants::LINE_STRIP |
|
||||
constants::LINE_LOOP | constants::LINES |
|
||||
|
@ -1504,6 +1560,10 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
|
|||
None => return self.webgl_error(InvalidValue),
|
||||
};
|
||||
|
||||
if !self.validate_framebuffer_complete() {
|
||||
return;
|
||||
}
|
||||
|
||||
match unsafe { JS_GetArrayBufferViewType(pixels) } {
|
||||
Type::Uint8 => (),
|
||||
_ => return self.webgl_error(InvalidOperation)
|
||||
|
|
4
components/servo/Cargo.lock
generated
4
components/servo/Cargo.lock
generated
|
@ -2643,7 +2643,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "webrender"
|
||||
version = "0.5.1"
|
||||
source = "git+https://github.com/servo/webrender#61b8f8bfefd472bd71dd9a06c1d16dab28c1fcc0"
|
||||
source = "git+https://github.com/servo/webrender#58b9e983a5e74ac1670fcf67f9c2ba68740ab2cc"
|
||||
dependencies = [
|
||||
"app_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bincode 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2668,7 +2668,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "webrender_traits"
|
||||
version = "0.5.1"
|
||||
source = "git+https://github.com/servo/webrender#61b8f8bfefd472bd71dd9a06c1d16dab28c1fcc0"
|
||||
source = "git+https://github.com/servo/webrender#58b9e983a5e74ac1670fcf67f9c2ba68740ab2cc"
|
||||
dependencies = [
|
||||
"app_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
4
ports/cef/Cargo.lock
generated
4
ports/cef/Cargo.lock
generated
|
@ -2503,7 +2503,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "webrender"
|
||||
version = "0.5.1"
|
||||
source = "git+https://github.com/servo/webrender#61b8f8bfefd472bd71dd9a06c1d16dab28c1fcc0"
|
||||
source = "git+https://github.com/servo/webrender#58b9e983a5e74ac1670fcf67f9c2ba68740ab2cc"
|
||||
dependencies = [
|
||||
"app_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bincode 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2528,7 +2528,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "webrender_traits"
|
||||
version = "0.5.1"
|
||||
source = "git+https://github.com/servo/webrender#61b8f8bfefd472bd71dd9a06c1d16dab28c1fcc0"
|
||||
source = "git+https://github.com/servo/webrender#58b9e983a5e74ac1670fcf67f9c2ba68740ab2cc"
|
||||
dependencies = [
|
||||
"app_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
|
@ -375,38 +375,44 @@ PrimitiveInfo fetch_text_run_glyph(int index, out vec4 color, out vec4 uv_rect)
|
|||
|
||||
struct Image {
|
||||
PrimitiveInfo info;
|
||||
vec4 st_rect; // Location of the image texture in the texture atlas.
|
||||
vec4 stretch_size_uvkind; // Size of the actual image.
|
||||
vec4 st_rect; // Location of the image texture in the texture atlas.
|
||||
vec4 stretch_size_and_tile_spacing; // Size of the actual image and amount of space between
|
||||
// tiled instances of this image.
|
||||
vec4 uvkind; // Type of texture coordinates.
|
||||
};
|
||||
|
||||
Image fetch_image(int index) {
|
||||
Image image;
|
||||
|
||||
int offset = index * 5;
|
||||
int offset = index * 6;
|
||||
|
||||
image.info = unpack_prim_info(offset);
|
||||
image.st_rect = data[offset + 3];
|
||||
image.stretch_size_uvkind = data[offset + 4];
|
||||
image.stretch_size_and_tile_spacing = data[offset + 4];
|
||||
image.uvkind = data[offset + 5];
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
struct ImageClip {
|
||||
PrimitiveInfo info;
|
||||
vec4 st_rect; // Location of the image texture in the texture atlas.
|
||||
vec4 stretch_size_uvkind; // Size of the actual image.
|
||||
vec4 st_rect; // Location of the image texture in the texture atlas.
|
||||
vec4 stretch_size_and_tile_spacing; // Size of the actual image and amount of space between
|
||||
// tiled instances of this image.
|
||||
vec4 uvkind; // Type of texture coordinates.
|
||||
Clip clip;
|
||||
};
|
||||
|
||||
ImageClip fetch_image_clip(int index) {
|
||||
ImageClip image;
|
||||
|
||||
int offset = index * 14;
|
||||
int offset = index * 15;
|
||||
|
||||
image.info = unpack_prim_info(offset);
|
||||
image.st_rect = data[offset + 3];
|
||||
image.stretch_size_uvkind = data[offset + 4];
|
||||
image.clip = unpack_clip(offset + 5);
|
||||
image.stretch_size_and_tile_spacing = data[offset + 4];
|
||||
image.uvkind = data[offset + 5];
|
||||
image.clip = unpack_clip(offset + 6);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
|
|
@ -11,16 +11,19 @@ void main(void) {
|
|||
|
||||
// We clamp the texture coordinate calculation here to the local rectangle boundaries,
|
||||
// which makes the edge of the texture stretch instead of repeat.
|
||||
vec2 uv = clamp(pos, vLocalRect.xy, vLocalRect.xy + vLocalRect.zw);
|
||||
vec2 relative_pos_in_rect =
|
||||
clamp(pos, vLocalRect.xy, vLocalRect.xy + vLocalRect.zw) - vLocalRect.xy;
|
||||
#else
|
||||
float alpha = 1.0;;
|
||||
vec2 relative_pos_in_rect = vLocalPos;
|
||||
#endif
|
||||
|
||||
// We calculate the particular tile this fragment belongs to, taking into
|
||||
// account the spacing in between tiles. We only paint if our fragment does
|
||||
// not fall into that spacing.
|
||||
vec2 position_in_tile = mod(relative_pos_in_rect, vStretchSize + vTileSpacing);
|
||||
vec2 st = vTextureOffset + ((position_in_tile / vStretchSize) * vTextureSize);
|
||||
alpha = alpha * float(all(bvec2(step(position_in_tile, vStretchSize))));
|
||||
|
||||
uv = (uv - vLocalRect.xy) / vStretchSize;
|
||||
#else
|
||||
vec2 uv = vUv;
|
||||
#endif
|
||||
vec2 st = vTextureOffset + vTextureSize * fract(uv);
|
||||
#ifdef WR_FEATURE_TRANSFORM
|
||||
oFragColor = vec4(1, 1, 1, alpha) * texture(sDiffuse, st);
|
||||
#else
|
||||
oFragColor = texture(sDiffuse, st);
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -4,11 +4,13 @@
|
|||
|
||||
flat varying vec2 vTextureOffset; // Offset of this image into the texture atlas.
|
||||
flat varying vec2 vTextureSize; // Size of the image in the texture atlas.
|
||||
flat varying vec2 vTileSpacing; // Amount of space between tiled instances of this image.
|
||||
|
||||
#ifdef WR_FEATURE_TRANSFORM
|
||||
varying vec3 vLocalPos;
|
||||
flat varying vec4 vLocalRect;
|
||||
flat varying vec2 vStretchSize;
|
||||
#else
|
||||
varying vec2 vUv; // Location within the CSS box to draw.
|
||||
varying vec2 vLocalPos;
|
||||
flat varying vec2 vStretchSize;
|
||||
#endif
|
||||
|
|
|
@ -10,17 +10,18 @@ void main(void) {
|
|||
TransformVertexInfo vi = write_transform_vertex(image.info);
|
||||
vLocalRect = vi.clipped_local_rect;
|
||||
vLocalPos = vi.local_pos;
|
||||
vStretchSize = image.stretch_size_uvkind.xy;
|
||||
vStretchSize = image.stretch_size_and_tile_spacing.xy;
|
||||
#else
|
||||
VertexInfo vi = write_vertex(image.info);
|
||||
vUv = (vi.local_clamped_pos - vi.local_rect.p0) / image.stretch_size_uvkind.xy;
|
||||
vStretchSize = image.stretch_size_and_tile_spacing.xy;
|
||||
vLocalPos = vi.local_clamped_pos - vi.local_rect.p0;
|
||||
#endif
|
||||
|
||||
// vUv will contain how many times this image has wrapped around the image size.
|
||||
vec2 st0 = image.st_rect.xy;
|
||||
vec2 st1 = image.st_rect.zw;
|
||||
|
||||
switch (uint(image.stretch_size_uvkind.z)) {
|
||||
switch (uint(image.uvkind.x)) {
|
||||
case UV_NORMALIZED:
|
||||
break;
|
||||
case UV_PIXEL: {
|
||||
|
@ -33,4 +34,5 @@ void main(void) {
|
|||
|
||||
vTextureSize = st1 - st0;
|
||||
vTextureOffset = st0;
|
||||
vTileSpacing = image.stretch_size_and_tile_spacing.zw;
|
||||
}
|
||||
|
|
|
@ -11,17 +11,22 @@ void main(void) {
|
|||
|
||||
// We clamp the texture coordinate calculation here to the local rectangle boundaries,
|
||||
// which makes the edge of the texture stretch instead of repeat.
|
||||
vec2 uv = clamp(local_pos.xy, vLocalRect.xy, vLocalRect.xy + vLocalRect.zw);
|
||||
|
||||
uv = (uv - vLocalRect.xy) / vStretchSize;
|
||||
vec2 pos_for_texture =
|
||||
clamp(pos, vLocalRect.xy, vLocalRect.xy + vLocalRect.zw) - vLocalRect.xy;
|
||||
#else
|
||||
float alpha = 1;
|
||||
vec2 local_pos = vLocalPos;
|
||||
vec2 uv = vUv;
|
||||
vec2 relative_pos_in_rect = vLocalPos - vLocalRect.xy;
|
||||
#endif
|
||||
|
||||
vec2 st = vTextureOffset + vTextureSize * fract(uv);
|
||||
|
||||
alpha = min(alpha, do_clip(local_pos, vClipRect, vClipRadius));
|
||||
|
||||
// We calculate the particular tile this fragment belongs to, taking into
|
||||
// account the spacing in between tiles. We only paint if our fragment does
|
||||
// not fall into that spacing.
|
||||
vec2 position_in_tile = mod(relative_pos_in_rect, vStretchSize + vTileSpacing);
|
||||
vec2 st = vTextureOffset + ((position_in_tile / vStretchSize) * vTextureSize);
|
||||
alpha = alpha * float(all(bvec2(step(position_in_tile, vStretchSize))));
|
||||
|
||||
oFragColor = texture(sDiffuse, st) * vec4(1, 1, 1, alpha);
|
||||
}
|
||||
|
|
|
@ -4,13 +4,14 @@
|
|||
|
||||
flat varying vec2 vTextureOffset; // Offset of this image into the texture atlas.
|
||||
flat varying vec2 vTextureSize; // Size of the image in the texture atlas.
|
||||
flat varying vec2 vTileSpacing; // Amount of space between tiled instances of this image.
|
||||
flat varying vec2 vStretchSize;
|
||||
flat varying vec4 vClipRect;
|
||||
flat varying vec4 vClipRadius;
|
||||
flat varying vec4 vLocalRect;
|
||||
|
||||
#ifdef WR_FEATURE_TRANSFORM
|
||||
varying vec3 vLocalPos;
|
||||
flat varying vec4 vLocalRect;
|
||||
flat varying vec2 vStretchSize;
|
||||
#else
|
||||
varying vec2 vLocalPos;
|
||||
varying vec2 vUv; // Location within the CSS box to draw.
|
||||
|
|
|
@ -8,13 +8,11 @@ void main(void) {
|
|||
|
||||
#ifdef WR_FEATURE_TRANSFORM
|
||||
TransformVertexInfo vi = write_transform_vertex(image.info);
|
||||
vLocalRect = vi.clipped_local_rect;
|
||||
vLocalPos = vi.local_pos;
|
||||
vStretchSize = image.stretch_size_uvkind.xy;
|
||||
#else
|
||||
VertexInfo vi = write_vertex(image.info);
|
||||
vUv = (vi.local_clamped_pos - vi.local_rect.p0) / image.stretch_size_uvkind.xy;
|
||||
vLocalPos = vi.local_clamped_pos;
|
||||
vLocalRect = image.info.local_rect;
|
||||
#endif
|
||||
|
||||
vClipRect = vec4(image.clip.rect.xy, image.clip.rect.xy + image.clip.rect.zw);
|
||||
|
@ -26,7 +24,7 @@ void main(void) {
|
|||
vec2 st0 = image.st_rect.xy;
|
||||
vec2 st1 = image.st_rect.zw;
|
||||
|
||||
switch (uint(image.stretch_size_uvkind.z)) {
|
||||
switch (uint(image.uvkind.x)) {
|
||||
case UV_NORMALIZED:
|
||||
break;
|
||||
case UV_PIXEL: {
|
||||
|
@ -39,4 +37,6 @@ void main(void) {
|
|||
|
||||
vTextureSize = st1 - st0;
|
||||
vTextureOffset = st0;
|
||||
vStretchSize = image.stretch_size_and_tile_spacing.xy;
|
||||
vTileSpacing = image.stretch_size_and_tile_spacing.zw;
|
||||
}
|
||||
|
|
|
@ -48,9 +48,6 @@
|
|||
[WebGL test #45: getError expected: NO_ERROR. Was INVALID_OPERATION : after evaluating: gl.bindTexture(gl.TEXTURE_CUBE_MAP, texCubeMap)]
|
||||
expected: FAIL
|
||||
|
||||
[WebGL test #68: gl.getParameter(gl.RENDERBUFFER_BINDING) should be [object WebGLRenderbuffer\]. Was null.]
|
||||
expected: FAIL
|
||||
|
||||
[WebGL test #69: gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo) threw exception TypeError: gl.framebufferRenderbuffer is not a function]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -66,12 +63,6 @@
|
|||
[WebGL test #74: gl.isRenderbuffer(rbo) should be false. Threw exception TypeError: gl.isRenderbuffer is not a function]
|
||||
expected: FAIL
|
||||
|
||||
[WebGL test #79: gl.getParameter(gl.RENDERBUFFER_BINDING) should be [object WebGLRenderbuffer\]. Was null.]
|
||||
expected: FAIL
|
||||
|
||||
[WebGL test #81: gl.getParameter(gl.RENDERBUFFER_BINDING) should be [object WebGLRenderbuffer\]. Was null.]
|
||||
expected: FAIL
|
||||
|
||||
[WebGL test #83: gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, 16, 16) threw exception TypeError: gl.renderbufferStorage is not a function]
|
||||
expected: FAIL
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue