Skip to content

Commit e1115ae

Browse files
authored
Clean up draw_commands in areas potentially helpful for pythonarcade#2065 (pythonarcade#2069)
* Replace old bloated color validation with Color's from_iterable + normalize * Improve readability & fail speed in draw_commands * Move validation & unpacking ctx members to locals to top of functions * Use Color's from_iterable + normalize * Use named variables for goal buffer sizes to make buffer expansion clearer * Add comments and attempt to make math more readable * Finish dedupe of logic / name shortening in draw_lines * Skip tuple redundant tuple conversion since array.array accepts appropriate iterables * Don't re-divide when we already know the size * Explain safety // 8 * Separate steps better in draw_points * Copy to ctypes array earlier * Skip tuple conversion for array.array since it takes generators * Attempt to make draw_polygon_outline's current logic more readable * Sequence.extend doesn't exist, so annotate local variable as List[Point]
1 parent 5e4af62 commit e1115ae

File tree

1 file changed

+91
-80
lines changed

1 file changed

+91
-80
lines changed

arcade/draw_commands.py

Lines changed: 91 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@
1010

1111
import array
1212
import math
13-
from typing import Optional, Tuple
13+
from typing import Optional, Tuple, List
1414

1515
import PIL.Image
1616
import PIL.ImageOps
1717
import PIL.ImageDraw
1818

1919
import pyglet.gl as gl
2020

