gsk: add OpenGL based GskNglRenderer

The primary goal here was to cleanup the current GL renderer to make
maintenance easier going forward. Furthermore, it tracks state to allow
us to implement more advanced renderer features going forward.

Reordering

This renderer will reorder batches by render target to reduce the number
of times render targets are changed.

In the future, we could also reorder by program within the render target
if we can determine that vertices do not overlap.

Uniform Snapshots

To allow for reordering of batches all uniforms need to be tracked for
the programs. This allows us to create the full uniform state when the
batch has been moved into a new position.

Some care was taken as it can be performance sensitive.

Attachment Snapshots

Similar to uniform snapshots, we need to know all of the texture
attachments so that we can rebind them when necessary.

Render Jobs

To help isolate the process of creating GL commands from the renderer
abstraction a render job abstraction was added. This could be extended
in the future if we decided to do tiling.

Command Queue

Render jobs create batches using the command queue. The command queue
will snapshot uniform and attachment state so that it can reorder
batches right before executing them.

Currently, the only reordering done is to ensure that we only visit
each render target once. We could extend this by tracking vertices,
attachments, and others.

This code currently uses an inline array helper to reduce overhead
from GArray which was showing up on profiles. It could be changed to
use GdkArray without too much work, but had roughly double the
instructions. Cycle counts have not yet been determined.

GLSL Programs

This was simplified to use XMACROS so that we can just extend one file
(gskglprograms.defs) instead of multiple places. The programs are added
as fields in the driver for easy access.

Driver

The driver manages textures, render targets, access to atlases,
programs, and more. There is one driver per display, by using the
shared GL context.

Some work could be done here to batch uploads so that we make fewer
calls to upload when sending icon theme data to the GPU. We'd need
to keep a copy of the atlas data for such purposes.
This commit is contained in:
Christian Hergert 2020-12-18 17:36:59 -08:00
parent 9698d4aa2a
commit 2a38cecd33
40 changed files with 12428 additions and 3 deletions

View File

