diff options
author | bors-servo <lbergstrom+bors@mozilla.com> | 2016-09-20 19:19:21 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-09-20 19:19:21 -0500 |
commit | 7c0dfd07ad5149406b389ca893d51f6fa442e98e (patch) | |
tree | ec17f52f39f2be23e00cb9372f5ce284c9ee0e4d | |
parent | 8a78e75d4314aa5ac770ec070a6ea7eed341e3ec (diff) | |
parent | 6b1104e7f64f947466366d6aee7db2e0ee95b298 (diff) | |
download | servo-7c0dfd07ad5149406b389ca893d51f6fa442e98e.tar.gz servo-7c0dfd07ad5149406b389ca893d51f6fa442e98e.zip |
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 -->
-rw-r--r-- | components/layout/webrender_helpers.rs | 3 | ||||
-rw-r--r-- | components/script/dom/webglframebuffer.rs | 7 | ||||
-rw-r--r-- | components/script/dom/webglrenderingcontext.rs | 62 | ||||
-rw-r--r-- | components/servo/Cargo.lock | 4 | ||||
-rw-r--r-- | ports/cef/Cargo.lock | 4 | ||||
-rw-r--r-- | resources/shaders/prim_shared.glsl | 24 | ||||
-rw-r--r-- | resources/shaders/ps_image.fs.glsl | 21 | ||||
-rw-r--r-- | resources/shaders/ps_image.glsl | 4 | ||||
-rw-r--r-- | resources/shaders/ps_image.vs.glsl | 8 | ||||
-rw-r--r-- | resources/shaders/ps_image_clip.fs.glsl | 17 | ||||
-rw-r--r-- | resources/shaders/ps_image_clip.glsl | 5 | ||||
-rw-r--r-- | resources/shaders/ps_image_clip.vs.glsl | 8 | ||||
-rw-r--r-- | tests/wpt/metadata/webgl/conformance-1.0.3/conformance/misc/object-deletion-behaviour.html.ini | 9 |
13 files changed, 128 insertions, 48 deletions
diff --git a/components/layout/webrender_helpers.rs b/components/layout/webrender_helpers.rs index c6da8f2d105..dacc8278306 100644 --- a/components/layout/webrender_helpers.rs +++ b/components/layout/webrender_helpers.rs @@ -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); } diff --git a/components/script/dom/webglframebuffer.rs b/components/script/dom/webglframebuffer.rs index 6e426fa0969..cd212d644bf 100644 --- a/components/script/dom/webglframebuffer.rs +++ b/components/script/dom/webglframebuffer.rs @@ -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() } diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs index 3fc63349634..f42eeab4dbf 100644 --- a/components/script/dom/webglrenderingcontext.rs +++ b/components/script/dom/webglrenderingcontext.rs @@ -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) diff --git a/components/servo/Cargo.lock b/components/servo/Cargo.lock index 647b62e58ae..e3b03e9f8bb 100644 --- a/components/servo/Cargo.lock +++ b/components/servo/Cargo.lock @@ -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)", diff --git a/ports/cef/Cargo.lock b/ports/cef/Cargo.lock index 92108a43380..6c5f8cd1394 100644 --- a/ports/cef/Cargo.lock +++ b/ports/cef/Cargo.lock @@ -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)", diff --git a/resources/shaders/prim_shared.glsl b/resources/shaders/prim_shared.glsl index 7336a400624..2220a7de34d 100644 --- a/resources/shaders/prim_shared.glsl +++ b/resources/shaders/prim_shared.glsl @@ -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; } diff --git a/resources/shaders/ps_image.fs.glsl b/resources/shaders/ps_image.fs.glsl index e787b9bbd65..b685e4772e1 100644 --- a/resources/shaders/ps_image.fs.glsl +++ b/resources/shaders/ps_image.fs.glsl @@ -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); - - uv = (uv - vLocalRect.xy) / vStretchSize; + vec2 relative_pos_in_rect = + clamp(pos, vLocalRect.xy, vLocalRect.xy + vLocalRect.zw) - vLocalRect.xy; #else - vec2 uv = vUv; + float alpha = 1.0;; + vec2 relative_pos_in_rect = vLocalPos; #endif - vec2 st = vTextureOffset + vTextureSize * fract(uv); -#ifdef WR_FEATURE_TRANSFORM + + // 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 = vec4(1, 1, 1, alpha) * texture(sDiffuse, st); -#else - oFragColor = texture(sDiffuse, st); -#endif } diff --git a/resources/shaders/ps_image.glsl b/resources/shaders/ps_image.glsl index b89a421789b..0ca0ac57211 100644 --- a/resources/shaders/ps_image.glsl +++ b/resources/shaders/ps_image.glsl @@ -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 diff --git a/resources/shaders/ps_image.vs.glsl b/resources/shaders/ps_image.vs.glsl index d146e98c8c3..1f5a997b0af 100644 --- a/resources/shaders/ps_image.vs.glsl +++ b/resources/shaders/ps_image.vs.glsl @@ -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; } diff --git a/resources/shaders/ps_image_clip.fs.glsl b/resources/shaders/ps_image_clip.fs.glsl index 2ee0f8ee0c7..62da5a846f9 100644 --- a/resources/shaders/ps_image_clip.fs.glsl +++ b/resources/shaders/ps_image_clip.fs.glsl @@ -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); } diff --git a/resources/shaders/ps_image_clip.glsl b/resources/shaders/ps_image_clip.glsl index 8b5f263fe72..fe4333271d9 100644 --- a/resources/shaders/ps_image_clip.glsl +++ b/resources/shaders/ps_image_clip.glsl @@ -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. diff --git a/resources/shaders/ps_image_clip.vs.glsl b/resources/shaders/ps_image_clip.vs.glsl index 6165f535ad9..3ba4bc3b49f 100644 --- a/resources/shaders/ps_image_clip.vs.glsl +++ b/resources/shaders/ps_image_clip.vs.glsl @@ -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; } diff --git a/tests/wpt/metadata/webgl/conformance-1.0.3/conformance/misc/object-deletion-behaviour.html.ini b/tests/wpt/metadata/webgl/conformance-1.0.3/conformance/misc/object-deletion-behaviour.html.ini index 3232baffd25..5561797ccd9 100644 --- a/tests/wpt/metadata/webgl/conformance-1.0.3/conformance/misc/object-deletion-behaviour.html.ini +++ b/tests/wpt/metadata/webgl/conformance-1.0.3/conformance/misc/object-deletion-behaviour.html.ini @@ -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 |