Files
deepgrain-foundry/src/render/gpu/textures.js
T

105 lines
7.1 KiB
JavaScript

// Whole-world allocation, direct strided uploads; no cell packing or dirty tracking.
function gpuTexture(w, h, format = 'rgba8unorm', storage = false) {
return GPU.device.createTexture({ size: [w, h], format, usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | (storage ? GPUTextureUsage.STORAGE_BINDING : 0) });
}
function gpuWrite(texture, data, stride, x, y, w, h) {
if (w <= 0 || h <= 0) return;
const bytes = data.BYTES_PER_ELEMENT;
GPU.device.queue.writeTexture({ texture, origin: [x, y] }, data, { offset: (y * stride + x) * bytes, bytesPerRow: stride * bytes }, [w, h]);
}
function gpuBuffer(data) {
const b = GPU.device.createBuffer({ size: data.byteLength, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST });
GPU.device.queue.writeBuffer(b, 0, data); return b;
}
function gpuWorldTextures() {
if (GPU.world === mat && GPU.sky === SKY_R) return;
if (GPU.worldTextures) for (const t of GPU.worldTextures) t.destroy();
if (GPU.lut) GPU.lut.destroy(); if (GPU.land) GPU.land.destroy();
GPU.world = mat; GPU.sky = SKY_R;
GPU.worldTextures = Array.from({ length: 8 }, (_, i) => gpuTexture(i === 7 ? FW : W, i === 7 ? FH : H, 'r8uint'));
GPU.temp = gpuTexture(W, H, 'r32float'); GPU.worldTextures.push(GPU.temp);
gpuWrite(GPU.worldTextures[6], noiseB, W, 0, 0, W, H);
const tables = new Float32Array((NMAT * 3 + 700 + 551 * 3) * 4);
for (let m = 0; m < NMAT; m++) {
tables.set([COLR[m], COLG[m], COLB[m], CVAR[m], PH[m], ALPHA[m], 0, 0, TREE_R[m], TREE_G[m], TREE_B[m], 0], m * 12);
}
for (let i = 0; i < 700; i++) tables.set([GLOW_R[i], GLOW_G[i], GLOW_B[i], GLOW_W[i]], (NMAT * 3 + i) * 4);
for (const [j, palette] of Object.values(THERM).entries()) for (let i = 0; i < 551; i++) tables.set([palette[i * 3], palette[i * 3 + 1], palette[i * 3 + 2], 0], (NMAT * 3 + 700 + j * 551 + i) * 4);
GPU.lut = gpuBuffer(tables);
GPU.landData = new Float32Array((W + H) * 4);
GPU.land = gpuBuffer(GPU.landData);
if (GPU.columns) GPU.columns.destroy();
GPU.columnData = new Float32Array(W * 4); GPU.columns = gpuBuffer(GPU.columnData);
GPU.mach = gpuTexture(W, H, 'r16uint'); GPU.block = gpuTexture(BW, BH, 'r8uint');
GPU.worldTextures.push(GPU.mach, GPU.block);
GPU.bindColour = null;
}
function gpuViewTextures() {
const key = `${R.bw}/${R.bh}/${R.lw}/${R.lh}/${+R.smoothOn}`;
if (GPU.viewKey === key) return;
GPU.viewKey = key; GPU.lightFrame = undefined;
if (GPU.viewTextures) for (const t of GPU.viewTextures) t.destroy();
const bw = R.bw, bh = R.bh, gw = R.glowC.width, gh = R.glowC.height;
GPU.override = gpuTexture(bw, bh); GPU.overrideGlow = gpuTexture(gw, gh);
GPU.colourTex = gpuTexture(bw, bh, 'rgba8unorm', true);
GPU.glowTex = gpuTexture(gw, gh, 'rgba8unorm', true);
GPU.blurX = gpuTexture(gw, gh, 'rgba8unorm', true); GPU.blurY = gpuTexture(gw, gh, 'rgba8unorm', true);
GPU.lightTex = gpuTexture(Math.max(1, R.lw), Math.max(1, R.lh), 'rgba8unorm', true);
GPU.lightWork = [0,1].map(() => gpuTexture(Math.max(1,R.lw),Math.max(1,R.lh),'rgba16float',true));
GPU.viewTextures = [GPU.override, GPU.overrideGlow, GPU.colourTex, GPU.glowTex, GPU.blurX, GPU.blurY, GPU.lightTex, ...GPU.lightWork];
if (R.smoothOn) gpuSmoothTextures();
GPU.bindColour = null;
}
function gpuBindings() {
if (GPU.bindColour) return;
const d = GPU.device, buffer = b => ({ buffer: b }), view = t => t.createView();
const group = (pipeline, resources) => d.createBindGroup({ layout: pipeline.getBindGroupLayout(0), entries: resources.map((resource, binding) => ({ binding, resource })) });
GPU.bindColour = group(GPU.colour, [buffer(GPU.uniform), buffer(GPU.lut), buffer(GPU.land), ...GPU.worldTextures.slice(0,9).map(view), view(GPU.override), view(GPU.overrideGlow), view(GPU.colourTex), view(GPU.glowTex)]);
if (R.smoothOn) gpuSmoothBindings(group, view, buffer);
gpuLightBindings(group, view, buffer);
GPU.bindDraw = group(GPU.draw, [buffer(GPU.uniform), view(GPU.colourTex), view(GPU.lightTex), view(GPU.blurY), GPU.sampler]);
if (GPU.blurUniforms) for (const b of GPU.blurUniforms) b.destroy();
GPU.blurUniforms = [0, 1].map(() => d.createBuffer({ size: 16, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST }));
GPU.bindBlur = [group(GPU.blur, [view(GPU.glowTex), view(GPU.blurX), buffer(GPU.blurUniforms[0])]), group(GPU.blur, [view(GPU.blurX), view(GPU.blurY), buffer(GPU.blurUniforms[1])])];
}
function gpuUpload(lighting, glowOn) {
gpuWorldTextures(); gpuViewTextures(); gpuBindings();
const margin = lighting ? Math.ceil(28 / R.lres) * R.lres + R.lres : 2;
const x = Math.max(0, R.vx0 - margin), y = Math.max(0, R.vy0 - margin);
const w = Math.min(W, R.vx0 + R.bw + margin) - x, h = Math.min(H, R.vy0 + R.bh + margin + 12) - y;
const arrays = [mat, flags, life, part, bgm, digp];
arrays.forEach((a, i) => gpuWrite(GPU.worldTextures[i], a, W, x, y, w, h));
gpuWrite(GPU.temp, temp, W, x, y, w, h);
if (lighting) {
let top=y;
for(let xx=x;xx<x+w;xx++) top=Math.min(top,roofY[xx]);
// Roof scans can start above the view; loose loads also inspect twelve grains below.
for(const [t,a] of [[GPU.worldTextures[0],mat],[GPU.worldTextures[1],flags],[GPU.worldTextures[3],part]]) gpuWrite(t,a,W,x,top,w,y+h-top);
gpuWrite(GPU.mach,mach,W,x,top,w,y+h-top);
const bx=x>>2, by=top>>2;
gpuWrite(GPU.block,blockP,BW,bx,by,Math.min(BW,Math.ceil((x+w)/4))-bx,Math.min(BH,Math.ceil((y+h)/4))-by);
}
const fx = Math.max(0, Math.floor(x / FOG)), fy = Math.max(0, Math.floor(y / FOG));
gpuWrite(GPU.worldTextures[7], fog, FW, fx, fy, Math.min(FW, Math.ceil((x + w) / FOG) + 1) - fx, Math.min(FH, Math.ceil((y + h) / FOG) + 1) - fy);
// Small per-row/column tables may change in scene tools independently of world allocation.
const land = GPU.landData;
for (let i = 0; i < W; i++) { const k = i * 4; land[k] = surfY[i]; land[k + 1] = dirtY[i]; land[k + 2] = graniteY[i]; land[k + 3] = basaltY[i]; }
for (let i = 0; i < H; i++) { const k = (W + i) * 4; land[k] = SKY_R[i]; land[k + 1] = SKY_G[i]; land[k + 2] = SKY_B[i]; land[k + 3] = AMB[i]; }
const q = GPU.device.queue;
q.writeBuffer(GPU.land, 0, land);
gpuWrite(GPU.override, R.px, R.bw, 0, 0, R.bw, R.bh);
gpuWrite(GPU.overrideGlow, R.glowPx, R.glowC.width, 0, 0, R.glowC.width, R.glowC.height);
for(let i=0;i<W;i++) { GPU.columnData[i*4]=surfY[i]; GPU.columnData[i*4+1]=roofY[i]; }
q.writeBuffer(GPU.columns,0,GPU.columnData);
if (R.smoothOn) gpuSmoothUpload();
const zp = R.zp, ox = Math.round(-(R.camX - R.vx0) * zp), oy = Math.round(-(R.camY - R.vy0) * zp);
GPU.params.set([R.vx0, R.vy0, R.bw, R.bh, W, H, FW, FH,
+SET.fog, R.rain, SET.grain, R.frame, +R.lens, Math.max(0, Object.keys(THERM).indexOf(SET.thermalPalette)), +SET.incandescence, +glowOn,
ox, oy, zp, 0, R.lx0, R.ly0, R.lres, +lighting,
R.canvas.width, R.canvas.height, SET.glow === 'high' ? 0.9 : 0.5, 0, R.camX, R.camY, 0, 0]);
q.writeBuffer(GPU.uniform, 0, GPU.params);
const sigma = Math.max(2, zp * (SET.glow === 'high' ? 3.2 : 2.2)) / (2 * zp);
q.writeBuffer(GPU.blurUniforms[0], 0, new Float32Array([1, 0, sigma, 0]));
q.writeBuffer(GPU.blurUniforms[1], 0, new Float32Array([0, 1, sigma, 0]));
}