Java tutorial
/******************************************************************************* * Copyright (c) 2015-2018 * * 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 org.lwjgl.BufferUtils; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL12; import org.lwjgl.opengl.GL15; import org.lwjgl.opengl.GLCapabilities; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.nio.ShortBuffer; import java.util.ArrayList; import go.graphics.GLDrawContext; import go.graphics.GeometryHandle; import go.graphics.IllegalBufferException; import go.graphics.TextureHandle; import go.graphics.swing.opengl.LWJGLBufferHandle.LWJGLGeometryHandle; import go.graphics.swing.opengl.LWJGLBufferHandle.LWJGLTextureHandle; import go.graphics.swing.text.LWJGLTextDrawer; import go.graphics.text.EFontSize; import go.graphics.text.TextDrawer; /** * This is the draw context implementation for LWJGL. OpenGL draw calles are mapped to the corresponding LWJGL calls. * * @author Michael Zangl * @author paul * */ public class LWJGLDrawContext implements GLDrawContext { private LWJGLTextDrawer[] textDrawers = new LWJGLTextDrawer[EFontSize.values().length]; private static final int FLOATS_PER_COLORED_TRI_VERTEX = 9; private final GLCapabilities glcaps; private final boolean canUseVBOs; public LWJGLDrawContext(GLCapabilities glcaps) { this.glcaps = glcaps; GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY); GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY); GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); GL11.glEnable(GL11.GL_BLEND); GL11.glEnable(GL11.GL_ALPHA_TEST); GL11.glDepthFunc(GL11.GL_LEQUAL); GL11.glEnable(GL11.GL_DEPTH_TEST); GL11.glEnable(GL11.GL_TEXTURE_2D); canUseVBOs = glcaps.GL_ARB_vertex_buffer_object; } public void startFrame() { GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT); } @Override public void color(float r, float g, float b, float a) { GL11.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); GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); GL11.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY); GL11.glVertexPointer(2, GL11.GL_FLOAT, 0, floatBuff); GL11.glDrawArrays(GL11.GL_QUADS, 0, 4); GL11.glEnableClientState(GL11.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); GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); GL11.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY); GL11.glVertexPointer(3, GL11.GL_FLOAT, 0, floatBuff); GL11.glDrawArrays(loop ? GL11.GL_LINE_LOOP : GL11.GL_LINE_STRIP, 0, points.length / 3); GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY); } private ByteBuffer reuseableBuffer = null; private ArrayList<ByteBuffer> geometries = new ArrayList<>(); /** * 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() { GL11.glPushMatrix(); } @Override public void glTranslatef(float x, float y, float z) { GL11.glTranslatef(x, y, z); } @Override public void glPopMatrix() { GL11.glPopMatrix(); } @Override public void glScalef(float x, float y, float z) { GL11.glScalef(x, y, z); } @Override public TextureHandle generateTexture(int width, int height, ShortBuffer data) { // 1 byte aligned. GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1); int texture = GL11.glGenTextures(); if (texture == 0) { return null; } //fix strange alpha test problem (minimap and landscape are unaffected) ShortBuffer bfr = BufferUtils.createShortBuffer(data.capacity()); int cap = data.capacity(); for (int i = 0; i != cap; i++) bfr.put(i, data.get(i)); GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture); GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, width, height, 0, GL11.GL_RGBA, GL12.GL_UNSIGNED_SHORT_4_4_4_4, bfr); setTextureParameters(); return new LWJGLTextureHandle(this, texture); } /** * Sets the texture parameters, assuming that the texture was just created and is bound. */ private void setTextureParameters() { GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP); GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP); GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST); GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST); GL11.glAlphaFunc(GL11.GL_GREATER, 0.5f); // prevent writing of transparent pixels to z buffer } @Override public void updateTexture(TextureHandle texture, int left, int bottom, int width, int height, ShortBuffer data) throws IllegalBufferException { bindTexture(texture); GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, left, bottom, width, height, GL11.GL_RGBA, GL12.GL_UNSIGNED_SHORT_4_4_4_4, 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(); } GL11.glBindTexture(GL11.GL_TEXTURE_2D, id); } 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(); } GL15.glBindBuffer(GL15.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); GL11.glVertexPointer(3, GL11.GL_FLOAT, 5 * 4, buffer); buffer.position(3 * 4); GL11.glTexCoordPointer(2, GL11.GL_FLOAT, 5 * 4, buffer); GL11.glDrawArrays(GL11.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); GL11.glVertexPointer(3, GL11.GL_FLOAT, 5 * 4, buffer); buffer.position(3 * 4); GL11.glTexCoordPointer(2, GL11.GL_FLOAT, 5 * 4, buffer); GL11.glDrawArrays(GL11.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); GL11.glVertexPointer(3, GL11.GL_FLOAT, 6 * 4, buffer); buffer.position(3 * 4); GL11.glTexCoordPointer(2, GL11.GL_FLOAT, 6 * 4, buffer); buffer.position(5 * 4); GL11.glColorPointer(4, GL11.GL_UNSIGNED_BYTE, 6 * 4, buffer); GL11.glEnableClientState(GL11.GL_COLOR_ARRAY); GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, triangles * 3); GL11.glDisableClientState(GL11.GL_COLOR_ARRAY); } @Override public int makeWidthValid(int width) { return TextureCalculator.supportedTextureSize(glcaps, width); } @Override public int makeHeightValid(int height) { return TextureCalculator.supportedTextureSize(glcaps, height); } @Override public void glMultMatrixf(float[] matrix) { GL11.glMultMatrixf(matrix); } /** * 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 LWJGLTextDrawer(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); GL11.glVertexPointer(3, GL11.GL_FLOAT, 5 * 4, 0); GL11.glTexCoordPointer(2, GL11.GL_FLOAT, 5 * 4, 3 * 4); GL11.glDrawArrays(GL11.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); GL11.glVertexPointer(3, GL11.GL_FLOAT, 5 * 4, 0); GL11.glTexCoordPointer(2, GL11.GL_FLOAT, 5 * 4, 3 * 4); GL11.glDrawArrays(GL11.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); GL11.glVertexPointer(3, GL11.GL_FLOAT, 6 * 4, 0); GL11.glTexCoordPointer(2, GL11.GL_FLOAT, 6 * 4, 3 * 4); GL11.glColorPointer(4, GL11.GL_UNSIGNED_BYTE, 6 * 4, 5 * 4); GL11.glEnableClientState(GL11.GL_COLOR_ARRAY); GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, triangleCount * 3); GL11.glDisableClientState(GL11.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 * Float.BYTES); 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 LWJGLGeometryHandle(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) { GL15.glDeleteBuffers(geometryindex); } else { // TODO: unsupported! geometries.set(geometryindex, null); } } @Override public GLBuffer startWriteGeometry(GeometryHandle geometry) throws IllegalBufferException { if (canUseVBOs) { bindArrayBuffer(geometry); ByteBuffer buffer = GL15.glMapBuffer(GL15.GL_ARRAY_BUFFER, GL15.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) { GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER); GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); } } @Override public GeometryHandle generateGeometry(int bytes) { int vertexBufferId; if (canUseVBOs) { vertexBufferId = allocateVBO(); if (vertexBufferId == 0) { return null; } GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vertexBufferId); GL15.glBufferData(GL15.GL_ARRAY_BUFFER, BufferUtils.createByteBuffer(bytes), GL15.GL_DYNAMIC_DRAW); GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); } else { ByteBuffer bb = ByteBuffer.allocateDirect(bytes); bb.order(ByteOrder.nativeOrder()); vertexBufferId = geometries.size(); geometries.add(bb); } return new LWJGLGeometryHandle(this, vertexBufferId); } private int allocateVBO() { return GL15.glGenBuffers(); } public void prepareFontDrawing() { } /** * Called whenever we should dispose all buffers associated with this context. */ public void disposeAll() { contextValid = false; } public boolean isValid() { return contextValid; } }