diff --git a/src/com/badlogic/gdx/graphics/GLTexture.java b/src/com/badlogic/gdx/graphics/GLTexture.java new file mode 100644 index 000000000..b67359c07 --- /dev/null +++ b/src/com/badlogic/gdx/graphics/GLTexture.java @@ -0,0 +1,287 @@ +/******************************************************************************* + * Copyright 2011 See AUTHORS file. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +package com.badlogic.gdx.graphics; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.Pixmap.Blending; +import com.badlogic.gdx.graphics.Texture.TextureFilter; +import com.badlogic.gdx.graphics.Texture.TextureWrap; +import com.badlogic.gdx.graphics.TextureData.TextureDataType; +import com.badlogic.gdx.graphics.glutils.MipMapGenerator; +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.utils.BufferUtils; +import com.badlogic.gdx.utils.Disposable; + +import java.nio.Buffer; +import java.nio.FloatBuffer; + +/** Class representing an OpenGL texture by its target and handle. Keeps track of its state like the TextureFilter and + * TextureWrap. Also provides some (protected) static methods to create TextureData and upload image data. + * + * This version of code is based on GDX 1.12.0 and was modified for the Terrarum project. + * + * @author badlogic, Xoppa, CuriousTorvald */ +public abstract class GLTexture implements Disposable { + /** The target of this texture, used when binding the texture, e.g. GL_TEXTURE_2D */ + public final int glTarget; + protected int glHandle; + protected TextureFilter minFilter = TextureFilter.Nearest; + protected TextureFilter magFilter = TextureFilter.Nearest; + protected TextureWrap uWrap = TextureWrap.ClampToEdge; + protected TextureWrap vWrap = TextureWrap.ClampToEdge; + protected float anisotropicFilterLevel = 1.0f; + private static float maxAnisotropicFilterLevel = 0; + + /** @return the width of the texture in pixels */ + public abstract int getWidth (); + + /** @return the height of the texture in pixels */ + public abstract int getHeight (); + + /** @return the depth of the texture in pixels */ + public abstract int getDepth (); + + /** Generates a new OpenGL texture with the specified target. */ + public GLTexture (int glTarget) { + this(glTarget, Gdx.gl.glGenTexture()); + } + + public GLTexture (int glTarget, int glHandle) { + this.glTarget = glTarget; + this.glHandle = glHandle; + } + + /** @return whether this texture is managed or not. */ + public abstract boolean isManaged (); + + protected abstract void reload (); + + /** Binds this texture. The texture will be bound to the currently active texture unit specified via + * {@link GL20#glActiveTexture(int)}. */ + public void bind () { + Gdx.gl.glBindTexture(glTarget, glHandle); + } + + /** Binds the texture to the given texture unit. Sets the currently active texture unit via {@link GL20#glActiveTexture(int)}. + * @param unit the unit (0 to MAX_TEXTURE_UNITS). */ + public void bind (int unit) { + Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0 + unit); + Gdx.gl.glBindTexture(glTarget, glHandle); + } + + /** @return The {@link Texture.TextureFilter} used for minification. */ + public TextureFilter getMinFilter () { + return minFilter; + } + + /** @return The {@link Texture.TextureFilter} used for magnification. */ + public TextureFilter getMagFilter () { + return magFilter; + } + + /** @return The {@link Texture.TextureWrap} used for horizontal (U) texture coordinates. */ + public TextureWrap getUWrap () { + return uWrap; + } + + /** @return The {@link Texture.TextureWrap} used for vertical (V) texture coordinates. */ + public TextureWrap getVWrap () { + return vWrap; + } + + /** @return The OpenGL handle for this texture. */ + public int getTextureObjectHandle () { + return glHandle; + } + + /** Sets the {@link TextureWrap} for this texture on the u and v axis. Assumes the texture is bound and active! + * @param u the u wrap + * @param v the v wrap */ + public void unsafeSetWrap (TextureWrap u, TextureWrap v) { + unsafeSetWrap(u, v, false); + } + + /** Sets the {@link TextureWrap} for this texture on the u and v axis. Assumes the texture is bound and active! + * @param u the u wrap + * @param v the v wrap + * @param force True to always set the values, even if they are the same as the current values. */ + public void unsafeSetWrap (TextureWrap u, TextureWrap v, boolean force) { + if (u != null && (force || uWrap != u)) { + Gdx.gl.glTexParameteri(glTarget, GL20.GL_TEXTURE_WRAP_S, u.getGLEnum()); + uWrap = u; + } + if (v != null && (force || vWrap != v)) { + Gdx.gl.glTexParameteri(glTarget, GL20.GL_TEXTURE_WRAP_T, v.getGLEnum()); + vWrap = v; + } + } + + /** Sets the {@link TextureWrap} for this texture on the u and v axis. This will bind this texture! + * @param u the u wrap + * @param v the v wrap */ + public void setWrap (TextureWrap u, TextureWrap v) { + this.uWrap = u; + this.vWrap = v; + bind(); + Gdx.gl.glTexParameteri(glTarget, GL20.GL_TEXTURE_WRAP_S, u.getGLEnum()); + Gdx.gl.glTexParameteri(glTarget, GL20.GL_TEXTURE_WRAP_T, v.getGLEnum()); + } + + /** Sets the {@link TextureFilter} for this texture for minification and magnification. Assumes the texture is bound and + * active! + * @param minFilter the minification filter + * @param magFilter the magnification filter */ + public void unsafeSetFilter (TextureFilter minFilter, TextureFilter magFilter) { + unsafeSetFilter(minFilter, magFilter, false); + } + + /** Sets the {@link TextureFilter} for this texture for minification and magnification. Assumes the texture is bound and + * active! + * @param minFilter the minification filter + * @param magFilter the magnification filter + * @param force True to always set the values, even if they are the same as the current values. */ + public void unsafeSetFilter (TextureFilter minFilter, TextureFilter magFilter, boolean force) { + if (minFilter != null && (force || this.minFilter != minFilter)) { + Gdx.gl.glTexParameteri(glTarget, GL20.GL_TEXTURE_MIN_FILTER, minFilter.getGLEnum()); + this.minFilter = minFilter; + } + if (magFilter != null && (force || this.magFilter != magFilter)) { + Gdx.gl.glTexParameteri(glTarget, GL20.GL_TEXTURE_MAG_FILTER, magFilter.getGLEnum()); + this.magFilter = magFilter; + } + } + + /** Sets the {@link TextureFilter} for this texture for minification and magnification. This will bind this texture! + * @param minFilter the minification filter + * @param magFilter the magnification filter */ + public void setFilter (TextureFilter minFilter, TextureFilter magFilter) { + this.minFilter = minFilter; + this.magFilter = magFilter; + bind(); + Gdx.gl.glTexParameteri(glTarget, GL20.GL_TEXTURE_MIN_FILTER, minFilter.getGLEnum()); + Gdx.gl.glTexParameteri(glTarget, GL20.GL_TEXTURE_MAG_FILTER, magFilter.getGLEnum()); + } + + /** Sets the anisotropic filter level for the texture. Assumes the texture is bound and active! + * + * @param level The desired level of filtering. The maximum level supported by the device up to this value will be used. + * @return The actual level set, which may be lower than the provided value due to device limitations. */ + public float unsafeSetAnisotropicFilter (float level) { + return unsafeSetAnisotropicFilter(level, false); + } + + /** Sets the anisotropic filter level for the texture. Assumes the texture is bound and active! + * + * @param level The desired level of filtering. The maximum level supported by the device up to this value will be used. + * @param force True to always set the value, even if it is the same as the current values. + * @return The actual level set, which may be lower than the provided value due to device limitations. */ + public float unsafeSetAnisotropicFilter (float level, boolean force) { + return 1f; +// float max = getMaxAnisotropicFilterLevel(); +// if (max == 1f) return 1f; +// level = Math.min(level, max); +// if (!force && MathUtils.isEqual(level, anisotropicFilterLevel, 0.1f)) return anisotropicFilterLevel; +// Gdx.gl20.glTexParameterf(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAX_ANISOTROPY_EXT, level); +// return anisotropicFilterLevel = level; + } + + /** Sets the anisotropic filter level for the texture. This will bind the texture! + * + * @param level The desired level of filtering. The maximum level supported by the device up to this value will be used. + * @return The actual level set, which may be lower than the provided value due to device limitations. */ + public float setAnisotropicFilter (float level) { + float max = getMaxAnisotropicFilterLevel(); + if (max == 1f) return 1f; + level = Math.min(level, max); + if (MathUtils.isEqual(level, anisotropicFilterLevel, 0.1f)) return level; + bind(); + Gdx.gl20.glTexParameterf(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAX_ANISOTROPY_EXT, level); + return anisotropicFilterLevel = level; + } + + /** @return The currently set anisotropic filtering level for the texture, or 1.0f if none has been set. */ + public float getAnisotropicFilter () { + return anisotropicFilterLevel; + } + + /** @return The maximum supported anisotropic filtering level supported by the device. */ + public static float getMaxAnisotropicFilterLevel () { + if (maxAnisotropicFilterLevel > 0) return maxAnisotropicFilterLevel; + if (Gdx.graphics.supportsExtension("GL_EXT_texture_filter_anisotropic")) { + FloatBuffer buffer = BufferUtils.newFloatBuffer(16); + ((Buffer)buffer).position(0); + ((Buffer)buffer).limit(buffer.capacity()); + Gdx.gl20.glGetFloatv(GL20.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, buffer); + return maxAnisotropicFilterLevel = buffer.get(0); + } + return maxAnisotropicFilterLevel = 1f; + } + + /** Destroys the OpenGL Texture as specified by the glHandle. */ + protected void delete () { + if (glHandle != 0) { + Gdx.gl.glDeleteTexture(glHandle); + glHandle = 0; + } + } + + @Override + public void dispose () { + delete(); + } + + protected static void uploadImageData (int target, TextureData data) { + uploadImageData(target, data, 0); + } + + public static void uploadImageData (int target, TextureData data, int miplevel) { + if (data == null) { + // FIXME: remove texture on target? + return; + } + + if (!data.isPrepared()) data.prepare(); + + final TextureDataType type = data.getType(); + if (type == TextureDataType.Custom) { + data.consumeCustomData(target); + return; + } + + Pixmap pixmap = data.consumePixmap(); + boolean disposePixmap = data.disposePixmap(); + if (data.getFormat() != pixmap.getFormat()) { + Pixmap tmp = new Pixmap(pixmap.getWidth(), pixmap.getHeight(), data.getFormat()); + tmp.setBlending(Blending.None); + tmp.drawPixmap(pixmap, 0, 0, 0, 0, pixmap.getWidth(), pixmap.getHeight()); + if (data.disposePixmap()) { + pixmap.dispose(); + } + pixmap = tmp; + disposePixmap = true; + } + + Gdx.gl.glPixelStorei(GL20.GL_UNPACK_ALIGNMENT, 1); + if (data.useMipMaps()) { + MipMapGenerator.generateMipMap(target, pixmap, pixmap.getWidth(), pixmap.getHeight()); + } else { + Gdx.gl.glTexImage2D(target, miplevel, pixmap.getGLInternalFormat(), pixmap.getWidth(), pixmap.getHeight(), 0, + pixmap.getGLFormat(), pixmap.getGLType(), pixmap.getPixels()); + } + if (disposePixmap) pixmap.dispose(); + } +} diff --git a/src/com/badlogic/gdx/graphics/glutils/Float16FrameBuffer.java b/src/com/badlogic/gdx/graphics/glutils/Float16FrameBuffer.java index 20f6a2c61..187db18c6 100644 --- a/src/com/badlogic/gdx/graphics/glutils/Float16FrameBuffer.java +++ b/src/com/badlogic/gdx/graphics/glutils/Float16FrameBuffer.java @@ -15,6 +15,8 @@ import net.torvald.terrarum.App; */ public class Float16FrameBuffer extends FrameBuffer { + private static boolean floatFramebufferAvailable = true; + Float16FrameBuffer () { } @@ -33,32 +35,36 @@ public class Float16FrameBuffer extends FrameBuffer { * @param hasDepth whether to attach a depth buffer * @throws GdxRuntimeException in case the FrameBuffer could not be created */ public Float16FrameBuffer (int width, int height, boolean hasDepth) { - /*if (!App.gl40capable || App.operationSystem.equals("OSX")) { // disable float framebuffer for Apple M chips + if (!floatFramebufferAvailable || App.operationSystem.equals("OSX")) { // disable float framebuffer for Apple M chips FrameBufferBuilder bufferBuilder = new FrameBufferBuilder(width, height); bufferBuilder.addColorTextureAttachment(GL20.GL_RGBA, GL20.GL_RGBA, GL20.GL_UNSIGNED_SHORT); // but 16bpp creates slight banding if (hasDepth) bufferBuilder.addBasicDepthRenderBuffer(); this.bufferBuilder = bufferBuilder; + build(); } else { - FloatFrameBufferBuilder bufferBuilder = new FloatFrameBufferBuilder(width, height); - bufferBuilder.addFloatAttachment(GL30.GL_RGBA16F, GL30.GL_RGBA, GL30.GL_FLOAT, false); // float16 will not create a banding - if (hasDepth) bufferBuilder.addBasicDepthRenderBuffer(); - this.bufferBuilder = bufferBuilder; - }*/ - - // FIXME temporarily disabling Float16 -- has LWJGL bug that prevents setOpenGLEmulation on Windows (or is this Nvidia's issue?) - - FrameBufferBuilder bufferBuilder = new FrameBufferBuilder(width, height); - bufferBuilder.addColorTextureAttachment(GL20.GL_RGBA, GL20.GL_RGBA, GL20.GL_UNSIGNED_SHORT); // but 16bpp int works perfectly?! - if (hasDepth) bufferBuilder.addBasicDepthRenderBuffer(); - this.bufferBuilder = bufferBuilder; - - build(); + try { + FloatFrameBufferBuilder bufferBuilder = new FloatFrameBufferBuilder(width, height); + bufferBuilder.addFloatAttachment(GL30.GL_RGBA16F, GL30.GL_RGBA, GL30.GL_FLOAT, false); // float16 will not create a banding + if (hasDepth) bufferBuilder.addBasicDepthRenderBuffer(); + this.bufferBuilder = bufferBuilder; + build(); + } + catch (GdxRuntimeException e) { + floatFramebufferAvailable = false; + System.out.println("[Float16FrameBuffer] WARNING: Float Framebuffer is not available, falling back to 16bpp..."); + FrameBufferBuilder bufferBuilder = new FrameBufferBuilder(width, height); + bufferBuilder.addColorTextureAttachment(GL20.GL_RGBA, GL20.GL_RGBA, GL20.GL_UNSIGNED_SHORT); // but 16bpp creates slight banding + if (hasDepth) bufferBuilder.addBasicDepthRenderBuffer(); + this.bufferBuilder = bufferBuilder; + build(); + } + } } @Override protected Texture createTexture (FrameBufferTextureAttachmentSpec attachmentSpec) { - /*if (!App.gl40capable || App.operationSystem.equals("OSX")) { + if (!floatFramebufferAvailable || App.operationSystem.equals("OSX")) { GLOnlyTextureData data = new GLOnlyTextureData(bufferBuilder.width, bufferBuilder.height, 0, attachmentSpec.internalFormat, attachmentSpec.format, attachmentSpec.type); Texture result = new Texture(data); @@ -73,14 +79,14 @@ public class Float16FrameBuffer extends FrameBuffer { result.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear); result.setWrap(Texture.TextureWrap.ClampToEdge, Texture.TextureWrap.ClampToEdge); return result; - }*/ + } - GLOnlyTextureData data = new GLOnlyTextureData(bufferBuilder.width, bufferBuilder.height, 0, attachmentSpec.internalFormat, + /*GLOnlyTextureData data = new GLOnlyTextureData(bufferBuilder.width, bufferBuilder.height, 0, attachmentSpec.internalFormat, attachmentSpec.format, attachmentSpec.type); Texture result = new Texture(data); result.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear); result.setWrap(Texture.TextureWrap.ClampToEdge, Texture.TextureWrap.ClampToEdge); - return result; + return result;*/ } } diff --git a/src/net/torvald/terrarum/App.java b/src/net/torvald/terrarum/App.java index 27c0bae13..8046ada48 100644 --- a/src/net/torvald/terrarum/App.java +++ b/src/net/torvald/terrarum/App.java @@ -419,7 +419,7 @@ public class App implements ApplicationListener { Lwjgl3ApplicationConfiguration appConfig = new Lwjgl3ApplicationConfiguration(); //appConfig.useGL30 = false; // https://stackoverflow.com/questions/46753218/libgdx-should-i-use-gl30 - if (SharedLibraryLoader.isMac) +// if (SharedLibraryLoader.isMac) appConfig.setOpenGLEmulation(Lwjgl3ApplicationConfiguration.GLEmulation.GL30, 3, 2); appConfig.useVsync(getConfigBoolean("usevsync")); appConfig.setResizable(false); diff --git a/src/net/torvald/terrarum/TerrarumGLinfo.kt b/src/net/torvald/terrarum/TerrarumGLinfo.kt index bbd6d54e5..75054bbb5 100644 --- a/src/net/torvald/terrarum/TerrarumGLinfo.kt +++ b/src/net/torvald/terrarum/TerrarumGLinfo.kt @@ -7,7 +7,7 @@ import org.lwjgl.opengl.GL11 class TerrarumGLinfo { private var _initialised = false - val MINIMAL_GL_VERSION = 330 + val MINIMAL_GL_VERSION = 320 var GL_VERSION = -1; private set get() = if (_initialised) field else throw UninitializedPropertyAccessException()