diff options
author | Sven Gothel <[email protected]> | 2023-03-10 03:11:24 +0100 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2023-03-10 03:11:24 +0100 |
commit | 3131eaaf5272ca3f0011e334eb08c6ba68702a6c (patch) | |
tree | 73f2e61e9986d2b72cad12ddc2b6cd5193e53028 /src/graphui/classes/com/jogamp/graph/ui/gl/Scene.java | |
parent | 52e4bc6cd4ace3fdf7ccaf790566500670709a44 (diff) |
GraphUI: Promote API to JOGL via graphui.jar or within any jogl-all*.jar (WIP)
Root package is 'com.jogamp.graph.ui.gl', i.e. a sub-package of Graph denoting UI and OpenGL usage.
Implementation will stay small, hence relative files size costs are minimal.
Source and build is in parallel to nativewindow, jogl and newt
and has a dependency to all of them.
The NEWT dependencies are merely the input listener ..
Diffstat (limited to 'src/graphui/classes/com/jogamp/graph/ui/gl/Scene.java')
-rw-r--r-- | src/graphui/classes/com/jogamp/graph/ui/gl/Scene.java | 636 |
1 files changed, 636 insertions, 0 deletions
diff --git a/src/graphui/classes/com/jogamp/graph/ui/gl/Scene.java b/src/graphui/classes/com/jogamp/graph/ui/gl/Scene.java new file mode 100644 index 000000000..a87dd9a23 --- /dev/null +++ b/src/graphui/classes/com/jogamp/graph/ui/gl/Scene.java @@ -0,0 +1,636 @@ +/** + * Copyright 2010-2023 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``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 JogAmp Community 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. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.graph.ui.gl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; + +import com.jogamp.opengl.FPSCounter; +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL2ES2; +import com.jogamp.opengl.GLAutoDrawable; +import com.jogamp.opengl.GLCapabilitiesImmutable; +import com.jogamp.opengl.GLEventListener; +import com.jogamp.opengl.GLRunnable; +import com.jogamp.opengl.fixedfunc.GLMatrixFunc; +import com.jogamp.graph.curve.Region; +import com.jogamp.graph.curve.opengl.RegionRenderer; +import com.jogamp.graph.curve.opengl.RenderState; +import com.jogamp.graph.geom.Vertex; +import com.jogamp.newt.event.GestureHandler; +import com.jogamp.newt.event.InputEvent; +import com.jogamp.newt.event.MouseEvent; +import com.jogamp.newt.event.MouseListener; +import com.jogamp.newt.event.PinchToZoomGesture; +import com.jogamp.newt.event.GestureHandler.GestureEvent; +import com.jogamp.newt.opengl.GLWindow; +import com.jogamp.opengl.math.FloatUtil; +import com.jogamp.opengl.math.Ray; +import com.jogamp.opengl.math.geom.AABBox; +import com.jogamp.opengl.util.PMVMatrix; + +/** + * GraphUI Scene + * <p> + * GraphUI is GPU based and resolution independent. + * </p> + * <p> + * GraphUI is intended to become an immediate- and retained-mode API. + * </p> + * @see Shape + */ +public class Scene implements GLEventListener{ + private final ArrayList<Shape> shapes = new ArrayList<Shape>(); + + private final float sceneDist, zNear, zFar; + + private RegionRenderer renderer; + + private final int[] sampleCount = new int[1]; + + /** Describing the bounding box in model-coordinates of the near-plane parallel at distance one. */ + private final AABBox nearPlane1Box = new AABBox(); + private final int[] viewport = new int[] { 0, 0, 0, 0 }; + private final float[] sceneScale = new float[3]; + private final float[] scenePlaneOrigin = new float[3]; + + + private volatile Shape activeShape = null; + + private SBCMouseListener sbcMouseListener = null; + private SBCGestureListener sbcGestureListener = null; + private PinchToZoomGesture pinchToZoomGesture = null; + + private GLAutoDrawable cDrawable = null; + + public Scene(final float sceneDist, final float zNear, final float zFar) { + this(null, sceneDist, zNear, zFar); + } + + public Scene(final RegionRenderer renderer, final float sceneDist, final float zNear, final float zFar) { + this.renderer = renderer; + this.sceneDist = sceneDist; + this.zFar = zFar; + this.zNear = zNear; + this.sampleCount[0] = 4; + } + + /** Returns the associated RegionRenderer */ + public RegionRenderer getRenderer() { return renderer; } + /** Sets the associated RegionRenderer, may set to null to avoid its destruction when {@link #dispose(GLAutoDrawable)} this instance. */ + public void setRenderer(final RegionRenderer renderer) { + this.renderer = renderer; + } + /** Returns the associated RegionRenderer's RenderState, may be null. */ + public RenderState getRenderState() { + if( null != renderer ) { + return renderer.getRenderState(); + } + return null; + } + public final Vertex.Factory<? extends Vertex> getVertexFactory() { + if( null != renderer ) { + return renderer.getRenderState().getVertexFactory(); + } + return null; + } + + public void attachInputListenerTo(final GLWindow window) { + if(null == sbcMouseListener) { + sbcMouseListener = new SBCMouseListener(); + window.addMouseListener(sbcMouseListener); + sbcGestureListener = new SBCGestureListener(); + window.addGestureListener(sbcGestureListener); + pinchToZoomGesture = new PinchToZoomGesture(window.getNativeSurface(), false); + window.addGestureHandler(pinchToZoomGesture); + } + } + + public void detachInputListenerFrom(final GLWindow window) { + if(null != sbcMouseListener) { + window.removeMouseListener(sbcMouseListener); + sbcMouseListener = null; + window.removeGestureListener(sbcGestureListener); + sbcGestureListener = null; + window.removeGestureHandler(pinchToZoomGesture); + pinchToZoomGesture = null; + } + } + + public ArrayList<Shape> getShapes() { + return shapes; + } + public void addShape(final Shape b) { + shapes.add(b); + } + public void removeShape(final Shape b) { + shapes.remove(b); + } + public final Shape getShapeByIdx(final int id) { + if( 0 > id ) { + return null; + } + return shapes.get(id); + } + public Shape getShapeByName(final int name) { + for(final Shape b : shapes) { + if(b.getName() == name ) { + return b; + } + } + return null; + } + + public int getSampleCount() { return sampleCount[0]; } + public int setSampleCount(final int v) { + sampleCount[0] = Math.min(8, Math.max(v, 1)); // clip + markAllShapesDirty(); + return sampleCount[0]; + } + + public void setAllShapesQuality(final int q) { + for(int i=0; i<shapes.size(); i++) { + shapes.get(i).setQuality(q); + } + } + public void setAllShapesSharpness(final float sharpness) { + for(int i=0; i<shapes.size(); i++) { + shapes.get(i).setSharpness(sharpness); + } + } + public void markAllShapesDirty() { + for(int i=0; i<shapes.size(); i++) { + shapes.get(i).markShapeDirty(); + } + } + + public final float[] getSceneScale() { return sceneScale; } + public final float[] getScenePlaneOrigin() { return scenePlaneOrigin; } + + @Override + public void init(final GLAutoDrawable drawable) { + System.err.println("SceneUIController: init"); + cDrawable = drawable; + if( null != renderer ) { + renderer.init(drawable.getGL().getGL2ES2()); + } + } + + private static Comparator<Shape> shapeZAscComparator = new Comparator<Shape>() { + @Override + public int compare(final Shape s1, final Shape s2) { + final float s1Z = s1.getBounds().getMinZ()+s1.getTranslate()[2]; + final float s2Z = s2.getBounds().getMinZ()+s2.getTranslate()[2]; + if( FloatUtil.isEqual(s1Z, s2Z, FloatUtil.EPSILON) ) { + return 0; + } else if( s1Z < s2Z ){ + return -1; + } else { + return 1; + } + } }; + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public void display(final GLAutoDrawable drawable) { + final GL2ES2 gl = drawable.getGL().getGL2ES2(); + + gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); + + final PMVMatrix pmv = renderer.getMatrix(); + pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); + + final Object[] shapesS = shapes.toArray(); + Arrays.sort(shapesS, (Comparator)shapeZAscComparator); + + renderer.enable(gl, true); + + //final int shapeCount = shapes.size(); + final int shapeCount = shapesS.length; + for(int i=0; i<shapeCount; i++) { + // final UIShape uiShape = shapes.get(i); + final Shape uiShape = (Shape)shapesS[i]; + // System.err.println("Id "+i+": "+uiShape); + if( uiShape.isEnabled() ) { + uiShape.validate(gl, renderer); + pmv.glPushMatrix(); + uiShape.setTransform(pmv); + uiShape.drawShape(gl, renderer, sampleCount); + pmv.glPopMatrix(); + } + } + + renderer.enable(gl, false); + } + + public void pickShape(final int glWinX, final int glWinY, final float[] objPos, final Shape[] shape, final Runnable runnable) { + if( null == cDrawable ) { + return; + } + cDrawable.invoke(false, new GLRunnable() { + @Override + public boolean run(final GLAutoDrawable drawable) { + shape[0] = pickShapeImpl(glWinX, glWinY, objPos); + if( null != shape[0] ) { + runnable.run(); + } + return true; + } } ); + } + @SuppressWarnings({ "unchecked", "rawtypes" }) + private Shape pickShapeImpl(final int glWinX, final int glWinY, final float[] objPos) { + final float winZ0 = 0f; + final float winZ1 = 0.3f; + /** + final FloatBuffer winZRB = Buffers.newDirectFloatBuffer(1); + gl.glReadPixels( x, y, 1, 1, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_FLOAT, winZRB); + winZ1 = winZRB.get(0); // dir + */ + final PMVMatrix pmv = renderer.getMatrix(); + pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); + + final Ray ray = new Ray(); + + final Object[] shapesS = shapes.toArray(); + Arrays.sort(shapesS, (Comparator)shapeZAscComparator); + + for(int i=shapesS.length-1; i>=0; i--) { + final Shape uiShape = (Shape)shapesS[i]; + + if( uiShape.isEnabled() ) { + pmv.glPushMatrix(); + uiShape.setTransform(pmv); + final boolean ok = pmv.gluUnProjectRay(glWinX, glWinY, winZ0, winZ1, viewport, 0, ray); + pmv.glPopMatrix(); + if( ok ) { + final AABBox sbox = uiShape.getBounds(); + if( sbox.intersectsRay(ray) ) { + // System.err.printf("Pick.0: shape %d, [%d, %d, %f/%f] -> %s%n", i, glWinX, glWinY, winZ0, winZ1, ray); + if( null == sbox.getRayIntersection(objPos, ray, FloatUtil.EPSILON, true, dpyTmp1V3, dpyTmp2V3, dpyTmp3V3) ) { + throw new InternalError("Ray "+ray+", box "+sbox); + } + // System.err.printf("Pick.1: shape %d @ [%f, %f, %f], within %s%n", i, objPos[0], objPos[1], objPos[2], uiShape.getBounds()); + return uiShape; + } + } + } + } + return null; + } + private final float[] dpyTmp1V3 = new float[3]; + private final float[] dpyTmp2V3 = new float[3]; + private final float[] dpyTmp3V3 = new float[3]; + + /** + * Calling {@link Shape#winToObjCoord(RegionRenderer, int, int, float[])}, retrieving its object position. + * @param activeShape + * @param glWinX in GL window coordinates, origin bottom-left + * @param glWinY in GL window coordinates, origin bottom-left + * @param objPos resulting object position + * @param runnable action + */ + public void windowToShapeCoords(final Shape activeShape, final int glWinX, final int glWinY, final float[] objPos, final Runnable runnable) { + if( null == cDrawable || null == activeShape ) { + return; + } + cDrawable.invoke(false, new GLRunnable() { + @Override + public boolean run(final GLAutoDrawable drawable) { + final boolean ok; + { + final PMVMatrix pmv = renderer.getMatrix(); + pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); + pmv.glPushMatrix(); + activeShape.setTransform(pmv); + ok = activeShape.winToObjCoord(renderer, glWinX, glWinY, objPos); + pmv.glPopMatrix(); + } + if( ok ) { + runnable.run(); + } + return true; + } } ); + } + + /** + * Disposes all {@link #addShape(Shape) added} {@link Shape}s. + * <p> + * Implementation also issues {@link RegionRenderer#destroy(GL2ES2)} if set + * and {@link #detachInputListenerFrom(GLWindow)} in case the drawable is of type {@link GLWindow}. + * </p> + * <p> + * {@inheritDoc} + * </p> + */ + @Override + public void dispose(final GLAutoDrawable drawable) { + System.err.println("SceneUIController: dispose"); + if( drawable instanceof GLWindow ) { + final GLWindow glw = (GLWindow) drawable; + detachInputListenerFrom(glw); + } + final GL2ES2 gl = drawable.getGL().getGL2ES2(); + for(int i=0; i<shapes.size(); i++) { + shapes.get(i).destroy(gl, renderer); + } + shapes.clear(); + cDrawable = null; + if( null != renderer ) { + renderer.destroy(gl); + } + } + + public static void mapWin2ObjectCoords(final PMVMatrix pmv, final int[] view, + final float zNear, final float zFar, + final float orthoX, final float orthoY, final float orthoDist, + final float[] winZ, final float[] objPos) { + winZ[0] = FloatUtil.getOrthoWinZ(orthoDist, zNear, zFar); + pmv.gluUnProject(orthoX, orthoY, winZ[0], view, 0, objPos, 0); + } + + @Override + public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) { + viewport[0] = x; + viewport[1] = y; + viewport[2] = width; + viewport[3] = height; + + final PMVMatrix pmv = renderer.getMatrix(); + renderer.reshapePerspective(45.0f, width, height, zNear, zFar); + pmv.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); + pmv.glLoadIdentity(); + + System.err.printf("Reshape: zNear %f, zFar %f%n", zNear, zFar); + System.err.printf("Reshape: Frustum: %s%n", pmv.glGetFrustum()); + { + final float orthoDist = 1f; + final float[] obj00Coord = new float[3]; + final float[] obj11Coord = new float[3]; + final float[] winZ = new float[1]; + + mapWin2ObjectCoords(pmv, viewport, zNear, zFar, 0f, 0f, orthoDist, winZ, obj00Coord); + System.err.printf("Reshape: mapped.00: [%f, %f, %f], winZ %f -> [%f, %f, %f]%n", 0f, 0f, orthoDist, winZ[0], obj00Coord[0], obj00Coord[1], obj00Coord[2]); + + mapWin2ObjectCoords(pmv, viewport, zNear, zFar, width, height, orthoDist, winZ, obj11Coord); + System.err.printf("Reshape: mapped.11: [%f, %f, %f], winZ %f -> [%f, %f, %f]%n", (float)width, (float)height, orthoDist, winZ[0], obj11Coord[0], obj11Coord[1], obj11Coord[2]); + + nearPlane1Box.setSize( obj00Coord[0], // lx + obj00Coord[1], // ly + obj00Coord[2], // lz + obj11Coord[0], // hx + obj11Coord[1], // hy + obj11Coord[2] );// hz + System.err.printf("Reshape: dist1Box: %s%n", nearPlane1Box); + } + scenePlaneOrigin[0] = nearPlane1Box.getMinX() * sceneDist; + scenePlaneOrigin[1] = nearPlane1Box.getMinY() * sceneDist; + scenePlaneOrigin[2] = nearPlane1Box.getMinZ() * sceneDist; + sceneScale[0] = ( nearPlane1Box.getWidth() * sceneDist ) / width; + sceneScale[1] = ( nearPlane1Box.getHeight() * sceneDist ) / height; + sceneScale[2] = 1f; + System.err.printf("Scene Origin [%f, %f, %f]%n", scenePlaneOrigin[0], scenePlaneOrigin[1], scenePlaneOrigin[2]); + System.err.printf("Scene Scale %f * [%f x %f] / [%d x %d] = [%f, %f, %f]%n", + sceneDist, nearPlane1Box.getWidth(), nearPlane1Box.getHeight(), + width, height, + sceneScale[0], sceneScale[1], sceneScale[2]); + + pmv.glTranslatef(scenePlaneOrigin[0], scenePlaneOrigin[1], scenePlaneOrigin[2]); + pmv.glScalef(sceneScale[0], sceneScale[1], sceneScale[2]); + } + + public final Shape getActiveShape() { + return activeShape; + } + + public void release() { + setActiveShape(null); + } + private void setActiveShape(final Shape shape) { + activeShape = shape; + } + + private final class SBCGestureListener implements GestureHandler.GestureListener { + @Override + public void gestureDetected(final GestureEvent gh) { + if( null != activeShape ) { + // gesture .. delegate to active shape! + final InputEvent orig = gh.getTrigger(); + if( orig instanceof MouseEvent ) { + final MouseEvent e = (MouseEvent) orig; + // flip to GL window coordinates + final int glWinX = e.getX(); + final int glWinY = viewport[3] - e.getY() - 1; + final float[] objPos = new float[3]; + final Shape shape = activeShape; + windowToShapeCoords(shape, glWinX, glWinY, objPos, new Runnable() { + @Override + public void run() { + shape.dispatchGestureEvent(gh, glWinX, glWinY, objPos); + } } ); + } + } + } + } + + /** + * Dispatch mouse event, either directly sending to activeShape or picking one + * @param e original Newt {@link MouseEvent} + * @param glWinX in GL window coordinates, origin bottom-left + * @param glWinY in GL window coordinates, origin bottom-left + */ + final void dispatchMouseEvent(final MouseEvent e, final int glWinX, final int glWinY) { + if( null == activeShape ) { + dispatchMouseEventPickShape(e, glWinX, glWinY, true); + } else { + dispatchMouseEventForShape(activeShape, e, glWinX, glWinY); + } + } + /** + * Pick the shape using the event coordinates + * @param e original Newt {@link MouseEvent} + * @param glWinX in GL window coordinates, origin bottom-left + * @param glWinY in GL window coordinates, origin bottom-left + * @param setActive + */ + final void dispatchMouseEventPickShape(final MouseEvent e, final int glWinX, final int glWinY, final boolean setActive) { + final float[] objPos = new float[3]; + final Shape[] shape = { null }; + pickShape(glWinX, glWinY, objPos, shape, new Runnable() { + @Override + public void run() { + if( setActive ) { + setActiveShape(shape[0]); + } + shape[0].dispatchMouseEvent(e, glWinX, glWinY, objPos); + } } ); + } + /** + * Dispatch event to shape + * @param shape target active shape of event + * @param e original Newt {@link MouseEvent} + * @param glWinX in GL window coordinates, origin bottom-left + * @param glWinY in GL window coordinates, origin bottom-left + */ + final void dispatchMouseEventForShape(final Shape shape, final MouseEvent e, final int glWinX, final int glWinY) { + final float[] objPos = new float[3]; + windowToShapeCoords(shape, glWinX, glWinY, objPos, new Runnable() { + @Override + public void run() { + shape.dispatchMouseEvent(e, glWinX, glWinY, objPos); + } } ); + } + + private class SBCMouseListener implements MouseListener { + int lx=-1, ly=-1, lId=-1; + + void clear() { + lx = -1; ly = -1; lId = -1; + } + + @Override + public void mousePressed(final MouseEvent e) { + if( -1 == lId || e.getPointerId(0) == lId ) { + lx = e.getX(); + ly = e.getY(); + lId = e.getPointerId(0); + } + // flip to GL window coordinates, origin bottom-left + final int glWinX = e.getX(); + final int glWinY = viewport[3] - e.getY() - 1; + dispatchMouseEvent(e, glWinX, glWinY); + } + + @Override + public void mouseReleased(final MouseEvent e) { + // flip to GL window coordinates, origin bottom-left + final int glWinX = e.getX(); + final int glWinY = viewport[3] - e.getY() - 1; + dispatchMouseEvent(e, glWinX, glWinY); + if( 1 == e.getPointerCount() ) { + // Release active shape: last pointer has been lifted! + release(); + clear(); + } + } + + @Override + public void mouseClicked(final MouseEvent e) { + // flip to GL window coordinates + final int glWinX = e.getX(); + final int glWinY = viewport[3] - e.getY() - 1; + // activeId should have been released by mouseRelease() already! + dispatchMouseEventPickShape(e, glWinX, glWinY, false); + // Release active shape: last pointer has been lifted! + release(); + clear(); + } + + @Override + public void mouseDragged(final MouseEvent e) { + // drag activeShape, if no gesture-activity, only on 1st pointer + if( null != activeShape && !pinchToZoomGesture.isWithinGesture() && e.getPointerId(0) == lId ) { + lx = e.getX(); + ly = e.getY(); + + // dragged .. delegate to active shape! + // flip to GL window coordinates, origin bottom-left + final int glWinX = lx; + final int glWinY = viewport[3] - ly - 1; + dispatchMouseEventForShape(activeShape, e, glWinX, glWinY); + } + } + + @Override + public void mouseWheelMoved(final MouseEvent e) { + // flip to GL window coordinates + final int glWinX = lx; + final int glWinY = viewport[3] - ly - 1; + dispatchMouseEventPickShape(e, glWinX, glWinY, true); + } + + @Override + public void mouseMoved(final MouseEvent e) { + if( -1 == lId || e.getPointerId(0) == lId ) { + lx = e.getX(); + ly = e.getY(); + lId = e.getPointerId(0); + } + } + @Override + public void mouseEntered(final MouseEvent e) { } + @Override + public void mouseExited(final MouseEvent e) { + release(); + clear(); + } + } + + /** + * Return a formatted status string containing avg fps and avg frame duration. + * @param glad GLAutoDrawable instance for FPSCounter, its chosen GLCapabilities and its GL's swap-interval + * @param renderModes render modes for {@link Region#getRenderModeString(int)} + * @param quality the Graph-Curve quality setting + * @param dpi the monitor's DPI (vertical preferred) + * @return formatted status string + */ + public String getStatusText(final GLAutoDrawable glad, final int renderModes, final int quality, final float dpi) { + final FPSCounter fpsCounter = glad.getAnimator(); + final float lfps, tfps, td; + if( null != fpsCounter ) { + lfps = fpsCounter.getLastFPS(); + tfps = fpsCounter.getTotalFPS(); + td = (float)fpsCounter.getLastFPSPeriod() / (float)fpsCounter.getUpdateFPSFrames(); + } else { + lfps = 0f; + tfps = 0f; + td = 0f; + } + final String modeS = Region.getRenderModeString(renderModes); + final GLCapabilitiesImmutable caps = glad.getChosenGLCapabilities(); + return String.format("%03.1f/%03.1f fps, %.1f ms/f, v-sync %d, dpi %.1f, %s-samples %d, q %d, msaa %d, blend %b, alpha %d", + lfps, tfps, td, glad.getGL().getSwapInterval(), dpi, modeS, getSampleCount(), quality, + caps.getNumSamples(), + getRenderState().isHintMaskSet(RenderState.BITHINT_BLENDING_ENABLED), + caps.getAlphaBits()); + } + + /** + * Return a formatted status string containing avg fps and avg frame duration. + * @param fpsCounter the counter, must not be null + * @return formatted status string + */ + public static String getStatusText(final FPSCounter fpsCounter) { + final float lfps = fpsCounter.getLastFPS(); + final float tfps = fpsCounter.getTotalFPS(); + final float td = (float)fpsCounter.getLastFPSPeriod() / (float)fpsCounter.getUpdateFPSFrames(); + return String.format("%03.1f/%03.1f fps, %.1f ms/f", lfps, tfps, td); + } + +} |