Merge branch 'renderer-fbreadasync' into experimental
This commit is contained in:
commit
c50839796f
280
jme3-core/src/main/java/com/jme3/renderer/opengl/AsyncFrameReader.java
Executable file
280
jme3-core/src/main/java/com/jme3/renderer/opengl/AsyncFrameReader.java
Executable file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
98
jme3-core/src/main/java/com/jme3/renderer/opengl/FrameBufferReadRequest.java
Executable file
98
jme3-core/src/main/java/com/jme3/renderer/opengl/FrameBufferReadRequest.java
Executable file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -64,6 +64,7 @@ import java.util.EnumMap;
|
|||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
@ -100,6 +101,7 @@ public final class GLRenderer implements Renderer {
|
|||||||
private final GLExt glext;
|
private final GLExt glext;
|
||||||
private final GLFbo glfbo;
|
private final GLFbo glfbo;
|
||||||
private final TextureUtil texUtil;
|
private final TextureUtil texUtil;
|
||||||
|
private final AsyncFrameReader frameReader;
|
||||||
|
|
||||||
public GLRenderer(GL gl, GLExt glext, GLFbo glfbo) {
|
public GLRenderer(GL gl, GLExt glext, GLFbo glfbo) {
|
||||||
this.gl = gl;
|
this.gl = gl;
|
||||||
@ -109,6 +111,7 @@ public final class GLRenderer implements Renderer {
|
|||||||
this.glfbo = glfbo;
|
this.glfbo = glfbo;
|
||||||
this.glext = glext;
|
this.glext = glext;
|
||||||
this.texUtil = new TextureUtil(gl, gl2, glext);
|
this.texUtil = new TextureUtil(gl, gl2, glext);
|
||||||
|
this.frameReader = new AsyncFrameReader(this, gl, glext, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -879,6 +882,7 @@ public final class GLRenderer implements Renderer {
|
|||||||
|
|
||||||
public void postFrame() {
|
public void postFrame() {
|
||||||
objManager.deleteUnused(this);
|
objManager.deleteUnused(this);
|
||||||
|
frameReader.updateReadRequests();
|
||||||
gl.resetStats();
|
gl.resetStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1685,11 +1689,11 @@ public final class GLRenderer implements Renderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) {
|
public Future<ByteBuffer> readFrameBufferLater(FrameBuffer fb, ByteBuffer byteBuf) {
|
||||||
readFrameBufferWithGLFormat(fb, byteBuf, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE);
|
return frameReader.readFrameBufferLater(fb, byteBuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readFrameBufferWithGLFormat(FrameBuffer fb, ByteBuffer byteBuf, int glFormat, int dataType) {
|
void readFrameBufferWithGLFormat(FrameBuffer fb, ByteBuffer byteBuf, int glFormat, int dataType, int pboId) {
|
||||||
if (fb != null) {
|
if (fb != null) {
|
||||||
RenderBuffer rb = fb.getColorBuffer();
|
RenderBuffer rb = fb.getColorBuffer();
|
||||||
if (rb == null) {
|
if (rb == null) {
|
||||||
@ -1708,12 +1712,30 @@ public final class GLRenderer implements Renderer {
|
|||||||
setFrameBuffer(null);
|
setFrameBuffer(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context.boundPixelPackPBO != pboId) {
|
||||||
|
gl.glBindBuffer(GLExt.GL_PIXEL_PACK_BUFFER_ARB, pboId);
|
||||||
|
context.boundPixelPackPBO = pboId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byteBuf == null) {
|
||||||
|
gl.glReadPixels(vpX, vpY, vpW, vpH, glFormat, dataType, 0);
|
||||||
|
} else {
|
||||||
gl.glReadPixels(vpX, vpY, vpW, vpH, glFormat, dataType, byteBuf);
|
gl.glReadPixels(vpX, vpY, vpW, vpH, glFormat, dataType, byteBuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context.boundPixelPackPBO != 0) {
|
||||||
|
gl.glBindBuffer(GLExt.GL_PIXEL_PACK_BUFFER_ARB, 0);
|
||||||
|
context.boundPixelPackPBO = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image.Format format) {
|
public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image.Format format) {
|
||||||
GLImageFormat glFormat = texUtil.getImageFormatWithError(format, false);
|
GLImageFormat glFormat = texUtil.getImageFormatWithError(format, false);
|
||||||
readFrameBufferWithGLFormat(fb, byteBuf, glFormat.format, glFormat.dataType);
|
readFrameBufferWithGLFormat(fb, byteBuf, glFormat.format, glFormat.dataType, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) {
|
||||||
|
readFrameBufferWithFormat(fb, byteBuf, Image.Format.RGBA8);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteRenderBuffer(FrameBuffer fb, RenderBuffer rb) {
|
private void deleteRenderBuffer(FrameBuffer fb, RenderBuffer rb) {
|
||||||
|
@ -34,6 +34,7 @@ package com.jme3.system.awt;
|
|||||||
import com.jme3.post.SceneProcessor;
|
import com.jme3.post.SceneProcessor;
|
||||||
import com.jme3.renderer.RenderManager;
|
import com.jme3.renderer.RenderManager;
|
||||||
import com.jme3.renderer.ViewPort;
|
import com.jme3.renderer.ViewPort;
|
||||||
|
import com.jme3.renderer.opengl.GLRenderer;
|
||||||
import com.jme3.renderer.queue.RenderQueue;
|
import com.jme3.renderer.queue.RenderQueue;
|
||||||
import com.jme3.texture.FrameBuffer;
|
import com.jme3.texture.FrameBuffer;
|
||||||
import com.jme3.texture.Image.Format;
|
import com.jme3.texture.Image.Format;
|
||||||
@ -47,20 +48,19 @@ import java.awt.image.AffineTransformOp;
|
|||||||
import java.awt.image.BufferStrategy;
|
import java.awt.image.BufferStrategy;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.IntBuffer;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
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.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
public class AwtPanel extends Canvas implements SceneProcessor {
|
public class AwtPanel extends Canvas implements JmePanel, SceneProcessor {
|
||||||
|
|
||||||
private boolean attachAsMain = false;
|
private boolean attachAsMain = false;
|
||||||
|
|
||||||
private BufferedImage img;
|
private BufferedImage img;
|
||||||
private FrameBuffer fb;
|
// private FrameBuffer fb;
|
||||||
private boolean srgb = false;
|
|
||||||
private ByteBuffer byteBuf;
|
|
||||||
private IntBuffer intBuf;
|
|
||||||
private RenderManager rm;
|
private RenderManager rm;
|
||||||
private PaintMode paintMode;
|
private PaintMode paintMode;
|
||||||
private ArrayList<ViewPort> viewPorts = new ArrayList<ViewPort>();
|
private ArrayList<ViewPort> viewPorts = new ArrayList<ViewPort>();
|
||||||
@ -75,27 +75,41 @@ public class AwtPanel extends Canvas implements SceneProcessor {
|
|||||||
// Reshape vars
|
// Reshape vars
|
||||||
private int newWidth = 1;
|
private int newWidth = 1;
|
||||||
private int newHeight = 1;
|
private int newHeight = 1;
|
||||||
private AtomicBoolean reshapeNeeded = new AtomicBoolean(false);
|
private AtomicBoolean reshapeNeeded = new AtomicBoolean(true);
|
||||||
private final Object lock = new Object();
|
private final Object lock = new Object();
|
||||||
|
|
||||||
public AwtPanel(PaintMode paintMode){
|
// Buffer pool and pending buffers
|
||||||
this(paintMode, false);
|
private int NUM_FRAMES = 3;
|
||||||
|
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 AwtPanel(PaintMode paintMode, boolean srgb){
|
public AwtPanel(PaintMode paintMode, boolean srgb){
|
||||||
this.paintMode = paintMode;
|
this.paintMode = paintMode;
|
||||||
this.srgb = srgb;
|
|
||||||
|
invalidatePendingFrames();
|
||||||
|
|
||||||
if (paintMode == PaintMode.Accelerated){
|
if (paintMode == PaintMode.Accelerated){
|
||||||
setIgnoreRepaint(true);
|
setIgnoreRepaint(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
addComponentListener(new ComponentAdapter(){
|
addComponentListener(resizeListener);
|
||||||
@Override
|
}
|
||||||
public void componentResized(ComponentEvent e) {
|
|
||||||
synchronized (lock){
|
public void onResize(ComponentEvent e) {
|
||||||
|
synchronized (lock) {
|
||||||
int newWidth2 = Math.max(getWidth(), 1);
|
int newWidth2 = Math.max(getWidth(), 1);
|
||||||
int newHeight2 = Math.max(getHeight(), 1);
|
int newHeight2 = Math.max(getHeight(), 1);
|
||||||
if (newWidth != newWidth2 || newHeight != newHeight2){
|
if (newWidth != newWidth2 || newHeight != newHeight2) {
|
||||||
newWidth = newWidth2;
|
newWidth = newWidth2;
|
||||||
newHeight = newHeight2;
|
newHeight = newHeight2;
|
||||||
reshapeNeeded.set(true);
|
reshapeNeeded.set(true);
|
||||||
@ -103,8 +117,6 @@ public class AwtPanel extends Canvas implements SceneProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addNotify(){
|
public void addNotify(){
|
||||||
@ -128,13 +140,13 @@ public class AwtPanel extends Canvas implements SceneProcessor {
|
|||||||
super.removeNotify();
|
super.removeNotify();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
// @Override
|
||||||
public void paint(Graphics g){
|
// public void paint(Graphics g){
|
||||||
Graphics2D g2d = (Graphics2D) g;
|
// Graphics2D g2d = (Graphics2D) g;
|
||||||
synchronized (lock){
|
// synchronized (lock){
|
||||||
g2d.drawImage(img, transformOp, 0, 0);
|
// g2d.drawImage(img, transformOp, 0, 0);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
public boolean checkVisibilityState(){
|
public boolean checkVisibilityState(){
|
||||||
if (!hasNativePeer.get()){
|
if (!hasNativePeer.get()){
|
||||||
@ -157,24 +169,85 @@ public class AwtPanel extends Canvas implements SceneProcessor {
|
|||||||
return currentShowing;
|
return currentShowing;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void repaintInThread(){
|
// public void repaintInThread(){
|
||||||
// Convert screenshot.
|
// // Convert screenshot.
|
||||||
byteBuf.clear();
|
// byteBuf.clear();
|
||||||
rm.getRenderer().readFrameBuffer(fb, byteBuf);
|
// rm.getRenderer().readFrameBuffer(fb, byteBuf);
|
||||||
|
//
|
||||||
|
// synchronized (lock){
|
||||||
|
// // All operations on img must be synchronized
|
||||||
|
// // as it is accessed from EDT.
|
||||||
|
// Screenshots.convertScreenShot2(intBuf, img);
|
||||||
|
// repaint();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
synchronized (lock){
|
public ByteBuffer acquireNextFrame() {
|
||||||
// All operations on img must be synchronized
|
if (pendingFrames.isEmpty()) {
|
||||||
// as it is accessed from EDT.
|
System.out.println("!!! No pending frames, returning null.");
|
||||||
Screenshots.convertScreenShot2(intBuf, img);
|
return null;
|
||||||
repaint();
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ByteBuffer nextFrame = null;
|
||||||
|
|
||||||
|
// while (!pendingFrames.isEmpty() && pendingFrames.peek().isDone()) {
|
||||||
|
// nextFrame = pendingFrames.take().get();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (nextFrame != null) {
|
||||||
|
// return nextFrame;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (pendingFrames.remainingCapacity() == 0) {
|
||||||
|
// Force it to finish ..
|
||||||
|
return pendingFrames.take().get();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Some frames are pending, none are finished though.
|
||||||
|
// return null;
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
} catch (ExecutionException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void drawFrameInThread(){
|
public void readNextFrame() {
|
||||||
// Convert screenshot.
|
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();
|
byteBuf.clear();
|
||||||
rm.getRenderer().readFrameBuffer(fb, byteBuf);
|
|
||||||
Screenshots.convertScreenShot2(intBuf, img);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawFrameInThread(ByteBuffer byteBuf){
|
||||||
|
// Convert the frame into the image so it can be rendered.
|
||||||
|
Screenshots.convertScreenShot2(byteBuf.asIntBuffer(), img);
|
||||||
|
|
||||||
|
// return the frame back to its rightful owner.
|
||||||
|
if (!bufferPool.offer(byteBuf)) {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
|
||||||
synchronized (lock){
|
synchronized (lock){
|
||||||
// All operations on strategy should be synchronized (?)
|
// All operations on strategy should be synchronized (?)
|
||||||
@ -235,44 +308,65 @@ public class AwtPanel extends Canvas implements SceneProcessor {
|
|||||||
if (this.rm == null){
|
if (this.rm == null){
|
||||||
// First time called in OGL thread
|
// First time called in OGL thread
|
||||||
this.rm = rm;
|
this.rm = rm;
|
||||||
reshapeInThread(1, 1);
|
// reshapeInThread(1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAccelerated() {
|
||||||
|
readNextFrame();
|
||||||
|
ByteBuffer byteBuf = acquireNextFrame();
|
||||||
|
if (byteBuf != null) {
|
||||||
|
drawFrameInThread(byteBuf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
private void reshapeInThread(int width, int height) {
|
||||||
byteBuf = BufferUtils.ensureLargeEnough(byteBuf, width * height * 4);
|
invalidatePendingFrames();
|
||||||
intBuf = byteBuf.asIntBuffer();
|
|
||||||
|
|
||||||
if (fb != null) {
|
for (FrameBuffer fb : fbs) {
|
||||||
fb.dispose();
|
fb.dispose();
|
||||||
fb = null;
|
|
||||||
}
|
}
|
||||||
|
fbs.clear();
|
||||||
|
|
||||||
fb = new FrameBuffer(width, height, 1);
|
for (int i = 0; i < NUM_FRAMES; i++) {
|
||||||
|
FrameBuffer fb = new FrameBuffer(width, height, 1);
|
||||||
fb.setDepthBuffer(Format.Depth);
|
fb.setDepthBuffer(Format.Depth);
|
||||||
fb.setColorBuffer(Format.RGB8);
|
fb.setColorBuffer(Format.RGBA8);
|
||||||
fb.setSrgb(srgb);
|
fbs.add(fb);
|
||||||
|
|
||||||
if (attachAsMain){
|
|
||||||
rm.getRenderer().setMainFrameBufferOverride(fb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// if (attachAsMain){
|
||||||
|
// rm.getRenderer().setMainFrameBufferOverride(fb);
|
||||||
|
// }
|
||||||
|
|
||||||
synchronized (lock){
|
synchronized (lock){
|
||||||
img = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
|
img = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
|
||||||
}
|
}
|
||||||
|
|
||||||
// synchronized (lock){
|
|
||||||
// img = (BufferedImage) getGraphicsConfiguration().createCompatibleImage(width, height);
|
|
||||||
// }
|
|
||||||
|
|
||||||
AffineTransform tx = AffineTransform.getScaleInstance(1, -1);
|
AffineTransform tx = AffineTransform.getScaleInstance(1, -1);
|
||||||
tx.translate(0, -img.getHeight());
|
tx.translate(0, -img.getHeight());
|
||||||
transformOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
|
transformOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
|
||||||
|
|
||||||
for (ViewPort vp : viewPorts){
|
for (ViewPort vp : viewPorts){
|
||||||
if (!attachAsMain){
|
// if (!attachAsMain){
|
||||||
vp.setOutputFrameBuffer(fb);
|
// vp.setOutputFrameBuffer(fb);
|
||||||
}
|
// }
|
||||||
vp.getCamera().resize(width, height, true);
|
vp.getCamera().resize(width, height, true);
|
||||||
|
|
||||||
// NOTE: Hack alert. This is done ONLY for custom framebuffers.
|
// NOTE: Hack alert. This is done ONLY for custom framebuffers.
|
||||||
@ -283,8 +377,9 @@ public class AwtPanel extends Canvas implements SceneProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean isInitialized() {
|
public boolean isInitialized() {
|
||||||
return fb != null;
|
return rm != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void preFrame(float tpf) {
|
public void preFrame(float tpf) {
|
||||||
@ -299,7 +394,20 @@ public class AwtPanel extends Canvas implements SceneProcessor {
|
|||||||
repaintRequest.set(true);
|
repaintRequest.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFrameEnd() {
|
@Override
|
||||||
|
public Component getComponent() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFrameBegin() {
|
||||||
|
if (attachAsMain && rm != null){
|
||||||
|
rm.getRenderer().setMainFrameBufferOverride(fbs.get(frameIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFrameEnd() {
|
||||||
if (reshapeNeeded.getAndSet(false)) {
|
if (reshapeNeeded.getAndSet(false)) {
|
||||||
reshapeInThread(newWidth, newHeight);
|
reshapeInThread(newWidth, newHeight);
|
||||||
} else {
|
} else {
|
||||||
@ -309,26 +417,24 @@ public class AwtPanel extends Canvas implements SceneProcessor {
|
|||||||
|
|
||||||
switch (paintMode) {
|
switch (paintMode) {
|
||||||
case Accelerated:
|
case Accelerated:
|
||||||
drawFrameInThread();
|
updateAccelerated();
|
||||||
break;
|
|
||||||
case Repaint:
|
|
||||||
repaintInThread();
|
|
||||||
break;
|
|
||||||
case OnRequest:
|
|
||||||
if (repaintRequest.getAndSet(false)) {
|
|
||||||
repaintInThread();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
// case Repaint:
|
||||||
|
// repaintInThread();
|
||||||
|
// break;
|
||||||
|
// case OnRequest:
|
||||||
|
// if (repaintRequest.getAndSet(false)) {
|
||||||
|
// repaintInThread();
|
||||||
|
// }
|
||||||
|
// break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void postFrame(FrameBuffer out) {
|
public void postFrame(FrameBuffer out) {
|
||||||
if (!attachAsMain && out != fb){
|
// if (!attachAsMain && out != fb){
|
||||||
throw new IllegalStateException("Why did you change the output framebuffer?");
|
// throw new IllegalStateException("Why did you change the output framebuffer?");
|
||||||
}
|
// }
|
||||||
|
|
||||||
// onFrameEnd();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reshape(ViewPort vp, int w, int h) {
|
public void reshape(ViewPort vp, int w, int h) {
|
||||||
|
@ -46,8 +46,8 @@ public class AwtPanelsContext implements JmeContext {
|
|||||||
protected JmeContext actualContext;
|
protected JmeContext actualContext;
|
||||||
protected AppSettings settings = new AppSettings(true);
|
protected AppSettings settings = new AppSettings(true);
|
||||||
protected SystemListener listener;
|
protected SystemListener listener;
|
||||||
protected ArrayList<AwtPanel> panels = new ArrayList<AwtPanel>();
|
protected ArrayList<JmePanel> panels = new ArrayList<JmePanel>();
|
||||||
protected AwtPanel inputSource;
|
protected JmePanel inputSource;
|
||||||
|
|
||||||
protected AwtMouseInput mouseInput = new AwtMouseInput();
|
protected AwtMouseInput mouseInput = new AwtMouseInput();
|
||||||
protected AwtKeyInput keyInput = new AwtKeyInput();
|
protected AwtKeyInput keyInput = new AwtKeyInput();
|
||||||
@ -92,13 +92,13 @@ public class AwtPanelsContext implements JmeContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setInputSource(AwtPanel panel){
|
public void setInputSource(JmePanel panel){
|
||||||
if (!panels.contains(panel))
|
if (!panels.contains(panel))
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
|
|
||||||
inputSource = panel;
|
inputSource = panel;
|
||||||
mouseInput.setInputSource(panel);
|
mouseInput.setInputSource(panel.getComponent());
|
||||||
keyInput.setInputSource(panel);
|
keyInput.setInputSource(panel.getComponent());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Type getType() {
|
public Type getType() {
|
||||||
@ -148,14 +148,14 @@ public class AwtPanelsContext implements JmeContext {
|
|||||||
public AwtPanelsContext(){
|
public AwtPanelsContext(){
|
||||||
}
|
}
|
||||||
|
|
||||||
public AwtPanel createPanel(PaintMode paintMode){
|
public JmePanel createPanel(PaintMode paintMode){
|
||||||
AwtPanel panel = new AwtPanel(paintMode);
|
JmePanel panel = new SwingPanel(paintMode, true);
|
||||||
panels.add(panel);
|
panels.add(panel);
|
||||||
return panel;
|
return panel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AwtPanel createPanel(PaintMode paintMode, boolean srgb){
|
public JmePanel createPanel(PaintMode paintMode, boolean srgb){
|
||||||
AwtPanel panel = new AwtPanel(paintMode, srgb);
|
JmePanel panel = new SwingPanel(paintMode, srgb);
|
||||||
panels.add(panel);
|
panels.add(panel);
|
||||||
return panel;
|
return panel;
|
||||||
}
|
}
|
||||||
@ -168,18 +168,18 @@ public class AwtPanelsContext implements JmeContext {
|
|||||||
// Check if throttle required
|
// Check if throttle required
|
||||||
boolean needThrottle = true;
|
boolean needThrottle = true;
|
||||||
|
|
||||||
for (AwtPanel panel : panels){
|
for (JmePanel panel : panels){
|
||||||
if (panel.isActiveDrawing()){
|
if (panel.isActiveDrawing()){
|
||||||
needThrottle = false;
|
needThrottle = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastThrottleState != needThrottle){
|
if (lastThrottleState != needThrottle) {
|
||||||
lastThrottleState = needThrottle;
|
lastThrottleState = needThrottle;
|
||||||
if (lastThrottleState){
|
if (lastThrottleState) {
|
||||||
System.out.println("OGL: Throttling update loop.");
|
System.out.println("OGL: Throttling update loop.");
|
||||||
}else{
|
} else {
|
||||||
System.out.println("OGL: Ceased throttling update loop.");
|
System.out.println("OGL: Ceased throttling update loop.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -191,9 +191,13 @@ public class AwtPanelsContext implements JmeContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (JmePanel panel : panels){
|
||||||
|
panel.onFrameBegin();
|
||||||
|
}
|
||||||
|
|
||||||
listener.update();
|
listener.update();
|
||||||
|
|
||||||
for (AwtPanel panel : panels){
|
for (JmePanel panel : panels){
|
||||||
panel.onFrameEnd();
|
panel.onFrameEnd();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
48
jme3-desktop/src/main/java/com/jme3/system/awt/JmePanel.java
Executable file
48
jme3-desktop/src/main/java/com/jme3/system/awt/JmePanel.java
Executable file
@ -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();
|
||||||
|
}
|
382
jme3-desktop/src/main/java/com/jme3/system/awt/SwingPanel.java
Executable file
382
jme3-desktop/src/main/java/com/jme3/system/awt/SwingPanel.java
Executable file
@ -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() {
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user