Skip to content

Commit 15f9ff8

Browse files
committed
Improve CPU renderer playground
1 parent 3e063e3 commit 15f9ff8

File tree

1 file changed

+149
-35
lines changed

1 file changed

+149
-35
lines changed

test/integration/cpu-render.html

Lines changed: 149 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,183 @@
1+
<!DOCTYPE html>
12
<body>
23
<script src="../../node_modules/scratch-vm/dist/web/scratch-vm.js"></script>
34
<script src="../../node_modules/scratch-storage/dist/web/scratch-storage.js"></script>
45
<script src="../../node_modules/@turbowarp/scratch-svg-renderer/dist/web/scratch-svg-renderer.js"></script>
56
<script src="../helper/page-util.js"></script>
67
<!-- note: this uses the BUILT version of scratch-render! make sure to npm run build -->
78
<script src="../../dist/web/scratch-render.js"></script>
9+
<style>
10+
canvas {
11+
border: 1px solid black;
12+
}
13+
canvas:hover {
14+
/* make it easier to spot transparent regions */
15+
background-color: rgba(1, 1, 1, 0.5);
16+
}
17+
tr {
18+
margin-bottom: 8px;
19+
}
20+
td {
21+
text-align: center;
22+
}
23+
</style>
824

9-
<canvas id="test" width="480" height="360"></canvas>
10-
<canvas id="cpu" width="480" height="360"></canvas>
11-
<br/>
12-
<canvas id="merge" width="480" height="360"></canvas>
13-
<input type="file" id="file" name="file">
25+
<input type="file" id="file" name="file" accept=".sb3,.sb2,.sb">
26+
<button id="manual-cpu-render">Update CPU render</button>
27+
<br>
28+
<table>
29+
<tr>
30+
<td>GPU</td>
31+
<td>CPU</td>
32+
</tr>
33+
<tr>
34+
<td><canvas id="gpu" width="480" height="360"></canvas></td>
35+
<td><canvas id="cpu" width="480" height="360"></canvas></td>
36+
</tr>
37+
<tr>
38+
<td>Color Difference</td>
39+
<td>Alpha Difference</td>
40+
</tr>
41+
<tr>
42+
<td><canvas id="color-merge" width="480" height="360"></canvas></td>
43+
<td><canvas id="alpha-merge" width="480" height="360"></canvas></td>
44+
</tr>
45+
</table>
46+
<br>
47+
48+
<p>For difference canvases: Hover to locate transparent parts (perfect match). Close to white means small deviation.</p>
1449

1550
<script>
1651
// These variables are going to be available in the "window global" intentionally.
1752
// Allows you easy access to debug with `vm.greenFlag()` etc.
1853
window.devicePixelRatio = 1;
19-
const gpuCanvas = document.getElementById('test');
54+
const gpuCanvas = document.getElementById('gpu');
2055
var render = new ScratchRender(gpuCanvas);
2156
var vm = initVM(render);
2257

58+
render.setBackgroundColor(1, 0, 0, 0.1);
59+
2360
const fileInput = document.getElementById('file');
2461
const loadFile = loadFileInputIntoVM.bind(null, fileInput, vm, render);
25-
fileInput.addEventListener('change', e => {
62+
function loadAndRender() {
2663
loadFile()
2764
.then(() => {
2865
vm.greenFlag();
2966
setTimeout(() => {
3067
renderCpu();
31-
}, 1000);
68+
}, 250);
3269
});
33-
});
70+
}
71+
fileInput.addEventListener('change', loadAndRender);
72+
if (fileInput.files.length) {
73+
loadAndRender();
74+
}
3475

