Bug 910953 - Implement the backend for a WebGL shader editor, r=dcamp,vlad

This commit is contained in:
Victor Porof 2013-09-09 23:33:25 +03:00
parent 8a430f311b
commit dfb7848eaa
24 changed files with 1997 additions and 13 deletions

View File

@ -5,24 +5,25 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DIRS += [
'inspector',
'markupview',
'webconsole',
'app-manager',
'commandline',
'debugger',
'fontinspector',
'framework',
'inspector',
'layoutview',
'markupview',
'netmonitor',
'profiler',
'responsivedesign',
'scratchpad',
'shadereditor',
'shared',
'sourceeditor',
'styleeditor',
'styleinspector',
'tilt',
'scratchpad',
'debugger',
'netmonitor',
'layoutview',
'shared',
'responsivedesign',
'framework',
'profiler',
'fontinspector',
'app-manager',
'webconsole',
]
EXTRA_COMPONENTS += [

View File

@ -0,0 +1,6 @@
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
TEST_DIRS += ['test']

View File

@ -0,0 +1,21 @@
[DEFAULT]
support-files =
doc_multiple-contexts.html
doc_shader-order.html
doc_simple-canvas.html
head.js
[browser_webgl-actor-test-01.js]
[browser_webgl-actor-test-02.js]
[browser_webgl-actor-test-03.js]
[browser_webgl-actor-test-04.js]
[browser_webgl-actor-test-05.js]
[browser_webgl-actor-test-06.js]
[browser_webgl-actor-test-07.js]
[browser_webgl-actor-test-08.js]
[browser_webgl-actor-test-09.js]
[browser_webgl-actor-test-10.js]
[browser_webgl-actor-test-11.js]
[browser_webgl-actor-test-12.js]
[browser_webgl-actor-test-13.js]
[browser_webgl-actor-test-14.js]

View File

@ -0,0 +1,17 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if a WebGL front can be created for a remote tab target.
*/
function ifWebGLSupported() {
let [target, debuggee, front] = yield initBackend(SIMPLE_CANVAS_URL);
ok(target, "Should have a target available.");
ok(debuggee, "Should have a debuggee available.");
ok(front, "Should have a protocol front available.");
yield removeTab(target.tab);
finish();
}

View File

@ -0,0 +1,19 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if notifications about WebGL programs being linked are not sent
* if the front wasn't set up first.
*/
function ifWebGLSupported() {
let [target, debuggee, front] = yield initBackend(SIMPLE_CANVAS_URL);
once(front, "program-linked").then(() => {
ok(false, "A 'program-linked' notification shouldn't have been sent!");
});
yield reload(target);
yield removeTab(target.tab);
finish();
}

View File

@ -0,0 +1,26 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if notifications about WebGL programs being linked are sent
* after a target navigation.
*/
function ifWebGLSupported() {
let [target, debuggee, front] = yield initBackend(SIMPLE_CANVAS_URL);
let navigated = once(target, "navigate");
let linked = once(front, "program-linked");
yield front.setup();
ok(true, "The front was setup up successfully.");
yield navigated;
ok(true, "Target automatically navigated when the front was set up.");
yield linked;
ok(true, "A 'program-linked' notification was sent after reloading.");
yield removeTab(target.tab);
finish();
}

View File

@ -0,0 +1,27 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if a program actor is sent when WebGL programs are linked,
* and that the corresponding vertex and fragment actors can be retrieved.
*/
function ifWebGLSupported() {
let [target, debuggee, front] = yield initBackend(SIMPLE_CANVAS_URL);
front.setup();
let programActor = yield once(front, "program-linked");
ok(programActor,
"A program actor was sent along with the 'program-linked' notification.")
let vertexShader = yield programActor.getVertexShader();
ok(programActor,
"A vertex shader actor was retrieved from the program actor.");
let fragmentShader = yield programActor.getFragmentShader();
ok(programActor,
"A fragment shader actor was retrieved from the program actor.");
yield removeTab(target.tab);
finish();
}

View File

@ -0,0 +1,27 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the source contents can be retrieved from the vertex and fragment
* shader actors.
*/
function ifWebGLSupported() {
let [target, debuggee, front] = yield initBackend(SIMPLE_CANVAS_URL);
front.setup();
let programActor = yield once(front, "program-linked");
let vertexShader = yield programActor.getVertexShader();
let fragmentShader = yield programActor.getFragmentShader();
let vertSource = yield vertexShader.getText();
ok(vertSource.contains("gl_Position"),
"The correct vertex shader source was retrieved.");
let fragSource = yield fragmentShader.getText();
ok(fragSource.contains("gl_FragColor"),
"The correct fragment shader source was retrieved.");
yield removeTab(target.tab);
finish();
}

View File

@ -0,0 +1,52 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the highlight/unhighlight operations on program actors
* work as expected.
*/
function ifWebGLSupported() {
let [target, debuggee, front] = yield initBackend(SIMPLE_CANVAS_URL);
front.setup();
let programActor = yield once(front, "program-linked");
let vertexShader = yield programActor.getVertexShader();
let fragmentShader = yield programActor.getFragmentShader();
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
yield checkShaderSource("The shader sources are correct before highlighting.");
ok(true, "The top left pixel color was correct before highlighting.");
yield programActor.highlight([0, 0, 1, 1]);
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 255, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 0, b: 255, a: 255 }, true);
yield checkShaderSource("The shader sources are preserved after highlighting.");
ok(true, "The top left pixel color is correct after highlighting.");
yield programActor.unhighlight();
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
yield checkShaderSource("The shader sources are correct after unhighlighting.");
ok(true, "The top left pixel color is correct after unhighlighting.");
function checkShaderSource(aMessage) {
return Task.spawn(function() {
let newVertexShader = yield programActor.getVertexShader();
let newFragmentShader = yield programActor.getFragmentShader();
is(vertexShader, newVertexShader,
"The same vertex shader actor was retrieved.");
is(fragmentShader, newFragmentShader,
"The same fragment shader actor was retrieved.");
let vertSource = yield newVertexShader.getText();
let fragSource = yield newFragmentShader.getText();
ok(vertSource.contains("I'm special!") &&
fragSource.contains("I'm also special!"), aMessage);
});
}
yield removeTab(target.tab);
finish();
}

View File

