Hello everyone,
I’m new to using TSL, and since the documentation is still quite limited due to how recent it is, I’m reaching out to the community for help!
About a year ago, I created a raymarching-based cloud generator in GLSL, and now I’m trying to rebuild it using TSL as an exercise to learn the system. One of the key components of this project is an SDF drawer using render targets in a ping-pong setup (GPGPU).
Using a single render target in TSL is pretty straightforward, but as soon as I try to implement ping-pong rendering, I get a read-write conflict error. After doing some research, I discovered the compute()
functions in TSL, and was able—after some struggle—to get the SDF drawer working properly.
However, I’m now facing a major issue: resizing. The resize logic doesn’t work properly, and the resolution scaling breaks as well. I created a scaleUv
function to try to handle resizing consistently, but I can’t use uv()
or screenUV()
inside my compute function. I’m basically stuck having to use posX
and posY
, which I don’t fully understand, to be honest.
Additionally, when I try to resize, the app starts lagging heavily, even though the resize doesn’t work visually. I suspect this might be a more general misunderstanding of how computeAsync()
works in TSL.
If anyone has a working example of using computeAsync()
for something similar, or any idea what I’m doing wrong, I would be extremely grateful for your help!
If you have any questions, feel free to ask!
Resources I’ve already read (including all current Three.js webGPU examples):
https://codesandbox.io/p/github/Stan2hssn/clouds_tsl/main
Regarding the resizing issue, WebGPU manages it differently compared to WebGL. Here’s the correct approach for handling resizing:
/ At init time
const canvas = document.querySelector('canvas');
const context = canvas.getContext('webgpu');
const presentationFormat = navigator.gpu.getPreferredFormat(adapter);
context.configure({
device,
format: presentationFormat,
});
const canvasInfo = {
canvas,
presentationFormat,
// these are filled out in resizeToDisplaySize
renderTarget: undefined,
renderTargetView: undefined,
depthTexture: undefined,
depthTextureView: undefined,
sampleCount: 4, // can be 1 or 4
};
// --- At render time ---
function resizeToDisplaySize(device, canvasInfo) {
const {
canvas,
context,
renderTarget,
presentationFormat,
depthTexture,
sampleCount,
} = canvasInfo;
const width = Math.max(1, Math.min(device.limits.maxTextureDimension2D, canvas.clientWidth));
const height = Math.max(1, Math.min(device.limits.maxTextureDimension2D, canvas.clientHeight));
const needResize = !canvasInfo.renderTarget ||
width !== canvas.width ||
height !== canvas.height;
if (needResize) {
if (renderTarget) {
renderTarget.destroy();
}
if (depthTexture) {
depthTexture.destroy();
}
canvas.width = width;
canvas.height = height;
if (sampleCount > 1) {
const newRenderTarget = device.createTexture({
size: [canvas.width, canvas.height],
format: presentationFormat,
sampleCount,
usage: GPUTextureUsage.RENDER_ATTACHMENT,
});
canvasInfo.renderTarget = newRenderTarget;
canvasInfo.renderTargetView = newRenderTarget.createView();
}
const newDepthTexture = device.createTexture({
size: [canvas.width, canvas.height,
format: 'depth24plus',
sampleCount,
usage: GPUTextureUsage.RENDER_ATTACHMENT,
});
canvasInfo.depthTexture = newDepthTexture;
canvasInfo.depthTextureView = newDepthTexture.createView();
}
return needResize;
}