#version 450 #extension GL_GOOGLE_include_directive : require layout(local_size_x = 64) in; #include "fx_common.glsl" // Explosion sprites and flashes: advance, draw the animated sprite (centred) and add a fading light. // One workgroup per animation record (the dispatch is FANIM_ALL groups): its 64 threads share the sprite's pixels. (One thread per record drew a big // explosion sprite alone - 100 000 pixels and more - and three bombs took 15 ms of GPU time.) void main() { const uint id = gl_WorkGroupID.x, lane = gl_LocalInvocationID.x; const bool drawOnly = pc.pass_ == 1; // between ticks nothing advances if (gl_GlobalInvocationID.x == 0u && !drawOnly) lpad[0] = lpad[0] * 5u / 6u; // camera shake decays if (id >= uint(FANIM_ALL)) return; FAnim a = fanims[id]; barrier(); // (every lane has read the record before lane 0 advances it) if (a.b.z <= 0) return; int age = drawOnly ? a.b.y : a.b.y + 1; if (age >= a.b.z) { if (!drawOnly && lane == 0u) fanims[id].b.z = 0; return; } float k = 1.0 - float(age) / float(a.b.z); if (!drawOnly && lane == 0u) { fanims[id].b.y = age; const float lk = a.c.y > 0.0 ? max(0.0, 1.0 - float(age) / a.c.y) : k; // (c.y: the light's own fade frames) if (a.a.w > 0.0 && lk > 0.0) add_light(a.a.xy, a.a.z, a.a.w * lk, rgb_of(uint(a.b.w))); } if (a.b.x < 0) return; int b = BOOM_BASE + a.b.x * BOOM_STRIDE; ivec4 b0 = fti(b), b1 = fti(b + 1); vec4 b3 = ftf(b + 3); if (b0.y <= 0 || b0.z <= 0) return; int frame = age / max(b1.x, 1); if (frame >= b0.w) return; // the sprite is placed with its pivot at the effect position and rotated by the angle (muzzle flashes point along the shot) // c.z: a scale (0: 1; negative: drawn behind - only where no cell and no sprite is, the render_back sprite particles of the host, main.cpp); c.w: a tint (rgb) and alpha // (bits 24-31) as bits (0: none); a host record (tinted) has its own y scale in c.y (the GPU's records keep their light's fade there) const uint tw = floatBitsToUint(a.c.w); const bool tinted = tw != 0u; const float sx0 = a.c.z == 0.0 ? 1.0 : abs(a.c.z); const vec2 sclv = max(vec2(sx0, tinted ? a.c.y : sx0), vec2(0.01)); const float scl = max(sclv.x, sclv.y); const bool behind = a.c.z < 0.0; const vec3 tint = vec3(float((tw >> 16) & 255u), float((tw >> 8) & 255u), float(tw & 255u)) / 255.0; const float alpha = tinted ? float(tw >> 24) / 255.0 : 1.0; vec2 pivot = b3.zw, size = vec2(b0.yz); float ca = cos(a.c.x), sa = sin(a.c.x); float r = (length(max(pivot, size - pivot)) + 1.5) * scl; int n = int(2.0 * r) + 2; ivec2 lo = ivec2(floor(a.a.xy - vec2(r))); bool additive = (b1.w & 8) != 0; for (int j = int(lane); j < n * n; j += 64) { ivec2 w = lo + ivec2(j % n, j / n); vec2 d = vec2(w) + 0.5 - a.a.xy; ivec2 sp = ivec2(floor(pivot + vec2(ca * d.x + sa * d.y, -sa * d.x + ca * d.y) / sclv)); if (sp.x < 0 || sp.y < 0 || sp.x >= b0.y || sp.y >= b0.z) continue; ivec2 q = w - ivec2(pc.view_x, pc.view_y); if (q.x < 0 || q.y < 0 || q.x >= pc.gw || q.y >= pc.gh) continue; uint t = fxtex[b0.x + frame * b0.y * b0.z + sp.y * b0.y + sp.x]; if ((t >> 24) == 0u) continue; const int qi = q.y * pc.gw + q.x; if (behind && (cell_mat(cells[qi]) != 0u || entlayer[qi] != 0u)) continue; // (behind the terrain and the sprites already drawn) if (tinted) { const vec3 c = vec3(float((t >> 16) & 255u), float((t >> 8) & 255u), float(t & 255u)) / 255.0 * tint; const float al = float(t >> 24) / 255.0 * alpha; if (al < 0.02) continue; if (additive) t = 0x01000000u | packUnorm4x8(vec4(c.b * al, c.g * al, c.r * al, 0.0)); else t = (uint(al * 255.0) << 24) | (packUnorm4x8(vec4(c.b, c.g, c.r, 0.0)) & 0xFFFFFFu); entlayer[qi] = t; } else entlayer[qi] = additive ? (0x01000000u | (t & 0xFFFFFFu)) : t; } }