playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
324 lines (323 loc) • 13.1 kB
JavaScript
const computeGsplatLocalRasterizeSource = `
#include "halfTypesCS"
#ifndef PICK_MODE
#include "decodePS"
#if FOG != NONE
#include "fogMathPS"
#include "gammaPS"
#endif
#endif
const BATCH_SIZE: u32 = 64u;
const ALPHA_THRESHOLD: half = half(1.0) / half(255.0);
const EXP4: half = exp(half(-4.0));
const INV_EXP4: half = half(1.0) / (half(1.0) - EXP4);
struct Uniforms {
screenWidth: u32,
screenHeight: u32,
numTilesX: u32,
nearClip: f32,
farClip: f32,
alphaClip: f32,
#if FOG != NONE
fog_color: vec3f,
fog_start: f32,
fog_end: f32,
fog_density: f32,
#endif
}
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
@group(0) @binding(1) var<storage, read> tileEntries: array<u32>;
@group(0) @binding(2) var<storage, read> tileSplatCounts: array<u32>;
@group(0) @binding(3) var<storage, read> projCache: array<u32>;
@group(0) @binding(4) var<storage, read> rasterizeTileList: array<u32>;
@group(0) @binding(5) var<storage, read> tileListCounts: array<u32>;
@group(0) @binding(6) var<storage, read> depthBuffer: array<u32>;
#ifdef PICK_MODE
@group(0) @binding(7) var pickIdTexture: texture_storage_2d<r32uint, write>;
@group(0) @binding(8) var pickDepthTexture: texture_storage_2d<rgba16float, write>;
#else
@group(0) @binding(7) var outputTexture: texture_storage_2d<rgba16float, write>;
#ifdef DEPTH_TEST
@group(0) @binding(8) var sceneDepthMap: texture_2d<f32>;
#endif
#endif
var<workgroup> sharedCenterScreen: array<vec2f, 64>;
var<workgroup> sharedCoeffs: array<vec3f, 64>;
var<workgroup> sharedPowerCutoff: array<f32, 64>;
#ifdef HEATMAP_MODE
var<workgroup> sharedHeatCount: atomic<u32>;
#endif
#ifdef PICK_MODE
var<workgroup> sharedOpacity: array<half, 64>;
var<workgroup> sharedPickId: array<u32, 64>;
var<workgroup> sharedViewDepth: array<f32, 64>;
#else
var<workgroup> sharedColor: array<half4, 64>;
#ifdef DEPTH_TEST
var<workgroup> sharedViewDepth: array<f32, 64>;
#endif
#endif
#ifdef PICK_MODE
fn evalSplatPick(pixelCoord: vec2f, center: vec2f, coeffX: f32, coeffY: f32, coeffXY: f32,
opacity: half, pickId: u32, viewDepth: f32, alphaClip: half,
bestPickId: ptr<function, u32>, depthAccum: ptr<function, f32>,
weightAccum: ptr<function, f32>, T: ptr<function, half>) {
let dx = pixelCoord - center;
let power = coeffX * dx.x * dx.x + coeffXY * dx.x * dx.y + coeffY * dx.y * dx.y;
let gauss = (half(exp(power)) - EXP4) * INV_EXP4;
let alpha = half(min(half(0.99), opacity * gauss));
let newT = *T * (half(1.0) - alpha);
let visible = power > -4.0 && alpha > ALPHA_THRESHOLD && *T >= ALPHA_THRESHOLD;
if (!visible) { return; }
if (alpha >= alphaClip) {
if (*bestPickId == 0xFFFFFFFFu) {
*bestPickId = pickId;
}
let normalizedDepth = saturate((viewDepth - uniforms.nearClip) / (uniforms.farClip - uniforms.nearClip));
let w = f32(alpha) * f32(*T);
*depthAccum += w * normalizedDepth;
*weightAccum += w;
}
*T = newT;
}
#endif
#ifdef HEATMAP_MODE
fn heatmapColor(v: f32) -> vec3f {
let t = saturate(v / 2000.0);
if (t < 0.2) {
return mix(vec3f(0.0, 0.0, 1.0), vec3f(0.0, 1.0, 1.0), t * 5.0);
} else if (t < 0.4) {
return mix(vec3f(0.0, 1.0, 1.0), vec3f(1.0, 1.0, 0.0), (t - 0.2) * 5.0);
} else if (t < 0.6) {
return mix(vec3f(1.0, 1.0, 0.0), vec3f(1.0, 0.0, 0.0), (t - 0.4) * 5.0);
}
return mix(vec3f(1.0, 0.0, 0.0), vec3f(0.15, 0.0, 0.0), (t - 0.6) * 2.5);
}
#endif
@compute @workgroup_size(8, 8)
fn main(
@builtin(local_invocation_id) lid: vec3u,
@builtin(local_invocation_index) localIdx: u32,
@builtin(workgroup_id) wid: vec3u,
@builtin(num_workgroups) numWorkgroups: vec3u
) {
let workgroupIdx = wid.y * numWorkgroups.x + wid.x;
if (workgroupIdx >= tileListCounts[2]) {
return;
}
let tileIdx = rasterizeTileList[workgroupIdx];
let tileX = tileIdx % uniforms.numTilesX;
let tileY = tileIdx / uniforms.numTilesX;
let tStart = tileSplatCounts[tileIdx];
let tEnd = tileSplatCounts[tileIdx + 1u];
let basePixel = vec2u(tileX * 16u + lid.x * 2u, tileY * 16u + lid.y * 2u);
let p00 = vec2f(f32(basePixel.x) + 0.5, f32(basePixel.y) + 0.5);
let p10 = p00 + vec2f(1.0, 0.0);
let p01 = p00 + vec2f(0.0, 1.0);
let p11 = p00 + vec2f(1.0, 1.0);
#ifdef PICK_MODE
var T00: half = half(1.0); var T10: half = half(1.0);
var T01: half = half(1.0); var T11: half = half(1.0);
var pickId00: u32 = 0xFFFFFFFFu; var pickId10: u32 = 0xFFFFFFFFu;
var pickId01: u32 = 0xFFFFFFFFu; var pickId11: u32 = 0xFFFFFFFFu;
var dAcc00: f32 = 0.0; var dAcc10: f32 = 0.0;
var dAcc01: f32 = 0.0; var dAcc11: f32 = 0.0;
var wAcc00: f32 = 0.0; var wAcc10: f32 = 0.0;
var wAcc01: f32 = 0.0; var wAcc11: f32 = 0.0;
let clipH = half(uniforms.alphaClip);
#else
var T = half4(1.0);
var c00 = half3(0.0); var c10 = half3(0.0);
var c01 = half3(0.0); var c11 = half3(0.0);
#ifdef DEPTH_TEST
var sceneDepth = vec4f(1e20);
let depthY0 = uniforms.screenHeight - 1u - basePixel.y;
let depthY1 = depthY0 - 1u;
if (basePixel.x < uniforms.screenWidth && basePixel.y < uniforms.screenHeight) {
sceneDepth.x = textureLoad(sceneDepthMap, vec2i(vec2u(basePixel.x, depthY0)), 0).r;
}
if (basePixel.x + 1u < uniforms.screenWidth && basePixel.y < uniforms.screenHeight) {
sceneDepth.y = textureLoad(sceneDepthMap, vec2i(vec2u(basePixel.x + 1u, depthY0)), 0).r;
}
if (basePixel.x < uniforms.screenWidth && basePixel.y + 1u < uniforms.screenHeight) {
sceneDepth.z = textureLoad(sceneDepthMap, vec2i(vec2u(basePixel.x, depthY1)), 0).r;
}
if (basePixel.x + 1u < uniforms.screenWidth && basePixel.y + 1u < uniforms.screenHeight) {
sceneDepth.w = textureLoad(sceneDepthMap, vec2i(vec2u(basePixel.x + 1u, depthY1)), 0).r;
}
#endif
#endif
let tileCount = tEnd - tStart;
#ifdef HEATMAP_MODE
if (localIdx == 0u) { atomicStore(&sharedHeatCount, 0u); }
workgroupBarrier();
var processedCount: u32 = 0u;
#endif
let numBatches = (tileCount + BATCH_SIZE - 1u) / BATCH_SIZE;
var threadDone = false;
for (var batch: u32 = 0u; batch < numBatches; batch++) {
let batchOffset = batch * BATCH_SIZE + localIdx;
if (batchOffset < tileCount) {
let cacheIdx = tileEntries[tStart + batchOffset];
let base = cacheIdx * {CACHE_STRIDE}u;
sharedCenterScreen[localIdx] = vec2f(
bitcast<f32>(projCache[base + 0u]),
bitcast<f32>(projCache[base + 1u])
);
let cx = bitcast<f32>(projCache[base + 2u]);
let cy = bitcast<f32>(projCache[base + 3u]);
let cz = bitcast<f32>(projCache[base + 4u]);
sharedCoeffs[localIdx] = vec3f(cx * -0.5, cz * -0.5, -cy);
sharedPowerCutoff[localIdx] = bitcast<f32>(projCache[base + 7u]);
#ifdef PICK_MODE
sharedPickId[localIdx] = projCache[base + 5u];
sharedOpacity[localIdx] = half(unpack2x16float(projCache[base + 6u]).y);
sharedViewDepth[localIdx] = bitcast<f32>(depthBuffer[cacheIdx]);
#else
let rg = unpack2x16float(projCache[base + 5u]);
let ba = unpack2x16float(projCache[base + 6u]);
#if FOG != NONE
let viewDepth = bitcast<f32>(depthBuffer[cacheIdx]);
#if (FOG == LINEAR)
let fogFactor = evaluateFogFactorLinear(viewDepth, uniforms.fog_start, uniforms.fog_end);
#elif (FOG == EXP)
let fogFactor = evaluateFogFactorExp(viewDepth, uniforms.fog_density);
#elif (FOG == EXP2)
let fogFactor = evaluateFogFactorExp2(viewDepth, uniforms.fog_density);
#endif
var foggedColor = decodeGamma3(vec3f(rg.x, rg.y, ba.x));
foggedColor = mix(uniforms.fog_color, foggedColor, fogFactor);
sharedColor[localIdx] = half4(half3(gammaCorrectOutput(foggedColor)), half(ba.y));
#else
sharedColor[localIdx] = half4(half(rg.x), half(rg.y), half(ba.x), half(ba.y));
#endif
#ifdef DEPTH_TEST
sharedViewDepth[localIdx] = bitcast<f32>(depthBuffer[cacheIdx]);
#endif
#endif
}
workgroupBarrier();
if (!threadDone) {
let batchCount = min(BATCH_SIZE, tileCount - batch * BATCH_SIZE);
for (var i: u32 = 0u; i < batchCount; i++) {
let center = sharedCenterScreen[i];
let coeffs = sharedCoeffs[i];
#ifdef PICK_MODE
let splatOpacity = sharedOpacity[i];
let splatPickId = sharedPickId[i];
let splatDepth = sharedViewDepth[i];
let d = p00 - center;
let dxV = vec4f(d.x, d.x + 1.0, d.x, d.x + 1.0);
let dyV = vec4f(d.y, d.y, d.y + 1.0, d.y + 1.0);
let power4 = coeffs.x * dxV * dxV + coeffs.z * dxV * dyV + coeffs.y * dyV * dyV;
if (all(power4 <= vec4f(sharedPowerCutoff[i]))) {
continue;
}
evalSplatPick(p00, center, coeffs.x, coeffs.y, coeffs.z, splatOpacity, splatPickId, splatDepth, clipH, &pickId00, &dAcc00, &wAcc00, &T00);
evalSplatPick(p10, center, coeffs.x, coeffs.y, coeffs.z, splatOpacity, splatPickId, splatDepth, clipH, &pickId10, &dAcc10, &wAcc10, &T10);
evalSplatPick(p01, center, coeffs.x, coeffs.y, coeffs.z, splatOpacity, splatPickId, splatDepth, clipH, &pickId01, &dAcc01, &wAcc01, &T01);
evalSplatPick(p11, center, coeffs.x, coeffs.y, coeffs.z, splatOpacity, splatPickId, splatDepth, clipH, &pickId11, &dAcc11, &wAcc11, &T11);
if (all(vec4<half>(T00, T10, T01, T11) < half4(ALPHA_THRESHOLD))) {
threadDone = true;
break;
}
#else
let splatColor = sharedColor[i];
#ifdef DEPTH_TEST
let splatDepth = sharedViewDepth[i];
if (all(vec4f(splatDepth) > sceneDepth)) {
threadDone = true;
break;
}
#endif
let d = p00 - center;
let dxV = vec4f(d.x, d.x + 1.0, d.x, d.x + 1.0);
let dyV = vec4f(d.y, d.y, d.y + 1.0, d.y + 1.0);
let power4 = coeffs.x * dxV * dxV + coeffs.z * dxV * dyV + coeffs.y * dyV * dyV;
if (all(power4 <= vec4f(sharedPowerCutoff[i]))) {
continue;
}
let gauss4 = (half4(exp(power4)) - half4(EXP4)) * half4(INV_EXP4);
let alpha4 = min(half4(0.99), half4(splatColor.a) * gauss4);
let newT = T * (half4(1.0) - alpha4);
var valid = (power4 > vec4f(-4.0)) & (alpha4 > half4(ALPHA_THRESHOLD)) & (T >= half4(ALPHA_THRESHOLD));
#ifdef DEPTH_TEST
valid = valid & (vec4f(splatDepth) <= sceneDepth);
#endif
let weight = alpha4 * T * select(half4(0.0), half4(1.0), valid);
c00 += splatColor.rgb * weight.x;
c10 += splatColor.rgb * weight.y;
c01 += splatColor.rgb * weight.z;
c11 += splatColor.rgb * weight.w;
T = select(T, newT, valid);
#ifdef HEATMAP_MODE
processedCount += 1u;
#endif
if (all(T < half4(ALPHA_THRESHOLD))) {
threadDone = true;
break;
}
#endif
}
}
workgroupBarrier();
}
#ifdef HEATMAP_MODE
atomicAdd(&sharedHeatCount, processedCount);
workgroupBarrier();
let avgCount = f32(atomicLoad(&sharedHeatCount)) / 64.0;
let heatColor = vec4f(heatmapColor(avgCount), 1.0);
if (basePixel.x < uniforms.screenWidth && basePixel.y < uniforms.screenHeight) {
textureStore(outputTexture, basePixel, heatColor);
}
if (basePixel.x + 1u < uniforms.screenWidth && basePixel.y < uniforms.screenHeight) {
textureStore(outputTexture, vec2u(basePixel.x + 1u, basePixel.y), heatColor);
}
if (basePixel.x < uniforms.screenWidth && basePixel.y + 1u < uniforms.screenHeight) {
textureStore(outputTexture, vec2u(basePixel.x, basePixel.y + 1u), heatColor);
}
if (basePixel.x + 1u < uniforms.screenWidth && basePixel.y + 1u < uniforms.screenHeight) {
textureStore(outputTexture, vec2u(basePixel.x + 1u, basePixel.y + 1u), heatColor);
}
#else
if (basePixel.x < uniforms.screenWidth && basePixel.y < uniforms.screenHeight) {
#ifdef PICK_MODE
textureStore(pickIdTexture, basePixel, vec4u(pickId00, 0u, 0u, 0u));
textureStore(pickDepthTexture, basePixel, vec4f(dAcc00, wAcc00, 0.0, 0.0));
#else
textureStore(outputTexture, basePixel, vec4f(decodeGamma3(vec3f(c00)), f32(half(1.0) - T.x)));
#endif
}
if (basePixel.x + 1u < uniforms.screenWidth && basePixel.y < uniforms.screenHeight) {
let px10 = vec2u(basePixel.x + 1u, basePixel.y);
#ifdef PICK_MODE
textureStore(pickIdTexture, px10, vec4u(pickId10, 0u, 0u, 0u));
textureStore(pickDepthTexture, px10, vec4f(dAcc10, wAcc10, 0.0, 0.0));
#else
textureStore(outputTexture, px10, vec4f(decodeGamma3(vec3f(c10)), f32(half(1.0) - T.y)));
#endif
}
if (basePixel.x < uniforms.screenWidth && basePixel.y + 1u < uniforms.screenHeight) {
let px01 = vec2u(basePixel.x, basePixel.y + 1u);
#ifdef PICK_MODE
textureStore(pickIdTexture, px01, vec4u(pickId01, 0u, 0u, 0u));
textureStore(pickDepthTexture, px01, vec4f(dAcc01, wAcc01, 0.0, 0.0));
#else
textureStore(outputTexture, px01, vec4f(decodeGamma3(vec3f(c01)), f32(half(1.0) - T.z)));
#endif
}
if (basePixel.x + 1u < uniforms.screenWidth && basePixel.y + 1u < uniforms.screenHeight) {
let px11 = vec2u(basePixel.x + 1u, basePixel.y + 1u);
#ifdef PICK_MODE
textureStore(pickIdTexture, px11, vec4u(pickId11, 0u, 0u, 0u));
textureStore(pickDepthTexture, px11, vec4f(dAcc11, wAcc11, 0.0, 0.0));
#else
textureStore(outputTexture, px11, vec4f(decodeGamma3(vec3f(c11)), f32(half(1.0) - T.w)));
#endif
}
#endif
}
`;
export {
computeGsplatLocalRasterizeSource
};