// 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 #include #include #include // 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 #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; }