/*
* JAME 6.2.1
* http://jame.sourceforge.net
*
* Copyright 2001, 2016 Andrea Medeghini
*
* This file is part of JAME.
*
* JAME is an application for creating fractals and other graphics artifacts.
*
* JAME is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* JAME is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with JAME. If not, see <http://www.gnu.org/licenses/>.
*
*/
package net.sf.jame.mandelbrot.renderer;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.util.Map;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Logger;
import net.sf.jame.core.math.Complex;
import net.sf.jame.core.util.Colors;
import net.sf.jame.core.util.DefaultThreadFactory;
import net.sf.jame.core.util.DoubleVector2D;
import net.sf.jame.core.util.DoubleVector4D;
import net.sf.jame.core.util.IntegerVector2D;
import net.sf.jame.core.util.IntegerVector4D;
import net.sf.jame.core.util.RenderWorker;
import net.sf.jame.core.util.Surface;
import net.sf.jame.core.util.Tile;
import net.sf.jame.mandelbrot.MandelbrotRuntime;
import net.sf.jame.mandelbrot.fractal.MandelbrotFractalRuntimeElement;
import net.sf.jame.mandelbrot.incolouringFormula.IncolouringFormulaRuntimeElement;
import net.sf.jame.mandelbrot.outcolouringFormula.OutcolouringFormulaRuntimeElement;
import net.sf.jame.twister.renderer.TwisterRenderer;
import net.sf.jame.twister.util.View;
/**
* @author Andrea Medeghini
*/
public abstract class AbstractMandelbrotRenderer implements MandelbrotRenderer {
protected static final Logger logger = Logger.getLogger(AbstractMandelbrotRenderer.class.getName());
private Graphics2D newBuffer;
private Graphics2D oldBuffer;
private BufferedImage newImage;
private BufferedImage oldImage;
private IntegerVector2D bufferSize;
private Tile newTile;
private Tile oldTile;
private int imageDim;
private int tileDim;
protected int newShiftValue;
protected int oldShiftValue;
protected double rotationValue;
protected DoubleVector2D newConstant;
protected DoubleVector2D oldConstant;
protected int renderMode = MandelbrotRenderer.MODE_CALCULATE;
protected int newImageMode = 0;
protected int oldImageMode = 0;
private boolean dynamic = false;
private boolean dynamicZoom = false;
private AffineTransform transform = new AffineTransform();
protected RenderedArea area = new RenderedArea();
protected RenderingStrategy renderingStrategy;
protected MandelbrotRuntime runtime;
private final Complex center = new Complex();
private final Complex scale = new Complex();
protected View newView = new View(new IntegerVector4D(0, 0, 0, 0), new DoubleVector4D(0, 0, 1, 0), new DoubleVector4D(0, 0, 0, 0));
protected View oldView = new View(new IntegerVector4D(0, 0, 0, 0), new DoubleVector4D(0, 0, 1, 0), new DoubleVector4D(0, 0, 0, 0));
protected int percent = 100;
protected MandelbrotFractalRuntimeElement fractalRuntime;
protected int status = TwisterRenderer.STATUS_TERMINATED;
private final MandelbrotWorker renderWorker;
protected final ThreadFactory factory;
private boolean viewChanged;
private boolean invalidated;
private final Object lock = new Object();
/**
* @param threadPriority
*/
public AbstractMandelbrotRenderer(final int threadPriority) {
factory = new DefaultThreadFactory("MandelbrotRendererWorker", true, threadPriority);
renderWorker = new MandelbrotWorker(factory);
}
/**
*
*/
public void start() {
renderWorker.start();
}
/**
*
*/
public void stop() {
renderWorker.stop();
}
public void startTasks() {
renderWorker.executeTask();
}
public void clearTasks() {
renderWorker.clearTasks();
}
public void abortTasks() {
renderWorker.abortTasks();
}
/**
* @see net.sf.jame.mandelbrot.renderer.MandelbrotRenderer#asyncStop()
*/
public final void asyncStop() {
clearTasks();
abortTasks();
}
/**
* @see net.sf.jame.mandelbrot.renderer.MandelbrotRenderer#asyncStart()
*/
public final void asyncStart() {
startTasks();
}
/**
* @see java.lang.Object#finalize()
*/
@Override
public void finalize() throws Throwable {
dispose();
area = null;
transform = null;
renderingStrategy = null;
super.finalize();
}
/**
* @see net.sf.jame.mandelbrot.renderer.MandelbrotRenderer#dispose()
*/
public final void dispose() {
stop();
free();
}
/**
* @see net.sf.jame.mandelbrot.renderer.MandelbrotRenderer#getRuntime()
*/
public MandelbrotRuntime getRuntime() {
return runtime;
}
/**
* @see net.sf.jame.mandelbrot.renderer.MandelbrotRenderer#setRuntime(net.sf.jame.mandelbrot.fractal.MandelbrotFractalRuntimeElement)
*/
public void setRuntime(final MandelbrotRuntime runtime) {
synchronized (this) {
this.runtime = runtime;
}
}
/**
* @see net.sf.jame.mandelbrot.renderer.MandelbrotRenderer#setView(net.sf.jame.twister.util.View, net.sf.jame.core.util.DoubleVector2D, int)
*/
public void setView(final View view, final DoubleVector2D constant, final int imageMode) {
synchronized (this) {
newView = view;
newConstant = constant;
newImageMode = imageMode;
}
}
/**
* @see net.sf.jame.mandelbrot.renderer.MandelbrotRenderer#setView(net.sf.jame.twister.util.View)
*/
public void setView(final View view) {
synchronized (this) {
newView = view;
}
}
private void updateView(final View view) {
rotationValue = view.getRotation().getZ();
newShiftValue = (int) Math.rint(view.getRotation().getW());
if (newShiftValue < 0) {
newShiftValue = 0;
}
dynamic = (view.getStatus().getZ() == 1) || (view.getStatus().getW() == 1);
dynamicZoom = dynamic;
if (view.getStatus().getZ() == 2) {
setMode(MandelbrotRenderer.MODE_CALCULATE);
}
}
/**
* @see net.sf.jame.mandelbrot.renderer.MandelbrotRenderer#isDynamic()
*/
public boolean isDynamic() {
final boolean value = dynamic;
dynamic = false;
return value;
}
/**
* @see net.sf.jame.mandelbrot.renderer.MandelbrotRenderer#isViewChanged()
*/
public boolean isViewChanged() {
final boolean value = viewChanged;
viewChanged = false;
return value;
}
/**
*
*/
protected void free() {
if (newBuffer != null) {
newBuffer.dispose();
}
if (newImage != null) {
newImage.flush();
}
newImage = null;
newBuffer = null;
if (oldBuffer != null) {
oldBuffer.dispose();
}
if (oldImage != null) {
oldImage.flush();
}
oldImage = null;
oldBuffer = null;
}
/**
*
*/
protected void init() {
imageDim = (int) Math.sqrt(((oldTile.getImageSize().getX() + oldTile.getTileBorder().getX() * 2) * (oldTile.getImageSize().getX() + oldTile.getTileBorder().getX() * 2)) + ((oldTile.getImageSize().getY() + oldTile.getTileBorder().getY() * 2) * (oldTile.getImageSize().getY() + oldTile.getTileBorder().getY() * 2)));
tileDim = (int) Math.sqrt(((oldTile.getTileSize().getX() + oldTile.getTileBorder().getX() * 2) * (oldTile.getTileSize().getX() + oldTile.getTileBorder().getX() * 2)) + ((oldTile.getTileSize().getY() + oldTile.getTileBorder().getY() * 2) * (oldTile.getTileSize().getY() + oldTile.getTileBorder().getY() * 2)));
bufferSize = new IntegerVector2D(tileDim, tileDim);
newImage = new BufferedImage(bufferSize.getX(), bufferSize.getY(), Surface.DEFAULT_TYPE);
oldImage = new BufferedImage(bufferSize.getX(), bufferSize.getY(), Surface.DEFAULT_TYPE);
newBuffer = newImage.createGraphics();
oldBuffer = oldImage.createGraphics();
}
/**
*
*/
protected final void swapImages() {
synchronized (lock) {
final BufferedImage tmpImage = oldImage;
oldImage = newImage;
newImage = tmpImage;
final Graphics2D tmpBuffer = oldBuffer;
oldBuffer = newBuffer;
newBuffer = tmpBuffer;
}
}
/**
* @see net.sf.jame.mandelbrot.renderer.MandelbrotRenderer#setRenderingHints(java.util.Map)
*/
public void setRenderingHints(final Map<Object, Object> hints) {
}
/**
* @see net.sf.jame.mandelbrot.renderer.MandelbrotRenderer#setMode(int)
*/
public void setMode(final int renderMode) {
this.renderMode |= renderMode;
}
/**
* @see net.sf.jame.mandelbrot.renderer.MandelbrotRenderer#getMode()
*/
public int getMode() {
return renderMode;
}
/**
* @see net.sf.jame.mandelbrot.renderer.MandelbrotRenderer#startRenderer()
*/
public void startRenderer() {
renderWorker.executeTask();
}
/**
* @see net.sf.jame.mandelbrot.renderer.MandelbrotRenderer#abortRenderer()
*/
public void abortRenderer() {
renderWorker.abortTasks();
}
/**
* @see net.sf.jame.mandelbrot.renderer.MandelbrotRenderer#joinRenderer()
*/
public void joinRenderer() throws InterruptedException {
renderWorker.waitTasks();
}
private void render() {
synchronized (this) {
fractalRuntime = runtime.getMandelbrotFractal();
if (oldView != newView) {
viewChanged = true;
updateView(newView);
}
if (newShiftValue != oldShiftValue) {
setMode(MandelbrotRenderer.MODE_REFRESH);
}
if (newImageMode != oldImageMode) {
setMode(MandelbrotRenderer.MODE_CALCULATE);
}
if ((newConstant != oldConstant) && (newImageMode != 0)) {
setMode(MandelbrotRenderer.MODE_CALCULATE);
}
if (fractalRuntime.isRenderingFormulaChanged()) {
setMode(MandelbrotRenderer.MODE_CALCULATE);
}
if (fractalRuntime.isTransformingFormulaChanged()) {
setMode(MandelbrotRenderer.MODE_CALCULATE);
}
if (fractalRuntime.isProcessingFormulaChanged()) {
setMode(MandelbrotRenderer.MODE_CALCULATE);
}
if (fractalRuntime.isOrbitTrapChanged()) {
setMode(MandelbrotRenderer.MODE_CALCULATE);
}
if (fractalRuntime.isIncolouringFormulaChanged()) {
setMode(MandelbrotRenderer.MODE_REFRESH);
}
if (fractalRuntime.isOutcolouringFormulaChanged()) {
setMode(MandelbrotRenderer.MODE_REFRESH);
}
// if (!isDynamic) {
// setMode(FractalRenderer.MODE_REFRESH);
// }
oldView = newView;
oldConstant = newConstant;
oldImageMode = newImageMode;
oldShiftValue = newShiftValue;
if (newTile != oldTile) {
setMode(MandelbrotRenderer.MODE_CALCULATE);
oldTile = newTile;
free();
init();
}
}
if (oldImageMode == 0) {
renderingStrategy = getMandelbrotRenderingStrategy();
if ((fractalRuntime.getRenderingFormula() != null) && (fractalRuntime.getRenderingFormula().getFormulaRuntime() != null) && !fractalRuntime.getRenderingFormula().getFormulaRuntime().isMandelbrotModeAllowed()) {
status = TwisterRenderer.STATUS_TERMINATED;
newBuffer.clearRect(0, 0, newImage.getWidth(), newImage.getHeight());
dynamicZoom = false;
return;
}
}
else {
renderingStrategy = getJuliaRenderingStrategy();
}
percent = 0;
status = TwisterRenderer.STATUS_RENDERING;
doRender(dynamicZoom);
if (percent == 100) {
status = TwisterRenderer.STATUS_TERMINATED;
}
else {
status = TwisterRenderer.STATUS_ABORTED;
}
dynamicZoom = false;
}
/**
* @see net.sf.jame.mandelbrot.renderer.MandelbrotRenderer#getRenderingStatus()
*/
public int getRenderingStatus() {
return status;
}
/**
* @see net.sf.jame.mandelbrot.renderer.MandelbrotRenderer#setTile(net.sf.jame.core.util.Tile)
*/
public void setTile(final Tile tile) {
synchronized (this) {
invalidated = false;
newTile = tile;
}
}
/**
* @see net.sf.jame.mandelbrot.renderer.MandelbrotRenderer#getTile()
*/
public Tile getTile() {
return oldTile;
}
/**
* @return
*/
public int getBufferWidth() {
return bufferSize.getX();
}
/**
* @return
*/
public int getBufferHeight() {
return bufferSize.getY();
}
/**
* @see net.sf.jame.mandelbrot.renderer.MandelbrotRenderer#setMandelbrotMode(boolean)
*/
public void setMandelbrotMode(final Integer mode) {
synchronized (this) {
newImageMode = mode;
}
}
/**
* @see net.sf.jame.mandelbrot.renderer.MandelbrotRenderer#setConstant(net.sf.jame.core.util.DoubleVector2D)
*/
public void setConstant(final DoubleVector2D constant) {
synchronized (this) {
newConstant = constant;
}
}
/**
* @return
*/
protected boolean isTileSupported() {
return true;
}
/**
*
*/
protected void updateShift() {
}
/**
*
*/
protected void updateRegion() {
if ((fractalRuntime.getRenderingFormula() != null) && (fractalRuntime.getRenderingFormula().getFormulaRuntime() != null)) {
final DoubleVector2D s = fractalRuntime.getRenderingFormula().getFormulaRuntime().getScale();
final double x = oldView.getPosition().getX();
final double y = oldView.getPosition().getY();
final double z = oldView.getPosition().getZ();
scale.r = s.getX() * z;
scale.i = s.getY() * z;
center.r = fractalRuntime.getRenderingFormula().getFormulaRuntime().getCenter().getX() + x;
center.i = fractalRuntime.getRenderingFormula().getFormulaRuntime().getCenter().getY() + y;
final double imageOffsetX = (imageDim - oldTile.getImageSize().getX()) / 2;
final double imageOffsetY = (imageDim - oldTile.getImageSize().getY()) / 2;
double sx = (scale.r * 0.5d * imageDim) / oldTile.getImageSize().getX();
double sy = (scale.i * 0.5d * imageDim) / oldTile.getImageSize().getX();
Complex p0 = new Complex(center.r - sx, center.i - sy);
Complex p1 = new Complex(center.r + sx, center.i + sy);
final Complex t0 = new Complex();
final Complex t1 = new Complex();
final double dr = p1.r - p0.r;
final double di = p1.i - p0.i;
t0.r = p0.r + dr * (imageOffsetX + oldTile.getTileOffset().getX() + oldTile.getTileSize().getX() / 2d) / imageDim;
t0.i = p0.i + di * (imageOffsetY + oldTile.getTileOffset().getY() + oldTile.getTileSize().getY() / 2d) / imageDim;
t1.r = p0.r + dr * 0.5;
t1.i = p0.i + di * 0.5;
final AffineTransform t = new AffineTransform();
t.rotate(-rotationValue, t1.r / dr, t1.i / di);
Point2D.Double p = new Point2D.Double(t0.r / dr, t0.i / di);
p = (Point2D.Double) t.transform(p, p);
p.setLocation(p.getX() * dr, p.getY() * di);
sx = dr * 0.5 * bufferSize.getX() / imageDim;
sy = di * 0.5 * bufferSize.getY() / imageDim;
p0 = new Complex(p.getX() - sx, p.getY() - sy);
p1 = new Complex(p.getX() + sx, p.getY() + sy);
area.points[0] = p0;
area.points[1] = p1;
}
else {
final Complex p0 = new Complex(-0.5, +0.5);
final Complex p1 = new Complex(-0.5, +0.5);
area.points[0] = p0;
area.points[1] = p1;
}
}
/**
*
*/
protected void updateTransform() {
final int offsetX = (getBufferWidth() - oldTile.getTileSize().getX() - oldTile.getTileBorder().getX() * 2) / 2;
final int offsetY = (getBufferHeight() - oldTile.getTileSize().getY() - oldTile.getTileBorder().getY() * 2) / 2;
transform.setToTranslation(-offsetX, -offsetY);
final int centerX = getBufferWidth() / 2;
final int centerY = getBufferHeight() / 2;
transform.rotate(rotationValue, centerX, centerY);
}
/**
* @param p
* @return the color.
*/
protected int renderPoint(final RenderedPoint cp) {
fractalRuntime.getRenderingFormula().getFormulaRuntime().renderPoint(cp);
return renderColor(cp);
}
/**
* @param p
* @return the color.
*/
protected int renderColor(final RenderedPoint cp) {
int newRGB = 0;
int tmpRGB = 0;
if (cp.time > 0) {
if (fractalRuntime.getOutcolouringFormulaCount() == 1) {
final OutcolouringFormulaRuntimeElement outcolouringFormula = fractalRuntime.getOutcolouringFormula(0);
if ((outcolouringFormula.getFormulaRuntime() != null) && outcolouringFormula.isEnabled()) {
if (newShiftValue != 0) {
newRGB = outcolouringFormula.getFormulaRuntime().renderColor(cp, newShiftValue);
}
else {
newRGB = outcolouringFormula.getFormulaRuntime().renderColor(cp);
}
}
}
else {
for (int i = 0; i < fractalRuntime.getOutcolouringFormulaCount(); i++) {
final OutcolouringFormulaRuntimeElement outcolouringFormula = fractalRuntime.getOutcolouringFormula(i);
if ((outcolouringFormula.getFormulaRuntime() != null) && outcolouringFormula.isEnabled()) {
if (newShiftValue != 0) {
tmpRGB = outcolouringFormula.getFormulaRuntime().renderColor(cp, newShiftValue);
}
else {
tmpRGB = outcolouringFormula.getFormulaRuntime().renderColor(cp);
}
newRGB = (newRGB != 0) ? Colors.mixColors(newRGB, tmpRGB, outcolouringFormula.getOpacity()) : tmpRGB;
}
}
}
return newRGB;
}
else {
if (fractalRuntime.getIncolouringFormulaCount() == 1) {
final IncolouringFormulaRuntimeElement incolouringFormula = fractalRuntime.getIncolouringFormula(0);
if ((incolouringFormula.getFormulaRuntime() != null) && incolouringFormula.isEnabled()) {
if (newShiftValue != 0) {
newRGB = incolouringFormula.getFormulaRuntime().renderColor(cp, newShiftValue);
}
else {
newRGB = incolouringFormula.getFormulaRuntime().renderColor(cp);
}
}
}
else {
for (int i = 0; i < fractalRuntime.getIncolouringFormulaCount(); i++) {
final IncolouringFormulaRuntimeElement incolouringFormula = fractalRuntime.getIncolouringFormula(i);
if ((incolouringFormula.getFormulaRuntime() != null) && incolouringFormula.isEnabled()) {
if (newShiftValue != 0) {
tmpRGB = incolouringFormula.getFormulaRuntime().renderColor(cp, newShiftValue);
}
else {
tmpRGB = incolouringFormula.getFormulaRuntime().renderColor(cp);
}
newRGB = (newRGB != 0) ? Colors.mixColors(newRGB, tmpRGB, incolouringFormula.getOpacity()) : tmpRGB;
}
}
}
return newRGB;
}
}
/**
* @param dynamic
*/
protected abstract void doRender(boolean dynamic);
/**
* @return true if solidguess is supported.
*/
public boolean isSolidGuessSupported() {
for (int i = 0; i < fractalRuntime.getOutcolouringFormulaCount(); i++) {
final OutcolouringFormulaRuntimeElement outcolouringFormula = fractalRuntime.getOutcolouringFormula(i);
if ((outcolouringFormula.getFormulaRuntime() != null) && !outcolouringFormula.getFormulaRuntime().isSolidGuessAllowed() && outcolouringFormula.isEnabled()) {
return false;
}
}
for (int i = 0; i < fractalRuntime.getIncolouringFormulaCount(); i++) {
final IncolouringFormulaRuntimeElement incolouringFormula = fractalRuntime.getIncolouringFormula(i);
if ((incolouringFormula.getFormulaRuntime() != null) && !incolouringFormula.getFormulaRuntime().isSolidGuessAllowed() && incolouringFormula.isEnabled()) {
return false;
}
}
return true;
}
/**
* @return true if symetry is supported.
*/
public boolean isVerticalSymetrySupported() {
return renderingStrategy.isVerticalSymetrySupported();
}
/**
* @return true if symetry is supported.
*/
public boolean isHorizontalSymetrySupported() {
return renderingStrategy.isHorizontalSymetrySupported();
}
/**
* @see net.sf.jame.mandelbrot.renderer.MandelbrotRenderer#drawImage(java.awt.Graphics2D)
*/
public void drawImage(final Graphics2D g) {
synchronized (lock) {
final AffineTransform t = g.getTransform();
if (oldTile != null) {
// g.setClip(oldTile.getTileBorder().getX(), oldTile.getTileBorder().getY(), oldTile.getTileSize().getX(), oldTile.getTileSize().getY());
// g.setClip(0, 0, oldTile.getTileSize().getX() + oldTile.getTileBorder().getX() + 2, oldTile.getTileSize().getY() + oldTile.getTileBorder().getY() + 2);
g.setTransform(transform);
g.drawImage(newImage, 0, 0, null);
g.setTransform(t);
g.setClip(null);
// g.dispose();
}
}
}
/**
* @see net.sf.jame.mandelbrot.renderer.MandelbrotRenderer#drawImage(java.awt.Graphics2D, int, int)
*/
public void drawImage(final Graphics2D g, final int x, final int y) {
synchronized (lock) {
final AffineTransform t = g.getTransform();
if (oldTile != null) {
// g.setClip(oldTile.getTileBorder().getX(), oldTile.getTileBorder().getY(), oldTile.getTileSize().getX(), oldTile.getTileSize().getY());
// g.setClip(0, 0, oldTile.getTileSize().getX() + oldTile.getTileBorder().getX() + 2, oldTile.getTileSize().getY() + oldTile.getTileBorder().getY() + 2);
g.setTransform(transform);
g.drawImage(newImage, x, y, null);
g.setTransform(t);
g.setClip(null);
// g.dispose();
}
}
}
/**
* @see net.sf.jame.mandelbrot.renderer.MandelbrotRenderer#drawImage(java.awt.Graphics2D, int, int, int, int)
*/
public void drawImage(final Graphics2D g, final int x, final int y, final int w, final int h) {
synchronized (lock) {
final AffineTransform t = g.getTransform();
if (oldTile != null) {
g.setClip(x, y, w, h);
g.setTransform(transform);
final double sx = w / (double) getTile().getTileSize().getX();
final double sy = h / (double) getTile().getTileSize().getY();
final int dw = (int) Math.rint(bufferSize.getX() * sx);
final int dh = (int) Math.rint(bufferSize.getY() * sy);
g.drawImage(newImage, x, y, dw, dh, null);
g.setTransform(t);
g.setClip(null);
// g.dispose();
}
}
}
/**
* @return
*/
protected Graphics2D getGraphics() {
swapImages();
return newBuffer;
}
/**
* @see net.sf.jame.mandelbrot.renderer.MandelbrotRenderer#isInterrupted()
*/
public boolean isInterrupted() {
return Thread.currentThread().isInterrupted();
}
/**
* @return the strategy.
*/
protected abstract RenderingStrategy getMandelbrotRenderingStrategy();
/**
* @return the strategy.
*/
protected abstract RenderingStrategy getJuliaRenderingStrategy();
/**
* @author Andrea Medeghini
*/
protected interface RenderingStrategy {
/**
* @param p
* @param pw
* @param px
* @return the time
*/
public int renderPoint(RenderedPoint p, Complex px, Complex pw);
/**
* @return true if vertical symetry is supported.
*/
public boolean isVerticalSymetrySupported();
/**
* @return true if horizontal symetry is supported.
*/
public boolean isHorizontalSymetrySupported();
/**
*
*/
public void updateParameters();
}
private class MandelbrotWorker extends RenderWorker {
/**
* @param factory
*/
public MandelbrotWorker(ThreadFactory factory) {
super(factory);
}
/**
* @see net.sf.jame.core.util.RenderWorker#execute()
*/
@Override
protected void execute() {
try {
if (!invalidated) {
render();
}
}
catch (final Throwable e) {
invalidated = true;
e.printStackTrace();
}
}
}
}