@ -25,6 +25,7 @@
#include "gsk/gskrendernodeparserprivate.h" #include "gsk/gskrendernodeparserprivate.h"
#include "gsk/gl/gskglrenderer.h" #include "gsk/gl/gskglrenderer.h"
#include "gsk/ngl/gsknglrenderer.h"
#ifdef GDK_WINDOWING_BROADWAY #ifdef GDK_WINDOWING_BROADWAY
#include "gsk/broadway/gskbroadwayrenderer.h" #include "gsk/broadway/gskbroadwayrenderer.h"
#endif #endif
@ -762,6 +763,9 @@ node_editor_window_realize (GtkWidget *widget)
node_editor_window_add_renderer (self, node_editor_window_add_renderer (self,
gsk_gl_renderer_new (), gsk_gl_renderer_new (),
"OpenGL"); "OpenGL");
node_editor_window_add_renderer (self,
gsk_ngl_renderer_new (),
"NGL");
#ifdef GDK_RENDERING_VULKAN #ifdef GDK_RENDERING_VULKAN
node_editor_window_add_renderer (self, node_editor_window_add_renderer (self,
gsk_vulkan_renderer_new (), gsk_vulkan_renderer_new (),

View File

@ -139,7 +139,9 @@
#include "gskglshader.h" #include "gskglshader.h"
#include "gskglshaderprivate.h" #include "gskglshaderprivate.h"
#include "gskdebugprivate.h" #include "gskdebugprivate.h"
#include "gl/gskglrendererprivate.h" #include "gl/gskglrendererprivate.h"
#include "ngl/gsknglrendererprivate.h"
static GskGLUniformType static GskGLUniformType
uniform_type_from_glsl (const char *str) uniform_type_from_glsl (const char *str)
@ -542,8 +544,9 @@ gsk_gl_shader_compile (GskGLShader *shader,
g_return_val_if_fail (GSK_IS_GL_SHADER (shader), FALSE); g_return_val_if_fail (GSK_IS_GL_SHADER (shader), FALSE);
if (GSK_IS_GL_RENDERER (renderer)) if (GSK_IS_GL_RENDERER (renderer))
return gsk_gl_renderer_try_compile_gl_shader (GSK_GL_RENDERER (renderer), return gsk_gl_renderer_try_compile_gl_shader (GSK_GL_RENDERER (renderer), shader, error);
shader, error); else if (GSK_IS_NGL_RENDERER (renderer))
return gsk_ngl_renderer_try_compile_gl_shader (GSK_NGL_RENDERER (renderer), shader, error);
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"The renderer does not support gl shaders"); "The renderer does not support gl shaders");

View File

@ -39,6 +39,7 @@
#include "gskcairorenderer.h" #include "gskcairorenderer.h"
#include "gskdebugprivate.h" #include "gskdebugprivate.h"
#include "gl/gskglrenderer.h" #include "gl/gskglrenderer.h"
#include "ngl/gsknglrenderer.h"
#include "gskprofilerprivate.h" #include "gskprofilerprivate.h"
#include "gskrendernodeprivate.h" #include "gskrendernodeprivate.h"
@ -496,6 +497,8 @@ get_renderer_for_name (const char *renderer_name)
else if (g_ascii_strcasecmp (renderer_name, "opengl") == 0 else if (g_ascii_strcasecmp (renderer_name, "opengl") == 0
|| g_ascii_strcasecmp (renderer_name, "gl") == 0) || g_ascii_strcasecmp (renderer_name, "gl") == 0)
return GSK_TYPE_GL_RENDERER; return GSK_TYPE_GL_RENDERER;
else if (g_ascii_strcasecmp (renderer_name, "ngl") == 0)
return GSK_TYPE_NGL_RENDERER;
#ifdef GDK_RENDERING_VULKAN #ifdef GDK_RENDERING_VULKAN
else if (g_ascii_strcasecmp (renderer_name, "vulkan") == 0) else if (g_ascii_strcasecmp (renderer_name, "vulkan") == 0)
return GSK_TYPE_VULKAN_RENDERER; return GSK_TYPE_VULKAN_RENDERER;
@ -511,6 +514,7 @@ get_renderer_for_name (const char *renderer_name)
g_print (" cairo - Use the Cairo fallback renderer\n"); g_print (" cairo - Use the Cairo fallback renderer\n");
g_print (" opengl - Use the default OpenGL renderer\n"); g_print (" opengl - Use the default OpenGL renderer\n");
g_print (" gl - Same as opengl\n"); g_print (" gl - Same as opengl\n");
g_print (" next - Another OpenGL renderer\n");
#ifdef GDK_RENDERING_VULKAN #ifdef GDK_RENDERING_VULKAN
g_print (" vulkan - Use the Vulkan renderer\n"); g_print (" vulkan - Use the Vulkan renderer\n");
#else #else

View File

@ -31,6 +31,7 @@ gsk_public_sources = files([
'gskroundedrect.c', 'gskroundedrect.c',
'gsktransform.c', 'gsktransform.c',
'gl/gskglrenderer.c', 'gl/gskglrenderer.c',
'ngl/gsknglrenderer.c',
]) ])
gsk_private_sources = files([ gsk_private_sources = files([
@ -48,6 +49,19 @@ gsk_private_sources = files([
'gl/gskgliconcache.c', 'gl/gskgliconcache.c',
'gl/opbuffer.c', 'gl/opbuffer.c',
'gl/stb_rect_pack.c', 'gl/stb_rect_pack.c',
'ngl/gsknglattachmentstate.c',
'ngl/gsknglbuffer.c',
'ngl/gsknglcommandqueue.c',
'ngl/gsknglcompiler.c',
'ngl/gskngldriver.c',
'ngl/gsknglglyphlibrary.c',
'ngl/gskngliconlibrary.c',
'ngl/gsknglprogram.c',
'ngl/gsknglrenderjob.c',
'ngl/gsknglshadowlibrary.c',
'ngl/gskngltexturelibrary.c',
'ngl/gskngluniformstate.c',
'ngl/gskngltexturepool.c',
]) ])
gsk_public_headers = files([ gsk_public_headers = files([
@ -64,7 +78,8 @@ gsk_public_headers = files([
install_headers(gsk_public_headers, 'gsk.h', subdir: 'gtk-4.0/gsk') install_headers(gsk_public_headers, 'gsk.h', subdir: 'gtk-4.0/gsk')
gsk_public_gl_headers = files([ gsk_public_gl_headers = files([
'gl/gskglrenderer.h' 'gl/gskglrenderer.h',
'ngl/gsknglrenderer.h',
]) ])
install_headers(gsk_public_gl_headers, subdir: 'gtk-4.0/gsk/gl') install_headers(gsk_public_gl_headers, subdir: 'gtk-4.0/gsk/gl')
gsk_public_headers += gsk_public_gl_headers gsk_public_headers += gsk_public_gl_headers

View File

@ -0,0 +1,106 @@
/* gsknglattachmentstate.c
*
* Copyright 2020 Christian Hergert <chergert@redhat.com>
*
* This file 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 file 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 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 "gsknglattachmentstateprivate.h"
GskNglAttachmentState *
gsk_ngl_attachment_state_new (void)
{
GskNglAttachmentState *self;
self = g_atomic_rc_box_new0 (GskNglAttachmentState);
self->fbo.changed = FALSE;
self->fbo.id = 0;
self->n_changed = 0;
/* Initialize textures, assume we are 2D by default since it
* doesn't really matter until we bind something other than
* GL_TEXTURE0 to it anyway.
*/
for (guint i = 0; i < G_N_ELEMENTS (self->textures); i++)
{
self->textures[i].target = GL_TEXTURE_2D;
self->textures[i].texture = GL_TEXTURE0;
self->textures[i].id = 0;
self->textures[i].changed = FALSE;
self->textures[i].initial = TRUE;
}
return self;
}
GskNglAttachmentState *
gsk_ngl_attachment_state_ref (GskNglAttachmentState *self)
{
return g_atomic_rc_box_acquire (self);
}
void
gsk_ngl_attachment_state_unref (GskNglAttachmentState *self)
{
g_atomic_rc_box_release (self);
}
void
gsk_ngl_attachment_state_bind_texture (GskNglAttachmentState *self,
GLenum target,
GLenum texture,
guint id)
{
GskNglBindTexture *attach;
g_assert (self != NULL);
g_assert (target == GL_TEXTURE_1D ||
target == GL_TEXTURE_2D ||
target == GL_TEXTURE_3D);
g_assert (texture >= GL_TEXTURE0 && texture <= GL_TEXTURE16);
attach = &self->textures[texture - GL_TEXTURE0];
if (attach->target != target || attach->texture != texture || attach->id != id)
{
attach->target = target;
attach->texture = texture;
attach->id = id;
attach->initial = FALSE;
if (attach->changed == FALSE)
{
attach->changed = TRUE;
self->n_changed++;
}
}
}
void
gsk_ngl_attachment_state_bind_framebuffer (GskNglAttachmentState *self,
guint id)
{
g_assert (self != NULL);
if (self->fbo.id != id)
{
self->fbo.id = id;
self->fbo.changed = TRUE;
}
}

View File

@ -0,0 +1,71 @@
/* gsknglattachmentstateprivate.h
*
* Copyright 2020 Christian Hergert <chergert@redhat.com>
*
* This file 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 file 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 General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __GSK_NGL_ATTACHMENT_STATE_PRIVATE_H__
#define __GSK_NGL_ATTACHMENT_STATE_PRIVATE_H__
#include "gskngltypesprivate.h"
G_BEGIN_DECLS
typedef struct _GskNglAttachmentState GskNglAttachmentState;
typedef struct _GskNglBindFramebuffer GskNglBindFramebuffer;
typedef struct _GskNglBindTexture GskNglBindTexture;
struct _GskNglBindTexture
{
guint changed : 1;
guint initial : 1;
GLenum target : 30;
GLenum texture;
guint id;
};
G_STATIC_ASSERT (sizeof (GskNglBindTexture) == 12);
struct _GskNglBindFramebuffer
{
guint changed : 1;
guint id : 31;
};
G_STATIC_ASSERT (sizeof (GskNglBindFramebuffer) == 4);
struct _GskNglAttachmentState
{
GskNglBindFramebuffer fbo;
/* Increase if shaders add more textures */
GskNglBindTexture textures[4];
guint n_changed;
};
GskNglAttachmentState *gsk_ngl_attachment_state_new (void);
GskNglAttachmentState *gsk_ngl_attachment_state_ref (GskNglAttachmentState *self);
void gsk_ngl_attachment_state_unref (GskNglAttachmentState *self);
void gsk_ngl_attachment_state_bind_texture (GskNglAttachmentState *self,
GLenum target,
GLenum texture,
guint id);
void gsk_ngl_attachment_state_bind_framebuffer (GskNglAttachmentState *self,
guint id);
G_END_DECLS
#endif /* __GSK_NGL_ATTACHMENT_STATE_PRIVATE_H__ */

69
gsk/ngl/gsknglbuffer.c Normal file
View File

@ -0,0 +1,69 @@
/* gsknglbufferprivate.h
*
* Copyright 2020 Christian Hergert <chergert@redhat.com>
*
* This file 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 file 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 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 <string.h>
#include "gsknglbufferprivate.h"
/**
* gsk_ngl_buffer_init:
* @target: the target buffer such as %GL_ARRAY_BUFFER or %GL_UNIFORM_BUFFER
* @element_size: the size of elements within the buffer
*
* Creates a new #GskNglBuffer which can be used to deliver data to shaders
* within a GLSL program. You can use this to store vertices such as with
* %GL_ARRAY_BUFFER or uniform data with %GL_UNIFORM_BUFFER.
*/
void
gsk_ngl_buffer_init (GskNglBuffer *self,
GLenum target,
guint element_size)
{
memset (self, 0, sizeof *self);
/* Default to 2 pages, power-of-two growth from there */
self->buffer_len = 4096 * 2;
self->buffer = g_malloc (self->buffer_len);
self->target = target;
self->element_size = element_size;
}
GLuint
gsk_ngl_buffer_submit (GskNglBuffer *buffer)
{
GLuint id;
glGenBuffers (1, &id);
glBindBuffer (buffer->target, id);
glBufferData (buffer->target, buffer->buffer_pos, buffer->buffer, GL_STATIC_DRAW);
buffer->buffer_pos = 0;
buffer->count = 0;
return id;
}
void
gsk_ngl_buffer_destroy (GskNglBuffer *buffer)
{
g_clear_pointer (&buffer->buffer, g_free);
}

View File

@ -0,0 +1,81 @@
/* gsknglbufferprivate.h
*
* Copyright 2020 Christian Hergert <chergert@redhat.com>
*
* This file 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 file 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 General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __GSK_NGL_BUFFER_PRIVATE_H__
#define __GSK_NGL_BUFFER_PRIVATE_H__
#include "gskngltypesprivate.h"
G_BEGIN_DECLS
typedef struct _GskNglBuffer
{
guint8 *buffer;
gsize buffer_pos;
gsize buffer_len;
guint count;
GLenum target;
guint element_size;
} GskNglBuffer;
void gsk_ngl_buffer_init (GskNglBuffer *self,
GLenum target,
guint element_size);
void gsk_ngl_buffer_destroy (GskNglBuffer *buffer);
GLuint gsk_ngl_buffer_submit (GskNglBuffer *buffer);
static inline gpointer
gsk_ngl_buffer_advance (GskNglBuffer *buffer,
guint count)
{
gpointer ret;
gsize to_alloc = count * buffer->element_size;
if G_UNLIKELY (buffer->buffer_pos + to_alloc > buffer->buffer_len)
{
buffer->buffer_len *= 2;
buffer->buffer = g_realloc (buffer->buffer, buffer->buffer_len);
}
ret = buffer->buffer + buffer->buffer_pos;
buffer->buffer_pos += to_alloc;
buffer->count += count;
return ret;
}
static inline void
gsk_ngl_buffer_retract (GskNglBuffer *buffer,
guint count)
{
buffer->buffer_pos -= count * buffer->element_size;
buffer->count -= count;
}
static inline guint
gsk_ngl_buffer_get_offset (GskNglBuffer *buffer)
{
return buffer->count;
}
G_END_DECLS
#endif /* __GSK_NGL_BUFFER_PRIVATE_H__ */

1507
gsk/ngl/gsknglcommandqueue.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,362 @@
/* gsknglcommandqueueprivate.h
*
* 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
*/
#ifndef __GSK_NGL_COMMAND_QUEUE_PRIVATE_H__
#define __GSK_NGL_COMMAND_QUEUE_PRIVATE_H__
#include <gsk/gskprofilerprivate.h>
#include "gskngltypesprivate.h"
#include "gsknglbufferprivate.h"
#include "gsknglattachmentstateprivate.h"
#include "gskngluniformstateprivate.h"
#include "inlinearray.h"
#include "../gl/gskglprofilerprivate.h"
G_BEGIN_DECLS
#define GSK_TYPE_GL_COMMAND_QUEUE (gsk_ngl_command_queue_get_type())
G_DECLARE_FINAL_TYPE (GskNglCommandQueue, gsk_ngl_command_queue, GSK, NGL_COMMAND_QUEUE, GObject)
typedef enum _GskNglCommandKind
{
/* The batch will perform a glClear() */
GSK_NGL_COMMAND_KIND_CLEAR,
/* The batch will perform a glDrawArrays() */
GSK_NGL_COMMAND_KIND_DRAW,
} GskNglCommandKind;
typedef struct _GskNglCommandBind
{
/* @texture is the value passed to glActiveTexture(), the "slot" the
* texture will be placed into. We always use GL_TEXTURE_2D so we don't
* waste any bits here to indicate that.
*/
guint texture : 5;
/* The identifier for the texture created with glGenTextures(). */
guint id : 27;
} GskNglCommandBind;
G_STATIC_ASSERT (sizeof (GskNglCommandBind) == 4);
typedef struct _GskNglCommandBatchAny
{
/* A GskNglCommandKind indicating what the batch will do */
guint kind : 8;
/* The program's identifier to use for determining if we can merge two
* batches together into a single set of draw operations. We put this
* here instead of the GskNglCommandDraw so that we can use the extra
* bits here without making the structure larger.
*/
guint program : 24;
/* The index of the next batch following this one. This is used
* as a sort of integer-based linked list to simplify out-of-order
* batching without moving memory around. -1 indicates last batch.
*/
gint16 next_batch_index;
/* Same but for reverse direction as we sort in reverse to get the
* batches ordered by framebuffer.
*/
gint16 prev_batch_index;
/* The viewport size of the batch. We check this as we process
* batches to determine if we need to resize the viewport.
*/
struct {
guint16 width;
guint16 height;
} viewport;
} GskNglCommandBatchAny;
G_STATIC_ASSERT (sizeof (GskNglCommandBatchAny) == 12);
typedef struct _GskNglCommandDraw
{
GskNglCommandBatchAny head;
/* There doesn't seem to be a limit on the framebuffer identifier that
* can be returned, so we have to use a whole unsigned for the framebuffer
* we are drawing to. When processing batches, we check to see if this
* changes and adjust the render target accordingly. Some sorting is
* performed to reduce the amount we change framebuffers.
*/
guint framebuffer;
/* The number of uniforms to change. This must be less than or equal to
* GL_MAX_UNIFORM_LOCATIONS but only guaranteed up to 1024 by any OpenGL
* implementation to be conformant.
*/
guint uniform_count : 11;
/* The number of textures to bind, which is only guaranteed up to 16
* by the OpenGL specification to be conformant.
*/
guint bind_count : 5;
/* GL_MAX_ELEMENTS_VERTICES specifies 33000 for this which requires 16-bit
* to address all possible counts <= GL_MAX_ELEMENTS_VERTICES.
*/
guint vbo_count : 16;
/* The offset within the VBO containing @vbo_count vertices to send with
* glDrawArrays().
*/
guint vbo_offset;
/* The offset within the array of uniform changes to be made containing
* @uniform_count #GskNglCommandUniform elements to apply.
*/
guint uniform_offset;
/* The offset within the array of bind changes to be made containing
* @bind_count #GskNglCommandBind elements to apply.
*/
guint bind_offset;
} GskNglCommandDraw;
G_STATIC_ASSERT (sizeof (GskNglCommandDraw) == 32);
typedef struct _GskNglCommandClear
{
GskNglCommandBatchAny any;
guint bits;
guint framebuffer;
} GskNglCommandClear;
G_STATIC_ASSERT (sizeof (GskNglCommandClear) == 20);
typedef struct _GskNglCommandUniform
{
GskNglUniformInfo info;
guint location;
} GskNglCommandUniform;
G_STATIC_ASSERT (sizeof (GskNglCommandUniform) == 8);
typedef union _GskNglCommandBatch
{
GskNglCommandBatchAny any;
GskNglCommandDraw draw;
GskNglCommandClear clear;
} GskNglCommandBatch;
G_STATIC_ASSERT (sizeof (GskNglCommandBatch) == 32);
DEFINE_INLINE_ARRAY (GskNglCommandBatches, gsk_ngl_command_batches, GskNglCommandBatch)
DEFINE_INLINE_ARRAY (GskNglCommandBinds, gsk_ngl_command_binds, GskNglCommandBind)
DEFINE_INLINE_ARRAY (GskNglCommandUniforms, gsk_ngl_command_uniforms, GskNglCommandUniform)
struct _GskNglCommandQueue
{
GObject parent_instance;
/* The GdkGLContext we make current before executing GL commands. */
GdkGLContext *context;
/* Array of GskNglCommandBatch which is a fixed size structure that will
* point into offsets of other arrays so that all similar data is stored
* together. The idea here is that we reduce the need for pointers so that
* using g_realloc()'d arrays is fine.
*/
GskNglCommandBatches batches;
/* Contains array of vertices and some wrapper code to help upload them
* to the GL driver. We can also tweak this to use double buffered arrays
* if we find that to be faster on some hardware and/or drivers.
*/
GskNglBuffer vertices;
/* The GskNglAttachmentState contains information about our FBO and texture
* attachments as we process incoming operations. We snapshot them into
* various batches so that we can compare differences between merge
* candidates.
*/
GskNglAttachmentState *attachments;
/* The uniform state across all programs. We snapshot this into batches so
* that we can compare uniform state between batches to give us more
* chances at merging draw commands.
*/
GskNglUniformState *uniforms;
/* Current program if we are in a draw so that we can send commands
* to the uniform state as needed.
*/
GskNglUniformProgram *program_info;
/* The profiler instance to deliver timing/etc data */
GskProfiler *profiler;
GskGLProfiler *gl_profiler;
/* Array of GskNglCommandBind which denote what textures need to be attached
* to which slot. GskNglCommandDraw.bind_offset and bind_count reference this
* array to determine what to attach.
*/
GskNglCommandBinds batch_binds;
/* Array of GskNglCommandUniform denoting which uniforms must be updated
* before the glDrawArrays() may be called. These are referenced from the
* GskNglCommandDraw.uniform_offset and uniform_count fields.
*/
GskNglCommandUniforms batch_uniforms;
/* String storage for debug groups */
GStringChunk *debug_groups;
/* Discovered max texture size when loading the command queue so that we
* can either scale down or slice textures to fit within this size. Assumed
* to be both height and width.
*/
int max_texture_size;
/* The index of the last batch in @batches, which may not be the element
* at the end of the array, as batches can be reordered. This is used to
* update the "next" index when adding a new batch.
*/
gint16 tail_batch_index;
gint16 head_batch_index;
/* Max framebuffer we used, so we can sort items faster */
guint fbo_max;
/* Various GSK and GDK metric counter ids */
struct {
GQuark n_frames;
GQuark cpu_time;
GQuark gpu_time;
guint n_binds;
guint n_fbos;
guint n_uniforms;
guint n_uploads;
guint queue_depth;
} metrics;
/* Counter for uploads on the frame */
guint n_uploads;
/* If we're inside a begin/end_frame pair */
guint in_frame : 1;
/* If we're inside of a begin_draw()/end_draw() pair. */
guint in_draw : 1;
/* If we've warned about truncating batches */
guint have_truncated : 1;
};
GskNglCommandQueue *gsk_ngl_command_queue_new (GdkGLContext *context,
GskNglUniformState *uniforms);
void gsk_ngl_command_queue_set_profiler (GskNglCommandQueue *self,
GskProfiler *profiler);
GdkGLContext *gsk_ngl_command_queue_get_context (GskNglCommandQueue *self);
void gsk_ngl_command_queue_make_current (GskNglCommandQueue *self);
void gsk_ngl_command_queue_begin_frame (GskNglCommandQueue *self);
void gsk_ngl_command_queue_end_frame (GskNglCommandQueue *self);
void gsk_ngl_command_queue_execute (GskNglCommandQueue *self,
guint surface_height,
guint scale_factor,
const cairo_region_t *scissor);
int gsk_ngl_command_queue_upload_texture (GskNglCommandQueue *self,
GdkTexture *texture,
guint x_offset,
guint y_offset,
guint width,
guint height,
int min_filter,
int mag_filter);
int gsk_ngl_command_queue_create_texture (GskNglCommandQueue *self,
int width,
int height,
int min_filter,
int mag_filter);
guint gsk_ngl_command_queue_create_framebuffer (GskNglCommandQueue *self);
gboolean gsk_ngl_command_queue_create_render_target (GskNglCommandQueue *self,
int width,
int height,
int min_filter,
int mag_filter,
guint *out_fbo_id,
guint *out_texture_id);
void gsk_ngl_command_queue_delete_program (GskNglCommandQueue *self,
guint program_id);
void gsk_ngl_command_queue_clear (GskNglCommandQueue *self,
guint clear_bits,
const graphene_rect_t *viewport);
void gsk_ngl_command_queue_begin_draw (GskNglCommandQueue *self,
GskNglUniformProgram *program_info,
guint width,
guint height);
void gsk_ngl_command_queue_end_draw (GskNglCommandQueue *self);
void gsk_ngl_command_queue_split_draw (GskNglCommandQueue *self);
static inline GskNglCommandBatch *
gsk_ngl_command_queue_get_batch (GskNglCommandQueue *self)
{
return gsk_ngl_command_batches_tail (&self->batches);
}
static inline GskNglDrawVertex *
gsk_ngl_command_queue_add_vertices (GskNglCommandQueue *self)
{
gsk_ngl_command_queue_get_batch (self)->draw.vbo_count += GSK_NGL_N_VERTICES;
return gsk_ngl_buffer_advance (&self->vertices, GSK_NGL_N_VERTICES);
}
static inline GskNglDrawVertex *
gsk_ngl_command_queue_add_n_vertices (GskNglCommandQueue *self,
guint count)
{
/* This is a batch form of gsk_ngl_command_queue_add_vertices(). Note that
* it does *not* add the count to .draw.vbo_count as the caller is responsible
* for that.
*/
return gsk_ngl_buffer_advance (&self->vertices, GSK_NGL_N_VERTICES * count);
}
static inline void
gsk_ngl_command_queue_retract_n_vertices (GskNglCommandQueue *self,
guint count)
{
/* Like gsk_ngl_command_queue_add_n_vertices(), this does not tweak
* the draw vbo_count.
*/
gsk_ngl_buffer_retract (&self->vertices, GSK_NGL_N_VERTICES * count);
}
static inline guint
gsk_ngl_command_queue_bind_framebuffer (GskNglCommandQueue *self,
guint framebuffer)
{
guint ret = self->attachments->fbo.id;
gsk_ngl_attachment_state_bind_framebuffer (self->attachments, framebuffer);
return ret;
}
G_END_DECLS
#endif /* __GSK_NGL_COMMAND_QUEUE_PRIVATE_H__ */

678
gsk/ngl/gsknglcompiler.c Normal file
View File

@ -0,0 +1,678 @@
/* gsknglcompiler.c
*
* 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 <gsk/gskdebugprivate.h>
#include <gio/gio.h>
#include <string.h>
#include "gsknglcommandqueueprivate.h"
#include "gsknglcompilerprivate.h"
#include "gsknglprogramprivate.h"
#define SHADER_VERSION_GLES 100
#define SHADER_VERSION_GL2_LEGACY 110
#define SHADER_VERSION_GL3_LEGACY 130
#define SHADER_VERSION_GL3 150
struct _GskNglCompiler
{
GObject parent_instance;
GskNglDriver *driver;
GBytes *all_preamble;
GBytes *fragment_preamble;
GBytes *vertex_preamble;
GBytes *fragment_source;
GBytes *fragment_suffix;
GBytes *vertex_source;
GBytes *vertex_suffix;
GArray *attrib_locations;
int glsl_version;
guint gl3 : 1;
guint gles : 1;
guint legacy : 1;
guint debug_shaders : 1;
};
typedef struct _GskNglProgramAttrib
{
const char *name;
guint location;
} GskNglProgramAttrib;
static GBytes *empty_bytes;
G_DEFINE_TYPE (GskNglCompiler, gsk_ngl_compiler, G_TYPE_OBJECT)
static void
gsk_ngl_compiler_finalize (GObject *object)
{
GskNglCompiler *self = (GskNglCompiler *)object;
g_clear_pointer (&self->all_preamble, g_bytes_unref);
g_clear_pointer (&self->fragment_preamble, g_bytes_unref);
g_clear_pointer (&self->vertex_preamble, g_bytes_unref);
g_clear_pointer (&self->vertex_suffix, g_bytes_unref);
g_clear_pointer (&self->fragment_source, g_bytes_unref);
g_clear_pointer (&self->fragment_suffix, g_bytes_unref);
g_clear_pointer (&self->vertex_source, g_bytes_unref);
g_clear_pointer (&self->attrib_locations, g_array_unref);
g_clear_object (&self->driver);
G_OBJECT_CLASS (gsk_ngl_compiler_parent_class)->finalize (object);
}
static void
gsk_ngl_compiler_class_init (GskNglCompilerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gsk_ngl_compiler_finalize;
empty_bytes = g_bytes_new (NULL, 0);
}
static void
gsk_ngl_compiler_init (GskNglCompiler *self)
{
self->glsl_version = 150;
self->attrib_locations = g_array_new (FALSE, FALSE, sizeof (GskNglProgramAttrib));
self->all_preamble = g_bytes_ref (empty_bytes);
self->vertex_preamble = g_bytes_ref (empty_bytes);
self->fragment_preamble = g_bytes_ref (empty_bytes);
self->vertex_source = g_bytes_ref (empty_bytes);
self->vertex_suffix = g_bytes_ref (empty_bytes);
self->fragment_source = g_bytes_ref (empty_bytes);
self->fragment_suffix = g_bytes_ref (empty_bytes);
}
GskNglCompiler *
gsk_ngl_compiler_new (GskNglDriver *driver,
gboolean debug_shaders)
{
GskNglCompiler *self;
GdkGLContext *context;
g_return_val_if_fail (GSK_IS_NGL_DRIVER (driver), NULL);
g_return_val_if_fail (driver->shared_command_queue != NULL, NULL);
self = g_object_new (GSK_TYPE_GL_COMPILER, NULL);
self->driver = g_object_ref (driver);
self->debug_shaders = !!debug_shaders;
context = gsk_ngl_command_queue_get_context (self->driver->shared_command_queue);
if (gdk_gl_context_get_use_es (context))
{
self->glsl_version = SHADER_VERSION_GLES;
self->gles = TRUE;
}
else if (gdk_gl_context_is_legacy (context))
{
int maj, min;
gdk_gl_context_get_version (context, &maj, &min);
if (maj == 3)
self->glsl_version = SHADER_VERSION_GL3_LEGACY;
else
self->glsl_version = SHADER_VERSION_GL2_LEGACY;
self->legacy = TRUE;
}
else
{
self->glsl_version = SHADER_VERSION_GL3;
self->gl3 = TRUE;
}
gsk_ngl_command_queue_make_current (self->driver->shared_command_queue);
return g_steal_pointer (&self);
}
void
gsk_ngl_compiler_bind_attribute (GskNglCompiler *self,
const char *name,
guint location)
{
GskNglProgramAttrib attrib;
g_return_if_fail (GSK_IS_NGL_COMPILER (self));
g_return_if_fail (name != NULL);
g_return_if_fail (location < 32);
attrib.name = g_intern_string (name);
attrib.location = location;
g_array_append_val (self->attrib_locations, attrib);
}
void
gsk_ngl_compiler_clear_attributes (GskNglCompiler *self)
{
g_return_if_fail (GSK_IS_NGL_COMPILER (self));
g_array_set_size (self->attrib_locations, 0);
}
void
gsk_ngl_compiler_set_preamble (GskNglCompiler *self,
GskNglCompilerKind kind,
GBytes *preamble_bytes)
{
GBytes **loc = NULL;
g_return_if_fail (GSK_IS_NGL_COMPILER (self));
g_return_if_fail (preamble_bytes != NULL);
if (kind == GSK_NGL_COMPILER_ALL)
loc = &self->all_preamble;
else if (kind == GSK_NGL_COMPILER_FRAGMENT)
loc = &self->fragment_preamble;
else if (kind == GSK_NGL_COMPILER_VERTEX)
loc = &self->vertex_preamble;
else
g_return_if_reached ();
g_assert (loc != NULL);
if (*loc != preamble_bytes)
{
g_clear_pointer (loc, g_bytes_unref);
*loc = preamble_bytes ? g_bytes_ref (preamble_bytes) : NULL;
}
}
void
gsk_ngl_compiler_set_preamble_from_resource (GskNglCompiler *self,
GskNglCompilerKind kind,
const char *resource_path)
{
GError *error = NULL;
GBytes *bytes;
g_return_if_fail (GSK_IS_NGL_COMPILER (self));
g_return_if_fail (kind == GSK_NGL_COMPILER_ALL ||
kind == GSK_NGL_COMPILER_VERTEX ||
kind == GSK_NGL_COMPILER_FRAGMENT);
g_return_if_fail (resource_path != NULL);
bytes = g_resources_lookup_data (resource_path,
G_RESOURCE_LOOKUP_FLAGS_NONE,
&error);
if (bytes == NULL)
g_warning ("Cannot set shader from resource: %s", error->message);
else
gsk_ngl_compiler_set_preamble (self, kind, bytes);
g_clear_pointer (&bytes, g_bytes_unref);
g_clear_error (&error);
}
void
gsk_ngl_compiler_set_source (GskNglCompiler *self,
GskNglCompilerKind kind,
GBytes *source_bytes)
{
GBytes **loc = NULL;
g_return_if_fail (GSK_IS_NGL_COMPILER (self));
g_return_if_fail (kind == GSK_NGL_COMPILER_ALL ||
kind == GSK_NGL_COMPILER_VERTEX ||
kind == GSK_NGL_COMPILER_FRAGMENT);
if (source_bytes == NULL)
source_bytes = empty_bytes;
/* If kind is ALL, then we need to split the fragment and
* vertex shaders from the bytes and assign them individually.
* This safely scans for FRAGMENT_SHADER and VERTEX_SHADER as
* specified within the GLSL resources. Some care is taken to
* use GBytes which reference the original bytes instead of
* copying them.
*/
if (kind == GSK_NGL_COMPILER_ALL)
{
gsize len = 0;
const char *source;
const char *vertex_shader_start;
const char *fragment_shader_start;
const char *endpos;
GBytes *fragment_bytes;
GBytes *vertex_bytes;
g_clear_pointer (&self->fragment_source, g_bytes_unref);
g_clear_pointer (&self->vertex_source, g_bytes_unref);
source = g_bytes_get_data (source_bytes, &len);
endpos = source + len;
vertex_shader_start = g_strstr_len (source, len, "VERTEX_SHADER");
fragment_shader_start = g_strstr_len (source, len, "FRAGMENT_SHADER");
if (vertex_shader_start == NULL)
{
g_warning ("Failed to locate VERTEX_SHADER in shader source");
return;
}
if (fragment_shader_start == NULL)
{
g_warning ("Failed to locate FRAGMENT_SHADER in shader source");
return;
}
if (vertex_shader_start > fragment_shader_start)
{
g_warning ("VERTEX_SHADER must come before FRAGMENT_SHADER");
return;
}
/* Locate next newlines */
while (vertex_shader_start < endpos && vertex_shader_start[0] != '\n')
vertex_shader_start++;
while (fragment_shader_start < endpos && fragment_shader_start[0] != '\n')
fragment_shader_start++;
vertex_bytes = g_bytes_new_from_bytes (source_bytes,
vertex_shader_start - source,
fragment_shader_start - vertex_shader_start);
fragment_bytes = g_bytes_new_from_bytes (source_bytes,
fragment_shader_start - source,
endpos - fragment_shader_start);
gsk_ngl_compiler_set_source (self, GSK_NGL_COMPILER_VERTEX, vertex_bytes);
gsk_ngl_compiler_set_source (self, GSK_NGL_COMPILER_FRAGMENT, fragment_bytes);
g_bytes_unref (fragment_bytes);
g_bytes_unref (vertex_bytes);
return;
}
if (kind == GSK_NGL_COMPILER_FRAGMENT)
loc = &self->fragment_source;
else if (kind == GSK_NGL_COMPILER_VERTEX)
loc = &self->vertex_source;
else
g_return_if_reached ();
if (*loc != source_bytes)
{
g_clear_pointer (loc, g_bytes_unref);
*loc = g_bytes_ref (source_bytes);
}
}
void
gsk_ngl_compiler_set_source_from_resource (GskNglCompiler *self,
GskNglCompilerKind kind,
const char *resource_path)
{
GError *error = NULL;
GBytes *bytes;
g_return_if_fail (GSK_IS_NGL_COMPILER (self));
g_return_if_fail (kind == GSK_NGL_COMPILER_ALL ||
kind == GSK_NGL_COMPILER_VERTEX ||
kind == GSK_NGL_COMPILER_FRAGMENT);
g_return_if_fail (resource_path != NULL);
bytes = g_resources_lookup_data (resource_path,
G_RESOURCE_LOOKUP_FLAGS_NONE,
&error);
if (bytes == NULL)
g_warning ("Cannot set shader from resource: %s", error->message);
else
gsk_ngl_compiler_set_source (self, kind, bytes);
g_clear_pointer (&bytes, g_bytes_unref);
g_clear_error (&error);
}
void
gsk_ngl_compiler_set_suffix (GskNglCompiler *self,
GskNglCompilerKind kind,
GBytes *suffix_bytes)
{
GBytes **loc;
g_return_if_fail (GSK_IS_NGL_COMPILER (self));
g_return_if_fail (kind == GSK_NGL_COMPILER_VERTEX ||
kind == GSK_NGL_COMPILER_FRAGMENT);
g_return_if_fail (suffix_bytes != NULL);
if (suffix_bytes == NULL)
suffix_bytes = empty_bytes;
if (kind == GSK_NGL_COMPILER_FRAGMENT)
loc = &self->fragment_suffix;
else if (kind == GSK_NGL_COMPILER_VERTEX)
loc = &self->vertex_suffix;
else
g_return_if_reached ();
if (*loc != suffix_bytes)
{
g_clear_pointer (loc, g_bytes_unref);
*loc = g_bytes_ref (suffix_bytes);
}
}
void
gsk_ngl_compiler_set_suffix_from_resource (GskNglCompiler *self,
GskNglCompilerKind kind,
const char *resource_path)
{
GError *error = NULL;
GBytes *bytes;
g_return_if_fail (GSK_IS_NGL_COMPILER (self));
g_return_if_fail (kind == GSK_NGL_COMPILER_VERTEX ||
kind == GSK_NGL_COMPILER_FRAGMENT);
g_return_if_fail (resource_path != NULL);
bytes = g_resources_lookup_data (resource_path,
G_RESOURCE_LOOKUP_FLAGS_NONE,
&error);
if (bytes == NULL)
g_warning ("Cannot set suffix from resource: %s", error->message);
else
gsk_ngl_compiler_set_suffix (self, kind, bytes);
g_clear_pointer (&bytes, g_bytes_unref);
g_clear_error (&error);
}
static void
prepend_line_numbers (char *code,
GString *s)
{
char *p;
int line;
p = code;
line = 1;
while (*p)
{
char *end = strchr (p, '\n');
if (end)
end = end + 1; /* Include newline */
else
end = p + strlen (p);
g_string_append_printf (s, "%3d| ", line++);
g_string_append_len (s, p, end - p);
p = end;
}
}
static gboolean
check_shader_error (int shader_id,
GError **error)
{
GLint status;
GLint log_len;
GLint code_len;
char *buffer;
char *code;
GString *s;
glGetShaderiv (shader_id, GL_COMPILE_STATUS, &status);
if G_LIKELY (status == GL_TRUE)
return TRUE;
glGetShaderiv (shader_id, GL_INFO_LOG_LENGTH, &log_len);
buffer = g_malloc0 (log_len + 1);
glGetShaderInfoLog (shader_id, log_len, NULL, buffer);
glGetShaderiv (shader_id, GL_SHADER_SOURCE_LENGTH, &code_len);
code = g_malloc0 (code_len + 1);
glGetShaderSource (shader_id, code_len, NULL, code);
s = g_string_new ("");
prepend_line_numbers (code, s);
g_set_error (error,
GDK_GL_ERROR,
GDK_GL_ERROR_COMPILATION_FAILED,
"Compilation failure in shader.\n"
"Source Code: %s\n"
"\n"
"Error Message:\n"
"%s\n"
"\n",
s->str,
buffer);
g_string_free (s, TRUE);
g_free (buffer);
g_free (code);
return FALSE;
}
static void
print_shader_info (const char *prefix,
int shader_id,
const char *name)
{
if (GSK_DEBUG_CHECK(SHADERS))
{
int code_len;
glGetShaderiv (shader_id, GL_SHADER_SOURCE_LENGTH, &code_len);
if (code_len > 0)
{
char *code;
GString *s;
code = g_malloc0 (code_len + 1);
glGetShaderSource (shader_id, code_len, NULL, code);
s = g_string_new (NULL);
prepend_line_numbers (code, s);
g_message ("%s %d, %s:\n%s",
prefix, shader_id,
name ? name : "unnamed",
s->str);
g_string_free (s, TRUE);
g_free (code);
}
}
}
static const char *
get_shader_string (GBytes *bytes)
{
/* 0 length bytes will give us NULL back */
const char *str = g_bytes_get_data (bytes, NULL);
return str ? str : "";
}
GskNglProgram *
gsk_ngl_compiler_compile (GskNglCompiler *self,
const char *name,
GError **error)
{
char version[32];
const char *debug = "";
const char *legacy = "";
const char *gl3 = "";
const char *gles = "";
int program_id;
int vertex_id;
int fragment_id;
int status;
g_return_val_if_fail (GSK_IS_NGL_COMPILER (self), NULL);
g_return_val_if_fail (self->all_preamble != NULL, NULL);
g_return_val_if_fail (self->fragment_preamble != NULL, NULL);
g_return_val_if_fail (self->vertex_preamble != NULL, NULL);
g_return_val_if_fail (self->fragment_source != NULL, NULL);
g_return_val_if_fail (self->vertex_source != NULL, NULL);
g_return_val_if_fail (self->driver != NULL, NULL);
gsk_ngl_command_queue_make_current (self->driver->command_queue);
g_snprintf (version, sizeof version, "#version %d\n", self->glsl_version);
if (self->debug_shaders)
debug = "#define GSK_DEBUG 1\n";
if (self->legacy)
legacy = "#define GSK_LEGACY 1\n";
if (self->gles)
gles = "#define GSK_NGLES 1\n";
if (self->gl3)
gl3 = "#define GSK_NGL3 1\n";
vertex_id = glCreateShader (GL_VERTEX_SHADER);
glShaderSource (vertex_id,
9,
(const char *[]) {
version, debug, legacy, gl3, gles,
get_shader_string (self->all_preamble),
get_shader_string (self->vertex_preamble),
get_shader_string (self->vertex_source),
get_shader_string (self->vertex_suffix),
},
(int[]) {
strlen (version),
strlen (debug),
strlen (legacy),
strlen (gl3),
strlen (gles),
g_bytes_get_size (self->all_preamble),
g_bytes_get_size (self->vertex_preamble),
g_bytes_get_size (self->vertex_source),
g_bytes_get_size (self->vertex_suffix),
});
glCompileShader (vertex_id);
if (!check_shader_error (vertex_id, error))
{
glDeleteShader (vertex_id);
return NULL;
}
print_shader_info ("Vertex shader", vertex_id, name);
fragment_id = glCreateShader (GL_FRAGMENT_SHADER);
glShaderSource (fragment_id,
9,
(const char *[]) {
version, debug, legacy, gl3, gles,
get_shader_string (self->all_preamble),
get_shader_string (self->fragment_preamble),
get_shader_string (self->fragment_source),
get_shader_string (self->fragment_suffix),
},
(int[]) {
strlen (version),
strlen (debug),
strlen (legacy),
strlen (gl3),
strlen (gles),
g_bytes_get_size (self->all_preamble),
g_bytes_get_size (self->fragment_preamble),
g_bytes_get_size (self->fragment_source),
g_bytes_get_size (self->fragment_suffix),
});
glCompileShader (fragment_id);
if (!check_shader_error (fragment_id, error))
{
glDeleteShader (vertex_id);
glDeleteShader (fragment_id);
return NULL;
}
print_shader_info ("Fragment shader", fragment_id, name);
program_id = glCreateProgram ();
glAttachShader (program_id, vertex_id);
glAttachShader (program_id, fragment_id);
for (guint i = 0; i < self->attrib_locations->len; i++)
{
const GskNglProgramAttrib *attrib;
attrib = &g_array_index (self->attrib_locations, GskNglProgramAttrib, i);
glBindAttribLocation (program_id, attrib->location, attrib->name);
}
glLinkProgram (program_id);
glGetProgramiv (program_id, GL_LINK_STATUS, &status);
glDetachShader (program_id, vertex_id);
glDeleteShader (vertex_id);
glDetachShader (program_id, fragment_id);
glDeleteShader (fragment_id);
if (status == GL_FALSE)
{
char *buffer = NULL;
int log_len = 0;
glGetProgramiv (program_id, GL_INFO_LOG_LENGTH, &log_len);
if (log_len > 0)
{
/* log_len includes NULL */
buffer = g_malloc0 (log_len);
glGetProgramInfoLog (program_id, log_len, NULL, buffer);
}
g_warning ("Linking failure in shader:\n%s",
buffer ? buffer : "");
g_set_error (error,
GDK_GL_ERROR,
GDK_GL_ERROR_LINK_FAILED,
"Linking failure in shader: %s",
buffer ? buffer : "");
g_free (buffer);
glDeleteProgram (program_id);
return NULL;
}
return gsk_ngl_program_new (self->driver, name, program_id);
}

View File

@ -0,0 +1,69 @@
/* gsknglcompilerprivate.h
*
* 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
*/
#ifndef __GSK_NGL_COMPILER_PRIVATE_H__
#define __GSK_NGL_COMPILER_PRIVATE_H__
#include "gskngltypesprivate.h"
G_BEGIN_DECLS
typedef enum _GskNglCompilerKind
{
GSK_NGL_COMPILER_ALL,
GSK_NGL_COMPILER_FRAGMENT,
GSK_NGL_COMPILER_VERTEX,
} GskNglCompilerKind;
#define GSK_TYPE_GL_COMPILER (gsk_ngl_compiler_get_type())
G_DECLARE_FINAL_TYPE (GskNglCompiler, gsk_ngl_compiler, GSK, NGL_COMPILER, GObject)
GskNglCompiler *gsk_ngl_compiler_new (GskNglDriver *driver,
gboolean debug);
void gsk_ngl_compiler_set_preamble (GskNglCompiler *self,
GskNglCompilerKind kind,
GBytes *preamble_bytes);
void gsk_ngl_compiler_set_preamble_from_resource (GskNglCompiler *self,
GskNglCompilerKind kind,
const char *resource_path);
void gsk_ngl_compiler_set_source (GskNglCompiler *self,
GskNglCompilerKind kind,
GBytes *source_bytes);
void gsk_ngl_compiler_set_source_from_resource (GskNglCompiler *self,
GskNglCompilerKind kind,
const char *resource_path);
void gsk_ngl_compiler_set_suffix (GskNglCompiler *self,
GskNglCompilerKind kind,
GBytes *suffix_bytes);
void gsk_ngl_compiler_set_suffix_from_resource (GskNglCompiler *self,
GskNglCompilerKind kind,
const char *resource_path);
void gsk_ngl_compiler_bind_attribute (GskNglCompiler *self,
const char *name,
guint location);
void gsk_ngl_compiler_clear_attributes (GskNglCompiler *self);
GskNglProgram *gsk_ngl_compiler_compile (GskNglCompiler *self,
const char *name,
GError **error);
G_END_DECLS
#endif /* __GSK_NGL_COMPILER_PRIVATE_H__ */

1296
gsk/ngl/gskngldriver.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,234 @@
/* gskngldriverprivate.h
*
* Copyright 2020 Christian Hergert <chergert@redhat.com>
*
* This file 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 file 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 General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __GSK_NGL_DRIVER_PRIVATE_H__
#define __GSK_NGL_DRIVER_PRIVATE_H__
#include <gdk/gdkgltextureprivate.h>
#include "gskngltypesprivate.h"
#include "gskngltexturepoolprivate.h"
G_BEGIN_DECLS
enum {
UNIFORM_SHARED_ALPHA,
UNIFORM_SHARED_SOURCE,
UNIFORM_SHARED_CLIP_RECT,
UNIFORM_SHARED_VIEWPORT,
UNIFORM_SHARED_PROJECTION,
UNIFORM_SHARED_MODELVIEW,
UNIFORM_SHARED_LAST
};
enum {
UNIFORM_CUSTOM_SIZE = UNIFORM_SHARED_LAST,
UNIFORM_CUSTOM_TEXTURE1,
UNIFORM_CUSTOM_TEXTURE2,
UNIFORM_CUSTOM_TEXTURE3,
UNIFORM_CUSTOM_TEXTURE4,
UNIFORM_CUSTOM_LAST
};
typedef struct {
gconstpointer pointer;
float scale_x;
float scale_y;
int filter;
int pointer_is_child;
graphene_rect_t parent_rect; /* Valid when pointer_is_child */
} GskTextureKey;
#define GSL_GK_NO_UNIFORMS UNIFORM_INVALID_##__COUNTER__
#define GSK_NGL_ADD_UNIFORM(pos, KEY, name) UNIFORM_##KEY = UNIFORM_SHARED_LAST + pos,
#define GSK_NGL_DEFINE_PROGRAM(name, resource, uniforms) enum { uniforms };
# include "gsknglprograms.defs"
#undef GSK_NGL_DEFINE_PROGRAM
#undef GSK_NGL_ADD_UNIFORM
#undef GSL_GK_NO_UNIFORMS
#define GSK_TYPE_NGL_DRIVER (gsk_ngl_driver_get_type())
G_DECLARE_FINAL_TYPE (GskNglDriver, gsk_ngl_driver, GSK, NGL_DRIVER, GObject)
struct _GskNglRenderTarget
{
guint framebuffer_id;
guint texture_id;
int min_filter;
int mag_filter;
int width;
int height;
};
struct _GskNglDriver
{
GObject parent_instance;
GskNglCommandQueue *shared_command_queue;
GskNglCommandQueue *command_queue;
GskNglTexturePool texture_pool;
GskNglGlyphLibrary *glyphs;
GskNglIconLibrary *icons;
GskNglShadowLibrary *shadows;
GHashTable *textures;
GHashTable *key_to_texture_id;
GHashTable *texture_id_to_key;
GPtrArray *atlases;
GHashTable *shader_cache;
GArray *autorelease_framebuffers;
GPtrArray *render_targets;
#define GSK_NGL_NO_UNIFORMS
#define GSK_NGL_ADD_UNIFORM(pos, KEY, name)
#define GSK_NGL_DEFINE_PROGRAM(name, resource, uniforms) GskNglProgram *name;
# include "gsknglprograms.defs"
#undef GSK_NGL_NO_UNIFORMS
#undef GSK_NGL_ADD_UNIFORM
#undef GSK_NGL_DEFINE_PROGRAM
gint64 current_frame_id;
/* Used to reduce number of comparisons */
guint stamps[UNIFORM_SHARED_LAST];
guint debug : 1;
guint in_frame : 1;
};
GskNglDriver *gsk_ngl_driver_from_shared_context (GdkGLContext *context,
gboolean debug_shaders,
GError **error);
GskNglCommandQueue *gsk_ngl_driver_create_command_queue (GskNglDriver *self,
GdkGLContext *context);
GdkGLContext *gsk_ngl_driver_get_context (GskNglDriver *self);
gboolean gsk_ngl_driver_create_render_target (GskNglDriver *self,
int width,
int height,
int min_filter,
int mag_filter,
GskNglRenderTarget **render_target);
guint gsk_ngl_driver_release_render_target (GskNglDriver *self,
GskNglRenderTarget *render_target,
gboolean release_texture);
void gsk_ngl_driver_begin_frame (GskNglDriver *self,
GskNglCommandQueue *command_queue);
void gsk_ngl_driver_end_frame (GskNglDriver *self);
void gsk_ngl_driver_after_frame (GskNglDriver *self);
GdkTexture *gsk_ngl_driver_create_gdk_texture (GskNglDriver *self,
guint texture_id);
void gsk_ngl_driver_cache_texture (GskNglDriver *self,
const GskTextureKey *key,
guint texture_id);
guint gsk_ngl_driver_load_texture (GskNglDriver *self,
GdkTexture *texture,
int min_filter,
int mag_filter);
GskNglTexture *gsk_ngl_driver_create_texture (GskNglDriver *self,
float width,
float height,
int min_filter,
int mag_filter);
void gsk_ngl_driver_release_texture (GskNglDriver *self,
GskNglTexture *texture);
void gsk_ngl_driver_release_texture_by_id (GskNglDriver *self,
guint texture_id);
GskNglTexture *gsk_ngl_driver_mark_texture_permanent (GskNglDriver *self,
guint texture_id);
void gsk_ngl_driver_add_texture_slices (GskNglDriver *self,
GdkTexture *texture,
GskNglTextureSlice **out_slices,
guint *out_n_slices);
GskNglProgram *gsk_ngl_driver_lookup_shader (GskNglDriver *self,
GskGLShader *shader,
GError **error);
GskNglTextureAtlas *gsk_ngl_driver_create_atlas (GskNglDriver *self);
#ifdef G_ENABLE_DEBUG
void gsk_ngl_driver_save_atlases_to_png (GskNglDriver *self,
const char *directory);
#endif
static inline GskNglTexture *
gsk_ngl_driver_get_texture_by_id (GskNglDriver *self,
guint texture_id)
{
return g_hash_table_lookup (self->textures, GUINT_TO_POINTER (texture_id));
}
/**
* gsk_ngl_driver_lookup_texture:
* @self: a #GskNglDriver
* @key: the key for the texture
*
* Looks up a texture in the texture cache by @key.
*
* If the texture could not be found, then zero is returned.
*
* Returns: a positive integer if the texture was found; otherwise 0.
*/
static inline guint
gsk_ngl_driver_lookup_texture (GskNglDriver *self,
const GskTextureKey *key)
{
gpointer id;
if (g_hash_table_lookup_extended (self->key_to_texture_id, key, NULL, &id))
{
GskNglTexture *texture = g_hash_table_lookup (self->textures, id);
if (texture != NULL)
texture->last_used_in_frame = self->current_frame_id;
return GPOINTER_TO_UINT (id);
}
return 0;
}
static inline void
gsk_ngl_driver_slice_texture (GskNglDriver *self,
GdkTexture *texture,
GskNglTextureSlice **out_slices,
guint *out_n_slices)
{
GskNglTexture *t;
if ((t = gdk_texture_get_render_data (texture, self)))
{
*out_slices = t->slices;
*out_n_slices = t->n_slices;
return;
}
gsk_ngl_driver_add_texture_slices (self, texture, out_slices, out_n_slices);
}
G_END_DECLS
#endif /* __GSK_NGL_DRIVER_PRIVATE_H__ */

View File

@ -0,0 +1,325 @@
/* gsknglglyphlibrary.c
*
* 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 <gdk/gdkglcontextprivate.h>
#include <gdk/gdkmemorytextureprivate.h>
#include <gdk/gdkprofilerprivate.h>
#include "gsknglcommandqueueprivate.h"
#include "gskngldriverprivate.h"
#include "gsknglglyphlibraryprivate.h"
#define MAX_GLYPH_SIZE 128
G_DEFINE_TYPE (GskNglGlyphLibrary, gsk_ngl_glyph_library, GSK_TYPE_GL_TEXTURE_LIBRARY)
GskNglGlyphLibrary *
gsk_ngl_glyph_library_new (GskNglDriver *driver)
{
g_return_val_if_fail (GSK_IS_NGL_DRIVER (driver), NULL);
return g_object_new (GSK_TYPE_GL_GLYPH_LIBRARY,
"driver", driver,
NULL);
}
static guint
gsk_ngl_glyph_key_hash (gconstpointer data)
{
const GskNglGlyphKey *key = data;
/* We do not store the hash within the key because GHashTable will already
* store the hash value for us and so this is called only a single time per
* cached item. This saves an extra 4 bytes per GskNglGlyphKey which means on
* 64-bit, we fit nicely within 2 pointers (the smallest allocation size
* for GSlice).
*/
return GPOINTER_TO_UINT (key->font) ^
key->glyph ^
(key->xshift << 24) ^
(key->yshift << 26) ^
key->scale;
}
static gboolean
gsk_ngl_glyph_key_equal (gconstpointer v1,
gconstpointer v2)
{
return memcmp (v1, v2, sizeof (GskNglGlyphKey)) == 0;
}
static void
gsk_ngl_glyph_key_free (gpointer data)
{
GskNglGlyphKey *key = data;
g_clear_object (&key->font);
g_slice_free (GskNglGlyphKey, key);
}
static void
gsk_ngl_glyph_value_free (gpointer data)
{
g_slice_free (GskNglGlyphValue, data);
}
static void
gsk_ngl_glyph_library_finalize (GObject *object)
{
GskNglGlyphLibrary *self = (GskNglGlyphLibrary *)object;
g_clear_pointer (&self->hash_table, g_hash_table_unref);
g_clear_pointer (&self->surface_data, g_free);
G_OBJECT_CLASS (gsk_ngl_glyph_library_parent_class)->finalize (object);
}
static void
gsk_ngl_glyph_library_class_init (GskNglGlyphLibraryClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gsk_ngl_glyph_library_finalize;
}
static void
gsk_ngl_glyph_library_init (GskNglGlyphLibrary *self)
{
GSK_NGL_TEXTURE_LIBRARY (self)->max_entry_size = MAX_GLYPH_SIZE;
gsk_ngl_texture_library_set_funcs (GSK_NGL_TEXTURE_LIBRARY (self),
gsk_ngl_glyph_key_hash,
gsk_ngl_glyph_key_equal,
gsk_ngl_glyph_key_free,
gsk_ngl_glyph_value_free);
}
static cairo_surface_t *
gsk_ngl_glyph_library_create_surface (GskNglGlyphLibrary *self,
int stride,
int width,
int height,
double device_scale)
{
cairo_surface_t *surface;
gsize n_bytes;
g_assert (GSK_IS_NGL_GLYPH_LIBRARY (self));
g_assert (width > 0);
g_assert (height > 0);
n_bytes = stride * height;
if G_LIKELY (n_bytes > self->surface_data_len)
{
self->surface_data = g_realloc (self->surface_data, n_bytes);
self->surface_data_len = n_bytes;
}
memset (self->surface_data, 0, n_bytes);
surface = cairo_image_surface_create_for_data (self->surface_data,
CAIRO_FORMAT_ARGB32,
width, height, stride);
cairo_surface_set_device_scale (surface, device_scale, device_scale);
return surface;
}
static void
render_glyph (cairo_surface_t *surface,
const cairo_scaled_font_t *scaled_font,
const GskNglGlyphKey *key,
const GskNglGlyphValue *value)
{
cairo_t *cr;
PangoGlyphString glyph_string;
PangoGlyphInfo glyph_info;
g_assert (surface != NULL);
g_assert (scaled_font != NULL);
cr = cairo_create (surface);
cairo_set_scaled_font (cr, scaled_font);
cairo_set_source_rgba (cr, 1, 1, 1, 1);
glyph_info.glyph = key->glyph;
glyph_info.geometry.width = value->ink_rect.width * 1024;
if (glyph_info.glyph & PANGO_GLYPH_UNKNOWN_FLAG)
glyph_info.geometry.x_offset = 250 * key->xshift;
else
glyph_info.geometry.x_offset = 250 * key->xshift - value->ink_rect.x * 1024;
glyph_info.geometry.y_offset = 250 * key->yshift - value->ink_rect.y * 1024;
glyph_string.num_glyphs = 1;
glyph_string.glyphs = &glyph_info;
pango_cairo_show_glyph_string (cr, key->font, &glyph_string);
cairo_destroy (cr);
cairo_surface_flush (surface);
}
static void
gsk_ngl_glyph_library_upload_glyph (GskNglGlyphLibrary *self,
const GskNglGlyphKey *key,
const GskNglGlyphValue *value,
int width,
int height,
double device_scale)
{
G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME;
cairo_scaled_font_t *scaled_font;
GskNglTextureAtlas *atlas;
cairo_surface_t *surface;
guchar *pixel_data;
guchar *free_data = NULL;
guint gl_format;
guint gl_type;
guint texture_id;
gsize stride;
int x, y;
g_assert (GSK_IS_NGL_GLYPH_LIBRARY (self));
g_assert (key != NULL);
g_assert (value != NULL);
scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont *)key->font);
if G_UNLIKELY (scaled_font == NULL ||
cairo_scaled_font_status (scaled_font) != CAIRO_STATUS_SUCCESS)
return;
stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, width);
atlas = value->entry.is_atlased ? value->entry.atlas : NULL;
gdk_gl_context_push_debug_group_printf (gdk_gl_context_get_current (),
"Uploading glyph %d",
key->glyph);
surface = gsk_ngl_glyph_library_create_surface (self, stride, width, height, device_scale);
render_glyph (surface, scaled_font, key, value);
texture_id = GSK_NGL_TEXTURE_ATLAS_ENTRY_TEXTURE (value);
g_assert (texture_id > 0);
glPixelStorei (GL_UNPACK_ROW_LENGTH, stride / 4);
glBindTexture (GL_TEXTURE_2D, texture_id);
if G_UNLIKELY (gdk_gl_context_get_use_es (gdk_gl_context_get_current ()))
{
pixel_data = free_data = g_malloc (width * height * 4);
gdk_memory_convert (pixel_data,
width * 4,
GDK_MEMORY_R8G8B8A8_PREMULTIPLIED,
cairo_image_surface_get_data (surface),
width * 4,
GDK_MEMORY_DEFAULT,
width, height);
gl_format = GL_RGBA;
gl_type = GL_UNSIGNED_BYTE;
}
else
{
pixel_data = cairo_image_surface_get_data (surface);
gl_format = GL_BGRA;
gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
}
if G_LIKELY (atlas != NULL)
{
x = atlas->width * value->entry.area.x;
y = atlas->width * value->entry.area.y;
}
else
{
x = 0;
y = 0;
}
glTexSubImage2D (GL_TEXTURE_2D, 0, x, y, width, height,
gl_format, gl_type, pixel_data);
glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
cairo_surface_destroy (surface);
g_free (free_data);
gdk_gl_context_pop_debug_group (gdk_gl_context_get_current ());
GSK_NGL_TEXTURE_LIBRARY (self)->driver->command_queue->n_uploads++;
if (gdk_profiler_is_running ())
{
char message[64];
g_snprintf (message, sizeof message, "Size %dx%d", width, height);
gdk_profiler_add_mark (start_time, GDK_PROFILER_CURRENT_TIME-start_time, "Upload Glyph", message);
}
}
gboolean
gsk_ngl_glyph_library_add (GskNglGlyphLibrary *self,
GskNglGlyphKey *key,
const GskNglGlyphValue **out_value)
{
PangoRectangle ink_rect;
GskNglGlyphValue *value;
int width;
int height;
guint packed_x;
guint packed_y;
g_assert (GSK_IS_NGL_GLYPH_LIBRARY (self));
g_assert (key != NULL);
g_assert (out_value != NULL);
pango_font_get_glyph_extents (key->font, key->glyph, &ink_rect, NULL);
pango_extents_to_pixels (&ink_rect, NULL);
if (key->xshift != 0)
ink_rect.width++;
if (key->yshift != 0)
ink_rect.height++;
width = ink_rect.width * key->scale / 1024;
height = ink_rect.height * key->scale / 1024;
value = gsk_ngl_texture_library_pack (GSK_NGL_TEXTURE_LIBRARY (self),
key,
sizeof *value,
width,
height,
0,
&packed_x, &packed_y);
memcpy (&value->ink_rect, &ink_rect, sizeof ink_rect);
if (key->scale > 0 && width > 0 && height > 0)
gsk_ngl_glyph_library_upload_glyph (self,
key,
value,
width,
height,
key->scale / 1024.0);
*out_value = value;
return GSK_NGL_TEXTURE_ATLAS_ENTRY_TEXTURE (value) != 0;
}

