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

Emscripten support. #884

Open
agausmann opened this issue Apr 27, 2019 · 26 comments
Open

Emscripten support. #884

agausmann opened this issue Apr 27, 2019 · 26 comments

Comments

@agausmann
Copy link
Contributor

agausmann commented Apr 27, 2019

wasm32-unknown-emscripten is a great target for compiling and running games using WebAssembly, particularly because it can produce web pages that run on most browser platforms instead of having to maintain and ship binaries for Windows, Linux, etc. separately.

There is a port of SDL2 officially maintained by the emscripten project. I have been using it off and on for about a year, bundling and statically linking it with this crate behind pistoncore-sdl_window

You can see the progress I've made on my rust-sdl2 fork, though it is hacked together and breaks other targets in its current state, that can theoretically be fixed. I'd love to eventually get it cleaned up and merged upstream.

@tanis2000
Copy link
Contributor

@agausmann if you're still interested I've got rust-sdl2 partially working with Emscripten. My prototype at https://github.com/tanis2000/minigame-rust/tree/webgl is actually compiling to wasm through Emscripten but I've still got an issue with the SDL_Renderer that seems to be invalid in the browser for some unknown reason.

The error I get is the following:

thread 'main' panicked at 'Invalid renderer', /Users/tanis/Documents/tanis2000-rust-sdl2/src/sdl2/render.rs:912:13

Maybe you already stumbled upon it and know why it's happening?

@hikari-no-yume
Copy link
Contributor

As I unfortunately discovered here, that this crate wants to link against SDL2 (i.e. links = "SDL2" which results in -lSDL2) means it doesn't “just work” for newer Emscripten SDKs, at least for me. If it weren't for that it would at least partially work out-of-the-box.

@Cobrand
Copy link
Member

Cobrand commented Oct 16, 2019

@hikari-no-yume This is extremely simple to fix, remove links = "SDL2" https://github.com/Rust-SDL2/rust-sdl2/blob/master/sdl2-sys/Cargo.toml (which has been an issue of dual -lSDL2 output for quite some time), and fix the function link_sdl2 whenever a target has -lSDL2 missing in sdl2-sys's build.rs

However, this line https://github.com/Rust-SDL2/rust-sdl2/blob/master/sdl2-sys/build.rs#L304 hints that someone was actually able to run this as emscripten, but I could be wrong?

@hikari-no-yume
Copy link
Contributor

@Cobrand Indeed it's a trivial fix, I might contribute a pull request with that at some point (once I can, unfortunately can't for reasons). I think that either a previous Emscripten toolchain or a previous version of this crate worked, just not any more.

@tanis2000
Copy link
Contributor

@Cobrand that line in build.rs comes from me and yeah it used to work with Emscripten back then when I made that change. I haven't been using Emscripten lately so I don't know if one of the latest releases changed anything in their toolchain.

@bsurmanski
Copy link
Contributor

Emscripten targeting should be fixed by #937

But on Windows specifically, there is still an issue when building the emscripten ports for SDL_mixer. I think it's using a 32bit binary when it expected a 64bit or vice versa. It gives an error of the form "%1 is not an application" or something like that. Workaround for now: build on OSX/Linux ¯_(ツ)_/¯

@hikari-no-yume
Copy link
Contributor

hikari-no-yume commented Nov 4, 2019

@bsurmanski unfortunately, that does not fix it for me:

          error: undefined symbol: SDL_CreateWindow
          warning: To disable errors for undefined symbols use `-s ERROR_ON_UNDEFINED_SYMBOLS=0`
          error: undefined symbol: SDL_GL_CreateContext
          Error: Aborting compilation due to previous errors
          shared:ERROR: '/Users/ajf/Apps/emsdk/node/12.9.1_64bit/bin/node /Users/ajf/Apps/emsdk/fastcomp/emscripten/src/compiler.js /var/folders/2n/tv3h_lg12f97hnc070p3dxqh0000gn/T/tmpQN5vpn.txt /Users/ajf/Apps/emsdk/fastcomp/emscripten/src/library_pthread_stub.js' failed (1)

I think this library needs to tell emscripten USE_SDL=2 itself.

@hikari-no-yume
Copy link
Contributor

hikari-no-yume commented Nov 4, 2019

Ah, but while it doesn't tell emscripten to use its SDL2 port, it does appear fix the -l SDL2 problem, so it's progress. Thank you!

@bsurmanski
Copy link
Contributor

How are you targeting emscripten? It works for me if I use Cargo Web. Creating a Web.toml will allow you to pass in the USE_SDL=2 flags. See: https://github.com/koute/cargo-web

@hikari-no-yume
Copy link
Contributor

I'm not using Cargo web, just plain cargo. But I'll check that out, it sounds useful.

@agausmann
Copy link
Contributor Author

Even without cargo-web, it is possible:

.cargo/config

