suwi/src/main.c++

313 lines
10 KiB
C++

#include "glad/gles2.h"
#include <SDL3/SDL.h>
#define SDL_MAIN_USE_CALLBACKS 1
#include <SDL3/SDL_main.h>
#include <unordered_map>
#include <string>
#include <cstdio>
#include <ctime>
#include "batch.h"
#include "assets.h++"
#include "main.h++"
#define WINDOW_VIRTUAL 128 // screen buffer, ideally with some padding for the offscreen
#define WINDOW_WIDTH 128 // viewport width
#define WINDOW_HEIGHT 128 // viewport height
#define WINDOW_DENOM 128 // highest common denominator of width and height, for square pixel purposes
#define BATCH_SIZE 1024
SDL_Window *window;
int16_t window_width = WINDOW_WIDTH, window_height = WINDOW_HEIGHT;
SDL_GLContext context;
std::unordered_map<std::string, unsigned> programs;
std::unordered_map<std::string, struct texture> textures;
GLuint framebuffer = 0;
bool square_pixels = true, integer_upscale = false;
static int framebuffer_init(void);
SDL_AppResult SDL_AppInit(void **, int argc, char **argv) {
clock_t begin = clock();
SDL_Init(SDL_INIT_VIDEO);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_SetHint(SDL_HINT_MAIN_CALLBACK_RATE, "60");
window = SDL_CreateWindow("suwi", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_TRANSPARENT);
SDL_SetWindowMinimumSize(window, WINDOW_WIDTH, WINDOW_HEIGHT);
context = SDL_GL_CreateContext(window);
int version = gladLoadGLES2((GLADloadfunc) SDL_GL_GetProcAddress);
if (version == 0) {
puts("error: GLES2 failed to initialize");
return SDL_APP_FAILURE;
}
fprintf(stderr, "info: GLES%u.%u\n", GLAD_VERSION_MAJOR(version), GLAD_VERSION_MINOR(version));
FILE *file;
if (argc < 2) {
std::string path = argv[0];
auto s = path.rfind('.');
if (s != path.npos) {
path.erase(s);
}
path.append(".assets");
file = fopen(path.c_str(), "rb");
if (file == NULL) {
perror(path.c_str());
return SDL_APP_FAILURE;
}
if (assets_load(file, path.c_str())) {
return SDL_APP_FAILURE;
}
} else {
file = fopen(argv[1], "rb");
if (file == NULL) {
perror(argv[1]);
return SDL_APP_FAILURE;
}
if (assets_load(file, argv[1])) {
return SDL_APP_FAILURE;
}
}
fclose(file);
batch_init(BATCH_SIZE, programs.at("spr2D"));
framebuffer_init();
SDL_ShowWindow(window);
fprintf(stderr, "info: init took %lu ms\n", ((clock() - begin) * 1000) / CLOCKS_PER_SEC);
return SDL_APP_CONTINUE;
}
SDL_AppResult SDL_AppEvent(void *, SDL_Event *evt) {
switch (evt->type) {
case SDL_EVENT_QUIT:
return SDL_APP_SUCCESS;
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
if (!evt->key.repeat && evt->key.down == true) {
if (evt->key.scancode == SDL_SCANCODE_1) {
square_pixels = !square_pixels;
} else if (evt->key.scancode == SDL_SCANCODE_2) {
integer_upscale = !integer_upscale;
}
}
break;
case SDL_EVENT_MOUSE_MOTION:
case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP:
case SDL_EVENT_MOUSE_WHEEL:
break;
case SDL_EVENT_WINDOW_SHOWN:
case SDL_EVENT_WINDOW_HIDDEN:
case SDL_EVENT_WINDOW_MINIMIZED:
case SDL_EVENT_WINDOW_MAXIMIZED:
case SDL_EVENT_WINDOW_RESTORED:
case SDL_EVENT_WINDOW_MOVED:
break;
case SDL_EVENT_WINDOW_RESIZED:
window_width = evt->window.data1;
window_height = evt->window.data2;
break;
case SDL_EVENT_WINDOW_MOUSE_ENTER:
case SDL_EVENT_WINDOW_MOUSE_LEAVE:
case SDL_EVENT_WINDOW_FOCUS_GAINED:
case SDL_EVENT_WINDOW_FOCUS_LOST:
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
break;
case SDL_EVENT_WINDOW_EXPOSED:
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
case SDL_EVENT_WINDOW_SAFE_AREA_CHANGED:
break;
case SDL_EVENT_CLIPBOARD_UPDATE:
break;
default:
fprintf(stderr, "info: unknown event %x\n", evt->type);
}
return SDL_APP_CONTINUE;
}
static struct {
GLuint vbo, program;
GLint aVertCoord, aTexCoord, uTex, uBackGround, uForeGround;
GLuint color, depth, stencil;
} fb;
SDL_AppResult SDL_AppIterate(void *) {
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glViewport(0, 0, WINDOW_VIRTUAL, WINDOW_VIRTUAL);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
struct crop c = {0, 0, 64, 64};
batch_blit(-32, -32, 0, &textures.at("turret"), &c);
batch_flush();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
if (square_pixels == false && integer_upscale == false) {
glViewport(0, 0, window_width, window_height);
} else if (square_pixels == false && integer_upscale == true) {
unsigned w = window_width / WINDOW_WIDTH * WINDOW_WIDTH, h = window_height / WINDOW_HEIGHT * WINDOW_HEIGHT;
glViewport((window_width - w) / 2, (window_height - h) / 2, w, h);
} else if (square_pixels == true && integer_upscale == false) {
static_assert(WINDOW_WIDTH / WINDOW_DENOM * WINDOW_DENOM == WINDOW_WIDTH);
static_assert(WINDOW_HEIGHT / WINDOW_DENOM * WINDOW_DENOM == WINDOW_HEIGHT);
unsigned w = window_width * WINDOW_DENOM / WINDOW_WIDTH, h = window_height * WINDOW_DENOM / WINDOW_HEIGHT;
unsigned s = w > h? h: w;
w = s * WINDOW_WIDTH / WINDOW_DENOM, h = s * WINDOW_HEIGHT / WINDOW_DENOM;
glViewport((window_width - w) / 2, (window_height - h) / 2, w, h);
} else if (square_pixels == true && integer_upscale == true) {
unsigned w = window_width / WINDOW_WIDTH, h = window_height / WINDOW_HEIGHT;
unsigned s = w > h? h: w;
w = s * WINDOW_WIDTH, h = s * WINDOW_HEIGHT;
glViewport((window_width - w) / 2, (window_height - h) / 2, w, h);
} else {
// how
}
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
glDisable(GL_DEPTH_TEST);
glBindBuffer(GL_ARRAY_BUFFER, fb.vbo);
glUseProgram(fb.program);
glVertexAttribPointer(fb.aVertCoord, 2, GL_FLOAT, GL_FALSE, sizeof (float [4]), (void *) 0);
glVertexAttribPointer(fb.aTexCoord, 2, GL_FLOAT, GL_TRUE, sizeof (float [4]), (void *) sizeof (float [2]));
glEnableVertexAttribArray(fb.aVertCoord);
glEnableVertexAttribArray(fb.aTexCoord);
glUniform4f(fb.uForeGround, 1, 1, 0, 1);
glUniform4f(fb.uBackGround, 0, 0, 1, 1);
glUniform1i(fb.uTex, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, fb.color);
glDrawArrays(GL_TRIANGLES, 0, 6);
glDisableVertexAttribArray(fb.aVertCoord);
glDisableVertexAttribArray(fb.aTexCoord);
SDL_GL_SwapWindow(window);
return SDL_APP_CONTINUE;
}
void SDL_AppQuit(void *, SDL_AppResult) {
;
}
static int framebuffer_init(void) {
fb.program = programs.at("up.trans");
glGenBuffers(1, &fb.vbo);
fb.aVertCoord = glGetAttribLocation(fb.program, "aVertCoord");
fb.aTexCoord = glGetAttribLocation(fb.program, "aTexCoord");
fb.uTex = glGetUniformLocation(fb.program, "uTex");
fb.uBackGround = glGetUniformLocation(fb.program, "uBackGround");
fb.uForeGround = glGetUniformLocation(fb.program, "uForeGround");
static_assert(WINDOW_WIDTH <= WINDOW_VIRTUAL);
static_assert(WINDOW_HEIGHT <= WINDOW_VIRTUAL);
const float horizontal = (WINDOW_VIRTUAL - WINDOW_WIDTH) / 2.0 / WINDOW_VIRTUAL;
const float vertical = (WINDOW_VIRTUAL - WINDOW_HEIGHT) / 2.0 / WINDOW_VIRTUAL;
const float vertices[] = {
-1, -1, 0 + horizontal, 0 + vertical,
+1, -1, 1 - horizontal, 0 + vertical,
-1, +1, 0 + horizontal, 1 - vertical,
+1, -1, 1 - horizontal, 0 + vertical,
-1, +1, 0 + horizontal, 1 - vertical,
+1, +1, 1 - horizontal, 1 - vertical,
};
glBindBuffer(GL_ARRAY_BUFFER, fb.vbo);
glBufferData(GL_ARRAY_BUFFER, 6 * sizeof (float [4]), vertices, GL_STATIC_DRAW);
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glGenTextures(1, &fb.color);
glBindTexture(GL_TEXTURE_2D, fb.color);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, WINDOW_VIRTUAL, WINDOW_VIRTUAL, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb.color, 0);
glGenRenderbuffers(1, &fb.depth);
glBindRenderbuffer(GL_RENDERBUFFER, fb.depth);
while (glGetError() != GL_NO_ERROR);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, WINDOW_VIRTUAL, WINDOW_VIRTUAL);
// too much hope in humanity? dont worry!
// GLES2 does not support a depth+stencil format
// GLES2 has extensions for D24S8 format
// WEBGL supports a depth+stencil format
// the problem? the two are DIFFERENT depth+stencil formats!
if (glGetError() == GL_NO_ERROR) {
// depth24_stencil8 supported
fprintf(stderr, "info: fb: D24S8\n");
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb.depth);
} else {
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_STENCIL, WINDOW_VIRTUAL, WINDOW_VIRTUAL);
if (glGetError() == GL_NO_ERROR) {
// depth_stencil supported (??)
fprintf(stderr, "info: fb: DS?\n");
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb.depth);
} else {
// neither, fallback to separate depth and stencil
fprintf(stderr, "info: fb: D16 S8\n");
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, WINDOW_VIRTUAL, WINDOW_VIRTUAL);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fb.depth);
glGenRenderbuffers(1, &fb.stencil);
glBindRenderbuffer(GL_RENDERBUFFER, fb.stencil);
glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, WINDOW_VIRTUAL, WINDOW_VIRTUAL);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb.stencil);
}
}
switch (glCheckFramebufferStatus(GL_FRAMEBUFFER)) {
case GL_FRAMEBUFFER_COMPLETE:
return 0;
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
fprintf(stderr, "error: fb: incomplete attachment\n");
return 1;
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
fprintf(stderr, "error: fb: missing attachment\n");
return 1;
case GL_FRAMEBUFFER_UNSUPPORTED:
fprintf(stderr, "error: fb: unsupported\n");
return 1;
case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
fprintf(stderr, "error: fb: samples dont match\n");
return 1;
case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
fprintf(stderr, "error: fb: layers\n");
return 1;
default:
fprintf(stderr, "error: fb: unknown error\n");
return 1;
}
}