444 lines
11 KiB
C++
444 lines
11 KiB
C++
#include "sprstk/sprstk.h"
|
|
|
|
#include <glad/gl.h>
|
|
#include <SDL3/SDL.h>
|
|
|
|
#include <stdexcept>
|
|
|
|
#include <cmath>
|
|
|
|
const char* MESH_SHADER_CODE = R"(
|
|
#version 460
|
|
|
|
#extension GL_NV_mesh_shader : require
|
|
|
|
layout (local_size_x = 32) in;
|
|
layout (triangles, max_vertices = 128, max_primitives = 64) out;
|
|
|
|
layout (location = 0) out PerVertexData
|
|
{
|
|
vec4 color;
|
|
} v_out[];
|
|
|
|
layout (location = 1) uniform vec3 screen_size_and_pixel_scale;
|
|
layout (location = 2) uniform mat2 rotation_matrix;
|
|
|
|
struct TileInfo
|
|
{
|
|
uint position;
|
|
};
|
|
|
|
layout (std430, binding = 0) restrict readonly buffer TileInfos
|
|
{
|
|
TileInfo[] tile_infos;
|
|
};
|
|
|
|
struct ColorInfo
|
|
{
|
|
uint color[32];
|
|
};
|
|
|
|
layout (std430, binding = 1) restrict readonly buffer ColorInfos
|
|
{
|
|
ColorInfo[] color_infos;
|
|
};
|
|
|
|
const uint indices[6] = { 0, 1, 2, 2, 1, 3 };
|
|
|
|
void main()
|
|
{
|
|
TileInfo t_info = tile_infos[gl_WorkGroupID.x];
|
|
uint position_x = bitfieldExtract(t_info.position, 0, 10);
|
|
uint position_y = bitfieldExtract(t_info.position, 10, 10);
|
|
vec2 stack_position = vec2(position_x, position_y) - vec2(512, 512);
|
|
|
|
uint layer_count = bitfieldExtract(t_info.position, 20, 5);
|
|
|
|
vec2 positions[4] = { vec2(0, 0), vec2(1, 0), vec2(0, 1), vec2(1, 1) };
|
|
for (uint i = 0; i < 4; i++)
|
|
{
|
|
positions[i] += stack_position;
|
|
positions[i] *= screen_size_and_pixel_scale.zz;
|
|
positions[i] /= vec2(min(screen_size_and_pixel_scale.x, screen_size_and_pixel_scale.y));
|
|
}
|
|
|
|
uint z_offset = bitfieldExtract(t_info.position, 25, 2);
|
|
|
|
uint palette_lookup = bitfieldExtract(t_info.position, 27, 5);
|
|
ColorInfo c_info = color_infos[palette_lookup];
|
|
float a = bitfieldExtract(c_info.color[gl_LocalInvocationID.x], 0, 8);
|
|
float b = bitfieldExtract(c_info.color[gl_LocalInvocationID.x], 8, 8);
|
|
float g = bitfieldExtract(c_info.color[gl_LocalInvocationID.x], 16, 8);
|
|
float r = bitfieldExtract(c_info.color[gl_LocalInvocationID.x], 24, 8);
|
|
|
|
for (uint i = 4 * gl_LocalInvocationID.x; i < 4 * gl_LocalInvocationID.x + 4; i++)
|
|
{
|
|
vec4 position = vec4(rotation_matrix * positions[i % 4], float(4 * gl_LocalInvocationID.x + z_offset) / 128, 1);
|
|
position.y += 24 * position.z / screen_size_and_pixel_scale.z;
|
|
position.xy *= vec2(0.05);
|
|
gl_MeshVerticesNV[i].gl_Position = position;
|
|
|
|
v_out[i].color = vec4(r, g, b, a) / vec4(256, 256, 256, 256);
|
|
}
|
|
|
|
for (uint i = 6 * gl_LocalInvocationID.x; i < 6 * gl_LocalInvocationID.x + 6; i++)
|
|
{
|
|
gl_PrimitiveIndicesNV[i] = 4 * gl_LocalInvocationID.x + indices[i % 6];
|
|
}
|
|
|
|
gl_PrimitiveCountNV = layer_count * 2 + 2;
|
|
}
|
|
)";
|
|
|
|
const char* FRAGMENT_SHADER_CODE = R"(
|
|
#version 460
|
|
|
|
layout(location = 0) out vec4 FragColor;
|
|
|
|
in PerVertexData
|
|
{
|
|
vec4 color;
|
|
} fragIn;
|
|
|
|
void main()
|
|
{
|
|
FragColor = fragIn.color;
|
|
}
|
|
)";
|
|
|
|
struct TileInfo
|
|
{
|
|
uint32_t position;
|
|
};
|
|
|
|
class application_error : public std::runtime_error
|
|
{
|
|
public:
|
|
using runtime_error::runtime_error;
|
|
};
|
|
|
|
class sprstk
|
|
{
|
|
public:
|
|
explicit sprstk(sprstk_callbacks& callbacks, void* userdata) :
|
|
callbacks(callbacks),
|
|
userdata(userdata),
|
|
should_stop(false),
|
|
prev_ticks(0)
|
|
{
|
|
if (!callbacks.update)
|
|
{
|
|
throw std::runtime_error("No update callback");
|
|
}
|
|
}
|
|
~sprstk()
|
|
{
|
|
destroy_gl();
|
|
destroy_sdl();
|
|
}
|
|
|
|
void run()
|
|
{
|
|
init_sdl();
|
|
init_gl();
|
|
|
|
if (callbacks.init) { callbacks.init(this, userdata); }
|
|
|
|
prev_ticks = SDL_GetTicks();
|
|
|
|
while (!should_stop)
|
|
{
|
|
SDL_Event e;
|
|
while (SDL_PollEvent(&e))
|
|
{
|
|
if (e.type == SDL_EVENT_QUIT)
|
|
{
|
|
stop();
|
|
}
|
|
|
|
if (e.type == SDL_EVENT_WINDOW_RESIZED)
|
|
{
|
|
int width, height;
|
|
SDL_GetWindowSizeInPixels(sdl.window, &width, &height);
|
|
glViewport(0, 0, width, height);
|
|
glProgramUniform3f(gl.program, 1, width, height, 8);
|
|
}
|
|
}
|
|
|
|
uint64_t current_ticks = SDL_GetTicks();
|
|
float dt = (current_ticks - prev_ticks) / 1000.0f;
|
|
prev_ticks = current_ticks;
|
|
|
|
gl.tile_count = 0;
|
|
callbacks.update(this, dt, userdata);
|
|
|
|
glClearColor(0, 0, 0, 1);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
int i;
|
|
for (i = 0; i < gl.tile_count; i += 65535)
|
|
{
|
|
glDrawMeshTasksNV(i, 65535);
|
|
}
|
|
|
|
SDL_GL_SwapWindow(sdl.window);
|
|
}
|
|
|
|
if (callbacks.quit) { callbacks.quit(this, userdata); }
|
|
}
|
|
|
|
void stop()
|
|
{
|
|
should_stop = true;
|
|
}
|
|
|
|
void put(int x, int y, unsigned int layers, unsigned int palette_lookup, unsigned int z_offset = 0)
|
|
{
|
|
x += 512;
|
|
x &= 0b1111111111;
|
|
|
|
y += 512;
|
|
y &= 0b1111111111;
|
|
y <<= 10;
|
|
|
|
layers &= 0b11111;
|
|
layers <<= 20;
|
|
|
|
z_offset &= 0b11;
|
|
z_offset <<= 25;
|
|
|
|
palette_lookup &= 0b11111;
|
|
palette_lookup <<= 27;
|
|
|
|
gl.tile_buffer_map[gl.tile_count++] = { x | y | layers | z_offset | palette_lookup };
|
|
}
|
|
|
|
void set_palette(unsigned int index, const sprstk_palette* palette)
|
|
{
|
|
if (index > (1 << 5)) { return; }
|
|
|
|
gl.color_info_map[index] = *palette;
|
|
}
|
|
|
|
void set_angle(float angle)
|
|
{
|
|
const float arr[4] = {
|
|
cosf(angle), -sinf(angle),
|
|
sinf(angle), cosf(angle)
|
|
};
|
|
glProgramUniformMatrix2fv(gl.program, 2, 1, false, arr);
|
|
}
|
|
|
|
private:
|
|
sprstk_callbacks callbacks;
|
|
void* userdata;
|
|
bool should_stop;
|
|
|
|
uint64_t prev_ticks;
|
|
|
|
struct
|
|
{
|
|
SDL_Window* window;
|
|
SDL_GLContext context;
|
|
} sdl;
|
|
struct
|
|
{
|
|
unsigned int program;
|
|
unsigned int tile_buffer;
|
|
TileInfo* tile_buffer_map;
|
|
unsigned int tile_count;
|
|
unsigned int color_buffer;
|
|
sprstk_palette* color_info_map;
|
|
} gl;
|
|
|
|
void init_sdl()
|
|
{
|
|
if (!SDL_Init(SDL_INIT_VIDEO))
|
|
{
|
|
throw application_error("Failed to initialize SDL");
|
|
}
|
|
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
|
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
|
|
|
|
sdl.window = SDL_CreateWindow("sprstk", 640, 480, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
|
|
if (!sdl.window)
|
|
{
|
|
throw application_error("Failed to create window");
|
|
}
|
|
}
|
|
|
|
void destroy_sdl()
|
|
{
|
|
if (sdl.window) { SDL_DestroyWindow(sdl.window); }
|
|
SDL_Quit();
|
|
}
|
|
|
|
void init_gl()
|
|
{
|
|
sdl.context = SDL_GL_CreateContext(sdl.window);
|
|
if (!sdl.context)
|
|
{
|
|
throw application_error("Failed to create OpenGL context");
|
|
}
|
|
SDL_GL_MakeCurrent(sdl.window, sdl.context);
|
|
SDL_GL_SetSwapInterval(-1);
|
|
|
|
gladLoadGL(SDL_GL_GetProcAddress);
|
|
|
|
if (!GLAD_GL_NV_mesh_shader)
|
|
{
|
|
throw application_error("Mesh shaders not supported");
|
|
}
|
|
|
|
glEnable(GL_BLEND);
|
|
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
|
|
|
|
glDebugMessageCallback([](GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userdata)
|
|
{
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "%s", message);
|
|
}, nullptr);
|
|
|
|
int success;
|
|
char info_log[512];
|
|
|
|
unsigned int mesh = glCreateShader(GL_MESH_SHADER_NV);
|
|
glShaderSource(mesh, 1, &MESH_SHADER_CODE, nullptr);
|
|
glCompileShader(mesh);
|
|
glGetShaderiv(mesh, GL_COMPILE_STATUS, &success);
|
|
if (!success)
|
|
{
|
|
glGetShaderInfoLog(mesh, sizeof(info_log), nullptr, info_log);
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Mesh shader: %s", info_log);
|
|
}
|
|
|
|
unsigned int fragment = glCreateShader(GL_FRAGMENT_SHADER);
|
|
glShaderSource(fragment, 1, &FRAGMENT_SHADER_CODE, nullptr);
|
|
glCompileShader(fragment);
|
|
glGetShaderiv(fragment, GL_COMPILE_STATUS, &success);
|
|
if (!success)
|
|
{
|
|
glGetShaderInfoLog(fragment, sizeof(info_log), nullptr, info_log);
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Fragment shader: %s", info_log);
|
|
}
|
|
|
|
gl.program = glCreateProgram();
|
|
glAttachShader(gl.program, mesh);
|
|
glAttachShader(gl.program, fragment);
|
|
glLinkProgram(gl.program);
|
|
glGetProgramiv(gl.program, GL_LINK_STATUS, &success);
|
|
if (!success)
|
|
{
|
|
glGetProgramInfoLog(gl.program, sizeof(info_log), nullptr, info_log);
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Program: %s", info_log);
|
|
}
|
|
|
|
glDeleteShader(mesh);
|
|
glDeleteShader(fragment);
|
|
|
|
if (!success)
|
|
{
|
|
throw application_error("Failed to compile and link shader program");
|
|
}
|
|
|
|
glUseProgram(gl.program);
|
|
|
|
int width, height;
|
|
SDL_GetWindowSizeInPixels(sdl.window, &width, &height);
|
|
glViewport(0, 0, width, height);
|
|
glProgramUniform3f(gl.program, 1, width, height, 8);
|
|
|
|
glGenBuffers(1, &gl.tile_buffer);
|
|
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, gl.tile_buffer);
|
|
glNamedBufferStorage(gl.tile_buffer, sizeof(TileInfo) * (1 << 26), nullptr, GL_MAP_WRITE_BIT | GL_MAP_COHERENT_BIT | GL_MAP_PERSISTENT_BIT);
|
|
|
|
gl.tile_count = 0;
|
|
gl.tile_buffer_map = (TileInfo*)glMapNamedBufferRange(gl.tile_buffer, 0, sizeof(TileInfo) * (1 << 26), GL_MAP_WRITE_BIT | GL_MAP_COHERENT_BIT);
|
|
|
|
glGenBuffers(1, &gl.color_buffer);
|
|
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, gl.color_buffer);
|
|
glNamedBufferStorage(gl.color_buffer, sizeof(sprstk_palette) * (1 << 5), nullptr, GL_MAP_WRITE_BIT | GL_MAP_COHERENT_BIT | GL_MAP_PERSISTENT_BIT);
|
|
|
|
gl.color_info_map = (sprstk_palette*)glMapNamedBufferRange(gl.color_buffer, 0, sizeof(sprstk_palette) * (1 << 5), GL_MAP_WRITE_BIT | GL_MAP_COHERENT_BIT);
|
|
|
|
const float arr[4] = {1, 0, 0, 1};
|
|
glProgramUniformMatrix2fv(gl.program, 2, 1, false, arr);
|
|
}
|
|
|
|
void destroy_gl()
|
|
{
|
|
glDeleteProgram(gl.program);
|
|
glDeleteBuffers(1, &gl.tile_buffer);
|
|
SDL_GL_DestroyContext(sdl.context);
|
|
}
|
|
};
|
|
|
|
extern "C"
|
|
{
|
|
|
|
sprstk* sprstk_new(sprstk_callbacks callbacks, void* userdata)
|
|
{
|
|
try
|
|
{
|
|
return new sprstk(callbacks, userdata);
|
|
}
|
|
catch (application_error& e)
|
|
{
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", e.what());
|
|
}
|
|
catch(std::exception& e)
|
|
{
|
|
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "%s", e.what());
|
|
}
|
|
return nullptr;
|
|
}
|
|
void sprstk_del(sprstk* instance)
|
|
{
|
|
delete instance;
|
|
}
|
|
|
|
void sprstk_run(sprstk* instance)
|
|
{
|
|
try
|
|
{
|
|
instance->run();
|
|
}
|
|
catch (application_error& e)
|
|
{
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", e.what());
|
|
}
|
|
catch(std::exception& e)
|
|
{
|
|
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "%s", e.what());
|
|
}
|
|
}
|
|
void sprstk_stop(sprstk* instance)
|
|
{
|
|
instance->stop();
|
|
}
|
|
|
|
void sprstk_put(sprstk* instance, int x, int y, unsigned int layers, unsigned int palette_lookup)
|
|
{
|
|
instance->put(x, y, layers, palette_lookup);
|
|
}
|
|
|
|
void sprstk_putz(sprstk* instance, int x, int y, unsigned int layers, unsigned int palette_lookup, unsigned int z_offset)
|
|
{
|
|
instance->put(x, y, layers, palette_lookup, z_offset);
|
|
}
|
|
|
|
void sprstk_set_palette(sprstk* instance, unsigned int index, const sprstk_palette* palette)
|
|
{
|
|
instance->set_palette(index, palette);
|
|
}
|
|
|
|
void sprstk_set_angle(sprstk* instance, float angle)
|
|
{
|
|
instance->set_angle(angle);
|
|
}
|
|
|
|
}
|