Skip to content

perf(player): draw the frames into the window swapchain instead of reading them back - #224

Merged
dmtrKovalenko merged 3 commits into
mainfrom
perf/player-present-to-window
Oct 16, 2026
Merged

dmtrKovalenko merged 3 commits into
mainfrom
perf/player-present-to-window

Conversation

@dmtrKovalenko

@dmtrKovalenko dmtrKovalenko commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

The native player draws each frame into the swapchain of the window: a CAMetalLayer drawable with Metal, a VkSwapchainKHR image with Vulkan. The player no longer reads the frame back from the GPU and copies it through softbuffer, and the CPU backend draws into the softbuffer pixel buffer directly. PlayerOptions::vsync = false shows each frame without a wait for the display refresh, and the fps in the title counts the frames drawn into the window.


flowchart LR
  subgraph before
    A[Skia on the GPU] --> B[offscreen texture] --> C[read_pixels into RAM] --> D[softbuffer upload] --> E[window]
  end
  subgraph after
    F[Skia on the GPU] --> G[swapchain image of the window] --> H[window]
  end
Loading

Measured with examples/hello_world on an M4 Max, 120 Hz display, window about 3150x2194 px, 30 s per run. "Late" is the time from the moment a frame is due to the moment it is on screen.

30 fps video

Metal before Metal after Vulkan (MoltenVK) before Vulkan after
present() mean 14.30 ms 1.85 ms 16.72 ms 2.05 ms
late, p50 16.2 ms 2.9 ms 17.5 ms 2.7 ms
screen updates / s 30.1 30.1 30.4 30.1

120 fps video

Metal before Metal after Vulkan before Vulkan after
screen updates / s 63.5 119.9 64.6 119.8
skipped frames 47% 0.06% 46% 0.14%
late, p50 19.7 ms 7.6 ms 19.4 ms 6.6 ms

500 fps video, vsync: false

Metal Vulkan
frames drawn into the window / s 314 (119 with vsync) 318
best second 411 420

Before the fix the title showed 128 fps for this video with vsync on, but only 119 frames per second reached the window. It now shows 118 to 120.

Note

Metal, Vulkan and the CPU backend give the same pixel values for the same frame (the Metal layer has the sRGB color space). On Linux and Windows only the type check of the Xlib, Xcb, Wayland and Win32 surface code was done, not a run.

Note

New public API in fframes_skia_renderer: SkiaVulkanCtx::new_with_instance_extensions, the accessors entry, instance, physical_device, device, queue_family_index, create_context with VulkanContext, and SkiaMetalCtx::device, command_queue, create_context.

Important

The player still waits for the GPU after each frame. The images of video frames point into the pixel buffers of the tree, and the next frame drops the tree.

Summary by CodeRabbit

  • New Features
    • Native playback can render through CPU, Metal, or Vulkan backends, with automatic backend selection and CPU fallback.
    • Added a vsync option, enabled by default, to control synchronization with display refresh on GPU backends.
    • Expanded access to Metal and Vulkan rendering resources for applications using the renderer.
  • Bug Fixes
    • Frame statistics now count distinct frames after successful presentation, rather than when frames are taken from the scheduler.

…ading them back

The Metal backend draws into the drawables of a CAMetalLayer of the window and the Vulkan
backend into the images of a VkSwapchainKHR. The CPU backend draws into the softbuffer
pixel buffer of the window.
…is drawn

With `PlayerOptions::vsync` set to false the Metal layer does not wait for the display
and the Vulkan swapchain presents in the immediate or the mailbox mode. Vsync stays on by
default.
…e title

The counter counted the frames taken from the scheduler, and a frame can be replaced
before a redraw shows it. With vsync it showed 128 fps on a 120 Hz display.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://perf-player-present-to-window-fframes.dmitriy-86d.workers.dev (commit 35693e6)