@ -0,0 +1,61 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that vertex and fragment shader sources can be changed.
*/
function ifWebGLSupported() {
let [target, debuggee, front] = yield initBackend(SIMPLE_CANVAS_URL);
front.setup();
let programActor = yield once(front, "program-linked");
let vertexShader = yield programActor.getVertexShader();
let fragmentShader = yield programActor.getFragmentShader();
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 128, y: 128 }, { r: 191, g: 64, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
let vertSource = yield vertexShader.getText();
let fragSource = yield fragmentShader.getText();
ok(!vertSource.contains("2.0"),
"The vertex shader source is correct before changing it.");
ok(!fragSource.contains("0.5"),
"The fragment shader source is correct before changing it.");
let newVertSource = vertSource.replace("1.0", "2.0");
let status = yield vertexShader.compile(newVertSource);
ok(!status,
"The new vertex shader source was compiled without errors.");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 128, y: 128 }, { r: 255, g: 0, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 0, b: 0, a: 255 }, true);
let vertSource = yield vertexShader.getText();
let fragSource = yield fragmentShader.getText();
ok(vertSource.contains("2.0"),
"The vertex shader source is correct after changing it.");
ok(!fragSource.contains("0.5"),
"The fragment shader source is correct after changing the vertex shader.");
let newFragSource = fragSource.replace("1.0", "0.5");
let status = yield fragmentShader.compile(newFragSource);
ok(!status,
"The new fragment shader source was compiled without errors.");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 128, y: 128 }, { r: 255, g: 0, b: 0, a: 127 }, true);
yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 0, b: 0, a: 255 }, true);
let vertSource = yield vertexShader.getText();
let fragSource = yield fragmentShader.getText();
ok(vertSource.contains("2.0"),
"The vertex shader source is correct after changing the fragment shader.");
ok(fragSource.contains("0.5"),
"The fragment shader source is correct after changing it.");
yield removeTab(target.tab);
finish();
}

View File

@ -0,0 +1,37 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the rendering is updated when a varying variable is
* changed in one shader.
*/
function ifWebGLSupported() {
let [target, debuggee, front] = yield initBackend(SIMPLE_CANVAS_URL);
front.setup();
let programActor = yield once(front, "program-linked");
let vertexShader = yield programActor.getVertexShader();
let fragmentShader = yield programActor.getFragmentShader();
let oldVertSource = yield vertexShader.getText();
let newVertSource = oldVertSource.replace("= aVertexColor", "= vec3(0, 0, 1)");
let status = yield vertexShader.compile(newVertSource);
ok(!status,
"The new vertex shader source was compiled without errors.");
yield waitForFrame(debuggee);
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 255, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 128, y: 128 }, { r: 0, g: 0, b: 255, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 0, b: 255, a: 255 }, true);
let vertSource = yield vertexShader.getText();
let fragSource = yield fragmentShader.getText();
ok(vertSource.contains("vFragmentColor = vec3(0, 0, 1);"),
"The vertex shader source is correct after changing it.");
ok(fragSource.contains("gl_FragColor = vec4(vFragmentColor, 1.0);"),
"The fragment shader source is correct after changing the vertex shader.");
yield removeTab(target.tab);
finish();
}

View File

@ -0,0 +1,85 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that errors are properly handled when trying to compile a
* defective shader source.
*/
function ifWebGLSupported() {
let [target, debuggee, front] = yield initBackend(SIMPLE_CANVAS_URL);
front.setup();
let programActor = yield once(front, "program-linked");
let vertexShader = yield programActor.getVertexShader();
let fragmentShader = yield programActor.getFragmentShader();
let oldVertSource = yield vertexShader.getText();
let newVertSource = oldVertSource.replace("vec4", "vec3");
try {
yield vertexShader.compile(newVertSource);
ok(false, "Vertex shader was compiled with a defective source!");
} catch (error) {
ok(error,
"The new vertex shader source was compiled with errors.");
is(error.compile, "",
"The compilation status should be empty.");
isnot(error.link, "",
"The linkage status should not be empty.");
is(error.link.split("ERROR").length - 1, 2,
"The linkage status contains two errors.");
ok(error.link.contains("ERROR: 0:8: 'constructor'"),
"A constructor error is contained in the linkage status.");
ok(error.link.contains("ERROR: 0:8: 'assign'"),
"An assignment error is contained in the linkage status.");
}
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
ok(true, "The shader was reverted to the old source.");
let vertSource = yield vertexShader.getText();
ok(vertSource.contains("vec4(aVertexPosition, 1.0);"),
"The previous correct vertex shader source was preserved.");
let oldFragSource = yield fragmentShader.getText();
let newFragSource = oldFragSource.replace("vec3", "vec4");
try {
yield fragmentShader.compile(newFragSource);
ok(false, "Fragment shader was compiled with a defective source!");
} catch (error) {
ok(error,
"The new fragment shader source was compiled with errors.");
is(error.compile, "",
"The compilation status should be empty.");
isnot(error.link, "",
"The linkage status should not be empty.");
is(error.link.split("ERROR").length - 1, 1,
"The linkage status contains one error.");
ok(error.link.contains("ERROR: 0:6: 'constructor'"),
"A constructor error is contained in the linkage status.");
}
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
ok(true, "The shader was reverted to the old source.");
let fragSource = yield fragmentShader.getText();
ok(fragSource.contains("vec3 vFragmentColor;"),
"The previous correct fragment shader source was preserved.");
yield programActor.highlight([0, 0, 1, 1]);
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 255, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 0, b: 255, a: 255 }, true);
ok(true, "Highlighting worked after setting a defective fragment source.");
yield programActor.unhighlight();
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
yield ensurePixelIs(debuggee, { x: 511, y: 511 }, { r: 0, g: 255, b: 0, a: 255 }, true);
ok(true, "Unhighlighting worked after setting a defective vertex source.");
yield removeTab(target.tab);
finish();
}

View File

@ -0,0 +1,44 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if the WebGL context is correctly instrumented every time the
* target navigates.
*/
function ifWebGLSupported() {
let [target, debuggee, front] = yield initBackend(SIMPLE_CANVAS_URL);
let linked = once(front, "program-linked");
yield front.setup();
yield linked;
ok(true, "Canvas was correctly instrumented on the first navigation.");
let linked = once(front, "program-linked");
yield reload(target);
yield linked;
ok(true, "Canvas was correctly instrumented on the second navigation.");
let linked = once(front, "program-linked");
yield reload(target);
yield linked;
ok(true, "Canvas was correctly instrumented on the third navigation.");
let programActor = yield linked;
let vertexShader = yield programActor.getVertexShader();
let fragmentShader = yield programActor.getFragmentShader();
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
ok(true, "The top left pixel color was correct before highlighting.");
yield programActor.highlight([0, 0, 1, 1]);
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 0, b: 255, a: 255 }, true);
ok(true, "The top left pixel color is correct after highlighting.");
yield programActor.unhighlight();
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true);
ok(true, "The top left pixel color is correct after unhighlighting.");
yield removeTab(target.tab);
finish();
}

View File

@ -0,0 +1,25 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if the WebGL context is never instrumented anymore after the
* finalize method is called.
*/
function ifWebGLSupported() {
let [target, debuggee, front] = yield initBackend(SIMPLE_CANVAS_URL);
let linked = once(front, "program-linked");
yield front.setup();
yield linked;
ok(true, "Canvas was correctly instrumented on the first navigation.");
once(front, "program-linked").then(() => {
ok(false, "A 'program-linked' notification shouldn't have been sent!");
});
yield front.finalize();
yield reload(target);
yield removeTab(target.tab);
finish();
}

View File