View File

@ -0,0 +1,119 @@
/* gsknglglyphlibraryprivate.h
*
* 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
*/
#ifndef __GSK_NGL_GLYPH_LIBRARY_PRIVATE_H__
#define __GSK_NGL_GLYPH_LIBRARY_PRIVATE_H__
#include <pango/pango.h>
#include "gskngltexturelibraryprivate.h"
G_BEGIN_DECLS
#define GSK_TYPE_GL_GLYPH_LIBRARY (gsk_ngl_glyph_library_get_type())
typedef struct _GskNglGlyphKey
{
PangoFont *font;
PangoGlyph glyph;
guint xshift : 3;
guint yshift : 3;
guint scale : 26; /* times 1024 */
} GskNglGlyphKey;
typedef struct _GskNglGlyphValue
{
GskNglTextureAtlasEntry entry;
PangoRectangle ink_rect;
} GskNglGlyphValue;
#if GLIB_SIZEOF_VOID_P == 8
G_STATIC_ASSERT (sizeof (GskNglGlyphKey) == 16);
#elif GLIB_SIZEOF_VOID_P == 4
G_STATIC_ASSERT (sizeof (GskNglGlyphKey) == 12);
#endif
G_DECLARE_FINAL_TYPE (GskNglGlyphLibrary, gsk_ngl_glyph_library, GSK, NGL_GLYPH_LIBRARY, GskNglTextureLibrary)
struct _GskNglGlyphLibrary
{
GskNglTextureLibrary parent_instance;
GHashTable *hash_table;
guint8 *surface_data;
gsize surface_data_len;
struct {
GskNglGlyphKey key;
const GskNglGlyphValue *value;
} front[256];
};
GskNglGlyphLibrary *gsk_ngl_glyph_library_new (GskNglDriver *driver);
gboolean gsk_ngl_glyph_library_add (GskNglGlyphLibrary *self,
GskNglGlyphKey *key,
const GskNglGlyphValue **out_value);
static inline int
gsk_ngl_glyph_key_phase (float value)
{
return floor (4 * (value + 0.125)) - 4 * floor (value + 0.125);
}
static inline void
gsk_ngl_glyph_key_set_glyph_and_shift (GskNglGlyphKey *key,
PangoGlyph glyph,
float x,
float y)
{
key->glyph = glyph;
key->xshift = gsk_ngl_glyph_key_phase (x);
key->yshift = gsk_ngl_glyph_key_phase (y);
}
static inline gboolean
gsk_ngl_glyph_library_lookup_or_add (GskNglGlyphLibrary *self,
const GskNglGlyphKey *key,
const GskNglGlyphValue **out_value)
{
GskNglTextureAtlasEntry *entry;
guint front_index = key->glyph & 0xFF;
if (memcmp (key, &self->front[front_index], sizeof *key) == 0)
{
*out_value = self->front[front_index].value;
}
else if (gsk_ngl_texture_library_lookup ((GskNglTextureLibrary *)self, key, &entry))
{
*out_value = (GskNglGlyphValue *)entry;
self->front[front_index].key = *key;
self->front[front_index].value = *out_value;
}
else
{
GskNglGlyphKey *k = g_slice_copy (sizeof *key, key);
g_object_ref (k->font);
gsk_ngl_glyph_library_add (self, k, out_value);
}
return GSK_NGL_TEXTURE_ATLAS_ENTRY_TEXTURE (*out_value) != 0;
}
G_END_DECLS
#endif /* __GSK_NGL_GLYPH_LIBRARY_PRIVATE_H__ */

213
gsk/ngl/gskngliconlibrary.c Normal file
View File

@ -0,0 +1,213 @@
/* gskngliconlibrary.c
*
* 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 <gdk/gdkglcontextprivate.h>
#include <gdk/gdkmemorytextureprivate.h>
#include <gdk/gdkprofilerprivate.h>
#include <gdk/gdktextureprivate.h>
#include "gsknglcommandqueueprivate.h"
#include "gskngldriverprivate.h"
#include "gskngliconlibraryprivate.h"
struct _GskNglIconLibrary
{
GskNglTextureLibrary parent_instance;
};
G_DEFINE_TYPE (GskNglIconLibrary, gsk_ngl_icon_library, GSK_TYPE_GL_TEXTURE_LIBRARY)
GskNglIconLibrary *
gsk_ngl_icon_library_new (GskNglDriver *driver)
{
g_return_val_if_fail (GSK_IS_NGL_DRIVER (driver), NULL);
return g_object_new (GSK_TYPE_GL_ICON_LIBRARY,
"driver", driver,
NULL);
}
static void
gsk_ngl_icon_data_free (gpointer data)
{
GskNglIconData *icon_data = data;
g_clear_object (&icon_data->source_texture);
g_slice_free (GskNglIconData, icon_data);
}
static void
gsk_ngl_icon_library_class_init (GskNglIconLibraryClass *klass)
{
}
static void
gsk_ngl_icon_library_init (GskNglIconLibrary *self)
{
GSK_NGL_TEXTURE_LIBRARY (self)->max_entry_size = 128;
gsk_ngl_texture_library_set_funcs (GSK_NGL_TEXTURE_LIBRARY (self),
NULL, NULL, NULL,
gsk_ngl_icon_data_free);
}
void
gsk_ngl_icon_library_add (GskNglIconLibrary *self,
GdkTexture *key,
const GskNglIconData **out_value)
{
G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME;
cairo_surface_t *surface;
GskNglIconData *icon_data;
guint8 *pixel_data;
guint8 *surface_data;
guint8 *free_data = NULL;
guint gl_format;
guint gl_type;
guint packed_x;
guint packed_y;
int width;
int height;
guint texture_id;
g_assert (GSK_IS_NGL_ICON_LIBRARY (self));
g_assert (GDK_IS_TEXTURE (key));
g_assert (out_value != NULL);
width = key->width;
height = key->height;
icon_data = gsk_ngl_texture_library_pack (GSK_NGL_TEXTURE_LIBRARY (self),
key,
sizeof (GskNglIconData),
width, height, 1,
&packed_x, &packed_y);
icon_data->source_texture = g_object_ref (key);
/* actually upload the texture */
surface = gdk_texture_download_surface (key);
surface_data = cairo_image_surface_get_data (surface);
gdk_gl_context_push_debug_group_printf (gdk_gl_context_get_current (),
"Uploading texture");
if (gdk_gl_context_get_use_es (gdk_gl_context_get_current ()))
{
pixel_data = free_data = g_malloc (width * height * 4);
gdk_memory_convert (pixel_data, width * 4,
GDK_MEMORY_R8G8B8A8_PREMULTIPLIED,
surface_data, cairo_image_surface_get_stride (surface),
GDK_MEMORY_DEFAULT, width, height);
gl_format = GL_RGBA;
gl_type = GL_UNSIGNED_BYTE;
}
else
{
pixel_data = surface_data;
gl_format = GL_BGRA;
gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
}
texture_id = GSK_NGL_TEXTURE_ATLAS_ENTRY_TEXTURE (icon_data);
glBindTexture (GL_TEXTURE_2D, texture_id);
glTexSubImage2D (GL_TEXTURE_2D, 0,
packed_x + 1, packed_y + 1,
width, height,
gl_format, gl_type,
pixel_data);
/* Padding top */
glTexSubImage2D (GL_TEXTURE_2D, 0,
packed_x + 1, packed_y,
width, 1,
gl_format, gl_type,
pixel_data);
/* Padding left */
glTexSubImage2D (GL_TEXTURE_2D, 0,
packed_x, packed_y + 1,
1, height,
gl_format, gl_type,
pixel_data);
/* Padding top left */
glTexSubImage2D (GL_TEXTURE_2D, 0,
packed_x, packed_y,
1, 1,
gl_format, gl_type,
pixel_data);
/* Padding right */
glPixelStorei (GL_UNPACK_ROW_LENGTH, width);
glPixelStorei (GL_UNPACK_SKIP_PIXELS, width - 1);
glTexSubImage2D (GL_TEXTURE_2D, 0,
packed_x + width + 1, packed_y + 1,
1, height,
gl_format, gl_type,
pixel_data);
/* Padding top right */
glTexSubImage2D (GL_TEXTURE_2D, 0,
packed_x + width + 1, packed_y,
1, 1,
gl_format, gl_type,
pixel_data);
/* Padding bottom */
glPixelStorei (GL_UNPACK_SKIP_PIXELS, 0);
glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
glPixelStorei (GL_UNPACK_SKIP_ROWS, height - 1);
glTexSubImage2D (GL_TEXTURE_2D, 0,
packed_x + 1, packed_y + 1 + height,
width, 1,
gl_format, gl_type,
pixel_data);
/* Padding bottom left */
glTexSubImage2D (GL_TEXTURE_2D, 0,
packed_x, packed_y + 1 + height,
1, 1,
gl_format, gl_type,
pixel_data);
/* Padding bottom right */
glPixelStorei (GL_UNPACK_ROW_LENGTH, width);
glPixelStorei (GL_UNPACK_SKIP_PIXELS, width - 1);
glTexSubImage2D (GL_TEXTURE_2D, 0,
packed_x + 1 + width, packed_y + 1 + height,
1, 1,
gl_format, gl_type,
pixel_data);
/* Reset this */
glPixelStorei (GL_UNPACK_SKIP_PIXELS, 0);
glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
glPixelStorei (GL_UNPACK_SKIP_ROWS, 0);
gdk_gl_context_pop_debug_group (gdk_gl_context_get_current ());
*out_value = icon_data;
cairo_surface_destroy (surface);
g_free (free_data);
GSK_NGL_TEXTURE_LIBRARY (self)->driver->command_queue->n_uploads++;
if (gdk_profiler_is_running ())
{
char message[64];
g_snprintf (message, sizeof message, "Size %dx%d", width, height);
gdk_profiler_add_mark (start_time, GDK_PROFILER_CURRENT_TIME-start_time, "Upload Icon", message);
}
}

View File

@ -0,0 +1,60 @@
/* gskngliconlibraryprivate.h
*
* 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
*/
#ifndef __GSK_NGL_ICON_LIBRARY_PRIVATE_H__
#define __GSK_NGL_ICON_LIBRARY_PRIVATE_H__
#include <pango/pango.h>
#include "gskngltexturelibraryprivate.h"
G_BEGIN_DECLS
#define GSK_TYPE_GL_ICON_LIBRARY (gsk_ngl_icon_library_get_type())
typedef struct _GskNglIconData
{
GskNglTextureAtlasEntry entry;
GdkTexture *source_texture;
} GskNglIconData;
G_DECLARE_FINAL_TYPE (GskNglIconLibrary, gsk_ngl_icon_library, GSK, NGL_ICON_LIBRARY, GskNglTextureLibrary)
GskNglIconLibrary *gsk_ngl_icon_library_new (GskNglDriver *driver);
void gsk_ngl_icon_library_add (GskNglIconLibrary *self,
GdkTexture *key,
const GskNglIconData **out_value);
static inline void
gsk_ngl_icon_library_lookup_or_add (GskNglIconLibrary *self,
GdkTexture *key,
const GskNglIconData **out_value)
{
GskNglTextureAtlasEntry *entry;
if G_LIKELY (gsk_ngl_texture_library_lookup ((GskNglTextureLibrary *)self, key, &entry))
*out_value = (GskNglIconData *)entry;
else
gsk_ngl_icon_library_add (self, key, out_value);
}
G_END_DECLS
#endif /* __GSK_NGL_ICON_LIBRARY_PRIVATE_H__ */

176
gsk/ngl/gsknglprogram.c Normal file
View File

@ -0,0 +1,176 @@
/* gsknglprogram.c
*
* 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 "gsknglcommandqueueprivate.h"
#include "gsknglprogramprivate.h"
#include "gskngluniformstateprivate.h"
G_DEFINE_TYPE (GskNglProgram, gsk_ngl_program, G_TYPE_OBJECT)
GskNglProgram *
gsk_ngl_program_new (GskNglDriver *driver,
const char *name,
int program_id)
{
GskNglProgram *self;
g_return_val_if_fail (GSK_IS_NGL_DRIVER (driver), NULL);
g_return_val_if_fail (program_id >= -1, NULL);
self = g_object_new (GSK_TYPE_GL_PROGRAM, NULL);
self->id = program_id;
self->name = g_strdup (name);
self->driver = g_object_ref (driver);
self->n_uniforms = 0;
return self;
}
static void
gsk_ngl_program_finalize (GObject *object)
{
GskNglProgram *self = (GskNglProgram *)object;
if (self->id >= 0)
g_warning ("Leaking GLSL program %d (%s)",
self->id,
self->name ? self->name : "");
g_clear_pointer (&self->name, g_free);
g_clear_object (&self->driver);
G_OBJECT_CLASS (gsk_ngl_program_parent_class)->finalize (object);
}
static void
gsk_ngl_program_class_init (GskNglProgramClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gsk_ngl_program_finalize;
}
static void
gsk_ngl_program_init (GskNglProgram *self)
{
self->id = -1;
for (guint i = 0; i < G_N_ELEMENTS (self->uniform_locations); i++)
self->uniform_locations[i] = -1;
}
/**
* gsk_ngl_program_add_uniform:
* @self: a #GskNglProgram
* @name: the name of the uniform such as "u_source"
* @key: the identifier to use for the uniform
*
* This method will create a mapping between @key and the location
* of the uniform on the GPU. This simplifies calling code to not
* need to know where the uniform location is and only register it
* when creating the program.
*
* You might use this with an enum of all your uniforms for the
* program and then register each of them like:
*
* ```
* gsk_ngl_program_add_uniform (program, "u_source", UNIFORM_SOURCE);
* ```
*
* That allows you to set values for the program with something
* like the following:
*
* ```
* gsk_ngl_program_set_uniform1i (program, UNIFORM_SOURCE, 1);
* ```
*
* Returns: %TRUE if the uniform was found; otherwise %FALSE
*/
gboolean
gsk_ngl_program_add_uniform (GskNglProgram *self,
const char *name,
guint key)
{
GLint location;
g_return_val_if_fail (GSK_IS_NGL_PROGRAM (self), FALSE);
g_return_val_if_fail (name != NULL, FALSE);
g_return_val_if_fail (key < 1024, FALSE);
if (-1 == (location = glGetUniformLocation (self->id, name)))
return FALSE;
self->uniform_locations[key] = location;
if (location >= self->n_uniforms)
self->n_uniforms = location + 1;
#if 0
g_print ("program [%d] %s uniform %s at location %d.\n",
self->id, self->name, name, location);
#endif
return TRUE;
}
/**
* gsk_ngl_program_delete:
* @self: a #GskNglProgram
*
* Deletes the GLSL program.
*
* You must call gsk_ngl_program_use() before and
* gsk_ngl_program_unuse() after this function.
*/
void
gsk_ngl_program_delete (GskNglProgram *self)
{
g_return_if_fail (GSK_IS_NGL_PROGRAM (self));
g_return_if_fail (self->driver->command_queue != NULL);
gsk_ngl_command_queue_delete_program (self->driver->command_queue, self->id);
self->id = -1;
}
/**
* gsk_ngl_program_uniforms_added:
* @self: a #GskNglProgram
* @has_attachments: if any uniform is for a bind/texture attachment
*
* This function should be called after all of the uniforms ahve
* been added with gsk_ngl_program_add_uniform().
*
* This function will setup the uniform state so that the program
* has fast access to the data buffers without as many lookups at
* runtime for comparison data.
*/
void
gsk_ngl_program_uniforms_added (GskNglProgram *self,
gboolean has_attachments)
{
g_return_if_fail (GSK_IS_NGL_PROGRAM (self));
g_return_if_fail (self->uniforms == NULL);
self->uniforms = self->driver->command_queue->uniforms;
self->program_info = gsk_ngl_uniform_state_get_program (self->uniforms, self->id, self->n_uniforms);
self->program_info->has_attachments = has_attachments;
}

View File

@ -0,0 +1,273 @@
/* gsknglprogramprivate.h
*
* 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
*/
#ifndef __GSK_NGL_PROGRAM_PRIVATE_H__
#define __GSK_NGL_PROGRAM_PRIVATE_H__
#include "gskngltypesprivate.h"
#include "gsknglcommandqueueprivate.h"
#include "gskngldriverprivate.h"
G_BEGIN_DECLS
#define GSK_TYPE_GL_PROGRAM (gsk_ngl_program_get_type())
G_DECLARE_FINAL_TYPE (GskNglProgram, gsk_ngl_program, GSK, NGL_PROGRAM, GObject)
struct _GskNglProgram
{
GObject parent_instance;
int id;
char *name;
GskNglDriver *driver;
/* In reality, this is the largest uniform position
* as returned after linking so that we can use direct
* indexes based on location.
*/
guint n_uniforms;
/* Cached pointer to avoid lots of pointer chasing/lookups */
GskNglUniformState *uniforms;
GskNglUniformProgram *program_info;
/* For custom programs */
int texture_locations[4];
int args_locations[8];
int size_location;
/* Static array for key->location transforms */
int uniform_locations[32];
};
GskNglProgram *gsk_ngl_program_new (GskNglDriver *driver,
const char *name,
int program_id);
gboolean gsk_ngl_program_add_uniform (GskNglProgram *self,
const char *name,
guint key);
void gsk_ngl_program_uniforms_added (GskNglProgram *self,
gboolean has_attachments);
void gsk_ngl_program_delete (GskNglProgram *self);
#define gsk_ngl_program_get_uniform_location(s,k) ((s)->uniform_locations[(k)])
static inline void
gsk_ngl_program_set_uniform1fv (GskNglProgram *self,
guint key,
guint stamp,
guint count,
const float *values)
{
gsk_ngl_uniform_state_set1fv (self->uniforms, self->program_info,
gsk_ngl_program_get_uniform_location (self, key),
stamp, count, values);
}
static inline void
gsk_ngl_program_set_uniform2fv (GskNglProgram *self,
guint key,
guint stamp,
guint count,
const float *values)
{
gsk_ngl_uniform_state_set2fv (self->uniforms, self->program_info,
gsk_ngl_program_get_uniform_location (self, key),
stamp, count, values);
}
static inline void
gsk_ngl_program_set_uniform4fv (GskNglProgram *self,
guint key,
guint stamp,
guint count,
const float *values)
{
gsk_ngl_uniform_state_set4fv (self->uniforms, self->program_info,
gsk_ngl_program_get_uniform_location (self, key),
stamp, count, values);
}
static inline void
gsk_ngl_program_set_uniform_rounded_rect (GskNglProgram *self,
guint key,
guint stamp,
const GskRoundedRect *rounded_rect)
{
gsk_ngl_uniform_state_set_rounded_rect (self->uniforms, self->program_info,
gsk_ngl_program_get_uniform_location (self, key),
stamp, rounded_rect);
}
static inline void
gsk_ngl_program_set_uniform1i (GskNglProgram *self,
guint key,
guint stamp,
int value0)
{
gsk_ngl_uniform_state_set1i (self->uniforms,
self->program_info,
gsk_ngl_program_get_uniform_location (self, key),
stamp, value0);
}
static inline void
gsk_ngl_program_set_uniform2i (GskNglProgram *self,
guint key,
guint stamp,
int value0,
int value1)
{
gsk_ngl_uniform_state_set2i (self->uniforms,
self->program_info,
gsk_ngl_program_get_uniform_location (self, key),
stamp, value0, value1);
}
static inline void
gsk_ngl_program_set_uniform3i (GskNglProgram *self,
guint key,
guint stamp,
int value0,
int value1,
int value2)
{
gsk_ngl_uniform_state_set3i (self->uniforms,
self->program_info,
gsk_ngl_program_get_uniform_location (self, key),
stamp, value0, value1, value2);
}
static inline void
gsk_ngl_program_set_uniform4i (GskNglProgram *self,
guint key,
guint stamp,
int value0,
int value1,
int value2,
int value3)
{
gsk_ngl_uniform_state_set4i (self->uniforms,
self->program_info,
gsk_ngl_program_get_uniform_location (self, key),
stamp, value0, value1, value2, value3);
}
static inline void
gsk_ngl_program_set_uniform1f (GskNglProgram *self,
guint key,
guint stamp,
float value0)
{
gsk_ngl_uniform_state_set1f (self->uniforms,
self->program_info,
gsk_ngl_program_get_uniform_location (self, key),
stamp, value0);
}
static inline void
gsk_ngl_program_set_uniform2f (GskNglProgram *self,
guint key,
guint stamp,
float value0,
float value1)
{
gsk_ngl_uniform_state_set2f (self->uniforms,
self->program_info,
gsk_ngl_program_get_uniform_location (self, key),
stamp, value0, value1);
}
static inline void
gsk_ngl_program_set_uniform3f (GskNglProgram *self,
guint key,
guint stamp,
float value0,
float value1,
float value2)
{
gsk_ngl_uniform_state_set3f (self->uniforms,
self->program_info,
gsk_ngl_program_get_uniform_location (self, key),
stamp, value0, value1, value2);
}
static inline void
gsk_ngl_program_set_uniform4f (GskNglProgram *self,
guint key,
guint stamp,
float value0,
float value1,
float value2,
float value3)
{
gsk_ngl_uniform_state_set4f (self->uniforms,
self->program_info,
gsk_ngl_program_get_uniform_location (self, key),
stamp, value0, value1, value2, value3);
}
static inline void
gsk_ngl_program_set_uniform_color (GskNglProgram *self,
guint key,
guint stamp,
const GdkRGBA *color)
{
gsk_ngl_uniform_state_set_color (self->uniforms,
self->program_info,
gsk_ngl_program_get_uniform_location (self, key),
stamp, color);
}
static inline void
gsk_ngl_program_set_uniform_texture (GskNglProgram *self,
guint key,
guint stamp,
GLenum texture_target,
GLenum texture_slot,
guint texture_id)
{
gsk_ngl_attachment_state_bind_texture (self->driver->command_queue->attachments,
texture_target,
texture_slot,
texture_id);
gsk_ngl_uniform_state_set_texture (self->uniforms,
self->program_info,
gsk_ngl_program_get_uniform_location (self, key),
stamp, texture_slot);
}
static inline void
gsk_ngl_program_set_uniform_matrix (GskNglProgram *self,
guint key,
guint stamp,
const graphene_matrix_t *matrix)
{
gsk_ngl_uniform_state_set_matrix (self->uniforms,
self->program_info,
gsk_ngl_program_get_uniform_location (self, key),
stamp, matrix);
}
G_END_DECLS
#endif /* __GSK_NGL_PROGRAM_PRIVATE_H__ */

View File

@ -0,0 +1,83 @@
GSK_NGL_DEFINE_PROGRAM (blend,
"/org/gtk/libgsk/glsl/blend.glsl",
GSK_NGL_ADD_UNIFORM (1, BLEND_SOURCE2, u_source2)
GSK_NGL_ADD_UNIFORM (2, BLEND_MODE, u_mode))
GSK_NGL_DEFINE_PROGRAM (blit,
"/org/gtk/libgsk/glsl/blit.glsl",
GSK_NGL_NO_UNIFORMS)
GSK_NGL_DEFINE_PROGRAM (blur,
"/org/gtk/libgsk/glsl/blur.glsl",
GSK_NGL_ADD_UNIFORM (1, BLUR_RADIUS, u_blur_radius)
GSK_NGL_ADD_UNIFORM (2, BLUR_SIZE, u_blur_size)
GSK_NGL_ADD_UNIFORM (3, BLUR_DIR, u_blur_dir))
GSK_NGL_DEFINE_PROGRAM (border,
"/org/gtk/libgsk/glsl/border.glsl",
GSK_NGL_ADD_UNIFORM (1, BORDER_COLOR, u_color)
GSK_NGL_ADD_UNIFORM (2, BORDER_WIDTHS, u_widths)
GSK_NGL_ADD_UNIFORM (3, BORDER_OUTLINE_RECT, u_outline_rect))
GSK_NGL_DEFINE_PROGRAM (color,
"/org/gtk/libgsk/glsl/color.glsl",
GSK_NGL_ADD_UNIFORM (1, COLOR_COLOR, u_color))
GSK_NGL_DEFINE_PROGRAM (coloring,
"/org/gtk/libgsk/glsl/coloring.glsl",
GSK_NGL_ADD_UNIFORM (1, COLORING_COLOR, u_color))
GSK_NGL_DEFINE_PROGRAM (color_matrix,
"/org/gtk/libgsk/glsl/color_matrix.glsl",
GSK_NGL_ADD_UNIFORM (1, COLOR_MATRIX_COLOR_MATRIX, u_color_matrix)
GSK_NGL_ADD_UNIFORM (2, COLOR_MATRIX_COLOR_OFFSET, u_color_offset))
GSK_NGL_DEFINE_PROGRAM (conic_gradient,
"/org/gtk/libgsk/glsl/conic_gradient.glsl",
GSK_NGL_ADD_UNIFORM (1, CONIC_GRADIENT_COLOR_STOPS, u_color_stops)
GSK_NGL_ADD_UNIFORM (2, CONIC_GRADIENT_NUM_COLOR_STOPS, u_num_color_stops)
GSK_NGL_ADD_UNIFORM (3, CONIC_GRADIENT_GEOMETRY, u_geometry))
GSK_NGL_DEFINE_PROGRAM (cross_fade,
"/org/gtk/libgsk/glsl/cross_fade.glsl",
GSK_NGL_ADD_UNIFORM (1, CROSS_FADE_PROGRESS, u_progress)
GSK_NGL_ADD_UNIFORM (2, CROSS_FADE_SOURCE2, u_source2))
GSK_NGL_DEFINE_PROGRAM (inset_shadow,
"/org/gtk/libgsk/glsl/inset_shadow.glsl",
GSK_NGL_ADD_UNIFORM (1, INSET_SHADOW_COLOR, u_color)
GSK_NGL_ADD_UNIFORM (2, INSET_SHADOW_SPREAD, u_spread)
GSK_NGL_ADD_UNIFORM (3, INSET_SHADOW_OFFSET, u_offset)
GSK_NGL_ADD_UNIFORM (4, INSET_SHADOW_OUTLINE_RECT, u_outline_rect))
GSK_NGL_DEFINE_PROGRAM (linear_gradient,
"/org/gtk/libgsk/glsl/linear_gradient.glsl",
GSK_NGL_ADD_UNIFORM (1, LINEAR_GRADIENT_COLOR_STOPS, u_color_stops)
GSK_NGL_ADD_UNIFORM (2, LINEAR_GRADIENT_NUM_COLOR_STOPS, u_num_color_stops)
GSK_NGL_ADD_UNIFORM (3, LINEAR_GRADIENT_POINTS, u_points)
GSK_NGL_ADD_UNIFORM (4, LINEAR_GRADIENT_REPEAT, u_repeat))
GSK_NGL_DEFINE_PROGRAM (outset_shadow,
"/org/gtk/libgsk/glsl/outset_shadow.glsl",
GSK_NGL_ADD_UNIFORM (1, OUTSET_SHADOW_COLOR, u_color)
GSK_NGL_ADD_UNIFORM (2, OUTSET_SHADOW_OUTLINE_RECT, u_outline_rect))
GSK_NGL_DEFINE_PROGRAM (radial_gradient,
"/org/gtk/libgsk/glsl/radial_gradient.glsl",
GSK_NGL_ADD_UNIFORM (1, RADIAL_GRADIENT_COLOR_STOPS, u_color_stops)
GSK_NGL_ADD_UNIFORM (2, RADIAL_GRADIENT_NUM_COLOR_STOPS, u_num_color_stops)
GSK_NGL_ADD_UNIFORM (3, RADIAL_GRADIENT_REPEAT, u_repeat)
GSK_NGL_ADD_UNIFORM (4, RADIAL_GRADIENT_RANGE, u_range)
GSK_NGL_ADD_UNIFORM (5, RADIAL_GRADIENT_GEOMETRY, u_geometry))
GSK_NGL_DEFINE_PROGRAM (repeat,
"/org/gtk/libgsk/glsl/repeat.glsl",
GSK_NGL_ADD_UNIFORM (1, REPEAT_CHILD_BOUNDS, u_child_bounds)
GSK_NGL_ADD_UNIFORM (2, REPEAT_TEXTURE_RECT, u_texture_rect))
GSK_NGL_DEFINE_PROGRAM (unblurred_outset_shadow,
"/org/gtk/libgsk/glsl/unblurred_outset_shadow.glsl",
GSK_NGL_ADD_UNIFORM (1, UNBLURRED_OUTSET_SHADOW_COLOR, u_color)
GSK_NGL_ADD_UNIFORM (2, UNBLURRED_OUTSET_SHADOW_SPREAD, u_spread)
GSK_NGL_ADD_UNIFORM (3, UNBLURRED_OUTSET_SHADOW_OFFSET, u_offset)
GSK_NGL_ADD_UNIFORM (4, UNBLURRED_OUTSET_SHADOW_OUTLINE_RECT, u_outline_rect))

312
gsk/ngl/gsknglrenderer.c Normal file
View File

@ -0,0 +1,312 @@
/* gsknglrenderer.c
*
* Copyright 2020 Christian Hergert <chergert@redhat.com>
*
* This file 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 file 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 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 <gdk/gdkprofilerprivate.h>
#include <gdk/gdksurfaceprivate.h>
#include <gsk/gskdebugprivate.h>
#include <gsk/gskrendererprivate.h>
#include "gsknglcommandqueueprivate.h"
#include "gskngldriverprivate.h"
#include "gsknglprogramprivate.h"
#include "gsknglrenderjobprivate.h"
#include "gsknglrendererprivate.h"
struct _GskNglRendererClass
{
GskRendererClass parent_class;
};
struct _GskNglRenderer
{
GskRenderer parent_instance;
/* This context is used to swap buffers when we are rendering directly
* to a GDK surface. It is also used to locate the shared driver for
* the display that we use to drive the command queue.
*/
GdkGLContext *context;
/* Our command queue is private to this renderer and talks to the GL
* context for our target surface. This ensure that framebuffer 0 matches
* the surface we care about. Since the context is shared with other
* contexts from other renderers on the display, texture atlases,
* programs, and other objects are available to them all.
*/
GskNglCommandQueue *command_queue;
/* The driver manages our program state and command queues. It also
* deals with caching textures, shaders, shadows, glyph, and icon
* caches through various helpers.
*/
GskNglDriver *driver;
};
G_DEFINE_TYPE (GskNglRenderer, gsk_ngl_renderer, GSK_TYPE_RENDERER)
GskRenderer *
gsk_ngl_renderer_new (void)
{
return g_object_new (GSK_TYPE_NGL_RENDERER, NULL);
}
static gboolean
gsk_ngl_renderer_realize (GskRenderer *renderer,
GdkSurface *surface,
GError **error)
{
G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME;
GskNglRenderer *self = (GskNglRenderer *)renderer;
GdkGLContext *context = NULL;
GdkGLContext *shared_context;
GskNglDriver *driver = NULL;
gboolean ret = FALSE;
gboolean debug_shaders = FALSE;
g_assert (GSK_IS_NGL_RENDERER (self));
g_assert (GDK_IS_SURFACE (surface));
if (self->context != NULL)
return TRUE;
g_assert (self->driver == NULL);
g_assert (self->context == NULL);
g_assert (self->command_queue == NULL);
if (!(context = gdk_surface_create_gl_context (surface, error)) ||
!gdk_gl_context_realize (context, error))
goto failure;
if (!(shared_context = gdk_surface_get_shared_data_gl_context (surface)))
{
g_set_error (error,
GDK_GL_ERROR,
GDK_GL_ERROR_NOT_AVAILABLE,
"Failed to locate shared GL context for driver");
goto failure;
}
#ifdef G_ENABLE_DEBUG
if (GSK_RENDERER_DEBUG_CHECK (GSK_RENDERER (self), SHADERS))
debug_shaders = TRUE;
#endif
if (!(driver = gsk_ngl_driver_from_shared_context (shared_context, debug_shaders, error)))
goto failure;
self->command_queue = gsk_ngl_driver_create_command_queue (driver, context);
self->context = g_steal_pointer (&context);
self->driver = g_steal_pointer (&driver);
gsk_ngl_command_queue_set_profiler (self->command_queue,
gsk_renderer_get_profiler (renderer));
ret = TRUE;
failure:
g_clear_object (&driver);
g_clear_object (&context);
gdk_profiler_end_mark (start_time, "GskNglRenderer realize", NULL);
return ret;
}
static void
gsk_ngl_renderer_unrealize (GskRenderer *renderer)
{
GskNglRenderer *self = (GskNglRenderer *)renderer;
g_assert (GSK_IS_NGL_RENDERER (renderer));
g_clear_object (&self->driver);
g_clear_object (&self->context);
g_clear_object (&self->command_queue);
}
static cairo_region_t *
get_render_region (GdkSurface *surface,
GdkGLContext *context)
{
const cairo_region_t *damage;
GdkRectangle whole_surface;
GdkRectangle extents;
g_assert (GDK_IS_SURFACE (surface));
g_assert (GDK_IS_GL_CONTEXT (context));
whole_surface.x = 0;
whole_surface.y = 0;
whole_surface.width = gdk_surface_get_width (surface);
whole_surface.height = gdk_surface_get_height (surface);
/* Damage does not have scale factor applied so we can compare it to
* @whole_surface which also doesn't have the scale factor applied.
*/
damage = gdk_draw_context_get_frame_region (GDK_DRAW_CONTEXT (context));
if (cairo_region_contains_rectangle (damage, &whole_surface) == CAIRO_REGION_OVERLAP_IN)
return NULL;
/* If the extents match the full-scene, do the same as above */
cairo_region_get_extents (damage, &extents);
if (gdk_rectangle_equal (&extents, &whole_surface))
return NULL;
/* Draw clipped to the bounding-box of the region. */
return cairo_region_create_rectangle (&extents);
}
static void
gsk_ngl_renderer_render (GskRenderer *renderer,
GskRenderNode *root,
const cairo_region_t *update_area)
{
GskNglRenderer *self = (GskNglRenderer *)renderer;
cairo_region_t *render_region;
graphene_rect_t viewport;
GskNglRenderJob *job;
GdkSurface *surface;
float scale_factor;
g_assert (GSK_IS_NGL_RENDERER (renderer));
g_assert (root != NULL);
surface = gdk_draw_context_get_surface (GDK_DRAW_CONTEXT (self->context));
scale_factor = gdk_surface_get_scale_factor (surface);
viewport.origin.x = 0;
viewport.origin.y = 0;
viewport.size.width = gdk_surface_get_width (surface) * scale_factor;
viewport.size.height = gdk_surface_get_height (surface) * scale_factor;
gdk_gl_context_make_current (self->context);
gdk_draw_context_begin_frame (GDK_DRAW_CONTEXT (self->context), update_area);
/* Must be called *AFTER* gdk_draw_context_begin_frame() */
render_region = get_render_region (surface, self->context);
gsk_ngl_driver_begin_frame (self->driver, self->command_queue);
job = gsk_ngl_render_job_new (self->driver, &viewport, scale_factor, render_region, 0);
#ifdef G_ENABLE_DEBUG
if (GSK_RENDERER_DEBUG_CHECK (GSK_RENDERER (self), FALLBACK))
gsk_ngl_render_job_set_debug_fallback (job, TRUE);
#endif
gsk_ngl_render_job_render (job, root);
gsk_ngl_driver_end_frame (self->driver);
gsk_ngl_render_job_free (job);
gdk_gl_context_make_current (self->context);
gdk_draw_context_end_frame (GDK_DRAW_CONTEXT (self->context));
gsk_ngl_driver_after_frame (self->driver);
cairo_region_destroy (render_region);
}
static GdkTexture *
gsk_ngl_renderer_render_texture (GskRenderer *renderer,
GskRenderNode *root,
const graphene_rect_t *viewport)
{
GskNglRenderer *self = (GskNglRenderer *)renderer;
GskNglRenderTarget *render_target;
GskNglRenderJob *job;
GdkTexture *texture = NULL;
guint texture_id;
int width;
int height;
g_assert (GSK_IS_NGL_RENDERER (renderer));
g_assert (root != NULL);
width = ceilf (viewport->size.width);
height = ceilf (viewport->size.height);
if (gsk_ngl_driver_create_render_target (self->driver,
width, height,
GL_NEAREST, GL_NEAREST,
&render_target))
{
gsk_ngl_driver_begin_frame (self->driver, self->command_queue);
job = gsk_ngl_render_job_new (self->driver, viewport, 1, NULL, render_target->framebuffer_id);
#ifdef G_ENABLE_DEBUG
if (GSK_RENDERER_DEBUG_CHECK (GSK_RENDERER (self), FALLBACK))
gsk_ngl_render_job_set_debug_fallback (job, TRUE);
#endif
gsk_ngl_render_job_render_flipped (job, root);
texture_id = gsk_ngl_driver_release_render_target (self->driver, render_target, FALSE);
texture = gsk_ngl_driver_create_gdk_texture (self->driver, texture_id);
gsk_ngl_driver_end_frame (self->driver);
gsk_ngl_render_job_free (job);
gsk_ngl_driver_after_frame (self->driver);
}
return g_steal_pointer (&texture);
}
static void
gsk_ngl_renderer_dispose (GObject *object)
{
#ifdef G_ENABLE_DEBUG
GskNglRenderer *self = (GskNglRenderer *)object;
g_assert (self->driver == NULL);
#endif
G_OBJECT_CLASS (gsk_ngl_renderer_parent_class)->dispose (object);
}
static void
gsk_ngl_renderer_class_init (GskNglRendererClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GskRendererClass *renderer_class = GSK_RENDERER_CLASS (klass);
object_class->dispose = gsk_ngl_renderer_dispose;
renderer_class->realize = gsk_ngl_renderer_realize;
renderer_class->unrealize = gsk_ngl_renderer_unrealize;
renderer_class->render = gsk_ngl_renderer_render;
renderer_class->render_texture = gsk_ngl_renderer_render_texture;
}
static void
gsk_ngl_renderer_init (GskNglRenderer *self)
{
}
gboolean
gsk_ngl_renderer_try_compile_gl_shader (GskNglRenderer *renderer,
GskGLShader *shader,
GError **error)
{
GskNglProgram *program;
g_return_val_if_fail (GSK_IS_NGL_RENDERER (renderer), FALSE);
g_return_val_if_fail (shader != NULL, FALSE);
program = gsk_ngl_driver_lookup_shader (renderer->driver, shader, error);
return program != NULL;
}

46
gsk/ngl/gsknglrenderer.h Normal file
View File

@ -0,0 +1,46 @@
/* gsknglrenderer.h
*
* 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
*/
#ifndef __GSK_NGL_RENDERER_H__
#define __GSK_NGL_RENDERER_H__
#include <gsk/gskrenderer.h>
G_BEGIN_DECLS
#define GSK_TYPE_NGL_RENDERER (gsk_ngl_renderer_get_type())
#define GSK_NGL_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSK_TYPE_NGL_RENDERER, GskNglRenderer))
#define GSK_IS_NGL_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSK_TYPE_NGL_RENDERER))
#define GSK_NGL_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_NGL_RENDERER, GskNglRendererClass))
#define GSK_IS_NGL_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_NGL_RENDERER))
#define GSK_NGL_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_NGL_RENDERER, GskNglRendererClass))
typedef struct _GskNglRenderer GskNglRenderer;
typedef struct _GskNglRendererClass GskNglRendererClass;
GDK_AVAILABLE_IN_ALL
GType gsk_ngl_renderer_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GskRenderer *gsk_ngl_renderer_new (void);
G_END_DECLS
#endif /* __GSK_NGL_RENDERER__ */

View File

@ -0,0 +1,34 @@
/* gsknglrendererprivate.h
*
* Copyright 2021 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
*/
#ifndef __GSK_NGL_RENDERER_PRIVATE_H__
#define __GSK_NGL_RENDERER_PRIVATE_H__
#include "gsknglrenderer.h"
G_BEGIN_DECLS
gboolean gsk_ngl_renderer_try_compile_gl_shader (GskNglRenderer *renderer,
GskGLShader *shader,
GError **error);
G_END_DECLS
#endif /* __GSK_NGL_RENDERER_PRIVATE_H__ */

3760
gsk/ngl/gsknglrenderjob.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,39 @@
/* gsknglrenderjobprivate.h
*
* 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
*/
#ifndef __GSK_NGL_RENDER_JOB_H__
#define __GSK_NGL_RENDER_JOB_H__
#include "gskngltypesprivate.h"
GskNglRenderJob *gsk_ngl_render_job_new (GskNglDriver *driver,
const graphene_rect_t *viewport,
float scale_factor,
const cairo_region_t *region,
guint framebuffer);
void gsk_ngl_render_job_free (GskNglRenderJob *job);
void gsk_ngl_render_job_render (GskNglRenderJob *job,
GskRenderNode *root);
void gsk_ngl_render_job_render_flipped (GskNglRenderJob *job,
GskRenderNode *root);
void gsk_ngl_render_job_set_debug_fallback (GskNglRenderJob *job,
gboolean debug_fallback);
#endif /* __GSK_NGL_RENDER_JOB_H__ */

View File

