forked from AuroraMiddleware/gtk
b76c5abb57
Rename the getter to follow the peek naming scheme. Update all callers.
1269 lines
38 KiB
C
1269 lines
38 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 "gsktextureprivate.h"
|
|
|
|
#include "gskprivate.h"
|
|
|
|
#include <epoxy/gl.h>
|
|
|
|
#define SHADER_VERSION_GLES 100
|
|
#define SHADER_VERSION_GL2_LEGACY 110
|
|
#define SHADER_VERSION_GL3_LEGACY 130
|
|
#define SHADER_VERSION_GL3 150
|
|
|
|
typedef struct {
|
|
int id;
|
|
/* Common locations (gl_common)*/
|
|
int mvp_location;
|
|
int source_location;
|
|
int mask_location;
|
|
int uv_location;
|
|
int position_location;
|
|
int alpha_location;
|
|
int blendMode_location;
|
|
|
|
/* Shader-specific locations */
|
|
union {
|
|
struct {
|
|
int color_location;
|
|
};
|
|
};
|
|
} Program;
|
|
|
|
typedef struct {
|
|
int render_target_id;
|
|
int vao_id;
|
|
int buffer_id;
|
|
int texture_id;
|
|
int program_id;
|
|
|
|
Program *program;
|
|
} RenderData;
|
|
|
|
enum {
|
|
MODE_COLOR = 1,
|
|
MODE_TEXTURE,
|
|
N_MODES
|
|
};
|
|
|
|
typedef struct {
|
|
int mode;
|
|
/* Back pointer to the node, only meant for comparison */
|
|
GskRenderNode *node;
|
|
|
|
graphene_point3d_t min;
|
|
graphene_point3d_t max;
|
|
|
|
graphene_size_t size;
|
|
|
|
graphene_matrix_t mvp;
|
|
|
|
float opacity;
|
|
float z;
|
|
|
|
union {
|
|
struct {
|
|
GdkRGBA color;
|
|
} color_data;
|
|
struct {
|
|
int a,b;
|
|
} texture_data;
|
|
};
|
|
|
|
const char *name;
|
|
|
|
GskBlendMode blend_mode;
|
|
|
|
RenderData render_data;
|
|
RenderData *parent_data;
|
|
|
|
GArray *children;
|
|
} RenderItem;
|
|
|
|
|
|
|
|
enum {
|
|
MVP,
|
|
SOURCE,
|
|
MASK,
|
|
ALPHA,
|
|
BLEND_MODE,
|
|
N_UNIFORMS
|
|
};
|
|
|
|
enum {
|
|
POSITION,
|
|
UV,
|
|
N_ATTRIBUTES
|
|
};
|
|
|
|
#ifdef G_ENABLE_DEBUG
|
|
typedef struct {
|
|
GQuark frames;
|
|
GQuark draw_calls;
|
|
} ProfileCounters;
|
|
|
|
typedef struct {
|
|
GQuark cpu_time;
|
|
GQuark gpu_time;
|
|
} ProfileTimers;
|
|
#endif
|
|
|
|
typedef enum {
|
|
RENDER_FULL,
|
|
RENDER_SCISSOR
|
|
} RenderMode;
|
|
|
|
#define NUM_PROGRAMS 3
|
|
|
|
struct _GskGLRenderer
|
|
{
|
|
GskRenderer parent_instance;
|
|
|
|
graphene_matrix_t mvp;
|
|
graphene_frustum_t frustum;
|
|
|
|
guint frame_buffer;
|
|
guint depth_stencil_buffer;
|
|
guint texture_id;
|
|
|
|
GQuark uniforms[N_UNIFORMS];
|
|
GQuark attributes[N_ATTRIBUTES];
|
|
|
|
GdkGLContext *gl_context;
|
|
GskGLDriver *gl_driver;
|
|
GskGLProfiler *gl_profiler;
|
|
GskShaderBuilder *shader_builder;
|
|
|
|
union {
|
|
struct {
|
|
Program blend_program;
|
|
Program blit_program;
|
|
Program color_program;
|
|
};
|
|
struct {
|
|
Program programs[NUM_PROGRAMS];
|
|
};
|
|
};
|
|
|
|
GArray *render_items;
|
|
|
|
#ifdef G_ENABLE_DEBUG
|
|
ProfileCounters profile_counters;
|
|
ProfileTimers profile_timers;
|
|
#endif
|
|
|
|
RenderMode render_mode;
|
|
|
|
gboolean has_buffers : 1;
|
|
};
|
|
|
|
struct _GskGLRendererClass
|
|
{
|
|
GskRendererClass parent_class;
|
|
};
|
|
|
|
G_DEFINE_TYPE (GskGLRenderer, gsk_gl_renderer, GSK_TYPE_RENDERER)
|
|
|
|
static void
|
|
gsk_gl_renderer_dispose (GObject *gobject)
|
|
{
|
|
GskGLRenderer *self = GSK_GL_RENDERER (gobject);
|
|
|
|
g_clear_object (&self->gl_context);
|
|
g_clear_pointer (&self->render_items, g_array_unref);
|
|
|
|
G_OBJECT_CLASS (gsk_gl_renderer_parent_class)->dispose (gobject);
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_create_buffers (GskGLRenderer *self,
|
|
int width,
|
|
int height,
|
|
int scale_factor)
|
|
{
|
|
if (self->has_buffers)
|
|
return;
|
|
|
|
GSK_NOTE (OPENGL, g_print ("Creating buffers (w:%d, h:%d, scale:%d)\n", width, height, scale_factor));
|
|
|
|
if (self->texture_id == 0)
|
|
{
|
|
self->texture_id = gsk_gl_driver_create_texture (self->gl_driver,
|
|
width * scale_factor,
|
|
height * scale_factor);
|
|
gsk_gl_driver_bind_source_texture (self->gl_driver, self->texture_id);
|
|
gsk_gl_driver_init_texture_empty (self->gl_driver, self->texture_id);
|
|
}
|
|
|
|
gsk_gl_driver_create_render_target (self->gl_driver, self->texture_id, TRUE, TRUE);
|
|
|
|
self->has_buffers = TRUE;
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_destroy_buffers (GskGLRenderer *self)
|
|
{
|
|
if (self->gl_context == NULL)
|
|
return;
|
|
|
|
if (!self->has_buffers)
|
|
return;
|
|
|
|
GSK_NOTE (OPENGL, g_print ("Destroying buffers\n"));
|
|
|
|
gdk_gl_context_make_current (self->gl_context);
|
|
|
|
if (self->texture_id != 0)
|
|
{
|
|
gsk_gl_driver_destroy_texture (self->gl_driver, self->texture_id);
|
|
self->texture_id = 0;
|
|
}
|
|
|
|
self->has_buffers = FALSE;
|
|
}
|
|
|
|
static void
|
|
init_common_locations (GskGLRenderer *self,
|
|
Program *prog)
|
|
{
|
|
prog->source_location =
|
|
gsk_shader_builder_get_uniform_location (self->shader_builder, prog->id, self->uniforms[SOURCE]);
|
|
prog->mask_location =
|
|
gsk_shader_builder_get_uniform_location (self->shader_builder, prog->id, self->uniforms[MASK]);
|
|
prog->mvp_location =
|
|
gsk_shader_builder_get_uniform_location (self->shader_builder, prog->id, self->uniforms[MVP]);
|
|
prog->alpha_location =
|
|
gsk_shader_builder_get_uniform_location (self->shader_builder, prog->id, self->uniforms[ALPHA]);
|
|
prog->blendMode_location =
|
|
gsk_shader_builder_get_uniform_location (self->shader_builder, prog->id, self->uniforms[BLEND_MODE]);
|
|
|
|
prog->position_location =
|
|
gsk_shader_builder_get_attribute_location (self->shader_builder, prog->id, self->attributes[POSITION]);
|
|
prog->uv_location =
|
|
gsk_shader_builder_get_attribute_location (self->shader_builder, prog->id, self->attributes[UV]);
|
|
}
|
|
|
|
static gboolean
|
|
gsk_gl_renderer_create_programs (GskGLRenderer *self,
|
|
GError **error)
|
|
{
|
|
GskShaderBuilder *builder;
|
|
GError *shader_error = NULL;
|
|
gboolean res = FALSE;
|
|
|
|
builder = gsk_shader_builder_new ();
|
|
|
|
gsk_shader_builder_set_resource_base_path (builder, "/org/gtk/libgsk/glsl");
|
|
|
|
self->uniforms[MVP] = gsk_shader_builder_add_uniform (builder, "uMVP");
|
|
self->uniforms[SOURCE] = gsk_shader_builder_add_uniform (builder, "uSource");
|
|
self->uniforms[MASK] = gsk_shader_builder_add_uniform (builder, "uMask");
|
|
self->uniforms[ALPHA] = gsk_shader_builder_add_uniform (builder, "uAlpha");
|
|
self->uniforms[BLEND_MODE] = gsk_shader_builder_add_uniform (builder, "uBlendMode");
|
|
|
|
self->attributes[POSITION] = gsk_shader_builder_add_attribute (builder, "aPosition");
|
|
self->attributes[UV] = gsk_shader_builder_add_attribute (builder, "aUv");
|
|
|
|
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_RENDER_MODE_CHECK (SHADERS))
|
|
gsk_shader_builder_add_define (builder, "GSK_DEBUG", "1");
|
|
#endif
|
|
/* Keep a pointer to query for the uniform and attribute locations
|
|
* when rendering the scene
|
|
*/
|
|
self->shader_builder = builder;
|
|
|
|
self->blend_program.id =
|
|
gsk_shader_builder_create_program (builder, "blend.vs.glsl", "blend.fs.glsl", &shader_error);
|
|
if (shader_error != NULL)
|
|
{
|
|
g_propagate_prefixed_error (error,
|
|
shader_error,
|
|
"Unable to create 'blend' program: ");
|
|
g_object_unref (builder);
|
|
goto out;
|
|
}
|
|
init_common_locations (self, &self->blend_program);
|
|
|
|
self->blit_program.id =
|
|
gsk_shader_builder_create_program (builder, "blit.vs.glsl", "blit.fs.glsl", &shader_error);
|
|
if (shader_error != NULL)
|
|
{
|
|
g_propagate_prefixed_error (error,
|
|
shader_error,
|
|
"Unable to create 'blit' program: ");
|
|
g_object_unref (builder);
|
|
goto out;
|
|
}
|
|
init_common_locations (self, &self->blit_program);
|
|
|
|
self->color_program.id =
|
|
gsk_shader_builder_create_program (builder, "color.vs.glsl", "color.fs.glsl", &shader_error);
|
|
if (shader_error != NULL)
|
|
{
|
|
g_propagate_prefixed_error (error,
|
|
shader_error,
|
|
"Unable to create 'color' program: ");
|
|
g_object_unref (builder);
|
|
goto out;
|
|
}
|
|
init_common_locations (self, &self->color_program);
|
|
self->color_program.color_location = gsk_shader_builder_get_uniform_location (self->shader_builder,
|
|
self->color_program.id,
|
|
g_quark_from_string("uColor"));
|
|
self->color_program.color_location = glGetUniformLocation(self->color_program.id, "uColor");
|
|
g_assert(self->color_program.color_location >= 0);
|
|
|
|
res = TRUE;
|
|
|
|
out:
|
|
return res;
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_destroy_programs (GskGLRenderer *self)
|
|
{
|
|
g_clear_object (&self->shader_builder);
|
|
}
|
|
|
|
static gboolean
|
|
gsk_gl_renderer_realize (GskRenderer *renderer,
|
|
GdkWindow *window,
|
|
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_window_create_gl_context (window, 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_driver = gsk_gl_driver_new (self->gl_context);
|
|
self->gl_profiler = gsk_gl_profiler_new (self->gl_context);
|
|
|
|
GSK_NOTE (OPENGL, g_print ("Creating buffers and programs\n"));
|
|
if (!gsk_gl_renderer_create_programs (self, error))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_unrealize (GskRenderer *renderer)
|
|
{
|
|
GskGLRenderer *self = GSK_GL_RENDERER (renderer);
|
|
|
|
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_clear_pointer (&self->render_items, g_array_unref);
|
|
|
|
gsk_gl_renderer_destroy_buffers (self);
|
|
gsk_gl_renderer_destroy_programs (self);
|
|
|
|
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 ();
|
|
}
|
|
|
|
static GdkDrawingContext *
|
|
gsk_gl_renderer_begin_draw_frame (GskRenderer *renderer,
|
|
const cairo_region_t *update_area)
|
|
{
|
|
GskGLRenderer *self = GSK_GL_RENDERER (renderer);
|
|
cairo_region_t *damage;
|
|
GdkDrawingContext *result;
|
|
GdkRectangle whole_window;
|
|
GdkWindow *window;
|
|
|
|
window = gsk_renderer_get_window (renderer);
|
|
whole_window = (GdkRectangle) {
|
|
0, 0,
|
|
gdk_window_get_width (window),
|
|
gdk_window_get_height (window)
|
|
};
|
|
damage = gdk_gl_context_get_damage (self->gl_context);
|
|
cairo_region_union (damage, update_area);
|
|
|
|
if (cairo_region_contains_rectangle (damage, &whole_window) == CAIRO_REGION_OVERLAP_IN)
|
|
{
|
|
self->render_mode = RENDER_FULL;
|
|
}
|
|
else
|
|
{
|
|
GdkRectangle extents;
|
|
|
|
cairo_region_get_extents (damage, &extents);
|
|
cairo_region_union_rectangle (damage, &extents);
|
|
|
|
if (gdk_rectangle_equal (&extents, &whole_window))
|
|
self->render_mode = RENDER_FULL;
|
|
else
|
|
self->render_mode = RENDER_SCISSOR;
|
|
}
|
|
|
|
result = gdk_window_begin_draw_frame (window,
|
|
GDK_DRAW_CONTEXT (self->gl_context),
|
|
damage);
|
|
|
|
cairo_region_destroy (damage);
|
|
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_resize_viewport (GskGLRenderer *self,
|
|
const graphene_rect_t *viewport,
|
|
int scale_factor)
|
|
{
|
|
int width = viewport->size.width * scale_factor;
|
|
int height = viewport->size.height * scale_factor;
|
|
|
|
GSK_NOTE (OPENGL, g_print ("glViewport(0, 0, %d, %d) [scale:%d]\n",
|
|
width,
|
|
height,
|
|
scale_factor));
|
|
|
|
glViewport (0, 0, width, height);
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_update_frustum (GskGLRenderer *self,
|
|
const graphene_matrix_t *modelview,
|
|
const graphene_matrix_t *projection)
|
|
{
|
|
GSK_NOTE (TRANSFORMS, g_print ("Updating the modelview/projection\n"));
|
|
|
|
graphene_matrix_multiply (modelview, projection, &self->mvp);
|
|
|
|
graphene_frustum_init_from_matrix (&self->frustum, &self->mvp);
|
|
|
|
GSK_NOTE (TRANSFORMS,
|
|
g_print ("Renderer MVP:\n");
|
|
graphene_matrix_print (&self->mvp);
|
|
g_print ("\n"));
|
|
}
|
|
|
|
#define N_VERTICES 6
|
|
|
|
static void
|
|
render_item (GskGLRenderer *self,
|
|
RenderItem *item)
|
|
{
|
|
float mvp[16];
|
|
float opacity;
|
|
|
|
if (item->children != NULL)
|
|
{
|
|
if (gsk_gl_driver_bind_render_target (self->gl_driver, item->render_data.render_target_id))
|
|
{
|
|
glViewport (0, 0, item->size.width, item->size.height);
|
|
|
|
glClearColor (0.0, 0.0, 0.0, 0.0);
|
|
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
|
}
|
|
}
|
|
|
|
gsk_gl_driver_bind_vao (self->gl_driver, item->render_data.vao_id);
|
|
|
|
glUseProgram (item->render_data.program->id);
|
|
|
|
switch(item->mode)
|
|
{
|
|
case MODE_COLOR:
|
|
{
|
|
glUniform4f (item->render_data.program->color_location,
|
|
item->color_data.color.red,
|
|
item->color_data.color.green,
|
|
item->color_data.color.blue,
|
|
item->color_data.color.alpha);
|
|
}
|
|
break;
|
|
|
|
case MODE_TEXTURE:
|
|
{
|
|
g_assert(item->render_data.texture_id != 0);
|
|
/* Use texture unit 0 for the source */
|
|
glUniform1i (item->render_data.program->source_location, 0);
|
|
gsk_gl_driver_bind_source_texture (self->gl_driver, item->render_data.texture_id);
|
|
|
|
if (item->parent_data != NULL)
|
|
{
|
|
glUniform1i (item->render_data.program->blendMode_location, item->blend_mode);
|
|
|
|
/* Use texture unit 1 for the mask */
|
|
if (item->parent_data->texture_id != 0)
|
|
{
|
|
glUniform1i (item->render_data.program->mask_location, 1);
|
|
gsk_gl_driver_bind_mask_texture (self->gl_driver, item->parent_data->texture_id);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
/* Pass the opacity component */
|
|
if (item->children != NULL)
|
|
opacity = 1.0;
|
|
else
|
|
opacity = item->opacity;
|
|
|
|
glUniform1f (item->render_data.program->alpha_location, opacity);
|
|
|
|
/* Pass the mvp to the vertex shader */
|
|
GSK_NOTE (TRANSFORMS, graphene_matrix_print (&item->mvp));
|
|
graphene_matrix_to_float (&item->mvp, mvp);
|
|
glUniformMatrix4fv (item->render_data.program->mvp_location, 1, GL_FALSE, mvp);
|
|
|
|
/* Draw the quad */
|
|
GSK_NOTE2 (OPENGL, TRANSFORMS,
|
|
g_print ("Drawing item <%s>[%p] (w:%g, h:%g) with opacity: %g blend mode: %d\n",
|
|
item->name,
|
|
item,
|
|
item->size.width, item->size.height,
|
|
item->opacity,
|
|
item->blend_mode));
|
|
|
|
glDrawArrays (GL_TRIANGLES, 0, N_VERTICES);
|
|
|
|
#ifdef G_ENABLE_DEBUG
|
|
gsk_profiler_counter_inc (gsk_renderer_get_profiler (GSK_RENDERER (self)),
|
|
self->profile_counters.draw_calls);
|
|
#endif
|
|
|
|
/* Render all children items, so we can take the result
|
|
* render target texture during the compositing
|
|
*/
|
|
if (item->children != NULL)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < item->children->len; i++)
|
|
{
|
|
RenderItem *child = &g_array_index (item->children, RenderItem, i);
|
|
|
|
render_item (self, child);
|
|
}
|
|
|
|
/* Bind the parent render target */
|
|
if (item->parent_data != NULL)
|
|
gsk_gl_driver_bind_render_target (self->gl_driver, item->parent_data->render_target_id);
|
|
|
|
/* Bind the same VAO, as the render target is created with the same size
|
|
* and vertices as the texture target
|
|
*/
|
|
gsk_gl_driver_bind_vao (self->gl_driver, item->render_data.vao_id);
|
|
|
|
/* Since we're rendering the target texture, we only need the blit program */
|
|
glUseProgram (self->blit_program.id);
|
|
|
|
/* Use texture unit 0 for the render target */
|
|
glUniform1i (item->render_data.program->source_location, 0);
|
|
gsk_gl_driver_bind_source_texture (self->gl_driver, item->render_data.render_target_id);
|
|
|
|
/* Pass the opacity component; if we got here, we know that the original render
|
|
* target is neither fully opaque nor at full opacity
|
|
*/
|
|
glUniform1f (item->render_data.program->alpha_location, item->opacity);
|
|
|
|
/* Pass the mvp to the vertex shader */
|
|
GSK_NOTE (TRANSFORMS, graphene_matrix_print (&item->mvp));
|
|
graphene_matrix_to_float (&item->mvp, mvp);
|
|
glUniformMatrix4fv (item->render_data.program->mvp_location, 1, GL_FALSE, mvp);
|
|
|
|
/* Draw the quad */
|
|
GSK_NOTE2 (OPENGL, TRANSFORMS,
|
|
g_print ("Drawing offscreen item <%s>[%p] (w:%g, h:%g) with opacity: %g\n",
|
|
item->name,
|
|
item,
|
|
item->size.width, item->size.height,
|
|
item->opacity));
|
|
|
|
glDrawArrays (GL_TRIANGLES, 0, N_VERTICES);
|
|
}
|
|
}
|
|
|
|
static void
|
|
get_gl_scaling_filters (GskRenderNode *node,
|
|
int *min_filter_r,
|
|
int *mag_filter_r)
|
|
{
|
|
switch (node->min_filter)
|
|
{
|
|
case GSK_SCALING_FILTER_NEAREST:
|
|
*min_filter_r = GL_NEAREST;
|
|
break;
|
|
|
|
case GSK_SCALING_FILTER_LINEAR:
|
|
*min_filter_r = GL_LINEAR;
|
|
break;
|
|
|
|
case GSK_SCALING_FILTER_TRILINEAR:
|
|
*min_filter_r = GL_LINEAR_MIPMAP_LINEAR;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (node->mag_filter)
|
|
{
|
|
case GSK_SCALING_FILTER_NEAREST:
|
|
*mag_filter_r = GL_NEAREST;
|
|
break;
|
|
|
|
/* There's no point in using anything above GL_LINEAR for
|
|
* magnification filters
|
|
*/
|
|
case GSK_SCALING_FILTER_LINEAR:
|
|
case GSK_SCALING_FILTER_TRILINEAR:
|
|
default:
|
|
*mag_filter_r = GL_LINEAR;
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
static gboolean
|
|
check_in_frustum (const graphene_frustum_t *frustum,
|
|
RenderItem *item)
|
|
{
|
|
graphene_box_t aabb;
|
|
|
|
graphene_box_init (&aabb, &item->min, &item->max);
|
|
graphene_matrix_transform_box (&item->mvp, &aabb, &aabb);
|
|
|
|
return graphene_frustum_intersects_box (frustum, &aabb);
|
|
}
|
|
#endif
|
|
|
|
static float
|
|
project_item (const graphene_matrix_t *projection,
|
|
const graphene_matrix_t *modelview)
|
|
{
|
|
graphene_vec4_t vec;
|
|
|
|
graphene_matrix_get_row (modelview, 3, &vec);
|
|
graphene_matrix_transform_vec4 (projection, &vec, &vec);
|
|
|
|
return graphene_vec4_get_z (&vec) / graphene_vec4_get_w (&vec);
|
|
}
|
|
|
|
static gboolean
|
|
render_node_needs_render_target (GskRenderNode *node)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_add_render_item (GskGLRenderer *self,
|
|
const graphene_matrix_t *projection,
|
|
const graphene_matrix_t *modelview,
|
|
GArray *render_items,
|
|
GskRenderNode *node,
|
|
RenderItem *parent)
|
|
{
|
|
RenderItem item;
|
|
RenderItem *ritem = NULL;
|
|
int program_id;
|
|
int scale_factor;
|
|
|
|
memset (&item, 0, sizeof (RenderItem));
|
|
|
|
scale_factor = gsk_renderer_get_scale_factor (GSK_RENDERER (self));
|
|
if (scale_factor < 1)
|
|
scale_factor = 1;
|
|
|
|
item.node = node;
|
|
item.name = node->name != NULL ? node->name : "unnamed";
|
|
|
|
/* The texture size */
|
|
item.size.width = node->bounds.size.width * scale_factor;
|
|
item.size.height = node->bounds.size.height * scale_factor;
|
|
|
|
/* Each render item is an axis-aligned bounding box that we
|
|
* transform using the given transformation matrix
|
|
*/
|
|
item.min.x = node->bounds.origin.x;
|
|
item.min.y = node->bounds.origin.y;
|
|
item.min.z = 0.f;
|
|
|
|
item.max.x = item.min.x + node->bounds.size.width;
|
|
item.max.y = item.min.y + node->bounds.size.height;
|
|
item.max.z = 0.f;
|
|
|
|
/* The location of the item, in normalized world coordinates */
|
|
graphene_matrix_multiply (modelview, &self->mvp, &item.mvp);
|
|
item.z = project_item (projection, modelview);
|
|
|
|
item.opacity = 1.0;
|
|
|
|
item.blend_mode = GSK_BLEND_MODE_DEFAULT;
|
|
|
|
/* Back-pointer to the parent node */
|
|
if (parent != NULL)
|
|
item.parent_data = &(parent->render_data);
|
|
else
|
|
item.parent_data = NULL;
|
|
|
|
/* Select the program to use */
|
|
if (parent != NULL)
|
|
{
|
|
item.render_data.program = &self->blend_program;
|
|
program_id = self->blend_program.id;
|
|
}
|
|
else
|
|
{
|
|
item.render_data.program = &self->blit_program;
|
|
program_id = self->blit_program.id;
|
|
}
|
|
|
|
if (render_node_needs_render_target (node))
|
|
{
|
|
item.render_data.render_target_id =
|
|
gsk_gl_driver_create_texture (self->gl_driver, item.size.width, item.size.height);
|
|
gsk_gl_driver_init_texture_empty (self->gl_driver, item.render_data.render_target_id);
|
|
gsk_gl_driver_create_render_target (self->gl_driver, item.render_data.render_target_id, TRUE, TRUE);
|
|
|
|
item.children = g_array_new (FALSE, FALSE, sizeof (RenderItem));
|
|
}
|
|
else
|
|
{
|
|
item.render_data.render_target_id = self->texture_id;
|
|
item.children = NULL;
|
|
}
|
|
|
|
switch (gsk_render_node_get_node_type (node))
|
|
{
|
|
case GSK_TEXTURE_NODE:
|
|
{
|
|
GskTexture *texture = gsk_texture_node_get_texture (node);
|
|
int gl_min_filter = GL_NEAREST, gl_mag_filter = GL_NEAREST;
|
|
|
|
get_gl_scaling_filters (node, &gl_min_filter, &gl_mag_filter);
|
|
|
|
item.render_data.texture_id = gsk_gl_driver_get_texture_for_texture (self->gl_driver,
|
|
texture,
|
|
gl_min_filter,
|
|
gl_mag_filter);
|
|
item.mode = MODE_TEXTURE;
|
|
}
|
|
break;
|
|
|
|
case GSK_CAIRO_NODE:
|
|
{
|
|
const cairo_surface_t *surface = gsk_cairo_node_peek_surface (node);
|
|
int gl_min_filter = GL_NEAREST, gl_mag_filter = GL_NEAREST;
|
|
|
|
if (surface == NULL)
|
|
return;
|
|
|
|
get_gl_scaling_filters (node, &gl_min_filter, &gl_mag_filter);
|
|
|
|
/* Upload the Cairo surface to a GL texture */
|
|
item.render_data.texture_id = gsk_gl_driver_create_texture (self->gl_driver,
|
|
item.size.width,
|
|
item.size.height);
|
|
gsk_gl_driver_bind_source_texture (self->gl_driver, item.render_data.texture_id);
|
|
gsk_gl_driver_init_texture_with_surface (self->gl_driver,
|
|
item.render_data.texture_id,
|
|
(cairo_surface_t *)surface,
|
|
gl_min_filter,
|
|
gl_mag_filter);
|
|
item.mode = MODE_TEXTURE;
|
|
}
|
|
break;
|
|
|
|
case GSK_COLOR_NODE:
|
|
{
|
|
const GdkRGBA *c = gsk_color_node_peek_color(node);
|
|
program_id = self->color_program.id;
|
|
item.render_data.program = &self->color_program;
|
|
item.mode = MODE_COLOR;
|
|
item.color_data.color= *c;
|
|
}
|
|
break;
|
|
|
|
case GSK_COLOR_MATRIX_NODE:
|
|
{
|
|
GskRenderNode *child = gsk_color_matrix_node_get_child (node);
|
|
|
|
gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child, ritem);
|
|
}
|
|
return;
|
|
|
|
case GSK_SHADOW_NODE:
|
|
{
|
|
GskRenderNode *child = gsk_shadow_node_get_child (node);
|
|
|
|
gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child, ritem);
|
|
}
|
|
return;
|
|
|
|
case GSK_REPEAT_NODE:
|
|
{
|
|
GskRenderNode *child = gsk_repeat_node_get_child (node);
|
|
|
|
gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child, ritem);
|
|
}
|
|
return;
|
|
|
|
case GSK_BLEND_NODE:
|
|
{
|
|
GskRenderNode *child = gsk_blend_node_get_bottom_child (node);
|
|
|
|
gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child, ritem);
|
|
|
|
child = gsk_blend_node_get_top_child (node);
|
|
gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child, ritem);
|
|
}
|
|
return;
|
|
|
|
case GSK_CROSS_FADE_NODE:
|
|
{
|
|
GskRenderNode *child = gsk_cross_fade_node_get_start_child (node);
|
|
|
|
gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child, ritem);
|
|
|
|
child = gsk_cross_fade_node_get_end_child (node);
|
|
gsk_gl_renderer_add_render_item (self, projection, modelview, render_items, child, ritem);
|
|
}
|
|
return;
|
|
|
|
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_item (self, projection, modelview, render_items, child, ritem);
|
|
}
|
|
}
|
|
return;
|
|
|
|
case GSK_TRANSFORM_NODE:
|
|
{
|
|
graphene_matrix_t transform, transformed_mv;
|
|
|
|
graphene_matrix_init_from_matrix (&transform, gsk_transform_node_peek_transform (node));
|
|
graphene_matrix_multiply (&transform, modelview, &transformed_mv);
|
|
gsk_gl_renderer_add_render_item (self,
|
|
projection, &transformed_mv,
|
|
render_items,
|
|
gsk_transform_node_get_child (node),
|
|
ritem);
|
|
}
|
|
return;
|
|
|
|
case GSK_NOT_A_RENDER_NODE:
|
|
g_assert_not_reached ();
|
|
return;
|
|
|
|
case GSK_LINEAR_GRADIENT_NODE:
|
|
case GSK_REPEATING_LINEAR_GRADIENT_NODE:
|
|
case GSK_BORDER_NODE:
|
|
case GSK_INSET_SHADOW_NODE:
|
|
case GSK_OUTSET_SHADOW_NODE:
|
|
case GSK_OPACITY_NODE:
|
|
case GSK_CLIP_NODE:
|
|
case GSK_ROUNDED_CLIP_NODE:
|
|
case GSK_TEXT_NODE:
|
|
case GSK_BLUR_NODE:
|
|
default:
|
|
{
|
|
cairo_surface_t *surface;
|
|
cairo_t *cr;
|
|
|
|
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
|
|
item.size.width,
|
|
item.size.height);
|
|
cairo_surface_set_device_scale (surface, scale_factor, scale_factor);
|
|
cr = cairo_create (surface);
|
|
cairo_translate (cr, -node->bounds.origin.x, -node->bounds.origin.y);
|
|
|
|
gsk_render_node_draw (node, cr);
|
|
|
|
cairo_destroy (cr);
|
|
|
|
/* Upload the Cairo surface to a GL texture */
|
|
item.render_data.texture_id = gsk_gl_driver_create_texture (self->gl_driver,
|
|
item.size.width,
|
|
item.size.height);
|
|
gsk_gl_driver_bind_source_texture (self->gl_driver, item.render_data.texture_id);
|
|
gsk_gl_driver_init_texture_with_surface (self->gl_driver,
|
|
item.render_data.texture_id,
|
|
surface,
|
|
GL_NEAREST, GL_NEAREST);
|
|
|
|
cairo_surface_destroy (surface);
|
|
item.mode = MODE_TEXTURE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
item.render_data.program_id = program_id;
|
|
|
|
/* Create the vertex buffers holding the geometry of the quad */
|
|
{
|
|
GskQuadVertex vertex_data[N_VERTICES] = {
|
|
{ { item.min.x, item.min.y }, { 0, 0 }, },
|
|
{ { item.min.x, item.max.y }, { 0, 1 }, },
|
|
{ { item.max.x, item.min.y }, { 1, 0 }, },
|
|
|
|
{ { item.max.x, item.max.y }, { 1, 1 }, },
|
|
{ { item.min.x, item.max.y }, { 0, 1 }, },
|
|
{ { item.max.x, item.min.y }, { 1, 0 }, },
|
|
};
|
|
|
|
item.render_data.vao_id =
|
|
gsk_gl_driver_create_vao_for_quad (self->gl_driver,
|
|
item.render_data.program->position_location,
|
|
item.render_data.program->uv_location,
|
|
N_VERTICES,
|
|
vertex_data);
|
|
}
|
|
|
|
GSK_NOTE (OPENGL, g_print ("Adding node <%s>[%p] to render items\n",
|
|
node->name != NULL ? node->name : "unnamed",
|
|
node));
|
|
g_array_append_val (render_items, item);
|
|
ritem = &g_array_index (render_items, RenderItem, render_items->len - 1);
|
|
|
|
if (item.children != NULL)
|
|
render_items = item.children;
|
|
}
|
|
|
|
static gboolean
|
|
gsk_gl_renderer_validate_tree (GskGLRenderer *self,
|
|
GskRenderNode *root,
|
|
const graphene_matrix_t *projection)
|
|
{
|
|
graphene_matrix_t identity;
|
|
|
|
if (self->gl_context == NULL)
|
|
{
|
|
GSK_NOTE (OPENGL, g_print ("No valid GL context associated to the renderer"));
|
|
return FALSE;
|
|
}
|
|
|
|
graphene_matrix_init_identity (&identity);
|
|
|
|
gdk_gl_context_make_current (self->gl_context);
|
|
|
|
gsk_gl_driver_begin_frame (self->gl_driver);
|
|
|
|
GSK_NOTE (OPENGL, g_print ("RenderNode -> RenderItem\n"));
|
|
gsk_gl_renderer_add_render_item (self, projection, &identity, self->render_items, root, NULL);
|
|
|
|
GSK_NOTE (OPENGL, g_print ("Total render items: %d\n",
|
|
self->render_items->len));
|
|
|
|
gsk_gl_driver_end_frame (self->gl_driver);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_clear_tree (GskGLRenderer *self)
|
|
{
|
|
int removed_textures, removed_vaos;
|
|
|
|
if (self->gl_context == NULL)
|
|
return;
|
|
|
|
gdk_gl_context_make_current (self->gl_context);
|
|
|
|
g_array_remove_range (self->render_items, 0, self->render_items->len);
|
|
|
|
removed_textures = gsk_gl_driver_collect_textures (self->gl_driver);
|
|
removed_vaos = gsk_gl_driver_collect_vaos (self->gl_driver);
|
|
|
|
GSK_NOTE (OPENGL, g_print ("Collected: %d textures, %d vaos\n",
|
|
removed_textures,
|
|
removed_vaos));
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_clear (GskGLRenderer *self)
|
|
{
|
|
GSK_NOTE (OPENGL, g_print ("Clearing viewport\n"));
|
|
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)
|
|
{
|
|
switch (self->render_mode)
|
|
{
|
|
case RENDER_FULL:
|
|
glDisable (GL_SCISSOR_TEST);
|
|
break;
|
|
|
|
case RENDER_SCISSOR:
|
|
{
|
|
GdkDrawingContext *context = gsk_renderer_get_drawing_context (GSK_RENDERER (self));
|
|
GdkWindow *window = gsk_renderer_get_window (GSK_RENDERER (self));
|
|
GdkRectangle extents;
|
|
int scale_factor = gsk_renderer_get_scale_factor (GSK_RENDERER (self));
|
|
|
|
cairo_region_get_extents (gdk_drawing_context_get_clip (context), &extents);
|
|
|
|
glScissor (extents.x * scale_factor,
|
|
(gdk_window_get_height (window) - extents.height - extents.y) * scale_factor,
|
|
extents.width * scale_factor, extents.height * scale_factor);
|
|
glEnable (GL_SCISSOR_TEST);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
}
|
|
|
|
#define ORTHO_NEAR_PLANE -10000
|
|
#define ORTHO_FAR_PLANE 10000
|
|
|
|
static void
|
|
gsk_gl_renderer_do_render (GskRenderer *renderer,
|
|
GskRenderNode *root,
|
|
const graphene_rect_t *viewport,
|
|
int scale_factor)
|
|
{
|
|
GskGLRenderer *self = GSK_GL_RENDERER (renderer);
|
|
graphene_matrix_t modelview, projection;
|
|
guint i;
|
|
#ifdef G_ENABLE_DEBUG
|
|
GskProfiler *profiler;
|
|
gint64 gpu_time, cpu_time;
|
|
#endif
|
|
|
|
#ifdef G_ENABLE_DEBUG
|
|
profiler = gsk_renderer_get_profiler (renderer);
|
|
#endif
|
|
|
|
/* 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 * scale_factor,
|
|
viewport->origin.y + viewport->size.height * scale_factor,
|
|
viewport->origin.y,
|
|
ORTHO_NEAR_PLANE,
|
|
ORTHO_FAR_PLANE);
|
|
|
|
gsk_gl_renderer_update_frustum (self, &modelview, &projection);
|
|
|
|
if (!gsk_gl_renderer_validate_tree (self, root, &projection))
|
|
return;
|
|
|
|
gsk_gl_driver_begin_frame (self->gl_driver);
|
|
|
|
#ifdef G_ENABLE_DEBUG
|
|
gsk_gl_profiler_begin_gpu_region (self->gl_profiler);
|
|
gsk_profiler_timer_begin (profiler, self->profile_timers.cpu_time);
|
|
#endif
|
|
|
|
/* Ensure that the viewport is up to date */
|
|
if (gsk_gl_driver_bind_render_target (self->gl_driver, self->texture_id))
|
|
gsk_gl_renderer_resize_viewport (self, viewport, scale_factor);
|
|
|
|
gsk_gl_renderer_setup_render_mode (self);
|
|
|
|
gsk_gl_renderer_clear (self);
|
|
|
|
glEnable (GL_DEPTH_TEST);
|
|
glDepthFunc (GL_LEQUAL);
|
|
|
|
glEnable (GL_BLEND);
|
|
glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
GSK_NOTE (OPENGL, g_print ("Rendering %u items\n", self->render_items->len));
|
|
for (i = 0; i < self->render_items->len; i++)
|
|
{
|
|
RenderItem *item = &g_array_index (self->render_items, RenderItem, i);
|
|
|
|
render_item (self, item);
|
|
}
|
|
|
|
/* Draw the output of the GL rendering to the window */
|
|
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 GskTexture *
|
|
gsk_gl_renderer_render_texture (GskRenderer *renderer,
|
|
GskRenderNode *root,
|
|
const graphene_rect_t *viewport)
|
|
{
|
|
GskGLRenderer *self = GSK_GL_RENDERER (renderer);
|
|
GskTexture *texture;
|
|
cairo_surface_t *surface;
|
|
cairo_t *cr;
|
|
|
|
g_return_val_if_fail (self->gl_context != NULL, NULL);
|
|
|
|
self->render_mode = RENDER_FULL;
|
|
|
|
gdk_gl_context_make_current (self->gl_context);
|
|
|
|
gsk_gl_driver_begin_frame (self->gl_driver);
|
|
gsk_gl_renderer_create_buffers (self, ceilf (viewport->size.width), ceilf (viewport->size.height), 1);
|
|
gsk_gl_driver_end_frame (self->gl_driver);
|
|
|
|
gsk_gl_renderer_do_render (renderer, root, viewport, 1);
|
|
|
|
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
|
|
ceilf (viewport->size.width),
|
|
ceilf (viewport->size.height));
|
|
cr = cairo_create (surface);
|
|
gdk_cairo_draw_from_gl (cr,
|
|
gsk_renderer_get_window (renderer),
|
|
self->texture_id,
|
|
GL_TEXTURE,
|
|
1.0,
|
|
0, 0,
|
|
viewport->size.width,
|
|
viewport->size.height);
|
|
cairo_destroy (cr);
|
|
|
|
texture = gsk_texture_new_for_surface (surface);
|
|
cairo_surface_destroy (surface);
|
|
|
|
return texture;
|
|
}
|
|
|
|
static void
|
|
gsk_gl_renderer_render (GskRenderer *renderer,
|
|
GskRenderNode *root)
|
|
{
|
|
GskGLRenderer *self = GSK_GL_RENDERER (renderer);
|
|
graphene_rect_t viewport;
|
|
int scale_factor;
|
|
|
|
if (self->gl_context == NULL)
|
|
return;
|
|
|
|
gdk_gl_context_make_current (self->gl_context);
|
|
|
|
gsk_renderer_get_viewport (renderer, &viewport);
|
|
scale_factor = gsk_renderer_get_scale_factor (renderer);
|
|
|
|
gsk_gl_renderer_do_render (renderer, root, &viewport, scale_factor);
|
|
|
|
gdk_gl_context_make_current (self->gl_context);
|
|
gsk_gl_renderer_clear_tree (self);
|
|
gsk_gl_renderer_destroy_buffers (self);
|
|
}
|
|
|
|
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->begin_draw_frame = gsk_gl_renderer_begin_draw_frame;
|
|
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 ();
|
|
|
|
graphene_matrix_init_identity (&self->mvp);
|
|
|
|
self->render_items = g_array_new (FALSE, FALSE, sizeof (RenderItem));
|
|
|
|
#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
|
|
}
|