Compare commits

...

14 Commits
master ... wasm

28 changed files with 1641 additions and 134 deletions

View File

@ -32,7 +32,7 @@ endif
ifneq ($(shell which xdg-open)$(FREEDESKTOP),)
# Running on an FreeDesktop environment, configure for (optional) installation
DESTDIR ?=
DESTDIR ?=
PREFIX ?= /usr/local
DATA_DIR ?= $(PREFIX)/share/sameboy/
FREEDESKTOP ?= true
@ -62,7 +62,7 @@ endif
# Use clang if it's available.
ifeq ($(origin CC),default)
ifneq (, $(shell which clang))
CC := clang
CC := clang
endif
endif
@ -203,7 +203,7 @@ quicklook: $(BIN)/SameBoy.qlgenerator
sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/mgb_boot.bin $(BIN)/SDL/cgb0_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/agb_boot.bin $(BIN)/SDL/sgb_boot.bin $(BIN)/SDL/sgb2_boot.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/background.bmp $(BIN)/SDL/Shaders
bootroms: $(BIN)/BootROMs/agb_boot.bin $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/cgb0_boot.bin $(BIN)/BootROMs/dmg_boot.bin $(BIN)/BootROMs/sgb_boot.bin $(BIN)/BootROMs/sgb2_boot.bin
tester: $(TESTER_TARGET) $(BIN)/tester/dmg_boot.bin $(BIN)/tester/cgb_boot.bin $(BIN)/tester/agb_boot.bin $(BIN)/tester/sgb_boot.bin $(BIN)/tester/sgb2_boot.bin
all: cocoa sdl tester libretro
all: cocoa sdl tester libretro wasm
# Get a list of our source files and their respective object file targets
@ -228,7 +228,7 @@ TESTER_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(TESTER_SOURCES))
# Automatic dependency generation
ifneq ($(filter-out clean bootroms libretro %.bin, $(MAKECMDGOALS)),)
ifneq ($(filter-out clean bootroms libretro wasm %.bin, $(MAKECMDGOALS)),)
-include $(CORE_OBJECTS:.o=.dep)
ifneq ($(filter $(MAKECMDGOALS),sdl),)
-include $(SDL_OBJECTS:.o=.dep)
@ -262,12 +262,12 @@ $(OBJ)/SDL/%.c.o: SDL/%.c
$(OBJ)/%.c.o: %.c
-@$(MKDIR) -p $(dir $@)
$(CC) $(CFLAGS) $(FAT_FLAGS) -c $< -o $@
# HexFiend requires more flags
$(OBJ)/HexFiend/%.m.o: HexFiend/%.m
-@$(MKDIR) -p $(dir $@)
$(CC) $(CFLAGS) $(FAT_FLAGS) $(OCFLAGS) -c $< -o $@ -fno-objc-arc -include HexFiend/HexFiend_2_Framework_Prefix.pch
$(OBJ)/%.m.o: %.m
-@$(MKDIR) -p $(dir $@)
$(CC) $(CFLAGS) $(FAT_FLAGS) $(OCFLAGS) -c $< -o $@
@ -307,7 +307,7 @@ endif
$(BIN)/SameBoy.app/Contents/Resources/Base.lproj/%.nib: Cocoa/%.xib
ibtool --compile $@ $^ 2>&1 | cat -
# Quick Look generator
$(BIN)/SameBoy.qlgenerator: $(BIN)/SameBoy.qlgenerator/Contents/MacOS/SameBoyQL \
@ -329,7 +329,7 @@ $(BIN)/SameBoy.qlgenerator/Contents/MacOS/SameBoyQL: $(CORE_OBJECTS) $(QUICKLOOK
$(BIN)/SameBoy.qlgenerator/Contents/Resources/cgb_boot_fast.bin: $(BIN)/BootROMs/cgb_boot_fast.bin
-@$(MKDIR) -p $(dir $@)
cp -f $^ $@
# SDL Port
# Unix versions build only one binary
@ -356,7 +356,7 @@ $(OBJ)/%.o: %.rc
else
$(OBJ)/%.res: %.rc
-@$(MKDIR) -p $(dir $@)
rc /fo $@ /dVERSION=\"$(VERSION)\" $^
rc /fo $@ /dVERSION=\"$(VERSION)\" $^
%.o: %.res
cvtres /OUT:"$@" $^
@ -416,7 +416,7 @@ $(OBJ)/%.2bpp: %.png
$(OBJ)/BootROMs/SameBoyLogo.pb12: $(OBJ)/BootROMs/SameBoyLogo.2bpp $(PB12_COMPRESS)
$(realpath $(PB12_COMPRESS)) < $< > $@
$(PB12_COMPRESS): BootROMs/pb12.c
$(NATIVE_CC) -std=c99 -Wall -Werror $< -o $@
@ -436,6 +436,10 @@ $(BIN)/BootROMs/%.bin: BootROMs/%.asm $(OBJ)/BootROMs/SameBoyLogo.pb12
libretro:
CFLAGS="$(WARNINGS)" $(MAKE) -C libretro
# WASM core (uses its own build system)
wasm:
$(MAKE) -C wasm
# install for Linux/FreeDesktop/etc.
# Does not install mimetype icons because FreeDesktop is cursed abomination with no right to exist.
# If you somehow find a reasonable way to make associate an icon with an extension in this dumpster
@ -465,15 +469,15 @@ endif
$(DESTDIR)$(PREFIX)/share/icons/hicolor/%/apps/sameboy.png: FreeDesktop/AppIcon/%.png
-@$(MKDIR) -p $(dir $@)
cp -f $^ $@
$(DESTDIR)$(PREFIX)/share/icons/hicolor/%/mimetypes/x-gameboy-rom.png: FreeDesktop/Cartridge/%.png
-@$(MKDIR) -p $(dir $@)
cp -f $^ $@
$(DESTDIR)$(PREFIX)/share/icons/hicolor/%/mimetypes/x-gameboy-color-rom.png: FreeDesktop/ColorCartridge/%.png
-@$(MKDIR) -p $(dir $@)
cp -f $^ $@
$(DESTDIR)$(PREFIX)/share/mime/packages/sameboy.xml: FreeDesktop/sameboy.xml
-@$(MKDIR) -p $(dir $@)
cp -f $^ $@
@ -483,4 +487,4 @@ endif
clean:
rm -rf build
.PHONY: libretro tester
.PHONY: libretro tester wasm

View File

@ -5,12 +5,14 @@
#include <stddef.h>
#include <Core/gb.h>
unsigned GB_audio_default_sample_rate(void);
bool GB_audio_is_playing(void);
void GB_audio_set_paused(bool paused);
void GB_audio_clear_queue(void);
unsigned GB_audio_get_frequency(void);
unsigned GB_audio_get_sample_rate(void);
size_t GB_audio_get_queue_length(void);
void GB_audio_queue_sample(GB_sample_t *sample);
void GB_audio_init(void);
void GB_audio_init(unsigned sample_rate);
void GB_audio_destroy(void);
#endif /* sdl_audio_h */

View File

@ -29,6 +29,11 @@ static SDL_AudioSpec want_aspec, have_aspec;
static unsigned buffer_pos = 0;
static GB_sample_t audio_buffer[AUDIO_BUFFER_SIZE];
unsigned GB_audio_default_sample_rate(void)
{
return AUDIO_FREQUENCY;
}
bool GB_audio_is_playing(void)
{
return SDL_GetAudioDeviceStatus(device_id) == SDL_AUDIO_PLAYING;
@ -45,7 +50,7 @@ void GB_audio_clear_queue(void)
SDL_ClearQueuedAudio(device_id);
}
unsigned GB_audio_get_frequency(void)
unsigned GB_audio_get_sample_rate(void)
{
return have_aspec.freq;
}
@ -65,11 +70,11 @@ void GB_audio_queue_sample(GB_sample_t *sample)
}
}
void GB_audio_init(void)
void GB_audio_init(unsigned sample_rate)
{
/* Configure Audio */
memset(&want_aspec, 0, sizeof(want_aspec));
want_aspec.freq = AUDIO_FREQUENCY;
want_aspec.freq = sample_rate == 0 ? GB_audio_default_sample_rate() : sample_rate;
want_aspec.format = AUDIO_S16SYS;
want_aspec.channels = 2;
want_aspec.samples = 512;
@ -94,3 +99,9 @@ void GB_audio_init(void)
device_id = SDL_OpenAudioDevice(0, 0, &want_aspec, &have_aspec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_SAMPLES_CHANGE);
}
void GB_audio_destroy() {
GB_audio_set_paused(true);
SDL_CloseAudioDevice(device_id);
}

View File