@ -0,0 +1,228 @@
/* gsknglshadowlibrary.c
*
* 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 <string.h>
#include "gskngldriverprivate.h"
#include "gsknglshadowlibraryprivate.h"
#define MAX_UNUSED_FRAMES (16 * 5)
struct _GskNglShadowLibrary
{
GObject parent_instance;
GskNglDriver *driver;
GArray *shadows;
};
typedef struct _Shadow
{
GskRoundedRect outline;
float blur_radius;
guint texture_id;
gint64 last_used_in_frame;
} Shadow;
G_DEFINE_TYPE (GskNglShadowLibrary, gsk_ngl_shadow_library, G_TYPE_OBJECT)
enum {
PROP_0,
PROP_DRIVER,
N_PROPS
};
static GParamSpec *properties [N_PROPS];
GskNglShadowLibrary *
gsk_ngl_shadow_library_new (GskNglDriver *driver)
{
g_return_val_if_fail (GSK_IS_NGL_DRIVER (driver), NULL);
return g_object_new (GSK_TYPE_GL_SHADOW_LIBRARY,
"driver", driver,
NULL);
}
static void
gsk_ngl_shadow_library_dispose (GObject *object)
{
GskNglShadowLibrary *self = (GskNglShadowLibrary *)object;
for (guint i = 0; i < self->shadows->len; i++)
{
const Shadow *shadow = &g_array_index (self->shadows, Shadow, i);
gsk_ngl_driver_release_texture_by_id (self->driver, shadow->texture_id);
}
g_clear_pointer (&self->shadows, g_array_unref);
g_clear_object (&self->driver);
G_OBJECT_CLASS (gsk_ngl_shadow_library_parent_class)->dispose (object);
}
static void
gsk_ngl_shadow_library_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GskNglShadowLibrary *self = GSK_NGL_SHADOW_LIBRARY (object);
switch (prop_id)
{
case PROP_DRIVER:
g_value_set_object (value, self->driver);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
gsk_ngl_shadow_library_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GskNglShadowLibrary *self = GSK_NGL_SHADOW_LIBRARY (object);
switch (prop_id)
{
case PROP_DRIVER:
self->driver = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
gsk_ngl_shadow_library_class_init (GskNglShadowLibraryClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gsk_ngl_shadow_library_dispose;
object_class->get_property = gsk_ngl_shadow_library_get_property;
object_class->set_property = gsk_ngl_shadow_library_set_property;
properties [PROP_DRIVER] =
g_param_spec_object ("driver",
"Driver",
"Driver",
GSK_TYPE_NGL_DRIVER,
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
gsk_ngl_shadow_library_init (GskNglShadowLibrary *self)
{
self->shadows = g_array_new (FALSE, FALSE, sizeof (Shadow));
}
void
gsk_ngl_shadow_library_insert (GskNglShadowLibrary *self,
const GskRoundedRect *outline,
float blur_radius,
guint texture_id)
{
Shadow *shadow;
g_assert (GSK_IS_NGL_SHADOW_LIBRARY (self));
g_assert (outline != NULL);
g_assert (texture_id != 0);
gsk_ngl_driver_mark_texture_permanent (self->driver, texture_id);
g_array_set_size (self->shadows, self->shadows->len + 1);
shadow = &g_array_index (self->shadows, Shadow, self->shadows->len - 1);
shadow->outline = *outline;
shadow->blur_radius = blur_radius;
shadow->texture_id = texture_id;
shadow->last_used_in_frame = self->driver->current_frame_id;
}
guint
gsk_ngl_shadow_library_lookup (GskNglShadowLibrary *self,
const GskRoundedRect *outline,
float blur_radius)
{
Shadow *ret = NULL;
g_assert (GSK_IS_NGL_SHADOW_LIBRARY (self));
g_assert (outline != NULL);
/* Ensure GskRoundedRect is 12 packed floats without padding
* so that we can use memcmp instead of float comparisons.
*/
G_STATIC_ASSERT (sizeof *outline == (sizeof (float) * 12));
for (guint i = 0; i < self->shadows->len; i++)
{
Shadow *shadow = &g_array_index (self->shadows, Shadow, i);
if (blur_radius == shadow->blur_radius &&
memcmp (outline, &shadow->outline, sizeof *outline) == 0)
{
ret = shadow;
break;
}
}
if (ret == NULL)
return 0;
g_assert (ret->texture_id != 0);
ret->last_used_in_frame = self->driver->current_frame_id;
return ret->texture_id;
}
void
gsk_ngl_shadow_library_begin_frame (GskNglShadowLibrary *self)
{
gint64 watermark;
int i;
int p;
g_return_if_fail (GSK_IS_NGL_SHADOW_LIBRARY (self));
watermark = self->driver->current_frame_id - MAX_UNUSED_FRAMES;
for (i = 0, p = self->shadows->len; i < p; i++)
{
const Shadow *shadow = &g_array_index (self->shadows, Shadow, i);
if (shadow->last_used_in_frame < watermark)
{
gsk_ngl_driver_release_texture_by_id (self->driver, shadow->texture_id);
g_array_remove_index_fast (self->shadows, i);
p--;
i--;
}
}
}

View File

@ -0,0 +1,44 @@
/* gsknglshadowlibraryprivate.h
*
* 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
*/
#ifndef __GSK_NGL_SHADOW_LIBRARY_PRIVATE_H__
#define __GSK_NGL_SHADOW_LIBRARY_PRIVATE_H__
#include "gskngltexturelibraryprivate.h"
G_BEGIN_DECLS
#define GSK_TYPE_GL_SHADOW_LIBRARY (gsk_ngl_shadow_library_get_type())
G_DECLARE_FINAL_TYPE (GskNglShadowLibrary, gsk_ngl_shadow_library, GSK, NGL_SHADOW_LIBRARY, GObject)
GskNglShadowLibrary *gsk_ngl_shadow_library_new (GskNglDriver *driver);
void gsk_ngl_shadow_library_begin_frame (GskNglShadowLibrary *self);
guint gsk_ngl_shadow_library_lookup (GskNglShadowLibrary *self,
const GskRoundedRect *outline,
float blur_radius);
void gsk_ngl_shadow_library_insert (GskNglShadowLibrary *self,
const GskRoundedRect *outline,
float blur_radius,
guint texture_id);
G_END_DECLS
#endif /* __GSK_NGL_SHADOW_LIBRARY_PRIVATE_H__ */

View File

@ -0,0 +1,315 @@
/* gskngltexturelibrary.c
*
* 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 "gsknglcommandqueueprivate.h"
#include "gskngldriverprivate.h"
#include "gskngltexturelibraryprivate.h"
G_DEFINE_ABSTRACT_TYPE (GskNglTextureLibrary, gsk_ngl_texture_library, G_TYPE_OBJECT)
enum {
PROP_0,
PROP_DRIVER,
N_PROPS
};
static GParamSpec *properties [N_PROPS];
static void
gsk_ngl_texture_library_constructed (GObject *object)
{
G_OBJECT_CLASS (gsk_ngl_texture_library_parent_class)->constructed (object);
g_assert (GSK_NGL_TEXTURE_LIBRARY (object)->hash_table != NULL);
}
static void
gsk_ngl_texture_library_dispose (GObject *object)
{
GskNglTextureLibrary *self = (GskNglTextureLibrary *)object;
g_clear_object (&self->driver);
G_OBJECT_CLASS (gsk_ngl_texture_library_parent_class)->dispose (object);
}
static void
gsk_ngl_texture_library_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GskNglTextureLibrary *self = GSK_NGL_TEXTURE_LIBRARY (object);
switch (prop_id)
{
case PROP_DRIVER:
g_value_set_object (value, self->driver);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
gsk_ngl_texture_library_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GskNglTextureLibrary *self = GSK_NGL_TEXTURE_LIBRARY (object);
switch (prop_id)
{
case PROP_DRIVER:
self->driver = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
gsk_ngl_texture_library_class_init (GskNglTextureLibraryClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->constructed = gsk_ngl_texture_library_constructed;
object_class->dispose = gsk_ngl_texture_library_dispose;
object_class->get_property = gsk_ngl_texture_library_get_property;
object_class->set_property = gsk_ngl_texture_library_set_property;
properties [PROP_DRIVER] =
g_param_spec_object ("driver",
"Driver",
"Driver",
GSK_TYPE_NGL_DRIVER,
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
gsk_ngl_texture_library_init (GskNglTextureLibrary *self)
{
}
void
gsk_ngl_texture_library_set_funcs (GskNglTextureLibrary *self,
GHashFunc hash_func,
GEqualFunc equal_func,
GDestroyNotify key_destroy,
GDestroyNotify value_destroy)
{
g_return_if_fail (GSK_IS_NGL_TEXTURE_LIBRARY (self));
g_return_if_fail (self->hash_table == NULL);
self->hash_table = g_hash_table_new_full (hash_func, equal_func,
key_destroy, value_destroy);
}
void
gsk_ngl_texture_library_begin_frame (GskNglTextureLibrary *self)
{
g_return_if_fail (GSK_IS_NGL_TEXTURE_LIBRARY (self));
if (GSK_NGL_TEXTURE_LIBRARY_GET_CLASS (self)->begin_frame)
GSK_NGL_TEXTURE_LIBRARY_GET_CLASS (self)->begin_frame (self);
}
void
gsk_ngl_texture_library_end_frame (GskNglTextureLibrary *self)
{
g_return_if_fail (GSK_IS_NGL_TEXTURE_LIBRARY (self));
if (GSK_NGL_TEXTURE_LIBRARY_GET_CLASS (self)->end_frame)
GSK_NGL_TEXTURE_LIBRARY_GET_CLASS (self)->end_frame (self);
}
static GskNglTexture *
gsk_ngl_texture_library_pack_one (GskNglTextureLibrary *self,
guint width,
guint height)
{
GskNglTexture *texture;
g_assert (GSK_IS_NGL_TEXTURE_LIBRARY (self));
if (width > self->driver->command_queue->max_texture_size ||
height > self->driver->command_queue->max_texture_size)
{
g_warning ("Clipping requested texture of size %ux%u to maximum allowable size %u.",
width, height, self->driver->command_queue->max_texture_size);
width = MIN (width, self->driver->command_queue->max_texture_size);
height = MIN (height, self->driver->command_queue->max_texture_size);
}
texture = gsk_ngl_driver_create_texture (self->driver, width, height, GL_LINEAR, GL_LINEAR);
texture->permanent = TRUE;
return texture;
}
static inline gboolean
gsk_ngl_texture_atlas_pack (GskNglTextureAtlas *self,
int width,
int height,
int *out_x,
int *out_y)
{
stbrp_rect rect;
rect.w = width;
rect.h = height;
stbrp_pack_rects (&self->context, &rect, 1);
if (rect.was_packed)
{
*out_x = rect.x;
*out_y = rect.y;
}
return rect.was_packed;
}
static void
gsk_ngl_texture_atlases_pack (GskNglDriver *driver,
int width,
int height,
GskNglTextureAtlas **out_atlas,
int *out_x,
int *out_y)
{
GskNglTextureAtlas *atlas = NULL;
int x, y;
for (guint i = 0; i < driver->atlases->len; i++)
{
atlas = g_ptr_array_index (driver->atlases, i);
if (gsk_ngl_texture_atlas_pack (atlas, width, height, &x, &y))
break;
atlas = NULL;
}
if (atlas == NULL)
{
/* No atlas has enough space, so create a new one... */
atlas = gsk_ngl_driver_create_atlas (driver);
/* Pack it onto that one, which surely has enough space... */
if (!gsk_ngl_texture_atlas_pack (atlas, width, height, &x, &y))
g_assert_not_reached ();
}
*out_atlas = atlas;
*out_x = x;
*out_y = y;
}
gpointer
gsk_ngl_texture_library_pack (GskNglTextureLibrary *self,
gpointer key,
gsize valuelen,
guint width,
guint height,
int padding,
guint *out_packed_x,
guint *out_packed_y)
{
GskNglTextureAtlasEntry *entry;
GskNglTextureAtlas *atlas = NULL;
g_assert (GSK_IS_NGL_TEXTURE_LIBRARY (self));
g_assert (key != NULL);
g_assert (valuelen > sizeof (GskNglTextureAtlasEntry));
g_assert (out_packed_x != NULL);
g_assert (out_packed_y != NULL);
entry = g_slice_alloc0 (valuelen);
entry->n_pixels = width * height;
entry->accessed = TRUE;
/* If our size is invisible then we just want an entry in the
* cache for faster lookups, but do not actually spend any texture
* allocations on this entry.
*/
if (width <= 0 && height <= 0)
{
entry->is_atlased = FALSE;
entry->texture = NULL;
entry->area.x = 0.0f;
entry->area.y = 0.0f;
entry->area.x2 = 0.0f;
entry->area.y2 = 0.0f;
*out_packed_x = 0;
*out_packed_y = 0;
}
else if (width <= self->max_entry_size && height <= self->max_entry_size)
{
int packed_x;
int packed_y;
gsk_ngl_texture_atlases_pack (self->driver,
padding + width + padding,
padding + height + padding,
&atlas,
&packed_x,
&packed_y);
entry->atlas = atlas;
entry->is_atlased = TRUE;
entry->area.x = (float)(packed_x + padding) / atlas->width;
entry->area.y = (float)(packed_y + padding) / atlas->height;
entry->area.x2 = entry->area.x + (float)width / atlas->width;
entry->area.y2 = entry->area.y + (float)height / atlas->height;
*out_packed_x = packed_x;
*out_packed_y = packed_y;
}
else
{
GskNglTexture *texture = gsk_ngl_texture_library_pack_one (self,
padding + width + padding,
padding + height + padding);
entry->texture = texture;
entry->is_atlased = FALSE;
entry->accessed = TRUE;
entry->area.x = 0.0f;
entry->area.y = 0.0f;
entry->area.x2 = 1.0f;
entry->area.y2 = 1.0f;
*out_packed_x = padding;
*out_packed_y = padding;
}
g_hash_table_insert (self->hash_table, key, entry);
return entry;
}

View File

@ -0,0 +1,202 @@
/* gskngltexturelibraryprivate.h
*
* Copyright 2020 Christian Hergert <chergert@redhat.com>
*
* This file 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 file 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 General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __GSK_NGL_TEXTURE_LIBRARY_PRIVATE_H__
#define __GSK_NGL_TEXTURE_LIBRARY_PRIVATE_H__
#include "gskngltypesprivate.h"
#include "gskngltexturepoolprivate.h"
#include "../gl/stb_rect_pack.h"
G_BEGIN_DECLS
#define GSK_TYPE_GL_TEXTURE_LIBRARY (gsk_ngl_texture_library_get_type ())
#define GSK_NGL_TEXTURE_LIBRARY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSK_TYPE_GL_TEXTURE_LIBRARY, GskNglTextureLibrary))
#define GSK_IS_NGL_TEXTURE_LIBRARY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSK_TYPE_GL_TEXTURE_LIBRARY))
#define GSK_NGL_TEXTURE_LIBRARY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_GL_TEXTURE_LIBRARY, GskNglTextureLibraryClass))
#define GSK_IS_NGL_TEXTURE_LIBRARY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_GL_TEXTURE_LIBRARY))
#define GSK_NGL_TEXTURE_LIBRARY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_GL_TEXTURE_LIBRARY, GskNglTextureLibraryClass))
typedef struct _GskNglTextureAtlas
{
struct stbrp_context context;
struct stbrp_node *nodes;
int width;
int height;
guint texture_id;
/* Pixels of rects that have been used at some point,
* But are now unused.
*/
int unused_pixels;
void *user_data;
} GskNglTextureAtlas;
typedef struct _GskNglTextureAtlasEntry
{
/* A backreference to either the atlas or texture containing
* the contents of the atlas entry. For larger items, no atlas
* is used and instead a direct texture.
*/
union {
GskNglTextureAtlas *atlas;
GskNglTexture *texture;
};
/* The area within the atlas translated to 0..1 bounds */
struct {
float x;
float y;
float x2;
float y2;
} area;
/* Number of pixels in the entry, used to calculate usage
* of an atlas while processing.
*/
guint n_pixels : 29;
/* If entry has marked pixels as used in the atlas this frame */
guint used : 1;
/* If entry was accessed this frame */
guint accessed : 1;
/* When true, backref is an atlas, otherwise texture */
guint is_atlased : 1;
/* Suffix data that is per-library specific. gpointer used to
* guarantee the alignment for the entries using this.
*/
gpointer data[0];
} GskNglTextureAtlasEntry;
typedef struct _GskNglTextureLibrary
{
GObject parent_instance;
GskNglDriver *driver;
GHashTable *hash_table;
guint max_entry_size;
} GskNglTextureLibrary;
typedef struct _GskNglTextureLibraryClass
{
GObjectClass parent_class;
void (*begin_frame) (GskNglTextureLibrary *library);
void (*end_frame) (GskNglTextureLibrary *library);
} GskNglTextureLibraryClass;
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GskNglTextureLibrary, g_object_unref)
GType gsk_ngl_texture_library_get_type (void) G_GNUC_CONST;
void gsk_ngl_texture_library_set_funcs (GskNglTextureLibrary *self,
GHashFunc hash_func,
GEqualFunc equal_func,
GDestroyNotify key_destroy,
GDestroyNotify value_destroy);
void gsk_ngl_texture_library_begin_frame (GskNglTextureLibrary *self);
void gsk_ngl_texture_library_end_frame (GskNglTextureLibrary *self);
gpointer gsk_ngl_texture_library_pack (GskNglTextureLibrary *self,
gpointer key,
gsize valuelen,
guint width,
guint height,
int padding,
guint *out_packed_x,
guint *out_packed_y);
static inline void
gsk_ngl_texture_atlas_mark_unused (GskNglTextureAtlas *self,
int n_pixels)
{
self->unused_pixels += n_pixels;
}
static inline void
gsk_ngl_texture_atlas_mark_used (GskNglTextureAtlas *self,
int n_pixels)
{
self->unused_pixels -= n_pixels;
}
static inline gboolean
gsk_ngl_texture_library_lookup (GskNglTextureLibrary *self,
gconstpointer key,
GskNglTextureAtlasEntry **out_entry)
{
GskNglTextureAtlasEntry *entry = g_hash_table_lookup (self->hash_table, key);
if G_LIKELY (entry != NULL && entry->accessed && entry->used)
{
*out_entry = entry;
return TRUE;
}
if (entry != NULL)
{
if (!entry->used && entry->is_atlased)
{
g_assert (entry->atlas != NULL);
gsk_ngl_texture_atlas_mark_used (entry->atlas, entry->n_pixels);
entry->used = TRUE;
}
entry->accessed = TRUE;
*out_entry = entry;
return TRUE;
}
return FALSE;
}
static inline guint
GSK_NGL_TEXTURE_ATLAS_ENTRY_TEXTURE (gconstpointer d)
{
const GskNglTextureAtlasEntry *e = d;
return e->is_atlased ? e->atlas->texture_id
: e->texture ? e->texture->texture_id : 0;
}
static inline double
gsk_ngl_texture_atlas_get_unused_ratio (const GskNglTextureAtlas *self)
{
if (self->unused_pixels > 0)
return (double)(self->unused_pixels) / (double)(self->width * self->height);
return 0.0;
}
static inline gboolean
gsk_ngl_texture_library_can_cache (GskNglTextureLibrary *self,
int width,
int height)
{
g_assert (self->max_entry_size > 0);
return width <= self->max_entry_size && height <= self->max_entry_size;
}
G_END_DECLS
#endif /* __GSK_NGL_TEXTURE_LIBRARY_PRIVATE_H__ */

188
gsk/ngl/gskngltexturepool.c Normal file
View File

@ -0,0 +1,188 @@
/* gskngltexturepool.c
*
* Copyright 2020 Christian Hergert <chergert@redhat.com>
*
* This file 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 file 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 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 <gdk/gdktextureprivate.h>
#include "gskngltexturepoolprivate.h"
#include "ninesliceprivate.h"
void
gsk_ngl_texture_free (GskNglTexture *texture)
{
if (texture != NULL)
{
g_assert (texture->link.prev == NULL);
g_assert (texture->link.next == NULL);
if (texture->user)
g_clear_pointer (&texture->user, gdk_texture_clear_render_data);
if (texture->texture_id != 0)
{
glDeleteTextures (1, &texture->texture_id);
texture->texture_id = 0;
}
for (guint i = 0; i < texture->n_slices; i++)
{
glDeleteTextures (1, &texture->slices[i].texture_id);
texture->slices[i].texture_id = 0;
}
g_clear_pointer (&texture->slices, g_free);
g_clear_pointer (&texture->nine_slice, g_free);
g_slice_free (GskNglTexture, texture);
}
}
void
gsk_ngl_texture_pool_init (GskNglTexturePool *self)
{
g_queue_init (&self->queue);
}
void
gsk_ngl_texture_pool_clear (GskNglTexturePool *self)
{
guint *free_me = NULL;
guint *texture_ids;
guint i = 0;
if G_LIKELY (self->queue.length <= 1024)
texture_ids = g_newa (guint, self->queue.length);
else
texture_ids = free_me = g_new (guint, self->queue.length);
while (self->queue.length > 0)
{
GskNglTexture *head = g_queue_peek_head (&self->queue);
g_queue_unlink (&self->queue, &head->link);
texture_ids[i++] = head->texture_id;
head->texture_id = 0;
gsk_ngl_texture_free (head);
}
g_assert (self->queue.length == 0);
if (i > 0)
glDeleteTextures (i, texture_ids);
g_free (free_me);
}
void
gsk_ngl_texture_pool_put (GskNglTexturePool *self,
GskNglTexture *texture)
{
g_assert (self != NULL);
g_assert (texture != NULL);
g_assert (texture->user == NULL);
g_assert (texture->link.prev == NULL);
g_assert (texture->link.next == NULL);
g_assert (texture->link.data == texture);
if (texture->permanent)
gsk_ngl_texture_free (texture);
else
g_queue_push_tail_link (&self->queue, &texture->link);
}
GskNglTexture *
gsk_ngl_texture_pool_get (GskNglTexturePool *self,
int width,
int height,
int min_filter,
int mag_filter)
{
GskNglTexture *texture;
g_assert (self != NULL);
texture = g_slice_new0 (GskNglTexture);
texture->link.data = texture;
texture->min_filter = min_filter;
texture->mag_filter = mag_filter;
glGenTextures (1, &texture->texture_id);
glActiveTexture (GL_TEXTURE0);
glBindTexture (GL_TEXTURE_2D, texture->texture_id);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
if (gdk_gl_context_get_use_es (gdk_gl_context_get_current ()))
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
else
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
glBindTexture (GL_TEXTURE_2D, 0);
return texture;
}
GskNglTexture *
gsk_ngl_texture_new (guint texture_id,
int width,
int height,
int min_filter,
int mag_filter,
gint64 frame_id)
{
GskNglTexture *texture;
texture = g_slice_new0 (GskNglTexture);
texture->texture_id = texture_id;
texture->link.data = texture;
texture->min_filter = min_filter;
texture->mag_filter = mag_filter;
texture->width = width;
texture->height = height;
texture->last_used_in_frame = frame_id;
return texture;
}
const GskNglTextureNineSlice *
gsk_ngl_texture_get_nine_slice (GskNglTexture *texture,
const GskRoundedRect *outline,
float extra_pixels)
{
g_assert (texture != NULL);
g_assert (outline != NULL);
if G_UNLIKELY (texture->nine_slice == NULL)
{
texture->nine_slice = g_new0 (GskNglTextureNineSlice, 9);
nine_slice_rounded_rect (texture->nine_slice, outline);
nine_slice_grow (texture->nine_slice, extra_pixels);
nine_slice_to_texture_coords (texture->nine_slice, texture->width, texture->height);
}
return texture->nine_slice;
}

View File

@ -0,0 +1,102 @@
/* gskngltexturepoolprivate.h
*
* Copyright 2020 Christian Hergert <chergert@redhat.com>
*
* This file 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 file 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 General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef _GSK_NGL_TEXTURE_POOL_PRIVATE_H__
#define _GSK_NGL_TEXTURE_POOL_PRIVATE_H__
#include "gskngltypesprivate.h"
G_BEGIN_DECLS
typedef struct _GskNglTexturePool
{
GQueue queue;
} GskNglTexturePool;
struct _GskNglTextureSlice
{
cairo_rectangle_int_t rect;
guint texture_id;
};
struct _GskNglTextureNineSlice
{
cairo_rectangle_int_t rect;
struct {
float x;
float y;
float x2;
float y2;
} area;
};
struct _GskNglTexture
{
/* Used to insert into queue */
GList link;
/* Identifier of the frame that created it */
gint64 last_used_in_frame;
/* Backpointer to texture (can be cleared asynchronously) */
GdkTexture *user;
/* Only used by sliced textures */
GskNglTextureSlice *slices;
guint n_slices;
/* Only used by nine-slice textures */
GskNglTextureNineSlice *nine_slice;
/* The actual GL texture identifier in some shared context */
guint texture_id;
int width;
int height;
int min_filter;
int mag_filter;
/* Set when used by an atlas so we don't drop the texture */
guint permanent : 1;
};
void gsk_ngl_texture_pool_init (GskNglTexturePool *self);
void gsk_ngl_texture_pool_clear (GskNglTexturePool *self);
GskNglTexture *gsk_ngl_texture_pool_get (GskNglTexturePool *self,
int width,
int height,
int min_filter,
int mag_filter);
void gsk_ngl_texture_pool_put (GskNglTexturePool *self,
GskNglTexture *texture);
GskNglTexture *gsk_ngl_texture_new (guint texture_id,
int width,
int height,
int min_filter,
int mag_filter,
gint64 frame_id);
const GskNglTextureNineSlice *gsk_ngl_texture_get_nine_slice (GskNglTexture *texture,
const GskRoundedRect *outline,
float extra_pixels);
void gsk_ngl_texture_free (GskNglTexture *texture);
G_END_DECLS
#endif /* _GSK_NGL_TEXTURE_POOL_PRIVATE_H__ */

