package org.geogebra.desktop.geogebra3D.euclidian3D.opengl;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import javax.media.opengl.GL;
import javax.media.opengl.GL2ES1;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.fixedfunc.GLLightingFunc;
import org.geogebra.common.awt.GBufferedImage;
import org.geogebra.common.geogebra3D.euclidian3D.EuclidianView3D;
import org.geogebra.common.geogebra3D.euclidian3D.draw.DrawLabel3D;
import org.geogebra.common.geogebra3D.euclidian3D.draw.Drawable3D;
import org.geogebra.common.geogebra3D.euclidian3D.openGL.RendererWithImpl;
import org.geogebra.common.util.debug.Log;
import org.geogebra.desktop.awt.GBufferedImageD;
import org.geogebra.desktop.geogebra3D.euclidian3D.EuclidianView3DD;
import org.geogebra.desktop.gui.menubar.GeoGebraMenuBar;
import org.geogebra.desktop.gui.util.ImageSelection;
import org.geogebra.desktop.util.FrameCollector;
/**
* Renderer checking if we can use shaders or not
*
* @author mathieu
*
*/
public class RendererCheckGLVersionD extends RendererWithImpl
implements GLEventListener {
protected RendererJogl jogl;
private Animator animator;
/** canvas usable for a JPanel */
public Component3D canvas;
protected FrameCollector gifEncoder;
protected BufferedImage bi;
private BufferedImage[] equirectangularTilesLeft;
private BufferedImage[] equirectangularTilesRight;
/**
* Constructor
*
* @param view
* @param useCanvas
*/
public RendererCheckGLVersionD(EuclidianView3D view, boolean useCanvas) {
this(view, useCanvas, RendererType.NOT_SPECIFIED);
}
private boolean isGL2ES2;
/**
* Constructor
*
* @param view
* @param useCanvas
* @param type
*/
public RendererCheckGLVersionD(EuclidianView3D view, boolean useCanvas,
RendererType type) {
super(view, type);
Log.debug("create jogl -- use Canvas : " + useCanvas);
jogl = new RendererJogl();
Log.debug("create jogl -- default profile");
// init jogl profile
isGL2ES2 = RendererJogl.setDefaultProfile();
// canvas = view;
Log.debug("create 3D component -- use Canvas : " + useCanvas);
RendererJogl.initCaps(view.getCompanion().isStereoBuffered());
canvas = RendererJogl.createComponent3D(useCanvas);
Log.debug("add gl event listener");
canvas.addGLEventListener(this);
Log.debug("create animator");
animator = RendererJogl.createAnimator(canvas, 60);
// animator.setRunAsFastAsPossible(true);
// animator.setRunAsFastAsPossible(false);
Log.debug("start animator");
animator.start();
}
/**
* Called by the drawable immediately after the OpenGL context is
* initialized for the first time. Can be used to perform one-time OpenGL
* initialization such as setup of lights and display lists.
*
* @param drawable
* The GLAutoDrawable object.
*/
@Override
public void init(GLAutoDrawable drawable) {
initCheckShaders(drawable);
setGL(drawable);
// check openGL version
final String version = getGL().glGetString(GL.GL_VERSION);
// Check For VBO support
final boolean VBOsupported = getGL()
.isFunctionAvailable("glGenBuffersARB")
&& getGL().isFunctionAvailable("glBindBufferARB")
&& getGL().isFunctionAvailable("glBufferDataARB")
&& getGL().isFunctionAvailable("glDeleteBuffersARB");
Log.debug("openGL version : " + version + ", vbo supported : "
+ VBOsupported);
getRendererImpl().initFBO();
init();
}
private void initCheckShaders(GLAutoDrawable drawable) {
// start init
String glInfo[] = RendererJogl.getGLInfos(drawable);
String glCard = glInfo[6];
String glVersion = glInfo[7];
Log.debug("Init on " + Thread.currentThread()
+ "\nChosen GLCapabilities: " + glInfo[0]
+ "\ndouble buffered: " + glInfo[1] + "\nstereo: " + glInfo[2]
+ "\nstencil: " + glInfo[3] + "\nINIT GL IS: " + glInfo[4]
+ "\nGL_VENDOR: " + glInfo[5] + "\nGL_RENDERER: " + glCard
+ "\nGL_VERSION: " + glVersion + "\nisGL2ES2: " + isGL2ES2);
GeoGebraMenuBar.setGlCard(glInfo[6]);
GeoGebraMenuBar.setGlVersion(glInfo[7]);
// this is abstract method: don't create old GL / shaders here
if (getType() == RendererType.NOT_SPECIFIED) {
if (isGL2ES2) {
try {
// retrieving version, which should be first char, e.g.
// "4.0 etc."
String[] version = glVersion.split("\\.");
int versionInt = Integer.parseInt(version[0]);
Log.debug("==== GL version is " + glVersion
+ " which means GL>=" + versionInt);
if (versionInt < 3) {
// GL 1.x: can't use shaders
// GL 2.x so GLSL < 1.3: not supported
setType(RendererType.GL2);
} else if (versionInt >= 4) {
// GL 4.x or above: can use shaders (GLSL >= 4.0)
setType(RendererType.SHADER);
} else {
// GL 3.x so GLSL < 1.3: not supported
if (version.length > 1) {
versionInt = Integer.parseInt(version[1]);
Log.debug("==== GL minor version is " + versionInt);
if (versionInt < 3) {
// GL 3.0 -- 3.2 so GLSL < 3.3: not supported
setType(RendererType.GL2);
} else {
// GL 3.3: can use shaders (GLSL = 3.3)
setType(RendererType.SHADER);
}
} else {
// probably GL 3.0 so GLSL = 1.3: not supported
setType(RendererType.GL2);
}
}
} catch (Exception e) {
// exception: don't use shaders
setType(RendererType.GL2);
}
} else {
// not GL2ES2 capable
Log.debug("==== not GL2ES2 capable");
setType(RendererType.GL2);
}
}
if (getType() == RendererType.SHADER) {
setRendererImpl(new RendererImplShadersD(this, view3D, jogl));
} else {
setRendererImpl(new RendererImplGL2(this, view3D, jogl));
}
}
@Override
public void setLineWidth(double width) {
getGL().glLineWidth((float) width);
}
protected static final int[] GL_CLIP_PLANE = { GL2ES1.GL_CLIP_PLANE0,
GL2ES1.GL_CLIP_PLANE1, GL2ES1.GL_CLIP_PLANE2, GL2ES1.GL_CLIP_PLANE3,
GL2ES1.GL_CLIP_PLANE4, GL2ES1.GL_CLIP_PLANE5 };
@Override
public void dispose(GLAutoDrawable drawable) {
// DO NOT REMOVE THE METHOD HERE -- NEEDED TO AVOID ERRORS IN
// INSTALLED/PORTABLE VERSIONS
setGL(drawable);
getRendererImpl().dispose();
}
@Override
public Component3D getCanvas() {
return canvas;
}
@Override
public void resumeAnimator() {
animator.resume();
}
@Override
public void display() {
canvas.display();
}
/**
*
* openGL method called when the display is to be computed.
* <p>
* First, it calls {@link #doPick()} if a picking is to be done. Then, for
* each {@link Drawable3D}, it calls:
* <ul>
* <li>{@link Drawable3D#drawHidden(EuclidianRenderer3D)} to draw hidden
* parts (dashed segments, lines, ...)</li>
* <li>{@link Drawable3D#drawTransp(EuclidianRenderer3D)} to draw
* transparent objects (planes, spheres, ...)</li>
* <li>{@link Drawable3D#drawSurfacesForHiding(EuclidianRenderer3D)} to draw
* in the z-buffer objects that hides others (planes, spheres, ...)</li>
* <li>{@link Drawable3D#drawTransp(EuclidianRenderer3D)} to re-draw
* transparent objects for a better alpha-blending</li>
* <li>{@link Drawable3D#drawOutline(EuclidianRenderer3D)} to draw not
* hidden parts (dash-less segments, lines, ...)</li>
* </ul>
*/
@Override
public void display(GLAutoDrawable gLDrawable) {
// Log.debug(gLDrawable+"");
setGL(gLDrawable);
drawScene();
if (((EuclidianView3DD) view3D).hasPrinter()) {
if (getType() == RendererType.SHADER) {
((EuclidianView3DD) view3D).exportToPrinter3D();
}
}
}
@Override
protected final void exportImage() {
switch (getExportType()) {
case ANIMATEDGIF:
Log.debug("Exporting frame: " + getExportI());
setExportImage();
if (bi == null) {
Log.error("image null");
} else {
gifEncoder.addFrame(bi);
}
setExportVal(getExportVal() + getExportStep());
if (getExportVal() > getExportMax() + 0.00000001
|| getExportVal() < getExportMin() - 0.00000001) {
setExportVal(getExportVal() - 2 * getExportStep());
setExportStep(getExportStep() * -1);
}
setExportI(getExportI() + 1);
if (getExportI() >= getExportN()) {
setExportType(ExportType.NONE);
gifEncoder.finish();
Log.debug("GIF export finished");
getRendererImpl().endNeedExportImage();
} else {
getExportNum().setValue(getExportVal());
getExportNum().updateRepaint();
}
break;
case CLIPBOARD:
setExportType(ExportType.NONE);
Log.debug("Exporting to clipboard");
setExportImage();
if (bi == null) {
Log.error("image null");
} else {
ImageSelection imgSel = new ImageSelection(bi);
Toolkit.getDefaultToolkit().getSystemClipboard()
.setContents(imgSel, null);
}
getRendererImpl().endNeedExportImage();
break;
case UPLOAD_TO_GEOGEBRATUBE:
setExportType(ExportType.NONE);
Log.debug("Uploading to GeoGebraTube");
setExportImage();
if (bi == null) {
Log.error("image null, uploading with no preview");
// TODO: set 2D preview image
}
view3D.getApplication().uploadToGeoGebraTube();
getRendererImpl().endNeedExportImage();
break;
default:
if (needExportImage) {
setExportImage();
if (!getExportImageForThumbnail()) {
// call write to file
((EuclidianView3DD) view3D).writeExportImage();
}
getRendererImpl().endNeedExportImage();
}
break;
}
}
@Override
protected void exportImageEquirectangular() {
if (bi == null) {
Log.error("image null");
} else {
ImageSelection imgSel = new ImageSelection(bi);
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(imgSel,
null);
}
getRendererImpl().endNeedExportImage();
}
@Override
protected void setDepthFunc() {
getGL().glDepthFunc(GL.GL_LEQUAL); // less or equal for
// transparency
}
@Override
protected void enablePolygonOffsetFill() {
getGL().glEnable(GL.GL_POLYGON_OFFSET_FILL);
}
@Override
protected void setBlendFunc() {
getGL().glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
}
@Override
protected void enableNormalNormalized() {
getGL().glEnable(GLLightingFunc.GL_NORMALIZE);
}
/**
* openGL method called when the canvas is reshaped.
*/
@Override
public void reshape(GLAutoDrawable drawable, int x, int y, int w, int h) {
setGL(drawable);
setView(x, y, w, h);
view3D.reset();
}
/**
* remove texture at index
*
* @param index
* texture index
*/
public void removeTexture(int index) {
getGL().glDeleteTextures(1, new int[] { index }, 0);
}
@Override
public GBufferedImage createBufferedImage(DrawLabel3D label) {
return new GBufferedImageD(label.getWidth(), label.getHeight(),
GBufferedImage.TYPE_INT_ARGB);
}
@Override
public void createAlphaTexture(DrawLabel3D label, GBufferedImage bimg) {
byte[] buffer = ARGBtoAlpha(label, ((GBufferedImageD) bimg).getData());
label.setTextureIndex(createAlphaTexture(label.getTextureIndex(),
label.waitForReset(), label.getWidthPowerOfTwo(),
label.getHeightPowerOfTwo(), buffer));
}
/**
* @param textureIndex
* texture index
* @param waitForReset
* wait for reset
* @param sizeX
* @param sizeY
* @param buf
* @return a texture for alpha channel
*/
private int createAlphaTexture(int textureIndex, boolean waitForReset,
int sizeX, int sizeY, byte[] buf) {
if (textureIndex != 0 && !waitForReset) {
removeTexture(textureIndex);
}
enableTextures2D();
int[] index = new int[1];
genTextures2D(1, index);
bindTexture(index[0]);
textureImage2D(sizeX, sizeY, buf);
disableTextures2D();
return index[0];
}
@Override
public void textureImage2D(int sizeX, int sizeY, byte[] buf) {
getGL().glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_ALPHA, sizeX, sizeY, 0,
GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE, ByteBuffer.wrap(buf));
}
@Override
public void setTextureLinear() {
getGL().glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER,
GL.GL_LINEAR);
getGL().glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER,
GL.GL_LINEAR);
getGL().glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S,
GL.GL_CLAMP_TO_EDGE); // prevent repeating the texture
getGL().glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T,
GL.GL_CLAMP_TO_EDGE); // prevent repeating the texture
}
@Override
public void setTextureNearest() {
getGL().glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER,
GL.GL_NEAREST);
getGL().glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER,
GL.GL_NEAREST);
}
/**
*
* @return GL instance
*/
protected GL getGL() {
return jogl.getGL();
}
/**
* set GL instance
*
* @param gLDrawable
* GL drawable
*/
public final void setGL(GLAutoDrawable gLDrawable) {
jogl.setGL(gLDrawable);
}
@Override
protected void disableStencilLines() {
getGL().glDisable(GL.GL_STENCIL_TEST);
waitForDisableStencilLines = false;
}
@Override
protected void setGIFEncoder(Object gifEncoder) {
this.gifEncoder = (FrameCollector) gifEncoder;
}
@Override
protected void initExportImageEquirectangularTiles() {
if (equirectangularTilesLeft == null) {
equirectangularTilesLeft = new BufferedImage[EXPORT_IMAGE_EQUIRECTANGULAR_LONGITUDE_STEPS];
equirectangularTilesRight = new BufferedImage[EXPORT_IMAGE_EQUIRECTANGULAR_LONGITUDE_STEPS];
}
}
@Override
protected void setExportImageEquirectangularTileLeft(int i) {
setExportImage();
equirectangularTilesLeft[i] = bi;
}
@Override
protected void setExportImageEquirectangularTileRight(int i) {
setExportImage();
equirectangularTilesRight[i] = bi;
}
private void setRGBFromTile(int i, int x, int y, int xTile, int yTile) {
bi.setRGB(x, y, equirectangularTilesLeft[i].getRGB(xTile, yTile));
bi.setRGB(x, y + EXPORT_IMAGE_EQUIRECTANGULAR_HEIGHT,
equirectangularTilesRight[i].getRGB(xTile, yTile));
}
private void setWhite(int x, int y) {
bi.setRGB(x, y, INT_RGB_WHITE);
bi.setRGB(x, y + EXPORT_IMAGE_EQUIRECTANGULAR_HEIGHT, INT_RGB_WHITE);
}
@Override
protected void setExportImageEquirectangularFromTiles() {
bi = new BufferedImage(EXPORT_IMAGE_EQUIRECTANGULAR_WIDTH,
EXPORT_IMAGE_EQUIRECTANGULAR_HEIGHT * 2,
BufferedImage.TYPE_INT_RGB);
int shiftY = (EXPORT_IMAGE_EQUIRECTANGULAR_HEIGHT
- EXPORT_IMAGE_EQUIRECTANGULAR_HEIGHT_ELEMENT) / 2;
int shiftAlpha = EXPORT_IMAGE_EQUIRECTANGULAR_HEIGHT / 2;
for (int i = 0; i < EXPORT_IMAGE_EQUIRECTANGULAR_LONGITUDE_STEPS; i++) {
int shiftX = i * EXPORT_IMAGE_EQUIRECTANGULAR_WIDTH_ELEMENT;
for (int x = 0; x < EXPORT_IMAGE_EQUIRECTANGULAR_WIDTH_ELEMENT; x++) {
// top white
for (int y = 0; y < shiftY; y++) {
setWhite(x + shiftX, y);
}
// first line will be missed by alpha
setRGBFromTile(i, x + shiftX, shiftY, x, 0);
// middle line
setRGBFromTile(i, x + shiftX, shiftAlpha, x,
EXPORT_IMAGE_EQUIRECTANGULAR_HEIGHT_ELEMENT / 2);
// angle - tangent match
for (int yAlpha = 1; yAlpha < EXPORT_IMAGE_EQUIRECTANGULAR_HEIGHT_ELEMENT
/ 2; yAlpha++) {
double alpha = ((double) (2 * yAlpha
* EXPORT_IMAGE_EQUIRECTANGULAR_LATITUTDE_MAX))
/ EXPORT_IMAGE_EQUIRECTANGULAR_HEIGHT_ELEMENT;
int y = (int) (EXPORT_IMAGE_EQUIRECTANGULAR_HEIGHT_ELEMENT
* Math.tan(alpha * Math.PI / 180)
/ (2 * EXPORT_IMAGE_EQUIRECTANGULAR_LATITUTDE_MAX_TAN));
setRGBFromTile(i, x + shiftX, shiftAlpha + yAlpha, x,
EXPORT_IMAGE_EQUIRECTANGULAR_HEIGHT_ELEMENT / 2
+ y);
setRGBFromTile(i, x + shiftX, shiftAlpha - yAlpha, x,
EXPORT_IMAGE_EQUIRECTANGULAR_HEIGHT_ELEMENT / 2
- y);
}
// bottom white
for (int y = EXPORT_IMAGE_EQUIRECTANGULAR_HEIGHT_ELEMENT
+ shiftY; y < EXPORT_IMAGE_EQUIRECTANGULAR_HEIGHT; y++) {
setWhite(x + shiftX, y);
}
}
}
}
/**
* creates an export image (and store it in BufferedImage bi)
*/
protected final void setExportImage() {
bi = null;
try {
// will use screen buffer or offscreen buffer, depending
// which is currently selected
int width = right - left;
int height = top - bottom;
FloatBuffer buffer = FloatBuffer.allocate(3 * width * height);
getGL().glReadPixels(0, 0, width, height, GL.GL_RGB, GL.GL_FLOAT,
buffer);
float[] pixels = buffer.array();
bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
int i = 0;
for (int y = height - 1; y >= 0; y--) {
for (int x = 0; x < width; x++) {
int r = (int) (pixels[i] * 255);
int g = (int) (pixels[i + 1] * 255);
int b = (int) (pixels[i + 2] * 255);
bi.setRGB(x, y, ((r << 16) | (g << 8) | b));
i += 3;
}
}
bi.flush();
} catch (Exception e) {
Log.error("setExportImage: " + e.getMessage());
}
}
@Override
public final GBufferedImage getExportImage() {
return new GBufferedImageD(bi);
}
private static final int INT_RGB_WHITE = ((255 << 16) | (255 << 8) | 255);
@Override
final public void enableTextures2D() {
getRendererImpl().glEnable(GL.GL_TEXTURE_2D);
}
@Override
final public void disableTextures2D() {
getRendererImpl().glDisable(GL.GL_TEXTURE_2D);
}
@Override
public void updateProjectionObliqueValues() {
if (getType() == RendererType.GL2) {
updateOrthoValues();
}
super.updateProjectionObliqueValues();
}
}