1 /* 2 * Copyright (c) 2002-2008 LWJGL Project 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'LWJGL' nor the names of 17 * its contributors may be used to endorse or promote products derived 18 * from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package org.lwjgl.opengl; 33 34 import org.lwjgl.LWJGLException; 35 import org.lwjgl.LWJGLUtil; 36 import org.lwjgl.MemoryUtil; 37 import org.lwjgl.Sys; 38 39 import java.lang.reflect.Method; 40 import java.nio.ByteBuffer; 41 import java.security.AccessController; 42 import java.security.PrivilegedAction; 43 import java.security.PrivilegedExceptionAction; 44 import java.util.*; 45 46 import static org.lwjgl.opengl.GL11.*; 47 import static org.lwjgl.opengl.GL30.*; 48 import static org.lwjgl.opengl.GL32.*; 49 50 /** 51 * <p/> 52 * Manages GL contexts. Before any rendering is done by a LWJGL system, a call should be made to GLContext.useContext() with a 53 * context. This will ensure that GLContext has an accurate reflection of the current context's capabilities and function 54 * pointers. 55 * <p/> 56 * This class is thread-safe in the sense that multiple threads can safely call all public methods. The class is also 57 * thread-aware in the sense that it tracks a per-thread current context (including capabilities and function pointers). 58 * That way, multiple threads can have multiple contexts current and render to them concurrently. 59 * 60 * @author elias_naur <elias_naur@users.sourceforge.net> 61 * @version $Revision$ 62 * $Id$ 63 */ 64 public final class GLContext { 65 66 /** Maps threads to their current context's ContextCapabilities, if any */ 67 private static final ThreadLocal<ContextCapabilities> current_capabilities = new ThreadLocal<ContextCapabilities>(); 68 69 /** 70 * The getCapabilities() method is a potential hot spot in any LWJGL application, since 71 * it is needed for context capability discovery (e.g. is OpenGL 2.0 supported?), and 72 * for the function pointers of gl functions. However, the 'current_capabilities' ThreadLocal 73 * is (relatively) expensive to look up, and since most OpenGL applications use are single threaded 74 * rendering, the following two is an optimization for this case. 75 * <p/> 76 * ThreadLocals can be thought of as a mapping between threads and values, so the idea 77 * is to use a lock-less cache of mappings between threads and the current ContextCapabilities. The cache 78 * could be any size, but in our case, we want a single sized cache for optimal performance 79 * in the single threaded case. 80 * <p/> 81 * 'fast_path_cache' is the most recent ContextCapabilities (potentially null) and its owner. By 82 * recent I mean the last thread setting the value in setCapabilities(). When getCapabilities() 83 * is called, a check to see if the current is the owner of the ContextCapabilities instance in 84 * fast_path_cache. If so, the instance is returned, if not, some thread has since taken ownership 85 * of the cache entry and the slower current_capabilities ThreadLocal is queried instead. 86 * <p/> 87 * No locks are needed in get/setCapabilities, because even though fast_path_cache can be accessed 88 * from multiple threads at once, we are guaranteed by the JVM spec that its value is always valid. 89 * Furthermore, if the ownership test in getCapabilities() succeeds, the cache entry can only contain 90 * the correct ContextCapabilites (that is, the one from getThreadLocalCapabilites()), 91 * since no other thread can set the owner to anyone else than itself. 92 */ 93 private static CapabilitiesCacheEntry fast_path_cache = new CapabilitiesCacheEntry(); 94 95 /** 96 * Simple lock-free cache of CapabilitesEntryCache to avoid allocating more than one 97 * cache entry per thread 98 */ 99 private static final ThreadLocal<CapabilitiesCacheEntry> thread_cache_entries = new ThreadLocal<CapabilitiesCacheEntry>(); 100 101 /** 102 * The weak mapping from context Object instances to ContextCapabilities. Used 103 * to avoid recreating a ContextCapabilities every time a context is made current. 104 */ 105 private static final Map<Object, ContextCapabilities> capability_cache = new WeakHashMap<Object, ContextCapabilities>(); 106 107 /** Reference count of the native opengl implementation library */ 108 private static int gl_ref_count; 109 private static boolean did_auto_load; 110 111 static { Sys.initialize()112 Sys.initialize(); 113 } 114 115 /** 116 * Get the current capabilities instance. It contains the flags used 117 * to test for support of a particular extension. 118 * 119 * @return The current capabilities instance. 120 */ getCapabilities()121 public static ContextCapabilities getCapabilities() { 122 ContextCapabilities caps = getCapabilitiesImpl(); 123 if ( caps == null ) 124 throw new RuntimeException("No OpenGL context found in the current thread."); 125 126 return caps; 127 } 128 getCapabilitiesImpl()129 private static ContextCapabilities getCapabilitiesImpl() { 130 CapabilitiesCacheEntry recent_cache_entry = fast_path_cache; 131 // Check owner of cache entry 132 if ( recent_cache_entry.owner == Thread.currentThread() ) { 133 /* The owner ship test succeeded, so the cache must contain the current ContextCapabilities instance 134 * assert recent_cache_entry.capabilities == getThreadLocalCapabilities(); 135 */ 136 return recent_cache_entry.capabilities; 137 } else // Some other thread has written to the cache since, and we fall back to the slower path 138 return getThreadLocalCapabilities(); 139 } 140 141 /** 142 * Returns the capabilities instance associated with the specified context object. 143 * 144 * @param context the context object 145 * 146 * @return the capabilities instance 147 */ getCapabilities(Object context)148 static ContextCapabilities getCapabilities(Object context) { 149 return capability_cache.get(context); 150 } 151 getThreadLocalCapabilities()152 private static ContextCapabilities getThreadLocalCapabilities() { 153 return current_capabilities.get(); 154 } 155 156 /** 157 * Set the current capabilities instance. It contains the flags used 158 * to test for support of a particular extension. 159 * 160 * @return The current capabilities instance. 161 */ setCapabilities(ContextCapabilities capabilities)162 static void setCapabilities(ContextCapabilities capabilities) { 163 current_capabilities.set(capabilities); 164 165 CapabilitiesCacheEntry thread_cache_entry = thread_cache_entries.get(); 166 if ( thread_cache_entry == null ) { 167 thread_cache_entry = new CapabilitiesCacheEntry(); 168 thread_cache_entries.set(thread_cache_entry); 169 } 170 thread_cache_entry.owner = Thread.currentThread(); 171 thread_cache_entry.capabilities = capabilities; 172 173 fast_path_cache = thread_cache_entry; 174 } 175 176 /** 177 * Helper method to get a pointer to a named function in the OpenGL library 178 * with a name dependent on the current platform 179 */ getPlatformSpecificFunctionAddress(String function_prefix, String[] os_prefixes, String[] os_function_prefixes, String function)180 static long getPlatformSpecificFunctionAddress(String function_prefix, String[] os_prefixes, String[] os_function_prefixes, String function) { 181 String os_name = AccessController.doPrivileged(new PrivilegedAction<String>() { 182 public String run() { 183 return System.getProperty("os.name"); 184 } 185 }); 186 for ( int i = 0; i < os_prefixes.length; i++ ) 187 if ( os_name.startsWith(os_prefixes[i]) ) { 188 String platform_function_name = function.replaceFirst(function_prefix, os_function_prefixes[i]); 189 long address = getFunctionAddress(platform_function_name); 190 return address; 191 } 192 return 0; 193 } 194 195 /** 196 * Helper method to get a pointer to a named function with aliases in the OpenGL library. 197 * 198 * @param aliases the function name aliases. 199 * 200 * @return the function pointer address 201 */ getFunctionAddress(String[] aliases)202 static long getFunctionAddress(String[] aliases) { 203 for ( String alias : aliases ) { 204 long address = getFunctionAddress(alias); 205 if ( address != 0 ) 206 return address; 207 } 208 return 0; 209 } 210 211 /** Helper method to get a pointer to a named function in the OpenGL library. */ getFunctionAddress(String name)212 static long getFunctionAddress(String name) { 213 ByteBuffer buffer = MemoryUtil.encodeASCII(name); 214 return ngetFunctionAddress(MemoryUtil.getAddress(buffer)); 215 } ngetFunctionAddress(long name)216 private static native long ngetFunctionAddress(long name); 217 218 /** 219 * Determine which extensions are available and returns the context profile mask. Helper method to ContextCapabilities. 220 * 221 * @param supported_extensions the Set to fill with the available extension names 222 * 223 * @return the context profile mask, will be 0 for any version < 3.2 224 */ getSupportedExtensions(final Set<String> supported_extensions)225 static int getSupportedExtensions(final Set<String> supported_extensions) { 226 // Detect OpenGL version first 227 228 final String version = glGetString(GL_VERSION); 229 if ( version == null ) 230 throw new IllegalStateException("glGetString(GL_VERSION) returned null - possibly caused by missing current context."); 231 232 final StringTokenizer version_tokenizer = new StringTokenizer(version, ". "); 233 final String major_string = version_tokenizer.nextToken(); 234 final String minor_string = version_tokenizer.nextToken(); 235 236 int majorVersion = 0; 237 int minorVersion = 0; 238 try { 239 majorVersion = Integer.parseInt(major_string); 240 minorVersion = Integer.parseInt(minor_string); 241 } catch (NumberFormatException e) { 242 LWJGLUtil.log("The major and/or minor OpenGL version is malformed: " + e.getMessage()); 243 } 244 245 final int[][] GL_VERSIONS = { 246 { 1, 2, 3, 4, 5 }, // OpenGL 1 247 { 0, 1 }, // OpenGL 2 248 { 0, 1, 2, 3 }, // OpenGL 3 249 { 0, 1, 2, 3, 4, 5 }, // OpenGL 4 250 }; 251 252 for ( int major = 1; major <= GL_VERSIONS.length; major++ ) { 253 int[] minors = GL_VERSIONS[major - 1]; 254 for ( int minor : minors ) { 255 if ( major < majorVersion || (major == majorVersion && minor <= minorVersion) ) 256 supported_extensions.add("OpenGL" + Integer.toString(major) + Integer.toString(minor)); 257 } 258 } 259 260 int profileMask = 0; 261 262 if ( majorVersion < 3 ) { 263 // Parse EXTENSIONS string 264 final String extensions_string = glGetString(GL_EXTENSIONS); 265 if ( extensions_string == null ) 266 throw new IllegalStateException("glGetString(GL_EXTENSIONS) returned null - is there a context current?"); 267 268 final StringTokenizer tokenizer = new StringTokenizer(extensions_string); 269 while ( tokenizer.hasMoreTokens() ) 270 supported_extensions.add(tokenizer.nextToken()); 271 } else { 272 // Use forward compatible indexed EXTENSIONS 273 final int extensionCount = glGetInteger(GL_NUM_EXTENSIONS); 274 275 for ( int i = 0; i < extensionCount; i++ ) 276 supported_extensions.add(glGetStringi(GL_EXTENSIONS, i)); 277 278 // Get the context profile mask for versions >= 3.2 279 if ( 3 < majorVersion || 2 <= minorVersion ) { 280 Util.checkGLError(); // Make sure we have no errors up to this point 281 282 try { 283 profileMask = glGetInteger(GL_CONTEXT_PROFILE_MASK); 284 // Retrieving GL_CONTEXT_PROFILE_MASK may generate an INVALID_OPERATION error on certain implementations, ignore. 285 // Happens on pre10.1 ATI drivers, when ContextAttribs.withProfileCompatibility is not used 286 Util.checkGLError(); 287 } catch (OpenGLException e) { 288 LWJGLUtil.log("Failed to retrieve CONTEXT_PROFILE_MASK"); 289 } 290 } 291 } 292 293 return profileMask; 294 } 295 296 /** 297 * Helper method to ContextCapabilities. It will try to initialize the native stubs, 298 * and remove the given extension name from the extension set if the initialization fails. 299 */ initNativeStubs(final Class<?> extension_class, Set supported_extensions, String ext_name)300 static void initNativeStubs(final Class<?> extension_class, Set supported_extensions, String ext_name) { 301 resetNativeStubs(extension_class); 302 if ( supported_extensions.contains(ext_name) ) { 303 try { 304 AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { 305 public Object run() throws Exception { 306 Method init_stubs_method = extension_class.getDeclaredMethod("initNativeStubs"); 307 init_stubs_method.invoke(null); 308 return null; 309 } 310 }); 311 } catch (Exception e) { 312 LWJGLUtil.log("Failed to initialize extension " + extension_class + " - exception: " + e); 313 supported_extensions.remove(ext_name); 314 } 315 } 316 } 317 318 /** 319 * Makes a GL context the current LWJGL context by loading GL function pointers. The context must be current before a call to 320 * this method! Instead it simply ensures that the current context is reflected accurately by GLContext's extension caps and 321 * function pointers. Use useContext(null) when no context is active. <p>If the context is the same as last time, then this is 322 * a no-op. <p>If the context has not been encountered before it will be fully initialized from scratch. Otherwise a cached set 323 * of caps and function pointers will be used. <p>The reference to the context is held in a weak reference; therefore if no 324 * strong reference exists to the GL context it will automatically be forgotten by the VM at an indeterminate point in the 325 * future, freeing up a little RAM. 326 * 327 * @param context The context object, which uniquely identifies a GL context. If context is null, the native stubs are 328 * unloaded. 329 * 330 * @throws LWJGLException if context non-null, and the gl library can't be loaded or the basic GL11 functions can't be loaded 331 */ useContext(Object context)332 public static synchronized void useContext(Object context) throws LWJGLException { 333 useContext(context, false); 334 } 335 336 /** 337 * Makes a GL context the current LWJGL context by loading GL function pointers. The context must be current before a call to 338 * this method! Instead it simply ensures that the current context is reflected accurately by GLContext's extension caps and 339 * function pointers. Use useContext(null) when no context is active. <p>If the context is the same as last time, then this is 340 * a no-op. <p>If the context has not been encountered before it will be fully initialized from scratch. Otherwise a cached set 341 * of caps and function pointers will be used. <p>The reference to the context is held in a weak reference; therefore if no 342 * strong reference exists to the GL context it will automatically be forgotten by the VM at an indeterminate point in the 343 * future, freeing up a little RAM. 344 * <p>If forwardCompatible is true, function pointers of deprecated GL11-GL21 functionality will not be loaded. Calling a deprecated 345 * function using the specified context will result in an <code>IllegalStateException</code>. 346 * 347 * @param context The context object, which uniquely identifies a GL context. If context is null, the native stubs are 348 * unloaded. 349 * @param forwardCompatible If the context is a forward compatible context (does not expose deprecated functionality, see XGL_ARB_create_context) 350 * 351 * @throws LWJGLException if context non-null, and the gl library can't be loaded or the basic GL11 functions can't be loaded 352 */ useContext(Object context, boolean forwardCompatible)353 public static synchronized void useContext(Object context, boolean forwardCompatible) throws LWJGLException { 354 if ( context == null ) { 355 ContextCapabilities.unloadAllStubs(); 356 setCapabilities(null); 357 if ( did_auto_load ) 358 unloadOpenGLLibrary(); 359 return; 360 } 361 if ( gl_ref_count == 0 ) { 362 loadOpenGLLibrary(); 363 did_auto_load = true; 364 } 365 try { 366 ContextCapabilities capabilities = capability_cache.get(context); 367 if ( capabilities == null ) { 368 /* 369 * The capabilities object registers itself as current. This behaviour is caused 370 * by a chicken-and-egg situation where the constructor needs to call GL functions 371 * as part of its capability discovery, but GL functions cannot be called before 372 * a capabilities object has been set. 373 */ 374 new ContextCapabilities(forwardCompatible); 375 capability_cache.put(context, getCapabilities()); 376 } else 377 setCapabilities(capabilities); 378 } catch (LWJGLException e) { 379 if ( did_auto_load ) 380 unloadOpenGLLibrary(); 381 throw e; 382 } 383 } 384 385 /** If the OpenGL reference count is 0, the library is loaded. The reference count is then incremented. */ loadOpenGLLibrary()386 public static synchronized void loadOpenGLLibrary() throws LWJGLException { 387 if ( gl_ref_count == 0 ) 388 nLoadOpenGLLibrary(); 389 gl_ref_count++; 390 } 391 nLoadOpenGLLibrary()392 private static native void nLoadOpenGLLibrary() throws LWJGLException; 393 394 /** The OpenGL library reference count is decremented, and if it reaches 0, the library is unloaded. */ unloadOpenGLLibrary()395 public static synchronized void unloadOpenGLLibrary() { 396 gl_ref_count--; 397 /* 398 * Unload the native OpenGL library unless we're on linux, since 399 * some drivers (NVIDIA proprietary) crash on exit when unloading the library. 400 */ 401 if ( gl_ref_count == 0 && LWJGLUtil.getPlatform() != LWJGLUtil.PLATFORM_LINUX ) 402 nUnloadOpenGLLibrary(); 403 } 404 nUnloadOpenGLLibrary()405 private static native void nUnloadOpenGLLibrary(); 406 407 /** Native method to clear native stub bindings */ resetNativeStubs(Class clazz)408 static native void resetNativeStubs(Class clazz); 409 410 private static final class CapabilitiesCacheEntry { 411 412 Thread owner; 413 ContextCapabilities capabilities; 414 } 415 }