mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-11 19:19:35 +02:00
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.
694 lines
31 KiB
C
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;
|
|
}
|
|
// }}}
|