From 3165c5d852983eab161fa3b867e290a41cc12b5b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 8 Aug 2025 14:22:12 +0530 Subject: [PATCH] Work on getting rendering of background image with background_opacity < 1 to work somewhat correctly at least --- kitty/cell_fragment.glsl | 12 ++++++-- kitty/screen.h | 2 +- kitty/shaders.c | 60 ++++++++++++++++++++++++++++------------ 3 files changed, 53 insertions(+), 21 deletions(-) diff --git a/kitty/cell_fragment.glsl b/kitty/cell_fragment.glsl index eb84fc82d..4eb7d4c04 100644 --- a/kitty/cell_fragment.glsl +++ b/kitty/cell_fragment.glsl @@ -7,6 +7,7 @@ uniform float text_contrast; uniform float text_gamma_adjustment; uniform float for_final_output; uniform float has_under_bg; +uniform float has_background_image; uniform sampler2DArray sprites; uniform sampler2D under_bg_layer; uniform sampler2D under_fg_layer; @@ -103,10 +104,15 @@ vec4 layer_color(sampler2D s) { void main() { vec4 ans_premul = vec4_premul(background); +#ifdef HAS_LAYERS // if its a default background cell and there is an under_bg layer, use the - // under_bg layer blended onto the background, as the background - blend_layer(ans_premul = if_one_then(has_under_bg * cell_has_default_bg, - alpha_blend_premul(layer_color(under_bg_layer), ans_premul), ans_premul)); + // under_bg layer blended onto the background, as the background. If there + // is a background image then dont blend onto the background as the + // background image is our background. + vec4 under_bg_color = layer_color(under_bg_layer); + under_bg_color = if_one_then(has_background_image, under_bg_color, alpha_blend_premul(under_bg_color, ans_premul)); + blend_layer(ans_premul = if_one_then(has_under_bg * cell_has_default_bg, under_bg_color, ans_premul)); +#endif // blend in the under_fg layer blend_layer(ans_premul = alpha_blend_premul(layer_color(under_fg_layer), ans_premul)); // blend in the foreground color diff --git a/kitty/screen.h b/kitty/screen.h index 583ae23d2..62bd7a49d 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -119,7 +119,7 @@ typedef struct { struct { uint32_t graphics_change_count; } under_fg_layer; struct { struct { bool was_drawn; uint32_t change_count; } graphics; - struct { bool was_drawn; uint32_t render_counter; } bgimage; + struct { bool was_drawn; uint32_t render_counter; float background_opacity; } bgimage; struct { bool was_drawn; unsigned width, height; int left, top; window_logo_id_t id; float alpha; } logo; } under_bg_layer; } last_rendered; diff --git a/kitty/shaders.c b/kitty/shaders.c index c1ef1f2c7..e04f53c9e 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -41,6 +41,7 @@ typedef struct UIRenderData { GraphicsRenderData grd; WindowLogoRenderData *window_logo; float inactive_text_alpha; + bool has_background_image; } UIRenderData; // Sprites {{{ @@ -74,6 +75,9 @@ color_vec4_premult(GLint location, color_type color, GLfloat alpha) { } +static void +clear_canvas(void) { glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT); } + SPRITE_MAP_HANDLE alloc_sprite_map(void) { if (!max_texture_size) { @@ -677,18 +681,38 @@ ensure_layer_ready_to_render(ScreenLayer *which, unsigned screen_width, unsigned return needs_redraw; } +static float +linear_to_srgb(float c) { return (c <= 0.0031308f) ? 12.92f * c : 1.055f * powf(c, 1.0f / 2.4f) - 0.055f; } + static void -save_texture_as_png(GLuint texture_id, unsigned width, unsigned height, const char *filename) { +save_texture_as_png(GLuint texture_id, const char *filename) { + GLint prev_tex = 0; glGetIntegerv(GL_TEXTURE_BINDING_2D, &prev_tex); glBindTexture(GL_TEXTURE_2D, texture_id); + int width = 0, height = 0; + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); size_t sz = width * height * sizeof(uint32_t); uint32_t* data = malloc(sz); glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + // assume data is linear and pre0multiplied + for (int i = 0; i < width * height; i++) { + uint32_t px = data[i]; + uint8_t r = (px >> 0) & 0xFF; uint8_t g = (px >> 8) & 0xFF; uint8_t b = (px >> 16) & 0xFF; + uint8_t a = (px >> 24) & 0xFF; float alpha = a / 255.0f; + float rf = 0, gf = 0, bf = 0; + if (alpha > 0.0f) { rf = (r / 255.0f) / alpha; gf = (g / 255.0f) / alpha; bf = (b / 255.0f) / alpha; } + rf = linear_to_srgb(rf); gf = linear_to_srgb(gf); bf = linear_to_srgb(bf); + r = (uint8_t)(rf*255); g = (uint8_t)(gf * 255); b = (uint8_t)(bf * 255); + data[i] = (r << 0) | (g << 8) | (b << 16) | (a << 24); + } + const char *png = png_from_32bit_rgba(data, width, height, &sz, true); if (!sz) fatal("Failed to save PNG to %s with error: %s", filename, png); free(data); FILE* file = fopen(filename, "wb"); fwrite(png, 1, sz, file); fclose(file); + glBindTexture(GL_TEXTURE_2D, prev_tex); } static void @@ -929,12 +953,12 @@ update_ui_layer(const UIRenderData *ui, bool cell_size_changed) { needs_redraw |= scrollbar_needs_redraw || visual_bell_needs_redraw || hyperlink_target_needs_redraw || window_number_needs_redraw; if (needs_redraw || ui->screen->reload_all_gpu_data) { - blank_canvas(0, 0); // clear the framebuffer + clear_canvas(); if (visual_bell_drawn && intensity > 0) draw_visual_bell_flash(lvb.intensity, lvb.color); if (scrollback_indicator_drawn) draw_scroll_indicator(bar_color, bar_alpha, bar_frac, ui); if (hyperlink_target_drawn) draw_hyperlink_target(screen, ui, ht.along_bottom); if (window_number_drawn) draw_window_number(ui); - if (0) save_texture_as_png(screen->textures.ui.id, ui->screen_width, ui->screen_height, "/tmp/ui.png"); + if (0) save_texture_as_png(screen->textures.ui.id, "/tmp/ui.png"); } #undef wn #undef ht @@ -990,30 +1014,31 @@ get_window_logo_settings(const UIRenderData *ui, Screen *s, bool has_logo) { static void update_under_bg_layer(const UIRenderData *ui) { const bool has_graphics = ui->grd.num_of_below_refs > 0; - const bool has_bg = has_bgimage(ui->os_window); const bool has_logo = ui->window_logo != NULL; - ui->screen->textures.under_bg.present = has_bg || has_graphics || has_logo; + ui->screen->textures.under_bg.present = ui->has_background_image || has_graphics || has_logo; if (!ui->screen->textures.under_bg.present) return; bool needs_redraw = ensure_layer_ready_to_render(&ui->screen->textures.under_bg, ui->screen_width, ui->screen_height); #define lr ui->screen->last_rendered.under_bg_layer needs_redraw |= lr.graphics.change_count != ui->grd.change_count || lr.graphics.was_drawn != has_graphics; lr.graphics.change_count = ui->grd.change_count; lr.graphics.was_drawn = has_graphics; - needs_redraw |= lr.bgimage.was_drawn != has_bg || lr.bgimage.render_counter != ui->os_window->bgimage.render_counter; - lr.bgimage.was_drawn = has_bg; lr.bgimage.render_counter = ui->os_window->bgimage.render_counter; + float background_opacity = ui->os_window->is_semi_transparent ? ui->os_window->background_opacity : 1.0f; + needs_redraw |= lr.bgimage.was_drawn != ui->has_background_image || lr.bgimage.render_counter != ui->os_window->bgimage.render_counter || lr.bgimage.background_opacity != background_opacity; + lr.bgimage.was_drawn = ui->has_background_image; lr.bgimage.render_counter = ui->os_window->bgimage.render_counter; + lr.bgimage.background_opacity = background_opacity; needs_redraw |= get_window_logo_settings(ui, ui->screen, has_logo); if (needs_redraw | ui->screen->reload_all_gpu_data) { - blank_canvas(0, 0); // clear the framebuffer - if (has_bg) { + clear_canvas(); + if (ui->has_background_image) { ImageRenderData d = {.texture_id = ui->os_window->bgimage.rendered_texture_id}; gpu_data_for_image(&d, -1, 1, 1, -1); d.src_rect.left = tex_pos_x(ui->screen_left, ui->full_framebuffer_width); d.src_rect.top = tex_pos_y(ui->screen_top, ui->full_framebuffer_height); d.src_rect.right = tex_pos_x(ui->screen_left + ui->screen_width, ui->full_framebuffer_width); d.src_rect.bottom = tex_pos_y(ui->screen_top + ui->screen_height, ui->full_framebuffer_height); - draw_graphics(GRAPHICS_PROGRAM, &d, 0, 1, 1.f); + draw_graphics(GRAPHICS_PROGRAM, &d, 0, 1, lr.bgimage.background_opacity); } if (has_logo) { float left = gl_pos_x(lr.logo.left, ui->screen_width), top = gl_pos_y(lr.logo.top, ui->screen_height); @@ -1023,7 +1048,7 @@ update_under_bg_layer(const UIRenderData *ui) { } if (has_graphics) draw_graphics( GRAPHICS_PROGRAM, ui->grd.images, 0, ui->grd.num_of_below_refs, ui->inactive_text_alpha); - if (0) save_texture_as_png(ui->screen->textures.under_bg.id, ui->screen_width, ui->screen_height, "/tmp/under-bg.png"); + if (0) save_texture_as_png(ui->screen->textures.under_bg.id, "/tmp/under-bg.png"); } #undef lr } @@ -1036,7 +1061,7 @@ update_under_fg_layer(const UIRenderData *ui) { needs_redraw |= ui->screen->last_rendered.under_fg_layer.graphics_change_count != ui->grd.change_count; ui->screen->last_rendered.under_fg_layer.graphics_change_count = ui->grd.change_count; if (needs_redraw | ui->screen->reload_all_gpu_data) { - blank_canvas(0, 0); // clear the framebuffer + clear_canvas(); if (ui->grd.num_of_negative_refs) draw_graphics( GRAPHICS_PROGRAM, ui->grd.images, ui->grd.num_of_below_refs, ui->grd.num_of_negative_refs, ui->inactive_text_alpha); @@ -1051,7 +1076,7 @@ update_over_fg_layer(const UIRenderData *ui) { needs_redraw |= ui->screen->last_rendered.over_fg_layer.graphics_change_count != ui->grd.change_count; ui->screen->last_rendered.over_fg_layer.graphics_change_count = ui->grd.change_count; if (needs_redraw | ui->screen->reload_all_gpu_data) { - blank_canvas(0, 0); // clear the framebuffer + clear_canvas(); if (ui->grd.num_of_positive_refs) draw_graphics( GRAPHICS_PROGRAM, ui->grd.images, ui->grd.num_of_negative_refs + ui->grd.num_of_below_refs, ui->grd.num_of_positive_refs, ui->inactive_text_alpha); @@ -1120,6 +1145,7 @@ draw_cells_with_layers(bool for_final_output, const UIRenderData *ui, ssize_t va } glUniform1f(cell_program_layouts[program].uniforms.has_under_bg, ui->screen->textures.under_bg.present); glUniform1f(cell_program_layouts[program].uniforms.inactive_text_alpha, ui->inactive_text_alpha); + glUniform1f(cell_program_layouts[program].uniforms.has_background_image, ui->has_background_image ? 1.f : 0.f); CELL_BUFFERS; bind_vao_uniform_buffer(vao_idx, uniform_buffer, cell_program_layouts[program].render_data.index); save_viewport_using_top_left_origin( @@ -1176,7 +1202,7 @@ draw_cells(bool for_final_output, const WindowRenderData *srd, OSWindow *os_wind .screen_left = srd->geometry.left, .screen_top = srd->geometry.top, .full_framebuffer_width = os_window->viewport_width, .full_framebuffer_height = os_window->viewport_height, .window = window, .screen = screen, .os_window = os_window, .grd = grman_render_data(grman), .window_logo = wl, - .inactive_text_alpha = current_inactive_text_alpha, + .inactive_text_alpha = current_inactive_text_alpha, .has_background_image = has_bgimage(os_window), }; update_screen_layers(&ui); screen->reload_all_gpu_data = false; @@ -1253,7 +1279,7 @@ draw_bgimage(GLuint texture_id, BackgroundImageRenderSettings s, unsigned image_ glActiveTexture(GL_TEXTURE0 + BGIMAGE_UNIT); glBindTexture(GL_TEXTURE_2D, texture_id); save_viewport_using_bottom_left_origin(0, 0, s.os_window.width, s.os_window.height); - blank_canvas(0, 0); + clear_canvas(); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); restore_viewport(); unbind_program(); @@ -1276,8 +1302,8 @@ ensure_bgimage_texture(OSWindow *os_window) { draw_bgimage(os_window->bgimage.instance->texture_id, s, os_window->bgimage.instance->width, os_window->bgimage.instance->height); bind_framebuffer_for_output(0); free_framebuffer(&framebuffer_id); - if (0) save_texture_as_png(os_window->bgimage.instance->texture_id, os_window->bgimage.instance->width, os_window->bgimage.instance->height, "/tmp/bg-image.png"); - if (0) save_texture_as_png(os_window->bgimage.rendered_texture_id, s.os_window.width, s.os_window.height, "/tmp/bg-layer.png"); + if (0) save_texture_as_png(os_window->bgimage.instance->texture_id, "/tmp/bg-image.png"); + if (0) save_texture_as_png(os_window->bgimage.rendered_texture_id, "/tmp/bg-layer.png"); os_window->bgimage.render_counter++; } os_window->bgimage.last_rendered = s;