c0db40f624
Пункт ноль плана: клиент теперь просит контекст 3.3 в режиме совместимости, а не тот, что выпадал по умолчанию. Зачем: дальний вид будет писаться шейдером GLSL 330, и контекст должен гарантированно быть не ниже 3.3. В профиле совместимости работают и шейдеры игры (#version 120, их 34 штуки), и наши 330-е. LWJGL 2.9.4 выше 3.3 контекст не заказывает - ограничение в его конструкторе, а не в драйвере. Драйвер при этом дает 4.6, и расширение GL_EXT_gpu_shader4, которое просит FXAA-шейдер игры, в этом контексте на месте. Замер кадров теперь пишется в лог каждую секунду: средний, медиана, 1% и 0.1% low. Одного среднего мало: клиент, который идет со 60 кадрами и дважды в секунду проваливается в 10, в среднем остается 60 и ощущается как slideshow. Медленные кадры - это то, что чувствует игрок, поэтому окно держит все времена кадров и говорит про хвост. Среднее и процентили видны и в HUD, так что их не надо вылавливать из скриншотов. Замер: 307 fps | med 2.9 ms | 1% 6.0 ms | 0.1% 18.1 ms.
112 lines
3.6 KiB
Java
112 lines
3.6 KiB
Java
package net.rustnotmc.client;
|
|
|
|
import java.util.Arrays;
|
|
import net.rustnotmc.api.client.FrameStats;
|
|
import net.rustnotmc.loader.Log;
|
|
|
|
/**
|
|
* Counts how fast the frames are going, and how often they do not.
|
|
*
|
|
* The average is what people quote and what hides the stutter. A client that
|
|
* runs at sixty frames a second and drops to ten twice a second still averages
|
|
* sixty, and feels like a slideshow. So the window keeps every frame time and
|
|
* reports the slow tail as well: the median, the one percent low and the tenth
|
|
* of a percent low.
|
|
*
|
|
* The frame time is measured between the frames, which is the whole point: it
|
|
* includes the waiting, so a client that is fast at drawing but slow at
|
|
* getting around to drawing again is caught by it.
|
|
*/
|
|
public final class RsxFrameStats {
|
|
|
|
/** A second of frames, with room to spare. */
|
|
private static final int MAX_FRAMES = 4096;
|
|
|
|
private static final long[] TIMES = new long[MAX_FRAMES];
|
|
|
|
private static int count;
|
|
private static long windowStart;
|
|
private static long lastFrameAt;
|
|
private static long totalFrames;
|
|
|
|
private static volatile FrameStats latest = FrameStats.EMPTY;
|
|
|
|
private RsxFrameStats() {
|
|
}
|
|
|
|
/**
|
|
* Called once per drawn frame.
|
|
*
|
|
* The first frame only starts the clock: there is nothing to compare it
|
|
* against yet.
|
|
*/
|
|
public static void frame() {
|
|
long now = System.nanoTime();
|
|
if (lastFrameAt != 0L) {
|
|
long delta = now - lastFrameAt;
|
|
// a frame longer than a second is a stall, a pause or a breakpoint,
|
|
// and putting it in the window would only say that the machine
|
|
// stopped for a moment
|
|
if (delta > 0L && delta < 1000000000L && count < MAX_FRAMES) {
|
|
TIMES[count++] = delta;
|
|
}
|
|
}
|
|
lastFrameAt = now;
|
|
totalFrames++;
|
|
|
|
if (windowStart == 0L) {
|
|
windowStart = now;
|
|
return;
|
|
}
|
|
long elapsed = now - windowStart;
|
|
if (elapsed < 1000000000L) {
|
|
return;
|
|
}
|
|
close(elapsed);
|
|
}
|
|
|
|
/** Ends the window, works out the numbers and writes them down. */
|
|
private static void close(long elapsedNanos) {
|
|
long[] sorted = Arrays.copyOf(TIMES, count);
|
|
Arrays.sort(sorted);
|
|
double seconds = elapsedNanos / 1000000000.0D;
|
|
double average = count == 0 ? 0.0D : count / seconds;
|
|
double median = percentile(sorted, 0.50D);
|
|
double onePercent = percentile(sorted, 0.99D);
|
|
double tenthPercent = percentile(sorted, 0.999D);
|
|
double worst = count == 0 ? 0.0D : sorted[count - 1];
|
|
|
|
FrameStats stats = new FrameStats(count, average, millis(median),
|
|
millis(onePercent), millis(tenthPercent), millis(worst));
|
|
latest = stats;
|
|
Log.info("frames: " + stats.describe() + " | total " + totalFrames);
|
|
|
|
count = 0;
|
|
windowStart = System.nanoTime();
|
|
}
|
|
|
|
/** The frame time at that fraction of the window, in nanoseconds. */
|
|
private static double percentile(long[] sorted, double fraction) {
|
|
if (sorted.length == 0) {
|
|
return 0.0D;
|
|
}
|
|
int index = (int) (fraction * sorted.length);
|
|
if (index >= sorted.length) {
|
|
index = sorted.length - 1;
|
|
}
|
|
if (index < 0) {
|
|
index = 0;
|
|
}
|
|
return sorted[index];
|
|
}
|
|
|
|
private static double millis(double nanos) {
|
|
return nanos / 1000000.0D;
|
|
}
|
|
|
|
/** The last window, never null. */
|
|
public static FrameStats latest() {
|
|
return latest;
|
|
}
|
|
}
|