/* * Copyright (c) 2003-2009 Sun Microsystems, Inc. All Rights Reserved. * Copyright (c) 2010 JogAmp Community. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistribution of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistribution in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Sun Microsystems, Inc. or the names of * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed or intended for use * in the design, construction, operation or maintenance of any nuclear * facility. * * Sun gratefully acknowledges that this software was originally authored * and developed by Kenneth Bradley Russell and Christopher John Kline. */ package com.jogamp.opengl; import com.jogamp.nativewindow.NativeWindowException; import java.util.List; import com.jogamp.nativewindow.CapabilitiesImmutable; import com.jogamp.common.ExceptionUtils; import com.jogamp.common.util.PropertyAccess; import jogamp.opengl.Debug; /** <P> The default implementation of the {@link GLCapabilitiesChooser} interface, which provides consistent visual selection behavior across platforms. The precise algorithm is deliberately left loosely specified. Some properties are: </P> <UL> <LI> As long as there is at least one available non-null GLCapabilities which matches the "stereo" option, will return a valid index. <LI> Attempts to match as closely as possible the given GLCapabilities, but will select one with fewer capabilities (i.e., lower color depth) if necessary. <LI> Prefers hardware-accelerated visuals to non-hardware-accelerated. <LI> If there is no exact match, prefers a more-capable visual to a less-capable one. <LI> If there is more than one exact match, chooses an arbitrary one. <LI> May select the opposite of a double- or single-buffered visual (based on the user's request) in dire situations. <LI> Color depth (including alpha) mismatches are weighted higher than depth buffer mismatches, which are in turn weighted higher than accumulation buffer (including alpha) and stencil buffer depth mismatches. <LI> If a valid windowSystemRecommendedChoice parameter is supplied, chooses that instead of using the cross-platform code. </UL> */ public class DefaultGLCapabilitiesChooser implements GLCapabilitiesChooser { private static final boolean DEBUG; static { Debug.initSingleton(); DEBUG = PropertyAccess.isPropertyDefined("jogl.debug.CapabilitiesChooser", true); } private final static int NO_SCORE = -9999999; private final static int DOUBLE_BUFFER_MISMATCH_PENALTY = 1000; private final static int OPAQUE_MISMATCH_PENALTY = 750; private final static int STENCIL_MISMATCH_PENALTY = 500; private final static int MULTISAMPLE_MISMATCH_PENALTY = 500; private final static int MULTISAMPLE_EXTENSION_MISMATCH_PENALTY = 250; // just a little drop, no scale // Pseudo attempt to keep equal rank penalties scale-equivalent // (e.g., stencil mismatch is 3 * accum because there are 3 accum // components) private final static int COLOR_MISMATCH_PENALTY_SCALE = 36; private final static int DEPTH_MISMATCH_PENALTY_SCALE = 6; private final static int ACCUM_MISMATCH_PENALTY_SCALE = 1; private final static int STENCIL_MISMATCH_PENALTY_SCALE = 3; private final static int MULTISAMPLE_MISMATCH_PENALTY_SCALE = 3; @Override public int chooseCapabilities(final CapabilitiesImmutable desired, final List<? extends CapabilitiesImmutable> available, final int windowSystemRecommendedChoice) { if ( null == desired ) { throw new NativeWindowException("Null desired capabilities"); } if ( 0 == available.size() ) { throw new NativeWindowException("Empty available capabilities"); } final GLCapabilitiesImmutable gldes = (GLCapabilitiesImmutable) desired; final int availnum = available.size(); if (DEBUG) { ExceptionUtils.dumpStack(System.err); System.err.println("Desired: " + gldes); System.err.println("Available: " + availnum); for (int i = 0; i < available.size(); i++) { System.err.println(i + ": " + available.get(i)); } System.err.println("Window system's recommended choice: " + windowSystemRecommendedChoice); } if (windowSystemRecommendedChoice >= 0 && windowSystemRecommendedChoice < availnum && null != available.get(windowSystemRecommendedChoice)) { if (DEBUG) { System.err.println("Choosing window system's recommended choice of " + windowSystemRecommendedChoice); System.err.println(available.get(windowSystemRecommendedChoice)); } return windowSystemRecommendedChoice; } // Create score array final int[] scores = new int[availnum]; for (int i = 0; i < scores.length; i++) { scores[i] = NO_SCORE; } final int gldes_samples = gldes.getNumSamples(); // Compute score for each for (int i = 0; i < availnum; i++) { final GLCapabilitiesImmutable cur = (GLCapabilitiesImmutable) available.get(i); if (cur == null) { continue; } if (gldes.isOnscreen() && !cur.isOnscreen()) { continue; // requested onscreen, but n/a } if (!gldes.isOnscreen()) { /** FBO is generic .. if (gldes.isFBO() && !cur.isFBO()) { continue; // requested FBO, but n/a } */ if (gldes.isPBuffer() && !cur.isPBuffer()) { continue; // requested pBuffer, but n/a } if (gldes.isBitmap() && !cur.isBitmap()) { continue; // requested pBuffer, but n/a } } if (gldes.getStereo() != cur.getStereo()) { continue; } final int cur_samples = cur.getNumSamples() ; int score = 0; // Compute difference in color depth // (Note that this decides the direction of all other penalties) score += (COLOR_MISMATCH_PENALTY_SCALE * ((cur.getRedBits() + cur.getGreenBits() + cur.getBlueBits() + cur.getAlphaBits()) - (gldes.getRedBits() + gldes.getGreenBits() + gldes.getBlueBits() + gldes.getAlphaBits()))); // Compute difference in depth buffer depth score += (DEPTH_MISMATCH_PENALTY_SCALE * sign(score) * Math.abs(cur.getDepthBits() - gldes.getDepthBits())); // Compute difference in accumulation buffer depth score += (ACCUM_MISMATCH_PENALTY_SCALE * sign(score) * Math.abs((cur.getAccumRedBits() + cur.getAccumGreenBits() + cur.getAccumBlueBits() + cur.getAccumAlphaBits()) - (gldes.getAccumRedBits() + gldes.getAccumGreenBits() + gldes.getAccumBlueBits() + gldes.getAccumAlphaBits()))); // Compute difference in stencil bits score += STENCIL_MISMATCH_PENALTY_SCALE * sign(score) * (cur.getStencilBits() - gldes.getStencilBits()); // Compute difference in multisampling bits score += MULTISAMPLE_MISMATCH_PENALTY_SCALE * sign(score) * (cur_samples - gldes_samples); // double buffer if (cur.getDoubleBuffered() != gldes.getDoubleBuffered()) { score += sign(score) * DOUBLE_BUFFER_MISMATCH_PENALTY; } // opaque if (cur.isBackgroundOpaque() != gldes.isBackgroundOpaque()) { score += sign(score) * OPAQUE_MISMATCH_PENALTY; } if ((gldes.getStencilBits() > 0) && (cur.getStencilBits() == 0)) { score += sign(score) * STENCIL_MISMATCH_PENALTY; } if (gldes_samples > 0) { if (cur_samples == 0) { score += sign(score) * MULTISAMPLE_MISMATCH_PENALTY; } if (!gldes.getSampleExtension().equals(cur.getSampleExtension())) { score += sign(score) * MULTISAMPLE_EXTENSION_MISMATCH_PENALTY; } } scores[i] = score; } // Now prefer hardware-accelerated visuals by pushing scores of // non-hardware-accelerated visuals out boolean gotHW = false; int maxAbsoluteHWScore = 0; for (int i = 0; i < availnum; i++) { final int score = scores[i]; if (score == NO_SCORE) { continue; } final GLCapabilitiesImmutable cur = (GLCapabilitiesImmutable) available.get(i); if (cur.getHardwareAccelerated()) { final int absScore = Math.abs(score); if (!gotHW || (absScore > maxAbsoluteHWScore)) { gotHW = true; maxAbsoluteHWScore = absScore; } } } if (gotHW) { for (int i = 0; i < availnum; i++) { int score = scores[i]; if (score == NO_SCORE) { continue; } final GLCapabilitiesImmutable cur = (GLCapabilitiesImmutable) available.get(i); if (!cur.getHardwareAccelerated()) { if (score <= 0) { score -= maxAbsoluteHWScore; } else if (score > 0) { score += maxAbsoluteHWScore; } scores[i] = score; } } } if (DEBUG) { System.err.print("Scores: ["); for (int i = 0; i < availnum; i++) { if (i > 0) { System.err.print(","); } System.err.print(" " + i +": " + scores[i]); } System.err.println(" ]"); } // Ready to select. Choose score closest to 0. int scoreClosestToZero = NO_SCORE; int chosenIndex = -1; for (int i = 0; i < availnum; i++) { final int score = scores[i]; if (score == NO_SCORE) { continue; } // Don't substitute a positive score for a smaller negative score if ((scoreClosestToZero == NO_SCORE) || (Math.abs(score) < Math.abs(scoreClosestToZero) && ((sign(scoreClosestToZero) < 0) || (sign(score) > 0)))) { scoreClosestToZero = score; chosenIndex = i; } } if (chosenIndex < 0) { throw new NativeWindowException("Unable to select one of the provided GLCapabilities"); } if (DEBUG) { System.err.println("Chosen index: " + chosenIndex); System.err.println("Chosen capabilities:"); System.err.println(available.get(chosenIndex)); } return chosenIndex; } private static int sign(final int score) { if (score < 0) { return -1; } return 1; } }