feat: add benchmark settings panel
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 1m59s

This commit is contained in:
2024-05-01 23:05:04 +02:00
parent 8bf2958e1d
commit d9afec5bf6
39 changed files with 1253 additions and 741 deletions

View File

@@ -1,11 +1,6 @@
<script lang="ts">
import { T } from "@threlte/core";
import {
MeshLineGeometry,
MeshLineMaterial,
Text,
useTexture,
} from "@threlte/extras";
import { T, useThrelte } from "@threlte/core";
import { MeshLineGeometry, MeshLineMaterial, Text } from "@threlte/extras";
import {
type Group,
type BufferGeometry,
@@ -16,15 +11,20 @@
import { AppSettings } from "../settings/app-settings";
import Camera from "./Camera.svelte";
const d = useThrelte();
export const invalidate = d.invalidate;
export let geometries: BufferGeometry[];
export let lines: Vector3[][];
export let scene;
let geos: Group;
$: scene = geos;
export let geoGroup: Group;
export let centerCamera: boolean = true;
let center = new Vector3(0, 4, 0);
const matcap = useTexture("/matcap_green.jpg");
function getPosition(geo: BufferGeometry, i: number) {
return [
geo.attributes.position.array[i],
@@ -48,9 +48,6 @@
<T.GridHelper args={[20, 20]} />
{/if}
<T.DirectionalLight position={[0, 10, 10]} />
<T.AmbientLight intensity={2} />
<T.Group bind:ref={geos}>
{#each geometries as geo}
{#if $AppSettings.showIndices}
@@ -67,15 +64,9 @@
<T.PointsMaterial size={0.25} />
</T.Points>
{/if}
{#await matcap then value}
<T.Mesh geometry={geo}>
<T.MeshMatcapMaterial
matcap={value}
wireframe={$AppSettings.wireframe}
/>
</T.Mesh>
{/await}
{/each}
<T.Group bind:ref={geoGroup}></T.Group>
</T.Group>
{#if $AppSettings.showStemLines && lines}

View File

@@ -1,155 +1,23 @@
<script lang="ts">
import { Canvas } from "@threlte/core";
import Scene from "./Scene.svelte";
import {
BufferAttribute,
BufferGeometry,
Float32BufferAttribute,
Vector3,
} from "three";
import { decodeFloat, fastHashArrayBuffer } from "@nodes/utils";
import { BufferGeometry, Group, Vector3 } from "three";
import { updateGeometries } from "./updateGeometries";
import { decodeFloat, splitNestedArray } from "@nodes/utils";
import type { PerformanceStore } from "$lib/performance";
import { AppSettings } from "$lib/settings/app-settings";
export let centerCamera: boolean = true;
export let perf: PerformanceStore;
export let scene: Group;
let geoGroup: Group;
let geometries: BufferGeometry[] = [];
let lines: Vector3[][] = [];
function fastArrayHash(arr: ArrayBuffer) {
let ints = new Uint8Array(arr);
const sampleDistance = Math.max(Math.floor(ints.length / 100), 1);
const sampleCount = Math.floor(ints.length / sampleDistance);
let hash = new Uint8Array(sampleCount);
for (let i = 0; i < sampleCount; i++) {
const index = i * sampleDistance;
hash[i] = ints[index];
}
return fastHashArrayBuffer(hash.buffer);
}
function createGeometryFromEncodedData(
encodedData: Int32Array,
geometry = new BufferGeometry(),
): BufferGeometry {
// Extract data from the encoded array
let index = 1;
// const geometryType = encodedData[index++];
const vertexCount = encodedData[index++];
const faceCount = encodedData[index++];
// Indices
const indicesEnd = index + faceCount * 3;
const indices = encodedData.subarray(index, indicesEnd);
index = indicesEnd;
// Vertices
const vertices = new Float32Array(
encodedData.buffer,
index * 4,
vertexCount * 3,
);
index = index + vertexCount * 3;
let hash = fastArrayHash(vertices);
let posAttribute = geometry.getAttribute(
"position",
) as BufferAttribute | null;
if (geometry.userData?.hash === hash) {
return geometry;
}
if (posAttribute && posAttribute.count === vertexCount) {
posAttribute.set(vertices, 0);
posAttribute.needsUpdate = true;
} else {
geometry.setAttribute(
"position",
new Float32BufferAttribute(vertices, 3),
);
}
const normals = new Float32Array(
encodedData.buffer,
index * 4,
vertexCount * 3,
);
index = index + vertexCount * 3;
if (
geometry.userData?.faceCount !== faceCount ||
geometry.userData?.vertexCount !== vertexCount
) {
// Add data to geometry
geometry.setIndex([...indices]);
}
const normalsAttribute = geometry.getAttribute(
"normal",
) as BufferAttribute | null;
if (normalsAttribute && normalsAttribute.count === vertexCount) {
normalsAttribute.set(normals, 0);
normalsAttribute.needsUpdate = true;
} else {
geometry.setAttribute("normal", new Float32BufferAttribute(normals, 3));
}
geometry.userData = {
vertexCount,
faceCount,
hash,
};
return geometry;
}
function parse_args(input: Int32Array) {
let index = 0;
const length = input.length;
let res: Int32Array[] = [];
let nextBracketIndex = 0;
let argStartIndex = 0;
let depth = -1;
while (index < length) {
const value = input[index];
if (index === nextBracketIndex) {
nextBracketIndex = index + input[index + 1] + 1;
if (value === 0) {
depth++;
} else {
depth--;
}
if (depth === 1 && value === 0) {
// if opening bracket
argStartIndex = index + 2;
}
if (depth === 0 && value === 1) {
// if closing bracket
res.push(input.slice(argStartIndex, index));
argStartIndex = index + 2;
}
index = nextBracketIndex;
continue;
}
// we should not be here
index++;
}
return res;
}
let invalidate: () => void;
function createLineGeometryFromEncodedData(encodedData: Int32Array) {
const positions: Vector3[] = [];
@@ -166,16 +34,13 @@
return positions;
}
export let result: Int32Array;
$: result && updateGeometries();
function updateGeometries() {
let a = performance.now();
const inputs = parse_args(result);
let b = performance.now();
perf?.addPoint("split-result", b - a);
export const update = function update(result: Int32Array) {
perf?.addPoint("split-result");
const inputs = splitNestedArray(result);
perf?.endPoint();
if ($AppSettings.showStemLines) {
a = performance.now();
perf?.addPoint("create-lines");
lines = inputs
.map((input) => {
if (input[0] === 0) {
@@ -183,31 +48,27 @@
}
})
.filter(Boolean) as Vector3[][];
b = performance.now();
perf?.addPoint("create-lines", b - a);
perf.endPoint();
}
let totalVertices = 0;
let totalFaces = 0;
perf?.addPoint("update-geometries");
const { totalVertices, totalFaces } = updateGeometries(inputs, geoGroup);
perf?.endPoint();
a = performance.now();
geometries = inputs
.map((input, i) => {
if (input[0] === 1) {
let geo = createGeometryFromEncodedData(input);
totalVertices += geo.userData.vertexCount;
totalFaces += geo.userData.faceCount;
return geo;
}
})
.filter(Boolean) as BufferGeometry[];
b = performance.now();
perf?.addPoint("create-geometries", b - a);
perf?.addPoint("total-vertices", totalVertices);
perf?.addPoint("total-faces", totalFaces);
}
invalidate();
};
</script>
<Canvas>
<Scene {geometries} {lines} {centerCamera} />
<Scene
bind:scene
bind:geoGroup
bind:invalidate
{geometries}
{lines}
{centerCamera}
/>
</Canvas>

View File

@@ -0,0 +1,146 @@
import { fastHashArrayBuffer } from "@nodes/utils";
import { BufferAttribute, BufferGeometry, Float32BufferAttribute, Mesh, MeshMatcapMaterial, TextureLoader, type Group } from "three";
function fastArrayHash(arr: ArrayBuffer) {
let ints = new Uint8Array(arr);
const sampleDistance = Math.max(Math.floor(ints.length / 100), 1);
const sampleCount = Math.floor(ints.length / sampleDistance);
let hash = new Uint8Array(sampleCount);
for (let i = 0; i < sampleCount; i++) {
const index = i * sampleDistance;
hash[i] = ints[index];
}
return fastHashArrayBuffer(hash.buffer);
}
const loader = new TextureLoader();
const matcap = loader.load('/matcap_green.jpg');
matcap.colorSpace = "srgb";
const material = new MeshMatcapMaterial({
color: 0xffffff,
matcap
});
function createGeometryFromEncodedData(
encodedData: Int32Array,
geometry = new BufferGeometry(),
): BufferGeometry {
// Extract data from the encoded array
let index = 1;
// const geometryType = encodedData[index++];
const vertexCount = encodedData[index++];
const faceCount = encodedData[index++];
let hash = fastArrayHash(encodedData);
if (geometry.userData?.hash === hash) {
return geometry;
}
// Indices
const indicesEnd = index + faceCount * 3;
const indices = encodedData.subarray(index, indicesEnd);
index = indicesEnd;
// Vertices
const vertices = new Float32Array(
encodedData.buffer,
index * 4,
vertexCount * 3,
);
index = index + vertexCount * 3;
let posAttribute = geometry.getAttribute(
"position",
) as BufferAttribute | null;
if (posAttribute && posAttribute.count === vertexCount) {
posAttribute.set(vertices, 0);
posAttribute.needsUpdate = true;
} else {
geometry.setAttribute(
"position",
new Float32BufferAttribute(vertices, 3),
);
}
const normals = new Float32Array(
encodedData.buffer,
index * 4,
vertexCount * 3,
);
index = index + vertexCount * 3;
if (
geometry.userData?.faceCount !== faceCount ||
geometry.userData?.vertexCount !== vertexCount
) {
// Add data to geometry
geometry.setIndex([...indices]);
}
const normalsAttribute = geometry.getAttribute(
"normal",
) as BufferAttribute | null;
if (normalsAttribute && normalsAttribute.count === vertexCount) {
normalsAttribute.set(normals, 0);
normalsAttribute.needsUpdate = true;
} else {
geometry.setAttribute("normal", new Float32BufferAttribute(normals, 3));
}
geometry.userData = {
vertexCount,
faceCount,
hash,
};
return geometry;
}
let meshes: Mesh[] = [];
export function updateGeometries(inputs: Int32Array[], group: Group) {
let totalVertices = 0;
let totalFaces = 0;
let newGeometries = [];
for (let i = 0; i < Math.max(meshes.length, inputs.length); i++) {
let existingMesh = meshes[i];
let input = inputs[i];
if (input) {
if (input[0] !== 1) {
continue
}
totalVertices += input[1];
totalFaces += input[2];
} else {
if (existingMesh) {
existingMesh.visible = false;
}
continue;
}
if (existingMesh) {
createGeometryFromEncodedData(input, existingMesh.geometry);
} else {
let geo = createGeometryFromEncodedData(input);
const mesh = new Mesh(geo, material);
meshes[i] = mesh;
newGeometries.push(mesh);
}
}
for (let i = 0; i < newGeometries.length; i++) {
group.add(newGeometries[i]);
}
return { totalFaces, totalVertices };
}