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