fix: bind performance evidence to production resources
This commit is contained in:
@@ -24,6 +24,31 @@ export interface SceneResourceSnapshot {
|
||||
rafCount: 0 | 1;
|
||||
}
|
||||
|
||||
export interface GpuTextureAllocation {
|
||||
id: string;
|
||||
width: number;
|
||||
height: number;
|
||||
bytesPerPixel: number;
|
||||
layers: number;
|
||||
mipmapped: boolean;
|
||||
}
|
||||
|
||||
export interface GpuRenderTargetAllocation {
|
||||
id: string;
|
||||
width: number;
|
||||
height: number;
|
||||
bytesPerPixel: number;
|
||||
attachments: number;
|
||||
samples: number;
|
||||
}
|
||||
|
||||
export interface SceneGpuBudgetSnapshot {
|
||||
tier: 'low' | 'high';
|
||||
textures: readonly GpuTextureAllocation[];
|
||||
geometryBytes: number;
|
||||
renderTargets: readonly GpuRenderTargetAllocation[];
|
||||
}
|
||||
|
||||
const resourceKinds: readonly SceneResourceKind[] = [
|
||||
'geometry', 'material', 'texture', 'target', 'pass', 'renderer', 'other',
|
||||
];
|
||||
@@ -127,4 +152,67 @@ export class SceneResourceBag {
|
||||
rafCount: this.raf ? 1 : 0,
|
||||
};
|
||||
}
|
||||
|
||||
gpuBudgetSnapshot(input: {
|
||||
tier: 'low' | 'high';
|
||||
defaultFramebuffer: Omit<GpuRenderTargetAllocation, 'id'>;
|
||||
}): SceneGpuBudgetSnapshot {
|
||||
let geometryBytes = 0;
|
||||
const seenBuffers = new Set<ArrayBufferLike>();
|
||||
const textures: GpuTextureAllocation[] = [];
|
||||
const renderTargets: GpuRenderTargetAllocation[] = [{ id: 'default-framebuffer', ...input.defaultFramebuffer }];
|
||||
let textureIndex = 0;
|
||||
let targetIndex = 0;
|
||||
for (const [resource, kind] of this.resources) {
|
||||
if (kind === 'geometry') {
|
||||
const geometry = resource as DisposableResource & {
|
||||
attributes?: Record<string, { array?: ArrayBufferView; data?: { array?: ArrayBufferView } }>;
|
||||
index?: { array?: ArrayBufferView } | null;
|
||||
};
|
||||
const arrays = [
|
||||
...Object.values(geometry.attributes ?? {}).map((attribute) => attribute.array ?? attribute.data?.array),
|
||||
geometry.index?.array,
|
||||
].filter((array): array is ArrayBufferView => array !== undefined);
|
||||
for (const array of arrays) {
|
||||
if (seenBuffers.has(array.buffer)) continue;
|
||||
seenBuffers.add(array.buffer);
|
||||
geometryBytes += array.byteLength;
|
||||
}
|
||||
}
|
||||
if (kind === 'texture') {
|
||||
const texture = resource as DisposableResource & {
|
||||
image?: { width?: number; height?: number; data?: ArrayBufferView };
|
||||
generateMipmaps?: boolean;
|
||||
};
|
||||
const width = Number(texture.image?.width);
|
||||
const height = Number(texture.image?.height);
|
||||
if (!Number.isInteger(width) || width <= 0 || !Number.isInteger(height) || height <= 0) {
|
||||
throw new Error('Owned production texture is missing measurable dimensions.');
|
||||
}
|
||||
const dataBytes = texture.image?.data?.byteLength;
|
||||
const bytesPerPixel = dataBytes === undefined ? 4 : dataBytes / (width * height);
|
||||
if (!Number.isInteger(bytesPerPixel) || bytesPerPixel <= 0) throw new Error('Owned production texture has an unsupported pixel layout.');
|
||||
textures.push({ id: `owned-texture-${textureIndex}`, width, height, bytesPerPixel, layers: 1, mipmapped: texture.generateMipmaps !== false });
|
||||
textureIndex += 1;
|
||||
}
|
||||
if (kind === 'target') {
|
||||
const target = resource as DisposableResource & { width?: number; height?: number; samples?: number; depthBuffer?: boolean };
|
||||
const width = Number(target.width);
|
||||
const height = Number(target.height);
|
||||
if (!Number.isInteger(width) || width <= 0 || !Number.isInteger(height) || height <= 0) {
|
||||
throw new Error('Owned production render target is missing measurable dimensions.');
|
||||
}
|
||||
renderTargets.push({
|
||||
id: `owned-render-target-${targetIndex}`,
|
||||
width,
|
||||
height,
|
||||
bytesPerPixel: 4,
|
||||
attachments: target.depthBuffer === false ? 1 : 2,
|
||||
samples: Math.max(1, Number(target.samples) || 1),
|
||||
});
|
||||
targetIndex += 1;
|
||||
}
|
||||
}
|
||||
return { tier: input.tier, textures, geometryBytes, renderTargets };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,6 +136,18 @@ export async function createProceduralSceneGeneration(
|
||||
Math.max(1, Math.floor(width * quality.pixelRatio)),
|
||||
Math.max(1, Math.floor(height * quality.pixelRatio)),
|
||||
);
|
||||
if (TEST_BUILD && context.quality !== 'medium') {
|
||||
window.__PORTFOLIO_PERFORMANCE__?.recordRuntimeAllocation(context.resources.gpuBudgetSnapshot({
|
||||
tier: context.quality,
|
||||
defaultFramebuffer: {
|
||||
width: Math.max(1, canvas.width),
|
||||
height: Math.max(1, canvas.height),
|
||||
bytesPerPixel: 4,
|
||||
attachments: 2,
|
||||
samples: context.quality === 'low' ? 1 : 4,
|
||||
},
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
if (context.signal.aborted) {
|
||||
|
||||
Reference in New Issue
Block a user