Skip to content

Instantly share code, notes, and snippets.

@seanbaxter
Last active September 11, 2020 02:13
Show Gist options
  • Save seanbaxter/49a62267137eb3fcfe7c053174343b22 to your computer and use it in GitHub Desktop.
Save seanbaxter/49a62267137eb3fcfe7c053174343b22 to your computer and use it in GitHub Desktop.
compiled sprites with SW framebuffer
#include <stb_image.h>
#include <string>
#include <vector>
#include <gl3w/GL/gl3w.h>
#include <GLFW/glfw3.h>
template<typename type_t>
const char* enum_to_string(type_t e) {
static_assert(std::is_enum_v<type_t>);
switch(e) {
@meta for enum(type_t e2 : type_t) {
case e2:
return @enum_name(e2);
}
default:
return nullptr;
}
}
const int PixSize = 4;
const int Width = 100;
const int Height = 200;
struct sprite_sheet_t {
sprite_sheet_t(const char* metadata, const char* image);
~sprite_sheet_t();
uint32_t transparent = 0;
struct sprite_t {
std::string name;
int left, top, width, height;
};
std::vector<sprite_t> sprites;
const uint32_t* data = nullptr;
int width, height;
};
inline sprite_sheet_t::sprite_sheet_t(const char* metadata, const char* image) {
FILE* f = fopen(metadata, "r");
int r, g, b;
fscanf(f, "%d %d %d", &r, &g, &b);
// This value indicates transparency.
transparent = r | (g<< 8) | (b<< 16) | (255<< 24);
// Load in each row of the sprite sheet.
char name[64];
int left, top, right, bottom;
while(5 == fscanf(f, "%s %d %d %d %d", name, &left, &top, &right, &bottom)) {
sprites.push_back({
name, left, top, right - left, bottom - top
});
}
fclose(f);
// Open the image with RGBA format.
int comp;
data = (uint32_t*)stbi_load("../assets/sprites.png", &width,
&height, &comp, STBI_rgb_alpha);
}
inline sprite_sheet_t::~sprite_sheet_t() {
if(data)
stbi_image_free((void*)data);
}
@meta sprite_sheet_t sprite_sheet("../assets/tyrian.sprites",
"../assets/sprites.png");
// Generate an enum with a name for each sprite.
enum class sprite_name_t {
@meta for(const auto& sprite : sprite_sheet.sprites)
@(sprite.name);
};
// Print the loaded sprites.
@meta printf("* %s\n", @enum_names(sprite_name_t))...;
// Compile each sprite into a function template.
template<sprite_name_t name>
void render_sprite(uint32_t* ptr) {
@meta+ {
printf("Generating compiled sprite '%s'\n", @enum_name(name));
auto sprite = sprite_sheet.sprites[(int)name];
const uint32_t* row_data = sprite_sheet.data +
sprite.top * sprite_sheet.width + sprite.left;
for(int row = 0; row < sprite.height; ++row) {
for(int col = 0; col < sprite.width; ++col) {
uint32_t value = row_data[col];
if(sprite_sheet.transparent != value)
@emit ptr[col] = value;
}
// Advance to the next scan line.
@emit ptr += Width;
row_data += sprite_sheet.width;
}
}
}
struct app_t {
app_t();
void loop();
void display();
void draw_sprite(int x, int y, sprite_name_t name);
void button_callback(int button, int action, int mods);
void scroll_callback(double x, double y);
GLFWwindow* window;
// The current sprite selected. Insert a new one of these whenever the mouse
// is clicked.
sprite_name_t cur_sprite;
double last_time;
struct item_t {
// Keep a buffer of sprites with their locations.
int x, y;
sprite_name_t name;
};
std::vector<item_t> sprites;
GLuint tex; // Texture backing for offscreen fbo.
GLuint fbo; // An offscreen framebuffer.
std::vector<uint32_t> framebuffer;
};
void debug_callback(GLenum source, GLenum type, GLuint id,
GLenum severity, GLsizei length, const GLchar* message,
const void* user_param) {
printf("OpenGL: %s\n", message);
if(GL_DEBUG_SEVERITY_HIGH == severity ||
GL_DEBUG_SEVERITY_MEDIUM == severity)
exit(1);
}
app_t::app_t() {
glfwWindowHint(GLFW_DOUBLEBUFFER, 1);
glfwWindowHint(GLFW_DEPTH_BITS, 24);
glfwWindowHint(GLFW_STENCIL_BITS, 8);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
window = glfwCreateWindow(Width * PixSize, Height * PixSize,
"compiled sprites", nullptr, nullptr);
glfwSetWindowUserPointer(window, this);
glfwMakeContextCurrent(window);
glfwSwapInterval(1);
auto bc = [](GLFWwindow* window, int button, int action, int mods) {
app_t* app = static_cast<app_t*>(glfwGetWindowUserPointer(window));
app->button_callback(button, action, mods);
};
glfwSetMouseButtonCallback(window, bc);
auto sc = [](GLFWwindow* window, double x, double y) {
app_t* app = static_cast<app_t*>(glfwGetWindowUserPointer(window));
app->scroll_callback(x, y);
};
glfwSetScrollCallback(window, sc);
gl3wInit();
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
glDebugMessageCallback(debug_callback, nullptr);
last_time = 0;
// Create memory backing for the offscreen.
glCreateTextures(GL_TEXTURE_2D, 1, &tex);
glTextureStorage2D(tex, 1, GL_RGBA8, Width, Height);
glCreateFramebuffers(1, &fbo);
glNamedFramebufferTexture(fbo, GL_COLOR_ATTACHMENT0, tex, 0);
framebuffer.resize(Width * Height);
}
void app_t::loop() {
while(!glfwWindowShouldClose(window)) {
display();
glfwSwapBuffers(window);
glfwPollEvents();
}
}
void app_t::button_callback(int button, int action, int mods) {
if(GLFW_PRESS == action && GLFW_MOUSE_BUTTON_LEFT == button) {
double x_, y_;
glfwGetCursorPos(window, &x_, &y_);
int x = (int)x_ / PixSize - 6;
int y = (int)y_ / PixSize - 7;
sprites.push_back({ x, y, cur_sprite });
}
}
void app_t::scroll_callback(double x, double y) {
int y2 = (int)y;
int next = ((int)cur_sprite + y2) % @enum_count(sprite_name_t);
cur_sprite = (sprite_name_t)next;
char title[128];
snprintf(title, 128, "[[ %s ]]", enum_to_string(cur_sprite));
glfwSetWindowTitle(window, title);
}
void app_t::display() {
// Animate the background.
for(int row = 0; row < Height; ++row) {
for(int col = 0; col < Width; ++col)
framebuffer[row * Width + col] = row;
}
double time = glfwGetTime();
double elapsed = time - last_time;
last_time = time;
int advect = (int)(100 * elapsed);
// Advect all sprites.
for(int i = 0; i < sprites.size(); ) {
item_t& item = sprites[i];
item.y -= advect;
if(item.y < 0) {
std::swap(sprites.back(), item);
sprites.resize(sprites.size() - 1);
} else
++i;
}
// Render all sprites.
for(item_t item : sprites)
draw_sprite(item.x, item.y, item.name);
// Copy the software buffer to the offscreen buffer.
glTextureSubImage2D(tex, 0, 0, 0, Width, Height, GL_RGBA, GL_UNSIGNED_BYTE,
framebuffer.data());
// Blit to the framebuffer.
GLint fbo_dest;
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fbo_dest);
int width2, height2;
glfwGetWindowSize(window, &width2, &height2);
glBlitNamedFramebuffer(fbo, fbo_dest, 0, Height, Width, 0,
0, 0, width2, height2, GL_COLOR_BUFFER_BIT, GL_NEAREST);
}
void app_t::draw_sprite(int x, int y, sprite_name_t name) {
uint32_t* data = framebuffer.data();
switch(name) {
@meta for enum(sprite_name_t name2 : sprite_name_t) {
case name2: {
// Center the sprite around the coordinate. Clamp so that it doesn't
// wrap around the framebuffer.
int width = sprite_sheet.sprites[(int)name2].width;
int height = sprite_sheet.sprites[(int)name2].height;
int x2 = std::min(std::max(x - width / 2, 0), Width - width);
int y2 = std::min(std::max(y - height / 2, 0), Height - height);
render_sprite<name2>(data + y2 * Width + x2);
break;
}
}
}
}
int main() {
glfwInit();
app_t app;
app.loop();
return 0;
}
circle sprites.cxx -isystem ~/projects/stb/ -M ../thirdparty/libstbi.so -lGL -lgl3w -lglfw
* one_shot
* two_shot
* three_shot
* four_shot
* five_shot
* energy
* reg_fireball
* blue_fireball
* arched_energy
* one_thin
* missile
* two_missile
* two_thin
* eagle
* nuke
* double_energy
* blue_haduken
* red_haduken
* missile2
Generating compiled sprite 'one_shot'
Generating compiled sprite 'two_shot'
Generating compiled sprite 'three_shot'
Generating compiled sprite 'four_shot'
Generating compiled sprite 'five_shot'
Generating compiled sprite 'energy'
Generating compiled sprite 'reg_fireball'
Generating compiled sprite 'blue_fireball'
Generating compiled sprite 'arched_energy'
Generating compiled sprite 'one_thin'
Generating compiled sprite 'missile'
Generating compiled sprite 'two_missile'
Generating compiled sprite 'two_thin'
Generating compiled sprite 'eagle'
Generating compiled sprite 'nuke'
Generating compiled sprite 'double_energy'
Generating compiled sprite 'blue_haduken'
Generating compiled sprite 'red_haduken'
Generating compiled sprite 'missile2'
191 220 191
one_shot 0 42 12 56
two_shot 12 42 24 56
three_shot 24 42 36 56
four_shot 36 42 48 56
five_shot 48 42 60 56
energy 60 42 72 56
reg_fireball 72 42 84 56
blue_fireball 84 42 96 56
arched_energy 96 42 108 56
one_thin 108 42 120 56
missile 120 42 132 56
two_missile 132 42 144 56
two_thin 144 42 156 56
eagle 156 42 168 56
nuke 168 42 180 56
double_energy 180 42 192 56
blue_haduken 192 42 204 56
red_haduken 204 42 216 56
missile2 216 42 228 56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment