Transform Feedback

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