Skip to content

Commit 971b42c

Browse files
authored
Merge pull request #385 from ChinYikMing/emcc-port-video-games
Enable run video games using WebAssembly
2 parents e39b8ac + 07b86f6 commit 971b42c

File tree

10 files changed

+422
-23
lines changed

10 files changed

+422
-23
lines changed

Makefile

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -193,17 +193,31 @@ OBJS := \
193193
OBJS := $(addprefix $(OUT)/, $(OBJS))
194194
deps := $(OBJS:%.o=%.o.d)
195195

196-
EXPORTED_FUNCS := _main
196+
include mk/external.mk
197+
198+
deps_emcc :=
199+
ASSETS := assets
200+
WEB_JS_RESOURCES := $(ASSETS)/js
201+
EXPORTED_FUNCS := _main,_indirect_rv_halt
197202
ifeq ("$(CC_IS_EMCC)", "1")
198203
CFLAGS_emcc += -sINITIAL_MEMORY=2GB \
199204
-sALLOW_MEMORY_GROWTH \
200205
-s"EXPORTED_FUNCTIONS=$(EXPORTED_FUNCS)" \
201-
--embed-file build \
206+
-sSTACK_SIZE=4MB \
207+
-sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency \
208+
--embed-file build@/ \
209+
--embed-file build/timidity@/etc/timidity \
202210
-DMEM_SIZE=0x40000000 \
211+
-DCYCLE_PER_STEP=2000000 \
212+
--pre-js $(WEB_JS_RESOURCES)/pre.js \
213+
-O3 \
203214
-w
215+
216+
# used to download all dependencies of elf executable and bundle into single wasm
217+
deps_emcc += $(DOOM_DATA) $(QUAKE_DATA) $(TIMIDITY_DATA)
204218
endif
205219

206-
$(OUT)/%.o: src/%.c
220+
$(OUT)/%.o: src/%.c $(deps_emcc)
207221
$(VECHO) " CC\t$@\n"
208222
$(Q)$(CC) -o $@ $(CFLAGS) $(CFLAGS_emcc) -c -MMD -MF $@.d $<
209223

@@ -259,22 +273,36 @@ misalign: $(BIN)
259273
$(PRINTF) "Failed.\n"; \
260274
fi
261275

262-
include mk/external.mk
263-
264276
# Non-trivial demonstration programs
265277
ifeq ($(call has, SDL), 1)
266-
doom: $(BIN) $(DOOM_DATA)
267-
(cd $(OUT); ../$(BIN) doom.elf)
278+
doom_action := (cd $(OUT); ../$(BIN) doom.elf)
279+
ifeq ("$(CC_IS_EMCC)", "1")
280+
# TODO: check Chrome or Firefox is available and serve python httpd and open the web page
281+
# TODO: serve and open a web page, show warning if environment not support pthread runtime
282+
doom_action :=
283+
endif
284+
doom_deps += $(DOOM_DATA) $(BIN)
285+
doom: $(doom_deps)
286+
$(doom_action)
287+
268288
ifeq ($(call has, EXT_F), 1)
269-
quake: $(BIN) $(QUAKE_DATA)
270-
(cd $(OUT); ../$(BIN) quake.elf)
289+
quake_action := (cd $(OUT); ../$(BIN) quake.elf)
290+
ifeq ("$(CC_IS_EMCC)", "1")
291+
# TODO: check Chrome or Firefox is available and serve python httpd and open the web page
292+
# TODO: serve and open a web page, show warning if environment not support pthread runtime
293+
quake_action :=
294+
endif
295+
quake_deps += $(QUAKE_DATA) $(BIN)
296+
quake: $(quake_deps)
297+
$(quake_action)
271298
endif
272299
endif
273300

274301
clean:
275302
$(RM) $(BIN) $(OBJS) $(HIST_BIN) $(HIST_OBJS) $(deps) $(WEB_FILES) $(CACHE_OUT) src/rv32_jit.c
276303
distclean: clean
277304
-$(RM) $(DOOM_DATA) $(QUAKE_DATA)
305+
$(RM) -r $(TIMIDITY_DATA)
278306
$(RM) -r $(OUT)/id1
279307
$(RM) *.zip
280308
$(RM) -r $(OUT)/mini-gdbstub

assets/html/index.html

Lines changed: 277 additions & 0 deletions
Large diffs are not rendered by default.

assets/js/pre.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Module['noInitialRun'] = true;
2+
Module['onRuntimeInitialized'] = function(target_elf) {
3+
if(target_elf === undefined){
4+
console.warn("target elf executable is undefined");
5+
return;
6+
}
7+
8+
callMain([target_elf]);
9+
};

