src/de/matthiasmann/twl/renderer/lwjgl/LWJGLRenderer.java
author Matthias Mann
Sat May 19 19:02:37 2012 +0200 (22 hours ago)
changeset 1038 0dda4b118c36
parent 1021 39ad49de2070
permissions -rw-r--r--
TextArea: added CSS attribute "tab-size" with "-moz-tab-size" alias
     1 /*
     2  * Copyright (c) 2008-2011, Matthias Mann
     3  *
     4  * All rights reserved.
     5  *
     6  * Redistribution and use in source and binary forms, with or without
     7  * modification, are permitted provided that the following conditions are met:
     8  *
     9  *     * Redistributions of source code must retain the above copyright notice,
    10  *       this list of conditions and the following disclaimer.
    11  *     * Redistributions in binary form must reproduce the above copyright
    12  *       notice, this list of conditions and the following disclaimer in the
    13  *       documentation and/or other materials provided with the distribution.
    14  *     * Neither the name of Matthias Mann nor the names of its contributors may
    15  *       be used to endorse or promote products derived from this software
    16  *       without specific prior written permission.
    17  *
    18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
    22  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
    23  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    24  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
    25  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
    26  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
    27  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    28  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    29  */
    30 package de.matthiasmann.twl.renderer.lwjgl;
    31 
    32 import de.matthiasmann.twl.Color;
    33 import de.matthiasmann.twl.Event;
    34 import de.matthiasmann.twl.Rect;
    35 import de.matthiasmann.twl.renderer.AnimationState;
    36 import de.matthiasmann.twl.renderer.AnimationState.StateKey;
    37 import de.matthiasmann.twl.renderer.CacheContext;
    38 import de.matthiasmann.twl.renderer.DynamicImage;
    39 import de.matthiasmann.twl.renderer.FontParameter;
    40 import de.matthiasmann.twl.renderer.Gradient;
    41 import de.matthiasmann.twl.renderer.Image;
    42 import de.matthiasmann.twl.renderer.MouseCursor;
    43 import de.matthiasmann.twl.renderer.Font;
    44 import de.matthiasmann.twl.renderer.FontMapper;
    45 import de.matthiasmann.twl.renderer.LineRenderer;
    46 import de.matthiasmann.twl.renderer.OffscreenRenderer;
    47 import de.matthiasmann.twl.renderer.Renderer;
    48 import de.matthiasmann.twl.renderer.Texture;
    49 import de.matthiasmann.twl.utils.ClipStack;
    50 import de.matthiasmann.twl.utils.StateSelect;
    51 import java.io.IOException;
    52 import java.net.URL;
    53 import java.nio.ByteBuffer;
    54 import java.nio.IntBuffer;
    55 import java.util.ArrayList;
    56 import java.util.Locale;
    57 import java.util.logging.Level;
    58 import java.util.logging.Logger;
    59 import org.lwjgl.BufferUtils;
    60 import org.lwjgl.LWJGLException;
    61 import org.lwjgl.Sys;
    62 import org.lwjgl.input.Cursor;
    63 import org.lwjgl.input.Mouse;
    64 import org.lwjgl.opengl.ContextCapabilities;
    65 import org.lwjgl.opengl.EXTTextureRectangle;
    66 import org.lwjgl.opengl.GL11;
    67 import org.lwjgl.opengl.GLContext;
    68 
    69 /**
    70  * A renderer using only GL11 features.
    71  *
    72  * <p>For correct rendering the OpenGL viewport size must be synchronized.</p>
    73  *
    74  * @author Matthias Mann
    75  * 
    76  * @see #syncViewportSize()
    77  */
    78 public class LWJGLRenderer implements Renderer, LineRenderer {
    79 
    80     public static final StateKey STATE_LEFT_MOUSE_BUTTON = StateKey.get("leftMouseButton");
    81     public static final StateKey STATE_MIDDLE_MOUSE_BUTTON = StateKey.get("middleMouseButton");
    82     public static final StateKey STATE_RIGHT_MOUSE_BUTTON = StateKey.get("rightMouseButton");
    83 
    84     public static final FontParameter.Parameter<Integer> FONTPARAM_OFFSET_X = FontParameter.newParameter("offsetX", 0);
    85     public static final FontParameter.Parameter<Integer> FONTPARAM_OFFSET_Y = FontParameter.newParameter("offsetY", 0);
    86     public static final FontParameter.Parameter<Integer> FONTPARAM_UNDERLINE_OFFSET = FontParameter.newParameter("underlineOffset", 0);  
    87     
    88     private final IntBuffer ib16;
    89     final int maxTextureSize;
    90 
    91     private int viewportX;
    92     private int viewportBottom;
    93     private int width;
    94     private int height;
    95     private boolean hasScissor;
    96     private final TintStack tintStateRoot;
    97     private final Cursor emptyCursor;
    98     private boolean useQuadsForLines;
    99     private boolean useSWMouseCursors;
   100     private SWCursor swCursor;
   101     private int mouseX;
   102     private int mouseY;
   103     private LWJGLCacheContext cacheContext;
   104     private FontMapper fontMapper;
   105 
   106     final SWCursorAnimState swCursorAnimState;
   107     final ArrayList<TextureArea> textureAreas;
   108     final ArrayList<TextureAreaRotated> rotatedTextureAreas;
   109     final ArrayList<LWJGLDynamicImage> dynamicImages;
   110     
   111     protected TintStack tintStack;
   112     protected final ClipStack clipStack;
   113     protected final Rect clipRectTemp;
   114     
   115     @SuppressWarnings("OverridableMethodCallInConstructor")
   116     public LWJGLRenderer() throws LWJGLException {
   117         this.ib16 = BufferUtils.createIntBuffer(16);
   118         this.textureAreas = new ArrayList<TextureArea>();
   119         this.rotatedTextureAreas = new ArrayList<TextureAreaRotated>();
   120         this.dynamicImages = new ArrayList<LWJGLDynamicImage>();
   121         this.tintStateRoot = new TintStack();
   122         this.tintStack = tintStateRoot;
   123         this.clipStack = new ClipStack();
   124         this.clipRectTemp = new Rect();
   125         syncViewportSize();
   126 
   127         GL11.glGetInteger(GL11.GL_MAX_TEXTURE_SIZE, ib16);
   128         maxTextureSize = ib16.get(0);
   129 
   130         if(Mouse.isCreated()) {
   131             int minCursorSize = Cursor.getMinCursorSize();
   132             IntBuffer tmp = BufferUtils.createIntBuffer(minCursorSize * minCursorSize);
   133             emptyCursor = new Cursor(minCursorSize, minCursorSize,
   134                     minCursorSize/2, minCursorSize/2, 1, tmp, null);
   135         } else {
   136             emptyCursor = null;
   137         }
   138 
   139         swCursorAnimState = new SWCursorAnimState();
   140     }
   141 
   142     public boolean isUseQuadsForLines() {
   143         return useQuadsForLines;
   144     }
   145 
   146     public void setUseQuadsForLines(boolean useQuadsForLines) {
   147         this.useQuadsForLines = useQuadsForLines;
   148     }
   149 
   150     public boolean isUseSWMouseCursors() {
   151         return useSWMouseCursors;
   152     }
   153 
   154     /**
   155      * Controls if the mouse cursor is rendered via SW or HW cursors.
   156      * HW cursors have reduced support for transparency and cursor size.
   157      *
   158      * This must be set before loading a theme !
   159      * 
   160      * @param useSWMouseCursors
   161      */
   162     public void setUseSWMouseCursors(boolean useSWMouseCursors) {
   163         this.useSWMouseCursors = useSWMouseCursors;
   164     }
   165 
   166     public CacheContext createNewCacheContext() {
   167         return new LWJGLCacheContext(this);
   168     }
   169 
   170     private LWJGLCacheContext activeCacheContext() {
   171         if(cacheContext == null) {
   172             setActiveCacheContext(createNewCacheContext());
   173         }
   174         return cacheContext;
   175     }
   176 
   177     public CacheContext getActiveCacheContext() {
   178         return activeCacheContext();
   179     }
   180 
   181     public void setActiveCacheContext(CacheContext cc) throws IllegalStateException {
   182         if(cc == null) {
   183             throw new NullPointerException();
   184         }
   185         if(!cc.isValid()) {
   186             throw new IllegalStateException("CacheContext is invalid");
   187         }
   188         if(!(cc instanceof LWJGLCacheContext)) {
   189             throw new IllegalArgumentException("CacheContext object not from this renderer");
   190         }
   191         LWJGLCacheContext lwjglCC = (LWJGLCacheContext)cc;
   192         if(lwjglCC.renderer != this) {
   193             throw new IllegalArgumentException("CacheContext object not from this renderer");
   194         }
   195         this.cacheContext = lwjglCC;
   196         try {
   197             for(TextureArea ta : textureAreas) {
   198                 ta.destroyRepeatCache();
   199             }
   200             for(TextureAreaRotated tar : rotatedTextureAreas) {
   201                 tar.destroyRepeatCache();
   202             }
   203         } finally {
   204             textureAreas.clear();
   205             rotatedTextureAreas.clear();
   206         }
   207     }
   208 
   209     /**
   210      * <p>Queries the current view port size & position and updates all related
   211      * internal state.</p>
   212      *
   213      * <p>It is important that the internal state matches the OpenGL viewport or
   214      * clipping won't work correctly.</p>
   215      *
   216      * <p>This method should only be called when the viewport size has changed.
   217      * It can have negative impact on performance to call every frame.</p>
   218      * 
   219      * @see #getWidth()
   220      * @see #getHeight()
   221      */
   222     public void syncViewportSize() {
   223         ib16.clear();
   224         GL11.glGetInteger(GL11.GL_VIEWPORT, ib16);
   225         viewportX = ib16.get(0);
   226         width = ib16.get(2);
   227         height = ib16.get(3);
   228         viewportBottom = ib16.get(1) + height;
   229     }
   230     
   231     /**
   232      * Sets the viewport size & position.
   233      * <p>This method is preferred over {@link #syncViewportSize() } as it avoids
   234      * calling {@link GL11#glGetInteger(int, java.nio.IntBuffer) }.</p>
   235      * 
   236      * @param x the X position (GL_VIEWPORT index 0)
   237      * @param y the Y position (GL_VIEWPORT index 1)
   238      * @param width the width (GL_VIEWPORT index 2)
   239      * @param height the height (GL_VIEWPORT index 3)
   240      */
   241     public void setViewport(int x, int y, int width, int height) {
   242         this.viewportX = x;
   243         this.viewportBottom = y + height;
   244         this.width = width;
   245         this.height = height;
   246     }
   247 
   248     public long getTimeMillis() {
   249         long res = Sys.getTimerResolution();
   250         long time = Sys.getTime();
   251         if(res != 1000) {
   252             time = (time * 1000) / res;
   253         }
   254         return time;
   255     }
   256     
   257     protected void setupGLState() {
   258         GL11.glPushAttrib(GL11.GL_ENABLE_BIT|GL11.GL_TRANSFORM_BIT|GL11.GL_HINT_BIT|
   259                 GL11.GL_COLOR_BUFFER_BIT|GL11.GL_SCISSOR_BIT|GL11.GL_LINE_BIT|GL11.GL_TEXTURE_BIT);
   260         GL11.glMatrixMode(GL11.GL_PROJECTION);
   261         GL11.glPushMatrix();
   262         GL11.glLoadIdentity();
   263         GL11.glOrtho(0, width, height, 0, -1.0, 1.0);
   264         GL11.glMatrixMode(GL11.GL_MODELVIEW);
   265         GL11.glPushMatrix();
   266         GL11.glLoadIdentity();
   267         GL11.glEnable(GL11.GL_TEXTURE_2D);
   268         GL11.glEnable(GL11.GL_BLEND);
   269         GL11.glEnable(GL11.GL_LINE_SMOOTH);
   270         GL11.glDisable(GL11.GL_DEPTH_TEST);
   271         GL11.glDisable(GL11.GL_LIGHTING);
   272         GL11.glDisable(GL11.GL_SCISSOR_TEST);
   273         GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
   274         GL11.glHint(GL11.GL_LINE_SMOOTH_HINT, GL11.GL_NICEST);
   275     }
   276     
   277     protected void revertGLState() {
   278         GL11.glPopMatrix();
   279         GL11.glMatrixMode(GL11.GL_PROJECTION);
   280         GL11.glPopMatrix();
   281         GL11.glPopAttrib();
   282     }
   283     
   284     /**
   285      * Setup GL to start rendering the GUI. It assumes default GL state.
   286      */
   287     public boolean startRendering() {
   288         if(width <= 0 || height <= 0) {
   289             return false;
   290         }
   291         
   292         prepareForRendering();
   293         setupGLState();
   294         
   295         return true;
   296     }
   297 
   298     public void endRendering() {
   299         renderSWCursor();
   300         revertGLState();
   301     }
   302     
   303     /**
   304      * Call to revert the GL state to the state before calling
   305      * {@link #startRendering()}.
   306      * @see #resumeRendering() 
   307      */
   308     public void pauseRendering() {
   309         revertGLState();
   310     }
   311     
   312     /**
   313      * Resume rendering after a call to {@link #pauseRendering()}.
   314      */
   315     public void resumeRendering() {
   316         hasScissor = false;
   317         setupGLState();
   318         setClipRect();
   319     }
   320     
   321     public int getHeight() {
   322         return height;
   323     }
   324 
   325     public int getWidth() {
   326         return width;
   327     }
   328     
   329     /**
   330      * Retrieves the X position of the OpenGL viewport (index 0 of GL_VIEWPORT)
   331      * @return the X position of the OpenGL viewport
   332      */
   333     public int getViewportX() {
   334         return viewportX;
   335     }
   336     
   337     /**
   338      * Retrieves the Y position of the OpenGL viewport (index 1 of GL_VIEWPORT)
   339      * @return the Y position of the OpenGL viewport
   340      */
   341     public int getViewportY() {
   342         return viewportBottom - height;
   343     }
   344 
   345     public Font loadFont(URL url, StateSelect select, FontParameter ... parameterList) throws IOException {
   346         if(url == null) {
   347             throw new NullPointerException("url");
   348         }
   349         if(select == null) {
   350             throw new NullPointerException("select");
   351         }
   352         if(parameterList == null) {
   353             throw new NullPointerException("parameterList");
   354         }
   355         if(select.getNumExpressions() + 1 != parameterList.length) {
   356             throw new IllegalArgumentException("select.getNumExpressions() + 1 != parameterList.length");
   357         }
   358         BitmapFont bmFont = activeCacheContext().loadBitmapFont(url);
   359         return new LWJGLFont(this, bmFont, select, parameterList);
   360     }
   361 
   362     public Texture loadTexture(URL url, String formatStr, String filterStr) throws IOException {
   363         LWJGLTexture.Format format = LWJGLTexture.Format.COLOR;
   364         LWJGLTexture.Filter filter = LWJGLTexture.Filter.LINEAR;
   365         if(formatStr != null) {
   366             try {
   367                 format = LWJGLTexture.Format.valueOf(formatStr.toUpperCase(Locale.ENGLISH));
   368             } catch(IllegalArgumentException ex) {
   369                 getLogger().log(Level.WARNING, "Unknown texture format: {0}", formatStr);
   370             }
   371         }
   372         if(filterStr != null) {
   373             try {
   374                 filter = LWJGLTexture.Filter.valueOf(filterStr.toUpperCase(Locale.ENGLISH));
   375             } catch(IllegalArgumentException ex) {
   376                 getLogger().log(Level.WARNING, "Unknown texture filter: {0}", filterStr);
   377             }
   378         }
   379         return load(url, format, filter);
   380     }
   381 
   382     public LineRenderer getLineRenderer() {
   383         return this;
   384     }
   385 
   386     public OffscreenRenderer getOffscreenRenderer() {
   387         return null;
   388     }
   389 
   390     public FontMapper getFontMapper() {
   391         return fontMapper;
   392     }
   393     
   394     /**
   395      * Installs a font mapper. It is the responsibility of the font mapper to
   396      * manage the OpenGL state correctly so that normal rendering by LWJGLRenderer
   397      * is not disturbed.
   398      * 
   399      * @param fontMapper the font mapper object - can be null.
   400      */
   401     public void setFontMapper(FontMapper fontMapper) {
   402         this.fontMapper = fontMapper;
   403     }
   404     
   405     public DynamicImage createDynamicImage(int width, int height) {
   406         if(width <= 0) {
   407             throw new IllegalArgumentException("width");
   408         }
   409         if(height <= 0) {
   410             throw new IllegalArgumentException("height");
   411         }
   412         if(width > maxTextureSize || height > maxTextureSize) {
   413             getLogger().log(Level.WARNING, "requested size {0} x {1} exceeds maximum texture size {3}",
   414                     new Object[]{ width, height, maxTextureSize });
   415             return null;
   416         }
   417 
   418         int texWidth = width;
   419         int texHeight = height;
   420         
   421         ContextCapabilities caps = GLContext.getCapabilities();
   422         boolean useTextureRectangle = caps.GL_EXT_texture_rectangle || caps.GL_ARB_texture_rectangle;
   423 
   424         if(!useTextureRectangle && !caps.GL_ARB_texture_non_power_of_two) {
   425             texWidth = nextPowerOf2(width);
   426             texHeight = nextPowerOf2(height);
   427         }
   428 
   429         // ARB and EXT versions use the same enum !
   430         int proxyTarget = useTextureRectangle ?
   431             EXTTextureRectangle.GL_PROXY_TEXTURE_RECTANGLE_EXT : GL11.GL_PROXY_TEXTURE_2D;
   432 
   433         GL11.glTexImage2D(proxyTarget, 0, GL11.GL_RGBA, texWidth, texHeight, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer)null);
   434         ib16.clear();
   435         GL11.glGetTexLevelParameter(proxyTarget, 0, GL11.GL_TEXTURE_WIDTH, ib16);
   436         if(ib16.get(0) != texWidth) {
   437             getLogger().log(Level.WARNING, "requested size {0} x {1} failed proxy texture test",
   438                     new Object[]{ texWidth, texHeight });
   439             return null;
   440         }
   441 
   442         // ARB and EXT versions use the same enum !
   443         int target = useTextureRectangle ?
   444             EXTTextureRectangle.GL_TEXTURE_RECTANGLE_EXT : GL11.GL_TEXTURE_2D;
   445         int id = GL11.glGenTextures();
   446 
   447         GL11.glBindTexture(target, id);
   448         GL11.glTexImage2D(target, 0, GL11.GL_RGBA, texWidth, texHeight, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer)null);
   449         GL11.glTexParameteri(target, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
   450         GL11.glTexParameteri(target, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
   451 
   452         LWJGLDynamicImage image = new LWJGLDynamicImage(this, target, id, width, height, texWidth, texHeight, Color.WHITE);
   453         dynamicImages.add(image);
   454         return image;
   455     }
   456 
   457     public Image createGradient(Gradient gradient) {
   458         return new GradientImage(this, gradient);
   459     }
   460 
   461     public void clipEnter(int x, int y, int w, int h) {
   462         clipStack.push(x, y, w, h);
   463         setClipRect();
   464     }
   465 
   466     public void clipEnter(Rect rect) {
   467         clipStack.push(rect);
   468         setClipRect();
   469     }
   470 
   471     public void clipLeave() {
   472         clipStack.pop();
   473         setClipRect();
   474     }
   475 
   476     public boolean clipIsEmpty() {
   477         return clipStack.isClipEmpty();
   478     }
   479 
   480     public void setCursor(MouseCursor cursor) {
   481         try {
   482             swCursor = null;
   483             if(isMouseInsideWindow()) {
   484                 if(cursor instanceof LWJGLCursor) {
   485                     setNativeCursor(((LWJGLCursor)cursor).cursor);
   486                 } else if(cursor instanceof SWCursor) {
   487                     setNativeCursor(emptyCursor);
   488                     swCursor = (SWCursor)cursor;
   489                 } else {
   490                     setNativeCursor(null);
   491                 }
   492             }
   493         } catch(LWJGLException ex) {
   494             getLogger().log(Level.WARNING, "Could not set native cursor", ex);
   495         }
   496     }
   497 
   498     public void setMousePosition(int mouseX, int mouseY) {
   499         this.mouseX = mouseX;
   500         this.mouseY = mouseY;
   501     }
   502 
   503     public void setMouseButton(int button, boolean state) {
   504         swCursorAnimState.setAnimationState(button, state);
   505     }
   506 
   507     public LWJGLTexture load(URL textureUrl, LWJGLTexture.Format fmt, LWJGLTexture.Filter filter) throws IOException {
   508         return load(textureUrl, fmt, filter, null);
   509     }
   510 
   511     public LWJGLTexture load(URL textureUrl, LWJGLTexture.Format fmt, LWJGLTexture.Filter filter, TexturePostProcessing tpp) throws IOException {
   512         if(textureUrl == null) {
   513             throw new NullPointerException("textureUrl");
   514         }
   515         LWJGLCacheContext cc = activeCacheContext();
   516         if(tpp != null) {
   517             return cc.createTexture(textureUrl, fmt, filter, tpp);
   518         } else {
   519             return cc.loadTexture(textureUrl, fmt, filter);
   520         }
   521     }
   522 
   523     public void pushGlobalTintColor(float r, float g, float b, float a) {
   524         tintStack = tintStack.push(r, g, b, a);
   525     }
   526 
   527     public void popGlobalTintColor() {
   528         tintStack = tintStack.pop();
   529     }
   530     
   531     /**
   532      * Pushes a white entry on the tint stack which ignores the previous
   533      * tint color. It must be removed by calling {@link #popGlobalTintColor()}.
   534      * <p>This is useful when rendering to texture</p>
   535      */
   536     public void pushGlobalTintColorReset() {
   537         tintStack = tintStack.pushReset();
   538     }
   539 
   540     /**
   541      * Calls GL11.glColor4f() with the specified color multiplied by the current global tint color.
   542      *
   543      * @param color the color to set
   544      */
   545     public void setColor(Color color) {
   546         tintStack.setColor(color);
   547     }
   548 
   549     public void drawLine(float[] pts, int numPts, float width, Color color, boolean drawAsLoop) {
   550         if(numPts*2 > pts.length) {
   551             throw new ArrayIndexOutOfBoundsException(numPts*2);
   552         }
   553         if(numPts >= 2) {
   554             tintStack.setColor(color);
   555             GL11.glDisable(GL11.GL_TEXTURE_2D);
   556             if(useQuadsForLines) {
   557                 drawLinesAsQuads(numPts, pts, width, drawAsLoop);
   558             } else {
   559                 drawLinesAsLines(numPts, pts, width, drawAsLoop);
   560             }
   561             GL11.glEnable(GL11.GL_TEXTURE_2D);
   562         }
   563     }
   564     
   565     private void drawLinesAsLines(int numPts, float[] pts, float width, boolean drawAsLoop) {
   566         GL11.glLineWidth(width);
   567         GL11.glBegin(drawAsLoop ? GL11.GL_LINE_LOOP : GL11.GL_LINE_STRIP);
   568         for(int i=0 ; i<numPts ; i++) {
   569             GL11.glVertex2f(pts[i*2+0], pts[i*2+1]);
   570         }
   571         GL11.glEnd();
   572     }
   573 
   574     private void drawLinesAsQuads(int numPts, float[] pts, float width, boolean drawAsLoop) {
   575         width *= 0.5f;
   576         GL11.glBegin(GL11.GL_QUADS);
   577         for(int i = 1 ; i < numPts ; i++) {
   578             drawLineAsQuad(pts[i * 2 - 2], pts[i * 2 - 1], pts[i * 2 + 0], pts[i * 2 + 1], width);
   579         }
   580         if(drawAsLoop) {
   581             int idx = numPts * 2;
   582             drawLineAsQuad(pts[idx], pts[idx + 1], pts[0], pts[1], width);
   583         }
   584         GL11.glEnd();
   585     }
   586 
   587     private static void drawLineAsQuad(float x0, float y0, float x1, float y1, float w) {
   588         float dx = x1 - x0;
   589         float dy = y1 - y0;
   590         float l = (float)Math.sqrt(dx*dx + dy*dy) / w;
   591         dx /= l;
   592         dy /= l;
   593         GL11.glVertex2f(x0 - dx + dy, y0 - dy - dx);
   594         GL11.glVertex2f(x0 - dx - dy, y0 - dy + dx);
   595         GL11.glVertex2f(x1 + dx - dy, y1 + dy + dx);
   596         GL11.glVertex2f(x1 + dx + dy, y1 + dy - dx);
   597     }
   598 
   599     protected void prepareForRendering() {
   600         hasScissor = false;
   601         tintStack = tintStateRoot;
   602         clipStack.clearStack();
   603     }
   604 
   605     protected void renderSWCursor() {
   606         if(swCursor != null) {
   607             tintStack = tintStateRoot;
   608             swCursor.render(mouseX, mouseY);
   609         }
   610     }
   611     
   612     protected void setNativeCursor(Cursor cursor) throws LWJGLException {
   613         Mouse.setNativeCursor(cursor);
   614     }
   615     
   616     protected boolean isMouseInsideWindow() {
   617         return Mouse.isInsideWindow();
   618     }
   619 
   620     protected void getTintedColor(Color color, float[] result) {
   621         result[0] = tintStack.r*color.getRed();
   622         result[1] = tintStack.g*color.getGreen();
   623         result[2] = tintStack.b*color.getBlue();
   624         result[3] = tintStack.a*color.getAlpha();
   625     }
   626     
   627     /**
   628      * Computes the tinted color from the given color.
   629      * @param color the input color in RGBA order, value range is 0.0 (black) to 255.0 (white).
   630      * @param result the tinted color in RGBA order, can be the same array as color.
   631      */
   632     protected void getTintedColor(float[] color, float[] result) {
   633         result[0] = tintStack.r*color[0];
   634         result[1] = tintStack.g*color[1];
   635         result[2] = tintStack.b*color[2];
   636         result[3] = tintStack.a*color[3];
   637     }
   638 
   639     protected void setClipRect() {
   640         final Rect rect = clipRectTemp;
   641         if(clipStack.getClipRect(rect)) {
   642             GL11.glScissor(viewportX + rect.getX(), viewportBottom - rect.getBottom(), rect.getWidth(), rect.getHeight());
   643             if(!hasScissor) {
   644                 GL11.glEnable(GL11.GL_SCISSOR_TEST);
   645                 hasScissor = true;
   646             }
   647         } else if(hasScissor) {
   648             GL11.glDisable(GL11.GL_SCISSOR_TEST);
   649             hasScissor = false;
   650         }
   651     }
   652 
   653     Logger getLogger() {
   654         return Logger.getLogger(LWJGLRenderer.class.getName());
   655     }
   656     
   657     /**
   658      * If the passed value is not a power of 2 then return the next highest power of 2
   659      * otherwise the value is returned unchanged.
   660      * 
   661      * <p> Warren Jr., Henry S. (2002). Hacker's Delight. Addison Wesley. pp. 48. ISBN 978-0201914658</p>
   662      * 
   663      * @param i a non negative number &lt;= 2^31
   664      * @return the smallest power of 2 which is &gt;= i
   665      */
   666     private static int nextPowerOf2(int i) {
   667         i--;
   668         i |= (i >>  1);
   669         i |= (i >>  2);
   670         i |= (i >>  4);
   671         i |= (i >>  8);
   672         i |= (i >> 16);
   673         return i+1;
   674     }
   675 
   676     private static class SWCursorAnimState implements AnimationState {
   677         private final long[] lastTime;
   678         private final boolean[] active;
   679 
   680         SWCursorAnimState() {
   681             lastTime = new long[3];
   682             active = new boolean[3];
   683         }
   684 
   685         void setAnimationState(int idx, boolean isActive) {
   686             if(idx >= 0 && idx < 3 && active[idx] != isActive) {
   687                 lastTime[idx] = Sys.getTime();
   688                 active[idx] = isActive;
   689             }
   690         }
   691 
   692         public int getAnimationTime(StateKey state) {
   693             long curTime = Sys.getTime();
   694             int idx = getMouseButton(state);
   695             if(idx >= 0) {
   696                 curTime -= lastTime[idx];
   697             }
   698             return (int)curTime & Integer.MAX_VALUE;
   699         }
   700 
   701         public boolean getAnimationState(StateKey state) {
   702             int idx = getMouseButton(state);
   703             if(idx >= 0) {
   704                 return active[idx];
   705             }
   706             return false;
   707         }
   708 
   709         public boolean getShouldAnimateState(StateKey state) {
   710             return true;
   711         }
   712 
   713         private int getMouseButton(StateKey key) {
   714             if(key == STATE_LEFT_MOUSE_BUTTON) {
   715                 return Event.MOUSE_LBUTTON;
   716             }
   717             if(key == STATE_MIDDLE_MOUSE_BUTTON) {
   718                 return Event.MOUSE_MBUTTON;
   719             }
   720             if(key == STATE_RIGHT_MOUSE_BUTTON) {
   721                 return Event.MOUSE_RBUTTON;
   722             }
   723             return -1;
   724         }
   725     }
   726 }