diff --git a/kitty/gl.h b/kitty/gl.h index f4518b3a5..ce8af4132 100644 --- a/kitty/gl.h +++ b/kitty/gl.h @@ -97,6 +97,19 @@ free_texture_impl(GLuint *tex_id) { *tex_id = 0; } +static void +send_image_to_gpu_impl(GLuint *tex_id, const void* data, GLsizei width, GLsizei height, bool is_rgb, bool is_4byte_aligned) { + if (!(*tex_id)) { glGenTextures(1, tex_id); check_gl(); } + glBindTexture(GL_TEXTURE_2D, *tex_id); check_gl(); + glPixelStorei(GL_UNPACK_ALIGNMENT, is_4byte_aligned ? 4 : 1); check_gl(); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); check_gl(); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, is_rgb ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, data); check_gl(); +} + + // }}} // Programs {{{ diff --git a/kitty/graphics.c b/kitty/graphics.c index b6ad0d808..b01cba223 100644 --- a/kitty/graphics.c +++ b/kitty/graphics.c @@ -19,6 +19,8 @@ #define REPORT_ERROR(...) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } +static bool send_to_gpu = true; + GraphicsManager* grman_realloc(GraphicsManager *old, index_type lines, index_type columns) { GraphicsManager *self = (GraphicsManager *)GraphicsManager_Type.tp_alloc(&GraphicsManager_Type, 0); @@ -53,6 +55,7 @@ free_load_data(LoadData *ld) { } free_texture_func free_texture = NULL; +send_image_to_gpu_func send_image_to_gpu = NULL; static inline void free_image(Image *img) { @@ -306,11 +309,11 @@ remove_images(GraphicsManager *self, bool(*predicate)(Image*)) { static Image* handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, bool *is_dirty) { -#define ABRT(code, ...) { set_add_response(#code, __VA_ARGS__); self->loading_image = 0; return NULL; } +#define ABRT(code, ...) { set_add_response(#code, __VA_ARGS__); self->loading_image = 0; if (img) img->data_loaded = false; return NULL; } #define MAX_DATA_SZ (4 * 100000000) has_add_respose = false; bool existing, init_img = true; - Image *img; + Image *img = NULL; unsigned char tt = g->transmission_type ? g->transmission_type : 'd'; enum FORMATS { RGB=24, RGBA=32, PNG=100 }; uint32_t fmt = g->format ? g->format : RGBA; @@ -335,6 +338,7 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_ case PNG: if (g->data_sz > MAX_DATA_SZ) ABRT(EINVAL, "PNG data size too large"); img->load_data.is_4byte_aligned = true; + img->load_data.is_rgb = false; img->load_data.data_sz = g->data_sz ? g->data_sz : 1024 * 100; break; case RGB: @@ -342,6 +346,7 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_ img->load_data.data_sz = g->data_width * g->data_height * (fmt / 8); if (!img->load_data.data_sz) ABRT(EINVAL, "Zero width/height not allowed"); img->load_data.is_4byte_aligned = fmt == RGBA || (img->width % 4 == 0); + img->load_data.is_rgb = fmt == RGB; break; default: ABRT(EINVAL, "Unknown image format: %u", fmt); @@ -413,7 +418,6 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_ break; default: ABRT(EINVAL, "Unknown image compression: %c", g->compressed); - img->data_loaded = false; return NULL; } switch(fmt) { case PNG: @@ -428,7 +432,6 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_ img->load_data.data = img->load_data.buf; if (img->load_data.buf_used < img->load_data.data_sz) { ABRT(ENODATA, "Insufficient image data: %zu < %zu", img->load_data.buf_used, img->load_data.data_sz); - img->data_loaded = false; } if (img->load_data.mapped_file) { munmap(img->load_data.mapped_file, img->load_data.mapped_file_sz); @@ -438,15 +441,19 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_ if (tt == 'd') { if (img->load_data.buf_used < img->load_data.data_sz) { ABRT(ENODATA, "Insufficient image data: %zu < %zu", img->load_data.buf_used, img->load_data.data_sz); - img->data_loaded = false; } else img->load_data.data = img->load_data.buf; } else { if (img->load_data.mapped_file_sz < img->load_data.data_sz) { ABRT(ENODATA, "Insufficient image data: %zu < %zu", img->load_data.mapped_file_sz, img->load_data.data_sz); - img->data_loaded = false; } else img->load_data.data = img->load_data.mapped_file; } } + size_t required_sz = (img->load_data.is_rgb ? 3 : 4) * img->width * img->height; + if (img->load_data.data_sz != required_sz) ABRT(EINVAL, "Image dimensions: %ux%u do not match data size: %zu, expected size: %zu", img->width, img->height, img->load_data.data_sz, required_sz); + if (LIKELY(img->data_loaded && send_to_gpu)) { + send_image_to_gpu(&img->texture_id, img->load_data.data, img->width, img->height, img->load_data.is_rgb, img->load_data.is_4byte_aligned); + free_load_data(&img->load_data); + } return img; #undef MAX_DATA_SZ #undef ABRT @@ -470,16 +477,20 @@ create_add_response(GraphicsManager UNUSED *self, const GraphicsCommand *g, bool // Displaying images {{{ +#define ensure_space_for(base, array, type, num, capacity, initial_cap) \ + if (base->capacity < num) { \ + base->capacity = MAX(initial_cap, 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 void handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty, Image *img) { if (img == NULL) img = img_by_client_id(self, g->id); if (img == NULL) { REPORT_ERROR("Put command refers to non-existent image with id: %u", g->id); return; } if (!img->data_loaded) { REPORT_ERROR("Put command refers to image with id: %u that could not load its data", g->id); return; } - if (img->refcnt >= img->refcap) { - img->refcap = MAX(10, img->refcap * 2); - img->refs = realloc(img->refs, img->refcap * sizeof(ImageRef)); - if (img->refs == NULL) { REPORT_ERROR("Out of memory growing image refs array"); img->refcap = 0; return; } - } + ensure_space_for(img, refs, ImageRef, img->refcnt + 1, refcap, 10); *is_dirty = true; self->layers_dirty = true; ImageRef *ref = NULL; @@ -519,13 +530,6 @@ handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, b 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_; @@ -549,7 +553,7 @@ grman_update_layers(GraphicsManager *self, unsigned int scrolled_by) { 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); + ensure_space_for(self, render_data, ImageRenderData, self->count + 1, capacity, 100); ImageRenderData *rd = self->render_data + self->count; self->count++; rd->z_index = ref->z_index; rd->image_id = img->internal_id; @@ -557,7 +561,7 @@ grman_update_layers(GraphicsManager *self, unsigned int scrolled_by) { }} if (!self->count) return; // Sort visible refs in draw order (z-index, img) - ensure_space_for(self, render_pointers, ImageRenderData*, self->count, rp_capacity); + ensure_space_for(self, render_pointers, ImageRenderData*, self->count, rp_capacity, 100); 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); } @@ -654,12 +658,15 @@ W(shm_unlink) { Py_RETURN_NONE; } +W(set_send_to_gpu) { + send_to_gpu = PyObject_IsTrue(args) ? true : false; + Py_RETURN_NONE; +} + #define M(x, va) {#x, (PyCFunction)py##x, va, ""} static PyMethodDef methods[] = { M(image_for_client_id, METH_O), - M(shm_write, METH_VARARGS), - M(shm_unlink, METH_VARARGS), {NULL} /* Sentinel */ }; @@ -675,10 +682,19 @@ PyTypeObject GraphicsManager_Type = { .tp_methods = methods, }; +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 */ +}; + + bool init_graphics(PyObject *module) { if (PyType_Ready(&GraphicsManager_Type) < 0) return false; if (PyModule_AddObject(module, "GraphicsManager", (PyObject *)&GraphicsManager_Type) != 0) return false; + if (PyModule_AddFunctions(module, module_methods) != 0) return false; Py_INCREF(&GraphicsManager_Type); return true; } diff --git a/kitty/graphics.h b/kitty/graphics.h index 6b415bb47..97948e21c 100644 --- a/kitty/graphics.h +++ b/kitty/graphics.h @@ -25,6 +25,7 @@ typedef struct { size_t data_sz; uint8_t *data; bool is_4byte_aligned; + bool is_rgb; } LoadData; typedef struct { diff --git a/kitty/shaders.c b/kitty/shaders.c index e3d28b711..5feaffec2 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -561,6 +561,7 @@ init_shaders(PyObject *module) { draw_cells = &draw_cells_impl; draw_cursor = &draw_cursor_impl; free_texture = &free_texture_impl; + send_image_to_gpu = &send_image_to_gpu_impl; return true; } // }}} diff --git a/kitty/state.h b/kitty/state.h index 7f538ce47..8dd9d4fea 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -102,3 +102,4 @@ EXTERNAL_FUNC(draw_cells, void, ssize_t, float, float, float, float, Screen *, C EXTERNAL_FUNC(draw_cursor, void, CursorRenderInfo *); EXTERNAL_FUNC(update_viewport_size, void, int, int); EXTERNAL_FUNC(free_texture, void, uint32_t*); +EXTERNAL_FUNC(send_image_to_gpu, void, uint32_t*, const void*, int32_t width, int32_t height, bool is_rgba, bool is_4byte_aligned); diff --git a/kitty_tests/graphics.py b/kitty_tests/graphics.py index ef8fd92d1..aef38c318 100644 --- a/kitty_tests/graphics.py +++ b/kitty_tests/graphics.py @@ -9,7 +9,7 @@ import zlib from base64 import standard_b64encode from io import BytesIO -from kitty.fast_data_types import parse_bytes +from kitty.fast_data_types import parse_bytes, shm_write, shm_unlink, set_send_to_gpu from . import BaseTest @@ -18,6 +18,8 @@ try: except ImportError: Image = None +set_send_to_gpu(False) + def relpath(name): base = os.path.dirname(os.path.abspath(__file__)) @@ -110,10 +112,10 @@ class TestGraphics(BaseTest): # Test loading from POSIX SHM name = '/kitty-test-shm' - g.shm_write(name, random_data) + shm_write(name, random_data) sl(name, s=24, v=32, t='s', expecting_data=random_data) self.assertRaises( - FileNotFoundError, g.shm_unlink, name + FileNotFoundError, shm_unlink, name ) # check that file was deleted @unittest.skipIf(Image is None, 'PIL not available, skipping PNG tests')