[target.wasm32-unknown-emscripten]
rustflags = [
    "-C", "link-arg=-s", "-C", "link-arg=USE_SDL=2",
]

@hikari-no-yume
Copy link
Contributor

I'm doing exactly that right now, but it feels like a hack. I don't know enough to think of what a better way would be though.

@tlively
Copy link

tlively commented Jan 2, 2020

What's the state of Emscripten support now? Has anyone tried using this since Rust switched away from fastcomp to upstream as its Emscripten backend?

@bsurmanski
Copy link
Contributor

bsurmanski commented Jan 2, 2020

IMO it needs more work. I just tried it right now on OSX, and the last few times I tried (this time included) the build broke on some LLVM IR issue. For example, I tried to compile nalgebra from git. Using the stable toolchain (1.40.0 - 2019-12-16) and it segfaulted compiling the num_integer package. Using the nightly toolchain (1.42.0 - 2020-01-01), I got the following error:

error: array lengths can't depend on generic parameters --> /Users/bsurmanski/.cargo/registry/src/github.com-1ecc6299db9ec823/matrixmultiply-0.2.3/src/sgemm_kernel.rs:223:40 | 223 | let mut ab = [_mm256_setzero_ps(); MR]; |

If I remember correctly, things worked better before the fastcomp->upstream transition; but maybe I just got lucky when I was using it then (around last January).

Proccess Details:
I installed emsdk by doing the following:
clone emsdk into a local directory.
run emsdk install latest && emsdk activate latest
run source ./emsdk_env.sh
in the same terminal run cargo build --target=wasm32-unknown-emscripten on the target package

I'm on OSX 10.14.6

@tlively
Copy link

tlively commented Jan 2, 2020

Looks like you're running into rust-lang/rust#66308. I wasn't aware that this issue was present when targeting wasm32-unknown-emscripten in addition to asmjs-uknown-emscripten, though. That's not great. I may have to look into that sooner rather than later. Thanks for the update!

@Cobrand
Copy link
Member

Cobrand commented Jan 2, 2020

num-integer is not required anymore on master. Try rebuilding it using the latest git version?

@bsurmanski
Copy link
Contributor

@Cobrand: I was using git master. looks like num-integer is a dependency of num-rational (which is still a dependency of nalgebra).

@leocavalcante
Copy link

leocavalcante commented Jan 8, 2020

