com.jme3.app.DetailedProfilerState Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jme3-core Show documentation
Show all versions of jme3-core Show documentation
jMonkeyEngine is a 3-D game engine for adventurous Java developers
/*
* Copyright (c) 2017-2021 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * 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.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 THE COPYRIGHT OWNER 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.
*/
package com.jme3.app;
import com.jme3.app.state.BaseAppState;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.input.*;
import com.jme3.input.controls.*;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.*;
import com.jme3.profile.AppStep;
import com.jme3.scene.*;
import com.jme3.scene.shape.Quad;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.*;
/**
* Created by Nehon on 25/01/2017.
*/
public class DetailedProfilerState extends BaseAppState {
private static final int PANEL_WIDTH = 400;
private static final int PADDING = 10;
private static final int LINE_HEIGHT = 12;
private static final int HEADER_HEIGHT = 100;
private static final float REFRESH_TIME = 1.0f;
private static final String TOGGLE_KEY = "Toggle_Detailed_Profiler";
private static final String CLICK_KEY = "Click_Detailed_Profiler";
private static final String INSIGNIFICANT = "Hide insignificant stat";
final private DetailedProfiler prof = new DetailedProfiler();
private float time = 0;
private BitmapFont font;
private BitmapFont bigFont;
final private Node ui = new Node("Stats ui");
final private Map lines = new HashMap<>();
private double totalTimeCpu;
private double totalTimeGpu;
private int maxLevel = 0;
private BitmapText frameTimeValue;
private BitmapText frameCpuTimeValue;
private BitmapText frameGpuTimeValue;
private BitmapText hideInsignificantField;
private BitmapText selectedField;
private double selectedValueCpu = 0;
private double selectedValueGpu = 0;
private boolean hideInsignificant = false;
private StatLineView rootLine;
private int height = 0;
final private DecimalFormat df = new DecimalFormat("##0.00", new DecimalFormatSymbols(Locale.US));
final private ColorRGBA dimmedWhite = ColorRGBA.White.mult(0.7f);
final private ColorRGBA dimmedGreen = ColorRGBA.Green.mult(0.7f);
final private ColorRGBA dimmedOrange = ColorRGBA.Orange.mult(0.7f);
final private ColorRGBA dimmedRed = ColorRGBA.Red.mult(0.7f);
final private ProfilerInputListener inputListener = new ProfilerInputListener();
public DetailedProfilerState() {
}
@Override
protected void initialize(Application app) {
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", new ColorRGBA(0, 0, 0, 0.5f));
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
Geometry darkenStats = new Geometry("StatsDarken", new Quad(PANEL_WIDTH, app.getCamera().getHeight()));
darkenStats.setMaterial(mat);
darkenStats.setLocalTranslation(0, -app.getCamera().getHeight(), -1);
ui.attachChild(darkenStats);
ui.setLocalTranslation(app.getCamera().getWidth() - PANEL_WIDTH, app.getCamera().getHeight(), 0);
font = app.getAssetManager().loadFont("Interface/Fonts/Console.fnt");
bigFont = app.getAssetManager().loadFont("Interface/Fonts/Default.fnt");
prof.setRenderer(app.getRenderer());
rootLine = new StatLineView("Frame");
rootLine.attachTo(ui);
BitmapText frameLabel = new BitmapText(bigFont);
frameLabel.setText("Total Frame Time: ");
ui.attachChild(frameLabel);
frameLabel.setLocalTranslation(new Vector3f(PANEL_WIDTH / 2 - bigFont.getLineWidth(frameLabel.getText()), -PADDING, 0));
BitmapText cpuLabel = new BitmapText(bigFont);
cpuLabel.setText("CPU");
ui.attachChild(cpuLabel);
cpuLabel.setLocalTranslation(PANEL_WIDTH / 4 - bigFont.getLineWidth(cpuLabel.getText()) / 2, -PADDING - 30, 0);
BitmapText gpuLabel = new BitmapText(bigFont);
gpuLabel.setText("GPU");
ui.attachChild(gpuLabel);
gpuLabel.setLocalTranslation(3 * PANEL_WIDTH / 4 - bigFont.getLineWidth(gpuLabel.getText()) / 2, -PADDING - 30, 0);
frameTimeValue = new BitmapText(bigFont);
frameCpuTimeValue = new BitmapText(bigFont);
frameGpuTimeValue = new BitmapText(bigFont);
selectedField = new BitmapText(font);
selectedField.setText("Selected: ");
selectedField.setLocalTranslation(PANEL_WIDTH / 2, -PADDING - 75, 0);
selectedField.setColor(ColorRGBA.Yellow);
ui.attachChild(frameTimeValue);
ui.attachChild(frameCpuTimeValue);
ui.attachChild(frameGpuTimeValue);
ui.attachChild(selectedField);
hideInsignificantField = new BitmapText(font);
hideInsignificantField.setText("O " + INSIGNIFICANT);
hideInsignificantField.setLocalTranslation(PADDING, -PADDING - 75, 0);
ui.attachChild(hideInsignificantField);
final InputManager inputManager = app.getInputManager();
if (inputManager != null) {
inputManager.addMapping(TOGGLE_KEY, new KeyTrigger(KeyInput.KEY_F6));
inputManager.addMapping(CLICK_KEY, new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
inputManager.addListener(inputListener, TOGGLE_KEY, CLICK_KEY);
}
}
@Override
protected void cleanup(Application app) {
ui.detachAllChildren();
InputManager manager = getApplication().getInputManager();
manager.deleteMapping(TOGGLE_KEY);
manager.deleteMapping(CLICK_KEY);
manager.removeListener(inputListener);
}
@Override
public void update(float tpf) {
time += tpf;
}
private void displayData(Map data) {
if (data == null || data.isEmpty()) {
return;
}
for (StatLineView statLine : lines.values()) {
statLine.reset();
statLine.removeFromParent();
}
rootLine.reset();
maxLevel = 0;
for (String path : data.keySet()) {
if (path.equals("EndFrame")) {
continue;
}
maxLevel = Math.max(maxLevel, path.split("/").length);
StatLineView line = getStatLineView(path);
DetailedProfiler.StatLine statLine = data.get(path);
line.updateValues(statLine.getAverageCpu(), statLine.getAverageGpu());
String parent = getParent(path);
while (parent != null) {
StatLineView parentView = getStatLineView(parent);
parentView.updateValues(statLine.getAverageCpu(), statLine.getAverageGpu());
parentView.children.add(line);
line.attachTo(ui);
line = parentView;
parent = getParent(parent);
}
rootLine.children.add(line);
line.attachTo(ui);
rootLine.updateValues(statLine.getAverageCpu(), statLine.getAverageGpu());
}
totalTimeCpu = rootLine.cpuValue;
totalTimeGpu = rootLine.gpuValue + data.get("EndFrame").getAverageGpu();
layout();
}
private void layout() {
height = 0;
selectedValueCpu = 0;
selectedValueGpu = 0;
rootLine.layout(0);
frameTimeValue.setText(df.format(getMsFromNs(prof.getAverageFrameTime())) + "ms");
frameTimeValue.setLocalTranslation(PANEL_WIDTH / 2, -PADDING, 0);
setColor(frameTimeValue, prof.getAverageFrameTime(), totalTimeCpu, false, false);
frameCpuTimeValue.setText(df.format(getMsFromNs(totalTimeCpu)) + "ms");
frameCpuTimeValue.setLocalTranslation(new Vector3f(PANEL_WIDTH / 4 - bigFont.getLineWidth(frameCpuTimeValue.getText()) / 2, -PADDING - 50, 0));
setColor(frameCpuTimeValue, totalTimeCpu, totalTimeCpu, false, false);
frameGpuTimeValue.setText(df.format(getMsFromNs(totalTimeGpu)) + "ms");
frameGpuTimeValue.setLocalTranslation(new Vector3f(3 * PANEL_WIDTH / 4 - bigFont.getLineWidth(frameGpuTimeValue.getText()) / 2, -PADDING - 50, 0));
setColor(frameGpuTimeValue, totalTimeGpu, totalTimeGpu, false, false);
selectedField.setText("Selected: " + df.format(getMsFromNs(selectedValueCpu)) + "ms / " + df.format(getMsFromNs(selectedValueGpu)) + "ms");
selectedField.setLocalTranslation(3 * PANEL_WIDTH / 4 - font.getLineWidth(selectedField.getText()) / 2, -PADDING - 75, 0);
}
private StatLineView getStatLineView(String path) {
StatLineView line = lines.get(path);
if (line == null) {
line = new StatLineView(getLeaf(path));
lines.put(path, line);
line.attachTo(ui);
}
return line;
}
private String getLeaf(String path) {
int idx = path.lastIndexOf("/");
return idx >= 0 ? path.substring(idx + 1) : path;
}
private String getParent(String path) {
int idx = path.lastIndexOf("/");
return idx >= 0 ? path.substring(0, idx) : null;
}
@Override
public void postRender() {
if (time > REFRESH_TIME) {
prof.appStep(AppStep.EndFrame);
Map data = prof.getStats();
displayData(data);
time = 0;
}
}
public Node getUiNode() {
return ui;
}
private double getMsFromNs(double time) {
return time / 1000000.0;
}
@Override
protected void onEnable() {
getApplication().setAppProfiler(prof);
((SimpleApplication) getApplication()).getGuiNode().attachChild(ui);
}
@Override
protected void onDisable() {
getApplication().setAppProfiler(null);
ui.removeFromParent();
}
public boolean setColor(BitmapText t, double value, double totalTime, boolean isParent, boolean expended) {
boolean dimmed = isParent && expended;
boolean insignificant = false;
if (value > 1000000000.0 / 30.0) {
t.setColor(dimmed ? dimmedRed : ColorRGBA.Red);
} else if (value > 1000000000.0 / 60.0) {
t.setColor(dimmed ? dimmedOrange : ColorRGBA.Orange);
} else if (value > totalTime / 3) {
t.setColor(dimmed ? dimmedGreen : ColorRGBA.Green);
} else if (value < 30000) {
t.setColor(ColorRGBA.DarkGray);
insignificant = true;
} else {
t.setColor(dimmed ? dimmedWhite : ColorRGBA.White);
}
return insignificant;
}
private void handleClick(Vector2f pos) {
Vector3f lp = hideInsignificantField.getWorldTranslation();
float width = font.getLineWidth(hideInsignificantField.getText());
if (pos.x > lp.x && pos.x < (lp.x + width)
&& pos.y < lp.y && pos.y > lp.y - LINE_HEIGHT) {
hideInsignificant = !hideInsignificant;
hideInsignificantField.setText((hideInsignificant ? "X " : "O ") + INSIGNIFICANT);
if (!hideInsignificant) {
rootLine.setExpended(true);
}
}
rootLine.onClick(pos);
for (StatLineView statLineView : lines.values()) {
statLineView.onClick(pos);
}
layout();
}
private class StatLineView {
BitmapText label;
BitmapText cpuText;
BitmapText gpuText;
BitmapText checkBox;
double cpuValue;
double gpuValue;
private boolean expended = true;
private boolean visible = true;
private boolean selected = false;
String text;
Set children = new LinkedHashSet<>();
public StatLineView(String label) {
this.text = label;
this.label = new BitmapText(font);
this.checkBox = new BitmapText(font);
this.checkBox.setText("O");
this.label.setText("- " + label);
this.cpuText = new BitmapText(font);
this.gpuText = new BitmapText(font);
}
public void onClick(Vector2f pos) {
if (!visible) {
return;
}
Vector3f lp = label.getWorldTranslation();
Vector3f cp = checkBox.getWorldTranslation();
if (pos.x > cp.x
&& pos.y < lp.y && pos.y > lp.y - LINE_HEIGHT) {
float width = font.getLineWidth(checkBox.getText());
if (pos.x >= cp.x && pos.x <= (cp.x + width)) {
selected = !selected;
if (selected) {
checkBox.setText("X");
} else {
checkBox.setText("O");
}
} else {
setExpended(!expended);
}
}
}
public void setExpended(boolean expended) {
this.expended = expended;
if (expended) {
label.setText("- " + text);
} else {
label.setText("+ " + text);
}
for (StatLineView child : children) {
child.setVisible(expended);
}
}
public void layout(int indent) {
boolean insignificant;
cpuText.setText(df.format(getMsFromNs(cpuValue)) + "ms /");
insignificant = setColor(cpuText, cpuValue, totalTimeCpu, !children.isEmpty(), expended);
gpuText.setText(" " + df.format(getMsFromNs(gpuValue)) + "ms");
insignificant &= setColor(gpuText, gpuValue, totalTimeGpu, !children.isEmpty(), expended);
if (insignificant && hideInsignificant) {
setVisible(false);
}
if (!visible) {
return;
}
if (selected) {
label.setColor(ColorRGBA.Yellow);
selectedValueCpu += cpuValue;
selectedValueGpu += gpuValue;
} else {
label.setColor(ColorRGBA.White);
}
int y = -(height * LINE_HEIGHT + HEADER_HEIGHT);
label.setLocalTranslation(PADDING + indent * PADDING, y, 0);
float gpuPos = PANEL_WIDTH - font.getLineWidth(gpuText.getText()) - PADDING * (maxLevel - indent + 1);
cpuText.setLocalTranslation(gpuPos - font.getLineWidth(cpuText.getText()), y, 0);
gpuText.setLocalTranslation(gpuPos, y, 0);
checkBox.setLocalTranslation(3, y, 0);
height++;
for (StatLineView child : children) {
child.layout(indent + 1);
}
}
public void updateValues(double cpu, double gpu) {
cpuValue += cpu;
gpuValue += gpu;
}
public void attachTo(Node node) {
node.attachChild(label);
node.attachChild(cpuText);
node.attachChild(gpuText);
node.attachChild(checkBox);
}
public void removeFromParent() {
label.removeFromParent();
cpuText.removeFromParent();
gpuText.removeFromParent();
checkBox.removeFromParent();
}
public void reset() {
children.clear();
cpuValue = 0;
gpuValue = 0;
}
public void setVisible(boolean visible) {
this.visible = visible;
label.setCullHint(visible ? Spatial.CullHint.Dynamic : Spatial.CullHint.Always);
cpuText.setCullHint(visible ? Spatial.CullHint.Dynamic : Spatial.CullHint.Always);
gpuText.setCullHint(visible ? Spatial.CullHint.Dynamic : Spatial.CullHint.Always);
checkBox.setCullHint(visible ? Spatial.CullHint.Dynamic : Spatial.CullHint.Always);
for (StatLineView child : children) {
child.setVisible(visible && expended);
}
}
@Override
public String toString() {
return label.getText() + " - " + df.format(getMsFromNs(cpuValue)) + "ms / " + df.format(getMsFromNs(gpuValue)) + "ms";
}
}
private class ProfilerInputListener implements ActionListener {
@Override
public void onAction(String name, boolean isPressed, float tpf) {
if (name.equals(TOGGLE_KEY) && isPressed) {
setEnabled(!isEnabled());
}
if (isEnabled() && name.equals(CLICK_KEY) && isPressed) {
handleClick(getApplication().getInputManager().getCursorPosition());
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy