# Transform Feedback¶

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