@ -0,0 +1,27 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the correct vertex and fragment shader sources are retrieved
* regardless of the order in which they were compiled and attached.
*/
function ifWebGLSupported() {
let [target, debuggee, front] = yield initBackend(SHADER_ORDER_URL);
front.setup();
let programActor = yield once(front, "program-linked");
let vertexShader = yield programActor.getVertexShader();
let fragmentShader = yield programActor.getFragmentShader();
let vertSource = yield vertexShader.getText();
let fragSource = yield fragmentShader.getText();
ok(vertSource.contains("I'm a vertex shader!"),
"The correct vertex shader text was retrieved.");
ok(fragSource.contains("I'm a fragment shader!"),
"The correct fragment shader text was retrieved.");
yield removeTab(target.tab);
finish();
}

View File

@ -0,0 +1,68 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if multiple WebGL contexts are correctly handled.
*/
function ifWebGLSupported() {
let [target, debuggee, front] = yield initBackend(MULTIPLE_CONTEXTS_URL);
front.setup();
let firstProgramActor = yield once(front, "program-linked");
let secondProgramActor = yield once(front, "program-linked");
isnot(firstProgramActor, secondProgramActor,
"Two distinct program actors were recevide from two separate contexts.");
let firstVertexShader = yield firstProgramActor.getVertexShader();
let firstFragmentShader = yield firstProgramActor.getFragmentShader();
let secondVertexShader = yield secondProgramActor.getVertexShader();
let secondFragmentShader = yield secondProgramActor.getFragmentShader();
isnot(firstVertexShader, secondVertexShader,
"The two programs should have distinct vertex shaders.");
isnot(firstFragmentShader, secondFragmentShader,
"The two programs should have distinct fragment shaders.");
let firstVertSource = yield firstVertexShader.getText();
let firstFragSource = yield firstFragmentShader.getText();
let secondVertSource = yield secondVertexShader.getText();
let secondFragSource = yield secondFragmentShader.getText();
is(firstVertSource, secondVertSource,
"The vertex shaders should have identical sources.");
is(firstFragSource, secondFragSource,
"The vertex shaders should have identical sources.");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
ok(true, "The two canvases are correctly drawn.");
yield firstProgramActor.highlight([1, 0, 0, 1]);
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
ok(true, "The first canvas was correctly filled after highlighting.");
yield secondProgramActor.highlight([0, 1, 0, 1]);
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 0, a: 255 }, true, "#canvas2");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 0, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 0, a: 255 }, true, "#canvas2");
ok(true, "The second canvas was correctly filled after highlighting.");
yield firstProgramActor.unhighlight();
yield secondProgramActor.unhighlight();
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 255, g: 255, b: 0, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
ok(true, "The two canvases were correctly filled after unhighlighting.");
yield removeTab(target.tab);
finish();
}

View File

@ -0,0 +1,46 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that the rendering is updated when a uniform variable is
* changed in one shader of a page with multiple WebGL contexts.
*/
function ifWebGLSupported() {
let [target, debuggee, front] = yield initBackend(MULTIPLE_CONTEXTS_URL);
front.setup();
let firstProgramActor = yield once(front, "program-linked");
let secondProgramActor = yield once(front, "program-linked");
let firstFragmentShader = yield firstProgramActor.getFragmentShader();
let secondFragmentShader = yield secondProgramActor.getFragmentShader();
let oldFragSource = yield firstFragmentShader.getText();
let newFragSource = oldFragSource.replace("vec4(uColor", "vec4(0.25, 0.25, 0.25");
let status = yield firstFragmentShader.compile(newFragSource);
ok(!status,
"The first new fragment shader source was compiled without errors.");
yield waitForFrame(debuggee);
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 64, g: 64, b: 64, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 64, g: 64, b: 64, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 0, g: 255, b: 255, a: 255 }, true, "#canvas2");
ok(true, "The first fragment shader was changed.");
let oldFragSource = yield secondFragmentShader.getText();
let newFragSource = oldFragSource.replace("vec4(uColor", "vec4(0.75, 0.75, 0.75");
let status = yield secondFragmentShader.compile(newFragSource);
ok(!status,
"The second new fragment shader source was compiled without errors.");
yield waitForFrame(debuggee);
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 64, g: 64, b: 64, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 64, g: 64, b: 64, a: 255 }, true, "#canvas1");
yield ensurePixelIs(debuggee, { x: 0, y: 0 }, { r: 191, g: 191, b: 191, a: 255 }, true, "#canvas2");
yield ensurePixelIs(debuggee, { x: 127, y: 127 }, { r: 191, g: 191, b: 191, a: 255 }, true, "#canvas2");
ok(true, "The second fragment shader was changed.");
yield removeTab(target.tab);
finish();
}

View File

