From 28ae99ed37b026f0e72a8e26f1fca448dc365c48 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 30 Sep 2017 14:46:17 +0530 Subject: [PATCH] More work on displaying images --- kitty/graphics.c | 65 +++++++++++++++++++++++++++++++++++++++++-- kitty/graphics.h | 26 +++++++++++++---- kitty/parser.c | 10 ++++--- kitty/screen.c | 4 +++ kitty_tests/parser.py | 3 +- 5 files changed, 94 insertions(+), 14 deletions(-) diff --git a/kitty/graphics.c b/kitty/graphics.c index 3d3480711..b6ad0d808 100644 --- a/kitty/graphics.c +++ b/kitty/graphics.c @@ -12,6 +12,8 @@ #include #include #include +#include + #include #include @@ -61,10 +63,12 @@ free_image(Image *img) { static void dealloc(GraphicsManager* self) { + size_t i; if (self->images) { - for (size_t i = 0; i < self->image_count; i++) free_image(self->images + i); + for (i = 0; i < self->image_count; i++) free_image(self->images + i); free(self->images); } + free(self->render_pointers); free(self->render_data); Py_TYPE(self)->tp_free((PyObject*)self); } @@ -294,6 +298,7 @@ remove_images(GraphicsManager *self, bool(*predicate)(Image*)) { if (predicate(self->images + i)) { free_image(self->images + i); remove_from_array(self->images, sizeof(Image), i, self->image_count--); + self->layers_dirty = true; } } } @@ -490,17 +495,71 @@ handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, b ref->src_height = MIN(ref->src_height, img->height - (img->height > ref->src_y ? ref->src_y : img->height)); ref->z_index = g->z_index; ref->start_row = c->y; ref->start_column = c->x; + ref->cell_x_offset = MIN(g->cell_x_offset, global_state.cell_width - 1); + ref->cell_y_offset = MIN(g->cell_y_offset, global_state.cell_height - 1); uint32_t num_cols = g->num_cells, num_rows = g->num_lines; if (num_cols == 0) { - num_cols = ref->src_width / global_state.cell_width; + num_cols = (ref->src_width + ref->cell_x_offset) / global_state.cell_width; if (ref->src_width > num_cols * global_state.cell_width) num_cols += 1; } if (num_rows == 0) { - num_rows = ref->src_height / global_state.cell_height; + num_rows = (ref->src_height + ref->cell_y_offset) / global_state.cell_height; if (ref->src_height > num_rows * global_state.cell_height) num_rows += 1; } ref->end_row = ref->start_row + num_rows; ref->end_column = ref->start_column + num_cols; + + // The src rect in OpenGL co-ords [0, 1] with origin at top-left corner of image + ref->src_rect.left = (float)ref->src_x / (float)img->width; + ref->src_rect.right = (float)(ref->src_x + ref->src_width) / (float)img->width; + ref->src_rect.top = (float)ref->src_y / (float)img->height; + ref->src_rect.bottom = (float)(ref->src_y + ref->src_height) / (float)img->height; + + // Move the cursor, the screen will take care of ensuring it is in bounds + c->x += num_cols; c->y += num_rows; +} + +#define ensure_space_for(base, array, type, num, capacity) \ + if (base->capacity < num) { \ + base->capacity = MAX(100, MAX(2 * base->capacity, num)); \ + base->array = realloc(base->array, sizeof(type) * base->capacity); \ + if (base->array == NULL) fatal("Out of memory while ensuring space in array"); \ + } + +static int +cmp_by_zindex_and_image(const void *a_, const void *b_) { + const ImageRenderData *a = (const ImageRenderData*)a_, *b = (const ImageRenderData*)b_; + int ans = a->z_index - b->z_index; + if (ans == 0) ans = a->image_id - b->image_id; + return ans; +} + +void +grman_update_layers(GraphicsManager *self, unsigned int scrolled_by) { + if (self->last_scrolled_by != scrolled_by) self->layers_dirty = true; + self->last_scrolled_by = scrolled_by; + if (!self->layers_dirty) return; + self->layers_dirty = false; + size_t i, j; + self->num_of_negative_refs = 0; self->num_of_positive_refs = 0; + Image *img; ImageRef *ref; + + // 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; + /* TODO: calculate geometry and ignore refs outside of current viewport */ + 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); + ImageRenderData *rd = self->render_data + self->count; + self->count++; + rd->z_index = ref->z_index; rd->image_id = img->internal_id; + rd->src_rect = ref->src_rect; + }} + if (!self->count) return; + // Sort visible refs in draw order (z-index, img) + ensure_space_for(self, render_pointers, ImageRenderData*, self->count, rp_capacity); + for (i = 0; i < self->count; i++) self->render_pointers[i] = self->render_data + i; + qsort(self->render_pointers, self->count, sizeof(ImageRenderData*), cmp_by_zindex_and_image); } // }}} diff --git a/kitty/graphics.h b/kitty/graphics.h index ef1406a32..d126fc426 100644 --- a/kitty/graphics.h +++ b/kitty/graphics.h @@ -10,11 +10,9 @@ typedef struct { unsigned char action, transmission_type, compressed; uint32_t format, more, id, data_sz, data_offset; - uint32_t width, height, x_offset, y_offset, data_height, data_width, num_cells, num_lines; + uint32_t width, height, x_offset, y_offset, data_height, data_width, num_cells, num_lines, cell_x_offset, cell_y_offset; int32_t z_index; size_t payload_sz; - - bool layers_dirty; } GraphicsCommand; typedef struct { @@ -29,11 +27,16 @@ typedef struct { bool is_4byte_aligned; } LoadData; +typedef struct { + float left, top, right, bottom; +} Rect; + typedef struct { uint32_t src_width, src_height, src_x, src_y; - uint32_t dest_x_offset, dest_y_offset; - int z_index; - int start_row, start_column, end_row, end_column; + uint32_t dest_x_offset, dest_y_offset, cell_x_offset, cell_y_offset; + int32_t z_index; + int32_t start_row, start_column, end_row, end_column; + Rect src_rect; } ImageRef; @@ -48,6 +51,12 @@ typedef struct { size_t refcnt, refcap; } Image; +typedef struct { + Rect src_rect, dest_rect; + uint32_t texture_id, group_count; + int z_index; + size_t image_id; +} ImageRenderData; typedef struct { PyObject_HEAD @@ -55,6 +64,11 @@ typedef struct { index_type lines, columns; size_t image_count, images_capacity, loading_image; Image *images; + size_t count, capacity, rp_capacity; + ImageRenderData *render_data, **render_pointers; + bool layers_dirty; + size_t num_of_negative_refs, num_of_positive_refs; + unsigned int last_scrolled_by; } GraphicsManager; PyTypeObject GraphicsManager_Type; diff --git a/kitty/parser.c b/kitty/parser.c index cef74be00..17e3d391e 100644 --- a/kitty/parser.c +++ b/kitty/parser.c @@ -555,6 +555,8 @@ parse_graphics_code(Screen *screen, PyObject UNUSED *dump_callback) { data_offset = 'O', num_cells = 'c', num_lines = 'r', + cell_x_offset = 'X', + cell_y_offset = 'Y', z_index = 'z' }; enum KEYS key = 'a'; @@ -576,7 +578,7 @@ parse_graphics_code(Screen *screen, PyObject UNUSED *dump_callback) { #define KS(n, vs) case n: state = EQUAL; value_state = vs; break #define U(x) KS(x, UINT) KS(action, FLAG); KS(transmission_type, FLAG); KS(compressed, FLAG); KS(z_index, INT); - U(format); U(more); U(id); U(data_sz); U(data_offset); U(width); U(height); U(x_offset); U(y_offset); U(data_height); U(data_width); U(num_cells); U(num_lines); + U(format); U(more); U(id); U(data_sz); U(data_offset); U(width); U(height); U(x_offset); U(y_offset); U(data_height); U(data_width); U(num_cells); U(num_lines); U(cell_x_offset); U(cell_y_offset); #undef U #undef KS default: @@ -629,7 +631,7 @@ parse_graphics_code(Screen *screen, PyObject UNUSED *dump_callback) { READ_UINT; #define U(x) case x: g.x = code; break switch(key) { - U(format); U(more); U(id); U(data_sz); U(data_offset); U(width); U(height); U(x_offset); U(y_offset); U(data_height); U(data_width); U(num_cells); U(num_lines); + U(format); U(more); U(id); U(data_sz); U(data_offset); U(width); U(height); U(x_offset); U(y_offset); U(data_height); U(data_width); U(num_cells); U(num_lines); U(cell_x_offset); U(cell_y_offset); default: break; } state = AFTER_VALUE; @@ -673,10 +675,10 @@ parse_graphics_code(Screen *screen, PyObject UNUSED *dump_callback) { #define A(x) #x, g.x #define U(x) #x, (unsigned int)(g.x) #define I(x) #x, (int)(g.x) - REPORT_VA_COMMAND("s {sc sc sc sI sI sI sI sI sI sI sI sI sI sI sI sI sI si} y#", "graphics_command", + REPORT_VA_COMMAND("s {sc sc sc sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI si} y#", "graphics_command", A(action), A(transmission_type), A(compressed), U(format), U(more), U(id), U(data_sz), U(data_offset), - U(width), U(height), U(x_offset), U(y_offset), U(data_height), U(data_width), U(num_cells), U(num_lines), + U(width), U(height), U(x_offset), U(y_offset), U(data_height), U(data_width), U(num_cells), U(num_lines), U(cell_x_offset), U(cell_y_offset), U(payload_sz), I(z_index), payload, g.payload_sz ); diff --git a/kitty/screen.c b/kitty/screen.c index 0071375bc..eb34810cf 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -418,8 +418,12 @@ write_to_child(Screen *self, const char *data, size_t sz) { void screen_handle_graphics_command(Screen *self, const GraphicsCommand *cmd, const uint8_t *payload) { + unsigned int x = self->cursor->x, y = self->cursor->y; const char *response = grman_handle_command(self->grman, cmd, payload, self->cursor, &self->is_dirty); if (response != NULL) write_to_child(self, response, strlen(response)); + if (x != self->cursor->x || y != self->cursor->y) { + fatal("TODO: Scroll the screen if needed and ensure cursor is in bounds, taking into account the margins"); + } } // }}} diff --git a/kitty_tests/parser.py b/kitty_tests/parser.py index 9334ae8e6..7d7ce6e99 100644 --- a/kitty_tests/parser.py +++ b/kitty_tests/parser.py @@ -204,7 +204,8 @@ class TestParser(BaseTest): k[p] = v.encode('ascii') for f in 'action transmission_type compressed'.split(): k.setdefault(f, b'\0') - for f in 'format more id data_sz data_offset width height x_offset y_offset data_height data_width num_cells num_lines z_index'.split(): + for f in ('format more id data_sz data_offset width height x_offset y_offset data_height data_width' + ' num_cells num_lines cell_x_offset cell_y_offset z_index').split(): k.setdefault(f, 0) p = k.pop('payload', '').encode('utf-8') k['payload_sz'] = len(p)