Mercurial > repos > blastem
changeset 1842:49f65d240299 mame_interp
Merge from default
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Sun, 14 Apr 2019 23:38:02 -0700 |
parents | 0c6d07f91346 (current diff) 78abbabfd58d (diff) |
children | 13abdc98379e |
files | Makefile blastem.c gdb_remote.c genesis.c sms.c |
diffstat | 46 files changed, 4198 insertions(+), 942 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgtags Thu Mar 14 23:40:50 2019 -0700 +++ b/.hgtags Sun Apr 14 23:38:02 2019 -0700 @@ -10,3 +10,4 @@ 3d48cb0c28be9045866e00795b698086018b825f v0.5.1 ef50c9affe6a7c86398f2c36eb5439a559808108 v0.6.0 357b4951d9b2d1999e4c2765ee53e946aaab864d v0.6.1 +8aeac7bd9fa7d9d978c99ec07e9a68989a12e453 v0.6.2
--- a/Android.mk Thu Mar 14 23:40:50 2019 -0700 +++ b/Android.mk Sun Apr 14 23:38:02 2019 -0700 @@ -8,14 +8,21 @@ LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include -LOCAL_CFLAGS += -std=gnu99 -DX86_32 -DDISABLE_OPENGL +LOCAL_CFLAGS += -std=gnu99 -DX86_32 -DUSE_GLES # Add your application source files here... LOCAL_SRC_FILES := $(SDL_PATH)/src/main/android/SDL_android_main.c \ 68kinst.c debug.c gst.c psg.c z80_to_x86.c backend.c io.c render_sdl.c \ tern.c backend_x86.c gdb_remote.c m68k_core.c romdb.c m68k_core_x86.c \ util.c wave.c blastem.c gen.c mem.c vdp.c ym2612.c config.c gen_x86.c \ - terminal.c z80inst.c menu.c arena.c + terminal.c z80inst.c menu.c arena.c zlib/adler32.c zlib/compress.c \ + zlib/crc32.c zlib/deflate.c zlib/gzclose.c zlib/gzlib.c zlib/gzread.c \ + zlib/gzwrite.c zlib/infback.c zlib/inffast.c zlib/inflate.c \ + zlib/inftrees.c zlib/trees.c zlib/uncompr.c zlib/zutil.c \ + nuklear_ui/font_android.c nuklear_ui/blastem_nuklear.c nuklear_ui/sfnt.c \ + ppm.c controller_info.c png.c system.c genesis.c sms.c serialize.c \ + saves.c hash.c xband.c zip.c bindings.c jcart.c paths.c megawifi.c \ + nor.c i2c.c sega_mapper.c realtec.c multi_game.c net.c LOCAL_SHARED_LIBRARIES := SDL2
--- a/CHANGELOG Thu Mar 14 23:40:50 2019 -0700 +++ b/CHANGELOG Sun Apr 14 23:38:02 2019 -0700 @@ -1,3 +1,33 @@ +0.6.2 +----- +*New Features* + + - Zipped and gzipped SMD ROMs are now supported + - Gain control for overall volume and FM/PSG invidually + +*Accuracy/Completeness Improvements* + + - Fixed timing of a few instructions in Z80 core + - Added optional emulation of YM2612 imperfections (aka "ladder effect") + - Fixed some unintentional extra precision in some FM LFO calculations + - Added a 1 sample delay in some FM operator results when used as modulators to match hardware + +*Bugfixes* + + - Fixed regression in NBA JAM TE and possibly other 32MBit Acclaim mapper titles + - Added code to handle controllers that have their d-pads mapped as buttons or axes + - Removed some problematic SDL2 game controller mappings + - Fixed crash that occurred when releasing mouse too clickly when loading a ROM + - Fixed SMD ROM support + - Fixed handling of audio contexts with more or less than 2 channels + - Fixed off-by-one error in IO device selection UI + - Fixed regression in GDB remote debugging support on Linux and OS X + +*Other Changes* + + - MegaWiFi hardware can now be enabled by a header string (still gated by config) + - Tweaked the style of checkboxes in the Nuklear UI to hopefully make the on/off state more clear + 0.6.1 ----- *Bugfixes* @@ -40,7 +70,7 @@ - Added support for Open GL ES in addition to the existing desktop GL support - Some small optimizations - Added ROM DB entry for Squirrel King to support it's copy protection - - Added support for float32 audio output (fixes an issue with defautl SDL2 driver in Windows when using more recent SDL2 versions) + - Added support for float32 audio output (fixes an issue with default SDL2 driver in Windows when using more recent SDL2 versions) 0.5.1 -----
--- a/Makefile Thu Mar 14 23:40:50 2019 -0700 +++ b/Makefile Sun Apr 14 23:38:02 2019 -0700 @@ -1,28 +1,43 @@ +#disable built-in rules +.SUFFIXES : + ifndef OS OS:=$(shell uname -s) endif FIXUP:=true +BUNDLED_LIBZ:=zlib/adler32.o zlib/compress.o zlib/crc32.o zlib/deflate.o zlib/gzclose.o zlib/gzlib.o zlib/gzread.o\ + zlib/gzwrite.o zlib/infback.o zlib/inffast.o zlib/inflate.o zlib/inftrees.o zlib/trees.o zlib/uncompr.o zlib/zutil.o + ifeq ($(OS),Windows) -ifndef SDL2_PREFIX -SDL2_PREFIX:="sdl/i686-w64-mingw32" -endif -ifndef GLEW_PREFIX + GLEW_PREFIX:=glew -endif -ifndef GLEW32S_LIB -GLEW32S_LIB:=$(GLEW_PREFIX)/lib/Release/Win32/glew32s.lib -endif - MEM:=mem_win.o TERMINAL:=terminal_win.o FONT:=nuklear_ui/font_win.o NET:=net_win.o EXE:=.exe +SO:=dll +CPU:=i686 +ifeq ($(CPU),i686) CC:=i686-w64-mingw32-gcc-win32 -CFLAGS:=-std=gnu99 -Wreturn-type -Werror=return-type -Werror=implicit-function-declaration -I"$(SDL2_PREFIX)/include/SDL2" -I"$(GLEW_PREFIX)/include" -DGLEW_STATIC -LDFLAGS:= $(GLEW32S_LIB) -L"$(SDL2_PREFIX)/lib" -lm -lmingw32 -lSDL2main -lSDL2 -lws2_32 -lopengl32 -lglu32 -mwindows -CPU:=i686 +WINDRES:=i686-w64-mingw32-windres +GLUDIR:=Win32 +SDL2_PREFIX:="sdl/i686-w64-mingw32" +else +CC:=x86_64-w64-mingw32-gcc-win32 +WINDRES:=x86_64-w64-mingw32-windres +SDL2_PREFIX:="sdl/x86_64-w64-mingw32" +GLUDIR:=x64 +endif +GLEW32S_LIB:=$(GLEW_PREFIX)/lib/Release/$(GLUDIR)/glew32s.lib +CFLAGS:=-std=gnu99 -Wreturn-type -Werror=return-type -Werror=implicit-function-declaration +LDFLAGS:=-lm -lmingw32 -lws2_32 -mwindows +ifneq ($(MAKECMDGOALS),libblastem.dll) +CFLAGS+= -I"$(SDL2_PREFIX)/include/SDL2" -I"$(GLEW_PREFIX)/include" -DGLEW_STATIC +LDFLAGS+= $(GLEW32S_LIB) -L"$(SDL2_PREFIX)/lib" -lSDL2main -lSDL2 -lopengl32 -lglu32 +endif +LIBZOBJS=$(BUNDLED_LIBZ) else @@ -37,7 +52,9 @@ ifeq ($(OS),Darwin) LIBS=sdl2 glew FONT:=nuklear_ui/font_mac.o +SO:=dylib else +SO:=so ifdef USE_FBDEV LIBS=alsa @@ -64,8 +81,7 @@ LIBS+= zlib LIBZOBJS= else -LIBZOBJS=zlib/adler32.o zlib/compress.o zlib/crc32.o zlib/deflate.o zlib/gzclose.o zlib/gzlib.o zlib/gzread.o\ - zlib/gzwrite.o zlib/infback.o zlib/inffast.o zlib/inflate.o zlib/inftrees.o zlib/trees.o zlib/uncompr.o zlib/zutil.o +LIBZOBJS=$(BUNDLED_LIBZ) endif ifeq ($(OS),Darwin) @@ -96,7 +112,7 @@ endif #Darwin else -ifeq ($(MAKECMDGOALS),libblastem.so) +ifeq ($(MAKECMDGOALS),libblastem.$(SO)) LDFLAGS:=-lm else CFLAGS:=$(shell pkg-config --cflags-only-I $(LIBS)) $(CFLAGS) @@ -138,7 +154,6 @@ endif ifdef NOGL CFLAGS+= -DDISABLE_OPENGL -NONUKLEAR:=1 endif ifdef M68030 @@ -204,9 +219,9 @@ realtec.o i2c.o nor.o sega_mapper.o multi_game.o megawifi.o $(NET) serialize.o $(TERMINAL) $(CONFIGOBJS) \ $(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o zip.o bindings.o jcart.o -LIBOBJS=libblastem.o system.o genesis.o debug.o gdb_remote.o vdp.o io.o romdb.o hash.o menu.o xband.o realtec.o \ +LIBOBJS=libblastem.o system.o genesis.o debug.o gdb_remote.o vdp.o io.o romdb.o hash.o xband.o realtec.o \ i2c.o nor.o sega_mapper.o multi_game.o megawifi.o $(NET) serialize.o $(TERMINAL) $(CONFIGOBJS) gst.o \ - $(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o jcart.o + $(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o jcart.o rom.db.o ifdef NONUKLEAR CFLAGS+= -DDISABLE_NUKLEAR @@ -250,13 +265,13 @@ ALL+= termhelper endif -ifeq ($(MAKECMDGOALS),libblastem.so) +ifeq ($(MAKECMDGOALS),libblastem.$(SO)) CFLAGS+= -fpic -DIS_LIB endif all : $(ALL) -libblastem.so : $(LIBOBJS) +libblastem.$(SO) : $(LIBOBJS) $(CC) -shared -o $@ $^ $(LDFLAGS) blastem$(EXE) : $(MAINOBJS) @@ -266,6 +281,9 @@ blastjag$(EXE) : jaguar.o jag_video.o $(RENDEROBJS) serialize.o $(M68KOBJS) $(TRANSOBJS) $(CONFIGOBJS) $(CC) -o $@ $^ $(LDFLAGS) +termhelper : termhelper.o + $(CC) -o $@ $^ $(LDFLAGS) + dis$(EXE) : dis.o 68kinst.o tern.o vos_program_module.o $(CC) -o $@ $^ $(OPT) @@ -328,6 +346,9 @@ %.c : %.cpu cpu_dsl.py ./cpu_dsl.py -d goto $< > $@ +%.db.c : %.db + sed $< -e 's/"/\\"/g' -e 's/^\(.*\)$$/"\1\\n"/' -e'1s/^\(.*\)$$/const char $(shell echo $< | tr '.' '_')_data[] = \1/' -e '$$s/^\(.*\)$$/\1;/' > $@ + %.o : %.S $(CC) -c -o $@ $< @@ -349,7 +370,7 @@ %.bin : %.sz8 vasmz80_mot -Fbin -spaces -o $@ $< res.o : blastem.rc - i686-w64-mingw32-windres blastem.rc res.o + $(WINDRES) blastem.rc res.o arrow.tiles : arrow.png cursor.tiles : cursor.png
--- a/README Thu Mar 14 23:40:50 2019 -0700 +++ b/README Sun Apr 14 23:38:02 2019 -0700 @@ -281,6 +281,22 @@ at least some Genesis/Megadrive models. Other models reportedly use an even lower value. +"gain" specifies the gain in decibels to be applied to the overall output. + +"fm_gain" specifies the gain to be applied to the emulated FM output before +mixing with the PSG. + +"psg_gain" specifies the gain to be applied to the emulated PSG output before +mixing with the FM chip. + +"fm_dac" controls the characteristics of the DAC in the emulated FM chip. If +this is set to "linear", then the DAC will have precise linear output similar +to the integrated YM3438 in later Gen/MD consoles. If it is set to "zero_offset", +there will be a larger gap between -1 and 0. This is commonly referred to as the +"ladder effect". This will also cause "leakage" on channels that are muted or +panned to one side in a similar manner to a discrete YM2612. + + Clocks ------ @@ -495,6 +511,15 @@ Eke-Eke - Eke-Eke wrote a great document on the use of I2C EEPROM in Genesis games and also left some useful very helpful comments about problematic games in Genesis Plus GX + +Sauraen - Sauraen has analyzed the YM2203 and YM2612 dies and written + a VHDL operator implementation. These have been useful in + improving the accuracy of my YM2612 core. + +Alexey Khokholov - Alexey (aka Nuke.YKT) has analyzed the YM3438 die and written + a fairly direct C implementation from that analysis. This + has been a useful reference for verifying and improving my + YM2612 core. Bart Trzynadlowski - His documents on the Genecyst save-state format and the mapper used in Super Street Fighter 2 were definitely @@ -530,5 +555,6 @@ modify the program as long as you follow the terms of the license. See the file COPYING for full license details. -Binary releases of BlastEm are packaged with GLEW and SDL2 which have thier own -licenses. See GLEW-LICENSE and SDL-LICENSE for details. +Binary releases of BlastEm are packaged with GLEW, SDL2 and zlib which have their +own licenses. See GLEW-LICENSE and SDL-LICENSE for details. For zlib license +information, please see zlib.h in the source code release.
--- a/android/assets/default.cfg Thu Mar 14 23:40:50 2019 -0700 +++ b/android/assets/default.cfg Sun Apr 14 23:38:02 2019 -0700 @@ -14,9 +14,14 @@ f gamepads.1.mode enter gamepads.1.start + r ui.release_mouse [ ui.vdp_debug_mode - ] ui.vdp_debug_pal u ui.enter_debugger + p ui.screenshot + b ui.plane_debug + v ui.vram_debug + c ui.cram_debug + n ui.compositing_debug esc ui.exit ` ui.save_state 0 ui.set_speed.0 @@ -29,6 +34,11 @@ 7 ui.set_speed.7 = ui.next_speed - ui.prev_speed + f11 ui.toggle_fullscreen + tab ui.soft_reset + f5 ui.reload + z ui.sms_pause + rctrl ui.toggle_keyboard_captured select gamepads.1.c @@ -36,44 +46,230 @@ back ui.exit } pads { - 0 { + default { dpads { 0 { - up gamepads.1.up - down gamepads.1.down - left gamepads.1.left - right gamepads.1.right + up gamepads.n.up + down gamepads.n.down + left gamepads.n.left + right gamepads.n.right } } buttons { - 0 gamepads.1.a - 1 gamepads.1.b - 2 gamepads.1.c - 3 gamepads.1.x - 4 gamepads.1.y - 5 gamepads.1.z - 6 gamepads.1.mode - 7 gamepads.1.start + a gamepads.n.a + b gamepads.n.b + rightshoulder gamepads.n.c + x gamepads.n.x + y gamepads.n.y + leftshoulder gamepads.n.z + back gamepads.n.mode + start gamepads.n.start + guide ui.exit + leftstick ui.save_state + } + axes { + lefty.positive gamepads.n.down + lefty.negative gamepads.n.up + leftx.positive gamepads.n.right + leftx.negative gamepads.n.left + lefttrigger ui.prev_speed + righttrigger ui.next_speed + } + } + ps4_6b_right { + axes { + lefttrigger ui.next_speed + leftx.negative gamepads.n.up + leftx.positive gamepads.n.down + lefty.negative gamepads.n.left + lefty.positive gamepads.n.right + righttrigger gamepads.n.c + } + buttons { + a gamepads.n.a + b gamepads.n.b + back ui.sms_pause + guide ui.exit + leftshoulder gamepads.n.mode + leftstick ui.save_state + rightshoulder gamepads.n.z + rightstick ui.prev_speed + start gamepads.n.start + x gamepads.n.x + y gamepads.n.y + } + dpads { + 0 { + down gamepads.n.down + left gamepads.n.left + right gamepads.n.right + up gamepads.n.up + } } } - 1 { + ps3_6b_right { + axes { + lefttrigger ui.next_speed + leftx.negative gamepads.n.up + leftx.positive gamepads.n.down + lefty.negative gamepads.n.left + lefty.positive gamepads.n.right + righttrigger gamepads.n.c + } + buttons { + a gamepads.n.a + b gamepads.n.b + back ui.sms_pause + guide ui.exit + leftshoulder gamepads.n.mode + leftstick ui.save_state + rightshoulder gamepads.n.z + rightstick ui.prev_speed + start gamepads.n.start + x gamepads.n.x + y gamepads.n.y + } + dpads { + 0 { + down gamepads.n.down + left gamepads.n.left + right gamepads.n.right + up gamepads.n.up + } + } + } + xbox_360_6b_right { + axes { + lefttrigger ui.next_speed + leftx.negative gamepads.n.up + leftx.positive gamepads.n.down + lefty.negative gamepads.n.left + lefty.positive gamepads.n.right + righttrigger gamepads.n.c + } + buttons { + a gamepads.n.a + b gamepads.n.b + back ui.sms_pause + guide ui.exit + leftshoulder gamepads.n.mode + leftstick ui.save_state + rightshoulder gamepads.n.z + rightstick ui.prev_speed + start gamepads.n.start + x gamepads.n.x + y gamepads.n.y + } dpads { 0 { - up gamepads.2.up - down gamepads.2.down - left gamepads.2.left - right gamepads.2.right + down gamepads.n.down + left gamepads.n.left + right gamepads.n.right + up gamepads.n.up } } + } + xbone_6b_right { + axes { + lefttrigger ui.next_speed + leftx.negative gamepads.n.up + leftx.positive gamepads.n.down + lefty.negative gamepads.n.left + lefty.positive gamepads.n.right + righttrigger gamepads.n.c + } + buttons { + a gamepads.n.a + b gamepads.n.b + back ui.sms_pause + guide ui.exit + leftshoulder gamepads.n.mode + leftstick ui.save_state + rightshoulder gamepads.n.z + rightstick ui.prev_speed + start gamepads.n.start + x gamepads.n.x + y gamepads.n.y + } + dpads { + 0 { + down gamepads.n.down + left gamepads.n.left + right gamepads.n.right + up gamepads.n.up + } + } + } + genesis_6b_bumpers { + axes { + lefttrigger ui.exit + righttrigger gamepads.n.mode + } buttons { - 0 gamepads.2.a - 1 gamepads.2.b - 2 gamepads.2.c - 3 gamepads.2.x - 4 gamepads.2.y - 5 gamepads.2.z - 6 gamepads.2.mode - 7 gamepads.2.start + a gamepads.n.a + b gamepads.n.b + back ui.sms_pause + guide ui.exit + leftshoulder gamepads.n.z + rightshoulder gamepads.n.c + start gamepads.n.start + x gamepads.n.x + y gamepads.n.y + } + dpads { + 0 { + down gamepads.n.down + left gamepads.n.left + right gamepads.n.right + up gamepads.n.up + } + } + } + saturn_6b_bumpers { + axes { + lefttrigger ui.exit + righttrigger gamepads.n.mode + } + buttons { + a gamepads.n.a + b gamepads.n.b + back ui.sms_pause + guide ui.exit + leftshoulder gamepads.n.z + rightshoulder gamepads.n.c + start gamepads.n.start + x gamepads.n.x + y gamepads.n.y + } + dpads { + 0 { + down gamepads.n.down + left gamepads.n.left + right gamepads.n.right + up gamepads.n.up + } + } + } + } + mice { + 0 { + motion mouse.1.motion + buttons { + 1 mouse.1.left + 2 mouse.1.middle + 3 mouse.1.right + 4 mouse.1.start + } + } + #having the second host mouse also mapped to the first emulated + #mouse is useful for laptop users with an external mouse + 1 { + motion mouse.1.motion + buttons { + 1 mouse.1.left + 2 mouse.1.middle + 3 mouse.1.right + 4 mouse.1.start } } } @@ -87,19 +283,69 @@ } video { + #special value "stretch" will cause aspect to match window aspect ratio + aspect 4:3 width 640 + #height is normally calculated automatically from width using the aspect setting + #if you would like to set it explicitly, uncomment the line below + #height 480 vertex_shader default.v.glsl fragment_shader default.f.glsl + scanlines off + vsync off + fullscreen off + #setting gl to off, will force use of the SDL2 fallback renderer + #this is useful for those running on machines with Open GL 2.0 unavailable + #so the warning doesn't display on startup + gl on + #scaling can be linear (for linear interpolation) or nearest (for nearest neighbor) + scaling linear + ntsc { + overscan { + #these values will result in square pixels in H40 mode + top 2 + bottom 1 + #if you want to completely hide the border instead + #comment out those two lines and uncomment these + #top 11 + #bottom 8 + + #these values will completely hide the horizontal border + left 13 + right 14 + } + } + pal { + overscan { + #these values will produce the same size border in V30 mode + #as the default NTSC settings will produce in V24 mode + #this results in a slightly vertically squished picture + #which is probably approximately correct on a properly calibrated TV + top 21 + bottom 17 + #for square pixels and zero border in V30 mode + #coment out those two lines and uncomment these + #top 30 + #bottom 24 + + #these values will completely hide the horizontal border + left 13 + right 14 + } + } } audio { rate 48000 buffer 512 + lowpass_cutoff 3390 } clocks { + m68k_divider 7 max_cycles 3420 speeds { + 0 100 1 150 2 200 3 300 @@ -111,8 +357,39 @@ } ui { + #specifies the ROM that implements the Menu UI rom menu.bin + #starting path for ROM browsing, accepts special variables $HOME, $EXEDIR + #and variables defined in the OS environment + initial_path $HOME + #if this is set to on, then the menu will remember the last path when visited + #if it's set to off, initial_path will always be used on startup + remember_path on + #path for storing internal screenshots, accepts the same variables as initial_path + screenshot_path $HOME + #see strftime for the format specifiers valid in screenshot_template + screenshot_template blastem_%Y%m%d_%H%M%S.png + #path template for saving SRAM, EEPROM and savestates + #accepts special variables $HOME, $EXEDIR, $USERDATA, $ROMNAME + save_path $USERDATA/blastem/$ROMNAME + #space delimited list of file extensions to filter against in menu + extensions bin gen md smd sms gg zip gz + #specifies the preferred save-state format, set to gst for Genecyst compatible states + state_format native } -default_region U +system { + #controls how the emulated system is synced to the host + #video provides the smoothest experience when the host and emulated system have similar refresh rates + #audio provides lower audio latency, especially when there is a refresh rate mismatch + sync_source audio + #set this to random to debug initialization bugs + ram_init zero + default_region U + #controls whether MegaWiFi support is enabled or not + #MegaWiFi allows ROMs to make connections to the internet + #so it should only be enabled for ROMs you trust + megawifi off +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/assets/images Sun Apr 14 23:38:02 2019 -0700 @@ -0,0 +1,1 @@ +../../images \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/assets/shaders Sun Apr 14 23:38:02 2019 -0700 @@ -0,0 +1,1 @@ +../../shaders \ No newline at end of file
--- a/android/jni/Application.mk Thu Mar 14 23:40:50 2019 -0700 +++ b/android/jni/Application.mk Sun Apr 14 23:38:02 2019 -0700 @@ -4,3 +4,5 @@ # APP_STL := stlport_static APP_ABI := x86 +APP_PLATFORM := android-16 +APP_OPTIM := release
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/src/org/libsdl/app/SDL.java Sun Apr 14 23:38:02 2019 -0700 @@ -0,0 +1,37 @@ +package org.libsdl.app; + +import android.content.Context; + +/** + SDL library initialization +*/ +public class SDL { + + // This function should be called first and sets up the native code + // so it can call into the Java classes + public static void setupJNI() { + SDLActivity.nativeSetupJNI(); + SDLAudioManager.nativeSetupJNI(); + SDLControllerManager.nativeSetupJNI(); + } + + // This function should be called each time the activity is started + public static void initialize() { + setContext(null); + + SDLActivity.initialize(); + SDLAudioManager.initialize(); + SDLControllerManager.initialize(); + } + + // This function stores the current activity (SDL or not) + public static void setContext(Context context) { + mContext = context; + } + + public static Context getContext() { + return mContext; + } + + protected static Context mContext; +}
--- a/android/src/org/libsdl/app/SDLActivity.java Thu Mar 14 23:40:50 2019 -0700 +++ b/android/src/org/libsdl/app/SDLActivity.java Sun Apr 14 23:38:02 2019 -0700 @@ -1,25 +1,30 @@ package org.libsdl.app; -import java.util.ArrayList; +import java.io.IOException; +import java.io.InputStream; import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; +import java.lang.reflect.Method; +import java.util.Objects; import android.app.*; import android.content.*; +import android.text.InputType; import android.view.*; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; -import android.widget.AbsoluteLayout; +import android.widget.RelativeLayout; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; import android.os.*; import android.util.Log; +import android.util.SparseArray; import android.graphics.*; -import android.media.*; +import android.graphics.drawable.Drawable; import android.hardware.*; - +import android.content.pm.ActivityInfo; /** SDL Activity @@ -27,34 +32,96 @@ public class SDLActivity extends Activity { private static final String TAG = "SDL"; - // Keep track of the paused state - public static boolean mIsPaused, mIsSurfaceReady, mHasFocus; + public static boolean mIsResumedCalled, mIsSurfaceReady, mHasFocus; + + // Handle the state of the native layer + public enum NativeState { + INIT, RESUMED, PAUSED + } + + public static NativeState mNextNativeState; + public static NativeState mCurrentNativeState; + public static boolean mExitCalledFromJava; + /** If shared libraries (e.g. SDL or the native application) could not be loaded. */ + public static boolean mBrokenLibraries; + + // If we want to separate mouse and touch events. + // This is only toggled in native code when a hint is set! + public static boolean mSeparateMouseAndTouch; + // Main components protected static SDLActivity mSingleton; protected static SDLSurface mSurface; protected static View mTextEdit; + protected static boolean mScreenKeyboardShown; protected static ViewGroup mLayout; - protected static SDLJoystickHandler mJoystickHandler; + protected static SDLClipboardHandler mClipboardHandler; + // This is what SDL runs in. It invokes SDL_main(), eventually protected static Thread mSDLThread; - - // Audio - protected static AudioTrack mAudioTrack; + + /** + * This method returns the name of the shared object with the application entry point + * It can be overridden by derived classes. + */ + protected String getMainSharedObject() { + String library; + String[] libraries = SDLActivity.mSingleton.getLibraries(); + if (libraries.length > 0) { + library = "lib" + libraries[libraries.length - 1] + ".so"; + } else { + library = "libmain.so"; + } + return library; + } + + /** + * This method returns the name of the application entry point + * It can be overridden by derived classes. + */ + protected String getMainFunction() { + return "SDL_main"; + } + + /** + * This method is called by SDL before loading the native shared libraries. + * It can be overridden to provide names of shared libraries to be loaded. + * The default implementation returns the defaults. It never returns null. + * An array returned by a new implementation must at least contain "SDL2". + * Also keep in mind that the order the libraries are loaded may matter. + * @return names of shared libraries to be loaded (e.g. "SDL2", "main"). + */ + protected String[] getLibraries() { + return new String[] { + "SDL2", + // "SDL2_image", + // "SDL2_mixer", + // "SDL2_net", + // "SDL2_ttf", + "main" + }; + } // Load the .so - static { - System.loadLibrary("SDL2"); - //System.loadLibrary("SDL2_image"); - //System.loadLibrary("SDL2_mixer"); - //System.loadLibrary("SDL2_net"); - //System.loadLibrary("SDL2_ttf"); - System.loadLibrary("main"); + public void loadLibraries() { + for (String lib : getLibraries()) { + System.loadLibrary(lib); + } } - - + + /** + * This method is called by SDL before starting the native application thread. + * It can be overridden to provide the arguments after the application name. + * The default implementation returns an empty array. It never returns null. + * @return arguments for the native application. + */ + protected String[] getArguments() { + return new String[0]; + } + public static void initialize() { // The static nature of the singleton and Android quirkyness force us to initialize everything here // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values @@ -62,78 +129,172 @@ mSurface = null; mTextEdit = null; mLayout = null; - mJoystickHandler = null; + mClipboardHandler = null; mSDLThread = null; - mAudioTrack = null; mExitCalledFromJava = false; - mIsPaused = false; + mBrokenLibraries = false; + mIsResumedCalled = false; mIsSurfaceReady = false; mHasFocus = true; + mNextNativeState = NativeState.INIT; + mCurrentNativeState = NativeState.INIT; } // Setup @Override protected void onCreate(Bundle savedInstanceState) { - Log.v("SDL", "onCreate():" + mSingleton); + Log.v(TAG, "Device: " + android.os.Build.DEVICE); + Log.v(TAG, "Model: " + android.os.Build.MODEL); + Log.v(TAG, "onCreate()"); super.onCreate(savedInstanceState); - - SDLActivity.initialize(); + + // Load shared libraries + String errorMsgBrokenLib = ""; + try { + loadLibraries(); + } catch(UnsatisfiedLinkError e) { + System.err.println(e.getMessage()); + mBrokenLibraries = true; + errorMsgBrokenLib = e.getMessage(); + } catch(Exception e) { + System.err.println(e.getMessage()); + mBrokenLibraries = true; + errorMsgBrokenLib = e.getMessage(); + } + + if (mBrokenLibraries) + { + AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this); + dlgAlert.setMessage("An error occurred while trying to start the application. Please try again and/or reinstall." + + System.getProperty("line.separator") + + System.getProperty("line.separator") + + "Error: " + errorMsgBrokenLib); + dlgAlert.setTitle("SDL Error"); + dlgAlert.setPositiveButton("Exit", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog,int id) { + // if this button is clicked, close current activity + SDLActivity.mSingleton.finish(); + } + }); + dlgAlert.setCancelable(false); + dlgAlert.create().show(); + + return; + } + + // Set up JNI + SDL.setupJNI(); + + // Initialize state + SDL.initialize(); + // So we can call stuff from static callbacks mSingleton = this; + SDL.setContext(this); + + if (Build.VERSION.SDK_INT >= 11) { + mClipboardHandler = new SDLClipboardHandler_API11(); + } else { + /* Before API 11, no clipboard notification (eg no SDL_CLIPBOARDUPDATE) */ + mClipboardHandler = new SDLClipboardHandler_Old(); + } // Set up the surface mSurface = new SDLSurface(getApplication()); - - if(Build.VERSION.SDK_INT >= 12) { - mJoystickHandler = new SDLJoystickHandler_API12(); - } - else { - mJoystickHandler = new SDLJoystickHandler(); - } - mLayout = new AbsoluteLayout(this); + mLayout = new RelativeLayout(this); mLayout.addView(mSurface); setContentView(mLayout); + + // Get filename from "Open with" of another application + Intent intent = getIntent(); + if (intent != null && intent.getData() != null) { + String filename = intent.getData().getPath(); + if (filename != null) { + Log.v(TAG, "Got filename: " + filename); + SDLActivity.onNativeDropFile(filename); + } + } } // Events @Override protected void onPause() { - Log.v("SDL", "onPause()"); + Log.v(TAG, "onPause()"); super.onPause(); - SDLActivity.handlePause(); + mNextNativeState = NativeState.PAUSED; + mIsResumedCalled = false; + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.handleNativeState(); } @Override protected void onResume() { - Log.v("SDL", "onResume()"); + Log.v(TAG, "onResume()"); super.onResume(); - SDLActivity.handleResume(); + mNextNativeState = NativeState.RESUMED; + mIsResumedCalled = true; + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.handleNativeState(); } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); - Log.v("SDL", "onWindowFocusChanged(): " + hasFocus); + Log.v(TAG, "onWindowFocusChanged(): " + hasFocus); + + if (SDLActivity.mBrokenLibraries) { + return; + } SDLActivity.mHasFocus = hasFocus; if (hasFocus) { - SDLActivity.handleResume(); + mNextNativeState = NativeState.RESUMED; + } else { + mNextNativeState = NativeState.PAUSED; } + + SDLActivity.handleNativeState(); } @Override public void onLowMemory() { - Log.v("SDL", "onLowMemory()"); + Log.v(TAG, "onLowMemory()"); super.onLowMemory(); + + if (SDLActivity.mBrokenLibraries) { + return; + } + SDLActivity.nativeLowMemory(); } @Override protected void onDestroy() { - Log.v("SDL", "onDestroy()"); + Log.v(TAG, "onDestroy()"); + + if (SDLActivity.mBrokenLibraries) { + super.onDestroy(); + // Reset everything in case the user re opens the app + SDLActivity.initialize(); + return; + } + + mNextNativeState = NativeState.PAUSED; + SDLActivity.handleNativeState(); + // Send a quit message to the application SDLActivity.mExitCalledFromJava = true; SDLActivity.nativeQuit(); @@ -143,57 +304,101 @@ try { SDLActivity.mSDLThread.join(); } catch(Exception e) { - Log.v("SDL", "Problem stopping thread: " + e); + Log.v(TAG, "Problem stopping thread: " + e); } SDLActivity.mSDLThread = null; - //Log.v("SDL", "Finished waiting for SDL thread"); + //Log.v(TAG, "Finished waiting for SDL thread"); } - + super.onDestroy(); + // Reset everything in case the user re opens the app SDLActivity.initialize(); } @Override public boolean dispatchKeyEvent(KeyEvent event) { + + if (SDLActivity.mBrokenLibraries) { + return false; + } + int keyCode = event.getKeyCode(); // Ignore certain special keys so they're handled by Android if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_CAMERA || - keyCode == 168 || /* API 11: KeyEvent.KEYCODE_ZOOM_IN */ - keyCode == 169 /* API 11: KeyEvent.KEYCODE_ZOOM_OUT */ + keyCode == KeyEvent.KEYCODE_ZOOM_IN || /* API 11 */ + keyCode == KeyEvent.KEYCODE_ZOOM_OUT /* API 11 */ ) { return false; } return super.dispatchKeyEvent(event); } - /** Called by onPause or surfaceDestroyed. Even if surfaceDestroyed - * is the first to be called, mIsSurfaceReady should still be set - * to 'true' during the call to onPause (in a usual scenario). - */ - public static void handlePause() { - if (!SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady) { - SDLActivity.mIsPaused = true; - SDLActivity.nativePause(); - mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, false); + /* Transition to next state */ + public static void handleNativeState() { + + if (mNextNativeState == mCurrentNativeState) { + // Already in same state, discard. + return; + } + + // Try a transition to init state + if (mNextNativeState == NativeState.INIT) { + + mCurrentNativeState = mNextNativeState; + return; + } + + // Try a transition to paused state + if (mNextNativeState == NativeState.PAUSED) { + nativePause(); + mSurface.handlePause(); + mCurrentNativeState = mNextNativeState; + return; + } + + // Try a transition to resumed state + if (mNextNativeState == NativeState.RESUMED) { + if (mIsSurfaceReady && mHasFocus && mIsResumedCalled) { + if (mSDLThread == null) { + // This is the entry point to the C app. + // Start up the C app thread and enable sensor input for the first time + // FIXME: Why aren't we enabling sensor input at start? + + final Thread sdlThread = new Thread(new SDLMain(), "SDLThread"); + mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true); + sdlThread.start(); + + // Set up a listener thread to catch when the native thread ends + mSDLThread = new Thread(new Runnable() { + @Override + public void run() { + try { + sdlThread.join(); + } catch (Exception e) { + // Ignore any exception + } finally { + // Native thread has finished + if (!mExitCalledFromJava) { + handleNativeExit(); + } + } + } + }, "SDLThreadListener"); + + mSDLThread.start(); + } + + nativeResume(); + mSurface.handleResume(); + mCurrentNativeState = mNextNativeState; + } } } - /** Called by onResume or surfaceCreated. An actual resume should be done only when the surface is ready. - * Note: Some Android variants may send multiple surfaceChanged events, so we don't need to resume - * every time we get one of those events, only if it comes after surfaceDestroyed - */ - public static void handleResume() { - if (SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady && SDLActivity.mHasFocus) { - SDLActivity.mIsPaused = false; - SDLActivity.nativeResume(); - mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true); - } - } - /* The native thread has finished */ public static void handleNativeExit() { SDLActivity.mSDLThread = null; @@ -205,6 +410,7 @@ static final int COMMAND_CHANGE_TITLE = 1; static final int COMMAND_UNUSED = 2; static final int COMMAND_TEXTEDIT_HIDE = 3; + static final int COMMAND_SET_KEEP_SCREEN_ON = 5; protected static final int COMMAND_USER = 0x8000; @@ -228,7 +434,7 @@ protected static class SDLCommandHandler extends Handler { @Override public void handleMessage(Message msg) { - Context context = getContext(); + Context context = SDL.getContext(); if (context == null) { Log.e(TAG, "error handling message, getContext() returned null"); return; @@ -243,13 +449,31 @@ break; case COMMAND_TEXTEDIT_HIDE: if (mTextEdit != null) { - mTextEdit.setVisibility(View.GONE); + // Note: On some devices setting view to GONE creates a flicker in landscape. + // Setting the View's sizes to 0 is similar to GONE but without the flicker. + // The sizes will be set to useful values when the keyboard is shown again. + mTextEdit.setLayoutParams(new RelativeLayout.LayoutParams(0, 0)); InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0); + + mScreenKeyboardShown = false; } break; - + case COMMAND_SET_KEEP_SCREEN_ON: + { + if (context instanceof Activity) { + Window window = ((Activity) context).getWindow(); + if (window != null) { + if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) { + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } else { + window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } + } + break; + } default: if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) { Log.e(TAG, "error handling message, command is " + msg.arg1); @@ -270,76 +494,123 @@ } // C functions we call - public static native void nativeInit(); + public static native int nativeSetupJNI(); + public static native int nativeRunMain(String library, String function, Object arguments); public static native void nativeLowMemory(); public static native void nativeQuit(); public static native void nativePause(); public static native void nativeResume(); - public static native void onNativeResize(int x, int y, int format); - public static native int onNativePadDown(int device_id, int keycode); - public static native int onNativePadUp(int device_id, int keycode); - public static native void onNativeJoy(int device_id, int axis, - float value); - public static native void onNativeHat(int device_id, int hat_id, - int x, int y); + public static native void onNativeDropFile(String filename); + public static native void onNativeResize(int x, int y, int format, float rate); public static native void onNativeKeyDown(int keycode); public static native void onNativeKeyUp(int keycode); public static native void onNativeKeyboardFocusLost(); + public static native void onNativeMouse(int button, int action, float x, float y); public static native void onNativeTouch(int touchDevId, int pointerFingerId, - int action, float x, + int action, float x, float y, float p); public static native void onNativeAccel(float x, float y, float z); + public static native void onNativeClipboardChanged(); public static native void onNativeSurfaceChanged(); public static native void onNativeSurfaceDestroyed(); - public static native void nativeFlipBuffers(); - public static native int nativeAddJoystick(int device_id, String name, - int is_accelerometer, int nbuttons, - int naxes, int nhats, int nballs); - public static native int nativeRemoveJoystick(int device_id); + public static native String nativeGetHint(String name); - public static void flipBuffers() { - SDLActivity.nativeFlipBuffers(); - } - + /** + * This method is called by SDL using JNI. + */ public static boolean setActivityTitle(String title) { // Called from SDLMain() thread and can't directly affect the view return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title); } - public static boolean sendMessage(int command, int param) { - return mSingleton.sendCommand(command, Integer.valueOf(param)); + /** + * This method is called by SDL using JNI. + * This is a static method for JNI convenience, it calls a non-static method + * so that is can be overridden + */ + public static void setOrientation(int w, int h, boolean resizable, String hint) + { + if (mSingleton != null) { + mSingleton.setOrientationBis(w, h, resizable, hint); + } + } + + /** + * This can be overridden + */ + public void setOrientationBis(int w, int h, boolean resizable, String hint) + { + int orientation = -1; + + if (!Objects.equals(hint, "")) { + if (hint.contains("LandscapeRight") && hint.contains("LandscapeLeft")) { + orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; + } else if (hint.contains("LandscapeRight")) { + orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; + } else if (hint.contains("LandscapeLeft")) { + orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; + } else if (hint.contains("Portrait") && hint.contains("PortraitUpsideDown")) { + orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; + } else if (hint.contains("Portrait")) { + orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + } else if (hint.contains("PortraitUpsideDown")) { + orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; + } + } + + /* no valid hint */ + if (orientation == -1) { + if (resizable) { + /* no fixed orientation */ + } else { + if (w > h) { + orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; + } else { + orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; + } + } + } + + Log.v("SDL", "setOrientation() orientation=" + orientation + " width=" + w +" height="+ h +" resizable=" + resizable + " hint=" + hint); + if (orientation != -1) { + mSingleton.setRequestedOrientation(orientation); + } } - public static Context getContext() { - return mSingleton; + + /** + * This method is called by SDL using JNI. + */ + public static boolean isScreenKeyboardShown() + { + if (mTextEdit == null) { + return false; + } + + if (!mScreenKeyboardShown) { + return false; + } + + InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + return imm.isAcceptingText(); + } /** - * @return result of getSystemService(name) but executed on UI thread. + * This method is called by SDL using JNI. */ - public Object getSystemServiceFromUiThread(final String name) { - final Object lock = new Object(); - final Object[] results = new Object[2]; // array for writable variables - synchronized (lock) { - runOnUiThread(new Runnable() { - @Override - public void run() { - synchronized (lock) { - results[0] = getSystemService(name); - results[1] = Boolean.TRUE; - lock.notify(); - } - } - }); - if (results[1] == null) { - try { - lock.wait(); - } catch (InterruptedException ex) { - ex.printStackTrace(); - } - } + public static boolean sendMessage(int command, int param) { + if (mSingleton == null) { + return false; } - return results[0]; + return mSingleton.sendCommand(command, Integer.valueOf(param)); + } + + /** + * This method is called by SDL using JNI. + */ + public static Context getContext() { + return SDL.getContext(); } static class ShowTextInputTask implements Runnable { @@ -361,11 +632,12 @@ @Override public void run() { - AbsoluteLayout.LayoutParams params = new AbsoluteLayout.LayoutParams( - w, h + HEIGHT_PADDING, x, y); + RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(w, h + HEIGHT_PADDING); + params.leftMargin = x; + params.topMargin = y; if (mTextEdit == null) { - mTextEdit = new DummyEdit(getContext()); + mTextEdit = new DummyEdit(SDL.getContext()); mLayout.addView(mTextEdit, params); } else { @@ -375,101 +647,47 @@ mTextEdit.setVisibility(View.VISIBLE); mTextEdit.requestFocus(); - InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); imm.showSoftInput(mTextEdit, 0); + + mScreenKeyboardShown = true; } } + /** + * This method is called by SDL using JNI. + */ public static boolean showTextInput(int x, int y, int w, int h) { // Transfer the task to the main thread as a Runnable return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h)); } - - public static Surface getNativeSurface() { - return SDLActivity.mSurface.getNativeSurface(); + + public static boolean isTextInputEvent(KeyEvent event) { + + // Key pressed with Ctrl should be sent as SDL_KEYDOWN/SDL_KEYUP and not SDL_TEXTINPUT + if (android.os.Build.VERSION.SDK_INT >= 11) { + if (event.isCtrlPressed()) { + return false; + } + } + + return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE; } - // Audio - public static int audioInit(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) { - int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO; - int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT; - int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1); - - Log.v("SDL", "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer"); - - // Let the user pick a larger buffer if they really want -- but ye - // gods they probably shouldn't, the minimums are horrifyingly high - // latency already - desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize); - - if (mAudioTrack == null) { - mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, - channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM); - - // Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid - // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java - // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState() - - if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) { - Log.e("SDL", "Failed during initialization of Audio Track"); - mAudioTrack = null; - return -1; - } - - mAudioTrack.play(); + /** + * This method is called by SDL using JNI. + */ + public static Surface getNativeSurface() { + if (SDLActivity.mSurface == null) { + return null; } - - Log.v("SDL", "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer"); - - return 0; - } - - public static void audioWriteShortBuffer(short[] buffer) { - for (int i = 0; i < buffer.length; ) { - int result = mAudioTrack.write(buffer, i, buffer.length - i); - if (result > 0) { - i += result; - } else if (result == 0) { - try { - Thread.sleep(1); - } catch(InterruptedException e) { - // Nom nom - } - } else { - Log.w("SDL", "SDL audio: error return from write(short)"); - return; - } - } - } - - public static void audioWriteByteBuffer(byte[] buffer) { - for (int i = 0; i < buffer.length; ) { - int result = mAudioTrack.write(buffer, i, buffer.length - i); - if (result > 0) { - i += result; - } else if (result == 0) { - try { - Thread.sleep(1); - } catch(InterruptedException e) { - // Nom nom - } - } else { - Log.w("SDL", "SDL audio: error return from write(byte)"); - return; - } - } - } - - public static void audioQuit() { - if (mAudioTrack != null) { - mAudioTrack.stop(); - mAudioTrack = null; - } + return SDLActivity.mSurface.getNativeSurface(); } // Input /** + * This method is called by SDL using JNI. * @return an array which may be empty but is never null. */ public static int[] inputGetInputDeviceIds(int sources) { @@ -484,41 +702,331 @@ } return Arrays.copyOf(filtered, used); } - - // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance - public static boolean handleJoystickMotionEvent(MotionEvent event) { - return mJoystickHandler.handleMotionEvent(event); + + // APK expansion files support + + /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */ + private static Object expansionFile; + + /** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */ + private static Method expansionFileMethod; + + /** + * This method is called by SDL using JNI. + * @return an InputStream on success or null if no expansion file was used. + * @throws IOException on errors. Message is set for the SDL error message. + */ + public static InputStream openAPKExpansionInputStream(String fileName) throws IOException { + // Get a ZipResourceFile representing a merger of both the main and patch files + if (expansionFile == null) { + String mainHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION"); + if (mainHint == null) { + return null; // no expansion use if no main version was set + } + String patchHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION"); + if (patchHint == null) { + return null; // no expansion use if no patch version was set + } + + Integer mainVersion; + Integer patchVersion; + try { + mainVersion = Integer.valueOf(mainHint); + patchVersion = Integer.valueOf(patchHint); + } catch (NumberFormatException ex) { + ex.printStackTrace(); + throw new IOException("No valid file versions set for APK expansion files", ex); + } + + try { + // To avoid direct dependency on Google APK expansion library that is + // not a part of Android SDK we access it using reflection + expansionFile = Class.forName("com.android.vending.expansion.zipfile.APKExpansionSupport") + .getMethod("getAPKExpansionZipFile", Context.class, int.class, int.class) + .invoke(null, SDL.getContext(), mainVersion, patchVersion); + + expansionFileMethod = expansionFile.getClass() + .getMethod("getInputStream", String.class); + } catch (Exception ex) { + ex.printStackTrace(); + expansionFile = null; + expansionFileMethod = null; + throw new IOException("Could not access APK expansion support library", ex); + } + } + + // Get an input stream for a known file inside the expansion file ZIPs + InputStream fileStream; + try { + fileStream = (InputStream)expansionFileMethod.invoke(expansionFile, fileName); + } catch (Exception ex) { + // calling "getInputStream" failed + ex.printStackTrace(); + throw new IOException("Could not open stream from APK expansion file", ex); + } + + if (fileStream == null) { + // calling "getInputStream" was successful but null was returned + throw new IOException("Could not find path in APK expansion file"); + } + + return fileStream; + } + + // Messagebox + + /** Result of current messagebox. Also used for blocking the calling thread. */ + protected final int[] messageboxSelection = new int[1]; + + /** Id of current dialog. */ + protected int dialogs = 0; + + /** + * This method is called by SDL using JNI. + * Shows the messagebox from UI thread and block calling thread. + * buttonFlags, buttonIds and buttonTexts must have same length. + * @param buttonFlags array containing flags for every button. + * @param buttonIds array containing id for every button. + * @param buttonTexts array containing text for every button. + * @param colors null for default or array of length 5 containing colors. + * @return button id or -1. + */ + public int messageboxShowMessageBox( + final int flags, + final String title, + final String message, + final int[] buttonFlags, + final int[] buttonIds, + final String[] buttonTexts, + final int[] colors) { + + messageboxSelection[0] = -1; + + // sanity checks + + if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) { + return -1; // implementation broken + } + + // collect arguments for Dialog + + final Bundle args = new Bundle(); + args.putInt("flags", flags); + args.putString("title", title); + args.putString("message", message); + args.putIntArray("buttonFlags", buttonFlags); + args.putIntArray("buttonIds", buttonIds); + args.putStringArray("buttonTexts", buttonTexts); + args.putIntArray("colors", colors); + + // trigger Dialog creation on UI thread + + runOnUiThread(new Runnable() { + @Override + public void run() { + showDialog(dialogs++, args); + } + }); + + // block the calling thread + + synchronized (messageboxSelection) { + try { + messageboxSelection.wait(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + return -1; + } + } + + // return selected value + + return messageboxSelection[0]; + } + + @Override + protected Dialog onCreateDialog(int ignore, Bundle args) { + + // TODO set values from "flags" to messagebox dialog + + // get colors + + int[] colors = args.getIntArray("colors"); + int backgroundColor; + int textColor; + int buttonBorderColor; + int buttonBackgroundColor; + int buttonSelectedColor; + if (colors != null) { + int i = -1; + backgroundColor = colors[++i]; + textColor = colors[++i]; + buttonBorderColor = colors[++i]; + buttonBackgroundColor = colors[++i]; + buttonSelectedColor = colors[++i]; + } else { + backgroundColor = Color.TRANSPARENT; + textColor = Color.TRANSPARENT; + buttonBorderColor = Color.TRANSPARENT; + buttonBackgroundColor = Color.TRANSPARENT; + buttonSelectedColor = Color.TRANSPARENT; + } + + // create dialog with title and a listener to wake up calling thread + + final Dialog dialog = new Dialog(this); + dialog.setTitle(args.getString("title")); + dialog.setCancelable(false); + dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface unused) { + synchronized (messageboxSelection) { + messageboxSelection.notify(); + } + } + }); + + // create text + + TextView message = new TextView(this); + message.setGravity(Gravity.CENTER); + message.setText(args.getString("message")); + if (textColor != Color.TRANSPARENT) { + message.setTextColor(textColor); + } + + // create buttons + + int[] buttonFlags = args.getIntArray("buttonFlags"); + int[] buttonIds = args.getIntArray("buttonIds"); + String[] buttonTexts = args.getStringArray("buttonTexts"); + + final SparseArray<Button> mapping = new SparseArray<Button>(); + + LinearLayout buttons = new LinearLayout(this); + buttons.setOrientation(LinearLayout.HORIZONTAL); + buttons.setGravity(Gravity.CENTER); + for (int i = 0; i < buttonTexts.length; ++i) { + Button button = new Button(this); + final int id = buttonIds[i]; + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + messageboxSelection[0] = id; + dialog.dismiss(); + } + }); + if (buttonFlags[i] != 0) { + // see SDL_messagebox.h + if ((buttonFlags[i] & 0x00000001) != 0) { + mapping.put(KeyEvent.KEYCODE_ENTER, button); + } + if ((buttonFlags[i] & 0x00000002) != 0) { + mapping.put(KeyEvent.KEYCODE_ESCAPE, button); /* API 11 */ + } + } + button.setText(buttonTexts[i]); + if (textColor != Color.TRANSPARENT) { + button.setTextColor(textColor); + } + if (buttonBorderColor != Color.TRANSPARENT) { + // TODO set color for border of messagebox button + } + if (buttonBackgroundColor != Color.TRANSPARENT) { + Drawable drawable = button.getBackground(); + if (drawable == null) { + // setting the color this way removes the style + button.setBackgroundColor(buttonBackgroundColor); + } else { + // setting the color this way keeps the style (gradient, padding, etc.) + drawable.setColorFilter(buttonBackgroundColor, PorterDuff.Mode.MULTIPLY); + } + } + if (buttonSelectedColor != Color.TRANSPARENT) { + // TODO set color for selected messagebox button + } + buttons.addView(button); + } + + // create content + + LinearLayout content = new LinearLayout(this); + content.setOrientation(LinearLayout.VERTICAL); + content.addView(message); + content.addView(buttons); + if (backgroundColor != Color.TRANSPARENT) { + content.setBackgroundColor(backgroundColor); + } + + // add content to dialog and return + + dialog.setContentView(content); + dialog.setOnKeyListener(new Dialog.OnKeyListener() { + @Override + public boolean onKey(DialogInterface d, int keyCode, KeyEvent event) { + Button button = mapping.get(keyCode); + if (button != null) { + if (event.getAction() == KeyEvent.ACTION_UP) { + button.performClick(); + } + return true; // also for ignored actions + } + return false; + } + }); + + return dialog; + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean clipboardHasText() { + return mClipboardHandler.clipboardHasText(); } - public static void pollInputDevices() { - if (SDLActivity.mSDLThread != null) { - mJoystickHandler.pollInputDevices(); - } + /** + * This method is called by SDL using JNI. + */ + public static String clipboardGetText() { + return mClipboardHandler.clipboardGetText(); } - + + /** + * This method is called by SDL using JNI. + */ + public static void clipboardSetText(String string) { + mClipboardHandler.clipboardSetText(string); + } + } /** - Simple nativeInit() runnable + Simple runnable to start the SDL application */ class SDLMain implements Runnable { @Override public void run() { // Runs SDL_main() - SDLActivity.nativeInit(); + String library = SDLActivity.mSingleton.getMainSharedObject(); + String function = SDLActivity.mSingleton.getMainFunction(); + String[] arguments = SDLActivity.mSingleton.getArguments(); - //Log.v("SDL", "SDL thread terminated"); + Log.v("SDL", "Running main function " + function + " from library " + library); + SDLActivity.nativeRunMain(library, function, arguments); + + Log.v("SDL", "Finished main function"); } } /** SDLSurface. This is what we draw on, so we need to know when it's created - in order to do anything useful. + in order to do anything useful. Because of this, that's where we set up the SDL thread */ -class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, +class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, View.OnKeyListener, View.OnTouchListener, SensorEventListener { // Sensors @@ -528,21 +1036,21 @@ // Keep track of the surface size to normalize touch events protected static float mWidth, mHeight; - // Startup + // Startup public SDLSurface(Context context) { super(context); - getHolder().addCallback(this); - + getHolder().addCallback(this); + setFocusable(true); setFocusableInTouchMode(true); requestFocus(); - setOnKeyListener(this); - setOnTouchListener(this); + setOnKeyListener(this); + setOnTouchListener(this); mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); - - if(Build.VERSION.SDK_INT >= 12) { + + if (Build.VERSION.SDK_INT >= 12) { setOnGenericMotionListener(new SDLGenericMotionListener_API12()); } @@ -550,7 +1058,20 @@ mWidth = 1.0f; mHeight = 1.0f; } - + + public void handlePause() { + enableSensor(Sensor.TYPE_ACCELEROMETER, false); + } + + public void handleResume() { + setFocusable(true); + setFocusableInTouchMode(true); + requestFocus(); + setOnKeyListener(this); + setOnTouchListener(this); + enableSensor(Sensor.TYPE_ACCELEROMETER, true); + } + public Surface getNativeSurface() { return getHolder().getSurface(); } @@ -566,8 +1087,11 @@ @Override public void surfaceDestroyed(SurfaceHolder holder) { Log.v("SDL", "surfaceDestroyed()"); - // Call this *before* setting mIsSurfaceReady to 'false' - SDLActivity.handlePause(); + + // Transition to pause, if needed + SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED; + SDLActivity.handleNativeState(); + SDLActivity.mIsSurfaceReady = false; SDLActivity.onNativeSurfaceDestroyed(); } @@ -625,66 +1149,78 @@ mWidth = width; mHeight = height; - SDLActivity.onNativeResize(width, height, sdlFormat); - Log.v("SDL", "Window size:" + width + "x"+height); + SDLActivity.onNativeResize(width, height, sdlFormat, mDisplay.getRefreshRate()); + Log.v("SDL", "Window size: " + width + "x" + height); + + + boolean skip = false; + int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation(); + + if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) + { + // Accept any + } + else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) + { + if (mWidth > mHeight) { + skip = true; + } + } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) { + if (mWidth < mHeight) { + skip = true; + } + } - // Set mIsSurfaceReady to 'true' *before* making a call to handleResume + // Special Patch for Square Resolution: Black Berry Passport + if (skip) { + double min = Math.min(mWidth, mHeight); + double max = Math.max(mWidth, mHeight); + + if (max / min < 1.20) { + Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution."); + skip = false; + } + } + + if (skip) { + Log.v("SDL", "Skip .. Surface is not ready."); + SDLActivity.mIsSurfaceReady = false; + return; + } + + /* Surface is ready */ SDLActivity.mIsSurfaceReady = true; + + /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */ SDLActivity.onNativeSurfaceChanged(); - - if (SDLActivity.mSDLThread == null) { - // This is the entry point to the C app. - // Start up the C app thread and enable sensor input for the first time - - SDLActivity.mSDLThread = new Thread(new SDLMain(), "SDLThread"); - enableSensor(Sensor.TYPE_ACCELEROMETER, true); - SDLActivity.mSDLThread.start(); - - // Set up a listener thread to catch when the native thread ends - new Thread(new Runnable(){ - @Override - public void run(){ - try { - SDLActivity.mSDLThread.join(); - } - catch(Exception e){} - finally{ - // Native thread has finished - if (! SDLActivity.mExitCalledFromJava) { - SDLActivity.handleNativeExit(); - } - } - } - }).start(); - } + SDLActivity.handleNativeState(); } - // unused - @Override - public void onDraw(Canvas canvas) {} - - // Key events @Override public boolean onKey(View v, int keyCode, KeyEvent event) { // Dispatch the different events depending on where they come from - // Some SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD - // So, we try to process them as DPAD or GAMEPAD events first, if that fails we try them as KEYBOARD - - if ( (event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) { + // Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD + // So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD + // + // Furthermore, it's possible a game controller has SOURCE_KEYBOARD and + // SOURCE_JOYSTICK, while its key events arrive from the keyboard source + // So, retrieve the device itself and check all of its sources + if (SDLControllerManager.isDeviceSDLJoystick(event.getDeviceId())) { + // Note that we process events with specific key codes here if (event.getAction() == KeyEvent.ACTION_DOWN) { - if (SDLActivity.onNativePadDown(event.getDeviceId(), keyCode) == 0) { + if (SDLControllerManager.onNativePadDown(event.getDeviceId(), keyCode) == 0) { return true; } } else if (event.getAction() == KeyEvent.ACTION_UP) { - if (SDLActivity.onNativePadUp(event.getDeviceId(), keyCode) == 0) { + if (SDLControllerManager.onNativePadUp(event.getDeviceId(), keyCode) == 0) { return true; } } } - - if( (event.getSource() & InputDevice.SOURCE_KEYBOARD) != 0) { + + if ((event.getSource() & InputDevice.SOURCE_KEYBOARD) != 0) { if (event.getAction() == KeyEvent.ACTION_DOWN) { //Log.v("SDL", "key down: " + keyCode); SDLActivity.onNativeKeyDown(keyCode); @@ -696,7 +1232,21 @@ return true; } } - + + if ((event.getSource() & InputDevice.SOURCE_MOUSE) != 0) { + // on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses + // they are ignored here because sending them as mouse input to SDL is messy + if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) { + switch (event.getAction()) { + case KeyEvent.ACTION_DOWN: + case KeyEvent.ACTION_UP: + // mark the event as handled or it will be handled by system + // handling KEYCODE_BACK by system will call onBackPressed() + return true; + } + } + } + return false; } @@ -708,58 +1258,98 @@ final int pointerCount = event.getPointerCount(); int action = event.getActionMasked(); int pointerFingerId; + int mouseButton; int i = -1; float x,y,p; - - switch(action) { - case MotionEvent.ACTION_MOVE: - for (i = 0; i < pointerCount; i++) { + + // !!! FIXME: dump this SDK check after 2.0.4 ships and require API14. + if (event.getSource() == InputDevice.SOURCE_MOUSE && SDLActivity.mSeparateMouseAndTouch) { + if (Build.VERSION.SDK_INT < 14) { + mouseButton = 1; // all mouse buttons are the left button + } else { + try { + mouseButton = (Integer) event.getClass().getMethod("getButtonState").invoke(event); + } catch(Exception e) { + mouseButton = 1; // oh well. + } + } + SDLActivity.onNativeMouse(mouseButton, action, event.getX(0), event.getY(0)); + } else { + switch(action) { + case MotionEvent.ACTION_MOVE: + for (i = 0; i < pointerCount; i++) { + pointerFingerId = event.getPointerId(i); + x = event.getX(i) / mWidth; + y = event.getY(i) / mHeight; + p = event.getPressure(i); + if (p > 1.0f) { + // may be larger than 1.0f on some devices + // see the documentation of getPressure(i) + p = 1.0f; + } + SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); + } + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_DOWN: + // Primary pointer up/down, the index is always zero + i = 0; + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_POINTER_DOWN: + // Non primary pointer up/down + if (i == -1) { + i = event.getActionIndex(); + } + pointerFingerId = event.getPointerId(i); x = event.getX(i) / mWidth; y = event.getY(i) / mHeight; p = event.getPressure(i); + if (p > 1.0f) { + // may be larger than 1.0f on some devices + // see the documentation of getPressure(i) + p = 1.0f; + } SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); - } - break; - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_DOWN: - // Primary pointer up/down, the index is always zero - i = 0; - case MotionEvent.ACTION_POINTER_UP: - case MotionEvent.ACTION_POINTER_DOWN: - // Non primary pointer up/down - if (i == -1) { - i = event.getActionIndex(); - } - - pointerFingerId = event.getPointerId(i); - x = event.getX(i) / mWidth; - y = event.getY(i) / mHeight; - p = event.getPressure(i); - SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); - break; - - default: - break; + break; + + case MotionEvent.ACTION_CANCEL: + for (i = 0; i < pointerCount; i++) { + pointerFingerId = event.getPointerId(i); + x = event.getX(i) / mWidth; + y = event.getY(i) / mHeight; + p = event.getPressure(i); + if (p > 1.0f) { + // may be larger than 1.0f on some devices + // see the documentation of getPressure(i) + p = 1.0f; + } + SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p); + } + break; + + default: + break; + } } return true; - } + } // Sensor events public void enableSensor(int sensortype, boolean enabled) { // TODO: This uses getDefaultSensor - what if we have >1 accels? if (enabled) { - mSensorManager.registerListener(this, - mSensorManager.getDefaultSensor(sensortype), + mSensorManager.registerListener(this, + mSensorManager.getDefaultSensor(sensortype), SensorManager.SENSOR_DELAY_GAME, null); } else { - mSensorManager.unregisterListener(this, + mSensorManager.unregisterListener(this, mSensorManager.getDefaultSensor(sensortype)); } } - + @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { // TODO @@ -789,9 +1379,9 @@ } SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH, y / SensorManager.GRAVITY_EARTH, - event.values[2] / SensorManager.GRAVITY_EARTH - 1); + event.values[2] / SensorManager.GRAVITY_EARTH); } - } + } } /* This is a fake invisible editor view that receives the input and defines the @@ -814,33 +1404,29 @@ @Override public boolean onKey(View v, int keyCode, KeyEvent event) { - - // This handles the hardware keyboard input - if (event.isPrintingKey()) { - if (event.getAction() == KeyEvent.ACTION_DOWN) { + /* + * This handles the hardware keyboard input + */ + if (event.getAction() == KeyEvent.ACTION_DOWN) { + if (SDLActivity.isTextInputEvent(event)) { ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1); } - return true; - } - - if (event.getAction() == KeyEvent.ACTION_DOWN) { SDLActivity.onNativeKeyDown(keyCode); return true; } else if (event.getAction() == KeyEvent.ACTION_UP) { SDLActivity.onNativeKeyUp(keyCode); return true; } - return false; } - + // @Override public boolean onKeyPreIme (int keyCode, KeyEvent event) { // As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event // FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639 // FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not - // FIXME: A more effective solution would be to change our Layout from AbsoluteLayout to Relative or Linear + // FIXME: A more effective solution would be to assume our Layout to be RelativeLayout or LinearLayout // FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android // FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :) if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { @@ -855,8 +1441,9 @@ public InputConnection onCreateInputConnection(EditorInfo outAttrs) { ic = new SDLInputConnection(this, true); + outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI - | 33554432 /* API 11: EditorInfo.IME_FLAG_NO_FULLSCREEN */; + | EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */; return ic; } @@ -871,20 +1458,17 @@ @Override public boolean sendKeyEvent(KeyEvent event) { - /* - * This handles the keycodes from soft keyboard (and IME-translated - * input from hardkeyboard) + * This handles the keycodes from soft keyboard (and IME-translated input from hardkeyboard) */ int keyCode = event.getKeyCode(); if (event.getAction() == KeyEvent.ACTION_DOWN) { - if (event.isPrintingKey()) { + if (SDLActivity.isTextInputEvent(event)) { commitText(String.valueOf((char) event.getUnicodeChar()), 1); } SDLActivity.onNativeKeyDown(keyCode); return true; } else if (event.getAction() == KeyEvent.ACTION_UP) { - SDLActivity.onNativeKeyUp(keyCode); return true; } @@ -912,162 +1496,100 @@ public native void nativeSetComposingText(String text, int newCursorPosition); @Override - public boolean deleteSurroundingText(int beforeLength, int afterLength) { + public boolean deleteSurroundingText(int beforeLength, int afterLength) { // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions/14560344/android-backspace-in-webview-baseinputconnection - if (beforeLength == 1 && afterLength == 0) { - // backspace - return super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)) - && super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL)); + // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265 + if (beforeLength > 0 && afterLength == 0) { + boolean ret = true; + // backspace(s) + while (beforeLength-- > 0) { + boolean ret_key = sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)) + && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL)); + ret = ret && ret_key; + } + return ret; } return super.deleteSurroundingText(beforeLength, afterLength); } } -/* A null joystick handler for API level < 12 devices (the accelerometer is handled separately) */ -class SDLJoystickHandler { - - public boolean handleMotionEvent(MotionEvent event) { - return false; - } - - public void pollInputDevices() { - } +interface SDLClipboardHandler { + + public boolean clipboardHasText(); + public String clipboardGetText(); + public void clipboardSetText(String string); + } -/* Actual joystick functionality available for API >= 12 devices */ -class SDLJoystickHandler_API12 extends SDLJoystickHandler { - - class SDLJoystick { - public int device_id; - public String name; - public ArrayList<InputDevice.MotionRange> axes; - public ArrayList<InputDevice.MotionRange> hats; + +class SDLClipboardHandler_API11 implements + SDLClipboardHandler, + android.content.ClipboardManager.OnPrimaryClipChangedListener { + + protected android.content.ClipboardManager mClipMgr; + + SDLClipboardHandler_API11() { + mClipMgr = (android.content.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE); + mClipMgr.addPrimaryClipChangedListener(this); } - class RangeComparator implements Comparator<InputDevice.MotionRange> - { - @Override - public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) { - return arg0.getAxis() - arg1.getAxis(); - } + + @Override + public boolean clipboardHasText() { + return mClipMgr.hasText(); } - - private ArrayList<SDLJoystick> mJoysticks; - - public SDLJoystickHandler_API12() { - - mJoysticks = new ArrayList<SDLJoystick>(); + + @Override + public String clipboardGetText() { + CharSequence text; + text = mClipMgr.getText(); + if (text != null) { + return text.toString(); + } + return null; } @Override - public void pollInputDevices() { - int[] deviceIds = InputDevice.getDeviceIds(); - // It helps processing the device ids in reverse order - // For example, in the case of the XBox 360 wireless dongle, - // so the first controller seen by SDL matches what the receiver - // considers to be the first controller - - for(int i=deviceIds.length-1; i>-1; i--) { - SDLJoystick joystick = getJoystick(deviceIds[i]); - if (joystick == null) { - joystick = new SDLJoystick(); - InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i]); - if( (joystickDevice.getSources() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { - joystick.device_id = deviceIds[i]; - joystick.name = joystickDevice.getName(); - joystick.axes = new ArrayList<InputDevice.MotionRange>(); - joystick.hats = new ArrayList<InputDevice.MotionRange>(); - - List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges(); - Collections.sort(ranges, new RangeComparator()); - for (InputDevice.MotionRange range : ranges ) { - if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ) { - if (range.getAxis() == MotionEvent.AXIS_HAT_X || - range.getAxis() == MotionEvent.AXIS_HAT_Y) { - joystick.hats.add(range); - } - else { - joystick.axes.add(range); - } - } - } - - mJoysticks.add(joystick); - SDLActivity.nativeAddJoystick(joystick.device_id, joystick.name, 0, -1, - joystick.axes.size(), joystick.hats.size()/2, 0); - } - } - } - - /* Check removed devices */ - ArrayList<Integer> removedDevices = new ArrayList<Integer>(); - for(int i=0; i < mJoysticks.size(); i++) { - int device_id = mJoysticks.get(i).device_id; - int j; - for (j=0; j < deviceIds.length; j++) { - if (device_id == deviceIds[j]) break; - } - if (j == deviceIds.length) { - removedDevices.add(device_id); - } - } - - for(int i=0; i < removedDevices.size(); i++) { - int device_id = removedDevices.get(i); - SDLActivity.nativeRemoveJoystick(device_id); - for (int j=0; j < mJoysticks.size(); j++) { - if (mJoysticks.get(j).device_id == device_id) { - mJoysticks.remove(j); - break; - } - } - } + public void clipboardSetText(String string) { + mClipMgr.removePrimaryClipChangedListener(this); + mClipMgr.setText(string); + mClipMgr.addPrimaryClipChangedListener(this); } - protected SDLJoystick getJoystick(int device_id) { - for(int i=0; i < mJoysticks.size(); i++) { - if (mJoysticks.get(i).device_id == device_id) { - return mJoysticks.get(i); - } - } - return null; - } - - @Override - public boolean handleMotionEvent(MotionEvent event) { - if ( (event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) { - int actionPointerIndex = event.getActionIndex(); - int action = event.getActionMasked(); - switch(action) { - case MotionEvent.ACTION_MOVE: - SDLJoystick joystick = getJoystick(event.getDeviceId()); - if ( joystick != null ) { - for (int i = 0; i < joystick.axes.size(); i++) { - InputDevice.MotionRange range = joystick.axes.get(i); - /* Normalize the value to -1...1 */ - float value = ( event.getAxisValue( range.getAxis(), actionPointerIndex) - range.getMin() ) / range.getRange() * 2.0f - 1.0f; - SDLActivity.onNativeJoy(joystick.device_id, i, value ); - } - for (int i = 0; i < joystick.hats.size(); i+=2) { - int hatX = Math.round(event.getAxisValue( joystick.hats.get(i).getAxis(), actionPointerIndex ) ); - int hatY = Math.round(event.getAxisValue( joystick.hats.get(i+1).getAxis(), actionPointerIndex ) ); - SDLActivity.onNativeHat(joystick.device_id, i/2, hatX, hatY ); - } - } - break; - default: - break; - } - } - return true; - } + @Override + public void onPrimaryClipChanged() { + SDLActivity.onNativeClipboardChanged(); + } + } -class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener { - // Generic Motion (mouse hover, joystick...) events go here - // We only have joysticks yet +class SDLClipboardHandler_Old implements + SDLClipboardHandler { + + protected android.text.ClipboardManager mClipMgrOld; + + SDLClipboardHandler_Old() { + mClipMgrOld = (android.text.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE); + } + @Override - public boolean onGenericMotion(View v, MotionEvent event) { - return SDLActivity.handleJoystickMotionEvent(event); + public boolean clipboardHasText() { + return mClipMgrOld.hasText(); + } + + @Override + public String clipboardGetText() { + CharSequence text; + text = mClipMgrOld.getText(); + if (text != null) { + return text.toString(); + } + return null; + } + + @Override + public void clipboardSetText(String string) { + mClipMgrOld.setText(string); } } +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/src/org/libsdl/app/SDLAudioManager.java Sun Apr 14 23:38:02 2019 -0700 @@ -0,0 +1,178 @@ +package org.libsdl.app; + +import android.media.*; +import android.util.Log; + +public class SDLAudioManager +{ + protected static final String TAG = "SDLAudio"; + + protected static AudioTrack mAudioTrack; + protected static AudioRecord mAudioRecord; + + public static void initialize() { + mAudioTrack = null; + mAudioRecord = null; + } + + // Audio + + /** + * This method is called by SDL using JNI. + */ + public static int audioOpen(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) { + int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO; + int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT; + int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1); + + Log.v(TAG, "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer"); + + // Let the user pick a larger buffer if they really want -- but ye + // gods they probably shouldn't, the minimums are horrifyingly high + // latency already + desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize); + + if (mAudioTrack == null) { + mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, + channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM); + + // Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid + // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java + // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState() + + if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) { + Log.e(TAG, "Failed during initialization of Audio Track"); + mAudioTrack = null; + return -1; + } + + mAudioTrack.play(); + } + + Log.v(TAG, "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer"); + + return 0; + } + + /** + * This method is called by SDL using JNI. + */ + public static void audioWriteShortBuffer(short[] buffer) { + if (mAudioTrack == null) { + Log.e(TAG, "Attempted to make audio call with uninitialized audio!"); + return; + } + + for (int i = 0; i < buffer.length; ) { + int result = mAudioTrack.write(buffer, i, buffer.length - i); + if (result > 0) { + i += result; + } else if (result == 0) { + try { + Thread.sleep(1); + } catch(InterruptedException e) { + // Nom nom + } + } else { + Log.w(TAG, "SDL audio: error return from write(short)"); + return; + } + } + } + + /** + * This method is called by SDL using JNI. + */ + public static void audioWriteByteBuffer(byte[] buffer) { + if (mAudioTrack == null) { + Log.e(TAG, "Attempted to make audio call with uninitialized audio!"); + return; + } + + for (int i = 0; i < buffer.length; ) { + int result = mAudioTrack.write(buffer, i, buffer.length - i); + if (result > 0) { + i += result; + } else if (result == 0) { + try { + Thread.sleep(1); + } catch(InterruptedException e) { + // Nom nom + } + } else { + Log.w(TAG, "SDL audio: error return from write(byte)"); + return; + } + } + } + + /** + * This method is called by SDL using JNI. + */ + public static int captureOpen(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) { + int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO; + int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT; + int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1); + + Log.v(TAG, "SDL capture: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer"); + + // Let the user pick a larger buffer if they really want -- but ye + // gods they probably shouldn't, the minimums are horrifyingly high + // latency already + desiredFrames = Math.max(desiredFrames, (AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize); + + if (mAudioRecord == null) { + mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate, + channelConfig, audioFormat, desiredFrames * frameSize); + + // see notes about AudioTrack state in audioOpen(), above. Probably also applies here. + if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) { + Log.e(TAG, "Failed during initialization of AudioRecord"); + mAudioRecord.release(); + mAudioRecord = null; + return -1; + } + + mAudioRecord.startRecording(); + } + + Log.v(TAG, "SDL capture: got " + ((mAudioRecord.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioRecord.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioRecord.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer"); + + return 0; + } + + /** This method is called by SDL using JNI. */ + public static int captureReadShortBuffer(short[] buffer, boolean blocking) { + // !!! FIXME: this is available in API Level 23. Until then, we always block. :( + //return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING); + return mAudioRecord.read(buffer, 0, buffer.length); + } + + /** This method is called by SDL using JNI. */ + public static int captureReadByteBuffer(byte[] buffer, boolean blocking) { + // !!! FIXME: this is available in API Level 23. Until then, we always block. :( + //return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING); + return mAudioRecord.read(buffer, 0, buffer.length); + } + + + /** This method is called by SDL using JNI. */ + public static void audioClose() { + if (mAudioTrack != null) { + mAudioTrack.stop(); + mAudioTrack.release(); + mAudioTrack = null; + } + } + + /** This method is called by SDL using JNI. */ + public static void captureClose() { + if (mAudioRecord != null) { + mAudioRecord.stop(); + mAudioRecord.release(); + mAudioRecord = null; + } + } + + public static native int nativeSetupJNI(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/android/src/org/libsdl/app/SDLControllerManager.java Sun Apr 14 23:38:02 2019 -0700 @@ -0,0 +1,433 @@ +package org.libsdl.app; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; + +import android.content.Context; +import android.os.*; +import android.view.*; +import android.util.Log; + + +public class SDLControllerManager +{ + + public static native int nativeSetupJNI(); + + public static native int nativeAddJoystick(int device_id, String name, String desc, + int is_accelerometer, int nbuttons, + int naxes, int nhats, int nballs); + public static native int nativeRemoveJoystick(int device_id); + public static native int nativeAddHaptic(int device_id, String name); + public static native int nativeRemoveHaptic(int device_id); + public static native int onNativePadDown(int device_id, int keycode); + public static native int onNativePadUp(int device_id, int keycode); + public static native void onNativeJoy(int device_id, int axis, + float value); + public static native void onNativeHat(int device_id, int hat_id, + int x, int y); + + protected static SDLJoystickHandler mJoystickHandler; + protected static SDLHapticHandler mHapticHandler; + + private static final String TAG = "SDLControllerManager"; + + public static void initialize() { + mJoystickHandler = null; + mHapticHandler = null; + + SDLControllerManager.setup(); + } + + public static void setup() { + if (Build.VERSION.SDK_INT >= 16) { + mJoystickHandler = new SDLJoystickHandler_API16(); + } else if (Build.VERSION.SDK_INT >= 12) { + mJoystickHandler = new SDLJoystickHandler_API12(); + } else { + mJoystickHandler = new SDLJoystickHandler(); + } + mHapticHandler = new SDLHapticHandler(); + } + + // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance + public static boolean handleJoystickMotionEvent(MotionEvent event) { + return mJoystickHandler.handleMotionEvent(event); + } + + /** + * This method is called by SDL using JNI. + */ + public static void pollInputDevices() { + mJoystickHandler.pollInputDevices(); + } + + /** + * This method is called by SDL using JNI. + */ + public static void pollHapticDevices() { + mHapticHandler.pollHapticDevices(); + } + + /** + * This method is called by SDL using JNI. + */ + public static void hapticRun(int device_id, int length) { + mHapticHandler.run(device_id, length); + } + + // Check if a given device is considered a possible SDL joystick + public static boolean isDeviceSDLJoystick(int deviceId) { + InputDevice device = InputDevice.getDevice(deviceId); + // We cannot use InputDevice.isVirtual before API 16, so let's accept + // only nonnegative device ids (VIRTUAL_KEYBOARD equals -1) + if ((device == null) || (deviceId < 0)) { + return false; + } + int sources = device.getSources(); + + if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_CLASS_JOYSTICK) { + Log.v(TAG, "Input device " + device.getName() + " is a joystick."); + } + if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) { + Log.v(TAG, "Input device " + device.getName() + " is a dpad."); + } + if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) { + Log.v(TAG, "Input device " + device.getName() + " is a gamepad."); + } + + return (((sources & InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_CLASS_JOYSTICK) || + ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) || + ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) + ); + } + +} + +/* A null joystick handler for API level < 12 devices (the accelerometer is handled separately) */ +class SDLJoystickHandler { + + /** + * Handles given MotionEvent. + * @param event the event to be handled. + * @return if given event was processed. + */ + public boolean handleMotionEvent(MotionEvent event) { + return false; + } + + /** + * Handles adding and removing of input devices. + */ + public void pollInputDevices() { + } +} + +/* Actual joystick functionality available for API >= 12 devices */ +class SDLJoystickHandler_API12 extends SDLJoystickHandler { + + static class SDLJoystick { + public int device_id; + public String name; + public String desc; + public ArrayList<InputDevice.MotionRange> axes; + public ArrayList<InputDevice.MotionRange> hats; + } + static class RangeComparator implements Comparator<InputDevice.MotionRange> { + @Override + public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) { + return arg0.getAxis() - arg1.getAxis(); + } + } + + private ArrayList<SDLJoystick> mJoysticks; + + public SDLJoystickHandler_API12() { + + mJoysticks = new ArrayList<SDLJoystick>(); + } + + @Override + public void pollInputDevices() { + int[] deviceIds = InputDevice.getDeviceIds(); + // It helps processing the device ids in reverse order + // For example, in the case of the XBox 360 wireless dongle, + // so the first controller seen by SDL matches what the receiver + // considers to be the first controller + + for(int i=deviceIds.length-1; i>-1; i--) { + SDLJoystick joystick = getJoystick(deviceIds[i]); + if (joystick == null) { + joystick = new SDLJoystick(); + InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i]); + if (SDLControllerManager.isDeviceSDLJoystick(deviceIds[i])) { + joystick.device_id = deviceIds[i]; + joystick.name = joystickDevice.getName(); + joystick.desc = getJoystickDescriptor(joystickDevice); + joystick.axes = new ArrayList<InputDevice.MotionRange>(); + joystick.hats = new ArrayList<InputDevice.MotionRange>(); + + List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges(); + Collections.sort(ranges, new RangeComparator()); + for (InputDevice.MotionRange range : ranges ) { + if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { + if (range.getAxis() == MotionEvent.AXIS_HAT_X || + range.getAxis() == MotionEvent.AXIS_HAT_Y) { + joystick.hats.add(range); + } + else { + joystick.axes.add(range); + } + } + } + + mJoysticks.add(joystick); + SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc, 0, -1, + joystick.axes.size(), joystick.hats.size()/2, 0); + } + } + } + + /* Check removed devices */ + ArrayList<Integer> removedDevices = new ArrayList<Integer>(); + for(int i=0; i < mJoysticks.size(); i++) { + int device_id = mJoysticks.get(i).device_id; + int j; + for (j=0; j < deviceIds.length; j++) { + if (device_id == deviceIds[j]) break; + } + if (j == deviceIds.length) { + removedDevices.add(Integer.valueOf(device_id)); + } + } + + for(int i=0; i < removedDevices.size(); i++) { + int device_id = removedDevices.get(i).intValue(); + SDLControllerManager.nativeRemoveJoystick(device_id); + for (int j=0; j < mJoysticks.size(); j++) { + if (mJoysticks.get(j).device_id == device_id) { + mJoysticks.remove(j); + break; + } + } + } + } + + protected SDLJoystick getJoystick(int device_id) { + for(int i=0; i < mJoysticks.size(); i++) { + if (mJoysticks.get(i).device_id == device_id) { + return mJoysticks.get(i); + } + } + return null; + } + + @Override + public boolean handleMotionEvent(MotionEvent event) { + if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) { + int actionPointerIndex = event.getActionIndex(); + int action = event.getActionMasked(); + switch(action) { + case MotionEvent.ACTION_MOVE: + SDLJoystick joystick = getJoystick(event.getDeviceId()); + if ( joystick != null ) { + for (int i = 0; i < joystick.axes.size(); i++) { + InputDevice.MotionRange range = joystick.axes.get(i); + /* Normalize the value to -1...1 */ + float value = ( event.getAxisValue( range.getAxis(), actionPointerIndex) - range.getMin() ) / range.getRange() * 2.0f - 1.0f; + SDLControllerManager.onNativeJoy(joystick.device_id, i, value ); + } + for (int i = 0; i < joystick.hats.size(); i+=2) { + int hatX = Math.round(event.getAxisValue( joystick.hats.get(i).getAxis(), actionPointerIndex ) ); + int hatY = Math.round(event.getAxisValue( joystick.hats.get(i+1).getAxis(), actionPointerIndex ) ); + SDLControllerManager.onNativeHat(joystick.device_id, i/2, hatX, hatY ); + } + } + break; + default: + break; + } + } + return true; + } + + public String getJoystickDescriptor(InputDevice joystickDevice) { + return joystickDevice.getName(); + } +} + + +class SDLJoystickHandler_API16 extends SDLJoystickHandler_API12 { + + @Override + public String getJoystickDescriptor(InputDevice joystickDevice) { + String desc = joystickDevice.getDescriptor(); + + if (desc != null && !Objects.equals(desc, "")) { + return desc; + } + + return super.getJoystickDescriptor(joystickDevice); + } +} + +class SDLHapticHandler { + + class SDLHaptic { + public int device_id; + public String name; + public Vibrator vib; + } + + private ArrayList<SDLHaptic> mHaptics; + + public SDLHapticHandler() { + mHaptics = new ArrayList<SDLHaptic>(); + } + + public void run(int device_id, int length) { + SDLHaptic haptic = getHaptic(device_id); + if (haptic != null) { + haptic.vib.vibrate (length); + } + } + + public void pollHapticDevices() { + + final int deviceId_VIBRATOR_SERVICE = 999999; + boolean hasVibratorService = false; + + int[] deviceIds = InputDevice.getDeviceIds(); + // It helps processing the device ids in reverse order + // For example, in the case of the XBox 360 wireless dongle, + // so the first controller seen by SDL matches what the receiver + // considers to be the first controller + + if (Build.VERSION.SDK_INT >= 16) + { + for (int i = deviceIds.length - 1; i > -1; i--) { + SDLHaptic haptic = getHaptic(deviceIds[i]); + if (haptic == null) { + InputDevice device = InputDevice.getDevice(deviceIds[i]); + Vibrator vib = device.getVibrator(); + if (vib.hasVibrator()) { + haptic = new SDLHaptic(); + haptic.device_id = deviceIds[i]; + haptic.name = device.getName(); + haptic.vib = vib; + mHaptics.add(haptic); + SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name); + } + } + } + } + + /* Check VIBRATOR_SERVICE */ + Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE); + if (vib != null) { + if (Build.VERSION.SDK_INT >= 11) { + hasVibratorService = vib.hasVibrator(); + } else { + hasVibratorService = true; + } + + if (hasVibratorService) { + SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE); + if (haptic == null) { + haptic = new SDLHaptic(); + haptic.device_id = deviceId_VIBRATOR_SERVICE; + haptic.name = "VIBRATOR_SERVICE"; + haptic.vib = vib; + mHaptics.add(haptic); + SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name); + } + } + } + + /* Check removed devices */ + ArrayList<Integer> removedDevices = new ArrayList<Integer>(); + for(int i=0; i < mHaptics.size(); i++) { + int device_id = mHaptics.get(i).device_id; + int j; + for (j=0; j < deviceIds.length; j++) { + if (device_id == deviceIds[j]) break; + } + + if (device_id == deviceId_VIBRATOR_SERVICE && hasVibratorService) { + // don't remove the vibrator if it is still present + } else if (j == deviceIds.length) { + removedDevices.add(device_id); + } + } + + for(int i=0; i < removedDevices.size(); i++) { + int device_id = removedDevices.get(i); + SDLControllerManager.nativeRemoveHaptic(device_id); + for (int j=0; j < mHaptics.size(); j++) { + if (mHaptics.get(j).device_id == device_id) { + mHaptics.remove(j); + break; + } + } + } + } + + protected SDLHaptic getHaptic(int device_id) { + for(int i=0; i < mHaptics.size(); i++) { + if (mHaptics.get(i).device_id == device_id) { + return mHaptics.get(i); + } + } + return null; + } +} + +class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener { + // Generic Motion (mouse hover, joystick...) events go here + @Override + public boolean onGenericMotion(View v, MotionEvent event) { + float x, y; + int action; + + switch ( event.getSource() ) { + case InputDevice.SOURCE_JOYSTICK: + case InputDevice.SOURCE_GAMEPAD: + case InputDevice.SOURCE_DPAD: + return SDLControllerManager.handleJoystickMotionEvent(event); + + case InputDevice.SOURCE_MOUSE: + if (!SDLActivity.mSeparateMouseAndTouch) { + break; + } + action = event.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_SCROLL: + x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0); + y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0); + SDLActivity.onNativeMouse(0, action, x, y); + return true; + + case MotionEvent.ACTION_HOVER_MOVE: + x = event.getX(0); + y = event.getY(0); + + SDLActivity.onNativeMouse(0, action, x, y); + return true; + + default: + break; + } + break; + + default: + break; + } + + // Event was not managed + return false; + } +} +
--- a/bindings.c Thu Mar 14 23:40:50 2019 -0700 +++ b/bindings.c Sun Apr 14 23:38:02 2019 -0700 @@ -816,7 +816,7 @@ if (hostbutton == RENDER_INVALID_NAME) { warning("%s is not a valid gamepad input name\n", key); } else if (hostbutton == RENDER_NOT_MAPPED && hostpadnum != map_warning_pad) { - warning("No SDL 2 mapping exists for input %s on gamepad %d\n", key, hostpadnum); + debug_message("No SDL 2 mapping exists for input %s on gamepad %d\n", key, hostpadnum); map_warning_pad = hostpadnum; } return; @@ -825,7 +825,7 @@ bind_dpad(hostpadnum, render_dpad_part(hostbutton), render_direction_part(hostbutton), bindtype, subtype_a, subtype_b); return; } else if (hostbutton & RENDER_AXIS_BIT) { - bind_axis(hostpadnum, render_axis_part(hostbutton), 1, bindtype, subtype_a, subtype_b); + bind_axis(hostpadnum, render_axis_part(hostbutton), hostbutton & RENDER_AXIS_POS, bindtype, subtype_a, subtype_b); return; } } @@ -863,7 +863,7 @@ if (axis == RENDER_INVALID_NAME) { warning("%s is not a valid gamepad input name\n", key); } else if (axis == RENDER_NOT_MAPPED && hostpadnum != map_warning_pad) { - warning("No SDL 2 mapping exists for input %s on gamepad %d\n", key, hostpadnum); + debug_message("No SDL 2 mapping exists for input %s on gamepad %d\n", key, hostpadnum); map_warning_pad = hostpadnum; } goto done;
--- a/blastem.c Thu Mar 14 23:40:50 2019 -0700 +++ b/blastem.c Sun Apr 14 23:38:02 2019 -0700 @@ -34,7 +34,7 @@ #include "nuklear_ui/blastem_nuklear.h" #endif -#define BLASTEM_VERSION "0.6.2-pre" +#define BLASTEM_VERSION "0.6.3-pre" #ifdef __ANDROID__ #define FULLSCREEN_DEFAULT 1 @@ -154,7 +154,7 @@ if (*dst) { if (is_smd_format(z->entries[i].name, *dst)) { size_t offset; - for (offset = 0; offset + SMD_BLOCK_SIZE + SMD_HEADER_SIZE < out_size; offset += SMD_BLOCK_SIZE) + for (offset = 0; offset + SMD_BLOCK_SIZE + SMD_HEADER_SIZE <= out_size; offset += SMD_BLOCK_SIZE) { uint8_t tmp[SMD_BLOCK_SIZE]; memcpy(tmp, *dst + offset + SMD_HEADER_SIZE, SMD_BLOCK_SIZE);
--- a/build_release Thu Mar 14 23:40:50 2019 -0700 +++ b/build_release Sun Apr 14 23:38:02 2019 -0700 @@ -25,10 +25,16 @@ cd .. fi echo "Path is: $PATH" -make PORTABLE=1 clean all +if [ $OS = "Win64" ]; then + make PORTABLE=1 OS=Windows CPU=x86_64 clean all + SDLDLLPATH=sdl/x86_64-w64-mingw32/bin +else + make PORTABLE=1 clean all + SDLDLLPATH=sdl/i686-w64-mingw32/bin +fi make menu.bin -if [ $OS = "Windows" ]; then - binaries="dis.exe zdis.exe stateview.exe vgmplay.exe blastem.exe SDL2.dll" +if [ $OS = "Windows" -o $OS = "Win64" ]; then + binaries="dis.exe zdis.exe stateview.exe vgmplay.exe blastem.exe $SDLDLLPATH/SDL2.dll" verstr=`sed -E -n 's/^[^B]+BLASTEM_VERSION "([^"]+)"/blastem \1/p' blastem.c` txt=".txt" else @@ -45,6 +51,8 @@ ver=`echo $verstr | awk '/blastem/ { gsub(/\r/, "", $2); print $2 }'` if [ $OS = "Windows" ]; then suffix='-win32' +elif [ $OS = "Win64" ]; then + suffix='-win64' elif [ $OS = "Darwin" ]; then suffix='-osx' else @@ -65,7 +73,7 @@ fi cp glew/LICENSE.txt "$dir"/GLEW-LICENSE$txt -if [ $OS = "Windows" ]; then +if [ $OS = "Windows" -o $OS = "Win64" ]; then rm -f "${dir}.zip" zip -r "${dir}.zip" "$dir" echo "${dir}.zip"
--- a/build_upload_nightly Thu Mar 14 23:40:50 2019 -0700 +++ b/build_upload_nightly Sun Apr 14 23:38:02 2019 -0700 @@ -14,6 +14,20 @@ if [ $result -ne 0 ]; then echo Build falied with return code $result stopping $CONTAINER_NAME lxc-stop -n "$CONTAINER_NAME" + curdate=`date -Iseconds` + curl -d'@-' -H 'Content-Type: application/json' "$WEBHOOKURL" <<WEBHOOKEOF +{ + "embeds": [ + { + "title": "Build $name failed!", + "type": "rich", + "description": "Build falied with return code $result stopping $CONTAINER_NAME", + "timestamp": "$curdate", + "color": 16711680 + } + ] +} +WEBHOOKEOF exit $result fi echo "Build succeeded, stopping $CONTAINER_NAME" @@ -23,3 +37,25 @@ echo "Uploaing $artifact to $REMOTE_HOST" scp -i "$REMOTE_IDENT" "$HOME/.local/share/lxc/$CONTAINER_NAME/rootfs/home/$BUILD_USER/blastem/$artifact" $REMOTE_USER@$REMOTE_HOST:/home/$REMOTE_USER/nightlies echo "Done" +curdate=`date -Iseconds` +version=`echo "$artifact" | sed -E 's/[^-]+-([0-9]+\.[0-9]+\.[0-9]+[^.]*)\..*$/\1/'` +curl -d'@-' -H 'Content-Type: application/json' "$WEBHOOKURL" <<WEBHOOKEOF +{ + "embeds": [ + { + "title": "$artifact", + "type": "rich", + "url": "https://www.retrodev.com/blastem/nightlies/$artifact", + "description": "New build of $name succeeded!", + "timestamp": "$curdate", + "color": 65280, + "fields": [ + { + "name": "Version", + "value": "$version" + } + ] + } + ] +} +WEBHOOKEOF \ No newline at end of file
--- a/build_upload_win_nightly Thu Mar 14 23:40:50 2019 -0700 +++ b/build_upload_win_nightly Sun Apr 14 23:38:02 2019 -0700 @@ -1,20 +1,56 @@ #!/bin/sh name=$1 - -cd $HOME/blastem_win +. "$HOME/$name.params" +cd $HOME/$DIR hg revert -a hg pull hg up rev=`hg summary | sed -E -n 's/^parent: [^:]+:([^ ]+) .*$/\1/p'` sed -i -E "s/(define BLASTEM_VERSION \"[^-]+)-pre\"/\1-pre-$rev\"/" blastem.c -export OS=Windows +export OS ./build_release > /tmp/build_${name}_out.log result=$? if [ $result -ne 0 ]; then echo Build falied with return code $result + curdate=`date -Iseconds` + curl -d'@-' -H 'Content-Type: application/json' "$WEBHOOKURL" <<WEBHOOKEOF +{ + "embeds": [ + { + "title": "Build $name failed!", + "type": "rich", + "description": "Build falied with return code $result", + "timestamp": "$curdate", + "color": 16711680 + } + ] +} +WEBHOOKEOF exit $result fi . $HOME/remote.params artifact=$(tail -n 1 /tmp/build_${name}_out.log) echo "Uploaing $artifact to $REMOTE_HOST" -scp -i "$REMOTE_IDENT" "$HOME/blastem_win/$artifact" $REMOTE_USER@$REMOTE_HOST:/home/$REMOTE_USER/nightlies +scp -i "$REMOTE_IDENT" "$HOME/$DIR/$artifact" $REMOTE_USER@$REMOTE_HOST:/home/$REMOTE_USER/nightlies +curdate=`date -Iseconds` +version=`echo "$artifact" | sed -E 's/[^-]+-([0-9]+\.[0-9]+\.[0-9]+[^.]*)\..*$/\1/'` +curl -d'@-' -H 'Content-Type: application/json' "$WEBHOOKURL" <<WEBHOOKEOF +{ + "embeds": [ + { + "title": "$artifact", + "type": "rich", + "url": "https://www.retrodev.com/blastem/nightlies/$artifact", + "description": "New build of $name succeeded!", + "timestamp": "$curdate", + "color": 65280, + "fields": [ + { + "name": "Version", + "value": "$version" + } + ] + } + ] +} +WEBHOOKEOF \ No newline at end of file
--- a/gamecontrollerdb.txt Thu Mar 14 23:40:50 2019 -0700 +++ b/gamecontrollerdb.txt Sun Apr 14 23:38:02 2019 -0700 @@ -132,7 +132,6 @@ 03000000380700006382000000000000,MLG GamePad PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, 03000000efbe0000edfe000000000000,Monect Virtual Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Windows, 03000000250900006688000000000000,MP-8866 Super Dual Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows, -030000001008000001e5000000000000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Windows, 03000000152000000182000000000000,NGDS,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Windows, 03000000bd12000015d0000000000000,Nintendo Retrolink USB Super SNES Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows, 030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, @@ -186,7 +185,6 @@ 030000000d0f00002200000000000000,REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, 030000000d0f00005b00000000000000,Real Arcade Pro.V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, 030000000d0f00005c00000000000000,Real Arcade Pro.V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, -03000000790000001100000000000000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows, 0300000000f000000300000000000000,RetroUSB.com RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Windows, 0300000000f00000f100000000000000,RetroUSB.com Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Windows, 030000006b140000010d000000000000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, @@ -287,7 +285,6 @@ 0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Mac OS X, 03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b4,b:b8,back:b32,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b16,leftstick:b40,lefttrigger:b24,leftx:a0,lefty:a4,rightshoulder:b20,rightstick:b44,righttrigger:b28,rightx:a8,righty:a12,start:b36,x:b0,y:b12,platform:Mac OS X, 03000000d8140000cecf000000000000,MC Cthulhu,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X, -030000001008000001e5000006010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Mac OS X, 030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X, 030000008f0e00000300000000000000,Piranha xtreme,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Mac OS X, 03000000d62000006dca000000010000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, @@ -303,7 +300,6 @@ 03000000321500000010000000010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, 0300000032150000030a000000000000,Razer Wildcat,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, 03000000790000001100000000000000,Retrolink Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a3,lefty:a4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X, -03000000790000001100000006010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X, 030000006b140000010d000000010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, 03000000c6240000fefa000000000000,Rock Candy Gamepad for PS3,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, 03000000811700007e05000000000000,Sega Saturn,a:b2,b:b4,dpdown:b16,dpleft:b15,dpright:b14,dpup:b17,leftshoulder:b8,lefttrigger:a5,leftx:a0,lefty:a2,rightshoulder:b9,righttrigger:a4,start:b13,x:b0,y:b6,platform:Mac OS X, @@ -435,7 +431,6 @@ 05000000d6200000ad0d000001000000,Moga Pro,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux, 03000000250900006688000000010000,MP-8866 Super Dual Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux, 030000000d0f00000900000010010000,Natec Genesis P44,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, -030000001008000001e5000010010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Linux, 050000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, 050000007e0500003003000001000000,Nintendo Wii Remote Pro Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux, 05000000010000000100000003000000,Nintendo Wiimote,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, @@ -487,7 +482,6 @@ 03000000321500000009000011010000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux, 050000003215000000090000163a0000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux, 0300000032150000030a000001010000,Razer Wildcat,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, -03000000790000001100000010010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Linux, 0300000000f000000300000000010000,RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux, 030000006b140000010d000011010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, 030000006f0e00001f01000000010000,Rock Candy,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
--- a/gdb_remote.c Thu Mar 14 23:40:50 2019 -0700 +++ b/gdb_remote.c Sun Apr 14 23:38:02 2019 -0700 @@ -596,5 +596,7 @@ fatal_error("accept returned an error while listening on GDB remote debugging socket"); } closesocket(listen_sock); +#else + disable_stdout_messages(); #endif }
--- a/gen_x86.c Thu Mar 14 23:40:50 2019 -0700 +++ b/gen_x86.c Sun Apr 14 23:38:02 2019 -0700 @@ -2111,7 +2111,12 @@ } #ifdef X86_64 uint32_t stack_args = 0; +#ifdef _WIN32 + //Microsoft is too good for the ABI that everyone else uses on x86-64 apparently + uint8_t abi_regs[] = {RCX, RDX, R8, R9}; +#else uint8_t abi_regs[] = {RDI, RSI, RDX, RCX, R8, R9}; +#endif int8_t reg_swap[R15+1]; uint32_t usage = 0; memset(reg_swap, -1, sizeof(reg_swap)); @@ -2153,6 +2158,11 @@ push_r(code, arg_arr[i]); } free(arg_arr); +#if defined(X86_64) && defined(_WIN32) + sub_ir(code, 32, RSP, SZ_PTR); + code->stack_off += 32; + adjust += 32; +#endif return stack_args * sizeof(void *) + adjust; } @@ -2218,7 +2228,8 @@ push_r(code, R13); push_r(code, R14); push_r(code, R15); -#else +#endif +#if !defined(X86_64) || defined(_WIN32) push_r(code, RDI); push_r(code, RSI); #endif @@ -2226,14 +2237,15 @@ void restore_callee_save_regs(code_info *code) { +#if !defined(X86_64) || defined(_WIN32) + pop_r(code, RSI); + pop_r(code, RDI); +#endif #ifdef X86_64 pop_r(code, R15); pop_r(code, R14); pop_r(code, R13); pop_r(code, R12); -#else - pop_r(code, RSI); - pop_r(code, RDI); #endif pop_r(code, RBP); pop_r(code, RBX);
--- a/gen_x86.h Thu Mar 14 23:40:50 2019 -0700 +++ b/gen_x86.h Sun Apr 14 23:38:02 2019 -0700 @@ -63,6 +63,13 @@ #ifdef X86_64 #define SZ_PTR SZ_Q #define MAX_INST_LEN 14 +#ifdef _WIN32 +#define FIRST_ARG_REG RCX +#define SECOND_ARG_REG RDX +#else +#define FIRST_ARG_REG RDI +#define SECOND_ARG_REG RSI +#endif #else #define SZ_PTR SZ_D #define MAX_INST_LEN 11
--- a/genesis.c Thu Mar 14 23:40:50 2019 -0700 +++ b/genesis.c Sun Apr 14 23:38:02 2019 -0700 @@ -1368,10 +1368,23 @@ io_keyboard_up(&gen->io, scancode); } +static void set_audio_config(genesis_context *gen) +{ + char *config_gain; + config_gain = tern_find_path(config, "audio\0psg_gain\0", TVAL_PTR).ptrval; + render_audio_source_gaindb(gen->psg->audio, config_gain ? atof(config_gain) : 0.0f); + config_gain = tern_find_path(config, "audio\0fm_gain\0", TVAL_PTR).ptrval; + render_audio_source_gaindb(gen->ym->audio, config_gain ? atof(config_gain) : 0.0f); + + char *config_dac = tern_find_path_default(config, "audio\0fm_dac\0", (tern_val){.ptrval="zero_offset"}, TVAL_PTR).ptrval; + ym_enable_zero_offset(gen->ym, !strcmp(config_dac, "zero_offset")); +} + static void config_updated(system_header *system) { genesis_context *gen = (genesis_context *)system; setup_io_devices(config, &system->info, &gen->io); + set_audio_config(gen); } genesis_context *alloc_init_genesis(rom_info *rom, void *main_rom, void *lock_on, uint32_t system_opts, uint8_t force_region) @@ -1425,6 +1438,8 @@ gen->psg = malloc(sizeof(psg_context)); psg_init(gen->psg, gen->master_clock, MCLKS_PER_PSG); + + set_audio_config(gen); z80_map[0].buffer = gen->zram = calloc(1, Z80_RAM_BYTES); #ifndef NO_Z80
--- a/io.c Thu Mar 14 23:40:50 2019 -0700 +++ b/io.c Sun Apr 14 23:38:02 2019 -0700 @@ -335,7 +335,7 @@ warning("IO port %s is configured to use the sega parallel board, but no paralell_pipe is set!\n", io_name(i)); ports[i].device_type = IO_NONE; } else { - printf("IO port: %s connected to device '%s' with pipe name: %s\n", io_name(i), device_type_names[ports[i].device_type], pipe_name); + debug_message("IO port: %s connected to device '%s' with pipe name: %s\n", io_name(i), device_type_names[ports[i].device_type], pipe_name); if (!strcmp("stdin", pipe_name)) { ports[i].device.stream.data_fd = STDIN_FILENO; @@ -361,7 +361,7 @@ warning("IO port %s is configured to use generic IO, but no socket is set!\n", io_name(i)); ports[i].device_type = IO_NONE; } else { - printf("IO port: %s connected to device '%s' with socket name: %s\n", io_name(i), device_type_names[ports[i].device_type], sock_name); + debug_message("IO port: %s connected to device '%s' with socket name: %s\n", io_name(i), device_type_names[ports[i].device_type], sock_name); ports[i].device.stream.data_fd = -1; ports[i].device.stream.listen_fd = socket(AF_UNIX, SOCK_STREAM, 0); size_t pathlen = strlen(sock_name); @@ -391,9 +391,9 @@ } else #endif if (ports[i].device_type == IO_GAMEPAD3 || ports[i].device_type == IO_GAMEPAD6 || ports[i].device_type == IO_GAMEPAD2) { - printf("IO port %s connected to gamepad #%d with type '%s'\n", io_name(i), ports[i].device.pad.gamepad_num + 1, device_type_names[ports[i].device_type]); + debug_message("IO port %s connected to gamepad #%d with type '%s'\n", io_name(i), ports[i].device.pad.gamepad_num, device_type_names[ports[i].device_type]); } else { - printf("IO port %s connected to device '%s'\n", io_name(i), device_type_names[ports[i].device_type]); + debug_message("IO port %s connected to device '%s'\n", io_name(i), device_type_names[ports[i].device_type]); } } } @@ -471,7 +471,7 @@ { if (port->device.stream.data_fd == -1) { - puts("Waiting for socket connection..."); + debug_message("Waiting for socket connection..."); port->device.stream.data_fd = accept(port->device.stream.listen_fd, NULL, NULL); fcntl(port->device.stream.data_fd, F_SETFL, O_NONBLOCK | O_RDWR); }
--- a/libblastem.c Thu Mar 14 23:40:50 2019 -0700 +++ b/libblastem.c Sun Apr 14 23:38:02 2019 -0700 @@ -69,7 +69,7 @@ RETRO_API void retro_get_system_info(struct retro_system_info *info) { info->library_name = "BlastEm"; - info->library_version = "0.6.2-pre"; //TODO: share this with blastem.c + info->library_version = "0.6.3-pre"; //TODO: share this with blastem.c info->valid_extensions = "md|gen|sms|bin|rom"; info->need_fullpath = 0; info->block_extract = 0; @@ -284,6 +284,11 @@ video_standard = std; } +int render_fullscreen(void) +{ + return 1; +} + void process_events() { static int16_t prev_state[2][RETRO_DEVICE_ID_JOYPAD_L2]; @@ -341,6 +346,11 @@ { } +void render_audio_source_gaindb(audio_source *src, float gain) +{ + //TODO: Implement this once I hook up a core option for individual FM/PSG gain +} + static void check_put_sample(void) { for (int i = 0; i < num_audio_sources; i++) @@ -392,3 +402,15 @@ void bindings_set_mouse_mode(uint8_t mode) { } + +extern const char rom_db_data[]; +char *read_bundled_file(char *name, uint32_t *sizeret) +{ + if (!strcmp(name, "rom.db")) { + *sizeret = strlen(rom_db_data); + char *ret = malloc(*sizeret+1); + memcpy(ret, rom_db_data, *sizeret + 1); + return ret; + } + return NULL; +}
--- a/m68k_core_x86.c Thu Mar 14 23:40:50 2019 -0700 +++ b/m68k_core_x86.c Sun Apr 14 23:38:02 2019 -0700 @@ -2658,8 +2658,11 @@ opts->start_context = (start_fun)code->cur; save_callee_save_regs(code); #ifdef X86_64 - if (opts->gen.scratch2 != RDI) { - mov_rr(code, RDI, opts->gen.scratch2, SZ_PTR); + if (opts->gen.scratch2 != FIRST_ARG_REG) { + mov_rr(code, FIRST_ARG_REG, opts->gen.scratch2, SZ_PTR); + } + if (opts->gen.context_reg != SECOND_ARG_REG) { + mov_rr(code, SECOND_ARG_REG, opts->gen.context_reg, SZ_PTR); } #else mov_rdispr(code, RSP, 20, opts->gen.scratch2, SZ_D);
--- a/menu.s68 Thu Mar 14 23:40:50 2019 -0700 +++ b/menu.s68 Sun Apr 14 23:38:02 2019 -0700 @@ -1251,8 +1251,8 @@ dc.b "Prev", 0 about_text: - dc.b "BlastEm v0.6.1", 0 - dc.b "Copyright 2011-2017 Michael Pavone", 0 + dc.b "BlastEm v0.6.3-pre", 0 + dc.b "Copyright 2011-2019 Michael Pavone", 0 dc.b " ", 0 dc.b "BlastEm is a high performance, open", 0 dc.b "source (GPLv3) Genesis/Megadrive", 0
--- a/net.c Thu Mar 14 23:40:50 2019 -0700 +++ b/net.c Sun Apr 14 23:38:02 2019 -0700 @@ -19,6 +19,10 @@ uint8_t get_host_address(iface_info *out) { +#ifdef __ANDROID__ + //TODO: write an implementation for Android + return 0; +#else struct ifaddrs *entries, *current, *localhost; if (getifaddrs(&entries)) { return 0; @@ -46,4 +50,5 @@ } freeifaddrs(entries); return ret; +#endif } \ No newline at end of file
--- a/nuklear_ui/blastem_nuklear.c Thu Mar 14 23:40:50 2019 -0700 +++ b/nuklear_ui/blastem_nuklear.c Sun Apr 14 23:38:02 2019 -0700 @@ -1,9 +1,13 @@ #define NK_IMPLEMENTATION #define NK_SDL_GLES2_IMPLEMENTATION +#define NK_RAWFB_IMPLEMENTATION +#define RAWFB_RGBX_8888 #include <stdlib.h> #include <limits.h> +#include <math.h> #include "blastem_nuklear.h" +#include "nuklear_rawfb.h" #include "font.h" #include "../render.h" #include "../render_sdl.h" @@ -18,6 +22,7 @@ #include "../bindings.h" static struct nk_context *context; +static struct rawfb_context *fb_context; typedef struct { @@ -160,8 +165,8 @@ void view_about(struct nk_context *context) { const char *lines[] = { - "BlastEm v0.6.1", - "Copyright 2012-2017 Michael Pavone", + "BlastEm v0.6.3-pre", + "Copyright 2012-2019 Michael Pavone", "", "BlastEm is a high performance open source", "(GPLv3) Genesis/Megadrive emulator", @@ -171,6 +176,8 @@ "Nemesis: Documentation and test ROMs", "Charles MacDonald: Documentation", "Eke-Eke: Documentation", + "Sauraen: YM2612/YM2203 Die Analysis", + "Alexey Khokholov: YM3438 Die Analysis", "Bart Trzynadlowski: Documentation", "KanedaFR: Hosting the best Sega forum", "Titan: Awesome demos and documentation", @@ -293,6 +300,7 @@ free_slot_info(slots); slots = NULL; } else if (current_view == view_play) { + clear_view_stack(); set_content_binding_state(1); } } else { @@ -314,6 +322,7 @@ static int32_t keycode; static const char *set_binding; +static uint8_t bind_click_release, click; char *set_label; void binding_group(struct nk_context *context, char *name, const char **binds, const char **bind_names, uint32_t num_binds, tern_node *binding_lookup) { @@ -332,6 +341,7 @@ if (nk_button_label(context, tern_find_ptr_default(binding_lookup, binds[i], "Not Set"))) { set_binding = binds[i]; set_label = strdup(label); + bind_click_release = 0; keycode = 0; } if (label_alloc) { @@ -483,7 +493,7 @@ nk_layout_row_static(context, 30, width/2-30, 1); nk_label(context, "Press new key for", NK_TEXT_CENTERED); nk_label(context, set_label, NK_TEXT_CENTERED); - if (nk_button_label(context, "Cancel")) { + if (nk_button_label(context, "Cancel") && bind_click_release) { free(set_label); set_binding = set_label = NULL; } else if (keycode) { @@ -517,6 +527,8 @@ } free(set_label); set_binding = set_label = NULL; + } else if (!click) { + bind_click_release = 1; } nk_end(context); } @@ -1054,12 +1066,19 @@ }); } - binding_box(context, bindings, "Right Shoulder", bind_box_left, font->height/2, bind_box_width, - selected_controller_info.variant == VARIANT_6B_BUMPERS ? 1 : 2, - (int[]){ - selected_controller_info.variant == VARIANT_6B_RIGHT ? SDL_CONTROLLER_BUTTON_LEFTSHOULDER : SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, - AXIS | SDL_CONTROLLER_AXIS_TRIGGERLEFT - }); + if (selected_controller_info.variant == VARIANT_NORMAL) { + binding_box(context, bindings, "Right Shoulder", bind_box_left, font->height/2, bind_box_width, 2, (int[]){ + SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, + AXIS | SDL_CONTROLLER_AXIS_TRIGGERRIGHT + }); + } else { + binding_box(context, bindings, "Right Shoulder", bind_box_left, font->height/2, bind_box_width, + selected_controller_info.variant == VARIANT_6B_BUMPERS ? 1 : 2, + (int[]){ + selected_controller_info.variant == VARIANT_6B_RIGHT ? SDL_CONTROLLER_BUTTON_LEFTSHOULDER : AXIS | SDL_CONTROLLER_AXIS_TRIGGERRIGHT, + AXIS | SDL_CONTROLLER_AXIS_TRIGGERLEFT + }); + } binding_box(context, bindings, "Misc Buttons", (render_width() - bind_box_width) / 2, font->height/2, bind_box_width, 3, (int[]){ SDL_CONTROLLER_BUTTON_BACK, @@ -1096,12 +1115,19 @@ dpad_top = img_top; } - binding_box(context, bindings, "Left Shoulder", bind_box_left, font->height/2, bind_box_width, - selected_controller_info.variant == VARIANT_6B_BUMPERS ? 1 : 2, - (int[]){ - selected_controller_info.variant == VARIANT_6B_RIGHT ? SDL_CONTROLLER_BUTTON_LEFTSTICK : SDL_CONTROLLER_BUTTON_LEFTSHOULDER, - SDL_CONTROLLER_BUTTON_RIGHTSTICK - }); + if (selected_controller_info.variant == VARIANT_NORMAL) { + binding_box(context, bindings, "Left Shoulder", bind_box_left, font->height/2, bind_box_width, 2, (int[]){ + SDL_CONTROLLER_BUTTON_LEFTSHOULDER, + AXIS | SDL_CONTROLLER_AXIS_TRIGGERLEFT + }); + } else { + binding_box(context, bindings, "Left Shoulder", bind_box_left, font->height/2, bind_box_width, + selected_controller_info.variant == VARIANT_6B_BUMPERS ? 1 : 2, + (int[]){ + selected_controller_info.variant == VARIANT_6B_RIGHT ? SDL_CONTROLLER_BUTTON_LEFTSTICK : AXIS | SDL_CONTROLLER_AXIS_TRIGGERLEFT, + SDL_CONTROLLER_BUTTON_RIGHTSTICK + }); + } binding_box(context, bindings, "D-pad", dpad_left, dpad_top, bind_box_width, 4, (int[]){ SDL_CONTROLLER_BUTTON_DPAD_UP, @@ -1114,7 +1140,7 @@ def_font->handle.height = orig_height; nk_layout_row_static(context, orig_height + 4, (render_width() - 2*orig_height) / 4, 1); - if (nk_button_label(context, "Back")) { + if (nk_button_label(context, controller_binding_changed ? "Save" : "Back")) { pop_view(); if (controller_binding_changed) { push_view(view_select_binding_dest); @@ -1128,7 +1154,7 @@ static int current_axis; static int button_pressed, last_button; static int hat_moved, hat_value, last_hat, last_hat_value; -static int axis_moved, axis_value, last_axis; +static int axis_moved, axis_value, last_axis, last_axis_value; static char *mapping_string; static size_t mapping_pos; @@ -1206,7 +1232,11 @@ last_hat = hat_moved; last_hat_value = hat_value; - } else if (axis_moved >= 0 && abs(axis_value) > 1000 && axis_moved != last_axis) { + } else if (axis_moved >= 0 && abs(axis_value) > 1000 && ( + axis_moved != last_axis || ( + axis_value/abs(axis_value) != last_axis_value/abs(axis_value) && current_button >= SDL_CONTROLLER_BUTTON_DPAD_UP + ) + )) { if (current_button <= SDL_CONTROLLER_BUTTON_B || axis_moved != button_a_axis) { start_mapping(); mapping_string[mapping_pos++] = 'a'; @@ -1214,18 +1244,27 @@ mapping_string[mapping_pos++] = '0' + axis_moved / 10; } mapping_string[mapping_pos++] = '0' + axis_moved % 10; + if (current_button >= SDL_CONTROLLER_BUTTON_DPAD_UP) { + mapping_string[mapping_pos++] = axis_value >= 0 ? '+' : '-'; + } last_axis = axis_moved; + last_axis_value = axis_value; } added_mapping = 1; } } - if (added_mapping) { + while (added_mapping) { quiet = QUIET_FRAMES; if (current_button < SDL_CONTROLLER_BUTTON_MAX) { current_button++; if (current_button == SDL_CONTROLLER_BUTTON_MAX) { current_axis = 0; + if (get_axis_label(&selected_controller_info, current_axis)) { + added_mapping = 0; + } + } else if (get_button_label(&selected_controller_info, current_button)) { + added_mapping = 0; } } else { current_axis++; @@ -1238,6 +1277,9 @@ pop_view(); push_view(view_controller_bindings); controller_binding_changed = 0; + added_mapping = 0; + } else if (get_axis_label(&selected_controller_info, current_axis)) { + added_mapping = 0; } } } @@ -1295,6 +1337,7 @@ last_hat = -1; axis_moved = -1; last_axis = -1; + last_axis_value = 0; SDL_Joystick *joy = render_get_joystick(selected_controller); const char *name = SDL_JoystickName(joy); size_t namesz = strlen(name); @@ -1438,6 +1481,21 @@ } } +void settings_float_property(struct nk_context *context, char *label, char *name, char *path, float def, float min, float max, float step) +{ + char *curstr = tern_find_path(config, path, TVAL_PTR).ptrval; + float curval = curstr ? atof(curstr) : def; + nk_label(context, label, NK_TEXT_LEFT); + float val = curval; + nk_property_float(context, name, min, &val, max, step, step); + if (val != curval) { + char buffer[64]; + sprintf(buffer, "%f", val); + config_dirty = 1; + config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(buffer)}, TVAL_PTR); + } +} + typedef struct { char *fragment; char *vertex; @@ -1653,13 +1711,24 @@ "128", "64" }; + const char *dac[] = { + "zero_offset", + "linear" + }; + const char *dac_desc[] = { + "Zero Offset", + "Linear" + }; const uint32_t num_rates = sizeof(rates)/sizeof(*rates); const uint32_t num_sizes = sizeof(sizes)/sizeof(*sizes); + const uint32_t num_dacs = sizeof(dac)/sizeof(*dac); static int32_t selected_rate = -1; static int32_t selected_size = -1; - if (selected_rate < 0 || selected_size < 0) { + static int32_t selected_dac = -1; + if (selected_rate < 0 || selected_size < 0 || selected_dac < 0) { selected_rate = find_match(rates, num_rates, "autio\0rate\0", "48000"); selected_size = find_match(sizes, num_sizes, "audio\0buffer\0", "512"); + selected_dac = find_match(dac, num_dacs, "audio\0fm_dac\0", "zero_offset"); } uint32_t width = render_width(); uint32_t height = render_height(); @@ -1672,6 +1741,10 @@ selected_rate = settings_dropdown(context, "Rate in Hz", rates, num_rates, selected_rate, "audio\0rate\0"); selected_size = settings_dropdown(context, "Buffer Samples", sizes, num_sizes, selected_size, "audio\0buffer\0"); settings_int_input(context, "Lowpass Cutoff Hz", "audio\0lowpass_cutoff\0", "3390"); + settings_float_property(context, "Gain (dB)", "Overall", "audio\0gain\0", 0, -30.0f, 30.0f, 0.5f); + settings_float_property(context, "", "FM", "audio\0fm_gain\0", 0, -30.0f, 30.0f, 0.5f); + settings_float_property(context, "", "PSG", "audio\0psg_gain\0", 0, -30.0f, 30.0f, 0.5f); + selected_dac = settings_dropdown_ex(context, "FM DAC", dac, dac_desc, num_dacs, selected_dac, "audio\0fm_dac\0"); if (nk_button_label(context, "Back")) { pop_view(); } @@ -1719,6 +1792,7 @@ selected_init = find_match(ram_inits, num_inits, "system\0ram_init\0", "zero"); } const char *io_opts_1[] = { + "none", "gamepad2.1", "gamepad3.1", "gamepad6.1", @@ -1727,6 +1801,7 @@ "xband keyboard" }; const char *io_opts_2[] = { + "none", "gamepad2.2", "gamepad3.2", "gamepad6.2", @@ -1829,7 +1904,15 @@ if (current_view != view_play) { nk_input_end(context); current_view(context); - nk_sdl_render(NK_ANTI_ALIASING_ON, 512 * 1024, 128 * 1024); + if (fb_context) { + fb_context->fb.pixels = render_get_framebuffer(FRAMEBUFFER_UI, &fb_context->fb.pitch); + nk_rawfb_render(fb_context, nk_rgb(0,0,0), 0); + render_framebuffer_updated(FRAMEBUFFER_UI, render_width()); + } else { +#ifndef DISABLE_OPENGL + nk_sdl_render(NK_ANTI_ALIASING_ON, 512 * 1024, 128 * 1024); +#endif + } nk_input_begin(context); } } @@ -1870,6 +1953,10 @@ axis_moved = event->jaxis.axis; axis_value = event->jaxis.value; } + } else if (event->type == SDL_MOUSEBUTTONDOWN && event->button.button == 0) { + click = 1; + } else if (event->type == SDL_MOUSEBUTTONUP && event->button.button == 0) { + click = 0; } nk_sdl_handle_event(event); } @@ -1883,6 +1970,12 @@ } } +static void fb_resize(void) +{ + nk_rawfb_resize_fb(fb_context, NULL, render_width(), render_height(), 0); +} + +#ifndef DISABLE_OPENGL static struct nk_image load_image_texture(uint32_t *buf, uint32_t width, uint32_t height) { GLuint tex; @@ -1899,11 +1992,29 @@ #endif return nk_image_id((int)tex); } +#endif + +static struct nk_image load_image_rawfb(uint32_t *buf, uint32_t width, uint32_t height) +{ + struct rawfb_image *fbimg = calloc(1, sizeof(struct rawfb_image)); + fbimg->pixels = buf; + fbimg->pitch = width * sizeof(uint32_t); + fbimg->w = width; + fbimg->h = height; + fbimg->format = NK_FONT_ATLAS_RGBA32; + return nk_image_ptr(fbimg); +} static void texture_init(void) { struct nk_font_atlas *atlas; - nk_sdl_font_stash_begin(&atlas); + if (fb_context) { + nk_rawfb_font_stash_begin(fb_context, &atlas); + } else { +#ifndef DISABLE_OPENGL + nk_sdl_font_stash_begin(&atlas); +#endif + } uint32_t font_size; uint8_t *font = default_font(&font_size); if (!font) { @@ -1911,27 +2022,60 @@ } def_font = nk_font_atlas_add_from_memory(atlas, font, font_size, render_height() / 16, NULL); free(font); - nk_sdl_font_stash_end(); + if (fb_context) { + nk_rawfb_font_stash_end(fb_context); + } else { +#ifndef DISABLE_OPENGL + nk_sdl_font_stash_end(); +#endif + } nk_style_set_font(context, &def_font->handle); for (uint32_t i = 0; i < num_ui_images; i++) { - ui_images[i]->ui = load_image_texture(ui_images[i]->image_data, ui_images[i]->width, ui_images[i]->height); +#ifndef DISABLE_OPENGL + if (fb_context) { +#endif + ui_images[i]->ui = load_image_rawfb(ui_images[i]->image_data, ui_images[i]->width, ui_images[i]->height); +#ifndef DISABLE_OPENGL + } else { + ui_images[i]->ui = load_image_texture(ui_images[i]->image_data, ui_images[i]->width, ui_images[i]->height); + } +#endif } } +static void style_init(void) +{ + context->style.checkbox.padding.x = render_height() / 120; + context->style.checkbox.padding.y = render_height() / 120; + context->style.checkbox.border = render_height() / 240; + context->style.checkbox.cursor_normal.type = NK_STYLE_ITEM_COLOR; + context->style.checkbox.cursor_normal.data.color = (struct nk_color){ + .r = 255, .g = 128, .b = 0, .a = 255 + }; + context->style.checkbox.cursor_hover = context->style.checkbox.cursor_normal; +} + static void context_created(void) { context = nk_sdl_init(render_get_window()); + nk_sdl_device_create(); + style_init(); texture_init(); } void show_pause_menu(void) { - set_content_binding_state(0); - context->style.window.background = nk_rgba(0, 0, 0, 128); - context->style.window.fixed_background = nk_style_item_color(nk_rgba(0, 0, 0, 128)); - current_view = view_pause; - current_system->request_exit(current_system); + if (current_view == view_play) { + set_content_binding_state(0); + context->style.window.background = nk_rgba(0, 0, 0, 128); + context->style.window.fixed_background = nk_style_item_color(nk_rgba(0, 0, 0, 128)); + current_view = view_pause; + current_system->request_exit(current_system); + } else if (current_system && !set_binding) { + clear_view_stack(); + show_play_view(); + } } void show_play_view(void) @@ -1948,10 +2092,10 @@ uint8_t is_nuklear_available(void) { - if (!render_has_gl()) { + /*if (!render_has_gl()) { //currently no fallback if GL2 unavailable return 0; - } + }*/ char *style = tern_find_path(config, "ui\0style\0", TVAL_PTR).ptrval; if (!style) { return 1; @@ -2001,6 +2145,17 @@ void blastem_nuklear_init(uint8_t file_loaded) { context = nk_sdl_init(render_get_window()); +#ifndef DISABLE_OPENGL + if (render_has_gl()) { + nk_sdl_device_create(); + } else { +#endif + fb_context = nk_rawfb_init(NULL, context, render_width(), render_height(), 0); + render_set_ui_fb_resize_handler(fb_resize); +#ifndef DISABLE_OPENGL + } +#endif + style_init(); controller_360 = load_ui_image("images/360.png"); controller_ps4 = load_ui_image("images/ps4.png");
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nuklear_ui/font_android.c Sun Apr 14 23:38:02 2019 -0700 @@ -0,0 +1,194 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <limits.h> +#include "../util.h" +#include "../paths.h" +#include "sfnt.h" + +typedef enum { + STATE_DEFAULT, + STATE_DECL, + STATE_COMMENT, + STATE_TAG, + STATE_PRE_ATTRIB, + STATE_ATTRIB, + STATE_PRE_VALUE, + STATE_VALUE +} parse_state; + +#define DEFAULT_WEIGHT 400 + +char *default_font_path(void) +{ + //Would probably be better to call into Java for this, but this should do for now + FILE *f = fopen("/system/etc/fonts.xml", "rb"); + if (!f) { + return NULL; + } + long size = file_size(f); + char *font_xml = malloc(size+1); + if (size != fread(font_xml, 1, size, f)) { + free(font_xml); + fclose(f); + return NULL; + } + fclose(f); + font_xml[size] = 0; + + char *last_tag = NULL, *last_attrib = NULL, *last_value = NULL; + uint8_t last_style_was_normal = 0; + char *capture_best = NULL; + char *best = NULL; + int best_weight_diff = INT_MAX; + int last_weight = INT_MAX; + parse_state state = STATE_DEFAULT; + for(char *cur = font_xml; *cur; ++cur) { + switch (state) + { + case STATE_DEFAULT: + if (*cur == '<' && cur[1]) { + cur++; + switch(*cur) + { + case '?': + state = STATE_DECL; + break; + case '!': + if (cur[1] == '-' && cur[2] == '-') { + state = STATE_COMMENT; + cur++; + } else { + debug_message("Invalid comment\n"); + cur = font_xml + size - 1; + } + break; + default: + if (capture_best) { + cur[-1] = 0; + best = strip_ws(capture_best); + capture_best = NULL; + best_weight_diff = abs(last_weight - DEFAULT_WEIGHT); + debug_message("Found candidate %s with weight %d\n", best, last_weight); + } + state = STATE_TAG; + last_tag = cur; + last_attrib = NULL; + last_value = NULL; + last_weight = INT_MAX; + break; + } + } + break; + case STATE_DECL: + if (*cur == '?' && cur[1] == '>') { + cur++; + state = STATE_DEFAULT; + } + break; + case STATE_COMMENT: + if (*cur == '-' && cur[1] == '-' && cur[2] == '>') { + cur += 2; + state = STATE_DEFAULT; + } + break; + case STATE_TAG: + if (*cur == ' ' || *cur == '\t' || *cur == '\n' || *cur == '\r') { + *cur = 0; + state = STATE_PRE_ATTRIB; + } else if (*cur == '>') { + *cur = 0; + state = STATE_DEFAULT; + } + break; + case STATE_PRE_ATTRIB: + if (!(*cur == ' ' || *cur == '\t' || *cur == '\n' || *cur == '\r')) { + if (*cur == '>') { + state = STATE_DEFAULT; + if (last_style_was_normal && abs(last_weight - DEFAULT_WEIGHT) < best_weight_diff) { + capture_best = cur + 1; + } else if (best && !strcmp("/family", last_tag)) { + debug_message("found family close tag, stopping search\n"); + cur = font_xml + size - 1; + } + } else { + last_attrib = cur; + state = STATE_ATTRIB; + } + } + break; + case STATE_ATTRIB: + if (*cur == '=') { + *cur = 0; + state = STATE_PRE_VALUE; + } else if (*cur == ' ' || *cur == '\t' || *cur == '\n' || *cur == '\r') { + *cur = 0; + } + break; + case STATE_PRE_VALUE: + if (*cur == '"') { + state = STATE_VALUE; + last_value = cur + 1; + } + break; + case STATE_VALUE: + if (*cur == '"') { + *cur = 0; + state = STATE_PRE_ATTRIB; + if (!strcmp("weight", last_attrib)) { + last_weight = atoi(last_value); + } else if (!strcmp("style", last_attrib)) { + last_style_was_normal = !strcmp("normal", last_value); + } + } + break; + } + } + if (best) { + best = path_append("/system/fonts", best); + } + free(font_xml); + return best; +} + +static uint8_t *try_load_font(char *path, uint32_t *size_out) +{ + debug_message("Trying to load font %s\n", path); + FILE *f = fopen(path, "rb"); + free(path); + if (!f) { + return NULL; + } + long size = file_size(f); + uint8_t *buffer = malloc(size); + if (size != fread(buffer, 1, size, f)) { + fclose(f); + return NULL; + } + fclose(f); + sfnt_container *sfnt = load_sfnt(buffer, size); + if (!sfnt) { + free(buffer); + return NULL; + } + return sfnt_flatten(sfnt->tables, size_out); +} + +uint8_t *default_font(uint32_t *size_out) +{ + char *path = default_font_path(); + if (!path) { + goto error; + } + uint8_t *ret = try_load_font(path, size_out); + if (ret) { + return ret; + } +error: + //try some likely suspects if we failed to parse fonts.xml or failed to find the indicated font + ret = try_load_font("/system/fonts/Roboto-Regular.ttf", size_out); + if (!ret) { + ret = try_load_font("/system/fonts/DroidSans.ttf", size_out); + } + return ret; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nuklear_ui/nuklear_rawfb.h Sun Apr 14 23:38:02 2019 -0700 @@ -0,0 +1,1007 @@ +/* + * MIT License + * + * Copyright (c) 2016-2017 Patrick Rudolph <siro@das-labor.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. +*/ +/* + * ============================================================== + * + * API + * + * =============================================================== + */ +#ifndef NK_RAWFB_H_ +#define NK_RAWFB_H_ + +struct rawfb_context; + +/* All functions are thread-safe */ +NK_API struct rawfb_context *nk_rawfb_init(void *fb, struct nk_context *context, const unsigned int w, const unsigned int h, const unsigned int pitch); +NK_API void nk_rawfb_render(const struct rawfb_context *rawfb, const struct nk_color clear, const unsigned char enable_clear); +NK_API void nk_rawfb_shutdown(struct rawfb_context *rawfb); +NK_API void nk_rawfb_resize_fb(struct rawfb_context *rawfb, void *fb, const unsigned int w, const unsigned int h, const unsigned int pitch); + +#endif +/* + * ============================================================== + * + * IMPLEMENTATION + * + * =============================================================== + */ +#ifdef NK_RAWFB_IMPLEMENTATION + +struct rawfb_image { + void *pixels; + int w, h, pitch; + enum nk_font_atlas_format format; +}; +struct rawfb_context { + struct nk_context *ctx; + struct nk_rect scissors; + struct rawfb_image fb; + struct rawfb_image font_tex; + struct nk_font_atlas atlas; +}; + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef MAX +#define MAX(a,b) ((a) < (b) ? (b) : (a)) +#endif + +static unsigned int +nk_color_from_byte(const nk_byte *c) +{ + unsigned int res = 0; +#if defined(RAWFB_RGBX_8888) + res |= (unsigned int)c[0] << 16; + res |= (unsigned int)c[1] << 8; + res |= (unsigned int)c[2] << 0; +#elif defined(RAWFB_XRGB_8888) + res = ((unsigned int *)c)[0]; +#else +#error Define one of RAWFB_RGBX_8888 , RAWFB_XRGB_8888 +#endif + return (res); +} + +static void +nk_rawfb_setpixel(const struct rawfb_context *rawfb, + const short x0, const short y0, const struct nk_color col) +{ + unsigned int c = nk_color_from_byte(&col.r); + unsigned char *pixels = rawfb->fb.pixels; + unsigned int *ptr; + + pixels += y0 * rawfb->fb.pitch; + ptr = (unsigned int *)pixels; + ptr += x0; + + if (y0 < rawfb->scissors.h && y0 >= rawfb->scissors.y && + x0 >= rawfb->scissors.x && x0 < rawfb->scissors.w) + *ptr = c; +} + +static void +nk_rawfb_line_horizontal(const struct rawfb_context *rawfb, + const short x0, const short y, const short x1, const struct nk_color col) +{ + /* This function is called the most. Try to optimize it a bit... + * It does not check for scissors or image borders. + * The caller has to make sure it does no exceed bounds. */ + unsigned int i, n; + unsigned int c[16]; + unsigned char *pixels = rawfb->fb.pixels; + unsigned int *ptr; + + pixels += y * rawfb->fb.pitch; + ptr = (unsigned int *)pixels; + ptr += x0; + + n = x1 - x0; + for (i = 0; i < sizeof(c) / sizeof(c[0]); i++) + c[i] = nk_color_from_byte(&col.r); + + while (n > 16) { + memcpy((void *)ptr, c, sizeof(c)); + n -= 16; ptr += 16; + } for (i = 0; i < n; i++) + ptr[i] = c[i]; +} + +static void +nk_rawfb_imagesetpixel(const struct rawfb_image *img, + const int x0, const int y0, const struct nk_color col) +{ + unsigned char *ptr; + NK_ASSERT(img); + if (y0 < img->h && y0 > 0 && x0 > 0 && x0 < img->w) { + ptr = img->pixels; + if (img->format == NK_FONT_ATLAS_ALPHA8) { + ptr += img->pitch * y0; + ptr[x0] = col.a; + } else { + ptr += img->pitch * y0; + ((struct nk_color *)ptr)[x0] = col; + } + } +} + +static struct nk_color +nk_image_getpixel(const struct rawfb_image *img, const int x0, const int y0) +{ + struct nk_color col = {0, 0, 0, 0}; + unsigned char *ptr; + NK_ASSERT(img); + if (y0 < img->h && y0 > 0 && x0 > 0 && x0 < img->w) { + ptr = img->pixels; + if (img->format == NK_FONT_ATLAS_ALPHA8) { + ptr += img->pitch * y0; + col.a = ptr[x0]; + col.b = col.g = col.r = 0xff; + } else { + ptr += img->pitch * y0; + col = ((struct nk_color *)ptr)[x0]; + } + } return col; +} + +static void +nk_image_blendpixel(const struct rawfb_image *img, + const int x0, const int y0, struct nk_color col) +{ + struct nk_color col2; + unsigned char inv_a; + if (col.a == 0) + return; + + inv_a = 0xff - col.a; + col2 = nk_image_getpixel(img, x0, y0); + col.r = (col.r * col.a + col2.r * inv_a) >> 8; + col.g = (col.g * col.a + col2.g * inv_a) >> 8; + col.b = (col.b * col.a + col2.b * inv_a) >> 8; + nk_rawfb_imagesetpixel(img, x0, y0, col); +} + +static void +nk_rawfb_scissor(struct rawfb_context *rawfb, + const float x, + const float y, + const float w, + const float h) +{ + rawfb->scissors.x = MIN(MAX(x, 0), rawfb->fb.w); + rawfb->scissors.y = MIN(MAX(y, 0), rawfb->fb.h); + rawfb->scissors.w = MIN(MAX(w + x, 0), rawfb->fb.w); + rawfb->scissors.h = MIN(MAX(h + y, 0), rawfb->fb.h); +} + +static void +nk_rawfb_stroke_line(const struct rawfb_context *rawfb, + short x0, short y0, short x1, short y1, + const unsigned int line_thickness, const struct nk_color col) +{ + short tmp; + int dy, dx, stepx, stepy; + + dy = y1 - y0; + dx = x1 - x0; + + /* fast path */ + if (dy == 0) { + if (dx == 0 || y0 >= rawfb->scissors.h || y0 < rawfb->scissors.y) + return; + + if (dx < 0) { + /* swap x0 and x1 */ + tmp = x1; + x1 = x0; + x0 = tmp; + } + x1 = MIN(rawfb->scissors.w - 1, x1); + x0 = MIN(rawfb->scissors.w - 1, x0); + x1 = MAX(rawfb->scissors.x, x1); + x0 = MAX(rawfb->scissors.x, x0); + nk_rawfb_line_horizontal(rawfb, x0, y0, x1, col); + return; + } + if (dy < 0) { + dy = -dy; + stepy = -1; + } else stepy = 1; + + if (dx < 0) { + dx = -dx; + stepx = -1; + } else stepx = 1; + + dy <<= 1; + dx <<= 1; + + nk_rawfb_setpixel(rawfb, x0, y0, col); + if (dx > dy) { + int fraction = dy - (dx >> 1); + while (x0 != x1) { + if (fraction >= 0) { + y0 += stepy; + fraction -= dx; + } + x0 += stepx; + fraction += dy; + nk_rawfb_setpixel(rawfb, x0, y0, col); + } + } else { + int fraction = dx - (dy >> 1); + while (y0 != y1) { + if (fraction >= 0) { + x0 += stepx; + fraction -= dy; + } + y0 += stepy; + fraction += dx; + nk_rawfb_setpixel(rawfb, x0, y0, col); + } + } +} + +static void +nk_rawfb_fill_polygon(const struct rawfb_context *rawfb, + const struct nk_vec2i *pnts, int count, const struct nk_color col) +{ + int i = 0; + #define MAX_POINTS 64 + int left = 10000, top = 10000, bottom = 0, right = 0; + int nodes, nodeX[MAX_POINTS], pixelX, pixelY, j, swap ; + + if (count == 0) return; + if (count > MAX_POINTS) + count = MAX_POINTS; + + /* Get polygon dimensions */ + for (i = 0; i < count; i++) { + if (left > pnts[i].x) + left = pnts[i].x; + if (right < pnts[i].x) + right = pnts[i].x; + if (top > pnts[i].y) + top = pnts[i].y; + if (bottom < pnts[i].y) + bottom = pnts[i].y; + } bottom++; right++; + + /* Polygon scanline algorithm released under public-domain by Darel Rex Finley, 2007 */ + /* Loop through the rows of the image. */ + for (pixelY = top; pixelY < bottom; pixelY ++) { + nodes = 0; /* Build a list of nodes. */ + j = count - 1; + for (i = 0; i < count; i++) { + if (((pnts[i].y < pixelY) && (pnts[j].y >= pixelY)) || + ((pnts[j].y < pixelY) && (pnts[i].y >= pixelY))) { + nodeX[nodes++]= (int)((float)pnts[i].x + + ((float)pixelY - (float)pnts[i].y) / ((float)pnts[j].y - (float)pnts[i].y) + * ((float)pnts[j].x - (float)pnts[i].x)); + } j = i; + } + + /* Sort the nodes, via a simple “Bubble” sort. */ + i = 0; + while (i < nodes - 1) { + if (nodeX[i] > nodeX[i+1]) { + swap = nodeX[i]; + nodeX[i] = nodeX[i+1]; + nodeX[i+1] = swap; + if (i) i--; + } else i++; + } + /* Fill the pixels between node pairs. */ + for (i = 0; i < nodes; i += 2) { + if (nodeX[i+0] >= right) break; + if (nodeX[i+1] > left) { + if (nodeX[i+0] < left) nodeX[i+0] = left ; + if (nodeX[i+1] > right) nodeX[i+1] = right; + for (pixelX = nodeX[i]; pixelX < nodeX[i + 1]; pixelX++) + nk_rawfb_setpixel(rawfb, pixelX, pixelY, col); + } + } + } + #undef MAX_POINTS +} + +static void +nk_rawfb_stroke_arc(const struct rawfb_context *rawfb, + short x0, short y0, short w, short h, const short s, + const short line_thickness, const struct nk_color col) +{ + /* Bresenham's ellipses - modified to draw one quarter */ + const int a2 = (w * w) / 4; + const int b2 = (h * h) / 4; + const int fa2 = 4 * a2, fb2 = 4 * b2; + int x, y, sigma; + + if (s != 0 && s != 90 && s != 180 && s != 270) return; + if (w < 1 || h < 1) return; + + /* Convert upper left to center */ + h = (h + 1) / 2; + w = (w + 1) / 2; + x0 += w; y0 += h; + + /* First half */ + for (x = 0, y = h, sigma = 2*b2+a2*(1-2*h); b2*x <= a2*y; x++) { + if (s == 180) + nk_rawfb_setpixel(rawfb, x0 + x, y0 + y, col); + else if (s == 270) + nk_rawfb_setpixel(rawfb, x0 - x, y0 + y, col); + else if (s == 0) + nk_rawfb_setpixel(rawfb, x0 + x, y0 - y, col); + else if (s == 90) + nk_rawfb_setpixel(rawfb, x0 - x, y0 - y, col); + if (sigma >= 0) { + sigma += fa2 * (1 - y); + y--; + } sigma += b2 * ((4 * x) + 6); + } + + /* Second half */ + for (x = w, y = 0, sigma = 2*a2+b2*(1-2*w); a2*y <= b2*x; y++) { + if (s == 180) + nk_rawfb_setpixel(rawfb, x0 + x, y0 + y, col); + else if (s == 270) + nk_rawfb_setpixel(rawfb, x0 - x, y0 + y, col); + else if (s == 0) + nk_rawfb_setpixel(rawfb, x0 + x, y0 - y, col); + else if (s == 90) + nk_rawfb_setpixel(rawfb, x0 - x, y0 - y, col); + if (sigma >= 0) { + sigma += fb2 * (1 - x); + x--; + } sigma += a2 * ((4 * y) + 6); + } +} + +static void +nk_rawfb_fill_arc(const struct rawfb_context *rawfb, short x0, short y0, + short w, short h, const short s, const struct nk_color col) +{ + /* Bresenham's ellipses - modified to fill one quarter */ + const int a2 = (w * w) / 4; + const int b2 = (h * h) / 4; + const int fa2 = 4 * a2, fb2 = 4 * b2; + int x, y, sigma; + struct nk_vec2i pnts[3]; + if (w < 1 || h < 1) return; + if (s != 0 && s != 90 && s != 180 && s != 270) + return; + + /* Convert upper left to center */ + h = (h + 1) / 2; + w = (w + 1) / 2; + x0 += w; + y0 += h; + + pnts[0].x = x0; + pnts[0].y = y0; + pnts[2].x = x0; + pnts[2].y = y0; + + /* First half */ + for (x = 0, y = h, sigma = 2*b2+a2*(1-2*h); b2*x <= a2*y; x++) { + if (s == 180) { + pnts[1].x = x0 + x; pnts[1].y = y0 + y; + } else if (s == 270) { + pnts[1].x = x0 - x; pnts[1].y = y0 + y; + } else if (s == 0) { + pnts[1].x = x0 + x; pnts[1].y = y0 - y; + } else if (s == 90) { + pnts[1].x = x0 - x; pnts[1].y = y0 - y; + } + nk_rawfb_fill_polygon(rawfb, pnts, 3, col); + pnts[2] = pnts[1]; + if (sigma >= 0) { + sigma += fa2 * (1 - y); + y--; + } sigma += b2 * ((4 * x) + 6); + } + + /* Second half */ + for (x = w, y = 0, sigma = 2*a2+b2*(1-2*w); a2*y <= b2*x; y++) { + if (s == 180) { + pnts[1].x = x0 + x; pnts[1].y = y0 + y; + } else if (s == 270) { + pnts[1].x = x0 - x; pnts[1].y = y0 + y; + } else if (s == 0) { + pnts[1].x = x0 + x; pnts[1].y = y0 - y; + } else if (s == 90) { + pnts[1].x = x0 - x; pnts[1].y = y0 - y; + } + nk_rawfb_fill_polygon(rawfb, pnts, 3, col); + pnts[2] = pnts[1]; + if (sigma >= 0) { + sigma += fb2 * (1 - x); + x--; + } sigma += a2 * ((4 * y) + 6); + } +} + +static void +nk_rawfb_stroke_rect(const struct rawfb_context *rawfb, + const short x, const short y, const short w, const short h, + const short r, const short line_thickness, const struct nk_color col) +{ + if (r == 0) { + nk_rawfb_stroke_line(rawfb, x, y, x + w, y, line_thickness, col); + nk_rawfb_stroke_line(rawfb, x, y + h, x + w, y + h, line_thickness, col); + nk_rawfb_stroke_line(rawfb, x, y, x, y + h, line_thickness, col); + nk_rawfb_stroke_line(rawfb, x + w, y, x + w, y + h, line_thickness, col); + } else { + const short xc = x + r; + const short yc = y + r; + const short wc = (short)(w - 2 * r); + const short hc = (short)(h - 2 * r); + + nk_rawfb_stroke_line(rawfb, xc, y, xc + wc, y, line_thickness, col); + nk_rawfb_stroke_line(rawfb, x + w, yc, x + w, yc + hc, line_thickness, col); + nk_rawfb_stroke_line(rawfb, xc, y + h, xc + wc, y + h, line_thickness, col); + nk_rawfb_stroke_line(rawfb, x, yc, x, yc + hc, line_thickness, col); + + nk_rawfb_stroke_arc(rawfb, xc + wc - r, y, + (unsigned)r*2, (unsigned)r*2, 0 , line_thickness, col); + nk_rawfb_stroke_arc(rawfb, x, y, + (unsigned)r*2, (unsigned)r*2, 90 , line_thickness, col); + nk_rawfb_stroke_arc(rawfb, x, yc + hc - r, + (unsigned)r*2, (unsigned)r*2, 270 , line_thickness, col); + nk_rawfb_stroke_arc(rawfb, xc + wc - r, yc + hc - r, + (unsigned)r*2, (unsigned)r*2, 180 , line_thickness, col); + } +} + +static void +nk_rawfb_fill_rect(const struct rawfb_context *rawfb, + const short x, const short y, const short w, const short h, + const short r, const struct nk_color col) +{ + int i; + if (r == 0) { + for (i = 0; i < h; i++) + nk_rawfb_stroke_line(rawfb, x, y + i, x + w, y + i, 1, col); + } else { + const short xc = x + r; + const short yc = y + r; + const short wc = (short)(w - 2 * r); + const short hc = (short)(h - 2 * r); + + struct nk_vec2i pnts[12]; + pnts[0].x = x; + pnts[0].y = yc; + pnts[1].x = xc; + pnts[1].y = yc; + pnts[2].x = xc; + pnts[2].y = y; + + pnts[3].x = xc + wc; + pnts[3].y = y; + pnts[4].x = xc + wc; + pnts[4].y = yc; + pnts[5].x = x + w; + pnts[5].y = yc; + + pnts[6].x = x + w; + pnts[6].y = yc + hc; + pnts[7].x = xc + wc; + pnts[7].y = yc + hc; + pnts[8].x = xc + wc; + pnts[8].y = y + h; + + pnts[9].x = xc; + pnts[9].y = y + h; + pnts[10].x = xc; + pnts[10].y = yc + hc; + pnts[11].x = x; + pnts[11].y = yc + hc; + + nk_rawfb_fill_polygon(rawfb, pnts, 12, col); + + nk_rawfb_fill_arc(rawfb, xc + wc - r, y, + (unsigned)r*2, (unsigned)r*2, 0 , col); + nk_rawfb_fill_arc(rawfb, x, y, + (unsigned)r*2, (unsigned)r*2, 90 , col); + nk_rawfb_fill_arc(rawfb, x, yc + hc - r, + (unsigned)r*2, (unsigned)r*2, 270 , col); + nk_rawfb_fill_arc(rawfb, xc + wc - r, yc + hc - r, + (unsigned)r*2, (unsigned)r*2, 180 , col); + } +} + +static void +nk_rawfb_fill_triangle(const struct rawfb_context *rawfb, + const short x0, const short y0, const short x1, const short y1, + const short x2, const short y2, const struct nk_color col) +{ + struct nk_vec2i pnts[3]; + pnts[0].x = x0; + pnts[0].y = y0; + pnts[1].x = x1; + pnts[1].y = y1; + pnts[2].x = x2; + pnts[2].y = y2; + nk_rawfb_fill_polygon(rawfb, pnts, 3, col); +} + +static void +nk_rawfb_stroke_triangle(const struct rawfb_context *rawfb, + const short x0, const short y0, const short x1, const short y1, + const short x2, const short y2, const unsigned short line_thickness, + const struct nk_color col) +{ + nk_rawfb_stroke_line(rawfb, x0, y0, x1, y1, line_thickness, col); + nk_rawfb_stroke_line(rawfb, x1, y1, x2, y2, line_thickness, col); + nk_rawfb_stroke_line(rawfb, x2, y2, x0, y0, line_thickness, col); +} + +static void +nk_rawfb_stroke_polygon(const struct rawfb_context *rawfb, + const struct nk_vec2i *pnts, const int count, + const unsigned short line_thickness, const struct nk_color col) +{ + int i; + for (i = 1; i < count; ++i) + nk_rawfb_stroke_line(rawfb, pnts[i-1].x, pnts[i-1].y, pnts[i].x, + pnts[i].y, line_thickness, col); + nk_rawfb_stroke_line(rawfb, pnts[count-1].x, pnts[count-1].y, + pnts[0].x, pnts[0].y, line_thickness, col); +} + +static void +nk_rawfb_stroke_polyline(const struct rawfb_context *rawfb, + const struct nk_vec2i *pnts, const int count, + const unsigned short line_thickness, const struct nk_color col) +{ + int i; + for (i = 0; i < count-1; ++i) + nk_rawfb_stroke_line(rawfb, pnts[i].x, pnts[i].y, + pnts[i+1].x, pnts[i+1].y, line_thickness, col); +} + +static void +nk_rawfb_fill_circle(const struct rawfb_context *rawfb, + short x0, short y0, short w, short h, const struct nk_color col) +{ + /* Bresenham's ellipses */ + const int a2 = (w * w) / 4; + const int b2 = (h * h) / 4; + const int fa2 = 4 * a2, fb2 = 4 * b2; + int x, y, sigma; + + /* Convert upper left to center */ + h = (h + 1) / 2; + w = (w + 1) / 2; + x0 += w; + y0 += h; + + /* First half */ + for (x = 0, y = h, sigma = 2*b2+a2*(1-2*h); b2*x <= a2*y; x++) { + nk_rawfb_stroke_line(rawfb, x0 - x, y0 + y, x0 + x, y0 + y, 1, col); + nk_rawfb_stroke_line(rawfb, x0 - x, y0 - y, x0 + x, y0 - y, 1, col); + if (sigma >= 0) { + sigma += fa2 * (1 - y); + y--; + } sigma += b2 * ((4 * x) + 6); + } + /* Second half */ + for (x = w, y = 0, sigma = 2*a2+b2*(1-2*w); a2*y <= b2*x; y++) { + nk_rawfb_stroke_line(rawfb, x0 - x, y0 + y, x0 + x, y0 + y, 1, col); + nk_rawfb_stroke_line(rawfb, x0 - x, y0 - y, x0 + x, y0 - y, 1, col); + if (sigma >= 0) { + sigma += fb2 * (1 - x); + x--; + } sigma += a2 * ((4 * y) + 6); + } +} + +static void +nk_rawfb_stroke_circle(const struct rawfb_context *rawfb, + short x0, short y0, short w, short h, const short line_thickness, + const struct nk_color col) +{ + /* Bresenham's ellipses */ + const int a2 = (w * w) / 4; + const int b2 = (h * h) / 4; + const int fa2 = 4 * a2, fb2 = 4 * b2; + int x, y, sigma; + + /* Convert upper left to center */ + h = (h + 1) / 2; + w = (w + 1) / 2; + x0 += w; + y0 += h; + + /* First half */ + for (x = 0, y = h, sigma = 2*b2+a2*(1-2*h); b2*x <= a2*y; x++) { + nk_rawfb_setpixel(rawfb, x0 + x, y0 + y, col); + nk_rawfb_setpixel(rawfb, x0 - x, y0 + y, col); + nk_rawfb_setpixel(rawfb, x0 + x, y0 - y, col); + nk_rawfb_setpixel(rawfb, x0 - x, y0 - y, col); + if (sigma >= 0) { + sigma += fa2 * (1 - y); + y--; + } sigma += b2 * ((4 * x) + 6); + } + /* Second half */ + for (x = w, y = 0, sigma = 2*a2+b2*(1-2*w); a2*y <= b2*x; y++) { + nk_rawfb_setpixel(rawfb, x0 + x, y0 + y, col); + nk_rawfb_setpixel(rawfb, x0 - x, y0 + y, col); + nk_rawfb_setpixel(rawfb, x0 + x, y0 - y, col); + nk_rawfb_setpixel(rawfb, x0 - x, y0 - y, col); + if (sigma >= 0) { + sigma += fb2 * (1 - x); + x--; + } sigma += a2 * ((4 * y) + 6); + } +} + +static void +nk_rawfb_stroke_curve(const struct rawfb_context *rawfb, + const struct nk_vec2i p1, const struct nk_vec2i p2, + const struct nk_vec2i p3, const struct nk_vec2i p4, + const unsigned int num_segments, const unsigned short line_thickness, + const struct nk_color col) +{ + unsigned int i_step, segments; + float t_step; + struct nk_vec2i last = p1; + + segments = MAX(num_segments, 1); + t_step = 1.0f/(float)segments; + for (i_step = 1; i_step <= segments; ++i_step) { + float t = t_step * (float)i_step; + float u = 1.0f - t; + float w1 = u*u*u; + float w2 = 3*u*u*t; + float w3 = 3*u*t*t; + float w4 = t * t *t; + float x = w1 * p1.x + w2 * p2.x + w3 * p3.x + w4 * p4.x; + float y = w1 * p1.y + w2 * p2.y + w3 * p3.y + w4 * p4.y; + nk_rawfb_stroke_line(rawfb, last.x, last.y, + (short)x, (short)y, line_thickness,col); + last.x = (short)x; last.y = (short)y; + } +} + +static void +nk_rawfb_clear(const struct rawfb_context *rawfb, const struct nk_color col) +{ + nk_rawfb_fill_rect(rawfb, 0, 0, rawfb->fb.w, rawfb->fb.h, 0, col); +} + +NK_API struct rawfb_context* +nk_rawfb_init(void *fb, struct nk_context *context, const unsigned int w, const unsigned int h, + const unsigned int pitch) +{ + struct rawfb_context *rawfb; + rawfb = malloc(sizeof(struct rawfb_context)); + if (!rawfb) + return NULL; + + nk_memset(rawfb, 0, sizeof(struct rawfb_context)); + rawfb->font_tex.format = NK_FONT_ATLAS_ALPHA8; + rawfb->font_tex.w = rawfb->font_tex.h = 0; + + rawfb->fb.pixels = fb; + rawfb->fb.w= w; + rawfb->fb.h = h; + +#if defined(RAWFB_XRGB_8888) || defined(RAWFB_RGBX_8888) + rawfb->fb.format = NK_FONT_ATLAS_RGBA32; + rawfb->fb.pitch = pitch; +#else + #error Fixme +#endif + + rawfb->ctx = context; + nk_rawfb_scissor(rawfb, 0, 0, rawfb->fb.w, rawfb->fb.h); + + return rawfb; +} + +NK_API void +nk_rawfb_font_stash_begin(struct rawfb_context *rawfb, struct nk_font_atlas **atlas) +{ + nk_font_atlas_init_default(&rawfb->atlas); + nk_font_atlas_begin(&rawfb->atlas); + *atlas = &rawfb->atlas; +} + +NK_API void +nk_rawfb_font_stash_end(struct rawfb_context *rawfb) +{ + const void *tex; + tex = nk_font_atlas_bake(&rawfb->atlas, &rawfb->font_tex.w, &rawfb->font_tex.h, rawfb->font_tex.format); + if (!tex) return; + + switch(rawfb->font_tex.format) { + case NK_FONT_ATLAS_ALPHA8: + rawfb->font_tex.pitch = rawfb->font_tex.w * 1; + break; + case NK_FONT_ATLAS_RGBA32: + rawfb->font_tex.pitch = rawfb->font_tex.w * 4; + break; + }; + /* Store the font texture in tex scratch memory */ + rawfb->font_tex.pixels = malloc(rawfb->font_tex.pitch * rawfb->font_tex.h); + memcpy(rawfb->font_tex.pixels, tex, rawfb->font_tex.pitch * rawfb->font_tex.h); + nk_font_atlas_end(&rawfb->atlas, nk_handle_ptr(NULL), NULL); + if (rawfb->atlas.default_font) + nk_style_set_font(rawfb->ctx, &rawfb->atlas.default_font->handle); + nk_style_load_all_cursors(rawfb->ctx, rawfb->atlas.cursors); +} + +static void +nk_rawfb_stretch_image(const struct rawfb_image *dst, + const struct rawfb_image *src, const struct nk_rect *dst_rect, + const struct nk_rect *src_rect, const struct nk_rect *dst_scissors) +{ + short i, j; + struct nk_color col; + float xinc = src_rect->w / dst_rect->w; + float yinc = src_rect->h / dst_rect->h; + float xoff = src_rect->x, yoff = src_rect->y; + + /* Simple nearest filtering rescaling */ + /* TODO: use bilinear filter */ + for (j = 0; j < (short)dst_rect->h; j++) { + for (i = 0; i < (short)dst_rect->w; i++) { + if (dst_scissors) { + if (i + (int)(dst_rect->x + 0.5f) < dst_scissors->x || i + (int)(dst_rect->x + 0.5f) >= dst_scissors->w) + continue; + if (j + (int)(dst_rect->y + 0.5f) < dst_scissors->y || j + (int)(dst_rect->y + 0.5f) >= dst_scissors->h) + continue; + } + col = nk_image_getpixel(src, (int)xoff, (int) yoff); + nk_image_blendpixel(dst, i + (int)(dst_rect->x + 0.5f), j + (int)(dst_rect->y + 0.5f), col); + xoff += xinc; + } + xoff = src_rect->x; + yoff += yinc; + } +} + +static void +nk_rawfb_font_query_font_glyph(nk_handle handle, const float height, + struct nk_user_font_glyph *glyph, const nk_rune codepoint, + const nk_rune next_codepoint) +{ + float scale; + const struct nk_font_glyph *g; + struct nk_font *font; + NK_ASSERT(glyph); + NK_UNUSED(next_codepoint); + + font = (struct nk_font*)handle.ptr; + NK_ASSERT(font); + NK_ASSERT(font->glyphs); + if (!font || !glyph) + return; + + scale = height/font->info.height; + g = nk_font_find_glyph(font, codepoint); + glyph->width = (g->x1 - g->x0) * scale; + glyph->height = (g->y1 - g->y0) * scale; + glyph->offset = nk_vec2(g->x0 * scale, g->y0 * scale); + glyph->xadvance = (g->xadvance * scale); + glyph->uv[0] = nk_vec2(g->u0, g->v0); + glyph->uv[1] = nk_vec2(g->u1, g->v1); +} + +NK_API void +nk_rawfb_draw_text(const struct rawfb_context *rawfb, + const struct nk_user_font *font, const struct nk_rect rect, + const char *text, const int len, const float font_height, + const struct nk_color fg) +{ + float x = 0; + int text_len = 0; + nk_rune unicode = 0; + nk_rune next = 0; + int glyph_len = 0; + int next_glyph_len = 0; + struct nk_user_font_glyph g; + if (!len || !text) return; + + x = 0; + glyph_len = nk_utf_decode(text, &unicode, len); + if (!glyph_len) return; + + /* draw every glyph image */ + while (text_len < len && glyph_len) { + struct nk_rect src_rect; + struct nk_rect dst_rect; + float char_width = 0; + if (unicode == NK_UTF_INVALID) break; + + /* query currently drawn glyph information */ + next_glyph_len = nk_utf_decode(text + text_len + glyph_len, &next, (int)len - text_len); + nk_rawfb_font_query_font_glyph(font->userdata, font_height, &g, unicode, + (next == NK_UTF_INVALID) ? '\0' : next); + + /* calculate and draw glyph drawing rectangle and image */ + char_width = g.xadvance; + src_rect.x = g.uv[0].x * rawfb->font_tex.w; + src_rect.y = g.uv[0].y * rawfb->font_tex.h; + src_rect.w = g.uv[1].x * rawfb->font_tex.w - g.uv[0].x * rawfb->font_tex.w; + src_rect.h = g.uv[1].y * rawfb->font_tex.h - g.uv[0].y * rawfb->font_tex.h; + + dst_rect.x = x + g.offset.x + rect.x; + dst_rect.y = g.offset.y + rect.y; + dst_rect.w = ceilf(g.width); + dst_rect.h = ceilf(g.height); + + /* TODO: account fg */ + /* Use software rescaling to blit glyph from font_text to framebuffer */ + nk_rawfb_stretch_image(&rawfb->fb, &rawfb->font_tex, &dst_rect, &src_rect, &rawfb->scissors); + + /* offset next glyph */ + text_len += glyph_len; + x += char_width; + glyph_len = next_glyph_len; + unicode = next; + } +} + +NK_API void +nk_rawfb_drawimage(const struct rawfb_context *rawfb, + const int x, const int y, const int w, const int h, + const struct nk_image *img, const struct nk_color *col) +{ + struct nk_rect src_rect; + struct nk_rect dst_rect; + + const struct rawfb_image *rfb_img = img->handle.ptr; + if (!rfb_img) { + rfb_img = &rawfb->font_tex; + } + + src_rect.x = img->region[0]; + src_rect.y = img->region[1]; + if (nk_image_is_subimage(img)) { + src_rect.w = img->region[2]; + src_rect.h = img->region[3]; + } else { + src_rect.w = rfb_img->w; + src_rect.h = rfb_img->h; + } + + dst_rect.x = x; + dst_rect.y = y; + dst_rect.w = w; + dst_rect.h = h; + nk_rawfb_stretch_image(&rawfb->fb, rfb_img, &dst_rect, &src_rect, &rawfb->scissors); +} + +NK_API void +nk_rawfb_shutdown(struct rawfb_context *rawfb) +{ + nk_memset(rawfb, 0, sizeof(struct rawfb_context)); + free(rawfb); +} + +NK_API void +nk_rawfb_resize_fb(struct rawfb_context *rawfb, + void *fb, + const unsigned int w, + const unsigned int h, + const unsigned int pitch) +{ + rawfb->fb.w = w; + rawfb->fb.h = h; + rawfb->fb.pixels = fb; + rawfb->fb.pitch = pitch; +} + +NK_API void +nk_rawfb_render(const struct rawfb_context *rawfb, + const struct nk_color clear, + const unsigned char enable_clear) +{ + const struct nk_command *cmd; + if (enable_clear) + nk_rawfb_clear(rawfb, clear); + + nk_foreach(cmd, rawfb->ctx) { + switch (cmd->type) { + case NK_COMMAND_NOP: break; + case NK_COMMAND_SCISSOR: { + const struct nk_command_scissor *s =(const struct nk_command_scissor*)cmd; + nk_rawfb_scissor((struct rawfb_context *)rawfb, s->x, s->y, s->w, s->h); + } break; + case NK_COMMAND_LINE: { + const struct nk_command_line *l = (const struct nk_command_line *)cmd; + nk_rawfb_stroke_line(rawfb, l->begin.x, l->begin.y, l->end.x, + l->end.y, l->line_thickness, l->color); + } break; + case NK_COMMAND_RECT: { + const struct nk_command_rect *r = (const struct nk_command_rect *)cmd; + nk_rawfb_stroke_rect(rawfb, r->x, r->y, r->w, r->h, + (unsigned short)r->rounding, r->line_thickness, r->color); + } break; + case NK_COMMAND_RECT_FILLED: { + const struct nk_command_rect_filled *r = (const struct nk_command_rect_filled *)cmd; + nk_rawfb_fill_rect(rawfb, r->x, r->y, r->w, r->h, + (unsigned short)r->rounding, r->color); + } break; + case NK_COMMAND_CIRCLE: { + const struct nk_command_circle *c = (const struct nk_command_circle *)cmd; + nk_rawfb_stroke_circle(rawfb, c->x, c->y, c->w, c->h, c->line_thickness, c->color); + } break; + case NK_COMMAND_CIRCLE_FILLED: { + const struct nk_command_circle_filled *c = (const struct nk_command_circle_filled *)cmd; + nk_rawfb_fill_circle(rawfb, c->x, c->y, c->w, c->h, c->color); + } break; + case NK_COMMAND_TRIANGLE: { + const struct nk_command_triangle*t = (const struct nk_command_triangle*)cmd; + nk_rawfb_stroke_triangle(rawfb, t->a.x, t->a.y, t->b.x, t->b.y, + t->c.x, t->c.y, t->line_thickness, t->color); + } break; + case NK_COMMAND_TRIANGLE_FILLED: { + const struct nk_command_triangle_filled *t = (const struct nk_command_triangle_filled *)cmd; + nk_rawfb_fill_triangle(rawfb, t->a.x, t->a.y, t->b.x, t->b.y, + t->c.x, t->c.y, t->color); + } break; + case NK_COMMAND_POLYGON: { + const struct nk_command_polygon *p =(const struct nk_command_polygon*)cmd; + nk_rawfb_stroke_polygon(rawfb, p->points, p->point_count, p->line_thickness,p->color); + } break; + case NK_COMMAND_POLYGON_FILLED: { + const struct nk_command_polygon_filled *p = (const struct nk_command_polygon_filled *)cmd; + nk_rawfb_fill_polygon(rawfb, p->points, p->point_count, p->color); + } break; + case NK_COMMAND_POLYLINE: { + const struct nk_command_polyline *p = (const struct nk_command_polyline *)cmd; + nk_rawfb_stroke_polyline(rawfb, p->points, p->point_count, p->line_thickness, p->color); + } break; + case NK_COMMAND_TEXT: { + const struct nk_command_text *t = (const struct nk_command_text*)cmd; + nk_rawfb_draw_text(rawfb, t->font, nk_rect(t->x, t->y, t->w, t->h), + t->string, t->length, t->height, t->foreground); + } break; + case NK_COMMAND_CURVE: { + const struct nk_command_curve *q = (const struct nk_command_curve *)cmd; + nk_rawfb_stroke_curve(rawfb, q->begin, q->ctrl[0], q->ctrl[1], + q->end, 22, q->line_thickness, q->color); + } break; + case NK_COMMAND_RECT_MULTI_COLOR: + case NK_COMMAND_IMAGE: { + const struct nk_command_image *q = (const struct nk_command_image *)cmd; + nk_rawfb_drawimage(rawfb, q->x, q->y, q->w, q->h, &q->img, &q->col); + } break; + case NK_COMMAND_ARC: { + assert(0 && "NK_COMMAND_ARC not implemented\n"); + } break; + case NK_COMMAND_ARC_FILLED: { + assert(0 && "NK_COMMAND_ARC_FILLED not implemented\n"); + } break; + default: break; + } + } nk_clear(rawfb->ctx); +} +#endif +
--- a/nuklear_ui/nuklear_sdl_gles2.h Thu Mar 14 23:40:50 2019 -0700 +++ b/nuklear_ui/nuklear_sdl_gles2.h Sun Apr 14 23:38:02 2019 -0700 @@ -47,6 +47,7 @@ #include <string.h> +#ifndef DISABLE_OPENGL struct nk_sdl_device { struct nk_buffer cmds; struct nk_draw_null_texture null; @@ -69,10 +70,13 @@ GLfloat uv[2]; nk_byte col[4]; }; +#endif static struct nk_sdl { SDL_Window *win; +#ifndef DISABLE_OPENGL struct nk_sdl_device ogl; +#endif struct nk_context ctx; struct nk_font_atlas atlas; } sdl; @@ -85,7 +89,7 @@ #define DECLARE_PRECISION #endif - +#ifndef DISABLE_OPENGL NK_API void nk_sdl_device_create(void) { @@ -296,6 +300,7 @@ glDisable(GL_BLEND); glDisable(GL_SCISSOR_TEST); } +#endif static void nk_sdl_clipbard_paste(nk_handle usr, struct nk_text_edit *edit) @@ -327,10 +332,10 @@ sdl.ctx.clip.copy = nk_sdl_clipbard_copy; sdl.ctx.clip.paste = nk_sdl_clipbard_paste; sdl.ctx.clip.userdata = nk_handle_ptr(0); - nk_sdl_device_create(); return &sdl.ctx; } +#ifndef DISABLE_OPENGL NK_API void nk_sdl_font_stash_begin(struct nk_font_atlas **atlas) { @@ -350,6 +355,7 @@ nk_style_set_font(&sdl.ctx, &sdl.atlas.default_font->handle); } +#endif NK_API int nk_sdl_handle_event(SDL_Event *evt) @@ -447,7 +453,9 @@ { nk_font_atlas_clear(&sdl.atlas); nk_free(&sdl.ctx); +#ifndef DISABLE_OPENGL nk_sdl_device_destroy(); +#endif memset(&sdl, 0, sizeof(sdl)); }
--- a/render.h Thu Mar 14 23:40:50 2019 -0700 +++ b/render.h Sun Apr 14 23:38:02 2019 -0700 @@ -74,7 +74,8 @@ #define FRAMEBUFFER_ODD 0 #define FRAMEBUFFER_EVEN 1 -#define FRAMEBUFFER_USER_START 2 +#define FRAMEBUFFER_UI 2 +#define FRAMEBUFFER_USER_START 3 #include "vdp.h" @@ -86,6 +87,7 @@ #define RENDER_DPAD_BIT 0x40000000 #define RENDER_AXIS_BIT 0x20000000 +#define RENDER_AXIS_POS 0x10000000 #define RENDER_INVALID_NAME -1 #define RENDER_NOT_MAPPED -2 #define RENDER_NOT_PLUGGED_IN -3 @@ -93,6 +95,7 @@ typedef struct audio_source audio_source; typedef void (*drop_handler)(const char *filename); typedef void (*window_close_handler)(uint8_t which); +typedef void (*ui_render_fun)(void); uint32_t render_map_color(uint8_t r, uint8_t g, uint8_t b); void render_save_screenshot(char *path); @@ -131,6 +134,7 @@ void render_sleep_ms(uint32_t delay); uint8_t render_has_gl(void); audio_source *render_audio_source(uint64_t master_clock, uint64_t sample_divider, uint8_t channels); +void render_audio_source_gaindb(audio_source *src, float gain); void render_audio_adjust_clock(audio_source *src, uint64_t master_clock, uint64_t sample_divider); void render_put_mono_sample(audio_source *src, int16_t value); void render_put_stereo_sample(audio_source *src, int16_t left, int16_t right); @@ -138,6 +142,9 @@ void render_resume_source(audio_source *src); void render_free_source(audio_source *src); void render_config_updated(void); +void render_set_gl_context_handlers(ui_render_fun destroy, ui_render_fun create); +void render_set_ui_render_fun(ui_render_fun); +void render_set_ui_fb_resize_handler(ui_render_fun resize); #endif //RENDER_H_
--- a/render_sdl.c Thu Mar 14 23:40:50 2019 -0700 +++ b/render_sdl.c Sun Apr 14 23:38:02 2019 -0700 @@ -61,6 +61,7 @@ double dt; uint64_t buffer_fraction; uint64_t buffer_inc; + float gain_mult; uint32_t buffer_pos; uint32_t read_start; uint32_t read_end; @@ -78,78 +79,72 @@ static uint8_t num_inactive_audio_sources; static uint8_t sync_to_audio; static uint32_t min_buffered; +static float overall_gain_mult, *mix_buf; +static int sample_size; -typedef int32_t (*mix_func)(audio_source *audio, void *vstream, int len); +typedef void (*conv_func)(float *samples, void *vstream, int sample_count); + +static void convert_null(float *samples, void *vstream, int sample_count) +{ + memset(vstream, 0, sample_count * sample_size); +} -static int32_t mix_s16(audio_source *audio, void *vstream, int len) +static void convert_s16(float *samples, void *vstream, int sample_count) { - int samples = len/(sizeof(int16_t)*2); int16_t *stream = vstream; - int16_t *end = stream + output_channels*samples; + for (int16_t *end = stream + sample_count; stream < end; stream++, samples++) + { + float sample = *samples; + int16_t out_sample; + if (sample >= 1.0f) { + out_sample = 0x7FFF; + } else if (sample <= -1.0f) { + out_sample = -0x8000; + } else { + out_sample = sample * 0x7FFF; + } + *stream = out_sample; + } +} + +static void clamp_f32(float *samples, void *vstream, int sample_count) +{ + for (; sample_count > 0; sample_count--, samples++) + { + float sample = *samples; + if (sample > 1.0f) { + sample = 1.0f; + } else if (sample < -1.0f) { + sample = -1.0f; + } + *samples = sample; + } +} + +static int32_t mix_f32(audio_source *audio, float *stream, int samples) +{ + float *end = stream + samples; int16_t *src = audio->front; uint32_t i = audio->read_start; uint32_t i_end = audio->read_end; - int16_t *cur = stream; + float *cur = stream; + float gain_mult = audio->gain_mult * overall_gain_mult; size_t first_add = output_channels > 1 ? 1 : 0, second_add = output_channels > 1 ? output_channels - 1 : 1; if (audio->num_channels == 1) { while (cur < end && i != i_end) { - *cur += src[i]; - cur += first_add; - *cur += src[i++]; - cur += second_add; - i &= audio->mask; - } - } else { - while (cur < end && i != i_end) - { - *cur += src[i++]; + *cur += gain_mult * ((float)src[i]) / 0x7FFF; cur += first_add; - *cur += src[i++]; - cur += second_add; - i &= audio->mask; - } - } - - if (cur != end) { - printf("Underflow of %d samples, read_start: %d, read_end: %d, mask: %X\n", (int)(end-cur)/2, audio->read_start, audio->read_end, audio->mask); - } - if (!sync_to_audio) { - audio->read_start = i; - } - if (cur != end) { - //printf("Underflow of %d samples, read_start: %d, read_end: %d, mask: %X\n", (int)(end-cur)/2, audio->read_start, audio->read_end, audio->mask); - return (cur-end)/2; - } else { - return ((i_end - i) & audio->mask) / audio->num_channels; - } -} - -static int32_t mix_f32(audio_source *audio, void *vstream, int len) -{ - int samples = len/(sizeof(float)*2); - float *stream = vstream; - float *end = stream + 2*samples; - int16_t *src = audio->front; - uint32_t i = audio->read_start; - uint32_t i_end = audio->read_end; - float *cur = stream; - size_t first_add = output_channels > 1 ? 1 : 0, second_add = output_channels > 1 ? output_channels - 1 : 1; - if (audio->num_channels == 1) { - while (cur < end && i != i_end) - { - *cur += ((float)src[i]) / 0x7FFF; - cur += first_add; - *cur += ((float)src[i++]) / 0x7FFF; + *cur += gain_mult * ((float)src[i++]) / 0x7FFF; cur += second_add; i &= audio->mask; } } else { while(cur < end && i != i_end) { - *cur += ((float)src[i++]) / 0x7FFF; + *cur += gain_mult * ((float)src[i++]) / 0x7FFF; cur += first_add; - *cur += ((float)src[i++]) / 0x7FFF; + *cur += gain_mult * ((float)src[i++]) / 0x7FFF; cur += second_add; i &= audio->mask; } @@ -158,24 +153,18 @@ audio->read_start = i; } if (cur != end) { - printf("Underflow of %d samples, read_start: %d, read_end: %d, mask: %X\n", (int)(end-cur)/2, audio->read_start, audio->read_end, audio->mask); + debug_message("Underflow of %d samples, read_start: %d, read_end: %d, mask: %X\n", (int)(end-cur)/2, audio->read_start, audio->read_end, audio->mask); return (cur-end)/2; } else { return ((i_end - i) & audio->mask) / audio->num_channels; } } -static int32_t mix_null(audio_source *audio, void *vstream, int len) -{ - return 0; -} - -static mix_func mix; +static conv_func convert; static void audio_callback(void * userdata, uint8_t *byte_stream, int len) { uint8_t num_populated; - memset(byte_stream, 0, len); SDL_LockMutex(audio_mutex); do { num_populated = 0; @@ -190,14 +179,18 @@ SDL_CondWait(audio_ready, audio_mutex); } } while(!quitting && num_populated < num_audio_sources); + int samples = len / sample_size; + float *mix_dest = mix_buf ? mix_buf : (float *)byte_stream; + memset(mix_dest, 0, samples * sizeof(float)); if (!quitting) { for (uint8_t i = 0; i < num_audio_sources; i++) { - mix(audio_sources[i], byte_stream, len); + mix_f32(audio_sources[i], mix_dest, samples); audio_sources[i]->front_populated = 0; SDL_CondSignal(audio_sources[i]->cond); } } + convert(mix_dest, byte_stream, samples); SDL_UnlockMutex(audio_mutex); } @@ -211,21 +204,24 @@ static uint32_t min_remaining_buffer; static void audio_callback_drc(void *userData, uint8_t *byte_stream, int len) { - memset(byte_stream, 0, len); if (cur_min_buffered < 0) { //underflow last frame, but main thread hasn't gotten a chance to call SDL_PauseAudio yet return; } cur_min_buffered = 0x7FFFFFFF; min_remaining_buffer = 0xFFFFFFFF; + float *mix_dest = mix_buf ? mix_buf : (float *)byte_stream; + int samples = len / sample_size; + memset(mix_dest, 0, samples * sizeof(float)); for (uint8_t i = 0; i < num_audio_sources; i++) { - int32_t buffered = mix(audio_sources[i], byte_stream, len); + int32_t buffered = mix_f32(audio_sources[i], mix_dest, samples); cur_min_buffered = buffered < cur_min_buffered ? buffered : cur_min_buffered; uint32_t remaining = (audio_sources[i]->mask + 1)/audio_sources[i]->num_channels - buffered; min_remaining_buffer = remaining < min_remaining_buffer ? remaining : min_remaining_buffer; } + convert(mix_dest, byte_stream, samples); } static void lock_audio() @@ -253,6 +249,10 @@ SDL_CondSignal(audio_ready); SDL_UnlockMutex(audio_mutex); SDL_CloseAudio(); + if (mix_buf) { + free(mix_buf); + mix_buf = NULL; + } } #define BUFFER_INC_RES 0x40000000UL @@ -292,6 +292,7 @@ ret->read_start = 0; ret->read_end = sync_to_audio ? buffer_samples * channels : 0; ret->mask = sync_to_audio ? 0xFFFFFFFF : alloc_size-1; + ret->gain_mult = 1.0f; } if (sync_to_audio && SDL_GetAudioStatus() == SDL_AUDIO_PAUSED) { SDL_PauseAudio(0); @@ -299,6 +300,16 @@ return ret; } +static float db_to_mult(float gain) +{ + return powf(10.0f, gain/20.0f); +} + +void render_audio_source_gaindb(audio_source *src, float gain) +{ + src->gain_mult = db_to_mult(gain); +} + void render_pause_source(audio_source *src) { uint8_t need_pause = 0; @@ -486,12 +497,15 @@ static GLuint load_shader(char * fname, GLenum shader_type) { + char * shader_path; + FILE *f; + GLchar *text; + long fsize; +#ifndef __ANDROID__ char const * parts[] = {get_home_dir(), "/.config/blastem/shaders/", fname}; - char * shader_path = alloc_concat_m(3, parts); - FILE * f = fopen(shader_path, "rb"); + shader_path = alloc_concat_m(3, parts); + f = fopen(shader_path, "rb"); free(shader_path); - GLchar * text; - long fsize; if (f) { fsize = file_size(f); text = malloc(fsize); @@ -501,6 +515,7 @@ return 0; } } else { +#endif shader_path = path_append("shaders", fname); uint32_t fsize32; text = read_bundled_file(shader_path, &fsize32); @@ -510,7 +525,10 @@ return 0; } fsize = fsize32; +#ifndef __ANDROID__ } +#endif + text[fsize] = 0; if (strncmp(text, "#version", strlen("#version"))) { GLchar *tmp = text; @@ -519,6 +537,10 @@ fsize += strlen(shader_prefix); } GLuint ret = glCreateShader(shader_type); + if (!ret) { + warning("glCreateShader failed with error %d\n", glGetError()); + return 0; + } glShaderSource(ret, 1, (const GLchar **)&text, (const GLint *)&fsize); free(text); glCompileShader(ret); @@ -538,7 +560,9 @@ #endif static uint32_t texture_buf[512 * 513]; -#ifndef DISABLE_OPENGL +#ifdef DISABLE_OPENGL +#define RENDER_FORMAT SDL_PIXELFORMAT_ARGB8888 +#else #ifdef USE_GLES #define INTERNAL_FORMAT GL_RGBA #define SRC_FORMAT GL_RGBA @@ -683,7 +707,9 @@ static void update_aspect() { //reset default values +#ifndef DISABLE_OPENGL memcpy(vertex_data, vertex_data_default, sizeof(vertex_data)); +#endif main_clip.w = main_width; main_clip.h = main_height; main_clip.x = main_clip.y = 0; @@ -715,13 +741,18 @@ } } -static ui_render_fun on_context_destroyed, on_context_created; +static ui_render_fun on_context_destroyed, on_context_created, on_ui_fb_resized; void render_set_gl_context_handlers(ui_render_fun destroy, ui_render_fun create) { on_context_destroyed = destroy; on_context_created = create; } +void render_set_ui_fb_resize_handler(ui_render_fun resize) +{ + on_ui_fb_resized = resize; +} + static uint8_t scancode_map[SDL_NUM_SCANCODES] = { [SDL_SCANCODE_A] = 0x1C, [SDL_SCANCODE_B] = 0x32, @@ -890,6 +921,7 @@ static uint32_t overscan_left[NUM_VID_STD] = {13, 13}; static uint32_t overscan_right[NUM_VID_STD] = {14, 14}; static vid_std video_standard = VID_NTSC; +static uint8_t need_ui_fb_resize; static int32_t handle_event(SDL_Event *event) { @@ -922,8 +954,8 @@ SDL_Joystick * joy = joysticks[index] = SDL_JoystickOpen(event->jdevice.which); joystick_sdl_index[index] = event->jdevice.which; if (joy) { - printf("Joystick %d added: %s\n", index, SDL_JoystickName(joy)); - printf("\tNum Axes: %d\n\tNum Buttons: %d\n\tNum Hats: %d\n", SDL_JoystickNumAxes(joy), SDL_JoystickNumButtons(joy), SDL_JoystickNumHats(joy)); + debug_message("Joystick %d added: %s\n", index, SDL_JoystickName(joy)); + debug_message("\tNum Axes: %d\n\tNum Buttons: %d\n\tNum Hats: %d\n", SDL_JoystickNumAxes(joy), SDL_JoystickNumButtons(joy), SDL_JoystickNumHats(joy)); handle_joy_added(index); } } @@ -934,9 +966,9 @@ if (index >= 0) { SDL_JoystickClose(joysticks[index]); joysticks[index] = NULL; - printf("Joystick %d removed\n", index); + debug_message("Joystick %d removed\n", index); } else { - printf("Failed to find removed joystick with instance ID: %d\n", index); + debug_message("Failed to find removed joystick with instance ID: %d\n", index); } break; } @@ -953,8 +985,12 @@ switch (event->window.event) { case SDL_WINDOWEVENT_SIZE_CHANGED: + if (!main_window) { + break; + } main_width = event->window.data1; main_height = event->window.data2; + need_ui_fb_resize = 1; update_aspect(); #ifndef DISABLE_OPENGL if (render_gl) { @@ -972,7 +1008,7 @@ #endif break; case SDL_WINDOWEVENT_CLOSE: - if (SDL_GetWindowID(main_window) == event->window.windowID) { + if (main_window && SDL_GetWindowID(main_window) == event->window.windowID) { exit(0); } else { for (int i = 0; i < num_textures - FRAMEBUFFER_USER_START; i++) @@ -1026,14 +1062,14 @@ rate = 48000; } desired.freq = rate; - desired.format = AUDIO_S16SYS; + desired.format = AUDIO_F32SYS; desired.channels = 2; char * samples_str = tern_find_path(config, "audio\0buffer\0", TVAL_PTR).ptrval; int samples = samples_str ? atoi(samples_str) : 0; if (!samples) { samples = 512; } - printf("config says: %d\n", samples); + debug_message("config says: %d\n", samples); desired.samples = samples*2; desired.callback = sync_to_audio ? audio_callback : audio_callback_drc; desired.userdata = NULL; @@ -1044,18 +1080,24 @@ buffer_samples = actual.samples; sample_rate = actual.freq; output_channels = actual.channels; - printf("Initialized audio at frequency %d with a %d sample buffer, ", actual.freq, actual.samples); + debug_message("Initialized audio at frequency %d with a %d sample buffer, ", actual.freq, actual.samples); + sample_size = SDL_AUDIO_BITSIZE(actual.format) / 8; if (actual.format == AUDIO_S16SYS) { - puts("signed 16-bit int format"); - mix = mix_s16; + debug_message("signed 16-bit int format\n"); + convert = convert_s16; + mix_buf = calloc(output_channels * buffer_samples, sizeof(float)); } else if (actual.format == AUDIO_F32SYS) { - puts("32-bit float format"); - mix = mix_f32; + debug_message("32-bit float format\n"); + convert = clamp_f32; + mix_buf = NULL; } else { - printf("unsupported format %X\n", actual.format); + debug_message("unsupported format %X\n", actual.format); warning("Unsupported audio sample format: %X\n", actual.format); - mix = mix_null; + convert = convert_null; + mix_buf = calloc(output_channels * buffer_samples, sizeof(float)); } + char * gain_str = tern_find_path(config, "audio\0gain\0", TVAL_PTR).ptrval; + overall_gain_mult = db_to_mult(gain_str ? atof(gain_str) : 0.0f); } void window_setup(void) @@ -1154,7 +1196,11 @@ } if (vsync) { if (SDL_GL_SetSwapInterval(!strcmp("on", vsync)) < 0) { +#ifdef __ANDROID__ + debug_message("Failed to set vsync to %s: %s\n", vsync, SDL_GetError()); +#else warning("Failed to set vsync to %s: %s\n", vsync, SDL_GetError()); +#endif } } } else { @@ -1174,7 +1220,7 @@ } SDL_RendererInfo rinfo; SDL_GetRendererInfo(main_renderer, &rinfo); - printf("SDL2 Render Driver: %s\n", rinfo.name); + debug_message("SDL2 Render Driver: %s\n", rinfo.name); main_clip.x = main_clip.y = 0; main_clip.w = main_width; main_clip.h = main_height; @@ -1183,7 +1229,7 @@ #endif SDL_GetWindowSize(main_window, &main_width, &main_height); - printf("Window created with size: %d x %d\n", main_width, main_height); + debug_message("Window created with size: %d x %d\n", main_width, main_height); update_aspect(); render_alloc_surfaces(); def.ptrval = "off"; @@ -1200,7 +1246,7 @@ float aspect = config_aspect() > 0.0f ? config_aspect() : 4.0f/3.0f; height = ((float)width / aspect) + 0.5f; } - printf("width: %d, height: %d\n", width, height); + debug_message("width: %d, height: %d\n", width, height); windowed_width = width; windowed_height = height; @@ -1233,7 +1279,7 @@ if (db_data) { int added = SDL_GameControllerAddMappingsFromRW(SDL_RWFromMem(db_data, db_size), 1); free(db_data); - printf("Added %d game controller mappings from gamecontrollerdb.txt\n", added); + debug_message("Added %d game controller mappings from gamecontrollerdb.txt\n", added); } controller_add_mappings(); @@ -1287,6 +1333,7 @@ #endif in_toggle = 1; SDL_DestroyWindow(main_window); + main_window = NULL; drain_events(); char *config_width = tern_find_path(config, "video\0width\0", TVAL_PTR).ptrval; @@ -1312,6 +1359,9 @@ main_width = windowed_width; main_height = windowed_height; } + if (on_ui_fb_resized) { + on_ui_fb_resized(); + } window_setup(); update_aspect(); @@ -1386,7 +1436,7 @@ max_repeat++; min_buffered = (((float)max_repeat * (float)sample_rate/(float)source_hz)/* / (float)buffer_samples*/);// + 0.9999; //min_buffered *= buffer_samples; - printf("Min samples buffered before audio start: %d\n", min_buffered); + debug_message("Min samples buffered before audio start: %d\n", min_buffered); max_adjust = BASE_MAX_ADJUST / source_hz; } @@ -1471,6 +1521,16 @@ return texture_buf; } else { #endif + if (which == FRAMEBUFFER_UI && which >= num_textures) { + sdl_textures = realloc(sdl_textures, sizeof(*sdl_textures) * (FRAMEBUFFER_UI + 1)); + for (; num_textures <= FRAMEBUFFER_UI; num_textures++) + { + sdl_textures[num_textures] = NULL; + } + } + if (which == FRAMEBUFFER_UI && !sdl_textures[which]) { + sdl_textures[which] = SDL_CreateTexture(main_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, main_width, main_height); + } if (which >= num_textures) { warning("Request for invalid framebuffer number %d\n", which); return NULL; @@ -1608,6 +1668,16 @@ last_height = height; if (which <= FRAMEBUFFER_EVEN) { render_update_display(); + } else if (which == FRAMEBUFFER_UI) { + SDL_RenderCopy(main_renderer, sdl_textures[which], NULL, NULL); + if (need_ui_fb_resize) { + SDL_DestroyTexture(sdl_textures[which]); + sdl_textures[which] = NULL; + if (on_ui_fb_resized) { + on_ui_fb_resized(); + } + need_ui_fb_resize = 0; + } } else { SDL_RenderCopy(extra_renderers[which - FRAMEBUFFER_USER_START], sdl_textures[which], NULL, NULL); SDL_RenderPresent(extra_renderers[which - FRAMEBUFFER_USER_START]); @@ -1623,7 +1693,7 @@ if ((last_frame - start) > FPS_INTERVAL) { if (start && (last_frame-start)) { #ifdef __ANDROID__ - info_message("%s - %.1f fps", caption, ((float)frame_counter) / (((float)(last_frame-start)) / 1000.0)); + debug_message("%s - %.1f fps", caption, ((float)frame_counter) / (((float)(last_frame-start)) / 1000.0)); #else if (!fps_caption) { fps_caption = malloc(strlen(caption) + strlen(" - 100000000.1 fps") + 1); @@ -1846,6 +1916,7 @@ } SDL_GameControllerButtonBind cbind; + int32_t is_positive = RENDER_AXIS_POS; if (is_axis) { int sdl_axis = render_lookup_axis(name); @@ -1860,6 +1931,10 @@ SDL_GameControllerClose(control); return RENDER_INVALID_NAME; } + if (sdl_button == SDL_CONTROLLER_BUTTON_DPAD_UP || sdl_button == SDL_CONTROLLER_BUTTON_DPAD_LEFT) { + //assume these will be negative if they are an axis + is_positive = 0; + } cbind = SDL_GameControllerGetBindForButton(control, sdl_button); } SDL_GameControllerClose(control); @@ -1868,7 +1943,7 @@ case SDL_CONTROLLER_BINDTYPE_BUTTON: return cbind.value.button; case SDL_CONTROLLER_BINDTYPE_AXIS: - return RENDER_AXIS_BIT | cbind.value.axis; + return RENDER_AXIS_BIT | cbind.value.axis | is_positive; case SDL_CONTROLLER_BINDTYPE_HAT: return RENDER_DPAD_BIT | (cbind.value.hat.hat << 4) | cbind.value.hat.hat_mask; } @@ -1939,6 +2014,7 @@ SDL_SetWindowSize(main_window, windowed_width, windowed_height); drain_events(); in_toggle = 0; + need_ui_fb_resize = 1; } uint32_t render_audio_buffer()
--- a/render_sdl.h Thu Mar 14 23:40:50 2019 -0700 +++ b/render_sdl.h Sun Apr 14 23:38:02 2019 -0700 @@ -4,12 +4,9 @@ #include <SDL.h> SDL_Window *render_get_window(void); -typedef void (*ui_render_fun)(void); typedef void (*event_handler)(SDL_Event *); void render_update_display(void); -void render_set_ui_render_fun(ui_render_fun); void render_set_event_handler(event_handler handler); -void render_set_gl_context_handlers(ui_render_fun destroy, ui_render_fun create); SDL_Joystick *render_get_joystick(int index); SDL_GameController *render_get_controller(int index); int render_lookup_button(char *name);
--- a/romdb.c Thu Mar 14 23:40:50 2019 -0700 +++ b/romdb.c Sun Apr 14 23:38:02 2019 -0700 @@ -293,6 +293,46 @@ info->map[8].write_16 = (write_16_fun)write_bank_reg_w; info->map[8].write_8 = (write_8_fun)write_bank_reg_b; return; + } else if(!memcmp("SEGA MEGAWIFI", rom + 0x100, strlen("SEGA MEGAWIFI"))) { + info->mapper_type = MAPPER_NONE; + info->map_chunks = base_chunks + 2; + info->map = malloc(sizeof(memmap_chunk) * info->map_chunks); + memset(info->map, 0, sizeof(memmap_chunk)*2); + memcpy(info->map+2, base_map, sizeof(memmap_chunk) * base_chunks); + info->save_size = 0x400000; + info->save_bus = RAM_FLAG_BOTH; + info->save_type = SAVE_NOR; + info->map[0].start = 0; + info->map[0].end = 0x400000; + info->map[0].mask = 0xFFFFFF; + info->map[0].write_16 = nor_flash_write_w; + info->map[0].write_8 = nor_flash_write_b; + info->map[0].read_16 = nor_flash_read_w; + info->map[0].read_8 = nor_flash_read_b; + info->map[0].flags = MMAP_READ_CODE | MMAP_CODE; + info->map[0].buffer = info->save_buffer = malloc(info->save_size); + uint32_t init_size = size < info->save_size ? size : info->save_size; + memcpy(info->save_buffer, rom, init_size); + byteswap_rom(info->save_size, (uint16_t *)info->save_buffer); + info->nor = calloc(1, sizeof(nor_state)); + nor_flash_init(info->nor, info->save_buffer, info->save_size, 128, 0xDA45, RAM_FLAG_BOTH); + info->nor->cmd_address1 = 0xAAB; + info->nor->cmd_address2 = 0x555; + info->map[1].start = 0xA130C0; + info->map[1].end = 0xA130D0; + info->map[1].mask = 0xFFFFFF; + if (!strcmp( + "on", + tern_find_path_default(config, "system\0megawifi\0", (tern_val){.ptrval="off"}, TVAL_PTR).ptrval) + ) { + info->map[1].write_16 = megawifi_write_w; + info->map[1].write_8 = megawifi_write_b; + info->map[1].read_16 = megawifi_read_w; + info->map[1].read_8 = megawifi_read_b; + } else { + warning("ROM uses MegaWiFi, but it is disabled\n"); + } + return; } else if (has_ram_header(rom, size)) { uint32_t ram_start = read_ram_header(info, rom); @@ -560,7 +600,7 @@ if (bits_write) { tern_foreach(bits_write, eeprom_write_fun, eep_map); } - printf("EEPROM address %X: sda read: %X, sda write: %X, scl: %X\n", start, eep_map->sda_read_bit, eep_map->sda_write_mask, eep_map->scl_mask); + debug_message("EEPROM address %X: sda read: %X, sda write: %X, scl: %X\n", start, eep_map->sda_read_bit, eep_map->sda_write_mask, eep_map->scl_mask); state->info->num_eeprom++; } @@ -771,12 +811,14 @@ map->mask = 0xFF; map->write_16 = (write_16_fun)write_bank_reg_w; map->write_8 = (write_8_fun)write_bank_reg_b; +#ifndef IS_LIB } else if (!strcmp(dtype, "MENU")) { //fake hardware for supporting menu map->buffer = NULL; map->mask = 0xFF; map->write_16 = menu_write_w; map->read_16 = menu_read_w; +#endif } else if (!strcmp(dtype, "fixed")) { uint16_t *value = malloc(2); map->buffer = value; @@ -847,18 +889,18 @@ product_id[i] = rom[GAME_ID_OFF + i]; } - printf("Product ID: %s\n", product_id); + debug_message("Product ID: %s\n", product_id); uint8_t raw_hash[20]; sha1(vrom, rom_size, raw_hash); uint8_t hex_hash[41]; bin_to_hex(hex_hash, raw_hash, 20); - printf("SHA1: %s\n", hex_hash); + debug_message("SHA1: %s\n", hex_hash); tern_node * entry = tern_find_node(rom_db, hex_hash); if (!entry) { entry = tern_find_node(rom_db, product_id); } if (!entry) { - puts("Not found in ROM DB, examining header\n"); + debug_message("Not found in ROM DB, examining header\n\n"); if (xband_detect(rom, rom_size)) { return xband_configure_rom(rom_db, rom, rom_size, lock_on, lock_on_size, base_map, base_chunks); } @@ -871,7 +913,7 @@ info.mapper_type = MAPPER_NONE; info.name = tern_find_ptr(entry, "name"); if (info.name) { - printf("Found name: %s\n", info.name); + debug_message("Found name: %s\n\n", info.name); info.name = strdup(info.name); } else { info.name = get_header_name(rom);
--- a/sms.c Thu Mar 14 23:40:50 2019 -0700 +++ b/sms.c Sun Apr 14 23:38:02 2019 -0700 @@ -573,6 +573,13 @@ io_keyboard_up(&sms->io, scancode); } +static void set_gain_config(sms_context *sms) +{ + char *config_gain; + config_gain = tern_find_path(config, "audio\0psg_gain\0", TVAL_PTR).ptrval; + render_audio_source_gaindb(sms->psg->audio, config_gain ? atof(config_gain) : 0.0f); +} + static void config_updated(system_header *system) { sms_context *sms = (sms_context *)system; @@ -624,6 +631,8 @@ sms->psg = malloc(sizeof(psg_context)); psg_init(sms->psg, sms->master_clock, 15*16); + set_gain_config(sms); + sms->vdp = init_vdp_context(0); sms->vdp->system = &sms->header;
--- a/stateview.c Thu Mar 14 23:40:50 2019 -0700 +++ b/stateview.c Sun Apr 14 23:38:02 2019 -0700 @@ -79,10 +79,6 @@ { } -void controller_add_mappings() -{ -} - tern_node * config; int headless = 0;
--- a/util.c Thu Mar 14 23:40:50 2019 -0700 +++ b/util.c Sun Apr 14 23:38:02 2019 -0700 @@ -477,13 +477,16 @@ va_end(args); } +static uint8_t output_enabled = 1; void info_message(char *format, ...) { va_list args; va_start(args, format); #ifndef _WIN32 if (headless || (isatty(STDOUT_FILENO) && isatty(STDIN_FILENO))) { - info_printf(format, args); + if (output_enabled) { + info_printf(format, args); + } } else { #endif int32_t size = strlen(format) * 2; @@ -503,7 +506,9 @@ va_start(args, format); vsnprintf(buf, actual, format, args); } - info_puts(buf); + if (output_enabled) { + info_puts(buf); + } render_infobox("BlastEm Info", buf); free(buf); #ifndef _WIN32 @@ -512,6 +517,20 @@ va_end(args); } +void debug_message(char *format, ...) +{ + va_list args; + va_start(args, format); + if (output_enabled) { + info_printf(format, args); + } +} + +void disable_stdout_messages(void) +{ + output_enabled = 0; +} + #ifdef _WIN32 #include <windows.h> #include <shlobj.h> @@ -838,6 +857,7 @@ #ifdef __ANDROID__ #include <SDL.h> +#ifndef IS_LIB char *read_bundled_file(char *name, uint32_t *sizeret) { SDL_RWops *rw = SDL_RWFromFile(name, "rb"); @@ -865,6 +885,7 @@ SDL_RWclose(rw); return ret; } +#endif char const *get_config_dir() { @@ -878,6 +899,7 @@ #else +#ifndef IS_LIB char *read_bundled_file(char *name, uint32_t *sizeret) { #ifdef DATA_PATH @@ -921,7 +943,7 @@ fclose(f); return ret; } - +#endif #ifdef _WIN32 char const *get_userdata_dir()
--- a/util.h Thu Mar 14 23:40:50 2019 -0700 +++ b/util.h Sun Apr 14 23:38:02 2019 -0700 @@ -84,5 +84,9 @@ void info_message(char *format, ...); //Prints an information message to stderr and to a message box if not in headless mode and not attached to a console void warning(char *format, ...); +//Prints a debug message to stdout +void debug_message(char *format, ...); +//Disables output of info and debug messages to stdout +void disable_stdout_messages(void); #endif //UTIL_H_
--- a/vdp.c Thu Mar 14 23:40:50 2019 -0700 +++ b/vdp.c Sun Apr 14 23:38:02 2019 -0700 @@ -51,8 +51,6 @@ #define BORDER_BOT_V28_PAL 32 #define BORDER_BOT_V30_PAL 24 -#define INVALID_LINE (PAL_INACTIVE_START+BORDER_TOP_V30_PAL+BORDER_BOT_V30_PAL) - enum { INACTIVE = 0, PREPARING, //used for line 0x1FF @@ -1328,16 +1326,14 @@ return (sh_pixel){.index = pixel, .intensity = intensity}; } -static void render_normal(vdp_context *context, int32_t col, uint32_t *dst, uint8_t *debug_dst, int plane_a_off, int plane_b_off) +static void render_normal(vdp_context *context, int32_t col, uint8_t *dst, uint8_t *debug_dst, int plane_a_off, int plane_b_off) { int start = 0; if (!col && (context->regs[REG_MODE_1] & BIT_COL0_MASK)) { - uint32_t bgcolor = context->colors[context->regs[REG_BG_COLOR] & 0x3F]; - for (int i = 0; i < 8; ++i) - { - *(dst++) = bgcolor; - *(debug_dst++) = DBG_SRC_BG; - } + memset(dst, 0, 8); + memset(debug_dst, DBG_SRC_BG, 8); + dst += 8; + debug_dst += 8; start = 8; } uint8_t *sprite_buf = context->linebuf + col * 8 + start; @@ -1347,22 +1343,19 @@ plane_a = context->tmp_buf_a[plane_a_off & SCROLL_BUFFER_MASK]; plane_b = context->tmp_buf_b[plane_b_off & SCROLL_BUFFER_MASK]; sprite = *sprite_buf; - uint8_t pixel = composite_normal(context, debug_dst, sprite, plane_a, plane_b, context->regs[REG_BG_COLOR]); + *(dst++) = composite_normal(context, debug_dst, sprite, plane_a, plane_b, context->regs[REG_BG_COLOR]) & 0x3F; debug_dst++; - *(dst++) = context->colors[pixel & 0x3F]; } } -static void render_highlight(vdp_context *context, int32_t col, uint32_t *dst, uint8_t *debug_dst, int plane_a_off, int plane_b_off) +static void render_highlight(vdp_context *context, int32_t col, uint8_t *dst, uint8_t *debug_dst, int plane_a_off, int plane_b_off) { int start = 0; if (!col && (context->regs[REG_MODE_1] & BIT_COL0_MASK)) { - uint32_t bgcolor = context->colors[SHADOW_OFFSET + (context->regs[REG_BG_COLOR] & 0x3F)]; - for (int i = 0; i < 8; ++i) - { - *(dst++) = bgcolor; - *(debug_dst++) = DBG_SRC_BG | DBG_SHADOW; - } + memset(dst, SHADOW_OFFSET + (context->regs[REG_BG_COLOR] & 0x3F), 8); + memset(debug_dst, DBG_SRC_BG | DBG_SHADOW, 8); + dst += 8; + debug_dst += 8; start = 8; } uint8_t *sprite_buf = context->linebuf + col * 8 + start; @@ -1373,20 +1366,20 @@ plane_b = context->tmp_buf_b[plane_b_off & SCROLL_BUFFER_MASK]; sprite = *sprite_buf; sh_pixel pixel = composite_highlight(context, debug_dst, sprite, plane_a, plane_b, context->regs[REG_BG_COLOR]); - uint32_t *colors; + uint8_t final_pixel; if (pixel.intensity == BUF_BIT_PRIORITY << 1) { - colors = context->colors + HIGHLIGHT_OFFSET; + final_pixel = (pixel.index & 0x3F) + HIGHLIGHT_OFFSET; } else if (pixel.intensity) { - colors = context->colors; + final_pixel = pixel.index & 0x3F; } else { - colors = context->colors + SHADOW_OFFSET; + final_pixel = (pixel.index & 0x3F) + SHADOW_OFFSET; } debug_dst++; - *(dst++) = colors[pixel.index & 0x3F]; + *(dst++) = final_pixel; } } -static void render_testreg(vdp_context *context, int32_t col, uint32_t *dst, uint8_t *debug_dst, int plane_a_off, int plane_b_off, uint8_t output_disabled, uint8_t test_layer) +static void render_testreg(vdp_context *context, int32_t col, uint8_t *dst, uint8_t *debug_dst, int plane_a_off, int plane_b_off, uint8_t output_disabled, uint8_t test_layer) { if (output_disabled) { switch (test_layer) @@ -1402,7 +1395,7 @@ uint8_t *sprite_buf = context->linebuf + col * 8; for (int i = 0; i < 16; i++) { - *(dst++) = context->colors[*(sprite_buf++) & 0x3F]; + *(dst++) = *(sprite_buf++) & 0x3F; *(debug_dst++) = DBG_SRC_S; } break; @@ -1410,14 +1403,14 @@ case 2: for (int i = 0; i < 16; i++) { - *(dst++) = context->colors[context->tmp_buf_a[(plane_a_off++) & SCROLL_BUFFER_MASK] & 0x3F]; + *(dst++) = context->tmp_buf_a[(plane_a_off++) & SCROLL_BUFFER_MASK] & 0x3F; *(debug_dst++) = DBG_SRC_A; } break; case 3: for (int i = 0; i < 16; i++) { - *(dst++) = context->colors[context->tmp_buf_b[(plane_b_off++) & SCROLL_BUFFER_MASK] & 0x3F]; + *(dst++) = context->tmp_buf_b[(plane_b_off++) & SCROLL_BUFFER_MASK] & 0x3F; *(debug_dst++) = DBG_SRC_B; } break; @@ -1427,7 +1420,7 @@ uint8_t *sprite_buf = context->linebuf + col * 8; if (!col && (context->regs[REG_MODE_1] & BIT_COL0_MASK)) { //TODO: Confirm how test register interacts with column 0 blanking - uint8_t pixel = context->regs[REG_BG_COLOR] & 0x3F; + uint8_t pixel = 0x3F; uint8_t src = DBG_SRC_BG; for (int i = 0; i < 8; ++i) { @@ -1453,7 +1446,7 @@ break; } - *(dst++) = context->colors[pixel & 0x3F]; + *(dst++) = pixel; *(debug_dst++) = src; } plane_a_off += 8; @@ -1467,7 +1460,7 @@ plane_a = context->tmp_buf_a[plane_a_off & SCROLL_BUFFER_MASK]; plane_b = context->tmp_buf_b[plane_b_off & SCROLL_BUFFER_MASK]; sprite = *sprite_buf; - uint8_t pixel = composite_normal(context, debug_dst, sprite, plane_a, plane_b, 0x3F); + uint8_t pixel = composite_normal(context, debug_dst, sprite, plane_a, plane_b, 0x3F) & 0x3F; switch (test_layer) { case 1: @@ -1490,18 +1483,18 @@ break; } debug_dst++; - *(dst++) = context->colors[pixel & 0x3F]; + *(dst++) = pixel; } } } -static void render_testreg_highlight(vdp_context *context, int32_t col, uint32_t *dst, uint8_t *debug_dst, int plane_a_off, int plane_b_off, uint8_t output_disabled, uint8_t test_layer) +static void render_testreg_highlight(vdp_context *context, int32_t col, uint8_t *dst, uint8_t *debug_dst, int plane_a_off, int plane_b_off, uint8_t output_disabled, uint8_t test_layer) { int start = 0; uint8_t *sprite_buf = context->linebuf + col * 8; if (!col && (context->regs[REG_MODE_1] & BIT_COL0_MASK)) { //TODO: Confirm how test register interacts with column 0 blanking - uint8_t pixel = context->regs[REG_BG_COLOR] & 0x3F; + uint8_t pixel = 0x3F; uint8_t src = DBG_SRC_BG | DBG_SHADOW; for (int i = 0; i < 8; ++i) { @@ -1527,7 +1520,7 @@ break; } - *(dst++) = context->colors[SHADOW_OFFSET + (pixel & 0x3F)]; + *(dst++) = SHADOW_OFFSET + pixel; *(debug_dst++) = src; } plane_a_off += 8; @@ -1542,16 +1535,10 @@ plane_b = context->tmp_buf_b[plane_b_off & SCROLL_BUFFER_MASK]; sprite = *sprite_buf; sh_pixel pixel = composite_highlight(context, debug_dst, sprite, plane_a, plane_b, 0x3F); - uint32_t *colors; - if (pixel.intensity == BUF_BIT_PRIORITY << 1) { - colors = context->colors + HIGHLIGHT_OFFSET; - } else if (pixel.intensity) { - colors = context->colors; - } else { - colors = context->colors + SHADOW_OFFSET; - } if (output_disabled) { pixel.index = 0x3F; + } else { + pixel.index &= 0x3F; } switch (test_layer) { @@ -1574,37 +1561,35 @@ } break; } + if (pixel.intensity == BUF_BIT_PRIORITY << 1) { + pixel.index += HIGHLIGHT_OFFSET; + } else if (!pixel.intensity) { + pixel.index += SHADOW_OFFSET; + } debug_dst++; - *(dst++) = colors[pixel.index & 0x3F]; + *(dst++) = pixel.index; } } static void render_map_output(uint32_t line, int32_t col, vdp_context * context) { - uint32_t *dst; + uint8_t *dst; uint8_t *debug_dst; uint8_t output_disabled = (context->test_port & TEST_BIT_DISABLE) != 0; uint8_t test_layer = context->test_port >> 7 & 3; if (context->state == PREPARING && !test_layer) { if (col) { col -= 2; - dst = context->output + BORDER_LEFT + col * 8; + dst = context->compositebuf + BORDER_LEFT + col * 8; } else { - dst = context->output; + dst = context->compositebuf; uint32_t bg_color = context->colors[context->regs[REG_BG_COLOR] & 0x3F]; - for (int i = 0; i < BORDER_LEFT; i++, dst++) - { - *dst = bg_color; - } - context->done_output = dst; + memset(dst, 0, BORDER_LEFT); + context->done_composite = dst + BORDER_LEFT; return; } - uint32_t color = context->colors[context->regs[REG_BG_COLOR] & 0x3F]; - for (int i = 0; i < 16; i++) - { - *(dst++) = color; - } - context->done_output = dst; + memset(dst, 0, 16); + context->done_composite = dst + 16; return; } line &= 0xFF; @@ -1615,7 +1600,7 @@ if (col) { col-=2; - dst = context->output + BORDER_LEFT + col * 8; + dst = context->compositebuf + BORDER_LEFT + col * 8; debug_dst = context->layer_debug_buf + BORDER_LEFT + col * 8; @@ -1645,24 +1630,18 @@ } dst += 16; } else { - dst = context->output; + dst = context->compositebuf; debug_dst = context->layer_debug_buf; - uint8_t pixel = context->regs[REG_BG_COLOR] & 0x3F; + uint8_t pixel = 0; if (output_disabled) { pixel = 0x3F; } - uint32_t bg_color = context->colors[pixel]; if (test_layer) { switch(test_layer) { case 1: - bg_color = context->colors[0]; - for (int i = 0; i < BORDER_LEFT; i++, dst++, debug_dst++) - { - *dst = bg_color; - *debug_dst = DBG_SRC_BG; - - } + memset(dst, 0, BORDER_LEFT); + memset(debug_dst, DBG_SRC_BG, BORDER_LEFT); break; case 2: { //plane A @@ -1673,7 +1652,7 @@ //uint8_t *src = context->tmp_buf_a + ((context->buf_a_off + (i ? 0 : (16 - BORDER_LEFT) - (context->hscroll_a & 0xF))) & SCROLL_BUFFER_MASK); for (; i < BORDER_LEFT; buf_off++, i++, dst++, debug_dst++) { - *dst = context->colors[context->tmp_buf_a[buf_off & SCROLL_BUFFER_MASK]]; + *dst = context->tmp_buf_a[buf_off & SCROLL_BUFFER_MASK]; *debug_dst = DBG_SRC_A; } break; @@ -1686,21 +1665,19 @@ //uint8_t *src = context->tmp_buf_b + ((context->buf_b_off + (i ? 0 : (16 - BORDER_LEFT) - (context->hscroll_b & 0xF))) & SCROLL_BUFFER_MASK); for (; i < BORDER_LEFT; buf_off++, i++, dst++, debug_dst++) { - *dst = context->colors[context->tmp_buf_b[buf_off & SCROLL_BUFFER_MASK]]; + *dst = context->tmp_buf_b[buf_off & SCROLL_BUFFER_MASK]; *debug_dst = DBG_SRC_B; } break; } } } else { - for (int i = 0; i < BORDER_LEFT; i++, dst++, debug_dst++) - { - *dst = bg_color; - *debug_dst = DBG_SRC_BG; - } + memset(dst, pixel, BORDER_LEFT); + memset(debug_dst, DBG_SRC_BG, BORDER_LEFT); } + dst += BORDER_LEFT; } - context->done_output = dst; + context->done_composite = dst; context->buf_a_off = (context->buf_a_off + SCROLL_BUFFER_DRAW) & SCROLL_BUFFER_MASK; context->buf_b_off = (context->buf_b_off + SCROLL_BUFFER_DRAW) & SCROLL_BUFFER_MASK; } @@ -1745,15 +1722,12 @@ } context->buf_a_off = (context->buf_a_off + 8) & 15; - uint8_t bgcolor = 0x10 | (context->regs[REG_BG_COLOR] & 0xF) + MODE4_OFFSET; - uint32_t *dst = context->output + col * 8 + BORDER_LEFT; + uint8_t *dst = context->compositebuf + col * 8 + BORDER_LEFT; uint8_t *debug_dst = context->layer_debug_buf + col * 8 + BORDER_LEFT; if (context->state == PREPARING) { - for (int i = 0; i < 16; i++) - { - *(dst++) = context->colors[bgcolor]; - } - context->done_output = dst; + memset(dst, 0, 8); + memset(debug_dst, DBG_SRC_BG, 8); + context->done_composite = dst + 8; return; } @@ -1767,22 +1741,21 @@ uint8_t *bg_src = context->tmp_buf_a + ((8 + i + col * 8 - (context->hscroll_a & 0x7)) & 15); if ((*bg_src & 0x4F) > 0x40 || !*sprite_src) { //background plane has priority and is opaque or sprite layer is transparent - *(dst++) = context->colors[(*bg_src & 0x1F) + MODE4_OFFSET]; - *(debug_dst++) = DBG_SRC_A; + uint8_t pixel = *bg_src & 0x1F; + *(dst++) = pixel + MODE4_OFFSET; + *(debug_dst++) = pixel ? DBG_SRC_A : DBG_SRC_BG; } else { //sprite layer is opaque and not covered by high priority BG pixels - *(dst++) = context->colors[*sprite_src | 0x10 + MODE4_OFFSET]; + *(dst++) = (*sprite_src | 0x10) + MODE4_OFFSET; *(debug_dst++) = DBG_SRC_S; } } + context->done_composite = dst; } else { - for (int i = 0; i < 8; i++) - { - *(dst++) = context->colors[bgcolor]; - *(debug_dst++) = DBG_SRC_BG; - } + memset(dst, 0, 8); + memset(dst, DBG_SRC_BG, 8); + context->done_composite = dst + 8; } - context->done_output = dst; } static uint32_t const h40_hsync_cycles[] = {19, 20, 20, 20, 18, 20, 20, 20, 18, 20, 20, 20, 18, 20, 20, 20, 19}; @@ -2100,17 +2073,16 @@ } output_line = context->output_lines++;//context->vcounter - (0x200 - context->border_top); } else { - output_line = INVALID_LINE; + context->output = NULL; } context->output = (uint32_t *)(((char *)context->fb) + context->output_pitch * output_line); - context->done_output = context->output; #ifdef DEBUG_FB_FILL for (int i = 0; i < LINEBUF_SIZE; i++) { context->output[i] = 0xFFFF00FF; } #endif - if (output_line != INVALID_LINE && (context->regs[REG_MODE_4] & BIT_H40)) { + if (context->output && (context->regs[REG_MODE_4] & BIT_H40)) { context->h40_lines++; } } @@ -2131,7 +2103,7 @@ if (context->output_lines <= lines_max && context->output_lines > 0) { context->output = (uint32_t *)(((char *)context->fb) + context->output_pitch * (context->output_lines - 1)); } else { - context->output = (uint32_t *)(((char *)context->fb) + context->output_pitch * INVALID_LINE); + context->output = NULL; } } @@ -2148,22 +2120,18 @@ static void draw_right_border(vdp_context *context) { - uint32_t *dst = context->output + BORDER_LEFT + ((context->regs[REG_MODE_4] & BIT_H40) ? 320 : 256); + uint8_t *dst = context->compositebuf + BORDER_LEFT + ((context->regs[REG_MODE_4] & BIT_H40) ? 320 : 256); uint8_t pixel = context->regs[REG_BG_COLOR] & 0x3F; if ((context->test_port & TEST_BIT_DISABLE) != 0) { pixel = 0x3F; } - uint32_t bg_color = context->colors[pixel]; uint8_t test_layer = context->test_port >> 7 & 3; if (test_layer) { switch(test_layer) { case 1: - bg_color = context->colors[0]; - for (int i = 0; i < BORDER_RIGHT; i++, dst++) - { - *dst = bg_color; - } + memset(dst, 0, BORDER_RIGHT); + dst += BORDER_RIGHT; break; case 2: { //plane A @@ -2174,7 +2142,7 @@ //uint8_t *src = context->tmp_buf_a + ((context->buf_a_off + (i ? 0 : (16 - BORDER_LEFT) - (context->hscroll_a & 0xF))) & SCROLL_BUFFER_MASK); for (; i < BORDER_RIGHT; buf_off++, i++, dst++) { - *dst = context->colors[context->tmp_buf_a[buf_off & SCROLL_BUFFER_MASK] & 0x3F]; + *dst = context->tmp_buf_a[buf_off & SCROLL_BUFFER_MASK] & 0x3F; } break; } @@ -2186,84 +2154,148 @@ //uint8_t *src = context->tmp_buf_b + ((context->buf_b_off + (i ? 0 : (16 - BORDER_LEFT) - (context->hscroll_b & 0xF))) & SCROLL_BUFFER_MASK); for (; i < BORDER_RIGHT; buf_off++, i++, dst++) { - *dst = context->colors[context->tmp_buf_b[buf_off & SCROLL_BUFFER_MASK] & 0x3F]; + *dst = context->tmp_buf_b[buf_off & SCROLL_BUFFER_MASK] & 0x3F; } break; } } } else { - for (int i = 0; i < BORDER_RIGHT; i++, dst++) - { - *dst = bg_color; - } + memset(dst, 0, BORDER_RIGHT); + dst += BORDER_RIGHT; } - context->done_output = dst; + context->done_composite = dst; context->buf_a_off = (context->buf_a_off + SCROLL_BUFFER_DRAW) & SCROLL_BUFFER_MASK; context->buf_b_off = (context->buf_b_off + SCROLL_BUFFER_DRAW) & SCROLL_BUFFER_MASK; } #define CHECK_ONLY if (context->cycles >= target_cycles) { return; } #define CHECK_LIMIT if (context->flags & FLAG_DMA_RUN) { run_dma_src(context, -1); } context->hslot++; context->cycles += slot_cycles; CHECK_ONLY +#define OUTPUT_PIXEL(slot) if ((slot) >= BG_START_SLOT) {\ + uint8_t *src = context->compositebuf + ((slot) - BG_START_SLOT) *2;\ + uint32_t *dst = context->output + ((slot) - BG_START_SLOT) *2;\ + if ((*src & 0x3F) | test_layer) {\ + *(dst++) = context->colors[*(src++)];\ + } else {\ + *(dst++) = context->colors[(*(src++) & 0xC0) | bgindex];\ + }\ + if ((*src & 0x3F) | test_layer) {\ + *(dst++) = context->colors[*(src++)];\ + } else {\ + *(dst++) = context->colors[(*(src++) & 0xC0) | bgindex];\ + }\ + } + +#define OUTPUT_PIXEL_H40(slot) if (slot <= (BG_START_SLOT + LINEBUF_SIZE/2)) {\ + uint8_t *src = context->compositebuf + (slot - BG_START_SLOT) *2;\ + uint32_t *dst = context->output + (slot - BG_START_SLOT) *2;\ + if ((*src & 0x3F) | test_layer) {\ + *(dst++) = context->colors[*(src++)];\ + } else {\ + *(dst++) = context->colors[(*(src++) & 0xC0) | bgindex];\ + }\ + if (slot != (BG_START_SLOT + LINEBUF_SIZE/2)) {\ + if ((*src & 0x3F) | test_layer) {\ + *(dst++) = context->colors[*(src++)];\ + } else {\ + *(dst++) = context->colors[(*(src++) & 0xC0) | bgindex];\ + }\ + }\ + } + +#define OUTPUT_PIXEL_H32(slot) if (slot <= (BG_START_SLOT + (256+HORIZ_BORDER)/2)) {\ + uint8_t *src = context->compositebuf + (slot - BG_START_SLOT) *2;\ + uint32_t *dst = context->output + (slot - BG_START_SLOT) *2;\ + if ((*src & 0x3F) | test_layer) {\ + *(dst++) = context->colors[*(src++)];\ + } else {\ + *(dst++) = context->colors[(*(src++) & 0xC0) | bgindex];\ + }\ + if (slot != (BG_START_SLOT + (256+HORIZ_BORDER)/2)) {\ + if ((*src & 0x3F) | test_layer) {\ + *(dst++) = context->colors[*(src++)];\ + } else {\ + *(dst++) = context->colors[(*(src++) & 0xC0) | bgindex];\ + }\ + }\ + } #define COLUMN_RENDER_BLOCK(column, startcyc) \ case startcyc:\ + OUTPUT_PIXEL(startcyc)\ read_map_scroll_a(column, context->vcounter, context);\ CHECK_LIMIT\ case ((startcyc+1)&0xFF):\ + OUTPUT_PIXEL((startcyc+1)&0xFF)\ external_slot(context);\ CHECK_LIMIT\ case ((startcyc+2)&0xFF):\ + OUTPUT_PIXEL((startcyc+2)&0xFF)\ render_map_1(context);\ CHECK_LIMIT\ case ((startcyc+3)&0xFF):\ + OUTPUT_PIXEL((startcyc+3)&0xFF)\ render_map_2(context);\ CHECK_LIMIT\ case ((startcyc+4)&0xFF):\ + OUTPUT_PIXEL((startcyc+4)&0xFF)\ read_map_scroll_b(column, context->vcounter, context);\ CHECK_LIMIT\ case ((startcyc+5)&0xFF):\ + OUTPUT_PIXEL((startcyc+5)&0xFF)\ read_sprite_x(context->vcounter, context);\ CHECK_LIMIT\ case ((startcyc+6)&0xFF):\ + OUTPUT_PIXEL((startcyc+6)&0xFF)\ render_map_3(context);\ CHECK_LIMIT\ case ((startcyc+7)&0xFF):\ + OUTPUT_PIXEL((startcyc+7)&0xFF)\ render_map_output(context->vcounter, column, context);\ CHECK_LIMIT #define COLUMN_RENDER_BLOCK_REFRESH(column, startcyc) \ case startcyc:\ + OUTPUT_PIXEL(startcyc)\ read_map_scroll_a(column, context->vcounter, context);\ CHECK_LIMIT\ case (startcyc+1):\ - /* refresh, no don't run dma src */\ + /* refresh, so don't run dma src */\ + OUTPUT_PIXEL((startcyc+1)&0xFF)\ context->hslot++;\ context->cycles += slot_cycles;\ CHECK_ONLY\ case (startcyc+2):\ + OUTPUT_PIXEL((startcyc+2)&0xFF)\ render_map_1(context);\ CHECK_LIMIT\ case (startcyc+3):\ + OUTPUT_PIXEL((startcyc+3)&0xFF)\ render_map_2(context);\ CHECK_LIMIT\ case (startcyc+4):\ + OUTPUT_PIXEL((startcyc+4)&0xFF)\ read_map_scroll_b(column, context->vcounter, context);\ CHECK_LIMIT\ case (startcyc+5):\ + OUTPUT_PIXEL((startcyc+5)&0xFF)\ read_sprite_x(context->vcounter, context);\ CHECK_LIMIT\ case (startcyc+6):\ + OUTPUT_PIXEL((startcyc+6)&0xFF)\ render_map_3(context);\ CHECK_LIMIT\ case (startcyc+7):\ + OUTPUT_PIXEL((startcyc+7)&0xFF)\ render_map_output(context->vcounter, column, context);\ CHECK_LIMIT #define COLUMN_RENDER_BLOCK_MODE4(column, startcyc) \ case startcyc:\ + OUTPUT_PIXEL(startcyc)\ read_map_mode4(column, context->vcounter, context);\ CHECK_LIMIT\ case ((startcyc+1)&0xFF):\ + OUTPUT_PIXEL((startcyc+1)&0xFF)\ if (column & 3) {\ scan_sprite_table_mode4(context);\ } else {\ @@ -2271,9 +2303,11 @@ }\ CHECK_LIMIT\ case ((startcyc+2)&0xFF):\ + OUTPUT_PIXEL((startcyc+2)&0xFF)\ fetch_map_mode4(column, context->vcounter, context);\ CHECK_LIMIT\ case ((startcyc+3)&0xFF):\ + OUTPUT_PIXEL((startcyc+3)&0xFF)\ render_map_mode4(context->vcounter, column, context);\ CHECK_LIMIT @@ -2293,8 +2327,12 @@ #define SPRITE_RENDER_H40(slot) \ case slot:\ + OUTPUT_PIXEL_H40(slot)\ if ((slot) == BG_START_SLOT + LINEBUF_SIZE/2) {\ advance_output_line(context);\ + if (!context->output) {\ + context->output = dummy_buffer;\ + }\ }\ if (slot == 168 || slot == 247 || slot == 248) {\ render_border_garbage(\ @@ -2327,8 +2365,12 @@ //as we're bumping up against the hcounter jump #define SPRITE_RENDER_H32(slot) \ case slot:\ + OUTPUT_PIXEL_H32(slot)\ if ((slot) == BG_START_SLOT + (256+HORIZ_BORDER)/2) {\ advance_output_line(context);\ + if (!context->output) {\ + context->output = dummy_buffer;\ + }\ }\ if (slot == 136 || slot == 247 || slot == 248) {\ render_border_garbage(\ @@ -2360,6 +2402,9 @@ if (context->flags & FLAG_DMA_RUN) { run_dma_src(context, -1); } \ if ((slot) == BG_START_SLOT + (256+HORIZ_BORDER)/2) {\ advance_output_line(context);\ + if (!context->output) {\ + context->output = dummy_buffer;\ + }\ }\ if ((slot) == 147) {\ context->hslot = 233;\ @@ -2379,43 +2424,49 @@ #define SPRITE_RENDER_H32_MODE4(slot) \ case slot:\ + OUTPUT_PIXEL_H32(slot)\ read_sprite_x_mode4(context);\ MODE4_CHECK_SLOT_LINE(slot)\ case CALC_SLOT(slot, 1):\ + OUTPUT_PIXEL(CALC_SLOT(slot, 1))\ read_sprite_x_mode4(context);\ MODE4_CHECK_SLOT_LINE(CALC_SLOT(slot,1))\ case CALC_SLOT(slot, 2):\ + OUTPUT_PIXEL(CALC_SLOT(slot, 2))\ fetch_sprite_cells_mode4(context);\ MODE4_CHECK_SLOT_LINE(CALC_SLOT(slot, 2))\ case CALC_SLOT(slot, 3):\ - if ((slot + 3) == 140) {\ - uint32_t *dst = context->output + BORDER_LEFT + 256 + 8;\ - uint32_t bgcolor = context->colors[0x10 | (context->regs[REG_BG_COLOR] & 0xF) + MODE4_OFFSET];\ - for (int i = 0; i < BORDER_RIGHT-8; i++, dst++)\ - {\ - *dst = bgcolor;\ - }\ - context->done_output = dst;\ - }\ + OUTPUT_PIXEL(CALC_SLOT(slot, 3))\ render_sprite_cells_mode4(context);\ MODE4_CHECK_SLOT_LINE(CALC_SLOT(slot, 3))\ case CALC_SLOT(slot, 4):\ + OUTPUT_PIXEL(CALC_SLOT(slot, 4))\ fetch_sprite_cells_mode4(context);\ MODE4_CHECK_SLOT_LINE(CALC_SLOT(slot, 4))\ case CALC_SLOT(slot, 5):\ + OUTPUT_PIXEL(CALC_SLOT(slot, 5))\ render_sprite_cells_mode4(context);\ MODE4_CHECK_SLOT_LINE(CALC_SLOT(slot, 5)) +static uint32_t dummy_buffer[LINEBUF_SIZE]; static void vdp_h40(vdp_context * context, uint32_t target_cycles) { uint16_t address; uint32_t mask; uint32_t const slot_cycles = MCLKS_SLOT_H40; + uint8_t bgindex = context->regs[REG_BG_COLOR] & 0x3F; + uint8_t test_layer = context->test_port >> 7 & 3; + if (!context->output) { + //This shouldn't happen normally, but it can theoretically + //happen when doing border busting + context->output = dummy_buffer; + } switch(context->hslot) { for (;;) { case 165: + OUTPUT_PIXEL(165) if (!(context->regs[REG_MODE_3] & BIT_VSCROLL)) { //TODO: Develop some tests on hardware to see when vscroll latch actually happens for full plane mode //See note in vdp_h32 for why this was originally moved out of read_map_scroll @@ -2425,31 +2476,14 @@ context->vscroll_latch[1] = context->vsram[1]; } if (context->state == PREPARING) { - uint32_t bg_color = context->colors[context->regs[REG_BG_COLOR] & 0x3F]; - uint32_t *dst = context->output + (context->hslot - BG_START_SLOT) * 2; - if (dst >= context->done_output) { - *dst = bg_color; - } - dst++; - if (dst >= context->done_output) { - *dst = bg_color; - } external_slot(context); } else { render_sprite_cells(context); } CHECK_LIMIT case 166: + OUTPUT_PIXEL(166) if (context->state == PREPARING) { - uint32_t bg_color = context->colors[context->regs[REG_BG_COLOR] & 0x3F]; - uint32_t *dst = context->output + (context->hslot - BG_START_SLOT) * 2; - if (dst >= context->done_output) { - *dst = bg_color; - } - dst++; - if (dst >= context->done_output) { - *dst = bg_color; - } external_slot(context); } else { render_sprite_cells(context); @@ -2462,16 +2496,7 @@ CHECK_LIMIT //sprite attribute table scan starts case 167: - if (context->state == PREPARING) { - uint32_t bg_color = context->colors[context->regs[REG_BG_COLOR] & 0x3F]; - uint32_t *dst = context->output + (context->hslot - BG_START_SLOT) * 2; - for (int i = 0; i < LINEBUF_SIZE - 2 * (context->hslot - BG_START_SLOT); i++, dst++) - { - if (dst >= context->done_output) { - *dst = bg_color; - } - } - } + OUTPUT_PIXEL(167) context->sprite_index = 0x80; context->slot_counter = 0; render_border_garbage( @@ -2591,13 +2616,16 @@ COLUMN_RENDER_BLOCK(38, 145) COLUMN_RENDER_BLOCK_REFRESH(40, 153) case 161: + OUTPUT_PIXEL(161) external_slot(context); CHECK_LIMIT case 162: + OUTPUT_PIXEL(162) external_slot(context); CHECK_LIMIT //sprite render to line buffer starts case 163: + OUTPUT_PIXEL(163) context->cur_slot = MAX_DRAWS-1; memset(context->linebuf, 0, LINEBUF_SIZE); render_border_garbage( @@ -2609,6 +2637,7 @@ render_sprite_cells(context); CHECK_LIMIT case 164: + OUTPUT_PIXEL(164) render_border_garbage( context, context->sprite_draw_list[context->cur_slot].address, @@ -2636,37 +2665,28 @@ uint16_t address; uint32_t mask; uint32_t const slot_cycles = MCLKS_SLOT_H32; + uint8_t bgindex = context->regs[REG_BG_COLOR] & 0x3F; + uint8_t test_layer = context->test_port >> 7 & 3; + if (!context->output) { + //This shouldn't happen normally, but it can theoretically + //happen when doing border busting + context->output = dummy_buffer; + } switch(context->hslot) { for (;;) { case 133: + OUTPUT_PIXEL(133) if (context->state == PREPARING) { - uint32_t bg_color = context->colors[context->regs[REG_BG_COLOR] & 0x3F]; - uint32_t *dst = context->output + (context->hslot - BG_START_SLOT) * 2; - if (dst >= context->done_output) { - *dst = bg_color; - } - dst++; - if (dst >= context->done_output) { - *dst = bg_color; - } external_slot(context); } else { render_sprite_cells(context); } CHECK_LIMIT case 134: + OUTPUT_PIXEL(134) if (context->state == PREPARING) { - uint32_t bg_color = context->colors[context->regs[REG_BG_COLOR] & 0x3F]; - uint32_t *dst = context->output + (context->hslot - BG_START_SLOT) * 2; - if (dst >= context->done_output) { - *dst = bg_color; - } - dst++; - if (dst >= context->done_output) { - *dst = bg_color; - } external_slot(context); } else { render_sprite_cells(context); @@ -2679,16 +2699,7 @@ CHECK_LIMIT //sprite attribute table scan starts case 135: - if (context->state == PREPARING) { - uint32_t bg_color = context->colors[context->regs[REG_BG_COLOR] & 0x3F]; - uint32_t *dst = context->output + (context->hslot - BG_START_SLOT) * 2; - for (int i = 0; i < (256+HORIZ_BORDER) - 2 * (context->hslot - BG_START_SLOT); i++) - { - if (dst >= context->done_output) { - *(dst++) = bg_color; - } - } - } + OUTPUT_PIXEL(135) context->sprite_index = 0x80; context->slot_counter = 0; render_border_garbage( @@ -2710,6 +2721,7 @@ SPRITE_RENDER_H32(143) SPRITE_RENDER_H32(144) case 145: + OUTPUT_PIXEL(145) external_slot(context); CHECK_LIMIT SPRITE_RENDER_H32(146) @@ -2815,14 +2827,17 @@ COLUMN_RENDER_BLOCK(30, 113) COLUMN_RENDER_BLOCK_REFRESH(32, 121) case 129: + OUTPUT_PIXEL(129) external_slot(context); CHECK_LIMIT case 130: { + OUTPUT_PIXEL(130) external_slot(context); CHECK_LIMIT } //sprite render to line buffer starts case 131: + OUTPUT_PIXEL(131) context->cur_slot = MAX_DRAWS_H32-1; memset(context->linebuf, 0, LINEBUF_SIZE); render_border_garbage( @@ -2834,6 +2849,7 @@ render_sprite_cells(context); CHECK_LIMIT case 132: + OUTPUT_PIXEL(132) render_border_garbage( context, context->sprite_draw_list[context->cur_slot].address, @@ -2860,6 +2876,13 @@ uint16_t address; uint32_t mask; uint32_t const slot_cycles = MCLKS_SLOT_H32; + uint8_t bgindex = 0x10 | (context->regs[REG_BG_COLOR] & 0xF) + MODE4_OFFSET; + uint8_t test_layer = context->test_port >> 7 & 3; + if (!context->output) { + //This shouldn't happen normally, but it can theoretically + //happen when doing border busting + context->output = dummy_buffer; + } switch(context->hslot) { for (;;) @@ -2909,13 +2932,6 @@ CHECK_LIMIT case 0: { scan_sprite_table_mode4(context); - uint32_t *dst = context->output;; - uint32_t bgcolor = context->colors[0x10 | (context->regs[REG_BG_COLOR] & 0xF) + MODE4_OFFSET]; - for (int i = 0; i < BORDER_LEFT-8; i++, dst++) - { - *dst = bgcolor; - } - context->done_output = dst; CHECK_LIMIT } case 1: @@ -2931,13 +2947,6 @@ scan_sprite_table_mode4(context); context->buf_a_off = 8; memset(context->tmp_buf_a, 0, 8); - uint32_t *dst = context->output + BORDER_LEFT - 8; - uint32_t bgcolor = context->colors[0x10 | (context->regs[REG_BG_COLOR] & 0xF) + MODE4_OFFSET]; - for (int i = 0; i < 8; i++, dst++) - { - *dst = bgcolor; - } - context->done_output = dst; CHECK_LIMIT } COLUMN_RENDER_BLOCK_MODE4(0, 5) @@ -2973,27 +2982,24 @@ COLUMN_RENDER_BLOCK_MODE4(30, 125) COLUMN_RENDER_BLOCK_MODE4(31, 129) case 133: + OUTPUT_PIXEL(133) external_slot(context); CHECK_LIMIT case 134: + OUTPUT_PIXEL(134) external_slot(context); CHECK_LIMIT case 135: + OUTPUT_PIXEL(135) external_slot(context); CHECK_LIMIT case 136: { + OUTPUT_PIXEL(136) external_slot(context); //set things up for sprite rendering in the next slot memset(context->linebuf, 0, LINEBUF_SIZE); context->cur_slot = context->sprite_index = MAX_DRAWS_H32_MODE4-1; context->sprite_draws = MAX_DRAWS_H32_MODE4; - uint32_t *dst = context->output + BORDER_LEFT + 256; - uint32_t bgcolor = context->colors[0x10 | (context->regs[REG_BG_COLOR] & 0xF) + MODE4_OFFSET]; - for (int i = 0; i < 8; i++, dst++) - { - *dst = bgcolor; - } - context->done_output = dst; CHECK_LIMIT }} default: @@ -3008,7 +3014,7 @@ if (context->hslot > max_slot) { return; } - uint32_t *dst = context->output + (context->hslot >> 3) * SCROLL_BUFFER_DRAW; + uint8_t *dst = context->compositebuf + (context->hslot >> 3) * SCROLL_BUFFER_DRAW; int32_t len; uint32_t src_off; if (context->hslot) { @@ -3019,7 +3025,7 @@ src_off = SCROLL_BUFFER_DRAW - BORDER_LEFT; len = BORDER_LEFT; } - uint8_t *src; + uint8_t *src = NULL; if (test_layer == 2) { //plane A src_off += context->buf_a_off + context->hscroll_a; @@ -3030,16 +3036,17 @@ src = context->tmp_buf_b; } else { //sprite layer + memset(dst, 0, len); + dst += len; + len = 0; + } + if (src) { for (; len >=0; len--, dst++, src_off++) { - *dst = context->colors[0]; + *dst = src[src_off & SCROLL_BUFFER_MASK] & 0x3F; } } - for (; len >=0; len--, dst++, src_off++) - { - *dst = context->colors[src[src_off & SCROLL_BUFFER_MASK] & 0x3F]; - } - context->done_output = dst; + context->done_composite = dst; context->buf_a_off = (context->buf_a_off + SCROLL_BUFFER_DRAW) & SCROLL_BUFFER_DRAW; context->buf_b_off = (context->buf_b_off + SCROLL_BUFFER_DRAW) & SCROLL_BUFFER_DRAW; } @@ -3113,38 +3120,19 @@ } uint32_t *dst; uint8_t *debug_dst; - if ( - ( - context->vcounter < context->inactive_start + context->border_bot - || context->vcounter >= 0x200 - context->border_top - ) && context->hslot >= BG_START_SLOT && context->hslot < bg_end_slot - ) { + if (context->output && context->hslot >= BG_START_SLOT && context->hslot < bg_end_slot) { dst = context->output + 2 * (context->hslot - BG_START_SLOT); debug_dst = context->layer_debug_buf + 2 * (context->hslot - BG_START_SLOT); } else { dst = NULL; } - if ( - !dst && context->vcounter == context->inactive_start + context->border_bot - && context->hslot >= line_change && context->hslot < bg_end_slot - ) { - dst = context->output + 2 * (context->hslot - BG_START_SLOT); - debug_dst = context->layer_debug_buf + 2 * (context->hslot - BG_START_SLOT); - } - uint8_t test_layer = context->test_port >> 7 & 3; - if (test_layer) { - dst = NULL; - } while(context->cycles < target_cycles) { check_switch_inactive(context, is_h40); - if (context->hslot == BG_START_SLOT && !test_layer && ( - context->vcounter < context->inactive_start + context->border_bot - || context->vcounter >= 0x200 - context->border_top - )) { + if (context->hslot == BG_START_SLOT && context->output) { dst = context->output + (context->hslot - BG_START_SLOT) * 2; debug_dst = context->layer_debug_buf + 2 * (context->hslot - BG_START_SLOT); } else if (context->hslot == bg_end_slot) { @@ -3200,30 +3188,58 @@ } if (dst) { + uint8_t bg_index; if (mode_5) { - bg_color = context->colors[context->regs[REG_BG_COLOR] & 0x3F]; + bg_index = context->regs[REG_BG_COLOR] & 0x3F; + bg_color = context->colors[bg_index]; } else if (context->regs[REG_MODE_1] & BIT_MODE_4) { - bg_color = context->colors[MODE4_OFFSET + 0x10 + (context->regs[REG_BG_COLOR] & 0xF)]; + bg_index = 0x10 + (context->regs[REG_BG_COLOR] & 0xF); + bg_color = context->colors[MODE4_OFFSET + bg_index]; } - if (dst >= context->done_output) { + if (context->done_composite) { + uint8_t pixel = context->compositebuf[dst-context->output]; + if (!(pixel & 0x3F | test_layer)) { + pixel = pixel & 0xC0 | bg_index; + } + *(dst++) = context->colors[pixel]; + if ((dst - context->output) == (context->done_composite - context->compositebuf)) { + context->done_composite = NULL; + memset(context->compositebuf, 0, sizeof(context->compositebuf)); + } + } else { *(dst++) = bg_color; *(debug_dst++) = DBG_SRC_BG; + } + if (context->done_composite) { + uint8_t pixel = context->compositebuf[dst-context->output]; + if (!(pixel & 0x3F | test_layer)) { + pixel = pixel & 0xC0 | bg_index; + } + *(dst++) = context->colors[pixel]; + if ((dst - context->output) == (context->done_composite - context->compositebuf)) { + context->done_composite = NULL; + memset(context->compositebuf, 0, sizeof(context->compositebuf)); + } } else { - dst++; - debug_dst++; - } - if (dst >= context->done_output) { *(dst++) = bg_color; *(debug_dst++) = DBG_SRC_BG; - context->done_output = dst; - } else { - dst++; - debug_dst++; } + if (context->hslot == (bg_end_slot-1)) { - *(dst++) = bg_color; - *(debug_dst++) = DBG_SRC_BG; - context->done_output = dst; + if (context->done_composite) { + uint8_t pixel = context->compositebuf[dst-context->output]; + if (!(pixel & 0x3F | test_layer)) { + pixel = pixel & 0xC0 | bg_index; + } + *(dst++) = context->colors[pixel]; + if ((dst - context->output) == (context->done_composite - context->compositebuf)) { + context->done_composite = NULL; + memset(context->compositebuf, 0, sizeof(context->compositebuf)); + } + } else { + *(dst++) = bg_color; + *(debug_dst++) = DBG_SRC_BG; + } } }
--- a/vdp.h Thu Mar 14 23:40:50 2019 -0700 +++ b/vdp.h Sun Apr 14 23:38:02 2019 -0700 @@ -163,9 +163,9 @@ system_header *system; //pointer to current line in framebuffer uint32_t *output; - uint32_t *done_output; //pointer to current framebuffer uint32_t *fb; + uint8_t *done_composite; uint32_t *debug_fbs[VDP_NUM_DEBUG_TYPES]; uint32_t output_pitch; uint32_t debug_fb_pitch[VDP_NUM_DEBUG_TYPES]; @@ -205,6 +205,7 @@ uint16_t test_port; //stores 2-bit palette + 4-bit palette index + priority for current sprite line uint8_t linebuf[LINEBUF_SIZE]; + uint8_t compositebuf[LINEBUF_SIZE]; uint8_t layer_debug_buf[LINEBUF_SIZE]; uint8_t hslot; //hcounter/2 uint8_t sprite_index;
--- a/vgmplay.c Thu Mar 14 23:40:50 2019 -0700 +++ b/vgmplay.c Sun Apr 14 23:38:02 2019 -0700 @@ -62,10 +62,6 @@ { } -void controller_add_mappings() -{ -} - int headless = 0; #define CYCLE_LIMIT MCLKS_NTSC/60
--- a/ym2612.c Thu Mar 14 23:40:50 2019 -0700 +++ b/ym2612.c Sun Apr 14 23:38:02 2019 -0700 @@ -256,6 +256,7 @@ } } ym_reset(context); + ym_enable_zero_offset(context, 1); } void ym_free(ym2612_context *context) @@ -267,8 +268,18 @@ free(context); } -#define YM_VOLUME_MULTIPLIER 2 -#define YM_VOLUME_DIVIDER 3 +void ym_enable_zero_offset(ym2612_context *context, uint8_t enabled) +{ + if (enabled) { + context->zero_offset = 0x70; + context->volume_mult = 79; + context->volume_div = 120; + } else { + context->zero_offset = 0; + context->volume_mult = 2; + context->volume_div = 3; + } +} #define YM_MOD_SHIFT 1 #define CSM_MODE 0x80 @@ -470,8 +481,7 @@ if (operator->mod_src[0]) { mod = *operator->mod_src[0]; if (operator->mod_src[1]) { - mod += * - operator->mod_src[1]; + mod += *operator->mod_src[1]; } mod >>= YM_MOD_SHIFT; } @@ -508,7 +518,7 @@ if (operator->am) { uint16_t base_am = (context->lfo_am_step & 0x80 ? context->lfo_am_step : ~context->lfo_am_step) & 0x7E; if (ams_shift[chan->ams] >= 0) { - env += base_am >> ams_shift[chan->ams]; + env += (base_am >> ams_shift[chan->ams]) & MAX_ENVELOPE; } else { env += base_am << (-ams_shift[chan->ams]); } @@ -529,6 +539,8 @@ } if (op % 4 == 0) { chan->op1_old = operator->output; + } else if (op % 4 == 2) { + chan->op2_old = operator->output; } operator->output = output; //Update the channel output if we've updated all operators @@ -549,7 +561,7 @@ if (value & 0x2000) { value |= 0xC000; } - dfprintf(debug_file, "channel %d output: %d\n", channel, (value * YM_VOLUME_MULTIPLIER) / YM_VOLUME_DIVIDER); + dfprintf(debug_file, "channel %d output: %d\n", channel, (value * context->volume_mult) / context->volume_div); } } //puts("operator update done"); @@ -571,14 +583,31 @@ value |= 0xC000; } } + if (value >= 0) { + value += context->zero_offset; + } else { + value -= context->zero_offset; + } if (context->channels[i].logfile) { fwrite(&value, sizeof(value), 1, context->channels[i].logfile); } if (context->channels[i].lr & 0x80) { - left += (value * YM_VOLUME_MULTIPLIER) / YM_VOLUME_DIVIDER; + left += (value * context->volume_mult) / context->volume_div; + } else if (context->zero_offset) { + if (value >= 0) { + left += (context->zero_offset * context->volume_mult) / context->volume_div; + } else { + left -= (context->zero_offset * context->volume_mult) / context->volume_div; + } } if (context->channels[i].lr & 0x40) { - right += (value * YM_VOLUME_MULTIPLIER) / YM_VOLUME_DIVIDER; + right += (value * context->volume_mult) / context->volume_div; + } else if (context->zero_offset) { + if (value >= 0) { + right += (context->zero_offset * context->volume_mult) / context->volume_div; + } else { + right -= (context->zero_offset * context->volume_mult) / context->volume_div; + } } } render_put_stereo_sample(context->audio, left, right); @@ -671,6 +700,7 @@ inc = context->ch3_supp[index].fnum; if (channel->pms) { inc = inc * 2 + lfo_pm_table[(inc & 0x7F0) * 16 + channel->pms + context->lfo_pm_step]; + inc &= 0xFFF; } if (!context->ch3_supp[index].block) { inc >>= 1; @@ -683,6 +713,7 @@ inc = channel->fnum; if (channel->pms) { inc = inc * 2 + lfo_pm_table[(inc & 0x7F0) * 16 + channel->pms + context->lfo_pm_step]; + inc &= 0xFFF; } if (!channel->block) { inc >>= 1; @@ -839,7 +870,6 @@ operator->rates[PHASE_ATTACK] = value & 0x1F; break; case REG_DECAY_AM: - //TODO: AM flag for LFO operator->am = value & 0x80; operator->rates[PHASE_DECAY] = value & 0x1F; break; @@ -900,6 +930,8 @@ { case 0: //operator 3 modulated by operator 2 + //this uses a special op2 result reg on HW, but that reg will have the most recent + //result from op2 when op3 starts executing context->operators[channel*4+1].mod_src[0] = &context->operators[channel*4+2].output; context->operators[channel*4+1].mod_src[1] = NULL; @@ -912,7 +944,11 @@ break; case 1: //operator 3 modulated by operator 1+2 - context->operators[channel*4+1].mod_src[0] = &context->operators[channel*4+0].output; + //op1 starts executing before this, but due to pipeline length the most current result is + //not available and instead the previous result is used + context->operators[channel*4+1].mod_src[0] = &context->channels[channel].op1_old; + //this uses a special op2 result reg on HW, but that reg will have the most recent + //result from op2 when op3 starts executing context->operators[channel*4+1].mod_src[1] = &context->operators[channel*4+2].output; //operator 2 unmodulated @@ -924,6 +960,8 @@ break; case 2: //operator 3 modulated by operator 2 + //this uses a special op2 result reg on HW, but that reg will have the most recent + //result from op2 when op3 starts executing context->operators[channel*4+1].mod_src[0] = &context->operators[channel*4+2].output; context->operators[channel*4+1].mod_src[1] = NULL; @@ -931,6 +969,8 @@ context->operators[channel*4+2].mod_src[0] = NULL; //operator 4 modulated by operator 1+3 + //this uses a special op1 result reg on HW, but that reg will have the most recent + //result from op1 when op4 starts executing context->operators[channel*4+3].mod_src[0] = &context->operators[channel*4+0].output; context->operators[channel*4+3].mod_src[1] = &context->operators[channel*4+1].output; break; @@ -943,7 +983,9 @@ context->operators[channel*4+2].mod_src[0] = &context->operators[channel*4+0].output; //operator 4 modulated by operator 2+3 - context->operators[channel*4+3].mod_src[0] = &context->operators[channel*4+2].output; + //op2 starts executing before this, but due to pipeline length the most current result is + //not available and instead the previous result is used + context->operators[channel*4+3].mod_src[0] = &context->channels[channel].op2_old; context->operators[channel*4+3].mod_src[1] = &context->operators[channel*4+1].output; break; case 4: @@ -960,13 +1002,17 @@ break; case 5: //operator 3 modulated by operator 1 - context->operators[channel*4+1].mod_src[0] = &context->operators[channel*4+0].output; + //op1 starts executing before this, but due to pipeline length the most current result is + //not available and instead the previous result is used + context->operators[channel*4+1].mod_src[0] = &context->channels[channel].op1_old; context->operators[channel*4+1].mod_src[1] = NULL; //operator 2 modulated by operator 1 context->operators[channel*4+2].mod_src[0] = &context->operators[channel*4+0].output; //operator 4 modulated by operator 1 + //this uses a special op1 result reg on HW, but that reg will have the most recent + //result from op1 when op4 starts executing context->operators[channel*4+3].mod_src[0] = &context->operators[channel*4+0].output; context->operators[channel*4+3].mod_src[1] = NULL; break;
--- a/ym2612.h Thu Mar 14 23:40:50 2019 -0700 +++ b/ym2612.h Sun Apr 14 23:38:02 2019 -0700 @@ -39,6 +39,7 @@ uint16_t fnum; int16_t output; int16_t op1_old; + int16_t op2_old; uint8_t block_fnum_latch; uint8_t block; uint8_t keycode; @@ -70,9 +71,11 @@ //TODO: Condense the next two fields into one uint32_t write_cycle; uint32_t busy_cycles; - uint32_t lowpass_alpha; + int32_t volume_mult; + int32_t volume_div; ym_operator operators[NUM_OPERATORS]; ym_channel channels[NUM_CHANNELS]; + int16_t zero_offset; uint16_t timer_a; uint16_t timer_a_load; uint16_t env_counter; @@ -128,6 +131,7 @@ void ym_init(ym2612_context * context, uint32_t master_clock, uint32_t clock_div, uint32_t options); void ym_reset(ym2612_context *context); void ym_free(ym2612_context *context); +void ym_enable_zero_offset(ym2612_context *context, uint8_t enabled); void ym_adjust_master_clock(ym2612_context * context, uint32_t master_clock); void ym_run(ym2612_context * context, uint32_t to_cycle); void ym_address_write_part1(ym2612_context * context, uint8_t address);
--- a/z80_to_x86.c Thu Mar 14 23:40:50 2019 -0700 +++ b/z80_to_x86.c Sun Apr 14 23:38:02 2019 -0700 @@ -3601,7 +3601,7 @@ tmp_stack_off = code->stack_off; save_callee_save_regs(code); #ifdef X86_64 - mov_rr(code, RDI, options->gen.context_reg, SZ_PTR); + mov_rr(code, FIRST_ARG_REG, options->gen.context_reg, SZ_PTR); #else mov_rdispr(code, RSP, 5 * sizeof(int32_t), options->gen.context_reg, SZ_PTR); #endif