diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 393b88fde2..c20d6fd868 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -6,16 +6,16 @@ jobs:
name: Linux
runs-on: ubuntu-20.04
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Install Dependencies
run: |
sudo apt-get update
sudo apt-get install libsdl2-dev
- name: Compile
- run: make release
+ run: make release -j$(nproc)
env:
ARCHIVE: 1
- - uses: actions/upload-artifact@v3
+ - uses: actions/upload-artifact@v4
with:
name: Linux
path: build/*.zip
@@ -23,27 +23,50 @@ jobs:
name: Windows
runs-on: windows-2019
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Compile
run: |
choco install zip
- make release
+ make release -j $env:NUMBER_OF_PROCESSORS
env:
ARCHIVE: 1
- - uses: actions/upload-artifact@v3
+ - uses: actions/upload-artifact@v4
with:
name: Windows
path: build/*.zip
macos:
name: macOS
- runs-on: macos-11
+ runs-on: macos-12
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Compile
- run: make release
+ run: make release -j$(sysctl -n hw.logicalcpu)
env:
ARCHIVE: 1
- - uses: actions/upload-artifact@v3
+ - uses: actions/upload-artifact@v4
with:
name: macOS
path: build/*.zip
+ web:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/checkout@v4
+ with:
+ repository: emscripten-core/emsdk
+ path: emsdk
+ - name: Install Dependencies
+ run: |
+ cd emsdk
+ ./emsdk install 3.1.58
+ ./emsdk activate 3.1.58
+ - name: Compile
+ env:
+ ARCHIVE: 1
+ run: |
+ source emsdk/emsdk_env.sh
+ emmake make release -j$(nproc)
+ - uses: actions/upload-artifact@v4
+ with:
+ name: Web
+ path: build/*.zip
diff --git a/.gitignore b/.gitignore
index eeb21870dd..f5d219f06f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,8 @@ Makefile.local
*.swp
*tags
*~
+/.vscode/
+/baseq3
# OS X
####################
diff --git a/Makefile b/Makefile
index c066619b35..7b08d4da8b 100644
--- a/Makefile
+++ b/Makefile
@@ -40,6 +40,9 @@ endif
ifndef BUILD_MISSIONPACK
BUILD_MISSIONPACK=
endif
+ifndef BUILD_RENDERER_OPENGL1
+ BUILD_RENDERER_OPENGL1=
+endif
ifndef BUILD_RENDERER_OPENGL2
BUILD_RENDERER_OPENGL2=
endif
@@ -62,6 +65,11 @@ ifeq ($(COMPILE_PLATFORM),cygwin)
PLATFORM=mingw32
endif
+# detect "emmake make"
+ifeq ($(findstring /emcc,$(CC)),/emcc)
+ PLATFORM=emscripten
+endif
+
ifndef PLATFORM
PLATFORM=$(COMPILE_PLATFORM)
endif
@@ -284,6 +292,7 @@ LIBTOMCRYPTSRCDIR=$(AUTOUPDATERSRCDIR)/rsa_tools/libtomcrypt-1.17
TOMSFASTMATHSRCDIR=$(AUTOUPDATERSRCDIR)/rsa_tools/tomsfastmath-0.13.1
LOKISETUPDIR=misc/setup
NSISDIR=misc/nsis
+WEBDIR=$(MOUNT_DIR)/web
SDLHDIR=$(MOUNT_DIR)/SDL2
LIBSDIR=$(MOUNT_DIR)/libs
@@ -806,6 +815,8 @@ else # ifdef MINGW
#############################################################################
ifeq ($(PLATFORM),freebsd)
+ # Use the default C compiler
+ TOOLS_CC=cc
# flags
BASE_CFLAGS = \
@@ -1041,6 +1052,67 @@ ifeq ($(PLATFORM),sunos)
else # ifeq sunos
+#############################################################################
+# SETUP AND BUILD -- emscripten
+#############################################################################
+
+ifeq ($(PLATFORM),emscripten)
+
+ ifneq ($(findstring /emcc,$(CC)),/emcc)
+ CC=emcc
+ endif
+ ARCH=wasm32
+ BINEXT=.js
+
+ # dlopen(), opengl1, and networking are not functional
+ USE_RENDERER_DLOPEN=0
+ USE_OPENAL_DLOPEN=0
+ BUILD_GAME_SO=0
+ BUILD_RENDERER_OPENGL1=0
+ BUILD_SERVER=0
+
+ CLIENT_CFLAGS+=-s USE_SDL=2
+
+ CLIENT_LDFLAGS+=-s TOTAL_MEMORY=256MB
+ CLIENT_LDFLAGS+=-s STACK_SIZE=5MB
+ CLIENT_LDFLAGS+=-s MIN_WEBGL_VERSION=1 -s MAX_WEBGL_VERSION=2
+
+ # 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 client-config.json. This way they don't have to be present at build time and can be
+ # changed later.
+ ifeq ($(EMSCRIPTEN_PRELOAD_FILE),1)
+ ifeq ($(wildcard $(BASEGAME)/*),)
+ $(error "No files in '$(BASEGAME)' directory for emscripten to preload.")
+ endif
+ CLIENT_LDFLAGS+=--preload-file $(BASEGAME)
+ endif
+
+ OPTIMIZEVM = -O3
+ OPTIMIZE = $(OPTIMIZEVM) -ffast-math
+
+ # These allow a warning-free build.
+ # Some of these warnings may actually be legit problems and should be fixed at some point.
+ BASE_CFLAGS+=-Wno-deprecated-non-prototype -Wno-dangling-else -Wno-implicit-const-int-float-conversion -Wno-misleading-indentation -Wno-format-overflow -Wno-logical-not-parentheses -Wno-absolute-value
+
+ DEBUG_CFLAGS=-g3 -O0 # -fsanitize=address -fsanitize=undefined
+ # Emscripten needs debug compiler flags to be passed to the linker as well
+ DEBUG_LDFLAGS=$(DEBUG_CFLAGS)
+
+ SHLIBEXT=wasm
+ SHLIBCFLAGS=-fPIC
+ SHLIBLDFLAGS=-s SIDE_MODULE
+
+else # ifeq emscripten
+
#############################################################################
# SETUP AND BUILD -- GENERIC
#############################################################################
@@ -1059,6 +1131,7 @@ endif #OpenBSD
endif #NetBSD
endif #IRIX
endif #SunOS
+endif #emscripten
ifndef CC
CC=gcc
@@ -1088,12 +1161,18 @@ endif
ifneq ($(BUILD_CLIENT),0)
ifneq ($(USE_RENDERER_DLOPEN),0)
- TARGETS += $(B)/$(CLIENTBIN)$(FULLBINEXT) $(B)/renderer_opengl1_$(SHLIBNAME)
+ TARGETS += $(B)/$(CLIENTBIN)$(FULLBINEXT)
+
+ ifneq ($(BUILD_RENDERER_OPENGL1),0)
+ TARGETS += $(B)/renderer_opengl1_$(SHLIBNAME)
+ endif
ifneq ($(BUILD_RENDERER_OPENGL2),0)
TARGETS += $(B)/renderer_opengl2_$(SHLIBNAME)
endif
else
- TARGETS += $(B)/$(CLIENTBIN)$(FULLBINEXT)
+ ifneq ($(BUILD_RENDERER_OPENGL1),0)
+ TARGETS += $(B)/$(CLIENTBIN)$(FULLBINEXT)
+ endif
ifneq ($(BUILD_RENDERER_OPENGL2),0)
TARGETS += $(B)/$(CLIENTBIN)_opengl2$(FULLBINEXT)
endif
@@ -1140,6 +1219,42 @@ ifneq ($(BUILD_AUTOUPDATER),0)
TARGETS += $(B)/$(AUTOUPDATER_BIN)
endif
+ifeq ($(PLATFORM),emscripten)
+ ifneq ($(BUILD_SERVER),0)
+ GENERATEDTARGETS += $(B)/$(SERVERBIN).$(ARCH).wasm
+ ifeq ($(EMSCRIPTEN_PRELOAD_FILE),1)
+ GENERATEDTARGETS += $(B)/$(SERVERBIN).$(ARCH).data
+ endif
+ endif
+
+ ifneq ($(BUILD_CLIENT),0)
+ TARGETS += $(B)/$(CLIENTBIN).html
+ ifneq ($(EMSCRIPTEN_PRELOAD_FILE),1)
+ TARGETS += $(B)/$(CLIENTBIN)-config.json
+ endif
+
+ ifneq ($(USE_RENDERER_DLOPEN),0)
+ GENERATEDTARGETS += $(B)/$(CLIENTBIN).$(ARCH).wasm
+ ifeq ($(EMSCRIPTEN_PRELOAD_FILE),1)
+ GENERATEDTARGETS += $(B)/$(CLIENTBIN).$(ARCH).data
+ endif
+ else
+ ifneq ($(BUILD_RENDERER_OPENGL1),0)
+ GENERATEDTARGETS += $(B)/$(CLIENTBIN).$(ARCH).wasm
+ ifeq ($(EMSCRIPTEN_PRELOAD_FILE),1)
+ GENERATEDTARGETS += $(B)/$(CLIENTBIN).$(ARCH).data
+ endif
+ endif
+ ifneq ($(BUILD_RENDERER_OPENGL2),0)
+ GENERATEDTARGETS += $(B)/$(CLIENTBIN)_opengl2.$(ARCH).wasm
+ ifeq ($(EMSCRIPTEN_PRELOAD_FILE),1)
+ GENERATEDTARGETS += $(B)/$(CLIENTBIN)_opengl2.$(ARCH).data
+ endif
+ endif
+ endif
+ endif
+endif
+
ifeq ($(USE_OPENAL),1)
CLIENT_CFLAGS += -DUSE_OPENAL
ifeq ($(USE_OPENAL_DLOPEN),1)
@@ -1405,7 +1520,8 @@ all: debug release
debug:
@$(MAKE) targets B=$(BD) CFLAGS="$(CFLAGS) $(BASE_CFLAGS) $(DEPEND_CFLAGS)" \
OPTIMIZE="$(DEBUG_CFLAGS)" OPTIMIZEVM="$(DEBUG_CFLAGS)" \
- CLIENT_CFLAGS="$(CLIENT_CFLAGS)" SERVER_CFLAGS="$(SERVER_CFLAGS)" V=$(V)
+ CLIENT_CFLAGS="$(CLIENT_CFLAGS)" SERVER_CFLAGS="$(SERVER_CFLAGS)" V=$(V) \
+ LDFLAGS="$(LDFLAGS) $(DEBUG_LDFLAGS)"
release:
@$(MAKE) targets B=$(BR) CFLAGS="$(CFLAGS) $(BASE_CFLAGS) $(DEPEND_CFLAGS)" \
@@ -1442,6 +1558,7 @@ ifneq ($(BUILD_CLIENT),0)
endif
NAKED_TARGETS=$(shell echo $(TARGETS) | sed -e "s!$(B)/!!g")
+NAKED_GENERATEDTARGETS=$(shell echo $(GENERATEDTARGETS) | sed -e "s!$(B)/!!g")
print_list=-@for i in $(1); \
do \
@@ -1497,6 +1614,7 @@ endif
@echo ""
@echo " Output:"
$(call print_list, $(NAKED_TARGETS))
+ $(call print_list, $(NAKED_GENERATEDTARGETS))
@echo ""
ifneq ($(TARGETS),)
ifndef DEBUG_MAKEFILE
@@ -1513,9 +1631,10 @@ endif
ifneq ($(PLATFORM),darwin)
ifdef ARCHIVE
@rm -f $@
- @(cd $(B) && zip -r9 ../../$@ $(NAKED_TARGETS))
+ @(cd $(B) && zip -r9 ../../$@ $(NAKED_TARGETS) $(NAKED_GENERATEDTARGETS))
endif
endif
+ @:
makedirs:
@$(MKDIR) $(B)/autoupdater
@@ -1869,10 +1988,15 @@ Q3OBJ = \
ifdef MINGW
Q3OBJ += \
$(B)/client/con_passive.o
+else
+ifeq ($(PLATFORM),emscripten)
+ Q3OBJ += \
+ $(B)/client/con_passive.o
else
Q3OBJ += \
$(B)/client/con_tty.o
endif
+endif
Q3R2OBJ = \
$(B)/renderergl2/tr_animation.o \
@@ -2964,6 +3088,19 @@ $(B)/$(MISSIONPACK)/qcommon/%.asm: $(CMDIR)/%.c $(Q3LCC)
$(DO_Q3LCC_MISSIONPACK)
+#############################################################################
+# EMSCRIPTEN
+#############################################################################
+
+$(B)/$(CLIENTBIN).html: $(WEBDIR)/client.html
+ $(echo_cmd) "SED $@"
+ $(Q)sed 's/__CLIENTBIN__/$(CLIENTBIN)/g;s/__BASEGAME__/$(BASEGAME)/g;s/__EMSCRIPTEN_PRELOAD_FILE__/$(EMSCRIPTEN_PRELOAD_FILE)/g' < $< > $@
+
+$(B)/$(CLIENTBIN)-config.json: $(WEBDIR)/client-config.json
+ $(echo_cmd) "CP $@"
+ $(Q)cp $< $@
+
+
#############################################################################
# MISC
#############################################################################
@@ -2987,13 +3124,18 @@ ifneq ($(BUILD_GAME_SO),0)
endif
ifneq ($(BUILD_CLIENT),0)
- $(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/$(CLIENTBIN)$(FULLBINEXT) $(COPYBINDIR)/$(CLIENTBIN)$(FULLBINEXT)
ifneq ($(USE_RENDERER_DLOPEN),0)
+ $(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/$(CLIENTBIN)$(FULLBINEXT) $(COPYBINDIR)/$(CLIENTBIN)$(FULLBINEXT)
+ ifneq ($(BUILD_RENDERER_OPENGL1),0)
$(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/renderer_opengl1_$(SHLIBNAME) $(COPYBINDIR)/renderer_opengl1_$(SHLIBNAME)
+ endif
ifneq ($(BUILD_RENDERER_OPENGL2),0)
$(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/renderer_opengl2_$(SHLIBNAME) $(COPYBINDIR)/renderer_opengl2_$(SHLIBNAME)
endif
else
+ ifneq ($(BUILD_RENDERER_OPENGL1),0)
+ $(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/$(CLIENTBIN)$(FULLBINEXT) $(COPYBINDIR)/$(CLIENTBIN)$(FULLBINEXT)
+ endif
ifneq ($(BUILD_RENDERER_OPENGL2),0)
$(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/$(CLIENTBIN)_opengl2$(FULLBINEXT) $(COPYBINDIR)/$(CLIENTBIN)_opengl2$(FULLBINEXT)
endif
@@ -3044,6 +3186,7 @@ clean2:
@rm -f $(OBJ_D_FILES)
@rm -f $(STRINGOBJ)
@rm -f $(TARGETS)
+ @rm -f $(GENERATEDTARGETS)
toolsclean: toolsclean-debug toolsclean-release
@@ -3083,6 +3226,11 @@ dist:
# DEPENDENCIES
#############################################################################
+# Rebuild every target if Makefile or Makefile.local changes
+ifneq ($(DEPEND_MAKEFILE),0)
+.EXTRA_PREREQS:= $(MAKEFILE_LIST)
+endif
+
ifneq ($(B),)
OBJ_D_FILES=$(filter %.d,$(OBJ:%.o=%.d))
TOOLSOBJ_D_FILES=$(filter %.d,$(TOOLSOBJ:%.o=%.d))
diff --git a/README.md b/README.md
index 9ea0c40b9d..671fd28ead 100644
--- a/README.md
+++ b/README.md
@@ -50,8 +50,9 @@ Some of the major features currently implemented are:
* HTTP/FTP download redirection (using cURL)
* Multiuser support on Windows systems (user specific game data
is stored in "%APPDATA%\Quake3")
-* PNG support
-* Many, many bug fixes
+ * PNG support
+ * Web support via Emscripten
+ * Many, many bug fixes
The map editor and associated compiling tools are not included. We suggest you
use a modern copy from .
@@ -120,6 +121,20 @@ For macOS, building a Universal Binary 2 (macOS 10.9+, arm64, x86_64)
4. Copy the resulting ioquake3.app in /build/release-darwin-universal2
to your /Applications/ioquake3 folder.
+For Web, building with Emscripten
+ 1. Follow the installation instructions for the Emscripten SDK including
+ setting up the environment with emsdk_env.
+ 2. Run `emmake make debug` (or release).
+ 3. Copy or symlink your baseq3 pk3 files into the `build/debug-emscripten-wasm32/baseq3`
+ directory so they can be loaded at run-time. Only game files listed in
+ `client-config.json` will be loaded.
+ 4. Start a web server serving this directory. `python3 -m http.server`
+ is an easy default that you may already have installed.
+ 5. Open `http://localhost:8000/build/debug-emscripten-wasm32/ioquake3.html`
+ in a web browser. Open the developer console to see errors and warnings.
+ 6. Debugging the C code is possible using a Chrome extension. For details
+ see https://developer.chrome.com/blog/wasm-debugging-2020
+
Installation, for *nix
1. Set the COPYDIR variable in the shell to be where you installed Quake 3
@@ -137,7 +152,9 @@ x86_64.
The following variables may be set, either on the command line or in
Makefile.local:
-```txt
+```
+ DEPEND_MAKEFILE - set to 0 to disable rebuilding all targets when
+ the Makefile or Makefile.local is changed
CFLAGS - use this for custom CFLAGS
V - set to show cc command line when building
DEFAULT_BASEDIR - extra path to search for baseq3 and such
@@ -151,6 +168,8 @@ Makefile.local:
SERVERBIN - rename 'ioq3ded' server binary
CLIENTBIN - rename 'ioquake3' client binary
USE_RENDERER_DLOPEN - build and use the renderer in a library
+ BUILD_RENDERER_OPENGL1 build the opengl1 client / renderer library
+ BUILD_RENDERER_OPENGL2 build the opengl2 client / renderer library
USE_YACC - use yacc to update code/tools/lcc/lburg/gram.c
BASEGAME - rename 'baseq3'
BASEGAME_CFLAGS - custom CFLAGS for basegame
@@ -178,13 +197,34 @@ Makefile.local:
DEBUG_CFLAGS - C compiler flags to use for building debug version
COPYDIR - the target installation directory
TEMPDIR - specify user defined directory for temp files
+ EMSCRIPTEN_PRELOAD_FILE - set to 1 to package 'baseq3' (BASEGAME) directory
+ containing pk3s and loose files as a single
+ .data file that is loaded instead of listing
+ individual files in client-config.json
```
The defaults for these variables differ depending on the target platform.
-## Console
-### New cvars
+# OpenGL ES support
+
+The opengl2 renderer (the default) supports OpenGL ES 2+. Though there
+are many missing features and the performance may not be sufficient for
+embedded System-on-a-Chip and mobile platforms.
+
+The opengl1 renderer does not have OpenGL ES support.
+
+The opengl2 renderer will try both OpenGL and OpenGL ES APIs to find one that
+works. The `r_preferOpenGLES` cvar controls which API to try first.
+Set it to -1 for auto (default), 0 for OpenGL, and 1 for OpenGL ES. It should be
+set using command line arguments:
+
+ ioquake3 +set cl_renderer opengl2 +set r_preferOpenGLES 1
+
+
+# Console
+
+## New cvars
```txt
cl_autoRecordDemo - record a new demo on each map change
diff --git a/SECURITY.md b/SECURITY.md
index 8732e8b16a..fd5c9f37fb 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -4,7 +4,7 @@ We take security very seriously at ioquake3. We welcome any peer review of our 1
### Where should I report security issues?
In order to give the community time to respond and upgrade we strongly urge you report all security issues privately.
-Please e-mail zachary@ioquake.org directly to provide details and repro steps and we will respond as soon as possible, but please note:
+Please e-mail jack@ioquake.org directly to provide details and repro steps and we will respond as soon as possible, but please note:
### This is an entirely free software project without much in the way of external funding or sponsorships.
### We cannot guarantee quick responses but we very much appreciate your discretion when reporting security vulnerabilities.
diff --git a/code/autoupdater/autoupdater.c b/code/autoupdater/autoupdater.c
index 4a0520273c..e013a1f433 100644
--- a/code/autoupdater/autoupdater.c
+++ b/code/autoupdater/autoupdater.c
@@ -933,10 +933,13 @@ static void waitToApplyUpdates(void)
OS forcibly closes the pipe), we will unblock. Then we can loop on
kill() until the process is truly gone. */
int x = 0;
+ struct timespec req;
+ req.tv_sec = 0;
+ req.tv_nsec = 100000000;
read(3, &x, sizeof (x));
info("Pipe has closed, waiting for process to fully go away now.");
while (kill(options.waitforprocess, 0) == 0) {
- usleep(100000);
+ nanosleep(&req, NULL);
}
#endif
}
diff --git a/code/client/cl_avi.c b/code/client/cl_avi.c
index 2f0dffd675..00cf7b1d3c 100644
--- a/code/client/cl_avi.c
+++ b/code/client/cl_avi.c
@@ -357,11 +357,12 @@ qboolean CL_OpenAVIForWriting( const char *fileName )
else
afd.motionJpeg = qfalse;
- // Buffers only need to store RGB pixels.
+ // Capture buffer stores RGB pixels but OpenGL ES reads RGBA and converts to RGB in-place.
+ // Encode buffer only needs to store RGB pixels.
// Allocate a bit more space for the capture buffer to account for possible
// padding at the end of pixel lines, and padding for alignment
#define MAX_PACK_LEN 16
- afd.cBuffer = Z_Malloc((afd.width * 3 + MAX_PACK_LEN - 1) * afd.height + MAX_PACK_LEN - 1);
+ afd.cBuffer = Z_Malloc((afd.width * 4 + MAX_PACK_LEN - 1) * afd.height + MAX_PACK_LEN - 1);
// raw avi files have pixel lines start on 4-byte boundaries
afd.eBuffer = Z_Malloc(PAD(afd.width * 3, AVI_LINE_PADDING) * afd.height);
diff --git a/code/qcommon/common.c b/code/qcommon/common.c
index 56a4f34e04..4453b6737a 100644
--- a/code/qcommon/common.c
+++ b/code/qcommon/common.c
@@ -2741,7 +2741,14 @@ void Com_Init( char *commandLine ) {
// init commands and vars
//
com_altivec = Cvar_Get ("com_altivec", "1", CVAR_ARCHIVE);
+#ifdef __EMSCRIPTEN__
+ // Under Emscripten the browser handles throttling the frame rate.
+ // Manual framerate throttling interacts poorly with Emscripten's
+ // browser-driven event loop. So default throttling to off.
+ com_maxfps = Cvar_Get ("com_maxfps", "0", CVAR_ARCHIVE);
+#else
com_maxfps = Cvar_Get ("com_maxfps", "125", CVAR_ARCHIVE);
+#endif
com_blood = Cvar_Get ("com_blood", "1", CVAR_ARCHIVE);
com_logfile = Cvar_Get ("logfile", "0", CVAR_TEMP );
diff --git a/code/qcommon/q_platform.h b/code/qcommon/q_platform.h
index 72dbfe1de1..53a532648a 100644
--- a/code/qcommon/q_platform.h
+++ b/code/qcommon/q_platform.h
@@ -290,6 +290,22 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#endif
+//================================================================== EMSCRIPTEN ===
+
+#ifdef __EMSCRIPTEN__
+
+#define OS_STRING "emscripten"
+#define ID_INLINE inline
+#define PATH_SEP '/'
+
+#define ARCH_STRING "wasm32"
+
+#define Q3_LITTLE_ENDIAN
+
+#define DLL_EXT ".wasm"
+
+#endif
+
//================================================================== Q3VM ===
#ifdef Q3_VM
diff --git a/code/renderercommon/qgl.h b/code/renderercommon/qgl.h
index 38f9919f43..f226e40884 100644
--- a/code/renderercommon/qgl.h
+++ b/code/renderercommon/qgl.h
@@ -80,7 +80,6 @@ extern void (APIENTRYP qglUnlockArraysEXT) (void);
GLE(void, TexParameterf, GLenum target, GLenum pname, GLfloat param) \
GLE(void, TexParameteri, GLenum target, GLenum pname, GLint param) \
GLE(void, TexSubImage2D, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels) \
- GLE(void, Translatef, GLfloat x, GLfloat y, GLfloat z) \
GLE(void, Viewport, GLint x, GLint y, GLsizei width, GLsizei height) \
// OpenGL 1.0/1.1 and OpenGL ES 1.x but not OpenGL 3.2 core profile
@@ -98,6 +97,7 @@ extern void (APIENTRYP qglUnlockArraysEXT) (void);
GLE(void, ShadeModel, GLenum mode) \
GLE(void, TexCoordPointer, GLint size, GLenum type, GLsizei stride, const GLvoid *ptr) \
GLE(void, TexEnvf, GLenum target, GLenum pname, GLfloat param) \
+ GLE(void, Translatef, GLfloat x, GLfloat y, GLfloat z) \
GLE(void, VertexPointer, GLint size, GLenum type, GLsizei stride, const GLvoid *ptr) \
// OpenGL 1.0/1.1 and 3.2 core profile but not OpenGL ES 1.x
diff --git a/code/renderergl1/tr_curve.c b/code/renderergl1/tr_curve.c
index eaabba4615..c5c9d98d1f 100644
--- a/code/renderergl1/tr_curve.c
+++ b/code/renderergl1/tr_curve.c
@@ -306,13 +306,13 @@ srfGridMesh_t *R_CreateSurfaceGridMesh(int width, int height,
grid->heightLodError = /*ri.Hunk_Alloc*/ ri.Malloc( height * 4 );
Com_Memcpy( grid->heightLodError, errorTable[1], height * 4 );
#else
- grid = ri.Hunk_Alloc( size );
+ grid = ri.Hunk_Alloc( size, h_low );
Com_Memset(grid, 0, size);
- grid->widthLodError = ri.Hunk_Alloc( width * 4 );
+ grid->widthLodError = ri.Hunk_Alloc( width * 4, h_low );
Com_Memcpy( grid->widthLodError, errorTable[0], width * 4 );
- grid->heightLodError = ri.Hunk_Alloc( height * 4 );
+ grid->heightLodError = ri.Hunk_Alloc( height * 4, h_low );
Com_Memcpy( grid->heightLodError, errorTable[1], height * 4 );
#endif
diff --git a/code/renderergl1/tr_sky.c b/code/renderergl1/tr_sky.c
index 1d12e924ec..2f5f7786f7 100644
--- a/code/renderergl1/tr_sky.c
+++ b/code/renderergl1/tr_sky.c
@@ -387,12 +387,17 @@ static void DrawSkySide( struct image_s *image, const int mins[2], const int max
static void DrawSkyBox( shader_t *shader )
{
int i;
+ float w_offset, w_scale;
+ float h_offset, h_scale;
sky_min = 0;
sky_max = 1;
Com_Memset( s_skyTexCoords, 0, sizeof( s_skyTexCoords ) );
+ w_offset = h_offset = 0;
+ w_scale = h_scale = 1;
+
for (i=0 ; i<6 ; i++)
{
int sky_mins_subd[2], sky_maxs_subd[2];
@@ -432,6 +437,15 @@ static void DrawSkyBox( shader_t *shader )
else if ( sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS )
sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS;
+ if ( !haveClampToEdge )
+ {
+ w_offset = 0.5f / shader->sky.outerbox[sky_texorder[i]]->width;
+ h_offset = 0.5f / shader->sky.outerbox[sky_texorder[i]]->height;
+
+ w_scale = 1.0f - w_offset * 2;
+ h_scale = 1.0f - h_offset * 2;
+ }
+
//
// iterate through the subdivisions
//
@@ -444,6 +458,12 @@ static void DrawSkyBox( shader_t *shader )
i,
s_skyTexCoords[t][s],
s_skyPoints[t][s] );
+
+ s_skyTexCoords[t][s][0] *= w_scale;
+ s_skyTexCoords[t][s][0] += w_offset;
+
+ s_skyTexCoords[t][s][1] *= h_scale;
+ s_skyTexCoords[t][s][1] += h_offset;
}
}
diff --git a/code/renderergl2/glsl/depthblur_fp.glsl b/code/renderergl2/glsl/depthblur_fp.glsl
index d63df88a6a..205ee4512e 100644
--- a/code/renderergl2/glsl/depthblur_fp.glsl
+++ b/code/renderergl2/glsl/depthblur_fp.glsl
@@ -52,10 +52,9 @@ vec4 depthGaussian1D(sampler2D imageMap, sampler2D depthMap, vec2 tex, float zFa
#endif
float zLimit = 5.0 / zFar;
- int i, j;
- for (i = 0; i < 2; i++)
+ for (int i = 0; i < 2; i++)
{
- for (j = 1; j < BLUR_SIZE; j++)
+ for (int j = 1; j < BLUR_SIZE; j++)
{
vec2 offset = direction * (float(j) - 0.25) + nudge;
#if defined(USE_DEPTH)
diff --git a/code/renderergl2/glsl/generic_vp.glsl b/code/renderergl2/glsl/generic_vp.glsl
index a005526371..e6af9f8134 100644
--- a/code/renderergl2/glsl/generic_vp.glsl
+++ b/code/renderergl2/glsl/generic_vp.glsl
@@ -16,8 +16,16 @@ attribute vec4 attr_TexCoord0;
attribute vec4 attr_TexCoord1;
#endif
-uniform vec4 u_DiffuseTexMatrix;
-uniform vec4 u_DiffuseTexOffTurb;
+#if defined(USE_TCMOD)
+uniform vec4 u_DiffuseTexMatrix0;
+uniform vec4 u_DiffuseTexMatrix1;
+uniform vec4 u_DiffuseTexMatrix2;
+uniform vec4 u_DiffuseTexMatrix3;
+uniform vec4 u_DiffuseTexMatrix4;
+uniform vec4 u_DiffuseTexMatrix5;
+uniform vec4 u_DiffuseTexMatrix6;
+uniform vec4 u_DiffuseTexMatrix7;
+#endif
#if defined(USE_TCGEN) || defined(USE_RGBAGEN)
uniform vec3 u_LocalViewOrigin;
@@ -140,19 +148,28 @@ vec2 GenTexCoords(int TCGen, vec3 position, vec3 normal, vec3 TCGenVector0, vec3
#endif
#if defined(USE_TCMOD)
-vec2 ModTexCoords(vec2 st, vec3 position, vec4 texMatrix, vec4 offTurb)
+vec2 ModTexCoords(vec2 st, vec3 position, vec4 texMatrix[8])
{
- float amplitude = offTurb.z;
- float phase = offTurb.w * 2.0 * M_PI;
- vec2 st2;
- st2.x = st.x * texMatrix.x + (st.y * texMatrix.z + offTurb.x);
- st2.y = st.x * texMatrix.y + (st.y * texMatrix.w + offTurb.y);
-
+ vec2 st2 = st;
vec2 offsetPos = vec2(position.x + position.z, position.y);
-
- vec2 texOffset = sin(offsetPos * (2.0 * M_PI / 1024.0) + vec2(phase));
-
- return st2 + texOffset * amplitude;
+
+ st2 = vec2(st2.x * texMatrix[0].x + st2.y * texMatrix[0].y + texMatrix[0].z,
+ st2.x * texMatrix[1].x + st2.y * texMatrix[1].y + texMatrix[1].z);
+ st2 += texMatrix[0].w * sin(offsetPos * (2.0 * M_PI / 1024.0) + vec2(texMatrix[1].w * 2.0 * M_PI));
+
+ st2 = vec2(st2.x * texMatrix[2].x + st2.y * texMatrix[2].y + texMatrix[2].z,
+ st2.x * texMatrix[3].x + st2.y * texMatrix[3].y + texMatrix[3].z);
+ st2 += texMatrix[2].w * sin(offsetPos * (2.0 * M_PI / 1024.0) + vec2(texMatrix[3].w * 2.0 * M_PI));
+
+ st2 = vec2(st2.x * texMatrix[4].x + st2.y * texMatrix[4].y + texMatrix[4].z,
+ st2.x * texMatrix[5].x + st2.y * texMatrix[5].y + texMatrix[5].z);
+ st2 += texMatrix[4].w * sin(offsetPos * (2.0 * M_PI / 1024.0) + vec2(texMatrix[5].w * 2.0 * M_PI));
+
+ st2 = vec2(st2.x * texMatrix[6].x + st2.y * texMatrix[6].y + texMatrix[6].z,
+ st2.x * texMatrix[7].x + st2.y * texMatrix[7].y + texMatrix[7].z);
+ st2 += texMatrix[6].w * sin(offsetPos * (2.0 * M_PI / 1024.0) + vec2(texMatrix[7].w * 2.0 * M_PI));
+
+ return st2;
}
#endif
@@ -236,7 +253,16 @@ void main()
#endif
#if defined(USE_TCMOD)
- var_DiffuseTex = ModTexCoords(tex, position, u_DiffuseTexMatrix, u_DiffuseTexOffTurb);
+ vec4 diffuseTexMatrix[8];
+ diffuseTexMatrix[0] = u_DiffuseTexMatrix0;
+ diffuseTexMatrix[1] = u_DiffuseTexMatrix1;
+ diffuseTexMatrix[2] = u_DiffuseTexMatrix2;
+ diffuseTexMatrix[3] = u_DiffuseTexMatrix3;
+ diffuseTexMatrix[4] = u_DiffuseTexMatrix4;
+ diffuseTexMatrix[5] = u_DiffuseTexMatrix5;
+ diffuseTexMatrix[6] = u_DiffuseTexMatrix6;
+ diffuseTexMatrix[7] = u_DiffuseTexMatrix7;
+ var_DiffuseTex = ModTexCoords(tex, position, diffuseTexMatrix);
#else
var_DiffuseTex = tex;
#endif
diff --git a/code/renderergl2/glsl/lightall_vp.glsl b/code/renderergl2/glsl/lightall_vp.glsl
index 428cf1e6d1..8ff5e7bbfb 100644
--- a/code/renderergl2/glsl/lightall_vp.glsl
+++ b/code/renderergl2/glsl/lightall_vp.glsl
@@ -37,8 +37,14 @@ uniform vec3 u_LocalViewOrigin;
#endif
#if defined(USE_TCMOD)
-uniform vec4 u_DiffuseTexMatrix;
-uniform vec4 u_DiffuseTexOffTurb;
+uniform vec4 u_DiffuseTexMatrix0;
+uniform vec4 u_DiffuseTexMatrix1;
+uniform vec4 u_DiffuseTexMatrix2;
+uniform vec4 u_DiffuseTexMatrix3;
+uniform vec4 u_DiffuseTexMatrix4;
+uniform vec4 u_DiffuseTexMatrix5;
+uniform vec4 u_DiffuseTexMatrix6;
+uniform vec4 u_DiffuseTexMatrix7;
#endif
uniform mat4 u_ModelViewProjectionMatrix;
@@ -114,19 +120,28 @@ vec2 GenTexCoords(int TCGen, vec3 position, vec3 normal, vec3 TCGenVector0, vec3
#endif
#if defined(USE_TCMOD)
-vec2 ModTexCoords(vec2 st, vec3 position, vec4 texMatrix, vec4 offTurb)
+vec2 ModTexCoords(vec2 st, vec3 position, vec4 texMatrix[8])
{
- float amplitude = offTurb.z;
- float phase = offTurb.w * 2.0 * M_PI;
- vec2 st2;
- st2.x = st.x * texMatrix.x + (st.y * texMatrix.z + offTurb.x);
- st2.y = st.x * texMatrix.y + (st.y * texMatrix.w + offTurb.y);
-
+ vec2 st2 = st;
vec2 offsetPos = vec2(position.x + position.z, position.y);
- vec2 texOffset = sin(offsetPos * (2.0 * M_PI / 1024.0) + vec2(phase));
+ st2 = vec2(st2.x * texMatrix[0].x + st2.y * texMatrix[0].y + texMatrix[0].z,
+ st2.x * texMatrix[1].x + st2.y * texMatrix[1].y + texMatrix[1].z);
+ st2 += texMatrix[0].w * sin(offsetPos * (2.0 * M_PI / 1024.0) + vec2(texMatrix[1].w * 2.0 * M_PI));
+
+ st2 = vec2(st2.x * texMatrix[2].x + st2.y * texMatrix[2].y + texMatrix[2].z,
+ st2.x * texMatrix[3].x + st2.y * texMatrix[3].y + texMatrix[3].z);
+ st2 += texMatrix[2].w * sin(offsetPos * (2.0 * M_PI / 1024.0) + vec2(texMatrix[3].w * 2.0 * M_PI));
+
+ st2 = vec2(st2.x * texMatrix[4].x + st2.y * texMatrix[4].y + texMatrix[4].z,
+ st2.x * texMatrix[5].x + st2.y * texMatrix[5].y + texMatrix[5].z);
+ st2 += texMatrix[4].w * sin(offsetPos * (2.0 * M_PI / 1024.0) + vec2(texMatrix[5].w * 2.0 * M_PI));
+
+ st2 = vec2(st2.x * texMatrix[6].x + st2.y * texMatrix[6].y + texMatrix[6].z,
+ st2.x * texMatrix[7].x + st2.y * texMatrix[7].y + texMatrix[7].z);
+ st2 += texMatrix[6].w * sin(offsetPos * (2.0 * M_PI / 1024.0) + vec2(texMatrix[7].w * 2.0 * M_PI));
- return st2 + texOffset * amplitude;
+ return st2;
}
#endif
@@ -183,7 +198,16 @@ void main()
#endif
#if defined(USE_TCMOD)
- var_TexCoords.xy = ModTexCoords(texCoords, position, u_DiffuseTexMatrix, u_DiffuseTexOffTurb);
+ vec4 diffuseTexMatrix[8];
+ diffuseTexMatrix[0] = u_DiffuseTexMatrix0;
+ diffuseTexMatrix[1] = u_DiffuseTexMatrix1;
+ diffuseTexMatrix[2] = u_DiffuseTexMatrix2;
+ diffuseTexMatrix[3] = u_DiffuseTexMatrix3;
+ diffuseTexMatrix[4] = u_DiffuseTexMatrix4;
+ diffuseTexMatrix[5] = u_DiffuseTexMatrix5;
+ diffuseTexMatrix[6] = u_DiffuseTexMatrix6;
+ diffuseTexMatrix[7] = u_DiffuseTexMatrix7;
+ var_TexCoords.xy = ModTexCoords(texCoords, position, diffuseTexMatrix);
#else
var_TexCoords.xy = texCoords;
#endif
diff --git a/code/renderergl2/glsl/shadowmask_fp.glsl b/code/renderergl2/glsl/shadowmask_fp.glsl
index 2b57e3ba4b..56d480fb6e 100644
--- a/code/renderergl2/glsl/shadowmask_fp.glsl
+++ b/code/renderergl2/glsl/shadowmask_fp.glsl
@@ -103,14 +103,19 @@ void main()
vec4 shadowpos = u_ShadowMvp * biasPos;
+ if ( depth >= 1.0 - DEPTH_MAX_ERROR )
+ {
+ result = 1.0;
+ }
+ else
#if defined(USE_SHADOW_CASCADE)
if (all(lessThan(abs(shadowpos.xyz), vec3(abs(shadowpos.w)))))
- {
#endif
+ {
shadowpos.xyz = shadowpos.xyz * (0.5 / shadowpos.w) + vec3(0.5);
result = PCF(u_ShadowMap, shadowpos.xy, shadowpos.z);
-#if defined(USE_SHADOW_CASCADE)
}
+#if defined(USE_SHADOW_CASCADE)
else
{
shadowpos = u_ShadowMvp2 * biasPos;
diff --git a/code/renderergl2/glsl/ssao_fp.glsl b/code/renderergl2/glsl/ssao_fp.glsl
index f3054404fb..2171f6e7cd 100644
--- a/code/renderergl2/glsl/ssao_fp.glsl
+++ b/code/renderergl2/glsl/ssao_fp.glsl
@@ -77,8 +77,7 @@ float ambientOcclusion(sampler2D depthMap, const vec2 tex, const float zFarDivZN
float invZFar = 1.0 / zFar;
float zLimit = 20.0 * invZFar;
- int i;
- for (i = 0; i < NUM_SAMPLES; i++)
+ for (int i = 0; i < NUM_SAMPLES; i++)
{
vec2 offset = rmat * poissonDisc[i] * offsetScale;
float sampleDiff = getLinearDepth(depthMap, tex + offset, zFarDivZNear) - sampleZ;
diff --git a/code/renderergl2/tr_backend.c b/code/renderergl2/tr_backend.c
index 7387101bdf..7154877983 100644
--- a/code/renderergl2/tr_backend.c
+++ b/code/renderergl2/tr_backend.c
@@ -342,7 +342,7 @@ void RB_BeginDrawingView (void) {
{
FBO_t *fbo = backEnd.viewParms.targetFbo;
- if (fbo == NULL && (!r_postProcess->integer || !(backEnd.refdef.rdflags & RDF_NOWORLDMODEL)))
+ if (fbo == NULL)
fbo = tr.renderFbo;
if (tr.renderCubeFbo && fbo == tr.renderCubeFbo)
@@ -455,7 +455,7 @@ void RB_RenderDrawSurfList( drawSurf_t *drawSurfs, int numDrawSurfs ) {
for (i = 0, drawSurf = drawSurfs ; i < numDrawSurfs ; i++, drawSurf++) {
if ( drawSurf->sort == oldSort && drawSurf->cubemapIndex == oldCubemapIndex) {
- if (backEnd.depthFill && shader && shader->sort != SS_OPAQUE)
+ if (backEnd.depthFill && shader && (shader->sort != SS_OPAQUE && shader->sort != SS_PORTAL))
continue;
// fast path, same as previous sort
@@ -484,7 +484,7 @@ void RB_RenderDrawSurfList( drawSurf_t *drawSurfs, int numDrawSurfs ) {
oldCubemapIndex = cubemapIndex;
}
- if (backEnd.depthFill && shader && shader->sort != SS_OPAQUE)
+ if (backEnd.depthFill && shader && (shader->sort != SS_OPAQUE && shader->sort != SS_PORTAL))
continue;
//
@@ -708,7 +708,7 @@ void RE_StretchRaw (int x, int y, int w, int h, int cols, int rows, const byte *
if (glRefConfig.framebufferObject)
{
- FBO_Bind(r_postProcess->integer ? NULL : tr.renderFbo);
+ FBO_Bind(tr.renderFbo);
}
RB_SetGL2D();
@@ -732,6 +732,7 @@ void RE_StretchRaw (int x, int y, int w, int h, int cols, int rows, const byte *
}
void RE_UploadCinematic (int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty) {
+ byte *buffer;
GLuint texture;
if (!tr.scratchImage[client])
@@ -746,7 +747,18 @@ void RE_UploadCinematic (int w, int h, int cols, int rows, const byte *data, int
if ( cols != tr.scratchImage[client]->width || rows != tr.scratchImage[client]->height ) {
tr.scratchImage[client]->width = tr.scratchImage[client]->uploadWidth = cols;
tr.scratchImage[client]->height = tr.scratchImage[client]->uploadHeight = rows;
- qglTextureImage2DEXT(texture, GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
+
+ if ( qglesMajorVersion >= 1 ) {
+ buffer = ri.Hunk_AllocateTempMemory( 3 * cols * rows );
+
+ R_ConvertTextureFormat( data, cols, rows, GL_RGB, GL_UNSIGNED_BYTE, buffer );
+ qglTextureImage2DEXT(texture, GL_TEXTURE_2D, 0, GL_RGB, cols, rows, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer);
+
+ ri.Hunk_FreeTempMemory( buffer );
+ } else {
+ qglTextureImage2DEXT(texture, GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
+ }
+
qglTextureParameterfEXT(texture, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
qglTextureParameterfEXT(texture, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
qglTextureParameterfEXT(texture, GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
@@ -755,7 +767,16 @@ void RE_UploadCinematic (int w, int h, int cols, int rows, const byte *data, int
if (dirty) {
// otherwise, just subimage upload it so that drivers can tell we are going to be changing
// it and don't try and do a texture compression
- qglTextureSubImage2DEXT(texture, GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data);
+ if ( qglesMajorVersion >= 1 ) {
+ buffer = ri.Hunk_AllocateTempMemory( 3 * cols * rows );
+
+ R_ConvertTextureFormat( data, cols, rows, GL_RGB, GL_UNSIGNED_BYTE, buffer );
+ qglTextureSubImage2DEXT(texture, GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGB, GL_UNSIGNED_BYTE, buffer);
+
+ ri.Hunk_FreeTempMemory( buffer );
+ } else {
+ qglTextureSubImage2DEXT(texture, GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data);
+ }
}
}
}
@@ -793,7 +814,7 @@ const void *RB_StretchPic ( const void *data ) {
cmd = (const stretchPicCommand_t *)data;
if (glRefConfig.framebufferObject)
- FBO_Bind(r_postProcess->integer ? NULL : tr.renderFbo);
+ FBO_Bind(tr.renderFbo);
RB_SetGL2D();
@@ -1140,14 +1161,14 @@ const void *RB_DrawSurfs( const void *data ) {
if (glRefConfig.occlusionQuery)
{
tr.sunFlareQueryActive[tr.sunFlareQueryIndex] = qtrue;
- qglBeginQuery(GL_SAMPLES_PASSED, tr.sunFlareQuery[tr.sunFlareQueryIndex]);
+ qglBeginQuery(glRefConfig.occlusionQueryTarget, tr.sunFlareQuery[tr.sunFlareQueryIndex]);
}
RB_DrawSun(0.3, tr.sunFlareShader);
if (glRefConfig.occlusionQuery)
{
- qglEndQuery(GL_SAMPLES_PASSED);
+ qglEndQuery(glRefConfig.occlusionQueryTarget);
}
FBO_Bind(oldFbo);
@@ -1202,15 +1223,12 @@ const void *RB_DrawBuffer( const void *data ) {
// clear screen for debugging
if ( r_clear->integer ) {
- qglClearColor( 1, 0, 0.5, 1 );
- qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
-
if (glRefConfig.framebufferObject && tr.renderFbo) {
FBO_Bind(tr.renderFbo);
-
- qglClearColor( 1, 0, 0.5, 1 );
- qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
}
+
+ qglClearColor( 1, 0, 0.5, 1 );
+ qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
}
return (const void *)(cmd + 1);
@@ -1381,18 +1399,15 @@ const void *RB_SwapBuffers( const void *data ) {
if (glRefConfig.framebufferObject)
{
- if (!r_postProcess->integer)
+ if (tr.msaaResolveFbo && r_hdr->integer)
{
- if (tr.msaaResolveFbo && r_hdr->integer)
- {
- // Resolving an RGB16F MSAA FBO to the screen messes with the brightness, so resolve to an RGB16F FBO first
- FBO_FastBlit(tr.renderFbo, NULL, tr.msaaResolveFbo, NULL, GL_COLOR_BUFFER_BIT, GL_NEAREST);
- FBO_FastBlit(tr.msaaResolveFbo, NULL, NULL, NULL, GL_COLOR_BUFFER_BIT, GL_NEAREST);
- }
- else if (tr.renderFbo)
- {
- FBO_FastBlit(tr.renderFbo, NULL, NULL, NULL, GL_COLOR_BUFFER_BIT, GL_NEAREST);
- }
+ // Resolving an RGB16F MSAA FBO to the screen messes with the brightness, so resolve to an RGB16F FBO first
+ FBO_FastBlit(tr.renderFbo, NULL, tr.msaaResolveFbo, NULL, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+ FBO_FastBlit(tr.msaaResolveFbo, NULL, NULL, NULL, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+ }
+ else if (tr.renderFbo)
+ {
+ FBO_FastBlit(tr.renderFbo, NULL, NULL, NULL, GL_COLOR_BUFFER_BIT, GL_NEAREST);
}
}
@@ -1454,7 +1469,7 @@ RB_PostProcess
const void *RB_PostProcess(const void *data)
{
const postProcessCommand_t *cmd = data;
- FBO_t *srcFbo;
+ FBO_t *srcFbo, *dstFbo;
ivec4_t srcBox, dstBox;
qboolean autoExposure;
@@ -1475,6 +1490,8 @@ const void *RB_PostProcess(const void *data)
}
srcFbo = tr.renderFbo;
+ dstFbo = tr.renderFbo;
+
if (tr.msaaResolveFbo)
{
// Resolve the MSAA before anything else
@@ -1508,13 +1525,13 @@ const void *RB_PostProcess(const void *data)
if (r_hdr->integer && (r_toneMap->integer || r_forceToneMap->integer))
{
autoExposure = r_autoExposure->integer || r_forceAutoExposure->integer;
- RB_ToneMap(srcFbo, srcBox, NULL, dstBox, autoExposure);
- }
- else if (r_cameraExposure->value == 0.0f)
- {
- FBO_FastBlit(srcFbo, srcBox, NULL, dstBox, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+
+ // Use an intermediate FBO because it can't blit to the same FBO directly
+ // and can't read from an MSAA dstFbo later.
+ RB_ToneMap(srcFbo, srcBox, tr.screenScratchFbo, srcBox, autoExposure);
+ FBO_FastBlit(tr.screenScratchFbo, srcBox, srcFbo, srcBox, GL_COLOR_BUFFER_BIT, GL_NEAREST);
}
- else
+ else if (r_cameraExposure->value != 0.0f)
{
vec4_t color;
@@ -1523,17 +1540,20 @@ const void *RB_PostProcess(const void *data)
color[2] = pow(2, r_cameraExposure->value); //exp2(r_cameraExposure->value);
color[3] = 1.0f;
- FBO_Blit(srcFbo, srcBox, NULL, NULL, dstBox, NULL, color, 0);
+ FBO_BlitFromTexture(tr.whiteImage, NULL, NULL, srcFbo, srcBox, NULL, color, GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO);
}
}
if (r_drawSunRays->integer)
- RB_SunRays(NULL, srcBox, NULL, dstBox);
+ RB_SunRays(srcFbo, srcBox, srcFbo, srcBox);
if (1)
- RB_BokehBlur(NULL, srcBox, NULL, dstBox, backEnd.refdef.blurFactor);
+ RB_BokehBlur(srcFbo, srcBox, srcFbo, srcBox, backEnd.refdef.blurFactor);
else
- RB_GaussianBlur(backEnd.refdef.blurFactor);
+ RB_GaussianBlur(srcFbo, srcFbo, backEnd.refdef.blurFactor);
+
+ if (srcFbo != dstFbo)
+ FBO_FastBlit(srcFbo, srcBox, dstFbo, dstBox, GL_COLOR_BUFFER_BIT, GL_NEAREST);
#if 0
if (0)
@@ -1549,7 +1569,7 @@ const void *RB_PostProcess(const void *data)
if (scale < 0.01f)
scale = 5.0f;
- FBO_FastBlit(NULL, NULL, tr.quarterFbo[0], NULL, GL_COLOR_BUFFER_BIT, GL_LINEAR);
+ FBO_FastBlit(dstFbo, NULL, tr.quarterFbo[0], NULL, GL_COLOR_BUFFER_BIT, GL_LINEAR);
iQtrBox[0] = backEnd.viewParms.viewportX * tr.quarterImage[0]->width / (float)glConfig.vidWidth;
iQtrBox[1] = backEnd.viewParms.viewportY * tr.quarterImage[0]->height / (float)glConfig.vidHeight;
@@ -1595,7 +1615,7 @@ const void *RB_PostProcess(const void *data)
SetViewportAndScissor();
- FBO_FastBlit(tr.quarterFbo[1], NULL, NULL, NULL, GL_COLOR_BUFFER_BIT, GL_LINEAR);
+ FBO_FastBlit(tr.quarterFbo[1], NULL, dstFbo, NULL, GL_COLOR_BUFFER_BIT, GL_LINEAR);
FBO_Bind(NULL);
}
#endif
@@ -1604,42 +1624,42 @@ const void *RB_PostProcess(const void *data)
{
ivec4_t dstBox;
VectorSet4(dstBox, 0, glConfig.vidHeight - 128, 128, 128);
- FBO_BlitFromTexture(tr.sunShadowDepthImage[0], NULL, NULL, NULL, dstBox, NULL, NULL, 0);
+ FBO_BlitFromTexture(tr.sunShadowDepthImage[0], NULL, NULL, dstFbo, dstBox, NULL, NULL, 0);
VectorSet4(dstBox, 128, glConfig.vidHeight - 128, 128, 128);
- FBO_BlitFromTexture(tr.sunShadowDepthImage[1], NULL, NULL, NULL, dstBox, NULL, NULL, 0);
+ FBO_BlitFromTexture(tr.sunShadowDepthImage[1], NULL, NULL, dstFbo, dstBox, NULL, NULL, 0);
VectorSet4(dstBox, 256, glConfig.vidHeight - 128, 128, 128);
- FBO_BlitFromTexture(tr.sunShadowDepthImage[2], NULL, NULL, NULL, dstBox, NULL, NULL, 0);
+ FBO_BlitFromTexture(tr.sunShadowDepthImage[2], NULL, NULL, dstFbo, dstBox, NULL, NULL, 0);
VectorSet4(dstBox, 384, glConfig.vidHeight - 128, 128, 128);
- FBO_BlitFromTexture(tr.sunShadowDepthImage[3], NULL, NULL, NULL, dstBox, NULL, NULL, 0);
+ FBO_BlitFromTexture(tr.sunShadowDepthImage[3], NULL, NULL, dstFbo, dstBox, NULL, NULL, 0);
}
if (0 && r_shadows->integer == 4)
{
ivec4_t dstBox;
VectorSet4(dstBox, 512 + 0, glConfig.vidHeight - 128, 128, 128);
- FBO_BlitFromTexture(tr.pshadowMaps[0], NULL, NULL, NULL, dstBox, NULL, NULL, 0);
+ FBO_BlitFromTexture(tr.pshadowMaps[0], NULL, NULL, dstFbo, dstBox, NULL, NULL, 0);
VectorSet4(dstBox, 512 + 128, glConfig.vidHeight - 128, 128, 128);
- FBO_BlitFromTexture(tr.pshadowMaps[1], NULL, NULL, NULL, dstBox, NULL, NULL, 0);
+ FBO_BlitFromTexture(tr.pshadowMaps[1], NULL, NULL, dstFbo, dstBox, NULL, NULL, 0);
VectorSet4(dstBox, 512 + 256, glConfig.vidHeight - 128, 128, 128);
- FBO_BlitFromTexture(tr.pshadowMaps[2], NULL, NULL, NULL, dstBox, NULL, NULL, 0);
+ FBO_BlitFromTexture(tr.pshadowMaps[2], NULL, NULL, dstFbo, dstBox, NULL, NULL, 0);
VectorSet4(dstBox, 512 + 384, glConfig.vidHeight - 128, 128, 128);
- FBO_BlitFromTexture(tr.pshadowMaps[3], NULL, NULL, NULL, dstBox, NULL, NULL, 0);
+ FBO_BlitFromTexture(tr.pshadowMaps[3], NULL, NULL, dstFbo, dstBox, NULL, NULL, 0);
}
if (0)
{
ivec4_t dstBox;
VectorSet4(dstBox, 256, glConfig.vidHeight - 256, 256, 256);
- FBO_BlitFromTexture(tr.renderDepthImage, NULL, NULL, NULL, dstBox, NULL, NULL, 0);
+ FBO_BlitFromTexture(tr.renderDepthImage, NULL, NULL, dstFbo, dstBox, NULL, NULL, 0);
VectorSet4(dstBox, 512, glConfig.vidHeight - 256, 256, 256);
- FBO_BlitFromTexture(tr.screenShadowImage, NULL, NULL, NULL, dstBox, NULL, NULL, 0);
+ FBO_BlitFromTexture(tr.screenShadowImage, NULL, NULL, dstFbo, dstBox, NULL, NULL, 0);
}
if (0)
{
ivec4_t dstBox;
VectorSet4(dstBox, 256, glConfig.vidHeight - 256, 256, 256);
- FBO_BlitFromTexture(tr.sunRaysImage, NULL, NULL, NULL, dstBox, NULL, NULL, 0);
+ FBO_BlitFromTexture(tr.sunRaysImage, NULL, NULL, dstFbo, dstBox, NULL, NULL, 0);
}
#if 0
@@ -1651,8 +1671,8 @@ const void *RB_PostProcess(const void *data)
if (cubemapIndex)
{
VectorSet4(dstBox, 0, glConfig.vidHeight - 256, 256, 256);
- //FBO_BlitFromTexture(tr.renderCubeImage, NULL, NULL, NULL, dstBox, &tr.testcubeShader, NULL, 0);
- FBO_BlitFromTexture(tr.cubemaps[cubemapIndex - 1].image, NULL, NULL, NULL, dstBox, &tr.testcubeShader, NULL, 0);
+ //FBO_BlitFromTexture(tr.renderCubeImage, NULL, NULL, dstFbo, dstBox, &tr.testcubeShader, NULL, 0);
+ FBO_BlitFromTexture(tr.cubemaps[cubemapIndex - 1].image, NULL, NULL, dstFbo, dstBox, &tr.testcubeShader, NULL, 0);
}
}
#endif
diff --git a/code/renderergl2/tr_bsp.c b/code/renderergl2/tr_bsp.c
index b316351e09..57a4efc3bf 100644
--- a/code/renderergl2/tr_bsp.c
+++ b/code/renderergl2/tr_bsp.c
@@ -276,7 +276,7 @@ static void R_LoadLightmaps( lump_t *l, lump_t *surfs ) {
tr.deluxemaps = ri.Hunk_Alloc( tr.numLightmaps * sizeof(image_t *), h_low );
textureInternalFormat = GL_RGBA8;
- if (r_hdr->integer)
+ if (r_hdr->integer && !qglesMajorVersion)
{
// Check for the first hdr lightmap, if it exists, use GL_RGBA16 for textures.
char filename[MAX_QPATH];
@@ -617,7 +617,7 @@ static shader_t *ShaderForShaderNum( int shaderNum, int lightmapNum ) {
lightmapNum = LIGHTMAP_WHITEIMAGE;
}
- shader = R_FindShader( dsh->shader, lightmapNum, qtrue );
+ shader = R_FindShaderEx( dsh->shader, FatLightmap( lightmapNum ), qtrue, lightmapNum );
// if the shader had errors, just use default shader
if ( shader->defaultShader ) {
@@ -706,7 +706,7 @@ static void ParseFace( dsurface_t *ds, drawVert_t *verts, float *hdrVertColors,
surf->fogIndex = LittleLong( ds->fogNum ) + 1;
// get shader value
- surf->shader = ShaderForShaderNum( ds->shaderNum, FatLightmap(realLightmapNum) );
+ surf->shader = ShaderForShaderNum( ds->shaderNum, realLightmapNum );
if ( r_singleShader->integer && !surf->shader->isSky ) {
surf->shader = tr.defaultShader;
}
@@ -813,7 +813,7 @@ static void ParseMesh ( dsurface_t *ds, drawVert_t *verts, float *hdrVertColors,
surf->fogIndex = LittleLong( ds->fogNum ) + 1;
// get shader value
- surf->shader = ShaderForShaderNum( ds->shaderNum, FatLightmap(realLightmapNum) );
+ surf->shader = ShaderForShaderNum( ds->shaderNum, realLightmapNum );
if ( r_singleShader->integer && !surf->shader->isSky ) {
surf->shader = tr.defaultShader;
}
diff --git a/code/renderergl2/tr_cmds.c b/code/renderergl2/tr_cmds.c
index 38ba45d2c3..2025c41d90 100644
--- a/code/renderergl2/tr_cmds.c
+++ b/code/renderergl2/tr_cmds.c
@@ -348,7 +348,13 @@ void RE_BeginFrame( stereoFrame_t stereoFrame ) {
//
if ( r_measureOverdraw->integer )
{
- if ( glConfig.stencilBits < 4 )
+ if ( qglesMajorVersion >= 1 && !glRefConfig.readStencil )
+ {
+ ri.Printf( PRINT_WARNING, "OpenGL ES needs GL_NV_read_stencil to read stencil bits to measure overdraw\n" );
+ ri.Cvar_Set( "r_measureOverdraw", "0" );
+ r_measureOverdraw->modified = qfalse;
+ }
+ else if ( glConfig.stencilBits < 4 )
{
ri.Printf( PRINT_ALL, "Warning: not enough stencil bits to measure overdraw: %d\n", glConfig.stencilBits );
ri.Cvar_Set( "r_measureOverdraw", "0" );
@@ -426,6 +432,13 @@ void RE_BeginFrame( stereoFrame_t stereoFrame ) {
}
else
{
+ if (qglesMajorVersion >= 1 && r_anaglyphMode->integer)
+ {
+ ri.Printf( PRINT_WARNING, "OpenGL ES does not support drawing to separate buffer for anaglyph mode\n" );
+ ri.Cvar_Set( "r_anaglyphMode", "0" );
+ r_anaglyphMode->modified = qfalse;
+ }
+
if(r_anaglyphMode->integer)
{
if(r_anaglyphMode->modified)
diff --git a/code/renderergl2/tr_curve.c b/code/renderergl2/tr_curve.c
index d175789bbf..54910d04d2 100644
--- a/code/renderergl2/tr_curve.c
+++ b/code/renderergl2/tr_curve.c
@@ -388,10 +388,10 @@ void R_CreateSurfaceGridMesh(srfBspSurface_t *grid, int width, int height,
grid->numVerts = (width * height);
grid->verts = ri.Malloc(grid->numVerts * sizeof(srfVert_t));
#else
- grid->widthLodError = ri.Hunk_Alloc( width * 4 );
+ grid->widthLodError = ri.Hunk_Alloc( width * 4, h_low );
Com_Memcpy( grid->widthLodError, errorTable[0], width * 4 );
- grid->heightLodError = ri.Hunk_Alloc( height * 4 );
+ grid->heightLodError = ri.Hunk_Alloc( height * 4, h_low );
Com_Memcpy( grid->heightLodError, errorTable[1], height * 4 );
grid->numIndexes = numIndexes;
diff --git a/code/renderergl2/tr_extensions.c b/code/renderergl2/tr_extensions.c
index ebc985a9e4..856526bfa2 100644
--- a/code/renderergl2/tr_extensions.c
+++ b/code/renderergl2/tr_extensions.c
@@ -45,6 +45,17 @@ void GLimp_InitExtraExtensions(void)
if (strstr((char *)qglGetString(GL_RENDERER), "Intel"))
glRefConfig.intelGraphics = qtrue;
+ if (qglesMajorVersion)
+ {
+ glRefConfig.vaoCacheGlIndexType = GL_UNSIGNED_SHORT;
+ glRefConfig.vaoCacheGlIndexSize = sizeof(unsigned short);
+ }
+ else
+ {
+ glRefConfig.vaoCacheGlIndexType = GL_UNSIGNED_INT;
+ glRefConfig.vaoCacheGlIndexSize = sizeof(unsigned int);
+ }
+
// set DSA fallbacks
#define GLE(ret, name, ...) qgl##name = GLDSA_##name;
QGL_EXT_direct_state_access_PROCS;
@@ -53,8 +64,107 @@ void GLimp_InitExtraExtensions(void)
// GL function loader, based on https://gist.github.com/rygorous/16796a0c876cf8a5f542caddb55bce8a
#define GLE(ret, name, ...) qgl##name = (name##proc *) SDL_GL_GetProcAddress("gl" #name);
+ //
+ // OpenGL ES extensions
+ //
+ if (qglesMajorVersion)
+ {
+ if (!r_allowExtensions->integer)
+ goto done;
+
+ extension = "GL_EXT_occlusion_query_boolean";
+ if (qglesMajorVersion >= 3 || SDL_GL_ExtensionSupported(extension))
+ {
+ glRefConfig.occlusionQuery = qtrue;
+ glRefConfig.occlusionQueryTarget = GL_ANY_SAMPLES_PASSED;
+
+ if (qglesMajorVersion >= 3) {
+ QGL_ARB_occlusion_query_PROCS;
+ } else {
+ // GL_EXT_occlusion_query_boolean uses EXT suffix
+#undef GLE
+#define GLE(ret, name, ...) qgl##name = (name##proc *) SDL_GL_GetProcAddress("gl" #name "EXT");
+
+ QGL_ARB_occlusion_query_PROCS;
+
+#undef GLE
+#define GLE(ret, name, ...) qgl##name = (name##proc *) SDL_GL_GetProcAddress("gl" #name);
+ }
+
+ ri.Printf(PRINT_ALL, result[glRefConfig.occlusionQuery], extension);
+ }
+ else
+ {
+ ri.Printf(PRINT_ALL, result[2], extension);
+ }
+
+ // GL_NV_read_depth
+ extension = "GL_NV_read_depth";
+ if (SDL_GL_ExtensionSupported(extension))
+ {
+ glRefConfig.readDepth = qtrue;
+ ri.Printf(PRINT_ALL, result[glRefConfig.readDepth], extension);
+ }
+ else
+ {
+ ri.Printf(PRINT_ALL, result[2], extension);
+ }
+
+ // GL_NV_read_stencil
+ extension = "GL_NV_read_stencil";
+ if (SDL_GL_ExtensionSupported(extension))
+ {
+ glRefConfig.readStencil = qtrue;
+ ri.Printf(PRINT_ALL, result[glRefConfig.readStencil], extension);
+ }
+ else
+ {
+ ri.Printf(PRINT_ALL, result[2], extension);
+ }
+
+ // GL_EXT_shadow_samplers
+ extension = "GL_EXT_shadow_samplers";
+ if (qglesMajorVersion >= 3 || SDL_GL_ExtensionSupported(extension))
+ {
+ glRefConfig.shadowSamplers = qtrue;
+ ri.Printf(PRINT_ALL, result[glRefConfig.shadowSamplers], extension);
+ }
+ else
+ {
+ ri.Printf(PRINT_ALL, result[2], extension);
+ }
+
+ // GL_OES_standard_derivatives
+ extension = "GL_OES_standard_derivatives";
+ if (qglesMajorVersion >= 3 || SDL_GL_ExtensionSupported(extension))
+ {
+ glRefConfig.standardDerivatives = qtrue;
+ ri.Printf(PRINT_ALL, result[glRefConfig.standardDerivatives], extension);
+ }
+ else
+ {
+ ri.Printf(PRINT_ALL, result[2], extension);
+ }
+
+ // GL_OES_element_index_uint
+ extension = "GL_OES_element_index_uint";
+ if (qglesMajorVersion >= 3 || SDL_GL_ExtensionSupported(extension))
+ {
+ glRefConfig.vaoCacheGlIndexType = GL_UNSIGNED_INT;
+ glRefConfig.vaoCacheGlIndexSize = sizeof(unsigned int);
+ ri.Printf(PRINT_ALL, result[1], extension);
+ }
+ else
+ {
+ ri.Printf(PRINT_ALL, result[2], extension);
+ }
+
+ goto done;
+ }
+
// OpenGL 1.5 - GL_ARB_occlusion_query
glRefConfig.occlusionQuery = qtrue;
+ glRefConfig.occlusionQueryTarget = GL_SAMPLES_PASSED;
QGL_ARB_occlusion_query_PROCS;
// OpenGL 3.0 - GL_ARB_framebuffer_object
@@ -146,18 +256,6 @@ void GLimp_InitExtraExtensions(void)
ri.Printf(PRINT_ALL, result[2], extension);
}
- // Determine GLSL version
- if (1)
- {
- char version[256];
-
- Q_strncpyz(version, (char *)qglGetString(GL_SHADING_LANGUAGE_VERSION), sizeof(version));
-
- sscanf(version, "%d.%d", &glRefConfig.glslMajorVersion, &glRefConfig.glslMinorVersion);
-
- ri.Printf(PRINT_ALL, "...using GLSL version %s\n", version);
- }
-
glRefConfig.memInfo = MI_NONE;
// GL_NVX_gpu_memory_info
@@ -249,5 +347,26 @@ void GLimp_InitExtraExtensions(void)
ri.Printf(PRINT_ALL, result[2], extension);
}
+done:
+
+ // Determine GLSL version
+ if (1)
+ {
+ char version[256], *version_p;
+
+ Q_strncpyz(version, (char *)qglGetString(GL_SHADING_LANGUAGE_VERSION), sizeof(version));
+
+ // Skip leading text such as "OpenGL ES GLSL ES "
+ version_p = version;
+ while ( *version_p && !isdigit( *version_p ) )
+ {
+ version_p++;
+ }
+
+ sscanf(version_p, "%d.%d", &glRefConfig.glslMajorVersion, &glRefConfig.glslMinorVersion);
+
+ ri.Printf(PRINT_ALL, "...using GLSL version %s\n", version);
+ }
+
#undef GLE
}
diff --git a/code/renderergl2/tr_fbo.c b/code/renderergl2/tr_fbo.c
index b0a9478f9c..223cbe7f7d 100644
--- a/code/renderergl2/tr_fbo.c
+++ b/code/renderergl2/tr_fbo.c
@@ -648,10 +648,30 @@ void FBO_FastBlit(FBO_t *src, ivec4_t srcBox, FBO_t *dst, ivec4_t dstBox, int bu
int width = dst ? dst->width : glConfig.vidWidth;
int height = dst ? dst->height : glConfig.vidHeight;
+ qglScissor(0, 0, width, height);
+
VectorSet4(dstBoxFinal, 0, 0, width, height);
}
else
{
+ ivec4_t scissorBox;
+
+ Vector4Copy(dstBox, scissorBox);
+
+ if (scissorBox[2] < 0)
+ {
+ scissorBox[0] += scissorBox[2];
+ scissorBox[2] = fabsf(scissorBox[2]);
+ }
+
+ if (scissorBox[3] < 0)
+ {
+ scissorBox[1] += scissorBox[3];
+ scissorBox[3] = fabsf(scissorBox[3]);
+ }
+
+ qglScissor(scissorBox[0], scissorBox[1], scissorBox[2], scissorBox[3]);
+
VectorSet4(dstBoxFinal, dstBox[0], dstBox[1], dstBox[0] + dstBox[2], dstBox[1] + dstBox[3]);
}
diff --git a/code/renderergl2/tr_flares.c b/code/renderergl2/tr_flares.c
index fc83df7b0a..edf7682207 100644
--- a/code/renderergl2/tr_flares.c
+++ b/code/renderergl2/tr_flares.c
@@ -478,6 +478,14 @@ void RB_RenderFlares (void) {
return;
}
+ if ( r_flares->modified ) {
+ if ( qglesMajorVersion >= 1 && !glRefConfig.readDepth ) {
+ ri.Printf( PRINT_WARNING, "OpenGL ES needs GL_NV_read_depth to read depth to determine if flares are visible\n" );
+ ri.Cvar_Set( "r_flares", "0" );
+ }
+ r_flares->modified = qfalse;
+ }
+
if(r_flareCoeff->modified)
{
R_SetFlareCoeff();
diff --git a/code/renderergl2/tr_glsl.c b/code/renderergl2/tr_glsl.c
index 86cca5998f..39223bf6f8 100644
--- a/code/renderergl2/tr_glsl.c
+++ b/code/renderergl2/tr_glsl.c
@@ -88,8 +88,14 @@ static uniformInfo_t uniformsInfo[] =
{ "u_EnableTextures", GLSL_VEC4 },
- { "u_DiffuseTexMatrix", GLSL_VEC4 },
- { "u_DiffuseTexOffTurb", GLSL_VEC4 },
+ { "u_DiffuseTexMatrix0", GLSL_VEC4 },
+ { "u_DiffuseTexMatrix1", GLSL_VEC4 },
+ { "u_DiffuseTexMatrix2", GLSL_VEC4 },
+ { "u_DiffuseTexMatrix3", GLSL_VEC4 },
+ { "u_DiffuseTexMatrix4", GLSL_VEC4 },
+ { "u_DiffuseTexMatrix5", GLSL_VEC4 },
+ { "u_DiffuseTexMatrix6", GLSL_VEC4 },
+ { "u_DiffuseTexMatrix7", GLSL_VEC4 },
{ "u_TCGen0", GLSL_INT },
{ "u_TCGen0Vector0", GLSL_VEC3 },
@@ -243,11 +249,25 @@ static void GLSL_GetShaderHeader( GLenum shaderType, const GLchar *extra, char *
// HACK: abuse the GLSL preprocessor to turn GLSL 1.20 shaders into 1.30 ones
if(glRefConfig.glslMajorVersion > 1 || (glRefConfig.glslMajorVersion == 1 && glRefConfig.glslMinorVersion >= 30))
{
- if (glRefConfig.glslMajorVersion > 1 || (glRefConfig.glslMajorVersion == 1 && glRefConfig.glslMinorVersion >= 50))
+ if (qglesMajorVersion >= 3 && glRefConfig.glslMajorVersion >= 3)
+ Q_strcat(dest, size, "#version 300 es\n");
+ else if (glRefConfig.glslMajorVersion > 1 || (glRefConfig.glslMajorVersion == 1 && glRefConfig.glslMinorVersion >= 50))
Q_strcat(dest, size, "#version 150\n");
else
Q_strcat(dest, size, "#version 130\n");
+ // `extra' may contain #extension which must be directly after #version
+ if (extra)
+ {
+ Q_strcat(dest, size, extra);
+ }
+
+ if (qglesMajorVersion >= 2)
+ {
+ Q_strcat(dest, size, "precision mediump float;\n");
+ Q_strcat(dest, size, "precision mediump sampler2DShadow;\n");
+ }
+
if(shaderType == GL_VERTEX_SHADER)
{
Q_strcat(dest, size, "#define attribute in\n");
@@ -266,8 +286,34 @@ static void GLSL_GetShaderHeader( GLenum shaderType, const GLchar *extra, char *
}
else
{
- Q_strcat(dest, size, "#version 120\n");
- Q_strcat(dest, size, "#define shadow2D(a,b) shadow2D(a,b).r \n");
+ if (qglesMajorVersion >= 2)
+ {
+ Q_strcat(dest, size, "#version 100\n");
+
+ if (extra)
+ {
+ Q_strcat(dest, size, extra);
+ }
+
+ Q_strcat(dest, size, "precision mediump float;\n");
+
+ if (glRefConfig.shadowSamplers)
+ {
+ Q_strcat(dest, size, "precision mediump sampler2DShadow;\n");
+ Q_strcat(dest, size, "#define shadow2D(a,b) shadow2DEXT(a,b)\n");
+ }
+ }
+ else
+ {
+ Q_strcat(dest, size, "#version 120\n");
+
+ if (extra)
+ {
+ Q_strcat(dest, size, extra);
+ }
+
+ Q_strcat(dest, size, "#define shadow2D(a,b) shadow2D(a,b).r\n");
+ }
}
// HACK: add some macros to avoid extra uniforms and save speed and code maintenance
@@ -355,11 +401,6 @@ static void GLSL_GetShaderHeader( GLenum shaderType, const GLchar *extra, char *
Q_strcat(dest, size, va("#define ROUGHNESS_MIPS float(%d)\n", numRoughnessMips));
}
- if (extra)
- {
- Q_strcat(dest, size, extra);
- }
-
// OK we added a lot of stuff but if we do something bad in the GLSL shaders then we want the proper line
// so we have to reset the line counting
Q_strcat(dest, size, "#line 0\n");
@@ -927,6 +968,15 @@ void GLSL_InitGPUShaders(void)
startTime = ri.Milliseconds();
+ // OpenGL ES may not have enough attributes to fit ones used for vertex animation
+ if ( glRefConfig.maxVertexAttribs > ATTR_INDEX_NORMAL2 ) {
+ ri.Printf(PRINT_ALL, "Using GPU vertex animation\n");
+ glRefConfig.gpuVertexAnimation = qtrue;
+ } else {
+ ri.Printf(PRINT_ALL, "Using CPU vertex animation\n");
+ glRefConfig.gpuVertexAnimation = qfalse;
+ }
+
for (i = 0; i < GENERICDEF_COUNT; i++)
{
if ((i & GENERICDEF_USE_VERTEX_ANIMATION) && (i & GENERICDEF_USE_BONE_ANIMATION))
@@ -949,6 +999,9 @@ void GLSL_InitGPUShaders(void)
if (i & GENERICDEF_USE_VERTEX_ANIMATION)
{
+ if (!glRefConfig.gpuVertexAnimation)
+ continue;
+
Q_strcat(extradefines, 1024, "#define USE_VERTEX_ANIMATION\n");
attribs |= ATTR_POSITION2 | ATTR_NORMAL2;
}
@@ -1000,6 +1053,9 @@ void GLSL_InitGPUShaders(void)
if ((i & FOGDEF_USE_VERTEX_ANIMATION) && (i & FOGDEF_USE_BONE_ANIMATION))
continue;
+ if ((i & FOGDEF_USE_VERTEX_ANIMATION) && !glRefConfig.gpuVertexAnimation)
+ continue;
+
if ((i & FOGDEF_USE_BONE_ANIMATION) && !glRefConfig.glslMaxAnimatedBones)
continue;
@@ -1180,12 +1236,17 @@ void GLSL_InitGPUShaders(void)
if (i & LIGHTDEF_ENTITY_VERTEX_ANIMATION)
{
- Q_strcat(extradefines, 1024, "#define USE_VERTEX_ANIMATION\n#define USE_MODELMATRIX\n");
- attribs |= ATTR_POSITION2 | ATTR_NORMAL2;
+ Q_strcat(extradefines, 1024, "#define USE_MODELMATRIX\n");
- if (r_normalMapping->integer)
+ if (glRefConfig.gpuVertexAnimation)
{
- attribs |= ATTR_TANGENT2;
+ Q_strcat(extradefines, 1024, "#define USE_VERTEX_ANIMATION\n");
+ attribs |= ATTR_POSITION2 | ATTR_NORMAL2;
+
+ if (r_normalMapping->integer)
+ {
+ attribs |= ATTR_TANGENT2;
+ }
}
}
else if (i & LIGHTDEF_ENTITY_BONE_ANIMATION)
@@ -1220,6 +1281,9 @@ void GLSL_InitGPUShaders(void)
if ((i & SHADOWMAPDEF_USE_VERTEX_ANIMATION) && (i & SHADOWMAPDEF_USE_BONE_ANIMATION))
continue;
+ if ((i & SHADOWMAPDEF_USE_VERTEX_ANIMATION) && !glRefConfig.gpuVertexAnimation)
+ continue;
+
if ((i & SHADOWMAPDEF_USE_BONE_ANIMATION) && !glRefConfig.glslMaxAnimatedBones)
continue;
@@ -1344,84 +1408,108 @@ void GLSL_InitGPUShaders(void)
}
- attribs = ATTR_POSITION | ATTR_TEXCOORD;
- extradefines[0] = '\0';
+ // GLSL 1.10+ or GL_EXT_shadow_samplers extension are required for sampler2DShadow type
+ if (glRefConfig.glslMajorVersion > 1 || (glRefConfig.glslMajorVersion == 1 && glRefConfig.glslMinorVersion >= 10)
+ || glRefConfig.shadowSamplers)
+ {
+ attribs = ATTR_POSITION | ATTR_TEXCOORD;
+ extradefines[0] = '\0';
- if (r_shadowFilter->integer >= 1)
- Q_strcat(extradefines, 1024, "#define USE_SHADOW_FILTER\n");
+ if (qglesMajorVersion < 3 && glRefConfig.shadowSamplers)
+ {
+ Q_strcat(extradefines, 1024, "#extension GL_EXT_shadow_samplers : enable\n");
+ }
- if (r_shadowFilter->integer >= 2)
- Q_strcat(extradefines, 1024, "#define USE_SHADOW_FILTER2\n");
+ if (r_shadowFilter->integer >= 1)
+ Q_strcat(extradefines, 1024, "#define USE_SHADOW_FILTER\n");
- if (r_shadowCascadeZFar->integer != 0)
- Q_strcat(extradefines, 1024, "#define USE_SHADOW_CASCADE\n");
+ if (r_shadowFilter->integer >= 2)
+ Q_strcat(extradefines, 1024, "#define USE_SHADOW_FILTER2\n");
- Q_strcat(extradefines, 1024, va("#define r_shadowMapSize %f\n", r_shadowMapSize->value));
- Q_strcat(extradefines, 1024, va("#define r_shadowCascadeZFar %f\n", r_shadowCascadeZFar->value));
+ if (r_shadowCascadeZFar->integer != 0)
+ Q_strcat(extradefines, 1024, "#define USE_SHADOW_CASCADE\n");
+ Q_strcat(extradefines, 1024, va("#define r_shadowMapSize %f\n", r_shadowMapSize->value));
+ Q_strcat(extradefines, 1024, va("#define r_shadowCascadeZFar %f\n", r_shadowCascadeZFar->value));
- if (!GLSL_InitGPUShader(&tr.shadowmaskShader, "shadowmask", attribs, qtrue, extradefines, qtrue, fallbackShader_shadowmask_vp, fallbackShader_shadowmask_fp))
- {
- ri.Error(ERR_FATAL, "Could not load shadowmask shader!");
- }
+ if (!GLSL_InitGPUShader(&tr.shadowmaskShader, "shadowmask", attribs, qtrue, extradefines, qtrue, fallbackShader_shadowmask_vp, fallbackShader_shadowmask_fp))
+ {
+ ri.Error(ERR_FATAL, "Could not load shadowmask shader!");
+ }
- GLSL_InitUniforms(&tr.shadowmaskShader);
-
- GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SCREENDEPTHMAP, TB_COLORMAP);
- GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP, TB_SHADOWMAP);
- GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP2, TB_SHADOWMAP2);
- GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP3, TB_SHADOWMAP3);
- GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP4, TB_SHADOWMAP4);
+ GLSL_InitUniforms(&tr.shadowmaskShader);
- GLSL_FinishGPUShader(&tr.shadowmaskShader);
+ GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SCREENDEPTHMAP, TB_COLORMAP);
+ GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP, TB_SHADOWMAP);
+ GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP2, TB_SHADOWMAP2);
+ GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP3, TB_SHADOWMAP3);
+ GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP4, TB_SHADOWMAP4);
- numEtcShaders++;
+ GLSL_FinishGPUShader(&tr.shadowmaskShader);
+ numEtcShaders++;
+ }
- attribs = ATTR_POSITION | ATTR_TEXCOORD;
- extradefines[0] = '\0';
- if (!GLSL_InitGPUShader(&tr.ssaoShader, "ssao", attribs, qtrue, extradefines, qtrue, fallbackShader_ssao_vp, fallbackShader_ssao_fp))
+ // GLSL 1.10+ or GL_OES_standard_derivatives extension are required for dFdx() and dFdy() GLSL functions
+ if (glRefConfig.glslMajorVersion > 1 || (glRefConfig.glslMajorVersion == 1 && glRefConfig.glslMinorVersion >= 10)
+ || glRefConfig.standardDerivatives)
{
- ri.Error(ERR_FATAL, "Could not load ssao shader!");
- }
+ attribs = ATTR_POSITION | ATTR_TEXCOORD;
+ extradefines[0] = '\0';
+
+ if (qglesMajorVersion < 3 && glRefConfig.standardDerivatives)
+ {
+ Q_strcat(extradefines, 1024, "#extension GL_OES_standard_derivatives : enable\n");
+ }
- GLSL_InitUniforms(&tr.ssaoShader);
+ if (!GLSL_InitGPUShader(&tr.ssaoShader, "ssao", attribs, qtrue, extradefines, qtrue, fallbackShader_ssao_vp, fallbackShader_ssao_fp))
+ {
+ ri.Error(ERR_FATAL, "Could not load ssao shader!");
+ }
- GLSL_SetUniformInt(&tr.ssaoShader, UNIFORM_SCREENDEPTHMAP, TB_COLORMAP);
+ GLSL_InitUniforms(&tr.ssaoShader);
- GLSL_FinishGPUShader(&tr.ssaoShader);
+ GLSL_SetUniformInt(&tr.ssaoShader, UNIFORM_SCREENDEPTHMAP, TB_COLORMAP);
- numEtcShaders++;
+ GLSL_FinishGPUShader(&tr.ssaoShader);
+ numEtcShaders++;
- for (i = 0; i < 4; i++)
- {
- attribs = ATTR_POSITION | ATTR_TEXCOORD;
- extradefines[0] = '\0';
- if (i & 1)
- Q_strcat(extradefines, 1024, "#define USE_VERTICAL_BLUR\n");
- else
- Q_strcat(extradefines, 1024, "#define USE_HORIZONTAL_BLUR\n");
+ for (i = 0; i < 4; i++)
+ {
+ attribs = ATTR_POSITION | ATTR_TEXCOORD;
+ extradefines[0] = '\0';
- if (!(i & 2))
- Q_strcat(extradefines, 1024, "#define USE_DEPTH\n");
+ if (qglesMajorVersion < 3 && glRefConfig.standardDerivatives)
+ {
+ Q_strcat(extradefines, 1024, "#extension GL_OES_standard_derivatives : enable\n");
+ }
+ if (i & 1)
+ Q_strcat(extradefines, 1024, "#define USE_VERTICAL_BLUR\n");
+ else
+ Q_strcat(extradefines, 1024, "#define USE_HORIZONTAL_BLUR\n");
- if (!GLSL_InitGPUShader(&tr.depthBlurShader[i], "depthBlur", attribs, qtrue, extradefines, qtrue, fallbackShader_depthblur_vp, fallbackShader_depthblur_fp))
- {
- ri.Error(ERR_FATAL, "Could not load depthBlur shader!");
- }
+ if (!(i & 2))
+ Q_strcat(extradefines, 1024, "#define USE_DEPTH\n");
+
+
+ if (!GLSL_InitGPUShader(&tr.depthBlurShader[i], "depthBlur", attribs, qtrue, extradefines, qtrue, fallbackShader_depthblur_vp, fallbackShader_depthblur_fp))
+ {
+ ri.Error(ERR_FATAL, "Could not load depthBlur shader!");
+ }
- GLSL_InitUniforms(&tr.depthBlurShader[i]);
+ GLSL_InitUniforms(&tr.depthBlurShader[i]);
- GLSL_SetUniformInt(&tr.depthBlurShader[i], UNIFORM_SCREENIMAGEMAP, TB_COLORMAP);
- GLSL_SetUniformInt(&tr.depthBlurShader[i], UNIFORM_SCREENDEPTHMAP, TB_LIGHTMAP);
+ GLSL_SetUniformInt(&tr.depthBlurShader[i], UNIFORM_SCREENIMAGEMAP, TB_COLORMAP);
+ GLSL_SetUniformInt(&tr.depthBlurShader[i], UNIFORM_SCREENDEPTHMAP, TB_LIGHTMAP);
- GLSL_FinishGPUShader(&tr.depthBlurShader[i]);
+ GLSL_FinishGPUShader(&tr.depthBlurShader[i]);
- numEtcShaders++;
+ numEtcShaders++;
+ }
}
#if 0
@@ -1456,7 +1544,7 @@ void GLSL_ShutdownGPUShaders(void)
ri.Printf(PRINT_ALL, "------- GLSL_ShutdownGPUShaders -------\n");
- for (i = 0; i < ATTR_INDEX_COUNT; i++)
+ for (i = 0; i < ATTR_INDEX_COUNT && i < glRefConfig.maxVertexAttribs; i++)
qglDisableVertexAttribArray(i);
GL_BindNullProgram();
diff --git a/code/renderergl2/tr_image.c b/code/renderergl2/tr_image.c
index 09ceff0831..0e829d5198 100644
--- a/code/renderergl2/tr_image.c
+++ b/code/renderergl2/tr_image.c
@@ -1455,6 +1455,106 @@ byte mipBlendColors[16][4] = {
{0,0,255,128},
};
+/*
+==================
+R_ConvertTextureFormat
+
+Convert RGBA unsigned byte to specified format and type
+==================
+*/
+#define ROW_PADDING( width, bpp, alignment ) PAD( (width) * (bpp), (alignment) ) - (width) * (bpp)
+void R_ConvertTextureFormat( const byte *in, int width, int height, GLenum format, GLenum type, byte *out )
+{
+ int x, y, rowPadding;
+ int unpackAlign = 4; // matches GL_UNPACK_ALIGNMENT default
+
+ if ( format == GL_RGB && type == GL_UNSIGNED_BYTE )
+ {
+ rowPadding = ROW_PADDING( width, 3, unpackAlign );
+
+ for ( y = 0; y < height; y++ )
+ {
+ for ( x = 0; x < width; x++ )
+ {
+ *out++ = *in++;
+ *out++ = *in++;
+ *out++ = *in++;
+ in++;
+ }
+
+ out += rowPadding;
+ }
+ }
+ else if ( format == GL_LUMINANCE && type == GL_UNSIGNED_BYTE )
+ {
+ rowPadding = ROW_PADDING( width, 1, unpackAlign );
+
+ for ( y = 0; y < height; y++ )
+ {
+ for ( x = 0; x < width; x++ )
+ {
+ *out++ = *in++; // red
+ in += 3;
+ }
+
+ out += rowPadding;
+ }
+ }
+ else if ( format == GL_LUMINANCE_ALPHA && type == GL_UNSIGNED_BYTE )
+ {
+ rowPadding = ROW_PADDING( width, 2, unpackAlign );
+
+ for ( y = 0; y < height; y++ )
+ {
+ for ( x = 0; x < width; x++ )
+ {
+ *out++ = *in++; // red
+ in += 2;
+ *out++ = *in++; // alpha
+ }
+
+ out += rowPadding;
+ }
+ }
+ else if ( format == GL_RGB && type == GL_UNSIGNED_SHORT_5_6_5 )
+ {
+ rowPadding = ROW_PADDING( width, 2, unpackAlign );
+
+ for ( y = 0; y < height; y++ )
+ {
+ for ( x = 0; x < width; x++, in += 4, out += 2 )
+ {
+ *((unsigned short*)out) = ( (unsigned short)( in[0] >> 3 ) << 11 )
+ | ( (unsigned short)( in[1] >> 2 ) << 5 )
+ | ( (unsigned short)( in[2] >> 3 ) << 0 );
+ }
+
+ out += rowPadding;
+ }
+ }
+ else if ( format == GL_RGBA && type == GL_UNSIGNED_SHORT_4_4_4_4 )
+ {
+ rowPadding = ROW_PADDING( width, 2, unpackAlign );
+
+ for ( y = 0; y < height; y++ )
+ {
+ for ( x = 0; x < width; x++, in += 4, out += 2 )
+ {
+ *((unsigned short*)out) = ( (unsigned short)( in[0] >> 4 ) << 12 )
+ | ( (unsigned short)( in[1] >> 4 ) << 8 )
+ | ( (unsigned short)( in[2] >> 4 ) << 4 )
+ | ( (unsigned short)( in[3] >> 4 ) << 0 );
+ }
+
+ out += rowPadding;
+ }
+ }
+ else
+ {
+ ri.Error( ERR_DROP, "Unable to convert RGBA image to OpenGL format 0x%X and type 0x%X", format, type );
+ }
+}
+
static void RawImage_SwizzleRA( byte *data, int width, int height )
{
int i;
@@ -1944,18 +2044,20 @@ static GLenum PixelDataFormatFromInternalFormat(GLenum internalFormat)
}
}
-static void RawImage_UploadTexture(GLuint texture, byte *data, int x, int y, int width, int height, GLenum target, GLenum picFormat, int numMips, GLenum internalFormat, imgType_t type, imgFlags_t flags, qboolean subtexture )
+static void RawImage_UploadTexture(GLuint texture, byte *data, int x, int y, int width, int height, GLenum target, GLenum picFormat, GLenum dataFormat, GLenum dataType, int numMips, GLenum internalFormat, imgType_t type, imgFlags_t flags, qboolean subtexture )
{
- GLenum dataFormat, dataType;
qboolean rgtc = internalFormat == GL_COMPRESSED_RG_RGTC2;
qboolean rgba8 = picFormat == GL_RGBA8 || picFormat == GL_SRGB8_ALPHA8_EXT;
qboolean rgba = rgba8 || picFormat == GL_RGBA16;
qboolean mipmap = !!(flags & IMGFLAG_MIPMAP);
int size, miplevel;
qboolean lastMip = qfalse;
+ byte *formatBuffer = NULL;
- dataFormat = PixelDataFormatFromInternalFormat(internalFormat);
- dataType = picFormat == GL_RGBA16 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE;
+ if (qglesMajorVersion && rgba8 && (dataFormat != GL_RGBA || dataType != GL_UNSIGNED_BYTE))
+ {
+ formatBuffer = ri.Hunk_AllocateTempMemory(4 * width * height);
+ }
miplevel = 0;
do
@@ -1974,6 +2076,11 @@ static void RawImage_UploadTexture(GLuint texture, byte *data, int x, int y, int
if (rgba8 && rgtc)
RawImage_UploadToRgtc2Texture(texture, miplevel, x, y, width, height, data);
+ else if (formatBuffer)
+ {
+ R_ConvertTextureFormat(data, width, height, dataFormat, dataType, formatBuffer);
+ qglTextureSubImage2DEXT(texture, target, miplevel, x, y, width, height, dataFormat, dataType, formatBuffer);
+ }
else
qglTextureSubImage2DEXT(texture, target, miplevel, x, y, width, height, dataFormat, dataType, data);
}
@@ -2007,6 +2114,9 @@ static void RawImage_UploadTexture(GLuint texture, byte *data, int x, int y, int
}
}
while (!lastMip);
+
+ if (formatBuffer != NULL)
+ ri.Hunk_FreeTempMemory(formatBuffer);
}
@@ -2016,7 +2126,7 @@ Upload32
===============
*/
-static void Upload32(byte *data, int x, int y, int width, int height, GLenum picFormat, int numMips, image_t *image, qboolean scaled)
+static void Upload32(byte *data, int x, int y, int width, int height, GLenum picFormat, GLenum dataFormat, GLenum dataType, int numMips, image_t *image, qboolean scaled)
{
int i, c;
byte *scan;
@@ -2071,7 +2181,7 @@ static void Upload32(byte *data, int x, int y, int width, int height, GLenum pic
for (i = 0; i < 6; i++)
{
int w2 = width, h2 = height;
- RawImage_UploadTexture(image->texnum, data, x, y, width, height, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, picFormat, numMips, internalFormat, type, flags, qfalse);
+ RawImage_UploadTexture(image->texnum, data, x, y, width, height, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, picFormat, dataFormat, dataType, numMips, internalFormat, type, flags, qfalse);
for (c = numMips; c; c--)
{
data += CalculateMipSize(w2, h2, picFormat);
@@ -2082,7 +2192,7 @@ static void Upload32(byte *data, int x, int y, int width, int height, GLenum pic
}
else
{
- RawImage_UploadTexture(image->texnum, data, x, y, width, height, GL_TEXTURE_2D, picFormat, numMips, internalFormat, type, flags, qfalse);
+ RawImage_UploadTexture(image->texnum, data, x, y, width, height, GL_TEXTURE_2D, picFormat, dataFormat, dataType, numMips, internalFormat, type, flags, qfalse);
}
GL_CheckErrors();
@@ -2108,7 +2218,7 @@ image_t *R_CreateImage2( const char *name, byte *pic, int width, int height, GLe
qboolean picmip = !!(flags & IMGFLAG_PICMIP);
qboolean lastMip;
GLenum textureTarget = cubemap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
- GLenum dataFormat;
+ GLenum dataFormat, dataType;
if (strlen(name) >= MAX_QPATH ) {
ri.Error (ERR_DROP, "R_CreateImage: \"%s\" is too long", name);
@@ -2140,6 +2250,53 @@ image_t *R_CreateImage2( const char *name, byte *pic, int width, int height, GLe
if (!internalFormat)
internalFormat = RawImage_GetFormat(pic, width * height, picFormat, isLightmap, image->type, image->flags);
+ dataFormat = PixelDataFormatFromInternalFormat(internalFormat);
+ dataType = picFormat == GL_RGBA16 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE;
+
+ // Convert image data format for OpenGL ES, data is converted for each mip level
+ if (qglesMajorVersion)
+ {
+ switch (internalFormat)
+ {
+ case GL_LUMINANCE:
+ case GL_LUMINANCE8:
+ internalFormat = GL_LUMINANCE;
+ dataFormat = GL_LUMINANCE;
+ dataType = GL_UNSIGNED_BYTE;
+ break;
+ case GL_LUMINANCE_ALPHA:
+ case GL_LUMINANCE8_ALPHA8:
+ internalFormat = GL_LUMINANCE_ALPHA;
+ dataFormat = GL_LUMINANCE_ALPHA;
+ dataType = GL_UNSIGNED_BYTE;
+ break;
+ case GL_RGB:
+ case GL_RGB8:
+ internalFormat = GL_RGB;
+ dataFormat = GL_RGB;
+ dataType = GL_UNSIGNED_BYTE;
+ break;
+ case GL_RGB5:
+ internalFormat = GL_RGB;
+ dataFormat = GL_RGB;
+ dataType = GL_UNSIGNED_SHORT_5_6_5;
+ break;
+ case GL_RGBA:
+ case GL_RGBA8:
+ internalFormat = GL_RGBA;
+ dataFormat = GL_RGBA;
+ dataType = GL_UNSIGNED_BYTE;
+ break;
+ case GL_RGBA4:
+ internalFormat = GL_RGBA;
+ dataFormat = GL_RGBA;
+ dataType = GL_UNSIGNED_SHORT_4_4_4_4;
+ break;
+ default:
+ ri.Error( ERR_DROP, "Missing OpenGL ES support for image '%s' with internal format 0x%X\n", name, internalFormat );
+ }
+ }
+
image->internalFormat = internalFormat;
// Possibly scale image before uploading.
@@ -2164,7 +2321,6 @@ image_t *R_CreateImage2( const char *name, byte *pic, int width, int height, GLe
image->uploadHeight = height;
// Allocate texture storage so we don't have to worry about it later.
- dataFormat = PixelDataFormatFromInternalFormat(internalFormat);
mipWidth = width;
mipHeight = height;
miplevel = 0;
@@ -2176,11 +2332,11 @@ image_t *R_CreateImage2( const char *name, byte *pic, int width, int height, GLe
int i;
for (i = 0; i < 6; i++)
- qglTextureImage2DEXT(image->texnum, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, miplevel, internalFormat, mipWidth, mipHeight, 0, dataFormat, GL_UNSIGNED_BYTE, NULL);
+ qglTextureImage2DEXT(image->texnum, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, miplevel, internalFormat, mipWidth, mipHeight, 0, dataFormat, dataType, NULL);
}
else
{
- qglTextureImage2DEXT(image->texnum, GL_TEXTURE_2D, miplevel, internalFormat, mipWidth, mipHeight, 0, dataFormat, GL_UNSIGNED_BYTE, NULL);
+ qglTextureImage2DEXT(image->texnum, GL_TEXTURE_2D, miplevel, internalFormat, mipWidth, mipHeight, 0, dataFormat, dataType, NULL);
}
mipWidth = MAX(1, mipWidth >> 1);
@@ -2191,7 +2347,7 @@ image_t *R_CreateImage2( const char *name, byte *pic, int width, int height, GLe
// Upload data.
if (pic)
- Upload32(pic, 0, 0, width, height, picFormat, numMips, image, scaled);
+ Upload32(pic, 0, 0, width, height, picFormat, dataFormat, dataType, numMips, image, scaled);
if (resampledBuffer != NULL)
ri.Hunk_FreeTempMemory(resampledBuffer);
@@ -2252,7 +2408,13 @@ image_t *R_CreateImage(const char *name, byte *pic, int width, int height, imgTy
void R_UpdateSubImage( image_t *image, byte *pic, int x, int y, int width, int height, GLenum picFormat )
{
- Upload32(pic, x, y, width, height, picFormat, 0, image, qfalse);
+ GLenum dataFormat, dataType;
+
+ // TODO: This is fine for lightmaps but (unused) general RGBA images need to store dataFormat / dataType in image_t for OpenGL ES?
+ dataFormat = PixelDataFormatFromInternalFormat(image->internalFormat);
+ dataType = picFormat == GL_RGBA16 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE;
+
+ Upload32(pic, x, y, width, height, picFormat, dataFormat, dataType, 0, image, qfalse);
}
//===================================================================
@@ -2766,7 +2928,7 @@ void R_CreateBuiltinImages( void ) {
tr.renderImage = R_CreateImage("_render", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, hdrFormat);
- if (r_shadowBlur->integer)
+ if (r_shadowBlur->integer || r_hdr->integer)
tr.screenScratchImage = R_CreateImage("screenScratch", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, rgbFormat);
if (r_shadowBlur->integer || r_ssao->integer)
diff --git a/code/renderergl2/tr_init.c b/code/renderergl2/tr_init.c
index 0edfe6befb..12bac4b09e 100644
--- a/code/renderergl2/tr_init.c
+++ b/code/renderergl2/tr_init.c
@@ -279,8 +279,16 @@ static void InitOpenGL( void )
qglGetIntegerv( GL_MAX_TEXTURE_IMAGE_UNITS, &temp );
glConfig.numTextureUnits = temp;
+ qglGetIntegerv( GL_MAX_VERTEX_ATTRIBS, &temp );
+ glRefConfig.maxVertexAttribs = temp;
+
// reserve 160 components for other uniforms
- qglGetIntegerv( GL_MAX_VERTEX_UNIFORM_COMPONENTS, &temp );
+ if ( qglesMajorVersion ) {
+ qglGetIntegerv( GL_MAX_VERTEX_UNIFORM_VECTORS, &temp );
+ temp *= 4;
+ } else {
+ qglGetIntegerv( GL_MAX_VERTEX_UNIFORM_COMPONENTS, &temp );
+ }
glRefConfig.glslMaxAnimatedBones = Com_Clamp( 0, IQM_MAX_JOINTS, ( temp - 160 ) / 16 );
if ( glRefConfig.glslMaxAnimatedBones < 12 ) {
glRefConfig.glslMaxAnimatedBones = 0;
@@ -451,21 +459,43 @@ Return value must be freed with ri.Hunk_FreeTempMemory()
byte *RB_ReadPixels(int x, int y, int width, int height, size_t *offset, int *padlen)
{
byte *buffer, *bufstart;
- int padwidth, linelen;
- GLint packAlign;
-
+ int padwidth, linelen, bytesPerPixel;
+ int yin, xin, xout;
+ GLint packAlign, format;
+
+ // OpenGL ES is only required to support reading GL_RGBA
+ if (qglesMajorVersion >= 1) {
+ format = GL_RGBA;
+ bytesPerPixel = 4;
+ } else {
+ format = GL_RGB;
+ bytesPerPixel = 3;
+ }
+
qglGetIntegerv(GL_PACK_ALIGNMENT, &packAlign);
-
- linelen = width * 3;
+
+ linelen = width * bytesPerPixel;
padwidth = PAD(linelen, packAlign);
-
+
// Allocate a few more bytes so that we can choose an alignment we like
buffer = ri.Hunk_AllocateTempMemory(padwidth * height + *offset + packAlign - 1);
-
+
bufstart = PADP((intptr_t) buffer + *offset, packAlign);
+ qglReadPixels(x, y, width, height, format, GL_UNSIGNED_BYTE, bufstart);
+
+ linelen = width * 3;
+
+ // Convert RGBA to RGB, in place, line by line
+ if (format == GL_RGBA) {
+ for (yin = 0; yin < height; yin++) {
+ for (xin = 0, xout = 0; xout < linelen; xin += 4, xout += 3) {
+ bufstart[yin*padwidth + xout + 0] = bufstart[yin*padwidth + xin + 0];
+ bufstart[yin*padwidth + xout + 1] = bufstart[yin*padwidth + xin + 1];
+ bufstart[yin*padwidth + xout + 2] = bufstart[yin*padwidth + xin + 2];
+ }
+ }
+ }
- qglReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, bufstart);
-
*offset = bufstart - buffer;
*padlen = padwidth - linelen;
@@ -877,9 +907,10 @@ const void *RB_TakeVideoFrameCmd( const void *data )
{
const videoFrameCommand_t *cmd;
byte *cBuf;
- size_t memcount, linelen;
+ size_t memcount, bytesPerPixel, linelen, avilinelen;
int padwidth, avipadwidth, padlen, avipadlen;
- GLint packAlign;
+ int yin, xin, xout;
+ GLint packAlign, format;
// finish any 2D drawing if needed
if(tess.numIndexes)
@@ -887,20 +918,32 @@ const void *RB_TakeVideoFrameCmd( const void *data )
cmd = (const videoFrameCommand_t *)data;
+ // OpenGL ES is only required to support reading GL_RGBA
+ if (qglesMajorVersion >= 1) {
+ format = GL_RGBA;
+ bytesPerPixel = 4;
+ } else {
+ format = GL_RGB;
+ bytesPerPixel = 3;
+ }
+
qglGetIntegerv(GL_PACK_ALIGNMENT, &packAlign);
- linelen = cmd->width * 3;
+ linelen = cmd->width * bytesPerPixel;
// Alignment stuff for glReadPixels
padwidth = PAD(linelen, packAlign);
padlen = padwidth - linelen;
+
+ avilinelen = cmd->width * 3;
+
// AVI line padding
- avipadwidth = PAD(linelen, AVI_LINE_PADDING);
- avipadlen = avipadwidth - linelen;
+ avipadwidth = PAD(avilinelen, AVI_LINE_PADDING);
+ avipadlen = avipadwidth - avilinelen;
cBuf = PADP(cmd->captureBuffer, packAlign);
- qglReadPixels(0, 0, cmd->width, cmd->height, GL_RGB,
+ qglReadPixels(0, 0, cmd->width, cmd->height, format,
GL_UNSIGNED_BYTE, cBuf);
memcount = padwidth * cmd->height;
@@ -911,7 +954,21 @@ const void *RB_TakeVideoFrameCmd( const void *data )
if(cmd->motionJpeg)
{
- memcount = RE_SaveJPGToBuffer(cmd->encodeBuffer, linelen * cmd->height,
+ // Convert RGBA to RGB, in place, line by line
+ if (format == GL_RGBA) {
+ linelen = cmd->width * 3;
+ padlen = padwidth - linelen;
+
+ for (yin = 0; yin < cmd->height; yin++) {
+ for (xin = 0, xout = 0; xout < linelen; xin += 4, xout += 3) {
+ cBuf[yin*padwidth + xout + 0] = cBuf[yin*padwidth + xin + 0];
+ cBuf[yin*padwidth + xout + 1] = cBuf[yin*padwidth + xin + 1];
+ cBuf[yin*padwidth + xout + 2] = cBuf[yin*padwidth + xin + 2];
+ }
+ }
+ }
+
+ memcount = RE_SaveJPGToBuffer(cmd->encodeBuffer, avilinelen * cmd->height,
r_aviMotionJpegQuality->integer,
cmd->width, cmd->height, cBuf, padlen);
ri.CL_WriteAVIVideoFrame(cmd->encodeBuffer, memcount);
@@ -934,7 +991,7 @@ const void *RB_TakeVideoFrameCmd( const void *data )
*destptr++ = srcptr[2];
*destptr++ = srcptr[1];
*destptr++ = srcptr[0];
- srcptr += 3;
+ srcptr += bytesPerPixel;
}
Com_Memset(destptr, '\0', avipadlen);
@@ -1308,8 +1365,15 @@ void R_Register( void )
r_dlightBacks = ri.Cvar_Get( "r_dlightBacks", "1", CVAR_ARCHIVE );
r_finish = ri.Cvar_Get ("r_finish", "0", CVAR_ARCHIVE);
r_textureMode = ri.Cvar_Get( "r_textureMode", "GL_LINEAR_MIPMAP_LINEAR", CVAR_ARCHIVE );
+#ifdef __EMSCRIPTEN__
+ // Under Emscripten we don't throttle framerate with com_maxfps by default, so enable
+ // vsync by default instead.
+ r_swapInterval = ri.Cvar_Get( "r_swapInterval", "1",
+ CVAR_ARCHIVE | CVAR_LATCH );
+#else
r_swapInterval = ri.Cvar_Get( "r_swapInterval", "0",
CVAR_ARCHIVE | CVAR_LATCH );
+#endif
r_gamma = ri.Cvar_Get( "r_gamma", "1", CVAR_ARCHIVE );
r_facePlaneCull = ri.Cvar_Get ("r_facePlaneCull", "1", CVAR_ARCHIVE );
diff --git a/code/renderergl2/tr_local.h b/code/renderergl2/tr_local.h
index adbb3d597f..9f46866fb9 100644
--- a/code/renderergl2/tr_local.h
+++ b/code/renderergl2/tr_local.h
@@ -49,8 +49,10 @@ QGL_ARB_vertex_array_object_PROCS;
QGL_EXT_direct_state_access_PROCS;
#undef GLE
-#define GL_INDEX_TYPE GL_UNSIGNED_INT
-typedef unsigned int glIndex_t;
+#define GL_INDEX_TYPE GL_UNSIGNED_SHORT
+typedef unsigned short glIndex_t;
+
+typedef unsigned int vaoCacheGlIndex_t;
#define BUFFER_OFFSET(i) ((char *)NULL + (i))
@@ -637,8 +639,14 @@ typedef enum
UNIFORM_ENABLETEXTURES,
- UNIFORM_DIFFUSETEXMATRIX,
- UNIFORM_DIFFUSETEXOFFTURB,
+ UNIFORM_DIFFUSETEXMATRIX0,
+ UNIFORM_DIFFUSETEXMATRIX1,
+ UNIFORM_DIFFUSETEXMATRIX2,
+ UNIFORM_DIFFUSETEXMATRIX3,
+ UNIFORM_DIFFUSETEXMATRIX4,
+ UNIFORM_DIFFUSETEXMATRIX5,
+ UNIFORM_DIFFUSETEXMATRIX6,
+ UNIFORM_DIFFUSETEXMATRIX7,
UNIFORM_TCGEN0,
UNIFORM_TCGEN0VECTOR0,
@@ -1400,6 +1408,7 @@ typedef struct {
qboolean intelGraphics;
qboolean occlusionQuery;
+ GLenum occlusionQueryTarget;
int glslMajorVersion;
int glslMinorVersion;
@@ -1423,6 +1432,18 @@ typedef struct {
qboolean vertexArrayObject;
qboolean directStateAccess;
+
+ int maxVertexAttribs;
+ qboolean gpuVertexAnimation;
+
+ GLenum vaoCacheGlIndexType; // GL_UNSIGNED_INT or GL_UNSIGNED_SHORT
+ size_t vaoCacheGlIndexSize; // must be <= sizeof( vaoCacheGlIndex_t )
+
+ // OpenGL ES extensions
+ qboolean readDepth;
+ qboolean readStencil;
+ qboolean shadowSamplers;
+ qboolean standardDerivatives;
} glRefConfig_t;
@@ -1989,6 +2010,7 @@ const void *RB_TakeVideoFrameCmd( const void *data );
// tr_shader.c
//
shader_t *R_FindShader( const char *name, int lightmapIndex, qboolean mipRawImage );
+shader_t *R_FindShaderEx( const char *name, int lightmapIndex, qboolean mipRawImage, int realLightmapIndex );
shader_t *R_GetShaderByHandle( qhandle_t hShader );
shader_t *R_GetShaderByState( int index, long *cycleTime );
shader_t *R_FindShaderByName( const char *name );
@@ -2206,6 +2228,7 @@ void R_VaoList_f(void);
void RB_UpdateTessVao(unsigned int attribBits);
void VaoCache_Commit(void);
+void VaoCache_DrawElements(int numIndexes, int firstIndex);
void VaoCache_Init(void);
void VaoCache_BindVao(void);
void VaoCache_CheckAdd(qboolean *endSurface, qboolean *recycleVertexBuffer, qboolean *recycleIndexBuffer, int numVerts, int numIndexes);
@@ -2495,5 +2518,7 @@ size_t RE_SaveJPGToBuffer(byte *buffer, size_t bufSize, int quality,
void RE_TakeVideoFrame( int width, int height,
byte *captureBuffer, byte *encodeBuffer, qboolean motionJpeg );
+void R_ConvertTextureFormat( const byte *in, int width, int height, GLenum format, GLenum type, byte *out );
+
#endif //TR_LOCAL_H
diff --git a/code/renderergl2/tr_mesh.c b/code/renderergl2/tr_mesh.c
index 4f2c67245b..222b06d0ee 100644
--- a/code/renderergl2/tr_mesh.c
+++ b/code/renderergl2/tr_mesh.c
@@ -282,9 +282,10 @@ R_AddMD3Surfaces
*/
void R_AddMD3Surfaces( trRefEntity_t *ent ) {
int i;
- mdvModel_t *model = NULL;
- mdvSurface_t *surface = NULL;
- shader_t *shader = NULL;
+ mdvModel_t *model;
+ mdvSurface_t *surface;
+ void *drawSurf;
+ shader_t *shader;
int cull;
int lod;
int fogNum;
@@ -382,6 +383,12 @@ void R_AddMD3Surfaces( trRefEntity_t *ent ) {
shader = tr.shaders[ surface->shaderIndexes[ ent->e.skinNum % surface->numShaderIndexes ] ];
}
+ if ( model->numVaoSurfaces > 0 ) {
+ drawSurf = &model->vaoSurfaces[i];
+ } else {
+ drawSurf = surface;
+ }
+
// we will add shadows even if the main object isn't visible in the view
// stencil shadows can't do personal models unless I polyhedron clip
@@ -390,7 +397,7 @@ void R_AddMD3Surfaces( trRefEntity_t *ent ) {
&& fogNum == 0
&& !(ent->e.renderfx & ( RF_NOSHADOW | RF_DEPTHHACK ) )
&& shader->sort == SS_OPAQUE ) {
- R_AddDrawSurf( (void *)&model->vaoSurfaces[i], tr.shadowShader, 0, qfalse, qfalse, 0 );
+ R_AddDrawSurf( drawSurf, tr.shadowShader, 0, qfalse, qfalse, 0 );
}
// projection shadows work fine with personal models
@@ -398,12 +405,12 @@ void R_AddMD3Surfaces( trRefEntity_t *ent ) {
&& fogNum == 0
&& (ent->e.renderfx & RF_SHADOW_PLANE )
&& shader->sort == SS_OPAQUE ) {
- R_AddDrawSurf( (void *)&model->vaoSurfaces[i], tr.projectionShadowShader, 0, qfalse, qfalse, 0 );
+ R_AddDrawSurf( drawSurf, tr.projectionShadowShader, 0, qfalse, qfalse, 0 );
}
// don't add third_person objects if not viewing through a portal
if ( !personalModel ) {
- R_AddDrawSurf((void *)&model->vaoSurfaces[i], shader, fogNum, qfalse, qfalse, cubemapIndex );
+ R_AddDrawSurf( drawSurf, shader, fogNum, qfalse, qfalse, cubemapIndex );
}
surface++;
diff --git a/code/renderergl2/tr_model.c b/code/renderergl2/tr_model.c
index 0fdf60d832..7201be1bf7 100644
--- a/code/renderergl2/tr_model.c
+++ b/code/renderergl2/tr_model.c
@@ -664,6 +664,12 @@ static qboolean R_LoadMD3(model_t * mod, int lod, void *buffer, int bufferSize,
surf++;
}
+ if (mdvModel->numFrames > 1 && !glRefConfig.gpuVertexAnimation)
+ {
+ mdvModel->numVaoSurfaces = 0;
+ mdvModel->vaoSurfaces = NULL;
+ }
+ else
{
srfVaoMdvMesh_t *vaoSurf;
diff --git a/code/renderergl2/tr_postprocess.c b/code/renderergl2/tr_postprocess.c
index 9931757b82..ff9a25a130 100644
--- a/code/renderergl2/tr_postprocess.c
+++ b/code/renderergl2/tr_postprocess.c
@@ -290,6 +290,8 @@ static qboolean RB_UpdateSunFlareVis(void)
ri.Printf(PRINT_DEVELOPER, "Waited %d iterations\n", iter);
}
+ // Note: On desktop OpenGL this is a sample count (glRefConfig.occlusionQueryTarget == GL_SAMPLES_PASSED)
+ // but on OpenGL ES this is a boolean (glRefConfig.occlusionQueryTarget == GL_ANY_SAMPLES_PASSED)
qglGetQueryObjectuiv(tr.sunFlareQuery[tr.sunFlareQueryIndex], GL_QUERY_RESULT, &sampleCount);
return sampleCount > 0;
}
@@ -447,7 +449,7 @@ static void RB_VBlur(FBO_t *srcFbo, FBO_t *dstFbo, float strength)
RB_BlurAxis(srcFbo, dstFbo, strength, qfalse);
}
-void RB_GaussianBlur(float blur)
+void RB_GaussianBlur(FBO_t *srcFbo, FBO_t *dstFbo, float blur)
{
//float mul = 1.f;
float factor = Com_Clamp(0.f, 1.f, blur);
@@ -462,7 +464,7 @@ void RB_GaussianBlur(float blur)
VectorSet4(color, 1, 1, 1, 1);
// first, downsample the framebuffer
- FBO_FastBlit(NULL, NULL, tr.quarterFbo[0], NULL, GL_COLOR_BUFFER_BIT, GL_LINEAR);
+ FBO_FastBlit(srcFbo, NULL, tr.quarterFbo[0], NULL, GL_COLOR_BUFFER_BIT, GL_LINEAR);
FBO_FastBlit(tr.quarterFbo[0], NULL, tr.textureScratchFbo[0], NULL, GL_COLOR_BUFFER_BIT, GL_LINEAR);
// set the alpha channel
@@ -478,6 +480,6 @@ void RB_GaussianBlur(float blur)
VectorSet4(srcBox, 0, 0, tr.textureScratchFbo[0]->width, tr.textureScratchFbo[0]->height);
VectorSet4(dstBox, 0, 0, glConfig.vidWidth, glConfig.vidHeight);
color[3] = factor;
- FBO_Blit(tr.textureScratchFbo[0], srcBox, NULL, NULL, dstBox, NULL, color, GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA);
+ FBO_Blit(tr.textureScratchFbo[0], srcBox, NULL, dstFbo, dstBox, NULL, color, GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA);
}
}
diff --git a/code/renderergl2/tr_postprocess.h b/code/renderergl2/tr_postprocess.h
index 09daf13465..a2d6d0b3f2 100644
--- a/code/renderergl2/tr_postprocess.h
+++ b/code/renderergl2/tr_postprocess.h
@@ -28,6 +28,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
void RB_ToneMap(FBO_t *hdrFbo, ivec4_t hdrBox, FBO_t *ldrFbo, ivec4_t ldrBox, int autoExposure);
void RB_BokehBlur(FBO_t *src, ivec4_t srcBox, FBO_t *dst, ivec4_t dstBox, float blur);
void RB_SunRays(FBO_t *srcFbo, ivec4_t srcBox, FBO_t *dstFbo, ivec4_t dstBox);
-void RB_GaussianBlur(float blur);
+void RB_GaussianBlur(FBO_t *srcFbo, FBO_t *dstFbo, float blur);
#endif
diff --git a/code/renderergl2/tr_shade.c b/code/renderergl2/tr_shade.c
index 5300898c3d..dec35e52a5 100644
--- a/code/renderergl2/tr_shade.c
+++ b/code/renderergl2/tr_shade.c
@@ -40,7 +40,14 @@ R_DrawElements
void R_DrawElements( int numIndexes, int firstIndex )
{
- qglDrawElements(GL_TRIANGLES, numIndexes, GL_INDEX_TYPE, BUFFER_OFFSET(firstIndex * sizeof(glIndex_t)));
+ if (tess.useCacheVao)
+ {
+ VaoCache_DrawElements(numIndexes, firstIndex);
+ }
+ else
+ {
+ qglDrawElements(GL_TRIANGLES, numIndexes, GL_INDEX_TYPE, BUFFER_OFFSET(firstIndex * sizeof(glIndex_t)));
+ }
}
@@ -181,33 +188,30 @@ extern float EvalWaveForm( const waveForm_t *wf );
extern float EvalWaveFormClamped( const waveForm_t *wf );
-static void ComputeTexMods( shaderStage_t *pStage, int bundleNum, float *outMatrix, float *outOffTurb)
+static void ComputeTexMods( shaderStage_t *pStage, int bundleNum, vec4_t outMatrix[8])
{
int tm;
- float matrix[6], currentmatrix[6];
+ float matrix[6];
+ float tmpmatrix[6];
+ float currentmatrix[6];
+ float turb[2];
textureBundle_t *bundle = &pStage->bundle[bundleNum];
-
- matrix[0] = 1.0f; matrix[2] = 0.0f; matrix[4] = 0.0f;
- matrix[1] = 0.0f; matrix[3] = 1.0f; matrix[5] = 0.0f;
+ qboolean hasTurb = qfalse;
currentmatrix[0] = 1.0f; currentmatrix[2] = 0.0f; currentmatrix[4] = 0.0f;
currentmatrix[1] = 0.0f; currentmatrix[3] = 1.0f; currentmatrix[5] = 0.0f;
- outMatrix[0] = 1.0f; outMatrix[2] = 0.0f;
- outMatrix[1] = 0.0f; outMatrix[3] = 1.0f;
-
- outOffTurb[0] = 0.0f; outOffTurb[1] = 0.0f; outOffTurb[2] = 0.0f; outOffTurb[3] = 0.0f;
-
for ( tm = 0; tm < bundle->numTexMods ; tm++ ) {
switch ( bundle->texMods[tm].type )
{
case TMOD_NONE:
- tm = TR_MAX_TEXMODS; // break out of for loop
+ matrix[0] = 1.0f; matrix[2] = 0.0f; matrix[4] = 0.0f;
+ matrix[1] = 0.0f; matrix[3] = 1.0f; matrix[5] = 0.0f;
break;
case TMOD_TURBULENT:
- RB_CalcTurbulentFactors(&bundle->texMods[tm].wave, &outOffTurb[2], &outOffTurb[3]);
+ RB_CalcTurbulentFactors(&bundle->texMods[tm].wave, &turb[0], &turb[1]);
break;
case TMOD_ENTITY_TRANSLATE:
@@ -246,35 +250,68 @@ static void ComputeTexMods( shaderStage_t *pStage, int bundleNum, float *outMatr
switch ( bundle->texMods[tm].type )
{
- case TMOD_NONE:
case TMOD_TURBULENT:
- default:
+ outMatrix[tm*2+0][0] = 1; outMatrix[tm*2+0][1] = 0; outMatrix[tm*2+0][2] = 0;
+ outMatrix[tm*2+1][0] = 0; outMatrix[tm*2+1][1] = 1; outMatrix[tm*2+1][2] = 0;
+
+ outMatrix[tm*2+0][3] = turb[0];
+ outMatrix[tm*2+1][3] = turb[1];
+
+ hasTurb = qtrue;
break;
+ case TMOD_NONE:
case TMOD_ENTITY_TRANSLATE:
case TMOD_SCROLL:
case TMOD_SCALE:
case TMOD_STRETCH:
case TMOD_TRANSFORM:
case TMOD_ROTATE:
- outMatrix[0] = matrix[0] * currentmatrix[0] + matrix[2] * currentmatrix[1];
- outMatrix[1] = matrix[1] * currentmatrix[0] + matrix[3] * currentmatrix[1];
+ default:
+ outMatrix[tm*2+0][0] = matrix[0]; outMatrix[tm*2+0][1] = matrix[2]; outMatrix[tm*2+0][2] = matrix[4];
+ outMatrix[tm*2+1][0] = matrix[1]; outMatrix[tm*2+1][1] = matrix[3]; outMatrix[tm*2+1][2] = matrix[5];
+
+ outMatrix[tm*2+0][3] = 0;
+ outMatrix[tm*2+1][3] = 0;
- outMatrix[2] = matrix[0] * currentmatrix[2] + matrix[2] * currentmatrix[3];
- outMatrix[3] = matrix[1] * currentmatrix[2] + matrix[3] * currentmatrix[3];
+ tmpmatrix[0] = matrix[0] * currentmatrix[0] + matrix[2] * currentmatrix[1];
+ tmpmatrix[1] = matrix[1] * currentmatrix[0] + matrix[3] * currentmatrix[1];
- outOffTurb[0] = matrix[0] * currentmatrix[4] + matrix[2] * currentmatrix[5] + matrix[4];
- outOffTurb[1] = matrix[1] * currentmatrix[4] + matrix[3] * currentmatrix[5] + matrix[5];
+ tmpmatrix[2] = matrix[0] * currentmatrix[2] + matrix[2] * currentmatrix[3];
+ tmpmatrix[3] = matrix[1] * currentmatrix[2] + matrix[3] * currentmatrix[3];
- currentmatrix[0] = outMatrix[0];
- currentmatrix[1] = outMatrix[1];
- currentmatrix[2] = outMatrix[2];
- currentmatrix[3] = outMatrix[3];
- currentmatrix[4] = outOffTurb[0];
- currentmatrix[5] = outOffTurb[1];
+ tmpmatrix[4] = matrix[0] * currentmatrix[4] + matrix[2] * currentmatrix[5] + matrix[4];
+ tmpmatrix[5] = matrix[1] * currentmatrix[4] + matrix[3] * currentmatrix[5] + matrix[5];
+
+ currentmatrix[0] = tmpmatrix[0];
+ currentmatrix[1] = tmpmatrix[1];
+ currentmatrix[2] = tmpmatrix[2];
+ currentmatrix[3] = tmpmatrix[3];
+ currentmatrix[4] = tmpmatrix[4];
+ currentmatrix[5] = tmpmatrix[5];
break;
}
}
+
+ // if turb isn't used, only one matrix is needed
+ if ( !hasTurb ) {
+ tm = 0;
+
+ outMatrix[tm*2+0][0] = currentmatrix[0]; outMatrix[tm*2+0][1] = currentmatrix[2]; outMatrix[tm*2+0][2] = currentmatrix[4];
+ outMatrix[tm*2+1][0] = currentmatrix[1]; outMatrix[tm*2+1][1] = currentmatrix[3]; outMatrix[tm*2+1][2] = currentmatrix[5];
+
+ outMatrix[tm*2+0][3] = 0;
+ outMatrix[tm*2+1][3] = 0;
+ tm++;
+ }
+
+ for ( ; tm < TR_MAX_TEXMODS ; tm++ ) {
+ outMatrix[tm*2+0][0] = 1; outMatrix[tm*2+0][1] = 0; outMatrix[tm*2+0][2] = 0;
+ outMatrix[tm*2+1][0] = 0; outMatrix[tm*2+1][1] = 1; outMatrix[tm*2+1][2] = 0;
+
+ outMatrix[tm*2+0][3] = 0;
+ outMatrix[tm*2+1][3] = 0;
+ }
}
@@ -665,8 +702,7 @@ static void ForwardDlight( void ) {
dlight_t *dl;
shaderProgram_t *sp;
vec4_t vector;
- vec4_t texMatrix;
- vec4_t texOffTurb;
+ vec4_t texMatrix[8];
if ( !( tess.dlightBits & ( 1 << l ) ) ) {
continue; // this surface definitely doesn't have any of this light
@@ -792,9 +828,15 @@ static void ForwardDlight( void ) {
if (r_dlightMode->integer >= 2)
GL_BindToTMU(tr.shadowCubemaps[l], TB_SHADOWMAP);
- ComputeTexMods( pStage, TB_DIFFUSEMAP, texMatrix, texOffTurb );
- GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX, texMatrix);
- GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXOFFTURB, texOffTurb);
+ ComputeTexMods( pStage, TB_DIFFUSEMAP, texMatrix );
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX0, texMatrix[0]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX1, texMatrix[1]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX2, texMatrix[2]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX3, texMatrix[3]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX4, texMatrix[4]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX5, texMatrix[5]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX6, texMatrix[6]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX7, texMatrix[7]);
GLSL_SetUniformInt(sp, UNIFORM_TCGEN0, pStage->bundle[0].tcGen);
@@ -996,8 +1038,7 @@ static void RB_IterateStagesGeneric( shaderCommands_t *input )
{
shaderStage_t *pStage = input->xstages[stage];
shaderProgram_t *sp;
- vec4_t texMatrix;
- vec4_t texOffTurb;
+ vec4_t texMatrix[8];
if ( !pStage )
{
@@ -1184,19 +1225,31 @@ static void RB_IterateStagesGeneric( shaderCommands_t *input )
if (r_lightmap->integer)
{
- vec4_t v;
- VectorSet4(v, 1.0f, 0.0f, 0.0f, 1.0f);
- GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX, v);
- VectorSet4(v, 0.0f, 0.0f, 0.0f, 0.0f);
- GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXOFFTURB, v);
+ vec4_t st[2];
+ VectorSet4(st[0], 1.0f, 0.0f, 0.0f, 0.0f);
+ VectorSet4(st[1], 0.0f, 1.0f, 0.0f, 0.0f);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX0, st[0]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX1, st[1]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX2, st[0]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX3, st[1]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX4, st[0]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX5, st[1]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX6, st[0]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX7, st[1]);
GLSL_SetUniformInt(sp, UNIFORM_TCGEN0, TCGEN_LIGHTMAP);
}
else
{
- ComputeTexMods(pStage, TB_DIFFUSEMAP, texMatrix, texOffTurb);
- GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX, texMatrix);
- GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXOFFTURB, texOffTurb);
+ ComputeTexMods(pStage, TB_DIFFUSEMAP, texMatrix);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX0, texMatrix[0]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX1, texMatrix[1]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX2, texMatrix[2]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX3, texMatrix[3]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX4, texMatrix[4]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX5, texMatrix[5]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX6, texMatrix[6]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX7, texMatrix[7]);
GLSL_SetUniformInt(sp, UNIFORM_TCGEN0, pStage->bundle[0].tcGen);
if (pStage->bundle[0].tcGen == TCGEN_VECTOR)
@@ -1674,6 +1727,8 @@ void RB_EndSurface( void ) {
tess.numIndexes = 0;
tess.numVertexes = 0;
tess.firstIndex = 0;
+ tess.useCacheVao = qfalse;
+ tess.useInternalVao = qfalse;
GLimp_LogComment( "----------\n" );
}
diff --git a/code/renderergl2/tr_shader.c b/code/renderergl2/tr_shader.c
index 4b9fa991b8..b08a932878 100644
--- a/code/renderergl2/tr_shader.c
+++ b/code/renderergl2/tr_shader.c
@@ -30,6 +30,7 @@ static char *s_shaderText;
static shaderStage_t stages[MAX_SHADER_STAGES];
static shader_t shader;
static texModInfo_t texMods[MAX_SHADER_STAGES][TR_MAX_TEXMODS];
+static int shader_realLightmapIndex;
#define FILE_HASH_SIZE 1024
static shader_t* hashTable[FILE_HASH_SIZE];
@@ -2929,7 +2930,7 @@ static void FixFatLightmapTexCoords(void)
return;
}
- lightmapnum = shader.lightmapIndex;
+ lightmapnum = shader_realLightmapIndex;
if (tr.worldDeluxeMapping)
lightmapnum >>= 1;
@@ -2943,21 +2944,44 @@ static void FixFatLightmapTexCoords(void)
break;
}
- // fix tcMod transform for internal lightmaps, it may be used by q3map2 lightstyles
if ( pStage->bundle[0].isLightmap ) {
- for ( i = 0; i < pStage->bundle[0].numTexMods; i++ ) {
- tmi = &pStage->bundle[0].texMods[i];
+ // fix tcMod transform for internal lightmaps, it may be used by q3map2 lightstyles
+ if ( pStage->bundle[0].tcGen == TCGEN_LIGHTMAP ) {
+ for ( i = 0; i < pStage->bundle[0].numTexMods; i++ ) {
+ tmi = &pStage->bundle[0].texMods[i];
+
+ if ( tmi->type == TMOD_TRANSFORM ) {
+ tmi->translate[0] /= (float)tr.fatLightmapCols;
+ tmi->translate[1] /= (float)tr.fatLightmapRows;
+ }
+ }
+ }
+
+ // fix tcGen environment for internal lightmaps to be limited to the sub-image of the atlas
+ // this is done last so other tcMods are applied first in the 0.0 to 1.0 space
+ if ( pStage->bundle[0].tcGen == TCGEN_ENVIRONMENT_MAPPED ) {
+ if ( pStage->bundle[0].numTexMods == TR_MAX_TEXMODS ) {
+ ri.Printf( PRINT_DEVELOPER, "WARNING: too many tcmods to fix lightmap texcoords for r_mergeLightmaps in shader '%s'", shader.name );
+ } else {
+ tmi = &pStage->bundle[0].texMods[pStage->bundle[0].numTexMods];
+ pStage->bundle[0].numTexMods++;
+
+ tmi->matrix[0][0] = 1.0f / tr.fatLightmapCols;
+ tmi->matrix[0][1] = 0;
+ tmi->matrix[1][0] = 0;
+ tmi->matrix[1][1] = 1.0f / tr.fatLightmapRows;
+
+ tmi->translate[0] = ( lightmapnum % tr.fatLightmapCols ) / (float)tr.fatLightmapCols;
+ tmi->translate[1] = ( lightmapnum / tr.fatLightmapCols ) / (float)tr.fatLightmapRows;
- if ( tmi->type == TMOD_TRANSFORM ) {
- tmi->translate[0] /= (float)tr.fatLightmapCols;
- tmi->translate[1] /= (float)tr.fatLightmapRows;
+ tmi->type = TMOD_TRANSFORM;
}
}
}
// add a tcMod transform for external lightmaps to convert back to the original texcoords
else if ( pStage->bundle[0].tcGen == TCGEN_LIGHTMAP ) {
if ( pStage->bundle[0].numTexMods == TR_MAX_TEXMODS ) {
- ri.Printf( PRINT_DEVELOPER, "WARNING: too many tcmods to fix external lightmap texcoords for r_mergeLightmaps in shader '%s'", shader.name );
+ ri.Printf( PRINT_DEVELOPER, "WARNING: too many tcmods to fix lightmap texcoords for r_mergeLightmaps in shader '%s'", shader.name );
} else {
size = pStage->bundle[0].numTexMods * sizeof( texModInfo_t );
@@ -2973,8 +2997,8 @@ static void FixFatLightmapTexCoords(void)
tmi->matrix[1][0] = 0;
tmi->matrix[1][1] = tr.fatLightmapRows;
- tmi->translate[0] = -(lightmapnum % tr.fatLightmapCols);
- tmi->translate[1] = -(lightmapnum / tr.fatLightmapCols);
+ tmi->translate[0] = -( lightmapnum % tr.fatLightmapCols );
+ tmi->translate[1] = -( lightmapnum / tr.fatLightmapCols );
tmi->type = TMOD_TRANSFORM;
}
@@ -2987,7 +3011,7 @@ static void FixFatLightmapTexCoords(void)
InitShader
===============
*/
-static void InitShader( const char *name, int lightmapIndex ) {
+static void InitShaderEx( const char *name, int lightmapIndex, int realLightmapIndex ) {
int i;
// clear the global shader
@@ -2996,6 +3020,7 @@ static void InitShader( const char *name, int lightmapIndex ) {
Q_strncpyz( shader.name, name, sizeof( shader.name ) );
shader.lightmapIndex = lightmapIndex;
+ shader_realLightmapIndex = realLightmapIndex;
for ( i = 0 ; i < MAX_SHADER_STAGES ; i++ ) {
stages[i].bundle[0].texMods = texMods[i];
@@ -3016,6 +3041,10 @@ static void InitShader( const char *name, int lightmapIndex ) {
}
}
+static void InitShader( const char *name, int lightmapIndex ) {
+ InitShaderEx( name, lightmapIndex, lightmapIndex );
+}
+
/*
=========================
FinishShader
@@ -3337,6 +3366,10 @@ most world construction surfaces.
===============
*/
shader_t *R_FindShader( const char *name, int lightmapIndex, qboolean mipRawImage ) {
+ return R_FindShaderEx( name, lightmapIndex, mipRawImage, lightmapIndex );
+}
+
+shader_t *R_FindShaderEx( const char *name, int lightmapIndex, qboolean mipRawImage, int realLightmapIndex ) {
char strippedName[MAX_QPATH];
int hash;
char *shaderText;
@@ -3376,7 +3409,7 @@ shader_t *R_FindShader( const char *name, int lightmapIndex, qboolean mipRawImag
}
}
- InitShader( strippedName, lightmapIndex );
+ InitShaderEx( strippedName, lightmapIndex, realLightmapIndex );
//
// attempt to define shader from an explicit parameter file
diff --git a/code/renderergl2/tr_sky.c b/code/renderergl2/tr_sky.c
index 94f68d26e6..c79f48f5a6 100644
--- a/code/renderergl2/tr_sky.c
+++ b/code/renderergl2/tr_sky.c
@@ -435,7 +435,7 @@ static void DrawSkySide( struct image_s *image, const int mins[2], const int max
*/
{
shaderProgram_t *sp = &tr.lightallShader[0];
- vec4_t vector;
+ vec4_t st[2];
GLSL_BindProgram(sp);
@@ -453,11 +453,16 @@ static void DrawSkySide( struct image_s *image, const int mins[2], const int max
color[3] = 0.0f;
GLSL_SetUniformVec4(sp, UNIFORM_VERTCOLOR, color);
- VectorSet4(vector, 1.0, 0.0, 0.0, 1.0);
- GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX, vector);
-
- VectorSet4(vector, 0.0, 0.0, 0.0, 0.0);
- GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXOFFTURB, vector);
+ VectorSet4(st[0], 1.0f, 0.0f, 0.0f, 0.0f);
+ VectorSet4(st[1], 0.0f, 1.0f, 0.0f, 0.0f);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX0, st[0]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX1, st[1]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX2, st[0]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX3, st[1]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX4, st[0]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX5, st[1]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX6, st[0]);
+ GLSL_SetUniformVec4(sp, UNIFORM_DIFFUSETEXMATRIX7, st[1]);
GLSL_SetUniformInt(sp, UNIFORM_ALPHATEST, 0);
}
diff --git a/code/renderergl2/tr_vbo.c b/code/renderergl2/tr_vbo.c
index df64f8bc6e..5094df1846 100644
--- a/code/renderergl2/tr_vbo.c
+++ b/code/renderergl2/tr_vbo.c
@@ -676,7 +676,7 @@ static struct
srfVert_t vertexes[VAOCACHE_QUEUE_MAX_VERTEXES];
int vertexCommitSize;
- glIndex_t indexes[VAOCACHE_QUEUE_MAX_INDEXES];
+ vaoCacheGlIndex_t indexes[VAOCACHE_QUEUE_MAX_INDEXES];
int indexCommitSize;
}
vcq;
@@ -687,18 +687,13 @@ vcq;
// srfVert_t is 60 bytes
// assuming each vert is referenced 4 times, need 16 bytes (4 glIndex_t) per vert
// -> need about 4/15ths the space for indexes as vertexes
-#if GL_INDEX_TYPE == GL_UNSIGNED_SHORT
-#define VAOCACHE_VERTEX_BUFFER_SIZE (sizeof(srfVert_t) * USHRT_MAX)
-#define VAOCACHE_INDEX_BUFFER_SIZE (sizeof(glIndex_t) * USHRT_MAX * 4)
-#else // GL_UNSIGNED_INT
#define VAOCACHE_VERTEX_BUFFER_SIZE (16 * 1024 * 1024)
#define VAOCACHE_INDEX_BUFFER_SIZE (5 * 1024 * 1024)
-#endif
typedef struct buffered_s
{
- void *data;
- int size;
+ glIndex_t *indexes;
+ int numIndexes;
int bufferOffset;
}
buffered_t;
@@ -736,7 +731,7 @@ void VaoCache_Commit(void)
buffered_t *indexSet2 = indexSet;
for (surf = vcq.surfaces; surf < end; surf++, indexSet2++)
{
- if (surf->indexes != indexSet2->data || (surf->numIndexes * sizeof(glIndex_t)) != indexSet2->size)
+ if (surf->indexes != indexSet2->indexes || surf->numIndexes != indexSet2->numIndexes)
break;
}
@@ -750,7 +745,7 @@ void VaoCache_Commit(void)
// If found, use it
if (indexSet < vc.surfaceIndexSets + vc.numSurfaces)
{
- tess.firstIndex = indexSet->bufferOffset / sizeof(glIndex_t);
+ tess.firstIndex = indexSet->bufferOffset / glRefConfig.vaoCacheGlIndexSize;
//ri.Printf(PRINT_ALL, "firstIndex %d numIndexes %d as %d\n", tess.firstIndex, tess.numIndexes, (int)(batchLength - vc.batchLengths));
//ri.Printf(PRINT_ALL, "vc.numSurfaces %d vc.numBatches %d\n", vc.numSurfaces, vc.numBatches);
}
@@ -759,20 +754,21 @@ void VaoCache_Commit(void)
else
{
srfVert_t *dstVertex = vcq.vertexes;
- glIndex_t *dstIndex = vcq.indexes;
+ vaoCacheGlIndex_t *dstIndex = vcq.indexes;
+ unsigned short *dstIndexUshort = (unsigned short *)vcq.indexes;
batchLength = vc.batchLengths + vc.numBatches;
*batchLength = vcq.numSurfaces;
vc.numBatches++;
- tess.firstIndex = vc.indexOffset / sizeof(glIndex_t);
+ tess.firstIndex = vc.indexOffset / glRefConfig.vaoCacheGlIndexSize;
vcq.vertexCommitSize = 0;
vcq.indexCommitSize = 0;
for (surf = vcq.surfaces; surf < end; surf++)
{
glIndex_t *srcIndex = surf->indexes;
int vertexesSize = surf->numVerts * sizeof(srfVert_t);
- int indexesSize = surf->numIndexes * sizeof(glIndex_t);
+ int indexesSize = surf->numIndexes * glRefConfig.vaoCacheGlIndexSize;
int i, indexOffset = (vc.vertexOffset + vcq.vertexCommitSize) / sizeof(srfVert_t);
Com_Memcpy(dstVertex, surf->vertexes, vertexesSize);
@@ -781,13 +777,21 @@ void VaoCache_Commit(void)
vcq.vertexCommitSize += vertexesSize;
indexSet = vc.surfaceIndexSets + vc.numSurfaces;
- indexSet->data = surf->indexes;
- indexSet->size = indexesSize;
+ indexSet->indexes = surf->indexes;
+ indexSet->numIndexes = surf->numIndexes;
indexSet->bufferOffset = vc.indexOffset + vcq.indexCommitSize;
vc.numSurfaces++;
- for (i = 0; i < surf->numIndexes; i++)
- *dstIndex++ = *srcIndex++ + indexOffset;
+ if (glRefConfig.vaoCacheGlIndexType == GL_UNSIGNED_SHORT)
+ {
+ for (i = 0; i < surf->numIndexes; i++)
+ *dstIndexUshort++ = *srcIndex++ + indexOffset;
+ }
+ else
+ {
+ for (i = 0; i < surf->numIndexes; i++)
+ *dstIndex++ = *srcIndex++ + indexOffset;
+ }
vcq.indexCommitSize += indexesSize;
}
@@ -810,9 +814,30 @@ void VaoCache_Commit(void)
}
}
+void VaoCache_DrawElements(int numIndexes, int firstIndex)
+{
+ assert( glState.currentVao == vc.vao );
+
+ qglDrawElements(GL_TRIANGLES, numIndexes, glRefConfig.vaoCacheGlIndexType, BUFFER_OFFSET(firstIndex * glRefConfig.vaoCacheGlIndexSize));
+}
+
void VaoCache_Init(void)
{
- vc.vao = R_CreateVao("VaoCache", NULL, VAOCACHE_VERTEX_BUFFER_SIZE, NULL, VAOCACHE_INDEX_BUFFER_SIZE, VAO_USAGE_DYNAMIC);
+ int vertexBufferSize;
+ int indexBufferSize;
+
+ if (glRefConfig.vaoCacheGlIndexType == GL_UNSIGNED_SHORT)
+ {
+ vertexBufferSize = sizeof(srfVert_t) * USHRT_MAX;
+ indexBufferSize = sizeof(unsigned short) * USHRT_MAX * 4;
+ }
+ else
+ {
+ vertexBufferSize = VAOCACHE_VERTEX_BUFFER_SIZE;
+ indexBufferSize = VAOCACHE_INDEX_BUFFER_SIZE;
+ }
+
+ vc.vao = R_CreateVao("VaoCache", NULL, vertexBufferSize, NULL, indexBufferSize, VAO_USAGE_DYNAMIC);
vc.vao->attribs[ATTR_INDEX_POSITION].enabled = 1;
vc.vao->attribs[ATTR_INDEX_TEXCOORD].enabled = 1;
@@ -881,7 +906,7 @@ void VaoCache_BindVao(void)
void VaoCache_CheckAdd(qboolean *endSurface, qboolean *recycleVertexBuffer, qboolean *recycleIndexBuffer, int numVerts, int numIndexes)
{
int vertexesSize = sizeof(srfVert_t) * numVerts;
- int indexesSize = sizeof(glIndex_t) * numIndexes;
+ int indexesSize = glRefConfig.vaoCacheGlIndexSize * numIndexes;
if (vc.vao->vertexesSize < vc.vertexOffset + vcq.vertexCommitSize + vertexesSize)
{
@@ -924,7 +949,7 @@ void VaoCache_CheckAdd(qboolean *endSurface, qboolean *recycleVertexBuffer, qboo
*endSurface = qtrue;
}
- if (VAOCACHE_QUEUE_MAX_INDEXES * sizeof(glIndex_t) < vcq.indexCommitSize + indexesSize)
+ if (VAOCACHE_QUEUE_MAX_INDEXES * glRefConfig.vaoCacheGlIndexSize < vcq.indexCommitSize + indexesSize)
{
//ri.Printf(PRINT_ALL, "out of queued indexes\n");
*endSurface = qtrue;
@@ -964,5 +989,5 @@ void VaoCache_AddSurface(srfVert_t *verts, int numVerts, glIndex_t *indexes, int
vcq.numSurfaces++;
vcq.vertexCommitSize += sizeof(srfVert_t) * numVerts;
- vcq.indexCommitSize += sizeof(glIndex_t) * numIndexes;
+ vcq.indexCommitSize += glRefConfig.vaoCacheGlIndexSize * numIndexes;
}
diff --git a/code/sdl/sdl_glimp.c b/code/sdl/sdl_glimp.c
index 30986ab118..5ce1e47d91 100644
--- a/code/sdl/sdl_glimp.c
+++ b/code/sdl/sdl_glimp.c
@@ -52,6 +52,7 @@ cvar_t *r_allowSoftwareGL; // Don't abort out if a hardware visual can't be obta
cvar_t *r_allowResize; // make window resizable
cvar_t *r_centerWindow;
cvar_t *r_sdlDriver;
+cvar_t *r_preferOpenGLES;
int qglMajorVersion, qglMinorVersion;
int qglesMajorVersion, qglesMinorVersion;
@@ -230,6 +231,27 @@ static void GLimp_DetectAvailableModes(void)
SDL_free( modes );
}
+/*
+===============
+OpenGL ES compatibility
+===============
+*/
+static void APIENTRY GLimp_GLES_ClearDepth( GLclampd depth ) {
+ qglClearDepthf( depth );
+}
+
+static void APIENTRY GLimp_GLES_DepthRange( GLclampd near_val, GLclampd far_val ) {
+ qglDepthRangef( near_val, far_val );
+}
+
+static void APIENTRY GLimp_GLES_DrawBuffer( GLenum mode ) {
+ // unsupported
+}
+
+static void APIENTRY GLimp_GLES_PolygonMode( GLenum face, GLenum mode ) {
+ // unsupported
+}
+
/*
===============
GLimp_GetProcAddresses
@@ -306,8 +328,11 @@ static qboolean GLimp_GetProcAddresses( qboolean fixedFunction ) {
QGL_1_3_PROCS;
QGL_1_5_PROCS;
QGL_2_0_PROCS;
- // error so this doesn't segfault due to NULL desktop GL functions being used
- Com_Error( ERR_FATAL, "Unsupported OpenGL Version: %s", version );
+
+ qglClearDepth = GLimp_GLES_ClearDepth;
+ qglDepthRange = GLimp_GLES_DepthRange;
+ qglDrawBuffer = GLimp_GLES_DrawBuffer;
+ qglPolygonMode = GLimp_GLES_PolygonMode;
} else {
Com_Error( ERR_FATAL, "Unsupported OpenGL Version (%s), OpenGL 2.0 is required", version );
}
@@ -369,6 +394,12 @@ GLimp_SetMode
*/
static int GLimp_SetMode(int mode, qboolean fullscreen, qboolean noborder, qboolean fixedFunction)
{
+ struct GLimp_ContextType {
+ int profileMask;
+ int majorVersion;
+ int minorVersion;
+ } contexts[4];
+ int numContexts, type;
const char *glstring;
int perChannelColorBits;
int colorBits, depthBits, stencilBits;
@@ -407,10 +438,11 @@ static int GLimp_SetMode(int mode, qboolean fullscreen, qboolean noborder, qbool
if( display < 0 )
{
ri.Printf( PRINT_DEVELOPER, "SDL_GetWindowDisplayIndex() failed: %s\n", SDL_GetError() );
+ display = 0;
}
}
- if( display >= 0 && SDL_GetDesktopDisplayMode( display, &desktopMode ) == 0 )
+ if( SDL_GetDesktopDisplayMode( display, &desktopMode ) == 0 )
{
displayAspect = (float)desktopMode.w / (float)desktopMode.h;
@@ -499,6 +531,63 @@ static int GLimp_SetMode(int mode, qboolean fullscreen, qboolean noborder, qbool
stencilBits = r_stencilbits->value;
samples = r_ext_multisample->value;
+ numContexts = 0;
+
+ if ( !fixedFunction ) {
+ int profileMask;
+ qboolean preferOpenGLES;
+
+ SDL_GL_ResetAttributes();
+ SDL_GL_GetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, &profileMask );
+
+ preferOpenGLES = ( r_preferOpenGLES->integer == 1 ||
+ ( r_preferOpenGLES->integer == -1 && profileMask == SDL_GL_CONTEXT_PROFILE_ES ) );
+
+ if ( preferOpenGLES ) {
+#ifdef __EMSCRIPTEN__
+ // WebGL 2.0 isn't fully backward compatible so you have to ask for it specifically
+ contexts[numContexts].profileMask = SDL_GL_CONTEXT_PROFILE_ES;
+ contexts[numContexts].majorVersion = 3;
+ contexts[numContexts].minorVersion = 0;
+ numContexts++;
+#endif
+
+ contexts[numContexts].profileMask = SDL_GL_CONTEXT_PROFILE_ES;
+ contexts[numContexts].majorVersion = 2;
+ contexts[numContexts].minorVersion = 0;
+ numContexts++;
+ }
+
+ contexts[numContexts].profileMask = SDL_GL_CONTEXT_PROFILE_CORE;
+ contexts[numContexts].majorVersion = 3;
+ contexts[numContexts].minorVersion = 2;
+ numContexts++;
+
+ contexts[numContexts].profileMask = 0;
+ contexts[numContexts].majorVersion = 2;
+ contexts[numContexts].minorVersion = 0;
+ numContexts++;
+
+ if ( !preferOpenGLES ) {
+#ifdef __EMSCRIPTEN__
+ contexts[numContexts].profileMask = SDL_GL_CONTEXT_PROFILE_ES;
+ contexts[numContexts].majorVersion = 3;
+ contexts[numContexts].minorVersion = 0;
+ numContexts++;
+#endif
+
+ contexts[numContexts].profileMask = SDL_GL_CONTEXT_PROFILE_ES;
+ contexts[numContexts].majorVersion = 2;
+ contexts[numContexts].minorVersion = 0;
+ numContexts++;
+ }
+ } else {
+ contexts[numContexts].profileMask = 0;
+ contexts[numContexts].majorVersion = 1;
+ contexts[numContexts].minorVersion = 1;
+ numContexts++;
+ }
+
for (i = 0; i < 16; i++)
{
int testColorBits, testDepthBits, testStencilBits;
@@ -631,82 +720,68 @@ static int GLimp_SetMode(int mode, qboolean fullscreen, qboolean noborder, qbool
SDL_SetWindowIcon( SDL_window, icon );
- if (!fixedFunction)
- {
- int profileMask, majorVersion, minorVersion;
- SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &profileMask);
- SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &majorVersion);
- SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &minorVersion);
-
- ri.Printf(PRINT_ALL, "Trying to get an OpenGL 3.2 core context\n");
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
- if ((SDL_glContext = SDL_GL_CreateContext(SDL_window)) == NULL)
- {
- ri.Printf(PRINT_ALL, "SDL_GL_CreateContext failed: %s\n", SDL_GetError());
- ri.Printf(PRINT_ALL, "Reverting to default context\n");
+ for ( type = 0; type < numContexts; type++ ) {
+ char contextName[32];
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profileMask);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, majorVersion);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minorVersion);
+ switch ( contexts[type].profileMask ) {
+ default:
+ case 0:
+ Com_sprintf( contextName, sizeof( contextName ), "OpenGL %d.%d",
+ contexts[type].majorVersion, contexts[type].minorVersion );
+ break;
+ case SDL_GL_CONTEXT_PROFILE_CORE:
+ Com_sprintf( contextName, sizeof( contextName ), "OpenGL %d.%d Core",
+ contexts[type].majorVersion, contexts[type].minorVersion );
+ break;
+ case SDL_GL_CONTEXT_PROFILE_ES:
+ Com_sprintf( contextName, sizeof( contextName ), "OpenGL ES %d.%d",
+ contexts[type].majorVersion, contexts[type].minorVersion );
+ break;
}
- else
- {
- const char *renderer;
-
- ri.Printf(PRINT_ALL, "SDL_GL_CreateContext succeeded.\n");
-
- if ( GLimp_GetProcAddresses( fixedFunction ) )
- {
- renderer = (const char *)qglGetString(GL_RENDERER);
- }
- else
- {
- ri.Printf( PRINT_ALL, "GLimp_GetProcAddresses() failed for OpenGL 3.2 core context\n" );
- renderer = NULL;
- }
-
- if (!renderer || (strstr(renderer, "Software Renderer") || strstr(renderer, "Software Rasterizer")))
- {
- if ( renderer )
- ri.Printf(PRINT_ALL, "GL_RENDERER is %s, rejecting context\n", renderer);
- GLimp_ClearProcAddresses();
- SDL_GL_DeleteContext(SDL_glContext);
- SDL_glContext = NULL;
+ SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, contexts[type].profileMask );
+ SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, contexts[type].majorVersion );
+ SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, contexts[type].minorVersion );
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profileMask);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, majorVersion);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minorVersion);
- }
- }
- }
- else
- {
- SDL_glContext = NULL;
- }
-
- if ( !SDL_glContext )
- {
- if( ( SDL_glContext = SDL_GL_CreateContext( SDL_window ) ) == NULL )
+ SDL_glContext = SDL_GL_CreateContext( SDL_window );
+ if ( !SDL_glContext )
{
- ri.Printf( PRINT_DEVELOPER, "SDL_GL_CreateContext failed: %s\n", SDL_GetError( ) );
- SDL_DestroyWindow( SDL_window );
- SDL_window = NULL;
+ ri.Printf( PRINT_ALL, "SDL_GL_CreateContext() for %s context failed: %s\n", contextName, SDL_GetError() );
continue;
}
if ( !GLimp_GetProcAddresses( fixedFunction ) )
{
- ri.Printf( PRINT_ALL, "GLimp_GetProcAddresses() failed\n" );
+ ri.Printf( PRINT_ALL, "GLimp_GetProcAddresses() for %s context failed\n", contextName );
GLimp_ClearProcAddresses();
SDL_GL_DeleteContext( SDL_glContext );
SDL_glContext = NULL;
- SDL_DestroyWindow( SDL_window );
- SDL_window = NULL;
continue;
}
+
+ if ( contexts[type].profileMask == SDL_GL_CONTEXT_PROFILE_CORE ) {
+ const char *renderer;
+
+ renderer = (const char *)qglGetString( GL_RENDERER );
+
+ if ( !renderer || strstr( renderer, "Software Renderer" ) || strstr( renderer, "Software Rasterizer" ) )
+ {
+ ri.Printf( PRINT_ALL, "GL_RENDERER is %s, rejecting %s context\n", renderer, contextName );
+
+ GLimp_ClearProcAddresses();
+ SDL_GL_DeleteContext( SDL_glContext );
+ SDL_glContext = NULL;
+ continue;
+ }
+ }
+
+ break;
+ }
+
+ if ( !SDL_glContext ) {
+ SDL_DestroyWindow( SDL_window );
+ SDL_window = NULL;
+ continue;
}
qglClearColor( 0, 0, 0, 1 );
@@ -815,7 +890,7 @@ static void GLimp_InitExtensions( qboolean fixedFunction )
glConfig.textureCompression = TC_NONE;
// GL_EXT_texture_compression_s3tc
- if ( SDL_GL_ExtensionSupported( "GL_ARB_texture_compression" ) &&
+ if ( ( QGLES_VERSION_ATLEAST( 2, 0 ) || SDL_GL_ExtensionSupported( "GL_ARB_texture_compression" ) ) &&
SDL_GL_ExtensionSupported( "GL_EXT_texture_compression_s3tc" ) )
{
if ( r_ext_compressed_textures->value )
@@ -996,6 +1071,7 @@ void GLimp_Init( qboolean fixedFunction )
r_sdlDriver = ri.Cvar_Get( "r_sdlDriver", "", CVAR_ROM );
r_allowResize = ri.Cvar_Get( "r_allowResize", "0", CVAR_ARCHIVE | CVAR_LATCH );
r_centerWindow = ri.Cvar_Get( "r_centerWindow", "0", CVAR_ARCHIVE | CVAR_LATCH );
+ r_preferOpenGLES = ri.Cvar_Get( "r_preferOpenGLES", "-1", CVAR_ARCHIVE | CVAR_LATCH );
if( ri.Cvar_VariableIntegerValue( "com_abnormalExit" ) )
{
diff --git a/code/sys/sys_main.c b/code/sys/sys_main.c
index 0113920b61..d734180617 100644
--- a/code/sys/sys_main.c
+++ b/code/sys/sys_main.c
@@ -31,6 +31,10 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#include
#include
+#ifdef __EMSCRIPTEN__
+#include
+#endif
+
#ifndef DEDICATED
#ifdef USE_LOCAL_HEADERS
# include "SDL.h"
@@ -863,10 +867,14 @@ int main( int argc, char **argv )
signal( SIGTERM, Sys_SigHandler );
signal( SIGINT, Sys_SigHandler );
+#ifdef __EMSCRIPTEN__
+ emscripten_set_main_loop( Com_Frame, 0, 1 );
+#else
while( 1 )
{
Com_Frame( );
}
+#endif
return 0;
}
diff --git a/code/sys/sys_unix.c b/code/sys/sys_unix.c
index 66b6fa17fa..db29e8f0b9 100644
--- a/code/sys/sys_unix.c
+++ b/code/sys/sys_unix.c
@@ -38,6 +38,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#include
#include
#include
+#include
qboolean stdinIsATTY;
@@ -548,11 +549,15 @@ void Sys_Sleep( int msec )
}
else
{
+ struct timespec req;
+
// With nothing to select() on, we can't wait indefinitely
if( msec < 0 )
msec = 10;
- usleep( msec * 1000 );
+ req.tv_sec = msec/1000;
+ req.tv_nsec = (msec%1000)*1000000;
+ nanosleep(&req, NULL);
}
}
diff --git a/code/sys/sys_win32.c b/code/sys/sys_win32.c
index d5e1c637b5..7d806a1674 100644
--- a/code/sys/sys_win32.c
+++ b/code/sys/sys_win32.c
@@ -20,6 +20,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
+// Use EnumProcesses() with Windows XP compatibility
+#define PSAPI_VERSION 1
+
#include "../qcommon/q_shared.h"
#include "../qcommon/qcommon.h"
#include "sys_local.h"
diff --git a/code/web/client-config.json b/code/web/client-config.json
new file mode 100644
index 0000000000..eddf35dbe9
--- /dev/null
+++ b/code/web/client-config.json
@@ -0,0 +1,47 @@
+{
+ "baseq3": {
+ "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"},
+ {"src": "baseq3/vm/cgame.qvm", "dst": "/baseq3/vm"},
+ {"src": "baseq3/vm/qagame.qvm", "dst": "/baseq3/vm"},
+ {"src": "baseq3/vm/ui.qvm", "dst": "/baseq3/vm"}
+ ]
+ },
+ "missionpack": {
+ "files": [
+ {"src": "missionpack/pak0.pk3", "dst": "/missionpack"},
+ {"src": "missionpack/pak1.pk3", "dst": "/missionpack"},
+ {"src": "missionpack/pak2.pk3", "dst": "/missionpack"},
+ {"src": "missionpack/pak3.pk3", "dst": "/missionpack"},
+ {"src": "missionpack/vm/cgame.qvm", "dst": "/missionpack/vm"},
+ {"src": "missionpack/vm/qagame.qvm", "dst": "/missionpack/vm"},
+ {"src": "missionpack/vm/ui.qvm", "dst": "/missionpack/vm"}
+ ]
+ },
+ "demoq3": {
+ "_comment": "Copy baseq3/vm/*.qvm to demoq3/vm/ as the Quake 3 demo QVMs are not compatible. However the botfiles are not fully compatible with newer QVMs.",
+ "files": [
+ {"src": "demoq3/pak0.pk3", "dst": "/demoq3"},
+ {"src": "demoq3/vm/cgame.qvm", "dst": "/demoq3/vm"},
+ {"src": "demoq3/vm/qagame.qvm", "dst": "/demoq3/vm"},
+ {"src": "demoq3/vm/ui.qvm", "dst": "/demoq3/vm"}
+ ]
+ },
+ "tademo": {
+ "_comment": "Copy missionpack/vm/*.qvm to tademo/vm/ as the Team Arena demo QVMs are not compatible.",
+ "files": [
+ {"src": "tademo/pak0.pk3", "dst": "/tademo"},
+ {"src": "tademo/vm/cgame.qvm", "dst": "/tademo/vm"},
+ {"src": "tademo/vm/qagame.qvm", "dst": "/tademo/vm"},
+ {"src": "tademo/vm/ui.qvm", "dst": "/tademo/vm"}
+ ]
+ }
+}
diff --git a/code/web/client.html b/code/web/client.html
new file mode 100644
index 0000000000..3a23ab92f3
--- /dev/null
+++ b/code/web/client.html
@@ -0,0 +1,116 @@
+
+__CLIENTBIN__ Emscripten demo
+
+
+
+
+
diff --git a/opengl2-readme.md b/opengl2-readme.md
index ee85c02c0f..c798d89c6f 100644
--- a/opengl2-readme.md
+++ b/opengl2-readme.md
@@ -63,6 +63,14 @@ For Win32:
CVARS
-------------------------------------------------------------------------------
+Cvars for API:
+
+* `r_preferOpenGLES` - This sets the preference for using OpenGL or OpenGL ES 2.
+ Many features are not supported when using OpenGL ES such as sun shadows and HDR.
+ 1 - Prefer OpenGL ES 2+.
+ 0 - Prefer desktop OpenGL.
+ -1 - Automatically pick (default).
+
Cvars for simple rendering features:
* `r_ext_compressed_textures` - Automatically compress textures.