Skip to content

Commit c47cbd2

Browse files
authored
feat(functions): Rename vertex count methods, fix getGLPrimitiveCount indexed counts (#1327)
1 parent 947faf2 commit c47cbd2

File tree

7 files changed

+93
-65
lines changed

7 files changed

+93
-65
lines changed

packages/cli/src/inspect.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,32 @@ function formatPropertyReport(property: AnyPropertyReport, index: number, format
121121

122122
function getFootnotes(type: string, rows: string[][], header: string[]): string[] {
123123
const footnotes = [];
124+
if (type === 'scenes') {
125+
for (let i = 0; i < header.length; i++) {
126+
if (header[i] === 'renderVertexCount') header[i] += '¹';
127+
if (header[i] === 'gpuVertexCount') header[i] += '²';
128+
if (header[i] === 'gpuNaiveVertexCount') header[i] += '³';
129+
}
130+
footnotes.push(
131+
'¹ Expected number of vertices processed by the vertex shader for one render\n' +
132+
' pass, without considering the vertex cache.\n',
133+
);
134+
footnotes.push(
135+
'² Expected number of vertices uploaded to GPU, assuming each Accessor\n' +
136+
' is uploaded only once. Actual number uploaded may be higher, \n' +
137+
' dependent on the implementation and vertex buffer layout.\n',
138+
);
139+
footnotes.push(
140+
'³ Expected number of vertices uploaded to GPU, assuming each Primitive\n' +
141+
' is uploaded once, duplicating vertex attributes shared among Primitives.',
142+
);
143+
}
124144
if (type === 'meshes') {
125145
for (let i = 0; i < header.length; i++) {
126146
if (header[i] === 'size') header[i] += '¹';
127147
}
128148
footnotes.push(
129-
'¹ size estimates GPU memory required by a mesh, in isolation. If accessors are\n' +
149+
' size estimates GPU memory required by a mesh, in isolation. If accessors are\n' +
130150
' shared by other mesh primitives, but the meshes themselves are not reused, then\n' +
131151
' the sum of all mesh sizes will overestimate the asset\'s total size. See "dedup".',
132152
);

packages/functions/src/get-vertex-count.ts

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,23 @@ export enum VertexCountMethod {
3232
* Typical GPU vertex caches are small, holding 16-32 vertices, and rarely
3333
* achieve 100% hit ratios in practice.
3434
*/
35-
RENDER_OPTIMISTIC = 'render-optimistic',
35+
RENDER_CACHED = 'render-cached',
3636

3737
/**
3838
* Expected number of vertices uploaded to the GPU, assuming that a client
39-
* uploads each unique {@link Primitive} individually, duplicating vertex
40-
* attribute {@link Accessor Accessors} shared by multiple primitives, but
41-
* never uploading the same mesh or primitive to GPU memory more than once.
39+
* uploads each unique {@link Accessor} only once. Unless glTF vertex
40+
* attributes are pre-processed to a known buffer layout, and the client is
41+
* optimized for that buffer layout, this total will be optimistic.
4242
*/
43-
UPLOAD = 'upload',
43+
GPU = 'gpu',
4444

4545
/**
4646
* Expected number of vertices uploaded to the GPU, assuming that a client
47-
* uploads each unique {@link Accessor} only once. Unless glTF vertex
48-
* attributes are pre-processed to a known buffer layout, and the client is
49-
* optimized for that buffer layout, this total will be optimistic.
47+
* uploads each unique {@link Primitive} individually, duplicating vertex
48+
* attribute {@link Accessor Accessors} shared by multiple primitives, but
49+
* never uploading the same mesh or primitive to GPU memory more than once.
5050
*/
51-
UPLOAD_OPTIMISTIC = 'upload-optimistic',
51+
GPU_NAIVE = 'gpu-naive',
5252

5353
/**
5454
* Total number of unique vertices represented, considering all attributes of
@@ -72,7 +72,7 @@ export enum VertexCountMethod {
7272

7373
/**
7474
* Number of vertex positions never used by any mesh primitive. If all
75-
* vertices are unused, this total will match `'upload-optimistic'`.
75+
* vertices are unused, this total will match `'gpu'`.
7676
*/
7777
UNUSED = 'unused',
7878
}
@@ -122,14 +122,14 @@ function _getSubtreeVertexCount(node: Node | Scene, method: VertexCountMethod):
122122

123123
switch (method) {
124124
case VertexCountMethod.RENDER:
125-
case VertexCountMethod.RENDER_OPTIMISTIC:
125+
case VertexCountMethod.RENDER_CACHED:
126126
return (
127127
_sum(nonInstancedMeshes.map((mesh) => getMeshVertexCount(mesh, method))) +
128128
_sum(instancedMeshes.map(([batch, mesh]) => batch * getMeshVertexCount(mesh, method)))
129129
);
130-
case VertexCountMethod.UPLOAD:
130+
case VertexCountMethod.GPU_NAIVE:
131131
return _sum(uniqueMeshes.map((mesh) => getMeshVertexCount(mesh, method)));
132-
case VertexCountMethod.UPLOAD_OPTIMISTIC:
132+
case VertexCountMethod.GPU:
133133
return _sum(uniquePositions.map((attribute) => attribute.getCount()));
134134
case VertexCountMethod.DISTINCT:
135135
case VertexCountMethod.DISTINCT_POSITION:
@@ -154,10 +154,10 @@ export function getMeshVertexCount(mesh: Mesh, method: VertexCountMethod): numbe
154154

155155
switch (method) {
156156
case VertexCountMethod.RENDER:
157-
case VertexCountMethod.RENDER_OPTIMISTIC:
158-
case VertexCountMethod.UPLOAD:
157+
case VertexCountMethod.RENDER_CACHED:
158+
case VertexCountMethod.GPU_NAIVE:
159159
return _sum(prims.map((prim) => getPrimitiveVertexCount(prim, method)));
160-
case VertexCountMethod.UPLOAD_OPTIMISTIC:
160+
case VertexCountMethod.GPU:
161161
return _sum(uniquePositions.map((attribute) => attribute.getCount()));
162162
case VertexCountMethod.DISTINCT:
163163
case VertexCountMethod.DISTINCT_POSITION:
@@ -180,10 +180,10 @@ export function getPrimitiveVertexCount(prim: Primitive, method: VertexCountMeth
180180
switch (method) {
181181
case VertexCountMethod.RENDER:
182182
return indices ? indices.getCount() : position.getCount();
183-
case VertexCountMethod.RENDER_OPTIMISTIC:
183+
case VertexCountMethod.RENDER_CACHED:
184184
return indices ? new Set(indices.getArray()).size : position.getCount();
185-
case VertexCountMethod.UPLOAD:
186-
case VertexCountMethod.UPLOAD_OPTIMISTIC:
185+
case VertexCountMethod.GPU_NAIVE:
186+
case VertexCountMethod.GPU:
187187
return position.getCount();
188188
case VertexCountMethod.DISTINCT:
189189
case VertexCountMethod.DISTINCT_POSITION:

packages/functions/src/inspect.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
} from '@gltf-transform/core';
1111
import { getGLPrimitiveCount } from './utils.js';
1212
import { KHR_DF_MODEL_ETC1S, KHR_DF_MODEL_UASTC, read as readKTX } from 'ktx-parse';
13-
import { VertexCountMethod, getSceneVertexCount } from './get-vertex-count.js';
13+
import { VertexCountMethod, getMeshVertexCount, getSceneVertexCount } from './get-vertex-count.js';
1414

1515
/** Inspects the contents of a glTF file and returns a JSON report. */
1616
export function inspect(doc: Document): InspectReport {
@@ -37,9 +37,8 @@ function listScenes(doc: Document): InspectPropertyReport<InspectSceneReport> {
3737
bboxMin: toPrecision(sceneBounds.min),
3838
bboxMax: toPrecision(sceneBounds.max),
3939
renderVertexCount: getSceneVertexCount(scene, VertexCountMethod.RENDER),
40-
renderOptimisticVertexCount: getSceneVertexCount(scene, VertexCountMethod.RENDER_OPTIMISTIC),
41-
uploadVertexCount: getSceneVertexCount(scene, VertexCountMethod.UPLOAD),
42-
uploadOptimisticVertexCount: getSceneVertexCount(scene, VertexCountMethod.UPLOAD_OPTIMISTIC),
40+
gpuVertexCount: getSceneVertexCount(scene, VertexCountMethod.GPU),
41+
gpuNaiveVertexCount: getSceneVertexCount(scene, VertexCountMethod.GPU_NAIVE),
4342
};
4443
});
4544
return { properties: scenes };
@@ -53,7 +52,6 @@ function listMeshes(doc: Document): InspectPropertyReport<InspectMeshReport> {
5352
.map((mesh) => {
5453
const instances = mesh.listParents().filter((parent) => parent.propertyType !== PropertyType.ROOT).length;
5554
let glPrimitives = 0;
56-
let verts = 0;
5755
const semantics = new Set<string>();
5856
const meshIndices = new Set<string>();
5957
const meshAccessors: Set<Accessor> = new Set();
@@ -72,7 +70,6 @@ function listMeshes(doc: Document): InspectPropertyReport<InspectMeshReport> {
7270
meshIndices.add(accessorToTypeLabel(indices));
7371
meshAccessors.add(indices);
7472
}
75-
verts += prim.listAttributes()[0].getCount();
7673
glPrimitives += getGLPrimitiveCount(prim);
7774
});
7875

@@ -84,9 +81,9 @@ function listMeshes(doc: Document): InspectPropertyReport<InspectMeshReport> {
8481
return {
8582
name: mesh.getName(),
8683
mode: Array.from(new Set(modes)),
87-
primitives: mesh.listPrimitives().length,
84+
meshPrimitives: mesh.listPrimitives().length,
8885
glPrimitives: glPrimitives,
89-
vertices: verts,
86+
vertices: getMeshVertexCount(mesh, VertexCountMethod.GPU),
9087
indices: Array.from(meshIndices).sort(),
9188
attributes: Array.from(semantics).sort(),
9289
instances: instances,
@@ -246,14 +243,13 @@ export interface InspectSceneReport {
246243
bboxMin: number[];
247244
bboxMax: number[];
248245
renderVertexCount: number;
249-
renderOptimisticVertexCount: number;
250-
uploadVertexCount: number;
251-
uploadOptimisticVertexCount: number;
246+
gpuVertexCount: number;
247+
gpuNaiveVertexCount: number;
252248
}
253249

254250
export interface InspectMeshReport {
255251
name: string;
256-
primitives: number;
252+
meshPrimitives: number;
257253
mode: string[];
258254
vertices: number;
259255
glPrimitives: number;

packages/functions/src/utils.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,18 +67,18 @@ export function getGLPrimitiveCount(prim: Primitive): number {
6767
// Reference: https://www.khronos.org/opengl/wiki/Primitive
6868
switch (prim.getMode()) {
6969
case Primitive.Mode.POINTS:
70-
return position.getCount();
70+
return indices ? indices.getCount() : position.getCount();
7171
case Primitive.Mode.LINES:
7272
return indices ? indices.getCount() / 2 : position.getCount() / 2;
7373
case Primitive.Mode.LINE_LOOP:
74-
return position.getCount();
74+
return indices ? indices.getCount() : position.getCount();
7575
case Primitive.Mode.LINE_STRIP:
76-
return position.getCount() - 1;
76+
return indices ? indices.getCount() - 1 : position.getCount() - 1;
7777
case Primitive.Mode.TRIANGLES:
7878
return indices ? indices.getCount() / 3 : position.getCount() / 3;
7979
case Primitive.Mode.TRIANGLE_STRIP:
8080
case Primitive.Mode.TRIANGLE_FAN:
81-
return position.getCount() - 2;
81+
return indices ? indices.getCount() - 2 : position.getCount() - 2;
8282
default:
8383
throw new Error('Unexpected mode: ' + prim.getMode());
8484
}

packages/functions/test/get-vertex-count.test.ts

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { EXTMeshGPUInstancing } from '@gltf-transform/extensions';
44
import { getSceneVertexCount, VertexCountMethod } from '@gltf-transform/functions';
55
import { logger } from '@gltf-transform/test-utils';
66

7-
const { RENDER, RENDER_OPTIMISTIC, UPLOAD, UPLOAD_OPTIMISTIC, UNUSED } = VertexCountMethod;
7+
const { RENDER, RENDER_CACHED, GPU, GPU_NAIVE, UNUSED } = VertexCountMethod;
88

99
test('render', async (t) => {
1010
const document = new Document().setLogger(logger);
@@ -15,31 +15,31 @@ test('render', async (t) => {
1515
t.is(getSceneVertexCount(createSceneUnused(document), RENDER), 15, 'unused');
1616
});
1717

18-
test('render-optimistic', async (t) => {
18+
test('render-cached', async (t) => {
1919
const document = new Document().setLogger(logger);
20-
t.is(getSceneVertexCount(createSceneBasic(document), RENDER_OPTIMISTIC), 32 * 4, 'basic');
21-
t.is(getSceneVertexCount(createSceneIndexed(document), RENDER_OPTIMISTIC), 32 + 5, 'indexed');
22-
t.is(getSceneVertexCount(createSceneInstanced(document), RENDER_OPTIMISTIC), 32 * 5, 'instanced');
23-
t.is(getSceneVertexCount(createSceneMixedAttributes(document), RENDER_OPTIMISTIC), 32 * 2, 'mixed attributes');
24-
t.is(getSceneVertexCount(createSceneUnused(document), RENDER_OPTIMISTIC), 11, 'unused');
20+
t.is(getSceneVertexCount(createSceneBasic(document), RENDER_CACHED), 32 * 4, 'basic');
21+
t.is(getSceneVertexCount(createSceneIndexed(document), RENDER_CACHED), 32 + 5, 'indexed');
22+
t.is(getSceneVertexCount(createSceneInstanced(document), RENDER_CACHED), 32 * 5, 'instanced');
23+
t.is(getSceneVertexCount(createSceneMixedAttributes(document), RENDER_CACHED), 32 * 2, 'mixed attributes');
24+
t.is(getSceneVertexCount(createSceneUnused(document), RENDER_CACHED), 11, 'unused');
2525
});
2626

27-
test('upload', async (t) => {
27+
test('gpu-naive', async (t) => {
2828
const document = new Document().setLogger(logger);
29-
t.is(getSceneVertexCount(createSceneBasic(document), UPLOAD), 32 * 4, 'basic');
30-
t.is(getSceneVertexCount(createSceneIndexed(document), UPLOAD), 32 * 2, 'indexed');
31-
t.is(getSceneVertexCount(createSceneInstanced(document), UPLOAD), 32, 'instanced');
32-
t.is(getSceneVertexCount(createSceneMixedAttributes(document), UPLOAD), 32 * 2, 'mixed attributes');
33-
t.is(getSceneVertexCount(createSceneUnused(document), UPLOAD), 32 * 2, 'unused');
29+
t.is(getSceneVertexCount(createSceneBasic(document), GPU_NAIVE), 32 * 4, 'basic');
30+
t.is(getSceneVertexCount(createSceneIndexed(document), GPU_NAIVE), 32 * 2, 'indexed');
31+
t.is(getSceneVertexCount(createSceneInstanced(document), GPU_NAIVE), 32, 'instanced');
32+
t.is(getSceneVertexCount(createSceneMixedAttributes(document), GPU_NAIVE), 32 * 2, 'mixed attributes');
33+
t.is(getSceneVertexCount(createSceneUnused(document), GPU_NAIVE), 32 * 2, 'unused');
3434
});
3535

36-
test('upload-optimistic', async (t) => {
36+
test('gpu', async (t) => {
3737
const document = new Document().setLogger(logger);
38-
t.is(getSceneVertexCount(createSceneBasic(document), UPLOAD_OPTIMISTIC), 32 * 4, 'basic');
39-
t.is(getSceneVertexCount(createSceneIndexed(document), UPLOAD_OPTIMISTIC), 32, 'indexed');
40-
t.is(getSceneVertexCount(createSceneInstanced(document), UPLOAD_OPTIMISTIC), 32, 'instanced');
41-
t.is(getSceneVertexCount(createSceneMixedAttributes(document), UPLOAD_OPTIMISTIC), 32, 'mixed attributes');
42-
t.is(getSceneVertexCount(createSceneUnused(document), UPLOAD_OPTIMISTIC), 32, 'unused');
38+
t.is(getSceneVertexCount(createSceneBasic(document), GPU), 32 * 4, 'basic');
39+
t.is(getSceneVertexCount(createSceneIndexed(document), GPU), 32, 'indexed');
40+
t.is(getSceneVertexCount(createSceneInstanced(document), GPU), 32, 'instanced');
41+
t.is(getSceneVertexCount(createSceneMixedAttributes(document), GPU), 32, 'mixed attributes');
42+
t.is(getSceneVertexCount(createSceneUnused(document), GPU), 32, 'unused');
4343
});
4444

4545
test.skip('distinct', async (t) => {

packages/functions/test/simplify.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ test('welded', async (t) => {
2626
const document = await io.read(path.join(__dirname, 'in', 'DenseSphere.glb'));
2727
const scene = document.getRoot().getDefaultScene()!;
2828

29-
const srcCount = getSceneVertexCount(scene, VertexCountMethod.UPLOAD);
29+
const srcCount = getSceneVertexCount(scene, VertexCountMethod.GPU_NAIVE);
3030
const srcBounds = roundBbox(getBounds(scene), 2);
3131

3232
await document.transform(weld(), simplify({ simplifier: MeshoptSimplifier, ratio: 0.5, error: 0.001 }));
3333

34-
const dstCount = getSceneVertexCount(scene, VertexCountMethod.UPLOAD);
34+
const dstCount = getSceneVertexCount(scene, VertexCountMethod.GPU_NAIVE);
3535
const dstBounds = roundBbox(getBounds(scene), 2);
3636

3737
t.truthy((srcCount - dstCount) / srcCount > 0.45, '>=45% reduction');
@@ -44,12 +44,12 @@ test('unwelded', async (t) => {
4444
const document = await io.read(path.join(__dirname, 'in', 'DenseSphere.glb'));
4545
const scene = document.getRoot().getDefaultScene()!;
4646

47-
const srcCount = getSceneVertexCount(scene, VertexCountMethod.UPLOAD);
47+
const srcCount = getSceneVertexCount(scene, VertexCountMethod.GPU_NAIVE);
4848
const srcBounds = roundBbox(getBounds(scene), 2);
4949

5050
await document.transform(unweld(), simplify({ simplifier: MeshoptSimplifier, ratio: 0.5, error: 0.001 }));
5151

52-
const dstCount = getSceneVertexCount(scene, VertexCountMethod.UPLOAD);
52+
const dstCount = getSceneVertexCount(scene, VertexCountMethod.GPU_NAIVE);
5353
const dstBounds = roundBbox(getBounds(scene), 2);
5454

5555
t.truthy((srcCount - dstCount) / srcCount > 0.45, '>=45% reduction');
@@ -79,12 +79,12 @@ test('shared accessors', async (t) => {
7979
const nodeB = document.createNode('B').setTranslation([-5, 0, 0]).setMesh(meshB);
8080
scene.addChild(nodeA).addChild(nodeB);
8181

82-
const srcCount = getSceneVertexCount(scene, VertexCountMethod.UPLOAD);
82+
const srcCount = getSceneVertexCount(scene, VertexCountMethod.GPU_NAIVE);
8383
const srcBounds = roundBbox(getBounds(scene), 2);
8484

8585
await document.transform(unweld(), simplify({ simplifier: MeshoptSimplifier, ratio: 0.5 }));
8686

87-
const dstCount = getSceneVertexCount(scene, VertexCountMethod.UPLOAD);
87+
const dstCount = getSceneVertexCount(scene, VertexCountMethod.GPU_NAIVE);
8888
const dstBounds = roundBbox(getBounds(scene), 2);
8989

9090
t.truthy((srcCount - dstCount) / srcCount > 0.5, '>=50% reduction');
@@ -109,7 +109,7 @@ test('degenerate', async (t) => {
109109
t.true(mesh.isDisposed(), 'mesh disposed');
110110
t.true(node.isDisposed(), 'node disposed');
111111
t.false(scene.isDisposed(), 'scene kept');
112-
t.is(getSceneVertexCount(scene, VertexCountMethod.UPLOAD), 0, '0 vertices');
112+
t.is(getSceneVertexCount(scene, VertexCountMethod.GPU_NAIVE), 0, '0 vertices');
113113
});
114114

115115
test('torus', async (t) => {

packages/functions/test/utils.test.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,28 +10,40 @@ test('getGLPrimitiveCount', async (t) => {
1010
const prim = doc.createPrimitive().setMode(Primitive.Mode.TRIANGLES).setAttribute('POSITION', position);
1111
const indexedPrim = prim.clone().setIndices(indices);
1212

13-
t.is(getGLPrimitiveCount(prim), 11, 'triangles');
14-
t.is(getGLPrimitiveCount(indexedPrim), 2, 'triangles (indexed)');
15-
1613
prim.setMode(Primitive.Mode.POINTS);
14+
indexedPrim.setMode(Primitive.Mode.POINTS);
1715
t.is(getGLPrimitiveCount(prim), 33, 'points');
16+
t.is(getGLPrimitiveCount(indexedPrim), 6, 'points (indexed)');
1817

1918
prim.setMode(Primitive.Mode.LINES);
2019
indexedPrim.setMode(Primitive.Mode.LINES);
2120
t.is(getGLPrimitiveCount(prim), 33 / 2, 'lines');
2221
t.is(getGLPrimitiveCount(indexedPrim), 3, 'lines (indexed)');
2322

2423
prim.setMode(Primitive.Mode.LINE_STRIP);
24+
indexedPrim.setMode(Primitive.Mode.LINE_STRIP);
2525
t.is(getGLPrimitiveCount(prim), 32, 'line strip');
26+
t.is(getGLPrimitiveCount(indexedPrim), 5, 'line strip (indexed)');
2627

2728
prim.setMode(Primitive.Mode.LINE_LOOP);
29+
indexedPrim.setMode(Primitive.Mode.LINE_LOOP);
2830
t.is(getGLPrimitiveCount(prim), 33, 'line loop');
31+
t.is(getGLPrimitiveCount(indexedPrim), 6, 'line loop (indexed)');
32+
33+
prim.setMode(Primitive.Mode.TRIANGLES);
34+
indexedPrim.setMode(Primitive.Mode.TRIANGLES);
35+
t.is(getGLPrimitiveCount(prim), 11, 'triangles');
36+
t.is(getGLPrimitiveCount(indexedPrim), 2, 'triangles (indexed)');
2937

3038
prim.setMode(Primitive.Mode.TRIANGLE_FAN);
39+
indexedPrim.setMode(Primitive.Mode.TRIANGLE_FAN);
3140
t.is(getGLPrimitiveCount(prim), 31, 'triangle strip');
41+
t.is(getGLPrimitiveCount(indexedPrim), 4, 'triangle strip (indexed)');
3242

3343
prim.setMode(Primitive.Mode.TRIANGLE_STRIP);
44+
indexedPrim.setMode(Primitive.Mode.TRIANGLE_STRIP);
3445
t.is(getGLPrimitiveCount(prim), 31, 'triangle fan');
46+
t.is(getGLPrimitiveCount(indexedPrim), 4, 'triangle fan (indexed)');
3547

3648
prim.setMode('TEST' as unknown as GLTF.MeshPrimitiveMode);
3749
t.throws(() => getGLPrimitiveCount(prim), { message: /mode/i }, 'invalid');

0 commit comments

Comments
 (0)