Skip to content

@moq/watch: add pull mode to video renderer#1367

Open
skirsten wants to merge 1 commit intomoq-dev:mainfrom
skirsten:feat/pull-renderer
Open

@moq/watch: add pull mode to video renderer#1367
skirsten wants to merge 1 commit intomoq-dev:mainfrom
skirsten:feat/pull-renderer

Conversation

@skirsten
Copy link
Copy Markdown
Contributor

@skirsten skirsten commented May 1, 2026

For me (on Chrome with 144hz+ monitors) the existing renderer for some reason causes chrome to render the page at 120fps. I checked and the logic is not drawing at that rate so nothing wrong with it.

Instead, if the requestAnimationFrame is done inside another one it apparently does different schedule logic and actually syncs correctly. So the new pull mode causes it to render at the actually video refresh rate...

push pull
image image

Let me know what you think and if you are seeing something similar.
Visually they both should be the same so if everything checks out we can also drop the push mode if you want.

Adds a mode: "push" | "pull" prop on Renderer:

  • "push" (default, existing behavior): the render effect subscribes to decoder.frame and schedules a single rAF paint per decoded frame.
  • "pull": runs a self-recursive rAF loop and redraws only when the decoder's current frame differs from what was last drawn.

MultiBackend's WebCodecs path now constructs the renderer with mode: "pull" so playback runs off the display's vsync rather than the decoder's frame cadence.

Adds a `mode: "push" | "pull"` prop on Renderer:

- "push" (default, existing behavior): the render effect subscribes to
  decoder.frame and schedules a single rAF paint per decoded frame.
- "pull": runs a self-recursive rAF loop and redraws only when the
  decoder's current frame differs from what was last drawn.

MultiBackend's WebCodecs path now constructs the renderer with
`mode: "pull"` so playback runs off the display's vsync rather than the
decoder's frame cadence.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 1, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ddc832dd-fe19-453c-b5fe-9811c97f90e5

📥 Commits

Reviewing files that changed from the base of the PR and between f0a842a and f8d33e5.

📒 Files selected for processing (2)
  • js/watch/src/backend.ts
  • js/watch/src/video/renderer.ts

Walkthrough

The changes introduce a configurable rendering strategy to the WebCodecs rendering pipeline. A new RendererMode type is exported with two possible values: "push" and "pull". The RendererProps interface is updated to accept an optional mode configuration. The rendering implementation is refactored to support both modes: "push" mode draws on each animation frame and reads decoder frames when not paused, while "pull" mode continuously loops but only redraws when the decoder frame reference changes. The previous monolithic render method is replaced with a shared draw method. The backend is updated to instantiate the renderer with mode: "pull".

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding a pull mode option to the video renderer in the @moq/watch package.
Description check ✅ Passed The description provides detailed context about the motivation (Chrome rendering issue), the implementation approach (pull vs push modes), and the specific behavior differences between modes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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
✨ Simplify code
  • Create PR with simplified code

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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

rafId = requestAnimationFrame(tick);
};

rafId = requestAnimationFrame(tick);
Copy link
Copy Markdown
Collaborator

@kixelated kixelated May 1, 2026

Choose a reason for hiding this comment

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

I'm not sure if this is worth the compute cost of busy looping requestAnimationFrame if there's no work to be done. Is 120fps versus 144fps that important?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

its 120fps vs the actual video fps (24) so the GPU is actually less busy with the change.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

wait what? how does that work?

With push we should schedule 24 rAF callbacks, while pull should schedule refresh rate rAF callbacks? Are you sure those screenshots are correct?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Pretty sure, but let me do some actual power testing on my phone or laptop...

I think it has to do with v-sync, but yea, it doesn't really make sense :)

Copy link
Copy Markdown
Contributor Author

@skirsten skirsten May 1, 2026

Choose a reason for hiding this comment

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

Okay, I did some more testing and the screenshots are real and there is definitely different magic on chrome's side between them.

But there is actually very little difference in the power draw. I remember this being a real issue in the past though, but that might have been fixed in a chrome or driver update.

Also it seems to depend on the display refresh rate a lot as on a 120 fps display I was getting very different numbers for both.

Anyway, I don't think this is actually needed so we can close this or continue but keep the current push as default?

Btw, both of these run into a issue on 60hz display with 60hz content: It's actually rendering at ~45 fps or so because some frames get unlucky and are being scheduled too late and get overwritten by the next one.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yeah, it might be a good idea to schedule the next RAF within the loop, but don't schedule again if the frame didn't change.

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