Skip to content

High GPU usage from static BoxShadowFilter instances in Pixi render loop #2

Description

@necatikcl

Summary

BoxShadowFilter appears to create very high GPU usage when applied to static display objects in a continuously-rendered PixiJS v8 scene. Even when the filter instances and scene are stable, the GPU cost remains high every frame.

This came up in a real app using pixi-box-shadow@1.2.0 with PixiJS v8. The scene is visually static except for a few connector animations, but the app intentionally keeps the Pixi ticker/render loop running to measure realistic runtime behavior.

Environment

  • Hardware: MacBook Pro M2 Max
  • Browser: Chrome via local Vite dev server
  • PixiJS: pixi.js@8.18.1
  • pixi-box-shadow: pixi-box-shadow@1.2.0
  • App renderer: WebGL (preference: "webgl")
  • Canvas size: about 641 x 561 logical px, using window.devicePixelRatio

Observed GPU Usage

Measured via macOS Activity Monitor GPU usage:

  • Normal scene with box-shadow filters: ~80% GPU
  • Disable only texture-mode icon shadows: ~30% GPU
  • Disable all BoxShadowFilter usage: ~10% GPU

So the texture-mode shadows are the largest cost, but the non-texture box/card/accent shadows still add a large amount of GPU work.

Filters in the Scene

The app uses only BoxShadowFilter; no other Pixi filters are present.

  1. Card shadow on icon-box card fill:
new BoxShadowFilter({
  boxShadow:
    "0 12px 24px rgba(0, 0, 0, 0.04), 0 6px 12px rgba(0, 0, 0, 0.02), 0 3px 6px rgba(0, 0, 0, 0.01)",
  borderRadius: 8,
});
  1. Accent bar glow using shapeMode: "box":
new BoxShadowFilter({
  shapeMode: "box",
  borderRadius: 0,
  shadows: [
    { offsetX: 0, offsetY: 4, blur: 12, spread: 0, color: fillRgb, alpha: 0.32, inset: false },
    { offsetX: 0, offsetY: 2, blur: 4, spread: 0, color: fillRgb, alpha: 0.12, inset: false },
    { offsetX: 0, offsetY: 1, blur: 1, spread: 0, color: fillRgb, alpha: 0.16, inset: false },
    { offsetX: 0, offsetY: 0.5, blur: 1, spread: 0, color: fillRgb, alpha: 0.12, inset: false },
  ],
});
  1. Icon drop shadow using shapeMode: "texture":
new BoxShadowFilter({
  shapeMode: "texture",
  quality: 4,
  shadows: [
    { offsetX: 0, offsetY: 0.5, blur: 1, spread: 0, color: iconColorHex, alpha: 0.12, inset: false },
    { offsetX: 0, offsetY: 1, blur: 2, spread: 0, color: iconColorHex, alpha: 0.12, inset: false },
  ],
});

The app caches/reuses filter objects by shadow parameters, but GPU usage does not improve meaningfully, which suggests the expensive part is the per-frame offscreen/filter/composite work rather than JS allocation.

Reproduction Steps

  1. Create a PixiJS v8 app with autoStart: true, WebGL renderer, and a continuously-running ticker/render loop.
  2. Add multiple static display objects with BoxShadowFilter applied, including several texture-mode filters.
  3. Let the scene sit idle; do not mutate the filtered display objects.
  4. Watch GPU usage in Activity Monitor.
  5. Toggle filters off and compare GPU usage.

In the real app:

  • Normal: ~80% GPU
  • Texture-mode shadows disabled: ~30% GPU
  • All box-shadow filters disabled: ~10% GPU

Expected Behavior

Static shadows should be much cheaper in a continuously-rendered Pixi scene. Ideally the library could avoid re-running expensive shadow work every frame when the target object and shadow parameters have not changed.

Potential directions:

  • Internal cache/render-texture path for static filter output.
  • A documented/static mode that bakes the shadow into a texture until invalidated.
  • Better defaults or optimizations for shapeMode: "texture".
  • Guidance around filterArea, padding, resolution, or Pixi cache APIs if user-land configuration can fix this.

Why This Matters

For canvas/editor apps, keeping the ticker/render loop alive can be the correct runtime model. The current cost makes static box shadows too expensive in that setup, especially on high-end Apple Silicon where ~75-80% GPU usage for a mostly static small canvas is surprising.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions