Transform Feedback#

Transform Feedback Example
transform_feedback.py#
  1"""
  2Shows simple use of transform feedback.
  3
  4Transforming is similar to rendering except that the output
  5or the shader is a buffer instead of a framebuffer/screen.
  6
  7This examples shows a common ping-pong technique were we
  8transform a buffer with positions and velocities between
  9two buffers so we always work on the previous state.
 10
 11* A list of N points are initialized with random positions and velocities
 12* A point of gravity is moving around on the screen affecting the points
 13
 14Using transforms in this way makes us able to process
 15a system that is reacting to external forces in this way.
 16There are no predetermined paths and they system just lives on its own.
 17
 18If Python and Arcade are installed, this example can be run from the command line with:
 19python -m arcade.examples.transform_feedback
 20"""
 21from __future__ import annotations
 22
 23from array import array
 24import math
 25import time
 26import random
 27import arcade
 28from arcade.gl import BufferDescription
 29
 30# Do the math to figure out our screen dimensions
 31SCREEN_WIDTH = 800
 32SCREEN_HEIGHT = 600
 33SCREEN_TITLE = "Transform Feedback"
 34
 35
 36class MyGame(arcade.Window):
 37
 38    def __init__(self, width, height, title):
 39        super().__init__(width, height, title, resizable=True)
 40        self.time = 0
 41
 42        # Program to visualize the points
 43        self.points_program = self.ctx.program(
 44            vertex_shader="""
 45            #version 330
 46            in vec2 in_pos;
 47            out vec3 color;
 48            void main() {
 49                // Let's just give them a "random" color based on the vertex id
 50                color = vec3(
 51                    mod(float(gl_VertexID * 100 % 11) / 10.0, 1.0),
 52                    mod(float(gl_VertexID * 100 % 27) / 10.0, 1.0),
 53                    mod(float(gl_VertexID * 100 % 71) / 10.0, 1.0));
 54                // Pass the point position to primitive assembly
 55                gl_Position = vec4(in_pos, 0.0, 1.0);
 56            }
 57            """,
 58            fragment_shader="""
 59            #version 330
 60
 61            // Color passed in from the vertex shader
 62            in vec3 color;
 63            // The pixel we are writing to in the framebuffer
 64            out vec4 fragColor;
 65
 66            void main() {
 67                // Fill the point
 68                fragColor = vec4(color, 1.0);
 69            }
 70            """,
 71        )
 72
 73        # A program transforming points being affected by a gravity point
 74        self.gravity_program = self.ctx.program(
 75            vertex_shader="""
 76            #version 330
 77
 78            // Delta time (since last frame)
 79            uniform float dt;
 80            // Strength of gravity
 81            uniform float force;
 82            // Position of gravity
 83            uniform vec2 gravity_pos;
 84
 85            // The format of the data in our transform buffer(s)
 86            in vec2 in_pos;
 87            in vec2 in_vel;
 88
 89            // We are writing to a buffer of the same format
 90            out vec2 out_pos;
 91            out vec2 out_vel;
 92
 93            void main() {
 94                // Simplified gravity calculations
 95                vec2 dir = normalize(gravity_pos - in_pos) * force;
 96                vec2 vel = in_vel + dir / length(dir) * 0.01;
 97
 98                // Write to the output buffer
 99                out_vel = vel;
100                out_pos = in_pos + vel * dt;
101            }
102            """,
103        )
104        N = 50_000
105        # Make two buffers we transform between so we can work on the previous result
106        self.buffer_1 = self.ctx.buffer(data=array('f', self.gen_initial_data(N)))
107        self.buffer_2 = self.ctx.buffer(reserve=self.buffer_1.size)
108
109        # We also need to be able to visualize both versions (draw to the screen)
110        self.vao_1 = self.ctx.geometry([BufferDescription(self.buffer_1, '2f 2x4', ['in_pos'])])
111        self.vao_2 = self.ctx.geometry([BufferDescription(self.buffer_2, '2f 2x4', ['in_pos'])])
112
113        # We need to be able to transform both buffers (ping-pong)
114        self.gravity_1 = self.ctx.geometry([BufferDescription(self.buffer_1, '2f 2f', ['in_pos', 'in_vel'])])
115        self.gravity_2 = self.ctx.geometry([BufferDescription(self.buffer_2, '2f 2f', ['in_pos', 'in_vel'])])
116
117        self.ctx.enable_only()  # Ensure no context flags are set
118        self.time = time.time()
119
120    def gen_initial_data(self, count):
121        for _ in range(count):
122            yield random.uniform(-1.2, 1.2)  # pos x
123            yield random.uniform(-1.2, 1.2)  # pos y
124            yield random.uniform(-.3, .3)  # velocity x
125            yield random.uniform(-.3, .3)  # velocity y
126
127    def on_draw(self):
128        self.clear()
129        self.ctx.point_size = 2 * self.get_pixel_ratio()
130
131        # Calculate the actual delta time and current time
132        t = time.time()
133        frame_time = t - self.time
134        self.time = t
135
136        # Set uniforms in the program
137        self.gravity_program['dt'] = frame_time
138        self.gravity_program['force'] = 0.25
139        self.gravity_program['gravity_pos'] = math.sin(self.time * 0.77) * 0.25, math.cos(self.time) * 0.25
140
141        # Transform data in buffer_1 into buffer_2
142        self.gravity_1.transform(self.gravity_program, self.buffer_2)
143        # Render the result (Draw buffer_2)
144        self.vao_2.render(self.points_program, mode=self.ctx.POINTS)
145
146        # Swap around stuff around so we transform back and fourth between the two buffers
147        self.gravity_1, self.gravity_2 = self.gravity_2, self.gravity_1
148        self.vao_1, self.vao_2 = self.vao_2, self.vao_1
149        self.buffer_1, self.buffer_2 = self.buffer_2, self.buffer_1
150
151
152if __name__ == "__main__":
153    window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
154    window.center_window()
155    arcade.run()