-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsim.h
More file actions
278 lines (251 loc) · 13.2 KB
/
Copy pathsim.h
File metadata and controls
278 lines (251 loc) · 13.2 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
#ifndef SIM_H
#define SIM_H
// Window-agnostic simulation core, shared by the four front-ends:
// - main.c -> bin/goo (RGFW window, X11/cocoa/win)
// - main-wlwp.c -> bin/goo-wlwp (wayland layer-shell wallpaper)
// - main-x11wp.c -> bin/goo-x11wp (x11 root/desktop wallpaper)
// - main-macwp.m -> bin/goo-macwp (macos desktop wallpaper)
// Everything here is pure GL + cli config; no windowing-toolkit symbols. Each
// front-end owns its own context creation, event pump, and buffer-swap, then
// drives the sim through the small API at the bottom of this file.
#include "buffer.h"
#include "shader.h"
#include <stdbool.h>
#include <stdio.h>
// ---- render / sim geometry (set by the front-end via sim_set_dims) ----
extern int width; // internal render resolution (logical / render_scale)
extern int height; //
extern int window_width; // actual framebuffer size in device px (upscale target)
extern int window_height; //
// upscale source-crop: sim_present samples the render target over this uv sub-rect
// instead of the full [0,0]..[1,1]. identity by default -> straight upscale; macwp
// -m=-2 sets a centred crop per monitor so a shared field presents 1:1 (no stretch).
extern float present_uv_min[2];
extern float present_uv_max[2];
extern double render_scale;
extern const double SIM_SCALE;
extern int sim_width, sim_height; // simulation field size (logical / SIM_SCALE)
extern float mouse_scale; // logical-point -> sim-space
extern const char *title;
// ---- runtime flags / params (filled by parse_args) ----
extern bool fullscreen;
extern bool vsync;
extern bool profile;
extern int max_iterations;
extern int warmup;
extern int fps_cap;
extern bool no_keyfocus_steal;
extern bool no_border;
extern bool no_mouse;
extern bool mouse_debug;
extern bool corners_debug;
extern bool exclusions_debug;
extern bool edge_debug;
extern char *dump_path;
extern char *headless_path;
extern FILE *ffmpeg_pipe;
extern unsigned char *headless_px;
extern int rng_seed;
extern double init_warp;
extern double init_density;
extern bool p_given;
extern int whichMonitor;
// ---- gl objects / buffers ----
// One texture + framebuffer pool shared by every sim's Buffers. It grows as each sim claims
// its slots at setup, so the slot indices are assigned at runtime rather than baked in. Buffers
// point at the pool structs, never into their items -- see the note in buffer.h.
extern GLuintArray pool_textures;
extern GLuintArray pool_framebuffers;
// Reserve n consecutive slots (generating the GL names) and return the first index. A slot index
// doubles as the texture unit the buffer is bound to, so the pool is capped at the GL limit.
PBindex pool_claim(int n);
// Delete every pooled GL object and free the pools. Replaces the front-ends each deleting a
// hardcoded count -- they no longer know how many buffers a sim allocated.
void sim_teardown_gl(void);
extern PBindex positionBufferIndex1;
extern PBindex positionBufferIndex2;
extern PBindex velocityBufferIndex1;
extern PBindex velocityBufferIndex2;
extern PBindex trailBufferIndex1;
extern PBindex trailBufferIndex2;
extern PBindex renderBufferIndex;
extern PBindex repelBufferIndex;
extern int PB_width, PB_height;
extern Shader screenShader, densityShader, positionShader, velocityShader;
extern Shader copyShader, trailShader, upscaleShader, debugShader, repelShader;
extern Buffer trailBuffer, positionBuffer, velocityBuffer;
extern Buffer screenBuffer, renderBuffer, repelBuffer;
// ---- tunables (default-params is the source of truth; see nob.c) ----
extern double densityAlpha, kernelRadius;
extern double densityForce, trailForce, repel, reach, trailReach, antiStick, dragCoefficient;
extern double ditherCoefficient, ditherDensityGain, ditherOrtho;
extern double mouseDrag;
extern int switchFrames;
extern double switchEvery;
extern int densitySubsample, trailSubsample, dens_every, trail_every;
extern double cullAmount;
extern double renderHeadroom, headroomMargin, headroomAttack, headroomRelease, renderGamma, alphaSpeed;
extern bool densityNearest;
extern double headroomPct;
extern int densityBufferDownsampling;
extern int repelBufferDownsampling, repel_width, repel_height;
extern bool legacyWedge;
extern double trailIntensity;
extern double trailTau;
extern double trailRadius;
extern int trailBufferDownsampling;
extern double trailVelocityFloor;
extern int trail_width, trail_height;
extern int P;
// ---- exclusion primitives (bounding rect minus N shapes) ----
// --core.exclusions "rect(x,y,w,h);rect(x,y,w,h);...": primitive calls in logical px, relative to
// the sim's own logical origin. Only "rect" is implemented today. Exposed on every front-end
// (see is_window_only). Default count 0 = today's full-rect behaviour, unchanged.
#define MAX_EXCLUSION_RECTS 16
extern float exclusionRects[MAX_EXCLUSION_RECTS][4]; // x,y,w,h per rect, logical px
extern int exclusionRectCount;
// --core.gl-refresh <dur>: how often (seconds) to recreate the GL context to reclaim
// the freedreno per-submit leak (see debug/). default 30m; 0 = never. wallpaper only.
extern double gl_refresh_seconds;
// tile-coherent draw order
extern GLuint ibo;
extern unsigned int *sort_idx;
extern int *sort_key;
extern int *sort_counts;
extern const int sort_tile;
extern int sort_every;
// ---- auto-exposure follower ----
// The colormap normalises by `ema`, an envelope follower tracking a high percentile of the live
// density field. The machinery -- async readback ring, percentile, asymmetric EMA -- is shared,
// but the STATE is per-sim: every sim's field has its own shape and its own exposure, and with
// more than one sim resident they would otherwise fight over one ring.
#define HEADROOM_PBO_N 3
typedef struct HeadroomState {
GLuint pbo[HEADROOM_PBO_N];
int ring; // next slot to write
int primed; // async reads issued so far (< HEADROOM_PBO_N = still priming)
int w, h; // field dims this ring is sized for
int channels;
float *readback;
float ema;
} HeadroomState;
// ---- density field: one core-owned service, instantiated per sim ----
// Every parameter describing the field is a --core.dens-* flag, so both sims build it the same way
// and read it on the same scale; a sim only chooses how many channels it wants. Goo takes 1, pollock
// 3 (one per family). The texture, the splat pass and the exposure follower all live here -- the
// follower is per-unit rather than per-channel on purpose, since normalising each channel separately
// would brighten a family as it faded and erase relative abundance.
typedef struct Density {
Buffer buffer;
HeadroomState headroom;
int channels;
int width, height;
// Per-particle channel index, as a vertex attribute. Left 0 by a single-channel sim, in which
// case the attribute array is disabled and every particle deposits into channel 0.
GLuint channel_vbo;
} Density;
// Claim a pool slot and allocate at the current render resolution. Call from a sim's buffers(),
// i.e. with a fresh GL context; channels is 1..3.
void density_create(Density *d, const char *name, int channels);
void density_resize(Density *d);
// The amortised splat: gated by --core.dens-every, additive, followed by the exposure update.
// Draws P * channels / --core.dens-sub points, so each channel receives the same number of
// particles a single-channel field would -- see the note on the flag.
void density_splat(Density *d);
// (Re)create and size the ring. Call from a sim's buffers() (fresh GL context) and its resize().
void headroom_reset(HeadroomState *hs, int w, int h, int channels);
// Fold the field's current high percentile into hs->ema. Multi-channel fields reduce by max
// across channels first: the channel nearest clipping should drive the exposure, and one shared
// value per sim keeps relative abundance between its channels legible rather than auto-levelling
// each to full range. No-op when --core.headroom pins the value.
void headroom_update(HeadroomState *hs, const Buffer *density);
// Forget the ring's GL names (they died with the context); called from buffer_setup.
void headroom_forget(HeadroomState *hs);
// Per-pass GPU timing (--window.profile). Core's own passes take the first slots and a sim's
// follow, so the set differs per sim and the labels are assembled at setup rather than fixed.
// Only the first pass_count entries are meaningful.
#define MAX_PASSES 16
extern double pass_ms[MAX_PASSES];
extern const char *pass_name[MAX_PASSES];
extern int pass_count;
// First slot a sim's own passes occupy; its LAP indices are offset by this.
extern const int sim_pass_base;
// ---- utilities (front-ends use these for pacing / debug dumps) ----
double get_time(void);
void sleep_ms(double ms);
double ema(double prev, double sample, double a);
void dump_ppm_rgb(const char *path, int w, int h, GLuint fbo);
// As dump_ppm_rgb, but normalises by the field's own joint max first and reports it. For the
// additive, unbounded density fields: a straight byte dump clips everything past 1.0 to white
// and hides both the structure and how far past it went.
void dump_ppm_rgb_scaled(const char *path, int w, int h, GLuint fbo);
void dump_ppm_scalar(const char *path, int w, int h, GLuint fbo);
// ---- core API ----
// Parse cli + config into the globals. Flags are namespaced by owner -- --core.* is the
// shared sim, --window.* is windowing/output, and each sim contributes its own --<name>.*
// from its SimDef. Every registered sim's flags are merged, not just the active one's.
// wlwp = wallpaper build: drops the window.* flags from both the option table and the
// baked-in defaults, so goo-wlwp only exposes what concerns a wallpaper. macwp additionally
// exposes -m (monitor; -1 = clone+stretch to all, -2 = clone+centre-crop, N = that monitor).
void parse_args(int argc, char **argv, bool wlwp, bool macwp);
void shader_setup(void);
void buffer_setup(void);
void updateShaderWindowShape(int width, int height);
void updateShaderBufferShape(int width, int height);
// set the geometry globals from the front-end's measured sizes (no GL side effects).
void sim_set_dims(int logical_w, int logical_h, int fb_w, int fb_h);
// derive P (unless -p given), compile shaders, allocate buffers, push static uniforms.
// call after the GL context is current and sim_set_dims has run.
void sim_setup(void);
// recompute geometry + reallocate the resolution-dependent buffers (resize path).
void sim_resize(int logical_w, int logical_h, int fb_w, int fb_h);
// one physics+render frame -> renderBuffer (render resolution). mouse_* in sim space;
// pass a parked sentinel ({-1e9,-1e9}) and zero velocity to disable the repel.
void sim_step(int epoch_counter, const float mouse_position[2], const float mouse_velocity[2]);
// ---- live sim switching ----
// Request a move to the next registered sim. Safe from a signal handler (it only sets a flag);
// the actual transition starts on the next sim_step. Dropped if one is already in flight.
void sim_request_switch(void);
// Install the SIGUSR1 handler that calls the above -- the only switch trigger the wallpaper
// front-ends have, since they deliberately never take keyboard focus.
void sim_install_signal_handlers(void);
// True while a transition is running. --core.gl-refresh must wait for it: the transition's state
// (which pair, how far through, the duty accumulator) lives in no buffer and would not survive a
// context recreate.
bool sim_transitioning(void);
// nearest-upscale renderBuffer onto the default framebuffer (the window). caller
// must have screenBuffer.width/height set to the current framebuffer size.
void sim_present(void);
// ---- GL-context refresh (freedreno leak workaround; see debug/) ----
// The whole sim state lives in GL objects, so recreating the EGL context to
// reclaim the driver leak means saving + restoring that state around it. Also the
// groundwork for a future dump-to-file / load-from-file of the sim.
#define MAX_SIMS 8
// One sim's own state, opaque to core: the sim mallocs and fills it, and reads it back in its
// buffers(). Core only carries and frees it, so a sim can preserve whatever it likes -- goo its
// trail, pollock its family assignment -- without core knowing the layout.
typedef struct SimBlob {
void *data;
size_t size;
} SimBlob;
typedef struct SimSnapshot {
int pb_w, pb_h; // physics buffer dims (holds P particles)
float *pos; // pb_w*pb_h*2 (RG)
float *vel; // pb_w*pb_h*2 (RG)
// Every registered sim's blob, in registry order -- all of them are resident, so all of
// their state has to survive, not just the active one's.
SimBlob sims[MAX_SIMS];
int active; // registry index of the sim that was running
} SimSnapshot;
// read the live particle + trail buffers back to CPU. call with the GL context
// still current (i.e. before tearing it down).
SimSnapshot *sim_snapshot(void);
// hand a snapshot to the NEXT sim_setup, which uploads it instead of generating
// fresh initial state. consumed (cleared) by that sim_setup.
void sim_restore(const SimSnapshot *s);
void sim_snapshot_free(SimSnapshot *s);
// free the CPU-side scratch (the tile-sort arrays, every sim's exposure readback) so a re-run of
// sim_setup in a fresh context doesn't leak the old allocations. the GL objects
// themselves are freed by the context teardown, so this only touches CPU memory.
void sim_teardown_cpu(void);
#endif /* SIM_H */