Move the sprites GPU code to shaders.c

This commit is contained in:
Kovid Goyal 2017-09-12 10:28:24 +05:30
parent cc8271b766
commit 3a883ad436
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
7 changed files with 250 additions and 208 deletions

View File

@ -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):

View File

@ -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);

View File

@ -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))()

View File

@ -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 */
};

View File

@ -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)

View File

@ -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
View 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));