Skip to content

fix: prevent crash when GPU is unavailable#772

Open
Bishtrahulsingh wants to merge 1 commit intoOpenCut-app:mainfrom
Bishtrahulsingh:fix/gpu-degraded-mode-compositor
Open

fix: prevent crash when GPU is unavailable#772
Bishtrahulsingh wants to merge 1 commit intoOpenCut-app:mainfrom
Bishtrahulsingh:fix/gpu-degraded-mode-compositor

Conversation

@Bishtrahulsingh
Copy link
Copy Markdown

@Bishtrahulsingh Bishtrahulsingh commented Apr 17, 2026

Fix: prevent "Failed to Load Project" when GPU is unavailable

Fixes #771

The editor fails to load projects across all browsers and platforms, showing the error "Failed to Load Project".

This happens when GPU initialization fails (for example, in environments without WebGPU support). Even in this state, the compositor continues to call WASM functions that depend on a valid GPU runtime, which results in runtime errors and breaks project loading.

Added guards to ensure compositor methods are only executed when GPU is available.

Changes include:

  • Added GPU availability checks before calling WASM compositor methods

  • Updated:

    • ensureInitialized
    • syncTextures
    • render
    • getCanvas
  • Enabled safe fallback behavior when GPU is not available

Result

  • Project loads successfully even without GPU support
  • No runtime crashes from WASM compositor
  • Editor remains usable in degraded mode

Testing

Tested in environments without WebGPU support:

  • Opened the editor
  • Created a new project
  • Loaded an existing project

All scenarios worked without triggering the error.
Screenshot from 2026-04-17 22-05-15

Summary by CodeRabbit

  • Bug Fixes

    • Improved canvas rendering system resilience with enhanced error handling
    • Strengthened rendering stability across different hardware configurations
  • Chores

    • Added development dependency for improved package management

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 17, 2026

@Bishtrahulsingh is attempting to deploy a commit to the OpenCut OSS Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 17, 2026

📝 Walkthrough

Walkthrough

These changes add GPU availability checks to the compositor's canvas operations, update canvas rendering methods to handle null values, and enforce memory alignment for the LayerUniformBuffer struct by adding explicit padding fields in both Rust and shader code. A development dependency (patch-package) is also introduced.

Changes

Cohort / File(s) Summary
Dependency Management
apps/web/package.json
Added patch-package (^8.0.1) to devDependencies.
Canvas Rendering & Type Safety
apps/web/src/services/renderer/canvas-renderer.ts
Updated getOutputCanvas() return type to HTMLCanvasElement | null and added null checks in renderToCanvas() before calling ctx.drawImage(). Reformatted indentation throughout.
WASM Compositor GPU Checks
apps/web/src/services/renderer/compositor/wasm-compositor.ts
Added early exits in ensureInitialized(), getCanvas(), syncTextures(), and render() to skip WASM calls when GPU is unavailable. Updated getCanvas() to return HTMLCanvasElement | null instead of throwing when GPU is unavailable.
Rust Compositor Memory Alignment
rust/crates/compositor/src/compositor.rs, rust/crates/compositor/src/shaders/layer.wgsl
Added explicit _padding: [f32; 2] field to LayerUniformBuffer struct with compile-time assertion ensuring 16-byte alignment. Added corresponding _padding: vec2f field to LayerUniforms struct in WGSL shader.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related issues

  • #768: The padding additions to LayerUniformBuffer and the shader's LayerUniforms struct with compile-time size assertion directly address WebGPU buffer alignment validation requirements.

Poem

🐰 A compositor hops with grace so fine,
With padded buffers, aligned at size divine,
When GPU's not found, we gracefully decline,
Canvas checks null, and the renderer will shine!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main objective: preventing crashes when GPU is unavailable, which aligns with the primary changes in preventing WASM compositor calls without GPU.
Description check ✅ Passed The PR description fully addresses the bug fix requirements from the template: issue #771 is referenced, fix details are provided, testing is documented, and a screenshot demonstrates the working state.
Linked Issues check ✅ Passed The PR successfully addresses issue #771 by adding GPU availability checks to prevent WASM calls when GPU is unavailable, enabling degraded-mode operation and reliable project loading as required.
Out of Scope Changes check ✅ Passed All changes are within scope: GPU availability guards [wasm-compositor.ts], return type updates [canvas-renderer.ts], and Rust struct alignment padding [compositor.rs, layer.wgsl] support the GPU degradation objective.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/package.json (1)

6-19: ⚠️ Potential issue | 🟡 Minor

Add postinstall script to enable patch-package functionality.

