Files
RetroArch/Makefile.emscripten
T
Davey HughesandLibretroAdmin ad2e9e05d8 scan: parallel readers, 3DS product codes, CRCs and index builds over steps (#19920)
* samples/database: include compat/strl.h in the scan test

The test calls strlcpy(), which mingw-w64 does not declare, so it did
not build for Windows.

* PS1 scan: PSX.EXE means no serial only as the BOOT target

A root directory can list a PSX.EXE ahead of the executable SYSTEM.CNF
boots: Tomb Raider (USA) lists one before SLUS_001.52. Stopping at the
first PSX.EXE in the probe window sent such discs to a whole-track CRC,
eight seconds for a CHD. Look for the BOOT target first, matched without
regard to case so cdrom:\psx.exe counts. A disc that boots PSX.EXE, or
has no SYSTEM.CNF so the BIOS boots PSX.EXE, has no serial and still
falls back to its CRC (#15241); any other disc is searched for its
serial with PSX.EXE directory records ignored.

* 3DS scan: identify .3ds, .cci and .cxi by the NCCH product code

These images took the default whole-file CRC, 0.5-4 GB a game. The
product code in the NCCH header (partition 0 of a cart image, or the
start of a bare NCCH) is the serial the 3DS database is keyed on, and
the header is plaintext in encrypted dumps too. A code held by one
record matches it outright. Revisions and regions that share a code fall
back to the file's CRC to choose, and to the first record when no CRC
matches; with scan_serial_and_crc on every match is confirmed by CRC, as
for PSP and Dreamcast, and a file none of whose records carry its CRC
goes on to the plain CRC lookup with the CRC already computed. A code no
record carries, or a header without valid magic such as a CIA, keeps the
CRC path, in first-match mode too. Like the CRC lookup, the serial pass
visits only databases an installed core claims the extension for, unless
Scan Without Core Match is on.

* scan: run ITERATE steps while the shared I/O window lasts

A file takes about twenty ITERATE steps, and the handler ran one per
call. On the regular task queue that is one step per frame: 1000 NES
zips took 335 s of pacing, 400 arcade sets 1639 s. The ITERATE states
now run in a loop under the same window the BEGIN states use, which
times the steps rather than counting them and grants every call at least
one. The scan test's unclaimed-database lane spends the window before
each check, so that it still counts steps.

* scan: hash file CRCs a quantum at a time across steps

A step that needed a CRC computed all of it in one handler call: a whole
CHD track with LZMA decode, or a multi-gigabyte 3DS image. On the
regular queue that froze a frame, and on the threaded queue it held the
worker for seconds. Each CRC site now opens its span and returns, and
later steps hash 256 KiB quanta while the shared I/O window lasts, at
least one per call. The lookup that wanted the CRC runs when it is in,
at the same entry. The one-shot CRC helpers, which nothing else called,
are gone.

* scan: walk each database once for its crc and serial indexes

The first file to reach the serial lookup built a crc index and then a
serial index for every database, a full walk each: 47 MB of Mobile -
J2ME twice in one handler call, the second for an index that came out
empty. The crc walk now reads serials as well. It notes whether a
database has any, so the serial lookup passes over one with none, and
for the serial lookup it builds the serial index in the same pass. Index
contents, matches and the split of the index budget are unchanged.

* scan: let a database's indexes be built a run of records at a time

A database's crc and serial indexes were built by one walk and a qsort
in a single call: 50-70 ms for Mobile - J2ME. libretrodb_scan_begin(),
_step() and _end() walk a run of records per call, and
database_info_index_job_*() builds both indexes over as many calls as it
takes, each walking a run of records or merging a run of entries in a
resumable merge sort. libretrodb_scan_field2() and
database_info_index_new() are built on them and answer as before.

* scan: build each database's indexes over steps

The first file to reach a database built its indexes in one handler
call: 52-68 ms on the regular queue for Mobile - J2ME, 13-17 frames'
worth of the 4 ms window. The build now runs over the steps that follow
the lookup that began it, a run of records walked or entries merged at a
time while the window lasts, and the lookup takes the indexes when they
are done. A database counts as size-checked only then. Cancelling frees
a build in flight. The one-shot builders, database_info_crc_index_new(),
database_info_serial_index_new(), database_info_index_new() and
libretrodb_scan_field() and _field2(), lose their last caller here and
go; the tests build through the job and the stepped scan.

* task_queue samples: build on macOS, run on GCD in a macOS job

No sample ran the GCD runner and nothing in CI built HAVE_GCD. Apple's
ld has no --wrap, so on Darwin the switch and init-failure tests
redirect the wrapped calls from task_queue.c by macro instead, and
libdispatch comes from libSystem there. The switch test gets a GCD=1
build, and a macOS job runs every task_queue sample plain, under
ASan+UBSan and TSan, threaded and, where it has a GCD=1 build, on GCD.

* task_queue: a switch waits out a wake from another thread

task_queue_wake() is documented as safe from any thread, but a switch
between the threaded and the unthreaded queue frees the threaded
runner's lock and condition variable while a waker on another thread can
still be inside it. Toggling Threaded Video makes that switch at the
next frame. Two seq_cst counters now keep them apart without a lock: a
switch that finds a waker inside is put off to the next
task_queue_check(), and a waker that finds a switch under way leaves its
task to it. A wake on the thread that drives the queue cannot meet a
switch and skips the counters.

A switch now ends every park, and the new runner takes wakes again
before it runs any task, so a parked task loses nothing to a wake
dropped during the switch. A wake dropped while a switch is put off and
then taken back, or for a task whose park a switch keeps because it
leaves a stuck handler behind, waits for that park's horizon.

The switch test holds a waker inside the queue across a switch, wakes
from four threads through 1000 switches, and holds the main thread where
the new runner first runs a task while that task's first pass wakes it.

GCD builds leave out the threaded runner's own code, which they never
used and which warned there. The switch test also checks that its
realloc() wrapper ran, since on macOS that wrap is a macro rather than a
link option.

* scan: a scan of one file only adds to its playlist

A Content Location that is a file is scanned as one file, which the Scan
button, --scan=FILE and iOS "open in" already rely on. Such a scan
cleared the playlist when Overwrite was on, and recorded the file, and
its core, as what the playlist refreshes with, so Refresh Playlist later
rescanned that file alone. Overwrite is now ignored for a file, and the
playlist's scan record and default core are left as they were. Validate
Existing Entries is skipped too: a file scan's extensions can differ
from its folder's, so validating with them could drop entries the folder
scan added. A later folder scan or Refresh still prunes. With no
database and an automatic target playlist, a file is filed under its
folder's name, as a scan of that folder files it, rather than in a
playlist named after the file. A Content Location that is missing, as on
a drive that is offline, counts as a file, so Overwrite no longer
empties its playlist while the drive is away.

* menu: Content Location takes a file or a folder

Content Scan's Content Location now opens one browser that lists folders
and files in every scan method: OK on a file picks it, <Use This
Directory> picks the folder. In Automatic it lists every file, as an
Automatic scan reads every file; Custom keeps listing the scan's
extensions. The Scan Single File toggle, which switched the browser to
files and was shown only in Custom with Search Recursively off, goes
with its setting and strings, and a scan of a file no longer turns
Search Recursively off behind the user's back. Import Content drops Scan
Directory and Scan File, as the Scan Directory list already had, leaving
Content Scan as the one way in; Scan File no longer started a scan. On
Apple, File > Open and the iOS document picker opened from Content
Location set the location as OK on a file does.

* task_queue: hold pushes from other threads for the main thread

task_queue_push() from another thread could meet a switch between the
threaded and the unthreaded queue, which frees the lock and condition
variable the push takes. On the unthreaded queue, with no switch at all,
it put the task on a list the main thread walks without a lock. Windows
pushes from the video thread under threaded video, and HID builds from
libusb's thread. Such a push now goes through the switch's gate: when
the gate is shut, or the unthreaded queue is up, the task goes onto a
lock-free list that the next gather pushes on the main thread, and the
push returns true. A push is also held while earlier ones still wait, so
a thread's pushes start in the order it made them. A BLOCKING task
refused there is cancelled, and its callback still runs. The switch test
pushes from four threads on the unthreaded queue and through 1000
switches, checks a held BLOCKING push, and holds the main thread inside
the gather that reopens the queue to show that a thread's next push does
not pass its held one.

* task_queue: cancel by ident and find safely from other threads

task_queue_cancel_task() and task_queue_find() from another thread met
the same switch and the same unlocked lists as a push did.
task_queue_cancel_ident() cancels by ident from any thread: a locked
walk when the threaded queue is up with nothing held, otherwise an ident
left for the next gather, which applies it after its pushes, to a held
push as well. A held push cancelled so retires cancelled without
running, and its callback runs. A find from another thread goes through
the switch's gate and reports false when it cannot walk the lists. The
queue's own workers and GCD threads skip the gate, and a find on the
main thread also sees held pushes. Each worker keeps its thread id in a
slot of its own and clears it before it reports that it left, so a
thread that reuses a joined worker's id is not taken for one. The switch
test cancels and finds from other threads on both queues and across a
put-off switch, and holds the main thread in the gather to show that a
thread's cancel of its own held push is not lost. Once extra workers
start, finds from handlers on the one worker and on an extra still skip
the gate, and a find with a joined extra worker's id from inside deinit
is refused.

* autoconfig: make connects reported on other threads on the main thread

libusb's hotplug thread and the Wii's HID poll thread report pads from
their own threads, where task_queue_find() can no longer answer exactly:
at a switch, or on the unthreaded queue, it reports false. Off the main
thread, input_autoconfigure_connect_ex() and
input_autoconfigure_disconnect() now copy the request into a main-thread
task and return true. Its callback makes the call on the main thread, in
the order the requests were made, so the duplicate check, the port's
sequence number and the settings it reads are the main thread's. A new
sample drives task_autodetect.c on the task queue with the rest stubbed,
reports pads from another thread, and checks that every find runs on the
main thread, that a pad reported twice queues one connect, that a
disconnect handed off is made and that a cancelled request is not.

* task_queue: wait, reset and deinit see pushes held for the main thread

On the unthreaded queue every push from another thread is held for the
next gather. A wait with nothing running returned without running it,
and a reset, the exit drain's included, left it to run normally
afterwards. Cancels held for the gather were never freed at deinit.

The unthreaded wait now drains held pushes first, and a reset drains
them into the runner before it cancels, so a held task meets a queued
one's fate. Deinit applies and frees held cancels; held pushes stay held
for the next runner's first gather, so no callback runs inside deinit.

* task_queue: make the Threaded Tasks flag atomic

Task handlers on the worker read it through task_queue_is_threaded(),
and the main thread writes it when the setting changes, so changing the
setting while a task ran was a data race.  ThreadSanitizer shows it on a
database scan whose queue is switched under it.

* task_queue: a parallel task names the extra workers it needs

retro_task_t gains parallel_workers, which task_init() clears. A
parallel push starts the extra workers it finds missing, up to the
larger of task_queue_set_parallel_workers() and the task's number, at
most 8. They serve every parallel task and stay until deinit, as before,
so the pool only grows; 0 keeps the frontend's number. The started count
is atomic, so a push that finds enough takes no lock, and one that needs
more claims the next entries under running_lock.

The parallel test has a task asking for three, a later one asking for
fewer, one asking for more than eight, a plain task whose number is
ignored, the frontend's number holding for a task asking for fewer, and
a task that leaves the number alone.

* task_queue: let deinit leave extra workers behind in detachable handlers

A parallel task may now also be detachable, and then runs on the extra
workers like any other parallel task. Deinit gives every worker the one
bound it gave the one worker, the extra workers first. One still inside
a detachable handler past it is left behind to finish alone, as the one
worker was, and one inside any other handler is still waited for. The
primitives of an init are held by deinit and by each worker it leaves
behind, and the last of them to let go frees them, so the one worker and
several extras can be left behind together.

A worker left behind, the one or an extra, now runs the task's cleanup
once the handler returns, then frees the task, so a detachable task
releases what it holds however it ends. Its callback still never runs;
its cleanup, like its handler, may touch only what the task owns.

The parallel test runs a detachable parallel task on an extra worker,
leaves one stuck past the bound, waits for a slow one that is not
detachable, and leaves the one worker and two extras behind together
with a new queue running beside them. Each task left behind is cleaned
up once, by its own worker, after its handler returns, and one not left
behind at its retirement as before. The switch test switches to the
unthreaded queue and back with extra workers live.

* task_queue: on Emscripten, sleep after a pass on the one worker only

On Emscripten every worker sleeps 1 ms after each handler call, a
pthreads workaround from when the one worker ran every task.  The extra
workers run parallel tasks, which work in many short passes, and the
sleep cost each of those passes 1 ms, more than the pass itself. The
extra workers no longer sleep; the one worker still does.

* emscripten: ask for at most 4 extra workers, and start 5 threads

In the browser a pthread the pool has not started is made on demand by
the main thread, and with the JS file system, where every thread's file
calls go through the main thread, 8 extra workers made each of those
calls about 40% dearer than 4 did.  The frontend asked for cores - 3
extra workers, up to 8; on Emscripten it now asks for at most 4, and the
build starts 5 pthreads up front, enough for the one worker and those 4.

* scan: read small plain files on parallel reader tasks, in list order

A database scan on the threaded queue pushes min(4, cores) reader tasks,
none below 2, as parallel tasks that ask for that many extra workers, in
HAVE_THREADS builds with lock-free atomics, except on Wii U, whose C
library and FAT layer take no locks, and on Vita, where Vita3K's sceIo
tables take none.  With Threaded Tasks off none starts and the scan
reads every file itself, stepped as before.

The scan hands the plain files and archives it would CRC whole to the
readers, up to 64 per reader ahead of it, each with its own copy of the
path.  A reader claims them in list order, opens and sizes each, leaves
one over 128 MiB to the scan unread and CRCs the rest a quantum per
call.  The scan takes each result at its own place in the content list,
so the databases see files in the same order as before, parks while the
next one is still being read, and reads itself whatever a reader could
not.

The scan never waits for its readers.  At its end, on cancel and once
the queue is unthreaded, each reader leaves at its next call and hands
back the file in hand.  Each reader drops its reference to what they
share in its cleanup, and the last reference frees it.  A queue deinit
leaves a reader stuck in a read behind after its 1 s bound and never
calls it again; the worker left with it runs its cleanup once the read
returns, which hands back the file in hand and drops the reference.

The scan test covers list order, files over the cutoff, failed pushes,
cancels and quits with a reader stuck in a read, and queue switches
under a scan; CI runs it under TSan, without threads and with no
readers.

* scan: run the command-line scan with no menu on the threaded queue

A command-line scan with no menu waits for the task queue to run dry,
and only a scan on the threaded queue gets readers.  In threaded builds
that run now uses the threaded queue whatever Threaded Tasks says; the
setting is not changed.

* menu: Import Content offers Scan Directory and Scan File

Both entries sit beside Content Scan when the database is built in.
OK on a file in Scan File's browser scans that file, as the Scan
action does.  The load_content_filter harness checks that the
entries are listed and that OK on a file starts its scan.

* manual scan: Content Location holds a full file path

The location can now be a file, so it is kept in a PATH_MAX_LENGTH
buffer, as is the scan task's copy.  A file whose path does not fit
is refused in the browser instead of being joined short.  The
load_content_filter harness sets a location deeper than a folder
buffer and checks that it is kept whole.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-26 13:28:42 +01:00

370 lines
11 KiB
Makefile

HAVE_STATIC_DUMMY ?= 0
ifeq ($(TARGET),)
ifeq ($(LIBRETRO),)
TARGET := retroarch.js
LIBRETRO = dummy
else
TARGET := $(LIBRETRO)_libretro.js
endif
endif
TARGET_BASE := $(subst .js,,$(TARGET))
OS = Emscripten
OBJ :=
DEFINES := -DRARCH_INTERNAL -DHAVE_MAIN
DEFINES += -DHAVE_FILTERS_BUILTIN -DHAVE_ONLINE_UPDATER -DHAVE_UPDATE_ASSETS -DHAVE_UPDATE_CORE_INFO
HAVE_STATESTREAM ?= 1
HAVE_PATCH = 1
HAVE_DSP_FILTER = 1
HAVE_VIDEO_FILTER = 1
HAVE_OVERLAY = 1
HAVE_NETWORKING ?= 1
HAVE_LIBRETRODB = 1
HAVE_COMPRESSION = 1
HAVE_UPDATE_ASSETS = 1
HAVE_ONLINE_UPDATER = 1
HAVE_GLSL = 1
HAVE_SCREENSHOTS = 1
HAVE_REWIND ?= 1
HAVE_RUNAHEAD ?= 1
HAVE_AUDIOMIXER = 1
HAVE_RVORBIS = 1
HAVE_CC_RESAMPLER ?= 1
HAVE_EGL ?= 0
HAVE_OPENGLES = 1
HAVE_RJPEG = 0
HAVE_RPNG = 1
HAVE_RWEBP = 1
HAVE_RDDS = 1
HAVE_EMSCRIPTEN = 1
HAVE_MENU ?= 1
HAVE_GFX_WIDGETS = 1
HAVE_RGUI = 1
HAVE_SDL = 0
HAVE_SDL2 = 0
HAVE_SHADERPIPELINE = 1
HAVE_STATIC_VIDEO_FILTERS = 1
HAVE_STATIC_AUDIO_FILTERS = 1
HAVE_CONFIGFILE = 1
HAVE_COMMAND = 1
HAVE_STDIN_CMD ?= 1
HAVE_CHEATS = 1
HAVE_RMODTRACKER = 1
HAVE_CORE_INFO_CACHE = 1
HAVE_7ZIP = 1
HAVE_RZSTD = 1
HAVE_BSV_MOVIE ?= 1
HAVE_CHD ?= 0
HAVE_RCHD = 1
HAVE_NETPLAYDISCOVERY ?= 0
# enables pthreads, requires special headers on the web server:
# see https://web.dev/articles/coop-coep
HAVE_THREADS ?= 0
# requires HAVE_THREADS
HAVE_AUDIOWORKLET ?= 0
# doesn't work on PROXY_TO_PTHREAD
HAVE_RWEBAUDIO ?= 1
# requires ASYNC or PROXY_TO_PTHREAD. Prefer another driver.
HAVE_AL ?= 0
# whether the browser thread is allowed to block to wait for audio to play, not CPU usage-friendly!
# currently this variable is only used by rwebaudio and audioworklet; openal will never busywait.
ALLOW_AUDIO_BUSYWAIT ?= 0
# asyncify: enable fibers and sleeping on the browser thread,
# this has a noticeable impact on compile time, output wasm size, and speed.
# see https://emscripten.org/docs/porting/asyncify.html for more info
ASYNC ?= 0
# minimal asyncify: better performance than full asyncify,
# but sleeping on the browser thread is only possible in some places.
MIN_ASYNC ?= 0
# JSPI: experimental asyncify alternative, see https://webassembly.org/features/
# currently (emscripten 4.0.15) this requires a patched emscripten toolchain.
JSPI ?= 0
# runs RetroArch on a pthread instead of the browser thread; implies HAVE_THREADS
PROXY_TO_PTHREAD ?= 0
# recommended FS when using HAVE_THREADS
HAVE_WASMFS ?= 0
# enables OPFS (origin private file system) and FETCHFS, requires PROXY_TO_PTHREAD or JSPI
HAVE_EXTRA_WASMFS ?= 0
# use the closure compiler to further minify output JS
CLOSURE_COMPILER ?= 0
# enable javascript filesystem tracking, incompatible with HAVE_WASMFS
FS_DEBUG ?= 0
# help diagnose GL problems (can cause issues in normal operation)
GL_DEBUG ?= 0
# enable runtime assertions (always enabled in debug builds)
# please use this when testing new emscripten-specific features!
ASSERTIONS ?= 0
# enable GLES 3.0 (WebGL 2).
# if left disabled, GLES 2.0 (WebGL 1) will be used.
HAVE_OPENGLES3 ?= 0
# does nothing on its own, but automatically selected by some other options
WASM_WORKERS = 0
LTO ?= 0
# the task worker and the 4 extra workers retroarch.c asks for at most
PTHREAD_POOL_SIZE ?= 5
ASYNCIFY_ADD ?= dynCall_*,emscripten_mainloop
ASYNCIFY_REMOVE ?= threaded_worker
STACK_SIZE ?= 4194304
INITIAL_HEAP ?= 134217728
# 4194304 ----- 4 MiB (Stack: recommended)
# 8388608 ----- 8 MiB
# 16777216 ---- 16 MiB
# 33554432 ---- 32 MiB
# 67108864 ---- 64 MiB
# 134217728 --- 128 MiB (Heap: recommended) (Stack: recommended for some cores [mupen64plus_next])
# 268435456 --- 256 MiB (Heap: recommended for some cores [mupen64plus_next])
# 536870912 --- 512 MiB (Heap: needed for some cores [mednafen_psx(_hw)])
# 1073741824 -- 1 GiB
# 1610612736 -- 1.5 GiB
# 2147483648 -- 2 GiB
OBJDIR := obj-emscripten
EXPORTED_FUNCTIONS = _main,_malloc,_free,_cmd_savefiles,_cmd_save_state,_cmd_load_state,_cmd_undo_save_state,_cmd_undo_load_state,_cmd_toggle_fullscreen,_cmd_take_screenshot,\
_cmd_toggle_menu,_cmd_reload_config,_cmd_toggle_grab_mouse,_cmd_toggle_game_focus,_cmd_reset,_cmd_toggle_pause,_cmd_pause,_cmd_unpause,\
_cmd_set_volume,_cmd_set_shader,_cmd_cheat_set_code,_cmd_cheat_get_code,_cmd_cheat_toggle_index,_cmd_cheat_get_code_state,_cmd_cheat_realloc,\
_cmd_cheat_get_size,_cmd_cheat_apply_cheats,EmscriptenSendCommand,EmscriptenReceiveCommandReply
EXPORTS := callMain,FS,PATH,ERRNO_CODES,ENV,stringToNewUTF8,UTF8ToString,Browser,EmscriptenSendCommand,EmscriptenReceiveCommandReply
LIBS := -s USE_ZLIB=1
CFLAGS := -s USE_ZLIB=1
ifeq ($(HAVE_EXTRA_WASMFS), 1)
LIBS += -lfetchfs.js -lopfs.js
DEFINES += -DHAVE_EXTRA_WASMFS
override HAVE_WASMFS = 1
ifeq ($(PROXY_TO_PTHREAD), 0)
# note: currently HAVE_EXTRA_WASMFS+JSPI requires ASSERTIONS to be turned off
ifeq ($(JSPI), 0)
$(error ERROR: HAVE_EXTRA_WASMFS requires PROXY_TO_PTHREAD or JSPI)
endif
endif
endif
ifeq ($(HAVE_WASMFS), 1)
LIBS += -s WASMFS -s FORCE_FILESYSTEM=1
endif
# note: real PROXY_TO_PTHREAD is not used here; we do the pthread management ourselves
ifeq ($(PROXY_TO_PTHREAD), 1)
LIBS += -s OFFSCREENCANVAS_SUPPORT
DEFINES += -DPROXY_TO_PTHREAD -DEMSCRIPTEN_STACK_SIZE=$(STACK_SIZE)
override HAVE_THREADS = 1
override WASM_WORKERS = 1
# use the default stack size for the browser thread; the RetroArch thread will be created with the specified stack size
override STACK_SIZE = 4194304
else ifeq ($(HAVE_AL), 1)
override ASYNC = 1
override MIN_ASYNC = 0
endif
ifeq ($(HAVE_SDL2), 1)
LIBS += -s USE_SDL=2
DEFINES += -DHAVE_SDL2
endif
LDFLAGS := -L. --no-heap-copy -s STACK_SIZE=$(STACK_SIZE) -s INITIAL_MEMORY=$(INITIAL_HEAP) \
-s EXPORTED_RUNTIME_METHODS=$(EXPORTS) -s EXIT_RUNTIME=1 \
-s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_FUNCTIONS="$(EXPORTED_FUNCTIONS)" \
-s MODULARIZE=1 -s EXPORT_ES6=1 -s EXPORT_NAME="libretro_$(subst -,_,$(LIBRETRO))" \
-s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=0 \
-s ENVIRONMENT=web,worker -s WASM_BIGINT=1 \
-s FETCH=1 \
--extern-pre-js emscripten/extern_pre.js \
--js-library emscripten/library_rwebcam.js \
--js-library emscripten/library_platform_emscripten.js
ifeq ($(HAVE_OPENGLES), 1)
ifeq ($(HAVE_OPENGLES3), 1)
LDFLAGS += -s FULL_ES3=1 -s MIN_WEBGL_VERSION=2 -s MAX_WEBGL_VERSION=2
else
LDFLAGS += -s FULL_ES2=1 -s MIN_WEBGL_VERSION=1 -s MAX_WEBGL_VERSION=2
endif
endif
ifeq ($(GL_DEBUG), 1)
LDFLAGS += -s GL_ASSERTIONS=1 -s GL_DEBUG=1
endif
ifeq ($(FS_DEBUG), 1)
LDFLAGS += -s FS_DEBUG=1
endif
ifeq ($(HAVE_RWEBAUDIO), 1)
LDFLAGS += --js-library emscripten/library_rwebaudio.js
DEFINES += -DHAVE_RWEBAUDIO
ifeq ($(PROXY_TO_PTHREAD), 1)
$(error ERROR: RWEBAUDIO is incompatible with PROXY_TO_PTHREAD)
endif
endif
ifeq ($(HAVE_AUDIOWORKLET), 1)
LDFLAGS += -s AUDIO_WORKLET=1
DEFINES += -DHAVE_AUDIOWORKLET
override WASM_WORKERS = 1
ifeq ($(HAVE_THREADS), 0)
$(error ERROR: AUDIOWORKLET requires HAVE_THREADS)
endif
endif
ifeq ($(HAVE_AL), 1)
LDFLAGS += -lopenal
DEFINES += -DHAVE_AL
endif
ifeq ($(PROXY_TO_PTHREAD), 1)
else ifeq ($(JSPI), 1)
else ifeq ($(ASYNC), 1)
else
DEFINES += -DEMSCRIPTEN_AUDIO_EXTERNAL_BLOCK
ifeq ($(MIN_ASYNC), 1)
DEFINES += -DEMSCRIPTEN_AUDIO_ASYNC_BLOCK
else
DEFINES += -DEMSCRIPTEN_AUDIO_FAKE_BLOCK
endif
ifneq ($(ALLOW_AUDIO_BUSYWAIT), 1)
DEFINES += -DEMSCRIPTEN_AUDIO_EXTERNAL_WRITE_BLOCK
endif
endif
ifeq ($(ALLOW_AUDIO_BUSYWAIT), 1)
DEFINES += -DEMSCRIPTEN_AUDIO_BUSYWAIT
endif
# explanation of some of these defines:
# EMSCRIPTEN_AUDIO_EXTERNAL_BLOCK: audio blocking occurs in the main loop instead of in the audio driver functions.
# EMSCRIPTEN_AUDIO_EXTERNAL_WRITE_BLOCK: along with above, enables external blocking in the write function.
# EMSCRIPTEN_AUDIO_BUSYWAIT: write function will busywait. init function may still use an external block.
# EMSCRIPTEN_AUDIO_ASYNC_BLOCK: external block uses emscripten_sleep (requires MIN_ASYNC).
# EMSCRIPTEN_AUDIO_FAKE_BLOCK: external block uses main loop timing (doesn't require asyncify).
# when building with either PROXY_TO_PTHREAD or ASYNC (full asyncify), none of the above are required.
ifeq ($(HAVE_THREADS), 1)
LDFLAGS += -pthread -s PTHREAD_POOL_SIZE=$(PTHREAD_POOL_SIZE)
CFLAGS += -pthread -s SHARED_MEMORY
endif
ifeq ($(WASM_WORKERS), 1)
LDFLAGS += -s WASM_WORKERS=1
endif
ifeq ($(JSPI), 1)
CFLAGS += -fwasm-exceptions
DEFINES += -DEMSCRIPTEN_ASYNCIFY -DEMSCRIPTEN_FULL_ASYNCIFY -DEMSCRIPTEN_JSPI
LDFLAGS += -s JSPI=1 -fwasm-exceptions
else ifeq ($(MIN_ASYNC), 1)
DEFINES += -DEMSCRIPTEN_ASYNCIFY -DEMSCRIPTEN_MIN_ASYNCIFY
LDFLAGS += -s ASYNCIFY=1 -s ASYNCIFY_STACK_SIZE=8192 -s ASYNCIFY_IGNORE_INDIRECT=1 -s ASYNCIFY_ADD='$(ASYNCIFY_ADD)' -s ASYNCIFY_REMOVE='$(ASYNCIFY_REMOVE)'
ifeq ($(DEBUG), 1)
LDFLAGS += -s ASYNCIFY_ADVISE #-s ASYNCIFY_DEBUG=1
endif
else ifeq ($(ASYNC), 1)
DEFINES += -DEMSCRIPTEN_ASYNCIFY -DEMSCRIPTEN_FULL_ASYNCIFY
LDFLAGS += -s ASYNCIFY=1 -s ASYNCIFY_STACK_SIZE=8192
ifeq ($(DEBUG), 1)
#LDFLAGS += -s ASYNCIFY_DEBUG=1 # broken?
endif
endif
ifeq ($(CLOSURE_COMPILER), 1)
LDFLAGS += --closure 1 --closure-args=--externs=emscripten/closure_externs.js --post-js emscripten/closure_post.js
endif
include Makefile.common
DEF_FLAGS += $(INCLUDE_DIRS) -Ideps
CFLAGS += $(DEF_FLAGS)
CXXFLAGS += $(DEF_FLAGS) -D__STDC_CONSTANT_MACROS
libretro =
libretro_new =
ifeq ($(HAVE_STATIC_DUMMY),1)
DEFINES += -DHAVE_STATIC_DUMMY
else
libretro = libretro_emscripten.bc
libretro_new = libretro_emscripten.a
endif
ifneq ($(V), 1)
Q := @
endif
ifeq ($(DEBUG), 1)
LDFLAGS += -O0 -g -gsource-map -s STACK_OVERFLOW_CHECK=2
# -O0 in cflags gives "too many locals" errors
CFLAGS += -O1 -g -gsource-map
override ASSERTIONS = 1
# "WASM_WORKERS is not currently compatible with `-fsanitize` tools"
ifeq ($(WASM_WORKERS), 0)
CFLAGS += -fsanitize=undefined
LDFLAGS += -fsanitize=undefined
endif
else
LDFLAGS += -O3
# WARNING: some optimizations can break some cores (ex: LTO breaks tyrquake)
ifeq ($(LTO), 1)
LDFLAGS += -flto
endif
CFLAGS += -O3
endif
ifneq ($(ASSERTIONS), 0)
LDFLAGS += -s ASSERTIONS=$(ASSERTIONS)
endif
CFLAGS += -Wall -I. -Ilibretro-common/include -std=gnu99
RARCH_OBJ := $(addprefix $(OBJDIR)/,$(OBJ))
all: $(TARGET)
$(libretro_new): ;
mv_libretro:
$(Q)mv -f $(libretro) $(libretro_new) || true
$(TARGET): $(RARCH_OBJ) $(libretro_new) mv_libretro
@$(if $(Q), $(shell echo echo "LD $@ \<obj\> $(libretro_new) $(LIBS) $(LDFLAGS)"),)
$(Q)$(LD) -o $@ $(RARCH_OBJ) $(libretro_new) $(LIBS) $(LDFLAGS)
$(OBJDIR)/%.o: %.c
@mkdir -p $(dir $@)
@$(if $(Q), $(shell echo echo CC $<),)
$(Q)$(CC) $(CFLAGS) $(DEFINES) -c -o $@ $<
$(OBJDIR)/%.o: %.cpp
@mkdir -p $(dir $@)
@$(if $(Q), $(shell echo echo CXX $<),)
$(Q)$(CXX) $(CXXFLAGS) $(DEFINES) -c -o $@ $<
clean:
rm -rf $(OBJDIR)
rm -f $(TARGET)
.PHONY: all clean mv_libretro