chore: sync from fn-registry agent
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
# engine_smoke — gamedev stack smoke test. Issue 0072a.
|
||||
# Standalone — does NOT use add_imgui_app() / fn_framework.
|
||||
#
|
||||
# Two build modes:
|
||||
# 1. As subdir of cpp/ (cmake -S cpp -B build): cpp/CMakeLists.txt already
|
||||
# adds vendor/sdl3 and exposes SDL3::SDL3-static. We just link.
|
||||
# 2. As top-level (cmake -S cpp/apps/engine_smoke -B build) — used by the
|
||||
# WASM pipeline and any standalone build. We do project() + manually
|
||||
# add_subdirectory the vendored SDL3.
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# Detect mode: top-level vs subdir.
|
||||
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
||||
project(engine_smoke CXX)
|
||||
set(_TOP_LEVEL TRUE)
|
||||
else()
|
||||
set(_TOP_LEVEL FALSE)
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
# Resolve the repo's cpp/vendor regardless of build mode.
|
||||
get_filename_component(_REPO_CPP_ROOT ${CMAKE_CURRENT_LIST_DIR}/../.. ABSOLUTE)
|
||||
set(_VENDOR ${_REPO_CPP_ROOT}/vendor)
|
||||
|
||||
if(_TOP_LEVEL)
|
||||
# Bring in SDL3 ourselves.
|
||||
if(NOT EXISTS ${_VENDOR}/sdl3/CMakeLists.txt)
|
||||
message(FATAL_ERROR "SDL3 vendoring missing: ${_VENDOR}/sdl3 — see cpp/vendor/sdl3.VENDORING.md")
|
||||
endif()
|
||||
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
|
||||
set(SDL_STATIC ON CACHE BOOL "" FORCE)
|
||||
set(SDL_TEST_LIBRARY OFF CACHE BOOL "" FORCE)
|
||||
set(SDL_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(SDL_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||
set(SDL_INSTALL OFF CACHE BOOL "" FORCE)
|
||||
set(SDL_X11_XSCRNSAVER OFF CACHE BOOL "" FORCE)
|
||||
add_subdirectory(${_VENDOR}/sdl3 ${CMAKE_BINARY_DIR}/_sdl3 EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# --- ImGui (sources we need) ---
|
||||
set(_IMGUI_SRCS
|
||||
${_VENDOR}/imgui/imgui.cpp
|
||||
${_VENDOR}/imgui/imgui_demo.cpp
|
||||
${_VENDOR}/imgui/imgui_draw.cpp
|
||||
${_VENDOR}/imgui/imgui_tables.cpp
|
||||
${_VENDOR}/imgui/imgui_widgets.cpp
|
||||
${_VENDOR}/imgui/backends/imgui_impl_sdl3.cpp
|
||||
${_VENDOR}/imgui/backends/imgui_impl_opengl3.cpp
|
||||
)
|
||||
|
||||
add_executable(engine_smoke
|
||||
main.cpp
|
||||
${_IMGUI_SRCS}
|
||||
)
|
||||
|
||||
target_include_directories(engine_smoke PRIVATE
|
||||
${_VENDOR}/imgui
|
||||
${_VENDOR}/sokol
|
||||
)
|
||||
|
||||
target_link_libraries(engine_smoke PRIVATE SDL3::SDL3-static)
|
||||
|
||||
if(NOT EMSCRIPTEN)
|
||||
find_package(OpenGL REQUIRED)
|
||||
target_link_libraries(engine_smoke PRIVATE OpenGL::GL)
|
||||
endif()
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
set_target_properties(engine_smoke PROPERTIES SUFFIX ".html")
|
||||
target_compile_options(engine_smoke PRIVATE
|
||||
-Os
|
||||
-fno-exceptions
|
||||
-fno-rtti
|
||||
-ffunction-sections
|
||||
-fdata-sections
|
||||
)
|
||||
target_link_options(engine_smoke PRIVATE
|
||||
-Os
|
||||
-sUSE_WEBGL2=1
|
||||
-sFULL_ES3=1
|
||||
-sALLOW_MEMORY_GROWTH=1
|
||||
-sINITIAL_MEMORY=33554432
|
||||
-sASSERTIONS=0
|
||||
-sENVIRONMENT=web
|
||||
-Wl,--gc-sections
|
||||
# NOTE: -sFILESYSTEM=0 + --closure=1 broke under SDL3 (closure
|
||||
# references undeclared FS). Saves ~30KB JS + ~30KB wasm if
|
||||
# we ever stub SDL3 filesystem refs out. Re-enable per app.
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
set_target_properties(engine_smoke PROPERTIES WIN32_EXECUTABLE TRUE)
|
||||
endif()
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
name: engine_smoke
|
||||
lang: cpp
|
||||
domain: gamedev
|
||||
description: "Smoke test del stack gamedev: SDL3 + sokol_gfx + Dear ImGui. Valida que el stack compila y corre en PC (Linux/Windows desktop GL) y WASM (WebGL2 via emscripten) antes de invertir tiempo en runtime real (issue 0072a)."
|
||||
tags: [imgui, sdl3, sokol, gamedev, smoke, wasm]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
framework: "imgui"
|
||||
entry_point: "main.cpp"
|
||||
dir_path: "cpp/apps/engine_smoke"
|
||||
repo_url: ""
|
||||
e2e_checks:
|
||||
- id: build_pc
|
||||
cmd: "cmake --build build --target engine_smoke -j"
|
||||
timeout_s: 300
|
||||
- id: smoke_self
|
||||
cmd: "timeout 5 ./build/cpp/apps/engine_smoke/engine_smoke || test $? = 124"
|
||||
timeout_s: 10
|
||||
severity: warning # requires display in CI; warning, not critical
|
||||
- id: build_wasm
|
||||
cmd: "bash bash/functions/pipelines/build_wasm_cpp_app.sh engine_smoke"
|
||||
timeout_s: 600
|
||||
severity: warning # requires emsdk
|
||||
- id: wasm_size_budget
|
||||
cmd: "test -f build/wasm/engine_smoke/engine_smoke.wasm.gz && test $(stat -c%s build/wasm/engine_smoke/engine_smoke.wasm.gz) -lt 1572864"
|
||||
severity: warning
|
||||
---
|
||||
|
||||
# engine_smoke
|
||||
|
||||
Primer experimento del stack gamedev (issue [0072a](../../../dev/issues/0072a-gamedev-smoke-sdl3-sokol-imgui.md)).
|
||||
|
||||
## Que hace
|
||||
|
||||
1. Abre ventana SDL3 1280x720 con context OpenGL.
|
||||
2. Inicializa `sokol_gfx` sobre ese context (GL 3.3 desktop, GLES3 en WASM).
|
||||
3. Pinta un fullscreen triangle con un fragment shader animado (gradient time-based).
|
||||
4. Pinta sobre eso un panel ImGui con FPS, tamaño de ventana y boton Quit.
|
||||
|
||||
Sin `fn_framework`, sin `add_imgui_app()`. CMakeLists standalone para evitar contaminar el shell desktop hasta validar el stack.
|
||||
|
||||
## Build PC (Linux)
|
||||
|
||||
Desde la raiz del fn_registry:
|
||||
|
||||
```bash
|
||||
cmake -S cpp -B build -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build --target engine_smoke -j
|
||||
./build/cpp/apps/engine_smoke/engine_smoke
|
||||
```
|
||||
|
||||
## Build WASM
|
||||
|
||||
Requiere `emsdk` instalado y activado en el shell. Ver `cpp/vendor/sokol.VENDORING.md` y `bash/functions/pipelines/build_wasm_cpp_app.sh`.
|
||||
|
||||
```bash
|
||||
bash bash/functions/pipelines/build_wasm_cpp_app.sh engine_smoke
|
||||
# Sirve build/wasm/engine_smoke/engine_smoke.html en navegador
|
||||
```
|
||||
|
||||
Budget objetivo: gzip ≤ 1.5 MB.
|
||||
|
||||
## Stack vendoreado
|
||||
|
||||
- `cpp/vendor/sdl3/` — SDL3 release-3.4.8
|
||||
- `cpp/vendor/sokol/` — sokol_gfx single-header pinned commit
|
||||
- `cpp/vendor/imgui/` — ImGui 1.92.7 con backends `imgui_impl_sdl3` + `imgui_impl_opengl3`
|
||||
|
||||
## Notas de implementacion
|
||||
|
||||
- Fullscreen tri via `gl_VertexID` (3 vertices, no VBO). Cubre todo el viewport sin atributos.
|
||||
- Uniform `u_time` actualizado por frame.
|
||||
- Backend ImGui = `imgui_impl_opengl3` (oficial, 0 LoC custom). Compatible WebGL2 emscripten.
|
||||
- `SOKOL_GLCORE` desktop / `SOKOL_GLES3` web — selccion compile-time.
|
||||
- `setup_gfx` se llama UNA VEZ tras crear el GL context. `sg_setup` toma `sglue_environment()` que lee el contexto activo.
|
||||
|
||||
## No-objetivos (smoke)
|
||||
|
||||
- No texturas reales (eso entra en 0072b).
|
||||
- No audio.
|
||||
- No input game-style.
|
||||
- No fn_framework integration.
|
||||
- No mobile (Android/iOS son issues 0072g/h).
|
||||
@@ -0,0 +1,242 @@
|
||||
// engine_smoke — gamedev stack smoke test.
|
||||
// Validates SDL3 + sokol_gfx + Dear ImGui (imgui_impl_sdl3 + imgui_impl_opengl3)
|
||||
// builds and runs on PC (desktop GL 3.3) and WASM (WebGL2 via -sFULL_ES3=1).
|
||||
//
|
||||
// Issue 0072a. Standalone — does NOT use fn_framework / add_imgui_app.
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include <cmath>
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
// sokol_gfx — single TU includes the implementation.
|
||||
#define SOKOL_IMPL
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
#define SOKOL_GLES3
|
||||
#else
|
||||
#define SOKOL_GLCORE
|
||||
#endif
|
||||
#include "sokol_gfx.h"
|
||||
#include "sokol_log.h"
|
||||
// sokol_glue.h is NOT used: it pulls in sokol_app symbols. We use SDL3 for
|
||||
// windowing, so we build sg_environment / sg_swapchain manually.
|
||||
|
||||
#include "imgui.h"
|
||||
#include "backends/imgui_impl_sdl3.h"
|
||||
#include "backends/imgui_impl_opengl3.h"
|
||||
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
#include <emscripten/emscripten.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
struct App {
|
||||
SDL_Window* win = nullptr;
|
||||
SDL_GLContext gl = nullptr;
|
||||
sg_pipeline pip{};
|
||||
sg_bindings bind{};
|
||||
sg_pass_action pass_action{};
|
||||
bool running = true;
|
||||
uint64_t frame = 0;
|
||||
};
|
||||
|
||||
App g_app;
|
||||
|
||||
// Fullscreen-quad vertex shader (no inputs, gl_VertexID trick).
|
||||
const char* VS_SRC =
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
"#version 300 es\n"
|
||||
#else
|
||||
"#version 330 core\n"
|
||||
#endif
|
||||
"out vec2 v_uv;\n"
|
||||
"void main() {\n"
|
||||
" vec2 p = vec2((gl_VertexID == 1) ? 3.0 : -1.0, (gl_VertexID == 2) ? 3.0 : -1.0);\n"
|
||||
" v_uv = p * 0.5 + 0.5;\n"
|
||||
" gl_Position = vec4(p, 0.0, 1.0);\n"
|
||||
"}\n";
|
||||
|
||||
const char* FS_SRC =
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
"#version 300 es\n"
|
||||
"precision mediump float;\n"
|
||||
#else
|
||||
"#version 330 core\n"
|
||||
#endif
|
||||
"in vec2 v_uv;\n"
|
||||
"out vec4 frag;\n"
|
||||
"uniform float u_time;\n"
|
||||
"void main() {\n"
|
||||
" float r = 0.5 + 0.5 * sin(u_time + v_uv.x * 6.2831);\n"
|
||||
" float g = 0.5 + 0.5 * sin(u_time * 1.3 + v_uv.y * 6.2831);\n"
|
||||
" float b = 0.5 + 0.5 * sin(u_time * 0.7 + (v_uv.x + v_uv.y) * 6.2831);\n"
|
||||
" frag = vec4(r, g, b, 1.0);\n"
|
||||
"}\n";
|
||||
|
||||
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 w, int h) {
|
||||
sg_swapchain sw{};
|
||||
sw.width = w;
|
||||
sw.height = h;
|
||||
sw.sample_count = 1;
|
||||
sw.color_format = SG_PIXELFORMAT_RGBA8;
|
||||
sw.depth_format = SG_PIXELFORMAT_DEPTH_STENCIL;
|
||||
sw.gl.framebuffer = 0; // default framebuffer
|
||||
return sw;
|
||||
}
|
||||
|
||||
void setup_gfx() {
|
||||
sg_desc d{};
|
||||
d.environment = make_environment();
|
||||
d.logger.func = slog_func;
|
||||
sg_setup(&d);
|
||||
|
||||
sg_shader_desc sd{};
|
||||
sd.vertex_func.source = VS_SRC;
|
||||
sd.fragment_func.source = FS_SRC;
|
||||
sd.uniform_blocks[0].stage = SG_SHADERSTAGE_FRAGMENT;
|
||||
sd.uniform_blocks[0].size = sizeof(float);
|
||||
sd.uniform_blocks[0].layout = SG_UNIFORMLAYOUT_NATIVE;
|
||||
sd.uniform_blocks[0].glsl_uniforms[0].type = SG_UNIFORMTYPE_FLOAT;
|
||||
sd.uniform_blocks[0].glsl_uniforms[0].glsl_name = "u_time";
|
||||
sg_shader shd = sg_make_shader(&sd);
|
||||
|
||||
sg_pipeline_desc pd{};
|
||||
pd.shader = shd;
|
||||
pd.primitive_type = SG_PRIMITIVETYPE_TRIANGLES;
|
||||
g_app.pip = sg_make_pipeline(&pd);
|
||||
|
||||
g_app.pass_action.colors[0].load_action = SG_LOADACTION_CLEAR;
|
||||
g_app.pass_action.colors[0].clear_value = {0.05f, 0.05f, 0.08f, 1.0f};
|
||||
}
|
||||
|
||||
void frame() {
|
||||
SDL_Event ev;
|
||||
while (SDL_PollEvent(&ev)) {
|
||||
ImGui_ImplSDL3_ProcessEvent(&ev);
|
||||
if (ev.type == SDL_EVENT_QUIT) g_app.running = false;
|
||||
if (ev.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED) g_app.running = false;
|
||||
}
|
||||
|
||||
int w, h;
|
||||
SDL_GetWindowSizeInPixels(g_app.win, &w, &h);
|
||||
|
||||
sg_pass p{};
|
||||
p.action = g_app.pass_action;
|
||||
p.swapchain = make_swapchain(w, h);
|
||||
sg_begin_pass(&p);
|
||||
sg_apply_pipeline(g_app.pip);
|
||||
|
||||
float t = (float)g_app.frame * (1.0f / 60.0f);
|
||||
sg_range time_range{ &t, sizeof(t) };
|
||||
sg_apply_uniforms(0, &time_range);
|
||||
|
||||
sg_draw(0, 3, 1); // 3 vertices, fullscreen tri via gl_VertexID
|
||||
|
||||
// ImGui on top
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplSDL3_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(10, 10));
|
||||
ImGui::Begin("engine_smoke", nullptr,
|
||||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove);
|
||||
ImGui::Text("SDL3 + sokol_gfx + ImGui");
|
||||
ImGui::Text("FPS: %.1f", ImGui::GetIO().Framerate);
|
||||
ImGui::Text("Frame: %llu Size: %dx%d", (unsigned long long)g_app.frame, w, h);
|
||||
if (ImGui::Button("Quit")) g_app.running = false;
|
||||
ImGui::End();
|
||||
|
||||
ImGui::Render();
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
|
||||
sg_end_pass();
|
||||
sg_commit();
|
||||
SDL_GL_SwapWindow(g_app.win);
|
||||
g_app.frame++;
|
||||
}
|
||||
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
void emscripten_loop() {
|
||||
if (!g_app.running) {
|
||||
emscripten_cancel_main_loop();
|
||||
return;
|
||||
}
|
||||
frame();
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
||||
std::fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError());
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
||||
#else
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
||||
#endif
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
||||
|
||||
g_app.win = SDL_CreateWindow("engine_smoke", 1280, 720,
|
||||
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY);
|
||||
if (!g_app.win) {
|
||||
std::fprintf(stderr, "SDL_CreateWindow failed: %s\n", SDL_GetError());
|
||||
SDL_Quit();
|
||||
return 1;
|
||||
}
|
||||
g_app.gl = SDL_GL_CreateContext(g_app.win);
|
||||
if (!g_app.gl) {
|
||||
std::fprintf(stderr, "SDL_GL_CreateContext failed: %s\n", SDL_GetError());
|
||||
SDL_DestroyWindow(g_app.win);
|
||||
SDL_Quit();
|
||||
return 1;
|
||||
}
|
||||
SDL_GL_MakeCurrent(g_app.win, g_app.gl);
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
|
||||
setup_gfx();
|
||||
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGui::StyleColorsDark();
|
||||
ImGui_ImplSDL3_InitForOpenGL(g_app.win, g_app.gl);
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
ImGui_ImplOpenGL3_Init("#version 300 es");
|
||||
#else
|
||||
ImGui_ImplOpenGL3_Init("#version 330 core");
|
||||
#endif
|
||||
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
emscripten_set_main_loop(emscripten_loop, 0, 1);
|
||||
#else
|
||||
while (g_app.running) frame();
|
||||
#endif
|
||||
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplSDL3_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
sg_shutdown();
|
||||
SDL_GL_DestroyContext(g_app.gl);
|
||||
SDL_DestroyWindow(g_app.win);
|
||||
SDL_Quit();
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user