first commit

This commit is contained in:
saarsena@gmail.com 2026-04-02 03:41:50 -04:00
commit 5c7d1905a9
25 changed files with 4034 additions and 0 deletions

33
.gitignore vendored Normal file
View file

@ -0,0 +1,33 @@
# Build directories
build/
cmake-build-*/
out/
.cache/
# IDE files
.idea/
.vscode/
*.swp
*.swo
*~
# Compiled files
*.o
*.obj
*.exe
*.out
*.app
# CMake generated
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
Makefile
compile_commands.json
# Dependencies fetched by CMake
_deps/
external/*/
# OS files
.DS_Store
Thumbs.db

120
CMakeLists.txt Normal file
View file

@ -0,0 +1,120 @@
cmake_minimum_required(VERSION 3.20)
project(sdl3_flecs_template VERSION 1.0.0 LANGUAGES CXX)
# C++ Standard
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Build type defaults to Debug
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif()
# Compiler warnings
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
add_compile_options(-g -O0)
else()
add_compile_options(-O2)
endif()
elseif(MSVC)
add_compile_options(/W4)
endif()
# =============================================================================
# Dependencies
# =============================================================================
# SDL3 - Using FetchContent for automatic download
include(FetchContent)
# SDL3
FetchContent_Declare(
SDL3
GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
GIT_TAG main
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
)
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
set(SDL_STATIC ON CACHE BOOL "" FORCE)
set(SDL_TEST OFF CACHE BOOL "" FORCE)
# Platform-specific SDL backends
if(EMSCRIPTEN)
set(SDL_X11 OFF CACHE BOOL "" FORCE)
set(SDL_WAYLAND OFF CACHE BOOL "" FORCE)
else()
# Disable X11, use Wayland only on Linux
set(SDL_X11 OFF CACHE BOOL "" FORCE)
set(SDL_WAYLAND ON CACHE BOOL "" FORCE)
endif()
FetchContent_MakeAvailable(SDL3)
# FLECS
FetchContent_Declare(
flecs
GIT_REPOSITORY https://github.com/SanderMertens/flecs.git
GIT_TAG master
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
)
FetchContent_MakeAvailable(flecs)
# =============================================================================
# Main Executable
# =============================================================================
add_executable(${PROJECT_NAME}
src/main.cpp
src/game.cpp
src/systems.cpp
src/pipeline.cpp
)
target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
)
target_link_libraries(${PROJECT_NAME} PRIVATE
SDL3::SDL3-static
flecs::flecs_static
)
# Platform-specific settings
if(EMSCRIPTEN)
set_target_properties(${PROJECT_NAME} PROPERTIES SUFFIX ".html")
target_link_options(${PROJECT_NAME} PRIVATE
-sUSE_SDL=0
-sALLOW_MEMORY_GROWTH=1
-sTOTAL_MEMORY=67108864
-sMAX_WEBGL_VERSION=2
--shell-file ${CMAKE_CURRENT_SOURCE_DIR}/web/shell.html
)
elseif(WIN32)
target_link_libraries(${PROJECT_NAME} PRIVATE winmm imm32 version setupapi)
elseif(UNIX AND NOT APPLE)
find_package(PkgConfig REQUIRED)
# Linux dependencies are handled by SDL3
endif()
# =============================================================================
# Install
# =============================================================================
install(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION bin
)
# =============================================================================
# CPack (optional packaging)
# =============================================================================
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)

165
README.md Normal file
View file

@ -0,0 +1,165 @@
# SDL3 + FLECS Template
A scaffold/template for building games using SDL3 and the FLECS Entity Component System.
## Features
- **SDL3** - Modern cross-platform multimedia library
- **FLECS** - Fast and lightweight Entity Component System
- **CMake** - Cross-platform build system with FetchContent for dependencies
- **Clean Architecture** - Separated components, systems, and game logic
## Project Structure
```
.
├── CMakeLists.txt # Build configuration
├── include/
│ ├── components/ # ECS component definitions
│ │ ├── transform.h # Position, Velocity, Rotation, Scale
│ │ ├── render.h # Color, Sprite, RectShape, CircleShape
│ │ ├── input.h # PlayerControlled, InputState
│ │ └── common.h # Name, ToDestroy, Lifetime, Health
│ ├── components.h # Master component include
│ ├── systems.h # System declarations
│ └── game.h # Game context and main loop
├── src/
│ ├── main.c # Entry point
│ ├── game.c # Game initialization and loop
│ └── systems.c # ECS system implementations
└── external/ # (unused - deps fetched via CMake)
```
## Building
### Prerequisites
- CMake 3.20+
- C11 compatible compiler (GCC, Clang, MSVC)
- Git (for fetching dependencies)
### Linux/macOS
```bash
# Create build directory
mkdir build && cd build
# Configure
cmake ..
# Build
cmake --build .
# Run
./sdl3_flecs_template
```
### Windows (Visual Studio)
```bash
mkdir build && cd build
cmake .. -G "Visual Studio 17 2022"
cmake --build . --config Release
.\Release\sdl3_flecs_template.exe
```
### Windows (MinGW)
```bash
mkdir build && cd build
cmake .. -G "MinGW Makefiles"
cmake --build .
.\sdl3_flecs_template.exe
```
## Usage Guide
### Adding Components
1. Create a new header in `include/components/` (e.g., `physics.h`)
2. Define your component struct:
```c
typedef struct RigidBody {
float mass;
float friction;
} RigidBody;
```
3. Include it in `include/components.h`
4. Register it in `src/systems.c`:
```c
ECS_COMPONENT_DECLARE(RigidBody);
// In register_components():
ECS_COMPONENT_DEFINE(world, RigidBody);
```
### Adding Systems
1. Declare the system in `include/systems.h`:
```c
void PhysicsSystem(ecs_iter_t* it);
```
2. Implement it in `src/systems.c`:
```c
void PhysicsSystem(ecs_iter_t* it) {
Position* pos = ecs_field(it, Position, 0);
RigidBody* rb = ecs_field(it, RigidBody, 1);
for (int i = 0; i < it->count; i++) {
// Physics logic here
}
}
```
3. Register it in `register_systems()`:
```c
ecs_system(world, {
.entity = ecs_entity(world, {
.name = "PhysicsSystem",
.add = ecs_ids(ecs_dependson(EcsOnUpdate))
}),
.query.terms = {
{ .id = ecs_id(Position), .inout = EcsInOut },
{ .id = ecs_id(RigidBody), .inout = EcsIn }
},
.callback = PhysicsSystem
});
```
### Creating Entities
```c
ecs_entity_t player = ecs_new(world);
ecs_set(world, player, Position, { .x = 100, .y = 100 });
ecs_set(world, player, Velocity, { .x = 0, .y = 0 });
ecs_set(world, player, RectShape, { .width = 32, .height = 32 });
ecs_set(world, player, Color, { .r = 0, .g = 255, .b = 0, .a = 255 });
ecs_set(world, player, PlayerControlled, { .player_id = 0 });
```
### System Phases
FLECS provides built-in phases for ordering systems:
- `EcsOnLoad` - Load external data
- `EcsPostLoad` - Process loaded data
- `EcsPreUpdate` - Prepare for update
- `EcsOnUpdate` - Main update logic
- `EcsOnValidate` - Validate state
- `EcsPostUpdate` - Post-update cleanup
- `EcsPreStore` - Prepare for rendering
- `EcsOnStore` - Render/store output
## Controls
- **ESC** - Quit
- **P** - Pause/unpause
## License
This template is released into the public domain. Use it however you like!
## Resources
- [SDL3 Documentation](https://wiki.libsdl.org/SDL3)
- [FLECS Documentation](https://www.flecs.dev/flecs/md_docs_2Docs.html)
- [FLECS Examples](https://github.com/SanderMertens/flecs/tree/master/examples)

View file

@ -0,0 +1,82 @@
# This file will be configured to contain variables for CPack. These variables
# should be set in the CMake list file of the project before CPack module is
# included. The list of available CPACK_xxx variables and their associated
# documentation may be obtained using
# cpack --help-variable-list
#
# Some variables are common to all generators (e.g. CPACK_PACKAGE_NAME)
# and some are specific to a generator
# (e.g. CPACK_NSIS_EXTRA_INSTALL_COMMANDS). The generator specific variables
# usually begin with CPACK_<GENNAME>_xxxx.
set(CPACK_BINARY_DEB "OFF")
set(CPACK_BINARY_FREEBSD "OFF")
set(CPACK_BINARY_IFW "OFF")
set(CPACK_BINARY_NSIS "OFF")
set(CPACK_BINARY_RPM "OFF")
set(CPACK_BINARY_STGZ "ON")
set(CPACK_BINARY_TBZ2 "OFF")
set(CPACK_BINARY_TGZ "ON")
set(CPACK_BINARY_TXZ "OFF")
set(CPACK_BINARY_TZ "ON")
set(CPACK_BUILD_SOURCE_DIRS "/home/saarsena/Dev/renderpip;/home/saarsena/Dev/renderpip/build-web")
set(CPACK_CMAKE_GENERATOR "Unix Makefiles")
set(CPACK_COMPONENT_UNSPECIFIED_HIDDEN "TRUE")
set(CPACK_COMPONENT_UNSPECIFIED_REQUIRED "TRUE")
set(CPACK_DEFAULT_PACKAGE_DESCRIPTION_FILE "/usr/share/cmake/Templates/CPack.GenericDescription.txt")
set(CPACK_DEFAULT_PACKAGE_DESCRIPTION_SUMMARY "sdl3_flecs_template built using CMake")
set(CPACK_GENERATOR "STGZ;TGZ;TZ")
set(CPACK_INNOSETUP_ARCHITECTURE "x86")
set(CPACK_INSTALL_CMAKE_PROJECTS "/home/saarsena/Dev/renderpip/build-web;sdl3_flecs_template;ALL;/")
set(CPACK_INSTALL_PREFIX "/home/saarsena/emsdk/upstream/emscripten/cache/sysroot")
set(CPACK_MODULE_PATH "/home/saarsena/emsdk/upstream/emscripten/cmake/Modules;/home/saarsena/emsdk/upstream/emscripten/cmake/Modules;/home/saarsena/emsdk/upstream/emscripten/cmake/Modules;/home/saarsena/emsdk/upstream/emscripten/cmake/Modules")
set(CPACK_NSIS_DISPLAY_NAME "sdl3_flecs_template 1.0.0")
set(CPACK_NSIS_INSTALLER_ICON_CODE "")
set(CPACK_NSIS_INSTALLER_MUI_ICON_CODE "")
set(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES")
set(CPACK_NSIS_PACKAGE_NAME "sdl3_flecs_template 1.0.0")
set(CPACK_NSIS_UNINSTALL_NAME "Uninstall")
set(CPACK_OBJCOPY_EXECUTABLE "/usr/bin/llvm-objcopy")
set(CPACK_OBJDUMP_EXECUTABLE "/usr/bin/llvm-objdump")
set(CPACK_OUTPUT_CONFIG_FILE "/home/saarsena/Dev/renderpip/build-web/CPackConfig.cmake")
set(CPACK_PACKAGE_DEFAULT_LOCATION "/")
set(CPACK_PACKAGE_DESCRIPTION_FILE "/usr/share/cmake/Templates/CPack.GenericDescription.txt")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "sdl3_flecs_template built using CMake")
set(CPACK_PACKAGE_FILE_NAME "sdl3_flecs_template-1.0.0-Emscripten")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "sdl3_flecs_template 1.0.0")
set(CPACK_PACKAGE_INSTALL_REGISTRY_KEY "sdl3_flecs_template 1.0.0")
set(CPACK_PACKAGE_NAME "sdl3_flecs_template")
set(CPACK_PACKAGE_RELOCATABLE "true")
set(CPACK_PACKAGE_VENDOR "Humanity")
set(CPACK_PACKAGE_VERSION "1.0.0")
set(CPACK_PACKAGE_VERSION_MAJOR "1")
set(CPACK_PACKAGE_VERSION_MINOR "0")
set(CPACK_PACKAGE_VERSION_PATCH "0")
set(CPACK_PROJECT_NAME "sdl3_flecs_template")
set(CPACK_PROJECT_VERSION "1.0.0")
set(CPACK_READELF_EXECUTABLE "/usr/bin/llvm-readelf")
set(CPACK_RESOURCE_FILE_LICENSE "/usr/share/cmake/Templates/CPack.GenericLicense.txt")
set(CPACK_RESOURCE_FILE_README "/usr/share/cmake/Templates/CPack.GenericDescription.txt")
set(CPACK_RESOURCE_FILE_WELCOME "/usr/share/cmake/Templates/CPack.GenericWelcome.txt")
set(CPACK_SET_DESTDIR "OFF")
set(CPACK_SOURCE_GENERATOR "TBZ2;TGZ;TXZ;TZ")
set(CPACK_SOURCE_OUTPUT_CONFIG_FILE "/home/saarsena/Dev/renderpip/build-web/CPackSourceConfig.cmake")
set(CPACK_SOURCE_RPM "OFF")
set(CPACK_SOURCE_TBZ2 "ON")
set(CPACK_SOURCE_TGZ "ON")
set(CPACK_SOURCE_TXZ "ON")
set(CPACK_SOURCE_TZ "ON")
set(CPACK_SOURCE_ZIP "OFF")
set(CPACK_SYSTEM_NAME "Emscripten")
set(CPACK_THREADS "1")
set(CPACK_TOPLEVEL_TAG "Emscripten")
set(CPACK_WIX_SIZEOF_VOID_P "4")
if(NOT CPACK_PROPERTIES_FILE)
set(CPACK_PROPERTIES_FILE "/home/saarsena/Dev/renderpip/build-web/CPackProperties.cmake")
endif()
if(EXISTS ${CPACK_PROPERTIES_FILE})
include(${CPACK_PROPERTIES_FILE})
endif()

View file

@ -0,0 +1,90 @@
# This file will be configured to contain variables for CPack. These variables
# should be set in the CMake list file of the project before CPack module is
# included. The list of available CPACK_xxx variables and their associated
# documentation may be obtained using
# cpack --help-variable-list
#
# Some variables are common to all generators (e.g. CPACK_PACKAGE_NAME)
# and some are specific to a generator
# (e.g. CPACK_NSIS_EXTRA_INSTALL_COMMANDS). The generator specific variables
# usually begin with CPACK_<GENNAME>_xxxx.
set(CPACK_BINARY_DEB "OFF")
set(CPACK_BINARY_FREEBSD "OFF")
set(CPACK_BINARY_IFW "OFF")
set(CPACK_BINARY_NSIS "OFF")
set(CPACK_BINARY_RPM "OFF")
set(CPACK_BINARY_STGZ "ON")
set(CPACK_BINARY_TBZ2 "OFF")
set(CPACK_BINARY_TGZ "ON")
set(CPACK_BINARY_TXZ "OFF")
set(CPACK_BINARY_TZ "ON")
set(CPACK_BUILD_SOURCE_DIRS "/home/saarsena/Dev/renderpip;/home/saarsena/Dev/renderpip/build-web")
set(CPACK_CMAKE_GENERATOR "Unix Makefiles")
set(CPACK_COMPONENT_UNSPECIFIED_HIDDEN "TRUE")
set(CPACK_COMPONENT_UNSPECIFIED_REQUIRED "TRUE")
set(CPACK_DEFAULT_PACKAGE_DESCRIPTION_FILE "/usr/share/cmake/Templates/CPack.GenericDescription.txt")
set(CPACK_DEFAULT_PACKAGE_DESCRIPTION_SUMMARY "sdl3_flecs_template built using CMake")
set(CPACK_GENERATOR "TBZ2;TGZ;TXZ;TZ")
set(CPACK_IGNORE_FILES "/CVS/;/\\.svn/;/\\.bzr/;/\\.hg/;/\\.git/;\\.swp\$;\\.#;/#")
set(CPACK_INNOSETUP_ARCHITECTURE "x86")
set(CPACK_INSTALLED_DIRECTORIES "/home/saarsena/Dev/renderpip;/")
set(CPACK_INSTALL_CMAKE_PROJECTS "")
set(CPACK_INSTALL_PREFIX "/home/saarsena/emsdk/upstream/emscripten/cache/sysroot")
set(CPACK_MODULE_PATH "/home/saarsena/emsdk/upstream/emscripten/cmake/Modules;/home/saarsena/emsdk/upstream/emscripten/cmake/Modules;/home/saarsena/emsdk/upstream/emscripten/cmake/Modules;/home/saarsena/emsdk/upstream/emscripten/cmake/Modules")
set(CPACK_NSIS_DISPLAY_NAME "sdl3_flecs_template 1.0.0")
set(CPACK_NSIS_INSTALLER_ICON_CODE "")
set(CPACK_NSIS_INSTALLER_MUI_ICON_CODE "")
set(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES")
set(CPACK_NSIS_PACKAGE_NAME "sdl3_flecs_template 1.0.0")
set(CPACK_NSIS_UNINSTALL_NAME "Uninstall")
set(CPACK_OBJCOPY_EXECUTABLE "/usr/bin/llvm-objcopy")
set(CPACK_OBJDUMP_EXECUTABLE "/usr/bin/llvm-objdump")
set(CPACK_OUTPUT_CONFIG_FILE "/home/saarsena/Dev/renderpip/build-web/CPackConfig.cmake")
set(CPACK_PACKAGE_DEFAULT_LOCATION "/")
set(CPACK_PACKAGE_DESCRIPTION_FILE "/usr/share/cmake/Templates/CPack.GenericDescription.txt")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "sdl3_flecs_template built using CMake")
set(CPACK_PACKAGE_FILE_NAME "sdl3_flecs_template-1.0.0-Source")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "sdl3_flecs_template 1.0.0")
set(CPACK_PACKAGE_INSTALL_REGISTRY_KEY "sdl3_flecs_template 1.0.0")
set(CPACK_PACKAGE_NAME "sdl3_flecs_template")
set(CPACK_PACKAGE_RELOCATABLE "true")
set(CPACK_PACKAGE_VENDOR "Humanity")
set(CPACK_PACKAGE_VERSION "1.0.0")
set(CPACK_PACKAGE_VERSION_MAJOR "1")
set(CPACK_PACKAGE_VERSION_MINOR "0")
set(CPACK_PACKAGE_VERSION_PATCH "0")
set(CPACK_PROJECT_NAME "sdl3_flecs_template")
set(CPACK_PROJECT_VERSION "1.0.0")
set(CPACK_READELF_EXECUTABLE "/usr/bin/llvm-readelf")
set(CPACK_RESOURCE_FILE_LICENSE "/usr/share/cmake/Templates/CPack.GenericLicense.txt")
set(CPACK_RESOURCE_FILE_README "/usr/share/cmake/Templates/CPack.GenericDescription.txt")
set(CPACK_RESOURCE_FILE_WELCOME "/usr/share/cmake/Templates/CPack.GenericWelcome.txt")
set(CPACK_RPM_PACKAGE_SOURCES "ON")
set(CPACK_SET_DESTDIR "OFF")
set(CPACK_SOURCE_GENERATOR "TBZ2;TGZ;TXZ;TZ")
set(CPACK_SOURCE_IGNORE_FILES "/CVS/;/\\.svn/;/\\.bzr/;/\\.hg/;/\\.git/;\\.swp\$;\\.#;/#")
set(CPACK_SOURCE_INSTALLED_DIRECTORIES "/home/saarsena/Dev/renderpip;/")
set(CPACK_SOURCE_OUTPUT_CONFIG_FILE "/home/saarsena/Dev/renderpip/build-web/CPackSourceConfig.cmake")
set(CPACK_SOURCE_PACKAGE_FILE_NAME "sdl3_flecs_template-1.0.0-Source")
set(CPACK_SOURCE_RPM "OFF")
set(CPACK_SOURCE_TBZ2 "ON")
set(CPACK_SOURCE_TGZ "ON")
set(CPACK_SOURCE_TOPLEVEL_TAG "Emscripten-Source")
set(CPACK_SOURCE_TXZ "ON")
set(CPACK_SOURCE_TZ "ON")
set(CPACK_SOURCE_ZIP "OFF")
set(CPACK_STRIP_FILES "")
set(CPACK_SYSTEM_NAME "Emscripten")
set(CPACK_THREADS "1")
set(CPACK_TOPLEVEL_TAG "Emscripten-Source")
set(CPACK_WIX_SIZEOF_VOID_P "4")
if(NOT CPACK_PROPERTIES_FILE)
set(CPACK_PROPERTIES_FILE "/home/saarsena/Dev/renderpip/build-web/CPackProperties.cmake")
endif()
if(EXISTS ${CPACK_PROPERTIES_FILE})
include(${CPACK_PROPERTIES_FILE})
endif()

View file

@ -0,0 +1 @@
<!doctypehtml><html lang=en><head><meta charset=utf-8><meta content="width=device-width,initial-scale=1"name=viewport><title>Render Pipeline Visualizer</title><style>*{margin:0;padding:0;box-sizing:border-box}body{background:#1e1e28;display:flex;flex-direction:column;align-items:center;justify-content:center;height:100vh;overflow:hidden;font-family:'Courier New',monospace;color:#aaa}canvas#canvas{width:100vw;height:100vh;display:block;object-fit:contain}#output{display:none}#status{position:fixed;bottom:4px;right:8px;font-size:.75em;color:#444}</style></head><body><canvas height=720 id=canvas oncontextmenu=event.preventDefault() tabindex=-1 width=1280></canvas><div id=status>Loading...</div><textarea id=output rows=8 style=display:none></textarea><script>var Module={print:function(t){console.log(t)},printErr:function(t){console.error(t)},canvas:document.getElementById("canvas"),setStatus:function(t){t&&(document.getElementById("status").textContent=t)},onRuntimeInitialized:function(){document.getElementById("status").textContent="Press H for controls"},totalDependencies:0,monitorRunDependencies:function(t){this.totalDependencies=Math.max(this.totalDependencies,t),t?Module.setStatus("Loading... ("+(this.totalDependencies-t)+"/"+this.totalDependencies+")"):Module.setStatus("")}};Module.setStatus("Downloading...")</script><script async src=sdl3_flecs_template.js></script></body></html>

File diff suppressed because one or more lines are too long

Binary file not shown.

27
build_web.sh Executable file
View file

@ -0,0 +1,27 @@
#!/bin/bash
# Build the Render Pipeline Visualizer for the web using Emscripten
set -e
# Source emsdk if not already in PATH
if ! command -v emcc &> /dev/null; then
source ~/emsdk/emsdk_env.sh
fi
BUILD_DIR="build-web"
mkdir -p "$BUILD_DIR"
cd "$BUILD_DIR"
emcmake cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DSDL_SHARED=OFF \
-DSDL_STATIC=ON \
-DSDL_TEST=OFF \
-DSDL_X11=OFF \
-DSDL_WAYLAND=OFF
emmake make -j$(nproc)
echo ""
echo "Build complete! Files are in $BUILD_DIR/"
echo "To test locally: cd $BUILD_DIR && python3 -m http.server 8080"
echo "Then open http://localhost:8080/sdl3_flecs_template.html"

11
include/components.hpp Normal file
View file

@ -0,0 +1,11 @@
/**
* @file components.hpp
* @brief Master include for all ECS components
*/
#pragma once
#include "components/transform.hpp"
#include "components/render.hpp"
#include "components/input.hpp"
#include "components/common.hpp"

