* Canvas is now using pbuffer workaround, allowing renderer to acquire renderer capabilities even if the canvas is not visible yet.
* Handling of context destruction is now handled individually for displays and canvases. For canvas, this allows it to destroy the pbuffer in addition to the display. * VertexBuffer now has better detection for data size changes, might prevent GL errors in certain cases. NOTE: VertexBuffer.updateData() is generally more stable than VertexBuffer.setUpdateNeeded(). Refrain from using setUpdateNeeded() .. its an internal call anyway. Using it directly could cause GL errors. git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@7374 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
This commit is contained in:
parent
4b52dba7f8
commit
0d0454f248
@ -225,6 +225,7 @@ public class VertexBuffer extends GLObject implements Savable, Cloneable {
|
||||
}
|
||||
|
||||
protected int offset = 0;
|
||||
protected int lastLimit = 0;
|
||||
protected int stride = 0;
|
||||
protected int components = 0;
|
||||
|
||||
@ -233,7 +234,6 @@ public class VertexBuffer extends GLObject implements Savable, Cloneable {
|
||||
*/
|
||||
protected transient int componentsLength = 0;
|
||||
protected Buffer data = null;
|
||||
protected transient ByteBuffer mappedData;
|
||||
protected Usage usage;
|
||||
protected Type bufType;
|
||||
protected Format format;
|
||||
@ -303,14 +303,6 @@ public class VertexBuffer extends GLObject implements Savable, Cloneable {
|
||||
return data;
|
||||
}
|
||||
|
||||
public ByteBuffer getMappedData() {
|
||||
return mappedData;
|
||||
}
|
||||
|
||||
public void setMappedData(ByteBuffer mappedData) {
|
||||
this.mappedData = mappedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The usage of this buffer. See {@link Usage} for more
|
||||
* information.
|
||||
@ -403,6 +395,7 @@ public class VertexBuffer extends GLObject implements Savable, Cloneable {
|
||||
this.usage = usage;
|
||||
this.format = format;
|
||||
this.componentsLength = components * format.getComponentSize();
|
||||
this.lastLimit = data.limit();
|
||||
setUpdateNeeded();
|
||||
}
|
||||
|
||||
@ -424,8 +417,9 @@ public class VertexBuffer extends GLObject implements Savable, Cloneable {
|
||||
}
|
||||
|
||||
// will force renderer to call glBufferData again
|
||||
if (this.data.capacity() != data.capacity()){
|
||||
if (this.data.getClass() != data.getClass() || data.limit() != lastLimit){
|
||||
dataSizeChanged = true;
|
||||
lastLimit = data.limit();
|
||||
}
|
||||
this.data = data;
|
||||
setUpdateNeeded();
|
||||
@ -696,10 +690,13 @@ public class VertexBuffer extends GLObject implements Savable, Cloneable {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public VertexBuffer clone(){
|
||||
// NOTE: Superclass GLObject automatically creates shallow clone
|
||||
// e.g re-use ID.
|
||||
VertexBuffer vb = (VertexBuffer) super.clone();
|
||||
vb.handleRef = new Object();
|
||||
vb.id = -1;
|
||||
if (data != null)
|
||||
vb.updateData(BufferUtils.clone(data));
|
||||
|
||||
|
@ -73,7 +73,6 @@ public class WireFrustum extends Mesh {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
FloatBuffer b = BufferUtils.createFloatBuffer(points);
|
||||
FloatBuffer a = (FloatBuffer) vb.getData();
|
||||
b.rewind();
|
||||
@ -81,7 +80,7 @@ public class WireFrustum extends Mesh {
|
||||
a.put(b);
|
||||
a.rewind();
|
||||
|
||||
vb.setUpdateNeeded();
|
||||
vb.updateData(a);
|
||||
|
||||
updateBound();
|
||||
}
|
||||
|
@ -417,9 +417,9 @@ public class LwjglRenderer implements Renderer {
|
||||
public void resetGLObjects() {
|
||||
objManager.resetObjects();
|
||||
statistics.clearMemory();
|
||||
context.reset();
|
||||
boundShader = null;
|
||||
lastFb = null;
|
||||
context.reset();
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
|
@ -81,7 +81,10 @@ public abstract class LwjglAbstractDisplay extends LwjglContext implements Runna
|
||||
*/
|
||||
protected abstract void createContext(AppSettings settings) throws LWJGLException;
|
||||
|
||||
|
||||
/**
|
||||
* Destroy the context.
|
||||
*/
|
||||
protected abstract void destroyContext();
|
||||
|
||||
/**
|
||||
* Does LWJGL display initialization in the OpenGL thread
|
||||
@ -97,10 +100,12 @@ public abstract class LwjglAbstractDisplay extends LwjglContext implements Runna
|
||||
});
|
||||
}
|
||||
|
||||
// For canvas, this wont happen until its initialized.
|
||||
// For canvas, this will create a pbuffer,
|
||||
// allowing us to query information.
|
||||
// When the canvas context becomes available, it will
|
||||
// be replaced seamlessly.
|
||||
createContext(settings);
|
||||
if (renderable.get()) // assumes createContext will set this flag
|
||||
printContextInitInfo();
|
||||
printContextInitInfo();
|
||||
|
||||
created.set(true);
|
||||
} catch (Exception ex){
|
||||
@ -137,6 +142,9 @@ public abstract class LwjglAbstractDisplay extends LwjglContext implements Runna
|
||||
|
||||
listener.update();
|
||||
|
||||
// All this does is call swap buffers
|
||||
// If the canvas is not active, there's no need to waste time
|
||||
// doing that ..
|
||||
if (renderable.get()){
|
||||
assert checkGLError();
|
||||
|
||||
@ -158,22 +166,16 @@ public abstract class LwjglAbstractDisplay extends LwjglContext implements Runna
|
||||
if (frameRate > 0)
|
||||
Display.sync(frameRate);
|
||||
|
||||
if (renderable.get() && autoFlush)
|
||||
renderer.onFrame();
|
||||
// Subclasses just call GLObjectManager clean up objects here
|
||||
// it is safe .. for now.
|
||||
renderer.onFrame();
|
||||
}
|
||||
|
||||
/**
|
||||
* De-initialize in the OpenGL thread.
|
||||
*/
|
||||
protected void deinitInThread(){
|
||||
if (Display.isCreated()){
|
||||
renderer.cleanup();
|
||||
Display.destroy();
|
||||
}else{
|
||||
// If using canvas temporary closing, the display would
|
||||
// be closed at this point
|
||||
renderer.resetGLObjects();
|
||||
}
|
||||
destroyContext();
|
||||
|
||||
listener.destroy();
|
||||
logger.info("Display destroyed.");
|
||||
|
@ -43,10 +43,10 @@ import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.SwingUtilities;
|
||||
import org.lwjgl.LWJGLException;
|
||||
import org.lwjgl.input.Controllers;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
import org.lwjgl.input.Mouse;
|
||||
import org.lwjgl.opengl.Display;
|
||||
import org.lwjgl.opengl.Pbuffer;
|
||||
import org.lwjgl.opengl.PixelFormat;
|
||||
|
||||
public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContext {
|
||||
@ -63,67 +63,72 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
|
||||
private Thread renderThread;
|
||||
private boolean runningFirstTime = true;
|
||||
private boolean mouseWasGrabbed = false;
|
||||
private boolean mouseActive, keyboardActive, joyActive;
|
||||
private boolean mouseActive, keyboardActive;
|
||||
|
||||
private Pbuffer pbuffer;
|
||||
|
||||
private class GLCanvas extends Canvas {
|
||||
@Override
|
||||
public void addNotify(){
|
||||
super.addNotify();
|
||||
|
||||
if (renderThread != null && renderThread.getState() == Thread.State.TERMINATED)
|
||||
return; // already destroyed.
|
||||
|
||||
if (renderThread == null){
|
||||
logger.log(Level.INFO, "EDT: Creating OGL thread.");
|
||||
|
||||
// Also set some settings on the canvas here.
|
||||
// So we don't do it outside the AWT thread.
|
||||
canvas.setFocusable(true);
|
||||
canvas.setIgnoreRepaint(true);
|
||||
|
||||
renderThread = new Thread(LwjglCanvas.this, "LWJGL Renderer Thread");
|
||||
renderThread.start();
|
||||
}else if (needClose.get()){
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log(Level.INFO, "EDT: Notifying OGL that canvas is visible..");
|
||||
needRestoreCanvas.set(true);
|
||||
|
||||
// NOTE: no need to wait for OGL to initialize the canvas,
|
||||
// it can happen at any time.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNotify(){
|
||||
if (needClose.get()){
|
||||
logger.log(Level.INFO, "EDT: Application is stopped. Not restoring canvas.");
|
||||
super.removeNotify();
|
||||
return;
|
||||
}
|
||||
|
||||
// We must tell GL context to shutdown and wait for it to
|
||||
// shutdown, otherwise, issues will occur.
|
||||
logger.log(Level.INFO, "EDT: Notifying OGL that canvas is about to become invisible..");
|
||||
needDestroyCanvas.set(true);
|
||||
try {
|
||||
actionRequiredBarrier.await();
|
||||
} catch (InterruptedException ex) {
|
||||
logger.log(Level.SEVERE, "EDT: Interrupted! ", ex);
|
||||
} catch (BrokenBarrierException ex){
|
||||
logger.log(Level.SEVERE, "EDT: Broken barrier! ", ex);
|
||||
}
|
||||
|
||||
logger.log(Level.INFO, "EDT: Acknowledged receipt of canvas death");
|
||||
// GL context is dead at this point
|
||||
|
||||
// Reset barrier for future use
|
||||
actionRequiredBarrier.reset();
|
||||
|
||||
super.removeNotify();
|
||||
}
|
||||
}
|
||||
|
||||
public LwjglCanvas(){
|
||||
super();
|
||||
|
||||
canvas = new Canvas(){
|
||||
@Override
|
||||
public void addNotify(){
|
||||
super.addNotify();
|
||||
|
||||
if (renderThread != null && renderThread.getState() == Thread.State.TERMINATED)
|
||||
return; // already destroyed.
|
||||
|
||||
if (renderThread == null){
|
||||
logger.log(Level.INFO, "EDT: Creating OGL thread.");
|
||||
|
||||
renderThread = new Thread(LwjglCanvas.this, "LWJGL Renderer Thread");
|
||||
renderThread.start();
|
||||
}else if (needClose.get()){
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log(Level.INFO, "EDT: Notifying OGL that canvas is visible..");
|
||||
needRestoreCanvas.set(true);
|
||||
|
||||
// NOTE: no need to wait for OGL to initialize the canvas,
|
||||
// it can happen at any time.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNotify(){
|
||||
if (needClose.get()){
|
||||
logger.log(Level.INFO, "EDT: Application is stopped. Not restoring canvas.");
|
||||
super.removeNotify();
|
||||
return;
|
||||
}
|
||||
|
||||
// We must tell GL context to shutdown and wait for it to
|
||||
// shutdown, otherwise, issues will occur.
|
||||
logger.log(Level.INFO, "EDT: Sending destroy request..");
|
||||
needDestroyCanvas.set(true);
|
||||
try {
|
||||
actionRequiredBarrier.await();
|
||||
} catch (InterruptedException ex) {
|
||||
logger.log(Level.SEVERE, "EDT: Interrupted! ", ex);
|
||||
} catch (BrokenBarrierException ex){
|
||||
logger.log(Level.SEVERE, "EDT: Broken barrier! ", ex);
|
||||
}
|
||||
|
||||
logger.log(Level.INFO, "EDT: Acknowledged receipt of destroy request!");
|
||||
// GL context is dead at this point
|
||||
|
||||
// Reset barrier for future use
|
||||
actionRequiredBarrier.reset();
|
||||
|
||||
super.removeNotify();
|
||||
}
|
||||
};
|
||||
|
||||
canvas.setFocusable(true);
|
||||
canvas.setIgnoreRepaint(true);
|
||||
canvas = new GLCanvas();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -150,6 +155,8 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
|
||||
|
||||
@Override
|
||||
public void restart() {
|
||||
frameRate = settings.getFrameRate();
|
||||
// TODO: Handle other cases, like change of pixel format, etc.
|
||||
}
|
||||
|
||||
public Canvas getCanvas(){
|
||||
@ -194,7 +201,6 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
|
||||
private void pauseCanvas(){
|
||||
mouseActive = Mouse.isCreated();
|
||||
keyboardActive = Keyboard.isCreated();
|
||||
joyActive = Controllers.isCreated();
|
||||
|
||||
if (mouseActive && Mouse.isGrabbed()){
|
||||
Mouse.setGrabbed(false);
|
||||
@ -205,13 +211,11 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
|
||||
Mouse.destroy();
|
||||
if (keyboardActive)
|
||||
Keyboard.destroy();
|
||||
if (joyActive)
|
||||
Controllers.destroy();
|
||||
|
||||
logger.log(Level.INFO, "OGL: Destroying display (temporarily)");
|
||||
Display.destroy();
|
||||
logger.log(Level.INFO, "OGL: Canvas will become invisible! Destroying ..");
|
||||
|
||||
renderable.set(false);
|
||||
destroyContext();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -234,9 +238,10 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
|
||||
createContext(settings);
|
||||
|
||||
// must call after createContext, as renderer might be null
|
||||
renderer.resetGLObjects();
|
||||
// renderer.resetGLObjects();
|
||||
|
||||
logger.log(Level.INFO, "OGL: Waiting for display to become active..");
|
||||
// NOTE: A deadlock will happen here if createContext had an exception
|
||||
while (!Display.isCreated()){
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
@ -269,33 +274,101 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure the pbuffer is available and ready for use
|
||||
*/
|
||||
protected void makePbufferAvailable() throws LWJGLException{
|
||||
if (pbuffer == null || pbuffer.isBufferLost()){
|
||||
if (pbuffer != null && pbuffer.isBufferLost()){
|
||||
pbuffer.releaseContext();
|
||||
pbuffer.destroy();
|
||||
}
|
||||
pbuffer = new Pbuffer(1, 1, new PixelFormat(0, 0, 0), null);
|
||||
logger.log(Level.INFO, "OGL: Pbuffer has been created");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called:
|
||||
* 1) When the context thread ends
|
||||
* 2) Any time the canvas becomes non-displayable
|
||||
*/
|
||||
protected void destroyContext(){
|
||||
try {
|
||||
renderer.resetGLObjects();
|
||||
if (Display.isCreated()){
|
||||
Display.releaseContext();
|
||||
Display.destroy();
|
||||
}
|
||||
} catch (LWJGLException ex) {
|
||||
listener.handleError("Failed to destroy context", ex);
|
||||
}
|
||||
|
||||
try {
|
||||
// The canvas is no longer visible,
|
||||
// but the context thread is still running.
|
||||
if (!needClose.get()){
|
||||
// MUST make sure there's still a context current here ..
|
||||
// Display is dead, make pbuffer available to the system
|
||||
makePbufferAvailable();
|
||||
|
||||
// pbuffer is now available, make it current
|
||||
pbuffer.makeCurrent();
|
||||
}else{
|
||||
// The context thread is no longer running.
|
||||
// Destroy pbuffer.
|
||||
if (pbuffer != null){
|
||||
pbuffer.destroy();
|
||||
}
|
||||
}
|
||||
} catch (LWJGLException ex) {
|
||||
listener.handleError("Failed make pbuffer available", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called:
|
||||
* 1) When the context thread starts
|
||||
* 2) Any time the canvas becomes displayable again.
|
||||
*/
|
||||
@Override
|
||||
protected void createContext(AppSettings settings) {
|
||||
// In case canvas is not visible, we still take framerate
|
||||
// from settings to prevent "100% CPU usage"
|
||||
frameRate = settings.getFrameRate();
|
||||
|
||||
if (!renderable.get())
|
||||
return;
|
||||
try {
|
||||
// First create the pbuffer, if it is needed.
|
||||
makePbufferAvailable();
|
||||
|
||||
Display.setVSyncEnabled(settings.isVSync());
|
||||
if (renderable.get()){
|
||||
if (pbuffer.isCurrent()){
|
||||
pbuffer.releaseContext();
|
||||
}
|
||||
|
||||
try{
|
||||
Display.setParent(canvas);
|
||||
PixelFormat pf = new PixelFormat(settings.getBitsPerPixel(),
|
||||
0,
|
||||
settings.getDepthBits(),
|
||||
settings.getStencilBits(),
|
||||
settings.getSamples());
|
||||
Display.create(pf);
|
||||
Display.makeCurrent();
|
||||
Display.setVSyncEnabled(settings.isVSync());
|
||||
Display.setParent(canvas);
|
||||
PixelFormat pf = new PixelFormat(settings.getBitsPerPixel(),
|
||||
0,
|
||||
settings.getDepthBits(),
|
||||
settings.getStencilBits(),
|
||||
settings.getSamples());
|
||||
Display.create(pf, pbuffer);
|
||||
Display.makeCurrent();
|
||||
}else{
|
||||
pbuffer.makeCurrent();
|
||||
}
|
||||
// At this point, the OpenGL context is active.
|
||||
|
||||
if (runningFirstTime){
|
||||
// THIS is the part that creates the renderer.
|
||||
// It must always be called, now that we have the pbuffer workaround.
|
||||
initContextFirstTime();
|
||||
runningFirstTime = false;
|
||||
}
|
||||
}catch (LWJGLException ex){
|
||||
listener.handleError("Failed to parent canvas to display", ex);
|
||||
} catch (LWJGLException ex) {
|
||||
listener.handleError("Failed to initialize OpenGL context", ex);
|
||||
// TODO: Fix deadlock that happens after the error (throw runtime exception?)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,8 +115,6 @@ public abstract class LwjglContext implements JmeContext {
|
||||
}
|
||||
|
||||
protected void initContextFirstTime(){
|
||||
assert renderable.get();
|
||||
|
||||
if (GLContext.getCapabilities().OpenGL20){
|
||||
renderer = new LwjglRenderer();
|
||||
}else{
|
||||
|
@ -134,6 +134,16 @@ public class LwjglDisplay extends LwjglAbstractDisplay {
|
||||
}
|
||||
}
|
||||
|
||||
protected void destroyContext(){
|
||||
try {
|
||||
renderer.cleanup();
|
||||
Display.releaseContext();
|
||||
Display.destroy();
|
||||
} catch (LWJGLException ex) {
|
||||
listener.handleError("Failed to destroy context", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void create(boolean waitFor){
|
||||
if (created.get()){
|
||||
logger.warning("create() called when display is already created!");
|
||||
|
@ -34,6 +34,7 @@ package com.jme3.system.lwjgl;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.system.Timer;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.lwjgl.Sys;
|
||||
|
||||
@ -81,7 +82,7 @@ public class LwjglTimer extends Timer {
|
||||
reset();
|
||||
|
||||
//print timer resolution info
|
||||
logger.info("Timer resolution: " + LWJGL_TIMER_RES + " ticks per second");
|
||||
logger.log(Level.INFO, "Timer resolution: {0} ticks per second", LWJGL_TIMER_RES);
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
|
@ -1,11 +1,7 @@
|
||||
package com.jme3.math;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Vector2f;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TrigonometryTest {
|
||||
|
Loading…
x
Reference in New Issue
Block a user