diff --git a/arcade/context.py b/arcade/context.py index 2cbbc33d7b..b727fa1795 100644 --- a/arcade/context.py +++ b/arcade/context.py @@ -159,12 +159,10 @@ def __init__( self.shape_ellipse_filled_unbuffered_program: Program = self.load_program( vertex_shader=":system:shaders/shapes/ellipse/filled_unbuffered_vs.glsl", fragment_shader=":system:shaders/shapes/ellipse/filled_unbuffered_fs.glsl", - geometry_shader=":system:shaders/shapes/ellipse/filled_unbuffered_geo.glsl", ) self.shape_ellipse_outline_unbuffered_program: Program = self.load_program( vertex_shader=":system:shaders/shapes/ellipse/outline_unbuffered_vs.glsl", fragment_shader=":system:shaders/shapes/ellipse/outline_unbuffered_fs.glsl", - geometry_shader=":system:shaders/shapes/ellipse/outline_unbuffered_geo.glsl", ) self.shape_rectangle_filled_unbuffered_program = self.load_program( vertex_shader=":system:shaders/shapes/rectangle/filled_unbuffered_vs.glsl", @@ -258,16 +256,10 @@ def __init__( ], mode=self.TRIANGLE_STRIP, ) - # ellipse/circle filled - self.shape_ellipse_unbuffered_buffer = self.buffer(reserve=8) - self.shape_ellipse_unbuffered_geometry: Geometry = self.geometry( - [BufferDescription(self.shape_ellipse_unbuffered_buffer, "2f", ["in_vert"])] - ) - # ellipse/circle outline - self.shape_ellipse_outline_unbuffered_buffer = self.buffer(reserve=8) - self.shape_ellipse_outline_unbuffered_geometry: Geometry = self.geometry( - [BufferDescription(self.shape_ellipse_outline_unbuffered_buffer, "2f", ["in_vert"])] - ) + # ellipse/circle filled. Empty geometry. We generate it on the fly in the vertex shader. + self.shape_ellipse_unbuffered_geometry: Geometry = self.geometry() + # ellipse/circle outline. Empty geometry. We generate it on the fly in the vertex shader. + self.shape_ellipse_outline_unbuffered_geometry: Geometry = self.geometry() # rectangle filled self.shape_rectangle_filled_unbuffered_buffer = self.buffer(reserve=8) # fmt: off diff --git a/arcade/draw/circle.py b/arcade/draw/circle.py index 729859c55f..c5bc4e98c2 100644 --- a/arcade/draw/circle.py +++ b/arcade/draw/circle.py @@ -1,6 +1,3 @@ -import array - -from arcade import gl from arcade.types import Color, RGBOrA255 from arcade.window_commands import get_window @@ -129,23 +126,31 @@ def draw_ellipse_filled( # Fail immediately if we have no window or context window = get_window() ctx = window.ctx - ctx.enable(ctx.BLEND) program = ctx.shape_ellipse_filled_unbuffered_program geometry = ctx.shape_ellipse_unbuffered_geometry - buffer = ctx.shape_ellipse_unbuffered_buffer # type: ignore # Normalize the color because this shader takes a float uniform color_normalized = Color.from_iterable(color).normalized + # Auto select number of segments if not specified + if num_segments == -1: + size = max(width, height) + if size <= 12: + num_segments = 6 + else: + num_segments = int(size) // 2 + + if num_segments < 3: + num_segments = 3 + # Pass data to the shader + program["center"] = center_x, center_y program["color"] = color_normalized program["shape"] = width / 2, height / 2, tilt_angle program["segments"] = num_segments - buffer.orphan() - buffer.write(data=array.array("f", (center_x, center_y))) - - geometry.render(program, mode=gl.POINTS, vertices=1) + ctx.enable(ctx.BLEND) + geometry.render(program, mode=ctx.TRIANGLES, vertices=num_segments * 3) ctx.disable(ctx.BLEND) @@ -190,20 +195,27 @@ def draw_ellipse_outline( ctx = window.ctx program = ctx.shape_ellipse_outline_unbuffered_program geometry = ctx.shape_ellipse_outline_unbuffered_geometry - buffer = ctx.shape_ellipse_outline_unbuffered_buffer # type: ignore # Normalize the color because this shader takes a float uniform color_normalized = Color.from_iterable(color).normalized - ctx.enable(ctx.BLEND) + # Auto select number of segments if not specified + if num_segments == -1: + size = max(width, height) + if size <= 12: + num_segments = 6 + else: + num_segments = int(size) // 2 + + if num_segments < 3: + num_segments = 3 # Pass data to shader + program["center"] = center_x, center_y program["color"] = color_normalized program["shape"] = width / 2, height / 2, tilt_angle, border_width program["segments"] = num_segments - buffer.orphan() - buffer.write(data=array.array("f", (center_x, center_y))) - - geometry.render(program, mode=gl.POINTS, vertices=1) + ctx.enable(ctx.BLEND) + geometry.render(program, mode=ctx.TRIANGLES, vertices=num_segments * 6) ctx.disable(ctx.BLEND) diff --git a/arcade/resources/system/shaders/shapes/ellipse/filled_unbuffered_geo.glsl b/arcade/resources/system/shaders/shapes/ellipse/filled_unbuffered_geo.glsl deleted file mode 100644 index e1c9a7f377..0000000000 --- a/arcade/resources/system/shaders/shapes/ellipse/filled_unbuffered_geo.glsl +++ /dev/null @@ -1,68 +0,0 @@ -#version 330 - -// 3 points per segment, max of 256 points, so 85 * 3 = 255 -const int MIN_SEGMENTS = 3; -const int MAX_SEGMENTS = 112; -const float PI = 3.141592; - -layout (points) in; -// TODO: We might want to increase the number of emitted vertices, 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 WindowBlock { - mat4 projection; - mat4 view; -} window; - -uniform int segments; -// [w, h, tilt] -uniform vec3 shape; - -void main() { - // Get center of the circle - vec2 center = gl_in[0].gl_Position.xy; - int segments_selected = segments; - - // Calculate rotation/tilt - float angle = radians(shape.z); - mat2 rot = mat2( - cos(angle), -sin(angle), - sin(angle), cos(angle) - ); - - if (segments_selected < 0) { - // Estimate the number of segments needed based on size - float size = max(shape.x, shape.y); - if (size <= 4.0) - segments_selected = 4; - else if (size <= 16.0) - segments_selected = 16; - else - segments_selected = 32; - } - // 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 st = PI * 2.0 / float(segments_selected); - - for (int i = 0; i < segments_selected; i++) { - gl_Position = window.projection * window.view * vec4(center, 0.0, 1.0); - EmitVertex(); - - // Calculate the ellipse/circle using 0, 0 as origin - vec2 p1 = vec2(sin((float(i) + 1.0) * st), cos((float(i) + 1.0) * st)) * shape.xy; - // Rotate the circle and then add translation to get the right origin - gl_Position = window.projection * window.view * vec4((rot * p1) + center, 0.0, 1.0); - EmitVertex(); - - // Calculate the ellipse/circle using 0, 0 as origin - vec2 p2 = vec2(sin(float(i) * st), cos(float(i) * st)) * shape.xy; - // Rotate the circle and then add translation to get the right origin - gl_Position = window.projection * window.view * vec4((rot * p2) + center, 0.0, 1.0); - EmitVertex(); - - EndPrimitive(); - } -} diff --git a/arcade/resources/system/shaders/shapes/ellipse/filled_unbuffered_vs.glsl b/arcade/resources/system/shaders/shapes/ellipse/filled_unbuffered_vs.glsl index 02be2f2654..efd5f7414d 100644 --- a/arcade/resources/system/shaders/shapes/ellipse/filled_unbuffered_vs.glsl +++ b/arcade/resources/system/shaders/shapes/ellipse/filled_unbuffered_vs.glsl @@ -1,7 +1,37 @@ #version 330 -in vec2 in_vert; +uniform WindowBlock { + mat4 projection; + mat4 view; +} window; + +uniform vec2 center; +uniform int segments; +// [w, h, tilt] +uniform vec3 shape; + +const float PI = 3.141592; void main() { - gl_Position = vec4(in_vert, 0.0, 1.0); + int triangle_id = gl_VertexID / 3; + int vertex_id = gl_VertexID % 3; + + // Calculate rotation/tilt + float angle = radians(shape.z); + mat2 rot = mat2( + cos(angle), -sin(angle), + sin(angle), cos(angle) + ); + // Calculate the positions for the full triangle in the current segment + vec2 positions[3] = vec2[3]( + vec2(0.0, 0.0), + vec2(sin((float(triangle_id) + 1.0) * (PI * 2.0 / float(segments))), + cos((float(triangle_id) + 1.0) * (PI * 2.0 / float(segments)))) * shape.xy, + vec2(sin(float(triangle_id) * (PI * 2.0 / float(segments))), + cos(float(triangle_id) * (PI * 2.0 / float(segments)))) * shape.xy + ); + + mat4 mvp = window.projection * window.view; + vec4 pos = vec4(rot * positions[vertex_id] + center, 0.0, 1.0); + gl_Position = mvp * pos; } diff --git a/arcade/resources/system/shaders/shapes/ellipse/outline_unbuffered_geo.glsl b/arcade/resources/system/shaders/shapes/ellipse/outline_unbuffered_geo.glsl deleted file mode 100644 index 2b9668ab1b..0000000000 --- a/arcade/resources/system/shaders/shapes/ellipse/outline_unbuffered_geo.glsl +++ /dev/null @@ -1,76 +0,0 @@ -#version 330 - -// 3 points per segment, max of 256 points, so 85 * 3 = 255 -const int MIN_SEGMENTS = 3; -const int MAX_SEGMENTS = 112; -const float PI = 3.141592; - -layout (points) in; -// TODO: We might want to increase the number of emitted vertices, 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 WindowBlock { - mat4 projection; - mat4 view; -} window; - -uniform int segments; -// [w, h, tilt, thickness] -uniform vec4 shape; - -void main() { - // Get center of the circle - vec2 center = gl_in[0].gl_Position.xy; - int segments_selected = segments; - - // Calculate rotation/tilt - float angle = radians(shape.z); - mat2 rot = mat2( - cos(angle), -sin(angle), - sin(angle), cos(angle) - ); - - if (segments_selected < 0) { - // Estimate the number of segments needed based on size - float size = max(shape.x, shape.y); - if (size <= 4.0) - segments_selected = 4; - else if (size <= 16.0) - segments_selected = 16; - else - segments_selected = 32; - } - // 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 st = PI * 2.0 / float(segments_selected); - - // Draw thick circle with triangle strip. This can be handled as a single primitive by the gpu. - // Number of vertices is segments * 2 + 2, so we need to emit the initial vertex first - - // First outer vertex - vec2 p_start = vec2(sin(0.0), cos(0.0)) * shape.xy; - gl_Position = window.projection * window.view * vec4((rot * p_start) + center, 0.0, 1.0); - EmitVertex(); - - // Draw cross segments from inner to outer - for (int i = 0; i < segments_selected; i++) { - // Inner vertex - vec2 p1 = vec2(sin(float(i) * st), cos(float(i) * st)) * (shape.xy - vec2(shape.w)); - gl_Position = window.projection * window.view * vec4((rot * p1) + center, 0.0, 1.0); - EmitVertex(); - - // Outer vertex - vec2 p2 = vec2(sin((float(i) + 1.0) * st), cos((float(i) + 1.0) * st)) * shape.xy; - gl_Position = window.projection * window.view * vec4((rot * p2) + center, 0.0, 1.0); - EmitVertex(); - } - // Last inner vertex to wrap up - vec2 p_end = vec2(sin(0.0), cos(0.0)) * (shape.xy - vec2(shape.w)); - gl_Position = window.projection * window.view * vec4((rot * p_end) + center, 0.0, 1.0); - EmitVertex(); - - EndPrimitive(); -} diff --git a/arcade/resources/system/shaders/shapes/ellipse/outline_unbuffered_vs.glsl b/arcade/resources/system/shaders/shapes/ellipse/outline_unbuffered_vs.glsl index 02be2f2654..8c0aea9da3 100644 --- a/arcade/resources/system/shaders/shapes/ellipse/outline_unbuffered_vs.glsl +++ b/arcade/resources/system/shaders/shapes/ellipse/outline_unbuffered_vs.glsl @@ -1,7 +1,45 @@ #version 330 -in vec2 in_vert; +uniform WindowBlock { + mat4 projection; + mat4 view; +} window; + +uniform vec2 center; +uniform int segments; +// [w, h, tilt, thickness] +uniform vec4 shape; + +const float PI = 3.141592; void main() { - gl_Position = vec4(in_vert, 0.0, 1.0); + // Two triangles per line segment of the outline + int segment_id = gl_VertexID / 6; + int vertex_id = gl_VertexID % 6; + + // Calculate rotation/tilt + float angle = radians(shape.z); + mat2 rot = mat2( + cos(angle), -sin(angle), + sin(angle), cos(angle) + ); + + // sin(v), cos(v) travels clockwise around the circle starting at 0, 1 (top of circle) + float st = PI * 2.0 / float(segments); + + // calculate the four points of the line segment + // Inner and outer points for the start of line segment + vec2 p0 = vec2(sin(float(segment_id) * st), cos(float(segment_id) * st)) * shape.xy; + vec2 p1 = vec2(sin(float(segment_id) * st), cos(float(segment_id) * st)) * (shape.xy - vec2(shape.w)); + + // Inner and outer points for the end of line segment + vec2 p2 = vec2(sin((float(segment_id) + 1.0) * st), cos((float(segment_id) + 1.0) * st)) * shape.xy; + vec2 p3 = vec2(sin((float(segment_id) + 1.0) * st), cos((float(segment_id) + 1.0) * st)) * (shape.xy - vec2(shape.w)); + + vec2 position[6] = vec2[6]( + p1, p0, p2, // first triangle + p1, p2, p3 // second triangle + ); + mat4 mvp = window.projection * window.view; + gl_Position = mvp * vec4(rot * position[vertex_id] + center, 0.0, 1.0); } diff --git a/arcade/resources/system/shaders/shapes/rectangle/filled_unbuffered_vs.glsl b/arcade/resources/system/shaders/shapes/rectangle/filled_unbuffered_vs.glsl index 6560d426c9..fbeb22178c 100644 --- a/arcade/resources/system/shaders/shapes/rectangle/filled_unbuffered_vs.glsl +++ b/arcade/resources/system/shaders/shapes/rectangle/filled_unbuffered_vs.glsl @@ -19,6 +19,6 @@ void main() { ); // vec2 size = shape.xy / 2.0; mat4 mvp = window.projection * window.view; - vec2 pos = in_instance_pos + (in_vert * shape.xy); - gl_Position = mvp * vec4(rot * pos, 0.0, 1.0); + vec2 pos = in_instance_pos + (rot * (in_vert * shape.xy)); + gl_Position = mvp * vec4(pos, 0.0, 1.0); }