perf: speed up world viewer loading
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-05-14 11:46:16 +08:00
parent 89ec01cfb8
commit dee8019efd
6 changed files with 105 additions and 15 deletions

View File

@ -4,6 +4,8 @@ import { buildThreeAssetUrl } from "@/lib/canvas/three-asset-url";
export const runtime = "nodejs"; export const runtime = "nodejs";
export const maxDuration = 60; export const maxDuration = 60;
const THREE_ASSET_BROWSER_CACHE_CONTROL =
"private, max-age=86400, stale-while-revalidate=604800";
function isAllowedModelHost(hostname: string) { function isAllowedModelHost(hostname: string) {
const normalized = hostname.toLowerCase(); const normalized = hostname.toLowerCase();
@ -144,7 +146,13 @@ export async function GET(request: Request) {
const acceptRanges = upstream.headers.get("accept-ranges"); const acceptRanges = upstream.headers.get("accept-ranges");
if (acceptRanges) responseHeaders.set("accept-ranges", acceptRanges); if (acceptRanges) responseHeaders.set("accept-ranges", acceptRanges);
responseHeaders.set("cache-control", "private, no-store"); const etag = upstream.headers.get("etag");
if (etag) responseHeaders.set("etag", etag);
const lastModified = upstream.headers.get("last-modified");
if (lastModified) responseHeaders.set("last-modified", lastModified);
responseHeaders.set("cache-control", THREE_ASSET_BROWSER_CACHE_CONTROL);
if (shouldRewriteGltf) { if (shouldRewriteGltf) {
const rewrittenPayload = rewriteGltfPayload(await upstream.text(), targetUrl); const rewrittenPayload = rewriteGltfPayload(await upstream.text(), targetUrl);

View File

@ -91,6 +91,7 @@ import { cn, fetcher } from "@/lib/utils";
import { import {
getSortedWorldLabsSplatEntries, getSortedWorldLabsSplatEntries,
pickBestArchivedWorldLabsSplatUrl, pickBestArchivedWorldLabsSplatUrl,
pickPreferredInlineWorldLabsSplatUrl,
} from "@/lib/worldlabs/splat-utils"; } from "@/lib/worldlabs/splat-utils";
import { import {
DEFAULT_PUBLIC_THREED_MODEL_ALIAS_CONFIG, DEFAULT_PUBLIC_THREED_MODEL_ALIAS_CONFIG,
@ -2605,7 +2606,10 @@ export default function CanvasModel3DPage() {
? buildWorldShareUrl(currentTask.shareToken) ? buildWorldShareUrl(currentTask.shareToken)
: null; : null;
const currentSpzEntries = getSortedWorldLabsSplatEntries(currentWorldAsset?.spzUrls); const currentSpzEntries = getSortedWorldLabsSplatEntries(currentWorldAsset?.spzUrls);
const currentPrimarySpzUrl = currentSpzEntries[0]?.[1] || null; const currentPrimarySpzUrl =
pickPreferredInlineWorldLabsSplatUrl(currentWorldAsset?.spzUrls) ||
currentSpzEntries[0]?.[1] ||
null;
const currentWorldImageUrl = const currentWorldImageUrl =
currentWorldAsset?.panoUrl || currentWorldAsset?.panoUrl ||
currentWorldAsset?.thumbnailUrl || currentWorldAsset?.thumbnailUrl ||

View File

@ -3,7 +3,7 @@ import { notFound } from "next/navigation";
import { WorldSharePage } from "@/components/canvas/world3d/WorldSharePage"; import { WorldSharePage } from "@/components/canvas/world3d/WorldSharePage";
import { db } from "@/lib/db"; import { db } from "@/lib/db";
import { model3dAsset } from "@/lib/db/schema"; import { model3dAsset } from "@/lib/db/schema";
import { pickBestArchivedWorldLabsSplatUrl } from "@/lib/worldlabs/splat-utils"; import { pickPreferredInlineWorldLabsSplatUrl } from "@/lib/worldlabs/splat-utils";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
@ -86,7 +86,7 @@ export default async function SharedWorldPage({
previewImageUrl: asset.previewImageUrl || assetPayload?.thumbnailUrl || null, previewImageUrl: asset.previewImageUrl || assetPayload?.thumbnailUrl || null,
prompt: asset.prompt || null, prompt: asset.prompt || null,
semanticsMetadata: assetPayload?.semanticsMetadata || null, semanticsMetadata: assetPayload?.semanticsMetadata || null,
splatUrl: pickBestArchivedWorldLabsSplatUrl(assetPayload?.spzUrls), splatUrl: pickPreferredInlineWorldLabsSplatUrl(assetPayload?.spzUrls),
title: assetPayload?.displayName || asset.prompt || `World ${asset.taskId.slice(0, 8)}`, title: assetPayload?.displayName || asset.prompt || `World ${asset.taskId.slice(0, 8)}`,
worldUrl: assetPayload?.worldUrl || null, worldUrl: assetPayload?.worldUrl || null,
}; };

View File

@ -35,6 +35,7 @@ import {
import { import {
isArchivedWorldLabsAssetUrl, isArchivedWorldLabsAssetUrl,
pickBestArchivedWorldLabsSplatUrl, pickBestArchivedWorldLabsSplatUrl,
pickPreferredInlineWorldLabsSplatUrl,
} from "@/lib/worldlabs/splat-utils"; } from "@/lib/worldlabs/splat-utils";
import { getNodeContentScale } from "@/lib/canvas/node-content-scale"; import { getNodeContentScale } from "@/lib/canvas/node-content-scale";
import { import {
@ -201,9 +202,17 @@ export function World3DNode({
const activeView = useMemo(() => resolveActiveView(data), [data]); const activeView = useMemo(() => resolveActiveView(data), [data]);
const availableViews = useMemo(() => resolveAvailableViews(data), [data]); const availableViews = useMemo(() => resolveAvailableViews(data), [data]);
const renderableSplatUrl = isArchivedWorldLabsAssetUrl(data.splatUrl) const renderableSplatUrl = useMemo(() => {
? data.splatUrl const assetPayloadSplatUrl = pickPreferredInlineWorldLabsSplatUrl(
: undefined; metadataAssetPayload.spzUrls as Record<string, string> | null | undefined
);
if (isArchivedWorldLabsAssetUrl(assetPayloadSplatUrl)) {
return assetPayloadSplatUrl;
}
return isArchivedWorldLabsAssetUrl(data.splatUrl) ? data.splatUrl : undefined;
}, [data.splatUrl, metadataAssetPayload.spzUrls]);
const renderableMeshUrl = isArchivedWorldLabsAssetUrl(data.meshUrl) const renderableMeshUrl = isArchivedWorldLabsAssetUrl(data.meshUrl)
? data.meshUrl ? data.meshUrl
: undefined; : undefined;

View File

@ -166,8 +166,10 @@ function lerpSparkRevealProfile(
}; };
} }
const SPLAT_REVEAL_DURATION_MS = 2600; const SPLAT_REVEAL_DURATION_MS = 900;
const SPLAT_PARTICLE_SAMPLE_LIMIT = 1800; const SPLAT_PARTICLE_SAMPLE_LIMIT = 720;
const SPLAT_WORLD_BOUNDS_SAMPLE_LIMIT = 1200;
const SPLAT_FAST_LOAD_SKIP_REVEAL_MS = 1200;
const KEY_TO_MOVEMENT: Partial<Record<string, MovementCommand>> = { const KEY_TO_MOVEMENT: Partial<Record<string, MovementCommand>> = {
KeyW: "forward", KeyW: "forward",
@ -307,7 +309,7 @@ function getRobustSplatWorldBounds(splatMesh: SparkSplatMesh) {
} }
splatMesh.updateMatrixWorld(true); splatMesh.updateMatrixWorld(true);
const targetCount = Math.min(totalSplats, 3200); const targetCount = Math.min(totalSplats, SPLAT_WORLD_BOUNDS_SAMPLE_LIMIT);
const sampleStep = Math.max(Math.floor(totalSplats / targetCount), 1); const sampleStep = Math.max(Math.floor(totalSplats / targetCount), 1);
const point = new THREE.Vector3(); const point = new THREE.Vector3();
const xs: number[] = []; const xs: number[] = [];
@ -1063,16 +1065,16 @@ function WorldSplatScene({
onLoadStateChange?.("ready"); onLoadStateChange?.("ready");
const buildParticleCloud = () => { const buildParticleCloud = () => {
if (cancelled) return; if (cancelled || revealPhaseRef.current === "hidden") return;
setParticleCloudData(buildSplatParticleCloudData(splatMesh)); setParticleCloudData(buildSplatParticleCloudData(splatMesh));
}; };
if ("requestIdleCallback" in window) { if ("requestIdleCallback" in window) {
idleCallbackId = window.requestIdleCallback(buildParticleCloud, { idleCallbackId = window.requestIdleCallback(buildParticleCloud, {
timeout: 900, timeout: 250,
}); });
} else { } else {
timeoutId = globalThis.setTimeout(buildParticleCloud, 120); timeoutId = globalThis.setTimeout(buildParticleCloud, 160);
} }
}); });
}) })
@ -1364,15 +1366,20 @@ function WorldSplatViewer({
const [loadState, setLoadState] = useState<"loading" | "ready" | "error">("loading"); const [loadState, setLoadState] = useState<"loading" | "ready" | "error">("loading");
const [overlayPhase, setOverlayPhase] = useState<SplatLoadingPhase>("loading"); const [overlayPhase, setOverlayPhase] = useState<SplatLoadingPhase>("loading");
const resolvedSplatUrl = useMemo(() => buildThreeAssetUrl(splatUrl), [splatUrl]); const resolvedSplatUrl = useMemo(() => buildThreeAssetUrl(splatUrl), [splatUrl]);
const loadStartedAtRef = useRef(0);
useEffect(() => { useEffect(() => {
loadStartedAtRef.current = performance.now();
setLoadState("loading"); setLoadState("loading");
setOverlayPhase("loading"); setOverlayPhase("loading");
}, [resolvedSplatUrl]); }, [resolvedSplatUrl]);
useEffect(() => { useEffect(() => {
if (loadState === "ready") { if (loadState === "ready") {
setOverlayPhase("revealing"); const elapsed = performance.now() - loadStartedAtRef.current;
setOverlayPhase(
elapsed <= SPLAT_FAST_LOAD_SKIP_REVEAL_MS ? "hidden" : "revealing"
);
} }
if (loadState === "loading") { if (loadState === "loading") {
@ -1421,8 +1428,13 @@ function WorldSplatViewer({
<Canvas <Canvas
camera={{ position: [0, 1.6, 4], fov: 55 }} camera={{ position: [0, 1.6, 4], fov: 55 }}
className="h-full w-full bg-[radial-gradient(circle_at_top,_rgba(15,23,42,0.95),_rgba(1,1,1,1)_68%)]" className="h-full w-full bg-[radial-gradient(circle_at_top,_rgba(15,23,42,0.95),_rgba(1,1,1,1)_68%)]"
dpr={[1, 1.25]}
frameloop="always" frameloop="always"
gl={{ antialias: false, preserveDrawingBuffer: true }} gl={{
antialias: false,
powerPreference: "high-performance",
preserveDrawingBuffer: true,
}}
> >
<WorldSplatCaptureBridge onCaptureReady={onCaptureReady} /> <WorldSplatCaptureBridge onCaptureReady={onCaptureReady} />
<WorldSplatScene <WorldSplatScene

View File

@ -59,6 +59,40 @@ export function pickBestWorldLabsSplatUrl(
return pickBestWorldLabsSplatEntry(spzUrls)?.[1] || null; return pickBestWorldLabsSplatEntry(spzUrls)?.[1] || null;
} }
const INLINE_WORLDLABS_SPLAT_TARGET_SCORE = 1_500_000;
function pickClosestResolutionEntry(
entries: Array<[string, string]>,
targetScore: number
) {
if (!entries.length) return null;
const scoredEntries = entries
.map((entry) => ({
entry,
score: parseResolutionScore(entry[0]),
}))
.filter(
(item) =>
Number.isFinite(item.score) &&
item.score > 0 &&
item.score !== Number.MAX_SAFE_INTEGER
);
if (!scoredEntries.length) {
return entries[0] || null;
}
return scoredEntries
.slice()
.sort((left, right) => {
const distanceDelta =
Math.abs(left.score - targetScore) - Math.abs(right.score - targetScore);
if (distanceDelta !== 0) return distanceDelta;
return left.score - right.score;
})[0]?.entry || null;
}
const KNOWN_ARCHIVED_ASSET_HOSTS = [ const KNOWN_ARCHIVED_ASSET_HOSTS = [
"blob.vercel-storage.com", "blob.vercel-storage.com",
"s3.1080p.hk", "s3.1080p.hk",
@ -129,3 +163,26 @@ export function pickBestArchivedWorldLabsSplatUrl(
) { ) {
return getArchivedWorldLabsSplatEntries(spzUrls)[0]?.[1] || null; return getArchivedWorldLabsSplatEntries(spzUrls)[0]?.[1] || null;
} }
export function pickPreferredInlineWorldLabsSplatEntry(
spzUrls?: Record<string, string> | null
) {
const archivedEntries = getArchivedWorldLabsSplatEntries(spzUrls);
if (archivedEntries.length > 0) {
return pickClosestResolutionEntry(
archivedEntries,
INLINE_WORLDLABS_SPLAT_TARGET_SCORE
);
}
return pickClosestResolutionEntry(
getSortedWorldLabsSplatEntries(spzUrls),
INLINE_WORLDLABS_SPLAT_TARGET_SCORE
);
}
export function pickPreferredInlineWorldLabsSplatUrl(
spzUrls?: Record<string, string> | null
) {
return pickPreferredInlineWorldLabsSplatEntry(spzUrls)?.[1] || null;
}