diff --git a/extensions/gdx-freetype/src/com/badlogic/gdx/graphics/g2d/freetype/FreeTypeFontGenerator.java b/extensions/gdx-freetype/src/com/badlogic/gdx/graphics/g2d/freetype/FreeTypeFontGenerator.java
index b6a7c81448f..4770322e960 100755
--- a/extensions/gdx-freetype/src/com/badlogic/gdx/graphics/g2d/freetype/FreeTypeFontGenerator.java
+++ b/extensions/gdx-freetype/src/com/badlogic/gdx/graphics/g2d/freetype/FreeTypeFontGenerator.java
@@ -30,6 +30,7 @@
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.BitmapFont.BitmapFontData;
import com.badlogic.gdx.graphics.g2d.BitmapFont.Glyph;
+import com.badlogic.gdx.graphics.g2d.GlyphLayout.GlyphRun;
import com.badlogic.gdx.graphics.g2d.PixmapPacker;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.g2d.freetype.FreeType.Bitmap;
@@ -61,7 +62,8 @@
* and have to be disposed as usual.
*
* @author mzechner
- * @author Nathan Sweet */
+ * @author Nathan Sweet
+ * @author Rob Rendell */
public class FreeTypeFontGenerator implements Disposable {
static public final String DEFAULT_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890\"!`?'.,;:()[]{}<>|/@\\^$-%+=#_&~*\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089\u008A\u008B\u008C\u008D\u008E\u008F\u0090\u0091\u0092\u0093\u0094\u0095\u0096\u0097\u0098\u0099\u009A\u009B\u009C\u009D\u009E\u009F\u00A0\u00A1\u00A2\u00A3\u00A4\u00A5\u00A6\u00A7\u00A8\u00A9\u00AA\u00AB\u00AC\u00AD\u00AE\u00AF\u00B0\u00B1\u00B2\u00B3\u00B4\u00B5\u00B6\u00B7\u00B8\u00B9\u00BA\u00BB\u00BC\u00BD\u00BE\u00BF\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF";
@@ -143,8 +145,7 @@ public BitmapFont generateFont (int size, String characters, boolean flip) {
}
/** Generates a new {@link BitmapFont}. The size is expressed in pixels. Throws a GdxRuntimeException if the font could not be
- * generated. Using big sizes might cause such an exception. All characters need to fit onto a single texture.
- *
+ * generated. Using big sizes might cause such an exception.
* @param size the size of the font in pixels
* @deprecated use {@link #generateFont(FreeTypeFontParameter)} instead */
public BitmapFont generateFont (int size) {
@@ -156,14 +157,13 @@ public BitmapFont generateFont (FreeTypeFontParameter parameter) {
}
/** Generates a new {@link BitmapFont}. The size is expressed in pixels. Throws a GdxRuntimeException if the font could not be
- * generated. Using big sizes might cause such an exception. All characters need to fit onto a single texture.
- *
+ * generated. Using big sizes might cause such an exception.
* @param parameter configures how the font is generated */
public BitmapFont generateFont (FreeTypeFontParameter parameter, FreeTypeBitmapFontData data) {
generateData(parameter, data);
if (data.regions == null && parameter.packer != null) {
- data.regions = new Array();
- parameter.packer.updateTextureRegions(data.regions, parameter.minFilter, parameter.magFilter, parameter.genMipMaps, false);
+ data.regions = new Array();
+ parameter.packer.updateTextureRegions(data.regions, parameter.minFilter, parameter.magFilter, parameter.genMipMaps);
}
BitmapFont font = new BitmapFont(data, data.regions, false);
font.setOwnsTexture(parameter.packer == null);
@@ -436,8 +436,8 @@ public FreeTypeBitmapFontData generateData (FreeTypeFontParameter parameter, Fre
// Generate texture regions.
if (ownsAtlas) {
- data.regions = new Array();
- packer.updateTextureRegions(data.regions, parameter.minFilter, parameter.magFilter, parameter.genMipMaps, true);
+ data.regions = new Array();
+ packer.updateTextureRegions(data.regions, parameter.minFilter, parameter.magFilter, parameter.genMipMaps);
}
return data;
@@ -531,18 +531,15 @@ Glyph createGlyph (char c, FreeTypeBitmapFontData data, FreeTypeFontParameter pa
}
- Rectangle rect = packer.packDirectToTexture(mainPixmap);
+ Rectangle rect = packer.pack(mainPixmap);
- // determine which page it was packed into
- glyph.page = packer.getPages().size - 1;
+ glyph.page = packer.getPages().size - 1; // Glyph is always packed into the last page for now.
glyph.srcX = (int)rect.x;
glyph.srcY = (int)rect.y;
- // If the data already has regions then this glyph is being added incrementally.
- // Create a new texture for the new glyph if necessary.
- if (data.regions != null && data.regions.size <= glyph.page) {
- packer.updateTextureRegions(data.regions, parameter.minFilter, parameter.magFilter, parameter.genMipMaps, false);
- }
+ // If a page was added, create a new texture region for the incrementally added glyph.
+ if (parameter.incremental && data.regions != null && data.regions.size <= glyph.page)
+ packer.updateTextureRegions(data.regions, parameter.minFilter, parameter.magFilter, parameter.genMipMaps);
mainPixmap.dispose();
mainGlyph.dispose();
@@ -604,7 +601,7 @@ public Glyph getGlyph (char ch) {
if (glyph == null) return null;
setGlyph(ch, glyph);
- setGlyphRegion(glyph, regions.get(glyph.page));
+ setGlyphRegion(glyph, regions.get(glyph.page), packer.getPageWidth(), packer.getPageHeight());
glyphs.add(glyph);
if (parameter.kerning) {
@@ -625,6 +622,11 @@ public Glyph getGlyph (char ch) {
return glyph;
}
+ public void getGlyphs (GlyphRun run, CharSequence str, int start, int end) {
+ if (packer != null) packer.setPackToTexture(true); // All glyphs added after this are packed directly to the texture.
+ super.getGlyphs(run, str, start, end);
+ }
+
@Override
public void dispose () {
if (stroker != null) stroker.dispose();
diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/BitmapFont.java b/gdx/src/com/badlogic/gdx/graphics/g2d/BitmapFont.java
index 0a6fdaa0996..9f8ff2f1eff 100644
--- a/gdx/src/com/badlogic/gdx/graphics/g2d/BitmapFont.java
+++ b/gdx/src/com/badlogic/gdx/graphics/g2d/BitmapFont.java
@@ -186,7 +186,8 @@ private void load (BitmapFontData data) {
throw new IllegalArgumentException("BitmapFont texture region array cannot contain null elements.");
}
- data.setGlyphRegion(glyph, region);
+ Texture texture = region.getTexture();
+ data.setGlyphRegion(glyph, region, texture.getWidth(), texture.getHeight());
}
}
}
@@ -631,9 +632,9 @@ public void load (FileHandle fontFile, boolean flip) {
}
}
- public void setGlyphRegion (Glyph glyph, TextureRegion region) {
- float invTexWidth = 1.0f / region.getTexture().getWidth();
- float invTexHeight = 1.0f / region.getTexture().getHeight();
+ public void setGlyphRegion (Glyph glyph, TextureRegion region, int textureWidth, int textureHeight) {
+ float invTexWidth = 1.0f / textureWidth;
+ float invTexHeight = 1.0f / textureHeight;
float offsetX = 0, offsetY = 0;
float u = region.u;
diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/PixmapPacker.java b/gdx/src/com/badlogic/gdx/graphics/g2d/PixmapPacker.java
index f22d43b1612..760ee7e656a 100755
--- a/gdx/src/com/badlogic/gdx/graphics/g2d/PixmapPacker.java
+++ b/gdx/src/com/badlogic/gdx/graphics/g2d/PixmapPacker.java
@@ -29,16 +29,12 @@
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.OrderedMap;
-/** Packs {@link Pixmap} instances into one more more {@link Page} instances to generate an atlas of Pixmap instances. Provides
- * means to directly convert the pixmap atlas to a {@link TextureAtlas}. The packer supports padding and border pixel duplication,
- * specified during construction. The packer supports incremental inserts and updates of TextureAtlases generated with this
- * class.
- *
- * All methods except {@link #getPage(String)} and {@link #getPages()} are thread safe. The methods
- * {@link #generateTextureAtlas(TextureFilter, TextureFilter, boolean)} and
- * {@link #updateTextureAtlas(TextureAtlas, TextureFilter, TextureFilter, boolean)} need to be called on the rendering thread, all
- * other methods can be called from any thread.
- *
+/** Packs {@link Pixmap pixmaps} into one or more {@link Page pages} to generate an atlas of pixmap instances. Provides means to
+ * directly convert the pixmap atlas to a {@link TextureAtlas}. The packer supports padding and border pixel duplication,
+ * specified during construction. The packer supports incremental inserts and updates of TextureAtlases generated with this class.
+ *
+ * All methods can be called from any thread unless otherwise noted.
+ *
* One-off usage:
*
*
@@ -46,28 +42,33 @@
* PixmapPacker packer = new PixmapPacker(512, 512, Format.RGB565, 2, true);
* packer.pack("First Pixmap", pixmap1);
* packer.pack("Second Pixmap", pixmap2);
- * TextureAtlas atlas = packer.generateTextureAtlas(TextureFilter.Nearest, TextureFilter.Nearest);
+ * TextureAtlas atlas = packer.generateTextureAtlas(TextureFilter.Nearest, TextureFilter.Nearest, false);
+ * packer.dispose();
+ * // ...
+ * atlas.dispose();
*
*
- * Note that you should not dispose the packer in this usage pattern. Instead, dispose the TextureAtlas if no longer needed.
+ * With this usage pattern, disposing the packer will not dispose any pixmaps used by the texture atlas. The texture atlas must
+ * also be disposed when no longer needed.
*
- * Incremental usage:
+ * Incremental texture atlas usage:
*
*
* // 512x512 pixel pages, RGB565 format, 2 pixels of padding, no border duplication
* PixmapPacker packer = new PixmapPacker(512, 512, Format.RGB565, 2, false);
- * TextureAtlas incrementalAtlas = new TextureAtlas();
+ * TextureAtlas atlas = new TextureAtlas();
*
* // potentially on a separate thread, e.g. downloading thumbnails
* packer.pack("thumbnail", thumbnail);
*
* // on the rendering thread, every frame
- * packer.updateTextureAtlas(incrementalAtlas, TextureFilter.Linear, TextureFilter.Linear);
+ * packer.updateTextureAtlas(atlas, TextureFilter.Linear, TextureFilter.Linear, false);
*
* // once the atlas is no longer needed, make sure you get the final additions. This might
* // be more elaborate depending on your threading model.
- * packer.updateTextureAtlas(incrementalAtlas, TextureFilter.Linear, TextureFilter.Linear);
- * incrementalAtlas.dispose();
+ * packer.updateTextureAtlas(atlas, TextureFilter.Linear, TextureFilter.Linear, false);
+ * // ...
+ * atlas.dispose();
*
*
* Pixmap-only usage:
@@ -79,165 +80,72 @@
*
* // do something interesting with the resulting pages
* for (Page page : packer.getPages()) {
+ * // ...
* }
*
- * // dispose of the packer in this case
* packer.dispose();
- * */
+ *
+ * @author mzechner
+ * @author Nathan Sweet
+ * @author Rob Rendell */
public class PixmapPacker implements Disposable {
+ static private final boolean debug = false;
+ static private final String ANONYMOUS = "ANONYMOUS";
- static final class Node {
- public Node leftChild;
- public Node rightChild;
- public Rectangle rect;
- public String leaveName;
-
- public Node (int x, int y, int width, int height, Node leftChild, Node rightChild, String leaveName) {
- this.rect = new Rectangle(x, y, width, height);
- this.leftChild = leftChild;
- this.rightChild = rightChild;
- this.leaveName = leaveName;
- }
-
- public Node () {
- rect = new Rectangle();
- }
- }
-
- public class Page {
- Node root;
- OrderedMap rects;
- Pixmap image;
- Texture texture;
- final Array addedRects = new Array();
- boolean textureNeedsRefresh = false;
-
- public Pixmap getPixmap () {
- return image;
- }
-
- public OrderedMap getRects () {
- return rects;
- }
- }
-
- private static final String ANONYMOUS = "ANONYMOUS";
-
- final int pageWidth;
- final int pageHeight;
+ final int pageWidth, pageHeight;
final Format pageFormat;
final int padding;
final boolean duplicateBorder;
final Array pages = new Array();
- Page currPage;
+ Page current;
+ boolean packToTexture;
boolean disposed;
- /**
- * Creates a new ImagePacker which will insert all supplied images into a width
by height
image.
- * padding
specifies the minimum number of pixels to insert between images. border
will duplicate the
- * border pixels of the inserted images to avoid seams when rendering with bi-linear filtering on.
- *
- *
- * @param width the width of the output image
- * @param height the height of the output image
- * @param padding the number of padding pixels
- * @param duplicateBorder whether to duplicate the border */
- public PixmapPacker (int width, int height, Format format, int padding, boolean duplicateBorder) {
- this.pageWidth = width;
- this.pageHeight = height;
- this.pageFormat = format;
+ /** Creates a new ImagePacker which will insert all supplied pixmaps into one or more pageWidth
by
+ * pageHeight
pixmaps.
+ * @param padding the number of blank pixels to insert between pixmaps.
+ * @param duplicateBorder duplicate the border pixels of the inserted images to avoid seams when rendering with bi-linear
+ * filtering on. */
+ public PixmapPacker (int pageWidth, int pageHeight, Format pageFormat, int padding, boolean duplicateBorder) {
+ this.pageWidth = pageWidth;
+ this.pageHeight = pageHeight;
+ this.pageFormat = pageFormat;
this.padding = padding;
this.duplicateBorder = duplicateBorder;
newPage();
}
- /**
- * Inserts the given {@link Pixmap} with no name. You must use the returned Rectangle to later access the
- * Pixmap's location in the appropriate page (which will be the last page in the PixmapPacker at the time this
- * method returns).
- *
- *
- * @param image the image
- * @return Rectangle describing the area the pixmap was rendered to.
- * @throws RuntimeException in case the image did not fit due to the page size being too small */
- public synchronized Rectangle pack(Pixmap image) {
- return pack(null, image, false);
+ /** Inserts the pixmap without a name. It cannot be looked up by name.
+ * @see #pack(String, Pixmap) */
+ public synchronized Rectangle pack (Pixmap image) {
+ return pack(null, image);
}
- /**
- * Inserts the given {@link Pixmap}. You can later retrieve the image's position in the output image via the supplied name
- * and the method {@link #getRect(String)}.
- *
- *
- * @param name the name of the image. This can be null, in which case the image is packed anonymously (see {@link #pack(Pixmap)}).
- * @param image the image
+ /** Inserts the pixmap. If name was not null, you can later retrieve the image's position in the output image via
+ * {@link #getRect(String)}.
+ * @param name If null, the image cannot be looked up by name.
* @return Rectangle describing the area the pixmap was rendered to.
- * @throws RuntimeException in case the image did not fit due to the page size being too small or providing a duplicate name */
+ * @throws GdxRuntimeException in case the image did not fit due to the page size being too small or providing a duplicate
+ * name. */
public synchronized Rectangle pack (String name, Pixmap image) {
- return pack(name, image, false);
- }
-
- /**
- * Inserts the given {@link Pixmap} with no name. You must use the returned Rectangle to later access the
- * Pixmap's location in the appropriate page (which will be the last page in the PixmapPacker at the time this
- * method returns).
- *
- *
- * The added pixmap will be directly added to the texture on the graphics card if it has already been generated
- * (e.g. if a TextureAtlas or TextureRegions have been built using this packer). This is more efficient for small
- * numbers of small Pixmaps. If you are packing large Pixmaps or many Pixmaps, use {@link #pack(Pixmap)} followed
- * by generating or updating a TextureAtlas instead.
- *
- *
- * @param image the image
- * @return Rectangle describing the area the pixmap was rendered to.
- * @throws RuntimeException in case the image did not fit due to the page size being too small */
- public synchronized Rectangle packDirectToTexture(Pixmap image) {
- return pack(null, image, true);
- }
-
- /**
- * Inserts the given {@link Pixmap}. You can later retrieve the image's position in the output image via the supplied name
- * and the method {@link #getRect(String)}.
- *
- *
- * The added pixmap will be directly added to the texture on the graphics card if it has already been generated
- * (e.g. if a TextureAtlas or TextureRegions have been built using this packer). This is more efficient for small
- * numbers of small Pixmaps. If you are packing large Pixmaps or many Pixmaps, use {@link #pack(String, Pixmap)}
- * followed by generating or updating a TextureAtlas instead.
- *
- *
- * @param name the name of the image. This can be null, in which case the image is packed anonymously (see {@link #packDirectToTexture(Pixmap)}).
- * @param image the image
- * @return Rectangle describing the area the pixmap was rendered to.
- * @throws RuntimeException in case the image did not fit due to the page size being too small or providing a duplicate name */
- public synchronized Rectangle packDirectToTexture(String name, Pixmap image) {
- return pack(name, image, true);
- }
-
- private Rectangle pack (String name, Pixmap image, boolean directToTexture) {
if (disposed) return null;
- if (name != null && getRect(name) != null) throw new RuntimeException("Key with name '" + name + "' is already in map");
- int borderPixels = padding + (duplicateBorder ? 1 : 0);
- borderPixels <<= 1;
+ if (name != null && getRect(name) != null)
+ throw new GdxRuntimeException("Pixmap has already been packed with name: " + name);
+ int borderPixels = (padding + (duplicateBorder ? 1 : 0)) << 1;
Rectangle rect = new Rectangle(0, 0, image.getWidth() + borderPixels, image.getHeight() + borderPixels);
if (rect.getWidth() > pageWidth || rect.getHeight() > pageHeight) {
- if (name == null) {
- throw new GdxRuntimeException("page size for anonymous pixmap too small");
- } else {
- throw new GdxRuntimeException("page size for '" + name + "' too small");
- }
+ if (name == null) throw new GdxRuntimeException("Page size too small for anonymous pixmap.");
+ throw new GdxRuntimeException("Page size too small for pixmap: " + name);
}
- Node node = insert(currPage.root, rect);
-
+ Node node = insert(current.root, rect);
if (node == null) {
newPage();
- return pack(name, image, directToTexture);
+ return pack(name, image);
}
+ node.leafName = name == null ? ANONYMOUS : name;
- node.leaveName = (name == null) ? ANONYMOUS : name;
rect = new Rectangle(node.rect);
rect.width -= borderPixels;
rect.height -= borderPixels;
@@ -245,37 +153,36 @@ private Rectangle pack (String name, Pixmap image, boolean directToTexture) {
rect.x += borderPixels;
rect.y += borderPixels;
if (name != null) {
- currPage.rects.put(name, rect);
- currPage.addedRects.add(name);
- }
- if (directToTexture && currPage.texture != null && !currPage.textureNeedsRefresh && !duplicateBorder) {
- currPage.texture.bind();
- Gdx.gl.glTexSubImage2D(currPage.texture.glTarget, 0, (int) rect.x, (int) rect.y, (int) rect.width, (int) rect.height,
- image.getGLFormat(), image.getGLType(), image.getPixels());
- } else {
- currPage.textureNeedsRefresh = true;
+ current.rects.put(name, rect);
+ current.addedRects.add(name);
}
+ int rectX = (int)rect.x, rectY = (int)rect.y, rectWidth = (int)rect.width, rectHeight = (int)rect.height;
+
+ if (packToTexture && current.texture != null && !duplicateBorder) {
+ if (debug) System.out.println("Incrementally updated texture.");
+ current.texture.bind();
+ Gdx.gl.glTexSubImage2D(current.texture.glTarget, 0, rectX, rectY, rectWidth, rectHeight, image.getGLFormat(),
+ image.getGLType(), image.getPixels());
+ } else
+ current.dirty = true;
Blending blending = Pixmap.getBlending();
Pixmap.setBlending(Blending.None);
- this.currPage.image.drawPixmap(image, (int)rect.x, (int)rect.y);
+
+ current.image.drawPixmap(image, rectX, rectY);
if (duplicateBorder) {
- int imageWidth = image.getWidth();
- int imageHeight = image.getHeight();
+ int imageWidth = image.getWidth(), imageHeight = image.getHeight();
// Copy corner pixels to fill corners of the padding.
- this.currPage.image.drawPixmap(image, 0, 0, 1, 1, (int)rect.x - 1, (int)rect.y - 1, 1, 1);
- this.currPage.image.drawPixmap(image, imageWidth - 1, 0, 1, 1, (int)rect.x + (int)rect.width, (int)rect.y - 1, 1, 1);
- this.currPage.image.drawPixmap(image, 0, imageHeight - 1, 1, 1, (int)rect.x - 1, (int)rect.y + (int)rect.height, 1, 1);
- this.currPage.image.drawPixmap(image, imageWidth - 1, imageHeight - 1, 1, 1, (int)rect.x + (int)rect.width, (int)rect.y
- + (int)rect.height, 1, 1);
+ current.image.drawPixmap(image, 0, 0, 1, 1, rectX - 1, rectY - 1, 1, 1);
+ current.image.drawPixmap(image, imageWidth - 1, 0, 1, 1, rectX + rectWidth, rectY - 1, 1, 1);
+ current.image.drawPixmap(image, 0, imageHeight - 1, 1, 1, rectX - 1, rectY + rectHeight, 1, 1);
+ current.image.drawPixmap(image, imageWidth - 1, imageHeight - 1, 1, 1, rectX + rectWidth, rectY + rectHeight, 1, 1);
// Copy edge pixels into padding.
- this.currPage.image.drawPixmap(image, 0, 0, imageWidth, 1, (int)rect.x, (int)rect.y - 1, (int)rect.width, 1);
- this.currPage.image.drawPixmap(image, 0, imageHeight - 1, imageWidth, 1, (int)rect.x, (int)rect.y + (int)rect.height,
- (int)rect.width, 1);
- this.currPage.image.drawPixmap(image, 0, 0, 1, imageHeight, (int)rect.x - 1, (int)rect.y, 1, (int)rect.height);
- this.currPage.image.drawPixmap(image, imageWidth - 1, 0, 1, imageHeight, (int)rect.x + (int)rect.width, (int)rect.y, 1,
- (int)rect.height);
+ current.image.drawPixmap(image, 0, 0, imageWidth, 1, rectX, rectY - 1, rectWidth, 1);
+ current.image.drawPixmap(image, 0, imageHeight - 1, imageWidth, 1, rectX, rectY + rectHeight, rectWidth, 1);
+ current.image.drawPixmap(image, 0, 0, 1, imageHeight, rectX - 1, rectY, 1, rectHeight);
+ current.image.drawPixmap(image, imageWidth - 1, 0, 1, imageHeight, rectX + rectWidth, rectY, 1, rectHeight);
}
Pixmap.setBlending(blending);
@@ -287,24 +194,19 @@ private void newPage () {
Page page = new Page();
page.image = new Pixmap(pageWidth, pageHeight, pageFormat);
page.root = new Node(0, 0, pageWidth, pageHeight, null, null, null);
- page.rects = new OrderedMap();
+ page.rects = new OrderedMap();
pages.add(page);
- currPage = page;
+ current = page;
}
private Node insert (Node node, Rectangle rect) {
- if (node.leaveName == null && node.leftChild != null && node.rightChild != null) {
- Node newNode = null;
-
- newNode = insert(node.leftChild, rect);
+ if (node.leafName == null && node.leftChild != null && node.rightChild != null) {
+ Node newNode = insert(node.leftChild, rect);
if (newNode == null) newNode = insert(node.rightChild, rect);
-
return newNode;
} else {
- if (node.leaveName != null) return null;
-
+ if (node.leafName != null) return null;
if (node.rect.width == rect.width && node.rect.height == rect.height) return node;
-
if (node.rect.width < rect.width || node.rect.height < rect.height) return null;
node.leftChild = new Node();
@@ -312,7 +214,6 @@ private Node insert (Node node, Rectangle rect) {
int deltaWidth = (int)node.rect.width - (int)rect.width;
int deltaHeight = (int)node.rect.height - (int)rect.height;
-
if (deltaWidth > deltaHeight) {
node.leftChild.rect.x = node.rect.x;
node.leftChild.rect.y = node.rect.y;
@@ -339,7 +240,8 @@ private Node insert (Node node, Rectangle rect) {
}
}
- /** @return the {@link Page} instances created so far. This method is not thread safe! */
+ /** @return the {@link Page} instances created so far. If multiple threads are accessing the packer, iterating over the pages
+ * must be done only after synchronizing on the packer. */
public Array getPages () {
return pages;
}
@@ -375,10 +277,8 @@ public synchronized int getPageIndex (String name) {
return -1;
}
- /** Disposes any Pixmap instances which haven't been used internally to create a texture (e.g. by
- * {@link #generateTextureAtlas(TextureFilter, TextureFilter, boolean)} or
- * {@link #updateTextureAtlas(TextureAtlas, TextureFilter, TextureFilter, boolean)}). If you call one of those
- * methods, you will also need to dispose of the texture atlas. */
+ /** Disposes any pixmap pages which don't have a texture. Page pixmaps that have a texture will not be disposed until their
+ * texture is disposed. */
public synchronized void dispose () {
for (Page page : pages) {
if (page.texture == null) {
@@ -388,85 +288,47 @@ public synchronized void dispose () {
disposed = true;
}
- /** Generates a new {@link TextureAtlas} from the {@link Pixmap} instances inserted so far. After calling this method,
- * disposing the PixmapPacker will no longer dispose the atlas' textures or their backing pixmaps.
- * @param minFilter
- * @param magFilter
- * @return the TextureAtlas */
+ /** Generates a new {@link TextureAtlas} from the pixmaps inserted so far. After calling this method, disposing the packer will
+ * no longer dispose the page pixmaps. */
public synchronized TextureAtlas generateTextureAtlas (TextureFilter minFilter, TextureFilter magFilter, boolean useMipMaps) {
TextureAtlas atlas = new TextureAtlas();
updateTextureAtlas(atlas, minFilter, magFilter, useMipMaps);
return atlas;
}
- /** Updates the given {@link TextureAtlas}, adding any new {@link Pixmap} instances packed since the last call to this method.
- * This can be used to insert Pixmap instances on a separate thread via {@link #pack(String, Pixmap)} and update the
- * TextureAtlas on the rendering thread. This method must be called on the rendering thread. After calling this method,
- * disposing the PixmapPacker will no longer dispose the atlas' textures or their backing pixmaps.
- */
- public synchronized void updateTextureAtlas(TextureAtlas atlas, TextureFilter minFilter, TextureFilter magFilter,
+ /** Updates the {@link TextureAtlas}, adding any new {@link Pixmap} instances packed since the last call to this method. This
+ * can be used to insert Pixmap instances on a separate thread via {@link #pack(String, Pixmap)} and update the TextureAtlas on
+ * the rendering thread. This method must be called on the rendering thread. After calling this method, disposing the packer
+ * will no longer dispose the page pixmaps. */
+ public synchronized void updateTextureAtlas (TextureAtlas atlas, TextureFilter minFilter, TextureFilter magFilter,
boolean useMipMaps) {
+ updatePageTextures(minFilter, magFilter, useMipMaps);
for (Page page : pages) {
- if (page.textureNeedsRefresh) {
- if (page.texture == null) {
- generatePageTexture(page, minFilter, magFilter, useMipMaps);
- } else {
- page.texture.load(page.texture.getTextureData());
- }
- page.textureNeedsRefresh = false;
- }
if (page.addedRects.size > 0) {
for (String name : page.addedRects) {
Rectangle rect = page.rects.get(name);
- TextureRegion region = new TextureRegion(page.texture, (int)rect.x, (int)rect.y, (int)rect.width,
- (int)rect.height);
+ TextureRegion region = new TextureRegion(page.texture, (int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height);
atlas.addRegion(name, region);
}
page.addedRects.clear();
+ atlas.getTextures().add(page.texture);
}
- atlas.getTextures().add(page.texture);
}
}
- private void generatePageTexture(Page page, TextureFilter minFilter, TextureFilter magFilter, boolean useMipMaps) {
- page.texture = new Texture(new PixmapTextureData(page.image, page.image.getFormat(), useMipMaps, false, true)) {
- @Override
- public void dispose() {
- super.dispose();
- getTextureData().consumePixmap().dispose();
- }
- };
- page.texture.setFilter(minFilter, magFilter);
+ /** Calls {@link Page#updateTexture(TextureFilter, TextureFilter, boolean) updateTexture} for each page and adds a region to the
+ * specified array for each page texture. */
+ public synchronized void updateTextureRegions (Array regions, TextureFilter minFilter, TextureFilter magFilter,
+ boolean useMipMaps) {
+ updatePageTextures(minFilter, magFilter, useMipMaps);
+ while (regions.size < pages.size)
+ regions.add(new TextureRegion(pages.get(regions.size).texture));
}
- /**
- * Update the provided array with texture regions corresponding with the pages of the packer, creating new
- * textures as necessary. When you are finished, you must dispose of the regions or a TextureAtlas created by this
- * PixmapPacker - disposing this PixmapPacker will no longer dispose these textures or their backing pixmaps.
- *
- * @param textureRegions The array of TextureRegions to refresh.
- * @param minFilter minFilter to use when generating required textures.
- * @param magFilter magFilter to use when generating required textures.
- * @param useMipMaps useMipMaps to use when generating required textures.
- * @param refresh If true, textures will be refreshed if required (i.e. if Pixmaps were added after the
- * texture was generated). If false, textures will not be refreshed, and will need to be
- * refreshed by a final call to this method with this parameter true or by generating or
- * updating a TextureAtlas before the textures can be correctly drawn.
- */
- public synchronized void updateTextureRegions(Array textureRegions, TextureFilter minFilter,
- TextureFilter magFilter, boolean useMipMaps, boolean refresh) {
- if (disposed) return;
- for (Page page : pages) {
- if (page.texture == null) {
- generatePageTexture(page, minFilter, magFilter, useMipMaps);
- } else if (refresh && page.textureNeedsRefresh) {
- page.texture.load(page.texture.getTextureData());
- page.textureNeedsRefresh = false;
- }
- if (textureRegions.size < pages.size) {
- textureRegions.add(new TextureRegion(page.texture));
- }
- }
+ /** Calls {@link Page#updateTexture(TextureFilter, TextureFilter, boolean) updateTexture} for each page. */
+ public synchronized void updatePageTextures (TextureFilter minFilter, TextureFilter magFilter, boolean useMipMaps) {
+ for (Page page : pages)
+ page.updateTexture(minFilter, magFilter, useMipMaps);
}
public int getPageWidth () {
@@ -481,8 +343,85 @@ public int getPadding () {
return padding;
}
- public boolean duplicateBorder () {
+ public boolean getDuplicateBorder () {
return duplicateBorder;
}
+ public boolean getPackToTexture () {
+ return packToTexture;
+ }
+
+ /** If true, when a pixmap is packed to a page that has a texture, the portion of the texture where the pixmap was packed is
+ * updated using glTexSubImage2D. Note if packing many pixmaps, this may be slower than reuploading the whole texture. This
+ * setting is ignored if {@link #getDuplicateBorder()} is true. */
+ public void setPackToTexture (boolean packToTexture) {
+ this.packToTexture = packToTexture;
+ }
+
+ static final class Node {
+ public Node leftChild;
+ public Node rightChild;
+ public Rectangle rect;
+ public String leafName;
+
+ public Node (int x, int y, int width, int height, Node leftChild, Node rightChild, String leafName) {
+ rect = new Rectangle(x, y, width, height);
+ this.leftChild = leftChild;
+ this.rightChild = rightChild;
+ this.leafName = leafName;
+ }
+
+ public Node () {
+ rect = new Rectangle();
+ }
+ }
+
+ /** @author mzechner
+ * @author Nathan Sweet
+ * @author Rob Rendell */
+ static public class Page {
+ Node root;
+ OrderedMap rects;
+ Pixmap image;
+ Texture texture;
+ final Array addedRects = new Array();
+ boolean dirty;
+
+ public Pixmap getPixmap () {
+ return image;
+ }
+
+ public OrderedMap getRects () {
+ return rects;
+ }
+
+ /** Returns the texture for this page, or null if the texture has not been created.
+ * @see #updateTexture(TextureFilter, TextureFilter, boolean) */
+ public Texture getTexture () {
+ return texture;
+ }
+
+ /** Creates the texture if it has not been created, else reuploads the entire page pixmap to the texture if the pixmap has
+ * changed since this method was last called.
+ * @return true if the texture was created or reuploaded. */
+ public boolean updateTexture (TextureFilter minFilter, TextureFilter magFilter, boolean useMipMaps) {
+ if (texture != null) {
+ if (!dirty) return false;
+ if (debug) System.out.println("Reuploaded existing texture.");
+ texture.load(texture.getTextureData());
+ } else {
+ if (debug) System.out.println("Created new texture.");
+ texture = new Texture(new PixmapTextureData(image, image.getFormat(), useMipMaps, false, true)) {
+ @Override
+ public void dispose () {
+ super.dispose();
+ image.dispose();
+ }
+ };
+ texture.setFilter(minFilter, magFilter);
+ }
+ dirty = false;
+ return true;
+ }
+ }
}
diff --git a/gdx/src/com/badlogic/gdx/utils/ObjectSet.java b/gdx/src/com/badlogic/gdx/utils/ObjectSet.java
index 28156376190..c72b591df29 100644
--- a/gdx/src/com/badlogic/gdx/utils/ObjectSet.java
+++ b/gdx/src/com/badlogic/gdx/utils/ObjectSet.java
@@ -359,7 +359,7 @@ public T first () {
T[] keyTable = this.keyTable;
for (int i = 0, n = capacity + stashSize; i < n; i++)
if (keyTable[i] != null) return keyTable[i];
- throw new IllegalStateException("IntSet is empty.");
+ throw new IllegalStateException("ObjectSet is empty.");
}
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
diff --git a/tests/gdx-tests/src/com/badlogic/gdx/tests/PixmapPackerTest.java b/tests/gdx-tests/src/com/badlogic/gdx/tests/PixmapPackerTest.java
index bf377ca99eb..ce1ff08dd89 100755
--- a/tests/gdx-tests/src/com/badlogic/gdx/tests/PixmapPackerTest.java
+++ b/tests/gdx-tests/src/com/badlogic/gdx/tests/PixmapPackerTest.java
@@ -37,12 +37,12 @@ public class PixmapPackerTest extends GdxTest {
OrthographicCamera camera;
SpriteBatch batch;
- ShapeRenderer shapeRenderer;
+ ShapeRenderer shapeRenderer;
TextureAtlas atlas;
int pageToShow = 0;
- private Array textureRegions;
+ Array textureRegions;
@Override
public void create () {
@@ -57,7 +57,7 @@ public void create () {
Pixmap pixmap2 = new Pixmap(Gdx.files.internal("data/particle-fire.png"));
Pixmap pixmap3 = new Pixmap(Gdx.files.internal("data/isotile.png"));
- PixmapPacker packer = new PixmapPacker(1024, 1024, Format.RGBA8888, 2, true);
+ PixmapPacker packer = new PixmapPacker(1024, 1024, Format.RGBA8888, 2, false);
for (int count = 1; count <= 3; ++count) {
packer.pack("badlogic " + count, pixmap1);
packer.pack("fire " + count, pixmap2);
@@ -67,6 +67,8 @@ public void create () {
atlas = packer.generateTextureAtlas(TextureFilter.Nearest, TextureFilter.Nearest, false);
Gdx.app.log("PixmapPackerTest", "Number of initial textures: " + atlas.getTextures().size);
+ packer.setPackToTexture(true);
+
for (int count = 4; count <= 10; ++count) {
packer.pack("badlogic " + count, pixmap1);
packer.pack("fire " + count, pixmap2);
@@ -79,11 +81,11 @@ public void create () {
packer.updateTextureAtlas(atlas, TextureFilter.Nearest, TextureFilter.Nearest, false);
textureRegions = new Array();
- packer.updateTextureRegions(textureRegions, TextureFilter.Nearest, TextureFilter.Nearest, false, true);
+ packer.updateTextureRegions(textureRegions, TextureFilter.Nearest, TextureFilter.Nearest, false);
Gdx.app.log("PixmapPackerTest", "Number of updated textures: " + atlas.getTextures().size);
Gdx.input.setInputProcessor(new InputAdapter() {
@Override
- public boolean keyDown(int keycode) {
+ public boolean keyDown (int keycode) {
if (keycode >= Input.Keys.NUM_0 && keycode <= Input.Keys.NUM_9) {
int number = keycode - Input.Keys.NUM_0;
if (number < textureRegions.size) {
diff --git a/tests/gdx-tests/src/com/badlogic/gdx/tests/extensions/FreeTypeAtlasTest.java b/tests/gdx-tests/src/com/badlogic/gdx/tests/extensions/FreeTypeAtlasTest.java
index 79532552ed3..eff94be535b 100755
--- a/tests/gdx-tests/src/com/badlogic/gdx/tests/extensions/FreeTypeAtlasTest.java
+++ b/tests/gdx-tests/src/com/badlogic/gdx/tests/extensions/FreeTypeAtlasTest.java
@@ -69,8 +69,7 @@ static enum FontStyle {
OrthographicCamera camera;
SpriteBatch batch;
String text;
- TextureAtlas atlas;
-
+ PixmapPacker packer;
FontMap fontMap;
public static final int FONT_ATLAS_WIDTH = 1024;
@@ -93,7 +92,7 @@ public void create() {
long start = System.currentTimeMillis();
int glyphCount = createFonts();
long time = System.currentTimeMillis() - start;
- text = glyphCount + " glyphs packed in " + atlas.getTextures().size + " page(s) in " + time + " ms";
+ text = glyphCount + " glyphs packed in " + packer.getPages().size + " page(s) in " + time + " ms";
}
@@ -128,7 +127,7 @@ public void render() {
// draw all glyphs in background
batch.setColor(1f, 1f, 1f, 0.15f);
- batch.draw(atlas.getTextures().first(), 0, 0);
+ batch.draw(packer.getPages().first().getTexture(), 0, 0);
batch.setColor(1f, 1f, 1f, 1f);
batch.end();
}
@@ -136,7 +135,7 @@ public void render() {
@Override
public void dispose() {
super.dispose();
- atlas.dispose();
+ packer.dispose();
batch.dispose();
}
@@ -157,7 +156,7 @@ protected int createFonts() {
// //////////////////////////////////////////////////////////////////////////////////////////////////////
// create the pixmap packer
- PixmapPacker packer = new PixmapPacker(FONT_ATLAS_WIDTH, FONT_ATLAS_HEIGHT, Format.RGBA8888, 2, false);
+ packer = new PixmapPacker(FONT_ATLAS_WIDTH, FONT_ATLAS_HEIGHT, Format.RGBA8888, 2, false);
fontMap = new FontMap();
int fontCount = 0;
@@ -184,11 +183,6 @@ protected int createFonts() {
gen.dispose();
}
- // Generate a texture atlas, which also finalises the textures.
- atlas = packer.generateTextureAtlas(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest, false);
-
- // We don't need to dispose packer, because its Pixmaps are now owned by the atlas.
-
// for the demo, show how many glyphs we loaded
return fontCount * CHARACTERS.length();
}
diff --git a/tests/gdx-tests/src/com/badlogic/gdx/tests/extensions/FreeTypePackTest.java b/tests/gdx-tests/src/com/badlogic/gdx/tests/extensions/FreeTypePackTest.java
index 0e016a51ef2..1cb69975152 100755
--- a/tests/gdx-tests/src/com/badlogic/gdx/tests/extensions/FreeTypePackTest.java
+++ b/tests/gdx-tests/src/com/badlogic/gdx/tests/extensions/FreeTypePackTest.java
@@ -187,7 +187,7 @@ protected int createFonts () {
// Get regions from our packer
regions = new Array();
- packer.updateTextureRegions(regions, TextureFilter.Nearest, TextureFilter.Nearest, false, true);
+ packer.updateTextureRegions(regions, TextureFilter.Nearest, TextureFilter.Nearest, false);
// No more need for our CPU-based pixmap packer, as our textures are now on GPU
packer.dispose();