TranslateProject/sources/talk/20181220 D in the Browser with Emscripten, LDC and bindbc-sdl (translation).md
DarkSun e36bf9af88 选题: 20181220 D in the Browser with Emscripten, LDC and bindbc-sdl (translation)
sources/talk/20181220 D in the Browser with Emscripten, LDC and bindbc-sdl (translation).md
2019-07-09 17:26:40 +08:00

277 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[#]: collector: (lujun9972)
[#]: translator: ( )
[#]: reviewer: ( )
[#]: publisher: ( )
[#]: url: ( )
[#]: subject: (D in the Browser with Emscripten, LDC and bindbc-sdl (translation))
[#]: via: (https://theartofmachinery.com/2018/12/20/emscripten_d.html)
[#]: author: (Simon Arneaud https://theartofmachinery.com)
D in the Browser with Emscripten, LDC and bindbc-sdl (translation)
======
Heres a tutorial about using Emscripten to run D code in a normal web browser. Its uses a different approach from the [Dscripten game demo][1] and the [dscripten-tools][2] toolchain thats based on it.
* Instead of porting the D runtime, it uses a lightweight, runtimeless `-betterC` build.
* It uses Docker to manage the Emscripten installation.
LDC has recently gained support for [compiling directly to WebAssembly][3], but (unlike the Emscripten approach) that doesnt automatically get you libraries.
You can find [the complete working code on Github][4]. `./run.sh` starts a shell in a Docker image that contains the development environment. `dub build --build=release` generates the HTML and JavaScript assets and puts them into the `dist/` directory.
[This tutorial is translated from a Japanese post by outlandkarasu][5], who deserves all the credit for figuring this stuff out.
### Background
#### Whats Emscripten?
[Emscripten][6] is a compiler toolchain for asm.js and WebAssembly that comes with ported versions of the libc and SDL2 C libraries. It can compile regular Linux-based applications in languages like C to code that can run in a browser.
### How do you use Emscripten with D?
Emscripten is a toolchain designed for C/C++, but the C/C++ part is just a frontend. The toolchain actually compiles LLVM intermediate representation (IR). You can generate LLVM IR bitcode from D using [LDC][7], so it should be possible to feed that through Emscripten and run D in a browser, just like C/C++.
#### Gotchas using Emscripten
Ideally thats all it would take, but there are some things that require special attention (or trial and error).
1. D runtime library features like GC and Phobos cant be used without an Emscripten port.
2. Its not enough to just produce LLVM IR. The code needs to meet Emscriptens requirements.
* It needs to use ported libraries.
* Pointer sizes and data structure binary layouts need to match.
3. Emscripten bugs need to be worked around.
* Debug information is particularly problematic.
### Implementation
#### Plan of attack
Heres the plan for making D+Emscripten development work:
1. Use `-betterC` and the `@nogc` and `nothrow` attributes to avoid D runtime features.
2. Use SDL2 functions directly by statically compiling with [`bindbc-sdl`][8].
3. Keep on trying.
#### Environment setup
Emscripten is based on LLVM, clang and various other libraries, and is hard to set up, so I decided to [do the job with Docker][9]. I wrote a Dockerfile that would also add LDC and other tools at `docker build` time:
```
FROM trzeci/emscripten-slim:sdk-tag-1.38.21-64bit
# Install D and tools, and enable them in the shell by adding them to .bashrc
RUN apt-get -y update && \
apt-get -y install vim sudo curl && \
sudo -u emscripten /bin/sh -c "curl -fsS https://dlang.org/install.sh | bash -s ldc-1.12.0" && \
(echo 'source $(~/dlang/install.sh ldc -a)' >> /home/emscripten/.bashrc)
# dub settings (explained later)
ADD settings.json /var/lib/dub/settings.json
```
Docker makes these big toolchains pretty easy :)
#### Coding
Heres a basic demo that displays an image:
```
// Import SDL2 and SDL_image
// Both work with Emscripten
import bindbc.sdl;
import bindbc.sdl.image;
import core.stdc.stdio : printf; // printf works in Emscripten, too
// Function declarations for the main loop
alias em_arg_callback_func = extern(C) void function(void*) @nogc nothrow;
extern(C) void emscripten_set_main_loop_arg(em_arg_callback_func func, void *arg, int fps, int simulate_infinite_loop) @nogc nothrow;
extern(C) void emscripten_cancel_main_loop() @nogc nothrow;
// Log output
void logError(size_t line = __LINE__)() @nogc nothrow {
printf("%d:%s\n", line, SDL_GetError());
}
struct MainLoopArguments {
SDL_Renderer* renderer;
SDL_Texture* texture;
}
// Language features restricted with @nogc and nothrow
extern(C) int main(int argc, const char** argv) @nogc nothrow {
// Initialise SDL
if(SDL_Init(SDL_INIT_VIDEO) != 0) {
logError();
return -1;
}
scope(exit) SDL_Quit();
// Initialise SDL_image (with PNG support)
if(IMG_Init(IMG_INIT_PNG) != IMG_INIT_PNG) {
logError();
return -1;
}
scope(exit) IMG_Quit();
// Make the window and its renderer
SDL_Window* window;
SDL_Renderer* renderer;
if(SDL_CreateWindowAndRenderer(640, 480, SDL_WINDOW_SHOWN, &window, &renderer) != 0) {
logError();
return -1;
}
scope(exit) {
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
}
// Load image file
auto dman = IMG_Load("images/dman.png");
if(!dman) {
logError();
return -1;
}
scope(exit) SDL_FreeSurface(dman);
// Make a texture from the image
auto texture = SDL_CreateTextureFromSurface(renderer, dman);
if(!texture) {
logError();
return -1;
}
scope(exit) SDL_DestroyTexture(texture);
// Start the image main loop
auto arguments = MainLoopArguments(renderer, texture);
emscripten_set_main_loop_arg(&mainLoop, &arguments, 60, 1);
return 0;
}
extern(C) void mainLoop(void* p) @nogc nothrow {
// Get arguments
auto arguments = cast(MainLoopArguments*) p;
auto renderer = arguments.renderer;
auto texture = arguments.texture;
// Clear background
SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0x00);
SDL_RenderClear(renderer);
// Texture image
SDL_RenderCopy(renderer, texture, null, null);
SDL_RenderPresent(renderer);
// End of loop iteration
emscripten_cancel_main_loop();
}
```
#### Building
Now building is the tricky bit.
##### `dub.json`
Heres the `dub.json` I made through trial and error. It runs the whole build from D to WebAssembly.
```
{
"name": "emdman",
"authors": [
"outland.karasu@gmail.com"
],
"description": "A minimal emscripten D man demo.",
"copyright": "Copyright © 2018, outland.karasu@gmail.com",
"license": "BSL-1.0",
"dflags-ldc": ["--output-bc", "-betterC"], // Settings for bitcode output
"targetName": "app.bc",
"dependencies": {
"bindbc-sdl": "~>0.4.1"
},
"subConfigurations": {
"bindbc-sdl": "staticBC" // Statically-linked, betterC build
},
"versions": ["BindSDL_Image"], // Use SDL_image
// Run the Emscripten compiler after generating bitcode
// * Disable optimisations
// * Enable WebAssembly
// * Use SDL+SDL_image (with PNG)
// * Set web-only as the environment
// * Embed image file(s)
// * Generate HTML for running in browser
"postBuildCommands": ["emcc -v -O0 -s WASM=1 -s USE_SDL=2 -s USE_SDL_IMAGE=2 -s SDL2_IMAGE_FORMATS='[\"png\"]' -s ENVIRONMENT=web --embed-file images -o dist/index.html app.bc"]
}
```
##### Switch to 32b (x86) code generation
Compiling with 64b “worked” but I got a warning about different data layouts:
```
warning: Linking two modules of different data layouts: '/tmp/emscripten_temp_WwvmL5_archive_contents/mulsc3_20989819.c.o' is 'e-p:32:32-i64:64-v128:32:128-n32-S128' whereas '/src/app.bc' is 'e-m:e-i64:64-f80:128-n8:16:32:64-S128'
warning: Linking two modules of different target triples: /tmp/emscripten_temp_WwvmL5_archive_contents/mulsc3_20989819.c.o' is 'asmjs-unknown-emscripten' whereas '/src/app.bc' is 'x86_64-unknown-linux-gnu'
```
Apparently Emscripten is basically for 32b code. Using mismatched pointer sizes sounds like a pretty bad idea, so I added this `/var/lib/dub/settings.json` to the Dockerfile:
```
{
"defaultArchitecture": "x86", // Set code generation to 32b
"defaultCompiler": "ldc" // Use LDC by default
}
```
Theres an [open issue for documenting `dub`s `settings.json`][10].
##### Remove debug information
Emscripten gave the following error when I ran a normal build with `dub`:
```
shared:ERROR: Failed to run llvm optimizations:
```
It looks like theres [an issue related to debugging information][11]. I worked around it by using `dub --build=release`.
### Results
After lots of trial and error, I finally succeeded in getting my demo to run in a browser. Heres how it looks:
![Meet D-Man \(Demo\)][12]
The Emscripten+D dev environment isnt as stable as a normal dev environment. For example, rendering didnt work if I used `SDL_LowerBlit` instead. But heres D-Man in a browser.
--------------------------------------------------------------------------------
via: https://theartofmachinery.com/2018/12/20/emscripten_d.html
作者:[Simon Arneaud][a]
选题:[lujun9972][b]
译者:[译者ID](https://github.com/译者ID)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]: https://theartofmachinery.com
[b]: https://github.com/lujun9972
[1]: https://github.com/Ace17/dscripten
[2]: https://github.com/CyberShadow/dscripten-tools
[3]: https://wiki.dlang.org/Generating_WebAssembly_with_LDC
[4]: https://github.com/outlandkarasu-sandbox/emdman
[5]: https://qiita.com/outlandkarasu@github/items/15e0f4b6d1b2a0eab846
[6]: http://kripken.github.io/emscripten-site/
[7]: https://wiki.dlang.org/LDC
[8]: https://github.com/BindBC/bindbc-sdl
[9]: https://hub.docker.com/r/trzeci/emscripten/
[10]: https://github.com/dlang/dub/issues/1463
[11]: https://github.com/kripken/emscripten/issues/4078
[12]: /images/emscripten_d/d-man-browser.png