mirror of
https://github.com/brl/mutter.git
synced 2024-11-23 16:40:41 -05:00
fcbeae5f60
Avoids leaking implementation details outside. Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1712>
418 lines
14 KiB
C
418 lines
14 KiB
C
/*
|
|
* presentation-time protocol
|
|
*
|
|
* Copyright (C) 2020 Ivan Molodetskikh <yalterz@gmail.com>
|
|
*
|
|
* 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 "config.h"
|
|
|
|
#include "meta-wayland-presentation-time-private.h"
|
|
|
|
#include <glib.h>
|
|
|
|
#include "compositor/meta-surface-actor-wayland.h"
|
|
#include "wayland/meta-wayland-cursor-surface.h"
|
|
#include "wayland/meta-wayland-private.h"
|
|
#include "wayland/meta-wayland-surface.h"
|
|
#include "wayland/meta-wayland-outputs.h"
|
|
#include "wayland/meta-wayland-versions.h"
|
|
|
|
#include "presentation-time-server-protocol.h"
|
|
|
|
static void
|
|
wp_presentation_feedback_destructor (struct wl_resource *resource)
|
|
{
|
|
MetaWaylandPresentationFeedback *feedback =
|
|
wl_resource_get_user_data (resource);
|
|
|
|
wl_list_remove (&feedback->link);
|
|
g_free (feedback);
|
|
}
|
|
|
|
static void
|
|
wp_presentation_destroy (struct wl_client *client,
|
|
struct wl_resource *resource)
|
|
{
|
|
wl_resource_destroy (resource);
|
|
}
|
|
|
|
static void
|
|
wp_presentation_feedback (struct wl_client *client,
|
|
struct wl_resource *resource,
|
|
struct wl_resource *surface_resource,
|
|
uint32_t callback_id)
|
|
{
|
|
MetaWaylandSurface *surface = wl_resource_get_user_data (surface_resource);
|
|
MetaWaylandSurfaceState *pending;
|
|
MetaWaylandPresentationFeedback *feedback;
|
|
|
|
feedback = g_new0 (MetaWaylandPresentationFeedback, 1);
|
|
wl_list_init (&feedback->link);
|
|
feedback->resource = wl_resource_create (client,
|
|
&wp_presentation_feedback_interface,
|
|
wl_resource_get_version (resource),
|
|
callback_id);
|
|
wl_resource_set_implementation (feedback->resource,
|
|
NULL,
|
|
feedback,
|
|
wp_presentation_feedback_destructor);
|
|
|
|
if (surface == NULL)
|
|
{
|
|
g_warn_if_reached ();
|
|
meta_wayland_presentation_feedback_discard (feedback);
|
|
return;
|
|
}
|
|
|
|
pending = meta_wayland_surface_get_pending_state (surface);
|
|
wl_list_insert (&pending->presentation_feedback_list, &feedback->link);
|
|
|
|
feedback->surface = surface;
|
|
}
|
|
|
|
static const struct wp_presentation_interface
|
|
meta_wayland_presentation_interface = {
|
|
wp_presentation_destroy,
|
|
wp_presentation_feedback,
|
|
};
|
|
|
|
static void
|
|
wp_presentation_bind (struct wl_client *client,
|
|
void *data,
|
|
uint32_t version,
|
|
uint32_t id)
|
|
{
|
|
struct wl_resource *resource;
|
|
|
|
resource = wl_resource_create (client,
|
|
&wp_presentation_interface,
|
|
version,
|
|
id);
|
|
wl_resource_set_implementation (resource,
|
|
&meta_wayland_presentation_interface,
|
|
NULL,
|
|
NULL);
|
|
|
|
/* Presentation timestamps in Mutter are guaranteed to be CLOCK_MONOTONIC. */
|
|
wp_presentation_send_clock_id (resource, CLOCK_MONOTONIC);
|
|
}
|
|
|
|
static void
|
|
discard_non_cursor_feedbacks (struct wl_list *feedbacks)
|
|
{
|
|
MetaWaylandPresentationFeedback *feedback, *next;
|
|
|
|
wl_list_for_each_safe (feedback, next, feedbacks, link)
|
|
{
|
|
if (META_IS_WAYLAND_CURSOR_SURFACE (feedback->surface->role))
|
|
continue;
|
|
|
|
meta_wayland_presentation_feedback_discard (feedback);
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_after_paint (ClutterStage *stage,
|
|
ClutterStageView *stage_view,
|
|
MetaWaylandCompositor *compositor)
|
|
{
|
|
struct wl_list *feedbacks;
|
|
GList *l;
|
|
|
|
/*
|
|
* We just painted this stage view, which means that all non-cursor feedbacks
|
|
* that didn't fire (e.g. due to page flip failing) are now obsolete and
|
|
* should be discarded.
|
|
*
|
|
* Cursor feedbacks have a similar mechanism done separately, mainly because
|
|
* they are painted earlier, in prepare_frame(). This means that the feedbacks
|
|
* list currently contains stale non-cursor feedbacks and up-to-date cursor
|
|
* feedbacks.
|
|
*/
|
|
feedbacks =
|
|
meta_wayland_presentation_time_ensure_feedbacks (&compositor->presentation_time,
|
|
stage_view);
|
|
discard_non_cursor_feedbacks (feedbacks);
|
|
|
|
l = compositor->presentation_time.feedback_surfaces;
|
|
while (l)
|
|
{
|
|
GList *l_cur = l;
|
|
MetaWaylandSurface *surface = l->data;
|
|
MetaSurfaceActor *actor;
|
|
ClutterStageView *surface_primary_view;
|
|
|
|
l = l->next;
|
|
|
|
actor = meta_wayland_surface_get_actor (surface);
|
|
if (!actor)
|
|
continue;
|
|
|
|
surface_primary_view =
|
|
meta_surface_actor_wayland_get_current_primary_view (actor, stage);
|
|
if (stage_view != surface_primary_view)
|
|
continue;
|
|
|
|
if (!wl_list_empty (&surface->presentation_time.feedback_list))
|
|
{
|
|
/* Add feedbacks to the list to be fired on presentation. */
|
|
wl_list_insert_list (feedbacks,
|
|
&surface->presentation_time.feedback_list);
|
|
wl_list_init (&surface->presentation_time.feedback_list);
|
|
|
|
surface->presentation_time.needs_sequence_update = TRUE;
|
|
}
|
|
|
|
compositor->presentation_time.feedback_surfaces =
|
|
g_list_delete_link (compositor->presentation_time.feedback_surfaces,
|
|
l_cur);
|
|
}
|
|
}
|
|
|
|
static void
|
|
destroy_feedback_list (gpointer data)
|
|
{
|
|
struct wl_list *feedbacks = data;
|
|
|
|
while (!wl_list_empty (feedbacks))
|
|
{
|
|
MetaWaylandPresentationFeedback *feedback =
|
|
wl_container_of (feedbacks->next, feedback, link);
|
|
|
|
meta_wayland_presentation_feedback_discard (feedback);
|
|
}
|
|
|
|
g_free (feedbacks);
|
|
}
|
|
|
|
static void
|
|
on_monitors_changed (MetaMonitorManager *manager,
|
|
MetaWaylandCompositor *compositor)
|
|
{
|
|
/* All ClutterStageViews were re-created, so clear our map. */
|
|
g_hash_table_remove_all (compositor->presentation_time.feedbacks);
|
|
}
|
|
|
|
void
|
|
meta_wayland_init_presentation_time (MetaWaylandCompositor *compositor)
|
|
{
|
|
MetaContext *context = compositor->context;
|
|
MetaBackend *backend = meta_context_get_backend (context);
|
|
MetaMonitorManager *monitor_manager =
|
|
meta_backend_get_monitor_manager (backend);
|
|
ClutterActor *stage = meta_backend_get_stage (backend);
|
|
|
|
compositor->presentation_time.feedbacks =
|
|
g_hash_table_new_full (NULL, NULL, NULL, destroy_feedback_list);
|
|
|
|
g_signal_connect (monitor_manager, "monitors-changed-internal",
|
|
G_CALLBACK (on_monitors_changed), compositor);
|
|
|
|
g_signal_connect (stage, "after-paint",
|
|
G_CALLBACK (on_after_paint), compositor);
|
|
|
|
if (wl_global_create (compositor->wayland_display,
|
|
&wp_presentation_interface,
|
|
META_WP_PRESENTATION_VERSION,
|
|
NULL,
|
|
wp_presentation_bind) == NULL)
|
|
g_error ("Failed to register a global wp_presentation object");
|
|
}
|
|
|
|
void
|
|
meta_wayland_presentation_feedback_discard (MetaWaylandPresentationFeedback *feedback)
|
|
{
|
|
wp_presentation_feedback_send_discarded (feedback->resource);
|
|
wl_resource_destroy (feedback->resource);
|
|
}
|
|
|
|
static void
|
|
maybe_update_presentation_sequence (MetaWaylandSurface *surface,
|
|
ClutterFrameInfo *frame_info,
|
|
MetaWaylandOutput *output)
|
|
{
|
|
unsigned int sequence_delta;
|
|
|
|
if (!surface->presentation_time.needs_sequence_update)
|
|
return;
|
|
|
|
surface->presentation_time.needs_sequence_update = FALSE;
|
|
|
|
if (!(frame_info->flags & CLUTTER_FRAME_INFO_FLAG_VSYNC))
|
|
goto invalid_sequence;
|
|
|
|
/* Getting sequence = 0 after sequence = UINT_MAX is likely valid (32-bit
|
|
* overflow, on a 144 Hz display that's ~173 days of operation). Getting it
|
|
* otherwise is usually a driver bug.
|
|
*/
|
|
if (frame_info->sequence == 0 &&
|
|
!(surface->presentation_time.is_last_output_sequence_valid &&
|
|
surface->presentation_time.last_output_sequence == UINT_MAX))
|
|
{
|
|
g_warning_once ("Invalid sequence for VSYNC frame info");
|
|
goto invalid_sequence;
|
|
}
|
|
|
|
if (surface->presentation_time.is_last_output_sequence_valid &&
|
|
surface->presentation_time.last_output == output)
|
|
{
|
|
sequence_delta =
|
|
frame_info->sequence - surface->presentation_time.last_output_sequence;
|
|
}
|
|
else
|
|
{
|
|
/* Sequence generally has different base between different outputs, but we
|
|
* want to keep it monotonic and without sudden jumps when the surface is
|
|
* moved between outputs. This matches the Xorg behavior with regards to
|
|
* the GLX_OML_sync_control implementation.
|
|
*/
|
|
sequence_delta = 1;
|
|
}
|
|
|
|
surface->presentation_time.sequence += sequence_delta;
|
|
surface->presentation_time.last_output = output;
|
|
surface->presentation_time.last_output_sequence = frame_info->sequence;
|
|
surface->presentation_time.is_last_output_sequence_valid = TRUE;
|
|
|
|
return;
|
|
|
|
invalid_sequence:
|
|
surface->presentation_time.sequence += 1;
|
|
surface->presentation_time.last_output = output;
|
|
surface->presentation_time.is_last_output_sequence_valid = FALSE;
|
|
}
|
|
|
|
void
|
|
meta_wayland_presentation_feedback_present (MetaWaylandPresentationFeedback *feedback,
|
|
ClutterFrameInfo *frame_info,
|
|
MetaWaylandOutput *output)
|
|
{
|
|
MetaWaylandSurface *surface = feedback->surface;
|
|
int64_t time_us = frame_info->presentation_time;
|
|
uint64_t time_s;
|
|
uint32_t tv_sec_hi, tv_sec_lo, tv_nsec;
|
|
uint32_t refresh_interval_ns;
|
|
uint32_t seq_hi, seq_lo;
|
|
uint32_t flags;
|
|
const GList *l;
|
|
|
|
if (output == NULL)
|
|
{
|
|
g_warning ("Output is NULL while sending presentation feedback");
|
|
meta_wayland_presentation_feedback_discard (feedback);
|
|
return;
|
|
}
|
|
|
|
time_s = us2s (time_us);
|
|
|
|
tv_sec_hi = time_s >> 32;
|
|
tv_sec_lo = time_s;
|
|
tv_nsec = (uint32_t) us2ns (time_us - s2us (time_s));
|
|
|
|
refresh_interval_ns = (uint32_t) (0.5 + s2ns (1) / frame_info->refresh_rate);
|
|
|
|
maybe_update_presentation_sequence (surface, frame_info, output);
|
|
|
|
seq_hi = surface->presentation_time.sequence >> 32;
|
|
seq_lo = surface->presentation_time.sequence;
|
|
|
|
flags = WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION;
|
|
|
|
if (frame_info->flags & CLUTTER_FRAME_INFO_FLAG_HW_CLOCK)
|
|
flags |= WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK;
|
|
|
|
if (frame_info->flags & CLUTTER_FRAME_INFO_FLAG_ZERO_COPY)
|
|
flags |= WP_PRESENTATION_FEEDBACK_KIND_ZERO_COPY;
|
|
|
|
if (frame_info->flags & CLUTTER_FRAME_INFO_FLAG_VSYNC)
|
|
flags |= WP_PRESENTATION_FEEDBACK_KIND_VSYNC;
|
|
|
|
for (l = meta_wayland_output_get_resources (output); l; l = l->next)
|
|
{
|
|
struct wl_resource *output_resource = l->data;
|
|
|
|
if (feedback->resource->client == output_resource->client)
|
|
{
|
|
wp_presentation_feedback_send_sync_output (feedback->resource,
|
|
output_resource);
|
|
}
|
|
}
|
|
|
|
wp_presentation_feedback_send_presented (feedback->resource,
|
|
tv_sec_hi,
|
|
tv_sec_lo,
|
|
tv_nsec,
|
|
refresh_interval_ns,
|
|
seq_hi,
|
|
seq_lo,
|
|
flags);
|
|
|
|
wl_resource_destroy (feedback->resource);
|
|
}
|
|
|
|
struct wl_list *
|
|
meta_wayland_presentation_time_ensure_feedbacks (MetaWaylandPresentationTime *presentation_time,
|
|
ClutterStageView *stage_view)
|
|
{
|
|
if (!g_hash_table_contains (presentation_time->feedbacks, stage_view))
|
|
{
|
|
struct wl_list *list;
|
|
|
|
list = g_new0 (struct wl_list, 1);
|
|
wl_list_init (list);
|
|
g_hash_table_insert (presentation_time->feedbacks, stage_view, list);
|
|
|
|
return list;
|
|
}
|
|
|
|
return g_hash_table_lookup (presentation_time->feedbacks, stage_view);
|
|
}
|
|
|
|
void
|
|
meta_wayland_presentation_time_cursor_painted (MetaWaylandPresentationTime *presentation_time,
|
|
ClutterStageView *stage_view,
|
|
MetaWaylandCursorSurface *cursor_surface)
|
|
{
|
|
struct wl_list *feedbacks;
|
|
MetaWaylandPresentationFeedback *feedback, *next;
|
|
MetaWaylandSurfaceRole *role = META_WAYLAND_SURFACE_ROLE (cursor_surface);
|
|
MetaWaylandSurface *surface = meta_wayland_surface_role_get_surface (role);
|
|
|
|
feedbacks =
|
|
meta_wayland_presentation_time_ensure_feedbacks (presentation_time,
|
|
stage_view);
|
|
|
|
/* Discard previous feedbacks for this cursor as now it has gone stale. */
|
|
wl_list_for_each_safe (feedback, next, feedbacks, link)
|
|
{
|
|
if (feedback->surface->role == role)
|
|
meta_wayland_presentation_feedback_discard (feedback);
|
|
}
|
|
|
|
/* Add new feedbacks. */
|
|
if (!wl_list_empty (&surface->presentation_time.feedback_list))
|
|
{
|
|
wl_list_insert_list (feedbacks,
|
|
&surface->presentation_time.feedback_list);
|
|
wl_list_init (&surface->presentation_time.feedback_list);
|
|
|
|
surface->presentation_time.needs_sequence_update = TRUE;
|
|
}
|
|
}
|