Files
kitty/kitty/shaders.c
Kovid Goyal 8dea5b3e3e Reduce data sent to GPU per draw by 30%
Split up the Cell structure into a CPUCell and a GPUCell. Only the
GPUCell part needs to be sent to the GPU. Should make kitty use even
less system resources and make a performance difference on systems where
the GPU bandwidth is constrained.

Also allows adding more CPU only data in the future without affecting
GPU bandwidth. For example, hyperlinks or more combining characters.
2018-05-27 21:25:09 +05:30

694 lines
31 KiB
C

/*
* shaders.c
* Copyright (C) 2017 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "gl.h"
#include "fonts.h"
enum { CELL_PROGRAM, CELL_BG_PROGRAM, CELL_SPECIAL_PROGRAM, CELL_FG_PROGRAM, CURSOR_PROGRAM, BORDERS_PROGRAM, GRAPHICS_PROGRAM, GRAPHICS_PREMULT_PROGRAM, BLIT_PROGRAM, NUM_PROGRAMS };
enum { SPRITE_MAP_UNIT, GRAPHICS_UNIT, BLIT_UNIT };
// Sprites {{{
typedef struct {
unsigned int cell_width, cell_height;
int xnum, ynum, x, y, z, last_num_of_layers, last_ynum;
GLuint texture_id;
GLint max_texture_size, max_array_texture_layers;
} SpriteMap;
static const SpriteMap NEW_SPRITE_MAP = { .xnum = 1, .ynum = 1, .last_num_of_layers = 1, .last_ynum = -1 };
static GLint max_texture_size = 0, max_array_texture_layers = 0;
SPRITE_MAP_HANDLE
alloc_sprite_map(unsigned int cell_width, unsigned int cell_height) {
if (!max_texture_size) {
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &(max_texture_size));
glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &(max_array_texture_layers));
#ifdef __APPLE__
// Since on Apple we could have multiple GPUs, with different capabilities,
// upper bound the values according to the data from http://developer.apple.com/graphicsimaging/opengl/capabilities/
max_texture_size = MIN(8192, max_texture_size);
max_array_texture_layers = MIN(512, max_array_texture_layers);
#endif
sprite_tracker_set_limits(max_texture_size, max_array_texture_layers);
}
SpriteMap *ans = calloc(1, sizeof(SpriteMap));
if (ans) {
*ans = NEW_SPRITE_MAP;
ans->max_texture_size = max_texture_size;
ans->max_array_texture_layers = max_array_texture_layers;
ans->cell_width = cell_width; ans->cell_height = cell_height;
}
return (SPRITE_MAP_HANDLE)ans;
}
SPRITE_MAP_HANDLE
free_sprite_map(SPRITE_MAP_HANDLE sm) {
SpriteMap *sprite_map = (SpriteMap*)sm;
if (sprite_map) {
if (sprite_map->texture_id) free_texture(&sprite_map->texture_id);
free(sprite_map);
}
return NULL;
}
static bool copy_image_warned = false;
static void
copy_image_sub_data(GLuint src_texture_id, GLuint dest_texture_id, unsigned int width, unsigned int height, unsigned int num_levels) {
if (!GLAD_GL_ARB_copy_image) {
// ARB_copy_image not available, do a slow roundtrip copy
if (!copy_image_warned) {
copy_image_warned = true;
log_error("WARNING: Your system's OpenGL implementation does not have glCopyImageSubData, falling back to a slower implementation");
}
size_t sz = width * height * num_levels;
pixel *src = malloc(sz * sizeof(pixel));
if (src == NULL) { fatal("Out of memory."); }
glBindTexture(GL_TEXTURE_2D_ARRAY, src_texture_id);
glGetTexImage(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, GL_UNSIGNED_BYTE, src);
glBindTexture(GL_TEXTURE_2D_ARRAY, dest_texture_id);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, num_levels, GL_RGBA, GL_UNSIGNED_BYTE, src);
free(src);
} else {
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);
}
}
static void
realloc_sprite_texture(FONTS_DATA_HANDLE fg) {
GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
// We use GL_NEAREST otherwise glyphs that touch the edge of the cell
// often show a border between cells
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
unsigned int xnum, ynum, z, znum, width, height, src_ynum;
sprite_tracker_current_layout(fg, &xnum, &ynum, &z);
znum = z + 1;
SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map;
width = xnum * sprite_map->cell_width; height = ynum * sprite_map->cell_height;
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, width, height, znum);
if (sprite_map->texture_id) {
// need to re-alloc
src_ynum = MAX(1, sprite_map->last_ynum);
copy_image_sub_data(sprite_map->texture_id, tex, width, src_ynum * sprite_map->cell_height, sprite_map->last_num_of_layers);
glDeleteTextures(1, &sprite_map->texture_id);
}
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
sprite_map->last_num_of_layers = znum;
sprite_map->last_ynum = ynum;
sprite_map->texture_id = tex;
}
static inline void
ensure_sprite_map(FONTS_DATA_HANDLE fg) {
SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map;
if (!sprite_map->texture_id) realloc_sprite_texture(fg);
// We have to rebind since we dont know if the texture was ever bound
// in the context of the current OSWindow
glActiveTexture(GL_TEXTURE0 + SPRITE_MAP_UNIT);
glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map->texture_id);
}
void
send_sprite_to_gpu(FONTS_DATA_HANDLE fg, unsigned int x, unsigned int y, unsigned int z, pixel *buf) {
SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map;
unsigned int xnum, ynum, znum;
sprite_tracker_current_layout(fg, &xnum, &ynum, &znum);
if ((int)znum >= sprite_map->last_num_of_layers || (znum == 0 && (int)ynum > sprite_map->last_ynum)) realloc_sprite_texture(fg);
glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map->texture_id);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
x *= sprite_map->cell_width; y *= sprite_map->cell_height;
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);
}
void
send_image_to_gpu(GLuint *tex_id, const void* data, GLsizei width, GLsizei height, bool is_opaque, bool is_4byte_aligned) {
if (!(*tex_id)) { glGenTextures(1, tex_id); }
glBindTexture(GL_TEXTURE_2D, *tex_id);
glPixelStorei(GL_UNPACK_ALIGNMENT, is_4byte_aligned ? 4 : 1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, is_opaque ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, data);
}
// }}}
// Cell {{{
typedef struct {
UniformBlock render_data;
ArrayInformation color_table;
} CellProgramLayout;
static CellProgramLayout cell_program_layouts[NUM_PROGRAMS];
static GLuint offscreen_framebuffer = 0;
static ssize_t blit_vertex_array;
static void
init_cell_program() {
for (int i = CELL_PROGRAM; i < CURSOR_PROGRAM; i++) {
cell_program_layouts[i].render_data.index = block_index(i, "CellRenderData");
cell_program_layouts[i].render_data.size = block_size(i, cell_program_layouts[i].render_data.index);
cell_program_layouts[i].color_table.size = get_uniform_information(i, "color_table[0]", GL_UNIFORM_SIZE);
cell_program_layouts[i].color_table.offset = get_uniform_information(i, "color_table[0]", GL_UNIFORM_OFFSET);
cell_program_layouts[i].color_table.stride = get_uniform_information(i, "color_table[0]", GL_UNIFORM_ARRAY_STRIDE);
}
// Sanity check to ensure the attribute location binding worked
#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); }
for (int p = CELL_PROGRAM; p < CURSOR_PROGRAM; p++) {
C(p, colors, 0); C(p, sprite_coords, 1); C(p, is_selected, 2);
}
#undef C
glGenFramebuffers(1, &offscreen_framebuffer);
blit_vertex_array = create_vao();
}
#define CELL_BUFFERS enum { cell_data_buffer, selection_buffer, uniform_buffer };
ssize_t
create_cell_vao() {
ssize_t vao_idx = create_vao();
#define A(name, size, dtype, offset, stride) \
add_attribute_to_vao(CELL_PROGRAM, vao_idx, #name, \
/*size=*/size, /*dtype=*/dtype, /*stride=*/stride, /*offset=*/offset, /*divisor=*/1);
#define A1(name, size, dtype, offset) A(name, size, dtype, (void*)(offsetof(GPUCell, offset)), sizeof(GPUCell))
add_buffer_to_vao(vao_idx, GL_ARRAY_BUFFER);
A1(sprite_coords, 4, GL_UNSIGNED_SHORT, sprite_x);
A1(colors, 3, GL_UNSIGNED_INT, fg);
add_buffer_to_vao(vao_idx, GL_ARRAY_BUFFER);
A(is_selected, 1, GL_UNSIGNED_BYTE, NULL, 0);
size_t bufnum = add_buffer_to_vao(vao_idx, GL_UNIFORM_BUFFER);
alloc_vao_buffer(vao_idx, cell_program_layouts[CELL_PROGRAM].render_data.size, bufnum, GL_STREAM_DRAW);
return vao_idx;
#undef A
#undef A1
}
ssize_t
create_graphics_vao() {
ssize_t vao_idx = create_vao();
add_buffer_to_vao(vao_idx, GL_ARRAY_BUFFER);
add_attribute_to_vao(GRAPHICS_PROGRAM, vao_idx, "src", 4, GL_FLOAT, 0, NULL, 0);
return vao_idx;
}
struct CellUniformData {
bool constants_set;
GLint gploc, gpploc, cploc, cfploc;
GLfloat prev_inactive_text_alpha;
};
static struct CellUniformData cell_uniform_data = {0, .prev_inactive_text_alpha=-1};
static inline 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) {
struct CellRenderData {
GLfloat xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity;
GLuint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, url_color, url_style, inverted;
GLuint xnum, ynum, cursor_x, cursor_y, cursor_w;
};
static struct CellRenderData *rd;
// Send the uniform data
rd = (struct CellRenderData*)map_vao_buffer(vao_idx, uniform_buffer, GL_WRITE_ONLY);
if (UNLIKELY(screen->color_profile->dirty)) {
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));
}
// Cursor position
if (cursor->is_visible && cursor->shape == CURSOR_BLOCK && cursor->is_focused) {
rd->cursor_x = screen->cursor->x, rd->cursor_y = screen->cursor->y;
} else {
rd->cursor_x = screen->columns, rd->cursor_y = screen->lines;
}
rd->cursor_w = rd->cursor_x + MAX(1, screen_current_char_width(screen)) - 1;
rd->xnum = screen->columns; rd->ynum = screen->lines;
rd->xstart = xstart; rd->ystart = ystart; rd->dx = dx; rd->dy = dy;
unsigned int x, y, z;
sprite_tracker_current_layout(os_window->fonts_data, &x, &y, &z);
rd->sprite_dx = 1.0f / (float)x; rd->sprite_dy = 1.0f / (float)y;
rd->inverted = inverted ? 1 : 0;
rd->background_opacity = os_window->background_opacity;
#define COLOR(name) colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.name, screen->color_profile->configured.name)
rd->default_fg = COLOR(default_fg); rd->default_bg = COLOR(default_bg); rd->highlight_fg = COLOR(highlight_fg); rd->highlight_bg = COLOR(highlight_bg);
#undef COLOR
rd->cursor_color = cursor->color; rd->url_color = OPT(url_color); rd->url_style = OPT(url_style);
unmap_vao_buffer(vao_idx, uniform_buffer); rd = NULL;
}
static inline 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) {
size_t sz;
CELL_BUFFERS;
void *address;
bool changed = false;
ensure_sprite_map(fonts_data);
if (screen->scroll_changed || screen->is_dirty) {
sz = sizeof(GPUCell) * screen->lines * screen->columns;
address = alloc_and_map_vao_buffer(vao_idx, sz, cell_data_buffer, GL_STREAM_DRAW, GL_WRITE_ONLY);
screen_update_cell_data(screen, address, sz, fonts_data);
unmap_vao_buffer(vao_idx, cell_data_buffer); address = NULL;
changed = true;
}
if (screen_is_selection_dirty(screen)) {
sz = screen->lines * screen->columns;
address = alloc_and_map_vao_buffer(vao_idx, sz, selection_buffer, GL_STREAM_DRAW, GL_WRITE_ONLY);
screen_apply_selection(screen, address, sz);
unmap_vao_buffer(vao_idx, selection_buffer); address = NULL;
changed = true;
}
if (gvao_idx && grman_update_layers(screen->grman, screen->scrolled_by, xstart, ystart, dx, dy, screen->columns, screen->lines, screen->cell_size)) {
sz = sizeof(GLfloat) * 16 * screen->grman->count;
GLfloat *a = alloc_and_map_vao_buffer(gvao_idx, sz, 0, GL_STREAM_DRAW, GL_WRITE_ONLY);
for (size_t i = 0; i < screen->grman->count; i++, a += 16) memcpy(a, screen->grman->render_data[i].vertices, sizeof(screen->grman->render_data[0].vertices));
unmap_vao_buffer(gvao_idx, 0); a = NULL;
changed = true;
}
return changed;
}
static void
draw_graphics(int program, ssize_t vao_idx, ssize_t gvao_idx, ImageRenderData *data, GLuint start, GLuint count) {
bind_program(program);
bind_vertex_array(gvao_idx);
glActiveTexture(GL_TEXTURE0 + GRAPHICS_UNIT);
GLuint base = 4 * start;
glEnable(GL_SCISSOR_TEST);
for (GLuint i=0; i < count;) {
ImageRenderData *rd = data + start + i;
glBindTexture(GL_TEXTURE_2D, rd->texture_id);
// You could reduce the number of draw calls by using
// glDrawArraysInstancedBaseInstance but Apple chose to abandon OpenGL
// before implementing it.
for (GLuint k=0; k < rd->group_count; k++, base += 4, i++) glDrawArrays(GL_TRIANGLE_FAN, base, 4);
}
glDisable(GL_SCISSOR_TEST);
bind_vertex_array(vao_idx);
}
#define BLEND_ONTO_OPAQUE glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // blending onto opaque colors
#define BLEND_PREMULT glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // blending of pre-multiplied colors
static void
draw_cells_simple(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen) {
bind_program(CELL_PROGRAM);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
if (screen->grman->count) {
glEnable(GL_BLEND);
BLEND_ONTO_OPAQUE;
draw_graphics(GRAPHICS_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, 0, screen->grman->count);
glDisable(GL_BLEND);
}
}
static void
draw_cells_interleaved(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen) {
bind_program(CELL_BG_PROGRAM);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
glEnable(GL_BLEND);
BLEND_ONTO_OPAQUE;
if (screen->grman->num_of_negative_refs) draw_graphics(GRAPHICS_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, 0, screen->grman->num_of_negative_refs);
bind_program(CELL_SPECIAL_PROGRAM);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
bind_program(CELL_FG_PROGRAM);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
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_positive_refs);
glDisable(GL_BLEND);
}
static void
draw_cells_interleaved_premult(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, OSWindow *os_window) {
if (!os_window->offscreen_texture_id) {
glGenTextures(1, &os_window->offscreen_texture_id);
glBindTexture(GL_TEXTURE_2D, os_window->offscreen_texture_id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, os_window->viewport_width, os_window->viewport_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, offscreen_framebuffer);
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, os_window->offscreen_texture_id, 0);
/* if (glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) fatal("offscreen framebuffer not complete"); */
bind_program(CELL_BG_PROGRAM);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
glEnable(GL_BLEND);
BLEND_PREMULT;
if (screen->grman->num_of_negative_refs) draw_graphics(GRAPHICS_PREMULT_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, 0, screen->grman->num_of_negative_refs);
bind_program(CELL_SPECIAL_PROGRAM);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
bind_program(CELL_FG_PROGRAM);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
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_positive_refs);
glDisable(GL_BLEND);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
// Now render the framebuffer to the screen reversing alpha pre-multiplication
glEnable(GL_SCISSOR_TEST);
bind_program(BLIT_PROGRAM); bind_vertex_array(blit_vertex_array);
static bool blit_constants_set = false;
if (!blit_constants_set) {
glUniform1i(glGetUniformLocation(program_id(BLIT_PROGRAM), "image"), BLIT_UNIT);
blit_constants_set = true;
}
glActiveTexture(GL_TEXTURE0 + BLIT_UNIT);
glBindTexture(GL_TEXTURE_2D, os_window->offscreen_texture_id);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glDisable(GL_SCISSOR_TEST);
}
static inline void
set_cell_uniforms(float current_inactive_text_alpha) {
if (!cell_uniform_data.constants_set) {
cell_uniform_data.gploc = glGetUniformLocation(program_id(GRAPHICS_PROGRAM), "inactive_text_alpha");
cell_uniform_data.gpploc = glGetUniformLocation(program_id(GRAPHICS_PREMULT_PROGRAM), "inactive_text_alpha");
cell_uniform_data.cploc = glGetUniformLocation(program_id(CELL_PROGRAM), "inactive_text_alpha");
cell_uniform_data.cfploc = glGetUniformLocation(program_id(CELL_FG_PROGRAM), "inactive_text_alpha");
#define S(prog, name, val, type) { bind_program(prog); glUniform##type(glGetUniformLocation(program_id(prog), #name), val); }
S(GRAPHICS_PROGRAM, image, GRAPHICS_UNIT, 1i); S(GRAPHICS_PREMULT_PROGRAM, image, GRAPHICS_UNIT, 1i);
S(CELL_PROGRAM, sprites, SPRITE_MAP_UNIT, 1i); S(CELL_FG_PROGRAM, sprites, SPRITE_MAP_UNIT, 1i);
S(CELL_PROGRAM, dim_opacity, OPT(dim_opacity), 1f); S(CELL_FG_PROGRAM, dim_opacity, OPT(dim_opacity), 1f);
#undef S
cell_uniform_data.constants_set = true;
}
if (current_inactive_text_alpha != cell_uniform_data.prev_inactive_text_alpha) {
cell_uniform_data.prev_inactive_text_alpha = current_inactive_text_alpha;
#define S(prog, loc) { bind_program(prog); glUniform1f(cell_uniform_data.loc, current_inactive_text_alpha); }
S(CELL_PROGRAM, cploc); S(CELL_FG_PROGRAM, cfploc); S(GRAPHICS_PROGRAM, gploc); S(GRAPHICS_PREMULT_PROGRAM, gpploc);
#undef S
}
}
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) {
bool changed = false;
if (os_window->clear_count < 2) {
os_window->clear_count++;
#define C(shift) (((GLfloat)((OPT(background) >> shift) & 0xFF)) / 255.0f)
glClearColor(C(16), C(8), C(0), os_window->is_semi_transparent ? os_window->background_opacity : 1.0f);
#undef C
glClear(GL_COLOR_BUFFER_BIT);
changed = true;
}
if (os_window->fonts_data) {
if (cell_prepare_to_render(vao_idx, gvao_idx, screen, xstart, ystart, dx, dy, os_window->fonts_data)) changed = true;
}
return changed;
}
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) {
CELL_BUFFERS;
bool inverted = screen_invert_colors(screen);
cell_update_uniform_block(vao_idx, screen, uniform_buffer, xstart, ystart, dx, dy, &screen->cursor_render_info, inverted, os_window);
bind_vao_uniform_buffer(vao_idx, uniform_buffer, cell_program_layouts[CELL_PROGRAM].render_data.index);
bind_vertex_array(vao_idx);
float current_inactive_text_alpha = screen->cursor_render_info.is_focused && is_active_window ? 1.0 : OPT(inactive_text_alpha);
set_cell_uniforms(current_inactive_text_alpha);
GLfloat w = (GLfloat)screen->columns * dx, h = (GLfloat)screen->lines * dy;
#define SCALE(w, x) ((GLfloat)(os_window->viewport_##w) * (GLfloat)(x))
glScissor(
(GLint)(SCALE(width, (xstart + 1.0f) / 2.0f)),
(GLint)(SCALE(height, ((ystart - h) + 1.0f) / 2.0f)),
(GLsizei)(ceilf(SCALE(width, w / 2.0f))),
(GLsizei)(ceilf(SCALE(height, h / 2.0f)))
);
#undef SCALE
if (os_window->is_semi_transparent) {
if (screen->grman->count) draw_cells_interleaved_premult(vao_idx, gvao_idx, screen, os_window);
else draw_cells_simple(vao_idx, gvao_idx, screen);
} else {
if (screen->grman->num_of_negative_refs) draw_cells_interleaved(vao_idx, gvao_idx, screen);
else draw_cells_simple(vao_idx, gvao_idx, screen);
}
}
// }}}
// Cursor {{{
enum CursorUniforms { CURSOR_color, CURSOR_pos, NUM_CURSOR_UNIFORMS };
static GLint cursor_uniform_locations[NUM_CURSOR_UNIFORMS] = {0};
static ssize_t cursor_vertex_array;
static void
init_cursor_program() {
Program *p = programs + CURSOR_PROGRAM;
int left = NUM_CURSOR_UNIFORMS;
cursor_vertex_array = create_vao();
for (int i = 0; i < p->num_of_uniforms; i++, left--) {
#define SET_LOC(which) if (strcmp(p->uniforms[i].name, #which) == 0) cursor_uniform_locations[CURSOR_##which] = p->uniforms[i].location
SET_LOC(color);
else SET_LOC(pos);
else { fatal("Unknown uniform in cursor program: %s", p->uniforms[i].name); }
}
if (left) { fatal("Left over uniforms in cursor program"); }
#undef SET_LOC
}
void
draw_cursor(CursorRenderInfo *cursor, bool is_focused) {
bind_program(CURSOR_PROGRAM); bind_vertex_array(cursor_vertex_array);
glUniform3f(cursor_uniform_locations[CURSOR_color], ((cursor->color >> 16) & 0xff) / 255.0, ((cursor->color >> 8) & 0xff) / 255.0, (cursor->color & 0xff) / 255.0);
glUniform4f(cursor_uniform_locations[CURSOR_pos], cursor->left, cursor->top, cursor->right, cursor->bottom);
glDrawArrays(is_focused ? GL_TRIANGLE_FAN : GL_LINE_LOOP, 0, 4);
unbind_vertex_array(); unbind_program();
}
// }}}
// Borders {{{
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 };
static GLint border_uniform_locations[NUM_BORDER_UNIFORMS] = {0};
static void
init_borders_program() {
Program *p = programs + BORDERS_PROGRAM;
int left = NUM_BORDER_UNIFORMS;
for (int i = 0; i < p->num_of_uniforms; i++, left--) {
#define SET_LOC(which) (strcmp(p->uniforms[i].name, #which) == 0) border_uniform_locations[BORDER_##which] = p->uniforms[i].location
if SET_LOC(viewport);
else if SET_LOC(background_opacity);
else if SET_LOC(default_bg);
else if SET_LOC(active_border_color);
else if SET_LOC(inactive_border_color);
else if SET_LOC(bell_border_color);
else { fatal("Unknown uniform in borders program: %s", p->uniforms[i].name); return; }
}
if (left) { fatal("Left over uniforms in borders program"); return; }
#undef SET_LOC
}
ssize_t
create_border_vao() {
ssize_t vao_idx = create_vao();
add_buffer_to_vao(vao_idx, GL_ARRAY_BUFFER);
add_attribute_to_vao(BORDERS_PROGRAM, vao_idx, "rect",
/*size=*/4, /*dtype=*/GL_UNSIGNED_INT, /*stride=*/sizeof(GLuint)*5, /*offset=*/0, /*divisor=*/1);
add_attribute_to_vao(BORDERS_PROGRAM, vao_idx, "rect_color",
/*size=*/1, /*dtype=*/GL_UNSIGNED_INT, /*stride=*/sizeof(GLuint)*5, /*offset=*/(void*)(sizeof(GLuint)*4), /*divisor=*/1);
return vao_idx;
}
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, OSWindow *w) {
if (num_border_rects) {
if (rect_data_is_dirty) {
size_t sz = sizeof(GLuint) * 5 * num_border_rects;
void *borders_buf_address = alloc_and_map_vao_buffer(vao_idx, sz, 0, GL_STATIC_DRAW, GL_WRITE_ONLY);
if (borders_buf_address) memcpy(borders_buf_address, rect_buf, sz);
unmap_vao_buffer(vao_idx, 0);
}
bind_program(BORDERS_PROGRAM);
static bool constants_set = false;
#define CV3(x) (((float)((x >> 16) & 0xff))/255.f), (((float)((x >> 8) & 0xff))/255.f), (((float)(x & 0xff))/255.f)
if (!constants_set) {
constants_set = true;
glUniform1f(border_uniform_locations[BORDER_background_opacity], w->background_opacity);
glUniform3f(border_uniform_locations[BORDER_active_border_color], CV3(OPT(active_border_color)));
glUniform3f(border_uniform_locations[BORDER_inactive_border_color], CV3(OPT(inactive_border_color)));
glUniform3f(border_uniform_locations[BORDER_bell_border_color], CV3(OPT(bell_border_color)));
}
if (OPT(dynamic_background_opacity)) {
glUniform1f(border_uniform_locations[BORDER_background_opacity], w->background_opacity);
}
glUniform2ui(border_uniform_locations[BORDER_viewport], viewport_width, viewport_height);
color_type default_bg = num_visible_windows > 1 ? OPT(background) : active_window_bg;
glUniform3f(border_uniform_locations[BORDER_default_bg], CV3(default_bg));
#undef CV3
bind_vertex_array(vao_idx);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, num_border_rects);
unbind_vertex_array();
unbind_program();
}
}
// }}}
// Python API {{{
static PyObject*
compile_program(PyObject UNUSED *self, PyObject *args) {
const char *vertex_shader, *fragment_shader;
int which;
GLuint vertex_shader_id = 0, fragment_shader_id = 0;
if (!PyArg_ParseTuple(args, "iss", &which, &vertex_shader, &fragment_shader)) return NULL;
if (which < 0 || which >= NUM_PROGRAMS) { PyErr_Format(PyExc_ValueError, "Unknown program: %d", which); return NULL; }
if (programs[which].id != 0) { PyErr_SetString(PyExc_ValueError, "program already compiled"); return NULL; }
programs[which].id = glCreateProgram();
vertex_shader_id = compile_shader(GL_VERTEX_SHADER, vertex_shader);
fragment_shader_id = compile_shader(GL_FRAGMENT_SHADER, fragment_shader);
glAttachShader(programs[which].id, vertex_shader_id);
glAttachShader(programs[which].id, fragment_shader_id);
glLinkProgram(programs[which].id);
GLint ret = GL_FALSE;
glGetProgramiv(programs[which].id, GL_LINK_STATUS, &ret);
if (ret != GL_TRUE) {
GLsizei len;
glGetProgramInfoLog(programs[which].id, sizeof(glbuf), &len, glbuf);
log_error("Failed to compile GLSL shader!\n%s", glbuf);
PyErr_SetString(PyExc_ValueError, "Failed to compile shader");
goto end;
}
init_uniforms(which);
end:
if (vertex_shader_id != 0) glDeleteShader(vertex_shader_id);
if (fragment_shader_id != 0) glDeleteShader(fragment_shader_id);
if (PyErr_Occurred()) { glDeleteProgram(programs[which].id); programs[which].id = 0; return NULL;}
return Py_BuildValue("I", programs[which].id);
Py_RETURN_NONE;
}
#define PYWRAP0(name) static PyObject* py##name(PYNOARG)
#define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args)
#define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL;
#define ONE_INT(name) PYWRAP1(name) { name(PyLong_AsSsize_t(args)); Py_RETURN_NONE; }
#define TWO_INT(name) PYWRAP1(name) { int a, b; PA("ii", &a, &b); name(a, b); Py_RETURN_NONE; }
#define NO_ARG(name) PYWRAP0(name) { name(); Py_RETURN_NONE; }
#define NO_ARG_INT(name) PYWRAP0(name) { return PyLong_FromSsize_t(name()); }
ONE_INT(bind_program)
NO_ARG(unbind_program)
PYWRAP0(create_vao) {
int ans = create_vao();
if (ans < 0) return NULL;
return Py_BuildValue("i", ans);
}
ONE_INT(bind_vertex_array)
NO_ARG(unbind_vertex_array)
TWO_INT(unmap_vao_buffer)
NO_ARG(init_cursor_program)
NO_ARG(init_borders_program)
NO_ARG(init_cell_program)
static PyObject*
sprite_map_set_limits(PyObject UNUSED *self, PyObject *args) {
unsigned int w, h;
if(!PyArg_ParseTuple(args, "II", &w, &h)) return NULL;
sprite_tracker_set_limits(w, h);
max_texture_size = w; max_array_texture_layers = h;
Py_RETURN_NONE;
}
#define M(name, arg_type) {#name, (PyCFunction)name, arg_type, NULL}
#define MW(name, arg_type) {#name, (PyCFunction)py##name, arg_type, NULL}
static PyMethodDef module_methods[] = {
M(compile_program, METH_VARARGS),
M(sprite_map_set_limits, METH_VARARGS),
MW(create_vao, METH_NOARGS),
MW(bind_vertex_array, METH_O),
MW(unbind_vertex_array, METH_NOARGS),
MW(unmap_vao_buffer, METH_VARARGS),
MW(bind_program, METH_O),
MW(unbind_program, METH_NOARGS),
MW(init_cursor_program, METH_NOARGS),
MW(init_borders_program, METH_NOARGS),
MW(init_cell_program, METH_NOARGS),
{NULL, NULL, 0, NULL} /* Sentinel */
};
bool
init_shaders(PyObject *module) {
#define C(x) if (PyModule_AddIntConstant(module, #x, x) != 0) { PyErr_NoMemory(); return false; }
C(CELL_PROGRAM); C(CELL_BG_PROGRAM); C(CELL_SPECIAL_PROGRAM); C(CELL_FG_PROGRAM); C(CURSOR_PROGRAM); C(BORDERS_PROGRAM); C(GRAPHICS_PROGRAM); C(GRAPHICS_PREMULT_PROGRAM); C(BLIT_PROGRAM);
C(GLSL_VERSION);
C(GL_VERSION);
C(GL_VENDOR);
C(GL_SHADING_LANGUAGE_VERSION);
C(GL_RENDERER);
C(GL_TRIANGLE_FAN); C(GL_TRIANGLE_STRIP); C(GL_TRIANGLES); C(GL_LINE_LOOP);
C(GL_COLOR_BUFFER_BIT);
C(GL_VERTEX_SHADER);
C(GL_FRAGMENT_SHADER);
C(GL_TRUE);
C(GL_FALSE);
C(GL_COMPILE_STATUS);
C(GL_LINK_STATUS);
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);
C(GL_MAX_ARRAY_TEXTURE_LAYERS); C(GL_TEXTURE_BINDING_BUFFER); C(GL_MAX_TEXTURE_BUFFER_SIZE);
C(GL_MAX_TEXTURE_SIZE);
C(GL_TEXTURE_2D_ARRAY);
C(GL_LINEAR); C(GL_CLAMP_TO_EDGE); C(GL_NEAREST);
C(GL_TEXTURE_MIN_FILTER); C(GL_TEXTURE_MAG_FILTER);
C(GL_TEXTURE_WRAP_S); C(GL_TEXTURE_WRAP_T);
C(GL_UNPACK_ALIGNMENT);
C(GL_R8); C(GL_RED); C(GL_UNSIGNED_BYTE); C(GL_UNSIGNED_SHORT); C(GL_R32UI); C(GL_RGB32UI); C(GL_RGBA);
C(GL_TEXTURE_BUFFER); C(GL_STATIC_DRAW); C(GL_STREAM_DRAW); C(GL_DYNAMIC_DRAW);
C(GL_SRC_ALPHA); C(GL_ONE_MINUS_SRC_ALPHA);
C(GL_WRITE_ONLY); C(GL_READ_ONLY); C(GL_READ_WRITE);
C(GL_BLEND); C(GL_FLOAT); C(GL_UNSIGNED_INT); C(GL_ARRAY_BUFFER); C(GL_UNIFORM_BUFFER);
#undef C
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
return true;
}
// }}}