diff --git a/src/gui/windows/debug.zig b/src/gui/windows/debug.zig index a45fa19e..dad26dab 100644 --- a/src/gui/windows/debug.zig +++ b/src/gui/windows/debug.zig @@ -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; } -} \ No newline at end of file +} diff --git a/src/gui/windows/graphics.zig b/src/gui/windows/graphics.zig index 14f00537..7eff5caf 100644 --- a/src/gui/windows/graphics.zig +++ b/src/gui/windows/graphics.zig @@ -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(); } -} \ No newline at end of file +} diff --git a/src/main.zig b/src/main.zig index 07390191..9012c8c6 100644 --- a/src/main.zig +++ b/src/main.zig @@ -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); } diff --git a/src/settings.zig b/src/settings.zig index e029a885..d691eba6 100644 --- a/src/settings.zig +++ b/src/settings.zig @@ -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;