Video on Linux went through a Flutter texture: 8-bit sRGB, which cannot carry HDR at all, and which forced a whole-window Flutter recomposite for every video frame. This moves it onto a wl_subsurface stacked below the Flutter surface, with mpv rendering into an EGL window surface on it through the libmpv render API. The subsurface is desynchronized, so video and UI now present independently. With the plane in place HDR follows: the surface is described to the compositor through wp_color_manager_v1 as the source's own curve and gamut - PQ or HLG, BT.2020 - carrying whatever HDR10 static metadata the stream actually declares. The description and the buffer it describes land on the same commit, staged and validated before mpv is switched, so a PQ frame is never presented labelled sRGB. A five-second watchdog bounds the one wait a compositor could otherwise leave hanging. A session that cannot host the plane - X11, or a compositor without wl_subcompositor - fails initialize with VIDEO_PLANE_UNSUPPORTED naming the reason: the texture path is gone, and refusing by name beats degrading to something the user cannot see. An SDR output, a missing capability or an 8-bit config keep the plane and simply leave it undescribed. The output's colour state is trusted only when it has been earned. Every landed property step records itself as it lands; a reset or sequence that cannot finish downgrades its result to unknown and marks the applied-output cache untrusted until a clean apply earns it back. A plane whose output state cannot be named is quarantined - hidden, its description withdrawn - and the quarantine is recorded state: an unrelated visibility change cannot put a mislabelled plane back on screen, and only a commit that resolves to a nameable outcome lifts it. A rect collapsing to zero detaches the buffer exactly as hiding does, a refused setVideoRect drops the Dart-side sent-rect cache so the next layout pass retries for free, and a refused tone-mapping pick tells the user instead of dying in a log. NVIDIA's Wayland EGL (through at least 610.xx) offers no 10-bit unorm window configs, so the plane takes half-float as the tier between 10-bit unorm and 8-bit, declares the whole surface opaque so the compositor never reads the alpha those configs carry, and states GL_RGBA16F rather than a 10-bit lie. Whether the output is in HDR is read from luminance headroom above its own reference white rather than from the preferred transfer function, which current KWin no longer answers PQ for; the margin is half a stop, because KWin reports an undimmed maximum over a software-dimmed SDR white. Validated on an RTX 4090 (driver 610.57.04) under KWin 6.7.4 with locked-exposure photographs. Who tone-maps is a user choice. The default is the compositor: photographed on a 400-nit HDR output against a PQ chart it keeps 400 -> 1000 nits monotonic and separated where the player leg flattens them, because the player path drives mpv's legacy vo_gpu, whose own standalone output scores the same. The gap is the renderer, not the wiring. The decision itself - what the source carries, what the output supports, what to tell mpv and what to tell the compositor - lives in hdr_metadata.h, free of Wayland and GTK so its luminance validation can be tested without a display server. Sending an incoherent luminance set is a protocol error that disconnects the client, so the rules are worth a unit test. The deb, rpm and pacman packages now declare wayland-client, wayland-egl and EGL: the plane links them directly and bundle-libs.sh deliberately never bundles them, since they are coupled to the running compositor and GPU driver. lib/dev/harness_main.dart is a second entrypoint for measuring this on hardware - it drives one clip with scripted mpv properties and reports the colour state mpv actually settled on. Nothing imports it, so it is tree-shaken out of the app. Verified on a Steam Deck against an external 400-nit HDR display: the compositor reports PQ / BT.2020, the connector carries HDR_OUTPUT_METADATA, and against mpv vo=gpu-next on the same frame the shipped build sits 4.90 counts away overall - closer to the reference HDR player than to its own SDR fallback.
410 lines
21 KiB
C++
410 lines
21 KiB
C++
#ifndef PLEZY_LINUX_MPV_WAYLAND_VIDEO_SURFACE_H_
|
|
#define PLEZY_LINUX_MPV_WAYLAND_VIDEO_SURFACE_H_
|
|
|
|
#include <EGL/egl.h>
|
|
#include <gtk/gtk.h>
|
|
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <string>
|
|
|
|
#include "hdr_metadata.h"
|
|
|
|
struct wl_callback;
|
|
struct wl_compositor;
|
|
struct wl_display;
|
|
struct wl_egl_window;
|
|
struct wl_subcompositor;
|
|
struct wl_subsurface;
|
|
struct wl_surface;
|
|
struct wp_color_management_surface_v1;
|
|
struct wp_color_management_surface_feedback_v1;
|
|
struct wp_color_manager_v1;
|
|
struct wp_image_description_v1;
|
|
struct wp_image_description_info_v1;
|
|
|
|
namespace mpv {
|
|
|
|
// The compositor's preferred colour encoding for a surface, as delivered by
|
|
// wp_image_description_info_v1 in response to get_information.
|
|
//
|
|
// Only the fields that matter for video are kept. The important one is
|
|
// max_luminance: it is the output's *target* peak (KWin sources it from an HDR
|
|
// peak override, else the EDID's desired maximum, else 800 nits), which is the
|
|
// number a player needs if it is going to tone-map for the display itself.
|
|
// Nothing else in the protocol reveals it — PQ's own encoded maximum is always
|
|
// 10000 regardless of the panel.
|
|
//
|
|
// pq, bt2020 and min_luminance_scaled drive no decision: they are kept so
|
|
// CommitPreferredQuery can tell a real change from a repeat, and so the logged
|
|
// description is complete.
|
|
struct PreferredColorDescription {
|
|
bool valid = false; // a complete info burst has arrived
|
|
bool pq = false; // transfer function is ST2084 PQ
|
|
bool bt2020 = false; // container primaries are BT.2020
|
|
uint32_t max_luminance = 0; // nits, the output's target peak
|
|
uint32_t min_luminance_scaled = 0; // nits * 10000, the output's target floor
|
|
uint32_t reference_luminance = 0; // nits, diffuse/SDR white
|
|
};
|
|
|
|
// A native Wayland video plane: a wl_subsurface stacked *below* the Flutter
|
|
// toplevel surface, carrying its own EGL window surface that mpv renders into
|
|
// directly.
|
|
//
|
|
// This is the Linux analogue of the Windows video child HWND. Flutter's own
|
|
// surface keeps the UI and alpha-blends over this one, so video never travels
|
|
// through Flutter's compositor — which on GTK3 costs one CPU-side upload of the
|
|
// whole window surface per presented frame (see gdk_cairo_draw_from_gl's
|
|
// alpha path), previously paid once per *video* frame.
|
|
//
|
|
// Everything here runs on the GTK main thread. The subsurface is desynchronized
|
|
// so its commits are independent of the parent's frame loop; position and
|
|
// stacking, however, are *parent* state and only take effect on a parent
|
|
// commit, which is why SetRect() asks the view to redraw.
|
|
//
|
|
// mpv is never told about Wayland: it only ever sees the EGL surface's default
|
|
// framebuffer. Embedding mpv into a foreign Wayland surface is not possible
|
|
// (mpv's --wid does not work on Wayland and upstream considers it out of
|
|
// scope), so the app owns the subsurface and drives the render itself.
|
|
class WaylandVideoSurface {
|
|
public:
|
|
WaylandVideoSurface() = default;
|
|
~WaylandVideoSurface();
|
|
|
|
WaylandVideoSurface(const WaylandVideoSurface&) = delete;
|
|
WaylandVideoSurface& operator=(const WaylandVideoSurface&) = delete;
|
|
|
|
// True when the process is on a Wayland display. Cheap; safe to call before
|
|
// the view is realized, so it can gate the window's visual.
|
|
static bool IsSupported(GdkDisplay* display);
|
|
|
|
// Binds the Wayland globals, creates the subsurface under `view`'s toplevel,
|
|
// and creates an EGL window surface on it.
|
|
//
|
|
// The plane is created at 10 bits per channel when the driver offers such a
|
|
// window config, then half-float (NVIDIA offers no 10-bit unorm configs on
|
|
// Wayland), falling back to 8. Returns false and fills `error` on any
|
|
// failure — the caller reports it; there is no other video path.
|
|
bool Create(GtkWidget* view, std::string* error);
|
|
|
|
// Releases the EGL surface, subsurface and Wayland objects. Idempotent.
|
|
void Destroy();
|
|
|
|
bool valid() const { return egl_surface_ != EGL_NO_SURFACE; }
|
|
EGLDisplay egl_display() const { return egl_display_; }
|
|
EGLConfig egl_config() const { return egl_config_; }
|
|
EGLSurface egl_surface() const { return egl_surface_; }
|
|
|
|
// Current buffer size in physical pixels. Zero until the first SetRect().
|
|
int32_t width() const { return width_; }
|
|
int32_t height() const { return height_; }
|
|
// "Dart has given the plane a rect worth showing", not "the stored numbers
|
|
// are non-zero" - SetRect floors the buffer size at one scale-sized block to
|
|
// keep it a multiple of the buffer scale, so after the first SetRect() the
|
|
// stored size is never zero.
|
|
bool has_size() const { return rect_valid_; }
|
|
|
|
// Places and sizes the plane. Coordinates are physical pixels in the
|
|
// toplevel's frame, matching what the Dart side sends via setVideoRect.
|
|
void SetRect(int32_t x, int32_t y, int32_t width, int32_t height, int32_t scale);
|
|
|
|
// Hides the plane by attaching a null buffer. The next Present() re-shows it.
|
|
void SetVisible(bool visible);
|
|
bool visible() const { return visible_; }
|
|
|
|
// True while a committed frame has not yet been acknowledged by the
|
|
// compositor. Callers must not render while this holds: the compositor stops
|
|
// acknowledging frames for an occluded or minimized surface, and rendering
|
|
// regardless would queue work that can never drain.
|
|
bool frame_pending() const { return frame_pending_; }
|
|
|
|
// Invoked on the GTK main thread when the compositor acknowledges a frame.
|
|
// This is what resumes rendering after the plane becomes visible again, so
|
|
// it must trigger a render — mpv's redraw latch stays set while frames are
|
|
// being skipped and will not notify again on its own.
|
|
void SetFrameCallback(std::function<void()> callback) { on_frame_ = std::move(callback); }
|
|
|
|
// Invoked when the plane needs a frame *now*, whether or not mpv has produced
|
|
// one. The frame callback above is not a substitute: it is the "a frame was
|
|
// acknowledged" path, and the plugin's handler skips rendering unless mpv's
|
|
// redraw latch or a pending resize says there is something new. The recovery
|
|
// after an abandoned colour transition has neither, and still has to commit -
|
|
// withdrawing a description only stages it, and eglSwapBuffers is what makes
|
|
// it real.
|
|
void SetForcedRenderCallback(std::function<void()> callback) { on_forced_render_ = std::move(callback); }
|
|
|
|
// Presents whatever was rendered into the EGL surface. No-op while hidden,
|
|
// while a frame is still pending, or while a colour transition is staged —
|
|
// the last being the one case a caller cannot read off the plane's visible
|
|
// state, so see hdr_transition_staged().
|
|
bool Present();
|
|
|
|
// True when this plane can be described as HDR at all: the compositor offers
|
|
// a parametric image-description creator, accepts the perceptual render
|
|
// intent, and advertises BT.2020 primaries plus at least one HDR curve (PQ or
|
|
// HLG) — *and* the plane itself got a deep EGL config (10-bit unorm or fp16)
|
|
// and a colour surface, without which the aggregate is dropped again in
|
|
// Create(). Which curve a given source needs is checked per source by
|
|
// CanDescribeSource(). This says nothing about whether the *display* is in
|
|
// HDR — see output_is_hdr().
|
|
bool supports_hdr() const { return supports_hdr_; }
|
|
|
|
// What the compositor says it would prefer for this surface, from
|
|
// wp_color_management_surface_feedback_v1. This is the only way to learn the
|
|
// output's *real* peak: an HDR output's preferred description carries the
|
|
// panel's target luminance, where PQ's own nominal maximum is always 10000.
|
|
const PreferredColorDescription& preferred() const { return preferred_; }
|
|
|
|
// True when the output this surface sits on has enough luminance headroom
|
|
// above its own reference white to be worth passing HDR through. The claim
|
|
// this makes is deliberately narrower than "the user's HDR toggle is on":
|
|
// OutputHasHdrHeadroom explains why no signal in this protocol answers that,
|
|
// and why the margin it applies is not a magic number.
|
|
bool output_is_hdr() const {
|
|
return preferred_.valid && OutputHasHdrHeadroom(preferred_.max_luminance, preferred_.reference_luminance);
|
|
}
|
|
|
|
// Invoked on the GTK main thread when the compositor's preferred description
|
|
// changes — a monitor move, or HDR being switched on or off under us.
|
|
void SetPreferredChangedCallback(std::function<void()> callback) { on_preferred_changed_ = std::move(callback); }
|
|
|
|
// Number of bits per colour channel the plane actually got: 16 on a
|
|
// half-float plane, 10 on a 10-bit unorm one, otherwise 8. PQ in 8 bits
|
|
// bands badly, so HDR needs at least 10.
|
|
int depth_bits() const { return depth_bits_; }
|
|
|
|
// Stages a colour change. It has to be two-phase; apply_hdr_state in
|
|
// mpv_plugin.cc tells that story in full. In outline: BeginHdrTransition
|
|
// stages and validates the description and holds Present() while it does, the
|
|
// caller switches mpv once it settles, and CommitHdrTransition attaches the
|
|
// state and releases the hold so the first buffer rendered in the new colour
|
|
// space is the one that carries it. Abort backs out and changes nothing.
|
|
//
|
|
// `describe` is an instruction, not a request: DecideHdr in hdr_metadata.h has
|
|
// already weighed the app's permission, this surface's capabilities, the
|
|
// output's state and the source.
|
|
//
|
|
// `on_settled(token, true)` means Commit may proceed. It fires synchronously
|
|
// when there is nothing to validate, so the caller must tolerate re-entry.
|
|
//
|
|
// The token identifies *this* transition, and Commit and Abort ignore any other,
|
|
// so a settled-but-uncommitted transition whose mpv request is still in flight
|
|
// cannot be committed against a newer description. A token of zero means nothing
|
|
// was staged, so there is nothing to commit or abort.
|
|
void BeginHdrTransition(bool describe, const HdrMetadata& metadata, std::function<void(uint64_t, bool)> on_settled);
|
|
|
|
// Applies the transition named by `token`. Returns true when the plane should
|
|
// be re-rendered and presented at once, so the new state reaches the screen
|
|
// instead of waiting for whatever frame mpv happens to produce next. Ignores a
|
|
// token that is not the staged one.
|
|
bool CommitHdrTransition(uint64_t token);
|
|
|
|
// Discards the transition named by `token` and releases the hold. The committed
|
|
// colour state is left exactly as it was. Ignores a stale token.
|
|
void AbortHdrTransition(uint64_t token);
|
|
|
|
// True while a transition is staged, i.e. while Present() is being held.
|
|
bool hdr_transition_staged() const { return transition_staged_; }
|
|
|
|
// Drops any staged transition and unsets the description immediately.
|
|
//
|
|
// For the case where mpv's colour space had to be forced back to SDR while
|
|
// unwinding a refused change: the description already committed is then no
|
|
// longer true of the pixels, and aborting alone would leave it in place. Returns
|
|
// true when the plane should be re-rendered and presented at once.
|
|
bool ForceUndescribed();
|
|
|
|
// Whether this source could be described at all: it carries an HDR curve, a
|
|
// BT.2020 container, and the compositor advertised that specific named pair.
|
|
//
|
|
// Public because the caller has to know the answer *before* it changes mpv's
|
|
// output colour space — the pixels have to be committed to before the surface
|
|
// is described, or the two disagree for a frame.
|
|
bool CanDescribeSource(const HdrMetadata& metadata) const;
|
|
|
|
// Whether a description is attached, i.e. whether the compositor is currently
|
|
// being told this plane carries an HDR curve.
|
|
bool hdr_active() const { return hdr_active_; }
|
|
|
|
private:
|
|
bool BindGlobals(GdkDisplay* display, std::string* error);
|
|
void BuildImageDescription();
|
|
bool InitEgl(std::string* error);
|
|
void RequestParentCommit();
|
|
void ClearFrameCallback();
|
|
/// Takes the current buffer off screen and drops any pending frame callback.
|
|
/// A subsurface has no visibility of its own, so this is what "not showing"
|
|
/// actually is - used both when Dart hides the plane and when the rect it was
|
|
/// covering goes away.
|
|
void DetachBuffer();
|
|
// Destroys the description staged for the pending transition, if any. The
|
|
// attached one is never held; see staged_description_.
|
|
void ClearStagedDescription();
|
|
// Tears the staged transition down unconditionally and tells whoever was
|
|
// waiting that it will not be committed. The token-checked Abort delegates here;
|
|
// teardown and ForceUndescribed call it directly.
|
|
void DiscardTransition();
|
|
// Shared tail of the transition's outcome, whichever event delivered it.
|
|
void SettleTransition(bool ok);
|
|
|
|
static void HandleFrameDone(void* data, wl_callback* callback, uint32_t time);
|
|
// Interface version 1 only; version 2 and later send ready2 in its place.
|
|
static void HandleImageDescriptionReady(void* data, wp_image_description_v1* desc, uint32_t identity);
|
|
// Interface version 2+. Must be present rather than null, for the reason
|
|
// given beside the description listener in BuildImageDescription().
|
|
static void HandleImageDescriptionReady2(
|
|
void* data, wp_image_description_v1* desc, uint32_t identity_hi, uint32_t identity_lo);
|
|
static void HandleImageDescriptionFailed(
|
|
void* data, wp_image_description_v1* desc, uint32_t cause, const char* message);
|
|
|
|
// Bounds each half of a staged transition: first the compositor's verdict on
|
|
// the image description, then the caller's mpv leg deciding to commit or
|
|
// abort. Present() and the plugin's render path are held across *both*, so it
|
|
// is re-armed rather than cancelled when the compositor answers - the second
|
|
// wait is the longer one and has no timeout of its own.
|
|
static constexpr int kTransitionTimeoutSeconds = 5;
|
|
// How many roundtrips a synchronous bootstrap waits for its answer. Ready,
|
|
// then the info burst, then done is three at worst, plus one spare for a
|
|
// compositor that splits them differently.
|
|
static constexpr int kBootstrapRoundtrips = 4;
|
|
void ArmTransitionWatchdog();
|
|
void CancelTransitionWatchdog();
|
|
guint watchdog_source_ = 0;
|
|
|
|
// Creates the preferred-description query. The returned description is ready
|
|
// immediately per the protocol, so get_information follows on ready, and the
|
|
// accumulated fields are committed when the info burst ends with done.
|
|
void BeginPreferredQuery();
|
|
void ClearPreferredQuery();
|
|
void CommitPreferredQuery();
|
|
|
|
static void HandlePreferredChanged(void* data, wp_color_management_surface_feedback_v1* feedback, uint32_t identity);
|
|
static void HandlePreferredChanged2(
|
|
void* data, wp_color_management_surface_feedback_v1* feedback, uint32_t identity_hi, uint32_t identity_lo);
|
|
static void HandlePreferredReady(void* data, wp_image_description_v1* desc, uint32_t identity);
|
|
static void HandlePreferredReady2(
|
|
void* data, wp_image_description_v1* desc, uint32_t identity_hi, uint32_t identity_lo);
|
|
static void HandlePreferredFailed(void* data, wp_image_description_v1* desc, uint32_t cause, const char* message);
|
|
|
|
// Only tf_named, primaries_named, luminances and target_luminance carry
|
|
// anything we use, and icc_file has to close the fd it is handed; the
|
|
// remainder are deliberate no-ops rather than omissions, for the reason given
|
|
// beside the description listener in BuildImageDescription().
|
|
static void HandleInfoDone(void* data, wp_image_description_info_v1* info);
|
|
static void HandleInfoIccFile(void* data, wp_image_description_info_v1* info, int32_t icc, uint32_t icc_size);
|
|
static void HandleInfoPrimaries(
|
|
void* data, wp_image_description_info_v1* info, int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x,
|
|
int32_t b_y, int32_t w_x, int32_t w_y);
|
|
static void HandleInfoPrimariesNamed(void* data, wp_image_description_info_v1* info, uint32_t primaries);
|
|
static void HandleInfoTfPower(void* data, wp_image_description_info_v1* info, uint32_t eexp);
|
|
static void HandleInfoTfNamed(void* data, wp_image_description_info_v1* info, uint32_t tf);
|
|
static void HandleInfoLuminances(
|
|
void* data, wp_image_description_info_v1* info, uint32_t min_lum, uint32_t max_lum, uint32_t reference_lum);
|
|
static void HandleInfoTargetPrimaries(
|
|
void* data, wp_image_description_info_v1* info, int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x,
|
|
int32_t b_y, int32_t w_x, int32_t w_y);
|
|
static void HandleInfoTargetLuminance(
|
|
void* data, wp_image_description_info_v1* info, uint32_t min_lum, uint32_t max_lum);
|
|
static void HandleInfoTargetMaxCll(void* data, wp_image_description_info_v1* info, uint32_t max_cll);
|
|
static void HandleInfoTargetMaxFall(void* data, wp_image_description_info_v1* info, uint32_t max_fall);
|
|
|
|
// The colour manager's capability burst. These are static members taking the
|
|
// surface as their user data, rather than file-locals over BindGlobals'
|
|
// stack; the add_listener call there says why.
|
|
static void HandleManagerIntent(void* data, wp_color_manager_v1* manager, uint32_t intent);
|
|
static void HandleManagerFeature(void* data, wp_color_manager_v1* manager, uint32_t feature);
|
|
static void HandleManagerTransferFunction(void* data, wp_color_manager_v1* manager, uint32_t tf);
|
|
static void HandleManagerPrimaries(void* data, wp_color_manager_v1* manager, uint32_t primaries);
|
|
static void HandleManagerDone(void* data, wp_color_manager_v1* manager);
|
|
|
|
GtkWidget* view_ = nullptr;
|
|
|
|
wl_display* wl_display_ = nullptr; // owned by GDK
|
|
wl_compositor* compositor_ = nullptr; // owned by GDK
|
|
wl_subcompositor* subcompositor_ = nullptr; // bound by us
|
|
wl_surface* surface_ = nullptr;
|
|
wl_subsurface* subsurface_ = nullptr;
|
|
wl_egl_window* egl_window_ = nullptr;
|
|
|
|
EGLDisplay egl_display_ = EGL_NO_DISPLAY;
|
|
EGLConfig egl_config_ = nullptr;
|
|
EGLSurface egl_surface_ = EGL_NO_SURFACE;
|
|
|
|
int32_t x_ = 0;
|
|
int32_t y_ = 0;
|
|
int32_t width_ = 0;
|
|
int32_t height_ = 0;
|
|
int32_t scale_ = 1;
|
|
// The view's own offset inside the toplevel, which is the frame
|
|
// wl_subsurface_set_position uses. Non-zero under client-side decorations.
|
|
int32_t view_x_ = 0;
|
|
int32_t view_y_ = 0;
|
|
bool visible_ = false;
|
|
// Set from the size Dart asked for, before SetRect rounds it into a whole
|
|
// number of scale-sized blocks. See has_size().
|
|
bool rect_valid_ = false;
|
|
bool buffer_attached_ = false;
|
|
bool frame_pending_ = false;
|
|
wl_callback* frame_callback_ = nullptr;
|
|
std::function<void()> on_frame_;
|
|
std::function<void()> on_forced_render_;
|
|
|
|
wp_color_manager_v1* color_manager_ = nullptr;
|
|
wp_color_management_surface_v1* color_surface_ = nullptr;
|
|
// The description being validated for a staged transition. Never the attached
|
|
// one: set_image_description copies, so the object is destroyed immediately
|
|
// after it is handed over.
|
|
wp_image_description_v1* staged_description_ = nullptr;
|
|
// A transition is staged: Present() is held, and Commit or Abort will release
|
|
// it. `staged_describe_` is what Commit will apply, and `transition_token_` is
|
|
// what Commit and Abort must match to act on it.
|
|
bool transition_staged_ = false;
|
|
uint64_t transition_token_ = 0;
|
|
bool staged_describe_ = false;
|
|
HdrMetadata staged_metadata_;
|
|
std::function<void(uint64_t, bool)> on_transition_settled_;
|
|
|
|
// Feedback lives for the whole surface lifetime so preferred_changed keeps
|
|
// arriving; the description and info objects are transient, created per query
|
|
// and destroyed as soon as their values have been copied out.
|
|
wp_color_management_surface_feedback_v1* color_feedback_ = nullptr;
|
|
wp_image_description_v1* preferred_description_ = nullptr;
|
|
wp_image_description_info_v1* preferred_info_ = nullptr;
|
|
PreferredColorDescription preferred_;
|
|
PreferredColorDescription pending_preferred_;
|
|
std::function<void()> on_preferred_changed_;
|
|
// The committed source description, i.e. what the attached description was
|
|
// built from. Compared against a new request so an identical one is a no-op
|
|
// rather than a needless round-trip through the compositor.
|
|
HdrMetadata metadata_;
|
|
int depth_bits_ = 8;
|
|
// supports_hdr_ is the aggregate gate; the three below are what the compositor
|
|
// advertised individually, because whether a *given* source can be described
|
|
// depends on its own curve, not on the aggregate.
|
|
bool supports_hdr_ = false;
|
|
bool supports_pq_ = false;
|
|
bool supports_hlg_ = false;
|
|
bool supports_bt2020_ = false;
|
|
// What the compositor will accept in a luminance description. Consulted by
|
|
// PlanHdrLuminance, which turns it plus the source into a legal request set.
|
|
CompositorLuminanceSupport luminance_support_;
|
|
// What the colour manager advertised, filled by the manager listener during
|
|
// the bootstrap. Kept on the object for the reason given above the handlers.
|
|
struct ManagerCaps {
|
|
bool parametric = false;
|
|
bool perceptual = false;
|
|
bool pq = false;
|
|
bool hlg = false;
|
|
bool bt2020 = false;
|
|
bool mastering = false;
|
|
bool extended_target_volume = false;
|
|
bool done = false;
|
|
};
|
|
ManagerCaps manager_caps_;
|
|
bool hdr_active_ = false;
|
|
};
|
|
|
|
} // namespace mpv
|
|
|
|
#endif // PLEZY_LINUX_MPV_WAYLAND_VIDEO_SURFACE_H_
|