mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-04-18 03:04:07 +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 = [],
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user