View file

@ -0,0 +1,24 @@
/**
* @file common.hpp
* @brief Common/utility components
*/
#pragma once
#include <string>
struct Name {
std::string value;
};
// Tag component - empty struct marks entities for destruction
struct ToDestroy {};
struct Lifetime {
float remaining = 0.0f;
};
struct Health {
float current = 100.0f;
float max = 100.0f;
};

View file

@ -0,0 +1,21 @@
/**
* @file input.hpp
* @brief Input-related components
*/
#pragma once
struct PlayerControlled {
int player_id = 0;
};
struct InputState {
bool move_up = false;
bool move_down = false;
bool move_left = false;
bool move_right = false;
bool action_primary = false;
bool action_secondary = false;
float mouse_x = 0.0f;
float mouse_y = 0.0f;
};

View file

@ -0,0 +1,32 @@
/**
* @file render.hpp
* @brief Rendering-related components
*/
#pragma once
#include <SDL3/SDL.h>
#include <cstdint>
struct Color {
uint8_t r = 255;
uint8_t g = 255;
uint8_t b = 255;
uint8_t a = 255;
};
struct Sprite {
SDL_Texture* texture = nullptr;
SDL_FRect src_rect{};
int z_order = 0;
bool visible = true;
};
struct RectShape {
float width = 0.0f;
float height = 0.0f;
};
struct CircleShape {
float radius = 0.0f;
};