patch-package only works when invoked via a postinstall script. Without it, any patches in a patches/ directory will be ignored on install. Since no patches/ directory or patched dependencies are visible in this PR, confirm this dependency is actually needed or remove it.

Proposed addition
   "scripts": {
     "dev": "next dev --turbopack",
+    "postinstall": "patch-package",
     "build": "next build",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/package.json` around lines 6 - 19, The package.json currently
includes patch-package as a dependency but lacks a postinstall script to apply
patches; add a "postinstall" entry under "scripts" that runs "patch-package" (or
remove the patch-package dependency if no patches exist) so that patches in the
patches/ directory are applied after install; look for the "scripts" object near
existing entries like "dev" and "build" to insert the "postinstall" script or
remove the dependency if it's unused.
🧹 Nitpick comments (2)
rust/crates/compositor/src/compositor.rs (1)

60-66: LGTM — explicit padding + compile-time alignment check is solid.

The _padding: [f32; 2] correctly mirrors the WGSL _padding: vec2f and the const _: () = assert!(...) guarantees any future field addition that breaks 16-byte alignment will fail at compile time rather than silently producing GPU layout bugs at runtime. Consider applying the same const _ alignment assert to BlendUniformBuffer and MaskUniformBuffer (lines 68–80) for consistency — purely optional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/crates/compositor/src/compositor.rs` around lines 60 - 66, Add the same
compile-time 16-byte alignment assertion used for LayerUniformBuffer to the
other uniform structs so future changes can't break GPU layout: locate the
BlendUniformBuffer and MaskUniformBuffer declarations and add a const _: () =
assert!(std::mem::size_of::<BlendUniformBuffer>() % 16 == 0, "BlendUniformBuffer
must be 16-byte aligned for cross-device wgpu compatibility") and similarly for
MaskUniformBuffer to enforce the same alignment check.
apps/web/src/services/renderer/canvas-renderer.ts (1)

71-83: Silent no-op render() in degraded mode — consider signalling state to UI.

When GPU is unavailable, wasmCompositor.ensureInitialized/syncTextures/render all early-exit, so render() resolves successfully without producing any frame. The preview canvas will just stay blank with no indication to the user about why. Consider exposing a degraded-mode flag (e.g. via isGpuAvailable()) the editor can surface as a banner/toast, so users understand why the preview is empty rather than thinking the editor is broken.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/services/renderer/canvas-renderer.ts` around lines 71 - 83, The
render method can silently no-op when the GPU is unavailable; detect that by
checking the compositor's initialization/availability (use
wasmCompositor.ensureInitialized's return or add/use
wasmCompositor.isGpuAvailable()), then set or expose a flag on the renderer
(e.g. this.degradedMode or add an isGpuAvailable() accessor on the class) and
update render to flip that flag (or emit a 'degraded' event) before returning so
the UI/editor can show a banner/toast; change references inside render where you
call wasmCompositor.ensureInitialized, wasmCompositor.syncTextures, and
wasmCompositor.render to early-check availability and mark the state
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/services/renderer/canvas-renderer.ts`:
- Around line 43-49: The export path currently assumes getOutputCanvas() returns
a non-null HTMLCanvasElement; add a null check before calling new
CanvasSource(...) in the export method where CanvasSource is instantiated (the
code that calls getOutputCanvas()). If getOutputCanvas() returns null (GPU
unavailable via wasmCompositor.getCanvas/getOutputCanvas), either return null
with a clear user-facing message like "Export failed: GPU unavailable — export
requires GPU" or throw a descriptive Error immediately to fail fast; update the
code that calls new CanvasSource(...) to handle the chosen behavior.

---

Outside diff comments:
In `@apps/web/package.json`:
- Around line 6-19: The package.json currently includes patch-package as a
dependency but lacks a postinstall script to apply patches; add a "postinstall"
entry under "scripts" that runs "patch-package" (or remove the patch-package
dependency if no patches exist) so that patches in the patches/ directory are
applied after install; look for the "scripts" object near existing entries like
"dev" and "build" to insert the "postinstall" script or remove the dependency if
it's unused.

---

Nitpick comments:
In `@apps/web/src/services/renderer/canvas-renderer.ts`:
- Around line 71-83: The render method can silently no-op when the GPU is
unavailable; detect that by checking the compositor's
initialization/availability (use wasmCompositor.ensureInitialized's return or
add/use wasmCompositor.isGpuAvailable()), then set or expose a flag on the
renderer (e.g. this.degradedMode or add an isGpuAvailable() accessor on the
class) and update render to flip that flag (or emit a 'degraded' event) before
returning so the UI/editor can show a banner/toast; change references inside
render where you call wasmCompositor.ensureInitialized,
wasmCompositor.syncTextures, and wasmCompositor.render to early-check
availability and mark the state accordingly.

In `@rust/crates/compositor/src/compositor.rs`:
- Around line 60-66: Add the same compile-time 16-byte alignment assertion used
for LayerUniformBuffer to the other uniform structs so future changes can't
break GPU layout: locate the BlendUniformBuffer and MaskUniformBuffer
declarations and add a const _: () =
assert!(std::mem::size_of::<BlendUniformBuffer>() % 16 == 0, "BlendUniformBuffer
must be 16-byte aligned for cross-device wgpu compatibility") and similarly for
MaskUniformBuffer to enforce the same alignment check.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a3ce8a41-2ee2-43d7-acbf-f2d3b5f3b9d8

📥 Commits

Reviewing files that changed from the base of the PR and between c46d288 and f6da909.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • apps/web/package.json
  • apps/web/src/services/renderer/canvas-renderer.ts
  • apps/web/src/services/renderer/compositor/wasm-compositor.ts
  • rust/crates/compositor/src/compositor.rs
  • rust/crates/compositor/src/shaders/layer.wgsl

Comment on lines +43 to +49
getOutputCanvas(): HTMLCanvasElement | null {
wasmCompositor.ensureInitialized({
width: this.width,
height: this.height,
});
return wasmCompositor.getCanvas();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP '\bgetOutputCanvas\s*\(' -C3

Repository: OpenCut-app/OpenCut

Length of output: 1266


🏁 Script executed:

#!/bin/bash
# Check the full context around scene-exporter.ts line 100
cat -n apps/web/src/services/renderer/scene-exporter.ts | sed -n '85,115p'

Repository: OpenCut-app/OpenCut

Length of output: 1386


🏁 Script executed:

#!/bin/bash
# Find CanvasSource type definition and constructor signature
rg -n "class CanvasSource|interface CanvasSource|type CanvasSource" -A5

Repository: OpenCut-app/OpenCut

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Check the full implementation of getOutputCanvas
cat -n apps/web/src/services/renderer/canvas-renderer.ts | sed -n '38,55p'

Repository: OpenCut-app/OpenCut

Length of output: 837


🏁 Script executed:

#!/bin/bash
# Check wasmCompositor.getCanvas() return type
rg -n "getCanvas\s*\(" -B2 -A3 apps/web/src/services/renderer/

Repository: OpenCut-app/OpenCut

Length of output: 1680


🏁 Script executed:

#!/bin/bash
# Find CanvasSource definition - could be imported from a library or defined locally
rg "CanvasSource" -B2 -A8 apps/web/src/services/renderer/scene-exporter.ts | head -30

Repository: OpenCut-app/OpenCut

Length of output: 663


🏁 Script executed:

#!/bin/bash
# Search for CanvasSource import/definition across the codebase
rg "class CanvasSource|from.*CanvasSource" -B1 -A1

Repository: OpenCut-app/OpenCut

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Check mediabunny version and any type definitions
cat -n package.json | grep -i mediabunny -B2 -A2

Repository: OpenCut-app/OpenCut

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Search for all uses of getOutputCanvas in the codebase
rg "getOutputCanvas" -B3 -A3

Repository: OpenCut-app/OpenCut

Length of output: 1220


🏁 Script executed:

#!/bin/bash
# Check if there's a try-catch around the export that might handle this
cat -n apps/web/src/services/renderer/scene-exporter.ts | sed -n '75,120p'

Repository: OpenCut-app/OpenCut

Length of output: 1746


Handle null return from getOutputCanvas() in export path.

getOutputCanvas() returns HTMLCanvasElement | null and can return null when GPU is unavailable (see wasm-compositor.ts:74-75). However, scene-exporter.ts:100 passes the result directly to new CanvasSource(...) without a null check. When GPU is unavailable, this will fail during export without a user-friendly error message.

Add a null check in the export method before instantiating CanvasSource, and either:

  1. Return null with a user-friendly error (export requires GPU), or
  2. Throw a descriptive error immediately for faster failure with clear messaging
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/services/renderer/canvas-renderer.ts` around lines 43 - 49, The
export path currently assumes getOutputCanvas() returns a non-null
HTMLCanvasElement; add a null check before calling new CanvasSource(...) in the
export method where CanvasSource is instantiated (the code that calls
getOutputCanvas()). If getOutputCanvas() returns null (GPU unavailable via
wasmCompositor.getCanvas/getOutputCanvas), either return null with a clear
user-facing message like "Export failed: GPU unavailable — export requires GPU"
or throw a descriptive Error immediately to fail fast; update the code that
calls new CanvasSource(...) to handle the chosen behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Failed to load project

1 participant