From 7e909ac7eb6ef0777695055fb62104e0b64552e7 Mon Sep 17 00:00:00 2001 From: odweta Date: Sun, 4 Oct 2026 20:29:49 +0200 Subject: [PATCH] initial commit with demo in main.c --- .gitignore | 2 + Makefile | 19 +++++ include/engine.h | 78 +++++++++++++++++++++ include/error.h | 10 +++ src/engine/engine.c | 59 ++++++++++++++++ src/engine/engine_init.c | 148 +++++++++++++++++++++++++++++++++++++++ src/engine/render.c | 37 ++++++++++ src/main.c | 66 +++++++++++++++++ 8 files changed, 419 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 include/engine.h create mode 100644 include/error.h create mode 100644 src/engine/engine.c create mode 100644 src/engine/engine_init.c create mode 100644 src/engine/render.c create mode 100644 src/main.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..abbdf74 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.clangd +*.sublime-* \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..96a5a37 --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +CC := gcc +LIBS := `pkg-config --libs sdl3` +INC := `pkg-config --cflags sdl3` -I./include +SRC := src/main.c \ + src/engine/engine.c \ + src/engine/engine_init.c \ + src/engine/render.c +OUT := build/main + +build: + $(CC) -o $(OUT) $(SRC) $(INC) $(LIBS) + +run: + $(OUT) + +clean: + rm $(OUT) + +.PHONY: build run \ No newline at end of file diff --git a/include/engine.h b/include/engine.h new file mode 100644 index 0000000..ff0fda0 --- /dev/null +++ b/include/engine.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include +#include +#include + +#define WINDOW_WIDTH 1280 +#define WINDOW_HEIGHT 720 +#define DEFAULT_WINDOW_NAME "SDL Playground" +#define WINDOW_NAME_LENGTH 128 +#define TARGET_FRAME 1.0 / 60.0 + +typedef struct { + char window_name[WINDOW_NAME_LENGTH]; + uint16_t width, height; + SDL_Window *window; + SDL_Renderer *renderer; + SDL_Texture *texture; +} engine_sdl_state_t; + +typedef struct { + /** + * this function runs inside of the SDL_PollEvent loop + * useful for user input, quitting the program and so on + */ + void (*engine_poll_events)(void); + /** + * meant as the "area" for manupulating the framebuffer; + * after this is executed, the contents of + * game_state->framebuffer get drawn + */ + void (*engine_update_framebuffer)(void); + engine_sdl_state_t *sdl_state; +} engine_internal_state_t; + +typedef struct { + uint32_t framebuffer[WINDOW_WIDTH * WINDOW_HEIGHT]; + SDL_Event event; + bool is_running; + uint16_t width, height; +} game_state_t; + +extern engine_internal_state_t *internal_state; +extern game_state_t *game_state; + +engine_sdl_state_t* +engine_sdl_init(); + +void +engine_sdl_destroy( + engine_sdl_state_t *state +); + +bool +engine_init( + void (*engine_poll_events)(void), + void (*engine_update_framebuffer)(void) +); + +void +engine_start(); + +void +engine_stop(); + +void +put_pixel( + unsigned int x, + unsigned int y, + uint32_t color +); + +void +clear_framebuffer( + uint32_t color +); \ No newline at end of file diff --git a/include/error.h b/include/error.h new file mode 100644 index 0000000..f5de78b --- /dev/null +++ b/include/error.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include + +#define eprintf(fmt, ...) \ + fprintf(stderr, "[ERROR] " fmt "\n", ##__VA_ARGS__) + +#define RCODE_FAILURE false +#define RCODE_SUCCESS true \ No newline at end of file diff --git a/src/engine/engine.c b/src/engine/engine.c new file mode 100644 index 0000000..7e12fc0 --- /dev/null +++ b/src/engine/engine.c @@ -0,0 +1,59 @@ +#include "engine.h" + +engine_internal_state_t *internal_state = NULL; +game_state_t *game_state = NULL; + +void +engine_start() { + // main game loop + while(game_state->is_running) { + uint64_t start = SDL_GetPerformanceCounter(); + + // poll events + while (SDL_PollEvent(&game_state->event)) { + internal_state->engine_poll_events(); + } + + // call the user-defined framebuffer manipulation function + internal_state->engine_update_framebuffer(); + + // update the texture with the modified framebuffer + SDL_UpdateTexture( + internal_state->sdl_state->texture, + NULL, game_state->framebuffer, + internal_state->sdl_state->width * sizeof(uint32_t) + ); + + SDL_RenderClear( + internal_state->sdl_state->renderer + ); + + SDL_RenderTexture( + internal_state->sdl_state->renderer, + internal_state->sdl_state->texture, + NULL, NULL + ); + + SDL_RenderPresent( + internal_state->sdl_state->renderer + ); + + uint64_t end = SDL_GetPerformanceCounter(); + + double elapsed = + (double)(end - start) / + (double)SDL_GetPerformanceFrequency(); + + // cap the framerate at '1.0/TARGET_FRAME' fps + if (elapsed < TARGET_FRAME) { + SDL_Delay((TARGET_FRAME - elapsed) * 1000); + } + } +} + +void +engine_stop() { + engine_sdl_destroy(internal_state->sdl_state); + free(internal_state); + free(game_state); +} \ No newline at end of file diff --git a/src/engine/engine_init.c b/src/engine/engine_init.c new file mode 100644 index 0000000..28e9444 --- /dev/null +++ b/src/engine/engine_init.c @@ -0,0 +1,148 @@ +#include "engine.h" +#include "error.h" +#include +#include + +bool +engine_sdl_init_state(engine_sdl_state_t **state) { + *state = (engine_sdl_state_t *) + malloc(sizeof(engine_sdl_state_t)); + + if (*state == NULL) { + eprintf("failed to initialize engine_sdl_state"); + return RCODE_FAILURE; + } + + (*state)->width = WINDOW_WIDTH; + (*state)->height = WINDOW_HEIGHT; + strncpy( + (*state)->window_name, DEFAULT_WINDOW_NAME, + WINDOW_NAME_LENGTH + ); + + return RCODE_SUCCESS; +} + +bool +engine_sdl_init_root() +{ + if (!SDL_Init(SDL_INIT_VIDEO)) { + eprintf("SDL_Init failed! %s\n", SDL_GetError()); + return RCODE_FAILURE; + } else { + return RCODE_SUCCESS; + } +} + + +bool +engine_sdl_init_window( + engine_sdl_state_t* state +) { + state->window = SDL_CreateWindow( + state->window_name, + state->width, state->height, 0 + ); + + if (state->window == NULL) { + eprintf("SDL_CreateWindow failed! %s\n", SDL_GetError()); + SDL_Quit(); + return RCODE_FAILURE; + } + + return RCODE_SUCCESS; +} + +bool +engine_sdl_init_renderer( + engine_sdl_state_t *state +) { + state->renderer = SDL_CreateRenderer(state->window, NULL); + if (state->renderer == NULL) { + eprintf("SDL_CreateRenderer failed! %s\n", SDL_GetError()); + SDL_DestroyWindow(state->window); + SDL_Quit(); + return RCODE_FAILURE; + } + + return RCODE_SUCCESS; +} + +bool +engine_sdl_init_texture( + engine_sdl_state_t *state +) { + state->texture = SDL_CreateTexture( + state->renderer, SDL_PIXELFORMAT_XRGB8888, + SDL_TEXTUREACCESS_STREAMING, + state->width, state->height + ); + if (state->texture == NULL) { + eprintf("SDL_CreateTexture failed! %s\n", SDL_GetError()); + SDL_DestroyWindow(state->window); + SDL_DestroyRenderer(state->renderer); + SDL_Quit(); + return RCODE_FAILURE; + } + + return RCODE_SUCCESS; +} + +engine_sdl_state_t* +engine_sdl_init() +{ + engine_sdl_state_t *state; + + if (!engine_sdl_init_state(&state) || + !engine_sdl_init_root() || + !engine_sdl_init_window(state) || + !engine_sdl_init_renderer(state) || + !engine_sdl_init_texture(state) + ) { + return NULL; + } + + return state; +} + +/** + * only call intentinally, not when engine_sdl_init fails + * -> it cleans up after itself already + */ +void +engine_sdl_destroy( + engine_sdl_state_t *state +) { + SDL_DestroyTexture(state->texture); + SDL_DestroyRenderer(state->renderer); + SDL_DestroyWindow(state->window); + SDL_Quit(); + free(state); +} + +bool +engine_init( + void (*engine_poll_events)(void), + void (*engine_update_framebuffer)(void) +) { + internal_state = (engine_internal_state_t *) + malloc(sizeof(engine_internal_state_t)); + + game_state = (game_state_t *) + malloc(sizeof(game_state_t)); + + internal_state->engine_poll_events + = engine_poll_events; + + internal_state->engine_update_framebuffer + = engine_update_framebuffer; + + internal_state->sdl_state = engine_sdl_init(); + + game_state->is_running = true; + game_state->width = internal_state->sdl_state->width; + game_state->height = internal_state->sdl_state->height; + + return (game_state == NULL || internal_state == NULL) + ? RCODE_FAILURE : RCODE_SUCCESS; +} \ No newline at end of file diff --git a/src/engine/render.c b/src/engine/render.c new file mode 100644 index 0000000..3f07353 --- /dev/null +++ b/src/engine/render.c @@ -0,0 +1,37 @@ +#include "engine.h" +#include "error.h" + +// alter a pixel at an imaginary x & y coordinates +void +put_pixel( + unsigned int x, + unsigned int y, + uint32_t color +) { + uint16_t w = game_state->width; + uint16_t h = game_state->height; + + if (x < 0 || x >= w || y < 0 || y >= h) { + eprintf( + "CRITICAL: attempted buffer overflow in put_pixel\n" + "at x = %d, y = %d", x, y + ); + } else { + game_state->framebuffer[game_state->width * y + x] + = color; + } +} + +// fill the entire framebuffer with a given color +void +clear_framebuffer( + uint32_t color +) { + uint16_t w = game_state->width; + uint16_t h = game_state->height; + + for (int i = 0; i < w * h; i++) { + game_state->framebuffer[i] = color; + } +} + diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..dd79fa2 --- /dev/null +++ b/src/main.c @@ -0,0 +1,66 @@ +#include "engine.h" + +bool left = true; + +// polling events every frame +void +client_poll_event() { + SDL_Event ev = game_state->event; + + if (ev.type == SDL_EVENT_QUIT) { + game_state->is_running = false; + } + + if (ev.type == SDL_EVENT_KEY_DOWN) { + switch (ev.key.key) { + case SDLK_Q: + game_state->is_running = false; + } + } +} + +// performed every frame +void +client_update_framebuffer() { + // draw a green square + // clear_framebuffer(0x000000); + // for (int i = 0; i < 20; i++) { + // for (int j = 0; j < 20; j++) { + // put_pixel(i, j, 0x00FF00); + // } + // } + + // draw alternating green squares + if (left) { + clear_framebuffer(0x000000); + for (int i = 0; i < 20; i++) { + for (int j = 0; j < 20; j++) { + put_pixel(i, j, 0x00FF00); + } + } + left = false; + } else { + clear_framebuffer(0x000000); + for (int i = 20; i < 40; i++) { + for (int j = 20; j < 40; j++) { + put_pixel(i, j, 0x00FFFF); + } + } + left = true; + } + SDL_Delay(100); +} + +int main() { + // initialize the engine + if (engine_init( + client_poll_event, + client_update_framebuffer + )) { + engine_start(); + engine_stop(); + return 0; + } else { + return 1; + } +} \ No newline at end of file