3576
const cpuCanvas = document.getElementById('cpu');
36-
const cpuCtx = cpuCanvas.getContext('2d');
37-
const cpuImageData = cpuCtx.getImageData(0, 0, cpuCanvas.width, cpuCanvas.height);
77+
const cpuCtx = cpuCanvas.getContext('2d', {
78+
alpha: true
79+
});
80+
const cpuData = cpuCtx.getImageData(0, 0, 480, 360);
81+
82+
const colorMergeCanvas = document.getElementById('color-merge');
83+
const colorMergeCtx = colorMergeCanvas.getContext('2d', {
84+
alpha: true
85+
});
86+
const colorMergeData = colorMergeCtx.getImageData(0, 0, 480, 360);
87+
88+
const alphaMergeCanvas = document.getElementById('alpha-merge');
89+
const alphaMergeCtx = alphaMergeCanvas.getContext('2d', {
90+
alpha: true
91+
});
92+
const alphaMergeData = alphaMergeCtx.getImageData(0, 0, 480, 360);
93+
94+
// single image re-used to cancel previous loads
95+
const snapshotImage = new Image();
96+
3897
function renderCpu() {
39-
cpuImageData.data.fill(255);
40-
const drawBits = render._drawList.map(id => {
41-
const drawable = render._allDrawables[id];
42-
if (!(drawable._visible && drawable.skin)) {
43-
return;
44-
}
45-
drawable.updateCPURenderAttributes();
46-
return { id, drawable };
47-
}).reverse().filter(Boolean);
48-
const color = new Uint8ClampedArray(3);
98+
cpuData.data.fill(255);
99+
100+
const drawBits = render._drawList
101+
.map(id => {
102+
const drawable = render._allDrawables[id];
103+
if (!(drawable._visible && drawable.skin)) {
104+
return;
105+
}
106+
drawable.updateCPURenderAttributes();
107+
return { id, drawable };
108+
})
109+
.reverse()
110+
.filter(Boolean);
111+
112+
console.time('CPU render');
113+
const color = new Uint8ClampedArray(4);
49114
for (let x = -239; x <= 240; x++) {
50-
for (let y = -180; y< 180; y++) {
51-
render.constructor.sampleColor3b([x, y], drawBits, color);
52-
const offset = (((179-y) * 480) + 239 + x) * 4
53-
cpuImageData.data.set(color, offset);
115+
for (let y = -180; y < 180; y++) {
116+
render.sampleColor4b([x, y], drawBits, color);
117+
const offset = (((179 - y) * 480) + 239 + x) * 4;
118+
// putImageData wants non-premultiplied alpha, for some reason.
119+
const alpha = color[3] / 255;
120+
color[0] /= alpha;
121+
color[1] /= alpha;
122+
color[2] /= alpha;
123+
cpuData.data.set(color, offset);
54124
}
55125
}
56-
cpuCtx.putImageData(cpuImageData, 0, 0);
57-
58-
const merge = document.getElementById('merge');
59-
const ctx = merge.getContext('2d');
60-
ctx.drawImage(gpuCanvas, 0, 0);
61-
const gpuImageData = ctx.getImageData(0, 0, 480, 360);
62-
for (let x=0; x<gpuImageData.data.length; x++) {
63-
gpuImageData.data[x] = 255 - Math.abs(gpuImageData.data[x] - cpuImageData.data[x]);
64-
}
126+
cpuCtx.putImageData(cpuData, 0, 0);
127+
console.timeEnd('CPU render');
65128

66-
ctx.putImageData(gpuImageData, 0, 0);
129+
render.requestSnapshot(snapshotDataURL => {
130+
snapshotImage.onload = () => {
131+
colorMergeCtx.clearRect(0, 0, 480, 360);
132+
alphaMergeCtx.clearRect(0, 0, 480, 360);
133+
134+
colorMergeCtx.drawImage(snapshotImage, 0, 0);
135+
const gpuImageData = colorMergeCtx.getImageData(0, 0, 480, 360);
136+
137+
for (let i = 0; i < gpuImageData.data.length; i += 4) {
138+
if (
139+
colorMergeData.data[i + 0] === gpuImageData.data[i + 0] &&
140+
colorMergeData.data[i + 1] === gpuImageData.data[i + 1] &&
141+
colorMergeData.data[i + 2] === gpuImageData.data[i + 2]
142+
) {
143+
colorMergeData.data[i + 0] = 0;
144+
colorMergeData.data[i + 1] = 0;
145+
colorMergeData.data[i + 2] = 0;
146+
colorMergeData.data[i + 3] = 0;
147+
} else {
148+
colorMergeData.data[i + 0] = 255 - Math.abs(gpuImageData.data[i + 0] - cpuData.data[i + 0]);
149+
colorMergeData.data[i + 1] = 255 - Math.abs(gpuImageData.data[i + 1] - cpuData.data[i + 1]);
150+
colorMergeData.data[i + 2] = 255 - Math.abs(gpuImageData.data[i + 2] - cpuData.data[i + 2]);
151+
colorMergeData.data[i + 3] = 255;
152+
}
153+
}
154+
colorMergeCtx.putImageData(colorMergeData, 0, 0);
155+
156+
for (let i = 0; i < gpuImageData.data.length; i += 4) {
157+
const alphaDifference = Math.abs(gpuImageData.data[i + 3] - cpuData.data[i + 3]);
158+
if (alphaDifference === 0) {
159+
alphaMergeData.data[i + 0] = 0;
160+
alphaMergeData.data[i + 1] = 0;
161+
alphaMergeData.data[i + 2] = 0;
162+
alphaMergeData.data[i + 3] = 0;
163+
} else {
164+
alphaMergeData.data[i + 0] = 255 - alphaDifference;
165+
alphaMergeData.data[i + 1] = 255 - alphaDifference;
166+
alphaMergeData.data[i + 2] = 255 - alphaDifference;
167+
alphaMergeData.data[i + 3] = 255;
168+
}
169+
}
170+
alphaMergeCtx.putImageData(alphaMergeData, 0, 0);
171+
};
172+
173+
snapshotImage.onerror = () => {
174+
console.error('Snapshot failed to load');
175+
};
176+
177+
snapshotImage.src = snapshotDataURL;
178+
});
67179
}
180+
181+
document.getElementById('manual-cpu-render').addEventListener('click', renderCpu);
68182
</script>
69183
</body>

0 commit comments

Comments
 (0)