fugafacere
Version:
A pure-JS implementation of the W3C's Canvas-2D Context API that can run on top of either Expo Graphics or a browser WebGL context.
303 lines (237 loc) • 8.4 kB
YAML
spec: |
<html>
<head>
<title>%name% // WebGL2DContext conformance</title>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
<style>
div {
padding: 5px;
}
canvas {
border: 1px black solid;
}
.progress {
font: 100% monospace;
}
.failure {
background-color: red;
color: white;
font: 100% sans-serif;
}
.success {
background-color: #88FF88;
font: 100% sans-serif;
}
.failure a:link {
color: white
}
.failure a:visited {
color: white
}
</style>
<script type="text/javascript" src="bundle.js"></script>
<script type="text/javascript" src="assets.js"></script>
<script type="text/javascript" src="asserts.js"></script>
<script type="text/javascript">
window.ImageData = ImageData;
async function getAsset(name) {
return new Promise((resolve, reject) => {
let img = document.createElement("IMG")
img.onload = () => resolve(img)
img.onerror = reject
img.src = rawImageAssets[name]
})
}
function bootCanvas(container, id, width, height, needsHighBitDepth) {
var canvas = document.createElement("CANVAS")
canvas.setAttribute("id", id)
canvas.setAttribute("width", width)
canvas.setAttribute("height", height)
container.appendChild(canvas)
var gl = canvas.getContext("webgl2", {
stencil: 8,
preserveDrawingBuffer: true
});
gl.viewportWidth = canvas.width;
gl.viewportHeight = canvas.height;
if (!gl) {
console.log("Could not initialise WebGL, sorry :-(")
return null;
}
gl.clearColor(1.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
/* TODO: figure out how to pass some gradient tests that need
* more grad stops: */
let ctx = new Expo2DContext.default(gl, {maxGradStops: 50, renderWithOffscreenBuffer: needsHighBitDepth})
ctx.getContext = () => {return ctx;}
return [canvas, gl, ctx]
}
function spinner_graphic(i) {
let anim = ["/","–","\\","|","/","–","\\", "|"]
return anim[i%anim.length]
}
var testContexts = {};
function focusTest (num) {
window.ctx = testContexts[num][0]
window.allContexts = testContexts[num]
}
window.onhashchange = () => {
var url = new URL(window.location)
var hashVal = url.hash.substr(1)
if (hashVal in testContexts) {
focusTest(hashVal)
}
}
async function runTests() {
// TODO: assets??
if (!window.testComplete) {
window.testComplete = (testResults, completedTests, totalTests) =>
{ /* PUPPETEER STUB */ };
}
testContexts = {};
var tests = document.getElementsByClassName("test")
var url = new URL(window.location)
if (url.searchParams.get("runTests") != null) {
selected_test_ids = url.searchParams.get("runTests").split(",").map((x) => parseInt(x))
selected_test_ids = selected_test_ids.filter((elm, idx) => {return isFinite(elm)})
tests = selected_test_ids.map((x) => document.getElementById("test_"+x+"_content"))
}
var cleanUp = url.searchParams.get("noCleanup") == null
let suite_status = document.getElementById("suite_status")
var failures = new Set();
var failureSummaryHTML = "Failed:<ul>"
let i = 0;
var testIteration = async (i) => {
if (i >= tests.length) {
finishTests();
return;
}
let test_area = tests[i]
let test = test_area.dataset
let allCanvases = []
let allWebGLContexts = []
let allContexts = []
let failureMessages = []
let test_fn = eval("test_"+test.id+"_body");
let needsHighBitDepth = test_fn.toString().includes("getImageData");
for (let j=0; j<parseInt(test.contexts); j++) {
let [canvas, gl, ctx] = bootCanvas(
test_area,
"canvas_"+test.id+"_"+j,
parseInt(test.width),
parseInt(test.height),
needsHighBitDepth
)
// Install fake getContext wrapper to allow context objects to be
// used in place of canvas objects for our tests:
ctx.getContext = (unused) => {return ctx};
allCanvases.push(canvas)
allWebGLContexts.push(gl)
allContexts.push(ctx)
}
let onFail = TriggerObject((message) => {
let status_box = document.getElementById("test_"+test.id+"_status")
status_box.setAttribute("class","failure")
status_box.innerHTML += message + "<br />";
failureMessages.push(message)
if (!failures.has(test.id)) {
failureSummaryHTML += "<li><a href='#"+test.id+"'>"+test.id+": "+test.description+"</a></li>"
failures.add(test.id);
}
})
try {
await test_fn(onFail, allCanvases, allContexts);
} catch (err) {
onFail.trigger(false, err.toString());
}
if (cleanUp) {
// Copy the resulting drawings out of the original canvas and
// safely trash the old gl context, to free up resources:
for (let j=0; j<parseInt(test.contexts); j++) {
let canvas_clone = allCanvases[j].cloneNode(true);
let clone_ctx = canvas_clone.getContext('2d');
clone_ctx.drawImage(allCanvases[j], 0, 0);
allCanvases[j].replaceWith(canvas_clone);
allWebGLContexts[j].getExtension('WEBGL_lose_context').loseContext();
allWebGLContexts[j] = null;
}
} else {
testContexts[test.id] = allContexts
}
suite_status.innerHTML = "["+ spinner_graphic(i) + "] Tests run: " + (i+1) + " / " + tests.length;
window.testComplete(
Object.assign({
"failureMessages": failureMessages
}, test), i+1, tests.length);
setTimeout(() => { testIteration(i+1) }, 1);
}
var finishTests = () => {
if (failures.size > 0) {
failureSummaryHTML = (tests.length-failures.size) + "/" + tests.length + " tests passed<br />"+failureSummaryHTML
failureSummaryHTML += "</ul><br />"
suite_status.setAttribute("class","failure")
suite_status.innerHTML = failureSummaryHTML
} else {
suite_status.setAttribute("class","success")
suite_status.innerHTML = "[:)] All tests pass !"
}
if (!cleanUp && tests.length==1){
focusTest(tests[0].dataset.id)
} else {
window.onhashchange()
}
};
testIteration(0);
}
</script>
</head>
<body onload="runTests();">
<h1> %name% </h1>
<div id="suite_status" class="progress"> </div>
<hr />
%tests%
<br /><br />
<3
</body>
</html>
test: |
<p><a id="%id%" href="#%id%"\>%id%</a>: %description%</p>
<script>
async function test_%id%_body(t, allCanvases, allContexts) {
%init%
let ctx = allContexts[0];
let canvas = allCanvases[0];
%body%
}
</script>
<div class="test"
id="test_%id%_content"
data-contexts="%totalContexts%"
data-id="%id%"
data-name="%name%"
data-width="%width%"
data-height="%height%"
data-description="%description%"> </div><br />
<div id="test_%id%_status"> </div>
[<a href="?runTests=%id%&noCleanup#%id%" onclick="window.location.reload()">solo run</a>]
<hr />
asset_definition: |
"%asset_filename%": "assets/%asset_filename%"
assets: |
var rawImageAssets = {
%assetlist%
}
index_item: |
<li><a data-name="2d.%name%" data-count="%count%" href="%filename%">2d.%name%</a> (%count% tests)</li>
index: |
<html><body>
<ul>
%indexlist%
</ul>
</body></html>
filenames: |
%name%.html
disabled: |
<!-- DISABLED: %reason%
%body%
-->