Skip to content

Commit

Permalink
draw_circle_filled with geometry shader
Browse files Browse the repository at this point in the history
  • Loading branch information
einarf committed Apr 16, 2020
1 parent 47da926 commit 44ab5b1
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 12 deletions.
36 changes: 31 additions & 5 deletions arcade/draw_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ def draw_parabola_outline(start_x: float, start_y: float, end_x: float,

def draw_circle_filled(center_x: float, center_y: float, radius: float,
color: Color,
num_segments: int = 128):
num_segments: int = -1):
"""
Draw a filled-in circle.
Expand All @@ -211,12 +211,38 @@ def draw_circle_filled(center_x: float, center_y: float, radius: float,
:param float radius: width of the circle.
:param Color color: color, specified in a list of 3 or 4 bytes in RGB or
RGBA format.
:param int num_segments: float of triangle segments that make up this
:param int num_segments: Number of triangle segments that make up this
circle. Higher is better quality, but slower render time.
The default value of -1 means arcade will try to calulate a reasonable
amount of segments based on the size of the circle.
"""
width = radius * 2
height = radius * 2
draw_ellipse_filled(center_x, center_y, width, height, color, num_segments=num_segments)
window = get_window()
if not window:
raise RuntimeError("No window found")

ctx = window.ctx
program = ctx.shape_ellipse_unbuffered_program
geometry = ctx.shape_ellipse_unbuffered_geometry
buffer = ctx.shape_ellipse_unbuffered_buffer
# We need to normalize the color because we are setting it as a float uniform
if len(color) == 3:
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['color'] = color_normalized
program['size'] = radius, radius
program['segments'] = num_segments
buffer.write(data=array.array('f', (center_x, center_y)).tobytes())

geometry.render(program, mode=gl.GL_POINTS, vertices=1)

# width = radius * 2
# height = radius * 2
# draw_ellipse_filled(center_x, center_y, width, height, color, num_segments=num_segments)


def draw_circle_outline(center_x: float, center_y: float, radius: float,
Expand Down
15 changes: 8 additions & 7 deletions arcade/experimental/examples/shapes_perf.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,24 @@ def random_color(alpha=127):
return random.randint(0, 255), random.randint(0, 255), random.randint(0, 255), alpha


def random_radius(range=(5, 25)):
return random.randrange(*range)
def random_radius(start=5, end=25):
return random.randrange(start, end)


class TestWindow(arcade.Window):

def __init__(self, width, height, title):
super().__init__(800, 600, "Test", antialiasing=True, resizable=True, fullscreen=False)
super().__init__(width, height, title, antialiasing=False, resizable=True)
# Single lines
self.single_lines_calls = [(*random_pos(), *random_pos(), random_color()) for _ in range(600)]
# Line list
self.line_list = [(random.randrange(0, SCREEN_WIDTH), random.randrange(0, SCREEN_HEIGHT)) for _ in range(2 * 10000)]

# Single ciricle draw calls
self.single_circle_calls = [(*random_pos(), random_radius(), random_color()) for _ in range(10000)]
# Single circle draw calls
self.single_circle_calls = [(*random_pos(), random_radius(), random_color()) for _ in range(600)]

self.frames = 0
self.elapsed = 0
self.execution_time = 0

def do_draw_line(self):
Expand Down Expand Up @@ -83,9 +84,9 @@ def on_resize(self, width, height):
gl.glViewport(0, 0, *self.get_framebuffer_size())

def on_update(self, dt):
pass
self.elapsed += dt


if __name__ == '__main__':
window = TestWindow(SCREEN_HEIGHT, SCREEN_HEIGHT, TTTLE)
window = TestWindow(SCREEN_WIDTH, SCREEN_HEIGHT, TTTLE)
arcade.run()
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;
}
48 changes: 48 additions & 0 deletions arcade/resources/shaders/shapes/ellipse/filled_unbuffered_geo.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#version 330

#define PI 3.1415926535897932384626433832795
#define MIN_SEGMENTS 16
#define MAX_SEGMENTS 85

layout (points) in;
// TODO: We might want to increase the number of emitted verties, but core 3.3 says 256 is min requirement.
// TODO: Normally 4096 is supported, but let's stay on the safe side
layout (triangle_strip, max_vertices = 256) out;

uniform mat4 Projection;
uniform int segments;
// xy size of the ellipse
uniform vec2 size;


void main() {
// Get center of the circle
vec2 center = gl_in[0].gl_Position.xy;
int segments_selected = 0;

if (segments > 0) {
// The user defined number of segments. Clamp it.
segments_selected = segments;
} else {
// Estimate the number of segments needed based on size
segments_selected = int(2.0 * PI * max(size.x, size.y) / 10.0);
}
// Clamp number of segments
segments_selected = clamp(segments_selected, MIN_SEGMENTS, MAX_SEGMENTS);

// sin(v), cos(v) travels clockwise around the circle starting at 0, 1 (top of circle)
float step = PI * 2 / segments_selected;

for (int i = 0; i < segments_selected; i++) {
gl_Position = Projection * vec4(center, 0.0, 1.0);
EmitVertex();

gl_Position = Projection * vec4(center + vec2(sin((i + 1) * step), cos((i + 1) * step)) * size, 0.0, 1.0);
EmitVertex();

gl_Position = Projection * vec4(center + vec2(sin(i * step), cos(i * step)) * size, 0.0, 1.0);
EmitVertex();

EndPrimitive();
}
}
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);
}
9 changes: 9 additions & 0 deletions arcade/shader.py
Original file line number Diff line number Diff line change
Expand Up @@ -1856,6 +1856,11 @@ def __init__(self, window):
fragment_shader=":resources:/shaders/shapes/line/unbuffered_fs.glsl",
geometry_shader=":resources:/shaders/shapes/line/unbuffered_geo.glsl",
)
self.shape_ellipse_unbuffered_program = self.load_program(
vertex_shader=":resources:/shaders/shapes/ellipse/filled_unbuffered_vs.glsl",
fragment_shader=":resources:/shaders/shapes/ellipse/filled_unbuffered_fs.glsl",
geometry_shader=":resources:/shaders/shapes/ellipse/filled_unbuffered_geo.glsl",
)

# --- Pre-created geometry and buffers for unbuffered draw calls ----
# FIXME: These pre-created resources needs to be packaged nicely
Expand All @@ -1875,6 +1880,10 @@ def __init__(self, window):
BufferDescription(self.shape_line_buffer_pos, '2f', ['in_vert']),
# BufferDescription(self.shape_line_buffer_color, '4f1', ['in_color'], normalized=['in_color'])
])
# ellipse/circle filled
self.shape_ellipse_unbuffered_buffer = self.buffer(reserve=8)
self.shape_ellipse_unbuffered_geometry = self.geometry([
BufferDescription(self.shape_ellipse_unbuffered_buffer, '2f', ['in_vert'])])

@property
def window(self):
Expand Down

0 comments on commit 44ab5b1

Please sign in to comment.