Refactored texture loading: added caching and allowed loading from RAM
This commit is contained in:
parent
e8cec807bc
commit
16205909ed
@ -1,167 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* Optica
|
|
||||||
* 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.optica.client;
|
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
|
|
||||||
import com.google.common.eventbus.Subscribe;
|
|
||||||
|
|
||||||
import glm.mat._3.Mat3;
|
|
||||||
import glm.mat._4.Mat4;
|
|
||||||
import glm.vec._3.Vec3;
|
|
||||||
import ru.windcorp.optica.client.graphics.Layer;
|
|
||||||
import ru.windcorp.optica.client.graphics.backend.GraphicsBackend;
|
|
||||||
import ru.windcorp.optica.client.graphics.backend.GraphicsInterface;
|
|
||||||
import ru.windcorp.optica.client.graphics.input.KeyEvent;
|
|
||||||
import ru.windcorp.optica.client.graphics.input.CursorMoveEvent;
|
|
||||||
import ru.windcorp.optica.client.graphics.model.Model;
|
|
||||||
import ru.windcorp.optica.client.graphics.model.DynamicModel;
|
|
||||||
import ru.windcorp.optica.client.graphics.model.Shapes;
|
|
||||||
import ru.windcorp.optica.client.graphics.texture.SimpleTexture;
|
|
||||||
import ru.windcorp.optica.client.graphics.texture.Sprite;
|
|
||||||
import ru.windcorp.optica.client.graphics.texture.Texture;
|
|
||||||
import ru.windcorp.optica.client.graphics.texture.TextureManager;
|
|
||||||
import ru.windcorp.optica.client.graphics.world.Camera;
|
|
||||||
import ru.windcorp.optica.client.graphics.world.WorldRenderer;
|
|
||||||
|
|
||||||
public class TestLayer extends Layer {
|
|
||||||
|
|
||||||
private final Model model;
|
|
||||||
|
|
||||||
private final Camera camera = new Camera(
|
|
||||||
new Vec3(),
|
|
||||||
0, (float) Math.PI,
|
|
||||||
(float) Math.toRadians(70)
|
|
||||||
);
|
|
||||||
|
|
||||||
private final Vec3 velocity = new Vec3();
|
|
||||||
private final Vec3 tmp = new Vec3();
|
|
||||||
|
|
||||||
private final Mat3 angMat = new Mat3();
|
|
||||||
|
|
||||||
private int movementX = 0;
|
|
||||||
private int movementY = 0;
|
|
||||||
private int movementZ = 0;
|
|
||||||
|
|
||||||
public TestLayer() {
|
|
||||||
super("Test");
|
|
||||||
|
|
||||||
Texture top = qtex("grass_top");
|
|
||||||
Texture side = qtex("grass_side");
|
|
||||||
Texture bottom = qtex("grass_bottom");
|
|
||||||
|
|
||||||
model = new DynamicModel(DynamicModel.builder()
|
|
||||||
.addDynamicPart(
|
|
||||||
new Shapes.PppBuilder(top, bottom, side, side, side, side)
|
|
||||||
.create()
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
@Override
|
|
||||||
protected void getDynamicTransform(int shapeIndex, Mat4 result) {
|
|
||||||
result.translate(0, 0, +5);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private Texture qtex(String name) {
|
|
||||||
return new SimpleTexture(new Sprite(TextureManager.load(name, false)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void initialize() {
|
|
||||||
GraphicsInterface.subscribeToInputEvents(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final WorldRenderer renderer = new WorldRenderer();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doRender() {
|
|
||||||
camera.apply(renderer);
|
|
||||||
|
|
||||||
angMat.set().rotateY(-camera.getYaw());
|
|
||||||
|
|
||||||
tmp.set(movementX, 0, movementZ);
|
|
||||||
angMat.mul_(tmp); // bug in jglm
|
|
||||||
tmp.y = movementY;
|
|
||||||
tmp.mul(0.1f);
|
|
||||||
tmp.sub(velocity);
|
|
||||||
tmp.mul(0.1f);
|
|
||||||
velocity.add(tmp);
|
|
||||||
camera.move(velocity);
|
|
||||||
|
|
||||||
model.render(renderer);
|
|
||||||
|
|
||||||
renderer.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean flag = true;
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void onKeyEvent(KeyEvent event) {
|
|
||||||
if (event.isRepeat()) return;
|
|
||||||
|
|
||||||
int multiplier = event.isPress() ? 1 : -1;
|
|
||||||
|
|
||||||
switch (event.getKey()) {
|
|
||||||
case GLFW.GLFW_KEY_W:
|
|
||||||
movementZ += -1 * multiplier;
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_KEY_S:
|
|
||||||
movementZ += +1 * multiplier;
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_KEY_A:
|
|
||||||
movementX += -1 * multiplier;
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_KEY_D:
|
|
||||||
movementX += +1 * multiplier;
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_KEY_SPACE:
|
|
||||||
movementY += +1 * multiplier;
|
|
||||||
break;
|
|
||||||
case GLFW.GLFW_KEY_LEFT_SHIFT:
|
|
||||||
movementY += -1 * multiplier;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GLFW.GLFW_KEY_ESCAPE:
|
|
||||||
if (!event.isPress()) return;
|
|
||||||
|
|
||||||
if (flag) {
|
|
||||||
GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_NORMAL);
|
|
||||||
} else {
|
|
||||||
GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED);
|
|
||||||
}
|
|
||||||
|
|
||||||
flag = !flag;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void onMouseMoved(CursorMoveEvent event) {
|
|
||||||
if (!flag) return;
|
|
||||||
|
|
||||||
final float yawScale = 0.002f;
|
|
||||||
final float pitchScale = yawScale;
|
|
||||||
|
|
||||||
camera.turn(
|
|
||||||
(float) (event.getChangeY() * pitchScale),
|
|
||||||
(float) (event.getChangeX() * yawScale)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -29,24 +29,30 @@ class Pixels {
|
|||||||
private final int bufferWidth;
|
private final int bufferWidth;
|
||||||
private final int bufferHeight;
|
private final int bufferHeight;
|
||||||
|
|
||||||
private final boolean filtered;
|
private final TextureSettings settings;
|
||||||
|
|
||||||
|
private final int width;
|
||||||
|
private final int height;
|
||||||
|
|
||||||
public Pixels(
|
public Pixels(
|
||||||
ByteBuffer data,
|
ByteBuffer data,
|
||||||
int bufferWidth, int bufferHeight,
|
int bufferWidth, int bufferHeight,
|
||||||
boolean filtered
|
int width, int height,
|
||||||
|
TextureSettings settings
|
||||||
) {
|
) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
this.bufferWidth = bufferWidth;
|
this.bufferWidth = bufferWidth;
|
||||||
this.bufferHeight = bufferHeight;
|
this.bufferHeight = bufferHeight;
|
||||||
this.filtered = filtered;
|
this.settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int load() {
|
public int load() {
|
||||||
int handle = glGenTextures();
|
int handle = glGenTextures();
|
||||||
glBindTexture(GL_TEXTURE_2D, handle);
|
glBindTexture(GL_TEXTURE_2D, handle);
|
||||||
|
|
||||||
if (filtered) {
|
if (settings.isFiltered()) {
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
} else {
|
} else {
|
||||||
@ -74,4 +80,28 @@ class Pixels {
|
|||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ByteBuffer getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBufferWidth() {
|
||||||
|
return bufferWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBufferHeight() {
|
||||||
|
return bufferHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getContentWidth() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getContentHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextureSettings getSettings() {
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,109 @@
|
|||||||
|
package ru.windcorp.optica.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.optica.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),
|
||||||
|
width, height,
|
||||||
|
bufferWidth, bufferHeight,
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -17,128 +17,202 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package ru.windcorp.optica.client.graphics.texture;
|
package ru.windcorp.optica.client.graphics.texture;
|
||||||
|
|
||||||
import java.awt.Graphics;
|
import java.io.IOException;
|
||||||
import java.awt.color.ColorSpace;
|
import java.io.InputStream;
|
||||||
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.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.util.HashMap;
|
||||||
import java.util.Hashtable;
|
import java.util.Map;
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
|
|
||||||
import ru.windcorp.optica.client.graphics.backend.RenderTaskQueue;
|
import ru.windcorp.optica.client.graphics.backend.RenderTaskQueue;
|
||||||
import ru.windcorp.optica.common.resource.Resource;
|
import ru.windcorp.optica.common.resource.Resource;
|
||||||
import ru.windcorp.optica.common.resource.ResourceManager;
|
import ru.windcorp.optica.common.resource.ResourceManager;
|
||||||
|
import ru.windcorp.optica.common.util.ByteBufferInputStream;
|
||||||
|
|
||||||
public class TextureManager {
|
public class TextureManager {
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
private static final String TEXTURE_ASSETS_PREFIX = "assets/textures/";
|
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) {
|
private static Resource getResource(String textureName) {
|
||||||
return ResourceManager.getResource(
|
return ResourceManager.getResource(
|
||||||
TEXTURE_ASSETS_PREFIX + textureName + ".png"
|
TEXTURE_ASSETS_PREFIX + textureName + ".png"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TexturePrimitive load(String textureName, boolean filtered) {
|
public static Pixels createOpenGLBuffer(
|
||||||
TexturePrimitive result = loadToByteBuffer(textureName, filtered);
|
InputStream stream,
|
||||||
RenderTaskQueue.invokeLater(result::load);
|
TextureSettings settings
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TexturePrimitive loadToByteBuffer(
|
|
||||||
String textureName, boolean filter
|
|
||||||
) {
|
) {
|
||||||
Resource resource = getResource(textureName);
|
|
||||||
|
|
||||||
BufferedImage source = readImage(resource);
|
|
||||||
|
|
||||||
int bufferWidth = toPowerOf2(source.getWidth()),
|
|
||||||
bufferHeight = toPowerOf2(source.getHeight());
|
|
||||||
|
|
||||||
WritableRaster raster = Raster.createInterleavedRaster(
|
|
||||||
DataBuffer.TYPE_BYTE, // Storage model
|
|
||||||
bufferWidth, // Buffer width
|
|
||||||
bufferHeight, // Buffer height
|
|
||||||
4, // RGBA
|
|
||||||
null // Location (here (0; 0))
|
|
||||||
);
|
|
||||||
|
|
||||||
BufferedImage canvas = new BufferedImage(
|
|
||||||
COLOR_MODEL, // Color model
|
|
||||||
raster, // Backing raster
|
|
||||||
false, // Raster is not premultipied
|
|
||||||
CANVAS_PROPERTIES // Properties
|
|
||||||
);
|
|
||||||
|
|
||||||
Graphics g = canvas.createGraphics();
|
|
||||||
g.setColor(CANVAS_BACKGROUND);
|
|
||||||
g.fillRect(0, 0, source.getWidth(), source.getHeight());
|
|
||||||
g.drawImage(
|
|
||||||
source,
|
|
||||||
0, 0, source.getWidth(), source.getHeight(),
|
|
||||||
0, source.getHeight(), source.getWidth(), 0, // Flip the image
|
|
||||||
null
|
|
||||||
);
|
|
||||||
g.dispose();
|
|
||||||
|
|
||||||
byte[] data = (
|
|
||||||
(DataBufferByte) canvas.getRaster().getDataBuffer()
|
|
||||||
).getData();
|
|
||||||
|
|
||||||
ByteBuffer buffer = ByteBuffer.allocateDirect(data.length);
|
|
||||||
buffer.order(ByteOrder.nativeOrder());
|
|
||||||
buffer.put(data);
|
|
||||||
buffer.flip();
|
|
||||||
|
|
||||||
Pixels pixels = new Pixels(buffer, bufferWidth, bufferHeight, filter);
|
|
||||||
|
|
||||||
TexturePrimitive result = new TexturePrimitive(
|
|
||||||
pixels,
|
|
||||||
source.getWidth(),
|
|
||||||
source.getHeight(),
|
|
||||||
bufferWidth,
|
|
||||||
bufferHeight
|
|
||||||
);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BufferedImage readImage(Resource resource) {
|
|
||||||
try {
|
try {
|
||||||
return ImageIO.read(resource.getInputStream());
|
return PngLoader.loadPngImage(stream, settings);
|
||||||
} catch (Exception e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException("too bad. refresh project u stupid. must be " + resource.getName(), e);
|
throw new RuntimeException("u stupid. refresh ur project");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int toPowerOf2(int i) {
|
public static Pixels createOpenGLBuffer(
|
||||||
|
Resource resource,
|
||||||
|
TextureSettings settings
|
||||||
|
) {
|
||||||
|
Pixels cache = getCachedPixels(resource);
|
||||||
|
if (cache != null) return cache;
|
||||||
|
return cachePixels(
|
||||||
|
createOpenGLBuffer(resource.getInputStream(), settings),
|
||||||
|
resource
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO optimize
|
public static Pixels createOpenGLBuffer(
|
||||||
|
String textureName,
|
||||||
|
TextureSettings settings
|
||||||
|
) {
|
||||||
|
Pixels cache = getCachedPixels(textureName);
|
||||||
|
if (cache != null) return cache;
|
||||||
|
return cachePixels(
|
||||||
|
createOpenGLBuffer(getResource(textureName), settings),
|
||||||
|
textureName
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
int result = 1;
|
public static Pixels createOpenGLBuffer(
|
||||||
do {
|
ByteBuffer bytes,
|
||||||
result *= 2;
|
TextureSettings settings
|
||||||
} while (result < i);
|
) {
|
||||||
|
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;
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -28,41 +28,26 @@ 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 int handle = NOT_LOADED;
|
||||||
private Pixels pixelsToLoad;
|
private Pixels pixels;
|
||||||
|
|
||||||
private final int width;
|
public TexturePrimitive(Pixels pixels) {
|
||||||
private final int height;
|
this.pixels = pixels;
|
||||||
private final int bufferWidth;
|
|
||||||
private final int bufferHeight;
|
|
||||||
|
|
||||||
protected TexturePrimitive(
|
|
||||||
Pixels pixels,
|
|
||||||
int width, int height,
|
|
||||||
int bufferWidth, int bufferHeight
|
|
||||||
) {
|
|
||||||
this.pixelsToLoad = pixels;
|
|
||||||
this.width = width;
|
|
||||||
this.height = height;
|
|
||||||
this.bufferWidth = bufferWidth;
|
|
||||||
this.bufferHeight = bufferHeight;
|
|
||||||
|
|
||||||
OpenGLObjectTracker.register(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getWidth() {
|
|
||||||
return width;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getHeight() {
|
|
||||||
return height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getBufferWidth() {
|
public int getBufferWidth() {
|
||||||
return bufferWidth;
|
return pixels.getBufferWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getBufferHeight() {
|
public int getBufferHeight() {
|
||||||
return bufferHeight;
|
return pixels.getBufferHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWidth() {
|
||||||
|
return pixels.getContentWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeight() {
|
||||||
|
return pixels.getContentHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLoaded() {
|
public boolean isLoaded() {
|
||||||
@ -83,13 +68,12 @@ public class TexturePrimitive implements OpenGLDeletable {
|
|||||||
protected void load() {
|
protected void load() {
|
||||||
if (isLoaded()) return;
|
if (isLoaded()) return;
|
||||||
|
|
||||||
handle = pixelsToLoad.load();
|
handle = pixels.load();
|
||||||
|
OpenGLObjectTracker.register(this);
|
||||||
|
|
||||||
if (handle < 0) {
|
if (handle < 0) {
|
||||||
throw new RuntimeException("oops");
|
throw new RuntimeException("oops");
|
||||||
}
|
}
|
||||||
|
|
||||||
pixelsToLoad = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
package ru.windcorp.optica.client.graphics.texture;
|
||||||
|
|
||||||
|
public class TextureSettings {
|
||||||
|
|
||||||
|
private final boolean isFiltered;
|
||||||
|
|
||||||
|
public TextureSettings(boolean isFiltered) {
|
||||||
|
this.isFiltered = isFiltered;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFiltered() {
|
||||||
|
return isFiltered;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -24,19 +24,23 @@ import ru.windcorp.optica.client.graphics.texture.SimpleTexture;
|
|||||||
import ru.windcorp.optica.client.graphics.texture.Sprite;
|
import ru.windcorp.optica.client.graphics.texture.Sprite;
|
||||||
import ru.windcorp.optica.client.graphics.texture.Texture;
|
import ru.windcorp.optica.client.graphics.texture.Texture;
|
||||||
import ru.windcorp.optica.client.graphics.texture.TextureManager;
|
import ru.windcorp.optica.client.graphics.texture.TextureManager;
|
||||||
|
import ru.windcorp.optica.client.graphics.texture.TextureSettings;
|
||||||
import ru.windcorp.optica.client.world.renders.bro.BlockRenderOpaqueCube;
|
import ru.windcorp.optica.client.world.renders.bro.BlockRenderOpaqueCube;
|
||||||
|
|
||||||
public class BlockRenders {
|
public class BlockRenders {
|
||||||
|
|
||||||
|
private static final Map<String, BlockRender> BLOCK_RENDERS =
|
||||||
|
new HashMap<>();
|
||||||
|
|
||||||
|
private static final TextureSettings TEXTURE_SETTINGS =
|
||||||
|
new TextureSettings(false);
|
||||||
|
|
||||||
private static Texture grassTop = qtex("grass_top");
|
private static Texture grassTop = qtex("grass_top");
|
||||||
private static Texture grassSide = qtex("grass_side");
|
private static Texture grassSide = qtex("grass_side");
|
||||||
private static Texture dirtT = qtex("grass_bottom");
|
private static Texture dirtT = qtex("grass_bottom");
|
||||||
private static Texture stoneT = qtex("stone");
|
private static Texture stoneT = qtex("stone");
|
||||||
private static Texture glassT = qtex("glass_clear");
|
private static Texture glassT = qtex("glass_clear");
|
||||||
|
|
||||||
private static final Map<String, BlockRender> BLOCK_RENDERS =
|
|
||||||
new HashMap<>();
|
|
||||||
|
|
||||||
private BlockRenders() {}
|
private BlockRenders() {}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@ -59,7 +63,9 @@ public class BlockRenders {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Texture qtex(String name) {
|
private static Texture qtex(String name) {
|
||||||
return new SimpleTexture(new Sprite(TextureManager.load(name, false)));
|
return new SimpleTexture(new Sprite(
|
||||||
|
TextureManager.load(name, TEXTURE_SETTINGS)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
18
src/main/java/ru/windcorp/optica/common/util/BinUtil.java
Normal file
18
src/main/java/ru/windcorp/optica/common/util/BinUtil.java
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package ru.windcorp.optica.common.util;
|
||||||
|
|
||||||
|
public class BinUtil {
|
||||||
|
|
||||||
|
public static int closestGreaterPowerOf2(int x) {
|
||||||
|
x |= x >> 1;
|
||||||
|
x |= x >> 2;
|
||||||
|
x |= x >> 4;
|
||||||
|
x |= x >> 8;
|
||||||
|
x |= x >> 16;
|
||||||
|
return x + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int roundToGreaterPowerOf2(int x) {
|
||||||
|
return closestGreaterPowerOf2(x - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package ru.windcorp.optica.common.util;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="https://stackoverflow.com/users/37416/mike-houston">Mike
|
||||||
|
* Houston</a>, adapted by Javapony
|
||||||
|
*/
|
||||||
|
public class ByteBufferInputStream extends InputStream {
|
||||||
|
|
||||||
|
private final ByteBuffer buffer;
|
||||||
|
|
||||||
|
public ByteBufferInputStream(ByteBuffer buffer) {
|
||||||
|
this.buffer = buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int read() {
|
||||||
|
if (!buffer.hasRemaining()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return buffer.get() & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int read(byte[] bytes, int off, int len) {
|
||||||
|
if (!buffer.hasRemaining()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
len = Math.min(len, buffer.remaining());
|
||||||
|
buffer.get(bytes, off, len);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package ru.windcorp.optica.common.util;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.BufferOverflowException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
public class ByteBufferOutputStream extends OutputStream {
|
||||||
|
|
||||||
|
private final ByteBuffer buffer;
|
||||||
|
|
||||||
|
public ByteBufferOutputStream(ByteBuffer buffer) {
|
||||||
|
this.buffer = buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(int b) throws IOException {
|
||||||
|
try {
|
||||||
|
buffer.put((byte) b);
|
||||||
|
} catch (BufferOverflowException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(byte[] bytes, int off, int len) throws IOException {
|
||||||
|
try {
|
||||||
|
buffer.put(bytes, off, len);
|
||||||
|
} catch (BufferOverflowException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
58
src/test/java/ru/windcorp/optica/util/BinUtilTest.java
Normal file
58
src/test/java/ru/windcorp/optica/util/BinUtilTest.java
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package ru.windcorp.optica.util;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import ru.windcorp.optica.common.util.BinUtil;
|
||||||
|
|
||||||
|
public class BinUtilTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void cornerCases() {
|
||||||
|
test(1);
|
||||||
|
test(2);
|
||||||
|
test(3);
|
||||||
|
test(4);
|
||||||
|
test(7);
|
||||||
|
test(8);
|
||||||
|
test(9);
|
||||||
|
|
||||||
|
test(1023);
|
||||||
|
test(1024);
|
||||||
|
test(1025);
|
||||||
|
|
||||||
|
test((1 << 16) - 1);
|
||||||
|
test(1 << 16);
|
||||||
|
test((1 << 16) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void random() {
|
||||||
|
Random random = new Random(0);
|
||||||
|
|
||||||
|
for (int i = 0; i < 10000; ++i) {
|
||||||
|
test(random.nextInt((1 << 30) - 2) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test(int x) {
|
||||||
|
assertEquals("Round, x = " + x, referenceRound(x), BinUtil.roundToGreaterPowerOf2(x));
|
||||||
|
assertEquals("Greater, x = " + x, referenceGreater(x), BinUtil.closestGreaterPowerOf2(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
int referenceGreater(int x) {
|
||||||
|
int p;
|
||||||
|
for (p = 1; p <= x; p *= 2);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
int referenceRound(int x) {
|
||||||
|
int p;
|
||||||
|
for (p = 1; p < x; p *= 2);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user