1525 lines
46 KiB
C
1525 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,
|
|
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);
|
|
}
|