azul3d / engine

Azul3D - A 3D game engine written in Go!
https://azul3d.org
Other
614 stars 52 forks source link

WebGL device #100

Open azul3d-bot opened 8 years ago

azul3d-bot commented 8 years ago

Issue by slimsag Saturday Nov 08, 2014 at 06:15 GMT Originally opened as https://github.com/azul3d/gfx/issues/40


For WebGL to be supported by the graphics package we need to:

Moved here from https://github.com/azul3d/issues/issues/29

azul3d-bot commented 8 years ago

Comment by shurcooL Saturday Nov 08, 2014 at 06:22 GMT


FWIW go.mobile/app has also invested in stealing the main loop:

FWIW, I'm starting to realize that might be the way to go.

It's only the desktop where it's feasible to take full control and do a busy loop to do what you want with full control. On a platform like the web, you have to play by its rules, because it's a more complicated ecosystem, and the browser can probably do a better job of telling you what to do, than you figuring it out.

Consider situations where the tab is inactive, or your viewport is offscreen, etc. It might make more sense to have the browser ask you to render frames when appropriate, rather than you trying to render them and figure out when to sleep longer, etc.

Just some thoughts.

azul3d-bot commented 8 years ago

Comment by slimsag Saturday Nov 08, 2014 at 10:41 GMT


It's only the desktop where it's feasible to take full control and do a busy loop to do what you want with full control.

You can do it on desktop, mobile, and the web. The difference is that we won't be busy waiting (I despise busy-waiting API's honestly, and I think given Go's nature is a horrible thing to do).

On a platform like the web, you have to play by its rules, because it's a more complicated ecosystem, and the browser can probably do a better job of telling you what to do, than you figuring it out.

Your comment actually got me very curious how we could implement it on the web. So I've taken the time to look into if my plans here would work with the web well, and I think it will (see below link).

Consider situations where the tab is inactive, or your viewport is offscreen, etc. It might make more sense to have the browser ask you to render frames when appropriate, rather than you trying to render them and figure out when to sleep longer, etc.

Absolutely. This is similar to how GLFW's swap buffers command pauses for a vertical refresh, but a bit different:

Instead of the command stalling (which goes against Javascript, somewhat excessive, use of callbacks) you pass in a function to be invoked at the next frame via window.requestAnimationFrame.

The basic plan is:

  1. Window spawns a seperate goroutine, which requests a animation frame.
  2. Main loop doesn't busy wait because it is reading from a channel.
  3. When a animation frame is received, a closure (full of e.g. OpenGL commands) to render the frame is built and sent over the channel.
  4. The main loop receives the closure function, and executes it.
    • When the closure executes, it asks for another animation frame from the browser (see 1).

I've made a dumb-go-version of that RequestAnimationFrame function in Go which simply pauses for a second and then calls it's function argument, I tested it on the Gopherjs playground and it works well:

https://gist.github.com/slimsag/0b7c40e8fb6b566e491f

What do you think?

azul3d-bot commented 8 years ago

Comment by shurcooL Monday Nov 10, 2014 at 11:05 GMT


I think that's pretty fantastic! But I'll have to play with it later, when I have more time.

azul3d-bot commented 8 years ago

Comment by slimsag Sunday Nov 16, 2014 at 21:19 GMT


For those on-looking this in the future, I've outlined the benefits of this approach of managing the main thread/loop in this blog post:

http://slimsag.blogspot.com/2014/11/go-solves-busy-waiting.html

azul3d-bot commented 8 years ago

Comment by shurcooL Thursday Dec 25, 2014 at 22:18 GMT


But I'll have to play with it later, when I have more time.

Playing with this right now. :D (With the specific goal that I want to be able to run Hover on desktop and in browser on my iPad).

I have two code paths (implemented via 2 packages or a single package with 2 build tags), one for desktop/OpenGL/gc and another for browser/WebGL/GopherJS.

I've realized it makes things a lot easier to reason about if I think of the browser path as originating from backend Go that hosts the frontend. The desktop path (for now) simply opens a glfw window and proceeds from there. The browser path sets up a server that hosts the index.html and compiled (via GopherJS) script.go file, and opens a browser window that navigates to the hosted index.html page.

That way I could actually implement a wrapper for glfw that has a 2 backends (desktop and browser), and CreateWindow would actually be responsible for generating the html by doing things like:

