gtk/gsk/gl/gskglcommandqueue.c
Matthias Clasen 0f61c52593 gdk: Simplify gdk_memory_format_gl_format
Make the callers of this function check for
straight alpha themselves, and only do the
version compatibility check here. This makes
the function usable in contexts where straight
alpha is acceptable.
2023-05-30 14:49:45 -04:00

1697 lines
53 KiB
C

/* gskglcommandqueue.c
*
* Copyright 2017 Timm Bäder <mail@baedert.org>
* Copyright 2018 Matthias Clasen <mclasen@redhat.com>
* Copyright 2018 Alexander Larsson <alexl@redhat.com>
* Copyright 2020 Christian Hergert <chergert@redhat.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "config.h"
#include <string.h>
#include <gdk/gdkglcontextprivate.h>
#include <gdk/gdkmemoryformatprivate.h>
#include <gdk/gdkprofilerprivate.h>
#include <gdk/gdktexturedownloaderprivate.h>
#include <gsk/gskdebugprivate.h>
#include <gsk/gskroundedrectprivate.h>
#include "gskglattachmentstateprivate.h"
#include "gskglbufferprivate.h"
#include "gskglcommandqueueprivate.h"
#include "gskgluniformstateprivate.h"
#include "inlinearray.h"
G_DEFINE_TYPE (GskGLCommandQueue, gsk_gl_command_queue, G_TYPE_OBJECT)
#if 0
G_GNUC_UNUSED static inline void
print_uniform (GskGLUniformFormat format,
guint array_count,
gconstpointer valueptr)
{
const union {
graphene_matrix_t matrix[0];
GskRoundedRect rounded_rect[0];
float fval[0];
int ival[0];
guint uval[0];
} *data = valueptr;
switch (format)
{
case GSK_GL_UNIFORM_FORMAT_1F:
g_printerr ("1f<%f>", data->fval[0]);
break;
case GSK_GL_UNIFORM_FORMAT_2F:
g_printerr ("2f<%f,%f>", data->fval[0], data->fval[1]);
break;
case GSK_GL_UNIFORM_FORMAT_3F:
g_printerr ("3f<%f,%f,%f>", data->fval[0], data->fval[1], data->fval[2]);
break;
case GSK_GL_UNIFORM_FORMAT_4F:
g_printerr ("4f<%f,%f,%f,%f>", data->fval[0], data->fval[1], data->fval[2], data->fval[3]);
break;
case GSK_GL_UNIFORM_FORMAT_1I:
case GSK_GL_UNIFORM_FORMAT_TEXTURE:
g_printerr ("1i<%d>", data->ival[0]);
break;
case GSK_GL_UNIFORM_FORMAT_1UI:
g_printerr ("1ui<%u>", data->uval[0]);
break;
case GSK_GL_UNIFORM_FORMAT_COLOR: {
char *str = gdk_rgba_to_string (valueptr);
g_printerr ("%s", str);
g_free (str);
break;
}
case GSK_GL_UNIFORM_FORMAT_ROUNDED_RECT: {
char *str = gsk_rounded_rect_to_string (valueptr);
g_printerr ("%s", str);
g_free (str);
break;
}
case GSK_GL_UNIFORM_FORMAT_MATRIX: {
float mat[16];
graphene_matrix_to_float (&data->matrix[0], mat);
g_printerr ("matrix<");
for (guint i = 0; i < G_N_ELEMENTS (mat)-1; i++)
g_printerr ("%f,", mat[i]);
g_printerr ("%f>", mat[G_N_ELEMENTS (mat)-1]);
break;
}
case GSK_GL_UNIFORM_FORMAT_1FV:
case GSK_GL_UNIFORM_FORMAT_2FV:
case GSK_GL_UNIFORM_FORMAT_3FV:
case GSK_GL_UNIFORM_FORMAT_4FV:
/* non-V variants are -4 from V variants */
format -= 4;
g_printerr ("[");
for (guint i = 0; i < array_count; i++)
{
print_uniform (format, 0, valueptr);
if (i + 1 != array_count)
g_printerr (",");
valueptr = ((guint8*)valueptr + gsk_gl_uniform_format_size (format));
}
g_printerr ("]");
break;
case GSK_GL_UNIFORM_FORMAT_2I:
g_printerr ("2i<%d,%d>", data->ival[0], data->ival[1]);
break;
case GSK_GL_UNIFORM_FORMAT_3I:
g_printerr ("3i<%d,%d,%d>", data->ival[0], data->ival[1], data->ival[2]);
break;
case GSK_GL_UNIFORM_FORMAT_4I:
g_printerr ("3i<%d,%d,%d,%d>", data->ival[0], data->ival[1], data->ival[2], data->ival[3]);
break;
case GSK_GL_UNIFORM_FORMAT_LAST:
default:
g_assert_not_reached ();
}
}
G_GNUC_UNUSED static inline void
gsk_gl_command_queue_print_batch (GskGLCommandQueue *self,
const GskGLCommandBatch *batch)
{
static const char *command_kinds[] = { "Clear", "Draw", };
guint framebuffer_id;
g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
g_assert (batch != NULL);
if (batch->any.kind == GSK_GL_COMMAND_KIND_CLEAR)
framebuffer_id = batch->clear.framebuffer;
else if (batch->any.kind == GSK_GL_COMMAND_KIND_DRAW)
framebuffer_id = batch->draw.framebuffer;
else
return;
g_printerr ("Batch {\n");
g_printerr (" Kind: %s\n", command_kinds[batch->any.kind]);
g_printerr (" Viewport: %dx%d\n", batch->any.viewport.width, batch->any.viewport.height);
g_printerr (" Framebuffer: %d\n", framebuffer_id);
if (batch->any.kind == GSK_GL_COMMAND_KIND_DRAW)
{
g_printerr (" Program: %d\n", batch->any.program);
g_printerr (" Vertices: %d\n", batch->draw.vbo_count);
for (guint i = 0; i < batch->draw.bind_count; i++)
{
const GskGLCommandBind *bind = &self->batch_binds.items[batch->draw.bind_offset + i];
g_printerr (" Bind[%d]: %u\n", bind->texture, bind->id);
}
for (guint i = 0; i < batch->draw.uniform_count; i++)
{
const GskGLCommandUniform *uniform = &self->batch_uniforms.items[batch->draw.uniform_offset + i];
g_printerr (" Uniform[%02d]: ", uniform->location);
print_uniform (uniform->info.format,
uniform->info.array_count,
gsk_gl_uniform_state_get_uniform_data (self->uniforms, uniform->info.offset));
g_printerr ("\n");
}
}
else if (batch->any.kind == GSK_GL_COMMAND_KIND_CLEAR)
{
g_printerr (" Bits: 0x%x\n", batch->clear.bits);
}
g_printerr ("}\n");
}
G_GNUC_UNUSED static inline void
gsk_gl_command_queue_capture_png (GskGLCommandQueue *self,
const char *filename,
guint width,
guint height,
gboolean flip_y)
{
guint stride;
guint8 *data;
GBytes *bytes;
GdkTexture *texture;
g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
g_assert (filename != NULL);
stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, width);
data = g_malloc_n (height, stride);
glReadPixels (0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, data);
if (flip_y)
{
guint8 *flipped = g_malloc_n (height, stride);
for (guint i = 0; i < height; i++)
memcpy (flipped + (height * stride) - ((i + 1) * stride),
data + (stride * i),
stride);
g_free (data);
data = flipped;
}
bytes = g_bytes_new_take (data, height * stride);
texture = gdk_memory_texture_new (width, height, GDK_MEMORY_DEFAULT, bytes, stride);
g_bytes_unref (bytes);
gdk_texture_save_to_png (texture, filename);
g_object_unref (texture);
}
#endif
static inline gboolean
will_ignore_batch (GskGLCommandQueue *self)
{
if G_LIKELY (self->batches.len < G_MAXINT16)
return FALSE;
if (!self->have_truncated)
{
self->have_truncated = TRUE;
g_critical ("GL command queue too large, truncating further batches.");
}
return TRUE;
}
static inline GLint
min_filter_from_index (guint index)
{
GLint filters[3] = { GL_LINEAR, GL_NEAREST, GL_LINEAR_MIPMAP_LINEAR };
g_assert (index < GSK_GL_N_FILTERS);
return filters[index];
}
static inline GLint
mag_filter_from_index (guint index)
{
GLint filters[3] = { GL_LINEAR, GL_NEAREST, GL_LINEAR };
g_assert (index < GSK_GL_N_FILTERS);
return filters[index];
}
static inline guint
snapshot_attachments (const GskGLAttachmentState *state,
GskGLCommandBinds *array)
{
GskGLCommandBind *bind = gsk_gl_command_binds_append_n (array, G_N_ELEMENTS (state->textures));
guint count = 0;
for (guint i = 0; i < G_N_ELEMENTS (state->textures); i++)
{
if (state->textures[i].id)
{
bind[count].id = state->textures[i].id;
bind[count].texture = state->textures[i].texture;
bind[count].sampler = state->textures[i].sampler;
count++;
}
}
if (count != G_N_ELEMENTS (state->textures))
array->len -= G_N_ELEMENTS (state->textures) - count;
return count;
}
static inline guint
snapshot_uniforms (GskGLUniformState *state,
GskGLUniformProgram *program,
GskGLCommandUniforms *array)
{
GskGLCommandUniform *uniform = gsk_gl_command_uniforms_append_n (array, program->n_mappings);
guint count = 0;
for (guint i = 0; i < program->n_mappings; i++)
{
const GskGLUniformMapping *mapping = &program->mappings[i];
if (!mapping->info.initial && mapping->info.format && mapping->location > -1)
{
uniform[count].location = mapping->location;
uniform[count].info = mapping->info;
count++;
}
}
if (count != program->n_mappings)
array->len -= program->n_mappings - count;
return count;
}
static inline gboolean
snapshots_equal (GskGLCommandQueue *self,
GskGLCommandBatch *first,
GskGLCommandBatch *second)
{
if (first->draw.bind_count != second->draw.bind_count ||
first->draw.uniform_count != second->draw.uniform_count)
return FALSE;
for (guint i = 0; i < first->draw.bind_count; i++)
{
const GskGLCommandBind *fb = &self->batch_binds.items[first->draw.bind_offset+i];
const GskGLCommandBind *sb = &self->batch_binds.items[second->draw.bind_offset+i];
if (fb->id != sb->id || fb->texture != sb->texture)
return FALSE;
}
for (guint i = 0; i < first->draw.uniform_count; i++)
{
const GskGLCommandUniform *fu = &self->batch_uniforms.items[first->draw.uniform_offset+i];
const GskGLCommandUniform *su = &self->batch_uniforms.items[second->draw.uniform_offset+i];
gconstpointer fdata;
gconstpointer sdata;
gsize len;
/* Short circuit if we'd end up with the same memory */
if (fu->info.offset == su->info.offset)
continue;
if (fu->info.format != su->info.format ||
fu->info.array_count != su->info.array_count)
return FALSE;
fdata = gsk_gl_uniform_state_get_uniform_data (self->uniforms, fu->info.offset);
sdata = gsk_gl_uniform_state_get_uniform_data (self->uniforms, su->info.offset);
switch (fu->info.format)
{
case GSK_GL_UNIFORM_FORMAT_1F:
case GSK_GL_UNIFORM_FORMAT_1FV:
case GSK_GL_UNIFORM_FORMAT_1I:
case GSK_GL_UNIFORM_FORMAT_TEXTURE:
case GSK_GL_UNIFORM_FORMAT_1UI:
len = 4;
break;
case GSK_GL_UNIFORM_FORMAT_2F:
case GSK_GL_UNIFORM_FORMAT_2FV:
case GSK_GL_UNIFORM_FORMAT_2I:
len = 8;
break;
case GSK_GL_UNIFORM_FORMAT_3F:
case GSK_GL_UNIFORM_FORMAT_3FV:
case GSK_GL_UNIFORM_FORMAT_3I:
len = 12;
break;
case GSK_GL_UNIFORM_FORMAT_4F:
case GSK_GL_UNIFORM_FORMAT_4FV:
case GSK_GL_UNIFORM_FORMAT_4I:
len = 16;
break;
case GSK_GL_UNIFORM_FORMAT_MATRIX:
len = sizeof (float) * 16;
break;
case GSK_GL_UNIFORM_FORMAT_ROUNDED_RECT:
len = sizeof (float) * 12;
break;
case GSK_GL_UNIFORM_FORMAT_COLOR:
len = sizeof (float) * 4;
break;
default:
g_assert_not_reached ();
}
len *= fu->info.array_count;
if (memcmp (fdata, sdata, len) != 0)
return FALSE;
}
return TRUE;
}
static void
gsk_gl_command_queue_dispose (GObject *object)
{
GskGLCommandQueue *self = (GskGLCommandQueue *)object;
g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
g_clear_object (&self->profiler);
g_clear_object (&self->gl_profiler);
g_clear_object (&self->context);
g_clear_pointer (&self->attachments, gsk_gl_attachment_state_unref);
g_clear_pointer (&self->uniforms, gsk_gl_uniform_state_unref);
if (self->has_samplers)
glDeleteSamplers (G_N_ELEMENTS (self->samplers), self->samplers);
gsk_gl_command_batches_clear (&self->batches);
gsk_gl_command_binds_clear (&self->batch_binds);
gsk_gl_command_uniforms_clear (&self->batch_uniforms);
gsk_gl_syncs_clear (&self->syncs);
gsk_gl_buffer_destroy (&self->vertices);
G_OBJECT_CLASS (gsk_gl_command_queue_parent_class)->dispose (object);
}
static void
gsk_gl_command_queue_class_init (GskGLCommandQueueClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gsk_gl_command_queue_dispose;
}
static void
gsk_gl_command_queue_init (GskGLCommandQueue *self)
{
self->max_texture_size = -1;
gsk_gl_command_batches_init (&self->batches, 128);
gsk_gl_command_binds_init (&self->batch_binds, 1024);
gsk_gl_command_uniforms_init (&self->batch_uniforms, 2048);
gsk_gl_syncs_init (&self->syncs, 10);
gsk_gl_buffer_init (&self->vertices, GL_ARRAY_BUFFER, sizeof (GskGLDrawVertex));
}
GskGLCommandQueue *
gsk_gl_command_queue_new (GdkGLContext *context,
GskGLUniformState *uniforms)
{
GskGLCommandQueue *self;
guint i;
g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL);
self = g_object_new (GSK_TYPE_GL_COMMAND_QUEUE, NULL);
self->context = g_object_ref (context);
self->attachments = gsk_gl_attachment_state_new ();
/* Use shared uniform state if we're provided one */
if (uniforms != NULL)
self->uniforms = gsk_gl_uniform_state_ref (uniforms);
else
self->uniforms = gsk_gl_uniform_state_new ();
/* Determine max texture size immediately and restore context */
gdk_gl_context_make_current (context);
glGetIntegerv (GL_MAX_TEXTURE_SIZE, &self->max_texture_size);
if (g_getenv ("GSK_MAX_TEXTURE_SIZE"))
{
int max_texture_size = atoi (g_getenv ("GSK_MAX_TEXTURE_SIZE"));
if (max_texture_size == 0)
{
g_warning ("Failed to parse GSK_MAX_TEXTURE_SIZE");
}
else
{
max_texture_size = MAX (max_texture_size, 512);
GSK_DEBUG(OPENGL, "Limiting max texture size to %d", max_texture_size);
self->max_texture_size = MIN (self->max_texture_size, max_texture_size);
}
}
self->has_samplers = gdk_gl_context_check_version (context, "3.3", "3.0");
/* create the samplers */
if (self->has_samplers)
{
glGenSamplers (G_N_ELEMENTS (self->samplers), self->samplers);
for (i = 0; i < G_N_ELEMENTS (self->samplers); i++)
{
glSamplerParameteri (self->samplers[i], GL_TEXTURE_MIN_FILTER, min_filter_from_index (i / GSK_GL_N_FILTERS));
glSamplerParameteri (self->samplers[i], GL_TEXTURE_MAG_FILTER, mag_filter_from_index (i % GSK_GL_N_FILTERS));
glSamplerParameteri (self->samplers[i], GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glSamplerParameteri (self->samplers[i], GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
}
return g_steal_pointer (&self);
}
static inline GskGLCommandBatch *
begin_next_batch (GskGLCommandQueue *self)
{
GskGLCommandBatch *batch;
g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
/* GskGLCommandBatch contains an embedded linked list using integers into the
* self->batches array. We can't use pointer because the batches could be
* realloc()'d at runtime.
*
* Before we execute the command queue, we sort the batches by framebuffer but
* leave the batches in place as we can just tweak the links via prev/next.
*
* Generally we only traverse forwards, so we could ignore the previous field.
* But to optimize the reordering of batches by framebuffer we walk backwards
* so we sort by most-recently-seen framebuffer to ensure draws happen in the
* proper order.
*/
batch = gsk_gl_command_batches_append (&self->batches);
batch->any.next_batch_index = -1;
batch->any.prev_batch_index = self->tail_batch_index;
return batch;
}
static void
enqueue_batch (GskGLCommandQueue *self)
{
guint index;
g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
g_assert (self->batches.len > 0);
/* Batches are linked lists but using indexes into the batches array instead
* of pointers. This is for two main reasons. First, 16-bit indexes allow us
* to store the information in 4 bytes, where as two pointers would take 16
* bytes. Furthermore, we have an array here so pointers would get
* invalidated if we realloc()'d (and that can happen from time to time).
*/
index = self->batches.len - 1;
if (self->head_batch_index == -1)
self->head_batch_index = index;
if (self->tail_batch_index != -1)
{
GskGLCommandBatch *prev = &self->batches.items[self->tail_batch_index];
prev->any.next_batch_index = index;
}
self->tail_batch_index = index;
}
static void
discard_batch (GskGLCommandQueue *self)
{
g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
g_assert (self->batches.len > 0);
self->batches.len--;
}
gboolean
gsk_gl_command_queue_begin_draw (GskGLCommandQueue *self,
GskGLUniformProgram *program,
guint width,
guint height)
{
GskGLCommandBatch *batch;
g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
g_assert (self->in_draw == FALSE);
g_assert (width <= G_MAXUINT16);
g_assert (height <= G_MAXUINT16);
/* Our internal links use 16-bits, so that is our max number
* of batches we can have in one frame.
*/
if (will_ignore_batch (self))
return FALSE;
self->program_info = program;
batch = begin_next_batch (self);
batch->any.kind = GSK_GL_COMMAND_KIND_DRAW;
batch->any.program = program->program_id;
batch->any.next_batch_index = -1;
batch->any.viewport.width = width;
batch->any.viewport.height = height;
batch->draw.framebuffer = 0;
batch->draw.uniform_count = 0;
batch->draw.uniform_offset = self->batch_uniforms.len;
batch->draw.bind_count = 0;
batch->draw.bind_offset = self->batch_binds.len;
batch->draw.vbo_count = 0;
batch->draw.vbo_offset = gsk_gl_buffer_get_offset (&self->vertices);
self->fbo_max = MAX (self->fbo_max, batch->draw.framebuffer);
self->in_draw = TRUE;
return TRUE;
}
void
gsk_gl_command_queue_end_draw (GskGLCommandQueue *self)
{
GskGLCommandBatch *last_batch;
GskGLCommandBatch *batch;
g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
g_assert (self->batches.len > 0);
if (will_ignore_batch (self))
{
self->in_draw = FALSE;
return;
}
batch = gsk_gl_command_batches_tail (&self->batches);
g_assert (self->in_draw == TRUE);
g_assert (batch->any.kind == GSK_GL_COMMAND_KIND_DRAW);
if G_UNLIKELY (batch->draw.vbo_count == 0)
{
discard_batch (self);
self->in_draw = FALSE;
return;
}
/* Track the destination framebuffer in case it changed */
batch->draw.framebuffer = self->attachments->fbo.id;
self->attachments->fbo.changed = FALSE;
self->fbo_max = MAX (self->fbo_max, self->attachments->fbo.id);
/* Save our full uniform state for this draw so we can possibly
* reorder the draw later.
*/
batch->draw.uniform_offset = self->batch_uniforms.len;
batch->draw.uniform_count = snapshot_uniforms (self->uniforms, self->program_info, &self->batch_uniforms);
/* Track the bind attachments that changed */
if (self->program_info->has_attachments)
{
batch->draw.bind_offset = self->batch_binds.len;
batch->draw.bind_count = snapshot_attachments (self->attachments, &self->batch_binds);
}
else
{
batch->draw.bind_offset = 0;
batch->draw.bind_count = 0;
}
if (self->batches.len > 1)
last_batch = &self->batches.items[self->batches.len - 2];
else
last_batch = NULL;
/* Do simple chaining of draw to last batch. */
if (last_batch != NULL &&
last_batch->any.kind == GSK_GL_COMMAND_KIND_DRAW &&
last_batch->any.program == batch->any.program &&
last_batch->any.viewport.width == batch->any.viewport.width &&
last_batch->any.viewport.height == batch->any.viewport.height &&
last_batch->draw.framebuffer == batch->draw.framebuffer &&
last_batch->draw.vbo_offset + last_batch->draw.vbo_count == batch->draw.vbo_offset &&
last_batch->draw.vbo_count + batch->draw.vbo_count <= 0xffff &&
snapshots_equal (self, last_batch, batch))
{
last_batch->draw.vbo_count += batch->draw.vbo_count;
discard_batch (self);
}
else
{
enqueue_batch (self);
}
self->in_draw = FALSE;
self->program_info = NULL;
}
/**
* gsk_gl_command_queue_split_draw:
* @self a `GskGLCommandQueue`
*
* This function is like calling gsk_gl_command_queue_end_draw() followed by
* a gsk_gl_command_queue_begin_draw() with the same parameters as a
* previous begin draw (if shared uniforms where not changed further).
*
* This is useful to avoid comparisons inside of loops where we know shared
* uniforms are not changing.
*
* This generally should just be called from gsk_gl_program_split_draw()
* as that is where the begin/end flow happens from the render job.
*/
void
gsk_gl_command_queue_split_draw (GskGLCommandQueue *self)
{
GskGLCommandBatch *batch;
GskGLUniformProgram *program;
guint width;
guint height;
g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
g_assert (self->batches.len > 0);
if (will_ignore_batch (self))
return;
g_assert (self->in_draw == TRUE);
program = self->program_info;
batch = gsk_gl_command_batches_tail (&self->batches);
g_assert (batch->any.kind == GSK_GL_COMMAND_KIND_DRAW);
width = batch->any.viewport.width;
height = batch->any.viewport.height;
gsk_gl_command_queue_end_draw (self);
gsk_gl_command_queue_begin_draw (self, program, width, height);
}
void
gsk_gl_command_queue_clear (GskGLCommandQueue *self,
guint clear_bits,
const graphene_rect_t *viewport)
{
GskGLCommandBatch *batch;
g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
g_assert (self->in_draw == FALSE);
if (will_ignore_batch (self))
return;
if (clear_bits == 0)
clear_bits = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
batch = begin_next_batch (self);
batch->any.kind = GSK_GL_COMMAND_KIND_CLEAR;
batch->any.viewport.width = viewport->size.width;
batch->any.viewport.height = viewport->size.height;
batch->clear.bits = clear_bits;
batch->clear.framebuffer = self->attachments->fbo.id;
batch->any.next_batch_index = -1;
batch->any.program = 0;
self->fbo_max = MAX (self->fbo_max, batch->clear.framebuffer);
enqueue_batch (self);
self->attachments->fbo.changed = FALSE;
}
GdkGLContext *
gsk_gl_command_queue_get_context (GskGLCommandQueue *self)
{
g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (self), NULL);
return self->context;
}
void
gsk_gl_command_queue_make_current (GskGLCommandQueue *self)
{
g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
g_assert (GDK_IS_GL_CONTEXT (self->context));
gdk_gl_context_make_current (self->context);
}
void
gsk_gl_command_queue_delete_program (GskGLCommandQueue *self,
guint program)
{
g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
glDeleteProgram (program);
}
static inline void
apply_viewport (guint *current_width,
guint *current_height,
guint width,
guint height)
{
if G_UNLIKELY (*current_width != width || *current_height != height)
{
*current_width = width;
*current_height = height;
glViewport (0, 0, width, height);
}
}
static inline void
apply_scissor (gboolean *state,
guint framebuffer,
const graphene_rect_t *scissor,
gboolean has_scissor,
guint default_framebuffer)
{
g_assert (framebuffer != (guint)-1);
if (framebuffer != default_framebuffer || !has_scissor)
{
if (*state != FALSE)
{
glDisable (GL_SCISSOR_TEST);
*state = FALSE;
}
}
else
{
if (*state != TRUE)
{
glEnable (GL_SCISSOR_TEST);
glScissor (scissor->origin.x,
scissor->origin.y,
scissor->size.width,
scissor->size.height);
*state = TRUE;
}
}
}
static inline gboolean
apply_framebuffer (int *framebuffer,
guint new_framebuffer)
{
if G_UNLIKELY (new_framebuffer != *framebuffer)
{
*framebuffer = new_framebuffer;
glBindFramebuffer (GL_FRAMEBUFFER, new_framebuffer);
return TRUE;
}
return FALSE;
}
static inline void
gsk_gl_command_queue_unlink (GskGLCommandQueue *self,
GskGLCommandBatch *batch)
{
if (batch->any.prev_batch_index == -1)
self->head_batch_index = batch->any.next_batch_index;
else
self->batches.items[batch->any.prev_batch_index].any.next_batch_index = batch->any.next_batch_index;
if (batch->any.next_batch_index == -1)
self->tail_batch_index = batch->any.prev_batch_index;
else
self->batches.items[batch->any.next_batch_index].any.prev_batch_index = batch->any.prev_batch_index;
batch->any.prev_batch_index = -1;
batch->any.next_batch_index = -1;
}
static inline void
gsk_gl_command_queue_insert_before (GskGLCommandQueue *self,
GskGLCommandBatch *batch,
GskGLCommandBatch *sibling)
{
int sibling_index;
int index;
g_assert (batch >= self->batches.items);
g_assert (batch < &self->batches.items[self->batches.len]);
g_assert (sibling >= self->batches.items);
g_assert (sibling < &self->batches.items[self->batches.len]);
index = gsk_gl_command_batches_index_of (&self->batches, batch);
sibling_index = gsk_gl_command_batches_index_of (&self->batches, sibling);
batch->any.next_batch_index = sibling_index;
batch->any.prev_batch_index = sibling->any.prev_batch_index;
if (batch->any.prev_batch_index > -1)
self->batches.items[batch->any.prev_batch_index].any.next_batch_index = index;
sibling->any.prev_batch_index = index;
if (batch->any.prev_batch_index == -1)
self->head_batch_index = index;
}
static void
gsk_gl_command_queue_sort_batches (GskGLCommandQueue *self)
{
int *seen;
int *seen_free = NULL;
int index;
g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
g_assert (self->tail_batch_index >= 0);
g_assert (self->fbo_max >= 0);
/* Create our seen list with most recent index set to -1,
* meaning we haven't yet seen that framebuffer.
*/
if (self->fbo_max < 1024)
seen = g_alloca (sizeof (int) * (self->fbo_max + 1));
else
seen = seen_free = g_new0 (int, (self->fbo_max + 1));
for (int i = 0; i <= self->fbo_max; i++)
seen[i] = -1;
/* Walk in reverse, and if we've seen that framebuffer before, we want to
* delay this operation until right before the last batch we saw for that
* framebuffer.
*
* We can do this because we don't use a framebuffer's texture until it has
* been completely drawn.
*/
index = self->tail_batch_index;
while (index >= 0)
{
GskGLCommandBatch *batch = &self->batches.items[index];
int cur_index = index;
int fbo = -1;
g_assert (index > -1);
g_assert (index < self->batches.len);
switch (batch->any.kind)
{
case GSK_GL_COMMAND_KIND_DRAW:
fbo = batch->draw.framebuffer;
break;
case GSK_GL_COMMAND_KIND_CLEAR:
fbo = batch->clear.framebuffer;
break;
default:
g_assert_not_reached ();
}
index = batch->any.prev_batch_index;
g_assert (index >= -1);
g_assert (index < (int)self->batches.len);
g_assert (fbo >= -1);
if (fbo == -1)
continue;
g_assert (fbo <= self->fbo_max);
g_assert (seen[fbo] >= -1);
g_assert (seen[fbo] < (int)self->batches.len);
if (seen[fbo] != -1 && seen[fbo] != batch->any.next_batch_index)
{
int mru_index = seen[fbo];
GskGLCommandBatch *mru = &self->batches.items[mru_index];
g_assert (mru_index > -1);
gsk_gl_command_queue_unlink (self, batch);
g_assert (batch->any.prev_batch_index == -1);
g_assert (batch->any.next_batch_index == -1);
gsk_gl_command_queue_insert_before (self, batch, mru);
g_assert (batch->any.prev_batch_index > -1 ||
self->head_batch_index == cur_index);
g_assert (batch->any.next_batch_index == seen[fbo]);
}
g_assert (cur_index > -1);
g_assert (seen[fbo] >= -1);
seen[fbo] = cur_index;
}
g_free (seen_free);
}
/**
* gsk_gl_command_queue_execute:
* @self: a `GskGLCommandQueue`
* @surface_height: the height of the backing surface
* @scale: the scale of the backing surface
* @scissor: (nullable): the scissor clip if any
* @default_framebuffer: the default framebuffer id if not zero
*
* Executes all of the batches in the command queue.
*
* Typically, the scissor rect is only applied when rendering to the default
* framebuffer (zero in most cases). However, if @default_framebuffer is not
* zero, it will be checked to see if the rendering target matches so that
* the scissor rect is applied. This should be used in cases where rendering
* to the backbuffer for display is not the default GL framebuffer of zero.
* Currently, this happens when rendering on macOS using IOSurface.
*/
void
gsk_gl_command_queue_execute (GskGLCommandQueue *self,
guint surface_height,
float scale,
const cairo_region_t *scissor,
guint default_framebuffer)
{
G_GNUC_UNUSED guint count = 0;
graphene_rect_t scissor_test = GRAPHENE_RECT_INIT (0, 0, 0, 0);
gboolean has_scissor = scissor != NULL;
gboolean scissor_state = -1;
guint program = 0;
guint width = 0;
guint height = 0;
G_GNUC_UNUSED unsigned int n_binds = 0;
G_GNUC_UNUSED unsigned int n_fbos = 0;
G_GNUC_UNUSED unsigned int n_uniforms = 0;
G_GNUC_UNUSED unsigned int n_programs = 0;
guint vao_id;
guint vbo_id;
int textures[GSK_GL_MAX_TEXTURES_PER_PROGRAM];
int samplers[GSK_GL_MAX_TEXTURES_PER_PROGRAM];
int framebuffer = -1;
int next_batch_index;
int active = -1;
g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
g_assert (self->in_draw == FALSE);
if (self->batches.len == 0)
return;
for (guint i = 0; i < G_N_ELEMENTS (textures); i++)
textures[i] = -1;
for (guint i = 0; i < G_N_ELEMENTS (samplers); i++)
samplers[i] = -1;
gsk_gl_command_queue_sort_batches (self);
gsk_gl_command_queue_make_current (self);
#ifdef G_ENABLE_DEBUG
gsk_gl_profiler_begin_gpu_region (self->gl_profiler);
gsk_profiler_timer_begin (self->profiler, self->metrics.cpu_time);
#endif
glEnable (GL_DEPTH_TEST);
glDepthFunc (GL_LEQUAL);
/* Pre-multiplied alpha */
glEnable (GL_BLEND);
glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glBlendEquation (GL_FUNC_ADD);
if (!gdk_gl_context_get_use_es (self->context))
{
glGenVertexArrays (1, &vao_id);
glBindVertexArray (vao_id);
}
vbo_id = gsk_gl_buffer_submit (&self->vertices);
/* 0 = position location */
glEnableVertexAttribArray (0);
glVertexAttribPointer (0, 2, GL_FLOAT, GL_FALSE,
sizeof (GskGLDrawVertex),
(void *) G_STRUCT_OFFSET (GskGLDrawVertex, position));
/* 1 = texture coord location */
glEnableVertexAttribArray (1);
glVertexAttribPointer (1, 2, GL_FLOAT, GL_FALSE,
sizeof (GskGLDrawVertex),
(void *) G_STRUCT_OFFSET (GskGLDrawVertex, uv));
/* 2 = color location */
glEnableVertexAttribArray (2);
glVertexAttribPointer (2, 4, GL_HALF_FLOAT, GL_FALSE,
sizeof (GskGLDrawVertex),
(void *) G_STRUCT_OFFSET (GskGLDrawVertex, color));
/* 3 = color2 location */
glEnableVertexAttribArray (3);
glVertexAttribPointer (3, 4, GL_HALF_FLOAT, GL_FALSE,
sizeof (GskGLDrawVertex),
(void *) G_STRUCT_OFFSET (GskGLDrawVertex, color2));
/* Setup initial scissor clip */
if (scissor != NULL)
{
cairo_rectangle_int_t r;
g_assert (cairo_region_num_rectangles (scissor) == 1);
cairo_region_get_rectangle (scissor, 0, &r);
scissor_test.origin.x = (int) floor (r.x * scale);
scissor_test.origin.y = (int) floor (surface_height - (r.height * scale) - (r.y * scale));
scissor_test.size.width = (int) ceil (r.width * scale);
scissor_test.size.height = (int) ceil (r.height * scale);
}
next_batch_index = self->head_batch_index;
while (next_batch_index >= 0)
{
const GskGLCommandBatch *batch = &self->batches.items[next_batch_index];
g_assert (next_batch_index >= 0);
g_assert (next_batch_index < self->batches.len);
g_assert (batch->any.next_batch_index != next_batch_index);
count++;
switch (batch->any.kind)
{
case GSK_GL_COMMAND_KIND_CLEAR:
if (apply_framebuffer (&framebuffer, batch->clear.framebuffer))
{
apply_scissor (&scissor_state, framebuffer, &scissor_test, has_scissor, default_framebuffer);
n_fbos++;
}
apply_viewport (&width,
&height,
batch->any.viewport.width,
batch->any.viewport.height);
glClearColor (0, 0, 0, 0);
glClear (batch->clear.bits);
break;
case GSK_GL_COMMAND_KIND_DRAW:
if (batch->any.program != program)
{
program = batch->any.program;
glUseProgram (program);
n_programs++;
}
if (apply_framebuffer (&framebuffer, batch->draw.framebuffer))
{
apply_scissor (&scissor_state, framebuffer, &scissor_test, has_scissor, default_framebuffer);
n_fbos++;
}
apply_viewport (&width,
&height,
batch->any.viewport.width,
batch->any.viewport.height);
if G_UNLIKELY (batch->draw.bind_count > 0)
{
const GskGLCommandBind *bind = &self->batch_binds.items[batch->draw.bind_offset];
g_assert (bind->texture < G_N_ELEMENTS (textures));
for (guint i = 0; i < batch->draw.bind_count; i++)
{
if (textures[bind->texture] != bind->id)
{
GskGLSync *s;
if (active != bind->texture)
{
active = bind->texture;
glActiveTexture (GL_TEXTURE0 + bind->texture);
}
s = gsk_gl_syncs_get_sync (&self->syncs, bind->id);
if (s && s->sync)
{
glWaitSync ((GLsync) s->sync, 0, GL_TIMEOUT_IGNORED);
s->sync = NULL;
}
glBindTexture (GL_TEXTURE_2D, bind->id);
textures[bind->texture] = bind->id;
if (!self->has_samplers)
{
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter_from_index (bind->sampler / GSK_GL_N_FILTERS));
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter_from_index (bind->sampler % GSK_GL_N_FILTERS));
}
}
if (samplers[bind->texture] != bind->sampler)
{
if (self->has_samplers)
glBindSampler (bind->texture, self->samplers[bind->sampler]);
else
{
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter_from_index (bind->sampler / GSK_GL_N_FILTERS));
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter_from_index (bind->sampler % GSK_GL_N_FILTERS));
}
samplers[bind->texture] = bind->sampler;
}
bind++;
}
n_binds += batch->draw.bind_count;
}
if (batch->draw.uniform_count > 0)
{
const GskGLCommandUniform *u = &self->batch_uniforms.items[batch->draw.uniform_offset];
for (guint i = 0; i < batch->draw.uniform_count; i++, u++)
gsk_gl_uniform_state_apply (self->uniforms, program, u->location, u->info);
n_uniforms += batch->draw.uniform_count;
}
glDrawArrays (GL_TRIANGLES, batch->draw.vbo_offset, batch->draw.vbo_count);
break;
default:
g_assert_not_reached ();
}
#if 0
if (batch->any.kind == GSK_GL_COMMAND_KIND_DRAW ||
batch->any.kind == GSK_GL_COMMAND_KIND_CLEAR)
{
char filename[128];
g_snprintf (filename, sizeof filename,
"capture%03u_batch%03d_kind%u_program%u_u%u_b%u_fb%u_ctx%p.png",
count, next_batch_index,
batch->any.kind, batch->any.program,
batch->any.kind == GSK_GL_COMMAND_KIND_DRAW ? batch->draw.uniform_count : 0,
batch->any.kind == GSK_GL_COMMAND_KIND_DRAW ? batch->draw.bind_count : 0,
framebuffer,
gdk_gl_context_get_current ());
gsk_gl_command_queue_capture_png (self, filename, width, height, TRUE);
gsk_gl_command_queue_print_batch (self, batch);
}
#endif
next_batch_index = batch->any.next_batch_index;
}
glDeleteBuffers (1, &vbo_id);
if (!gdk_gl_context_get_use_es (self->context))
glDeleteVertexArrays (1, &vao_id);
gdk_profiler_set_int_counter (self->metrics.n_binds, n_binds);
gdk_profiler_set_int_counter (self->metrics.n_uniforms, n_uniforms);
gdk_profiler_set_int_counter (self->metrics.n_fbos, n_fbos);
gdk_profiler_set_int_counter (self->metrics.n_programs, n_programs);
gdk_profiler_set_int_counter (self->metrics.n_uploads, self->n_uploads);
gdk_profiler_set_int_counter (self->metrics.queue_depth, self->batches.len);
#ifdef G_ENABLE_DEBUG
{
gint64 start_time G_GNUC_UNUSED = gsk_profiler_timer_get_start (self->profiler, self->metrics.cpu_time);
gint64 cpu_time = gsk_profiler_timer_end (self->profiler, self->metrics.cpu_time);
gint64 gpu_time = gsk_gl_profiler_end_gpu_region (self->gl_profiler);
gsk_profiler_timer_set (self->profiler, self->metrics.gpu_time, gpu_time);
gsk_profiler_timer_set (self->profiler, self->metrics.cpu_time, cpu_time);
gsk_profiler_counter_inc (self->profiler, self->metrics.n_frames);
gsk_profiler_push_samples (self->profiler);
}
#endif
}
void
gsk_gl_command_queue_begin_frame (GskGLCommandQueue *self)
{
g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
g_assert (self->batches.len == 0);
gsk_gl_command_queue_make_current (self);
self->fbo_max = 0;
self->tail_batch_index = -1;
self->head_batch_index = -1;
self->in_frame = TRUE;
}
/**
* gsk_gl_command_queue_end_frame:
* @self: a `GskGLCommandQueue`
*
* This function performs cleanup steps that need to be done after
* a frame has finished. This is not performed as part of the command
* queue execution to allow for the frame to be submitted as soon
* as possible.
*
* However, it should be executed after the draw contexts end_frame
* has been called to swap the OpenGL framebuffers.
*/
void
gsk_gl_command_queue_end_frame (GskGLCommandQueue *self)
{
g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
gsk_gl_command_queue_make_current (self);
gsk_gl_uniform_state_end_frame (self->uniforms);
/* Reset attachments so we don't hold on to any textures
* that might be released after the frame.
*/
for (guint i = 0; i < G_N_ELEMENTS (self->attachments->textures); i++)
{
if (self->attachments->textures[i].id != 0)
{
glActiveTexture (GL_TEXTURE0 + i);
glBindTexture (GL_TEXTURE_2D, 0);
self->attachments->textures[i].id = 0;
self->attachments->textures[i].changed = FALSE;
self->attachments->textures[i].initial = TRUE;
}
}
self->batches.len = 0;
self->batch_binds.len = 0;
self->batch_uniforms.len = 0;
self->syncs.len = 0;
self->n_uploads = 0;
self->tail_batch_index = -1;
self->in_frame = FALSE;
}
gboolean
gsk_gl_command_queue_create_render_target (GskGLCommandQueue *self,
int width,
int height,
int format,
guint *out_fbo_id,
guint *out_texture_id)
{
GLuint fbo_id = 0;
GLint texture_id;
g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
g_assert (width > 0);
g_assert (height > 0);
g_assert (out_fbo_id != NULL);
g_assert (out_texture_id != NULL);
texture_id = gsk_gl_command_queue_create_texture (self,
width, height,
format);
if (texture_id == -1)
{
*out_fbo_id = 0;
*out_texture_id = 0;
return FALSE;
}
fbo_id = gsk_gl_command_queue_create_framebuffer (self);
glBindFramebuffer (GL_FRAMEBUFFER, fbo_id);
glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_id, 0);
g_assert_cmphex (glCheckFramebufferStatus (GL_FRAMEBUFFER), ==, GL_FRAMEBUFFER_COMPLETE);
*out_fbo_id = fbo_id;
*out_texture_id = texture_id;
return TRUE;
}
int
gsk_gl_command_queue_create_texture (GskGLCommandQueue *self,
int width,
int height,
int format)
{
GLuint texture_id = 0;
g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
if G_UNLIKELY (self->max_texture_size == -1)
glGetIntegerv (GL_MAX_TEXTURE_SIZE, &self->max_texture_size);
if (width > self->max_texture_size || height > self->max_texture_size)
return -1;
glGenTextures (1, &texture_id);
glActiveTexture (GL_TEXTURE0);
glBindTexture (GL_TEXTURE_2D, texture_id);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
switch (format)
{
case GL_RGBA8:
glTexImage2D (GL_TEXTURE_2D, 0, format, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
break;
case GL_RGBA16F:
glTexImage2D (GL_TEXTURE_2D, 0, format, width, height, 0, GL_RGBA, GL_HALF_FLOAT, NULL);
break;
case GL_RGBA32F:
glTexImage2D (GL_TEXTURE_2D, 0, format, width, height, 0, GL_RGBA, GL_FLOAT, NULL);
break;
default:
/* If you add new formats, make sure to set the correct format and type here
* so that GLES doesn't barf invalid operations at you.
* Because it is very important that these 3 values match when data is set to
* NULL, do you hear me?
*/
g_assert_not_reached ();
break;
}
/* Restore the previous texture if it was set */
if (self->attachments->textures[0].id != 0)
glBindTexture (GL_TEXTURE_2D, self->attachments->textures[0].id);
return (int)texture_id;
}
guint
gsk_gl_command_queue_create_framebuffer (GskGLCommandQueue *self)
{
GLuint fbo_id;
g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
glGenFramebuffers (1, &fbo_id);
return fbo_id;
}
static GdkMemoryFormat
memory_format_gl_format (GdkMemoryFormat data_format,
gboolean use_es,
guint major,
guint minor,
guint *gl_internalformat,
guint *gl_format,
guint *gl_type,
GLint (*gl_swizzle)[4])
{
if (gdk_memory_format_gl_format (data_format,
use_es,
major,
minor,
gl_internalformat,
gl_format,
gl_type,
gl_swizzle) &&
gdk_memory_format_alpha (data_format) != GDK_MEMORY_ALPHA_STRAIGHT)
return data_format;
if (gdk_memory_format_prefers_high_depth (data_format))
{
data_format = GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED;
if (gdk_memory_format_gl_format (data_format,
use_es,
major,
minor,
gl_internalformat,
gl_format,
gl_type,
gl_swizzle))
return data_format;
}
data_format = GDK_MEMORY_R8G8B8A8_PREMULTIPLIED;
if (!gdk_memory_format_gl_format (data_format,
use_es,
major,
minor,
gl_internalformat,
gl_format,
gl_type,
gl_swizzle))
{
g_assert_not_reached ();
}
return data_format;
}
static void
gsk_gl_command_queue_do_upload_texture_chunk (GskGLCommandQueue *self,
GdkTexture *texture,
int x,
int y)
{
const guchar *data;
gsize stride;
GBytes *bytes;
GdkTextureDownloader downloader;
GdkMemoryFormat data_format;
int width, height;
GLenum gl_internalformat;
GLenum gl_format;
GLenum gl_type;
GLint gl_swizzle[4];
gsize bpp;
gboolean use_es;
int major, minor;
use_es = gdk_gl_context_get_use_es (self->context);
gdk_gl_context_get_version (self->context, &major, &minor);
data_format = gdk_texture_get_format (texture);
width = gdk_texture_get_width (texture);
height = gdk_texture_get_height (texture);
data_format = memory_format_gl_format (data_format,
use_es,
major,
minor,
&gl_internalformat,
&gl_format,
&gl_type,
&gl_swizzle);
gdk_texture_downloader_init (&downloader, texture);
gdk_texture_downloader_set_format (&downloader, data_format);
bytes = gdk_texture_downloader_download_bytes (&downloader, &stride);
gdk_texture_downloader_finish (&downloader);
data = g_bytes_get_data (bytes, NULL);
bpp = gdk_memory_format_bytes_per_pixel (data_format);
glPixelStorei (GL_UNPACK_ALIGNMENT, gdk_memory_format_alignment (data_format));
/* GL_UNPACK_ROW_LENGTH is available on desktop GL, OpenGL ES >= 3.0, or if
* the GL_EXT_unpack_subimage extension for OpenGL ES 2.0 is available
*/
if (stride == width * bpp)
{
glTexSubImage2D (GL_TEXTURE_2D, 0, x, y, width, height, gl_format, gl_type, data);
}
else if (stride % bpp == 0 &&
(gdk_gl_context_check_version (self->context, NULL, "3.0") || gdk_gl_context_has_unpack_subimage (self->context)))
{
glPixelStorei (GL_UNPACK_ROW_LENGTH, stride / bpp);
glTexSubImage2D (GL_TEXTURE_2D, 0, x, y, width, height, gl_format, gl_type, data);
glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
}
else
{
for (int i = 0; i < height; i++)
glTexSubImage2D (GL_TEXTURE_2D, 0, x, y + i, width, 1, gl_format, gl_type, data + (i * stride));
}
glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
/* Only apply swizzle if really needed, might not even be
* supported if default values are set
*/
if (gl_swizzle[0] != GL_RED || gl_swizzle[1] != GL_GREEN || gl_swizzle[2] != GL_BLUE)
{
/* Set each channel independently since GLES 3.0 doesn't support the iv method */
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, gl_swizzle[0]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, gl_swizzle[1]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, gl_swizzle[2]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, gl_swizzle[3]);
}
g_bytes_unref (bytes);
}
int
gsk_gl_command_queue_upload_texture_chunks (GskGLCommandQueue *self,
unsigned int n_chunks,
GskGLTextureChunk *chunks)
{
G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME;
int width, height;
GdkMemoryFormat data_format;
GLenum gl_internalformat;
GLenum gl_format;
GLenum gl_type;
GLint gl_swizzle[4];
gboolean use_es;
int texture_id;
int major, minor;
g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
width = height = 0;
for (unsigned int i = 0; i < n_chunks; i++)
{
GskGLTextureChunk *c = &chunks[i];
width = MAX (width, c->x + gdk_texture_get_width (c->texture));
height = MAX (height, c->y + gdk_texture_get_height (c->texture));
}
if (width > self->max_texture_size || height > self->max_texture_size)
{
g_warning ("Attempt to create texture of size %ux%u but max size is %d. "
"Clipping will occur.",
width, height, self->max_texture_size);
width = MIN (width, self->max_texture_size);
height = MIN (height, self->max_texture_size);
}
texture_id = gsk_gl_command_queue_create_texture (self, width, height, GL_RGBA8);
if (texture_id == -1)
return texture_id;
self->n_uploads++;
/* Switch to texture0 as 2D. We'll restore it later. */
glActiveTexture (GL_TEXTURE0);
glBindTexture (GL_TEXTURE_2D, texture_id);
/* Initialize the texture */
use_es = gdk_gl_context_get_use_es (self->context);
gdk_gl_context_get_version (self->context, &major, &minor);
data_format = gdk_texture_get_format (chunks[0].texture);
data_format = memory_format_gl_format (data_format,
use_es,
major,
minor,
&gl_internalformat,
&gl_format,
&gl_type,
&gl_swizzle);
glTexImage2D (GL_TEXTURE_2D, 0, gl_internalformat, width, height, 0, gl_format, gl_type, NULL);
for (unsigned int i = 0; i < n_chunks; i++)
{
GskGLTextureChunk *c = &chunks[i];
gsk_gl_command_queue_do_upload_texture_chunk (self, c->texture, c->x, c->y);
}
/* Restore previous texture state if any */
if (self->attachments->textures[0].id > 0)
glBindTexture (self->attachments->textures[0].target,
self->attachments->textures[0].id);
if (gdk_profiler_is_running ())
gdk_profiler_add_markf (start_time, GDK_PROFILER_CURRENT_TIME-start_time,
"Upload Texture",
"Size %dx%d", width, height);
return texture_id;
}
int
gsk_gl_command_queue_upload_texture (GskGLCommandQueue *self,
GdkTexture *texture)
{
return gsk_gl_command_queue_upload_texture_chunks (self, 1, &(GskGLTextureChunk){ texture, 0, 0});
}
void
gsk_gl_command_queue_set_profiler (GskGLCommandQueue *self,
GskProfiler *profiler)
{
#ifdef G_ENABLE_DEBUG
g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
g_assert (GSK_IS_PROFILER (profiler));
if (g_set_object (&self->profiler, profiler))
{
self->gl_profiler = gsk_gl_profiler_new (self->context);
self->metrics.n_frames = gsk_profiler_add_counter (profiler, "frames", "Frames", FALSE);
self->metrics.cpu_time = gsk_profiler_add_timer (profiler, "cpu-time", "CPU Time", FALSE, TRUE);
self->metrics.gpu_time = gsk_profiler_add_timer (profiler, "gpu-time", "GPU Time", FALSE, TRUE);
self->metrics.n_binds = gdk_profiler_define_int_counter ("attachments", "Number of texture attachments");
self->metrics.n_fbos = gdk_profiler_define_int_counter ("fbos", "Number of framebuffers attached");
self->metrics.n_uniforms = gdk_profiler_define_int_counter ("uniforms", "Number of uniforms changed");
self->metrics.n_uploads = gdk_profiler_define_int_counter ("uploads", "Number of texture uploads");
self->metrics.n_programs = gdk_profiler_define_int_counter ("programs", "Number of program changes");
self->metrics.queue_depth = gdk_profiler_define_int_counter ("gl-queue-depth", "Depth of GL command batches");
}
#endif
}