mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-12-27 06:00:22 +00:00
64e27cd87d
g_hash_table_insert() frees the given key if it already exists in the hashtable. But since we use the same pointer in the following line, it will result in use-after-free. So instead, insert the key only if it doesn't exist.
1647 lines
56 KiB
C
1647 lines
56 KiB
C
/* gskgldriver.c
|
|
*
|
|
* Copyright 2017 Timm Bäder <mail@baedert.org>
|
|
* Copyright 2018 Matthias Clasen <mclasen@redhat.com>
|
|
* Copyright 2018 Alexander Larsson <alexl@redhat.com>
|
|
* Copyright 2020 Christian Hergert <chergert@redhat.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "gskgldriverprivate.h"
|
|
|
|
#include <gsk/gskdebugprivate.h>
|
|
#include <gsk/gskglshaderprivate.h>
|
|
#include <gsk/gskrendererprivate.h>
|
|
|
|
#include "gskglcommandqueueprivate.h"
|
|
#include "gskglcompilerprivate.h"
|
|
#include "gskglglyphlibraryprivate.h"
|
|
#include "gskgliconlibraryprivate.h"
|
|
#include "gskglprogramprivate.h"
|
|
#include "gskglshadowlibraryprivate.h"
|
|
#include "fp16private.h"
|
|
|
|
#include <gdk/gdkglcontextprivate.h>
|
|
#include <gdk/gdkdisplayprivate.h>
|
|
#include <gdk/gdkmemorytextureprivate.h>
|
|
#include <gdk/gdkprofilerprivate.h>
|
|
#include <gdk/gdktextureprivate.h>
|
|
|
|
#include <gdk/gdkmemoryformatprivate.h>
|
|
|
|
G_DEFINE_TYPE (GskGLDriver, gsk_gl_driver, G_TYPE_OBJECT)
|
|
|
|
static guint
|
|
texture_key_hash (gconstpointer v)
|
|
{
|
|
const GskTextureKey *k = (const GskTextureKey *)v;
|
|
|
|
/* Optimize for 0..3 where 0 is the scaled out case. Usually
|
|
* we'll be squarely on 1 or 2 for standard vs HiDPI. When rendering
|
|
* to a texture scaled out like in node-editor, we might be < 1.
|
|
*/
|
|
guint scale_x = floorf (k->scale_x);
|
|
guint scale_y = floorf (k->scale_y);
|
|
|
|
return GPOINTER_TO_SIZE (k->pointer) ^
|
|
((scale_x << 8) |
|
|
(scale_y << 4) |
|
|
k->pointer_is_child);
|
|
}
|
|
|
|
static gboolean
|
|
texture_key_equal (gconstpointer v1,
|
|
gconstpointer v2)
|
|
{
|
|
const GskTextureKey *k1 = (const GskTextureKey *)v1;
|
|
const GskTextureKey *k2 = (const GskTextureKey *)v2;
|
|
|
|
return k1->pointer == k2->pointer &&
|
|
k1->scale_x == k2->scale_x &&
|
|
k1->scale_y == k2->scale_y &&
|
|
k1->pointer_is_child == k2->pointer_is_child &&
|
|
(!k1->pointer_is_child || memcmp (&k1->parent_rect, &k2->parent_rect, sizeof k1->parent_rect) == 0);
|
|
}
|
|
|
|
static void
|
|
remove_texture_key_for_id (GskGLDriver *self,
|
|
guint texture_id)
|
|
{
|
|
GskTextureKey *key;
|
|
|
|
g_assert (GSK_IS_GL_DRIVER (self));
|
|
g_assert (texture_id > 0);
|
|
|
|
/* g_hash_table_remove() will cause @key to be freed */
|
|
if (g_hash_table_steal_extended (self->texture_id_to_key,
|
|
GUINT_TO_POINTER (texture_id),
|
|
NULL,
|
|
(gpointer *)&key))
|
|
g_hash_table_remove (self->key_to_texture_id, key);
|
|
}
|
|
|
|
static void
|
|
gsk_gl_texture_destroyed (gpointer data)
|
|
{
|
|
((GskGLTexture *)data)->user = NULL;
|
|
}
|
|
|
|
static void
|
|
gsk_gl_driver_autorelease_texture (GskGLDriver *self,
|
|
guint texture_id)
|
|
{
|
|
g_assert (GSK_IS_GL_DRIVER (self));
|
|
|
|
g_array_append_val (self->texture_pool, texture_id);
|
|
}
|
|
|
|
static guint
|
|
gsk_gl_driver_collect_unused_textures (GskGLDriver *self,
|
|
gint64 watermark)
|
|
{
|
|
GHashTableIter iter;
|
|
gpointer k, v;
|
|
guint old_size;
|
|
guint collected;
|
|
|
|
g_assert (GSK_IS_GL_DRIVER (self));
|
|
|
|
old_size = g_hash_table_size (self->textures);
|
|
|
|
g_hash_table_iter_init (&iter, self->textures);
|
|
while (g_hash_table_iter_next (&iter, &k, &v))
|
|
{
|
|
GskGLTexture *t = v;
|
|
|
|
if (t->user || t->permanent)
|
|
continue;
|
|
|
|
if (t->last_used_in_frame <= watermark)
|
|
{
|
|
g_hash_table_iter_steal (&iter);
|
|
|
|
g_assert (t->link.prev == NULL);
|
|
g_assert (t->link.next == NULL);
|
|
g_assert (t->link.data == t);
|
|
|
|
remove_texture_key_for_id (self, t->texture_id);
|
|
gsk_gl_driver_autorelease_texture (self, t->texture_id);
|
|
t->texture_id = 0;
|
|
gsk_gl_texture_free (t);
|
|
}
|
|
}
|
|
|
|
collected = old_size - g_hash_table_size (self->textures);
|
|
|
|
return collected;
|
|
}
|
|
|
|
static void
|
|
remove_program (gpointer data)
|
|
{
|
|
GskGLProgram *program = data;
|
|
|
|
g_assert (!program || GSK_IS_GL_PROGRAM (program));
|
|
|
|
if (program != NULL)
|
|
{
|
|
gsk_gl_program_delete (program);
|
|
g_object_unref (program);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gsk_gl_driver_shader_weak_cb (gpointer data,
|
|
GObject *where_object_was)
|
|
{
|
|
GskGLDriver *self = data;
|
|
|
|
g_assert (GSK_IS_GL_DRIVER (self));
|
|
|
|
if (self->shader_cache != NULL)
|
|
{
|
|
if (self->command_queue != NULL)
|
|
gsk_gl_command_queue_make_current (self->command_queue);
|
|
|
|
g_hash_table_remove (self->shader_cache, where_object_was);
|
|
}
|
|
}
|
|
|
|
G_GNUC_UNUSED G_GNUC_NULL_TERMINATED static inline GBytes *
|
|
join_sources (GBytes *first_bytes,
|
|
...)
|
|
{
|
|
GByteArray *byte_array = g_byte_array_new ();
|
|
GBytes *bytes = first_bytes;
|
|
va_list args;
|
|
|
|
va_start (args, first_bytes);
|
|
while (bytes != NULL)
|
|
{
|
|
gsize len;
|
|
const guint8 *data = g_bytes_get_data (bytes, &len);
|
|
if (len > 0)
|
|
g_byte_array_append (byte_array, data, len);
|
|
g_bytes_unref (bytes);
|
|
bytes = va_arg (args, GBytes *);
|
|
}
|
|
va_end (args);
|
|
|
|
return g_byte_array_free_to_bytes (byte_array);
|
|
}
|
|
|
|
static void
|
|
gsk_gl_driver_dispose (GObject *object)
|
|
{
|
|
GskGLDriver *self = (GskGLDriver *)object;
|
|
|
|
g_assert (GSK_IS_GL_DRIVER (self));
|
|
g_assert (self->in_frame == FALSE);
|
|
|
|
#define GSK_GL_NO_UNIFORMS
|
|
#define GSK_GL_SHADER_RESOURCE(name)
|
|
#define GSK_GL_SHADER_STRING(str)
|
|
#define GSK_GL_SHADER_SINGLE(name)
|
|
#define GSK_GL_SHADER_JOINED(kind, ...)
|
|
#define GSK_GL_ADD_UNIFORM(pos, KEY, name)
|
|
#define GSK_GL_DEFINE_PROGRAM(name, resource, uniforms) \
|
|
GSK_GL_DELETE_PROGRAM(name); \
|
|
GSK_GL_DELETE_PROGRAM(name ## _no_clip); \
|
|
GSK_GL_DELETE_PROGRAM(name ## _rect_clip);
|
|
#define GSK_GL_DELETE_PROGRAM(name) \
|
|
G_STMT_START { \
|
|
if (self->name) \
|
|
gsk_gl_program_delete (self->name); \
|
|
g_clear_object (&self->name); \
|
|
} G_STMT_END;
|
|
# include "gskglprograms.defs"
|
|
#undef GSK_GL_NO_UNIFORMS
|
|
#undef GSK_GL_SHADER_RESOURCE
|
|
#undef GSK_GL_SHADER_STRING
|
|
#undef GSK_GL_SHADER_SINGLE
|
|
#undef GSK_GL_SHADER_JOINED
|
|
#undef GSK_GL_ADD_UNIFORM
|
|
#undef GSK_GL_DEFINE_PROGRAM
|
|
|
|
if (self->shader_cache != NULL)
|
|
{
|
|
GHashTableIter iter;
|
|
gpointer k, v;
|
|
|
|
g_hash_table_iter_init (&iter, self->shader_cache);
|
|
while (g_hash_table_iter_next (&iter, &k, &v))
|
|
{
|
|
GskGLShader *shader = k;
|
|
g_object_weak_unref (G_OBJECT (shader),
|
|
gsk_gl_driver_shader_weak_cb,
|
|
self);
|
|
g_hash_table_iter_remove (&iter);
|
|
}
|
|
|
|
g_clear_pointer (&self->shader_cache, g_hash_table_unref);
|
|
}
|
|
|
|
if (self->command_queue != NULL)
|
|
{
|
|
gsk_gl_command_queue_make_current (self->command_queue);
|
|
gsk_gl_driver_collect_unused_textures (self, 0);
|
|
g_clear_object (&self->command_queue);
|
|
}
|
|
|
|
if (self->autorelease_framebuffers != NULL &&
|
|
self->autorelease_framebuffers->len > 0)
|
|
{
|
|
glDeleteFramebuffers (self->autorelease_framebuffers->len,
|
|
(GLuint *)(gpointer)self->autorelease_framebuffers->data);
|
|
self->autorelease_framebuffers->len = 0;
|
|
}
|
|
|
|
g_clear_object (&self->glyphs_library);
|
|
g_clear_object (&self->icons_library);
|
|
g_clear_object (&self->shadows_library);
|
|
|
|
g_clear_pointer (&self->texture_pool, g_array_unref);
|
|
|
|
g_clear_pointer (&self->autorelease_framebuffers, g_array_unref);
|
|
g_clear_pointer (&self->key_to_texture_id, g_hash_table_unref);
|
|
g_clear_pointer (&self->textures, g_hash_table_unref);
|
|
g_clear_pointer (&self->key_to_texture_id, g_hash_table_unref);
|
|
g_clear_pointer (&self->texture_id_to_key, g_hash_table_unref);
|
|
g_clear_pointer (&self->render_targets, g_ptr_array_unref);
|
|
g_clear_pointer (&self->shader_cache, g_hash_table_unref);
|
|
|
|
g_clear_object (&self->command_queue);
|
|
g_clear_object (&self->shared_command_queue);
|
|
|
|
G_OBJECT_CLASS (gsk_gl_driver_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
gsk_gl_driver_class_init (GskGLDriverClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->dispose = gsk_gl_driver_dispose;
|
|
}
|
|
|
|
static void
|
|
gsk_gl_driver_init (GskGLDriver *self)
|
|
{
|
|
self->autorelease_framebuffers = g_array_new (FALSE, FALSE, sizeof (guint));
|
|
self->textures = g_hash_table_new_full (NULL, NULL, NULL,
|
|
(GDestroyNotify)gsk_gl_texture_free);
|
|
self->texture_id_to_key = g_hash_table_new (NULL, NULL);
|
|
self->key_to_texture_id = g_hash_table_new_full (texture_key_hash,
|
|
texture_key_equal,
|
|
g_free,
|
|
NULL);
|
|
self->shader_cache = g_hash_table_new_full (NULL, NULL, NULL, remove_program);
|
|
self->texture_pool = g_array_new (FALSE, FALSE, sizeof (guint));
|
|
self->render_targets = g_ptr_array_new ();
|
|
}
|
|
|
|
static gboolean
|
|
gsk_gl_driver_load_programs (GskGLDriver *self,
|
|
GError **error)
|
|
{
|
|
GskGLCompiler *compiler;
|
|
gboolean ret = FALSE;
|
|
G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME;
|
|
|
|
g_assert (GSK_IS_GL_DRIVER (self));
|
|
g_assert (GSK_IS_GL_COMMAND_QUEUE (self->command_queue));
|
|
|
|
compiler = gsk_gl_compiler_new (self, self->debug);
|
|
|
|
/* Setup preambles that are shared by all shaders */
|
|
gsk_gl_compiler_set_preamble_from_resource (compiler,
|
|
GSK_GL_COMPILER_ALL,
|
|
"/org/gtk/libgsk/gl/preamble.glsl");
|
|
gsk_gl_compiler_set_preamble_from_resource (compiler,
|
|
GSK_GL_COMPILER_VERTEX,
|
|
"/org/gtk/libgsk/gl/preamble.vs.glsl");
|
|
gsk_gl_compiler_set_preamble_from_resource (compiler,
|
|
GSK_GL_COMPILER_FRAGMENT,
|
|
"/org/gtk/libgsk/gl/preamble.fs.glsl");
|
|
|
|
/* Setup attributes that are provided via VBO */
|
|
gsk_gl_compiler_bind_attribute (compiler, "aPosition", 0);
|
|
gsk_gl_compiler_bind_attribute (compiler, "aUv", 1);
|
|
gsk_gl_compiler_bind_attribute (compiler, "aColor", 2);
|
|
gsk_gl_compiler_bind_attribute (compiler, "aColor2", 3);
|
|
|
|
/* Use XMacros to register all of our programs and their uniforms */
|
|
#define GSK_GL_NO_UNIFORMS
|
|
#define GSK_GL_SHADER_RESOURCE(name) \
|
|
g_resources_lookup_data("/org/gtk/libgsk/gl/" name, 0, NULL)
|
|
#define GSK_GL_SHADER_STRING(str) \
|
|
g_bytes_new_static(str, strlen(str))
|
|
#define GSK_GL_SHADER_SINGLE(bytes) \
|
|
G_STMT_START { \
|
|
GBytes *b = bytes; \
|
|
gsk_gl_compiler_set_source (compiler, GSK_GL_COMPILER_ALL, b); \
|
|
g_bytes_unref (b); \
|
|
} G_STMT_END;
|
|
#define GSK_GL_SHADER_JOINED(kind, ...) \
|
|
G_STMT_START { \
|
|
GBytes *bytes = join_sources(__VA_ARGS__); \
|
|
gsk_gl_compiler_set_source (compiler, GSK_GL_COMPILER_##kind, bytes); \
|
|
g_bytes_unref (bytes); \
|
|
} G_STMT_END;
|
|
#define GSK_GL_ADD_UNIFORM(pos, KEY, name) \
|
|
gsk_gl_program_add_uniform (program, #name, UNIFORM_##KEY);
|
|
#define GSK_GL_DEFINE_PROGRAM(name, sources, uniforms) \
|
|
gsk_gl_compiler_set_source (compiler, GSK_GL_COMPILER_VERTEX, NULL); \
|
|
gsk_gl_compiler_set_source (compiler, GSK_GL_COMPILER_FRAGMENT, NULL); \
|
|
sources \
|
|
GSK_GL_COMPILE_PROGRAM(name ## _no_clip, uniforms, "#define NO_CLIP 1\n"); \
|
|
GSK_GL_COMPILE_PROGRAM(name ## _rect_clip, uniforms, "#define RECT_CLIP 1\n"); \
|
|
GSK_GL_COMPILE_PROGRAM(name, uniforms, "");
|
|
#define GSK_GL_COMPILE_PROGRAM(name, uniforms, clip) \
|
|
G_STMT_START { \
|
|
GskGLProgram *program; \
|
|
gboolean have_alpha; \
|
|
gboolean have_source; \
|
|
\
|
|
if (!(program = gsk_gl_compiler_compile (compiler, #name, clip, error))) \
|
|
goto failure; \
|
|
\
|
|
have_alpha = gsk_gl_program_add_uniform (program, "u_alpha", UNIFORM_SHARED_ALPHA); \
|
|
have_source = gsk_gl_program_add_uniform (program, "u_source", UNIFORM_SHARED_SOURCE); \
|
|
gsk_gl_program_add_uniform (program, "u_clip_rect", UNIFORM_SHARED_CLIP_RECT); \
|
|
gsk_gl_program_add_uniform (program, "u_viewport", UNIFORM_SHARED_VIEWPORT); \
|
|
gsk_gl_program_add_uniform (program, "u_projection", UNIFORM_SHARED_PROJECTION); \
|
|
gsk_gl_program_add_uniform (program, "u_modelview", UNIFORM_SHARED_MODELVIEW); \
|
|
\
|
|
uniforms \
|
|
\
|
|
gsk_gl_program_uniforms_added (program, have_source); \
|
|
if (have_alpha) \
|
|
gsk_gl_program_set_uniform1f (program, UNIFORM_SHARED_ALPHA, 0, 1.0f); \
|
|
\
|
|
*(GskGLProgram **)(((guint8 *)self) + G_STRUCT_OFFSET (GskGLDriver, name)) = \
|
|
g_steal_pointer (&program); \
|
|
} G_STMT_END;
|
|
# include "gskglprograms.defs"
|
|
#undef GSK_GL_DEFINE_PROGRAM_CLIP
|
|
#undef GSK_GL_DEFINE_PROGRAM
|
|
#undef GSK_GL_ADD_UNIFORM
|
|
#undef GSK_GL_SHADER_SINGLE
|
|
#undef GSK_GL_SHADER_JOINED
|
|
#undef GSK_GL_SHADER_RESOURCE
|
|
#undef GSK_GL_SHADER_STRING
|
|
#undef GSK_GL_NO_UNIFORMS
|
|
|
|
ret = TRUE;
|
|
|
|
failure:
|
|
g_clear_object (&compiler);
|
|
|
|
gdk_profiler_end_mark (start_time, "load programs", NULL);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* gsk_gl_driver_autorelease_framebuffer:
|
|
* @self: a `GskGLDriver`
|
|
* @framebuffer_id: the id of the OpenGL framebuffer
|
|
*
|
|
* Marks @framebuffer_id to be deleted when the current frame has cmopleted.
|
|
*/
|
|
static void
|
|
gsk_gl_driver_autorelease_framebuffer (GskGLDriver *self,
|
|
guint framebuffer_id)
|
|
{
|
|
g_assert (GSK_IS_GL_DRIVER (self));
|
|
|
|
g_array_append_val (self->autorelease_framebuffers, framebuffer_id);
|
|
}
|
|
|
|
static GskGLDriver *
|
|
gsk_gl_driver_new (GskGLCommandQueue *command_queue,
|
|
gboolean debug_shaders,
|
|
GError **error)
|
|
{
|
|
GskGLDriver *self;
|
|
GdkGLContext *context;
|
|
gint64 before G_GNUC_UNUSED;
|
|
|
|
g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (command_queue), NULL);
|
|
|
|
before = GDK_PROFILER_CURRENT_TIME;
|
|
|
|
context = gsk_gl_command_queue_get_context (command_queue);
|
|
|
|
gdk_gl_context_make_current (context);
|
|
|
|
self = g_object_new (GSK_TYPE_GL_DRIVER, NULL);
|
|
self->command_queue = g_object_ref (command_queue);
|
|
self->shared_command_queue = g_object_ref (command_queue);
|
|
self->debug = !!debug_shaders;
|
|
|
|
if (!gsk_gl_driver_load_programs (self, error))
|
|
{
|
|
g_object_unref (self);
|
|
return NULL;
|
|
}
|
|
|
|
self->glyphs_library = gsk_gl_glyph_library_new (self);
|
|
self->icons_library = gsk_gl_icon_library_new (self);
|
|
self->shadows_library = gsk_gl_shadow_library_new (self);
|
|
|
|
gdk_profiler_end_mark (before, "create GskGLDriver", NULL);
|
|
|
|
return g_steal_pointer (&self);
|
|
}
|
|
|
|
static void
|
|
free_driver (GskGLDriver *driver)
|
|
{
|
|
g_object_run_dispose (G_OBJECT (driver));
|
|
g_object_unref (driver);
|
|
}
|
|
|
|
static void
|
|
display_closed (GdkDisplay *display)
|
|
{
|
|
g_object_set_data (G_OBJECT (display), "GSK_GL_DRIVER", NULL);
|
|
}
|
|
|
|
/**
|
|
* gsk_gl_driver_for_display:
|
|
* @display: A #GdkDisplay that is known to support GL
|
|
* @debug_shaders: if debug information for shaders should be displayed
|
|
* @error: location for error information
|
|
*
|
|
* Retrieves a driver for a shared display. Generally this is shared across all GL
|
|
* contexts for a display so that fewer programs are necessary for driving output.
|
|
*
|
|
* Returns: (transfer full): a `GskGLDriver` if successful; otherwise %NULL and
|
|
* @error is set.
|
|
*/
|
|
GskGLDriver *
|
|
gsk_gl_driver_for_display (GdkDisplay *display,
|
|
gboolean debug_shaders,
|
|
GError **error)
|
|
{
|
|
GdkGLContext *context;
|
|
GskGLCommandQueue *command_queue = NULL;
|
|
GskGLDriver *driver;
|
|
|
|
g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL);
|
|
|
|
if ((driver = g_object_get_data (G_OBJECT (display), "GSK_GL_DRIVER")))
|
|
return g_object_ref (driver);
|
|
|
|
context = gdk_display_get_gl_context (display);
|
|
g_assert (context);
|
|
|
|
gdk_gl_context_make_current (context);
|
|
|
|
/* Initially we create a command queue using the shared context. However,
|
|
* as frames are processed this will be replaced with the command queue
|
|
* for a given renderer. But since the programs are compiled into the
|
|
* shared context, all other contexts sharing with it will have access
|
|
* to those programs.
|
|
*/
|
|
command_queue = gsk_gl_command_queue_new (context, NULL);
|
|
|
|
if (!(driver = gsk_gl_driver_new (command_queue, debug_shaders, error)))
|
|
goto failure;
|
|
|
|
g_object_set_data_full (G_OBJECT (display),
|
|
"GSK_GL_DRIVER",
|
|
g_object_ref (driver),
|
|
(GDestroyNotify) free_driver);
|
|
g_signal_connect (display, "closed", G_CALLBACK (display_closed), NULL);
|
|
|
|
failure:
|
|
g_clear_object (&command_queue);
|
|
|
|
return g_steal_pointer (&driver);
|
|
}
|
|
|
|
/**
|
|
* gsk_gl_driver_begin_frame:
|
|
* @self: a `GskGLDriver`
|
|
* @command_queue: A `GskGLCommandQueue` from the renderer
|
|
*
|
|
* Begin a new frame.
|
|
*
|
|
* Texture atlases, pools, and other resources will be prepared to draw the
|
|
* next frame. The command queue should be one that was created for the
|
|
* target context to be drawn into (the context of the renderer's surface).
|
|
*/
|
|
void
|
|
gsk_gl_driver_begin_frame (GskGLDriver *self,
|
|
GskGLCommandQueue *command_queue)
|
|
{
|
|
gint64 last_frame_id;
|
|
|
|
g_return_if_fail (GSK_IS_GL_DRIVER (self));
|
|
g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (command_queue));
|
|
g_return_if_fail (self->in_frame == FALSE);
|
|
|
|
last_frame_id = self->current_frame_id;
|
|
|
|
self->in_frame = TRUE;
|
|
self->current_frame_id++;
|
|
|
|
g_set_object (&self->command_queue, command_queue);
|
|
|
|
gsk_gl_command_queue_begin_frame (self->command_queue);
|
|
|
|
/* Mark unused pixel regions of the atlases */
|
|
gsk_gl_texture_library_begin_frame (GSK_GL_TEXTURE_LIBRARY (self->icons_library),
|
|
self->current_frame_id);
|
|
gsk_gl_texture_library_begin_frame (GSK_GL_TEXTURE_LIBRARY (self->glyphs_library),
|
|
self->current_frame_id);
|
|
|
|
/* Cleanup old shadows */
|
|
gsk_gl_shadow_library_begin_frame (self->shadows_library);
|
|
|
|
/* Remove all textures that are from a previous frame or are no
|
|
* longer used by linked GdkTexture. We do this at the beginning
|
|
* of the following frame instead of the end so that we reduce chances
|
|
* we block on any resources while delivering our frames.
|
|
*/
|
|
gsk_gl_driver_collect_unused_textures (self, last_frame_id - 1);
|
|
}
|
|
|
|
/**
|
|
* gsk_gl_driver_end_frame:
|
|
* @self: a `GskGLDriver`
|
|
*
|
|
* Clean up resources from drawing the current frame.
|
|
*
|
|
* Temporary resources used while drawing will be released.
|
|
*/
|
|
void
|
|
gsk_gl_driver_end_frame (GskGLDriver *self)
|
|
{
|
|
g_return_if_fail (GSK_IS_GL_DRIVER (self));
|
|
g_return_if_fail (self->in_frame == TRUE);
|
|
|
|
gsk_gl_command_queue_make_current (self->command_queue);
|
|
gsk_gl_command_queue_end_frame (self->command_queue);
|
|
|
|
self->in_frame = FALSE;
|
|
}
|
|
|
|
/**
|
|
* gsk_gl_driver_after_frame:
|
|
* @self: a `GskGLDriver`
|
|
*
|
|
* This function does post-frame cleanup operations.
|
|
*
|
|
* To reduce the chances of blocking on the driver it is performed
|
|
* after the frame has swapped buffers.
|
|
*/
|
|
void
|
|
gsk_gl_driver_after_frame (GskGLDriver *self)
|
|
{
|
|
g_return_if_fail (GSK_IS_GL_DRIVER (self));
|
|
g_return_if_fail (self->in_frame == FALSE);
|
|
|
|
/* Release any render targets (possibly adding them to
|
|
* self->autorelease_framebuffers) so we can release the FBOs immediately
|
|
* afterwards.
|
|
*/
|
|
while (self->render_targets->len > 0)
|
|
{
|
|
GskGLRenderTarget *render_target = g_ptr_array_index (self->render_targets, self->render_targets->len - 1);
|
|
|
|
gsk_gl_driver_autorelease_framebuffer (self, render_target->framebuffer_id);
|
|
gsk_gl_driver_autorelease_texture (self, render_target->texture_id);
|
|
g_free (render_target);
|
|
|
|
self->render_targets->len--;
|
|
}
|
|
|
|
/* Now that we have collected render targets, release all the FBOs */
|
|
if (self->autorelease_framebuffers->len > 0)
|
|
{
|
|
glDeleteFramebuffers (self->autorelease_framebuffers->len,
|
|
(GLuint *)(gpointer)self->autorelease_framebuffers->data);
|
|
self->autorelease_framebuffers->len = 0;
|
|
}
|
|
|
|
/* Release any cached textures we used during the frame */
|
|
if (self->texture_pool->len > 0)
|
|
{
|
|
glDeleteTextures (self->texture_pool->len,
|
|
(GLuint *)(gpointer)self->texture_pool->data);
|
|
self->texture_pool->len = 0;
|
|
}
|
|
|
|
/* Reset command queue to our shared queue in case we have operations
|
|
* that need to be processed outside of a frame (such as callbacks
|
|
* from external systems such as GDK).
|
|
*/
|
|
g_set_object (&self->command_queue, self->shared_command_queue);
|
|
}
|
|
|
|
GdkGLContext *
|
|
gsk_gl_driver_get_context (GskGLDriver *self)
|
|
{
|
|
g_return_val_if_fail (GSK_IS_GL_DRIVER (self), NULL);
|
|
g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (self->command_queue), NULL);
|
|
|
|
return gsk_gl_command_queue_get_context (self->command_queue);
|
|
}
|
|
|
|
/**
|
|
* gsk_gl_driver_cache_texture:
|
|
* @self: a `GskGLDriver`
|
|
* @key: the key for the texture
|
|
* @texture_id: the id of the texture to be cached
|
|
*
|
|
* Inserts @texture_id into the texture cache using @key.
|
|
*
|
|
* Textures can be looked up by @key after calling this function using
|
|
* gsk_gl_driver_lookup_texture().
|
|
*
|
|
* Textures that have not been used within a number of frames will be
|
|
* purged from the texture cache automatically.
|
|
*/
|
|
void
|
|
gsk_gl_driver_cache_texture (GskGLDriver *self,
|
|
const GskTextureKey *key,
|
|
guint texture_id)
|
|
{
|
|
g_assert (GSK_IS_GL_DRIVER (self));
|
|
g_assert (key != NULL);
|
|
g_assert (texture_id > 0);
|
|
g_assert (g_hash_table_contains (self->textures, GUINT_TO_POINTER (texture_id)));
|
|
|
|
if (!g_hash_table_contains (self->key_to_texture_id, key))
|
|
{
|
|
GskTextureKey *k;
|
|
|
|
k = g_memdup (key, sizeof *key);
|
|
|
|
g_assert (!g_hash_table_contains (self->texture_id_to_key, GUINT_TO_POINTER (texture_id)));
|
|
g_hash_table_insert (self->key_to_texture_id, k, GUINT_TO_POINTER (texture_id));
|
|
g_hash_table_insert (self->texture_id_to_key, GUINT_TO_POINTER (texture_id), k);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gsk_gl_driver_load_texture:
|
|
* @self: a `GdkTexture`
|
|
* @texture: a `GdkTexture`
|
|
* @ensure_mipmap: Mipmaps for this texture must exist for downscaling
|
|
*
|
|
* Loads a `GdkTexture` by uploading the contents to the GPU when
|
|
* necessary. If @texture is a `GdkGLTexture`, it can be used without
|
|
* uploading contents to the GPU.
|
|
*
|
|
* If the texture has already been uploaded and not yet released
|
|
* from cache, this function returns that texture id without further
|
|
* work.
|
|
*
|
|
* If the texture has not been used for a number of frames, it will
|
|
* be removed from cache.
|
|
*
|
|
* There is no need to release the resulting texture identifier after
|
|
* using it. It will be released automatically.
|
|
*
|
|
* Returns: a texture identifier
|
|
*/
|
|
guint
|
|
gsk_gl_driver_load_texture (GskGLDriver *self,
|
|
GdkTexture *texture,
|
|
gboolean ensure_mipmap)
|
|
{
|
|
GdkGLContext *context;
|
|
GdkMemoryTexture *downloaded_texture;
|
|
GskGLTexture *t;
|
|
guint texture_id;
|
|
int height;
|
|
int width;
|
|
|
|
g_return_val_if_fail (GSK_IS_GL_DRIVER (self), 0);
|
|
g_return_val_if_fail (GDK_IS_TEXTURE (texture), 0);
|
|
g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (self->command_queue), 0);
|
|
|
|
context = self->command_queue->context;
|
|
|
|
texture_id = 0;
|
|
downloaded_texture = NULL;
|
|
|
|
t = gdk_texture_get_render_data (texture, self);
|
|
if (t && t->texture_id)
|
|
{
|
|
if (ensure_mipmap && !t->has_mipmap)
|
|
{
|
|
glBindTexture (GL_TEXTURE_2D, t->texture_id);
|
|
glGenerateMipmap (GL_TEXTURE_2D);
|
|
t->has_mipmap = TRUE;
|
|
}
|
|
|
|
return t->texture_id;
|
|
}
|
|
|
|
if (GDK_IS_GL_TEXTURE (texture))
|
|
{
|
|
GdkGLTexture *gl_texture = (GdkGLTexture *) texture;
|
|
GdkGLContext *texture_context = gdk_gl_texture_get_context (gl_texture);
|
|
|
|
if (gdk_gl_context_is_shared (context, texture_context) &&
|
|
(!ensure_mipmap || gdk_gl_texture_has_mipmap (gl_texture)) &&
|
|
gdk_memory_format_alpha (gdk_texture_get_format (texture)) != GDK_MEMORY_ALPHA_STRAIGHT)
|
|
{
|
|
/* A GL texture from the same GL context is a simple task... */
|
|
return gdk_gl_texture_get_id (gl_texture);
|
|
}
|
|
}
|
|
|
|
if (texture_id == 0)
|
|
{
|
|
downloaded_texture = gdk_memory_texture_from_texture (texture, gdk_texture_get_format (texture));
|
|
|
|
/* The download_texture() call may have switched the GL context. Make sure
|
|
* the right context is at work again.
|
|
*/
|
|
gdk_gl_context_make_current (context);
|
|
|
|
texture_id = gsk_gl_command_queue_upload_texture (self->command_queue, GDK_TEXTURE (downloaded_texture));
|
|
}
|
|
|
|
width = gdk_texture_get_width (texture);
|
|
height = gdk_texture_get_height (texture);
|
|
|
|
t = gsk_gl_texture_new (texture_id,
|
|
width, height,
|
|
self->current_frame_id);
|
|
if (ensure_mipmap)
|
|
{
|
|
glBindTexture (GL_TEXTURE_2D, t->texture_id);
|
|
glGenerateMipmap (GL_TEXTURE_2D);
|
|
t->has_mipmap = TRUE;
|
|
}
|
|
|
|
g_hash_table_insert (self->textures, GUINT_TO_POINTER (texture_id), t);
|
|
|
|
if (gdk_texture_set_render_data (texture, self, t, gsk_gl_texture_destroyed))
|
|
t->user = texture;
|
|
|
|
gdk_gl_context_label_object_printf (context, GL_TEXTURE, t->texture_id,
|
|
"GdkTexture<%p> %d", texture, t->texture_id);
|
|
|
|
g_clear_object (&downloaded_texture);
|
|
|
|
return t->texture_id;
|
|
}
|
|
|
|
/**
|
|
* gsk_gl_driver_create_texture:
|
|
* @self: a `GskGLDriver`
|
|
* @width: the width of the texture
|
|
* @height: the height of the texture
|
|
* @format: format for the texture
|
|
*
|
|
* Creates a new texture immediately that can be used by the caller
|
|
* to upload data, map to a framebuffer, or other uses which may
|
|
* modify the texture immediately.
|
|
*
|
|
* Typical examples for @format are GL_RGBA8, GL_RGBA16F or GL_RGBA32F.
|
|
*
|
|
* Use gsk_gl_driver_release_texture() to release this texture back into
|
|
* the pool so it may be reused later in the pipeline.
|
|
*
|
|
* Returns: a `GskGLTexture` which can be returned to the pool with
|
|
* gsk_gl_driver_release_texture().
|
|
*/
|
|
GskGLTexture *
|
|
gsk_gl_driver_create_texture (GskGLDriver *self,
|
|
float width,
|
|
float height,
|
|
int format)
|
|
{
|
|
GskGLTexture *texture;
|
|
guint texture_id;
|
|
|
|
g_return_val_if_fail (GSK_IS_GL_DRIVER (self), NULL);
|
|
|
|
texture_id = gsk_gl_command_queue_create_texture (self->command_queue,
|
|
width, height,
|
|
format);
|
|
texture = gsk_gl_texture_new (texture_id,
|
|
width, height,
|
|
self->current_frame_id);
|
|
g_hash_table_insert (self->textures,
|
|
GUINT_TO_POINTER (texture->texture_id),
|
|
texture);
|
|
|
|
return texture;
|
|
}
|
|
|
|
/**
|
|
* gsk_gl_driver_release_texture:
|
|
* @self: a `GskGLDriver`
|
|
* @texture: a `GskGLTexture`
|
|
*
|
|
* Releases @texture back into the pool so that it can be used later
|
|
* in the command stream by future batches. This helps reduce VRAM
|
|
* usage on the GPU.
|
|
*
|
|
* When the frame has completed, pooled textures will be released
|
|
* to free additional VRAM back to the system.
|
|
*/
|
|
void
|
|
gsk_gl_driver_release_texture (GskGLDriver *self,
|
|
GskGLTexture *texture)
|
|
{
|
|
guint texture_id;
|
|
|
|
g_assert (GSK_IS_GL_DRIVER (self));
|
|
g_assert (texture != NULL);
|
|
|
|
texture_id = texture->texture_id;
|
|
texture->texture_id = 0;
|
|
gsk_gl_texture_free (texture);
|
|
|
|
if (texture_id > 0)
|
|
remove_texture_key_for_id (self, texture_id);
|
|
|
|
g_hash_table_steal (self->textures, GUINT_TO_POINTER (texture_id));
|
|
gsk_gl_driver_autorelease_texture (self, texture_id);
|
|
}
|
|
|
|
/**
|
|
* gsk_gl_driver_create_render_target:
|
|
* @self: a `GskGLDriver`
|
|
* @width: the width for the render target
|
|
* @height: the height for the render target
|
|
* @format: the format to use
|
|
* @out_render_target: (out): a location for the render target
|
|
*
|
|
* Creates a new render target which contains a framebuffer and a texture
|
|
* bound to that framebuffer of the size @width x @height and using the
|
|
* appropriate filters.
|
|
*
|
|
* Typical examples for @format are GK_RGBA8, GL_RGBA16F or GL_RGBA32F.
|
|
*
|
|
* Use gsk_gl_driver_release_render_target() when you are finished with
|
|
* the render target to release it. You may steal the texture from the
|
|
* render target when releasing it.
|
|
*
|
|
* Returns: %TRUE if successful; otherwise %FALSE and @out_fbo_id and
|
|
* @out_texture_id are undefined.
|
|
*/
|
|
gboolean
|
|
gsk_gl_driver_create_render_target (GskGLDriver *self,
|
|
int width,
|
|
int height,
|
|
int format,
|
|
GskGLRenderTarget **out_render_target)
|
|
{
|
|
guint framebuffer_id;
|
|
guint texture_id;
|
|
|
|
g_return_val_if_fail (GSK_IS_GL_DRIVER (self), FALSE);
|
|
g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (self->command_queue), FALSE);
|
|
g_return_val_if_fail (out_render_target != NULL, FALSE);
|
|
|
|
#if 0
|
|
if (self->render_targets->len > 0)
|
|
{
|
|
for (guint i = self->render_targets->len; i > 0; i--)
|
|
{
|
|
GskGLRenderTarget *render_target = g_ptr_array_index (self->render_targets, i-1);
|
|
|
|
if (render_target->width == width &&
|
|
render_target->height == height)
|
|
{
|
|
*out_render_target = g_ptr_array_steal_index_fast (self->render_targets, i-1);
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (gsk_gl_command_queue_create_render_target (self->command_queue,
|
|
width, height,
|
|
format,
|
|
&framebuffer_id, &texture_id))
|
|
{
|
|
GskGLRenderTarget *render_target;
|
|
|
|
render_target = g_new0 (GskGLRenderTarget, 1);
|
|
render_target->format = format;
|
|
render_target->width = width;
|
|
render_target->height = height;
|
|
render_target->framebuffer_id = framebuffer_id;
|
|
render_target->texture_id = texture_id;
|
|
|
|
*out_render_target = render_target;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
*out_render_target = NULL;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* gsk_gl_driver_release_render_target:
|
|
* @self: a `GskGLDriver`
|
|
* @render_target: a `GskGLRenderTarget` created with
|
|
* gsk_gl_driver_create_render_target().
|
|
* @release_texture: if the texture should also be released
|
|
*
|
|
* Releases a render target that was previously created. An attempt may
|
|
* be made to cache the render target so that future creations of render
|
|
* targets are performed faster.
|
|
*
|
|
* If @release_texture is %FALSE, the backing texture id is returned and
|
|
* the framebuffer is released. Otherwise, both the texture and framebuffer
|
|
* are released or cached until the end of the frame.
|
|
*
|
|
* This may be called when building the render job as the texture or
|
|
* framebuffer will not be removed immediately.
|
|
*
|
|
* Returns: a texture id if @release_texture is %FALSE, otherwise zero.
|
|
*/
|
|
guint
|
|
gsk_gl_driver_release_render_target (GskGLDriver *self,
|
|
GskGLRenderTarget *render_target,
|
|
gboolean release_texture)
|
|
{
|
|
guint texture_id;
|
|
|
|
g_return_val_if_fail (GSK_IS_GL_DRIVER (self), 0);
|
|
g_return_val_if_fail (render_target != NULL, 0);
|
|
|
|
if (release_texture)
|
|
{
|
|
texture_id = 0;
|
|
g_ptr_array_add (self->render_targets, render_target);
|
|
}
|
|
else
|
|
{
|
|
GskGLTexture *texture;
|
|
|
|
texture_id = render_target->texture_id;
|
|
|
|
texture = gsk_gl_texture_new (render_target->texture_id,
|
|
render_target->width,
|
|
render_target->height,
|
|
self->current_frame_id);
|
|
g_hash_table_insert (self->textures,
|
|
GUINT_TO_POINTER (texture_id),
|
|
g_steal_pointer (&texture));
|
|
|
|
gsk_gl_driver_autorelease_framebuffer (self, render_target->framebuffer_id);
|
|
g_free (render_target);
|
|
|
|
}
|
|
|
|
return texture_id;
|
|
}
|
|
|
|
/**
|
|
* gsk_gl_driver_lookup_shader:
|
|
* @self: a `GskGLDriver`
|
|
* @shader: the shader to lookup or load
|
|
* @error: a location for a `GError`
|
|
*
|
|
* Attepts to load @shader from the shader cache.
|
|
*
|
|
* If it has not been loaded, then it will compile the shader on demand.
|
|
*
|
|
* Returns: (nullable) (transfer none): a `GskGLShader`
|
|
*/
|
|
GskGLProgram *
|
|
gsk_gl_driver_lookup_shader (GskGLDriver *self,
|
|
GskGLShader *shader,
|
|
GError **error)
|
|
{
|
|
GskGLProgram *program;
|
|
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
g_return_val_if_fail (shader != NULL, NULL);
|
|
|
|
program = g_hash_table_lookup (self->shader_cache, shader);
|
|
|
|
if (program == NULL)
|
|
{
|
|
const GskGLUniform *uniforms;
|
|
GskGLCompiler *compiler;
|
|
GBytes *suffix;
|
|
int n_required_textures;
|
|
int n_uniforms;
|
|
|
|
uniforms = gsk_gl_shader_get_uniforms (shader, &n_uniforms);
|
|
if (n_uniforms > GSK_GL_PROGRAM_MAX_CUSTOM_ARGS)
|
|
{
|
|
g_set_error (error,
|
|
GDK_GL_ERROR,
|
|
GDK_GL_ERROR_UNSUPPORTED_FORMAT,
|
|
"Tried to use %d uniforms, while only %d is supported",
|
|
n_uniforms,
|
|
GSK_GL_PROGRAM_MAX_CUSTOM_ARGS);
|
|
return NULL;
|
|
}
|
|
|
|
n_required_textures = gsk_gl_shader_get_n_textures (shader);
|
|
if (n_required_textures > GSK_GL_PROGRAM_MAX_CUSTOM_TEXTURES)
|
|
{
|
|
g_set_error (error,
|
|
GDK_GL_ERROR,
|
|
GDK_GL_ERROR_UNSUPPORTED_FORMAT,
|
|
"Tried to use %d textures, while only %d is supported",
|
|
n_required_textures,
|
|
GSK_GL_PROGRAM_MAX_CUSTOM_TEXTURES);
|
|
return NULL;
|
|
}
|
|
|
|
compiler = gsk_gl_compiler_new (self, FALSE);
|
|
suffix = gsk_gl_shader_get_source (shader);
|
|
|
|
gsk_gl_compiler_set_preamble_from_resource (compiler,
|
|
GSK_GL_COMPILER_ALL,
|
|
"/org/gtk/libgsk/gl/preamble.glsl");
|
|
gsk_gl_compiler_set_preamble_from_resource (compiler,
|
|
GSK_GL_COMPILER_VERTEX,
|
|
"/org/gtk/libgsk/gl/preamble.vs.glsl");
|
|
gsk_gl_compiler_set_preamble_from_resource (compiler,
|
|
GSK_GL_COMPILER_FRAGMENT,
|
|
"/org/gtk/libgsk/gl/preamble.fs.glsl");
|
|
gsk_gl_compiler_set_source_from_resource (compiler,
|
|
GSK_GL_COMPILER_ALL,
|
|
"/org/gtk/libgsk/gl/custom.glsl");
|
|
gsk_gl_compiler_set_suffix (compiler, GSK_GL_COMPILER_FRAGMENT, suffix);
|
|
|
|
/* Setup attributes that are provided via VBO */
|
|
gsk_gl_compiler_bind_attribute (compiler, "aPosition", 0);
|
|
gsk_gl_compiler_bind_attribute (compiler, "aUv", 1);
|
|
gsk_gl_compiler_bind_attribute (compiler, "aColor", 2);
|
|
gsk_gl_compiler_bind_attribute (compiler, "aColor2", 3);
|
|
|
|
if ((program = gsk_gl_compiler_compile (compiler, NULL, "", error)))
|
|
{
|
|
gboolean have_alpha;
|
|
|
|
gsk_gl_program_add_uniform (program, "u_source", UNIFORM_SHARED_SOURCE);
|
|
gsk_gl_program_add_uniform (program, "u_clip_rect", UNIFORM_SHARED_CLIP_RECT);
|
|
gsk_gl_program_add_uniform (program, "u_viewport", UNIFORM_SHARED_VIEWPORT);
|
|
gsk_gl_program_add_uniform (program, "u_projection", UNIFORM_SHARED_PROJECTION);
|
|
gsk_gl_program_add_uniform (program, "u_modelview", UNIFORM_SHARED_MODELVIEW);
|
|
have_alpha = gsk_gl_program_add_uniform (program, "u_alpha", UNIFORM_SHARED_ALPHA);
|
|
|
|
gsk_gl_program_add_uniform (program, "u_size", UNIFORM_CUSTOM_SIZE);
|
|
gsk_gl_program_add_uniform (program, "u_texture1", UNIFORM_CUSTOM_TEXTURE1);
|
|
gsk_gl_program_add_uniform (program, "u_texture2", UNIFORM_CUSTOM_TEXTURE2);
|
|
gsk_gl_program_add_uniform (program, "u_texture3", UNIFORM_CUSTOM_TEXTURE3);
|
|
gsk_gl_program_add_uniform (program, "u_texture4", UNIFORM_CUSTOM_TEXTURE4);
|
|
|
|
/* Custom arguments (max is 8) */
|
|
for (guint i = 0; i < n_uniforms; i++)
|
|
gsk_gl_program_add_uniform (program, uniforms[i].name, UNIFORM_CUSTOM_ARG0+i);
|
|
|
|
gsk_gl_program_uniforms_added (program, TRUE);
|
|
|
|
if (have_alpha)
|
|
gsk_gl_program_set_uniform1f (program, UNIFORM_SHARED_ALPHA, 0, 1.0f);
|
|
|
|
g_hash_table_insert (self->shader_cache, shader, program);
|
|
g_object_weak_ref (G_OBJECT (shader),
|
|
gsk_gl_driver_shader_weak_cb,
|
|
self);
|
|
}
|
|
|
|
g_object_unref (compiler);
|
|
}
|
|
|
|
return program;
|
|
}
|
|
|
|
#if 0
|
|
void
|
|
gsk_gl_driver_save_texture_to_png (GskGLDriver *driver,
|
|
int texture_id,
|
|
int width,
|
|
int height,
|
|
const char *filename)
|
|
{
|
|
GdkGLTextureBuilder *builder;
|
|
GdkTexture *texture;
|
|
|
|
builder = gdk_gl_texture_builder_new ();
|
|
gdk_gl_texture_builder_set_context (builder, gsk_gl_driver_get_context (driver));
|
|
gdk_gl_texture_builder_set_id (builder, texture_id);
|
|
gdk_gl_texture_builder_set_width (builder, width);
|
|
gdk_gl_texture_builder_set_height (builder, height);
|
|
|
|
texture = gdk_gl_texture_builder_build (builder, NULL, NULL);
|
|
gdk_texture_save_to_png (texture, filename);
|
|
|
|
g_object_unref (texture);
|
|
g_object_unref (builder);
|
|
}
|
|
|
|
void
|
|
gsk_gl_driver_save_atlases_to_png (GskGLDriver *self,
|
|
const char *directory)
|
|
{
|
|
GPtrArray *atlases;
|
|
|
|
g_return_if_fail (GSK_IS_GL_DRIVER (self));
|
|
|
|
if (directory == NULL)
|
|
directory = ".";
|
|
|
|
#define copy_atlases(dst, library) \
|
|
g_ptr_array_extend(dst, GSK_GL_TEXTURE_LIBRARY(library)->atlases, NULL, NULL)
|
|
atlases = g_ptr_array_new ();
|
|
copy_atlases (atlases, self->glyphs_library);
|
|
copy_atlases (atlases, self->icons_library);
|
|
#undef copy_atlases
|
|
|
|
for (guint i = 0; i < atlases->len; i++)
|
|
{
|
|
GskGLTextureAtlas *atlas = g_ptr_array_index (atlases, i);
|
|
char *filename = g_strdup_printf ("%s%sframe-%d-atlas-%d.png",
|
|
directory,
|
|
G_DIR_SEPARATOR_S,
|
|
(int)self->current_frame_id,
|
|
atlas->texture_id);
|
|
gsk_gl_driver_save_texture_to_png (self, atlas->texture_id, atlas->width, atlas->height, filename);
|
|
g_free (filename);
|
|
}
|
|
|
|
g_ptr_array_unref (atlases);
|
|
}
|
|
#endif
|
|
|
|
GskGLCommandQueue *
|
|
gsk_gl_driver_create_command_queue (GskGLDriver *self,
|
|
GdkGLContext *context)
|
|
{
|
|
g_return_val_if_fail (GSK_IS_GL_DRIVER (self), NULL);
|
|
g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL);
|
|
|
|
return gsk_gl_command_queue_new (context, self->shared_command_queue->uniforms);
|
|
}
|
|
|
|
void
|
|
gsk_gl_driver_add_texture_slices (GskGLDriver *self,
|
|
GdkTexture *texture,
|
|
gboolean ensure_mipmap,
|
|
GskGLTextureSlice **out_slices,
|
|
guint *out_n_slices)
|
|
{
|
|
int max_slice_size;
|
|
GskGLTextureSlice *slices;
|
|
GskGLTexture *t;
|
|
guint n_slices;
|
|
guint cols;
|
|
guint rows;
|
|
int tex_width;
|
|
int tex_height;
|
|
int x = 0, y = 0;
|
|
GdkMemoryTexture *memtex;
|
|
int extra_pixels;
|
|
GdkMemoryTexture *memtex1 = NULL;
|
|
GdkMemoryTexture *memtex2 = NULL;
|
|
GdkMemoryTexture *memtex3 = NULL;
|
|
GdkMemoryTexture *memtex4 = NULL;
|
|
|
|
g_assert (GSK_IS_GL_DRIVER (self));
|
|
g_assert (GDK_IS_TEXTURE (texture));
|
|
g_assert (out_slices != NULL);
|
|
g_assert (out_n_slices != NULL);
|
|
|
|
max_slice_size = self->command_queue->max_texture_size / 2;
|
|
tex_width = texture->width;
|
|
tex_height = texture->height;
|
|
|
|
cols = (texture->width / max_slice_size) + 1;
|
|
rows = (texture->height / max_slice_size) + 1;
|
|
|
|
n_slices = cols * rows;
|
|
|
|
t = gdk_texture_get_render_data (texture, self);
|
|
|
|
if (t)
|
|
{
|
|
if (t->n_slices == n_slices &&
|
|
(t->has_mipmap || !ensure_mipmap))
|
|
{
|
|
*out_slices = t->slices;
|
|
*out_n_slices = t->n_slices;
|
|
return;
|
|
}
|
|
|
|
gdk_texture_clear_render_data (texture);
|
|
}
|
|
|
|
slices = g_new0 (GskGLTextureSlice, n_slices);
|
|
memtex = gdk_memory_texture_from_texture (texture,
|
|
gdk_texture_get_format (texture));
|
|
|
|
if (ensure_mipmap)
|
|
{
|
|
guchar *data1, *data2, *data3, *data4;
|
|
guchar *top_row, *bot_row, *left_row, *right_row;
|
|
GdkTexture *tmp;
|
|
int w;
|
|
GBytes *bytes;
|
|
|
|
/* We need some extra pixels around our tiles, in order for
|
|
* GL to properly determine the right level of detail to use.
|
|
* This number should probably depend on the scale, but for
|
|
* now we just hardcode it.
|
|
*
|
|
* We create some auxiliary textures to hold the extra pixels:
|
|
*
|
|
* +---------------------+
|
|
* | memtex1 |
|
|
* *---+-------------+---+
|
|
* | | | |
|
|
* memtex2| memtex |memtex3
|
|
* | | | |
|
|
* *---+-------------+---+
|
|
* | memtex4 | |
|
|
* +---------------------+
|
|
*/
|
|
|
|
extra_pixels = 15;
|
|
|
|
top_row = g_malloc (4 * tex_width);
|
|
tmp = gdk_memory_texture_new_subtexture (memtex, 0, 0, tex_width, 1);
|
|
gdk_texture_download (tmp, top_row, 4 * tex_width);
|
|
g_object_unref (tmp);
|
|
|
|
bot_row = g_malloc (4 * tex_width);
|
|
tmp = gdk_memory_texture_new_subtexture (memtex, 0, tex_height - 1, tex_width, 1);
|
|
gdk_texture_download (tmp, bot_row, 4 * tex_width);
|
|
g_object_unref (tmp);
|
|
|
|
left_row = g_malloc (4 * tex_height);
|
|
tmp = gdk_memory_texture_new_subtexture (memtex, 0, 0, 1, tex_height);
|
|
gdk_texture_download (tmp, left_row, 4);
|
|
g_object_unref (tmp);
|
|
|
|
right_row = g_malloc (4 * tex_height);
|
|
tmp = gdk_memory_texture_new_subtexture (memtex, tex_width - 1, 0, 1, tex_height);
|
|
gdk_texture_download (tmp, right_row, 4);
|
|
g_object_unref (tmp);
|
|
|
|
w = tex_width + 2 * extra_pixels;
|
|
|
|
data1 = g_malloc (4 * w * extra_pixels);
|
|
data2 = g_malloc (4 * extra_pixels * tex_height);
|
|
data3 = g_malloc (4 * extra_pixels * tex_height);
|
|
data4 = g_malloc (4 * w * extra_pixels);
|
|
|
|
for (int i = 0; i < w; i++)
|
|
{
|
|
int ii = CLAMP (i, extra_pixels, (tex_width - 1) + extra_pixels) - extra_pixels;
|
|
|
|
for (int j = 0; j < extra_pixels; j++)
|
|
{
|
|
data1[(j * w + i) * 4] = top_row[ii * 4];
|
|
data1[(j * w + i) * 4 + 1] = top_row[ii * 4 + 1];
|
|
data1[(j * w + i) * 4 + 2] = top_row[ii * 4 + 2];
|
|
data1[(j * w + i) * 4 + 3] = top_row[ii * 4 + 3];
|
|
|
|
data4[(j * w + i) * 4] = bot_row[ii * 4];
|
|
data4[(j * w + i) * 4 + 1] = bot_row[ii * 4 + 1];
|
|
data4[(j * w + i) * 4 + 2] = bot_row[ii * 4 + 2];
|
|
data4[(j * w + i) * 4 + 3] = bot_row[ii * 4 + 3];
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < extra_pixels; i++)
|
|
{
|
|
for (int j = 0; j < tex_height; j++)
|
|
{
|
|
data2[(j * extra_pixels + i) * 4] = left_row[j * 4];
|
|
data2[(j * extra_pixels + i) * 4 + 1] = left_row[j * 4 + 1];
|
|
data2[(j * extra_pixels + i) * 4 + 2] = left_row[j * 4 + 2];
|
|
data2[(j * extra_pixels + i) * 4 + 3] = left_row[j * 4 + 3];
|
|
|
|
data3[(j * extra_pixels + i) * 4] = right_row[j * 4];
|
|
data3[(j * extra_pixels + i) * 4 + 1] = right_row[j * 4 + 1];
|
|
data3[(j * extra_pixels + i) * 4 + 2] = right_row[j * 4 + 2];
|
|
data3[(j * extra_pixels + i) * 4 + 3] = right_row[j * 4 + 3];
|
|
}
|
|
}
|
|
|
|
g_free (top_row);
|
|
g_free (bot_row);
|
|
g_free (left_row);
|
|
g_free (right_row);
|
|
|
|
bytes = g_bytes_new_take (data1, 4 * w * extra_pixels);
|
|
memtex1 = GDK_MEMORY_TEXTURE (gdk_memory_texture_new (w, extra_pixels, GDK_MEMORY_DEFAULT, bytes, 4 * w));
|
|
g_bytes_unref (bytes);
|
|
|
|
bytes = g_bytes_new_take (data2, 4 * extra_pixels * tex_height);
|
|
memtex2 = GDK_MEMORY_TEXTURE (gdk_memory_texture_new (extra_pixels, tex_height, GDK_MEMORY_DEFAULT, bytes, 4 * extra_pixels));
|
|
g_bytes_unref (bytes);
|
|
|
|
bytes = g_bytes_new_take (data3, 4 * extra_pixels * tex_height);
|
|
memtex3 = GDK_MEMORY_TEXTURE (gdk_memory_texture_new (extra_pixels, tex_height, GDK_MEMORY_DEFAULT, bytes, 4 * extra_pixels));
|
|
g_bytes_unref (bytes);
|
|
|
|
bytes = g_bytes_new_take (data4, 4 * w * extra_pixels);
|
|
memtex4 = GDK_MEMORY_TEXTURE (gdk_memory_texture_new (w, extra_pixels, GDK_MEMORY_DEFAULT, bytes, 4 * w));
|
|
g_bytes_unref (bytes);
|
|
}
|
|
else
|
|
extra_pixels = 0;
|
|
|
|
x = 0;
|
|
for (guint col = 0; col < cols; col++)
|
|
{
|
|
int slice_width = col + 1 < cols ? tex_width / cols : tex_width - x;
|
|
|
|
y = 0;
|
|
for (guint row = 0; row < rows; row++)
|
|
{
|
|
int slice_height = row + 1 < rows ? tex_height / rows : tex_height - y;
|
|
int slice_index = (col * rows) + row;
|
|
guint texture_id;
|
|
|
|
if (ensure_mipmap)
|
|
{
|
|
GskGLTextureChunk chunks[5];
|
|
unsigned int n_chunks = 0;
|
|
|
|
if (row == 0)
|
|
{
|
|
chunks[n_chunks].texture = gdk_memory_texture_new_subtexture (memtex1,
|
|
x, 0,
|
|
slice_width + 2 * extra_pixels, extra_pixels);
|
|
chunks[n_chunks].x = 0;
|
|
chunks[n_chunks].y = 0;
|
|
n_chunks++;
|
|
}
|
|
|
|
if (row == rows - 1)
|
|
{
|
|
chunks[n_chunks].texture = gdk_memory_texture_new_subtexture (memtex4,
|
|
x, 0,
|
|
slice_width + 2 * extra_pixels, extra_pixels);
|
|
chunks[n_chunks].x = 0;
|
|
chunks[n_chunks].y = slice_height + extra_pixels;
|
|
n_chunks++;
|
|
}
|
|
|
|
if (col == 0)
|
|
{
|
|
int yy = y - extra_pixels;
|
|
int hh = slice_height + 2 * extra_pixels;
|
|
int y0 = 0;
|
|
|
|
if (row == 0)
|
|
{
|
|
yy = 0;
|
|
y0 = extra_pixels;
|
|
hh -= extra_pixels;
|
|
}
|
|
if (row == rows - 1)
|
|
{
|
|
hh -= extra_pixels;
|
|
}
|
|
|
|
chunks[n_chunks].texture = gdk_memory_texture_new_subtexture (memtex2,
|
|
0, yy, extra_pixels, hh);
|
|
chunks[n_chunks].x = 0;
|
|
chunks[n_chunks].y = y0;
|
|
n_chunks++;
|
|
}
|
|
|
|
if (col == cols - 1)
|
|
{
|
|
int yy = y - extra_pixels;
|
|
int hh = slice_height + 2 * extra_pixels;
|
|
int y0 = 0;
|
|
|
|
if (row == 0)
|
|
{
|
|
yy = 0;
|
|
y0 = extra_pixels;
|
|
hh -= extra_pixels;
|
|
}
|
|
if (row == rows - 1)
|
|
{
|
|
hh -= extra_pixels;
|
|
}
|
|
|
|
chunks[n_chunks].texture = gdk_memory_texture_new_subtexture (memtex3,
|
|
0, yy, extra_pixels, hh);
|
|
chunks[n_chunks].x = slice_width + extra_pixels;
|
|
chunks[n_chunks].y = y0;
|
|
n_chunks++;
|
|
}
|
|
|
|
{
|
|
int xx = x - extra_pixels;
|
|
int yy = y - extra_pixels;
|
|
int ww = slice_width + 2 * extra_pixels;
|
|
int hh = slice_height + 2 * extra_pixels;
|
|
int x0 = 0;
|
|
int y0 = 0;
|
|
if (col == 0)
|
|
{
|
|
xx = 0;
|
|
ww -= extra_pixels;
|
|
x0 = extra_pixels;
|
|
}
|
|
if (col == cols - 1)
|
|
{
|
|
ww -= extra_pixels;
|
|
}
|
|
if (row == 0)
|
|
{
|
|
yy = 0;
|
|
hh -= extra_pixels;
|
|
y0 = extra_pixels;
|
|
}
|
|
if (row == rows - 1)
|
|
{
|
|
hh -= extra_pixels;
|
|
}
|
|
|
|
chunks[n_chunks].texture = gdk_memory_texture_new_subtexture (memtex, xx, yy, ww, hh);
|
|
chunks[n_chunks].x = x0;
|
|
chunks[n_chunks].y = y0;
|
|
n_chunks++;
|
|
}
|
|
|
|
texture_id = gsk_gl_command_queue_upload_texture_chunks (self->command_queue, n_chunks, chunks);
|
|
|
|
glBindTexture (GL_TEXTURE_2D, texture_id);
|
|
glGenerateMipmap (GL_TEXTURE_2D);
|
|
|
|
for (unsigned int i = 0; i < n_chunks; i++)
|
|
g_object_unref (chunks[i].texture);
|
|
}
|
|
else
|
|
{
|
|
GdkTexture *subtex;
|
|
|
|
subtex = gdk_memory_texture_new_subtexture (memtex, x, y, slice_width, slice_height);
|
|
texture_id = gsk_gl_command_queue_upload_texture (self->command_queue, subtex);
|
|
g_object_unref (subtex);
|
|
}
|
|
|
|
slices[slice_index].rect.x = x;
|
|
slices[slice_index].rect.y = y;
|
|
slices[slice_index].rect.width = slice_width;
|
|
slices[slice_index].rect.height = slice_height;
|
|
slices[slice_index].texture_id = texture_id;
|
|
slices[slice_index].area.x = extra_pixels / (float) (slice_width + 2 * extra_pixels);
|
|
slices[slice_index].area.y = extra_pixels / (float) (slice_height + 2 * extra_pixels);
|
|
slices[slice_index].area.x2 = (extra_pixels + slice_width) / (float) (slice_width + 2 * extra_pixels);
|
|
slices[slice_index].area.y2 = (extra_pixels + slice_height) / (float) (slice_height + 2 * extra_pixels);
|
|
|
|
y += slice_height;
|
|
}
|
|
|
|
x += slice_width;
|
|
}
|
|
|
|
g_object_unref (memtex);
|
|
g_clear_object (&memtex1);
|
|
g_clear_object (&memtex2);
|
|
g_clear_object (&memtex3);
|
|
g_clear_object (&memtex4);
|
|
|
|
/* Allocate one Texture for the entire thing. */
|
|
t = gsk_gl_texture_new (0,
|
|
tex_width, tex_height,
|
|
self->current_frame_id);
|
|
t->has_mipmap = ensure_mipmap;
|
|
|
|
/* Use gsk_gl_texture_free() as destroy notify here since we are
|
|
* not inserting this GskGLTexture into self->textures!
|
|
*/
|
|
gdk_texture_set_render_data (texture, self, t,
|
|
(GDestroyNotify)gsk_gl_texture_free);
|
|
|
|
t->slices = *out_slices = slices;
|
|
t->n_slices = *out_n_slices = n_slices;
|
|
}
|
|
|
|
GskGLTexture *
|
|
gsk_gl_driver_mark_texture_permanent (GskGLDriver *self,
|
|
guint texture_id)
|
|
{
|
|
GskGLTexture *t;
|
|
|
|
g_return_val_if_fail (GSK_IS_GL_DRIVER (self), NULL);
|
|
g_return_val_if_fail (texture_id > 0, NULL);
|
|
|
|
if ((t = g_hash_table_lookup (self->textures, GUINT_TO_POINTER (texture_id))))
|
|
t->permanent = TRUE;
|
|
|
|
return t;
|
|
}
|
|
|
|
void
|
|
gsk_gl_driver_release_texture_by_id (GskGLDriver *self,
|
|
guint texture_id)
|
|
{
|
|
GskGLTexture *texture;
|
|
|
|
g_return_if_fail (GSK_IS_GL_DRIVER (self));
|
|
g_return_if_fail (texture_id > 0);
|
|
|
|
remove_texture_key_for_id (self, texture_id);
|
|
|
|
if ((texture = g_hash_table_lookup (self->textures, GUINT_TO_POINTER (texture_id))))
|
|
gsk_gl_driver_release_texture (self, texture);
|
|
}
|
|
|
|
typedef struct _GskGLTextureState
|
|
{
|
|
GdkGLContext *context;
|
|
GLuint texture_id;
|
|
GLsync sync;
|
|
} GskGLTextureState;
|
|
|
|
static void
|
|
create_texture_from_texture_destroy (gpointer data)
|
|
{
|
|
GskGLTextureState *state = data;
|
|
|
|
g_assert (state != NULL);
|
|
g_assert (GDK_IS_GL_CONTEXT (state->context));
|
|
|
|
gdk_gl_context_make_current (state->context);
|
|
glDeleteTextures (1, &state->texture_id);
|
|
if (state->sync)
|
|
glDeleteSync (state->sync);
|
|
g_clear_object (&state->context);
|
|
g_free (state);
|
|
}
|
|
|
|
GdkTexture *
|
|
gsk_gl_driver_create_gdk_texture (GskGLDriver *self,
|
|
guint texture_id)
|
|
{
|
|
GskGLTextureState *state;
|
|
GdkGLTextureBuilder *builder;
|
|
GskGLTexture *texture;
|
|
GdkTexture *result;
|
|
|
|
g_return_val_if_fail (GSK_IS_GL_DRIVER (self), NULL);
|
|
g_return_val_if_fail (self->command_queue != NULL, NULL);
|
|
g_return_val_if_fail (GDK_IS_GL_CONTEXT (self->command_queue->context), NULL);
|
|
g_return_val_if_fail (texture_id > 0, NULL);
|
|
g_return_val_if_fail (!g_hash_table_contains (self->texture_id_to_key, GUINT_TO_POINTER (texture_id)), NULL);
|
|
|
|
/* We must be tracking this texture_id already to use it */
|
|
if (!(texture = g_hash_table_lookup (self->textures, GUINT_TO_POINTER (texture_id))))
|
|
g_return_val_if_reached (NULL);
|
|
|
|
state = g_new0 (GskGLTextureState, 1);
|
|
state->texture_id = texture_id;
|
|
state->context = g_object_ref (self->command_queue->context);
|
|
if (gdk_gl_context_has_sync (self->command_queue->context))
|
|
state->sync = glFenceSync (GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
|
|
|
g_hash_table_steal (self->textures, GUINT_TO_POINTER (texture_id));
|
|
|
|
builder = gdk_gl_texture_builder_new ();
|
|
gdk_gl_texture_builder_set_context (builder, self->command_queue->context);
|
|
gdk_gl_texture_builder_set_id (builder, texture_id);
|
|
gdk_gl_texture_builder_set_width (builder, texture->width);
|
|
gdk_gl_texture_builder_set_height (builder, texture->height);
|
|
gdk_gl_texture_builder_set_sync (builder, state->sync);
|
|
|
|
result = gdk_gl_texture_builder_build (builder,
|
|
create_texture_from_texture_destroy,
|
|
state);
|
|
|
|
texture->texture_id = 0;
|
|
gsk_gl_texture_free (texture);
|
|
g_object_unref (builder);
|
|
|
|
return result;
|
|
}
|