Skip to content

Commit 3501dbd

Browse files
authored
New spritelist rendering system - Support for non-geo shader system (#2699)
* Initial work * Initial attempt separating out spritelist data * More spritelist rendering work * Merge pos and angle buffers * Prepare data for non-geo shaders * Create renderer for non-geo shaders * More work .. * Fix texture formats * First working version * Remove debug prints in atlas * Remove debug code in texture data * Support 1M sprites * Remove debug stuff * Rewrite gpu collision for spritelists * Working gpu collision with texture backend * Remove debug print * GLSL: avoid C-style initialization * Fix tests and typing * Type fix after mypy upgrade * import order * test: Add WindowProxy.register_event_type * test: Skip spritelist interaction for now * Remaining sprite work - UV bias - Single sprite rendering - Cache more stuff in context * lint
1 parent fbafcb5 commit 3501dbd

22 files changed

Lines changed: 1160 additions & 448 deletions

arcade/context.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
Contains pre-loaded programs
44
"""
55

6+
from array import array
67
from collections.abc import Iterable, Sequence
78
from pathlib import Path
89
from typing import Any
@@ -99,6 +100,20 @@ def __init__(
99100
self.sprite_list_program_cull["sprite_texture"] = 0
100101
self.sprite_list_program_cull["uv_texture"] = 1
101102

103+
self.sprite_list_program_no_geo = self.load_program(
104+
vertex_shader=":system:shaders/sprites/sprite_list_simple_vs.glsl",
105+
fragment_shader=":system:shaders/sprites/sprite_list_simple_fs.glsl",
106+
)
107+
self.sprite_list_program_no_geo["sprite_texture"] = 0
108+
self.sprite_list_program_no_geo["uv_texture"] = 1
109+
# Per-instance data
110+
self.sprite_list_program_no_geo["pos_data"] = 2
111+
self.sprite_list_program_no_geo["size_data"] = 3
112+
self.sprite_list_program_no_geo["color_data"] = 4
113+
self.sprite_list_program_no_geo["texture_id_data"] = 5
114+
self.sprite_list_program_no_geo["index_data"] = 6
115+
116+
# Geo shader single sprite program
102117
self.sprite_program_single = self.load_program(
103118
vertex_shader=":system:shaders/sprites/sprite_single_vs.glsl",
104119
geometry_shader=":system:shaders/sprites/sprite_list_geometry_no_cull_geo.glsl",
@@ -107,6 +122,34 @@ def __init__(
107122
self.sprite_program_single["sprite_texture"] = 0
108123
self.sprite_program_single["uv_texture"] = 1
109124
self.sprite_program_single["spritelist_color"] = 1.0, 1.0, 1.0, 1.0
125+
# Non-geometry shader single sprite program
126+
self.sprite_program_single_simple = self.load_program(
127+
vertex_shader=":system:shaders/sprites/sprite_single_simple_vs.glsl",
128+
fragment_shader=":system:shaders/sprites/sprite_list_simple_fs.glsl",
129+
)
130+
self.sprite_program_single_simple["sprite_texture"] = 0
131+
self.sprite_program_single_simple["uv_texture"] = 1
132+
self.sprite_program_single_simple["spritelist_color"] = 1.0, 1.0, 1.0, 1.0
133+
134+
# fmt: off
135+
self.spritelist_geometry_simple = self.geometry(
136+
[
137+
BufferDescription(
138+
self.buffer(
139+
data=array("f", [
140+
-0.5, +0.5, # Upper left
141+
-0.5, -0.5, # lower left
142+
+0.5, +0.5, # upper right
143+
+0.5, -0.5, # lower right
144+
])
145+
),
146+
"2f",
147+
["in_pos"]
148+
),
149+
],
150+
mode=self.TRIANGLE_STRIP,
151+
)
152+
# fmt: on
110153

111154
# Shapes
112155
self.shape_line_program: Program = self.load_program(
@@ -144,11 +187,22 @@ def __init__(
144187
self.atlas_resize_program["texcoords_old"] = 2
145188
self.atlas_resize_program["texcoords_new"] = 3
146189

190+
# NOTE: These should not be created when WebGL is used
147191
# SpriteList collision resources
192+
# Buffer version of the collision detection program.
148193
self.collision_detection_program = self.load_program(
149194
vertex_shader=":system:shaders/collision/col_trans_vs.glsl",
150195
geometry_shader=":system:shaders/collision/col_trans_gs.glsl",
151196
)
197+
# Texture version of the collision detection program.
198+
self.collision_detection_program_simple = self.load_program(
199+
vertex_shader=":system:shaders/collision/col_tex_trans_vs.glsl",
200+
geometry_shader=":system:shaders/collision/col_tex_trans_gs.glsl",
201+
)
202+
self.collision_detection_program_simple["pos_angle_data"] = 0
203+
self.collision_detection_program_simple["size_data"] = 1
204+
self.collision_detection_program_simple["index_data"] = 2
205+
152206
self.collision_buffer = self.buffer(reserve=1024 * 4)
153207
self.collision_query = self.query(samples=False, time=False, primitives=True)
154208

arcade/draw/rect.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def draw_texture_rect(
5454
ctx.disable(ctx.BLEND)
5555

5656
atlas = atlas or ctx.default_atlas
57-
program = ctx.sprite_program_single
57+
program = ctx.sprite_program_single_simple
5858

5959
texture_id, _ = atlas.add(texture)
6060
if pixelated:
@@ -68,14 +68,13 @@ def draw_texture_rect(
6868
atlas.use_uv_texture(unit=1)
6969

7070
geometry = ctx.geometry_empty
71-
program["pos"] = rect.x, rect.y, 0
71+
program["pos_rot"] = rect.x, rect.y, 0, angle
7272
program["color"] = color.normalized
7373
program["size"] = rect.width, rect.height
74-
program["angle"] = angle
75-
program["texture_id"] = float(texture_id)
74+
program["texture_id"] = texture_id
7675
program["spritelist_color"] = 1.0, 1.0, 1.0, alpha_normalized
7776

78-
geometry.render(program, mode=gl.POINTS, vertices=1)
77+
geometry.render(program, mode=gl.TRIANGLE_STRIP, vertices=4)
7978

8079
if blend:
8180
ctx.disable(ctx.BLEND)

arcade/examples/sections_demo_3.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def draw_button(self):
8787
def on_resize(self, width: int, height: int):
8888
"""set position on screen resize"""
8989
self.left = width // 3
90-
self.bottom = (height // 2) - self.height // 2
90+
self.bottom = (height // 2) - self.height // 2 # type: ignore
9191
pos = self.left + self.width / 2, self.bottom + self.height / 2
9292
self.button.position = pos
9393

@@ -203,7 +203,7 @@ def on_mouse_press(self, x: float, y: float, button: int, modifiers: int):
203203

204204
def on_resize(self, width: int, height: int):
205205
# stick to the right
206-
self.left = width - self.width
206+
self.left = width - self.width # type: ignore
207207
self.height = height - self.view.info_bar.height
208208
self.button_stop.position = self.left + self.width / 2, self.top - 80
209209

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#version 330
2+
// Texture version if collision shader
3+
4+
layout(points) in;
5+
layout(points, max_vertices=1) out;
6+
7+
uniform vec2 check_pos;
8+
uniform vec2 check_size;
9+
10+
in vec2 pos[];
11+
in vec2 size[];
12+
13+
out int spriteIndex;
14+
15+
void main() {
16+
// Calculate the distance between the sprite center
17+
// and the point we want to check
18+
float dist = distance(pos[0], check_pos);
19+
20+
// Get the maximum x and y size
21+
// max() works per component
22+
vec2 size = max(size[0], check_size);
23+
24+
// Destroy the sprite if too far away
25+
if (dist < max(size.x, size.y) * 1.42) {
26+
// Set the sprite index to the current primitive id
27+
// We are only processing points, so it will match
28+
// the spritelist index
29+
spriteIndex = int(gl_PrimitiveIDIn);
30+
EmitVertex();
31+
}
32+
33+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#version 330
2+
// Texture version if collision shader
3+
4+
#include :system:shaders/lib/sprite.glsl
5+
6+
uniform sampler2D pos_angle_data;
7+
uniform sampler2D size_data;
8+
uniform isampler2D index_data;
9+
10+
out vec2 pos;
11+
out vec2 size;
12+
13+
void main() {
14+
int index = getInstanceIndex(index_data, gl_VertexID);
15+
vec4 _pos_rot = getInstancePosRot(pos_angle_data, index);
16+
vec2 _size = getInstanceSize(size_data, index);
17+
18+
pos = _pos_rot.xy;
19+
size = _size.xy;
20+
}

arcade/resources/system/shaders/collision/col_trans_gs.glsl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#version 330
2+
// Buffer version if collision shader
23

34
layout(points) in;
45
layout(points, max_vertices=1) out;

arcade/resources/system/shaders/collision/col_trans_vs.glsl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#version 330
2-
// A simple passthrough shader forwarding data to the geomtry shader
2+
// Buffer version if collision shader
33

4-
in vec3 in_pos;
4+
in vec4 in_pos;
55
in vec2 in_size;
66

77
out vec2 pos;

arcade/resources/system/shaders/lib/sprite.glsl

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Fetch texture coordiantes from uv texture
1+
// Fetch texture coordinates from uv texture
22
void getSpriteUVs(sampler2D uvData, int texture_id, out vec2 uv0, out vec2 uv1, out vec2 uv2, out vec2 uv3) {
33
texture_id *= 2;
44
// Calculate the position in the texture. Basic "line wrapping".
@@ -14,3 +14,26 @@ void getSpriteUVs(sampler2D uvData, int texture_id, out vec2 uv0, out vec2 uv1,
1414
uv2 = data_2.xy;
1515
uv3 = data_2.zw;
1616
}
17+
18+
// Functions for fetching per-instance data from textures.
19+
// These are used with the shader program that uses instancing to render sprites
20+
// meaning there is no geo shader involved. This should work for WebGL.
21+
vec4 getInstancePosRot(sampler2D posData, int index) {
22+
return texelFetch(posData, ivec2(index % 256, index / 256), 0);
23+
}
24+
25+
vec2 getInstanceSize(sampler2D sizeData, int index) {
26+
return texelFetch(sizeData, ivec2(index % 256, index / 256), 0).xy;
27+
}
28+
29+
vec4 getInstanceColor(sampler2D colorData, int index) {
30+
return texelFetch(colorData, ivec2(index % 256, index / 256), 0);
31+
}
32+
33+
int getInstanceTextureId(sampler2D textureIdData, int index) {
34+
return int(texelFetch(textureIdData, ivec2(index % 256, index / 256), 0).x);
35+
}
36+
37+
int getInstanceIndex(isampler2D indexData, int index) {
38+
return texelFetch(indexData, ivec2(index % 256, index / 256), 0).x;
39+
}

arcade/resources/system/shaders/sprites/sprite_list_geometry_cull_geo.glsl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ void main() {
4242
mat4 mvp = window.projection * window.view;
4343
// Do viewport culling for sprites.
4444
// We do this in normalized device coordinates to make it simple
45-
// apply projection to the center point. This is important so we get zooming/scrollig right
45+
// apply projection to the center point. This is important so we get zooming/scrolling right
4646
vec2 ct = (mvp * vec4(center.xy, 0.0, 1.0)).xy;
4747
// We can get away with cheaper calculation of size
4848
// The length of the diagonal is the cheapest estimation in case rotation is applied

arcade/resources/system/shaders/sprites/sprite_list_geometry_fs.glsl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ in vec4 gs_color;
1111
out vec4 f_color;
1212

1313
void main() {
14-
vec4 basecolor = texture(sprite_texture, gs_uv);
15-
basecolor *= gs_color * spritelist_color;
14+
vec4 base_color = texture(sprite_texture, gs_uv);
15+
base_color *= gs_color * spritelist_color;
1616
// Alpha test
17-
if (basecolor.a == 0.0) {
17+
if (base_color.a == 0.0) {
1818
discard;
1919
}
20-
f_color = basecolor;
20+
f_color = base_color;
2121
}

0 commit comments

Comments
 (0)