Skip to content

Add experimental WebGL mesh renderer - #14

Merged
aomona merged 3 commits into
mainfrom
feature/webgl-renderer
May 28, 2026
Merged

Add experimental WebGL mesh renderer#14
aomona merged 3 commits into
mainfrom
feature/webgl-renderer

Conversation

@aomona

@aomona aomona commented May 28, 2026

Copy link
Copy Markdown
Owner

Summary

  • add optional WebGL mesh renderer exports under grain-gradient/webgl and grain-gradient/webgl/react
  • add progressive WebGL playground renderer with CSS fallback and make animated defaults use WebGL unless reduced motion is enabled
  • make CSS motion presets transform-only to reduce paint-heavy animation
  • document WebGL usage and add coverage for transform-only motion and WebGL option resolution

Validation

  • npm run format
  • npm run lint
  • npm run typecheck
  • npm test

Notes

  • WebGL is experimental and remains optional; CSS/SVG stays as the SSR-safe static/export fallback.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added experimental WebGL-based renderer for animated mesh backgrounds with improved performance
    • Introduced WebGLGrainGradient React component for WebGL mesh gradient rendering
    • Added renderer mode selector in playground to switch between CSS and WebGL renderers
  • Documentation

    • Updated README and API documentation with WebGL experimental feature guides and React/TypeScript examples
  • Improvements

    • Optimized motion presets to use transforms only for better performance

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@aomona, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 17 minutes and 59 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d3a58a1d-ac5a-4433-b98c-57c32d40bbc2

📥 Commits

Reviewing files that changed from the base of the PR and between 21cef58 and 3d0d77f.

📒 Files selected for processing (3)
  • README.md
  • playground/index.html
  • src/webgl.ts
📝 Walkthrough

Walkthrough

This PR adds experimental WebGL-based mesh gradient rendering as an alternative to the default CSS/SVG renderer. Core changes include a new WebGL renderer implementation with React component wrapper, simplification of CSS motion keyframes to transform-only, playground integration with mode switching, and comprehensive documentation.

Changes

WebGL Renderer Feature Addition

Layer / File(s) Summary
WebGL Core Renderer Implementation
src/webgl.ts
WebGL renderer with embedded vertex/fragment shaders, color parsing, option resolution, and canvas-backed rendering. Exports createWebGLMeshRenderer(), isWebGLAvailable(), resolveWebGLMeshGradientOptions(), plus public types WebGLMeshGradientOptions, ResolvedWebGLMeshGradientOptions, and WebGLMeshRenderer interface with update/start/stop/resize/destroy methods.
WebGL React Component Wrapper
src/webgl-react.tsx
React component WebGLGrainGradient that initializes and manages WebGL renderer lifecycle on canvas, observes resizes, recovers from context loss, applies Android fallback styling, and layers SVG grain overlay. Props interface WebGLGrainGradientProps extends grain and WebGL options with React-specific fields. Re-exports WebGL types and factory functions.
CSS Motion Simplification
src/core.ts, src/react.tsx
Removes internal shiftedPosition helper and updates breathe/orbit keyframe animations to be transform-only by removing animated filter properties and background-position offsets, keeping only scale/rotate/translate transforms.
Test Coverage: Motion and WebGL Options
tests/library.test.mjs
Updates motion preset tests to assert transform-only output (breathe and orbit without filter or background-position). Adds test validating resolveWebGLMeshGradientOptions option resolution and defaults in non-browser environment.
Playground Renderer Mode Switching
playground/index.html
Adds renderer mode selector (CSS vs WebGL experimental), WebGL canvas element with visibility tied to readiness state, dynamic WebGL module import, renderer state initialization based on reduced-motion preferences, and ensureWebGLRenderer() orchestration that manages mode switching, WebGL fallback, and context-loss recovery.
Package Exports and Documentation
package.json, README.md, docs/API.md
Adds subpath exports ./webgl and ./webgl/react in package.json. Updates motion preset documentation to reflect transform-only behavior. Adds "WebGL API experimental" section to API.md documenting renderer methods, React component, options (maxPixelRatio, fps, pauseWhenHidden), and usage examples. Adds README example using WebGLGrainGradient React component.

Sequence Diagram

sequenceDiagram
  participant User
  participant Playground
  participant WebGLGrainGradient
  participant createWebGLMeshRenderer
  participant Canvas
  participant WebGLProgram
  
  User->>Playground: select WebGL renderer mode
  Playground->>WebGLGrainGradient: mount with WebGL options
  WebGLGrainGradient->>Canvas: ref.current acquired
  WebGLGrainGradient->>createWebGLMeshRenderer: create renderer with canvas
  createWebGLMeshRenderer->>WebGLProgram: compile shaders, link program
  createWebGLMeshRenderer->>WebGLProgram: set uniforms (colors, motion, resolution)
  createWebGLMeshRenderer-->>WebGLGrainGradient: return renderer
  WebGLGrainGradient->>Canvas: renderer.start()
  Canvas->>WebGLProgram: requestAnimationFrame loop (fps throttled)
  WebGLProgram->>Canvas: draw animated mesh gradient
  Canvas-->>User: WebGL canvas visible
  User->>Playground: switch back to CSS mode
  Playground->>Canvas: renderer.stop(), renderer.destroy()
  Playground->>Canvas: show CSS/SVG fallback
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

  • aomona/grain-gradient#5: Both PRs modify CSS background-position behavior in orbit keyframes; PR #5 introduces swirl-based offsets while this PR removes background-position entirely in favor of transform-only motion.
  • aomona/grain-gradient#3: This PR's motion preset simplifications (breathe and orbit keyframes) directly refactor the animated gradient motion controls introduced in PR #3.