View File

@ -0,0 +1,62 @@
/* gskngltypesprivate.h
*
* 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
*/
#ifndef __GSK_NGL_TYPES_PRIVATE_H__
#define __GSK_NGL_TYPES_PRIVATE_H__
#include <epoxy/gl.h>
#include <graphene.h>
#include <gdk/gdk.h>
#include <gsk/gsk.h>
G_BEGIN_DECLS
#define GSK_NGL_N_VERTICES 6
typedef struct _GskNglAttachmentState GskNglAttachmentState;
typedef struct _GskNglBuffer GskNglBuffer;
typedef struct _GskNglCommandQueue GskNglCommandQueue;
typedef struct _GskNglCompiler GskNglCompiler;
typedef struct _GskNglDrawVertex GskNglDrawVertex;
typedef struct _GskNglRenderTarget GskNglRenderTarget;
typedef struct _GskNglGlyphLibrary GskNglGlyphLibrary;
typedef struct _GskNglIconLibrary GskNglIconLibrary;
typedef struct _GskNglProgram GskNglProgram;
typedef struct _GskNglRenderJob GskNglRenderJob;
typedef struct _GskNglShadowLibrary GskNglShadowLibrary;
typedef struct _GskNglTexture GskNglTexture;
typedef struct _GskNglTextureSlice GskNglTextureSlice;
typedef struct _GskNglTextureAtlas GskNglTextureAtlas;
typedef struct _GskNglTextureLibrary GskNglTextureLibrary;
typedef struct _GskNglTextureNineSlice GskNglTextureNineSlice;
typedef struct _GskNglUniformInfo GskNglUniformInfo;
typedef struct _GskNglUniformProgram GskNglUniformProgram;
typedef struct _GskNglUniformState GskNglUniformState;
typedef struct _GskNglDriver GskNglDriver;
struct _GskNglDrawVertex
{
float position[2];
float uv[2];
};
G_END_DECLS
#endif /* __GSK_NGL_TYPES_PRIVATE_H__ */

View File

@ -0,0 +1,270 @@
/* gskngluniformstate.c
*
* 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 <gsk/gskroundedrectprivate.h>
#include <string.h>
#include "gskngluniformstateprivate.h"
static const guint8 uniform_sizes[] = {
0,
sizeof (Uniform1f),
sizeof (Uniform2f),
sizeof (Uniform3f),
sizeof (Uniform4f),
sizeof (Uniform1f),
sizeof (Uniform2f),
sizeof (Uniform3f),
sizeof (Uniform4f),
sizeof (Uniform1i),
sizeof (Uniform2i),
sizeof (Uniform3i),
sizeof (Uniform4i),
sizeof (Uniform1ui),
sizeof (guint),
sizeof (graphene_matrix_t),
sizeof (GskRoundedRect),
sizeof (GdkRGBA),
0,
};
GskNglUniformState *
gsk_ngl_uniform_state_new (void)
{
GskNglUniformState *state;
state = g_atomic_rc_box_new0 (GskNglUniformState);
state->programs = g_hash_table_new_full (NULL, NULL, NULL, g_free);
state->values_len = 4096;
state->values_pos = 0;
state->values_buf = g_malloc (4096);
return g_steal_pointer (&state);
}
GskNglUniformState *
gsk_ngl_uniform_state_ref (GskNglUniformState *state)
{
return g_atomic_rc_box_acquire (state);
}
static void
gsk_ngl_uniform_state_finalize (gpointer data)
{
GskNglUniformState *state = data;
g_clear_pointer (&state->programs, g_hash_table_unref);
g_clear_pointer (&state->values_buf, g_free);
}
void
gsk_ngl_uniform_state_unref (GskNglUniformState *state)
{
g_atomic_rc_box_release_full (state, gsk_ngl_uniform_state_finalize);
}
gpointer
gsk_ngl_uniform_state_init_value (GskNglUniformState *state,
GskNglUniformProgram *program,
GskNglUniformFormat format,
guint array_count,
guint location,
GskNglUniformInfoElement **infoptr)
{
GskNglUniformInfoElement *info;
guint offset;
g_assert (state != NULL);
g_assert (array_count < 32);
g_assert ((int)format >= 0 && format < GSK_NGL_UNIFORM_FORMAT_LAST);
g_assert (format > 0);
g_assert (program != NULL);
g_assert (program->sparse != NULL);
g_assert (program->n_sparse <= program->n_uniforms);
g_assert (location < GL_MAX_UNIFORM_LOCATIONS || location == (guint)-1);
g_assert (location < program->n_uniforms);
/* Handle unused uniforms gracefully */
if G_UNLIKELY (location == (guint)-1)
return NULL;
info = &program->uniforms[location];
if G_LIKELY (format == info->info.format)
{
if G_LIKELY (array_count <= info->info.array_count)
{
*infoptr = info;
return GSK_NGL_UNIFORM_VALUE (state->values_buf, info->info.offset);
}
/* We found the uniform, but there is not enough space for the
* amount that was requested. Instead, allocate new space and
* set the value to "initial" so that the caller just writes
* over the previous value.
*
* This can happen when using dynamic array lengths like the
* "n_color_stops" in gradient shaders.
*/
goto setup_info;
}
else if (info->info.format == 0)
{
goto setup_info;
}
else
{
g_critical ("Attempt to access uniform with different type of value "
"than it was initialized with. Program %u Location %u. "
"Was %d now %d (array length %d now %d).",
program->program_id, location, info->info.format, format,
info->info.array_count, array_count);
*infoptr = NULL;
return NULL;
}
setup_info:
gsk_ngl_uniform_state_realloc (state,
uniform_sizes[format] * MAX (1, array_count),
&offset);
/* we have 21 bits for offset */
g_assert (offset < (1 << GSK_NGL_UNIFORM_OFFSET_BITS));
/* We could once again be setting up this info if the array size grew.
* So make sure that we have space in our space array for the value.
*/
g_assert (info->info.format != 0 || program->n_sparse < program->n_uniforms);
if (info->info.format == 0)
program->sparse[program->n_sparse++] = location;
info->info.format = format;
info->info.offset = offset;
info->info.array_count = array_count;
info->info.initial = TRUE;
info->stamp = 0;
*infoptr = info;
return GSK_NGL_UNIFORM_VALUE (state->values_buf, info->info.offset);
}
void
gsk_ngl_uniform_state_end_frame (GskNglUniformState *state)
{
GHashTableIter iter;
GskNglUniformProgram *program;
guint allocator = 0;
g_return_if_fail (state != NULL);
/* After a frame finishes, we want to remove all our copies of uniform
* data that isn't needed any longer. Since we treat it as uninitialized
* after this frame (to reset it on first use next frame) we can just
* discard it but keep an allocation around to reuse.
*/
g_hash_table_iter_init (&iter, state->programs);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&program))
{
for (guint j = 0; j < program->n_sparse; j++)
{
guint location = program->sparse[j];
GskNglUniformInfoElement *info = &program->uniforms[location];
guint size;
g_assert (info->info.format > 0);
/* Calculate how much size is needed for the uniform, including arrays */
size = uniform_sizes[info->info.format] * MAX (1, info->info.array_count);
/* Adjust alignment for value */
allocator += gsk_ngl_uniform_state_align (allocator, size);
/* Offset is in slots of 4 bytes */
info->info.offset = allocator / 4;
info->info.initial = TRUE;
info->stamp = 0;
/* Now advance for this items data */
allocator += size;
}
}
state->values_pos = allocator;
g_assert (allocator <= state->values_len);
}
gsize
gsk_ngl_uniform_format_size (GskNglUniformFormat format)
{
g_assert (format > 0);
g_assert (format < GSK_NGL_UNIFORM_FORMAT_LAST);
return uniform_sizes[format];
}
GskNglUniformProgram *
gsk_ngl_uniform_state_get_program (GskNglUniformState *state,
guint program,
guint n_uniforms)
{
GskNglUniformProgram *ret;
g_return_val_if_fail (state != NULL, NULL);
g_return_val_if_fail (program > 0, NULL);
g_return_val_if_fail (program < G_MAXUINT, NULL);
ret = g_hash_table_lookup (state->programs, GUINT_TO_POINTER (program));
if (ret == NULL)
{
gsize uniform_size = n_uniforms * sizeof (GskNglUniformInfoElement);
gsize sparse_size = n_uniforms * sizeof (guint);
gsize size = sizeof (GskNglUniformProgram) + uniform_size + sparse_size;
/* Must be multiple of 4 for space pointer to align */
G_STATIC_ASSERT (sizeof (GskNglUniformInfoElement) == 8);
ret = g_malloc0 (size);
ret->program_id = program;
ret->n_uniforms = n_uniforms;
ret->n_sparse = 0;
ret->sparse = (guint *)&ret->uniforms[n_uniforms];
for (guint i = 0; i < n_uniforms; i++)
ret->uniforms[i].info.initial = TRUE;
g_hash_table_insert (state->programs, GUINT_TO_POINTER (program), ret);
}
return ret;
}

View File

@ -0,0 +1,685 @@
/* gskngluniformstateprivate.h
*
* 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
*/
#ifndef GSK_NGL_UNIFORM_STATE_PRIVATE_H
#define GSK_NGL_UNIFORM_STATE_PRIVATE_H
#include "gskngltypesprivate.h"
G_BEGIN_DECLS
typedef struct { float v0; } Uniform1f;
typedef struct { float v0; float v1; } Uniform2f;
typedef struct { float v0; float v1; float v2; } Uniform3f;
typedef struct { float v0; float v1; float v2; float v3; } Uniform4f;
typedef struct { int v0; } Uniform1i;
typedef struct { int v0; int v1; } Uniform2i;
typedef struct { int v0; int v1; int v2; } Uniform3i;
typedef struct { int v0; int v1; int v2; int v3; } Uniform4i;
typedef struct { guint v0; } Uniform1ui;
#define GSK_NGL_UNIFORM_ARRAY_BITS 5
#define GSK_NGL_UNIFORM_FORMAT_BITS 5
#define GSK_NGL_UNIFORM_OFFSET_BITS 21
typedef struct _GskNglUniformInfo
{
guint initial : 1;
guint format : GSK_NGL_UNIFORM_FORMAT_BITS;
guint array_count : GSK_NGL_UNIFORM_ARRAY_BITS;
guint offset : GSK_NGL_UNIFORM_OFFSET_BITS;
} GskNglUniformInfo;
G_STATIC_ASSERT (sizeof (GskNglUniformInfo) == 4);
typedef struct _GskNglUniformInfoElement
{
GskNglUniformInfo info;
guint stamp;
} GskNglUniformInfoElement;
G_STATIC_ASSERT (sizeof (GskNglUniformInfoElement) == 8);
typedef struct _GskNglUniformProgram
{
guint program_id;
guint n_uniforms : 12;
guint has_attachments : 1;
/* To avoid walking our 1:1 array of location->uniform slots, we have
* a sparse index that allows us to skip the empty zones.
*/
guint *sparse;
guint n_sparse;
/* Uniforms are provided inline at the end of structure to avoid
* an extra dereference.
*/
GskNglUniformInfoElement uniforms[0];
} GskNglUniformProgram;
typedef struct _GskNglUniformState
{
GHashTable *programs;
guint8 *values_buf;
guint values_pos;
guint values_len;
} GskNglUniformState;
/**
* GskNglUniformStateCallback:
* @info: a pointer to the information about the uniform
* @location: the location of the uniform within the GPU program.
* @user_data: closure data for the callback
*
* This callback can be used to snapshot state of a program which
* is useful when batching commands so that the state may be compared
* with future evocations of the program.
*/
typedef void (*GskNglUniformStateCallback) (const GskNglUniformInfo *info,
guint location,
gpointer user_data);
typedef enum _GskNglUniformKind
{
GSK_NGL_UNIFORM_FORMAT_1F = 1,
GSK_NGL_UNIFORM_FORMAT_2F,
GSK_NGL_UNIFORM_FORMAT_3F,
GSK_NGL_UNIFORM_FORMAT_4F,
GSK_NGL_UNIFORM_FORMAT_1FV,
GSK_NGL_UNIFORM_FORMAT_2FV,
GSK_NGL_UNIFORM_FORMAT_3FV,
GSK_NGL_UNIFORM_FORMAT_4FV,
GSK_NGL_UNIFORM_FORMAT_1I,
GSK_NGL_UNIFORM_FORMAT_2I,
GSK_NGL_UNIFORM_FORMAT_3I,
GSK_NGL_UNIFORM_FORMAT_4I,
GSK_NGL_UNIFORM_FORMAT_1UI,
GSK_NGL_UNIFORM_FORMAT_TEXTURE,
GSK_NGL_UNIFORM_FORMAT_MATRIX,
GSK_NGL_UNIFORM_FORMAT_ROUNDED_RECT,
GSK_NGL_UNIFORM_FORMAT_COLOR,
GSK_NGL_UNIFORM_FORMAT_LAST
} GskNglUniformFormat;
G_STATIC_ASSERT (GSK_NGL_UNIFORM_FORMAT_LAST < (1 << GSK_NGL_UNIFORM_FORMAT_BITS));
GskNglUniformState *gsk_ngl_uniform_state_new (void);
GskNglUniformState *gsk_ngl_uniform_state_ref (GskNglUniformState *state);
void gsk_ngl_uniform_state_unref (GskNglUniformState *state);
GskNglUniformProgram *gsk_ngl_uniform_state_get_program (GskNglUniformState *state,
guint program,
guint n_uniforms);
void gsk_ngl_uniform_state_end_frame (GskNglUniformState *state);
gsize gsk_ngl_uniform_format_size (GskNglUniformFormat format);
gpointer gsk_ngl_uniform_state_init_value (GskNglUniformState *state,
GskNglUniformProgram *program,
GskNglUniformFormat format,
guint array_count,
guint location,
GskNglUniformInfoElement **infoptr);
#define GSK_NGL_UNIFORM_VALUE(base, offset) ((gpointer)((base) + ((offset) * 4)))
#define gsk_ngl_uniform_state_get_uniform_data(state,offset) GSK_NGL_UNIFORM_VALUE((state)->values_buf, offset)
#define gsk_ngl_uniform_state_snapshot(state, program_info, callback, user_data) \
G_STMT_START { \
for (guint z = 0; z < program_info->n_sparse; z++) \
{ \
guint location = program_info->sparse[z]; \
GskNglUniformInfoElement *info = &program_info->uniforms[location]; \
\
g_assert (location < GL_MAX_UNIFORM_LOCATIONS); \
g_assert (location < program_info->n_uniforms); \
\
if (info->info.format > 0) \
callback (&info->info, location, user_data); \
} \
} G_STMT_END
static inline gpointer
gsk_ngl_uniform_state_get_value (GskNglUniformState *state,
GskNglUniformProgram *program,
GskNglUniformFormat format,
guint array_count,
guint location,
guint stamp,
GskNglUniformInfoElement **infoptr)
{
GskNglUniformInfoElement *info;
if (location == (guint)-1)
return NULL;
/* If the stamp is the same, then we can ignore the request
* and short-circuit as early as possible. This requires the
* caller to increment their private stamp when they change
* internal state.
*
* This is generally used for the shared uniforms like projection,
* modelview, clip, etc to avoid so many comparisons which cost
* considerable CPU.
*/
info = &program->uniforms[location];
if (stamp != 0 && stamp == info->stamp)
return NULL;
if G_LIKELY (format == info->info.format && array_count <= info->info.array_count)
{
*infoptr = info;
return GSK_NGL_UNIFORM_VALUE (state->values_buf, info->info.offset);
}
return gsk_ngl_uniform_state_init_value (state, program, format, array_count, location, infoptr);
}
static inline guint
gsk_ngl_uniform_state_align (guint current_pos,
guint size)
{
guint align = size > 8 ? 16 : (size > 4 ? 8 : 4);
guint masked = current_pos & (align - 1);
g_assert (size > 0);
g_assert (align == 4 || align == 8 || align == 16);
g_assert (masked < align);
return align - masked;
}
static inline gpointer
gsk_ngl_uniform_state_realloc (GskNglUniformState *state,
guint size,
guint *offset)
{
guint padding = gsk_ngl_uniform_state_align (state->values_pos, size);
if G_UNLIKELY (state->values_len - padding - size < state->values_pos)
{
state->values_len *= 2;
state->values_buf = g_realloc (state->values_buf, state->values_len);
}
/* offsets are in slots of 4 to use fewer bits */
g_assert ((state->values_pos + padding) % 4 == 0);
*offset = (state->values_pos + padding) / 4;
state->values_pos += padding + size;
return GSK_NGL_UNIFORM_VALUE (state->values_buf, *offset);
}
#define GSK_NGL_UNIFORM_STATE_REPLACE(info, u, type, count) \
G_STMT_START { \
if ((info)->info.initial && count == (info)->info.array_count) \
{ \
u = GSK_NGL_UNIFORM_VALUE (state->values_buf, (info)->info.offset); \
} \
else \
{ \
guint offset; \
u = gsk_ngl_uniform_state_realloc (state, sizeof(type) * MAX (1, count), &offset); \
g_assert (offset < (1 << GSK_NGL_UNIFORM_OFFSET_BITS)); \
(info)->info.offset = offset; \
/* We might have increased array length */ \
(info)->info.array_count = count; \
} \
} G_STMT_END
static inline void
gsk_ngl_uniform_info_changed (GskNglUniformInfoElement *info,
guint location,
guint stamp)
{
info->stamp = stamp;
info->info.initial = FALSE;
}
static inline void
gsk_ngl_uniform_state_set1f (GskNglUniformState *state,
GskNglUniformProgram *program,
guint location,
guint stamp,
float value0)
{
Uniform1f *u;
GskNglUniformInfoElement *info;
g_assert (state != NULL);
g_assert (program != 0);
if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_1F, 1, location, stamp, &info)))
{
GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform1f , 1);
u->v0 = value0;
gsk_ngl_uniform_info_changed (info, location, stamp);
}
}
static inline void
gsk_ngl_uniform_state_set2f (GskNglUniformState *state,
GskNglUniformProgram *program,
guint location,
guint stamp,
float value0,
float value1)
{
Uniform2f *u;
GskNglUniformInfoElement *info;
g_assert (state != NULL);
g_assert (program != NULL);
if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_2F, 1, location, stamp, &info)))
{
GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform2f, 1);
u->v0 = value0;
u->v1 = value1;
gsk_ngl_uniform_info_changed (info, location, stamp);
}
}
static inline void
gsk_ngl_uniform_state_set3f (GskNglUniformState *state,
GskNglUniformProgram *program,
guint location,
guint stamp,
float value0,
float value1,
float value2)
{
Uniform3f *u;
GskNglUniformInfoElement *info;
g_assert (state != NULL);
g_assert (program != NULL);
if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_3F, 1, location, stamp, &info)))
{
GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform3f, 1);
u->v0 = value0;
u->v1 = value1;
u->v2 = value2;
gsk_ngl_uniform_info_changed (info, location, stamp);
}
}
static inline void
gsk_ngl_uniform_state_set4f (GskNglUniformState *state,
GskNglUniformProgram *program,
guint location,
guint stamp,
float value0,
float value1,
float value2,
float value3)
{
Uniform4f *u;
GskNglUniformInfoElement *info;
g_assert (state != NULL);
g_assert (program != NULL);
if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_4F, 1, location, stamp, &info)))
{
GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform4f, 1);
u->v0 = value0;
u->v1 = value1;
u->v2 = value2;
u->v3 = value3;
gsk_ngl_uniform_info_changed (info, location, stamp);
}
}
static inline void
gsk_ngl_uniform_state_set1ui (GskNglUniformState *state,
GskNglUniformProgram *program,
guint location,
guint stamp,
guint value0)
{
Uniform1ui *u;
GskNglUniformInfoElement *info;
g_assert (state != NULL);
g_assert (program != NULL);
if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_1UI, 1, location, stamp, &info)))
{
GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform1ui, 1);
u->v0 = value0;
gsk_ngl_uniform_info_changed (info, location, stamp);
}
}
static inline void
gsk_ngl_uniform_state_set1i (GskNglUniformState *state,
GskNglUniformProgram *program,
guint location,
guint stamp,
int value0)
{
Uniform1i *u;
GskNglUniformInfoElement *info;
g_assert (state != NULL);
g_assert (program != NULL);
if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_1I, 1, location, stamp, &info)))
{
GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform1i, 1);
u->v0 = value0;
gsk_ngl_uniform_info_changed (info, location, stamp);
}
}
static inline void
gsk_ngl_uniform_state_set2i (GskNglUniformState *state,
GskNglUniformProgram *program,
guint location,
guint stamp,
int value0,
int value1)
{
Uniform2i *u;
GskNglUniformInfoElement *info;
g_assert (state != NULL);
g_assert (program != NULL);
if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_2I, 1, location, stamp, &info)))
{
GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform2i, 1);
u->v0 = value0;
u->v1 = value1;
gsk_ngl_uniform_info_changed (info, location, stamp);
}
}
static inline void
gsk_ngl_uniform_state_set3i (GskNglUniformState *state,
GskNglUniformProgram *program,
guint location,
guint stamp,
int value0,
int value1,
int value2)
{
Uniform3i *u;
GskNglUniformInfoElement *info;
g_assert (state != NULL);
g_assert (program != NULL);
if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_3I, 1, location, stamp, &info)))
{
GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform3i, 1);
u->v0 = value0;
u->v1 = value1;
u->v2 = value2;
gsk_ngl_uniform_info_changed (info, location, stamp);
}
}
static inline void
gsk_ngl_uniform_state_set4i (GskNglUniformState *state,
GskNglUniformProgram *program,
guint location,
guint stamp,
int value0,
int value1,
int value2,
int value3)
{
Uniform4i *u;
GskNglUniformInfoElement *info;
g_assert (state != NULL);
g_assert (program != NULL);
if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_4I, 1, location, stamp, &info)))
{
GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform4i, 1);
u->v0 = value0;
u->v1 = value1;
u->v2 = value2;
u->v3 = value3;
gsk_ngl_uniform_info_changed (info, location, stamp);
}
}
static inline void
gsk_ngl_uniform_state_set_rounded_rect (GskNglUniformState *state,
GskNglUniformProgram *program,
guint location,
guint stamp,
const GskRoundedRect *rounded_rect)
{
GskRoundedRect *u;
GskNglUniformInfoElement *info;
g_assert (state != NULL);
g_assert (program != NULL);
g_assert (rounded_rect != NULL);
if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_ROUNDED_RECT, 1, location, stamp, &info)))
{
GSK_NGL_UNIFORM_STATE_REPLACE (info, u, GskRoundedRect, 1);
memcpy (u, rounded_rect, sizeof *rounded_rect);
gsk_ngl_uniform_info_changed (info, location, stamp);
}
}
static inline void
gsk_ngl_uniform_state_set_matrix (GskNglUniformState *state,
GskNglUniformProgram *program,
guint location,
guint stamp,
const graphene_matrix_t *matrix)
{
graphene_matrix_t *u;
GskNglUniformInfoElement *info;
g_assert (state != NULL);
g_assert (program != NULL);
g_assert (matrix != NULL);
if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_MATRIX, 1, location, stamp, &info)))
{
GSK_NGL_UNIFORM_STATE_REPLACE (info, u, graphene_matrix_t, 1);
memcpy (u, matrix, sizeof *matrix);
gsk_ngl_uniform_info_changed (info, location, stamp);
}
}
/**
* gsk_ngl_uniform_state_set_texture:
* @state: a #GskNglUniformState
* @program: the program id
* @location: the location of the texture
* @texture_slot: a texturing slot such as GL_TEXTURE0
*
* Sets the uniform expecting a texture to @texture_slot. This API
* expects a texture slot such as GL_TEXTURE0 to reduce chances of
* miss-use by the caller.
*
* The value stored to the uniform is in the form of 0 for GL_TEXTURE0,
* 1 for GL_TEXTURE1, and so on.
*/
static inline void
gsk_ngl_uniform_state_set_texture (GskNglUniformState *state,
GskNglUniformProgram *program,
guint location,
guint stamp,
guint texture_slot)
{
GskNglUniformInfoElement *info;
guint *u;
g_assert (texture_slot >= GL_TEXTURE0);
g_assert (texture_slot < GL_TEXTURE16);
texture_slot -= GL_TEXTURE0;
if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_TEXTURE, 1, location, stamp, &info)))
{
GSK_NGL_UNIFORM_STATE_REPLACE (info, u, guint, 1);
*u = texture_slot;
gsk_ngl_uniform_info_changed (info, location, stamp);
}
}
/**
* gsk_ngl_uniform_state_set_color:
* @state: a #GskNglUniformState
* @program: a program id > 0
* @location: the uniform location
* @color: a color to set or %NULL for transparent
*
* Sets a uniform to the color described by @color. This is a convenience
* function to allow callers to avoid having to translate colors to floats
* in other portions of the renderer.
*/
static inline void
gsk_ngl_uniform_state_set_color (GskNglUniformState *state,
GskNglUniformProgram *program,
guint location,
guint stamp,
const GdkRGBA *color)
{
static const GdkRGBA transparent = {0};
GskNglUniformInfoElement *info;
GdkRGBA *u;
g_assert (state != NULL);
g_assert (program != NULL);
if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_COLOR, 1, location, stamp, &info)))
{
if (color == NULL)
color = &transparent;
GSK_NGL_UNIFORM_STATE_REPLACE (info, u, GdkRGBA, 1);
memcpy (u, color, sizeof *color);
gsk_ngl_uniform_info_changed (info, location, stamp);
}
}
static inline void
gsk_ngl_uniform_state_set1fv (GskNglUniformState *state,
GskNglUniformProgram *program,
guint location,
guint stamp,
guint count,
const float *value)
{
Uniform1f *u;
GskNglUniformInfoElement *info;
g_assert (state != NULL);
g_assert (program != NULL);
g_assert (count > 0);
if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_1FV, count, location, stamp, &info)))
{
GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform1f, count);
memcpy (u, value, sizeof (Uniform1f) * count);
gsk_ngl_uniform_info_changed (info, location, stamp);
}
}
static inline void
gsk_ngl_uniform_state_set2fv (GskNglUniformState *state,
GskNglUniformProgram *program,
guint location,
guint stamp,
guint count,
const float *value)
{
Uniform2f *u;
GskNglUniformInfoElement *info;
g_assert (state != NULL);
g_assert (program != NULL);
g_assert (count > 0);
if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_2FV, count, location, stamp, &info)))
{
GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform2f, count);
memcpy (u, value, sizeof (Uniform2f) * count);
gsk_ngl_uniform_info_changed (info, location, stamp);
}
}
static inline void
gsk_ngl_uniform_state_set3fv (GskNglUniformState *state,
GskNglUniformProgram *program,
guint location,
guint stamp,
guint count,
const float *value)
{
Uniform3f *u;
GskNglUniformInfoElement *info;
g_assert (state != NULL);
g_assert (program != NULL);
g_assert (count > 0);
if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_3FV, count, location, stamp, &info)))
{
GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform3f, count);
memcpy (u, value, sizeof (Uniform3f) * count);
gsk_ngl_uniform_info_changed (info, location, stamp);
}
}
static inline void
gsk_ngl_uniform_state_set4fv (GskNglUniformState *state,
GskNglUniformProgram *program,
guint location,
guint stamp,
guint count,
const float *value)
{
Uniform4f *u;
GskNglUniformInfoElement *info;
g_assert (state != NULL);
g_assert (program != NULL);
g_assert (count > 0);
if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_4FV, count, location, stamp, &info)))
{
GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform4f, count);
memcpy (u, value, sizeof (Uniform4f) * count);
gsk_ngl_uniform_info_changed (info, location, stamp);
}
}
G_END_DECLS
#endif /* GSK_NGL_UNIFORM_STATE_PRIVATE_H */

