diff --git a/CMakeLists.txt b/CMakeLists.txt index faff68c..265f910 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,10 +11,22 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) # Headless build option (no SFML, no graphics - for server/testing/Emscripten prep) option(MCRF_HEADLESS "Build without graphics dependencies (SFML, ImGui)" OFF) -# Emscripten builds are always headless (no SFML yet - using stubs) +# SDL2 backend option (SDL2 + OpenGL ES 2 - for Emscripten/WebGL, Android, cross-platform) +option(MCRF_SDL2 "Build with SDL2+OpenGL ES 2 backend instead of SFML" OFF) + +# Emscripten builds: use SDL2 if specified, otherwise fall back to headless if(EMSCRIPTEN) - set(MCRF_HEADLESS ON) - message(STATUS "Emscripten detected - forcing HEADLESS mode") + if(MCRF_SDL2) + message(STATUS "Emscripten detected - using SDL2 backend") + set(MCRF_HEADLESS OFF) + else() + set(MCRF_HEADLESS ON) + message(STATUS "Emscripten detected - forcing HEADLESS mode (use -DMCRF_SDL2=ON for graphics)") + endif() +endif() + +if(MCRF_SDL2) + message(STATUS "Building with SDL2 backend - SDL2+OpenGL ES 2") endif() if(MCRF_HEADLESS) @@ -36,9 +48,13 @@ if(EMSCRIPTEN) # Emscripten build: use Python headers compiled for wasm32-emscripten # The pyconfig.h from cross-build has correct LONG_BIT and other settings set(PYTHON_WASM_BUILD "${CMAKE_SOURCE_DIR}/deps/cpython/cross-build/wasm32-emscripten/build/python") + # Force-include wasm pyconfig.h BEFORE anything else to set correct platform defines add_compile_options(-include ${PYTHON_WASM_BUILD}/pyconfig.h) + # Override LONG_BIT - Emscripten's limits.h incorrectly defines it as 64 for wasm32 + add_compile_definitions(LONG_BIT=32) + # Include wasm build directory FIRST so its pyconfig.h is found by #include "pyconfig.h" + include_directories(BEFORE ${PYTHON_WASM_BUILD}) include_directories(${CMAKE_SOURCE_DIR}/deps/cpython/Include) - include_directories(${PYTHON_WASM_BUILD}) # For generated headers message(STATUS "Using Emscripten Python from: ${PYTHON_WASM_BUILD}") elseif(MCRF_CROSS_WINDOWS) # Windows cross-compilation: use cpython headers with PC/pyconfig.h @@ -57,8 +73,9 @@ else() include_directories(${CMAKE_SOURCE_DIR}/deps/Python) endif() -# ImGui and ImGui-SFML include directories (not needed in headless mode) -if(NOT MCRF_HEADLESS) +# ImGui and ImGui-SFML include directories (not needed in headless or SDL2 mode) +# SDL2 builds will use ImGui with SDL2 backend later; for now, no ImGui +if(NOT MCRF_HEADLESS AND NOT MCRF_SDL2) include_directories(${CMAKE_SOURCE_DIR}/modules/imgui) include_directories(${CMAKE_SOURCE_DIR}/modules/imgui-sfml) @@ -75,13 +92,14 @@ endif() # Collect all the source files file(GLOB_RECURSE SOURCES "src/*.cpp") -# Add ImGui sources to the build (only if not headless) -if(NOT MCRF_HEADLESS) +# Add ImGui sources to the build (only if using SFML) +if(NOT MCRF_HEADLESS AND NOT MCRF_SDL2) list(APPEND SOURCES ${IMGUI_SOURCES}) endif() # Find OpenGL (required by ImGui-SFML) - not needed in headless mode -if(NOT MCRF_HEADLESS) +# SDL2 builds handle OpenGL ES 2 differently (via SDL2 or Emscripten) +if(NOT MCRF_HEADLESS AND NOT MCRF_SDL2) if(MCRF_CROSS_WINDOWS) # For cross-compilation, OpenGL is provided by MinGW set(OPENGL_LIBRARIES opengl32) @@ -109,8 +127,27 @@ if(EMSCRIPTEN) ${LIBTCOD_WASM_BUILD}/_deps/lodepng-c-build/liblodepng-c.a ${LIBTCOD_WASM_BUILD}/_deps/utf8proc-build/libutf8proc.a) include_directories(${CMAKE_SOURCE_DIR}/deps/platform/linux) # Use Linux platform stubs for now + # For SDL2 builds, add stb headers for image/font loading + if(MCRF_SDL2) + include_directories(${CMAKE_SOURCE_DIR}/deps/stb) + endif() message(STATUS "Linking Emscripten Python: ${PYTHON_WASM_BUILD}/libpython3.14.a") message(STATUS "Linking Emscripten libtcod: ${LIBTCOD_WASM_BUILD}/libtcod.a") +elseif(MCRF_SDL2) + # SDL2 build (non-Emscripten): link against SDL2 and system libraries + # Note: For desktop SDL2 builds in the future + find_package(SDL2 REQUIRED) + find_package(OpenGL REQUIRED) + set(LINK_LIBS + SDL2::SDL2 + OpenGL::GL + tcod + python3.14 + m dl util pthread) + include_directories(${CMAKE_SOURCE_DIR}/deps/platform/linux) + include_directories(${CMAKE_SOURCE_DIR}/deps/stb) # stb_image.h, stb_truetype.h + link_directories(${CMAKE_SOURCE_DIR}/__lib) + message(STATUS "Building with SDL2 backend (desktop)") elseif(MCRF_HEADLESS) # Headless build: no SFML, no OpenGL if(WIN32 OR MCRF_CROSS_WINDOWS) @@ -195,6 +232,8 @@ endif() add_executable(mcrogueface ${SOURCES}) # Define NO_SDL for libtcod-headless headers (excludes SDL-dependent code) +# We ALWAYS need this because libtcod headers expect SDL3, not SDL2 +# Our SDL2 backend is separate from libtcod's SDL3 renderer target_compile_definitions(mcrogueface PRIVATE NO_SDL) # Define MCRF_HEADLESS for headless builds (excludes SFML/ImGui code) @@ -202,9 +241,15 @@ if(MCRF_HEADLESS) target_compile_definitions(mcrogueface PRIVATE MCRF_HEADLESS) endif() +# Define MCRF_SDL2 for SDL2 builds (uses SDL2+OpenGL ES 2 instead of SFML) +if(MCRF_SDL2) + target_compile_definitions(mcrogueface PRIVATE MCRF_SDL2) +endif() + # Emscripten-specific link options (use ports for zlib, bzip2, sqlite3) if(EMSCRIPTEN) - target_link_options(mcrogueface PRIVATE + # Base Emscripten options + set(EMSCRIPTEN_LINK_OPTIONS -sUSE_ZLIB=1 -sUSE_BZIP2=1 -sUSE_SQLITE3=1 @@ -215,6 +260,9 @@ if(EMSCRIPTEN) -sSTACK_OVERFLOW_CHECK=2 -fexceptions -sNO_DISABLE_EXCEPTION_CATCHING + # Disable features that require dynamic linking support + -sERROR_ON_UNDEFINED_SYMBOLS=0 + -sALLOW_UNIMPLEMENTED_SYSCALLS=1 # Preload Python stdlib into virtual filesystem at /lib/python3.14 --preload-file=${CMAKE_SOURCE_DIR}/wasm_stdlib/lib@/lib # Preload game scripts into /scripts @@ -222,6 +270,24 @@ if(EMSCRIPTEN) # Preload assets --preload-file=${CMAKE_SOURCE_DIR}/assets@/assets ) + + # Add SDL2 options if using SDL2 backend + if(MCRF_SDL2) + list(APPEND EMSCRIPTEN_LINK_OPTIONS + -sUSE_SDL=2 + -sFULL_ES2=1 + -sMIN_WEBGL_VERSION=2 + -sMAX_WEBGL_VERSION=2 + ) + # SDL2 flags are also needed at compile time for headers + target_compile_options(mcrogueface PRIVATE + -sUSE_SDL=2 + ) + message(STATUS "Emscripten SDL2 options enabled: -sUSE_SDL=2 -sFULL_ES2=1") + endif() + + target_link_options(mcrogueface PRIVATE ${EMSCRIPTEN_LINK_OPTIONS}) + # Set Python home for the embedded interpreter target_compile_definitions(mcrogueface PRIVATE MCRF_WASM_PYTHON_HOME="/lib/python3.14" diff --git a/src/Common.h b/src/Common.h index 2827d94..b77c7be 100644 --- a/src/Common.h +++ b/src/Common.h @@ -10,11 +10,15 @@ // ============================================================================= #ifdef MCRF_HEADLESS - // Use headless type stubs instead of SFML + // Use headless type stubs instead of SFML (no graphics, for CI/testing) #include "platform/HeadlessTypes.h" #define MCRF_GRAPHICS_BACKEND "headless" +#elif defined(MCRF_SDL2) + // Use SDL2 + OpenGL ES 2 backend (for Emscripten/WebGL, Android, cross-platform) + #include "platform/SDL2Types.h" + #define MCRF_GRAPHICS_BACKEND "sdl2" #else - // Use SFML for graphics and audio + // Use SFML for graphics and audio (default desktop build) #include #include #define MCRF_GRAPHICS_BACKEND "sfml" diff --git a/src/GameEngine.cpp b/src/GameEngine.cpp index 774ed6a..68c3f8e 100644 --- a/src/GameEngine.cpp +++ b/src/GameEngine.cpp @@ -7,7 +7,8 @@ #include "Animation.h" #include "Timer.h" #include "BenchmarkLogger.h" -#ifndef MCRF_HEADLESS +// ImGui is only available for SFML builds (not headless, not SDL2) +#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) #include "imgui.h" #include "imgui-SFML.h" #endif @@ -86,8 +87,8 @@ GameEngine::GameEngine(const McRogueFaceConfig& cfg) window->setFramerateLimit(60); render_target = window.get(); -#ifndef MCRF_HEADLESS - // Initialize ImGui for the window +#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) + // Initialize ImGui for the window (SFML builds only) if (ImGui::SFML::Init(*window)) { imguiInitialized = true; // Register settings handler before .ini is loaded (happens on first frame) @@ -199,7 +200,7 @@ void GameEngine::cleanup() } // Shutdown ImGui AFTER window is closed to avoid X11 BadCursor errors -#ifndef MCRF_HEADLESS +#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) if (imguiInitialized) { ImGui::SFML::Shutdown(); imguiInitialized = false; @@ -361,8 +362,8 @@ void GameEngine::doFrame() if (!headless) { sUserInput(); -#ifndef MCRF_HEADLESS - // Update ImGui +#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) + // Update ImGui (SFML builds only) if (imguiInitialized) { ImGui::SFML::Update(*window, clock.getElapsedTime()); } @@ -405,8 +406,8 @@ void GameEngine::doFrame() profilerOverlay->render(*render_target); } -#ifndef MCRF_HEADLESS - // Render ImGui overlays (console and scene explorer) +#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) + // Render ImGui overlays (console and scene explorer) - SFML builds only if (imguiInitialized && !headless) { console.render(); sceneExplorer.render(*this); @@ -593,8 +594,8 @@ void GameEngine::sUserInput() sf::Event event; while (window && window->pollEvent(event)) { -#ifndef MCRF_HEADLESS - // Process event through ImGui first +#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) + // Process event through ImGui first (SFML builds only) if (imguiInitialized) { ImGui::SFML::ProcessEvent(*window, event); } diff --git a/src/GameEngine.h b/src/GameEngine.h index f9d7a40..36d507c 100644 --- a/src/GameEngine.h +++ b/src/GameEngine.h @@ -10,7 +10,8 @@ #include "HeadlessRenderer.h" #include "SceneTransition.h" #include "Profiler.h" -#ifndef MCRF_HEADLESS +// ImGui is only available for SFML builds (not headless, not SDL2) +#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) #include "ImGuiConsole.h" #include "ImGuiSceneExplorer.h" #endif @@ -196,7 +197,8 @@ private: int overlayUpdateCounter = 0; // Only update overlay every N frames ProfilerOverlay* profilerOverlay = nullptr; // The actual overlay renderer -#ifndef MCRF_HEADLESS +// ImGui is only available for SFML builds +#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) // ImGui console overlay ImGuiConsole console; ImGuiSceneExplorer sceneExplorer; diff --git a/src/ImGuiConsole.cpp b/src/ImGuiConsole.cpp index 6f5f149..284f8d0 100644 --- a/src/ImGuiConsole.cpp +++ b/src/ImGuiConsole.cpp @@ -1,7 +1,7 @@ // ImGuiConsole.cpp - Debug console using ImGui -// This file is excluded from headless builds (no GUI/debug interface needed) +// This file is excluded from headless and SDL2 builds (ImGui-SFML only) -#ifndef MCRF_HEADLESS +#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) #include "ImGuiConsole.h" #include "imgui.h" diff --git a/src/ImGuiConsole.h b/src/ImGuiConsole.h index e705936..0184bb4 100644 --- a/src/ImGuiConsole.h +++ b/src/ImGuiConsole.h @@ -1,7 +1,7 @@ #pragma once -// ImGuiConsole - excluded from headless builds (no GUI/debug interface) -#ifndef MCRF_HEADLESS +// ImGuiConsole - excluded from headless and SDL2 builds (ImGui-SFML only) +#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) #include #include diff --git a/src/ImGuiSceneExplorer.cpp b/src/ImGuiSceneExplorer.cpp index 0eb7c03..e255bbf 100644 --- a/src/ImGuiSceneExplorer.cpp +++ b/src/ImGuiSceneExplorer.cpp @@ -1,7 +1,7 @@ // ImGuiSceneExplorer.cpp - Debug scene hierarchy explorer using ImGui -// This file is excluded from headless builds (no GUI/debug interface needed) +// This file is excluded from headless and SDL2 builds (ImGui-SFML only) -#ifndef MCRF_HEADLESS +#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) #include "ImGuiSceneExplorer.h" #include "imgui.h" diff --git a/src/ImGuiSceneExplorer.h b/src/ImGuiSceneExplorer.h index 7293ed3..76ef22e 100644 --- a/src/ImGuiSceneExplorer.h +++ b/src/ImGuiSceneExplorer.h @@ -1,7 +1,7 @@ #pragma once -// ImGuiSceneExplorer - excluded from headless builds (no GUI/debug interface) -#ifndef MCRF_HEADLESS +// ImGuiSceneExplorer - excluded from headless and SDL2 builds (ImGui-SFML only) +#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) #include #include diff --git a/src/McRFPy_API.cpp b/src/McRFPy_API.cpp index c06eee3..292236d 100644 --- a/src/McRFPy_API.cpp +++ b/src/McRFPy_API.cpp @@ -32,7 +32,8 @@ #include "PyUniformCollection.h" // Shader uniform collection (#106) #include "McRogueFaceVersion.h" #include "GameEngine.h" -#ifndef MCRF_HEADLESS +// ImGui is only available for SFML builds +#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) #include "ImGuiConsole.h" #endif #include "BenchmarkLogger.h" @@ -1627,7 +1628,7 @@ PyObject* McRFPy_API::_setDevConsole(PyObject* self, PyObject* args) { return NULL; } -#ifndef MCRF_HEADLESS +#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) ImGuiConsole::setEnabled(enabled); #endif Py_RETURN_NONE; diff --git a/src/platform/EmscriptenStubs.cpp b/src/platform/EmscriptenStubs.cpp new file mode 100644 index 0000000..105c79d --- /dev/null +++ b/src/platform/EmscriptenStubs.cpp @@ -0,0 +1,38 @@ +// EmscriptenStubs.cpp - Stub implementations for missing POSIX functions in Emscripten +// These functions are referenced by Python's posixmodule and timemodule but not available in WASM + +#ifdef __EMSCRIPTEN__ + +#include +#include +#include +#include + +extern "C" { + +// wait3 and wait4 are BSD-style wait functions not available in Emscripten +// Python's posixmodule references them but they're not critical for WASM +pid_t wait3(int *status, int options, struct rusage *rusage) { + errno = ENOSYS; + return -1; +} + +pid_t wait4(pid_t pid, int *status, int options, struct rusage *rusage) { + errno = ENOSYS; + return -1; +} + +// wcsftime - wide character strftime, used by Python's timemodule +// Provide a minimal implementation that returns 0 (no characters written) +size_t wcsftime(wchar_t *wcs, size_t maxsize, const wchar_t *format, const struct tm *timeptr) { + // In a full implementation, this would format time as wide chars + // For WASM, just return 0 to indicate nothing written + if (maxsize > 0) { + wcs[0] = L'\0'; + } + return 0; +} + +} // extern "C" + +#endif // __EMSCRIPTEN__ diff --git a/src/platform/SDL2Renderer.cpp b/src/platform/SDL2Renderer.cpp new file mode 100644 index 0000000..d558875 --- /dev/null +++ b/src/platform/SDL2Renderer.cpp @@ -0,0 +1,1338 @@ +// SDL2Renderer.cpp - OpenGL ES 2 rendering implementation for SDL2 backend +// Implements the SDL2 types defined in SDL2Types.h using SDL2 and OpenGL ES 2 + +#ifdef MCRF_SDL2 + +#include "SDL2Renderer.h" +#include "SDL2Types.h" + +#include +#include +#include + +// SDL2 and OpenGL ES 2 headers +#ifdef __EMSCRIPTEN__ +#include +#include +// Emscripten's USE_SDL=2 port puts headers directly in include path +#include +#include +#else +#include +#include +// Desktop OpenGL - we'll use GL 2.1 compatible subset that matches GLES2 +#define GL_GLEXT_PROTOTYPES +#include +#endif + +// stb libraries for image/font loading (from deps/stb/) +#define STB_IMAGE_IMPLEMENTATION +#include + +#define STB_TRUETYPE_IMPLEMENTATION +#include + +namespace sf { + +// ============================================================================= +// Built-in Shaders (GLSL ES 2.0 / GLSL 1.20 compatible) +// ============================================================================= + +static const char* SHAPE_VERTEX_SHADER = R"( +#ifdef GL_ES +precision mediump float; +#endif +attribute vec2 a_position; +attribute vec4 a_color; +uniform mat4 u_projection; +varying vec4 v_color; + +void main() { + gl_Position = u_projection * vec4(a_position, 0.0, 1.0); + v_color = a_color; +} +)"; + +static const char* SHAPE_FRAGMENT_SHADER = R"( +#ifdef GL_ES +precision mediump float; +#endif +varying vec4 v_color; + +void main() { + gl_FragColor = v_color; +} +)"; + +static const char* SPRITE_VERTEX_SHADER = R"( +#ifdef GL_ES +precision mediump float; +#endif +attribute vec2 a_position; +attribute vec4 a_color; +attribute vec2 a_texcoord; +uniform mat4 u_projection; +varying vec4 v_color; +varying vec2 v_texcoord; + +void main() { + gl_Position = u_projection * vec4(a_position, 0.0, 1.0); + v_color = a_color; + v_texcoord = a_texcoord; +} +)"; + +static const char* SPRITE_FRAGMENT_SHADER = R"( +#ifdef GL_ES +precision mediump float; +#endif +varying vec4 v_color; +varying vec2 v_texcoord; +uniform sampler2D u_texture; + +void main() { + gl_FragColor = texture2D(u_texture, v_texcoord) * v_color; +} +)"; + +// Text shader is same as sprite for now +static const char* TEXT_VERTEX_SHADER = SPRITE_VERTEX_SHADER; +static const char* TEXT_FRAGMENT_SHADER = R"( +#ifdef GL_ES +precision mediump float; +#endif +varying vec4 v_color; +varying vec2 v_texcoord; +uniform sampler2D u_texture; + +void main() { + // Text rendering: use texture alpha as coverage + float alpha = texture2D(u_texture, v_texcoord).a; + gl_FragColor = vec4(v_color.rgb, v_color.a * alpha); +} +)"; + +// ============================================================================= +// SDL2Renderer Implementation +// ============================================================================= + +SDL2Renderer& SDL2Renderer::getInstance() { + static SDL2Renderer instance; + return instance; +} + +bool SDL2Renderer::init() { + if (initialized_) return true; + + // Initialize SDL2 if not already done + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) { + std::cerr << "SDL2Renderer: Failed to initialize SDL: " << SDL_GetError() << std::endl; + return false; + } + + // Note: Shaders are initialized in initGL() after GL context is created + + // Set up initial projection matrix (identity) + memset(projectionMatrix_, 0, sizeof(projectionMatrix_)); + projectionMatrix_[0] = 1.0f; + projectionMatrix_[5] = 1.0f; + projectionMatrix_[10] = 1.0f; + projectionMatrix_[15] = 1.0f; + + initialized_ = true; + return true; +} + +bool SDL2Renderer::initGL() { + if (glInitialized_) return true; + + // Initialize built-in shaders (requires active GL context) + initBuiltinShaders(); + + glInitialized_ = true; + return true; +} + +void SDL2Renderer::shutdown() { + if (!initialized_) return; + + // Delete built-in shader programs + if (shapeProgram_) glDeleteProgram(shapeProgram_); + if (spriteProgram_) glDeleteProgram(spriteProgram_); + if (textProgram_) glDeleteProgram(textProgram_); + + shapeProgram_ = spriteProgram_ = textProgram_ = 0; + + SDL_Quit(); + initialized_ = false; +} + +void SDL2Renderer::initBuiltinShaders() { + // Compile shape shader + if (!compileAndLinkProgram(SHAPE_VERTEX_SHADER, SHAPE_FRAGMENT_SHADER, shapeProgram_)) { + std::cerr << "SDL2Renderer: Failed to compile shape shader" << std::endl; + } + + // Compile sprite shader + if (!compileAndLinkProgram(SPRITE_VERTEX_SHADER, SPRITE_FRAGMENT_SHADER, spriteProgram_)) { + std::cerr << "SDL2Renderer: Failed to compile sprite shader" << std::endl; + } + + // Compile text shader + if (!compileAndLinkProgram(TEXT_VERTEX_SHADER, TEXT_FRAGMENT_SHADER, textProgram_)) { + std::cerr << "SDL2Renderer: Failed to compile text shader" << std::endl; + } +} + +unsigned int SDL2Renderer::compileShaderStage(unsigned int type, const char* source) { + unsigned int shader = glCreateShader(type); + glShaderSource(shader, 1, &source, nullptr); + glCompileShader(shader); + + int success; + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + if (!success) { + char infoLog[512]; + glGetShaderInfoLog(shader, 512, nullptr, infoLog); + std::cerr << "SDL2Renderer: Shader compilation failed: " << infoLog << std::endl; + glDeleteShader(shader); + return 0; + } + + return shader; +} + +bool SDL2Renderer::compileAndLinkProgram(const char* vertexSrc, const char* fragmentSrc, unsigned int& programOut) { + unsigned int vertexShader = compileShaderStage(GL_VERTEX_SHADER, vertexSrc); + if (!vertexShader) return false; + + unsigned int fragmentShader = compileShaderStage(GL_FRAGMENT_SHADER, fragmentSrc); + if (!fragmentShader) { + glDeleteShader(vertexShader); + return false; + } + + unsigned int program = glCreateProgram(); + glAttachShader(program, vertexShader); + glAttachShader(program, fragmentShader); + + // Bind attribute locations before linking + glBindAttribLocation(program, 0, "a_position"); + glBindAttribLocation(program, 1, "a_color"); + glBindAttribLocation(program, 2, "a_texcoord"); + + glLinkProgram(program); + + // Shaders can be deleted after linking + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + + int success; + glGetProgramiv(program, GL_LINK_STATUS, &success); + if (!success) { + char infoLog[512]; + glGetProgramInfoLog(program, 512, nullptr, infoLog); + std::cerr << "SDL2Renderer: Program linking failed: " << infoLog << std::endl; + glDeleteProgram(program); + return false; + } + + programOut = program; + return true; +} + +unsigned int SDL2Renderer::getShaderProgram(ShaderType type) const { + switch (type) { + case ShaderType::Shape: return shapeProgram_; + case ShaderType::Sprite: return spriteProgram_; + case ShaderType::Text: return textProgram_; + default: return 0; + } +} + +unsigned int SDL2Renderer::compileShader(const std::string& vertexSource, const std::string& fragmentSource) { + unsigned int program = 0; + if (compileAndLinkProgram(vertexSource.c_str(), fragmentSource.c_str(), program)) { + return program; + } + return 0; +} + +void SDL2Renderer::deleteShaderProgram(unsigned int programId) { + if (programId) { + glDeleteProgram(programId); + } +} + +unsigned int SDL2Renderer::createTexture(unsigned int width, unsigned int height, const unsigned char* pixels) { + unsigned int textureId; + glGenTextures(1, &textureId); + glBindTexture(GL_TEXTURE_2D, textureId); + + // Set default texture parameters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // Upload pixel data (RGBA format) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + return textureId; +} + +void SDL2Renderer::updateTexture(unsigned int textureId, unsigned int x, unsigned int y, + unsigned int width, unsigned int height, const unsigned char* pixels) { + glBindTexture(GL_TEXTURE_2D, textureId); + glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); +} + +void SDL2Renderer::deleteTexture(unsigned int textureId) { + if (textureId) { + glDeleteTextures(1, &textureId); + } +} + +void SDL2Renderer::setTextureSmooth(unsigned int textureId, bool smooth) { + glBindTexture(GL_TEXTURE_2D, textureId); + GLint filter = smooth ? GL_LINEAR : GL_NEAREST; + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); +} + +void SDL2Renderer::setTextureRepeated(unsigned int textureId, bool repeated) { + glBindTexture(GL_TEXTURE_2D, textureId); + GLint wrap = repeated ? GL_REPEAT : GL_CLAMP_TO_EDGE; + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap); +} + +unsigned int SDL2Renderer::createFBO(unsigned int width, unsigned int height, unsigned int& colorTexture) { + // Create color texture + colorTexture = createTexture(width, height, nullptr); + + // Create FBO + unsigned int fbo; + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + // Attach color texture + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0); + + // Check completeness + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + std::cerr << "SDL2Renderer: FBO is not complete" << std::endl; + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &fbo); + deleteTexture(colorTexture); + return 0; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + return fbo; +} + +void SDL2Renderer::deleteFBO(unsigned int fboId) { + if (fboId) { + glDeleteFramebuffers(1, &fboId); + } +} + +void SDL2Renderer::bindFBO(unsigned int fboId) { + fboStack_.push_back(fboId); + glBindFramebuffer(GL_FRAMEBUFFER, fboId); +} + +void SDL2Renderer::unbindFBO() { + if (!fboStack_.empty()) { + fboStack_.pop_back(); + } + unsigned int fbo = fboStack_.empty() ? 0 : fboStack_.back(); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); +} + +void SDL2Renderer::setViewport(int x, int y, unsigned int width, unsigned int height) { + glViewport(x, y, width, height); +} + +void SDL2Renderer::setProjection(float left, float right, float bottom, float top) { + // Build orthographic projection matrix + float near = -1.0f; + float far = 1.0f; + + memset(projectionMatrix_, 0, sizeof(projectionMatrix_)); + projectionMatrix_[0] = 2.0f / (right - left); + projectionMatrix_[5] = 2.0f / (top - bottom); + projectionMatrix_[10] = -2.0f / (far - near); + projectionMatrix_[12] = -(right + left) / (right - left); + projectionMatrix_[13] = -(top + bottom) / (top - bottom); + projectionMatrix_[14] = -(far + near) / (far - near); + projectionMatrix_[15] = 1.0f; +} + +void SDL2Renderer::clear(float r, float g, float b, float a) { + glClearColor(r, g, b, a); + glClear(GL_COLOR_BUFFER_BIT); +} + +void SDL2Renderer::drawTriangles(const float* vertices, size_t vertexCount, + const float* colors, const float* texCoords, + unsigned int textureId) { + if (vertexCount == 0) return; + + unsigned int program = textureId ? spriteProgram_ : shapeProgram_; + glUseProgram(program); + + // Set projection uniform + int projLoc = glGetUniformLocation(program, "u_projection"); + glUniformMatrix4fv(projLoc, 1, GL_FALSE, projectionMatrix_); + + // Enable blending + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // Set up vertex attributes + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices); + + if (colors) { + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, colors); + } + + if (texCoords && textureId) { + glEnableVertexAttribArray(2); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, texCoords); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, textureId); + int texLoc = glGetUniformLocation(program, "u_texture"); + glUniform1i(texLoc, 0); + } + + // Draw + glDrawArrays(GL_TRIANGLES, 0, vertexCount); + + // Clean up + glDisableVertexAttribArray(0); + if (colors) glDisableVertexAttribArray(1); + if (texCoords && textureId) glDisableVertexAttribArray(2); +} + +// ============================================================================= +// sf::RenderWindow Implementation +// ============================================================================= + +RenderWindow::~RenderWindow() { + close(); +} + +void RenderWindow::create(VideoMode mode, const std::string& title, uint32_t style) { + // Close any existing window + close(); + + // Initialize SDL2 renderer + if (!SDL2Renderer::getInstance().init()) { + std::cerr << "RenderWindow: Failed to initialize SDL2Renderer" << std::endl; + return; + } + + // Set OpenGL attributes for ES2/WebGL compatibility + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); +#ifdef __EMSCRIPTEN__ + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); +#else + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); +#endif + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0); + + // Convert sf::Style to SDL window flags + Uint32 sdlFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN; + if (style & Style::Fullscreen) { + sdlFlags |= SDL_WINDOW_FULLSCREEN; + } + if (style & Style::Resize) { + sdlFlags |= SDL_WINDOW_RESIZABLE; + } + if (!(style & Style::Titlebar)) { + sdlFlags |= SDL_WINDOW_BORDERLESS; + } + +#ifdef __EMSCRIPTEN__ + // For Emscripten, we need to set the canvas size explicitly + // The canvas element with id="canvas" is used by default + emscripten_set_canvas_element_size("#canvas", mode.width, mode.height); +#endif + + // Create window + SDL_Window* window = SDL_CreateWindow( + title.c_str(), + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + mode.width, mode.height, + sdlFlags + ); + + if (!window) { + std::cerr << "RenderWindow: Failed to create window: " << SDL_GetError() << std::endl; + return; + } + + // Create OpenGL context + SDL_GLContext context = SDL_GL_CreateContext(window); + if (!context) { + std::cerr << "RenderWindow: Failed to create GL context: " << SDL_GetError() << std::endl; + SDL_DestroyWindow(window); + return; + } + + sdlWindow_ = window; + glContext_ = context; + size_ = Vector2u(mode.width, mode.height); + title_ = title; + open_ = true; + + // Initialize OpenGL resources now that we have a context + if (!SDL2Renderer::getInstance().initGL()) { + std::cerr << "RenderWindow: Failed to initialize OpenGL resources" << std::endl; + } + + // Set up initial view + view_ = View(FloatRect(0, 0, static_cast(mode.width), static_cast(mode.height))); + defaultView_ = view_; + + // Set up OpenGL state + glViewport(0, 0, mode.width, mode.height); + SDL2Renderer::getInstance().setProjection(0, mode.width, mode.height, 0); + + // Enable blending for transparency + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // Initial clear to a visible color to confirm GL is working + glClearColor(0.2f, 0.3f, 0.4f, 1.0f); // Blue-gray + glClear(GL_COLOR_BUFFER_BIT); + SDL_GL_SwapWindow(window); + + std::cout << "RenderWindow: Created " << mode.width << "x" << mode.height << " window" << std::endl; +} + +void RenderWindow::close() { + if (glContext_) { + SDL_GL_DeleteContext(static_cast(glContext_)); + glContext_ = nullptr; + } + if (sdlWindow_) { + SDL_DestroyWindow(static_cast(sdlWindow_)); + sdlWindow_ = nullptr; + } + open_ = false; +} + +void RenderWindow::clear(const Color& color) { + SDL2Renderer::getInstance().clear( + color.r / 255.0f, + color.g / 255.0f, + color.b / 255.0f, + color.a / 255.0f + ); +} + +void RenderWindow::display() { + SDL_GL_SwapWindow(static_cast(sdlWindow_)); +} + +void RenderWindow::setTitle(const std::string& title) { + title_ = title; + if (sdlWindow_) { + SDL_SetWindowTitle(static_cast(sdlWindow_), title.c_str()); + } +} + +void RenderWindow::setFramerateLimit(unsigned int limit) { + // SDL2 doesn't have built-in framerate limiting + // We'd need to implement this manually with timing + // For now, VSync is the recommended approach +} + +void RenderWindow::setVerticalSyncEnabled(bool enabled) { + SDL_GL_SetSwapInterval(enabled ? 1 : 0); +} + +void RenderWindow::setVisible(bool visible) { + if (sdlWindow_) { + if (visible) { + SDL_ShowWindow(static_cast(sdlWindow_)); + } else { + SDL_HideWindow(static_cast(sdlWindow_)); + } + } +} + +void RenderWindow::setMouseCursorVisible(bool visible) { + SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE); +} + +void RenderWindow::setMouseCursorGrabbed(bool grabbed) { + if (sdlWindow_) { + SDL_SetWindowGrab(static_cast(sdlWindow_), grabbed ? SDL_TRUE : SDL_FALSE); + } +} + +Vector2i RenderWindow::getPosition() const { + int x = 0, y = 0; + if (sdlWindow_) { + SDL_GetWindowPosition(static_cast(sdlWindow_), &x, &y); + } + return Vector2i(x, y); +} + +void RenderWindow::setPosition(const Vector2i& position) { + if (sdlWindow_) { + SDL_SetWindowPosition(static_cast(sdlWindow_), position.x, position.y); + } +} + +void RenderWindow::setSize(const Vector2u& size) { + size_ = size; + if (sdlWindow_) { + SDL_SetWindowSize(static_cast(sdlWindow_), size.x, size.y); + glViewport(0, 0, size.x, size.y); + } +} + +// Event polling - translate SDL events to sf::Event +bool RenderWindow::pollEvent(Event& event) { + SDL_Event sdlEvent; + while (SDL_PollEvent(&sdlEvent)) { + if (translateSDLEvent(&sdlEvent, &event)) { + return true; + } + } + return false; +} + +bool RenderWindow::waitEvent(Event& event) { + SDL_Event sdlEvent; + if (SDL_WaitEvent(&sdlEvent)) { + return translateSDLEvent(&sdlEvent, &event); + } + return false; +} + +// ============================================================================= +// VideoMode Implementation +// ============================================================================= + +VideoMode VideoMode::getDesktopMode() { + SDL_DisplayMode mode; + if (SDL_GetDesktopDisplayMode(0, &mode) == 0) { + return VideoMode(mode.w, mode.h, SDL_BITSPERPIXEL(mode.format)); + } + return VideoMode(1920, 1080, 32); +} + +const std::vector& VideoMode::getFullscreenModes() { + static std::vector modes; + static bool initialized = false; + + if (!initialized) { + int numModes = SDL_GetNumDisplayModes(0); + for (int i = 0; i < numModes; ++i) { + SDL_DisplayMode mode; + if (SDL_GetDisplayMode(0, i, &mode) == 0) { + modes.push_back(VideoMode(mode.w, mode.h, SDL_BITSPERPIXEL(mode.format))); + } + } + initialized = true; + } + + return modes; +} + +// ============================================================================= +// Event Translation +// ============================================================================= + +// SDL scancode to sf::Keyboard::Key mapping table +static const Keyboard::Key SDL_SCANCODE_TO_SF_KEY[] = { + // This is a simplified mapping - full implementation would have all keys + Keyboard::Unknown // Placeholder +}; + +bool translateSDLEvent(const void* sdlEventPtr, void* sfEventPtr) { + const SDL_Event& sdlEvent = *static_cast(sdlEventPtr); + Event& sfEvent = *static_cast(sfEventPtr); + + switch (sdlEvent.type) { + case SDL_QUIT: + sfEvent.type = Event::Closed; + return true; + + case SDL_WINDOWEVENT: + switch (sdlEvent.window.event) { + case SDL_WINDOWEVENT_RESIZED: + case SDL_WINDOWEVENT_SIZE_CHANGED: + sfEvent.type = Event::Resized; + sfEvent.size.width = sdlEvent.window.data1; + sfEvent.size.height = sdlEvent.window.data2; + return true; + case SDL_WINDOWEVENT_FOCUS_GAINED: + sfEvent.type = Event::GainedFocus; + return true; + case SDL_WINDOWEVENT_FOCUS_LOST: + sfEvent.type = Event::LostFocus; + return true; + case SDL_WINDOWEVENT_ENTER: + sfEvent.type = Event::MouseEntered; + return true; + case SDL_WINDOWEVENT_LEAVE: + sfEvent.type = Event::MouseLeft; + return true; + } + break; + + case SDL_KEYDOWN: + case SDL_KEYUP: + sfEvent.type = sdlEvent.type == SDL_KEYDOWN ? Event::KeyPressed : Event::KeyReleased; + sfEvent.key.code = static_cast(sdlScancodeToSfKey(sdlEvent.key.keysym.scancode)); + sfEvent.key.alt = (sdlEvent.key.keysym.mod & KMOD_ALT) != 0; + sfEvent.key.control = (sdlEvent.key.keysym.mod & KMOD_CTRL) != 0; + sfEvent.key.shift = (sdlEvent.key.keysym.mod & KMOD_SHIFT) != 0; + sfEvent.key.system = (sdlEvent.key.keysym.mod & KMOD_GUI) != 0; + return true; + + case SDL_TEXTINPUT: + sfEvent.type = Event::TextEntered; + // Convert UTF-8 to single codepoint (simplified - only handles ASCII and simple UTF-8) + sfEvent.text.unicode = static_cast(sdlEvent.text.text[0]); + return true; + + case SDL_MOUSEMOTION: + sfEvent.type = Event::MouseMoved; + sfEvent.mouseMove.x = sdlEvent.motion.x; + sfEvent.mouseMove.y = sdlEvent.motion.y; + return true; + + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + sfEvent.type = sdlEvent.type == SDL_MOUSEBUTTONDOWN ? Event::MouseButtonPressed : Event::MouseButtonReleased; + sfEvent.mouseButton.button = static_cast(sdlButtonToSfButton(sdlEvent.button.button)); + sfEvent.mouseButton.x = sdlEvent.button.x; + sfEvent.mouseButton.y = sdlEvent.button.y; + return true; + + case SDL_MOUSEWHEEL: + sfEvent.type = Event::MouseWheelScrolled; + sfEvent.mouseWheelScroll.wheel = sdlEvent.wheel.x != 0 ? Mouse::HorizontalWheel : Mouse::VerticalWheel; + sfEvent.mouseWheelScroll.delta = sdlEvent.wheel.x != 0 ? sdlEvent.wheel.x : sdlEvent.wheel.y; + // Get current mouse position + SDL_GetMouseState(&sfEvent.mouseWheelScroll.x, &sfEvent.mouseWheelScroll.y); + return true; + } + + return false; +} + +// ============================================================================= +// Keyboard/Mouse Implementation +// ============================================================================= + +int sdlScancodeToSfKey(int sdlScancode) { + // Simplified mapping - covers most common keys + switch (sdlScancode) { + case SDL_SCANCODE_A: return Keyboard::A; + case SDL_SCANCODE_B: return Keyboard::B; + case SDL_SCANCODE_C: return Keyboard::C; + case SDL_SCANCODE_D: return Keyboard::D; + case SDL_SCANCODE_E: return Keyboard::E; + case SDL_SCANCODE_F: return Keyboard::F; + case SDL_SCANCODE_G: return Keyboard::G; + case SDL_SCANCODE_H: return Keyboard::H; + case SDL_SCANCODE_I: return Keyboard::I; + case SDL_SCANCODE_J: return Keyboard::J; + case SDL_SCANCODE_K: return Keyboard::K; + case SDL_SCANCODE_L: return Keyboard::L; + case SDL_SCANCODE_M: return Keyboard::M; + case SDL_SCANCODE_N: return Keyboard::N; + case SDL_SCANCODE_O: return Keyboard::O; + case SDL_SCANCODE_P: return Keyboard::P; + case SDL_SCANCODE_Q: return Keyboard::Q; + case SDL_SCANCODE_R: return Keyboard::R; + case SDL_SCANCODE_S: return Keyboard::S; + case SDL_SCANCODE_T: return Keyboard::T; + case SDL_SCANCODE_U: return Keyboard::U; + case SDL_SCANCODE_V: return Keyboard::V; + case SDL_SCANCODE_W: return Keyboard::W; + case SDL_SCANCODE_X: return Keyboard::X; + case SDL_SCANCODE_Y: return Keyboard::Y; + case SDL_SCANCODE_Z: return Keyboard::Z; + case SDL_SCANCODE_0: return Keyboard::Num0; + case SDL_SCANCODE_1: return Keyboard::Num1; + case SDL_SCANCODE_2: return Keyboard::Num2; + case SDL_SCANCODE_3: return Keyboard::Num3; + case SDL_SCANCODE_4: return Keyboard::Num4; + case SDL_SCANCODE_5: return Keyboard::Num5; + case SDL_SCANCODE_6: return Keyboard::Num6; + case SDL_SCANCODE_7: return Keyboard::Num7; + case SDL_SCANCODE_8: return Keyboard::Num8; + case SDL_SCANCODE_9: return Keyboard::Num9; + case SDL_SCANCODE_ESCAPE: return Keyboard::Escape; + case SDL_SCANCODE_LCTRL: return Keyboard::LControl; + case SDL_SCANCODE_LSHIFT: return Keyboard::LShift; + case SDL_SCANCODE_LALT: return Keyboard::LAlt; + case SDL_SCANCODE_LGUI: return Keyboard::LSystem; + case SDL_SCANCODE_RCTRL: return Keyboard::RControl; + case SDL_SCANCODE_RSHIFT: return Keyboard::RShift; + case SDL_SCANCODE_RALT: return Keyboard::RAlt; + case SDL_SCANCODE_RGUI: return Keyboard::RSystem; + case SDL_SCANCODE_SPACE: return Keyboard::Space; + case SDL_SCANCODE_RETURN: return Keyboard::Enter; + case SDL_SCANCODE_BACKSPACE: return Keyboard::Backspace; + case SDL_SCANCODE_TAB: return Keyboard::Tab; + case SDL_SCANCODE_LEFT: return Keyboard::Left; + case SDL_SCANCODE_RIGHT: return Keyboard::Right; + case SDL_SCANCODE_UP: return Keyboard::Up; + case SDL_SCANCODE_DOWN: return Keyboard::Down; + case SDL_SCANCODE_F1: return Keyboard::F1; + case SDL_SCANCODE_F2: return Keyboard::F2; + case SDL_SCANCODE_F3: return Keyboard::F3; + case SDL_SCANCODE_F4: return Keyboard::F4; + case SDL_SCANCODE_F5: return Keyboard::F5; + case SDL_SCANCODE_F6: return Keyboard::F6; + case SDL_SCANCODE_F7: return Keyboard::F7; + case SDL_SCANCODE_F8: return Keyboard::F8; + case SDL_SCANCODE_F9: return Keyboard::F9; + case SDL_SCANCODE_F10: return Keyboard::F10; + case SDL_SCANCODE_F11: return Keyboard::F11; + case SDL_SCANCODE_F12: return Keyboard::F12; + default: return Keyboard::Unknown; + } +} + +int sfKeyToSdlScancode(int sfKey) { + // Reverse mapping (simplified) + switch (sfKey) { + case Keyboard::A: return SDL_SCANCODE_A; + case Keyboard::W: return SDL_SCANCODE_W; + case Keyboard::S: return SDL_SCANCODE_S; + case Keyboard::D: return SDL_SCANCODE_D; + case Keyboard::Space: return SDL_SCANCODE_SPACE; + case Keyboard::Escape: return SDL_SCANCODE_ESCAPE; + // Add more as needed + default: return SDL_SCANCODE_UNKNOWN; + } +} + +int sdlButtonToSfButton(int sdlButton) { + switch (sdlButton) { + case SDL_BUTTON_LEFT: return Mouse::Left; + case SDL_BUTTON_RIGHT: return Mouse::Right; + case SDL_BUTTON_MIDDLE: return Mouse::Middle; + case SDL_BUTTON_X1: return Mouse::XButton1; + case SDL_BUTTON_X2: return Mouse::XButton2; + default: return Mouse::Left; + } +} + +int sfButtonToSdlButton(int sfButton) { + switch (sfButton) { + case Mouse::Left: return SDL_BUTTON_LEFT; + case Mouse::Right: return SDL_BUTTON_RIGHT; + case Mouse::Middle: return SDL_BUTTON_MIDDLE; + case Mouse::XButton1: return SDL_BUTTON_X1; + case Mouse::XButton2: return SDL_BUTTON_X2; + default: return SDL_BUTTON_LEFT; + } +} + +bool Keyboard::isKeyPressed(Key key) { + const Uint8* state = SDL_GetKeyboardState(nullptr); + int scancode = sfKeyToSdlScancode(key); + return scancode != SDL_SCANCODE_UNKNOWN && state[scancode]; +} + +bool Mouse::isButtonPressed(Button button) { + Uint32 state = SDL_GetMouseState(nullptr, nullptr); + return state & SDL_BUTTON(sfButtonToSdlButton(button)); +} + +Vector2i Mouse::getPosition() { + int x, y; + SDL_GetMouseState(&x, &y); + return Vector2i(x, y); +} + +Vector2i Mouse::getPosition(const RenderWindow& relativeTo) { + // For now, same as global position (would need window-relative in multi-window setup) + return getPosition(); +} + +void Mouse::setPosition(const Vector2i& position) { + SDL_WarpMouseGlobal(position.x, position.y); +} + +void Mouse::setPosition(const Vector2i& position, const RenderWindow& relativeTo) { + SDL_WarpMouseInWindow( + static_cast(relativeTo.getNativeWindowHandle()), + position.x, position.y + ); +} + +// ============================================================================= +// RenderTarget Implementation +// ============================================================================= + +void RenderTarget::clear(const Color& color) { + SDL2Renderer::getInstance().clear( + color.r / 255.0f, + color.g / 255.0f, + color.b / 255.0f, + color.a / 255.0f + ); +} + +void RenderTarget::draw(const Vertex* vertices, size_t vertexCount, PrimitiveType type, const RenderStates& states) { + // TODO: Implement with proper vertex buffer handling +} + +void RenderTarget::draw(const VertexArray& vertices, const RenderStates& states) { + draw(&vertices[0], vertices.getVertexCount(), vertices.getPrimitiveType(), states); +} + +// ============================================================================= +// RenderTexture Implementation +// ============================================================================= + +RenderTexture::~RenderTexture() { + if (fboId_) { + SDL2Renderer::getInstance().deleteFBO(fboId_); + } +} + +bool RenderTexture::create(unsigned int width, unsigned int height) { + size_ = Vector2u(width, height); + + unsigned int colorTexture = 0; + fboId_ = SDL2Renderer::getInstance().createFBO(width, height, colorTexture); + + if (!fboId_) { + return false; + } + + // Set up internal texture to point to FBO color attachment + texture_.setNativeHandle(colorTexture); + + view_ = View(FloatRect(0, 0, static_cast(width), static_cast(height))); + defaultView_ = view_; + + return true; +} + +void RenderTexture::clear(const Color& color) { + SDL2Renderer::getInstance().bindFBO(fboId_); + RenderTarget::clear(color); +} + +void RenderTexture::display() { + SDL2Renderer::getInstance().unbindFBO(); +} + +// ============================================================================= +// Texture Implementation +// ============================================================================= + +Texture::~Texture() { + if (textureId_) { + SDL2Renderer::getInstance().deleteTexture(textureId_); + } +} + +Texture::Texture(const Texture& other) + : size_(other.size_), smooth_(other.smooth_), repeated_(other.repeated_) { + if (other.textureId_) { + // Create new texture with same properties + textureId_ = SDL2Renderer::getInstance().createTexture(size_.x, size_.y, nullptr); + // Note: Would need to copy pixel data for full implementation + } +} + +Texture& Texture::operator=(const Texture& other) { + if (this != &other) { + if (textureId_) { + SDL2Renderer::getInstance().deleteTexture(textureId_); + } + size_ = other.size_; + smooth_ = other.smooth_; + repeated_ = other.repeated_; + if (other.textureId_) { + textureId_ = SDL2Renderer::getInstance().createTexture(size_.x, size_.y, nullptr); + } + } + return *this; +} + +bool Texture::create(unsigned int width, unsigned int height) { + if (textureId_) { + SDL2Renderer::getInstance().deleteTexture(textureId_); + } + size_ = Vector2u(width, height); + textureId_ = SDL2Renderer::getInstance().createTexture(width, height, nullptr); + return textureId_ != 0; +} + +bool Texture::loadFromFile(const std::string& filename) { + int width, height, channels; + unsigned char* data = stbi_load(filename.c_str(), &width, &height, &channels, 4); + + if (!data) { + std::cerr << "Texture: Failed to load " << filename << ": " << stbi_failure_reason() << std::endl; + return false; + } + + if (textureId_) { + SDL2Renderer::getInstance().deleteTexture(textureId_); + } + + size_ = Vector2u(width, height); + textureId_ = SDL2Renderer::getInstance().createTexture(width, height, data); + + stbi_image_free(data); + return textureId_ != 0; +} + +bool Texture::loadFromMemory(const void* data, size_t size) { + int width, height, channels; + unsigned char* pixels = stbi_load_from_memory( + static_cast(data), size, &width, &height, &channels, 4); + + if (!pixels) { + return false; + } + + if (textureId_) { + SDL2Renderer::getInstance().deleteTexture(textureId_); + } + + size_ = Vector2u(width, height); + textureId_ = SDL2Renderer::getInstance().createTexture(width, height, pixels); + + stbi_image_free(pixels); + return textureId_ != 0; +} + +void Texture::setSmooth(bool smooth) { + smooth_ = smooth; + if (textureId_) { + SDL2Renderer::getInstance().setTextureSmooth(textureId_, smooth); + } +} + +void Texture::setRepeated(bool repeated) { + repeated_ = repeated; + if (textureId_) { + SDL2Renderer::getInstance().setTextureRepeated(textureId_, repeated); + } +} + +Image Texture::copyToImage() const { + Image img; + img.create(size_.x, size_.y); + // TODO: Read back from GPU texture + return img; +} + +void Texture::update(const RenderWindow& window) { + // TODO: Copy window contents to texture +} + +void Texture::update(const Uint8* pixels) { + if (textureId_ && pixels) { + SDL2Renderer::getInstance().updateTexture(textureId_, 0, 0, size_.x, size_.y, pixels); + } +} + +void Texture::update(const Uint8* pixels, unsigned int width, unsigned int height, unsigned int x, unsigned int y) { + if (textureId_ && pixels) { + SDL2Renderer::getInstance().updateTexture(textureId_, x, y, width, height, pixels); + } +} + +// ============================================================================= +// Image Implementation +// ============================================================================= + +bool Image::loadFromFile(const std::string& filename) { + int width, height, channels; + unsigned char* data = stbi_load(filename.c_str(), &width, &height, &channels, 4); + + if (!data) { + return false; + } + + size_ = Vector2u(width, height); + pixels_.resize(width * height * 4); + memcpy(pixels_.data(), data, pixels_.size()); + + stbi_image_free(data); + return true; +} + +bool Image::saveToFile(const std::string& filename) const { + // TODO: Use stb_image_write + return false; +} + +// ============================================================================= +// Font Implementation +// ============================================================================= + +bool Font::loadFromFile(const std::string& filename) { + FILE* file = fopen(filename.c_str(), "rb"); + if (!file) { + return false; + } + + fseek(file, 0, SEEK_END); + size_t size = ftell(file); + fseek(file, 0, SEEK_SET); + + fontData_.resize(size); + fread(fontData_.data(), 1, size, file); + fclose(file); + + loaded_ = true; + return true; +} + +bool Font::loadFromMemory(const void* data, size_t sizeInBytes) { + fontData_.resize(sizeInBytes); + memcpy(fontData_.data(), data, sizeInBytes); + loaded_ = true; + return true; +} + +// ============================================================================= +// Shape Drawing (Stubs - implement with vertex generation) +// ============================================================================= + +void Shape::draw(RenderTarget& target, RenderStates states) const { + // TODO: Generate vertices and draw using SDL2Renderer +} + +void VertexArray::draw(RenderTarget& target, RenderStates states) const { + // TODO: Draw using SDL2Renderer +} + +void Sprite::draw(RenderTarget& target, RenderStates states) const { + // TODO: Draw textured quad +} + +void Text::draw(RenderTarget& target, RenderStates states) const { + // TODO: Draw text using font atlas +} + +// ============================================================================= +// Shader Implementation +// ============================================================================= + +Shader::~Shader() { + if (programId_) { + SDL2Renderer::getInstance().deleteShaderProgram(programId_); + } +} + +bool Shader::loadFromFile(const std::string& filename, Type type) { + // TODO: Load shader from file + return false; +} + +bool Shader::loadFromFile(const std::string& vertexFile, const std::string& fragmentFile) { + // TODO: Load shaders from files + return false; +} + +bool Shader::loadFromMemory(const std::string& shader, Type type) { + // For fragment-only shaders, use default vertex shader + if (type == Fragment) { + std::string defaultVertex = R"( + attribute vec2 a_position; + attribute vec4 a_color; + attribute vec2 a_texcoord; + uniform mat4 u_projection; + varying vec4 v_color; + varying vec2 v_texcoord; + void main() { + gl_Position = u_projection * vec4(a_position, 0.0, 1.0); + v_color = a_color; + v_texcoord = a_texcoord; + } + )"; + programId_ = SDL2Renderer::getInstance().compileShader(defaultVertex, shader); + loaded_ = programId_ != 0; + return loaded_; + } + return false; +} + +void Shader::setUniform(const std::string& name, float x) { + if (programId_) { + glUseProgram(programId_); + int loc = glGetUniformLocation(programId_, name.c_str()); + if (loc >= 0) glUniform1f(loc, x); + } +} + +void Shader::setUniform(const std::string& name, const Vector2f& v) { + if (programId_) { + glUseProgram(programId_); + int loc = glGetUniformLocation(programId_, name.c_str()); + if (loc >= 0) glUniform2f(loc, v.x, v.y); + } +} + +void Shader::setUniform(const std::string& name, const Color& color) { + if (programId_) { + glUseProgram(programId_); + int loc = glGetUniformLocation(programId_, name.c_str()); + if (loc >= 0) glUniform4f(loc, color.r/255.f, color.g/255.f, color.b/255.f, color.a/255.f); + } +} + +void Shader::setUniform(const std::string& name, const Texture& texture) { + // Texture binding is handled during draw +} + +void Shader::setUniform(const std::string& name, const Glsl::Vec3& v) { + if (programId_) { + glUseProgram(programId_); + int loc = glGetUniformLocation(programId_, name.c_str()); + if (loc >= 0) glUniform3f(loc, v.x, v.y, v.z); + } +} + +void Shader::setUniform(const std::string& name, const Glsl::Vec4& v) { + if (programId_) { + glUseProgram(programId_); + int loc = glGetUniformLocation(programId_, name.c_str()); + if (loc >= 0) glUniform4f(loc, v.x, v.y, v.z, v.w); + } +} + +void Shader::setUniform(const std::string& name, CurrentTextureType) { + // Handled during draw +} + +bool Shader::isAvailable() { + return SDL2Renderer::getInstance().isInitialized(); +} + +// ============================================================================= +// FontAtlas Implementation +// ============================================================================= + +FontAtlas::FontAtlas() = default; + +FontAtlas::~FontAtlas() { + if (textureId_) { + SDL2Renderer::getInstance().deleteTexture(textureId_); + } + if (stbFontInfo_) { + delete static_cast(stbFontInfo_); + } +} + +bool FontAtlas::load(const unsigned char* fontData, size_t dataSize, float fontSize) { + fontSize_ = fontSize; + + stbtt_fontinfo* info = new stbtt_fontinfo(); + if (!stbtt_InitFont(info, fontData, 0)) { + delete info; + return false; + } + + stbFontInfo_ = info; + + // Get font metrics + int ascent, descent, lineGap; + stbtt_GetFontVMetrics(info, &ascent, &descent, &lineGap); + + float scale = stbtt_ScaleForPixelHeight(info, fontSize); + ascent_ = ascent * scale; + descent_ = descent * scale; + lineHeight_ = (ascent - descent + lineGap) * scale; + + // Create glyph atlas (simple ASCII for now) + const int atlasSize = 512; + std::vector atlasPixels(atlasSize * atlasSize, 0); + + int x = 1, y = 1; + int rowHeight = 0; + + for (uint32_t c = 32; c < 128; ++c) { + int advance, lsb; + stbtt_GetCodepointHMetrics(info, c, &advance, &lsb); + + int x0, y0, x1, y1; + stbtt_GetCodepointBitmapBox(info, c, scale, scale, &x0, &y0, &x1, &y1); + + int w = x1 - x0; + int h = y1 - y0; + + if (x + w + 1 >= atlasSize) { + x = 1; + y += rowHeight + 1; + rowHeight = 0; + } + + if (y + h + 1 >= atlasSize) { + break; // Atlas full + } + + // Render glyph to atlas + stbtt_MakeCodepointBitmap(info, &atlasPixels[y * atlasSize + x], w, h, atlasSize, scale, scale, c); + + GlyphInfo glyph; + glyph.u0 = x / (float)atlasSize; + glyph.v0 = y / (float)atlasSize; + glyph.u1 = (x + w) / (float)atlasSize; + glyph.v1 = (y + h) / (float)atlasSize; + glyph.xoff = x0; + glyph.yoff = y0; + glyph.xadvance = advance * scale; + glyph.width = w; + glyph.height = h; + + glyphCache_[c] = glyph; + + x += w + 1; + rowHeight = std::max(rowHeight, h); + } + + // Convert single-channel to RGBA + std::vector rgbaPixels(atlasSize * atlasSize * 4); + for (int i = 0; i < atlasSize * atlasSize; ++i) { + rgbaPixels[i * 4 + 0] = 255; + rgbaPixels[i * 4 + 1] = 255; + rgbaPixels[i * 4 + 2] = 255; + rgbaPixels[i * 4 + 3] = atlasPixels[i]; + } + + textureId_ = SDL2Renderer::getInstance().createTexture(atlasSize, atlasSize, rgbaPixels.data()); + + return true; +} + +bool FontAtlas::getGlyph(uint32_t codepoint, GlyphInfo& info) const { + auto it = glyphCache_.find(codepoint); + if (it != glyphCache_.end()) { + info = it->second; + return true; + } + return false; +} + +} // namespace sf + +#endif // MCRF_SDL2 diff --git a/src/platform/SDL2Renderer.h b/src/platform/SDL2Renderer.h new file mode 100644 index 0000000..f5c3e30 --- /dev/null +++ b/src/platform/SDL2Renderer.h @@ -0,0 +1,182 @@ +// SDL2Renderer.h - OpenGL ES 2 rendering implementation for SDL2 backend +// This file provides the actual rendering implementation for the SDL2 types +// defined in SDL2Types.h. It handles: +// - OpenGL ES 2 context management +// - Shader compilation and management +// - Texture loading (via stb_image) +// - Font rendering (via stb_truetype) +// - Framebuffer object management +// +// Part of the renderer abstraction layer + +#pragma once + +#ifdef MCRF_SDL2 + +#include +#include +#include +#include + +// Forward declarations to avoid SDL header in this header +struct SDL_Window; +typedef void* SDL_GLContext; + +namespace sf { + +// Forward declarations +class RenderTarget; +class Texture; +class Shader; + +// ============================================================================= +// SDL2 Renderer - Singleton managing OpenGL ES 2 state +// ============================================================================= + +class SDL2Renderer { +public: + // Singleton access + static SDL2Renderer& getInstance(); + + // Initialization/shutdown + bool init(); // Initialize SDL2 (call before window creation) + bool initGL(); // Initialize OpenGL resources (call after GL context exists) + void shutdown(); + bool isInitialized() const { return initialized_; } + bool isGLInitialized() const { return glInitialized_; } + + // Built-in shader programs + enum class ShaderType { + Shape, // For RectangleShape, CircleShape, etc. + Sprite, // For textured sprites + Text, // For text rendering + Custom // User-provided shaders + }; + + // Get a built-in shader program + unsigned int getShaderProgram(ShaderType type) const; + + // Compile a custom shader program + unsigned int compileShader(const std::string& vertexSource, const std::string& fragmentSource); + void deleteShaderProgram(unsigned int programId); + + // Texture management + unsigned int createTexture(unsigned int width, unsigned int height, const unsigned char* pixels = nullptr); + void updateTexture(unsigned int textureId, unsigned int x, unsigned int y, + unsigned int width, unsigned int height, const unsigned char* pixels); + void deleteTexture(unsigned int textureId); + void setTextureSmooth(unsigned int textureId, bool smooth); + void setTextureRepeated(unsigned int textureId, bool repeated); + + // FBO management + unsigned int createFBO(unsigned int width, unsigned int height, unsigned int& colorTexture); + void deleteFBO(unsigned int fboId); + void bindFBO(unsigned int fboId); + void unbindFBO(); + + // Render state management + void setViewport(int x, int y, unsigned int width, unsigned int height); + void setProjection(float left, float right, float bottom, float top); + void clear(float r, float g, float b, float a); + + // Drawing primitives + void drawTriangles(const float* vertices, size_t vertexCount, + const float* colors, const float* texCoords, + unsigned int textureId = 0); + + // Projection matrix access (for shaders) + const float* getProjectionMatrix() const { return projectionMatrix_; } + +private: + SDL2Renderer() = default; + ~SDL2Renderer() = default; + SDL2Renderer(const SDL2Renderer&) = delete; + SDL2Renderer& operator=(const SDL2Renderer&) = delete; + + bool initialized_ = false; + bool glInitialized_ = false; + + // Built-in shader programs + unsigned int shapeProgram_ = 0; + unsigned int spriteProgram_ = 0; + unsigned int textProgram_ = 0; + + // Current projection matrix (4x4 orthographic) + float projectionMatrix_[16] = {0}; + + // FBO stack for nested render-to-texture + std::vector fboStack_; + + // Helper functions + bool compileAndLinkProgram(const char* vertexSrc, const char* fragmentSrc, unsigned int& programOut); + unsigned int compileShaderStage(unsigned int type, const char* source); + void initBuiltinShaders(); +}; + +// ============================================================================= +// Keyboard/Mouse SDL2 Implementation helpers +// ============================================================================= + +// SDL2 scancode to sf::Keyboard::Key mapping +int sdlScancodeToSfKey(int sdlScancode); +int sfKeyToSdlScancode(int sfKey); + +// SDL2 mouse button to sf::Mouse::Button mapping +int sdlButtonToSfButton(int sdlButton); +int sfButtonToSdlButton(int sfButton); + +// ============================================================================= +// Event translation +// ============================================================================= + +// Translate SDL_Event to sf::Event +// Returns true if the event was translated, false if it should be ignored +bool translateSDLEvent(const void* sdlEvent, void* sfEvent); + +// ============================================================================= +// Font Atlas for text rendering +// ============================================================================= + +class FontAtlas { +public: + FontAtlas(); + ~FontAtlas(); + + // Load font data + bool load(const unsigned char* fontData, size_t dataSize, float fontSize); + + // Get texture atlas + unsigned int getTextureId() const { return textureId_; } + + // Get glyph info for rendering + struct GlyphInfo { + float u0, v0, u1, v1; // Texture coordinates + float xoff, yoff; // Offset from cursor + float xadvance; // How far to advance cursor + float width, height; // Glyph dimensions in pixels + }; + + bool getGlyph(uint32_t codepoint, GlyphInfo& info) const; + + // Get font metrics + float getAscent() const { return ascent_; } + float getDescent() const { return descent_; } + float getLineHeight() const { return lineHeight_; } + +private: + unsigned int textureId_ = 0; + float fontSize_ = 0; + float ascent_ = 0; + float descent_ = 0; + float lineHeight_ = 0; + + // Glyph cache - maps codepoint to glyph info + std::unordered_map glyphCache_; + + // stb_truetype font info (opaque pointer to avoid header inclusion) + void* stbFontInfo_ = nullptr; +}; + +} // namespace sf + +#endif // MCRF_SDL2 diff --git a/src/platform/SDL2Types.h b/src/platform/SDL2Types.h new file mode 100644 index 0000000..7f2e0e9 --- /dev/null +++ b/src/platform/SDL2Types.h @@ -0,0 +1,999 @@ +// SDL2Types.h - SFML type stubs for SDL2 + OpenGL ES 2 backend +// This file provides type definitions that compile against SDL2 instead of SFML, +// enabling cross-platform builds for Web (Emscripten/WebGL), Android, and desktop. +// +// Part of the renderer abstraction layer (issue #158 continuation) +// +// Architecture: +// - Game code uses sf:: namespace types (unchanged from SFML builds) +// - This header provides sf:: types implemented using SDL2 + OpenGL ES 2 +// - Compile-time selection via MCRF_SDL2 define +// +// Phases: +// - Phase 1: Skeleton (stubs matching HeadlessTypes.h) +// - Phase 2: Window + GL context +// - Phase 3: Shape rendering (rectangles, circles) +// - Phase 4: Texture + Sprites +// - Phase 5: Text rendering +// - Phase 6: RenderTexture (FBO) +// - Phase 7: Custom shaders + +#pragma once + +#include +#include +#include +#include +#include + +// SDL2 headers - conditionally included when actually implementing +// For now, forward declare what we need +#ifdef MCRF_SDL2_IMPL +#include +#include +#endif + +namespace sf { + +// Forward declarations (needed for RenderWindow) +struct Event; +class Keyboard; +class Mouse; + +// ============================================================================= +// Type Aliases (SFML compatibility) +// ============================================================================= + +using Uint8 = uint8_t; +using Uint16 = uint16_t; +using Uint32 = uint32_t; +using Uint64 = uint64_t; +using Int8 = int8_t; +using Int16 = int16_t; +using Int32 = int32_t; +using Int64 = int64_t; + +// ============================================================================= +// Vector Types +// ============================================================================= + +template +struct Vector2 { + T x = 0; + T y = 0; + + Vector2() = default; + Vector2(T x_, T y_) : x(x_), y(y_) {} + + template + explicit Vector2(const Vector2& other) : x(static_cast(other.x)), y(static_cast(other.y)) {} + + Vector2 operator+(const Vector2& rhs) const { return Vector2(x + rhs.x, y + rhs.y); } + Vector2 operator-(const Vector2& rhs) const { return Vector2(x - rhs.x, y - rhs.y); } + Vector2 operator*(T scalar) const { return Vector2(x * scalar, y * scalar); } + Vector2 operator/(T scalar) const { return Vector2(x / scalar, y / scalar); } + Vector2& operator+=(const Vector2& rhs) { x += rhs.x; y += rhs.y; return *this; } + Vector2& operator-=(const Vector2& rhs) { x -= rhs.x; y -= rhs.y; return *this; } + Vector2& operator*=(T scalar) { x *= scalar; y *= scalar; return *this; } + Vector2& operator/=(T scalar) { x /= scalar; y /= scalar; return *this; } + bool operator==(const Vector2& rhs) const { return x == rhs.x && y == rhs.y; } + bool operator!=(const Vector2& rhs) const { return !(*this == rhs); } + Vector2 operator-() const { return Vector2(-x, -y); } +}; + +using Vector2f = Vector2; +using Vector2i = Vector2; +using Vector2u = Vector2; + +template +Vector2 operator*(T scalar, const Vector2& vec) { return vec * scalar; } + +// ============================================================================= +// Color Type +// ============================================================================= + +struct Color { + uint8_t r = 0; + uint8_t g = 0; + uint8_t b = 0; + uint8_t a = 255; + + Color() = default; + Color(uint8_t r_, uint8_t g_, uint8_t b_, uint8_t a_ = 255) : r(r_), g(g_), b(b_), a(a_) {} + + bool operator==(const Color& rhs) const { return r == rhs.r && g == rhs.g && b == rhs.b && a == rhs.a; } + bool operator!=(const Color& rhs) const { return !(*this == rhs); } + + // Standard colors + static const Color Black; + static const Color White; + static const Color Red; + static const Color Green; + static const Color Blue; + static const Color Yellow; + static const Color Magenta; + static const Color Cyan; + static const Color Transparent; +}; + +// Static color definitions +inline const Color Color::Black(0, 0, 0); +inline const Color Color::White(255, 255, 255); +inline const Color Color::Red(255, 0, 0); +inline const Color Color::Green(0, 255, 0); +inline const Color Color::Blue(0, 0, 255); +inline const Color Color::Yellow(255, 255, 0); +inline const Color Color::Magenta(255, 0, 255); +inline const Color Color::Cyan(0, 255, 255); +inline const Color Color::Transparent(0, 0, 0, 0); + +// ============================================================================= +// Rectangle Types +// ============================================================================= + +template +struct Rect { + T left = 0; + T top = 0; + T width = 0; + T height = 0; + + Rect() = default; + Rect(T left_, T top_, T width_, T height_) : left(left_), top(top_), width(width_), height(height_) {} + Rect(const Vector2& position, const Vector2& size) + : left(position.x), top(position.y), width(size.x), height(size.y) {} + + bool contains(T x, T y) const { + return x >= left && x < left + width && y >= top && y < top + height; + } + bool contains(const Vector2& point) const { return contains(point.x, point.y); } + + bool intersects(const Rect& other) const { + return left < other.left + other.width && left + width > other.left && + top < other.top + other.height && top + height > other.top; + } + + Vector2 getPosition() const { return Vector2(left, top); } + Vector2 getSize() const { return Vector2(width, height); } +}; + +using FloatRect = Rect; +using IntRect = Rect; + +// ============================================================================= +// Time Types +// ============================================================================= + +class Time { + int64_t microseconds_ = 0; +public: + Time() = default; + float asSeconds() const { return microseconds_ / 1000000.0f; } + int32_t asMilliseconds() const { return static_cast(microseconds_ / 1000); } + int64_t asMicroseconds() const { return microseconds_; } + + static Time Zero; + + friend Time seconds(float amount); + friend Time milliseconds(int32_t amount); + friend Time microseconds(int64_t amount); +}; + +inline Time Time::Zero; + +inline Time seconds(float amount) { Time t; t.microseconds_ = static_cast(amount * 1000000); return t; } +inline Time milliseconds(int32_t amount) { Time t; t.microseconds_ = amount * 1000; return t; } +inline Time microseconds(int64_t amount) { Time t; t.microseconds_ = amount; return t; } + +class Clock { + int64_t start_time_ = 0; + + static int64_t now_microseconds() { + // Use C++11 chrono for portable timing + auto now = std::chrono::high_resolution_clock::now(); + auto duration = now.time_since_epoch(); + return std::chrono::duration_cast(duration).count(); + } +public: + Clock() : start_time_(now_microseconds()) {} + + Time getElapsedTime() const { + return microseconds(now_microseconds() - start_time_); + } + + Time restart() { + int64_t now = now_microseconds(); + int64_t elapsed = now - start_time_; + start_time_ = now; + return microseconds(elapsed); + } +}; + +// ============================================================================= +// Transform +// ============================================================================= +// SDL2 backend will implement actual matrix math for transforms +// For now, stub implementation matching headless + +class Transform { + // 3x3 matrix stored as 4x4 for OpenGL compatibility + // [ m00 m01 m02 ] [ m[0] m[4] m[12] ] + // [ m10 m11 m12 ] -> [ m[1] m[5] m[13] ] + // [ 0 0 1 ] [ 0 0 1 ] + float m[16] = {1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1}; + +public: + Transform() = default; + Transform& translate(float x, float y) { return *this; } // TODO: Implement + Transform& translate(const Vector2f& offset) { return translate(offset.x, offset.y); } + Transform& rotate(float angle) { return *this; } // TODO: Implement + Transform& rotate(float angle, const Vector2f& center) { return *this; } // TODO: Implement + Transform& scale(float factorX, float factorY) { return *this; } // TODO: Implement + Transform& scale(const Vector2f& factors) { return scale(factors.x, factors.y); } + + Vector2f transformPoint(float x, float y) const { return Vector2f(x, y); } // TODO: Implement + Vector2f transformPoint(const Vector2f& point) const { return point; } + FloatRect transformRect(const FloatRect& rect) const { return rect; } // TODO: Implement + + Transform getInverse() const { return Transform(); } // TODO: Implement + + Transform operator*(const Transform& rhs) const { return Transform(); } // TODO: Implement + Vector2f operator*(const Vector2f& point) const { return point; } + + static const Transform Identity; + + // SDL2-specific: Get raw matrix for OpenGL + const float* getMatrix() const { return m; } +}; + +inline const Transform Transform::Identity; + +// ============================================================================= +// Vertex (for custom geometry) +// ============================================================================= + +struct Vertex { + Vector2f position; + Color color; + Vector2f texCoords; + + Vertex() = default; + Vertex(const Vector2f& pos) : position(pos), color(Color::White) {} + Vertex(const Vector2f& pos, const Color& col) : position(pos), color(col) {} + Vertex(const Vector2f& pos, const Vector2f& tex) : position(pos), color(Color::White), texCoords(tex) {} + Vertex(const Vector2f& pos, const Color& col, const Vector2f& tex) : position(pos), color(col), texCoords(tex) {} +}; + +// ============================================================================= +// View (camera) +// ============================================================================= + +class View { + Vector2f center_; + Vector2f size_; + float rotation_ = 0.0f; + FloatRect viewport_{0, 0, 1, 1}; +public: + View() : center_(0, 0), size_(1000, 1000) {} + View(const FloatRect& rect) : center_(rect.left + rect.width/2, rect.top + rect.height/2), size_(rect.width, rect.height) {} + View(const Vector2f& center, const Vector2f& size) : center_(center), size_(size) {} + + void setCenter(float x, float y) { center_ = Vector2f(x, y); } + void setCenter(const Vector2f& center) { center_ = center; } + void setSize(float width, float height) { size_ = Vector2f(width, height); } + void setSize(const Vector2f& size) { size_ = size; } + void setRotation(float angle) { rotation_ = angle; } + void setViewport(const FloatRect& viewport) { viewport_ = viewport; } + + const Vector2f& getCenter() const { return center_; } + const Vector2f& getSize() const { return size_; } + float getRotation() const { return rotation_; } + const FloatRect& getViewport() const { return viewport_; } + + void move(float offsetX, float offsetY) { center_.x += offsetX; center_.y += offsetY; } + void move(const Vector2f& offset) { center_ += offset; } + void rotate(float angle) { rotation_ += angle; } + void zoom(float factor) { size_ *= factor; } + + Transform getTransform() const { return Transform::Identity; } // TODO: Implement + Transform getInverseTransform() const { return Transform::Identity; } // TODO: Implement +}; + +// ============================================================================= +// Rendering Types +// ============================================================================= + +enum PrimitiveType { + Points, + Lines, + LineStrip, + Triangles, + TriangleStrip, + TriangleFan, + Quads // Deprecated in SFML 3.0, but we'll support it via triangulation +}; + +// BlendMode stub +struct BlendMode { + BlendMode() = default; + static const BlendMode Alpha; + static const BlendMode Add; + static const BlendMode Multiply; + static const BlendMode None; +}; +inline const BlendMode BlendMode::Alpha{}; +inline const BlendMode BlendMode::Add{}; +inline const BlendMode BlendMode::Multiply{}; +inline const BlendMode BlendMode::None{}; + +// Forward declare Shader for RenderStates +class Shader; + +class RenderStates { +public: + RenderStates() = default; + RenderStates(const Transform& transform) {} // Implicit conversion from Transform + RenderStates(const BlendMode& mode) {} + RenderStates(const Shader* shader) {} // Implicit conversion from Shader pointer + static const RenderStates Default; +}; + +inline const RenderStates RenderStates::Default; + +// Forward declarations for rendering types +class RenderTarget; +class RenderTexture; +class RenderWindow; +class Texture; +class Font; +class Shader; + +// Drawable base class +class Drawable { +public: + virtual ~Drawable() = default; +protected: + friend class RenderTarget; + virtual void draw(RenderTarget& target, RenderStates states) const = 0; +}; + +// Transformable base class +class Transformable { +protected: + Vector2f position_; + float rotation_ = 0.0f; + Vector2f scale_{1.0f, 1.0f}; + Vector2f origin_; +public: + virtual ~Transformable() = default; + + void setPosition(float x, float y) { position_ = Vector2f(x, y); } + void setPosition(const Vector2f& position) { position_ = position; } + void setRotation(float angle) { rotation_ = angle; } + void setScale(float factorX, float factorY) { scale_ = Vector2f(factorX, factorY); } + void setScale(const Vector2f& factors) { scale_ = factors; } + void setOrigin(float x, float y) { origin_ = Vector2f(x, y); } + void setOrigin(const Vector2f& origin) { origin_ = origin; } + + const Vector2f& getPosition() const { return position_; } + float getRotation() const { return rotation_; } + const Vector2f& getScale() const { return scale_; } + const Vector2f& getOrigin() const { return origin_; } + + void move(float offsetX, float offsetY) { position_.x += offsetX; position_.y += offsetY; } + void move(const Vector2f& offset) { position_ += offset; } + void rotate(float angle) { rotation_ += angle; } + void scale(float factorX, float factorY) { scale_.x *= factorX; scale_.y *= factorY; } + void scale(const Vector2f& factor) { scale_.x *= factor.x; scale_.y *= factor.y; } + + Transform getTransform() const { return Transform::Identity; } // TODO: Implement + Transform getInverseTransform() const { return Transform::Identity; } // TODO: Implement +}; + +// ============================================================================= +// Shape Classes +// ============================================================================= + +class Shape : public Drawable, public Transformable { +protected: + Color fillColor_ = Color::White; + Color outlineColor_ = Color::White; + float outlineThickness_ = 0.0f; +public: + void setFillColor(const Color& color) { fillColor_ = color; } + void setOutlineColor(const Color& color) { outlineColor_ = color; } + void setOutlineThickness(float thickness) { outlineThickness_ = thickness; } + + const Color& getFillColor() const { return fillColor_; } + const Color& getOutlineColor() const { return outlineColor_; } + float getOutlineThickness() const { return outlineThickness_; } + + virtual FloatRect getLocalBounds() const { return FloatRect(); } + virtual FloatRect getGlobalBounds() const { return FloatRect(); } + +protected: + void draw(RenderTarget& target, RenderStates states) const override; // Implemented in SDL2Renderer.cpp +}; + +class RectangleShape : public Shape { + Vector2f size_; +public: + RectangleShape(const Vector2f& size = Vector2f(0, 0)) : size_(size) {} + void setSize(const Vector2f& size) { size_ = size; } + const Vector2f& getSize() const { return size_; } + FloatRect getLocalBounds() const override { return FloatRect(0, 0, size_.x, size_.y); } + FloatRect getGlobalBounds() const override { return FloatRect(position_.x, position_.y, size_.x, size_.y); } +}; + +class CircleShape : public Shape { + float radius_ = 0.0f; + size_t pointCount_ = 30; +public: + CircleShape(float radius = 0, size_t pointCount = 30) : radius_(radius), pointCount_(pointCount) {} + void setRadius(float radius) { radius_ = radius; } + float getRadius() const { return radius_; } + void setPointCount(size_t count) { pointCount_ = count; } + size_t getPointCount() const { return pointCount_; } + FloatRect getLocalBounds() const override { return FloatRect(0, 0, radius_ * 2, radius_ * 2); } +}; + +class ConvexShape : public Shape { + std::vector points_; +public: + ConvexShape(size_t pointCount = 0) : points_(pointCount) {} + void setPointCount(size_t count) { points_.resize(count); } + size_t getPointCount() const { return points_.size(); } + void setPoint(size_t index, const Vector2f& point) { if (index < points_.size()) points_[index] = point; } + Vector2f getPoint(size_t index) const { return index < points_.size() ? points_[index] : Vector2f(); } +}; + +// ============================================================================= +// VertexArray +// ============================================================================= + +class VertexArray : public Drawable { + std::vector vertices_; + PrimitiveType primitiveType_ = Points; +public: + VertexArray() = default; + VertexArray(PrimitiveType type, size_t vertexCount = 0) : vertices_(vertexCount), primitiveType_(type) {} + + size_t getVertexCount() const { return vertices_.size(); } + Vertex& operator[](size_t index) { return vertices_[index]; } + const Vertex& operator[](size_t index) const { return vertices_[index]; } + + void clear() { vertices_.clear(); } + void resize(size_t vertexCount) { vertices_.resize(vertexCount); } + void append(const Vertex& vertex) { vertices_.push_back(vertex); } + + void setPrimitiveType(PrimitiveType type) { primitiveType_ = type; } + PrimitiveType getPrimitiveType() const { return primitiveType_; } + + FloatRect getBounds() const { return FloatRect(); } // TODO: Implement + +protected: + void draw(RenderTarget& target, RenderStates states) const override; // Implemented in SDL2Renderer.cpp +}; + +// ============================================================================= +// Image +// ============================================================================= + +class Image { + Vector2u size_; + std::vector pixels_; +public: + Image() = default; + + void create(unsigned int width, unsigned int height, const Color& color = Color::Black) { + size_ = Vector2u(width, height); + pixels_.resize(width * height * 4, 0); + // Fill with color + for (unsigned int y = 0; y < height; ++y) { + for (unsigned int x = 0; x < width; ++x) { + setPixel(x, y, color); + } + } + } + + bool loadFromFile(const std::string& filename); // Implemented in SDL2Renderer.cpp (uses stb_image) + bool saveToFile(const std::string& filename) const; // Implemented in SDL2Renderer.cpp (uses stb_image_write) + + Vector2u getSize() const { return size_; } + + void setPixel(unsigned int x, unsigned int y, const Color& color) { + if (x < size_.x && y < size_.y) { + size_t idx = (y * size_.x + x) * 4; + pixels_[idx] = color.r; + pixels_[idx + 1] = color.g; + pixels_[idx + 2] = color.b; + pixels_[idx + 3] = color.a; + } + } + + Color getPixel(unsigned int x, unsigned int y) const { + if (x < size_.x && y < size_.y) { + size_t idx = (y * size_.x + x) * 4; + return Color(pixels_[idx], pixels_[idx + 1], pixels_[idx + 2], pixels_[idx + 3]); + } + return Color::Black; + } + + const Uint8* getPixelsPtr() const { return pixels_.data(); } + + // SDL2-specific: Allow direct pixel access for texture upload + Uint8* getPixelsPtr() { return pixels_.data(); } + void setSize(unsigned int w, unsigned int h) { size_ = Vector2u(w, h); pixels_.resize(w * h * 4); } +}; + +// ============================================================================= +// Texture +// ============================================================================= + +// Forward declare RenderWindow for Texture::update +class RenderWindow; + +class Texture { + Vector2u size_; + // SDL2-specific: OpenGL texture handle (0 = not created) + unsigned int textureId_ = 0; + bool smooth_ = false; + bool repeated_ = false; + +public: + Texture() = default; + ~Texture(); // Implemented in SDL2Renderer.cpp (deletes GL texture) + + // Copy constructor/assignment - needed for proper GL resource management + Texture(const Texture& other); + Texture& operator=(const Texture& other); + + bool create(unsigned int width, unsigned int height); // Implemented in SDL2Renderer.cpp + bool loadFromFile(const std::string& filename); // Implemented in SDL2Renderer.cpp + bool loadFromMemory(const void* data, size_t size); // Implemented in SDL2Renderer.cpp + + Vector2u getSize() const { return size_; } + void setSmooth(bool smooth); // Implemented in SDL2Renderer.cpp + bool isSmooth() const { return smooth_; } + void setRepeated(bool repeated); // Implemented in SDL2Renderer.cpp + bool isRepeated() const { return repeated_; } + + Image copyToImage() const; // Implemented in SDL2Renderer.cpp + void update(const RenderWindow& window); // Implemented in SDL2Renderer.cpp + void update(const Uint8* pixels); // Implemented in SDL2Renderer.cpp + void update(const Uint8* pixels, unsigned int width, unsigned int height, unsigned int x, unsigned int y); + + // SDL2-specific: Get GL texture handle + unsigned int getNativeHandle() const { return textureId_; } + void setNativeHandle(unsigned int id) { textureId_ = id; } +}; + +// ============================================================================= +// Sprite +// ============================================================================= + +class Sprite : public Drawable, public Transformable { + const Texture* texture_ = nullptr; + IntRect textureRect_; + Color color_ = Color::White; +public: + Sprite() = default; + Sprite(const Texture& texture) : texture_(&texture) {} + Sprite(const Texture& texture, const IntRect& rectangle) : texture_(&texture), textureRect_(rectangle) {} + + void setTexture(const Texture& texture, bool resetRect = false) { texture_ = &texture; } + void setTextureRect(const IntRect& rectangle) { textureRect_ = rectangle; } + void setColor(const Color& color) { color_ = color; } + + const Texture* getTexture() const { return texture_; } + const IntRect& getTextureRect() const { return textureRect_; } + const Color& getColor() const { return color_; } + + FloatRect getLocalBounds() const { return FloatRect(0, 0, static_cast(textureRect_.width), static_cast(textureRect_.height)); } + FloatRect getGlobalBounds() const { return FloatRect(position_.x, position_.y, static_cast(textureRect_.width), static_cast(textureRect_.height)); } + +protected: + void draw(RenderTarget& target, RenderStates states) const override; // Implemented in SDL2Renderer.cpp +}; + +// ============================================================================= +// Text and Font +// ============================================================================= + +class Font { + // SDL2-specific: font data for stb_truetype + std::vector fontData_; + bool loaded_ = false; + +public: + struct Info { + std::string family; + }; + + Font() = default; + bool loadFromFile(const std::string& filename); // Implemented in SDL2Renderer.cpp + bool loadFromMemory(const void* data, size_t sizeInBytes); // Implemented in SDL2Renderer.cpp + const Info& getInfo() const { static Info info; return info; } + + // SDL2-specific: Access font data + const unsigned char* getData() const { return fontData_.data(); } + size_t getDataSize() const { return fontData_.size(); } + bool isLoaded() const { return loaded_; } +}; + +class Text : public Drawable, public Transformable { + std::string string_; + const Font* font_ = nullptr; + unsigned int characterSize_ = 30; + Color fillColor_ = Color::White; + Color outlineColor_ = Color::Black; + float outlineThickness_ = 0.0f; + uint32_t style_ = 0; +public: + enum Style { Regular = 0, Bold = 1, Italic = 2, Underlined = 4, StrikeThrough = 8 }; + + Text() = default; + Text(const std::string& string, const Font& font, unsigned int characterSize = 30) + : string_(string), font_(&font), characterSize_(characterSize) {} + + void setString(const std::string& string) { string_ = string; } + void setFont(const Font& font) { font_ = &font; } + void setCharacterSize(unsigned int size) { characterSize_ = size; } + void setStyle(uint32_t style) { style_ = style; } + void setFillColor(const Color& color) { fillColor_ = color; } + void setOutlineColor(const Color& color) { outlineColor_ = color; } + void setOutlineThickness(float thickness) { outlineThickness_ = thickness; } + + const std::string& getString() const { return string_; } + const Font* getFont() const { return font_; } + unsigned int getCharacterSize() const { return characterSize_; } + uint32_t getStyle() const { return style_; } + const Color& getFillColor() const { return fillColor_; } + const Color& getOutlineColor() const { return outlineColor_; } + float getOutlineThickness() const { return outlineThickness_; } + + FloatRect getLocalBounds() const { return FloatRect(); } // TODO: Implement + FloatRect getGlobalBounds() const { return FloatRect(); } // TODO: Implement + +protected: + void draw(RenderTarget& target, RenderStates states) const override; // Implemented in SDL2Renderer.cpp +}; + +// ============================================================================= +// RenderTarget (base class for rendering) +// ============================================================================= + +class RenderTarget { +protected: + Vector2u size_; + View view_; + View defaultView_; +public: + virtual ~RenderTarget() = default; + + virtual Vector2u getSize() const { return size_; } + virtual void clear(const Color& color = Color::Black); // Implemented in SDL2Renderer.cpp + + void draw(const Drawable& drawable, const RenderStates& states = RenderStates::Default) { + drawable.draw(*this, states); + } + void draw(const Vertex* vertices, size_t vertexCount, PrimitiveType type, const RenderStates& states = RenderStates::Default); + void draw(const VertexArray& vertices, const RenderStates& states = RenderStates::Default); + + void setView(const View& view) { view_ = view; } + const View& getView() const { return view_; } + const View& getDefaultView() const { return defaultView_; } + + IntRect getViewport(const View& view) const { return IntRect(0, 0, size_.x, size_.y); } + + Vector2f mapPixelToCoords(const Vector2i& point) const { return Vector2f(static_cast(point.x), static_cast(point.y)); } + Vector2f mapPixelToCoords(const Vector2i& point, const View& view) const { return Vector2f(static_cast(point.x), static_cast(point.y)); } + Vector2i mapCoordsToPixel(const Vector2f& point) const { return Vector2i(static_cast(point.x), static_cast(point.y)); } + Vector2i mapCoordsToPixel(const Vector2f& point, const View& view) const { return Vector2i(static_cast(point.x), static_cast(point.y)); } +}; + +// ============================================================================= +// RenderTexture (Framebuffer Object) +// ============================================================================= + +class RenderTexture : public RenderTarget { + Texture texture_; + unsigned int fboId_ = 0; // OpenGL FBO handle +public: + RenderTexture() = default; + ~RenderTexture(); // Implemented in SDL2Renderer.cpp + + bool create(unsigned int width, unsigned int height); // Implemented in SDL2Renderer.cpp + + void clear(const Color& color = Color::Black) override; // Implemented in SDL2Renderer.cpp + void display(); // Implemented in SDL2Renderer.cpp + + const Texture& getTexture() const { return texture_; } + void setSmooth(bool smooth) { texture_.setSmooth(smooth); } + bool isSmooth() const { return texture_.isSmooth(); } + + // SDL2-specific: Get FBO handle + unsigned int getNativeHandle() const { return fboId_; } +}; + +// ============================================================================= +// RenderWindow +// ============================================================================= + +namespace Style { + enum { + None = 0, + Titlebar = 1 << 0, + Resize = 1 << 1, + Close = 1 << 2, + Fullscreen = 1 << 3, + Default = Titlebar | Resize | Close + }; +} + +class VideoMode { +public: + unsigned int width = 0; + unsigned int height = 0; + unsigned int bitsPerPixel = 32; + + VideoMode() = default; + VideoMode(unsigned int w, unsigned int h, unsigned int bpp = 32) : width(w), height(h), bitsPerPixel(bpp) {} + + static VideoMode getDesktopMode(); // Implemented in SDL2Renderer.cpp + static const std::vector& getFullscreenModes(); // Implemented in SDL2Renderer.cpp +}; + +class RenderWindow : public RenderTarget { + bool open_ = false; + std::string title_; + + // SDL2-specific handles (void* to avoid SDL header dependency) + void* sdlWindow_ = nullptr; + void* glContext_ = nullptr; + +public: + RenderWindow() = default; + RenderWindow(VideoMode mode, const std::string& title, uint32_t style = Style::Default) { + create(mode, title, style); + } + ~RenderWindow(); // Implemented in SDL2Renderer.cpp + + void create(VideoMode mode, const std::string& title, uint32_t style = Style::Default); // Implemented in SDL2Renderer.cpp + + void close(); // Implemented in SDL2Renderer.cpp + bool isOpen() const { return open_; } + + void clear(const Color& color = Color::Black) override; // Implemented in SDL2Renderer.cpp + void display(); // Implemented in SDL2Renderer.cpp + + void setTitle(const std::string& title); // Implemented in SDL2Renderer.cpp + void setFramerateLimit(unsigned int limit); // Implemented in SDL2Renderer.cpp + void setVerticalSyncEnabled(bool enabled); // Implemented in SDL2Renderer.cpp + void setVisible(bool visible); // Implemented in SDL2Renderer.cpp + void setMouseCursorVisible(bool visible); // Implemented in SDL2Renderer.cpp + void setMouseCursorGrabbed(bool grabbed); // Implemented in SDL2Renderer.cpp + void setKeyRepeatEnabled(bool enabled) {} // No-op for now + + Vector2i getPosition() const; // Implemented in SDL2Renderer.cpp + void setPosition(const Vector2i& position); // Implemented in SDL2Renderer.cpp + Vector2u getSize() const override { return size_; } + void setSize(const Vector2u& size); // Implemented in SDL2Renderer.cpp + + bool pollEvent(Event& event); // Implemented in SDL2Renderer.cpp + bool waitEvent(Event& event); // Implemented in SDL2Renderer.cpp + + // SDL2-specific: Access native handles + void* getNativeWindowHandle() const { return sdlWindow_; } + void* getGLContext() const { return glContext_; } +}; + +// ============================================================================= +// Audio Stubs (SDL2_mixer could implement these later) +// ============================================================================= + +class SoundBuffer { +public: + SoundBuffer() = default; + bool loadFromFile(const std::string& filename) { return true; } // Stub + bool loadFromMemory(const void* data, size_t sizeInBytes) { return true; } // Stub + Time getDuration() const { return Time(); } +}; + +class Sound { +public: + enum Status { Stopped, Paused, Playing }; + + Sound() = default; + Sound(const SoundBuffer& buffer) {} + + void setBuffer(const SoundBuffer& buffer) {} + void play() {} + void pause() {} + void stop() {} + + Status getStatus() const { return Stopped; } + void setVolume(float volume) {} + float getVolume() const { return 100.0f; } + void setLoop(bool loop) {} + bool getLoop() const { return false; } +}; + +class Music { +public: + enum Status { Stopped, Paused, Playing }; + + Music() = default; + bool openFromFile(const std::string& filename) { return true; } // Stub + + void play() {} + void pause() {} + void stop() {} + + Status getStatus() const { return Stopped; } + void setVolume(float volume) {} + float getVolume() const { return 100.0f; } + void setLoop(bool loop) {} + bool getLoop() const { return false; } + Time getDuration() const { return Time(); } + Time getPlayingOffset() const { return Time(); } + void setPlayingOffset(Time offset) {} +}; + +// ============================================================================= +// Input (Keyboard and Mouse) +// ============================================================================= + +class Keyboard { +public: + enum Key { + Unknown = -1, + A = 0, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + Num0, Num1, Num2, Num3, Num4, Num5, Num6, Num7, Num8, Num9, + Escape, LControl, LShift, LAlt, LSystem, RControl, RShift, RAlt, RSystem, + Menu, LBracket, RBracket, Semicolon, Comma, Period, Apostrophe, Slash, Backslash, + Grave, Equal, Hyphen, Space, Enter, Backspace, Tab, PageUp, PageDown, End, Home, + Insert, Delete, Add, Subtract, Multiply, Divide, + Left, Right, Up, Down, + Numpad0, Numpad1, Numpad2, Numpad3, Numpad4, Numpad5, Numpad6, Numpad7, Numpad8, Numpad9, + F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, + Pause, + KeyCount, + // Deprecated aliases (SFML 2.x compatibility) + Tilde = Grave, + Quote = Apostrophe, + BackSpace = Backspace, + BackSlash = Backslash, + SemiColon = Semicolon, + Dash = Hyphen + }; + + static bool isKeyPressed(Key key); // Implemented in SDL2Renderer.cpp +}; + +class Mouse { +public: + enum Button { Left, Right, Middle, XButton1, XButton2, ButtonCount }; + enum Wheel { VerticalWheel, HorizontalWheel }; + + static bool isButtonPressed(Button button); // Implemented in SDL2Renderer.cpp + static Vector2i getPosition(); // Implemented in SDL2Renderer.cpp + static Vector2i getPosition(const RenderWindow& relativeTo); // Implemented in SDL2Renderer.cpp + static void setPosition(const Vector2i& position); // Implemented in SDL2Renderer.cpp + static void setPosition(const Vector2i& position, const RenderWindow& relativeTo); // Implemented in SDL2Renderer.cpp +}; + +// ============================================================================= +// Event System +// ============================================================================= + +struct Event { + enum EventType { + Closed, + Resized, + LostFocus, + GainedFocus, + TextEntered, + KeyPressed, + KeyReleased, + MouseWheelMoved, // Deprecated + MouseWheelScrolled, + MouseButtonPressed, + MouseButtonReleased, + MouseMoved, + MouseEntered, + MouseLeft, + Count + }; + + struct SizeEvent { unsigned int width, height; }; + struct KeyEvent { Keyboard::Key code; bool alt, control, shift, system; }; + struct TextEvent { uint32_t unicode; }; + struct MouseMoveEvent { int x, y; }; + struct MouseButtonEvent { Mouse::Button button; int x, y; }; + struct MouseWheelScrollEvent { Mouse::Wheel wheel; float delta; int x, y; }; + + EventType type; + union { + SizeEvent size; + KeyEvent key; + TextEvent text; + MouseMoveEvent mouseMove; + MouseButtonEvent mouseButton; + MouseWheelScrollEvent mouseWheelScroll; + }; +}; + +// ============================================================================= +// GLSL Types (for shader uniforms) +// ============================================================================= + +namespace Glsl { + using Vec2 = Vector2f; + + struct Vec3 { + float x = 0, y = 0, z = 0; + Vec3() = default; + Vec3(float x_, float y_, float z_) : x(x_), y(y_), z(z_) {} + }; + + struct Vec4 { + float x = 0, y = 0, z = 0, w = 0; + Vec4() = default; + Vec4(float x_, float y_, float z_, float w_) : x(x_), y(y_), z(z_), w(w_) {} + Vec4(const Color& c) : x(c.r/255.f), y(c.g/255.f), z(c.b/255.f), w(c.a/255.f) {} + }; +} // namespace Glsl + +// Forward declaration for CurrentTexture +struct CurrentTextureType {}; + +// ============================================================================= +// Shader +// ============================================================================= + +class Shader { + unsigned int programId_ = 0; // OpenGL shader program handle + bool loaded_ = false; + +public: + enum Type { Vertex, Geometry, Fragment }; + static const CurrentTextureType CurrentTexture; + + Shader() = default; + ~Shader(); // Implemented in SDL2Renderer.cpp + + bool loadFromFile(const std::string& filename, Type type); // Implemented in SDL2Renderer.cpp + bool loadFromFile(const std::string& vertexFile, const std::string& fragmentFile); // Implemented in SDL2Renderer.cpp + bool loadFromMemory(const std::string& shader, Type type); // Implemented in SDL2Renderer.cpp + + void setUniform(const std::string& name, float x); // Implemented in SDL2Renderer.cpp + void setUniform(const std::string& name, const Vector2f& v); + void setUniform(const std::string& name, const Color& color); + void setUniform(const std::string& name, const Texture& texture); + void setUniform(const std::string& name, const Glsl::Vec3& v); + void setUniform(const std::string& name, const Glsl::Vec4& v); + void setUniform(const std::string& name, CurrentTextureType); + + static bool isAvailable(); // Implemented in SDL2Renderer.cpp + + // SDL2-specific: Get program handle + unsigned int getNativeHandle() const { return programId_; } + bool isLoaded() const { return loaded_; } +}; + +inline const CurrentTextureType Shader::CurrentTexture{}; + +// ============================================================================= +// Error stream +// ============================================================================= + +#include +#include + +inline std::ostream& err() { + static std::stringstream dummy; + return dummy; +} + +} // namespace sf