Skip to content

Commit

Permalink
platform: support to compile to WebAssembly (#25)
Browse files Browse the repository at this point in the history
* platform: add support for running the emulator in a web browser

* log: init

* emulator: support webassembly and desktop

* ci: add emscripten
  • Loading branch information
gapry authored Aug 1, 2024
1 parent 11d108a commit f4d8650
Show file tree
Hide file tree
Showing 13 changed files with 170 additions and 85 deletions.
22 changes: 14 additions & 8 deletions .github/workflows/develop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ jobs:
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
- uses: actions/checkout@v4
- uses: mymindstorm/setup-emsdk@v14
- name: verify emscripten toolchain
run: emcc -v
- uses: lukka/get-cmake@latest
- name: install dependencies
run: ./vcpkg.sh
- name: build
run: make build
- name: build
run: make test
- name: build desktop
run: make build_desktop
- name: build test
run: make build_test

ci-mac:
name: Build on MacOS
Expand All @@ -36,13 +39,16 @@ jobs:
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
- uses: actions/checkout@v4
- uses: mymindstorm/setup-emsdk@v14
- name: verify emscripten toolchain
run: emcc -v
- uses: lukka/get-cmake@latest
- name: install dependencies
run: ./vcpkg.sh
- name: build
run: make build
- name: build
run: make test
- name: build desktop
run: make build_desktop
- name: build test
run: make build_test

fmt:
name: Formatting Check
Expand Down
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
# VSCode
# CHIP-8 ROM
rom/
*.ch8

# VSCode
Expand Down
29 changes: 22 additions & 7 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,31 @@ file(GLOB_RECURSE src_app "${dir_app}/*.cpp")
file(GLOB_RECURSE src_emulator "${dir_emulator}/*.cpp")
file(GLOB_RECURSE src_test "${dir_test}/*.cpp")

OPTION(BUILD_APP "Build App" OFF)
OPTION(BUILD_TEST "Build Test" OFF)
OPTION(BUILD_DESKTOP "Build Desktop" OFF)
OPTION(BUILD_TEST "Build Test" OFF)
OPTION(BUILD_WASM "Build Webassembly" OFF)

add_library(${dir_emulator} ${src_emulator})

target_compile_options(${dir_emulator} PUBLIC -g)
target_compile_options(${dir_emulator} PUBLIC -O0)

target_link_libraries(${dir_emulator} PUBLIC -lm)
target_link_libraries(${dir_emulator} PUBLIC fmt::fmt)
target_link_libraries(${dir_emulator} PUBLIC SDL2::SDL2)
IF(BUILD_DESKTOP)
target_link_libraries(${dir_emulator} PUBLIC fmt::fmt)
target_link_libraries(${dir_emulator} PUBLIC SDL2::SDL2)

IF(BUILD_APP)
add_executable(${project_name}.out ${src_app})

target_compile_options(${project_name}.out PRIVATE -g)
target_compile_options(${project_name}.out PRIVATE -O0)

target_link_libraries(${project_name}.out PUBLIC ${dir_emulator})
ENDIF(BUILD_APP)
ENDIF(BUILD_DESKTOP)

IF(BUILD_TEST)
target_link_libraries(${dir_emulator} PUBLIC fmt::fmt)
target_link_libraries(${dir_emulator} PUBLIC SDL2::SDL2)

add_executable(test_${project_name}.out ${src_test})

target_compile_options(test_${project_name}.out PRIVATE -g)
Expand All @@ -57,3 +60,15 @@ IF(BUILD_TEST)
GTest::gmock_main)
ENDIF(BUILD_TEST)

IF(BUILD_WASM)
set(CMAKE_EXECUTABLE_SUFFIX ".html")

target_link_libraries(${dir_emulator} PUBLIC "-s WASM=1 -s USE_SDL=2 -s -O3 --preload-file ../roms@/roms")

add_executable(${project_name}_wasm ${src_app})

target_compile_options(${project_name}_wasm PRIVATE -g)
target_compile_options(${project_name}_wasm PRIVATE -O0)

target_link_libraries(${project_name}_wasm PUBLIC ${dir_emulator})
ENDIF(BUILD_WASM)
53 changes: 34 additions & 19 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,38 +1,53 @@
app = arabica
test = test_$(app)
cc = clang
cxx = clang++
cc_desktop = clang
cxx_desktop = clang++
cc_wasm = emcc
cxx_wasm = em++
src_app = app
src_emulator = arabica
src_test = test
src = $(src_app) $(src_emulator) $(src_test)
dir_build = build
dir_rom = rom
dir_rom = roms
game = Tetris_Fran_Dachille_1991.ch8