View file

@ -0,0 +1,25 @@
/**
* @file transform.hpp
* @brief Transform components for position, velocity, and rotation
*/
#pragma once
struct Position {
float x = 0.0f;
float y = 0.0f;
};
struct Velocity {
float x = 0.0f;
float y = 0.0f;
};
struct Rotation {
float angle = 0.0f;
};
struct Scale {
float x = 1.0f;
float y = 1.0f;
};

52
include/game.hpp Normal file
View file

@ -0,0 +1,52 @@
/**
* @file game.hpp
* @brief Main game state and context
*/
#pragma once
#include <SDL3/SDL.h>
#include <flecs.h>
struct WindowConfig {
const char *title = "RIDGE RACER 37";
int width = 1280;
int height = 720;
SDL_WindowFlags flags = 0;
};
struct PipelineState;
/**
* @brief Game context containing all core resources
*/
struct GameContext {
// SDL Resources
SDL_Window *window = nullptr;
SDL_Renderer *renderer = nullptr;
// FLECS World (C++ wrapper)
flecs::world ecs;
// Timing
uint64_t last_time = 0;
float delta_time = 0.0f;
// State
bool running = true;
bool paused = false;
// Window info
int window_width = 0;
int window_height = 0;
// Pipeline visualization
PipelineState *pipeline = nullptr;
};
bool game_init(GameContext &ctx, const WindowConfig &config);
void game_shutdown(GameContext &ctx);
void game_process_events(GameContext &ctx);
void game_update(GameContext &ctx);
void game_render(GameContext &ctx);
bool game_loop(GameContext &ctx);