@ -502,15 +502,15 @@ static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample)
if (turbo_down) {
static unsigned skip = 0;
skip++;
if (skip == GB_audio_get_frequency() / 8) {
if (skip == GB_audio_get_sample_rate() / 8) {
skip = 0;
}
if (skip > GB_audio_get_frequency() / 16) {
if (skip > GB_audio_get_sample_rate() / 16) {
return;
}
}
if (GB_audio_get_queue_length() / sizeof(*sample) > GB_audio_get_frequency() / 4) {
if (GB_audio_get_queue_length() / sizeof(*sample) > GB_audio_get_sample_rate() / 4) {
return;
}
@ -644,7 +644,7 @@ restart:
GB_set_rgb_encode_callback(&gb, rgb_encode);
GB_set_rumble_callback(&gb, rumble);
GB_set_rumble_mode(&gb, configuration.rumble_mode);
GB_set_sample_rate(&gb, GB_audio_get_frequency());
GB_set_sample_rate(&gb, GB_audio_get_sample_rate());
GB_set_color_correction_mode(&gb, configuration.color_correction_mode);
GB_set_light_temperature(&gb, (configuration.color_temperature - 10.0) / 10.0);
GB_set_interference_volume(&gb, configuration.interference_volume / 100.0);
@ -886,7 +886,7 @@ int main(int argc, char **argv)
pixel_format = SDL_AllocFormat(SDL_PIXELFORMAT_ABGR8888);
}
GB_audio_init();
GB_audio_init(0);
SDL_EventState(SDL_DROPFILE, SDL_ENABLE);

View File

@ -7,36 +7,36 @@
STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution)
{
/* Curve and pixel ratio */
float y_curve = cos(position.x - 0.5) * CURVENESS + (1 - CURVENESS);
float y_curve = cos(position.x - 0.5) * CURVENESS + (1.0 - CURVENESS);
float y_multiplier = 8.0 / 7.0 / y_curve;
position.y *= y_multiplier;
position.y -= (y_multiplier - 1) / 2;
position.y -= (y_multiplier - 1.0) / 2.0;
if (position.y < 0.0) return vec4(0,0,0,0);
if (position.y > 1.0) return vec4(0,0,0,0);
float x_curve = cos(position.y - 0.5) * CURVENESS + (1 - CURVENESS);
float x_multiplier = 1/x_curve;
float x_curve = cos(position.y - 0.5) * CURVENESS + (1.0 - CURVENESS);
float x_multiplier = 1.0 / x_curve;
position.x *= x_multiplier;
position.x -= (x_multiplier - 1) / 2;
if (position.x < 0.0) return vec4(0,0,0,0);
if (position.x > 1.0) return vec4(0,0,0,0);
position.x -= (x_multiplier - 1.0) / 2.0;
if (position.x < 0.0) return vec4(0.0, 0.0, 0.0, 0.0);
if (position.x > 1.0) return vec4(0.0, 0.0, 0.0, 0.0);
/* Setting up common vars */
vec2 pos = fract(position * input_resolution);
vec2 sub_pos = fract(position * input_resolution * 6);
vec2 sub_pos = fract(position * input_resolution * 6.0);
vec4 center = texture(image, position);
vec4 left = texture(image, position - vec2(1.0 / input_resolution.x, 0));
vec4 right = texture(image, position + vec2(1.0 / input_resolution.x, 0));
vec4 left = texture(image, position - vec2(1.0 / input_resolution.x, 0.0));
vec4 right = texture(image, position + vec2(1.0 / input_resolution.x, 0.0));
/* Vertical blurring */
if (pos.y < 1.0 / 6.0) {
center = mix(center, texture(image, position + vec2(0, -1.0 / input_resolution.y)), 0.5 - sub_pos.y / 2.0);
center = mix(center, texture(image, position + vec2(0.0, -1.0 / input_resolution.y)), 0.5 - sub_pos.y / 2.0);
left = mix(left, texture(image, position + vec2(-1.0 / input_resolution.x, -1.0 / input_resolution.y)), 0.5 - sub_pos.y / 2.0);
right = mix(right, texture(image, position + vec2( 1.0 / input_resolution.x, -1.0 / input_resolution.y)), 0.5 - sub_pos.y / 2.0);
}
else if (pos.y > 5.0 / 6.0) {
center = mix(center, texture(image, position + vec2(0, 1.0 / input_resolution.y)), sub_pos.y / 2.0);
center = mix(center, texture(image, position + vec2(0.0, 1.0 / input_resolution.y)), sub_pos.y / 2.0);
left = mix(left, texture(image, position + vec2(-1.0 / input_resolution.x, 1.0 / input_resolution.y)), sub_pos.y / 2.0);
right = mix(right, texture(image, position + vec2( 1.0 / input_resolution.x, 1.0 / input_resolution.y)), sub_pos.y / 2.0);
}
@ -44,10 +44,10 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou
/* Scanlines */
float scanline_multiplier;
if (pos.y < 0.5) {
scanline_multiplier = (pos.y * 2) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH);
scanline_multiplier = (pos.y * 2.0) * SCANLINE_DEPTH + (1.0 - SCANLINE_DEPTH);
}
else {
scanline_multiplier = ((1 - pos.y) * 2) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH);
scanline_multiplier = ((1.0 - pos.y) * 2.0) * SCANLINE_DEPTH + (1.0 - SCANLINE_DEPTH);
}
center *= scanline_multiplier;
@ -55,7 +55,7 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou
right *= scanline_multiplier;
/* Vertical seperator for shadow masks */
bool odd = bool(int((position * input_resolution).x) & 1);
bool odd = bool(int((position * input_resolution).x) >= 1);
if (odd) {
pos.y += 0.5;
pos.y = fract(pos.y);
@ -63,15 +63,15 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou
if (pos.y < 1.0 / 3.0) {
float gradient_position = pos.y * 3.0;
center *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH);
left *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH);
right *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH);
center *= gradient_position * VERTICAL_BORDER_DEPTH + (1.0 - VERTICAL_BORDER_DEPTH);
left *= gradient_position * VERTICAL_BORDER_DEPTH + (1.0 - VERTICAL_BORDER_DEPTH);
right *= gradient_position * VERTICAL_BORDER_DEPTH + (1.0 - VERTICAL_BORDER_DEPTH);
}
else if (pos.y > 2.0 / 3.0) {
float gradient_position = (1 - pos.y) * 3.0;
center *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH);
left *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH);
right *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH);
float gradient_position = (1.0 - pos.y) * 3.0;
center *= gradient_position * VERTICAL_BORDER_DEPTH + (1.0 - VERTICAL_BORDER_DEPTH);
left *= gradient_position * VERTICAL_BORDER_DEPTH + (1.0 - VERTICAL_BORDER_DEPTH);
right *= gradient_position * VERTICAL_BORDER_DEPTH + (1.0 - VERTICAL_BORDER_DEPTH);
}
/* Blur the edges of the separators of adjacent columns */
@ -82,26 +82,26 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou
if (pos.y < 1.0 / 3.0) {
float gradient_position = pos.y * 3.0;
if (pos.x < 0.5) {
gradient_position = 1 - (1 - gradient_position) * (1 - (pos.x) * 6.0);
gradient_position = 1.0 - (1.0 - gradient_position) * (1.0 - (pos.x) * 6.0);
}
else {
gradient_position = 1 - (1 - gradient_position) * (1 - (1 - pos.x) * 6.0);
gradient_position = 1.0 - (1.0 - gradient_position) * (1.0 - (1.0 - pos.x) * 6.0);
}
center *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH);
left *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH);
right *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH);
center *= gradient_position * VERTICAL_BORDER_DEPTH + (1.0 - VERTICAL_BORDER_DEPTH);
left *= gradient_position * VERTICAL_BORDER_DEPTH + (1.0 - VERTICAL_BORDER_DEPTH);
right *= gradient_position * VERTICAL_BORDER_DEPTH + (1.0 - VERTICAL_BORDER_DEPTH);
}
else if (pos.y > 2.0 / 3.0) {
float gradient_position = (1 - pos.y) * 3.0;
float gradient_position = (1.0 - pos.y) * 3.0;
if (pos.x < 0.5) {
gradient_position = 1 - (1 - gradient_position) * (1 - (pos.x) * 6.0);
gradient_position = 1.0 - (1.0 - gradient_position) * (1.0 - (pos.x) * 6.0);
}
else {
gradient_position = 1 - (1 - gradient_position) * (1 - (1 - pos.x) * 6.0);
gradient_position = 1.0 - (1.0 - gradient_position) * (1.0 - (1.0 - pos.x) * 6.0);
}
center *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH);
left *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH);
right *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH);
center *= gradient_position * VERTICAL_BORDER_DEPTH + (1.0 - VERTICAL_BORDER_DEPTH);
left *= gradient_position * VERTICAL_BORDER_DEPTH + (1.0 - VERTICAL_BORDER_DEPTH);
right *= gradient_position * VERTICAL_BORDER_DEPTH + (1.0 - VERTICAL_BORDER_DEPTH);
}
}
@ -113,48 +113,48 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou
vec4 ret;
if (pos.x < 1.0 / 6.0) {
ret = mix(vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_HIGH * left.b, 1),
vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_LOW * left.b, 1),
ret = mix(vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_HIGH * left.b, 1.0),
vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_LOW * left.b, 1.0),
sub_pos.x);
}
else if (pos.x < 2.0 / 6.0) {
ret = mix(vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_LOW * left.b, 1),
vec4(COLOR_HIGH * center.r, COLOR_HIGH * center.g, COLOR_LOW * midleft.b, 1),
ret = mix(vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_LOW * left.b, 1.0),
vec4(COLOR_HIGH * center.r, COLOR_HIGH * center.g, COLOR_LOW * midleft.b, 1.0),
sub_pos.x);
}
else if (pos.x < 3.0 / 6.0) {
ret = mix(vec4(COLOR_HIGH * center.r , COLOR_HIGH * center.g, COLOR_LOW * midleft.b, 1),
vec4(COLOR_LOW * midright.r, COLOR_HIGH * center.g, COLOR_LOW * center.b, 1),
ret = mix(vec4(COLOR_HIGH * center.r , COLOR_HIGH * center.g, COLOR_LOW * midleft.b, 1.0),
vec4(COLOR_LOW * midright.r, COLOR_HIGH * center.g, COLOR_LOW * center.b, 1.0),
sub_pos.x);
}
else if (pos.x < 4.0 / 6.0) {
ret = mix(vec4(COLOR_LOW * midright.r, COLOR_HIGH * center.g , COLOR_LOW * center.b, 1),
vec4(COLOR_LOW * right.r , COLOR_HIGH * center.g, COLOR_HIGH * center.b, 1),
ret = mix(vec4(COLOR_LOW * midright.r, COLOR_HIGH * center.g , COLOR_LOW * center.b, 1.0),
vec4(COLOR_LOW * right.r , COLOR_HIGH * center.g, COLOR_HIGH * center.b, 1.0),
sub_pos.x);
}
else if (pos.x < 5.0 / 6.0) {
ret = mix(vec4(COLOR_LOW * right.r, COLOR_HIGH * center.g , COLOR_HIGH * center.b, 1),
vec4(COLOR_LOW * right.r, COLOR_LOW * midright.g, COLOR_HIGH * center.b, 1),
ret = mix(vec4(COLOR_LOW * right.r, COLOR_HIGH * center.g , COLOR_HIGH * center.b, 1.0),
vec4(COLOR_LOW * right.r, COLOR_LOW * midright.g, COLOR_HIGH * center.b, 1.0),
sub_pos.x);
}
else {
ret = mix(vec4(COLOR_LOW * right.r, COLOR_LOW * midright.g, COLOR_HIGH * center.b, 1),
vec4(COLOR_HIGH * right.r, COLOR_LOW * right.g , COLOR_HIGH * center.b, 1),
ret = mix(vec4(COLOR_LOW * right.r, COLOR_LOW * midright.g, COLOR_HIGH * center.b, 1.0),
vec4(COLOR_HIGH * right.r, COLOR_LOW * right.g , COLOR_HIGH * center.b, 1.0),
sub_pos.x);
}
/* Anti alias the curve */
vec2 pixel_position = position * output_resolution;
if (pixel_position.x < 1) {
if (pixel_position.x < 1.0) {
ret *= pixel_position.x;
}
else if (pixel_position.x > output_resolution.x - 1) {
else if (pixel_position.x > output_resolution.x - 1.0) {
ret *= output_resolution.x - pixel_position.x;
}
if (pixel_position.y < 1) {
if (pixel_position.y < 1.0) {
ret *= pixel_position.y;
}
else if (pixel_position.y > output_resolution.y - 1) {
else if (pixel_position.y > output_resolution.y - 1.0) {
ret *= output_resolution.y - pixel_position.y;
}

View File

@ -2,7 +2,7 @@
/* The colorspace used by the HQnx filters is not really YUV, despite the algorithm description claims it is. It is
also not normalized. Therefore, we shall call the colorspace used by HQnx "HQ Colorspace" to avoid confusion. */
STATIC vec3 rgb_to_hq_colospace(vec4 rgb)
STATIC vec3 rgb_to_hq_colorspace(vec4 rgb)
{
return vec3( 0.250 * rgb.r + 0.250 * rgb.g + 0.250 * rgb.b,
0.250 * rgb.r - 0.000 * rgb.g - 0.250 * rgb.b,
@ -11,7 +11,7 @@ STATIC vec3 rgb_to_hq_colospace(vec4 rgb)
STATIC bool is_different(vec4 a, vec4 b)
{
vec3 diff = abs(rgb_to_hq_colospace(a) - rgb_to_hq_colospace(b));
vec3 diff = abs(rgb_to_hq_colorspace(a) - rgb_to_hq_colorspace(b));
return diff.x > 0.018 || diff.y > 0.002 || diff.z > 0.005;
}
@ -31,7 +31,7 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou
{
// o = offset, the width of a pixel
vec2 o = 1.0 / input_resolution;
/* We always calculate the top left pixel. If we need a different pixel, we flip the image */
// p = the position within a pixel [0...1]
@ -43,13 +43,13 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou
vec4 w0 = texture(image, position + vec2( -o.x, -o.y));
vec4 w1 = texture(image, position + vec2( 0, -o.y));
vec4 w1 = texture(image, position + vec2( 0.0, -o.y));
vec4 w2 = texture(image, position + vec2( o.x, -o.y));
vec4 w3 = texture(image, position + vec2( -o.x, 0));
vec4 w4 = texture(image, position + vec2( 0, 0));
vec4 w5 = texture(image, position + vec2( o.x, 0));
vec4 w3 = texture(image, position + vec2( -o.x, 0.0));
vec4 w4 = texture(image, position + vec2( 0.0, 0.0));
vec4 w5 = texture(image, position + vec2( o.x, 0.0));
vec4 w6 = texture(image, position + vec2( -o.x, o.y));
vec4 w7 = texture(image, position + vec2( 0, o.y));
vec4 w7 = texture(image, position + vec2( 0.0, o.y));
vec4 w8 = texture(image, position + vec2( o.x, o.y));
int pattern = 0;
@ -110,6 +110,6 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou
P(0x3b,0x1b)) {
return interp_3px(w4, 2.0, w3, 1.0, w1, 1.0);
}
return interp_3px(w4, 6.0, w3, 1.0, w1, 1.0);
}

View File

@ -5,27 +5,27 @@
STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution)
{
vec2 pos = fract(position * input_resolution);
vec2 sub_pos = fract(position * input_resolution * 6);
vec2 sub_pos = fract(position * input_resolution * 6.0);
vec4 center = texture(image, position);
vec4 left = texture(image, position - vec2(1.0 / input_resolution.x, 0));
vec4 right = texture(image, position + vec2(1.0 / input_resolution.x, 0));
if (pos.y < 1.0 / 6.0) {
center = mix(center, texture(image, position + vec2(0, -1.0 / input_resolution.y)), 0.5 - sub_pos.y / 2.0);
center = mix(center, texture(image, position + vec2(0.0, -1.0 / input_resolution.y)), 0.5 - sub_pos.y / 2.0);
left = mix(left, texture(image, position + vec2(-1.0 / input_resolution.x, -1.0 / input_resolution.y)), 0.5 - sub_pos.y / 2.0);
right = mix(right, texture(image, position + vec2( 1.0 / input_resolution.x, -1.0 / input_resolution.y)), 0.5 - sub_pos.y / 2.0);
center *= sub_pos.y * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH);
left *= sub_pos.y * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH);
right *= sub_pos.y * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH);
center *= sub_pos.y * SCANLINE_DEPTH + (1.0 - SCANLINE_DEPTH);
left *= sub_pos.y * SCANLINE_DEPTH + (1.0 - SCANLINE_DEPTH);
right *= sub_pos.y * SCANLINE_DEPTH + (1.0 - SCANLINE_DEPTH);
}
else if (pos.y > 5.0 / 6.0) {
center = mix(center, texture(image, position + vec2(0, 1.0 / input_resolution.y)), sub_pos.y / 2.0);
center = mix(center, texture(image, position + vec2(0.0, 1.0 / input_resolution.y)), sub_pos.y / 2.0);
left = mix(left, texture(image, position + vec2(-1.0 / input_resolution.x, 1.0 / input_resolution.y)), sub_pos.y / 2.0);
right = mix(right, texture(image, position + vec2( 1.0 / input_resolution.x, 1.0 / input_resolution.y)), sub_pos.y / 2.0);
center *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH);
left *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH);
right *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH);
center *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1.0 - SCANLINE_DEPTH);
left *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1.0 - SCANLINE_DEPTH);
right *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1.0 - SCANLINE_DEPTH);
}
@ -34,33 +34,33 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou
vec4 ret;
if (pos.x < 1.0 / 6.0) {
ret = mix(vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_HIGH * left.b, 1),
vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_LOW * left.b, 1),
ret = mix(vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_HIGH * left.b, 1.0),
vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_LOW * left.b, 1.0),
sub_pos.x);
}
else if (pos.x < 2.0 / 6.0) {
ret = mix(vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_LOW * left.b, 1),
vec4(COLOR_HIGH * center.r, COLOR_HIGH * center.g, COLOR_LOW * midleft.b, 1),
ret = mix(vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_LOW * left.b, 1.0),
vec4(COLOR_HIGH * center.r, COLOR_HIGH * center.g, COLOR_LOW * midleft.b, 1.0),
sub_pos.x);
}
else if (pos.x < 3.0 / 6.0) {
ret = mix(vec4(COLOR_HIGH * center.r , COLOR_HIGH * center.g, COLOR_LOW * midleft.b, 1),
vec4(COLOR_LOW * midright.r, COLOR_HIGH * center.g, COLOR_LOW * center.b, 1),
ret = mix(vec4(COLOR_HIGH * center.r , COLOR_HIGH * center.g, COLOR_LOW * midleft.b, 1.0),
vec4(COLOR_LOW * midright.r, COLOR_HIGH * center.g, COLOR_LOW * center.b, 1.0),
sub_pos.x);
}
else if (pos.x < 4.0 / 6.0) {
ret = mix(vec4(COLOR_LOW * midright.r, COLOR_HIGH * center.g , COLOR_LOW * center.b, 1),
vec4(COLOR_LOW * right.r , COLOR_HIGH * center.g, COLOR_HIGH * center.b, 1),
ret = mix(vec4(COLOR_LOW * midright.r, COLOR_HIGH * center.g , COLOR_LOW * center.b, 1.0),
vec4(COLOR_LOW * right.r , COLOR_HIGH * center.g, COLOR_HIGH * center.b, 1.0),
sub_pos.x);
}
else if (pos.x < 5.0 / 6.0) {
ret = mix(vec4(COLOR_LOW * right.r, COLOR_HIGH * center.g , COLOR_HIGH * center.b, 1),
vec4(COLOR_LOW * right.r, COLOR_LOW * midright.g, COLOR_HIGH * center.b, 1),
ret = mix(vec4(COLOR_LOW * right.r, COLOR_HIGH * center.g , COLOR_HIGH * center.b, 1.0),
vec4(COLOR_LOW * right.r, COLOR_LOW * midright.g, COLOR_HIGH * center.b, 1.0),
sub_pos.x);
}
else {
ret = mix(vec4(COLOR_LOW * right.r, COLOR_LOW * midright.g, COLOR_HIGH * center.b, 1),
vec4(COLOR_HIGH * right.r, COLOR_LOW * right.g , COLOR_HIGH * center.b, 1),
ret = mix(vec4(COLOR_LOW * right.r, COLOR_LOW * midright.g, COLOR_HIGH * center.b, 1.0),
vec4(COLOR_HIGH * right.r, COLOR_LOW * right.g , COLOR_HIGH * center.b, 1.0),
sub_pos.x);
}

View File

@ -2,9 +2,9 @@
- The actual output calculating was completely redesigned as resolution independent graphic generator. This allows
scaling to any factor.
- HQnx approximations that were good enough for a 2x/3x/4x factor were refined, creating smoother gradients.
- "Quarters" can be interpolated in more ways than in the HQnx filters
- If a pattern does not provide enough information to determine the suitable scaling interpolation, up to 16 pixels
per quarter are sampled (in contrast to the usual 9) in order to determine the best interpolation.
- "Quarters" can be interpolated in more ways than in the HQnx filters
- If a pattern does not provide enough information to determine the suitable scaling interpolation, up to 16 pixels
per quarter are sampled (in contrast to the usual 9) in order to determine the best interpolation.
*/
/* We use the same colorspace as the HQ algorithms. */
@ -28,7 +28,7 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou
{
// o = offset, the width of a pixel
vec2 o = 1.0 / input_resolution;
/* We always calculate the top left quarter. If we need a different quarter, we flip our co-ordinates */
// p = the position within a pixel [0...1]
@ -44,13 +44,13 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou
}
vec4 w0 = texture(image, position + vec2( -o.x, -o.y));
vec4 w1 = texture(image, position + vec2( 0, -o.y));
vec4 w1 = texture(image, position + vec2( 0.0, -o.y));
vec4 w2 = texture(image, position + vec2( o.x, -o.y));
vec4 w3 = texture(image, position + vec2( -o.x, 0));
vec4 w4 = texture(image, position + vec2( 0, 0));
vec4 w5 = texture(image, position + vec2( o.x, 0));
vec4 w3 = texture(image, position + vec2( -o.x, 0.0));
vec4 w4 = texture(image, position + vec2( 0.0, 0.0));
vec4 w5 = texture(image, position + vec2( o.x, 0.0));
vec4 w6 = texture(image, position + vec2( -o.x, o.y));
vec4 w7 = texture(image, position + vec2( 0, o.y));
vec4 w7 = texture(image, position + vec2( 0.0, o.y));
vec4 w8 = texture(image, position + vec2( o.x, o.y));
int pattern = 0;
@ -87,7 +87,7 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou
if (P(0x2f,0x2f)) {
float dist = length(p - vec2(0.5));
float pixel_size = length(1.0 / (output_resolution / input_resolution));
if (dist < 0.5 - pixel_size / 2) {
if (dist < 0.5 - pixel_size / 2.0) {
return w4;
}
vec4 r;
@ -98,40 +98,40 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou
r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0);
}
if (dist > 0.5 + pixel_size / 2) {
if (dist > 0.5 + pixel_size / 2.0) {
return r;
}
return mix(w4, r, (dist - 0.5 + pixel_size / 2) / pixel_size);
return mix(w4, r, (dist - 0.5 + pixel_size / 2.0) / pixel_size);
}
if (P(0xbf,0x37) || P(0xdb,0x13)) {
float dist = p.x - 2.0 * p.y;
float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0);
if (dist > pixel_size / 2) {
if (dist > pixel_size / 2.0) {
return w1;
}
vec4 r = mix(w3, w4, p.x + 0.5);
if (dist < -pixel_size / 2) {
if (dist < -pixel_size / 2.0) {
return r;
}
return mix(r, w1, (dist + pixel_size / 2) / pixel_size);
return mix(r, w1, (dist + pixel_size / 2.0) / pixel_size);
}
if (P(0xdb,0x49) || P(0xef,0x6d)) {
float dist = p.y - 2.0 * p.x;
float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0);
if (p.y - 2.0 * p.x > pixel_size / 2) {
if (p.y - 2.0 * p.x > pixel_size / 2.0) {
return w3;
}
vec4 r = mix(w1, w4, p.x + 0.5);
if (dist < -pixel_size / 2) {
if (dist < -pixel_size / 2.0) {
return r;
}
return mix(r, w3, (dist + pixel_size / 2) / pixel_size);
return mix(r, w3, (dist + pixel_size / 2.0) / pixel_size);
}
if (P(0xbf,0x8f) || P(0x7e,0x0e)) {
float dist = p.x + 2.0 * p.y;
float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0);
if (dist > 1.0 + pixel_size / 2) {
if (dist > 1.0 + pixel_size / 2.0) {
return w4;
}
@ -143,18 +143,18 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou
r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0);
}
if (dist < 1.0 - pixel_size / 2) {
if (dist < 1.0 - pixel_size / 2.0) {
return r;
}
return mix(r, w4, (dist + pixel_size / 2 - 1.0) / pixel_size);
return mix(r, w4, (dist + pixel_size / 2.0 - 1.0) / pixel_size);
}
if (P(0x7e,0x2a) || P(0xef,0xab)) {
float dist = p.y + 2.0 * p.x;
float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0);
if (p.y + 2.0 * p.x > 1.0 + pixel_size / 2) {
if (p.y + 2.0 * p.x > 1.0 + pixel_size / 2.0) {
return w4;
}
@ -167,11 +167,11 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou
r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0);
}
if (dist < 1.0 - pixel_size / 2) {
if (dist < 1.0 - pixel_size / 2.0) {
return r;
}
return mix(r, w4, (dist + pixel_size / 2 - 1.0) / pixel_size);
return mix(r, w4, (dist + pixel_size / 2.0 - 1.0) / pixel_size);
}
if (P(0x1b,0x03) || P(0x4f,0x43) || P(0x8b,0x83) || P(0x6b,0x43)) {
@ -193,7 +193,7 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou
float dist = p.x + p.y;
float pixel_size = length(1.0 / (output_resolution / input_resolution));
if (dist > 0.5 + pixel_size / 2) {
if (dist > 0.5 + pixel_size / 2.0) {
return w4;
}
@ -205,11 +205,11 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou
r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0);
}
if (dist < 0.5 - pixel_size / 2) {
if (dist < 0.5 - pixel_size / 2.0) {
return r;
}
return mix(r, w4, (dist + pixel_size / 2 - 0.5) / pixel_size);
return mix(r, w4, (dist + pixel_size / 2.0 - 0.5) / pixel_size);
}
if (P(0x0b,0x01)) {
@ -223,7 +223,7 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou
float dist = p.x + p.y;
float pixel_size = length(1.0 / (output_resolution / input_resolution));
if (dist > 0.5 + pixel_size / 2) {
if (dist > 0.5 + pixel_size / 2.0)
return w4;
}
@ -252,11 +252,11 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou
if (diagonal_bias <= 0) {
vec4 r = mix(w1, w3, p.y - p.x + 0.5);
if (dist < 0.5 - pixel_size / 2) {
if (dist < 0.5 - pixel_size / 2.0) {
return r;
}
return mix(r, w4, (dist + pixel_size / 2 - 0.5) / pixel_size);
return mix(r, w4, (dist + pixel_size / 2.0 - 0.5) / pixel_size);
}
return w4;
}

View File

@ -0,0 +1,40 @@
precision mediump float;
uniform sampler2D image;
uniform sampler2D previous_image;
uniform bool mix_previous;
uniform vec2 input_resolution;
uniform vec2 output_resolution;
uniform vec2 origin;
#define equal(x, y) ((x) == (y))
#define inequal(x, y) ((x) != (y))
#define STATIC
#if VERSION >= 0x300
#define FRAG_COLOR fragColor
out vec4 FRAG_COLOR;
#else
#define FRAG_COLOR gl_FragColor
vec4 texture(sampler2D s, vec2 c) { return texture2D(s, c); }
vec4 texture(sampler2D s, vec2 c, float b) { return texture2D(s, c, b); }
#endif
#line 1
{filter}
void main()
{
vec2 position = gl_FragCoord.xy - origin;
position /= output_resolution;
position.y = 1.0 - position.y;
if (mix_previous) {
FRAG_COLOR = mix(scale(image, position, input_resolution, output_resolution),
scale(previous_image, position, input_resolution, output_resolution), 0.5);
}
else {
FRAG_COLOR = scale(image, position, input_resolution, output_resolution);
}
}

24
Windows/resources.rc~ Normal file
View File

@ -0,0 +1,24 @@
1 VERSIONINFO
FILEOS 0x4
FILETYPE 0x1
{
BLOCK "StringFileInfo"
{
BLOCK "040904E4"
{
VALUE "CompanyName", "Lior Halphon"
VALUE "FileDescription", "SameBoy"
VALUE "FileVersion", VERSION
VALUE "LegalCopyright", "Copyright © 2015-2020 Lior Halphon"
VALUE "ProductName", "SameBoy"
VALUE "ProductVersion", VERSION
VALUE "WWW", "https://github.com/LIJI32/SameBoy"
}
}
BLOCK "VarFileInfo"
{
VALUE "Translation", 0x0409 0x04E4
}
}
IDI_ICON1 ICON DISCARDABLE "SameBoy.ico"

153
wasm/Makefile Normal file
View File

@ -0,0 +1,153 @@
# Make hacks
.INTERMEDIATE:
# Set target, configuration, version and destination folders
PLATFORM := $(shell uname -s)
ifneq ($(findstring MINGW,$(PLATFORM)),)
PLATFORM := windows32
USE_WINDRES := true
endif
ifneq ($(findstring MSYS,$(PLATFORM)),)
PLATFORM := windows32
endif
ifeq ($(PLATFORM),windows32)
_ := $(shell chcp 65001)
endif
DEFAULT := wasm
default: $(DEFAULT)
ifeq ($(MAKECMDGOALS),)
MAKECMDGOALS := $(DEFAULT)
endif
CORE_DIR += ..
CFLAGS += -DGB_VERSION=\"$(VERSION)\"
CONF ?= debug
BIN := $(CORE_DIR)/build/wasm_bin
OBJ := $(CORE_DIR)/build/wasm_obj
BOOTROMS_DIR ?= $(CORE_DIR)/build/bin/BootROMs
ifdef DATA_DIR
CFLAGS += -DDATA_DIR="\"$(DATA_DIR)\""
endif
# Set tools
CC := emcc
ifeq ($(PLATFORM),windows32)
# To force use of the Unix version instead of the Windows version
MKDIR := $(shell which mkdir)
else
MKDIR := mkdir
endif
ifeq ($(CONF),native_release)
override CONF := release
endif
# Set compilation and linkage flags based on target, platform and configuration
CFLAGS += -Werror -Wall -Wno-strict-aliasing -Wno-unknown-warning -Wno-unknown-warning-option -Wno-multichar -Wno-int-in-bool-context -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES
# CFLAGS += -DGB_INTERNAL=1 # get access to internal APIs
CFLAGS += -I$(CORE_DIR)
CFLAGS += -s WASM=1 -s USE_SDL=2 --preload-file $(BOOTROMS_DIR)@/BootROMs --preload-file $(CORE_DIR)/Shaders@/Shaders -s "EXPORTED_RUNTIME_METHODS=['ccall', 'cwrap', 'getValue', 'AsciiToString', 'FS']"
CFLAGS += -s USE_WEBGL2=1
# -Werror implies -Wpass-failed=transform-warning and for some reason Clang fails to unroll some loops in apu.c
CFLAGS += -Wno-pass-failed
# CFLAGS += -Wcast-align -Wover-aligned -s SAFE_HEAP=1 -s WARN_UNALIGNED=1
WASM_LDFLAGS := -lidbfs.js
LDFLAGS += -lc -lm -ldl
CFLAGS += -Wno-deprecated-declarations
ifeq ($(CONF),debug)
CFLAGS += -g -g4
CFLAGS += --cpuprofiler --memoryprofiler
else ifeq ($(CONF), release)
CFLAGS += -O3 -DNDEBUG --emit-symbol-map
CFLAGS += -flto
WASM_LDFLAGS += -flto
else
$(error Invalid value for CONF: $(CONF). Use "debug", "release" or "native_release")
endif
# Define our targets
bootroms: $(BOOTROMS_DIR)/agb_boot.bin \
$(BOOTROMS_DIR)/cgb_boot.bin \
$(BOOTROMS_DIR)/dmg_boot.bin \
$(BOOTROMS_DIR)/sgb_boot.bin \
$(BOOTROMS_DIR)/sgb2_boot.bin
# Get a list of our source files and their respective object file targets
CORE_SOURCES_RAW := $(shell ls $(CORE_DIR)/Core/*.c)
CORE_SOURCES := $(shell realpath --relative-to=$(CORE_DIR) $(CORE_SOURCES_RAW))
CORE_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(CORE_SOURCES))
WASM_SOURCES := $(shell ls *.c) $(CORE_DIR)/SDL/audio/sdl.c
WASM_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(WASM_SOURCES))
WEB_SOURCES := $(shell ls ressources/.)
WEB_OBJECTS := $(patsubst %,$(BIN)/ressources/%,$(WEB_SOURCES))
SHADERS := $(shell ls $(CORE_DIR)/Shaders/*.fsh)
wasm: bootroms $(BIN)/index.html $(WEB_OBJECTS) $(SHADERS)
all: wasm
# Automatic dependency generation
ifneq ($(filter-out clean %.bin, $(MAKECMDGOALS)),)
-include $(CORE_OBJECTS:.o=.dep)
ifneq ($(filter $(MAKECMDGOALS),wasm),)
-include $(WASM_OBJECTS:.o=.dep)
endif
endif
$(OBJ)/%.dep: %
-@$(MKDIR) -p $(dir $@)
$(CC) $(CFLAGS) -MT $(OBJ)/$^.o -M $^ -c -o $@
$(CORE_DIR)/build/bin/BootROMs/%_boot.bin:
$(MAKE) -C $(CORE_DIR) $(patsubst $(CORE_DIR)/%,%,$@)
# Compilation rules
$(OBJ)/Core/%.c.o: $(CORE_DIR)/Core/%.c
-@$(MKDIR) -p $(dir $@)
$(CC) $(CFLAGS) -DGB_INTERNAL -c $< -o $@
$(OBJ)/%.c.o: %.c
-@$(MKDIR) -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
$(BIN)/ressources/%:
-@$(MKDIR) -p $(dir $@)
cp -a $(patsubst $(BIN)/%,%,$@) $@
$(BIN)/index.html: $(CORE_OBJECTS) $(WASM_OBJECTS) index-shell.html main.js
-@$(MKDIR) -p $(dir $@)
$(CC) \
$(CFLAGS) \
$(filter %.o, $^) \
-o $@ \
--shell-file "index-shell.html" \
--post-js "main.js" \
$(LDFLAGS) \
$(WASM_LDFLAGS)
ifeq ($(CONF), release)
endif
$(CORE_DIR)/build/bin/BootROMs/%_boot.bin:
$(MAKE) -C $(CORE_DIR) $(patsubst $(CORE_DIR)/%,%,$@)
clean:
rm -f $(WASM_OBJECTS) $(BIN)/SameBoy.js $(BIN)/SameBoy.wasm

204
wasm/index-shell.html Normal file
View File

@ -0,0 +1,204 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"/>
<title>SameBoy</title>
<style>
body {
margin: 0;
padding: 0;
}
.center {
text-align: center;
}
.canvas-wrapper {
border: 1px solid black;
display: inline-block;
}
/* the canvas *must not* have any border or padding, or mouse coords will be wrong */
.canvas-wrapper > canvas {
border: 0px none;
background-color: black;
display: block;
}
.crisp {
image-rendering: optimizeSpeed;
image-rendering: -moz-crisp-edges;
image-rendering: -o-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: optimize-contrast;
image-rendering: crisp-edges;
image-rendering: pixelated;
-ms-interpolation-mode: nearest-neighbor;
}
#end {
display: none;
}
/* if an element has been placed after the end marker it is the CPU profiler */
#end + div > div {
margin-bottom: 10px;
margin-left: 5px;
margin-right: 5px;
margin-top: 5px;
}
/* if an element has been placed after the CPU profiler it is the memory profiler */
#end + div + div > div {
margin-bottom: 10px;
margin-left: 5px;
margin-right: 5px;
margin-top: 5px;
}
</style>
</head>
<body>
<ul id="demo-roms">
<li><a href="ressources/demos/oh.gb">Oh!</a></li>
<li><a href="ressources/demos/pocket.gb">Is that a demo in your pocket?</a></li>
<li><a href="ressources/demos/gejmboj.gb">Gejmboj</a></li>
<li><a href="ressources/demos/video.gbc">GBVP1</a></li>
<li><a href="ressources/demos/mezase.gbc">GBVP2</a></li>
<li><a href="ressources/demos/pht-mr.gbc">Mental Respirator</a></li>
<li><a href="ressources/demos/pht-pz.gbc">It came from planet Zilog</a></li>
</ul>
<input type="file" id="file" name="file" accept=".gb,.gbc,.bin">
<hr/>
<div class="center">
<div id="status">Downloading...</div>
<progress value="0" max="100" id="progress" hidden=1></progress>
</div>
<div class="center">
<div class="canvas-wrapper">
<canvas class="crisp" id="canvas" oncontextmenu="event.preventDefault()"></canvas>
</div>
</div>
<hr/>
<div class="center">
<input type="checkbox" id="resize">Resize canvas
<input type="checkbox" id="pointerLock" checked>Lock/hide mouse pointer
&nbsp;&nbsp;&nbsp;
<input type="button" value="Fullscreen" onclick="Module.requestFullscreen(document.querySelector('#pointerLock').checked, document.querySelector('#resize').checked)">
</div>
<script type='text/javascript'>
const statusElement = document.querySelector('#status');
const progressElement = document.querySelector('#progress');
var Module = {
get_models: function () {
const ptr = Module._get_models_string_pointer();
const ptr_size = Module._get_models_string_pointer_size();
const size = Module._get_models_string_size() - ptr_size; // last element is a end marker
const models = [];
for (let i = 0; i < size; i += ptr_size) {
const key = AsciiToString(getValue(ptr + i, '*'));
models.push(key.replace(/^MODEL_/, ''));
}
return models
},
get_sgb_revisions: function () {
const ptr = Module._get_sgb_revisions_string_pointer();
const ptr_size = Module._get_sgb_revisions_string_pointer_size();
const size = Module._get_sgb_revisions_string_size() - ptr_size; // last element is a end marker
const revisions = [];
for (let i = 0; i < size; i += ptr_size) {
const key = AsciiToString(getValue(ptr + i, '*'));
revisions.push(key.replace(/^SGB_/, ''));
}
return revisions
},
sync_fs: function () {
console.log("Syncing file system ...");
FS.syncfs(function (err) {
if (!err) {
console.log("File system synchronized.")
}
else {
console.error(err);
}
});
},
preRun: [],
postRun: [],
print: function () {
const text = Array.prototype.slice.call(arguments).join(' ');
console.log(text);
},
printErr: function() {
const text = Array.prototype.slice.call(arguments).join(' ');
console.error(text);
},
canvas: (function() {
const canvas = document.querySelector('#canvas');
// As a default initial behavior, pop up an alert when webgl context is lost. To make your
// application robust, you may want to override this behavior before shipping!
// See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
canvas.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false);
return canvas;
})(),
setStatus: function (text) {
if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };
if (text === Module.setStatus.last.text) return;
const m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
const now = Date.now();
if (m && now - Module.setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon
Module.setStatus.last.time = now;
Module.setStatus.last.text = text;
if (m) {
text = m[1];
progressElement.value = parseInt(m[2])*100;
progressElement.max = parseInt(m[4])*100;
progressElement.hidden = false;
}
else {
progressElement.value = null;
progressElement.max = null;
progressElement.hidden = true;
}
statusElement.innerHTML = text;
},
totalDependencies: 0,
monitorRunDependencies: function (left) {
this.totalDependencies = Math.max(this.totalDependencies, left);
Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
}
};
Module.setStatus('Downloading...');
window.onerror = function() {
Module.setStatus('Exception thrown, see JavaScript console');
Module.setStatus = function(text) {
if (text) Module.printErr('[post-exception status] ' + text);
};
};
</script>
{{{ SCRIPT }}}
<a href="#bottom" id="end"></a>
</body>
</html>

447
wasm/main.c Normal file
View File

@ -0,0 +1,447 @@
#include <emscripten.h>
#include <SDL2/SDL_video.h>
#include <SDL2/SDL.h>
#include <stdbool.h>
#include <stdio.h>
#include <Core/gb.h>
#include "main.h"
#include "utils.h"
#include "shader.h"
#include "SDL/audio/audio.h"
GB_gameboy_t gb;
SDL_Window *window;
SDL_Renderer *renderer;
SDL_Surface *screen;
SDL_Texture *texture;
SDL_PixelFormat *pixel_format;
shader_t shader;
static SDL_Rect rect;
static unsigned factor;
static uint32_t pixel_buffer_1[256 * 224], pixel_buffer_2[256 * 224];
static uint32_t *active_pixel_buffer = pixel_buffer_1;
static uint32_t *previous_pixel_buffer = pixel_buffer_2;
static char *battery_save_path_ptr;
struct shader_name {
const char *file_name;
const char *display_name;
} shaders[] =
{
{"NearestNeighbor", "Nearest Neighbor"},
{"Bilinear", "Bilinear"},
{"SmoothBilinear", "Smooth Bilinear"},
{"LCD", "LCD Display"},
{"CRT", "CRT Display"},
{"Scale2x", "Scale2x"},
{"Scale4x", "Scale4x"},
{"AAScale2x", "Anti-aliased Scale2x"},
{"AAScale4x", "Anti-aliased Scale4x"},
// {"HQ2x", "HQ2x"}, // requires OpenGL ES 1.30 features
// {"OmniScale", "OmniScale"}, // requires OpenGL ES 1.30 features
{"OmniScaleLegacy", "OmniScale Legacy"},
{"AAOmniScaleLegacy", "AA OmniScale Legacy"},
};
configuration_t configuration =
{
.keys = {
SDL_SCANCODE_RIGHT,
SDL_SCANCODE_LEFT,
SDL_SCANCODE_UP,
SDL_SCANCODE_DOWN,
SDL_SCANCODE_X,
SDL_SCANCODE_Z,
SDL_SCANCODE_BACKSPACE,
SDL_SCANCODE_RETURN,
SDL_SCANCODE_SPACE
},
.keys_2 = {
SDL_SCANCODE_TAB,
SDL_SCANCODE_LSHIFT,
},
.joypad_configuration = {
13,
14,
11,
12,
0,
1,
9,
8,
10,
4,
-1,
5,
},
.joypad_axises = {
0,
1,
},
.color_correction_mode = GB_COLOR_CORRECTION_EMULATE_HARDWARE,
.highpass_mode = GB_HIGHPASS_ACCURATE,
.scaling_mode = GB_SDL_SCALING_INTEGER_FACTOR,
.blend_frames = true,
.rewind_length = 60 * 2,
.model = MODEL_CGB,
.filter = "OmniScaleLegacy",
};
// Use this function instead of GB_save_battery()
int save_battery(GB_gameboy_t *gb, const char *path) {
int result = GB_save_battery(gb, path);
fprintf(stderr, "Saving battery: \"%s\": %d\n", path, result);
EM_ASM(Module.sync_fs());
return result;
}
unsigned query_sample_rate_of_audiocontexts() {
return EM_ASM_INT({
if (!Module.SDL2 || !Module.SDL2.audioContext) {
const AudioContext = window.AudioContext || window.webkitAudioContext;
const ctx = new AudioContext();
const sr = ctx.sampleRate;
ctx.close();
return sr;
}
return Module.SDL2.audioContext.sampleRate;
});
}
static void gb_audio_callback(GB_gameboy_t *gb, GB_sample_t *sample)
{
if (GB_audio_get_queue_length() / sizeof(*sample) > GB_audio_get_sample_rate() / 4) {
return;
}
GB_audio_queue_sample(sample);
}
void update_viewport(void)
{
int win_width, win_height;
SDL_GL_GetDrawableSize(window, &win_width, &win_height);
int logical_width, logical_height;
SDL_GetWindowSize(window, &logical_width, &logical_height);
factor = win_width / logical_width;
double x_factor = win_width / (double) GB_get_screen_width(&gb);
double y_factor = win_height / (double) GB_get_screen_height(&gb);
if (configuration.scaling_mode == GB_SDL_SCALING_INTEGER_FACTOR) {
x_factor = (int)(x_factor);
y_factor = (int)(y_factor);
}
if (configuration.scaling_mode != GB_SDL_SCALING_ENTIRE_WINDOW) {
if (x_factor > y_factor) {
x_factor = y_factor;
}
else {
y_factor = x_factor;
}
}
unsigned new_width = x_factor * GB_get_screen_width(&gb);
unsigned new_height = y_factor * GB_get_screen_height(&gb);
rect = (SDL_Rect){(win_width - new_width) / 2, (win_height - new_height) / 2,
new_width, new_height};
if (renderer) {
SDL_RenderSetViewport(renderer, &rect);
}
else {
glViewport(rect.x, rect.y, rect.w, rect.h);
}
}
void render_texture(void *pixels, void *previous)
{
if (renderer) {
if (pixels) {
SDL_UpdateTexture(texture, NULL, pixels, 160 * sizeof (uint32_t));
}
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);
}
else {
static void *_pixels = NULL;
if (pixels) {
_pixels = pixels;
}
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
render_bitmap_with_shader(&shader, _pixels, previous,
GB_get_screen_width(&gb), GB_get_screen_height(&gb),
rect.x, rect.y, rect.w, rect.h);
SDL_GL_SwapWindow(window);
}
}
static void handle_events(GB_gameboy_t *gb) {
GB_set_key_state(gb, GB_KEY_START, true);
}
static void vblank(GB_gameboy_t *gb) {
if (configuration.blend_frames) {
render_texture(active_pixel_buffer, previous_pixel_buffer);
uint32_t *temp = active_pixel_buffer;
active_pixel_buffer = previous_pixel_buffer;
previous_pixel_buffer = temp;
GB_set_pixels_output(gb, active_pixel_buffer);
}
else {
render_texture(active_pixel_buffer, NULL);
}
handle_events(gb);
}
static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b)
{
return SDL_MapRGB(pixel_format, r, g, b);
}
void init_gb() {
GB_model_t model;
model = (GB_model_t [])
{
[MODEL_DMG] = GB_MODEL_DMG_B,
[MODEL_CGB] = GB_MODEL_CGB_E,
[MODEL_AGB] = GB_MODEL_AGB,
[MODEL_SGB] = (GB_model_t [])
{
[SGB_NTSC] = GB_MODEL_SGB_NTSC,
[SGB_PAL] = GB_MODEL_SGB_PAL,
[SGB_2] = GB_MODEL_SGB2,
}[configuration.sgb_revision],
}[configuration.model];
fprintf(stderr, "Initializing ...\n");
if (GB_is_inited(&gb)) {
fprintf(stderr, "Already initialized, switching model ...\n");
GB_switch_model_and_reset(&gb, model);
}
else {
fprintf(stderr, "Initializing new GB ...\n");
GB_init(&gb, model);
GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank);
GB_set_pixels_output(&gb, active_pixel_buffer);
GB_set_rgb_encode_callback(&gb, rgb_encode);
GB_set_sample_rate(&gb, GB_audio_get_sample_rate());
GB_set_color_correction_mode(&gb, configuration.color_correction_mode);
GB_set_highpass_filter_mode(&gb, configuration.highpass_mode);
GB_set_rewind_length(&gb, 0);
GB_apu_set_sample_callback(&gb, gb_audio_callback);
GB_set_input_callback(&gb, NULL);
GB_set_async_input_callback(&gb, NULL);
}
SDL_DestroyTexture(texture);
texture = SDL_CreateTexture(
renderer,
SDL_GetWindowPixelFormat(window),
SDL_TEXTUREACCESS_STREAMING,
GB_get_screen_width(&gb),
GB_get_screen_height(&gb)
);
SDL_SetWindowMinimumSize(window, GB_get_screen_width(&gb), GB_get_screen_height(&gb));
bool error = false;
const char * const boot_roms[] = {
"dmg_boot.bin",
"cgb_boot.bin",
"agb_boot.bin",
"sgb_boot.bin"
};
const char *boot_rom = boot_roms[configuration.model];
if (configuration.model == GB_MODEL_SGB && configuration.sgb_revision == SGB_2) {
boot_rom = "sgb2_boot.bin";
}
const char *boot_rom_path = resource_path(concat("BootROMs/", boot_rom));
fprintf(stderr, "Loading boot ROM: %s\n", boot_rom_path);
error = GB_load_boot_rom(&gb, boot_rom_path);
}
int EMSCRIPTEN_KEEPALIVE init() {
#define str(x) #x
#define xstr(x) str(x)
pixel_format = (SDL_PixelFormat *) malloc(sizeof(SDL_PixelFormat));
if (!pixel_format) {
fprintf(stderr, "Failed to allocate memory\n");
return EXIT_FAILURE;
}
// emscripten_sample_gamepad_data();
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) != 0) {
fprintf(stderr, "SDL_Init Error: %s\n", SDL_GetError());
return EXIT_FAILURE;
}
fprintf(stderr, "SameBoy v" xstr(VERSION) "\n");
window = SDL_CreateWindow(
"SameBoy v" xstr(VERSION),
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
VIDEO_WIDTH * 2,
VIDEO_HEIGHT * 2,
SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS | SDL_WINDOW_ALLOW_HIGHDPI
);
if (!window) {
printf("Could not create window: %s\n", SDL_GetError());
return EXIT_FAILURE;
}
SDL_SetWindowMinimumSize(window, VIDEO_WIDTH, VIDEO_HEIGHT);
SDL_SetWindowMaximumSize(window, VIDEO_WIDTH, VIDEO_HEIGHT);
// Try to get a GLES 3.0 context
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
SDL_GLContext gl_context = SDL_GL_CreateContext(window);
if (gl_context == NULL) {
// Try to get a GLES 2.0 context
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
gl_context = SDL_GL_CreateContext(window);
}
if (gl_context == NULL) {
fprintf(stderr, "Using software renderer!\n");
renderer = SDL_CreateRenderer(window, -1, 0);
texture = SDL_CreateTexture(renderer, SDL_GetWindowPixelFormat(window), SDL_TEXTUREACCESS_STREAMING, 160, 144);
pixel_format = SDL_AllocFormat(SDL_GetWindowPixelFormat(window));
}
else {
fprintf(stderr, "Using OpenGL renderer!\n");
pixel_format = SDL_AllocFormat(SDL_PIXELFORMAT_ABGR8888);
fprintf(stderr, "GLES: %s\n", glGetString(GL_VERSION));
fprintf(stderr, "GLSL: %s\n", glGetString(GL_SHADING_LANGUAGE_VERSION));
fprintf(stderr, "Parsed GL version: %hu\n", get_gl_version());
}
unsigned audio_sample_rate = query_sample_rate_of_audiocontexts();
fprintf(stderr, "Sample rate: %u\n", audio_sample_rate);
GB_audio_init(audio_sample_rate);
EM_ASM({
function audio_workaround(e) {
if (!Module.SDL2 || !Module.SDL2.audioContext || !Module.SDL2.audioContext.resume) return;
console.log('Applying audio workarounds...');
if (Module.SDL2.audioContext.state == 'suspended') {
Module.SDL2.audioContext.resume();
}
if (Module.SDL2.audioContext.state == 'running') {
document.removeEventListener('touchstart', audio_workaround);
document.removeEventListener('click', audio_workaround);
document.removeEventListener('keydown', audio_workaround);
if (Module.canvas) {
Module.canvas.removeEventListener('touchstart', audio_workaround);
Module.canvas.removeEventListener('click', audio_workaround);
Module.canvas.removeEventListener('keydown', audio_workaround);
}
}
else if (Module.SDL2.audioContext && Module.SDL2.audioContext.currentTime == 0) {
// unlock audio for iOS
let buffer = Module.SDL2.audioContext.createBuffer(1, 1, 22050);
let source = Module.SDL2.audioContext.createBufferSource();
source.buffer = buffer;
source.connect(Module.SDL2.audioContext.destination);
source.start(0);
}
}
document.addEventListener('touchstart', audio_workaround);
document.addEventListener('click', audio_workaround);
document.addEventListener('keydown', audio_workaround);
if (Module.canvas) {
Module.canvas.addEventListener('touchstart', audio_workaround);
Module.canvas.addEventListener('click', audio_workaround);
Module.canvas.addEventListener('keydown', audio_workaround);
}
audio_workaround();
});
init_gb();
if (!init_shader_with_name(&shader, configuration.filter)) {
init_shader_with_name(&shader, "NearestNeighbor");
}
update_viewport();
GB_audio_set_paused(false);
return EXIT_SUCCESS;
}
int EMSCRIPTEN_KEEPALIVE load_boot_rom_from_file(char* filename) {
return GB_load_boot_rom(&gb, filename);
}
int EMSCRIPTEN_KEEPALIVE load_rom_from_file(char* filename, char* battery_save_path) {
int result = GB_load_rom(&gb, filename);
if (result == 0) {
battery_save_path_ptr = battery_save_path;
GB_load_battery(&gb, battery_save_path);
save_battery(&gb, battery_save_path_ptr);
}
return result;
}
void EMSCRIPTEN_KEEPALIVE quit() {
fprintf(stderr, "Quitting ...\n");
emscripten_set_main_loop(NULL, 0, false);
GB_free(&gb);
SDL_FreeSurface(screen);
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
}
void EMSCRIPTEN_KEEPALIVE run_frame() {
GB_run_frame(&gb);
}

