R179 Now Processes WebGPU Compute dispatchSize Parameter

For some reason, the latest version of three.js broke my WebGPU iFFT Ocean Wave Generator module.

EDIT Aug 8 **************************************************************************

It turns out that the problem was that one of the instructions in my module had a compdispatchSize parameter which was unnecessary. Prior to r179, this parameter was ignored and the module worked fine. However, r179 now processes that parameter. In my case, I was able to solve the problem by simply deleting the parameter. So most of the following discussion no longer has much meaning.

Thanks to everyone who contributed and eventually led me to the correct answer.

END EDIT ****************************************************************************

This is how it looks in r178: This is how it looks in r179 (and r179.1):

I have seen this pattern (or lack thereof) before, but I can’t remember what caused it or how I fixed it.

I don’t expect anyone to dig into my program or module, but does anyone have an idea what might be causing this? Is there some kind of simple programming change that I need to implement with r179?

In a nutshell, the module makes a series of linked computations, using WebGPU shaders (including a ping-pong operation) that generates XYZ displacements (wav_.Dsp) and a normal map(wav_.Nrm) that I attach to a segmented plane object using these commands:

positionNode: positionLocal.add(texture(wav_.Dsp).xyz),

normalNode: normalMap(texture(wav_.Nrm),normalMapScale),

2 Likes

It would be helpful if you could reproduce the issue with a smaller test case so we are able to track down the issue.

Another strategy: You can check out different commits between r178 and 179, perform a build and then see when the application starts to break. In this way, it’s possible to identify the exact PR that potentially introduced a regression.

2 Likes

It’s an odd error.

In the last couple of versions, I had a couple of error notices. The first was: :

THREE.Renderer: compute() called before the backend is initialized. Try using .computeAsync() instead. three.webgpu.js:53411

So I changed the compute commands to computeAsync

Then I got this error notice:

WebGPUTimesstampQuertyPool [compute]: Maximum number of queries exceeded, when using trackTimestamp it is necessary to resolves the queries via renderer.resolveTimestampAsync(THREE.TimestampQuery.COMPUTE). three.core.js:6250

So I added: renderer.resolveTimestampsAsync(THREE.TimestampQuery.COMPUTE); to the program.

That all worked with r178 but not with r179.

After some trial and error, I was able to get my smaller programs working by changing the two computeAsync commands in the initialization back to compute. But, of course, that takes me back to my original error notice, but the programs run.

Unfortunately, this change does not appear to fix my larger programs.

Here is the source code for the module that worked with r178. The computeAsync commands begin on line 605 which is part of the initialization.

Here is the source code for the module that sometimes works with r179. I changed lines 605-606.

Here is an update of an older version of a program that contains much of the source code for the module and shows the results of each of the computations in separate panels. Here is the source code. I was able to get this to work with r179 by making the changes shown in lines 594-598. Without those changes, the second panel in the second row (the colorful one) was distorted vertically which, I think caused the following computations (panels) to become corrupted. (You can download or copy the program and run it locally).

I am no expert on the rules relating to computeAsync and renderAsync or TimestampQuery, so it is quite possible that I am doing something wrong.

1 Like

Maybe another strategy: compare the whole shader code generate by R178 and R179ļ¼›especially vertex shader code

1 Like

What happens if you try to initialize the backend first before calling compute?

1 Like

I am not sure what that means or how I would do that.

I assume the phrase ā€œinitialize the backendā€ means to get everything set up before calling compute.

Should I never call compute when initializing a class?

The only reason I do so is to compute two tables with values that will not change. I could move them to the update section and initialize them with the first update.

UPDATE

I tried moving them and ended up with the same error. For some reason, in my particular program and with r179, these 2 instructions need to be part of the initialization and cannot be computeAsync:

renderer.compute(initSpectrumComp);
renderer.compute(butterflyComp,[1,8,1]);

That generates two error notices.

