tinygo-org / tinygo

Go compiler for small places. Microcontrollers, WebAssembly (WASM/WASI), and command-line tools. Based on LLVM.
https://tinygo.org
Other
15.44k stars 911 forks source link

wasm - [question] example requestAnimationFrame loop #1907

Open ghost opened 3 years ago

ghost commented 3 years ago

So I had quite a complex thread safe WAF loop using channels, but then realised it was overkill with javascript being mostly single threaded. And anyway it seemed to cause a lot of resume() calls due to a select with 6 channel reads. So I simplified the code now, but it still seems to use a lot of CPU even when rendering a simple scene with a clearRect and fillRect (every frame). I was just looking for some input please on if I am doing something obviously wrong in my animation loop?

tinygo version 0.15.0 darwin/amd64 (using go version go1.15.3 and LLVM version 10.0.1) macos 10.12.4 firefox 68.12

Also is using a chan like this the best way to prevent the wasm reaching the end of main and terminating?

    keepaliveC := make(chan interface{})
    animator.frameCallback = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
        if animator.quit {
            keepaliveC <- struct{}{}
        }
        frameMS := args[0].Int()
        if !animator.paused {
            for _, scene := range animator.scenes {
                scene.Draw(frameMS)
            }
        } else {
            //TODO a sleep?
        }
        js.Global().Call("requestAnimationFrame", animator.frameCallback)
                //here I do some none rendering stuff like check WebSockets etc
        return nil
    })

    js.Global().Call("requestAnimationFrame", animator.frameCallback)

    fmt.Println("animator::waiting @keepalive")
    <-keepaliveC //Blocking
    animator.frameCallback.Release()
ghost commented 3 years ago

OK, so I ported my canvas2D code over to WebGL (using the canvas2D as a texture). CPU usage went down from 60% to 5%, but then as I started adding more UI features back in, the CPU usage was going up quite fast. But I no longer suspect the animation loop, and that's why I posted back.

I was also thinking for a time that the overhead on wasm/js bridge was too high, but I am pretty sure that is also not the problem here. I think actually I am seeing fill rate limitations. Pretty sure firefox is using a hardware surface for rendering, and the GPU should handle this "Radeon Pro 460 4096 MB". So my latest theory is that this is a bug in macos, since I see WindowServer CPU usage starting to spike. I found a couple threads talking about a possible bug in scaled 4K support... Anyway, I just wanted to post back some details in case someone else goes down this route. Fell free to close this topic.

aykevl commented 3 years ago

It's hard to say exactly what is causing the performance drop. Both Firefox and Chrome have a built-in profiler, which you could try to use (so far I found the Chrome profiler easier to use).

ghost commented 3 years ago

Thanks. I tried with Firefox performance tools, and can see 80% of the time in texImage2D. I am using canvas (2D) to do some drawing, text, and SVG->Image()->'drawImage'. I then use 'texImage2D' with the canvas2D as a source: ie this webgl2 method:

void gl.texImage2D(target, level, internalformat, width, height, border, format, type, HTMLCanvasElement source);

I was expecting a GPU-GPU blit (canvas->texture) using the approach, i.e. fast enough that I should be able to to this on every single frame... That does not seem to be the case. Of course if this is triggering a read-back on a 4K canvas it would explain a lot :) Thanks for the tip, maybe I have to look for an alternative approach.

ghost commented 3 years ago

I just had a thought that it might be pixel format, but I am using RGBA for the texture, and pretty sure canvas is the same.

    gl.TexImage2D(TEXTURE_2D, 0, RGBA, RGBA, UNSIGNED_BYTE, &PixelSource{canvas.canvas})
    gl.TexParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, LINEAR)
    gl.TexParameteri(TEXTURE_2D, TEXTURE_WRAP_S, CLAMP_TO_EDGE)
    gl.TexParameteri(TEXTURE_2D, TEXTURE_WRAP_T, CLAMP_TO_EDGE)
    gl.TexParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, NEAREST)
    gl.TexParameteri(TEXTURE_2D, TEXTURE_MAG_FILTER, NEAREST)

Anyway, I also realised this is now more an issue with my understanding of canvas & WebGL, and unrelated to tinygo. So apologies for the noise, and thanks for a great tool!