102
wasm/main.h Normal file
View File

@ -0,0 +1,102 @@
#ifndef main_h
#define main_h
#define str(x) #x
#define xstr(x) str(x)
#define VIDEO_WIDTH 160
#define VIDEO_HEIGHT 144
#define VIDEO_PIXELS (VIDEO_WIDTH * VIDEO_HEIGHT)
#define SGB_VIDEO_WIDTH 256
#define SGB_VIDEO_HEIGHT 224
#define SGB_VIDEO_PIXELS (SGB_VIDEO_WIDTH * SGB_VIDEO_HEIGHT)
#define GENERATE_ENUM(ENUM) ENUM,
#define GENERATE_STRING(STRING) #STRING,
#define MODELS(MODEL) \
MODEL(MODEL_DMG) \
MODEL(MODEL_CGB) \
MODEL(MODEL_AGB) \
MODEL(MODEL_SGB) \
MODEL(MODEL_MAX)
#define SGB_REVISIONS(REVISION) \
REVISION(SGB_NTSC) \
REVISION(SGB_PAL) \
REVISION(SGB_2) \
REVISION(SGB_MAX)
typedef enum {
JOYPAD_AXISES_X,
JOYPAD_AXISES_Y,
JOYPAD_AXISES_MAX
} joypad_axis_t;
enum scaling_mode {
GB_SDL_SCALING_ENTIRE_WINDOW,
GB_SDL_SCALING_KEEP_RATIO,
GB_SDL_SCALING_INTEGER_FACTOR,
GB_SDL_SCALING_MAX,
};
typedef struct {
SDL_Scancode keys[9];
GB_color_correction_mode_t color_correction_mode;
enum scaling_mode scaling_mode;
bool blend_frames;
GB_highpass_mode_t highpass_mode;
char filter[32];
enum {
MODELS(GENERATE_ENUM)
} model;
/* v0.11 */
uint32_t rewind_length;
SDL_Scancode keys_2[32]; /* Rewind and underclock, + padding for the future */
uint8_t joypad_configuration[32]; /* 12 Keys + padding for the future*/;
uint8_t joypad_axises[JOYPAD_AXISES_MAX];
/* v0.12 */
enum {
SGB_REVISIONS(GENERATE_ENUM)
} sgb_revision;
} configuration_t;
// TODO: There must be a better way to not duplicate this data on the JavaScript side
const char *MODELS_STRING[] = {
MODELS(GENERATE_STRING)
};
const char *SGB_REVISIONS_STRING[] = {
SGB_REVISIONS(GENERATE_STRING)
};
const char** EMSCRIPTEN_KEEPALIVE get_models_string_pointer() {
return MODELS_STRING;
}
const size_t EMSCRIPTEN_KEEPALIVE get_models_string_pointer_size() {
return sizeof(*MODELS_STRING);
}
const size_t EMSCRIPTEN_KEEPALIVE get_models_string_size() {
return sizeof(MODELS_STRING);
}
const char** EMSCRIPTEN_KEEPALIVE get_sgb_revisions_string_pointer() {
return SGB_REVISIONS_STRING;
}
const size_t EMSCRIPTEN_KEEPALIVE get_sgb_revisions_string_pointer_size() {
return sizeof(*SGB_REVISIONS_STRING);
}
const size_t EMSCRIPTEN_KEEPALIVE get_sgb_revisions_string_size() {
return sizeof(SGB_REVISIONS_STRING);
}
#endif /* main_h */