Look into async await having an async function alone will simply not make everything in the function async, you have two options, you can await a function to finish before a next is started or you can use the method .then(()=> //do the next task) if your code is not already setup this way and you’re not familiar with this I’d look into async await

1 Like

My wild guess is that three first needs to render something in order to initialize a bunch of stuff.

1 Like

You’ve identified an area in which I am totally ignorant.

In the ā€œold daysā€, my programs had an initialization subroutine and a render subroutine. The initialization subroutine could both load files and initialize values.

However, as the size of external files grew, I found that I had to create an initial load subroutine that would complete the loading process before proceeding to the initialization subroutine. I accomplished this using a loading manager. When the loading manager has determined that all files had loaded, it calls the initialization subroutine.

I ran into a similar problem with running the render subroutine before the initialization was done. So I added a last step to the initialization subroutine that would set initFlag = 1. In the compute subroutine, I added an instruction to check if initFlag = 1 before running anything.

With the switch to WebGPU, I am now using the setAnimationLoop command to call my render subroutine.

I believe that the ā€œmodern methodā€ would be to ditch the loading manager and initFlag and use some kind of await command. But I have never figured out how to do that and I expect that, with r179, ignorance is no longer tolerated.

Is there a good discussion regarding how to accomplish these things in three.js using the await command? Should the initialization and render subroutines be somehow preceded by an await command? In the past, I have tried and failed to make the await command do anything but generate error messages.

As far as I’m aware, loading manager is already async and awaits all loading before calling the callback onLoad so you’re on the right track here, however, WebGPU compute essentially brings the async await logic into userland, in your pre init async function you may need to use a pattern such as…

await renderer.computeAsync() init()

which makes the call to compute completely async within your function or…

renderer.computeAsync().then(()=>init())

This may also apply to other parts of your program but it is odd that this has changed from one release of three

1 Like

Every time I use the await command, I get an error message about using a reserved word.

I was able to get the revised module to work with my big programs by initializing the Ocean module within my loading section - even though the function does not load anything. So this feels like a kludge.

But I still get the two error messages about compute being called before the backend is initialized and that Timestamp tracking is disabled (which makes sense because there are no render.compteAsync commands for TimestampAsync to keep track of).

ADD: But removing TimestampAsync causes the problem to reappear

I mean: you are using three.js shade language, finally your TSL code have to be translated to glsl shader code or wgsl shader code. check the translated shader code, compare them between R178 and R179, especially the vertex shader code part.

1 Like

Thanks for the explanation. I did not use TSL to create the shaders. They were written in wgsl. The wgslFn should just be a wrapper used to integrate it with TSL and should not change the enclosed wgsl code. At least, that’s the way I think it works.

Just a thought. I wonder if r179 is not correctly parsing the bracketed instructions in this line:

this.renderer.computeAsync(this.butterflyComp,[1,8,1]);

Although the display makes the computation look square (and it is typically displayed that way), that particular table is not square. I think it is supposed to be something like 512 x 8. But r179 does appear to correctly parse the command where there is not an Async.

Can you reproduce this issue with a fiddle somehow? If there is indeed a difference across versions, use the fiddle to report an issue at the GitHub repository.

I still feel that you should try initializing the backend before calling compute, and then use the sync method instead of the async. Have you tried that?

Looking at the source, it seems that it would definitely get rid of your error:

this sets the flag to true, and then the renderer doesnt complain and doesnt use the async thing.

These two statements are conflicting as Renderer.init is an async function, you would still need to wrap this in an async function and await the result of the promise returned from Renderer.init eg…

const initializeRenderer = async () => {
 
 // await for renderer to initialize =>
 await renderer.init()
 // call initialize program =>
 initializeProgram()

} 

otherwise there’s an unhandled race condition and the rest of the program would still continue to execute regardless of the result of Renderer.init which could resolve at any time in the programs processes…

Just to make sure I understand, what does it mean to ā€œinitialize the backendā€? The error notices indicate that using the renderer too soon will generate this error. So what processes need to initialized before using the renderer?

In most programs, it appears that this initialization is handled automatically without special commands. Why is this particular program different?

I think the messages above address some of those questions, but I just want to ask this explicitly in case I am missing something.