feat(cpp/gfx): GPU compute primitives for Monte Carlo (G1-G7)

Stack base de compute shaders OpenGL 4.3 para cargas Monte Carlo intensivas
en GPU. Reutiliza el patron de graph_force_layout_gpu (SSBO + compute) y se
integra con el resto del registry sin nuevos simbolos en gl_loader (todo lo
que se necesita ya estaba expuesto).

- gpu_ssbo: lifecycle de Shader Storage Buffer Objects.
- gpu_compute_program: compila compute GLSL 4.3 con preamble inyectable
  (mismo pattern de gl_shader::compile_fragment).
- gpu_dispatch: dispatch_1d/2d/3d con ceil(N/local) automatico + barrier
  helpers (storage, uniform, image, buffer_update, all).
- gpu_rng_glsl: PCG32 GLSL (uniform/normal/below) + SplitMix64 seed walkers
  para sembrar deterministicamente N walkers desde un master seed.
- gpu_histogram_1d: SSBO float[N] -> uint[nbins] via atomicAdd.
- gpu_histogram_2d: SSBO float[2N] xy-interleaved -> uint[nx*ny] +
  to_density helper para alimentar heatmap_cpp_viz.
- gpu_reduce: workgroup-shared sum/min/max/mean (local 256, partials CPU).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-04 11:52:08 +02:00
parent b04bb846c7
commit 07d06d5e7d
21 changed files with 1544 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
---
name: gpu_rng_glsl
kind: function
lang: cpp
domain: gfx
version: "1.0.0"
purity: pure
signature: "std::string glsl_rng_preamble(int seed_binding); void seed_walkers_init(uint64_t master_seed, uint32_t* out, int count)"
description: "Generador de preamble GLSL con primitivas PCG32 (uniform, normal, below) inyectables en compute shaders, mas helper CPU para sembrar N seeds deterministas via SplitMix64. Sin GL ni I/O — funciones puras que producen string y rellenan array."
tags: [glsl, rng, pcg32, splitmix64, montecarlo, compute, gpu, gfx]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: [string, cstdint, cstdio]
tested: false
tests: []
test_file_path: ""
file_path: "cpp/functions/gfx/gpu_rng_glsl.cpp"
framework: opengl
params:
- name: seed_binding
desc: "Indice del binding std430 donde reside el SSBO uint rng_seeds[]. Tipicamente reservar el ultimo binding del shader (8 o 9)."
- name: master_seed
desc: "Semilla maestra de la que se derivan todas las semillas individuales. 0 se sustituye por la constante de Knuth para evitar arranques degenerados."
- name: out
desc: "Buffer destino de count uint32. Garantiza out[i] != 0 (requisito de PCG32)."
- name: count
desc: "Numero de semillas a generar. Igual al numero de threads/walkers que ejecutaran el compute."
output: "glsl_rng_preamble: string GLSL listo para concatenar como preamble de compile_compute. seed_walkers_init: rellena out[count] in-place. Ambas son puras."
---
# gpu_rng_glsl
Pareja CPU + GLSL para Monte Carlo en compute shaders.
## Calidad
PCG32 (variante recomendada por O'Neill, 2014) tiene state de 32 bits, periodo 2^32 por chain y supera los tests de PractRand hasta varios TB. Para Monte Carlo no criptografico es la opcion estandar moderna. Cada thread tiene su propio state independiente (sin contention atomico), asi que el periodo agregado de N chains es N · 2^32 — mas que suficiente para 10^10 samples.
`rng_normal` usa Box-Muller con cos (descarta la sin) — ~30 ciclos GPU por sample, un orden de magnitud mas barato que el Ziggurat y sin tablas de lookup que machacarian el cache.
## API GLSL inyectada por `glsl_rng_preamble`
```glsl
layout(std430, binding = <seed_binding>) buffer RngSeeds { uint rng_seeds[]; };
uint pcg32 (inout uint state);
float rng_uniform (inout uint state); // [0, 1)
float rng_normal (inout uint state); // N(0, 1)
uint rng_below (inout uint state, uint n); // [0, n) sin sesgo
```
## Patron de uso
CPU:
```cpp
std::vector<unsigned int> seeds(N);
fn::gfx::seed_walkers_init(0xC0FFEE, seeds.data(), N);
fn::gfx::Ssbo seed_ssbo = fn::gfx::ssbo_create(
N * sizeof(unsigned int), seeds.data(), GL_DYNAMIC_COPY);
auto rng_src = fn::gfx::glsl_rng_preamble(/*seed_binding=*/9);
auto r = fn::gfx::compile_compute(my_body, 64, rng_src);
```
GLSL (`my_body`):
```glsl
layout(std430, binding = 0) buffer Out { float samples[]; };
uniform uint u_count;
void main() {
uint i = gl_GlobalInvocationID.x;
if (i >= u_count) return;
uint s = rng_seeds[i];
samples[i] = rng_normal(s);
rng_seeds[i] = s; // persistir para el siguiente dispatch
}
```
Despacho:
```cpp
glUseProgram(r.program);
fn::gfx::ssbo_bind(out_ssbo, 0);
fn::gfx::ssbo_bind(seed_ssbo, 9);
glUniform1ui(loc_count, N);
fn::gfx::dispatch_1d(N, 64);
fn::gfx::barrier_storage();
```
## Notas
- El binding del SSBO de seeds es parametro porque cada shader puede usar bindings distintos para sus datos. Convencion sugerida: el ultimo binding del shader (8 o 9).
- Para reproducibilidad bit-exacta entre runs, mantener `master_seed` y N constantes; los samples seran identicos. Util para tests numericos.
- `seed_walkers_init` es deterministica y no depende de GL — se puede invocar en tests unitarios CPU sin contexto OpenGL.
- Para muestreo categorico (sampleTier de vr_tiered_lab) usar `rng_uniform` con cumulative-sum manual en el shader; un helper dedicado se puede añadir si aparece en multiples kernels.