mirror of
https://github.com/ioquake/ioq3.git
synced 2024-11-10 07:11:46 +00:00
ioquake3.html replaces Emscripten-generated HTML shell
This enables several things: * Optionally load pk3 files from a web server at runtime instead of bundling them with Emscripten at build time * Set command line arguments via URL param * It's not ugly
This commit is contained in:
parent
e247b316a7
commit
db24dfe13f
4 changed files with 151 additions and 18 deletions
81
Makefile
81
Makefile
|
@ -1066,17 +1066,52 @@ ifeq ($(PLATFORM),emscripten)
|
||||||
|
|
||||||
CC=emcc
|
CC=emcc
|
||||||
ARCH=wasm32
|
ARCH=wasm32
|
||||||
|
BINEXT=.js
|
||||||
|
|
||||||
# LDFLAGS+=-s MAIN_MODULE is needed for dlopen() in client/server but it causes compile errors
|
# LDFLAGS+=-s MAIN_MODULE is needed for dlopen() in client/server but it causes compile errors
|
||||||
USE_RENDERER_DLOPEN=0
|
USE_RENDERER_DLOPEN=0
|
||||||
|
USE_OPENAL_DLOPEN=0
|
||||||
|
USE_CURL=0
|
||||||
|
HAVE_VM_COMPILED=false
|
||||||
|
BUILD_GAME_SO=0
|
||||||
|
BUILD_GAME_QVM=0
|
||||||
|
# Would be interesting to try to get the server working via WebRTC DataChannel.
|
||||||
|
# This would enable P2P play, hosting a server in the browser. Also,
|
||||||
|
# DataChannel is the only way to use UDP in the browser.
|
||||||
|
BUILD_SERVER=0
|
||||||
|
|
||||||
|
CLIENT_EXTRA_FILES+=code/web/ioquake3.html
|
||||||
|
|
||||||
|
CLIENT_CFLAGS+=-s USE_SDL=2
|
||||||
|
|
||||||
|
CLIENT_LDFLAGS+=-s TOTAL_MEMORY=256mb
|
||||||
|
CLIENT_LDFLAGS+=-s STACK_SIZE=5MB
|
||||||
|
# Informing Emscripten which WebGL versions we support makes the JS bundle smaller and faster to load.
|
||||||
|
CLIENT_LDFLAGS+=-s MIN_WEBGL_VERSION=2
|
||||||
|
CLIENT_LDFLAGS+=-s MAX_WEBGL_VERSION=2
|
||||||
|
CLIENT_LDFLAGS+=-s FULL_ES2=1
|
||||||
|
# The HTML file can use these functions to load extra files before the game starts.
|
||||||
|
CLIENT_LDFLAGS+=-s EXPORTED_RUNTIME_METHODS=FS,addRunDependency,removeRunDependency
|
||||||
|
CLIENT_LDFLAGS+=-s EXIT_RUNTIME=1
|
||||||
|
CLIENT_LDFLAGS+=-s EXPORT_ES6
|
||||||
|
CLIENT_LDFLAGS+=-s EXPORT_NAME=ioquake3
|
||||||
|
# Game data files can be packaged by emcc into a .data file that lives next to the wasm bundle
|
||||||
|
# and added to the virtual filesystem before the game starts. This requires the game data to be
|
||||||
|
# present at build time and it can't be changed afterward.
|
||||||
|
# For more flexibility, game data files can be loaded from a web server at runtime by listing
|
||||||
|
# them in ioq3-config.json. This way they don't have to be present at build time and can be
|
||||||
|
# changed later.
|
||||||
|
ifneq ($(wildcard $(BASEGAME)/*),)
|
||||||
|
CLIENT_LDFLAGS+=--preload-file $(BASEGAME)
|
||||||
|
EMSCRIPTEN_PRELOAD_FILE=1
|
||||||
|
CLIENT_EXTRA_FILES+=code/web/empty/ioq3-config.json
|
||||||
|
else
|
||||||
|
CLIENT_EXTRA_FILES+=code/web/$(BASEGAME)/ioq3-config.json
|
||||||
|
endif
|
||||||
|
|
||||||
BASE_CFLAGS=-fPIC -s USE_SDL=2
|
|
||||||
LDFLAGS=-s STACK_SIZE=5MB -s TOTAL_MEMORY=256MB -s MAX_WEBGL_VERSION=2 --preload-file $(BASEGAME)
|
|
||||||
OPTIMIZEVM = -O3
|
OPTIMIZEVM = -O3
|
||||||
OPTIMIZE = $(OPTIMIZEVM) -ffast-math
|
OPTIMIZE = $(OPTIMIZEVM) -ffast-math
|
||||||
|
|
||||||
FULLBINEXT=.html
|
|
||||||
|
|
||||||
SHLIBEXT=wasm
|
SHLIBEXT=wasm
|
||||||
SHLIBCFLAGS=-fPIC
|
SHLIBCFLAGS=-fPIC
|
||||||
SHLIBLDFLAGS=-s SIDE_MODULE
|
SHLIBLDFLAGS=-s SIDE_MODULE
|
||||||
|
@ -1129,19 +1164,16 @@ ifneq ($(BUILD_SERVER),0)
|
||||||
TARGETS += $(B)/$(SERVERBIN)$(FULLBINEXT)
|
TARGETS += $(B)/$(SERVERBIN)$(FULLBINEXT)
|
||||||
|
|
||||||
ifeq ($(PLATFORM),emscripten)
|
ifeq ($(PLATFORM),emscripten)
|
||||||
EMSCRIPTENOBJ += $(B)/$(SERVERBIN).js \
|
EMSCRIPTENOBJ+=$(B)/$(SERVERBIN).wasm
|
||||||
$(B)/$(SERVERBIN).wasm \
|
ifeq ($(EMSCRIPTEN_PRELOAD_FILE),1)
|
||||||
$(B)/$(SERVERBIN).data
|
EMSCRIPTENOBJ+=$(B)/$(SERVERBIN).data
|
||||||
|
endif
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifneq ($(BUILD_CLIENT),0)
|
ifneq ($(BUILD_CLIENT),0)
|
||||||
TARGETS += $(B)/$(CLIENTBIN)$(FULLBINEXT)
|
ifneq ($(PLATFORM),emscripten)
|
||||||
|
TARGETS += $(B)/$(CLIENTBIN)$(FULLBINEXT)
|
||||||
ifeq ($(PLATFORM),emscripten)
|
|
||||||
EMSCRIPTENOBJ += $(B)/$(CLIENTBIN).js \
|
|
||||||
$(B)/$(CLIENTBIN).wasm \
|
|
||||||
$(B)/$(CLIENTBIN).data
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifneq ($(USE_RENDERER_DLOPEN),0)
|
ifneq ($(USE_RENDERER_DLOPEN),0)
|
||||||
|
@ -1154,9 +1186,10 @@ ifneq ($(BUILD_CLIENT),0)
|
||||||
TARGETS += $(B)/$(CLIENTBIN)_opengl2$(FULLBINEXT)
|
TARGETS += $(B)/$(CLIENTBIN)_opengl2$(FULLBINEXT)
|
||||||
|
|
||||||
ifeq ($(PLATFORM),emscripten)
|
ifeq ($(PLATFORM),emscripten)
|
||||||
EMSCRIPTENOBJ += $(B)/$(CLIENTBIN)_opengl2.js \
|
EMSCRIPTENOBJ+=$(B)/$(CLIENTBIN)_opengl2.wasm32.wasm
|
||||||
$(B)/$(CLIENTBIN)_opengl2.wasm \
|
ifeq ($(EMSCRIPTEN_PRELOAD_FILE),1)
|
||||||
$(B)/$(CLIENTBIN)_opengl2.data
|
EMSCRIPTENOBJ+=$(B)/$(CLIENTBIN)_opengl2.wasm32.data
|
||||||
|
endif
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
@ -1504,6 +1537,7 @@ ifneq ($(BUILD_CLIENT),0)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
NAKED_TARGETS=$(shell echo $(TARGETS) | sed -e "s!$(B)/!!g")
|
NAKED_TARGETS=$(shell echo $(TARGETS) | sed -e "s!$(B)/!!g")
|
||||||
|
NAKED_EMSCRIPTENOBJ=$(shell echo $(EMSCRIPTENOBJ) | sed -e "s!$(B)/!!g")
|
||||||
|
|
||||||
print_list=-@for i in $(1); \
|
print_list=-@for i in $(1); \
|
||||||
do \
|
do \
|
||||||
|
@ -1559,7 +1593,20 @@ endif
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo " Output:"
|
@echo " Output:"
|
||||||
$(call print_list, $(NAKED_TARGETS))
|
$(call print_list, $(NAKED_TARGETS))
|
||||||
|
$(call print_list, $(NAKED_EMSCRIPTENOBJ))
|
||||||
@echo ""
|
@echo ""
|
||||||
|
ifeq ($(PLATFORM),emscripten)
|
||||||
|
ifneq ($(EMSCRIPTEN_PRELOAD_FILE),1)
|
||||||
|
@echo " Warning: Game files not found in '$(BASEGAME)'."
|
||||||
|
@echo " They will not be packaged by Emscripten or preloaded."
|
||||||
|
@echo " To run this build you must serve the game files from a web server"
|
||||||
|
@echo " and list their paths in ioq3-config.json."
|
||||||
|
@echo " To make a build that automatically loads the game files, create a"
|
||||||
|
@echo " directory called '$(BASEGAME)' and copy your game files into it, then"
|
||||||
|
@echo " 'emmake make clean' and rebuild."
|
||||||
|
@echo ""
|
||||||
|
endif
|
||||||
|
endif
|
||||||
ifneq ($(TARGETS),)
|
ifneq ($(TARGETS),)
|
||||||
ifndef DEBUG_MAKEFILE
|
ifndef DEBUG_MAKEFILE
|
||||||
@$(MAKE) $(TARGETS) $(B).zip V=$(V)
|
@$(MAKE) $(TARGETS) $(B).zip V=$(V)
|
||||||
|
@ -1575,7 +1622,7 @@ endif
|
||||||
ifneq ($(PLATFORM),darwin)
|
ifneq ($(PLATFORM),darwin)
|
||||||
ifdef ARCHIVE
|
ifdef ARCHIVE
|
||||||
@rm -f $@
|
@rm -f $@
|
||||||
@(cd $(B) && zip -r9 ../../$@ $(NAKED_TARGETS))
|
@(cd $(B) && zip -r9 ../../$@ $(NAKED_TARGETS) $(NAKED_EMSCRIPTENOBJ))
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
13
code/web/baseq3/ioq3-config.json
Normal file
13
code/web/baseq3/ioq3-config.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
{"src": "baseq3/pak0.pk3", "dst": "/baseq3"},
|
||||||
|
{"src": "baseq3/pak1.pk3", "dst": "/baseq3"},
|
||||||
|
{"src": "baseq3/pak2.pk3", "dst": "/baseq3"},
|
||||||
|
{"src": "baseq3/pak3.pk3", "dst": "/baseq3"},
|
||||||
|
{"src": "baseq3/pak4.pk3", "dst": "/baseq3"},
|
||||||
|
{"src": "baseq3/pak5.pk3", "dst": "/baseq3"},
|
||||||
|
{"src": "baseq3/pak6.pk3", "dst": "/baseq3"},
|
||||||
|
{"src": "baseq3/pak7.pk3", "dst": "/baseq3"},
|
||||||
|
{"src": "baseq3/pak8.pk3", "dst": "/baseq3"}
|
||||||
|
]
|
||||||
|
}
|
3
code/web/empty/ioq3-config.json
Normal file
3
code/web/empty/ioq3-config.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"files": []
|
||||||
|
}
|
70
code/web/ioquake3.html
Normal file
70
code/web/ioquake3.html
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
<!DOCTYPE html><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>ioquake3 Emscripten demo</title>
|
||||||
|
<style>
|
||||||
|
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: rgb(0, 0, 0); display:flex; align-items: center; justify-content: center; }
|
||||||
|
canvas { max-width: 100%; max-height: 100%; min-width: 100%; min-height: 100%; object-fit: contain; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<canvas id=canvas></canvas>
|
||||||
|
|
||||||
|
<script type=module>
|
||||||
|
if (window.location.protocol === 'file:') throw new Error('Unfortunately browser security restrictions prevent loading wasm from a file: URL. This file must be loaded from a web server. The easiest way to do this is probably to use Python\'s built-in web server by running `python3 -m http.server` in the top level ioq3 directory and then navigating to http://localhost:8000/code/web/ioquake3.html');
|
||||||
|
|
||||||
|
// First set up the command line arguments and the Emscripten filesystem.
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const basegame = urlParams.get('basegame') || 'baseq3';
|
||||||
|
let generatedArguments = `
|
||||||
|
+set net_enabled 0
|
||||||
|
+set r_mode -2
|
||||||
|
+set fs_game ${basegame}
|
||||||
|
`;
|
||||||
|
// Note that unfortunately "+" needs to be encoded as "%2b" in URL query strings or it will be stripped by the browser.
|
||||||
|
const queryArgs = urlParams.get('args');
|
||||||
|
if (queryArgs) generatedArguments += ` ${queryArgs} `;
|
||||||
|
|
||||||
|
// If buildPath is not specified, try to find a build in one of a few default paths.
|
||||||
|
let buildPath = urlParams.get('buildPath');
|
||||||
|
if (buildPath && !buildPath.endsWith('/')) buildPath += '/';
|
||||||
|
const buildPaths = buildPath ? [buildPath] : ['../../build/debug-emscripten-wasm32/', '../../build/release-emscripten-wasm32/', './'];
|
||||||
|
const scriptPaths = buildPaths.map(buildPath => buildPath + 'ioquake3_opengl2.wasm32.js');
|
||||||
|
const scriptResponses = await Promise.all(scriptPaths.map(p => fetch(p, {method: 'HEAD'})));
|
||||||
|
const validBuilds = scriptResponses.filter(r => r.ok).length;
|
||||||
|
const goodURL = (newPath) => {
|
||||||
|
const url = new URL(window.location);
|
||||||
|
url.searchParams.set('buildPath', newPath);
|
||||||
|
return url.toString().replace(/%2f/gi, '/');
|
||||||
|
};
|
||||||
|
if (validBuilds === 0) throw new Error(`Didn't find any wasm builds. Run \`emmake make debug\` to build one, or use the buildPath query parameter to specify a directory containing ioquake3_opengl2.wasm32.[js,wasm,data], e.g. ${goodURL('../../build/debug-emscripten-wasm32/')}`);
|
||||||
|
if (validBuilds > 1) throw new Error(`Found multiple valid builds at the following paths: [${buildPaths.filter((path, i)=>scriptResponses[i].ok)}]. Please specify which one to run by adding a buildPath query parameter to the URL, e.g. ${goodURL(buildPaths.filter((path, i)=>scriptResponses[i].ok)[0])}`);
|
||||||
|
const buildIndex = scriptResponses.findIndex(r => r.ok);
|
||||||
|
const selectedScript = scriptPaths[buildIndex];
|
||||||
|
buildPath = buildPaths[buildIndex];
|
||||||
|
const buildURL = new URL(buildPath, location.origin + location.pathname);
|
||||||
|
|
||||||
|
const configPromise = fetch(buildPath + 'ioq3-config.json').then(r => r.ok ? r.json() : {files: []});
|
||||||
|
|
||||||
|
const ioquake3 = (await import(selectedScript)).default;
|
||||||
|
ioquake3({
|
||||||
|
canvas: canvas,
|
||||||
|
arguments: generatedArguments.trim().split(/\s+/),
|
||||||
|
locateFile: (file) => buildPath + file,
|
||||||
|
preRun: [async (module) => {
|
||||||
|
module.addRunDependency('setup-ioq3-filesystem');
|
||||||
|
try {
|
||||||
|
const config = await configPromise;
|
||||||
|
const fetches = config.files.map(file => fetch(new URL(file.src, buildURL)));
|
||||||
|
for (let i = 0; i < config.files.length; i++) {
|
||||||
|
const response = await fetches[i];
|
||||||
|
if (!response.ok) continue;
|
||||||
|
const data = await response.arrayBuffer();
|
||||||
|
let name = config.files[i].src.match(/[^/]+$/)[0];
|
||||||
|
let dir = config.files[i].dst;
|
||||||
|
module.FS.mkdirTree(dir);
|
||||||
|
module.FS.writeFile(`${dir}/${name}`, new Uint8Array(data));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
module.removeRunDependency('setup-ioq3-filesystem');
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
</script>
|
Loading…
Reference in a new issue