#version 450 #extension GL_GOOGLE_include_directive : require layout(local_size_x = 8, local_size_y = 8) in; #include "common.glsl" // Fog of war, as FogOfWarRadiusSystem (lambda 00d8f000 -> 00aeef70): a persistent map of the world in 32 x 32 pixel texels (the engine's fog byte, 255 = fog, 0 = clear; // kept here as its complement, the "clear" amount, so that the zeroed buffer is all fog). It only clears: what was seen stays seen. Every frame, for the player // (FogOfWarRadiusComponent radius 200, data/entities/misc/fogofwar_radius.xml; the position lifted by 4 x uniform(0, 3) pixels): // * the 3 x 3 texels around it: fog = min(fog, max(fog - 3 d, 255 d)), d = the distance from the texel centre in texels; // * 36 rays (10 degrees apart) of the radius, stopped by the terrain: at 0, 1/4, 2/4 and 3/4 of the free part of each ray the texel loses 5 of fog // (a texel crossed by several rays loses more; 51 frames clear one entirely). // render.comp reads the map bilinearly (the texel centres at 32 n + 16) as post_final.frag reads tex_fog. struct Ent { vec4 a, box, mv, ai; ivec4 st; vec4 hp; ivec4 x; ivec4 rg; ivec4 rg2; ivec4 aux; vec4 ex; vec4 ex2; ivec4 ex3; vec4 ex4; vec4 ex5; vec4 ex6; vec4 ex7; ivec4 ex8; vec4 ex9; vec4 ex10; vec4 ex11; vec4 ex12, ex13, ex14, ex15, ex16, ex17, ex18, ex19; }; layout(std430, set = 0, binding = 22) readonly buffer Ents { Ent ents[]; }; layout(std430, set = 0, binding = 49) buffer Fog { uint fog[]; }; // per texel: 255 - the fog byte (the clear amount) layout(std430, set = 0, binding = 8) readonly buffer Tables { uint tbl[]; }; // after the fire palettes: the biome map's width, height and fog_of_war_type per tile (main.cpp) #include "biome_mods.glsl" uint fog_type_at(vec2 w) { // 0 default, 1 HEAVY_CLEAR_AT_PLAYER, 2 HEAVY_CLEAR_WITH_MAGIC, 3 HEAVY_NO_CLEAR; outside the map: the default const uint n = mats[mats.length() - 1u].w; const uint fb = 1024u + 9u * n + 132u + n; const int bw = int(tbl[fb]), bh = int(tbl[fb + 1u]); const ivec2 t = ivec2(floor(w / 512.0)) + ivec2(35, 14); if (bw <= 0 || t.x < 0 || t.y < 0 || t.x >= bw || t.y >= bh) return 0u; return tbl[fb + 2u + uint(t.y * bw + t.x)]; } const int FOG_TW = 1152, FOG_TH = 896, FOG_OX = 18432, FOG_OY = 14336; // texels of 32 pixels from the world origin (-18432, -14336) const float RADIUS = 256.0; // the player's (component_documentation: "256 is the default player has"; the X-ray card's fogofwar_radius.xml has 200) bool solid_at(ivec2 w) { // (world cell; outside the window: open) const ivec2 p = w - ivec2(pc.view_x, pc.view_y); if (p.x < 0 || p.y < 0 || p.x >= pc.gw || p.y >= pc.gh) return false; const uint m = cell_mat(cells[p.y * pc.gw + p.x]); return m != 0u && (mflags(m) & (F_STATIC | F_SAND)) != 0u; } int texel_index(vec2 w) { const ivec2 t = ivec2(floor((w + vec2(FOG_OX, FOG_OY)) / 32.0)); if (t.x < 0 || t.y < 0 || t.x >= FOG_TW || t.y >= FOG_TH) return -1; return t.y * FOG_TW + t.x; } // the solid share of the 64 x 64 pixel region of a point, 0..255 (the coarse map of the world the 3 x 3 rule reads; outside the window: 0) float coarse_at(vec2 w) { const ivec2 r0 = ivec2(floor(w / 64.0)) * 64; int n = 0, tot = 0; for (int y = 2; y < 64; y += 4) for (int x = 2; x < 64; x += 4) { const ivec2 p = r0 + ivec2(x, y) - ivec2(pc.view_x, pc.view_y); if (p.x < 0 || p.y < 0 || p.x >= pc.gw || p.y >= pc.gh) continue; ++tot; const uint m = cell_mat(cells[p.y * pc.gw + p.x]); if (m != 0u && (mflags(m) & (F_STATIC | F_SAND)) != 0u) ++n; } return tot > 0 ? 255.0 * float(n) / float(tot) : 0.0; } void main() { // one workgroup per source: 0 the player (radius 256), then the other FogOfWarRadiusComponents (main.cpp, after the holes in the tables) const uint t = gl_LocalInvocationIndex; if (pc.pass_ != 0) return; if (t >= 45u) { // 00c42d30: the biome's fog_of_war_delta (FOG_OF_WAR_REAPPEARS: 2) - every frame the fog bytes of the 17 x 17 texels around the player (ceil(427 / 32 x 0.5) + 1 = 8 each way) gain it if (gl_WorkGroupID.x != 0u || (ents[0].st.w & 8) != 0) return; const vec2 P0 = ents[0].a.xy; const int delta = int(bitfieldExtract(int(bm_bytes(P0)), 0, 8)); // (a signed byte) if (delta == 0) return; for (uint k = t - 45u; k < 289u; k += 19u) { const int ti = texel_index(P0 + vec2(float(int(k % 17u) - 8), float(int(k / 17u) - 8)) * 32.0); if (ti < 0) continue; uint old = fog[ti]; for (int tries = 0; tries < 4; ++tries) { const uint nv = uint(clamp(int(min(old, 255u)) - delta, 0, 255)); const uint got = atomicCompSwap(fog[ti], old, nv); if (got == old) break; old = got; } // (fog[] holds 255 - the fog byte) } return; } const uint wg = gl_WorkGroupID.x; vec2 P; float R = RADIUS; if (wg == 0u) { if ((ents[0].st.w & 8) != 0) return; // (dead) P = ents[0].a.xy; } else { const uint n = mats[mats.length() - 1u].w; const uint sb = 1024u + 9u * n + 132u + n + 2u + 8192u + 1u + 64u * 6u; if (int(wg) > int(uintBitsToFloat(tbl[sb]))) return; const uint o = sb + 1u + (wg - 1u) * 3u; P = vec2(uintBitsToFloat(tbl[o]), uintBitsToFloat(tbl[o + 1u])); R = uintBitsToFloat(tbl[o + 2u]); } P -= vec2(0.0, 4.0 * float(hash3(uint(pc.frame) + wg * 131u, 61u, 7u) & 3u)); if (t < 36u) { // a ray const float a = float(t) * 0.17453294; const vec2 dir = vec2(cos(a), sin(a)); float len = R; for (int s = 1; s < int(R); ++s) if (solid_at(ivec2(floor(P + dir * float(s))))) { len = float(s); break; } for (int k = 0; k < 4; ++k) { const vec2 sw = P + dir * (len * 0.25 * float(k)); if (fog_type_at(sw) != 0u) continue; // only the default fog of war clears along the rays const int i = texel_index(sw); if (i >= 0 && fog[i] < 255u) atomicAdd(fog[i], 5u); } return; } // the 3 x 3 texels around the player const int k = int(t) - 36; const vec2 tc = (floor((P + vec2(FOG_OX, FOG_OY)) / 32.0) + vec2(float(k % 3 - 1), float(k / 3 - 1))) * 32.0 + 16.0 - vec2(FOG_OX, FOG_OY); const int i = texel_index(tc); if (i < 0 || fog_type_at(tc) > 1u) return; // the default and HEAVY_CLEAR_AT_PLAYER clear around the player const float d = distance(P, tc) / 32.0; const float old = 255.0 - float(min(fog[i], 255u)); float target = 255.0 * d; if (k % 3 != 1 && k / 3 != 1) target = max(target, (coarse_at(tc) * 16.0 / 1024.0 - d) * 255.0); // the four diagonal texels: a solid region stays fogged (the coarse byte x 16 / 1024) const float nf = min(old, max(old - 3.0 * d, clamp(target, 0.0, 255.0))); atomicMax(fog[i], uint(255.0 - nf)); }