Last active
September 11, 2020 02:13
-
-
Save seanbaxter/49a62267137eb3fcfe7c053174343b22 to your computer and use it in GitHub Desktop.
compiled sprites with SW framebuffer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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