texture quality and mipmapping settings, making sure that text looks crisp in 2D.

This commit is contained in:
codeanticode
2012-05-28 15:45:49 +00:00
parent 6e15d85629
commit cfa6b4565a
7 changed files with 194 additions and 72 deletions
@@ -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<PFont.Glyph, TextureInfo> 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];
@@ -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;
@@ -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");
@@ -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;
}