mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 15:11:31 +00:00
The buffer scale and viewport were only set from the resize handling in the swap path, which runs after the buffer has been committed. The first commit therefore carried a buffer of scale x the window size with no scale declared, so the compositor sized the window from the raw buffer. On a scaled output that is larger than the work area GNOME maximizes it, after which libdecor drops every resize as the frame is no longer floating, leaving the window stuck at the size of the display. Split the scaling out of the swap path and apply it when the EGL window is created, so it is in place for the first commit.
789 lines
24 KiB
C
789 lines
24 KiB
C
/**
|
|
* Looking Glass
|
|
* Copyright © 2017-2026 The Looking Glass Authors
|
|
* https://looking-glass.io
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation; either version 2 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
|
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "wayland.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
|
|
#include <EGL/egl.h>
|
|
#include <wayland-client.h>
|
|
|
|
#include "app.h"
|
|
#include "common/debug.h"
|
|
#include "common/time.h"
|
|
#include "util.h"
|
|
|
|
#if defined(ENABLE_EGL) || defined(ENABLE_OPENGL)
|
|
#include "egl_dynprocs.h"
|
|
#include "eglutil.h"
|
|
|
|
// Map the buffer onto width x height logical pixels, through the viewport for
|
|
// a fractional scale, or the buffer scale otherwise
|
|
static void applySurfaceScale(int width, int height)
|
|
{
|
|
if (wlWm.fractionalScale)
|
|
{
|
|
wl_surface_set_buffer_scale(wlWm.surface, 1);
|
|
if (!wlWm.viewport)
|
|
wlWm.viewport = wp_viewporter_get_viewport(wlWm.viewporter, wlWm.surface);
|
|
wp_viewport_set_source(
|
|
wlWm.viewport,
|
|
wl_fixed_from_int(-1), wl_fixed_from_int(-1),
|
|
wl_fixed_from_int(-1), wl_fixed_from_int(-1)
|
|
);
|
|
wp_viewport_set_destination(wlWm.viewport, width, height);
|
|
}
|
|
else
|
|
{
|
|
if (wlWm.viewport)
|
|
{
|
|
// Clearing the source and destination rectangles should happen in wp_viewport_destroy.
|
|
// However, wlroots does not clear the rectangle until fixed in 456c6e22 (2021-08-02).
|
|
// This should be kept to work around old versions of wlroots.
|
|
wl_fixed_t clear = wl_fixed_from_int(-1);
|
|
wp_viewport_set_source(wlWm.viewport, clear, clear, clear, clear);
|
|
wp_viewport_set_destination(wlWm.viewport, -1, -1);
|
|
|
|
wp_viewport_destroy(wlWm.viewport);
|
|
wlWm.viewport = NULL;
|
|
}
|
|
wl_surface_set_buffer_scale(wlWm.surface, waylandScaleFloor(wlWm.scale));
|
|
}
|
|
}
|
|
|
|
bool waylandEGLInit(int w, int h)
|
|
{
|
|
wlWm.eglWindow = wl_egl_window_create(wlWm.surface, w, h);
|
|
if (!wlWm.eglWindow)
|
|
{
|
|
DEBUG_ERROR("Failed to create EGL window");
|
|
return false;
|
|
}
|
|
|
|
// The compositor sizes the window from the first buffer committed, so the
|
|
// scale has to be applied before then
|
|
int width, height;
|
|
wlWm.desktop->getSize(&width, &height);
|
|
if (width > 0 && height > 0)
|
|
applySurfaceScale(width, height);
|
|
|
|
return true;
|
|
}
|
|
|
|
EGLDisplay waylandGetEGLDisplay(void)
|
|
{
|
|
EGLNativeDisplayType native = (EGLNativeDisplayType) wlWm.display;
|
|
|
|
const char *early_exts = eglQueryString(NULL, EGL_EXTENSIONS);
|
|
|
|
if (util_hasGLExt(early_exts, "EGL_KHR_platform_wayland") &&
|
|
g_egl_dynProcs.eglGetPlatformDisplay)
|
|
{
|
|
DEBUG_INFO("Using eglGetPlatformDisplay");
|
|
return g_egl_dynProcs.eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, native, NULL);
|
|
}
|
|
|
|
if (util_hasGLExt(early_exts, "EGL_EXT_platform_wayland") &&
|
|
g_egl_dynProcs.eglGetPlatformDisplayEXT)
|
|
{
|
|
DEBUG_INFO("Using eglGetPlatformDisplayEXT");
|
|
return g_egl_dynProcs.eglGetPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_EXT, native, NULL);
|
|
}
|
|
|
|
DEBUG_INFO("Using eglGetDisplay");
|
|
return eglGetDisplay(native);
|
|
}
|
|
|
|
static void applyHDRPending(void)
|
|
{
|
|
enum WaylandHDRPendingAction action;
|
|
struct WaylandHDRParameters params;
|
|
|
|
LG_LOCK(wlWm.pendingHDRLock);
|
|
action = wlWm.pendingHDRAction;
|
|
params = wlWm.pendingHDR;
|
|
wlWm.pendingHDRAction = WAYLAND_HDR_PENDING_NONE;
|
|
LG_UNLOCK(wlWm.pendingHDRLock);
|
|
|
|
if (action == WAYLAND_HDR_PENDING_APPLY)
|
|
{
|
|
waylandSetHDRImageDesc(
|
|
params.displayPrimary, params.whitePoint,
|
|
params.maxDisplayLuminance,
|
|
params.minDisplayLuminance,
|
|
params.maxCLL,
|
|
params.maxFALL,
|
|
params.referenceWhiteLevel,
|
|
params.pq,
|
|
params.metadata);
|
|
}
|
|
else if (action == WAYLAND_HDR_PENDING_CLEAR)
|
|
waylandClearHDRImageDesc();
|
|
}
|
|
|
|
void waylandClearHDRImageDesc(void)
|
|
{
|
|
LG_LOCK(wlWm.hdrLock);
|
|
if (wlWm.hdrImageCreator)
|
|
{
|
|
wp_image_description_creator_params_v1_destroy(wlWm.hdrImageCreator);
|
|
wlWm.hdrImageCreator = NULL;
|
|
}
|
|
if (wlWm.hdrImageDesc)
|
|
{
|
|
wp_image_description_v1_destroy(wlWm.hdrImageDesc);
|
|
wlWm.hdrImageDesc = NULL;
|
|
}
|
|
wlWm.hdrImageDescReady = false;
|
|
|
|
if (wlWm.colorSurface && atomic_load(&wlWm.hdrActive))
|
|
wp_color_management_surface_v1_unset_image_description(wlWm.colorSurface);
|
|
|
|
atomic_store(&wlWm.hdrActive, false);
|
|
LG_UNLOCK(wlWm.hdrLock);
|
|
DEBUG_INFO("HDR image description removed from surface");
|
|
}
|
|
|
|
static void hdrImageDescReady(struct wp_image_description_v1 * desc)
|
|
{
|
|
LG_LOCK(wlWm.hdrLock);
|
|
if (desc != wlWm.hdrImageDesc)
|
|
{
|
|
LG_UNLOCK(wlWm.hdrLock);
|
|
return;
|
|
}
|
|
|
|
if (!wlWm.colorSurface)
|
|
{
|
|
DEBUG_WARN("HDR image description became ready without a color surface");
|
|
wp_image_description_v1_destroy(desc);
|
|
wlWm.hdrImageDesc = NULL;
|
|
LG_UNLOCK(wlWm.hdrLock);
|
|
return;
|
|
}
|
|
|
|
// Defer attachment until just after the current surface commit. EGL can
|
|
// then switch to native HDR for the next render, and that native frame and
|
|
// its image description become active in the same following commit.
|
|
wlWm.hdrImageDescReady = true;
|
|
const bool pq = wlWm.hdrImageDescPQ;
|
|
LG_UNLOCK(wlWm.hdrLock);
|
|
DEBUG_INFO("HDR image description is ready (%s)",
|
|
pq ? "PQ" : "scRGB");
|
|
app_invalidateWindow(true);
|
|
waylandStopWaitFrame();
|
|
}
|
|
|
|
static void activateReadyHDRImageDesc(void)
|
|
{
|
|
LG_LOCK(wlWm.hdrLock);
|
|
if (!wlWm.hdrImageDesc || !wlWm.hdrImageDescReady || !wlWm.colorSurface)
|
|
{
|
|
LG_UNLOCK(wlWm.hdrLock);
|
|
return;
|
|
}
|
|
|
|
struct wp_image_description_v1 * desc = wlWm.hdrImageDesc;
|
|
wp_color_management_surface_v1_set_image_description(
|
|
wlWm.colorSurface, desc,
|
|
WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL);
|
|
atomic_store(&wlWm.hdrActivePQ, wlWm.hdrImageDescPQ);
|
|
atomic_store(&wlWm.hdrActive, true);
|
|
|
|
// set_image_description has copy semantics; the protocol object is no
|
|
// longer needed once it has been attached to the pending surface state.
|
|
wlWm.hdrImageDesc = NULL;
|
|
wlWm.hdrImageDescReady = false;
|
|
wp_image_description_v1_destroy(desc);
|
|
const bool pq = atomic_load(&wlWm.hdrActivePQ);
|
|
LG_UNLOCK(wlWm.hdrLock);
|
|
|
|
DEBUG_INFO("HDR image description pending next surface commit (%s)",
|
|
pq ? "PQ" : "scRGB");
|
|
app_invalidateWindow(true);
|
|
waylandStopWaitFrame();
|
|
}
|
|
|
|
static void hdrImageDescFailed(void * data,
|
|
struct wp_image_description_v1 * desc, uint32_t cause, const char * message)
|
|
{
|
|
(void)data;
|
|
LG_LOCK(wlWm.hdrLock);
|
|
if (desc != wlWm.hdrImageDesc)
|
|
{
|
|
LG_UNLOCK(wlWm.hdrLock);
|
|
return;
|
|
}
|
|
|
|
DEBUG_WARN("Failed to create HDR image description (cause:%u): %s",
|
|
cause, message);
|
|
if (atomic_load(&wlWm.hdrActive))
|
|
DEBUG_WARN("Retaining the previous active HDR image description");
|
|
wlWm.hdrImageDesc = NULL;
|
|
wlWm.hdrImageDescReady = false;
|
|
wp_image_description_v1_destroy(desc);
|
|
LG_UNLOCK(wlWm.hdrLock);
|
|
app_invalidateWindow(true);
|
|
waylandStopWaitFrame();
|
|
}
|
|
|
|
static void hdrImageDescReadyV1(void * data,
|
|
struct wp_image_description_v1 * desc, uint32_t identity)
|
|
{
|
|
(void)data;
|
|
(void)identity;
|
|
hdrImageDescReady(desc);
|
|
}
|
|
|
|
static void hdrImageDescReadyV2(void * data,
|
|
struct wp_image_description_v1 * desc, uint32_t identityHi,
|
|
uint32_t identityLo)
|
|
{
|
|
(void)data;
|
|
(void)identityHi;
|
|
(void)identityLo;
|
|
hdrImageDescReady(desc);
|
|
}
|
|
|
|
static const struct wp_image_description_v1_listener hdrImageDescListener =
|
|
{
|
|
.failed = hdrImageDescFailed,
|
|
.ready = hdrImageDescReadyV1,
|
|
.ready2 = hdrImageDescReadyV2,
|
|
};
|
|
|
|
bool waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface,
|
|
const struct Rect * damage, int count, uint64_t frameToken,
|
|
uint64_t * swapTime, bool * presentTracked)
|
|
{
|
|
bool result = false;
|
|
*swapTime = 0;
|
|
*presentTracked = false;
|
|
|
|
// EGL presentation sends a batch of Wayland requests ending in a surface
|
|
// commit. A concurrent commit would apply a partial batch and, when explicit
|
|
// sync is active, omit one of the required acquire/release timeline points.
|
|
INTERLOCKED_SECTION(wlWm.surfaceLock,
|
|
{
|
|
if (!wlWm.swapWithDamage.init)
|
|
{
|
|
if (wl_proxy_get_version((struct wl_proxy *) wlWm.surface) < 4)
|
|
{
|
|
DEBUG_INFO("Swapping buffers with damage: not supported, need wl_compositor v4");
|
|
swapWithDamageDisable(&wlWm.swapWithDamage);
|
|
}
|
|
else
|
|
swapWithDamageInit(&wlWm.swapWithDamage, display);
|
|
}
|
|
|
|
struct WaylandPresentationFrame * presentationFrame =
|
|
waylandPresentationFrame(frameToken);
|
|
applyHDRPending();
|
|
const uint64_t swapStart = nanotime();
|
|
result = swapWithDamage(
|
|
&wlWm.swapWithDamage, display, surface, damage, count);
|
|
*swapTime = nanotime() - swapStart;
|
|
waylandPresentationSwapDone(
|
|
presentationFrame, result, presentTracked);
|
|
if (result)
|
|
activateReadyHDRImageDesc();
|
|
});
|
|
|
|
if (wlWm.needsResize)
|
|
{
|
|
bool skipResize = false;
|
|
|
|
int width, height;
|
|
wlWm.desktop->getSize(&width, &height);
|
|
wl_egl_window_resize(wlWm.eglWindow, waylandScaleMulInt(wlWm.scale, width),
|
|
waylandScaleMulInt(wlWm.scale, height), 0, 0);
|
|
|
|
if (width == 0 || height == 0)
|
|
skipResize = true;
|
|
else
|
|
applySurfaceScale(width, height);
|
|
|
|
struct wl_region * region = wl_compositor_create_region(wlWm.compositor);
|
|
wl_region_add(region, 0, 0, width, height);
|
|
wl_surface_set_opaque_region(wlWm.surface, region);
|
|
wl_region_destroy(region);
|
|
|
|
app_handleResizeEvent(width, height, waylandScaleToDouble(wlWm.scale),
|
|
(struct Border) {0, 0, 0, 0});
|
|
app_invalidateWindow(true);
|
|
waylandStopWaitFrame();
|
|
wlWm.needsResize = skipResize;
|
|
}
|
|
|
|
wlWm.desktop->shellAckConfigureIfNeeded();
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
#ifdef ENABLE_EGL
|
|
EGLNativeWindowType waylandGetEGLNativeWindow(void)
|
|
{
|
|
return (EGLNativeWindowType) wlWm.eglWindow;
|
|
}
|
|
#endif
|
|
|
|
// HDR color management support
|
|
enum
|
|
{
|
|
HDR_CHROMATICITY_SCALE = 20,
|
|
HDR_MIN_LUMINANCE_SCALE = 10000,
|
|
HDR_PQ_MIN_LUMINANCE = 50,
|
|
HDR_PQ_MAX_LUMINANCE = 10000,
|
|
HDR_PQ_DEFAULT_WHITE_LEVEL = 203,
|
|
};
|
|
|
|
static int64_t hdrTriangleEdge(int32_t ax, int32_t ay,
|
|
int32_t bx, int32_t by, int32_t px, int32_t py)
|
|
{
|
|
return (int64_t)(px - ax) * (by - ay) -
|
|
(int64_t)(py - ay) * (bx - ax);
|
|
}
|
|
|
|
static bool hdrPointInBT2020(int32_t x, int32_t y)
|
|
{
|
|
// BT.2020 primaries in the KVMFR/DXGI scale of 50,000 units.
|
|
static const int32_t primary[3][2] =
|
|
{
|
|
{ 35400, 14600 },
|
|
{ 8500, 39850 },
|
|
{ 6550, 2300 },
|
|
};
|
|
|
|
const int64_t edge0 = hdrTriangleEdge(
|
|
primary[0][0], primary[0][1], primary[1][0], primary[1][1], x, y);
|
|
const int64_t edge1 = hdrTriangleEdge(
|
|
primary[1][0], primary[1][1], primary[2][0], primary[2][1], x, y);
|
|
const int64_t edge2 = hdrTriangleEdge(
|
|
primary[2][0], primary[2][1], primary[0][0], primary[0][1], x, y);
|
|
return (edge0 >= 0 && edge1 >= 0 && edge2 >= 0) ||
|
|
(edge0 <= 0 && edge1 <= 0 && edge2 <= 0);
|
|
}
|
|
|
|
static bool hdrTargetPrimariesContained(const uint16_t primary[3][2],
|
|
const uint16_t whitePoint[2])
|
|
{
|
|
for (unsigned i = 0; i < 3; ++i)
|
|
if (!hdrPointInBT2020(primary[i][0], primary[i][1]))
|
|
return false;
|
|
|
|
return hdrPointInBT2020(whitePoint[0], whitePoint[1]);
|
|
}
|
|
|
|
static bool hdrTargetLuminanceContained(uint32_t minLuminance,
|
|
uint32_t maxLuminance)
|
|
{
|
|
return minLuminance >= HDR_PQ_MIN_LUMINANCE &&
|
|
maxLuminance <= HDR_PQ_MAX_LUMINANCE;
|
|
}
|
|
|
|
void waylandSetHDRImageDesc(const uint16_t displayPrimary[3][2],
|
|
const uint16_t whitePoint[2], uint32_t maxDisplayLuminance,
|
|
uint32_t minDisplayLuminance, uint32_t maxCLL, uint32_t maxFALL,
|
|
uint32_t referenceWhiteLevel, bool hdrPQ, bool hdrMetadata)
|
|
{
|
|
if (!wlWm.colorManager)
|
|
return;
|
|
|
|
if (!atomic_load_explicit(&wlWm.cmFeaturesDone, memory_order_acquire))
|
|
{
|
|
DEBUG_WARN("Color management features not yet advertised, deferring HDR");
|
|
return;
|
|
}
|
|
|
|
if (!wlWm.cmHasPerceptualIntent)
|
|
{
|
|
DEBUG_WARN("Compositor does not support the perceptual render intent");
|
|
return;
|
|
}
|
|
|
|
if (hdrPQ && !wlWm.cmHasParametric)
|
|
{
|
|
DEBUG_WARN("Compositor does not support parametric image descriptions");
|
|
return;
|
|
}
|
|
|
|
// Verify the compositor supports the transfer function we need
|
|
if (hdrPQ && !wlWm.cmHasTFSt2084PQ)
|
|
{
|
|
DEBUG_WARN("Compositor does not support ST2084_PQ transfer function");
|
|
return;
|
|
}
|
|
if (!hdrPQ && !wlWm.cmHasWindowsSCRGB)
|
|
{
|
|
DEBUG_WARN("Compositor does not support Windows-scRGB image descriptions");
|
|
return;
|
|
}
|
|
|
|
// Verify primaries support for the target color space
|
|
if (hdrPQ && !wlWm.cmHasPrimariesBT2020)
|
|
{
|
|
DEBUG_WARN("Compositor does not support BT.2020 primaries");
|
|
return;
|
|
}
|
|
LG_LOCK(wlWm.hdrLock);
|
|
|
|
// Cancel only an in-flight replacement. The active surface description is
|
|
// retained until this replacement is ready.
|
|
if (wlWm.hdrImageCreator)
|
|
{
|
|
wp_image_description_creator_params_v1_destroy(wlWm.hdrImageCreator);
|
|
wlWm.hdrImageCreator = NULL;
|
|
}
|
|
if (wlWm.hdrImageDesc)
|
|
{
|
|
wp_image_description_v1_destroy(wlWm.hdrImageDesc);
|
|
wlWm.hdrImageDesc = NULL;
|
|
}
|
|
wlWm.hdrImageDescReady = false;
|
|
// If the encoding changed, remove the old description in the same surface
|
|
// commit that presents the software-mapped transition frame.
|
|
if (atomic_load(&wlWm.hdrActive) &&
|
|
atomic_load(&wlWm.hdrActivePQ) != hdrPQ)
|
|
{
|
|
wp_color_management_surface_v1_unset_image_description(wlWm.colorSurface);
|
|
atomic_store(&wlWm.hdrActive, false);
|
|
}
|
|
|
|
if (!wlWm.colorSurface)
|
|
{
|
|
wlWm.colorSurface =
|
|
wp_color_manager_v1_get_surface(wlWm.colorManager, wlWm.surface);
|
|
if (!wlWm.colorSurface)
|
|
{
|
|
DEBUG_WARN("Failed to get color management surface");
|
|
LG_UNLOCK(wlWm.hdrLock);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!hdrPQ)
|
|
{
|
|
wlWm.hdrImageDesc =
|
|
wp_color_manager_v1_create_windows_scrgb(wlWm.colorManager);
|
|
if (!wlWm.hdrImageDesc)
|
|
{
|
|
DEBUG_WARN("Failed to create Windows-scRGB image description");
|
|
LG_UNLOCK(wlWm.hdrLock);
|
|
return;
|
|
}
|
|
|
|
wlWm.hdrImageDescPQ = false;
|
|
wlWm.hdrImageDescReady = false;
|
|
wp_image_description_v1_add_listener(
|
|
wlWm.hdrImageDesc, &hdrImageDescListener, NULL);
|
|
LG_UNLOCK(wlWm.hdrLock);
|
|
DEBUG_INFO("HDR image description requested (scRGB, Windows-scRGB)");
|
|
return;
|
|
}
|
|
|
|
wlWm.hdrImageCreator =
|
|
wp_color_manager_v1_create_parametric_creator(wlWm.colorManager);
|
|
if (!wlWm.hdrImageCreator)
|
|
{
|
|
DEBUG_WARN("Failed to create parametric image description creator");
|
|
LG_UNLOCK(wlWm.hdrLock);
|
|
return;
|
|
}
|
|
|
|
// Set primaries: BT.2020 for PQ HDR10, sRGB for scRGB/FP16
|
|
wp_image_description_creator_params_v1_set_primaries_named(
|
|
wlWm.hdrImageCreator,
|
|
WP_COLOR_MANAGER_V1_PRIMARIES_BT2020);
|
|
|
|
// Select transfer function: PQ for PQ-encoded content, linear for scRGB/FP16
|
|
wp_image_description_creator_params_v1_set_tf_named(
|
|
wlWm.hdrImageCreator,
|
|
WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ);
|
|
|
|
const uint32_t sourceWhiteLevel = referenceWhiteLevel ?
|
|
referenceWhiteLevel : HDR_PQ_DEFAULT_WHITE_LEVEL;
|
|
|
|
// The primary colour volume describes the PQ encoding itself. Mastering
|
|
// display luminances are target-volume metadata and are set separately.
|
|
// Reference white describes the guest image content; the Wayland output
|
|
// reference white is used independently when composing local overlays.
|
|
if (wlWm.cmHasLuminances)
|
|
wp_image_description_creator_params_v1_set_luminances(
|
|
wlWm.hdrImageCreator,
|
|
HDR_PQ_MIN_LUMINANCE,
|
|
HDR_PQ_MAX_LUMINANCE,
|
|
sourceWhiteLevel);
|
|
else if (sourceWhiteLevel != HDR_PQ_DEFAULT_WHITE_LEVEL)
|
|
DEBUG_WARN("Compositor cannot accept the guest reference white level; "
|
|
"using the PQ default");
|
|
|
|
// KVMFR uses the ST 2086/DXGI scale of 50,000 units per coordinate while
|
|
// color-management-v1 uses 1,000,000 units per coordinate.
|
|
const bool canSetMastering = hdrPQ && hdrMetadata &&
|
|
wlWm.cmHasMasteringPrimaries;
|
|
const bool masteringPrimariesContained =
|
|
hdrTargetPrimariesContained(displayPrimary, whitePoint);
|
|
const bool masteringLuminanceContained =
|
|
hdrTargetLuminanceContained(minDisplayLuminance,
|
|
maxDisplayLuminance);
|
|
const bool validMasteringLuminance =
|
|
(uint64_t)maxDisplayLuminance * HDR_MIN_LUMINANCE_SCALE >
|
|
minDisplayLuminance;
|
|
if (canSetMastering &&
|
|
(masteringPrimariesContained || wlWm.cmHasExtendedTargetVolume))
|
|
{
|
|
wp_image_description_creator_params_v1_set_mastering_display_primaries(
|
|
wlWm.hdrImageCreator,
|
|
displayPrimary[0][0] * HDR_CHROMATICITY_SCALE,
|
|
displayPrimary[0][1] * HDR_CHROMATICITY_SCALE,
|
|
displayPrimary[1][0] * HDR_CHROMATICITY_SCALE,
|
|
displayPrimary[1][1] * HDR_CHROMATICITY_SCALE,
|
|
displayPrimary[2][0] * HDR_CHROMATICITY_SCALE,
|
|
displayPrimary[2][1] * HDR_CHROMATICITY_SCALE,
|
|
whitePoint[0] * HDR_CHROMATICITY_SCALE,
|
|
whitePoint[1] * HDR_CHROMATICITY_SCALE);
|
|
}
|
|
else if (canSetMastering)
|
|
DEBUG_WARN("HDR mastering primaries exceed the BT.2020 primary volume "
|
|
"without extended target-volume support; omitting them "
|
|
"(R:%u,%u G:%u,%u B:%u,%u W:%u,%u)",
|
|
displayPrimary[0][0], displayPrimary[0][1],
|
|
displayPrimary[1][0], displayPrimary[1][1],
|
|
displayPrimary[2][0], displayPrimary[2][1],
|
|
whitePoint[0], whitePoint[1]);
|
|
|
|
if (canSetMastering && validMasteringLuminance &&
|
|
(masteringLuminanceContained || wlWm.cmHasExtendedTargetVolume))
|
|
{
|
|
wp_image_description_creator_params_v1_set_mastering_luminance(
|
|
wlWm.hdrImageCreator,
|
|
minDisplayLuminance, maxDisplayLuminance);
|
|
}
|
|
else if (canSetMastering && !validMasteringLuminance)
|
|
DEBUG_WARN("Invalid HDR mastering luminance range; omitting it "
|
|
"(max:%u cd/m^2 min:%u (0.0001 cd/m^2))",
|
|
maxDisplayLuminance, minDisplayLuminance);
|
|
else if (canSetMastering)
|
|
DEBUG_WARN("HDR mastering luminance exceeds the PQ primary volume "
|
|
"without extended target-volume support; omitting it "
|
|
"(max:%u cd/m^2 min:%u (0.0001 cd/m^2))",
|
|
maxDisplayLuminance, minDisplayLuminance);
|
|
|
|
if (hdrPQ && hdrMetadata)
|
|
{
|
|
if (maxCLL > 0)
|
|
wp_image_description_creator_params_v1_set_max_cll(
|
|
wlWm.hdrImageCreator, maxCLL);
|
|
if (maxFALL > 0 && (maxCLL == 0 || maxFALL <= maxCLL))
|
|
wp_image_description_creator_params_v1_set_max_fall(
|
|
wlWm.hdrImageCreator, maxFALL);
|
|
}
|
|
|
|
wlWm.hdrImageDesc =
|
|
wp_image_description_creator_params_v1_create(wlWm.hdrImageCreator);
|
|
wlWm.hdrImageCreator = NULL; // consumed by create
|
|
|
|
if (!wlWm.hdrImageDesc)
|
|
{
|
|
DEBUG_WARN("Failed to create HDR image description");
|
|
LG_UNLOCK(wlWm.hdrLock);
|
|
return;
|
|
}
|
|
|
|
wlWm.hdrImageDescPQ = hdrPQ;
|
|
wlWm.hdrImageDescReady = false;
|
|
wp_image_description_v1_add_listener(
|
|
wlWm.hdrImageDesc, &hdrImageDescListener, NULL);
|
|
LG_UNLOCK(wlWm.hdrLock);
|
|
|
|
DEBUG_INFO("HDR image description requested (%s, %s, "
|
|
"referenceWhite:%u cd/m² maxLum:%u cd/m² "
|
|
"minLum:%u (0.0001 cd/m²) maxCLL:%u maxFALL:%u)",
|
|
hdrPQ ? "PQ" : "scRGB", hdrPQ ? "BT.2020" : "sRGB",
|
|
sourceWhiteLevel, maxDisplayLuminance, minDisplayLuminance,
|
|
maxCLL, maxFALL);
|
|
}
|
|
|
|
bool waylandRequestHDR(const uint16_t displayPrimary[3][2],
|
|
const uint16_t whitePoint[2], uint32_t maxDisplayLuminance,
|
|
uint32_t minDisplayLuminance, uint32_t maxCLL, uint32_t maxFALL,
|
|
uint32_t referenceWhiteLevel, bool hdrPQ, bool hdrMetadata)
|
|
{
|
|
if (!atomic_load_explicit(&wlWm.cmFeaturesDone, memory_order_acquire))
|
|
return false;
|
|
if (!wlWm.cmHasPerceptualIntent)
|
|
return false;
|
|
if (hdrPQ && (!wlWm.cmHasParametric || !wlWm.cmHasTFSt2084PQ ||
|
|
!wlWm.cmHasPrimariesBT2020))
|
|
return false;
|
|
if (!hdrPQ && !wlWm.cmHasWindowsSCRGB)
|
|
return false;
|
|
|
|
atomic_store(&wlWm.hdrRequestedPQ, hdrPQ);
|
|
atomic_store(&wlWm.hdrRequested, true);
|
|
|
|
LG_LOCK(wlWm.pendingHDRLock);
|
|
wlWm.pendingHDR.pq = hdrPQ;
|
|
wlWm.pendingHDR.metadata = hdrMetadata;
|
|
wlWm.pendingHDR.maxDisplayLuminance = maxDisplayLuminance;
|
|
wlWm.pendingHDR.minDisplayLuminance = minDisplayLuminance;
|
|
wlWm.pendingHDR.maxCLL = maxCLL;
|
|
wlWm.pendingHDR.maxFALL = maxFALL;
|
|
wlWm.pendingHDR.referenceWhiteLevel = referenceWhiteLevel;
|
|
memcpy(wlWm.pendingHDR.displayPrimary, displayPrimary,
|
|
sizeof(wlWm.pendingHDR.displayPrimary));
|
|
memcpy(wlWm.pendingHDR.whitePoint, whitePoint,
|
|
sizeof(wlWm.pendingHDR.whitePoint));
|
|
|
|
wlWm.pendingHDRAction = WAYLAND_HDR_PENDING_APPLY;
|
|
LG_UNLOCK(wlWm.pendingHDRLock);
|
|
return true;
|
|
}
|
|
|
|
void waylandRequestClearHDR(void)
|
|
{
|
|
atomic_store(&wlWm.hdrRequested, false);
|
|
LG_LOCK(wlWm.pendingHDRLock);
|
|
wlWm.pendingHDRAction = WAYLAND_HDR_PENDING_CLEAR;
|
|
LG_UNLOCK(wlWm.pendingHDRLock);
|
|
}
|
|
|
|
#ifdef ENABLE_OPENGL
|
|
|
|
static const EGLint eglBaseAttrs[] =
|
|
{
|
|
EGL_CONFORMANT , EGL_OPENGL_BIT,
|
|
EGL_RENDERABLE_TYPE , EGL_OPENGL_BIT,
|
|
EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER,
|
|
EGL_SAMPLE_BUFFERS , 0,
|
|
EGL_SAMPLES , 0,
|
|
EGL_NONE
|
|
};
|
|
|
|
static bool eglChooseConfigWithDepth(EGLDisplay display, EGLint red,
|
|
EGLint green, EGLint blue, EGLint alpha, EGLint depth,
|
|
EGLConfig * config, const char ** desc)
|
|
{
|
|
// Build attr array with the base attrs plus color depth
|
|
EGLint attr[32];
|
|
int ai = 0;
|
|
attr[ai++] = EGL_RED_SIZE ; attr[ai++] = red;
|
|
attr[ai++] = EGL_GREEN_SIZE; attr[ai++] = green;
|
|
attr[ai++] = EGL_BLUE_SIZE ; attr[ai++] = blue;
|
|
attr[ai++] = EGL_ALPHA_SIZE; attr[ai++] = alpha;
|
|
attr[ai++] = EGL_BUFFER_SIZE; attr[ai++] = depth;
|
|
for (int i = 0; eglBaseAttrs[i] != EGL_NONE; ++i)
|
|
attr[ai++] = eglBaseAttrs[i];
|
|
attr[ai] = EGL_NONE;
|
|
|
|
EGLint num_config;
|
|
if (eglChooseConfig(display, attr, config, 1, &num_config) && num_config > 0)
|
|
{
|
|
if (desc)
|
|
*desc = red >= 16 ? "FP16 (RGBA16F)" :
|
|
red >= 10 ? "10-bit (RGBA10)" : "8-bit (RGBA8)";
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool waylandOpenGLInit(void)
|
|
{
|
|
wlWm.glDisplay = waylandGetEGLDisplay();
|
|
|
|
int maj, min;
|
|
if (!eglInitialize(wlWm.glDisplay, &maj, &min))
|
|
{
|
|
DEBUG_ERROR("Unable to initialize EGL");
|
|
return false;
|
|
}
|
|
|
|
if (wlWm.glDisplay == EGL_NO_DISPLAY)
|
|
{
|
|
DEBUG_ERROR("Failed to get EGL display (eglError: 0x%x)", eglGetError());
|
|
return false;
|
|
}
|
|
|
|
EGLConfig config;
|
|
const char * configDesc = NULL;
|
|
|
|
// Probe for best available color depth: FP16 → 10-bit → 8-bit
|
|
if (!eglChooseConfigWithDepth(wlWm.glDisplay,
|
|
16, 16, 16, 0, 48, &config, &configDesc) &&
|
|
!eglChooseConfigWithDepth(wlWm.glDisplay,
|
|
10, 10, 10, 2, 32, &config, &configDesc) &&
|
|
!eglChooseConfigWithDepth(wlWm.glDisplay,
|
|
8, 8, 8, 0, 24, &config, &configDesc))
|
|
{
|
|
DEBUG_ERROR("Failed to choose any EGL config");
|
|
return false;
|
|
}
|
|
|
|
wlWm.glConfig = config;
|
|
DEBUG_INFO("EGL config: %s", configDesc);
|
|
|
|
// Also store the 8-bit fallback for SDR contexts
|
|
if (configDesc && strcmp(configDesc, "8-bit (RGBA8)") != 0)
|
|
eglChooseConfigWithDepth(wlWm.glDisplay,
|
|
8, 8, 8, 0, 24, &wlWm.glConfigSDR, NULL);
|
|
else
|
|
wlWm.glConfigSDR = wlWm.glConfig;
|
|
|
|
wlWm.glSurface = eglCreateWindowSurface(wlWm.glDisplay, wlWm.glConfig, wlWm.eglWindow, NULL);
|
|
if (wlWm.glSurface == EGL_NO_SURFACE)
|
|
{
|
|
DEBUG_ERROR("Failed to create EGL surface (eglError: 0x%x)", eglGetError());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
LG_DSGLContext waylandGLCreateContext(void)
|
|
{
|
|
eglBindAPI(EGL_OPENGL_API);
|
|
return eglCreateContext(wlWm.glDisplay, wlWm.glConfig, EGL_NO_CONTEXT, NULL);
|
|
}
|
|
|
|
void waylandGLDeleteContext(LG_DSGLContext context)
|
|
{
|
|
eglDestroyContext(wlWm.glDisplay, context);
|
|
}
|
|
|
|
void waylandGLMakeCurrent(LG_DSGLContext context)
|
|
{
|
|
eglMakeCurrent(wlWm.glDisplay, wlWm.glSurface, wlWm.glSurface, context);
|
|
}
|
|
|
|
void waylandGLSetSwapInterval(int interval)
|
|
{
|
|
eglSwapInterval(wlWm.glDisplay, interval);
|
|
}
|
|
|
|
void waylandGLSwapBuffers(void)
|
|
{
|
|
uint64_t swapTime = 0;
|
|
bool presentTracked = false;
|
|
(void)waylandEGLSwapBuffers(wlWm.glDisplay, wlWm.glSurface, NULL, 0, 0,
|
|
&swapTime, &presentTracked);
|
|
}
|
|
#endif
|