129
include/math3d.hpp Normal file
View file

@ -0,0 +1,129 @@
/**
* @file math3d.hpp
* @brief Minimal 3D math library for render pipeline visualization
*/
#pragma once
#include <cmath>
#include <algorithm>
constexpr float PI = 3.14159265358979323846f;
struct Vec3 {
float x = 0, y = 0, z = 0;
Vec3 operator+(Vec3 o) const { return {x+o.x, y+o.y, z+o.z}; }
Vec3 operator-(Vec3 o) const { return {x-o.x, y-o.y, z-o.z}; }
Vec3 operator*(float s) const { return {x*s, y*s, z*s}; }
float dot(Vec3 o) const { return x*o.x + y*o.y + z*o.z; }
Vec3 cross(Vec3 o) const { return {y*o.z - z*o.y, z*o.x - x*o.z, x*o.y - y*o.x}; }
float length() const { return std::sqrt(x*x + y*y + z*z); }
Vec3 normalized() const {
float l = length();
return l > 0 ? Vec3{x/l, y/l, z/l} : Vec3{0, 0, 0};
}
};
inline Vec3 lerp(Vec3 a, Vec3 b, float t) {
return {a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t};
}
inline float smoothstep(float t) {
return t * t * (3.0f - 2.0f * t);
}
struct Vec4 {
float x = 0, y = 0, z = 0, w = 1;
Vec3 xyz() const { return {x, y, z}; }
Vec3 perspDiv() const {
return w != 0 ? Vec3{x/w, y/w, z/w} : Vec3{x, y, z};
}
};
struct Mat4 {
float m[4][4] = {};
static Mat4 identity() {
Mat4 r;
r.m[0][0] = r.m[1][1] = r.m[2][2] = r.m[3][3] = 1;
return r;
}
Vec4 operator*(Vec4 v) const {
return {
m[0][0]*v.x + m[0][1]*v.y + m[0][2]*v.z + m[0][3]*v.w,
m[1][0]*v.x + m[1][1]*v.y + m[1][2]*v.z + m[1][3]*v.w,
m[2][0]*v.x + m[2][1]*v.y + m[2][2]*v.z + m[2][3]*v.w,
m[3][0]*v.x + m[3][1]*v.y + m[3][2]*v.z + m[3][3]*v.w,
};
}
Mat4 operator*(const Mat4& o) const {
Mat4 r;
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
for (int k = 0; k < 4; k++)
r.m[i][j] += m[i][k] * o.m[k][j];
return r;
}
static Mat4 translate(float x, float y, float z) {
Mat4 r = identity();
r.m[0][3] = x; r.m[1][3] = y; r.m[2][3] = z;
return r;
}
static Mat4 scale(float sx, float sy, float sz) {
Mat4 r = identity();
r.m[0][0] = sx; r.m[1][1] = sy; r.m[2][2] = sz;
return r;
}
static Mat4 rotateX(float angle) {
float c = std::cos(angle), s = std::sin(angle);
Mat4 r = identity();
r.m[1][1] = c; r.m[1][2] = -s;
r.m[2][1] = s; r.m[2][2] = c;
return r;
}
static Mat4 rotateY(float angle) {
float c = std::cos(angle), s = std::sin(angle);
Mat4 r = identity();
r.m[0][0] = c; r.m[0][2] = s;
r.m[2][0] = -s; r.m[2][2] = c;
return r;
}
static Mat4 rotateZ(float angle) {
float c = std::cos(angle), s = std::sin(angle);
Mat4 r = identity();
r.m[0][0] = c; r.m[0][1] = -s;
r.m[1][0] = s; r.m[1][1] = c;
return r;
}
// Standard OpenGL-style perspective (right-handed, -Z forward)
static Mat4 perspective(float fov, float aspect, float near, float far) {
float t = std::tan(fov * 0.5f);
Mat4 r;
r.m[0][0] = 1.0f / (aspect * t);
r.m[1][1] = 1.0f / t;
r.m[2][2] = -(far + near) / (far - near);
r.m[2][3] = -(2.0f * far * near) / (far - near);
r.m[3][2] = -1.0f;
return r;
}
// Standard lookAt (right-handed)
static Mat4 lookAt(Vec3 eye, Vec3 target, Vec3 up) {
Vec3 f = (target - eye).normalized();
Vec3 r = f.cross(up).normalized();
Vec3 u = r.cross(f);
Mat4 mat = identity();
mat.m[0][0] = r.x; mat.m[0][1] = r.y; mat.m[0][2] = r.z; mat.m[0][3] = -r.dot(eye);
mat.m[1][0] = u.x; mat.m[1][1] = u.y; mat.m[1][2] = u.z; mat.m[1][3] = -u.dot(eye);
mat.m[2][0] = -f.x; mat.m[2][1] = -f.y; mat.m[2][2] = -f.z; mat.m[2][3] = f.dot(eye);
return mat;
}
};

