Settings: add FPS cap (#532)

* settings: add fpsCap

* fpsCap: convert to nanos correctly

* main.zig: use specific names for lastBeginRendering and frameTime

* physics/fps window: use real time between frames

* graphics menu: add fps slider

* fpsCap: round slider + fix formatter

* fpsCap: prevent overflow

* fpsCap: remove debug spam

* fpsCap setting: add 'Limit'

* debug menu: note fps limit

* fpsCap formatter: dupe constant string to prevent free failure

* debug menu: remove contradictory (vsync) (unlimited)

* debug menu: fix mem leak in fps format

* debug manu: simplify fps limit text allocation
This commit is contained in:
archbirdplus 2024-07-01 02:59:51 -07:00 committed by GitHub
parent 3956f07556
commit ad9cad6270
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 56 additions and 12 deletions

View File

@ -25,7 +25,14 @@ pub var window = GuiWindow {
pub fn render() void {
draw.setColor(0xffffffff);
var y: f32 = 0;
draw.print("fps: {d:.0} Hz{s}", .{1.0/main.lastFrameTime.load(.monotonic), if(main.settings.vsync) @as([]const u8, " (vsync)") else ""}, 0, y, 8, .left);
const fpsCapText = if(main.settings.fpsCap) |fpsCap| std.fmt.allocPrint(main.stackAllocator.allocator, " (limit: {d:.0} Hz)", .{fpsCap}) catch unreachable else "";
defer main.stackAllocator.allocator.free(fpsCapText);
const fpsLimit = std.fmt.allocPrint(main.stackAllocator.allocator, "{s}{s}", .{
fpsCapText,
if(main.settings.vsync) " (vsync)" else "",
}) catch unreachable;
defer main.stackAllocator.allocator.free(fpsLimit);
draw.print("fps: {d:.0} Hz{s}", .{1.0/main.lastDeltaTime.load(.monotonic), fpsLimit}, 0, y, 8, .left);
y += 8;
draw.print("frameTime: {d:.1} ms", .{main.lastFrameTime.load(.monotonic)*1000.0}, 0, y, 8, .left);
y += 8;
@ -57,4 +64,4 @@ pub fn render() void {
draw.print("Opaque faces: {}, Transparent faces: {}", .{main.renderer.chunk_meshing.quadsDrawn, main.renderer.chunk_meshing.transparentQuadsDrawn}, 0, y, 8, .left);
y += 8;
}
}
}

View File

@ -24,6 +24,27 @@ const anisotropy = [_]u8{1, 2, 4, 8, 16};
const resolutions = [_]u16{25, 50, 100};
fn fpsCapRound(newValue: f32) ?u32 {
if(newValue < 144.0) {
return @as(u32, @intFromFloat(newValue/5.0))*5;
} else if (newValue < 149.0) {
return 144;
} else {
return null;
}
}
fn fpsCapFormatter(allocator: main.utils.NeverFailingAllocator, value: f32) []const u8 {
const cap = fpsCapRound(value);
if(cap == null)
return allocator.dupe(u8, "#ffffffFPS: Unlimited");
return std.fmt.allocPrint(allocator.allocator, "#ffffffFPS Limit: {d:.0}", .{cap.?}) catch unreachable;
}
fn fpsCapCallback(newValue: f32) void {
settings.fpsCap = fpsCapRound(newValue);
}
fn renderDistanceCallback(newValue: u16) void {
settings.renderDistance = newValue + renderDistances[0];
}
@ -64,6 +85,7 @@ fn resolutionScaleCallback(newValue: u16) void {
pub fn onOpen() void {
const list = VerticalList.init(.{padding, 16 + padding}, 300, 16);
list.add(ContinuousSlider.init(.{0, 0}, 128, 10.0, 154.0, @floatFromInt(settings.fpsCap orelse 144), &fpsCapCallback, &fpsCapFormatter));
list.add(DiscreteSlider.init(.{0, 0}, 128, "#ffffffRender Distance: ", "{}", &renderDistances, settings.renderDistance - renderDistances[0], &renderDistanceCallback));
list.add(ContinuousSlider.init(.{0, 0}, 128, 40.0, 120.0, settings.fov, &fovCallback, &fovFormatter));
list.add(CheckBox.init(.{0, 0}, 128, "Bloom", settings.bloom, &bloomCallback));
@ -80,4 +102,4 @@ pub fn onClose() void {
if(window.rootComponent) |*comp| {
comp.deinit();
}
}
}

View File

@ -347,7 +347,10 @@ pub const KeyBoard = struct {
}
};
/// Records gpu time per frame.
pub var lastFrameTime = std.atomic.Value(f64).init(0);
/// Measures time between different frames' beginnings.
pub var lastDeltaTime = std.atomic.Value(f64).init(0);
pub fn main() void {
seed = @bitCast(std.time.milliTimestamp());
@ -428,7 +431,7 @@ pub fn main() void {
c.glDepthFunc(c.GL_LESS);
c.glBlendFunc(c.GL_SRC_ALPHA, c.GL_ONE_MINUS_SRC_ALPHA);
Window.GLFWCallbacks.framebufferSize(undefined, Window.width, Window.height);
var lastTime = std.time.nanoTimestamp();
var lastBeginRendering = std.time.nanoTimestamp();
if(settings.developerAutoEnterWorld.len != 0) {
// Speed up the dev process by entering the world directly.
@ -448,17 +451,27 @@ pub fn main() void {
std.time.sleep(16_000_000);
}
const endRendering = std.time.nanoTimestamp();
const frameTime = @as(f64, @floatFromInt(endRendering -% lastBeginRendering))/1e9;
if(settings.developerGPUInfiniteLoopDetection and frameTime > 5) { // On linux a process that runs 10 seconds or longer on the GPU will get stopped. This allows detecting an infinite loop on the GPU.
std.log.err("Frame got too long with {} seconds. Infinite loop on GPU?", .{frameTime});
std.posix.exit(1);
}
lastFrameTime.store(frameTime, .monotonic);
if(settings.fpsCap) |fpsCap| {
const minFrameTime = @divFloor(1000*1000*1000, fpsCap);
const sleep = @min(minFrameTime, @max(0, minFrameTime - (endRendering -% lastBeginRendering)));
std.time.sleep(sleep);
}
const begin = std.time.nanoTimestamp();
const deltaTime = @as(f64, @floatFromInt(begin -% lastBeginRendering))/1e9;
lastDeltaTime.store(deltaTime, .monotonic);
lastBeginRendering = begin;
Window.handleEvents();
file_monitor.handleEvents();
const newTime = std.time.nanoTimestamp();
const deltaTime = @as(f64, @floatFromInt(newTime -% lastTime))/1e9;
if(settings.developerGPUInfiniteLoopDetection and deltaTime > 5) { // On linux a process that runs 10 seconds or longer on the GPU will get stopped. This allows detecting an infinite loop on the GPU.
std.log.err("Frame got too long with {} seconds. Infinite loop on GPU?", .{deltaTime});
std.posix.exit(1);
}
lastFrameTime.store(deltaTime, .monotonic);
lastTime = newTime;
if(game.world != null) { // Update the game
game.update(deltaTime);
}

View File

@ -20,6 +20,8 @@ pub var cpuThreads: ?u64 = null;
pub var anisotropicFiltering: u8 = 4.0;
pub var fpsCap: ?u32 = null;
pub var fov: f32 = 70;
pub var mouseSensitivity: f32 = 1;