77
gsk/ngl/inlinearray.h Normal file
View File

@ -0,0 +1,77 @@
#ifndef __INLINE_ARRAY_H__
#define __INLINE_ARRAY_H__
#define DEFINE_INLINE_ARRAY(Type, prefix, ElementType) \
typedef struct _##Type { \
gsize len; \
gsize allocated; \
ElementType *items; \
} Type; \
\
static inline void \
prefix##_init (Type *ar, \
gsize initial_size) \
{ \
ar->len = 0; \
ar->allocated = initial_size ? initial_size : 16; \
ar->items = g_new0 (ElementType, ar->allocated); \
} \
\
static inline void \
prefix##_clear (Type *ar) \
{ \
ar->len = 0; \
ar->allocated = 0; \
g_clear_pointer (&ar->items, g_free); \
} \
\
static inline ElementType * \
prefix##_head (Type *ar) \
{ \
return &ar->items[0]; \
} \
\
static inline ElementType * \
prefix##_tail (Type *ar) \
{ \
return &ar->items[ar->len-1]; \
} \
\
static inline ElementType * \
prefix##_append (Type *ar) \
{ \
if G_UNLIKELY (ar->len == ar->allocated) \
{ \
ar->allocated *= 2; \
ar->items = g_renew (ElementType, ar->items, ar->allocated);\
} \
\
ar->len++; \
\
return prefix##_tail (ar); \
} \
\
static inline ElementType * \
prefix##_append_n (Type *ar, \
gsize n) \
{ \
if G_UNLIKELY ((ar->len + n) > ar->allocated) \
{ \
while ((ar->len + n) > ar->allocated) \
ar->allocated *= 2; \
ar->items = g_renew (ElementType, ar->items, ar->allocated);\
} \
\
ar->len += n; \
\
return &ar->items[ar->len-n]; \
} \
\
static inline gsize \
prefix##_index_of (Type *ar, \
const ElementType *element) \
{ \
return element - &ar->items[0]; \
}
#endif /* __INLINE_ARRAY_H__ */

309
gsk/ngl/ninesliceprivate.h Normal file
View File

@ -0,0 +1,309 @@
/* ninesliceprivate.h
*
* Copyright 2017 Timm Bäder <mail@baedert.org>
* Copyright 2021 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
*/
#ifndef __NINE_SLICE_PRIVATE_H__
#define __NINE_SLICE_PRIVATE_H__
#include "gskngltexturepoolprivate.h"
#if 0
# define DEBUG_NINE_SLICE
#endif
G_BEGIN_DECLS
enum {
NINE_SLICE_TOP_LEFT = 0,
NINE_SLICE_TOP_CENTER = 1,
NINE_SLICE_TOP_RIGHT = 2,
NINE_SLICE_LEFT_CENTER = 3,
NINE_SLICE_CENTER = 4,
NINE_SLICE_RIGHT_CENTER = 5,
NINE_SLICE_BOTTOM_LEFT = 6,
NINE_SLICE_BOTTOM_CENTER = 7,
NINE_SLICE_BOTTOM_RIGHT = 8,
};
static inline bool G_GNUC_PURE
nine_slice_is_visible (const GskNglTextureNineSlice *slice)
{
return slice->rect.width > 0 && slice->rect.height > 0;
}
static inline void
nine_slice_rounded_rect (GskNglTextureNineSlice *slices,
const GskRoundedRect *rect)
{
const graphene_point_t *origin = &rect->bounds.origin;
const graphene_size_t *size = &rect->bounds.size;
int top_height = ceilf (MAX (rect->corner[GSK_CORNER_TOP_LEFT].height,
rect->corner[GSK_CORNER_TOP_RIGHT].height));
int bottom_height = ceilf (MAX (rect->corner[GSK_CORNER_BOTTOM_LEFT].height,
rect->corner[GSK_CORNER_BOTTOM_RIGHT].height));
int right_width = ceilf (MAX (rect->corner[GSK_CORNER_TOP_RIGHT].width,
rect->corner[GSK_CORNER_BOTTOM_RIGHT].width));
int left_width = ceilf (MAX (rect->corner[GSK_CORNER_TOP_LEFT].width,
rect->corner[GSK_CORNER_BOTTOM_LEFT].width));
/* Top left */
slices[0].rect.x = origin->x;
slices[0].rect.y = origin->y;
slices[0].rect.width = left_width;
slices[0].rect.height = top_height;
/* Top center */
slices[1].rect.x = origin->x + size->width / 2.0 - 0.5;
slices[1].rect.y = origin->y;
slices[1].rect.width = 1;
slices[1].rect.height = top_height;
/* Top right */
slices[2].rect.x = origin->x + size->width - right_width;
slices[2].rect.y = origin->y;
slices[2].rect.width = right_width;
slices[2].rect.height = top_height;
/* Left center */
slices[3].rect.x = origin->x;
slices[3].rect.y = origin->y + size->height / 2;
slices[3].rect.width = left_width;
slices[3].rect.height = 1;
/* center */
slices[4].rect.x = origin->x + size->width / 2.0 - 0.5;
slices[4].rect.y = origin->y + size->height / 2.0 - 0.5;
slices[4].rect.width = 1;
slices[4].rect.height = 1;
/* Right center */
slices[5].rect.x = origin->x + size->width - right_width;
slices[5].rect.y = origin->y + (size->height / 2.0) - 0.5;
slices[5].rect.width = right_width;
slices[5].rect.height = 1;
/* Bottom Left */
slices[6].rect.x = origin->x;
slices[6].rect.y = origin->y + size->height - bottom_height;
slices[6].rect.width = left_width;
slices[6].rect.height = bottom_height;
/* Bottom center */
slices[7].rect.x = origin->x + (size->width / 2.0) - 0.5;
slices[7].rect.y = origin->y + size->height - bottom_height;
slices[7].rect.width = 1;
slices[7].rect.height = bottom_height;
/* Bottom right */
slices[8].rect.x = origin->x + size->width - right_width;
slices[8].rect.y = origin->y + size->height - bottom_height;
slices[8].rect.width = right_width;
slices[8].rect.height = bottom_height;
#ifdef DEBUG_NINE_SLICE
/* These only hold true when the values from ceilf() above
* are greater than one. Otherwise they fail, like will happen
* with the node editor viewing the textures zoomed out.
*/
if (size->width > 1)
g_assert_cmpfloat (size->width, >=, left_width + right_width);
if (size->height > 1)
g_assert_cmpfloat (size->height, >=, top_height + bottom_height);
#endif
}
static inline void
nine_slice_to_texture_coords (GskNglTextureNineSlice *slices,
int texture_width,
int texture_height)
{
float fw = texture_width;
float fh = texture_height;
for (guint i = 0; i < 9; i++)
{
GskNglTextureNineSlice *slice = &slices[i];
slice->area.x = slice->rect.x / fw;
slice->area.y = 1.0 - ((slice->rect.y + slice->rect.height) / fh);
slice->area.x2 = ((slice->rect.x + slice->rect.width) / fw);
slice->area.y2 = (1.0 - (slice->rect.y / fh));
#ifdef DEBUG_NINE_SLICE
g_assert_cmpfloat (slice->area.x, >=, 0);
g_assert_cmpfloat (slice->area.x, <=, 1);
g_assert_cmpfloat (slice->area.y, >=, 0);
g_assert_cmpfloat (slice->area.y, <=, 1);
g_assert_cmpfloat (slice->area.x2, >, slice->area.x);
g_assert_cmpfloat (slice->area.y2, >, slice->area.y);
#endif
}
}
static inline void
nine_slice_grow (GskNglTextureNineSlice *slices,
int amount)
{
if (amount == 0)
return;
/* top left */
slices[0].rect.x -= amount;
slices[0].rect.y -= amount;
if (amount > slices[0].rect.width)
slices[0].rect.width += amount * 2;
else
slices[0].rect.width += amount;
if (amount > slices[0].rect.height)
slices[0].rect.height += amount * 2;
else
slices[0].rect.height += amount;
/* Top center */
slices[1].rect.y -= amount;
if (amount > slices[1].rect.height)
slices[1].rect.height += amount * 2;
else
slices[1].rect.height += amount;
/* top right */
slices[2].rect.y -= amount;
if (amount > slices[2].rect.width)
{
slices[2].rect.x -= amount;
slices[2].rect.width += amount * 2;
}
else
{
slices[2].rect.width += amount;
}
if (amount > slices[2].rect.height)
slices[2].rect.height += amount * 2;
else
slices[2].rect.height += amount;
slices[3].rect.x -= amount;
if (amount > slices[3].rect.width)
slices[3].rect.width += amount * 2;
else
slices[3].rect.width += amount;
/* Leave center alone */
if (amount > slices[5].rect.width)
{
slices[5].rect.x -= amount;
slices[5].rect.width += amount * 2;
}
else
{
slices[5].rect.width += amount;
}
/* Bottom left */
slices[6].rect.x -= amount;
if (amount > slices[6].rect.width)
{
slices[6].rect.width += amount * 2;
}
else
{
slices[6].rect.width += amount;
}
if (amount > slices[6].rect.height)
{
slices[6].rect.y -= amount;
slices[6].rect.height += amount * 2;
}
else
{
slices[6].rect.height += amount;
}
/* Bottom center */
if (amount > slices[7].rect.height)
{
slices[7].rect.y -= amount;
slices[7].rect.height += amount * 2;
}
else
{
slices[7].rect.height += amount;
}
if (amount > slices[8].rect.width)
{
slices[8].rect.x -= amount;
slices[8].rect.width += amount * 2;
}
else
{
slices[8].rect.width += amount;
}
if (amount > slices[8].rect.height)
{
slices[8].rect.y -= amount;
slices[8].rect.height += amount * 2;
}
else
{
slices[8].rect.height += amount;
}
#ifdef DEBUG_NINE_SLICE
/* These cannot be relied on in all cases right now, specifically
* when viewing data zoomed out.
*/
for (guint i = 0; i < 9; i ++)
{
g_assert_cmpint (slices[i].rect.x, >=, 0);
g_assert_cmpint (slices[i].rect.y, >=, 0);
g_assert_cmpint (slices[i].rect.width, >=, 0);
g_assert_cmpint (slices[i].rect.height, >=, 0);
}
/* Rows don't overlap */
for (guint i = 0; i < 3; i++)
{
int lhs = slices[i * 3 + 0].rect.x + slices[i * 3 + 0].rect.width;
int rhs = slices[i * 3 + 1].rect.x;
/* Ignore the case where we are scaled out and the
* positioning is degenerate, such as from node-editor.
*/
if (rhs > 1)
g_assert_cmpint (lhs, <, rhs);
}
#endif
}
G_END_DECLS
#endif /* __NINE_SLICE_PRIVATE_H__ */

View File

@ -41,6 +41,7 @@
#define GTK_COMPILATION #define GTK_COMPILATION
#include <gsk/gl/gskglrenderer.h> #include <gsk/gl/gskglrenderer.h>
#include <gsk/ngl/gsknglrenderer.h>
#ifdef GDK_WINDOWING_BROADWAY #ifdef GDK_WINDOWING_BROADWAY
#include <gsk/broadway/gskbroadwayrenderer.h> #include <gsk/broadway/gskbroadwayrenderer.h>

View File

@ -147,6 +147,8 @@ init_version (GtkInspectorGeneral *gen)
renderer = "Vulkan"; renderer = "Vulkan";
else if (strcmp (G_OBJECT_TYPE_NAME (gsk_renderer), "GskGLRenderer") == 0) else if (strcmp (G_OBJECT_TYPE_NAME (gsk_renderer), "GskGLRenderer") == 0)
renderer = "GL"; renderer = "GL";
else if (strcmp (G_OBJECT_TYPE_NAME (gsk_renderer), "GskNgltRenderer") == 0)
renderer = "NGL";
else if (strcmp (G_OBJECT_TYPE_NAME (gsk_renderer), "GskCairoRenderer") == 0) else if (strcmp (G_OBJECT_TYPE_NAME (gsk_renderer), "GskCairoRenderer") == 0)
renderer = "Cairo"; renderer = "Cairo";
else else

View File

@ -90,6 +90,7 @@ informative_render_tests = [
renderers = [ renderers = [
# name exclude term # name exclude term
[ 'opengl', '' ], [ 'opengl', '' ],
[ 'next', '' ],
[ 'broadway', '-3d' ], [ 'broadway', '-3d' ],
[ 'cairo', '-3d' ], [ 'cairo', '-3d' ],
] ]