diff --git a/build.xml b/build.xml index 33157702..22e1059d 100644 --- a/build.xml +++ b/build.xml @@ -410,7 +410,7 @@ - + diff --git a/libs/asm-debug-all.jar b/libs/asm-debug-all.jar new file mode 100644 index 00000000..d5aa15e0 Binary files /dev/null and b/libs/asm-debug-all.jar differ diff --git a/libs/asm-util.jar b/libs/asm-util.jar deleted file mode 100644 index 499d2290..00000000 Binary files a/libs/asm-util.jar and /dev/null differ diff --git a/libs/asm.jar b/libs/asm.jar deleted file mode 100644 index 334e7fdc..00000000 Binary files a/libs/asm.jar and /dev/null differ diff --git a/src/java/org/lwjgl/test/mapped/MappedObjectBench.java b/src/java/org/lwjgl/test/mapped/MappedObjectBench.java index 87bf883e..c918c4a1 100644 --- a/src/java/org/lwjgl/test/mapped/MappedObjectBench.java +++ b/src/java/org/lwjgl/test/mapped/MappedObjectBench.java @@ -31,6 +31,7 @@ */ package org.lwjgl.test.mapped; +import org.lwjgl.MemoryUtil; import org.lwjgl.util.mapped.MappedObjectUnsafe; import java.nio.ByteBuffer; @@ -212,7 +213,7 @@ public class MappedObjectBench { final int iterations = 64 * 1024; ByteBuffer bb = ByteBuffer.allocateDirect(200); - long addr = MappedObjectUnsafe.getBufferBaseAddress(bb); + long addr = MemoryUtil.getAddress(bb); long[] took = new long[runs]; for ( int run = 0; run < runs; run++ ) { diff --git a/src/java/org/lwjgl/test/mapped/MappedObjectTests1.java b/src/java/org/lwjgl/test/mapped/MappedObjectTests1.java index cb402522..88d10773 100644 --- a/src/java/org/lwjgl/test/mapped/MappedObjectTests1.java +++ b/src/java/org/lwjgl/test/mapped/MappedObjectTests1.java @@ -31,6 +31,7 @@ */ package org.lwjgl.test.mapped; +import org.lwjgl.MemoryUtil; import org.lwjgl.util.mapped.MappedHelper; import org.lwjgl.util.mapped.MappedObjectUnsafe; @@ -143,9 +144,9 @@ public class MappedObjectTests1 { // test newBuffer { - long addr1 = MappedObjectUnsafe.getBufferBaseAddress(bb); + long addr1 = MemoryUtil.getAddress(bb); ByteBuffer bb2 = MappedHelper.newBuffer(addr1, bb.capacity()); - long addr2 = MappedObjectUnsafe.getBufferBaseAddress(bb); + long addr2 = MemoryUtil.getAddress(bb); System.out.println(bb); System.out.println(bb2); diff --git a/src/java/org/lwjgl/test/mapped/MappedObjectTests3.java b/src/java/org/lwjgl/test/mapped/MappedObjectTests3.java index 1f90eb8c..05e21bdb 100644 --- a/src/java/org/lwjgl/test/mapped/MappedObjectTests3.java +++ b/src/java/org/lwjgl/test/mapped/MappedObjectTests3.java @@ -31,7 +31,11 @@ */ package org.lwjgl.test.mapped; -import org.lwjgl.util.mapped.*; +import org.lwjgl.MemoryUtil; +import org.lwjgl.util.mapped.MappedObject; +import org.lwjgl.util.mapped.MappedSet; +import org.lwjgl.util.mapped.MappedSet2; +import org.lwjgl.util.mapped.MappedType; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -49,10 +53,10 @@ public class MappedObjectTests3 { assert (some.data != some.data); - long addr1 = MappedObjectUnsafe.getBufferBaseAddress(some.backingByteBuffer()); + long addr1 = MemoryUtil.getAddress(some.backingByteBuffer()); ByteBuffer mapped = some.data; // creates new ByteBuffer instance - long addr2 = MappedObjectUnsafe.getBufferBaseAddress(mapped); + long addr2 = MemoryUtil.getAddress(mapped); assert (addr2 - addr1 == 4); assert (mapped.capacity() == MappedSomething.SIZEOF - 4); @@ -60,7 +64,7 @@ public class MappedObjectTests3 { some.view++; mapped = some.data; // creates new ByteBuffer instance - long addr3 = MappedObjectUnsafe.getBufferBaseAddress(mapped); + long addr3 = MemoryUtil.getAddress(mapped); assert (addr3 - addr1 == 4 + MappedSomething.SIZEOF); assert (addr3 - addr2 == 0 + MappedSomething.SIZEOF); assert (mapped.capacity() == MappedSomething.SIZEOF - 4); @@ -87,7 +91,7 @@ public class MappedObjectTests3 { static void testConstructor() { int capacity = 1024; ByteBuffer bb = ByteBuffer.allocateDirect(capacity).order(ByteOrder.nativeOrder()); - long address = MappedObjectUnsafe.getBufferBaseAddress(bb); + long address = MemoryUtil.getAddress(bb); MappedFloat mf = MappedFloat.map(address, capacity); diff --git a/src/java/org/lwjgl/test/mapped/MappedObjectTests4.java b/src/java/org/lwjgl/test/mapped/MappedObjectTests4.java new file mode 100644 index 00000000..2b695f44 --- /dev/null +++ b/src/java/org/lwjgl/test/mapped/MappedObjectTests4.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2002-2011 LWJGL Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'LWJGL' nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.lwjgl.test.mapped; + +import org.lwjgl.MemoryUtil; +import org.lwjgl.opengl.Display; + +import java.io.File; +import java.nio.ByteBuffer; + +/** @author Riven */ +@SuppressWarnings("static-access") +public class MappedObjectTests4 { + + public static void testLWJGL() throws Exception { + System.out.println(new File(System.getProperty("java.library.path")).getCanonicalPath()); + Display.create(); + } + + public static void testLocalView() { + MappedFloat mapped1 = MappedFloat.malloc(5); + MappedFloat mapped2 = MappedFloat.malloc(5); + + testLocalView(mapped1); + testLocalView(mapped1, mapped2); + + MappedSomething some = MappedSomething.malloc(5); + testLocalView(some); + } + + private static void testLocalView(MappedFloat mapped1) { + final MappedFloat[] array = mapped1.asArray(); + + assert (array.length == 5); + + int l = 10 * array.length; + for ( int localView1 = 0; localView1 < 5; localView1++ ) { + array[localView1].value = localView1 * 5; + array[localView1].value *= 2.0f; + } + System.out.println(); + float x = 0.0f; + for ( int localView1 = 0; localView1 < 5; localView1++ ) { + System.out.println("[" + localView1 + "] =>" + array[localView1].value); + x += array[localView1].value; + } + System.out.println("x = " + x); + } + + private static void testLocalView(MappedFloat mo1, MappedFloat mo2) { + final MappedFloat[] array1 = mo1.asArray(); + for ( int v1 = 0; v1 < 5; v1++ ) { + array1[v1].value = (float)Math.random(); + array1[v1].value *= 2.0f; + } + final MappedFloat[] array2 = mo2.asArray(); + for ( int v2 = 0; v2 < 5; v2++ ) { + array2[v2].value = (float)Math.random(); + array2[v2].value *= 2.0f; + } + + System.out.println(); + + for ( int v1 = 0; v1 < 5; v1++ ) { + System.out.println("[" + v1 + "] =>" + array1[v1].value); + } + for ( int v2 = 0; v2 < 5; v2++ ) { + System.out.println("[" + v2 + "] =>" + array2[v2].value); + } + } + + private static void testLocalView(MappedSomething some) { + final MappedSomething[] array = some.asArray(); + + assert (array.length == 5); + + final long baseAddress = MemoryUtil.getAddress(some.backingByteBuffer()); + for ( int i = 0; i < array.length; i++ ) { + ByteBuffer data = array[i].data; + + assert (data.capacity() == (64 - 4)); + assert (MemoryUtil.getAddress(data) == baseAddress + i * MappedSomething.SIZEOF + 4); + } + } + +} \ No newline at end of file diff --git a/src/java/org/lwjgl/test/mapped/TestMappedObject.java b/src/java/org/lwjgl/test/mapped/TestMappedObject.java index e0110bd5..d8635812 100644 --- a/src/java/org/lwjgl/test/mapped/TestMappedObject.java +++ b/src/java/org/lwjgl/test/mapped/TestMappedObject.java @@ -71,6 +71,11 @@ public class TestMappedObject { MappedObjectTests3.testConstructor(); MappedObjectTests3.testMappedSet(); + MappedObjectTests4.testLocalView(); + + //MappedObjectTests4.testLWJGL(); + + System.out.println("done"); } diff --git a/src/java/org/lwjgl/test/opengl/sprites/SpriteShootoutMapped.java b/src/java/org/lwjgl/test/opengl/sprites/SpriteShootoutMapped.java new file mode 100644 index 00000000..5992cf6c --- /dev/null +++ b/src/java/org/lwjgl/test/opengl/sprites/SpriteShootoutMapped.java @@ -0,0 +1,831 @@ +/* + * Copyright (c) 2002-2011 LWJGL Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'LWJGL' nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.lwjgl.test.opengl.sprites; + +import org.lwjgl.BufferUtils; +import org.lwjgl.LWJGLException; +import org.lwjgl.Sys; +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.*; +import org.lwjgl.util.mapped.MappedObject; +import org.lwjgl.util.mapped.MappedObjectClassLoader; +import org.lwjgl.util.mapped.MappedObjectTransformer; +import org.lwjgl.util.mapped.MappedType; + +import java.awt.image.BufferedImage; +import java.awt.image.Raster; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Random; +import javax.imageio.ImageIO; + +import static org.lwjgl.opengl.EXTTransformFeedback.*; +import static org.lwjgl.opengl.GL11.*; +import static org.lwjgl.opengl.GL12.*; +import static org.lwjgl.opengl.GL15.*; +import static org.lwjgl.opengl.GL20.*; +import static org.lwjgl.opengl.GL30.*; + +/** + * Sprite rendering demo. Three implementations are supported: + * a) CPU animation + BufferData VBO update. + * b) CPU animation + MapBufferRange VBO update. + * c) GPU animation using transform feedback with a vertex shader. + * + * @author Spasi + * @since 18/3/2011 + */ +public final class SpriteShootoutMapped { + + static final int SCREEN_WIDTH = 800; + static final int SCREEN_HEIGHT = 600; + + private static final int ANIMATION_TICKS = 60; + + private boolean run = true; + private boolean render = true; + private boolean colorMask = true; + private boolean animate = true; + private boolean smooth; + private boolean vsync; + + int ballSize = 42; + int ballCount = 100 * 1000; + + private SpriteRenderer renderer; + + // OpenGL stuff + private int texID; + private int texBigID; + private int texSmallID; + + private SpriteShootoutMapped() { + } + + public static void main(String[] args) { + MappedObjectTransformer.register(Pixel4b.class); + MappedObjectTransformer.register(Pixel3b.class); + MappedObjectTransformer.register(Sprite.class); + MappedObjectTransformer.register(SpriteRender.class); + + if ( MappedObjectClassLoader.fork(SpriteShootoutMapped.class, args) ) + return; + + try { + new SpriteShootoutMapped().start(); + } catch (LWJGLException e) { + e.printStackTrace(); + } + } + + private void start() throws LWJGLException { + try { + initGL(); + + final ContextCapabilities caps = GLContext.getCapabilities(); + if ( !true && (caps.OpenGL30 || caps.GL_EXT_transform_feedback) ) + renderer = new SpriteRendererTF(); + else if ( true && caps.GL_ARB_map_buffer_range ) + renderer = new SpriteRendererMapped(); + else + renderer = new SpriteRendererPlain(); + + updateBalls(ballCount); + run(); + } catch (Throwable t) { + t.printStackTrace(); + } finally { + destroy(); + } + } + + private void initGL() throws LWJGLException { + Display.setLocation((Display.getDisplayMode().getWidth() - SCREEN_WIDTH) / 2, + (Display.getDisplayMode().getHeight() - SCREEN_HEIGHT) / 2); + Display.setDisplayMode(new DisplayMode(SCREEN_WIDTH, SCREEN_HEIGHT)); + Display.setTitle("Sprite Shootout"); + Display.create(); + //Display.create(new PixelFormat(), new ContextAttribs(4, 1).withProfileCompatibility(true).withDebug(true)); + //AMDDebugOutput.glDebugMessageCallbackAMD(new AMDDebugOutputCallback()); + + if ( !GLContext.getCapabilities().OpenGL20 ) + throw new RuntimeException("OpenGL 2.0 is required for this demo."); + + // Setup viewport + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, SCREEN_WIDTH, 0, SCREEN_HEIGHT, -1.0, 1.0); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + + glClearColor(1.0f, 1.0f, 1.0f, 0.0f); + + // Create textures + + try { + texSmallID = createTexture("res/ball_sm.png"); + texBigID = createTexture("res/ball.png"); + } catch (IOException e) { + e.printStackTrace(); + System.exit(-1); + } + texID = texBigID; + + // Setup rendering state + + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + glEnable(GL_ALPHA_TEST); + glAlphaFunc(GL_GREATER, 0.0f); + + glColorMask(colorMask, colorMask, colorMask, false); + glDepthMask(false); + glDisable(GL_DEPTH_TEST); + + // Setup geometry + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + Util.checkGLError(); + } + + private static int createTexture(final String path) throws IOException { + final BufferedImage img = ImageIO.read(SpriteShootoutMapped.class.getClassLoader().getResource(path)); + + final int w = img.getWidth(); + final int h = img.getHeight(); + + final ByteBuffer buffer = readImage(img); + + final int texID = glGenTextures(); + + glBindTexture(GL_TEXTURE_2D, texID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_BGRA, GL_UNSIGNED_BYTE, buffer); + + return texID; + } + + @MappedType(sizeof = 4) + public static class Pixel4b extends MappedObject { + + public byte r, g, b, a; + + } + + @MappedType(sizeof = 3, align = 3) + public static class Pixel3b extends MappedObject { + + public byte r, g, b; + + } + + private static ByteBuffer readImage(final BufferedImage img) throws IOException { + final Raster raster = img.getRaster(); + + final int bands = raster.getNumBands(); + + final int w = img.getWidth(); + final int h = img.getHeight(); + + final int count = w * h; + + final byte[] pixels = new byte[count * bands]; + raster.getDataElements(0, 0, w, h, pixels); + + if ( bands == 4 ) { + Pixel4b p = Pixel4b.malloc(count); + + int b = 0; + for ( int i = 0; i < count; i++, b += 4 ) { + // Pre-multiply alpha + final float a = unpackUByte01(pixels[b + 3]); + + p.view = i; + p.r = packUByte01(unpackUByte01(pixels[b + 2]) * a); + p.g = packUByte01(unpackUByte01(pixels[b + 1]) * a); + p.b = packUByte01(unpackUByte01(pixels[b + 0]) * a); + p.a = pixels[b + 3]; + } + + return p.backingByteBuffer(); + } else if ( bands == 3 ) { + Pixel3b p = Pixel3b.malloc(count); + + int b = 0; + for ( int i = 0; i < count; i++, b += 3 ) { + p.view = i; + p.r = pixels[b + 2]; + p.g = pixels[b + 1]; + p.b = pixels[b + 0]; + } + + return p.backingByteBuffer(); + } else { + ByteBuffer p = BufferUtils.createByteBuffer(count * bands); + p.put(pixels, 0, p.capacity()); + p.flip(); + return p; + } + + } + + private static float unpackUByte01(final byte x) { + return (x & 0xFF) / 255.0f; + } + + private static byte packUByte01(final float x) { + return (byte)(x * 255.0f); + } + + private void updateBalls(final int count) { + System.out.println("NUMBER OF BALLS: " + count); + renderer.updateBalls(ballCount); + } + + private void run() { + long startTime = System.currentTimeMillis() + 5000; + long fps = 0; + + long time = Sys.getTime(); + final int ticksPerUpdate = (int)(Sys.getTimerResolution() / ANIMATION_TICKS); + + renderer.render(false, true, 0); + + while ( run ) { + Display.processMessages(); + handleInput(); + + glClear(GL_COLOR_BUFFER_BIT); + + final long currTime = Sys.getTime(); + final int delta = (int)(currTime - time); + if ( smooth || delta >= ticksPerUpdate ) { + renderer.render(render, animate, delta); + time = currTime; + } else + renderer.render(render, false, 0); + + Display.update(false); + //Display.sync(60); + + if ( startTime > System.currentTimeMillis() ) { + fps++; + } else { + long timeUsed = 5000 + (startTime - System.currentTimeMillis()); + startTime = System.currentTimeMillis() + 5000; + System.out.println("FPS: " + (Math.round(fps / (timeUsed / 1000.0) * 10) / 10.0) + ", Balls: " + ballCount); + fps = 0; + } + } + } + + private void handleInput() { + if ( Display.isCloseRequested() ) + run = false; + + while ( Keyboard.next() ) { + if ( Keyboard.getEventKeyState() ) + continue; + + switch ( Keyboard.getEventKey() ) { + case Keyboard.KEY_1: + case Keyboard.KEY_2: + case Keyboard.KEY_3: + case Keyboard.KEY_4: + case Keyboard.KEY_5: + case Keyboard.KEY_6: + case Keyboard.KEY_7: + case Keyboard.KEY_8: + case Keyboard.KEY_9: + case Keyboard.KEY_0: + ballCount = 1 << (Keyboard.getEventKey() - Keyboard.KEY_1); + updateBalls(ballCount); + break; + case Keyboard.KEY_ADD: + case Keyboard.KEY_SUBTRACT: + int mult; + if ( Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT) ) + mult = 1000; + else if ( Keyboard.isKeyDown(Keyboard.KEY_LMENU) || Keyboard.isKeyDown(Keyboard.KEY_RMENU) ) + mult = 100; + else if ( Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL) ) + mult = 10; + else + mult = 1; + if ( Keyboard.getEventKey() == Keyboard.KEY_SUBTRACT ) + mult = -mult; + ballCount += mult * 100; + if ( ballCount <= 0 ) + ballCount = 1; + updateBalls(ballCount); + break; + case Keyboard.KEY_ESCAPE: + run = false; + break; + case Keyboard.KEY_A: + animate = !animate; + System.out.println("Animation is now " + (animate ? "on" : "off") + "."); + break; + case Keyboard.KEY_C: + colorMask = !colorMask; + glColorMask(colorMask, colorMask, colorMask, false); + System.out.println("Color mask is now " + (colorMask ? "on" : "off") + "."); + // Disable alpha test when color mask is off, else we get no benefit. + if ( colorMask ) { + glEnable(GL_BLEND); + glEnable(GL_ALPHA_TEST); + } else { + glDisable(GL_BLEND); + glDisable(GL_ALPHA_TEST); + } + break; + case Keyboard.KEY_R: + render = !render; + System.out.println("Rendering is now " + (render ? "on" : "off") + "."); + break; + case Keyboard.KEY_S: + smooth = !smooth; + System.out.println("Smooth animation is now " + (smooth ? "on" : "off") + "."); + break; + case Keyboard.KEY_T: + if ( texID == texBigID ) { + texID = texSmallID; + ballSize = 16; + } else { + texID = texBigID; + ballSize = 42; + } + renderer.updateBallSize(); + glBindTexture(GL_TEXTURE_2D, texID); + System.out.println("Now using the " + (texID == texBigID ? "big" : "small") + " texture."); + break; + case Keyboard.KEY_V: + vsync = !vsync; + Display.setVSyncEnabled(vsync); + System.out.println("VSYNC is now " + (vsync ? "enabled" : "disabled") + "."); + break; + } + } + + while ( Mouse.next() ) ; + } + + private void destroy() { + Display.destroy(); + } + + @MappedType(sizeof = 4 * 4) + public static class Sprite extends MappedObject { + + public float x, y; + public float dx, dy; + + } + + @MappedType(sizeof = 2 * 4) + public static class SpriteRender extends MappedObject { + + public float x, y; + + } + + private abstract class SpriteRenderer { + + protected Sprite sprites; + + protected int spriteCount; + + protected int vshID; + protected int progID; + + protected void createProgram() { + final int fshID = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fshID, "uniform sampler2D COLOR_MAP;\n" + + "void main(void) {\n" + + " gl_FragColor = texture2D(COLOR_MAP, gl_PointCoord);\n" + + "}"); + glCompileShader(fshID); + if ( glGetShader(fshID, GL_COMPILE_STATUS) == GL_FALSE ) { + System.out.println(glGetShaderInfoLog(fshID, glGetShader(fshID, GL_INFO_LOG_LENGTH))); + throw new RuntimeException("Failed to compile fragment shader."); + } + + progID = glCreateProgram(); + glAttachShader(progID, vshID); + glAttachShader(progID, fshID); + glLinkProgram(progID); + if ( glGetProgram(progID, GL_LINK_STATUS) == GL_FALSE ) { + System.out.println(glGetProgramInfoLog(progID, glGetProgram(progID, GL_INFO_LOG_LENGTH))); + throw new RuntimeException("Failed to link shader program."); + } + + glUseProgram(progID); + glUniform1i(glGetUniformLocation(progID, "COLOR_MAP"), 0); + + updateBallSize(); + + glEnableClientState(GL_VERTEX_ARRAY); + } + + public void updateBallSize() { + glPointSize(ballSize); + } + + public abstract void updateBalls(int count); + + protected abstract void render(boolean render, boolean animate, int delta); + + } + + private abstract class SpriteRendererBatched extends SpriteRenderer { + + protected static final int BALLS_PER_BATCH = 10 * 1000; + + SpriteRendererBatched() { + vshID = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vshID, "void main(void) {\n" + + " gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n" + + "}"); + glCompileShader(vshID); + if ( glGetShader(vshID, GL_COMPILE_STATUS) == GL_FALSE ) { + System.out.println(glGetShaderInfoLog(vshID, glGetShader(vshID, GL_INFO_LOG_LENGTH))); + throw new RuntimeException("Failed to compile vertex shader."); + } + + createProgram(); + } + + public void updateBalls(final int count) { + final Random random = new Random(); + + final Sprite newSprites = Sprite.malloc(count); + if ( sprites != null ) { + sprites.view = 0; + sprites.copyRange(newSprites, Math.min(count, spriteCount)); + } + + if ( count > spriteCount ) { + for ( int i = spriteCount; i < count; i++ ) { + newSprites.view = i; + + newSprites.x = (int)(random.nextFloat() * (SCREEN_WIDTH - ballSize) + ballSize * 0.5f); + newSprites.y = (int)(random.nextFloat() * (SCREEN_HEIGHT - ballSize) + ballSize * 0.5f); + newSprites.dx = random.nextFloat() * 0.4f - 0.2f; + newSprites.dy = random.nextFloat() * 0.4f - 0.2f; + } + } + + sprites = newSprites; + spriteCount = count; + } + + protected void animate( + final Sprite sprite, + final SpriteRender spriteRender, + final int ballSize, final int ballIndex, final int batchSize, final int delta + ) { + final float ballRadius = ballSize * 0.5f; + final float boundW = SCREEN_WIDTH - ballRadius; + final float boundH = SCREEN_HEIGHT - ballRadius; + + final Sprite[] sprites = sprite.asArray(); + final SpriteRender[] spritesRender = spriteRender.asArray(); + for ( int b = ballIndex, r = 0, len = (ballIndex + batchSize); b < len; b++, r++ ) { + float x = sprites[b].x; + float dx = sprites[b].dx; + + x += dx * delta; + if ( x < ballRadius ) { + x = ballRadius; + sprites[b].dx = -dx; + } else if ( x > boundW ) { + x = boundW; + sprites[b].dx = -dx; + } + sprites[b].x = x; + + float y = sprites[b].y; + float dy = sprites[b].dy; + + y += dy * delta; + if ( y < ballRadius ) { + y = ballRadius; + sprites[b].dy = -dy; + } else if ( y > boundH ) { + y = boundH; + sprites[b].dy = -dy; + } + sprites[b].y = y; + + spritesRender[r].x = x; + spritesRender[r].y = y; + } + } + + } + + private class SpriteRendererPlain extends SpriteRendererBatched { + + private final int DATA_PER_BATCH = BALLS_PER_BATCH * 2 * 4; // balls * 2 floats * 4 bytes + + protected int[] animVBO; + + private SpriteRender spritesRender; + + SpriteRendererPlain() { + System.out.println("Shootout Implementation: CPU animation & BufferData"); + spritesRender = SpriteRender.malloc(BALLS_PER_BATCH); + } + + public void updateBalls(final int count) { + super.updateBalls(count); + + final int batchCount = count / BALLS_PER_BATCH + (count % BALLS_PER_BATCH == 0 ? 0 : 1); + if ( animVBO != null && batchCount == animVBO.length ) + return; + + final int[] newAnimVBO = new int[batchCount]; + if ( animVBO != null ) { + System.arraycopy(animVBO, 0, newAnimVBO, 0, Math.min(animVBO.length, newAnimVBO.length)); + for ( int i = newAnimVBO.length; i < animVBO.length; i++ ) + glDeleteBuffers(animVBO[i]); + } + for ( int i = animVBO == null ? 0 : animVBO.length; i < newAnimVBO.length; i++ ) { + newAnimVBO[i] = glGenBuffers(); + glBindBuffer(GL_ARRAY_BUFFER, newAnimVBO[i]); + } + + animVBO = newAnimVBO; + } + + public void render(final boolean render, final boolean animate, final int delta) { + int batchSize = Math.min(ballCount, BALLS_PER_BATCH); + int ballIndex = 0; + int batchIndex = 0; + while ( ballIndex < ballCount ) { + glBindBuffer(GL_ARRAY_BUFFER, animVBO[batchIndex]); + + if ( animate ) + animate(ballIndex, batchSize, delta); + + if ( render ) { + glVertexPointer(2, GL_FLOAT, 0, 0); + glDrawArrays(GL_POINTS, 0, batchSize); + } + + ballIndex += batchSize; + batchSize = Math.min(ballCount - ballIndex, BALLS_PER_BATCH); + batchIndex++; + } + } + + private void animate(final int ballIndex, final int batchSize, final int delta) { + animate( + sprites, spritesRender, + ballSize, ballIndex, batchSize, delta + ); + + glBufferData(GL_ARRAY_BUFFER, DATA_PER_BATCH, GL_STREAM_DRAW); + glBufferSubData(GL_ARRAY_BUFFER, 0, spritesRender.backingByteBuffer()); + } + + } + + private class SpriteRendererMapped extends SpriteRendererBatched { + + private StreamVBO animVBO; + + SpriteRendererMapped() { + System.out.println("Shootout Implementation: CPU animation & MapBufferRange"); + } + + public void updateBalls(final int count) { + super.updateBalls(count); + + if ( animVBO != null ) + animVBO.destroy(); + + animVBO = new StreamVBO(GL_ARRAY_BUFFER, ballCount * (2 * 4)); + } + + public void render(final boolean render, final boolean animate, final int delta) { + int batchSize = Math.min(ballCount, BALLS_PER_BATCH); + int ballIndex = 0; + while ( ballIndex < ballCount ) { + if ( animate ) { + final ByteBuffer buffer = animVBO.map(batchSize * (2 * 4)); + + animate(sprites, SpriteRender.map(buffer), ballSize, ballIndex, batchSize, delta); + + animVBO.unmap(); + } + + if ( render ) { + glVertexPointer(2, GL_FLOAT, 0, ballIndex * (2 * 4)); + glDrawArrays(GL_POINTS, 0, batchSize); + } + + ballIndex += batchSize; + batchSize = Math.min(ballCount - ballIndex, BALLS_PER_BATCH); + } + } + + } + + private class SpriteRendererTF extends SpriteRenderer { + + private int progIDTF; + private int ballSizeLoc; + private int deltaLoc; + + private int[] tfVBO = new int[2]; + private int currVBO; + + SpriteRendererTF() { + System.out.println("Shootout Implementation: TF GPU animation"); + + // Transform-feedback program + + final int vshID = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vshID, "#version 130\n" + + "const float WIDTH = " + SCREEN_WIDTH + ";\n" + + "const float HEIGHT = " + SCREEN_HEIGHT + ";\n" + + "uniform float ballSize;\n" + // ballSize / 2 + "uniform float delta;\n" + + "void main(void) {\n" + + " vec4 anim = gl_Vertex;\n" + + " anim.xy = anim.xy + anim.zw * delta;\n" + + " vec2 animC = clamp(anim.xy, vec2(ballSize), vec2(WIDTH - ballSize, HEIGHT - ballSize));\n" + + " if ( anim.x != animC.x ) anim.z = -anim.z;\n" + + " if ( anim.y != animC.y ) anim.w = -anim.w;\n" + + " gl_Position = vec4(animC, anim.zw);\n" + + "}"); + glCompileShader(vshID); + if ( glGetShader(vshID, GL_COMPILE_STATUS) == GL_FALSE ) { + System.out.println(glGetShaderInfoLog(vshID, glGetShader(vshID, GL_INFO_LOG_LENGTH))); + throw new RuntimeException("Failed to compile vertex shader."); + } + + progIDTF = glCreateProgram(); + glAttachShader(progIDTF, vshID); + glTransformFeedbackVaryings(progIDTF, new CharSequence[] { "gl_Position" }, GL_SEPARATE_ATTRIBS); + glLinkProgram(progIDTF); + if ( glGetProgram(progIDTF, GL_LINK_STATUS) == GL_FALSE ) { + System.out.println(glGetProgramInfoLog(progIDTF, glGetProgram(progIDTF, GL_INFO_LOG_LENGTH))); + throw new RuntimeException("Failed to link shader program."); + } + + glUseProgram(progIDTF); + + ballSizeLoc = glGetUniformLocation(progIDTF, "ballSize"); + deltaLoc = glGetUniformLocation(progIDTF, "delta"); + + glUniform1f(ballSizeLoc, ballSize * 0.5f); + + // ----------------- + + this.vshID = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(this.vshID, "void main(void) {\n" + + " gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n" + + "}"); + glCompileShader(this.vshID); + if ( glGetShader(this.vshID, GL_COMPILE_STATUS) == GL_FALSE ) { + System.out.println(glGetShaderInfoLog(this.vshID, glGetShader(this.vshID, GL_INFO_LOG_LENGTH))); + throw new RuntimeException("Failed to compile vertex shader."); + } + + createProgram(); + } + + public void updateBallSize() { + glUseProgram(progIDTF); + glUniform1f(ballSizeLoc, ballSize * 0.5f); + + glUseProgram(progID); + super.updateBallSize(); + } + + private void doUpdateBalls(final int count) { + final Random random = new Random(); + + final Sprite newSprites = Sprite.malloc(count); + if ( sprites != null ) { + sprites.view = 0; + sprites.copyRange(newSprites, Math.min(count, spriteCount)); + } + + if ( count > spriteCount ) { + for ( int i = spriteCount; i < count; i++ ) { + newSprites.view = i; + + newSprites.x = (int)(random.nextFloat() * (SCREEN_WIDTH - ballSize) + ballSize * 0.5f); + newSprites.y = (int)(random.nextFloat() * (SCREEN_HEIGHT - ballSize) + ballSize * 0.5f); + newSprites.dx = random.nextFloat() * 0.4f - 0.2f; + newSprites.dy = random.nextFloat() * 0.4f - 0.2f; + } + } + + sprites = newSprites; + spriteCount = count; + } + + public void updateBalls(final int count) { + if ( tfVBO[0] != 0 ) { + // Fetch current animation state + glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sprites.backingByteBuffer()); + } + + doUpdateBalls(count); + + if ( tfVBO[0] != 0 ) { + for ( int i = 0; i < tfVBO.length; i++ ) + glDeleteBuffers(tfVBO[i]); + } + + for ( int i = 0; i < tfVBO.length; i++ ) { + tfVBO[i] = glGenBuffers(); + glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, tfVBO[i]); + glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, sprites.backingByteBuffer(), GL_STATIC_DRAW); + } + + glBindBuffer(GL_ARRAY_BUFFER, tfVBO[0]); + glVertexPointer(2, GL_FLOAT, (4 * 4), 0); + } + + public void render(final boolean render, final boolean animate, final int delta) { + if ( animate ) { + glUseProgram(progIDTF); + glUniform1f(deltaLoc, delta); + + final int vbo = currVBO; + currVBO = 1 - currVBO; + + glBindBuffer(GL_ARRAY_BUFFER, tfVBO[vbo]); + glVertexPointer(4, GL_FLOAT, 0, 0); + + glEnable(GL_RASTERIZER_DISCARD); + if ( GLContext.getCapabilities().OpenGL30 ) { + glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tfVBO[1 - vbo]); + + glBeginTransformFeedback(GL_POINTS); + glDrawArrays(GL_POINTS, 0, ballCount); + glEndTransformFeedback(); + } else { + glBindBufferBaseEXT(GL_TRANSFORM_FEEDBACK_BUFFER_EXT, 0, tfVBO[1 - vbo]); + + glBeginTransformFeedbackEXT(GL_POINTS); + glDrawArrays(GL_POINTS, 0, ballCount); + glEndTransformFeedbackEXT(); + } + glDisable(GL_RASTERIZER_DISCARD); + + glUseProgram(progID); + glVertexPointer(2, GL_FLOAT, (4 * 4), 0); + } + + if ( render ) + glDrawArrays(GL_POINTS, 0, ballCount); + } + + } + +} \ No newline at end of file diff --git a/src/java/org/lwjgl/util/mapped/MappedHelper.java b/src/java/org/lwjgl/util/mapped/MappedHelper.java index 2b9b5ada..bc3b4152 100644 --- a/src/java/org/lwjgl/util/mapped/MappedHelper.java +++ b/src/java/org/lwjgl/util/mapped/MappedHelper.java @@ -31,6 +31,9 @@ */ package org.lwjgl.util.mapped; +import org.lwjgl.LWJGLUtil; +import org.lwjgl.MemoryUtil; + import java.nio.ByteBuffer; /** @@ -43,30 +46,32 @@ import java.nio.ByteBuffer; public class MappedHelper { public static void setup(MappedObject mo, ByteBuffer buffer, int align, int sizeof) { - if ( mo.baseAddress != 0L ) + if ( LWJGLUtil.CHECKS && mo.baseAddress != 0L ) throw new IllegalStateException("this method should not be called by user-code"); - if ( buffer == null ) - throw new NullPointerException("buffer"); - if ( !buffer.isDirect() ) + if ( LWJGLUtil.CHECKS && !buffer.isDirect() ) throw new IllegalArgumentException("bytebuffer must be direct"); mo.preventGC = buffer; - if ( align <= 0 ) + if ( LWJGLUtil.CHECKS && align <= 0 ) throw new IllegalArgumentException("invalid alignment"); mo.align = align; - if ( sizeof % align != 0 ) + if ( LWJGLUtil.CHECKS && (sizeof <= 0 || sizeof % align != 0) ) throw new IllegalStateException("sizeof not a multiple of alignment"); mo.sizeof = sizeof; - long addr = MappedObjectUnsafe.getBufferBaseAddress(buffer) + buffer.position(); - if ( addr % align != 0 ) + long addr = MemoryUtil.getAddress(buffer); + if ( LWJGLUtil.CHECKS && addr % align != 0 ) throw new IllegalStateException("buffer address not aligned on " + align + " bytes"); mo.baseAddress = mo.viewAddress = addr; } + public static void checkAddress(MappedObject mapped, long viewAddress) { + mapped.checkAddress(viewAddress); + } + public static void put_views(MappedSet2 set, int view) { set.view(view); } @@ -87,6 +92,14 @@ public class MappedHelper { return (int)(mapped.viewAddress - mapped.baseAddress) / sizeof; } + public static void put_view_shift(MappedObject mapped, int view, int sizeof_shift) { + mapped.setViewAddress(mapped.baseAddress + (view << sizeof_shift)); + } + + public static int get_view_shift(MappedObject mapped, int sizeof_shift) { + return ((int)(mapped.viewAddress - mapped.baseAddress)) >> sizeof_shift; + } + public static void put_view_next(MappedObject mapped, int sizeof) { mapped.setViewAddress(mapped.viewAddress + sizeof); } @@ -130,38 +143,70 @@ public class MappedHelper { MappedObjectUnsafe.INSTANCE.putByte(addr, value); } + public static void bput(MappedObject mapped, byte value, int fieldOffset) { + MappedObjectUnsafe.INSTANCE.putByte(mapped.viewAddress + fieldOffset, value); + } + public static byte bget(long addr) { return MappedObjectUnsafe.INSTANCE.getByte(addr); } + public static byte bget(MappedObject mapped, int fieldOffset) { + return MappedObjectUnsafe.INSTANCE.getByte(mapped.viewAddress + fieldOffset); + } + // short public static void sput(short value, long addr) { MappedObjectUnsafe.INSTANCE.putShort(addr, value); } + public static void sput(MappedObject mapped, short value, int fieldOffset) { + MappedObjectUnsafe.INSTANCE.putShort(mapped.viewAddress + fieldOffset, value); + } + public static short sget(long addr) { return MappedObjectUnsafe.INSTANCE.getShort(addr); } + public static short sget(MappedObject mapped, int fieldOffset) { + return MappedObjectUnsafe.INSTANCE.getShort(mapped.viewAddress + fieldOffset); + } + // char public static void cput(char value, long addr) { MappedObjectUnsafe.INSTANCE.putChar(addr, value); } + public static void cput(MappedObject mapped, char value, int fieldOffset) { + MappedObjectUnsafe.INSTANCE.putChar(mapped.viewAddress + fieldOffset, value); + } + public static char cget(long addr) { return MappedObjectUnsafe.INSTANCE.getChar(addr); } + public static char cget(MappedObject mapped, int fieldOffset) { + return MappedObjectUnsafe.INSTANCE.getChar(mapped.viewAddress + fieldOffset); + } + // int public static void iput(int value, long addr) { MappedObjectUnsafe.INSTANCE.putInt(addr, value); } - public static int iget(long addr) { - return MappedObjectUnsafe.INSTANCE.getInt(addr); + public static void iput(MappedObject mapped, int value, int fieldOffset) { + MappedObjectUnsafe.INSTANCE.putInt(mapped.viewAddress + fieldOffset, value); + } + + public static int iget(long address) { + return MappedObjectUnsafe.INSTANCE.getInt(address); + } + + public static int iget(MappedObject mapped, int fieldOffset) { + return MappedObjectUnsafe.INSTANCE.getInt(mapped.viewAddress + fieldOffset); } // float @@ -170,28 +215,52 @@ public class MappedHelper { MappedObjectUnsafe.INSTANCE.putFloat(addr, value); } + public static void fput(MappedObject mapped, float value, int fieldOffset) { + MappedObjectUnsafe.INSTANCE.putFloat(mapped.viewAddress + fieldOffset, value); + } + public static float fget(long addr) { return MappedObjectUnsafe.INSTANCE.getFloat(addr); } + public static float fget(MappedObject mapped, int fieldOffset) { + return MappedObjectUnsafe.INSTANCE.getFloat(mapped.viewAddress + fieldOffset); + } + // long public static void jput(long value, long addr) { MappedObjectUnsafe.INSTANCE.putLong(addr, value); } + public static void jput(MappedObject mapped, long value, int fieldOffset) { + MappedObjectUnsafe.INSTANCE.putLong(mapped.viewAddress + fieldOffset, value); + } + public static long jget(long addr) { return MappedObjectUnsafe.INSTANCE.getLong(addr); } + public static long lget(MappedObject mapped, int fieldOffset) { + return MappedObjectUnsafe.INSTANCE.getLong(mapped.viewAddress + fieldOffset); + } + // double public static void dput(double value, long addr) { MappedObjectUnsafe.INSTANCE.putDouble(addr, value); } + public static void dput(MappedObject mapped, double value, int fieldOffset) { + MappedObjectUnsafe.INSTANCE.putDouble(mapped.viewAddress + fieldOffset, value); + } + public static double dget(long addr) { return MappedObjectUnsafe.INSTANCE.getDouble(addr); } + public static double dget(MappedObject mapped, int fieldOffset) { + return MappedObjectUnsafe.INSTANCE.getDouble(mapped.viewAddress + fieldOffset); + } + } \ No newline at end of file diff --git a/src/java/org/lwjgl/util/mapped/MappedObject.java b/src/java/org/lwjgl/util/mapped/MappedObject.java index 9d3a93b0..6760b12d 100644 --- a/src/java/org/lwjgl/util/mapped/MappedObject.java +++ b/src/java/org/lwjgl/util/mapped/MappedObject.java @@ -32,6 +32,7 @@ package org.lwjgl.util.mapped; import org.lwjgl.LWJGLUtil; +import org.lwjgl.MemoryUtil; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; @@ -93,7 +94,7 @@ public class MappedObject { } final void checkAddress(final long address) { - final long base = MappedObjectUnsafe.getBufferBaseAddress(preventGC); + final long base = MemoryUtil.getAddress0(preventGC); final int offset = (int)(address - base); if ( address < base || preventGC.capacity() < (offset + this.sizeof) ) throw new IndexOutOfBoundsException(Integer.toString(offset / sizeof)); @@ -103,7 +104,7 @@ public class MappedObject { if ( bytes < 0 ) throw new IllegalArgumentException(); - if ( preventGC.capacity() < (viewAddress - MappedObjectUnsafe.getBufferBaseAddress(preventGC) + bytes) ) + if ( preventGC.capacity() < (viewAddress - MemoryUtil.getAddress0(preventGC) + bytes) ) throw new BufferOverflowException(); } @@ -155,7 +156,7 @@ public class MappedObject { /** * Creates an identical new MappedObject instance, comparable to the - * contract of {@link ByteBuffer#duplicate}. This is useful when more than one + * contract of {@link java.nio.ByteBuffer#duplicate}. This is useful when more than one * views of the mapped object are required at the same time, e.g. in * multithreaded access. */ @@ -166,7 +167,7 @@ public class MappedObject { /** * Creates a new MappedObject instance, with a base offset equal to - * the offset of the current view, comparable to the contract of {@link ByteBuffer#slice}. + * the offset of the current view, comparable to the contract of {@link java.nio.ByteBuffer#slice}. */ public final T slice() { // any method that calls this method will have its call-site modified @@ -188,6 +189,11 @@ public class MappedObject { throw new InternalError("type not registered"); } + /** Moves the current view to the next element. Non-transformed implementation for MappedSets. */ + final void nextSet() { + setViewAddress(viewAddress + sizeof); + } + /** * Copies and amount of SIZEOF bytes, from the current * mapped object, to the specified mapped object. @@ -220,10 +226,16 @@ public class MappedObject { return new MappedForeach(mapped, elementCount); } + @SuppressWarnings("unused") + public final T[] asArray() { + // any method that calls this method will have its call-site modified + throw new InternalError("type not registered"); + } + ByteBuffer preventGC; /** - * Returns the {@link ByteBuffer} that backs this mapped object. + * Returns the {@link java.nio.ByteBuffer} that backs this mapped object. * * @return the backing buffer */ diff --git a/src/java/org/lwjgl/util/mapped/MappedObjectClassLoader.java b/src/java/org/lwjgl/util/mapped/MappedObjectClassLoader.java index 0452937f..feec2e7d 100644 --- a/src/java/org/lwjgl/util/mapped/MappedObjectClassLoader.java +++ b/src/java/org/lwjgl/util/mapped/MappedObjectClassLoader.java @@ -117,7 +117,7 @@ public class MappedObjectClassLoader extends URLClassLoader { byte[] bytecode = readStream(this.getResourceAsStream(className.concat(".class"))); long t0 = System.nanoTime(); - bytecode = MappedObjectTransformer.transformFieldAccess(className, bytecode); + bytecode = MappedObjectTransformer.transformMappedAPI(className, bytecode); long t1 = System.nanoTime(); total_time_transforming += (t1 - t0); diff --git a/src/java/org/lwjgl/util/mapped/MappedObjectTransformer.java b/src/java/org/lwjgl/util/mapped/MappedObjectTransformer.java index c257720c..305b2ef8 100644 --- a/src/java/org/lwjgl/util/mapped/MappedObjectTransformer.java +++ b/src/java/org/lwjgl/util/mapped/MappedObjectTransformer.java @@ -1,21 +1,51 @@ /* - * Created on Jun 23, 2011 + * Copyright (c) 2002-2011 LWJGL Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'LWJGL' nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - package org.lwjgl.util.mapped; import org.lwjgl.LWJGLUtil; import org.objectweb.asm.*; +import org.objectweb.asm.tree.*; +import org.objectweb.asm.tree.analysis.*; import org.objectweb.asm.util.TraceClassVisitor; -import java.io.*; +import java.io.PrintWriter; +import java.io.StringWriter; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; -import java.util.StringTokenizer; +import static org.objectweb.asm.ClassWriter.*; import static org.objectweb.asm.Opcodes.*; /** @@ -25,7 +55,7 @@ import static org.objectweb.asm.Opcodes.*; * The transformer supports some debugging tools, enabled through JVM system properties:
* org.lwjgl.util.mapped.PrintTiming=true, prints timing information for the transformation step.
* org.lwjgl.util.mapped.PrintActivity=true, prints activity information.
- * org.lwjgl.util.mapped.PrintBytecode=true, prints the transformed bytecode. [not working atm]
+ * org.lwjgl.util.mapped.PrintBytecode=true, prints the transformed bytecode.
* org.lwjgl.util.Debug must also be set to true for the above to work. * * @author Riven @@ -41,7 +71,24 @@ public class MappedObjectTransformer { static final String MAPPED_OBJECT_JVM = jvmClassName(MappedObject.class); static final String MAPPED_HELPER_JVM = jvmClassName(MappedHelper.class); + static final String MAPPEDSET_PREFIX = jvmClassName(MappedSet.class); + static final String MAPPED_SET2_JVM = jvmClassName(MappedSet2.class); + static final String MAPPED_SET3_JVM = jvmClassName(MappedSet3.class); + static final String MAPPED_SET4_JVM = jvmClassName(MappedSet4.class); + + static final String LENGTH_METHOD_NAME = "length$LWJGL"; + static final String VIEWADDRESS_METHOD_NAME = "getViewAddress$LWJGL"; + static final String VIEW_CONSTRUCTOR_NAME = "constructView$LWJGL"; + + static final Map OPCODE_TO_NAME = new HashMap(); + static final Map INSNTYPE_TO_NAME = new HashMap(); + + static boolean is_currently_computing_frames; + static { + getClassEnums(Opcodes.class, OPCODE_TO_NAME, "V1_", "ACC_", "T_", "F_", "MH_"); + getClassEnums(AbstractInsnNode.class, INSNTYPE_TO_NAME); + className_to_subtype = new HashMap(); { @@ -59,11 +106,10 @@ public class MappedObjectTransformer { // => IADD // => PUTFIELD MyMappedType.view // - MappedSubtypeInfo info = new MappedSubtypeInfo(MAPPED_OBJECT_JVM, -1, -1); - className_to_subtype.put(info.className, info); + className_to_subtype.put(MAPPED_OBJECT_JVM, new MappedSubtypeInfo(MAPPED_OBJECT_JVM, -1, -1)); } - String vmName = System.getProperty("java.vm.name"); + final String vmName = System.getProperty("java.vm.name"); if ( vmName != null && !vmName.contains("Server") ) { System.err.println("Warning: " + MappedObject.class.getSimpleName() + "s have inferiour performance on Client VMs, please consider switching to a Server VM."); } @@ -71,7 +117,7 @@ public class MappedObjectTransformer { /** * Registers a class as a mapped object. - * The class must extend {@link MappedObject} and be annotated with {@link MappedField}. + * The class must extend {@link org.lwjgl.util.mapped.MappedObject} and be annotated with {@link org.lwjgl.util.mapped.MappedField}. * * @param type the mapped object class. */ @@ -79,131 +125,87 @@ public class MappedObjectTransformer { if ( MappedObjectClassLoader.FORKED ) return; - MappedType mapped = type.getAnnotation(MappedType.class); + final MappedType mapped = type.getAnnotation(MappedType.class); if ( mapped == null ) throw new InternalError("missing " + MappedType.class.getName() + " annotation"); if ( type.getEnclosingClass() != null && !Modifier.isStatic(type.getModifiers()) ) throw new InternalError("only top-level or static inner classes are allowed"); - String className = jvmClassName(type); - - MappedSubtypeInfo mappedType = new MappedSubtypeInfo(className, mapped.sizeof(), mapped.align()); + final MappedSubtypeInfo mappedType = new MappedSubtypeInfo(jvmClassName(type), mapped.sizeof(), mapped.align()); int advancingOffset = 0; + for ( Field field : type.getDeclaredFields() ) + advancingOffset += registerField(mapped, mappedType.className, mappedType, advancingOffset, field); - for ( Field field : type.getDeclaredFields() ) { - // static fields are never mapped - if ( Modifier.isStatic(field.getModifiers()) ) - continue; - - // we only support primitives and ByteBuffers - if ( !field.getType().isPrimitive() && field.getType() != ByteBuffer.class ) - throw new InternalError("field '" + className + "." + field.getName() + "' not supported: " + field.getType()); - - MappedField meta = field.getAnnotation(MappedField.class); - if ( meta == null && !mapped.autoGenerateOffsets() ) - throw new InternalError("field '" + className + "." + field.getName() + "' missing annotation " + MappedField.class.getName() + ": " + className); - - // quick hack - long byteOffset = meta == null ? advancingOffset : meta.byteOffset(); - long byteLength; - if ( field.getType() == long.class || field.getType() == double.class ) - byteLength = 8; - else if ( field.getType() == int.class || field.getType() == float.class ) - byteLength = 4; - else if ( field.getType() == char.class || field.getType() == short.class ) - byteLength = 2; - else if ( field.getType() == byte.class ) - byteLength = 1; - else if ( field.getType() == ByteBuffer.class ) { - byteLength = meta.byteLength(); - if ( byteLength < 0 ) - throw new IllegalStateException("invalid byte length for mapped ByteBuffer field: " + className + "." + field.getName() + " [length=" + byteLength + "]"); - } else - throw new IllegalStateException(field.getType().getName()); - - if ( field.getType() != ByteBuffer.class ) - if ( (advancingOffset % byteLength) != 0 ) - throw new IllegalStateException("misaligned mapped type: " + className + "." + field.getName()); - - if ( PRINT_ACTIVITY ) - LWJGLUtil.log(MappedObjectTransformer.class.getSimpleName() + ": " + className + "." + field.getName() + " [type=" + field.getType().getSimpleName() + ", offset=" + byteOffset + "]"); - - mappedType.fieldToOffset.put(field.getName(), byteOffset); - mappedType.fieldToLength.put(field.getName(), byteLength); - - advancingOffset += byteLength; - } - - if ( className_to_subtype.put(className, mappedType) != null ) { - throw new InternalError("duplicate mapped type: " + className); - } + if ( className_to_subtype.put(mappedType.className, mappedType) != null ) + throw new InternalError("duplicate mapped type: " + mappedType.className); } - static boolean is_currently_computing_frames = false; - static final String view_constructor_method = "_construct_view_"; + private static int registerField(final MappedType mapped, final String className, final MappedSubtypeInfo mappedType, int advancingOffset, final Field field) { + if ( Modifier.isStatic(field.getModifiers()) ) // static fields are never mapped + return 0; - static byte[] transformFieldAccess(final String className, byte[] bytecode) { - int flags = ClassWriter.COMPUTE_FRAMES; + // we only support primitives and ByteBuffers + if ( !field.getType().isPrimitive() && field.getType() != ByteBuffer.class ) + throw new InternalError("field '" + className + "." + field.getName() + "' not supported: " + field.getType()); - ClassWriter writer = new ClassWriter(flags) { - // HACK: prevent user-code static-initialization-blocks to be executed + MappedField meta = field.getAnnotation(MappedField.class); + if ( meta == null && !mapped.autoGenerateOffsets() ) + throw new InternalError("field '" + className + "." + field.getName() + "' missing annotation " + MappedField.class.getName() + ": " + className); + + // quick hack + long byteOffset = meta == null ? advancingOffset : meta.byteOffset(); + long byteLength; + if ( field.getType() == long.class || field.getType() == double.class ) + byteLength = 8; + else if ( field.getType() == int.class || field.getType() == float.class ) + byteLength = 4; + else if ( field.getType() == char.class || field.getType() == short.class ) + byteLength = 2; + else if ( field.getType() == byte.class ) + byteLength = 1; + else if ( field.getType() == ByteBuffer.class ) { + byteLength = meta.byteLength(); + if ( byteLength < 0 ) + throw new IllegalStateException("invalid byte length for mapped ByteBuffer field: " + className + "." + field.getName() + " [length=" + byteLength + "]"); + } else + throw new InternalError(field.getType().getName()); + + if ( field.getType() != ByteBuffer.class && (advancingOffset % byteLength) != 0 ) + throw new IllegalStateException("misaligned mapped type: " + className + "." + field.getName()); + + if ( PRINT_ACTIVITY ) + LWJGLUtil.log(MappedObjectTransformer.class.getSimpleName() + ": " + className + "." + field.getName() + " [type=" + field.getType().getSimpleName() + ", offset=" + byteOffset + "]"); + + mappedType.fieldToOffset.put(field.getName(), byteOffset); + mappedType.fieldToLength.put(field.getName(), byteLength); + mappedType.fieldToType.put(field.getName(), Type.getType(field.getType())); + + return (int)byteLength; + } + + static byte[] transformMappedAPI(final String className, byte[] bytecode) { + final ClassWriter cw = new ClassWriter(COMPUTE_FRAMES) { @Override protected String getCommonSuperClass(String a, String b) { - if ( is_currently_computing_frames ) - if ( !a.startsWith("java/") || !b.startsWith("java/") ) - return "java/lang/Object"; + // HACK: prevent user-code static-initialization-blocks to be executed + if ( is_currently_computing_frames && !a.startsWith("java/") || !b.startsWith("java/") ) + return "java/lang/Object"; + return super.getCommonSuperClass(a, b); } + }; - ClassAdapter adapter = new ClassAdapter(writer) { - @Override - public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - { - MappedSubtypeInfo mappedSubtype = className_to_subtype.get(className); + ClassVisitor cv = getTransformationAdapter(className, cw); + if ( className_to_subtype.containsKey(className) ) // Do a first pass to generate address getters + cv = getMethodGenAdapter(className, cv); - if ( mappedSubtype != null && !mappedSubtype.className.equals(MAPPED_OBJECT_JVM) ) { - if ( "".equals(name) ) { - if ( !"()V".equals(desc) ) - throw new IllegalStateException(className + " can only have a default constructor, found: " + desc); - - MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); - mv.visitCode(); - mv.visitVarInsn(ALOAD, 0); - mv.visitMethodInsn(INVOKESPECIAL, MAPPED_OBJECT_JVM, "", "()V"); - mv.visitInsn(RETURN); - mv.visitMaxs(1, 1); - mv.visitEnd(); - - // put the method body in another method - name = view_constructor_method; - } - } - } - - return new MappedInstanceMethodAdapter(className, name, desc, super.visitMethod(access, name, desc, signature, exceptions)); - } - - @Override - public FieldVisitor visitField(int access, String fieldName, String typeName, String dunno, Object value) { - // remove redirected fields - MappedSubtypeInfo mappedSubtype = className_to_subtype.get(className); - - if ( mappedSubtype != null && mappedSubtype.fieldToOffset.containsKey(fieldName) ) { - if ( PRINT_ACTIVITY ) - LWJGLUtil.log(MappedObjectTransformer.class.getSimpleName() + ": discarding field: " + className + "." + fieldName + ":" + typeName); - return null; - } - - return super.visitField(access, fieldName, typeName, dunno, value); - } - }; - - new ClassReader(bytecode).accept(adapter, 0); - bytecode = writer.toByteArray(); + //cr.accept(cv, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + new ClassReader(bytecode).accept(cv, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + bytecode = cw.toByteArray(); if ( PRINT_BYTECODE ) printBytecode(bytecode); @@ -211,6 +213,897 @@ public class MappedObjectTransformer { return bytecode; } + private static ClassAdapter getMethodGenAdapter(final String className, final ClassVisitor cv) { + return new ClassAdapter(cv) { + + @Override + public void visitEnd() { + generateViewAddressGetter(); + generateLengthGetter(); + + final MappedSubtypeInfo mappedSubtype = className_to_subtype.get(className); + + for ( String fieldName : mappedSubtype.fieldToOffset.keySet() ) { + final Type type = mappedSubtype.fieldToType.get(fieldName); + + if ( type.getDescriptor().length() > 1 ) { // ByteBuffer, getter only + generateByteBufferGetter(mappedSubtype, fieldName, type); + } else { + generateFieldGetter(mappedSubtype, fieldName, type); + generateFieldSetter(mappedSubtype, fieldName, type); + } + } + + super.visitEnd(); + } + + private void generateViewAddressGetter() { + MethodVisitor mv = super.visitMethod(ACC_PUBLIC | ACC_STATIC, VIEWADDRESS_METHOD_NAME, "(L" + className + ";I)J", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, MAPPED_OBJECT_JVM, "baseAddress", "J"); + mv.visitVarInsn(ILOAD, 1); + mv.visitFieldInsn(GETSTATIC, className, "SIZEOF", "I"); + mv.visitInsn(IMUL); + mv.visitInsn(I2L); + mv.visitInsn(LADD); + if ( MappedObject.CHECKS ) { + mv.visitVarInsn(LSTORE, 2); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(LLOAD, 2); + mv.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, "checkAddress", "(L" + MAPPED_OBJECT_JVM + ";J)V"); + mv.visitVarInsn(LLOAD, 2); + } + mv.visitInsn(LRETURN); + if ( MappedObject.CHECKS ) + mv.visitMaxs(3, 4); + else + mv.visitMaxs(3, 2); + mv.visitEnd(); + } + + private void generateLengthGetter() { + MethodVisitor mv = super.visitMethod(ACC_PUBLIC | ACC_STATIC, LENGTH_METHOD_NAME, "(L" + className + ";)I", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKEVIRTUAL, className, "backingByteBuffer", "()L" + jvmClassName(ByteBuffer.class) + ";"); + mv.visitMethodInsn(INVOKEVIRTUAL, jvmClassName(ByteBuffer.class), "capacity", "()I"); + mv.visitFieldInsn(GETSTATIC, className, "SIZEOF", "I"); + mv.visitInsn(IDIV); + mv.visitInsn(IRETURN); + mv.visitMaxs(2, 1); + mv.visitEnd(); + } + + private void generateByteBufferGetter(final MappedSubtypeInfo mappedSubtype, final String fieldName, final Type type) { + MethodVisitor mv = super.visitMethod(ACC_PUBLIC | ACC_STATIC, getterName(fieldName), "(L" + className + ";I)" + type.getDescriptor(), null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ILOAD, 1); + mv.visitMethodInsn(INVOKESTATIC, className, VIEWADDRESS_METHOD_NAME, "(L" + className + ";I)J"); + visitIntNode(mv, mappedSubtype.fieldToOffset.get(fieldName).intValue()); + mv.visitInsn(I2L); + mv.visitInsn(LADD); + visitIntNode(mv, mappedSubtype.fieldToLength.get(fieldName).intValue()); + mv.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, "newBuffer", "(JI)L" + jvmClassName(ByteBuffer.class) + ";"); + mv.visitInsn(ARETURN); + mv.visitMaxs(4, 2); + mv.visitEnd(); + } + + private void generateFieldGetter(final MappedSubtypeInfo mappedSubtype, final String fieldName, final Type type) { + MethodVisitor mv = super.visitMethod(ACC_PUBLIC | ACC_STATIC, getterName(fieldName), "(L" + className + ";I)" + type.getDescriptor(), null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ILOAD, 1); + mv.visitMethodInsn(INVOKESTATIC, className, VIEWADDRESS_METHOD_NAME, "(L" + className + ";I)J"); + visitIntNode(mv, mappedSubtype.fieldToOffset.get(fieldName).intValue()); + mv.visitInsn(I2L); + mv.visitInsn(LADD); + mv.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, type.getDescriptor().toLowerCase() + "get", "(J)" + type.getDescriptor()); + mv.visitInsn(type.getOpcode(IRETURN)); + mv.visitMaxs(2, 2); + mv.visitEnd(); + } + + private void generateFieldSetter(final MappedSubtypeInfo mappedSubtype, final String fieldName, final Type type) { + MethodVisitor mv = super.visitMethod(ACC_PUBLIC | ACC_STATIC, setterName(fieldName), "(L" + className + ";I" + type.getDescriptor() + ")V", null, null); + mv.visitCode(); + int load = 0; + switch ( type.getSort() ) { + case Type.BOOLEAN: + case Type.CHAR: + case Type.BYTE: + case Type.SHORT: + case Type.INT: + load = ILOAD; + break; + case Type.FLOAT: + load = FLOAD; + break; + case Type.LONG: + load = LLOAD; + break; + case Type.DOUBLE: + load = DLOAD; + break; + } + mv.visitVarInsn(load, 2); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ILOAD, 1); + mv.visitMethodInsn(INVOKESTATIC, className, VIEWADDRESS_METHOD_NAME, "(L" + className + ";I)J"); + visitIntNode(mv, mappedSubtype.fieldToOffset.get(fieldName).intValue()); + mv.visitInsn(I2L); + mv.visitInsn(LADD); + mv.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, type.getDescriptor().toLowerCase() + "put", "(" + type.getDescriptor() + "J)V"); + mv.visitInsn(RETURN); + mv.visitMaxs(3, 3); + mv.visitEnd(); + } + + }; + } + + private static ClassAdapter getTransformationAdapter(final String className, final ClassWriter cw) { + return new ClassAdapter(cw) { + + @Override + public FieldVisitor visitField(final int access, final String name, final String desc, final String signature, final Object value) { + // remove redirected fields + final MappedSubtypeInfo mappedSubtype = className_to_subtype.get(className); + if ( mappedSubtype != null && mappedSubtype.fieldToOffset.containsKey(name) ) { + if ( PRINT_ACTIVITY ) + LWJGLUtil.log(MappedObjectTransformer.class.getSimpleName() + ": discarding field: " + className + "." + name + ":" + desc); + return null; + } + + return super.visitField(access, name, desc, signature, value); + } + + @Override + public MethodVisitor visitMethod(final int access, String name, final String desc, final String signature, final String[] exceptions) { + // Move MappedSubtype constructors to another method + if ( "".equals(name) ) { + final MappedSubtypeInfo mappedSubtype = className_to_subtype.get(className); + if ( mappedSubtype != null ) { + if ( !"()V".equals(desc) ) + throw new ClassFormatError(className + " can only have a default constructor, found: " + desc); + + final MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, MAPPED_OBJECT_JVM, "", "()V"); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + + // put the method body in another method + name = VIEW_CONSTRUCTOR_NAME; + } + } + + final MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); + return new MethodNode(access, name, desc, signature, exceptions) { + + /** When true, the method has touched a mapped object and needs to be transformed. We track this + * so we can skip the expensive frame analysis and tree API usage. */ + boolean needsTransformation; + + @Override + public void visitMaxs(int a, int b) { + try { + is_currently_computing_frames = true; + super.visitMaxs(a, b); + } finally { + is_currently_computing_frames = false; + } + } + + @Override + public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) { + if ( className_to_subtype.containsKey(owner) || owner.startsWith(MAPPEDSET_PREFIX) ) + needsTransformation = true; + + super.visitFieldInsn(opcode, owner, name, desc); + } + + @Override + public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) { + if ( className_to_subtype.containsKey(owner) ) + needsTransformation = true; + + super.visitMethodInsn(opcode, owner, name, desc); + } + + @Override + public void visitEnd() { + if ( needsTransformation ) // Early-out for methods that do not touch a mapped object. + try { + transformMethod(analyse()); + } catch (Exception e) { + throw new RuntimeException(e); + } + + // Pass the instruction stream to the adapter's MethodVisitor + accept(mv); + } + + private Frame[] analyse() throws AnalyzerException { + final Analyzer a = new Analyzer(new SimpleVerifier()); + a.analyze(className, this); + return a.getFrames(); + } + + private void transformMethod(final Frame[] frames) { + //System.err.println("\nTRANSFORMING: " + className + " - " + name + desc); + + final InsnList instructions = this.instructions; + + final Map arrayVars = new HashMap(); + + /* + We need this map because we insert/remove instructions from the stream and we need a way + to match each original instruction with the corresponding frame. + TODO: Can we keep track of everything more efficiently without a map? + */ + final Map> frameMap = new HashMap>(); + for ( int i = 0; i < frames.length; i++ ) + frameMap.put(instructions.get(i), frames[i]); + + for ( int i = 0; i < instructions.size(); i++ ) { // f is a separate cursor for frames + final AbstractInsnNode instruction = instructions.get(i); + + //System.out.println("MAIN LOOP #" + i + " - " + getOpcodeName(instruction)); + + switch ( instruction.getType() ) { + case AbstractInsnNode.VAR_INSN: + if ( instruction.getOpcode() == ALOAD ) { + VarInsnNode varInsn = (VarInsnNode)instruction; + final MappedSubtypeInfo mappedSubtype = arrayVars.get(varInsn.var); + if ( mappedSubtype != null ) + i = transformArrayAccess(instructions, i, frameMap, varInsn, mappedSubtype, varInsn.var); + } + break; + case AbstractInsnNode.FIELD_INSN: + FieldInsnNode fieldInsn = (FieldInsnNode)instruction; + + final InsnList list = transformFieldAccess(fieldInsn); + if ( list != null ) + i = replace(instructions, i, instruction, list); + + break; + case AbstractInsnNode.METHOD_INSN: + MethodInsnNode methodInsn = (MethodInsnNode)instruction; + final MappedSubtypeInfo mappedType = className_to_subtype.get(methodInsn.owner); + if ( mappedType != null ) + i = transformMethodCall(instructions, i, frameMap, methodInsn, mappedType, arrayVars); + break; + } + } + } + }; + } + }; + } + + static int transformMethodCall(final InsnList instructions, int i, final Map> frameMap, final MethodInsnNode methodInsn, final MappedSubtypeInfo mappedType, final Map arrayVars) { + switch ( methodInsn.getOpcode() ) { + case INVOKEVIRTUAL: + if ( "asArray".equals(methodInsn.name) && methodInsn.desc.equals("()[L" + MAPPED_OBJECT_JVM + ";") ) { + // Go forward and store the local variable index. + // We only allow this pattern: INVOKEVIRTUAL -> CHECKCAST -> ASTORE. + // We remove the first two and store the target MappedSubtype in the ASTORE variable + AbstractInsnNode nextInstruction; + checkInsnAfterIsArray(nextInstruction = methodInsn.getNext(), CHECKCAST); + checkInsnAfterIsArray(nextInstruction = nextInstruction.getNext(), ASTORE); + + final Frame frame = frameMap.get(nextInstruction); + final String targetType = frame.getStack(frame.getStackSize() - 1).getType().getElementType().getInternalName(); + if ( !methodInsn.owner.equals(targetType) ) { + /* + This may happen with the current API, like so: + MappedA foo = MappedA.malloc(...); + MappedB[] cursor = foo.asArray(); + We have to parameterize MappedObject to avoid this. + */ + throw new ClassCastException("Source: " + methodInsn.owner + " - Target: " + targetType); + } + + final VarInsnNode varInstruction = (VarInsnNode)nextInstruction; + + arrayVars.put(varInstruction.var, mappedType); + + instructions.remove(methodInsn.getNext()); // Remove CHECKCAST + instructions.remove(methodInsn); // Remove INVOKEVIRTUAL + } + + if ( "dup".equals(methodInsn.name) && methodInsn.desc.equals("()L" + MAPPED_OBJECT_JVM + ";") ) { + i = replace(instructions, i, methodInsn, generateDupInstructions(methodInsn)); + break; + } + + if ( "slice".equals(methodInsn.name) && methodInsn.desc.equals("()L" + MAPPED_OBJECT_JVM + ";") ) { + i = replace(instructions, i, methodInsn, generateSliceInstructions(methodInsn)); + break; + } + + if ( "runViewConstructor".equals(methodInsn.name) && "()V".equals(methodInsn.desc) ) { + i = replace(instructions, i, methodInsn, generateRunViewConstructorInstructions(methodInsn)); + break; + } + + if ( "copyTo".equals(methodInsn.name) && methodInsn.desc.equals("(L" + MAPPED_OBJECT_JVM + ";)V") ) { + i = replace(instructions, i, methodInsn, generateCopyToInstructions(mappedType)); + break; + } + + if ( "copyRange".equals(methodInsn.name) && methodInsn.desc.equals("(L" + MAPPED_OBJECT_JVM + ";I)V") ) { + i = replace(instructions, i, methodInsn, generateCopyRangeInstructions(mappedType)); + break; + } + + if ( "next".equals(methodInsn.name) && "()V".equals(methodInsn.desc) ) { + i = replace(instructions, i, methodInsn, generateNextInstructions(mappedType)); + break; + } + break; + case INVOKESPECIAL: + // super() in VIEW_CONSTRUCTOR_NAME, remove + if ( methodInsn.owner.equals(MAPPED_OBJECT_JVM) && "".equals(methodInsn.name) && "()V".equals(methodInsn.desc) ) { + instructions.remove(methodInsn.getPrevious()); // ALOAD + instructions.remove(methodInsn); // INVOKESPECIAL + + i -= 2; + } + break; + case INVOKESTATIC: + boolean isMapDirectMethod = "map".equals(methodInsn.name) && methodInsn.desc.equals("(JI)L" + MAPPED_OBJECT_JVM + ";"); + boolean isMapBufferMethod = "map".equals(methodInsn.name) && methodInsn.desc.equals("(Ljava/nio/ByteBuffer;)L" + MAPPED_OBJECT_JVM + ";"); + boolean isMallocMethod = "malloc".equals(methodInsn.name) && methodInsn.desc.equals("(I)L" + MAPPED_OBJECT_JVM + ";"); + + if ( (isMapDirectMethod || isMapBufferMethod) || isMallocMethod ) + i = replace(instructions, i, methodInsn, generateMapInstructions(mappedType, methodInsn.owner, isMapDirectMethod, isMallocMethod)); + break; + } + + return i; + } + + private static InsnList generateNextInstructions(final MappedSubtypeInfo mappedType) { + final InsnList list = new InsnList(); + + // stack: this + list.add(getIntNode(mappedType.sizeof)); + // stack: sizeof, this + list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "put_view_next", "(L" + MAPPED_OBJECT_JVM + ";I)V")); + // stack: - + + return list; + } + + private static InsnList generateCopyRangeInstructions(final MappedSubtypeInfo mappedType) { + final InsnList list = new InsnList(); + + // stack: instances, target, this + list.add(getIntNode(mappedType.sizeof)); + // stack: sizeof, instances, target, this + list.add(new InsnNode(IMUL)); + // stack: bytes, target, this + list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "copy", "(L" + MAPPED_OBJECT_JVM + ";L" + MAPPED_OBJECT_JVM + ";I)V")); + // stack: - + + return list; + } + + private static InsnList generateCopyToInstructions(final MappedSubtypeInfo mappedType) { + final InsnList list = new InsnList(); + + // stack: target, this + list.add(getIntNode(mappedType.sizeof)); + // stack: sizeof, target, this + list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "copy", "(L" + MAPPED_OBJECT_JVM + ";L" + MAPPED_OBJECT_JVM + ";I)V")); + // stack: - + + return list; + } + + private static InsnList generateRunViewConstructorInstructions(final MethodInsnNode methodInsn) { + final InsnList list = new InsnList(); + + // stack: this + list.add(new InsnNode(DUP)); + // stack: this, this + list.add(new MethodInsnNode(INVOKEVIRTUAL, methodInsn.owner, VIEW_CONSTRUCTOR_NAME, "()V")); + // stack: this + + return list; + } + + private static InsnList generateSliceInstructions(final MethodInsnNode methodInsn) { + final InsnList list = new InsnList(); + + // stack: this + list.add(new TypeInsnNode(NEW, methodInsn.owner)); + // stack: new, this + list.add(new InsnNode(DUP)); + // stack: new, new, this + list.add(new MethodInsnNode(INVOKESPECIAL, methodInsn.owner, "", "()V")); + // stack: new, this + list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "slice", "(L" + MAPPED_OBJECT_JVM + ";L" + MAPPED_OBJECT_JVM + ";)L" + MAPPED_OBJECT_JVM + ";")); + // stack: new + + return list; + } + + private static InsnList generateDupInstructions(final MethodInsnNode methodInsn) { + final InsnList list = new InsnList(); + + // stack: this + list.add(new TypeInsnNode(NEW, methodInsn.owner)); + // stack: new, this + list.add(new InsnNode(DUP)); + // stack: new, new, this + list.add(new MethodInsnNode(INVOKESPECIAL, methodInsn.owner, "", "()V")); + // stack: new, this + list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "dup", "(L" + MAPPED_OBJECT_JVM + ";L" + MAPPED_OBJECT_JVM + ";)L" + MAPPED_OBJECT_JVM + ";")); + // stack: new + + return list; + } + + private static InsnList generateMapInstructions(final MappedSubtypeInfo mappedType, final String className, final boolean mapDirectMethod, final boolean mallocMethod) { + final InsnList trg = new InsnList(); + + if ( mallocMethod ) { + // stack: count + trg.add(getIntNode(mappedType.sizeof)); + // stack: sizeof, count + trg.add(new InsnNode(IMUL)); + // stack: bytes + trg.add(new MethodInsnNode(INVOKESTATIC, jvmClassName(ByteBuffer.class), "allocateDirect", "(I)L" + jvmClassName(ByteBuffer.class) + ";")); + // stack: buffer + } else if ( mapDirectMethod ) { + // stack: capacity, address + trg.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "newBuffer", "(JI)L" + jvmClassName(ByteBuffer.class) + ";")); + // stack: buffer + } + + // stack: buffer + trg.add(new TypeInsnNode(NEW, className)); + // stack: new, buffer + trg.add(new InsnNode(DUP)); + // stack: new, new, buffer + trg.add(new MethodInsnNode(INVOKESPECIAL, className, "", "()V")); + // stack: new, buffer + trg.add(new InsnNode(DUP_X1)); + // stack: new, buffer, new + trg.add(new InsnNode(SWAP)); + // stack: buffer, new, new + trg.add(getIntNode(mappedType.align)); + // stack: int, buffer, new, new + trg.add(getIntNode(mappedType.sizeof)); + // stack: int, int, buffer, new, new + trg.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "setup", "(L" + MAPPED_OBJECT_JVM + ";Ljava/nio/ByteBuffer;II)V")); + // stack: new + + return trg; + } + + static InsnList transformFieldAccess(final FieldInsnNode fieldInsn) { + final MappedSubtypeInfo mappedSubtype; + mappedSubtype = className_to_subtype.get(fieldInsn.owner); + if ( mappedSubtype == null ) { // early out + // MappedSet.view + outer: + if ( "view".equals(fieldInsn.name) && fieldInsn.owner.startsWith(MAPPEDSET_PREFIX) ) + return generateSetViewInstructions(fieldInsn); + + return null; // early out + } + + if ( "SIZEOF".equals(fieldInsn.name) ) + return generateSIZEOFInstructions(fieldInsn, mappedSubtype); + + if ( "view".equals(fieldInsn.name) ) + return generateViewInstructions(fieldInsn, mappedSubtype); + + if ( "align".equals(fieldInsn.name) || "sizeof".equals(fieldInsn.name) ) + return generateAlignSizeofInstructions(fieldInsn, mappedSubtype); + + if ( "baseAddress".equals(fieldInsn.name) || "viewAddress".equals(fieldInsn.name) ) { + return generateAddressInstructions(fieldInsn); + } + + final Long fieldOffset = mappedSubtype.fieldToOffset.get(fieldInsn.name); + if ( fieldOffset == null ) // early out + return null; + + // now we're going to transform ByteBuffer-typed field access + if ( fieldInsn.desc.equals("L" + jvmClassName(ByteBuffer.class) + ";") ) + return generateByteBufferInstructions(fieldInsn, mappedSubtype, fieldOffset); + + // we're now going to transform the field access + return generateFieldInstructions(fieldInsn, fieldOffset); + } + + private static InsnList generateSetViewInstructions(final FieldInsnNode fieldInsn) { + if ( fieldInsn.getOpcode() == GETFIELD ) + throwAccessErrorOnReadOnlyField(fieldInsn.owner, fieldInsn.name); + if ( fieldInsn.getOpcode() != PUTFIELD ) + throw new InternalError(); + + final InsnList list = new InsnList(); + + // stack: index, this + if ( MAPPED_SET2_JVM.equals(fieldInsn.owner) ) + list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "put_views", "(L" + MAPPED_SET2_JVM + ";I)V")); + else if ( MAPPED_SET3_JVM.equals(fieldInsn.owner) ) + list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "put_views", "(L" + MAPPED_SET3_JVM + ";I)V")); + else if ( MAPPED_SET4_JVM.equals(fieldInsn.owner) ) + list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "put_views", "(L" + MAPPED_SET4_JVM + ";I)V")); + else + throw new InternalError(); + // stack: - + + return list; + } + + private static InsnList generateSIZEOFInstructions(final FieldInsnNode fieldInsn, final MappedSubtypeInfo mappedSubtype) { + if ( !"I".equals(fieldInsn.desc) ) + throw new InternalError(); + + final InsnList list = new InsnList(); + + if ( fieldInsn.getOpcode() == GETSTATIC ) { + list.add(getIntNode(mappedSubtype.sizeof)); + return list; + } + + if ( fieldInsn.getOpcode() == PUTSTATIC ) + throwAccessErrorOnReadOnlyField(fieldInsn.owner, fieldInsn.name); + + throw new InternalError(); + } + + private static InsnList generateViewInstructions(final FieldInsnNode fieldInsn, final MappedSubtypeInfo mappedSubtype) { + if ( !"I".equals(fieldInsn.desc) ) + throw new InternalError(); + + final InsnList list = new InsnList(); + + if ( fieldInsn.getOpcode() == GETFIELD ) { + if ( mappedSubtype.sizeof_shift != 0 ) { + // stack: instance + list.add(getIntNode(mappedSubtype.sizeof_shift)); + // stack: sizeof, instance + list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "get_view_shift", "(L" + MAPPED_OBJECT_JVM + ";I)I")); + // stack: view + } else { + // stack: instance + list.add(getIntNode(mappedSubtype.sizeof)); + // stack: sizeof, instance + list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "get_view", "(L" + MAPPED_OBJECT_JVM + ";I)I")); + // stack: view + } + return list; + } + + if ( fieldInsn.getOpcode() == PUTFIELD ) { + if ( mappedSubtype.sizeof_shift != 0 ) { + // stack: view, instance + list.add(getIntNode(mappedSubtype.sizeof_shift)); + // stack: sizeof, view, instance + list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "put_view_shift", "(L" + MAPPED_OBJECT_JVM + ";II)V")); + // stack: - + } else { + // stack: view, instance + list.add(getIntNode(mappedSubtype.sizeof)); + // stack: sizeof, view, instance + list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "put_view", "(L" + MAPPED_OBJECT_JVM + ";II)V")); + // stack: - + } + return list; + } + + throw new InternalError(); + } + + private static InsnList generateAlignSizeofInstructions(final FieldInsnNode fieldInsn, final MappedSubtypeInfo mappedSubtype) { + if ( !"I".equals(fieldInsn.desc) ) + throw new InternalError(); + + if ( fieldInsn.getOpcode() == GETFIELD ) { + final InsnList list = new InsnList(); + + // stack: instance + list.add(new InsnNode(POP)); + // stack: - + if ( "sizeof".equals(fieldInsn.name) ) + list.add(getIntNode(mappedSubtype.sizeof)); + else if ( "align".equals(fieldInsn.name) ) + list.add(getIntNode(mappedSubtype.align)); + // stack: int + return list; + } + + if ( fieldInsn.getOpcode() == PUTFIELD ) + throwAccessErrorOnReadOnlyField(fieldInsn.owner, fieldInsn.name); + throw new InternalError(); + } + + private static InsnList generateAddressInstructions(final FieldInsnNode fieldInsn) { + if ( !"J".equals(fieldInsn.desc) ) + throw new IllegalStateException(); + + if ( fieldInsn.getOpcode() == GETFIELD ) // do not change a thing + return null; + + if ( fieldInsn.getOpcode() == PUTFIELD ) + throwAccessErrorOnReadOnlyField(fieldInsn.owner, fieldInsn.name); + + throw new InternalError(); + } + + private static InsnList generateByteBufferInstructions(final FieldInsnNode fieldInsn, final MappedSubtypeInfo mappedSubtype, final Long fieldOffset) { + if ( fieldInsn.getOpcode() == PUTFIELD ) + throwAccessErrorOnReadOnlyField(fieldInsn.owner, fieldInsn.name); + + if ( fieldInsn.getOpcode() == GETFIELD ) { + final Long fieldLength = mappedSubtype.fieldToLength.get(fieldInsn.name); + + final InsnList list = new InsnList(); + + // stack: ref + list.add(new FieldInsnNode(GETFIELD, mappedSubtype.className, "viewAddress", "J")); + // stack: long + list.add(new LdcInsnNode(fieldOffset)); + // stack: long, long + list.add(new InsnNode(LADD)); + // stack: long + list.add(new LdcInsnNode(fieldLength)); + // stack: long, long + list.add(new InsnNode(L2I)); + // stack: int, long + list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, "newBuffer", "(JI)L" + jvmClassName(ByteBuffer.class) + ";")); + // stack: buffer + + return list; + } + + throw new InternalError(); + } + + private static InsnList generateFieldInstructions(final FieldInsnNode fieldInsn, final Long fieldOffset) { + final InsnList list = new InsnList(); + + if ( fieldInsn.getOpcode() == PUTFIELD ) { + // stack: value, ref + list.add(getIntNode(fieldOffset.intValue())); + // stack: fieldOffset, value, ref + list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, fieldInsn.desc.toLowerCase() + "put", "(L" + MAPPED_OBJECT_JVM + ";" + fieldInsn.desc + "I)V")); + // stack - + return list; + } + + if ( fieldInsn.getOpcode() == GETFIELD ) { + // stack: ref + list.add(getIntNode(fieldOffset.intValue())); + // stack: fieldOffset, ref + list.add(new MethodInsnNode(INVOKESTATIC, MAPPED_HELPER_JVM, fieldInsn.desc.toLowerCase() + "get", "(L" + MAPPED_OBJECT_JVM + ";I)" + fieldInsn.desc)); + // stack: - + return list; + } + + throw new InternalError(); + } + + static int transformArrayAccess(final InsnList instructions, int i, final Map> frameMap, final VarInsnNode loadInsn, final MappedSubtypeInfo mappedSubtype, final int var) { + // We need to go forward in time to find how we use the array var + final int loadStackSize = frameMap.get(loadInsn).getStackSize() + 1; + + AbstractInsnNode nextInsn = loadInsn; + + while ( true ) { + nextInsn = nextInsn.getNext(); + if ( nextInsn == null ) + throw new InternalError(); + + Frame frame = frameMap.get(nextInsn); + if ( frame == null ) + continue; + + int stackSize = frame.getStackSize(); + + if ( stackSize == loadStackSize + 1 && nextInsn.getOpcode() == AALOAD ) { + final AbstractInsnNode aaLoadInsn = nextInsn; + + while ( true ) { + nextInsn = nextInsn.getNext(); + if ( nextInsn == null ) + break; + + frame = frameMap.get(nextInsn); + if ( frame == null ) + continue; + stackSize = frame.getStackSize(); + + if ( stackSize == loadStackSize + 1 && nextInsn.getOpcode() == PUTFIELD ) { + final FieldInsnNode fieldInsn = (FieldInsnNode)nextInsn; + + // stack: value, view, ref + instructions.insert(nextInsn, new MethodInsnNode(INVOKESTATIC, mappedSubtype.className, setterName(fieldInsn.name), "(L" + mappedSubtype.className + ";I" + fieldInsn.desc + ")V")); + // stack: - + instructions.remove(nextInsn); + + break; + } else if ( stackSize == loadStackSize && nextInsn.getOpcode() == GETFIELD ) { + final FieldInsnNode fieldInsn = (FieldInsnNode)nextInsn; + + // stack: view, ref + instructions.insert(nextInsn, new MethodInsnNode(INVOKESTATIC, mappedSubtype.className, getterName(fieldInsn.name), "(L" + mappedSubtype.className + ";I)" + fieldInsn.desc)); + // stack: value + instructions.remove(nextInsn); + + break; + } else if ( stackSize == loadStackSize && nextInsn.getOpcode() == DUP && nextInsn.getNext().getOpcode() == GETFIELD ) { + // May happen with operator+assignment (e.g. cursor[i].value += 10) + final FieldInsnNode fieldInsn = (FieldInsnNode)nextInsn.getNext(); + + final MethodInsnNode getter = new MethodInsnNode(INVOKESTATIC, mappedSubtype.className, getterName(fieldInsn.name), "(L" + mappedSubtype.className + ";I)" + fieldInsn.desc); + + // stack: view, ref + instructions.insert(nextInsn, new InsnNode(DUP2)); + // stack: view, ref, view, ref + instructions.insert(nextInsn.getNext(), getter); + // stack: value, view, ref + + instructions.remove(nextInsn); + instructions.remove(fieldInsn); + + nextInsn = getter; + continue; + } else if ( stackSize < loadStackSize ) + throw new ClassFormatError("Invalid .asArray() usage detected: " + getOpcodeName(nextInsn)); + } + + instructions.remove(aaLoadInsn); + + return i; + } else if ( stackSize == loadStackSize && nextInsn.getOpcode() == ARRAYLENGTH ) { + if ( LWJGLUtil.DEBUG && loadInsn.getNext() != nextInsn ) + throw new InternalError(); + + instructions.remove(nextInsn); + loadInsn.var = var; + instructions.insert(loadInsn, new MethodInsnNode(INVOKESTATIC, mappedSubtype.className, LENGTH_METHOD_NAME, "(L" + mappedSubtype.className + ";)I")); + + return i + 1; + } else if ( stackSize < loadStackSize ) // Consumed by something other than AALOAD or ARRAYLENGTH + throw new ClassFormatError("Invalid " + mappedSubtype.className + " view array usage detected: " + getOpcodeName(nextInsn)); + } + } + + private static class MappedSubtypeInfo { + + public final String className; + + public int sizeof; + public int sizeof_shift; + public int align; + + public Map fieldToOffset; + public Map fieldToLength; + public Map fieldToType; + + MappedSubtypeInfo(String className, int sizeof, int align) { + this.className = className; + + this.sizeof = sizeof; + if ( ((sizeof - 1) & sizeof) == 0 ) + this.sizeof_shift = getPoT(sizeof); + this.align = align; + + this.fieldToOffset = new HashMap(); + this.fieldToLength = new HashMap(); + this.fieldToType = new HashMap(); + } + + private static int getPoT(int value) { + int pot = -1; + while ( value > 0 ) { + pot++; + value >>= 1; + } + return pot; + } + + } + + // ------------------------------------------------------- + // -------------------[ MACROS & UTILS ]------------------ + // ------------------------------------------------------- + + private static void getClassEnums(final Class clazz, final Map map, final String... prefixFilters) { + try { + OUTER: + for ( Field field : clazz.getFields() ) { + if ( !Modifier.isStatic(field.getModifiers()) || field.getType() != int.class ) + continue; + + for ( String filter : prefixFilters ) { + if ( field.getName().startsWith(filter) ) + continue OUTER; + } + + if ( map.put((Integer)field.get(null), field.getName()) != null ) + throw new IllegalStateException(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + static String getOpcodeName(final AbstractInsnNode insn) { + final String op = OPCODE_TO_NAME.get(insn.getOpcode()); + return INSNTYPE_TO_NAME.get(insn.getType()) + ": " + insn.getOpcode() + (op == null ? "" : " [" + OPCODE_TO_NAME.get(insn.getOpcode()) + "]"); + } + + static String jvmClassName(Class type) { + return type.getName().replace('.', '/'); + } + + static String getterName(final String fieldName) { + return "get$" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1) + "$LWJGL"; + } + + static String setterName(final String fieldName) { + return "set$" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1) + "$LWJGL"; + } + + private static void checkInsnAfterIsArray(final AbstractInsnNode instruction, final int opcode) { + if ( instruction == null ) + throw new ClassFormatError("Unexpected end of instructions after .asArray() method."); + + if ( instruction.getOpcode() != opcode ) + throw new ClassFormatError("The result of .asArray() must be stored to a local variable. Found: " + getOpcodeName(instruction)); + } + + static AbstractInsnNode getIntNode(final int value) { + if ( value <= 5 && -1 <= value ) + return new InsnNode(ICONST_M1 + value + 1); + + if ( value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE ) + return new IntInsnNode(BIPUSH, value); + + if ( value >= Short.MIN_VALUE && value <= Short.MAX_VALUE ) + return new IntInsnNode(SIPUSH, value); + + return new LdcInsnNode(value); + } + + static void visitIntNode(final MethodVisitor mv, final int value) { + if ( value <= 5 && -1 <= value ) + mv.visitInsn(ICONST_M1 + value + 1); + else if ( value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE ) + mv.visitIntInsn(BIPUSH, value); + else if ( value >= Short.MIN_VALUE && value <= Short.MAX_VALUE ) + mv.visitIntInsn(SIPUSH, value); + else + mv.visitLdcInsn(value); + } + + /** Replace an instruction with a list of instructions. */ + static int replace(final InsnList instructions, final int i, final AbstractInsnNode location, final InsnList list) { + final int size = list.size(); + + instructions.insert(location, list); + instructions.remove(location); + + return i + (size - 1); + } + + private static void throwAccessErrorOnReadOnlyField(String className, String fieldName) { + throw new IllegalAccessError("The " + className + "." + fieldName + " field is final."); + } + private static void printBytecode(byte[] bytecode) { StringWriter sw = new StringWriter(); ClassVisitor tracer = new TraceClassVisitor(new ClassWriter(0), new PrintWriter(sw)); @@ -220,444 +1113,4 @@ public class MappedObjectTransformer { LWJGLUtil.log(dump); } - private static class MappedInstanceMethodAdapter extends MethodAdapter { - - private final String className; - private final String methodName; - private final String descr; - - MappedInstanceMethodAdapter(String className, String methodName, String descr, MethodVisitor backing) { - super(backing); - this.className = className; - this.methodName = methodName; - this.descr = descr; - } - - @Override - public void visitTypeInsn(int opcode, String typeName) { - if ( opcode == NEW && className_to_subtype.containsKey(typeName) ) { - throw new IllegalAccessError("must not manually create instances of " + typeName + " in method " + this.className + "." + this.methodName + this.descr); - } - - super.visitTypeInsn(opcode, typeName); - } - - @Override - public void visitMaxs(int a, int b) { - try { - is_currently_computing_frames = true; - - super.visitMaxs(a, b); - } finally { - is_currently_computing_frames = false; - } - } - - @Override - public void visitMethodInsn(int opcode, String className, String methodName, String signature) { - if ( opcode == INVOKESPECIAL && className.equals(MAPPED_OBJECT_JVM) && "".equals(methodName) && "()V".equals(signature) ) { - // stack: instance - visitInsn(POP); - // stack: - - return; - } - - MappedSubtypeInfo mappedType = className_to_subtype.get(className); - if ( mappedType != null && visitMappedMethod(opcode, className, methodName, signature, mappedType) ) - return; - - super.visitMethodInsn(opcode, className, methodName, signature); - } - - private boolean visitMappedMethod(final int opcode, final String className, final String methodName, final String signature, final MappedSubtypeInfo mappedType) { - if ( opcode == INVOKESTATIC ) { - boolean isMapDirectMethod = "map".equals(methodName) && signature.equals("(JI)L" + MAPPED_OBJECT_JVM + ";"); - boolean isMapBufferMethod = "map".equals(methodName) && signature.equals("(Ljava/nio/ByteBuffer;)L" + MAPPED_OBJECT_JVM + ";"); - boolean isMallocMethod = "malloc".equals(methodName) && signature.equals("(I)L" + MAPPED_OBJECT_JVM + ";"); - - if ( (isMapDirectMethod || isMapBufferMethod) || isMallocMethod ) { - if ( isMallocMethod ) { - // stack: count - pushInt(super.mv, mappedType.sizeof); - // stack: sizeof, count - super.visitInsn(IMUL); - // stack: bytes - super.visitMethodInsn(INVOKESTATIC, jvmClassName(ByteBuffer.class), "allocateDirect", "(I)L" + jvmClassName(ByteBuffer.class) + ";"); - // stack: buffer - } else if ( isMapDirectMethod ) { - // stack: capacity, address - super.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, "newBuffer", "(JI)L" + jvmClassName(ByteBuffer.class) + ";"); - // stack: buffer - } - - // stack: buffer - super.visitTypeInsn(NEW, className); - // stack: new, buffer - super.visitInsn(DUP); - // stack: new, new, buffer - super.visitMethodInsn(INVOKESPECIAL, className, "", "()V"); - // stack: new, buffer - super.visitInsn(DUP_X1); - // stack: new, buffer, new - super.visitInsn(SWAP); - // stack: buffer, new, new - pushInt(super.mv, mappedType.align); - // stack: int, buffer, new, new - pushInt(super.mv, mappedType.sizeof); - // stack: int, int, buffer, new, new - super.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, "setup", "(L" + MAPPED_OBJECT_JVM + ";Ljava/nio/ByteBuffer;II)V"); - // stack: new - return true; - } - } else if ( opcode == INVOKEVIRTUAL ) { - if ( "dup".equals(methodName) && signature.equals("()L" + MAPPED_OBJECT_JVM + ";") ) { - // stack: this - super.visitTypeInsn(NEW, className); - // stack: new, this - super.visitInsn(DUP); - // stack: new, new, this - super.visitMethodInsn(INVOKESPECIAL, className, "", "()V"); - // stack: new, this - super.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, "dup", "(L" + MAPPED_OBJECT_JVM + ";L" + MAPPED_OBJECT_JVM + ";)L" + MAPPED_OBJECT_JVM + ";"); - // stack: new - return true; - } - - if ( "slice".equals(methodName) && signature.equals("()L" + MAPPED_OBJECT_JVM + ";") ) { - // stack: this - super.visitTypeInsn(NEW, className); - // stack: new, this - super.visitInsn(DUP); - // stack: new, new, this - super.visitMethodInsn(INVOKESPECIAL, className, "", "()V"); - // stack: new, this - super.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, "slice", "(L" + MAPPED_OBJECT_JVM + ";L" + MAPPED_OBJECT_JVM + ";)L" + MAPPED_OBJECT_JVM + ";"); - // stack: new - return true; - } - - // - - if ( "runViewConstructor".equals(methodName) && "()V".equals(signature) ) { - // stack: this - super.visitInsn(DUP); - // stack: this, this - super.visitMethodInsn(INVOKEVIRTUAL, className, view_constructor_method, "()V"); - // stack: this - return true; - } - - // - - if ( "copyTo".equals(methodName) && signature.equals("(L" + MAPPED_OBJECT_JVM + ";)V") ) { - // stack: target, this - pushInt(super.mv, mappedType.sizeof); - // stack: sizeof, target, this - super.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, "copy", "(L" + MAPPED_OBJECT_JVM + ";L" + MAPPED_OBJECT_JVM + ";I)V"); - // stack: - - return true; - } - - if ( "copyRange".equals(methodName) && signature.equals("(L" + MAPPED_OBJECT_JVM + ";I)V") ) { - // stack: instances, target, this - pushInt(super.mv, mappedType.sizeof); - // stack: sizeof, instances, target, this - super.visitInsn(IMUL); - // stack: bytes, target, this - super.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, "copy", "(L" + MAPPED_OBJECT_JVM + ";L" + MAPPED_OBJECT_JVM + ";I)V"); - // stack: - - return true; - } - - if ( "next".equals(methodName) && "()V".equals(signature) ) { - // stack: this - pushInt(super.mv, mappedType.sizeof); - // stack: sizeof, this - super.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, "put_view_next", "(L" + MAPPED_OBJECT_JVM + ";I)V"); - // stack: - - return true; - } - } - - return false; - } - - private static void throwAccessErrorOnReadOnlyField(String className, String fieldName) { - throw new IllegalAccessError("field '" + className + "." + fieldName + "' is final"); - } - - @Override - public void visitFieldInsn(int opcode, String className, String fieldName, String typeName) { - MappedSubtypeInfo mappedSubtype = className_to_subtype.get(className); - if ( mappedSubtype == null ) { - String mappedSetPrefix = jvmClassName(MappedSet.class); - - // MappedSet.view - outer: - if ( "view".equals(fieldName) && className.startsWith(mappedSetPrefix) ) { - if ( opcode == GETFIELD ) - throwAccessErrorOnReadOnlyField(className, fieldName); - if ( opcode != PUTFIELD ) - break outer; - - // stack: index, this - if ( false ) - break outer; - else if ( className.equals(jvmClassName(MappedSet2.class)) ) - super.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, "put_views", "(L" + jvmClassName(MappedSet2.class) + ";I)V"); - else if ( className.equals(jvmClassName(MappedSet3.class)) ) - super.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, "put_views", "(L" + jvmClassName(MappedSet3.class) + ";I)V"); - else if ( className.equals(jvmClassName(MappedSet4.class)) ) - super.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, "put_views", "(L" + jvmClassName(MappedSet4.class) + ";I)V"); - else - break outer; - // stack: - - return; - } - - // early out - super.visitFieldInsn(opcode, className, fieldName, typeName); - return; - } - - if ( "SIZEOF".equals(fieldName) ) { - if ( !"I".equals(typeName) ) - throw new IllegalStateException(); - - if ( opcode == GETSTATIC ) { - pushInt(super.mv, mappedSubtype.sizeof); - return; - } - if ( opcode == PUTSTATIC ) { - throwAccessErrorOnReadOnlyField(className, fieldName); - } - } - - if ( "view".equals(fieldName) ) { - if ( !"I".equals(typeName) ) - throw new IllegalStateException(); - - if ( opcode == GETFIELD ) { - // stack: instance - pushInt(super.mv, mappedSubtype.sizeof); - // stack: sizeof, instance - super.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, "get_view", "(L" + MAPPED_OBJECT_JVM + ";I)I"); - // stack: view - return; - } - if ( opcode == PUTFIELD ) { - // stack: view, instance - pushInt(super.mv, mappedSubtype.sizeof); - // stack: sizeof, view, instance - super.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, "put_view", "(L" + MAPPED_OBJECT_JVM + ";II)V"); - // stack: - - return; - } - } - - if ( "align".equals(fieldName) || "sizeof".equals(fieldName) ) { - if ( !"I".equals(typeName) ) - throw new IllegalStateException(); - - if ( opcode == GETFIELD ) { - // stack: instance - super.visitInsn(POP); - // stack: - - if ( "sizeof".equals(fieldName) ) - pushInt(super.mv, mappedSubtype.sizeof); - else if ( "align".equals(fieldName) ) - pushInt(super.mv, mappedSubtype.align); - // stack: int - return; - } - if ( opcode == PUTFIELD ) { - throwAccessErrorOnReadOnlyField(className, fieldName); - } - } - - if ( "baseAddress".equals(fieldName) || "viewAddress".equals(fieldName) ) { - if ( !"J".equals(typeName) ) - throw new IllegalStateException(); - - if ( opcode == GETFIELD ) { - // do not change a thing - } - if ( opcode == PUTFIELD ) { - throwAccessErrorOnReadOnlyField(className, fieldName); - } - } - - Long fieldOffset = mappedSubtype.fieldToOffset.get(fieldName); - if ( fieldOffset == null ) { - // early out - super.visitFieldInsn(opcode, className, fieldName, typeName); - return; - } - - // now we're going to transform ByteBuffer-typed field access - - if ( typeName.equals("L" + jvmClassName(ByteBuffer.class) + ";") ) { - if ( opcode == PUTFIELD ) { - throwAccessErrorOnReadOnlyField(className, fieldName); - } - if ( opcode == GETFIELD ) { - Long fieldLength = mappedSubtype.fieldToLength.get(fieldName); - - // stack: ref - super.visitFieldInsn(GETFIELD, mappedSubtype.className, "viewAddress", "J"); - // stack: long - super.visitLdcInsn(fieldOffset); - // stack: long, long - super.visitInsn(LADD); - // stack: long - super.visitLdcInsn(fieldLength); - // stack: long, long - super.visitInsn(L2I); - // stack: int, long - super.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, "newBuffer", "(JI)L" + jvmClassName(ByteBuffer.class) + ";"); - // stack: buffer - return; - } - } - - // we're now going to transform the field access - - if ( opcode == PUTFIELD ) { - // stack: value, ref - super.visitInsn(SWAP); - // stack: ref, value - super.visitFieldInsn(GETFIELD, mappedSubtype.className, "viewAddress", "J"); - // stack: viewAddr, value - super.visitLdcInsn(fieldOffset); - // stack: offset, viewAddr, value - super.visitInsn(LADD); - // stack: fieldAddr, value - super.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, typeName.toLowerCase() + "put", "(" + typeName + "J)V"); - // stack - - - return; - } - if ( opcode == GETFIELD ) { - // stack: ref - super.visitFieldInsn(GETFIELD, mappedSubtype.className, "viewAddress", "J"); - // stack: viewAddr - super.visitLdcInsn(fieldOffset); - // stack: fieldOffset, viewAddr - super.visitInsn(LADD); - // stack: fieldAddr - super.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, typeName.toLowerCase() + "get", "(J)" + typeName); - // stack: value - - return; - } - - // original field access - super.visitFieldInsn(opcode, className, fieldName, typeName); - return; - } - } - - static void pushInt(MethodVisitor mv, int value) { - if ( value == -1 ) - mv.visitInsn(ICONST_M1); - else if ( value == 0 ) - mv.visitInsn(ICONST_0); - else if ( value == 1 ) - mv.visitInsn(ICONST_1); - else if ( value == 2 ) - mv.visitInsn(ICONST_2); - else if ( value == 3 ) - mv.visitInsn(ICONST_3); - else if ( value == 4 ) - mv.visitInsn(ICONST_4); - else if ( value == 5 ) - mv.visitInsn(ICONST_5); - else if ( value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE ) - mv.visitIntInsn(BIPUSH, value); - else if ( value >= Short.MIN_VALUE && value <= Short.MAX_VALUE ) - mv.visitIntInsn(SIPUSH, value); - else - mv.visitLdcInsn(value); - } - - static String jvmClassName(Class type) { - return type.getName().replace('.', '/'); - } - - private static class MappedSubtypeInfo { - - public final String className; - - public int sizeof; - public int align; - - public Map fieldToOffset; - public Map fieldToLength; - - MappedSubtypeInfo(String className, int sizeof, int align) { - this.className = className; - - this.sizeof = sizeof; - this.align = align; - - this.fieldToOffset = new HashMap(); - this.fieldToLength = new HashMap(); - } - } - - public static String exportConfiguration() { - StringBuilder sb = new StringBuilder(); - - for ( MappedSubtypeInfo info : className_to_subtype.values() ) { - sb.append("class\t" + info.className + "\t" + info.sizeof + "\t" + info.align + "\r\n"); - - for ( String fieldName : info.fieldToOffset.keySet() ) { - sb.append("field\t" + info.className + "\t" + fieldName + "\t" + info.fieldToOffset.get(fieldName) + "\t" + info.fieldToLength.get(fieldName) + "\r\n"); - } - } - - className_to_subtype.clear(); - - return sb.toString(); - } - - public static void importConfigation(String input) { - className_to_subtype.clear(); - - try { - BufferedReader br = new BufferedReader(new StringReader(input)); - - while ( true ) { - String line = br.readLine(); - if ( line == null ) - break; - if ( (line = line.trim()).isEmpty() ) - continue; - - StringTokenizer st = new StringTokenizer(line, "\t"); - - String type = st.nextToken(); - if ( type.equals("class") ) { - String className = st.nextToken(); - int sizeof = Integer.parseInt(st.nextToken()); - int align = Integer.parseInt(st.nextToken()); - - className_to_subtype.put(className, new MappedSubtypeInfo(className, sizeof, align)); - } else if ( type.equals("field") ) { - MappedObjectTransformer.MappedSubtypeInfo info = className_to_subtype.get(st.nextToken()); - String methodName = st.nextToken(); - int off = Integer.parseInt(st.nextToken()); - int len = Integer.parseInt(st.nextToken()); - - info.fieldToOffset.put(methodName, Long.valueOf(off)); - info.fieldToLength.put(methodName, Long.valueOf(len)); - } else { - throw new IllegalStateException(type); - } - } - } catch (IOException exc) { - throw new IllegalStateException("never happens"); - } - } - } \ No newline at end of file diff --git a/src/java/org/lwjgl/util/mapped/MappedObjectUnsafe.java b/src/java/org/lwjgl/util/mapped/MappedObjectUnsafe.java index e4fb56b2..20fa3b50 100644 --- a/src/java/org/lwjgl/util/mapped/MappedObjectUnsafe.java +++ b/src/java/org/lwjgl/util/mapped/MappedObjectUnsafe.java @@ -32,7 +32,7 @@ package org.lwjgl.util.mapped; import java.lang.reflect.Field; -import java.nio.Buffer; +import java.lang.reflect.Modifier; import java.nio.ByteBuffer; import sun.misc.Unsafe; @@ -49,10 +49,6 @@ public class MappedObjectUnsafe { private static final long BUFFER_ADDRESS_OFFSET = getObjectFieldOffset(ByteBuffer.class, "address"); private static final long BUFFER_CAPACITY_OFFSET = getObjectFieldOffset(ByteBuffer.class, "capacity"); - public static long getBufferBaseAddress(Buffer buffer) { - return INSTANCE.getLong(buffer, BUFFER_ADDRESS_OFFSET); - } - private static final ByteBuffer global = ByteBuffer.allocateDirect(4 * 1024); static ByteBuffer newBuffer(long address, int capacity) { @@ -75,20 +71,39 @@ public class MappedObjectUnsafe { type = type.getSuperclass(); } } - throw new InternalError(); + + throw new UnsupportedOperationException(); } private static Unsafe getUnsafeInstance() { - try { - ByteBuffer buffer = ByteBuffer.allocateDirect(1); - Field unsafeField = buffer.getClass().getDeclaredField("unsafe"); - unsafeField.setAccessible(true); - Unsafe instance = (Unsafe)unsafeField.get(buffer); - buffer.flip(); // prevented 'buffer' from being gc'ed - return instance; - } catch (Exception exc) { - throw new InternalError(); + final Field[] fields = Unsafe.class.getDeclaredFields(); + + /* + Different runtimes use different names for the Unsafe singleton, + so we cannot use .getDeclaredField and we scan instead. For example: + + Oracle: theUnsafe + PERC : m_unsafe_instance + Android: THE_ONE + */ + for ( Field field : fields ) { + if ( !field.getType().equals(Unsafe.class) ) + continue; + + final int modifiers = field.getModifiers(); + if ( !(Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)) ) + continue; + + field.setAccessible(true); + try { + return (Unsafe)field.get(null); + } catch (IllegalAccessException e) { + // ignore + } + break; } + + throw new UnsupportedOperationException(); } } \ No newline at end of file diff --git a/src/java/org/lwjgl/util/mapped/MappedSet2.java b/src/java/org/lwjgl/util/mapped/MappedSet2.java index 4fdf514e..30287929 100644 --- a/src/java/org/lwjgl/util/mapped/MappedSet2.java +++ b/src/java/org/lwjgl/util/mapped/MappedSet2.java @@ -49,8 +49,8 @@ public class MappedSet2 { } public void next() { - this.a.next(); - this.b.next(); + this.a.nextSet(); + this.b.nextSet(); } } \ No newline at end of file diff --git a/src/java/org/lwjgl/util/mapped/MappedSet3.java b/src/java/org/lwjgl/util/mapped/MappedSet3.java index 308f70ad..bcef2cb9 100644 --- a/src/java/org/lwjgl/util/mapped/MappedSet3.java +++ b/src/java/org/lwjgl/util/mapped/MappedSet3.java @@ -51,9 +51,9 @@ public class MappedSet3 { } public void next() { - this.a.next(); - this.b.next(); - this.c.next(); + this.a.nextSet(); + this.b.nextSet(); + this.c.nextSet(); } } \ No newline at end of file diff --git a/src/java/org/lwjgl/util/mapped/MappedSet4.java b/src/java/org/lwjgl/util/mapped/MappedSet4.java index 0b05f97e..c986244d 100644 --- a/src/java/org/lwjgl/util/mapped/MappedSet4.java +++ b/src/java/org/lwjgl/util/mapped/MappedSet4.java @@ -53,9 +53,9 @@ public class MappedSet4 { } public void next() { - this.a.next(); - this.b.next(); - this.c.next(); - this.d.next(); + this.a.nextSet(); + this.b.nextSet(); + this.c.nextSet(); + this.d.nextSet(); } } \ No newline at end of file