Refactored texture loading: added caching and allowed loading from RAM

This commit is contained in:
OLEGSHA 2020-07-31 14:42:28 +03:00
parent e8cec807bc
commit 16205909ed
11 changed files with 503 additions and 309 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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

View File

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

View File

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

View 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);
}
}

View File

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

View File

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

View 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;
}
}