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.