PageRenderTime 55ms CodeModel.GetById 0ms RepoModel.GetById 0ms app.codeStats 0ms

/newcode/src/com/prupe/mcpatcher/hd/MipmapHelper.java

https://bitbucket.org/SevenBits/mcpatcher
Java | 467 lines | 418 code | 49 blank | 0 comment | 95 complexity | ed25dda5aa3177ddb7a0e033864962d9 MD5 | raw file
  1. package com.prupe.mcpatcher.hd;
  2. import com.prupe.mcpatcher.Config;
  3. import com.prupe.mcpatcher.MCLogger;
  4. import com.prupe.mcpatcher.MCPatcherUtils;
  5. import com.prupe.mcpatcher.TexturePackAPI;
  6. import net.minecraft.src.ResourceLocation;
  7. import net.minecraft.src.TextureAtlasSprite;
  8. import org.lwjgl.opengl.*;
  9. import org.lwjgl.util.glu.GLU;
  10. import java.awt.*;
  11. import java.awt.image.BufferedImage;
  12. import java.awt.image.DataBuffer;
  13. import java.awt.image.DataBufferByte;
  14. import java.awt.image.DataBufferInt;
  15. import java.lang.ref.Reference;
  16. import java.lang.ref.SoftReference;
  17. import java.math.BigInteger;
  18. import java.nio.ByteBuffer;
  19. import java.nio.ByteOrder;
  20. import java.nio.IntBuffer;
  21. import java.util.*;
  22. public class MipmapHelper {
  23. private static final MCLogger logger = MCLogger.getLogger(MCPatcherUtils.MIPMAP);
  24. private static final ResourceLocation MIPMAP_PROPERTIES = TexturePackAPI.newMCPatcherResourceLocation("mipmap.properties");
  25. static final int TEX_FORMAT = GL12.GL_BGRA;
  26. static final int TEX_DATA_TYPE = GL12.GL_UNSIGNED_INT_8_8_8_8_REV;
  27. private static final int MIN_ALPHA = 0x1a;
  28. private static final int MAX_ALPHA = 0xe5;
  29. private static final boolean mipmapSupported;
  30. static final boolean mipmapEnabled = Config.getBoolean(MCPatcherUtils.EXTENDED_HD, "mipmap", false);
  31. static final int maxMipmapLevel = Config.getInt(MCPatcherUtils.EXTENDED_HD, "maxMipmapLevel", 3);
  32. private static final boolean useMipmap;
  33. private static final int mipmapAlignment = (1 << Config.getInt(MCPatcherUtils.EXTENDED_HD, "mipmapAlignment", 3)) - 1;
  34. private static final boolean anisoSupported;
  35. static final int anisoLevel;
  36. private static final int anisoMax;
  37. private static final boolean lodSupported;
  38. private static final int lodBias;
  39. private static final Map<String, Reference<BufferedImage>> imagePool = new HashMap<String, Reference<BufferedImage>>();
  40. private static final Map<Integer, Reference<ByteBuffer>> bufferPool = new HashMap<Integer, Reference<ByteBuffer>>();
  41. private static final Map<String, Boolean> mipmapType = new HashMap<String, Boolean>();
  42. static {
  43. mipmapSupported = GLContext.getCapabilities().OpenGL12;
  44. useMipmap = mipmapSupported && mipmapEnabled && maxMipmapLevel > 0;
  45. anisoSupported = GLContext.getCapabilities().GL_EXT_texture_filter_anisotropic;
  46. if (anisoSupported) {
  47. anisoMax = (int) GL11.glGetFloat(EXTTextureFilterAnisotropic.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT);
  48. checkGLError("glGetFloat(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT)");
  49. anisoLevel = Math.max(Math.min(Config.getInt(MCPatcherUtils.EXTENDED_HD, "anisotropicFiltering", 1), anisoMax), 1);
  50. } else {
  51. anisoMax = anisoLevel = 1;
  52. }
  53. lodSupported = GLContext.getCapabilities().GL_EXT_texture_lod_bias;
  54. if (lodSupported) {
  55. lodBias = Config.getInt(MCPatcherUtils.EXTENDED_HD, "lodBias", 0);
  56. } else {
  57. lodBias = 0;
  58. }
  59. logger.config("mipmap: supported=%s, enabled=%s, level=%d", mipmapSupported, mipmapEnabled, maxMipmapLevel);
  60. logger.config("anisotropic: supported=%s, level=%d, max=%d", anisoSupported, anisoLevel, anisoMax);
  61. logger.config("lod bias: supported=%s, bias=%d", lodSupported, lodBias);
  62. }
  63. private static void setupTexture(int width, int height, boolean blur, boolean clamp, String textureName) {
  64. int mipmaps = useMipmapsForTexture(textureName) ? getMipmapLevels(width, height, 1) : 0;
  65. logger.finer("setupTexture(%s) %dx%d %d mipmaps", textureName, width, height, mipmaps);
  66. int magFilter = blur ? GL11.GL_LINEAR : GL11.GL_NEAREST;
  67. int minFilter = mipmaps > 0 ? GL11.GL_NEAREST_MIPMAP_LINEAR : magFilter;
  68. int wrap = clamp ? GL11.GL_CLAMP : GL11.GL_REPEAT;
  69. if (mipmaps > 0) {
  70. GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, mipmaps);
  71. checkGLError("%s: set GL_TEXTURE_MAX_LEVEL = %d", textureName, mipmaps);
  72. if (anisoSupported && anisoLevel > 1) {
  73. GL11.glTexParameterf(GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, anisoLevel);
  74. checkGLError("%s: set GL_TEXTURE_MAX_ANISOTROPY_EXT = %f", textureName, anisoLevel);
  75. }
  76. if (lodSupported) {
  77. GL11.glTexEnvi(EXTTextureLODBias.GL_TEXTURE_FILTER_CONTROL_EXT, EXTTextureLODBias.GL_TEXTURE_LOD_BIAS_EXT, lodBias);
  78. checkGLError("%s: set GL_TEXTURE_LOD_BIAS_EXT = %d", textureName, lodBias);
  79. }
  80. }
  81. GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, minFilter);
  82. GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, magFilter);
  83. GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, wrap);
  84. GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, wrap);
  85. for (int level = 0; level <= mipmaps; level++) {
  86. GL11.glTexImage2D(GL11.GL_TEXTURE_2D, level, GL11.GL_RGBA, width, height, 0, TEX_FORMAT, TEX_DATA_TYPE, (IntBuffer) null);
  87. checkGLError("%s: glTexImage2D %dx%d level %d", textureName, width, height, level);
  88. width >>= 1;
  89. height >>= 1;
  90. }
  91. }
  92. public static void setupTexture(int[] rgb, int width, int height, int x, int y, boolean blur, boolean clamp, String textureName) {
  93. setupTexture(width, height, blur, clamp, textureName);
  94. copySubTexture(rgb, width, height, x, y, textureName);
  95. }
  96. public static int setupTexture(int glTexture, BufferedImage image, boolean blur, boolean clamp, ResourceLocation textureName) {
  97. int width = image.getWidth();
  98. int height = image.getHeight();
  99. GL11.glBindTexture(GL11.GL_TEXTURE_2D, glTexture);
  100. logger.finer("setupTexture(%s, %d, %dx%d, %s, %s)", textureName, glTexture, width, height, blur, clamp);
  101. int[] rgb = new int[width * height];
  102. image.getRGB(0, 0, width, height, rgb, 0, width);
  103. setupTexture(rgb, width, height, 0, 0, blur, clamp, textureName.getPath());
  104. return glTexture;
  105. }
  106. public static void setupTexture(int glTexture, int width, int height, String textureName) {
  107. GL11.glBindTexture(GL11.GL_TEXTURE_2D, glTexture);
  108. logger.finer("setupTexture(tilesheet %s, %d, %dx%d)", textureName, glTexture, width, height);
  109. setupTexture(width, height, false, false, textureName);
  110. }
  111. public static void copySubTexture(int[] rgb, int width, int height, int x, int y, String textureName) {
  112. if (rgb == null) {
  113. logger.error("copySubTexture %s %d,%d %dx%d: rgb data is null", textureName, x, y, width, height);
  114. return;
  115. }
  116. IntBuffer buffer = getPooledBuffer(width * height * 4).asIntBuffer();
  117. buffer.put(rgb).position(0);
  118. int mipmaps = getMipmapLevelsForCurrentTexture();
  119. IntBuffer newBuffer;
  120. logger.finest("copySubTexture %s %d,%d %dx%d %d mipmaps", textureName, x, y, width, height, mipmaps);
  121. for (int level = 0; ; ) {
  122. if (width <= 0 || height <= 0) {
  123. break;
  124. }
  125. GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, level, x, y, width, height, TEX_FORMAT, TEX_DATA_TYPE, buffer);
  126. checkGLError("%s: glTexSubImage2D(%d, %d, %d, %d, %d)", textureName, level, x, y, width, height);
  127. if (level >= mipmaps) {
  128. break;
  129. }
  130. newBuffer = getPooledBuffer(width * height).asIntBuffer();
  131. scaleHalf(buffer, width, height, newBuffer, 0);
  132. buffer = newBuffer;
  133. level++;
  134. x >>= 1;
  135. y >>= 1;
  136. width >>= 1;
  137. height >>= 1;
  138. }
  139. }
  140. public static void copySubTexture(TextureAtlasSprite texture, int index) {
  141. int width = texture.getWidth();
  142. int height = texture.getHeight();
  143. if (texture.mipmaps == null || texture.mipmaps.size() != texture.animationFrames.size()) {
  144. texture.mipmaps = new ArrayList<IntBuffer[]>(texture.animationFrames.size());
  145. int mipmaps = getMipmapLevelsForCurrentTexture();
  146. if (mipmaps > 0) {
  147. logger.fine("generating %d mipmaps for tile %s", mipmaps, texture.getIconName());
  148. }
  149. for (int i = 0; i < texture.animationFrames.size(); i++) {
  150. texture.mipmaps.add(generateMipmaps(texture.animationFrames.get(i), width, height, mipmaps));
  151. }
  152. }
  153. int x = texture.getX0();
  154. int y = texture.getY0();
  155. IntBuffer[] mipmapData = texture.mipmaps.get(index);
  156. if (mipmapData == null) {
  157. return;
  158. }
  159. for (int level = 0; level < mipmapData.length; level++) {
  160. GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, level, x, y, width, height, TEX_FORMAT, TEX_DATA_TYPE, mipmapData[level]);
  161. x >>= 1;
  162. y >>= 1;
  163. width >>= 1;
  164. height >>= 1;
  165. }
  166. }
  167. private static IntBuffer[] generateMipmaps(int[] rgb, int width, int height, int mipmaps) {
  168. if (rgb == null) {
  169. return null;
  170. }
  171. ArrayList<IntBuffer> mipmapData = new ArrayList<IntBuffer>();
  172. IntBuffer buffer = newIntBuffer(width * height * 4);
  173. buffer.put(rgb).position(0);
  174. IntBuffer newBuffer;
  175. for (int level = 0; ; ) {
  176. mipmapData.add(buffer);
  177. if (width <= 0 || height <= 0 || level >= mipmaps) {
  178. break;
  179. }
  180. newBuffer = newIntBuffer(width * height);
  181. scaleHalf(buffer, width, height, newBuffer, 0);
  182. buffer = newBuffer;
  183. level++;
  184. width >>= 1;
  185. height >>= 1;
  186. }
  187. return mipmapData.toArray(new IntBuffer[mipmapData.size()]);
  188. }
  189. static BufferedImage fixTransparency(ResourceLocation name, BufferedImage image) {
  190. if (image == null) {
  191. return image;
  192. }
  193. long s1 = System.currentTimeMillis();
  194. image = convertToARGB(image);
  195. int width = image.getWidth();
  196. int height = image.getHeight();
  197. IntBuffer buffer = getImageAsARGBIntBuffer(image);
  198. IntBuffer scaledBuffer = buffer;
  199. outer:
  200. while (width % 2 == 0 && height % 2 == 0) {
  201. for (int i = 0; i < scaledBuffer.limit(); i++) {
  202. if (scaledBuffer.get(i) >>> 24 == 0) {
  203. IntBuffer newBuffer = getPooledBuffer(width * height).asIntBuffer();
  204. scaleHalf(scaledBuffer, width, height, newBuffer, 8);
  205. scaledBuffer = newBuffer;
  206. width >>= 1;
  207. height >>= 1;
  208. continue outer;
  209. }
  210. }
  211. break;
  212. }
  213. long s2 = System.currentTimeMillis();
  214. if (scaledBuffer != buffer) {
  215. setBackgroundColor(buffer, image.getWidth(), image.getHeight(), scaledBuffer, image.getWidth() / width);
  216. }
  217. long s3 = System.currentTimeMillis();
  218. logger.finer("bg fix (tile %s): scaling %dms, setbg %dms", name, s2 - s1, s3 - s2);
  219. return image;
  220. }
  221. static void reset() {
  222. mipmapType.clear();
  223. mipmapType.put("terrain", true);
  224. mipmapType.put("items", false);
  225. Properties properties = TexturePackAPI.getProperties(MIPMAP_PROPERTIES);
  226. if (properties != null) {
  227. for (Map.Entry entry : properties.entrySet()) {
  228. if (entry.getKey() instanceof String && entry.getValue() instanceof String) {
  229. String key = ((String) entry.getKey()).trim();
  230. boolean value = Boolean.parseBoolean(((String) entry.getValue()).trim().toLowerCase());
  231. if (key.endsWith(".png")) {
  232. mipmapType.put(key, value);
  233. }
  234. }
  235. }
  236. }
  237. }
  238. static boolean useMipmapsForTexture(String texture) {
  239. if (!useMipmap || texture == null) {
  240. return false;
  241. } else if (mipmapType.containsKey(texture)) {
  242. return mipmapType.get(texture);
  243. } else if (texture.contains("item") ||
  244. texture.startsWith("textures/colormap/") ||
  245. texture.startsWith("textures/environment/") ||
  246. texture.startsWith("textures/font/") ||
  247. texture.startsWith("textures/gui/") ||
  248. texture.startsWith("textures/map/") ||
  249. texture.startsWith("textures/misc/") ||
  250. texture.startsWith(TexturePackAPI.MCPATCHER_SUBDIR + "colormap/") ||
  251. texture.startsWith(TexturePackAPI.MCPATCHER_SUBDIR + "cit/") ||
  252. texture.startsWith(TexturePackAPI.MCPATCHER_SUBDIR + "dial/") ||
  253. texture.startsWith(TexturePackAPI.MCPATCHER_SUBDIR + "font/") ||
  254. texture.startsWith(TexturePackAPI.MCPATCHER_SUBDIR + "lightmap/") ||
  255. texture.startsWith(TexturePackAPI.MCPATCHER_SUBDIR + "sky/")) {
  256. return false;
  257. } else {
  258. return true;
  259. }
  260. }
  261. static int getMipmapLevelsForCurrentTexture() {
  262. int filter = GL11.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER);
  263. if (filter != GL11.GL_NEAREST_MIPMAP_LINEAR && filter != GL11.GL_NEAREST_MIPMAP_NEAREST) {
  264. return 0;
  265. }
  266. return Math.min(maxMipmapLevel, GL11.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL));
  267. }
  268. private static int gcd(int a, int b) {
  269. return BigInteger.valueOf(a).gcd(BigInteger.valueOf(b)).intValue();
  270. }
  271. private static int getMipmapLevels(int width, int height, int minSize) {
  272. int size = gcd(width, height);
  273. int mipmap;
  274. for (mipmap = 0; size >= minSize && ((size & 1) == 0) && mipmap < maxMipmapLevel; size >>= 1, mipmap++) {
  275. }
  276. return mipmap;
  277. }
  278. private static BufferedImage getPooledImage(int width, int height, int index) {
  279. String key = String.format("%dx%d#%d", width, height, index);
  280. Reference<BufferedImage> ref = imagePool.get(key);
  281. BufferedImage image = (ref == null ? null : ref.get());
  282. if (image == null) {
  283. image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
  284. imagePool.put(key, new SoftReference<BufferedImage>(image));
  285. }
  286. return image;
  287. }
  288. private static IntBuffer newIntBuffer(int size) {
  289. ByteBuffer buffer = ByteBuffer.allocateDirect(size);
  290. buffer.order(ByteOrder.LITTLE_ENDIAN);
  291. return buffer.asIntBuffer();
  292. }
  293. private static ByteBuffer getPooledBuffer(int size) {
  294. Reference<ByteBuffer> ref = bufferPool.get(size);
  295. ByteBuffer buffer = (ref == null ? null : ref.get());
  296. if (buffer == null) {
  297. buffer = ByteBuffer.allocateDirect(size);
  298. bufferPool.put(size, new SoftReference<ByteBuffer>(buffer));
  299. }
  300. buffer.order(ByteOrder.LITTLE_ENDIAN);
  301. buffer.position(0);
  302. return buffer;
  303. }
  304. private static BufferedImage convertToARGB(BufferedImage image) {
  305. if (image == null) {
  306. return null;
  307. } else if (image.getType() == BufferedImage.TYPE_INT_ARGB) {
  308. return image;
  309. } else {
  310. int width = image.getWidth();
  311. int height = image.getHeight();
  312. logger.finest("converting %dx%d image to ARGB", width, height);
  313. BufferedImage newImage = getPooledImage(width, height, 0);
  314. Graphics2D graphics = newImage.createGraphics();
  315. Arrays.fill(getImageAsARGBIntBuffer(newImage).array(), 0);
  316. graphics.drawImage(image, 0, 0, null);
  317. return newImage;
  318. }
  319. }
  320. private static IntBuffer getImageAsARGBIntBuffer(BufferedImage image) {
  321. DataBuffer buffer = image.getRaster().getDataBuffer();
  322. if (buffer instanceof DataBufferInt) {
  323. return IntBuffer.wrap(((DataBufferInt) buffer).getData());
  324. } else if (buffer instanceof DataBufferByte) {
  325. return ByteBuffer.wrap(((DataBufferByte) buffer).getData()).order(ByteOrder.BIG_ENDIAN).asIntBuffer();
  326. } else {
  327. int width = image.getWidth();
  328. int height = image.getHeight();
  329. int[] pixels = new int[width * height];
  330. image.getRGB(0, 0, width, height, pixels, 0, width);
  331. return IntBuffer.wrap(pixels);
  332. }
  333. }
  334. private static void setBackgroundColor(IntBuffer buffer, int width, int height, IntBuffer scaledBuffer, int scale) {
  335. for (int i = 0; i < width; i++) {
  336. for (int j = 0; j < height; j++) {
  337. int k = width * j + i;
  338. int pixel = buffer.get(k);
  339. if ((pixel & 0xff000000) == 0) {
  340. pixel = scaledBuffer.get((j / scale) * (width / scale) + i / scale);
  341. buffer.put(k, pixel & 0x00ffffff);
  342. }
  343. }
  344. }
  345. }
  346. static void scaleHalf(IntBuffer in, int w, int h, IntBuffer out, int rotate) {
  347. for (int i = 0; i < w / 2; i++) {
  348. for (int j = 0; j < h / 2; j++) {
  349. int k = w * 2 * j + 2 * i;
  350. int pixel00 = in.get(k);
  351. int pixel01 = in.get(k + 1);
  352. int pixel10 = in.get(k + w);
  353. int pixel11 = in.get(k + w + 1);
  354. if (rotate != 0) {
  355. pixel00 = Integer.rotateLeft(pixel00, rotate);
  356. pixel01 = Integer.rotateLeft(pixel01, rotate);
  357. pixel10 = Integer.rotateLeft(pixel10, rotate);
  358. pixel11 = Integer.rotateLeft(pixel11, rotate);
  359. }
  360. int pixel = average4RGBA(pixel00, pixel01, pixel10, pixel11);
  361. if (rotate != 0) {
  362. pixel = Integer.rotateRight(pixel, rotate);
  363. }
  364. out.put(w / 2 * j + i, pixel);
  365. }
  366. }
  367. }
  368. private static int average4RGBA(int pixel00, int pixel01, int pixel10, int pixel11) {
  369. int a00 = pixel00 & 0xff;
  370. int a01 = pixel01 & 0xff;
  371. int a10 = pixel10 & 0xff;
  372. int a11 = pixel11 & 0xff;
  373. switch ((a00 << 24) | (a01 << 16) | (a10 << 8) | a11) {
  374. case 0xff000000:
  375. return pixel00;
  376. case 0x00ff0000:
  377. return pixel01;
  378. case 0x0000ff00:
  379. return pixel10;
  380. case 0x000000ff:
  381. return pixel11;
  382. case 0xffff0000:
  383. return average2RGBA(pixel00, pixel01);
  384. case 0xff00ff00:
  385. return average2RGBA(pixel00, pixel10);
  386. case 0xff0000ff:
  387. return average2RGBA(pixel00, pixel11);
  388. case 0x00ffff00:
  389. return average2RGBA(pixel01, pixel10);
  390. case 0x00ff00ff:
  391. return average2RGBA(pixel01, pixel11);
  392. case 0x0000ffff:
  393. return average2RGBA(pixel10, pixel11);
  394. case 0x00000000:
  395. case 0xffffffff:
  396. return average2RGBA(average2RGBA(pixel00, pixel11), average2RGBA(pixel01, pixel10));
  397. default:
  398. int a = a00 + a01 + a10 + a11;
  399. int pixel = a >> 2;
  400. for (int i = 8; i < 32; i += 8) {
  401. int average = (a00 * ((pixel00 >> i) & 0xff) + a01 * ((pixel01 >> i) & 0xff) +
  402. a10 * ((pixel10 >> i) & 0xff) + a11 * ((pixel11 >> i) & 0xff)) / a;
  403. pixel |= (average << i);
  404. }
  405. return pixel;
  406. }
  407. }
  408. private static int average2RGBA(int a, int b) {
  409. return (((a & 0xfefefefe) >>> 1) + ((b & 0xfefefefe) >>> 1)) | (a & b & 0x01010101);
  410. }
  411. private static void checkGLError(String format, Object... params) {
  412. int error = GL11.glGetError();
  413. if (error != 0) {
  414. String message = GLU.gluErrorString(error) + ": " + String.format(format, params);
  415. new RuntimeException(message).printStackTrace();
  416. }
  417. }
  418. }