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 array import array
 22import math
 23import random
 24import arcade
 25from arcade.gl import BufferDescription
 26
 27# Do the math to figure out our screen dimensions
 28WINDOW_WIDTH = 1280
 29WINDOW_HEIGHT = 720
 30WINDOW_TITLE = "Transform Feedback"
 31
 32
 33class GameView(arcade.View):
 34
 35    def __init__(self):
 36        super().__init__()
 37        # get the GL context from the window
 38        context = self.window.ctx
 39
 40        # Program to visualize the points
 41        self.points_program = context.program(
 42            vertex_shader="""
 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            """,
 56            fragment_shader="""
 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 = context.program(
 73            vertex_shader="""
 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 = context.buffer(data=array('f', self.gen_initial_data(N)))
105        self.buffer_2 = context.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 = context.geometry([BufferDescription(self.buffer_1, '2f 2x4', ['in_pos'])])
109        self.vao_2 = context.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 = context.geometry(
113            [BufferDescription(self.buffer_1, '2f 2f', ['in_pos', 'in_vel'])]
114        )
115        self.gravity_2 = context.geometry(
116            [BufferDescription(self.buffer_2, '2f 2f', ['in_pos', 'in_vel'])]
117        )
118
119        context.enable_only()  # Ensure no context flags are set
120
121    def gen_initial_data(self, count):
122        for _ in range(count):
123            yield random.uniform(-1.2, 1.2)  # pos x
124            yield random.uniform(-1.2, 1.2)  # pos y
125            yield random.uniform(-.3, .3)  # velocity x
126            yield random.uniform(-.3, .3)  # velocity y
127
128    def on_draw(self):
129        self.clear()
130        self.window.ctx.point_size = 2 * self.window.get_pixel_ratio()
131
132        # Set uniforms in the program
133        self.gravity_program['dt'] = self.window.delta_time
134        self.gravity_program['force'] = 0.25
135        self.gravity_program['gravity_pos'] = (
136            math.sin(self.window.time * 0.77) * 0.25, math.cos(self.window.time) * 0.25
137        )
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=arcade.gl.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
149def main():
150    """ Main function """
151    # Create a window class. This is what actually shows up on screen
152    window = arcade.Window(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE)
153
154    # Create the GameView
155    game = GameView()
156
157    # Show GameView on screen
158    window.show_view(game)
159
160    # Start the arcade game loop
161    arcade.run()
162
163
164
165if __name__ == "__main__":
166    main()