==> src/doom.c <==1/* doom: a DDA raycaster (Lode Vandevenne's), one lane per screen column, 4
2 * tasks a frame (arg b: columns 32 b .. 32 b + 31). The pose lives in the
3 * view: the frame's arg-0 task drains the input queue (w s a d q e) into it,
4 * with wall collision, and publishes D_POSE_T = ticket + 1; the frame's other
5 * tasks (tickets t0 + k, consecutive within a publish) wait for it, bounded.
6 * The map comes from OP_INIT. Layout: @ajax/mesh-abi-view-doom. Split across
7 * machines (manifest `place = "split"`), every system but the owner renders
8 * its columns with OP_FOLLOW: meshd wrote the owner's pose for that frame into
9 * the view beforehand, so it neither waits nor reads input.
10 * v1's raycaster, kept as written: it mirrors the CPU reference (`h == h` is the NaN test).
11 * Ported from alpha-rust-mesh programs/doom/src/lib.rs. */
12#include "mesh/rt.h"
13
14static float doom_fabs(float x) { return mesh_f32_from_bits(mesh_f32_bits(x) & 0x7fffffffu); }
15/* f32 signum for non-NaN x (+0.0 gives 1.0, as the host Pose did). */
16static float doom_sign(float x) { return (mesh_f32_bits(x) >> 31) ? -1.0f : 1.0f; }
17
18static void doom_init(const mesh_ctx *ctx) {
19 for (uint32_t c = mesh_ctx_lane(ctx); c < 256; c += 32) mesh_ctx_set_u32(ctx, MESH_DOOM_D_MAP + 4 * c, mesh_doom_map_cell(c % 16, c / 16));
20 if (mesh_ctx_leader(ctx)) {
21 for (uint32_t i = 0; i < MESH_DOOM_START_LEN; i++) mesh_ctx_set_f32(ctx, MESH_DOOM_D_POS_X + 4 * i, MESH_DOOM_START[i]);
22 mesh_ctx_set_f32(ctx, MESH_DOOM_D_ROWS, MESH_DOOM_ROWS);
23 mesh_ctx_set_u32(ctx, MESH_DOOM_D_MAX_STEPS, MESH_DOOM_MAX_STEPS);
24 mesh_ctx_set_u32(ctx, MESH_DOOM_D_FRAME, 0);
25 mesh_ctx_set_u32(ctx, MESH_DOOM_D_POSE_T, 0);
26 mesh_ctx_set_u32(ctx, MESH_DOOM_D_KEYS, 0);
27 }
28}
29
30/* The pose: x y, dir x y, plane x y. */
31typedef struct {
32 float p[6];
33} doom_pose;
34
35static doom_pose doom_pose_read(mesh_view v) {
36 doom_pose o;
37 for (uint32_t i = 0; i < 6; i++) o.p[i] = mesh_view_f32(v, MESH_DOOM_D_POS_X + 4 * i);
38 return o;
39}
40static void doom_pose_write(const doom_pose *o, mesh_view v) {
41 for (uint32_t i = 0; i < 6; i++) mesh_view_set_f32(v, MESH_DOOM_D_POS_X + 4 * i, o->p[i]);
42}
43static bool doom_walkable(mesh_view v, float x, float y) {
44 return x >= 0.0f && y >= 0.0f && x < 16.0f && y < 16.0f &&
45 (mesh_view_u32(v, MESH_DOOM_D_MAP + 4 * (16 * (uint32_t)y + (uint32_t)x)) & 15u) == 0;
46}
47/* Moves by (dx, dy), each axis separately so the player slides along walls,
48 * keeping RADIUS from them. */
49static void doom_step(doom_pose *o, mesh_view v, float dx, float dy) {
50 if (doom_walkable(v, o->p[0] + dx + MESH_DOOM_RADIUS * doom_sign(dx), o->p[1])) o->p[0] += dx;
51 if (doom_walkable(v, o->p[0], o->p[1] + dy + MESH_DOOM_RADIUS * doom_sign(dy))) o->p[1] += dy;
52}
53static void doom_rotate(doom_pose *o, float s) {
54 float c = MESH_DOOM_TURN_COS;
55 float x = o->p[2], y = o->p[3];
56 o->p[2] = x * c - y * s;
57 o->p[3] = x * s + y * c;
58 x = o->p[4], y = o->p[5];
59 o->p[4] = x * c - y * s;
60 o->p[5] = x * s + y * c;
61}
62/* w/s forward/back, a/d turn left/right, q/e strafe (y points down the map). */
63static void doom_apply(doom_pose *o, mesh_view v, uint8_t key) {
64 float dx = o->p[2] * MESH_DOOM_MOVE, dy = o->p[3] * MESH_DOOM_MOVE;
65 float px = o->p[4], py = o->p[5];
66 switch (key) {
67 case 'w': doom_step(o, v, dx, dy); break;
68 case 's': doom_step(o, v, -dx, -dy); break;
69 case 'a': doom_rotate(o, -MESH_DOOM_TURN_SIN); break;
70 case 'd': doom_rotate(o, MESH_DOOM_TURN_SIN); break;
71 /* The camera plane points to the player's right. */
72 case 'q': doom_step(o, v, -px / MESH_DOOM_PLANE * MESH_DOOM_MOVE, -py / MESH_DOOM_PLANE * MESH_DOOM_MOVE); break;
73 case 'e': doom_step(o, v, px / MESH_DOOM_PLANE * MESH_DOOM_MOVE, py / MESH_DOOM_PLANE * MESH_DOOM_MOVE); break;
74 default: break;
75 }
76}
77
78/* One ray: the wall kind or 0, *side 0 x / 1 y, *dist the perpendicular distance or 1e30. */
79static inline __attribute__((always_inline)) uint32_t doom_cast(const uint32_t map[32], float px, float py, float dx, float dy,
80 float plx, float ply, uint32_t c, uint32_t max_steps,
81 uint32_t *side_out, float *dist_out) {
82 float cam = 2.0f * (float)c / (float)MESH_DOOM_WIDTH - 1.0f;
83 float rx = dx + plx * cam;
84 float ry = dy + ply * cam;
85 int32_t mx = (int32_t)px, my = (int32_t)py;
86 float ddx = rx == 0.0f ? 1e30f : doom_fabs(1.0f / rx);
87 float ddy = ry == 0.0f ? 1e30f : doom_fabs(1.0f / ry);
88 int32_t sx, sy;
89 float sdx, sdy;
90 if (rx < 0.0f) { sx = -1; sdx = (px - (float)mx) * ddx; }
91 else { sx = 1; sdx = ((float)mx + 1.0f - px) * ddx; }
92 if (ry < 0.0f) { sy = -1; sdy = (py - (float)my) * ddy; }
93 else { sy = 1; sdy = ((float)my + 1.0f - py) * ddy; }
94 uint32_t side = 0;
95 for (uint32_t i = 0; i < max_steps; i++) {
96 if (sdx < sdy) { sdx += ddx; mx += sx; side = 0; }
97 else { sdy += ddy; my += sy; side = 1; }
98 if (mx < 0 || mx > 15 || my < 0 || my > 15) break;
99 uint32_t cell = (uint32_t)(my * 16 + mx);
100 uint32_t k = (map[cell >> 3] >> (4 * (cell & 7))) & 15u;
101 if (k != 0) {
102 *side_out = side;
103 *dist_out = side == 0 ? sdx - ddx : sdy - ddy;
104 return k;
105 }
106 }
107 *side_out = side;
108 *dist_out = 1e30f;
109 return 0;
110}
111
112/* Renders columns 32 b .. 32 b + 31; returns how many hit a wall. */
113static uint32_t doom_render(const mesh_ctx *ctx, mesh_view v, const doom_pose *pose, uint32_t b) {
114 uint32_t l = mesh_ctx_lane(ctx);
115 /* The 16x16 map, 4 bits a cell: lane l packs cells 8l..8l+7, then every lane
116 * gathers all 32 words so the DDA loop never touches host memory. */
117 uint32_t packed = 0;
118 for (uint32_t j = 0; j < 8; j++) packed |= (mesh_view_u32(v, MESH_DOOM_D_MAP + 4 * (8 * l + j)) & 15u) << (4 * j);
119 uint32_t map[32];
120 for (uint32_t w = 0; w < 32; w++) map[w] = mesh_rt_shfl_idx(packed, w);
121
122 uint32_t c = b * 32 + l;
123 bool hit = false;
124 if (c < MESH_DOOM_WIDTH) {
125 float rows = mesh_view_f32(v, MESH_DOOM_D_ROWS);
126 uint32_t side;
127 float dist;
128 uint32_t kind = doom_cast(map, pose->p[0], pose->p[1], pose->p[2], pose->p[3], pose->p[4], pose->p[5], c,
129 mesh_view_u32(v, MESH_DOOM_D_MAX_STEPS), &side, &dist);
130 hit = kind != 0;
131 float height = 0.0f;
132 if (hit) {
133 float h = rows / dist;
134 height = (h > 4.0f * rows || !(h == h)) ? 4.0f * rows : h;
135 }
136 mesh_view_set_f32(v, MESH_DOOM_D_HEIGHT + 4 * c, height);
137 mesh_view_set_u32(v, MESH_DOOM_D_KIND + 4 * c, kind);
138 mesh_view_set_u32(v, MESH_DOOM_D_SIDE + 4 * c, side);
139 mesh_view_set_f32(v, MESH_DOOM_D_DIST + 4 * c, dist);
140 }
141 return (uint32_t)__builtin_popcount(mesh_rt_ballot(hit));
142}
143
144/* Arg 0: apply the queued keys (all lanes pop collectively and compute the
145 * same pose; at most one queue's worth a frame), then release the pose. */
146static doom_pose doom_begin_frame(const mesh_ctx *ctx, mesh_view v) {
147 doom_pose pose = doom_pose_read(v);
148 uint32_t n = 0;
149 while (n < MESH_DOOM_INPUT_BYTES) {
150 uint8_t k;
151 if (!mesh_ctx_input_pop(ctx, &k)) break;
152 doom_apply(&pose, v, k);
153 n++;
154 }
155 if (mesh_ctx_leader(ctx)) {
156 doom_pose_write(&pose, v);
157 mesh_view_set_u32(v, MESH_DOOM_D_FRAME, mesh_view_u32(v, MESH_DOOM_D_FRAME) + 1);
158 mesh_view_set_u32(v, MESH_DOOM_D_KEYS, mesh_view_u32(v, MESH_DOOM_D_KEYS) + n);
159 mesh_ctx_count(ctx, MESH_DOOM_C_FRAMES, 1);
160 if (n > 0) mesh_ctx_count(ctx, MESH_DOOM_C_KEYS, n);
161 mesh_rt_fence_acq_rel();
162 mesh_view w;
163 if (mesh_view_sub(v, MESH_DOOM_D_POSE_T, 4, &w)) mesh_rt_st_release(mesh_view_ptr(w), mesh_ctx_ticket(ctx) + 1);
164 }
165 mesh_rt_warp_sync();
166 return pose;
167}
168
169/* Args k > 0: wait until the arg-0 task of this publish (ticket t - k) has
170 * released the pose; a stray or lost arg-0 costs at most POSE_WAIT_NS and
171 * bumps C_STALLS. */
172static doom_pose doom_wait_pose(const mesh_ctx *ctx, mesh_view v, uint32_t k) {
173 if (mesh_ctx_leader(ctx)) {
174 mesh_view w;
175 if (mesh_view_sub(v, MESH_DOOM_D_POSE_T, 4, &w)) {
176 uint32_t want = mesh_ctx_ticket(ctx) + 1;
177 uint64_t t0 = mesh_rt_gtimer();
178 /* The arg-0 ticket lies in [t - k, t]: want - pose_t in [0, k]. */
179 while (want - mesh_rt_ld_acquire(mesh_view_ptr(w)) > k) {
180 if (mesh_rt_gtimer() - t0 > MESH_DOOM_POSE_WAIT_NS) {
181 mesh_ctx_count(ctx, MESH_DOOM_C_STALLS, 1);
182 break;
183 }
184 mesh_rt_nanosleep(200);
185 }
186 }
187 }
188 /* Lane 0's acquire, then the warp barrier, orders every lane's pose reads. */
189 mesh_rt_warp_sync();
190 return doom_pose_read(v);
191}
192
193static uint32_t doom_task(const mesh_ctx *ctx, uint32_t op, uint32_t arg) {
194 mesh_view v = mesh_ctx_view(ctx);
195 if (op == MESH_OP_FOLLOW && arg < MESH_DOOM_WIDTH / 32) {
196 if (mesh_ctx_leader(ctx)) mesh_ctx_count(ctx, MESH_DOOM_C_FOLLOWS, 1);
197 doom_pose pose = doom_pose_read(v);
198 return doom_render(ctx, v, &pose, arg);
199 }
200 if (op != MESH_OP_TASK) return MESH_R_BAD_OP;
201 doom_pose pose = arg == 0 ? doom_begin_frame(ctx, v) : doom_wait_pose(ctx, v, arg);
202 return doom_render(ctx, v, &pose, arg);
203}
204
205MESH_PROGRAM(doom_init, doom_task);