129
include/pipeline.hpp Normal file
View file

@ -0,0 +1,129 @@
/**
* @file pipeline.hpp
* @brief Render pipeline visualization state and interface
*/
#pragma once
#include "math3d.hpp"
#include <SDL3/SDL.h>
struct GameContext;
constexpr int NUM_CUBE_VERTS = 8;
constexpr int NUM_CUBE_EDGES = 12;
constexpr int NUM_TRI_VERTS = 3;
constexpr int NUM_TRI_EDGES = 3;
struct PipelineState {
int current_stage = 0; // 0-5
int prev_stage = 0;
float transition_t = 1.0f; // 0→1 animation progress (1 = complete)
float fade_alpha = 1.0f; // for 3D↔2D mode transitions
// Interpolated vertices for rendering during transitions
Vec3 display_verts[NUM_CUBE_VERTS];
// Vertex selection
int selected_vertex = -1; // -1 = none, 0-7 = selected vertex index
bool pending_click = false;
float click_x = 0, click_y = 0;
// Object transform parameters (user-adjustable)
float obj_rot_y = 0.0f;
float obj_pos_x = 2.0f;
float obj_pos_y = 1.0f;
float obj_pos_z = -3.0f;
float obj_scale = 1.0f;
bool auto_rotate = false;
// Display camera (orbit around scene for visualization)
float cam_orbit_pitch = 25.0f; // degrees
float cam_orbit_yaw = -30.0f; // degrees
float cam_distance = 10.0f;
// Pipeline camera (the one being visualized in the pipeline)
float pipe_cam_x = 0.0f;
float pipe_cam_y = 2.0f;
float pipe_cam_z = 5.0f;
float pipe_cam_fov = 1.0f; // radians (~57 degrees)
float pipe_cam_near = 0.1f;
float pipe_cam_far = 100.0f;
// Mouse drag state
bool mouse_down = false;
float mouse_start_x = 0, mouse_start_y = 0;
float orbit_start_pitch = 0, orbit_start_yaw = 0;
// Slider drag state
int dragging_slider = -1; // -1 = none, 0+ = slider index
bool show_sliders = true;
// First-person camera view (picture-in-picture)
bool show_first_person = false;
// Matrix color breakdown
bool show_matrix_breakdown = false;
// Vertex shader simulation
bool shader_mode = false;
float shader_amplitude = 0.3f;
float shader_frequency = 2.0f;
float shader_time = 0.0f;
// Help overlay
bool show_help = false;
// Clipping visualizer
bool show_clipping = false;
struct ClippedFace {
static constexpr int MAX_VERTS = 24;
Vec4 verts[MAX_VERTS];
int count = 0;
bool was_clipped = false;
};
ClippedFace clipped_faces[6];
// Depth buffer / Z-fighting demo
bool show_depth_viz = false;
bool show_zfight_plane = false;
float zfight_plane_z = -3.0f;
static constexpr int NUM_PLANE_VERTS = 4;
Vec4 plane_world[4];
Vec3 plane_screen[4];
Vec3 plane_display[4];
// Input flags (set per event, cleared after processing)
bool key_left = false;
bool key_right = false;
bool key_space = false;
bool key_r = false;
bool key_num[6] = {};
// Computed transforms
Mat4 model_matrix;
Mat4 view_matrix;
Mat4 proj_matrix;
// Transformed vertices at each pipeline stage (cube)
Vec4 verts_object[NUM_CUBE_VERTS];
Vec4 verts_world[NUM_CUBE_VERTS];
Vec4 verts_view[NUM_CUBE_VERTS];
Vec4 verts_clip[NUM_CUBE_VERTS];
Vec3 verts_ndc[NUM_CUBE_VERTS];
Vec3 verts_screen[NUM_CUBE_VERTS];
// Second shape: triangle
bool show_triangle = false;
Vec4 tri_object[NUM_TRI_VERTS];
Vec4 tri_world[NUM_TRI_VERTS];
Vec4 tri_view[NUM_TRI_VERTS];
Vec4 tri_clip[NUM_TRI_VERTS];
Vec3 tri_ndc[NUM_TRI_VERTS];
Vec3 tri_screen[NUM_TRI_VERTS];
Vec3 tri_display[NUM_TRI_VERTS];
};
void pipeline_handle_event(PipelineState& ps, const SDL_Event& event);
void pipeline_update(PipelineState& ps, GameContext& ctx);
void pipeline_render(PipelineState& ps, GameContext& ctx);

