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

platform: support to compile to WebAssembly #25

Merged
merged 10 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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