load and display bitmaps

This commit is contained in:
zlago 2025-11-09 02:37:17 +01:00
parent 857f368970
commit 0f5e6d9a4d
6 changed files with 325 additions and 73 deletions

255
src/assets.c++ Normal file
View File

@ -0,0 +1,255 @@
#include <cstdio>
#include <unordered_map>
#include <vector>
#include <tuple>
#include <string>
#include "ass.h"
#include "rect-pack.h"
#include "batch.h"
#include <glad/gles2.h>
#include "main.h++"
static int read_vlq(unsigned char const *buf, size_t size, size_t &out) {
size_t outsize = buf[0];
unsigned outsizebytes = 1;
for (unsigned m = 0x80; outsize & m; m = m >> 1) {
outsizebytes++;
outsize &= ~m;
}
if (outsizebytes > size) {
return -outsizebytes;
}
for (unsigned i = 1; i < outsizebytes; i++) {
outsize = (outsize << 8) | buf[i];
}
out = outsize;
return outsizebytes;
}
struct {
std::vector<std::string> filename;
std::vector<struct rect> rect;
std::vector<std::vector<unsigned char>> data;
} pictures = {};
static int decode_pic(struct rect const &r, std::vector<unsigned char> &data, unsigned char *out, size_t const xstride, size_t const ystride) {
if (data.size() != r.w * r.h) {
return 1;
}
for (size_t y = 0; y < r.h; y++) {
for (size_t x = 0; x < r.w; x++) {
out[y * ystride + x * xstride] = data[y * r.w + x];
}
}
return 0;
}
GLuint util_compileShader(char *name, GLsizei size, GLchar const **strings, GLint *lengths, GLenum shaderType) {
GLuint shader = glCreateShader(shaderType);
if (shader == 0) {
return 0;
}
glShaderSource(shader, size, strings, lengths);
glCompileShader(shader);
int success;
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (success == false) {
char message[512];
glGetShaderInfoLog(shader, sizeof (message), NULL, message);
fprintf(stderr, "error: %s: %s\n", name, message);
glDeleteShader(shader);
return 0;
}
return shader;
}
GLuint util_linkProgram(char *name, GLuint Vshader, GLuint Fshader) {
GLuint program = glCreateProgram();
if (program == 0) {
return 0;
}
glAttachShader(program, Vshader);
glAttachShader(program, Fshader);
glLinkProgram(program);
int success;
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (success == false) {
char message[512];
glGetProgramInfoLog(program, sizeof (message), NULL, message);
fprintf(stderr, "error: %s: %s\n", name, message);
glDeleteProgram(program);
return 0;
}
return program;
}
int assets_load(FILE *file, char const *name) {
enum ass_status ret = assread(file, [](char const *filename_, size_t length, void const *data_, size_t size, void *ctx) -> int {
(void) ctx;
std::string filename = std::string(filename_, length);
auto pos = filename.find_last_of('.');
std::string const name = filename.substr(0, pos);
std::string const extension = pos == filename.npos? "": filename.substr(pos);
unsigned char const *data = (unsigned char const *) data_;
if (extension == ".shader") {
char const *vsrc = (char const *) data;
size_t len = strnlen(vsrc, size);
if (len == size) {
return 1;
}
char const *fsrc = vsrc + len + 1;
len = size - len - 1;
if (strnlen(fsrc, len) != len - 1) {
return 1;
}
GLuint vshader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vshader, 1, &vsrc, NULL);
glCompileShader(vshader);
GLuint fshader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fshader, 1, &fsrc, NULL);
glCompileShader(fshader);
int vsuccess, fsuccess;
glGetShaderiv(vshader, GL_COMPILE_STATUS, &vsuccess);
glGetShaderiv(fshader, GL_COMPILE_STATUS, &fsuccess);
if (vsuccess == false || fsuccess == false) {
char message[1024];
if (vsuccess == false) {
glGetShaderInfoLog(vshader, sizeof (message), NULL, message);
fprintf(stderr, "error: %s.vert: %s\n", name.c_str(), message);
}
if (fsuccess == false) {
glGetShaderInfoLog(fshader, sizeof (message), NULL, message);
fprintf(stderr, "error: %s.frag: %s\n", name.c_str(), message);
}
} else {
GLuint program = glCreateProgram();
glAttachShader(program, vshader);
glAttachShader(program, fshader);
glLinkProgram(program);
int success;
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (success == false) {
char message[1024];
glGetProgramInfoLog(program, sizeof (message), NULL, message);
fprintf(stderr, "error: %s: %s\n", name.c_str(), message);
glDeleteProgram(program);
} else {
programs[name] = program;
}
}
glDeleteShader(vshader);
glDeleteShader(fshader);
return vsuccess == false || fsuccess == false;
} else if (extension == ".picture") {
if (memcmp(data, "\xdd\xddpic\n\0\0", 8)) {
return 1;
}
data += 8; size -= 8;
size_t value;
struct rect r = {};
int result = read_vlq(data, size, value);
r.w = value + 1;
if (result < 0 || r.w - 1 != value) {
return 1;
}
data += result; size -= result;
result = read_vlq(data, size, value);
r.h = value + 1;
if (result < 0 || r.h - 1 != value) {
return 1;
}
data += result; size -= result;
std::vector<unsigned char> v(data, data + size);
pictures.filename.push_back(name);
pictures.rect.push_back(r);
pictures.data.push_back(v);
} else {
return 1;
}
return 0;
}, NULL);
if (ret != ASS_OK) {
switch (ret) {
case ASS_OK: break;
case ASS_BAD: fprintf(stderr, "error: %s: malformed asset pack\n", name); break;
case ASS_USER: fprintf(stderr, "error: %s: malformed asset\n", name); break;
case ASS_TRUNC: fprintf(stderr, "error: %s: truncated asset pack\n", name); break;
case ASS_LAZY: fprintf(stderr, "error: %s: this asset pack will not load, sorry\n", name); break;
case ASS_MEM: fprintf(stderr, "error: %s: memory exhausted while trying to load asset pack\n", name); break;
case ASS_UNSUPPORTED: fprintf(stderr, "error: %s: unsupported asset pack version\n", name); break;
}
return 1;
}
struct pack_result res = guilotine(pictures.rect.data(), pictures.rect.size(), 512, 1);
if (res.bins == 0) {
return 1;
}
size_t const stride = res.last_bin_size;
unsigned char *tex = new unsigned char [stride * stride];
memset(tex, 69, stride * stride);
for (size_t i = 0; i < pictures.rect.size(); i++) {
struct rect const &r = pictures.rect[i];
//fprintf(stderr, "%s: %ux%u +%u+%u @ %u %c\n", pictures.filename[i].c_str(), r.w, r.h, r.x, r.y, r.bin, r.flipped? '+': '-');
int ret = decode_pic(r, pictures.data[i], tex + r.y * stride + r.x, r.flipped? stride: 1, r.flipped? 1: stride);
if (ret) {
delete[] tex;
return 1;
}
}
//fprintf(stderr, "info: %u %u %u %u\n", res.bins, res.last_bin_size, res.last_bin_width, res.last_bin_height);
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, stride, stride, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, tex);
delete[] tex;
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);
#if 1
unsigned tsize = 65536 / stride;
#else
unsigned tsize = 0;
for (unsigned s = stride; s & 0xffff; s <<= 1, tsize++)
;
#endif
for (size_t i = 0; i < pictures.rect.size(); i++) {
struct rect const &r = pictures.rect[i];
struct texture t = {.texture = texture, .x = r.x, .y = r.y, .size = tsize, .flipped = r.flipped};
textures[pictures.filename[i]] = t;
}
pictures.rect.clear();
pictures.rect.shrink_to_fit();
pictures.data.clear();
pictures.data.shrink_to_fit();
pictures.filename.clear();
pictures.filename.shrink_to_fit();
return 0;
}

