diff --git a/core/src/processing/core/PApplet.java b/core/src/processing/core/PApplet.java index 1c501e6f1..4a9573050 100644 --- a/core/src/processing/core/PApplet.java +++ b/core/src/processing/core/PApplet.java @@ -9945,6 +9945,12 @@ public class PApplet extends Applet } + public void textureQuality(int quality) { + if (recorder != null) recorder.textureQuality(quality); + g.textureQuality(quality); + } + + /** * ( begin auto-generated from texture.xml ) * diff --git a/core/src/processing/core/PConstants.java b/core/src/processing/core/PConstants.java index fc5c85f2f..791c99492 100644 --- a/core/src/processing/core/PConstants.java +++ b/core/src/processing/core/PConstants.java @@ -437,13 +437,26 @@ public interface PConstants { /** This constant identifies the texture target GL_TEXTURE_2D, that is, * textures with normalized coordinates */ public static final int TEXTURE2D = 0; + + /** Texture quality constants */ + public static final int LOW = 0; + public static final int MEDIUM = 1; + public static final int HIGH = 2; + public static final int BEST = 3; - /** This constant identifies the nearest texture filter (point sampling) */ + /** Point sampling: both magnification and minification filtering are set to nearest */ //public static final int POINT = 2; // shared with shape feature - /** This constant identifies the linear texture filter, usually called bilinear sampling */ - public static final int BILINEAR = 3; - /** This constant identifies the linear/linear function to build mipmaps */ - public static final int TRILINEAR = 4; + /** Linear sampling: magnification filtering is nearest, minification set to linear */ + public static final int LINEAR = 3; + /** Bilinear sampling: both magnification filtering is set to linear and minification + * either to linear-mipmap-nearest (linear interplation is used within a mipmap, but + * not between different mipmaps). */ + public static final int BILINEAR = 4; + /** Trilinear sampling: magnification filtering set to linear, minification to + * linear-mipmap-linear, which offers the best mipmap quality since linear + * interpolation to compute the value in each of two maps and then interpolates linearly + * between these two value. */ + public static final int TRILINEAR = 5; /** This constant identifies the clamp-to-edge wrapping mode */ public static final int CLAMP = 0; @@ -561,7 +574,10 @@ public interface PConstants { static final int ENABLE_PERSPECTIVE_CORRECTED_LINES = 10; static final int DISABLE_PERSPECTIVE_CORRECTED_LINES = -10; - static final int HINT_COUNT = 11; + static final int DISABLE_TEXTURE_MIPMAPS = 11; + static final int ENABLE_TEXTURE_MIPMAPS = -11; + + static final int HINT_COUNT = 12; // error messages diff --git a/core/src/processing/core/PGraphics.java b/core/src/processing/core/PGraphics.java index e4098b6dd..52a396edb 100644 --- a/core/src/processing/core/PGraphics.java +++ b/core/src/processing/core/PGraphics.java @@ -543,9 +543,10 @@ public class PGraphics extends PImage implements PConstants { * vertex() calls will be based on coordinates that are * based on the IMAGE or NORMALIZED. */ - public int textureMode; + public int textureMode = IMAGE; - public int textureWrap; + public int textureWrap = CLAMP; + public int textureQuality = BEST; /** * Current horizontal coordinate for texture, will always @@ -1066,17 +1067,14 @@ public class PGraphics extends PImage implements PConstants { } - // TODO: use this setting in GL renderer. public void textureWrap(int wrap) { this.textureWrap = wrap; } - // TODO: do we need something like this to choose between - // point, linear and trilinear texture sampling? - //public void textureQualityt(int quality) { - // this.textureQuality = quality; - //} + public void textureQuality(int quality) { + this.textureQuality = quality; + } /** diff --git a/java/libraries/opengl/src/processing/opengl/PFontTexture.java b/java/libraries/opengl/src/processing/opengl/PFontTexture.java index 93ef2d24e..f06dc8575 100644 --- a/java/libraries/opengl/src/processing/opengl/PFontTexture.java +++ b/java/libraries/opengl/src/processing/opengl/PFontTexture.java @@ -52,6 +52,7 @@ class PFontTexture implements PConstants { protected PGraphicsOpenGL pg; protected PGL pgl; protected PFont font; + protected boolean is3D; protected int maxTexWidth; protected int maxTexHeight; @@ -65,11 +66,12 @@ class PFontTexture implements PConstants { protected TextureInfo[] glyphTexinfos; protected HashMap texinfoMap; - public PFontTexture(PApplet parent, PFont font, int maxw, int maxh) { + public PFontTexture(PApplet parent, PFont font, int maxw, int maxh, boolean is3D) { this.parent = parent; this.font = font; pg = (PGraphicsOpenGL)parent.g; pgl = pg.pgl; + this.is3D = is3D; initTexture(maxw, maxh); } @@ -115,7 +117,17 @@ class PFontTexture implements PConstants { resize = false; } - PTexture tex = new PTexture(parent, w, h, new PTexture.Parameters(ARGB, BILINEAR)); + PTexture tex; + if (is3D) { + // Bilinear sampling ensures that the texture doesn't look pixelated either + // when it is magnified or minified... + tex = new PTexture(parent, w, h, new PTexture.Parameters(ARGB, BILINEAR, false)); + } else { + // ...however, the effect of bilinear sampling is to add some blurriness to the text + // in its original size. In 2D, we assume that text will be shown at its original + // size, so linear sampling is chosen instead (which only affects minimized text). + tex = new PTexture(parent, w, h, new PTexture.Parameters(ARGB, LINEAR, false)); + } if (textures == null) { textures = new PTexture[1]; diff --git a/java/libraries/opengl/src/processing/opengl/PGL.java b/java/libraries/opengl/src/processing/opengl/PGL.java index a4d39807b..a1463bc3e 100644 --- a/java/libraries/opengl/src/processing/opengl/PGL.java +++ b/java/libraries/opengl/src/processing/opengl/PGL.java @@ -191,10 +191,11 @@ public class PGL { public static final int GL_UNSIGNED_SHORT = GL.GL_UNSIGNED_SHORT; public static final int GL_FLOAT = GL.GL_FLOAT; - public static final int GL_NEAREST = GL.GL_NEAREST; - public static final int GL_LINEAR = GL.GL_LINEAR; - public static final int GL_LINEAR_MIPMAP_LINEAR = GL.GL_LINEAR_MIPMAP_LINEAR; - + public static final int GL_NEAREST = GL.GL_NEAREST; + public static final int GL_LINEAR = GL.GL_LINEAR; + public static final int GL_LINEAR_MIPMAP_NEAREST = GL.GL_LINEAR_MIPMAP_NEAREST; + public static final int GL_LINEAR_MIPMAP_LINEAR = GL.GL_LINEAR_MIPMAP_LINEAR; + public static final int GL_CLAMP_TO_EDGE = GL.GL_CLAMP_TO_EDGE; public static final int GL_REPEAT = GL.GL_REPEAT; diff --git a/java/libraries/opengl/src/processing/opengl/PGraphicsOpenGL.java b/java/libraries/opengl/src/processing/opengl/PGraphicsOpenGL.java index 82978ff31..41376a80e 100644 --- a/java/libraries/opengl/src/processing/opengl/PGraphicsOpenGL.java +++ b/java/libraries/opengl/src/processing/opengl/PGraphicsOpenGL.java @@ -53,7 +53,7 @@ public class PGraphicsOpenGL extends PGraphics { /** The main PApplet renderer. */ protected static PGraphicsOpenGL pgPrimary = null; - /** The renderer currenty in use. */ + /** The renderer currently in use. */ protected static PGraphicsOpenGL pgCurrent = null; // ........................................................ @@ -116,7 +116,7 @@ public class PGraphicsOpenGL extends PGraphics { /** Extensions used by Processing */ static public boolean npotTexSupported; - static public boolean mipmapGeneration; + static public boolean autoMipmapGenSupported; static public boolean fboMultisampleSupported; static public boolean packedDepthStencilSupported; static public boolean blendEqSupported; @@ -3181,12 +3181,12 @@ public class PGraphicsOpenGL extends PGraphics { protected void textLineImpl(char buffer[], int start, int stop, float x, float y) { textTex = (PFontTexture)textFont.getCache(pgPrimary); if (textTex == null) { - textTex = new PFontTexture(parent, textFont, maxTextureSize, maxTextureSize); + textTex = new PFontTexture(parent, textFont, maxTextureSize, maxTextureSize, is3D()); textFont.setCache(this, textTex); } else { if (textTex.contextIsOutdated()) { textTex = new PFontTexture(parent, textFont, PApplet.min(PGL.MAX_FONT_TEX_SIZE, maxTextureSize), - PApplet.min(PGL.MAX_FONT_TEX_SIZE, maxTextureSize)); + PApplet.min(PGL.MAX_FONT_TEX_SIZE, maxTextureSize), is3D()); textFont.setCache(this, textTex); } } @@ -4870,7 +4870,7 @@ public class PGraphicsOpenGL extends PGraphics { protected void loadTextureImpl(int sampling) { if (width == 0 || height == 0) return; if (texture == null || texture.contextIsOutdated()) { - PTexture.Parameters params = PTexture.newParameters(ARGB, sampling); + PTexture.Parameters params = new PTexture.Parameters(ARGB, sampling); texture = new PTexture(parent, width, height, params); texture.setFlippedY(true); this.setCache(pgPrimary, texture); @@ -5182,7 +5182,26 @@ public class PGraphicsOpenGL extends PGraphics { protected PTexture addTexture(PImage img) { PTexture.Parameters params = (PTexture.Parameters)img.getParams(pgPrimary); if (params == null) { - params = PTexture.newParameters(); + params = new PTexture.Parameters(); + if (hints[DISABLE_TEXTURE_MIPMAPS]) { + params.mipmaps = false; + } else { + params.mipmaps = true; + } + if (textureQuality == LOW) { + params.sampling = POINT; + } else if (textureQuality == MEDIUM) { + params.sampling = LINEAR; + } else if (textureQuality == HIGH) { + params.sampling = BILINEAR; + } else if (textureQuality == BEST) { + if (params.mipmaps) { + params.sampling = TRILINEAR; + } else { + params.sampling = BILINEAR; + PGraphics.showWarning("BEST texture quality requires mipmaps, will switch to HIGH."); + } + } params.wrapU = textureWrap; params.wrapV = textureWrap; img.setParams(pgPrimary, params); @@ -5295,12 +5314,11 @@ public class PGraphicsOpenGL extends PGraphics { OPENGL_VENDOR = pgl.glGetString(PGL.GL_VENDOR); OPENGL_RENDERER = pgl.glGetString(PGL.GL_RENDERER); OPENGL_VERSION = pgl.glGetString(PGL.GL_VERSION); - OPENGL_VERSION = pgl.glGetString(PGL.GL_VERSION); OPENGL_EXTENSIONS = pgl.glGetString(PGL.GL_EXTENSIONS); GLSL_VERSION = pgl.glGetString(PGL.GL_SHADING_LANGUAGE_VERSION); npotTexSupported = -1 < OPENGL_EXTENSIONS.indexOf("texture_non_power_of_two"); - mipmapGeneration = -1 < OPENGL_EXTENSIONS.indexOf("generate_mipmap"); + autoMipmapGenSupported = -1 < OPENGL_EXTENSIONS.indexOf("generate_mipmap"); fboMultisampleSupported = -1 < OPENGL_EXTENSIONS.indexOf("framebuffer_multisample"); packedDepthStencilSupported = -1 < OPENGL_EXTENSIONS.indexOf("packed_depth_stencil"); diff --git a/java/libraries/opengl/src/processing/opengl/PTexture.java b/java/libraries/opengl/src/processing/opengl/PTexture.java index 3ebe2d822..90a856de7 100644 --- a/java/libraries/opengl/src/processing/opengl/PTexture.java +++ b/java/libraries/opengl/src/processing/opengl/PTexture.java @@ -249,17 +249,72 @@ public class PTexture implements PConstants { pgl.glBindTexture(glTarget, glID); if (usingMipmaps) { - if (PGraphicsOpenGL.mipmapGeneration) { + if (PGraphicsOpenGL.autoMipmapGenSupported) { // Automatic mipmap generation. int[] rgbaPixels = new int[w * h]; convertToRGBA(pixels, rgbaPixels, format, w, h); setTexels(rgbaPixels, x, y, w, h); pgl.glGenerateMipmap(glTarget); rgbaPixels = null; - } else { - // TODO: Manual mipmap generation. - // Open source implementation of gluBuild2DMipmaps here: - // http://code.google.com/p/glues/source/browse/trunk/glues/source/glues_mipmap.c + } else { + // TODO: finish manual mipmap generation, replacing Bitmap with AWT's BufferedImage, + // making it work in npot textures (embed npot tex into larger pot tex?), subregions, + // and moving GLUtils.texImage2D (originally from Android SDK) into PGL. + // Actually, this whole code should go into PGL, so the Android implementation can + // use Bitmap, and desktop use BufferedImage. + + /* + if (w != width || h != height) { + System.err.println("Sorry but I don't know how to generate mipmaps for a subregion."); + return; + } + + // Code by Mike Miller obtained from here: + // http://insanitydesign.com/wp/2009/08/01/android-opengl-es-mipmaps/ + int w0 = glWidth; + int h0 = glHeight; + int[] argbPixels = new int[w0 * h0]; + convertToARGB(pixels, argbPixels, format); + int level = 0; + int denom = 1; + + // We create a Bitmap because then we use its built-in filtered downsampling + // functionality. + Bitmap bitmap = Bitmap.createBitmap(w0, h0, Config.ARGB_8888); + bitmap.setPixels(argbPixels, 0, w0, 0, 0, w0, h0); + + while (w0 >= 1 || h0 >= 1) { + //First of all, generate the texture from our bitmap and set it to the according level + GLUtils.texImage2D(glTarget, level, bitmap, 0); + + // We are done. + if (w0 == 1 && h0 == 1) { + break; + } + + // Increase the mipmap level + level++; + denom *= 2; + + // Downsampling bitmap. We must eventually arrive to the 1x1 level, + // and if the width and height are different, there will be a few 1D + // texture levels just before. + // This update formula also allows for NPOT resolutions. + w0 = PApplet.max(1, PApplet.floor((float)glWidth / denom)); + h0 = PApplet.max(1, PApplet.floor((float)glHeight / denom)); + // (see getScaledInstance in AWT Image) + Bitmap bitmap2 = Bitmap.createScaledBitmap(bitmap, w0, h0, true); + + // Clean up + bitmap.recycle(); + bitmap = bitmap2; + } + */ + + int[] rgbaPixels = new int[w * h]; + convertToRGBA(pixels, rgbaPixels, format, w, h); + setTexels(rgbaPixels, x, y, w, h); + rgbaPixels = null; } } else { int[] rgbaPixels = new int[w * h]; @@ -904,14 +959,26 @@ public class PTexture implements PConstants { res.format = ALPHA; } - if (glMinFilter == PGL.GL_NEAREST) { + if (glMagFilter == PGL.GL_NEAREST && glMinFilter == PGL.GL_NEAREST) { res.sampling = POINT; - } else if (glMinFilter == PGL.GL_LINEAR) { + res.mipmaps = false; + } else if (glMagFilter == PGL.GL_NEAREST && glMinFilter == PGL.GL_LINEAR) { + res.sampling = LINEAR; + res.mipmaps = false; + } else if (glMagFilter == PGL.GL_NEAREST && glMinFilter == PGL.GL_LINEAR_MIPMAP_NEAREST) { + res.sampling = LINEAR; + res.mipmaps = true; + } else if (glMagFilter == PGL.GL_LINEAR && glMinFilter == PGL.GL_LINEAR) { res.sampling = BILINEAR; - } else if (glMinFilter == PGL.GL_LINEAR_MIPMAP_LINEAR) { + res.mipmaps = false; + } else if (glMagFilter == PGL.GL_LINEAR && glMinFilter == PGL.GL_LINEAR_MIPMAP_NEAREST) { + res.sampling = BILINEAR; + res.mipmaps = true; + } else if (glMagFilter == PGL.GL_LINEAR && glMinFilter == PGL.GL_LINEAR_MIPMAP_LINEAR) { res.sampling = TRILINEAR; + res.mipmaps = true; } - + if (glWrapS == PGL.GL_CLAMP_TO_EDGE) { res.wrapU = CLAMP; } else if (glWrapS == PGL.GL_REPEAT) { @@ -937,7 +1004,7 @@ public class PTexture implements PConstants { if (params.target == TEXTURE2D) { glTarget = PGL.GL_TEXTURE_2D; } else { - throw new RuntimeException("OPENGL2: Unknown texture target"); + throw new RuntimeException("Unknown texture target"); } if (params.format == RGB) { @@ -947,20 +1014,23 @@ public class PTexture implements PConstants { } else if (params.format == ALPHA) { glFormat = PGL.GL_ALPHA; } else { - throw new RuntimeException("OPENGL2: Unknown texture format"); + throw new RuntimeException("Unknown texture format"); } if (params.sampling == POINT) { glMagFilter = PGL.GL_NEAREST; glMinFilter = PGL.GL_NEAREST; + } else if (params.sampling == LINEAR) { + glMagFilter = PGL.GL_NEAREST; + glMinFilter = params.mipmaps ? PGL.GL_LINEAR_MIPMAP_NEAREST : PGL.GL_LINEAR; } else if (params.sampling == BILINEAR) { glMagFilter = PGL.GL_LINEAR; - glMinFilter = PGL.GL_LINEAR; + glMinFilter = params.mipmaps ? PGL.GL_LINEAR_MIPMAP_NEAREST : PGL.GL_LINEAR; } else if (params.sampling == TRILINEAR) { glMagFilter = PGL.GL_LINEAR; - glMinFilter = PGL.GL_LINEAR_MIPMAP_LINEAR; + glMinFilter = PGL.GL_LINEAR_MIPMAP_LINEAR; } else { - throw new RuntimeException("OPENGL2: Unknown texture filtering mode"); + throw new RuntimeException("Unknown texture filtering mode"); } if (params.wrapU == CLAMP) { @@ -968,7 +1038,7 @@ public class PTexture implements PConstants { } else if (params.wrapU == REPEAT) { glWrapS = PGL.GL_REPEAT; } else { - throw new RuntimeException("OPENGL2: Unknown wrapping mode"); + throw new RuntimeException("Unknown wrapping mode"); } if (params.wrapV == CLAMP) { @@ -976,10 +1046,11 @@ public class PTexture implements PConstants { } else if (params.wrapV == REPEAT) { glWrapT = PGL.GL_REPEAT; } else { - throw new RuntimeException("OPENGL2: Unknown wrapping mode"); + throw new RuntimeException("Unknown wrapping mode"); } - usingMipmaps = glMinFilter == PGL.GL_LINEAR_MIPMAP_LINEAR; + usingMipmaps = glMinFilter == PGL.GL_LINEAR_MIPMAP_NEAREST || + glMinFilter == PGL.GL_LINEAR_MIPMAP_LINEAR; flippedX = false; flippedY = false; @@ -989,27 +1060,7 @@ public class PTexture implements PConstants { /////////////////////////////////////////////////////////////////////////// // Parameters object - - - static public Parameters newParameters() { - return new Parameters(); - } - - static public Parameters newParameters(int format) { - return new Parameters(format); - } - - - static public Parameters newParameters(int format, int sampling) { - return new Parameters(format, sampling); - } - - - static public Parameters newParameters(Parameters params) { - return new Parameters(params); - } - /** * This class stores the parameters for a texture: target, internal format, minimization filter @@ -1027,10 +1078,15 @@ public class PTexture implements PConstants { public int format; /** - * Texture filtering (POINT, BILINEAR or TRILINEAR). + * Texture filtering (POINT, LINEAR, BILINEAR or TRILINEAR). */ public int sampling; + /** + * Use mipmaps or not. + */ + public boolean mipmaps; + /** * Wrapping mode along U. */ @@ -1042,12 +1098,13 @@ public class PTexture implements PConstants { public int wrapV; /** - * Creates an instance of GLTextureParameters, setting all the parameters to default values. + * Sets all the parameters to default values. */ public Parameters() { this.target = TEXTURE2D; this.format = ARGB; this.sampling = BILINEAR; + this.mipmaps = true; this.wrapU = CLAMP; this.wrapV = CLAMP; } @@ -1056,6 +1113,7 @@ public class PTexture implements PConstants { this.target = TEXTURE2D; this.format = format; this.sampling = BILINEAR; + this.mipmaps = true; this.wrapU = CLAMP; this.wrapV = CLAMP; } @@ -1064,16 +1122,22 @@ public class PTexture implements PConstants { this.target = TEXTURE2D; this.format = format; this.sampling = sampling; + this.mipmaps = true; this.wrapU = CLAMP; this.wrapV = CLAMP; } + public Parameters(int format, int sampling, boolean mipmaps) { + this.target = TEXTURE2D; + this.format = format; + this.sampling = sampling; + this.mipmaps = mipmaps; + this.wrapU = CLAMP; + this.wrapV = CLAMP; + } + public Parameters(Parameters src) { - this.target = src.target; - this.format = src.format; - this.sampling = src.sampling; - this.wrapU = src.wrapU; - this.wrapV = src.wrapV; + set(src); } public void set(int format) { @@ -1085,10 +1149,17 @@ public class PTexture implements PConstants { this.sampling = sampling; } + public void set(int format, int sampling, boolean mipmaps) { + this.format = format; + this.sampling = sampling; + this.mipmaps = mipmaps; + } + public void set(Parameters src) { this.target = src.target; this.format = src.format; this.sampling = src.sampling; + this.mipmaps = src.mipmaps; this.wrapU = src.wrapU; this.wrapV = src.wrapV; }