Move the sprites GPU code to shaders.c
This commit is contained in:
parent
cc8271b766
commit
3a883ad436
@ -15,15 +15,14 @@ from .constants import (
|
||||
from .fast_data_types import (
|
||||
GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA, GLFW_CURSOR, GLFW_CURSOR_HIDDEN,
|
||||
GLFW_CURSOR_NORMAL, GLFW_MOUSE_BUTTON_1, GLFW_PRESS, GLFW_REPEAT,
|
||||
ChildMonitor, Timers as _Timers, glBlendFunc, glfw_post_empty_event,
|
||||
glViewport, draw_borders
|
||||
ChildMonitor, Timers as _Timers, destroy_sprite_map, draw_borders,
|
||||
glBlendFunc, glfw_post_empty_event, glViewport, layout_sprite_map
|
||||
)
|
||||
from .fonts.render import set_font_family
|
||||
from .fonts.render import render_cell_wrapper, set_font_family
|
||||
from .keys import (
|
||||
get_sent_data, get_shortcut, interpret_key_event, interpret_text_event
|
||||
)
|
||||
from .session import create_session
|
||||
from .shaders import Sprites
|
||||
from .tabs import SpecialWindow, TabManager
|
||||
from .utils import safe_print
|
||||
|
||||
@ -118,8 +117,7 @@ class Boss:
|
||||
load_shader_programs()
|
||||
self.tab_manager = TabManager(opts, args)
|
||||
self.tab_manager.init(startup_session)
|
||||
self.sprites = Sprites()
|
||||
self.sprites.do_layout(cell_size.width, cell_size.height)
|
||||
layout_sprite_map(cell_size.width, cell_size.height, render_cell_wrapper)
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
||||
self.glfw_window.set_click_cursor(False)
|
||||
self.show_mouse_cursor()
|
||||
@ -201,7 +199,7 @@ class Boss:
|
||||
self.current_font_size = new_size
|
||||
cell_size.width, cell_size.height = set_font_family(
|
||||
self.opts, override_font_size=self.current_font_size)
|
||||
self.sprites.do_layout(cell_size.width, cell_size.height)
|
||||
layout_sprite_map(cell_size.width, cell_size.height, render_cell_wrapper)
|
||||
self.resize_windows_after_font_size_change()
|
||||
for window in self.window_id_map.values():
|
||||
if window is not None:
|
||||
@ -373,26 +371,24 @@ class Boss:
|
||||
self.glfw_window.set_title(self.glfw_window_title)
|
||||
if isosx:
|
||||
cocoa_update_title(self.glfw_window_title)
|
||||
with self.sprites:
|
||||
self.sprites.render_dirty_sprites()
|
||||
draw_borders()
|
||||
self.tab_manager.render()
|
||||
for window in tab.visible_windows():
|
||||
if not window.needs_layout:
|
||||
window.render_cells()
|
||||
active = self.active_window
|
||||
if active is not None:
|
||||
draw_cursor = True
|
||||
if self.cursor_blinking and self.opts.cursor_blink_interval > 0 and self.window_is_focused:
|
||||
now = monotonic() - self.cursor_blink_zero_time
|
||||
t = int(now * 1000)
|
||||
d = int(self.opts.cursor_blink_interval * 1000)
|
||||
n = t // d
|
||||
draw_cursor = n % 2 == 0
|
||||
self.ui_timers.add_if_before(
|
||||
((n + 1) * d / 1000) - now, None)
|
||||
if draw_cursor:
|
||||
active.char_grid.render_cursor(self.window_is_focused)
|
||||
draw_borders()
|
||||
self.tab_manager.render()
|
||||
for window in tab.visible_windows():
|
||||
if not window.needs_layout:
|
||||
window.render_cells()
|
||||
active = self.active_window
|
||||
if active is not None:
|
||||
draw_cursor = True
|
||||
if self.cursor_blinking and self.opts.cursor_blink_interval > 0 and self.window_is_focused:
|
||||
now = monotonic() - self.cursor_blink_zero_time
|
||||
t = int(now * 1000)
|
||||
d = int(self.opts.cursor_blink_interval * 1000)
|
||||
n = t // d
|
||||
draw_cursor = n % 2 == 0
|
||||
self.ui_timers.add_if_before(
|
||||
((n + 1) * d / 1000) - now, None)
|
||||
if draw_cursor:
|
||||
active.char_grid.render_cursor(self.window_is_focused)
|
||||
|
||||
def gui_close_window(self, window):
|
||||
window.char_grid.destroy()
|
||||
@ -418,8 +414,7 @@ class Boss:
|
||||
for t in self.tab_manager:
|
||||
t.destroy()
|
||||
del self.tab_manager
|
||||
self.sprites.destroy()
|
||||
del self.sprites
|
||||
destroy_sprite_map()
|
||||
del self.glfw_window
|
||||
|
||||
def paste_from_clipboard(self):
|
||||
|
||||
@ -327,7 +327,6 @@ bool set_iutf8(int, bool);
|
||||
|
||||
color_type colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval);
|
||||
void copy_color_table_to_buffer(ColorProfile *self, void *address, int offset, size_t stride);
|
||||
void sprite_map_current_layout(unsigned int *x, unsigned int *y, unsigned int*);
|
||||
|
||||
unsigned int safe_wcwidth(uint32_t ch);
|
||||
void change_wcwidth(bool use9);
|
||||
|
||||
@ -57,6 +57,22 @@ def render_cell(text=' ', bold=False, italic=False, underline=0, strikethrough=F
|
||||
return first, second
|
||||
|
||||
|
||||
class Buf:
|
||||
|
||||
def __init__(self, buf):
|
||||
self.buf = buf
|
||||
|
||||
def __call__(self):
|
||||
return ctypes.addressof(self.buf)
|
||||
|
||||
|
||||
def render_cell_wrapper(text, bold, italic, underline, strikethrough, is_second):
|
||||
first, second = render_cell(text, bold, italic, underline, strikethrough)
|
||||
ans = second if is_second else first
|
||||
ans = ans or render_cell()[0]
|
||||
return Buf(ans)
|
||||
|
||||
|
||||
def join_cells(cell_width, cell_height, *cells):
|
||||
dstride = len(cells) * cell_width
|
||||
ans = (ctypes.c_ubyte * (cell_height * dstride))()
|
||||
|
||||
182
kitty/shaders.c
182
kitty/shaders.c
@ -7,6 +7,7 @@
|
||||
|
||||
#include "data-types.h"
|
||||
#include "screen.h"
|
||||
#include "sprites.h"
|
||||
#ifdef __APPLE__
|
||||
#include <OpenGL/gl3.h>
|
||||
#include <OpenGL/gl3ext.h>
|
||||
@ -33,7 +34,6 @@ static char glbuf[4096];
|
||||
#endif
|
||||
|
||||
#define fatal(...) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); exit(EXIT_FAILURE); }
|
||||
#define fatal_msg(msg) fatal("%s", msg);
|
||||
|
||||
#ifdef ENABLE_DEBUG_GL
|
||||
static void
|
||||
@ -382,6 +382,171 @@ unmap_vao_buffer(ssize_t vao_idx, size_t bufnum) {
|
||||
|
||||
// }}}
|
||||
|
||||
// Sprites {{{
|
||||
typedef struct {
|
||||
int xnum, ynum, x, y, z, last_num_of_layers, last_ynum;
|
||||
unsigned int cell_width, cell_height;
|
||||
GLuint texture_id;
|
||||
GLenum texture_unit;
|
||||
GLint max_texture_size, max_array_texture_layers;
|
||||
PyObject *render_cell;
|
||||
} SpriteMap;
|
||||
|
||||
static SpriteMap sprite_map = { .xnum = 1, .ynum = 1, .last_num_of_layers = 1, .last_ynum = -1, .texture_unit = GL_TEXTURE0 };
|
||||
|
||||
#ifdef __APPLE__
|
||||
#ifdef glCopyImageSubData
|
||||
#define GLEW_ARB_copy_image true
|
||||
#else
|
||||
#define GLEW_ARB_copy_image false
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static bool copy_image_warned = false;
|
||||
|
||||
static void
|
||||
copy_image_sub_data(GLuint src_texture_id, GLuint dest_texture_id, unsigned int width, unsigned int height, unsigned int num_levels) {
|
||||
if (!GLEW_ARB_copy_image) {
|
||||
// ARB_copy_image not available, do a slow roundtrip copy
|
||||
if (!copy_image_warned) {
|
||||
copy_image_warned = true;
|
||||
fprintf(stderr, "WARNING: Your system's OpenGL implementation does not have glCopyImageSubData, falling back to a slower implementation.\n");
|
||||
}
|
||||
uint8_t *src = malloc(5 * width * height * num_levels);
|
||||
if (src == NULL) { fatal("Out of memory."); }
|
||||
uint8_t *dest = src + (4 * width * height * num_levels);
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, src_texture_id); check_gl();
|
||||
glGetTexImage(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, GL_UNSIGNED_BYTE, src); check_gl();
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, dest_texture_id); check_gl();
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); check_gl();
|
||||
for(size_t i = 0; i < width * height * num_levels; i++) dest[i] = src[4*i];
|
||||
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, num_levels, GL_RED, GL_UNSIGNED_BYTE, dest); check_gl();
|
||||
free(src);
|
||||
} else {
|
||||
glCopyImageSubData(src_texture_id, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, dest_texture_id, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, num_levels); check_gl();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
realloc_sprite_texture() {
|
||||
const GLenum tgt = GL_TEXTURE_2D_ARRAY;
|
||||
GLuint tex;
|
||||
glGenTextures(1, &tex); check_gl();
|
||||
glBindTexture(tgt, tex); check_gl();
|
||||
// We use GL_NEAREST otherwise glyphs that touch the edge of the cell
|
||||
// often show a border between cells
|
||||
glTexParameteri(tgt, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(tgt, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(tgt, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(tgt, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); check_gl();
|
||||
unsigned int xnum, ynum, z, znum, width, height, src_ynum;
|
||||
sprite_map_current_layout(&xnum, &ynum, &z);
|
||||
znum = z + 1;
|
||||
width = xnum * sprite_map.cell_width; height = ynum * sprite_map.cell_height;
|
||||
glTexStorage3D(tgt, 1, GL_R8, width, height, znum); check_gl();
|
||||
if (sprite_map.texture_id) {
|
||||
// need to re-alloc
|
||||
src_ynum = z == 0 ? ynum - 1 : ynum; // Only copy the previous rows
|
||||
copy_image_sub_data(sprite_map.texture_id, tex, width, src_ynum * sprite_map.cell_height, sprite_map.last_num_of_layers);
|
||||
glDeleteTextures(1, &sprite_map.texture_id); check_gl();
|
||||
}
|
||||
sprite_map.last_num_of_layers = znum;
|
||||
sprite_map.last_ynum = ynum;
|
||||
sprite_map.texture_id = tex;
|
||||
}
|
||||
|
||||
static inline PyObject*
|
||||
render_cell(PyObject *text, bool bold, bool italic, unsigned int underline, bool strikethrough, bool is_second) {
|
||||
#define B(x) (x ? Py_True : Py_False)
|
||||
PyObject *ret = PyObject_CallFunction(sprite_map.render_cell, "OOOIOO", text, B(bold), B(italic), underline, B(strikethrough), B(is_second));
|
||||
if (ret == NULL) { PyErr_Print(); fatal("Rendering of a cell failed, aborting"); }
|
||||
return ret;
|
||||
#undef B
|
||||
}
|
||||
|
||||
static inline int
|
||||
bind_sprite_map() {
|
||||
if (!sprite_map.texture_id) realloc_sprite_texture();
|
||||
glActiveTexture(GL_TEXTURE0); check_gl();
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map.texture_id); check_gl();
|
||||
return 0; // corresponds to GL_TEXTURE0
|
||||
}
|
||||
|
||||
static inline void
|
||||
unbind_sprite_map() {
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, 0); check_gl();
|
||||
}
|
||||
|
||||
static void
|
||||
sprite_send_to_gpu(unsigned int x, unsigned int y, unsigned int z, PyObject *buf) {
|
||||
unsigned int xnum, ynum, znum;
|
||||
sprite_map_current_layout(&xnum, &ynum, &znum);
|
||||
if ((int)znum >= sprite_map.last_num_of_layers || (znum == 0 && (int)ynum > sprite_map.last_ynum)) realloc_sprite_texture();
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map.texture_id); check_gl();
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); check_gl();
|
||||
x *= sprite_map.cell_width; y *= sprite_map.cell_height;
|
||||
PyObject *ret = PyObject_CallObject(buf, NULL);
|
||||
if (ret == NULL) { PyErr_Print(); fatal("Failed to get address of rendered cell buffer"); }
|
||||
void *address = PyLong_AsVoidPtr(ret);
|
||||
Py_DECREF(ret);
|
||||
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, x, y, z, sprite_map.cell_width, sprite_map.cell_height, 1, GL_RED, GL_UNSIGNED_BYTE, address); check_gl();
|
||||
Py_DECREF(buf);
|
||||
}
|
||||
|
||||
static void
|
||||
render_and_send_dirty_sprites(PyObject *text, bool bold, bool italic, bool is_second, sprite_index x, sprite_index y, sprite_index z) {
|
||||
if (text == NULL) { fatal("The text for a sprite was NULL, probably out of memory."); }
|
||||
PyObject *buf = render_cell(text, bold, italic, false, false, is_second);
|
||||
sprite_send_to_gpu(x, y, z, buf);
|
||||
}
|
||||
|
||||
static inline sprite_index
|
||||
send_prerendered(unsigned int underline, bool strikethrough) {
|
||||
sprite_index x, y, z;
|
||||
PyObject *blank = PyUnicode_FromString(" ");
|
||||
if (blank == NULL) { fatal("Out of memory"); }
|
||||
PyObject *buf = render_cell(blank, false, false, underline, strikethrough, false);
|
||||
Py_CLEAR(blank);
|
||||
if (sprite_map_increment(&x, &y, &z) != 0) { fatal("Failed to increment sprite map for prerendering"); }
|
||||
sprite_send_to_gpu(x, y, z, buf);
|
||||
return x;
|
||||
}
|
||||
|
||||
static void
|
||||
layout_sprite_map(unsigned int cell_width, unsigned int cell_height, PyObject *render_cell) {
|
||||
sprite_map.cell_width = MAX(1, cell_width);
|
||||
sprite_map.cell_height = MAX(1, cell_height);
|
||||
if (sprite_map.max_texture_size == 0) {
|
||||
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &(sprite_map.max_texture_size));
|
||||
glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &(sprite_map.max_array_texture_layers));
|
||||
check_gl();
|
||||
sprite_map_set_limits(sprite_map.max_texture_size, sprite_map.max_array_texture_layers);
|
||||
}
|
||||
sprite_map_set_layout(cell_width, cell_height);
|
||||
Py_CLEAR(sprite_map.render_cell);
|
||||
sprite_map.render_cell = render_cell; Py_INCREF(sprite_map.render_cell);
|
||||
if (!sprite_map.texture_id) realloc_sprite_texture();
|
||||
// Pre-render the basic cells to ensure they have known sprite numbers
|
||||
send_prerendered(0, false);
|
||||
send_prerendered(1, false);
|
||||
send_prerendered(2, false);
|
||||
if (send_prerendered(0, true) != 3) { fatal("Available OpenGL texture size is too small"); }
|
||||
}
|
||||
|
||||
static void
|
||||
destroy_sprite_map() {
|
||||
sprite_map_free();
|
||||
Py_CLEAR(sprite_map.render_cell);
|
||||
if (sprite_map.texture_id) {
|
||||
glDeleteTextures(1, &(sprite_map.texture_id));
|
||||
check_gl();
|
||||
sprite_map.texture_id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
// Cell {{{
|
||||
|
||||
enum CellUniforms { CELL_dimensions, CELL_default_colors, CELL_color_indices, CELL_steps, CELL_sprites, CELL_sprite_layout, CELL_color_table, NUM_CELL_UNIFORMS };
|
||||
@ -455,6 +620,8 @@ draw_cells(ssize_t vao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat
|
||||
copy_color_table_to_buffer(screen->color_profile, address, cell_color_table_offset, cell_color_table_stride);
|
||||
unmap_vao_buffer(vao_idx, 2);
|
||||
}
|
||||
int sprite_map_unit = bind_sprite_map();
|
||||
render_dirty_sprites(render_and_send_dirty_sprites);
|
||||
#define UL(name) cell_uniform_locations[CELL_##name]
|
||||
bind_program(CELL_PROGRAM);
|
||||
glUniform2ui(UL(dimensions), screen->columns, screen->lines);
|
||||
@ -467,7 +634,7 @@ draw_cells(ssize_t vao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat
|
||||
glUniform4ui(UL(default_colors), COLOR(default_fg), COLOR(default_bg), COLOR(highlight_fg), COLOR(highlight_bg));
|
||||
check_gl();
|
||||
#undef COLOR
|
||||
glUniform1i(UL(sprites), 0); // the sprite map is bound to GL_TEXTURE0
|
||||
glUniform1i(UL(sprites), sprite_map_unit);
|
||||
check_gl();
|
||||
unsigned int x, y, z;
|
||||
sprite_map_current_layout(&x, &y, &z);
|
||||
@ -478,6 +645,7 @@ draw_cells(ssize_t vao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat
|
||||
check_gl();
|
||||
unbind_vertex_array();
|
||||
unbind_program();
|
||||
unbind_sprite_map();
|
||||
#undef UL
|
||||
}
|
||||
// }}}
|
||||
@ -680,6 +848,14 @@ PYWRAP1(draw_cells) {
|
||||
draw_cells(vao_idx, xstart, ystart, dx, dy, inverted & 1, screen);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
NO_ARG(destroy_sprite_map)
|
||||
PYWRAP1(layout_sprite_map) {
|
||||
unsigned int cell_width, cell_height;
|
||||
PyObject *render_cell;
|
||||
PA("IIO", &cell_width, &cell_height, &render_cell);
|
||||
layout_sprite_map(cell_width, cell_height, render_cell);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
#define M(name, arg_type) {#name, (PyCFunction)name, arg_type, NULL}
|
||||
#define MW(name, arg_type) {#name, (PyCFunction)py##name, arg_type, NULL}
|
||||
@ -703,6 +879,8 @@ static PyMethodDef module_methods[] = {
|
||||
MW(init_cell_program, METH_NOARGS),
|
||||
MW(create_cell_vao, METH_NOARGS),
|
||||
MW(draw_cells, METH_VARARGS),
|
||||
MW(layout_sprite_map, METH_VARARGS),
|
||||
MW(destroy_sprite_map, METH_NOARGS),
|
||||
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
136
kitty/shaders.py
136
kitty/shaders.py
@ -1,136 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import sys
|
||||
from ctypes import addressof
|
||||
|
||||
from .fast_data_types import (
|
||||
GL_CLAMP_TO_EDGE, GL_MAX_ARRAY_TEXTURE_LAYERS, GL_MAX_TEXTURE_SIZE,
|
||||
GL_NEAREST, GL_R8, GL_RED, GL_TEXTURE0, GL_TEXTURE_2D_ARRAY,
|
||||
GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_WRAP_S,
|
||||
GL_TEXTURE_WRAP_T, GL_UNPACK_ALIGNMENT, GL_UNSIGNED_BYTE,
|
||||
copy_image_sub_data, glActiveTexture, glBindTexture, glCopyImageSubData,
|
||||
glDeleteTexture, glGenTextures, glGetIntegerv, glPixelStorei,
|
||||
glTexParameteri, glTexStorage3D, glTexSubImage3D, render_dirty_sprites,
|
||||
sprite_map_current_layout, sprite_map_free, sprite_map_increment,
|
||||
sprite_map_set_layout, sprite_map_set_limits
|
||||
)
|
||||
from .fonts.render import render_cell
|
||||
from .utils import safe_print
|
||||
|
||||
|
||||
class Sprites:
|
||||
''' Maintain sprite sheets of all rendered characters on the GPU as a texture
|
||||
array with each texture being a sprite sheet. '''
|
||||
|
||||
# TODO: Rewrite this class using the ARB_shader_image_load_store and ARB_shader_storage_buffer_object
|
||||
# extensions one they become available.
|
||||
|
||||
def __init__(self):
|
||||
self.xnum = self.ynum = 1
|
||||
self.x = self.y = self.z = 0
|
||||
self.texture_id = None
|
||||
self.last_num_of_layers = 1
|
||||
self.last_ynum = -1
|
||||
self.texture_unit = GL_TEXTURE0
|
||||
sprite_map_set_limits(glGetIntegerv(GL_MAX_TEXTURE_SIZE), glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS))
|
||||
|
||||
def do_layout(self, cell_width=1, cell_height=1):
|
||||
self.cell_width, self.cell_height = cell_width, cell_height
|
||||
sprite_map_set_layout(cell_width or 1, cell_height or 1)
|
||||
if self.texture_id is not None:
|
||||
glDeleteTexture(self.texture_id)
|
||||
self.texture_id = None
|
||||
self.ensure_state()
|
||||
# Pre-render the basic cells to ensure they have known sprite numbers
|
||||
|
||||
def send(*a, **kw):
|
||||
buf = render_cell(*a, **kw)[0]
|
||||
x, y, z = sprite_map_increment()
|
||||
self.send_to_gpu(x, y, z, buf)
|
||||
return x
|
||||
|
||||
send() # blank
|
||||
send(underline=1)
|
||||
send(underline=2)
|
||||
if send(strikethrough=True) != 3:
|
||||
raise RuntimeError('Available OpenGL texture size is too small')
|
||||
|
||||
def render_cell(self, text, bold, italic, is_second):
|
||||
first, second = render_cell(text, bold, italic)
|
||||
ans = second if is_second else first
|
||||
return ans or render_cell()[0]
|
||||
|
||||
def render_dirty_sprites(self):
|
||||
ret = render_dirty_sprites()
|
||||
if ret:
|
||||
for text, bold, italic, is_second, x, y, z in ret:
|
||||
cell = self.render_cell(text, bold, italic, is_second)
|
||||
self.send_to_gpu(x, y, z, cell)
|
||||
|
||||
def send_to_gpu(self, x, y, z, buf):
|
||||
xnum, ynum, znum = sprite_map_current_layout()
|
||||
if znum >= self.last_num_of_layers:
|
||||
self.realloc_texture()
|
||||
else:
|
||||
if znum == 0 and ynum > self.last_ynum:
|
||||
self.realloc_texture()
|
||||
tgt = GL_TEXTURE_2D_ARRAY
|
||||
glBindTexture(tgt, self.texture_id)
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
|
||||
x, y = x * self.cell_width, y * self.cell_height
|
||||
glTexSubImage3D(tgt, 0, x, y, z, self.cell_width, self.cell_height, 1, GL_RED, GL_UNSIGNED_BYTE, addressof(buf))
|
||||
|
||||
def realloc_texture(self):
|
||||
tgt = GL_TEXTURE_2D_ARRAY
|
||||
tex = glGenTextures(1)
|
||||
glBindTexture(tgt, tex)
|
||||
# We use GL_NEAREST otherwise glyphs that touch the edge of the cell
|
||||
# often show a border between cells
|
||||
glTexParameteri(tgt, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
|
||||
glTexParameteri(tgt, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
|
||||
glTexParameteri(tgt, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
|
||||
glTexParameteri(tgt, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
|
||||
xnum, bynum, z = sprite_map_current_layout()
|
||||
znum = z + 1
|
||||
width, height = xnum * self.cell_width, bynum * self.cell_height
|
||||
glTexStorage3D(tgt, 1, GL_R8, width, height, znum)
|
||||
if self.texture_id is not None:
|
||||
ynum = bynum
|
||||
if z == 0:
|
||||
ynum -= 1 # Only copy the previous rows
|
||||
try:
|
||||
glCopyImageSubData(self.texture_id, tgt, 0, 0, 0, 0, tex, tgt, 0, 0, 0, 0,
|
||||
width, ynum * self.cell_height, self.last_num_of_layers)
|
||||
except RuntimeError:
|
||||
# OpenGL does not have ARB_copy_image
|
||||
if not hasattr(self, 'realloc_warned'):
|
||||
safe_print(
|
||||
'WARNING: Your system\'s OpenGL implementation does not have glCopyImageSubData, falling back to a slower implementation',
|
||||
file=sys.stderr)
|
||||
self.realloc_warned = True
|
||||
copy_image_sub_data(self.texture_id, tex, width, ynum * self.cell_height, self.last_num_of_layers)
|
||||
glBindTexture(tgt, tex)
|
||||
glDeleteTexture(self.texture_id)
|
||||
self.last_num_of_layers = znum
|
||||
self.last_ynum = bynum
|
||||
self.texture_id = tex
|
||||
|
||||
def destroy(self):
|
||||
sprite_map_free()
|
||||
if self.texture_id is not None:
|
||||
glDeleteTexture(self.texture_id)
|
||||
self.texture_id = None
|
||||
|
||||
def ensure_state(self):
|
||||
if self.texture_id is None:
|
||||
self.realloc_texture()
|
||||
|
||||
def __enter__(self):
|
||||
self.ensure_state()
|
||||
glActiveTexture(self.texture_unit)
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, self.texture_id)
|
||||
|
||||
def __exit__(self, *a):
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, 0)
|
||||
@ -46,13 +46,13 @@ sprite_map_set_error(int error) {
|
||||
}
|
||||
}
|
||||
|
||||
PyObject*
|
||||
sprite_map_set_limits(PyObject UNUSED *self, PyObject *args) {
|
||||
if (!PyArg_ParseTuple(args, "kk", &(sprite_map.max_texture_size), &(sprite_map.max_array_len))) return NULL;
|
||||
Py_RETURN_NONE;
|
||||
void
|
||||
sprite_map_set_limits(size_t max_texture_size, size_t max_array_len) {
|
||||
sprite_map.max_texture_size = max_texture_size;
|
||||
sprite_map.max_array_len = max_array_len;
|
||||
}
|
||||
|
||||
PyObject*
|
||||
void
|
||||
sprite_map_free() {
|
||||
SpritePosition *s, *t;
|
||||
for (size_t i = 0; i < sizeof(sprite_map.cache)/sizeof(sprite_map.cache[0]); i++) {
|
||||
@ -64,7 +64,6 @@ sprite_map_free() {
|
||||
PyMem_Free(t);
|
||||
}
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static inline void
|
||||
@ -125,21 +124,17 @@ set_sprite_position(Cell *cell, Cell *previous_cell) {
|
||||
cell->sprite_z = sp->z;
|
||||
}
|
||||
|
||||
PyObject*
|
||||
sprite_map_increment() {
|
||||
#define increment_doc "Increment the current position and return the old (x, y, z) values"
|
||||
unsigned int x = sprite_map.x, y = sprite_map.y, z = sprite_map.z;
|
||||
int
|
||||
sprite_map_increment(sprite_index *x, sprite_index *y, sprite_index *z) {
|
||||
int error = 0;
|
||||
*x = sprite_map.x; *y = sprite_map.y; *z = sprite_map.z;
|
||||
do_increment(&error);
|
||||
if (error) { sprite_map_set_error(error); return NULL; }
|
||||
return Py_BuildValue("III", x, y, z);
|
||||
return error;
|
||||
}
|
||||
|
||||
PyObject*
|
||||
sprite_map_set_layout(PyObject UNUSED *s_, PyObject *args) {
|
||||
void
|
||||
sprite_map_set_layout(unsigned int cell_width, unsigned int cell_height) {
|
||||
// Invalidate cache since cell size has changed.
|
||||
unsigned long cell_width, cell_height;
|
||||
if (!PyArg_ParseTuple(args, "kk", &cell_width, &cell_height)) return NULL;
|
||||
SpritePosition *s;
|
||||
sprite_map.xnum = MIN(MAX(1, sprite_map.max_texture_size / cell_width), UINT16_MAX);
|
||||
sprite_map.max_y = MIN(MAX(1, sprite_map.max_texture_size / cell_height), UINT16_MAX);
|
||||
@ -158,7 +153,6 @@ sprite_map_set_layout(PyObject UNUSED *s_, PyObject *args) {
|
||||
} while (s != NULL);
|
||||
}
|
||||
sprite_map.dirty = true;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
void
|
||||
@ -166,11 +160,6 @@ sprite_map_current_layout(unsigned int *x, unsigned int *y, unsigned int *z) {
|
||||
*x = sprite_map.xnum; *y = sprite_map.ynum; *z = sprite_map.z;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
current_layout(PyObject UNUSED *self) {
|
||||
return Py_BuildValue("III", sprite_map.xnum, sprite_map.ynum, sprite_map.z);
|
||||
}
|
||||
|
||||
PyObject*
|
||||
sprite_position_for(PyObject UNUSED *self, PyObject *args) {
|
||||
#define position_for_doc "position_for(ch, cc, is_second) -> x, y, z the sprite position for the specified text"
|
||||
@ -183,49 +172,35 @@ sprite_position_for(PyObject UNUSED *self, PyObject *args) {
|
||||
return Py_BuildValue("III", pos->x, pos->y, pos->z);
|
||||
}
|
||||
|
||||
PyObject*
|
||||
render_dirty_sprites(PyObject UNUSED *s_) {
|
||||
void
|
||||
render_dirty_sprites(void (*render)(PyObject*, bool, bool, bool, sprite_index, sprite_index, sprite_index)) {
|
||||
#define render_dirty_cells_doc "Render all cells that are marked as dirty"
|
||||
if (!sprite_map.dirty) { Py_RETURN_NONE; }
|
||||
PyObject *ans = PyList_New(0);
|
||||
if (ans == NULL) return NULL;
|
||||
if (!sprite_map.dirty) return;
|
||||
|
||||
for (size_t i = 0; i < sizeof(sprite_map.cache)/sizeof(sprite_map.cache[0]); i++) {
|
||||
SpritePosition *sp = &(sprite_map.cache[i]);
|
||||
do {
|
||||
if (sp->filled && !sp->rendered) {
|
||||
PyObject *text = line_text_at(sp->ch & CHAR_MASK, sp->cc);
|
||||
if (text == NULL) { Py_CLEAR(ans); return NULL; }
|
||||
char_type attrs = sp->ch >> ATTRS_SHIFT;
|
||||
bool bold = (attrs >> BOLD_SHIFT) & 1, italic = (attrs >> ITALIC_SHIFT) & 1;
|
||||
PyObject *x = Py_BuildValue("OOOOHHH", text, bold ? Py_True : Py_False, italic ? Py_True : Py_False, sp->is_second ? Py_True : Py_False, sp->x, sp->y, sp->z);
|
||||
render(text, bold, italic, sp->is_second, sp->x, sp->y, sp->z);
|
||||
Py_CLEAR(text);
|
||||
if (x == NULL) { Py_CLEAR(ans); return NULL; }
|
||||
if (PyList_Append(ans, x) != 0) { Py_CLEAR(x); Py_CLEAR(ans); return NULL; }
|
||||
Py_CLEAR(x);
|
||||
sp->rendered = true;
|
||||
}
|
||||
sp = sp->next;
|
||||
} while(sp);
|
||||
}
|
||||
sprite_map.dirty = false;
|
||||
return ans;
|
||||
}
|
||||
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
{"sprite_map_set_limits", (PyCFunction)sprite_map_set_limits, METH_VARARGS, ""}, \
|
||||
{"sprite_map_set_layout", (PyCFunction)sprite_map_set_layout, METH_VARARGS, ""}, \
|
||||
{"sprite_map_free", (PyCFunction)sprite_map_free, METH_NOARGS, ""}, \
|
||||
{"sprite_map_increment", (PyCFunction)sprite_map_increment, METH_NOARGS, ""}, \
|
||||
{"sprite_position_for", (PyCFunction)sprite_position_for, METH_VARARGS, ""}, \
|
||||
{"render_dirty_sprites", (PyCFunction)render_dirty_sprites, METH_NOARGS, ""}, \
|
||||
{"sprite_map_current_layout", (PyCFunction)current_layout, METH_NOARGS, ""}, \
|
||||
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
||||
bool
|
||||
init_sprites(PyObject *module) {
|
||||
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
|
||||
|
||||
15
kitty/sprites.h
Normal file
15
kitty/sprites.h
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
void sprite_map_current_layout(unsigned int *x, unsigned int *y, unsigned int*);
|
||||
void sprite_map_set_layout(unsigned int cell_width, unsigned int cell_height);
|
||||
void sprite_map_set_limits(size_t max_texture_size, size_t max_array_len);
|
||||
void sprite_map_free();
|
||||
int sprite_map_increment(sprite_index *x, sprite_index *y, sprite_index *z);
|
||||
void render_dirty_sprites(void (*render)(PyObject*, bool, bool, bool, sprite_index, sprite_index, sprite_index));
|
||||
Loading…
x
Reference in New Issue
Block a user