153
wasm/main.js Normal file
View File

@ -0,0 +1,153 @@
const frame_rate = (0x400000 / 70224.0);
const ms_per_frame = 1000 / frame_rate;
let last_frame_time = 0;
const stringHash = str => {
let hash = 0;
if (str.length === 0) return hash;
for (let i = 0; i < str.length; i++) {
let chr = str.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
}
const run_frame = time => {
window.requestAnimationFrame(run_frame);
const delta = time - last_frame_time;
if (delta > ms_per_frame) {
Module._run_frame();
last_frame_time = time - (delta % ms_per_frame);
}
}
const loadRomFromMemory = (name, data) => {
const pos = name.lastIndexOf('.');
const battery_name = name.substr(0, pos < 0 ? name.length : pos) + '.sav';
try {
// try to create the virtual ROM folder
FS.mkdir('/rom');
} catch (e) { }
try {
// try to delete all previous ROM files
for (let file of FS.readdir('/rom').filter(f => f != '.' && f != '..')) {
FS.unlink(`/rom/${file}`)
}
} catch (e) { }
// create a new virtual file from memory
Module['FS_createDataFile']('/rom/', name, new Uint8Array(data), true, true);
const rom_path = allocate(intArrayFromString(`/rom/${name}`), 'i8', ALLOC_NORMAL);
const battery_path = allocate(intArrayFromString(`/persist/${battery_name}`), 'i8', ALLOC_NORMAL);
Module._load_rom_from_file(rom_path, battery_path);
// The ROM has been read into memory, we can unlink the file now
FS.unlink(`/rom/${name}`)
window.requestAnimationFrame(run_frame)
}
const loadROM = f => {
const reader = new FileReader();
reader.onload = (file => {
return event => {
loadRomFromMemory(file.name, event.target.result)
};
})(f);
reader.readAsArrayBuffer(f);
}
const loadRemoteRom = url => {
const request = new Request(url);
const name = (_ => {
const name = url.substring(url.lastIndexOf('/') + 1);
if (name.endsWith('.gb') || name.endsWith('.gbc')) {
return name
}
else if (name.length) {
return `${name}.gb`
}
return stringHash(url)
})()
return fetch(request).then(response => {
if (!response.ok) {
throw new Error('HTTP error, status = ' + response.status);
}
return response.arrayBuffer();
}).then(buf => {
loadRomFromMemory(name, buf)
})
}
const handleFileSelect = (evt, files) => {
evt.stopPropagation();
evt.preventDefault();
if (files.length) {
loadROM(files[0]);
}
}
const handleDragOver = evt => {
evt.stopPropagation();
evt.preventDefault();
evt.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
}
window.addEventListener('dragover', handleDragOver, false);
window.addEventListener('drop', e => {
handleFileSelect(e, e.dataTransfer.files);
}, false);
document.getElementById('file').addEventListener('change', e => {
handleFileSelect(e, e.target.files);
}, false);
Module.onRuntimeInitialized = _ => {
console.log(Module.get_models());
console.log(Module.get_sgb_revisions());
FS.mkdir('/persist');
FS.mount(IDBFS, { }, '/persist');
FS.syncfs(true, function (err) {
if (!err) {
console.log('Successfully loaded FS from persistent storage')
}
else {
console.error(err)
}
// Call the exported init function
Module._init();
})
};
const romClickHandler = event => {
event.stopPropagation();
event.preventDefault();
loadRemoteRom(event.target.href);
}
for (const anchor of document.querySelectorAll('#demo-roms a')) {
anchor.addEventListener('click', romClickHandler);
}

1
wasm/opengl_compat.c Normal file
View File

@ -0,0 +1 @@
#include "opengl_compat.h"

7
wasm/opengl_compat.h Normal file
View File

@ -0,0 +1,7 @@
#ifndef opengl_compat_h
#define opengl_compat_h
#define GL_GLEXT_PROTOTYPES 1
#include <GLES3/gl3.h>
#endif /* opengl_compat_h */

Binary file not shown.

Binary file not shown.

BIN
wasm/ressources/demos/oh.gb Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

261
wasm/shader.c Normal file
View File

@ -0,0 +1,261 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "shader.h"
#include "utils.h"
static const char *vertex_shader_100 = "\
#version 100 \n\
attribute vec4 aPosition;\n\
void main(void) {\n\
gl_Position = aPosition;\n\
}\n\
";
static const char *vertex_shader_300 = "\
#version 300 es\n\
in vec4 aPosition;\n\
void main(void) {\n\
gl_Position = aPosition;\n\
}\n\
";
uint16_t get_gl_version() {
GLint major = 0, minor = 0;
#if defined(GL_MAJOR_VERSION) && defined(GL_MINOR_VERSION)
glGetIntegerv(GL_MAJOR_VERSION, &major);
glGetIntegerv(GL_MINOR_VERSION, &minor);
#else
char *version = (char *) glGetString(GL_VERSION);
int res = sscanf(version, "OpenGL ES %d.%d", &major, &minor);
if (res != 2) {
// Maybe the OpenGL ES prefixed was missing
res = sscanf(version, "%d.%d", &major, &minor);
}
if (res != 2) {
major = 0;
minor = 0;
}
#endif
return (uint16_t)(major * 0x100 + minor);
}
static GLuint create_shader(const char *source, GLenum type)
{
// Create the shader object
GLuint shader = glCreateShader(type);
// Load the shader source
glShaderSource(shader, 1, &source, 0);
// Compile the shader
glCompileShader(shader);
// Check for errors
GLint status = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE) {
GLchar messages[1024];
glGetShaderInfoLog(shader, sizeof(messages), 0, &messages[0]);
fprintf(stderr, "GLSL Shader Error: %s", messages);
}
return shader;
}
static GLuint create_program(const char *vsh, const char *fsh)
{
// Build shaders
GLuint vertex_shader = create_shader(vsh, GL_VERTEX_SHADER);
GLuint fragment_shader = create_shader(fsh, GL_FRAGMENT_SHADER);
// Create program
GLuint program = glCreateProgram();
fprintf(stderr, "Creating program...\n");
// Attach shaders
glAttachShader(program, vertex_shader);
glAttachShader(program, fragment_shader);
fprintf(stderr, "Linking program...\n");
// Link program
glLinkProgram(program);
fprintf(stderr, "Checking for errors...\n");
// Check for errors
GLint status;
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (status == GL_FALSE) {
GLint info_len = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &info_len);
if (info_len > 1) {
char* info_log = (char*)malloc(sizeof(char) * info_len);
glGetProgramInfoLog(program, info_len, NULL, info_log);
printf("Error linking program:\n%s\n", info_log);
free(info_log);
}
glDeleteProgram(program);
}
// Delete shaders
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
return program;
}
bool init_shader_with_name(shader_t *shader, const char *name)
{
uint16_t gl_version = get_gl_version();
static char master_shader_code[0x801] = {0,};
static char shader_code[0x10001] = {0,};
static char final_shader_code[0x10801] = {0,};
static signed long filter_token_location = 0;
if (!master_shader_code[0]) {
size_t header_len = 0;
if (gl_version >= 0x300) {
char* header = "#version 300 es\n#define VERSION 0x300\n";
header_len = strlen(header);
strcpy(master_shader_code, header);
}
else {
char* header = "#version 100\n#define VERSION 0x100\n";
header_len = strlen(header);
strcpy(master_shader_code, header);
}
FILE *master_shader_f = fopen(resource_path("Shaders/WasmMasterShader.fsh"), "r");
if (!master_shader_f) return false;
fread(master_shader_code + header_len, 1, sizeof(master_shader_code) - 1, master_shader_f);
fclose(master_shader_f);
filter_token_location = strstr(master_shader_code, "{filter}") - master_shader_code;
if (filter_token_location < 0) {
master_shader_code[0] = 0;
return false;
}
}
char shader_path[1024];
sprintf(shader_path, "Shaders/%s.fsh", name);
FILE *shader_f = fopen(resource_path(shader_path), "r");
if (!shader_f) return false;
memset(shader_code, 0, sizeof(shader_code));
fread(shader_code, 1, sizeof(shader_code) - 1, shader_f);
fclose(shader_f);
memset(final_shader_code, 0, sizeof(final_shader_code));
memcpy(final_shader_code, master_shader_code, filter_token_location);
strcpy(final_shader_code + filter_token_location, shader_code);
strcat(final_shader_code + filter_token_location,
master_shader_code + filter_token_location + sizeof("{filter}") - 1);
fprintf(stderr, "Shader code:\n%s\n", final_shader_code);
if (gl_version >= 0x300) {
shader->program = create_program(vertex_shader_300, final_shader_code);
}
else {
shader->program = create_program(vertex_shader_100, final_shader_code);
}
// Attributes
shader->position_attribute = glGetAttribLocation(shader->program, "aPosition");
// Uniforms
shader->input_resolution_uniform = glGetUniformLocation(shader->program, "input_resolution");
shader->resolution_uniform = glGetUniformLocation(shader->program, "output_resolution");
shader->origin_uniform = glGetUniformLocation(shader->program, "origin");
glGenTextures(1, &shader->texture);
glBindTexture(GL_TEXTURE_2D, shader->texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
shader->texture_uniform = glGetUniformLocation(shader->program, "image");
glGenTextures(1, &shader->previous_texture);
glBindTexture(GL_TEXTURE_2D, shader->previous_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
shader->previous_texture_uniform = glGetUniformLocation(shader->program, "previous_image");
shader->mix_previous_uniform = glGetUniformLocation(shader->program, "mix_previous");
// Program
glUseProgram(shader->program);
GLuint vao;
if (gl_version >= 0x300) {
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
}
GLuint vbo;
glGenBuffers(1, &vbo);
// Attributes
static GLfloat const quad[16] = {
-1.f, -1.f, 0, 1,
-1.f, +1.f, 0, 1,
+1.f, -1.f, 0, 1,
+1.f, +1.f, 0, 1,
};
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(quad), quad, GL_STATIC_DRAW);
glEnableVertexAttribArray(shader->position_attribute);
glVertexAttribPointer(shader->position_attribute, 4, GL_FLOAT, GL_FALSE, 0, 0);
return true;
}
void render_bitmap_with_shader(shader_t *shader, void *bitmap, void *previous,
unsigned source_width, unsigned source_height,
unsigned x, unsigned y, unsigned w, unsigned h)
{
glUseProgram(shader->program);
glUniform2f(shader->origin_uniform, x, y);
glUniform2f(shader->input_resolution_uniform, source_width, source_height);
glUniform2f(shader->resolution_uniform, w, h);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, shader->texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, source_width, source_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap);
glUniform1i(shader->texture_uniform, 0);
glUniform1i(shader->mix_previous_uniform, previous != NULL);
if (previous) {
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, shader->previous_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, source_width, source_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, previous);
glUniform1i(shader->previous_texture_uniform, 1);
}
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
void free_shader(shader_t *shader)
{
glDeleteProgram(shader->program);
glDeleteTextures(1, &shader->texture);
glDeleteTextures(1, &shader->previous_texture);
}

