cb6d9e61d1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
177 lines
5.6 KiB
C++
177 lines
5.6 KiB
C++
#include "sprite_batch.h"
|
|
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
namespace fn::gfx {
|
|
|
|
namespace {
|
|
|
|
struct Vertex {
|
|
float x, y;
|
|
float u, v;
|
|
float r, g, b, a;
|
|
};
|
|
|
|
const char* VS =
|
|
#if defined(__EMSCRIPTEN__)
|
|
"#version 300 es\n"
|
|
#else
|
|
"#version 330 core\n"
|
|
#endif
|
|
"uniform mat4 u_view_proj;\n"
|
|
"layout(location = 0) in vec2 a_pos;\n"
|
|
"layout(location = 1) in vec2 a_uv;\n"
|
|
"layout(location = 2) in vec4 a_color;\n"
|
|
"out vec2 v_uv;\n"
|
|
"out vec4 v_color;\n"
|
|
"void main() {\n"
|
|
" v_uv = a_uv;\n"
|
|
" v_color = a_color;\n"
|
|
" gl_Position = u_view_proj * vec4(a_pos, 0.0, 1.0);\n"
|
|
"}\n";
|
|
|
|
const char* FS =
|
|
#if defined(__EMSCRIPTEN__)
|
|
"#version 300 es\n"
|
|
"precision mediump float;\n"
|
|
#else
|
|
"#version 330 core\n"
|
|
#endif
|
|
"in vec2 v_uv;\n"
|
|
"in vec4 v_color;\n"
|
|
"out vec4 frag;\n"
|
|
"uniform sampler2D u_tex;\n"
|
|
"void main() {\n"
|
|
" frag = texture(u_tex, v_uv) * v_color;\n"
|
|
"}\n";
|
|
|
|
void flush(SpriteBatch& b) {
|
|
if (b.cpu_count_quads == 0) return;
|
|
int verts = b.cpu_count_quads * 6;
|
|
sg_range data{ b.cpu_buffer, (size_t)verts * sizeof(Vertex) };
|
|
sg_update_buffer(b.vbo, &data);
|
|
|
|
sg_apply_pipeline(b.pipeline);
|
|
|
|
sg_bindings bind{};
|
|
bind.vertex_buffers[0] = b.vbo;
|
|
bind.views[0] = b.current_view;
|
|
bind.samplers[0] = b.sampler;
|
|
sg_apply_bindings(&bind);
|
|
|
|
sg_range proj_range{ b.proj, sizeof(b.proj) };
|
|
sg_apply_uniforms(0, &proj_range);
|
|
|
|
sg_draw(0, verts, 1);
|
|
b.cpu_count_quads = 0;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
SpriteBatch sprite_batch_create(int cpu_capacity_quads) {
|
|
SpriteBatch b{};
|
|
b.cpu_capacity_quads = cpu_capacity_quads > 0 ? cpu_capacity_quads : 4096;
|
|
b.cpu_buffer = std::malloc((size_t)b.cpu_capacity_quads * 6 * sizeof(Vertex));
|
|
if (!b.cpu_buffer) return b;
|
|
|
|
sg_buffer_desc bd{};
|
|
bd.size = (size_t)b.cpu_capacity_quads * 6 * sizeof(Vertex);
|
|
bd.usage.vertex_buffer = true;
|
|
bd.usage.stream_update = true;
|
|
b.vbo = sg_make_buffer(&bd);
|
|
|
|
sg_shader_desc sd{};
|
|
sd.vertex_func.source = VS;
|
|
sd.fragment_func.source = FS;
|
|
sd.attrs[0].glsl_name = "a_pos";
|
|
sd.attrs[1].glsl_name = "a_uv";
|
|
sd.attrs[2].glsl_name = "a_color";
|
|
sd.uniform_blocks[0].stage = SG_SHADERSTAGE_VERTEX;
|
|
sd.uniform_blocks[0].size = 16 * sizeof(float);
|
|
sd.uniform_blocks[0].layout = SG_UNIFORMLAYOUT_NATIVE;
|
|
sd.uniform_blocks[0].glsl_uniforms[0].type = SG_UNIFORMTYPE_MAT4;
|
|
sd.uniform_blocks[0].glsl_uniforms[0].glsl_name = "u_view_proj";
|
|
sd.views[0].texture.stage = SG_SHADERSTAGE_FRAGMENT;
|
|
sd.views[0].texture.image_type = SG_IMAGETYPE_2D;
|
|
sd.views[0].texture.sample_type = SG_IMAGESAMPLETYPE_FLOAT;
|
|
sd.samplers[0].stage = SG_SHADERSTAGE_FRAGMENT;
|
|
sd.samplers[0].sampler_type = SG_SAMPLERTYPE_FILTERING;
|
|
sd.texture_sampler_pairs[0].stage = SG_SHADERSTAGE_FRAGMENT;
|
|
sd.texture_sampler_pairs[0].view_slot = 0;
|
|
sd.texture_sampler_pairs[0].sampler_slot = 0;
|
|
sd.texture_sampler_pairs[0].glsl_name = "u_tex";
|
|
sg_shader shd = sg_make_shader(&sd);
|
|
|
|
sg_pipeline_desc pd{};
|
|
pd.shader = shd;
|
|
pd.layout.attrs[0].format = SG_VERTEXFORMAT_FLOAT2;
|
|
pd.layout.attrs[1].format = SG_VERTEXFORMAT_FLOAT2;
|
|
pd.layout.attrs[2].format = SG_VERTEXFORMAT_FLOAT4;
|
|
pd.colors[0].blend.enabled = true;
|
|
pd.colors[0].blend.src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA;
|
|
pd.colors[0].blend.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
|
pd.colors[0].blend.src_factor_alpha = SG_BLENDFACTOR_ONE;
|
|
pd.colors[0].blend.dst_factor_alpha = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
|
pd.primitive_type = SG_PRIMITIVETYPE_TRIANGLES;
|
|
b.pipeline = sg_make_pipeline(&pd);
|
|
|
|
sg_sampler_desc smd{};
|
|
smd.min_filter = SG_FILTER_LINEAR;
|
|
smd.mag_filter = SG_FILTER_LINEAR;
|
|
smd.wrap_u = SG_WRAP_CLAMP_TO_EDGE;
|
|
smd.wrap_v = SG_WRAP_CLAMP_TO_EDGE;
|
|
b.sampler = sg_make_sampler(&smd);
|
|
|
|
b.ok = true;
|
|
return b;
|
|
}
|
|
|
|
void sprite_batch_destroy(SpriteBatch& b) {
|
|
if (b.cpu_buffer) { std::free(b.cpu_buffer); b.cpu_buffer = nullptr; }
|
|
if (b.pipeline.id) sg_destroy_pipeline(b.pipeline);
|
|
if (b.vbo.id) sg_destroy_buffer(b.vbo);
|
|
if (b.sampler.id) sg_destroy_sampler(b.sampler);
|
|
b = {};
|
|
}
|
|
|
|
void sprite_batch_begin(SpriteBatch& b, const float view_proj_col_major[16]) {
|
|
std::memcpy(b.proj, view_proj_col_major, sizeof(b.proj));
|
|
b.cpu_count_quads = 0;
|
|
b.current_view = {};
|
|
b.in_pass = true;
|
|
}
|
|
|
|
void sprite_batch_draw(SpriteBatch& b, sg_view view, int img_w, int img_h,
|
|
fn::math2d::Rect src, fn::math2d::Rect dst,
|
|
fn::math2d::Color tint) {
|
|
if (!b.in_pass || !b.ok) return;
|
|
if (b.current_view.id != view.id) {
|
|
flush(b);
|
|
b.current_view = view;
|
|
}
|
|
if (b.cpu_count_quads >= b.cpu_capacity_quads) flush(b);
|
|
|
|
float u0 = src.x / (float)img_w;
|
|
float v0 = src.y / (float)img_h;
|
|
float u1 = (src.x + src.w) / (float)img_w;
|
|
float v1 = (src.y + src.h) / (float)img_h;
|
|
|
|
Vertex* base = (Vertex*)b.cpu_buffer + b.cpu_count_quads * 6;
|
|
Vertex tl{ dst.x, dst.y, u0, v0, tint.r, tint.g, tint.b, tint.a };
|
|
Vertex tr{ dst.x + dst.w, dst.y, u1, v0, tint.r, tint.g, tint.b, tint.a };
|
|
Vertex bl{ dst.x, dst.y + dst.h, u0, v1, tint.r, tint.g, tint.b, tint.a };
|
|
Vertex br{ dst.x + dst.w, dst.y + dst.h, u1, v1, tint.r, tint.g, tint.b, tint.a };
|
|
base[0] = tl; base[1] = tr; base[2] = br;
|
|
base[3] = tl; base[4] = br; base[5] = bl;
|
|
b.cpu_count_quads++;
|
|
}
|
|
|
|
void sprite_batch_end(SpriteBatch& b) {
|
|
if (!b.in_pass) return;
|
|
flush(b);
|
|
b.in_pass = false;
|
|
}
|
|
|
|
} // namespace fn::gfx
|