@ -0,0 +1,112 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>WebGL editor test page</title>
<script id="shader-vs" type="x-shader/x-vertex">
precision lowp float;
attribute vec3 aVertexPosition;
void main(void) {
gl_Position = vec4(aVertexPosition, 1);
}
</script>
<script id="shader-fs" type="x-shader/x-fragment">
precision lowp float;
uniform vec3 uColor;
void main(void) {
gl_FragColor = vec4(uColor, 1);
}
</script>
</head>
<body>
<canvas id="canvas1" width="128" height="128"></canvas>
<canvas id="canvas2" width="128" height="128"></canvas>
<script type="text/javascript;version=1.8">
"use strict";
let canvas = [], gl = [];
let program = [];
let squareVerticesPositionBuffer = [];
let vertexPositionAttribute = [];
let colorUniform = [];
window.onload = function() {
for (let i = 0; i < 2; i++) {
canvas[i] = document.querySelector("#canvas" + (i + 1));
gl[i] = canvas[i].getContext("webgl");
gl[i].clearColor(0.0, 0.0, 0.0, 1.0);
initProgram(i);
initBuffers(i);
drawScene(i);
}
}
function initProgram(i) {
let vertexShader = getShader(gl[i], "shader-vs");
let fragmentShader = getShader(gl[i], "shader-fs");
program[i] = gl[i].createProgram();
gl[i].attachShader(program[i], vertexShader);
gl[i].attachShader(program[i], fragmentShader);
gl[i].linkProgram(program[i]);
vertexPositionAttribute[i] = gl[i].getAttribLocation(program[i], "aVertexPosition");
gl[i].enableVertexAttribArray(vertexPositionAttribute[i]);
colorUniform[i] = gl[i].getUniformLocation(program[i], "uColor");
}
function getShader(gl, id) {
let script = document.getElementById(id);
let source = script.textContent;
let shader;
if (script.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (script.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
}
gl.shaderSource(shader, source);
gl.compileShader(shader);
return shader;
}
function initBuffers(i) {
squareVerticesPositionBuffer[i] = gl[i].createBuffer();
gl[i].bindBuffer(gl[i].ARRAY_BUFFER, squareVerticesPositionBuffer[i]);
gl[i].bufferData(gl[i].ARRAY_BUFFER, new Float32Array([
1.0, 1.0, 0.0,
-1.0, 1.0, 0.0,
1.0, -1.0, 0.0,
-1.0, -1.0, 0.0
]), gl[i].STATIC_DRAW);
}
function drawScene(i) {
gl[i].clear(gl[i].COLOR_BUFFER_BIT);
gl[i].bindBuffer(gl[i].ARRAY_BUFFER, squareVerticesPositionBuffer[i]);
gl[i].vertexAttribPointer(vertexPositionAttribute[i], 3, gl[i].FLOAT, false, 0, 0);
gl[i].useProgram(program[i]);
gl[i].uniform3fv(colorUniform[i], i == 0 ? [1, 1, 0] : [0, 1, 1]);
gl[i].drawArrays(gl[i].TRIANGLE_STRIP, 0, 4);
window.requestAnimationFrame(() => drawScene(i));
}
</script>
</body>
</html>

View File

@ -0,0 +1,83 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>WebGL editor test page</title>
<script id="shader-vs" type="x-shader/x-vertex">
precision lowp float;
void main(void) {
gl_Position = vec4(0, 0, 0, 1); // I'm a vertex shader!
}
</script>
<script id="shader-fs" type="x-shader/x-fragment">
precision lowp float;
varying vec3 vFragmentColor;
void main(void) {
gl_FragColor = vec4(1, 0, 0, 1); // I'm a fragment shader!
}
</script>
</head>
<body>
<canvas width="512" height="512"></canvas>
<script type="text/javascript;version=1.8">
"use strict";
let canvas, gl;
window.onload = function() {
canvas = document.querySelector("canvas");
gl = canvas.getContext("webgl");
let shaderProgram = gl.createProgram();
let vertexShader, fragmentShader;
// Compile and attach the shaders in a random order. The test will
// ensure that the correct vertex and fragment source is retrieved
// regardless of this crazyness.
if (Math.random() > 0.5) {
vertexShader = getShader(gl, "shader-vs");
fragmentShader = getShader(gl, "shader-fs");
} else {
fragmentShader = getShader(gl, "shader-fs");
vertexShader = getShader(gl, "shader-vs");
}
if (Math.random() > 0.5) {
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
} else {
gl.attachShader(shaderProgram, fragmentShader);
gl.attachShader(shaderProgram, vertexShader);
}
gl.linkProgram(shaderProgram);
}
function getShader(gl, id) {
let script = document.getElementById(id);
let source = script.textContent;
let shader;
if (script.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (script.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
}
gl.shaderSource(shader, source);
gl.compileShader(shader);
return shader;
}
</script>
</body>
</html>

View File

@ -0,0 +1,125 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>WebGL editor test page</title>
<script id="shader-vs" type="x-shader/x-vertex">
precision lowp float;
attribute vec3 aVertexPosition;
attribute vec3 aVertexColor;
varying vec3 vFragmentColor;
void main(void) {
gl_Position = vec4(aVertexPosition, 1.0);
vFragmentColor = aVertexColor; // I'm special!
}
</script>
<script id="shader-fs" type="x-shader/x-fragment">
precision lowp float;
varying vec3 vFragmentColor;
void main(void) {
gl_FragColor = vec4(vFragmentColor, 1.0); // I'm also special!
}
</script>
</head>
<body>
<canvas width="512" height="512"></canvas>
<script type="text/javascript;version=1.8">
"use strict";
let canvas, gl;
let program;
let squareVerticesPositionBuffer;
let squareVerticesColorBuffer;
let vertexPositionAttribute;
let vertexColorAttribute;
window.onload = function() {
canvas = document.querySelector("canvas");
gl = canvas.getContext("webgl");
gl.clearColor(0.0, 0.0, 0.0, 1.0);
initProgram();
initBuffers();
drawScene();
}
function initProgram() {
let vertexShader = getShader(gl, "shader-vs");
let fragmentShader = getShader(gl, "shader-fs");
program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
vertexPositionAttribute = gl.getAttribLocation(program, "aVertexPosition");
gl.enableVertexAttribArray(vertexPositionAttribute);
vertexColorAttribute = gl.getAttribLocation(program, "aVertexColor");
gl.enableVertexAttribArray(vertexColorAttribute);
}
function getShader(gl, id) {
let script = document.getElementById(id);
let source = script.textContent;
let shader;
if (script.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (script.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
}
gl.shaderSource(shader, source);
gl.compileShader(shader);
return shader;
}
function initBuffers() {
squareVerticesPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesPositionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
1.0, 1.0, 0.0,
-1.0, 1.0, 0.0,
1.0, -1.0, 0.0,
-1.0, -1.0, 0.0
]), gl.STATIC_DRAW);
squareVerticesColorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesColorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
1.0, 1.0, 1.0, 1.0,
1.0, 0.0, 0.0, 1.0,
0.0, 1.0, 0.0, 1.0,
0.0, 0.0, 1.0, 1.0
]), gl.STATIC_DRAW);
}
function drawScene() {
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesPositionBuffer);
gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesColorBuffer);
gl.vertexAttribPointer(vertexColorAttribute, 4, gl.FLOAT, false, 0, 0);
gl.useProgram(program);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
window.requestAnimationFrame(drawScene);
}
</script>
</body>
</html>

View File