default: build execute
default: desktop

fmt:
for dir in $(src); do \
find $$dir -type f -iname "*.cpp" -o -iname "*.hpp" | xargs clang-format -i -style=file; \
done

build: clean
build_desktop: clean
mkdir $(dir_build);
cd $(dir_build); cmake -DCMAKE_C_COMPILER="$(cc)" -DCMAKE_CXX_COMPILER="$(cxx)" -DBUILD_APP=ON -GNinja ..; ninja;
cd $(dir_build); cmake -DCMAKE_C_COMPILER="$(cc_desktop)" -DCMAKE_CXX_COMPILER="$(cxx_desktop)" -DBUILD_DESKTOP=ON -GNinja ..; ninja;

build_webassembly: clean
mkdir -p $(dir_build)
cd $(dir_build) && cmake -DCMAKE_C_COMPILER="$(cc_wasm)" -DCMAKE_CXX_COMPILER="$(cxx_wasm)" -DBUILD_WASM=ON -GNinja .. && ninja;

execute:
build_test: clean
mkdir $(dir_build);
cd $(dir_build); cmake -DCMAKE_C_COMPILER="$(cc_desktop)" -DCMAKE_CXX_COMPILER="$(cxx_desktop)" -DBUILD_TEST=ON -GNinja ..; ninja;

desktop: build_desktop
./$(dir_build)/$(app).out $(dir_rom)/$(game)

webassembly: build_webassembly kill_webserver
cd build; python3 -m http.server 8080 &
firefox http://localhost:8080/arabica_wasm.html

test: build_test
./$(test).app

debug: build_desktop
gdb -x commands.gdb --args ./$(dir_build)/$(app).out $(dir_rom)/$(game).ch8

clean:
rm -rf $(dir_build)

test: clean
mkdir $(dir_build); \
cd $(dir_build); cmake -DCMAKE_C_COMPILER="$(cc)" -DCMAKE_CXX_COMPILER="$(cxx)" -DBUILD_TEST=ON -GNinja ..; ninja; \
./$(test).out
fmt:
for dir in $(src); do \
find $$dir -type f -iname "*.cpp" -o -iname "*.hpp" | xargs clang-format -i -style=file; \
done

debug: clean build
gdb -x commands.gdb --args ./$(dir_build)/$(app).out $(dir_rom)/$(game).ch8
kill_webserver:
pkill -f 8080

.PHONY: default fmt build execute clean debug
.PHONY: default build_desktop build_webassembly build_test desktop webassembly test debug clean fmt kill_webserver
26 changes: 21 additions & 5 deletions app/main.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
#include <arabica/ui/window.hpp>
#include <fmt/core.h>
#include <arabica/log/log.hpp>

