feat: GPU optimization — lighting, viewport CA, benchmark mode, 531 FPS
- Resolution: 8x8 world cells, 2x2 UI cells (UI_SCALE=4) - GPU lighting: vertex-shader computed, light source list buffer (max 64) instead of O(N×R²) grid scan, O(N×S) per cell - Viewport-aware CA: iterate only active chunks, not all 250×250 - Flat array entity/item/shadow maps instead of HashMaps - Flat 128-entry ASCII atlas array instead of HashMap lookup - Partial grid upload: viewport + 30-cell margin only - Pre-allocated viewport arrays in renderer structs (zero alloc/frame) - Skip CPU lighting for GPU modes (pass None) - Benchmark mode: --mode benchmark with per-subsystem timing - GpuLightSource struct, light_count in push constants - gather_sources_in_range() for viewport-scoped source gathering Benchmark (600 ticks, release): Graphics: 531 FPS (was 386, +38%), render 1013us (was 1699us, -40%) ASCII: 402 FPS (was 313, +28%), render 1502us (was 2346us, -36%) All 171 tests + 14 scenarios pass.
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
layout(location = 0) in vec2 in_uv;
|
||||
layout(location = 1) in vec4 in_fg;
|
||||
layout(location = 2) in vec4 in_bg;
|
||||
layout(location = 3) in vec3 in_light;
|
||||
|
||||
layout(binding = 0) uniform sampler2D atlas;
|
||||
|
||||
@@ -10,5 +11,6 @@ layout(location = 0) out vec4 out_color;
|
||||
|
||||
void main() {
|
||||
float alpha = texture(atlas, in_uv).r;
|
||||
out_color = mix(in_bg, in_fg, alpha);
|
||||
vec4 color = mix(in_bg, in_fg, alpha);
|
||||
out_color = vec4(color.rgb * in_light, color.a);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
layout(push_constant) uniform PC {
|
||||
vec2 screen_size;
|
||||
vec2 cell_size;
|
||||
ivec2 world_size;
|
||||
ivec2 cam_pos;
|
||||
vec3 ambient;
|
||||
uint is_ui;
|
||||
uint light_count;
|
||||
} pc;
|
||||
|
||||
layout(location = 0) in vec2 in_pos;
|
||||
@@ -14,6 +19,59 @@ layout(location = 4) in vec4 in_bg;
|
||||
layout(location = 0) out vec2 out_uv;
|
||||
layout(location = 1) out vec4 out_fg;
|
||||
layout(location = 2) out vec4 out_bg;
|
||||
layout(location = 3) out vec3 out_light;
|
||||
|
||||
layout(std430, binding = 1) readonly buffer GridBuffer {
|
||||
uint cells[];
|
||||
} grid;
|
||||
|
||||
struct LightSrc {
|
||||
vec2 pos;
|
||||
float radius;
|
||||
float pad0;
|
||||
vec3 color;
|
||||
float pad1;
|
||||
};
|
||||
|
||||
layout(std430, binding = 2) readonly buffer LightBuffer {
|
||||
LightSrc sources[];
|
||||
} lights;
|
||||
|
||||
bool is_solid(uint m) {
|
||||
return m == 3u || m == 5u || m == 6u || m == 7u || m == 12u || m == 13u || m == 14u;
|
||||
}
|
||||
|
||||
bool line_of_sight(ivec2 a, ivec2 b) {
|
||||
ivec2 p = a;
|
||||
ivec2 d = abs(b - a);
|
||||
ivec2 s = ivec2(a.x < b.x ? 1 : -1, a.y < b.y ? 1 : -1);
|
||||
int err = d.x - d.y;
|
||||
while (true) {
|
||||
if (p == b) return true;
|
||||
if (p.x < 0 || p.x >= pc.world_size.x || p.y < 0 || p.y >= pc.world_size.y) return false;
|
||||
uint m = grid.cells[p.y * pc.world_size.x + p.x];
|
||||
if (is_solid(m)) return false;
|
||||
int e2 = 2 * err;
|
||||
if (e2 > -d.y) { err -= d.y; p.x += s.x; }
|
||||
if (e2 < d.x) { err += d.x; p.y += s.y; }
|
||||
}
|
||||
}
|
||||
|
||||
vec3 compute_light(ivec2 world_pos) {
|
||||
vec3 light = pc.ambient;
|
||||
for (uint i = 0u; i < pc.light_count; i++) {
|
||||
LightSrc src = lights.sources[i];
|
||||
ivec2 src_pos = ivec2(src.pos);
|
||||
float dist = length(vec2(world_pos - src_pos));
|
||||
float rad = src.radius;
|
||||
if (dist >= rad) continue;
|
||||
if (!line_of_sight(src_pos, world_pos)) continue;
|
||||
float t = 1.0 - dist / rad;
|
||||
float att = t * t;
|
||||
light += src.color * att;
|
||||
}
|
||||
return min(light, vec3(1.0));
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 pixel = (in_grid + in_pos) * pc.cell_size;
|
||||
@@ -25,4 +83,10 @@ void main() {
|
||||
out_uv = in_atlas.xy + in_pos * in_atlas.zw;
|
||||
out_fg = in_fg;
|
||||
out_bg = in_bg;
|
||||
if (pc.is_ui != 0u) {
|
||||
out_light = vec3(1.0);
|
||||
} else {
|
||||
ivec2 world_pos = ivec2(in_grid + vec2(pc.cam_pos));
|
||||
out_light = compute_light(world_pos);
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,9 +1,10 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec4 in_color;
|
||||
layout(location = 1) in vec3 in_light;
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
void main() {
|
||||
out_color = in_color;
|
||||
out_color = vec4(in_color.rgb * in_light, in_color.a);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
layout(push_constant) uniform PC {
|
||||
vec2 screen_size;
|
||||
vec2 cell_size;
|
||||
ivec2 world_size;
|
||||
ivec2 cam_pos;
|
||||
vec3 ambient;
|
||||
uint is_ui;
|
||||
uint light_count;
|
||||
} pc;
|
||||
|
||||
layout(location = 0) in vec2 in_pos;
|
||||
@@ -10,6 +15,59 @@ layout(location = 1) in vec2 in_grid;
|
||||
layout(location = 2) in vec4 in_color;
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
layout(location = 1) out vec3 out_light;
|
||||
|
||||
layout(std430, binding = 0) readonly buffer GridBuffer {
|
||||
uint cells[];
|
||||
} grid;
|
||||
|
||||
struct LightSrc {
|
||||
vec2 pos;
|
||||
float radius;
|
||||
float pad0;
|
||||
vec3 color;
|
||||
float pad1;
|
||||
};
|
||||
|
||||
layout(std430, binding = 1) readonly buffer LightBuffer {
|
||||
LightSrc sources[];
|
||||
} lights;
|
||||
|
||||
bool is_solid(uint m) {
|
||||
return m == 3u || m == 5u || m == 6u || m == 7u || m == 12u || m == 13u || m == 14u;
|
||||
}
|
||||
|
||||
bool line_of_sight(ivec2 a, ivec2 b) {
|
||||
ivec2 p = a;
|
||||
ivec2 d = abs(b - a);
|
||||
ivec2 s = ivec2(a.x < b.x ? 1 : -1, a.y < b.y ? 1 : -1);
|
||||
int err = d.x - d.y;
|
||||
while (true) {
|
||||
if (p == b) return true;
|
||||
if (p.x < 0 || p.x >= pc.world_size.x || p.y < 0 || p.y >= pc.world_size.y) return false;
|
||||
uint m = grid.cells[p.y * pc.world_size.x + p.x];
|
||||
if (is_solid(m)) return false;
|
||||
int e2 = 2 * err;
|
||||
if (e2 > -d.y) { err -= d.y; p.x += s.x; }
|
||||
if (e2 < d.x) { err += d.x; p.y += s.y; }
|
||||
}
|
||||
}
|
||||
|
||||
vec3 compute_light(ivec2 world_pos) {
|
||||
vec3 light = pc.ambient;
|
||||
for (uint i = 0u; i < pc.light_count; i++) {
|
||||
LightSrc src = lights.sources[i];
|
||||
ivec2 src_pos = ivec2(src.pos);
|
||||
float dist = length(vec2(world_pos - src_pos));
|
||||
float rad = src.radius;
|
||||
if (dist >= rad) continue;
|
||||
if (!line_of_sight(src_pos, world_pos)) continue;
|
||||
float t = 1.0 - dist / rad;
|
||||
float att = t * t;
|
||||
light += src.color * att;
|
||||
}
|
||||
return min(light, vec3(1.0));
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 pixel = (in_grid + in_pos) * pc.cell_size;
|
||||
@@ -19,4 +77,10 @@ void main() {
|
||||
0.0, 1.0
|
||||
);
|
||||
out_color = in_color;
|
||||
if (pc.is_ui != 0u) {
|
||||
out_light = vec3(1.0);
|
||||
} else {
|
||||
ivec2 world_pos = ivec2(in_grid + vec2(pc.cam_pos));
|
||||
out_light = compute_light(world_pos);
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user