5
src/assets.h++ Normal file
View File

@ -0,0 +1,5 @@
#pragma once
#include <cstdio>
int assets_load(FILE *file, char const *name);

View File

@ -9,39 +9,22 @@
#include <cstdio> #include <cstdio>
#include <ctime> #include <ctime>
#include "ass.h" #include "batch.h"
#include "assets.h++"
#include "main.h++"
#define WINDOW_WIDTH 128 #define WINDOW_WIDTH 128
#define WINDOW_HEIGHT 128 #define WINDOW_HEIGHT 128
#if 0 #define BATCH_SIZE 1024
// something something string_view faster but actually cryptic error messages so w/e future me problem
#include <string_view>
struct string_hash {
using is_transparent = void;
[[nodiscard]] size_t operator()(const char *txt) const {
return std::hash<std::string_view>{}(txt);
}
[[nodiscard]] size_t operator()(std::string_view txt) const {
return std::hash<std::string_view>{}(txt);
}
[[nodiscard]] size_t operator()(const std::string &txt) const {
return std::hash<std::string>{}(txt);
}
};
std::unordered_map<std::string, unsigned, string_hash, std::equal_to<>> programs, textures;
#endif
struct state { SDL_Window *window;
SDL_Window *window; SDL_GLContext context;
SDL_GLContext context; std::unordered_map<std::string, unsigned> programs;
float color; std::unordered_map<std::string, struct texture> textures;
std::unordered_map<std::string, unsigned> programs, textures;
};
SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) { SDL_AppResult SDL_AppInit(void **, int argc, char **argv) {
clock_t begin = clock(); clock_t begin = clock();
auto state = new (struct state);
SDL_Init(SDL_INIT_VIDEO); SDL_Init(SDL_INIT_VIDEO);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
@ -50,10 +33,10 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_SetHint(SDL_HINT_MAIN_CALLBACK_RATE, "60"); SDL_SetHint(SDL_HINT_MAIN_CALLBACK_RATE, "60");
state->window = SDL_CreateWindow("suwi", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL /*| SDL_WINDOW_RESIZABLE*/ | SDL_WINDOW_TRANSPARENT); window = SDL_CreateWindow("suwi", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL /*| SDL_WINDOW_RESIZABLE*/ | SDL_WINDOW_TRANSPARENT);
SDL_SetWindowMinimumSize(state->window, WINDOW_WIDTH, WINDOW_HEIGHT); SDL_SetWindowMinimumSize(window, WINDOW_WIDTH, WINDOW_HEIGHT);
state->context = SDL_GL_CreateContext(state->window); context = SDL_GL_CreateContext(window);
int version = gladLoadGLES2((GLADloadfunc) SDL_GL_GetProcAddress); int version = gladLoadGLES2((GLADloadfunc) SDL_GL_GetProcAddress);
if (version == 0) { if (version == 0) {
@ -75,35 +58,28 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
perror(path.c_str()); perror(path.c_str());
return SDL_APP_FAILURE; return SDL_APP_FAILURE;
} }
if (assets_load(file, path.c_str())) {
return SDL_APP_FAILURE;
}
} else { } else {
file = fopen(argv[1], "rb"); file = fopen(argv[1], "rb");
if (file == NULL) { if (file == NULL) {
perror(argv[1]); perror(argv[1]);
return SDL_APP_FAILURE; return SDL_APP_FAILURE;
} }
} if (assets_load(file, argv[1])) {
if (assread(file, [](char const *name, size_t length, void const *data, size_t size, /* struct state *state */ void *ctx) -> int { return SDL_APP_FAILURE;
auto &state = *(struct state *) ctx;
std::string filename = std::string(name, length);
(void) name;
(void) length;
(void) data;
(void) size;
if (filename.ends_with(".glsl")) {
state.programs[filename];
} }
state.textures[filename];
return 0;
}, state)) {
return SDL_APP_FAILURE;
} }
fclose(file);
batch_init(BATCH_SIZE, programs.at("spr2D"));
glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
SDL_ShowWindow(state->window); SDL_ShowWindow(window);
fprintf(stderr, "info: init took %lu ms\n", ((clock() - begin) * 1000) / CLOCKS_PER_SEC); fprintf(stderr, "info: init took %lu ms\n", ((clock() - begin) * 1000) / CLOCKS_PER_SEC);
*appstate = state;
return SDL_APP_CONTINUE; return SDL_APP_CONTINUE;
} }
@ -146,12 +122,15 @@ SDL_AppResult SDL_AppEvent(void *, SDL_Event *evt) {
return SDL_APP_CONTINUE; return SDL_APP_CONTINUE;
} }
SDL_AppResult SDL_AppIterate(void *appstate) { SDL_AppResult SDL_AppIterate(void *) {
auto &state = *(struct state *) appstate;
glClearColor(0.5, 0.5, 0.5, 0.0); glClearColor(0.5, 0.5, 0.5, 0.0);
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
struct crop c = {0, 0, 64, 64};
batch_blit(-32, -32, &textures.at("turret"), &c);
batch_flush();
SDL_GL_SwapWindow(state.window); SDL_GL_SwapWindow(window);
return SDL_APP_CONTINUE; return SDL_APP_CONTINUE;
} }

