Compare commits
92 Commits
master
...
experiment
Author | SHA1 | Date |
---|---|---|
Kirill Vainer | 74a1d8b219 | 9 years ago |
Kirill Vainer | 3bb01d6963 | 9 years ago |
Kirill Vainer | 4fcd575c47 | 9 years ago |
Kirill Vainer | 60d9d1b4d8 | 9 years ago |
Kirill Vainer | 4ab5a9f7dd | 9 years ago |
Kirill Vainer | 452c307d59 | 9 years ago |
Kirill Vainer | a8fca2bcf6 | 9 years ago |
Kirill Vainer | 2fc4e5b607 | 9 years ago |
Kirill Vainer | f28d74a1f6 | 9 years ago |
Kirill Vainer | 87af1f30b0 | 9 years ago |
Kirill Vainer | e691de4459 | 9 years ago |
Kirill Vainer | 2dafd1e485 | 9 years ago |
Kirill Vainer | c586bacbb0 | 9 years ago |
Kirill Vainer | fde095458e | 9 years ago |
Kirill Vainer | e8fd22223a | 9 years ago |
Kirill Vainer | 53d3b72478 | 9 years ago |
Kirill Vainer | 644b8167b8 | 9 years ago |
Kirill Vainer | 2cdb4a8486 | 9 years ago |
Kirill Vainer | 152a7638cd | 9 years ago |
Kirill Vainer | adc5084f5d | 9 years ago |
Kirill Vainer | 42d76cfd29 | 9 years ago |
Kirill Vainer | ba22487c38 | 9 years ago |
Kirill Vainer | 00b6d904af | 9 years ago |
Kirill Vainer | 2d2c394b42 | 9 years ago |
Kirill Vainer | 655457ab6a | 9 years ago |
Kirill Vainer | 7639657fc1 | 9 years ago |
Kirill Vainer | e87f008244 | 9 years ago |
Kirill Vainer | 12b3c4a140 | 9 years ago |
Kirill Vainer | ae7fb6984c | 9 years ago |
Kirill Vainer | 3b9e412f80 | 9 years ago |
Kirill Vainer | 0aaa28e66b | 9 years ago |
Kirill Vainer | 9b45189f48 | 9 years ago |
Kirill Vainer | 69eaf39da9 | 9 years ago |
Kirill Vainer | 8024babb47 | 9 years ago |
Kirill Vainer | c7da1c4efd | 9 years ago |
Kirill Vainer | ab8527770c | 9 years ago |
Kirill Vainer | c6b568c125 | 9 years ago |
Kirill Vainer | 772330c308 | 9 years ago |
Kirill Vainer | 8413ed715c | 9 years ago |
Kirill Vainer | d76cb99772 | 9 years ago |
Kirill Vainer | 454e210d3d | 9 years ago |
Kirill Vainer | c6336c0781 | 9 years ago |
Kirill Vainer | fda40563c5 | 9 years ago |
Kirill Vainer | 34aa21bfd9 | 9 years ago |
Kirill Vainer | f9969008c3 | 9 years ago |
Kirill Vainer | 2893ac9156 | 9 years ago |
Kirill Vainer | a3638f3e0c | 9 years ago |
Kirill Vainer | 27041e1341 | 9 years ago |
Kirill Vainer | 962ab22ef4 | 9 years ago |
Kirill Vainer | f9500f955f | 9 years ago |
Kirill Vainer | c50839796f | 9 years ago |
Kirill Vainer | ff6b1be725 | 9 years ago |
Kirill Vainer | 97281de5c4 | 9 years ago |
Kirill Vainer | 8f54af3263 | 9 years ago |
Kirill Vainer | 79125f2f63 | 9 years ago |
Kirill Vainer | 908b37350d | 9 years ago |
Kirill Vainer | f986043745 | 9 years ago |
Kirill Vainer | 961bf92734 | 9 years ago |
Kirill Vainer | 3d82f5c459 | 9 years ago |
Kirill Vainer | 4a646de49d | 9 years ago |
Kirill Vainer | 06e8210e5d | 9 years ago |
Kirill Vainer | 85feb305ef | 9 years ago |
Kirill Vainer | 15465a020f | 9 years ago |
Kirill Vainer | f005c05f8d | 9 years ago |
Kirill Vainer | 42729b2302 | 9 years ago |
Kirill Vainer | 30855f5bb4 | 9 years ago |
Kirill Vainer | 352c02db8a | 9 years ago |
Kirill Vainer | ea4d750d52 | 9 years ago |
Kirill Vainer | 6db1d15045 | 9 years ago |
Kirill Vainer | f9ce9e246c | 9 years ago |
Kirill Vainer | 18db26292f | 9 years ago |
Kirill Vainer | 0d3ebf75bd | 10 years ago |
Kirill Vainer | 4e572605a8 | 10 years ago |
Kirill Vainer | 28e2b5650c | 10 years ago |
Kirill Vainer | 3d2a9b83e9 | 10 years ago |
Kirill Vainer | 618c8d02eb | 10 years ago |
Kirill Vainer | 3a00aff886 | 10 years ago |
Kirill Vainer | fc680ea121 | 10 years ago |
Kirill Vainer | 12c001addc | 10 years ago |
Kirill Vainer | 7b147171bf | 10 years ago |
Kirill Vainer | 2e9a9f9f9e | 10 years ago |
Kirill Vainer | c72b036c9f | 10 years ago |
Kirill Vainer | 81a76fdf69 | 10 years ago |
Kirill Vainer | 7b64e91681 | 10 years ago |
Kirill Vainer | d2f38f8adb | 10 years ago |
Kirill Vainer | 937d97b8d7 | 10 years ago |
Kirill Vainer | 9e17f39cfb | 10 years ago |
Kirill Vainer | 2ced7653a7 | 10 years ago |
Kirill Vainer | f0b63e7910 | 10 years ago |
Kirill Vainer | 860de88298 | 10 years ago |
Kirill Vainer | e8f344a0db | 10 years ago |
Kirill Vainer | bee759bddc | 10 years ago |
@ -0,0 +1,280 @@ |
||||
/* |
||||
* Copyright (c) 2009-2015 jMonkeyEngine |
||||
* 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 'jMonkeyEngine' 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 com.jme3.renderer.opengl; |
||||
|
||||
import com.jme3.renderer.RenderContext; |
||||
import com.jme3.renderer.RendererException; |
||||
import com.jme3.texture.FrameBuffer; |
||||
import com.jme3.util.BufferUtils; |
||||
import java.nio.ByteBuffer; |
||||
import java.nio.IntBuffer; |
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.Iterator; |
||||
import java.util.List; |
||||
import java.util.concurrent.CancellationException; |
||||
import java.util.concurrent.ExecutionException; |
||||
import java.util.concurrent.Future; |
||||
import java.util.concurrent.TimeUnit; |
||||
import java.util.concurrent.TimeoutException; |
||||
|
||||
/** |
||||
* @author Kirill Vainer |
||||
*/ |
||||
final class AsyncFrameReader { |
||||
|
||||
private final ArrayList<PixelBuffer> pboPool = new ArrayList<PixelBuffer>(); |
||||
private final List<FrameBufferReadRequest> pending = Collections.synchronizedList(new ArrayList<FrameBufferReadRequest>()); |
||||
private final GLRenderer renderer; |
||||
private final GL gl; |
||||
private final GLExt glext; |
||||
private final IntBuffer intBuf = BufferUtils.createIntBuffer(1); |
||||
private final RenderContext context; |
||||
private final Thread glThread; |
||||
|
||||
AsyncFrameReader(GLRenderer renderer, GL gl, GLExt glext, RenderContext context) { |
||||
this.renderer = renderer; |
||||
this.gl = gl; |
||||
this.glext = glext; |
||||
this.context = context; |
||||
this.glThread = Thread.currentThread(); |
||||
} |
||||
|
||||
private PixelBuffer acquirePixelBuffer(int dataSize) { |
||||
PixelBuffer pb; |
||||
|
||||
if (pboPool.isEmpty()) { |
||||
// create PBO
|
||||
pb = new PixelBuffer(); |
||||
intBuf.clear(); |
||||
gl.glGenBuffers(intBuf); |
||||
pb.id = intBuf.get(0); |
||||
} else { |
||||
// reuse PBO.
|
||||
pb = pboPool.remove(pboPool.size() - 1); |
||||
} |
||||
|
||||
// resize or allocate PBO if required.
|
||||
if (pb.size != dataSize) { |
||||
if (context.boundPixelPackPBO != pb.id) { |
||||
gl.glBindBuffer(GLExt.GL_PIXEL_PACK_BUFFER_ARB, pb.id); |
||||
context.boundPixelPackPBO = pb.id; |
||||
} |
||||
gl.glBufferData(GLExt.GL_PIXEL_PACK_BUFFER_ARB, dataSize, GL.GL_STREAM_READ); |
||||
} |
||||
|
||||
pb.size = dataSize; |
||||
|
||||
return pb; |
||||
} |
||||
|
||||
private void readFrameBufferFromPBO(FrameBufferReadRequest fbrr) { |
||||
// assumes waitForCompletion was already called!
|
||||
if (context.boundPixelPackPBO != fbrr.pb.id) { |
||||
gl.glBindBuffer(GLExt.GL_PIXEL_PACK_BUFFER_ARB, fbrr.pb.id); |
||||
context.boundPixelPackPBO = fbrr.pb.id; |
||||
} |
||||
gl.glGetBufferSubData(GLExt.GL_PIXEL_PACK_BUFFER_ARB, 0, fbrr.targetBuf); |
||||
} |
||||
|
||||
private boolean waitForCompletion(FrameBufferReadRequest fbrr, long time, TimeUnit unit, boolean flush) { |
||||
int flags = flush ? GLExt.GL_SYNC_FLUSH_COMMANDS_BIT : 0; |
||||
long nanos = unit.toNanos(time); |
||||
switch (glext.glClientWaitSync(fbrr.fence, flags, nanos)) { |
||||
case GLExt.GL_ALREADY_SIGNALED: |
||||
case GLExt.GL_CONDITION_SATISFIED: |
||||
return true; |
||||
case GLExt.GL_TIMEOUT_EXPIRED: |
||||
return false; |
||||
case GLExt.GL_WAIT_FAILED: |
||||
throw new RendererException("Waiting for fence failed"); |
||||
default: |
||||
throw new RendererException("Unexpected result from glClientWaitSync"); |
||||
} |
||||
} |
||||
|
||||
private void signalFinished(FrameBufferReadRequest fbrr) { |
||||
fbrr.lock.lock(); |
||||
try { |
||||
fbrr.done = true; |
||||
fbrr.cond.signalAll(); |
||||
} finally { |
||||
fbrr.lock.unlock(); |
||||
} |
||||
} |
||||
|
||||
void signalCancelled(FrameBufferReadRequest fbrr) { |
||||
fbrr.lock.lock(); |
||||
try { |
||||
fbrr.cancelled = true; |
||||
fbrr.cond.signalAll(); |
||||
} finally { |
||||
fbrr.lock.unlock(); |
||||
} |
||||
} |
||||
|
||||
public void updateReadRequests() { |
||||
// Update requests in the order they were made (e.g. earliest first)
|
||||
for (Iterator<FrameBufferReadRequest> it = pending.iterator(); it.hasNext();) { |
||||
FrameBufferReadRequest fbrr = it.next(); |
||||
|
||||
// Check status for the user... (non-blocking)
|
||||
if (!fbrr.cancelled && !fbrr.done) { |
||||
// Request a flush if we know clients are waiting
|
||||
// (to speed up the process, or make it take finite time ..)
|
||||
boolean flush = false; // fbrr.clientsWaiting.get() > 0;
|
||||
if (waitForCompletion(fbrr, 0, TimeUnit.NANOSECONDS, flush)) { |
||||
if (!fbrr.cancelled) { |
||||
// Operation completed.
|
||||
// Read data into user's ByteBuffer
|
||||
readFrameBufferFromPBO(fbrr); |
||||
|
||||
// Signal any waiting threads that we are done.
|
||||
// Also, set the done flag.
|
||||
signalFinished(fbrr); |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (fbrr.cancelled || fbrr.done) { |
||||
// Cleanup
|
||||
// Return the pixel buffer back into the pool.
|
||||
if (!pboPool.contains(fbrr.pb)) { |
||||
pboPool.add(fbrr.pb); |
||||
} |
||||
|
||||
// Remove this request from the pending requests list.
|
||||
it.remove(); |
||||
|
||||
// Get rid of the fence
|
||||
glext.glDeleteSync(fbrr.fence); |
||||
|
||||
fbrr.pb = null; |
||||
fbrr.fence = null; |
||||
} |
||||
} |
||||
} |
||||
|
||||
ByteBuffer getFrameBufferData(FrameBufferReadRequest fbrr, long time, TimeUnit unit) |
||||
throws InterruptedException, ExecutionException, TimeoutException { |
||||
|
||||
if (fbrr.cancelled) { |
||||
throw new CancellationException(); |
||||
} |
||||
|
||||
if (fbrr.done) { |
||||
return fbrr.targetBuf; |
||||
} |
||||
|
||||
if (glThread == Thread.currentThread()) { |
||||
// Running on GL thread, hence can use GL commands ..
|
||||
try { |
||||
// Wait until we reach the fence..
|
||||
|
||||
// PROBLEM: if the user is holding any locks,
|
||||
// they will not be released here,
|
||||
// causing a potential deadlock!
|
||||
if (!waitForCompletion(fbrr, time, unit, true)) { |
||||
throw new TimeoutException(); |
||||
} |
||||
|
||||
// Command stream reached this point.
|
||||
if (fbrr.cancelled) { |
||||
// User not interested in this anymore.
|
||||
throw new CancellationException(); |
||||
} else { |
||||
// Read data into user's ByteBuffer
|
||||
readFrameBufferFromPBO(fbrr); |
||||
} |
||||
|
||||
// Mark it as done, so future get() calls always return.
|
||||
signalFinished(fbrr); |
||||
|
||||
return fbrr.targetBuf; |
||||
} catch (RendererException ex) { |
||||
throw new ExecutionException(ex); |
||||
} |
||||
} else { |
||||
long nanos = unit.toNanos(time); |
||||
|
||||
fbrr.lock.lock(); |
||||
try { |
||||
// Not running on GL thread, indicate that we are running
|
||||
// so GL thread can request GPU to finish quicker ...
|
||||
fbrr.clientsWaiting.getAndIncrement(); |
||||
|
||||
// Wait until we finish
|
||||
while (!fbrr.done && !fbrr.cancelled) { |
||||
if (nanos <= 0L) { |
||||
throw new TimeoutException(); |
||||
} |
||||
|
||||
nanos = fbrr.cond.awaitNanos(nanos); |
||||
} |
||||
|
||||
if (fbrr.cancelled) { |
||||
throw new CancellationException(); |
||||
} |
||||
|
||||
return fbrr.targetBuf; |
||||
} finally { |
||||
fbrr.lock.unlock(); |
||||
fbrr.clientsWaiting.getAndDecrement(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public Future<ByteBuffer> readFrameBufferLater(FrameBuffer fb, ByteBuffer byteBuf) { |
||||
// Create & allocate a PBO (or reuse an existing one if available)
|
||||
FrameBufferReadRequest fbrr = new FrameBufferReadRequest(this); |
||||
fbrr.targetBuf = byteBuf; |
||||
|
||||
int desiredSize = fb.getWidth() * fb.getHeight() * 4; |
||||
|
||||
if (byteBuf.remaining() != desiredSize) { |
||||
throw new IllegalArgumentException("Ensure buffer size matches framebuffer size"); |
||||
} |
||||
|
||||
fbrr.pb = acquirePixelBuffer(desiredSize); |
||||
|
||||
// Read into PBO (asynchronous)
|
||||
// renderer.readFrameBufferWithGLFormat(fb, null, GL2.GL_BGRA, GL2.GL_UNSIGNED_BYTE, fbrr.pb.id);
|
||||
|
||||
// Insert fence into command stream.
|
||||
fbrr.fence = glext.glFenceSync(GLExt.GL_SYNC_GPU_COMMANDS_COMPLETE, 0); |
||||
|
||||
// Insert into FIFO
|
||||
pending.add(fbrr); |
||||
|
||||
return fbrr; |
||||
} |
||||
} |
@ -0,0 +1,98 @@ |
||||
/* |
||||
* Copyright (c) 2009-2015 jMonkeyEngine |
||||
* 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 'jMonkeyEngine' 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 com.jme3.renderer.opengl; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
import java.util.concurrent.ExecutionException; |
||||
import java.util.concurrent.Future; |
||||
import java.util.concurrent.TimeUnit; |
||||
import java.util.concurrent.TimeoutException; |
||||
import java.util.concurrent.atomic.AtomicInteger; |
||||
import java.util.concurrent.locks.Condition; |
||||
import java.util.concurrent.locks.ReentrantLock; |
||||
|
||||
class PixelBuffer { |
||||
int id = -1; |
||||
int size = -1; |
||||
} |
||||
|
||||
class FrameBufferReadRequest implements Future<ByteBuffer> { |
||||
|
||||
AsyncFrameReader reader; |
||||
Object fence; |
||||
PixelBuffer pb; |
||||
ByteBuffer targetBuf; |
||||
boolean cancelled; |
||||
boolean done; |
||||
|
||||
final ReentrantLock lock = new ReentrantLock(); |
||||
final Condition cond = lock.newCondition(); |
||||
final AtomicInteger clientsWaiting = new AtomicInteger(0); |
||||
|
||||
public FrameBufferReadRequest(AsyncFrameReader reader) { |
||||
this.reader = reader; |
||||
} |
||||
|
||||
@Override |
||||
public boolean cancel(boolean mayInterruptIfRunning) { |
||||
if (isDone()) { |
||||
return false; |
||||
} |
||||
reader.signalCancelled(this); |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isCancelled() { |
||||
return cancelled; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isDone() { |
||||
return done; |
||||
} |
||||
|
||||
@Override |
||||
public ByteBuffer get(long l, TimeUnit tu) throws InterruptedException, ExecutionException, TimeoutException { |
||||
return reader.getFrameBufferData(this, l, tu); |
||||
} |
||||
|
||||
@Override |
||||
public ByteBuffer get() throws InterruptedException, ExecutionException { |
||||
try { |
||||
return get(1, TimeUnit.SECONDS); |
||||
} catch (TimeoutException ex) { |
||||
throw new ExecutionException(ex); |
||||
} |
||||
} |
||||
} |
||||
|
@ -0,0 +1,35 @@ |
||||
/* |
||||
* Copyright (c) 2009-2015 jMonkeyEngine |
||||
* 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 'jMonkeyEngine' 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 com.jme3; |
||||
|
||||
public interface IntegrationTest { |
||||
} |
@ -0,0 +1,60 @@ |
||||
package com.jme3.app; |
||||
|
||||
import com.jme3.IntegrationTest; |
||||
import com.jme3.scene.Mesh; |
||||
import com.jme3.system.AppSettings; |
||||
import java.io.ByteArrayInputStream; |
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.util.prefs.BackingStoreException; |
||||
import org.junit.Test; |
||||
|
||||
import static org.junit.Assert.*; |
||||
import org.junit.experimental.categories.Category; |
||||
|
||||
@Category(IntegrationTest.class) |
||||
public class AppSettingsIT { |
||||
|
||||
private static final String APPSETTINGS_KEY = "JME_AppSettingsTest"; |
||||
|
||||
@Test |
||||
public void testPreferencesSaveLoad() throws BackingStoreException { |
||||
AppSettings settings = new AppSettings(false); |
||||
settings.putBoolean("TestBool", true); |
||||
settings.putInteger("TestInt", 123); |
||||
settings.putString("TestStr", "HelloWorld"); |
||||
settings.putFloat("TestFloat", 123.567f); |
||||
settings.put("TestObj", new Mesh()); // Objects not supported by preferences
|
||||
settings.save(APPSETTINGS_KEY); |
||||
|
||||
AppSettings loadedSettings = new AppSettings(false); |
||||
loadedSettings.load(APPSETTINGS_KEY); |
||||
|
||||
assertEquals(true, loadedSettings.getBoolean("TestBool")); |
||||
assertEquals(123, loadedSettings.getInteger("TestInt")); |
||||
assertEquals("HelloWorld", loadedSettings.getString("TestStr")); |
||||
assertEquals(123.567f, loadedSettings.get("TestFloat")); |
||||
} |
||||
|
||||
@Test |
||||
public void testStreamSaveLoad() throws IOException { |
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
||||
|
||||
AppSettings settings = new AppSettings(false); |
||||
settings.putBoolean("TestBool", true); |
||||
settings.putInteger("TestInt", 123); |
||||
settings.putString("TestStr", "HelloWorld"); |
||||
settings.putFloat("TestFloat", 123.567f); |
||||
settings.put("TestObj", new Mesh()); // Objects not supported by file settings
|
||||
settings.save(baos); |
||||
|
||||
AppSettings loadedSettings = new AppSettings(false); |
||||
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); |
||||
loadedSettings.load(bais); |
||||
|
||||
assertEquals(true, loadedSettings.getBoolean("TestBool")); |
||||
assertEquals(123, loadedSettings.getInteger("TestInt")); |
||||
assertEquals("HelloWorld", loadedSettings.getString("TestStr")); |
||||
assertEquals(123.567f, loadedSettings.get("TestFloat")); |
||||
} |
||||
} |
@ -0,0 +1,48 @@ |
||||
/* |
||||
* Copyright (c) 2009-2015 jMonkeyEngine |
||||
* 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 'jMonkeyEngine' 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 com.jme3.system.awt; |
||||
|
||||
import com.jme3.renderer.ViewPort; |
||||
import java.awt.Component; |
||||
|
||||
public interface JmePanel { |
||||
|
||||
public void attachTo(boolean overrideMainFramebuffer, ViewPort ... vps); |
||||
|
||||
public boolean isActiveDrawing(); |
||||
|
||||
public void onFrameBegin(); |
||||
|
||||
public void onFrameEnd(); |
||||
|
||||
public Component getComponent(); |
||||
} |
@ -0,0 +1,382 @@ |
||||
/* |
||||
* Copyright (c) 2009-2015 jMonkeyEngine |
||||
* 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 'jMonkeyEngine' 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 com.jme3.system.awt; |
||||
|
||||
import com.jme3.post.SceneProcessor; |
||||
import com.jme3.renderer.RenderManager; |
||||
import com.jme3.renderer.ViewPort; |
||||
import com.jme3.renderer.opengl.GLRenderer; |
||||
import com.jme3.renderer.queue.RenderQueue; |
||||
import com.jme3.texture.FrameBuffer; |
||||
import com.jme3.texture.Image; |
||||
import com.jme3.util.BufferUtils; |
||||
import com.jme3.util.Screenshots; |
||||
import java.awt.AWTException; |
||||
import java.awt.BufferCapabilities; |
||||
import java.awt.Canvas; |
||||
import java.awt.Component; |
||||
import java.awt.Graphics; |
||||
import java.awt.Graphics2D; |
||||
import java.awt.ImageCapabilities; |
||||
import java.awt.RenderingHints; |
||||
import java.awt.event.ComponentAdapter; |
||||
import java.awt.event.ComponentEvent; |
||||
import java.awt.geom.AffineTransform; |
||||
import java.awt.image.AffineTransformOp; |
||||
import java.awt.image.BufferStrategy; |
||||
import java.awt.image.BufferedImage; |
||||
import java.nio.ByteBuffer; |
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.concurrent.ArrayBlockingQueue; |
||||
import java.util.concurrent.ExecutionException; |
||||
import java.util.concurrent.Future; |
||||
import java.util.concurrent.atomic.AtomicBoolean; |
||||
import java.util.logging.Level; |
||||
import java.util.logging.Logger; |
||||
import javax.swing.JPanel; |
||||
|
||||
public class SwingPanel extends JPanel implements JmePanel, SceneProcessor { |
||||
|
||||
private boolean attachAsMain = false; |
||||
|
||||
private BufferedImage img; |
||||
// private FrameBuffer fb;
|
||||
private RenderManager rm; |
||||
private PaintMode paintMode; |
||||
private ArrayList<ViewPort> viewPorts = new ArrayList<ViewPort>(); |
||||
|
||||
// Visibility/drawing vars
|
||||
private AffineTransformOp transformOp; |
||||
private AtomicBoolean hasNativePeer = new AtomicBoolean(false); |
||||
private AtomicBoolean showing = new AtomicBoolean(false); |
||||
private AtomicBoolean repaintRequest = new AtomicBoolean(false); |
||||
|
||||
// Reshape vars
|
||||
private int newWidth = 1; |
||||
private int newHeight = 1; |
||||
private AtomicBoolean reshapeNeeded = new AtomicBoolean(true); |
||||
private final Object lock = new Object(); |
||||
|
||||
// Buffer pool and pending buffers
|
||||
private final int NUM_FRAMES = 2; |
||||
private final ArrayBlockingQueue<Future<ByteBuffer>> pendingFrames = new ArrayBlockingQueue<Future<ByteBuffer>>(NUM_FRAMES); |
||||
private final ArrayBlockingQueue<ByteBuffer> bufferPool = new ArrayBlockingQueue<ByteBuffer>(NUM_FRAMES); |
||||
private final ArrayList<FrameBuffer> fbs = new ArrayList<FrameBuffer>(NUM_FRAMES); |
||||
private int frameIndex = 0; |
||||
|
||||
private final ComponentAdapter resizeListener = new ComponentAdapter() { |
||||
@Override |
||||
public void componentResized(ComponentEvent e) { |
||||
onResize(e); |
||||
} |
||||
}; |
||||
|
||||
public SwingPanel(PaintMode paintMode, boolean srgb){ |
||||
this.paintMode = paintMode; |
||||
invalidatePendingFrames(); |
||||
addComponentListener(resizeListener); |
||||
} |
||||
|
||||
public void onResize(ComponentEvent e) { |
||||
synchronized (lock) { |
||||
int newWidth2 = Math.max(getWidth(), 1); |
||||
int newHeight2 = Math.max(getHeight(), 1); |
||||
if (newWidth != newWidth2 || newHeight != newHeight2) { |
||||
newWidth = newWidth2; |
||||
newHeight = newHeight2; |
||||
reshapeNeeded.set(true); |
||||
System.out.println("EDT: componentResized " + newWidth + ", " + newHeight); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public Component getComponent() { |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public void addNotify(){ |
||||
super.addNotify(); |
||||
|
||||
synchronized (lock){ |
||||
hasNativePeer.set(true); |
||||
System.out.println("EDT: addNotify"); |
||||
} |
||||
|
||||
requestFocusInWindow(); |
||||
} |
||||
|
||||
@Override |
||||
public void removeNotify(){ |
||||
synchronized (lock){ |
||||
hasNativePeer.set(false); |
||||
System.out.println("EDT: removeNotify"); |
||||
} |
||||
|
||||
super.removeNotify(); |
||||
} |
||||
|
||||
public boolean checkVisibilityState() { |
||||
if (!hasNativePeer.get()) { |
||||
return false; |
||||
} |
||||
|
||||
boolean currentShowing = isShowing(); |
||||
if (showing.getAndSet(currentShowing) != currentShowing) { |
||||
if (currentShowing) { |
||||
System.out.println("OGL: Enter showing state."); |
||||
} else { |
||||
System.out.println("OGL: Exit showing state."); |
||||
} |
||||
} |
||||
return currentShowing; |
||||
} |
||||
|
||||
@Override |
||||
public void paintComponent(Graphics g){ |
||||
Graphics2D g2d = (Graphics2D) g; |
||||
|
||||
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, |
||||
RenderingHints.VALUE_RENDER_SPEED); |
||||
|
||||
ByteBuffer byteBuf = null; |
||||
|
||||
synchronized (lock){ |
||||
if (pendingFrames.size() > NUM_FRAMES - 1) { |
||||
byteBuf = acquireNextFrame(); |
||||
} |
||||
|
||||
if (byteBuf != null) { |
||||
// Convert the frame into the image so it can be rendered.
|
||||
Screenshots.convertScreenShot2(byteBuf.asIntBuffer(), img); |
||||
|
||||
try { |
||||
// return the frame back to its rightful owner.
|
||||
bufferPool.put(byteBuf); |
||||
} catch (InterruptedException ex) { |
||||
Logger.getLogger(SwingPanel.class.getName()).log(Level.SEVERE, null, ex); |
||||
} |
||||
} |
||||
} |
||||
|
||||
g2d.drawImage(img, transformOp, 0, 0); |
||||
} |
||||
|
||||
public ByteBuffer acquireNextFrame() { |
||||
if (pendingFrames.isEmpty()) { |
||||
System.out.println("!!! No pending frames, returning null."); |
||||
return null; |
||||
} |
||||
|
||||
try { |
||||
return pendingFrames.take().get(); |
||||
} catch (InterruptedException ex) { |
||||
throw new RuntimeException(ex); |
||||
} catch (ExecutionException ex) { |
||||
throw new RuntimeException(ex); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Grabs an available buffer from the available frames pool, |
||||
* reads the OpenGL backbuffer into it, then adds it to the pending frames pool. |
||||
*/ |
||||
public void readNextFrame() { |
||||
if (bufferPool.isEmpty()) { |
||||
System.out.println("??? Too many pending frames!"); |
||||
return; // need to draw more frames ..
|
||||
} |
||||
|
||||
try { |
||||
int size = fbs.get(frameIndex).getWidth() * fbs.get(frameIndex).getHeight() * 4; |
||||
ByteBuffer byteBuf = bufferPool.take(); |
||||
byteBuf = BufferUtils.ensureLargeEnough(byteBuf, size); |
||||
byteBuf.clear(); |
||||
|
||||
GLRenderer renderer = (GLRenderer) rm.getRenderer(); |
||||
// Future<ByteBuffer> future = renderer.readFrameBufferLater(fbs.get(frameIndex), byteBuf);
|
||||
// if (!pendingFrames.offer(future)) {
|
||||
// throw new AssertionError();
|
||||
// }
|
||||
|
||||
frameIndex ++; |
||||
if (frameIndex >= NUM_FRAMES) { |
||||
frameIndex = 0; |
||||
} |
||||
} catch (InterruptedException ex) { |
||||
throw new RuntimeException(ex); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public boolean isActiveDrawing() { |
||||
return paintMode != PaintMode.OnRequest && showing.get(); |
||||
} |
||||
|
||||
public void attachTo(boolean overrideMainFramebuffer, ViewPort ... vps){ |
||||
if (viewPorts.size() > 0){ |
||||
for (ViewPort vp : viewPorts){ |
||||
vp.setOutputFrameBuffer(null); |
||||
} |
||||
viewPorts.get(viewPorts.size()-1).removeProcessor(this); |
||||
} |
||||
|
||||
viewPorts.addAll(Arrays.asList(vps)); |
||||
viewPorts.get(viewPorts.size()-1).addProcessor(this); |
||||
|
||||
this.attachAsMain = overrideMainFramebuffer; |
||||
} |
||||
|
||||
public void initialize(RenderManager rm, ViewPort vp) { |
||||
if (this.rm == null){ |
||||
// First time called in OGL thread
|
||||
this.rm = rm; |
||||
// reshapeInThread(1, 1);
|
||||
} |
||||
} |
||||
|
||||
private void invalidatePendingFrames() { |
||||
// NOTE: all pending read requests are invalid!
|
||||
for (Future<ByteBuffer> pendingRequest : pendingFrames) { |
||||
pendingRequest.cancel(true); |
||||
} |
||||
pendingFrames.clear(); |
||||
bufferPool.clear(); |
||||
|
||||
// Populate buffer pool.
|
||||
int cap = bufferPool.remainingCapacity(); |
||||
for (int i = 0; i < cap; i++) { |
||||
bufferPool.add(BufferUtils.createByteBuffer(1 * 1 * 4)); |
||||
} |
||||
} |
||||
|
||||
private void reshapeInThread(int width, int height) { |
||||
invalidatePendingFrames(); |
||||
|
||||
for (FrameBuffer fb : fbs) { |
||||
fb.dispose(); |
||||
} |
||||
|
||||
fbs.clear(); |
||||
|
||||
for (int i = 0; i < NUM_FRAMES; i++) { |
||||
FrameBuffer fb = new FrameBuffer(width, height, 1); |
||||
fb.setDepthBuffer(Image.Format.Depth); |
||||
fb.setColorBuffer(Image.Format.RGBA8); |
||||
fbs.add(fb); |
||||
} |
||||
|
||||
synchronized (lock){ |
||||
img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); |
||||
|
||||
AffineTransform tx = AffineTransform.getScaleInstance(1, -1); |
||||
tx.translate(0, -img.getHeight()); |
||||
transformOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); |
||||
} |
||||
|
||||
if (attachAsMain) { |
||||
rm.notifyReshape(width, height); |
||||
} else { |
||||
for (ViewPort vp : viewPorts){ |
||||
vp.getCamera().resize(width, height, true); |
||||
|
||||
// NOTE: Hack alert. This is done ONLY for custom framebuffers.
|
||||
// Main framebuffer should use RenderManager.notifyReshape().
|
||||
for (SceneProcessor sp : vp.getProcessors()){ |
||||
sp.reshape(vp, width, height); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public boolean isInitialized() { |
||||
return rm != null; |
||||
} |
||||
|
||||
@Override |
||||
public void preFrame(float tpf) { |
||||
} |
||||
|
||||
@Override |
||||
public void postQueue(RenderQueue rq) { |
||||
} |
||||
|
||||
@Override |
||||
public void invalidate(){ |
||||
// For "PaintMode.OnDemand" only.
|
||||
repaintRequest.set(true); |
||||
} |
||||
|
||||
@Override |
||||
public void onFrameBegin() { |
||||
if (attachAsMain && rm != null){ |
||||
rm.getRenderer().setMainFrameBufferOverride(fbs.get(frameIndex)); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onFrameEnd() { |
||||
if (reshapeNeeded.getAndSet(false)) { |
||||
reshapeInThread(newWidth, newHeight); |
||||
} else { |
||||
if (!checkVisibilityState()) { |
||||
return; |
||||
} |
||||
|
||||
switch (paintMode) { |
||||
case Accelerated: |
||||
case Repaint: |
||||
readNextFrame(); |
||||
repaint(); |
||||
break; |
||||
case OnRequest: |
||||
if (repaintRequest.getAndSet(false)) { |
||||
readNextFrame(); |
||||
repaint(); |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
public void postFrame(FrameBuffer out) { |
||||
} |
||||
|
||||
public void reshape(ViewPort vp, int w, int h) { |
||||
} |
||||
|
||||
public void cleanup() { |
||||
} |
||||
} |
@ -0,0 +1,203 @@ |
||||
/* |
||||
* Copyright (c) 2009-2015 jMonkeyEngine |
||||
* 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 'jMonkeyEngine' 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 com.jme3.system; |
||||
|
||||
import java.io.File; |
||||
import java.io.FileOutputStream; |
||||
import java.io.IOException; |
||||
import java.nio.channels.FileLock; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
|
||||
import static org.junit.Assert.*; |
||||
import org.junit.FixMethodOrder; |
||||
import org.junit.experimental.categories.Category; |
||||
|
||||
import com.jme3.IntegrationTest; |
||||
import org.junit.Ignore; |
||||
|
||||
/** |
||||
* Integration test for {@link NativeLibraryLoader}. |
||||
* |
||||
* Note that it uses the file system. |
||||
* |
||||
* @author Kirill Vainer |
||||
*/ |
||||
@Ignore |
||||
@Category(IntegrationTest.class) |
||||
@FixMethodOrder |
||||
public class NativeLibraryLoaderIT { |
||||
|
||||
private File extractFolder; |
||||
|
||||
static { |
||||
NativeLibraryLoader.registerNativeLibrary("test", Platform.Linux64, "natives/linux64/libtest.so"); |
||||
NativeLibraryLoader.registerNativeLibrary("notexist", Platform.Linux64, "natives/linux64/libnotexist.so"); |
||||
NativeLibraryLoader.registerNativeLibrary("nativesfolder", Platform.Linux64, "natives/linux64/libnativesfolder.so"); |
||||
NativeLibraryLoader.registerNativeLibrary("jarroot", Platform.Linux64, "natives/linux64/libjarroot.so"); |
||||
NativeLibraryLoader.registerNativeLibrary("nullpath", Platform.Linux64, null); |
||||
NativeLibraryLoader.registerNativeLibrary("jawt", Platform.Linux64, "whatever/doesnt/matter/libjawt.so"); |
||||
NativeLibraryLoader.registerNativeLibrary("asname", Platform.Linux64, "natives/linux64/libasname.so", "other.name"); |
||||
} |
||||
|
||||
@Before |
||||
public void setUp() { |
||||
extractFolder = NativeLibraryLoader.getExtractionFolder(); |
||||
} |
||||
|
||||
@Test(expected = UnsatisfiedLinkError.class) |
||||
public void testRequiredNonExistentFile() { |
||||
NativeLibraryLoader.loadNativeLibrary("notexist", true, false); |
||||
} |
||||
|
||||
@Test |
||||
public void testOptionalNonExistentFile() throws Exception { |
||||
NativeLibraryLoader.loadNativeLibrary("notexist", false, false); |
||||
} |
||||
|
||||
@Test(expected = UnsatisfiedLinkError.class) |
||||
public void testRequiredUnregisteredLibrary() { |
||||
NativeLibraryLoader.loadNativeLibrary("unregistered", true, false); |
||||
} |
||||
|
||||
@Test |
||||
public void testOptionalUnregisteredLibrary() { |
||||
NativeLibraryLoader.loadNativeLibrary("unregistered", false, false); |
||||
} |
||||
|
||||
@Test |
||||
public void testLibraryNullPath() { |
||||
NativeLibraryLoader.loadNativeLibrary("nullpath", true, false); |
||||
NativeLibraryLoader.loadNativeLibrary("nullpath", false, false); |
||||
} |
||||
|
||||
private static void fudgeLastModifiedTime(File file) { |
||||
// fudge last modified date to force extraction attempt
|
||||
long yesterdayModifiedtime = file.lastModified() - 24 * 60 * 60 * 1000; |
||||
assertTrue(file.setLastModified(yesterdayModifiedtime)); |
||||
assertTrue(Math.abs(file.lastModified() - yesterdayModifiedtime) < 10000); |
||||
} |
||||
|
||||
@Test |
||||
public void testDifferentLastModifiedDates() throws IOException { |
||||
File libFile = new File(extractFolder, "libtest.so"); |
||||
|
||||
assertTrue(libFile.createNewFile()); |
||||
assertTrue(libFile.exists() && libFile.length() == 0); |
||||
|
||||
fudgeLastModifiedTime(libFile); |
||||
NativeLibraryLoader.loadNativeLibrary("test", true, false); |
||||
assertTrue(libFile.length() == 12); |
||||
|
||||
assertTrue(libFile.delete()); |
||||
assertTrue(!libFile.exists()); |
||||
} |
||||
|
||||
@Test |
||||
public void testLibraryInUse() throws IOException { |
||||
File libFile = new File(extractFolder, "libtest.so"); |
||||
|
||||
NativeLibraryLoader.loadNativeLibrary("test", true, false); |
||||
assertTrue(libFile.exists()); |
||||
|
||||
fudgeLastModifiedTime(libFile); |
||||
|
||||
FileOutputStream out = null; |
||||
try { |
||||
out = new FileOutputStream(libFile); |
||||
FileLock lock = out.getChannel().lock(); |
||||
assertTrue(lock.isValid()); |
||||
|
||||
NativeLibraryLoader.loadNativeLibrary("test", true, false); |
||||
} finally { |
||||
if (out != null) { |
||||
out.close(); |
||||
} |
||||
} |
||||
|
||||
libFile.delete(); |
||||
} |
||||
|
||||
@Test |
||||
public void testLoadSystemLibrary() { |
||||
NativeLibraryLoader.loadNativeLibrary("jawt", true, true); |
||||
} |
||||
|
||||
@Test |
||||
public void testExtractAsName() { |
||||
NativeLibraryLoader.loadNativeLibrary("asname", true, false); |
||||
assertTrue(new File(extractFolder, "other.name").exists()); |
||||
assertTrue(new File(extractFolder, "other.name").delete()); |
||||
} |
||||
|
||||
@Test |
||||
public void testCustomExtractFolder() { |
||||
File customExtractFolder = new File(System.getProperty("java.io.tmpdir"), "jme3_test_tmp"); |
||||
if (!customExtractFolder.exists()) { |
||||
assertTrue(customExtractFolder.mkdir()); |
||||
} |
||||
|
||||
NativeLibraryLoader.setCustomExtractionFolder(customExtractFolder.getAbsolutePath()); |
||||
NativeLibraryLoader.loadNativeLibrary("test", true, false); |
||||
|
||||
assertTrue(new File(customExtractFolder, "libtest.so").exists()); |
||||
assertTrue(new File(customExtractFolder, "libtest.so").delete()); |
||||
assertTrue(!new File(customExtractFolder, "libtest.so").exists()); |
||||
|
||||
NativeLibraryLoader.setCustomExtractionFolder(null); |
||||
NativeLibraryLoader.loadNativeLibrary("test", true, false); |
||||
|
||||
assertTrue(new File(extractFolder, "libtest.so").exists()); |
||||
new File(extractFolder, "libtest.so").delete(); |
||||
customExtractFolder.delete(); |
||||
} |
||||
|
||||
@Test |
||||
public void testExtractFromNativesFolderInJar() { |
||||
NativeLibraryLoader.loadNativeLibrary("nativesfolder", true, false); |
||||
|
||||
File libFile = new File(extractFolder, "libnativesfolder.so"); |
||||
assertTrue(libFile.exists() && libFile.length() == 12); |
||||
|
||||
libFile.delete(); |
||||
} |
||||
|
||||
@Test |
||||
public void testExtractFromJarRoot() { |
||||
NativeLibraryLoader.loadNativeLibrary("jarroot", true, false); |
||||
|
||||
File libFile = new File(extractFolder, "libjarroot.so"); |
||||
assertTrue(libFile.exists() && libFile.length() == 12); |
||||
|
||||
libFile.delete(); |
||||
} |
||||
} |
@ -1,88 +0,0 @@ |
||||
package jme3test.app; |
||||
|
||||
import com.jme3.scene.Mesh; |
||||
import com.jme3.system.AppSettings; |
||||
import java.io.ByteArrayInputStream; |
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.util.prefs.BackingStoreException; |
||||
|
||||
public class TestCustomAppSettings { |
||||
|
||||
private static final String APPSETTINGS_KEY = "JME_AppSettingsTest"; |
||||
|
||||
private static void assertEqual(Object a, Object b) { |
||||
if (!a.equals(b)){ |
||||
throw new AssertionError(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Tests preference based AppSettings. |
||||
*/ |
||||
private static void testPreferenceSettings() { |
||||
AppSettings settings = new AppSettings(false); |
||||
settings.putBoolean("TestBool", true); |
||||
settings.putInteger("TestInt", 123); |
||||
settings.putString("TestStr", "HelloWorld"); |
||||
settings.putFloat("TestFloat", 123.567f); |
||||
settings.put("TestObj", new Mesh()); // Objects not supported by preferences
|
||||
|
||||
try { |
||||
settings.save(APPSETTINGS_KEY); |
||||
} catch (BackingStoreException ex) { |
||||
ex.printStackTrace(); |
||||
} |
||||
|
||||
AppSettings loadedSettings = new AppSettings(false); |
||||
try { |
||||
loadedSettings.load(APPSETTINGS_KEY); |
||||
} catch (BackingStoreException ex) { |
||||
ex.printStackTrace(); |
||||
} |
||||
|
||||
assertEqual(loadedSettings.getBoolean("TestBool"), true); |
||||
assertEqual(loadedSettings.getInteger("TestInt"), 123); |
||||
assertEqual(loadedSettings.getString("TestStr"), "HelloWorld"); |
||||
assertEqual(loadedSettings.get("TestFloat"), 123.567f); |
||||
} |
||||
|
||||
/** |
||||
* Test Java properties file based AppSettings. |
||||
*/ |
||||
private static void testFileSettings() { |
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
||||
|
||||
AppSettings settings = new AppSettings(false); |
||||
settings.putBoolean("TestBool", true); |
||||
settings.putInteger("TestInt", 123); |
||||
settings.putString("TestStr", "HelloWorld"); |
||||
settings.putFloat("TestFloat", 123.567f); |
||||
settings.put("TestObj", new Mesh()); // Objects not supported by file settings
|
||||
|
||||
try { |
||||
settings.save(baos); |
||||
} catch (IOException ex) { |
||||
ex.printStackTrace(); |
||||
} |
||||
|
||||
AppSettings loadedSettings = new AppSettings(false); |
||||
try { |
||||
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); |
||||
loadedSettings.load(bais); |
||||
} catch (IOException ex) { |
||||
ex.printStackTrace(); |
||||
} |
||||
|
||||
assertEqual(loadedSettings.getBoolean("TestBool"), true); |
||||
assertEqual(loadedSettings.getInteger("TestInt"), 123); |
||||
assertEqual(loadedSettings.getString("TestStr"), "HelloWorld"); |
||||
assertEqual(loadedSettings.get("TestFloat"), 123.567f); |
||||
} |
||||
|
||||
public static void main(String[] args){ |
||||
testPreferenceSettings(); |
||||
testFileSettings(); |
||||
System.out.println("All OK"); |
||||
} |
||||
} |
@ -1,112 +0,0 @@ |
||||
package jme3test.awt; |
||||
|
||||
import com.jme3.app.SimpleApplication; |
||||
import com.jme3.material.Material; |
||||
import com.jme3.math.Vector3f; |
||||
import com.jme3.scene.Geometry; |
||||
import com.jme3.scene.shape.Box; |
||||
import com.jme3.system.AppSettings; |
||||
import com.jme3.system.awt.AwtPanel; |
||||
import com.jme3.system.awt.AwtPanelsContext; |
||||
import com.jme3.system.awt.PaintMode; |
||||
import java.awt.BorderLayout; |
||||
import java.awt.Dimension; |
||||
import java.awt.Toolkit; |
||||
import java.awt.event.WindowAdapter; |
||||
import java.awt.event.WindowEvent; |
||||
import java.util.concurrent.CountDownLatch; |
||||
import java.util.logging.Level; |
||||
import java.util.logging.Logger; |
||||
import javax.swing.JFrame; |
||||
import javax.swing.SwingUtilities; |
||||
|
||||
public class TestAwtPanels extends SimpleApplication { |
||||
|
||||
final private static CountDownLatch panelsAreReady = new CountDownLatch(1); |
||||
private static TestAwtPanels app; |
||||
private static AwtPanel panel, panel2; |
||||
private static int panelsClosed = 0; |
||||
|
||||
private static void createWindowForPanel(AwtPanel panel, int location){ |
||||
JFrame frame = new JFrame("Render Display " + location); |
||||
frame.getContentPane().setLayout(new BorderLayout()); |
||||
frame.getContentPane().add(panel, BorderLayout.CENTER); |
||||
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); |
||||
frame.addWindowListener(new WindowAdapter() { |
||||
@Override |
||||
public void windowClosed(WindowEvent e) { |
||||
if (++panelsClosed == 2){ |
||||
app.stop(); |
||||
} |
||||
} |
||||
}); |
||||
frame.pack(); |
||||
frame.setLocation(location, Toolkit.getDefaultToolkit().getScreenSize().height - 400); |
||||
frame.setVisible(true); |
||||
} |
||||
|
||||
public static void main(String[] args){ |
||||
Logger.getLogger("com.jme3").setLevel(Level.WARNING); |
||||
|
||||
app = new TestAwtPanels(); |
||||
app.setShowSettings(false); |
||||
AppSettings settings = new AppSettings(true); |
||||
settings.setCustomRenderer(AwtPanelsContext.class); |
||||
settings.setFrameRate(60); |
||||
app.setSettings(settings); |
||||
app.start(); |
||||
|
||||
SwingUtilities.invokeLater(new Runnable(){ |
||||
public void run(){ |
||||
/* |
||||
* Sleep 2 seconds to ensure there's no race condition. |
||||
* The sleep is not required for correctness. |
||||
*/ |
||||
try { |
||||
Thread.sleep(2000); |
||||
} catch (InterruptedException exception) { |
||||
return; |
||||
} |
||||
|
||||
final AwtPanelsContext ctx = (AwtPanelsContext) app.getContext(); |
||||
panel = ctx.createPanel(PaintMode.Accelerated); |
||||
panel.setPreferredSize(new Dimension(400, 300)); |
||||
ctx.setInputSource(panel); |
||||
|
||||
panel2 = ctx.createPanel(PaintMode.Accelerated); |
||||
panel2.setPreferredSize(new Dimension(400, 300)); |
||||
|
||||
createWindowForPanel(panel, 300); |
||||
createWindowForPanel(panel2, 700); |
||||
/* |
||||
* Both panels are ready. |
||||
*/ |
||||
panelsAreReady.countDown(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public void simpleInitApp() { |
||||
flyCam.setDragToRotate(true); |
||||
|
||||
Box b = new Box(Vector3f.ZERO, 1, 1, 1); |
||||
Geometry geom = new Geometry("Box", b); |
||||
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); |
||||
mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); |
||||
geom.setMaterial(mat); |
||||
rootNode.attachChild(geom); |
||||
/* |
||||
* Wait until both AWT panels are ready. |
||||
*/ |
||||
try { |
||||
panelsAreReady.await(); |
||||
} catch (InterruptedException exception) { |
||||
throw new RuntimeException("Interrupted while waiting for panels", exception); |
||||
} |
||||
|
||||
panel.attachTo(true, viewPort); |
||||
guiViewPort.setClearFlags(true, true, true); |
||||
panel2.attachTo(false, guiViewPort); |
||||
} |
||||
} |
@ -0,0 +1,141 @@ |
||||
/* |
||||
* Copyright (c) 2009-2015 jMonkeyEngine |
||||
* 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 'jMonkeyEngine' 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 jme3test.stress; |
||||
|
||||
import com.jme3.app.SimpleApplication; |
||||
import com.jme3.light.DirectionalLight; |
||||
import com.jme3.material.Material; |
||||
import com.jme3.math.ColorRGBA; |
||||
import com.jme3.math.FastMath; |
||||
import com.jme3.math.Quaternion; |
||||
import com.jme3.math.Vector3f; |
||||
import com.jme3.scene.Geometry; |
||||
import com.jme3.scene.shape.Box; |
||||
import com.jme3.system.AppSettings; |
||||
|
||||
// Let's see if we can render 2500 batches in 60 fps.
|
||||
// We'll use 50 materials with various combinations of textures and colors
|
||||
// to make things wild.
|
||||
public class TestUniqueGeometries extends SimpleApplication { |
||||
|
||||
private Material[] randomMaterials = new Material[50]; |
||||
|
||||
private String[] textureList = new String[] { |
||||
"Blender/2.4x/textures/Concrete_Wall.PNG", |
||||
"Blender/2.4x/textures/Grass_256.png", |
||||
"Blender/2.4x/textures/SandDesert_StartTower.png", |
||||
"Blender/2.4x/textures/Tar_Cracked.png", |
||||
"Blender/2.4x/textures/WarningStrip.png", |
||||
"Blender/2.4x/WoodCrate_lighter.png", |
||||
"Interface/Logo/Monkey.jpg", |
||||
"Interface/Logo/Monkey.png", |
||||
"Models/Boat/boat.png", |
||||
"Models/Ninja/Ninja.jpg", |
||||
"Models/Tree/BarkColor.jpg", |
||||
"Textures/Terrain/BrickWall/BrickWall.jpg", |
||||
"Textures/Terrain/Pond/Pond.jpg", |
||||
"Textures/Terrain/Pond/Pond_normal.png", |
||||
"Textures/Terrain/Rock/Rock.PNG", |
||||
"Textures/Terrain/Rock/Rock_normal.png", |
||||
"Textures/Terrain/Rock2/rock.jpg", |
||||
"Textures/Terrain/Rocky/RockyNormals.jpg", |
||||
"Textures/Terrain/Rocky/RockyTexture.jpg", |
||||
"Textures/Terrain/splat/alpha1.png", |
||||
"Textures/Terrain/splat/alpha2.png", |
||||
"Textures/Terrain/splat/alphamap.png", |
||||
"Textures/Terrain/splat/alphamap2.png", |
||||
"Textures/Terrain/splat/dirt.jpg", |
||||
"Textures/Terrain/splat/dirt_normal.png", |
||||
"Textures/Terrain/splat/fortress512.png", |
||||
"Textures/Terrain/splat/grass.jpg", |
||||
"Textures/Terrain/splat/grass_normal.jpg", |
||||
"Textures/Terrain/splat/mountains128.png", |
||||
"Textures/Terrain/splat/road.jpg", |
||||
"Textures/Terrain/splat/road_normal.png", |
||||
}; |
||||
|
||||
public static void main(String[] args) { |
||||
TestUniqueGeometries app = new TestUniqueGeometries(); |
||||
AppSettings settings = new AppSettings(true); |
||||
settings.putBoolean("GraphicsTrace", false); |
||||
settings.putBoolean("GraphicsTiming", true); |
||||
app.setSettings(settings); |
||||
app.start(); |
||||
} |
||||
|
||||
private void loadRandomMaterials() { |
||||
for (int i = 0; i < randomMaterials.length; i++) { |
||||
Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); |
||||
mat.setBoolean("VertexLighting", true); |
||||
mat.setBoolean("UseMaterialColors", true); |
||||
mat.setColor("Ambient", ColorRGBA.Black); |
||||
mat.setColor("Diffuse", ColorRGBA.White); |
||||
mat.setColor("Specular", ColorRGBA.White); |
||||
mat.setFloat("Shininess", 32); |
||||
mat.setTexture("DiffuseMap", assetManager.loadTexture(textureList[i % textureList.length])); |
||||
randomMaterials[i] = mat; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void simpleInitApp() { |
||||
flyCam.setDragToRotate(true); |
||||
|
||||
cam.setLocation(new Vector3f(22.717342f, 18.366547f, 22.043106f)); |
||||
cam.setRotation(new Quaternion(-0.11630201f, 0.8794429f, -0.27703872f, -0.36919326f)); |
||||
|
||||
DirectionalLight dl = new DirectionalLight(); |
||||
dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); |
||||
rootNode.addLight(dl); |
||||
|
||||
flyCam.setMoveSpeed(5); |
||||
|
||||
loadRandomMaterials(); |
||||
|
||||
// Box box = new Box(1,1,1);
|
||||
|
||||
for (int y = -25; y < 25; y++) { |
||||
for (int x = -25; x < 25; x++) { |
||||
Material mat = randomMaterials[0]; // randomMaterials[FastMath.nextRandomInt(0, randomMaterials.length - 1)];
|
||||
|
||||
Box box = new Box(1,1,1); |
||||
Geometry boxClone = new Geometry("box", box); |
||||
boxClone.setMaterial(mat); |
||||
|
||||
boxClone.setLocalTranslation(x * .5f, 0, y * .5f); |
||||
boxClone.setLocalScale(.15f); |
||||
boxClone.setMaterial(mat); |
||||
rootNode.attachChild(boxClone); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
if (!hasProperty('mainClass')) { |
||||
ext.mainClass = '' |
||||
} |
||||
|
||||
dependencies { |
||||
compile project(':jme3-core') |
||||
compile project(':jme3-desktop') |
||||
compile 'net.java.jinput:jinput:2.0.6' |
||||
compile 'net.java.jinput:jinput-platform:2.0.6' |
||||
|
||||
testCompile project(path: ':jme3-core', configuration: 'testOutput') |
||||
} |
@ -1,529 +0,0 @@ |
||||
/* |
||||
* Copyright (c) 2009-2012 jMonkeyEngine |
||||
* 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 'jMonkeyEngine' 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 com.jme3.renderer.jogl; |
||||
|
||||
import com.jme3.renderer.RendererException; |
||||
import com.jme3.texture.Image; |
||||
import com.jme3.texture.Image.Format; |
||||
import com.jme3.texture.image.ColorSpace; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
import java.util.logging.Level; |
||||
import java.util.logging.Logger; |
||||
|
||||
import com.jogamp.opengl.GL; |
||||
import com.jogamp.opengl.GL2; |
||||
import com.jogamp.opengl.GL2ES2; |
||||
import com.jogamp.opengl.GL2ES3; |
||||
import com.jogamp.opengl.GL2GL3; |
||||
import com.jogamp.opengl.GLContext; |
||||
|
||||
public class TextureUtil { |
||||
|
||||
private static boolean abgrToRgbaConversionEnabled = false; |
||||
|
||||
public static int convertTextureFormat(Format fmt) { |
||||
switch (fmt) { |
||||
case Alpha8: |
||||
return GL.GL_ALPHA; |
||||
case Luminance8Alpha8: |
||||
return GL.GL_LUMINANCE_ALPHA; |
||||
case Luminance8: |
||||
return GL.GL_LUMINANCE; |
||||
case BGR8: |
||||
case RGB8: |
||||
case RGB565: |
||||
return GL.GL_RGB; |
||||
case RGB5A1: |
||||
case RGBA8: |
||||
return GL.GL_RGBA; |
||||
case Depth: |
||||
return GL2ES2.GL_DEPTH_COMPONENT; |
||||
default: |
||||
throw new UnsupportedOperationException("Unrecognized format: " + fmt); |
||||
} |
||||
} |
||||
|
||||
public static class GLImageFormat { |
||||
|
||||
int internalFormat; |
||||
int format; |
||||
int dataType; |
||||
boolean compressed; |
||||
|
||||
public GLImageFormat(int internalFormat, int format, int dataType, boolean compressed) { |
||||
this.internalFormat = internalFormat; |
||||
this.format = format; |
||||
this.dataType = dataType; |
||||
this.compressed = compressed; |
||||
} |
||||
} |
||||
|
||||
private static final GLImageFormat[] formatToGL = new GLImageFormat[Format.values().length]; |
||||
|
||||
private static void setFormat(Format format, int glInternalFormat, int glFormat, int glDataType, boolean glCompressed){ |
||||
formatToGL[format.ordinal()] = new GLImageFormat(glInternalFormat, glFormat, glDataType, glCompressed); |
||||
} |
||||
|
||||
static { |
||||
// Alpha formats
|
||||
setFormat(Format.Alpha8, GL2.GL_ALPHA8, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE, false); |
||||
|
||||
// Luminance formats
|
||||
setFormat(Format.Luminance8, GL2.GL_LUMINANCE8, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE, false); |
||||
setFormat(Format.Luminance16F, GL2.GL_LUMINANCE16F, GL.GL_LUMINANCE, GL.GL_HALF_FLOAT, false); |
||||
setFormat(Format.Luminance32F, GL2.GL_LUMINANCE32F, GL.GL_LUMINANCE, GL.GL_FLOAT, false); |
||||
|
||||
// Luminance alpha formats
|
||||
setFormat(Format.Luminance8Alpha8, GL2.GL_LUMINANCE8_ALPHA8, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE, false); |
||||
setFormat(Format.Luminance16FAlpha16F, GL2.GL_LUMINANCE_ALPHA16F, GL.GL_LUMINANCE_ALPHA, GL.GL_HALF_FLOAT, false); |
||||
|
||||
// Depth formats
|
||||
setFormat(Format.Depth, GL2ES2.GL_DEPTH_COMPONENT, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_BYTE, false); |
||||
setFormat(Format.Depth16, GL.GL_DEPTH_COMPONENT16, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_SHORT, false); |
||||
setFormat(Format.Depth24, GL.GL_DEPTH_COMPONENT24, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_INT, false); |
||||
setFormat(Format.Depth32, GL.GL_DEPTH_COMPONENT32, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_INT, false); |
||||
setFormat(Format.Depth32F, GL2GL3.GL_DEPTH_COMPONENT32F, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_FLOAT, false); |
||||
|
||||
// Depth stencil formats
|
||||
setFormat(Format.Depth24Stencil8, GL.GL_DEPTH24_STENCIL8, GL.GL_DEPTH_STENCIL, GL.GL_UNSIGNED_INT_24_8, false); |
||||
|
||||
// RGB formats
|
||||
setFormat(Format.BGR8, GL.GL_RGB8, GL2GL3.GL_BGR, GL.GL_UNSIGNED_BYTE, false); |
||||
setFormat(Format.ARGB8, GL.GL_RGBA8, GL.GL_BGRA, GL2.GL_UNSIGNED_INT_8_8_8_8_REV, false); |
||||
setFormat(Format.BGRA8, GL.GL_RGBA8, GL.GL_BGRA, GL.GL_UNSIGNED_BYTE, false); |
||||
setFormat(Format.RGB8, GL.GL_RGB8, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, false); |
||||
setFormat(Format.RGB16F, GL2ES2.GL_RGB16F, GL.GL_RGB, GL.GL_HALF_FLOAT, false); |
||||
setFormat(Format.RGB32F, GL.GL_RGB32F, GL.GL_RGB, GL.GL_FLOAT, false); |
||||
|
||||
// Special RGB formats
|
||||
setFormat(Format.RGB111110F, GL2ES3.GL_R11F_G11F_B10F, GL.GL_RGB, GL.GL_UNSIGNED_INT_10F_11F_11F_REV, false); |
||||
setFormat(Format.RGB9E5, GL2GL3.GL_RGB9_E5, GL.GL_RGB, GL2GL3.GL_UNSIGNED_INT_5_9_9_9_REV, false); |
||||
setFormat(Format.RGB16F_to_RGB111110F, GL2ES3.GL_R11F_G11F_B10F, GL.GL_RGB, GL.GL_HALF_FLOAT, false); |
||||
setFormat(Format.RGB16F_to_RGB9E5, GL2.GL_RGB9_E5, GL.GL_RGB, GL.GL_HALF_FLOAT, false); |
||||
|
||||
// RGBA formats
|
||||
setFormat(Format.ABGR8, GL.GL_RGBA8, GL2.GL_ABGR_EXT, GL.GL_UNSIGNED_BYTE, false); |
||||
setFormat(Format.RGB5A1, GL.GL_RGB5_A1, GL.GL_RGBA, GL.GL_UNSIGNED_SHORT_5_5_5_1, false); |
||||
setFormat(Format.RGBA8, GL.GL_RGBA8, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, false); |
||||
setFormat(Format.RGBA16F, GL2ES2.GL_RGBA16F, GL.GL_RGBA, GL.GL_HALF_FLOAT, false); |
||||
setFormat(Format.RGBA32F, GL.GL_RGBA32F, GL.GL_RGBA, GL.GL_FLOAT, false); |
||||
|
||||
// DXT formats
|
||||
setFormat(Format.DXT1, GL.GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, true); |
||||
setFormat(Format.DXT1A, GL.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true); |
||||
setFormat(Format.DXT3, GL.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true); |
||||
setFormat(Format.DXT5, GL.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true); |
||||
} |
||||
|
||||
//sRGB formats
|
||||
private static final GLImageFormat sRGB_RGB8 = new GLImageFormat(GL2.GL_SRGB8,GL.GL_RGB, GL.GL_UNSIGNED_BYTE, false); |
||||
private static final GLImageFormat sRGB_RGBA8 = new GLImageFormat(GL.GL_SRGB8_ALPHA8,GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, false); |
||||
private static final GLImageFormat sRGB_Luminance8 = new GLImageFormat(GL2.GL_SLUMINANCE8,GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE, false); |
||||
private static final GLImageFormat sRGB_LuminanceAlpha8 = new GLImageFormat(GL2.GL_SLUMINANCE8_ALPHA8,GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE, false); |
||||
private static final GLImageFormat sRGB_BGR8 = new GLImageFormat(GL2.GL_SRGB8, GL2GL3.GL_BGR, GL.GL_UNSIGNED_BYTE, false); |
||||
private static final GLImageFormat sRGB_ABGR8 = new GLImageFormat(GL2.GL_SRGB8_ALPHA8, GL2.GL_ABGR_EXT, GL.GL_UNSIGNED_BYTE, false); |
||||
|
||||
//FIXME cannot find GL_COMPRESSED_RGB_S3TC_DXT1,GL_COMPRESSED_RGBA_S3TC_DXT1,GL_COMPRESSED_RGB_S3TC_DXT3,GL_COMPRESSED_RGB_S3TC_DXT5 in JOGL used constants
|
||||
//GL_COMPRESSED_RGB_S3TC_DXT1 = 33776;
|
||||
//GL_COMPRESSED_RGBA_S3TC_DXT1_EXT = 33777;
|
||||
//GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 33778;
|
||||
//GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 33779;
|
||||
private static final GLImageFormat sRGB_DXT1 = new GLImageFormat(33776, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, true); |
||||
private static final GLImageFormat sRGB_DXT1A = new GLImageFormat( 33777, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true); |
||||
private static final GLImageFormat sRGB_DXT3 = new GLImageFormat(33778, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true); |
||||
private static final GLImageFormat sRGB_DXT5 = new GLImageFormat( 33779, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true); |
||||
|
||||
|
||||
public static GLImageFormat getImageFormat(Format fmt, boolean isSrgb){ |
||||
GL gl = GLContext.getCurrentGL(); |
||||
switch (fmt){ |
||||
case ABGR8: |
||||
if (!gl.isExtensionAvailable("GL_EXT_abgr") && !abgrToRgbaConversionEnabled) { |
||||
setFormat(Format.ABGR8, GL.GL_RGBA, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, false); |
||||
abgrToRgbaConversionEnabled = true; |
||||
} |
||||
break; |
||||
case BGR8: |
||||
if (!gl.isExtensionAvailable("GL_VERSION_1_2") && !gl.isExtensionAvailable("EXT_bgra")){ |
||||
return null; |
||||
} |
||||
break; |
||||
case DXT1: |
||||
case DXT1A: |
||||
case DXT3: |
||||
case DXT5: |
||||
if (!gl.isExtensionAvailable("GL_EXT_texture_compression_s3tc")) { |
||||
return null; |
||||
} |
||||
break; |
||||
case Depth: |
||||
case Depth16: |
||||
case Depth24: |
||||
case Depth32: |
||||
if (!gl.isExtensionAvailable("GL_VERSION_1_4") && !gl.isExtensionAvailable("ARB_depth_texture")){ |
||||
return null; |
||||
} |
||||
break; |
||||
case Depth24Stencil8: |
||||
if (!gl.isExtensionAvailable("GL_VERSION_3_0")){ |
||||
return null; |
||||
} |
||||
break; |
||||
case Luminance16F: |
||||
case Luminance16FAlpha16F: |
||||
case Luminance32F: |
||||
if (!gl.isExtensionAvailable("GL_ARB_texture_float")){ |
||||
return null; |
||||
} |
||||
break; |
||||
case RGB16F: |
||||
case RGB32F: |
||||
case RGBA16F: |
||||
case RGBA32F: |
||||
if (!gl.isExtensionAvailable("GL_VERSION_3_0") && !gl.isExtensionAvailable("GL_ARB_texture_float")){ |
||||
return null; |
||||
} |
||||
break; |
||||
case Depth32F: |
||||
if (!gl.isExtensionAvailable("GL_VERSION_3_0") && !gl.isExtensionAvailable("GL_NV_depth_buffer_float")){ |
||||
return null; |
||||
} |
||||
break; |
||||
case RGB9E5: |
||||
case RGB16F_to_RGB9E5: |
||||
if (!gl.isExtensionAvailable("GL_VERSION_3_0") && !gl.isExtensionAvailable("GL_EXT_texture_shared_exponent")){ |
||||
return null; |
||||
} |
||||
break; |
||||
case RGB111110F: |
||||
case RGB16F_to_RGB111110F: |
||||
if (!gl.isExtensionAvailable("GL_VERSION_3_0") && !gl.isExtensionAvailable("GL_EXT_packed_float")){ |
||||
return null; |
||||
} |
||||
break; |
||||
} |
||||
|
||||
if(isSrgb){ |
||||
return getSrgbFormat(fmt); |
||||
} |
||||
return formatToGL[fmt.ordinal()]; |
||||
} |
||||
|
||||
public static GLImageFormat getImageFormatWithError(Format fmt, boolean isSrgb) { |
||||
GLImageFormat glFmt = getImageFormat(fmt, isSrgb); |
||||
if (glFmt == null) { |
||||
throw new RendererException("Image format '" + fmt + "' is unsupported by the video hardware."); |
||||
} |
||||
return glFmt; |
||||
} |
||||
|
||||
private static GLImageFormat getSrgbFormat(Format fmt){ |
||||
switch (fmt){ |
||||
case RGB8 : return sRGB_RGB8; |
||||
case RGBA8 : return sRGB_RGBA8; |
||||
case BGR8 : return sRGB_BGR8; |
||||
case ABGR8 : return sRGB_ABGR8; |
||||
case Luminance8 : return sRGB_Luminance8; |
||||
case Luminance8Alpha8 : return sRGB_LuminanceAlpha8; |
||||
case DXT1 : return sRGB_DXT1; |
||||
case DXT1A : return sRGB_DXT1A; |
||||
case DXT3 : return sRGB_DXT3; |
||||
case DXT5 : return sRGB_DXT5; |
||||
default : Logger.getLogger(TextureUtil.class.getName()).log(Level.WARNING, "Format {0} has no sRGB equivalent, using linear format.", fmt.toString()); |
||||
return formatToGL[fmt.ordinal()]; |
||||
} |
||||
} |
||||
|
||||
public static void uploadTexture(Image image, |
||||
int target, |
||||
int index, |
||||
int border, |
||||
boolean linearizeSrgb){ |
||||
GL gl = GLContext.getCurrentGL(); |
||||
Image.Format fmt = image.getFormat(); |
||||
GLImageFormat glFmt = getImageFormatWithError(fmt, image.getColorSpace() == ColorSpace.sRGB && linearizeSrgb); |
||||
|
||||
ByteBuffer data; |
||||
if (index >= 0 && image.getData() != null && image.getData().size() > 0){ |
||||
data = image.getData(index); |
||||
}else{ |
||||
data = null; |
||||
} |
||||
|
||||
int width = image.getWidth(); |
||||
int height = image.getHeight(); |
||||
int depth = image.getDepth(); |
||||
|
||||
if (data != null) { |
||||
if (abgrToRgbaConversionEnabled) { |
||||
convertABGRtoRGBA(data); |
||||
} |
||||
gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1); |
||||
} |
||||
|
||||
int[] mipSizes = image.getMipMapSizes(); |
||||
int pos = 0; |
||||
// TODO: Remove unneccessary allocation
|
||||
if (mipSizes == null){ |
||||
if (data != null) |
||||
mipSizes = new int[]{ data.capacity() }; |
||||
else |
||||
mipSizes = new int[]{ width * height * fmt.getBitsPerPixel() / 8 }; |
||||
} |
||||
|
||||
boolean subtex = false; |
||||
int samples = image.getMultiSamples(); |
||||
|
||||
for (int i = 0; i < mipSizes.length; i++){ |
||||
int mipWidth = Math.max(1, width >> i); |
||||
int mipHeight = Math.max(1, height >> i); |
||||
int mipDepth = Math.max(1, depth >> i); |
||||
|
||||
if (data != null){ |
||||
data.position(pos); |
||||
data.limit(pos + mipSizes[i]); |
||||
} |
||||
|
||||
if (glFmt.compressed && data != null){ |
||||
if (target == GL2ES2.GL_TEXTURE_3D){ |
||||
gl.getGL2ES2().glCompressedTexImage3D(target, |
||||
i, |
||||
glFmt.internalFormat, |
||||
mipWidth, |
||||
mipHeight, |
||||
mipDepth, |
||||
data.remaining(), |
||||
border, |
||||
data); |
||||
}else{ |
||||
//all other targets use 2D: array, cubemap, 2d
|
||||
gl.glCompressedTexImage2D(target, |
||||
i, |
||||
glFmt.internalFormat, |
||||
mipWidth, |
||||
mipHeight, |
||||
data.remaining(), |
||||
border, |
||||
data); |
||||
} |
||||
}else{ |
||||
if (target == GL2ES2.GL_TEXTURE_3D){ |
||||
gl.getGL2ES2().glTexImage3D(target, |
||||
i, |
||||
glFmt.internalFormat, |
||||
mipWidth, |
||||
mipHeight, |
||||
mipDepth, |
||||
border, |
||||
glFmt.format, |
||||
glFmt.dataType, |
||||
data); |
||||
}else if (target == GL2ES3.GL_TEXTURE_2D_ARRAY){ |
||||
// prepare data for 2D array
|
||||
// or upload slice
|
||||
if (index == -1){ |
||||
gl.getGL2ES2().glTexImage3D(target, |
||||
0, |
||||
glFmt.internalFormat, |
||||
mipWidth, |
||||
mipHeight, |
||||
image.getData().size(), //# of slices
|
||||
border, |
||||
glFmt.format, |
||||
glFmt.dataType, |
||||
data); |
||||
}else{ |
||||
gl.getGL2ES2().glTexSubImage3D(target, |
||||
i, // level
|
||||
0, // xoffset
|
||||
0, // yoffset
|
||||
index, // zoffset
|
||||
width, // width
|
||||
height, // height
|
||||
1, // depth
|
||||
glFmt.format, |
||||
glFmt.dataType, |
||||
data); |
||||
} |
||||
}else{ |
||||
if (subtex){ |
||||
if (samples > 1){ |
||||
throw new IllegalStateException("Cannot update multisample textures"); |
||||
} |
||||
|
||||
gl.glTexSubImage2D(target, |
||||
i, |
||||
0, 0, |
||||
mipWidth, mipHeight, |
||||
glFmt.format, |
||||
glFmt.dataType, |
||||
data); |
||||
}else{ |
||||
if (samples > 1){ |
||||
if (gl.isGL2GL3()) { |
||||
gl.getGL3().glTexImage2DMultisample(target, |
||||
samples, |
||||
glFmt.internalFormat, |
||||
mipWidth, |
||||
mipHeight, |
||||
true); |
||||
} |
||||
} else { |
||||
gl.glTexImage2D(target, |
||||
i, |
||||
glFmt.internalFormat, |
||||
mipWidth, |
||||
mipHeight, |
||||
border, |
||||
glFmt.format, |
||||
glFmt.dataType, |
||||
data); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
pos += mipSizes[i]; |
||||
} |
||||
} |
||||
|
||||
private static void convertABGRtoRGBA(ByteBuffer buffer) { |
||||
|
||||
for (int i = 0; i < buffer.capacity(); i++) { |
||||
|
||||
int a = buffer.get(i++); |
||||
int b = buffer.get(i++); |
||||
int g = buffer.get(i++); |
||||
int r = buffer.get(i); |
||||
|
||||
buffer.put(i - 3, (byte) r); |
||||
buffer.put(i - 2, (byte) g); |
||||
buffer.put(i - 1, (byte) b); |
||||
buffer.put(i, (byte) a); |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Update the texture currently bound to target at with data from the given Image at position x and y. The parameter |
||||
* index is used as the zoffset in case a 3d texture or texture 2d array is being updated. |
||||
* |
||||
* @param image Image with the source data (this data will be put into the texture) |
||||
* @param target the target texture |
||||
* @param index the mipmap level to update |
||||
* @param x the x position where to put the image in the texture |
||||
* @param y the y position where to put the image in the texture |
||||
*/ |
||||
public static void uploadSubTexture( |
||||
Image image, |
||||
int target, |
||||
int index, |
||||
int x, |
||||
int y, |
||||
boolean linearizeSrgb) { |
||||
GL gl = GLContext.getCurrentGL(); |
||||
Image.Format fmt = image.getFormat(); |
||||
GLImageFormat glFmt = getImageFormatWithError(fmt, image.getColorSpace() == ColorSpace.sRGB && linearizeSrgb); |
||||
|
||||
ByteBuffer data = null; |
||||
if (index >= 0 && image.getData() != null && image.getData().size() > 0) { |
||||
data = image.getData(index); |
||||
} |
||||
|
||||
int width = image.getWidth(); |
||||
int height = image.getHeight(); |
||||
int depth = image.getDepth(); |
||||
|
||||
if (data != null) { |
||||
gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1); |
||||
} |
||||
|
||||
int[] mipSizes = image.getMipMapSizes(); |
||||
int pos = 0; |
||||
|
||||
// TODO: Remove unneccessary allocation
|
||||
if (mipSizes == null){ |
||||
if (data != null) { |
||||
mipSizes = new int[]{ data.capacity() }; |
||||
} else { |
||||
mipSizes = new int[]{ width * height * fmt.getBitsPerPixel() / 8 }; |
||||
} |
||||
} |
||||
|
||||
int samples = image.getMultiSamples(); |
||||
|
||||
for (int i = 0; i < mipSizes.length; i++){ |
||||
int mipWidth = Math.max(1, width >> i); |
||||
int mipHeight = Math.max(1, height >> i); |
||||
int mipDepth = Math.max(1, depth >> i); |
||||
|
||||
if (data != null){ |
||||
data.position(pos); |
||||
data.limit(pos + mipSizes[i]); |
||||
} |
||||
|
||||
// to remove the cumbersome if/then/else stuff below we'll update the pos right here and use continue after each
|
||||
// gl*Image call in an attempt to unclutter things a bit
|
||||
pos += mipSizes[i]; |
||||
|
||||
int glFmtInternal = glFmt.internalFormat; |
||||
int glFmtFormat = glFmt.format; |
||||
int glFmtDataType = glFmt.dataType; |
||||
|
||||
if (glFmt.compressed && data != null){ |
||||
if (target == GL2ES2.GL_TEXTURE_3D){ |
||||
gl.getGL2ES2().glCompressedTexSubImage3D(target, i, x, y, index, mipWidth, mipHeight, mipDepth, glFmtInternal, data.limit(), data); |
||||
continue; |
||||
} |
||||
|
||||
// all other targets use 2D: array, cubemap, 2d
|
||||
gl.getGL2ES2().glCompressedTexSubImage2D(target, i, x, y, mipWidth, mipHeight, glFmtInternal, data.limit(), data); |
||||
continue; |
||||
} |
||||
|
||||
if (target == GL2ES2.GL_TEXTURE_3D){ |
||||
gl.getGL2ES2().glTexSubImage3D(target, i, x, y, index, mipWidth, mipHeight, mipDepth, glFmtFormat, glFmtDataType, data); |
||||
continue; |
||||
} |
||||
|
||||
if (samples > 1){ |
||||
throw new IllegalStateException("Cannot update multisample textures"); |
||||
} |
||||
|
||||
gl.glTexSubImage2D(target, i, x, y, mipWidth, mipHeight, glFmtFormat, glFmtDataType, data); |
||||
continue; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,112 @@ |
||||
/* |
||||
* Copyright (c) 2009-2015 jMonkeyEngine |
||||
* 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 'jMonkeyEngine' 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 com.jme3.scene.plugins.fbx.anim; |
||||
|
||||
import com.jme3.animation.Bone; |
||||
import com.jme3.animation.Skeleton; |
||||
import com.jme3.scene.plugins.fbx.node.FbxNode; |
||||
import java.util.ArrayList; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* Similar to {@link Skeleton jME skeleton} except |
||||
* contains {@link FbxLimbNode limb nodes}. |
||||
* |
||||
* This is used to determine the bone indices (for assigning clusters to meshes) |
||||
* as well as the limb hierarchy when creating the jME3 Skeleton. |
||||
* |
||||
* @author Kirill Vainer |
||||
*/ |
||||
public class FbxSkeleton { |
||||
|
||||
FbxLimbNode[] rootLimbs; |
||||
FbxLimbNode[] allLimbs; |
||||
HashMap<FbxLimbNode, Integer> limbToIndexMap = new HashMap<FbxLimbNode, Integer>(); |
||||
|
||||
private FbxSkeleton() { |
||||
} |
||||
|
||||
public static void populateSkeletonData(FbxNode skeletonRoot) { |
||||
// if (skeletonRoot instanceof FbxLimbNode) {
|
||||
// throw new UnsupportedOperationException("Limb node cannot be a skeleton root");
|
||||
// }
|
||||
//
|
||||
// FbxSkeleton skeleton = new FbxSkeleton();
|
||||
// skeleton.scanLimbs(skeletonRoot);
|
||||
// skeletonRoot.setFbxSkeleton(skeleton);
|
||||
} |
||||
|
||||
private void scanLimbs(FbxNode skeletonRoot, FbxLimbNode limb, List<FbxLimbNode> limbList) { |
||||
// limb.skeletonRoot = skeletonRoot;
|
||||
// limbList.add(limb);
|
||||
// for (FbxNode child : limb.getChildren()) {
|
||||
// if (child instanceof FbxLimbNode) {
|
||||
// FbxLimbNode childLimb = (FbxLimbNode) child;
|
||||
// scanLimbs(skeletonRoot, childLimb, limbList);
|
||||
// }
|
||||
// }
|
||||
} |
||||
|
||||
private void scanLimbs(FbxNode skeletonRoot) { |
||||
List<FbxLimbNode> limbList = new ArrayList<FbxLimbNode>(); |
||||
List<FbxLimbNode> rootList = new ArrayList<FbxLimbNode>(); |
||||
|
||||
for (FbxNode child : skeletonRoot.getChildren()) { |
||||
if (child instanceof FbxLimbNode) { |
||||
FbxLimbNode limb = (FbxLimbNode) child; |
||||
rootList.add(limb); |
||||
scanLimbs(skeletonRoot, limb, limbList); |
||||
} |
||||
} |
||||
|
||||
allLimbs = limbList.toArray(new FbxLimbNode[0]); |
||||
rootLimbs = rootList.toArray(new FbxLimbNode[0]); |
||||
|
||||
for (int i = 0; i < allLimbs.length; i++) { |
||||
limbToIndexMap.put(allLimbs[i], i); |
||||
} |
||||
} |
||||
|
||||
public int getLimbIndex(FbxLimbNode limbNode) { |
||||
return limbToIndexMap.get(limbNode); |
||||
} |
||||
|
||||
public FbxLimbNode getLimb(int index) { |
||||
return allLimbs[index]; |
||||
} |
||||
|
||||
public FbxLimbNode[] getRootLimbs() { |
||||
return rootLimbs; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,241 @@ |
||||
/* |
||||
* Copyright (c) 2009-2015 jMonkeyEngine |
||||
* 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 'jMonkeyEngine' 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 com.jme3.scene.plugins.triangulator; |
||||
|
||||
import com.jme3.math.FastMath; |
||||
import com.jme3.math.Matrix3f; |
||||
import com.jme3.math.Vector2f; |
||||
import com.jme3.math.Vector3f; |
||||
import com.jme3.scene.plugins.IrPolygon; |
||||
import com.jme3.scene.plugins.IrVertex; |
||||
import java.util.ArrayList; |
||||
|
||||
/** |
||||
* Implemented according to |
||||
* <ul> |
||||
* <li>http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf</li>
|
||||
* <li>http://cgm.cs.mcgill.ca/~godfried/teaching/cg-projects/97/Ian/algorithm2.html</li>
|
||||
* </ul> |
||||
*/ |
||||
public final class EarClippingTriangulator { |
||||
|
||||
private static enum VertexType { |
||||
Convex, |
||||
Reflex, |
||||
Ear; |
||||
} |
||||
|
||||
private final ArrayList<Integer> indices = new ArrayList<Integer>(); |
||||
private final ArrayList<VertexType> types = new ArrayList<VertexType>(); |
||||
private final ArrayList<Vector2f> positions = new ArrayList<Vector2f>(); |
||||
|
||||
public EarClippingTriangulator() { |
||||
} |
||||
|
||||
private static int ccw(Vector2f p0, Vector2f p1, Vector2f p2) { |
||||
float result = (p1.x - p0.x) * (p2.y - p1.y) - (p1.y - p0.y) * (p2.x - p1.x); |
||||
if (result > 0) { |
||||
return 1; |
||||
} else if (result < 0) { |
||||
return -1; |
||||
} else { |
||||
return 0; |
||||
} |
||||
} |
||||
|
||||
private static boolean pointInTriangle(Vector2f t0, Vector2f t1, Vector2f t2, Vector2f p) { |
||||
float d = ((t1.y - t2.y) * (t0.x - t2.x) + (t2.x - t1.x) * (t0.y - t2.y)); |
||||
float a = ((t1.y - t2.y) * (p.x - t2.x) + (t2.x - t1.x) * (p.y - t2.y)) / d; |
||||
float b = ((t2.y - t0.y) * (p.x - t2.x) + (t0.x - t2.x) * (p.y - t2.y)) / d; |
||||
float c = 1 - a - b; |
||||
return 0 <= a && a <= 1 && 0 <= b && b <= 1 && 0 <= c && c <= 1; |
||||
} |
||||
|
||||
private static Matrix3f normalToMatrix(Vector3f norm) { |
||||
Vector3f tang1 = norm.cross(Vector3f.UNIT_X); |
||||
if (tang1.lengthSquared() < FastMath.ZERO_TOLERANCE) { |
||||
tang1 = norm.cross(Vector3f.UNIT_Y); |
||||
} |
||||
tang1.normalizeLocal(); |
||||
Vector3f tang2 = norm.cross(tang1).normalizeLocal(); |
||||
|
||||
return new Matrix3f( |
||||
tang1.x, tang1.y, tang1.z, |
||||
tang2.x, tang2.y, tang2.z, |
||||
norm.x, norm.y, norm.z); |
||||
} |
||||
|
||||
private int prev(int index) { |
||||
if (index == 0) { |
||||
return indices.size() - 1; |
||||
} else { |
||||
return index - 1; |
||||
} |
||||
} |
||||
|
||||
private int next(int index) { |
||||
if (index == indices.size() - 1) { |
||||
return 0; |
||||
} else { |
||||
return index + 1; |
||||
} |
||||
} |
||||
|
||||
private VertexType calcType(int index) { |
||||
int prev = prev(index); |
||||
int next = next(index); |
||||
|
||||
Vector2f p0 = positions.get(prev); |
||||
Vector2f p1 = positions.get(index); |
||||
Vector2f p2 = positions.get(next); |
||||
|
||||
if (ccw(p0, p1, p2) <= 0) { |
||||
return VertexType.Reflex; |
||||
} else { |
||||
for (int i = 0; i < positions.size() - 3; i++) { |
||||
int testIndex = (index + 2 + i) % positions.size(); |
||||
if (types.get(testIndex) != VertexType.Reflex) { |
||||
continue; |
||||
} |
||||
Vector2f p = positions.get(testIndex); |
||||
if (pointInTriangle(p0, p1, p2, p)) { |
||||
return VertexType.Convex; |
||||
} |
||||
} |
||||
return VertexType.Ear; |
||||
} |
||||
} |
||||
|
||||
private void updateType(int index) { |
||||
if (types.get(index) == VertexType.Convex) { |
||||
return; |
||||
} |
||||
types.set(index, calcType(index)); |
||||
} |
||||
|
||||
private void loadVertices(IrVertex[] vertices) { |
||||
indices.ensureCapacity(vertices.length); |
||||
types.ensureCapacity(vertices.length); |
||||
positions.ensureCapacity(vertices.length); |
||||
|
||||
Vector3f normal = FastMath.computeNormal( |
||||
vertices[0].pos, |
||||
vertices[1].pos, |
||||
vertices[2].pos); |
||||
|
||||
Matrix3f transform = normalToMatrix(normal); |
||||
|
||||
for (int i = 0; i < vertices.length; i++) { |
||||
Vector3f projected = transform.mult(vertices[i].pos); |
||||
indices.add(i); |
||||
positions.add(new Vector2f(projected.x, projected.y)); |
||||
types.add(VertexType.Reflex); |
||||
} |
||||
|
||||
for (int i = 0; i < vertices.length; i++) { |
||||
types.set(i, calcType(i)); |
||||
} |
||||
} |
||||
|
||||
private IrPolygon createTriangle(IrPolygon polygon, int prev, int index, int next) { |
||||
int p0 = indices.get(prev); |
||||
int p1 = indices.get(index); |
||||
int p2 = indices.get(next); |
||||
IrPolygon triangle = new IrPolygon(); |
||||
triangle.vertices = new IrVertex[] { |
||||
polygon.vertices[p0], |
||||
polygon.vertices[p1], |
||||
polygon.vertices[p2], |
||||
}; |
||||
return triangle; |
||||
} |
||||
|
||||
/** |
||||
* Triangulates the given polygon. |
||||
* |
||||
* Five or more vertices are required, if less are given, an exception |
||||
* is thrown. |
||||
* |
||||
* @param polygon The polygon to triangulate. |
||||
* @return N - 2 triangles, where N is the number of vertices in the polygon. |
||||
* |
||||
* @throws IllegalArgumentException If the polygon has less than 5 vertices. |
||||
*/ |
||||
public IrPolygon[] triangulate(IrPolygon polygon) { |
||||
if (polygon.vertices.length < 5) { |
||||
throw new IllegalArgumentException("Only polygons with 5 or more vertices are supported"); |
||||
} |
||||
|
||||
try { |
||||
int numTris = 0; |
||||
IrPolygon[] triangles = new IrPolygon[polygon.vertices.length - 2]; |
||||
|
||||
loadVertices(polygon.vertices); |
||||
|
||||
int index = 0; |
||||
while (types.size() > 3) { |
||||
if (types.get(index) == VertexType.Ear) { |
||||
int prev = prev(index); |
||||
int next = next(index); |
||||
|
||||
triangles[numTris++] = createTriangle(polygon, prev, index, next); |
||||
|
||||
indices.remove(index); |
||||
types.remove(index); |
||||
positions.remove(index); |
||||
|
||||
next = next(prev); |
||||
updateType(prev); |
||||
updateType(next); |
||||
|
||||
index = next(next); |
||||
} else { |
||||
index = next(index); |
||||
} |
||||
} |
||||
|
||||
if (types.size() == 3) { |
||||
triangles[numTris++] = createTriangle(polygon, 0, 1, 2); |
||||
} |
||||
|
||||
if (numTris != triangles.length) { |
||||
throw new AssertionError("Triangulation failed to generate enough triangles"); |
||||
} |
||||
|
||||
return triangles; |
||||
} finally { |
||||
indices.clear(); |
||||
positions.clear(); |
||||
types.clear(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,64 @@ |
||||
/* |
||||
* Copyright (c) 2009-2015 jMonkeyEngine |
||||
* 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 'jMonkeyEngine' 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 com.jme3.scene.plugins.triangulator; |
||||
|
||||
import com.jme3.math.Vector3f; |
||||
import com.jme3.scene.plugins.IrPolygon; |
||||
import com.jme3.scene.plugins.IrVertex; |
||||
import junit.framework.TestCase; |
||||
|
||||
public class TriangulatorTest extends TestCase { |
||||
|
||||
public void testTriangulator() { |
||||
Vector3f[] dataSet = new Vector3f[]{ |
||||
new Vector3f(0.75f, 0.3f, 1.2f), |
||||
new Vector3f(0.75f, 0.3f, 0.0f), |
||||
new Vector3f(0.75f, 0.17f, 0.0f), |
||||
new Vector3f(0.75000095f, 0.17f, 1.02f), |
||||
new Vector3f(0.75f, -0.17f, 1.02f), |
||||
new Vector3f(0.75f, -0.17f, 0.0f), |
||||
new Vector3f(0.75f, -0.3f, 0.0f), |
||||
new Vector3f(0.75f, -0.3f, 1.2f) |
||||
}; |
||||
|
||||
IrPolygon poly = new IrPolygon(); |
||||
poly.vertices = new IrVertex[dataSet.length]; |
||||
for (int i = 0; i < dataSet.length; i++) { |
||||
poly.vertices[i] = new IrVertex(); |
||||
poly.vertices[i].pos = dataSet[i]; |
||||
} |
||||
|
||||
EarClippingTriangulator triangulator = new EarClippingTriangulator(); |
||||
triangulator.triangulate(poly); |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue