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:
calixteman 2026-04-13 23:55:52 +02:00 committed by GitHub
commit 1025af059f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
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));
});
});
});