32
src/main.h++ Normal file
View File

@ -0,0 +1,32 @@
#pragma once
#include <glad/gles2.h>
#include <SDL3/SDL.h>
#include <unordered_map>
#include <string>
#include "batch.h"
#if 0
// something something string_view faster but actually cryptic error messages so w/e future me problem
#include <string_view>
struct string_hash {
using is_transparent = void;
[[nodiscard]] size_t operator()(const char *txt) const {
return std::hash<std::string_view>{}(txt);
}
[[nodiscard]] size_t operator()(std::string_view txt) const {
return std::hash<std::string_view>{}(txt);
}
[[nodiscard]] size_t operator()(const std::string &txt) const {
return std::hash<std::string>{}(txt);
}
};
std::unordered_map<std::string, unsigned, string_hash, std::equal_to<>> programs, textures;
#endif
extern SDL_Window *window;
extern SDL_GLContext context;
extern std::unordered_map<std::string, unsigned> programs;
extern std::unordered_map<std::string, struct texture> textures;

View File

@ -95,7 +95,7 @@ struct pack_result guilotine(struct rect *rects, size_t items, unsigned maxsize,
} }
static struct pack_result guilotine_(struct rect **left_base, size_t left_count, unsigned maxwidth, unsigned maxheight, unsigned bins, unsigned maxbins) { static struct pack_result guilotine_(struct rect **left_base, size_t left_count, unsigned maxwidth, unsigned maxheight, unsigned bins, unsigned maxbins) {
fprintf(stderr, "%zu %ux%u, %u\n", left_count, maxwidth, maxheight, maxbins - bins); //fprintf(stderr, "%zu %ux%u, %u\n", left_count, maxwidth, maxheight, maxbins - bins);
struct rect **left = left_base; struct rect **left = left_base;
unsigned in_last_bin = 0; unsigned in_last_bin = 0;
size_t slot_count = 1; size_t slot_count = 1;
@ -233,28 +233,8 @@ static struct pack_result guilotine_(struct rect **left_base, size_t left_count,
//free(other_left); //free(other_left);
free(tighter); free(tighter);
#if 0
fprintf(stdout, "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 %u %u\">", (maxsize + 16) * bins - 16, maxsize);
for (unsigned i = 0; i < bins; i++) {
unsigned binx = (maxsize + 16) * i, biny = 0;
fprintf(stdout, "<view id=\"%u\" viewBox=\"%u %u %u %u\"/>", i, binx, biny, maxsize, maxsize);
}
fputs("<style>rect{stroke:black;}.bg{fill:gray;}.flipped{fill:green;}.normal{fill:blue}text{font-family:monospace;text-anchor:middle;alignment-baseline:central;}</style>", stdout);
for (unsigned i = 0; i < items; i++) {
unsigned w = rects[i].flipped? rects[i].h: rects[i].w;
unsigned h = rects[i].flipped? rects[i].w: rects[i].h;
unsigned binx = (maxsize + 16) * rects[i].bin, biny = 0;
fprintf(stdout, "<rect x=\"%u.5\" y=\"%u.5\" width=\"%u\" height=\"%u\" class=\"%s\"/><text x=\"%u\" y=\"%u\" style=\"font-size: %upx\">%u</text>", rects[i].x + binx, rects[i].y + biny, w - 1, h - 1, rects[i].flipped? "flipped": "normal", rects[i].x + w / 2 + binx, rects[i].y + h / 2 + biny, h > 20? 20: h, i);
}
for (unsigned i = 0; i < slot_count; i++) {
unsigned binx = (maxsize + 16) * slots[i].bin, biny = 0;
fprintf(stdout, "<rect x=\"%u.5\" y=\"%u.5\" width=\"%u\" height=\"%u\" class=\"bg\"/>", slots[i].x + binx, slots[i].y + biny, slots[i].w - 1, slots[i].h - 1);
}
fputs("</svg>", stdout);
#endif
free(left_base); free(left_base);
free(slots); free(slots);
fprintf(stderr, "%u %u %u %u\n", bins, last_bin.last_bin_size, last_bin.last_bin_width, last_bin.last_bin_height); //fprintf(stderr, "%u %u %u %u\n", bins, last_bin.last_bin_size, last_bin.last_bin_width, last_bin.last_bin_height);
return (struct pack_result) {bins, last_bin.last_bin_size, last_bin.last_bin_width, last_bin.last_bin_height}; return (struct pack_result) {bins, last_bin.last_bin_size, last_bin.last_bin_width, last_bin.last_bin_height};
} }

View File

@ -15,7 +15,7 @@ attribute vec2 aTexCoord;
void main(void) { void main(void) {
vTexCoord = aTexCoord; vTexCoord = aTexCoord;
gl_Position = vec4(aVertCoord * vec2(1.0 / 256.0), 0.0, 1.0); gl_Position = vec4(aVertCoord * vec2(1.0 / 64.0), 0.0, 1.0);
} }
## ##
@ -23,8 +23,9 @@ void main(void) {
uniform sampler2D uTex; uniform sampler2D uTex;
void main(void) { void main(void) {
if (texture2D(uTex, vTexCoord).r < 0.5) { float pixel = texture2D(uTex, vTexCoord).r;
if (pixel < 0.5) {
discard; discard;
} }
gl_FragColor = vec4(1.0); gl_FragColor = step(vec4(0.75, 0.75, 0.75, 0.5), vec4(pixel));
} }