16
include/systems.hpp Normal file
View file

@ -0,0 +1,16 @@
/**
* @file systems.hpp
* @brief ECS System registration
*/
#pragma once
#include <flecs.h>
// Forward declaration
struct GameContext;
/**
* @brief Register all components and systems with the ECS world
*/
void register_systems(flecs::world& ecs, GameContext* ctx);

38
remove_comments.py Normal file
View file

@ -0,0 +1,38 @@
#!/usr/bin/env python3
"""Remove all C/C++ comments from a file."""
import sys
import re
def remove_comments(source):
pattern = re.compile(
r'//.*?$|/\*.*?\*/|"(?:\\.|[^"\\])*"|\'(?:\\.|[^\'\\])*\'',
re.DOTALL | re.MULTILINE,
)
def replacer(match):
s = match.group(0)
if s.startswith("/"):
return " " if s.startswith("/*") else ""
return s
return pattern.sub(replacer, source)
if __name__ == "__main__":
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <file> [--inplace]", file=sys.stderr)
sys.exit(1)
path = sys.argv[1]
inplace = "--inplace" in sys.argv
with open(path) as f:
result = remove_comments(f.read())
if inplace:
with open(path, "w") as f:
f.write(result)
else:
print(result, end="")

BIN
renderpip-web.zip Normal file

