Use the gpu for drawing meshes only when it has more than 16 triangles (bug 2030745)

And in order to slightly improve performances, move the figure creation in the worker.
This commit is contained in:
Calixte Denizet 2026-04-13 21:05:00 +02:00
parent 96debf0c81
commit a2c57ee69e
No known key found for this signature in database
GPG Key ID: 0C5442631EE0691F
7 changed files with 151 additions and 365 deletions

View File

@ -265,7 +265,6 @@ function compilePatternInfo(ir) {
coords = [],
colors = [],
colorStops = [],
figures = [],
shadingType = null, // only needed for mesh patterns
background = null; // background for mesh patterns
@ -285,7 +284,6 @@ function compilePatternInfo(ir) {
shadingType = ir[1];
coords = ir[2];
colors = ir[3];
figures = ir[4] || [];
bbox = ir[6];
background = ir[7];
break;
@ -296,18 +294,6 @@ function compilePatternInfo(ir) {
const nCoord = Math.floor(coords.length / 2);
const nColor = Math.floor(colors.length / 4);
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 =
20 +
@ -315,8 +301,7 @@ function compilePatternInfo(ir) {
nColor * 4 +
nStop * 8 +
(bbox ? 16 : 0) +
(background ? 3 : 0) +
figuresSize;
(background ? 3 : 0);
const buffer = new ArrayBuffer(byteLen);
const dataView = new DataView(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_COLOR, nColor, 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;
const coordsView = new Float32Array(buffer, offset, nCoord * 2);
@ -353,34 +338,6 @@ function compilePatternInfo(ir) {
if (background) {
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;
}

View File

@ -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.
class FunctionBasedShading extends BaseShading {
// Maximum grid steps per axis to avoid huge meshes.
@ -485,12 +542,17 @@ class FunctionBasedShading extends BaseShading {
}
getIR() {
const { posData, colData, vertexCount } = buildMeshVertexData(
this.coords,
this.colors,
this.figures
);
return [
"Mesh",
ShadingType.FUNCTION_BASED,
this.coords,
this.colors,
this.figures,
posData,
colData,
vertexCount,
this.bounds,
this.bbox,
this.background,
@ -1114,12 +1176,17 @@ class MeshShading extends BaseShading {
}
getIR() {
const { posData, colData, vertexCount } = buildMeshVertexData(
this.coords,
this.colors,
this.figures
);
return [
"Mesh",
this.shadingType,
this.coords,
this.colors,
this.figures,
posData,
colData,
vertexCount,
this.bounds,
this.bbox,
this.background,

View File

@ -13,13 +13,7 @@
* limitations under the License.
*/
import {
assert,
BBOX_INIT,
FeatureTest,
MeshFigureType,
Util,
} from "../shared/util.js";
import { assert, BBOX_INIT, FeatureTest, Util } from "../shared/util.js";
import {
CSS_FONT_INFO,
FONT_INFO,
@ -354,7 +348,6 @@ class PatternInfo {
const nCoord = dataView.getUint32(PATTERN_INFO.N_COORD, true);
const nColor = dataView.getUint32(PATTERN_INFO.N_COLOR, true);
const nStop = dataView.getUint32(PATTERN_INFO.N_STOP, true);
const nFigures = dataView.getUint32(PATTERN_INFO.N_FIGURES, true);
let offset = 20;
const coords = new Float32Array(this.buffer, offset, nCoord * 2);
@ -384,37 +377,6 @@ class PatternInfo {
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) {
// axial
return [
@ -455,7 +417,7 @@ class PatternInfo {
shadingType,
coords,
colors,
figures,
nCoord,
bounds,
bbox,
background,

View File

@ -14,13 +14,7 @@
*/
import { drawMeshWithGPU, isGPUReady, loadMeshShader } from "./webgpu.js";
import {
FormatError,
info,
MeshFigureType,
unreachable,
Util,
} from "../shared/util.js";
import { FormatError, info, unreachable, Util } from "../shared/util.js";
import { getCurrentTransform } from "./display_utils.js";
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 {
constructor(IR) {
super();
this._coords = IR[2];
this._colors = IR[3];
this._figures = IR[4];
this._posData = IR[2];
this._colData = IR[3];
this._vertexCount = IR[4];
this._bounds = IR[5];
this._bbox = IR[6];
this._background = IR[7];
@ -477,8 +417,8 @@ class MeshShadingPattern extends BaseShadingPattern {
const scaleY = boundsHeight ? boundsHeight / height : 1;
const context = {
coords: this._coords,
colors: this._colors,
coords: this._posData,
colors: this._colData,
offsetX: -offsetX,
offsetY: -offsetY,
scaleX: 1 / scaleX,
@ -489,10 +429,17 @@ class MeshShadingPattern extends BaseShadingPattern {
const paddedHeight = height + BORDER_SIZE * 2;
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(
drawMeshWithGPU(
this._figures,
this._posData,
this._colData,
this._vertexCount,
context,
backgroundColor,
paddedWidth,
@ -513,8 +460,8 @@ class MeshShadingPattern extends BaseShadingPattern {
bytes[i + 3] = 255;
}
}
for (const figure of this._figures) {
drawFigure(data, figure, context);
for (let i = 0, ii = this._vertexCount; i < ii; i += 3) {
drawTriangle(data, context, i, i + 1, i + 2, i, i + 1, i + 2);
}
tmpCanvas.context.putImageData(data, BORDER_SIZE, BORDER_SIZE);
}

View File

@ -13,8 +13,6 @@
* limitations under the License.
*/
import { MeshFigureType } from "../shared/util.js";
// WGSL shader for Gouraud-shaded triangle mesh rasterization.
// Vertices arrive in PDF content-space coordinates; the vertex shader
// 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.
*
* Two flat vertex streams (positions and colors) are uploaded from the
* packed IR typed arrays. A uniform buffer carries the affine transform
* so the vertex shader maps PDF content-space coordinates to NDC without
* any CPU arithmetic per vertex.
* The flat vertex streams (positions and colors) were pre-built by the
* worker and arrive ready to upload. A uniform buffer carries the affine
* transform so the vertex shader maps PDF content-space coordinates to NDC
* without any CPU arithmetic per vertex.
*
* After `device.queue.submit()`, `transferToImageBitmap()` presents the
* 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`).
*
* @param {Array} figures
* @param {Object} context coords/colors/offsetX/offsetY/
* @param {Float32Array} posData flat vertex positions (x,y per vertex)
* @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 {number} paddedWidth render-target width
* @param {number} paddedHeight render-target height
@ -246,7 +165,9 @@ class WebGPU {
* @returns {ImageBitmap}
*/
draw(
figures,
posData,
colData,
vertexCount,
context,
backgroundColor,
paddedWidth,
@ -258,10 +179,6 @@ class WebGPU {
const device = this.#device;
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.
// GPUBufferUsage requires size > 0.
@ -381,7 +298,9 @@ function loadMeshShader() {
}
function drawMeshWithGPU(
figures,
posData,
colData,
vertexCount,
context,
backgroundColor,
paddedWidth,
@ -389,7 +308,9 @@ function drawMeshWithGPU(
borderSize
) {
return _webGPU.draw(
figures,
posData,
colData,
vertexCount,
context,
backgroundColor,
paddedWidth,

View File

@ -27,7 +27,7 @@ import {
PatternInfo,
SystemFontInfo,
} 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("Font data", function () {
@ -208,6 +208,8 @@ describe("obj_bin_transform", function () {
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 = [
"Mesh",
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,
0, 255, 0, 255, 0, 0, 255, 255, 0, 255, 128, 0, 0, 128, 0, 128, 0,
]),
[
{
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,
},
],
9, // vertexCount (3 triangles × 3 vertices)
[0, 0, 100, 100],
[0, 0, 100, 100],
[128, 128, 128],
@ -302,27 +292,7 @@ describe("obj_bin_transform", function () {
expect(Array.from(reconstructedIR[3])).toEqual(
Array.from(meshPatternIR[3])
);
expect(reconstructedIR[4].length).toEqual(2);
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[4]).toEqual(9); // vertexCount
expect(reconstructedIR[5]).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]);
});
it("must handle mesh patterns with no figures", function () {
const noFiguresIR = [
it("must handle mesh patterns with no vertices", function () {
const noVerticesIR = [
"Mesh",
4,
new Float32Array([0, 0, 10, 10]),
new Uint8Array([255, 0, 0, 0]),
[],
2, // vertexCount
[0, 0, 10, 10],
[0, 0, 10, 10],
null,
];
const buffer = compilePatternInfo(noFiguresIR);
const buffer = compilePatternInfo(noVerticesIR);
const patternInfo = new PatternInfo(buffer);
const reconstructedIR = patternInfo.getIR();
expect(reconstructedIR[4]).toEqual([]);
expect(reconstructedIR[4]).toEqual(2); // vertexCount
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 patternInfo = new PatternInfo(buffer);
const reconstructedIR = patternInfo.getIR();
// Verify data integrity by checking exact values
const originalFig = meshPatternIR[4][0];
const reconstructedFig = reconstructedIR[4][0];
for (let i = 0; i < originalFig.coords.length; i++) {
expect(reconstructedFig.coords[i]).toEqual(originalFig.coords[i]);
}
for (let i = 0; i < originalFig.colors.length; i++) {
expect(reconstructedFig.colors[i]).toEqual(originalFig.colors[i]);
}
// Verify posData and colData are preserved exactly
expect(Array.from(reconstructedIR[2])).toEqual(
Array.from(meshPatternIR[2])
);
expect(Array.from(reconstructedIR[3])).toEqual(
Array.from(meshPatternIR[3])
);
});
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);
});
it("must handle figures with different type enums correctly", function () {
const customFiguresIR = [
it("must round-trip mesh pattern posData and colData correctly", function () {
const customMeshIR = [
"Mesh",
6,
new Float32Array([0, 0, 10, 10]),
new Uint8Array([255, 128, 64, 0]),
[
{
type: MeshFigureType.PATCH,
coords: new Int32Array([0, 2]),
colors: new Int32Array([0, 2]),
},
{
type: MeshFigureType.TRIANGLES,
coords: new Int32Array([0]),
colors: new Int32Array([0]),
},
],
2, // vertexCount
[0, 0, 10, 10],
null,
null,
];
const buffer = compilePatternInfo(customFiguresIR);
const buffer = compilePatternInfo(customMeshIR);
const patternInfo = new PatternInfo(buffer);
const reconstructedIR = patternInfo.getIR();
expect(reconstructedIR[4].length).toEqual(2);
expect(reconstructedIR[4][0].type).toEqual(MeshFigureType.PATCH);
expect(reconstructedIR[4][1].type).toEqual(MeshFigureType.TRIANGLES);
expect(reconstructedIR[4]).toEqual(2); // vertexCount
expect(Array.from(reconstructedIR[2])).toEqual([0, 0, 10, 10]);
expect(Array.from(reconstructedIR[3])).toEqual([255, 128, 64, 0]);
});
it("must handle mesh patterns with different background values", function () {
@ -416,7 +371,7 @@ describe("obj_bin_transform", function () {
4,
new Float32Array([0, 0, 10, 10]),
new Uint8Array([255, 0, 0, 0]),
[],
2, // vertexCount
[0, 0, 10, 10],
[0, 0, 10, 10],
new Uint8Array([255, 128, 64]),
@ -433,7 +388,7 @@ describe("obj_bin_transform", function () {
5,
new Float32Array([0, 0, 5, 5]),
new Uint8Array([0, 255, 0, 0]),
[],
2, // vertexCount
[0, 0, 5, 5],
null,
null,
@ -452,7 +407,7 @@ describe("obj_bin_transform", function () {
4,
new Float32Array([-10, -5, 20, 15, 0, 30]),
new Uint8Array([255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 255, 0]),
[],
3, // vertexCount
null,
null,
null,

View File

@ -74,33 +74,13 @@ describe("pattern", function () {
expect(ir[0]).toEqual("Mesh");
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].length).toEqual(24);
expect(Array.from(ir[2].slice(0, 6))).toEqual([10, 20, 11, 20, 12, 20]);
expect(Array.from(ir[2].slice(-6))).toEqual([10, 23, 11, 23, 12, 23]);
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[2].length).toEqual(72); // 36 vertices × 2 coords
expect(ir[3]).toBeInstanceOf(Uint8Array);
expect(ir[3].length).toEqual(144); // 36 vertices × 4 bytes
expect(ir[4]).toEqual(36); // vertexCount
expect(ir[5]).toEqual([10, 20, 12, 23]);
expect(ir[6]).toBeNull();
expect(ir[7]).toBeNull();
@ -110,17 +90,14 @@ describe("pattern", function () {
const shading = createFunctionBasedShading({
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();
expect(reconstructedIR[0]).toEqual("Mesh");
expect(reconstructedIR[1]).toEqual(1);
expect(Array.from(reconstructedIR[2])).toEqual(
Array.from(shading.coords)
);
expect(Array.from(reconstructedIR[3])).toEqual(
Array.from(shading.colors)
);
expect(Array.from(reconstructedIR[2])).toEqual(Array.from(ir[2]));
expect(Array.from(reconstructedIR[3])).toEqual(Array.from(ir[3]));
expect(Array.from(reconstructedIR[7])).toEqual([64, 128, 191]);
});
@ -134,8 +111,8 @@ describe("pattern", function () {
});
const [, , , colors] = shading.getIR();
expect(colors.length).toEqual(48);
expect(Array.from(colors)).toEqual(new Array(48).fill(0));
expect(colors.length).toEqual(144);
expect(Array.from(colors)).toEqual(new Array(144).fill(0));
});
});
});