mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-08-02 20:37:21 +02:00
Merge pull request #21099 from calixteman/no_gpu
Use the gpu for drawing meshes only when it has more than 16 triangles (bug 2030745)
This commit is contained in:
commit
1025af059f
@ -265,7 +265,6 @@ function compilePatternInfo(ir) {
|
|||||||
coords = [],
|
coords = [],
|
||||||
colors = [],
|
colors = [],
|
||||||
colorStops = [],
|
colorStops = [],
|
||||||
figures = [],
|
|
||||||
shadingType = null, // only needed for mesh patterns
|
shadingType = null, // only needed for mesh patterns
|
||||||
background = null; // background for mesh patterns
|
background = null; // background for mesh patterns
|
||||||
|
|
||||||
@ -285,7 +284,6 @@ function compilePatternInfo(ir) {
|
|||||||
shadingType = ir[1];
|
shadingType = ir[1];
|
||||||
coords = ir[2];
|
coords = ir[2];
|
||||||
colors = ir[3];
|
colors = ir[3];
|
||||||
figures = ir[4] || [];
|
|
||||||
bbox = ir[6];
|
bbox = ir[6];
|
||||||
background = ir[7];
|
background = ir[7];
|
||||||
break;
|
break;
|
||||||
@ -296,18 +294,6 @@ function compilePatternInfo(ir) {
|
|||||||
const nCoord = Math.floor(coords.length / 2);
|
const nCoord = Math.floor(coords.length / 2);
|
||||||
const nColor = Math.floor(colors.length / 4);
|
const nColor = Math.floor(colors.length / 4);
|
||||||
const nStop = colorStops.length;
|
const nStop = colorStops.length;
|
||||||
const nFigures = figures.length;
|
|
||||||
|
|
||||||
let figuresSize = 0;
|
|
||||||
for (const figure of figures) {
|
|
||||||
figuresSize += 1;
|
|
||||||
figuresSize = Math.ceil(figuresSize / 4) * 4; // Ensure 4-byte alignment
|
|
||||||
figuresSize += 4 + figure.coords.length * 4;
|
|
||||||
figuresSize += 4 + figure.colors.length * 4;
|
|
||||||
if (figure.verticesPerRow !== undefined) {
|
|
||||||
figuresSize += 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const byteLen =
|
const byteLen =
|
||||||
20 +
|
20 +
|
||||||
@ -315,8 +301,7 @@ function compilePatternInfo(ir) {
|
|||||||
nColor * 4 +
|
nColor * 4 +
|
||||||
nStop * 8 +
|
nStop * 8 +
|
||||||
(bbox ? 16 : 0) +
|
(bbox ? 16 : 0) +
|
||||||
(background ? 3 : 0) +
|
(background ? 3 : 0);
|
||||||
figuresSize;
|
|
||||||
const buffer = new ArrayBuffer(byteLen);
|
const buffer = new ArrayBuffer(byteLen);
|
||||||
const dataView = new DataView(buffer);
|
const dataView = new DataView(buffer);
|
||||||
const u8data = new Uint8Array(buffer);
|
const u8data = new Uint8Array(buffer);
|
||||||
@ -328,7 +313,7 @@ function compilePatternInfo(ir) {
|
|||||||
dataView.setUint32(PATTERN_INFO.N_COORD, nCoord, true);
|
dataView.setUint32(PATTERN_INFO.N_COORD, nCoord, true);
|
||||||
dataView.setUint32(PATTERN_INFO.N_COLOR, nColor, true);
|
dataView.setUint32(PATTERN_INFO.N_COLOR, nColor, true);
|
||||||
dataView.setUint32(PATTERN_INFO.N_STOP, nStop, true);
|
dataView.setUint32(PATTERN_INFO.N_STOP, nStop, true);
|
||||||
dataView.setUint32(PATTERN_INFO.N_FIGURES, nFigures, true);
|
dataView.setUint32(PATTERN_INFO.N_FIGURES, 0, true);
|
||||||
|
|
||||||
let offset = 20;
|
let offset = 20;
|
||||||
const coordsView = new Float32Array(buffer, offset, nCoord * 2);
|
const coordsView = new Float32Array(buffer, offset, nCoord * 2);
|
||||||
@ -353,34 +338,6 @@ function compilePatternInfo(ir) {
|
|||||||
|
|
||||||
if (background) {
|
if (background) {
|
||||||
u8data.set(background, offset);
|
u8data.set(background, offset);
|
||||||
offset += 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < figures.length; i++) {
|
|
||||||
const figure = figures[i];
|
|
||||||
dataView.setUint8(offset, figure.type);
|
|
||||||
offset += 1;
|
|
||||||
// Ensure 4-byte alignment
|
|
||||||
offset = Math.ceil(offset / 4) * 4;
|
|
||||||
dataView.setUint32(offset, figure.coords.length, true);
|
|
||||||
offset += 4;
|
|
||||||
const figureCoordsView = new Int32Array(
|
|
||||||
buffer,
|
|
||||||
offset,
|
|
||||||
figure.coords.length
|
|
||||||
);
|
|
||||||
figureCoordsView.set(figure.coords);
|
|
||||||
offset += figure.coords.length * 4;
|
|
||||||
dataView.setUint32(offset, figure.colors.length, true);
|
|
||||||
offset += 4;
|
|
||||||
const colorsView = new Int32Array(buffer, offset, figure.colors.length);
|
|
||||||
colorsView.set(figure.colors);
|
|
||||||
offset += figure.colors.length * 4;
|
|
||||||
|
|
||||||
if (figure.verticesPerRow !== undefined) {
|
|
||||||
dataView.setUint32(offset, figure.verticesPerRow, true);
|
|
||||||
offset += 4;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -379,6 +379,63 @@ function meshPackData(self) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildMeshVertexData(coords, colors, figures) {
|
||||||
|
// Count the total expanded vertex count first for a single allocation.
|
||||||
|
let vertexCount = 0;
|
||||||
|
for (const figure of figures) {
|
||||||
|
if (figure.type === MeshFigureType.TRIANGLES) {
|
||||||
|
vertexCount += figure.coords.length;
|
||||||
|
} else if (figure.type === MeshFigureType.LATTICE) {
|
||||||
|
const vpr = figure.verticesPerRow;
|
||||||
|
vertexCount +=
|
||||||
|
(Math.floor(figure.coords.length / vpr) - 1) * (vpr - 1) * 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// posData: 2 × float32 per vertex (raw PDF content-space x, y).
|
||||||
|
// colData: 4 × uint8 per vertex (r, g, b, unused).
|
||||||
|
const posData = new Float32Array(vertexCount * 2);
|
||||||
|
const colData = new Uint8Array(vertexCount * 4);
|
||||||
|
let pOff = 0,
|
||||||
|
cOff = 0;
|
||||||
|
|
||||||
|
const addVertex = (pi, ci) => {
|
||||||
|
posData[pOff++] = coords[pi * 2];
|
||||||
|
posData[pOff++] = coords[pi * 2 + 1];
|
||||||
|
colData[cOff++] = colors[ci * 4];
|
||||||
|
colData[cOff++] = colors[ci * 4 + 1];
|
||||||
|
colData[cOff++] = colors[ci * 4 + 2];
|
||||||
|
cOff++; // alpha padding
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const figure of figures) {
|
||||||
|
const ps = figure.coords;
|
||||||
|
const cs = figure.colors;
|
||||||
|
if (figure.type === MeshFigureType.TRIANGLES) {
|
||||||
|
for (let i = 0, ii = ps.length; i < ii; i++) {
|
||||||
|
addVertex(ps[i], cs[i]);
|
||||||
|
}
|
||||||
|
} else if (figure.type === MeshFigureType.LATTICE) {
|
||||||
|
const vpr = figure.verticesPerRow;
|
||||||
|
const rows = Math.floor(ps.length / vpr) - 1;
|
||||||
|
const cols = vpr - 1;
|
||||||
|
for (let i = 0; i < rows; i++) {
|
||||||
|
let q = i * vpr;
|
||||||
|
for (let j = 0; j < cols; j++, q++) {
|
||||||
|
addVertex(ps[q], cs[q]);
|
||||||
|
addVertex(ps[q + 1], cs[q + 1]);
|
||||||
|
addVertex(ps[q + vpr], cs[q + vpr]);
|
||||||
|
addVertex(ps[q + vpr + 1], cs[q + vpr + 1]);
|
||||||
|
addVertex(ps[q + 1], cs[q + 1]);
|
||||||
|
addVertex(ps[q + vpr], cs[q + vpr]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { posData, colData, vertexCount };
|
||||||
|
}
|
||||||
|
|
||||||
// Type 1 shading: a 2-in, n-out function sampled over a rectangular domain.
|
// Type 1 shading: a 2-in, n-out function sampled over a rectangular domain.
|
||||||
class FunctionBasedShading extends BaseShading {
|
class FunctionBasedShading extends BaseShading {
|
||||||
// Maximum grid steps per axis to avoid huge meshes.
|
// Maximum grid steps per axis to avoid huge meshes.
|
||||||
@ -485,12 +542,17 @@ class FunctionBasedShading extends BaseShading {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getIR() {
|
getIR() {
|
||||||
|
const { posData, colData, vertexCount } = buildMeshVertexData(
|
||||||
|
this.coords,
|
||||||
|
this.colors,
|
||||||
|
this.figures
|
||||||
|
);
|
||||||
return [
|
return [
|
||||||
"Mesh",
|
"Mesh",
|
||||||
ShadingType.FUNCTION_BASED,
|
ShadingType.FUNCTION_BASED,
|
||||||
this.coords,
|
posData,
|
||||||
this.colors,
|
colData,
|
||||||
this.figures,
|
vertexCount,
|
||||||
this.bounds,
|
this.bounds,
|
||||||
this.bbox,
|
this.bbox,
|
||||||
this.background,
|
this.background,
|
||||||
@ -1114,12 +1176,17 @@ class MeshShading extends BaseShading {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getIR() {
|
getIR() {
|
||||||
|
const { posData, colData, vertexCount } = buildMeshVertexData(
|
||||||
|
this.coords,
|
||||||
|
this.colors,
|
||||||
|
this.figures
|
||||||
|
);
|
||||||
return [
|
return [
|
||||||
"Mesh",
|
"Mesh",
|
||||||
this.shadingType,
|
this.shadingType,
|
||||||
this.coords,
|
posData,
|
||||||
this.colors,
|
colData,
|
||||||
this.figures,
|
vertexCount,
|
||||||
this.bounds,
|
this.bounds,
|
||||||
this.bbox,
|
this.bbox,
|
||||||
this.background,
|
this.background,
|
||||||
|
|||||||
@ -13,13 +13,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { assert, BBOX_INIT, FeatureTest, Util } from "../shared/util.js";
|
||||||
assert,
|
|
||||||
BBOX_INIT,
|
|
||||||
FeatureTest,
|
|
||||||
MeshFigureType,
|
|
||||||
Util,
|
|
||||||
} from "../shared/util.js";
|
|
||||||
import {
|
import {
|
||||||
CSS_FONT_INFO,
|
CSS_FONT_INFO,
|
||||||
FONT_INFO,
|
FONT_INFO,
|
||||||
@ -354,7 +348,6 @@ class PatternInfo {
|
|||||||
const nCoord = dataView.getUint32(PATTERN_INFO.N_COORD, true);
|
const nCoord = dataView.getUint32(PATTERN_INFO.N_COORD, true);
|
||||||
const nColor = dataView.getUint32(PATTERN_INFO.N_COLOR, true);
|
const nColor = dataView.getUint32(PATTERN_INFO.N_COLOR, true);
|
||||||
const nStop = dataView.getUint32(PATTERN_INFO.N_STOP, true);
|
const nStop = dataView.getUint32(PATTERN_INFO.N_STOP, true);
|
||||||
const nFigures = dataView.getUint32(PATTERN_INFO.N_FIGURES, true);
|
|
||||||
|
|
||||||
let offset = 20;
|
let offset = 20;
|
||||||
const coords = new Float32Array(this.buffer, offset, nCoord * 2);
|
const coords = new Float32Array(this.buffer, offset, nCoord * 2);
|
||||||
@ -384,37 +377,6 @@ class PatternInfo {
|
|||||||
offset += 3;
|
offset += 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
const figures = [];
|
|
||||||
for (let i = 0; i < nFigures; ++i) {
|
|
||||||
const type = dataView.getUint8(offset);
|
|
||||||
offset += 1;
|
|
||||||
// Ensure 4-byte alignment
|
|
||||||
offset = Math.ceil(offset / 4) * 4;
|
|
||||||
|
|
||||||
const coordsLength = dataView.getUint32(offset, true);
|
|
||||||
offset += 4;
|
|
||||||
const figureCoords = new Int32Array(this.buffer, offset, coordsLength);
|
|
||||||
offset += coordsLength * 4;
|
|
||||||
|
|
||||||
const colorsLength = dataView.getUint32(offset, true);
|
|
||||||
offset += 4;
|
|
||||||
const figureColors = new Int32Array(this.buffer, offset, colorsLength);
|
|
||||||
offset += colorsLength * 4;
|
|
||||||
|
|
||||||
const figure = {
|
|
||||||
type,
|
|
||||||
coords: figureCoords,
|
|
||||||
colors: figureColors,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (type === MeshFigureType.LATTICE) {
|
|
||||||
figure.verticesPerRow = dataView.getUint32(offset, true);
|
|
||||||
offset += 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
figures.push(figure);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (kind === 1) {
|
if (kind === 1) {
|
||||||
// axial
|
// axial
|
||||||
return [
|
return [
|
||||||
@ -455,7 +417,7 @@ class PatternInfo {
|
|||||||
shadingType,
|
shadingType,
|
||||||
coords,
|
coords,
|
||||||
colors,
|
colors,
|
||||||
figures,
|
nCoord,
|
||||||
bounds,
|
bounds,
|
||||||
bbox,
|
bbox,
|
||||||
background,
|
background,
|
||||||
|
|||||||
@ -14,13 +14,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { drawMeshWithGPU, isGPUReady, loadMeshShader } from "./webgpu.js";
|
import { drawMeshWithGPU, isGPUReady, loadMeshShader } from "./webgpu.js";
|
||||||
import {
|
import { FormatError, info, unreachable, Util } from "../shared/util.js";
|
||||||
FormatError,
|
|
||||||
info,
|
|
||||||
MeshFigureType,
|
|
||||||
unreachable,
|
|
||||||
Util,
|
|
||||||
} from "../shared/util.js";
|
|
||||||
import { getCurrentTransform } from "./display_utils.js";
|
import { getCurrentTransform } from "./display_utils.js";
|
||||||
|
|
||||||
const PathType = {
|
const PathType = {
|
||||||
@ -377,66 +371,12 @@ function drawTriangle(data, context, p1, p2, p3, c1, c2, c3) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawFigure(data, figure, context) {
|
|
||||||
const ps = figure.coords;
|
|
||||||
const cs = figure.colors;
|
|
||||||
let i, ii;
|
|
||||||
switch (figure.type) {
|
|
||||||
case MeshFigureType.LATTICE:
|
|
||||||
const verticesPerRow = figure.verticesPerRow;
|
|
||||||
const rows = Math.floor(ps.length / verticesPerRow) - 1;
|
|
||||||
const cols = verticesPerRow - 1;
|
|
||||||
for (i = 0; i < rows; i++) {
|
|
||||||
let q = i * verticesPerRow;
|
|
||||||
for (let j = 0; j < cols; j++, q++) {
|
|
||||||
drawTriangle(
|
|
||||||
data,
|
|
||||||
context,
|
|
||||||
ps[q],
|
|
||||||
ps[q + 1],
|
|
||||||
ps[q + verticesPerRow],
|
|
||||||
cs[q],
|
|
||||||
cs[q + 1],
|
|
||||||
cs[q + verticesPerRow]
|
|
||||||
);
|
|
||||||
drawTriangle(
|
|
||||||
data,
|
|
||||||
context,
|
|
||||||
ps[q + verticesPerRow + 1],
|
|
||||||
ps[q + 1],
|
|
||||||
ps[q + verticesPerRow],
|
|
||||||
cs[q + verticesPerRow + 1],
|
|
||||||
cs[q + 1],
|
|
||||||
cs[q + verticesPerRow]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case MeshFigureType.TRIANGLES:
|
|
||||||
for (i = 0, ii = ps.length; i < ii; i += 3) {
|
|
||||||
drawTriangle(
|
|
||||||
data,
|
|
||||||
context,
|
|
||||||
ps[i],
|
|
||||||
ps[i + 1],
|
|
||||||
ps[i + 2],
|
|
||||||
cs[i],
|
|
||||||
cs[i + 1],
|
|
||||||
cs[i + 2]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error("illegal figure");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MeshShadingPattern extends BaseShadingPattern {
|
class MeshShadingPattern extends BaseShadingPattern {
|
||||||
constructor(IR) {
|
constructor(IR) {
|
||||||
super();
|
super();
|
||||||
this._coords = IR[2];
|
this._posData = IR[2];
|
||||||
this._colors = IR[3];
|
this._colData = IR[3];
|
||||||
this._figures = IR[4];
|
this._vertexCount = IR[4];
|
||||||
this._bounds = IR[5];
|
this._bounds = IR[5];
|
||||||
this._bbox = IR[6];
|
this._bbox = IR[6];
|
||||||
this._background = IR[7];
|
this._background = IR[7];
|
||||||
@ -477,8 +417,8 @@ class MeshShadingPattern extends BaseShadingPattern {
|
|||||||
const scaleY = boundsHeight ? boundsHeight / height : 1;
|
const scaleY = boundsHeight ? boundsHeight / height : 1;
|
||||||
|
|
||||||
const context = {
|
const context = {
|
||||||
coords: this._coords,
|
coords: this._posData,
|
||||||
colors: this._colors,
|
colors: this._colData,
|
||||||
offsetX: -offsetX,
|
offsetX: -offsetX,
|
||||||
offsetY: -offsetY,
|
offsetY: -offsetY,
|
||||||
scaleX: 1 / scaleX,
|
scaleX: 1 / scaleX,
|
||||||
@ -489,10 +429,17 @@ class MeshShadingPattern extends BaseShadingPattern {
|
|||||||
const paddedHeight = height + BORDER_SIZE * 2;
|
const paddedHeight = height + BORDER_SIZE * 2;
|
||||||
const tmpCanvas = canvasFactory.create(paddedWidth, paddedHeight);
|
const tmpCanvas = canvasFactory.create(paddedWidth, paddedHeight);
|
||||||
|
|
||||||
if (isGPUReady()) {
|
// Use the GPU path when there are more than 16 triangles (> 48 vertices).
|
||||||
|
// With small meshes the GPU overhead is significant and the CPU path is
|
||||||
|
// faster. The texture has to move from the GPU to the main thread and it's
|
||||||
|
// costly. So it's frequent to have a lot of mesh-based shading patterns
|
||||||
|
// when rendering some 3D surfaces (see bug 2030745).
|
||||||
|
if (isGPUReady() && this._vertexCount > 48) {
|
||||||
tmpCanvas.context.drawImage(
|
tmpCanvas.context.drawImage(
|
||||||
drawMeshWithGPU(
|
drawMeshWithGPU(
|
||||||
this._figures,
|
this._posData,
|
||||||
|
this._colData,
|
||||||
|
this._vertexCount,
|
||||||
context,
|
context,
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
paddedWidth,
|
paddedWidth,
|
||||||
@ -513,8 +460,8 @@ class MeshShadingPattern extends BaseShadingPattern {
|
|||||||
bytes[i + 3] = 255;
|
bytes[i + 3] = 255;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const figure of this._figures) {
|
for (let i = 0, ii = this._vertexCount; i < ii; i += 3) {
|
||||||
drawFigure(data, figure, context);
|
drawTriangle(data, context, i, i + 1, i + 2, i, i + 1, i + 2);
|
||||||
}
|
}
|
||||||
tmpCanvas.context.putImageData(data, BORDER_SIZE, BORDER_SIZE);
|
tmpCanvas.context.putImageData(data, BORDER_SIZE, BORDER_SIZE);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,8 +13,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MeshFigureType } from "../shared/util.js";
|
|
||||||
|
|
||||||
// WGSL shader for Gouraud-shaded triangle mesh rasterization.
|
// WGSL shader for Gouraud-shaded triangle mesh rasterization.
|
||||||
// Vertices arrive in PDF content-space coordinates; the vertex shader
|
// Vertices arrive in PDF content-space coordinates; the vertex shader
|
||||||
// applies the affine transform supplied via a uniform buffer to map them
|
// applies the affine transform supplied via a uniform buffer to map them
|
||||||
@ -141,94 +139,13 @@ class WebGPU {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Build flat Float32Array (positions) and Uint8Array (colors) vertex
|
|
||||||
* streams for non-indexed triangle-list rendering.
|
|
||||||
*
|
|
||||||
* Coords and colors intentionally use separate lookup indices. For patch
|
|
||||||
* mesh figures (types 6/7 converted to LATTICE in the worker), the coord
|
|
||||||
* index-space and color index-space differ because the stream interleaves
|
|
||||||
* them at different densities (12 coords but 4 colors per flag-0 patch).
|
|
||||||
* A single shared index buffer cannot represent both simultaneously, so we
|
|
||||||
* expand each triangle vertex individually into the two flat streams.
|
|
||||||
*
|
|
||||||
* @param {Array} figures
|
|
||||||
* @param {Object} context coords/colors/offsetX/offsetY/scaleX/scaleY
|
|
||||||
* @returns {{ posData: Float32Array, colData: Uint8Array,
|
|
||||||
* vertexCount: number }}
|
|
||||||
*/
|
|
||||||
#buildVertexStreams(figures, context) {
|
|
||||||
const { coords, colors } = context;
|
|
||||||
|
|
||||||
// Count vertices first so we can allocate the typed arrays exactly once.
|
|
||||||
let vertexCount = 0;
|
|
||||||
for (const figure of figures) {
|
|
||||||
const ps = figure.coords;
|
|
||||||
if (figure.type === MeshFigureType.TRIANGLES) {
|
|
||||||
vertexCount += ps.length;
|
|
||||||
} else if (figure.type === MeshFigureType.LATTICE) {
|
|
||||||
const vpr = figure.verticesPerRow;
|
|
||||||
// 2 triangles × 3 vertices per quad cell
|
|
||||||
vertexCount += (Math.floor(ps.length / vpr) - 1) * (vpr - 1) * 6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// posData: 2 × float32 per vertex (raw PDF content-space x, y).
|
|
||||||
// colData: 4 × uint8 per vertex (r, g, b, unused — required by unorm8x4).
|
|
||||||
const posData = new Float32Array(vertexCount * 2);
|
|
||||||
const colData = new Uint8Array(vertexCount * 4);
|
|
||||||
let pOff = 0,
|
|
||||||
cOff = 0;
|
|
||||||
|
|
||||||
// pi and ci are raw vertex indices; coords is stride-2, colors stride-4.
|
|
||||||
const addVertex = (pi, ci) => {
|
|
||||||
posData[pOff++] = coords[pi * 2];
|
|
||||||
posData[pOff++] = coords[pi * 2 + 1];
|
|
||||||
colData[cOff++] = colors[ci * 4];
|
|
||||||
colData[cOff++] = colors[ci * 4 + 1];
|
|
||||||
colData[cOff++] = colors[ci * 4 + 2];
|
|
||||||
cOff++; // alpha channel — unused in the fragment shader
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const figure of figures) {
|
|
||||||
const ps = figure.coords;
|
|
||||||
const cs = figure.colors;
|
|
||||||
if (figure.type === MeshFigureType.TRIANGLES) {
|
|
||||||
for (let i = 0, ii = ps.length; i < ii; i += 3) {
|
|
||||||
addVertex(ps[i], cs[i]);
|
|
||||||
addVertex(ps[i + 1], cs[i + 1]);
|
|
||||||
addVertex(ps[i + 2], cs[i + 2]);
|
|
||||||
}
|
|
||||||
} else if (figure.type === MeshFigureType.LATTICE) {
|
|
||||||
const vpr = figure.verticesPerRow;
|
|
||||||
const rows = Math.floor(ps.length / vpr) - 1;
|
|
||||||
const cols = vpr - 1;
|
|
||||||
for (let i = 0; i < rows; i++) {
|
|
||||||
let q = i * vpr;
|
|
||||||
for (let j = 0; j < cols; j++, q++) {
|
|
||||||
// Upper-left triangle: q, q+1, q+vpr
|
|
||||||
addVertex(ps[q], cs[q]);
|
|
||||||
addVertex(ps[q + 1], cs[q + 1]);
|
|
||||||
addVertex(ps[q + vpr], cs[q + vpr]);
|
|
||||||
// Lower-right triangle: q+vpr+1, q+1, q+vpr
|
|
||||||
addVertex(ps[q + vpr + 1], cs[q + vpr + 1]);
|
|
||||||
addVertex(ps[q + 1], cs[q + 1]);
|
|
||||||
addVertex(ps[q + vpr], cs[q + vpr]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { posData, colData, vertexCount };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render a mesh shading to an ImageBitmap using WebGPU.
|
* Render a mesh shading to an ImageBitmap using WebGPU.
|
||||||
*
|
*
|
||||||
* Two flat vertex streams (positions and colors) are uploaded from the
|
* The flat vertex streams (positions and colors) were pre-built by the
|
||||||
* packed IR typed arrays. A uniform buffer carries the affine transform
|
* worker and arrive ready to upload. A uniform buffer carries the affine
|
||||||
* so the vertex shader maps PDF content-space coordinates to NDC without
|
* transform so the vertex shader maps PDF content-space coordinates to NDC
|
||||||
* any CPU arithmetic per vertex.
|
* without any CPU arithmetic per vertex.
|
||||||
*
|
*
|
||||||
* After `device.queue.submit()`, `transferToImageBitmap()` presents the
|
* After `device.queue.submit()`, `transferToImageBitmap()` presents the
|
||||||
* current GPU frame synchronously – the browser ensures all submitted GPU
|
* current GPU frame synchronously – the browser ensures all submitted GPU
|
||||||
@ -237,8 +154,10 @@ class WebGPU {
|
|||||||
*
|
*
|
||||||
* The GPU device must already be initialized (`this.isReady === true`).
|
* The GPU device must already be initialized (`this.isReady === true`).
|
||||||
*
|
*
|
||||||
* @param {Array} figures
|
* @param {Float32Array} posData flat vertex positions (x,y per vertex)
|
||||||
* @param {Object} context coords/colors/offsetX/offsetY/…
|
* @param {Uint8Array} colData flat vertex colors (r,g,b,_ per vertex)
|
||||||
|
* @param {number} vertexCount
|
||||||
|
* @param {Object} context offsetX/offsetY/scaleX/scaleY
|
||||||
* @param {Uint8Array|null} backgroundColor [r,g,b] or null for transparent
|
* @param {Uint8Array|null} backgroundColor [r,g,b] or null for transparent
|
||||||
* @param {number} paddedWidth render-target width
|
* @param {number} paddedWidth render-target width
|
||||||
* @param {number} paddedHeight render-target height
|
* @param {number} paddedHeight render-target height
|
||||||
@ -246,7 +165,9 @@ class WebGPU {
|
|||||||
* @returns {ImageBitmap}
|
* @returns {ImageBitmap}
|
||||||
*/
|
*/
|
||||||
draw(
|
draw(
|
||||||
figures,
|
posData,
|
||||||
|
colData,
|
||||||
|
vertexCount,
|
||||||
context,
|
context,
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
paddedWidth,
|
paddedWidth,
|
||||||
@ -258,10 +179,6 @@ class WebGPU {
|
|||||||
|
|
||||||
const device = this.#device;
|
const device = this.#device;
|
||||||
const { offsetX, offsetY, scaleX, scaleY } = context;
|
const { offsetX, offsetY, scaleX, scaleY } = context;
|
||||||
const { posData, colData, vertexCount } = this.#buildVertexStreams(
|
|
||||||
figures,
|
|
||||||
context
|
|
||||||
);
|
|
||||||
|
|
||||||
// Upload vertex positions (raw PDF coords) and colors as separate buffers.
|
// Upload vertex positions (raw PDF coords) and colors as separate buffers.
|
||||||
// GPUBufferUsage requires size > 0.
|
// GPUBufferUsage requires size > 0.
|
||||||
@ -381,7 +298,9 @@ function loadMeshShader() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function drawMeshWithGPU(
|
function drawMeshWithGPU(
|
||||||
figures,
|
posData,
|
||||||
|
colData,
|
||||||
|
vertexCount,
|
||||||
context,
|
context,
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
paddedWidth,
|
paddedWidth,
|
||||||
@ -389,7 +308,9 @@ function drawMeshWithGPU(
|
|||||||
borderSize
|
borderSize
|
||||||
) {
|
) {
|
||||||
return _webGPU.draw(
|
return _webGPU.draw(
|
||||||
figures,
|
posData,
|
||||||
|
colData,
|
||||||
|
vertexCount,
|
||||||
context,
|
context,
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
paddedWidth,
|
paddedWidth,
|
||||||
|
|||||||
@ -27,7 +27,7 @@ import {
|
|||||||
PatternInfo,
|
PatternInfo,
|
||||||
SystemFontInfo,
|
SystemFontInfo,
|
||||||
} from "../../src/display/obj_bin_transform_display.js";
|
} from "../../src/display/obj_bin_transform_display.js";
|
||||||
import { FeatureTest, MeshFigureType } from "../../src/shared/util.js";
|
import { FeatureTest } from "../../src/shared/util.js";
|
||||||
|
|
||||||
describe("obj_bin_transform", function () {
|
describe("obj_bin_transform", function () {
|
||||||
describe("Font data", function () {
|
describe("Font data", function () {
|
||||||
@ -208,6 +208,8 @@ describe("obj_bin_transform", function () {
|
|||||||
25,
|
25,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Vertices are pre-expanded in the new IR format: posData/colData contain
|
||||||
|
// one entry per vertex (no indexing), and ir[4] is the vertex count.
|
||||||
const meshPatternIR = [
|
const meshPatternIR = [
|
||||||
"Mesh",
|
"Mesh",
|
||||||
4,
|
4,
|
||||||
@ -218,19 +220,7 @@ describe("obj_bin_transform", function () {
|
|||||||
255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 255, 0, 255, 255, 0, 0, 128, 128, 128,
|
255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 255, 0, 255, 255, 0, 0, 128, 128, 128,
|
||||||
0, 255, 0, 255, 0, 0, 255, 255, 0, 255, 128, 0, 0, 128, 0, 128, 0,
|
0, 255, 0, 255, 0, 0, 255, 255, 0, 255, 128, 0, 0, 128, 0, 128, 0,
|
||||||
]),
|
]),
|
||||||
[
|
9, // vertexCount (3 triangles × 3 vertices)
|
||||||
{
|
|
||||||
type: MeshFigureType.TRIANGLES,
|
|
||||||
coords: new Int32Array([0, 2, 4, 6, 8, 10, 12, 14, 16]),
|
|
||||||
colors: new Int32Array([0, 2, 4, 6, 8, 10, 12, 14, 16]),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: MeshFigureType.LATTICE,
|
|
||||||
coords: new Int32Array([0, 2, 4, 6, 8, 10]),
|
|
||||||
colors: new Int32Array([0, 2, 4, 6, 8, 10]),
|
|
||||||
verticesPerRow: 3,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[0, 0, 100, 100],
|
[0, 0, 100, 100],
|
||||||
[0, 0, 100, 100],
|
[0, 0, 100, 100],
|
||||||
[128, 128, 128],
|
[128, 128, 128],
|
||||||
@ -302,27 +292,7 @@ describe("obj_bin_transform", function () {
|
|||||||
expect(Array.from(reconstructedIR[3])).toEqual(
|
expect(Array.from(reconstructedIR[3])).toEqual(
|
||||||
Array.from(meshPatternIR[3])
|
Array.from(meshPatternIR[3])
|
||||||
);
|
);
|
||||||
expect(reconstructedIR[4].length).toEqual(2);
|
expect(reconstructedIR[4]).toEqual(9); // vertexCount
|
||||||
|
|
||||||
const fig1 = reconstructedIR[4][0];
|
|
||||||
expect(fig1.type).toEqual(MeshFigureType.TRIANGLES);
|
|
||||||
expect(fig1.coords).toBeInstanceOf(Int32Array);
|
|
||||||
expect(Array.from(fig1.coords)).toEqual([
|
|
||||||
0, 2, 4, 6, 8, 10, 12, 14, 16,
|
|
||||||
]);
|
|
||||||
expect(fig1.colors).toBeInstanceOf(Int32Array);
|
|
||||||
expect(Array.from(fig1.colors)).toEqual([
|
|
||||||
0, 2, 4, 6, 8, 10, 12, 14, 16,
|
|
||||||
]);
|
|
||||||
expect(fig1.verticesPerRow).toBeUndefined();
|
|
||||||
|
|
||||||
const fig2 = reconstructedIR[4][1];
|
|
||||||
expect(fig2.type).toEqual(MeshFigureType.LATTICE);
|
|
||||||
expect(fig2.coords).toBeInstanceOf(Int32Array);
|
|
||||||
expect(Array.from(fig2.coords)).toEqual([0, 2, 4, 6, 8, 10]);
|
|
||||||
expect(fig2.colors).toBeInstanceOf(Int32Array);
|
|
||||||
expect(Array.from(fig2.colors)).toEqual([0, 2, 4, 6, 8, 10]);
|
|
||||||
expect(fig2.verticesPerRow).toEqual(3);
|
|
||||||
|
|
||||||
expect(reconstructedIR[5]).toEqual([0, 0, 100, 100]);
|
expect(reconstructedIR[5]).toEqual([0, 0, 100, 100]);
|
||||||
expect(reconstructedIR[6]).toEqual([0, 0, 100, 100]);
|
expect(reconstructedIR[6]).toEqual([0, 0, 100, 100]);
|
||||||
@ -330,42 +300,38 @@ describe("obj_bin_transform", function () {
|
|||||||
expect(Array.from(reconstructedIR[7])).toEqual([128, 128, 128]);
|
expect(Array.from(reconstructedIR[7])).toEqual([128, 128, 128]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("must handle mesh patterns with no figures", function () {
|
it("must handle mesh patterns with no vertices", function () {
|
||||||
const noFiguresIR = [
|
const noVerticesIR = [
|
||||||
"Mesh",
|
"Mesh",
|
||||||
4,
|
4,
|
||||||
new Float32Array([0, 0, 10, 10]),
|
new Float32Array([0, 0, 10, 10]),
|
||||||
new Uint8Array([255, 0, 0, 0]),
|
new Uint8Array([255, 0, 0, 0]),
|
||||||
[],
|
2, // vertexCount
|
||||||
[0, 0, 10, 10],
|
[0, 0, 10, 10],
|
||||||
[0, 0, 10, 10],
|
[0, 0, 10, 10],
|
||||||
null,
|
null,
|
||||||
];
|
];
|
||||||
|
|
||||||
const buffer = compilePatternInfo(noFiguresIR);
|
const buffer = compilePatternInfo(noVerticesIR);
|
||||||
const patternInfo = new PatternInfo(buffer);
|
const patternInfo = new PatternInfo(buffer);
|
||||||
const reconstructedIR = patternInfo.getIR();
|
const reconstructedIR = patternInfo.getIR();
|
||||||
|
|
||||||
expect(reconstructedIR[4]).toEqual([]);
|
expect(reconstructedIR[4]).toEqual(2); // vertexCount
|
||||||
expect(reconstructedIR[7]).toBeNull(); // background should be null
|
expect(reconstructedIR[7]).toBeNull(); // background should be null
|
||||||
});
|
});
|
||||||
|
|
||||||
it("must preserve figure data integrity across serialization", function () {
|
it("must preserve vertex data integrity across serialization", function () {
|
||||||
const buffer = compilePatternInfo(meshPatternIR);
|
const buffer = compilePatternInfo(meshPatternIR);
|
||||||
const patternInfo = new PatternInfo(buffer);
|
const patternInfo = new PatternInfo(buffer);
|
||||||
const reconstructedIR = patternInfo.getIR();
|
const reconstructedIR = patternInfo.getIR();
|
||||||
|
|
||||||
// Verify data integrity by checking exact values
|
// Verify posData and colData are preserved exactly
|
||||||
const originalFig = meshPatternIR[4][0];
|
expect(Array.from(reconstructedIR[2])).toEqual(
|
||||||
const reconstructedFig = reconstructedIR[4][0];
|
Array.from(meshPatternIR[2])
|
||||||
|
);
|
||||||
for (let i = 0; i < originalFig.coords.length; i++) {
|
expect(Array.from(reconstructedIR[3])).toEqual(
|
||||||
expect(reconstructedFig.coords[i]).toEqual(originalFig.coords[i]);
|
Array.from(meshPatternIR[3])
|
||||||
}
|
);
|
||||||
|
|
||||||
for (let i = 0; i < originalFig.colors.length; i++) {
|
|
||||||
expect(reconstructedFig.colors[i]).toEqual(originalFig.colors[i]);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("must calculate correct buffer sizes for different pattern types", function () {
|
it("must calculate correct buffer sizes for different pattern types", function () {
|
||||||
@ -378,36 +344,25 @@ describe("obj_bin_transform", function () {
|
|||||||
expect(meshBuffer.byteLength).toBeGreaterThan(radialBuffer.byteLength);
|
expect(meshBuffer.byteLength).toBeGreaterThan(radialBuffer.byteLength);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("must handle figures with different type enums correctly", function () {
|
it("must round-trip mesh pattern posData and colData correctly", function () {
|
||||||
const customFiguresIR = [
|
const customMeshIR = [
|
||||||
"Mesh",
|
"Mesh",
|
||||||
6,
|
6,
|
||||||
new Float32Array([0, 0, 10, 10]),
|
new Float32Array([0, 0, 10, 10]),
|
||||||
new Uint8Array([255, 128, 64, 0]),
|
new Uint8Array([255, 128, 64, 0]),
|
||||||
[
|
2, // vertexCount
|
||||||
{
|
|
||||||
type: MeshFigureType.PATCH,
|
|
||||||
coords: new Int32Array([0, 2]),
|
|
||||||
colors: new Int32Array([0, 2]),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: MeshFigureType.TRIANGLES,
|
|
||||||
coords: new Int32Array([0]),
|
|
||||||
colors: new Int32Array([0]),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[0, 0, 10, 10],
|
[0, 0, 10, 10],
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
];
|
];
|
||||||
|
|
||||||
const buffer = compilePatternInfo(customFiguresIR);
|
const buffer = compilePatternInfo(customMeshIR);
|
||||||
const patternInfo = new PatternInfo(buffer);
|
const patternInfo = new PatternInfo(buffer);
|
||||||
const reconstructedIR = patternInfo.getIR();
|
const reconstructedIR = patternInfo.getIR();
|
||||||
|
|
||||||
expect(reconstructedIR[4].length).toEqual(2);
|
expect(reconstructedIR[4]).toEqual(2); // vertexCount
|
||||||
expect(reconstructedIR[4][0].type).toEqual(MeshFigureType.PATCH);
|
expect(Array.from(reconstructedIR[2])).toEqual([0, 0, 10, 10]);
|
||||||
expect(reconstructedIR[4][1].type).toEqual(MeshFigureType.TRIANGLES);
|
expect(Array.from(reconstructedIR[3])).toEqual([255, 128, 64, 0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("must handle mesh patterns with different background values", function () {
|
it("must handle mesh patterns with different background values", function () {
|
||||||
@ -416,7 +371,7 @@ describe("obj_bin_transform", function () {
|
|||||||
4,
|
4,
|
||||||
new Float32Array([0, 0, 10, 10]),
|
new Float32Array([0, 0, 10, 10]),
|
||||||
new Uint8Array([255, 0, 0, 0]),
|
new Uint8Array([255, 0, 0, 0]),
|
||||||
[],
|
2, // vertexCount
|
||||||
[0, 0, 10, 10],
|
[0, 0, 10, 10],
|
||||||
[0, 0, 10, 10],
|
[0, 0, 10, 10],
|
||||||
new Uint8Array([255, 128, 64]),
|
new Uint8Array([255, 128, 64]),
|
||||||
@ -433,7 +388,7 @@ describe("obj_bin_transform", function () {
|
|||||||
5,
|
5,
|
||||||
new Float32Array([0, 0, 5, 5]),
|
new Float32Array([0, 0, 5, 5]),
|
||||||
new Uint8Array([0, 255, 0, 0]),
|
new Uint8Array([0, 255, 0, 0]),
|
||||||
[],
|
2, // vertexCount
|
||||||
[0, 0, 5, 5],
|
[0, 0, 5, 5],
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
@ -452,7 +407,7 @@ describe("obj_bin_transform", function () {
|
|||||||
4,
|
4,
|
||||||
new Float32Array([-10, -5, 20, 15, 0, 30]),
|
new Float32Array([-10, -5, 20, 15, 0, 30]),
|
||||||
new Uint8Array([255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 255, 0]),
|
new Uint8Array([255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 255, 0]),
|
||||||
[],
|
3, // vertexCount
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
|||||||
@ -74,33 +74,13 @@ describe("pattern", function () {
|
|||||||
|
|
||||||
expect(ir[0]).toEqual("Mesh");
|
expect(ir[0]).toEqual("Mesh");
|
||||||
expect(ir[1]).toEqual(1);
|
expect(ir[1]).toEqual(1);
|
||||||
|
// Vertices are pre-expanded: 3×4 lattice →
|
||||||
|
// 6 quads → 12 triangles → 36 vertices
|
||||||
expect(ir[2]).toBeInstanceOf(Float32Array);
|
expect(ir[2]).toBeInstanceOf(Float32Array);
|
||||||
expect(ir[2].length).toEqual(24);
|
expect(ir[2].length).toEqual(72); // 36 vertices × 2 coords
|
||||||
expect(Array.from(ir[2].slice(0, 6))).toEqual([10, 20, 11, 20, 12, 20]);
|
expect(ir[3]).toBeInstanceOf(Uint8Array);
|
||||||
expect(Array.from(ir[2].slice(-6))).toEqual([10, 23, 11, 23, 12, 23]);
|
expect(ir[3].length).toEqual(144); // 36 vertices × 4 bytes
|
||||||
|
expect(ir[4]).toEqual(36); // vertexCount
|
||||||
expect(ir[3]).toBeInstanceOf(Uint8ClampedArray);
|
|
||||||
expect(ir[3].length).toEqual(48);
|
|
||||||
expect(Array.from(ir[3].slice(0, 12))).toEqual([
|
|
||||||
0, 0, 0, 0, 128, 0, 0, 0, 191, 0, 0, 0,
|
|
||||||
]);
|
|
||||||
expect(Array.from(ir[3].slice(-12))).toEqual([
|
|
||||||
0, 212, 0, 0, 128, 212, 0, 0, 191, 212, 0, 0,
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(ir[4]).toEqual([
|
|
||||||
jasmine.objectContaining({
|
|
||||||
verticesPerRow: 3,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
expect(ir[4][0].coords).toBeInstanceOf(Uint32Array);
|
|
||||||
expect(Array.from(ir[4][0].coords)).toEqual([
|
|
||||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
|
|
||||||
]);
|
|
||||||
expect(ir[4][0].colors).toBeInstanceOf(Uint32Array);
|
|
||||||
expect(Array.from(ir[4][0].colors)).toEqual([
|
|
||||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
|
|
||||||
]);
|
|
||||||
expect(ir[5]).toEqual([10, 20, 12, 23]);
|
expect(ir[5]).toEqual([10, 20, 12, 23]);
|
||||||
expect(ir[6]).toBeNull();
|
expect(ir[6]).toBeNull();
|
||||||
expect(ir[7]).toBeNull();
|
expect(ir[7]).toBeNull();
|
||||||
@ -110,17 +90,14 @@ describe("pattern", function () {
|
|||||||
const shading = createFunctionBasedShading({
|
const shading = createFunctionBasedShading({
|
||||||
background: [0.25, 0.5, 0.75],
|
background: [0.25, 0.5, 0.75],
|
||||||
});
|
});
|
||||||
const buffer = compilePatternInfo(shading.getIR());
|
const ir = shading.getIR();
|
||||||
|
const buffer = compilePatternInfo(ir);
|
||||||
const reconstructedIR = new PatternInfo(buffer).getIR();
|
const reconstructedIR = new PatternInfo(buffer).getIR();
|
||||||
|
|
||||||
expect(reconstructedIR[0]).toEqual("Mesh");
|
expect(reconstructedIR[0]).toEqual("Mesh");
|
||||||
expect(reconstructedIR[1]).toEqual(1);
|
expect(reconstructedIR[1]).toEqual(1);
|
||||||
expect(Array.from(reconstructedIR[2])).toEqual(
|
expect(Array.from(reconstructedIR[2])).toEqual(Array.from(ir[2]));
|
||||||
Array.from(shading.coords)
|
expect(Array.from(reconstructedIR[3])).toEqual(Array.from(ir[3]));
|
||||||
);
|
|
||||||
expect(Array.from(reconstructedIR[3])).toEqual(
|
|
||||||
Array.from(shading.colors)
|
|
||||||
);
|
|
||||||
expect(Array.from(reconstructedIR[7])).toEqual([64, 128, 191]);
|
expect(Array.from(reconstructedIR[7])).toEqual([64, 128, 191]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -134,8 +111,8 @@ describe("pattern", function () {
|
|||||||
});
|
});
|
||||||
const [, , , colors] = shading.getIR();
|
const [, , , colors] = shading.getIR();
|
||||||
|
|
||||||
expect(colors.length).toEqual(48);
|
expect(colors.length).toEqual(144);
|
||||||
expect(Array.from(colors)).toEqual(new Array(48).fill(0));
|
expect(Array.from(colors)).toEqual(new Array(144).fill(0));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user