From d335f0a439a86daaae64162da9441251ba0f8bae Mon Sep 17 00:00:00 2001 From: Andrea Odetti Date: Sun, 21 Dec 2025 19:55:14 +0000 Subject: [PATCH 01/10] Rename imconfig.h -> sa2_config.h to avoid clashes. Signed-off-by: Andrea Odetti --- source/frontends/sdl/CMakeLists.txt | 4 ++-- source/frontends/sdl/imgui/{imconfig.h => sa2_imconfig.h} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename source/frontends/sdl/imgui/{imconfig.h => sa2_imconfig.h} (100%) diff --git a/source/frontends/sdl/CMakeLists.txt b/source/frontends/sdl/CMakeLists.txt index 017645f1a..19364d254 100644 --- a/source/frontends/sdl/CMakeLists.txt +++ b/source/frontends/sdl/CMakeLists.txt @@ -94,7 +94,7 @@ target_sources(sa2 PRIVATE imgui/sdlsettings.h imgui/sdldebugger.h imgui/sdlmemory.h - imgui/imconfig.h + imgui/sa2_imconfig.h imgui/glselector.h imgui/inputtexthistory.h imgui/cycletabitems.h @@ -118,7 +118,7 @@ target_include_directories(sa2 PRIVATE ) target_compile_definitions(sa2 PRIVATE - IMGUI_USER_CONFIG="frontends/sdl/imgui/imconfig.h" + IMGUI_USER_CONFIG="frontends/sdl/imgui/sa2_imconfig.h" ) install(TARGETS sa2 diff --git a/source/frontends/sdl/imgui/imconfig.h b/source/frontends/sdl/imgui/sa2_imconfig.h similarity index 100% rename from source/frontends/sdl/imgui/imconfig.h rename to source/frontends/sdl/imgui/sa2_imconfig.h From 39658e38cde7be93b644c26c5d228bdc22a96e29 Mon Sep 17 00:00:00 2001 From: Andrea Odetti Date: Sun, 21 Dec 2025 19:56:02 +0000 Subject: [PATCH 02/10] Refresh sa2_imconfig.h Signed-off-by: Andrea Odetti --- source/frontends/sdl/imgui/sa2_imconfig.h | 61 ++++++++++++++++------- 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/source/frontends/sdl/imgui/sa2_imconfig.h b/source/frontends/sdl/imgui/sa2_imconfig.h index 1586d3cbc..a6e1d1c75 100644 --- a/source/frontends/sdl/imgui/sa2_imconfig.h +++ b/source/frontends/sdl/imgui/sa2_imconfig.h @@ -1,7 +1,7 @@ // clang-format off //----------------------------------------------------------------------------- -// COMPILE-TIME OPTIONS FOR DEAR IMGUI +// DEAR IMGUI COMPILE-TIME OPTIONS // Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure. // You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions. //----------------------------------------------------------------------------- @@ -11,62 +11,74 @@ // You need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include the imgui*.cpp // files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures. // Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts. -// Call IMGUI_CHECKVERSION() from your .cpp files to verify that the data structures your files are using are matching the ones imgui.cpp is using. +// Call IMGUI_CHECKVERSION() from your .cpp file to verify that the data structures your files are using are matching the ones imgui.cpp is using. //----------------------------------------------------------------------------- #pragma once //---- Define assertion handler. Defaults to calling assert(). -// If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement. +// - If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement. +// - Compiling with NDEBUG will usually strip out assert() to nothing, which is NOT recommended because we use asserts to notify of programmer mistakes. //#define IM_ASSERT(_EXPR) MyAssert(_EXPR) //#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts //---- Define attributes of all API symbols declarations, e.g. for DLL under Windows // Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility. -// DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions() -// for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for more details. -//#define IMGUI_API __declspec( dllexport ) -//#define IMGUI_API __declspec( dllimport ) +// - Windows DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions() +// for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for more details. +//#define IMGUI_API __declspec(dllexport) // MSVC Windows: DLL export +//#define IMGUI_API __declspec(dllimport) // MSVC Windows: DLL import +//#define IMGUI_API __attribute__((visibility("default"))) // GCC/Clang: override visibility when set is hidden -//---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to avoid using soon-to-be obsolete function/names. -// #define IMGUI_DISABLE_OBSOLETE_FUNCTIONS -// #define IMGUI_DISABLE_OBSOLETE_KEYIO // 1.87: disable legacy io.KeyMap[]+io.KeysDown[] in favor io.AddKeyEvent(). This will be folded into IMGUI_DISABLE_OBSOLETE_FUNCTIONS in a few versions. +//---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to clean your code of obsolete function/names. +//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS //---- Disable all of Dear ImGui or don't implement standard windows/tools. // It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp. //#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty. //#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty. -//#define IMGUI_DISABLE_DEBUG_TOOLS // Disable metrics/debugger and other debug tools: ShowMetricsWindow(), ShowDebugLogWindow() and ShowStackToolWindow() will be empty (this was called IMGUI_DISABLE_METRICS_WINDOW before 1.88). +//#define IMGUI_DISABLE_DEBUG_TOOLS // Disable metrics/debugger and other debug tools: ShowMetricsWindow(), ShowDebugLogWindow() and ShowIDStackToolWindow() will be empty. //---- Don't implement some functions to reduce linkage requirements. //#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. (user32.lib/.a, kernel32.lib/.a) //#define IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with Visual Studio] Implement default IME handler (require imm32.lib/.a, auto-link for Visual Studio, -limm32 on command-line for MinGW) //#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with non-Visual Studio compilers] Don't implement default IME handler (won't require imm32.lib/.a) -//#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, ime). +//#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, IME). //#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default). +//#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS // Don't implement default platform_io.Platform_OpenInShellFn() handler (Win32: ShellExecute(), require shell32.lib/.a, Mac/Linux: use system("")). //#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf) //#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself. //#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies) //#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function. //#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions(). +//#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded font (ProggyClean.ttf), remove ~9.5 KB from output binary. AddFontDefault() will assert. //#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available +//---- Enable Test Engine / Automation features. +//#define IMGUI_ENABLE_TEST_ENGINE // Enable imgui_test_engine hooks. Generally set automatically by include "imgui_te_config.h", see Test Engine for details. + //---- Include imgui_user.h at the end of imgui.h as a convenience +// May be convenient for some users to only explicitly include vanilla imgui.h and have extra stuff included. //#define IMGUI_INCLUDE_IMGUI_USER_H +//#define IMGUI_USER_H_FILENAME "my_folder/my_imgui_user.h" -//---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another) +//---- Pack vertex colors as BGRA8 instead of RGBA8 (to avoid converting from one to another). Need dedicated backend support. //#define IMGUI_USE_BGRA_PACKED_COLOR -//---- Use 32-bit for ImWchar (default is 16-bit) to support unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...) +//---- Use legacy CRC32-adler tables (used before 1.91.6), in order to preserve old .ini data that you cannot afford to invalidate. +//#define IMGUI_USE_LEGACY_CRC32_ADLER + +//---- Use 32-bit for ImWchar (default is 16-bit) to support Unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...) //#define IMGUI_USE_WCHAR32 //---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version // By default the embedded implementations are declared static and not available outside of Dear ImGui sources files. //#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h" //#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h" -//#define IMGUI_STB_SPRINTF_FILENAME "my_folder/stb_sprintf.h" // only used if enabled +//#define IMGUI_STB_SPRINTF_FILENAME "my_folder/stb_sprintf.h" // only used if IMGUI_USE_STB_SPRINTF is defined. //#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION //#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION +//#define IMGUI_DISABLE_STB_SPRINTF_IMPLEMENTATION // only disabled if IMGUI_USE_STB_SPRINTF is defined. //---- Use stb_sprintf.h for a faster implementation of vsnprintf instead of the one from libc (unless IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS is defined) // Compatibility checks of arguments and formats done by clang and GCC will be disabled in order to support the extra formats provided by stb_sprintf.h. @@ -74,9 +86,18 @@ //---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui) // Requires FreeType headers to be available in the include path. Requires program to be compiled with 'misc/freetype/imgui_freetype.cpp' (in this repository) + the FreeType library (not provided). +// Note that imgui_freetype.cpp may be used _without_ this define, if you manually call ImFontAtlas::SetFontLoader(). The define is simply a convenience. // On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'. //#define IMGUI_ENABLE_FREETYPE +//---- Use FreeType + plutosvg or lunasvg to render OpenType SVG fonts (SVGinOT) +// Only works in combination with IMGUI_ENABLE_FREETYPE. +// - plutosvg is currently easier to install, as e.g. it is part of vcpkg. It will support more fonts and may load them faster. See misc/freetype/README for instructions. +// - Both require headers to be available in the include path + program to be linked with the library code (not provided). +// - (note: lunasvg implementation is based on Freetype's rsvg-port.c which is licensed under CeCILL-C Free Software License Agreement) +//#define IMGUI_ENABLE_FREETYPE_PLUTOSVG +//#define IMGUI_ENABLE_FREETYPE_LUNASVG + //---- Use stb_truetype to build and rasterize the font atlas (default) // The only purpose of this define is if you want force compilation of the stb_truetype backend ALONG with the FreeType backend. //#define IMGUI_ENABLE_STB_TRUETYPE @@ -107,19 +128,23 @@ //typedef void (*MyImDrawCallback)(const ImDrawList* draw_list, const ImDrawCmd* cmd, void* my_renderer_user_data); //#define ImDrawCallback MyImDrawCallback -//---- Debug Tools: Macro to break in Debugger +//---- Debug Tools: Macro to break in Debugger (we provide a default implementation of this in the codebase) // (use 'Metrics->Tools->Item Picker' to pick widgets with the mouse and break into them for easy debugging.) //#define IM_DEBUG_BREAK IM_ASSERT(0) //#define IM_DEBUG_BREAK __debugbreak() +//---- Debug Tools: Enable highlight ID conflicts _before_ hovering items. When io.ConfigDebugHighlightIdConflicts is set. +// (THIS WILL SLOW DOWN DEAR IMGUI. Only use occasionally and disable after use) +//#define IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS + //---- Debug Tools: Enable slower asserts //#define IMGUI_DEBUG_PARANOID -//---- Tip: You can add extra functions within the ImGui:: namespace, here or in your own headers files. +//---- Tip: You can add extra functions within the ImGui:: namespace from anywhere (e.g. your own sources/header files) /* namespace ImGui { - void MyFunction(const char* name, const MyMatrix44& v); + void MyFunction(const char* name, MyMatrix44* mtx); } */ From 29cea501b6d69b431de7059352e006fad7ee19e7 Mon Sep 17 00:00:00 2001 From: Andrea Odetti Date: Sun, 21 Dec 2025 20:00:11 +0000 Subject: [PATCH 03/10] sa2: use better font for menus: Cousine-Regular. Signed-off-by: Andrea Odetti --- resource/CMakeLists.txt | 1 + resource/resource.h | 3 +++ source/frontends/sdl/imgui/sa2_imconfig.h | 4 ++-- source/frontends/sdl/imgui/sdlimguiframe.cpp | 10 +++++++--- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/resource/CMakeLists.txt b/resource/CMakeLists.txt index 124052ed8..dccc5ef22 100644 --- a/resource/CMakeLists.txt +++ b/resource/CMakeLists.txt @@ -38,6 +38,7 @@ add_resources(source_files apple2roms IDC_APPLEWIN_ICON "APPLEWIN.ICO" IDB_DEBUG_FONT_7_by_8 "debug6502.ttf" + IDB_IMGUI_FONT_COUSIN "../source/frontends/sdl/imgui/imgui/misc/fonts/Cousine-Regular.ttf" ) add_library(apple2roms STATIC diff --git a/resource/resource.h b/resource/resource.h index 9a3ebfb0f..d42a42f23 100644 --- a/resource/resource.h +++ b/resource/resource.h @@ -173,3 +173,6 @@ #define _APS_NEXT_SYMED_VALUE 101 #endif #endif + +// Added manually, to avoid conflicts +#define IDB_IMGUI_FONT_COUSIN 2000 diff --git a/source/frontends/sdl/imgui/sa2_imconfig.h b/source/frontends/sdl/imgui/sa2_imconfig.h index a6e1d1c75..731423ee7 100644 --- a/source/frontends/sdl/imgui/sa2_imconfig.h +++ b/source/frontends/sdl/imgui/sa2_imconfig.h @@ -48,10 +48,10 @@ //#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS // Don't implement default platform_io.Platform_OpenInShellFn() handler (Win32: ShellExecute(), require shell32.lib/.a, Mac/Linux: use system("")). //#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf) //#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself. -//#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies) +#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies) //#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function. //#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions(). -//#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded font (ProggyClean.ttf), remove ~9.5 KB from output binary. AddFontDefault() will assert. +#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded font (ProggyClean.ttf), remove ~9.5 KB from output binary. AddFontDefault() will assert. //#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available //---- Enable Test Engine / Automation features. diff --git a/source/frontends/sdl/imgui/sdlimguiframe.cpp b/source/frontends/sdl/imgui/sdlimguiframe.cpp index 08d2ac841..91536734a 100644 --- a/source/frontends/sdl/imgui/sdlimguiframe.cpp +++ b/source/frontends/sdl/imgui/sdlimguiframe.cpp @@ -100,12 +100,16 @@ namespace sa2 ImGui::CreateContext(); ImGuiIO &io = ImGui::GetIO(); - io.Fonts->AddFontDefault(); - const auto debug6502TTF = GetResourceData(IDB_DEBUG_FONT_7_by_8); ImFontConfig fontConfig; fontConfig.FontDataOwnedByAtlas = false; + + const auto cousinTTF = GetResourceData(IDB_IMGUI_FONT_COUSIN); + io.Fonts->AddFontFromMemoryTTF( + const_cast(cousinTTF.first), cousinTTF.second, 18.0f, &fontConfig); + + const auto debug6502TTF = GetResourceData(IDB_DEBUG_FONT_7_by_8); myDebuggerFont = io.Fonts->AddFontFromMemoryTTF( - const_cast(debug6502TTF.first), debug6502TTF.second, 13, &fontConfig); + const_cast(debug6502TTF.first), debug6502TTF.second, 13.0f, &fontConfig); myIniFileLocation = common2::getConfigFile("imgui.ini").string(); if (myIniFileLocation.empty()) From b6f8397db0d9ce85c538a654ba1a4731de42ccef Mon Sep 17 00:00:00 2001 From: Andrea Odetti Date: Wed, 17 Dec 2025 20:08:07 +0000 Subject: [PATCH 04/10] Add new web target via Emscripten. Signed-off-by: Andrea Odetti --- CMakeLists.txt | 13 ++ minizip/CMakeLists.txt | 10 +- resource/CMakeResources.cmake | 7 +- source/CMakeLists.txt | 2 +- source/Video.h | 7 + source/frontends/common2/CMakeLists.txt | 1 - source/frontends/common2/gnuframe.cpp | 2 + source/frontends/sdl/CMakeLists.txt | 28 +++- source/frontends/sdl/a2e.html | 41 +++++ source/frontends/sdl/em_js.cpp | 88 ++++++++++ .../sdl/emsc/.config/applewin/applewin.yaml | 21 +++ .../sdl/emsc/.config/applewin/imgui.ini | 24 +++ source/frontends/sdl/emsc/disks/A2_BASIC.SYM | 1 + source/frontends/sdl/emsc/disks/APPLE2E.SYM | 1 + source/frontends/sdl/emsc/disks/BLANK.DSK | 1 + .../DOS 3.3 System Master - 680-0210-A.dsk | 1 + .../frontends/sdl/emsc/disks/ProDOS_2_4_3.po | 1 + source/frontends/sdl/imgui/glselector.h | 34 +++- source/frontends/sdl/imgui/image.cpp | 25 +-- source/frontends/sdl/imgui/sa2_imconfig.h | 2 +- source/frontends/sdl/imgui/sdlimguiframe.cpp | 17 ++ source/frontends/sdl/imgui/sdlsettings.cpp | 11 +- source/frontends/sdl/main.cpp | 158 ++++++++++++------ source/frontends/sdl/sdlframe.cpp | 9 + source/linux/libwindows/misc.h | 2 + 25 files changed, 427 insertions(+), 80 deletions(-) create mode 100644 source/frontends/sdl/a2e.html create mode 100644 source/frontends/sdl/em_js.cpp create mode 100644 source/frontends/sdl/emsc/.config/applewin/applewin.yaml create mode 100644 source/frontends/sdl/emsc/.config/applewin/imgui.ini create mode 120000 source/frontends/sdl/emsc/disks/A2_BASIC.SYM create mode 120000 source/frontends/sdl/emsc/disks/APPLE2E.SYM create mode 120000 source/frontends/sdl/emsc/disks/BLANK.DSK create mode 120000 source/frontends/sdl/emsc/disks/DOS 3.3 System Master - 680-0210-A.dsk create mode 120000 source/frontends/sdl/emsc/disks/ProDOS_2_4_3.po diff --git a/CMakeLists.txt b/CMakeLists.txt index 25ed60626..c869bf24f 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/resource/CMakeResources.cmake b/resource/CMakeResources.cmake index 41b64972e..a34bbe122 100644 --- a/resource/CMakeResources.cmake +++ b/resource/CMakeResources.cmake @@ -27,18 +27,19 @@ function(add_resources out_var id) while(options) list(POP_FRONT options resource_id in_f_bin) + get_filename_component(in_f_name "${in_f_bin}" NAME) - set(out_f_cpp "${CMAKE_CURRENT_BINARY_DIR}/${in_f_bin}.cpp") + set(out_f_cpp "${CMAKE_CURRENT_BINARY_DIR}/${in_f_name}.cpp") add_custom_command( OUTPUT ${out_f_cpp} - COMMAND xxd -i ${in_f_bin} > ${out_f_cpp} + COMMAND xxd -i -n ${in_f_name} ${in_f_bin} > ${out_f_cpp} COMMENT "Adding resource: ${in_f_bin} -> ${out_f_cpp}" DEPENDS ${binary_file} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) # this is the same as what xxd does re. filename - string(REGEX REPLACE "[ ./-]" "_" symbol ${in_f_bin}) + string(REGEX REPLACE "[ ./-]" "_" symbol ${in_f_name}) string(APPEND content_cpp_private "// ${in_f_bin}\n" diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index f2fc8fde2..4b16d993a 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1,6 +1,6 @@ include(FindPkgConfig) -if (NOT WIN32) +if (NOT WIN32 AND NOT EMSCRIPTEN) pkg_search_module(SLIRP slirp) # if slirp is not found, we will try pcap diff --git a/source/Video.h b/source/Video.h index dafd59ad9..b134e2d9b 100644 --- a/source/Video.h +++ b/source/Video.h @@ -98,10 +98,17 @@ enum AppleFont_e // 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 }; struct WinBmpHeader_t diff --git a/source/frontends/common2/CMakeLists.txt b/source/frontends/common2/CMakeLists.txt index a871564de..7134b5633 100644 --- a/source/frontends/common2/CMakeLists.txt +++ b/source/frontends/common2/CMakeLists.txt @@ -13,7 +13,6 @@ set(SOURCE_FILES timer.cpp speed.cpp yamlmap.cpp - yamlselftest.cpp ) set(HEADER_FILES diff --git a/source/frontends/common2/gnuframe.cpp b/source/frontends/common2/gnuframe.cpp index 388f7bdea..c60f4bb89 100644 --- a/source/frontends/common2/gnuframe.cpp +++ b/source/frontends/common2/gnuframe.cpp @@ -76,8 +76,10 @@ namespace common2 : CommonFrame(options) { // should this go down to LinuxFrame (maybe Initialisation?) +#ifndef __EMSCRIPTEN__ g_sProgramDir = getResourceFolder("bin").string() + PATH_SEPARATOR; LogFileOutput("Program Dir: '%s'\n", g_sProgramDir.c_str()); +#endif } std::pair GNUFrame::GetResourceData(WORD id) const diff --git a/source/frontends/sdl/CMakeLists.txt b/source/frontends/sdl/CMakeLists.txt index 19364d254..35eb44c49 100644 --- a/source/frontends/sdl/CMakeLists.txt +++ b/source/frontends/sdl/CMakeLists.txt @@ -6,9 +6,11 @@ add_executable(sa2) set(IMGUI_PATH "imgui/imgui") set(IMGUI_CLUB_PATH "imgui/imgui_club") -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) +if (NOT EMSCRIPTEN) + 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) +endif() if ((${CMAKE_SYSTEM_NAME} MATCHES "Darwin") OR (${CMAKE_SYSTEM_NAME} MATCHES "Windows")) @@ -29,6 +31,7 @@ endif() set(SOURCE_FILES + em_js.cpp main.cpp gamepad.cpp sdirectsound.cpp @@ -121,5 +124,24 @@ target_compile_definitions(sa2 PRIVATE IMGUI_USER_CONFIG="frontends/sdl/imgui/sa2_imconfig.h" ) +if (EMSCRIPTEN) + set_target_properties(sa2 PROPERTIES + SUFFIX ".html" + ) + + target_compile_options(sa2 PRIVATE + --use-port=sdl2 + ) + + target_link_options(sa2 PRIVATE + --use-port=sdl2 + -sFULL_ES3=1 + -sMIN_WEBGL_VERSION=2 + -sEXPORTED_RUNTIME_METHODS=[] + --shell-file ${CMAKE_SOURCE_DIR}/source/frontends/sdl/a2e.html + --preload-file ${CMAKE_SOURCE_DIR}/source/frontends/sdl/emsc@/home/web_user + ) +endif() + install(TARGETS sa2 DESTINATION bin) diff --git a/source/frontends/sdl/a2e.html b/source/frontends/sdl/a2e.html new file mode 100644 index 000000000..c4b16d4ec --- /dev/null +++ b/source/frontends/sdl/a2e.html @@ -0,0 +1,41 @@ + + + + + + Your App + + + + + + + + + + + {{{ SCRIPT }}} + + + \ No newline at end of file diff --git a/source/frontends/sdl/em_js.cpp b/source/frontends/sdl/em_js.cpp new file mode 100644 index 000000000..278c281f8 --- /dev/null +++ b/source/frontends/sdl/em_js.cpp @@ -0,0 +1,88 @@ +#ifdef __EMSCRIPTEN__ + +#include +#include +#include + +// clang-format off +EM_JS(void, init_dragdrop, (), { + console.log('[DND] Drag & drop initialized'); + + function prevent(e) { + e.preventDefault(); + e.stopPropagation(); + return false; + } + + // Convert JS string to UTF8 pointer for WASM + function cstr(str) { + const len = lengthBytesUTF8(str) + 1; + const ptr = _malloc(len); + stringToUTF8(str, ptr, len); + return ptr; + } + + const DROP_PATH = '/home/web_user/disks'; + + // Ensure /drop directory exists in MEMFS + if (!FS.analyzePath(DROP_PATH).exists) { + FS.mkdir(DROP_PATH); + console.log('[DND] Created ' + DROP_PATH + ' directory in MEMFS'); + } + + // Attach global handlers + document.addEventListener('dragenter', prevent, false); + document.addEventListener('dragover', prevent, false); + document.addEventListener('dragleave', prevent, false); + + document.addEventListener('drop', (e) => { + prevent(e); + + const files = e.dataTransfer.files; + console.log('[DND] drop event, files:', files.length); + + for (let i = 0; i < files.length; ++i) { + const file = files[i]; + console.log('[DND] dropped file:', file.name, file.size, 'bytes'); + + const reader = new FileReader(); + reader.onload = function(evt) { + const data = new Uint8Array(evt.target.result); + + // MEMFS path + const path = DROP_PATH + '/' + file.name; + + try { + FS.writeFile(path, data); + console.log('[DND] saved to MEMFS:', path); + + // Push SDL_DROPFILE event + const pathPtr = cstr(path); + Module._sdl_dropfile(pathPtr); + _free(pathPtr); + + } catch (err) { + console.error('[DND] MEMFS write failed:', err); + } + }; + + reader.readAsArrayBuffer(file); + } + }, false); +}); +// clang-format on + +extern "C" EMSCRIPTEN_KEEPALIVE void sdl_dropfile(const char *filename) +{ + SDL_Event e; + SDL_zero(e); + + e.type = SDL_DROPFILE; + e.drop.file = SDL_strdup(filename); // SDL requires this + + printf("[DND] pushing SDL_DROPFILE event for '%s'\n", filename); + + SDL_PushEvent(&e); +} + +#endif diff --git a/source/frontends/sdl/emsc/.config/applewin/applewin.yaml b/source/frontends/sdl/emsc/.config/applewin/applewin.yaml new file mode 100644 index 000000000..68e726e78 --- /dev/null +++ b/source/frontends/sdl/emsc/.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: /home/web_user/disks/DOS 3.3 System Master - 680-0210-A.dsk + Last Disk Image 2: /home/web_user/disks/ProDOS_2_4_3.po +Configuration\Slot Auxiliary: + Card type: 13 + Number of Banks: 1 +Preferences: + Starting Directory: /home/web_user/disks/ diff --git a/source/frontends/sdl/emsc/.config/applewin/imgui.ini b/source/frontends/sdl/emsc/.config/applewin/imgui.ini new file mode 100644 index 000000000..962fd9dcf --- /dev/null +++ b/source/frontends/sdl/emsc/.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/disks/A2_BASIC.SYM b/source/frontends/sdl/emsc/disks/A2_BASIC.SYM new file mode 120000 index 000000000..0a3af83db --- /dev/null +++ b/source/frontends/sdl/emsc/disks/A2_BASIC.SYM @@ -0,0 +1 @@ +../../../../../bin/A2_BASIC.SYM \ No newline at end of file diff --git a/source/frontends/sdl/emsc/disks/APPLE2E.SYM b/source/frontends/sdl/emsc/disks/APPLE2E.SYM new file mode 120000 index 000000000..4a6aeee10 --- /dev/null +++ b/source/frontends/sdl/emsc/disks/APPLE2E.SYM @@ -0,0 +1 @@ +../../../../../bin/APPLE2E.SYM \ No newline at end of file diff --git a/source/frontends/sdl/emsc/disks/BLANK.DSK b/source/frontends/sdl/emsc/disks/BLANK.DSK new file mode 120000 index 000000000..0dedd2288 --- /dev/null +++ b/source/frontends/sdl/emsc/disks/BLANK.DSK @@ -0,0 +1 @@ +../../../../../bin/BLANK.DSK \ No newline at end of file diff --git a/source/frontends/sdl/emsc/disks/DOS 3.3 System Master - 680-0210-A.dsk b/source/frontends/sdl/emsc/disks/DOS 3.3 System Master - 680-0210-A.dsk new file mode 120000 index 000000000..349619d61 --- /dev/null +++ b/source/frontends/sdl/emsc/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/disks/ProDOS_2_4_3.po b/source/frontends/sdl/emsc/disks/ProDOS_2_4_3.po new file mode 120000 index 000000000..a78e56a5a --- /dev/null +++ b/source/frontends/sdl/emsc/disks/ProDOS_2_4_3.po @@ -0,0 +1 @@ +../../../../../bin/ProDOS_2_4_3.po \ No newline at end of file diff --git a/source/frontends/sdl/imgui/glselector.h b/source/frontends/sdl/imgui/glselector.h index fd1450d95..718246dbd 100644 --- a/source/frontends/sdl/imgui/glselector.h +++ b/source/frontends/sdl/imgui/glselector.h @@ -1,6 +1,19 @@ #pragma once -#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" @@ -15,7 +28,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) @@ -31,7 +44,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 @@ -39,8 +59,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 @@ -63,7 +83,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/sa2_imconfig.h b/source/frontends/sdl/imgui/sa2_imconfig.h index 731423ee7..e2c902fe6 100644 --- a/source/frontends/sdl/imgui/sa2_imconfig.h +++ b/source/frontends/sdl/imgui/sa2_imconfig.h @@ -48,7 +48,7 @@ //#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS // Don't implement default platform_io.Platform_OpenInShellFn() handler (Win32: ShellExecute(), require shell32.lib/.a, Mac/Linux: use system("")). //#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf) //#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself. -#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies) +//#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies) //#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function. //#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions(). #define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded font (ProggyClean.ttf), remove ~9.5 KB from output binary. AddFontDefault() will assert. diff --git a/source/frontends/sdl/imgui/sdlimguiframe.cpp b/source/frontends/sdl/imgui/sdlimguiframe.cpp index 91536734a..ebb265802 100644 --- a/source/frontends/sdl/imgui/sdlimguiframe.cpp +++ b/source/frontends/sdl/imgui/sdlimguiframe.cpp @@ -100,6 +100,18 @@ namespace sa2 ImGui::CreateContext(); ImGuiIO &io = ImGui::GetIO(); + int w, h; + SDL_GL_GetDrawableSize(myWindow.get(), &w, &h); + if (w <= 0 || h <= 0) + { + w = 100; + h = 100; + } + + io.DisplaySize = ImVec2((float)w, (float)h); + io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); + io.FontGlobalScale = 1.0f; + ImFontConfig fontConfig; fontConfig.FontDataOwnedByAtlas = false; @@ -239,6 +251,11 @@ namespace sa2 if (!myPresenting) { myPresenting = true; + + int w, h; + SDL_GL_GetDrawableSize(myWindow.get(), &w, &h); + glViewport(0, 0, w, h); + ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); diff --git a/source/frontends/sdl/imgui/sdlsettings.cpp b/source/frontends/sdl/imgui/sdlsettings.cpp index 323bb4f01..bc7ccdfc5 100644 --- a/source/frontends/sdl/imgui/sdlsettings.cpp +++ b/source/frontends/sdl/imgui/sdlsettings.cpp @@ -829,13 +829,18 @@ namespace sa2 int nMajor, nMinor, nFixMajor, nFixMinor; UnpackVersion(DEBUGGER_VERSION, nMajor, nMinor, nFixMajor, nFixMinor); - ImGui::Text("Debugger %d.%d.%d.%d", nMajor, nMinor, nFixMajor, nFixMinor); + ImGui::Text("Debugger: %d.%d.%d.%d", nMajor, nMinor, nFixMajor, nFixMinor); ImGui::Separator(); SDL_version sdl; SDL_GetVersion(&sdl); - ImGui::Text("SDL version %d.%d.%d", sdl.major, sdl.minor, sdl.patch); - ImGui::Text("Dear ImGui %s", ImGui::GetVersion()); + ImGui::Text("SDL: %d.%d.%d", sdl.major, sdl.minor, sdl.patch); + ImGui::Text("Dear ImGui: %s", ImGui::GetVersion()); + const char *str = reinterpret_cast(glGetString(GL_VERSION)); + if (str) + { + ImGui::Text("GL_VERSION: %s", str); + } ImGui::Separator(); const int glSwap = SDL_GL_GetSwapInterval(); diff --git a/source/frontends/sdl/main.cpp b/source/frontends/sdl/main.cpp index 29d02158e..1a5da35d9 100644 --- a/source/frontends/sdl/main.cpp +++ b/source/frontends/sdl/main.cpp @@ -24,6 +24,10 @@ #include "NTSC.h" #include "Interface.h" +#ifdef __EMSCRIPTEN__ +#include "examples/libs/emscripten/emscripten_mainloop_stub.h" +#endif + // comment out to test / debug init / shutdown only #define EMULATOR_RUN @@ -51,8 +55,85 @@ namespace common2::Timer *timer; }; + class MainLoop + { + public: + MainLoop(std::shared_ptr &aframe, common2::EmulatorOptions aoptions, const int fps) + : frame(aframe) + , options(aoptions) + , oneFrameMicros(1000000 / fps) + { + } + + ~MainLoop() + { + // printf("%s:%d\n", __FILE__, __LINE__); + } + + void loop() + { + frameTimer.tic(); + + eventTimer.tic(); + frame->ProcessEvents(quit); + eventTimer.toc(); + + cpuTimer.tic(); + frame->ExecuteOneFrame(oneFrameMicros); + cpuTimer.toc(); + + if (!options.headless) + { + refreshScreenTimer.tic(); + if (g_bFullSpeed) + { + frame->VideoRedrawScreenDuringFullSpeed(g_dwCyclesThisFrame); + } + else + { + frame->SyncVideoPresentScreen(oneFrameMicros); + } + refreshScreenTimer.toc(); + } + + frameTimer.toc(); + } + + void end() + { + global.toc(); + + std::cerr << "Global: " << global << std::endl; + std::cerr << "Frame: " << frameTimer << std::endl; + std::cerr << "Screen: " << refreshScreenTimer << std::endl; + std::cerr << "Events: " << eventTimer << std::endl; + std::cerr << "CPU: " << cpuTimer << std::endl; + } + + bool isQuit() const + { + return quit || frame->Quit(); + } + + private: + const std::shared_ptr frame; + const common2::EmulatorOptions options; + const int64_t oneFrameMicros; + bool quit = false; + + common2::Timer global; + common2::Timer refreshScreenTimer; + common2::Timer cpuTimer; + common2::Timer eventTimer; + common2::Timer frameTimer; + }; + + std::unique_ptr mainLoopPtr; + } // namespace +extern "C" void init_dragdrop(); + void run_sdl(int argc, char *const argv[]) { common2::EmulatorOptions options; @@ -84,6 +165,12 @@ void run_sdl(int argc, char *const argv[]) frame = std::make_shared(options); } +#ifdef __EMSCRIPTEN__ + SDL_SetHint(SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT, "#canvas"); + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest"); + init_dragdrop(); +#endif + std::cerr << "Default GL swap interval: " << SDL_GL_GetSwapInterval() << std::endl; const common2::CommonInitialisation init(frame, paddle, options); @@ -112,56 +199,33 @@ void run_sdl(int argc, char *const argv[]) } else { - common2::Timer global; - common2::Timer refreshScreenTimer; - common2::Timer cpuTimer; - common2::Timer eventTimer; - common2::Timer frameTimer; - - const std::string globalTag = ". ."; - std::string updateTextureTimerTag, refreshScreenTimerTag, cpuTimerTag, eventTimerTag; - - // it does not need to be exact - const int64_t oneFrameMicros = 1000000 / fps; - - bool quit = false; - - do + mainLoopPtr = std::make_unique(frame, options, fps); +#ifdef __EMSCRIPTEN__ + EMSCRIPTEN_MAINLOOP_BEGIN +#else + while (!mainLoopPtr->isQuit()) +#endif { - frameTimer.tic(); - - eventTimer.tic(); - frame->ProcessEvents(quit); - eventTimer.toc(); - - cpuTimer.tic(); - frame->ExecuteOneFrame(oneFrameMicros); - cpuTimer.toc(); - - if (!options.headless) +#ifdef __EMSCRIPTEN__ + if (mainLoopPtr->isQuit()) { - refreshScreenTimer.tic(); - if (g_bFullSpeed) - { - frame->VideoRedrawScreenDuringFullSpeed(g_dwCyclesThisFrame); - } - else - { - frame->SyncVideoPresentScreen(oneFrameMicros); - } - refreshScreenTimer.toc(); + emscripten_cancel_main_loop(); } - - frameTimer.toc(); - } while (!quit && !frame->Quit()); - - global.toc(); - - std::cerr << "Global: " << global << std::endl; - std::cerr << "Frame: " << frameTimer << std::endl; - std::cerr << "Screen: " << refreshScreenTimer << std::endl; - std::cerr << "Events: " << eventTimer << std::endl; - std::cerr << "CPU: " << cpuTimer << std::endl; +#endif + try + { + mainLoopPtr->loop(); + } + catch (const std::exception &e) + { + std::cerr << "Exception caught in main loop: " << e.what() << std::endl; + } + } +#ifdef __EMSCRIPTEN__ + EMSCRIPTEN_MAINLOOP_END; +#endif + mainLoopPtr->end(); + mainLoopPtr.reset(); } #endif } diff --git a/source/frontends/sdl/sdlframe.cpp b/source/frontends/sdl/sdlframe.cpp index f3089d66b..5f9a97fd0 100644 --- a/source/frontends/sdl/sdlframe.cpp +++ b/source/frontends/sdl/sdlframe.cpp @@ -20,7 +20,9 @@ #include "linux/keyboardbuffer.h" #include "linux/network/slirp2.h" +#ifndef __EMSCRIPTEN__ #include +#endif // #define KEY_LOGGING_VERBOSE @@ -171,7 +173,11 @@ namespace sa2 void SDLFrame::SetGLSynchronisation(const common2::EmulatorOptions &options) { +#ifdef __EMSCRIPTEN__ + const int defaultGLSwap = 1; // vsync on by default on desktop +#else const int defaultGLSwap = SDL_GL_GetSwapInterval(); +#endif if (defaultGLSwap == 0) { // sane default @@ -230,6 +236,7 @@ namespace sa2 void SDLFrame::SetApplicationIcon() { +#ifndef __EMSCRIPTEN__ const auto resource = GetResourceData(IDC_APPLEWIN_ICON); SDL_RWops *ops = SDL_RWFromConstMem(resource.first, resource.second); @@ -239,6 +246,7 @@ namespace sa2 { SDL_SetWindowIcon(myWindow.get(), icon.get()); } +#endif } const std::shared_ptr &SDLFrame::GetWindow() const @@ -302,6 +310,7 @@ namespace sa2 } case SDL_DROPFILE: { + printf("File dropped: %s\n", e.drop.file); ProcessDropEvent(e.drop); SDL_free(e.drop.file); break; 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); From 017a86283a74e51a7b17857cea1ff9d363085e3a Mon Sep 17 00:00:00 2001 From: Andrea Odetti Date: Fri, 26 Dec 2025 14:19:13 +0000 Subject: [PATCH 05/10] A working Emscripten configuration. Signed-off-by: Andrea Odetti --- source/AY8910.cpp | 2 +- source/frontends/common2/gnuframe.cpp | 4 +- source/frontends/common2/utils.cpp | 32 +++++++-- source/frontends/sdl/CMakeLists.txt | 12 ++-- source/frontends/sdl/em_js.cpp | 69 ------------------- source/frontends/sdl/emsc/disks/A2_BASIC.SYM | 1 - source/frontends/sdl/emsc/disks/APPLE2E.SYM | 1 - source/frontends/sdl/emsc/disks/BLANK.DSK | 1 - .../DOS 3.3 System Master - 680-0210-A.dsk | 1 - .../frontends/sdl/emsc/disks/ProDOS_2_4_3.po | 1 - .../defaults}/.config/applewin/applewin.yaml | 6 +- .../defaults}/.config/applewin/imgui.ini | 0 .../frontends/sdl/emsc/fs/disks/A2_BASIC.SYM | 1 + .../frontends/sdl/emsc/fs/disks/APPLE2E.SYM | 1 + source/frontends/sdl/emsc/fs/disks/BLANK.DSK | 1 + .../DOS 3.3 System Master - 680-0210-A.dsk | 1 + .../sdl/emsc/fs/disks/ProDOS_2_4_3.po | 1 + source/frontends/sdl/{ => emsc/src}/a2e.html | 19 ++++- source/frontends/sdl/emsc/src/dragdrop.js | 65 +++++++++++++++++ source/frontends/sdl/emsc/src/fs.js | 45 ++++++++++++ source/frontends/sdl/main.cpp | 17 +++-- 21 files changed, 186 insertions(+), 95 deletions(-) delete mode 120000 source/frontends/sdl/emsc/disks/A2_BASIC.SYM delete mode 120000 source/frontends/sdl/emsc/disks/APPLE2E.SYM delete mode 120000 source/frontends/sdl/emsc/disks/BLANK.DSK delete mode 120000 source/frontends/sdl/emsc/disks/DOS 3.3 System Master - 680-0210-A.dsk delete mode 120000 source/frontends/sdl/emsc/disks/ProDOS_2_4_3.po rename source/frontends/sdl/emsc/{ => fs/defaults}/.config/applewin/applewin.yaml (64%) rename source/frontends/sdl/emsc/{ => fs/defaults}/.config/applewin/imgui.ini (100%) create mode 120000 source/frontends/sdl/emsc/fs/disks/A2_BASIC.SYM create mode 120000 source/frontends/sdl/emsc/fs/disks/APPLE2E.SYM create mode 120000 source/frontends/sdl/emsc/fs/disks/BLANK.DSK create mode 120000 source/frontends/sdl/emsc/fs/disks/DOS 3.3 System Master - 680-0210-A.dsk create mode 120000 source/frontends/sdl/emsc/fs/disks/ProDOS_2_4_3.po rename source/frontends/sdl/{ => emsc/src}/a2e.html (56%) create mode 100644 source/frontends/sdl/emsc/src/dragdrop.js create mode 100644 source/frontends/sdl/emsc/src/fs.js diff --git a/source/AY8910.cpp b/source/AY8910.cpp index 22cc7a41d..0be6c3cc5 100644 --- a/source/AY8910.cpp +++ b/source/AY8910.cpp @@ -784,7 +784,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/frontends/common2/gnuframe.cpp b/source/frontends/common2/gnuframe.cpp index c60f4bb89..3a65a000e 100644 --- a/source/frontends/common2/gnuframe.cpp +++ b/source/frontends/common2/gnuframe.cpp @@ -78,8 +78,10 @@ namespace common2 // should this go down to LinuxFrame (maybe Initialisation?) #ifndef __EMSCRIPTEN__ g_sProgramDir = getResourceFolder("bin").string() + PATH_SEPARATOR; - LogFileOutput("Program Dir: '%s'\n", g_sProgramDir.c_str()); +#else + g_sProgramDir = "/home/web_user/disks/"; #endif + LogFileOutput("Program Dir: '%s'\n", g_sProgramDir.c_str()); } std::pair GNUFrame::GetResourceData(WORD id) const diff --git a/source/frontends/common2/utils.cpp b/source/frontends/common2/utils.cpp index 373f9fdf3..47ebe2e79 100644 --- a/source/frontends/common2/utils.cpp +++ b/source/frontends/common2/utils.cpp @@ -5,15 +5,29 @@ #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) { @@ -34,10 +48,18 @@ namespace common2 return profile; #else // https://specifications.freedesktop.org/basedir-spec/latest/ - const std::filesystem::path home = getHomeDir(); - const std::filesystem::path config = getEnvOrDefault("XDG_CONFIG_HOME", ".config"); +#ifdef __EMSCRIPTEN__ + const std::optional xdgConfigHome = "/defaults/.config"; +#else + const std::optional xdgConfigHome = tryGetEnv("XDG_CONFIG_HOME"); +#endif + if (xdgConfigHome.has_value()) + { + return *xdgConfigHome; + } - return home / config; + 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 35eb44c49..3f6da6070 100644 --- a/source/frontends/sdl/CMakeLists.txt +++ b/source/frontends/sdl/CMakeLists.txt @@ -133,14 +133,18 @@ if (EMSCRIPTEN) --use-port=sdl2 ) + set(EMSC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/emsc) + target_link_options(sa2 PRIVATE --use-port=sdl2 -sFULL_ES3=1 -sMIN_WEBGL_VERSION=2 - -sEXPORTED_RUNTIME_METHODS=[] - --shell-file ${CMAKE_SOURCE_DIR}/source/frontends/sdl/a2e.html - --preload-file ${CMAKE_SOURCE_DIR}/source/frontends/sdl/emsc@/home/web_user - ) + "SHELL:-lidbfs.js" + "SHELL:--pre-js ${EMSC_DIR}/src/fs.js" + "SHELL:--pre-js ${EMSC_DIR}/src/dragdrop.js" + "SHELL:--shell-file ${EMSC_DIR}/src/a2e.html" + "SHELL:--preload-file ${EMSC_DIR}/fs@/" + ) endif() install(TARGETS sa2 diff --git a/source/frontends/sdl/em_js.cpp b/source/frontends/sdl/em_js.cpp index 278c281f8..b293030a0 100644 --- a/source/frontends/sdl/em_js.cpp +++ b/source/frontends/sdl/em_js.cpp @@ -2,75 +2,6 @@ #include #include -#include - -// clang-format off -EM_JS(void, init_dragdrop, (), { - console.log('[DND] Drag & drop initialized'); - - function prevent(e) { - e.preventDefault(); - e.stopPropagation(); - return false; - } - - // Convert JS string to UTF8 pointer for WASM - function cstr(str) { - const len = lengthBytesUTF8(str) + 1; - const ptr = _malloc(len); - stringToUTF8(str, ptr, len); - return ptr; - } - - const DROP_PATH = '/home/web_user/disks'; - - // Ensure /drop directory exists in MEMFS - if (!FS.analyzePath(DROP_PATH).exists) { - FS.mkdir(DROP_PATH); - console.log('[DND] Created ' + DROP_PATH + ' directory in MEMFS'); - } - - // Attach global handlers - document.addEventListener('dragenter', prevent, false); - document.addEventListener('dragover', prevent, false); - document.addEventListener('dragleave', prevent, false); - - document.addEventListener('drop', (e) => { - prevent(e); - - const files = e.dataTransfer.files; - console.log('[DND] drop event, files:', files.length); - - for (let i = 0; i < files.length; ++i) { - const file = files[i]; - console.log('[DND] dropped file:', file.name, file.size, 'bytes'); - - const reader = new FileReader(); - reader.onload = function(evt) { - const data = new Uint8Array(evt.target.result); - - // MEMFS path - const path = DROP_PATH + '/' + file.name; - - try { - FS.writeFile(path, data); - console.log('[DND] saved to MEMFS:', path); - - // Push SDL_DROPFILE event - const pathPtr = cstr(path); - Module._sdl_dropfile(pathPtr); - _free(pathPtr); - - } catch (err) { - console.error('[DND] MEMFS write failed:', err); - } - }; - - reader.readAsArrayBuffer(file); - } - }, false); -}); -// clang-format on extern "C" EMSCRIPTEN_KEEPALIVE void sdl_dropfile(const char *filename) { diff --git a/source/frontends/sdl/emsc/disks/A2_BASIC.SYM b/source/frontends/sdl/emsc/disks/A2_BASIC.SYM deleted file mode 120000 index 0a3af83db..000000000 --- a/source/frontends/sdl/emsc/disks/A2_BASIC.SYM +++ /dev/null @@ -1 +0,0 @@ -../../../../../bin/A2_BASIC.SYM \ No newline at end of file diff --git a/source/frontends/sdl/emsc/disks/APPLE2E.SYM b/source/frontends/sdl/emsc/disks/APPLE2E.SYM deleted file mode 120000 index 4a6aeee10..000000000 --- a/source/frontends/sdl/emsc/disks/APPLE2E.SYM +++ /dev/null @@ -1 +0,0 @@ -../../../../../bin/APPLE2E.SYM \ No newline at end of file diff --git a/source/frontends/sdl/emsc/disks/BLANK.DSK b/source/frontends/sdl/emsc/disks/BLANK.DSK deleted file mode 120000 index 0dedd2288..000000000 --- a/source/frontends/sdl/emsc/disks/BLANK.DSK +++ /dev/null @@ -1 +0,0 @@ -../../../../../bin/BLANK.DSK \ No newline at end of file diff --git a/source/frontends/sdl/emsc/disks/DOS 3.3 System Master - 680-0210-A.dsk b/source/frontends/sdl/emsc/disks/DOS 3.3 System Master - 680-0210-A.dsk deleted file mode 120000 index 349619d61..000000000 --- a/source/frontends/sdl/emsc/disks/DOS 3.3 System Master - 680-0210-A.dsk +++ /dev/null @@ -1 +0,0 @@ -../../../../../bin/DOS 3.3 System Master - 680-0210-A.dsk \ No newline at end of file diff --git a/source/frontends/sdl/emsc/disks/ProDOS_2_4_3.po b/source/frontends/sdl/emsc/disks/ProDOS_2_4_3.po deleted file mode 120000 index a78e56a5a..000000000 --- a/source/frontends/sdl/emsc/disks/ProDOS_2_4_3.po +++ /dev/null @@ -1 +0,0 @@ -../../../../../bin/ProDOS_2_4_3.po \ No newline at end of file diff --git a/source/frontends/sdl/emsc/.config/applewin/applewin.yaml b/source/frontends/sdl/emsc/fs/defaults/.config/applewin/applewin.yaml similarity index 64% rename from source/frontends/sdl/emsc/.config/applewin/applewin.yaml rename to source/frontends/sdl/emsc/fs/defaults/.config/applewin/applewin.yaml index 68e726e78..4fe5b039c 100644 --- a/source/frontends/sdl/emsc/.config/applewin/applewin.yaml +++ b/source/frontends/sdl/emsc/fs/defaults/.config/applewin/applewin.yaml @@ -12,10 +12,10 @@ Configuration\Slot 3: Uthernet Interface: Configuration\Slot 6: Card type: 1 - Last Disk Image 1: /home/web_user/disks/DOS 3.3 System Master - 680-0210-A.dsk - Last Disk Image 2: /home/web_user/disks/ProDOS_2_4_3.po + 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: /home/web_user/disks/ + Starting Directory: /disks/ diff --git a/source/frontends/sdl/emsc/.config/applewin/imgui.ini b/source/frontends/sdl/emsc/fs/defaults/.config/applewin/imgui.ini similarity index 100% rename from source/frontends/sdl/emsc/.config/applewin/imgui.ini rename to source/frontends/sdl/emsc/fs/defaults/.config/applewin/imgui.ini 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/ProDOS_2_4_3.po b/source/frontends/sdl/emsc/fs/disks/ProDOS_2_4_3.po new file mode 120000 index 000000000..7ba635cef --- /dev/null +++ b/source/frontends/sdl/emsc/fs/disks/ProDOS_2_4_3.po @@ -0,0 +1 @@ +../../../../../../bin/ProDOS_2_4_3.po \ No newline at end of file diff --git a/source/frontends/sdl/a2e.html b/source/frontends/sdl/emsc/src/a2e.html similarity index 56% rename from source/frontends/sdl/a2e.html rename to source/frontends/sdl/emsc/src/a2e.html index c4b16d4ec..a55826fa0 100644 --- a/source/frontends/sdl/a2e.html +++ b/source/frontends/sdl/emsc/src/a2e.html @@ -3,9 +3,8 @@ - Your App + AppleWin Emscripten -