Binary file not shown.

155
src/game.cpp Normal file
View file

@ -0,0 +1,155 @@
/**
* @file game.cpp
* @brief Main game implementation
*/
#include "game.hpp"
#include "systems.hpp"
#include "components.hpp"
#include "pipeline.hpp"
#include <cstdio>
bool game_init(GameContext& ctx, const WindowConfig& config) {
// Initialize SDL3
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) {
SDL_Log("Failed to initialize SDL: %s", SDL_GetError());
return false;
}
// Create window
ctx.window = SDL_CreateWindow(
config.title,
config.width,
config.height,
config.flags
);
if (!ctx.window) {
SDL_Log("Failed to create window: %s", SDL_GetError());
game_shutdown(ctx);
return false;
}
ctx.window_width = config.width;
ctx.window_height = config.height;
// Create renderer
ctx.renderer = SDL_CreateRenderer(ctx.window, nullptr);
if (!ctx.renderer) {
SDL_Log("Failed to create renderer: %s", SDL_GetError());
game_shutdown(ctx);
return false;
}
// Enable VSync
SDL_SetRenderVSync(ctx.renderer, 1);
// Register components and systems
register_systems(ctx.ecs, &ctx);
// Initialize timing
ctx.last_time = SDL_GetPerformanceCounter();
ctx.delta_time = 0.0f;
// Set running state
ctx.running = true;
ctx.paused = false;
SDL_Log("Game initialized successfully");
return true;
}
void game_shutdown(GameContext& ctx) {
if (ctx.renderer) {
SDL_DestroyRenderer(ctx.renderer);
ctx.renderer = nullptr;
}
if (ctx.window) {
SDL_DestroyWindow(ctx.window);
ctx.window = nullptr;
}
SDL_Quit();
SDL_Log("Game shutdown complete");
}
void game_process_events(GameContext& ctx) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
// Forward events to pipeline visualization (skip while paused to avoid queuing)
if (ctx.pipeline && !ctx.paused) {
pipeline_handle_event(*ctx.pipeline, event);
}
switch (event.type) {
case SDL_EVENT_QUIT:
ctx.running = false;
break;
case SDL_EVENT_KEY_DOWN:
if (event.key.key == SDLK_ESCAPE) {
ctx.running = false;
}
if (event.key.key == SDLK_P) {
ctx.paused = !ctx.paused;
}
break;
case SDL_EVENT_WINDOW_RESIZED:
ctx.window_width = event.window.data1;
ctx.window_height = event.window.data2;
break;
default:
break;
}
}
}
void game_update(GameContext& ctx) {
// Calculate delta time
uint64_t current_time = SDL_GetPerformanceCounter();
uint64_t freq = SDL_GetPerformanceFrequency();
ctx.delta_time = static_cast<float>(current_time - ctx.last_time) / static_cast<float>(freq);
ctx.last_time = current_time;
// Clamp delta time to prevent spiral of death
if (ctx.delta_time > 0.25f) {
ctx.delta_time = 0.25f;
}
// Clear screen
SDL_SetRenderDrawColor(ctx.renderer, 30, 30, 40, 255);
SDL_RenderClear(ctx.renderer);
if (!ctx.paused) {
// Update pipeline and ECS only when not paused
if (ctx.pipeline) {
pipeline_update(*ctx.pipeline, ctx);
}
ctx.ecs.progress(ctx.delta_time);
}
// Always render so the display doesn't blank while paused
if (ctx.pipeline) {
pipeline_render(*ctx.pipeline, ctx);
}
SDL_RenderPresent(ctx.renderer);
}
void game_render(GameContext& ctx) {
// Rendering is handled in game_update()
(void)ctx;
}
bool game_loop(GameContext& ctx) {
game_process_events(ctx);
game_update(ctx);
game_render(ctx);
return ctx.running;
}

82
src/main.cpp Normal file
View file

