Compare commits
1 Commits
greenkeepe
...
greenkeepe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ba438a7b8 |
@@ -1,10 +1,14 @@
|
||||
language: node_js
|
||||
dist: trusty
|
||||
addons:
|
||||
chrome: stable
|
||||
node_js:
|
||||
- 10
|
||||
- 8
|
||||
- node
|
||||
env:
|
||||
- NODE_ENV=production
|
||||
before_install:
|
||||
- google-chrome-stable --headless --no-sandbox --remote-debugging-port=9222 &
|
||||
install:
|
||||
- npm --production=false install
|
||||
- npm --production=false update
|
||||
@@ -12,7 +16,6 @@ sudo: false
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
- ~/.cache/ms-playwright
|
||||
jobs:
|
||||
include:
|
||||
- stage: test
|
||||
@@ -21,7 +24,7 @@ jobs:
|
||||
- npm run docs
|
||||
- npm run tap
|
||||
- stage: deploy
|
||||
node_js: 10
|
||||
node_js: 8
|
||||
script: npm run build
|
||||
before_deploy:
|
||||
- VPKG=$($(npm bin)/json -f package.json version)
|
||||
|
||||
@@ -29,14 +29,14 @@
|
||||
"babel-loader": "^7.1.4",
|
||||
"babel-polyfill": "^6.22.0",
|
||||
"babel-preset-env": "^1.6.1",
|
||||
"copy-webpack-plugin": "^6.0.1",
|
||||
"chromeless": "^1.5.1",
|
||||
"copy-webpack-plugin": "^4.5.1",
|
||||
"docdash": "^0.4.0",
|
||||
"eslint": "^4.6.1",
|
||||
"eslint-config-scratch": "^5.0.0",
|
||||
"gh-pages": "^1.0.0",
|
||||
"jsdoc": "^3.5.5",
|
||||
"json": "^9.0.4",
|
||||
"playwright-chromium": "^1.0.1",
|
||||
"scratch-vm": "0.2.0-prerelease.20191227164934",
|
||||
"tap": "^11.0.0",
|
||||
"travis-after-all": "^1.4.4",
|
||||
@@ -51,9 +51,9 @@
|
||||
"ify-loader": "1.0.4",
|
||||
"linebreak": "0.3.0",
|
||||
"minilog": "3.1.0",
|
||||
"raw-loader": "^0.5.1",
|
||||
"raw-loader": "^4.0.1",
|
||||
"scratch-storage": "^1.0.0",
|
||||
"scratch-svg-renderer": "0.2.0-prerelease.20200507183648",
|
||||
"scratch-svg-renderer": "0.2.0-prerelease.20200205003400",
|
||||
"twgl.js": "4.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,16 @@ class BitmapSkin extends Skin {
|
||||
return this._texture || super.getTexture();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bounds of the drawable for determining its fenced position.
|
||||
* @param {Array<number>} drawable - The Drawable instance this skin is using.
|
||||
* @param {?Rectangle} result - Optional destination for bounds calculation.
|
||||
* @return {!Rectangle} The drawable's bounds. For compatibility with Scratch 2, we always use getAABB for bitmaps.
|
||||
*/
|
||||
getFenceBounds (drawable, result) {
|
||||
return drawable.getAABB(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the contents of this skin to a snapshot of the provided bitmap data.
|
||||
* @param {ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} bitmapData - new contents for this skin.
|
||||
|
||||
@@ -679,10 +679,9 @@ class Drawable {
|
||||
* @param {twgl.v3} vec The scratch space [x,y] vector
|
||||
* @param {Drawable} drawable The drawable to sample the texture from
|
||||
* @param {Uint8ClampedArray} dst The "color4b" representation of the texture at point.
|
||||
* @param {number} [effectMask] A bitmask for which effects to use. Optional.
|
||||
* @returns {Uint8ClampedArray} The dst object filled with the color4b
|
||||
*/
|
||||
static sampleColor4b (vec, drawable, dst, effectMask) {
|
||||
static sampleColor4b (vec, drawable, dst) {
|
||||
const localPosition = getLocalPosition(drawable, vec);
|
||||
if (localPosition[0] < 0 || localPosition[1] < 0 ||
|
||||
localPosition[0] > 1 || localPosition[1] > 1) {
|
||||
@@ -699,7 +698,7 @@ class Drawable {
|
||||
// : drawable.skin._silhouette.colorAtLinear(localPosition, dst);
|
||||
|
||||
if (drawable.enabledEffects === 0) return textColor;
|
||||
return EffectTransform.transformColor(drawable, textColor, effectMask);
|
||||
return EffectTransform.transformColor(drawable, textColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -118,17 +118,16 @@ class EffectTransform {
|
||||
* Ghost and Color and Brightness effects.
|
||||
* @param {Drawable} drawable The drawable to get uniforms from.
|
||||
* @param {Uint8ClampedArray} inOutColor The color to transform.
|
||||
* @param {number} [effectMask] A bitmask for which effects to use. Optional.
|
||||
* @returns {Uint8ClampedArray} dst filled with the transformed color
|
||||
*/
|
||||
static transformColor (drawable, inOutColor, effectMask) {
|
||||
static transformColor (drawable, inOutColor) {
|
||||
|
||||
// If the color is fully transparent, don't bother attempting any transformations.
|
||||
if (inOutColor[3] === 0) {
|
||||
return inOutColor;
|
||||
}
|
||||
|
||||
let effects = drawable.enabledEffects;
|
||||
if (typeof effectMask === 'number') effects &= effectMask;
|
||||
const effects = drawable.enabledEffects;
|
||||
const uniforms = drawable.getUniforms();
|
||||
|
||||
const enableColor = (effects & ShaderManager.EFFECT_INFO.color.mask) !== 0;
|
||||
|
||||
117
src/PenSkin.js
117
src/PenSkin.js
@@ -44,6 +44,11 @@ const __projectionMatrix = twgl.m4.identity();
|
||||
*/
|
||||
const __modelTranslationMatrix = twgl.m4.identity();
|
||||
|
||||
/**
|
||||
* Reused memory location for rotation matrix for building a model matrix.
|
||||
* @type {FloatArray}
|
||||
*/
|
||||
const __modelRotationMatrix = twgl.m4.identity();
|
||||
|
||||
/**
|
||||
* Reused memory location for scaling matrix for building a model matrix.
|
||||
@@ -130,7 +135,7 @@ class PenSkin extends Skin {
|
||||
this._stampShader = this._renderer._shaderManager.getShader(ShaderManager.DRAW_MODE.default, NO_EFFECTS);
|
||||
|
||||
/** @type {twgl.ProgramInfo} */
|
||||
this._lineShader = this._renderer._shaderManager.getShader(ShaderManager.DRAW_MODE.line, NO_EFFECTS);
|
||||
this._lineShader = this._renderer._shaderManager.getShader(ShaderManager.DRAW_MODE.lineSample, NO_EFFECTS);
|
||||
|
||||
this._createLineGeometry();
|
||||
|
||||
@@ -216,15 +221,10 @@ class PenSkin extends Skin {
|
||||
* @param {number} y1 - the Y coordinate of the end of the line.
|
||||
*/
|
||||
drawLine (penAttributes, x0, y0, x1, y1) {
|
||||
// For compatibility with Scratch 2.0, offset pen lines of width 1 and 3 so they're pixel-aligned.
|
||||
// See https://github.com/LLK/scratch-render/pull/314
|
||||
const diameter = penAttributes.diameter || DefaultPenAttributes.diameter;
|
||||
const offset = (diameter === 1 || diameter === 3) ? 0.5 : 0;
|
||||
|
||||
this._drawLineOnBuffer(
|
||||
penAttributes,
|
||||
x0 + offset, y0 + offset,
|
||||
x1 + offset, y1 + offset
|
||||
this._rotationCenter[0] + x0, this._rotationCenter[1] - y0,
|
||||
this._rotationCenter[0] + x1, this._rotationCenter[1] - y1
|
||||
);
|
||||
|
||||
this._silhouetteDirty = true;
|
||||
@@ -234,16 +234,72 @@ class PenSkin extends Skin {
|
||||
* Create 2D geometry for drawing lines to a framebuffer.
|
||||
*/
|
||||
_createLineGeometry () {
|
||||
// Create a set of triangulated quads that break up a line into 3 parts:
|
||||
// 2 caps and a body. The y component of these position vertices are
|
||||
// divided to bring a value of 1 down to 0.5 to 0. The large y values
|
||||
// are set so they will still be at least 0.5 after division. The
|
||||
// divisor is scaled based on the length of the line and the lines
|
||||
// width.
|
||||
//
|
||||
// Texture coordinates are based on a "generated" texture whose general
|
||||
// shape is a circle. The line caps set their texture values to define
|
||||
// there roundedness with the texture. The body has all of its texture
|
||||
// values set to the center of the texture so it's a solid block.
|
||||
const quads = {
|
||||
a_position: {
|
||||
numComponents: 2,
|
||||
data: [
|
||||
-0.5, 1,
|
||||
0.5, 1,
|
||||
-0.5, 100000,
|
||||
|
||||
-0.5, 100000,
|
||||
0.5, 1,
|
||||
0.5, 100000,
|
||||
|
||||
-0.5, 1,
|
||||
0.5, 1,
|
||||
-0.5, -1,
|
||||
|
||||
-0.5, -1,
|
||||
0.5, 1,
|
||||
0.5, -1,
|
||||
|
||||
-0.5, -100000,
|
||||
0.5, -100000,
|
||||
-0.5, -1,
|
||||
|
||||
-0.5, -1,
|
||||
0.5, -100000,
|
||||
0.5, -1
|
||||
]
|
||||
},
|
||||
a_texCoord: {
|
||||
numComponents: 2,
|
||||
data: [
|
||||
1, 0.5,
|
||||
0, 0.5,
|
||||
1, 0,
|
||||
|
||||
1, 0,
|
||||
0, 0.5,
|
||||
0, 0,
|
||||
|
||||
0.5, 0,
|
||||
0.5, 1,
|
||||
0.5, 0,
|
||||
|
||||
0.5, 0,
|
||||
0.5, 1,
|
||||
0.5, 1,
|
||||
|
||||
1, 0,
|
||||
0, 0,
|
||||
1, 1,
|
||||
1, 1,
|
||||
1, 0.5,
|
||||
|
||||
1, 0.5,
|
||||
0, 0,
|
||||
0, 1
|
||||
0, 0.5
|
||||
]
|
||||
}
|
||||
};
|
||||
@@ -288,8 +344,6 @@ class PenSkin extends Skin {
|
||||
|
||||
/**
|
||||
* Draw a line on the framebuffer.
|
||||
* Note that the point coordinates are in the following coordinate space:
|
||||
* +y is down, (0, 0) is the center, and the coords range from (-width / 2, -height / 2) to (height / 2, width / 2).
|
||||
* @param {PenAttributes} penAttributes - how the line should be drawn.
|
||||
* @param {number} x0 - the X coordinate of the beginning of the line.
|
||||
* @param {number} y0 - the Y coordinate of the beginning of the line.
|
||||
@@ -303,6 +357,26 @@ class PenSkin extends Skin {
|
||||
|
||||
this._renderer.enterDrawRegion(this._lineOnBufferDrawRegionId);
|
||||
|
||||
const diameter = penAttributes.diameter || DefaultPenAttributes.diameter;
|
||||
const length = Math.hypot(Math.abs(x1 - x0) - 0.001, Math.abs(y1 - y0) - 0.001);
|
||||
const avgX = (x0 + x1) / 2;
|
||||
const avgY = (y0 + y1) / 2;
|
||||
const theta = Math.atan2(y0 - y1, x0 - x1);
|
||||
const alias = 1;
|
||||
|
||||
// The line needs a bit of aliasing to look smooth. Add a small offset
|
||||
// and a small size boost to scaling to give a section to alias.
|
||||
const translationVector = __modelTranslationVector;
|
||||
translationVector[0] = avgX - (alias / 2);
|
||||
translationVector[1] = avgY + (alias / 4);
|
||||
|
||||
const scalingVector = __modelScalingVector;
|
||||
scalingVector[0] = diameter + alias;
|
||||
scalingVector[1] = length + diameter - (alias / 2);
|
||||
|
||||
const radius = diameter / 2;
|
||||
const yScalar = (0.50001 - (radius / (length + diameter)));
|
||||
|
||||
// Premultiply pen color by pen transparency
|
||||
const penColor = penAttributes.color4f || DefaultPenAttributes.color4f;
|
||||
__premultipliedColor[0] = penColor[0] * penColor[3];
|
||||
@@ -311,10 +385,19 @@ class PenSkin extends Skin {
|
||||
__premultipliedColor[3] = penColor[3];
|
||||
|
||||
const uniforms = {
|
||||
u_lineColor: __premultipliedColor,
|
||||
u_lineThickness: penAttributes.diameter || DefaultPenAttributes.diameter,
|
||||
u_penPoints: [x0, -y0, x1, -y1],
|
||||
u_stageSize: this.size
|
||||
u_positionScalar: yScalar,
|
||||
u_capScale: diameter,
|
||||
u_aliasAmount: alias,
|
||||
u_modelMatrix: twgl.m4.multiply(
|
||||
twgl.m4.multiply(
|
||||
twgl.m4.translation(translationVector, __modelTranslationMatrix),
|
||||
twgl.m4.rotationZ(theta - (Math.PI / 2), __modelRotationMatrix),
|
||||
__modelMatrix
|
||||
),
|
||||
twgl.m4.scaling(scalingVector, __modelScalingMatrix),
|
||||
__modelMatrix
|
||||
),
|
||||
u_lineColor: __premultipliedColor
|
||||
};
|
||||
|
||||
twgl.setUniforms(currentShader, uniforms);
|
||||
|
||||
@@ -220,20 +220,9 @@ class RenderWebGL extends EventEmitter {
|
||||
* @param {int} pixelsTall The desired height in device-independent pixels.
|
||||
*/
|
||||
resize (pixelsWide, pixelsTall) {
|
||||
const {canvas} = this._gl;
|
||||
const pixelRatio = window.devicePixelRatio || 1;
|
||||
const newWidth = pixelsWide * pixelRatio;
|
||||
const newHeight = pixelsTall * pixelRatio;
|
||||
|
||||
// Certain operations, such as moving the color picker, call `resize` once per frame, even though the canvas
|
||||
// size doesn't change. To avoid unnecessary canvas updates, check that we *really* need to resize the canvas.
|
||||
if (canvas.width !== newWidth || canvas.height !== newHeight) {
|
||||
canvas.width = newWidth;
|
||||
canvas.height = newHeight;
|
||||
// Resizing the canvas causes it to be cleared, so redraw it.
|
||||
this.draw();
|
||||
}
|
||||
|
||||
this._gl.canvas.width = pixelsWide * pixelRatio;
|
||||
this._gl.canvas.height = pixelsTall * pixelRatio;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -763,9 +752,6 @@ class RenderWebGL extends EventEmitter {
|
||||
const color = __touchingColor;
|
||||
const hasMask = Boolean(mask3b);
|
||||
|
||||
// Masked drawable ignores ghost effect
|
||||
const effectMask = ~ShaderManager.EFFECT_INFO.ghost.mask;
|
||||
|
||||
// Scratch Space - +y is top
|
||||
for (let y = bounds.bottom; y <= bounds.top; y++) {
|
||||
if (bounds.width * (y - bounds.bottom) * (candidates.length + 1) >= maxPixelsForCPU) {
|
||||
@@ -776,7 +762,7 @@ class RenderWebGL extends EventEmitter {
|
||||
point[0] = x;
|
||||
// if we use a mask, check our sample color...
|
||||
if (hasMask ?
|
||||
maskMatches(Drawable.sampleColor4b(point, drawable, color, effectMask), mask3b) :
|
||||
maskMatches(Drawable.sampleColor4b(point, drawable, color), mask3b) :
|
||||
drawable.isTouching(point)) {
|
||||
RenderWebGL.sampleColor3b(point, candidates, color);
|
||||
if (debugCanvasContext) {
|
||||
@@ -1768,16 +1754,14 @@ class RenderWebGL extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Get the convex hull points for a particular Drawable.
|
||||
* To do this, calculate it based on the drawable's Silhouette.
|
||||
* To do this, draw the Drawable unrotated, unscaled, and untranslated.
|
||||
* Read back the pixels and find all boundary points.
|
||||
* Finally, apply a convex hull algorithm to simplify the set.
|
||||
* @param {int} drawableID The Drawable IDs calculate convex hull for.
|
||||
* @return {Array<Array<number>>} points Convex hull points, as [[x, y], ...]
|
||||
*/
|
||||
_getConvexHullPointsForDrawable (drawableID) {
|
||||
const drawable = this._allDrawables[drawableID];
|
||||
|
||||
drawable.updateMatrix();
|
||||
drawable.skin.updateSilhouette(this._getDrawableScreenSpaceScale(drawable));
|
||||
|
||||
const [width, height] = drawable.skin.size;
|
||||
// No points in the hull if invisible or size is 0.
|
||||
if (!drawable.getVisible() || width === 0 || height === 0) {
|
||||
@@ -1785,120 +1769,110 @@ class RenderWebGL extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the determinant of two vectors, the vector from A to B and the vector from A to C.
|
||||
* Return the determinant of two vectors, the vector from A to B and
|
||||
* the vector from A to C.
|
||||
*
|
||||
* The determinant is useful in this case to know if AC is counter-clockwise from AB.
|
||||
* A positive value means that AC is counter-clockwise from AB. A negative value means AC is clockwise from AB.
|
||||
* The determinant is useful in this case to know if AC is counter
|
||||
* clockwise from AB. A positive value means the AC is counter
|
||||
* clockwise from AC. A negative value menas AC is clockwise from AB.
|
||||
*
|
||||
* @param {Float32Array} A A 2d vector in space.
|
||||
* @param {Float32Array} B A 2d vector in space.
|
||||
* @param {Float32Array} C A 2d vector in space.
|
||||
* @return {number} Greater than 0 if counter clockwise, less than if clockwise, 0 if all points are on a line.
|
||||
* @return {number} Greater than 0 if counter clockwise, less than if
|
||||
* clockwise, 0 if all points are on a line.
|
||||
*/
|
||||
const determinant = function (A, B, C) {
|
||||
const CCW = function (A, B, C) {
|
||||
// AB = B - A
|
||||
// AC = C - A
|
||||
// det (AB BC) = AB0 * AC1 - AB1 * AC0
|
||||
return (((B[0] - A[0]) * (C[1] - A[1])) - ((B[1] - A[1]) * (C[0] - A[0])));
|
||||
};
|
||||
|
||||
// This algorithm for calculating the convex hull somewhat resembles the monotone chain algorithm.
|
||||
// The main difference is that instead of sorting the points by x-coordinate, and y-coordinate in case of ties,
|
||||
// it goes through them by y-coordinate in the outer loop and x-coordinate in the inner loop.
|
||||
// This gives us "left" and "right" hulls, whereas the monotone chain algorithm gives "top" and "bottom" hulls.
|
||||
// Adapted from https://github.com/LLK/scratch-flash/blob/dcbeeb59d44c3be911545dfe54d46a32404f8e69/src/scratch/ScratchCostume.as#L369-L413
|
||||
|
||||
const leftHull = [];
|
||||
const rightHull = [];
|
||||
|
||||
// While convex hull algorithms usually push and pop values from the list of hull points,
|
||||
// here, we keep indices for the "last" point in each array. Any points past these indices are ignored.
|
||||
// This is functionally equivalent to pushing and popping from a "stack" of hull points.
|
||||
let leftEndPointIndex = -1;
|
||||
let rightEndPointIndex = -1;
|
||||
|
||||
// https://github.com/LLK/scratch-flash/blob/dcbeeb59d44c3be911545dfe54d
|
||||
// 46a32404f8e69/src/scratch/ScratchCostume.as#L369-L413 Following
|
||||
// RasterHull creation, compare and store left and right values that
|
||||
// maintain a convex shape until that data can be passed to `hull` for
|
||||
// further work.
|
||||
const L = [];
|
||||
const R = [];
|
||||
const _pixelPos = twgl.v3.create();
|
||||
const _effectPos = twgl.v3.create();
|
||||
|
||||
let currentPoint;
|
||||
|
||||
// *Not* Scratch Space-- +y is bottom
|
||||
// Loop over all rows of pixels, starting at the top
|
||||
let ll = -1;
|
||||
let rr = -1;
|
||||
let Q;
|
||||
for (let y = 0; y < height; y++) {
|
||||
_pixelPos[1] = y / height;
|
||||
|
||||
// We start at the leftmost point, then go rightwards until we hit an opaque pixel
|
||||
// Scan from left to right, looking for a touchable spot in the
|
||||
// skin.
|
||||
let x = 0;
|
||||
for (; x < width; x++) {
|
||||
_pixelPos[0] = x / width;
|
||||
EffectTransform.transformPoint(drawable, _pixelPos, _effectPos);
|
||||
if (drawable.skin.isTouchingLinear(_effectPos)) {
|
||||
currentPoint = [x, y];
|
||||
Q = [x, y];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we managed to loop all the way through, there are no opaque pixels on this row. Go to the next one
|
||||
// If x is equal to the width there are no touchable points in the
|
||||
// skin. Nothing we can add to L. And looping for R would find the
|
||||
// same thing.
|
||||
if (x >= width) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Because leftEndPointIndex is initialized to -1, this is skipped for the first two rows.
|
||||
// It runs only when there are enough points in the left hull to make at least one line.
|
||||
// If appending the current point to the left hull makes a counter-clockwise turn,
|
||||
// we want to append the current point. Otherwise, we decrement the index of the "last" hull point until the
|
||||
// current point makes a counter-clockwise turn.
|
||||
// This decrementing has the same effect as popping from the point list, but is hopefully faster.
|
||||
while (leftEndPointIndex > 0) {
|
||||
if (determinant(leftHull[leftEndPointIndex], leftHull[leftEndPointIndex - 1], currentPoint) > 0) {
|
||||
// Decrement ll until Q is clockwise (CCW returns negative) from the
|
||||
// last two points in L.
|
||||
while (ll > 0) {
|
||||
if (CCW(L[ll - 1], L[ll], Q) < 0) {
|
||||
break;
|
||||
} else {
|
||||
// leftHull.pop();
|
||||
--leftEndPointIndex;
|
||||
--ll;
|
||||
}
|
||||
}
|
||||
// Increment ll and then set L[ll] to Q. If ll was -1 before this
|
||||
// line, this will set L[0] to Q. If ll was 0 before this line, this
|
||||
// will set L[1] to Q.
|
||||
L[++ll] = Q;
|
||||
|
||||
// This has the same effect as pushing to the point list.
|
||||
// This "list head pointer" coding style leaves excess points dangling at the end of the list,
|
||||
// but that doesn't matter; we simply won't copy them over to the final hull.
|
||||
|
||||
// leftHull.push(currentPoint);
|
||||
leftHull[++leftEndPointIndex] = currentPoint;
|
||||
|
||||
// Now we repeat the process for the right side, looking leftwards for a pixel.
|
||||
// Scan from right to left, looking for a touchable spot in the
|
||||
// skin.
|
||||
for (x = width - 1; x >= 0; x--) {
|
||||
_pixelPos[0] = x / width;
|
||||
EffectTransform.transformPoint(drawable, _pixelPos, _effectPos);
|
||||
if (drawable.skin.isTouchingLinear(_effectPos)) {
|
||||
currentPoint = [x, y];
|
||||
Q = [x, y];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Because we're coming at this from the right, it goes clockwise this time.
|
||||
while (rightEndPointIndex > 0) {
|
||||
if (determinant(rightHull[rightEndPointIndex], rightHull[rightEndPointIndex - 1], currentPoint) < 0) {
|
||||
// Decrement rr until Q is counter clockwise (CCW returns positive)
|
||||
// from the last two points in L. L takes clockwise points and R
|
||||
// takes counter clockwise points. if y was decremented instead of
|
||||
// incremented R would take clockwise points. We are going in the
|
||||
// right direction for L and the wrong direction for R, so we
|
||||
// compare the opposite value for R from L.
|
||||
while (rr > 0) {
|
||||
if (CCW(R[rr - 1], R[rr], Q) > 0) {
|
||||
break;
|
||||
} else {
|
||||
--rightEndPointIndex;
|
||||
--rr;
|
||||
}
|
||||
}
|
||||
|
||||
rightHull[++rightEndPointIndex] = currentPoint;
|
||||
// Increment rr and then set R[rr] to Q.
|
||||
R[++rr] = Q;
|
||||
}
|
||||
|
||||
// Start off "hullPoints" with the left hull points.
|
||||
const hullPoints = leftHull;
|
||||
// This is where we get rid of those dangling extra points.
|
||||
hullPoints.length = leftEndPointIndex + 1;
|
||||
// Add points from the right side in reverse order so all points are ordered clockwise.
|
||||
for (let j = rightEndPointIndex; j >= 0; --j) {
|
||||
hullPoints.push(rightHull[j]);
|
||||
// Known boundary points on left/right edges of pixels.
|
||||
const boundaryPoints = L;
|
||||
// Truncate boundaryPoints to the index of the last added Q to L. L may
|
||||
// have more entries than the index for the last Q.
|
||||
boundaryPoints.length = ll + 1;
|
||||
// Add points in R to boundaryPoints in reverse so all points in
|
||||
// boundaryPoints are clockwise from each other.
|
||||
for (let j = rr; j >= 0; --j) {
|
||||
boundaryPoints.push(R[j]);
|
||||
}
|
||||
|
||||
// Simplify boundary points using hull.js.
|
||||
// TODO: Remove this; this algorithm already generates convex hulls.
|
||||
return hull(hullPoints, Infinity);
|
||||
// Simplify boundary points using convex hull.
|
||||
return hull(boundaryPoints, Infinity);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -174,9 +174,9 @@ ShaderManager.DRAW_MODE = {
|
||||
colorMask: 'colorMask',
|
||||
|
||||
/**
|
||||
* Draw a line with caps.
|
||||
* Sample a "texture" to draw a line with caps.
|
||||
*/
|
||||
line: 'line'
|
||||
lineSample: 'lineSample'
|
||||
};
|
||||
|
||||
module.exports = ShaderManager;
|
||||
|
||||
@@ -143,10 +143,10 @@ class Skin extends EventEmitter {
|
||||
* Get the bounds of the drawable for determining its fenced position.
|
||||
* @param {Array<number>} drawable - The Drawable instance this skin is using.
|
||||
* @param {?Rectangle} result - Optional destination for bounds calculation.
|
||||
* @return {!Rectangle} The drawable's bounds. For compatibility with Scratch 2, we always use getAABB.
|
||||
* @return {!Rectangle} The drawable's bounds.
|
||||
*/
|
||||
getFenceBounds (drawable, result) {
|
||||
return drawable.getAABB(result);
|
||||
return drawable.getFastBounds(result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,11 +33,11 @@ uniform float u_mosaic;
|
||||
uniform float u_ghost;
|
||||
#endif // ENABLE_ghost
|
||||
|
||||
#ifdef DRAW_MODE_line
|
||||
#ifdef DRAW_MODE_lineSample
|
||||
uniform vec4 u_lineColor;
|
||||
uniform float u_lineThickness;
|
||||
uniform vec4 u_penPoints;
|
||||
#endif // DRAW_MODE_line
|
||||
uniform float u_capScale;
|
||||
uniform float u_aliasAmount;
|
||||
#endif // DRAW_MODE_lineSample
|
||||
|
||||
uniform sampler2D u_skin;
|
||||
|
||||
@@ -109,7 +109,7 @@ const vec2 kCenter = vec2(0.5, 0.5);
|
||||
|
||||
void main()
|
||||
{
|
||||
#ifndef DRAW_MODE_line
|
||||
#ifndef DRAW_MODE_lineSample
|
||||
vec2 texcoord0 = v_texCoord;
|
||||
|
||||
#ifdef ENABLE_mosaic
|
||||
@@ -215,40 +215,14 @@ void main()
|
||||
gl_FragColor.rgb /= gl_FragColor.a + epsilon;
|
||||
#endif
|
||||
|
||||
#else // DRAW_MODE_line
|
||||
// Maaaaagic antialiased-line-with-round-caps shader.
|
||||
// Adapted from Inigo Quilez' 2D distance function cheat sheet
|
||||
// https://www.iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm
|
||||
|
||||
// On (some?) devices with 16-bit float precision, sufficiently long lines will overflow the float's range.
|
||||
// Avoid this by scaling these problematic values down to fit within (-1, 1) then scaling them back up later.
|
||||
// TODO: Is this a problem on all drivers with 16-bit mediump floats, or just Mali?
|
||||
vec2 pointDiff = abs(u_penPoints.zw - u_penPoints.xy);
|
||||
float FLOAT_SCALING_INVERSE = max(1.0, max(pointDiff.x, pointDiff.y));
|
||||
float FLOAT_SCALING = 1.0 / FLOAT_SCALING_INVERSE;
|
||||
|
||||
// The xy component of u_penPoints is the first point; the zw is the second point.
|
||||
// This is done to minimize the number of gl.uniform calls, which can add up.
|
||||
// vec2 pa = v_texCoord - u_penPoints.xy, ba = u_penPoints.zw - u_penPoints.xy;
|
||||
vec2 pa = (v_texCoord - u_penPoints.xy) * FLOAT_SCALING, ba = (u_penPoints.zw - u_penPoints.xy) * FLOAT_SCALING;
|
||||
|
||||
// Avoid division by zero
|
||||
float baDot = dot(ba, ba);
|
||||
// the dot product of a vector and itself is always positive
|
||||
baDot = max(baDot, epsilon);
|
||||
|
||||
// Magnitude of vector projection of this fragment onto the line (both relative to the line's start point).
|
||||
// This results in a "linear gradient" which goes from 0.0 at the start point to 1.0 at the end point.
|
||||
float projMagnitude = clamp(dot(pa, ba) / baDot, 0.0, 1.0);
|
||||
|
||||
float lineDistance = length(pa - (ba * projMagnitude)) * FLOAT_SCALING_INVERSE;
|
||||
|
||||
// The distance to the line allows us to create lines of any thickness.
|
||||
// Instead of checking whether this fragment's distance < the line thickness,
|
||||
// utilize the distance field to get some antialiasing. Fragments far away from the line are 0,
|
||||
// fragments close to the line are 1, and fragments that are within a 1-pixel border of the line are in between.
|
||||
float cappedLine = clamp((u_lineThickness + 1.0) * 0.5 - lineDistance, 0.0, 1.0);
|
||||
|
||||
gl_FragColor = u_lineColor * cappedLine;
|
||||
#endif // DRAW_MODE_line
|
||||
#else // DRAW_MODE_lineSample
|
||||
gl_FragColor = u_lineColor * clamp(
|
||||
// Scale the capScale a little to have an aliased region.
|
||||
(u_capScale + u_aliasAmount -
|
||||
u_capScale * 2.0 * distance(v_texCoord, vec2(0.5, 0.5))
|
||||
) / (u_aliasAmount + 1.0),
|
||||
0.0,
|
||||
1.0
|
||||
);
|
||||
#endif // DRAW_MODE_lineSample
|
||||
}
|
||||
|
||||
@@ -1,63 +1,22 @@
|
||||
precision mediump float;
|
||||
|
||||
#ifdef DRAW_MODE_line
|
||||
uniform vec2 u_stageSize;
|
||||
uniform float u_lineThickness;
|
||||
uniform vec4 u_penPoints;
|
||||
|
||||
// Add this to divisors to prevent division by 0, which results in NaNs propagating through calculations.
|
||||
// Smaller values can cause problems on some mobile devices.
|
||||
const float epsilon = 1e-3;
|
||||
#endif
|
||||
|
||||
#ifndef DRAW_MODE_line
|
||||
uniform mat4 u_projectionMatrix;
|
||||
uniform mat4 u_modelMatrix;
|
||||
attribute vec2 a_texCoord;
|
||||
#endif
|
||||
|
||||
attribute vec2 a_position;
|
||||
attribute vec2 a_texCoord;
|
||||
|
||||
varying vec2 v_texCoord;
|
||||
|
||||
#ifdef DRAW_MODE_lineSample
|
||||
uniform float u_positionScalar;
|
||||
#endif
|
||||
|
||||
void main() {
|
||||
#ifdef DRAW_MODE_line
|
||||
// Calculate a rotated ("tight") bounding box around the two pen points.
|
||||
// Yes, we're doing this 6 times (once per vertex), but on actual GPU hardware,
|
||||
// it's still faster than doing it in JS combined with the cost of uniformMatrix4fv.
|
||||
|
||||
// Expand line bounds by sqrt(2) / 2 each side-- this ensures that all antialiased pixels
|
||||
// fall within the quad, even at a 45-degree diagonal
|
||||
vec2 position = a_position;
|
||||
float expandedRadius = (u_lineThickness * 0.5) + 1.4142135623730951;
|
||||
|
||||
float lineLength = length(u_penPoints.zw - u_penPoints.xy);
|
||||
|
||||
position.x *= lineLength + (2.0 * expandedRadius);
|
||||
position.y *= 2.0 * expandedRadius;
|
||||
|
||||
// Center around first pen point
|
||||
position -= expandedRadius;
|
||||
|
||||
// Rotate quad to line angle
|
||||
vec2 pointDiff = u_penPoints.zw - u_penPoints.xy;
|
||||
// Ensure line has a nonzero length so it's rendered properly
|
||||
// As long as either component is nonzero, the line length will be nonzero
|
||||
pointDiff.x = abs(pointDiff.x) < epsilon ? epsilon : pointDiff.x;
|
||||
// The `normalized` vector holds rotational values equivalent to sine/cosine
|
||||
// We're applying the standard rotation matrix formula to the position to rotate the quad to the line angle
|
||||
vec2 normalized = pointDiff / max(lineLength, epsilon);
|
||||
position = mat2(normalized.x, normalized.y, -normalized.y, normalized.x) * position;
|
||||
// Translate quad
|
||||
position += u_penPoints.xy;
|
||||
|
||||
// Apply view transform
|
||||
position *= 2.0 / u_stageSize;
|
||||
|
||||
gl_Position = vec4(position, 0, 1);
|
||||
v_texCoord = position * 0.5 * u_stageSize;
|
||||
#else
|
||||
gl_Position = u_projectionMatrix * u_modelMatrix * vec4(a_position, 0, 1);
|
||||
v_texCoord = a_texCoord;
|
||||
#endif
|
||||
#ifdef DRAW_MODE_lineSample
|
||||
vec2 position = a_position;
|
||||
position.y = clamp(position.y * u_positionScalar, -0.5, 0.5);
|
||||
gl_Position = u_projectionMatrix * u_modelMatrix * vec4(position, 0, 1);
|
||||
#else
|
||||
gl_Position = u_projectionMatrix * u_modelMatrix * vec4(a_position, 0, 1);
|
||||
#endif
|
||||
v_texCoord = a_texCoord;
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
/* global window, VirtualMachine, ScratchStorage, ScratchSVGRenderer */
|
||||
/* eslint-env browser */
|
||||
|
||||
// Wait for all SVG skins to be loaded.
|
||||
// TODO: this is extremely janky and should be removed once vm.loadProject waits for SVG skins to load
|
||||
// https://github.com/LLK/scratch-render/issues/563
|
||||
window.waitForSVGSkinLoad = renderer => new Promise(resolve => {
|
||||
// eslint-disable-next-line prefer-const
|
||||
let interval;
|
||||
|
||||
const waitInner = () => {
|
||||
let numSVGSkins = 0;
|
||||
let numLoadedSVGSkins = 0;
|
||||
for (const skin of renderer._allSkins) {
|
||||
if (skin.constructor.name !== 'SVGSkin') continue;
|
||||
numSVGSkins++;
|
||||
if (skin._svgRenderer.loaded) numLoadedSVGSkins++;
|
||||
}
|
||||
|
||||
if (numSVGSkins === numLoadedSVGSkins) {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
||||
interval = setInterval(waitInner, 1);
|
||||
});
|
||||
|
||||
window.loadFileInputIntoVM = (fileInput, vm, render) => {
|
||||
const reader = new FileReader();
|
||||
return new Promise(resolve => {
|
||||
reader.onload = () => {
|
||||
vm.start();
|
||||
vm.loadProject(reader.result)
|
||||
.then(() => window.waitForSVGSkinLoad(render))
|
||||
.then(() => {
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
reader.readAsArrayBuffer(fileInput.files[0]);
|
||||
});
|
||||
};
|
||||
|
||||
window.initVM = render => {
|
||||
const vm = new VirtualMachine();
|
||||
const storage = new ScratchStorage();
|
||||
|
||||
vm.attachStorage(storage);
|
||||
vm.attachRenderer(render);
|
||||
vm.attachV2SVGAdapter(new ScratchSVGRenderer.SVGRenderer());
|
||||
vm.attachV2BitmapAdapter(new ScratchSVGRenderer.BitmapAdapter());
|
||||
|
||||
return vm;
|
||||
};
|
||||
@@ -2,7 +2,6 @@
|
||||
<script src="../../node_modules/scratch-vm/dist/web/scratch-vm.js"></script>
|
||||
<script src="../../node_modules/scratch-storage/dist/web/scratch-storage.js"></script>
|
||||
<script src="../../node_modules/scratch-svg-renderer/dist/web/scratch-svg-renderer.js"></script>
|
||||
<script src="../helper/page-util.js"></script>
|
||||
<!-- note: this uses the BUILT version of scratch-render! make sure to npm run build -->
|
||||
<script src="../../dist/web/scratch-render.js"></script>
|
||||
|
||||
@@ -18,18 +17,38 @@
|
||||
window.devicePixelRatio = 1;
|
||||
const gpuCanvas = document.getElementById('test');
|
||||
var render = new ScratchRender(gpuCanvas);
|
||||
var vm = initVM(render);
|
||||
var vm = new VirtualMachine();
|
||||
var storage = new ScratchStorage();
|
||||
|
||||
const fileInput = document.getElementById('file');
|
||||
const loadFile = loadFileInputIntoVM.bind(null, fileInput, vm, render);
|
||||
fileInput.addEventListener('change', e => {
|
||||
loadFile()
|
||||
.then(() => {
|
||||
vm.greenFlag();
|
||||
setTimeout(() => {
|
||||
renderCpu();
|
||||
}, 1000);
|
||||
});
|
||||
vm.attachStorage(storage);
|
||||
vm.attachRenderer(render);
|
||||
vm.attachV2SVGAdapter(new ScratchSVGRenderer.SVGRenderer());
|
||||
vm.attachV2BitmapAdapter(new ScratchSVGRenderer.BitmapAdapter());
|
||||
|
||||
document.getElementById('file').addEventListener('click', e => {
|
||||
document.body.removeChild(document.getElementById('loaded'));
|
||||
});
|
||||
|
||||
document.getElementById('file').addEventListener('change', e => {
|
||||
const reader = new FileReader();
|
||||
const thisFileInput = e.target;
|
||||
reader.onload = () => {
|
||||
vm.start();
|
||||
vm.loadProject(reader.result)
|
||||
.then(() => {
|
||||
// we add a `#loaded` div to our document, the integration suite
|
||||
// waits for that element to show up to assume the vm is ready
|
||||
// to play!
|
||||
const div = document.createElement('div');
|
||||
div.id='loaded';
|
||||
document.body.appendChild(div);
|
||||
vm.greenFlag();
|
||||
setTimeout(() => {
|
||||
renderCpu();
|
||||
}, 1000);
|
||||
});
|
||||
};
|
||||
reader.readAsArrayBuffer(thisFileInput.files[0]);
|
||||
});
|
||||
|
||||
const cpuCanvas = document.getElementById('cpu');
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<script src="../../node_modules/scratch-vm/dist/web/scratch-vm.js"></script>
|
||||
<script src="../../node_modules/scratch-storage/dist/web/scratch-storage.js"></script>
|
||||
<script src="../../node_modules/scratch-svg-renderer/dist/web/scratch-svg-renderer.js"></script>
|
||||
<script src="../helper/page-util.js"></script>
|
||||
<!-- note: this uses the BUILT version of scratch-render! make sure to npm run build -->
|
||||
<script src="../../dist/web/scratch-render.js"></script>
|
||||
|
||||
@@ -16,13 +15,39 @@
|
||||
|
||||
var canvas = document.getElementById('test');
|
||||
var render = new ScratchRender(canvas);
|
||||
var vm = initVM(render);
|
||||
var vm = new VirtualMachine();
|
||||
var storage = new ScratchStorage();
|
||||
var mockMouse = data => vm.runtime.postIOData('mouse', {
|
||||
canvasWidth: canvas.width,
|
||||
canvasHeight: canvas.height,
|
||||
...data,
|
||||
});
|
||||
|
||||
const loadFile = loadFileInputIntoVM.bind(null, document.getElementById('file'), vm, render);
|
||||
vm.attachStorage(storage);
|
||||
vm.attachRenderer(render);
|
||||
vm.attachV2SVGAdapter(new ScratchSVGRenderer.SVGRenderer());
|
||||
vm.attachV2BitmapAdapter(new ScratchSVGRenderer.BitmapAdapter());
|
||||
|
||||
document.getElementById('file').addEventListener('click', e => {
|
||||
document.body.removeChild(document.getElementById('loaded'));
|
||||
});
|
||||
|
||||
document.getElementById('file').addEventListener('change', e => {
|
||||
const reader = new FileReader();
|
||||
const thisFileInput = e.target;
|
||||
reader.onload = () => {
|
||||
vm.start();
|
||||
vm.loadProject(reader.result)
|
||||
.then(() => {
|
||||
// we add a `#loaded` div to our document, the integration suite
|
||||
// waits for that element to show up to assume the vm is ready
|
||||
// to play!
|
||||
const div = document.createElement('div');
|
||||
div.id='loaded';
|
||||
document.body.appendChild(div);
|
||||
});
|
||||
};
|
||||
reader.readAsArrayBuffer(thisFileInput.files[0]);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -1,34 +1,29 @@
|
||||
/* global vm, render, Promise */
|
||||
const {chromium} = require('playwright-chromium');
|
||||
const {Chromeless} = require('chromeless');
|
||||
const test = require('tap').test;
|
||||
const path = require('path');
|
||||
const chromeless = new Chromeless();
|
||||
|
||||
const indexHTML = path.resolve(__dirname, 'index.html');
|
||||
const testDir = (...args) => path.resolve(__dirname, 'pick-tests', ...args);
|
||||
|
||||
const runFile = async (file, action, page, script) => {
|
||||
const runFile = (file, action, script) =>
|
||||
// start each test by going to the index.html, and loading the scratch file
|
||||
await page.goto(`file://${indexHTML}`);
|
||||
const fileInput = await page.$('#file');
|
||||
await fileInput.setInputFiles(testDir(file));
|
||||
|
||||
await page.evaluate(() =>
|
||||
// `loadFile` is defined on the page itself.
|
||||
// eslint-disable-next-line no-undef
|
||||
loadFile()
|
||||
);
|
||||
return page.evaluate(`(function () {return (${script})(${action});})()`);
|
||||
};
|
||||
chromeless.goto(`file://${indexHTML}`)
|
||||
.setFileInput('#file', testDir(file))
|
||||
// the index.html handler for file input will add a #loaded element when it
|
||||
// finishes.
|
||||
.wait('#loaded')
|
||||
.evaluate(`function () {return (${script})(${action});}`)
|
||||
;
|
||||
|
||||
// immediately invoked async function to let us wait for each test to finish before starting the next.
|
||||
(async () => {
|
||||
const browser = await chromium.launch();
|
||||
const page = await browser.newPage();
|
||||
|
||||
const testOperation = async function (name, action, expect) {
|
||||
await test(name, async t => {
|
||||
|
||||
const results = await runFile('test-mouse-touch.sb2', action, page, boundAction => {
|
||||
const results = await runFile('test-mouse-touch.sb2', action, boundAction => {
|
||||
vm.greenFlag();
|
||||
const sendResults = [];
|
||||
|
||||
@@ -102,10 +97,5 @@ const runFile = async (file, action, page, script) => {
|
||||
}
|
||||
|
||||
// close the browser window we used
|
||||
await browser.close();
|
||||
})().catch(err => {
|
||||
// Handle promise rejections by exiting with a nonzero code to ensure that tests don't erroneously pass
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
await chromeless.end();
|
||||
})();
|
||||
|
||||
@@ -1,15 +1,55 @@
|
||||
/* global vm, Promise */
|
||||
const {chromium} = require('playwright-chromium');
|
||||
const {Chromeless} = require('chromeless');
|
||||
const test = require('tap').test;
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const allGpuModes = ['ForceCPU', 'ForceGPU', 'Automatic'];
|
||||
const chromeless = new Chromeless();
|
||||
|
||||
const indexHTML = path.resolve(__dirname, 'index.html');
|
||||
const testDir = (...args) => path.resolve(__dirname, 'scratch-tests', ...args);
|
||||
|
||||
const checkOneGpuMode = (t, says) => {
|
||||
const testFile = file => test(file, async t => {
|
||||
// start each test by going to the index.html, and loading the scratch file
|
||||
const says = await chromeless.goto(`file://${indexHTML}`)
|
||||
.setFileInput('#file', testDir(file))
|
||||
// the index.html handler for file input will add a #loaded element when it
|
||||
// finishes.
|
||||
.wait('#loaded')
|
||||
.evaluate(() => {
|
||||
// This function is run INSIDE the integration chrome browser via some
|
||||
// injection and .toString() magic. We can return some "simple data"
|
||||
// back across as a promise, so we will just log all the says that happen
|
||||
// for parsing after.
|
||||
|
||||
// this becomes the `says` in the outer scope
|
||||
const messages = [];
|
||||
const TIMEOUT = 5000;
|
||||
|
||||
vm.runtime.on('SAY', (_, __, message) => {
|
||||
messages.push(message);
|
||||
});
|
||||
|
||||
vm.greenFlag();
|
||||
const startTime = Date.now();
|
||||
|
||||
return Promise.resolve()
|
||||
.then(async () => {
|
||||
// waiting for all threads to complete, then we return
|
||||
while (vm.runtime.threads.some(thread => vm.runtime.isActiveThread(thread))) {
|
||||
if ((Date.now() - startTime) >= TIMEOUT) {
|
||||
// if we push the message after end, the failure from tap is not very useful:
|
||||
// "not ok test after end() was called"
|
||||
messages.unshift(`fail Threads still running after ${TIMEOUT}ms`);
|
||||
break;
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
}
|
||||
|
||||
return messages;
|
||||
});
|
||||
});
|
||||
|
||||
// Map string messages to tap reporting methods. This will be used
|
||||
// with events from scratch's runtime emitted on block instructions.
|
||||
let didPlan = false;
|
||||
@@ -59,78 +99,17 @@ const checkOneGpuMode = (t, says) => {
|
||||
t.fail('did not say "end"');
|
||||
t.end();
|
||||
}
|
||||
};
|
||||
|
||||
const testFile = async (file, page) => {
|
||||
// start each test by going to the index.html, and loading the scratch file
|
||||
await page.goto(`file://${indexHTML}`);
|
||||
const fileInput = await page.$('#file');
|
||||
await fileInput.setInputFiles(testDir(file));
|
||||
await page.evaluate(() =>
|
||||
// `loadFile` is defined on the page itself.
|
||||
// eslint-disable-next-line no-undef
|
||||
loadFile()
|
||||
);
|
||||
const says = await page.evaluate(async useGpuModes => {
|
||||
// This function is run INSIDE the integration chrome browser via some
|
||||
// injection and .toString() magic. We can return some "simple data"
|
||||
// back across as a promise, so we will just log all the says that happen
|
||||
// for parsing after.
|
||||
|
||||
// this becomes the `says` in the outer scope
|
||||
const allMessages = {};
|
||||
const TIMEOUT = 5000;
|
||||
|
||||
vm.runtime.on('SAY', (_, __, message) => {
|
||||
const messages = allMessages[vm.renderer._useGpuMode];
|
||||
messages.push(message);
|
||||
});
|
||||
|
||||
for (const useGpuMode of useGpuModes) {
|
||||
const messages = allMessages[useGpuMode] = [];
|
||||
|
||||
vm.renderer.setUseGpuMode(useGpuMode);
|
||||
vm.greenFlag();
|
||||
const startTime = Date.now();
|
||||
|
||||
// wait for all threads to complete before moving on to the next mode
|
||||
while (vm.runtime.threads.some(thread => vm.runtime.isActiveThread(thread))) {
|
||||
if ((Date.now() - startTime) >= TIMEOUT) {
|
||||
// if we push the message after end, the failure from tap is not very useful:
|
||||
// "not ok test after end() was called"
|
||||
messages.unshift(`fail Threads still running after ${TIMEOUT}ms`);
|
||||
break;
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
}
|
||||
}
|
||||
|
||||
return allMessages;
|
||||
}, allGpuModes);
|
||||
|
||||
for (const gpuMode of allGpuModes) {
|
||||
test(`File: ${file}, GPU Mode: ${gpuMode}`, t => checkOneGpuMode(t, says[gpuMode]));
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// immediately invoked async function to let us wait for each test to finish before starting the next.
|
||||
(async () => {
|
||||
const browser = await chromium.launch();
|
||||
const page = await browser.newPage();
|
||||
|
||||
const files = fs.readdirSync(testDir())
|
||||
.filter(uri => uri.endsWith('.sb2') || uri.endsWith('.sb3'));
|
||||
|
||||
for (const file of files) {
|
||||
await testFile(file, page);
|
||||
await testFile(file);
|
||||
}
|
||||
|
||||
// close the browser window we used
|
||||
await browser.close();
|
||||
})().catch(err => {
|
||||
// Handle promise rejections by exiting with a nonzero code to ensure that tests don't erroneously pass
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
await chromeless.end();
|
||||
})();
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user