@ -0,0 +1,217 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
// Enable logging for all the tests. Both the debugger server and frontend will
// be affected by this pref.
let gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
Services.prefs.setBoolPref("devtools.debugger.log", true);
let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
let { Promise: promise } = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
let { WebGLFront } = devtools.require("devtools/server/actors/webgl");
let TiltGL = devtools.require("devtools/tilt/tilt-gl");
let TargetFactory = devtools.TargetFactory;
let Toolbox = devtools.Toolbox;
const EXAMPLE_URL = "http://example.com/browser/browser/devtools/shadereditor/test/";
const SIMPLE_CANVAS_URL = EXAMPLE_URL + "doc_simple-canvas.html";
const SHADER_ORDER_URL = EXAMPLE_URL + "doc_shader-order.html";
const MULTIPLE_CONTEXTS_URL = EXAMPLE_URL + "doc_multiple-contexts.html";
// All tests are asynchronous.
waitForExplicitFinish();
registerCleanupFunction(() => {
info("finish() was called, cleaning up...");
Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
});
function addTab(aUrl, aWindow) {
info("Adding tab: " + aUrl);
let deferred = promise.defer();
let targetWindow = aWindow || window;
let targetBrowser = targetWindow.gBrowser;
targetWindow.focus();
let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl);
let linkedBrowser = tab.linkedBrowser;
linkedBrowser.addEventListener("load", function onLoad() {
linkedBrowser.removeEventListener("load", onLoad, true);
info("Tab added and finished loading: " + aUrl);
deferred.resolve(tab);
}, true);
return deferred.promise;
}
function removeTab(aTab, aWindow) {
info("Removing tab.");
let deferred = promise.defer();
let targetWindow = aWindow || window;
let targetBrowser = targetWindow.gBrowser;
let tabContainer = targetBrowser.tabContainer;
tabContainer.addEventListener("TabClose", function onClose(aEvent) {
tabContainer.removeEventListener("TabClose", onClose, false);
info("Tab removed and finished closing.");
deferred.resolve();
}, false);
targetBrowser.removeTab(aTab);
return deferred.promise;
}
function handleError(aError) {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
finish();
}
function ifWebGLSupported() {
ok(false, "You need to define a 'ifWebGLSupported' function.");
finish();
}
function ifWebGLUnsupported() {
todo(false, "Skipping test because WebGL isn't supported.");
finish();
}
function test() {
let generator = isWebGLSupported() ? ifWebGLSupported : ifWebGLUnsupported;
Task.spawn(generator).then(null, handleError);
}
function createCanvas() {
return document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
}
function isWebGLSupported() {
let supported =
!TiltGL.isWebGLForceEnabled() &&
TiltGL.isWebGLSupported() &&
TiltGL.create3DContext(createCanvas());
info("Apparently, WebGL is" + (supported ? "" : " not") + " supported.");
return supported;
}
function once(aTarget, aEventName, aUseCapture = false) {
info("Waiting for event: '" + aEventName + "' on " + aTarget + ".");
let deferred = promise.defer();
for (let [add, remove] of [
["addEventListener", "removeEventListener"],
["addListener", "removeListener"],
["on", "off"]
]) {
if ((add in aTarget) && (remove in aTarget)) {
aTarget[add](aEventName, function onEvent(...aArgs) {
aTarget[remove](aEventName, onEvent, aUseCapture);
deferred.resolve.apply(deferred, aArgs);
}, aUseCapture);
break;
}
}
return deferred.promise;
}
function waitForFrame(aDebuggee) {
let deferred = promise.defer();
aDebuggee.mozRequestAnimationFrame(deferred.resolve);
return deferred.promise;
}
function isApprox(aFirst, aSecond, aMargin = 1) {
return Math.abs(aFirst - aSecond) <= aMargin;
}
function isApproxColor(aFirst, aSecond, aMargin) {
return isApprox(aFirst.r, aSecond.r, aMargin) &&
isApprox(aFirst.g, aSecond.g, aMargin) &&
isApprox(aFirst.b, aSecond.b, aMargin) &&
isApprox(aFirst.a, aSecond.a, aMargin);
}
function getPixels(aDebuggee, aSelector = "canvas") {
let canvas = aDebuggee.document.querySelector(aSelector);
let gl = canvas.getContext("webgl");
let { width, height } = canvas;
let buffer = new aDebuggee.Uint8Array(width * height * 4);
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buffer);
info("Retrieved pixels: " + width + "x" + height);
return [buffer, width, height];
}
function getPixel(aDebuggee, aPosition, aSelector = "canvas") {
let canvas = aDebuggee.document.querySelector(aSelector);
let gl = canvas.getContext("webgl");
let { width, height } = canvas;
let { x, y } = aPosition;
let buffer = new aDebuggee.Uint8Array(4);
gl.readPixels(x, height - y - 1, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, buffer);
let pixel = { r: buffer[0], g: buffer[1], b: buffer[2], a: buffer[3] };
info("Retrieved pixel: " + pixel.toSource() + " at " + aPosition.toSource());
return pixel;
}
function ensurePixelIs(aDebuggee, aPosition, aColor, aWaitFlag = false, aSelector = "canvas") {
let pixel = getPixel(aDebuggee, aPosition, aSelector);
if (isApproxColor(pixel, aColor)) {
ok(true, "Expected pixel is shown at: " + aPosition.toSource());
return promise.resolve(null);
}
if (aWaitFlag) {
return Task.spawn(function() {
yield waitForFrame(aDebuggee);
yield ensurePixelIs(aDebuggee, aPosition, aColor, aWaitFlag, aSelector);
});
}
ok(false, "Expected pixel was not already shown at: " + aPosition.toSource());
return promise.reject(null);
}
function reload(aTarget) {
let navigated = once(aTarget, "navigate");
aTarget.client.activeTab.reload();
return navigated;
}
function initBackend(aUrl) {
info("Initializing a shader editor front.");
if (!DebuggerServer.initialized) {
DebuggerServer.init(() => true);
DebuggerServer.addBrowserActors();
}
return Task.spawn(function*() {
let tab = yield addTab(aUrl);
let target = TargetFactory.forTab(tab);
let debuggee = target.window.wrappedJSObject;
yield target.makeRemote();
let front = new WebGLFront(target.client, target.form);
return [target, debuggee, front];
});
}

View File

@ -0,0 +1,6 @@
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
BROWSER_CHROME_MANIFESTS += ['browser.ini']

View File