Finally! I was able to build this study-case snake game using this fork without lazy_static (I created an issue there to see why it doesn't compiles to wasm) and downgrading Emscripten SDK because of this issue. Hope it helps.

Edit:
Well, not so fast. It starts and even resizes the canvas and sets its background to black, but then fails with thread 'main' panicked at 'Invalid renderer'. If someone knows how to proceed after that....

https://github.com/Rust-SDL2/rust-sdl2/blob/master/src/sdl2/render.rs#L910

It sets the background, but for some reason the ret is not 0, then it fails.

@Cobrand
Copy link
Member

Cobrand commented Jan 9, 2020

I think the renderer support is a WIP for emscripten. Perhaps you can try to use manual OpenGL calls or whatever emscripten uses as a video driver? If someone managed to use the SDL2 renderer with emscripten, then this is probably coming from this crate, but since it's basically a one to one translation of the C version, I doubt it's coming from this crate...

@leocavalcante
Copy link

Sorry, I'm a total newbie and I'm not sure what you mean, but from what I searched for, the support of Emscripten is fairly great for SDL2.
My guess is that maybe this Rust bidings are not fully aware about the Emscripten port (?)
From what I understood, when o pass -C link-arg=USE_SDL=2 it fowards USE_SDL=2 to emcc linking process, which makes it uses the port.

@hikari-no-yume
Copy link
Contributor

hikari-no-yume commented Jan 9, 2020

I've used the SDL2 renderer before in C code which I compiled with emscripten, and it could at the very least do a background clear colour and draw sprites. So I wonder why it wouldn't work in Rust, then?

@danielrh
Copy link
Contributor

danielrh commented Aug 26, 2020

ok so I got to the bottom of this issue.... the challenge lies in the way the current rust-sdl2 API requires some stack variables to track lifetimes. Since you can't have self-referential structs, you're forced to keep things like the texture_creator on the stack.
However, when calling emscripten_set_main_loop you're presented with the void emscripten_set_main_loop(fb func, int fps, int simulate_infinite_loop) options
from my experimentation simulate_infinite_loop causes problems with the calling convention, so we're forced to unwind the stack.
My current solution for safely unwinding the stack is to allocate every variable I care about inside a Box
Then I can std::mem::forget each box, and when main returns, no variables on the stack are still referenced.
https://github.com/danielrh/stamps/blob/master/src/bin/stamps.rs#L904

I wish there were a better solution: can we avoid the texture_creator paradigm and somehow keep all the texture_creators easily accessible from the canvas?

I think splitting part of the canvas state into texture creator prevents us from bundling all the variables into a struct. Ideally Textures would have their own safe way of being found using just a Canvas/TextureCreator and linking lifetimes together.
i.e. any canvas could have a get_texture(texture_ref) function that would borrow the Canvas until the operation was complete...that way we could avoid tying together lifetimes and making structs difficult to persist and encapsulate.

@Cobrand
Copy link
Member

Cobrand commented Aug 26, 2020

I think ironically using global variables would be cleaner than using mem::forget on boxes. I don't see any other way a language like C or C++ can otherwise share data between the calls of the loop, aside from global variables. It's dirty but to be expected when working with libraries designed with only C in mind.

i.e. any canvas could have a get_texture(texture_ref) function that would borrow the Canvas until the operation was complete...that way we could avoid tying together lifetimes and making structs difficult to persist and encapsulate.

That would either require a self-referential struct, (e.g. struct { canvas: Canvas, Hashmap<Id, Texture<'canvas>> }) or the textures "stored" directly in the Canvas, which we would like to avoid (remember that while we make it a bit easier, this crate is mainly designed to be as close as the original SDL2 as possible API-wise).

A tricky problem for sure.

@lwb4
Copy link

lwb4 commented Oct 14, 2020

@danielrh Thanks for posting with your idea -- I studied your code and tried to implement it in the most basic possible form so the solution is reproducible, but it still gives me the pernicious "thread 'main' panicked at 'Invalid renderer'" error.

Would you mind glancing at this gist and helping me figure it out?

EDIT: Figured it out -- I was missing an asterisk before the first "arg" on line 92.

@coderedart
Copy link

sdl2 crate builds on emscripten properly. needs a couple emscripten rust flags in .cargo/config though and set raw-window-handle properly.

for a minimal example, https://github.com/coderedart/rust-sdl2-wasm

live version with opengl (glow + sdl2), https://coderedart.github.io/rust-sdl2-wasm/

There are some problems like sdl2 canvas not working, but idc about those. just wanted a window so i can start drawing with glow (and maybe wgpu eventualy).

@lexi-the-cute
Copy link

lexi-the-cute commented Jun 17, 2023

Where I'm at, I'm able to get it to draw the graphics when I set the code to the below.

    debug!("Starting Render Loop...");
    /*
     * Intentionally not targetting feature "browser" here
     *   as emscripten is multi-platform.
     */
    // #[cfg(all(target_family="wasm", target_os="emscripten"))]
    // unsafe {
    //     emscripten_set_main_loop_arg(render_loop, Box::from(&mut loopstruct), 0, 1);
    // }
    
    // #[cfg(not(all(target_family="wasm", target_os="emscripten")))]
    loop {
        let exit_loop: bool = render_loop(Box::from(&mut loopstruct));
        if exit_loop {
            // Ending Loop
            break;
        }

        // Slow Down Rendering (60 FPS)
        // thread::sleep(Duration::new(0, 1_000_000_000u32 / 60));
    }

Screenshot_20230617_014735


When I set the code to the below, it only gets to the point of drawing the cyan screen before it crashes on the line emscripten_set_main_loop_arg(render_loop, Box::from(&mut loopstruct), 0, 1);. It finds that GL.currentContext is actually an int and not Object { handle: 2321496, attributes: {…}, version: 1, GLctx: WebGLRenderingContext, initExtensionsDone: true, maxVertexAttribs: 16, clientBuffers: (16) […], tempVertexBufferCounters1: (22) […], tempVertexBufferCounters2: (22) […], tempVertexBuffers1: (22) […], … }.

I create and attempt to read Gl.currentContext on the render thread, but the render thread gets the context handle as an integer and the main thread gets the actual context.

    /*
     * Intentionally not targetting feature "browser" here
     *   as emscripten is multi-platform.
     */
    #[cfg(all(target_family="wasm", target_os="emscripten"))]
    unsafe {
        emscripten_set_main_loop_arg(render_loop, Box::from(&mut loopstruct), 0, 1);
    }
    
    #[cfg(not(all(target_family="wasm", target_os="emscripten")))]
    loop {
        let exit_loop: bool = render_loop(Box::from(&mut loopstruct));
        if exit_loop {
            // Ending Loop
            break;
        }

        // Slow Down Rendering (60 FPS)
        thread::sleep(Duration::new(0, 1_000_000_000u32 / 60));
    }

Screenshot_20230614_220326

Be warned, the example that "works" can cause epileptic seizures and doesn't take user input for some reason

If you want to see my current work, it's at https://github.com/alexisart/catgirl-engine/blob/9e4e1ed06ab295a86c04719a0c805c922b80bc20/src/game/render.rs#L162-L181

I'll upload the Emscripten build of the engine as soon as the CI finishes building it

Edit: Here's the promised build. CatgirlEngine-Linux-WebAssembly-Emscripten-32-Zip.zip

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