package com.jme3.app; import com.jme3.profile.*; import com.jme3.renderer.Renderer; import com.jme3.renderer.ViewPort; import com.jme3.renderer.queue.RenderQueue; import java.util.*; /** * Created by Nehon on 25/01/2017. */ public class DetailedProfiler implements AppProfiler { private final static int MAX_FRAMES = 100; private Map<String, StatLine> data; private Map<String, StatLine> pool; private long startFrame; private static int currentFrame = 0; private String prevPath = null; private boolean frameEnded = false; private Renderer renderer; private boolean ongoingGpuProfiling = false; private String curAppPath = null; private String curVpPath = null; private String curSpPath = null; private VpStep lastVpStep = null; private StringBuilder path = new StringBuilder(256); private StringBuilder vpPath = new StringBuilder(256); private Deque<Integer> idsPool = new ArrayDeque<>(100); StatLine frameTime; @Override public void appStep(AppStep step) { curAppPath = step.name(); if (step == AppStep.BeginFrame) { if (data == null) { data = new LinkedHashMap<>(); pool = new HashMap<>(); frameTime = new StatLine(currentFrame); } if (frameTime.isActive()) { frameTime.setValueCpu(System.nanoTime() - frameTime.getValueCpu()); frameTime.closeFrame(); } frameTime.setNewFrameValueCpu(System.nanoTime()); frameEnded = false; for (StatLine statLine : data.values()) { for (Iterator<Integer> i = statLine.taskIds.iterator(); i.hasNext(); ) { int id = i.next(); if (renderer.isTaskResultAvailable(id)) { long val = renderer.getProfilingTime(id); statLine.setValueGpu(val); i.remove(); idsPool.push(id); } } } data.clear(); } if (data != null) { String path = getPath(step.name()); if (step == AppStep.EndFrame) { if (frameEnded) { return; } addStep(path, System.nanoTime()); StatLine end = data.get(path); end.setValueCpu(System.nanoTime() - startFrame); frameEnded = true; } else { addStep(path, System.nanoTime()); } } if (step == AppStep.EndFrame) { closeFrame(); } } private void closeFrame() { //close frame if (data != null) { prevPath = null; for (StatLine statLine : data.values()) { statLine.closeFrame(); } currentFrame++; } } @Override public void vpStep(VpStep step, ViewPort vp, RenderQueue.Bucket bucket) { if (data != null) { vpPath.setLength(0); vpPath.append(vp.getName()).append("/").append((bucket == null ? step.name() : bucket.name() + " Bucket")); path.setLength(0); if ((lastVpStep == VpStep.PostQueue || lastVpStep == VpStep.PostFrame) && bucket != null) { path.append(curAppPath).append("/").append(curVpPath).append(curSpPath).append("/").append(vpPath); curVpPath = vpPath.toString(); } else { if (bucket != null) { path.append(curAppPath).append("/").append(curVpPath).append("/").append(bucket.name() + " Bucket"); } else { path.append(curAppPath).append("/").append(vpPath); curVpPath = vpPath.toString(); } } lastVpStep = step; addStep(path.toString(), System.nanoTime()); } } @Override public void spStep(SpStep step, String... additionalInfo) { if (data != null) { curSpPath = getPath("", additionalInfo); path.setLength(0); path.append(curAppPath).append("/").append(curVpPath).append(curSpPath); addStep(path.toString(), System.nanoTime()); } } public Map<String, StatLine> getStats() { if (data != null) { return data;//new LinkedHashMap<>(data); } return null; } public double getAverageFrameTime() { return frameTime.getAverageCpu(); } private void addStep(String path, long value) { if (ongoingGpuProfiling && renderer != null) { renderer.stopProfiling(); ongoingGpuProfiling = false; } if (prevPath != null) { StatLine prevLine = data.get(prevPath); if (prevLine != null) { prevLine.setValueCpu(value - prevLine.getValueCpu()); } } StatLine line = pool.get(path); if (line == null) { line = new StatLine(currentFrame); pool.put(path, line); } data.put(path, line); line.setNewFrameValueCpu(value); if (renderer != null) { int id = getUnusedTaskId(); line.taskIds.add(id); renderer.startProfiling(id); } ongoingGpuProfiling = true; prevPath = path; } private String getPath(String step, String... subPath) { StringBuilder path = new StringBuilder(step); if (subPath != null) { for (String s : subPath) { path.append("/").append(s); } } return path.toString(); } public void setRenderer(Renderer renderer) { this.renderer = renderer; poolTaskIds(renderer); } private void poolTaskIds(Renderer renderer) { int[] ids = renderer.generateProfilingTasks(100); for (int id : ids) { idsPool.push(id); } } private int getUnusedTaskId() { if (idsPool.isEmpty()) { poolTaskIds(renderer); } return idsPool.pop(); } public static class StatLine { private long[] cpuTimes = new long[MAX_FRAMES]; private long[] gpuTimes = new long[MAX_FRAMES]; private int startCursor = 0; private int cpuCursor = 0; private int gpuCursor = 0; private long cpuSum = 0; private long gpuSum = 0; private long lastValue = 0; private int nbFramesCpu; private int nbFramesGpu; List<Integer> taskIds = new ArrayList<>(); private StatLine(int currentFrame) { startCursor = currentFrame % MAX_FRAMES; cpuCursor = startCursor; gpuCursor = startCursor; } private void setNewFrameValueCpu(long value) { int newCursor = currentFrame % MAX_FRAMES; if (nbFramesCpu == 0) { startCursor = newCursor; } cpuCursor = newCursor; lastValue = value; } private void setValueCpu(long val) { lastValue = val; } private long getValueCpu() { return lastValue; } private void closeFrame() { if (isActive()) { cpuSum -= cpuTimes[cpuCursor]; cpuTimes[cpuCursor] = lastValue; cpuSum += lastValue; nbFramesCpu++; } else { nbFramesCpu = 0; } } public void setValueGpu(long value) { gpuSum -= gpuTimes[gpuCursor]; gpuTimes[gpuCursor] = value; gpuSum += value; nbFramesGpu++; gpuCursor = (gpuCursor + 1) % MAX_FRAMES; } public boolean isActive() { return cpuCursor >= currentFrame % MAX_FRAMES - 1; } public double getAverageCpu() { if (nbFramesCpu == 0) { return 0; } return (double) cpuSum / (double) Math.min(nbFramesCpu, MAX_FRAMES); } public double getAverageGpu() { if (nbFramesGpu == 0) { return 0; } return (double) gpuSum / (double) Math.min(nbFramesGpu, MAX_FRAMES); } } }