-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsim_pollock.c
More file actions
300 lines (259 loc) · 14.5 KB
/
Copy pathsim_pollock.c
File metadata and controls
300 lines (259 loc) · 14.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
// The pollock simulation -> SIM_POLLOCK. P particles split into three families that share the
// particle substrate but keep separate density fields -- one channel each of a single RGB
// texture. A particle feels only its own family's gradient, so the three fields are independent:
// goo's force law, three times over, differing in nothing but hue. No trail field.
//
// Family membership is STORED, not derived from the particle index: the screen pass draws
// through the tile-sorted index buffer, so index ranges are not spatially meaningful, and
// membership is meant to become mutable. It lives in its own single-buffered R32F texture (read
// by the physics, which is a fullscreen pass over the particle buffer) plus a matching static
// vertex attribute (read by the point passes).
#include "sim_def.h"
#include <stdlib.h>
#include <gl.h>
#include "pollock/family.h"
#include "pollock/screen.h"
#include "pollock/velocity.h"
#define POLLOCK_FAMILIES 3
#define FAMILY_ATTRIB 2 // loc 0 = position, loc 1 = velocity (both core's)
Shader pollockScreenShader = {.name = "pollockScreenShader"};
Shader pollockVelocityShader = {.name = "pollockVelocityShader"};
Shader pollockFamilyShader = {.name = "pollockFamilyShader"};
Buffer pollockFamilyBuffer = {.name = "Pollock family buffer", .textures = &pool_textures, .framebuffers = &pool_framebuffers, .current = 0, .other = 0};
static PBindex pollock_family_index = -1;
static GLuint family_vbo = 0; // the family texture read back, as a vertex attribute
// Placeholder so pollock keeps a --pollock.* block: the option table, its --help section and the
// merge into core's table all key off a non-empty opts array, and pollock will have real flags
// again. Reads and writes nothing.
static dropt_bool pollock_dummy = 0;
// Three channels, one per family; the family texture doubles as the channel index the core
// density pass reads off vertex attribute 2.
static Density pollock_density = {0};
// pollock' snapshot blob: the family assignment and the exposure level. Family membership is
// ASSIGNED state, not derived -- either randomly at a cold start or from goo's trail on a
// transition -- so losing it across a context refresh would silently re-roll every particle's
// identity. Density is absent for the same reason as goo's: it regenerates in a frame or two.
typedef struct PollockBlob {
int pb_w, pb_h;
float ema;
// float family[pb_w * pb_h] follows
} PollockBlob;
static void pollock_shaders(void) {
shader_create(&pollockVelocityShader);
shader_compile(&pollockVelocityShader, GL_VERTEX_SHADER, pollock_velocity_VertexShaderSource);
shader_compile(&pollockVelocityShader, GL_FRAGMENT_SHADER, pollock_velocity_FragmentShaderSource);
shader_link(&pollockVelocityShader);
shader_create(&pollockFamilyShader);
shader_compile(&pollockFamilyShader, GL_VERTEX_SHADER, pollock_family_VertexShaderSource);
shader_compile(&pollockFamilyShader, GL_FRAGMENT_SHADER, pollock_family_FragmentShaderSource);
shader_link(&pollockFamilyShader);
shader_create(&pollockScreenShader);
shader_compile(&pollockScreenShader, GL_VERTEX_SHADER, pollock_screen_VertexShaderSource);
shader_compile(&pollockScreenShader, GL_FRAGMENT_SHADER, pollock_screen_FragmentShaderSource);
shader_link(&pollockScreenShader);
}
// Cold start: one seeded draw per particle. Membership is interleaved at every scale, which the
// day-1 force law never undoes -- so the image is a uniform three-colour mix and cannot be used
// to tell a working family assignment from a broken one. Verify with --window.dump instead: the
// density dump is RGB, one channel per family.
static float *pollock_family_field(int n) {
float *f = (float *)malloc((size_t)n * sizeof(float));
if (!f) {
fprintf(stderr, "goo: out of memory generating boid families\n");
exit(EXIT_FAILURE);
}
srand(rng_seed ? (unsigned int)rng_seed : (unsigned int)P);
for (int i = 0; i < n; i++)
f[i] = (float)(rand() % POLLOCK_FAMILIES);
return f;
}
// Point the family vertex attribute at the readback buffer. This has to happen before EVERY point
// pass that reads it, not once at setup: the core density splat owns the same attribute and turns
// it off for single-channel sims, so during a transition -- where goo's fields() and pollock's
// draw() run in the same frame -- goo's splat would otherwise leave it disabled and every pollock
// particle would render as family 0. That alternates with the duty cycle and reads as a colour
// flash rather than a cross-fade.
static void pollock_bind_family_attrib(void) {
glBindBuffer(GL_ARRAY_BUFFER, family_vbo);
glEnableVertexAttribArray(FAMILY_ATTRIB);
glVertexAttribPointer(FAMILY_ATTRIB, 1, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
// The family TEXTURE is the source of truth -- the physics is a fullscreen pass and reads it by
// texel. The point passes need the same values as a vertex attribute, so copy texture -> buffer
// (an on-GPU transfer, like the position/velocity readback) whenever the assignment changes:
// once at setup, and again after a transition rewrites it.
static void pollock_family_sync(void) {
glBindFramebuffer(GL_READ_FRAMEBUFFER, pool_framebuffers.items[pollockFamilyBuffer.current]);
glBindBuffer(GL_PIXEL_PACK_BUFFER, family_vbo);
glReadPixels(0, 0, PB_width, PB_height, GL_RED, GL_FLOAT, 0);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
pollock_bind_family_attrib();
}
static void pollock_buffers(const void *blob, size_t blob_size) {
// A blob from a differently-sized run is not usable; fall through to a fresh assignment.
const PollockBlob *b = NULL;
if (blob && blob_size >= sizeof(PollockBlob)) {
const PollockBlob *cand = (const PollockBlob *)blob;
if (cand->pb_w == PB_width && cand->pb_h == PB_height &&
blob_size == sizeof(PollockBlob) + (size_t)PB_width * PB_height * sizeof(float))
b = cand;
}
density_create(&pollock_density, "Pollock density buffer", POLLOCK_FAMILIES);
pollock_family_index = pool_claim(1);
// Family field: NEAREST and CLAMP, since an interpolated family index is meaningless.
int n = PB_width * PB_height;
float *fam = NULL;
const float *fam_src;
if (b) {
fam_src = (const float *)(b + 1); // restored assignment, not a re-roll
pollock_density.headroom.ema = b->ema;
} else {
fam = pollock_family_field(n);
fam_src = fam;
}
pollockFamilyBuffer.minmag_filter = GL_NEAREST;
pollockFamilyBuffer.wrap_st = GL_CLAMP_TO_EDGE;
pollockFamilyBuffer.dim = BE_1D;
buffer_allocate(&pollockFamilyBuffer, current, pollock_family_index, PB_width, PB_height, (const char *)fam_src);
free(fam); // NULL on the restore path, which is a no-op
// Generate unconditionally: on a --core.gl-refresh the old name died with the old context,
// so reusing it would bind an object that no longer exists. Same reasoning as the ring reset
// inside density_create, and as sim.c's unconditional gen of the position/velocity PBO ring.
glGenBuffers(1, &family_vbo);
glBindBuffer(GL_ARRAY_BUFFER, family_vbo);
glBufferData(GL_ARRAY_BUFFER, (size_t)n * sizeof(float), NULL, GL_DYNAMIC_COPY);
glBindBuffer(GL_ARRAY_BUFFER, 0);
pollock_family_sync();
pollock_density.channel_vbo = family_vbo; // the family index IS the density channel index
if (b)
pollock_density.headroom.ema = b->ema; // after density_create, which seeds it to 0
}
static void pollock_static_uniforms(void) {
shader_set_uniform_int(&pollockVelocityShader, "position_buffer", positionBuffer.current);
shader_set_uniform_int(&pollockVelocityShader, "velocity_buffer", velocityBuffer.current);
shader_set_uniform_int(&pollockVelocityShader, "family_buffer", pollockFamilyBuffer.current);
shader_set_uniform_int(&pollockVelocityShader, "density_buffer", pollock_density.buffer.current);
shader_set_uniform_int(&pollockVelocityShader, "repel_buffer", repelBuffer.current);
shader_set_uniform_float(&pollockVelocityShader, "drag_coefficient", (float)dragCoefficient);
shader_set_uniform_float(&pollockVelocityShader, "dither_coefficient", (float)ditherCoefficient);
shader_set_uniform_float(&pollockVelocityShader, "dither_density_gain", (float)ditherDensityGain);
shader_set_uniform_float(&pollockVelocityShader, "dither_ortho", (float)ditherOrtho);
shader_set_uniform_float(&pollockVelocityShader, "density_force", (float)densityForce);
shader_set_uniform_float(&pollockVelocityShader, "reach", (float)reach);
shader_set_uniform_float(&pollockVelocityShader, "repel_coefficient", (float)repel);
shader_set_uniform_float(&pollockVelocityShader, "anti_stick", (float)antiStick);
shader_set_uniform_int(&pollockScreenShader, "density_buffer", pollock_density.buffer.current);
shader_set_uniform_float(&pollockScreenShader, "cull_amount", (float)cullAmount);
shader_set_uniform_float(&pollockScreenShader, "render_gamma", (float)renderGamma);
shader_set_uniform_float(&pollockScreenShader, "alpha_speed", (float)alphaSpeed);
}
static void pollock_integrate(int epoch_counter, const float mouse_position[2], const float mouse_velocity[2]) {
(void)mouse_position;
(void)mouse_velocity; // the mouse reaches pollock through the shared repel field, not directly
shader_use(&pollockVelocityShader);
shader_set_uniform_vec(&pollockVelocityShader, "buffer_shape", 2, (float[]){(float)repel_width, (float)repel_height});
shader_set_uniform_int(&pollockVelocityShader, "position_buffer", positionBuffer.current);
shader_set_uniform_int(&pollockVelocityShader, "velocity_buffer", velocityBuffer.current);
shader_set_uniform_int(&pollockVelocityShader, "epoch_counter", epoch_counter);
buffer_bind(&velocityBuffer, other);
buffer_update(&velocityBuffer);
buffer_flip(&velocityBuffer);
LAP(sim_pass_base + 0); // vel
shader_use(&positionShader); // core's, shared by every sim
shader_set_uniform_int(&positionShader, "position_buffer", positionBuffer.current);
shader_set_uniform_int(&positionShader, "velocity_buffer", velocityBuffer.current);
buffer_bind(&positionBuffer, other);
buffer_update(&positionBuffer);
buffer_flip(&positionBuffer);
LAP(sim_pass_base + 1); // pos
}
static void pollock_fields(int epoch_counter) {
if (dens_every <= 1 || epoch_counter % dens_every == 0)
density_splat(&pollock_density);
LAP(sim_pass_base + 2); // dens
}
static void pollock_draw(float weight) {
pollock_bind_family_attrib();
shader_use(&pollockScreenShader);
shader_set_uniform_float(&pollockScreenShader, "draw_weight", weight);
shader_set_uniform_float(&pollockScreenShader, "render_headroom",
renderHeadroom > 0.0 ? (float)renderHeadroom : pollock_density.headroom.ema);
glDrawElements(GL_POINTS, P, GL_UNSIGNED_INT, 0);
}
static void pollock_resize(void) {
density_resize(&pollock_density);
}
// The overlays cannot show a field they do not sample, so the fields go out as-is: the density
// dump is RGB with one channel per family, which is the only way to see that the split works.
// Entering pollock from goo: split goo's trail into three families rather than re-rolling at
// random. Veins become family 0, mid-field 1, voids 2 -- so pollock opens with structure inherited
// from what goo had built, and since the day-1 force law does not segregate, it persists.
// From any other sim (or a cold start) the random assignment made in buffers() stands.
static void pollock_enter_from(const SimDef *prev) {
if (prev != &SIM_GOO)
return;
glDisable(GL_BLEND); // this pass overwrites the family field outright
shader_use(&pollockFamilyShader);
shader_set_uniform_int(&pollockFamilyShader, "position_buffer", positionBuffer.current);
shader_set_uniform_int(&pollockFamilyShader, "trail_buffer", trailBuffer.current);
shader_set_uniform_vec(&pollockFamilyShader, "window_shape", 2, (float[]){(float)sim_width, (float)sim_height});
buffer_bind(&pollockFamilyBuffer, current);
buffer_update(&pollockFamilyBuffer);
glEnable(GL_BLEND);
pollock_family_sync(); // the point passes read the attribute, not the texture
}
static void pollock_update_shapes(const float sim_shape[2], const float render_shape[2]) {
shader_set_uniform_vec(&pollockScreenShader, "window_shape", 2, (float *)sim_shape);
shader_set_uniform_vec(&pollockVelocityShader, "window_shape", 2, (float *)sim_shape);
shader_set_uniform_vec(&pollockScreenShader, "render_shape", 2, (float *)render_shape);
}
static void *pollock_snapshot(size_t *size) {
size_t n = (size_t)PB_width * PB_height;
*size = sizeof(PollockBlob) + n * sizeof(float);
PollockBlob *b = (PollockBlob *)malloc(*size);
if (!b) {
fprintf(stderr, "goo: out of memory snapshotting boid families\n");
exit(EXIT_FAILURE);
}
b->pb_w = PB_width;
b->pb_h = PB_height;
b->ema = pollock_density.headroom.ema;
glBindFramebuffer(GL_READ_FRAMEBUFFER, pool_framebuffers.items[pollockFamilyBuffer.current]);
glReadPixels(0, 0, PB_width, PB_height, GL_RED, GL_FLOAT, (float *)(b + 1));
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
return b;
}
static void pollock_dump(const char *prefix) {
char path[1024];
snprintf(path, sizeof path, "%s_density.ppm", prefix);
dump_ppm_rgb_scaled(path, pollock_density.width, pollock_density.height,
pool_framebuffers.items[pollock_density.buffer.current]);
snprintf(path, sizeof path, "%s_family.ppm", prefix);
dump_ppm_scalar(path, PB_width, PB_height, pool_framebuffers.items[pollockFamilyBuffer.current]);
}
static dropt_option pollock_opts[] = {
{'\0', "pollock.dummy", "Placeholder, does nothing -- pollock has no tunables of its own right now.", NULL,
dropt_handle_bool, &pollock_dummy},
};
static const char *const pollock_pass_names[] = {"vel", "pos", "dens"};
const SimDef SIM_POLLOCK = {
.name = "pollock",
.shaders = pollock_shaders,
.buffers = pollock_buffers,
.static_uniforms = pollock_static_uniforms,
.integrate = pollock_integrate,
.fields = pollock_fields,
.draw = pollock_draw,
.resize = pollock_resize,
.update_shapes = pollock_update_shapes,
.dump = pollock_dump,
.density = &pollock_density,
.snapshot = pollock_snapshot,
.enter_from = pollock_enter_from,
.opts = pollock_opts,
.n_opts = sizeof pollock_opts / sizeof pollock_opts[0],
.pass_names = pollock_pass_names,
.n_passes = sizeof pollock_pass_names / sizeof pollock_pass_names[0],
};