Repository navigation
Open images up to the GPU's texture limit and reject larger ones by size #92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import type { Gpu } from "vgpu"; | ||
|
|
||
| const count = new Intl.NumberFormat("en-US"); | ||
|
|
||
| /** Rejects an image larger than the device's textures hold. */ | ||
| export function checkSize(gpu: Gpu, width: number, height: number) { | ||
| const limit = gpu.gpu.limits.maxTextureDimension2D; | ||
| if (Math.max(width, height) <= limit) { | ||
| return; | ||
| } | ||
| throw new Error( | ||
| `This device opens images up to ${count.format(limit)} pixels per side; this one is ${count.format(width)} × ${count.format(height)}.`, | ||
| ); | ||
| } | ||
|
|
||
| /** The size the header declares; a header it can't read is the decoder's to report. */ | ||
| async function declaredSize(file: Blob) { | ||
| const { imageSize } = await import("image-size"); | ||
| const bytes = new Uint8Array(await file.arrayBuffer()); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Large images exhaust memory before rejection For a large file, Learn moreThe size check runs before decoding, but Example: An illustrative uncompressed 10,000 × 10,000 RGBA TIFF is roughly 400 MB. Checking its dimensions first copies roughly 400 MB into a buffer, even though its TIFF directory can identify its dimensions without loading the full image; the tab can fail before reporting Recommended fix: Parse dimensions from bounded Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| try { | ||
| return imageSize(bytes); | ||
| } catch { | ||
| return undefined; | ||
| } | ||
| } | ||
|
|
||
| /** Checks the size its header declares, without decoding a pixel. */ | ||
| export async function checkHeader(gpu: Gpu, file: Blob) { | ||
| const size = await declaredSize(file); | ||
| if (size) { | ||
| checkSize(gpu, size.width, size.height); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,13 +45,23 @@ class GpuBoundary extends Component< | |
| } | ||
| } | ||
|
|
||
| /** The adapter's own texture limit rather than WebGPU's default 8,192, so images open as large as the GPU holds. */ | ||
| async function gpuOptions() { | ||
| const adapter = await navigator.gpu?.requestAdapter(); | ||
| if (!adapter) { | ||
| return undefined; | ||
| } | ||
| const { maxTextureDimension2D } = adapter.limits; | ||
| return { requiredLimits: { maxTextureDimension2D } }; | ||
| } | ||
|
|
||
| const root = document.getElementById("root"); | ||
|
|
||
| if (root) { | ||
| createRoot(root).render( | ||
| <> | ||
| <GpuBoundary> | ||
| <GpuProvider> | ||
| <GpuProvider options={await gpuOptions()}> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 GPU adapter failures blank the page When Learn moreThe root render is suspended on a promise before React has mounted. The GPU fallback exists inside Example: On a browser where Recommended fix: Move adapter acquisition into the provider or a component covered by Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| <TooltipProvider delay={500}> | ||
| <App /> | ||
| </TooltipProvider> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Rejected bitmaps remain open after decoding
When
checkSizerejects a decoded SVG bitmap,pixelFormatskips uploadDecoded, which closes decoded bitmaps. Repeated failed opens retain large bitmap resources until garbage collection.Learn more
pixelFormathandles bothImageBitmapoutputs from SVG/native decoders and video frames from HEIC. It checks dimensions after decoding but before uploadDecoded, which normally closes those resources. When the dimension check throws, no code closes the bitmap or frames. Repeated oversized SVG opens thus leave their decoded bitmap resources pending garbage collection.Example: Opening an SVG that rasterizes to 16,385 × 10 pixels on a 16,384-pixel GPU transfers it to an ImageBitmap, then rejects it without calling
ImageBitmap.close().Recommended fix: Ensure the dimension check runs before decoding where possible; for paths checked after decoding, close an
ImageBitmapor allVideoFrametiles on any validation failure.Was this helpful? React with 👍 or 👎 to provide feedback.