public GdxFont( GdxRenderer renderer, BitmapFont bitmapFont, Map<String, String> params, Collection<FontParameter> condParams) { this.bitmapFont = bitmapFont; this.renderer = renderer; yOffset = -bitmapFont.getAscent(); ArrayList<FontState> states = new ArrayList<FontState>(); for (FontParameter p : condParams) { HashMap<String, String> effective = new HashMap<String, String>(params); effective.putAll(p.getParams()); states.add(new FontState(p.getCondition(), effective)); } states.add(new FontState(null, params)); this.fontStates = states.toArray(new FontState[states.size()]); }
/** * A renderer using only GL11 features. * * <p>For correct rendering the OpenGL viewport size must be synchronized. * * @author Matthias Mann * @see #syncViewportSize() */ public class LWJGLRenderer implements Renderer, LineRenderer { public static final StateKey STATE_LEFT_MOUSE_BUTTON = StateKey.get("leftMouseButton"); public static final StateKey STATE_MIDDLE_MOUSE_BUTTON = StateKey.get("middleMouseButton"); public static final StateKey STATE_RIGHT_MOUSE_BUTTON = StateKey.get("rightMouseButton"); public static final FontParameter.Parameter<Integer> FONTPARAM_OFFSET_X = FontParameter.newParameter("offsetX", 0); public static final FontParameter.Parameter<Integer> FONTPARAM_OFFSET_Y = FontParameter.newParameter("offsetY", 0); public static final FontParameter.Parameter<Integer> FONTPARAM_UNDERLINE_OFFSET = FontParameter.newParameter("underlineOffset", 0); private final IntBuffer ib16; final int maxTextureSize; private int viewportX; private int viewportBottom; private int width; private int height; private boolean hasScissor; private final TintStack tintStateRoot; private final Cursor emptyCursor; private boolean useQuadsForLines; private boolean useSWMouseCursors; private SWCursor swCursor; private int mouseX; private int mouseY; private LWJGLCacheContext cacheContext; private FontMapper fontMapper; final SWCursorAnimState swCursorAnimState; final ArrayList<TextureArea> textureAreas; final ArrayList<TextureAreaRotated> rotatedTextureAreas; final ArrayList<LWJGLDynamicImage> dynamicImages; protected TintStack tintStack; protected final ClipStack clipStack; protected final Rect clipRectTemp; @SuppressWarnings("OverridableMethodCallInConstructor") public LWJGLRenderer() throws LWJGLException { this.ib16 = BufferUtils.createIntBuffer(16); this.textureAreas = new ArrayList<TextureArea>(); this.rotatedTextureAreas = new ArrayList<TextureAreaRotated>(); this.dynamicImages = new ArrayList<LWJGLDynamicImage>(); this.tintStateRoot = new TintStack(); this.tintStack = tintStateRoot; this.clipStack = new ClipStack(); this.clipRectTemp = new Rect(); syncViewportSize(); GL11.glGetInteger(GL11.GL_MAX_TEXTURE_SIZE, ib16); maxTextureSize = ib16.get(0); if (Mouse.isCreated()) { int minCursorSize = Cursor.getMinCursorSize(); IntBuffer tmp = BufferUtils.createIntBuffer(minCursorSize * minCursorSize); emptyCursor = new Cursor( minCursorSize, minCursorSize, minCursorSize / 2, minCursorSize / 2, 1, tmp, null); } else { emptyCursor = null; } swCursorAnimState = new SWCursorAnimState(); } public boolean isUseQuadsForLines() { return useQuadsForLines; } public void setUseQuadsForLines(boolean useQuadsForLines) { this.useQuadsForLines = useQuadsForLines; } public boolean isUseSWMouseCursors() { return useSWMouseCursors; } /** * Controls if the mouse cursor is rendered via SW or HW cursors. HW cursors have reduced support * for transparency and cursor size. * * <p>This must be set before loading a theme ! * * @param useSWMouseCursors */ public void setUseSWMouseCursors(boolean useSWMouseCursors) { this.useSWMouseCursors = useSWMouseCursors; } public CacheContext createNewCacheContext() { return new LWJGLCacheContext(this); } private LWJGLCacheContext activeCacheContext() { if (cacheContext == null) { setActiveCacheContext(createNewCacheContext()); } return cacheContext; } public CacheContext getActiveCacheContext() { return activeCacheContext(); } public void setActiveCacheContext(CacheContext cc) throws IllegalStateException { if (cc == null) { throw new NullPointerException(); } if (!cc.isValid()) { throw new IllegalStateException("CacheContext is invalid"); } if (!(cc instanceof LWJGLCacheContext)) { throw new IllegalArgumentException("CacheContext object not from this renderer"); } LWJGLCacheContext lwjglCC = (LWJGLCacheContext) cc; if (lwjglCC.renderer != this) { throw new IllegalArgumentException("CacheContext object not from this renderer"); } this.cacheContext = lwjglCC; try { for (TextureArea ta : textureAreas) { ta.destroyRepeatCache(); } for (TextureAreaRotated tar : rotatedTextureAreas) { tar.destroyRepeatCache(); } } finally { textureAreas.clear(); rotatedTextureAreas.clear(); } } /** * Queries the current view port size & position and updates all related internal state. * * <p>It is important that the internal state matches the OpenGL viewport or clipping won't work * correctly. * * <p>This method should only be called when the viewport size has changed. It can have negative * impact on performance to call every frame. * * @see #getWidth() * @see #getHeight() */ public void syncViewportSize() { ib16.clear(); GL11.glGetInteger(GL11.GL_VIEWPORT, ib16); viewportX = ib16.get(0); width = ib16.get(2); height = ib16.get(3); viewportBottom = ib16.get(1) + height; } /** * Sets the viewport size & position. * * <p>This method is preferred over {@link #syncViewportSize() } as it avoids calling {@link * GL11#glGetInteger(int, java.nio.IntBuffer) }. * * @param x the X position (GL_VIEWPORT index 0) * @param y the Y position (GL_VIEWPORT index 1) * @param width the width (GL_VIEWPORT index 2) * @param height the height (GL_VIEWPORT index 3) */ public void setViewport(int x, int y, int width, int height) { this.viewportX = x; this.viewportBottom = y + height; this.width = width; this.height = height; } public long getTimeMillis() { long res = Sys.getTimerResolution(); long time = Sys.getTime(); if (res != 1000) { time = (time * 1000) / res; } return time; } protected void setupGLState() { GL11.glPushAttrib( GL11.GL_ENABLE_BIT | GL11.GL_TRANSFORM_BIT | GL11.GL_HINT_BIT | GL11.GL_COLOR_BUFFER_BIT | GL11.GL_SCISSOR_BIT | GL11.GL_LINE_BIT | GL11.GL_TEXTURE_BIT); GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glPushMatrix(); GL11.glLoadIdentity(); GL11.glOrtho(0, width, height, 0, -1.0, 1.0); GL11.glMatrixMode(GL11.GL_MODELVIEW); GL11.glPushMatrix(); GL11.glLoadIdentity(); GL11.glEnable(GL11.GL_TEXTURE_2D); GL11.glEnable(GL11.GL_BLEND); GL11.glEnable(GL11.GL_LINE_SMOOTH); GL11.glDisable(GL11.GL_DEPTH_TEST); GL11.glDisable(GL11.GL_LIGHTING); GL11.glDisable(GL11.GL_SCISSOR_TEST); GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); GL11.glHint(GL11.GL_LINE_SMOOTH_HINT, GL11.GL_NICEST); } protected void revertGLState() { GL11.glPopMatrix(); GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glPopMatrix(); GL11.glPopAttrib(); } /** Setup GL to start rendering the GUI. It assumes default GL state. */ public boolean startRendering() { if (width <= 0 || height <= 0) { return false; } prepareForRendering(); setupGLState(); RenderScale.doscale(); return true; } public void endRendering() { renderSWCursor(); RenderScale.descale(); revertGLState(); } /** * Call to revert the GL state to the state before calling {@link #startRendering()}. * * @see #resumeRendering() */ public void pauseRendering() { RenderScale.descale(); revertGLState(); } /** Resume rendering after a call to {@link #pauseRendering()}. */ public void resumeRendering() { hasScissor = false; setupGLState(); RenderScale.doscale(); setClipRect(); } public int getHeight() { return height; } public int getWidth() { return width; } /** * Retrieves the X position of the OpenGL viewport (index 0 of GL_VIEWPORT) * * @return the X position of the OpenGL viewport */ public int getViewportX() { return viewportX; } /** * Retrieves the Y position of the OpenGL viewport (index 1 of GL_VIEWPORT) * * @return the Y position of the OpenGL viewport */ public int getViewportY() { return viewportBottom - height; } public Font loadFont(URL url, StateSelect select, FontParameter... parameterList) throws IOException { if (url == null) { throw new NullPointerException("url"); } if (select == null) { throw new NullPointerException("select"); } if (parameterList == null) { throw new NullPointerException("parameterList"); } if (select.getNumExpressions() + 1 != parameterList.length) { throw new IllegalArgumentException("select.getNumExpressions() + 1 != parameterList.length"); } BitmapFont bmFont = activeCacheContext().loadBitmapFont(url); return new LWJGLFont(this, bmFont, select, parameterList); } public Texture loadTexture(URL url, String formatStr, String filterStr) throws IOException { LWJGLTexture.Format format = LWJGLTexture.Format.COLOR; LWJGLTexture.Filter filter = LWJGLTexture.Filter.NEAREST; if (formatStr != null) { try { format = LWJGLTexture.Format.valueOf(formatStr.toUpperCase(Locale.ENGLISH)); } catch (IllegalArgumentException ex) { getLogger().log(Level.WARNING, "Unknown texture format: {0}", formatStr); } } if (filterStr != null) { try { filter = LWJGLTexture.Filter.valueOf(filterStr.toUpperCase(Locale.ENGLISH)); } catch (IllegalArgumentException ex) { getLogger().log(Level.WARNING, "Unknown texture filter: {0}", filterStr); } } return load(url, format, filter); } public LineRenderer getLineRenderer() { return this; } public OffscreenRenderer getOffscreenRenderer() { return null; } public FontMapper getFontMapper() { return fontMapper; } /** * Installs a font mapper. It is the responsibility of the font mapper to manage the OpenGL state * correctly so that normal rendering by LWJGLRenderer is not disturbed. * * @param fontMapper the font mapper object - can be null. */ public void setFontMapper(FontMapper fontMapper) { this.fontMapper = fontMapper; } public DynamicImage createDynamicImage(int width, int height) { if (width <= 0) { throw new IllegalArgumentException("width"); } if (height <= 0) { throw new IllegalArgumentException("height"); } if (width > maxTextureSize || height > maxTextureSize) { getLogger() .log( Level.WARNING, "requested size {0} x {1} exceeds maximum texture size {3}", new Object[] {width, height, maxTextureSize}); return null; } int texWidth = width; int texHeight = height; ContextCapabilities caps = GLContext.getCapabilities(); boolean useTextureRectangle = caps.GL_EXT_texture_rectangle || caps.GL_ARB_texture_rectangle; if (!useTextureRectangle && !caps.GL_ARB_texture_non_power_of_two) { texWidth = nextPowerOf2(width); texHeight = nextPowerOf2(height); } // ARB and EXT versions use the same enum ! int proxyTarget = useTextureRectangle ? EXTTextureRectangle.GL_PROXY_TEXTURE_RECTANGLE_EXT : GL11.GL_PROXY_TEXTURE_2D; GL11.glTexImage2D( proxyTarget, 0, GL11.GL_RGBA, texWidth, texHeight, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null); ib16.clear(); GL11.glGetTexLevelParameter(proxyTarget, 0, GL11.GL_TEXTURE_WIDTH, ib16); if (ib16.get(0) != texWidth) { getLogger() .log( Level.WARNING, "requested size {0} x {1} failed proxy texture test", new Object[] {texWidth, texHeight}); return null; } // ARB and EXT versions use the same enum ! int target = useTextureRectangle ? EXTTextureRectangle.GL_TEXTURE_RECTANGLE_EXT : GL11.GL_TEXTURE_2D; int id = GL11.glGenTextures(); GL11.glBindTexture(target, id); GL11.glTexImage2D( target, 0, GL11.GL_RGBA, texWidth, texHeight, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null); GL11.glTexParameteri(target, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST); GL11.glTexParameteri(target, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST); LWJGLDynamicImage image = new LWJGLDynamicImage(this, target, id, width, height, texWidth, texHeight, Color.WHITE); dynamicImages.add(image); return image; } public Image createGradient(Gradient gradient) { return new GradientImage(this, gradient); } public void clipEnter(int x, int y, int w, int h) { clipStack.push(x, y, w, h); setClipRect(); } public void clipEnter(Rect rect) { clipStack.push(rect); setClipRect(); } public void clipLeave() { clipStack.pop(); setClipRect(); } public boolean clipIsEmpty() { return clipStack.isClipEmpty(); } public void setCursor(MouseCursor cursor) { try { swCursor = null; if (isMouseInsideWindow()) { if (cursor instanceof LWJGLCursor) { setNativeCursor(((LWJGLCursor) cursor).cursor); } else if (cursor instanceof SWCursor) { setNativeCursor(emptyCursor); swCursor = (SWCursor) cursor; } else { setNativeCursor(null); } } } catch (LWJGLException ex) { getLogger().log(Level.WARNING, "Could not set native cursor", ex); } } public void setMousePosition(int mouseX, int mouseY) { this.mouseX = mouseX; this.mouseY = mouseY; } public void setMouseButton(int button, boolean state) { swCursorAnimState.setAnimationState(button, state); } public LWJGLTexture load(URL textureUrl, LWJGLTexture.Format fmt, LWJGLTexture.Filter filter) throws IOException { return load(textureUrl, fmt, filter, null); } public LWJGLTexture load( URL textureUrl, LWJGLTexture.Format fmt, LWJGLTexture.Filter filter, TexturePostProcessing tpp) throws IOException { if (textureUrl == null) { throw new NullPointerException("textureUrl"); } LWJGLCacheContext cc = activeCacheContext(); if (tpp != null) { return cc.createTexture(textureUrl, fmt, filter, tpp); } else { return cc.loadTexture(textureUrl, fmt, filter); } } public void pushGlobalTintColor(float r, float g, float b, float a) { tintStack = tintStack.push(r, g, b, a); } public void popGlobalTintColor() { tintStack = tintStack.pop(); } /** * Pushes a white entry on the tint stack which ignores the previous tint color. It must be * removed by calling {@link #popGlobalTintColor()}. * * <p>This is useful when rendering to texture */ public void pushGlobalTintColorReset() { tintStack = tintStack.pushReset(); } /** * Calls GL11.glColor4f() with the specified color multiplied by the current global tint color. * * @param color the color to set */ public void setColor(Color color) { tintStack.setColor(color); } public void drawLine(float[] pts, int numPts, float width, Color color, boolean drawAsLoop) { if (numPts * 2 > pts.length) { throw new ArrayIndexOutOfBoundsException(numPts * 2); } if (numPts >= 2) { tintStack.setColor(color); GL11.glDisable(GL11.GL_TEXTURE_2D); if (useQuadsForLines) { drawLinesAsQuads(numPts, pts, width, drawAsLoop); } else { drawLinesAsLines(numPts, pts, width, drawAsLoop); } GL11.glEnable(GL11.GL_TEXTURE_2D); } } private void drawLinesAsLines(int numPts, float[] pts, float width, boolean drawAsLoop) { GL11.glLineWidth(width); GL11.glBegin(drawAsLoop ? GL11.GL_LINE_LOOP : GL11.GL_LINE_STRIP); for (int i = 0; i < numPts; i++) { GL11.glVertex2f(pts[i * 2 + 0], pts[i * 2 + 1]); } GL11.glEnd(); } private void drawLinesAsQuads(int numPts, float[] pts, float width, boolean drawAsLoop) { width *= 0.5f; GL11.glBegin(GL11.GL_QUADS); for (int i = 1; i < numPts; i++) { drawLineAsQuad(pts[i * 2 - 2], pts[i * 2 - 1], pts[i * 2 + 0], pts[i * 2 + 1], width); } if (drawAsLoop) { int idx = numPts * 2; drawLineAsQuad(pts[idx], pts[idx + 1], pts[0], pts[1], width); } GL11.glEnd(); } private static void drawLineAsQuad(float x0, float y0, float x1, float y1, float w) { float dx = x1 - x0; float dy = y1 - y0; float l = (float) Math.sqrt(dx * dx + dy * dy) / w; dx /= l; dy /= l; GL11.glVertex2f(x0 - dx + dy, y0 - dy - dx); GL11.glVertex2f(x0 - dx - dy, y0 - dy + dx); GL11.glVertex2f(x1 + dx - dy, y1 + dy + dx); GL11.glVertex2f(x1 + dx + dy, y1 + dy - dx); } protected void prepareForRendering() { hasScissor = false; tintStack = tintStateRoot; clipStack.clearStack(); } protected void renderSWCursor() { if (swCursor != null) { tintStack = tintStateRoot; swCursor.render(mouseX, mouseY); } } protected void setNativeCursor(Cursor cursor) throws LWJGLException { Mouse.setNativeCursor(cursor); } protected boolean isMouseInsideWindow() { return Mouse.isInsideWindow(); } protected void getTintedColor(Color color, float[] result) { result[0] = tintStack.r * color.getRed(); result[1] = tintStack.g * color.getGreen(); result[2] = tintStack.b * color.getBlue(); result[3] = tintStack.a * color.getAlpha(); } /** * Computes the tinted color from the given color. * * @param color the input color in RGBA order, value range is 0.0 (black) to 255.0 (white). * @param result the tinted color in RGBA order, can be the same array as color. */ protected void getTintedColor(float[] color, float[] result) { result[0] = tintStack.r * color[0]; result[1] = tintStack.g * color[1]; result[2] = tintStack.b * color[2]; result[3] = tintStack.a * color[3]; } public void setClipRect() { final Rect rect = clipRectTemp; if (clipStack.getClipRect(rect)) { GL11.glScissor( viewportX + rect.getX() * RenderScale.scale, viewportBottom - rect.getBottom() * RenderScale.scale, rect.getWidth() * RenderScale.scale, rect.getHeight() * RenderScale.scale); if (!hasScissor) { GL11.glEnable(GL11.GL_SCISSOR_TEST); hasScissor = true; } } else if (hasScissor) { GL11.glDisable(GL11.GL_SCISSOR_TEST); hasScissor = false; } } /** * Retrieves the active clip region from the top of the stack * * @param rect the rect coordinates - may not be updated when clipping is disabled * @return true if clipping is active, false if clipping is disabled */ public boolean getClipRect(Rect rect) { return clipStack.getClipRect(rect); } Logger getLogger() { return Logger.getLogger(LWJGLRenderer.class.getName()); } /** * If the passed value is not a power of 2 then return the next highest power of 2 otherwise the * value is returned unchanged. * * <p>Warren Jr., Henry S. (2002). Hacker's Delight. Addison Wesley. pp. 48. ISBN 978-0201914658 * * @param i a non negative number <= 2^31 * @return the smallest power of 2 which is >= i */ private static int nextPowerOf2(int i) { i--; i |= (i >> 1); i |= (i >> 2); i |= (i >> 4); i |= (i >> 8); i |= (i >> 16); return i + 1; } private static class SWCursorAnimState implements AnimationState { private final long[] lastTime; private final boolean[] active; SWCursorAnimState() { lastTime = new long[3]; active = new boolean[3]; } void setAnimationState(int idx, boolean isActive) { if (idx >= 0 && idx < 3 && active[idx] != isActive) { lastTime[idx] = Sys.getTime(); active[idx] = isActive; } } public int getAnimationTime(StateKey state) { long curTime = Sys.getTime(); int idx = getMouseButton(state); if (idx >= 0) { curTime -= lastTime[idx]; } return (int) curTime & Integer.MAX_VALUE; } public boolean getAnimationState(StateKey state) { int idx = getMouseButton(state); if (idx >= 0) { return active[idx]; } return false; } public boolean getShouldAnimateState(StateKey state) { return true; } private int getMouseButton(StateKey key) { if (key == STATE_LEFT_MOUSE_BUTTON) { return Event.MOUSE_LBUTTON; } if (key == STATE_MIDDLE_MOUSE_BUTTON) { return Event.MOUSE_MBUTTON; } if (key == STATE_RIGHT_MOUSE_BUTTON) { return Event.MOUSE_RBUTTON; } return -1; } } }