@ -0,0 +1,82 @@
/**
* @file main.cpp
* @brief Godot Render Pipeline Visualizer
*
* Interactive tool to visualize the 6 stages of the render pipeline:
* Object Space -> World Space -> View Space -> Clip Space -> NDC -> Screen Space
*
* Controls:
* Left/Right arrows: Navigate between pipeline stages
* 1-6: Jump to a specific stage
* WASD: Move object (X/Z plane)
* Q/E: Move object (Y axis)
* Mouse drag: Orbit the display camera
* Scroll wheel: Zoom in/out
* Space: Toggle auto-rotation
* R: Reset all transforms
* P: Pause
* ESC: Quit
*/
#define SDL_MAIN_HANDLED
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
#include "game.hpp"
#include "pipeline.hpp"
#include <cstdio>
#include <cstdlib>
// Global context for emscripten callback
static GameContext* g_ctx = nullptr;
#ifdef __EMSCRIPTEN__
static void em_main_loop() {
if (!game_loop(*g_ctx)) {
emscripten_cancel_main_loop();
}
}
#endif
int main(int argc, char *argv[]) {
(void)argc;
(void)argv;
SDL_SetMainReady();
static GameContext ctx;
static PipelineState pipeline;
ctx.pipeline = &pipeline;
g_ctx = &ctx;
WindowConfig config{
.title = "Render Pipeline Visualizer",
.width = 1280,
.height = 720,
.flags = SDL_WINDOW_RESIZABLE
};
if (!game_init(ctx, config)) {
std::fprintf(stderr, "Failed to initialize\n");
return EXIT_FAILURE;
}
std::printf("Render Pipeline Visualizer running.\n");
std::printf("Use Left/Right arrows to navigate stages, WASD/QE to move object.\n");
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop(em_main_loop, 0, 1);
#else
while (game_loop(ctx)) {
// Game loop handles everything
}
#endif
game_shutdown(ctx);
return EXIT_SUCCESS;
}

2661
src/pipeline.cpp Normal file

File diff suppressed because it is too large Load diff

72
src/systems.cpp Normal file
View file

@ -0,0 +1,72 @@
/**
* @file systems.cpp
* @brief ECS System implementations using FLECS C++ API
*/
#include "systems.hpp"
#include "components.hpp"
#include "game.hpp"
void register_systems(flecs::world &ecs, GameContext *ctx) {
// Movement system - updates positions based on velocities
ecs.system<Position, const Velocity>("MovementSystem")
.kind(flecs::OnUpdate)
.each([](flecs::iter &it, size_t, Position &pos, const Velocity &vel) {
pos.x += vel.x * it.delta_time();
pos.y += vel.y * it.delta_time();
});
// Lifetime system - decrements lifetime and destroys expired entities
ecs.system<Lifetime>("LifetimeSystem")
.kind(flecs::OnUpdate)
.each([](flecs::iter &it, size_t i, Lifetime &life) {
life.remaining -= it.delta_time();
if (life.remaining <= 0.0f) {
it.entity(i).add<ToDestroy>();
}
});
// Cleanup system - destroys entities marked with ToDestroy
ecs.system<const ToDestroy>("CleanupSystem")
.kind(flecs::PostUpdate)
.each([](flecs::entity e, const ToDestroy &) { e.destruct(); });
// Render rect system
ecs.system<const Position, const RectShape, const Color>("RenderRectSystem")
.kind(flecs::OnStore)
.ctx(ctx)
.each([](flecs::iter &it, size_t, const Position &pos,
const RectShape &rect, const Color &color) {
auto *game_ctx = static_cast<GameContext *>(it.ctx());
if (!game_ctx || !game_ctx->renderer)
return;
SDL_SetRenderDrawColor(game_ctx->renderer, color.r, color.g, color.b,
color.a);
SDL_FRect dst{pos.x - rect.width / 2.0f, pos.y - rect.height / 2.0f,
rect.width, rect.height};
SDL_RenderFillRect(game_ctx->renderer, &dst);
});
// Render sprite system
ecs.system<const Position, const Sprite>("RenderSpriteSystem")
.kind(flecs::OnStore)
.ctx(ctx)
.each([](flecs::iter &it, size_t, const Position &pos,
const Sprite &sprite) {
auto *game_ctx = static_cast<GameContext *>(it.ctx());
if (!game_ctx || !game_ctx->renderer)
return;
if (!sprite.visible || !sprite.texture)
return;
SDL_FRect dst{pos.x - sprite.src_rect.w / 2.0f,
pos.y - sprite.src_rect.h / 2.0f, sprite.src_rect.w,
sprite.src_rect.h};
SDL_RenderTexture(game_ctx->renderer, sprite.texture, &sprite.src_rect,
&dst);
});
}

68
web/shell.html Normal file
View file

@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Render Pipeline Visualizer</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #1e1e28;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
overflow: hidden;
font-family: 'Courier New', monospace;
color: #aaa;
}
canvas#canvas {
width: 100vw;
height: 100vh;
display: block;
object-fit: contain;
}
#output {
display: none;
}
#status {
position: fixed;
bottom: 4px;
right: 8px;
font-size: 0.75em;
color: #444;
}
</style>
</head>
<body>
<canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex="-1" width="1280" height="720"></canvas>
<div id="status">Loading...</div>
<textarea id="output" rows="8" style="display:none"></textarea>
<script>
var Module = {
print: function(text) { console.log(text); },
printErr: function(text) { console.error(text); },
canvas: document.getElementById('canvas'),
setStatus: function(text) {
if (text) document.getElementById('status').textContent = text;
},
onRuntimeInitialized: function() {
document.getElementById('status').textContent = 'Press H for controls';
},
totalDependencies: 0,
monitorRunDependencies: function(left) {
this.totalDependencies = Math.max(this.totalDependencies, left);
if (left) {
Module.setStatus('Loading... (' + (this.totalDependencies - left) + '/' + this.totalDependencies + ')');
} else {
Module.setStatus('');
}
}
};
Module.setStatus('Downloading...');
</script>
{{{ SCRIPT }}}
</body>
</html>