/* MipzipLoader.java * * Copyright (c) 2003, : Tuomas J. Lukka * * This file is part of Gzz. * * Gzz is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Gzz is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General * Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with Gzz; if not, write to the Free * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA * * */ /* * Written by : Tuomas J. Lukka */ package org.nongnu.libvob.gl; import org.nongnu.libvob.*; import org.nongnu.libvob.util.Background; import java.awt.Dimension; import java.io.*; import java.util.*; import java.util.zip.*; /** A loader for Mipzip files: files of zipped, * compressed mipmap levels. *
* RESPONSIBILITIES: Manage the combination of
* a single texture and a mipzip file, creating a "virtualized" texture
* of which better-quality versions can be loaded and discarded at will.
*/
public class MipzipLoader {
public static boolean dbg = false;
final static void pa(String s) { System.out.println(s); }
/** I have a hunch that zlib might not be re-entrant
* and that that would be causing our problems,
* since loading a level synchronously
* at the beginning mixed everything.
*/
private static Object zlibLock = new Object();
private int goalBaseLevel = -15;
private int currentBaseLevel = 1000;
/** The base level will never be set higher than this.
* Apparently, the NV driver prefers to have more than one
* mipmap level loaded in the beginning, possibly
* because of the array shape?
*/
private int maxBaseLevel;
private Background bg = null;
private float priority = 0;
/** The file in which the mipmaps are stored.
*/
private final File mipzipFile;
/** The texture.
*/
private final GL.Texture tex;
/** The compressed format.
*/
private final String texFormat;
private final boolean compressedFormat;
private int bitsPerTexel;
/** The opened Zip file. At any given
* time, may be null or open.
* Use getZipFile() to get it inside these classes.
*/
private ZipFile zip;
private final Level[] levels;
/** Open (if not already open) and return the ZipFile.
*/
synchronized private ZipFile getZipFile() throws IOException {
if(zip == null)
zip = new ZipFile(mipzipFile);
return zip;
}
synchronized private byte[] readEntry(String name) throws IOException {
synchronized(zlibLock) {
ZipFile f = getZipFile();
ZipEntry e = f.getEntry(name);
byte[] loadedData = new byte[(int)e.getSize()];
InputStream i = f.getInputStream(e);
int offs = 0;
while(offs < loadedData.length) {
int res = i.read(loadedData, offs, loadedData.length - offs);
if(res < 0) throw new IOException("EOF");
offs += res;
}
i.close();
return loadedData;
}
}
private static final int STATE_NONE = 1,
STATE_DATALOADED = 2,
STATE_INTEXTURE = 3;
/** A single mipmap level.
* Responsibilities: Know if loaded, load data, teximage data, discard.
*/
private class Level implements Runnable {
/** Whether this level is loaded into
* the texture.
*/
int state = STATE_NONE;
/** The index of this level.
*/
int level;
/** Sizes of the mipmap levels.
*/
Dimension size;
/** The data loaded from the disk.
*/
byte[] loadedData;
/** Load the data for this mipmap level
* synchronously into memory. This method
* does not cause TexImage to be called
* and can therefore be called in any
* thread.
*/
synchronized void loadData() throws IOException {
if(dbg)
pa("LoadData "+MipzipLoader.this+" "+level+
" "+state);
if(state != STATE_NONE) return;
if(loadedData == null)
loadedData = readEntry(""+level);
state = STATE_DATALOADED;
}
synchronized public void run() { texImage(); }
/** Call TexImage and afterwards discard the loaded data.
* Must be called in GL thread.
*/
synchronized void texImage() {
if(dbg)
pa("TexImage "+MipzipLoader.this+" "+level+
" "+state);
if(state != STATE_DATALOADED) return;
if(compressedFormat)
tex.compressedTexImage(level, texFormat,
size.width, size.height, 0,
loadedData);
else
tex.texImage2D(level, "RGB", size.width, size.height,
0, texFormat, "BYTE", loadedData);
state = STATE_INTEXTURE;
loadedData = null;
AbstractUpdateManager.doWhenIdle( r_runGL, priority);
}
/** Discard the texture level from the GL side.
* Done by loading an 1x1 image in its place.
* Hope this really works.
*/
synchronized void discard() {
if(dbg)
pa("Discard "+MipzipLoader.this+" "+level+
" "+state);
switch(state) {
case STATE_INTEXTURE:
/*
tex.loadNull2D(level, texFormat,
4, 2, 0, "RGB", "FLOAT");
*/
case STATE_DATALOADED:
loadedData = null;
}
state = STATE_NONE;
}
/** The texture level from the GL side was discarded.
* Because discard() doesn't work on NV drivers,
* do it this way.
*/
synchronized void wasDiscarded() {
if(dbg)
pa("Discarded "+MipzipLoader.this+" "+level+
" "+state);
loadedData = null;
state = STATE_NONE;
}
/** Create a level.
* @param l The mipmap level index
* @param s The size string from the comment in the file, e.g."64x128"
*/
Level(int l, String s) throws IOException {
this.level = l;
int i = s.indexOf('x');
if(i < 0) throw new IOException("Invalid size string");
int w = Integer.parseInt(s.substring(0,i));
int h = Integer.parseInt(s.substring(i+1));
size = new Dimension(w, h);
}
}
/** Set the texture parameter "BASE_LEVEL" to the given value.
*/
synchronized private void setBaseLevel(int level) {
if(level < 0) level = 0;
if(level >= maxBaseLevel) level = maxBaseLevel;
tex.setTexParameter("TEXTURE_2D", "TEXTURE_BASE_LEVEL", level);
tex.setTexParameter("TEXTURE_2D", "TEXTURE_MIN_LOD", level);
currentBaseLevel = level;
}
/** Load to the base level synchronously: no discards.
* @param level The base level: the lowest-detail level to be loaded
*/
synchronized public void loadToBaseLevelSynch(int level) throws IOException {
int cur = currentBaseLevel;
setBaseLevel(level);
if(currentBaseLevel > cur) {
for(int j=0; j
*
* Texture base level and max level and the actual texture
* images should not be touched.
*/
public GL.Texture getTexture() {
return tex;
}
/** Get the amount of memory currently used.
* @return Memory, in bytes
*/
public int getMemory() {
return getMemory(currentBaseLevel);
}
/** Get the amount of memory used if given level is loaded.
* @return Memory, in bytes
*/
public int getMemory(int level) {
if(level < 0) level = 0;
if(level > levels.length-1) level = levels.length-1;
int bytesForLevel = levels[level].size.width * levels[level].size.height * bitsPerTexel / 8;
int totalBytes = (bytesForLevel * 4) / 3;
return totalBytes;
}
/** Get the level that uses at most given amount of memory.
* @return The level index.
*/
public int getLevelForBytes(int memory) {
for(int i = 0; i