Skip to content

Commit

Permalink
Make line drawing twice as fast by making separate shaders for buffer…
Browse files Browse the repository at this point in the history
…ed and unbuffered
  • Loading branch information
einarf committed Apr 15, 2020
1 parent bc27e48 commit b595439
Show file tree
Hide file tree
Showing 10 changed files with 104 additions and 41 deletions.
23 changes: 14 additions & 9 deletions arcade/draw_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,16 +461,19 @@ def draw_line(start_x: float, start_y: float, end_x: float, end_y: float,
ctx = window.ctx
program = ctx.shape_line_program
geometry = ctx.shape_line_geometry
# We need to normalize the color because we are setting it as a float uniform
if len(color) == 3:
# color = (*color, 255)
color = (color[0], color[1], color[2], 255)
color_normalized = (color[0] / 255, color[1] / 255, color[2] / 255, 1.0)
elif len(color) == 4:
color_normalized = (color[0] / 255, color[1] / 255, color[2] / 255, color[3] / 255) # type: ignore
else:
raise ValueError("Invalid color format. Use a 3 or 4 component tuple")

program['Projection'] = get_projection().flatten()
program['line_width'] = line_width
program['color'] = color_normalized
ctx.shape_line_buffer_pos.write(
data=array.array('f', [start_x, start_y, end_x, end_y]).tobytes())
ctx.shape_line_buffer_color.write(
data=array.array('B', color).tobytes())
geometry.render(program, mode=gl.GL_LINES, vertices=2)

# NOTE: Keep old code just in case
Expand All @@ -494,28 +497,30 @@ def draw_lines(point_list: PointList,
RGBA format.
:param float line_width: Width of the line in pixels.
"""
# # New code
window = get_window()
if not window:
raise RuntimeError("No window found")

ctx = window.ctx
program = ctx.shape_line_program
geometry = ctx.shape_line_geometry
# We need to normalize the color because we are setting it as a float uniform
if len(color) == 3:
color = (color[0], color[1], color[2], 255)
color_normalized = (color[0] / 255, color[1] / 255, color[2] / 255, 1.0)
elif len(color) == 4:
color_normalized = (color[0] / 255, color[1] / 255, color[2] / 255, color[3] / 255) # type: ignore
else:
raise ValueError("Invalid color format. Use a 3 or 4 component tuple")

while len(point_list) * 3 * 4 > ctx.shape_line_buffer_pos.size:
ctx.shape_line_buffer_pos.orphan(ctx.shape_line_buffer_pos.size * 2)
ctx.shape_line_buffer_color.orphan(ctx.shape_line_buffer_color.size * 2)
# print('-> ', len(point_list) * 3 * 4, ctx.shape_line_buffer_pos.size)

program['Projection'] = get_projection().flatten()
program['line_width'] = line_width
program['color'] = color_normalized
ctx.shape_line_buffer_pos.write(
data=array.array('f', [v for point in point_list for v in point]).tobytes())
ctx.shape_line_buffer_color.write(
data=array.array('B', color * len(point_list)).tobytes())
geometry.render(program, mode=gl.GL_LINES, vertices=len(point_list))

# Keep old code just in case
Expand Down
21 changes: 13 additions & 8 deletions arcade/experimental/examples/test_shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,27 @@ def __init__(self, width, height, title):
for _ in range(600)
]
# Line list
self.line_list = [(random.randrange(0, SCREEN_WIDTH), random.randrange(0, SCREEN_HEIGHT)) for _ in range(2 * 5000)]
self.line_list = [(random.randrange(0, SCREEN_WIDTH), random.randrange(0, SCREEN_HEIGHT)) for _ in range(2 * 10000)]

self.frames = 0
self.execution_time = 0

def do_draw_line(self):
for l in self.single_lines_calls:
arcade.draw_line(l[0], l[1], l[2], l[3], l[4], 10)

def do_draw_lines(self):
arcade.draw_lines(self.line_list, (255, 0, 0, 10))

def on_draw(self):
self.clear()
# arcade.draw_point(155, 150, arcade.color.WHITE, 4)
# arcade.draw_circle_filled(400, 300, 250, arcade.color.GREEN)

# Single lines
# for l in self.single_lines_calls:
# arcade.draw_line(l[0], l[1], l[2], l[3], l[4], 10)

start = time.time()
arcade.draw_lines(self.line_list, arcade.color.AERO_BLUE)

# Toggle what to test here
# self.single_lines()
self.do_draw_lines()

self.execution_time += time.time() - start
self.frames += 1

Expand Down
14 changes: 14 additions & 0 deletions arcade/resources/shaders/shapes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Line Shaders

## Unbuffered

Unbuffered lines have a single color even when drawing
multiple lines. `draw_line` and `draw_lines`.
This shader is simpler just taking a color as a uniform
in the fragment shader.

## Buffered

Buffered lines need color passed in as a vertex attribute
because we are concatenating several different line
draw calls with different color configurations.
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#version 330


in vec2 in_vert;
in vec4 in_color;
out vec4 vs_color;
Expand Down
9 changes: 9 additions & 0 deletions arcade/resources/shaders/shapes/line_unbuffered_fs.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#version 330

uniform vec4 color;

out vec4 f_color;

void main() {
f_color = color;
}
33 changes: 33 additions & 0 deletions arcade/resources/shaders/shapes/line_unbuffered_geo.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#version 330

layout (lines) in;
layout (triangle_strip, max_vertices = 4) out;

uniform mat4 Projection;
uniform float line_width;

vec2 lineNormal2D(vec2 start, vec2 end) {
vec2 n = end - start;
return normalize(vec2(-n.y, n.x));
}

void main() {
// Get the line segment
vec2 line_start = gl_in[0].gl_Position.xy;
vec2 line_end = gl_in[1].gl_Position.xy;

// Calculate normal
vec2 normal = lineNormal2D(line_start, line_end) * line_width / 2.0;

// Emit a quad using a line strip with the correct line width
gl_Position = Projection * vec4(line_start + normal, 0.0, 1.0);
EmitVertex();
gl_Position = Projection * vec4(line_start - normal, 0.0, 1.0);
EmitVertex();
gl_Position = Projection * vec4(line_end + normal, 0.0, 1.0);
EmitVertex();
gl_Position = Projection * vec4(line_end - normal, 0.0, 1.0);
EmitVertex();

EndPrimitive();
}
7 changes: 7 additions & 0 deletions arcade/resources/shaders/shapes/line_unbuffered_vs.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#version 330

in vec2 in_vert;

void main() {
gl_Position = vec4(in_vert, 0.0, 1.0);
}
37 changes: 14 additions & 23 deletions arcade/shader.py
Original file line number Diff line number Diff line change
Expand Up @@ -1831,7 +1831,8 @@ def __init__(self, window):
self.TRIANGLE_STRIP_ADJACENCY = gl.GL_TRIANGLE_STRIP_ADJACENCY # 13

# --- Pre-load system shaders here ---

# FIXME: These pre-created resources needs to be packaged nicely
# Just having them globally in the context is probably not a good idea
self.line_vertex_shader = self.load_program(
vertex_shader=self.resource_root / 'shaders/line_vertex_shader_vs.glsl',
fragment_shader=self.resource_root / 'shaders/line_vertex_shader_fs.glsl',
Expand All @@ -1851,38 +1852,28 @@ def __init__(self, window):

# Shapes
self.shape_line_program = self.load_program(
vertex_shader=":resources:/shaders/shapes/line_vs.glsl",
fragment_shader=":resources:/shaders/shapes/line_fs.glsl",
geometry_shader=":resources:/shaders/shapes/line_geo.glsl",
vertex_shader=":resources:/shaders/shapes/line_unbuffered_vs.glsl",
fragment_shader=":resources:/shaders/shapes/line_unbuffered_fs.glsl",
geometry_shader=":resources:/shaders/shapes/line_unbuffered_geo.glsl",
)

# --- Pre-created geometry and buffers for unbuffered draw calls ----
# FIXME: This is a temporary test
# FIXME: These pre-created resources needs to be packaged nicely
# Just having them globally in the context is probably not a good idea
self.generic_draw_line_strip_color = self.buffer(reserve=4 * 1000)
self.generic_draw_line_strip_vbo = self.buffer(reserve=8 * 1000)
self.generic_draw_line_strip_geometry = self.geometry(
[
BufferDescription(
self.generic_draw_line_strip_vbo,
'2f',
['in_vert']
),
BufferDescription(
self.generic_draw_line_strip_color,
'4f1',
['in_color'],
normalized=['in_color'],
),
]
)

self.generic_draw_line_strip_geometry = self.geometry([
BufferDescription(self.generic_draw_line_strip_vbo, '2f', ['in_vert']),
BufferDescription(self.generic_draw_line_strip_color, '4f1', ['in_color'], normalized=['in_color'])])
# Shape line(s)
# Reserve space for 1000 lines (2f pos, 4f color)
# TODO: Different version for buffered and unbuffered
# TODO: Make round-robin buffers
self.shape_line_buffer_pos = self.buffer(reserve=8 * 10)
self.shape_line_buffer_color = self.buffer(reserve=4 * 10)
# self.shape_line_buffer_color = self.buffer(reserve=4 * 10)
self.shape_line_geometry = self.geometry([
BufferDescription(self.shape_line_buffer_pos, '2f', ['in_vert']),
BufferDescription(self.shape_line_buffer_color, '4f1', ['in_color'], normalized=['in_color']),
# BufferDescription(self.shape_line_buffer_color, '4f1', ['in_color'], normalized=['in_color'])
])

@property
Expand Down

0 comments on commit b595439

Please sign in to comment.