var document = dom.GetWindow().Document().(dom.HTMLDocument)
canvas := document.CreateElement("canvas").(*dom.HTMLCanvasElement)

Having control over both the backend (serving resources, generating index page html) and frontend (GopherJS code that runs in the browser) seems to be a great idea so far.

Now, I'm getting closer to trying out your idea of using channels so that the main package can retain main() control instead of delegating it to an app.Run(app.Callbacks{...}).

azul3d-bot commented 8 years ago

Comment by slimsag Thursday Dec 25, 2014 at 22:35 GMT


Nice! I think for gfx/window we'll have a simple ID assignment, perhaps:

props := window.NewProps()
props.SetHTMLCanvas("mycanvas")
window.Run(gfxLoop, props) // just the easy/simple way to start the app.

I've done a lot more research over the past two weeks into an OpenGL ES and WebGL device -- Since WebGL 1/2 was based on OpenGL ES 2/3 I believe we can expose just two simply device packages: gfx/gles and gfx/webgl.

WebGL 2 and OpenGL ES 3 are both just functionally extensions on top of the existing WebGL 1 / OpenGL ES 2 API's, so we could have the devices start out by trying the latest (2/3) and then falling back to the standard (1/2) versions.

azul3d-bot commented 8 years ago

Comment by shurcooL Friday Dec 26, 2014 at 08:58 GMT


The basic plan is:

  1. Window spawns a seperate goroutine, which requests a animation frame.
  2. Main loop doesn't busy wait because it is reading from a channel.
  3. When a animation frame is received, a closure (full of e.g. OpenGL commands) to render the frame is built and sent over the channel.
  4. The main loop receives the closure function, and executes it.
    • When the closure executes, it asks for another animation frame from the browser (see 1).

I've made a dumb-go-version of that RequestAnimationFrame function in Go which simply pauses for a second and then calls it's function argument, I tested it on the Gopherjs playground and it works well:

https://gist.github.com/slimsag/0b7c40e8fb6b566e491f

I've tried that and I got it to work using time.Sleep(time.Second) and time.Sleep(16 * time.Millisecond), but I couldn't get it to work using the browser's own requestAnimationFrame. I kept getting:

Uncaught Error: not supported by GopherJS: non-blocking call to blocking function (mark call with "//gopherjs:blocking" to fix)

As far as I understand, using requestAnimationFrame is a must for smooth and efficient animation in the browser. Any ideas?

Edit: More precisely, I couldn't get requestAnimationFrame to work while using channels to maintain main loop. I could easily get requestAnimationFrame to work if I just used it the traditional JavaScript way of setting a callback.

azul3d-bot commented 8 years ago

Comment by slimsag Friday Dec 26, 2014 at 14:01 GMT


@shurcooL

AFAIK the problem lies in the fact that JS cannot call back into Go functions if they are blocking. For this reason you'd have to have whatever function requestAnimationFrame is calling be non-blocking.

I updated the gist to have renderFrame spawn a goroutine itself -- I haven't looked into the performance of this yet (you certainly don't want to do this with the gc compiler) -- but it does work.

azul3d-bot commented 8 years ago

Comment by shurcooL Friday Dec 26, 2014 at 22:26 GMT


AFAIK the problem lies in the fact that JS cannot call back into Go functions if they are blocking. For this reason you'd have to have whatever function requestAnimationFrame is calling be non-blocking.

I forgot about, thanks! I should add a wiki page that outlines all of the JS gotchas on one page.

azul3d-bot commented 8 years ago

Comment by shurcooL Saturday Dec 27, 2014 at 00:00 GMT


Sorry if this is a little off topic, but I was able to get the main loop of the desktop path and browser path to be essentially identical! And it really works. IMO this is incredibly cool. ;D

image

(I've now implemented SetFramebufferSizeCallback for browser backend too, leaving just the GL context creation really as the only remaining difference to be abstracted away, but that is easy to do now.)

Here it is running as a desktop app:

image

And inside the browser:

image

azul3d-bot commented 8 years ago

Comment by slimsag Saturday Dec 27, 2014 at 03:04 GMT


I should add a wiki page that outlines all of the JS gotchas on one page.

That sounds like a great idea. :+1:

Sorry if this is a little off topic, but I was able to get the main loop of the desktop path and browser path to be essentially identical! And it really works. IMO this is incredibly cool. ;D

I think it's pretty on-topic (and cool!), actually. It's a strong indicator that this pattern will work well in lieu of an actual implementation for us here (thus far).