Skip to content

check with the hardware GPU on macOS: every overlap sample of a data-color-grading image holds a full-size GPU buffer until the pass ends (6 GB for one 19 s image) #7259

Description

@aacruzgon

Describe the bug

On an Apple Silicon Mac with the hardware GPU (--browser-gpu, which auto-detect also picks), the Chrome GPU process grows during hyperframes check by about one decoded copy of each graded image on screen for every sampled time in the check: overlap pass. The memory is IOAccelerator (graphics) in footprint, and it's freed only when the pass ends. So the peak is roughly overlap samples × graded images on screen × decoded image size.

  • A single graded 4000×3000 image for 19 s: the GPU process passes 6 GB about 12 s into the check.
  • Our 2-minute film with 16 graded photos: it reached 10 GB in 12 s once the overlap pass began, and was killed. In an earlier run, with other work also running on the machine, it reached 32 GB.

On an 18 GB machine that runs into swap. In one run, the HyperFrames desktop app, open at the same time, showed "Graphics turned off for this session: Your graphics driver stopped responding".

With --no-browser-gpu, or with the data-color-grading attribute removed, memory stays flat.

Minimal reproduction

One <img> with data-color-grading is enough. The index.html below is a 19 s, 1920×1080 composition with a single 4000×3000 JPEG, graded with {"preset":null,"intensity":0.7,"adjust":{"contrast":0.05}}. The JPEG is an FFmpeg test pattern: ffmpeg -f lavfi -i testsrc2=s=4000x3000 -frames:v 1 -q:v 3 plate.jpg. It has none of the following:

  • animation (the timeline is a single tl.set({}, {}, 19));
  • nested compositions or motion blur;
  • CSS filters or will-change.

With --browser-gpu, the Chrome GPU process stays at 0.4 GB through check: craft. During check: overlap it grows linearly by about 42 MB per sampled time (one 4000×3000 RGBA frame is 48 MB). It passes 6 GB at overlap 18/19, about 12 s in: two runs measured 6.25 and 6.39 GB, nearly all IOAccelerator (graphics). We stopped both at 6 GB, so the full peak wasn't measured.

Run (one check, nothing else running) GPU process peak
repro, --browser-gpu (×2) 6.25 / 6.39 GB (stopped at 6 GB)
repro, --no-browser-gpu (×2) 0.31 GB
repro, data-color-grading removed, --browser-gpu 0.30 GB
our 2-minute film, --browser-gpu 10.2 GB at overlap 6/23 (stopped)
our film, --no-browser-gpu 0.90 GB
our film, grades removed, --browser-gpu 1.12 GB, check done in 14 s
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=1920, height=1080" />
    <title>check GPU memory repro</title>
    <!-- One 4000x3000 <img> with data-color-grading, no animation, 19 s.
         plate.jpg: ffmpeg -f lavfi -i testsrc2=s=4000x3000 -frames:v 1 -q:v 3 plate.jpg
         Remove data-color-grading and the GPU process stays under 0.3 GB. -->
    <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
    <style>
      html, body { margin: 0; background: #000; }
      #root { position: relative; width: 1920px; height: 1080px; overflow: hidden; }
      .clip { position: absolute; inset: 0; }
      #plate { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
    </style>
  </head>
  <body>
    <div id="root" data-composition-id="main" data-start="0" data-duration="19"
         data-width="1920" data-height="1080" data-fps="30">
      <div class="clip" id="shot" data-start="0" data-duration="19" data-track-index="0">
        <img id="plate" src="plate.jpg" alt=""
             data-color-grading='{"preset":null,"intensity":0.7,"adjust":{"contrast":0.05}}'>
      </div>
    </div>
    <script>
      window.__timelines = window.__timelines || {};
      const tl = gsap.timeline({ paused: true });
      tl.set({}, {}, 19);
      window.__timelines["main"] = tl;
    </script>
  </body>
</html>

Steps to reproduce

  1. On an Apple Silicon Mac, put the index.html above in an empty folder, and make plate.jpg there with the ffmpeg command above.
  2. There, run CI=1 npx hyperframes@0.9.16 check --browser-gpu --json > out.json 2>&1. Auto-detect also picks hardware on this machine. CI=1 only keeps the warm checker out of the measurement.
  3. While it runs, sample footprint -p <pid> of the chrome-headless-shell --type=gpu-process child every 0.5 s, or watch it in Activity Monitor. It passes 6 GB about 12 s in, during the check: overlap n/19 progress lines.
  4. Run it again with --no-browser-gpu, or with the data-color-grading attribute removed. The GPU process stays near 0.3 GB.

Expected behavior

GPU memory stays roughly flat across overlap samples. Each sample's graded texture is released, or reused, before the next sample, as it is with the software renderer.

Actual behavior

Every overlap sample adds about one full-size decoded copy of each graded image on screen. None is freed until the pass ends.

Environment

hyperframes doctor

  ✓ Version          0.9.16 (latest)
  ✓ Node.js          v26.12.0 (darwin arm64)
  ✓ CPU              11 cores · Apple M3 Pro @ 2400MHz
  ✓ Memory           18.0 GB total
  ✓ FFmpeg           ffmpeg 9.0.2 at /opt/homebrew/bin/ffmpeg
  ✓ Chrome           cache: ~/.cache/hyperframes/chrome/chrome-headless-shell/mac_arm-152.0.7977.30/chrome-headless-shell-mac-arm64/chrome-headless-shell
  ✓ Docker           Docker version 29.8.2 (not running)

macOS 27.2 (26B123).

Additional context

Reproduced with a single check and nothing else running. The grades also make check slow: the same film checks in 14 s with them removed. Our workaround is --no-browser-gpu for check.

Activity

  1. added a commit that references this issue on Nov 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions