From fef5885293d0d367210612cc6e51dc819b58e0bd Mon Sep 17 00:00:00 2001 From: Quinn Date: Wed, 10 Sep 2025 11:52:58 +0200 Subject: [PATCH] full removal of SDL --- res/sh.frag.glsl | 1 + res/sh.vert.glsl | 1 + src/game/game.c | 76 -------------------- src/game/game.h | 43 ----------- src/game/time.c | 39 ---------- src/game/time.h | 12 ---- src/io/audio.c | 150 --------------------------------------- src/io/audio.h | 33 --------- src/io/colour/colour32.h | 42 ----------- src/io/colour/colour8.h | 53 -------------- src/io/input.c | 88 ++--------------------- src/io/input.h | 22 ++---- src/io/render.c | 133 +++++----------------------------- src/io/render.h | 26 ++----- src/io/shader.c | 50 +++++++++++++ src/io/shader.h | 5 ++ src/io/window.c | 92 ++++++++++++------------ src/io/window.h | 17 ++--- src/main.c | 42 ++++++----- src/util/compat.h | 57 --------------- 20 files changed, 173 insertions(+), 809 deletions(-) create mode 100644 res/sh.frag.glsl create mode 100644 res/sh.vert.glsl delete mode 100644 src/game/game.c delete mode 100644 src/game/game.h delete mode 100644 src/game/time.c delete mode 100644 src/game/time.h delete mode 100644 src/io/audio.c delete mode 100644 src/io/audio.h delete mode 100644 src/io/colour/colour32.h delete mode 100644 src/io/colour/colour8.h create mode 100644 src/io/shader.c create mode 100644 src/io/shader.h delete mode 100644 src/util/compat.h diff --git a/res/sh.frag.glsl b/res/sh.frag.glsl new file mode 100644 index 0000000..5ae7f43 --- /dev/null +++ b/res/sh.frag.glsl @@ -0,0 +1 @@ +#version 330 core diff --git a/res/sh.vert.glsl b/res/sh.vert.glsl new file mode 100644 index 0000000..5ae7f43 --- /dev/null +++ b/res/sh.vert.glsl @@ -0,0 +1 @@ +#version 330 core diff --git a/src/game/game.c b/src/game/game.c deleted file mode 100644 index 0659a53..0000000 --- a/src/game/game.c +++ /dev/null @@ -1,76 +0,0 @@ -#include "game.h" - -#include -#include -#include -#include -#include - -#include "../io/colour/colour8.h" -#include "../io/input.h" -#include "../io/window.h" -#include "../util/types.h" -#include "../util/vec.h" -#include "./tetromino/shapes.h" -#include "tetromino/placing.h" -#include "time.h" - -static colour8 rowdat[COLUMNS * ROWS] = {0}; // contains the raw data of the rows, in no particular order -static struct gamedata dat = {0}; - -/* shuffle an array using the Fisher–Yates shuffle algorithm. - * `nmemb` is the number of members. - * `membs` is the byte size of each member */ -static void shuffle(void *restrict ptr, size_t nmemb, size_t membs) { - u8 dat[membs]; - - for (size_t i = 0; i < nmemb; i++) { - size_t j = i + rand() % (nmemb - i); - void *ptri = (u8 *)ptr + i * membs; - void *ptrj = (u8 *)ptr + j * membs; - memcpy(dat, ptri, membs); - memcpy(ptri, ptrj, membs); - memcpy(ptrj, dat, membs); - } -} - -void next_shape(void) { - // as long as we're not at the last shape, we can just increment - dat.pdat.sel = (i8vec2){COLUMNS / 2 - SHAPE_WIDTH / 2, 0}; - dat.pdat.cur = dat.pdat.nxt[dat.pdat.idx]; - dat.pdat.idx++; - if (dat.pdat.idx < TETC) return; - - // shuffle all next shapes, preserving the last - dat.pdat.idx = 0; - shuffle(dat.pdat.nxt, TETC, sizeof(u8)); -} - -struct gamedata *game_init(void) { - srand(time(NULL)); - - // populate the data arrays - for (int i = 0; i < ROWS; i++) - dat.rows[i] = rowdat + i * COLUMNS; - for (int i = 0; i < TETC; i++) - dat.pdat.nxt[i] = i; - - // initialise the placing data correctly - dat.pdat.sel = (i8vec2){COLUMNS / 2 - SHAPE_WIDTH / 2, 0}; - shuffle(dat.pdat.nxt, TETC, sizeof(u8)); - dat.pdat.cur = dat.pdat.nxt[dat.pdat.idx]; - dat.pdat.idx++; - return &dat; -} - -// called every time the game's state is updated -void game_update(int movdat, size_t time) { - static time_t drop_timeout = 0; - movdat |= MOVD & -!!time_poll(time, 200, &drop_timeout); - - if (place_update(&dat, movdat)) - window_close(); -} - -void game_free(void) { -} diff --git a/src/game/game.h b/src/game/game.h deleted file mode 100644 index 616ccdc..0000000 --- a/src/game/game.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once -#include -#include -#include -#include - -#include "../io/colour/colour8.h" -#include "../util/types.h" -#include "../util/vec.h" -#include "tetromino/shapes.h" - -// constants for pi(π) and tau(τ) -#define PI (M_PI) // π constant -#define TAU (M_PI * 2.0) // τ constant -#define PIf (M_PIf) // π constant as a 32-bit floating point -#define TAUf (M_PIf * 2.0F) // τ constant as a 32-bit floating point - -// stores the data used in the game -#define COLUMNS 10 -#define ROWS 24 - -/* contains the placement data */ -struct pdat { - u8 nxt[TETC]; // shuffled data representing the next shapes - i8vec2 sel; // position of the current shape - u8 idx; // the index of the current shape - u8 cur; // the current id of the shape -}; - -/* contains game data that's commonly shared */ -struct gamedata { - struct pdat pdat; - colour8 *rows[ROWS]; - u16 pnts; -}; - - -/* increments to the next shape, shuffling the next shapes, if there isn't a next shape immediately after the current one. */ -void next_shape(void); - -struct gamedata *game_init(void); -void game_update(int movdat, size_t time); -void game_free(void); diff --git a/src/game/time.c b/src/game/time.c deleted file mode 100644 index 9174b98..0000000 --- a/src/game/time.c +++ /dev/null @@ -1,39 +0,0 @@ -#include "time.h" - -#include -#include - -#if __has_include() -#include -#endif -#if __has_include() && _POSIX_C_SOURCE >= 199309L -#include -static void gettime(struct timespec *ts) { - clock_gettime(CLOCK_MONOTONIC, ts); -} -#elif defined(_WIN32) -#include -#include -#include -static void gettime(struct timespec *ts) { - LARGE_INTEGER cnt, frq; - QueryPerformanceCounter(&cnt); - QueryPerformanceFrequency(&frq); - ts->tv_sec = (time_t)(cnt.QuadPart / frq.QuadPart); - ts->tv_nsec = (time_t)((cnt.QuadPart % frq.QuadPart) * 1000000000 / frq.QuadPart); -} -#else -#error no implementation of a monotonic clock was available -#endif - -time_t time_pull(void) { - struct timespec ts; - gettime(&ts); - return ts.tv_sec * 1000 + ts.tv_nsec / 1000000; -} - -int time_poll(time_t curr, time_t delta, time_t *restrict proj) { - bool tpass = curr >= *proj; - *proj += tpass * ((curr + delta) - *proj); // adds 0, or the difference to proj - return tpass; -} diff --git a/src/game/time.h b/src/game/time.h deleted file mode 100644 index e1212ea..0000000 --- a/src/game/time.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include -#include - -/* gets the current time in milliseconds */ -time_t time_pull(void); - -/* Polls the time whether a given timeout has passed, comparing against `curr` as the current time. - * if `curr` ≥ `*proj`, `curr` + `delta` is written to `*proj`. `1` is returned. - * otherwise, we just return `0`. */ -__nonnull((3)) int time_poll(time_t curr, time_t delta, time_t *restrict proj); diff --git a/src/io/audio.c b/src/io/audio.c deleted file mode 100644 index 2b233f5..0000000 --- a/src/io/audio.c +++ /dev/null @@ -1,150 +0,0 @@ -#include "audio.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "../error.h" -#include "../util/compat.h" -#include "../util/types.h" - -struct audioplayer { - const u8 *buf; - int len; -}; - -static struct audiodevice { - struct audioplayer audio[AUDIO_MAX]; - u32 id; - SDL_AudioSpec spec; -} dev; - -struct audiodata audio_dat[AUDIO_ID_COUNT] = {0}; // contains pointers to audio buffers. - -static const char *const audio_path[AUDIO_ID_COUNT] = { - "korobeiniki.wav", - "place.wav", -}; - -/* mixes the audio output stream, using the different audio as sources */ -static void audiomixer(void *const userdata, u8 *const stream, const int len) { - (void)userdata; - memset(stream, 0, len); // clear the playing audio - - // update the counts for the audio array - for (unsigned i = 0; i < AUDIO_MAX; i++) { - if (dev.audio[i].len > 0) { - unsigned mixlen = SDL_min(len, dev.audio[i].len); - SDL_MixAudioFormat(stream, dev.audio[i].buf, dev.spec.format, mixlen, SDL_MIX_MAXVOLUME); - dev.audio[i].len -= mixlen; - dev.audio[i].buf += mixlen; - } - } -} - -/* converts input audio buffer to one that matches the buffer of dev. - * `len` is a pointer to the current size, the new size will be written to this location. - * returns the pointer to the audio buffer to use, or NULL, when something went wrong. - * NULL will never be returned after the conversion */ -static u8 *audio_cvt(const SDL_AudioSpec *spec, u8 *bufptr, unsigned *len) { - if (!bufptr) return NULL; - - // init the converter - SDL_AudioCVT cvt; - if (SDL_BuildAudioCVT(&cvt, spec->format, spec->channels, spec->freq, dev.spec.format, dev.spec.channels, dev.spec.freq) < 0) { - error("could not build the audio converter! SDL Error: %s", SDL_GetError()); - return NULL; - } - if (!cvt.needed) return bufptr; // ensure the conversion is necessary - - // grow the inputted buffer for the conversion - cvt.len = *len; - cvt.buf = realloc(bufptr, cvt.len * cvt.len_mult); - if (!cvt.buf) { - warn("failed to grow the audio buffer to the new size of %u bytes! audio may be bugged", cvt.len); - return bufptr; - } - - // convert the audio to the new format - if (SDL_ConvertAudio(&cvt) < 0) { - warn("couldn't convert an audio buffer to the set format, audio may be bugged! SDL Error: %s", SDL_GetError()); - assert(cvt.buf); - return cvt.buf; - } - assert(cvt.buf); - - // shrink to reduce memory footprint - *len = cvt.len_cvt; - bufptr = realloc(cvt.buf, *len); - return bufptr ? bufptr : cvt.buf; -} - -/* computes the time in milliseconds of the audio fragment by dividing - * the audio byte length by the format's bit size, which is divided by - * the audio device's channels and frequency */ -static inline u32 audio_btoms(u32 len) { - return (((1000 * len) / (SDL_AUDIO_BITSIZE(dev.spec.format) / 8)) / dev.spec.channels / dev.spec.freq); -} - -/* loads a `struct audiodata` from `fpat` to `out`. */ -static void audio_wav_load(const char *restrict fpat, struct audiodata *restrict out) { - debug("loading audio file '%s'...", fpat); - if (faccess(fpat, FA_R)) { - error("audio file either isn't readable or doesn't exist. path: '%s'!", fpat); - return; - } - - // load the audio - u32 len; - u8 *ptr, *tmp; - SDL_AudioSpec spec; - SDL_LoadWAV(fpat, &spec, &ptr, &len); - - // convert the audio data to the format reflecting dev - tmp = audio_cvt(&spec, ptr, &len); - if (!tmp) free(ptr); // free the buffer if NULL was returned; failure - - *out = (struct audiodata){tmp, len, audio_btoms(len)}; -} - -/* loads the audio data into the buffer */ -static inline void audio_load(void) { - for (size_t i = 0; i < AUDIO_ID_COUNT; i++) - audio_wav_load(audio_path[i], &audio_dat[i]); -} - -void audio_play(u32 audio_id) { - if (!dev.id) return; - size_t i = 0; - while (i < AUDIO_MAX && dev.audio[i].len > 0) i++; - if (i >= AUDIO_MAX) return; - - dev.audio[i] = (struct audioplayer){ - audio_dat[audio_id].buf, - audio_dat[audio_id].len, - }; -} - -int audio_init(int freq, SDL_AudioFormat fmt, u8 ch, u16 samples) { - // initialise the audio device + specification - SDL_AudioSpec spec = {freq, fmt, ch, 0, samples, 0, 0, audiomixer, NULL}; - unsigned id = SDL_OpenAudioDevice(NULL, 0, &spec, &spec, 0); - dev = (struct audiodevice){{0}, id, spec}; - - if (!id) return 1; - SDL_PauseAudioDevice(id, 0); - audio_load(); - return 0; -} - -void audio_free(void) { - SDL_CloseAudioDevice(dev.id); - dev = (struct audiodevice){0}; - - for (size_t i = 0; i < AUDIO_ID_COUNT; i++) - free((void *)audio_dat[i].buf); -} diff --git a/src/io/audio.h b/src/io/audio.h deleted file mode 100644 index 1fd238e..0000000 --- a/src/io/audio.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include -#include - -#include "../util/types.h" - -#define AUDIO_MAX 4 // maximum number of sound effects that are allowed to play at once - -struct audiodata { - const u8 *buf; // pointer to the audio buffer - u32 len; // length in bytes of the audio buffer - u32 ms; // length in miliseconds of the audio buffer -}; - -enum audio_id { - AUDIO_ID_MUSIC, - AUDIO_ID_PLACE, - - // leave at end, will contain count - AUDIO_ID_COUNT, -}; - -extern struct audiodata audio_dat[AUDIO_ID_COUNT]; - -/* loads the audio to be played, unless `AUDIO_MAX` has been reached, then this call will fail */ -void audio_play(u32); - -/* initialises the audio device, then loads the audio data */ -int audio_init(int freq, SDL_AudioFormat fmt, u8 ch, u16 samples); - -/* frees up resources held by the audio device and audio buffers */ -void audio_free(void); diff --git a/src/io/colour/colour32.h b/src/io/colour/colour32.h deleted file mode 100644 index e28e298..0000000 --- a/src/io/colour/colour32.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once -#include - -#include "SDL_render.h" - -// stores colour in a rgba format, each channel being a 8 bits wide. -typedef union { - uint32_t packed; - struct { - uint8_t a; - uint8_t b; - uint8_t g; - uint8_t r; - }; -} colour32; - -#define COLOUR32_BLACK ((colour32){0x000000FF}) -#define COLOUR32_RED ((colour32){0xFF0000FF}) -#define COLOUR32_YELLOW ((colour32){0xFFFF00FF}) -#define COLOUR32_ORANGE ((colour32){0xFF6D00FF}) -#define COLOUR32_GREEN ((colour32){0x00FF00FF}) -#define COLOUR32_CYAN ((colour32){0x00FFFFFF}) -#define COLOUR32_BLUE ((colour32){0x0000FFFF}) -#define COLOUR32_MAGENTA ((colour32){0xFF00FFFF}) -#define COLOUR32_WHITE ((colour32){0xFFFFFFFF}) - -// sets the render colour to a colour32 value -static inline void set_colour32(SDL_Renderer *const renderer, const colour32 c) { - (void)SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, c.a); -} - -// american macros: -#define color32 colour32 -#define COLOR32_BLACK COLOUR32_BLACK -#define COLOR32_RED COLOUR32_RED -#define COLOR32_YELLOW COLOUR32_YELLOW -#define COLOR32_ORANGE COLOUR32_ORANGE -#define COLOR32_GREEN COLOUR32_GREEN -#define COLOR32_CYAN COLOUR32_CYAN -#define COLOR32_BLUE COLOUR32_BLUE -#define COLOR32_MAGENTA COLOUR32_MAGENTA -#define COLOR32_WHITE COLOUR32_WHITE diff --git a/src/io/colour/colour8.h b/src/io/colour/colour8.h deleted file mode 100644 index 83d703c..0000000 --- a/src/io/colour/colour8.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once -#include - -#include "SDL_render.h" - -// stores colour in a rrrgggbb format, which maps exactly to 8 bits -typedef uint8_t colour8; - -/* rrrg ggbb */ -#define COLOUR8_BLACK ((colour8)0x00) // 0000 0000 -#define COLOUR8_RED ((colour8)0xE0) // 1110 0000 -#define COLOUR8_YELLOW ((colour8)0xFC) // 1111 1100 -#define COLOUR8_ORANGE ((colour8)0xEC) // 1111 1100 -#define COLOUR8_GREEN ((colour8)0x1C) // 0001 1100 -#define COLOUR8_CYAN ((colour8)0x1F) // 0001 1111 -#define COLOUR8_BLUE ((colour8)0x03) // 0000 0011 -#define COLOUR8_MAGENTA ((colour8)0xE3) // 1110 0011 -#define COLOUR8_WHITE ((colour8)0xFF) // 1111 1111 - -// gets the red channel in 32 bit colour space -static inline uint8_t colour8_red32(const colour8 colour) { - return (colour >> 5) * (255 / 7); -} - -// gets the green channel in 32 bit colour space -static inline uint8_t colour8_green32(const colour8 colour) { - return ((colour >> 2) & 7) * (255 / 7); -} - -// gets the blue channel in 32 bit colour space -static inline uint8_t colour8_blue32(const colour8 colour) { - return (colour & 3) * (255 / 3); -} - -// sets the render colour to a colour8 value -static inline void set_colour8(SDL_Renderer *const renderer, const colour8 c) { - (void)SDL_SetRenderDrawColor(renderer, colour8_red32(c), colour8_green32(c), colour8_blue32(c), 0xFF); -} - -// american macros: -#define color8 colour8 -#define color8_red32 colour8_red32 -#define color8_green32 colour8_green32 -#define color8_blue32 colour8_blue32 -#define COLOR8_BLACK COLOUR8_BLACK -#define COLOR8_RED COLOUR8_RED -#define COLOR8_YELLOW COLOUR8_YELLOW -#define COLOR8_ORANGE COLOUR8_ORANGE -#define COLOR8_GREEN COLOUR8_GREEN -#define COLOR8_CYAN COLOUR8_CYAN -#define COLOR8_BLUE COLOUR8_BLUE -#define COLOR8_MAGENTA COLOUR8_MAGENTA -#define COLOR8_WHITE COLOUR8_WHITE diff --git a/src/io/input.c b/src/io/input.c index 8aaffc2..15424d0 100644 --- a/src/io/input.c +++ b/src/io/input.c @@ -1,82 +1,8 @@ -#include "input.h" - -#include -#include -#include - -#include "../game/time.h" -#include "../util/types.h" -#include "window.h" - -/* processes an incoming scancode, returns the associated movement data, or performs the close action directly - * NOTE: if the action is mapped to multiple keys, pressing both and then releasing one, disables the action. Minor issue, won't fix. */ -__attribute__((const)) static int procscancode(SDL_Scancode code) { - switch (code) { - case SDL_SCANCODE_Q: - return MOVRL; - - case SDL_SCANCODE_E: - return MOVRR; - - case SDL_SCANCODE_LEFT: - case SDL_SCANCODE_A: - case SDL_SCANCODE_H: - return MOVL; - - case SDL_SCANCODE_RIGHT: - case SDL_SCANCODE_D: - case SDL_SCANCODE_L: - return MOVR; - - case SDL_SCANCODE_DOWN: - case SDL_SCANCODE_S: - case SDL_SCANCODE_J: - case SDL_SCANCODE_SPACE: - return MOVD; - - case SDL_SCANCODE_ESCAPE: - window_close(); - return 0; - default: return 0; - } -} - -static int timeout_mask(time_t time) { - static time_t timeout = 0, timeout_roll = 0; - int msk = 0; - // only add to the mask if time_poll returns `1`, negating becomes `-1`; 0b1111... - // this is masked with the desired movement action. - msk |= ((MOVR | MOVL | MOVD) & -!!time_poll(time, 64, &timeout)); - msk |= ((MOVRL | MOVRR) & -!!time_poll(time, 100, &timeout_roll)); - return msk; -} - -int input_getdat(time_t time) { - static u8 movdat = 0, nmovdat = 0, lmovdat = 0; // stores the static movement data - int mov = movdat, nmov = nmovdat, lmov = lmovdat; // stores the runtime movement data for easy register access - - // process the events - SDL_Event e; - while (SDL_PollEvent(&e)) { - switch (e.type) { - case SDL_QUIT: window_close(); break; - case SDL_KEYDOWN: mov |= procscancode(e.key.keysym.scancode); break; - case SDL_KEYUP: nmov |= procscancode(e.key.keysym.scancode); break; - } - } - - // compute the current movement - int mask = timeout_mask(time); - - // handle releasing of keys - mov &= ~(nmov & lmov & mask); // only remove the keys that have been pressed since lmov - lmov = (mov & mask) | (lmov & ~mask); // set the value of lmov to the new value mov - nmov &= mov; // set nmov to only those in mov - int cmov = mov & mask; - - // write to static variables (shrinking the values, and memory usage) - movdat = mov; - lmovdat = lmov; - nmovdat = nmov; - return cmov; +#include +void key_callback(GLFWwindow *win, int key, int scancode, int action, int mods) { + (void)win, (void)key, (void)scancode, (void)action, (void)mods; +#ifndef NDEBUG + if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) + glfwSetWindowShouldClose(win, 1); +#endif } diff --git a/src/io/input.h b/src/io/input.h index a28da16..fa08c71 100644 --- a/src/io/input.h +++ b/src/io/input.h @@ -1,17 +1,5 @@ -#pragma once - -#include - -/* 8 bit enumeration storing the movement data */ -enum movdat { - MOVL = 1, // move left - MOVR = 2, // move right - MOVD = 4, // move down - MOVRL = 8, // move roll left - MOVRR = 16, // move roll right - MOVPL = 32, // move place -}; - -/* returns an OR'd string from `enum movdat`, containing the movement data. - * assumes that SDL has been initialized and a window has successfully been created. */ -__attribute__((__pure__)) int input_getdat(time_t time); +#ifndef INPUT_H +#define INPUT_H 1 +#include +void key_callback(GLFWwindow *win, int key, int scancode, int action, int mods); +#endif diff --git a/src/io/render.c b/src/io/render.c index 449c324..e2e6081 100644 --- a/src/io/render.c +++ b/src/io/render.c @@ -1,125 +1,30 @@ #include "render.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include -#include "../error.h" -#include "../game/game.h" -#include "../game/tetromino/shapes.h" -#include "../util/types.h" -#include "../util/vec.h" -#include "colour/colour32.h" -#include "colour/colour8.h" +#include -#define COLOUR_SCORE COLOUR32_YELLOW +#include "shader.h" +#include "../util/error.h" -SDL_Renderer *rend = NULL; -TTF_Font *font = NULL; -struct gamedata const *gdat = NULL; +GLuint pipe; -static SDL_Surface *score_surface = NULL; -static SDL_Texture *score_texture = NULL; +int render_init(void) { + pipe = glCreateProgram(); + shader_init(pipe); + glLinkProgram(pipe); + glValidateProgram(pipe); -static inline i32 colpos(uint column) { - return column * BLOCK_WIDTH + 1 + TET_PADDING; -} - -static inline i32 rowpos(uint row) { - return row * BLOCK_HEIGHT + 1 + TET_PADDING; -} - -static void draw_score_text(void) { - static u16 prev_pts = 0xFFFF; // initialise to maximum, so the code below is triggered on first run - - if (prev_pts ^ gdat->pnts) { - char score_text[6] = "0"; // max (base 10) digits of a u16 + null terminator - prev_pts = gdat->pnts; - if (gdat->pnts) sprintf(score_text, "%hu0", gdat->pnts); - - SDL_FreeSurface(score_surface); - SDL_DestroyTexture(score_texture); - score_surface = TTF_RenderText_Solid(font, score_text, (SDL_Colour){COLOUR_SCORE.r, COLOUR_SCORE.g, COLOUR_SCORE.b, COLOUR_SCORE.a}); - score_texture = SDL_CreateTextureFromSurface(rend, score_surface); + int len; + glGetProgramiv(pipe, GL_INFO_LOG_LENGTH, &len); + if (len > 0) { + char log[len]; + glGetProgramInfoLog(pipe, len, &len, log); + log[len - 1] = '\0'; // terminate the string one character sooner since the log includes a newline + fatal("error whilst linking the pipe: '%s'", log); } - - assert(score_surface && score_texture); - SDL_Rect text_rect = {colpos(COLUMNS + 1), rowpos(0), score_surface->w, score_surface->h}; - SDL_RenderCopy(rend, score_texture, NULL, &text_rect); + return 1; } -static inline int draw_block(SDL_Renderer *const renderer, i8vec2 pos) { - const SDL_Rect block = {colpos(pos[VX]), rowpos(pos[VY]), BLOCK_WIDTH - 1, BLOCK_HEIGHT - 1}; - return SDL_RenderFillRect(renderer, &block); -} - -static void draw_shape(const u8 id, i8vec2 pos) { - set_colour8(rend, colour_from_id(id)); - i8vec2 bpos[4]; - shape_getblocks(id, bpos); - draw_block(rend, pos + bpos[0]); - draw_block(rend, pos + bpos[1]); - draw_block(rend, pos + bpos[2]); - draw_block(rend, pos + bpos[3]); -} - -static void render_level(void) { - for (int y = 0; y < ROWS; y++) { - const u8 *row = gdat->rows[y]; - - for (int x = 0; x < COLUMNS; x++) { - if (row[x] != 0) { - set_colour8(rend, row[x]); - draw_block(rend, (i8vec2){x, y}); - } - } - } -} - -void render_init(SDL_Window *win, struct gamedata const *game_data) { - rend = SDL_CreateRenderer(win, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED); - if (rend == NULL) fatal(ERROR_SDL_RENDERING_INIT, "Renderer failed to be created! SDL Error: %s", SDL_GetError()); - - font = TTF_OpenFont("pixeldroid_botic-regular.ttf", PX_DENS); - if (font == NULL) error("Failed to open font! TTF Error: %s", TTF_GetError()); - - gdat = game_data; -} - -void render_update(void) { - set_colour32(rend, COLOUR32_BLACK); - SDL_RenderClear(rend); - set_colour32(rend, COLOUR32_WHITE); - - static const SDL_Rect field_size = {TET_PADDING, TET_PADDING, TET_WIDTH + 1, TET_HEIGHT + 1}; - SDL_RenderDrawRect(rend, &field_size); - - if (font) draw_score_text(); - - render_level(); - draw_shape(gdat->pdat.cur, gdat->pdat.sel); - draw_shape(gdat->pdat.nxt[gdat->pdat.idx], (i8vec2){COLUMNS + 1, 3}); - - SDL_RenderPresent(rend); -} - -void render_free(void) { - assert(rend); - SDL_DestroyRenderer(rend); - rend = NULL; - - TTF_CloseFont(font); - SDL_FreeSurface(score_surface); - SDL_DestroyTexture(score_texture); - font = NULL; - score_surface = NULL; - score_texture = NULL; +void render_update(GLFWwindow *win) { } diff --git a/src/io/render.h b/src/io/render.h index 122f5fe..6224f29 100644 --- a/src/io/render.h +++ b/src/io/render.h @@ -1,20 +1,8 @@ -#pragma once +#ifndef RENDER_H +#define RENDER_H 1 +#include +#include -#include -#include -#include - -#include "../game/game.h" - -#define PX_DENS 25 // pixel density; pixels per block -#define TET_PADDING 10 // padding around the tetris playing field -#define TET_WIDTH (COLUMNS * PX_DENS - TET_PADDING) // tetris playing field width -#define TET_HEIGHT (TET_WIDTH / COLUMNS * ROWS) // tetris playing field height -#define SCREEN_WIDTH ((COLUMNS + 6) * PX_DENS) // window width -#define SCREEN_HEIGHT ((COLUMNS) * PX_DENS / COLUMNS * ROWS) // window height -#define BLOCK_WIDTH (TET_WIDTH / COLUMNS) // width of a block -#define BLOCK_HEIGHT (TET_HEIGHT / ROWS) // height of a block - -__nonnull((1, 2)) void render_init(SDL_Window *, struct gamedata const *); -void render_update(void); // causes a draw to occur, will also determine update rate -void render_free(void); // frees the memory allocated to the renderer in render_data +int render_init(void); +void render_update(GLFWwindow *win); +#endif diff --git a/src/io/shader.c b/src/io/shader.c new file mode 100644 index 0000000..d7a073c --- /dev/null +++ b/src/io/shader.c @@ -0,0 +1,50 @@ +#include "shader.h" + +#include +#include + +#include "../util/error.h" + + +/* for compiling a shader */ +static GLuint shader_compile(GLenum type, const char *src, size_t len) { + int ilen = len; + GLuint shader = glCreateShader(type); + glShaderSource(shader, 1, &src, &ilen); + glCompileShader(shader); + + /* Repurposing ilen for the max length of the shader log */ + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &ilen); + if (ilen > 0) { + char log[ilen]; + glGetShaderInfoLog(shader, ilen, &ilen, log); + log[ilen - 1] = '\0'; // terminate the string one character sooner since the log includes a newline + error("error whilst compiling shader type '0X%X': '%s'", type, log); + } + + return shader; +} + +#define NAME_START(name) _binary_res_##name##_start /* name of a start variable */ +#define NAME_END(name) _binary_res_##name##_end /* name of an end variable */ + +/* wrapper for calling `shader_compile`, but with some more sane parameters. */ +#define ADD_SHADER(_type, _name, _store) \ + do { \ + extern const char NAME_START(_name)[]; \ + extern const char NAME_END(_name)[]; \ + (_store) = shader_compile(_type, NAME_START(_name), (size_t)NAME_END(_name) - (size_t)NAME_START(_name)); \ + } while (0) + +int shader_init(GLuint pipe) { + GLuint vs, fs; + ADD_SHADER(GL_VERTEX_SHADER, sh_vert_glsl, vs); + ADD_SHADER(GL_FRAGMENT_SHADER, sh_frag_glsl, fs); + + glAttachShader(pipe, vs); + glAttachShader(pipe, fs); + + glDeleteShader(vs); + glDeleteShader(fs); + return 1; +} diff --git a/src/io/shader.h b/src/io/shader.h new file mode 100644 index 0000000..75c8513 --- /dev/null +++ b/src/io/shader.h @@ -0,0 +1,5 @@ +#ifndef SHADER_H +#define SHADER_H 1 +#include +int shader_init(GLuint pipe); +#endif diff --git a/src/io/window.c b/src/io/window.c index 60d5ce4..0b94e6f 100644 --- a/src/io/window.c +++ b/src/io/window.c @@ -1,60 +1,64 @@ #include "window.h" -#include -#include -#include -#include #include -#include +#include +#include -#include "../error.h" -#include "../game/game.h" -#include "../game/time.h" -#include "SDL_ttf.h" -#include "audio.h" +#include "../tetris.h" +#include "../util/error.h" #include "input.h" #include "render.h" -static SDL_Window *win = NULL; -static bool close = false; +#define WIN_DPI 32 +#define WIN_WIDTH (WIN_DPI * (TET_WIDTH + 3)) +#define WIN_HEIGHT (WIN_DPI * TET_HEIGHT) -void window_init(struct gamedata const *gdat) { - assert(!win && !close); - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) - fatal(ERROR_SDL_INIT, "SDL could not initialize! SDL Error: %s", SDL_GetError()); - if (TTF_Init() < 0) - fatal(ERROR_SDL_INIT, "TTF could not initialize! TTF Error: %s", TTF_GetError()); +static GLFWwindow *win = NULL; - win = SDL_CreateWindow("tetris clone", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN); - if (win == NULL) - fatal(ERROR_SDL_RENDERING_INIT, "Window failed to be created! SDL Error: %s", SDL_GetError()); +int window_init(void) { +#ifndef NDEBUG + glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); +#endif + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); + glfwWindowHint(GLFW_RED_BITS, 3); + glfwWindowHint(GLFW_GREEN_BITS, 3); + glfwWindowHint(GLFW_BLUE_BITS, 2); + glfwWindowHint(GLFW_ALPHA_BITS, 0); + win = glfwCreateWindow(WIN_WIDTH, WIN_HEIGHT, "Quinn's tetris", NULL, NULL); + if (!win) return 1; - render_init(win, gdat); - audio_init(32000, AUDIO_S16, 1, 4096); + glfwMakeContextCurrent(win); + if (!gladLoadGL(glfwGetProcAddress)) return 1; + glfwSwapInterval(1); /* Wait one screen update for redraw. A.k.a. "vsync" */ + + glfwSetKeyCallback(win, key_callback); + + debug( + "version info:\n" + "\tvendor: %s\n" + "\trenderer: %s\n" + "\tversion: %s\n" + "\tshading lang: %s\n", + glGetString(GL_VENDOR), + glGetString(GL_RENDERER), + glGetString(GL_VERSION), + glGetString(GL_SHADING_LANGUAGE_VERSION)); + + return 0; //render_init(); } -void window_free(void) { - assert(win); - render_free(); - SDL_DestroyWindow(win); - win = NULL; - close = false; +void window_loop(void) { + assert(win != NULL); + while (!glfwWindowShouldClose(win)) { + glfwWaitEventsTimeout(1.0 / 60.0); - audio_free(); -} - -void window_open(void) { - while (!close) { - size_t time = time_pull(); - game_update(input_getdat(time), time); - render_update(); - - static time_t timeout = 0; - if (time_poll(time, audio_dat[AUDIO_ID_MUSIC].ms, &timeout)) - audio_play(AUDIO_ID_MUSIC); + // render_update(win); + glfwSwapBuffers(win); } -} -void window_close(void) { - close = true; + glfwDestroyWindow(win); } diff --git a/src/io/window.h b/src/io/window.h index b67c7da..ac0e2fd 100644 --- a/src/io/window.h +++ b/src/io/window.h @@ -1,12 +1,5 @@ -#pragma once - -#include "../game/game.h" - -#define PX_DENS 25 // pixel density; pixels per block -#define SCREEN_WIDTH ((COLUMNS + 6) * PX_DENS) // window width -#define SCREEN_HEIGHT ((COLUMNS) * PX_DENS / COLUMNS * ROWS) // window height - -void window_init(struct gamedata const *); -void window_open(void); -void window_close(void); -void window_free(void); +#ifndef WINDOW_H +#define WINDOW_H 1 +int window_init(void); +void window_loop(void); +#endif diff --git a/src/main.c b/src/main.c index a1f74c5..94023ce 100644 --- a/src/main.c +++ b/src/main.c @@ -1,25 +1,33 @@ -#include +#include +#include #include - -#include "error.h" -#include "game/game.h" #include "io/window.h" +#include "util/error.h" -static void stop(void) { - debug("stopping...", ); - window_close(); - window_free(); - SDL_Quit(); +/* callback for GLFW errors */ +static void error_callback(int err, const char *const msg) { + error("glfw (%i); \"%s\"", err, msg); } -// entry-point of the application -int main(int argc, char **argv) { - (void)argc, (void)argv; - // register stop as exit function - atexit(stop); - - window_init(game_init()); - window_open(); +static int init(void) { + glfwSetErrorCallback(error_callback); + glfwInitHint(GLFW_JOYSTICK_HAT_BUTTONS, GLFW_FALSE); // disable joystick buttons + if (!glfwInit()) return 1; + if (window_init()) return 1; return 0; } + +void quit(void) { + glfwTerminate(); +} + +int main(int argc, char **argv) { + (void)argc, (void)argv; + printf("debug: [DBG], info: [INF], warning: [WAR], error: [ERR], fatal: [FAT]\n"); + atexit(quit); + if (init()) fatal("failed to initialise the programme!"); + window_loop(); + quit(); + return EXIT_SUCCESS; +} diff --git a/src/util/compat.h b/src/util/compat.h deleted file mode 100644 index bece763..0000000 --- a/src/util/compat.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -#if defined __unix__ -#include -#include -#elif defined _WIN32 -#include -#else -#error platform not supported! -#endif - -#ifdef _WIN32 -#define PATH_SEP '\\' // contains the path separator as a character. Yes it is extremely annoying that this has to exist. -#define PATH_SEP_STR "\\" // contains the path separator as a string, useful for concatenation. Yes it is extremely annoying that this has to exist. - -#define unixonly(_exec) // (no-op) executes inline code when __unix__ is defined, otherwise is no-op -#define winonly(_exec) _exec // executes inline code when _WIN32 is defined, otherwise is no-op -#else -#define PATH_SEP '/' // contains the path separator as a character. Yes it is extremely annoying that this has to exist. -#define PATH_SEP_STR "/" // contains the path separator as a string, useful for concatenation. Yes it is extremely annoying that this has to exist. - -#define unixonly(_exec) _exec // executes inline code when __unix__ is defined, otherwise is no-op -#define winonly(_exec) // (no-op) executes inline code when _WIN32 is defined, otherwise is no-op -#endif - -// define the constants if they haven't been -#ifndef F_OK -#define F_OK 0 -#endif -#ifndef X_OK -#define X_OK 1 -#endif -#ifndef W_OK -#define W_OK 2 -#endif -#ifndef R_OK -#define R_OK 4 -#endif - -enum faccess_perms { - FA_F = F_OK, // test for file's existence - FA_X = X_OK, // test for executing permission - FA_W = W_OK, // test for write permissions - FA_R = R_OK, // test for read permissions -}; - -/* tests a files access with F_OK, X_OK, R_OK, W_OK OR'd together - returns 0 upon success. -1 when errno is set and anything else when one or more of the permissions isn't set */ -static inline int faccess(const char *restrict fname, int perms) { -#if defined __unix__ && _POSIX_C_SOURCE >= 200809L - return access(fname, perms); -#elif defined _WIN32 - return _access(fname, perms); -#else -#error platform unsupported! -#endif -}