[codex] optimize texture uploads before storage - #49
Conversation
There was a problem hiding this comment.
Code Review
This pull request implements client-side texture optimization, increasing the maximum supported upload size to 16MB by downscaling images to a maximum dimension of 2048px and compressing them before storage. The changes include updates to the MaterialEditor component, new utility functions for image processing in textureUpload.ts, and expanded unit and E2E tests. Review feedback suggests optimizing memory usage by lazily generating original data URLs and replacing the manual base64 conversion logic with the native FileReader API.
| } | ||
|
|
||
| export async function prepareTextureUpload(file: File): Promise<PreparedTextureUpload> { | ||
| const originalDataUrl = await blobToDataUrl(file); |
There was a problem hiding this comment.
The originalDataUrl is generated eagerly for every upload. For large textures (up to 16MB), this creates a ~21MB string in memory before optimization even begins. Since optimization is likely to be used for these large files, it would be more memory-efficient to generate the original data URL lazily only if optimization fails or if the optimized version is rejected by storage limits. You can compare binary sizes (blob.size vs file.size) inside optimizeTextureFile to determine if the compressed version is smaller without needing the full original data URL string.
| async function blobToDataUrl(blob: Blob): Promise<string> { | ||
| const buffer = await blob.arrayBuffer(); | ||
| const base64 = arrayBufferToBase64(buffer); | ||
| return `data:${blob.type || 'application/octet-stream'};base64,${base64}`; | ||
| } | ||
|
|
||
| function arrayBufferToBase64(buffer: ArrayBuffer): string { | ||
| const maybeBuffer = ( | ||
| globalThis as typeof globalThis & { | ||
| Buffer?: { from: (input: ArrayBuffer) => { toString: (encoding: 'base64') => string } }; | ||
| } | ||
| ).Buffer; | ||
| if (maybeBuffer) return maybeBuffer.from(buffer).toString('base64'); | ||
|
|
||
| const bytes = new Uint8Array(buffer); | ||
| const chunkSize = 0x8000; | ||
| let binary = ''; | ||
| for (let offset = 0; offset < bytes.length; offset += chunkSize) { | ||
| const chunk = bytes.subarray(offset, offset + chunkSize); | ||
| for (let index = 0; index < chunk.length; index += 1) { | ||
| binary += String.fromCharCode(chunk[index]); | ||
| } | ||
| } | ||
| return btoa(binary); | ||
| } |
There was a problem hiding this comment.
Using FileReader.readAsDataURL is a more efficient and idiomatic way to generate data URLs in the browser than manually converting an ArrayBuffer to base64. This approach leverages native browser implementation and avoids the overhead of manual chunking and string concatenation. Since this file already depends on browser-only APIs like canvas and createImageBitmap, FileReader is fully supported and allows you to remove the arrayBufferToBase64 helper entirely.
async function blobToDataUrl(blob: Blob): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result as string);
reader.onerror = () => reject(reader.error);
reader.readAsDataURL(blob);
});
}
Summary
Validation
npm test -- --run src/components/editor/textureUpload.test.tsnpm run test:cinpm run lintnpm run type-checknpx playwright test tests/e2e/smoke.spec.ts -g "texture"npm run buildnpm run check:bundlenpm run check-formatgit diff --checkBrowser QA
http://127.0.0.1:4173/in the Codex in-app browser.