21-
from arcade.types import Color, RGBA255, PointList
21+
from arcade.types import Color, RGBA255, PointList, Point
2222
from arcade.earclip import earclip
2323
from .math import rotate_point
2424
from arcade import (
@@ -293,20 +293,17 @@ def draw_ellipse_filled(center_x: float, center_y: float,
293293
The default value of -1 means arcade will try to calculate a reasonable
294294
amount of segments based on the size of the circle.
295295
"""
296+
# Fail immediately if we have no window or context
296297
window = get_window()
297298
ctx = window.ctx
298-
299299
program = ctx.shape_ellipse_filled_unbuffered_program
300300
geometry = ctx.shape_ellipse_unbuffered_geometry
301301
buffer = ctx.shape_ellipse_unbuffered_buffer
302-
# We need to normalize the color because we are setting it as a float uniform
303-
if len(color) == 3:
304-
color_normalized = color[0] / 255, color[1] / 255, color[2] / 255, 1.0
305-
elif len(color) == 4:
306-
color_normalized = color[0] / 255, color[1] / 255, color[2] / 255, color[3] / 255 # type: ignore
307-
else:
308-
raise ValueError("Invalid color format. Use a 3 or 4 component tuple")
309302

303+
# Normalize the color because this shader takes a float uniform
304+
color_normalized = Color.from_iterable(color).normalized
305+
306+
# Pass data to the shader
310307
program['color'] = color_normalized
311308
program['shape'] = width / 2, height / 2, tilt_angle
312309
program['segments'] = num_segments
@@ -339,20 +336,17 @@ def draw_ellipse_outline(center_x: float, center_y: float,
339336
The default value of -1 means arcade will try to calculate a reasonable
340337
amount of segments based on the size of the circle.
341338
"""
339+
# Fail immediately if we have no window or context
342340
window = get_window()
343341
ctx = window.ctx
344-
345342
program = ctx.shape_ellipse_outline_unbuffered_program
346343
geometry = ctx.shape_ellipse_outline_unbuffered_geometry
347344
buffer = ctx.shape_ellipse_outline_unbuffered_buffer
348-
# We need to normalize the color because we are setting it as a float uniform
349-
if len(color) == 3:
350-
color_normalized = color[0] / 255, color[1] / 255, color[2] / 255, 1.0
351-
elif len(color) == 4:
352-
color_normalized = color[0] / 255, color[1] / 255, color[2] / 255, color[3] / 255 # type: ignore
353-
else:
354-
raise ValueError("Invalid color format. Use a 3 or 4 component tuple")
355345

346+
# Normalize the color because this shader takes a float uniform
347+
color_normalized = Color.from_iterable(color).normalized
348+
349+
# Pass data to shader
356350
program['color'] = color_normalized
357351
program['shape'] = width / 2, height / 2, tilt_angle, border_width
358352
program['segments'] = num_segments
@@ -380,29 +374,36 @@ def _generic_draw_line_strip(point_list: PointList,
380374
:param color: A color, specified as an RGBA tuple or a
381375
:py:class:`~arcade.types.Color` instance.
382376
"""
377+
# Fail if we don't have a window, context, or right GL abstractions
383378
window = get_window()
384379
ctx = window.ctx
385-
386-
c4 = Color.from_iterable(color)
387-
c4e = c4 * len(point_list)
388-
a = array.array('B', c4e)
389-
vertices = array.array('f', tuple(item for sublist in point_list for item in sublist))
390-
391380
geometry = ctx.generic_draw_line_strip_geometry
381+
vertex_buffer = ctx.generic_draw_line_strip_vbo
382+
color_buffer = ctx.generic_draw_line_strip_color
392383
program = ctx.line_vertex_shader
393-
geometry.num_vertices = len(point_list)
394384

395-
# Double buffer sizes if out of space
396-
while len(vertices) * 4 > ctx.generic_draw_line_strip_vbo.size:
397-
ctx.generic_draw_line_strip_vbo.orphan(ctx.generic_draw_line_strip_vbo.size * 2)
398-
ctx.generic_draw_line_strip_color.orphan(ctx.generic_draw_line_strip_color.size * 2)
385+
# Validate and alpha-pad color, then expand to multi-vertex form since
386+
# this shader normalizes internally as if made to draw multicolor lines.
387+
rgba = Color.from_iterable(color)
388+
num_vertices = len(point_list) # Fail if it isn't a sized / sequence object
389+
390+
# Translate Python objects into types arcade's Buffer objects accept
391+
color_array = array.array('B', rgba * num_vertices)
392+
vertex_array = array.array('f', tuple(item for sublist in point_list for item in sublist))
393+
geometry.num_vertices = num_vertices
394+
395+
# Double buffer sizes until they can hold all our data
396+
goal_vertex_buffer_size = len(vertex_array) * 4
397+
while goal_vertex_buffer_size > vertex_buffer.size:
398+
vertex_buffer.orphan(color_buffer.size * 2)
399+
color_buffer.orphan(color_buffer.size * 2)
399400
else:
400-
ctx.generic_draw_line_strip_vbo.orphan()
401-
ctx.generic_draw_line_strip_color.orphan()
402-
403-
ctx.generic_draw_line_strip_vbo.write(vertices)
404-
ctx.generic_draw_line_strip_color.write(a)
401+
vertex_buffer.orphan()
402+
color_buffer.orphan()
405403

404+
# Write data & render
405+
vertex_buffer.write(vertex_array)
406+
color_buffer.write(color_array)
406407
geometry.render(program, mode=mode)
407408

408409

@@ -419,7 +420,7 @@ def draw_line_strip(point_list: PointList,
419420
if line_width == 1:
420421
_generic_draw_line_strip(point_list, color, gl.GL_LINE_STRIP)
421422
else:
422-
triangle_point_list: PointList = []
423+
triangle_point_list: List[Point] = []
423424
# This needs a lot of improvement
424425
last_point = None
425426
for point in point_list:
@@ -444,24 +445,23 @@ def draw_line(start_x: float, start_y: float, end_x: float, end_y: float,
444445
:py:class:`~arcade.types.Color` instance.
445446
:param line_width: Width of the line in pixels.
446447
"""
448+
# Fail if we don't have a window, context, or right GL abstractions
447449
window = get_window()
448450
ctx = window.ctx
449-
450451
program = ctx.shape_line_program
451452
geometry = ctx.shape_line_geometry
452-
# We need to normalize the color because we are setting it as a float uniform
453-
if len(color) == 3:
454-
color_normalized = color[0] / 255, color[1] / 255, color[2] / 255, 1.0
455-
elif len(color) == 4:
456-
color_normalized = color[0] / 255, color[1] / 255, color[2] / 255, color[3] / 255 # type: ignore
457-
else:
458-
raise ValueError("Invalid color format. Use a 3 or 4 component tuple")
453+
line_pos_buffer = ctx.shape_line_buffer_pos
459454

460-
program['line_width'] = line_width
455+
# Validate & normalize to a pass the shader an RGBA float uniform
456+
color_normalized = Color.from_iterable(color).normalized
457+
458+
# Pass data to the shader
461459
program['color'] = color_normalized
462-
ctx.shape_line_buffer_pos.orphan() # Allocate new buffer internally
463-
ctx.shape_line_buffer_pos.write(
460+
program['line_width'] = line_width
461+
line_pos_buffer.orphan() # Allocate new buffer internally
462+
line_pos_buffer.write(
464463
data=array.array('f', (start_x, start_y, end_x, end_y)))
464+
465465
geometry.render(program, mode=gl.GL_LINES, vertices=2)
466466

467467

@@ -479,29 +479,32 @@ def draw_lines(point_list: PointList,
479479
:py:class:`~arcade.types.Color` instance.
480480
:param line_width: Width of the line in pixels.
481481
"""
482+
# Fail if we don't have a window, context, or right GL abstractions
482483
window = get_window()
483484
ctx = window.ctx
484-
485485
program = ctx.shape_line_program
486486
geometry = ctx.shape_line_geometry
487-
# We need to normalize the color because we are setting it as a float uniform
488-
if len(color) == 3:
489-
color_normalized = color[0] / 255, color[1] / 255, color[2] / 255, 1.0
490-
elif len(color) == 4:
491-
color_normalized = color[0] / 255, color[1] / 255, color[2] / 255, color[3] / 255 # type: ignore
492-
else:
493-
raise ValueError("Invalid color format. Use a 3 or 4 component tuple")
487+
line_buffer_pos = ctx.shape_line_buffer_pos
488+
489+
# Validate & normalize to a pass the shader an RGBA float uniform
490+
color_normalized = Color.from_iterable(color).normalized
494491

495-
while len(point_list) * 3 * 4 > ctx.shape_line_buffer_pos.size:
496-
ctx.shape_line_buffer_pos.orphan(ctx.shape_line_buffer_pos.size * 2)
492+
line_pos_array = array.array('f', (v for point in point_list for v in point))
493+
num_points = len(point_list)
494+
495+
# Grow buffer until large enough to hold all our data
496+
goal_buffer_size = num_points * 3 * 4
497+
while goal_buffer_size > line_buffer_pos.size:
498+
ctx.shape_line_buffer_pos.orphan(line_buffer_pos.size * 2)
497499
else:
498500
ctx.shape_line_buffer_pos.orphan()
499501

502+
# Pass data to shader
500503
program['line_width'] = line_width
501504
program['color'] = color_normalized
502-
ctx.shape_line_buffer_pos.write(
503-
data=array.array('f', tuple(v for point in point_list for v in point)))
504-
geometry.render(program, mode=gl.GL_LINES, vertices=len(point_list))
505+
line_buffer_pos.write(data=line_pos_array)
506+
507+
geometry.render(program, mode=gl.GL_LINES, vertices=num_points)
505508

506509

507510
# --- BEGIN POINT FUNCTIONS # # #
@@ -530,28 +533,31 @@ def draw_points(point_list: PointList, color: RGBA255, size: float = 1):
530533
:py:class:`~arcade.types.Color` instance.
531534
:param size: Size of the point in pixels.
532535
"""
536+
# Fails immediately if we don't have a window or context
533537
window = get_window()
534538
ctx = window.ctx
535-
536539
program = ctx.shape_rectangle_filled_unbuffered_program
537540
geometry = ctx.shape_rectangle_filled_unbuffered_geometry
538541
buffer = ctx.shape_rectangle_filled_unbuffered_buffer
539-
# We need to normalize the color because we are setting it as a float uniform
540-
if len(color) == 3:
541-
color_normalized = color[0] / 255, color[1] / 255, color[2] / 255, 1.0
542-
elif len(color) == 4:
543-
color_normalized = color[0] / 255, color[1] / 255, color[2] / 255, color[3] / 255 # type: ignore
544-
else:
545-
raise ValueError("Invalid color format. Use a 3 or 4 component tuple")
542+
543+
# Validate & normalize to a pass the shader an RGBA float uniform
544+
color_normalized = Color.from_iterable(color).normalized
545+
546+
# Get # of points and translate Python tuples to a C-style array
547+
num_points = len(point_list)
548+
point_array = array.array('f', (v for point in point_list for v in point))
546549

547550
# Resize buffer
548-
data_size = len(point_list) * 8
551+
data_size = num_points * 8
549552
# if data_size > buffer.size:
550553
buffer.orphan(size=data_size)
551554

555+
# Pass data to shader
552556
program['color'] = color_normalized
553557
program['shape'] = size, size, 0
554-
buffer.write(data=array.array('f', tuple(v for point in point_list for v in point)))
558+
buffer.write(data=point_array)
559+
560+
# Only render the # of points we have complete data for
555561
geometry.render(program, mode=ctx.POINTS, vertices=data_size // 8)
556562

557563

@@ -585,22 +591,29 @@ def draw_polygon_outline(point_list: PointList,
585591
:py:class:`~arcade.types.Color` instance.
586592
:param line_width: Width of the line in pixels.
587593
"""
594+
# Convert to modifiable list & close the loop
588595
new_point_list = list(point_list)
589596
new_point_list.append(point_list[0])
590597

598+
# Create a place to store the triangles we'll use to thicken the line
591599
triangle_point_list = []
600+
592601
# This needs a lot of improvement
593602
last_point = None
594603
for point in new_point_list:
595604
if last_point is not None:
596-
points = get_points_for_thick_line(last_point[0], last_point[1], point[0], point[1], line_width)
605+
# Calculate triangles, then re-order to link up the quad?
606+
points = get_points_for_thick_line(*last_point, *point, line_width)
597607
reordered_points = points[1], points[0], points[2], points[3]
608+
598609
triangle_point_list.extend(reordered_points)
599610
last_point = point
600611

601-
points = get_points_for_thick_line(new_point_list[0][0], new_point_list[0][1], new_point_list[1][0],
602-
new_point_list[1][1], line_width)
612+
# Use first two points of new list to close the loop
613+
new_start, new_next = new_point_list[:2]
614+
points = get_points_for_thick_line(*new_start, *new_next, line_width)
603615
triangle_point_list.append(points[1])
616+
604617
_generic_draw_line_strip(triangle_point_list, color, gl.GL_TRIANGLE_STRIP)
605618

606619

@@ -875,24 +888,22 @@ def draw_rectangle_filled(center_x: float, center_y: float, width: float,
875888
:py:class:`tuple` or :py:class`~arcade.types.Color` instance.
876889
:param tilt_angle: rotation of the rectangle (clockwise). Defaults to zero.
877890
"""
891+
# Fail if we don't have a window, context, or right GL abstractions
878892
window = get_window()
879893
ctx = window.ctx
880-
881894
program = ctx.shape_rectangle_filled_unbuffered_program
882895
geometry = ctx.shape_rectangle_filled_unbuffered_geometry
883896
buffer = ctx.shape_rectangle_filled_unbuffered_buffer
884-
# We need to normalize the color because we are setting it as a float uniform
885-
if len(color) == 3:
886-
color_normalized = (color[0] / 255, color[1] / 255, color[2] / 255, 1.0)
887-
elif len(color) == 4:
888-
color_normalized = (color[0] / 255, color[1] / 255, color[2] / 255, color[3] / 255) # type: ignore
889-
else:
890-
raise ValueError("Invalid color format. Use a 3 or 4 component tuple")
891897

898+
# Validate & normalize to a pass the shader an RGBA float uniform
899+
color_normalized = Color.from_iterable(color).normalized
900+
901+
# Pass data to the shader
892902
program['color'] = color_normalized
893903
program['shape'] = width, height, tilt_angle
894904
buffer.orphan()
895905
buffer.write(data=array.array('f', (center_x, center_y)))
906+
896907
geometry.render(program, mode=ctx.POINTS, vertices=1)
897908

898909

0 commit comments

Comments
 (0)