diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index 6d7b9bb9b..6113e5330 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -689,7 +689,7 @@ render(monotonic_t now, bool input_read) { if (!w->fonts_data) { log_error("No fonts data found for window id: %llu", w->id); continue; } if (prepare_to_render_os_window(w, now, &active_window_id, &active_window_bg, &num_visible_windows, &all_windows_have_same_bg)) needs_render = true; if (w->last_active_window_id != active_window_id || w->last_active_tab != w->active_tab || w->focused_at_last_render != w->is_focused) needs_render = true; - if (w->render_calls < 3 && w->bgimage.texture_id) needs_render = true; + if (w->render_calls < 3 && w->bgimage && w->bgimage->texture_id) needs_render = true; if (needs_render) render_os_window(w, now, active_window_id, active_window_bg, num_visible_windows, all_windows_have_same_bg); } last_render_at = now; diff --git a/kitty/graphics.h b/kitty/graphics.h index 4c62cbb37..4a951b385 100644 --- a/kitty/graphics.h +++ b/kitty/graphics.h @@ -59,7 +59,7 @@ typedef struct { uint32_t texture_id; unsigned int height, width; uint8_t* bitmap; - bool load_failed, needs_free; + uint32_t refcnt; } BackgroundImage; typedef struct { diff --git a/kitty/shaders.c b/kitty/shaders.c index 793f5a5dd..43cc3d1a3 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -355,9 +355,9 @@ draw_bg(OSWindow *w) { bgimage_constants_set = true; } glUniform4f(bgimage_program_layout.sizes_location, - (GLfloat)w->window_width, (GLfloat)w->window_height, (GLfloat)w->bgimage.width, (GLfloat)w->bgimage.height); + (GLfloat)w->window_width, (GLfloat)w->window_height, (GLfloat)w->bgimage->width, (GLfloat)w->bgimage->height); glActiveTexture(GL_TEXTURE0 + BGIMAGE_UNIT); - glBindTexture(GL_TEXTURE_2D, w->bgimage.texture_id); + glBindTexture(GL_TEXTURE_2D, w->bgimage->texture_id); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); unbind_vertex_array(); unbind_program(); @@ -428,6 +428,11 @@ draw_cells_simple(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen) { } } +static inline bool +has_bgimage(OSWindow *w) { + return w->bgimage && w->bgimage->texture_id > 0; +} + static void draw_cells_interleaved(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, OSWindow *w) { glEnable(GL_BLEND); @@ -435,12 +440,12 @@ draw_cells_interleaved(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, OSWind bind_program(CELL_BG_PROGRAM); // draw background for all cells - if (!w->bgimage.texture_id) { + if (!has_bgimage(w)) { glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].draw_bg_bitfield_location, 3); glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); } - if (screen->grman->num_of_below_refs || w->bgimage.texture_id) { + if (screen->grman->num_of_below_refs || has_bgimage(w)) { if (screen->grman->num_of_below_refs) draw_graphics( GRAPHICS_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, 0, screen->grman->num_of_below_refs); bind_program(CELL_BG_PROGRAM); @@ -479,7 +484,7 @@ draw_cells_interleaved_premult(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen 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); - if (!os_window->bgimage.texture_id) { + if (!has_bgimage(os_window)) { // draw background for all cells glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].draw_bg_bitfield_location, 3); glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); @@ -487,7 +492,7 @@ draw_cells_interleaved_premult(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen glEnable(GL_BLEND); BLEND_PREMULT; - if (screen->grman->num_of_below_refs || os_window->bgimage.texture_id) { + if (screen->grman->num_of_below_refs || has_bgimage(os_window)) { if (screen->grman->num_of_below_refs) draw_graphics( GRAPHICS_PREMULT_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, 0, screen->grman->num_of_below_refs); bind_program(CELL_BG_PROGRAM); @@ -513,7 +518,7 @@ draw_cells_interleaved_premult(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen if (screen->grman->num_of_positive_refs) draw_graphics(GRAPHICS_PREMULT_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, screen->grman->num_of_negative_refs + screen->grman->num_of_below_refs, screen->grman->num_of_positive_refs); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - if (!os_window->bgimage.texture_id) glDisable(GL_BLEND); + if (!has_bgimage(os_window)) glDisable(GL_BLEND); glEnable(GL_SCISSOR_TEST); // Now render the framebuffer to the screen @@ -602,10 +607,10 @@ draw_cells(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GL ); #undef SCALE if (os_window->is_semi_transparent) { - if (screen->grman->count || os_window->bgimage.texture_id) draw_cells_interleaved_premult(vao_idx, gvao_idx, screen, os_window); + if (screen->grman->count || has_bgimage(os_window)) 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 || screen->grman->num_of_below_refs || os_window->bgimage.texture_id) draw_cells_interleaved(vao_idx, gvao_idx, screen, os_window); + if (screen->grman->num_of_negative_refs || screen->grman->num_of_below_refs || has_bgimage(os_window)) draw_cells_interleaved(vao_idx, gvao_idx, screen, os_window); else draw_cells_simple(vao_idx, gvao_idx, screen); } } @@ -649,7 +654,7 @@ create_border_vao(void) { void draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_buf, bool rect_data_is_dirty, uint32_t viewport_width, uint32_t viewport_height, color_type active_window_bg, unsigned int num_visible_windows, bool all_windows_have_same_bg, OSWindow *w) { - if (w->bgimage.texture_id) { + if (has_bgimage(w)) { glEnable(GL_BLEND); BLEND_ONTO_OPAQUE; draw_bg(w); @@ -677,7 +682,7 @@ draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_bu unbind_vertex_array(); unbind_program(); } - if (w->bgimage.texture_id) glDisable(GL_BLEND); + if (has_bgimage(w)) glDisable(GL_BLEND); } // }}} diff --git a/kitty/state.c b/kitty/state.c index c90059dd7..630fbc6d5 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -71,6 +71,37 @@ os_window_for_kitty_window(id_type kitty_window_id) { return NULL; } +static void +send_bgimage_to_gpu(BackgroundImageLayout layout, BackgroundImage *bgimage) { + RepeatStrategy r; + switch (layout) { + case SCALED: + r = REPEAT_CLAMP; break; + case MIRRORED: + r = REPEAT_MIRROR; break; + case TILING: + default: + r = REPEAT_DEFAULT; break; + } + bgimage->texture_id = 0; + send_image_to_gpu(&bgimage->texture_id, bgimage->bitmap, bgimage->width, + bgimage->height, false, true, OPT(background_image_linear), r); + free(bgimage->bitmap); bgimage->bitmap = NULL; +} + +static void +free_bgimage(BackgroundImage *bgimage, bool release_texture) { + if (bgimage && bgimage->refcnt) { + bgimage->refcnt--; + if (bgimage->refcnt == 0) { + free(bgimage->bitmap); bgimage->bitmap = NULL; + if (release_texture) free_texture(&bgimage->texture_id); + } + } +} + +static BackgroundImage global_bg_image = {0}; + OSWindow* add_os_window() { WITH_OS_WINDOW_REFS @@ -84,29 +115,17 @@ add_os_window() { bool wants_bg = OPT(background_image) && OPT(background_image)[0] != 0; if (wants_bg) { - if (!global_state.bgimage.texture_id && !global_state.bgimage.load_failed) { + if (!global_state.bgimage) { + global_state.bgimage = &global_bg_image; + global_state.bgimage->refcnt++; size_t size; - if (png_path_to_bitmap(OPT(background_image), &global_state.bgimage.bitmap, &global_state.bgimage.width, &global_state.bgimage.height, &size)) { - RepeatStrategy r; - switch (OPT(background_image_layout)) { - case SCALED: - r = REPEAT_CLAMP; break; - case MIRRORED: - r = REPEAT_MIRROR; break; - case TILING: - default: - r = REPEAT_DEFAULT; break; - } - global_state.bgimage.texture_id = 0; - send_image_to_gpu(&global_state.bgimage.texture_id, global_state.bgimage.bitmap, global_state.bgimage.width, - global_state.bgimage.height, false, true, OPT(background_image_linear), r); - global_state.bgimage.needs_free = true; - free(global_state.bgimage.bitmap); global_state.bgimage.bitmap = NULL; - } else global_state.bgimage.load_failed = true; + if (png_path_to_bitmap(OPT(background_image), &global_state.bgimage->bitmap, &global_state.bgimage->width, &global_state.bgimage->height, &size)) { + send_bgimage_to_gpu(OPT(background_image_layout), global_state.bgimage); + } } - if (global_state.bgimage.texture_id) { - memcpy(&ans->bgimage, &global_state.bgimage, sizeof(global_state.bgimage)); - ans->bgimage.needs_free = false; + if (global_state.bgimage->texture_id) { + ans->bgimage = global_state.bgimage; + ans->bgimage->refcnt++; } } @@ -288,9 +307,8 @@ destroy_os_window_item(OSWindow *w) { remove_vao(w->tab_bar_render_data.vao_idx); remove_vao(w->gvao_idx); free(w->tabs); w->tabs = NULL; - if (w->bgimage.needs_free) { - free(w->bgimage.bitmap); free_texture(&w->bgimage.texture_id); - } + free_bgimage(w->bgimage, true); + w->bgimage = NULL; } bool @@ -884,6 +902,44 @@ PYWRAP1(patch_global_colors) { Py_RETURN_NONE; } +static PyObject* +pyset_background_image(PyObject *self UNUSED, PyObject *args) { + const char *path; + PyObject *layout_name = NULL; + PyObject *os_window_ids; + int configured = 0; + PA("sO&|pU", &path, &PyTuple_Type, &os_window_ids, &configured, &layout_name); + size_t size; + BackgroundImageLayout layout = layout_name ? bglayout(layout_name) : OPT(background_image_layout); + BackgroundImage *bgimage = calloc(1, sizeof(BackgroundImage)); + if (!bgimage) return PyErr_NoMemory(); + if (!png_path_to_bitmap(path, &bgimage->bitmap, &bgimage->width, &bgimage->height, &size)) { + PyErr_Format(PyExc_ValueError, "Failed to load image from: %s", path); + free(bgimage); + return NULL; + } + send_bgimage_to_gpu(layout, bgimage); + bgimage->refcnt++; + if (configured) { + free_bgimage(global_state.bgimage, true); + global_state.bgimage = bgimage; + bgimage->refcnt++; + OPT(background_image_layout) = layout; + } + for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(os_window_ids); i++) { + id_type os_window_id = PyLong_AsUnsignedLongLong(PyTuple_GET_ITEM(os_window_ids, i)); + WITH_OS_WINDOW(os_window_id) + make_os_window_context_current(os_window); + free_bgimage(os_window->bgimage, true); + os_window->bgimage = bgimage; + os_window->render_calls = 0; + bgimage->refcnt++; + END_WITH_OS_WINDOW + } + free_bgimage(bgimage, true); + Py_RETURN_NONE; +} + PYWRAP0(destroy_global_data) { Py_CLEAR(global_state.boss); free(global_state.os_windows); global_state.os_windows = NULL; @@ -940,6 +996,7 @@ static PyMethodDef module_methods[] = { MW(background_opacity_of, METH_O), MW(update_window_visibility, METH_VARARGS), MW(global_font_size, METH_VARARGS), + MW(set_background_image, METH_VARARGS), MW(os_window_font_size, METH_VARARGS), MW(set_boss, METH_O), MW(patch_global_colors, METH_VARARGS), @@ -956,13 +1013,12 @@ finalize(void) { if (detached_windows.windows) free(detached_windows.windows); detached_windows.capacity = 0; if (OPT(background_image)) free(OPT(background_image)); - if (global_state.bgimage.needs_free) { - free(global_state.bgimage.bitmap); - // we leak the texture here since it is not guaranteed - // that freeing the texture will work during shutdown and - // the GPU driver should take care of it when the OpenGL context is - // destroyed. - } + // we leak the texture here since it is not guaranteed + // that freeing the texture will work during shutdown and + // the GPU driver should take care of it when the OpenGL context is + // destroyed. + free_bgimage(global_state.bgimage, false); + global_state.bgimage = NULL; } bool diff --git a/kitty/state.h b/kitty/state.h index c37d87c4f..71fdb4f9a 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -142,7 +142,7 @@ typedef struct { int viewport_width, viewport_height, window_width, window_height; double viewport_x_ratio, viewport_y_ratio; Tab *tabs; - BackgroundImage bgimage; + BackgroundImage *bgimage; unsigned int active_tab, num_tabs, capacity, last_active_tab, last_num_tabs, last_active_window_id; bool focused_at_last_render, needs_render; ScreenRenderData tab_bar_render_data; @@ -176,7 +176,7 @@ typedef struct { id_type os_window_id_counter, tab_id_counter, window_id_counter; PyObject *boss; - BackgroundImage bgimage; + BackgroundImage *bgimage; OSWindow *os_windows; size_t num_os_windows, capacity; OSWindow *callback_os_window;