/* gsknglcommandqueueprivate.h * * Copyright 2020 Christian Hergert * * 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 . * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __GSK_NGL_COMMAND_QUEUE_PRIVATE_H__ #define __GSK_NGL_COMMAND_QUEUE_PRIVATE_H__ #include #include "gskngltypesprivate.h" #include "gsknglbufferprivate.h" #include "gsknglattachmentstateprivate.h" #include "gskngluniformstateprivate.h" #include "inlinearray.h" #include "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; /* 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 n_programs; 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__ */