@ -0,0 +1,850 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {Cc, Ci, Cu, Cr} = require("chrome");
Cu.import("resource://gre/modules/Services.jsm");
const events = require("sdk/event/core");
const protocol = require("devtools/server/protocol");
const { on, once, off, emit } = events;
const { method, Arg, Option, RetVal } = protocol;
const WEBGL_CONTEXT_NAMES = ["webgl", "experimental-webgl", "moz-webgl"];
const HIGHLIGHT_FRAG_SHADER = [
"precision lowp float;",
"void main() {",
"gl_FragColor.rgba = vec4(%color);",
"}"
].join("\n");
exports.register = function(handle) {
handle.addTabActor(WebGLActor, "webglActor");
}
exports.unregister = function(handle) {
handle.removeTabActor(WebGLActor);
}
/**
* A WebGL Shader contributing to building a WebGL Program.
* You can either retrieve, or compile the source of a shader, which will
* automatically inflict the necessary changes to the WebGL state.
*/
let ShaderActor = protocol.ActorClass({
typeName: "gl-shader",
initialize: function(conn, id) {
protocol.Actor.prototype.initialize.call(this, conn);
},
/**
* Gets the source code for this shader.
*/
getText: method(function() {
return this.text;
}, {
response: { text: RetVal("string") }
}),
/**
* Sets and compiles new source code for this shader.
*/
compile: method(function(text) {
// Get the shader and corresponding program to change via the WebGL proxy.
let { context, shader, program, observer: { proxy } } = this;
// Get the new shader source to inject.
let oldText = this.text;
let newText = text;
// Overwrite the shader's source.
let error = proxy.call("compileShader", context, program, shader, this.text = newText);
// If something went wrong, revert to the previous shader.
if (error.compile || error.link) {
proxy.call("compileShader", context, program, shader, this.text = oldText);
return error;
}
}, {
request: { text: Arg(0, "string") },
response: { error: RetVal("nullable:json") }
})
});
/**
* The corresponding Front object for the ShaderActor.
*/
let ShaderFront = protocol.FrontClass(ShaderActor, {
initialize: function(client, form) {
protocol.Front.prototype.initialize.call(this, client, form);
}
});
/**
* A WebGL program is composed (at the moment, analogue to OpenGL ES 2.0)
* of two shaders: a vertex shader and a fragment shader.
*/
let ProgramActor = protocol.ActorClass({
typeName: "gl-program",
initialize: function(conn, id) {
protocol.Actor.prototype.initialize.call(this, conn);
this._shaderActorsCache = { vertex: null, fragment: null };
},
/**
* Gets the vertex shader linked to this program. This method guarantees
* a single actor instance per shader.
*/
getVertexShader: method(function() {
return this._getShaderActor("vertex");
}, {
response: { shader: RetVal("gl-shader") }
}),
/**
* Gets the fragment shader linked to this program. This method guarantees
* a single actor instance per shader.
*/
getFragmentShader: method(function() {
return this._getShaderActor("fragment");
}, {
response: { shader: RetVal("gl-shader") }
}),
/**
* Replaces this program's fragment shader with an temporary
* easy-to-distinguish alternative. See HIGHLIGHT_FRAG_SHADER.
*/
highlight: method(function(color) {
let shaderActor = this._getShaderActor("fragment");
let oldText = shaderActor.text;
let newText = HIGHLIGHT_FRAG_SHADER.replace("%color", color)
shaderActor.compile(newText);
shaderActor.text = oldText;
}, {
request: { color: Arg(0, "array:string") },
oneway: true
}),
/**
* Reverts this program's fragment shader to the latest user-defined source.
*/
unhighlight: method(function() {
let shaderActor = this._getShaderActor("fragment");
shaderActor.compile(shaderActor.text);
}, {
oneway: true
}),
/**
* Returns a cached ShaderActor instance based on the required shader type.
*
* @param string type
* Either "vertex" or "fragment".
* @return ShaderActor
* The respective shader actor instance.
*/
_getShaderActor: function(type) {
if (this._shaderActorsCache[type]) {
return this._shaderActorsCache[type];
}
let shaderActor = new ShaderActor(this.conn);
shaderActor.context = this.context;
shaderActor.observer = this.observer;
shaderActor.program = this.program;
shaderActor.shader = this.shadersData[type].ref;
shaderActor.text = this.shadersData[type].text;
return this._shaderActorsCache[type] = shaderActor;
}
});
/**
* The corresponding Front object for the ProgramActor.
*/
let ProgramFront = protocol.FrontClass(ProgramActor, {
initialize: function(client, form) {
protocol.Front.prototype.initialize.call(this, client, form);
}
});
/**
* The WebGL Actor handles simple interaction with a WebGL context via a few
* high-level methods. After instantiating this actor, you'll need to set it
* up by calling setup().
*/
let WebGLActor = exports.WebGLActor = protocol.ActorClass({
typeName: "webgl",
initialize: function(conn, tabActor) {
protocol.Actor.prototype.initialize.call(this, conn);
this.tabActor = tabActor;
this._onGlobalCreated = this._onGlobalCreated.bind(this);
this._onProgramLinked = this._onProgramLinked.bind(this);
},
destroy: function(conn) {
protocol.Actor.prototype.destroy.call(this, conn);
this.finalize();
},
/**
* Starts waiting for the current tab actor's document global to be
* created, in order to instrument the Canvas context and become
* aware of everything the content does WebGL-wise.
*
* See ContentObserver and WebGLInstrumenter for more details.
*/
setup: method(function() {
if (this._initialized) {
return;
}
this._initialized = true;
this._contentObserver = new ContentObserver(this.tabActor);
this._webglObserver = new WebGLObserver();
on(this._contentObserver, "global-created", this._onGlobalCreated);
on(this._webglObserver, "program-linked", this._onProgramLinked);
this.tabActor.window.location.reload();
}, {
oneway: true
}),
/**
* Stops listening for document global changes and puts this actor
* to hibernation. This method is called automatically just before the
* actor is destroyed.
*/
finalize: method(function() {
if (!this._initialized) {
return;
}
this._initialized = false;
this._contentObserver.stopListening();
off(this._contentObserver, "global-created", this._onGlobalCreated);
off(this._webglObserver, "program-linked", this._onProgramLinked);
}, {
oneway: true
}),
/**
* Events emitted by this actor. The "program-linked" event is fired
* every time a WebGL program was linked with its respective two shaders.
*/
events: {
"program-linked": {
type: "programLinked",
program: Arg(0, "gl-program")
}
},
/**
* Invoked whenever the current tab actor's document global is created.
*/
_onGlobalCreated: function(window) {
WebGLInstrumenter.handle(window, this._webglObserver);
},
/**
* Invoked whenever the current WebGL context links a program.
*/
_onProgramLinked: function(gl, program, shaders) {
let observer = this._webglObserver;
let shadersData = { vertex: null, fragment: null };
for (let shader of shaders) {
let text = observer.cache.call("getShaderInfo", shader);
let data = { ref: shader, text: text };
// Make sure the shader data object always contains the vertex shader
// first, and the fragment shader second. There are no guarantees that
// the compilation order of shaders in the debuggee is always the same.
if (gl.getShaderParameter(shader, gl.SHADER_TYPE) == gl.VERTEX_SHADER) {
shadersData.vertex = data;
} else {
shadersData.fragment = data;
}
}
let programActor = new ProgramActor(this.conn);
programActor.context = gl;
programActor.observer = observer;
programActor.program = program;
programActor.shadersData = shadersData;
events.emit(this, "program-linked", programActor);
}
});
/**
* The corresponding Front object for the WebGLActor.
*/
let WebGLFront = exports.WebGLFront = protocol.FrontClass(WebGLActor, {
initialize: function(client, { webglActor }) {
protocol.Front.prototype.initialize.call(this, client, { actor: webglActor });
client.addActorPool(this);
this.manage(this);
}
});
/**
* Handles adding an observer for the creation of content document globals,
* event sent immediately after a web content document window has been set up,
* but before any script code has been executed. This will allow us to
* instrument the HTMLCanvasElement with the appropriate inspection methods.
*/
function ContentObserver(tabActor) {
this._contentWindow = tabActor.browser.contentWindow;
this._onContentGlobalCreated = this._onContentGlobalCreated.bind(this);
this.startListening();
}
ContentObserver.prototype = {
/**
* Starts listening for the required observer messages.
*/
startListening: function() {
Services.obs.addObserver(
this._onContentGlobalCreated, "content-document-global-created", false);
},
/**
* Stops listening for the required observer messages.
*/
stopListening: function() {
Services.obs.removeObserver(
this._onContentGlobalCreated, "content-document-global-created", false);
},
/**
* Fired immediately after a web content document window has been set up.
*/
_onContentGlobalCreated: function(subject, topic, data) {
if (subject == this._contentWindow) {
emit(this, "global-created", subject);
}
}
};
/**
* Instruments a HTMLCanvasElement with the appropriate inspection methods.
*/
let WebGLInstrumenter = {
/**
* Overrides the getContext method in the HTMLCanvasElement prototype.
*
* @param nsIDOMWindow window
* The window to perform the instrumentation in.
* @param WebGLObserver observer
* The observer watching function calls in the context.
*/
handle: function(window, observer) {
let self = this;
let canvasElem = XPCNativeWrapper.unwrap(window.HTMLCanvasElement);
let canvasPrototype = canvasElem.prototype;
let originalGetContext = canvasPrototype.getContext;
/**
* Returns a drawing context on the canvas, or null if the context ID is
* not supported. This override creates an observer for the targeted context
* type and instruments specific functions in the targeted context instance.
*/
canvasPrototype.getContext = function(name, options) {
// Make sure a context was able to be created.
let context = originalGetContext.call(this, name, options);
if (!context) {
return context;
}
// Make sure a WebGL (not a 2D) context will be instrumented.
if (WEBGL_CONTEXT_NAMES.indexOf(name) == -1) {
return context;
}
// Link our observer to the new WebGL context methods.
for (let { timing, callback, functions } of self._methods) {
for (let func of functions) {
self._instrument(observer, context, func, timing, callback);
}
}
// Return the decorated context back to the content consumer, which
// will continue using it normally.
return context;
};
},
/**
* Overrides a specific method in a HTMLCanvasElement context.
*
* @param WebGLObserver observer
* The observer watching function calls in the context.
* @param WebGLRenderingContext context
* The targeted context instance.
* @param string funcName
* The function to override.
* @param string timing [optional]
* When to issue the callback in relation to the actual context
* function call. Availalble values are "before" and "after" (default).
* @param string callbackName [optional]
* A custom callback function name in the observer. If unspecified,
* it will default to the name of the function to override.
*/
_instrument: function(observer, context, funcName, timing, callbackName) {
let originalFunc = context[funcName];
context[funcName] = function() {
let glArgs = Array.slice(arguments);
let glResult, glBreak;
if (timing == "before" && !observer.suppressHandlers) {
glBreak = observer.call(callbackName || funcName, context, glArgs);
if (glBreak) return;
}
glResult = originalFunc.apply(this, glArgs);
if (timing == "after" && !observer.suppressHandlers) {
glBreak = observer.call(callbackName || funcName, context, glArgs, glResult);
if (glBreak) return;
}
return glResult;
};
},
/**
* Override mappings for WebGL methods.
*/
_methods: [{
timing: "after",
functions: [
"linkProgram", "getAttribLocation", "getUniformLocation"
]
}, {
timing: "before",
callback: "toggleVertexAttribArray",
functions: [
"enableVertexAttribArray", "disableVertexAttribArray"
]
}, {
timing: "before",
callback: "attribute_",
functions: [
"vertexAttrib1f", "vertexAttrib2f", "vertexAttrib3f", "vertexAttrib4f",
"vertexAttrib1fv", "vertexAttrib2fv", "vertexAttrib3fv", "vertexAttrib4fv",
"vertexAttribPointer"
]
}, {
timing: "before",
callback: "uniform_",
functions: [
"uniform1i", "uniform2i", "uniform3i", "uniform4i",
"uniform1f", "uniform2f", "uniform3f", "uniform4f",
"uniform1iv", "uniform2iv", "uniform3iv", "uniform4iv",
"uniform1fv", "uniform2fv", "uniform3fv", "uniform4fv",
"uniformMatrix2fv", "uniformMatrix3fv", "uniformMatrix4fv"
]
}]
// TODO: It'd be a good idea to handle other functions as well:
// - getActiveUniform
// - getUniform
// - getActiveAttrib
// - getVertexAttrib
};
/**
* An observer that captures a WebGL context's method calls.
*/
function WebGLObserver() {
this.cache = new WebGLCache(this);
this.proxy = new WebGLProxy(this);
}
WebGLObserver.prototype = {
/**
* Set this flag to true to stop observing any context function calls.
*/
suppressHandlers: false,
/**
* Called immediately *after* 'linkProgram' is requested in the context.
*
* @param WebGLRenderingContext gl
* The WebGL context initiating this call.
* @param array glArgs
* Overridable arguments with which the function is called.
* @param void glResult
* The returned value of the original function call.
*/
linkProgram: function(gl, glArgs, glResult) {
let program = glArgs[0];
let shaders = gl.getAttachedShaders(program);
for (let shader of shaders) {
let source = gl.getShaderSource(shader);
this.cache.call("addShaderInfo", shader, source);
}
emit(this, "program-linked", gl, program, shaders);
},
/**
* Called immediately *after* 'getAttribLocation' is requested in the context.
*
* @param WebGLRenderingContext gl
* The WebGL context initiating this call.
* @param array glArgs
* Overridable arguments with which the function is called.
* @param GLint glResult
* The returned value of the original function call.
*/
getAttribLocation: function(gl, glArgs, glResult) {
let [program, name] = glArgs;
this.cache.call("addAttribute", program, name, glResult);
},
/**
* Called immediately *after* 'getUniformLocation' is requested in the context.
*
* @param WebGLRenderingContext gl
* The WebGL context initiating this call.
* @param array glArgs
* Overridable arguments with which the function is called.
* @param WebGLUniformLocation glResult
* The returned value of the original function call.
*/
getUniformLocation: function(gl, glArgs, glResult) {
let [program, name] = glArgs;
this.cache.call("addUniform", program, name, glResult);
},
/**
* Called immediately *before* 'enableVertexAttribArray' or
* 'disableVertexAttribArray'is requested in the context.
*
* @param WebGLRenderingContext gl
* The WebGL context initiating this call.
* @param array glArgs
* Overridable arguments with which the function is called.
*/
toggleVertexAttribArray: function(gl, glArgs) {
glArgs[0] = this.cache.call("getCurrentAttributeLocation", glArgs[0]);
return glArgs[0] < 0;
},
/**
* Called immediately *before* 'attribute_' is requested in the context.
*
* @param WebGLRenderingContext gl
* The WebGL context initiating this call.
* @param array glArgs
* Overridable arguments with which the function is called.
*/
attribute_: function(gl, glArgs) {
glArgs[0] = this.cache.call("getCurrentAttributeLocation", glArgs[0]);
return glArgs[0] < 0;
},
/**
* Called immediately *before* 'uniform_' is requested in the context.
*
* @param WebGLRenderingContext gl
* The WebGL context initiating this call.
* @param array glArgs
* Overridable arguments with which the function is called.
*/
uniform_: function(gl, glArgs) {
glArgs[0] = this.cache.call("getCurrentUniformLocation", glArgs[0]);
return !glArgs[0];
},
/**
* Executes a function in this object.
* This method makes sure that any handlers in the context observer are
* suppressed, hence stopping observing any context function calls.
*
* @param string funcName
* The function to call.
*/
call: function(funcName, ...args) {
let prevState = this.suppressHandlers;
this.suppressHandlers = true;
let result = this[funcName].apply(this, args);
this.suppressHandlers = prevState;
return result;
}
};
/**
* A cache storing WebGL state, like shaders, attributes or uniforms.
*
* @param WebGLObserver observer
* The observer for the target context.
*/
function WebGLCache(observer) {
this._observer = observer;
this._shaders = new Map();
this._attributes = [];
this._uniforms = [];
this._attributesBridge = new Map();
this._uniformsBridge = new Map();
}
WebGLCache.prototype = {
/**
* Adds shader information to the cache.
*
* @param WebGLShader shader
* The shader for which the source is to be cached. If the shader
* was already cached, nothing happens.
* @param string text
* The current shader text.
*/
_addShaderInfo: function(shader, text) {
if (!this._shaders.has(shader)) {
this._shaders.set(shader, text);
}
},
/**
* Gets shader information from the cache.
*
* @param WebGLShader shader
* The shader for which the source was cached.
* @return object | null
* The original shader source, or null if there's a cache miss.
*/
_getShaderInfo: function(shader) {
return this._shaders.get(shader);
},
/**
* Adds an attribute to the cache.
*
* @param WebGLProgram program
* The program for which the attribute is bound. If the attribute
* was already cached, nothing happens.
* @param string name
* The attribute name.
* @param GLint value
* The attribute value.
*/
_addAttribute: function(program, name, value) {
let isCached = this._attributes.some(e => e.program == program && e.name == name);
if (isCached || value < 0) {
return;
}
let attributeInfo = {
program: program,
name: name,
value: value
};
this._attributes.push(attributeInfo);
this._attributesBridge.set(value, attributeInfo);
},
/**
* Adds a uniform to the cache.
*
* @param WebGLProgram program
* The program for which the uniform is bound. If the uniform
* was already cached, nothing happens.
* @param string name
* The uniform name.
* @param WebGLUniformLocation value
* The uniform value.
*/
_addUniform: function(program, name, value) {
let isCached = this._uniforms.some(e => e.program == program && e.name == name);
if (isCached || !value) {
return;
}
let uniformInfo = {
program: program,
name: name,
value: value
};
this._uniforms.push(uniformInfo);
this._uniformsBridge.set(new XPCNativeWrapper(value), uniformInfo);
},
/**
* Gets all the cached attributes for a specific program.
*
* @param WebGLProgram program
* The program for which the attributes are bound.
* @return array
* A list containing information about all the attributes.
*/
_getAttributesForProgram: function(program) {
return this._attributes.filter(e => e.program == program);
},
/**
* Gets all the cached uniforms for a specific program.
*
* @param WebGLProgram program
* The program for which the uniforms are bound.
* @return array
* A list containing information about all the uniforms.
*/
_getUniformsForProgram: function(program) {
return this._uniforms.filter(e => e.program == program);
},
/**
* Updates the attribute locations for a specific program.
* This is necessary, for example, when the shader is relinked and all the
* attribute locations become obsolete.
*
* @param WebGLRenderingContext gl
* The WebGL context owning the program.
* @param WebGLProgram program
* The program for which the attributes need updating.
*/
_updateAttributesForProgram: function(gl, program) {
let dirty = this._attributes.filter(e => e.program == program);
dirty.forEach(e => e.value = gl.getAttribLocation(program, e.name));
},
/**
* Updates the uniform locations for a specific program.
* This is necessary, for example, when the shader is relinked and all the
* uniform locations become obsolete.
*
* @param WebGLRenderingContext gl
* The WebGL context owning the program.
* @param WebGLProgram program
* The program for which the uniforms need updating.
*/
_updateUniformsForProgram: function(gl, program) {
let dirty = this._uniforms.filter(e => e.program == program);
dirty.forEach(e => e.value = gl.getUniformLocation(program, e.name));
},
/**
* Gets the actual attribute location in a specific program.
* When relinked, all the attribute locations become obsolete and are updated
* in the cache. This method returns the (current) real attribute location.
*
* @param GLint initialValue
* The initial attribute value.
* @return GLint
* The current attribute value, or the initial value if it's already
* up to date with its corresponding program.
*/
_getCurrentAttributeLocation: function(initialValue) {
let currentInfo = this._attributesBridge.get(initialValue);
return currentInfo ? currentInfo.value : initialValue;
},
/**
* Gets the actual uniform location in a specific program.
* When relinked, all the uniform locations become obsolete and are updated
* in the cache. This method returns the (current) real uniform location.
*
* @param WebGLUniformLocation initialValue
* The initial uniform value.
* @return WebGLUniformLocation
* The current uniform value, or the initial value if it's already
* up to date with its corresponding program.
*/
_getCurrentUniformLocation: function(initialValue) {
let currentInfo = this._uniformsBridge.get(initialValue);
return currentInfo ? currentInfo.value : initialValue;
},
/**
* Executes a function in this object.
* This method makes sure that any handlers in the context observer are
* suppressed, hence stopping observing any context function calls.
*
* @param string funcName
* The function to call.
* @return any
* The called function result.
*/
call: function(funcName, ...aArgs) {
let prevState = this._observer.suppressHandlers;
this._observer.suppressHandlers = true;
let result = this["_" + funcName].apply(this, aArgs);
this._observer.suppressHandlers = prevState;
return result;
}
};
/**
* A mechanism for injecting or qureying state into/from a WebGL context.
*
* @param WebGLObserver observer
* The observer for the target context.
*/
function WebGLProxy(observer) {
this._observer = observer;
}
WebGLProxy.prototype = {
get cache() this._observer.cache,
/**
* Changes a shader's source code and relinks the respective program.
*
* @param WebGLRenderingContext gl
* The WebGL context owning the program.
* @param WebGLProgram program
* The program who's linked shader is to be modified.
* @param WebGLShader shader
* The shader to be modified.
* @param string text
* The new shader source code.
* @return string
* The shader's compilation and linking status.
*/
_compileShader: function(gl, program, shader, text) {
gl.shaderSource(shader, text);
gl.compileShader(shader);
gl.linkProgram(program);
let error = { compile: "", link: "" };
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
error.compile = gl.getShaderInfoLog(shader);
}
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
error.link = gl.getShaderInfoLog(shader);
}
this.cache.call("updateAttributesForProgram", gl, program);
this.cache.call("updateUniformsForProgram", gl, program);
return error;
},
/**
* Executes a function in this object.
* This method makes sure that any handlers in the context observer are
* suppressed, hence stopping observing any context function calls.
*
* @param string funcName
* The function to call.
* @return any
* The called function result.
*/
call: function(funcName, ...aArgs) {
let prevState = this._observer.suppressHandlers;
this._observer.suppressHandlers = true;
let result = this["_" + funcName].apply(this, aArgs);
this._observer.suppressHandlers = prevState;
return result;
}
};

View File

@ -366,6 +366,7 @@ var DebuggerServer = {
this.addActors("resource://gre/modules/devtools/server/actors/styleeditor.js");
this.addActors("resource://gre/modules/devtools/server/actors/webapps.js");
this.registerModule("devtools/server/actors/inspector");
this.registerModule("devtools/server/actors/webgl");
this.registerModule("devtools/server/actors/tracer");
this.registerModule("devtools/server/actors/device");
},
@ -384,6 +385,7 @@ var DebuggerServer = {
this.addActors("resource://gre/modules/devtools/server/actors/gcli.js");
this.addActors("resource://gre/modules/devtools/server/actors/styleeditor.js");
this.registerModule("devtools/server/actors/inspector");
this.registerModule("devtools/server/actors/webgl");
}
if (!("ContentAppActor" in DebuggerServer)) {
this.addActors("resource://gre/modules/devtools/server/actors/childtab.js");