Files
OpenFrontIO/src/client/graphics/webgpu/render/TerritoryRenderPass.ts
T
scamiv 97603f7a1a replace defended epoch stamping with defended-strength field
Store defended influence in defendedStrengthTexture and sample it in territory render shader
Recompute defended strength on tick for state-updated tiles and for post-change dirty tiles, with full-map fallback when diffs are large
Pack defense posts by owner on GPU (owner offsets + posts buffer)
Remove old defended clear/update passes and epoch-based params
2026-05-26 20:17:52 +02:00

179 lines
4.5 KiB
TypeScript

import { GroundTruthData } from "../core/GroundTruthData";
import { loadShader } from "../core/ShaderLoader";
import { RenderPass } from "./RenderPass";
/**
* Main territory rendering pass.
* Renders territory colors, defended tiles, fallout, and hover highlights.
*/
export class TerritoryRenderPass implements RenderPass {
name = "territory";
dependencies: string[] = [];
private pipeline: GPURenderPipeline | null = null;
private bindGroupLayout: GPUBindGroupLayout | null = null;
private bindGroup: GPUBindGroup | null = null;
private device: GPUDevice | null = null;
private resources: GroundTruthData | null = null;
private canvasFormat: GPUTextureFormat | null = null;
private clearR = 0;
private clearG = 0;
private clearB = 0;
async init(
device: GPUDevice,
resources: GroundTruthData,
canvasFormat: GPUTextureFormat,
): Promise<void> {
this.device = device;
this.resources = resources;
this.canvasFormat = canvasFormat;
const shaderCode = await loadShader("render/territory.wgsl");
const shaderModule = device.createShaderModule({ code: shaderCode });
this.bindGroupLayout = device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: 2 /* FRAGMENT */,
buffer: { type: "uniform" },
},
{
binding: 1,
visibility: 2 /* FRAGMENT */,
texture: { sampleType: "uint" },
},
{
binding: 2,
visibility: 2 /* FRAGMENT */,
texture: { sampleType: "float" },
},
{
binding: 3,
visibility: 2 /* FRAGMENT */,
texture: { sampleType: "float" },
},
{
binding: 4,
visibility: 2 /* FRAGMENT */,
texture: { sampleType: "float" },
},
],
});
this.pipeline = device.createRenderPipeline({
layout: device.createPipelineLayout({
bindGroupLayouts: [this.bindGroupLayout],
}),
vertex: { module: shaderModule, entryPoint: "vsMain" },
fragment: {
module: shaderModule,
entryPoint: "fsMain",
targets: [{ format: canvasFormat }],
},
primitive: { topology: "triangle-list" },
});
this.rebuildBindGroup();
// Extract clear color from theme
const bg = resources.getTheme().backgroundColor().rgba;
this.clearR = bg.r / 255;
this.clearG = bg.g / 255;
this.clearB = bg.b / 255;
}
needsUpdate(): boolean {
// Always run every frame (can be optimized later if needed)
return true;
}
execute(
encoder: GPUCommandEncoder,
resources: GroundTruthData,
target: GPUTextureView,
): void {
if (!this.device || !this.pipeline) {
return;
}
// Rebuild bind group if needed (e.g., after texture recreation)
this.rebuildBindGroup();
if (!this.bindGroup) {
return;
}
// Update uniforms
resources.writeUniformBuffer(performance.now() / 1000);
const pass = encoder.beginRenderPass({
colorAttachments: [
{
view: target,
loadOp: "clear",
storeOp: "store",
clearValue: {
r: this.clearR,
g: this.clearG,
b: this.clearB,
a: 1,
},
},
],
});
pass.setPipeline(this.pipeline);
pass.setBindGroup(0, this.bindGroup);
pass.draw(3);
pass.end();
}
rebuildBindGroup(): void {
if (
!this.device ||
!this.bindGroupLayout ||
!this.resources ||
!this.resources.uniformBuffer ||
!this.resources.stateTexture ||
!this.resources.defendedStrengthTexture ||
!this.resources.paletteTexture ||
!this.resources.terrainTexture
) {
return;
}
this.bindGroup = this.device.createBindGroup({
layout: this.bindGroupLayout,
entries: [
{ binding: 0, resource: { buffer: this.resources.uniformBuffer } },
{
binding: 1,
resource: this.resources.stateTexture.createView(),
},
{
binding: 2,
resource: this.resources.defendedStrengthTexture.createView(),
},
{
binding: 3,
resource: this.resources.paletteTexture.createView(),
},
{
binding: 4,
resource: this.resources.terrainTexture.createView(),
},
],
});
}
dispose(): void {
this.pipeline = null;
this.bindGroupLayout = null;
this.bindGroup = null;
this.device = null;
this.resources = null;
}
}