/******************************************************************************* * Copyright (c) 2015 * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *******************************************************************************/ package go.graphics.swing.opengl; import go.graphics.GLDrawContext; import go.graphics.GeometryHandle; import go.graphics.IllegalBufferException; import go.graphics.TextureHandle; import go.graphics.swing.opengl.JOGLBufferHandle.JOGLGeometryHandle; import go.graphics.swing.opengl.JOGLBufferHandle.JOGLTextureHandle; import go.graphics.swing.text.JOGLTextDrawer; import go.graphics.text.EFontSize; import go.graphics.text.TextDrawer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.nio.ShortBuffer; import java.util.ArrayList; import com.jogamp.common.nio.Buffers; import com.jogamp.opengl.GL; import com.jogamp.opengl.GL2; /** * This is the draw context implementation for JOGL. OpenGL draw calles are mapped to the corresponding JOGL calls. * * @author Michael Zangl * */ public class JOGLDrawContext implements GLDrawContext { private JOGLTextDrawer[] textDrawers = new JOGLTextDrawer[EFontSize .values().length]; private static final int FLOATS_PER_COLORED_TRI_VERTEX = 9; private final GL2 gl2; private final boolean canUseVBOs; public JOGLDrawContext(GL2 gl2) { this.gl2 = gl2; gl2.glEnableClientState(GL2.GL_VERTEX_ARRAY); gl2.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY); gl2.glAlphaFunc(GL2.GL_GREATER, 0.1f); gl2.glEnable(GL2.GL_ALPHA_TEST); gl2.glDepthFunc(GL2.GL_LEQUAL); gl2.glEnable(GL2.GL_DEPTH_TEST); gl2.glEnable(GL2.GL_TEXTURE_2D); canUseVBOs = gl2.isExtensionAvailable("GL_ARB_vertex_buffer_object"); } public void startFrame() { gl2.glClear(GL2.GL_DEPTH_BUFFER_BIT); } @Override public void color(float r, float g, float b, float a) { gl2.glColor4f(r, g, b, a); } @Override public void fillQuad(float x1, float y1, float x2, float y2) { ByteBuffer quadPoints = ByteBuffer.allocateDirect(4 * 2 * 4); quadPoints.order(ByteOrder.nativeOrder()); FloatBuffer floatBuff = quadPoints.asFloatBuffer(); floatBuff.put(x1); floatBuff.put(y1); floatBuff.put(x1); floatBuff.put(y2); floatBuff.put(x2); floatBuff.put(y2); floatBuff.put(x2); floatBuff.put(y1); floatBuff.position(0); gl2.glBindTexture(GL.GL_TEXTURE_2D, 0); gl2.glDisableClientState(GL2.GL_TEXTURE_COORD_ARRAY); gl2.glVertexPointer(2, GL2.GL_FLOAT, 0, floatBuff); gl2.glDrawArrays(GL2.GL_QUADS, 0, 4); gl2.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY); } @Override public void drawLine(float[] points, boolean loop) { if (points.length % 3 != 0) { throw new IllegalArgumentException( "Point array length needs to be multiple of 3."); } ByteBuffer floatBuff = generateTemporaryFloatBuffer(points); gl2.glBindTexture(GL.GL_TEXTURE_2D, 0); gl2.glDisableClientState(GL2.GL_TEXTURE_COORD_ARRAY); gl2.glVertexPointer(3, GL2.GL_FLOAT, 0, floatBuff); gl2.glDrawArrays(loop ? GL2.GL_LINE_LOOP : GL2.GL_LINE_STRIP, 0, points.length / 3); gl2.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY); } private ByteBuffer reuseableBuffer = null; private ArrayList<ByteBuffer> geometries = new ArrayList<ByteBuffer>(); /** * The global context valid flag. As soon as this is set to false, the context is not valid any more. */ private boolean contextValid = true; private ByteBuffer generateTemporaryFloatBuffer(float[] points) { FloatBuffer buffer; if (reuseableBuffer == null || reuseableBuffer.capacity() < points.length * 4) { reuseableBuffer = ByteBuffer.allocateDirect(points.length * 4); reuseableBuffer.order(ByteOrder.nativeOrder()); } else { reuseableBuffer.position(0); } buffer = reuseableBuffer.asFloatBuffer(); buffer.put(points); buffer.position(0); return reuseableBuffer; } private static ByteBuffer genertateBuffer(float[] points) { ByteBuffer bb = ByteBuffer.allocateDirect(points.length * 4); bb.order(ByteOrder.nativeOrder()); bb.asFloatBuffer().put(points); bb.position(0); return bb; } @Override public void glPushMatrix() { gl2.glPushMatrix(); } @Override public void glTranslatef(float x, float y, float z) { gl2.glTranslatef(x, y, z); } @Override public void glPopMatrix() { gl2.glPopMatrix(); } @Deprecated public GL2 getGl2() { return gl2; } @Override public void glScalef(float x, float y, float z) { gl2.glScalef(x, y, z); } @Override public TextureHandle generateTexture(int width, int height, ShortBuffer data) { // 1 byte aligned. gl2.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1); int[] textureIndexes = new int[1]; gl2.glGenTextures(1, textureIndexes, 0); int texture = textureIndexes[0]; if (texture == 0) { return null; } gl2.glBindTexture(GL.GL_TEXTURE_2D, texture); gl2.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGB5_A1, width, height, 0, GL.GL_RGBA, GL.GL_UNSIGNED_SHORT_5_5_5_1, data); setTextureParameters(); return new JOGLTextureHandle(this, texture); } /** * Sets the texture parameters, assuming that the texture was just created and is bound. */ private void setTextureParameters() { gl2.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL2.GL_CLAMP); gl2.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL2.GL_CLAMP); gl2.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST); gl2.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST); } @Override public void updateTexture(TextureHandle texture, int left, int bottom, int width, int height, ShortBuffer data) throws IllegalBufferException { bindTexture(texture); gl2.glTexSubImage2D(GL2.GL_TEXTURE_2D, 0, left, bottom, width, height, GL2.GL_RGBA, GL2.GL_UNSIGNED_SHORT_5_5_5_1, data); } private void bindTexture(TextureHandle texture) throws IllegalBufferException { int id; if (texture == null) { id = 0; } else { if (!texture.isValid()) { throw new IllegalBufferException("Texture handle is not valid: " + texture); } id = texture.getInternalId(); } gl2.glBindTexture(GL.GL_TEXTURE_2D, id); } void deleteTexture(int textureIndex) { gl2.glDeleteTextures(1, new int[] { textureIndex }, 0); } private void bindArrayBuffer(GeometryHandle geometry) throws IllegalBufferException { int id; if (geometry == null) { id = 0; } else { if (!geometry.isValid()) { throw new IllegalBufferException("Geometry handle is not valid: " + geometry); } id = geometry.getInternalId(); } gl2.glBindBuffer(GL2.GL_ARRAY_BUFFER, id); } @Override public void drawQuadWithTexture(TextureHandle texture, float[] geometry) throws IllegalBufferException { ByteBuffer buffer = generateTemporaryFloatBuffer(geometry); drawQuadWithTexture(texture, buffer, geometry.length / 5); } private void drawQuadWithTexture(TextureHandle texture, ByteBuffer buffer, int len) throws IllegalBufferException { bindTexture(texture); gl2.glVertexPointer(3, GL2.GL_FLOAT, 5 * 4, buffer); buffer.position(3 * 4); gl2.glTexCoordPointer(2, GL2.GL_FLOAT, 5 * 4, buffer); gl2.glDrawArrays(GL2.GL_QUADS, 0, len); } @Override public void drawTrianglesWithTexture(TextureHandle texture, float[] geometry) throws IllegalBufferException { ByteBuffer buffer = generateTemporaryFloatBuffer(geometry); drawTrianglesWithTexture(texture, buffer, geometry.length / 5 / 3); } private void drawTrianglesWithTexture(TextureHandle texture, ByteBuffer buffer, int triangles) throws IllegalBufferException { bindTexture(texture); gl2.glVertexPointer(3, GL2.GL_FLOAT, 5 * 4, buffer); buffer.position(3 * 4); gl2.glTexCoordPointer(2, GL2.GL_FLOAT, 5 * 4, buffer); gl2.glDrawArrays(GL2.GL_TRIANGLES, 0, triangles * 3); } @Override public void drawTrianglesWithTextureColored(TextureHandle texture, float[] geometry) throws IllegalBufferException { ByteBuffer buffer = generateTemporaryFloatBuffer(geometry); drawTrianglesWithTextureColored(texture, buffer, geometry.length / 3 / FLOATS_PER_COLORED_TRI_VERTEX); } @Override public void drawTrianglesWithTextureColored(TextureHandle texture, ByteBuffer buffer, int triangles) throws IllegalBufferException { bindTexture(texture); gl2.glVertexPointer(3, GL2.GL_FLOAT, 6 * 4, buffer); buffer.position(3 * 4); gl2.glTexCoordPointer(2, GL2.GL_FLOAT, 6 * 4, buffer); buffer.position(5 * 4); gl2.glColorPointer(4, GL2.GL_UNSIGNED_BYTE, 6 * 4, buffer); gl2.glEnableClientState(GL2.GL_COLOR_ARRAY); gl2.glDrawArrays(GL2.GL_TRIANGLES, 0, triangles * 3); gl2.glDisableClientState(GL2.GL_COLOR_ARRAY); } @Override public int makeWidthValid(int width) { return TextureCalculator.supportedTextureSize(gl2, width); } @Override public int makeHeightValid(int height) { return TextureCalculator.supportedTextureSize(gl2, height); } @Override public void glMultMatrixf(float[] matrix, int offset) { gl2.glMultMatrixf(matrix, offset); } /** * Gets a text drawer for the given text size. * * @param size * The size for the drawer. * @return An instance of a drawer for that size. */ @Override public TextDrawer getTextDrawer(EFontSize size) { if (textDrawers[size.ordinal()] == null) { textDrawers[size.ordinal()] = new JOGLTextDrawer(size, this); } return textDrawers[size.ordinal()]; } @Override public void drawQuadWithTexture(TextureHandle texture, GeometryHandle geometry) throws IllegalBufferException { if (geometry == null) { throw new NullPointerException("Cannot draw a null geometry"); } if (canUseVBOs) { bindTexture(texture); bindArrayBuffer(geometry); gl2.glVertexPointer(3, GL2.GL_FLOAT, 5 * 4, 0); gl2.glTexCoordPointer(2, GL2.GL_FLOAT, 5 * 4, 3 * 4); gl2.glDrawArrays(GL2.GL_QUADS, 0, 4); bindArrayBuffer(null); } else { drawQuadWithTexture(texture, getGeometryBuffer(geometry), 4); } } @Override public void drawTrianglesWithTexture(TextureHandle texture, GeometryHandle geometry, int triangleCount) throws IllegalBufferException { if (canUseVBOs) { bindTexture(texture); bindArrayBuffer(geometry); gl2.glVertexPointer(3, GL2.GL_FLOAT, 5 * 4, 0); gl2.glTexCoordPointer(2, GL2.GL_FLOAT, 5 * 4, 3 * 4); gl2.glDrawArrays(GL2.GL_TRIANGLES, 0, triangleCount * 3); bindArrayBuffer(null); } else { ByteBuffer buffer = getGeometryBuffer(geometry); buffer.rewind(); drawTrianglesWithTexture(texture, buffer, buffer.remaining() / 5 / 4); } } private ByteBuffer getGeometryBuffer(GeometryHandle geometry) { return geometries.get(geometry.getInternalId()); } @Override public void drawTrianglesWithTextureColored(TextureHandle texture, GeometryHandle geometry, int triangleCount) throws IllegalBufferException { if (canUseVBOs) { bindTexture(texture); bindArrayBuffer(geometry); gl2.glVertexPointer(3, GL2.GL_FLOAT, 6 * 4, 0); gl2.glTexCoordPointer(2, GL2.GL_FLOAT, 6 * 4, 3 * 4); gl2.glColorPointer(4, GL2.GL_UNSIGNED_BYTE, 6 * 4, 5 * 4); gl2.glEnableClientState(GL2.GL_COLOR_ARRAY); gl2.glDrawArrays(GL2.GL_TRIANGLES, 0, triangleCount * 3); gl2.glDisableClientState(GL2.GL_COLOR_ARRAY); bindArrayBuffer(null); } else { ByteBuffer buffer = getGeometryBuffer(geometry); drawTrianglesWithTextureColored(texture, buffer, buffer.remaining() / 4 / 5); } } @Override public GeometryHandle storeGeometry(float[] geometry) { if (canUseVBOs) { try { GeometryHandle geometryBuffer = generateGeometry(geometry.length * Buffers.SIZEOF_FLOAT); if (geometryBuffer == null) { return null; } GLBuffer buffer = startWriteGeometry(geometryBuffer); for (int i = 0; i < geometry.length; i++) { buffer.putFloat(geometry[i]); } endWriteGeometry(geometryBuffer); return geometryBuffer; } catch (IllegalBufferException e) { // TODO: Use a normal buffer instead. return null; } } else { geometries.add(genertateBuffer(geometry)); return new JOGLGeometryHandle(this, geometries.size() - 1); } } boolean checkGeometryIndex(int geometryindex) { if (canUseVBOs) { // TODO: can we find out more? return geometryindex > 0; } else { return geometryindex >= 0 && geometryindex < geometries.size(); } } void deleteGeometry(int geometryindex) { if (canUseVBOs) { gl2.glDeleteBuffers(1, new int[] { geometryindex }, 0); } else { // TODO: unsupported! geometries.set(geometryindex, null); } } @Override public GLBuffer startWriteGeometry(GeometryHandle geometry) throws IllegalBufferException { if (canUseVBOs) { bindArrayBuffer(geometry); ByteBuffer buffer = gl2.glMapBuffer(GL2.GL_ARRAY_BUFFER, GL2.GL_WRITE_ONLY) .order(ByteOrder.nativeOrder()); return new GLByteBufferWrapper(buffer); } else { return new GLByteBufferWrapper(getGeometryBuffer(geometry)); } } private static class GLByteBufferWrapper implements GLBuffer { private final ByteBuffer buffer; private GLByteBufferWrapper(ByteBuffer buffer) { this.buffer = buffer; } @Override public void putFloat(float f) { buffer.putFloat(f); } @Override public void putByte(byte b) { buffer.put(b); } @Override public void position(int position) { buffer.position(position); } } @Override public void endWriteGeometry(GeometryHandle geometry) { if (canUseVBOs) { gl2.glUnmapBuffer(GL2.GL_ARRAY_BUFFER); gl2.glBindBuffer(GL2.GL_ARRAY_BUFFER, 0); } } @Override public GeometryHandle generateGeometry(int bytes) { int vertexBufferId; if (canUseVBOs) { vertexBufferId = allocateVBO(); if (vertexBufferId == 0) { return null; } gl2.glBindBuffer(GL2.GL_ARRAY_BUFFER, vertexBufferId); gl2.glBufferData(GL.GL_ARRAY_BUFFER, bytes, null, GL.GL_DYNAMIC_DRAW); gl2.glBindBuffer(GL2.GL_ARRAY_BUFFER, 0); } else { ByteBuffer bb = ByteBuffer.allocateDirect(bytes); bb.order(ByteOrder.nativeOrder()); vertexBufferId = geometries.size(); geometries.add(bb); } return new JOGLGeometryHandle(this, vertexBufferId); } private int allocateVBO() { int[] vertexBuffIds = new int[] { 0 }; gl2.glGenBuffers(1, vertexBuffIds, 0); return vertexBuffIds[0]; } public void prepareFontDrawing() { } /** * Called whenever we should dispose all buffers associated with this context. */ public void disposeAll() { contextValid = false; } public boolean isValid() { return contextValid; } }