<< Back to previous view

[JAVA3D-674] Offscreen rendering is missing geometry/content branch (sometimes) Created: 04/Apr/12  Updated: 10/Oct/12

Status: Open
Project: java3d
Component/s: j3d-core
Affects Version/s: 1.5.2
Fix Version/s: None

Type: Bug Priority: Major
Reporter: sstevenson638 Assignee: Unassigned
Resolution: Unresolved Votes: 0
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified
Environment:

Windows XP, NVIDIA graphics card


Tags:
Participants: harveyh and sstevenson638

 Description   

When I do a screen capture using an offscreen canvas, the View does not always render the content branch of my scene graph. It seems to work when I run from Netbeans in debug mode and am breaking to look at code, but it almost never works when I run in non-debug mode.

The onscreen canvas always draws correctly. The offscreen canvas always draws the background correctly, but not the other content in the scene.

I use the same offscreen canvas component used in two different applications. One application uses a SimpleUniverse, the other I create my own Universe and View objects manually. The one using SimpleUniverse seems to capture images correctly, the one I created manually does not. Is there something about the View created by SimpleUniverse that could effect the behavior? I've looked all over the source to see if it does anything special, but haven't found anything.

Source to some of my components is listed below. The idea is to print Components that contain Java3D scenes using the normal Component.print() methods. They also support "Actors" that can draw on the Canvas3D after rendering is complete.

Please help! I've been working on this for a week and have no idea what is wrong. It's almost like a threading issue, but I think that I'm following all the rules.

class OffScreenCanvas3D extends MarkupCanvas3D {
private static Logger logger = Logger.getLogger(OffScreenCanvas3D.class);

volatile boolean waitingForRender = false;
int maxWaitTimeForRender = 3000;

/**

  • @param gc
    */
    public OffScreenCanvas3D(GraphicsConfiguration gc, int maxWaitTimeForReder) { super(gc,true); this.maxWaitTimeForRender = maxWaitTimeForRender; }

@Override
public void preRender() { super.preRender(); }
@Override
public void postRender() { super.postRender(); }

@Override
public void postSwap() { super.postSwap(); waitingForRender = false; }

public void renderOffScreenBufferAndWait() {
waitingForRender = true;

View view = this.getView();
view.repaint();
// Tell Java3D to render this canvas
if(!view.isViewRunning()) { view.renderOnce(); }
else { super.renderOffScreenBuffer(); }

// Wait for rendering to finish.
// The Canvas3D.waitForOffScreenRendering does not seem to work and can cause thread deadlocks.
// It never re-sets is rendering flag while waiting.
int count = 0;
while(waitingForRender) {
try { Thread.sleep(2); }
catch (InterruptedException ex) { break; }
count+=2;
if(count>this.maxWaitTimeForRender) { logger.error("Failed to print 3D image. Rendering seems to be taking a long time."); break; // Give up and return if still have not rendered }
}
}

}

public class PrintableCanvas3DContainer extends Container {

private static Logger logger = Logger.getLogger(PrintableCanvas3DContainer.class);
private MarkupCanvas3D onScreenCanvas;
private OffScreenCanvas3D offScreenCanvas;
BufferedImage offScreenImageBuffer = null;
private transient List<Java2DActor> actors = new ArrayList();

/**

  • Creates a PrintableCanvas3DContainer with specified GraphicsConfiguration.
    *
  • @param gc
    */
    public PrintableCanvas3DContainer(GraphicsConfiguration gc) { super(); super.setLayout(new BorderLayout()); onScreenCanvas = new MarkupCanvas3D(gc, false); super.add(onScreenCanvas, BorderLayout.CENTER); }

/**

  • Creates a PrintableCanvas3DContainer with default GraphicsConfiguration obtained
  • from SimpleUniverse.getPreferredConfiguration().
    *
    */
    public PrintableCanvas3DContainer() { this(SimpleUniverse.getPreferredConfiguration()); }

/**

  • Returns the on-screen canvas displayed in this container.
  • This canvas is owned by this container and should never be added to an outside
  • component. Add this container to your containers instead.
  • @return
    */
    public final Canvas3D getOnScreenCanvas() { return this.onScreenCanvas; }

/**

  • Override all add and remove methods to prevent them.
  • @param comp
  • @return
    */
    @Override
    public Component add(Component comp) { throw new UnsupportedOperationException("PrintableCanvas3DContainer hardwired to contain only 1 Canvas3D object."); }

    @Override
    public Component add(String name, Component comp) { throw new UnsupportedOperationException("PrintableCanvas3DContainer hardwired to contain only 1 Canvas3D object."); } }

@Override
public Component add(Component comp, int index) { throw new UnsupportedOperationException("PrintableCanvas3DContainer hardwired to contain only 1 Canvas3D object."); }

@Override
public void add(Component comp, Object constraints) { throw new UnsupportedOperationException("PrintableCanvas3DContainer hardwired to contain only 1 Canvas3D object."); } }

@Override
public void add(Component comp, Object constraints, int index) { throw new UnsupportedOperationException("PrintableCanvas3DContainer hardwired to contain only 1 Canvas3D object."); }

@Override
public void setLayout(LayoutManager mgr) { throw new UnsupportedOperationException("PrintableCanvas3DContainer has hardwired LayoutManager."); }

/**

  • Print this component to make image captures.
    *
  • @param g
    */
    @Override
    public void print(Graphics g) {

View view = this.onScreenCanvas.getView();
if (view == null) { return; }

BufferedImage img = captureImageInternal();
if (img != null) { g.drawImage(img, 0, 0, null); }

}

@Override
public void printAll(Graphics g) { print(g); }

/**
* Capture the current display image.
*
* @return
*/
public BufferedImage captureImage() { BufferedImage image = captureImageInternal(); return image; // TODO make a copy of the image to return }

private BufferedImage captureImageInternal() { this.attachOffScreenCanvas(); View view = this.onScreenCanvas.getView(); Rectangle rect = this.onScreenCanvas.getBounds(); this.offScreenCanvas.setBounds(rect); Screen3D offScreenScreen = offScreenCanvas.getScreen3D(); Screen3D onScreenScreen = onScreenCanvas.getScreen3D(); offScreenScreen.setPhysicalScreenHeight(onScreenScreen.getPhysicalScreenHeight()); offScreenScreen.setPhysicalScreenWidth(onScreenScreen.getPhysicalScreenWidth()); offScreenScreen.setSize(onScreenScreen.getSize()); Point loc = onScreenCanvas.getLocationOnScreen(); offScreenCanvas.setOffScreenLocation(loc); offScreenCanvas.invalidate(); offScreenCanvas.renderOffScreenBufferAndWait(); BufferedImage img2 = offScreenCanvas.getOffScreenBuffer().getImage(); // offScreenCanvas.setOffScreenBuffer(null); // Must free off screen canvas ID for next use. return img2; }

public void attachOffScreenCanvas() {
if (offScreenCanvas != null) {
// Create a new off screen canvas if the size has changed.
if (!offScreenCanvas.getSize().equals(onScreenCanvas.getSize())) { // Re-size only seems to work for the 3D part, but the actors // don't render correctly in the first screen shot. // Must create a whole new offscreen canvas to get this to work when the window size changes. resizeImageBufferIfNeeded(); return; // this.detachOffScreenCanvas(); }
else {
// Size not changed so we can re-use the existing offscreen canvas.
// However,
// Detach and re-attach current offscreen canvas for each snapshot.
// This is the only thing that seems to fix the blank image issue that
// occures if this component is not visible or active.
// This step is not needed if the current component is always visible and active on the screen.
View view = this.onScreenCanvas.getView();
if (view == null) { return; }
boolean started = view.isViewRunning();
if (started) { view.stopView(); }
view.removeCanvas3D(offScreenCanvas);
view.addCanvas3D(offScreenCanvas);
if (started) { view.startView(); }
return; // no need to re-create the offscreen canvas
}
}
// If offscreen canvas is null, create, size, and attach the offscreen canvas
// offScreenCanvas = new OffScreenCanvas3D(this.onScreenCanvas.getGraphicsConfiguration(), 3000);
GraphicsConfiguration gconfig = SimpleUniverse.getPreferredConfiguration();
offScreenCanvas = new OffScreenCanvas3D(gconfig, 3000);
resizeImageBufferIfNeeded();
offScreenCanvas.setBounds(onScreenCanvas.getBounds());
for (Java2DActor java2DActor : actors) { offScreenCanvas.addActor((Java2DActor) java2DActor.clone()); }
View view = onScreenCanvas.getView();
boolean started = view.isViewRunning();
if (started) { view.stopView(); }
view.addCanvas3D(offScreenCanvas);
if (started) { view.startView(); }
}

private void resizeImageBufferIfNeeded() {
boolean resize = false;
Rectangle rect = onScreenCanvas.getBounds();
if (this.offScreenImageBuffer == null) { resize = true; }
else {
if (offScreenImageBuffer.getWidth() != rect.getWidth()) { resize = true; }
if (offScreenImageBuffer.getHeight() != rect.getHeight()) { resize = true; } }
}
if (!resize) { return; } }
if(rect.width<=0) return; // Not sized yet
offScreenImageBuffer = new BufferedImage(rect.width, rect.height, BufferedImage.TYPE_INT_RGB);
ImageComponent2D comp = new ImageComponent2D(ImageComponent.FORMAT_RGB, offScreenImageBuffer, true, false);
offScreenCanvas.setOffScreenBuffer(comp);
}

/**

  • Get preferred configuration for Canvas3D.
  • This method is the same as SimpleUniverise.getPreferredConfiguration().
  • For dual monitor situations, it only works if the main application window
  • is on the primary monitor.
    *
  • @return
    */
    public static GraphicsConfiguration getPreferredConfiguration() {
    GraphicsConfigTemplate3D template = new GraphicsConfigTemplate3D();
    String stereo;

// Check if the user has set the Java 3D stereo option.
// Getting the system properties causes appletviewer to fail with a
// security exception without a try/catch.

stereo = (String) java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {

public Object run() { return System.getProperty("j3d.stereo"); }
});

// update template based on properties.
if (stereo != null) {
if (stereo.equals("REQUIRED")) { template.setStereo(template.REQUIRED); }
else if (stereo.equals("PREFERRED")) { template.setStereo(template.PREFERRED); }
}

// Return the GraphicsConfiguration that best fits our needs.
return GraphicsEnvironment.getLocalGraphicsEnvironment().
getDefaultScreenDevice().getBestConfiguration(template);
}

