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/
This commit is contained in:
OLEGSHA 2020-08-15 22:15:26 +04:00
parent aeed34d105
commit 6d2adb0f31
25 changed files with 660 additions and 396 deletions

View File

@ -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.FlatRenderProgram;
import ru.windcorp.progressia.client.graphics.flat.LayerTestUI; import ru.windcorp.progressia.client.graphics.flat.LayerTestUI;
import ru.windcorp.progressia.client.graphics.gui.LayerTestGUI; 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.LayerWorld;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram; import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.world.renders.BlockRenders;
public class ClientProxy implements Proxy { public class ClientProxy implements Proxy {
@ -40,6 +42,9 @@ public class ClientProxy implements Proxy {
e.printStackTrace(); e.printStackTrace();
} }
BlockRenders.registerTest();
Atlases.loadAllAtlases();
GUI.addBottomLayer(new LayerWorld()); GUI.addBottomLayer(new LayerWorld());
GUI.addTopLayer(new LayerTestUI()); GUI.addTopLayer(new LayerTestUI());
GUI.addTopLayer(new LayerTestGUI()); GUI.addTopLayer(new LayerTestGUI());

View File

@ -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.KeyEvent;
import ru.windcorp.progressia.client.graphics.input.bus.Input; import ru.windcorp.progressia.client.graphics.input.bus.Input;
import ru.windcorp.progressia.client.graphics.model.LambdaModel; import ru.windcorp.progressia.client.graphics.model.LambdaModel;
import ru.windcorp.progressia.client.graphics.texture.SimpleTexture; import ru.windcorp.progressia.client.graphics.texture.SimpleTextures;
import ru.windcorp.progressia.client.graphics.texture.Sprite;
import ru.windcorp.progressia.client.graphics.texture.Texture; 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; import ru.windcorp.progressia.client.graphics.world.LayerWorld;
public class LayerTestUI extends AssembledFlatLayer { 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)); target.pushTransform(new Mat4().identity().translate(x + 2*BORDER, y + 2*BORDER, 0));
final Texture compassBg = qtex("compass_icon"); final Texture compassBg = SimpleTextures.get("compass_icon");
final Texture compassFg = qtex("compass_icon_arrow"); final Texture compassFg = SimpleTextures.get("compass_icon_arrow");
target.drawTexture(texShadow, texShadow, texSize, texSize, Colors.BLACK, compassBg); target.drawTexture(texShadow, texShadow, texSize, texSize, Colors.BLACK, compassBg);
target.drawTexture(0, 0, texSize, texSize, compassBg); target.drawTexture(0, 0, texSize, texSize, compassBg);
@ -143,17 +140,4 @@ public class LayerTestUI extends AssembledFlatLayer {
invalidate(); 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)
));
}
} }

View File

@ -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<Resource, Sprite> LOADED =
new HashMap<>();
private static final Multimap<AtlasGroup, Atlas> 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<Atlas> atlases = (List<Atlas>) 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() {}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*******************************************************************************/
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;
}
}

View File

@ -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<Resource, Texture> 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() {}
}

View File

@ -22,7 +22,7 @@ import static org.lwjgl.opengl.GL12.*;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
class Pixels { class TextureData {
private final ByteBuffer data; private final ByteBuffer data;
@ -34,7 +34,7 @@ class Pixels {
private final int width; private final int width;
private final int height; private final int height;
public Pixels( public TextureData(
ByteBuffer data, ByteBuffer data,
int bufferWidth, int bufferHeight, int bufferWidth, int bufferHeight,
int width, int height, int width, int height,

View File

@ -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()
);
}
}

View File

@ -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() {}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*******************************************************************************/
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<String, Pixels> LOADED_PIXELS =
new HashMap<>();
private static final Map<String, TexturePrimitive> 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();
}
}
}

View File

@ -20,6 +20,8 @@ package ru.windcorp.progressia.client.graphics.texture;
import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL20.*; 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;
import ru.windcorp.progressia.client.graphics.backend.OpenGLObjectTracker.OpenGLDeletable; 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 final int NOT_LOADED = -1;
private int handle = NOT_LOADED; private static int[] currentlyBound = new int[32];
private Pixels pixels; static {
Arrays.fill(currentlyBound, NOT_LOADED);
}
public TexturePrimitive(Pixels pixels) { private int handle = NOT_LOADED;
private TextureData pixels;
public TexturePrimitive(TextureData pixels) {
this.pixels = pixels; this.pixels = pixels;
} }
public TextureData getData() {
return pixels;
}
public int getBufferWidth() { public int getBufferWidth() {
return pixels.getBufferWidth(); return pixels.getBufferWidth();
} }
@ -59,10 +70,16 @@ public class TexturePrimitive implements OpenGLDeletable {
load(); load();
} }
if (currentlyBound[slot] == handle) {
return;
}
int code = GL_TEXTURE0 + slot; int code = GL_TEXTURE0 + slot;
glActiveTexture(code); glActiveTexture(code);
glBindTexture(GL_TEXTURE_2D, handle); glBindTexture(GL_TEXTURE_2D, handle);
currentlyBound[slot] = handle;
} }
protected void load() { protected void load() {

View File

@ -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() {}
}

View File

@ -20,38 +20,39 @@ package ru.windcorp.progressia.client.world.renders;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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.SimpleTexture;
import ru.windcorp.progressia.client.graphics.texture.Sprite;
import ru.windcorp.progressia.client.graphics.texture.Texture; 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.client.world.renders.bro.BlockRenderOpaqueCube;
import ru.windcorp.progressia.common.resource.ResourceManager;
public class BlockRenders { public class BlockRenders {
private static final Map<String, BlockRender> BLOCK_RENDERS = private static final Map<String, BlockRender> BLOCK_RENDERS =
new HashMap<>(); new HashMap<>();
private static final TextureSettings TEXTURE_SETTINGS = private static final AtlasGroup BLOCKS_ATLAS_GROUP =
new TextureSettings(false); new AtlasGroup("Blocks", 1 << 6);
private static Texture grassTop = qtex("grass_top"); private static Texture grassTop = getTexture("grass_top");
private static Texture grassSide = qtex("grass_side"); private static Texture grassSide = getTexture("grass_side");
private static Texture dirtT = qtex("grass_bottom"); private static Texture dirt = getTexture("grass_bottom");
private static Texture stoneT = qtex("stone"); private static Texture stone = getTexture("stone");
private static Texture glassT = qtex("glass_clear"); private static Texture glass = getTexture("glass_clear");
private static Texture compass = getTexture("compass");
private BlockRenders() {} private BlockRenders() {}
static { public static void registerTest() {
register(new BlockRenderOpaqueCube("Test", "Grass", grassTop, dirtT, grassSide, grassSide, grassSide, grassSide)); register(new BlockRenderOpaqueCube("Test", "Grass", grassTop, dirt, grassSide, grassSide, grassSide, grassSide));
register(new BlockRenderOpaqueCube("Test", "Dirt", dirtT, dirtT, dirtT, dirtT, dirtT, dirtT)); register(new BlockRenderOpaqueCube("Test", "Dirt", dirt, dirt, dirt, dirt, dirt, dirt));
register(new BlockRenderOpaqueCube("Test", "Stone", stoneT, stoneT, stoneT, stoneT, stoneT, stoneT)); 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 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) { public static BlockRender get(String name) {
@ -62,10 +63,13 @@ public class BlockRenders {
BLOCK_RENDERS.put(blockRender.getId(), blockRender); BLOCK_RENDERS.put(blockRender.getId(), blockRender);
} }
private static Texture qtex(String name) { public static Texture getTexture(String name) {
return new SimpleTexture(new Sprite( return new SimpleTexture(
TextureManager.load(name, TEXTURE_SETTINGS) Atlases.getSprite(
)); ResourceManager.getTextureResource("blocks/" + name),
BLOCKS_ATLAS_GROUP
)
);
} }
} }

View File

@ -25,22 +25,17 @@ import java.io.Reader;
import com.google.common.io.CharStreams; import com.google.common.io.CharStreams;
import ru.windcorp.progressia.Progressia; import ru.windcorp.progressia.Progressia;
import ru.windcorp.progressia.common.util.Named;
public class Resource { public class Resource extends Named {
private final String name;
public Resource(String name) { public Resource(String name) {
this.name = name; super(name);
}
public String getName() {
return name;
} }
public InputStream getInputStream() { public InputStream getInputStream() {
// TODO Do proper resource lookup // TODO Do proper resource lookup
return Progressia.class.getClassLoader().getResourceAsStream(name); return Progressia.class.getClassLoader().getResourceAsStream(getName());
} }
public Reader getReader() { public Reader getReader() {

View File

@ -23,4 +23,10 @@ public class ResourceManager {
return new Resource(name); return new Resource(name);
} }
public static Resource getTextureResource(String name) {
return getResource("assets/textures/" + name + ".png");
}
private ResourceManager() {}
} }

View File

Before

Width:  |  Height:  |  Size: 256 B

After

Width:  |  Height:  |  Size: 256 B

View File

Before

Width:  |  Height:  |  Size: 644 B

After

Width:  |  Height:  |  Size: 644 B

View File

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 588 B

After

Width:  |  Height:  |  Size: 588 B

View File

Before

Width:  |  Height:  |  Size: 609 B

After

Width:  |  Height:  |  Size: 609 B

View File

Before

Width:  |  Height:  |  Size: 591 B

After

Width:  |  Height:  |  Size: 591 B

View File

Before

Width:  |  Height:  |  Size: 604 B

After

Width:  |  Height:  |  Size: 604 B

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB