├── .gitignore ├── dlsym_wrapper.h ├── script.gnuplot ├── dlsym_wrapper.c ├── setup_env ├── Makefile ├── setup_env_debug ├── LICENSE ├── setup_env_dxx ├── setup_env_overload ├── README.md └── glx_hook.c /.gitignore: -------------------------------------------------------------------------------- 1 | glx_hook.so 2 | glx_hook_bare.so 3 | dlsym_wrapper.so 4 | *~ 5 | *.swp 6 | 7 | -------------------------------------------------------------------------------- /dlsym_wrapper.h: -------------------------------------------------------------------------------- 1 | #ifndef DLSYM_WRAPPER_H 2 | #define DLSYM_WRAPPER_H 3 | 4 | #define DLSYM_WRAPPER_ENVNAME "DLSYM_WRAPPER_ORIG_FPTR" 5 | #define DLSYM_WRAPPER_NAME "dlsym_wrapper.so" 6 | typedef void* (*DLSYM_PROC_T)(void*, const char*); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /script.gnuplot: -------------------------------------------------------------------------------- 1 | # run with gnuplot -e filename=\'glx_hook_frametimes-ctx1.csv\' script.gnuplot 2 | 3 | set yrange [0:50] 4 | set ylabel 'time [milliseconds]' 5 | set xlabel 'frame number' 6 | 7 | set style line 1 lt 1 lw 1 lc rgb "#109010" 8 | set style line 2 lt 1 lw 1 lc rgb "black" 9 | set style line 3 lt 1 lw 3 lc rgb "red" 10 | set style line 4 lt 1 lw 1 lc rgb "blue" 11 | 12 | set terminal png size 1600,1000 13 | outfile=sprintf("%s.png",filename) 14 | set output outfile 15 | 16 | plot \ 17 | filename using 1:($2/1000000) w l ls 1 title 'draw (CPU)', \ 18 | filename using 1:($3/1000000) w l ls 2 title 'draw (GPU)', \ 19 | filename using 1:($6/1000000) w l ls 3 title 'whole frame (GPU)', \ 20 | filename using 1:($7/1000000) w l ls 4 title 'latency' 21 | -------------------------------------------------------------------------------- /dlsym_wrapper.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "dlsym_wrapper.h" 6 | 7 | __attribute__((constructor)) 8 | static void dlsym_wrapper_init() 9 | { 10 | if (getenv(DLSYM_WRAPPER_ENVNAME) == NULL) { 11 | /* big enough to hold our pointer as hex string, plus a NUL-terminator */ 12 | char buf[sizeof(DLSYM_PROC_T)*2 + 3]; 13 | DLSYM_PROC_T dlsym_ptr=dlsym; 14 | if (snprintf(buf, sizeof(buf), "%p", dlsym_ptr) < (int)sizeof(buf)) { 15 | buf[sizeof(buf)-1] = 0; 16 | if (setenv(DLSYM_WRAPPER_ENVNAME, buf, 1)) { 17 | fprintf(stderr,"dlsym_wrapper: failed to set '%s' to '%s'\n", DLSYM_WRAPPER_ENVNAME, buf); 18 | } 19 | } else { 20 | fprintf(stderr,"dlsym_wrapper: failed to encode 0x%p in buffer\n", dlsym_ptr); 21 | } 22 | } else { 23 | fprintf(stderr,"dlsym_wrapper: '%s' already set\n", DLSYM_WRAPPER_ENVNAME); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /setup_env: -------------------------------------------------------------------------------- 1 | # NOTE: this MUST be sourced in the current shell to have any effect. 2 | # The problem here is: we cannot determine the path of the script by 3 | # itself, since in the sourced case, $0 is the path to the shell the 4 | # user is running. So the user must specify the path for us. As a 5 | # simplification when no parameter is given, the current working dir 6 | # is used. 7 | 8 | DIR="`pwd`" 9 | 10 | while [[ "x$1x" != "xx" ]]; do 11 | DIR="$1" 12 | shift 13 | done 14 | 15 | 16 | # check if this directory seems right: it should contain the glx_hook.so 17 | 18 | if [ -r "$DIR"/glx_hook.so ]; then 19 | echo using "$DIR" 20 | export LD_PRELOAD="$DIR/glx_hook.so":"$LD_PRELOAD" 21 | # settings to use 22 | export GH_SWAP_MODE=force=1 23 | export GH_LATENCY=1 24 | else 25 | echo "$DIR" seems not to be the right place 26 | echo 'please use: source $PATH_GLX_HOOK/setup_env $PATH_TO_GLX_HOOK' 27 | echo ' or: cd $PATH_TO_GLX_HOOK && source ./setup_env' 28 | fi 29 | 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CPPFLAGS += -Wall -Wextra 2 | 3 | ifeq ($(DEBUG), 1) 4 | CFLAGS += -g 5 | CPPFLAGS += -Werror 6 | else 7 | CFLAGS += -O2 8 | CPPFLAGS += -DNDEBUG 9 | endif 10 | 11 | ifeq ($(METHOD),) 12 | METHOD = 2 13 | endif 14 | CPPFLAGS += -D GH_DLSYM_METHOD=$(METHOD) 15 | 16 | STDDEFINES=-DGH_CONTEXT_TRACKING -DGH_SWAPBUFFERS_INTERCEPT 17 | BAREDEFINES= 18 | 19 | BASEFILES=glx_hook.so glx_hook_bare.so 20 | 21 | .PHONY: all 22 | ifeq ($(METHOD),3) 23 | all: $(BASEFILES) dlsym_wrapper.so 24 | else 25 | all: $(BASEFILES) 26 | endif 27 | 28 | glx_hook.so: glx_hook.c dlsym_wrapper.h Makefile 29 | $(CC) -shared -fPIC -Bsymbolic -pthread -o $@ $< $(CPPFLAGS) $(STDDEFINES) $(CFLAGS) $(LDFLAGS) -lrt 30 | glx_hook_bare.so: glx_hook.c dlsym_wrapper.h Makefile 31 | $(CC) -shared -fPIC -Bsymbolic -pthread -o $@ $< $(CPPFLAGS) $(BAREDEFINES) $(CFLAGS) $(LDFLAGS) 32 | dlsym_wrapper.so: dlsym_wrapper.c dlsym_wrapper.h Makefile 33 | $(CC) -shared -fPIC -Bsymbolic -o $@ $< $(CPPFLAGS) $(BAREDEFINES) $(CFLAGS) $(LDFLAGS) -ldl 34 | 35 | .PHONY: clean 36 | clean: 37 | -rm $(BASEFILES) dlsym_wrapper.so 38 | 39 | -------------------------------------------------------------------------------- /setup_env_debug: -------------------------------------------------------------------------------- 1 | # NOTE: this MUST be sourced in the current shell to have any effect. 2 | # The problem here is: we cannot determine the path of the script by 3 | # itself, since in the sourced case, $0 is the path to the shell the 4 | # user is running. So the user must specify the path for us. As a 5 | # simplification when no parameter is given, the current working dir 6 | # is used. 7 | 8 | DIR="`pwd`" 9 | 10 | while [[ "x$1x" != "xx" ]]; do 11 | DIR="$1" 12 | shift 13 | done 14 | 15 | 16 | # check if this directory seems right: it should contain the glx_hook.so 17 | 18 | if [ -r "$DIR"/glx_hook.so ]; then 19 | echo using "$DIR" 20 | export LD_PRELOAD="$DIR/glx_hook.so":"$LD_PRELOAD" 21 | # settings to use 22 | export GH_VERBOSE=5 23 | export GH_VERBOSE_FILE=/tmp/glxhook-%p-%c-%t.log 24 | export GH_FORCE_MIN_GL_VERSION_MAJOR=3 25 | export GH_FORCE_MIN_GL_VERSION_MINOR=2 26 | export GH_FORCE_GL_CONTEXT_PROFILE_COMPAT=2 27 | export GH_FORCE_GL_CONTEXT_FLAGS_DEBUG=1 28 | export GH_GL_DEBUG_OUTPUT=1 29 | export GH_GL_INJECT_DEBUG_OUTPUT=1 30 | else 31 | echo "$DIR" seems not to be the right place 32 | echo 'please use: source $PATH_GLX_HOOK/setup_env $PATH_TO_GLX_HOOK' 33 | echo ' or: cd $PATH_TO_GLX_HOOK && source ./setup_env' 34 | fi 35 | 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /setup_env_dxx: -------------------------------------------------------------------------------- 1 | # NOTE: this MUST be sourced in the current shell to have any effect. 2 | # The problem here is: we cannot determine the path of the script by 3 | # itself, since in the sourced case, $0 is the path to the shell the 4 | # user is running. So the user must specify the path for us. As a 5 | # simplification when no parameter is given, the current working dir 6 | # is used. 7 | 8 | DIR="`pwd`" 9 | 10 | while [[ "x$1x" != "xx" ]]; do 11 | DIR="$1" 12 | shift 13 | done 14 | 15 | 16 | # check if this directory seems right: it should contain the glx_hook.so 17 | 18 | if [ -r "$DIR"/glx_hook.so ]; then 19 | echo using "$DIR" 20 | export LD_PRELOAD="$DIR/glx_hook.so":"$LD_PRELOAD" 21 | # settings to use 22 | # do not force vsync, as it is enabled in the game, and not forcing it "on" everywhere speeds up initial loading screen a bit 23 | export GH_SWAP_MODE=max=1 24 | export GH_LATENCY=1 25 | #experimental stuff 26 | export GH_LATENCY_GL_WAIT_TIMEOUT_USECS=8000 # not really used, since MANUAL_WAIT=1 27 | #almost busy-waiting 28 | export GH_LATENCY_GL_WAIT_USECS=100 29 | export GH_LATENCY_WAIT_USECS=0 30 | export GH_LATENCY_MANUAL_WAIT=1 31 | # request GL 4.6 to get the newest wait behavior 32 | #export GH_FORCE_MIN_GL_VERSION_MAJOR=4 33 | #export GH_FORCE_MIN_GL_VERSION_MINOR=6 34 | #export GH_VERBOSE=6 35 | #export GH_VERBOSE_FILE=/tmp/ghxxx 36 | #export GH_SWAP_SLEEP_USECS=30000 37 | else 38 | echo "$DIR" seems not to be the right place 39 | echo 'please use: source $PATH_GLX_HOOK/setup_env $PATH_TO_GLX_HOOK' 40 | echo ' or: cd $PATH_TO_GLX_HOOK && source ./setup_env' 41 | fi 42 | 43 | -------------------------------------------------------------------------------- /setup_env_overload: -------------------------------------------------------------------------------- 1 | # NOTE: this MUST be sourced in the current shell to have any effect. 2 | # The problem here is: we cannot determine the path of the script by 3 | # itself, since in the sourced case, $0 is the path to the shell the 4 | # user is running. So the user must specify the path for us. As a 5 | # simplification when no parameter is given, the current working dir 6 | # is used. 7 | 8 | DIR="`pwd`" 9 | 10 | while [[ "x$1x" != "xx" ]]; do 11 | DIR="$1" 12 | shift 13 | done 14 | 15 | 16 | # check if this directory seems right: it should contain the glx_hook.so 17 | 18 | if [ -r "$DIR"/glx_hook.so ]; then 19 | echo using "$DIR" 20 | export LD_PRELOAD="$DIR/glx_hook.so":"$LD_PRELOAD" 21 | # settings to use 22 | # do not force vsync, as it is enabled in the game, and not forcing it "on" everywhere speeds up initial loading screen a bit 23 | export GH_SWAP_MODE=max=1 24 | export GH_SWAP_TEAR=enable 25 | export GH_LATENCY=1 26 | #experimental stuff 27 | export GH_LATENCY_GL_WAIT_TIMEOUT_USECS=8000 # not really used, since MANUAL_WAIT=1 28 | #almost busy-waiting 29 | export GH_LATENCY_GL_WAIT_USECS=100 30 | export GH_LATENCY_WAIT_USECS=0 31 | export GH_LATENCY_MANUAL_WAIT=1 32 | # request GL 4.6 to get the newest wait behavior 33 | export GH_FORCE_MIN_GL_VERSION_MAJOR=4 34 | export GH_FORCE_MIN_GL_VERSION_MINOR=6 35 | export GH_FORCE_GL_CONTEXT_FLAGS_NO_ERROR=1 # request a no-error context 36 | #export GH_VERBOSE=6 37 | #export GH_VERBOSE_FILE=/tmp/ghxxx 38 | #export GH_SWAP_SLEEP_USECS=30000 39 | else 40 | echo "$DIR" seems not to be the right place 41 | echo 'please use: source $PATH_GLX_HOOK/setup_env $PATH_TO_GLX_HOOK' 42 | echo ' or: cd $PATH_TO_GLX_HOOK && source ./setup_env' 43 | fi 44 | 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | glx_hook 2 | ======== 3 | 4 | Simple tool for Linux/glibc hooking into OpenGL functions. 5 | 6 | The main motivation for this was intercepting application calls to 7 | `glXSwapInterval[EXT|SGI|MESA]` to override VSync settings if your GPU driver 8 | doesn't allow you to override this. 9 | With the proprietary NVIDIA Linux driver, the nvidia-settings and 10 | `__GL_SYNC_TO_VBLANK` environment variable are actually overriden by an 11 | application using 12 | [`GLX_EXT_swap_control`](https://registry.khronos.org/OpenGL/extensions/EXT/EXT_swap_control.txt), 13 | [`GLX_SGI_swap_control`](https://registry.khronos.org/OpenGL/extensions/SGI/GLX_SGI_swap_control.txt) 14 | or `GLX_MESA_swap_control` extensions. 15 | 16 | This tool works by exchanging the 17 | value (or silently ignoring the calls altoghether, so that you driver 18 | settings become effective). To do so, it is using the (in)famous `LD_PRELOAD` 19 | approach. 20 | 21 | There are also some more advanced features, notably a latency limiter and 22 | a frametime measurement mode, see the section [Experimental Features](#experimental-features) below. 23 | 24 | ### USAGE: 25 | 26 | $ LD_PRELOAD=path/to/glx_hook.so GH_SWAP_MODE=$mode target_binary 27 | 28 | or 29 | 30 | $ LD_PRELOAD=path/to/glx_hook_bare.so GH_SWAP_MODE=$mode target_binary 31 | 32 | where `$mode` controls how the values are exchanged. Valid modes are 33 | * `nop`: keep calls as intended by the application 34 | * `ignore`: silently ignore the calls (return success to the application) 35 | * `clamp=$x,$y`: clamp requested swap interval to [`$x`, `$y`] 36 | * `force=$x`: always set swap interval `$x` 37 | * `disable`: same as `force=0` 38 | * `enable`: same as `min=1` 39 | * `min=$x`: set interval to at least `$x` 40 | * `max=$x`: set interval to at most `$x` 41 | 42 | **NOTE**: This option only changes values forwarded to the swap interval 43 | functions, or ignores these calls completely, but never adds new calls 44 | to set the swap interval. If the app doesn't do it, this option does nothing. 45 | For actually injection such calls, have a look at the experimental option 46 | [`GH_INJECT_SWAPINTERVAL`](#swap-interval-injection) below. 47 | 48 | NVidia is promoting a feature called "adaptive vsync" where a "late" buffer 49 | swap is done immediately instead of being delayed to the next sync interval. 50 | This feature is exposed via the 51 | [`GLX_EXT_swap_control_tear`](https://registry.khronos.org/OpenGL/extensions/EXT/GLX_EXT_swap_control_tear.txt) 52 | extension. If this 53 | is present, negative intervals enable adaptive vsync with the absolute 54 | value beeing the swap interval. The `GH_SWAP_TEAR` environment variable can 55 | be used to control this feature: 56 | 57 | * `raw`: do not treat positive and negative intervals as different. This has 58 | the effect that you for example could do a `clamp=-1,1` 59 | * `keep`: keep the adaptive vsync setting, modify only the absoulte value 60 | * `disable`: always disable adaptive vsync 61 | * `enable`: always enable adaptive vsync 62 | * `invert`: enable adaptive vsync if the app disables it and vice versa (whatever this might be useful for...) 63 | 64 | **NOTE**: we do not check for the presence of this extension. Negative swap 65 | intervals are invalid if the extension is not present. So if you enable 66 | adaptive vsync without your driver supporting it, the calls will fail. Most 67 | likely, the application won't care and no swap interval will be set. 68 | 69 | Further environment variables controlling the behavior: 70 | * `GH_VERBOSE=$level`: control level of verbosity (0 to 5) 71 | * `GH_VERBOSE_FILE=$file`: redirect verbose output to `$file` (default is to use 72 | standard error stream), see section [File Names](#file-names) 73 | for details about how the file name is parsed 74 | 75 | The `glx_hook.so` version is the full version which tracks GL contexts, and 76 | allows also for `glXSwapBuffers` manipulations (see below). However, the GL 77 | context tracking adds a whole layer of complexity and might fail in some 78 | scenarios. If you are only interested in the swap interval manipulations, 79 | you can try to use the `glx_hook_bare.so` library, which only tries to deal 80 | with the bare minimum of `glX` (and `dlsym`) functions. 81 | 82 | #### Extra Options 83 | 84 | If a GL symbol cannot be resolved, glx_hook tries to manually load the 85 | OpenGL library via `dlopen(3)`. This behavior can be controlled by the 86 | `GH_LIBGL_FILE` environment variable: If it is not set, `libGL.so` is 87 | used, but you might want to specify another one, potentially with 88 | full path. If you set `GH_LIBGL_FILE=""`, libGL loading is disabled. 89 | 90 | ### EXPERIMENTAL FEATURES 91 | 92 | The following features are only available in `glx_hook.so` (and not `glx_hook_bare.so`): 93 | 94 | #### Swap Interval Injection 95 | 96 | Set `GH_INJECT_SWAPINTERVAL=$n` to inject a `SwapInterval` call when a context 97 | is made current for the first time. By default, this is disabled. The `GH_SWAP_MODE` 98 | setting does not affect the operation of this option. This option is most 99 | useful if the application never sets a swap interval, but it might be combined 100 | with the other `GH_SWAP_MODE` settings, i.e. `GH_SWAP_MODE=ignore` to prevent 101 | the app from changing th injected setting later on. 102 | 103 | #### Frame timing measurement / benchmarking 104 | 105 | Set `GH_FRAMETIME=$mode` to aquire frame timings.The following modes are 106 | supported: 107 | * `0`: no frametime measurements (the default) 108 | * `1`: measure frametime on CPU only 109 | * `2`: measure frametimes on CPU and GPU (requires a context >= 3.3, or supporting the 110 | [`GL_ARB_timer_query`](https://registry.khronos.org/OpenGL/extensions/ARB/ARB_timer_query.txt) 111 | extension) 112 | 113 | Use `GH_FRAMETIME_DELAY=$n` to set the delay for the timer queries (default: 10 frames). 114 | This controls the number of frames the GPU might lag behind of the CPU. Setting a 115 | too low number may result in performance degradation in comparison to not measuring 116 | the frametimes. The implicit synchronizations can have a similar effect as the 117 | [`GH_LATENCY`](#latency-limiter) setting, albeit only as an unintented side-effect, 118 | and might completely invalidate the measurements. Just leave this value 119 | at the default unless you know exactly what you are doing... 120 | 121 | Use `GH_FRAMETIME_FRAMES=$n` to control the number of frames which are buffered 122 | internally (default: 1000 frames). The results will be dumped to disk if the buffer is full. Setting 123 | a too low value here might result in performance degradation due to the output. 124 | 125 | Use `GH_FRAMETIME_FILE=$name` to control the output file name (default: 126 | `glx_hook_frametimes-ctx%c.csv`). See section [File Names](#file-names) 127 | for details about how the file name is parsed. 128 | The output will be one line per frame, 129 | with the following values: 130 | 131 | frame_number CPU GPU latency CPU GPU latency 132 | 133 | where `CPU` denotes timestamps on the CPU, `GPU` denotes timestamps on the GPU 134 | and `latency` denotes the latency of the GPU. All values are in nanoseconds. 135 | The first three values refer to the time directly before the buffer swap, 136 | the latter to directly after the swap. The `CPU` and `GPU` values are always 137 | relative to the buffer swap of the _previous_ frame, and `latency` is just 138 | the observed latency at the respective timing probe. The data for frame 0 might 139 | be useless. 140 | 141 | Included is an example script for [gnuplot](http://www.gnuplot.info), 142 | [`script.gnuplot`](https://raw.githubusercontent.com/derhass/glx_hook/master/script.gnuplot), 143 | to easily create some simple frame timing graphs. You can use it directly 144 | on any frametime file by specifying the `filename` variable on the gnuplot command line: 145 | 146 | gnuplot -e filename=\'glx_hook_frametimes-ctx1.csv\' script.gnuplot 147 | 148 | #### Latency Limiter 149 | 150 | Use `GH_LATENCY=$n` to limit the number of frames the GPU lags behind. The following 151 | values might be used: 152 | * `-2`: no limit (the default) 153 | * `-1`: limit to 0, force a sync right _after_ the buffer swap 154 | * `0`: limit to 0, force a sync right _before_ the buffer swap 155 | * `>0`: limit the number of pending frames to `$n` (requires a context >= 3.2, 156 | or supporting the 157 | [`GL_ARB_sync`](https://registry.khronos.org/OpenGL/extensions/ARB/ARB_sync.txt) 158 | extension) 159 | 160 | This can be helpful in situations where you experience stuttering in a GL application. Preferably, 161 | you should use `GH_LATENCY=1` to not degrade performance too much. 162 | 163 | Some GL drivers may use busy waiting when waiting for the sync objects, resulting 164 | in maxing out one CPU core for only very little of an advantage. However, sometimes you might 165 | even want to use busy waiting (even if the driver doesn't do it for you). Two different 166 | modes are implemented: 167 | * the _standard_ mode, which uses a single call to `glClientWaitSync`, 168 | which wait for either the completion of rendering of the relevant frame, or the reaching of 169 | a timeout, whatever comes first. The timeout can be specified by setting 170 | `GH_LATENCY_GL_WAIT_TIMEOUT_USECS=$n`, where `$n` is the timeout in microseconds. Default is 171 | `1000000` (1 second), but you can turn this down significantly if you don't wait for 172 | long periods even in extreme cases. Setting this too low might result in not actually 173 | full synchronization. 174 | * the _manual_ mode, where the wait is performed in a loop, until synchronization is 175 | achieved. Use `GH_LATENCY_GL_WAIT_USECS=$n` to set the wait timeout for each individual 176 | GL wait operation to `$n` microseconds (default: 0) and `GH_LATENCY_WAIT_USECS=$n` 177 | to add an additional _sleep_ cycle of `$n` microseconds per loop iteration (default: 0). 178 | 179 | The mode is selected by setting `GH_LATENCY_MANUAL_WAIT=$n`, where `$n` is 180 | * `-1`: automatic mode selecion (the default): enable manual mode if either of `GH_LATENCY_GL_WAIT_USECS` or `GH_LATENCY_WAIT_USECS` 181 | is set to a non-zero value 182 | * `0`: always use standard mode 183 | * `1`: always use manual mode (this allows explicit busy waiting by setting both wait usecs to 0) 184 | 185 | #### Buffer Swap Omission 186 | 187 | Set `GH_SWAPBUFFERS=$n` to only execute every `$n`-th buffer swap. This might be 188 | useful for games where all game logic is implemented in the same loop as 189 | the rendering, and you want vsync on but stilll a higher frequency for the loop. 190 | In this mode, you need to reach a frame rate of `$n` times the refresh 191 | to not miss any display frames. 192 | 193 | There is also an experimental - and very crude - adaptive mode which can be enabled by setting 194 | `GH_MIN_SWAP_USECS` to a value above zero. If enabled, the `GH_SWAPBUFFERS` setting is 195 | ignored, and the swapbuffer omission value is calculated based on the frame timeings 196 | of the previous frames. The idea is to set the value to the frametime of your monitor 197 | value's refresh rate, or somewhat lower, eg. something in the range of `14000` to `16600` 198 | for a 60Hz display. You can set `GH_SWAP_OMISSION_MEASURE` to either `1` to use CPU 199 | frame times, `2` to use GPU frame times, or `3` to use the maximum of both. 200 | The actual swap buffer omission value is clamped between `GH_SWAP_OMISSION_MIN' (default `1`, 201 | meaning no omission), and `GH_SWAP_OMISSION_MAX` (default `4`). 202 | The measurement is capturing the famre times over the last `GH_SWAP_OMISSION_MEASURE_TOT` fames 203 | (default: `6`, min: `2`, max: `16`) and using the average of the oldest `GH_SWAP_OMISSION_MEASURE_AVG` 204 | frames of these (default: `4`, min: `1`, max: `total frames - 1`). 205 | Note that this mode 206 | can be very unstable, depending on the app and also the GL driver. If might help to 207 | test it in combination with various latency limiter and swap omission flush modes, and also 208 | different threshold durations as well as measurement modes. 209 | 210 | The interaction between the latency limiter and swap buffer omission can be controlled 211 | by the `GH_SWAP_OMISSION_LATENCY` option as follows: 212 | * `0`: apply the latency limiter on every swapbuffer operation which is actually carried out (the default) 213 | * `1`: apply the letancy limiter to every swapbuffer operation the application attempts to do, including the omitted ones 214 | 215 | Furthermore, you can control the flush behavior at omitted swapbuffer operations via 216 | the `GH_SWAP_OMISSION_FLUSH` variable: 217 | * `0`: do nothing 218 | * `1`: do a flush via `glFlush` (the default) 219 | * `2`: do a full sync via `glFinish` 220 | When the latency limiter is enabled and `GH_SWAP_OMISSION_LATENCY` is set to `1`, 221 | you probably should set `GH_SWAP_OMISSION_FLUSH` to 0 to avoid additional syncs 222 | the latency limiter already cares about. 223 | 224 | Frametime measurements will always measure each individual frame the application 225 | attempted to render, and is not (directly) affected by the swap buffer omission 226 | settings. 227 | 228 | #### Sleep injection 229 | 230 | Set `GH_SWAP_SLEEP_USECS=$n` to force an addition sleep of that many microseconds 231 | after each buffer swap. This might be useful if you want to reduce the framerate or simulate 232 | a slower machine. 233 | 234 | #### GL Context attribute overrides 235 | 236 | You can override the attributes for GL context creation. This will require the 237 | [`GLX_ARB_create_context`](https://registry.khronos.org/OpenGL/extensions/ARB/GLX_ARB_create_context.txt) 238 | extension. The following overrides are defined: 239 | * `GH_FORCE_MIN_GL_VERSION_MAJOR`: set the the minimum GL major version number to request 240 | * `GH_FORCE_MIN_GL_VERSION_MINOR`: set the the minimum GL minor version number to request 241 | * `GH_FORCE_MAX_GL_VERSION_MAJOR`: set the the maximum GL major version number to request 242 | * `GH_FORCE_MAX_GL_VERSION_MINOR`: set the the maximum GL minor version number to request 243 | * `GH_FORCE_GL_VERSION_MAJOR`: set the the exact GL major version number to request 244 | * `GH_FORCE_GL_VERSION_MINOR`: set the the exact GL minor version number to request 245 | * `GH_FORCE_GL_CONTEXT_PROFILE_CORE`: set to non-zero to force the creation of a core profile. (Requires GL version of at least 3.2) 246 | * `GH_FORCE_GL_CONTEXT_PROFILE_COMPAT`: set to non-zero to force the creation of a compat profile. (Requires GL version of at least 3.2). Set to 1 to always force compat, and to 2 only if the app would be using legacy instead. 247 | * `GH_FORCE_GL_CONTEXT_FLAGS_NO_DEBUG`: set to non-zero to disable debug contexts. 248 | * `GH_FORCE_GL_CONTEXT_FLAGS_DEBUG`: set to non-zero to force debug contexts. `GH_FORCE_GL_CONTEXT_FLAGS_DEBUG` takes precedence over `GH_FORCE_GL_CONTEXT_FLAGS_NO_DEBUG`. 249 | * `GH_FORCE_GL_CONTEXT_FLAGS_NO_FORWARD_COMPAT`: set to non-zero to disable forwadr-compatible contexts. 250 | * `GH_FORCE_GL_CONTEXT_FLAGS_FORWARD_COMPAT`: set to non-zero to force forward-compatible contexts. `GH_FORCE_GL_CONTEXT_FLAGS_FORWARD_COMPAT` takes precedence over `GH_FORCE_GL_CONTEXT_FLAGS_NO_FORWARD_COMPAT`. 251 | * `GH_FORCE_GL_CONTEXT_FLAGS_NO_ERROR`: set to non-zero to force a no-error context (as defined in [`GL_KHR_NO_ERROR`](https://registry.khronos.org/OpenGL/extensions/KHR/KHR_no_error.txt)). 252 | * `GH_FORCE_GL_CONTEXT_FLAGS_ERROR`: set to non-zero to force removal of the no-error context flag if the application may request that. 253 | 254 | The GL version overrides are applied in the order `min,max,exact`. Set a component to `-1` for no override. 255 | Note: it is advised to set both the major and minor version, the version comparision will then take 256 | both major and minor into account (e.g. a minumium of 3.2 will not change a requested 4.0 to 4.2), 257 | but it is also possible to override only one component if you really want to (e.g. a minimum of -1.2 will 258 | change a requested 4.0 to 4.2). 259 | 260 | You can also directly specify the bitmasks for the context flags and profile mask (see the various `GLX` context creation extensions for the actual values): 261 | * `GH_FORCE_GL_CONTEXT_FLAGS_ON` manaully specify a the bits which must be forced on in the context flags bitmask. 262 | * `GH_FORCE_GL_CONTEXT_FLAGS_OFF` manaully specify a the bits which must be forced off in the context flags bitmask. 263 | * `GH_FORCE_GL_CONTEXT_PROFILE_MASK_ON` manaully specify a the bits which must be forced on in the context profile bitmask. 264 | * `GH_FORCE_GL_CONTEXT_PROFILE_MASK_OFF` manaully specify a the bits which must be forced off in the context profile bitmask. 265 | When setting these, they will override any settings by the other `GL_FORCE_GL_*` environment variables. 266 | 267 | Note that the context profile is only relevant for GL version 3.2 and up. When forcing a GL version 268 | of 3.2 or higher, the default profile is the core profile. You must explicitely request a compat profile 269 | if the application would otherwise work with a leagcy context (by not using `GLX_ARB_create_context` or 270 | specifying an earlier version), or use `GH_FORCE_GL_CONTEXT_PROFILE_COMPAT=2` to dynamically request 271 | compatibility profile only if legacy profiles were requested. 272 | 273 | #### GL Debug Output 274 | 275 | By setting `GH_GL_DEBUG_OUTPUT` to a non-zero value, [GL debug output](https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_debug_output.txt) message callbacks will be intercepted. The debug messages will be logged as `INFO` level messages in the GH log. Set `GH_GL_INJECT_DEBUG_OUTPUT` to a non-zero value to inject a call to the 276 | debug output functionality into the application. Note that to get debug output, you must 277 | force the creation of a debug GL context if the app does not do it on its own. 278 | 279 | See the example script [`setup_env_debug`](https://github.com/derhass/glx_hook/blob/master/setup_env_debug) 280 | for a setup which tries to inject Debug Output into an unspecified GL app. 281 | 282 | ### FILE NAMES 283 | 284 | Whenever an output file name is specified, special run-time information 285 | can be inserted in the file name to avoid overwriting previous files in 286 | complex situations (i.e. the application is using several processes). 287 | A sequence of `%` followed by another character is treated depending 288 | on the second character as follows: 289 | 290 | * `c`: the GL context number (sequetially counted from 0), (this is not 291 | available for the `GH_VERBOSE_FILE` output, context number is 292 | always 0 there) 293 | * `p`: the PID of the process 294 | * `t`: the current timestamp as `.` 295 | * `%`: the `%` sign itself 296 | 297 | ### INSTALLATION: 298 | 299 | To build, just type 300 | 301 | $ make 302 | 303 | (assuming you have a C compiler and the standard libs installed). 304 | Finally copy the `glx_hook.so` to where you like it. For a debug build, do 305 | 306 | $ make DEBUG=1 307 | 308 | glx_hook requires glibc, as we rely on some glibc internas. 309 | Tested with glibc-2.13 (from debian wheezy), glibc-2.24 310 | (from debian stretch) and glibc-2.28 (from debian buster). 311 | 312 | #### Hooking mechanism 313 | 314 | glx_hook works by exporting all the relevant GL functions in the shared object, 315 | as well as hooking into `dlsym()` (and optionally also `dlvsym()`) as well as 316 | `glXGetProcAddress`/`glXGetProcAddressARB`. 317 | 318 | Howver, hooking `dlsym()`/`dlvsym()` can be done via different methods, 319 | The method is selected at compile time via the `METHOD` variable: 320 | 321 | $ make METHOD=2 322 | 323 | The following methods are available: 324 | 325 | * `1`: Deprecated: Use the internal `_dl_sym()` function of glibc. However, this function 326 | is not exported any more since glibc-2.34, so this approach won't work with 327 | newer linux distros beginning some time around autumn of 2021. 328 | Using this method allows for hooking `dlsym()` and `dlvsym`. 329 | 330 | * `2`: Use the `dlvsym()` function which is an official part of the glibc API and ABI. 331 | To query the original `dlsym` via `dlvsym`, we need to know the exact version 332 | of the symbol, which in glibc is dependent on the platform. 333 | glx_hook currently supports the platforms x86_64 and i386 via this method, 334 | but other platforms can easyly be added. Just do a 335 | `grep 'GLIBC_.*\bdlsym\b' -r sysdeps` in the root folder of the glibc source. 336 | Using this methid allows for hooking `dlsym()`, but not `dlvsym`. 337 | This is currently the default. 338 | 339 | * `3`: Use a second helper library `dlsym_wrapper.so`. That file will be automatically 340 | built if this mode is selected. It must be placed in the same folder where 341 | the `glx_hook.so` is located, and will be dynamically loaded at runtime when 342 | `glx_hook.so` initializes itself. Using this method allows for hooking `dlsym()` and `dlvsym`. 343 | It is probably the most flexible approach, but it adds some complexity. 344 | 345 | When using the method 2, this means that we end up getting the symbol 346 | from `glibc` even if another hooking library is injected to the same process. 347 | By default, glx_hooks plays nice and actually uses the `dlsym()` queried by 348 | `dlvsym()` to again query for the unversioned `dlsym`. This behavior can 349 | be prevented by setting the `GH_ALLOW_DLSYM_REDIRECTION` environment variable 350 | to 0. It is only relevant for `METHOD=2`. 351 | 352 | You can control wether we shall also hook the `dlsym()` and `dlvsym()` methods 353 | dynamically, meaning an application calling (our) `dlsym()` to query for `"dlsym"` itself 354 | should be redirected to our implementation. Use `GH_HOOK_DLSYM_DYNAMICALLY=1` or 355 | `GH_HOOK_DLVSYM_DYNAMICALLY=1` to enable is. Bu default, this is disabled, as this 356 | creates lots of shenanigans, especially if we are not the only `dlsym`/`dlvsym` hook 357 | around. Use with care. 358 | 359 | ### EXAMPLES 360 | 361 | There are some example scripts to simplify the setup: 362 | * [`setup_env`](https://github.com/derhass/glx_hook/blob/master/setup_env) 363 | * [`setup_env_debug`](https://github.com/derhass/glx_hook/blob/master/setup_env_debug) 364 | 365 | Use either of these to set up your current shell's environment for the use of `glx_hook.so`: 366 | * `cd /path/to/your/glx_hook/installation; source ./setup_env` (assumes the `setup.env` and the `glx_hook.so` are in the same directory) 367 | * `source /path/to/your/glx_hook/scripts/setup_env /path/to/your/glx_hook/installation` (directories might differ) 368 | 369 | Have fun, 370 | derhass 371 | () 372 | 373 | -------------------------------------------------------------------------------- /glx_hook.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include /* for RTLD_NEXT */ 3 | #include /* for mutextes */ 4 | #include /* for usleep(3) */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #ifdef GH_CONTEXT_TRACKING 17 | #include /* for clock_gettime */ 18 | #include 19 | #endif 20 | 21 | #include "dlsym_wrapper.h" 22 | 23 | #ifdef __GLIBC__ 24 | #if (__GLIBC__ > 2) || ( (__GLIBC__ == 2 ) && (__GLIBC_MINOR__ >= 34)) 25 | /* glibc >= 2.34 does not export _dl_sym() any more, we MUST use the dlvsym approach */ 26 | #if (GH_DLSYM_METHOD == 1) 27 | #error GH_DLSYM_METHOD 1 is not available with this libc version 28 | #endif 29 | #endif 30 | #endif 31 | 32 | /* we use this value as swap interval to mark the situation that we should 33 | * not set the swap interval at all. Note that with 34 | * GLX_EXT_swap_control_tear, negative intervals are allowed, and the 35 | * absolute value specifies the real interval, so we use just INT_MIN to 36 | * avoid conflicts with values an application might set. */ 37 | #define GH_SWAP_DONT_SET INT_MIN 38 | 39 | /*************************************************************************** 40 | * GH_DLSYM_METHOD selection * 41 | ***************************************************************************/ 42 | 43 | /* supported methods: 44 | * 0: _dl_sym() 45 | * 1: dlvsym() 46 | * 2: dlsym_wrapper.so 47 | * See README.md for details 48 | */ 49 | #if (GH_DLSYM_METHOD == 1) /* METHOD 1*/ 50 | #define GH_DLSYM_NEED_LOCK /* METHOD 2*/ 51 | #elif (GH_DLSYM_METHOD == 2) 52 | #if defined(__x86_64__) /* platforms */ 53 | #define GH_DLSYM_ABI_VERSION "2.2.5" 54 | #elif defined(__i386__) 55 | #define GH_DLSYM_ABI_VERSION "2.0" 56 | #else 57 | /* if you need support for your platform, get the exact version for dlsym 58 | * for the glibc ABI of your platform */ 59 | #error platform not supported 60 | #endif /* platforms */ 61 | #elif (GH_DLSYM_METHOD == 3) /* METHOD 3*/ 62 | #define GH_DLSYM_NEED_LOCK 63 | #else 64 | #error GH_DLSYM_METHOD not supported 65 | #endif /* GH_DLSYM_METHOD */ 66 | 67 | /*************************************************************************** 68 | * helpers * 69 | ***************************************************************************/ 70 | 71 | static const char * 72 | get_envs(const char *name, const char *def) 73 | { 74 | const char *s=getenv(name); 75 | return (s)?s:def; 76 | } 77 | 78 | static int 79 | get_envi(const char *name, int def) 80 | { 81 | const char *s=getenv(name); 82 | int i; 83 | 84 | if (s) { 85 | i=(int)strtol(s,NULL,0); 86 | } else { 87 | i=def; 88 | } 89 | return i; 90 | } 91 | 92 | #ifdef GH_CONTEXT_TRACKING 93 | 94 | static unsigned int 95 | get_envui(const char *name, unsigned int def) 96 | { 97 | const char *s=getenv(name); 98 | int i; 99 | 100 | if (s) { 101 | i=(unsigned)strtoul(s,NULL,0); 102 | } else { 103 | i=def; 104 | } 105 | return i; 106 | } 107 | 108 | #endif 109 | 110 | static size_t 111 | buf_printf(char *buf, size_t pos, size_t size, const char *fmt, ...) 112 | { 113 | va_list args; 114 | size_t left=size-pos; 115 | int r; 116 | 117 | va_start(args, fmt); 118 | r=vsnprintf(buf+pos, left, fmt, args); 119 | va_end(args); 120 | 121 | if (r > 0) { 122 | size_t written=(size_t)r; 123 | pos += (written >= left)?left:written; 124 | } 125 | return pos; 126 | } 127 | 128 | static void 129 | parse_name(char *buf, size_t size, const char *name_template, unsigned int ctx_num) 130 | { 131 | struct timespec ts_now; 132 | int in_escape=0; 133 | size_t pos=0; 134 | char c; 135 | 136 | buf[--size]=0; /* resverve space for final NUL terminator */ 137 | while ( (pos < size) && (c=*(name_template++)) ) { 138 | if (in_escape) { 139 | switch(c) { 140 | case '%': 141 | buf[pos++]=c; 142 | break; 143 | case 'c': 144 | pos=buf_printf(buf,pos,size,"%u",ctx_num); 145 | break; 146 | case 'p': 147 | pos=buf_printf(buf,pos,size,"%u",(unsigned)getpid()); 148 | break; 149 | case 't': 150 | clock_gettime(CLOCK_REALTIME, &ts_now); 151 | pos=buf_printf(buf,pos,size,"%09lld.%ld", 152 | (long long)ts_now.tv_sec, 153 | ts_now.tv_nsec); 154 | break; 155 | default: 156 | pos=buf_printf(buf,pos,size,"%%%c",c); 157 | } 158 | in_escape=0; 159 | } else { 160 | switch(c) { 161 | case '%': 162 | in_escape=1; 163 | break; 164 | default: 165 | buf[pos++]=c; 166 | } 167 | } 168 | } 169 | buf[pos]=0; 170 | } 171 | 172 | /*************************************************************************** 173 | * MESSAGE OUTPUT * 174 | ***************************************************************************/ 175 | 176 | typedef enum { 177 | GH_MSG_NONE=0, 178 | GH_MSG_ERROR, 179 | GH_MSG_WARNING, 180 | GH_MSG_INFO, 181 | GH_MSG_DEBUG, 182 | GH_MSG_DEBUG_INTERCEPTION 183 | } GH_msglevel; 184 | 185 | #ifdef NDEBUG 186 | #define GH_MSG_LEVEL_DEFAULT GH_MSG_WARNING 187 | #else 188 | #define GH_MSG_LEVEL_DEFAULT GH_MSG_DEBUG_INTERCEPTION 189 | #endif 190 | 191 | #define GH_DEFAULT_OUTPUT_STREAM stderr 192 | 193 | static void GH_verbose(int level, const char *fmt, ...) 194 | { 195 | static int verbosity=-1; 196 | static FILE *output_stream=NULL; 197 | static int stream_initialized=0; 198 | va_list args; 199 | 200 | if (verbosity < 0) { 201 | verbosity=get_envi("GH_VERBOSE", GH_MSG_LEVEL_DEFAULT); 202 | } 203 | 204 | if (level > verbosity) { 205 | return; 206 | } 207 | 208 | if (!stream_initialized) { 209 | const char *file=getenv("GH_VERBOSE_FILE"); 210 | if (file) { 211 | char buf[PATH_MAX]; 212 | parse_name(buf, sizeof(buf), file, 0); 213 | output_stream=fopen(buf,"a+t"); 214 | } 215 | if (!output_stream) 216 | output_stream=GH_DEFAULT_OUTPUT_STREAM; 217 | stream_initialized=1; 218 | } 219 | fprintf(output_stream,"GH: "); 220 | va_start(args, fmt); 221 | vfprintf(output_stream, fmt, args); 222 | va_end(args); 223 | fflush(output_stream); 224 | } 225 | 226 | /*************************************************************************** 227 | * FUNCTION INTERCEPTOR LOGIC * 228 | ***************************************************************************/ 229 | 230 | typedef void (*GH_fptr)(); 231 | typedef void * (*GH_resolve_func)(const char *); 232 | 233 | #ifdef GH_DLSYM_NEED_LOCK 234 | /* mutex used during GH_dlsym_internal () */ 235 | static pthread_mutex_t GH_mutex=PTHREAD_MUTEX_INITIALIZER; 236 | #endif 237 | 238 | #if (GH_DLSYM_METHOD == 1) 239 | /* THIS IS AN EVIL HACK: we directly call _dl_sym() of the glibc 240 | * NOTE: the approrpiate function prototype could be 241 | * extern void *_dl_sym(void *, const char *, void (*)() ); 242 | * but we use it only with some specific argument for the 243 | * last parameter, and use the function pointer type 244 | * directly in this declaration, which avoids us to do a 245 | * cast between (technically incompatible) function pointer 246 | * types. Hiding the cast in the declatation is of course 247 | * as broken as before from a C correctness point of view, 248 | * but the compiler won't notice any more... ;) */ 249 | extern void *_dl_sym(void *, const char *, void* (*)(void *, const char *) ); 250 | #elif (GH_DLSYM_METHOD == 3) 251 | /* Use the dlsym_wrapper. We can use dlopen(), but we can not 252 | * use dlsym() or dlvsym(), as these functions are hooked by ourselves. 253 | * However, dlsym_wrapper will execute an intialization function when 254 | * dlopen() is called. The trick is the we load dlsym_wrapper.so 255 | * with RTLD_LOCAL | RTLD_DEEPBIND, so that it gets its own private 256 | * linker map to the libdl.so, without falling back on the dlsym() 257 | * defined here. It then forwards the pointer via an environment 258 | * variable (as hex via printf %p format string) */ 259 | static void *dlsym_wrapper_get(void* handle, const char *name) 260 | { 261 | char dlsym_wrapper_name[4096]; 262 | void *wrapper; 263 | const char * ptr_str; 264 | void *res = NULL; 265 | void *ptr = NULL; 266 | Dl_info self; 267 | size_t len; 268 | char *pos; 269 | size_t idx; 270 | size_t slen; 271 | 272 | (void)handle; 273 | (void)name; 274 | 275 | memset(&self,0, sizeof(self)); 276 | if (!dladdr(dlsym_wrapper_get, &self)) { 277 | GH_verbose(GH_MSG_ERROR,"dlsym_wrapper: failed to find myself!\n"); 278 | return NULL; 279 | } 280 | if (!self.dli_fname) { 281 | GH_verbose(GH_MSG_ERROR,"dlsym_wrapper: failed to find my path!\n"); 282 | return NULL; 283 | } 284 | GH_verbose(GH_MSG_DEBUG,"dlsym_wrapper: I am at '%s'\n", self.dli_fname); 285 | len = strlen(self.dli_fname); 286 | if (len >= sizeof(dlsym_wrapper_name)) { 287 | GH_verbose(GH_MSG_ERROR,"dlsym_wrapper: my path '%s' is too long: %zu >= %zu\n", self.dli_fname, len, sizeof(dlsym_wrapper_name)); 288 | return NULL; 289 | } 290 | memcpy(dlsym_wrapper_name, self.dli_fname, len+1); 291 | pos = strrchr(dlsym_wrapper_name, '/'); 292 | if (pos) { 293 | idx = pos - dlsym_wrapper_name + 1; 294 | } else { 295 | idx = 0; 296 | } 297 | if (idx > len) { 298 | GH_verbose(GH_MSG_ERROR,"dlsym_wrapper: failed to process my path '%s'\n", dlsym_wrapper_name); 299 | return NULL; 300 | } 301 | len = sizeof(dlsym_wrapper_name) - idx; 302 | slen = strlen(DLSYM_WRAPPER_NAME) + 1; 303 | if (len < slen) { 304 | GH_verbose(GH_MSG_ERROR,"dlsym_wrapper: failed to build path to the wrapper library: avail %zu < needed %zu\n", len, slen); 305 | return NULL; 306 | } 307 | memcpy(dlsym_wrapper_name + idx, DLSYM_WRAPPER_NAME, slen); 308 | GH_verbose(GH_MSG_ERROR,"dlsym_wrapper: wrapper library should be at '%s'\n", dlsym_wrapper_name); 309 | 310 | if (getenv(DLSYM_WRAPPER_ENVNAME)) { 311 | GH_verbose(GH_MSG_WARNING,"dlsym_wrapper: '%s' already defined, shouldn't be\n", DLSYM_WRAPPER_ENVNAME); 312 | } 313 | wrapper = dlopen(dlsym_wrapper_name, RTLD_LAZY | RTLD_LOCAL | RTLD_DEEPBIND | RTLD_NOLOAD); 314 | if (wrapper) { 315 | GH_verbose(GH_MSG_ERROR,"dlsym_wrapper: '%s' already loaded, this does not work!\n", dlsym_wrapper_name); 316 | dlclose(wrapper); 317 | return NULL; 318 | } 319 | wrapper = dlopen(dlsym_wrapper_name, RTLD_LAZY | RTLD_LOCAL | RTLD_DEEPBIND); 320 | if (!wrapper) { 321 | GH_verbose(GH_MSG_ERROR,"dlsym_wrapper: '%s' could not be loaded!\n", dlsym_wrapper_name); 322 | return NULL; 323 | } 324 | 325 | ptr_str = getenv(DLSYM_WRAPPER_ENVNAME); 326 | if (!ptr_str) { 327 | GH_verbose(GH_MSG_ERROR,"dlsym_wrapper: '%s' was not defined by the wrapper library\n", DLSYM_WRAPPER_ENVNAME); 328 | dlclose(wrapper); 329 | return NULL; 330 | } 331 | GH_verbose(GH_MSG_DEBUG, "dlsym_wrapper: got '%s'='%s'\n", DLSYM_WRAPPER_ENVNAME, ptr_str); 332 | if (sscanf(ptr_str, "%p", &ptr) == 1) { 333 | if (ptr) { 334 | GH_verbose(GH_MSG_DEBUG, "dlsym_wrapper: using %p as original dlsym()\n", ptr); 335 | res = ptr; 336 | } else { 337 | GH_verbose(GH_MSG_ERROR, "dlsym_wrapper: original dlsym() pointer is invalid\n", ptr); 338 | } 339 | } else { 340 | GH_verbose(GH_MSG_WARNING,"dlsym_wrapper: failed to parse pointer from '%s'='%s'\n", DLSYM_WRAPPER_ENVNAME, ptr_str); 341 | } 342 | 343 | dlclose(wrapper); 344 | if (res) { 345 | GH_verbose(GH_MSG_ERROR, "dlsym_wrapper: successfilly queried '%s' = %p\n", name, res); 346 | } else { 347 | GH_verbose(GH_MSG_ERROR, "dlsym_wrapper: failed to query '%s'\n", name); 348 | } 349 | return res; 350 | } 351 | #endif 352 | 353 | /* Mutex for the function pointers. We only guard the 354 | * if (ptr == NULL) ptr=...; part. The pointers will never 355 | * change after being set to a non-NULL value for the first time, 356 | * so it is safe to dereference them without locking */ 357 | static pthread_mutex_t GH_fptr_mutex=PTHREAD_MUTEX_INITIALIZER; 358 | 359 | /* Wrapper function called in place of dlsym(), since we intercept dlsym(). 360 | * We use this ONLY to get the original dlsym() itself, all other symbol 361 | * resolutions are done via that original function, then. 362 | */ 363 | static void *GH_dlsym_internal(void *handle, const char *name) 364 | { 365 | void *ptr; 366 | 367 | #ifdef GH_DLSYM_NEED_LOCK 368 | /* ARGH: we are bypassing glibc's locking for dlsym(), so we 369 | * must do this on our own */ 370 | pthread_mutex_lock(&GH_mutex); 371 | #endif 372 | 373 | #if (GH_DLSYM_METHOD == 1) 374 | GH_verbose(GH_MSG_DEBUG, "using _dl_sym() method\n"); 375 | 376 | /* Third argument is the address of the caller, (glibc uses stack 377 | * unwinding internally to get this), we just use the address of our 378 | * wrapper function itself, which is wrong when this is called on 379 | * behalf of the real application doing a dlsycm, but we do not 380 | * care... */ 381 | ptr=_dl_sym(handle, name, GH_dlsym_internal); 382 | #elif (GH_DLSYM_METHOD == 2) 383 | GH_verbose(GH_MSG_DEBUG, "using dlvsym() method\n"); 384 | ptr=dlvsym(handle, name, "GLIBC_" GH_DLSYM_ABI_VERSION); 385 | #elif (GH_DLSYM_METHOD == 3) 386 | GH_verbose(GH_MSG_DEBUG, "using dlsym_wrapper.so method\n"); 387 | ptr=dlsym_wrapper_get(handle, name); 388 | #else 389 | #error GH_DLSYM_METHOD not supported 390 | #endif /* GH_DLSYM_METHOD */ 391 | 392 | #ifdef GH_DLSYM_NEED_LOCK 393 | pthread_mutex_unlock(&GH_mutex); 394 | #endif 395 | return ptr; 396 | } 397 | 398 | /* Wrapper funtcion to query an original function avoiding 399 | * recursively calls to the interceptor dlsym() below */ 400 | static void *GH_dlsym_internal_next(const char *name) 401 | { 402 | return GH_dlsym_internal(RTLD_NEXT, name); 403 | } 404 | 405 | /* return intercepted function pointer for a symbol */ 406 | static void *GH_get_interceptor(const char*, GH_resolve_func, const char *); 407 | 408 | /* function pointers to call the real functions that we did intercept */ 409 | static void * (* volatile GH_dlsym)(void *, const char*)=NULL; 410 | static void * (* volatile GH_dlvsym)(void *, const char*, const char *)=NULL; 411 | static GH_fptr (* volatile GH_glXGetProcAddress)(const char*)=NULL; 412 | static GH_fptr (* volatile GH_glXGetProcAddressARB)(const char *)=NULL; 413 | static void (* volatile GH_glXSwapBuffers)(Display *, GLXDrawable); 414 | static void (* volatile GH_glXSwapIntervalEXT)(Display *, GLXDrawable, int); 415 | static int (* volatile GH_glXSwapIntervalSGI)(int); 416 | static int (* volatile GH_glXSwapIntervalMESA)(unsigned int); 417 | static GLXContext (* volatile GH_glXCreateContext)(Display*, XVisualInfo *, GLXContext, Bool); 418 | static GLXContext (* volatile GH_glXCreateNewContext)(Display *, GLXFBConfig, int, GLXContext, Bool); 419 | static GLXContext (* volatile GH_glXCreateContextAttribsARB)(Display *, GLXFBConfig, GLXContext, Bool, const int *); 420 | static GLXContext (* volatile GH_glXImportContextEXT)(Display *, GLXContextID); 421 | static GLXContext (* volatile GH_glXCreateContextWithConfigSGIX)(Display *, GLXFBConfigSGIX, int, GLXContext, Bool); 422 | static void (* volatile GH_glXDestroyContext)(Display *, GLXContext); 423 | static void (* volatile GH_glXFreeContextEXT)(Display *, GLXContext); 424 | static Bool (* volatile GH_glXMakeCurrent)(Display *, GLXDrawable, GLXContext); 425 | static Bool (* volatile GH_glXMakeContextCurrent)(Display *, GLXDrawable, GLXDrawable, GLXContext); 426 | static Bool (* volatile GH_glXMakeCurrentReadSGI)(Display *, GLXDrawable, GLXDrawable, GLXContext); 427 | 428 | static void (* volatile GH_glDebugMessageCallback)(GLDEBUGPROC, const GLvoid*); 429 | static void (* volatile GH_glDebugMessageCallbackARB)(GLDEBUGPROC, const GLvoid*); 430 | static void (* volatile GH_glDebugMessageCallbackKHR)(GLDEBUGPROC, const GLvoid*); 431 | static void (* volatile GH_glDebugMessageCallbackAMD)(GLDEBUGPROCAMD, GLvoid*); 432 | 433 | /* function pointers we just might qeury */ 434 | static void (* volatile GH_glFlush)(void); 435 | static void (* volatile GH_glFinish)(void); 436 | 437 | static GLXFBConfig* (* volatile GH_glXGetFBConfigs)(Display*, int, int*); 438 | static int (* volatile GH_glXGetFBConfigAttrib)(Display*, GLXFBConfig, int, int*); 439 | static int (* volatile GH_XFree)(void*); 440 | 441 | #ifdef GH_CONTEXT_TRACKING 442 | /* OpenGL extension functions we might query */ 443 | static PFNGLGENQUERIESPROC GH_glGenQueries=NULL; 444 | static PFNGLDELETEQUERIESPROC GH_glDeleteQueries=NULL; 445 | static PFNGLGETINTEGER64VPROC GH_glGetInteger64v=NULL; 446 | static PFNGLQUERYCOUNTERPROC GH_glQueryCounter=NULL; 447 | static PFNGLGETQUERYOBJECTUI64VPROC GH_glGetQueryObjectui64v=NULL; 448 | static PFNGLFENCESYNCPROC GH_glFenceSync=NULL; 449 | static PFNGLDELETESYNCPROC GH_glDeleteSync=NULL; 450 | static PFNGLCLIENTWAITSYNCPROC GH_glClientWaitSync=NULL; 451 | #endif /* GH_CONTEXT_TRACKING */ 452 | 453 | /* Resolve an unintercepted symbol via the original dlsym() */ 454 | static void *GH_dlsym_next(const char *name) 455 | { 456 | if (GH_dlsym) { 457 | return GH_dlsym(RTLD_NEXT, name); 458 | } 459 | GH_verbose(GH_MSG_WARNING, "failed to dynamically query '%s' because I don't have a dlsym\n", name); 460 | return NULL; 461 | } 462 | 463 | /* Wrapper funtcion to query the original dlsym() function avoiding 464 | * recursively calls to the interceptor dlsym() below */ 465 | static void GH_dlsym_internal_dlsym() 466 | { 467 | static const char *dlsymname = "dlsym"; 468 | static const char *dlvsymname = "dlvsym"; 469 | DLSYM_PROC_T orig_dlsym = NULL; 470 | if (GH_dlsym == NULL) { 471 | GH_dlsym = GH_dlsym_internal_next(dlsymname); 472 | orig_dlsym = GH_dlsym; 473 | if (GH_dlsym) { 474 | void *ptr; 475 | GH_verbose(GH_MSG_DEBUG_INTERCEPTION,"INTERNAL: (%s) = %p, ours is %p\n",dlsymname,GH_dlsym,dlsym); 476 | ptr = GH_dlsym_next(dlsymname); 477 | if (ptr != (void*)GH_dlsym) { 478 | if (ptr) { 479 | if (get_envi("GH_ALLOW_DLSYM_REDIRECTION", 1)) { 480 | GH_verbose(GH_MSG_DEBUG_INTERCEPTION,"INTERNAL: (%s) = %p intercepted to %p\n",dlsymname,GH_dlsym,ptr); 481 | GH_dlsym = ptr; 482 | } else { 483 | GH_verbose(GH_MSG_WARNING, "INTERNAL: (%s) = %p would be intercepted to %p but ignoring it\n",dlsymname,GH_dlsym,ptr); 484 | } 485 | } else { 486 | GH_verbose(GH_MSG_WARNING,"INTERNAL: (%s) would be intercepted to NULL, ignoring it\n",dlsymname); 487 | } 488 | } 489 | } else { 490 | GH_verbose(GH_MSG_WARNING, "failed to dynamically query '%s'\n", dlsymname); 491 | } 492 | } 493 | 494 | /* use the original dlsym, not a potentially redirected one */ 495 | if (orig_dlsym && (GH_dlvsym == NULL)) { 496 | Dl_info info; 497 | memset(&info,0,sizeof(info)); 498 | GH_dlvsym = orig_dlsym(RTLD_NEXT,dlvsymname); 499 | } 500 | } 501 | 502 | /* Resolve an unintercepted symbol via the original dlsym(), 503 | * special libGL variant: if not found, dymically try to 504 | * load libGL.so */ 505 | static void *GH_dlsym_gl(const char *name) 506 | { 507 | static void *libgl_handle=NULL; 508 | static int try_load_libgl=1; 509 | 510 | void *ptr=GH_dlsym(RTLD_NEXT, name); 511 | if (!ptr) { 512 | if (try_load_libgl && !libgl_handle) { 513 | const char *libname=get_envs("GH_LIBGL_FILE", "libGL.so"); 514 | if (libname[0]) { 515 | GH_verbose(GH_MSG_DEBUG, "trying to load libGL manually: '%s'\n", libname); 516 | libgl_handle=dlopen(libname, RTLD_GLOBAL | RTLD_LAZY); 517 | if (!libgl_handle) { 518 | GH_verbose(GH_MSG_WARNING, "failed to load '%s' manually\n", libname); 519 | /* give up on loading libGL */ 520 | try_load_libgl=0; 521 | } 522 | } else { 523 | try_load_libgl=0; 524 | } 525 | } 526 | if (libgl_handle) { 527 | GH_verbose(GH_MSG_DEBUG, "trying to find '%s' in manually loaded libGL\n", name); 528 | ptr=GH_dlsym(libgl_handle, name); 529 | } 530 | } 531 | return ptr; 532 | } 533 | 534 | /* helper macro: query the symbol pointer if it is NULL 535 | * handle the locking */ 536 | #define GH_GET_PTR(func) \ 537 | pthread_mutex_lock(&GH_fptr_mutex); \ 538 | if(GH_ ##func == NULL) \ 539 | GH_ ##func = GH_dlsym_next(#func);\ 540 | pthread_mutex_unlock(&GH_fptr_mutex) 541 | 542 | /* helper macro: query the symbol pointer if it is NULL 543 | * handle the locking, special libGL variant */ 544 | #define GH_GET_PTR_GL(func) \ 545 | pthread_mutex_lock(&GH_fptr_mutex); \ 546 | if(GH_ ##func == NULL) \ 547 | GH_ ##func = GH_dlsym_gl(#func);\ 548 | pthread_mutex_unlock(&GH_fptr_mutex) 549 | 550 | #ifdef GH_CONTEXT_TRACKING 551 | 552 | /* try to get an OpenGL function */ 553 | static void * 554 | GH_get_gl_proc(const char *name) 555 | { 556 | static void *proc; 557 | 558 | /* try glXGetProcAddressARB first */ 559 | GH_GET_PTR(glXGetProcAddressARB); 560 | if (GH_glXGetProcAddressARB && (proc=GH_glXGetProcAddressARB(name)) ) 561 | return proc; 562 | 563 | /* try glXGetProcAddress as second chance */ 564 | GH_GET_PTR(glXGetProcAddress); 565 | if (GH_glXGetProcAddress && (proc=GH_glXGetProcAddress(name)) ) 566 | return proc; 567 | 568 | /* try dlsym as last resort */ 569 | return GH_dlsym_gl(name); 570 | } 571 | 572 | #define GH_GET_GL_PROC(func) \ 573 | pthread_mutex_lock(&GH_fptr_mutex); \ 574 | if ( (GH_ ##func == NULL)) { \ 575 | void *ptr; \ 576 | pthread_mutex_unlock(&GH_fptr_mutex); \ 577 | ptr = GH_get_gl_proc(#func); \ 578 | GH_verbose(GH_MSG_DEBUG,"queried internal GL %s: %p\n", \ 579 | #func, ptr); \ 580 | pthread_mutex_lock(&GH_fptr_mutex); \ 581 | GH_ ##func = ptr; \ 582 | } \ 583 | pthread_mutex_unlock(&GH_fptr_mutex); 584 | 585 | #define GH_GET_GL_PROC_OR_FAIL(func, level, fail_code) \ 586 | GH_GET_GL_PROC(func); \ 587 | if (GH_ ##func == NULL) { \ 588 | GH_verbose(level, "%s not available!\n", #func); \ 589 | return fail_code; \ 590 | } \ 591 | (void)0 592 | 593 | /*************************************************************************** 594 | * LATENCY LIMITER * 595 | ***************************************************************************/ 596 | 597 | 598 | /* we support different modes for the frame time measurements */ 599 | typedef enum { 600 | GH_LATENCY_NOP=-2, /* do nothing */ 601 | GH_LATENCY_FINISH_AFTER, /* force a finish after buffer swaps */ 602 | GH_LATENCY_FINISH_BEFORE, /* force a finish before buffer swaps */ 603 | /* values above 0 indicate use of GL_ARB_sync to limit to n frames */ 604 | } GH_latency_mode; 605 | 606 | typedef struct { 607 | int latency; 608 | GLsync *sync_object; 609 | unsigned int cur_pos; 610 | unsigned int flags; 611 | GLuint64 gl_wait_timeout; 612 | GLuint64 gl_wait_interval; 613 | useconds_t self_wait_interval; 614 | } GH_latency; 615 | 616 | /* latency flags */ 617 | #define GH_LATENCY_FLAG_MANUAL_WAIT 0x1 618 | 619 | static int 620 | latency_gl_init() 621 | { 622 | GH_GET_GL_PROC_OR_FAIL(glFenceSync, GH_MSG_WARNING, -1); 623 | GH_GET_GL_PROC_OR_FAIL(glDeleteSync, GH_MSG_WARNING, -1); 624 | GH_GET_GL_PROC_OR_FAIL(glClientWaitSync, GH_MSG_WARNING, -1); 625 | return 0; 626 | } 627 | 628 | static void 629 | latency_init(GH_latency *lat, int latency, int manual_wait, unsigned gl_wait_timeout_usecs, unsigned int gl_wait_interval_usecs, unsigned int self_wait_interval_usecs) 630 | { 631 | lat->latency=latency; 632 | lat->sync_object=NULL; 633 | lat->cur_pos=0; 634 | lat->flags=0; 635 | lat->gl_wait_timeout=(GLuint64)gl_wait_timeout_usecs * (GLuint64)1000; 636 | lat->gl_wait_interval=(GLuint64)gl_wait_interval_usecs * (GLuint64)1000; 637 | lat->self_wait_interval=(useconds_t)self_wait_interval_usecs; 638 | 639 | if (manual_wait > 0) { 640 | lat->flags |= GH_LATENCY_FLAG_MANUAL_WAIT; 641 | } else if (manual_wait < 0) { 642 | if ((gl_wait_interval_usecs > 0) || (self_wait_interval_usecs > 0)) { 643 | lat->flags |= GH_LATENCY_FLAG_MANUAL_WAIT; 644 | } 645 | } 646 | 647 | if (latency != GH_LATENCY_NOP) { 648 | GH_verbose(GH_MSG_INFO, "setting up latency limiter mode %d\n", latency); 649 | } 650 | 651 | if (latency > 0) { 652 | if (latency_gl_init()) { 653 | lat->latency=GH_LATENCY_FINISH_BEFORE; 654 | GH_verbose(GH_MSG_WARNING, "GPU sync not available, using latency mode %d\n", 655 | GH_LATENCY_FINISH_BEFORE); 656 | } 657 | } 658 | 659 | if (lat->latency > 0) { 660 | unsigned int cnt=(unsigned)lat->latency; 661 | lat->sync_object=malloc(sizeof(*lat->sync_object) * cnt); 662 | if (!lat->sync_object) { 663 | lat->latency=GH_LATENCY_FINISH_BEFORE; 664 | GH_verbose(GH_MSG_WARNING, "out of memory for sync objects, using latency mode %d\n"); 665 | } else { 666 | unsigned int i; 667 | for (i=0; isync_object[i]=NULL; 669 | } 670 | GH_verbose(GH_MSG_DEBUG, "enabling latency limiter: %d\n",lat->latency); 671 | if (lat->flags & GH_LATENCY_FLAG_MANUAL_WAIT) { 672 | GH_verbose(GH_MSG_INFO, "latency limiter with manual waits GL: %u usecs + self: %u usecs\n", 673 | gl_wait_interval_usecs, self_wait_interval_usecs); 674 | } else { 675 | GH_verbose(GH_MSG_INFO, "latency limiter with timeout: %u usecs\n", 676 | gl_wait_timeout_usecs); 677 | } 678 | } 679 | } 680 | } 681 | 682 | static void 683 | latency_destroy(GH_latency *lat) 684 | { 685 | if (lat) { 686 | if (lat->sync_object && lat->latency > 0) { 687 | unsigned int i; 688 | for (i=0; i<(unsigned)lat->latency; i++) { 689 | if (lat->sync_object[i]) { 690 | GH_glDeleteSync(lat->sync_object[i]); 691 | } 692 | } 693 | } 694 | } 695 | } 696 | 697 | static void 698 | latency_before_swap(GH_latency *lat) 699 | { 700 | GLsync sync; 701 | 702 | switch(lat->latency) { 703 | case GH_LATENCY_NOP: 704 | case GH_LATENCY_FINISH_AFTER: 705 | (void)lat; 706 | break; 707 | case GH_LATENCY_FINISH_BEFORE: 708 | GH_glFinish(); 709 | break; 710 | default: 711 | if ( (sync=lat->sync_object[lat->cur_pos]) ) { 712 | if (lat->flags & GH_LATENCY_FLAG_MANUAL_WAIT) { 713 | /* check for the fence in a loop */ 714 | while(GH_glClientWaitSync(sync, GL_SYNC_FLUSH_COMMANDS_BIT, lat->gl_wait_interval) == GL_TIMEOUT_EXPIRED) { 715 | if (lat->self_wait_interval) { 716 | usleep(lat->self_wait_interval); 717 | } 718 | } 719 | } else { 720 | /* just wait for the fence */ 721 | GH_glClientWaitSync(sync, GL_SYNC_FLUSH_COMMANDS_BIT, lat->gl_wait_timeout); 722 | /* NOTE: we do not care about the result */ 723 | } 724 | } 725 | } 726 | } 727 | 728 | static void 729 | latency_after_swap(GH_latency *lat) 730 | { 731 | switch(lat->latency) { 732 | case GH_LATENCY_NOP: 733 | case GH_LATENCY_FINISH_BEFORE: 734 | (void)lat; 735 | break; 736 | case GH_LATENCY_FINISH_AFTER: 737 | GH_glFinish(); 738 | break; 739 | default: 740 | if ( (lat->sync_object[lat->cur_pos]) ) { 741 | GH_glDeleteSync(lat->sync_object[lat->cur_pos]); 742 | } 743 | lat->sync_object[lat->cur_pos]=GH_glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); 744 | if (++lat->cur_pos == (unsigned)lat->latency) { 745 | lat->cur_pos=0; 746 | } 747 | } 748 | } 749 | 750 | /*************************************************************************** 751 | * FRAME TIMING MEASUREMENTS * 752 | ***************************************************************************/ 753 | 754 | /* we support different modes for the frame time measurements */ 755 | typedef enum { 756 | GH_FRAMETIME_NONE=0, /* do not measure any frame times */ 757 | GH_FRAMETIME_CPU, /* measure only on CPU */ 758 | GH_FRAMETIME_CPU_GPU, /* measure both on CPU and GPU */ 759 | } GH_frametime_mode; 760 | 761 | /* a single timestamp */ 762 | typedef struct { 763 | struct timespec timestamp_cpu; /* CPU timestamp */ 764 | GLuint64 timestamp_gl; /* OpenGL timestamp (client side) */ 765 | GLuint query_object; /* the OpenGL timer query object */ 766 | } GH_timestamp; 767 | 768 | /* the _result_ of the measurement */ 769 | typedef struct { 770 | uint64_t cpu; 771 | uint64_t gl; 772 | uint64_t gpu; 773 | } GH_frametime; 774 | 775 | /* the complete state needed for frametime measurements */ 776 | typedef struct { 777 | GH_frametime_mode mode; /* the mode we are in */ 778 | unsigned int delay; /* number of frames for delay */ 779 | unsigned int num_timestamps; /* number of timestamps per frame */ 780 | unsigned int num_results; /* number of frames to store results */ 781 | GH_timestamp *timestamp; /* the array of timestamps, used as ring buffer */ 782 | unsigned int cur_pos; /* the current frame in the delay ring buffer */ 783 | GH_frametime *frametime; /* the array of frametimes */ 784 | unsigned int cur_result; /* the current result index */ 785 | unsigned int frame; /* the current frame */ 786 | FILE *dump; /* the stream to dump the results to */ 787 | } GH_frametimes; 788 | 789 | /* the probes we take each frame */ 790 | typedef enum { 791 | GH_FRAMETIME_BEFORE_SWAPBUFFERS=0, 792 | GH_FRAMETIME_AFTER_SWAPBUFFERS, 793 | GH_FRAMETIME_COUNT 794 | } GH_frametime_probe; 795 | 796 | static void 797 | timestamp_init(GH_timestamp *ts) 798 | { 799 | ts->query_object=0; 800 | ts->timestamp_gl=0; 801 | ts->timestamp_cpu.tv_sec=0; 802 | ts->timestamp_cpu.tv_nsec=0; 803 | } 804 | 805 | static void 806 | timestamp_cleanup(GH_timestamp *ts) 807 | { 808 | if (ts->query_object && GH_glDeleteQueries) { 809 | GH_glDeleteQueries(1, &ts->query_object); 810 | ts->query_object=0; 811 | } 812 | } 813 | 814 | static void 815 | timestamp_set(GH_timestamp *ts, GH_frametime *rs, GH_frametime_mode mode) 816 | { 817 | /* CPU */ 818 | /* collect previous result */ 819 | rs->cpu=(uint64_t)ts->timestamp_cpu.tv_sec * (uint64_t)1000000000UL 820 | + (uint64_t)ts->timestamp_cpu.tv_nsec; 821 | /* new query */ 822 | clock_gettime(CLOCK_REALTIME, &ts->timestamp_cpu); 823 | 824 | /* GPU */ 825 | if (mode >= GH_FRAMETIME_CPU_GPU) { 826 | /* collect previous result */ 827 | if (ts->query_object) { 828 | GLuint64 value; 829 | GH_glGetQueryObjectui64v(ts->query_object, GL_QUERY_RESULT, &value); 830 | rs->gpu=(uint64_t)value; 831 | } else { 832 | GH_glGenQueries(1, &ts->query_object); 833 | } 834 | rs->gl=(uint64_t)(ts->timestamp_gl); 835 | /* new query */ 836 | GH_glQueryCounter(ts->query_object, GL_TIMESTAMP); 837 | GH_glGetInteger64v(GL_TIMESTAMP, (GLint64*)&ts->timestamp_gl); 838 | } 839 | } 840 | 841 | static void 842 | frametime_init(GH_frametime *rs) 843 | { 844 | rs->cpu = 0; 845 | rs->gl = 0; 846 | rs->gpu = 0; 847 | } 848 | 849 | static int 850 | frametimes_gl_init() 851 | { 852 | GH_GET_GL_PROC_OR_FAIL(glGenQueries, GH_MSG_WARNING, -1); 853 | GH_GET_GL_PROC_OR_FAIL(glDeleteQueries, GH_MSG_WARNING, -1); 854 | GH_GET_GL_PROC_OR_FAIL(glGetInteger64v, GH_MSG_WARNING, -1); 855 | GH_GET_GL_PROC_OR_FAIL(glQueryCounter, GH_MSG_WARNING, -1); 856 | GH_GET_GL_PROC_OR_FAIL(glGetQueryObjectui64v, GH_MSG_WARNING, -1); 857 | return 0; 858 | } 859 | 860 | static void 861 | frametimes_init(GH_frametimes *ft, GH_frametime_mode mode, unsigned int delay, unsigned int num_timestamps, unsigned int num_results, unsigned int ctx_num) 862 | { 863 | ft->cur_pos=0; 864 | ft->cur_result=0; 865 | ft->frame=0; 866 | ft->dump=NULL; 867 | 868 | if (mode >= GH_FRAMETIME_CPU_GPU) { 869 | if (frametimes_gl_init()) { 870 | GH_verbose(GH_MSG_WARNING, "GPU timer queries not available, using CPU only\n"); 871 | mode = GH_FRAMETIME_CPU; 872 | } 873 | } 874 | 875 | if (mode && delay && num_timestamps && num_results) { 876 | ft->mode=mode; 877 | ft->delay=delay; 878 | ft->num_timestamps=num_timestamps; 879 | ft->num_results=num_results; 880 | if ((ft->frametime=malloc(sizeof(*ft->frametime) * (num_results+1) * num_timestamps))) { 881 | if ((ft->timestamp=malloc(sizeof(*ft->timestamp) * delay * num_timestamps))) { 882 | unsigned int i; 883 | GH_verbose(GH_MSG_DEBUG, "enabling frametime measurements mode %d, %u x %u timestamps\n", 884 | (int)mode, delay, num_timestamps); 885 | for (i=0; itimestamp[i]); 887 | } 888 | } else { 889 | GH_verbose(GH_MSG_WARNING, "failed to allocate memory for %u x %u timestamps, " 890 | "disbaling timestamps\n", 891 | delay, num_timestamps); 892 | mode=GH_FRAMETIME_NONE; 893 | } 894 | } else { 895 | GH_verbose(GH_MSG_WARNING, "failed to allocate memory for %u x %u frametime results, " 896 | "disbaling timestamps\n", 897 | num_results, num_timestamps); 898 | mode=GH_FRAMETIME_NONE; 899 | } 900 | } 901 | 902 | if (!mode || !delay || !num_timestamps || !num_results) { 903 | ft->mode=GH_FRAMETIME_NONE; 904 | ft->delay=0; 905 | ft->num_timestamps=0; 906 | ft->num_results=0; 907 | ft->timestamp=NULL; 908 | ft->frametime=NULL; 909 | } 910 | 911 | if (ft->mode) { 912 | const char *file=get_envs("GH_FRAMETIME_FILE","glx_hook_frametimes-ctx%c.csv"); 913 | if (file) { 914 | char buf[PATH_MAX]; 915 | parse_name(buf, sizeof(buf), file, ctx_num); 916 | ft->dump=fopen(buf,"wt"); 917 | } 918 | if (!ft->dump) { 919 | ft->dump=stderr; 920 | } 921 | } 922 | } 923 | 924 | static void 925 | frametimes_init_base(GH_frametimes *ft) 926 | { 927 | /* get the base timestamp */ 928 | if (ft->mode > GH_FRAMETIME_NONE) { 929 | GH_timestamp base; 930 | unsigned int i; 931 | GH_frametime *last=&ft->frametime[ft->num_results * ft->num_timestamps]; 932 | 933 | timestamp_init(&base); 934 | timestamp_set(&base, &last[0], ft->mode); 935 | timestamp_set(&base, &last[0], ft->mode); 936 | for (i=1; inum_timestamps; i++) { 937 | last[i]=last[0]; 938 | } 939 | timestamp_cleanup(&base); 940 | } 941 | } 942 | 943 | static void 944 | frametimes_dump_diff(const GH_frametimes *ft, uint64_t val, uint64_t base) 945 | { 946 | fprintf(ft->dump, "\t%llu", (unsigned long long) (val-base)); 947 | } 948 | 949 | static void 950 | frametimes_dump_result(const GH_frametimes *ft, const GH_frametime *rs, const GH_frametime *base) 951 | { 952 | frametimes_dump_diff(ft, rs->cpu, base->cpu); 953 | frametimes_dump_diff(ft, rs->gpu, base->gpu); 954 | frametimes_dump_diff(ft, rs->gpu, rs->gl); 955 | } 956 | 957 | static void 958 | frametimes_dump_results(const GH_frametimes *ft, const GH_frametime *rs, const GH_frametime *prev) 959 | { 960 | unsigned int i; 961 | for (i=0; inum_timestamps; i++) { 962 | frametimes_dump_result(ft, &rs[i], &prev[GH_FRAMETIME_AFTER_SWAPBUFFERS]); 963 | } 964 | } 965 | 966 | static void 967 | frametimes_flush(GH_frametimes *ft) 968 | { 969 | unsigned int i; 970 | GH_frametime *last; 971 | const GH_frametime *cur,*prev=&ft->frametime[ft->num_results * ft->num_timestamps]; 972 | 973 | if (ft->cur_result == 0) { 974 | return; 975 | } 976 | 977 | GH_verbose(GH_MSG_DEBUG, "frametimes: dumping results of %u frames\n", ft->cur_result); 978 | for (i=0; icur_result; i++) { 979 | unsigned int frame=ft->frame - ft->cur_result + i; 980 | if (frame >= ft->delay) { 981 | fprintf(ft->dump, "%u", frame - ft->delay); 982 | cur=&ft->frametime[i * ft->num_timestamps]; 983 | frametimes_dump_results(ft, cur, prev); 984 | prev=cur; 985 | fputc('\n', ft->dump); 986 | } 987 | } 988 | fflush(ft->dump); 989 | /* copy the last result */ 990 | last=&ft->frametime[ft->num_results * ft->num_timestamps]; 991 | cur=&ft->frametime[(ft->cur_result-1) * ft->num_timestamps]; 992 | for (i=0; inum_timestamps; i++) { 993 | last[i]=cur[i]; 994 | } 995 | 996 | 997 | ft->cur_result=0; 998 | } 999 | 1000 | static void 1001 | frametimes_destroy(GH_frametimes *ft) 1002 | { 1003 | if (ft) { 1004 | unsigned int cnt=ft->delay * ft->num_timestamps; 1005 | unsigned int i; 1006 | 1007 | frametimes_flush(ft); 1008 | if (ft->dump != NULL && ft->dump != stdout && ft->dump != stderr) { 1009 | fclose(ft->dump); 1010 | } 1011 | for (i=0; itimestamp[i]); 1013 | } 1014 | free(ft->timestamp); 1015 | free(ft->frametime); 1016 | } 1017 | } 1018 | 1019 | static void 1020 | frametimes_before_swap(GH_frametimes *ft) 1021 | { 1022 | unsigned int ts_idx; 1023 | unsigned int rs_idx; 1024 | 1025 | if (ft->mode == GH_FRAMETIME_NONE) 1026 | return; 1027 | ts_idx=ft->cur_pos * ft->num_timestamps + GH_FRAMETIME_BEFORE_SWAPBUFFERS; 1028 | rs_idx=ft->cur_result * ft->num_timestamps + GH_FRAMETIME_BEFORE_SWAPBUFFERS; 1029 | timestamp_set(&ft->timestamp[ts_idx], &ft->frametime[rs_idx], ft->mode); 1030 | } 1031 | 1032 | static void 1033 | frametimes_finish_frame(GH_frametimes *ft) 1034 | { 1035 | if (++ft->cur_pos == ft->delay) { 1036 | ft->cur_pos=0; 1037 | } 1038 | ++ft->frame; 1039 | if (++ft->cur_result >= ft->num_results) { 1040 | frametimes_flush(ft); 1041 | } 1042 | } 1043 | 1044 | static void 1045 | frametimes_after_swap(GH_frametimes *ft) 1046 | { 1047 | unsigned int ts_idx; 1048 | unsigned int rs_idx; 1049 | 1050 | if (ft->mode == GH_FRAMETIME_NONE) 1051 | return; 1052 | ts_idx=ft->cur_pos * ft->num_timestamps + GH_FRAMETIME_AFTER_SWAPBUFFERS; 1053 | rs_idx=ft->cur_result * ft->num_timestamps + GH_FRAMETIME_AFTER_SWAPBUFFERS; 1054 | timestamp_set(&ft->timestamp[ts_idx], &ft->frametime[rs_idx], ft->mode); 1055 | frametimes_finish_frame(ft); 1056 | } 1057 | 1058 | /*************************************************************************** 1059 | * SWAPBUFFER OMISSION (very experimental) * 1060 | ***************************************************************************/ 1061 | 1062 | #define GH_SWAP_OMISSION_FRAMES_MAX 16 1063 | 1064 | typedef struct { 1065 | int swapbuffers; 1066 | int swapbuffer_cnt; 1067 | int latency_mode; 1068 | int flush_mode; 1069 | int measure_mode; 1070 | int limits[2]; 1071 | uint64_t min_swap_time; 1072 | GH_timestamp prev_frame_ts[GH_SWAP_OMISSION_FRAMES_MAX][2]; 1073 | GH_frametime prev_frames[GH_SWAP_OMISSION_FRAMES_MAX][2]; 1074 | int prev_intervals[GH_SWAP_OMISSION_FRAMES_MAX]; 1075 | unsigned int cur_pos; 1076 | unsigned int measure_frames_tot; 1077 | unsigned int measure_frames_avg; 1078 | } GH_swapbuffer_omission_t; 1079 | 1080 | static void 1081 | swapbuffer_omission_init(GH_swapbuffer_omission_t *swo) 1082 | { 1083 | int i; 1084 | int min_swap_usecs = get_envi("GH_MIN_SWAP_USECS",0); 1085 | swo->swapbuffers=get_envi("GH_SWAPBUFFERS",0); 1086 | swo->min_swap_time = 0; 1087 | if (min_swap_usecs > 0) { 1088 | swo->swapbuffers = 1; 1089 | swo->min_swap_time = (uint64_t)min_swap_usecs * (uint64_t)1000UL; 1090 | } 1091 | swo->latency_mode = get_envi("GH_SWAP_OMISSION_LATENCY", 0); 1092 | swo->flush_mode = get_envi("GH_SWAP_OMISSION_FLUSH", 1); 1093 | swo->measure_mode = get_envi("GH_SWAP_OMISSION_MEASURE", 3); 1094 | swo->limits[0] = get_envi("GH_SWAP_OMISSION_MIN",1); 1095 | swo->limits[1] = get_envi("GH_SWAP_OMISSION_MAX",4); 1096 | if (swo->limits[0] < 1) { 1097 | swo->limits[0] = 1; 1098 | } 1099 | if (swo->limits[1] < swo->limits[0]) { 1100 | swo->limits[1] = swo->limits[0]; 1101 | } 1102 | swo->swapbuffer_cnt=0; 1103 | swo->cur_pos = 0; 1104 | swo->measure_frames_tot = get_envui("GH_SWAP_OMISSION_MEASURE_TOT", 6U); 1105 | swo->measure_frames_avg = get_envui("GH_SWAP_OMISSION_MEASURE_AVG", 4U); 1106 | if (swo->measure_frames_tot > GH_SWAP_OMISSION_FRAMES_MAX) { 1107 | swo->measure_frames_tot = GH_SWAP_OMISSION_FRAMES_MAX; 1108 | } else if (swo->measure_frames_tot < 2) { 1109 | swo->measure_frames_tot = 2; 1110 | } 1111 | if (swo->measure_frames_avg >= swo->measure_frames_tot) { 1112 | swo->measure_frames_avg = swo->measure_frames_tot - 1U; 1113 | } 1114 | if (swo->measure_frames_avg < 1) { 1115 | swo->measure_frames_avg = 1; 1116 | } 1117 | 1118 | for (i=0; iprev_frame_ts[i][0]); 1120 | timestamp_init(&swo->prev_frame_ts[i][1]); 1121 | frametime_init(&swo->prev_frames[i][0]); 1122 | frametime_init(&swo->prev_frames[i][1]); 1123 | swo->prev_intervals[i] = 1; 1124 | } 1125 | } 1126 | 1127 | static int 1128 | swapbuffer_omission_init_gl_funcs(void) 1129 | { 1130 | GH_GET_GL_PROC_OR_FAIL(glGenQueries, GH_MSG_WARNING, -1); 1131 | GH_GET_GL_PROC_OR_FAIL(glDeleteQueries, GH_MSG_WARNING, -1); 1132 | GH_GET_GL_PROC_OR_FAIL(glGetInteger64v, GH_MSG_WARNING, -1); 1133 | GH_GET_GL_PROC_OR_FAIL(glQueryCounter, GH_MSG_WARNING, -1); 1134 | GH_GET_GL_PROC_OR_FAIL(glGetQueryObjectui64v, GH_MSG_WARNING, -1); 1135 | return 0; 1136 | } 1137 | 1138 | static void 1139 | swapbuffer_omission_init_gl(GH_swapbuffer_omission_t *swo) 1140 | { 1141 | if (swo->min_swap_time > 0) { 1142 | if (swapbuffer_omission_init_gl_funcs()) { 1143 | GH_verbose(GH_MSG_WARNING,"adaptive swapbuffer omission not availabe without timer query, disabling it"); 1144 | swo->min_swap_time = 0; 1145 | swo->swapbuffers = get_envi("GH_SWAPBUFFERS",0);; 1146 | } 1147 | } 1148 | } 1149 | 1150 | static int 1151 | swapbuffer_omission_do_swap(GH_swapbuffer_omission_t *swo) 1152 | { 1153 | int do_swap; 1154 | if (swo->min_swap_time > 0) { 1155 | unsigned int i,idx; 1156 | uint64_t cpu = 0; 1157 | uint64_t gpu = 0; 1158 | uint64_t val; 1159 | timestamp_set(&swo->prev_frame_ts[swo->cur_pos][1],&swo->prev_frames[swo->cur_pos][1], GH_FRAMETIME_CPU_GPU); 1160 | if (++swo->cur_pos >= swo->measure_frames_tot) { 1161 | swo->cur_pos = 0; 1162 | } 1163 | idx = swo->cur_pos; 1164 | 1165 | for (i=0; imeasure_frames_avg; i++) { 1166 | cpu += (swo->prev_frames[idx][1].cpu - swo->prev_frames[idx][0].cpu); 1167 | gpu += (swo->prev_frames[idx][1].gpu - swo->prev_frames[idx][0].gpu); 1168 | if (++idx >= swo->measure_frames_tot) { 1169 | idx = 0; 1170 | } 1171 | } 1172 | cpu /= swo->measure_frames_avg; 1173 | gpu /= swo->measure_frames_avg; 1174 | switch (swo->measure_mode) { 1175 | case 1: 1176 | val=cpu; 1177 | break; 1178 | case 2: 1179 | val=gpu; 1180 | break; 1181 | default: 1182 | val = (cpu>gpu)?cpu:gpu; 1183 | } 1184 | if (val < 1000) { 1185 | val = 1000; 1186 | } 1187 | int interval = (int)(swo->min_swap_time/val); 1188 | if (interval < swo->limits[0]) { 1189 | interval = swo->limits[0]; 1190 | } 1191 | if (interval > swo->limits[1]) { 1192 | interval = swo->limits[1]; 1193 | } 1194 | swo->prev_intervals[swo->cur_pos]=interval; 1195 | idx = swo->cur_pos + swo->measure_frames_tot - swo->measure_frames_avg + 1; 1196 | for (i=0; i<(swo->measure_frames_avg-1); i++) { 1197 | if (idx >= swo->measure_frames_tot) { 1198 | idx -= swo->measure_frames_tot; 1199 | } 1200 | interval += swo->prev_intervals[idx++]; 1201 | } 1202 | swo->swapbuffers = interval / swo->measure_frames_avg; 1203 | 1204 | /*printf("XXX %d %d cpu: %lu, gpu: %lu\n", swo->swapbuffers, interval, cpu, gpu);*/ 1205 | } 1206 | do_swap = (++swo->swapbuffer_cnt >= swo->swapbuffers); 1207 | return do_swap; 1208 | } 1209 | 1210 | static void 1211 | swapbuffer_omission_swap_skipped(GH_swapbuffer_omission_t *swo) 1212 | { 1213 | switch (swo->flush_mode) { 1214 | case 1: 1215 | GH_glFlush(); 1216 | break; 1217 | case 2: 1218 | GH_glFinish(); 1219 | break; 1220 | default: 1221 | (void)0; /* nop */ 1222 | } 1223 | } 1224 | 1225 | static void 1226 | swapbuffer_omission_swap_finished(GH_swapbuffer_omission_t *swo, int did_swap) 1227 | { 1228 | if (did_swap) { 1229 | swo->swapbuffer_cnt = 0; 1230 | } 1231 | if (swo->min_swap_time > 0) { 1232 | timestamp_set(&swo->prev_frame_ts[swo->cur_pos][0],&swo->prev_frames[swo->cur_pos][0], GH_FRAMETIME_CPU_GPU); 1233 | } 1234 | } 1235 | 1236 | static void 1237 | swapbuffer_omission_destroy(GH_swapbuffer_omission_t *swo) 1238 | { 1239 | int i; 1240 | for (i=0; iprev_frame_ts[i][0]); 1242 | timestamp_cleanup(&swo->prev_frame_ts[i][1]); 1243 | } 1244 | } 1245 | 1246 | /*************************************************************************** 1247 | * GL context tracking * 1248 | ***************************************************************************/ 1249 | 1250 | typedef struct gl_context_ceation_opts_s { 1251 | pthread_mutex_t mutex; 1252 | unsigned int flags; 1253 | int force_version[2]; 1254 | int force_version_min[2]; 1255 | int force_version_max[2]; 1256 | int force_flags_on; 1257 | int force_flags_off; 1258 | int force_profile_on; 1259 | int force_profile_off; 1260 | int force_no_error; 1261 | } gl_context_creation_opts_t; 1262 | 1263 | /* flag bits */ 1264 | #define GH_GLCTX_CREATE_INITIALIZED 0x1 1265 | #define GH_GLCTX_COMPAT_IF_LEGACY 0x2 1266 | 1267 | typedef struct gl_context_s { 1268 | GLXContext ctx; 1269 | GLXDrawable draw; 1270 | GLXDrawable read; 1271 | unsigned int flags; 1272 | int inject_swapinterval; 1273 | unsigned int num; 1274 | struct gl_context_s *next; 1275 | GH_frametimes frametimes; 1276 | GH_latency latency; 1277 | GH_swapbuffer_omission_t swapbuffer_omission; 1278 | useconds_t swap_sleep_usecs; 1279 | GLDEBUGPROC original_debug_callback; 1280 | GLDEBUGPROCAMD original_debug_callback_AMD; 1281 | const GLvoid* original_debug_callback_user_ptr; 1282 | GLvoid* original_debug_callback_AMD_user_ptr; 1283 | } gl_context_t; 1284 | 1285 | /* flag bits */ 1286 | #define GH_GL_CURRENT 0x1 1287 | #define GH_GL_NEVER_CURRENT 0x2 1288 | #define GH_GL_INTERCEPT_DEBUG 0x4 1289 | #define GH_GL_INJECT_DEBUG 0x8 1290 | 1291 | static gl_context_creation_opts_t ctx_creation_opts = { 1292 | .mutex = PTHREAD_MUTEX_INITIALIZER, 1293 | .flags = 0, 1294 | .force_version = {-1, -1}, 1295 | .force_version_min = {-1, -1}, 1296 | .force_version_max = {-1, -1}, 1297 | .force_flags_on = 0, 1298 | .force_flags_off = 0, 1299 | .force_profile_on = 0, 1300 | .force_profile_off = 0, 1301 | .force_no_error = -1, 1302 | }; 1303 | static gl_context_t * volatile ctx_list=NULL; 1304 | static volatile int ctx_counter=0; 1305 | static pthread_mutex_t ctx_mutex=PTHREAD_MUTEX_INITIALIZER; 1306 | static pthread_key_t ctx_current; 1307 | 1308 | /* forward declarations */ 1309 | static void APIENTRY 1310 | GH_debug_callback(GLenum source, GLenum type, GLuint id, GLenum severity, 1311 | GLsizei length, const GLchar *message, const GLvoid* userParam); 1312 | static void APIENTRY 1313 | GH_debug_callback_AMD(GLuint id, GLenum category, GLenum severity, 1314 | GLsizei length, const GLchar *message, GLvoid* userParam); 1315 | 1316 | static void 1317 | add_ctx(gl_context_t *glc) 1318 | { 1319 | pthread_mutex_lock(&ctx_mutex); 1320 | glc->next=ctx_list; 1321 | ctx_list=glc; 1322 | pthread_mutex_unlock(&ctx_mutex); 1323 | } 1324 | 1325 | static gl_context_t * 1326 | create_ctx(GLXContext ctx, unsigned int num) 1327 | { 1328 | gl_context_t *glc; 1329 | 1330 | glc=malloc(sizeof(*glc)); 1331 | if (glc) { 1332 | glc->ctx=ctx; 1333 | glc->draw=None; 1334 | glc->read=None; 1335 | glc->inject_swapinterval=GH_SWAP_DONT_SET; 1336 | glc->flags=GH_GL_NEVER_CURRENT; 1337 | glc->num=num; 1338 | glc->original_debug_callback=(GLDEBUGPROC)NULL; 1339 | glc->original_debug_callback_AMD=(GLDEBUGPROCAMD)NULL; 1340 | glc->original_debug_callback_user_ptr=NULL; 1341 | glc->original_debug_callback_AMD_user_ptr=NULL; 1342 | 1343 | glc->swap_sleep_usecs=0; 1344 | frametimes_init(&glc->frametimes, GH_FRAMETIME_NONE, 0, 0, 0, num); 1345 | latency_init(&glc->latency, GH_LATENCY_NOP, 0, 1000000, 0, 0); 1346 | swapbuffer_omission_init(&glc->swapbuffer_omission); 1347 | } 1348 | return glc; 1349 | } 1350 | 1351 | static void 1352 | destroy_ctx(gl_context_t *glc) 1353 | { 1354 | if (glc) { 1355 | frametimes_destroy(&glc->frametimes); 1356 | latency_destroy(&glc->latency); 1357 | swapbuffer_omission_destroy(&glc->swapbuffer_omission); 1358 | free(glc); 1359 | } 1360 | } 1361 | 1362 | static gl_context_t * 1363 | find_ctx(GLXContext ctx) 1364 | { 1365 | gl_context_t *glc; 1366 | 1367 | pthread_mutex_lock(&ctx_mutex); 1368 | for (glc=ctx_list; glc; glc=glc->next) 1369 | if (glc->ctx == ctx) 1370 | break; 1371 | pthread_mutex_unlock(&ctx_mutex); 1372 | return glc; 1373 | } 1374 | 1375 | static void 1376 | remove_ctx(GLXContext ctx) 1377 | { 1378 | gl_context_t *glc,*prev=NULL; 1379 | 1380 | pthread_mutex_lock(&ctx_mutex); 1381 | for (glc=ctx_list; glc; glc=glc->next) { 1382 | if (glc->ctx == ctx) 1383 | break; 1384 | prev=glc; 1385 | } 1386 | if (glc) { 1387 | if (prev) 1388 | prev->next=glc->next; 1389 | else 1390 | ctx_list=glc->next; 1391 | } 1392 | pthread_mutex_unlock(&ctx_mutex); 1393 | if (glc) { 1394 | destroy_ctx(glc); 1395 | } 1396 | } 1397 | 1398 | static void 1399 | read_config(gl_context_t *glc) 1400 | { 1401 | glc->inject_swapinterval=get_envi("GH_INJECT_SWAPINTERVAL", GH_SWAP_DONT_SET); 1402 | if (get_envi("GH_GL_DEBUG_OUTPUT",0)) { 1403 | glc->flags |= GH_GL_INTERCEPT_DEBUG; 1404 | } 1405 | if (get_envi("GH_GL_INJECT_DEBUG_OUTPUT",0)) { 1406 | glc->flags |= GH_GL_INJECT_DEBUG; 1407 | } 1408 | } 1409 | 1410 | static void 1411 | create_context(GLXContext ctx) 1412 | { 1413 | gl_context_t *glc; 1414 | unsigned int ctx_num; 1415 | 1416 | GH_verbose(GH_MSG_DEBUG, "created ctx %p\n",ctx); 1417 | 1418 | pthread_mutex_lock(&ctx_mutex); 1419 | ctx_num=ctx_counter++; 1420 | if (!ctx_num) { 1421 | pthread_key_create(&ctx_current, NULL); 1422 | pthread_setspecific(ctx_current, NULL); 1423 | /* query the function pointers for the standard functions 1424 | * which might be often called ... */ 1425 | GH_GET_PTR_GL(glXSwapBuffers); 1426 | GH_GET_PTR_GL(glXMakeCurrent); 1427 | GH_GET_PTR_GL(glXMakeContextCurrent); 1428 | GH_GET_PTR_GL(glXMakeCurrentReadSGI); 1429 | GH_GET_PTR_GL(glFlush); 1430 | GH_GET_PTR_GL(glFinish); 1431 | } 1432 | pthread_mutex_unlock(&ctx_mutex); 1433 | 1434 | glc=create_ctx(ctx, ctx_num); 1435 | if (glc) { 1436 | read_config(glc); 1437 | /* add to our list */ 1438 | add_ctx(glc); 1439 | } else { 1440 | GH_verbose(GH_MSG_ERROR, "out of memory\n"); 1441 | } 1442 | 1443 | } 1444 | 1445 | static void 1446 | destroy_context(GLXContext ctx) 1447 | { 1448 | GH_verbose(GH_MSG_INFO, "destroyed ctx %p\n",ctx); 1449 | remove_ctx(ctx); 1450 | } 1451 | 1452 | static void 1453 | make_current(GLXContext ctx, Display *dpy, GLXDrawable draw, GLXDrawable read) 1454 | { 1455 | gl_context_t *glc; 1456 | 1457 | glc=(gl_context_t*)pthread_getspecific(ctx_current); 1458 | if (glc) { 1459 | /* old context */ 1460 | glc->flags &= ~GH_GL_CURRENT; 1461 | GH_verbose(GH_MSG_DEBUG, "unbound context %p\n",glc->ctx); 1462 | } 1463 | 1464 | if (ctx) { 1465 | glc=find_ctx(ctx); 1466 | 1467 | if (glc == NULL) { 1468 | GH_verbose(GH_MSG_WARNING, "app tried to make current non-existing context %p\n",ctx); 1469 | } else { 1470 | glc->draw=draw; 1471 | glc->read=read; 1472 | glc->flags |= GH_GL_CURRENT; 1473 | GH_verbose(GH_MSG_DEBUG, "made current context %p\n",ctx); 1474 | if (glc->flags & GH_GL_NEVER_CURRENT) { 1475 | unsigned int ft_delay=get_envui("GH_FRAMETIME_DELAY", 10); 1476 | unsigned int ft_frames=get_envui("GH_FRAMETIME_FRAMES", 1000); 1477 | GH_frametime_mode ft_mode=(GH_frametime_mode)get_envi("GH_FRAMETIME", (int)GH_FRAMETIME_NONE); 1478 | int latency=get_envi("GH_LATENCY", GH_LATENCY_NOP); 1479 | int latency_manual_wait=get_envi("GH_LATENCY_MANUAL_WAIT", -1); 1480 | unsigned int latency_gl_wait_timeout=get_envui("GH_LATENCY_GL_WAIT_TIMEOUT_USECS", 1000000); 1481 | unsigned int latency_gl_wait_interval=get_envui("GH_LATENCY_GL_WAIT_USECS", 0); 1482 | unsigned int latency_self_wait_interval=get_envui("GH_LATENCY_WAIT_USECS", 0); 1483 | /* made current for the first time */ 1484 | glc->flags &= ~ GH_GL_NEVER_CURRENT; 1485 | 1486 | glc->swap_sleep_usecs=(useconds_t)get_envui("GH_SWAP_SLEEP_USECS",0); 1487 | frametimes_init(&glc->frametimes, ft_mode, ft_delay, GH_FRAMETIME_COUNT, ft_frames, glc->num); 1488 | frametimes_init_base(&glc->frametimes); 1489 | latency_init(&glc->latency, latency, latency_manual_wait, latency_gl_wait_timeout, latency_gl_wait_interval, latency_self_wait_interval); 1490 | swapbuffer_omission_init_gl(&glc->swapbuffer_omission); 1491 | if (glc->inject_swapinterval != GH_SWAP_DONT_SET) { 1492 | GH_GET_PTR_GL(glXSwapIntervalEXT); 1493 | if (GH_glXSwapIntervalEXT) { 1494 | GH_verbose(GH_MSG_INFO, "injecting swap interval: %d\n", 1495 | glc->inject_swapinterval); 1496 | GH_glXSwapIntervalEXT(dpy, glc->draw, glc->inject_swapinterval); 1497 | } else { 1498 | GH_GET_PTR_GL(glXSwapIntervalSGI); 1499 | if (GH_glXSwapIntervalEXT) { 1500 | GH_verbose(GH_MSG_INFO, "injecting swap interval: %d\n", 1501 | glc->inject_swapinterval); 1502 | GH_glXSwapIntervalSGI(glc->inject_swapinterval); 1503 | } 1504 | } 1505 | } 1506 | if (glc->flags & GH_GL_INJECT_DEBUG) { 1507 | GH_GET_PTR_GL(glDebugMessageCallback); 1508 | if (GH_glDebugMessageCallback) { 1509 | GH_glDebugMessageCallback(GH_debug_callback, glc); 1510 | GH_verbose(GH_MSG_INFO, "injecting debug callback [core]\n"); 1511 | } else { 1512 | GH_GET_PTR_GL(glDebugMessageCallbackARB); 1513 | if (GH_glDebugMessageCallbackARB) { 1514 | GH_glDebugMessageCallbackARB(GH_debug_callback, glc); 1515 | GH_verbose(GH_MSG_INFO, "injecting debug callback [ARB]\n"); 1516 | } else { 1517 | GH_GET_PTR_GL(glDebugMessageCallbackKHR); 1518 | if (GH_glDebugMessageCallbackKHR) { 1519 | GH_glDebugMessageCallbackKHR(GH_debug_callback, glc); 1520 | GH_verbose(GH_MSG_INFO, "injecting debug callback [KHR]\n"); 1521 | } else { 1522 | GH_GET_PTR_GL(glDebugMessageCallbackAMD); 1523 | if (GH_glDebugMessageCallbackAMD) { 1524 | GH_glDebugMessageCallbackAMD(GH_debug_callback_AMD, glc); 1525 | GH_verbose(GH_MSG_INFO, "injecting debug callback [AMD]\n"); 1526 | } else { 1527 | GH_verbose(GH_MSG_WARNING, "failed ot iject debug message callback\n"); 1528 | } 1529 | } 1530 | } 1531 | } 1532 | } 1533 | } 1534 | } 1535 | } else { 1536 | glc=NULL; 1537 | } 1538 | 1539 | pthread_setspecific(ctx_current, glc); 1540 | } 1541 | 1542 | /**************************************************************************** 1543 | * GL DEBUG MESSAGES * 1544 | ****************************************************************************/ 1545 | 1546 | /* Newer versions of the GL support the generation of human-readable messages 1547 | for GL errors, performance warnings and hints. These messages are 1548 | forwarded to a debug callback which has to be registered with the GL 1549 | context. Debug output may only be available in special debug context... */ 1550 | 1551 | /* translate the debug message "source" enum to human-readable string */ 1552 | static const char * 1553 | translateDebugSourceEnum(GLenum source) 1554 | { 1555 | const char *s; 1556 | 1557 | switch (source) { 1558 | case GL_DEBUG_SOURCE_API: 1559 | s="API"; 1560 | break; 1561 | case GL_DEBUG_SOURCE_WINDOW_SYSTEM: 1562 | s="window system"; 1563 | break; 1564 | case GL_DEBUG_SOURCE_SHADER_COMPILER: 1565 | s="shader compiler"; 1566 | break; 1567 | case GL_DEBUG_SOURCE_THIRD_PARTY: 1568 | s="3rd party"; 1569 | break; 1570 | case GL_DEBUG_SOURCE_APPLICATION: 1571 | s="application"; 1572 | break; 1573 | case GL_DEBUG_SOURCE_OTHER: 1574 | s="other"; 1575 | break; 1576 | default: 1577 | s="[UNKNOWN SOURCE]"; 1578 | } 1579 | 1580 | return s; 1581 | } 1582 | 1583 | /* translate the debug message "category" enum to human-readable string */ 1584 | static const char * 1585 | translateDebugCategoryEnum(GLenum cat) 1586 | { 1587 | const char *s; 1588 | 1589 | switch (cat) { 1590 | case GL_DEBUG_CATEGORY_API_ERROR_AMD: 1591 | s="API error"; 1592 | break; 1593 | case GL_DEBUG_CATEGORY_WINDOW_SYSTEM_AMD: 1594 | s="window system"; 1595 | break; 1596 | case GL_DEBUG_CATEGORY_DEPRECATION_AMD: 1597 | s="deprecation"; 1598 | break; 1599 | case GL_DEBUG_CATEGORY_UNDEFINED_BEHAVIOR_AMD: 1600 | s="undefined behavior"; 1601 | break; 1602 | case GL_DEBUG_CATEGORY_PERFORMANCE_AMD: 1603 | s="performance"; 1604 | break; 1605 | case GL_DEBUG_CATEGORY_SHADER_COMPILER_AMD: 1606 | s="shader compiler"; 1607 | break; 1608 | case GL_DEBUG_CATEGORY_APPLICATION_AMD: 1609 | s="application"; 1610 | break; 1611 | case GL_DEBUG_CATEGORY_OTHER_AMD: 1612 | s="other"; 1613 | break; 1614 | default: 1615 | s="[UNKNOWN CATEGORY]"; 1616 | } 1617 | 1618 | return s; 1619 | } 1620 | 1621 | /* translate the debug message "type" enum to human-readable string */ 1622 | static const char * 1623 | translateDebugTypeEnum(GLenum type) 1624 | { 1625 | const char *s; 1626 | 1627 | switch (type) { 1628 | case GL_DEBUG_TYPE_ERROR: 1629 | s="error"; 1630 | break; 1631 | case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: 1632 | s="deprecated"; 1633 | break; 1634 | case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: 1635 | s="undefined behavior"; 1636 | break; 1637 | case GL_DEBUG_TYPE_PORTABILITY: 1638 | s="portability"; 1639 | break; 1640 | case GL_DEBUG_TYPE_PERFORMANCE: 1641 | s="performance"; 1642 | break; 1643 | case GL_DEBUG_TYPE_OTHER: 1644 | s="other"; 1645 | break; 1646 | default: 1647 | s="[UNKNOWN TYPE]"; 1648 | } 1649 | return s; 1650 | } 1651 | 1652 | /* translate the debug message "xeverity" enum to human-readable string */ 1653 | static const char * 1654 | translateDebugSeverityEnum(GLenum severity) 1655 | { 1656 | const char *s; 1657 | 1658 | switch (severity) { 1659 | case GL_DEBUG_SEVERITY_HIGH: 1660 | s="high"; 1661 | break; 1662 | case GL_DEBUG_SEVERITY_MEDIUM: 1663 | s="medium"; 1664 | break; 1665 | case GL_DEBUG_SEVERITY_LOW: 1666 | s="low"; 1667 | break; 1668 | case GL_DEBUG_SEVERITY_NOTIFICATION: 1669 | s="notification"; 1670 | break; 1671 | default: 1672 | s="[UNKNOWN SEVERITY]"; 1673 | } 1674 | 1675 | return s; 1676 | } 1677 | 1678 | /* our debug callback */ 1679 | static void APIENTRY 1680 | GH_debug_callback(GLenum source, GLenum type, GLuint id, GLenum severity, 1681 | GLsizei length, const GLchar *message, const GLvoid* userParam) 1682 | { 1683 | const gl_context_t *glc=(const gl_context_t*)userParam; 1684 | if (glc) { 1685 | GH_verbose(GH_MSG_INFO, "GLDEBUG: %s %s %s [0x%x]: %s\n", 1686 | translateDebugSourceEnum(source), 1687 | translateDebugTypeEnum(type), 1688 | translateDebugSeverityEnum(severity), 1689 | id, message); 1690 | if (glc->original_debug_callback) { 1691 | glc->original_debug_callback(source, type, id, severity, 1692 | length, message, glc->original_debug_callback_user_ptr); 1693 | } 1694 | } 1695 | } 1696 | 1697 | /* our debug callback AMD */ 1698 | static void APIENTRY 1699 | GH_debug_callback_AMD(GLuint id, GLenum category, GLenum severity, 1700 | GLsizei length, const GLchar *message, GLvoid* userParam) 1701 | { 1702 | const gl_context_t *glc=(const gl_context_t*)userParam; 1703 | 1704 | if (glc) { 1705 | GH_verbose(GH_MSG_INFO, "GLDEBUG[AMD]: %s %s %s [0x%x]: %s\n", 1706 | translateDebugCategoryEnum(category), 1707 | translateDebugSeverityEnum(severity), 1708 | id, message); 1709 | if (glc->original_debug_callback_AMD) { 1710 | glc->original_debug_callback_AMD(id, category, severity, 1711 | length, message, 1712 | glc->original_debug_callback_AMD_user_ptr); 1713 | } 1714 | } 1715 | } 1716 | 1717 | /*************************************************************************** 1718 | * GL context creation overrides * 1719 | ***************************************************************************/ 1720 | 1721 | static void context_creation_opts_init(gl_context_creation_opts_t *opts) 1722 | { 1723 | opts->force_profile_on = 0; 1724 | opts->force_profile_off = 0; 1725 | opts->force_flags_on = 0; 1726 | opts->force_flags_off = 0; 1727 | opts->force_no_error = -1; 1728 | 1729 | if (get_envi("GH_FORCE_GL_CONTEXT_PROFILE_CORE", 0)) { 1730 | opts->force_profile_on = GLX_CONTEXT_CORE_PROFILE_BIT_ARB; 1731 | opts->force_profile_off = GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; 1732 | } 1733 | 1734 | int value = get_envi("GH_FORCE_GL_CONTEXT_PROFILE_COMPAT", 0); 1735 | if (value == 1) { 1736 | opts->force_profile_on = GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; 1737 | opts->force_profile_off = GLX_CONTEXT_CORE_PROFILE_BIT_ARB; 1738 | } else if (value == 2) { 1739 | opts->flags |= GH_GLCTX_COMPAT_IF_LEGACY; 1740 | } 1741 | if (get_envi("GH_FORCE_GL_CONTEXT_FLAGS_NO_FORWARD_COMPAT", 0)) { 1742 | opts->force_flags_on &= ~GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB; 1743 | opts->force_flags_off |= GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB; 1744 | } 1745 | if (get_envi("GH_FORCE_GL_CONTEXT_FLAGS_NO_DEBUG", 0)) { 1746 | opts->force_flags_on &= ~GLX_CONTEXT_DEBUG_BIT_ARB; 1747 | opts->force_flags_off |= GLX_CONTEXT_DEBUG_BIT_ARB; 1748 | } 1749 | if (get_envi("GH_FORCE_GL_CONTEXT_FLAGS_FORWARD_COMPAT", 0)) { 1750 | opts->force_flags_on |= GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB; 1751 | opts->force_flags_off &= ~GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB; 1752 | } 1753 | if (get_envi("GH_FORCE_GL_CONTEXT_FLAGS_DEBUG", 0)) { 1754 | opts->force_flags_on |= GLX_CONTEXT_DEBUG_BIT_ARB; 1755 | opts->force_flags_off &= ~GLX_CONTEXT_DEBUG_BIT_ARB; 1756 | } 1757 | if (get_envi("GH_FORCE_GL_CONTEXT_FLAGS_NO_ERROR", 0)) { 1758 | opts->force_no_error = GL_TRUE; 1759 | } 1760 | if (get_envi("GH_FORCE_GL_CONTEXT_FLAGS_ERROR", 0)) { 1761 | opts->force_no_error = GL_FALSE; 1762 | } 1763 | 1764 | opts->force_version[0] = get_envi("GH_FORCE_GL_VERSION_MAJOR", opts->force_version[0]); 1765 | opts->force_version[1] = get_envi("GH_FORCE_GL_VERSION_MINOR", opts->force_version[1]); 1766 | 1767 | opts->force_version_min[0] = get_envi("GH_FORCE_MIN_GL_VERSION_MAJOR", opts->force_version_min[0]); 1768 | opts->force_version_min[1] = get_envi("GH_FORCE_MIN_GL_VERSION_MINOR", opts->force_version_min[1]); 1769 | 1770 | opts->force_version_max[0] = get_envi("GH_FORCE_MAX_GL_VERSION_MAJOR", opts->force_version_max[0]); 1771 | opts->force_version_max[1] = get_envi("GH_FORCE_MAX_GL_VERSION_MINOR", opts->force_version_max[1]); 1772 | 1773 | opts->force_flags_on = get_envi("GH_FORCE_GL_CONTEXT_FLAGS_ON", opts->force_flags_on); 1774 | opts->force_flags_off = get_envi("GH_FORCE_GL_CONTEXT_FLAGS_OFF", opts->force_flags_off); 1775 | 1776 | opts->force_profile_on = get_envi("GH_FORCE_GL_CONTEXT_PROFILE_MASK_ON", opts->force_profile_on); 1777 | opts->force_profile_off = get_envi("GH_FORCE_GL_CONTEXT_PROFILE_MASK_OFF", opts->force_profile_off); 1778 | 1779 | GH_verbose(GH_MSG_DEBUG, "got GL override options: force version %d.%d, min %d.%d, max %d.%d, flags +0x%x -0x%x, profile flags: +0x%x -0x%x, no error: %d\n", 1780 | opts->force_version[0],opts->force_version[1], 1781 | opts->force_version_min[0], opts->force_version_min[1], 1782 | opts->force_version_max[0], opts->force_version_max[1], 1783 | (unsigned)opts->force_flags_on, (unsigned)opts->force_flags_off, 1784 | (unsigned)opts->force_profile_on, (unsigned)opts->force_profile_off, 1785 | opts->force_no_error); 1786 | } 1787 | 1788 | static int need_creation_override(const gl_context_creation_opts_t *opts) 1789 | { 1790 | if (opts->force_version[0] >= 0 || opts->force_version[1] >= 0) { 1791 | return 1; 1792 | } 1793 | if (opts->force_version_min[0] >= 0 || opts->force_version_min[1] >= 0) { 1794 | return 1; 1795 | } 1796 | if (opts->force_version_max[0] >= 0 || opts->force_version_max[1] >= 0) { 1797 | return 1; 1798 | } 1799 | if (opts->force_flags_on || opts->force_flags_off) { 1800 | return 2; 1801 | } 1802 | if (opts->force_profile_on || opts->force_profile_off) { 1803 | return 3; 1804 | } 1805 | if (opts->force_no_error >= 0) { 1806 | return 4; 1807 | } 1808 | 1809 | return 0; 1810 | } 1811 | 1812 | static int* get_override_attributes(gl_context_creation_opts_t *opts, const int *attribs) 1813 | { 1814 | const int our_count=5; 1815 | int count = 0; 1816 | int additional_count = 0; 1817 | int req_version[2] = {1,0}; 1818 | int req_profile_mask = GLX_CONTEXT_CORE_PROFILE_BIT_ARB; 1819 | int req_no_error = -1; 1820 | int req_flags = 0; 1821 | int legacy = 0; 1822 | int *attr_override; 1823 | int pos=0; 1824 | int i; 1825 | 1826 | GH_verbose(GH_MSG_INFO, "overriding context attributes for creation\n"); 1827 | if (attribs) { 1828 | while (attribs[2*count] != None) { 1829 | unsigned int name = (unsigned)attribs[2*count]; 1830 | int value = attribs[2*count + 1]; 1831 | GH_verbose(GH_MSG_INFO, "originally requested attrib: 0x%x = %d\n", name, value); 1832 | switch(name) { 1833 | case GLX_CONTEXT_MAJOR_VERSION_ARB: 1834 | req_version[0] = value; 1835 | break; 1836 | case GLX_CONTEXT_MINOR_VERSION_ARB: 1837 | req_version[1] = value; 1838 | break; 1839 | case GLX_CONTEXT_PROFILE_MASK_ARB: 1840 | req_profile_mask = value; 1841 | break; 1842 | case GLX_CONTEXT_FLAGS_ARB: 1843 | req_flags = value; 1844 | break; 1845 | case GLX_CONTEXT_OPENGL_NO_ERROR_ARB: 1846 | req_no_error = value; 1847 | break; 1848 | default: 1849 | additional_count++; 1850 | } 1851 | count++; 1852 | } 1853 | } 1854 | 1855 | legacy = ((req_version[0] < 3) || ( (req_version[0] == 3) && req_version[1] < 2) ); 1856 | if (legacy && (opts->flags & GH_GLCTX_COMPAT_IF_LEGACY)) { 1857 | GH_verbose(GH_MSG_INFO, "overriding legacy context to compat profile\n"); 1858 | req_profile_mask &= ~GLX_CONTEXT_CORE_PROFILE_BIT_ARB; 1859 | req_profile_mask |= GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; 1860 | } 1861 | 1862 | if (opts->force_version_min[0] >= 0) { 1863 | if (req_version[0] < opts->force_version_min[0]) { 1864 | GH_verbose(GH_MSG_INFO, "overriding context major version from %d to %d [min]\n", 1865 | req_version[0], opts->force_version_min[0]); 1866 | req_version[0] = opts->force_version_min[0]; 1867 | } 1868 | } 1869 | if (opts->force_version_min[1] >= 0) { 1870 | int override=0; 1871 | if (opts->force_version_min[0] >= 0) { 1872 | if (req_version[0] <= opts->force_version_min[0]) { 1873 | if (req_version[1] < opts->force_version_min[1]) { 1874 | override = 1; 1875 | } 1876 | } 1877 | } else { 1878 | if (req_version[1] < opts->force_version_min[1]) { 1879 | override = 1; 1880 | } 1881 | } 1882 | 1883 | if (override) { 1884 | GH_verbose(GH_MSG_INFO, "overriding context minor version from %d to %d [min]\n", 1885 | req_version[1], opts->force_version_min[1]); 1886 | req_version[1] = opts->force_version_min[1]; 1887 | } 1888 | } 1889 | 1890 | if (opts->force_version_max[0] >= 0) { 1891 | if (req_version[0] > opts->force_version_max[0]) { 1892 | GH_verbose(GH_MSG_INFO, "overriding context major version from %d to %d [max]\n", 1893 | req_version[0], opts->force_version_max[0]); 1894 | req_version[0] = opts->force_version_max[0]; 1895 | } 1896 | } 1897 | if (opts->force_version_max[1] >= 0) { 1898 | int override=0; 1899 | if (opts->force_version_max[0] >= 0) { 1900 | if (req_version[0] >= opts->force_version_max[0]) { 1901 | if (req_version[1] > opts->force_version_max[1]) { 1902 | override = 1; 1903 | } 1904 | } 1905 | } else { 1906 | if (req_version[1] > opts->force_version_max[1]) { 1907 | override = 1; 1908 | } 1909 | } 1910 | 1911 | if (override) { 1912 | GH_verbose(GH_MSG_INFO, "overriding context minor version from %d to %d [max]\n", 1913 | req_version[1], opts->force_version_max[1]); 1914 | req_version[1] = opts->force_version_max[1]; 1915 | } 1916 | } 1917 | 1918 | if (opts->force_version[0] >= 0) { 1919 | GH_verbose(GH_MSG_INFO, "overriding context major version from %d to %d\n", 1920 | req_version[0], opts->force_version[0]); 1921 | req_version[0] = opts->force_version[0]; 1922 | } 1923 | if (opts->force_version[1] >= 0) { 1924 | GH_verbose(GH_MSG_INFO, "overriding context minor version from %d to %d\n", 1925 | req_version[1], opts->force_version[1]); 1926 | req_version[1] = opts->force_version[1]; 1927 | } 1928 | if (opts->force_flags_on || opts->force_flags_off) { 1929 | int new_flags = (req_flags | opts->force_flags_on)&(~opts->force_flags_off); 1930 | GH_verbose(GH_MSG_INFO, "overriding context flags from 0x%x to 0x%x\n", 1931 | (unsigned)req_flags, (unsigned)new_flags); 1932 | req_flags = new_flags; 1933 | } 1934 | if (opts->force_profile_on || opts->force_profile_off) { 1935 | int new_profile = (req_profile_mask | opts->force_profile_on)&(~opts->force_profile_off); 1936 | GH_verbose(GH_MSG_INFO, "overriding context profile mask from 0x%x to 0x%x\n", 1937 | (unsigned)req_profile_mask, (unsigned)new_profile); 1938 | req_profile_mask = new_profile; 1939 | } 1940 | if (opts->force_no_error >= 0) { 1941 | GH_verbose(GH_MSG_INFO, "overriding context NO_ERROR behavior from %d to %d\n", 1942 | req_no_error, opts->force_no_error); 1943 | req_no_error = opts->force_no_error; 1944 | } 1945 | 1946 | attr_override = malloc(sizeof(*attr_override) * ((additional_count + our_count) * 2 + 2)); 1947 | if (!attr_override) { 1948 | GH_verbose(GH_MSG_ERROR, "overriding context attributes not possible: out of memory\n"); 1949 | return NULL; 1950 | } 1951 | 1952 | GH_verbose(GH_MSG_INFO, "requesting GL %d.%d flags: 0x%x, profile: 0x%x\n", 1953 | req_version[0], req_version[1], (unsigned)req_flags, (unsigned)req_profile_mask); 1954 | attr_override[pos++] = GLX_CONTEXT_MAJOR_VERSION_ARB; 1955 | attr_override[pos++] = req_version[0]; 1956 | attr_override[pos++] = GLX_CONTEXT_MINOR_VERSION_ARB; 1957 | attr_override[pos++] = req_version[1]; 1958 | attr_override[pos++] = GLX_CONTEXT_PROFILE_MASK_ARB; 1959 | attr_override[pos++] = req_profile_mask; 1960 | attr_override[pos++] = GLX_CONTEXT_FLAGS_ARB; 1961 | attr_override[pos++] = req_flags; 1962 | if (req_no_error >= 0) { 1963 | attr_override[pos++] = GLX_CONTEXT_OPENGL_NO_ERROR_ARB; 1964 | attr_override[pos++] = req_no_error; 1965 | } 1966 | 1967 | for (i=0; i 0) { 2009 | int i; 2010 | for (i=0; ivisualid) { 2016 | GH_verbose(GH_MSG_INFO, "found fbconfig %d for visual ID %d\n", i, value); 2017 | *cfg = cfgs[i]; 2018 | new_cfg=cfg; 2019 | break; 2020 | } 2021 | } else { 2022 | GH_verbose(GH_MSG_WARNING, "glxGerFBConfigAttrib failed!\n"); 2023 | } 2024 | } 2025 | } 2026 | 2027 | if (cfgs) { 2028 | GH_XFree(cfgs); 2029 | } 2030 | 2031 | return new_cfg; 2032 | } 2033 | 2034 | static GLXContext override_create_context(Display *dpy, XVisualInfo *vis, const GLXFBConfig *fbconfig, GLXContext shareList, Bool direct, const int *attribs) 2035 | { 2036 | GLXFBConfig internal_fbconfig; 2037 | 2038 | pthread_mutex_lock(&ctx_creation_opts.mutex); 2039 | if (!(ctx_creation_opts.flags & GH_GLCTX_CREATE_INITIALIZED)) { 2040 | context_creation_opts_init(&ctx_creation_opts); 2041 | ctx_creation_opts.flags |= GH_GLCTX_CREATE_INITIALIZED; 2042 | } 2043 | pthread_mutex_unlock(&ctx_creation_opts.mutex); 2044 | 2045 | if (need_creation_override(&ctx_creation_opts)) { 2046 | GLXContext ctx = NULL; 2047 | int *attribs_override = get_override_attributes(&ctx_creation_opts, attribs); 2048 | if (!attribs_override) { 2049 | GH_verbose(GH_MSG_WARNING, "failed to generate context creation override attributes!\n"); 2050 | return NULL; 2051 | } 2052 | if (!fbconfig) { 2053 | if (!vis) { 2054 | GH_verbose(GH_MSG_WARNING, "create context attempt without Visual and FBConfig!\n"); 2055 | free(attribs_override); 2056 | return NULL; 2057 | } 2058 | /* find FBConfig vor Visual ... */ 2059 | fbconfig=get_fbconfig_for_visual(dpy, vis, &internal_fbconfig); 2060 | if (!fbconfig) { 2061 | GH_verbose(GH_MSG_WARNING, "create context: failed to get fbconfig for visual!\n"); 2062 | free(attribs_override); 2063 | return NULL; 2064 | } 2065 | } 2066 | GH_GET_GL_PROC(glXCreateContextAttribsARB); 2067 | if (GH_glXCreateContextAttribsARB) { 2068 | ctx=GH_glXCreateContextAttribsARB(dpy, *fbconfig, shareList, direct, attribs_override); 2069 | } else { 2070 | GH_verbose(GH_MSG_WARNING, "failed to get glXCreateContextAttribsARB\n"); 2071 | } 2072 | if (ctx == NULL) { 2073 | GH_verbose(GH_MSG_WARNING, "overridden context creation failed!\n"); 2074 | } else { 2075 | GH_verbose(GH_MSG_INFO, "created context %p with overriden attributes!\n", ctx); 2076 | } 2077 | free(attribs_override); 2078 | return ctx; 2079 | 2080 | } 2081 | return NULL; 2082 | } 2083 | 2084 | #endif /* GH_CONTEXT_TRACKING */ 2085 | 2086 | /*************************************************************************** 2087 | * SWAP INTERVAL LOGIC * 2088 | ***************************************************************************/ 2089 | 2090 | /* possible GH_SWAP_MODEs */ 2091 | typedef enum { 2092 | GH_SWAP_MODE_NOP=0, /* do not change anything */ 2093 | GH_SWAP_MODE_IGNORE, /* ignore all attempts to set swap interval */ 2094 | GH_SWAP_MODE_CLAMP, /* clamp interval to (a,b) */ 2095 | GH_SWAP_MODE_FORCE, /* force interval to a */ 2096 | GH_SWAP_MODE_DISABLE, /* force interval to 0 */ 2097 | GH_SWAP_MODE_ENABLE, /* force interval to >= 1 */ 2098 | GH_SWAP_MODE_MIN, /* force interval to >= a */ 2099 | GH_SWAP_MODE_MAX, /* force interval to <= a */ 2100 | /* always add new elements here */ 2101 | GH_SWAP_MODES_COUNT 2102 | } GH_swap_mode; 2103 | 2104 | /* possible GH_SWAP_TEAR modes */ 2105 | typedef enum { 2106 | GH_SWAP_TEAR_RAW, /* handle interval as if normal */ 2107 | GH_SWAP_TEAR_KEEP, /* keep tearing control as requested */ 2108 | GH_SWAP_TEAR_DISABLE, /* always disbale adaptive vsync */ 2109 | GH_SWAP_TEAR_ENABLE, /* enable adaptive vsync */ 2110 | GH_SWAP_TEAR_INVERT, /* could one ever need this? */ 2111 | /* always add new elements here */ 2112 | GH_SWAP_TEAR_COUNT 2113 | } GH_swap_tear; 2114 | 2115 | typedef struct { 2116 | GH_swap_mode swap_mode; 2117 | GH_swap_tear swap_tear; 2118 | int swap_param[2]; 2119 | } GH_config; 2120 | 2121 | static void GH_swap_mode_from_str(GH_config volatile *cfg, const char *str) 2122 | { 2123 | static const char *mode_str[GH_SWAP_MODES_COUNT+1]={ 2124 | "nop", 2125 | "ignore", 2126 | "clamp", 2127 | "force", 2128 | "disable", 2129 | "enable", 2130 | "min", 2131 | "max", 2132 | NULL 2133 | }; 2134 | 2135 | size_t l=0; 2136 | int idx; 2137 | char *nstr; 2138 | 2139 | cfg->swap_mode=GH_SWAP_MODE_NOP; 2140 | 2141 | if (str == NULL) 2142 | return; 2143 | 2144 | for (idx=0; mode_str[idx]; idx++) { 2145 | l=strlen(mode_str[idx]); 2146 | if (!strncmp(str, mode_str[idx], l)) { 2147 | break; 2148 | } 2149 | } 2150 | 2151 | if (idx >= 0 && idx<(int)GH_SWAP_MODES_COUNT) { 2152 | cfg->swap_mode=(GH_swap_mode)idx; 2153 | } 2154 | 2155 | str += l; 2156 | 2157 | /* read up to 2 ints as arguments */ 2158 | while(*str && !isdigit(*str)) 2159 | str++; 2160 | cfg->swap_param[0]=(int)strtol(str, &nstr, 0); 2161 | str=nstr; 2162 | while(*str && !isdigit(*str)) 2163 | str++; 2164 | cfg->swap_param[1]=(int)strtol(str, &nstr,0); 2165 | GH_verbose(GH_MSG_DEBUG, "SWAP_MODE: %d %d %d\n", 2166 | cfg->swap_mode,cfg->swap_param[0],cfg->swap_param[1]); 2167 | } 2168 | 2169 | static void GH_swap_tear_from_str(GH_config volatile *cfg, const char *str) 2170 | { 2171 | static const char *mode_str[GH_SWAP_TEAR_COUNT+1]={ 2172 | "raw", 2173 | "keep", 2174 | "disable", 2175 | "enable", 2176 | "invert", 2177 | 2178 | NULL 2179 | }; 2180 | 2181 | int idx; 2182 | 2183 | cfg->swap_tear=GH_SWAP_TEAR_KEEP; 2184 | 2185 | if (str == NULL) 2186 | return; 2187 | 2188 | for (idx=0; mode_str[idx]; idx++) { 2189 | if (!strcmp(str, mode_str[idx])) { 2190 | break; 2191 | } 2192 | } 2193 | 2194 | if (idx >= 0 && idx<(int)GH_SWAP_MODES_COUNT) { 2195 | cfg->swap_tear=(GH_swap_mode)idx; 2196 | } 2197 | } 2198 | 2199 | static int GH_swap_interval_absolute(const volatile GH_config *cfg, int interval) 2200 | { 2201 | int new_interval; 2202 | 2203 | switch(cfg->swap_mode) { 2204 | case GH_SWAP_MODE_IGNORE: 2205 | new_interval=GH_SWAP_DONT_SET; 2206 | break; 2207 | case GH_SWAP_MODE_CLAMP: 2208 | new_interval=interval; 2209 | if (new_interval < cfg->swap_param[0]) 2210 | new_interval=cfg->swap_param[0]; 2211 | if (new_interval > cfg->swap_param[1]) 2212 | interval=cfg->swap_param[1]; 2213 | break; 2214 | case GH_SWAP_MODE_FORCE: 2215 | new_interval=cfg->swap_param[0]; 2216 | break; 2217 | case GH_SWAP_MODE_DISABLE: 2218 | new_interval=0; 2219 | break; 2220 | case GH_SWAP_MODE_ENABLE: 2221 | new_interval=interval; 2222 | if (new_interval < 1) 2223 | new_interval=1; 2224 | break; 2225 | case GH_SWAP_MODE_MIN: 2226 | new_interval=interval; 2227 | if (new_interval < cfg->swap_param[0]) 2228 | new_interval=cfg->swap_param[0]; 2229 | break; 2230 | case GH_SWAP_MODE_MAX: 2231 | new_interval=interval; 2232 | if (new_interval > cfg->swap_param[0]) 2233 | new_interval=cfg->swap_param[0]; 2234 | break; 2235 | default: 2236 | new_interval=interval; 2237 | } 2238 | 2239 | GH_verbose(GH_MSG_DEBUG,"swap interval, absolute value %d -> %d\n", interval, new_interval); 2240 | return new_interval; 2241 | } 2242 | 2243 | static int GH_swap_interval_base(const volatile GH_config *cfg, int interval) 2244 | { 2245 | int sign_interval; 2246 | int abs_interval; 2247 | int new_interval; 2248 | 2249 | if (cfg->swap_tear == GH_SWAP_TEAR_RAW) { 2250 | sign_interval=0; 2251 | abs_interval=interval; /* moght be negative */ 2252 | } else if (interval < 0) { 2253 | sign_interval=-1; 2254 | abs_interval=-interval; 2255 | } else { 2256 | sign_interval=1; 2257 | abs_interval=interval; 2258 | } 2259 | 2260 | abs_interval=GH_swap_interval_absolute(cfg, abs_interval); 2261 | if (abs_interval == GH_SWAP_DONT_SET) { 2262 | GH_verbose(GH_MSG_INFO,"swap interval %d setting ignored\n", 2263 | interval); 2264 | return GH_SWAP_DONT_SET; 2265 | } 2266 | 2267 | /* set sign based on GH_SWAP_TEAR mode */ 2268 | switch (cfg->swap_tear) { 2269 | case GH_SWAP_TEAR_KEEP: 2270 | new_interval=abs_interval * sign_interval; 2271 | break; 2272 | case GH_SWAP_TEAR_DISABLE: 2273 | new_interval=abs_interval; 2274 | break; 2275 | case GH_SWAP_TEAR_ENABLE: 2276 | new_interval=-abs_interval; 2277 | break; 2278 | case GH_SWAP_TEAR_INVERT: 2279 | new_interval=abs_interval * (-sign_interval); 2280 | break; 2281 | default: 2282 | new_interval=abs_interval; 2283 | } 2284 | 2285 | GH_verbose(GH_MSG_INFO,"swap interval %d -> %d\n", interval, new_interval); 2286 | return new_interval; 2287 | } 2288 | 2289 | static int GH_swap_interval(int interval) 2290 | { 2291 | static pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; 2292 | static volatile GH_config cfg={GH_SWAP_MODES_COUNT, GH_SWAP_TEAR_KEEP, {0,1}}; 2293 | 2294 | pthread_mutex_lock(&mutex); 2295 | if (cfg.swap_mode >= GH_SWAP_MODES_COUNT) { 2296 | GH_swap_mode_from_str(&cfg, getenv("GH_SWAP_MODE")); 2297 | GH_swap_tear_from_str(&cfg, getenv("GH_SWAP_TEAR")); 2298 | } 2299 | pthread_mutex_unlock(&mutex); 2300 | return GH_swap_interval_base(&cfg, interval); 2301 | } 2302 | 2303 | 2304 | /*************************************************************************** 2305 | * INTERCEPTED FUNCTIONS: libdl/libc * 2306 | ***************************************************************************/ 2307 | 2308 | /* intercept dlsym() itself */ 2309 | extern void * 2310 | dlsym(void *handle, const char *name) 2311 | { 2312 | void *interceptor; 2313 | void *ptr; 2314 | /* special case: we cannot use GH_GET_PTR as it relies on 2315 | * GH_dlsym() which we have to query using GH_dlsym_internal */ 2316 | pthread_mutex_lock(&GH_fptr_mutex); 2317 | GH_dlsym_internal_dlsym(); 2318 | pthread_mutex_unlock(&GH_fptr_mutex); 2319 | interceptor=GH_get_interceptor(name, GH_dlsym_next, "dlsym"); 2320 | ptr=(interceptor)?interceptor:GH_dlsym(handle,name); 2321 | GH_verbose(GH_MSG_DEBUG_INTERCEPTION,"dlsym(%p, %s) = %p%s\n",handle,name,ptr, 2322 | interceptor?" [intercepted]":""); 2323 | return ptr; 2324 | } 2325 | 2326 | #if (GH_DLSYM_METHOD != 2) 2327 | /* Also intercept GNU specific dlvsym(). 2328 | * We can do this only if we are not using dlvsym to query dlsym... */ 2329 | extern void * 2330 | dlvsym(void *handle, const char *name, const char *version) 2331 | { 2332 | void *interceptor; 2333 | void *ptr; 2334 | pthread_mutex_lock(&GH_fptr_mutex); 2335 | GH_dlsym_internal_dlsym(); 2336 | pthread_mutex_unlock(&GH_fptr_mutex); 2337 | GH_GET_PTR(dlvsym); 2338 | interceptor=GH_get_interceptor(name, GH_dlsym_next, "dlvsym"); 2339 | ptr=(interceptor)?interceptor:GH_dlvsym(handle,name,version); 2340 | GH_verbose(GH_MSG_DEBUG_INTERCEPTION,"dlvsym(%p, %s, %s) = %p%s\n",handle,name,version,ptr, 2341 | interceptor?" [intercepted]":""); 2342 | return ptr; 2343 | } 2344 | #endif 2345 | 2346 | /*************************************************************************** 2347 | * INTERCEPTED FUNCTIONS: glX * 2348 | ***************************************************************************/ 2349 | 2350 | /* Actually, our goal is to intercept glXSwapInterval[EXT|SGI]() etc. But 2351 | * these are extension functions not required to be provided as external 2352 | * symbols. However, some applications just likn them anyways, so we have 2353 | * to handle the case were dlsym() or glXGetProcAddress[ARB]() is used to 2354 | * query the function pointers, and have to intercept these as well. 2355 | */ 2356 | 2357 | /* helper macro to handle both glxGetProcAddress and glXGetProcAddressARB */ 2358 | #define GH_GLXGETPROCADDRESS_GENERIC(procname) \ 2359 | extern GH_fptr procname(const GLubyte *name) \ 2360 | { \ 2361 | void *interceptor; \ 2362 | GH_fptr ptr; \ 2363 | GH_GET_PTR(procname); \ 2364 | interceptor=GH_get_interceptor((const char *)name, \ 2365 | (GH_resolve_func)GH_ ##procname, \ 2366 | #procname); \ 2367 | ptr=(interceptor)?(GH_fptr)interceptor:GH_ ##procname((const char*)name); \ 2368 | GH_verbose(GH_MSG_DEBUG_INTERCEPTION,#procname "(%s) = %p%s\n",(const char *)name, ptr, \ 2369 | interceptor?" [intercepted]":""); \ 2370 | return ptr; \ 2371 | } 2372 | 2373 | GH_GLXGETPROCADDRESS_GENERIC(glXGetProcAddress) 2374 | GH_GLXGETPROCADDRESS_GENERIC(glXGetProcAddressARB) 2375 | 2376 | #ifdef GH_CONTEXT_TRACKING 2377 | 2378 | /* ---------- Context Creation ---------- */ 2379 | 2380 | extern GLXContext glXCreateContext(Display *dpy, XVisualInfo *vis, GLXContext shareList, Bool direct ) 2381 | { 2382 | GLXContext ctx; 2383 | 2384 | ctx = override_create_context(dpy, vis, NULL, shareList, direct, NULL); 2385 | if (ctx == NULL) { 2386 | GH_GET_PTR_GL(glXCreateContext); 2387 | ctx=GH_glXCreateContext(dpy, vis, shareList, direct); 2388 | } 2389 | create_context(ctx); 2390 | return ctx; 2391 | } 2392 | 2393 | extern GLXContext glXCreateNewContext( Display *dpy, GLXFBConfig config, int renderType, GLXContext shareList, Bool direct ) 2394 | { 2395 | GLXContext ctx; 2396 | 2397 | ctx = override_create_context(dpy, NULL, &config, shareList, direct, NULL); 2398 | if (ctx == NULL) { 2399 | GH_GET_PTR_GL(glXCreateNewContext); 2400 | ctx=GH_glXCreateNewContext(dpy, config, renderType, shareList, direct); 2401 | } 2402 | create_context(ctx); 2403 | return ctx; 2404 | } 2405 | 2406 | extern GLXContext glXCreateContextAttribsARB (Display * dpy, GLXFBConfig config, GLXContext shareList, Bool direct, const int *attr) 2407 | { 2408 | GLXContext ctx; 2409 | 2410 | ctx = override_create_context(dpy, NULL, &config, shareList, direct, attr); 2411 | if (ctx == NULL) { 2412 | GH_GET_PTR_GL(glXCreateContextAttribsARB); 2413 | ctx=GH_glXCreateContextAttribsARB(dpy, config, shareList, direct, attr); 2414 | } 2415 | create_context(ctx); 2416 | return ctx; 2417 | } 2418 | 2419 | extern GLXContext glXImportContextEXT (Display *dpy, GLXContextID id) 2420 | { 2421 | GLXContext ctx; 2422 | 2423 | GH_GET_PTR_GL(glXImportContextEXT); 2424 | ctx=GH_glXImportContextEXT(dpy, id); 2425 | create_context(ctx); 2426 | return ctx; 2427 | } 2428 | 2429 | extern GLXContext glXCreateContextWithConfigSGIX (Display *dpy, GLXFBConfigSGIX config, int renderType, GLXContext shareList, Bool direct) 2430 | { 2431 | GLXContext ctx; 2432 | 2433 | /* TODO: override_create_context for this case */ 2434 | GH_GET_PTR_GL(glXCreateContextWithConfigSGIX); 2435 | ctx=GH_glXCreateContextWithConfigSGIX(dpy, config, renderType, shareList, direct); 2436 | create_context(ctx); 2437 | return ctx; 2438 | } 2439 | 2440 | /* ---------- Context Destruction ---------- */ 2441 | 2442 | extern void glXDestroyContext(Display *dpy, GLXContext ctx) 2443 | { 2444 | GH_GET_PTR_GL(glXDestroyContext); 2445 | GH_glXDestroyContext(dpy, ctx); 2446 | destroy_context(ctx); 2447 | } 2448 | 2449 | extern void glXFreeContextEXT(Display *dpy, GLXContext ctx) 2450 | { 2451 | GH_GET_PTR_GL(glXFreeContextEXT); 2452 | GH_glXFreeContextEXT(dpy, ctx); 2453 | destroy_context(ctx); 2454 | } 2455 | 2456 | /* ---------- Current Context Tracking ---------- */ 2457 | 2458 | extern Bool glXMakeCurrent(Display *dpy, GLXDrawable drawable, GLXContext ctx) 2459 | { 2460 | Bool result; 2461 | 2462 | result=GH_glXMakeCurrent(dpy, drawable, ctx); 2463 | make_current(ctx, dpy, drawable, drawable); 2464 | return result; 2465 | } 2466 | 2467 | extern Bool glXMakeContextCurrent(Display *dpy, GLXDrawable draw, GLXDrawable read, GLXContext ctx) 2468 | { 2469 | Bool result; 2470 | 2471 | result=GH_glXMakeContextCurrent(dpy, draw, read, ctx); 2472 | make_current(ctx, dpy, draw, read); 2473 | return result; 2474 | } 2475 | 2476 | extern Bool glXMakeCurrentReadSGI(Display *dpy, GLXDrawable draw, GLXDrawable read, GLXContext ctx) 2477 | { 2478 | Bool result; 2479 | 2480 | result=GH_glXMakeCurrentReadSGI(dpy, draw, read, ctx); 2481 | make_current(ctx, dpy, draw, read); 2482 | return result; 2483 | } 2484 | 2485 | /* ---------- GL Debug Output ---------- */ 2486 | 2487 | extern void glDebugMessageCallback(GLDEBUGPROC proc, const GLvoid *user_ptr) 2488 | { 2489 | GH_GET_PTR_GL(glDebugMessageCallback); 2490 | if (GH_glDebugMessageCallback) { 2491 | gl_context_t *glc=(gl_context_t*)pthread_getspecific(ctx_current); 2492 | if (glc) { 2493 | glc->original_debug_callback = proc; 2494 | glc->original_debug_callback_user_ptr = user_ptr; 2495 | 2496 | if (glc->flags & GH_GL_INTERCEPT_DEBUG) { 2497 | GH_verbose(GH_MSG_INFO, "injecting debug output [core]!\n"); 2498 | proc = GH_debug_callback; 2499 | user_ptr = glc; 2500 | } 2501 | } else { 2502 | GH_verbose(GH_MSG_WARNING, "debug message callback without current context!\n"); 2503 | } 2504 | GH_glDebugMessageCallback(proc, user_ptr); 2505 | } 2506 | } 2507 | 2508 | extern void glDebugMessageCallbackARB(GLDEBUGPROC proc, const GLvoid *user_ptr) 2509 | { 2510 | GH_GET_PTR_GL(glDebugMessageCallbackARB); 2511 | if (GH_glDebugMessageCallbackARB) { 2512 | gl_context_t *glc=(gl_context_t*)pthread_getspecific(ctx_current); 2513 | if (glc) { 2514 | glc->original_debug_callback = proc; 2515 | glc->original_debug_callback_user_ptr = user_ptr; 2516 | 2517 | if (glc->flags & GH_GL_INTERCEPT_DEBUG) { 2518 | GH_verbose(GH_MSG_INFO, "injecting debug output [ARB]!\n"); 2519 | proc = GH_debug_callback; 2520 | user_ptr = glc; 2521 | } 2522 | } else { 2523 | GH_verbose(GH_MSG_WARNING, "debug message callback without current context!\n"); 2524 | } 2525 | GH_glDebugMessageCallbackARB(proc, user_ptr); 2526 | } 2527 | } 2528 | 2529 | extern void glDebugMessageCallbackKHR(GLDEBUGPROC proc, const GLvoid *user_ptr) 2530 | { 2531 | GH_GET_PTR_GL(glDebugMessageCallbackKHR); 2532 | if (GH_glDebugMessageCallbackKHR) { 2533 | gl_context_t *glc=(gl_context_t*)pthread_getspecific(ctx_current); 2534 | if (glc) { 2535 | glc->original_debug_callback = proc; 2536 | glc->original_debug_callback_user_ptr = user_ptr; 2537 | 2538 | if (glc->flags & GH_GL_INTERCEPT_DEBUG) { 2539 | GH_verbose(GH_MSG_INFO, "injecting debug output [KHR]!\n"); 2540 | proc = GH_debug_callback; 2541 | user_ptr = glc; 2542 | } 2543 | } else { 2544 | GH_verbose(GH_MSG_WARNING, "debug message callback without current context!\n"); 2545 | } 2546 | GH_glDebugMessageCallbackKHR(proc, user_ptr); 2547 | } 2548 | } 2549 | 2550 | extern void glDebugMessageCallbackAMD(GLDEBUGPROCAMD proc, GLvoid *user_ptr) 2551 | { 2552 | GH_GET_PTR_GL(glDebugMessageCallbackAMD); 2553 | if (GH_glDebugMessageCallbackAMD) { 2554 | gl_context_t *glc=(gl_context_t*)pthread_getspecific(ctx_current); 2555 | if (glc) { 2556 | glc->original_debug_callback_AMD = proc; 2557 | glc->original_debug_callback_AMD_user_ptr = user_ptr; 2558 | 2559 | if (glc->flags & GH_GL_INTERCEPT_DEBUG) { 2560 | GH_verbose(GH_MSG_INFO, "injecting debug output [AMD]!\n"); 2561 | proc = GH_debug_callback_AMD; 2562 | user_ptr = glc; 2563 | } 2564 | } else { 2565 | GH_verbose(GH_MSG_WARNING, "debug message callback without current context!\n"); 2566 | } 2567 | GH_glDebugMessageCallbackAMD(proc, user_ptr); 2568 | } 2569 | } 2570 | 2571 | #endif /* GH_CONTEXT_TRACKING */ 2572 | 2573 | /* ---------- Swap Interval---------- */ 2574 | 2575 | extern void glXSwapIntervalEXT(Display *dpy, GLXDrawable drawable, 2576 | int interval) 2577 | { 2578 | interval=GH_swap_interval(interval); 2579 | if (interval == GH_SWAP_DONT_SET) { 2580 | /* ignore the call */ 2581 | return; 2582 | } 2583 | GH_GET_PTR_GL(glXSwapIntervalEXT); 2584 | GH_glXSwapIntervalEXT(dpy, drawable, interval); 2585 | } 2586 | 2587 | extern int glXSwapIntervalSGI(int interval) 2588 | { 2589 | interval=GH_swap_interval(interval); 2590 | if (interval == GH_SWAP_DONT_SET) { 2591 | /* ignore the call */ 2592 | return 0; /* success */ 2593 | } 2594 | GH_GET_PTR_GL(glXSwapIntervalSGI); 2595 | return GH_glXSwapIntervalSGI(interval); 2596 | } 2597 | 2598 | extern int glXSwapIntervalMESA(unsigned int interval) 2599 | { 2600 | int signed_interval; 2601 | if (interval > (unsigned)INT_MAX) { 2602 | signed_interval=INT_MAX; 2603 | } else { 2604 | signed_interval=(int)interval; 2605 | } 2606 | signed_interval=GH_swap_interval(signed_interval); 2607 | if (signed_interval == GH_SWAP_DONT_SET) { 2608 | /* ignore the call */ 2609 | return 0; /* success */ 2610 | } 2611 | if (signed_interval < 0) { 2612 | GH_verbose(GH_MSG_WARNING,"glXSwapIntervalMESA does not support negative swap intervals\n"); 2613 | interval=(unsigned)(-signed_interval); 2614 | } else { 2615 | interval=(unsigned)signed_interval; 2616 | } 2617 | GH_GET_PTR_GL(glXSwapIntervalMESA); 2618 | return GH_glXSwapIntervalMESA(interval); 2619 | } 2620 | 2621 | /* ---------- Swap Buffers ---------- */ 2622 | 2623 | #ifdef GH_SWAPBUFFERS_INTERCEPT 2624 | extern void glXSwapBuffers(Display *dpy, GLXDrawable drawable) 2625 | { 2626 | #ifdef GH_CONTEXT_TRACKING 2627 | gl_context_t *glc=(gl_context_t*)pthread_getspecific(ctx_current); 2628 | 2629 | if (glc) { 2630 | frametimes_before_swap(&glc->frametimes); 2631 | if (glc->swapbuffer_omission.swapbuffers > 0) { 2632 | int do_swap; 2633 | if (glc->swapbuffer_omission.latency_mode > 0) { 2634 | latency_before_swap(&glc->latency); 2635 | } 2636 | do_swap = swapbuffer_omission_do_swap(&glc->swapbuffer_omission); 2637 | if (do_swap) { 2638 | if (glc->swapbuffer_omission.latency_mode < 1) { 2639 | latency_before_swap(&glc->latency); 2640 | GH_glXSwapBuffers(dpy, drawable); 2641 | latency_after_swap(&glc->latency); 2642 | } else { 2643 | GH_glXSwapBuffers(dpy, drawable); 2644 | } 2645 | } else { 2646 | swapbuffer_omission_swap_skipped(&glc->swapbuffer_omission); 2647 | } 2648 | if (glc->swapbuffer_omission.latency_mode > 0) { 2649 | latency_after_swap(&glc->latency); 2650 | } 2651 | swapbuffer_omission_swap_finished(&glc->swapbuffer_omission, do_swap); 2652 | } else { 2653 | latency_before_swap(&glc->latency); 2654 | GH_glXSwapBuffers(dpy, drawable); 2655 | latency_after_swap(&glc->latency); 2656 | } 2657 | frametimes_after_swap(&glc->frametimes); 2658 | if (glc->swap_sleep_usecs) { 2659 | usleep(glc->swap_sleep_usecs); 2660 | } 2661 | } else { 2662 | GH_verbose(GH_MSG_WARNING,"SwapBuffers called without a context\n"); 2663 | GH_GET_PTR_GL(glXSwapBuffers); 2664 | GH_glXSwapBuffers(dpy, drawable); 2665 | } 2666 | #else /* GH_CONTEXT_TRACKING */ 2667 | GH_GET_PTR_GL(glXSwapBuffers); 2668 | GH_glXSwapBuffers(dpy, drawable); 2669 | #endif /* GH_CONTEXT_TRACKING */ 2670 | } 2671 | #endif /* GH_SWAPBUFFERS_INTERCEPT */ 2672 | 2673 | /*************************************************************************** 2674 | * LIST OF INTERCEPTED FUNCTIONS * 2675 | ***************************************************************************/ 2676 | 2677 | /* return intercepted fuction pointer for "name", or NULL if 2678 | * "name" is not to be intercepted. If function is intercepted, 2679 | * use query to resolve the original function pointer and store 2680 | * it in the GH_"name" static pointer. That way, we use the same 2681 | * function the original application were using without the interceptor. 2682 | * The interceptor functions will fall back to using GH_dlsym() if the 2683 | * name resolution here did fail for some reason. 2684 | */ 2685 | static void* GH_get_interceptor(const char *name, GH_resolve_func query, 2686 | const char *query_name ) 2687 | { 2688 | #define GH_INTERCEPT(func) \ 2689 | if (!strcmp(#func, name)) {\ 2690 | pthread_mutex_lock(&GH_fptr_mutex); \ 2691 | if ( (GH_ ##func == NULL) && query) { \ 2692 | GH_ ##func = query(#func); \ 2693 | GH_verbose(GH_MSG_DEBUG,"queried internal %s via %s: %p\n", \ 2694 | name,query_name, GH_ ##func); \ 2695 | } \ 2696 | pthread_mutex_unlock(&GH_fptr_mutex); \ 2697 | return func; \ 2698 | } 2699 | 2700 | #ifdef GH_SWAPBUFFERS_INTERCEPT 2701 | static int do_swapbuffers=0; 2702 | #endif 2703 | static int do_dlsym = 0; 2704 | #if (GH_DLSYM_METHOD != 2) 2705 | static int do_dlvsym = 0; 2706 | #endif 2707 | static int inited = 0; 2708 | 2709 | if (!inited) { 2710 | #ifdef GH_SWAPBUFFERS_INTERCEPT 2711 | do_swapbuffers =get_envi("GH_SWAPBUFFERS", 0) || 2712 | get_envi("GH_FRAMETIME", 0) || 2713 | get_envi("GH_SWAP_SLEEP_USECS", 0) || 2714 | (get_envi("GH_LATENCY", GH_LATENCY_NOP) != GH_LATENCY_NOP); 2715 | #endif 2716 | do_dlsym = get_envi("GH_HOOK_DLSYM_DYNAMICALLY", 0); 2717 | #if (GH_DLSYM_METHOD != 2) 2718 | do_dlvsym = get_envi("GH_HOOK_DLVSYM_DYNAMICALLY", 0); 2719 | #endif 2720 | inited = 1; 2721 | } 2722 | 2723 | if (do_dlsym) { 2724 | GH_INTERCEPT(dlsym); 2725 | } 2726 | #if (GH_DLSYM_METHOD != 2) 2727 | if (do_dlvsym) { 2728 | GH_INTERCEPT(dlvsym); 2729 | } 2730 | #endif 2731 | GH_INTERCEPT(glXGetProcAddress); 2732 | GH_INTERCEPT(glXGetProcAddressARB); 2733 | GH_INTERCEPT(glXSwapIntervalEXT); 2734 | GH_INTERCEPT(glXSwapIntervalSGI); 2735 | GH_INTERCEPT(glXSwapIntervalMESA); 2736 | #ifdef GH_CONTEXT_TRACKING 2737 | GH_INTERCEPT(glXCreateContext); 2738 | GH_INTERCEPT(glXCreateNewContext); 2739 | GH_INTERCEPT(glXCreateContextAttribsARB); 2740 | GH_INTERCEPT(glXImportContextEXT); 2741 | GH_INTERCEPT(glXCreateContextWithConfigSGIX); 2742 | GH_INTERCEPT(glXDestroyContext); 2743 | GH_INTERCEPT(glXFreeContextEXT); 2744 | GH_INTERCEPT(glXMakeCurrent); 2745 | GH_INTERCEPT(glXMakeContextCurrent); 2746 | GH_INTERCEPT(glXMakeCurrentReadSGI); 2747 | GH_INTERCEPT(glDebugMessageCallback); 2748 | GH_INTERCEPT(glDebugMessageCallbackARB); 2749 | GH_INTERCEPT(glDebugMessageCallbackKHR); 2750 | GH_INTERCEPT(glDebugMessageCallbackAMD); 2751 | #endif 2752 | #ifdef GH_SWAPBUFFERS_INTERCEPT 2753 | if (do_swapbuffers) { 2754 | if (do_swapbuffers < 0) { 2755 | do_swapbuffers =get_envi("GH_SWAPBUFFERS", 0) || 2756 | get_envi("GH_FRAMETIME", 0) || 2757 | get_envi("GH_SWAP_SLEEP_USECS", 0) || 2758 | (get_envi("GH_LATENCY", GH_LATENCY_NOP) != GH_LATENCY_NOP); 2759 | } 2760 | if (do_swapbuffers) 2761 | GH_INTERCEPT(glXSwapBuffers); 2762 | } 2763 | #endif 2764 | return NULL; 2765 | } 2766 | 2767 | --------------------------------------------------------------------------------