Compare commits
14 Commits
Author | SHA1 | Date |
---|---|---|
Maximilian Mader | d2b919746e | |
Maximilian Mader | 9dc9e58a7f | |
Maximilian Mader | 405d85343f | |
Maximilian Mader | bb5d7bcf00 | |
Maximilian Mader | 8f03312ad6 | |
Maximilian Mader | 19df86cc48 | |
Maximilian Mader | 5c4603891c | |
Maximilian Mader | e2ce844d2b | |
Maximilian Mader | 5029b9f2a6 | |
Maximilian Mader | c2432b7348 | |
Maximilian Mader | f9c932b737 | |
Maximilian Mader | 7550707562 | |
Maximilian Mader | 3ad106d9c5 | |
Maximilian Mader | 0692347002 |
32
Makefile
32
Makefile
|
@ -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
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
10
SDL/main.c
10
SDL/main.c
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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"
|
|
@ -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
|
|
@ -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
|
||||
|
||||
<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>
|
|
@ -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);
|
||||
}
|
|
@ -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 */
|
|
@ -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);
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
#include "opengl_compat.h"
|
|
@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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);
|
||||
}
|
|
@ -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 */
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
Loading…
Reference in New Issue