mirror of
https://github.com/c3d/DB48X-on-DM42.git
synced 2024-09-28 03:20:53 +02:00
wasm: Implement WASM support
This is an in-tree port of [sunpazed's excellent work][wasm]. The goal is to be able to run the simulator in a browser. This builds with emscripten, as installed by the `emsdk`. The `emsdk` is added as a submodule, and initialized on first build. To build the browser WASM version of the simulator, run `make wasm`. Fixes: #993 Signed-off-by: Christophe de Dinechin <christophe@dinechin.org> [wasm]: https://github.com/sunpazed/db48x-wasm
This commit is contained in:
parent
5c551ce194
commit
9bc7afd519
23 changed files with 713 additions and 54 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -7,3 +7,6 @@
|
|||
[submodule "dmcp"]
|
||||
path = dmcp
|
||||
url = git@github.com:c3d/SDKdemo.git
|
||||
[submodule "emsdk"]
|
||||
path = emsdk
|
||||
url = git@github.com:emscripten-core/emsdk.git
|
||||
|
|
127
Makefile
127
Makefile
|
@ -101,6 +101,25 @@ sim: recorder/config.h \
|
|||
keyboard \
|
||||
.ALWAYS
|
||||
|
||||
WASM_TARGET=db48x.js
|
||||
WASM_HTML=index.html
|
||||
wasm: emsdk $(WASM_TARGET) $(WASM_HTML)
|
||||
|
||||
ifneq ($(VARIANT),wasm)
|
||||
$(WASM_TARGET): $(SOURCES)
|
||||
(source emsdk/emsdk_env.sh && \
|
||||
make VARIANT=wasm TARGET=db48x PGM=js SDK=sim )
|
||||
$(WASM_HTML): src/wasm/index.html
|
||||
cp $< $@
|
||||
endif
|
||||
|
||||
emsdk: emsdk/emsdk
|
||||
emcc --version > /dev/null || \
|
||||
(cd emsdk && ./emsdk install latest && ./emsdk activate latest)
|
||||
|
||||
emsdk/emsdk:
|
||||
git submodule update --init --recursive
|
||||
|
||||
clangdb: sim/$(TARGET).mak .ALWAYS
|
||||
cd sim && rm -f *.o && compiledb make -f $(TARGET).mak && mv compile_commands.json ..
|
||||
|
||||
|
@ -197,17 +216,12 @@ fastest-%:
|
|||
$(MAKE) $* OPT=fastest
|
||||
|
||||
|
||||
######################################
|
||||
# System sources
|
||||
######################################
|
||||
C_INCLUDES += -I$(SDK)
|
||||
C_SOURCES += $(SDK)/sys/pgm_syscalls.c
|
||||
ASM_SOURCES = $(SDK)/startup_pgm.s
|
||||
|
||||
#######################################
|
||||
# Custom section
|
||||
#######################################
|
||||
|
||||
SOURCES=$(C_SOURCES) $(CXX_SOURCES)
|
||||
|
||||
# Includes
|
||||
C_INCLUDES += -Isrc/$(VARIANT) -Isrc/$(PLATFORM) -Isrc
|
||||
|
||||
|
@ -271,7 +285,8 @@ CXX_SOURCES += \
|
|||
src/unit.cc \
|
||||
src/user_interface.cc \
|
||||
src/util.cc \
|
||||
src/variables.cc
|
||||
src/variables.cc \
|
||||
$(PLATFORM_SOURCES)
|
||||
|
||||
# ASM sources
|
||||
#ASM_SOURCES += src/xxx.s
|
||||
|
@ -285,16 +300,17 @@ DEFINES += \
|
|||
$(DEFINES_$(VARIANT)) \
|
||||
HELPFILE_NAME=\"/help/$(TARGET).md\"
|
||||
DEFINES_debug=DEBUG
|
||||
DEFINES_release=RELEASE
|
||||
DEFINES_small=RELEASE
|
||||
DEFINES_fast=RELEASE
|
||||
DEFINES_faster=RELEASE
|
||||
DEFINES_fastes=RELEASE
|
||||
DEFINES_release=NDEBUG
|
||||
DEFINES_small=NDEBUG
|
||||
DEFINES_fast=NDEBUG
|
||||
DEFINES_faster=NDEBUG
|
||||
DEFINES_fastes=NDEBUG
|
||||
DEFINES_dm32 = DM32 \
|
||||
CONFIG_FIXED_BASED_OBJECTS \
|
||||
DEOPTIMIZE_CATALOG
|
||||
|
||||
DEFINES_dm42 = DM42
|
||||
DEFINES_wasm = $(DEFINES_dm32) SIMULATOR WASM
|
||||
|
||||
C_DEFS += $(DEFINES:%=-D%)
|
||||
|
||||
|
@ -309,6 +325,23 @@ $(BUILD)/recorder.o $(BUILD)/recorder_ring.o: recorder/config.h
|
|||
#######################################
|
||||
# binaries
|
||||
#######################################
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
ifeq ($(VARIANT),wasm)
|
||||
#------------------------------------------------------------------------------
|
||||
CC = emcc
|
||||
CXX = emcc -x c++ -std=gnu++17
|
||||
PLATFORM_SOURCES=src/wasp/dmcp.cc src/wasm/sim-screen.cc src/wasm/sim-window.cc
|
||||
C_SOURCES += recorder/recorder.c
|
||||
CFLAGS += -O3
|
||||
LDFLAGS += -s MODULARIZE=0 \
|
||||
-s EXPORT_NAME='Module' \
|
||||
-s RESERVED_FUNCTION_POINTERS=20 \
|
||||
--bind
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
else
|
||||
#------------------------------------------------------------------------------
|
||||
CC = arm-none-eabi-gcc
|
||||
CXX = arm-none-eabi-g++
|
||||
AS = arm-none-eabi-gcc -x assembler-with-cpp
|
||||
|
@ -317,6 +350,37 @@ AR = arm-none-eabi-ar
|
|||
SIZE = arm-none-eabi-size
|
||||
HEX = $(OBJCOPY) -O ihex
|
||||
BIN = $(OBJCOPY) -O binary -S
|
||||
CPUFLAGS += -mthumb -march=armv7e-m -mfloat-abi=hard -mfpu=fpv4-sp-d16
|
||||
PLATFORM_FLAGS = -Wno-packed-bitfield-compat -Wall -fdata-sections -ffunction-sections
|
||||
CXX_PLATFORM_FLAGS = -fno-rtti
|
||||
C_LIST_FLAGS = -Wa,-a,-ad,-alms=$(BUILD)/$(notdir $(<:.c=.lst))
|
||||
CXX_LIST_FLAGS = -Wa,-a,-ad,-alms=$(BUILD)/$(notdir $(<:.cc=.lst))
|
||||
ASM_SOURCES = $(SDK)/startup_pgm.s
|
||||
LINK_OPTS= \
|
||||
-Wl,-Map=$(BUILD)/$(TARGET).map,--cref \
|
||||
-Wl,--gc-sections \
|
||||
-Wl,--wrap=_malloc_r
|
||||
|
||||
# System sources
|
||||
C_INCLUDES += -I$(SDK)
|
||||
C_SOURCES += $(SDK)/sys/pgm_syscalls.c
|
||||
|
||||
$(BUILD)/$(TARGET).elf: $(OBJECTS) Makefile
|
||||
$(CC) $(OBJECTS) $(LDFLAGS) -o $@
|
||||
$(TARGET).$(PGM): $(BUILD)/$(TARGET).elf Makefile $(CRCFIX)
|
||||
$(OBJCOPY) --remove-section .qspi -O binary $< $(FLASH)
|
||||
$(OBJCOPY) --remove-section .qspi -O ihex $< $(FLASH:.bin=.hex)
|
||||
$(OBJCOPY) --only-section .qspi -O binary $< $(QSPI)
|
||||
$(OBJCOPY) --only-section .qspi -O ihex $< $(QSPI:.bin=.hex)
|
||||
$(TOOLS)/adjust_crc $(CRCFIX) $(QSPI)
|
||||
$(TOOLS)/check_qspi_crc $(TARGET) $(BUILD)/$(TARGET)_qspi.bin src/$(VARIANT)/qspi_crc.h || ( rm -rf build/$(VARIANT) && exit 1)
|
||||
$(TOOLS)/add_pgm_chsum $(BUILD)/$(TARGET)_flash.bin $@
|
||||
$(SIZE) $<
|
||||
wc -c $@
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
endif
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
#######################################
|
||||
# CFLAGS
|
||||
|
@ -326,12 +390,9 @@ AS_DEFS =
|
|||
C_DEFS += -D__weak="__attribute__((weak))" -D__packed="__attribute__((__packed__))"
|
||||
AS_INCLUDES =
|
||||
|
||||
|
||||
CPUFLAGS += -mthumb -march=armv7e-m -mfloat-abi=hard -mfpu=fpv4-sp-d16
|
||||
|
||||
# compile gcc flags
|
||||
ASFLAGS = $(CPUFLAGS) $(AS_DEFS) $(AS_INCLUDES) $(ASFLAGS_$(OPT)) -Wall -fdata-sections -ffunction-sections
|
||||
CFLAGS = $(CPUFLAGS) $(C_DEFS) $(C_INCLUDES) $(CFLAGS_$(OPT)) -Wall -fdata-sections -ffunction-sections
|
||||
ASFLAGS = $(CPUFLAGS) $(PLATFORM_FLAGS) $(AS_DEFS) $(AS_INCLUDES) $(ASFLAGS_$(OPT))
|
||||
CFLAGS += $(CPUFLAGS) $(PLATFORM_FLAGS) $(C_DEFS) $(C_INCLUDES) $(CFLAGS_$(OPT))
|
||||
CFLAGS += -Wno-misleading-indentation
|
||||
DBGFLAGS = $(DBGFLAGS_$(OPT))
|
||||
DBGFLAGS_debug = -g
|
||||
|
@ -357,12 +418,7 @@ CFLAGS += -MD -MP -MF .dep/$(@F).d
|
|||
# link script
|
||||
LDSCRIPT = src/$(VARIANT)/stm32_program.ld
|
||||
LIBDIR =
|
||||
LDFLAGS = $(CPUFLAGS) -T$(LDSCRIPT) $(LIBDIR) $(LIBS) \
|
||||
-Wl,-Map=$(BUILD)/$(TARGET).map,--cref \
|
||||
-Wl,--gc-sections \
|
||||
-Wl,--wrap=_malloc_r
|
||||
|
||||
|
||||
LDFLAGS += $(CPUFLAGS) -T$(LDSCRIPT) $(LIBDIR) $(LIBS) $(LINK_OPTS)
|
||||
|
||||
|
||||
#######################################
|
||||
|
@ -378,29 +434,24 @@ vpath %.cc $(sort $(dir $(CXX_SOURCES)))
|
|||
OBJECTS += $(addprefix $(BUILD)/,$(notdir $(ASM_SOURCES:.s=.o)))
|
||||
vpath %.s $(sort $(dir $(ASM_SOURCES)))
|
||||
|
||||
CXXFLAGS = $(CFLAGS) -fno-exceptions -fno-rtti -Wno-packed-bitfield-compat
|
||||
CXXFLAGS = $(CFLAGS) -fno-exceptions $(CXX_PLATFORM_FLAGS)
|
||||
|
||||
$(BUILD)/%.o: %.c Makefile | $(BUILD)/.exists
|
||||
$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD)/$(notdir $(<:.c=.lst)) $< -o $@
|
||||
$(CC) -c $(CFLAGS) $(C_LIST_FLAGS) $< -o $@
|
||||
|
||||
$(BUILD)/%.o: %.cc Makefile | $(BUILD)/.exists
|
||||
$(CXX) -c $(CXXFLAGS) -Wa,-a,-ad,-alms=$(BUILD)/$(notdir $(<:.cc=.lst)) $< -o $@
|
||||
$(CXX) -c $(CXXFLAGS) $(CXX_LIST_FLAGS) $< -o $@
|
||||
|
||||
$(BUILD)/%.o: %.cpp Makefile | $(BUILD)/.exists
|
||||
$(CXX) -c $(CXXFLAGS) $(CXX_LIST_FLAGS) $< -o $@
|
||||
|
||||
$(BUILD)/%.o: %.s Makefile | $(BUILD)/.exists
|
||||
$(AS) -c $(CFLAGS) $< -o $@
|
||||
|
||||
$(BUILD)/$(TARGET).elf: $(OBJECTS) Makefile
|
||||
ifeq ($(VARIANT),wasm)
|
||||
$(WASM_TARGET): $(OBJECTS) Makefile
|
||||
$(CC) $(OBJECTS) $(LDFLAGS) -o $@
|
||||
$(TARGET).$(PGM): $(BUILD)/$(TARGET).elf Makefile $(CRCFIX)
|
||||
$(OBJCOPY) --remove-section .qspi -O binary $< $(FLASH)
|
||||
$(OBJCOPY) --remove-section .qspi -O ihex $< $(FLASH:.bin=.hex)
|
||||
$(OBJCOPY) --only-section .qspi -O binary $< $(QSPI)
|
||||
$(OBJCOPY) --only-section .qspi -O ihex $< $(QSPI:.bin=.hex)
|
||||
$(TOOLS)/adjust_crc $(CRCFIX) $(QSPI)
|
||||
$(TOOLS)/check_qspi_crc $(TARGET) $(BUILD)/$(TARGET)_qspi.bin src/$(VARIANT)/qspi_crc.h || ( rm -rf build/$(VARIANT) && exit 1)
|
||||
$(TOOLS)/add_pgm_chsum $(BUILD)/$(TARGET)_flash.bin $@
|
||||
$(SIZE) $<
|
||||
wc -c $@
|
||||
endif
|
||||
|
||||
DECIMAL_CONSTANTS=pi e
|
||||
DECIMAL_SOURCES=$(DECIMAL_CONSTANTS:%=src/decimal-%.h)
|
||||
|
|
1
emsdk
Submodule
1
emsdk
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 4b9e83d629ec8a9468aa3bf388cec447ab771312
|
|
@ -31,18 +31,44 @@
|
|||
|
||||
#include "dmcp.h"
|
||||
#include "sim-dmcp.h"
|
||||
|
||||
#include <QBitmap>
|
||||
#include <QGraphicsPixmapItem>
|
||||
#include <QTimer>
|
||||
#include <target.h>
|
||||
|
||||
|
||||
SimScreen *SimScreen::theScreen = nullptr;
|
||||
|
||||
// A copy of the LCD buffer
|
||||
pixword lcd_copy[sizeof(lcd_buffer) / sizeof(*lcd_buffer)];
|
||||
|
||||
#ifdef WASM
|
||||
|
||||
uintptr_t ui_update_pixmap()
|
||||
// ----------------------------------------------------------------------------
|
||||
// Recompute the pixmap
|
||||
// ----------------------------------------------------------------------------
|
||||
// This should be done on the RPL thread to get a consistent picture
|
||||
{
|
||||
// Monochrome screen
|
||||
pixword mask = ~(~0U << color::BPP);
|
||||
surface s(lcd_buffer, LCD_W, LCD_H, LCD_SCANLINE);
|
||||
for (int y = 0; y < SIM_LCD_H; y++)
|
||||
{
|
||||
for (int xw = 0; xw < SIM_LCD_SCANLINE*color::BPP/32; xw++)
|
||||
{
|
||||
unsigned woffs = y * (SIM_LCD_SCANLINE*color::BPP/32) + xw;
|
||||
if (uint32_t diffs = lcd_copy[woffs] ^ lcd_buffer[woffs])
|
||||
lcd_copy[woffs] = lcd_buffer[woffs];
|
||||
}
|
||||
}
|
||||
return uintptr_t(lcd_buffer);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#else // Qt simulator
|
||||
|
||||
#include <QBitmap>
|
||||
#include <QGraphicsPixmapItem>
|
||||
#include <QTimer>
|
||||
|
||||
SimScreen *SimScreen::theScreen = nullptr;
|
||||
|
||||
SimScreen::SimScreen(QWidget *parent)
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -163,3 +189,4 @@ void SimScreen::refreshScreen()
|
|||
QGraphicsView::update();
|
||||
redraws++;
|
||||
}
|
||||
#endif // WASM
|
||||
|
|
|
@ -29,6 +29,8 @@
|
|||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
// ****************************************************************************
|
||||
|
||||
#ifndef WASM
|
||||
|
||||
#include <QGraphicsView>
|
||||
#include <QGraphicsPixmapItem>
|
||||
|
||||
|
@ -70,4 +72,6 @@ public:
|
|||
static uint redraw_count() { return theScreen->redraws; }
|
||||
};
|
||||
|
||||
#endif // WASM
|
||||
|
||||
#endif // SIMSCREEN_H
|
||||
|
|
|
@ -33,11 +33,14 @@
|
|||
#include "main.h"
|
||||
#include "recorder.h"
|
||||
#include "sim-dmcp.h"
|
||||
#include "sim-rpl.h"
|
||||
#include "target.h"
|
||||
#include "tests.h"
|
||||
#include "ui_sim-window.h"
|
||||
|
||||
#ifdef WASM
|
||||
#include "emcc.h"
|
||||
#else
|
||||
#include "sim-rpl.h"
|
||||
#include "ui_sim-window.h"
|
||||
#include <iostream>
|
||||
#include <QAudioDevice>
|
||||
#include <QAudioFormat>
|
||||
|
@ -53,6 +56,8 @@
|
|||
#include <QtCore>
|
||||
#include <QtGui>
|
||||
#include <QtMath>
|
||||
#endif // WASM
|
||||
|
||||
|
||||
RECORDER(sim_keys, 16, "Recorder keys from the simulator");
|
||||
RECORDER(sim_audio, 16, "Recorder keys from the simulator");
|
||||
|
@ -61,6 +66,9 @@ extern bool run_tests;
|
|||
extern bool db48x_keyboard;
|
||||
extern bool shift_held;
|
||||
extern bool alt_held;
|
||||
|
||||
#ifndef WASM
|
||||
|
||||
MainWindow *MainWindow::mainWindow = nullptr;
|
||||
qreal MainWindow::devicePixelRatio = 1.0;
|
||||
|
||||
|
@ -1044,3 +1052,170 @@ bool tests::image_match(cstring file, int x, int y, int w, int h, bool force)
|
|||
}
|
||||
return data.toImage() == img.toImage();
|
||||
}
|
||||
|
||||
#else // WASM
|
||||
|
||||
// ============================================================================
|
||||
//
|
||||
// Platform support on WASM
|
||||
//
|
||||
// ============================================================================
|
||||
|
||||
void ui_refresh()
|
||||
// ----------------------------------------------------------------------------
|
||||
// Request a refresh of the LCD
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
uint ui_refresh_count()
|
||||
// ----------------------------------------------------------------------------
|
||||
// Return the number of times the display was actually udpated
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void ui_screenshot()
|
||||
// ----------------------------------------------------------------------------
|
||||
// Take a screen snapshot
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
void ui_push_key(int k)
|
||||
// ----------------------------------------------------------------------------
|
||||
// Update display when pushing a key
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
// key_push(k);
|
||||
}
|
||||
|
||||
|
||||
void ui_ms_sleep(uint ms_delay)
|
||||
// ----------------------------------------------------------------------------
|
||||
// Suspend the current thread for the given interval in milliseconds
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
int ui_file_selector(const char *title,
|
||||
const char *base_dir,
|
||||
const char *ext,
|
||||
file_sel_fn callback,
|
||||
void *data,
|
||||
int disp_new,
|
||||
int overwrite_check)
|
||||
// ----------------------------------------------------------------------------
|
||||
// File selector function
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void ui_save_setting(const char *name, const char *value)
|
||||
// ----------------------------------------------------------------------------
|
||||
// Save some settings
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
size_t ui_read_setting(const char *name, char *value, size_t maxlen)
|
||||
// ----------------------------------------------------------------------------
|
||||
// Save some settings
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
uint last_battery_ms = 0;
|
||||
uint battery = 1000;
|
||||
bool charging = false;
|
||||
|
||||
uint ui_battery()
|
||||
// ----------------------------------------------------------------------------
|
||||
// Return the battery voltage
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
uint now = sys_current_ms();
|
||||
if (last_battery_ms < now - 1000)
|
||||
last_battery_ms = now - 1000;
|
||||
|
||||
if (charging)
|
||||
{
|
||||
battery += (1000 - battery) * (now - last_battery_ms) / 6000;
|
||||
if (battery >= 990)
|
||||
charging = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
battery -= (now - last_battery_ms) / 10;
|
||||
uint v = battery * (BATTERY_VMAX - BATTERY_VMIN) / 1000 + BATTERY_VMIN;
|
||||
if (v < BATTERY_VLOW)
|
||||
charging = true;
|
||||
}
|
||||
|
||||
last_battery_ms = now;
|
||||
return battery;
|
||||
}
|
||||
|
||||
|
||||
bool ui_charging()
|
||||
// ----------------------------------------------------------------------------
|
||||
// Return true if USB-powered or not
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
return charging;
|
||||
}
|
||||
|
||||
|
||||
void ui_start_buzzer(uint frequency)
|
||||
// ----------------------------------------------------------------------------
|
||||
// Start buzzer at given frequency
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void ui_stop_buzzer()
|
||||
// ----------------------------------------------------------------------------
|
||||
// Stop buzzer in simulator
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void ui_run_rpl()
|
||||
// ----------------------------------------------------------------------------
|
||||
// Thread entry point
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
program_main();
|
||||
}
|
||||
|
||||
|
||||
|
||||
int ui_wrap_io(file_sel_fn callback, const char *path, void *data, bool)
|
||||
// ----------------------------------------------------------------------------
|
||||
// Wrap I/Os into thread safety / file sync
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
cstring name = path;
|
||||
for (cstring p = path; *p; p++)
|
||||
if (*p == '/' || *p == '\\')
|
||||
name = p + 1;
|
||||
return callback(path, name, data);
|
||||
}
|
||||
|
||||
|
||||
#endif // WASM
|
||||
|
|
|
@ -29,6 +29,8 @@
|
|||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
// ****************************************************************************
|
||||
|
||||
#ifndef WASM
|
||||
|
||||
#include "sim-rpl.h"
|
||||
#include "tests.h"
|
||||
#include "ui_sim-window.h"
|
||||
|
@ -190,12 +192,5 @@ static void postToThread(F && fun, QThread *thread = qApp->thread())
|
|||
QMetaObject::invokeMethod(obj, std::forward<F>(fun));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============================================================================
|
||||
//
|
||||
// Audio support (lifted from QT audiooutput example)
|
||||
//
|
||||
// ============================================================================
|
||||
|
||||
#endif // WASM
|
||||
#endif // SIM_WINDOW_H
|
||||
|
|
|
@ -316,6 +316,25 @@ bool power_check(bool draw_off_image)
|
|||
}
|
||||
|
||||
|
||||
#ifdef WASM
|
||||
uint memory_size = 100;
|
||||
volatile uint test_command = 0;
|
||||
bool noisy_tests = false;
|
||||
bool tests::running = false;
|
||||
|
||||
|
||||
void ui_init()
|
||||
// ----------------------------------------------------------------------------
|
||||
// Initialization for the JavaScript version
|
||||
// ----------------------------------------------------------------------------
|
||||
{
|
||||
program_init();
|
||||
redraw_lcd(true);
|
||||
last_keystroke_time = sys_current_ms();
|
||||
}
|
||||
#endif // WASM
|
||||
|
||||
|
||||
extern "C" void program_main()
|
||||
// ----------------------------------------------------------------------------
|
||||
// DMCP main entry point and main loop
|
||||
|
|
|
@ -101,4 +101,10 @@ int ui_wrap_io(file_sel_fn callback,
|
|||
void *data,
|
||||
bool writing);
|
||||
|
||||
#ifdef WASM
|
||||
void ui_init();
|
||||
uintptr_t ui_update_pixmap();
|
||||
void ui_run_rpl();
|
||||
# endif // WASM
|
||||
//
|
||||
#endif // SIM_DMCP
|
||||
|
|
1
src/wasm/dmcp.cc
Symbolic link
1
src/wasm/dmcp.cc
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../sim/dmcp.cpp
|
1
src/wasm/dmcp.h
Symbolic link
1
src/wasm/dmcp.h
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../sim/dmcp.h
|
1
src/wasm/dmcp_fonts.c
Symbolic link
1
src/wasm/dmcp_fonts.c
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../sim/dmcp_fonts.c
|
10
src/wasm/emcc.h
Normal file
10
src/wasm/emcc.h
Normal file
|
@ -0,0 +1,10 @@
|
|||
#include <emscripten/bind.h>
|
||||
using namespace emscripten;
|
||||
|
||||
EMSCRIPTEN_BINDINGS(my_module) {
|
||||
function("ui_battery", &ui_battery);
|
||||
function("ui_update_pixmap", &ui_update_pixmap);
|
||||
function("ui_run_rpl", &ui_run_rpl);
|
||||
function("ui_init", &ui_init);
|
||||
function("ui_push_key", &key_push);
|
||||
}
|
1
src/wasm/ff_ifc.h
Symbolic link
1
src/wasm/ff_ifc.h
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../sim/ff_ifc.h
|
356
src/wasm/index.html
Normal file
356
src/wasm/index.html
Normal file
|
@ -0,0 +1,356 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>DB48X</title>
|
||||
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=0.9, maximum-scale=1.0" />
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<link rel="apple-touch-icon" href="./logo.png">
|
||||
<script src="./db48x.js"></script>
|
||||
<style>
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: auto auto;
|
||||
background-color: black;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-ms-user-select: none; /* IE 10 and IE 11 */
|
||||
user-select: none; /* Standard syntax */
|
||||
}
|
||||
|
||||
canvas {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#keyCanvas {
|
||||
background-image: url('keyboard-db48x.png');
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
margin: 0 auto;
|
||||
align-self: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#lcdCanvas {
|
||||
align-self: center;
|
||||
width: 100%;
|
||||
/* IE, only works on <img> tags */
|
||||
-ms-interpolation-mode: nearest-neighbor;
|
||||
/* Firefox */
|
||||
image-rendering: crisp-edges;
|
||||
/* Chromium + Safari */
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
#calcContainer {
|
||||
margin: auto;
|
||||
grid-column: 1 / 1;
|
||||
grid-row: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="calcContainer">
|
||||
<canvas id="lcdCanvas"></canvas>
|
||||
<canvas id="keyCanvas"></canvas>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementsByTagName('body')[0].onscroll = function(event) {
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
const DEBUG = false;
|
||||
const SIM_LCD_H = 240; // Height of the LCD
|
||||
const SIM_LCD_SCANLINE = 416; // Width of the LCD scanline
|
||||
const SIM_LCD_OFFSET = 16;
|
||||
const BPP = 1; // Bits per pixel
|
||||
const WORD_SIZE = 32; // Size of the word in bits
|
||||
const MASK = (1 << BPP) - 1; // Mask to extract BPP bits
|
||||
const KB_RATIO = 1.3; // Keyboard aspect ratio
|
||||
let keyBuffer = []; // keyBuffer
|
||||
let threadSince = 0;
|
||||
let threadInterval = 50; // 50ms
|
||||
|
||||
// Keyboard mapping with key areas
|
||||
const key_map = [
|
||||
[ 38, 0.03, 0.15, 0.03, 0.10 ], // Qt::Key_F1,
|
||||
[ 39, 0.20, 0.32, 0.03, 0.10 ], // Qt::Key_F2,
|
||||
[ 40, 0.345, 0.47, 0.03, 0.10 ], // Qt::Key_F3,
|
||||
[ 41, 0.52, 0.63, 0.03, 0.10 ], // Qt::Key_F4,
|
||||
[ 42, 0.68, 0.80, 0.03, 0.10 ], // Qt::Key_F5,
|
||||
[ 43, 0.83, 0.95, 0.03, 0.10 ], // Qt::Key_F6,
|
||||
|
||||
[ 1, 0.03, 0.15, 0.15, 0.22 ], // Qt::Key_A,
|
||||
[ 2, 0.20, 0.32, 0.15, 0.22 ], // Qt::Key_B,
|
||||
[ 3, 0.345, 0.47, 0.15, 0.22 ], // Qt::Key_C,
|
||||
[ 4, 0.52, 0.63, 0.15, 0.22 ], // Qt::Key_D,
|
||||
[ 5, 0.68, 0.80, 0.15, 0.22 ], // Qt::Key_E,
|
||||
[ 6, 0.83, 0.95, 0.15, 0.22 ], // Qt::Key_F,
|
||||
|
||||
[ 7, 0.03, 0.15, 0.275, 0.345 ], // Qt::Key_G,
|
||||
[ 8, 0.20, 0.32, 0.275, 0.345 ], // Qt::Key_H,
|
||||
[ 9, 0.345, 0.47, 0.275, 0.345 ], // Qt::Key_I,
|
||||
[ 10, 0.52, 0.63, 0.275, 0.345 ], // Qt::Key_J,
|
||||
[ 11, 0.68, 0.80, 0.275, 0.345 ], // Qt::Key_K,
|
||||
[ 12, 0.83, 0.95, 0.275, 0.345 ], // Qt::Key_L,
|
||||
|
||||
[ 13, 0.03, 0.32, 0.40, 0.47 ], // Qt::Key_Return,
|
||||
[ 14, 0.345, 0.47, 0.40, 0.47 ], // Qt::Key_M,
|
||||
[ 15, 0.51, 0.64, 0.40, 0.47 ], // Qt::Key_N,
|
||||
[ 16, 0.68, 0.80, 0.40, 0.47 ], // Qt::Key_O,
|
||||
[ 17, 0.83, 0.95, 0.40, 0.47 ], // Qt::Key_Backspace
|
||||
|
||||
[ 18, 0.03, 0.15, 0.52, 0.59 ], // Qt::Key_Up,
|
||||
[ 19, 0.23, 0.36, 0.52, 0.59 ], // Qt::Key_7,
|
||||
[ 20, 0.42, 0.56, 0.52, 0.59 ], // Qt::Key_8,
|
||||
[ 21, 0.62, 0.75, 0.52, 0.59 ], // Qt::Key_9,
|
||||
[ 22, 0.81, 0.95, 0.52, 0.59 ], // Qt::Key_Slash,
|
||||
|
||||
[ 23, 0.03, 0.15, 0.645, 0.715 ], // Qt::Key_Down,
|
||||
[ 24, 0.23, 0.36, 0.645, 0.715 ], // Qt::Key_4,
|
||||
[ 25, 0.42, 0.56, 0.645, 0.715 ], // Qt::Key_5,
|
||||
[ 26, 0.62, 0.75, 0.645, 0.715 ], // Qt::Key_6,
|
||||
[ 27, 0.81, 0.95, 0.645, 0.715 ], // Qt::Key_Asterisk,
|
||||
|
||||
[ 28, 0.028, 0.145, 0.77, 0.84 ], // Qt::Key_Control,
|
||||
[ 29, 0.23, 0.36, 0.77, 0.84 ], // Qt::Key_1,
|
||||
[ 30, 0.42, 0.56, 0.77, 0.84 ], // Qt::Key_2,
|
||||
[ 31, 0.62, 0.75, 0.77, 0.84 ], // Qt::Key_3,
|
||||
[ 32, 0.81, 0.95, 0.77, 0.84 ], // Qt::Key_Minus,
|
||||
|
||||
[ 33, 0.03, 0.15, 0.89, 0.97 ], // Qt::Key_Escape,
|
||||
[ 34, 0.23, 0.36, 0.89, 0.97 ], // Qt::Key_0,
|
||||
[ 35, 0.42, 0.55, 0.89, 0.97 ], // Qt::Key_Period,
|
||||
[ 36, 0.62, 0.74, 0.89, 0.97 ], // Qt::Key_Question,
|
||||
[ 37, 0.81, 0.95, 0.89, 0.97 ], // Qt::Key_Plus,
|
||||
];
|
||||
|
||||
const keystroke_map = {
|
||||
"0": 34,
|
||||
"1": 29,
|
||||
"2": 30,
|
||||
"3": 31,
|
||||
"4": 24,
|
||||
"5": 25,
|
||||
"6": 26,
|
||||
"7": 19,
|
||||
"8": 20,
|
||||
"9": 21,
|
||||
".": 35,
|
||||
"+": 37,
|
||||
"-": 32,
|
||||
"*": 27,
|
||||
"/": 22,
|
||||
"'": 6,
|
||||
"=": 36,
|
||||
" ": 36,
|
||||
"`": 1,
|
||||
"Alt": 1,
|
||||
"Meta": 1,
|
||||
"Enter": 13,
|
||||
"Backspace": 17,
|
||||
"Escape": 33,
|
||||
"F1": 38,
|
||||
"F2": 39,
|
||||
"F3": 40,
|
||||
"F4": 41,
|
||||
"F5": 42,
|
||||
"F6": 43,
|
||||
"Up": 18,
|
||||
"Down": 23,
|
||||
"Shift": 28,
|
||||
"Control": 28,
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
"c": 3,
|
||||
"d": 4,
|
||||
"e": 5,
|
||||
"f": 6,
|
||||
"g": 7,
|
||||
"h": 8,
|
||||
"i": 9,
|
||||
"j": 10,
|
||||
"k": 11,
|
||||
"l": 12,
|
||||
"m": 14,
|
||||
"n": 15,
|
||||
"o": 16,
|
||||
"p": 36,
|
||||
"q": 20,
|
||||
"r": 21,
|
||||
"s": 22,
|
||||
"t": 24,
|
||||
"u": 25,
|
||||
"v": 26,
|
||||
"w": 27,
|
||||
"x": 29,
|
||||
"y": 30,
|
||||
"z": 31,
|
||||
"ArrowLeft": 18,
|
||||
"ArrowRight": 23,
|
||||
"ArrowUp": 18,
|
||||
"ArrowDown": 23,
|
||||
};
|
||||
|
||||
// canvas for the keyboard
|
||||
const canvas_key = document.getElementById('keyCanvas');
|
||||
const KEY_H = SIM_LCD_SCANLINE * KB_RATIO;
|
||||
const KEY_W = SIM_LCD_SCANLINE - SIM_LCD_OFFSET;
|
||||
canvas_key.width = KEY_W;
|
||||
canvas_key.height = KEY_H;
|
||||
const ctx_key = canvas_key.getContext('2d');
|
||||
ctx_key.clearRect( 0, 0, KEY_W, KEY_H);
|
||||
// debug presses
|
||||
if (DEBUG) {
|
||||
ctx_key.strokeStyle = "red";
|
||||
for (let i=0; i<key_map.length; i++) {
|
||||
ctx_key.beginPath();
|
||||
ctx_key.moveTo(key_map[i][1]*KEY_W, key_map[i][3]*KEY_H);
|
||||
ctx_key.lineTo(key_map[i][1]*KEY_W, key_map[i][4]*KEY_H);
|
||||
ctx_key.lineTo(key_map[i][2]*KEY_W, key_map[i][4]*KEY_H);
|
||||
ctx_key.lineTo(key_map[i][2]*KEY_W, key_map[i][3]*KEY_H);
|
||||
ctx_key.lineTo(key_map[i][1]*KEY_W, key_map[i][3]*KEY_H);
|
||||
ctx_key.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
// get the mouse/touch position
|
||||
const getMousePosition = function(canvas, event) {
|
||||
let rect = canvas.getBoundingClientRect();
|
||||
let x = event.clientX - rect.left;
|
||||
let y = event.clientY - rect.top;
|
||||
if (DEBUG) {
|
||||
console.log("clicked x: " + x, " y: " + y);
|
||||
}
|
||||
// should use html elements for the keys in future
|
||||
for (let i=0; i<key_map.length; i++) {
|
||||
if (x > key_map[i][1]*KEY_W && x < key_map[i][2]*KEY_W && y > key_map[i][3]*KEY_H && y < key_map[i][4]*KEY_H) {
|
||||
if (DEBUG) {
|
||||
console.log("clicked:", key_map[i][0]);
|
||||
}
|
||||
keyBuffer.push(key_map[i][0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const processKey = function() {
|
||||
if (keyBuffer.length > 0) {
|
||||
let key = keyBuffer.shift();
|
||||
if (DEBUG) {
|
||||
console.log("processed key:", key);
|
||||
}
|
||||
Module.ui_push_key(key);
|
||||
Module.ui_push_key(0);
|
||||
}
|
||||
}
|
||||
|
||||
// add an event listener to the canvas so we can parse the key presses
|
||||
canvas_key.addEventListener("mousedown", function (e) {
|
||||
getMousePosition(canvas_key, e);
|
||||
});
|
||||
|
||||
// add an event listener to the canvas so we can parse the key presses
|
||||
document.addEventListener("keyup", (event)=>{
|
||||
// check that event.key matches a key in the keystroke_map
|
||||
if (Object.keys(keystroke_map).includes(event.key)) {
|
||||
keyBuffer.push(keystroke_map[event.key]);
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
console.log("Key not found in keystroke_map: ", event.key);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// draw the LCD panel
|
||||
const canvas = document.getElementById('lcdCanvas');
|
||||
canvas.width = SIM_LCD_SCANLINE - SIM_LCD_OFFSET;
|
||||
canvas.height = SIM_LCD_H;
|
||||
const ctx = canvas.getContext('2d');
|
||||
const imgData = ctx.createImageData(canvas.width, canvas.height);
|
||||
|
||||
// parse the LCD data and draw to the canvas
|
||||
const drawBitmap = function(canvasId, dataArray, width, height) {
|
||||
const canvas = document.getElementById(canvasId);
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
canvas.width = SIM_LCD_SCANLINE - SIM_LCD_OFFSET;
|
||||
canvas.height = SIM_LCD_H;
|
||||
|
||||
const imageData = ctx.createImageData(width, height);
|
||||
const data = imageData.data;
|
||||
|
||||
for (let i = 0; i < dataArray.length; i++) {
|
||||
const bitmask = dataArray[i];
|
||||
for (let bit = 0; bit < 32; bit++) {
|
||||
const value = (bitmask >> bit) & 1;
|
||||
const pixelIndex = i * 32 + bit;
|
||||
const x = pixelIndex % width;
|
||||
const y = Math.floor(pixelIndex / width);
|
||||
const mirroredX = width - 1 - x; // Mirroring the x position
|
||||
const index = (y * width + mirroredX) * 4;
|
||||
// const index = (y * width + x) * 4;
|
||||
|
||||
const color = value === 1 ? 213 : 0;
|
||||
data[index] = color;
|
||||
data[index + 1] = color;
|
||||
data[index + 2] = color;
|
||||
data[index + 3] = 255; // Alpha channel
|
||||
}
|
||||
}
|
||||
|
||||
// offset -16 pixels to the left
|
||||
ctx.putImageData(imageData, -SIM_LCD_OFFSET, 0);
|
||||
}
|
||||
|
||||
|
||||
Module.onRuntimeInitialized = function() {
|
||||
|
||||
// initialise the calculator
|
||||
console.log("loaded wasm...");
|
||||
console.log("ui_init()",Module.ui_init());
|
||||
|
||||
// fetch and display the LCD
|
||||
const getLCD = function() {
|
||||
var heap = Module.ui_update_pixmap();
|
||||
const lcd_buffer = []
|
||||
for (let v=0; v < (13*240); v++) {
|
||||
lcd_buffer.push( Module.HEAP32[heap/Int32Array.BYTES_PER_ELEMENT+v] )
|
||||
}
|
||||
drawBitmap('lcdCanvas', lcd_buffer, SIM_LCD_SCANLINE, SIM_LCD_H);
|
||||
}
|
||||
|
||||
// thread to draw the LCD and run the calculator
|
||||
const drawThread = function() {
|
||||
let now = new Date().getTime();
|
||||
let dt = now - threadSince;
|
||||
processKey();
|
||||
if (dt > threadInterval) {
|
||||
Module.ui_run_rpl();
|
||||
getLCD();
|
||||
threadSince = now;
|
||||
}
|
||||
window.requestAnimationFrame(drawThread);
|
||||
}
|
||||
|
||||
window.requestAnimationFrame(drawThread);
|
||||
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
1
src/wasm/lft_ifc.h
Symbolic link
1
src/wasm/lft_ifc.h
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../sim/lft_ifc.h
|
1
src/wasm/main.h
Symbolic link
1
src/wasm/main.h
Symbolic link
|
@ -0,0 +1 @@
|
|||
../dm32/main.h
|
1
src/wasm/qspi_crc.h
Symbolic link
1
src/wasm/qspi_crc.h
Symbolic link
|
@ -0,0 +1 @@
|
|||
../dm32/qspi_crc.h
|
1
src/wasm/sim-dmcp.h
Symbolic link
1
src/wasm/sim-dmcp.h
Symbolic link
|
@ -0,0 +1 @@
|
|||
../dmcp/sim-dmcp.h
|
1
src/wasm/sim-screen.cc
Symbolic link
1
src/wasm/sim-screen.cc
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../sim/sim-screen.cpp
|
1
src/wasm/sim-screen.h
Symbolic link
1
src/wasm/sim-screen.h
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../sim/sim-screen.h
|
1
src/wasm/sim-window.cc
Symbolic link
1
src/wasm/sim-window.cc
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../sim/sim-window.cpp
|
1
src/wasm/sim-window.h
Symbolic link
1
src/wasm/sim-window.h
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../sim/sim-window.h
|
Loading…
Reference in a new issue