forked from AuroraMiddleware/gtk
43c212ac28
This patch makes that work using 1 of 2 options: 1. Add all missing enums to the switch statement or 2. Cast the switch argument to a uint to avoid having to do that (mostly for GdkEventType). I even found a bug while doing that: clearing a GtkImage with a surface did not notify thae surface property. The reason for enabling this flag even though it is tedious at times is that it is very useful when adding values to an enum, because it makes GTK immediately warn about all the switch statements where this enum is relevant. And I expect changes to enums to be frequent during the GTK4 development cycle.
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:
|
|
{
|
|
cairo_surface_t *surface = gsk_cairo_node_get_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,
|
|
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;
|
|
|
|
gsk_transform_node_get_transform (node, &transform);
|
|
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
|
|
}
|