mk/external.mk

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,40 @@
22
# _DATA_URL : the hyperlink which points to archive.
33
# _DATA : the file to be read by specific executable.
44
# _DATA_SHA1 : the checksum of the content in _DATA
5+
# _DATA_EXTRACT : the way to extract content from compressed file
6+
# _DATA_VERIFY : the way to verify the checksum of extracted file
57

68
# Doom
79
# https://tipsmake.com/how-to-run-doom-on-raspberry-pi-without-emulator
810
DOOM_DATA_URL = http://www.doomworld.com/3ddownloads/ports/shareware_doom_iwad.zip
911
DOOM_DATA = $(OUT)/DOOM1.WAD
1012
DOOM_DATA_SHA1 = 5b2e249b9c5133ec987b3ea77596381dc0d6bc1d
13+
DOOM_DATA_EXTRACT = unzip -d $(OUT) $(notdir $($(T)_DATA_URL))
14+
DOOM_DATA_VERIFY = echo "$(strip $$($(T)_DATA_SHA1)) $$@" | $(SHA1SUM) -c
1115

1216
# Quake
1317
QUAKE_DATA_URL = https://www.libsdl.org/projects/quake/data/quakesw-1.0.6.zip
1418
QUAKE_DATA = $(OUT)/id1/pak0.pak
1519
QUAKE_DATA_SHA1 = 36b42dc7b6313fd9cabc0be8b9e9864840929735
20+
QUAKE_DATA_EXTRACT = unzip -d $(OUT) $(notdir $($(T)_DATA_URL))
21+
QUAKE_DATA_VERIFY = echo "$(strip $$($(T)_DATA_SHA1)) $$@" | $(SHA1SUM) -c
22+
23+
# Timidity software synthesizer configuration for SDL2_mixer
24+
TIMIDITY_DATA_URL = http://www.libsdl.org/projects/mixer/timidity/timidity.tar.gz
25+
TIMIDITY_DATA = $(OUT)/timidity
26+
TIMIDITY_DATA_SHA1 = cdd30736508d26968222a6414f3beabc3b7a0725
27+
TIMIDITY_DATA_EXTRACT = tar -xf $(notdir $($(T)_DATA_URL)) -C $(OUT)
28+
TIMIDITY_TMP_FILE = /tmp/timidity_sha1.txt
29+
TIMIDITY_DATA_VERIFY = echo "$(TIMIDITY_DATA_SHA1)" > $(TIMIDITY_TMP_FILE) | find $(TIMIDITY_DATA) -type f -print0 | sort -z | xargs -0 shasum | shasum | cut -f 1 -d ' '
1630

1731
define download-n-extract
1832
$($(T)_DATA):
1933
$(VECHO) " GET\t$$@\n"
2034
$(Q)curl --progress-bar -O -L -C - "$(strip $($(T)_DATA_URL))"
21-
$(Q)unzip -d $(OUT) $(notdir $($(T)_DATA_URL))
22-
$(Q)echo "$(strip $$($(T)_DATA_SHA1)) $$@" | $(SHA1SUM) -c
23-
$(Q)$(RM) $(notdir $($(T)_DATA_URL))
35+
$(Q)$($(T)_DATA_EXTRACT)
36+
$(Q)$($(T)_DATA_VERIFY)
37+
$(Q)$(RM) $(notdir $($(T)_DATA_URL)) $($(T)_TMP_FILE)
2438
endef
2539

26-
EXTERNAL_DATA = DOOM QUAKE
27-
$(foreach T,$(EXTERNAL_DATA),$(eval $(download-n-extract)))
40+
EXTERNAL_DATA = DOOM QUAKE TIMIDITY
41+
$(foreach T,$(EXTERNAL_DATA),$(eval $(download-n-extract)))

mk/toolchain.mk

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,35 @@ CC_IS_GCC :=
44
ifneq ($(shell $(CC) --version | head -n 1 | grep emcc),)
55
CC_IS_EMCC := 1
66

