From 6d2adb0f31fa96c729335f0ecd2c6c5f4d0992a9 Mon Sep 17 00:00:00 2001 From: OLEGSHA Date: Sat, 15 Aug 2020 22:15:26 +0400 Subject: [PATCH] Refactored texture loading system to allow runtime editing and atlases - Removed TextureManager, use TextureLoader instead - Removed PngLoader, AWT image compat moved to TextureUtils - Pixels renamed to TextureData - Added TextureDataEditor for runtime editing of TextureData - Added SimpleTextures to load random, one-off textures - Added Atlases to load textures into runtime-compiled atlases - BlockRenders now contains a method to load a block texture properly - Moved block textures into assets/textures/blocks/ --- .../progressia/client/ClientProxy.java | 5 + .../client/graphics/flat/LayerTestUI.java | 22 +- .../client/graphics/texture/Atlases.java | 211 +++++++++++++++++ .../client/graphics/texture/PngLoader.java | 126 ---------- .../graphics/texture/SimpleTextures.java | 44 ++++ .../texture/{Pixels.java => TextureData.java} | 4 +- .../graphics/texture/TextureDataEditor.java | 185 +++++++++++++++ .../graphics/texture/TextureLoader.java | 49 ++++ .../graphics/texture/TextureManager.java | 218 ------------------ .../graphics/texture/TexturePrimitive.java | 21 +- .../client/graphics/texture/TextureUtils.java | 108 +++++++++ .../client/world/renders/BlockRenders.java | 44 ++-- .../progressia/common/resource/Resource.java | 13 +- .../common/resource/ResourceManager.java | 6 + .../assets/textures/{ => blocks}/compass.png | Bin .../assets/textures/{ => blocks}/glass.png | Bin .../textures/{ => blocks}/glass_clear.png | Bin .../textures/{ => blocks}/grass_bottom.png | Bin .../textures/{ => blocks}/grass_side.png | Bin .../textures/{ => blocks}/grass_top.png | Bin .../textures/{ => blocks}/side_east.png | Bin .../textures/{ => blocks}/side_north.png | Bin .../textures/{ => blocks}/side_south.png | Bin .../textures/{ => blocks}/side_west.png | Bin .../assets/textures/{ => blocks}/stone.png | Bin 25 files changed, 660 insertions(+), 396 deletions(-) create mode 100644 src/main/java/ru/windcorp/progressia/client/graphics/texture/Atlases.java delete mode 100644 src/main/java/ru/windcorp/progressia/client/graphics/texture/PngLoader.java create mode 100644 src/main/java/ru/windcorp/progressia/client/graphics/texture/SimpleTextures.java rename src/main/java/ru/windcorp/progressia/client/graphics/texture/{Pixels.java => TextureData.java} (95%) create mode 100644 src/main/java/ru/windcorp/progressia/client/graphics/texture/TextureDataEditor.java create mode 100644 src/main/java/ru/windcorp/progressia/client/graphics/texture/TextureLoader.java delete mode 100644 src/main/java/ru/windcorp/progressia/client/graphics/texture/TextureManager.java create mode 100644 src/main/java/ru/windcorp/progressia/client/graphics/texture/TextureUtils.java rename src/main/resources/assets/textures/{ => blocks}/compass.png (100%) rename src/main/resources/assets/textures/{ => blocks}/glass.png (100%) rename src/main/resources/assets/textures/{ => blocks}/glass_clear.png (100%) rename src/main/resources/assets/textures/{ => blocks}/grass_bottom.png (100%) rename src/main/resources/assets/textures/{ => blocks}/grass_side.png (100%) rename src/main/resources/assets/textures/{ => blocks}/grass_top.png (100%) rename src/main/resources/assets/textures/{ => blocks}/side_east.png (100%) rename src/main/resources/assets/textures/{ => blocks}/side_north.png (100%) rename src/main/resources/assets/textures/{ => blocks}/side_south.png (100%) rename src/main/resources/assets/textures/{ => blocks}/side_west.png (100%) rename src/main/resources/assets/textures/{ => blocks}/stone.png (100%) diff --git a/src/main/java/ru/windcorp/progressia/client/ClientProxy.java b/src/main/java/ru/windcorp/progressia/client/ClientProxy.java index d03ec3d..1bd692a 100644 --- a/src/main/java/ru/windcorp/progressia/client/ClientProxy.java +++ b/src/main/java/ru/windcorp/progressia/client/ClientProxy.java @@ -24,8 +24,10 @@ import ru.windcorp.progressia.client.graphics.backend.RenderTaskQueue; import ru.windcorp.progressia.client.graphics.flat.FlatRenderProgram; import ru.windcorp.progressia.client.graphics.flat.LayerTestUI; import ru.windcorp.progressia.client.graphics.gui.LayerTestGUI; +import ru.windcorp.progressia.client.graphics.texture.Atlases; import ru.windcorp.progressia.client.graphics.world.LayerWorld; import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram; +import ru.windcorp.progressia.client.world.renders.BlockRenders; public class ClientProxy implements Proxy { @@ -40,6 +42,9 @@ public class ClientProxy implements Proxy { e.printStackTrace(); } + BlockRenders.registerTest(); + Atlases.loadAllAtlases(); + GUI.addBottomLayer(new LayerWorld()); GUI.addTopLayer(new LayerTestUI()); GUI.addTopLayer(new LayerTestGUI()); diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/flat/LayerTestUI.java b/src/main/java/ru/windcorp/progressia/client/graphics/flat/LayerTestUI.java index 981796f..4586f12 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/flat/LayerTestUI.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/flat/LayerTestUI.java @@ -27,11 +27,8 @@ import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface; import ru.windcorp.progressia.client.graphics.input.KeyEvent; import ru.windcorp.progressia.client.graphics.input.bus.Input; import ru.windcorp.progressia.client.graphics.model.LambdaModel; -import ru.windcorp.progressia.client.graphics.texture.SimpleTexture; -import ru.windcorp.progressia.client.graphics.texture.Sprite; +import ru.windcorp.progressia.client.graphics.texture.SimpleTextures; import ru.windcorp.progressia.client.graphics.texture.Texture; -import ru.windcorp.progressia.client.graphics.texture.TextureManager; -import ru.windcorp.progressia.client.graphics.texture.TextureSettings; import ru.windcorp.progressia.client.graphics.world.LayerWorld; public class LayerTestUI extends AssembledFlatLayer { @@ -67,8 +64,8 @@ public class LayerTestUI extends AssembledFlatLayer { target.pushTransform(new Mat4().identity().translate(x + 2*BORDER, y + 2*BORDER, 0)); - final Texture compassBg = qtex("compass_icon"); - final Texture compassFg = qtex("compass_icon_arrow"); + final Texture compassBg = SimpleTextures.get("compass_icon"); + final Texture compassFg = SimpleTextures.get("compass_icon_arrow"); target.drawTexture(texShadow, texShadow, texSize, texSize, Colors.BLACK, compassBg); target.drawTexture(0, 0, texSize, texSize, compassBg); @@ -142,18 +139,5 @@ public class LayerTestUI extends AssembledFlatLayer { flag = event.isPress(); invalidate(); } - - /* - * TMP texture loader - */ - - private static final TextureSettings TEXTURE_SETTINGS = - new TextureSettings(false); - - private static Texture qtex(String name) { - return new SimpleTexture(new Sprite( - TextureManager.load(name, TEXTURE_SETTINGS) - )); - } } diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/texture/Atlases.java b/src/main/java/ru/windcorp/progressia/client/graphics/texture/Atlases.java new file mode 100644 index 0000000..3ea7828 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/graphics/texture/Atlases.java @@ -0,0 +1,211 @@ +package ru.windcorp.progressia.client.graphics.texture; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.Multimap; +import com.google.common.collect.MultimapBuilder; + +import glm.vec._2.Vec2; +import ru.windcorp.progressia.client.graphics.backend.RenderTaskQueue; +import ru.windcorp.progressia.common.resource.Resource; +import ru.windcorp.progressia.common.util.BinUtil; +import ru.windcorp.progressia.common.util.Named; + +public class Atlases { + + public static class AtlasGroup extends Named { + private final int atlasSize; + + public AtlasGroup(String name, int atlasSize) { + super(name); + this.atlasSize = atlasSize; + + if (!BinUtil.isPowerOf2(atlasSize)) { + throw new IllegalArgumentException( + "Atlas size " + atlasSize + + " is not a power of 2" + ); + } + } + + public int getAtlasSize() { + return atlasSize; + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + } + + public static class Atlas { + private final AtlasGroup group; + + private final TextureDataEditor editor; + private int nextX, nextY; + private int rowHeight; + + private final TexturePrimitive primitive; + + public Atlas(AtlasGroup group) { + this.group = group; + int size = group.getAtlasSize(); + + this.editor = new TextureDataEditor(size, size, size, size, SETTINGS); + this.primitive = new TexturePrimitive(editor.createSnapshot()); + } + + public Sprite addSprite(TextureDataEditor data) { + int width = data.getContentWidth(); + int height = data.getContentHeight(); + + selectPosition(width, height); + + editor.draw(data, nextX, nextY); + + Sprite result = new Sprite( + getPrimitive(), + toPrimitiveCoords(nextX, nextY), + toPrimitiveCoords(width, height) + ); + + nextX += width; + + return result; + } + + private void selectPosition(int width, int height) { + if (nextX + width > getSize()) { + // Wrapping + nextY += rowHeight; // Move to next row + rowHeight = height; // Next row is at least 'height' high + nextX = 0; // Start the row over + } else { + // Not wrapping + + // Update rowHeight if necessary + if (rowHeight < height) { + rowHeight = height; + } + } + } + + private Vec2 toPrimitiveCoords(int x, int y) { + return new Vec2( + toPrimitiveCoord(x), + toPrimitiveCoord(y) + ); + } + + private float toPrimitiveCoord(int c) { + return c / (float) getSize(); + } + + public boolean canAddSprite(TextureDataEditor data) { + int width = data.getContentWidth(); + int height = data.getContentHeight(); + + // Try to fit without wrapping + + if (nextY + height > getSize()) + // Does not fit vertically + return false; + + if (nextX + width <= getSize()) + // Can place at (nextX; nextY) + return true; + + // Try wrapping + + if (width > getSize()) + // GTFO. We couldn't fit if if we tried + return false; + + if (nextY + rowHeight + height > getSize()) + // Does not fit vertically when wrapped + return false; + + // Can place at (0; nextY + rowHeight) + return true; + } + + public void flush() { + editor.createSnapshot(primitive.getData()); + editor.close(); + } + + public AtlasGroup getGroup() { + return group; + } + + public TexturePrimitive getPrimitive() { + return primitive; + } + + public int getSize() { + return editor.getBufferWidth(); + } + } + + private static final TextureSettings SETTINGS = new TextureSettings(false); + + private static final Map LOADED = + new HashMap<>(); + private static final Multimap ATLASES = + MultimapBuilder.hashKeys().arrayListValues().build(); + + public static Sprite getSprite(Resource resource, AtlasGroup group) { + return LOADED.computeIfAbsent(resource, k -> loadSprite(k, group)); + } + + private static Sprite loadSprite(Resource resource, AtlasGroup group) { + try ( + TextureDataEditor data = + TextureLoader.loadPixels(resource, SETTINGS) + ) { + + Atlas atlas = getReadyAtlas(group, data); + return atlas.addSprite(data); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static Atlas getReadyAtlas(AtlasGroup group, TextureDataEditor data) { + List atlases = (List) ATLASES.get(group); + + if ( + atlases.isEmpty() || + !(atlases.get(atlases.size() - 1).canAddSprite(data)) + ) { + Atlas newAtlas = new Atlas(group); + + if (!newAtlas.canAddSprite(data)) { + throw new RuntimeException("Could not fit tex into atlas of size " + newAtlas.getSize()); + } + + atlases.add(newAtlas); + } + + return atlases.get(atlases.size() - 1); + } + + public static void loadAllAtlases() { + ATLASES.forEach((group, atlas) -> { + atlas.flush(); + RenderTaskQueue.invokeLater(atlas.getPrimitive()::load); + }); + } + + private Atlases() {} + +} diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/texture/PngLoader.java b/src/main/java/ru/windcorp/progressia/client/graphics/texture/PngLoader.java deleted file mode 100644 index 94da1da..0000000 --- a/src/main/java/ru/windcorp/progressia/client/graphics/texture/PngLoader.java +++ /dev/null @@ -1,126 +0,0 @@ -/******************************************************************************* - * Progressia - * Copyright (C) 2020 Wind Corporation - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - *******************************************************************************/ -package ru.windcorp.progressia.client.graphics.texture; - -import java.awt.Graphics2D; -import java.awt.color.ColorSpace; -import java.awt.image.BufferedImage; -import java.awt.image.ColorModel; -import java.awt.image.ComponentColorModel; -import java.awt.image.DataBuffer; -import java.awt.image.DataBufferByte; -import java.awt.image.Raster; -import java.awt.image.WritableRaster; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.util.Hashtable; -import javax.imageio.ImageIO; -import org.lwjgl.BufferUtils; - -import ru.windcorp.progressia.common.util.BinUtil; - -class PngLoader { - - private static final ColorModel COLOR_MODEL = new ComponentColorModel( - ColorSpace.getInstance(ColorSpace.CS_sRGB), // Use RGB - new int[] {8, 8, 8, 8}, // Use every bit - true, // Has alpha - false, // Not premultiplied - ComponentColorModel.TRANSLUCENT, // Can have any alpha - DataBuffer.TYPE_BYTE // Alpha is one byte - ); - - private static final Hashtable CANVAS_PROPERTIES = new Hashtable<>(); - - private static final java.awt.Color CANVAS_BACKGROUND = new java.awt.Color(0, 0, 0, 0); - - public static Pixels loadPngImage( - InputStream pngStream, - TextureSettings settings - ) throws IOException { - - BufferedImage readResult = ImageIO.read(pngStream); - - int width = readResult.getWidth(); - int height = readResult.getHeight(); - - int bufferWidth = BinUtil.roundToGreaterPowerOf2(width); - int bufferHeight = BinUtil.roundToGreaterPowerOf2(height); - - WritableRaster raster = createRaster(bufferWidth, bufferHeight); - - BufferedImage canvas = createCanvas(raster); - - Graphics2D g = canvas.createGraphics(); - try { - g.setColor(CANVAS_BACKGROUND); - g.fillRect(0, 0, width, height); - g.drawImage( - readResult, - 0, 0, width, height, - 0, height, width, 0, // Flip the image - null - ); - } finally { - g.dispose(); - } - - return new Pixels( - extractBytes(raster), - bufferWidth, bufferHeight, - width, height, - settings - ); - } - - private static WritableRaster createRaster( - int bufferWidth, - int bufferHeight - ) { - return Raster.createInterleavedRaster( - DataBuffer.TYPE_BYTE, // Storage model - bufferWidth, // Buffer width - bufferHeight, // Buffer height - 4, // RGBA - null // Location (here (0; 0)) - ); - } - - private static BufferedImage createCanvas(WritableRaster raster) { - return new BufferedImage( - COLOR_MODEL, // Color model - raster, // Backing raster - false, // Raster is not premultipied - CANVAS_PROPERTIES // Properties - ); - } - - private static ByteBuffer extractBytes(WritableRaster raster) { - byte[] data = ( - (DataBufferByte) raster.getDataBuffer() - ).getData(); - - ByteBuffer buffer = BufferUtils.createByteBuffer(data.length); - buffer.put(data); - buffer.flip(); - - return buffer; - } - -} diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/texture/SimpleTextures.java b/src/main/java/ru/windcorp/progressia/client/graphics/texture/SimpleTextures.java new file mode 100644 index 0000000..a2f68e3 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/graphics/texture/SimpleTextures.java @@ -0,0 +1,44 @@ +package ru.windcorp.progressia.client.graphics.texture; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import ru.windcorp.progressia.common.resource.Resource; +import ru.windcorp.progressia.common.resource.ResourceManager; + +public class SimpleTextures { + + private static final TextureSettings SETTINGS = new TextureSettings(false); + + private static final Map TEXTURES = new HashMap<>(); + + public static Texture get(Resource resource) { + return TEXTURES.computeIfAbsent(resource, SimpleTextures::load); + } + + public static Texture get(String textureName) { + return get(ResourceManager.getTextureResource(textureName)); + } + + private static Texture load(Resource resource) { + try ( + TextureDataEditor data = + TextureLoader.loadPixels(resource, SETTINGS) + ) { + return new SimpleTexture( + new Sprite( + new TexturePrimitive( + data.toStatic() + ) + ) + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + private SimpleTextures() {} + +} diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/texture/Pixels.java b/src/main/java/ru/windcorp/progressia/client/graphics/texture/TextureData.java similarity index 95% rename from src/main/java/ru/windcorp/progressia/client/graphics/texture/Pixels.java rename to src/main/java/ru/windcorp/progressia/client/graphics/texture/TextureData.java index 90498c2..afd44db 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/texture/Pixels.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/texture/TextureData.java @@ -22,7 +22,7 @@ import static org.lwjgl.opengl.GL12.*; import java.nio.ByteBuffer; -class Pixels { +class TextureData { private final ByteBuffer data; @@ -34,7 +34,7 @@ class Pixels { private final int width; private final int height; - public Pixels( + public TextureData( ByteBuffer data, int bufferWidth, int bufferHeight, int width, int height, diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/texture/TextureDataEditor.java b/src/main/java/ru/windcorp/progressia/client/graphics/texture/TextureDataEditor.java new file mode 100644 index 0000000..2cbe1c9 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/graphics/texture/TextureDataEditor.java @@ -0,0 +1,185 @@ +package ru.windcorp.progressia.client.graphics.texture; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; + +public class TextureDataEditor implements AutoCloseable { + + protected final BufferedImage image; + protected Graphics2D graphics; + + private final int contentWidth; + private final int contentHeight; + private final TextureSettings settings; + + protected TextureDataEditor( + BufferedImage image, + int contentWidth, int contentHeight, + TextureSettings settings + ) { + this.image = image; + this.contentWidth = contentWidth; + this.contentHeight = contentHeight; + this.settings = settings; + + startEditing(); + } + + public TextureDataEditor( + int bufferWidth, int bufferHeight, + int contentWidth, int contentHeight, + TextureSettings settings + ) { + this( + TextureUtils.createCanvas( + TextureUtils.createRaster(bufferWidth, bufferHeight) + ), + contentWidth, contentHeight, + settings + ); + } + + public TextureDataEditor(TextureData buffer) { + this( + createImage(buffer), + buffer.getContentWidth(), + buffer.getContentHeight(), + buffer.getSettings() + ); + } + + private static BufferedImage createImage(TextureData buffer) { + return TextureUtils.createCanvas( + TextureUtils.createRaster( + buffer.getData(), + buffer.getBufferWidth(), + buffer.getBufferHeight() + ) + ); + } + + public void startEditing() { + if (isEditable()) { + throw new IllegalStateException("This object is already editable"); + } + + this.graphics = this.image.createGraphics(); + + graphics.setColor(TextureUtils.CANVAS_BACKGROUND); + graphics.fillRect(0, 0, image.getWidth(), image.getHeight()); + + graphics.clipRect(0, 0, contentWidth, contentHeight); + } + + public void finishEditing() { + checkEditability(); + this.graphics.dispose(); + this.graphics = null; + } + + @Override + public void close() { + if (isEditable()) { + finishEditing(); + } + } + + public boolean isEditable() { + return this.graphics != null; + } + + protected void checkEditability() { + if (!isEditable()) { + throw new IllegalStateException("This object is not editable"); + } + } + + public TextureData createSnapshot() { + return new TextureData( + TextureUtils.extractBytes(image.getRaster()), + image.getWidth(), image.getHeight(), + contentWidth, contentHeight, + settings + ); + } + + public TextureData createSnapshot(TextureData output) { + TextureUtils.extractBytes(image.getRaster(), output.getData()); + return output; + } + + public TextureData toStatic() { + close(); + return createSnapshot(); + } + + public TextureData toStatic(TextureData data) { + close(); + return createSnapshot(data); + } + + public int getBufferWidth() { + return image.getWidth(); + } + + public int getBufferHeight() { + return image.getHeight(); + } + + public int getContentWidth() { + return contentWidth; + } + + public int getContentHeight() { + return contentHeight; + } + + public TextureSettings getSettings() { + return settings; + } + + public void draw( + BufferedImage source, + int srcX, int srcY, + int dstX, int dstY, + int width, int height + ) { + checkEditability(); + + graphics.drawImage( + source, + dstX, dstY, + dstX + width, dstY + height, + srcX, srcY, + srcX + width, srcY + height, + null + ); + } + + public void draw( + BufferedImage source, + int dstX, int dstY + ) { + draw(source, 0, 0, dstX, dstY, source.getWidth(), source.getHeight()); + } + + public void draw( + TextureDataEditor source, + int srcX, int srcY, + int dstX, int dstY, + int width, int height + ) { + draw(source.image, srcX, srcY, dstX, dstY, width, height); + } + + public void draw( + TextureDataEditor source, + int dstX, int dstY + ) { + draw( + source, 0, 0, dstX, dstY, + source.getContentWidth(), source.getContentHeight() + ); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/texture/TextureLoader.java b/src/main/java/ru/windcorp/progressia/client/graphics/texture/TextureLoader.java new file mode 100644 index 0000000..6a82641 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/graphics/texture/TextureLoader.java @@ -0,0 +1,49 @@ +package ru.windcorp.progressia.client.graphics.texture; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; + +import javax.imageio.ImageIO; + +import ru.windcorp.progressia.common.resource.Resource; +import ru.windcorp.progressia.common.util.BinUtil; + +public class TextureLoader { + + public static TextureDataEditor loadPixels( + InputStream compressed, TextureSettings settings + ) throws IOException { + BufferedImage readResult = ImageIO.read(compressed); + + int width = readResult.getWidth(); + int height = readResult.getHeight(); + + int bufferWidth = BinUtil.roundToGreaterPowerOf2(width); + int bufferHeight = BinUtil.roundToGreaterPowerOf2(height); + + TextureDataEditor result = new TextureDataEditor( + bufferWidth, bufferHeight, width, height, settings + ); + + Graphics2D g = result.graphics; + g.drawImage( + readResult, + 0, 0, width, height, + 0, height, width, 0, // Flip the image + null + ); + + return result; + } + + public static TextureDataEditor loadPixels( + Resource resource, TextureSettings settings + ) throws IOException { + return loadPixels(resource.getInputStream(), settings); + } + + private TextureLoader() {} + +} diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/texture/TextureManager.java b/src/main/java/ru/windcorp/progressia/client/graphics/texture/TextureManager.java deleted file mode 100644 index 6d08d9f..0000000 --- a/src/main/java/ru/windcorp/progressia/client/graphics/texture/TextureManager.java +++ /dev/null @@ -1,218 +0,0 @@ -/******************************************************************************* - * Progressia - * Copyright (C) 2020 Wind Corporation - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - *******************************************************************************/ -package ru.windcorp.progressia.client.graphics.texture; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.util.HashMap; -import java.util.Map; - -import ru.windcorp.progressia.client.graphics.backend.RenderTaskQueue; -import ru.windcorp.progressia.common.resource.Resource; -import ru.windcorp.progressia.common.resource.ResourceManager; -import ru.windcorp.progressia.common.util.ByteBufferInputStream; - -public class TextureManager { - - private static final String TEXTURE_ASSETS_PREFIX = "assets/textures/"; - - private static final Map LOADED_PIXELS = - new HashMap<>(); - - private static final Map LOADED_PRIMITIVES = - new HashMap<>(); - - private static Pixels getCachedPixels(String name) { - return LOADED_PIXELS.get(name); - } - - private static Pixels getCachedPixels(Resource resource) { - return getCachedPixels(resource.getName()); - } - - private static Pixels cachePixels(Pixels pixels, String name) { - LOADED_PIXELS.put(name, pixels); - return pixels; - } - - private static Pixels cachePixels(Pixels pixels, Resource resource) { - return cachePixels(pixels, resource.getName()); - } - - private static TexturePrimitive getCachedPrimitive(String name) { - return LOADED_PRIMITIVES.get(name); - } - - private static TexturePrimitive getCachedPrimitive(Resource resource) { - return getCachedPrimitive(resource.getName()); - } - - private static TexturePrimitive cachePrimitive( - TexturePrimitive primitive, - String name - ) { - LOADED_PRIMITIVES.put(name, primitive); - return primitive; - } - - private static TexturePrimitive cachePrimitive( - TexturePrimitive primitive, - Resource resource - ) { - return cachePrimitive(primitive, resource.getName()); - } - - private static Resource getResource(String textureName) { - return ResourceManager.getResource( - TEXTURE_ASSETS_PREFIX + textureName + ".png" - ); - } - - public static Pixels createOpenGLBuffer( - InputStream stream, - TextureSettings settings - ) { - try { - return PngLoader.loadPngImage(stream, settings); - } catch (IOException e) { - throw new RuntimeException("u stupid. refresh ur project"); - } - } - - public static Pixels createOpenGLBuffer( - Resource resource, - TextureSettings settings - ) { - Pixels cache = getCachedPixels(resource); - if (cache != null) return cache; - return cachePixels( - createOpenGLBuffer(resource.getInputStream(), settings), - resource - ); - } - - public static Pixels createOpenGLBuffer( - String textureName, - TextureSettings settings - ) { - Pixels cache = getCachedPixels(textureName); - if (cache != null) return cache; - return cachePixels( - createOpenGLBuffer(getResource(textureName), settings), - textureName - ); - } - - public static Pixels createOpenGLBuffer( - ByteBuffer bytes, - TextureSettings settings - ) { - bytes.mark(); - try { - return createOpenGLBuffer( - new ByteBufferInputStream(bytes), settings - ); - } finally { - bytes.reset(); - } - } - - public static TexturePrimitive createTexturePrimitive( - InputStream stream, - TextureSettings settings - ) { - Pixels pixels = createOpenGLBuffer(stream, settings); - TexturePrimitive result = new TexturePrimitive(pixels); - return result; - } - - public static TexturePrimitive createTexturePrimitive( - Resource resource, - TextureSettings settings - ) { - TexturePrimitive primitive = getCachedPrimitive(resource); - if (primitive != null) return primitive; - return cachePrimitive( - createTexturePrimitive(resource.getInputStream(), settings), - resource - ); - } - - public static TexturePrimitive createTexturePrimitive( - String textureName, - TextureSettings settings - ) { - TexturePrimitive primitive = getCachedPrimitive(textureName); - if (primitive != null) return primitive; - return cachePrimitive( - createTexturePrimitive(getResource(textureName), settings), - textureName - ); - } - - public static TexturePrimitive createTexturePrimitive( - ByteBuffer bytes, - TextureSettings settings - ) { - bytes.mark(); - try { - return createTexturePrimitive( - new ByteBufferInputStream(bytes), settings - ); - } finally { - bytes.reset(); - } - } - - public static TexturePrimitive load( - InputStream stream, - TextureSettings settings - ) { - TexturePrimitive result = createTexturePrimitive(stream, settings); - if (!result.isLoaded()) RenderTaskQueue.invokeLater(result::load); - return result; - } - - public static TexturePrimitive load( - Resource resource, - TextureSettings settings - ) { - return load(resource.getInputStream(), settings); - } - - public static TexturePrimitive load( - String textureName, - TextureSettings settings - ) { - return load(getResource(textureName), settings); - } - - public static TexturePrimitive load( - ByteBuffer bytes, - TextureSettings settings - ) { - bytes.mark(); - try { - return load(new ByteBufferInputStream(bytes), settings); - } finally { - bytes.reset(); - } - } - -} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/texture/TexturePrimitive.java b/src/main/java/ru/windcorp/progressia/client/graphics/texture/TexturePrimitive.java index b288352..6a3fcf0 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/texture/TexturePrimitive.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/texture/TexturePrimitive.java @@ -20,6 +20,8 @@ package ru.windcorp.progressia.client.graphics.texture; import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.opengl.GL20.*; +import java.util.Arrays; + import ru.windcorp.progressia.client.graphics.backend.OpenGLObjectTracker; import ru.windcorp.progressia.client.graphics.backend.OpenGLObjectTracker.OpenGLDeletable; @@ -27,13 +29,22 @@ public class TexturePrimitive implements OpenGLDeletable { private static final int NOT_LOADED = -1; + private static int[] currentlyBound = new int[32]; + static { + Arrays.fill(currentlyBound, NOT_LOADED); + } + private int handle = NOT_LOADED; - private Pixels pixels; + private TextureData pixels; - public TexturePrimitive(Pixels pixels) { + public TexturePrimitive(TextureData pixels) { this.pixels = pixels; } + public TextureData getData() { + return pixels; + } + public int getBufferWidth() { return pixels.getBufferWidth(); } @@ -59,10 +70,16 @@ public class TexturePrimitive implements OpenGLDeletable { load(); } + if (currentlyBound[slot] == handle) { + return; + } + int code = GL_TEXTURE0 + slot; glActiveTexture(code); glBindTexture(GL_TEXTURE_2D, handle); + + currentlyBound[slot] = handle; } protected void load() { diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/texture/TextureUtils.java b/src/main/java/ru/windcorp/progressia/client/graphics/texture/TextureUtils.java new file mode 100644 index 0000000..4a96a53 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/graphics/texture/TextureUtils.java @@ -0,0 +1,108 @@ +package ru.windcorp.progressia.client.graphics.texture; + +import java.awt.Color; +import java.awt.Transparency; +import java.awt.color.ColorSpace; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.nio.ByteBuffer; +import java.util.Hashtable; + +import org.lwjgl.BufferUtils; + +public class TextureUtils { + + public static final Color CANVAS_BACKGROUND = new Color(0, true); + + public static final ColorModel COLOR_MODEL = new ComponentColorModel( + ColorSpace.getInstance(ColorSpace.CS_sRGB), // Use RGB + new int[] {8, 8, 8, 8}, // Use every bit + true, // Has alpha + false, // Not premultiplied + Transparency.TRANSLUCENT, // Can have any alpha + DataBuffer.TYPE_BYTE // Alpha is one byte + ); + + private static final Hashtable BUFFERED_IMAGE_PROPERTIES = + new Hashtable<>(); + + public static WritableRaster createRaster( + int bufferWidth, + int bufferHeight + ) { + return Raster.createInterleavedRaster( + DataBuffer.TYPE_BYTE, // Storage model + bufferWidth, // Buffer width + bufferHeight, // Buffer height + 4, // RGBA + null // Location (here (0; 0)) + ); + } + + public static WritableRaster createRaster( + ByteBuffer buffer, + int bufferWidth, + int bufferHeight + ) { + final int bands = 4; // RGBA + + byte[] bytes = new byte[bufferWidth * bufferHeight * bands]; + + buffer.get(bytes); + buffer.position(buffer.position() - bytes.length); + + DataBufferByte dataBuffer = new DataBufferByte(bytes, bytes.length); + + return Raster.createInterleavedRaster( + dataBuffer, // The buffer + bufferWidth, // Buffer width + bufferHeight, // Buffer height + bands * bufferWidth, // Scanline stride + bands, // Pixel stride + new int[] {0, 1, 2, 3}, // Band offsets + null // Location (here (0; 0)) + ); + } + + public static BufferedImage createCanvas(WritableRaster raster) { + return new BufferedImage( + COLOR_MODEL, // Color model + raster, // Backing raster + false, // Raster is not premultipied + BUFFERED_IMAGE_PROPERTIES // Properties + ); + } + + public static ByteBuffer extractBytes(WritableRaster raster) { + byte[] data = ( + (DataBufferByte) raster.getDataBuffer() + ).getData(); + + ByteBuffer buffer = BufferUtils.createByteBuffer(data.length); + buffer.put(data); + buffer.flip(); + + return buffer; + } + + public static ByteBuffer extractBytes( + WritableRaster raster, ByteBuffer output + ) { + byte[] data = ( + (DataBufferByte) raster.getDataBuffer() + ).getData(); + + output.put(data); + output.flip(); + + return output; + } + + private TextureUtils() {} + +} diff --git a/src/main/java/ru/windcorp/progressia/client/world/renders/BlockRenders.java b/src/main/java/ru/windcorp/progressia/client/world/renders/BlockRenders.java index 8dbe6b6..499cc84 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/renders/BlockRenders.java +++ b/src/main/java/ru/windcorp/progressia/client/world/renders/BlockRenders.java @@ -20,38 +20,39 @@ package ru.windcorp.progressia.client.world.renders; import java.util.HashMap; import java.util.Map; +import ru.windcorp.progressia.client.graphics.texture.Atlases; +import ru.windcorp.progressia.client.graphics.texture.Atlases.AtlasGroup; import ru.windcorp.progressia.client.graphics.texture.SimpleTexture; -import ru.windcorp.progressia.client.graphics.texture.Sprite; import ru.windcorp.progressia.client.graphics.texture.Texture; -import ru.windcorp.progressia.client.graphics.texture.TextureManager; -import ru.windcorp.progressia.client.graphics.texture.TextureSettings; import ru.windcorp.progressia.client.world.renders.bro.BlockRenderOpaqueCube; +import ru.windcorp.progressia.common.resource.ResourceManager; public class BlockRenders { private static final Map BLOCK_RENDERS = new HashMap<>(); - private static final TextureSettings TEXTURE_SETTINGS = - new TextureSettings(false); + private static final AtlasGroup BLOCKS_ATLAS_GROUP = + new AtlasGroup("Blocks", 1 << 6); - private static Texture grassTop = qtex("grass_top"); - private static Texture grassSide = qtex("grass_side"); - private static Texture dirtT = qtex("grass_bottom"); - private static Texture stoneT = qtex("stone"); - private static Texture glassT = qtex("glass_clear"); + private static Texture grassTop = getTexture("grass_top"); + private static Texture grassSide = getTexture("grass_side"); + private static Texture dirt = getTexture("grass_bottom"); + private static Texture stone = getTexture("stone"); + private static Texture glass = getTexture("glass_clear"); + private static Texture compass = getTexture("compass"); private BlockRenders() {} - static { - register(new BlockRenderOpaqueCube("Test", "Grass", grassTop, dirtT, grassSide, grassSide, grassSide, grassSide)); - register(new BlockRenderOpaqueCube("Test", "Dirt", dirtT, dirtT, dirtT, dirtT, dirtT, dirtT)); - register(new BlockRenderOpaqueCube("Test", "Stone", stoneT, stoneT, stoneT, stoneT, stoneT, stoneT)); + public static void registerTest() { + register(new BlockRenderOpaqueCube("Test", "Grass", grassTop, dirt, grassSide, grassSide, grassSide, grassSide)); + register(new BlockRenderOpaqueCube("Test", "Dirt", dirt, dirt, dirt, dirt, dirt, dirt)); + register(new BlockRenderOpaqueCube("Test", "Stone", stone, stone, stone, stone, stone, stone)); - register(new BlockRenderOpaqueCube("Test", "Compass", qtex("compass"), qtex("compass"), qtex("side_north"), qtex("side_south"), qtex("side_east"), qtex("side_west"))); + register(new BlockRenderOpaqueCube("Test", "Compass", compass, compass, getTexture("side_north"), getTexture("side_south"), getTexture("side_east"), getTexture("side_west"))); register(new BlockRenderNone("Test", "Air")); - register(new BlockRenderTransparentCube("Test", "Glass", glassT, glassT, glassT, glassT, glassT, glassT)); + register(new BlockRenderTransparentCube("Test", "Glass", glass, glass, glass, glass, glass, glass)); } public static BlockRender get(String name) { @@ -62,10 +63,13 @@ public class BlockRenders { BLOCK_RENDERS.put(blockRender.getId(), blockRender); } - private static Texture qtex(String name) { - return new SimpleTexture(new Sprite( - TextureManager.load(name, TEXTURE_SETTINGS) - )); + public static Texture getTexture(String name) { + return new SimpleTexture( + Atlases.getSprite( + ResourceManager.getTextureResource("blocks/" + name), + BLOCKS_ATLAS_GROUP + ) + ); } } diff --git a/src/main/java/ru/windcorp/progressia/common/resource/Resource.java b/src/main/java/ru/windcorp/progressia/common/resource/Resource.java index 84af0b0..ce675dc 100644 --- a/src/main/java/ru/windcorp/progressia/common/resource/Resource.java +++ b/src/main/java/ru/windcorp/progressia/common/resource/Resource.java @@ -25,22 +25,17 @@ import java.io.Reader; import com.google.common.io.CharStreams; import ru.windcorp.progressia.Progressia; +import ru.windcorp.progressia.common.util.Named; -public class Resource { - - private final String name; +public class Resource extends Named { public Resource(String name) { - this.name = name; - } - - public String getName() { - return name; + super(name); } public InputStream getInputStream() { // TODO Do proper resource lookup - return Progressia.class.getClassLoader().getResourceAsStream(name); + return Progressia.class.getClassLoader().getResourceAsStream(getName()); } public Reader getReader() { diff --git a/src/main/java/ru/windcorp/progressia/common/resource/ResourceManager.java b/src/main/java/ru/windcorp/progressia/common/resource/ResourceManager.java index ebc602d..86471b4 100644 --- a/src/main/java/ru/windcorp/progressia/common/resource/ResourceManager.java +++ b/src/main/java/ru/windcorp/progressia/common/resource/ResourceManager.java @@ -22,5 +22,11 @@ public class ResourceManager { public static Resource getResource(String name) { return new Resource(name); } + + public static Resource getTextureResource(String name) { + return getResource("assets/textures/" + name + ".png"); + } + + private ResourceManager() {} } diff --git a/src/main/resources/assets/textures/compass.png b/src/main/resources/assets/textures/blocks/compass.png similarity index 100% rename from src/main/resources/assets/textures/compass.png rename to src/main/resources/assets/textures/blocks/compass.png diff --git a/src/main/resources/assets/textures/glass.png b/src/main/resources/assets/textures/blocks/glass.png similarity index 100% rename from src/main/resources/assets/textures/glass.png rename to src/main/resources/assets/textures/blocks/glass.png diff --git a/src/main/resources/assets/textures/glass_clear.png b/src/main/resources/assets/textures/blocks/glass_clear.png similarity index 100% rename from src/main/resources/assets/textures/glass_clear.png rename to src/main/resources/assets/textures/blocks/glass_clear.png diff --git a/src/main/resources/assets/textures/grass_bottom.png b/src/main/resources/assets/textures/blocks/grass_bottom.png similarity index 100% rename from src/main/resources/assets/textures/grass_bottom.png rename to src/main/resources/assets/textures/blocks/grass_bottom.png diff --git a/src/main/resources/assets/textures/grass_side.png b/src/main/resources/assets/textures/blocks/grass_side.png similarity index 100% rename from src/main/resources/assets/textures/grass_side.png rename to src/main/resources/assets/textures/blocks/grass_side.png diff --git a/src/main/resources/assets/textures/grass_top.png b/src/main/resources/assets/textures/blocks/grass_top.png similarity index 100% rename from src/main/resources/assets/textures/grass_top.png rename to src/main/resources/assets/textures/blocks/grass_top.png diff --git a/src/main/resources/assets/textures/side_east.png b/src/main/resources/assets/textures/blocks/side_east.png similarity index 100% rename from src/main/resources/assets/textures/side_east.png rename to src/main/resources/assets/textures/blocks/side_east.png diff --git a/src/main/resources/assets/textures/side_north.png b/src/main/resources/assets/textures/blocks/side_north.png similarity index 100% rename from src/main/resources/assets/textures/side_north.png rename to src/main/resources/assets/textures/blocks/side_north.png diff --git a/src/main/resources/assets/textures/side_south.png b/src/main/resources/assets/textures/blocks/side_south.png similarity index 100% rename from src/main/resources/assets/textures/side_south.png rename to src/main/resources/assets/textures/blocks/side_south.png diff --git a/src/main/resources/assets/textures/side_west.png b/src/main/resources/assets/textures/blocks/side_west.png similarity index 100% rename from src/main/resources/assets/textures/side_west.png rename to src/main/resources/assets/textures/blocks/side_west.png diff --git a/src/main/resources/assets/textures/stone.png b/src/main/resources/assets/textures/blocks/stone.png similarity index 100% rename from src/main/resources/assets/textures/stone.png rename to src/main/resources/assets/textures/blocks/stone.png