313 lines
10 KiB
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;
|
|
}
|
|
}
|