diff --git a/kitty/graphics.c b/kitty/graphics.c index 34bccf65f..8d8b3d177 100644 --- a/kitty/graphics.c +++ b/kitty/graphics.c @@ -61,8 +61,6 @@ cache_size(const GraphicsManager *self) { return disk_cache_total_size(self->dis #undef CK -static bool send_to_gpu = true; - GraphicsManager* grman_alloc() { GraphicsManager *self = (GraphicsManager *)GraphicsManager_Type.tp_alloc(&GraphicsManager_Type, 0); @@ -525,6 +523,14 @@ initialize_load_data(GraphicsManager *self, const GraphicsCommand *g, Image *img } #define MAX_IMAGE_DIMENSION 10000u +static void +upload_to_gpu(GraphicsManager *self, Image *img, void *data) { + if (self->window_id && !self->context_made_current_for_this_command) { + if (make_window_context_current(self->window_id)) { + self->context_made_current_for_this_command = true; + send_image_to_gpu(&img->texture_id, data, img->width, img->height, img->is_opaque, img->is_4byte_aligned, false, REPEAT_CLAMP); + }} +} static Image* handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, bool *is_dirty, uint32_t iid) { @@ -543,6 +549,8 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_ if (existing) { free_load_data(&img->load_data); img->data_loaded = false; + img->is_drawn = false; + img->current_frame_shown_at = 0; free_refs_data(img); *is_dirty = true; self->layers_dirty = true; @@ -576,9 +584,7 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_ if (img->data_loaded) { img->is_opaque = img->load_data.is_opaque; img->is_4byte_aligned = img->load_data.is_4byte_aligned; - if (send_to_gpu) { - send_image_to_gpu(&img->texture_id, img->load_data.data, img->width, img->height, img->is_opaque, img->is_4byte_aligned, false, REPEAT_CLAMP); - } + upload_to_gpu(self, img, img->load_data.data); if (img->root_frame.id) remove_from_cache(self, (const ImageAndFrame){.image_id=img->internal_id, .frame_id=img->root_frame.id}); img->root_frame.id = ++img->frame_id_counter; if (!add_to_cache(self, (const ImageAndFrame){.image_id = img->internal_id, .frame_id=img->root_frame.id}, img->load_data.data, img->load_data.data_sz)) { @@ -729,30 +735,41 @@ grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float scree // Iterate over all visible refs and create render data self->count = 0; - for (i = 0; i < self->image_count; i++) { img = self->images + i; for (j = 0; j < img->refcnt; j++) { ref = img->refs + j; - r.top = y0 - ref->start_row * dy - dy * (float)ref->cell_y_offset / (float)cell.height; - if (ref->num_rows > 0) r.bottom = y0 - (ref->start_row + (int32_t)ref->num_rows) * dy; - else r.bottom = r.top - screen_height * (float)ref->src_height / screen_height_px; - if (r.top <= screen_bottom || r.bottom >= screen_top) continue; // not visible + for (i = 0; i < self->image_count; i++) { + img = self->images + i; + bool was_drawn = img->is_drawn; + img->is_drawn = false; - r.left = screen_left + ref->start_column * dx + dx * (float)ref->cell_x_offset / (float) cell.width; - if (ref->num_cols > 0) r.right = screen_left + (ref->start_column + (int32_t)ref->num_cols) * dx; - else r.right = r.left + screen_width * (float)ref->src_width / screen_width_px; + for (j = 0; j < img->refcnt; j++) { ref = img->refs + j; + r.top = y0 - ref->start_row * dy - dy * (float)ref->cell_y_offset / (float)cell.height; + if (ref->num_rows > 0) r.bottom = y0 - (ref->start_row + (int32_t)ref->num_rows) * dy; + else r.bottom = r.top - screen_height * (float)ref->src_height / screen_height_px; + if (r.top <= screen_bottom || r.bottom >= screen_top) continue; // not visible - if (ref->z_index < ((int32_t)INT32_MIN/2)) - self->num_of_below_refs++; - else if (ref->z_index < 0) - self->num_of_negative_refs++; - else - self->num_of_positive_refs++; - ensure_space_for(self, render_data, ImageRenderData, self->count + 1, capacity, 64, true); - ImageRenderData *rd = self->render_data + self->count; - zero_at_ptr(rd); - set_vertex_data(rd, ref, &r); - self->count++; - rd->z_index = ref->z_index; rd->image_id = img->internal_id; - rd->texture_id = img->texture_id; - }} + r.left = screen_left + ref->start_column * dx + dx * (float)ref->cell_x_offset / (float) cell.width; + if (ref->num_cols > 0) r.right = screen_left + (ref->start_column + (int32_t)ref->num_cols) * dx; + else r.right = r.left + screen_width * (float)ref->src_width / screen_width_px; + + if (ref->z_index < ((int32_t)INT32_MIN/2)) + self->num_of_below_refs++; + else if (ref->z_index < 0) + self->num_of_negative_refs++; + else + self->num_of_positive_refs++; + ensure_space_for(self, render_data, ImageRenderData, self->count + 1, capacity, 64, true); + ImageRenderData *rd = self->render_data + self->count; + zero_at_ptr(rd); + set_vertex_data(rd, ref, &r); + self->count++; + rd->z_index = ref->z_index; rd->image_id = img->internal_id; + rd->texture_id = img->texture_id; + img->is_drawn = true; + } + if (img->is_drawn && !was_drawn && img->animation_enabled && img->extra_framecnt) { + self->has_images_needing_animation = true; + // TODO: rescan animation timeouts + } + } if (!self->count) return false; // Sort visible refs in draw order (z-index, img) #define lt(a, b) ( (a)->z_index < (b)->z_index || ((a)->z_index == (b)->z_index && (a)->image_id < (b)->image_id) ) @@ -798,10 +815,9 @@ update_current_frame(GraphicsManager *self, Image *img, void *data) { return; } } - if (send_to_gpu) { - send_image_to_gpu(&img->texture_id, data, img->width, img->height, img->is_opaque, img->is_4byte_aligned, false, REPEAT_CLAMP); - } + upload_to_gpu(self, img, data); if (needs_load) free(data); + img->current_frame_shown_at = monotonic(); } static Image* @@ -982,9 +998,40 @@ handle_animation_control_command(GraphicsManager *self, bool *is_dirty, const Gr } } if (g->_animation_enabled) { + bool was_enabled = img->animation_enabled; img->animation_enabled = g->_animation_enabled == 1; + if (img->animation_enabled) { + self->has_images_needing_animation = true; + if (!was_enabled) img->current_frame_shown_at = monotonic(); + // TODO: schedule animation rescan + } } } + +bool +scan_active_animations(GraphicsManager *self, const monotonic_t now, monotonic_t *minimum_gap) { + bool dirtied = false; + *minimum_gap = MONOTONIC_T_MAX; + if (!self->has_images_needing_animation) return dirtied; + self->has_images_needing_animation = false; + for (size_t i = self->image_count; i-- > 0;) { + Image *img = self->images + i; + if (img->animation_enabled && img->extra_framecnt && img->is_drawn) { + self->has_images_needing_animation = true; + Frame *next_frame = img->current_frame_index + 1 < img->extra_framecnt ? img->extra_frames + img->current_frame_index + 1 : &img->root_frame; + monotonic_t next_frame_at = img->current_frame_shown_at + next_frame->gap; + if (now >= next_frame_at) { + dirtied = true; + img->current_frame_index = (img->current_frame_index + 1) % (img->extra_framecnt + 1); + update_current_frame(self, img, NULL); + next_frame = img->current_frame_index + 1 < img->extra_framecnt ? img->extra_frames + img->current_frame_index + 1 : &img->root_frame; + } + next_frame_at = img->current_frame_shown_at + next_frame->gap; + if (next_frame_at - now < *minimum_gap) *minimum_gap = next_frame_at - now; + } + } + return dirtied; +} // }}} // Image lifetime/scrolling {{{ @@ -1203,6 +1250,7 @@ const char* grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, Cursor *c, bool *is_dirty, CellPixelSize cell) { const char *ret = NULL; command_response[0] = 0; + self->context_made_current_for_this_command = false; if (g->id && g->image_number) { set_command_failed_response("EINVAL", "Must not specify both image id and image number"); @@ -1343,11 +1391,6 @@ W(shm_unlink) { Py_RETURN_NONE; } -W(set_send_to_gpu) { - send_to_gpu = PyObject_IsTrue(args) ? true : false; - Py_RETURN_NONE; -} - W(update_layers) { unsigned int scrolled_by, sx, sy; float xstart, ystart, dx, dy; CellPixelSize cell; @@ -1396,7 +1439,6 @@ PyTypeObject GraphicsManager_Type = { static PyMethodDef module_methods[] = { M(shm_write, METH_VARARGS), M(shm_unlink, METH_VARARGS), - M(set_send_to_gpu, METH_O), {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/kitty/graphics.h b/kitty/graphics.h index 32676c225..5932a9f22 100644 --- a/kitty/graphics.h +++ b/kitty/graphics.h @@ -43,8 +43,7 @@ typedef struct { } ImageRef; typedef struct { - uint32_t gap; - uint32_t id; + uint32_t gap, id; } Frame; @@ -61,7 +60,8 @@ typedef struct { size_t refcnt, refcap, extra_framecnt; monotonic_t atime; size_t used_storage; - bool is_opaque, is_4byte_aligned, animation_enabled; + bool is_opaque, is_4byte_aligned, animation_enabled, is_drawn; + monotonic_t current_frame_shown_at; } Image; typedef struct { @@ -98,6 +98,8 @@ typedef struct { unsigned int last_scrolled_by; size_t used_storage; PyObject *disk_cache; + bool has_images_needing_animation, context_made_current_for_this_command; + id_type window_id; } GraphicsManager; @@ -116,3 +118,4 @@ void grman_resize(GraphicsManager*, index_type, index_type, index_type, index_ty void grman_rescale(GraphicsManager *self, CellPixelSize fg); void gpu_data_for_centered_image(ImageRenderData *ans, unsigned int screen_width_px, unsigned int screen_height_px, unsigned int width, unsigned int height); bool png_path_to_bitmap(const char *path, uint8_t** data, unsigned int* width, unsigned int* height, size_t* sz); +bool scan_active_animations(GraphicsManager *self, const monotonic_t now, monotonic_t *minimum_gap); diff --git a/kitty/screen.c b/kitty/screen.c index 96d7dc903..9ec50c117 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -131,6 +131,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { ) { Py_CLEAR(self); return NULL; } + self->main_grman->window_id = self->window_id; self->alt_grman->window_id = self->window_id; self->alt_tabstops = self->main_tabstops + self->columns; self->tabstops = self->main_tabstops; init_tabstops(self->main_tabstops, self->columns); diff --git a/kitty/state.c b/kitty/state.c index ecafaebf0..252c272b8 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -460,6 +460,34 @@ mark_os_window_for_close(OSWindow* w, CloseRequest cr) { w->close_request = cr; } +static bool +owners_for_window_id(id_type window_id, OSWindow **os_window, Tab **tab) { + if (os_window) *os_window = NULL; + if (tab) *tab = NULL; + for (size_t o = 0; o < global_state.num_os_windows; o++) { + OSWindow *osw = global_state.os_windows + o; + for (size_t t = 0; t < osw->num_tabs; t++) { + Tab *qtab = osw->tabs + t; + for (size_t w = 0; w < qtab->num_windows; w++) { + Window *window = qtab->windows + w; + if (window->id == window_id) { + if (os_window) *os_window = osw; + if (tab) *tab = qtab; + return true; + }}}} + return false; +} + + +bool +make_window_context_current(id_type window_id) { + OSWindow *os_window; + if (owners_for_window_id(window_id, &os_window, NULL)) { + make_os_window_context_current(os_window); + return true; + } + return false; +} // Python API {{{ diff --git a/kitty/state.h b/kitty/state.h index 750585e6f..1f03a6b8a 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -229,7 +229,7 @@ void update_os_window_viewport(OSWindow *window, bool); bool should_os_window_be_rendered(OSWindow* w); void wakeup_main_loop(void); void swap_window_buffers(OSWindow *w); -void make_window_context_current(OSWindow *w); +bool make_window_context_current(id_type); void hide_mouse(OSWindow *w); bool is_mouse_hidden(OSWindow *w); void destroy_os_window(OSWindow *w); diff --git a/kitty_tests/graphics.py b/kitty_tests/graphics.py index 41cb27441..778af6930 100644 --- a/kitty_tests/graphics.py +++ b/kitty_tests/graphics.py @@ -15,8 +15,7 @@ from typing import NamedTuple from kitty.constants import cache_dir from kitty.fast_data_types import ( - load_png_data, parse_bytes, set_send_to_gpu, shm_unlink, shm_write, - xor_data + load_png_data, parse_bytes, shm_unlink, shm_write, xor_data ) from . import BaseTest @@ -26,8 +25,6 @@ try: except ImportError: Image = None -set_send_to_gpu(False) - def relpath(name): base = os.path.dirname(os.path.abspath(__file__))