This URL reflects your latest Preview deployment

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://643b014d-fframes.dmitriy-86d.workers.dev 35693e6 2026-10-16T02:25:08.579Z Visit the dashboard ↗

@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 7fdc50ec-7482-4967-a13e-4dacccac1812
📥 Commits

Reviewing files that changed from the base of the PR and between 44f5431 and 35693e6.

⛔ Files ignored due to path filters: 1
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing: 12
  • fframes-native-player/Cargo.toml
  • fframes-native-player/src/app.rs
  • fframes-native-player/src/error.rs
  • fframes-native-player/src/lib.rs
  • fframes-native-player/src/options.rs
  • fframes-native-player/src/presenter.rs
  • fframes-native-player/src/presenter/cpu.rs
  • fframes-native-player/src/presenter/metal.rs
  • fframes-native-player/src/presenter/mod.rs
  • fframes-native-player/src/presenter/vulkan.rs
  • fframes-skia-renderer/src/backends/metal.rs
  • fframes-skia-renderer/src/backends/vulkan.rs
💤 Files with no reviewable changes: 1
  • fframes-native-player/src/presenter.rs

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

📝 Walkthrough

The native player now presents frames through CPU, Metal, or Vulkan window targets. The change adds a vsync option, updates frame statistics to count successfully presented positions, and exposes additional Skia renderer backend resources.

Changes: Native window presentation

Layer / File(s) Summary
Renderer backend access
fframes-skia-renderer/src/backends/metal.rs, fframes-skia-renderer/src/backends/vulkan.rs
The renderer exposes Metal device and command queue accessors, and exposes Vulkan context resources and instance-extension configuration.
CPU and Metal window targets
fframes-native-player/Cargo.toml, fframes-native-player/src/presenter/cpu.rs, fframes-native-player/src/presenter/metal.rs, fframes-native-player/src/error.rs
The CPU target draws through softbuffer. The Metal target draws to AppKit CAMetalLayer drawables. The player adds optional platform dependencies and converts window-handle errors.
Vulkan swapchain presentation
fframes-native-player/Cargo.toml, fframes-native-player/src/presenter/vulkan.rs
The Vulkan target creates platform surfaces and swapchains, draws into acquired images, and handles resize, presentation results, and resource teardown.
Presenter and backend selection
fframes-native-player/src/presenter.rs, fframes-native-player/src/presenter/mod.rs
The previous presenter implementation is replaced with target-based presentation. The new presenter draws the video tree and controls, and selects an explicit target or tries Metal, Vulkan, then CPU in automatic mode.
Player options and frame statistics
fframes-native-player/src/options.rs, fframes-native-player/src/app.rs, fframes-native-player/src/lib.rs
PlayerOptions adds vsync, defaulting to true, and passes it to the presenter. Frame statistics count distinct non-empty positions after successful presentation. Crate documentation describes direct GPU rendering to window swapchains.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant App
  participant Presenter
  participant WindowTarget
  participant SkiaSurface
  App->>Presenter: present tree and controls
  Presenter->>WindowTarget: present draw callback
  WindowTarget->>SkiaSurface: draw frame
  WindowTarget-->>Presenter: presentation result
  Presenter-->>App: return result
Loading
Merge Risk: ⚪ Minimal · up to 35693

The player now draws frames directly into the window's swapchain, with vsync still on by default. The review found no concrete defects. The Linux and Windows Vulkan paths were not run, so test them before release.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 38.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 50 functions across 10 files. (1 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed 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 clearly and concisely describes the main change: rendering frames directly into the window swapchain instead of reading them back.
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.
Full details: Docstring Coverage

Explanation

Docstring coverage is 38.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 50 functions across 10 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@dmtrKovalenko
dmtrKovalenko merged commit 3f188aa into main Oct 16, 2026
14 checks passed
@dmtrKovalenko
dmtrKovalenko deleted the perf/player-present-to-window branch October 16, 2026 02:56
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