Closed lucklove closed 6 months ago
Does anyone have a testing environment set up? What are the requirements?
If needed, we could write a window backend for the "web".
To compile piston to web, we should have emscripten installed. And the work to port to emscripten is simple since emscripten reimplement sdl1 and support sdl2 natively. In fact, I'm already on the way, but I need some time to make clear how to deal with the main loop of sdl. About how to compile rust to web, this will help.
I made a demo to show how to make rust and sdl2 work on web, hope it can help.
I'm getting this error:
$ cargo build --target asmjs-unknown-emscripten
Compiling web_demo v0.1.0 (file:///Users/sven/rust/web_demo)
error: linking with `emcc` failed: exit code: 1
|
= note: "emcc" "-L" "/Users/sven/.multirust/toolchains/nightly-x86_64-apple-darwin/lib/rustlib/asmjs-unknown-emscripten/lib" "/Users/sven/rust/web_demo/target/asmjs-unknown-emscripten/debug/deps/web_demo-ab888d76389d5a0c.0.o" "-o" "/Users/sven/rust/web_demo/target/asmjs-unknown-emscripten/debug/deps/web_demo-ab888d76389d5a0c.js" "-Wl,--gc-sections" "-nodefaultlibs" "-L" "/Users/sven/rust/web_demo/target/asmjs-unknown-emscripten/debug/deps" "-L" "/Users/sven/.multirust/toolchains/nightly-x86_64-apple-darwin/lib/rustlib/asmjs-unknown-emscripten/lib" "-Wl,-Bstatic" "-Wl,-Bdynamic" "/Users/sven/rust/web_demo/target/asmjs-unknown-emscripten/debug/deps/libsdl2-6d4b96e8b9dbc83c.rlib" "/Users/sven/rust/web_demo/target/asmjs-unknown-emscripten/debug/deps/libsdl2_sys-524b015e9982a43f.rlib" "/Users/sven/rust/web_demo/target/asmjs-unknown-emscripten/debug/deps/liblazy_static-5e13a0eed191ed30.rlib" "/Users/sven/rust/web_demo/target/asmjs-unknown-emscripten/debug/deps/librand-35167bebb3b301e7.rlib" "/Users/sven/rust/web_demo/target/asmjs-unknown-emscripten/debug/deps/libnum-351b1cddc85503c1.rlib" "/Users/sven/rust/web_demo/target/asmjs-unknown-emscripten/debug/deps/libnum_iter-618824a5957aaa8f.rlib" "/Users/sven/rust/web_demo/target/asmjs-unknown-emscripten/debug/deps/libnum_integer-9eb11829964f3c6e.rlib" "/Users/sven/rust/web_demo/target/asmjs-unknown-emscripten/debug/deps/libnum_traits-b3505e86c388a8aa.rlib" "/Users/sven/rust/web_demo/target/asmjs-unknown-emscripten/debug/deps/libbitflags-8f04bbe98b78ab83.rlib" "/Users/sven/rust/web_demo/target/asmjs-unknown-emscripten/debug/deps/libemscripten-4877834e79cf9ec4.rlib" "/Users/sven/rust/web_demo/target/asmjs-unknown-emscripten/debug/deps/libemscripten_sys-c4cd19d68d42016c.rlib" "/Users/sven/rust/web_demo/target/asmjs-unknown-emscripten/debug/deps/liblibc-38a11e2c014183dd.rlib" "/Users/sven/.multirust/toolchains/nightly-x86_64-apple-darwin/lib/rustlib/asmjs-unknown-emscripten/lib/libstd-17342542cc541012.rlib" "/Users/sven/.multirust/toolchains/nightly-x86_64-apple-darwin/lib/rustlib/asmjs-unknown-emscripten/lib/librand-46ed9b788a6928f6.rlib" "/Users/sven/.multirust/toolchains/nightly-x86_64-apple-darwin/lib/rustlib/asmjs-unknown-emscripten/lib/libcollections-e32369d7fef31fbf.rlib" "/Users/sven/.multirust/toolchains/nightly-x86_64-apple-darwin/lib/rustlib/asmjs-unknown-emscripten/lib/librustc_unicode-844a33a197b559a5.rlib" "/Users/sven/.multirust/toolchains/nightly-x86_64-apple-darwin/lib/rustlib/asmjs-unknown-emscripten/lib/libpanic_unwind-f78756b576499725.rlib" "/Users/sven/.multirust/toolchains/nightly-x86_64-apple-darwin/lib/rustlib/asmjs-unknown-emscripten/lib/libunwind-11f7709e0c71505b.rlib" "/Users/sven/.multirust/toolchains/nightly-x86_64-apple-darwin/lib/rustlib/asmjs-unknown-emscripten/lib/liballoc-24699c1ddb055eb0.rlib" "/Users/sven/.multirust/toolchains/nightly-x86_64-apple-darwin/lib/rustlib/asmjs-unknown-emscripten/lib/liballoc_system-3e467e865c8fa572.rlib" "/Users/sven/.multirust/toolchains/nightly-x86_64-apple-darwin/lib/rustlib/asmjs-unknown-emscripten/lib/liblibc-60365c932e50e382.rlib" "/Users/sven/.multirust/toolchains/nightly-x86_64-apple-darwin/lib/rustlib/asmjs-unknown-emscripten/lib/libcore-d9873b515905cac5.rlib" "/Users/sven/.multirust/toolchains/nightly-x86_64-apple-darwin/lib/rustlib/asmjs-unknown-emscripten/lib/libcompiler_builtins-e428224f6caf212a.rlib" "-l" "SDL2" "-l" "c" "-s" "USE_SDL=2" "-s" "ERROR_ON_UNDEFINED_SYMBOLS=1"
= note: WARNING:root:LLVM version appears incorrect (seeing "7.3", expected "3.9")
CRITICAL:root:fastcomp in use, but LLVM has not been built with the JavaScript backend as a target, llc reports:
===========================================================================
(no targets could be identified: [Errno 2] No such file or directory)
===========================================================================
CRITICAL:root:you can fall back to the older (pre-fastcomp) compiler core, although that is not recommended, see http://kripken.github.io/emscripten-site/docs/building_from_source/LLVM-Backend.html
INFO:root:(Emscripten: Running sanity checks)
CRITICAL:root:Cannot find /usr/bin/llvm-link, check the paths in ~/.emscripten
Did you do something special to get LLVM 3.9?
Do you have llvm-link in your PATH? my llvm-link is at ${emsdk_portable}/clang/fastcomp/build_incoming_64//bin/llvm-link
, and I have these lines to setup my PATH:
PATH+=:${emsdk_portable}
PATH+=:${emsdk_portable}/clang/fastcomp/build_incoming_64/bin
PATH+=:${emsdk_portable}/node/4.1.1_64bit/bin
PATH+=:${emsdk_portable}/emscripten/incoming
The ${emsdk_portable}
is where your emsdk at.
I guess it used your native llvm(by mistake) instead of the one in ${emsdk_portable}
directory.
It works (OSX)! I copied "emsdk_portable" to "/Applications" and made the following changes:
~/.bash_profile
:
PATH="/Applications/emsdk_portable/emscripten/incoming":"$PATH"
~/.emscripten
:
# this helps projects using emscripten find it
EMSCRIPTEN_ROOT = os.path.expanduser(os.getenv('EMSCRIPTEN') or '/Applications/emsdk_portable/emscripten/incoming') # directory
LLVM_ROOT = os.path.expanduser(os.getenv('LLVM') or '/Applications/emsdk_portable/clang/fastcomp/build_incoming_64/bin') # directory
BINARYEN_ROOT = os.path.expanduser(os.getenv('BINARYEN') or '/Applications/emsdk_portable/node/4.1.1_64bit/bin') # directory
FYI: I've created a piston project that can be compiled into both native and web. https://github.com/gifnksm/game-of-life-rs
This project uses piston2d-opengl_graphics
with small patch and piston-shaders_graphics2d
with WebGL shader support.
I initially tried using piston_window
with SDL2 backend, but it didn't run because piston_window
always sets WindowSettings::srgb
is true
(SDL2 seems not to work with SRGB).
@gifnksm You should post this to https://www.reddit.com/r/rust_gamedev/!
I'd like to do some research/testing to see if it is possible to implement window and input using web_sys
or stdweb
targeting wasm32-unknown-unknown
. I've been struggling for over a year now trying to get things compiling/running with Emscripten/SDL2; I've had some successes and the cross-platform compatibility of SDL is nice, but it's way too much effort for me to maintain. A lot of the problems have been caused by compatibility issues with the Emscripten SDK, and I would love to be able to move away from it. It was great for initial support and testing of WebAssembly, but in my personal experience it is very difficult to set up the build environment correctly.
It appears that piston
(core crates) and piston2d-opengl_graphics
both compile successfully on the wasm32-unknown-unknown
target, so that is a good start. AFAICT, the gl
crate has WebGL bindings as well. I'll do some more research and see how far I can get.
It's been less than a day, and I've already travelled deeper into the rabbit hole than I ever could have anticipated. There is so much more to it than writing a window library for stdweb; we will also need to do some work to support WebGL directly.
The issue that I ran into immediately is that gl
doesn't actually support WebGL, at least not directly. WebGL isn't compatible with the way that OpenGL functions are typically loaded - using raw function pointers returned by the loader's getProcAddress
function - as it is a JS API. To get around this, Emscripten defines their own C bindings that delegate to the WebGL API, so they can then return pointers to those functions in their own getProcAddress
function. This is the compatibility layer that makes the gl
crate work, which is built around this loader pattern.
These are the options I see moving forward:
"Cherry-pick" the compatibility layer and omit the rest of the Emscripten SDK, allowing us to use the emscripten_GetProcAddress
binding provided via emscripten-sys
. This might work and would be the easiest solution in the short term, but I don't know if I like it.
Write our own layer that essentially does the same thing as above. I'm not very familiar with bindgen-like tools, but it seems possible to write our own generator for webgl_generator
to create extern "C" fn
s and a get_proc_address
function.
Write a graphics implementation based on WebGL bindings instead of OpenGL. This will likely take the most effort and will require a bit more work from the end user in the case that cross-compatibility is desired, but it could potentially be the cleanest solution with the fewest layers between piston2d and the calls to GL.
I think I'm going to focus my efforts around number 2 for now, but I do think number 3 could also be promising for the long run, if anybody would be interested in looking into it.
While I was writing this, I also found out that gfx-backend-gl
(the HAL-based implementation) recently added support for WebGL using glow
, which is a relatively new crate that provides a unified API for GL, GLES and WebGL. I'm not totally familiar with Piston's support for gfx, but I'm pretty sure it is for pre-LL so unfortunately it's not compatible. Maybe in the long term, it would make sense to port opengl_graphics
to glow
?
I have an update! I spent the last week developing bindings for WebGL, and I think I have a solution that is at least presentable.
At first I tried to generate the bindings using a combination of gl_generator
and webgl_generator
- it sounded nice since a lot of the GL functions have very similar functions, but there are also several that are unique and require special cases. After a couple of days messing around with this idea, I settled on writing the bindings by hand using webgl-stdweb
. It's only a few hundred functions and I got the easy ones done within a day, but there are some more complex ones that took quite a while longer.
I've uploaded it to a repository: webgl_loader so you can take a look at it, but I'm not confident in its correctness/soundness, and I still have to do a couple more passes to clean it up:
Moving uses of unsafe
into "safe" abstractions, or at least as safe as I can make them in GL. There are some parts of the API that assume the user upholds the contract that might otherwise cause UB. For example, there are parts of the API where the user must provide a sufficiently-sized buffer where the size is well-defined by the OpenGL specifications and is not passed with the pointer itself.
Even if that is technically still unsafe, I'd prefer to not litter the codebase with unsafe
all the way up to the binding implementations themselves as that would make it much harder to audit. Rather, I'd assume that any contract with the user of the API is upheld and document those invariants where unsafe is used internally. That seems most readable to me, but I'm open to hearing other opinions.
I do think there might be some benefit to keeping some implementation details unsafe - especially the user-provided buffers that I mentioned before. For those, the buffer size has to be specified per-binding; in those cases, unsafe
might help us explicitly recognize, document (how it is calculated, why it is as such, and links to standard specifications) and verify them.
(Re)documenting the implementation details. There is a significant amount of code that works alongside the function delegations themselves; for example, managing a mapping between integers and JavaScript object references for handles to buffers/programs.
Cleaning up the implementations themselves. It's a bit scattered at the moment because I wanted to do a simple, big-picture implementation and I implemented features as-needed. Now that I see everything at the same time, I feel like I can easily go back and make it cleaner.
I don't know what the status of this is now, but I saw mention of gl
not having WebGL bindings and glow
( GL on Whatever ) could be really useful for that ( if I understand the problem correctly ).
Another note about Window
implementations. If piston
is going to support web "natively", we need to overhaul the event loop API just like winit
has done in 0.20. See rust-windowing/winit#459 for the detail of their work.
In short, we can't implement Window::poll_event
or wait_event
for web browsers, because we can't dequeue an event from a browser's event loop. It is meant to be run by the browser itself, not by our user code.
And in fact, the example above (https://github.com/gifnksm/game-of-life-rs) defines two different event_loop::run
functions for native and web, wihch can be switched by cfg
. The native one simply goes with a familiar dequeuing code while let Some(e) = events.next(&mut window) { ... }
, but the web one doesn't use any parts of the piston
's event loop system. Instead it tells a browser to run a event handler with help of empscripten_sys::emscripten_set_main_loop_arg
. It would be really awkward to write these codes manually, so I hope it can be handled inside the piston
library.
I opened https://github.com/PistonDevelopers/piston/issues/1398 for web event loop.
Closing.
Since rust nightly has support asmjs and wasm, maybe It's possible that we can use piston to write web game in the soon future.