18c414cca4
Make sure that when we've recreated views that we'll actually paint a new frame for it. This was very rarely a problem, as views tend to result in getting damage etc being queued as side effects of various things, like layout, but e.g. when running certain tests, this might not happen. There is no situation where we want to create a new view that should remain unpainted, so just make sure we initialize it to become up to date. Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1947>
1526 lines
46 KiB
C
1526 lines
46 KiB
C
/*
|
|
* Copyright (C) 2016 Red Hat Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "clutter-build-config.h"
|
|
|
|
#include "clutter/clutter-stage-view.h"
|
|
#include "clutter/clutter-stage-view-private.h"
|
|
|
|
#include <cairo-gobject.h>
|
|
#include <math.h>
|
|
|
|
#include "clutter/clutter-damage-history.h"
|
|
#include "clutter/clutter-frame-clock.h"
|
|
#include "clutter/clutter-frame-private.h"
|
|
#include "clutter/clutter-private.h"
|
|
#include "clutter/clutter-mutter.h"
|
|
#include "clutter/clutter-stage-private.h"
|
|
#include "cogl/cogl.h"
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
|
|
PROP_NAME,
|
|
PROP_STAGE,
|
|
PROP_LAYOUT,
|
|
PROP_FRAMEBUFFER,
|
|
PROP_OFFSCREEN,
|
|
PROP_USE_SHADOWFB,
|
|
PROP_SCALE,
|
|
PROP_REFRESH_RATE,
|
|
PROP_VBLANK_DURATION_US,
|
|
|
|
PROP_LAST
|
|
};
|
|
|
|
static GParamSpec *obj_props[PROP_LAST];
|
|
|
|
typedef struct _ClutterStageViewPrivate
|
|
{
|
|
char *name;
|
|
|
|
ClutterStage *stage;
|
|
|
|
cairo_rectangle_int_t layout;
|
|
float scale;
|
|
CoglFramebuffer *framebuffer;
|
|
|
|
CoglOffscreen *offscreen;
|
|
CoglPipeline *offscreen_pipeline;
|
|
|
|
gboolean use_shadowfb;
|
|
struct {
|
|
struct {
|
|
CoglDmaBufHandle *handles[2];
|
|
int current_idx;
|
|
ClutterDamageHistory *damage_history;
|
|
} dma_buf;
|
|
|
|
CoglOffscreen *framebuffer;
|
|
} shadow;
|
|
|
|
CoglScanout *next_scanout;
|
|
|
|
gboolean has_redraw_clip;
|
|
cairo_region_t *redraw_clip;
|
|
|
|
float refresh_rate;
|
|
int64_t vblank_duration_us;
|
|
ClutterFrameClock *frame_clock;
|
|
|
|
struct {
|
|
int frame_count;
|
|
int64_t last_print_time_us;
|
|
int64_t cumulative_draw_time_us;
|
|
int64_t began_draw_time_us;
|
|
int64_t worst_draw_time_us;
|
|
} frame_timings;
|
|
|
|
guint dirty_viewport : 1;
|
|
guint dirty_projection : 1;
|
|
} ClutterStageViewPrivate;
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (ClutterStageView, clutter_stage_view, G_TYPE_OBJECT)
|
|
|
|
void
|
|
clutter_stage_view_destroy (ClutterStageView *view)
|
|
{
|
|
g_object_run_dispose (G_OBJECT (view));
|
|
g_object_unref (view);
|
|
}
|
|
|
|
void
|
|
clutter_stage_view_get_layout (ClutterStageView *view,
|
|
cairo_rectangle_int_t *rect)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
*rect = priv->layout;
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_view_get_framebuffer:
|
|
* @view: a #ClutterStageView
|
|
*
|
|
* Retrieves the framebuffer of @view to draw to.
|
|
*
|
|
* Returns: (transfer none): a #CoglFramebuffer
|
|
*/
|
|
CoglFramebuffer *
|
|
clutter_stage_view_get_framebuffer (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
if (priv->offscreen)
|
|
return COGL_FRAMEBUFFER (priv->offscreen);
|
|
else if (priv->shadow.framebuffer)
|
|
return COGL_FRAMEBUFFER (priv->shadow.framebuffer);
|
|
else
|
|
return priv->framebuffer;
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_view_get_onscreen:
|
|
* @view: a #ClutterStageView
|
|
*
|
|
* Retrieves the onscreen framebuffer of @view if available.
|
|
*
|
|
* Returns: (transfer none): a #CoglFramebuffer
|
|
*/
|
|
CoglFramebuffer *
|
|
clutter_stage_view_get_onscreen (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
return priv->framebuffer;
|
|
}
|
|
|
|
static CoglPipeline *
|
|
clutter_stage_view_create_offscreen_pipeline (CoglOffscreen *offscreen)
|
|
{
|
|
CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (offscreen);
|
|
CoglPipeline *pipeline;
|
|
|
|
pipeline = cogl_pipeline_new (cogl_framebuffer_get_context (framebuffer));
|
|
|
|
cogl_pipeline_set_layer_filters (pipeline, 0,
|
|
COGL_PIPELINE_FILTER_NEAREST,
|
|
COGL_PIPELINE_FILTER_NEAREST);
|
|
cogl_pipeline_set_layer_texture (pipeline, 0,
|
|
cogl_offscreen_get_texture (offscreen));
|
|
cogl_pipeline_set_layer_wrap_mode (pipeline, 0,
|
|
COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE);
|
|
|
|
return pipeline;
|
|
}
|
|
|
|
static void
|
|
clutter_stage_view_ensure_offscreen_blit_pipeline (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
ClutterStageViewClass *view_class =
|
|
CLUTTER_STAGE_VIEW_GET_CLASS (view);
|
|
|
|
g_assert (priv->offscreen != NULL);
|
|
|
|
if (priv->offscreen_pipeline)
|
|
return;
|
|
|
|
priv->offscreen_pipeline =
|
|
clutter_stage_view_create_offscreen_pipeline (priv->offscreen);
|
|
|
|
if (view_class->setup_offscreen_blit_pipeline)
|
|
view_class->setup_offscreen_blit_pipeline (view, priv->offscreen_pipeline);
|
|
}
|
|
|
|
void
|
|
clutter_stage_view_invalidate_offscreen_blit_pipeline (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
g_clear_pointer (&priv->offscreen_pipeline, cogl_object_unref);
|
|
}
|
|
|
|
void
|
|
clutter_stage_view_transform_rect_to_onscreen (ClutterStageView *view,
|
|
const cairo_rectangle_int_t *src_rect,
|
|
int dst_width,
|
|
int dst_height,
|
|
cairo_rectangle_int_t *dst_rect)
|
|
{
|
|
ClutterStageViewClass *view_class = CLUTTER_STAGE_VIEW_GET_CLASS (view);
|
|
|
|
return view_class->transform_rect_to_onscreen (view,
|
|
src_rect,
|
|
dst_width,
|
|
dst_height,
|
|
dst_rect);
|
|
}
|
|
|
|
static void
|
|
paint_transformed_framebuffer (ClutterStageView *view,
|
|
CoglPipeline *pipeline,
|
|
CoglOffscreen *src_framebuffer,
|
|
CoglFramebuffer *dst_framebuffer,
|
|
const cairo_region_t *redraw_clip)
|
|
{
|
|
graphene_matrix_t matrix;
|
|
unsigned int n_rectangles, i;
|
|
int dst_width, dst_height;
|
|
cairo_rectangle_int_t view_layout;
|
|
cairo_rectangle_int_t onscreen_layout;
|
|
float view_scale;
|
|
float *coordinates;
|
|
|
|
dst_width = cogl_framebuffer_get_width (dst_framebuffer);
|
|
dst_height = cogl_framebuffer_get_height (dst_framebuffer);
|
|
clutter_stage_view_get_layout (view, &view_layout);
|
|
clutter_stage_view_transform_rect_to_onscreen (view,
|
|
&(cairo_rectangle_int_t) {
|
|
.width = view_layout.width,
|
|
.height = view_layout.height,
|
|
},
|
|
view_layout.width,
|
|
view_layout.height,
|
|
&onscreen_layout);
|
|
view_scale = clutter_stage_view_get_scale (view);
|
|
|
|
cogl_framebuffer_push_matrix (dst_framebuffer);
|
|
|
|
graphene_matrix_init_translate (&matrix,
|
|
&GRAPHENE_POINT3D_INIT (-dst_width / 2.0,
|
|
-dst_height / 2.0,
|
|
0.f));
|
|
graphene_matrix_scale (&matrix,
|
|
1.0 / (dst_width / 2.0),
|
|
-1.0 / (dst_height / 2.0),
|
|
0.f);
|
|
cogl_framebuffer_set_projection_matrix (dst_framebuffer, &matrix);
|
|
cogl_framebuffer_set_viewport (dst_framebuffer,
|
|
0, 0, dst_width, dst_height);
|
|
|
|
n_rectangles = cairo_region_num_rectangles (redraw_clip);
|
|
coordinates = g_newa (float, 2 * 4 * n_rectangles);
|
|
|
|
for (i = 0; i < n_rectangles; i++)
|
|
{
|
|
cairo_rectangle_int_t src_rect;
|
|
cairo_rectangle_int_t dst_rect;
|
|
|
|
cairo_region_get_rectangle (redraw_clip, i, &src_rect);
|
|
_clutter_util_rectangle_offset (&src_rect,
|
|
-view_layout.x,
|
|
-view_layout.y,
|
|
&src_rect);
|
|
|
|
clutter_stage_view_transform_rect_to_onscreen (view,
|
|
&src_rect,
|
|
onscreen_layout.width,
|
|
onscreen_layout.height,
|
|
&dst_rect);
|
|
|
|
coordinates[i * 8 + 0] = (float) dst_rect.x * view_scale;
|
|
coordinates[i * 8 + 1] = (float) dst_rect.y * view_scale;
|
|
coordinates[i * 8 + 2] = ((float) (dst_rect.x + dst_rect.width) *
|
|
view_scale);
|
|
coordinates[i * 8 + 3] = ((float) (dst_rect.y + dst_rect.height) *
|
|
view_scale);
|
|
|
|
coordinates[i * 8 + 4] = (((float) dst_rect.x / (float) dst_width) *
|
|
view_scale);
|
|
coordinates[i * 8 + 5] = (((float) dst_rect.y / (float) dst_height) *
|
|
view_scale);
|
|
coordinates[i * 8 + 6] = ((float) (dst_rect.x + dst_rect.width) /
|
|
(float) dst_width) * view_scale;
|
|
coordinates[i * 8 + 7] = ((float) (dst_rect.y + dst_rect.height) /
|
|
(float) dst_height) * view_scale;
|
|
}
|
|
|
|
cogl_framebuffer_draw_textured_rectangles (dst_framebuffer,
|
|
pipeline,
|
|
coordinates,
|
|
n_rectangles);
|
|
|
|
cogl_framebuffer_pop_matrix (dst_framebuffer);
|
|
}
|
|
|
|
static gboolean
|
|
is_shadowfb_double_buffered (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
return priv->shadow.dma_buf.handles[0] && priv->shadow.dma_buf.handles[1];
|
|
}
|
|
|
|
static gboolean
|
|
init_dma_buf_shadowfbs (ClutterStageView *view,
|
|
CoglContext *cogl_context,
|
|
int width,
|
|
int height,
|
|
GError **error)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
CoglRenderer *cogl_renderer = cogl_context_get_renderer (cogl_context);
|
|
CoglFramebuffer *initial_shadowfb;
|
|
|
|
if (!cogl_clutter_winsys_has_feature (COGL_WINSYS_FEATURE_BUFFER_AGE))
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
|
"Buffer age not supported");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!COGL_IS_ONSCREEN (priv->framebuffer))
|
|
{
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
|
"Tried to use shadow buffer without onscreen");
|
|
return FALSE;
|
|
}
|
|
|
|
priv->shadow.dma_buf.handles[0] = cogl_renderer_create_dma_buf (cogl_renderer,
|
|
width, height,
|
|
error);
|
|
if (!priv->shadow.dma_buf.handles[0])
|
|
return FALSE;
|
|
|
|
priv->shadow.dma_buf.handles[1] = cogl_renderer_create_dma_buf (cogl_renderer,
|
|
width, height,
|
|
error);
|
|
if (!priv->shadow.dma_buf.handles[1])
|
|
{
|
|
g_clear_pointer (&priv->shadow.dma_buf.handles[0],
|
|
cogl_dma_buf_handle_free);
|
|
return FALSE;
|
|
}
|
|
|
|
priv->shadow.dma_buf.damage_history = clutter_damage_history_new ();
|
|
|
|
initial_shadowfb =
|
|
cogl_dma_buf_handle_get_framebuffer (priv->shadow.dma_buf.handles[0]);
|
|
priv->shadow.framebuffer = COGL_OFFSCREEN (g_object_ref (initial_shadowfb));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static CoglOffscreen *
|
|
create_offscreen_framebuffer (CoglContext *context,
|
|
int width,
|
|
int height,
|
|
GError **error)
|
|
{
|
|
CoglOffscreen *framebuffer;
|
|
CoglTexture2D *texture;
|
|
|
|
texture = cogl_texture_2d_new_with_size (context, width, height);
|
|
cogl_primitive_texture_set_auto_mipmap (COGL_PRIMITIVE_TEXTURE (texture),
|
|
FALSE);
|
|
|
|
if (!cogl_texture_allocate (COGL_TEXTURE (texture), error))
|
|
{
|
|
cogl_object_unref (texture);
|
|
return FALSE;
|
|
}
|
|
|
|
framebuffer = cogl_offscreen_new_with_texture (COGL_TEXTURE (texture));
|
|
cogl_object_unref (texture);
|
|
if (!cogl_framebuffer_allocate (COGL_FRAMEBUFFER (framebuffer), error))
|
|
{
|
|
g_object_unref (framebuffer);
|
|
return FALSE;
|
|
}
|
|
|
|
return framebuffer;
|
|
}
|
|
|
|
static gboolean
|
|
init_fallback_shadowfb (ClutterStageView *view,
|
|
CoglContext *cogl_context,
|
|
int width,
|
|
int height,
|
|
GError **error)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
CoglOffscreen *offscreen;
|
|
|
|
offscreen = create_offscreen_framebuffer (cogl_context, width, height, error);
|
|
if (!offscreen)
|
|
return FALSE;
|
|
|
|
priv->shadow.framebuffer = offscreen;
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
init_shadowfb (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
g_autoptr (GError) error = NULL;
|
|
int width;
|
|
int height;
|
|
CoglContext *cogl_context;
|
|
|
|
width = cogl_framebuffer_get_width (priv->framebuffer);
|
|
height = cogl_framebuffer_get_height (priv->framebuffer);
|
|
cogl_context = cogl_framebuffer_get_context (priv->framebuffer);
|
|
|
|
if (g_strcmp0 (g_getenv ("MUTTER_DEBUG_ENABLE_DOUBLE_SHADOWFB"), "1") == 0)
|
|
{
|
|
if (init_dma_buf_shadowfbs (view, cogl_context, width, height, &error))
|
|
{
|
|
g_message ("Initialized double buffered shadow fb for %s",
|
|
priv->name);
|
|
return;
|
|
}
|
|
|
|
g_warning ("Failed to initialize double buffered shadow fb for %s: %s",
|
|
priv->name, error->message);
|
|
g_clear_error (&error);
|
|
}
|
|
|
|
if (!init_fallback_shadowfb (view, cogl_context, width, height, &error))
|
|
{
|
|
g_warning ("Failed to initialize single buffered shadow fb for %s: %s",
|
|
priv->name, error->message);
|
|
}
|
|
else
|
|
{
|
|
g_message ("Initialized single buffered shadow fb for %s", priv->name);
|
|
}
|
|
}
|
|
|
|
void
|
|
clutter_stage_view_after_paint (ClutterStageView *view,
|
|
cairo_region_t *redraw_clip)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
if (priv->offscreen)
|
|
{
|
|
clutter_stage_view_ensure_offscreen_blit_pipeline (view);
|
|
|
|
if (priv->shadow.framebuffer)
|
|
{
|
|
CoglFramebuffer *shadowfb =
|
|
COGL_FRAMEBUFFER (priv->shadow.framebuffer);
|
|
|
|
paint_transformed_framebuffer (view,
|
|
priv->offscreen_pipeline,
|
|
priv->offscreen,
|
|
shadowfb,
|
|
redraw_clip);
|
|
}
|
|
else
|
|
{
|
|
paint_transformed_framebuffer (view,
|
|
priv->offscreen_pipeline,
|
|
priv->offscreen,
|
|
priv->framebuffer,
|
|
redraw_clip);
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
is_tile_dirty (cairo_rectangle_int_t *tile,
|
|
uint8_t *current_data,
|
|
uint8_t *prev_data,
|
|
int bpp,
|
|
int stride)
|
|
{
|
|
int y;
|
|
|
|
for (y = tile->y; y < tile->y + tile->height; y++)
|
|
{
|
|
if (memcmp (prev_data + y * stride + tile->x * bpp,
|
|
current_data + y * stride + tile->x * bpp,
|
|
tile->width * bpp) != 0)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static int
|
|
flip_dma_buf_idx (int idx)
|
|
{
|
|
return (idx + 1) % 2;
|
|
}
|
|
|
|
static cairo_region_t *
|
|
find_damaged_tiles (ClutterStageView *view,
|
|
const cairo_region_t *damage_region,
|
|
GError **error)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
cairo_region_t *tile_damage_region;
|
|
cairo_rectangle_int_t damage_extents;
|
|
cairo_rectangle_int_t fb_rect;
|
|
int prev_dma_buf_idx;
|
|
CoglDmaBufHandle *prev_dma_buf_handle;
|
|
uint8_t *prev_data;
|
|
int current_dma_buf_idx;
|
|
CoglDmaBufHandle *current_dma_buf_handle;
|
|
uint8_t *current_data;
|
|
int width, height, stride, bpp;
|
|
int tile_x_min, tile_x_max;
|
|
int tile_y_min, tile_y_max;
|
|
int tile_x, tile_y;
|
|
const int tile_size = 16;
|
|
|
|
prev_dma_buf_idx = flip_dma_buf_idx (priv->shadow.dma_buf.current_idx);
|
|
prev_dma_buf_handle = priv->shadow.dma_buf.handles[prev_dma_buf_idx];
|
|
|
|
current_dma_buf_idx = priv->shadow.dma_buf.current_idx;
|
|
current_dma_buf_handle = priv->shadow.dma_buf.handles[current_dma_buf_idx];
|
|
|
|
width = cogl_dma_buf_handle_get_width (current_dma_buf_handle);
|
|
height = cogl_dma_buf_handle_get_height (current_dma_buf_handle);
|
|
stride = cogl_dma_buf_handle_get_stride (current_dma_buf_handle);
|
|
bpp = cogl_dma_buf_handle_get_bpp (current_dma_buf_handle);
|
|
|
|
cogl_framebuffer_finish (COGL_FRAMEBUFFER (priv->shadow.framebuffer));
|
|
|
|
if (!cogl_dma_buf_handle_sync_read_start (prev_dma_buf_handle, error))
|
|
return NULL;
|
|
|
|
if (!cogl_dma_buf_handle_sync_read_start (current_dma_buf_handle, error))
|
|
goto err_sync_read_current;
|
|
|
|
prev_data = cogl_dma_buf_handle_mmap (prev_dma_buf_handle, error);
|
|
if (!prev_data)
|
|
goto err_mmap_prev;
|
|
current_data = cogl_dma_buf_handle_mmap (current_dma_buf_handle, error);
|
|
if (!current_data)
|
|
goto err_mmap_current;
|
|
|
|
fb_rect = (cairo_rectangle_int_t) {
|
|
.width = width,
|
|
.height = height,
|
|
};
|
|
|
|
cairo_region_get_extents (damage_region, &damage_extents);
|
|
|
|
tile_x_min = damage_extents.x / tile_size;
|
|
tile_x_max = ((damage_extents.x + damage_extents.width + tile_size - 1) /
|
|
tile_size);
|
|
tile_y_min = damage_extents.y / tile_size;
|
|
tile_y_max = ((damage_extents.y + damage_extents.height + tile_size - 1) /
|
|
tile_size);
|
|
|
|
tile_damage_region = cairo_region_create ();
|
|
|
|
for (tile_y = tile_y_min; tile_y <= tile_y_max; tile_y++)
|
|
{
|
|
for (tile_x = tile_x_min; tile_x <= tile_x_max; tile_x++)
|
|
{
|
|
cairo_rectangle_int_t tile = {
|
|
.x = tile_x * tile_size,
|
|
.y = tile_y * tile_size,
|
|
.width = tile_size,
|
|
.height = tile_size,
|
|
};
|
|
|
|
if (cairo_region_contains_rectangle (damage_region, &tile) ==
|
|
CAIRO_REGION_OVERLAP_OUT)
|
|
continue;
|
|
|
|
_clutter_util_rectangle_intersection (&tile, &fb_rect, &tile);
|
|
|
|
if (is_tile_dirty (&tile, current_data, prev_data, bpp, stride))
|
|
cairo_region_union_rectangle (tile_damage_region, &tile);
|
|
}
|
|
}
|
|
|
|
if (!cogl_dma_buf_handle_sync_read_end (prev_dma_buf_handle, error))
|
|
{
|
|
g_warning ("Failed to end DMA buffer read synchronization: %s",
|
|
(*error)->message);
|
|
g_clear_error (error);
|
|
}
|
|
|
|
if (!cogl_dma_buf_handle_sync_read_end (current_dma_buf_handle, error))
|
|
{
|
|
g_warning ("Failed to end DMA buffer read synchronization: %s",
|
|
(*error)->message);
|
|
g_clear_error (error);
|
|
}
|
|
|
|
cogl_dma_buf_handle_munmap (prev_dma_buf_handle, prev_data, NULL);
|
|
cogl_dma_buf_handle_munmap (current_dma_buf_handle, current_data, NULL);
|
|
|
|
cairo_region_intersect (tile_damage_region, damage_region);
|
|
|
|
return tile_damage_region;
|
|
|
|
err_mmap_current:
|
|
cogl_dma_buf_handle_munmap (prev_dma_buf_handle, prev_data, NULL);
|
|
|
|
err_mmap_prev:
|
|
cogl_dma_buf_handle_sync_read_end (current_dma_buf_handle, NULL);
|
|
|
|
err_sync_read_current:
|
|
cogl_dma_buf_handle_sync_read_end (prev_dma_buf_handle, NULL);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
swap_dma_buf_framebuffer (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
int next_idx;
|
|
CoglDmaBufHandle *next_dma_buf_handle;
|
|
CoglFramebuffer *next_framebuffer;
|
|
|
|
next_idx = ((priv->shadow.dma_buf.current_idx + 1) %
|
|
G_N_ELEMENTS (priv->shadow.dma_buf.handles));
|
|
priv->shadow.dma_buf.current_idx = next_idx;
|
|
|
|
next_dma_buf_handle = priv->shadow.dma_buf.handles[next_idx];
|
|
next_framebuffer =
|
|
cogl_dma_buf_handle_get_framebuffer (next_dma_buf_handle);
|
|
g_clear_object (&priv->shadow.framebuffer);
|
|
priv->shadow.framebuffer = COGL_OFFSCREEN (g_object_ref (next_framebuffer));
|
|
}
|
|
|
|
static void
|
|
copy_shadowfb_to_onscreen (ClutterStageView *view,
|
|
const cairo_region_t *swap_region)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
ClutterDamageHistory *damage_history = priv->shadow.dma_buf.damage_history;
|
|
cairo_region_t *damage_region;
|
|
int age;
|
|
int i;
|
|
|
|
if (cairo_region_is_empty (swap_region))
|
|
{
|
|
cairo_rectangle_int_t full_damage = {
|
|
.width = cogl_framebuffer_get_width (priv->framebuffer),
|
|
.height = cogl_framebuffer_get_height (priv->framebuffer),
|
|
};
|
|
damage_region = cairo_region_create_rectangle (&full_damage);
|
|
}
|
|
else
|
|
{
|
|
damage_region = cairo_region_copy (swap_region);
|
|
}
|
|
|
|
if (is_shadowfb_double_buffered (view))
|
|
{
|
|
CoglOnscreen *onscreen = COGL_ONSCREEN (priv->framebuffer);
|
|
cairo_region_t *changed_region;
|
|
|
|
if (cogl_onscreen_get_frame_counter (onscreen) >= 1)
|
|
{
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
changed_region = find_damaged_tiles (view, damage_region, &error);
|
|
if (!changed_region)
|
|
{
|
|
int other_dma_buf_idx;
|
|
|
|
g_warning ("Disabling actual damage detection: %s",
|
|
error->message);
|
|
|
|
other_dma_buf_idx =
|
|
flip_dma_buf_idx (priv->shadow.dma_buf.current_idx);
|
|
g_clear_pointer (&priv->shadow.dma_buf.handles[other_dma_buf_idx],
|
|
cogl_dma_buf_handle_free);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
changed_region = cairo_region_copy (damage_region);
|
|
}
|
|
|
|
if (changed_region)
|
|
{
|
|
int buffer_age;
|
|
|
|
clutter_damage_history_record (damage_history, changed_region);
|
|
|
|
buffer_age = cogl_onscreen_get_buffer_age (onscreen);
|
|
if (clutter_damage_history_is_age_valid (damage_history, buffer_age))
|
|
{
|
|
for (age = 1; age <= buffer_age; age++)
|
|
{
|
|
const cairo_region_t *old_damage;
|
|
|
|
old_damage = clutter_damage_history_lookup (damage_history, age);
|
|
cairo_region_union (changed_region, old_damage);
|
|
}
|
|
|
|
cairo_region_destroy (damage_region);
|
|
damage_region = g_steal_pointer (&changed_region);
|
|
}
|
|
else
|
|
{
|
|
cairo_region_destroy (changed_region);
|
|
}
|
|
|
|
clutter_damage_history_step (damage_history);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < cairo_region_num_rectangles (damage_region); i++)
|
|
{
|
|
CoglFramebuffer *shadowfb = COGL_FRAMEBUFFER (priv->shadow.framebuffer);
|
|
g_autoptr (GError) error = NULL;
|
|
cairo_rectangle_int_t rect;
|
|
|
|
cairo_region_get_rectangle (damage_region, i, &rect);
|
|
|
|
if (!cogl_blit_framebuffer (shadowfb,
|
|
priv->framebuffer,
|
|
rect.x, rect.y,
|
|
rect.x, rect.y,
|
|
rect.width, rect.height,
|
|
&error))
|
|
{
|
|
g_warning ("Failed to blit shadow buffer: %s", error->message);
|
|
cairo_region_destroy (damage_region);
|
|
return;
|
|
}
|
|
}
|
|
|
|
cairo_region_destroy (damage_region);
|
|
|
|
if (is_shadowfb_double_buffered (view))
|
|
swap_dma_buf_framebuffer (view);
|
|
}
|
|
|
|
void
|
|
clutter_stage_view_before_swap_buffer (ClutterStageView *view,
|
|
const cairo_region_t *swap_region)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
if (priv->shadow.framebuffer)
|
|
copy_shadowfb_to_onscreen (view, swap_region);
|
|
}
|
|
|
|
float
|
|
clutter_stage_view_get_scale (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
return priv->scale;
|
|
}
|
|
|
|
typedef void (*FrontBufferCallback) (CoglFramebuffer *framebuffer,
|
|
gconstpointer user_data);
|
|
|
|
static void
|
|
clutter_stage_view_foreach_front_buffer (ClutterStageView *view,
|
|
FrontBufferCallback callback,
|
|
gconstpointer user_data)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
if (priv->offscreen)
|
|
{
|
|
callback (COGL_FRAMEBUFFER (priv->offscreen), user_data);
|
|
}
|
|
else if (priv->shadow.framebuffer)
|
|
{
|
|
if (is_shadowfb_double_buffered (view))
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (priv->shadow.dma_buf.handles); i++)
|
|
{
|
|
CoglDmaBufHandle *handle = priv->shadow.dma_buf.handles[i];
|
|
CoglFramebuffer *framebuffer =
|
|
cogl_dma_buf_handle_get_framebuffer (handle);
|
|
|
|
callback (framebuffer, user_data);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
callback (COGL_FRAMEBUFFER (priv->shadow.framebuffer), user_data);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
callback (priv->framebuffer, user_data);
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
clutter_stage_view_is_dirty_viewport (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
return priv->dirty_viewport;
|
|
}
|
|
|
|
void
|
|
clutter_stage_view_invalidate_viewport (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
priv->dirty_viewport = TRUE;
|
|
}
|
|
|
|
static void
|
|
set_framebuffer_viewport (CoglFramebuffer *framebuffer,
|
|
gconstpointer user_data)
|
|
{
|
|
const graphene_rect_t *rect = user_data;
|
|
|
|
cogl_framebuffer_set_viewport (framebuffer,
|
|
rect->origin.x,
|
|
rect->origin.y,
|
|
rect->size.width,
|
|
rect->size.height);
|
|
}
|
|
|
|
void
|
|
clutter_stage_view_set_viewport (ClutterStageView *view,
|
|
float x,
|
|
float y,
|
|
float width,
|
|
float height)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
graphene_rect_t rect;
|
|
|
|
priv->dirty_viewport = FALSE;
|
|
|
|
rect = (graphene_rect_t) {
|
|
.origin = { .x = x, .y = y },
|
|
.size = { .width = width, .height = height },
|
|
};
|
|
clutter_stage_view_foreach_front_buffer (view,
|
|
set_framebuffer_viewport,
|
|
&rect);
|
|
}
|
|
|
|
gboolean
|
|
clutter_stage_view_is_dirty_projection (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
return priv->dirty_projection;
|
|
}
|
|
|
|
static void
|
|
set_framebuffer_projection_matrix (CoglFramebuffer *framebuffer,
|
|
gconstpointer user_data)
|
|
{
|
|
cogl_framebuffer_set_projection_matrix (framebuffer, user_data);
|
|
}
|
|
|
|
void
|
|
clutter_stage_view_invalidate_projection (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
priv->dirty_projection = TRUE;
|
|
}
|
|
|
|
void
|
|
clutter_stage_view_set_projection (ClutterStageView *view,
|
|
const graphene_matrix_t *matrix)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
priv->dirty_projection = FALSE;
|
|
clutter_stage_view_foreach_front_buffer (view,
|
|
set_framebuffer_projection_matrix,
|
|
matrix);
|
|
}
|
|
|
|
void
|
|
clutter_stage_view_get_offscreen_transformation_matrix (ClutterStageView *view,
|
|
graphene_matrix_t *matrix)
|
|
{
|
|
ClutterStageViewClass *view_class = CLUTTER_STAGE_VIEW_GET_CLASS (view);
|
|
|
|
view_class->get_offscreen_transformation_matrix (view, matrix);
|
|
}
|
|
|
|
void
|
|
clutter_stage_view_add_redraw_clip (ClutterStageView *view,
|
|
const cairo_rectangle_int_t *clip)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
if (priv->has_redraw_clip && !priv->redraw_clip)
|
|
return;
|
|
|
|
if (!clip)
|
|
{
|
|
g_clear_pointer (&priv->redraw_clip, cairo_region_destroy);
|
|
priv->has_redraw_clip = TRUE;
|
|
return;
|
|
}
|
|
|
|
if (clip->width == 0 || clip->height == 0)
|
|
return;
|
|
|
|
if (!priv->redraw_clip)
|
|
{
|
|
if (!clutter_util_rectangle_equal (&priv->layout, clip))
|
|
priv->redraw_clip = cairo_region_create_rectangle (clip);
|
|
}
|
|
else
|
|
{
|
|
cairo_region_union_rectangle (priv->redraw_clip, clip);
|
|
|
|
if (cairo_region_num_rectangles (priv->redraw_clip) == 1)
|
|
{
|
|
cairo_rectangle_int_t redraw_clip_extents;
|
|
|
|
cairo_region_get_extents (priv->redraw_clip, &redraw_clip_extents);
|
|
if (clutter_util_rectangle_equal (&priv->layout, &redraw_clip_extents))
|
|
g_clear_pointer (&priv->redraw_clip, cairo_region_destroy);
|
|
}
|
|
}
|
|
|
|
priv->has_redraw_clip = TRUE;
|
|
}
|
|
|
|
gboolean
|
|
clutter_stage_view_has_redraw_clip (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
return priv->has_redraw_clip;
|
|
}
|
|
|
|
gboolean
|
|
clutter_stage_view_has_full_redraw_clip (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
return priv->has_redraw_clip && !priv->redraw_clip;
|
|
}
|
|
|
|
const cairo_region_t *
|
|
clutter_stage_view_peek_redraw_clip (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
return priv->redraw_clip;
|
|
}
|
|
|
|
cairo_region_t *
|
|
clutter_stage_view_take_redraw_clip (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
priv->has_redraw_clip = FALSE;
|
|
|
|
return g_steal_pointer (&priv->redraw_clip);
|
|
}
|
|
|
|
static void
|
|
clutter_stage_default_get_offscreen_transformation_matrix (ClutterStageView *view,
|
|
graphene_matrix_t *matrix)
|
|
{
|
|
graphene_matrix_init_identity (matrix);
|
|
}
|
|
|
|
void
|
|
clutter_stage_view_assign_next_scanout (ClutterStageView *view,
|
|
CoglScanout *scanout)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
g_set_object (&priv->next_scanout, scanout);
|
|
}
|
|
|
|
CoglScanout *
|
|
clutter_stage_view_take_scanout (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
return g_steal_pointer (&priv->next_scanout);
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_view_peek_scanout: (skip)
|
|
*/
|
|
CoglScanout *
|
|
clutter_stage_view_peek_scanout (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
return priv->next_scanout;
|
|
}
|
|
|
|
void
|
|
clutter_stage_view_schedule_update (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
clutter_frame_clock_schedule_update (priv->frame_clock);
|
|
}
|
|
|
|
float
|
|
clutter_stage_view_get_refresh_rate (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
return priv->refresh_rate;
|
|
}
|
|
|
|
/**
|
|
* clutter_stage_view_get_frame_clock: (skip)
|
|
*/
|
|
ClutterFrameClock *
|
|
clutter_stage_view_get_frame_clock (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
return priv->frame_clock;
|
|
}
|
|
|
|
static void
|
|
handle_frame_clock_before_frame (ClutterFrameClock *frame_clock,
|
|
int64_t frame_count,
|
|
gpointer user_data)
|
|
{
|
|
ClutterStageView *view = user_data;
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
_clutter_stage_process_queued_events (priv->stage);
|
|
}
|
|
|
|
static void
|
|
begin_frame_timing_measurement (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
priv->frame_timings.began_draw_time_us = g_get_monotonic_time ();
|
|
}
|
|
|
|
static void
|
|
end_frame_timing_measurement (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
int64_t now_us = g_get_monotonic_time ();
|
|
int64_t draw_time_us;
|
|
|
|
draw_time_us = now_us - priv->frame_timings.began_draw_time_us;
|
|
|
|
priv->frame_timings.frame_count++;
|
|
priv->frame_timings.cumulative_draw_time_us += draw_time_us;
|
|
if (draw_time_us > priv->frame_timings.worst_draw_time_us)
|
|
priv->frame_timings.worst_draw_time_us = draw_time_us;
|
|
|
|
if (priv->frame_timings.frame_count && priv->frame_timings.last_print_time_us)
|
|
{
|
|
float time_since_last_print_s;
|
|
|
|
time_since_last_print_s =
|
|
(now_us - priv->frame_timings.last_print_time_us) /
|
|
(float) G_USEC_PER_SEC;
|
|
|
|
if (time_since_last_print_s >= 1.0)
|
|
{
|
|
float avg_fps, avg_draw_time_ms, worst_draw_time_ms;
|
|
|
|
avg_fps = priv->frame_timings.frame_count / time_since_last_print_s;
|
|
|
|
avg_draw_time_ms =
|
|
(priv->frame_timings.cumulative_draw_time_us / 1000.0) /
|
|
priv->frame_timings.frame_count;
|
|
|
|
worst_draw_time_ms = priv->frame_timings.worst_draw_time_us / 1000.0;
|
|
|
|
g_print ("*** %s frame timings over %.01fs: "
|
|
"%.02f FPS, average: %.01fms, peak: %.01fms\n",
|
|
priv->name,
|
|
time_since_last_print_s,
|
|
avg_fps,
|
|
avg_draw_time_ms,
|
|
worst_draw_time_ms);
|
|
|
|
priv->frame_timings.frame_count = 0;
|
|
priv->frame_timings.cumulative_draw_time_us = 0;
|
|
priv->frame_timings.worst_draw_time_us = 0;
|
|
priv->frame_timings.last_print_time_us = now_us;
|
|
}
|
|
}
|
|
else if (!priv->frame_timings.last_print_time_us)
|
|
{
|
|
priv->frame_timings.last_print_time_us = now_us;
|
|
}
|
|
}
|
|
|
|
static ClutterFrameResult
|
|
handle_frame_clock_frame (ClutterFrameClock *frame_clock,
|
|
int64_t frame_count,
|
|
int64_t time_us,
|
|
gpointer user_data)
|
|
{
|
|
ClutterStageView *view = user_data;
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
ClutterStage *stage = priv->stage;
|
|
ClutterStageWindow *stage_window = _clutter_stage_get_window (stage);
|
|
g_autoptr (GSList) devices = NULL;
|
|
ClutterFrame frame;
|
|
|
|
if (CLUTTER_ACTOR_IN_DESTRUCTION (stage))
|
|
return CLUTTER_FRAME_RESULT_IDLE;
|
|
|
|
if (!clutter_actor_is_realized (CLUTTER_ACTOR (stage)))
|
|
return CLUTTER_FRAME_RESULT_IDLE;
|
|
|
|
if (!clutter_actor_is_mapped (CLUTTER_ACTOR (stage)))
|
|
return CLUTTER_FRAME_RESULT_IDLE;
|
|
|
|
if (_clutter_context_get_show_fps ())
|
|
begin_frame_timing_measurement (view);
|
|
|
|
_clutter_run_repaint_functions (CLUTTER_REPAINT_FLAGS_PRE_PAINT);
|
|
clutter_stage_emit_before_update (stage, view);
|
|
|
|
clutter_stage_maybe_relayout (CLUTTER_ACTOR (stage));
|
|
clutter_stage_maybe_finish_queue_redraws (stage);
|
|
|
|
clutter_stage_finish_layout (stage);
|
|
|
|
devices = clutter_stage_find_updated_devices (stage);
|
|
|
|
frame = CLUTTER_FRAME_INIT;
|
|
|
|
_clutter_stage_window_prepare_frame (stage_window, view, &frame);
|
|
|
|
if (clutter_stage_view_has_redraw_clip (view))
|
|
{
|
|
clutter_stage_emit_before_paint (stage, view);
|
|
|
|
_clutter_stage_window_redraw_view (stage_window, view, &frame);
|
|
|
|
clutter_frame_clock_record_flip_time (frame_clock,
|
|
g_get_monotonic_time ());
|
|
|
|
clutter_stage_emit_after_paint (stage, view);
|
|
|
|
if (_clutter_context_get_show_fps ())
|
|
end_frame_timing_measurement (view);
|
|
}
|
|
|
|
_clutter_stage_window_finish_frame (stage_window, view, &frame);
|
|
|
|
clutter_stage_update_devices (stage, devices);
|
|
|
|
_clutter_run_repaint_functions (CLUTTER_REPAINT_FLAGS_POST_PAINT);
|
|
clutter_stage_emit_after_update (stage, view);
|
|
|
|
return clutter_frame_get_result (&frame);
|
|
}
|
|
|
|
static const ClutterFrameListenerIface frame_clock_listener_iface = {
|
|
.before_frame = handle_frame_clock_before_frame,
|
|
.frame = handle_frame_clock_frame,
|
|
};
|
|
|
|
void
|
|
clutter_stage_view_notify_presented (ClutterStageView *view,
|
|
ClutterFrameInfo *frame_info)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
clutter_stage_presented (priv->stage, view, frame_info);
|
|
clutter_frame_clock_notify_presented (priv->frame_clock, frame_info);
|
|
}
|
|
|
|
void
|
|
clutter_stage_view_notify_ready (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
clutter_frame_clock_notify_ready (priv->frame_clock);
|
|
}
|
|
|
|
static void
|
|
sanity_check_framebuffer (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
G_GNUC_UNUSED int fb_width, fb_height;
|
|
|
|
fb_width = cogl_framebuffer_get_width (priv->framebuffer);
|
|
fb_height = cogl_framebuffer_get_height (priv->framebuffer);
|
|
|
|
g_warn_if_fail (fabsf (roundf (fb_width / priv->scale) -
|
|
fb_width / priv->scale) < FLT_EPSILON);
|
|
g_warn_if_fail (fabsf (roundf (fb_height / priv->scale) -
|
|
fb_height / priv->scale) < FLT_EPSILON);
|
|
}
|
|
|
|
static void
|
|
clutter_stage_view_set_framebuffer (ClutterStageView *view,
|
|
CoglFramebuffer *framebuffer)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
g_warn_if_fail (!priv->framebuffer);
|
|
if (framebuffer)
|
|
{
|
|
priv->framebuffer = g_object_ref (framebuffer);
|
|
sanity_check_framebuffer (view);
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_stage_view_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ClutterStageView *view = CLUTTER_STAGE_VIEW (object);
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_NAME:
|
|
g_value_set_string (value, priv->name);
|
|
break;
|
|
case PROP_STAGE:
|
|
g_value_set_boxed (value, &priv->stage);
|
|
break;
|
|
case PROP_LAYOUT:
|
|
g_value_set_boxed (value, &priv->layout);
|
|
break;
|
|
case PROP_FRAMEBUFFER:
|
|
g_value_set_object (value, priv->framebuffer);
|
|
break;
|
|
case PROP_OFFSCREEN:
|
|
g_value_set_object (value, priv->offscreen);
|
|
break;
|
|
case PROP_USE_SHADOWFB:
|
|
g_value_set_boolean (value, priv->use_shadowfb);
|
|
break;
|
|
case PROP_SCALE:
|
|
g_value_set_float (value, priv->scale);
|
|
break;
|
|
case PROP_REFRESH_RATE:
|
|
g_value_set_float (value, priv->refresh_rate);
|
|
break;
|
|
case PROP_VBLANK_DURATION_US:
|
|
g_value_set_int64 (value, priv->vblank_duration_us);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_stage_view_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ClutterStageView *view = CLUTTER_STAGE_VIEW (object);
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
cairo_rectangle_int_t *layout;
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_NAME:
|
|
priv->name = g_value_dup_string (value);
|
|
break;
|
|
case PROP_STAGE:
|
|
priv->stage = g_value_get_object (value);
|
|
break;
|
|
case PROP_LAYOUT:
|
|
layout = g_value_get_boxed (value);
|
|
priv->layout = *layout;
|
|
break;
|
|
case PROP_FRAMEBUFFER:
|
|
clutter_stage_view_set_framebuffer (view, g_value_get_object (value));
|
|
break;
|
|
case PROP_OFFSCREEN:
|
|
priv->offscreen = g_value_dup_object (value);
|
|
break;
|
|
case PROP_USE_SHADOWFB:
|
|
priv->use_shadowfb = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_SCALE:
|
|
priv->scale = g_value_get_float (value);
|
|
break;
|
|
case PROP_REFRESH_RATE:
|
|
priv->refresh_rate = g_value_get_float (value);
|
|
break;
|
|
case PROP_VBLANK_DURATION_US:
|
|
priv->vblank_duration_us = g_value_get_int64 (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_stage_view_constructed (GObject *object)
|
|
{
|
|
ClutterStageView *view = CLUTTER_STAGE_VIEW (object);
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
if (priv->use_shadowfb)
|
|
init_shadowfb (view);
|
|
|
|
priv->frame_clock = clutter_frame_clock_new (priv->refresh_rate,
|
|
priv->vblank_duration_us,
|
|
&frame_clock_listener_iface,
|
|
view);
|
|
|
|
clutter_stage_view_add_redraw_clip (view, NULL);
|
|
clutter_stage_view_schedule_update (view);
|
|
|
|
G_OBJECT_CLASS (clutter_stage_view_parent_class)->constructed (object);
|
|
}
|
|
|
|
static void
|
|
clutter_stage_view_dispose (GObject *object)
|
|
{
|
|
ClutterStageView *view = CLUTTER_STAGE_VIEW (object);
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
int i;
|
|
|
|
g_clear_pointer (&priv->name, g_free);
|
|
|
|
g_clear_object (&priv->shadow.framebuffer);
|
|
for (i = 0; i < G_N_ELEMENTS (priv->shadow.dma_buf.handles); i++)
|
|
{
|
|
g_clear_pointer (&priv->shadow.dma_buf.handles[i],
|
|
cogl_dma_buf_handle_free);
|
|
}
|
|
g_clear_pointer (&priv->shadow.dma_buf.damage_history,
|
|
clutter_damage_history_free);
|
|
|
|
g_clear_object (&priv->offscreen);
|
|
g_clear_pointer (&priv->offscreen_pipeline, cogl_object_unref);
|
|
g_clear_pointer (&priv->redraw_clip, cairo_region_destroy);
|
|
g_clear_pointer (&priv->frame_clock, clutter_frame_clock_destroy);
|
|
|
|
G_OBJECT_CLASS (clutter_stage_view_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
clutter_stage_view_finalize (GObject *object)
|
|
{
|
|
ClutterStageView *view = CLUTTER_STAGE_VIEW (object);
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
g_clear_object (&priv->framebuffer);
|
|
|
|
G_OBJECT_CLASS (clutter_stage_view_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
clutter_stage_view_init (ClutterStageView *view)
|
|
{
|
|
ClutterStageViewPrivate *priv =
|
|
clutter_stage_view_get_instance_private (view);
|
|
|
|
priv->dirty_viewport = TRUE;
|
|
priv->dirty_projection = TRUE;
|
|
priv->scale = 1.0;
|
|
priv->refresh_rate = 60.0;
|
|
}
|
|
|
|
static void
|
|
clutter_stage_view_class_init (ClutterStageViewClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
klass->get_offscreen_transformation_matrix =
|
|
clutter_stage_default_get_offscreen_transformation_matrix;
|
|
|
|
object_class->get_property = clutter_stage_view_get_property;
|
|
object_class->set_property = clutter_stage_view_set_property;
|
|
object_class->constructed = clutter_stage_view_constructed;
|
|
object_class->dispose = clutter_stage_view_dispose;
|
|
object_class->finalize = clutter_stage_view_finalize;
|
|
|
|
obj_props[PROP_NAME] =
|
|
g_param_spec_string ("name",
|
|
"Name",
|
|
"Name of view",
|
|
NULL,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_STRINGS);
|
|
|
|
obj_props[PROP_STAGE] =
|
|
g_param_spec_object ("stage",
|
|
"The stage",
|
|
"The ClutterStage",
|
|
CLUTTER_TYPE_STAGE,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_STRINGS);
|
|
|
|
obj_props[PROP_LAYOUT] =
|
|
g_param_spec_boxed ("layout",
|
|
"View layout",
|
|
"The view layout on the screen",
|
|
CAIRO_GOBJECT_TYPE_RECTANGLE_INT,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT |
|
|
G_PARAM_STATIC_STRINGS);
|
|
|
|
obj_props[PROP_FRAMEBUFFER] =
|
|
g_param_spec_object ("framebuffer",
|
|
"View framebuffer",
|
|
"The front buffer of the view",
|
|
COGL_TYPE_FRAMEBUFFER,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT |
|
|
G_PARAM_STATIC_STRINGS);
|
|
|
|
obj_props[PROP_OFFSCREEN] =
|
|
g_param_spec_object ("offscreen",
|
|
"Offscreen buffer",
|
|
"Framebuffer used as intermediate buffer",
|
|
COGL_TYPE_OFFSCREEN,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_STRINGS);
|
|
|
|
obj_props[PROP_USE_SHADOWFB] =
|
|
g_param_spec_boolean ("use-shadowfb",
|
|
"Use shadowfb",
|
|
"Whether to use one or more shadow framebuffers",
|
|
FALSE,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_STRINGS);
|
|
|
|
obj_props[PROP_SCALE] =
|
|
g_param_spec_float ("scale",
|
|
"View scale",
|
|
"The view scale",
|
|
0.5, G_MAXFLOAT, 1.0,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT |
|
|
G_PARAM_STATIC_STRINGS);
|
|
|
|
obj_props[PROP_REFRESH_RATE] =
|
|
g_param_spec_float ("refresh-rate",
|
|
"Refresh rate",
|
|
"Update refresh rate",
|
|
1.0, G_MAXFLOAT, 60.0,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT |
|
|
G_PARAM_STATIC_STRINGS);
|
|
|
|
obj_props[PROP_VBLANK_DURATION_US] =
|
|
g_param_spec_int64 ("vblank-duration-us",
|
|
"Vblank duration (µs)",
|
|
"The vblank duration",
|
|
0, G_MAXINT64, 0,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_STRINGS);
|
|
|
|
g_object_class_install_properties (object_class, PROP_LAST, obj_props);
|
|
}
|