#include "glad/gles2.h" #include #define SDL_MAIN_USE_CALLBACKS 1 #include #include #include #include #include #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 programs; std::unordered_map 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; } }