Start work on a class to manage cell textures on the GPU

This commit is contained in:
Kovid Goyal
2016-10-26 15:44:27 +05:30
parent 8c76596f5c
commit 321373056c
3 changed files with 93 additions and 35 deletions

View File

@@ -34,6 +34,8 @@ class Renderer:
print(gl.glGetIntegerv(gl.GL_MAX_VERTEX_UNIFORM_COMPONENTS))
print(gl.glGetIntegerv(gl.GL_MAX_UNIFORM_BLOCK_SIZE))
print(gl.glGetIntegerv(gl.GL_MAX_ARRAY_TEXTURE_LAYERS))
print(gl.glGetIntegerv(gl.GL_MAX_TEXTURE_IMAGE_UNITS))
print(gl.glGetIntegerv(gl.GL_MAX_TEXTURE_SIZE))
def on_resize(self, window, w, h):
gl.glViewport(0, 0, w, h)

View File

@@ -8,7 +8,6 @@ import re
import ctypes
from collections import namedtuple
from functools import lru_cache
from threading import Lock
from freetype import (
Face, FT_LOAD_RENDER, FT_LOAD_TARGET_NORMAL, FT_LOAD_TARGET_LIGHT,
@@ -93,7 +92,7 @@ def set_font_family(family, size_in_pts):
underline_position = baseline - font_units_to_pixels(face.underline_position, face.units_per_EM, size_in_pts, dpi[1])
underline_thickness = font_units_to_pixels(face.underline_thickness, face.units_per_EM, size_in_pts, dpi[1])
CharTexture = ctypes.c_ubyte * (cell_width * cell_height)
glyph_cache = GlyphCache()
glyph_cache = GlyphWidthCache()
set_current_font_metrics(glyph_cache.width)
return cell_width, cell_height
@@ -214,34 +213,17 @@ def create_cell_buffer(bitmap_char, src_start_row, dest_start_row, row_count, sr
return dest
class GlyphCache:
class GlyphWidthCache:
def __init__(self):
self.lock = Lock()
self.clear()
self.prepopulate()
def render(self, text, bold=False, italic=False):
first, second = render_cell(text, bold, italic)
self.width_map[text] = 1 if second is None else 2
self.char_map[text] = self.add_cell(first)
if second is not None:
self.second_char_map[text] = self.add_cell(second)
def add_cell(self, data):
with self.lock:
i = len(self.texture_array)
self.texture_array.append(data)
self.to_sync.append(i)
return i
def clear(self):
with self.lock:
self.char_map = {}
self.second_char_map = {}
self.texture_array = []
self.to_sync = []
self.width_map = {}
self.width_map = {}
def width(self, text):
try:
@@ -251,17 +233,6 @@ class GlyphCache:
self.render(text)
return self.width_map[text]
def prepopulate(self):
# Pre-render the basic ASCII chars in the current font
for i in range(32, 128):
self.render(chr(i))
def __iter__(self):
with self.lock:
for f in self.to_sync:
yield f, self.texture_array[f]
self.to_sync = []
def join_cells(*cells):
dstride = len(cells) * cell_width

View File

@@ -7,10 +7,91 @@ from functools import lru_cache
from OpenGL.arrays import ArrayDatatype
import OpenGL.GL as gl
from .fonts import render_cell, cell_size
GL_VERSION = (4, 1)
VERSION = GL_VERSION[0] * 100 + GL_VERSION[1] * 10
class TextureManager:
def __init__(self):
self.textures = []
self.current_texture_array = None
self.current_array_dirty = False
self.current_array_data = None
self.arraylengths = {}
self.first_cell_cache = {}
self.second_cell_cache = {}
self.max_array_len = gl.glGetIntegerv(gl.GL_MAX_ARRAY_TEXTURE_LAYERS)
self.max_active_textures = gl.glGetIntegerv(gl.GL_MAX_TEXTURE_IMAGE_UNITS)
def ensure_texture_array(self, amt=2):
if self.current_texture_array is None or self.arraylengths[self.current_texture_array] > self.max_array_len - amt:
if self.current_texture_array is not None and len(self.textures) >= self.max_active_textures:
raise RuntimeError('No space left to allocate character textures')
if self.current_array_dirty:
self.commit_current_array()
self.current_texture_array = gl.glGenTextures(1)
self.current_array_data = None
self.current_array_dirty = False
self.textures.append(self.current_texture_array)
self.arraylengths[self.current_texture_array] = 0
def texture_ids_for(self, items):
for key in items:
first = self.first_cell_cache.get(key)
if first is None:
self.ensure_texture_array()
first, second = render_cell(*key)
items = (first, second) if second is not None else (first,)
texture_unit = len(self.textures) - 1
layerf = len(self.arraylengths[self.current_texture_array])
self.append_to_current_array(items)
self.first_cell_cache[key] = first = texture_unit, layerf
if second is not None:
self.second_cell_cache[key] = texture_unit, layerf + 1
yield first, self.second_cell_cache.get(key)
if self.current_array_dirty:
self.commit_current_array()
def commit_current_array(self):
gl.glBindTexture(gl.GL_TEXTURE_2D_ARRAY, self.current_texture_array)
gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE)
width, height = cell_size()
gl.glTexStorage3D(gl.GL_TEXTURE_2D_ARRAY, 0, gl.GL_R8, width, height, self.arraylengths[self.current_texture_array])
gl.glTexSubImage3D(gl.GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, self.arraylengths[
self.current_texture_array], gl.GL_RED, gl.GL_UNSIGNED_BYTE, self.current_array_data)
gl.glBindTexture(gl.GL_TEXTURE_2D_ARRAY, 0)
self.current_array_dirty = False
def append_to_current_array(self, items):
current_len = len(self.current_array_data or '')
self.arraylengths[self.current_texture_array] += len(items)
new_data = (gl.GLubyte * (current_len + sum(map(len, items))))()
if current_len:
new_data[:current_len] = self.current_array_data
gl.glDeleteTextures([self.current_texture_array])
pos = current_len
for i in items:
new_data[pos:pos + len(i)] = i
pos += len(i)
self.current_array_data = new_data
self.current_array_dirty = True
def __enter__(self):
for i, texture_id in enumerate(self.textures):
gl.glActiveTexture(getattr(gl, 'GL_TEXTURE{}'.format(i)))
gl.glBindTexture(gl.GL_TEXTURE_2D_ARRAY, texture_id)
def __exit__(self, *a):
gl.glBindTexture(gl.GL_TEXTURE_2D_ARRAY, 0)
class ShaderProgram:
""" Helper class for using GLSL shader programs """
@@ -79,11 +160,12 @@ class ShaderProgram:
gl.glBindVertexArray(self.vao_id)
self.is_active = True
if self.texture_id is not None:
gl.glActiveTexture(gl.GL_TEXTURE0)
gl.glBindTexture(gl.GL_TEXTURE_2D, self.texture_id)
gl.glActiveTexture(gl.GL_TEXTURE0)
gl.glUniform1i(self.texture_var, 0) # 0 because using GL_TEXTURE0
def __exit__(self, *args):
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
gl.glBindVertexArray(0)
gl.glUseProgram(0)
self.is_active = False
@@ -97,8 +179,8 @@ class ShaderProgram:
gl.glDeleteTextures([self.texture_id])
texture_id = self.texture_id = gl.glGenTextures(1)
self.texture_var = self.uniform_location(var_name)
gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1 if data_type == 'red' else 4)
gl.glBindTexture(gl.GL_TEXTURE_2D, texture_id)
gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1 if data_type == 'red' else 4)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, min_filter)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, mag_filter)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, swrap)
@@ -110,9 +192,10 @@ class ShaderProgram:
}[data_type]
gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, internal_format, width, height,
0, external_format, gl.GL_UNSIGNED_BYTE, data)
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
return texture_id
def set_attribute_data(self, attribute_name, data, items_per_attribute_value=2, normalize=False):
def set_attribute_data(self, attribute_name, data, items_per_attribute_value=2, divisor=None, normalize=False):
if not self.is_active:
raise RuntimeError('The program must be active before you can add buffers')
if len(data) % items_per_attribute_value != 0:
@@ -132,6 +215,8 @@ class ShaderProgram:
gl.glVertexAttribPointer(
loc, items_per_attribute_value, typ, gl.GL_TRUE if normalize else gl.GL_FALSE, 0, None
)
if divisor is not None:
gl.glVertexBindingDivisor(loc, divisor)
def array(*args, dtype=gl.GLfloat):