Skip to content

Render libmpv video through Avalonia's OpenGL control - #8

Closed
amaztony wants to merge 3 commits into
Bitpainter75:mainfrom
amaztony:fix/macos-video-playback
Closed

Render libmpv video through Avalonia's OpenGL control#8
amaztony wants to merge 3 commits into
Bitpainter75:mainfrom
amaztony:fix/macos-video-playback

Conversation

@amaztony

@amaztony amaztony commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

  • render libmpv frames through Avalonia's OpenGL control instead of a platform-native child window
  • serialize libmpv commands away from the UI and render callbacks
  • keep one Avalonia video control across fullscreen transitions and video navigation, while reattaching libmpv if Avalonia recreates the OpenGL context
  • synchronize playback state immediately and keep video controls readable over the frame
  • hide image-only histogram content while viewing video
  • discover system-installed libmpv in conventional Homebrew, Intel Homebrew, and MacPorts locations on macOS

Motivation

The previous native child-window approach used platform window handles and did not compose reliably with Avalonia layout and overlays on macOS. In failure cases, the embedded surface could remain black while mpv opened a separate window.

This change uses the libmpv Render API with an Avalonia OpenGL control. Avalonia owns the framebuffer, clipping, controls, and fullscreen layout, while libmpv commands are kept away from the UI thread and OpenGL render callbacks. The same rendering and composition path is now used on Windows, Linux, and macOS.

macOS also has a separate library-discovery issue. A Homebrew-installed libmpv normally lives under /opt/homebrew, which is not necessarily searched by the .NET native-library resolver when FerrumPix is launched as an application bundle. The resolver now checks the default library names first, then conventional Homebrew, Intel Homebrew, and MacPorts library directories before retaining the existing application-local fallback.

Dependency policy

This PR does not bundle libmpv or codecs and does not add package references or packaging changes. It preserves the existing system-first policy. On macOS, libmpv must still be installed separately, for example with:

brew install mpv

Validation

  • user-level testing completed on a Mac mini with an M4 chip and Homebrew mpv
  • video frames render inside the FerrumPix window without a separate mpv window
  • playback, pause, resume, seeking, fullscreen entry and exit, returning to the gallery, and application exit were verified
  • Windows and Linux now use the same Avalonia OpenGL composition path; build and runtime validation on both platforms is still required before cross-platform behavior can be considered verified

amaztony added 2 commits July 31, 2026 12:21
Replace native child-window embedding with libmpv's OpenGL render API so video frames participate in Avalonia layout, clipping, overlays, and fullscreen transitions on macOS. Serialize media commands away from the UI and render threads, preserve playback state across view changes, and hide image-only histogram content for videos.
@amaztony
amaztony force-pushed the fix/macos-video-playback branch from 7612ea2 to 0f9363a Compare July 31, 2026 06:57
@amaztony amaztony changed the title Render libmpv video inside Avalonia Render libmpv video through Avalonia's OpenGL control Jul 31, 2026
@amaztony
amaztony marked this pull request as ready for review July 31, 2026 09:02
@Bitpainter75

Copy link
Copy Markdown
Owner

Thanks for this, and thanks for the detailed write-up. Moving from --wid and
NativeControlHost to the libmpv render API is the right direction. The frame now
lives inside Avalonia's tree, clipping and overlays behave, and fullscreen only moves
the grid instead of reparenting a native window. The interop declarations look correct
to me (struct layouts, alignment, calling conventions), the macOS library discovery via
Homebrew and MacPorts is a good addition, and the EndFileReason.Stop filter in
OnVideoEndReached fixes a real race. I built the branch locally: 0 errors, 0 warnings.

Before I can merge this, a few things need to change.

1. Lock contention in the render path (blocking)

