Skip to content

Commit c9f86ef

Browse files
authored
Merge pull request #406 from cwillisf/playground-webpack
Add "query playground"
2 parents 4bf233e + c390124 commit c9f86ef

File tree

8 files changed

+529
-160
lines changed

8 files changed

+529
-160
lines changed

src/RenderWebGL.js

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ class RenderWebGL extends EventEmitter {
132132
throw new Error('Could not get WebGL context: this browser or environment may not support WebGL.');
133133
}
134134

135+
/** @type {RenderWebGL.UseGpuModes} */
136+
this._useGpuMode = RenderWebGL.UseGpuModes.Automatic;
137+
135138
/** @type {Drawable[]} */
136139
this._allDrawables = [];
137140

@@ -243,6 +246,14 @@ class RenderWebGL extends EventEmitter {
243246
this._debugCanvas = canvas;
244247
}
245248

249+
/**
250+
* Control the use of the GPU or CPU paths in `isTouchingColor`.
251+
* @param {RenderWebGL.UseGpuModes} useGpuMode - automatically decide, force CPU, or force GPU.
252+
*/
253+
setUseGpuMode (useGpuMode) {
254+
this._useGpuMode = useGpuMode;
255+
}
256+
246257
/**
247258
* Set logical size of the stage in Scratch units.
248259
* @param {int} xLeft The left edge's x-coordinate. Scratch 2 uses -240.
@@ -717,9 +728,16 @@ class RenderWebGL extends EventEmitter {
717728

718729
const bounds = this._candidatesBounds(candidates);
719730

720-
// if there are just too many pixels to CPU render efficently, we
721-
// need to let readPixels happen
722-
if (bounds.width * bounds.height * (candidates.length + 1) >= __cpuTouchingColorPixelCount) {
731+
const maxPixelsForCPU = this._getMaxPixelsForCPU();
732+
733+
const debugCanvasContext = this._debugCanvas && this._debugCanvas.getContext('2d');
734+
if (debugCanvasContext) {
735+
this._debugCanvas.width = bounds.width;
736+
this._debugCanvas.height = bounds.height;
737+
}
738+
739+
// if there are just too many pixels to CPU render efficiently, we need to let readPixels happen
740+
if (bounds.width * bounds.height * (candidates.length + 1) >= maxPixelsForCPU) {
723741
this._isTouchingColorGpuStart(drawableID, candidates.map(({id}) => id).reverse(), bounds, color3b, mask3b);
724742
}
725743

@@ -728,29 +746,45 @@ class RenderWebGL extends EventEmitter {
728746
const color = __touchingColor;
729747
const hasMask = Boolean(mask3b);
730748

749+
// Scratch Space - +y is top
731750
for (let y = bounds.bottom; y <= bounds.top; y++) {
732-
if (bounds.width * (y - bounds.bottom) * (candidates.length + 1) >= __cpuTouchingColorPixelCount) {
751+
if (bounds.width * (y - bounds.bottom) * (candidates.length + 1) >= maxPixelsForCPU) {
733752
return this._isTouchingColorGpuFin(bounds, color3b, y - bounds.bottom);
734753
}
735-
// Scratch Space - +y is top
736754
for (let x = bounds.left; x <= bounds.right; x++) {
737755
point[1] = y;
738756
point[0] = x;
739-
if (
740-
// if we use a mask, check our sample color
741-
(hasMask ?
742-
maskMatches(Drawable.sampleColor4b(point, drawable, color), mask3b) :
743-
drawable.isTouching(point)) &&
744-
// and the target color is drawn at this pixel
745-
colorMatches(RenderWebGL.sampleColor3b(point, candidates, color), color3b, 0)
746-
) {
747-
return true;
757+
// if we use a mask, check our sample color...
758+
if (hasMask ?
759+
maskMatches(Drawable.sampleColor4b(point, drawable, color), mask3b) :
760+
drawable.isTouching(point)) {
761+
RenderWebGL.sampleColor3b(point, candidates, color);
762+
if (debugCanvasContext) {
763+
debugCanvasContext.fillStyle = `rgb(${color[0]},${color[1]},${color[2]})`;
764+
debugCanvasContext.fillRect(x - bounds.left, bounds.bottom - y, 1, 1);
765+
}
766+
// ...and the target color is drawn at this pixel
767+
if (colorMatches(color, color3b, 0)) {
768+
return true;
769+
}
748770
}
749771
}
750772
}
751773
return false;
752774
}
753775

776+
_getMaxPixelsForCPU () {
777+
switch (this._useGpuMode) {
778+
case RenderWebGL.UseGpuModes.ForceCPU:
779+
return Infinity;
780+
case RenderWebGL.UseGpuModes.ForceGPU:
781+
return 0;
782+
case RenderWebGL.UseGpuModes.Automatic:
783+
default:
784+
return __cpuTouchingColorPixelCount;
785+
}
786+
}
787+
754788
_isTouchingColorGpuStart (drawableID, candidateIDs, bounds, color3b, mask3b) {
755789
this._doExitDrawRegion();
756790

@@ -1769,4 +1803,25 @@ class RenderWebGL extends EventEmitter {
17691803
// :3
17701804
RenderWebGL.prototype.canHazPixels = RenderWebGL.prototype.extractDrawable;
17711805

1806+
/**
1807+
* Values for setUseGPU()
1808+
* @enum {string}
1809+
*/
1810+
RenderWebGL.UseGpuModes = {
1811+
/**
1812+
* Heuristically decide whether to use the GPU path, the CPU path, or a dynamic mixture of the two.
1813+
*/
1814+
Automatic: 'Automatic',
1815+
1816+
/**
1817+
* Always use the GPU path.
1818+
*/
1819+
ForceGPU: 'ForceGPU',
1820+
1821+
/**
1822+
* Always use the CPU path.
1823+
*/
1824+
ForceCPU: 'ForceCPU'
1825+
};
1826+
17721827
module.exports = RenderWebGL;