7+
EMCC_VERSION := $(shell $(CC) --version | head -n 1 | cut -f10 -d ' ')
8+
EMCC_MAJOR := $(shell echo $(EMCC_VERSION) | cut -f1 -d.)
9+
EMCC_MINOR := $(shell echo $(EMCC_VERSION) | cut -f2 -d.)
10+
EMCC_PATCH := $(shell echo $(EMCC_VERSION) | cut -f3 -d.)
11+
12+
# When the emcc version is not 3.1.51, the latest SDL2_mixer library is fetched by emcc and music might not be played in the web browser
13+
SDL_MUSIC_PLAY_AT_EMCC_MAJOR := 3
14+
SDL_MUSIC_PLAY_AT_EMCC_MINOR := 1
15+
SDL_MUSIC_PLAY_AT_EMCC_PATCH := 51
16+
SDL_MUSIC_CANNOT_PLAY_WARNING := Video games music might not be played. You may switch emcc to version $(SDL_MUSIC_PLAY_AT_EMCC_MAJOR).$(SDL_MUSIC_PLAY_AT_EMCC_MINOR).$(SDL_MUSIC_PLAY_AT_EMCC_PATCH)
17+
ifeq ($(shell echo $(EMCC_MAJOR)\==$(SDL_MUSIC_PLAY_AT_EMCC_MAJOR) | bc), 1)
18+
ifeq ($(shell echo $(EMCC_MINOR)\==$(SDL_MUSIC_PLAY_AT_EMCC_MINOR) | bc), 1)
19+
ifeq ($(shell echo $(EMCC_PATCH)\==$(SDL_MUSIC_PLAY_AT_EMCC_PATCH) | bc), 1)
20+
# do nothing
21+
else
22+
$(warning $(SDL_MUSIC_CANNOT_PLAY_WARNING))
23+
endif
24+
else
25+
$(warning $(SDL_MUSIC_CANNOT_PLAY_WARNING))
26+
endif
27+
else
28+
$(warning $(SDL_MUSIC_CANNOT_PLAY_WARNING))
29+
endif
30+
731
# see commit 165c1a3 of emscripten
832
MIMALLOC_SUPPORT_SINCE_MAJOR := 3
933
MIMALLOC_SUPPORT_SINCE_MINOR := 1
1034
MIMALLOC_SUPPORT_SINCE_PATCH := 50
1135
MIMALLOC_UNSUPPORTED_WARNING := mimalloc is supported after version $(MIMALLOC_SUPPORT_SINCE_MAJOR).$(MIMALLOC_SUPPORT_SINCE_MINOR).$(MIMALLOC_SUPPORT_SINCE_PATCH)
12-
EMCC_VERSION := $(shell $(CC) --version | head -n 1 | cut -f10 -d ' ')
13-
EMCC_MAJOR := $(shell echo $(EMCC_VERSION) | cut -f1 -d.)
14-
EMCC_MINOR := $(shell echo $(EMCC_VERSION) | cut -f2 -d.)
15-
EMCC_PATCH := $(shell echo $(EMCC_VERSION) | cut -f3 -d.)
1636
ifeq ($(shell echo $(EMCC_MAJOR)\>=$(MIMALLOC_SUPPORT_SINCE_MAJOR) | bc), 1)
1737
ifeq ($(shell echo $(EMCC_MINOR)\>=$(MIMALLOC_SUPPORT_SINCE_MINOR) | bc), 1)
1838
ifeq ($(shell echo $(EMCC_PATCH)\>=$(MIMALLOC_SUPPORT_SINCE_PATCH) | bc), 1)

src/emulate.c

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
#include <stdlib.h>
1111
#include <string.h>
1212

13+
#ifdef __EMSCRIPTEN__
14+
#include <emscripten.h>
15+
#endif
16+
1317
#if RV32_HAS(EXT_F)
1418
#include <math.h>
1519
#include "softfloat.h"
@@ -1074,9 +1078,11 @@ static bool runtime_profiler(riscv_t *rv, block_t *block)
10741078
typedef void (*exec_block_func_t)(riscv_t *rv, uintptr_t);
10751079
#endif
10761080

1077-
void rv_step(riscv_t *rv)
1081+
void rv_step(void *arg)
10781082
{
1079-
assert(rv);
1083+
assert(arg);
1084+
riscv_t *rv = arg;
1085+
10801086
vm_attr_t *attr = PRIV(rv);
10811087
uint32_t cycles = attr->cycle_per_step;
10821088

@@ -1174,6 +1180,13 @@ void rv_step(riscv_t *rv)
11741180
#endif
11751181
prev = block;
11761182
}
1183+
1184+
#ifdef __EMSCRIPTEN__
1185+
if (rv_has_halted(rv)) {
1186+
emscripten_cancel_main_loop();
1187+
rv_delete(rv); /* clean up and reuse memory */
1188+
}
1189+
#endif
11771190
}
11781191

11791192
void ebreak_handler(riscv_t *rv)

src/main.c

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,13 +168,31 @@ static void dump_test_signature(const char *prog_name)
168168
elf_delete(elf);
169169
}
170170