int main(int argc, char* argv[]) {
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#include <emscripten/html5.h>
#include <fstream>
#include <sstream>
#endif

std::string rom_file(int argc, char* argv[]) {
#ifdef __EMSCRIPTEN__
const char* const rom = "/roms/Tetris_Fran_Dachille_1991.ch8";
return rom;
#else
if (argc != 2) {
return 1;
fmt::print("Usage: ./arabica.out rom-file");
ARABICA_LOG_INFO("Usage: ./arabica.out rom-file\n");
std::exit(1);
}
arabica::Window window("Arabica Emulator", 640, 320, argv[1]);
return argv[1];
#endif
}

int main(int argc, char* argv[]) {
arabica::Window window("Arabica Emulator", 640, 320, rom_file(argc, argv));
window.execute();
return 0;
}
3 changes: 2 additions & 1 deletion arabica/device/sound.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <arabica/log/log.hpp>
#include <SDL2/SDL.h>
#include <cmath>

Expand Down Expand Up @@ -41,7 +42,7 @@ class Sound {

_device = SDL_OpenAudioDevice(nullptr, 0, &_desired_spec, &_spec, 0);
if (_device == 0) {
SDL_Log("Failed to open audio: %s", SDL_GetError());
ARABICA_LOG_INFO("Failed to open audio: %s", SDL_GetError());
return false;
}

Expand Down
11 changes: 5 additions & 6 deletions arabica/emulator/emulator.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <arabica/emulator/emulator.hpp>
#include <arabica/log/log.hpp>
#include <cstdint>
#include <vector>

Expand All @@ -13,17 +14,15 @@ bool Emulator::load(const std::string& rom) {
}

void Emulator::execute() {
is_enable_log = false;

log_info("PC = {:x}\n", cpu.pc);
ARABICA_LOG_INFO("PC = {:x}\n", cpu.pc);

// 500 Hz / 60 FPS = 500 (Instructions / Second) / 60 (Frames / Second) = 500 / 60 (Instructions / Frame)
const int instructions_pre_frames = cpu.clock_speed / fps;
for (int i = 0; i < instructions_pre_frames; ++i) {
single_step();
}

log_info("The current cycle is {}\n", cycle);
ARABICA_LOG_INFO("The current cycle is {}\n", cycle);
cycle++;

delay.tick();
Expand All @@ -35,7 +34,7 @@ void Emulator::single_step() {
uint16_t prefix = cpu.instruction & 0xF000;
cpu.opcode = static_cast<OP_CODE>(prefix);

log_info("cpu.instruction is {0:x}\n", cpu.instruction);
ARABICA_LOG_INFO("cpu.instruction is {0:x}\n", cpu.instruction);

switch (prefix) {
case 0x0: {
Expand Down Expand Up @@ -552,7 +551,7 @@ void Emulator::single_step() {
cpu.advance_pc();
} break;
default: {
log_info("Unknown opcode: 0x{:X}\n", static_cast<uint16_t>(cpu.opcode));
ARABICA_LOG_INFO("Unknown opcode: 0x{:X}\n", static_cast<uint16_t>(cpu.opcode));
} break;
}
}
Expand Down
17 changes: 3 additions & 14 deletions arabica/emulator/emulator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
#include <arabica/device/display.hpp>
#include <arabica/device/sound.hpp>
#include <arabica/device/delay.hpp>
#include <fmt/core.h>
#include <random>

namespace arabica {
Expand All @@ -15,7 +14,6 @@ class Emulator {
public:
Emulator()
: cycle(0)
, is_enable_log(false)
, cpu(memory) {
}

Expand All @@ -24,10 +22,9 @@ class Emulator {
void single_step();
void execute();

int fps = 60; // 60 FPS = 60 (Frames Per Second) = 60 (frames) / 1 (second)
int milliseconds_per_frame = 1000 / fps; // (FPS)^{-1} = 1 (s) / 60 (frames) = 1000 (milliseconds) / 60 (frames)
int cycle = 0;
bool is_enable_log = false;
int fps = 60; // 60 FPS = 60 (Frames Per Second) = 60 (frames) / 1 (second)
int milliseconds_per_frame = 1000 / fps; // (FPS)^{-1} = 1 (s) / 60 (frames) = 1000 (milliseconds) / 60 (frames)
int cycle = 0;

CPU cpu;
Memory memory;
Expand All @@ -44,14 +41,6 @@ class Emulator {
std::uniform_int_distribution<T> distr(range_from, range_to);
return distr(generator);
}

template<typename... Args>
inline void log_info(const char* const format, const Args&... args) {
if (is_enable_log) {
fmt::print("[emulator log] ");
fmt::print(format, args...);
}
}
};

} // namespace arabica
27 changes: 27 additions & 0 deletions arabica/log/log.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once

#define ARABICA_IS_ENABLE_LOG 0

#ifdef __EMSCRIPTEN__
#else
#include <fmt/core.h>
#endif

#if ARABICA_IS_ENABLE_LOG
#define ARABICA_LOG_INFO(fmt, ...) arabica::log_info(fmt, __VA_ARGS__)
#else
#define ARABICA_LOG_INFO(fmt, ...)
#endif

namespace arabica {

template<typename... Args>
inline void log_info(const char* const format, const Args&... args) {
#ifdef __EMSCRIPTEN__
#else
fmt::print("[emulator log] ");
fmt::print(format, args...);
#endif
}

} // namespace arabica
4 changes: 2 additions & 2 deletions arabica/memory/memory.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#include <arabica/memory/memory.hpp>
#include <arabica/log/log.hpp>
#include <iostream>
#include <fstream>
#include <stdexcept>
#include <fmt/core.h>

namespace arabica {

Expand Down Expand Up @@ -49,7 +49,7 @@ void Memory::is_valid(const address_t address) const {
bool Memory::load(const std::string& rom) {
std::ifstream file(rom, std::ios::binary | std::ios::ate);
if (!file) {
fmt::print("Failed to open the file.");
ARABICA_LOG_INFO("{}\n", "Failed to open the file.");
return false;
}

Expand Down
Loading

0 comments on commit f4d8650

Please sign in to comment.