src/playground/.eslintrc.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
module.exports = {
2-
root: true,
32
extends: ['scratch'],
43
env: {
54
browser: true
5+
},
6+
rules: {
7+
'no-console': 'off'
68
}
79
};

src/playground/getMousePosition.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Adapted from code by Simon Sarris: http://stackoverflow.com/a/10450761
2+
const getMousePos = function (event, element) {
3+
const stylePaddingLeft = parseInt(document.defaultView.getComputedStyle(element, null).paddingLeft, 10) || 0;
4+
const stylePaddingTop = parseInt(document.defaultView.getComputedStyle(element, null).paddingTop, 10) || 0;
5+
const styleBorderLeft = parseInt(document.defaultView.getComputedStyle(element, null).borderLeftWidth, 10) || 0;
6+
const styleBorderTop = parseInt(document.defaultView.getComputedStyle(element, null).borderTopWidth, 10) || 0;
7+
8+
// Some pages have fixed-position bars at the top or left of the page
9+
// They will mess up mouse coordinates and this fixes that
10+
const html = document.body.parentNode;
11+
const htmlTop = html.offsetTop;
12+
const htmlLeft = html.offsetLeft;
13+
14+
// Compute the total offset. It's possible to cache this if you want
15+
let offsetX = 0;
16+
let offsetY = 0;
17+
if (typeof element.offsetParent !== 'undefined') {
18+
do {
19+
offsetX += element.offsetLeft;
20+
offsetY += element.offsetTop;
21+
} while ((element = element.offsetParent));
22+
}
23+
24+
// Add padding and border style widths to offset
25+
// Also add the <html> offsets in case there's a position:fixed bar
26+
// This part is not strictly necessary, it depends on your styling
27+
offsetX += stylePaddingLeft + styleBorderLeft + htmlLeft;
28+
offsetY += stylePaddingTop + styleBorderTop + htmlTop;
29+
30+
// We return a simple javascript object with x and y defined
31+
return {
32+
x: event.pageX - offsetX,
33+
y: event.pageY - offsetY
34+
};
35+
};
36+
37+
module.exports = getMousePos;

src/playground/index.html

Lines changed: 5 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<canvas id="debug-canvas" width="10" height="10" style="border:3px dashed red"></canvas>
1313
<p>
1414
<label for="fudgeproperty">Property to tweak:</label>
15-
<select id="fudgeproperty" onchange="onFudgePropertyChanged(this.value)">
15+
<select id="fudgeproperty">
1616
<option value="posx">Position X</option>
1717
<option value="posy">Position Y</option>
1818
<option value="direction">Direction</option>
@@ -27,149 +27,12 @@
2727
<option value="ghost">Ghost</option>
2828
</select>
2929
<label for="fudge">Property Value:</label>
30-
<input type="range" id="fudge" style="width:50%" value="90" min="-90" max="270" step="any" oninput="onFudgeChanged(this.value)" onchange="onFudgeChanged(this.value)">
30+
<input type="range" id="fudge" style="width:50%" value="90" min="-90" max="270" step="any">
3131
</p>
3232
<p>
33-
<label for="fudgeMin">Min:</label><input id="fudgeMin" type="number" onchange="onFudgeMinChanged(this.value)">
34-
<label for="fudgeMax">Max:</label><input id="fudgeMax" type="number" onchange="onFudgeMaxChanged(this.value)">
33+
<label for="fudgeMin">Min:</label><input id="fudgeMin" type="number">
34+
<label for="fudgeMax">Max:</label><input id="fudgeMax" type="number">
3535
</p>
36-
<script src="scratch-render.js"></script>
37-
<script>
38-
var canvas = document.getElementById('scratch-stage');
39-
var fudge = 90;
40-
var renderer = new ScratchRender(canvas);
41-
renderer.setLayerGroupOrdering(['group1']);
42-
43-
var drawableID = renderer.createDrawable('group1');
44-
renderer.updateDrawableProperties(drawableID, {
45-
position: [0, 0],
46-
scale: [100, 100],
47-
direction: 90
48-
});
49-
50-
var drawableID2 = renderer.createDrawable('group1');
51-
var wantBitmapSkin = false;
52-
53-
// Bitmap (squirrel)
54-
var image = new Image();
55-
image.onload = function () {
56-
var bitmapSkinId = renderer.createBitmapSkin(image);
57-
if (wantBitmapSkin) {
58-
renderer.updateDrawableProperties(drawableID2, {
59-
skinId: bitmapSkinId
60-
});
61-
}
62-
};
63-
image.crossOrigin = 'anonymous';
64-
image.src = 'https://cdn.assets.scratch.mit.edu/internalapi/asset/7e24c99c1b853e52f8e7f9004416fa34.png/get/';
65-
66-
// SVG (cat 1-a)
67-
var xhr = new XMLHttpRequest();
68-
xhr.addEventListener("load", function () {
69-
var skinId = renderer.createSVGSkin(xhr.responseText);
70-
if (!wantBitmapSkin) {
71-
renderer.updateDrawableProperties(drawableID2, {
72-
skinId: skinId
73-
});
74-
}
75-
});
76-
xhr.open('GET', 'https://cdn.assets.scratch.mit.edu/internalapi/asset/f88bf1935daea28f8ca098462a31dbb0.svg/get/');
77-
xhr.send();
78-
79-
var posX = 0;
80-
var posY = 0;
81-
var scaleX = 100;
82-
var scaleY = 100;
83-
var fudgeProperty = 'posx';
84-
function onFudgePropertyChanged(newValue) {
85-
fudgeProperty = newValue;
86-
}
87-
var fudgeInput = document.getElementById('fudge');
88-
function onFudgeMinChanged(newValue) {
89-
fudgeInput.min = newValue;
90-
}
91-
function onFudgeMaxChanged(newValue) {
92-
fudgeInput.max = newValue;
93-
}
94-
function onFudgeChanged(newValue) {
95-
fudge = newValue;
96-
var props = {};
97-
switch (fudgeProperty) {
98-
case 'posx': props.position = [fudge, posY]; posX = fudge; break;
99-
case 'posy': props.position = [posX, fudge]; posY = fudge; break;
100-
case 'direction': props.direction = fudge; break;
101-
case 'scalex': props.scale = [fudge, scaleY]; scaleX = fudge; break;
102-
case 'scaley': props.scale = [scaleX, fudge]; scaleY = fudge; break;
103-
case 'color': props.color = fudge; break;
104-
case 'whirl': props.whirl = fudge; break;
105-
case 'fisheye': props.fisheye = fudge; break;
106-
case 'pixelate': props.pixelate = fudge; break;
107-
case 'mosaic': props.mosaic = fudge; break;
108-
case 'brightness': props.brightness = fudge; break;
109-
case 'ghost': props.ghost = fudge; break;
110-
}
111-
renderer.updateDrawableProperties(drawableID2, props);
112-
}
113-
114-
// Adapted from code by Simon Sarris: http://stackoverflow.com/a/10450761
115-
function getMousePos(event, element) {
116-
var stylePaddingLeft = parseInt(document.defaultView.getComputedStyle(element, null)['paddingLeft'], 10) || 0;
117-
var stylePaddingTop = parseInt(document.defaultView.getComputedStyle(element, null)['paddingTop'], 10) || 0;
118-
var styleBorderLeft = parseInt(document.defaultView.getComputedStyle(element, null)['borderLeftWidth'], 10) || 0;
119-
var styleBorderTop = parseInt(document.defaultView.getComputedStyle(element, null)['borderTopWidth'], 10) || 0;
120-
121-
// Some pages have fixed-position bars at the top or left of the page
122-
// They will mess up mouse coordinates and this fixes that
123-
var html = document.body.parentNode;
124-
var htmlTop = html.offsetTop;
125-
var htmlLeft = html.offsetLeft;
126-
127-
// Compute the total offset. It's possible to cache this if you want
128-
var offsetX = 0, offsetY = 0;
129-
if (element.offsetParent !== undefined) {
130-
do {
131-
offsetX += element.offsetLeft;
132-
offsetY += element.offsetTop;
133-
} while ((element = element.offsetParent));
134-
}
135-
136-
// Add padding and border style widths to offset
137-
// Also add the <html> offsets in case there's a position:fixed bar
138-
// This part is not strictly necessary, it depends on your styling
139-
offsetX += stylePaddingLeft + styleBorderLeft + htmlLeft;
140-
offsetY += stylePaddingTop + styleBorderTop + htmlTop;
141-
142-
// We return a simple javascript object with x and y defined
143-
return {
144-
x: event.pageX - offsetX,
145-
y: event.pageY - offsetY
146-
};
147-
}
148-
149-
canvas.onmousemove = function(event) {
150-
var mousePos = getMousePos(event, canvas);
151-
renderer.extractColor(mousePos.x, mousePos.y, 30);
152-
};
153-
154-
canvas.onclick = function(event) {
155-
var mousePos = getMousePos(event, canvas);
156-
var pickID = renderer.pick(mousePos.x, mousePos.y);
157-
console.log('You clicked on ' + (pickID < 0 ? 'nothing' : 'ID# ' + pickID));
158-
if (pickID >= 0) {
159-
console.dir(renderer.extractDrawable(pickID, mousePos.x, mousePos.y));
160-
}
161-
};
162-
163-
function drawStep() {
164-
renderer.draw();
165-
// renderer.getBounds(drawableID2);
166-
// renderer.isTouchingColor(drawableID2, [255,255,255]);
167-
requestAnimationFrame(drawStep);
168-
}
169-
drawStep();
170-
171-
var debugCanvas = /** @type {canvas} */ document.getElementById('debug-canvas');
172-
renderer.setDebugCanvas(debugCanvas);
173-
</script>
36+
<script src="playground.js"></script>
17437
</body>
17538
</html>

0 commit comments

Comments
 (0)