/**
* Get a GraphicsConfiguration configured for Java3D for the target
* GraphicsDevice.
* The device should be the screen device for displaying the intended Canvas3D.
*
* @return
*/
public static GraphicsConfiguration getPreferredConfiguration(GraphicsDevice device) {

GraphicsConfigTemplate3D template = new GraphicsConfigTemplate3D();
String stereo;

// Check if the user has set the Java 3D stereo option.
// Getting the system properties causes appletviewer to fail with a
// security exception without a try/catch.

stereo = (String) java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {

public Object run() { return System.getProperty("j3d.stereo"); } }
});

// update template based on properties.
if (stereo != null) {
if (stereo.equals("REQUIRED")) { template.setStereo(template.REQUIRED); }
else if (stereo.equals("PREFERRED")) { template.setStereo(template.PREFERRED); }
}

// Return the GraphicsConfiguration that best fits our needs.
return device.getBestConfiguration(template);

}

/**

  • Get preferred configuration for Canvas3D for Canvas3D objects intended to be
  • added to the provided Window.
  • In multiple monitor situations you should pass the window to this method
  • and it will figure out the correct GraphicContext for the monitor holding the
  • target window.
    *
  • @return
    */
    public static GraphicsConfiguration getPreferredConfiguration(Window window) { GraphicsDevice device = window.getGraphicsConfiguration().getDevice(); return getPreferredConfiguration(device); }

/**

  • Adds a Java2DActor class to the Canvas3D components in this container.
  • Java2DActors perform post render drawing or other interactions on top of the
  • Canvas3D.
  • @param actor Adds this actor (not a clone) to the onScreenCanvas.
  • Adds a clone of this actor to the offscreen canvas.
    */
    public void addActor(Java2DActor actor)
    Unknown macro: { this.actors.add(actor); this.onScreenCanvas.addActor(actor); if (offScreenCanvas != null) { offScreenCanvas.addActor((Java2DActor) actor.clone()); } }

/**

  • Removes a Java2DActor class to the Canvas3D components in this container.
  • Java2DActors perform post render drawing or other interactions on top of the
  • Canvas3D.
  • @param actor
    */
    public boolean removeActor(Java2DActor actor)
    Unknown macro: { this.actors.remove(actor); boolean result = this.onScreenCanvas.removeActor(actor); if (offScreenCanvas != null) { offScreenCanvas.removeActor(actor); } return result; }

/**

  • This method detaches and removes the offscreen canvas. The offscreen
  • canvas is used for making screen shots of the view. It will be automatically
  • re-created when needed for a screenshot.
  • Due to issues in Java3D, this method must be called to
  • explicitly delete the offscreen canvas whenever certain view properties are changed
  • such as the projection policy, screen size, etc.
    */
    public void detachOffScreenCanvas() {
    if (offScreenCanvas != null)
    Unknown macro: { View oldView = offScreenCanvas.getView(); if (oldView != null) { oldView.removeCanvas3D(offScreenCanvas); } offScreenCanvas = null; offScreenImageBuffer = null; }

    }
    }


 Comments   
Comment by harveyh [ 10/Oct/12 07:00 AM ]

I've got a fork of the Java3d code up on github that has a JOGL2 backend, maybe you'll
have better luck with those:

https://github.com/hharrison/java3d-core
https://github.com/hharrison/java3d-utils
https://github.com/hharrison/vecmath

I'm working with the people at jogamp.org to get binary builds going again, feel free to
inquire in the java3d forum there if you have any questions...or if you have success.

Harvey

Generated at Thu Apr 24 13:21:45 UTC 2014 using JIRA 4.0.2#472.