diff --git a/CMakeLists.txt b/CMakeLists.txt index d4c9ff999..65ba946f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,19 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) add_compile_definitions("$<$:_DEBUG>") add_compile_options(-Werror=return-type -Wno-switch) +if (EMSCRIPTEN) + add_compile_options( + -fexceptions + -Oz + ) + + add_link_options( + -fexceptions + -sALLOW_MEMORY_GROWTH=1 + -Oz + ) +endif() + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") add_compile_options(-Werror=format -Wno-error=format-overflow -Wno-error=format-truncation -Wno-psabi) endif() diff --git a/minizip/CMakeLists.txt b/minizip/CMakeLists.txt index 769bbbd95..abac3ba7d 100644 --- a/minizip/CMakeLists.txt +++ b/minizip/CMakeLists.txt @@ -15,10 +15,12 @@ target_include_directories(minizip INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/.. ) -# use 32‑bit file API to fix Android compilation issue -target_compile_definitions(minizip PUBLIC - -DUSE_FILE32API -) +if (NOT EMSCRIPTEN) + # use 32‑bit file API to fix Android compilation issue + target_compile_definitions(minizip PUBLIC + -DUSE_FILE32API + ) +endif() target_link_libraries(minizip PUBLIC zlib2 diff --git a/source/AY8910.cpp b/source/AY8910.cpp index 187e89bb3..dcabf4d39 100644 --- a/source/AY8910.cpp +++ b/source/AY8910.cpp @@ -804,7 +804,7 @@ void AY8913::sound_ay_write( int reg, int val, libspectrum_dword now ) } else { - LogOutput("AY reg write discarded: %02X = %02X\n", reg, val); + // LogOutput("AY reg write discarded: %02X = %02X\n", reg, val); } } diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 6f0037295..a5828d50d 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -5,7 +5,7 @@ option(ENABLE_NETWORKING "Enable networking support (SLIRP/PCAP)" ON) set(SLIRP_FOUND "0") set(PCAP_FOUND "0") -if (ENABLE_NETWORKING AND NOT WIN32 AND NOT IOS) +if (ENABLE_NETWORKING AND NOT WIN32 AND NOT IOS AND NOT EMSCRIPTEN) pkg_search_module(SLIRP slirp) # if slirp is not found, we will try pcap diff --git a/source/NTSC.cpp b/source/NTSC.cpp index f8c2e2733..770b57589 100644 --- a/source/NTSC.cpp +++ b/source/NTSC.cpp @@ -2716,9 +2716,9 @@ static void GenerateBaseColors(baseColors_t pBaseNtscColors) int r = (((colors[0]>>16)&0xff) + ((colors[1]>>16)&0xff) + ((colors[2]>>16)&0xff) + ((colors[3]>>16)&0xff)) / 4; int g = (((colors[0]>> 8)&0xff) + ((colors[1]>> 8)&0xff) + ((colors[2]>> 8)&0xff) + ((colors[3]>> 8)&0xff)) / 4; int b = (((colors[0] )&0xff) + ((colors[1] )&0xff) + ((colors[2] )&0xff) + ((colors[3] )&0xff)) / 4; - uint32_t color = ((r<<16) | (g<<8) | b) | ALPHA32_MASK; + bgra_t color = makeBGRA(b, g, r, ALPHA); - (*pBaseNtscColors)[i] = * (bgra_t*) &color; + (*pBaseNtscColors)[i] = color; } } diff --git a/source/RGBMonitor.cpp b/source/RGBMonitor.cpp index 8d1dc2b32..1c32ae4ad 100644 --- a/source/RGBMonitor.cpp +++ b/source/RGBMonitor.cpp @@ -114,9 +114,9 @@ const BYTE DoubleHiresPalIndex[16] = { ORANGE, PINK, YELLOW, WHITE }; -static RGBQUAD* g_pPaletteRGB; +static bgra_t* g_pPaletteRGB; -static RGBQUAD PaletteRGB_NTSC[] = +static bgra_t PaletteRGB_NTSC[] = { // hires #if DO_OPT_PALETTE @@ -166,7 +166,7 @@ static RGBQUAD PaletteRGB_NTSC[] = // Le Chat Mauve Feline's palette // extracted from a white-balanced RGB video capture -static RGBQUAD PaletteRGB_Feline[] = +static bgra_t PaletteRGB_Feline[] = { SETRGBCOLOR(/*HGR_BLACK, */ 0x00,0x00,0x00), SETRGBCOLOR(/*HGR_WHITE, */ 0xFF,0xFF,0xFF), @@ -529,7 +529,7 @@ static void CopyMixedSource(int x, int y, int sx, int sy, bgra_t *pVideoAddress) // color mixing between adjacent scanlines at current x position MixColorsVertical(matx+nBytes, maty, isSWMIXED); //Post: colormixbuffer[] - UINT32* pDst = (UINT32*) pVideoAddress; + bgra_t* pDst = pVideoAddress; for (int h=HGR_MATRIX_YOFFSET; h<=HGR_MATRIX_YOFFSET+1; h++) { @@ -541,8 +541,8 @@ static void CopyMixedSource(int x, int y, int sx, int sy, bgra_t *pVideoAddress) else { _ASSERT( colormixbuffer[h] < (sizeof(PaletteRGB_NTSC)/sizeof(PaletteRGB_NTSC[0])) ); - const RGBQUAD& rRGB = g_pPaletteRGB[ colormixbuffer[h] ]; - *(pDst+nBytes) = *reinterpret_cast(&rRGB); + const bgra_t& rRGB = g_pPaletteRGB[ colormixbuffer[h] ]; + *(pDst+nBytes) = rRGB; } pDst -= frameBufferWidth; @@ -555,7 +555,7 @@ static void CopyMixedSource(int x, int y, int sx, int sy, bgra_t *pVideoAddress) // Pre: nSrcAdjustment: for 160-color images, src is +1 compared to dst static void CopySource(int w, int h, int sx, int sy, bgra_t *pVideoAddress, const int nSrcAdjustment = 0) { - UINT32* pDst = (UINT32*) pVideoAddress; + bgra_t* pDst = pVideoAddress; const BYTE* const pSrc = g_aSourceStartofLine[ sy ] + sx; const bool bIsHalfScanLines = GetVideo().IsVideoStyle(VS_HALF_SCANLINES); @@ -573,8 +573,8 @@ static void CopySource(int w, int h, int sx, int sy, bgra_t *pVideoAddress, cons for (int nBytes=0; nBytes(&rRGB); + const bgra_t& rRGB = g_pPaletteRGB[ *(pSrc+nBytes+nSrcAdjustment) ]; + *(pDst+nBytes) = rRGB; } } @@ -667,7 +667,7 @@ void UpdateHiResRGBCell(int x, int y, uint16_t addr, bgra_t* pVideoAddress) uint32_t dwordval = (byteval1 & 0x7F) | ((byteval2 & 0x7F) << 7) | ((byteval3 & 0x7F) << 14) | ((byteval4 & 0x7F) << 21); // Extraction of 14 color pixels - UINT32 colors[14]; + bgra_t colors[14]; int color = 0; uint32_t dwordval_tmp = dwordval; dwordval_tmp = dwordval_tmp >> 7; @@ -678,15 +678,15 @@ void UpdateHiResRGBCell(int x, int y, uint16_t addr, bgra_t* pVideoAddress) color = dwordval_tmp & 0x3; // Two cases because AppleWin's palette is in a strange order if (offset) - colors[i] = *reinterpret_cast(&g_pPaletteRGB[1 + color]); + colors[i] = g_pPaletteRGB[1 + color]; else - colors[i] = *reinterpret_cast(&g_pPaletteRGB[6 - color]); + colors[i] = g_pPaletteRGB[6 - color]; if (i%2) dwordval_tmp >>= 2; } // Black and White - UINT32 bw[2]; - bw[0] = *reinterpret_cast(&g_pPaletteRGB[0]); - bw[1] = *reinterpret_cast(&g_pPaletteRGB[1]); + bgra_t bw[2]; + bw[0] = g_pPaletteRGB[0]; + bw[1] = g_pPaletteRGB[1]; uint32_t mask = 0x01C0; // 00|000001 1|1000000 uint32_t chck1 = 0x0140; // 00|000001 0|1000000 @@ -697,7 +697,7 @@ void UpdateHiResRGBCell(int x, int y, uint16_t addr, bgra_t* pVideoAddress) // In all other cases, it's black if 0 and white if 1 // The value of 'color' is defined on a 2-bits basis - UINT32* pDst = (UINT32*)pVideoAddress; + bgra_t* pDst = pVideoAddress; if (xoffset) { @@ -729,7 +729,7 @@ void UpdateHiResRGBCell(int x, int y, uint16_t addr, bgra_t* pVideoAddress) const bool bIsHalfScanLines = GetVideo().IsVideoStyle(VS_HALF_SCANLINES); // Second line - UINT32* pSrc = (UINT32*)pVideoAddress; + bgra_t* pSrc = pVideoAddress; pDst = pSrc - GetVideo().GetFrameBufferWidth(); if (bIsHalfScanLines) { @@ -766,19 +766,19 @@ void UpdateDHiResCellRGB(int x, int y, uint16_t addr, bgra_t* pVideoAddress, boo // Extraction of 7 color pixels and 7x4 bits int bits[7]; - UINT32 colors[7]; + bgra_t colors[7]; int color = 0; uint32_t dwordval_tmp = dwordval; for (int i = 0; i < 7; i++) { bits[i] = dwordval_tmp & 0xF; color = ((bits[i] & 7) << 1) | ((bits[i] & 8) >> 3); // DHGR colors are rotated 1 bit to the right - colors[i] = *reinterpret_cast(&g_pPaletteRGB[12 + color]); + colors[i] = g_pPaletteRGB[12 + color]; dwordval_tmp >>= 4; } - UINT32 bw[2]; - bw[0] = *reinterpret_cast(&g_pPaletteRGB[12 + 0]); - bw[1] = *reinterpret_cast(&g_pPaletteRGB[12 + 15]); + bgra_t bw[2]; + bw[0] = g_pPaletteRGB[12 + 0]; + bw[1] = g_pPaletteRGB[12 + 15]; if (isBit7Inversed) { @@ -800,7 +800,7 @@ void UpdateDHiResCellRGB(int x, int y, uint16_t addr, bgra_t* pVideoAddress, boo // // (Tested on Le Chat Mauve IIc adapter, which was made under patent of Video-7) - UINT32* pDst = (UINT32*)pVideoAddress; + bgra_t* pDst = pVideoAddress; if (xoffset == 0) // First cell { @@ -944,7 +944,7 @@ void UpdateDHiResCellRGB(int x, int y, uint16_t addr, bgra_t* pVideoAddress, boo const bool bIsHalfScanLines = GetVideo().IsVideoStyle(VS_HALF_SCANLINES); // Second line - UINT32* pSrc = (UINT32*)pVideoAddress ; + bgra_t* pSrc = pVideoAddress ; pDst = pSrc - GetVideo().GetFrameBufferWidth(); if (bIsHalfScanLines) { @@ -1119,11 +1119,11 @@ void UpdateHiResDuochromeCell(int x, int y, uint16_t addr, bgra_t* pVideoAddress // Can be used in TEXT or HGR mode. The foreground & background colors could be fixed by hardware switches or data lying in AUX. void UpdateDuochromeCell(int h, int w, bgra_t* pVideoAddress, uint8_t bits, uint8_t foreground, uint8_t background) { - UINT32* pDst = (UINT32*)pVideoAddress; + bgra_t* pDst = pVideoAddress; const bool bIsHalfScanLines = GetVideo().IsVideoStyle(VS_HALF_SCANLINES); const UINT frameBufferWidth = GetVideo().GetFrameBufferWidth(); - RGBQUAD colors[2]; + bgra_t colors[2]; // use LoRes palette background += 12; foreground += 12; @@ -1147,11 +1147,11 @@ void UpdateDuochromeCell(int h, int w, bgra_t* pVideoAddress, uint8_t bits, uint { int bit = (bits & 1); bits >>= 1; - const RGBQUAD& rRGB = colors[bit]; - *(pDst + nBytes) = *reinterpret_cast(&rRGB); + const bgra_t& rRGB = colors[bit]; + *(pDst + nBytes) = rRGB; if (doublepixels) { - *(pDst + nBytes + 1) = *reinterpret_cast(&rRGB); + *(pDst + nBytes + 1) = rRGB; } } } diff --git a/source/Video.cpp b/source/Video.cpp index 38aa83ed4..2056e2b88 100644 --- a/source/Video.cpp +++ b/source/Video.cpp @@ -872,7 +872,7 @@ void Video::VideoRefreshBuffer(uint32_t uRedrawWholeScreenVideoMode, bool bRedra void Video::ClearFrameBuffer() { - UINT32* frameBuffer = (UINT32*)GetFrameBuffer(); + bgra_t* frameBuffer = (bgra_t*)GetFrameBuffer(); std::fill(frameBuffer, frameBuffer + GetFrameBufferWidth() * GetFrameBufferHeight(), OPAQUE_BLACK); } diff --git a/source/Video.h b/source/Video.h index b60421430..be381e032 100644 --- a/source/Video.h +++ b/source/Video.h @@ -5,10 +5,37 @@ // Windows // SDL (SDL_PIXELFORMAT_ARGB8888) // Qt (QImage::Format_ARGB32_Premultiplied) +// TODO: Replace with WinGDI.h / RGBQUAD + +struct bgra_t +{ +#ifdef __EMSCRIPTEN__ + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; // reserved on Win32 +#else + uint8_t b; + uint8_t g; + uint8_t r; + uint8_t a; // reserved on Win32 +#endif +}; + +constexpr bgra_t makeBGRA(uint8_t b, uint8_t g, uint8_t r, uint8_t a) +{ + bgra_t bgra{}; + bgra.b = b; + bgra.g = g; + bgra.r = r; + bgra.a = a; + return bgra; +} + #define ALPHA 0xFF // fully opaque #define ALPHA32_MASK (ALPHA << 24) -#define SETRGBCOLOR(r, g, b) {b, g, r, ALPHA} // to create a RGBQUAD -#define OPAQUE_BLACK (0 | ALPHA32_MASK) // Black is RGB 0 0 0 +#define SETRGBCOLOR(r, g, b) makeBGRA(b, g, r, ALPHA) +#define OPAQUE_BLACK SETRGBCOLOR(0, 0, 0) // Black is RGB 0 0 0 // Types ____________________________________________________________ @@ -97,15 +124,6 @@ enum AppleFont_e // turn on struct member padding #pragma pack(push,1) -// TODO: Replace with WinGDI.h / RGBQUAD -struct bgra_t -{ - uint8_t b; - uint8_t g; - uint8_t r; - uint8_t a; // reserved on Win32 -}; - struct WinBmpHeader_t { // BITMAPFILEHEADER // Addr Size diff --git a/source/frontends/common2/CMakeLists.txt b/source/frontends/common2/CMakeLists.txt index 7134b5633..6f8446eec 100644 --- a/source/frontends/common2/CMakeLists.txt +++ b/source/frontends/common2/CMakeLists.txt @@ -20,6 +20,7 @@ set(HEADER_FILES commonframe.h commoncontext.h controllerdoublepress.h + emscriptenpaths.h gnuframe.h fileregistry.h ptreeregistry.h diff --git a/source/frontends/common2/emscriptenpaths.h b/source/frontends/common2/emscriptenpaths.h new file mode 100644 index 000000000..4260a4f16 --- /dev/null +++ b/source/frontends/common2/emscriptenpaths.h @@ -0,0 +1,15 @@ +#pragma once + +#ifdef __EMSCRIPTEN__ + +namespace common2 +{ + namespace emscripten + { + constexpr const char *STORAGE = "/storage"; // IDBFS persistent mount (SDL3) + constexpr const char *DEFAULTS = "/defaults"; // read-only preloaded bundle + constexpr const char *DISKS = "/disks"; // disk images & symbols + } // namespace emscripten +} // namespace common2 + +#endif diff --git a/source/frontends/common2/fileregistry.cpp b/source/frontends/common2/fileregistry.cpp index 1960e59a0..288a96499 100644 --- a/source/frontends/common2/fileregistry.cpp +++ b/source/frontends/common2/fileregistry.cpp @@ -36,6 +36,9 @@ namespace std::string getLocation() const override; + protected: + void synchronise() override; + private: const std::filesystem::path myFilename; }; @@ -53,6 +56,17 @@ namespace } } + void Configuration::synchronise() + { + try + { + saveToYamlFile(myFilename.string()); + } + catch (const std::exception &) + { + } + } + Configuration::~Configuration() { try diff --git a/source/frontends/common2/gnuframe.cpp b/source/frontends/common2/gnuframe.cpp index 388f7bdea..da7b7bacd 100644 --- a/source/frontends/common2/gnuframe.cpp +++ b/source/frontends/common2/gnuframe.cpp @@ -1,6 +1,7 @@ #include "StdAfx.h" #include "frontends/common2/gnuframe.h" #include "frontends/common2/utils.h" +#include "frontends/common2/emscriptenpaths.h" #include "apple2roms_data.h" #ifdef __APPLE__ @@ -76,7 +77,11 @@ namespace common2 : CommonFrame(options) { // should this go down to LinuxFrame (maybe Initialisation?) +#ifndef __EMSCRIPTEN__ g_sProgramDir = getResourceFolder("bin").string() + PATH_SEPARATOR; +#else + g_sProgramDir = std::string(emscripten::DISKS) + PATH_SEPARATOR; +#endif LogFileOutput("Program Dir: '%s'\n", g_sProgramDir.c_str()); } diff --git a/source/frontends/common2/ptreeregistry.cpp b/source/frontends/common2/ptreeregistry.cpp index 474892e36..e50bd9bb9 100644 --- a/source/frontends/common2/ptreeregistry.cpp +++ b/source/frontends/common2/ptreeregistry.cpp @@ -34,11 +34,13 @@ namespace common2 void PTreeRegistry::putString(const std::string §ion, const std::string &key, const std::string &value) { myData[section][key] = value; + synchronise(); } void PTreeRegistry::putDWord(const std::string §ion, const std::string &key, const uint32_t value) { myData[section][key] = std::to_string(value); + synchronise(); } const std::map> &PTreeRegistry::getAllValues() const diff --git a/source/frontends/common2/utils.cpp b/source/frontends/common2/utils.cpp index 73fd8d5eb..bc846f876 100644 --- a/source/frontends/common2/utils.cpp +++ b/source/frontends/common2/utils.cpp @@ -1,19 +1,34 @@ #include "StdAfx.h" #include "frontends/common2/utils.h" #include "frontends/common2/programoptions.h" +#include "frontends/common2/emscriptenpaths.h" #include "SaveState.h" #include "Registry.h" +#include + namespace { - std::string getEnvOrDefault(const char *var, const char *fallback = nullptr) + std::optional tryGetEnv(const char *var) { const char *value = getenv(var); if (value) { - return value; + std::cout << "Environment variable " << var << " = " << value << std::endl; + return std::string(value); + } + std::cout << "Environment variable " << var << " missing" << std::endl; + return std::nullopt; + } + + std::string getEnvOrDefault(const char *var, const char *fallback = nullptr) + { + std::optional value = tryGetEnv(var); + if (value.has_value()) + { + return *value; } if (fallback) { @@ -33,11 +48,21 @@ namespace common2 const std::string profile = getEnvOrDefault("LOCALAPPDATA"); return profile; #else + +#ifdef __EMSCRIPTEN__ + const std::optional xdgConfigHome = std::string(emscripten::STORAGE) + "/.config"; +#else // https://specifications.freedesktop.org/basedir-spec/latest/ - const std::filesystem::path home = getHomeDir(); - const std::filesystem::path config = getEnvOrDefault("XDG_CONFIG_HOME", ".config"); + const std::optional xdgConfigHome = tryGetEnv("XDG_CONFIG_HOME"); +#endif - return home / config; + if (xdgConfigHome.has_value()) + { + return *xdgConfigHome; + } + + const std::filesystem::path home = getHomeDir(); + return home / ".config"; #endif } diff --git a/source/frontends/sdl/CMakeLists.txt b/source/frontends/sdl/CMakeLists.txt index b2764c2dd..dd76bdc45 100644 --- a/source/frontends/sdl/CMakeLists.txt +++ b/source/frontends/sdl/CMakeLists.txt @@ -10,14 +10,21 @@ option(SA2_SDL3 "Use SDL3" OFF) if (SA2_SDL3) message("sa2: using SDL3") + find_package(SDL3 REQUIRED) - find_package(SDL3_image REQUIRED) target_link_libraries(sa2 PRIVATE SDL3::SDL3 - SDL3_image::SDL3_image ) + if (NOT EMSCRIPTEN) + find_package(SDL3_image REQUIRED) + + target_link_libraries(sa2 PRIVATE + SDL3_image::SDL3_image + ) + endif() + target_sources(sa2 PRIVATE ${IMGUI_PATH}/backends/imgui_impl_sdl3.cpp ) @@ -25,6 +32,11 @@ if (SA2_SDL3) target_compile_definitions(sa2 PRIVATE SDL_ENABLE_OLD_NAMES) else() message("sa2: using SDL2") + + if (EMSCRIPTEN) + message(FATAL_ERROR "Emscripten only supports SDL3") + endif() + find_package(SDL2 REQUIRED) # we should use find_package, but Ubuntu does not provide it for SDL2_image pkg_search_module(SDL2_IMAGE REQUIRED SDL2_image) @@ -155,6 +167,23 @@ target_compile_definitions(sa2 PRIVATE IMGUI_USER_CONFIG="frontends/sdl/imgui/sa2_imconfig.h" ) +if (EMSCRIPTEN) + set_target_properties(sa2 PROPERTIES + SUFFIX ".html" + ) + + set(EMSC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/emsc) + + target_link_options(sa2 PRIVATE + # --emrun + -sFULL_ES3=1 + -sMIN_WEBGL_VERSION=2 + "SHELL:-lidbfs.js" + "SHELL:--shell-file ${EMSC_DIR}/src/a2e.html" + "SHELL:--preload-file ${EMSC_DIR}/fs@/" + ) +endif() + configure_file(sa2_config.h.in sa2_config.h) install(TARGETS sa2 diff --git a/source/frontends/sdl/emsc/fs/defaults/.config/applewin/applewin.yaml b/source/frontends/sdl/emsc/fs/defaults/.config/applewin/applewin.yaml new file mode 100644 index 000000000..4fe5b039c --- /dev/null +++ b/source/frontends/sdl/emsc/fs/defaults/.config/applewin/applewin.yaml @@ -0,0 +1,21 @@ +--- +Configuration: + Emulation Speed: 10 +Configuration\Slot 0: + Card type: 17 +Configuration\Slot 1: + Card type: 0 +Configuration\Slot 2: + Card type: 0 +Configuration\Slot 3: + Card type: 0 + Uthernet Interface: +Configuration\Slot 6: + Card type: 1 + Last Disk Image 1: /disks/DOS 3.3 System Master - 680-0210-A.dsk + Last Disk Image 2: /disks/ProDOS_2_4_3.po +Configuration\Slot Auxiliary: + Card type: 13 + Number of Banks: 1 +Preferences: + Starting Directory: /disks/ diff --git a/source/frontends/sdl/emsc/fs/defaults/.config/applewin/imgui.ini b/source/frontends/sdl/emsc/fs/defaults/.config/applewin/imgui.ini new file mode 100644 index 000000000..962fd9dcf --- /dev/null +++ b/source/frontends/sdl/emsc/fs/defaults/.config/applewin/imgui.ini @@ -0,0 +1,24 @@ +[Window][Debug##Default] +Pos=60,60 +Size=400,400 + +[Window][Shortcuts] +Pos=60,60 +Size=1148,656 + +[Window][Settings] +Pos=60,60 +Size=1199,504 + +[Window][Memory viewer] +Pos=60,60 +Size=772,492 + +[Window][Memory editor] +Pos=60,60 +Size=924,672 + +[Window][Debugger] +Pos=60,60 +Size=1077,799 + diff --git a/source/frontends/sdl/emsc/fs/disks/A2_BASIC.SYM b/source/frontends/sdl/emsc/fs/disks/A2_BASIC.SYM new file mode 120000 index 000000000..58a5a6f59 --- /dev/null +++ b/source/frontends/sdl/emsc/fs/disks/A2_BASIC.SYM @@ -0,0 +1 @@ +../../../../../../bin/A2_BASIC.SYM \ No newline at end of file diff --git a/source/frontends/sdl/emsc/fs/disks/APPLE2E.SYM b/source/frontends/sdl/emsc/fs/disks/APPLE2E.SYM new file mode 120000 index 000000000..dbd033795 --- /dev/null +++ b/source/frontends/sdl/emsc/fs/disks/APPLE2E.SYM @@ -0,0 +1 @@ +../../../../../../bin/APPLE2E.SYM \ No newline at end of file diff --git a/source/frontends/sdl/emsc/fs/disks/BLANK.DSK b/source/frontends/sdl/emsc/fs/disks/BLANK.DSK new file mode 120000 index 000000000..6fa5df75e --- /dev/null +++ b/source/frontends/sdl/emsc/fs/disks/BLANK.DSK @@ -0,0 +1 @@ +../../../../../../bin/BLANK.DSK \ No newline at end of file diff --git a/source/frontends/sdl/emsc/fs/disks/DOS 3.3 System Master - 680-0210-A.dsk b/source/frontends/sdl/emsc/fs/disks/DOS 3.3 System Master - 680-0210-A.dsk new file mode 120000 index 000000000..699f90a72 --- /dev/null +++ b/source/frontends/sdl/emsc/fs/disks/DOS 3.3 System Master - 680-0210-A.dsk @@ -0,0 +1 @@ +../../../../../../bin/DOS 3.3 System Master - 680-0210-A.dsk \ No newline at end of file diff --git a/source/frontends/sdl/emsc/fs/disks/applewriterii.dsk b/source/frontends/sdl/emsc/fs/disks/applewriterii.dsk new file mode 100644 index 000000000..7ca75546d Binary files /dev/null and b/source/frontends/sdl/emsc/fs/disks/applewriterii.dsk differ diff --git a/source/frontends/sdl/emsc/src/a2e.html b/source/frontends/sdl/emsc/src/a2e.html new file mode 100644 index 000000000..790894b23 --- /dev/null +++ b/source/frontends/sdl/emsc/src/a2e.html @@ -0,0 +1,36 @@ + + + + + + AppleWin Emscripten + + + + + + + + + {{{ SCRIPT }}} + + + + \ No newline at end of file diff --git a/source/frontends/sdl/imgui/glselector.h b/source/frontends/sdl/imgui/glselector.h index 39bb7a0eb..d8e347a85 100644 --- a/source/frontends/sdl/imgui/glselector.h +++ b/source/frontends/sdl/imgui/glselector.h @@ -2,7 +2,20 @@ #include "frontends/sdl/sdlcompat.h" -#if defined(IMGUI_IMPL_OPENGL_ES2) +#if defined __EMSCRIPTEN__ + +#include + +#define SA2_CONTEXT_FLAGS 0 +#define SA2_CONTEXT_PROFILE_MASK SDL_GL_CONTEXT_PROFILE_ES +#define SA2_CONTEXT_MAJOR_VERSION 3 +#define SA2_CONTEXT_MINOR_VERSION 0 + +// this is defined in gl2ext.h and nowhere in gl3.h +#define SA2_IMAGE_FORMAT_INTERNAL GL_RGBA8 +#define SA2_IMAGE_FORMAT GL_RGBA + +#elif defined(IMGUI_IMPL_OPENGL_ES2) // Pi3 with Fake KMS // "OpenGL ES 2.0 Mesa 19.3.2" @@ -17,7 +30,7 @@ #define SA2_CONTEXT_MINOR_VERSION 0 // this is defined in gl2ext.h and nowhere in gl3.h -#define SA2_IMAGE_FORMAT_INTERNAL GL_BGRA_EXT +#define SA2_IMAGE_FORMAT_INTERNAL GL_RGBA #define SA2_IMAGE_FORMAT GL_BGRA_EXT #elif defined(IMGUI_IMPL_OPENGL_ES3) @@ -33,7 +46,14 @@ // "310 es" is accepted on a Pi4, but the imgui shaders do not compile #include -#include + +#ifndef GL_BGRA +#ifdef GL_BGRA_EXT +#define GL_BGRA GL_BGRA_EXT +#else +#define GL_BGRA 0x80E1 +#endif +#endif #define SA2_CONTEXT_FLAGS 0 #define SA2_CONTEXT_PROFILE_MASK SDL_GL_CONTEXT_PROFILE_ES @@ -41,8 +61,8 @@ #define SA2_CONTEXT_MINOR_VERSION 0 // this is defined in gl2ext.h and nowhere in gl3.h -#define SA2_IMAGE_FORMAT_INTERNAL GL_BGRA_EXT -#define SA2_IMAGE_FORMAT GL_BGRA_EXT +#define SA2_IMAGE_FORMAT_INTERNAL GL_RGBA8 +#define SA2_IMAGE_FORMAT GL_BGRA #elif defined(__APPLE__) @@ -51,7 +71,7 @@ #define SA2_CONTEXT_MAJOR_VERSION 3 #define SA2_CONTEXT_MINOR_VERSION 2 -#define SA2_IMAGE_FORMAT_INTERNAL GL_RGBA +#define SA2_IMAGE_FORMAT_INTERNAL GL_RGBA8 #define SA2_IMAGE_FORMAT GL_BGRA #else @@ -61,7 +81,7 @@ #define SA2_CONTEXT_MAJOR_VERSION 3 #define SA2_CONTEXT_MINOR_VERSION 2 -#define SA2_IMAGE_FORMAT_INTERNAL GL_RGBA +#define SA2_IMAGE_FORMAT_INTERNAL GL_RGBA8 #define SA2_IMAGE_FORMAT GL_BGRA #endif diff --git a/source/frontends/sdl/imgui/image.cpp b/source/frontends/sdl/imgui/image.cpp index da32f5cb0..5adcf51f5 100644 --- a/source/frontends/sdl/imgui/image.cpp +++ b/source/frontends/sdl/imgui/image.cpp @@ -30,24 +30,29 @@ namespace sa2 void allocateTexture(GLuint texture, size_t width, size_t height) { glBindTexture(GL_TEXTURE_2D, texture); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - const GLenum type = GL_UNSIGNED_BYTE; - glTexImage2D(GL_TEXTURE_2D, 0, SA2_IMAGE_FORMAT_INTERNAL, width, height, 0, SA2_IMAGE_FORMAT, type, nullptr); - } - - void loadTextureFromData(GLuint texture, const uint8_t *data, size_t width, size_t height, size_t pitch) - { - glBindTexture(GL_TEXTURE_2D, texture); - glPixelStorei(UGL_UNPACK_LENGTH, pitch); // in pixels +#if defined(__EMSCRIPTEN__) || defined(GL_ES_VERSION_3_0) + glTexStorage2D(GL_TEXTURE_2D, 1, SA2_IMAGE_FORMAT_INTERNAL, width, height); +#else + glTexImage2D( + GL_TEXTURE_2D, 0, SA2_IMAGE_FORMAT_INTERNAL, width, height, 0, SA2_IMAGE_FORMAT, GL_UNSIGNED_BYTE, nullptr); +#endif // Setup filtering parameters for display glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + void loadTextureFromData(GLuint texture, const uint8_t *data, size_t width, size_t height, size_t pitchPixels) + { + glBindTexture(GL_TEXTURE_2D, texture); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - const GLenum type = GL_UNSIGNED_BYTE; - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, SA2_IMAGE_FORMAT, type, data); + glPixelStorei(UGL_UNPACK_LENGTH, pitchPixels); // in pixels + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, SA2_IMAGE_FORMAT, GL_UNSIGNED_BYTE, data); // reset to default state glPixelStorei(UGL_UNPACK_LENGTH, 0); } diff --git a/source/frontends/sdl/imgui/sdlimguiframe.cpp b/source/frontends/sdl/imgui/sdlimguiframe.cpp index 037fa7304..c2eb4ac3a 100644 --- a/source/frontends/sdl/imgui/sdlimguiframe.cpp +++ b/source/frontends/sdl/imgui/sdlimguiframe.cpp @@ -238,6 +238,17 @@ namespace sa2 if (!myPresenting) { myPresenting = true; + + int w, h; + +#if SDL_VERSION_ATLEAST(3, 0, 0) + SDL_GetWindowSizeInPixels(myWindow.get(), &w, &h); +#else + SDL_GL_GetDrawableSize(myWindow.get(), &w, &h); +#endif + + glViewport(0, 0, w, h); + ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplSDLX_NewFrame(); ImGui::NewFrame(); diff --git a/source/frontends/sdl/imgui/sdlsettings.cpp b/source/frontends/sdl/imgui/sdlsettings.cpp index fe997f1c1..78d780a17 100644 --- a/source/frontends/sdl/imgui/sdlsettings.cpp +++ b/source/frontends/sdl/imgui/sdlsettings.cpp @@ -861,6 +861,9 @@ namespace sa2 ImGui::Text("GL Swap: %d", glSwap); ImGuiIO &io = ImGui::GetIO(); ImGui::Text("FPS: %d", int(io.Framerate)); + + ImGui::Separator(); + ImGui::Text("Built: %s %s", __DATE__, __TIME__); } ImGui::End(); diff --git a/source/frontends/sdl/processfile.cpp b/source/frontends/sdl/processfile.cpp index bc0fc7f1c..f371fc11a 100644 --- a/source/frontends/sdl/processfile.cpp +++ b/source/frontends/sdl/processfile.cpp @@ -1,5 +1,6 @@ #include "StdAfx.h" #include "frontends/sdl/processfile.h" +#include "frontends/common2/emscriptenpaths.h" #include "frontends/sdl/sdlframe.h" #include "frontends/common2/utils.h" @@ -138,7 +139,28 @@ namespace sa2 } else { +#ifdef __EMSCRIPTEN__ + // copy disk image to persistent storatge so the config path survises page reloads + const std::filesystem::path sourcePath = filename; + const std::filesystem::path destPath = + std::filesystem::path(common2::emscripten::STORAGE) / "disks" / sourcePath.filename(); + + std::error_code ec; + std::filesystem::create_directories(destPath.parent_path(), ec); + std::filesystem::copy_file(sourcePath, destPath, std::filesystem::copy_options::overwrite_existing, ec); + + if (ec) + { + LogOutput("Failed to copy disk image to persistent storage: %s\n", ec.message().c_str()); + insertDisk(frame, filename, dragAndDropSlot, dragAndDropDrive); + } + else + { + insertDisk(frame, destPath.string().c_str(), dragAndDropSlot, dragAndDropDrive); + } +#else insertDisk(frame, filename, dragAndDropSlot, dragAndDropDrive); +#endif } frame->ResetSpeed(); } diff --git a/source/frontends/sdl/sdlappmain.cpp b/source/frontends/sdl/sdlappmain.cpp index 15a715562..c7bbe9db2 100644 --- a/source/frontends/sdl/sdlappmain.cpp +++ b/source/frontends/sdl/sdlappmain.cpp @@ -4,6 +4,7 @@ #include "linux/version.h" #include "frontends/common2/fileregistry.h" +#include "frontends/common2/emscriptenpaths.h" #include "frontends/common2/commoncontext.h" #include "frontends/common2/argparser.h" #include "frontends/common2/programoptions.h" @@ -29,6 +30,40 @@ namespace { +#ifdef __EMSCRIPTEN__ + void setupPersistentConfig() + { + namespace fs = std::filesystem; + + const fs::path storagePath = std::string(common2::emscripten::STORAGE) + "/.config/applewin"; + const fs::path defaultPath = std::string(common2::emscripten::DEFAULTS) + "/.config/applewin"; + const std::vector configFiles = {"applewin.yaml", "imgui.ini"}; + + // Ensure the destination directory exists in IDBFS + if (!fs::exists(storagePath)) + { + fs::create_directories(storagePath); + } + + for (const auto &file : configFiles) + { + const auto dst = storagePath / file; + const auto src = defaultPath / file; + + if (!fs::exists(dst)) + { + std::cerr << "Restoring default config: " << file << std::endl; + std::error_code ec; + fs::copy_file(src, dst, fs::copy_options::overwrite_existing, ec); + if (ec) + { + std::cerr << "Failed to copy " << file << ": " << ec.message() << std::endl; + } + } + } + } +#endif + int getRefreshRate() { SDL_DisplayMode dummy; @@ -61,6 +96,10 @@ namespace myFrame = std::make_shared(options); } +#ifdef __EMSCRIPTEN__ + SDL_SetHint(SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT, "#canvas"); +#endif + std::cerr << "GL swap interval: " << sa2::compat::getGLSwapInterval() << std::endl; myInit = std::make_unique(myFrame, myPaddle, options); @@ -199,6 +238,11 @@ extern "C" const std::string version = getVersion(); SDL_SetAppMetadata("AppleWin", version.c_str(), "org.applewin"); #endif + +#ifdef __EMSCRIPTEN__ + setupPersistentConfig(); +#endif + return SDL_AppCreate(appstate, argc, argv); } catch (const std::exception &e) diff --git a/source/frontends/sdl/sdlcompat.cpp b/source/frontends/sdl/sdlcompat.cpp index d6c46c83c..dc463d65a 100644 --- a/source/frontends/sdl/sdlcompat.cpp +++ b/source/frontends/sdl/sdlcompat.cpp @@ -87,12 +87,13 @@ namespace sa2 SDL_Renderer *createRenderer(SDL_Window *window, const int index) { - // I am not sure whether we should worry about SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER - const char *name = index >= 0 ? SDL_GetRenderDriver(index) : nullptr; - SDL_PropertiesID props = SDL_CreateProperties(); SDL_SetPointerProperty(props, SDL_PROP_RENDERER_CREATE_WINDOW_POINTER, window); + +#ifndef __EMSCRIPTEN__ SDL_SetBooleanProperty(props, SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER, true); +#endif + if (index >= 0) { SDL_SetStringProperty(props, SDL_PROP_RENDERER_CREATE_NAME_STRING, SDL_GetRenderDriver(index)); @@ -103,6 +104,7 @@ namespace sa2 return renderer; } +#ifndef __EMSCRIPTEN__ SDL_Surface *createSurfaceFromResource(const unsigned char *data, unsigned int size) { SDL_IOStream *ops = SDL_IOFromConstMem(data, size); @@ -117,6 +119,7 @@ namespace sa2 } return surface; } +#endif bool convertAudio( const SDL_AudioSpec &wavSpec, const Uint8 *wavBuffer, const Uint32 wavLength, std::vector &output) diff --git a/source/frontends/sdl/sdlcompat.h b/source/frontends/sdl/sdlcompat.h index a3c625a46..64e7e0586 100644 --- a/source/frontends/sdl/sdlcompat.h +++ b/source/frontends/sdl/sdlcompat.h @@ -11,7 +11,10 @@ #include #include + +#ifndef __EMSCRIPTEN__ #include +#endif #define ImGui_ImplSDLX_InitForOpenGL ImGui_ImplSDL3_InitForOpenGL #define ImGui_ImplSDLX_Shutdown ImGui_ImplSDL3_Shutdown diff --git a/source/frontends/sdl/sdlframe.cpp b/source/frontends/sdl/sdlframe.cpp index 067adbec7..97e067b42 100644 --- a/source/frontends/sdl/sdlframe.cpp +++ b/source/frontends/sdl/sdlframe.cpp @@ -241,6 +241,7 @@ namespace sa2 void SDLFrame::SetApplicationIcon() { +#ifndef __EMSCRIPTEN__ const auto resource = GetResourceData(IDC_APPLEWIN_ICON); std::shared_ptr icon( @@ -249,6 +250,7 @@ namespace sa2 { SDL_SetWindowIcon(myWindow.get(), icon.get()); } +#endif } const std::shared_ptr &SDLFrame::GetWindow() const @@ -409,6 +411,7 @@ namespace sa2 void SDLFrame::ProcessDropEvent(const SDL_DropEvent &drop) { const auto file = SA2_DROP_FILE(drop); + LogOutput("File dropped: %s\n", file); processFile(this, file, myDragAndDropSlot, myDragAndDropDrive); sa2::compat::maybeSDLfree(file); } diff --git a/source/linux/libwindows/misc.h b/source/linux/libwindows/misc.h index 62f6fa459..a08e164a7 100644 --- a/source/linux/libwindows/misc.h +++ b/source/linux/libwindows/misc.h @@ -29,7 +29,9 @@ #define IDYES 6 #define IDNO 7 +#ifndef EINVAL #define EINVAL 22 +#endif BOOL WINAPI PostMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); int MessageBox(HWND, const char *, const char *, UINT); diff --git a/source/linux/registryclass.cpp b/source/linux/registryclass.cpp index c32146161..c81801b88 100644 --- a/source/linux/registryclass.cpp +++ b/source/linux/registryclass.cpp @@ -68,3 +68,8 @@ void RegSaveValue(LPCTSTR section, LPCTSTR key, bool peruser, uint32_t value) Registry::instance->putDWord(section, key, value); LogFileOutput("RegSaveValue: %s - %s = %d\n", section, key, value); } + +void Registry::synchronise() +{ + +} diff --git a/source/linux/registryclass.h b/source/linux/registryclass.h index a0b2309f6..a54738231 100644 --- a/source/linux/registryclass.h +++ b/source/linux/registryclass.h @@ -22,4 +22,6 @@ class Registry virtual const std::map> &getAllValues() const = 0; virtual std::string getLocation() const = 0; + + virtual void synchronise(); };