29
wasm/shader.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef shader_h
#define shader_h
#include "opengl_compat.h"
#include <stdbool.h>
typedef struct shader_s {
GLuint input_resolution_uniform;
GLuint resolution_uniform;
GLuint origin_uniform;
GLuint texture_uniform;
GLuint previous_texture_uniform;
GLuint mix_previous_uniform;
GLuint position_attribute;
GLuint texture;
GLuint previous_texture;
GLuint program;
} shader_t;
uint16_t get_gl_version();
bool init_shader_with_name(shader_t *shader, const char *name);
void render_bitmap_with_shader(shader_t *shader, void *bitmap, void *previous,
unsigned source_width, unsigned source_height,
unsigned x, unsigned y, unsigned w, unsigned h);
void free_shader(struct shader_s *shader);
#endif /* shader_h */

59
wasm/utils.c Normal file
View File

@ -0,0 +1,59 @@
#include <SDL2/SDL.h>
#include <stdio.h>
#include <string.h>
#include "utils.h"
const char *resource_folder(void)
{
#ifdef DATA_DIR
return DATA_DIR;
#else
static const char *ret = NULL;
if (!ret) {
ret = SDL_GetBasePath();
if (!ret) {
ret = "./";
}
}
return ret;
#endif
}
char *resource_path(const char *filename)
{
static char path[1024];
snprintf(path, sizeof(path), "%s%s", resource_folder(), filename);
return path;
}
void replace_extension(const char *src, size_t length, char *dest, const char *ext)
{
memcpy(dest, src, length);
dest[length] = 0;
/* Remove extension */
for (size_t i = length; i--;) {
if (dest[i] == '/') break;
if (dest[i] == '.') {
dest[i] = 0;
break;
}
}
/* Add new extension */
strcat(dest, ext);
}
char* concat(const char *s1, const char *s2)
{
char *result = malloc(strlen(s1) + strlen(s2) + 1); // +1 for the null-terminator
if (!result) {
fprintf(stderr, "Failed to allocate memory\n");
exit(EXIT_FAILURE);
}
strcpy(result, s1);
strcat(result, s2);
return result;
}

10
wasm/utils.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef utils_h
#define utils_h
#include <stddef.h>
const char *resource_folder(void);
char *resource_path(const char *filename);
void replace_extension(const char *src, size_t length, char *dest, const char *ext);
char* concat(const char *s1, const char *s2);
#endif /* utils_h */