WIP decoupled rendering
This commit is contained in:
parent
ba53a7dd66
commit
a43b46449f
58
README.md
Normal file
58
README.md
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
## Physics and Timing
|
||||||
|
|
||||||
|
I want to have decoupled rendering with the ability to render partial physics simulations, so it needs to be deterministic.
|
||||||
|
|
||||||
|
Based on this snippet from [this article](https://gafferongames.com/post/fix_your_timestep/)
|
||||||
|
|
||||||
|
```c
|
||||||
|
double t = 0.0;
|
||||||
|
double dt = 0.01;
|
||||||
|
|
||||||
|
double currentTime = hires_time_in_seconds();
|
||||||
|
double accumulator = 0.0;
|
||||||
|
|
||||||
|
State previous;
|
||||||
|
State current;
|
||||||
|
|
||||||
|
while ( !quit )
|
||||||
|
{
|
||||||
|
double newTime = time();
|
||||||
|
double frameTime = newTime - currentTime;
|
||||||
|
if ( frameTime > 0.25 )
|
||||||
|
frameTime = 0.25;
|
||||||
|
currentTime = newTime;
|
||||||
|
|
||||||
|
accumulator += frameTime;
|
||||||
|
|
||||||
|
while ( accumulator >= dt )
|
||||||
|
{
|
||||||
|
previousState = currentState;
|
||||||
|
integrate( currentState, t, dt );
|
||||||
|
t += dt;
|
||||||
|
accumulator -= dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const double alpha = accumulator / dt;
|
||||||
|
|
||||||
|
State state = currentState * alpha +
|
||||||
|
previousState * ( 1.0 - alpha );
|
||||||
|
|
||||||
|
render( state );
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
or from this [medium article](https://medium.com/@tglaiel/how-to-make-your-game-run-at-60fps-24c61210fe75)
|
||||||
|
|
||||||
|
```c
|
||||||
|
while(running){
|
||||||
|
computeDeltaTimeSomehow();
|
||||||
|
accumulator += deltaTime;
|
||||||
|
while(accumulator >= 1.0/60.0){
|
||||||
|
previous_state = current_state;
|
||||||
|
current_state = update();
|
||||||
|
accumulator -= 1.0/60.0;
|
||||||
|
}
|
||||||
|
render_interpolated_somehow(previous_state, current_state, accumulator/(1.0/60.0));
|
||||||
|
display();
|
||||||
|
}
|
||||||
|
```
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 300 KiB |
@ -1,6 +1,9 @@
|
|||||||
const sdl = @import("./sdl.zig").c;
|
const sdl = @import("./sdl.zig").c;
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const sf = @import("./slime_factory.zig");
|
const sf = @import("./slime_factory.zig");
|
||||||
|
const atils = @import("./asset_utils.zig");
|
||||||
|
const GameText = @import("./text.zig").GameText;
|
||||||
|
const RGBAColor = @import("./utils/rgb_color.zig").RGBAColor;
|
||||||
|
|
||||||
pub const GameState = struct {
|
pub const GameState = struct {
|
||||||
r: u8 = 0xff,
|
r: u8 = 0xff,
|
||||||
@ -21,7 +24,66 @@ pub const GameState = struct {
|
|||||||
self.slime_factory.deinit();
|
self.slime_factory.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_tick(self: *GameState) void {
|
// Physics main loop that processes variable time steps
|
||||||
|
pub fn update_tick(self: *GameState) !void {
|
||||||
|
const bg_texture = try atils.loadTexture(self.renderer, "assets/background.png");
|
||||||
|
defer sdl.SDL_DestroyTexture(bg_texture);
|
||||||
|
|
||||||
|
_ = sdl.SDL_SetRenderDrawColor(self.renderer, 0xff, 0xff, 0xff, 0xff);
|
||||||
|
_ = sdl.SDL_RenderClear(self.renderer);
|
||||||
|
_ = sdl.SDL_RenderCopy(self.renderer, bg_texture, null, null);
|
||||||
|
|
||||||
|
var text = try GameText.loadFromRenderedText("Press S to start / stop", RGBAColor.whiteSmoke().tosdl());
|
||||||
|
try text.render(
|
||||||
|
self,
|
||||||
|
.{ .x = @intCast(self.SCREEN_WIDTH - text.w), .y = 2 },
|
||||||
|
);
|
||||||
|
text = try GameText.loadFromRenderedText("Press P to pause / unpause", RGBAColor.whiteSmoke().tosdl());
|
||||||
|
try text.render(
|
||||||
|
self,
|
||||||
|
.{ .x = @intCast(self.SCREEN_WIDTH - text.w), .y = 24 },
|
||||||
|
);
|
||||||
|
|
||||||
|
// var buf: [16]u8 = undefined;
|
||||||
|
// const timer_ms = timer.getTicks();
|
||||||
|
// // const timer_base = std.math.log10(timer_ms) + 3;
|
||||||
|
// const time_str: [*:0]const u8 = try std.fmt.bufPrintZ(&buf, "{d}ms", .{timer_ms});
|
||||||
|
// var time_text = try GameText.loadFromRenderedText(time_str, RGBAColor.whiteSmoke().tosdl());
|
||||||
|
// try time_text.render(
|
||||||
|
// &game_state,
|
||||||
|
// .{ .x = @intCast(game_state.SCREEN_WIDTH - time_text.w), .y = 64 },
|
||||||
|
// );
|
||||||
|
|
||||||
|
// const fps_ms: f32 = @floatFromInt(fps_timer.getTicks());
|
||||||
|
// const avg_fps: f32 = @round((frames / fps_ms) * 100_000) / 100.0;
|
||||||
|
// var fps_text = try GameText.loadFromRenderedText(try std.fmt.bufPrintZ(&buf, "{d} fps", .{avg_fps}), RGBAColor.whiteSmoke().tosdl());
|
||||||
|
// try fps_text.render(
|
||||||
|
// &game_state,
|
||||||
|
// .{ .x = 5, .y = 5 },
|
||||||
|
// );
|
||||||
|
|
||||||
|
// Render red rect
|
||||||
|
// const fill_rect: sdl.struct_SDL_Rect = sdl.SDL_Rect{ .x = SCREEN_WIDTH / 4, .y = SCREEN_HEIGHT / 4, .w = SCREEN_WIDTH / 2 + img_pos.x, .h = SCREEN_HEIGHT / 2 + img_pos.y };
|
||||||
|
// _ = sdl.SDL_SetRenderDrawColor(self.renderer, 0xff, 0x00, 0x00, 0xff);
|
||||||
|
// _ = sdl.SDL_RenderFillRect(self.renderer, &fill_rect);
|
||||||
|
// _ = sdl.SDL_RenderSetViewport(self.renderer, &.{ .x = SCREEN_WIDTH / 2, .y = SCREEN_HEIGHT / 2, .w = SCREEN_WIDTH, .h = SCREEN_HEIGHT / 2 });
|
||||||
|
// _ = sdl.SDL_RenderCopy(self.renderer, texture, null, null);
|
||||||
|
// frames += 1;
|
||||||
|
// if (frames > 60) {
|
||||||
|
// frames = 1;
|
||||||
|
// fps_timer.reset();
|
||||||
|
// }
|
||||||
|
// var dest_rect = sdl.SDL_Rect{ .x = src_rect.x + img_pos.x, .y = src_rect.y + img_pos.y, .w = SCREEN_WIDTH, .h = SCREEN_HEIGHT };
|
||||||
|
// _ = sdl.SDL_BlitScaled(zig_image, &src_rect, screen_surface, &dest_rect);
|
||||||
|
|
||||||
|
// _ = sdl.SDL_UpdateWindowSurface(window);
|
||||||
|
// sdl.SDL_Delay(100);
|
||||||
|
|
||||||
self.slime_factory.render(self);
|
self.slime_factory.render(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Renders the current frame to screen
|
||||||
|
pub fn render(self: *GameState) void {
|
||||||
|
_ = sdl.SDL_RenderPresent(self.renderer);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
123
src/main.zig
123
src/main.zig
@ -1,10 +1,11 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const sdl = @import("sdl.zig").c;
|
const sdl = @import("sdl.zig").c;
|
||||||
const atils = @import("./asset_utils.zig");
|
|
||||||
const gs = @import("./game_state.zig");
|
const gs = @import("./game_state.zig");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const SCREEN_WIDTH = 640;
|
const SCREEN_WIDTH = 640;
|
||||||
const SCREEN_HEIGHT = 480;
|
const SCREEN_HEIGHT = 480;
|
||||||
|
const SCREEN_FPS: f64 = 60.0;
|
||||||
|
const SCREEN_TICKS_PER_FRAME: f64 = 1000 / SCREEN_FPS;
|
||||||
const log = sdl.SDL_Log;
|
const log = sdl.SDL_Log;
|
||||||
const GameText = @import("./text.zig").GameText;
|
const GameText = @import("./text.zig").GameText;
|
||||||
const RGBAColor = @import("./utils/rgb_color.zig").RGBAColor;
|
const RGBAColor = @import("./utils/rgb_color.zig").RGBAColor;
|
||||||
@ -27,64 +28,29 @@ pub fn main() !void {
|
|||||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
const bg_texture = try atils.loadTexture(renderer, "assets/background.png");
|
|
||||||
defer sdl.SDL_DestroyTexture(bg_texture);
|
|
||||||
|
|
||||||
try GameText.initFont();
|
try GameText.initFont();
|
||||||
defer GameText.deinitFont();
|
defer GameText.deinitFont();
|
||||||
|
|
||||||
// var slime_factory = try sf.SlimeFactory.init(allocator, renderer);
|
|
||||||
// defer slime_factory.deinit();
|
|
||||||
// try slime_factory.add(.{});
|
|
||||||
|
|
||||||
var game_state = try gs.GameState.init(allocator, renderer);
|
var game_state = try gs.GameState.init(allocator, renderer);
|
||||||
defer game_state.deinit();
|
defer game_state.deinit();
|
||||||
|
|
||||||
var quit = false;
|
var quit = false;
|
||||||
var shifted = false;
|
|
||||||
var start_time: u32 = 0;
|
|
||||||
var timer = Timer{};
|
|
||||||
var fps_timer = Timer{};
|
var fps_timer = Timer{};
|
||||||
fps_timer.start();
|
fps_timer.start();
|
||||||
var frames: f32 = 0;
|
var frame_time = Timer{};
|
||||||
|
var prev_frame_ms: f64 = 16.0;
|
||||||
|
var accumulator: f64 = 0.0;
|
||||||
|
|
||||||
while (!quit) {
|
while (!quit) {
|
||||||
|
frame_time.reset();
|
||||||
var event: sdl.SDL_Event = undefined;
|
var event: sdl.SDL_Event = undefined;
|
||||||
while (sdl.SDL_PollEvent(&event) != 0) {
|
while (sdl.SDL_PollEvent(&event) != 0) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
sdl.SDL_QUIT => {
|
sdl.SDL_QUIT => {
|
||||||
quit = true;
|
quit = true;
|
||||||
},
|
},
|
||||||
sdl.SDL_KEYUP => {
|
|
||||||
switch (event.key.keysym.sym) {
|
|
||||||
sdl.SDLK_LSHIFT => {
|
|
||||||
shifted = false;
|
|
||||||
},
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sdl.SDL_KEYDOWN => {
|
sdl.SDL_KEYDOWN => {
|
||||||
switch (event.key.keysym.sym) {
|
switch (event.key.keysym.sym) {
|
||||||
sdl.SDLK_RETURN => {
|
|
||||||
start_time = sdl.SDL_GetTicks();
|
|
||||||
},
|
|
||||||
sdl.SDLK_s => {
|
|
||||||
if (timer.status == .started) timer.stop() else timer.start();
|
|
||||||
},
|
|
||||||
sdl.SDLK_p => {
|
|
||||||
if (timer.status == .paused) timer.unpause() else timer.pause();
|
|
||||||
},
|
|
||||||
// sdl.SDLK_UP => {
|
|
||||||
// img_pos.y = img_pos.y - 5;
|
|
||||||
// },
|
|
||||||
// sdl.SDLK_DOWN => {
|
|
||||||
// img_pos.y = img_pos.y + 5;
|
|
||||||
// },
|
|
||||||
// sdl.SDLK_LEFT => {
|
|
||||||
// img_pos.x = img_pos.x - 5;
|
|
||||||
// },
|
|
||||||
// sdl.SDLK_RIGHT => {
|
|
||||||
// img_pos.x = img_pos.x + 5;
|
|
||||||
// },
|
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
log("Got key event: %i\n", event.key.keysym.sym);
|
log("Got key event: %i\n", event.key.keysym.sym);
|
||||||
@ -92,52 +58,43 @@ pub fn main() !void {
|
|||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = sdl.SDL_SetRenderDrawColor(renderer, 0xff, 0xff, 0xff, 0xff);
|
|
||||||
_ = sdl.SDL_RenderClear(renderer);
|
|
||||||
_ = sdl.SDL_RenderCopy(renderer, bg_texture, null, null);
|
|
||||||
game_state.update_tick();
|
|
||||||
var text = try GameText.loadFromRenderedText("Press S to start / stop", RGBAColor.whiteSmoke().tosdl());
|
|
||||||
try text.render(
|
|
||||||
&game_state,
|
|
||||||
.{ .x = @intCast(game_state.SCREEN_WIDTH - text.w), .y = 2 },
|
|
||||||
);
|
|
||||||
text = try GameText.loadFromRenderedText("Press P to pause / unpause", RGBAColor.whiteSmoke().tosdl());
|
|
||||||
try text.render(
|
|
||||||
&game_state,
|
|
||||||
.{ .x = @intCast(game_state.SCREEN_WIDTH - text.w), .y = 24 },
|
|
||||||
);
|
|
||||||
|
|
||||||
var buf: [16]u8 = undefined;
|
// double newTime = time();
|
||||||
const timer_ms = timer.getTicks();
|
// double frameTime = newTime - currentTime;
|
||||||
// const timer_base = std.math.log10(timer_ms) + 3;
|
// if ( frameTime > 0.25 )
|
||||||
const time_str: [*:0]const u8 = try std.fmt.bufPrintZ(&buf, "{d}ms", .{timer_ms});
|
// frameTime = 0.25;
|
||||||
var time_text = try GameText.loadFromRenderedText(time_str, RGBAColor.whiteSmoke().tosdl());
|
// currentTime = newTime;
|
||||||
try time_text.render(
|
|
||||||
&game_state,
|
|
||||||
.{ .x = @intCast(game_state.SCREEN_WIDTH - time_text.w), .y = 64 },
|
|
||||||
);
|
|
||||||
|
|
||||||
const fps_time: f32 = @floatFromInt(fps_timer.getTicks() / 1000);
|
// accumulator += frameTime;
|
||||||
const avg_fps: f32 = @round((frames / fps_time) * 100) / 100.0;
|
|
||||||
var fps_text = try GameText.loadFromRenderedText(try std.fmt.bufPrintZ(&buf, "{d} fps", .{avg_fps}), RGBAColor.whiteSmoke().tosdl());
|
|
||||||
try fps_text.render(
|
|
||||||
&game_state,
|
|
||||||
.{ .x = 5, .y = 5 },
|
|
||||||
);
|
|
||||||
|
|
||||||
// Render red rect
|
// while ( accumulator >= dt )
|
||||||
// const fill_rect: sdl.struct_SDL_Rect = sdl.SDL_Rect{ .x = SCREEN_WIDTH / 4, .y = SCREEN_HEIGHT / 4, .w = SCREEN_WIDTH / 2 + img_pos.x, .h = SCREEN_HEIGHT / 2 + img_pos.y };
|
// {
|
||||||
// _ = sdl.SDL_SetRenderDrawColor(renderer, 0xff, 0x00, 0x00, 0xff);
|
// previousState = currentState;
|
||||||
// _ = sdl.SDL_RenderFillRect(renderer, &fill_rect);
|
// integrate( currentState, t, dt );
|
||||||
// _ = sdl.SDL_RenderSetViewport(renderer, &.{ .x = SCREEN_WIDTH / 2, .y = SCREEN_HEIGHT / 2, .w = SCREEN_WIDTH, .h = SCREEN_HEIGHT / 2 });
|
// t += dt;
|
||||||
// _ = sdl.SDL_RenderCopy(renderer, texture, null, null);
|
// accumulator -= dt;
|
||||||
_ = sdl.SDL_RenderPresent(renderer);
|
// }
|
||||||
frames += 1;
|
|
||||||
// var dest_rect = sdl.SDL_Rect{ .x = src_rect.x + img_pos.x, .y = src_rect.y + img_pos.y, .w = SCREEN_WIDTH, .h = SCREEN_HEIGHT };
|
|
||||||
// _ = sdl.SDL_BlitScaled(zig_image, &src_rect, screen_surface, &dest_rect);
|
|
||||||
|
|
||||||
// _ = sdl.SDL_UpdateWindowSurface(window);
|
// const double alpha = accumulator / dt;
|
||||||
// sdl.SDL_Delay(100);
|
|
||||||
|
// State state = currentState * alpha +
|
||||||
|
// previousState * ( 1.0 - alpha );
|
||||||
|
|
||||||
|
// render( state );
|
||||||
|
accumulator = accumulator + prev_frame_ms;
|
||||||
|
while (accumulator >= SCREEN_TICKS_PER_FRAME) {
|
||||||
|
try game_state.update_tick();
|
||||||
|
accumulator = accumulator - SCREEN_TICKS_PER_FRAME;
|
||||||
|
std.debug.print("Tick update done. Acc: {d}\n", .{accumulator});
|
||||||
|
}
|
||||||
|
|
||||||
|
game_state.render();
|
||||||
|
|
||||||
|
prev_frame_ms = @floatFromInt(frame_time.getTicks());
|
||||||
|
// const frame_time_ticks = frame_time.getTicks();
|
||||||
|
// if (frame_time_ticks < SCREEN_TICKS_PER_FRAME) {
|
||||||
|
// sdl.SDL_Delay(SCREEN_TICKS_PER_FRAME - frame_time_ticks);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ pub const Timer = struct {
|
|||||||
self.pause_ticks_ms = 0;
|
self.pause_ticks_ms = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resets the timer to 0 and starts it
|
||||||
pub fn reset(self: *Timer) void {
|
pub fn reset(self: *Timer) void {
|
||||||
self.status = .started;
|
self.status = .started;
|
||||||
self.start_ticks_ms = sdl.SDL_GetTicks();
|
self.start_ticks_ms = sdl.SDL_GetTicks();
|
||||||
|
Loading…
Reference in New Issue
Block a user