// 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>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