Poem

🎨 A canvas blooms with shaders bright,
WebGL paints the mesh with light,
React wraps it, smooth and tidy,
While CSS still stands beside-y,
Transform-only, no more blur—
The gradients dance, a speedy whir! ✨

🚥 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding an experimental WebGL mesh renderer as a new feature. It is concise, specific, and directly reflects the primary objective of the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/webgl-renderer

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
README.md (2)

81-103: ⚡ Quick win

Consider documenting SSR constraints explicitly.

The phrase "CSS/SVG renderer remains the SSR-safe fallback" implies WebGL is not SSR-safe, but this constraint isn't stated explicitly. Since the React section above provides detailed SSR guidance with user-agent examples (lines 66-77), users might expect similar guidance here.

Consider adding a brief note that WebGL components require a browser environment and should be wrapped in client-only boundaries for SSR frameworks (e.g., Next.js 'use client' directive or dynamic imports with ssr: false).

📝 Example addition

After line 83, add:

For SSR frameworks, ensure the WebGL component renders client-side only:

\`\`\`tsx
// Next.js example
import dynamic from 'next/dynamic';

const WebGLGrainGradient = dynamic(
  () => import('grain-gradient/webgl/react').then(mod => mod.WebGLGrainGradient),
  { ssr: false }
);
\`\`\`
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 81 - 103, Add an explicit SSR note to the WebGL
experimental section stating that WebGL components (e.g., WebGLGrainGradient)
require a browser environment and must be rendered client-side only; update the
text after the WebGL paragraph to instruct users to wrap these components in
client-only boundaries (for example using Next.js "use client" in a client
component or dynamic imports with ssr: false) and include a concise Next.js
example referencing WebGLGrainGradient so readers know how to safely render it
in SSR frameworks.

95-95: 💤 Low value

Consider adding a brief explanation of maxPixelRatio.

The maxPixelRatio prop purpose may not be immediately clear to users. A brief inline comment or sentence explaining it controls canvas resolution (e.g., "caps canvas pixel density for performance") would improve discoverability.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 95, Add a short explanatory note next to the maxPixelRatio
prop to clarify its purpose: mention that maxPixelRatio controls the canvas
pixel density (device pixel ratio) and is used to cap rendering resolution for
performance (e.g., to avoid excessive GPU/CPU usage on high-DPI displays).
Update the README example where maxPixelRatio={1.25} appears to include a
one-line comment or parenthetical sentence describing this behavior and
recommended usage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@playground/index.html`:
- Around line 936-942: The ready message in render() unconditionally overwrites
any error/fallback set by ensureWebGLRenderer(); modify the logic so
setFeedback("ready", ...) is only called when no renderer error occurred — for
example have ensureWebGLRenderer() return a success boolean or set
state.renderer to "error" on failure, then in render() only call
setFeedback("ready", ...) when that success flag is true (or when state.renderer
!== "error") and preserve existing feedback otherwise; update references to
ensureWebGLRenderer(), render(), setFeedback(), and state.renderer accordingly.
- Around line 760-768: The webglcontextlost handler falls back UI to CSS but
doesn't update the application state, so update the shared renderer state when
handling the event: inside the "webglcontextlost" listener (the block that calls
webglRenderer?.destroy(), sets canvas.dataset.ready and
elements.preview.dataset.renderer, and calls setFeedback), also set
state.renderer = "css" (and if you have a state.ready or similar flag, set it
false) so controls reflect the CSS renderer; ensure any UI-binding code that
reads state.renderer will now show the correct selection.

In `@src/webgl.ts`:
- Around line 321-323: The update(nextOptions) method currently replaces options
wholesale; instead merge the incoming partial nextOptions into the existing
options before resolving so unspecified fields are preserved. In the update
function, merge nextOptions into the current options object (e.g., Object.assign
or spread) and then call resolveWebGLMeshGradientOptions with the merged object,
then call setStaticUniforms(); ensure you update references to the same options
variable used elsewhere so partial patches (like only swirl) do not reset
colors/motion/etc.

---

Nitpick comments:
In `@README.md`:
- Around line 81-103: Add an explicit SSR note to the WebGL experimental section
stating that WebGL components (e.g., WebGLGrainGradient) require a browser
environment and must be rendered client-side only; update the text after the
WebGL paragraph to instruct users to wrap these components in client-only
boundaries (for example using Next.js "use client" in a client component or
dynamic imports with ssr: false) and include a concise Next.js example
referencing WebGLGrainGradient so readers know how to safely render it in SSR
frameworks.
- Line 95: Add a short explanatory note next to the maxPixelRatio prop to
clarify its purpose: mention that maxPixelRatio controls the canvas pixel
density (device pixel ratio) and is used to cap rendering resolution for
performance (e.g., to avoid excessive GPU/CPU usage on high-DPI displays).
Update the README example where maxPixelRatio={1.25} appears to include a
one-line comment or parenthetical sentence describing this behavior and
recommended usage.
🪄 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: d1118c9d-d07c-432b-a176-eb15d484e9fa

📥 Commits

Reviewing files that changed from the base of the PR and between 4879a8b and 21cef58.

📒 Files selected for processing (9)
  • README.md
  • docs/API.md
  • package.json
  • playground/index.html
  • src/core.ts
  • src/react.tsx
  • src/webgl-react.tsx
  • src/webgl.ts
  • tests/library.test.mjs

Comment thread playground/index.html Outdated
Comment thread playground/index.html Outdated
Comment thread src/webgl.ts
@aomona
aomona merged commit 2635689 into main May 28, 2026
2 checks passed
@aomona
aomona deleted the feature/webgl-renderer branch July 8, 2026 09:46
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.

1 participant