diff --git a/src/core/obj_bin_transform_core.js b/src/core/obj_bin_transform_core.js index 76ec76b81..8704bc062 100644 --- a/src/core/obj_bin_transform_core.js +++ b/src/core/obj_bin_transform_core.js @@ -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; } diff --git a/src/core/pattern.js b/src/core/pattern.js index 7cdee3fc3..db55561df 100644 --- a/src/core/pattern.js +++ b/src/core/pattern.js @@ -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, diff --git a/src/display/obj_bin_transform_display.js b/src/display/obj_bin_transform_display.js index 30dabaae3..9e21c7bb0 100644 --- a/src/display/obj_bin_transform_display.js +++ b/src/display/obj_bin_transform_display.js @@ -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, diff --git a/src/display/pattern_helper.js b/src/display/pattern_helper.js index bfe175574..70c390de3 100644 --- a/src/display/pattern_helper.js +++ b/src/display/pattern_helper.js @@ -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); } diff --git a/src/display/webgpu.js b/src/display/webgpu.js index 7ecb2820d..1bd704bcc 100644 --- a/src/display/webgpu.js +++ b/src/display/webgpu.js @@ -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, diff --git a/test/unit/obj_bin_transform_spec.js b/test/unit/obj_bin_transform_spec.js index 199df449d..beaf675b9 100644 --- a/test/unit/obj_bin_transform_spec.js +++ b/test/unit/obj_bin_transform_spec.js @@ -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, diff --git a/test/unit/pattern_spec.js b/test/unit/pattern_spec.js index e85f9f9f2..6b36caa1d 100644 --- a/test/unit/pattern_spec.js +++ b/test/unit/pattern_spec.js @@ -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)); }); }); });