Use instanced rendering for the sprites as well
This commit is contained in:
@@ -13,7 +13,9 @@ textured_shaders = (
|
|||||||
'''\
|
'''\
|
||||||
uniform uvec2 dimensions; // xnum, ynum
|
uniform uvec2 dimensions; // xnum, ynum
|
||||||
uniform vec4 steps; // xstart, ystart, dx, dy
|
uniform vec4 steps; // xstart, ystart, dx, dy
|
||||||
out vec4 color;
|
uniform vec2 sprite_layout; // dx, dy
|
||||||
|
uniform usamplerBuffer sprite_map; // gl_InstanceID -> x, y, z
|
||||||
|
out vec3 sprite_pos;
|
||||||
|
|
||||||
const uvec2 pos_map[] = uvec2[6](
|
const uvec2 pos_map[] = uvec2[6](
|
||||||
uvec2(1, 0), // right, top
|
uvec2(1, 0), // right, top
|
||||||
@@ -25,31 +27,35 @@ const uvec2 pos_map[] = uvec2[6](
|
|||||||
);
|
);
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
uint r = uint(gl_InstanceID) / dimensions[0];
|
uint instance_id = uint(gl_InstanceID);
|
||||||
uint c = uint(gl_InstanceID) - r * dimensions[0];
|
uint r = instance_id / dimensions[0];
|
||||||
|
uint c = instance_id - r * dimensions[0];
|
||||||
float left = steps[0] + c * steps[2];
|
float left = steps[0] + c * steps[2];
|
||||||
float top = steps[1] - r * steps[3];
|
float top = steps[1] - r * steps[3];
|
||||||
vec2 xpos = vec2(left, left + steps[2]);
|
vec2 xpos = vec2(left, left + steps[2]);
|
||||||
vec2 ypos = vec2(top, top - steps[3]);
|
vec2 ypos = vec2(top, top - steps[3]);
|
||||||
uvec2 pos = pos_map[gl_VertexID];
|
uvec2 pos = pos_map[gl_VertexID];
|
||||||
gl_Position = vec4(xpos[pos[0]], ypos[pos[1]], 0, 1);
|
gl_Position = vec4(xpos[pos[0]], ypos[pos[1]], 0, 1);
|
||||||
color = vec4(mod(gl_InstanceID, 2), 1, 1, 1);
|
|
||||||
|
uvec4 spos = texelFetch(sprite_map, int(instance_id));
|
||||||
|
vec2 s_xpos = vec2(spos[0], spos[0] + 1.0) * sprite_layout[0];
|
||||||
|
vec2 s_ypos = vec2(spos[1], spos[1] + 1.0) * sprite_layout[1];
|
||||||
|
sprite_pos = vec3(s_xpos[pos[0]], s_ypos[pos[1]], spos[2]);
|
||||||
}
|
}
|
||||||
''',
|
''',
|
||||||
|
|
||||||
'''\
|
'''\
|
||||||
uniform sampler2DArray sprites;
|
uniform sampler2DArray sprites;
|
||||||
uniform vec3 sprite_scale;
|
in vec3 sprite_pos;
|
||||||
in vec4 color;
|
|
||||||
out vec4 final_color;
|
out vec4 final_color;
|
||||||
|
|
||||||
const vec3 background = vec3(0, 0, 1);
|
const vec3 background = vec3(0, 0, 1);
|
||||||
const vec3 foreground = vec3(0, 1, 0);
|
const vec3 foreground = vec3(0, 1, 0);
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
// float alpha = texture(sprites, texture_position_for_fs / sprite_scale).r;
|
float alpha = texture(sprites, sprite_pos).r;
|
||||||
// vec3 color = background * (1 - alpha) + foreground * alpha;
|
vec3 color = background * (1 - alpha) + foreground * alpha;
|
||||||
// final_color = vec4(color, 1);
|
final_color = vec4(color, 1);
|
||||||
final_color = color;
|
|
||||||
}
|
}
|
||||||
''')
|
''')
|
||||||
|
|
||||||
@@ -93,12 +99,22 @@ class Renderer:
|
|||||||
# Divide into cells
|
# Divide into cells
|
||||||
cell_width, cell_height = cell_size()
|
cell_width, cell_height = cell_size()
|
||||||
self.xnum, self.ynum, self.xstart, self.ystart, self.dx, self.dy = calculate_vertices(cell_width, cell_height, self.w, self.h)
|
self.xnum, self.ynum, self.xstart, self.ystart, self.dx, self.dy = calculate_vertices(cell_width, cell_height, self.w, self.h)
|
||||||
|
data = (gl.GLuint * (self.xnum * self.ynum * 3))()
|
||||||
|
for i in range(0, self.xnum * self.ynum * 3, 3):
|
||||||
|
c = '%d' % ((i // 3) % 10)
|
||||||
|
data[i:i+3] = self.sprites.primary_sprite_position(c)
|
||||||
|
self.sprites.set_sprite_map(data)
|
||||||
|
|
||||||
def render(self):
|
def render(self):
|
||||||
with self.program:
|
with self.program:
|
||||||
gl.glUniform2ui(self.program.uniform_location('dimensions'), self.xnum, self.ynum)
|
ul = self.program.uniform_location
|
||||||
gl.glUniform4f(self.program.uniform_location('steps'), self.xstart, self.ystart, self.dx, self.dy)
|
gl.glUniform2ui(ul('dimensions'), self.xnum, self.ynum)
|
||||||
gl.glDrawArraysInstanced(gl.GL_TRIANGLES, 0, 6, self.xnum * self.ynum)
|
gl.glUniform4f(ul('steps'), self.xstart, self.ystart, self.dx, self.dy)
|
||||||
|
gl.glUniform1i(ul('sprites'), self.sprites.sampler_num)
|
||||||
|
gl.glUniform1i(ul('sprite_map'), self.sprites.buffer_sampler_num)
|
||||||
|
gl.glUniform2f(ul('sprite_layout'), *self.sprites.layout)
|
||||||
|
with self.sprites:
|
||||||
|
gl.glDrawArraysInstanced(gl.GL_TRIANGLES, 0, 6, self.xnum * self.ynum)
|
||||||
|
|
||||||
# window setup {{{
|
# window setup {{{
|
||||||
|
|
||||||
|
|||||||
@@ -23,12 +23,16 @@ class Sprites:
|
|||||||
''' Maintain sprite sheets of all rendered characters on the GPU as a texture
|
''' Maintain sprite sheets of all rendered characters on the GPU as a texture
|
||||||
array with each texture being a sprite sheet. '''
|
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, texture_unit=0):
|
def __init__(self, texture_unit=0):
|
||||||
self.sampler_num = texture_unit
|
self.sampler_num = texture_unit
|
||||||
|
self.buffer_sampler_num = texture_unit + 1
|
||||||
self.first_cell_cache = {}
|
self.first_cell_cache = {}
|
||||||
self.second_cell_cache = {}
|
self.second_cell_cache = {}
|
||||||
self.x = self.y = self.z = 0
|
self.x = self.y = self.z = 0
|
||||||
self.texture_id = None
|
self.texture_id = self.buffer_id = self.buffer_texture_id = None
|
||||||
self.last_num_of_layers = 1
|
self.last_num_of_layers = 1
|
||||||
|
|
||||||
def do_layout(self):
|
def do_layout(self):
|
||||||
@@ -38,9 +42,12 @@ class Sprites:
|
|||||||
self.cell_width, self.cell_height = cell_size()
|
self.cell_width, self.cell_height = cell_size()
|
||||||
self.xnum = self.max_texture_size // self.cell_width
|
self.xnum = self.max_texture_size // self.cell_width
|
||||||
self.max_y = self.max_texture_size // self.cell_height
|
self.max_y = self.max_texture_size // self.cell_height
|
||||||
# self.xnum, self.max_y = 2, 2
|
|
||||||
self.ynum = 1
|
self.ynum = 1
|
||||||
|
|
||||||
|
@property
|
||||||
|
def layout(self):
|
||||||
|
return 1 / self.xnum, 1 / self.ynum
|
||||||
|
|
||||||
def realloc_texture(self):
|
def realloc_texture(self):
|
||||||
if self.texture_id is None:
|
if self.texture_id is None:
|
||||||
self.do_layout()
|
self.do_layout()
|
||||||
@@ -65,21 +72,13 @@ class Sprites:
|
|||||||
self.texture_id = tex
|
self.texture_id = tex
|
||||||
gl.glBindTexture(tgt, 0)
|
gl.glBindTexture(tgt, 0)
|
||||||
|
|
||||||
def positions_for(self, items):
|
|
||||||
''' Yield 2, 5-tuples (left, top, right, bottom, z) pointing to the
|
|
||||||
desired sprite and its second sprite if it is a wide character. '''
|
|
||||||
for key in items:
|
|
||||||
first = self.first_cell_cache.get(key)
|
|
||||||
if first is None:
|
|
||||||
first, second = render_cell(*key)
|
|
||||||
self.first_cell_cache[key] = first = self.add_sprite(first)
|
|
||||||
if second is not None:
|
|
||||||
self.second_cell_cache[key] = self.add_sprite(second)
|
|
||||||
yield first, self.second_cell_cache.get(key)
|
|
||||||
|
|
||||||
def add_sprite(self, buf):
|
def add_sprite(self, buf):
|
||||||
if self.texture_id is None:
|
if self.texture_id is None:
|
||||||
self.realloc_texture()
|
self.realloc_texture()
|
||||||
|
if self.buffer_id is None:
|
||||||
|
self.buffer_id = gl.glGenBuffers(1)
|
||||||
|
self.buffer_texture_id = gl.glGenTextures(1)
|
||||||
|
self.buffer_texture_unit = getattr(gl, 'GL_TEXTURE%d' % self.buffer_sampler_num)
|
||||||
tgt = gl.GL_TEXTURE_2D_ARRAY
|
tgt = gl.GL_TEXTURE_2D_ARRAY
|
||||||
gl.glBindTexture(tgt, self.texture_id)
|
gl.glBindTexture(tgt, self.texture_id)
|
||||||
gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1)
|
gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1)
|
||||||
@@ -87,8 +86,8 @@ class Sprites:
|
|||||||
gl.glTexSubImage3D(tgt, 0, x, y, self.z, self.cell_width, self.cell_height, 1, gl.GL_RED, gl.GL_UNSIGNED_BYTE, buf)
|
gl.glTexSubImage3D(tgt, 0, x, y, self.z, self.cell_width, self.cell_height, 1, gl.GL_RED, gl.GL_UNSIGNED_BYTE, buf)
|
||||||
gl.glBindTexture(tgt, 0)
|
gl.glBindTexture(tgt, 0)
|
||||||
|
|
||||||
# UV space co-ordinates
|
# co-ordinates for this sprite in the sprite sheet
|
||||||
left, top, z = self.x, self.y, self.z
|
x, y, z = self.x, self.y, self.z
|
||||||
|
|
||||||
# Now increment the current cell position
|
# Now increment the current cell position
|
||||||
self.x += 1
|
self.x += 1
|
||||||
@@ -100,14 +99,46 @@ class Sprites:
|
|||||||
self.y = 0
|
self.y = 0
|
||||||
self.z += 1
|
self.z += 1
|
||||||
self.realloc_texture() # we allocate a row at a time
|
self.realloc_texture() # we allocate a row at a time
|
||||||
return left, top, left + 1, top + 1, z
|
return x, y, z
|
||||||
|
|
||||||
|
def set_sprite_map(self, data):
|
||||||
|
tgt = gl.GL_TEXTURE_BUFFER
|
||||||
|
gl.glBindBuffer(tgt, self.buffer_id)
|
||||||
|
gl.glBufferData(tgt, ArrayDatatype.arrayByteCount(data), data, gl.GL_STATIC_DRAW)
|
||||||
|
gl.glBindBuffer(tgt, 0)
|
||||||
|
|
||||||
|
def primary_sprite_position(self, text, bold=False, italic=False):
|
||||||
|
' Return a 3-tuple (x, y, z) giving the position of this sprite on the sprite sheet '
|
||||||
|
key = text, bold, italic
|
||||||
|
first = self.first_cell_cache.get(key)
|
||||||
|
if first is None:
|
||||||
|
first, second = render_cell(text, bold, italic)
|
||||||
|
self.first_cell_cache[key] = first = self.add_sprite(first)
|
||||||
|
if second is not None:
|
||||||
|
self.second_cell_cache[key] = self.add_sprite(second)
|
||||||
|
return first
|
||||||
|
|
||||||
|
def secondary_sprite_position(self, text, bold=False, italic=False):
|
||||||
|
key = text, bold, italic
|
||||||
|
ans = self.second_cell_cache.get(key)
|
||||||
|
if ans is None:
|
||||||
|
self.primary_sprite_position(text, bold, italic)
|
||||||
|
ans = self.second_cell_cache.get(key)
|
||||||
|
if ans is None:
|
||||||
|
return 0, 0, 0
|
||||||
|
return ans
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
gl.glActiveTexture(self.texture_unit)
|
gl.glActiveTexture(self.texture_unit)
|
||||||
gl.glBindTexture(gl.GL_TEXTURE_2D_ARRAY, self.texture_id)
|
gl.glBindTexture(gl.GL_TEXTURE_2D_ARRAY, self.texture_id)
|
||||||
|
|
||||||
|
gl.glActiveTexture(self.buffer_texture_unit)
|
||||||
|
gl.glBindTexture(gl.GL_TEXTURE_BUFFER, self.buffer_texture_id)
|
||||||
|
gl.glTexBuffer(gl.GL_TEXTURE_BUFFER, gl.GL_RGB32UI, self.buffer_id)
|
||||||
|
|
||||||
def __exit__(self, *a):
|
def __exit__(self, *a):
|
||||||
gl.glBindTexture(gl.GL_TEXTURE_2D_ARRAY, 0)
|
gl.glBindTexture(gl.GL_TEXTURE_2D_ARRAY, 0)
|
||||||
|
gl.glBindTexture(gl.GL_TEXTURE_BUFFER, 0)
|
||||||
|
|
||||||
|
|
||||||
class ShaderProgram:
|
class ShaderProgram:
|
||||||
@@ -132,11 +163,10 @@ class ShaderProgram:
|
|||||||
gl.glDeleteProgram(self.program_id)
|
gl.glDeleteProgram(self.program_id)
|
||||||
gl.glDeleteShader(vs_id)
|
gl.glDeleteShader(vs_id)
|
||||||
gl.glDeleteShader(frag_id)
|
gl.glDeleteShader(frag_id)
|
||||||
raise ValueError('Error linking shader program: %s' % info)
|
raise ValueError('Error linking shader program: \n%s' % info.decode('utf-8'))
|
||||||
gl.glDeleteShader(vs_id)
|
gl.glDeleteShader(vs_id)
|
||||||
gl.glDeleteShader(frag_id)
|
gl.glDeleteShader(frag_id)
|
||||||
self.vao_id = gl.glGenVertexArrays(1)
|
self.vao_id = gl.glGenVertexArrays(1)
|
||||||
self.attribute_buffers = {}
|
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
def __hash__(self) -> int:
|
||||||
return self.program_id
|
return self.program_id
|
||||||
@@ -156,7 +186,7 @@ class ShaderProgram:
|
|||||||
gl.glCompileShader(shader_id)
|
gl.glCompileShader(shader_id)
|
||||||
if gl.glGetShaderiv(shader_id, gl.GL_COMPILE_STATUS) != gl.GL_TRUE:
|
if gl.glGetShaderiv(shader_id, gl.GL_COMPILE_STATUS) != gl.GL_TRUE:
|
||||||
info = gl.glGetShaderInfoLog(shader_id)
|
info = gl.glGetShaderInfoLog(shader_id)
|
||||||
raise ValueError('GLSL Shader compilation failed: %s' % info)
|
raise ValueError('GLSL Shader compilation failed: \n%s' % info.decode('utf-8'))
|
||||||
return shader_id
|
return shader_id
|
||||||
except Exception:
|
except Exception:
|
||||||
gl.glDeleteShader(shader_id)
|
gl.glDeleteShader(shader_id)
|
||||||
@@ -178,29 +208,6 @@ class ShaderProgram:
|
|||||||
self.is_active = True
|
self.is_active = True
|
||||||
|
|
||||||
def __exit__(self, *args):
|
def __exit__(self, *args):
|
||||||
gl.glBindVertexArray(0)
|
|
||||||
gl.glUseProgram(0)
|
gl.glUseProgram(0)
|
||||||
|
gl.glBindVertexArray(0)
|
||||||
self.is_active = False
|
self.is_active = 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:
|
|
||||||
raise ValueError('The length of the data buffer is not a multiple of the items_per_attribute_value')
|
|
||||||
buf_id = self.attribute_buffers.get(attribute_name) # glBufferData auto-deletes previous data
|
|
||||||
if buf_id is None:
|
|
||||||
buf_id = self.attribute_buffers[attribute_name] = gl.glGenBuffers(1)
|
|
||||||
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, buf_id)
|
|
||||||
gl.glBufferData(gl.GL_ARRAY_BUFFER, ArrayDatatype.arrayByteCount(data), data, gl.GL_STATIC_DRAW)
|
|
||||||
loc = self.attribute_location(attribute_name)
|
|
||||||
gl.glEnableVertexAttribArray(loc)
|
|
||||||
typ = {
|
|
||||||
gl.GLfloat: gl.GL_FLOAT,
|
|
||||||
gl.GLubyte: gl.GL_UNSIGNED_BYTE,
|
|
||||||
gl.GLuint: gl.GL_UNSIGNED_INT,
|
|
||||||
}[data._type_]
|
|
||||||
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)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user