diff --git a/src/itdelatrisu/opsu/Options.java b/src/itdelatrisu/opsu/Options.java index 4f37e9b4..bdea4d04 100644 --- a/src/itdelatrisu/opsu/Options.java +++ b/src/itdelatrisu/opsu/Options.java @@ -67,6 +67,7 @@ import yugecin.opsudance.events.ResolutionOrSkinChangedEvent; import yugecin.opsudance.movers.factories.ExgonMoverFactory; import yugecin.opsudance.movers.factories.QuadraticBezierMoverFactory; import yugecin.opsudance.movers.slidermovers.DefaultSliderMoverController; +import yugecin.opsudance.utils.CachedVariable; /** * Handles all user options. @@ -382,19 +383,38 @@ public class Options { @Override public void read(String s) { skinName = s; } }, - TARGET_FPS ("Frame Limiter", "FrameSync", "Higher values may cause high CPU usage.") { + TARGET_UPS ("target UPS", "targetUPS", "Higher values result in less input lag and smoother cursor trail, but may cause high CPU usage.", 480, 20, 1000) { @Override public String getValueString() { - return String.format((getTargetFPS() == 60) ? "%dfps (vsync)" : "%dfps", getTargetFPS()); + return String.format("%dups", val); } @Override - public Object[] getListItems() { - String[] list = new String[targetFPS.length]; - for (int i = 0; i < targetFPS.length; i++) { - list[i] = String.format(targetFPS[i] == 60 ? "%dfps (vsync)" : "%dfps", targetFPS[i]); + public void setValue(int value) { + super.setValue(value); + displayContainer.setUPS(value); + } + }, + TARGET_FPS ("FPS limit", "FPSlimit", "Higher values may cause high CPU usage. A value higher than the UPS has no effect.") { + @Override + public String getValueString() { + return String.format("%dfps", getTargetFPS()); + } + + private CachedVariable $_getListItems = new CachedVariable<>(new CachedVariable.Getter() { + @Override + public Object[] get() { + String[] list = new String[targetFPS.length]; + for (int i = 0; i < targetFPS.length; i++) { + list[i] = String.format("%dfps", targetFPS[i]); + } + return list; } - return list; + }); + + @Override + public Object[] getListItems() { + return $_getListItems.get(); } @Override @@ -404,7 +424,9 @@ public class Options { } @Override - public String write() { return Integer.toString(targetFPS[targetFPSindex]); } + public String write() { + return Integer.toString(targetFPS[targetFPSindex]); + } @Override public void read(String s) { @@ -417,7 +439,13 @@ public class Options { } } }, - SHOW_FPS ("Show FPS Counter", "FpsCounter", "Show an FPS counter in the bottom-right hand corner.", true), + SHOW_FPS ("Show FPS Counters", "FpsCounter", "Show FPS and UPS counters in the bottom-right hand corner.", true), + USE_FPS_DELTAS ("Use deltas for FPS counters", "FpsCounterDeltas", "Show time between updates instead of updates per second.", false) { + @Override + public boolean showCondition() { + return SHOW_FPS.bool; + } + }, SHOW_UNICODE ("Prefer Non-English Metadata", "ShowUnicode", "Where available, song titles will be shown in their native language.", false) { @Override public void click() { @@ -1304,6 +1332,10 @@ public class Options { */ public static int getTargetFPS() { return targetFPS[targetFPSindex]; } + public static int getTargetUPS() { + return GameOption.TARGET_UPS.val; + } + /** * Sets the target frame rate to the next available option, and sends a * bar notification about the action. @@ -1503,6 +1535,8 @@ public class Options { */ public static boolean isFPSCounterEnabled() { return GameOption.SHOW_FPS.getBooleanValue(); } + public static boolean useDeltasForFPSCounter() { return GameOption.USE_FPS_DELTAS.getBooleanValue(); } + /** * Returns whether or not hit lighting effects are enabled. * @return true if enabled diff --git a/src/itdelatrisu/opsu/states/OptionsMenu.java b/src/itdelatrisu/opsu/states/OptionsMenu.java index d13d82f4..a43121cb 100644 --- a/src/itdelatrisu/opsu/states/OptionsMenu.java +++ b/src/itdelatrisu/opsu/states/OptionsMenu.java @@ -38,9 +38,10 @@ public class OptionsMenu { GameOption.SCREEN_RESOLUTION, GameOption.ALLOW_LARGER_RESOLUTIONS, GameOption.FULLSCREEN, - // TODO d: UPS option + GameOption.TARGET_UPS, GameOption.TARGET_FPS, GameOption.SHOW_FPS, + GameOption.USE_FPS_DELTAS, GameOption.SCREENSHOT_FORMAT, }), new OptionTab("SLIDER OPTIONS", new GameOption[]{ diff --git a/src/yugecin/opsudance/core/DisplayContainer.java b/src/yugecin/opsudance/core/DisplayContainer.java index 12043a67..7b34b3a7 100644 --- a/src/yugecin/opsudance/core/DisplayContainer.java +++ b/src/yugecin/opsudance/core/DisplayContainer.java @@ -153,12 +153,12 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen }); this.nativeDisplayMode = Display.getDisplayMode(); - setUPS(1000); - setFPS(60); targetBackgroundRenderInterval = 41; // ~24 fps lastFrame = getTime(); delta = 1; renderDelta = 1; + + Options.GameOption.displayContainer = this; } public void setUPS(int ups) { @@ -172,6 +172,9 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen } public void init(Class startingState) { + setUPS(Options.getTargetUPS()); + setFPS(Options.getTargetFPS()); + state = instanceContainer.provide(startingState); state.enter(); @@ -192,6 +195,8 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen mouseX = input.getMouseX(); mouseY = input.getMouseY(); + fpsState.update(); + state.update(); if (drawCursor) { cursor.setCursorPosition(delta, mouseX, mouseY); diff --git a/src/yugecin/opsudance/core/state/specialstates/FpsRenderState.java b/src/yugecin/opsudance/core/state/specialstates/FpsRenderState.java index 432c5b5c..c869de3a 100644 --- a/src/yugecin/opsudance/core/state/specialstates/FpsRenderState.java +++ b/src/yugecin/opsudance/core/state/specialstates/FpsRenderState.java @@ -17,42 +17,63 @@ */ package yugecin.opsudance.core.state.specialstates; +import itdelatrisu.opsu.Options; import itdelatrisu.opsu.ui.Fonts; import org.newdawn.slick.Color; import org.newdawn.slick.Graphics; import yugecin.opsudance.core.DisplayContainer; import yugecin.opsudance.core.events.EventListener; import yugecin.opsudance.events.ResolutionOrSkinChangedEvent; +import yugecin.opsudance.utils.FPSMeter; public class FpsRenderState implements EventListener { - private final DisplayContainer displayContainer; - private final static Color GREEN = new Color(171, 218, 25); private final static Color ORANGE = new Color(255, 204, 34); private final static Color DARKORANGE = new Color(255, 149, 24); + private final DisplayContainer displayContainer; + private final FPSMeter fpsMeter; + private final FPSMeter upsMeter; + private int x; private int y; private int singleHeight; public FpsRenderState(DisplayContainer displayContainer) { this.displayContainer = displayContainer; + fpsMeter = new FPSMeter(10); + upsMeter = new FPSMeter(10); displayContainer.eventBus.subscribe(ResolutionOrSkinChangedEvent.class, this); } + public void update() { + upsMeter.update(displayContainer.delta); + } + public void render(Graphics g) { + fpsMeter.update(displayContainer.renderDelta); + if (!Options.isFPSCounterEnabled()) { + return; + } int x = this.x; - int target = displayContainer.targetRenderInterval + (displayContainer.targetUpdateInterval % displayContainer.targetRenderInterval); - x = drawText(g, getColor(target, displayContainer.renderDelta), (1000 / displayContainer.renderDelta) + " fps", x, this.y); - drawText(g, getColor(displayContainer.targetUpdateInterval, displayContainer.delta), (1000 / displayContainer.delta) + " ups", x, this.y); + int fpsDeviation = displayContainer.delta % displayContainer.targetRenderInterval; + x = drawText(g, getColor((int) (Options.getTargetFPS() * 0.9f) - fpsDeviation, fpsMeter.getValue()), getText(fpsMeter.getValue(), "fps"), x, this.y); + drawText(g, getColor((int) (Options.getTargetUPS() * 0.9f), upsMeter.getValue()), getText(upsMeter.getValue(), "ups"), x, this.y); + } + + private String getText(int value, String unit) { + if (Options.useDeltasForFPSCounter()) { + return String.format("%.2fms", 1000f / value); + } + return value + " " + unit; } private Color getColor(int targetValue, int realValue) { - if (realValue <= targetValue) { + if (realValue >= targetValue) { return GREEN; } - if (realValue <= targetValue * 1.15f) { + if (realValue >= targetValue * 0.85f) { return ORANGE; } return DARKORANGE; @@ -64,7 +85,7 @@ public class FpsRenderState implements EventListener. + */ +package yugecin.opsudance.utils; + +public class FPSMeter { + + private final int targetTimeBetweenUpdates; + + private int[] measurements; + private int timeBetweenUpdates; + private int currentMeasureIndex; + + public FPSMeter(int measurements) { + targetTimeBetweenUpdates = 1000 / measurements; + this.measurements = new int[measurements]; + } + + public void update(int delta) { + timeBetweenUpdates += delta; + while (timeBetweenUpdates >= targetTimeBetweenUpdates) { + timeBetweenUpdates -= targetTimeBetweenUpdates; + measurements[currentMeasureIndex] = 0; + currentMeasureIndex = ++currentMeasureIndex % measurements.length; + } + for (int i = 0; i < measurements.length; i++) { + if (i == currentMeasureIndex) { + continue; + } + measurements[i]++; + } + } + + public int getValue() { + return measurements[currentMeasureIndex]; + } + +}