171+
/* CYCLE_PER_STEP shall be defined on different runtime */
172+
#ifndef CYCLE_PER_STEP
173+
#define CYCLE_PER_STEP 100
174+
#endif
171175
/* MEM_SIZE shall be defined on different runtime */
172176
#ifndef MEM_SIZE
173177
#define MEM_SIZE 0xFFFFFFFFULL /* 2^32 - 1 */
174178
#endif
175179
#define STACK_SIZE 0x1000 /* 4096 */
176180
#define ARGS_OFFSET_SIZE 0x1000 /* 4096 */
177181

182+
/* To use rv_halt function in wasm, we have to expose RISC-V instance(rv),
183+
* but we can add a layer to not expose the instance and make rv_halt
184+
* callable. A small trade-off is that declaring instance as a global
185+
* variable. rv_halt is useful when cancelling the main loop of wasm,
186+
* see rv_step in emulate.c for more detail
187+
*/
188+
riscv_t *rv;
189+
#ifdef __EMSCRIPTEN__
190+
void indirect_rv_halt()
191+
{
192+
rv_halt(rv);
193+
}
194+
#endif
195+
178196
int main(int argc, char **args)
179197
{
180198
if (argc == 1 || !parse_args(argc, args)) {
@@ -199,14 +217,14 @@ int main(int argc, char **args)
199217
.run_flag = run_flag,
200218
.profile_output_file = prof_out_file,
201219
.data.user = malloc(sizeof(vm_user_t)),
202-
.cycle_per_step = 100,
220+
.cycle_per_step = CYCLE_PER_STEP,
203221
.allow_misalign = opt_misaligned,
204222
};
205223
assert(attr.data.user);
206224
attr.data.user->elf_program = opt_prog_name;
207225

208226
/* create the RISC-V runtime */
209-
riscv_t *rv = rv_create(&attr);
227+
rv = rv_create(&attr);
210228
if (!rv) {
211229
fprintf(stderr, "Unable to create riscv emulator\n");
212230
attr.exit_code = 1;

src/riscv.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
#define STDERR_FILENO FILENO(stderr)
1919
#endif
2020

21+
#ifdef __EMSCRIPTEN__
22+
#include <emscripten.h>
23+
#endif
24+
2125
#include "elf.h"
2226
#include "mpool.h"
2327
#include "riscv.h"
@@ -315,9 +319,13 @@ void rv_run(riscv_t *rv)
315319
rv_debug(rv);
316320
#endif
317321
else {
322+
#ifdef __EMSCRIPTEN__
323+
emscripten_set_main_loop_arg(rv_step, (void *) rv, 0, 1);
324+
#else
318325
/* default main loop */
319326
for (; !rv_has_halted(rv);) /* run until the flag is done */
320327
rv_step(rv); /* step instructions */
328+
#endif
321329
}
322330

323331
if (attr->run_flag & RV_RUN_PROFILE) {

src/riscv.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ void rv_debug(riscv_t *rv);
166166
#endif
167167

168168
/* step the RISC-V emulator */
169-
void rv_step(riscv_t *rv);
169+
void rv_step(void *arg);
170170

171171
/* set the program counter of a RISC-V emulator */
172172
bool rv_set_pc(riscv_t *rv, riscv_word_t pc);

src/syscall_sdl.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,12 @@ static void play_sfx(riscv_t *rv)
708708
.volume = volume,
709709
};
710710
pthread_create(&sfx_thread, NULL, sfx_handler, &sfx);
711+
/* FIXME: In web browser runtime, web workers in thread pool do not reap
712+
* after sfx_handler return, thus we have to join them. sfx_handler does not
713+
* contain infinite loop,so do not worry to be stalled by it */
714+
#ifdef __EMSCRIPTEN__
715+
pthread_join(sfx_thread, NULL);
716+
#endif
711717
}
712718

713719
static void play_music(riscv_t *rv)
@@ -738,6 +744,12 @@ static void play_music(riscv_t *rv)
738744
.volume = volume,
739745
};
740746
pthread_create(&music_thread, NULL, music_handler, &music);
747+
/* FIXME: In web browser runtime, web workers in thread pool do not reap
748+
* after music_handler return, thus we have to join them. music_handler does
749+
* not contain infinite loop,so do not worry to be stalled by it */
750+
#ifdef __EMSCRIPTEN__
751+
pthread_join(music_thread, NULL);
752+
#endif
741753
}
742754

743755
static void stop_music(riscv_t *rv UNUSED)

0 commit comments

Comments
 (0)