Get background_opacity working, at the cost of breaking negative z-index image rendering.

Image rendering will need to use an FBO since OpenGL cannot do proper alpha compositing.
As a first step, the interleaved shaders now generate premultiplied colors as opengl can only alpha blend pre-multipled colors
This commit is contained in:
Kovid Goyal 2017-11-23 09:39:13 +05:30
parent fe214f43cb
commit f85c050235
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
7 changed files with 187 additions and 104 deletions

View File

@ -1,6 +1,23 @@
#version GLSL_VERSION
#define WHICH_PROGRAM
#if defined(FOREGROUND) || defined(ALL)
#define NOT_TRANSPARENT
#if defined(SIMPLE) || defined(BACKGROUND) || defined(SPECIAL)
#define NEEDS_BACKROUND
#endif
#if defined(SIMPLE) || defined(FOREGROUND)
#define NEEDS_FOREGROUND
#endif
#ifdef NEEDS_BACKROUND
in vec3 background;
#ifdef TRANSPARENT
in float bg_alpha;
#endif
#endif
#ifdef NEEDS_FOREGROUND
uniform sampler2DArray sprites;
in vec3 sprite_pos;
in vec3 underline_pos;
@ -8,13 +25,10 @@ in vec3 strike_pos;
in vec3 foreground;
in vec3 decoration_fg;
#endif
in vec3 background;
#ifdef SPECIAL
in vec4 special_bg;
#endif
out vec4 final_color;
// Util functions {{{
vec4 alpha_blend(vec3 over, float over_alpha, vec3 under, float under_alpha) {
// Alpha blend two colors returning the resulting color pre-multiplied by its alpha
// and its alpha.
@ -24,33 +38,51 @@ vec4 alpha_blend(vec3 over, float over_alpha, vec3 under, float under_alpha) {
return vec4(combined_color, alpha);
}
void main() {
#if defined(FOREGROUND) || defined(ALL)
vec3 premul_blend(vec3 over, float over_alpha, vec3 under) {
return over + (1.0f - over_alpha) * under;
}
vec4 alpha_blend_premul(vec3 over, float over_alpha, vec3 under, float under_alpha) {
// Same as alpha_blend() except that it assumes over and under are both premultiplied.
float alpha = mix(under_alpha, 1.0f, over_alpha);
return vec4(premul_blend(over, over_alpha, under), alpha);
}
// }}}
#ifdef NEEDS_FOREGROUND
vec4 calculate_foreground() {
float text_alpha = texture(sprites, sprite_pos).r;
float underline_alpha = texture(sprites, underline_pos).r;
float strike_alpha = texture(sprites, strike_pos).r;
// Since strike and text are the same color, we simply add the alpha values
float combined_alpha = min(text_alpha + strike_alpha, 1.0f);
// Underline color might be different, so alpha blend
vec4 fg = alpha_blend(decoration_fg, underline_alpha, foreground, combined_alpha);
#ifdef ALL
// since background alpha is 1.0 and fg color is pre-multiplied by its alpha,
// we can simplify the alpha blend equation
final_color = vec4(fg.rgb + (1.0f - fg.a) * background, 1.0f);
#else
// FOREGROUND
// fg is pre-multipled so divide it by alpha
final_color = vec4(fg.rgb / fg.a, fg.a);
return alpha_blend(decoration_fg, underline_alpha, foreground, combined_alpha);
}
#endif
void main() {
#if defined(BACKGROUND) || defined(SPECIAL)
#ifdef TRANSPARENT
final_color = vec4(background.rgb * bg_alpha, bg_alpha);
#else
final_color = vec4(background.rgb, 1.0f);
#endif
#else
vec4 fg = calculate_foreground(); // pre-multiplied foreground
#ifdef FOREGROUND
final_color = fg;
#else
#ifdef SPECIAL
final_color = special_bg;
#ifdef TRANSPARENT
final_color = alpha_blend_premul(fg.rgb, fg.a, background * bg_alpha, bg_alpha);
final_color = vec4(final_color.rgb / final_color.a, final_color.a);
#else
// BACKGROUND
final_color = vec4(background, 1.0f);
// since background alpha is 1.0, it is effectively premultipled
final_color = vec4(premul_blend(fg.rgb, fg.a, background), 1.0f);
final_color = vec4(final_color.rgb / final_color.a, final_color.a);
#endif
#endif
#endif

View File

@ -1,7 +1,10 @@
#version GLSL_VERSION
#define WHICH_PROGRAM
#define NOT_TRANSPARENT
// Inputs {{{
layout(std140) uniform CellRenderData {
float xstart, ystart, dx, dy, sprite_dx, sprite_dy;
float xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity;
uint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, url_color;
@ -17,26 +20,42 @@ layout(location=0) in uvec3 colors;
layout(location=1) in uvec4 sprite_coords;
layout(location=2) in float is_selected;
#if defined(FOREGROUND) || defined(ALL)
const uvec2 cell_pos_map[] = uvec2[4](
uvec2(1, 0), // right, top
uvec2(1, 1), // right, bottom
uvec2(0, 1), // left, bottom
uvec2(0, 0) // left, top
);
// }}}
#if defined(SIMPLE) || defined(BACKGROUND) || defined(SPECIAL)
#define NEEDS_BACKROUND
#endif
#if defined(SIMPLE) || defined(FOREGROUND)
#define NEEDS_FOREGROUND
#endif
#ifdef NEEDS_BACKROUND
out vec3 background;
#ifdef TRANSPARENT
out float bg_alpha;
#endif
#endif
#ifdef NEEDS_FOREGROUND
out vec3 sprite_pos;
out vec3 underline_pos;
out vec3 strike_pos;
out vec3 foreground;
out vec3 decoration_fg;
#endif
out vec3 background;
#ifdef SPECIAL
out vec4 special_bg;
#endif
const uvec2 pos_map[] = uvec2[4](
uvec2(1, 0), // right, top
uvec2(1, 1), // right, bottom
uvec2(0, 1), // left, bottom
uvec2(0, 0) // left, top
);
// Utility functions {{{
const uint BYTE_MASK = uint(0xFF);
const uint SHORT_MASK = uint(0xFFFF);
const uint ZERO = uint(0);
@ -83,7 +102,7 @@ vec3 to_sprite_pos(uvec2 pos, uint x, uint y, uint z) {
}
vec3 choose_color(float q, vec3 a, vec3 b) {
return q * a + (1.0 - q) * b;
return mix(b, a, q);
}
float in_range(uint x, uint y) {
@ -95,35 +114,44 @@ float is_cursor(uint x, uint y) {
if (y == cursor_y && (x == cursor_x || x == cursor_w)) return 1.0;
return 0.0;
}
// }}}
void main() {
float cursor;
uint instance_id = uint(gl_InstanceID);
// The current cell being rendered
uint r = instance_id / xnum;
uint c = instance_id - r * xnum;
// The position of this vertex, at a corner of the cell
float left = xstart + c * dx;
float top = ystart - r * dy;
vec2 xpos = vec2(left, left + dx);
vec2 ypos = vec2(top, top - dy);
uvec2 pos = pos_map[gl_VertexID];
// set cell vertex position {{{
uint instance_id = uint(gl_InstanceID);
/* The current cell being rendered */
uint r = instance_id / xnum;
uint c = instance_id - r * xnum;
/* The position of this vertex, at a corner of the cell */
float left = xstart + c * dx;
float top = ystart - r * dy;
vec2 xpos = vec2(left, left + dx);
vec2 ypos = vec2(top, top - dy);
uvec2 pos = cell_pos_map[gl_VertexID];
gl_Position = vec4(xpos[pos.x], ypos[pos.y], 0, 1);
uvec2 default_colors = uvec2(default_fg, default_bg);
ivec2 color_indices = ivec2(color1, color2);
// }}}
// set cell color indices {{{
uvec2 default_colors = uvec2(default_fg, default_bg);
ivec2 color_indices = ivec2(color1, color2);
uint text_attrs = sprite_coords[3];
int fg_index = color_indices[(text_attrs >> 6) & REVERSE_MASK];
int bg_index = color_indices[1 - fg_index];
float cursor = is_cursor(c, r);
vec3 bg = to_color(colors[bg_index], default_colors[bg_index]);
// }}}
uint text_attrs = sprite_coords[3];
int fg_index = color_indices[(text_attrs >> 6) & REVERSE_MASK];
int bg_index = color_indices[1 - fg_index];
background = to_color(colors[bg_index], default_colors[bg_index]);
// Foreground {{{
#ifdef NEEDS_FOREGROUND
#if defined(FOREGROUND) || defined(ALL)
// The character sprite being rendered
sprite_pos = to_sprite_pos(pos, sprite_coords.x, sprite_coords.y, sprite_coords.z & SHORT_MASK);
// Foreground and background colors
// Foreground
uint resolved_fg = resolve_color(colors[fg_index], default_colors[fg_index]);
foreground = color_to_vec(resolved_fg);
// Selection
@ -133,20 +161,31 @@ void main() {
decoration_fg = choose_color(in_url, color_to_vec(url_color), to_color(colors[2], resolved_fg));
underline_pos = choose_color(in_url, to_sprite_pos(pos, TWO, ZERO, ZERO), to_sprite_pos(pos, (text_attrs >> 2) & DECORATION_MASK, ZERO, ZERO));
strike_pos = to_sprite_pos(pos, ((text_attrs >> 7) & STRIKE_MASK) * THREE, ZERO, ZERO);
// Block cursor rendering
cursor = is_cursor(c, r);
foreground = choose_color(cursor, background, foreground);
decoration_fg = choose_color(cursor, background, decoration_fg);
// Cursor
foreground = choose_color(cursor, bg, foreground);
decoration_fg = choose_color(cursor, bg, decoration_fg);
#endif
// }}}
// Background {{{
#ifdef NEEDS_BACKROUND
#if defined(BACKGROUND)
background = bg;
#else
// Selection and cursor
bg = choose_color(is_selected, color_to_vec(highlight_bg), bg);
background = choose_color(cursor, color_to_vec(cursor_color), bg);
#endif
#if defined(ALL)
// Selection and cursor
background = choose_color(is_selected, color_to_vec(highlight_bg), background);
background = choose_color(cursor, color_to_vec(cursor_color), background);
#elif defined(SPECIAL)
cursor = is_cursor(c, r);
background = choose_color(is_selected, color_to_vec(highlight_bg), background);
background = choose_color(cursor, color_to_vec(cursor_color), background);
special_bg = vec4(background, max(cursor, is_selected));
#ifdef TRANSPARENT
// If the background color is default, set its opacity to background_opacity, otherwise it should be opaque
bg_alpha = step(0.5, float(colors[bg_index] & BYTE_MASK));
bg_alpha = bg_alpha + (1.0f - bg_alpha) * background_opacity;
#endif
#endif
// }}}
}

View File

@ -238,7 +238,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
glfwSwapInterval(swap_interval); // a value of 1 makes mouse selection laggy
if (is_first_window) {
gl_init();
PyObject *ret = PyObject_CallFunction(load_programs, NULL);
PyObject *ret = PyObject_CallFunction(load_programs, "i", glfwGetWindowAttrib(glfw_window, GLFW_TRANSPARENT_FRAMEBUFFER));
if (ret == NULL) return NULL;
Py_DECREF(ret);
#ifdef __APPLE__

View File

@ -44,8 +44,15 @@ foreground #dddddd
background #000000
# The opacity of the background. A number between 0 and 1, where 1 is opaque and 0 is fully transparent.
# Note that this will only work if supported by the OS (for instance, when using a compositor under X11).
# Be aware that using a value less than 1.0 is a performance hit.
# This will only work if supported by the OS (for instance, when using a compositor under X11). Note
# that it only sets the default background color's opacity. This is so that
# things like the status bar in vim, powerline prompts, etc. still look good.
# But it means that if you use a color theme with a background color in your
# editor, it will not be rendered as transparent. Instead you should change the
# default background color in your kitty config and not use a background color
# in the editor color scheme. Or use the escape codes to set the terminals
# default colors in a shell script to launch your editor.
# Be aware that using a value less than 1.0 is a (possibly significant) performance hit.
background_opacity 1.0
# The foreground for selections

View File

@ -26,8 +26,8 @@ from .utils import (
from .window import load_shader_programs
def load_all_shaders():
load_shader_programs()
def load_all_shaders(semi_transparent=0):
load_shader_programs(semi_transparent)
load_borders_program()

View File

@ -9,7 +9,7 @@
#include "fonts.h"
#include <sys/sysctl.h>
enum { CELL_PROGRAM, CELL_BACKGROUND_PROGRAM, CELL_SPECIAL_PROGRAM, CELL_FOREGROUND_PROGRAM, CURSOR_PROGRAM, BORDERS_PROGRAM, GRAPHICS_PROGRAM, NUM_PROGRAMS };
enum { CELL_PROGRAM, CELL_BG_PROGRAM, CELL_SPECIAL_PROGRAM, CELL_FG_PROGRAM, CURSOR_PROGRAM, BORDERS_PROGRAM, GRAPHICS_PROGRAM, NUM_PROGRAMS };
enum { SPRITE_MAP_UNIT, GRAPHICS_UNIT };
// Sprites {{{
@ -145,7 +145,7 @@ static CellProgramLayout cell_program_layouts[NUM_PROGRAMS];
static void
init_cell_program() {
for (int i = CELL_PROGRAM; i <= CELL_FOREGROUND_PROGRAM; i++) {
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);
@ -154,7 +154,7 @@ init_cell_program() {
}
// 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 <= CELL_FOREGROUND_PROGRAM; 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
@ -197,7 +197,7 @@ create_graphics_vao() {
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) {
struct CellRenderData {
GLfloat xstart, ystart, dx, dy, sprite_dx, sprite_dy;
GLfloat xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity;
GLuint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, url_color;
@ -228,6 +228,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, G
sprite_tracker_current_layout(&x, &y, &z);
rd->sprite_dx = 1.0f / (float)x; rd->sprite_dy = 1.0f / (float)y;
rd->color1 = inverted & 1; rd->color2 = 1 - (inverted & 1);
rd->background_opacity = OPT(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);
@ -312,21 +313,22 @@ draw_all_cells(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen) {
static void
draw_cells_interleaved(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen) {
bind_program(CELL_BACKGROUND_PROGRAM);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
if (screen->grman->num_of_negative_refs) draw_graphics(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_FOREGROUND_PROGRAM);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
if (screen->grman->num_of_positive_refs) draw_graphics(vao_idx, gvao_idx, screen->grman->render_data, screen->grman->num_of_negative_refs, screen->grman->num_of_positive_refs);
glDisable(GL_BLEND);
(void)vao_idx; (void)gvao_idx; (void)screen;
/* bind_program(CELL_BACKGROUND_PROGRAM); */
/* glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); */
/* glEnable(GL_BLEND); */
/* glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); */
/* */
/* if (screen->grman->num_of_negative_refs) draw_graphics(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_FOREGROUND_PROGRAM); */
/* glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); */
/* */
/* if (screen->grman->num_of_positive_refs) draw_graphics(vao_idx, gvao_idx, screen->grman->render_data, screen->grman->num_of_negative_refs, screen->grman->num_of_positive_refs); */
/* glDisable(GL_BLEND); */
}
void
@ -345,11 +347,10 @@ draw_cells(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GL
if (!cell_constants_set) {
bind_program(CELL_PROGRAM);
glUniform1i(glGetUniformLocation(program_id(CELL_PROGRAM), "sprites"), SPRITE_MAP_UNIT);
bind_program(CELL_FOREGROUND_PROGRAM);
glUniform1i(glGetUniformLocation(program_id(CELL_FOREGROUND_PROGRAM), "sprites"), SPRITE_MAP_UNIT);
cell_constants_set = true;
}
if (screen->grman->num_of_negative_refs) draw_cells_interleaved(vao_idx, gvao_idx, screen);
bool needs_complex_rendering = screen->grman->num_of_negative_refs || (screen->grman->num_of_positive_refs && os_window->is_semi_transparent);
if (needs_complex_rendering) draw_cells_interleaved(vao_idx, gvao_idx, screen);
else draw_all_cells(vao_idx, gvao_idx, screen);
}
// }}}
@ -526,7 +527,7 @@ static PyMethodDef module_methods[] = {
bool
init_shaders(PyObject *module) {
#define C(x) if (PyModule_AddIntConstant(module, #x, x) != 0) { PyErr_NoMemory(); return false; }
C(CELL_PROGRAM); C(CELL_BACKGROUND_PROGRAM); C(CELL_SPECIAL_PROGRAM); C(CELL_FOREGROUND_PROGRAM); C(CURSOR_PROGRAM); C(BORDERS_PROGRAM); C(GRAPHICS_PROGRAM);
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(GLSL_VERSION);
C(GL_VERSION);
C(GL_VENDOR);

View File

@ -12,13 +12,12 @@ from .constants import (
ScreenGeometry, WindowGeometry, appname, get_boss, wakeup
)
from .fast_data_types import (
BRACKETED_PASTE_END, BRACKETED_PASTE_START, CELL_BACKGROUND_PROGRAM,
CELL_FOREGROUND_PROGRAM, CELL_PROGRAM, CELL_SPECIAL_PROGRAM,
CURSOR_PROGRAM, GRAPHICS_PROGRAM, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE,
Screen, add_window, compile_program, glfw_post_empty_event,
init_cell_program, init_cursor_program, set_clipboard_string,
set_window_render_data, update_window_title, update_window_visibility,
viewport_for_window
BRACKETED_PASTE_END, BRACKETED_PASTE_START, CELL_BG_PROGRAM,
CELL_FG_PROGRAM, CELL_PROGRAM, CELL_SPECIAL_PROGRAM, CURSOR_PROGRAM,
GRAPHICS_PROGRAM, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE, Screen,
add_window, compile_program, glfw_post_empty_event, init_cell_program,
init_cursor_program, set_clipboard_string, set_window_render_data,
update_window_title, update_window_visibility, viewport_for_window
)
from .keys import keyboard_mode_name
from .rgb import to_color
@ -52,14 +51,19 @@ def calculate_gl_geometry(window_geometry, viewport_width, viewport_height, cell
return ScreenGeometry(xstart, ystart, window_geometry.xnum, window_geometry.ynum, dx, dy)
def load_shader_programs():
def load_shader_programs(semi_transparent=0):
v, f = load_shaders('cell')
compile_program(GRAPHICS_PROGRAM, *load_shaders('graphics'))
for which, p in {
'ALL': CELL_PROGRAM, 'BACKGROUND': CELL_BACKGROUND_PROGRAM, 'SPECIAL': CELL_SPECIAL_PROGRAM,
'FOREGROUND': CELL_FOREGROUND_PROGRAM
'SIMPLE': CELL_PROGRAM,
'BACKGROUND': CELL_BG_PROGRAM,
'SPECIAL': CELL_SPECIAL_PROGRAM,
'FOREGROUND': CELL_FG_PROGRAM,
}.items():
vv, ff = v.replace('WHICH_PROGRAM', which), f.replace('WHICH_PROGRAM', which)
if semi_transparent:
vv = vv.replace('#define NOT_TRANSPARENT', '#define TRANSPARENT')
ff = ff.replace('#define NOT_TRANSPARENT', '#define TRANSPARENT')
compile_program(p, vv, ff)
init_cell_program()
compile_program(CURSOR_PROGRAM, *load_shaders('cursor'))