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 std = @import("std");
 | 
			
		||||
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 {
 | 
			
		||||
    r: u8 = 0xff,
 | 
			
		||||
@ -21,7 +24,66 @@ pub const GameState = struct {
 | 
			
		||||
        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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 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 sdl = @import("sdl.zig").c;
 | 
			
		||||
const atils = @import("./asset_utils.zig");
 | 
			
		||||
const gs = @import("./game_state.zig");
 | 
			
		||||
const assert = std.debug.assert;
 | 
			
		||||
const SCREEN_WIDTH = 640;
 | 
			
		||||
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 GameText = @import("./text.zig").GameText;
 | 
			
		||||
const RGBAColor = @import("./utils/rgb_color.zig").RGBAColor;
 | 
			
		||||
@ -27,64 +28,29 @@ pub fn main() !void {
 | 
			
		||||
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
 | 
			
		||||
    const allocator = gpa.allocator();
 | 
			
		||||
 | 
			
		||||
    const bg_texture = try atils.loadTexture(renderer, "assets/background.png");
 | 
			
		||||
    defer sdl.SDL_DestroyTexture(bg_texture);
 | 
			
		||||
 | 
			
		||||
    try GameText.initFont();
 | 
			
		||||
    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);
 | 
			
		||||
    defer game_state.deinit();
 | 
			
		||||
 | 
			
		||||
    var quit = false;
 | 
			
		||||
    var shifted = false;
 | 
			
		||||
    var start_time: u32 = 0;
 | 
			
		||||
    var timer = Timer{};
 | 
			
		||||
    var fps_timer = Timer{};
 | 
			
		||||
    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) {
 | 
			
		||||
        frame_time.reset();
 | 
			
		||||
        var event: sdl.SDL_Event = undefined;
 | 
			
		||||
        while (sdl.SDL_PollEvent(&event) != 0) {
 | 
			
		||||
            switch (event.type) {
 | 
			
		||||
                sdl.SDL_QUIT => {
 | 
			
		||||
                    quit = true;
 | 
			
		||||
                },
 | 
			
		||||
                sdl.SDL_KEYUP => {
 | 
			
		||||
                    switch (event.key.keysym.sym) {
 | 
			
		||||
                        sdl.SDLK_LSHIFT => {
 | 
			
		||||
                            shifted = false;
 | 
			
		||||
                        },
 | 
			
		||||
                        else => {},
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                sdl.SDL_KEYDOWN => {
 | 
			
		||||
                    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 => {},
 | 
			
		||||
                    }
 | 
			
		||||
                    log("Got key event: %i\n", event.key.keysym.sym);
 | 
			
		||||
@ -92,52 +58,43 @@ pub fn main() !void {
 | 
			
		||||
                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;
 | 
			
		||||
        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 },
 | 
			
		||||
        );
 | 
			
		||||
        // double newTime = time();
 | 
			
		||||
        // double frameTime = newTime - currentTime;
 | 
			
		||||
        // if ( frameTime > 0.25 )
 | 
			
		||||
        //     frameTime = 0.25;
 | 
			
		||||
        // currentTime = newTime;
 | 
			
		||||
 | 
			
		||||
        const fps_time: f32 = @floatFromInt(fps_timer.getTicks() / 1000);
 | 
			
		||||
        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 },
 | 
			
		||||
        );
 | 
			
		||||
        // accumulator += frameTime;
 | 
			
		||||
 | 
			
		||||
        // 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(renderer, 0xff, 0x00, 0x00, 0xff);
 | 
			
		||||
        // _ = sdl.SDL_RenderFillRect(renderer, &fill_rect);
 | 
			
		||||
        // _ = sdl.SDL_RenderSetViewport(renderer, &.{ .x = SCREEN_WIDTH / 2, .y = SCREEN_HEIGHT / 2, .w = SCREEN_WIDTH, .h = SCREEN_HEIGHT / 2 });
 | 
			
		||||
        // _ = sdl.SDL_RenderCopy(renderer, texture, null, null);
 | 
			
		||||
        _ = 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);
 | 
			
		||||
        // while ( accumulator >= dt )
 | 
			
		||||
        // {
 | 
			
		||||
        //     previousState = currentState;
 | 
			
		||||
        //     integrate( currentState, t, dt );
 | 
			
		||||
        //     t += dt;
 | 
			
		||||
        //     accumulator -= dt;
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        // _ = sdl.SDL_UpdateWindowSurface(window);
 | 
			
		||||
        // sdl.SDL_Delay(100);
 | 
			
		||||
        // const double alpha = accumulator / dt;
 | 
			
		||||
 | 
			
		||||
        // 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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -21,6 +21,7 @@ pub const Timer = struct {
 | 
			
		||||
        self.pause_ticks_ms = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Resets the timer to 0 and starts it
 | 
			
		||||
    pub fn reset(self: *Timer) void {
 | 
			
		||||
        self.status = .started;
 | 
			
		||||
        self.start_ticks_ms = sdl.SDL_GetTicks();
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user