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 }