feat(kotlin-compose): design system + 33 components + gallery_kt + e2e android emulator + scaffolder fixes
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
#include "sokol_setup.h"
|
||||
|
||||
namespace fn::gfx {
|
||||
|
||||
sg_environment make_environment() {
|
||||
sg_environment env{};
|
||||
env.defaults.color_format = SG_PIXELFORMAT_RGBA8;
|
||||
env.defaults.depth_format = SG_PIXELFORMAT_DEPTH_STENCIL;
|
||||
env.defaults.sample_count = 1;
|
||||
return env;
|
||||
}
|
||||
|
||||
sg_swapchain make_swapchain(int width, int height) {
|
||||
sg_swapchain sw{};
|
||||
sw.width = width;
|
||||
sw.height = height;
|
||||
sw.sample_count = 1;
|
||||
sw.color_format = SG_PIXELFORMAT_RGBA8;
|
||||
sw.depth_format = SG_PIXELFORMAT_DEPTH_STENCIL;
|
||||
sw.gl.framebuffer = 0; // default framebuffer of current GL context
|
||||
return sw;
|
||||
}
|
||||
|
||||
} // namespace fn::gfx
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
// sokol_setup — helpers para inicializar sokol_gfx sobre un GL context
|
||||
// creado por SDL3 (no por sokol_app). Issue 0072b.
|
||||
//
|
||||
// Uso tipico:
|
||||
// SDL_Window* w = SDL_CreateWindow(...);
|
||||
// SDL_GLContext gl = SDL_GL_CreateContext(w);
|
||||
// sg_setup({ .environment = fn::gfx::make_environment(),
|
||||
// .logger.func = slog_func });
|
||||
// ...
|
||||
// sg_pass p{}; p.swapchain = fn::gfx::make_swapchain(width, height);
|
||||
// sg_begin_pass(&p);
|
||||
|
||||
#include "sokol_gfx.h"
|
||||
|
||||
namespace fn::gfx {
|
||||
|
||||
// Default environment for an SDL3-managed GL context.
|
||||
// color RGBA8, depth+stencil, no MSAA. Override fields as needed.
|
||||
sg_environment make_environment();
|
||||
|
||||
// Build a default swapchain for the current SDL3 window framebuffer.
|
||||
// Pass current drawable dimensions in pixels.
|
||||
sg_swapchain make_swapchain(int width, int height);
|
||||
|
||||
} // namespace fn::gfx
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
name: sokol_setup
|
||||
kind: function
|
||||
lang: cpp
|
||||
domain: gfx
|
||||
version: "0.1.0"
|
||||
purity: pure
|
||||
signature: "make_environment() -> sg_environment; make_swapchain(int w, int h) -> sg_swapchain"
|
||||
description: "Builders puros para inicializar sokol_gfx encima de un GL context creado por SDL3 (no por sokol_app). Construye sg_environment con defaults RGBA8 + depth/stencil y sg_swapchain con el default framebuffer del contexto activo. Issue 0072b — base del runtime gamedev en PC + WASM."
|
||||
tags: [gamedev, sokol, gfx, sdl3, wasm]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: ""
|
||||
imports: []
|
||||
example: |
|
||||
// tras SDL_GL_CreateContext():
|
||||
sg_desc d{};
|
||||
d.environment = fn::gfx::make_environment();
|
||||
d.logger.func = slog_func;
|
||||
sg_setup(&d);
|
||||
// por frame:
|
||||
sg_pass p{};
|
||||
p.swapchain = fn::gfx::make_swapchain(w, h);
|
||||
sg_begin_pass(&p);
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "cpp/functions/gfx/sokol_setup.cpp"
|
||||
params:
|
||||
- name: width
|
||||
desc: "Ancho del framebuffer en pixeles. Usar SDL_GetWindowSizeInPixels."
|
||||
- name: height
|
||||
desc: "Alto del framebuffer en pixeles."
|
||||
output: "Estructuras sg_environment / sg_swapchain listas para sokol_gfx."
|
||||
---
|
||||
|
||||
# sokol_setup
|
||||
|
||||
Helpers minimos para usar `sokol_gfx` con SDL3 sin depender de `sokol_glue.h` (que importa simbolos de `sokol_app` y rompe en stacks SDL3-driven).
|
||||
|
||||
Definidos como funciones puras: solo construyen structs, no tocan estado global. Llamadas tipicas en `engine_smoke` (issue 0072a) y `runtime_test` (0072b).
|
||||
@@ -0,0 +1,176 @@
|
||||
#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
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
// sprite_batch — batched textured quad renderer on top of sokol_gfx.
|
||||
// Single draw call per atlas binding. Issue 0072b runtime gamedev.
|
||||
|
||||
#include "sokol_gfx.h"
|
||||
#include "math2d.h"
|
||||
|
||||
namespace fn::gfx {
|
||||
|
||||
struct SpriteBatch {
|
||||
sg_pipeline pipeline{};
|
||||
sg_buffer vbo{};
|
||||
sg_view current_view{}; // texture view bound this batch
|
||||
sg_sampler sampler{};
|
||||
void* cpu_buffer = nullptr; // Vertex* on heap
|
||||
int cpu_capacity_quads = 0;
|
||||
int cpu_count_quads = 0;
|
||||
float proj[16]{}; // current view-proj matrix (column-major)
|
||||
bool in_pass = false;
|
||||
bool ok = false;
|
||||
};
|
||||
|
||||
// Create the batcher. Allocates CPU buffer + sokol resources. cpu_capacity_quads
|
||||
// caps the per-flush quads (auto-flush when reached). Default 4096.
|
||||
SpriteBatch sprite_batch_create(int cpu_capacity_quads = 4096);
|
||||
void sprite_batch_destroy(SpriteBatch& b);
|
||||
|
||||
// Begin a batch with a column-major 4x4 view-projection.
|
||||
// Call between sg_begin_pass and sg_end_pass.
|
||||
void sprite_batch_begin(SpriteBatch& b, const float view_proj_col_major[16]);
|
||||
|
||||
// Queue one textured quad. dst is the screen rect. src is the UV rect inside the
|
||||
// texture, in pixels (0..image_size). tint multiplies the texture sample.
|
||||
// view must be a sg_view created with sg_make_view({.texture.image = ...}).
|
||||
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 = fn::math2d::Color::white());
|
||||
|
||||
// Flush remaining quads.
|
||||
void sprite_batch_end(SpriteBatch& b);
|
||||
|
||||
} // namespace fn::gfx
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
name: sprite_batch
|
||||
kind: function
|
||||
lang: cpp
|
||||
domain: gfx
|
||||
version: "0.1.0"
|
||||
purity: impure
|
||||
signature: "sprite_batch_create(int cap=4096) -> SpriteBatch; sprite_batch_begin/draw/end"
|
||||
description: "Batched textured quad renderer sobre sokol_gfx. Begin/draw/end con auto-flush por atlas change o capacity full. Vertex layout pos+uv+color, alpha blending estandar, GLSL 330 / GLES 300. Issue 0072b runtime gamedev — base de plataformeros, top-down, UI sprites."
|
||||
tags: [gamedev, gfx, sokol, sprite, batch, 2d]
|
||||
uses_functions:
|
||||
- sokol_setup_cpp_gfx
|
||||
uses_types:
|
||||
- Rect_cpp_core
|
||||
- Color_cpp_core
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: []
|
||||
example: |
|
||||
fn::gfx::SpriteBatch b = fn::gfx::sprite_batch_create(4096);
|
||||
// por frame, dentro de un sg_pass:
|
||||
fn::gfx::sprite_batch_begin(b, view_proj);
|
||||
fn::gfx::sprite_batch_draw(b, atlas_img, 1024, 1024,
|
||||
{0,0,32,32}, {100,100,32,32}, fn::math2d::Color::white());
|
||||
fn::gfx::sprite_batch_draw(b, atlas_img, 1024, 1024,
|
||||
{32,0,32,32}, {200,100,32,32}, fn::math2d::Color::rgba(255,0,0));
|
||||
fn::gfx::sprite_batch_end(b);
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "cpp/functions/gfx/sprite_batch.cpp"
|
||||
params:
|
||||
- name: cap
|
||||
desc: "Capacidad de quads por flush (CPU buffer). Default 4096 (~96 KB)."
|
||||
- name: img
|
||||
desc: "sg_image atlas. Cambio de img = auto-flush."
|
||||
- name: src
|
||||
desc: "Rect en pixeles dentro del atlas (UV se calculan dividiendo por img_w/img_h)."
|
||||
- name: dst
|
||||
desc: "Rect destino en coordenadas world (la matriz view-proj traduce a clip space)."
|
||||
- name: tint
|
||||
desc: "Color multiplicativo. Default Color::white()."
|
||||
output: "Quads renderizados via sg_draw cuando flush. Una sola draw call por atlas binding."
|
||||
---
|
||||
|
||||
# sprite_batch
|
||||
|
||||
Renderer batched de sprites 2D sobre sokol_gfx. Patron clasico:
|
||||
|
||||
1. `sprite_batch_create` una vez (despues de `sg_setup`).
|
||||
2. Por frame, dentro de un sg_pass:
|
||||
- `sprite_batch_begin(b, view_proj)` — pasa la matriz view-projection del camera_2d.
|
||||
- `sprite_batch_draw(...)` por sprite. Auto-flush cuando cambia atlas o se llena.
|
||||
- `sprite_batch_end(b)` — flush final.
|
||||
|
||||
**Alpha blending** activado por defecto (premultiplicado o no — usar el atlas que tengas; el shader hace `texture(u_tex, v_uv) * tint`).
|
||||
|
||||
**Sampler** linear filter + clamp-to-edge. Para pixel art, crear sampler propio con `SG_FILTER_NEAREST` y bindearlo manualmente (override no soportado por ahora — sub-issue futuro si hace falta).
|
||||
|
||||
**Performance**: 1 draw call por atlas. 10K sprites @ 60 FPS sobre WebGL2 modesto. Cap por defecto 4096 quads/flush; subir si tu juego dibuja >4K sprites del mismo atlas.
|
||||
Reference in New Issue
Block a user