1 /*
2  * shaders.c
3  * Copyright (C) 2017 Kovid Goyal <kovid at kovidgoyal.net>
4  *
5  * Distributed under terms of the GPL3 license.
6  */
7 
8 #include "fonts.h"
9 #include "gl.h"
10 #include <stddef.h>
11 
12 enum { CELL_PROGRAM, CELL_BG_PROGRAM, CELL_SPECIAL_PROGRAM, CELL_FG_PROGRAM, BORDERS_PROGRAM, GRAPHICS_PROGRAM, GRAPHICS_PREMULT_PROGRAM, GRAPHICS_ALPHA_MASK_PROGRAM, BLIT_PROGRAM, BGIMAGE_PROGRAM, TINT_PROGRAM, NUM_PROGRAMS };
13 enum { SPRITE_MAP_UNIT, GRAPHICS_UNIT, BLIT_UNIT, BGIMAGE_UNIT };
14 
15 // Sprites {{{
16 typedef struct {
17     unsigned int cell_width, cell_height;
18     int xnum, ynum, x, y, z, last_num_of_layers, last_ynum;
19     GLuint texture_id;
20     GLint max_texture_size, max_array_texture_layers;
21 } SpriteMap;
22 
23 static const SpriteMap NEW_SPRITE_MAP = { .xnum = 1, .ynum = 1, .last_num_of_layers = 1, .last_ynum = -1 };
24 static GLint max_texture_size = 0, max_array_texture_layers = 0;
25 
26 SPRITE_MAP_HANDLE
alloc_sprite_map(unsigned int cell_width,unsigned int cell_height)27 alloc_sprite_map(unsigned int cell_width, unsigned int cell_height) {
28     if (!max_texture_size) {
29         glGetIntegerv(GL_MAX_TEXTURE_SIZE, &(max_texture_size));
30         glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &(max_array_texture_layers));
31 #ifdef __APPLE__
32         // Since on Apple we could have multiple GPUs, with different capabilities,
33         // upper bound the values according to the data from https://developer.apple.com/graphicsimaging/opengl/capabilities/
34         max_texture_size = MIN(8192, max_texture_size);
35         max_array_texture_layers = MIN(512, max_array_texture_layers);
36 #endif
37         sprite_tracker_set_limits(max_texture_size, max_array_texture_layers);
38     }
39     SpriteMap *ans = calloc(1, sizeof(SpriteMap));
40     if (!ans) fatal("Out of memory allocating a sprite map");
41     *ans = NEW_SPRITE_MAP;
42     ans->max_texture_size = max_texture_size;
43     ans->max_array_texture_layers = max_array_texture_layers;
44     ans->cell_width = cell_width; ans->cell_height = cell_height;
45     return (SPRITE_MAP_HANDLE)ans;
46 }
47 
48 SPRITE_MAP_HANDLE
free_sprite_map(SPRITE_MAP_HANDLE sm)49 free_sprite_map(SPRITE_MAP_HANDLE sm) {
50     SpriteMap *sprite_map = (SpriteMap*)sm;
51     if (sprite_map) {
52         if (sprite_map->texture_id) free_texture(&sprite_map->texture_id);
53         free(sprite_map);
54     }
55     return NULL;
56 }
57 
58 static bool copy_image_warned = false;
59 
60 static void
copy_image_sub_data(GLuint src_texture_id,GLuint dest_texture_id,unsigned int width,unsigned int height,unsigned int num_levels)61 copy_image_sub_data(GLuint src_texture_id, GLuint dest_texture_id, unsigned int width, unsigned int height, unsigned int num_levels) {
62     if (!GLAD_GL_ARB_copy_image) {
63         // ARB_copy_image not available, do a slow roundtrip copy
64         if (!copy_image_warned) {
65             copy_image_warned = true;
66             log_error("WARNING: Your system's OpenGL implementation does not have glCopyImageSubData, falling back to a slower implementation");
67         }
68         size_t sz = (size_t)width * height * num_levels;
69         pixel *src = malloc(sz * sizeof(pixel));
70         if (src == NULL) { fatal("Out of memory."); }
71         glBindTexture(GL_TEXTURE_2D_ARRAY, src_texture_id);
72         glGetTexImage(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, GL_UNSIGNED_BYTE, src);
73         glBindTexture(GL_TEXTURE_2D_ARRAY, dest_texture_id);
74         glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
75         glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, num_levels, GL_RGBA, GL_UNSIGNED_BYTE, src);
76         free(src);
77     } else {
78         glCopyImageSubData(src_texture_id, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, dest_texture_id, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, num_levels);
79     }
80 }
81 
82 
83 static void
realloc_sprite_texture(FONTS_DATA_HANDLE fg)84 realloc_sprite_texture(FONTS_DATA_HANDLE fg) {
85     GLuint tex;
86     glGenTextures(1, &tex);
87     glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
88     // We use GL_NEAREST otherwise glyphs that touch the edge of the cell
89     // often show a border between cells
90     glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
91     glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
92     glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
93     glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
94     unsigned int xnum, ynum, z, znum, width, height, src_ynum;
95     sprite_tracker_current_layout(fg, &xnum, &ynum, &z);
96     znum = z + 1;
97     SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map;
98     width = xnum * sprite_map->cell_width; height = ynum * sprite_map->cell_height;
99     glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, width, height, znum);
100     if (sprite_map->texture_id) {
101         // need to re-alloc
102         src_ynum = MAX(1, sprite_map->last_ynum);
103         copy_image_sub_data(sprite_map->texture_id, tex, width, src_ynum * sprite_map->cell_height, sprite_map->last_num_of_layers);
104         glDeleteTextures(1, &sprite_map->texture_id);
105     }
106     glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
107     sprite_map->last_num_of_layers = znum;
108     sprite_map->last_ynum = ynum;
109     sprite_map->texture_id = tex;
110 }
111 
112 static void
ensure_sprite_map(FONTS_DATA_HANDLE fg)113 ensure_sprite_map(FONTS_DATA_HANDLE fg) {
114     SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map;
115     if (!sprite_map->texture_id) realloc_sprite_texture(fg);
116     // We have to rebind since we don't know if the texture was ever bound
117     // in the context of the current OSWindow
118     glActiveTexture(GL_TEXTURE0 + SPRITE_MAP_UNIT);
119     glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map->texture_id);
120 }
121 
122 void
send_sprite_to_gpu(FONTS_DATA_HANDLE fg,unsigned int x,unsigned int y,unsigned int z,pixel * buf)123 send_sprite_to_gpu(FONTS_DATA_HANDLE fg, unsigned int x, unsigned int y, unsigned int z, pixel *buf) {
124     SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map;
125     unsigned int xnum, ynum, znum;
126     sprite_tracker_current_layout(fg, &xnum, &ynum, &znum);
127     if ((int)znum >= sprite_map->last_num_of_layers || (znum == 0 && (int)ynum > sprite_map->last_ynum)) realloc_sprite_texture(fg);
128     glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map->texture_id);
129     glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
130     x *= sprite_map->cell_width; y *= sprite_map->cell_height;
131     glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, x, y, z, sprite_map->cell_width, sprite_map->cell_height, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, buf);
132 }
133 
134 void
send_image_to_gpu(GLuint * tex_id,const void * data,GLsizei width,GLsizei height,bool is_opaque,bool is_4byte_aligned,bool linear,RepeatStrategy repeat)135 send_image_to_gpu(GLuint *tex_id, const void* data, GLsizei width, GLsizei height, bool is_opaque, bool is_4byte_aligned, bool linear, RepeatStrategy repeat) {
136     if (!(*tex_id)) { glGenTextures(1, tex_id);  }
137     glBindTexture(GL_TEXTURE_2D, *tex_id);
138     glPixelStorei(GL_UNPACK_ALIGNMENT, is_4byte_aligned ? 4 : 1);
139     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, linear ? GL_LINEAR : GL_NEAREST);
140     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, linear ? GL_LINEAR : GL_NEAREST);
141     RepeatStrategy r;
142     switch (repeat) {
143         case REPEAT_MIRROR:
144             r = GL_MIRRORED_REPEAT; break;
145         case REPEAT_CLAMP:
146             r = GL_CLAMP_TO_EDGE; break;
147         default:
148             r = GL_REPEAT;
149     }
150     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, r);
151     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, r);
152     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, is_opaque ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, data);
153 }
154 
155 // }}}
156 
157 // Cell {{{
158 
159 typedef struct {
160     UniformBlock render_data;
161     ArrayInformation color_table;
162     GLint draw_bg_bitfield_location;
163 } CellProgramLayout;
164 
165 static CellProgramLayout cell_program_layouts[NUM_PROGRAMS];
166 static ssize_t blit_vertex_array;
167 typedef struct {
168     GLint image_location, tiled_location, sizes_location, opacity_location, premult_location;
169 } BGImageProgramLayout;
170 static BGImageProgramLayout bgimage_program_layout = {0};
171 typedef struct {
172     GLint tint_color_location, edges_location;
173 } TintProgramLayout;
174 static TintProgramLayout tint_program_layout = {0};
175 
176 static void
init_cell_program(void)177 init_cell_program(void) {
178     for (int i = CELL_PROGRAM; i < BORDERS_PROGRAM; i++) {
179         cell_program_layouts[i].render_data.index = block_index(i, "CellRenderData");
180         cell_program_layouts[i].render_data.size = block_size(i, cell_program_layouts[i].render_data.index);
181         cell_program_layouts[i].color_table.size = get_uniform_information(i, "color_table[0]", GL_UNIFORM_SIZE);
182         cell_program_layouts[i].color_table.offset = get_uniform_information(i, "color_table[0]", GL_UNIFORM_OFFSET);
183         cell_program_layouts[i].color_table.stride = get_uniform_information(i, "color_table[0]", GL_UNIFORM_ARRAY_STRIDE);
184     }
185     cell_program_layouts[CELL_BG_PROGRAM].draw_bg_bitfield_location = get_uniform_location(CELL_BG_PROGRAM, "draw_bg_bitfield");
186     // Sanity check to ensure the attribute location binding worked
187 #define C(p, name, expected) { int aloc = attrib_location(p, #name); if (aloc != expected && aloc != -1) fatal("The attribute location for %s is %d != %d in program: %d", #name, aloc, expected, p); }
188     for (int p = CELL_PROGRAM; p < BORDERS_PROGRAM; p++) {
189         C(p, colors, 0); C(p, sprite_coords, 1); C(p, is_selected, 2);
190     }
191 #undef C
192     blit_vertex_array = create_vao();
193     bgimage_program_layout.image_location = get_uniform_location(BGIMAGE_PROGRAM, "image");
194     bgimage_program_layout.opacity_location = get_uniform_location(BGIMAGE_PROGRAM, "opacity");
195     bgimage_program_layout.sizes_location = get_uniform_location(BGIMAGE_PROGRAM, "sizes");
196     bgimage_program_layout.tiled_location = get_uniform_location(BGIMAGE_PROGRAM, "tiled");
197     bgimage_program_layout.premult_location = get_uniform_location(BGIMAGE_PROGRAM, "premult");
198     tint_program_layout.tint_color_location = get_uniform_location(TINT_PROGRAM, "tint_color");
199     tint_program_layout.edges_location = get_uniform_location(TINT_PROGRAM, "edges");
200 }
201 
202 #define CELL_BUFFERS enum { cell_data_buffer, selection_buffer, uniform_buffer };
203 
204 ssize_t
create_cell_vao()205 create_cell_vao() {
206     ssize_t vao_idx = create_vao();
207 #define A(name, size, dtype, offset, stride) \
208     add_attribute_to_vao(CELL_PROGRAM, vao_idx, #name, \
209             /*size=*/size, /*dtype=*/dtype, /*stride=*/stride, /*offset=*/offset, /*divisor=*/1);
210 #define A1(name, size, dtype, offset) A(name, size, dtype, (void*)(offsetof(GPUCell, offset)), sizeof(GPUCell))
211 
212     add_buffer_to_vao(vao_idx, GL_ARRAY_BUFFER);
213     A1(sprite_coords, 4, GL_UNSIGNED_SHORT, sprite_x);
214     A1(colors, 3, GL_UNSIGNED_INT, fg);
215 
216     add_buffer_to_vao(vao_idx, GL_ARRAY_BUFFER);
217     A(is_selected, 1, GL_UNSIGNED_BYTE, NULL, 0);
218 
219     size_t bufnum = add_buffer_to_vao(vao_idx, GL_UNIFORM_BUFFER);
220     alloc_vao_buffer(vao_idx, cell_program_layouts[CELL_PROGRAM].render_data.size, bufnum, GL_STREAM_DRAW);
221 
222     return vao_idx;
223 #undef A
224 #undef A1
225 }
226 
227 ssize_t
create_graphics_vao()228 create_graphics_vao() {
229     ssize_t vao_idx = create_vao();
230     add_buffer_to_vao(vao_idx, GL_ARRAY_BUFFER);
231     add_attribute_to_vao(GRAPHICS_PROGRAM, vao_idx, "src", 4, GL_FLOAT, 0, NULL, 0);
232     return vao_idx;
233 }
234 
235 struct CellUniformData {
236     bool constants_set;
237     bool alpha_mask_fg_set;
238     GLint gploc, gpploc, cploc, cfploc, fg_loc, amask_premult_loc;
239     GLfloat prev_inactive_text_alpha;
240 };
241 
242 static struct CellUniformData cell_uniform_data = {0, .prev_inactive_text_alpha=-1};
243 
244 static void
send_graphics_data_to_gpu(size_t image_count,ssize_t gvao_idx,const ImageRenderData * render_data)245 send_graphics_data_to_gpu(size_t image_count, ssize_t gvao_idx, const ImageRenderData *render_data) {
246     size_t sz = sizeof(GLfloat) * 16 * image_count;
247     GLfloat *a = alloc_and_map_vao_buffer(gvao_idx, sz, 0, GL_STREAM_DRAW, GL_WRITE_ONLY);
248     for (size_t i = 0; i < image_count; i++, a += 16) memcpy(a, render_data[i].vertices, sizeof(render_data[0].vertices));
249     unmap_vao_buffer(gvao_idx, 0); a = NULL;
250 }
251 
252 static void
cell_update_uniform_block(ssize_t vao_idx,Screen * screen,int uniform_buffer,GLfloat xstart,GLfloat ystart,GLfloat dx,GLfloat dy,CursorRenderInfo * cursor,bool inverted,OSWindow * os_window)253 cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, CursorRenderInfo *cursor, bool inverted, OSWindow *os_window) {
254     struct CellRenderData {
255         GLfloat xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity, cursor_text_uses_bg;
256 
257         GLuint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, cursor_text_color, url_color, url_style, inverted;
258 
259         GLuint xnum, ynum, cursor_fg_sprite_idx;
260         GLfloat cursor_x, cursor_y, cursor_w;
261     };
262     static struct CellRenderData *rd;
263 
264     // Send the uniform data
265     rd = (struct CellRenderData*)map_vao_buffer(vao_idx, uniform_buffer, GL_WRITE_ONLY);
266     if (UNLIKELY(screen->color_profile->dirty || screen->reload_all_gpu_data)) {
267         copy_color_table_to_buffer(screen->color_profile, (GLuint*)rd, cell_program_layouts[CELL_PROGRAM].color_table.offset / sizeof(GLuint), cell_program_layouts[CELL_PROGRAM].color_table.stride / sizeof(GLuint));
268     }
269     // Cursor position
270     enum { BLOCK_IDX = 0, BEAM_IDX = 6, UNDERLINE_IDX = 7, UNFOCUSED_IDX = 8 };
271     if (cursor->is_visible) {
272         rd->cursor_x = screen->cursor->x, rd->cursor_y = screen->cursor->y;
273         if (cursor->is_focused) {
274             switch(cursor->shape) {
275                 default:
276                     rd->cursor_fg_sprite_idx = BLOCK_IDX; break;
277                 case CURSOR_BEAM:
278                     rd->cursor_fg_sprite_idx = BEAM_IDX; break;
279                 case CURSOR_UNDERLINE:
280                     rd->cursor_fg_sprite_idx = UNDERLINE_IDX; break;
281             }
282         } else rd->cursor_fg_sprite_idx = UNFOCUSED_IDX;
283     } else rd->cursor_x = screen->columns, rd->cursor_y = screen->lines;
284     rd->cursor_w = rd->cursor_x;
285     if (
286             (rd->cursor_fg_sprite_idx == BLOCK_IDX || rd->cursor_fg_sprite_idx == UNDERLINE_IDX) &&
287             screen_current_char_width(screen) > 1
288     ) rd->cursor_w += 1;
289 
290     rd->xnum = screen->columns; rd->ynum = screen->lines;
291 
292     rd->xstart = xstart; rd->ystart = ystart; rd->dx = dx; rd->dy = dy;
293     unsigned int x, y, z;
294     sprite_tracker_current_layout(os_window->fonts_data, &x, &y, &z);
295     rd->sprite_dx = 1.0f / (float)x; rd->sprite_dy = 1.0f / (float)y;
296     rd->inverted = inverted ? 1 : 0;
297     rd->background_opacity = os_window->is_semi_transparent ? os_window->background_opacity : 1.0f;
298 
299 #define COLOR(name) colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.name, screen->color_profile->configured.name)
300     rd->default_fg = COLOR(default_fg); rd->default_bg = COLOR(default_bg); rd->highlight_fg = COLOR(highlight_fg); rd->highlight_bg = COLOR(highlight_bg);
301     rd->cursor_text_color = COLOR(cursor_text_color);
302 #undef COLOR
303     rd->cursor_color = cursor->color; rd->url_color = OPT(url_color); rd->url_style = OPT(url_style);
304     rd->cursor_text_uses_bg = cursor_text_as_bg(screen->color_profile);
305 
306     unmap_vao_buffer(vao_idx, uniform_buffer); rd = NULL;
307 }
308 
309 static bool
cell_prepare_to_render(ssize_t vao_idx,ssize_t gvao_idx,Screen * screen,GLfloat xstart,GLfloat ystart,GLfloat dx,GLfloat dy,FONTS_DATA_HANDLE fonts_data)310 cell_prepare_to_render(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, FONTS_DATA_HANDLE fonts_data) {
311     size_t sz;
312     CELL_BUFFERS;
313     void *address;
314     bool changed = false;
315 
316     ensure_sprite_map(fonts_data);
317 
318     bool cursor_pos_changed = screen->cursor->x != screen->last_rendered.cursor_x
319                            || screen->cursor->y != screen->last_rendered.cursor_y;
320     bool disable_ligatures = screen->disable_ligatures == DISABLE_LIGATURES_CURSOR;
321     bool screen_resized = screen->last_rendered.columns != screen->columns || screen->last_rendered.lines != screen->lines;
322 
323     if (screen->reload_all_gpu_data || screen->scroll_changed || screen->is_dirty || screen_resized || (disable_ligatures && cursor_pos_changed)) {
324         sz = sizeof(GPUCell) * screen->lines * screen->columns;
325         address = alloc_and_map_vao_buffer(vao_idx, sz, cell_data_buffer, GL_STREAM_DRAW, GL_WRITE_ONLY);
326         screen_update_cell_data(screen, address, fonts_data, disable_ligatures && cursor_pos_changed);
327         unmap_vao_buffer(vao_idx, cell_data_buffer); address = NULL;
328         changed = true;
329     }
330 
331     if (cursor_pos_changed) {
332         screen->last_rendered.cursor_x = screen->cursor->x;
333         screen->last_rendered.cursor_y = screen->cursor->y;
334     }
335 
336     if (screen->reload_all_gpu_data || screen_resized || screen_is_selection_dirty(screen)) {
337         sz = (size_t)screen->lines * screen->columns;
338         address = alloc_and_map_vao_buffer(vao_idx, sz, selection_buffer, GL_STREAM_DRAW, GL_WRITE_ONLY);
339         screen_apply_selection(screen, address, sz);
340         unmap_vao_buffer(vao_idx, selection_buffer); address = NULL;
341         changed = true;
342     }
343 
344     if (gvao_idx && grman_update_layers(screen->grman, screen->scrolled_by, xstart, ystart, dx, dy, screen->columns, screen->lines, screen->cell_size)) {
345         send_graphics_data_to_gpu(screen->grman->count, gvao_idx, screen->grman->render_data);
346         changed = true;
347     }
348     screen->last_rendered.scrolled_by = screen->scrolled_by;
349     screen->last_rendered.columns = screen->columns;
350     screen->last_rendered.lines = screen->lines;
351     return changed;
352 }
353 
354 static void
draw_bg(OSWindow * w)355 draw_bg(OSWindow *w) {
356     blank_canvas(w->is_semi_transparent ? OPT(background_opacity) : 1.0f, OPT(background));
357     bind_program(BGIMAGE_PROGRAM);
358     bind_vertex_array(blit_vertex_array);
359 
360     static bool bgimage_constants_set = false;
361     if (!bgimage_constants_set) {
362         glUniform1i(bgimage_program_layout.image_location, BGIMAGE_UNIT);
363         glUniform1f(bgimage_program_layout.opacity_location, OPT(background_opacity));
364         GLfloat tiled = (OPT(background_image_layout) == TILING || OPT(background_image_layout) == MIRRORED) ? 1 : 0;
365         glUniform1f(bgimage_program_layout.tiled_location, tiled);
366         bgimage_constants_set = true;
367     }
368     glUniform4f(bgimage_program_layout.sizes_location,
369         (GLfloat)w->window_width, (GLfloat)w->window_height, (GLfloat)w->bgimage->width, (GLfloat)w->bgimage->height);
370     glUniform1f(bgimage_program_layout.premult_location, w->is_semi_transparent ? 1.f : 0.f);
371     glActiveTexture(GL_TEXTURE0 + BGIMAGE_UNIT);
372     glBindTexture(GL_TEXTURE_2D, w->bgimage->texture_id);
373     glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
374     unbind_vertex_array();
375     unbind_program();
376 }
377 
378 static void
draw_graphics(int program,ssize_t vao_idx,ssize_t gvao_idx,ImageRenderData * data,GLuint start,GLuint count)379 draw_graphics(int program, ssize_t vao_idx, ssize_t gvao_idx, ImageRenderData *data, GLuint start, GLuint count) {
380     bind_program(program);
381     bind_vertex_array(gvao_idx);
382     glActiveTexture(GL_TEXTURE0 + GRAPHICS_UNIT);
383 
384     GLuint base = 4 * start;
385     glEnable(GL_SCISSOR_TEST);
386     for (GLuint i=0; i < count;) {
387         ImageRenderData *rd = data + start + i;
388         glBindTexture(GL_TEXTURE_2D, rd->texture_id);
389         // You could reduce the number of draw calls by using
390         // glDrawArraysInstancedBaseInstance but Apple chose to abandon OpenGL
391         // before implementing it.
392         for (GLuint k=0; k < rd->group_count; k++, base += 4, i++) glDrawArrays(GL_TRIANGLE_FAN, base, 4);
393     }
394     glDisable(GL_SCISSOR_TEST);
395     bind_vertex_array(vao_idx);
396 }
397 
398 #define BLEND_ONTO_OPAQUE  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);  // blending onto opaque colors
399 #define BLEND_PREMULT glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);  // blending of pre-multiplied colors
400 
401 void
draw_centered_alpha_mask(OSWindow * os_window,size_t screen_width,size_t screen_height,size_t width,size_t height,uint8_t * canvas)402 draw_centered_alpha_mask(OSWindow *os_window, size_t screen_width, size_t screen_height, size_t width, size_t height, uint8_t *canvas) {
403     static ImageRenderData data = {.group_count=1};
404     gpu_data_for_centered_image(&data, screen_width, screen_height, width, height);
405     if (!data.texture_id) { glGenTextures(1, &data.texture_id); }
406     glBindTexture(GL_TEXTURE_2D, data.texture_id);
407     glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
408     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
409     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
410     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
411     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
412     glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, canvas);
413     bind_program(GRAPHICS_ALPHA_MASK_PROGRAM);
414     if (!cell_uniform_data.alpha_mask_fg_set) {
415         cell_uniform_data.alpha_mask_fg_set = true;
416         glUniform1i(glGetUniformLocation(program_id(GRAPHICS_ALPHA_MASK_PROGRAM), "image"), GRAPHICS_UNIT);
417         glUniform1ui(glGetUniformLocation(program_id(GRAPHICS_ALPHA_MASK_PROGRAM), "fg"), OPT(foreground));
418     }
419     glUniform1f(cell_uniform_data.amask_premult_loc, os_window->is_semi_transparent ? 1.f : 0.f);
420     send_graphics_data_to_gpu(1, os_window->gvao_idx, &data);
421     glEnable(GL_BLEND);
422     if (os_window->is_semi_transparent) {
423         BLEND_PREMULT;
424     } else {
425         BLEND_ONTO_OPAQUE;
426     }
427     draw_graphics(GRAPHICS_ALPHA_MASK_PROGRAM, 0, os_window->gvao_idx, &data, 0, 1);
428     glDisable(GL_BLEND);
429 }
430 
431 static void
draw_cells_simple(ssize_t vao_idx,ssize_t gvao_idx,Screen * screen)432 draw_cells_simple(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen) {
433     bind_program(CELL_PROGRAM);
434     glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
435     if (screen->grman->count) {
436         glEnable(GL_BLEND);
437         BLEND_ONTO_OPAQUE;
438         draw_graphics(GRAPHICS_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, 0, screen->grman->count);
439         glDisable(GL_BLEND);
440     }
441 }
442 
443 static bool
has_bgimage(OSWindow * w)444 has_bgimage(OSWindow *w) {
445     return w->bgimage && w->bgimage->texture_id > 0;
446 }
447 
448 static void
draw_tint(bool premult,Screen * screen,GLfloat xstart,GLfloat ystart,GLfloat width,GLfloat height)449 draw_tint(bool premult, Screen *screen, GLfloat xstart, GLfloat ystart, GLfloat width, GLfloat height) {
450     bind_program(TINT_PROGRAM);
451     color_type window_bg = colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.default_bg, screen->color_profile->configured.default_bg);
452 #define C(shift) ((((GLfloat)((window_bg >> shift) & 0xFF)) / 255.0f))
453     float alpha = OPT(background_tint);
454     if (premult) glUniform4f(tint_program_layout.tint_color_location, C(16) * alpha, C(8) * alpha, C(0) * alpha, alpha);
455     else glUniform4f(tint_program_layout.tint_color_location, C(16), C(8), C(0), alpha);
456 #undef C
457     glUniform4f(tint_program_layout.edges_location, xstart, ystart - height, xstart + width, ystart);
458     glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
459 }
460 
461 static void
draw_visual_bell_flash(GLfloat intensity,GLfloat xstart,GLfloat ystart,GLfloat w,GLfloat h,Screen * screen)462 draw_visual_bell_flash(GLfloat intensity, GLfloat xstart, GLfloat ystart, GLfloat w, GLfloat h, Screen *screen) {
463     glEnable(GL_BLEND);
464     // BLEND_PREMULT
465     glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
466     bind_program(TINT_PROGRAM);
467     GLfloat attenuation = 0.4f;
468     const color_type flash = colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.highlight_bg, screen->color_profile->configured.highlight_bg);
469 #define C(shift) ((((GLfloat)((flash >> shift) & 0xFF)) / 255.0f) )
470     const GLfloat r = C(16), g = C(8), b = C(0);
471     const GLfloat max_channel = r > g ? (r > b ? r : b) : (g > b ? g : b);
472 #undef C
473 #define C(x) (x * intensity * attenuation)
474     if (max_channel > 0.45) attenuation = 0.6f;  // light color
475     glUniform4f(tint_program_layout.tint_color_location, C(r), C(g), C(b), C(1));
476 #undef C
477     glUniform4f(tint_program_layout.edges_location, xstart, ystart - h, xstart + w, ystart);
478     glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
479     glDisable(GL_BLEND);
480 }
481 
482 static void
draw_cells_interleaved(ssize_t vao_idx,ssize_t gvao_idx,Screen * screen,OSWindow * w,GLfloat xstart,GLfloat ystart,GLfloat width,GLfloat height)483 draw_cells_interleaved(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, OSWindow *w, GLfloat xstart, GLfloat ystart, GLfloat width, GLfloat height) {
484     glEnable(GL_BLEND);
485     BLEND_ONTO_OPAQUE;
486 
487     // draw background for all cells
488     if (!has_bgimage(w)) {
489         bind_program(CELL_BG_PROGRAM);
490         glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].draw_bg_bitfield_location, 3);
491         glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
492     } else if (OPT(background_tint) > 0) {
493         glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
494         draw_tint(false, screen, xstart, ystart, width, height);
495         BLEND_ONTO_OPAQUE;
496     }
497 
498     if (screen->grman->num_of_below_refs || has_bgimage(w)) {
499         if (screen->grman->num_of_below_refs) draw_graphics(
500                 GRAPHICS_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, 0, screen->grman->num_of_below_refs);
501         bind_program(CELL_BG_PROGRAM);
502         // draw background for non-default bg cells
503         glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].draw_bg_bitfield_location, 2);
504         glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
505     }
506 
507     if (screen->grman->num_of_negative_refs) draw_graphics(GRAPHICS_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, screen->grman->num_of_below_refs, screen->grman->num_of_negative_refs);
508 
509     bind_program(CELL_SPECIAL_PROGRAM);
510     glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
511 
512     bind_program(CELL_FG_PROGRAM);
513     glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
514 
515     if (screen->grman->num_of_positive_refs) draw_graphics(GRAPHICS_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, screen->grman->num_of_negative_refs + screen->grman->num_of_below_refs, screen->grman->num_of_positive_refs);
516 
517     glDisable(GL_BLEND);
518 }
519 
520 static void
draw_cells_interleaved_premult(ssize_t vao_idx,ssize_t gvao_idx,Screen * screen,OSWindow * os_window,GLfloat xstart,GLfloat ystart,GLfloat width,GLfloat height)521 draw_cells_interleaved_premult(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, OSWindow *os_window, GLfloat xstart, GLfloat ystart, GLfloat width, GLfloat height) {
522     if (OPT(background_tint) > 0.f) {
523         glEnable(GL_BLEND);
524         BLEND_PREMULT;
525         draw_tint(true, screen, xstart, ystart, width, height);
526         glDisable(GL_BLEND);
527     }
528     if (!os_window->offscreen_texture_id) {
529         glGenFramebuffers(1, &os_window->offscreen_framebuffer);
530         glGenTextures(1, &os_window->offscreen_texture_id);
531         glBindTexture(GL_TEXTURE_2D, os_window->offscreen_texture_id);
532         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, os_window->viewport_width, os_window->viewport_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
533         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
534         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
535         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
536         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
537     }
538     glBindTexture(GL_TEXTURE_2D, 0);
539     glBindFramebuffer(GL_DRAW_FRAMEBUFFER, os_window->offscreen_framebuffer);
540     glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, os_window->offscreen_texture_id, 0);
541     /* if (glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) fatal("Offscreen framebuffer not complete"); */
542     bind_program(CELL_BG_PROGRAM);
543     if (!has_bgimage(os_window)) {
544         // draw background for all cells
545         glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].draw_bg_bitfield_location, 3);
546         glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
547     } else blank_canvas(0, 0);
548     glEnable(GL_BLEND);
549     BLEND_PREMULT;
550 
551     if (screen->grman->num_of_below_refs || has_bgimage(os_window)) {
552         if (screen->grman->num_of_below_refs) draw_graphics(
553             GRAPHICS_PREMULT_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, 0, screen->grman->num_of_below_refs);
554         bind_program(CELL_BG_PROGRAM);
555         // Draw background for non-default bg cells
556         glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].draw_bg_bitfield_location, 2);
557         glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
558     } else {
559         // Apply background_opacity
560         glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].draw_bg_bitfield_location, 0);
561         glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
562     }
563 
564     if (screen->grman->num_of_negative_refs) {
565         draw_graphics(GRAPHICS_PREMULT_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, screen->grman->num_of_below_refs, screen->grman->num_of_negative_refs);
566     }
567 
568     bind_program(CELL_SPECIAL_PROGRAM);
569     glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
570 
571     bind_program(CELL_FG_PROGRAM);
572     glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
573 
574     if (screen->grman->num_of_positive_refs) draw_graphics(GRAPHICS_PREMULT_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, screen->grman->num_of_negative_refs + screen->grman->num_of_below_refs, screen->grman->num_of_positive_refs);
575 
576     glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
577     if (!has_bgimage(os_window)) glDisable(GL_BLEND);
578     glEnable(GL_SCISSOR_TEST);
579 
580     // Now render the framebuffer to the screen
581     bind_program(BLIT_PROGRAM); bind_vertex_array(blit_vertex_array);
582     static bool blit_constants_set = false;
583     if (!blit_constants_set) {
584         glUniform1i(glGetUniformLocation(program_id(BLIT_PROGRAM), "image"), BLIT_UNIT);
585         blit_constants_set = true;
586     }
587     glActiveTexture(GL_TEXTURE0 + BLIT_UNIT);
588     glBindTexture(GL_TEXTURE_2D, os_window->offscreen_texture_id);
589     glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
590     glDisable(GL_SCISSOR_TEST);
591     glDisable(GL_BLEND);
592 }
593 
594 static void
set_cell_uniforms(float current_inactive_text_alpha,bool force)595 set_cell_uniforms(float current_inactive_text_alpha, bool force) {
596     if (!cell_uniform_data.constants_set || force) {
597         cell_uniform_data.gploc = glGetUniformLocation(program_id(GRAPHICS_PROGRAM), "inactive_text_alpha");
598         cell_uniform_data.gpploc = glGetUniformLocation(program_id(GRAPHICS_PREMULT_PROGRAM), "inactive_text_alpha");
599         cell_uniform_data.cploc = glGetUniformLocation(program_id(CELL_PROGRAM), "inactive_text_alpha");
600         cell_uniform_data.cfploc = glGetUniformLocation(program_id(CELL_FG_PROGRAM), "inactive_text_alpha");
601         cell_uniform_data.amask_premult_loc = glGetUniformLocation(program_id(GRAPHICS_ALPHA_MASK_PROGRAM), "alpha_mask_premult");
602 #define S(prog, name, val, type) { bind_program(prog); glUniform##type(glGetUniformLocation(program_id(prog), #name), val); }
603         S(GRAPHICS_PROGRAM, image, GRAPHICS_UNIT, 1i);
604         S(GRAPHICS_PREMULT_PROGRAM, image, GRAPHICS_UNIT, 1i);
605         S(CELL_PROGRAM, sprites, SPRITE_MAP_UNIT, 1i); S(CELL_FG_PROGRAM, sprites, SPRITE_MAP_UNIT, 1i);
606         S(CELL_PROGRAM, dim_opacity, OPT(dim_opacity), 1f); S(CELL_FG_PROGRAM, dim_opacity, OPT(dim_opacity), 1f);
607         S(CELL_BG_PROGRAM, defaultbg, OPT(background), 1f);
608 #undef S
609         cell_uniform_data.constants_set = true;
610     }
611     if (current_inactive_text_alpha != cell_uniform_data.prev_inactive_text_alpha || force) {
612         cell_uniform_data.prev_inactive_text_alpha = current_inactive_text_alpha;
613 #define S(prog, loc) { bind_program(prog); glUniform1f(cell_uniform_data.loc, current_inactive_text_alpha); }
614         S(CELL_PROGRAM, cploc); S(CELL_FG_PROGRAM, cfploc); S(GRAPHICS_PROGRAM, gploc); S(GRAPHICS_PREMULT_PROGRAM, gpploc);
615 #undef S
616     }
617 }
618 
619 void
blank_canvas(float background_opacity,color_type color)620 blank_canvas(float background_opacity, color_type color) {
621     // See https://github.com/glfw/glfw/issues/1538 for why we use pre-multiplied alpha
622 #define C(shift) ((((GLfloat)((color >> shift) & 0xFF)) / 255.0f) * background_opacity)
623     glClearColor(C(16), C(8), C(0), background_opacity);
624 #undef C
625     glClear(GL_COLOR_BUFFER_BIT);
626 }
627 
628 bool
send_cell_data_to_gpu(ssize_t vao_idx,ssize_t gvao_idx,GLfloat xstart,GLfloat ystart,GLfloat dx,GLfloat dy,Screen * screen,OSWindow * os_window)629 send_cell_data_to_gpu(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, Screen *screen, OSWindow *os_window) {
630     bool changed = false;
631     if (os_window->fonts_data) {
632         if (cell_prepare_to_render(vao_idx, gvao_idx, screen, xstart, ystart, dx, dy, os_window->fonts_data)) changed = true;
633     }
634     return changed;
635 }
636 
637 static float
ease_out_cubic(float phase)638 ease_out_cubic(float phase) {
639     return 1.0f - powf(1.0f - phase, 3.0f);
640 }
641 
642 static float
ease_in_out_cubic(float phase)643 ease_in_out_cubic(float phase) {
644     return phase < 0.5f ?
645         4.0f * powf(phase, 3.0f) :
646         1.0f - powf(-2.0f * phase + 2.0f, 3.0f) / 2.0f;
647 }
648 
649 static float
visual_bell_intensity(float phase)650 visual_bell_intensity(float phase) {
651     static const float peak = 0.2f;
652     const float fade = 1.0f - peak;
653     return phase < peak ? ease_out_cubic(phase / peak) : ease_in_out_cubic((1.0f - phase) / fade);
654 }
655 
656 static float
get_visual_bell_intensity(Screen * screen)657 get_visual_bell_intensity(Screen *screen) {
658     if (screen->start_visual_bell_at > 0) {
659         monotonic_t progress = monotonic() - screen->start_visual_bell_at;
660         monotonic_t duration = OPT(visual_bell_duration);
661         if (progress <= duration) return visual_bell_intensity((float)progress / duration);
662         screen->start_visual_bell_at = 0;
663     }
664     return 0.0f;
665 }
666 
667 void
draw_cells(ssize_t vao_idx,ssize_t gvao_idx,GLfloat xstart,GLfloat ystart,GLfloat dx,GLfloat dy,Screen * screen,OSWindow * os_window,bool is_active_window,bool can_be_focused)668 draw_cells(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, Screen *screen, OSWindow *os_window, bool is_active_window, bool can_be_focused) {
669     CELL_BUFFERS;
670     bool inverted = screen_invert_colors(screen);
671 
672     cell_update_uniform_block(vao_idx, screen, uniform_buffer, xstart, ystart, dx, dy, &screen->cursor_render_info, inverted, os_window);
673 
674     bind_vao_uniform_buffer(vao_idx, uniform_buffer, cell_program_layouts[CELL_PROGRAM].render_data.index);
675     bind_vertex_array(vao_idx);
676 
677     float current_inactive_text_alpha = (!can_be_focused || screen->cursor_render_info.is_focused) && is_active_window ? 1.0f : (float)OPT(inactive_text_alpha);
678     set_cell_uniforms(current_inactive_text_alpha, screen->reload_all_gpu_data);
679     screen->reload_all_gpu_data = false;
680     GLfloat w = (GLfloat)screen->columns * dx, h = (GLfloat)screen->lines * dy;
681     // The scissor limits below are calculated to ensure that they do not
682     // overlap with the pixels outside the draw area,
683     // for a test case (scissor is also used to blit framebuffer in draw_cells_interleaved_premult) run:
684     // kitty -o background=cyan -o background_opacity=0.7 -o cursor_blink_interval=0 -o window_margin_width=40 sh -c "kitty +kitten icat logo/kitty.png; read"
685 #define SCALE(w, x) ((GLfloat)(os_window->viewport_##w) * (GLfloat)(x))
686     /* printf("columns=%d dx=%f w=%f vw=%d vh=%d left=%f width=%f\n", screen->columns, dx, w, os_window->viewport_width, os_window->viewport_height, SCALE(width, (xstart + 1.f)/2.f), SCALE(width, w / 2.f)); */
687 
688     glScissor(
689         (GLint)roundf(SCALE(width, (xstart + 1.f)/2.f)),  // x
690         (GLint)roundf(SCALE(height, (ystart - h + 1.f)/2.f)),  // y
691         (GLsizei)roundf(SCALE(width, w / 2.f)),  // width
692         (GLsizei)roundf(SCALE(height, h / 2.f)) // height
693     );
694 #undef SCALE
695     if (os_window->is_semi_transparent) {
696         if (screen->grman->count || has_bgimage(os_window)) draw_cells_interleaved_premult(
697                 vao_idx, gvao_idx, screen, os_window, xstart, ystart, w, h);
698         else draw_cells_simple(vao_idx, gvao_idx, screen);
699     } else {
700         if (screen->grman->num_of_negative_refs || screen->grman->num_of_below_refs || has_bgimage(os_window)) draw_cells_interleaved(
701                 vao_idx, gvao_idx, screen, os_window, xstart, ystart, w, h);
702         else draw_cells_simple(vao_idx, gvao_idx, screen);
703     }
704 
705     if (screen->start_visual_bell_at) {
706         GLfloat intensity = get_visual_bell_intensity(screen);
707         if (intensity > 0.0f) draw_visual_bell_flash(intensity, xstart, ystart, w, h, screen);
708     }
709 }
710 // }}}
711 
712 // Borders {{{
713 enum BorderUniforms { BORDER_viewport, BORDER_background_opacity, BORDER_default_bg, BORDER_active_border_color, BORDER_inactive_border_color, BORDER_bell_border_color, NUM_BORDER_UNIFORMS };
714 static GLint border_uniform_locations[NUM_BORDER_UNIFORMS] = {0};
715 
716 static void
init_borders_program(void)717 init_borders_program(void) {
718 #define SET_LOC(which) border_uniform_locations[BORDER_##which] = get_uniform_location(BORDERS_PROGRAM, #which);
719         SET_LOC(viewport)
720         SET_LOC(background_opacity)
721         SET_LOC(default_bg)
722         SET_LOC(active_border_color)
723         SET_LOC(inactive_border_color)
724         SET_LOC(bell_border_color)
725 #undef SET_LOC
726 }
727 
728 ssize_t
create_border_vao(void)729 create_border_vao(void) {
730     ssize_t vao_idx = create_vao();
731 
732     add_buffer_to_vao(vao_idx, GL_ARRAY_BUFFER);
733     add_attribute_to_vao(BORDERS_PROGRAM, vao_idx, "rect",
734             /*size=*/4, /*dtype=*/GL_UNSIGNED_INT, /*stride=*/sizeof(GLuint)*5, /*offset=*/0, /*divisor=*/1);
735     add_attribute_to_vao(BORDERS_PROGRAM, vao_idx, "rect_color",
736             /*size=*/1, /*dtype=*/GL_UNSIGNED_INT, /*stride=*/sizeof(GLuint)*5, /*offset=*/(void*)(sizeof(GLuint)*4), /*divisor=*/1);
737 
738     return vao_idx;
739 }
740 
741 void
draw_borders(ssize_t vao_idx,unsigned int num_border_rects,BorderRect * rect_buf,bool rect_data_is_dirty,uint32_t viewport_width,uint32_t viewport_height,color_type active_window_bg,unsigned int num_visible_windows,bool all_windows_have_same_bg,OSWindow * w)742 draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_buf, bool rect_data_is_dirty, uint32_t viewport_width, uint32_t viewport_height, color_type active_window_bg, unsigned int num_visible_windows, bool all_windows_have_same_bg, OSWindow *w) {
743 
744     if (has_bgimage(w)) {
745         glEnable(GL_BLEND);
746         BLEND_ONTO_OPAQUE;
747         draw_bg(w);
748     }
749 
750     if (num_border_rects) {
751         bind_vertex_array(vao_idx);
752         bind_program(BORDERS_PROGRAM);
753         if (rect_data_is_dirty) {
754             size_t sz = sizeof(GLuint) * 5 * num_border_rects;
755             void *borders_buf_address = alloc_and_map_vao_buffer(vao_idx, sz, 0, GL_STATIC_DRAW, GL_WRITE_ONLY);
756             if (borders_buf_address) memcpy(borders_buf_address, rect_buf, sz);
757             unmap_vao_buffer(vao_idx, 0);
758         }
759 #define CV3(x) (((float)((x >> 16) & 0xff))/255.f), (((float)((x >> 8) & 0xff))/255.f), (((float)(x & 0xff))/255.f)
760         glUniform1f(border_uniform_locations[BORDER_background_opacity], w->is_semi_transparent ? w->background_opacity: 1.0f);
761         glUniform3f(border_uniform_locations[BORDER_active_border_color], CV3(OPT(active_border_color)));
762         glUniform3f(border_uniform_locations[BORDER_inactive_border_color], CV3(OPT(inactive_border_color)));
763         glUniform3f(border_uniform_locations[BORDER_bell_border_color], CV3(OPT(bell_border_color)));
764         glUniform2ui(border_uniform_locations[BORDER_viewport], viewport_width, viewport_height);
765         color_type default_bg = (num_visible_windows > 1 && !all_windows_have_same_bg) ? OPT(background) : active_window_bg;
766         glUniform3f(border_uniform_locations[BORDER_default_bg], CV3(default_bg));
767 #undef CV3
768         glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, num_border_rects);
769         unbind_vertex_array();
770         unbind_program();
771     }
772     if (has_bgimage(w)) glDisable(GL_BLEND);
773 }
774 
775 // }}}
776 
777 // Python API {{{
778 static PyObject*
compile_program(PyObject UNUSED * self,PyObject * args)779 compile_program(PyObject UNUSED *self, PyObject *args) {
780     const char *vertex_shader, *fragment_shader;
781     int which;
782     GLuint vertex_shader_id = 0, fragment_shader_id = 0;
783     if (!PyArg_ParseTuple(args, "iss", &which, &vertex_shader, &fragment_shader)) return NULL;
784     if (which < 0 || which >= NUM_PROGRAMS) { PyErr_Format(PyExc_ValueError, "Unknown program: %d", which); return NULL; }
785     Program *program = program_ptr(which);
786     if (program->id != 0) { PyErr_SetString(PyExc_ValueError, "program already compiled"); return NULL; }
787     program->id = glCreateProgram();
788     vertex_shader_id = compile_shader(GL_VERTEX_SHADER, vertex_shader);
789     fragment_shader_id = compile_shader(GL_FRAGMENT_SHADER, fragment_shader);
790     glAttachShader(program->id, vertex_shader_id);
791     glAttachShader(program->id, fragment_shader_id);
792     glLinkProgram(program->id);
793     GLint ret = GL_FALSE;
794     glGetProgramiv(program->id, GL_LINK_STATUS, &ret);
795     if (ret != GL_TRUE) {
796         GLsizei len;
797         static char glbuf[4096];
798         glGetProgramInfoLog(program->id, sizeof(glbuf), &len, glbuf);
799         log_error("Failed to compile GLSL shader!\n%s", glbuf);
800         PyErr_SetString(PyExc_ValueError, "Failed to compile shader");
801         goto end;
802     }
803     init_uniforms(which);
804 
805 end:
806     if (vertex_shader_id != 0) glDeleteShader(vertex_shader_id);
807     if (fragment_shader_id != 0) glDeleteShader(fragment_shader_id);
808     if (PyErr_Occurred()) { glDeleteProgram(program->id); program->id = 0; return NULL;}
809     return Py_BuildValue("I", program->id);
810 }
811 
812 #define PYWRAP0(name) static PyObject* py##name(PYNOARG)
813 #define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args)
814 #define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL;
815 #define ONE_INT(name) PYWRAP1(name) { name(PyLong_AsSsize_t(args)); Py_RETURN_NONE; }
816 #define TWO_INT(name) PYWRAP1(name) { int a, b; PA("ii", &a, &b); name(a, b); Py_RETURN_NONE; }
817 #define NO_ARG(name) PYWRAP0(name) { name(); Py_RETURN_NONE; }
818 #define NO_ARG_INT(name) PYWRAP0(name) { return PyLong_FromSsize_t(name()); }
819 
820 ONE_INT(bind_program)
NO_ARG(unbind_program)821 NO_ARG(unbind_program)
822 
823 PYWRAP0(create_vao) {
824     int ans = create_vao();
825     if (ans < 0) return NULL;
826     return Py_BuildValue("i", ans);
827 }
828 
829 ONE_INT(bind_vertex_array)
NO_ARG(unbind_vertex_array)830 NO_ARG(unbind_vertex_array)
831 TWO_INT(unmap_vao_buffer)
832 
833 NO_ARG(init_borders_program)
834 
835 NO_ARG(init_cell_program)
836 
837 static PyObject*
838 sprite_map_set_limits(PyObject UNUSED *self, PyObject *args) {
839     unsigned int w, h;
840     if(!PyArg_ParseTuple(args, "II", &w, &h)) return NULL;
841     sprite_tracker_set_limits(w, h);
842     max_texture_size = w; max_array_texture_layers = h;
843     Py_RETURN_NONE;
844 }
845 
846 
847 
848 #define M(name, arg_type) {#name, (PyCFunction)name, arg_type, NULL}
849 #define MW(name, arg_type) {#name, (PyCFunction)py##name, arg_type, NULL}
850 static PyMethodDef module_methods[] = {
851     M(compile_program, METH_VARARGS),
852     M(sprite_map_set_limits, METH_VARARGS),
853     MW(create_vao, METH_NOARGS),
854     MW(bind_vertex_array, METH_O),
855     MW(unbind_vertex_array, METH_NOARGS),
856     MW(unmap_vao_buffer, METH_VARARGS),
857     MW(bind_program, METH_O),
858     MW(unbind_program, METH_NOARGS),
859     MW(init_borders_program, METH_NOARGS),
860     MW(init_cell_program, METH_NOARGS),
861 
862     {NULL, NULL, 0, NULL}        /* Sentinel */
863 };
864 
865 bool
init_shaders(PyObject * module)866 init_shaders(PyObject *module) {
867 #define C(x) if (PyModule_AddIntConstant(module, #x, x) != 0) { PyErr_NoMemory(); return false; }
868     C(CELL_PROGRAM); C(CELL_BG_PROGRAM); C(CELL_SPECIAL_PROGRAM); C(CELL_FG_PROGRAM); C(BORDERS_PROGRAM); C(GRAPHICS_PROGRAM); C(GRAPHICS_PREMULT_PROGRAM); C(GRAPHICS_ALPHA_MASK_PROGRAM); C(BLIT_PROGRAM); C(BGIMAGE_PROGRAM); C(TINT_PROGRAM);
869     C(GLSL_VERSION);
870     C(GL_VERSION);
871     C(GL_VENDOR);
872     C(GL_SHADING_LANGUAGE_VERSION);
873     C(GL_RENDERER);
874     C(GL_TRIANGLE_FAN); C(GL_TRIANGLE_STRIP); C(GL_TRIANGLES); C(GL_LINE_LOOP);
875     C(GL_COLOR_BUFFER_BIT);
876     C(GL_VERTEX_SHADER);
877     C(GL_FRAGMENT_SHADER);
878     C(GL_TRUE);
879     C(GL_FALSE);
880     C(GL_COMPILE_STATUS);
881     C(GL_LINK_STATUS);
882     C(GL_TEXTURE0); C(GL_TEXTURE1); C(GL_TEXTURE2); C(GL_TEXTURE3); C(GL_TEXTURE4); C(GL_TEXTURE5); C(GL_TEXTURE6); C(GL_TEXTURE7); C(GL_TEXTURE8);
883     C(GL_MAX_ARRAY_TEXTURE_LAYERS); C(GL_TEXTURE_BINDING_BUFFER); C(GL_MAX_TEXTURE_BUFFER_SIZE);
884     C(GL_MAX_TEXTURE_SIZE);
885     C(GL_TEXTURE_2D_ARRAY);
886     C(GL_LINEAR); C(GL_CLAMP_TO_EDGE); C(GL_NEAREST);
887     C(GL_TEXTURE_MIN_FILTER); C(GL_TEXTURE_MAG_FILTER);
888     C(GL_TEXTURE_WRAP_S); C(GL_TEXTURE_WRAP_T);
889     C(GL_UNPACK_ALIGNMENT);
890     C(GL_R8); C(GL_RED); C(GL_UNSIGNED_BYTE); C(GL_UNSIGNED_SHORT); C(GL_R32UI); C(GL_RGB32UI); C(GL_RGBA);
891     C(GL_TEXTURE_BUFFER); C(GL_STATIC_DRAW); C(GL_STREAM_DRAW); C(GL_DYNAMIC_DRAW);
892     C(GL_SRC_ALPHA); C(GL_ONE_MINUS_SRC_ALPHA);
893     C(GL_WRITE_ONLY); C(GL_READ_ONLY); C(GL_READ_WRITE);
894     C(GL_BLEND); C(GL_FLOAT); C(GL_UNSIGNED_INT); C(GL_ARRAY_BUFFER); C(GL_UNIFORM_BUFFER);
895 
896 #undef C
897     if (PyModule_AddFunctions(module, module_methods) != 0) return false;
898     return true;
899 }
900 // }}}
901