playcanvas
Version:
PlayCanvas WebGL game engine
141 lines (138 loc) • 5.96 kB
JavaScript
import { Texture } from '../../platform/graphics/texture.js';
import { SEMANTIC_POSITION, ADDRESS_CLAMP_TO_EDGE, FILTER_NEAREST, PIXELFORMAT_R32U, PIXELFORMAT_RG32U, CULLFACE_NONE } from '../../platform/graphics/constants.js';
import { RenderTarget } from '../../platform/graphics/render-target.js';
import { drawQuadWithShader } from '../graphics/quad-render-utils.js';
import { BlendState } from '../../platform/graphics/blend-state.js';
import { DepthState } from '../../platform/graphics/depth-state.js';
import { ShaderUtils } from '../shader-lib/shader-utils.js';
import gsplatIntervalTextureGLSL from '../shader-lib/glsl/chunks/gsplat/frag/gsplatIntervalTexture.js';
import gsplatIntervalTextureWGSL from '../shader-lib/wgsl/chunks/gsplat/frag/gsplatIntervalTexture.js';
/**
* @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
* @import { Shader } from '../../platform/graphics/shader.js'
*/ /**
* Manages the intervals texture generation for GSplat LOD system using GPU acceleration. A list of
* intervals is provided to the update method, and the texture is generated on the GPU. The texture
* is then used to map target indices to source splat indices.
*
* @ignore
*/ class GSplatIntervalTexture {
/**
* @param {GraphicsDevice} device - The graphics device
*/ constructor(device){
/**
* Texture that maps target indices to source splat indices based on intervals
*
* @type {Texture|null}
*/ this.texture = null;
/**
* Render target for the intervals texture
*
* @type {RenderTarget|null}
*/ this.rt = null;
/**
* Texture that stores interval data (start + accumulated sum pairs) for GPU processing
*
* @type {Texture|null}
*/ this.intervalsDataTexture = null;
/**
* Shader for generating intervals texture on GPU
*
* @type {Shader|null}
*/ this.shader = null;
this.device = device;
}
destroy() {
this.texture?.destroy();
this.texture = null;
this.rt?.destroy();
this.rt = null;
this.intervalsDataTexture?.destroy();
this.intervalsDataTexture = null;
this.shader = null;
}
/**
* Creates shader for GPU-based intervals texture generation
*/ getShader() {
if (!this.shader) {
this.shader = ShaderUtils.createShader(this.device, {
uniqueName: 'GSplatIntervalsShader',
attributes: {
aPosition: SEMANTIC_POSITION
},
vertexChunk: 'quadVS',
fragmentGLSL: gsplatIntervalTextureGLSL,
fragmentWGSL: gsplatIntervalTextureWGSL,
fragmentOutputTypes: [
'uint'
]
});
}
return this.shader;
}
/**
* Creates a texture with specified parameters
*/ createTexture(name, format, width, height) {
return new Texture(this.device, {
name: name,
width: width,
height: height,
format: format,
cubemap: false,
mipmaps: false,
minFilter: FILTER_NEAREST,
magFilter: FILTER_NEAREST,
addressU: ADDRESS_CLAMP_TO_EDGE,
addressV: ADDRESS_CLAMP_TO_EDGE
});
}
/**
* Updates the intervals texture based on provided intervals array
*
* @param {number[]} intervals - Array of intervals (start, end pairs)
* @param {number} totalIntervalSplats - Total number of splats referenced by the intervals
* @returns {number} The number of active splats
*/ update(intervals, totalIntervalSplats) {
// Calculate texture dimensions for output intervals texture
const maxTextureSize = this.device.maxTextureSize;
let textureWidth = Math.ceil(Math.sqrt(totalIntervalSplats));
textureWidth = Math.min(textureWidth, maxTextureSize);
const textureHeight = Math.ceil(totalIntervalSplats / textureWidth);
// Create main intervals texture
this.texture = this.createTexture('intervalsTexture', PIXELFORMAT_R32U, textureWidth, textureHeight);
this.rt = new RenderTarget({
colorBuffer: this.texture,
depth: false
});
// Prepare intervals data with CPU prefix sum
const numIntervals = intervals.length / 2;
const dataTextureSize = Math.ceil(Math.sqrt(numIntervals));
// Create intervals data texture
this.intervalsDataTexture = this.createTexture('intervalsData', PIXELFORMAT_RG32U, dataTextureSize, dataTextureSize);
// Compute intervals data with accumulated sums on CPU
// TODO: consider doing this using compute shader on WebGPU
const intervalsData = this.intervalsDataTexture.lock();
let runningSum = 0;
for(let i = 0; i < numIntervals; i++){
const start = intervals[i * 2];
const end = intervals[i * 2 + 1];
const intervalSize = end - start;
runningSum += intervalSize;
intervalsData[i * 2] = start; // R: interval start
intervalsData[i * 2 + 1] = runningSum; // G: accumulated sum
}
this.intervalsDataTexture.unlock();
// Generate intervals texture on GPU
const scope = this.device.scope;
scope.resolve('uIntervalsTexture').setValue(this.intervalsDataTexture);
scope.resolve('uNumIntervals').setValue(numIntervals);
scope.resolve('uTextureWidth').setValue(textureWidth);
scope.resolve('uActiveSplats').setValue(totalIntervalSplats);
this.device.setCullMode(CULLFACE_NONE);
this.device.setBlendState(BlendState.NOBLEND);
this.device.setDepthState(DepthState.NODEPTH);
drawQuadWithShader(this.device, this.rt, this.getShader());
return totalIntervalSplats;
}
}
export { GSplatIntervalTexture };