Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebGL device #40

Closed
2 tasks
emidoots opened this issue Nov 8, 2014 · 15 comments
Closed
2 tasks

WebGL device #40

emidoots opened this issue Nov 8, 2014 · 15 comments

Comments

@emidoots
Copy link

emidoots commented Nov 8, 2014

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

  • Add support for creating a WebGL canvas on a target HTML element ID (e.g. a div tag), via the gfx/window package.
  • Add a WebGL-based renderer.

Moved here from azul3d-legacy/issues#29

@dmitshur
Copy link

dmitshur commented Nov 8, 2014

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.

@emidoots
Copy link
Author

emidoots commented Nov 8, 2014

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?

@dmitshur
Copy link

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

emidoots pushed a commit that referenced this issue Nov 15, 2014
@emidoots
Copy link
Author

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

emidoots pushed a commit that referenced this issue Nov 19, 2014
@emidoots emidoots changed the title WebGL support WebGL device Dec 14, 2014
@dmitshur
Copy link

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{...}).

@emidoots
Copy link
Author

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.

@dmitshur
Copy link

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.

@emidoots
Copy link
Author

@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.

@dmitshur
Copy link

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.

dmitshur added a commit to shurcooL/play that referenced this issue Dec 26, 2014
Callbacks from JavaScript cannot be blocking.
Thanks @slimsag for the hint in
azul3d-legacy/gfx#40 (comment)!
@dmitshur
Copy link

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

@emidoots
Copy link
Author

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

That sounds like a great idea. 👍

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).

@dmitshur
Copy link

That sounds like a great idea.

Done at https://github.com/gopherjs/gopherjs/wiki/JavaScript-Gotchas. It can be removed if deemed unhelpful (because the information is available elsewhere); just trying it out to have a single page that lists bullet points with links to more info.

@dmitshur
Copy link

Made some progress on the demo above. Instead of rendering a single triangle, it now renders a million. See here for more info.

It's pretty impressive that it runs at roughly 30~ FPS on an iPad mini with Retina, and this is just the first cut. I'm sure there's still quite some room for optimization.

@emidoots emidoots closed this as completed Mar 6, 2016
@dmitshur
Copy link

dmitshur commented Mar 6, 2016

We've done some pretty cool things above. :)

It's interesting that I take Hover and eX0-go running in desktop/browser via same codebase for granted now, but this thread shows the progression and how it took collaboration, hard work, and multiple breakthroughs to make today's reality possible. Thanks again for help @slimsag! And @neelance for making it possible in the first place. ;)

@emidoots
Copy link
Author

emidoots commented Mar 6, 2016

😄 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants