Skip to content

fix: resolve green MP4 exports on CachyOS/Arch Linux (Wayland)#330

Merged
siddharthvaddem merged 1 commit intosiddharthvaddem:mainfrom
maxbailey:main
Apr 8, 2026
Merged

fix: resolve green MP4 exports on CachyOS/Arch Linux (Wayland)#330
siddharthvaddem merged 1 commit intosiddharthvaddem:mainfrom
maxbailey:main

Conversation

@maxbailey
Copy link
Copy Markdown
Contributor

@maxbailey maxbailey commented Apr 5, 2026

Description

On Linux/Wayland (specifically CachyOS/Arch with KDE), exported MP4 videos are solid green despite audio working correctly. This fix uses explicit gl.readPixels() to copy pixel data from the WebGL framebuffer to CPU memory, bypassing the broken shared-image path entirely.

Motivation

PR #281 resolved green MP4 exports on Ubuntu by reading raw pixels at the VideoFrame construction step. However, on CachyOS/Arch Linux with Wayland/KDE, the corruption occurs one layer deeper in FrameRenderer.compositeWithShadows(), where ctx.drawImage(webglCanvas, ...) copies the PixiJS WebGL canvas onto the 2D composite canvas. By the time the VideoFrame fix reads pixels, the composite canvas already contains corrupted data.

This PR fixes the compositing layer by reading back the WebGL framebuffer directly via gl.readPixels(), which always performs an explicit GPU-to-CPU copy.

Type of Change

  • New Feature
  • Bug Fix
  • Refactor / Code Cleanup
  • Documentation Update
  • Other (please specify)

Related Issue(s)

#264

Checklist

  • I have performed a self-review of my code.
  • I have added any necessary screenshots or videos.
  • I have linked related issue(s) and updated the changelog if applicable.

Summary by CodeRabbit

  • Bug Fixes
    • Improved rendering stability and cross-platform compatibility for shadow compositing operations.

On Linux/Wayland the implicit GPU-to-2D texture-sharing path used by
drawImage(webglCanvas) fails silently (EGL/Ozone), producing green
frames. Use explicit gl.readPixels to copy from GPU to CPU memory,
bypassing that path.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 5, 2026

📝 Walkthrough

Walkthrough

FrameRenderer adds GPU-to-CPU pixel readback functionality. An offscreen raster canvas and 2D context are initialized and cleaned up in destroy(). A new private readbackVideoCanvas() method reads WebGL pixels via gl.readPixels, applies vertical flipping, and writes the buffer to the raster canvas. compositeWithShadows() is modified to use this readback mechanism instead of directly accessing the WebGL canvas.

Changes

Cohort / File(s) Summary
GPU Pixel Readback
src/lib/exporter/frameRenderer.ts
Added raster canvas initialization and cleanup; introduced readbackVideoCanvas() private method for GPU→CPU pixel transfer with buffer flipping; updated compositeWithShadows() to use readback path instead of direct canvas access.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • siddharthvaddem

Poem

🐰 A canvas twice now takes the stage,
GPU whispers to the page,
Pixels flip in rhythm true,
From WebGL's glow to raster's view—
The renderer's dance, renewed! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically identifies the fix as resolving green MP4 exports on CachyOS/Arch Linux with Wayland, which matches the main objective of the changeset.
Description check ✅ Passed The description covers all required sections: purpose, motivation, type of change (Bug Fix selected), related issues (#264 linked), and relevant checklist items completed.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3b5ad5064e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Copy link
Copy Markdown

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/exporter/frameRenderer.ts`:
- Around line 704-719: The current path in compositeWithShadows()
unconditionally calls readbackVideoCanvas() which always does a gl.readPixels()
into a freshly allocated Uint8Array and temp buffer then flips rows and
putImageData, causing synchronous stalls and per-frame allocations; change this
so the default path uses the existing drawImage() branch and only uses the
readbackVideoCanvas() code when a platform/workaround flag (detect affected
Wayland/Linux cases) is set, and when readback is enabled reuse buffers across
frames by promoting buf and the row temp buffer to reusable properties (cache a
Uint8Array for pixel data and a row-sized temp Uint8Array) instead of allocating
them inside readbackVideoCanvas(), and avoid repeated ImageData allocations by
reusing a single ImageData / Uint8ClampedArray view where possible before
calling this.rasterCtx.putImageData; keep references to compositeWithShadows()
and readbackVideoCanvas() to locate the change.
🪄 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: 7e8c45f0-1a34-4572-83fa-b2fb6c7b55f8

📥 Commits

Reviewing files that changed from the base of the PR and between 21893f0 and 3b5ad50.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (1)
  • src/lib/exporter/frameRenderer.ts

Copy link
Copy Markdown
Owner

@siddharthvaddem siddharthvaddem left a comment

Choose a reason for hiding this comment

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

🙏

@siddharthvaddem siddharthvaddem merged commit 5583797 into siddharthvaddem:main Apr 8, 2026
5 checks passed
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.

2 participants