forked from AuroraMiddleware/gtk
359bc7695c
Some of the flags got lost in the meson transition or were demoted from error flags to warning flags. This commit reintroduces them. It also includes fixes for the code that had warnings with those flags. The big one being -Wshadow.
2974 lines
100 KiB
C
2974 lines
100 KiB
C
#include "config.h"
|
|
|
|
#include "gskglrendererprivate.h"
|
|
|
|
#include "gskdebugprivate.h"
|
|
#include "gskenums.h"
|
|
#include "gskgldriverprivate.h"
|
|
#include "gskglprofilerprivate.h"
|
|
#include "gskprofilerprivate.h"
|
|
#include "gskrendererprivate.h"
|
|
#include "gskrendernodeprivate.h"
|
|
#include "gskshaderbuilderprivate.h"
|
|
#include "gskglglyphcacheprivate.h"
|
|
#include "gskglrenderopsprivate.h"
|
|
#include "gskcairoblurprivate.h"
|
|
#include "gskglshadowcacheprivate.h"
|
|
#include "gskglnodesampleprivate.h"
|
|
|
|
#include "gskprivate.h"
|
|
|
|
#include "gdk/gdkgltextureprivate.h"
|
|
|
|
#include <epoxy/gl.h>
|
|
#include <cairo-ft.h>
|
|
|
|
#define SHADER_VERSION_GLES 100
|
|
#define SHADER_VERSION_GL2_LEGACY 110
|
|
#define SHADER_VERSION_GL3_LEGACY 130
|
|
#define SHADER_VERSION_GL3 150
|
|
|
|
#define ORTHO_NEAR_PLANE -10000
|
|
#define ORTHO_FAR_PLANE 10000
|
|
|
|
#define HIGHLIGHT_FALLBACK 0
|
|
#define DEBUG_OPS 0
|
|
|
|
#define SHADOW_EXTRA_SIZE 4
|
|
|
|
#if DEBUG_OPS
|
|
#define OP_PRINT(format, ...) g_print(format, ## __VA_ARGS__)
|
|
#else
|
|
#define OP_PRINT(format, ...)
|
|
#endif
|
|
|
|
#define INIT_PROGRAM_UNIFORM_LOCATION(program_name, uniform_basename) \
|
|
G_STMT_START{\
|
|
self->program_name ## _program.program_name.uniform_basename ## _location = \
|
|
glGetUniformLocation(self->program_name ## _program.id, "u_" #uniform_basename);\
|
|
g_assert_cmpint (self->program_name ## _program.program_name.uniform_basename ## _location, >, -1); \
|
|
}G_STMT_END
|
|
|
|
#define INIT_COMMON_UNIFORM_LOCATION(program_ptr, uniform_basename) \
|
|
G_STMT_START{\
|
|
program_ptr->uniform_basename ## _location = \
|
|
glGetUniformLocation(program_ptr->id, "u_" #uniform_basename);\
|
|
}G_STMT_END
|
|
|
|
typedef enum
|
|
{
|
|
FORCE_OFFSCREEN = 1 << 0,
|
|
RESET_CLIP = 1 << 1,
|
|
RESET_OPACITY = 1 << 2
|
|
} OffscreenFlags;
|
|
|
|
static void G_GNUC_UNUSED
|
|
print_render_node_tree (GskRenderNode *root, int level)
|
|
{
|
|
#define INDENT 4
|
|
const guint type = gsk_render_node_get_node_type (root);
|
|
guint i;
|
|
|
|
switch (type)
|
|
{
|
|
case GSK_CONTAINER_NODE:
|
|
g_print ("%*s Container\n", level * INDENT, " ");
|
|
for (i = 0; i < gsk_container_node_get_n_children (root); i++)
|
|
print_render_node_tree (gsk_container_node_get_child (root, i), level + 1);
|
|
break;
|
|
|
|
case GSK_OFFSET_NODE:
|
|
g_print ("%*s Offset\n", level * INDENT, " ");
|
|
print_render_node_tree (gsk_offset_node_get_child (root), level + 1);
|
|
break;
|
|
|
|
case GSK_TRANSFORM_NODE:
|
|
g_print ("%*s Transform\n", level * INDENT, " ");
|
|
print_render_node_tree (gsk_transform_node_get_child (root), level + 1);
|
|
break;
|
|
|
|
case GSK_COLOR_MATRIX_NODE:
|
|
g_print ("%*s Color Matrix\n", level * INDENT, " ");
|
|
print_render_node_tree (gsk_color_matrix_node_get_child (root), level + 1);
|
|
break;
|
|
|
|
case GSK_CROSS_FADE_NODE:
|
|
g_print ("%*s Crossfade(%.2f)\n", level * INDENT, " ",
|
|
gsk_cross_fade_node_get_progress (root));
|
|
print_render_node_tree (gsk_cross_fade_node_get_start_child (root), level + 1);
|
|
print_render_node_tree (gsk_cross_fade_node_get_end_child (root), level + 1);
|
|
break;
|
|
|
|
case GSK_TEXT_NODE:
|
|
g_print ("%*s Text\n", level * INDENT, " ");
|
|
break;
|
|
|
|
case GSK_SHADOW_NODE:
|
|
g_print ("%*s Shadow\n", level * INDENT, " ");
|
|
print_render_node_tree (gsk_shadow_node_get_child (root), level + 1);
|
|
break;
|
|
|
|
case GSK_TEXTURE_NODE:
|
|
g_print ("%*s Texture %p\n", level * INDENT, " ", gsk_texture_node_get_texture (root));
|
|
break;
|
|
|
|
case GSK_DEBUG_NODE:
|
|
g_print ("%*s Debug: %s\n", level * INDENT, " ", gsk_debug_node_get_message (root));
|
|
print_render_node_tree (gsk_debug_node_get_child (root), level + 1);
|
|
break;
|
|
|
|
case GSK_CLIP_NODE:
|
|
g_print ("%*s Clip (%f, %f, %f, %f):\n", level * INDENT, " ",
|
|
root->bounds.origin.x, root->bounds.origin.y, root->bounds.size.width, root->bounds.size.height);
|
|
print_render_node_tree (gsk_clip_node_get_child (root), level + 1);
|
|
break;
|
|
|
|
default:
|
|
g_print ("%*s %s\n", level * INDENT, " ", root->node_class->type_name);
|
|
}
|
|
|
|
#undef INDENT
|
|
}
|
|
|
|
|
|
static void G_GNUC_UNUSED
|
|
dump_framebuffer (const char *filename, int w, int h)
|
|
{
|
|
int stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, w);
|
|
guchar *data = g_malloc (h * stride);
|
|
cairo_surface_t *s;
|
|
|
|
glReadPixels (0, 0, w, h, GL_BGRA, GL_UNSIGNED_BYTE, data);
|
|
s = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_ARGB32, w, h, stride);
|
|
cairo_surface_write_to_png (s, filename);
|
|
|
|
cairo_surface_destroy (s);
|
|
g_free (data);
|
|
}
|
|
|
|
static gboolean
|
|
font_has_color_glyphs (const PangoFont *font)
|
|
{
|
|
cairo_scaled_font_t *scaled_font;
|
|
gboolean has_color = FALSE;
|
|
|
|
scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont *)font);
|
|
if (cairo_scaled_font_get_type (scaled_font) == CAIRO_FONT_TYPE_FT)
|
|
{
|
|
FT_Face ft_face = cairo_ft_scaled_font_lock_face (scaled_font);
|
|
has_color = (FT_HAS_COLOR (ft_face) != 0);
|
|
cairo_ft_scaled_font_unlock_face (scaled_font);
|
|
}
|
|
|
|
return has_color;
|
|
}
|
|
|
|
static void
|
|
get_gl_scaling_filters (GskRenderNode *node,
|
|
int *min_filter_r,
|
|
int *mag_filter_r)
|
|
{
|
|
*min_filter_r = GL_LINEAR;
|
|
*mag_filter_r = GL_LINEAR;
|
|
}
|
|
|
|
static inline void
|
|
rgba_to_float (const GdkRGBA *c,
|
|
float *f)
|
|
{
|
|
f[0] = c->red;
|
|
f[1] = c->green;
|
|
f[2] = c->blue;
|
|
f[3] = c->alpha;
|
|
}
|
|
|
|
static inline void
|
|
sort_border_sides (const GdkRGBA *colors,
|
|
int *indices)
|
|
{
|
|
gboolean done[4] = {0, 0, 0, 0};
|
|
int i, k;
|
|
int cur = 0;
|
|
|
|
for (i = 0; i < 3; i ++)
|
|
{
|
|
if (done[i])
|
|
continue;
|
|
|
|
indices[cur] = i;
|
|
done[i] = TRUE;
|
|
cur ++;
|
|
|
|
for (k = i + 1; k < 4; k ++)
|
|
{
|
|
if (gdk_rgba_equal (&colors[k], &colors[i]))
|
|
{
|
|
indices[cur] = k;
|
|
done[k] = TRUE;
|
|
cur ++;
|
|
}
|
|
}
|
|
|
|
if (cur >= 4)
|
|
break;
|
|
}
|
|
}
|
|
|
|
static inline gboolean
|
|
color_matrix_modifies_alpha (GskRenderNode *node)
|
|
{
|
|
const graphene_matrix_t *matrix = gsk_color_matrix_node_peek_color_matrix (node);
|
|
const graphene_vec4_t *offset = gsk_color_matrix_node_peek_color_offset (node);
|
|
graphene_vec4_t row3;
|
|
|
|
if (graphene_vec4_get_w (offset) != 0.0f)
|
|
return TRUE;
|
|
|
|
graphene_matrix_get_row (matrix, 3, &row3);
|
|
|
|
return !graphene_vec4_equal (graphene_vec4_w_axis (), &row3);
|
|
}
|
|
|
|
static inline void
|
|
gsk_rounded_rect_shrink_to_minimum (GskRoundedRect *self)
|
|
{
|
|
self->bounds.size.width = ceilf (MAX (MAX (self->corner[0].width, self->corner[1].width),
|
|
MAX (self->corner[2].width, self->corner[3].width)) * 2);
|
|
self->bounds.size.height = ceilf (MAX (MAX (self->corner[0].height, self->corner[1].height),
|
|
MAX (self->corner[2].height, self->corner[3].height)) * 2);
|
|
}
|
|
|
|
static inline gboolean
|
|
node_supports_transform (GskRenderNode *node)
|
|
{
|
|
/* Some nodes can't handle non-trivial transforms without being
|
|
* rendered to a texture (e.g. rotated clips, etc.). Some however
|
|
* work just fine, mostly because they already draw their child
|
|
* to a texture and just render the texture manipulated in some
|
|
* way, think opacity or color matrix. */
|
|
const guint node_type = gsk_render_node_get_node_type (node);
|
|
|
|
switch (node_type)
|
|
{
|
|
case GSK_COLOR_NODE:
|
|
case GSK_OPACITY_NODE:
|
|
case GSK_COLOR_MATRIX_NODE:
|
|
case GSK_TEXTURE_NODE:
|
|
return TRUE;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static void gsk_gl_renderer_setup_render_mode (GskGLRenderer *self);
|
|
static void add_offscreen_ops (GskGLRenderer *self,
|
|
RenderOpBuilder *builder,
|
|
const graphene_rect_t *bounds,
|
|
GskRenderNode *child_node,
|
|
int *texture_id,
|
|
gboolean *is_offscreen,
|
|
guint flags);
|
|
static void gsk_gl_renderer_add_render_ops (GskGLRenderer *self,
|
|
GskRenderNode *node,
|
|
RenderOpBuilder *builder);
|
|
|
|
struct _GskGLRenderer
|
|
{
|
|
GskRenderer parent_instance;
|
|
|
|
int scale_factor;
|
|
|
|
GdkGLContext *gl_context;
|
|
GskGLDriver *gl_driver;
|
|
GskGLProfiler *gl_profiler;
|
|
|
|
union {
|
|
Program programs[GL_N_PROGRAMS];
|
|
struct {
|
|
Program blit_program;
|
|
Program color_program;
|
|
Program coloring_program;
|
|
Program color_matrix_program;
|
|
Program linear_gradient_program;
|
|
Program blur_program;
|
|
Program inset_shadow_program;
|
|
Program outset_shadow_program;
|
|
Program unblurred_outset_shadow_program;
|
|
Program border_program;
|
|
Program cross_fade_program;
|
|
};
|
|
};
|
|
|
|
GArray *render_ops;
|
|
|
|
GskGLGlyphCache glyph_cache;
|
|
GskGLShadowCache shadow_cache;
|
|
|
|
#ifdef G_ENABLE_DEBUG
|
|
struct {
|
|
GQuark frames;
|
|
GQuark draw_calls;
|
|
} profile_counters;
|
|
struct {
|
|
GQuark cpu_time;
|
|
GQuark gpu_time;
|
|
} profile_timers;
|
|
#endif
|
|
|
|
cairo_region_t *render_region;
|
|
};
|
|
|
|
struct _GskGLRendererClass
|
|
{
|
|
GskRendererClass parent_class;
|
|
};
|
|
|
|
G_DEFINE_TYPE (GskGLRenderer, gsk_gl_renderer, GSK_TYPE_RENDERER)
|
|
|
|
static void G_GNUC_UNUSED
|
|
add_rect_ops (RenderOpBuilder *builder,
|
|
const graphene_rect_t *r)
|
|
{
|
|
const float min_x = r->origin.x;
|
|
const float min_y = r->origin.y;
|
|
const float max_x = min_x + r->size.width;
|
|
const float max_y = min_y + r->size.height;
|
|
|
|
ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) {
|
|
{ { min_x, min_y }, { 0, 1 }, },
|
|
{ { min_x, max_y }, { 0, 0 }, },
|
|
{ { max_x, min_y }, { 1, 1 }, },
|
|
|
|
{ { max_x, max_y }, { 1, 0 }, },
|
|
{ { min_x, max_y }, { 0, 0 }, },
|
|
{ { max_x, min_y }, { 1, 1 }, },
|
|
});
|
|
}
|
|
|
|
static void G_GNUC_UNUSED
|
|
add_rect_outline_ops (GskGLRenderer *self,
|
|
RenderOpBuilder *builder,
|
|
const graphene_rect_t *rect)
|
|
{
|
|
ops_set_program (builder, &self->color_program);
|
|
ops_set_color (builder, &(GdkRGBA) { 1, 0, 0, 1 });
|
|
|
|
add_rect_ops (builder,
|
|
&GRAPHENE_RECT_INIT (rect->origin.x, rect->origin.y,
|
|
1, rect->size.height));
|
|
add_rect_ops (builder,
|
|
&GRAPHENE_RECT_INIT (rect->origin.x, rect->origin.y,
|
|
rect->size.width, 1));
|
|
add_rect_ops (builder,
|
|
&GRAPHENE_RECT_INIT (rect->origin.x + rect->size.width - 1, rect->origin.y,
|
|
1, rect->size.height));
|
|
|
|
add_rect_ops (builder,
|
|
&GRAPHENE_RECT_INIT (rect->origin.x, rect->origin.y + rect->size.height - 1,
|
|
rect->size.width, 1));
|
|
}
|
|
|
|
static inline void
|
|
rounded_rect_to_floats (GskGLRenderer *self,
|
|
RenderOpBuilder *builder,
|
|
const GskRoundedRect *rect,
|
|
float *outline,
|
|
float *corner_widths,
|
|
float *corner_heights)
|
|
{
|
|
const float scale = ops_get_scale (builder);
|
|
int i;
|
|
graphene_rect_t transformed_bounds;
|
|
|
|
ops_transform_bounds_modelview (builder, &rect->bounds, &transformed_bounds);
|
|
|
|
outline[0] = transformed_bounds.origin.x;
|
|
outline[1] = transformed_bounds.origin.y;
|
|
outline[2] = transformed_bounds.size.width;
|
|
outline[3] = transformed_bounds.size.height;
|
|
|
|
for (i = 0; i < 4; i ++)
|
|
{
|
|
corner_widths[i] = rect->corner[i].width * scale;
|
|
corner_heights[i] = rect->corner[i].height * scale;
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
render_fallback_node (GskGLRenderer *self,
|
|
GskRenderNode *node,
|
|
RenderOpBuilder *builder,
|
|
const GskQuadVertex *vertex_data)
|
|
{
|
|
const float scale = ops_get_scale (builder);
|
|
const int surface_width = ceilf (node->bounds.size.width) * scale;
|
|
const int surface_height = ceilf (node->bounds.size.height) * scale;
|
|
cairo_surface_t *surface;
|
|
cairo_t *cr;
|
|
int texture_id;
|
|
|
|
if (surface_width <= 0 ||
|
|
surface_height <= 0)
|
|
return;
|
|
|
|
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
|
|
surface_width,
|
|
surface_height);
|
|
cairo_surface_set_device_scale (surface, scale, scale);
|
|
cr = cairo_create (surface);
|
|
|
|
cairo_save (cr);
|
|
cairo_translate (cr, -node->bounds.origin.x, -node->bounds.origin.y);
|
|
gsk_render_node_draw (node, cr);
|
|
cairo_restore (cr);
|
|
|
|
#if HIGHLIGHT_FALLBACK
|
|
if (gsk_render_node_get_node_type (node) != GSK_CAIRO_NODE)
|
|
{
|
|
cairo_move_to (cr, 0, 0);
|
|
cairo_rectangle (cr, 0, 0, node->bounds.size.width, node->bounds.size.height);
|
|
cairo_set_source_rgba (cr, 1, 0, 0, 1);
|
|
cairo_stroke (cr);
|
|
}
|
|
#endif
|
|
cairo_destroy (cr);
|
|
|
|
/* Upload the Cairo surface to a GL texture */
|
|
texture_id = gsk_gl_driver_create_texture (self->gl_driver,
|
|
surface_width,
|
|
surface_height);
|
|
|
|
gsk_gl_driver_bind_source_texture (self->gl_driver, texture_id);
|
|
gsk_gl_driver_init_texture_with_surface (self->gl_driver,
|
|
texture_id,
|
|
surface,
|
|
GL_NEAREST, GL_NEAREST);
|
|
|
|
cairo_surface_destroy (surface);
|
|
|
|
ops_set_program (builder, &self->blit_program);
|
|
ops_set_texture (builder, texture_id);
|
|
ops_draw (builder, vertex_data);
|
|
}
|
|
|
|
static inline void
|
|
render_text_node (GskGLRenderer *self,
|
|
GskRenderNode *node,
|
|
RenderOpBuilder *builder,
|
|
const GdkRGBA *color,
|
|
gboolean force_color)
|
|
{
|
|
const PangoFont *font = gsk_text_node_peek_font (node);
|
|
const PangoGlyphInfo *glyphs = gsk_text_node_peek_glyphs (node);
|
|
const float text_scale = ops_get_scale (builder);
|
|
guint num_glyphs = gsk_text_node_get_num_glyphs (node);
|
|
int i;
|
|
int x_position = 0;
|
|
float x = gsk_text_node_get_x (node) + builder->dx;
|
|
float y = gsk_text_node_get_y (node) + builder->dy;
|
|
|
|
/* If the font has color glyphs, we don't need to recolor anything */
|
|
if (!force_color && font_has_color_glyphs (font))
|
|
{
|
|
ops_set_program (builder, &self->blit_program);
|
|
}
|
|
else
|
|
{
|
|
ops_set_program (builder, &self->coloring_program);
|
|
ops_set_color (builder, color);
|
|
}
|
|
|
|
/* We use one quad per character, unlike the other nodes which
|
|
* use at most one quad altogether */
|
|
for (i = 0; i < num_glyphs; i++)
|
|
{
|
|
const PangoGlyphInfo *gi = &glyphs[i];
|
|
const GskGLCachedGlyph *glyph;
|
|
float glyph_x, glyph_y, glyph_w, glyph_h;
|
|
float tx, ty, tx2, ty2;
|
|
double cx;
|
|
double cy;
|
|
|
|
if (gi->glyph == PANGO_GLYPH_EMPTY)
|
|
continue;
|
|
|
|
glyph = gsk_gl_glyph_cache_lookup (&self->glyph_cache,
|
|
TRUE,
|
|
(PangoFont *)font,
|
|
gi->glyph,
|
|
text_scale);
|
|
|
|
/* e.g. whitespace */
|
|
if (glyph->draw_width <= 0 || glyph->draw_height <= 0 || glyph->scale <= 0)
|
|
goto next;
|
|
|
|
cx = (double)(x_position + gi->geometry.x_offset) / PANGO_SCALE;
|
|
cy = (double)(gi->geometry.y_offset) / PANGO_SCALE;
|
|
|
|
ops_set_texture (builder, gsk_gl_glyph_cache_get_glyph_image (&self->glyph_cache,
|
|
glyph)->texture_id);
|
|
|
|
tx = glyph->tx;
|
|
ty = glyph->ty;
|
|
tx2 = tx + glyph->tw;
|
|
ty2 = ty + glyph->th;
|
|
|
|
glyph_x = x + cx + glyph->draw_x;
|
|
glyph_y = y + cy + glyph->draw_y;
|
|
glyph_w = glyph->draw_width;
|
|
glyph_h = glyph->draw_height;
|
|
|
|
ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) {
|
|
{ { glyph_x, glyph_y }, { tx, ty }, },
|
|
{ { glyph_x, glyph_y + glyph_h }, { tx, ty2 }, },
|
|
{ { glyph_x + glyph_w, glyph_y }, { tx2, ty }, },
|
|
|
|
{ { glyph_x + glyph_w, glyph_y + glyph_h }, { tx2, ty2 }, },
|
|
{ { glyph_x, glyph_y + glyph_h }, { tx, ty2 }, },
|
|
{ { glyph_x + glyph_w, glyph_y }, { tx2, ty }, },
|
|
});
|
|
|
|
next:
|
|
x_position += gi->geometry.width;
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
render_border_node (GskGLRenderer *self,
|
|
GskRenderNode *node,
|
|
RenderOpBuilder *builder)
|
|
{
|
|
const float scale = ops_get_scale (builder);
|
|
const float min_x = builder->dx + node->bounds.origin.x;
|
|
const float min_y = builder->dy + node->bounds.origin.y;
|
|
const float max_x = min_x + node->bounds.size.width;
|
|
const float max_y = min_y + node->bounds.size.height;
|
|
const GdkRGBA *colors = gsk_border_node_peek_colors (node);
|
|
const GskRoundedRect *rounded_outline = gsk_border_node_peek_outline (node);
|
|
const float *og_widths = gsk_border_node_peek_widths (node);
|
|
GskRoundedRect outline;
|
|
float widths[4];
|
|
int i;
|
|
struct {
|
|
float w;
|
|
float h;
|
|
} sizes[4];
|
|
|
|
|
|
for (i = 0; i < 4; i ++)
|
|
widths[i] = og_widths[i];
|
|
|
|
/* Top left */
|
|
if (widths[3] > 0)
|
|
sizes[0].w = MAX (widths[3], rounded_outline->corner[0].width);
|
|
else
|
|
sizes[0].w = 0;
|
|
|
|
if (widths[0] > 0)
|
|
sizes[0].h = MAX (widths[0], rounded_outline->corner[0].height);
|
|
else
|
|
sizes[0].h = 0;
|
|
|
|
/* Top right */
|
|
if (widths[1] > 0)
|
|
sizes[1].w = MAX (widths[1], rounded_outline->corner[1].width);
|
|
else
|
|
sizes[1].w = 0;
|
|
|
|
if (widths[0] > 0)
|
|
sizes[1].h = MAX (widths[0], rounded_outline->corner[1].height);
|
|
else
|
|
sizes[1].h = 0;
|
|
|
|
/* Bottom right */
|
|
if (widths[1] > 0)
|
|
sizes[2].w = MAX (widths[1], rounded_outline->corner[2].width);
|
|
else
|
|
sizes[2].w = 0;
|
|
|
|
if (widths[2] > 0)
|
|
sizes[2].h = MAX (widths[2], rounded_outline->corner[2].height);
|
|
else
|
|
sizes[2].h = 0;
|
|
|
|
|
|
/* Bottom left */
|
|
if (widths[3] > 0)
|
|
sizes[3].w = MAX (widths[3], rounded_outline->corner[3].width);
|
|
else
|
|
sizes[3].w = 0;
|
|
|
|
if (widths[2] > 0)
|
|
sizes[3].h = MAX (widths[2], rounded_outline->corner[3].height);
|
|
else
|
|
sizes[3].h = 0;
|
|
|
|
for (i = 0; i < 4; i ++)
|
|
widths[i] *= scale;
|
|
|
|
{
|
|
const GskQuadVertex side_data[4][6] = {
|
|
/* Top */
|
|
{
|
|
{ { min_x, min_y }, { 0, 1 }, }, /* Upper left */
|
|
{ { min_x + sizes[0].w, min_y + sizes[0].h }, { 0, 0 }, }, /* Lower left */
|
|
{ { max_x, min_y }, { 1, 1 }, }, /* Upper right */
|
|
|
|
{ { max_x - sizes[1].w, min_y + sizes[1].h }, { 1, 0 }, }, /* Lower right */
|
|
{ { min_x + sizes[0].w, min_y + sizes[0].h }, { 0, 0 }, }, /* Lower left */
|
|
{ { max_x, min_y }, { 1, 1 }, }, /* Upper right */
|
|
},
|
|
/* Right */
|
|
{
|
|
{ { max_x - sizes[1].w, min_y + sizes[1].h }, { 0, 1 }, }, /* Upper left */
|
|
{ { max_x - sizes[2].w, max_y - sizes[2].h }, { 0, 0 }, }, /* Lower left */
|
|
{ { max_x, min_y }, { 1, 1 }, }, /* Upper right */
|
|
|
|
{ { max_x, max_y }, { 1, 0 }, }, /* Lower right */
|
|
{ { max_x - sizes[2].w, max_y - sizes[2].h }, { 0, 0 }, }, /* Lower left */
|
|
{ { max_x, min_y }, { 1, 1 }, }, /* Upper right */
|
|
},
|
|
/* Bottom */
|
|
{
|
|
{ { min_x + sizes[3].w, max_y - sizes[3].h }, { 0, 1 }, }, /* Upper left */
|
|
{ { min_x, max_y }, { 0, 0 }, }, /* Lower left */
|
|
{ { max_x - sizes[2].w, max_y - sizes[2].h }, { 1, 1 }, }, /* Upper right */
|
|
|
|
{ { max_x, max_y }, { 1, 0 }, }, /* Lower right */
|
|
{ { min_x , max_y }, { 0, 0 }, }, /* Lower left */
|
|
{ { max_x - sizes[2].w, max_y - sizes[2].h }, { 1, 1 }, }, /* Upper right */
|
|
},
|
|
/* Left */
|
|
{
|
|
{ { min_x, min_y }, { 0, 1 }, }, /* Upper left */
|
|
{ { min_x, max_y }, { 0, 0 }, }, /* Lower left */
|
|
{ { min_x + sizes[0].w, min_y + sizes[0].h }, { 1, 1 }, }, /* Upper right */
|
|
|
|
{ { min_x + sizes[3].w, max_y - sizes[3].h }, { 1, 0 }, }, /* Lower right */
|
|
{ { min_x, max_y }, { 0, 0 }, }, /* Lower left */
|
|
{ { min_x + sizes[0].w, min_y + sizes[0].h }, { 1, 1 }, }, /* Upper right */
|
|
}
|
|
};
|
|
int indices[4] = { 0, 1, 2, 3 };
|
|
|
|
/* We sort them by color */
|
|
sort_border_sides (colors, indices);
|
|
|
|
/* Prepare outline */
|
|
outline = *rounded_outline;
|
|
ops_transform_bounds_modelview (builder, &outline.bounds, &outline.bounds);
|
|
|
|
for (i = 0; i < 4; i ++)
|
|
{
|
|
outline.corner[i].width *= scale;
|
|
outline.corner[i].height *= scale;
|
|
}
|
|
|
|
ops_set_program (builder, &self->border_program);
|
|
ops_set_border_width (builder, widths);
|
|
ops_set_border (builder, &outline);
|
|
|
|
for (i = 0; i < 4; i ++)
|
|
{
|
|
if (widths[indices[i]] > 0)
|
|
{
|
|
ops_set_border_color (builder, &colors[indices[i]]);
|
|
ops_draw (builder, side_data[indices[i]]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
render_color_node (GskGLRenderer *self,
|
|
GskRenderNode *node,
|
|
RenderOpBuilder *builder,
|
|
const GskQuadVertex *vertex_data)
|
|
{
|
|
ops_set_program (builder, &self->color_program);
|
|
ops_set_color (builder, gsk_color_node_peek_color (node));
|
|
ops_draw (builder, vertex_data);
|
|
}
|
|
|
|
static inline void
|
|
render_texture_node (GskGLRenderer *self,
|
|
GskRenderNode *node,
|
|
RenderOpBuilder *builder)
|
|
{
|
|
GdkTexture *texture = gsk_texture_node_get_texture (node);
|
|
const int max_texture_size = gsk_gl_driver_get_max_texture_size (self->gl_driver);
|
|
const float min_x = builder->dx + node->bounds.origin.x;
|
|
const float min_y = builder->dy + node->bounds.origin.y;
|
|
const float max_x = min_x + node->bounds.size.width;
|
|
const float max_y = min_y + node->bounds.size.height;
|
|
|
|
if (texture->width > max_texture_size || texture->height > max_texture_size)
|
|
{
|
|
const float scale_x = (max_x - min_x) / texture->width;
|
|
const float scale_y = (max_y - min_y) / texture->height;
|
|
TextureSlice *slices;
|
|
guint n_slices;
|
|
guint i;
|
|
|
|
gsk_gl_driver_slice_texture (self->gl_driver, texture, &slices, &n_slices);
|
|
|
|
ops_set_program (builder, &self->blit_program);
|
|
for (i = 0; i < n_slices; i ++)
|
|
{
|
|
const TextureSlice *slice = &slices[i];
|
|
float x1, x2, y1, y2;
|
|
|
|
x1 = min_x + (scale_x * slice->rect.x);
|
|
x2 = x1 + (slice->rect.width * scale_x);
|
|
y1 = min_y + (scale_y * slice->rect.y);
|
|
y2 = y1 + (slice->rect.height * scale_y);
|
|
|
|
ops_set_texture (builder, slice->texture_id);
|
|
ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) {
|
|
{ { x1, y1 }, { 0, 0 }, },
|
|
{ { x1, y2 }, { 0, 1 }, },
|
|
{ { x2, y1 }, { 1, 0 }, },
|
|
|
|
{ { x2, y2 }, { 1, 1 }, },
|
|
{ { x1, y2 }, { 0, 1 }, },
|
|
{ { x2, y1 }, { 1, 0 }, },
|
|
});
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int gl_min_filter = GL_NEAREST, gl_mag_filter = GL_NEAREST;
|
|
int texture_id;
|
|
|
|
get_gl_scaling_filters (node, &gl_min_filter, &gl_mag_filter);
|
|
|
|
texture_id = gsk_gl_driver_get_texture_for_texture (self->gl_driver,
|
|
texture,
|
|
gl_min_filter,
|
|
gl_mag_filter);
|
|
ops_set_program (builder, &self->blit_program);
|
|
ops_set_texture (builder, texture_id);
|
|
|
|
|
|
ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) {
|
|
{ { min_x, min_y }, { 0, 0 }, },
|
|
{ { min_x, max_y }, { 0, 1 }, },
|
|
{ { max_x, min_y }, { 1, 0 }, },
|
|
|
|
{ { max_x, max_y }, { 1, 1 }, },
|
|
{ { min_x, max_y }, { 0, 1 }, },
|
|
{ { max_x, min_y }, { 1, 0 }, },
|
|
});
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
render_offset_node (GskGLRenderer *self,
|
|
GskRenderNode *node,
|
|
RenderOpBuilder *builder)
|
|
{
|
|
GskRenderNode *child = gsk_offset_node_get_child (node);
|
|
const float dx = gsk_offset_node_get_x_offset (node);
|
|
const float dy = gsk_offset_node_get_y_offset (node);
|
|
|
|
ops_offset (builder, dx, dy);
|
|
gsk_gl_renderer_add_render_ops (self, child, builder);
|
|
ops_offset (builder, - dx, - dy);
|
|
}
|
|
|
|
static inline void
|
|
render_transform_node (GskGLRenderer *self,
|
|
GskRenderNode *node,
|
|
RenderOpBuilder *builder)
|
|
{
|
|
const float scale = ops_get_scale (builder);
|
|
GskRenderNode *child = gsk_transform_node_get_child (node);
|
|
graphene_matrix_t transform, transformed_mv;
|
|
|
|
graphene_matrix_init_from_matrix (&transform, gsk_transform_node_peek_transform (node));
|
|
graphene_matrix_multiply (&transform, builder->current_modelview, &transformed_mv);
|
|
graphene_matrix_translate (&transformed_mv,
|
|
&(graphene_point3d_t) { builder->dx * scale, builder->dy * scale, 0});
|
|
|
|
/* We just added the offset to the new modelview matrix, so the following
|
|
* cases dont' have to care about builder->dx/dy! */
|
|
|
|
ops_push_modelview (builder, &transformed_mv);
|
|
if (ops_modelview_is_simple (builder) ||
|
|
node_supports_transform (child))
|
|
{
|
|
const float dx = builder->dx;
|
|
const float dy = builder->dy;
|
|
|
|
builder->dx = 0;
|
|
builder->dy = 0;
|
|
gsk_gl_renderer_add_render_ops (self, child, builder);
|
|
builder->dx = dx;
|
|
builder->dy = dy;
|
|
}
|
|
else
|
|
{
|
|
const float min_x = node->bounds.origin.x;
|
|
const float min_y = node->bounds.origin.y;
|
|
const float max_x = min_x + node->bounds.size.width;
|
|
const float max_y = min_y + node->bounds.size.height;
|
|
const GskQuadVertex vertex_data[GL_N_VERTICES] = {
|
|
{ { min_x, min_y }, { 0, 1 }, },
|
|
{ { min_x, max_y }, { 0, 0 }, },
|
|
{ { max_x, min_y }, { 1, 1 }, },
|
|
|
|
{ { max_x, max_y }, { 1, 0 }, },
|
|
{ { min_x, max_y }, { 0, 0 }, },
|
|
{ { max_x, min_y }, { 1, 1 }, },
|
|
};
|
|
int texture_id;
|
|
gboolean is_offscreen;
|
|
/* For non-trivial transforms, we draw everything on a texture and then
|
|
* draw the texture transformed. */
|
|
/* TODO: We should compute a modelview containing only the "non-trivial"
|
|
* part (e.g. the rotation) and use that. We want to keep the scale
|
|
* for the texture.
|
|
*/
|
|
add_offscreen_ops (self, builder,
|
|
&node->bounds,
|
|
child,
|
|
&texture_id, &is_offscreen,
|
|
RESET_CLIP | RESET_OPACITY);
|
|
ops_set_texture (builder, texture_id);
|
|
ops_set_program (builder, &self->blit_program);
|
|
ops_draw (builder, vertex_data);
|
|
}
|
|
ops_pop_modelview (builder);
|
|
}
|
|
|
|
static inline void
|
|
render_opacity_node (GskGLRenderer *self,
|
|
GskRenderNode *node,
|
|
RenderOpBuilder *builder)
|
|
{
|
|
float prev_opacity;
|
|
|
|
prev_opacity = ops_set_opacity (builder,
|
|
builder->current_opacity * gsk_opacity_node_get_opacity (node));
|
|
|
|
gsk_gl_renderer_add_render_ops (self, gsk_opacity_node_get_child (node), builder);
|
|
|
|
ops_set_opacity (builder, prev_opacity);
|
|
}
|
|
|
|
static inline void
|
|
render_linear_gradient_node (GskGLRenderer *self,
|
|
GskRenderNode *node,
|
|
RenderOpBuilder *builder,
|
|
const GskQuadVertex *vertex_data)
|
|
{
|
|
RenderOp op;
|
|
int n_color_stops = MIN (8, gsk_linear_gradient_node_get_n_color_stops (node));
|
|
const GskColorStop *stops = gsk_linear_gradient_node_peek_color_stops (node);
|
|
const graphene_point_t *start = gsk_linear_gradient_node_peek_start (node);
|
|
const graphene_point_t *end = gsk_linear_gradient_node_peek_end (node);
|
|
int i;
|
|
|
|
for (i = 0; i < n_color_stops; i ++)
|
|
{
|
|
const GskColorStop *stop = stops + i;
|
|
|
|
op.linear_gradient.color_stops[(i * 4) + 0] = stop->color.red;
|
|
op.linear_gradient.color_stops[(i * 4) + 1] = stop->color.green;
|
|
op.linear_gradient.color_stops[(i * 4) + 2] = stop->color.blue;
|
|
op.linear_gradient.color_stops[(i * 4) + 3] = stop->color.alpha;
|
|
op.linear_gradient.color_offsets[i] = stop->offset;
|
|
}
|
|
|
|
ops_set_program (builder, &self->linear_gradient_program);
|
|
op.op = OP_CHANGE_LINEAR_GRADIENT;
|
|
op.linear_gradient.n_color_stops = n_color_stops;
|
|
op.linear_gradient.start_point = *start;
|
|
op.linear_gradient.start_point.x += builder->dx;
|
|
op.linear_gradient.start_point.y += builder->dy;
|
|
op.linear_gradient.end_point = *end;
|
|
op.linear_gradient.end_point.x += builder->dx;
|
|
op.linear_gradient.end_point.y += builder->dy;
|
|
ops_add (builder, &op);
|
|
|
|
ops_draw (builder, vertex_data);
|
|
}
|
|
|
|
static inline void
|
|
render_clip_node (GskGLRenderer *self,
|
|
GskRenderNode *node,
|
|
RenderOpBuilder *builder)
|
|
{
|
|
GskRenderNode *child = gsk_clip_node_get_child (node);
|
|
graphene_rect_t transformed_clip;
|
|
graphene_rect_t intersection;
|
|
GskRoundedRect child_clip;
|
|
|
|
transformed_clip = *gsk_clip_node_peek_clip (node);
|
|
ops_transform_bounds_modelview (builder, &transformed_clip, &transformed_clip);
|
|
|
|
graphene_rect_intersection (&transformed_clip,
|
|
&builder->current_clip->bounds,
|
|
&intersection);
|
|
|
|
gsk_rounded_rect_init_from_rect (&child_clip, &intersection, 0.0f);
|
|
|
|
ops_push_clip (builder, &child_clip);
|
|
gsk_gl_renderer_add_render_ops (self, child, builder);
|
|
ops_pop_clip (builder);
|
|
}
|
|
|
|
static gboolean
|
|
gsk_rounded_rect_intersection (const GskRoundedRect *self,
|
|
const GskRoundedRect *other,
|
|
GskRoundedRect *out_intersection)
|
|
{
|
|
const graphene_rect_t *self_bounds = &self->bounds;
|
|
const graphene_rect_t *other_bounds = &other->bounds;
|
|
|
|
if (graphene_rect_contains_rect (self_bounds, other_bounds))
|
|
{
|
|
*out_intersection = *other;
|
|
return TRUE;
|
|
}
|
|
|
|
/* TODO: There are a few cases here that we can express using a single
|
|
* rounded rectangle, which are even interesting in every day usage.
|
|
* For example, a partially scrolled-away rounded rectangle
|
|
* might just work.
|
|
*/
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
static inline void
|
|
render_rounded_clip_node (GskGLRenderer *self,
|
|
GskRenderNode *node,
|
|
RenderOpBuilder *builder)
|
|
{
|
|
const float scale = ops_get_scale (builder);
|
|
GskRoundedRect child_clip = *gsk_rounded_clip_node_peek_clip (node);
|
|
GskRoundedRect transformed_clip;
|
|
GskRenderNode *child = gsk_rounded_clip_node_get_child (node);
|
|
GskRoundedRect intersection;
|
|
gboolean need_offscreen;
|
|
int i;
|
|
|
|
transformed_clip = child_clip;
|
|
ops_transform_bounds_modelview (builder, &child_clip.bounds, &transformed_clip.bounds);
|
|
|
|
if (!ops_has_clip (builder))
|
|
{
|
|
intersection = transformed_clip;
|
|
need_offscreen = FALSE;
|
|
}
|
|
else
|
|
{
|
|
need_offscreen = !gsk_rounded_rect_intersection (builder->current_clip,
|
|
&transformed_clip,
|
|
&intersection);
|
|
}
|
|
|
|
if (!need_offscreen)
|
|
{
|
|
/* If they don't intersect at all, we can simply set
|
|
* the new clip and add the render ops */
|
|
for (i = 0; i < 4; i ++)
|
|
{
|
|
intersection.corner[i].width *= scale;
|
|
intersection.corner[i].height *= scale;
|
|
}
|
|
|
|
ops_push_clip (builder, &intersection);
|
|
gsk_gl_renderer_add_render_ops (self, child, builder);
|
|
ops_pop_clip (builder);
|
|
}
|
|
else
|
|
{
|
|
const float min_x = builder->dx + node->bounds.origin.x;
|
|
const float min_y = builder->dy + node->bounds.origin.y;
|
|
const float max_x = min_x + node->bounds.size.width;
|
|
const float max_y = min_y + node->bounds.size.height;
|
|
graphene_matrix_t scale_matrix;
|
|
gboolean is_offscreen;
|
|
int texture_id;
|
|
/* NOTE: We are *not* transforming the clip by the current modelview here.
|
|
* We instead draw the untransformed clip to a texture and then transform
|
|
* that texture.
|
|
*
|
|
* We do, however, apply the scale factor to the child clip of course.
|
|
*/
|
|
|
|
graphene_matrix_init_scale (&scale_matrix, scale, scale, 1.0f);
|
|
graphene_matrix_transform_bounds (&scale_matrix, &child_clip.bounds, &child_clip.bounds);
|
|
|
|
/* Increase corner radius size by scale factor */
|
|
for (i = 0; i < 4; i ++)
|
|
{
|
|
child_clip.corner[i].width *= scale;
|
|
child_clip.corner[i].height *= scale;
|
|
}
|
|
|
|
ops_push_clip (builder, &child_clip);
|
|
add_offscreen_ops (self, builder, &node->bounds,
|
|
child,
|
|
&texture_id, &is_offscreen,
|
|
FORCE_OFFSCREEN | RESET_OPACITY);
|
|
ops_pop_clip (builder);
|
|
|
|
ops_set_program (builder, &self->blit_program);
|
|
ops_set_texture (builder, texture_id);
|
|
|
|
ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) {
|
|
{ { min_x, min_y }, { 0, 1 }, },
|
|
{ { min_x, max_y }, { 0, 0 }, },
|
|
{ { max_x, min_y }, { 1, 1 }, },
|
|
|
|
{ { max_x, max_y }, { 1, 0 }, },
|
|
{ { min_x, max_y }, { 0, 0 }, },
|
|
{ { max_x, min_y }, { 1, 1 }, },
|
|
});
|
|
}
|
|
/* Else this node is entirely out of the current clip node and we don't draw it anyway. */
|
|
}
|
|
|
|
static inline void
|
|
render_color_matrix_node (GskGLRenderer *self,
|
|
GskRenderNode *node,
|
|
RenderOpBuilder *builder,
|
|
const GskQuadVertex *vertex_data)
|
|
{
|
|
const float min_x = builder->dx + node->bounds.origin.x;
|
|
const float min_y = builder->dy + node->bounds.origin.y;
|
|
const float max_x = min_x + node->bounds.size.width;
|
|
const float max_y = min_y + node->bounds.size.height;
|
|
int texture_id;
|
|
gboolean is_offscreen;
|
|
|
|
/* Pass min_x/max_x/min_y/max_y without builder->dx/dy! */
|
|
add_offscreen_ops (self, builder,
|
|
&node->bounds,
|
|
gsk_color_matrix_node_get_child (node),
|
|
&texture_id, &is_offscreen,
|
|
RESET_CLIP | RESET_OPACITY);
|
|
|
|
ops_set_program (builder, &self->color_matrix_program);
|
|
ops_set_color_matrix (builder,
|
|
gsk_color_matrix_node_peek_color_matrix (node),
|
|
gsk_color_matrix_node_peek_color_offset (node));
|
|
|
|
ops_set_texture (builder, texture_id);
|
|
|
|
if (is_offscreen)
|
|
{
|
|
GskQuadVertex offscreen_vertex_data[GL_N_VERTICES] = {
|
|
{ { min_x, min_y }, { 0, 1 }, },
|
|
{ { min_x, max_y }, { 0, 0 }, },
|
|
{ { max_x, min_y }, { 1, 1 }, },
|
|
|
|
{ { max_x, max_y }, { 1, 0 }, },
|
|
{ { min_x, max_y }, { 0, 0 }, },
|
|
{ { max_x, min_y }, { 1, 1 }, },
|
|
};
|
|
|
|
ops_draw (builder, offscreen_vertex_data);
|
|
}
|
|
else
|
|
{
|
|
ops_draw (builder, vertex_data);
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
render_blur_node (GskGLRenderer *self,
|
|
GskRenderNode *node,
|
|
RenderOpBuilder *builder,
|
|
const GskQuadVertex *vertex_data)
|
|
{
|
|
const float min_x = builder->dx + node->bounds.origin.x;
|
|
const float min_y = builder->dy + node->bounds.origin.y;
|
|
const float max_x = min_x + node->bounds.size.width;
|
|
const float max_y = min_y + node->bounds.size.height;
|
|
int texture_id;
|
|
gboolean is_offscreen;
|
|
RenderOp op;
|
|
|
|
/* TODO(perf): We're forcing the child offscreen even if it's a texture
|
|
* so the resulting offscreen texture is bigger by the gaussian blur factor
|
|
* (see gsk_blur_node_new), but we didn't have to do that if the blur
|
|
* shader could handle that situation. */
|
|
|
|
add_offscreen_ops (self, builder,
|
|
&node->bounds,
|
|
gsk_blur_node_get_child (node),
|
|
&texture_id, &is_offscreen,
|
|
RESET_CLIP | FORCE_OFFSCREEN | RESET_OPACITY);
|
|
|
|
ops_set_program (builder, &self->blur_program);
|
|
op.op = OP_CHANGE_BLUR;
|
|
graphene_size_init_from_size (&op.blur.size, &node->bounds.size);
|
|
op.blur.radius = gsk_blur_node_get_radius (node);
|
|
ops_add (builder, &op);
|
|
|
|
ops_set_texture (builder, texture_id);
|
|
|
|
if (is_offscreen)
|
|
{
|
|
GskQuadVertex offscreen_vertex_data[GL_N_VERTICES] = {
|
|
{ { min_x, min_y }, { 0, 1 }, },
|
|
{ { min_x, max_y }, { 0, 0 }, },
|
|
{ { max_x, min_y }, { 1, 1 }, },
|
|
|
|
{ { max_x, max_y }, { 1, 0 }, },
|
|
{ { min_x, max_y }, { 0, 0 }, },
|
|
{ { max_x, min_y }, { 1, 1 }, },
|
|
};
|
|
|
|
ops_draw (builder, offscreen_vertex_data);
|
|
}
|
|
else
|
|
{
|
|
ops_draw (builder, vertex_data);
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
render_inset_shadow_node (GskGLRenderer *self,
|
|
GskRenderNode *node,
|
|
RenderOpBuilder *builder,
|
|
const GskQuadVertex *vertex_data)
|
|
{
|
|
const float scale = ops_get_scale (builder);
|
|
RenderOp op;
|
|
|
|
/* TODO: Implement blurred inset shadows as well */
|
|
if (gsk_inset_shadow_node_get_blur_radius (node) > 0)
|
|
{
|
|
render_fallback_node (self, node, builder, vertex_data);
|
|
return;
|
|
}
|
|
|
|
op.op = OP_CHANGE_INSET_SHADOW;
|
|
rgba_to_float (gsk_inset_shadow_node_peek_color (node), op.inset_shadow.color);
|
|
rounded_rect_to_floats (self, builder,
|
|
gsk_inset_shadow_node_peek_outline (node),
|
|
op.inset_shadow.outline,
|
|
op.inset_shadow.corner_widths,
|
|
op.inset_shadow.corner_heights);
|
|
op.inset_shadow.radius = gsk_inset_shadow_node_get_blur_radius (node) * scale;
|
|
op.inset_shadow.spread = gsk_inset_shadow_node_get_spread (node) * scale;
|
|
op.inset_shadow.offset[0] = gsk_inset_shadow_node_get_dx (node) * scale;
|
|
op.inset_shadow.offset[1] = -gsk_inset_shadow_node_get_dy (node) * scale;
|
|
|
|
ops_set_program (builder, &self->inset_shadow_program);
|
|
ops_add (builder, &op);
|
|
ops_draw (builder, vertex_data);
|
|
}
|
|
|
|
static inline void
|
|
render_unblurred_outset_shadow_node (GskGLRenderer *self,
|
|
GskRenderNode *node,
|
|
RenderOpBuilder *builder,
|
|
const GskQuadVertex *vertex_data)
|
|
{
|
|
const float scale = ops_get_scale (builder);
|
|
const float spread = gsk_outset_shadow_node_get_spread (node);
|
|
GskRoundedRect r = *gsk_outset_shadow_node_peek_outline (node);
|
|
RenderOp op;
|
|
|
|
op.op = OP_CHANGE_UNBLURRED_OUTSET_SHADOW;
|
|
rgba_to_float (gsk_outset_shadow_node_peek_color (node), op.unblurred_outset_shadow.color);
|
|
|
|
gsk_rounded_rect_shrink (&r, -spread, -spread, -spread, -spread);
|
|
|
|
rounded_rect_to_floats (self, builder,
|
|
&r,
|
|
op.unblurred_outset_shadow.outline,
|
|
op.unblurred_outset_shadow.corner_widths,
|
|
op.unblurred_outset_shadow.corner_heights);
|
|
|
|
op.unblurred_outset_shadow.spread = gsk_outset_shadow_node_get_spread (node) * scale;
|
|
op.unblurred_outset_shadow.offset[0] = gsk_outset_shadow_node_get_dx (node) * scale;
|
|
op.unblurred_outset_shadow.offset[1] = -gsk_outset_shadow_node_get_dy (node) * scale;
|
|
|
|
ops_set_program (builder, &self->unblurred_outset_shadow_program);
|
|
ops_add (builder, &op);
|
|
ops_draw (builder, vertex_data);
|
|
}
|
|
|
|
static inline void
|
|
render_outset_shadow_node (GskGLRenderer *self,
|
|
GskRenderNode *node,
|
|
RenderOpBuilder *builder)
|
|
{
|
|
const GskRoundedRect *outline = gsk_outset_shadow_node_peek_outline (node);
|
|
GskRoundedRect offset_outline;
|
|
const float blur_radius = gsk_outset_shadow_node_get_blur_radius (node);
|
|
const float blur_extra = gsk_cairo_blur_compute_pixels (blur_radius);
|
|
const float spread = gsk_outset_shadow_node_get_spread (node);
|
|
const float dx = gsk_outset_shadow_node_get_dx (node);
|
|
const float dy = gsk_outset_shadow_node_get_dy (node);
|
|
const float min_x = builder->dx + outline->bounds.origin.x - spread - blur_extra / 2.0;
|
|
const float min_y = builder->dy + outline->bounds.origin.y - spread - blur_extra / 2.0;
|
|
const float max_x = min_x + outline->bounds.size.width + (spread + blur_extra/2.0) * 2;
|
|
const float max_y = min_y + outline->bounds.size.height + (spread + blur_extra/2.0) * 2;
|
|
float texture_width, texture_height;
|
|
RenderOp op;
|
|
graphene_matrix_t identity;
|
|
graphene_matrix_t prev_projection;
|
|
graphene_rect_t prev_viewport;
|
|
graphene_matrix_t item_proj;
|
|
int blurred_texture_id;
|
|
int cached_tid;
|
|
|
|
/* offset_outline is the minimal outline we need to draw the given drop shadow,
|
|
* enlarged by the spread and offset by the blur radius. */
|
|
offset_outline = *outline;
|
|
/* Shrink our outline to the minimum size that can still hold all the border radii */
|
|
gsk_rounded_rect_shrink_to_minimum (&offset_outline);
|
|
/* Increase by the spread */
|
|
gsk_rounded_rect_shrink (&offset_outline, -spread, -spread, -spread, -spread);
|
|
/* No we need to incorporate the blur radius; since we blur an edge an equal blur_extra/2.0
|
|
* on both sides, the minimum side of both width and height needs to be blur_extra */
|
|
offset_outline.bounds.size.width = MAX (offset_outline.bounds.size.width, blur_extra);
|
|
offset_outline.bounds.size.height = MAX (offset_outline.bounds.size.height, blur_extra);
|
|
/* For the center part, we add a few pixels */
|
|
offset_outline.bounds.size.width += SHADOW_EXTRA_SIZE;
|
|
offset_outline.bounds.size.height += SHADOW_EXTRA_SIZE;
|
|
offset_outline.bounds.origin.x = blur_extra / 2.0f;
|
|
offset_outline.bounds.origin.y = blur_extra / 2.0f;
|
|
|
|
texture_width = offset_outline.bounds.size.width + blur_extra;
|
|
texture_height = offset_outline.bounds.size.height + blur_extra;
|
|
|
|
cached_tid = gsk_gl_shadow_cache_get_texture_id (&self->shadow_cache,
|
|
self->gl_driver,
|
|
&offset_outline,
|
|
blur_radius);
|
|
if (cached_tid == 0)
|
|
{
|
|
int texture_id, render_target;
|
|
int blurred_render_target;
|
|
int prev_render_target;
|
|
GskRoundedRect blit_clip;
|
|
|
|
texture_id = gsk_gl_driver_create_texture (self->gl_driver, texture_width, texture_height);
|
|
gsk_gl_driver_bind_source_texture (self->gl_driver, texture_id);
|
|
gsk_gl_driver_init_texture_empty (self->gl_driver, texture_id);
|
|
render_target = gsk_gl_driver_create_render_target (self->gl_driver, texture_id, FALSE, FALSE);
|
|
|
|
|
|
graphene_matrix_init_ortho (&item_proj,
|
|
0, texture_width, 0, texture_height,
|
|
ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE);
|
|
graphene_matrix_scale (&item_proj, 1, -1, 1);
|
|
graphene_matrix_init_identity (&identity);
|
|
|
|
prev_render_target = ops_set_render_target (builder, render_target);
|
|
op.op = OP_CLEAR;
|
|
ops_add (builder, &op);
|
|
prev_projection = ops_set_projection (builder, &item_proj);
|
|
ops_push_modelview (builder, &identity);
|
|
prev_viewport = ops_set_viewport (builder, &GRAPHENE_RECT_INIT (0, 0, texture_width, texture_height));
|
|
|
|
/* Draw outline */
|
|
ops_set_program (builder, &self->color_program);
|
|
ops_push_clip (builder, &offset_outline);
|
|
ops_set_color (builder, gsk_outset_shadow_node_peek_color (node));
|
|
ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) {
|
|
{ { 0, }, { 0, 1 }, },
|
|
{ { 0, texture_height }, { 0, 0 }, },
|
|
{ { texture_width, }, { 1, 1 }, },
|
|
|
|
{ { texture_width, texture_height }, { 1, 0 }, },
|
|
{ { 0, texture_height }, { 0, 0 }, },
|
|
{ { texture_width, }, { 1, 1 }, },
|
|
});
|
|
|
|
blurred_texture_id = gsk_gl_driver_create_permanent_texture (self->gl_driver, texture_width, texture_height);
|
|
gsk_gl_driver_bind_source_texture (self->gl_driver, blurred_texture_id);
|
|
gsk_gl_driver_init_texture_empty (self->gl_driver, blurred_texture_id);
|
|
blurred_render_target = gsk_gl_driver_create_render_target (self->gl_driver, blurred_texture_id, TRUE, TRUE);
|
|
|
|
ops_set_render_target (builder, blurred_render_target);
|
|
ops_pop_clip (builder);
|
|
op.op = OP_CLEAR;
|
|
ops_add (builder, &op);
|
|
|
|
gsk_rounded_rect_init_from_rect (&blit_clip,
|
|
&GRAPHENE_RECT_INIT (0, 0, texture_width, texture_height), 0.0f);
|
|
|
|
ops_set_program (builder, &self->blur_program);
|
|
op.op = OP_CHANGE_BLUR;
|
|
op.blur.size.width = texture_width;
|
|
op.blur.size.height = texture_height;
|
|
op.blur.radius = blur_radius;
|
|
ops_add (builder, &op);
|
|
|
|
ops_push_clip (builder, &blit_clip);
|
|
ops_set_texture (builder, texture_id);
|
|
ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) {
|
|
{ { 0, 0 }, { 0, 1 }, },
|
|
{ { 0, texture_height }, { 0, 0 }, },
|
|
{ { texture_width, 0 }, { 1, 1 }, },
|
|
|
|
{ { texture_width, texture_height }, { 1, 0 }, },
|
|
{ { 0, texture_height }, { 0, 0 }, },
|
|
{ { texture_width, 0 }, { 1, 1 }, },
|
|
});
|
|
|
|
|
|
ops_pop_clip (builder);
|
|
ops_set_viewport (builder, &prev_viewport);
|
|
ops_pop_modelview (builder);
|
|
ops_set_projection (builder, &prev_projection);
|
|
ops_set_render_target (builder, prev_render_target);
|
|
|
|
gsk_gl_shadow_cache_commit (&self->shadow_cache,
|
|
&offset_outline,
|
|
blur_radius,
|
|
blurred_texture_id);
|
|
}
|
|
else
|
|
{
|
|
blurred_texture_id = cached_tid;
|
|
}
|
|
|
|
ops_set_program (builder, &self->outset_shadow_program);
|
|
ops_set_texture (builder, blurred_texture_id);
|
|
op.op = OP_CHANGE_OUTSET_SHADOW;
|
|
rounded_rect_to_floats (self, builder,
|
|
outline,
|
|
op.outset_shadow.outline,
|
|
op.outset_shadow.corner_widths,
|
|
op.outset_shadow.corner_heights);
|
|
ops_add (builder, &op);
|
|
|
|
/* We use the one outset shadow op from above to draw all 8 sides/corners. */
|
|
{
|
|
const GskRoundedRect *o = &offset_outline;
|
|
float top_height = MAX (o->corner[0].height, o->corner[1].height);
|
|
float bottom_height = MAX (o->corner[2].height, o->corner[3].height);
|
|
float left_width = MAX (o->corner[0].width, o->corner[3].width);
|
|
float right_width = MAX (o->corner[1].width, o->corner[2].width);
|
|
float x1, x2, y1, y2, tx1, tx2, ty1, ty2;
|
|
|
|
top_height = MAX (top_height, blur_extra / 2.0f) + (blur_extra / 2.0f);
|
|
bottom_height = MAX (bottom_height, blur_extra / 2.0f) + (blur_extra / 2.0f);
|
|
left_width = MAX (left_width, blur_extra / 2.0f) + (blur_extra / 2.0f);
|
|
right_width = MAX (right_width, blur_extra / 2.0f) + (blur_extra / 2.0f);
|
|
|
|
/* Top left */
|
|
if (top_height > 0 && left_width > 0)
|
|
{
|
|
x1 = min_x + dx;
|
|
x2 = min_x + dx + left_width;
|
|
y1 = min_y + dy;
|
|
y2 = min_y + dy + top_height;
|
|
tx1 = 0;
|
|
tx2 = left_width / texture_width;
|
|
ty1 = 1 - (top_height / texture_height);
|
|
ty2 = 1;
|
|
ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) {
|
|
{ { x1, y1 }, { tx1, ty2 }, },
|
|
{ { x1, y2 }, { tx1, ty1 }, },
|
|
{ { x2, y1 }, { tx2, ty2 }, },
|
|
|
|
{ { x2, y2 }, { tx2, ty1 }, },
|
|
{ { x1, y2 }, { tx1, ty1 }, },
|
|
{ { x2, y1 }, { tx2, ty2 }, },
|
|
});
|
|
}
|
|
|
|
/* Top right */
|
|
if (top_height > 0 && right_width > 0)
|
|
{
|
|
x1 = max_x + dx - right_width;
|
|
x2 = max_x + dx;
|
|
y1 = min_y + dy;
|
|
y2 = min_y + dy + top_height;
|
|
tx1 = 1 - (right_width / texture_width);
|
|
tx2 = 1;
|
|
ty1 = 1 - (top_height / texture_height);
|
|
ty2 = 1;
|
|
ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) {
|
|
{ { x1, y1 }, { tx1, ty2 }, },
|
|
{ { x1, y2 }, { tx1, ty1 }, },
|
|
{ { x2, y1 }, { tx2, ty2 }, },
|
|
|
|
{ { x2, y2 }, { tx2, ty1 }, },
|
|
{ { x1, y2 }, { tx1, ty1 }, },
|
|
{ { x2, y1 }, { tx2, ty2 }, },
|
|
});
|
|
}
|
|
|
|
/* Bottom right */
|
|
if (bottom_height > 0 && left_width > 0)
|
|
{
|
|
x1 = max_x + dx - right_width;
|
|
x2 = max_x + dx;
|
|
y1 = max_y + dy - bottom_height;
|
|
y2 = max_y + dy;
|
|
tx1 = 1 - (right_width / texture_width);
|
|
tx2 = 1;
|
|
ty1 = 0;
|
|
ty2 = (bottom_height / texture_height);
|
|
ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) {
|
|
{ { x1, y1 }, { tx1, ty2 }, },
|
|
{ { x1, y2 }, { tx1, ty1 }, },
|
|
{ { x2, y1 }, { tx2, ty2 }, },
|
|
|
|
{ { x2, y2 }, { tx2, ty1 }, },
|
|
{ { x1, y2 }, { tx1, ty1 }, },
|
|
{ { x2, y1 }, { tx2, ty2 }, },
|
|
});
|
|
}
|
|
|
|
/* Bottom left */
|
|
if (bottom_height > 0 && left_width > 0)
|
|
{
|
|
x1 = min_x + dx;
|
|
x2 = min_x + dx + left_width;
|
|
y1 = max_y + dy - bottom_height;
|
|
y2 = max_y + dy;
|
|
tx1 = 0;
|
|
tx2 = left_width / texture_width;
|
|
ty1 = 0;
|
|
ty2 = bottom_height / texture_height;
|
|
ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) {
|
|
{ { x1, y1 }, { tx1, ty2 }, },
|
|
{ { x1, y2 }, { tx1, ty1 }, },
|
|
{ { x2, y1 }, { tx2, ty2 }, },
|
|
|
|
{ { x2, y2 }, { tx2, ty1 }, },
|
|
{ { x1, y2 }, { tx1, ty1 }, },
|
|
{ { x2, y1 }, { tx2, ty2 }, },
|
|
});
|
|
}
|
|
|
|
/* Left side */
|
|
if (left_width > 0)
|
|
{
|
|
x1 = min_x + dx;
|
|
x2 = min_x + dx + left_width;
|
|
y1 = min_y + dy + top_height;
|
|
y2 = max_y + dy - bottom_height;
|
|
tx1 = 0;
|
|
tx2 = left_width / texture_width;
|
|
ty1 = 0.5f - SHADOW_EXTRA_SIZE / 2.0f / texture_height;
|
|
ty2 = ty1 + (SHADOW_EXTRA_SIZE / texture_height);
|
|
ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) {
|
|
{ { x1, y1 }, { tx1, ty2 }, },
|
|
{ { x1, y2 }, { tx1, ty1 }, },
|
|
{ { x2, y1 }, { tx2, ty2 }, },
|
|
|
|
{ { x2, y2 }, { tx2, ty1 }, },
|
|
{ { x1, y2 }, { tx1, ty1 }, },
|
|
{ { x2, y1 }, { tx2, ty2 }, },
|
|
});
|
|
}
|
|
|
|
/* Right side */
|
|
if (right_width > 0)
|
|
{
|
|
x1 = max_x + dx - right_width;
|
|
x2 = max_x + dx;
|
|
y1 = min_y + dy + top_height;
|
|
y2 = max_y + dy - bottom_height;
|
|
tx1 = 1 - (right_width / texture_width);
|
|
tx2 = 1;
|
|
ty1 = 0.5f - SHADOW_EXTRA_SIZE / 2.0f / texture_height;
|
|
ty2 = ty1 + (SHADOW_EXTRA_SIZE / texture_height);
|
|
ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) {
|
|
{ { x1, y1 }, { tx1, ty2 }, },
|
|
{ { x1, y2 }, { tx1, ty1 }, },
|
|
{ { x2, y1 }, { tx2, ty2 }, },
|
|
|
|
{ { x2, y2 }, { tx2, ty1 }, },
|
|
{ { x1, y2 }, { tx1, ty1 }, },
|
|
{ { x2, y1 }, { tx2, ty2 }, },
|
|
});
|
|
}
|
|
|
|
/* Top side */
|
|
if (top_height > 0)
|
|
{
|
|
x1 = min_x + dx + left_width;
|
|
x2 = max_x + dx - right_width;
|
|
y1 = min_y + dy;
|
|
y2 = min_y + dy + top_height;
|
|
tx1 = 0.5f - (SHADOW_EXTRA_SIZE / 2.0f / texture_width);
|
|
tx2 = tx1 + (SHADOW_EXTRA_SIZE / texture_width);
|
|
ty1 = 1 - (top_height / texture_height);
|
|
ty2 = 1;
|
|
ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) {
|
|
{ { x1, y1 }, { tx1, ty2 }, },
|
|
{ { x1, y2 }, { tx1, ty1 }, },
|
|
{ { x2, y1 }, { tx2, ty2 }, },
|
|
|
|
{ { x2, y2 }, { tx2, ty1 }, },
|
|
{ { x1, y2 }, { tx1, ty1 }, },
|
|
{ { x2, y1 }, { tx2, ty2 }, },
|
|
});
|
|
}
|
|
|
|
/* Bottom side */
|
|
if (bottom_height > 0)
|
|
{
|
|
x1 = min_x + dx + left_width;
|
|
x2 = max_x + dx - right_width;
|
|
y1 = max_y + dy - bottom_height;
|
|
y2 = max_y + dy;
|
|
tx1 = 0.5f - (SHADOW_EXTRA_SIZE / 2.0f / texture_width);
|
|
tx2 = tx1 + (SHADOW_EXTRA_SIZE / texture_width);
|
|
ty1 = 0;
|
|
ty2 = bottom_height / texture_height;
|
|
ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) {
|
|
{ { x1, y1 }, { tx1, ty2 }, },
|
|
{ { x1, y2 }, { tx1, ty1 }, },
|
|
{ { x2, y1 }, { tx2, ty2 }, },
|
|
|
|
{ { x2, y2 }, { tx2, ty1 }, },
|
|
{ { x1, y2 }, { tx1, ty1 }, },
|
|
{ { x2, y1 }, { tx2, ty2 }, },
|
|
});
|
|
}
|
|
|
|
/* Middle */
|
|
x1 = min_x + dx + left_width;
|
|
x2 = max_x + dx - right_width;
|
|
y1 = min_y + dy + top_height;
|
|
y2 = max_y + dy - bottom_height;
|
|
if (x2 > x1 && y2 > y1)
|
|
{
|
|
tx1 = (texture_width - SHADOW_EXTRA_SIZE) / 2.0f / texture_width;
|
|
tx2 = (texture_width + SHADOW_EXTRA_SIZE) / 2.0f / texture_width;
|
|
ty1 = (texture_height - SHADOW_EXTRA_SIZE) / 2.0f / texture_height;
|
|
ty2 = (texture_height + SHADOW_EXTRA_SIZE) / 2.0f / texture_height;
|
|
ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) {
|
|
{ { x1, y1 }, { tx1, ty2 }, },
|
|
{ { x1, y2 }, { tx1, ty1 }, },
|
|
{ { x2, y1 }, { tx2, ty2 }, },
|
|
|
|
{ { x2, y2 }, { tx2, ty1 }, },
|
|
{ { x1, y2 }, { tx1, ty1 }, },
|
|
{ { x2, y1 }, { tx2, ty2 }, },
|
|
});
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
render_shadow_node (GskGLRenderer *self,
|
|
GskRenderNode *node,
|
|
RenderOpBuilder *builder,
|
|
const GskQuadVertex *vertex_data)
|
|
{
|
|
float min_x = node->bounds.origin.x;
|
|
float min_y = node->bounds.origin.y;
|
|
float max_x = min_x + node->bounds.size.width;
|
|
float max_y = min_y + node->bounds.size.height;
|
|
GskRenderNode *original_child = gsk_shadow_node_get_child (node);
|
|
GskRenderNode *shadow_child = original_child;
|
|
gsize n_shadows = gsk_shadow_node_get_n_shadows (node);
|
|
guint i;
|
|
|
|
/* TODO: Implement blurred shadow nodes */;
|
|
for (i = 0; i < n_shadows; i ++)
|
|
{
|
|
const GskShadow *shadow = gsk_shadow_node_peek_shadow (node, i);
|
|
|
|
if (shadow->radius > 0)
|
|
{
|
|
render_fallback_node (self, node, builder, vertex_data);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Shadow nodes recolor every pixel of the source texture, but leave the alpha in tact.
|
|
* If the child is a color matrix node that doesn't touch the alpha, we can throw that away. */
|
|
if (gsk_render_node_get_node_type (shadow_child) == GSK_COLOR_MATRIX_NODE &&
|
|
!color_matrix_modifies_alpha (shadow_child))
|
|
{
|
|
shadow_child = gsk_color_matrix_node_get_child (shadow_child);
|
|
}
|
|
|
|
for (i = 0; i < n_shadows; i ++)
|
|
{
|
|
const GskShadow *shadow = gsk_shadow_node_peek_shadow (node, i);
|
|
const float dx = shadow->dx;
|
|
const float dy = shadow->dy;
|
|
int texture_id;
|
|
gboolean is_offscreen;
|
|
|
|
g_assert (shadow->radius <= 0);
|
|
|
|
if (gsk_render_node_get_node_type (shadow_child) == GSK_TEXT_NODE)
|
|
{
|
|
ops_offset (builder, dx, dy);
|
|
render_text_node (self, shadow_child, builder, &shadow->color, TRUE);
|
|
ops_offset (builder, - dx, - dy);
|
|
continue;
|
|
}
|
|
|
|
min_x = builder->dx + shadow_child->bounds.origin.x;
|
|
min_y = builder->dy + shadow_child->bounds.origin.y;
|
|
max_x = min_x + shadow_child->bounds.size.width;
|
|
max_y = min_y + shadow_child->bounds.size.height;
|
|
|
|
/* Draw the child offscreen, without the offset. */
|
|
add_offscreen_ops (self, builder,
|
|
&shadow_child->bounds,
|
|
shadow_child, &texture_id, &is_offscreen,
|
|
RESET_CLIP | RESET_OPACITY);
|
|
|
|
ops_offset (builder, dx, dy);
|
|
ops_set_program (builder, &self->coloring_program);
|
|
ops_set_color (builder, &shadow->color);
|
|
ops_set_texture (builder, texture_id);
|
|
if (is_offscreen)
|
|
{
|
|
const GskQuadVertex offscreen_vertex_data[GL_N_VERTICES] = {
|
|
{ { dx + min_x, dy + min_y }, { 0, 1 }, },
|
|
{ { dx + min_x, dy + max_y }, { 0, 0 }, },
|
|
{ { dx + max_x, dy + min_y }, { 1, 1 }, },
|
|
|
|
{ { dx + max_x, dy + max_y }, { 1, 0 }, },
|
|
{ { dx + min_x, dy + max_y }, { 0, 0 }, },
|
|
{ { dx + max_x, dy + min_y }, { 1, 1 }, },
|
|
};
|
|
|
|
ops_draw (builder, offscreen_vertex_data);
|
|
}
|
|
else
|
|
{
|
|
const GskQuadVertex onscreen_vertex_data[GL_N_VERTICES] = {
|
|
{ { dx + min_x, dy + min_y }, { 0, 0 }, },
|
|
{ { dx + min_x, dy + max_y }, { 0, 1 }, },
|
|
{ { dx + max_x, dy + min_y }, { 1, 0 }, },
|
|
|
|
{ { dx + max_x, dy + max_y }, { 1, 1 }, },
|
|
{ { dx + min_x, dy + max_y }, { 0, 1 }, },
|
|
{ { dx + max_x, dy + min_y }, { 1, 0 }, },
|
|
};
|
|
|
|
ops_draw (builder, onscreen_vertex_data);
|
|
}
|
|
|
|
ops_offset (builder, - dx, - dy);
|
|
}
|
|
|
|
/* Now draw the child normally */
|
|
gsk_gl_renderer_add_render_ops (self, original_child, builder);
|
|
}
|
|
|
|
static inline void
|
|
render_cross_fade_node (GskGLRenderer *self,
|
|
GskRenderNode *node,
|
|
RenderOpBuilder *builder)
|
|
{
|
|
const float min_x = builder->dx + node->bounds.origin.x;
|
|
const float min_y = builder->dy + node->bounds.origin.y;
|
|
const float max_x = min_x + node->bounds.size.width;
|
|
const float max_y = min_y + node->bounds.size.height;
|
|
GskRenderNode *start_node = gsk_cross_fade_node_get_start_child (node);
|
|
GskRenderNode *end_node = gsk_cross_fade_node_get_end_child (node);
|
|
float progress = gsk_cross_fade_node_get_progress (node);
|
|
int start_texture_id;
|
|
int end_texture_id;
|
|
gboolean is_offscreen1, is_offscreen2;
|
|
RenderOp op;
|
|
const GskQuadVertex vertex_data[GL_N_VERTICES] = {
|
|
{ { min_x, min_y }, { 0, 1 }, },
|
|
{ { min_x, max_y }, { 0, 0 }, },
|
|
{ { max_x, min_y }, { 1, 1 }, },
|
|
|
|
{ { max_x, max_y }, { 1, 0 }, },
|
|
{ { min_x, max_y }, { 0, 0 }, },
|
|
{ { max_x, min_y }, { 1, 1 }, },
|
|
};
|
|
|
|
/* TODO: We create 2 textures here as big as the cross-fade node, but both the
|
|
* start and the end node might be a lot smaller than that. */
|
|
|
|
add_offscreen_ops (self, builder,
|
|
&node->bounds,
|
|
start_node,
|
|
&start_texture_id, &is_offscreen1,
|
|
FORCE_OFFSCREEN | RESET_CLIP);
|
|
|
|
add_offscreen_ops (self, builder,
|
|
&node->bounds,
|
|
end_node,
|
|
&end_texture_id, &is_offscreen2,
|
|
FORCE_OFFSCREEN | RESET_CLIP);
|
|
|
|
ops_set_program (builder, &self->cross_fade_program);
|
|
op.op = OP_CHANGE_CROSS_FADE;
|
|
op.cross_fade.progress = progress;
|
|
op.cross_fade.source2 = end_texture_id;
|
|
ops_add (builder, &op);
|
|
ops_set_texture (builder, start_texture_id);
|
|
|
|
ops_draw (builder, vertex_data);
|
|
}
|
|
|
|
static inline void
|
|
apply_viewport_op (const Program *program,
|
|
const RenderOp *op)
|
|
{
|
|
OP_PRINT (" -> New Viewport: %f, %f, %f, %f", op->viewport.origin.x, op->viewport.origin.y, op->viewport.size.width, op->viewport.size.height);
|
|
glUniform4f (program->viewport_location,
|
|
op->viewport.origin.x, op->viewport.origin.y,
|
|
op->viewport.size.width, op->viewport.size.height);
|
|
glViewport (0, 0, op->viewport.size.width, op->viewport.size.height);
|
|
}
|
|
|
|
static inline void
|
|
apply_modelview_op (const Program *program,
|
|
const RenderOp *op)
|
|
{
|
|
float mat[16];
|
|
|
|
OP_PRINT (" -> Modelview");
|
|
graphene_matrix_to_float (&op->modelview, mat);
|
|
glUniformMatrix4fv (program->modelview_location, 1, GL_FALSE, mat);
|
|
}
|
|
|
|
static inline void
|
|
apply_projection_op (const Program *program,
|
|
const RenderOp *op)
|
|
{
|
|
float mat[16];
|
|
|
|
OP_PRINT (" -> Projection");
|
|
graphene_matrix_to_float (&op->projection, mat);
|
|
glUniformMatrix4fv (program->projection_location, 1, GL_FALSE, mat);
|
|
}
|
|
|
|
static inline void
|
|
apply_program_op (const Program *program,
|
|
const RenderOp *op)
|
|
{
|
|
OP_PRINT (" -> Program: %d", op->program->index);
|
|
glUseProgram (op->program->id);
|
|
}
|
|
|
|
static inline void
|
|
apply_render_target_op (GskGLRenderer *self,
|
|
const Program *program,
|
|
const RenderOp *op)
|
|
{
|
|
OP_PRINT (" -> Render Target: %d", op->render_target_id);
|
|
|
|
glBindFramebuffer (GL_FRAMEBUFFER, op->render_target_id);
|
|
|
|
if (op->render_target_id != 0)
|
|
glDisable (GL_SCISSOR_TEST);
|
|
else
|
|
gsk_gl_renderer_setup_render_mode (self); /* Reset glScissor etc. */
|
|
}
|
|
|
|
static inline void
|
|
apply_color_op (const Program *program,
|
|
const RenderOp *op)
|
|
{
|
|
OP_PRINT (" -> Color: (%f, %f, %f, %f)", op->color.red, op->color.green, op->color.blue, op->color.alpha);
|
|
glUniform4f (program->color.color_location,
|
|
op->color.red, op->color.green, op->color.blue, op->color.alpha);
|
|
}
|
|
|
|
static inline void
|
|
apply_opacity_op (const Program *program,
|
|
const RenderOp *op)
|
|
{
|
|
OP_PRINT (" -> Opacity %f", op->opacity);
|
|
glUniform1f (program->alpha_location, op->opacity);
|
|
}
|
|
|
|
static inline void
|
|
apply_source_texture_op (const Program *program,
|
|
const RenderOp *op)
|
|
{
|
|
g_assert(op->texture_id != 0);
|
|
OP_PRINT (" -> New texture: %d", op->texture_id);
|
|
/* Use texture unit 0 for the source */
|
|
glUniform1i (program->source_location, 0);
|
|
glActiveTexture (GL_TEXTURE0);
|
|
glBindTexture (GL_TEXTURE_2D, op->texture_id);
|
|
}
|
|
|
|
static inline void
|
|
apply_color_matrix_op (const Program *program,
|
|
const RenderOp *op)
|
|
{
|
|
float mat[16];
|
|
float vec[4];
|
|
OP_PRINT (" -> Color Matrix");
|
|
graphene_matrix_to_float (&op->color_matrix.matrix, mat);
|
|
glUniformMatrix4fv (program->color_matrix.color_matrix_location, 1, GL_FALSE, mat);
|
|
|
|
graphene_vec4_to_float (&op->color_matrix.offset, vec);
|
|
glUniform4fv (program->color_matrix.color_offset_location, 1, vec);
|
|
}
|
|
|
|
static inline void
|
|
apply_clip_op (const Program *program,
|
|
const RenderOp *op)
|
|
{
|
|
OP_PRINT (" -> Clip (%f, %f, %f, %f) (%f, %f, %f, %f), (%f, %f, %f, %f)",
|
|
op->clip.bounds.origin.x, op->clip.bounds.origin.y,
|
|
op->clip.bounds.size.width, op->clip.bounds.size.height,
|
|
op->clip.corner[0].width,
|
|
op->clip.corner[1].width,
|
|
op->clip.corner[2].width,
|
|
op->clip.corner[3].width,
|
|
op->clip.corner[0].height,
|
|
op->clip.corner[1].height,
|
|
op->clip.corner[2].height,
|
|
op->clip.corner[3].height);
|
|
glUniform4f (program->clip_location,
|
|
op->clip.bounds.origin.x, op->clip.bounds.origin.y,
|
|
op->clip.bounds.size.width, op->clip.bounds.size.height);
|
|
|
|
glUniform4f (program->clip_corner_widths_location,
|
|
op->clip.corner[0].width,
|
|
op->clip.corner[1].width,
|
|
op->clip.corner[2].width,
|
|
op->clip.corner[3].width);
|
|
glUniform4f (program->clip_corner_heights_location,
|
|
op->clip.corner[0].height,
|
|
op->clip.corner[1].height,
|
|
op->clip.corner[2].height,
|
|
op->clip.corner[3].height);
|
|
}
|
|
|
|
static inline void
|
|
apply_inset_shadow_op (const Program *program,
|
|
const RenderOp *op)
|
|
{
|
|
OP_PRINT (" -> inset shadow. Color: (%f, %f, %f, %f), Offset: (%f, %f), Spread: %f, Outline: (%f, %f, %f, %f) Corner widths: (%f, %f, %f, %f), Corner Heights: (%f, %f, %f, %f)",
|
|
op->inset_shadow.color[0],
|
|
op->inset_shadow.color[1],
|
|
op->inset_shadow.color[2],
|
|
op->inset_shadow.color[3],
|
|
op->inset_shadow.offset[0],
|
|
op->inset_shadow.offset[1],
|
|
op->inset_shadow.spread,
|
|
op->inset_shadow.outline[0],
|
|
op->inset_shadow.outline[1],
|
|
op->inset_shadow.outline[2],
|
|
op->inset_shadow.outline[3],
|
|
op->inset_shadow.corner_widths[0],
|
|
op->inset_shadow.corner_widths[1],
|
|
op->inset_shadow.corner_widths[2],
|
|
op->inset_shadow.corner_widths[3],
|
|
op->inset_shadow.corner_heights[0],
|
|
op->inset_shadow.corner_heights[1],
|
|
op->inset_shadow.corner_heights[2],
|
|
op->inset_shadow.corner_heights[3]);
|
|
glUniform4fv (program->inset_shadow.color_location, 1, op->inset_shadow.color);
|
|
glUniform2fv (program->inset_shadow.offset_location, 1, op->inset_shadow.offset);
|
|
glUniform1f (program->inset_shadow.spread_location, op->inset_shadow.spread);
|
|
glUniform4fv (program->inset_shadow.outline_location, 1, op->inset_shadow.outline);
|
|
glUniform4fv (program->inset_shadow.corner_widths_location, 1, op->inset_shadow.corner_widths);
|
|
glUniform4fv (program->inset_shadow.corner_heights_location, 1, op->inset_shadow.corner_heights);
|
|
}
|
|
|
|
static inline void
|
|
apply_unblurred_outset_shadow_op (const Program *program,
|
|
const RenderOp *op)
|
|
{
|
|
OP_PRINT (" -> unblurred outset shadow");
|
|
glUniform4fv (program->unblurred_outset_shadow.color_location, 1, op->unblurred_outset_shadow.color);
|
|
glUniform2fv (program->unblurred_outset_shadow.offset_location, 1, op->unblurred_outset_shadow.offset);
|
|
glUniform1f (program->unblurred_outset_shadow.spread_location, op->unblurred_outset_shadow.spread);
|
|
glUniform4fv (program->unblurred_outset_shadow.outline_location, 1, op->unblurred_outset_shadow.outline);
|
|
glUniform4fv (program->unblurred_outset_shadow.corner_widths_location, 1,
|
|
op->unblurred_outset_shadow.corner_widths);
|
|
glUniform4fv (program->unblurred_outset_shadow.corner_heights_location, 1,
|
|
op->unblurred_outset_shadow.corner_heights);
|
|
}
|
|
|
|
static inline void
|
|
apply_outset_shadow_op (const Program *program,
|
|
const RenderOp *op)
|
|
{
|
|
OP_PRINT (" -> outset shadow");
|
|
glUniform4fv (program->outset_shadow.outline_location, 1, op->outset_shadow.outline);
|
|
glUniform4fv (program->outset_shadow.corner_widths_location, 1, op->outset_shadow.corner_widths);
|
|
glUniform4fv (program->outset_shadow.corner_heights_location, 1, op->outset_shadow.corner_heights);
|
|
}
|
|
|
|
static inline void
|
|
apply_linear_gradient_op (const Program *program,
|
|
const RenderOp *op)
|
|
{
|
|
OP_PRINT (" -> Linear gradient");
|
|
glUniform1i (program->linear_gradient.num_color_stops_location,
|
|
op->linear_gradient.n_color_stops);
|
|
glUniform4fv (program->linear_gradient.color_stops_location,
|
|
op->linear_gradient.n_color_stops,
|
|
op->linear_gradient.color_stops);
|
|
glUniform1fv (program->linear_gradient.color_offsets_location,
|
|
op->linear_gradient.n_color_stops,
|
|
op->linear_gradient.color_offsets);
|
|
glUniform2f (program->linear_gradient.start_point_location,
|
|
op->linear_gradient.start_point.x, op->linear_gradient.start_point.y);
|
|
glUniform2f (program->linear_gradient.end_point_location,
|
|
op->linear_gradient.end_point.x, op->linear_gradient.end_point.y);
|
|
}
|
|
|
|
static inline void
|
|
apply_border_op (const Program *program,
|
|
const RenderOp *op)
|
|
{
|
|
const GskRoundedRect *o = &op->border.outline;
|
|
float outline[4];
|
|
float widths[4];
|
|
float heights[4];
|
|
int i;
|
|
OP_PRINT (" -> Border Outline");
|
|
|
|
outline[0] = o->bounds.origin.x;
|
|
outline[1] = o->bounds.origin.y;
|
|
outline[2] = o->bounds.size.width;
|
|
outline[3] = o->bounds.size.height;
|
|
|
|
for (i = 0; i < 4; i ++)
|
|
{
|
|
widths[i] = o->corner[i].width;
|
|
heights[i] = o->corner[i].height;
|
|
}
|
|
|
|
glUniform4fv (program->border.outline_location, 1, outline);
|
|
glUniform4fv (program->border.corner_widths_location, 1, widths);
|
|
glUniform4fv (program->border.corner_heights_location, 1, heights);
|
|
}
|
|
|
|
static inline void
|
|
apply_border_width_op (const Program *program,
|
|
const RenderOp *op)
|
|
{
|
|
OP_PRINT (" -> Border width (%f, %f, %f, %f)",
|
|
op->border.widths[0], op->border.widths[1], op->border.widths[2], op->border.widths[3]);
|
|
|
|
glUniform4fv (program->border.widths_location, 1, op->border.widths);
|
|
}
|
|
|
|
static inline void
|
|
apply_border_color_op (const Program *program,
|
|
const RenderOp *op)
|
|
{
|
|
OP_PRINT (" -> Border color (%f, %f, %f, %f)",
|
|
op->border.color[0], op->border.color[1], op->border.color[2], op->border.color[3]);
|
|
glUniform4fv (program->border.color_location, 1, op->border.color);
|
|
}
|
|
|
|
static inline void
|
|
apply_blur_op (const Program *program,
|
|
const RenderOp *op)
|
|
{
|
|
OP_PRINT (" -> Blur");
|
|
glUniform1f (program->blur.blur_radius_location, op->blur.radius);
|
|
glUniform2f (program->blur.blur_size_location, op->blur.size.width, op->blur.size.height);
|
|
/*glUniform2f (program->blur.dir_location, op->blur.dir[0], op->blur.dir[1]);*/
|
|
}
|
|
|
|
static inline void
|
|
apply_cross_fade_op (const Program *program,
|
|
const RenderOp *op)
|
|
{
|
|
/* End texture id */
|
|
glUniform1i (program->cross_fade.source2_location, 1);
|
|
glActiveTexture (GL_TEXTURE0 + 1);
|
|
glBindTexture (GL_TEXTURE_2D, op->cross_fade.source2);
|
|
/* progress */
|
|
glUniform1f (program->cross_fade.progress_location, op->cross_fade.progress);
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_dispose (GObject *gobject)
|
|
{
|
|
GskGLRenderer *self = GSK_GL_RENDERER (gobject);
|
|
|
|
g_clear_pointer (&self->render_ops, g_array_unref);
|
|
|
|
G_OBJECT_CLASS (gsk_gl_renderer_parent_class)->dispose (gobject);
|
|
}
|
|
|
|
static gboolean
|
|
gsk_gl_renderer_create_programs (GskGLRenderer *self,
|
|
GError **error)
|
|
{
|
|
GskShaderBuilder *builder;
|
|
GError *shader_error = NULL;
|
|
int i;
|
|
static const struct {
|
|
const char *name;
|
|
const char *fs;
|
|
} program_definitions[] = {
|
|
{ "blit", "blit.fs.glsl" },
|
|
{ "color", "color.fs.glsl" },
|
|
{ "coloring", "coloring.fs.glsl" },
|
|
{ "color matrix", "color_matrix.fs.glsl" },
|
|
{ "linear gradient", "linear_gradient.fs.glsl" },
|
|
{ "blur", "blur.fs.glsl" },
|
|
{ "inset shadow", "inset_shadow.fs.glsl" },
|
|
{ "outset shadow", "outset_shadow.fs.glsl" },
|
|
{ "unblurred outset shadow", "unblurred_outset_shadow.fs.glsl" },
|
|
{ "border", "border.fs.glsl" },
|
|
{ "cross fade", "cross_fade.fs.glsl" },
|
|
};
|
|
|
|
builder = gsk_shader_builder_new ();
|
|
|
|
gsk_shader_builder_set_resource_base_path (builder, "/org/gtk/libgsk/glsl");
|
|
|
|
if (gdk_gl_context_get_use_es (self->gl_context))
|
|
{
|
|
gsk_shader_builder_set_version (builder, SHADER_VERSION_GLES);
|
|
gsk_shader_builder_set_vertex_preamble (builder, "es2_common.vs.glsl");
|
|
gsk_shader_builder_set_fragment_preamble (builder, "es2_common.fs.glsl");
|
|
gsk_shader_builder_add_define (builder, "GSK_GLES", "1");
|
|
}
|
|
else if (gdk_gl_context_is_legacy (self->gl_context))
|
|
{
|
|
int maj, min;
|
|
gdk_gl_context_get_version (self->gl_context, &maj, &min);
|
|
|
|
if (maj == 3)
|
|
gsk_shader_builder_set_version (builder, SHADER_VERSION_GL3_LEGACY);
|
|
else
|
|
gsk_shader_builder_set_version (builder, SHADER_VERSION_GL2_LEGACY);
|
|
|
|
gsk_shader_builder_set_vertex_preamble (builder, "gl_common.vs.glsl");
|
|
gsk_shader_builder_set_fragment_preamble (builder, "gl_common.fs.glsl");
|
|
gsk_shader_builder_add_define (builder, "GSK_LEGACY", "1");
|
|
}
|
|
else
|
|
{
|
|
gsk_shader_builder_set_version (builder, SHADER_VERSION_GL3);
|
|
gsk_shader_builder_set_vertex_preamble (builder, "gl3_common.vs.glsl");
|
|
gsk_shader_builder_set_fragment_preamble (builder, "gl3_common.fs.glsl");
|
|
gsk_shader_builder_add_define (builder, "GSK_GL3", "1");
|
|
}
|
|
|
|
#ifdef G_ENABLE_DEBUG
|
|
if (GSK_RENDERER_DEBUG_CHECK (GSK_RENDERER (self), SHADERS))
|
|
gsk_shader_builder_add_define (builder, "GSK_DEBUG", "1");
|
|
#endif
|
|
|
|
gsk_shader_builder_set_common_vertex_shader (builder, "blit.vs.glsl",
|
|
&shader_error);
|
|
|
|
g_assert_no_error (shader_error);
|
|
|
|
for (i = 0; i < GL_N_PROGRAMS; i ++)
|
|
{
|
|
Program *prog = &self->programs[i];
|
|
|
|
prog->index = i;
|
|
prog->id = gsk_shader_builder_create_program (builder,
|
|
program_definitions[i].fs,
|
|
&shader_error);
|
|
|
|
if (shader_error != NULL)
|
|
{
|
|
g_propagate_prefixed_error (error, shader_error,
|
|
"Unable to create '%s' program (from %s and %s):\n",
|
|
program_definitions[i].name,
|
|
"blit.vs.glsl",
|
|
program_definitions[i].fs);
|
|
|
|
g_object_unref (builder);
|
|
return FALSE;
|
|
}
|
|
|
|
INIT_COMMON_UNIFORM_LOCATION (prog, alpha);
|
|
INIT_COMMON_UNIFORM_LOCATION (prog, source);
|
|
INIT_COMMON_UNIFORM_LOCATION (prog, clip);
|
|
INIT_COMMON_UNIFORM_LOCATION (prog, clip_corner_widths);
|
|
INIT_COMMON_UNIFORM_LOCATION (prog, clip_corner_heights);
|
|
INIT_COMMON_UNIFORM_LOCATION (prog, viewport);
|
|
INIT_COMMON_UNIFORM_LOCATION (prog, projection);
|
|
INIT_COMMON_UNIFORM_LOCATION (prog, modelview);
|
|
}
|
|
|
|
/* color */
|
|
INIT_PROGRAM_UNIFORM_LOCATION (color, color);
|
|
|
|
/* coloring */
|
|
INIT_PROGRAM_UNIFORM_LOCATION (coloring, color);
|
|
|
|
/* color matrix */
|
|
INIT_PROGRAM_UNIFORM_LOCATION (color_matrix, color_matrix);
|
|
INIT_PROGRAM_UNIFORM_LOCATION (color_matrix, color_offset);
|
|
|
|
/* linear gradient */
|
|
INIT_PROGRAM_UNIFORM_LOCATION (linear_gradient, color_stops);
|
|
INIT_PROGRAM_UNIFORM_LOCATION (linear_gradient, color_offsets);
|
|
INIT_PROGRAM_UNIFORM_LOCATION (linear_gradient, num_color_stops);
|
|
INIT_PROGRAM_UNIFORM_LOCATION (linear_gradient, start_point);
|
|
INIT_PROGRAM_UNIFORM_LOCATION (linear_gradient, end_point);
|
|
|
|
/* blur */
|
|
INIT_PROGRAM_UNIFORM_LOCATION (blur, blur_radius);
|
|
INIT_PROGRAM_UNIFORM_LOCATION (blur, blur_size);
|
|
/*INIT_PROGRAM_UNIFORM_LOCATION (blur, dir);*/
|
|
|
|
/* inset shadow */
|
|
INIT_PROGRAM_UNIFORM_LOCATION (inset_shadow, color);
|
|
INIT_PROGRAM_UNIFORM_LOCATION (inset_shadow, spread);
|
|
INIT_PROGRAM_UNIFORM_LOCATION (inset_shadow, offset);
|
|
INIT_PROGRAM_UNIFORM_LOCATION (inset_shadow, outline);
|
|
INIT_PROGRAM_UNIFORM_LOCATION (inset_shadow, corner_widths);
|
|
INIT_PROGRAM_UNIFORM_LOCATION (inset_shadow, corner_heights);
|
|
|
|
/* outset shadow */
|
|
INIT_PROGRAM_UNIFORM_LOCATION (outset_shadow, outline);
|
|
INIT_PROGRAM_UNIFORM_LOCATION (outset_shadow, corner_widths);
|
|
INIT_PROGRAM_UNIFORM_LOCATION (outset_shadow, corner_heights);
|
|
|
|
/* unblurred outset shadow */
|
|
INIT_PROGRAM_UNIFORM_LOCATION (unblurred_outset_shadow, color);
|
|
INIT_PROGRAM_UNIFORM_LOCATION (unblurred_outset_shadow, spread);
|
|
INIT_PROGRAM_UNIFORM_LOCATION (unblurred_outset_shadow, offset);
|
|
INIT_PROGRAM_UNIFORM_LOCATION (unblurred_outset_shadow, outline);
|
|
INIT_PROGRAM_UNIFORM_LOCATION (unblurred_outset_shadow, corner_widths);
|
|
INIT_PROGRAM_UNIFORM_LOCATION (unblurred_outset_shadow, corner_heights);
|
|
|
|
/* border */
|
|
INIT_PROGRAM_UNIFORM_LOCATION (border, color);
|
|
INIT_PROGRAM_UNIFORM_LOCATION (border, widths);
|
|
INIT_PROGRAM_UNIFORM_LOCATION (border, outline);
|
|
INIT_PROGRAM_UNIFORM_LOCATION (border, corner_widths);
|
|
INIT_PROGRAM_UNIFORM_LOCATION (border, corner_heights);
|
|
|
|
/* cross fade */
|
|
INIT_PROGRAM_UNIFORM_LOCATION (cross_fade, progress);
|
|
INIT_PROGRAM_UNIFORM_LOCATION (cross_fade, source2);
|
|
|
|
g_object_unref (builder);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gsk_gl_renderer_realize (GskRenderer *renderer,
|
|
GdkSurface *surface,
|
|
GError **error)
|
|
{
|
|
GskGLRenderer *self = GSK_GL_RENDERER (renderer);
|
|
|
|
/* If we didn't get a GdkGLContext before realization, try creating
|
|
* one now, for our exclusive use.
|
|
*/
|
|
if (self->gl_context == NULL)
|
|
{
|
|
self->gl_context = gdk_surface_create_gl_context (surface, error);
|
|
if (self->gl_context == NULL)
|
|
return FALSE;
|
|
}
|
|
|
|
if (!gdk_gl_context_realize (self->gl_context, error))
|
|
return FALSE;
|
|
|
|
gdk_gl_context_make_current (self->gl_context);
|
|
|
|
g_assert (self->gl_driver == NULL);
|
|
self->gl_profiler = gsk_gl_profiler_new (self->gl_context);
|
|
self->gl_driver = gsk_gl_driver_new (self->gl_context);
|
|
|
|
GSK_RENDERER_NOTE (renderer, OPENGL, g_message ("Creating buffers and programs"));
|
|
if (!gsk_gl_renderer_create_programs (self, error))
|
|
return FALSE;
|
|
|
|
gsk_gl_glyph_cache_init (&self->glyph_cache, renderer, self->gl_driver);
|
|
gsk_gl_shadow_cache_init (&self->shadow_cache);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_unrealize (GskRenderer *renderer)
|
|
{
|
|
GskGLRenderer *self = GSK_GL_RENDERER (renderer);
|
|
guint i;
|
|
|
|
if (self->gl_context == NULL)
|
|
return;
|
|
|
|
gdk_gl_context_make_current (self->gl_context);
|
|
|
|
/* We don't need to iterate to destroy the associated GL resources,
|
|
* as they will be dropped when we finalize the GskGLDriver
|
|
*/
|
|
g_array_set_size (self->render_ops, 0);
|
|
|
|
for (i = 0; i < GL_N_PROGRAMS; i ++)
|
|
glDeleteProgram (self->programs[i].id);
|
|
|
|
gsk_gl_glyph_cache_free (&self->glyph_cache);
|
|
gsk_gl_shadow_cache_free (&self->shadow_cache, self->gl_driver);
|
|
|
|
g_clear_object (&self->gl_profiler);
|
|
g_clear_object (&self->gl_driver);
|
|
|
|
if (self->gl_context == gdk_gl_context_get_current ())
|
|
gdk_gl_context_clear_current ();
|
|
|
|
g_clear_object (&self->gl_context);
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_clear_tree (GskGLRenderer *self)
|
|
{
|
|
int removed_textures;
|
|
|
|
if (self->gl_context == NULL)
|
|
return;
|
|
|
|
gdk_gl_context_make_current (self->gl_context);
|
|
|
|
g_array_remove_range (self->render_ops, 0, self->render_ops->len);
|
|
removed_textures = gsk_gl_driver_collect_textures (self->gl_driver);
|
|
|
|
GSK_RENDERER_NOTE (GSK_RENDERER (self), OPENGL, g_message ("Collected: %d textures", removed_textures));
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_clear (GskGLRenderer *self)
|
|
{
|
|
GSK_RENDERER_NOTE (GSK_RENDERER (self), OPENGL, g_message ("Clearing viewport"));
|
|
glClearColor (0, 0, 0, 0);
|
|
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_setup_render_mode (GskGLRenderer *self)
|
|
{
|
|
if (self->render_region == NULL)
|
|
{
|
|
glDisable (GL_SCISSOR_TEST);
|
|
}
|
|
else
|
|
{
|
|
GdkSurface *surface = gsk_renderer_get_surface (GSK_RENDERER (self));
|
|
cairo_rectangle_int_t extents;
|
|
int surface_height;
|
|
|
|
g_assert (cairo_region_num_rectangles (self->render_region) == 1);
|
|
|
|
surface_height = gdk_surface_get_height (surface) * self->scale_factor;
|
|
cairo_region_get_rectangle (self->render_region, 0, &extents);
|
|
|
|
glEnable (GL_SCISSOR_TEST);
|
|
glScissor (extents.x * self->scale_factor,
|
|
surface_height - (extents.height * self->scale_factor) - (extents.y * self->scale_factor),
|
|
extents.width * self->scale_factor,
|
|
extents.height * self->scale_factor);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
gsk_gl_renderer_add_render_ops (GskGLRenderer *self,
|
|
GskRenderNode *node,
|
|
RenderOpBuilder *builder)
|
|
{
|
|
const float min_x = builder->dx + node->bounds.origin.x;
|
|
const float min_y = builder->dy + node->bounds.origin.y;
|
|
const float max_x = min_x + node->bounds.size.width;
|
|
const float max_y = min_y + node->bounds.size.height;
|
|
/* Default vertex data */
|
|
const GskQuadVertex vertex_data[GL_N_VERTICES] = {
|
|
{ { min_x, min_y }, { 0, 0 }, },
|
|
{ { min_x, max_y }, { 0, 1 }, },
|
|
{ { max_x, min_y }, { 1, 0 }, },
|
|
|
|
{ { max_x, max_y }, { 1, 1 }, },
|
|
{ { min_x, max_y }, { 0, 1 }, },
|
|
{ { max_x, min_y }, { 1, 0 }, },
|
|
};
|
|
|
|
/* This can still happen, even if the render nodes are created using
|
|
* GtkSnapshot, so let's juse be safe. */
|
|
if (node->bounds.size.width == 0.0f || node->bounds.size.height == 0.0f)
|
|
return;
|
|
|
|
/* Check whether the render node is entirely out of the current
|
|
* already transformed clip region */
|
|
{
|
|
graphene_rect_t transformed_node_bounds;
|
|
|
|
ops_transform_bounds_modelview (builder,
|
|
&node->bounds,
|
|
&transformed_node_bounds);
|
|
|
|
if (!graphene_rect_intersection (&builder->current_clip->bounds,
|
|
&transformed_node_bounds, NULL))
|
|
return;
|
|
}
|
|
|
|
switch (gsk_render_node_get_node_type (node))
|
|
{
|
|
case GSK_NOT_A_RENDER_NODE:
|
|
g_assert_not_reached ();
|
|
|
|
case GSK_CONTAINER_NODE:
|
|
{
|
|
guint i, p;
|
|
|
|
for (i = 0, p = gsk_container_node_get_n_children (node); i < p; i ++)
|
|
{
|
|
GskRenderNode *child = gsk_container_node_get_child (node, i);
|
|
|
|
gsk_gl_renderer_add_render_ops (self, child, builder);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case GSK_DEBUG_NODE:
|
|
gsk_gl_renderer_add_render_ops (self,
|
|
gsk_debug_node_get_child (node),
|
|
builder);
|
|
break;
|
|
|
|
case GSK_COLOR_NODE:
|
|
render_color_node (self, node, builder, vertex_data);
|
|
break;
|
|
|
|
case GSK_TEXTURE_NODE:
|
|
render_texture_node (self, node, builder);
|
|
break;
|
|
|
|
case GSK_OFFSET_NODE:
|
|
render_offset_node (self, node, builder);
|
|
break;
|
|
|
|
case GSK_TRANSFORM_NODE:
|
|
render_transform_node (self, node, builder);
|
|
break;
|
|
|
|
case GSK_OPACITY_NODE:
|
|
render_opacity_node (self, node, builder);
|
|
break;
|
|
|
|
case GSK_LINEAR_GRADIENT_NODE:
|
|
render_linear_gradient_node (self, node, builder, vertex_data);
|
|
break;
|
|
|
|
case GSK_CLIP_NODE:
|
|
render_clip_node (self, node, builder);
|
|
break;
|
|
|
|
case GSK_ROUNDED_CLIP_NODE:
|
|
render_rounded_clip_node (self, node, builder);
|
|
break;
|
|
|
|
case GSK_TEXT_NODE:
|
|
render_text_node (self, node, builder,
|
|
gsk_text_node_peek_color (node), FALSE);
|
|
break;
|
|
|
|
case GSK_COLOR_MATRIX_NODE:
|
|
render_color_matrix_node (self, node, builder, vertex_data);
|
|
break;
|
|
|
|
case GSK_BLUR_NODE:
|
|
render_blur_node (self, node, builder, vertex_data);
|
|
break;
|
|
|
|
case GSK_INSET_SHADOW_NODE:
|
|
render_inset_shadow_node (self, node, builder, vertex_data);
|
|
break;
|
|
|
|
case GSK_OUTSET_SHADOW_NODE:
|
|
if (gsk_outset_shadow_node_get_blur_radius (node) > 0)
|
|
render_outset_shadow_node (self, node, builder);
|
|
else
|
|
render_unblurred_outset_shadow_node (self, node, builder, vertex_data);
|
|
break;
|
|
|
|
case GSK_SHADOW_NODE:
|
|
render_shadow_node (self, node, builder, vertex_data);
|
|
break;
|
|
|
|
case GSK_BORDER_NODE:
|
|
render_border_node (self, node, builder);
|
|
break;
|
|
|
|
case GSK_CROSS_FADE_NODE:
|
|
render_cross_fade_node (self, node, builder);
|
|
break;
|
|
|
|
case GSK_REPEATING_LINEAR_GRADIENT_NODE:
|
|
case GSK_BLEND_NODE:
|
|
case GSK_REPEAT_NODE:
|
|
case GSK_CAIRO_NODE:
|
|
default:
|
|
{
|
|
render_fallback_node (self, node, builder, vertex_data);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
add_offscreen_ops (GskGLRenderer *self,
|
|
RenderOpBuilder *builder,
|
|
const graphene_rect_t *bounds,
|
|
GskRenderNode *child_node,
|
|
int *texture_id_out,
|
|
gboolean *is_offscreen,
|
|
guint flags)
|
|
{
|
|
const float scale = ops_get_scale (builder);
|
|
const float width = bounds->size.width * scale;
|
|
const float height = bounds->size.height * scale;
|
|
const float dx = builder->dx;
|
|
const float dy = builder->dy;
|
|
int render_target;
|
|
int prev_render_target;
|
|
RenderOp op;
|
|
graphene_matrix_t identity;
|
|
graphene_matrix_t prev_projection;
|
|
graphene_rect_t prev_viewport;
|
|
graphene_matrix_t item_proj;
|
|
float prev_opacity;
|
|
int texture_id = 0;
|
|
|
|
/* We need the child node as a texture. If it already is one, we don't need to draw
|
|
* it on a framebuffer of course. */
|
|
if (gsk_render_node_get_node_type (child_node) == GSK_TEXTURE_NODE &&
|
|
(flags & FORCE_OFFSCREEN) == 0)
|
|
{
|
|
GdkTexture *texture = gsk_texture_node_get_texture (child_node);
|
|
int gl_min_filter = GL_NEAREST, gl_mag_filter = GL_NEAREST;
|
|
|
|
get_gl_scaling_filters (child_node, &gl_min_filter, &gl_mag_filter);
|
|
|
|
*texture_id_out = gsk_gl_driver_get_texture_for_texture (self->gl_driver,
|
|
texture,
|
|
gl_min_filter,
|
|
gl_mag_filter);
|
|
*is_offscreen = FALSE;
|
|
return;
|
|
}
|
|
|
|
/* Check if we've already cached the drawn texture. */
|
|
{
|
|
const int cached_id = gsk_gl_driver_get_texture_for_pointer (self->gl_driver, child_node);
|
|
|
|
if (cached_id != 0)
|
|
{
|
|
*texture_id_out = cached_id;
|
|
/* We didn't render it offscreen, but hand out an offscreen texture id */
|
|
*is_offscreen = TRUE;
|
|
return;
|
|
}
|
|
}
|
|
|
|
texture_id = gsk_gl_driver_create_texture (self->gl_driver, width, height);
|
|
gsk_gl_driver_bind_source_texture (self->gl_driver, texture_id);
|
|
gsk_gl_driver_init_texture_empty (self->gl_driver, texture_id);
|
|
render_target = gsk_gl_driver_create_render_target (self->gl_driver, texture_id, TRUE, TRUE);
|
|
|
|
graphene_matrix_init_ortho (&item_proj,
|
|
bounds->origin.x * scale,
|
|
(bounds->origin.x + bounds->size.width) * scale,
|
|
bounds->origin.y * scale,
|
|
(bounds->origin.y + bounds->size.height) * scale,
|
|
ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE);
|
|
graphene_matrix_scale (&item_proj, 1, -1, 1);
|
|
graphene_matrix_init_identity (&identity);
|
|
graphene_matrix_scale (&identity, scale, scale, 1);
|
|
|
|
prev_render_target = ops_set_render_target (builder, render_target);
|
|
/* Clear since we use this rendertarget for the first time */
|
|
op.op = OP_CLEAR;
|
|
ops_add (builder, &op);
|
|
prev_projection = ops_set_projection (builder, &item_proj);
|
|
ops_push_modelview (builder, &identity);
|
|
prev_viewport = ops_set_viewport (builder,
|
|
&GRAPHENE_RECT_INIT (bounds->origin.x * scale,
|
|
bounds->origin.y * scale,
|
|
width, height));
|
|
if (flags & RESET_CLIP)
|
|
ops_push_clip (builder,
|
|
&GSK_ROUNDED_RECT_INIT (bounds->origin.x * scale,
|
|
bounds->origin.y * scale,
|
|
width, height));
|
|
|
|
builder->dx = 0;
|
|
builder->dy = 0;
|
|
if (flags & RESET_OPACITY)
|
|
prev_opacity = ops_set_opacity (builder, 1.0);
|
|
|
|
gsk_gl_renderer_add_render_ops (self, child_node, builder);
|
|
|
|
if (flags & RESET_OPACITY)
|
|
ops_set_opacity (builder, prev_opacity);
|
|
|
|
builder->dx = dx;
|
|
builder->dy = dy;
|
|
|
|
if (flags & RESET_CLIP)
|
|
ops_pop_clip (builder);
|
|
|
|
ops_set_viewport (builder, &prev_viewport);
|
|
ops_pop_modelview (builder);
|
|
ops_set_projection (builder, &prev_projection);
|
|
ops_set_render_target (builder, prev_render_target);
|
|
|
|
*is_offscreen = TRUE;
|
|
*texture_id_out = texture_id;
|
|
|
|
gsk_gl_driver_set_texture_for_pointer (self->gl_driver, child_node, texture_id);
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_render_ops (GskGLRenderer *self,
|
|
gsize vertex_data_size)
|
|
{
|
|
guint i;
|
|
guint n_ops = self->render_ops->len;
|
|
const Program *program = NULL;
|
|
gsize buffer_index = 0;
|
|
float *vertex_data = g_malloc (vertex_data_size);
|
|
|
|
/*g_message ("%s: Buffer size: %ld", __FUNCTION__, vertex_data_size);*/
|
|
|
|
|
|
GLuint buffer_id, vao_id;
|
|
glGenVertexArrays (1, &vao_id);
|
|
glBindVertexArray (vao_id);
|
|
|
|
glGenBuffers (1, &buffer_id);
|
|
glBindBuffer (GL_ARRAY_BUFFER, buffer_id);
|
|
|
|
|
|
// Fill buffer data
|
|
for (i = 0; i < n_ops; i ++)
|
|
{
|
|
const RenderOp *op = &g_array_index (self->render_ops, RenderOp, i);
|
|
|
|
if (op->op == OP_CHANGE_VAO)
|
|
{
|
|
memcpy (vertex_data + buffer_index, &op->vertex_data, sizeof (GskQuadVertex) * GL_N_VERTICES);
|
|
buffer_index += sizeof (GskQuadVertex) * GL_N_VERTICES / sizeof (float);
|
|
}
|
|
}
|
|
|
|
// Set buffer data
|
|
glBufferData (GL_ARRAY_BUFFER, vertex_data_size, vertex_data, GL_STATIC_DRAW);
|
|
|
|
// Describe buffer contents
|
|
|
|
/* 0 = position location */
|
|
glEnableVertexAttribArray (0);
|
|
glVertexAttribPointer (0, 2, GL_FLOAT, GL_FALSE,
|
|
sizeof (GskQuadVertex),
|
|
(void *) G_STRUCT_OFFSET (GskQuadVertex, position));
|
|
/* 1 = texture coord location */
|
|
glEnableVertexAttribArray (1);
|
|
glVertexAttribPointer (1, 2, GL_FLOAT, GL_FALSE,
|
|
sizeof (GskQuadVertex),
|
|
(void *) G_STRUCT_OFFSET (GskQuadVertex, uv));
|
|
|
|
for (i = 0; i < n_ops; i ++)
|
|
{
|
|
const RenderOp *op = &g_array_index (self->render_ops, RenderOp, i);
|
|
|
|
if (op->op == OP_NONE ||
|
|
op->op == OP_CHANGE_VAO)
|
|
continue;
|
|
|
|
if (op->op != OP_CHANGE_PROGRAM &&
|
|
op->op != OP_CHANGE_RENDER_TARGET &&
|
|
op->op != OP_CLEAR &&
|
|
program == NULL)
|
|
continue;
|
|
|
|
OP_PRINT ("Op %u: %u", i, op->op);
|
|
|
|
switch (op->op)
|
|
{
|
|
case OP_CHANGE_PROJECTION:
|
|
apply_projection_op (program, op);
|
|
break;
|
|
|
|
case OP_CHANGE_MODELVIEW:
|
|
apply_modelview_op (program, op);
|
|
break;
|
|
|
|
case OP_CHANGE_PROGRAM:
|
|
apply_program_op (program, op);
|
|
program = op->program;
|
|
break;
|
|
|
|
case OP_CHANGE_RENDER_TARGET:
|
|
apply_render_target_op (self, program, op);
|
|
break;
|
|
|
|
case OP_CLEAR:
|
|
glClearColor (0, 0, 0, 0);
|
|
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
|
break;
|
|
|
|
case OP_CHANGE_VIEWPORT:
|
|
apply_viewport_op (program, op);
|
|
break;
|
|
|
|
case OP_CHANGE_OPACITY:
|
|
apply_opacity_op (program, op);
|
|
break;
|
|
|
|
case OP_CHANGE_COLOR_MATRIX:
|
|
apply_color_matrix_op (program, op);
|
|
break;
|
|
|
|
case OP_CHANGE_COLOR:
|
|
/*g_assert (program == &self->color_program || program == &self->coloring_program ||*/
|
|
/*program == &self->shadow_program);*/
|
|
apply_color_op (program, op);
|
|
break;
|
|
|
|
case OP_CHANGE_BORDER_COLOR:
|
|
apply_border_color_op (program, op);
|
|
break;
|
|
|
|
case OP_CHANGE_CLIP:
|
|
apply_clip_op (program, op);
|
|
break;
|
|
|
|
case OP_CHANGE_SOURCE_TEXTURE:
|
|
apply_source_texture_op (program, op);
|
|
break;
|
|
|
|
case OP_CHANGE_CROSS_FADE:
|
|
g_assert (program == &self->cross_fade_program);
|
|
apply_cross_fade_op (program, op);
|
|
break;
|
|
|
|
case OP_CHANGE_LINEAR_GRADIENT:
|
|
apply_linear_gradient_op (program, op);
|
|
break;
|
|
|
|
case OP_CHANGE_BLUR:
|
|
apply_blur_op (program, op);
|
|
break;
|
|
|
|
case OP_CHANGE_INSET_SHADOW:
|
|
apply_inset_shadow_op (program, op);
|
|
break;
|
|
|
|
case OP_CHANGE_OUTSET_SHADOW:
|
|
apply_outset_shadow_op (program, op);
|
|
break;
|
|
|
|
case OP_CHANGE_BORDER:
|
|
apply_border_op (program, op);
|
|
break;
|
|
|
|
case OP_CHANGE_BORDER_WIDTH:
|
|
apply_border_width_op (program, op);
|
|
break;
|
|
|
|
case OP_CHANGE_UNBLURRED_OUTSET_SHADOW:
|
|
apply_unblurred_outset_shadow_op (program, op);
|
|
break;
|
|
|
|
case OP_DRAW:
|
|
OP_PRINT (" -> draw %ld, size %ld and program %d\n",
|
|
op->draw.vao_offset, op->draw.vao_size, program->index);
|
|
glDrawArrays (GL_TRIANGLES, op->draw.vao_offset, op->draw.vao_size);
|
|
break;
|
|
|
|
case OP_DUMP_FRAMEBUFFER:
|
|
dump_framebuffer (op->dump.filename, op->dump.width, op->dump.height);
|
|
break;
|
|
|
|
default:
|
|
g_warn_if_reached ();
|
|
}
|
|
|
|
OP_PRINT ("\n");
|
|
}
|
|
|
|
/* Done drawing, destroy the buffer again.
|
|
* TODO: Can we reuse the memory, though? */
|
|
g_free (vertex_data);
|
|
glDeleteVertexArrays (1, &vao_id);
|
|
glDeleteBuffers (1, &buffer_id);
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_do_render (GskRenderer *renderer,
|
|
GskRenderNode *root,
|
|
const graphene_rect_t *viewport,
|
|
int fbo_id,
|
|
int scale_factor)
|
|
{
|
|
GskGLRenderer *self = GSK_GL_RENDERER (renderer);
|
|
RenderOpBuilder render_op_builder;
|
|
graphene_matrix_t modelview, projection;
|
|
#ifdef G_ENABLE_DEBUG
|
|
GskProfiler *profiler;
|
|
gint64 gpu_time, cpu_time;
|
|
#endif
|
|
|
|
#ifdef G_ENABLE_DEBUG
|
|
profiler = gsk_renderer_get_profiler (renderer);
|
|
#endif
|
|
|
|
if (self->gl_context == NULL)
|
|
{
|
|
GSK_RENDERER_NOTE (renderer, OPENGL, g_message ("No valid GL context associated to the renderer"));
|
|
return;
|
|
}
|
|
|
|
/* Set up the modelview and projection matrices to fit our viewport */
|
|
graphene_matrix_init_scale (&modelview, scale_factor, scale_factor, 1.0);
|
|
graphene_matrix_init_ortho (&projection,
|
|
viewport->origin.x,
|
|
viewport->origin.x + viewport->size.width,
|
|
viewport->origin.y,
|
|
viewport->origin.y + viewport->size.height,
|
|
ORTHO_NEAR_PLANE,
|
|
ORTHO_FAR_PLANE);
|
|
graphene_matrix_scale (&projection, 1, -1, 1);
|
|
|
|
gsk_gl_driver_begin_frame (self->gl_driver);
|
|
gsk_gl_glyph_cache_begin_frame (&self->glyph_cache);
|
|
gsk_gl_shadow_cache_begin_frame (&self->shadow_cache, self->gl_driver);
|
|
|
|
memset (&render_op_builder, 0, sizeof (render_op_builder));
|
|
render_op_builder.renderer = self;
|
|
render_op_builder.current_projection = projection;
|
|
render_op_builder.current_viewport = *viewport;
|
|
render_op_builder.current_opacity = 1.0f;
|
|
render_op_builder.render_ops = self->render_ops;
|
|
ops_push_modelview (&render_op_builder, &modelview);
|
|
|
|
/* Initial clip is self->render_region! */
|
|
if (self->render_region != NULL)
|
|
{
|
|
GskRoundedRect transformed_render_region = { 0, };
|
|
cairo_rectangle_int_t render_extents;
|
|
|
|
cairo_region_get_extents (self->render_region, &render_extents);
|
|
|
|
ops_transform_bounds_modelview (&render_op_builder,
|
|
&GRAPHENE_RECT_INIT (render_extents.x,
|
|
render_extents.y,
|
|
render_extents.width,
|
|
render_extents.height),
|
|
&transformed_render_region.bounds);
|
|
ops_push_clip (&render_op_builder, &transformed_render_region);
|
|
}
|
|
else
|
|
{
|
|
ops_push_clip (&render_op_builder,
|
|
&GSK_ROUNDED_RECT_INIT (viewport->origin.x,
|
|
viewport->origin.y,
|
|
viewport->size.width,
|
|
viewport->size.height));
|
|
}
|
|
|
|
if (fbo_id != 0)
|
|
ops_set_render_target (&render_op_builder, fbo_id);
|
|
|
|
gsk_gl_renderer_add_render_ops (self, root, &render_op_builder);
|
|
|
|
/* We correctly reset the state everywhere */
|
|
g_assert_cmpint (render_op_builder.current_render_target, ==, fbo_id);
|
|
ops_pop_modelview (&render_op_builder);
|
|
ops_pop_clip (&render_op_builder);
|
|
ops_finish (&render_op_builder);
|
|
|
|
/*g_message ("Ops: %u", self->render_ops->len);*/
|
|
|
|
/* Now actually draw things... */
|
|
#ifdef G_ENABLE_DEBUG
|
|
gsk_gl_profiler_begin_gpu_region (self->gl_profiler);
|
|
gsk_profiler_timer_begin (profiler, self->profile_timers.cpu_time);
|
|
#endif
|
|
|
|
glViewport (0, 0, ceilf (viewport->size.width), ceilf (viewport->size.height));
|
|
gsk_gl_renderer_setup_render_mode (self);
|
|
gsk_gl_renderer_clear (self);
|
|
|
|
glEnable (GL_DEPTH_TEST);
|
|
glDepthFunc (GL_LEQUAL);
|
|
|
|
/* Pre-multiplied alpha! */
|
|
glEnable (GL_BLEND);
|
|
glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
|
glBlendEquation (GL_FUNC_ADD);
|
|
|
|
gsk_gl_renderer_render_ops (self, render_op_builder.buffer_size);
|
|
|
|
gsk_gl_driver_end_frame (self->gl_driver);
|
|
|
|
#ifdef G_ENABLE_DEBUG
|
|
gsk_profiler_counter_inc (profiler, self->profile_counters.frames);
|
|
|
|
cpu_time = gsk_profiler_timer_end (profiler, self->profile_timers.cpu_time);
|
|
gsk_profiler_timer_set (profiler, self->profile_timers.cpu_time, cpu_time);
|
|
|
|
gpu_time = gsk_gl_profiler_end_gpu_region (self->gl_profiler);
|
|
gsk_profiler_timer_set (profiler, self->profile_timers.gpu_time, gpu_time);
|
|
|
|
gsk_profiler_push_samples (profiler);
|
|
#endif
|
|
}
|
|
|
|
static GdkTexture *
|
|
gsk_gl_renderer_render_texture (GskRenderer *renderer,
|
|
GskRenderNode *root,
|
|
const graphene_rect_t *viewport)
|
|
{
|
|
GskGLRenderer *self = GSK_GL_RENDERER (renderer);
|
|
GdkTexture *texture;
|
|
int width, height;
|
|
guint texture_id;
|
|
guint fbo_id;
|
|
|
|
g_return_val_if_fail (self->gl_context != NULL, NULL);
|
|
|
|
width = ceilf (viewport->size.width);
|
|
height = ceilf (viewport->size.height);
|
|
|
|
self->scale_factor = gdk_surface_get_scale_factor (gsk_renderer_get_surface (renderer));
|
|
gdk_gl_context_make_current (self->gl_context);
|
|
|
|
/* Prepare our framebuffer */
|
|
gsk_gl_driver_begin_frame (self->gl_driver);
|
|
glGenTextures (1, &texture_id);
|
|
glBindTexture (GL_TEXTURE_2D, texture_id);
|
|
|
|
if (gdk_gl_context_get_use_es (self->gl_context))
|
|
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
|
else
|
|
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
|
|
|
|
glGenFramebuffers (1, &fbo_id);
|
|
glBindFramebuffer (GL_FRAMEBUFFER, fbo_id);
|
|
glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_id, 0);
|
|
g_assert_cmphex (glCheckFramebufferStatus (GL_FRAMEBUFFER), ==, GL_FRAMEBUFFER_COMPLETE);
|
|
|
|
gsk_gl_renderer_clear (self);
|
|
gsk_gl_driver_end_frame (self->gl_driver);
|
|
|
|
/* Render the actual scene */
|
|
gsk_gl_renderer_do_render (renderer, root, viewport, fbo_id, 1);
|
|
|
|
texture = gdk_gl_texture_new (self->gl_context,
|
|
texture_id,
|
|
width, height,
|
|
NULL, NULL);
|
|
|
|
gsk_gl_renderer_clear_tree (self);
|
|
return texture;
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_render (GskRenderer *renderer,
|
|
GskRenderNode *root,
|
|
const cairo_region_t *update_area)
|
|
{
|
|
GskGLRenderer *self = GSK_GL_RENDERER (renderer);
|
|
graphene_rect_t viewport;
|
|
const cairo_region_t *damage;
|
|
GdkRectangle whole_surface;
|
|
GdkSurface *surface;
|
|
|
|
if (self->gl_context == NULL)
|
|
return;
|
|
|
|
surface = gsk_renderer_get_surface (renderer);
|
|
whole_surface = (GdkRectangle) {
|
|
0, 0,
|
|
gdk_surface_get_width (surface) * self->scale_factor,
|
|
gdk_surface_get_height (surface) * self->scale_factor
|
|
};
|
|
|
|
gdk_draw_context_begin_frame (GDK_DRAW_CONTEXT (self->gl_context),
|
|
update_area);
|
|
|
|
damage = gdk_draw_context_get_frame_region (GDK_DRAW_CONTEXT (self->gl_context));
|
|
|
|
if (cairo_region_contains_rectangle (damage, &whole_surface) == CAIRO_REGION_OVERLAP_IN)
|
|
{
|
|
self->render_region = NULL;
|
|
}
|
|
else
|
|
{
|
|
GdkRectangle extents;
|
|
|
|
cairo_region_get_extents (damage, &extents);
|
|
|
|
if (gdk_rectangle_equal (&extents, &whole_surface))
|
|
self->render_region = NULL;
|
|
else
|
|
self->render_region = cairo_region_create_rectangle (&extents);
|
|
}
|
|
|
|
self->scale_factor = gdk_surface_get_scale_factor (surface);
|
|
gdk_gl_context_make_current (self->gl_context);
|
|
|
|
viewport.origin.x = 0;
|
|
viewport.origin.y = 0;
|
|
viewport.size.width = gdk_surface_get_width (surface) * self->scale_factor;
|
|
viewport.size.height = gdk_surface_get_height (surface) * self->scale_factor;
|
|
|
|
gsk_gl_renderer_do_render (renderer, root, &viewport, 0, self->scale_factor);
|
|
|
|
gdk_gl_context_make_current (self->gl_context);
|
|
gsk_gl_renderer_clear_tree (self);
|
|
|
|
gdk_draw_context_end_frame (GDK_DRAW_CONTEXT (self->gl_context));
|
|
|
|
g_clear_pointer (&self->render_region, cairo_region_destroy);
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_class_init (GskGLRendererClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
GskRendererClass *renderer_class = GSK_RENDERER_CLASS (klass);
|
|
|
|
gobject_class->dispose = gsk_gl_renderer_dispose;
|
|
|
|
renderer_class->realize = gsk_gl_renderer_realize;
|
|
renderer_class->unrealize = gsk_gl_renderer_unrealize;
|
|
renderer_class->render = gsk_gl_renderer_render;
|
|
renderer_class->render_texture = gsk_gl_renderer_render_texture;
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_init (GskGLRenderer *self)
|
|
{
|
|
gsk_ensure_resources ();
|
|
|
|
self->render_ops = g_array_new (FALSE, FALSE, sizeof (RenderOp));
|
|
|
|
#ifdef G_ENABLE_DEBUG
|
|
{
|
|
GskProfiler *profiler = gsk_renderer_get_profiler (GSK_RENDERER (self));
|
|
|
|
self->profile_counters.frames = gsk_profiler_add_counter (profiler, "frames", "Frames", FALSE);
|
|
self->profile_counters.draw_calls = gsk_profiler_add_counter (profiler, "draws", "glDrawArrays", TRUE);
|
|
|
|
self->profile_timers.cpu_time = gsk_profiler_add_timer (profiler, "cpu-time", "CPU time", FALSE, TRUE);
|
|
self->profile_timers.gpu_time = gsk_profiler_add_timer (profiler, "gpu-time", "GPU time", FALSE, TRUE);
|
|
}
|
|
#endif
|
|
}
|