RenderOpenGlFrame and IsDisposeRequested take _syncRoot just to read a field. The
new command thread holds that same lock while making blocking libmpv calls
(mpv_set_property_string("time-pos"), loadfile). If the mpv core is waiting on a
render pass while the render thread is waiting on the lock, both stall. This is exactly
the hazard the mpv render API docs warn about. On an M4 with a local file it will never
show up, but it will under load or during scrubbing. The render path must not touch that
lock: please move _renderContext behind its own narrow lock or read it with
Volatile.Read.

2. panscan=1.0 crops the video (blocking)

The comment says this matches the image behavior, but it does not. MainImage in
ViewerView.axaml:222 uses Stretch="Uniform", meaning fit with bars. panscan=1.0 is
the opposite: it fills the surface and cuts off content. A 16:9 video in a 4:3 frame
visibly loses picture on both sides. Silently cropping is the wrong default for a photo
application. Please set this to 0.

3. Dispose can never complete (blocking)

While a render context is attached, actual teardown is deferred to DetachOpenGl, which
only runs if another render pass or OnOpenGlDeinit happens. If the control stays in the
tree but is hidden, or the window is minimized, neither occurs, and the mpv handle, the
render context and the command thread stay alive. The same applies to Player = Nothing,
which no longer detaches directly but waits for a frame that may never come. There needs
to be a teardown path that does not depend on a render pass.

4. Comment language

This repository keeps identifiers in English and comments in German. The PR rewrites
existing German doc comments in MpvInterop.vb, ViewerView.axaml and
ViewerView.axaml.vb into English and adds all new comments in English. Please restore
the existing German comments and write the new ones in German as well.

5. Please split out the histogram change

The histogram visibility work (ShowHistogram across three view models plus
InfoSidebarView) is unrelated to video rendering and is not mentioned in the PR
description. The change itself is correct and I am happy to take it, just in its own PR.
Note that EditorViewModel.ShowHistogram is a constant True there, so it is only a
placeholder to satisfy the shared sidebar binding.

Smaller items, worth folding in

  • MpvOpenGlInitParams has two fields here. libmpv up to 0.35 still has a third one
    (extra_exts), so an older system library reads past the block sized by
    Marshal.SizeOf. Allocating and zeroing a few extra bytes costs nothing.
  • Two AllocHGlobal plus FreeHGlobal per frame is unnecessary at 60 fps. Preallocated
    buffers would do.
  • The return value of mpv_render_context_update is discarded, so a frame is rendered
    even when no new one is available.
  • Program.vb sets AvaloniaNativePlatformOptions to exactly the default value
    ({OpenGl, Software}), so it is a no-op and can be dropped. More importantly: if
    Avalonia does fall back to Software, there is no OpenGL context, the video stays black,
    and the "playback unavailable" notice does not appear because libmpv is present. That
    case needs handling.
  • mpv_render_context_report_swap is documented as only meaningful with
    ADVANCED_CONTROL, which is not set here.

Validation

Since this replaces the render path on all three platforms, macOS alone is not enough. The
two things most likely to break elsewhere are flip_y and Windows with ANGLE, where mpv
runs on GLES rather than desktop GL. I will test Linux on my machine. If you can cover
Windows, that would leave us with all three.

Happy to discuss any of the above if you see it differently.

@amaztony

amaztony commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

I’m closing this PR for now.

After testing the implementation, I realized that the remaining questions—particularly end-of-file frame behavior and HDR/color-management policy—are not isolated rendering fixes. They affect FerrumPix’s broader platform and architecture direction and need project-level decisions first.

Continuing to add changes without that direction could increase the maintenance burden and make future review harder. I would rather pause this work, follow the project’s current development direction, and revisit a similar contribution when the relevant architecture and platform policies are clearer.

Thank you for the careful review and for the useful feedback.

@amaztony amaztony closed this Aug 1, 2026
@amaztony

amaztony commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for pointing out the histogram change in your review. I checked the current main branch and confirmed that the histogram visibility and loading behavior has already been fixed there: videos no longer show or trigger histogram loading, while images and the editor retain their normal histogram behavior.

Since this is already covered by your mainline changes, I will not pursue a separate histogram PR. Thanks for taking care of it.

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