Merge branch 'renderer-fbreadasync' into experimental

experimental
Kirill Vainer 9 years ago
commit c50839796f
  1. 280
      jme3-core/src/main/java/com/jme3/renderer/opengl/AsyncFrameReader.java
  2. 98
      jme3-core/src/main/java/com/jme3/renderer/opengl/FrameBufferReadRequest.java
  3. 32
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
  4. 270
      jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanel.java
  5. 32
      jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java
  6. 48
      jme3-desktop/src/main/java/com/jme3/system/awt/JmePanel.java
  7. 382
      jme3-desktop/src/main/java/com/jme3/system/awt/SwingPanel.java

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

@ -64,6 +64,7 @@ import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
@ -100,6 +101,7 @@ public final class GLRenderer implements Renderer {
private final GLExt glext;
private final GLFbo glfbo;
private final TextureUtil texUtil;
private final AsyncFrameReader frameReader;
public GLRenderer(GL gl, GLExt glext, GLFbo glfbo) {
this.gl = gl;
@ -109,6 +111,7 @@ public final class GLRenderer implements Renderer {
this.glfbo = glfbo;
this.glext = glext;
this.texUtil = new TextureUtil(gl, gl2, glext);
this.frameReader = new AsyncFrameReader(this, gl, glext, context);
}
@Override
@ -879,6 +882,7 @@ public final class GLRenderer implements Renderer {
public void postFrame() {
objManager.deleteUnused(this);
frameReader.updateReadRequests();
gl.resetStats();
}
@ -1685,11 +1689,11 @@ public final class GLRenderer implements Renderer {
}
}
public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) {
readFrameBufferWithGLFormat(fb, byteBuf, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE);
public Future<ByteBuffer> readFrameBufferLater(FrameBuffer fb, ByteBuffer byteBuf) {
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) {
RenderBuffer rb = fb.getColorBuffer();
if (rb == null) {
@ -1708,12 +1712,30 @@ public final class GLRenderer implements Renderer {
setFrameBuffer(null);
}
gl.glReadPixels(vpX, vpY, vpW, vpH, glFormat, dataType, byteBuf);
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);
}
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) {
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) {

@ -34,6 +34,7 @@ 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.Format;
@ -47,20 +48,19 @@ import java.awt.image.AffineTransformOp;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
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;
public class AwtPanel extends Canvas implements SceneProcessor {
public class AwtPanel extends Canvas implements JmePanel, SceneProcessor {
private boolean attachAsMain = false;
private BufferedImage img;
private FrameBuffer fb;
private boolean srgb = false;
private ByteBuffer byteBuf;
private IntBuffer intBuf;
// private FrameBuffer fb;
private RenderManager rm;
private PaintMode paintMode;
private ArrayList<ViewPort> viewPorts = new ArrayList<ViewPort>();
@ -75,35 +75,47 @@ public class AwtPanel extends Canvas implements SceneProcessor {
// Reshape vars
private int newWidth = 1;
private int newHeight = 1;
private AtomicBoolean reshapeNeeded = new AtomicBoolean(false);
private AtomicBoolean reshapeNeeded = new AtomicBoolean(true);
private final Object lock = new Object();
public AwtPanel(PaintMode paintMode){
this(paintMode, false);
}
// Buffer pool and pending buffers
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){
this.paintMode = paintMode;
this.srgb = srgb;
invalidatePendingFrames();
if (paintMode == PaintMode.Accelerated){
setIgnoreRepaint(true);
}
addComponentListener(new ComponentAdapter(){
@Override
public void componentResized(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);
}
}
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
@ -128,13 +140,13 @@ public class AwtPanel extends Canvas implements SceneProcessor {
super.removeNotify();
}
@Override
public void paint(Graphics g){
Graphics2D g2d = (Graphics2D) g;
synchronized (lock){
g2d.drawImage(img, transformOp, 0, 0);
}
}
// @Override
// public void paint(Graphics g){
// Graphics2D g2d = (Graphics2D) g;
// synchronized (lock){
// g2d.drawImage(img, transformOp, 0, 0);
// }
// }
public boolean checkVisibilityState(){
if (!hasNativePeer.get()){
@ -157,24 +169,85 @@ public class AwtPanel extends Canvas implements SceneProcessor {
return currentShowing;
}
public void repaintInThread(){
// Convert screenshot.
byteBuf.clear();
rm.getRenderer().readFrameBuffer(fb, byteBuf);
// public void repaintInThread(){
// // Convert screenshot.
// byteBuf.clear();
// 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();
// }
// }
public ByteBuffer acquireNextFrame() {
if (pendingFrames.isEmpty()) {
System.out.println("!!! No pending frames, returning null.");
return null;
}
synchronized (lock){
// All operations on img must be synchronized
// as it is accessed from EDT.
Screenshots.convertScreenShot2(intBuf, img);
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(){
// Convert screenshot.
byteBuf.clear();
rm.getRenderer().readFrameBuffer(fb, byteBuf);
Screenshots.convertScreenShot2(intBuf, img);
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);
}
}
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){
// All operations on strategy should be synchronized (?)
@ -235,44 +308,65 @@ public class AwtPanel extends Canvas implements SceneProcessor {
if (this.rm == null){
// First time called in OGL thread
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) {
byteBuf = BufferUtils.ensureLargeEnough(byteBuf, width * height * 4);
intBuf = byteBuf.asIntBuffer();
if (fb != null) {
invalidatePendingFrames();
for (FrameBuffer fb : fbs) {
fb.dispose();
fb = null;
}
fbs.clear();
fb = new FrameBuffer(width, height, 1);
fb.setDepthBuffer(Format.Depth);
fb.setColorBuffer(Format.RGB8);
fb.setSrgb(srgb);
if (attachAsMain){
rm.getRenderer().setMainFrameBufferOverride(fb);
for (int i = 0; i < NUM_FRAMES; i++) {
FrameBuffer fb = new FrameBuffer(width, height, 1);
fb.setDepthBuffer(Format.Depth);
fb.setColorBuffer(Format.RGBA8);
fbs.add(fb);
}
// if (attachAsMain){
// rm.getRenderer().setMainFrameBufferOverride(fb);
// }
synchronized (lock){
img = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
}
// synchronized (lock){
// img = (BufferedImage) getGraphicsConfiguration().createCompatibleImage(width, height);
// }
AffineTransform tx = AffineTransform.getScaleInstance(1, -1);
tx.translate(0, -img.getHeight());
transformOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
for (ViewPort vp : viewPorts){
if (!attachAsMain){
vp.setOutputFrameBuffer(fb);
}
// if (!attachAsMain){
// vp.setOutputFrameBuffer(fb);
// }
vp.getCamera().resize(width, height, true);
// 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() {
return fb != null;
return rm != null;
}
public void preFrame(float tpf) {
@ -298,8 +393,21 @@ public class AwtPanel extends Canvas implements SceneProcessor {
// For "PaintMode.OnDemand" only.
repaintRequest.set(true);
}
@Override
public Component getComponent() {
return this;
}
@Override
public void onFrameBegin() {
if (attachAsMain && rm != null){
rm.getRenderer().setMainFrameBufferOverride(fbs.get(frameIndex));
}
}
void onFrameEnd() {
@Override
public void onFrameEnd() {
if (reshapeNeeded.getAndSet(false)) {
reshapeInThread(newWidth, newHeight);
} else {
@ -309,26 +417,24 @@ public class AwtPanel extends Canvas implements SceneProcessor {
switch (paintMode) {
case Accelerated:
drawFrameInThread();
break;
case Repaint:
repaintInThread();
break;
case OnRequest:
if (repaintRequest.getAndSet(false)) {
repaintInThread();
}
updateAccelerated();
break;
// case Repaint:
// repaintInThread();
// break;
// case OnRequest:
// if (repaintRequest.getAndSet(false)) {
// repaintInThread();
// }
// break;
}
}
}
public void postFrame(FrameBuffer out) {
if (!attachAsMain && out != fb){
throw new IllegalStateException("Why did you change the output framebuffer?");
}
// onFrameEnd();
// if (!attachAsMain && out != fb){
// throw new IllegalStateException("Why did you change the output framebuffer?");
// }
}
public void reshape(ViewPort vp, int w, int h) {

@ -46,8 +46,8 @@ public class AwtPanelsContext implements JmeContext {
protected JmeContext actualContext;
protected AppSettings settings = new AppSettings(true);
protected SystemListener listener;
protected ArrayList<AwtPanel> panels = new ArrayList<AwtPanel>();
protected AwtPanel inputSource;
protected ArrayList<JmePanel> panels = new ArrayList<JmePanel>();
protected JmePanel inputSource;
protected AwtMouseInput mouseInput = new AwtMouseInput();
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))
throw new IllegalArgumentException();
inputSource = panel;
mouseInput.setInputSource(panel);
keyInput.setInputSource(panel);
mouseInput.setInputSource(panel.getComponent());
keyInput.setInputSource(panel.getComponent());
}
public Type getType() {
@ -148,14 +148,14 @@ public class AwtPanelsContext implements JmeContext {
public AwtPanelsContext(){
}
public AwtPanel createPanel(PaintMode paintMode){
AwtPanel panel = new AwtPanel(paintMode);
public JmePanel createPanel(PaintMode paintMode){
JmePanel panel = new SwingPanel(paintMode, true);
panels.add(panel);
return panel;
}
public AwtPanel createPanel(PaintMode paintMode, boolean srgb){
AwtPanel panel = new AwtPanel(paintMode, srgb);
public JmePanel createPanel(PaintMode paintMode, boolean srgb){
JmePanel panel = new SwingPanel(paintMode, srgb);
panels.add(panel);
return panel;
}
@ -168,18 +168,18 @@ public class AwtPanelsContext implements JmeContext {
// Check if throttle required
boolean needThrottle = true;
for (AwtPanel panel : panels){
for (JmePanel panel : panels){
if (panel.isActiveDrawing()){
needThrottle = false;
break;
}
}
if (lastThrottleState != needThrottle){
if (lastThrottleState != needThrottle) {
lastThrottleState = needThrottle;
if (lastThrottleState){
if (lastThrottleState) {
System.out.println("OGL: Throttling update loop.");
}else{
} else {
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();
for (AwtPanel panel : panels){
for (JmePanel panel : panels){
panel.onFrameEnd();
}
}

@ -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() {
}
}
Loading…
Cancel
Save