diff --git a/.clang-format b/.clang-format index 8d9e83ff5..3de6a1a25 100644 --- a/.clang-format +++ b/.clang-format @@ -310,7 +310,7 @@ StatementMacros: - _FMT_P2286_END - _EXTERN_C_UNLESS_PURE - _END_EXTERN_C_UNLESS_PURE -# TabWidth: 8 +TabWidth: 4 UseTab: Never # VerilogBreakBetweenInstancePorts: true # WhitespaceSensitiveMacros: diff --git a/.gitignore b/.gitignore index 76d395422..0067cd082 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ build/ .DS_Store -.vscode/ *.pak @@ -14,14 +13,16 @@ build/ cmake-build-debug - .xmake/ jniLibs/ -res/sounds +logs/ -res/sprites +#res/sounds +#res/sprites +#res/shaders/test +res/test res/scripts/test.lua -res/scripts/test_2d.lua \ No newline at end of file +res/scripts/test_2d.lua diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..0824428e0 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,32 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "(Cross-platform) Launch", + "type": "cppdbg", + "request": "launch", + "program": "${command:cmake.launchTargetPath}", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "linux": { + "MIMode": "gdb", + }, + "osx": { + "MIMode": "lldb", + }, + "windows": { + "MIMode": "gdb", + }, + "setupCommands": [ + { + "description": "Enable pretty-printing", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..99d5bee81 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,28 @@ +{ + "clangd.arguments": [ + "--compile-commands-dir=${workspaceFolder}", + "--background-index", + "--clang-tidy", + "--header-insertion=iwyu", + "--completion-style=detailed", + "--function-arg-placeholders", + "--fallback-style=llvm", + "--query-driver=**" + ], + "clangd.fallbackFlags": [ + "-std=c++20", + "-I${workspaceFolder}/engine/public", + "-I${workspaceFolder}/vendor/lua", + "-I${workspaceFolder}/vendor/mini_audio/include", + "-I${workspaceFolder}/vendor/glm", + "-I${workspaceFolder}/vendor/SDL/include", + "-I${workspaceFolder}/vendor/SDL_ttf/include", + "-I${workspaceFolder}/vendor/glad/include", + "-I${workspaceFolder}/vendor/stb_image/include", + "-I${workspaceFolder}/vendor/tinyxml/include", + "-I${workspaceFolder}/vendor/json", + "-I${workspaceFolder}/vendor/spdlog/include", + "-I${workspaceFolder}/vendor/assimp/include", + "-I${workspaceFolder}/vendor/sol2", + ] +} diff --git a/CMakeLists.txt b/CMakeLists.txt index 33b38cd36..6b28ee568 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,6 @@ cmake_minimum_required(VERSION 3.22) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) project(golias_engine) set(CMAKE_CXX_STANDARD 20) @@ -13,9 +14,7 @@ set(CMAKE_DEBUG_POSTFIX "") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/$") set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/$") -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -set(ENGINE_VERSION "0.0.5") +set(ENGINE_VERSION "0.0.1") string(TIMESTAMP BUILD_TS "%Y-%m-%d %H:%M") message("Golias Engine - Building in 2D/3D mode | (OPENGL/VULKAN/METAL/DIRECTX12) | Version ${ENGINE_VERSION} | Date: ${BUILD_TS}") @@ -24,15 +23,11 @@ message("Golias Engine - Building in 2D/3D mode | (OPENGL/VULKAN/METAL/DIRECTX12 # ENGINE CONFIGURATION # TODO: build client shared and server static # ========================================================= -option(WITH_EDITOR "Build the engine with editor support" OFF) -option(BUILD_SERVER "Build server binaries" OFF) -option(BUILD_RUNTIME "Build runtime binaries" ON) +option(BUILD_SANDBOX "Build Sandbox (engine development)" ON) option(BUILD_GOLIAS_TESTS "Build tests" OFF) message(STATUS "Build configuration:") -message(STATUS " WITH_EDITOR: ${WITH_EDITOR}") -message(STATUS " BUILD RUNTIME (CLIENT): ${BUILD_RUNTIME}") -message(STATUS " BUILD RUNTIME (SERVER): ${BUILD_SERVER}") +message(STATUS " BUILD SANDBOX (ENGINE DEVELOPMENT): ${BUILD_SANDBOX}") message(STATUS " BUILD TESTS: ${BUILD_GOLIAS_TESTS}") message(STATUS " Platform: ${CMAKE_SYSTEM_NAME}") @@ -58,7 +53,6 @@ endif () # ========================================================= # VENDOR LIBRARIES AND CONFIGURATION # ========================================================= - add_subdirectory(vendor) # ========================================================= @@ -70,22 +64,13 @@ add_subdirectory(engine) # ========================================================= # TESTS # ========================================================= -if (BUILD_Golias_TESTS) +if (BUILD_GOLIAS_TESTS) enable_testing() -endif () - -if (BUILD_Golias_TESTS) add_subdirectory(tests) endif () -if (BUILD_SERVER) - message(STATUS "Adding server build") - add_compile_definitions(BUILD_SERVER) - add_subdirectory(server) -endif () - -if (BUILD_RUNTIME) - message(STATUS "Adding runtime build") - add_compile_definitions(BUILD_RUNTIME) - add_subdirectory(runtime) +if (BUILD_SANDBOX) + message(STATUS "Adding sandbox build") + add_compile_definitions(BUILD_SANDBOX) + add_subdirectory(sandbox) endif () diff --git a/CMakePresets.json b/CMakePresets.json index dff5298b9..080c28d73 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -6,6 +6,16 @@ "patch": 1 }, "configurePresets": [ + { + "name": "tests", + "displayName": "Tests Configuration", + "binaryDir": "${sourceDir}/build/default", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "BUILD_GOLIAS_TESTS": "ON", + "BUILD_SANDBOX": "OFF" + } + }, { "name": "linux-debug", "displayName": "Linux Debug [gcc/clang]", @@ -91,7 +101,7 @@ "CMAKE_SYSTEM_NAME": "Emscripten", "CMAKE_BUILD_TYPE": "Debug", "WITH_EDITOR": "ON", - "CURL_DISABLE_INSTALL":"ON" + "CURL_DISABLE_INSTALL": "ON" } }, { @@ -101,8 +111,7 @@ "toolchainFile": "$env{EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake", "cacheVariables": { "CMAKE_SYSTEM_NAME": "Emscripten", - "CMAKE_BUILD_TYPE": "Release", - "CURL_DISABLE_INSTALL":"ON" + "CMAKE_BUILD_TYPE": "Release" } }, { @@ -128,60 +137,85 @@ } ], "buildPresets": [ + { + "name": "tests-build", + "displayName": "Tests Build", + "configurePreset": "tests", + "targets": [ + "golias_tests" + ], + "verbose": true + }, { "name": "mac-debug-build", "displayName": "macOS Debug Build", "configurePreset": "mac-debug", - "targets": [ "golias_engine" ], + "targets": [ + "golias_sandbox" + ], "verbose": true }, { "name": "mac-release-build", "displayName": "macOS Release Build", "configurePreset": "mac-release", - "targets": [ "golias_engine" ], + "targets": [ + "golias_sandbox" + ], "verbose": true }, { "name": "linux-debug-build", "displayName": "Linux Debug Build", "configurePreset": "linux-debug", - "targets": [ "golias_engine" ], + "targets": [ + "golias_sandbox" + ], "verbose": true }, { "name": "linux-release-build", "displayName": "Linux Release Build", "configurePreset": "linux-release", - "targets": [ "golias_engine" ], + "targets": [ + "golias_sandbox" + ], "verbose": true }, { "name": "windows-debug-build", "displayName": "Windows Debug Build", "configurePreset": "windows-debug", - "targets": [ "golias_engine" ], + "targets": [ + "golias_sandbox" + ], "verbose": true }, { "name": "windows-release-build", "displayName": "Windows Release Build", "configurePreset": "windows-release", - "targets": [ "golias_engine" ], + "targets": [ + "golias_sandbox" + ], "verbose": true }, { "name": "webgl-debug-build", "displayName": "WebGL Debug Build", "configurePreset": "web-debug", - "targets": [ "golias_engine" ], + "targets": [ + "golias_sandbox" + ], "verbose": true }, { "name": "webgl-release-build", "displayName": "WebGL Release Build", "configurePreset": "web-release", - "targets": [ "golias_engine" ], + "targets": [ + "golias_sandbox" + ], "verbose": true } ] diff --git a/docs/shader_reference.md b/docs/shader_reference.md new file mode 100644 index 000000000..1cd035acc --- /dev/null +++ b/docs/shader_reference.md @@ -0,0 +1,381 @@ +# Shader System Documentation + +## Overview + +The shader system provides a flexible way to create custom visual effects for your game using GLSL shaders. It's +compatible with OpenGL GLSL version 3.3 and OpenGL ES 3.0, making it suitable for both desktop and mobile platforms. + +## Table of Contents + +- [Getting Started](#getting-started) +- [Shader Structure](#shader-structure) +- [Built-in Variables](#built-in-variables) +- [Common Functions](#common-functions) +- [Custom Variables](#custom-variables) +- [Best Practices](#best-practices) +- [Examples](examples.md) + +--- + +## Getting Started + +### Loading a Shader + +To load a shader in your game, use the rendering API: + +```cpp +RID shader = rendering->create_shader_from_file("res/shaders/my_shader.glsl"); +``` + +The shader file should be placed in your project's shader directory (typically `res/shaders/`). + +### Basic Shader Template + +Every shader consists of two main functions: `vertex()` and `fragment()`. Here's a minimal working shader: + +### Shader Header + +- shader_type canvas_item; // For 2D shaders +- shader_type spatial; // For 3D shaders +- #[compute]; // For Compute shaders + +| Header | Type | Description | + |----------------------------|-----------|--------------------------| +| `shader_type canvas_item;` | `2D` | Used for 2D rendering | +| `shader_type spatial;` | `3D` | Used for 3D rendering | +| `#[compute]` | `compute` | Used for compute shaders | + + + +```glsl + +shader_type canvas_item; + +// Can be empty if not needed +void vertex() { + +} + +void fragment() { + vec4 tex = texture(TEXTURE, UV); + COLOR = tex.rgb * COLOR.rgb; + ALPHA = tex.a * COLOR.a; +} +``` + +**What this does:** + +- **Vertex Shader**: Transforms vertex positions to screen space +- **Fragment Shader**: Samples the texture and applies vertex colors + +--- + +## Shader Structure + +### Compute Shader + +The compute shader is used for general-purpose computing tasks on the GPU. It operates independently of the graphics +pipeline. + +> ⚠️ Note: Compute shaders are not supported on `Compatibility` renderer backend. + +```glsl +#[compute] +#version 450 + +layout (local_size_x = 16, local_size_y = 16) in; + +void main(){ +// Do compute work here +} + +``` + +### Vertex Shader + +The vertex shader processes each vertex of your mesh. + +```glsl + +void vertex() { + // You can also modify vertex positions, pass data to fragment shader, etc. + VERTEX.z += sin(TIME * 2.0 + VERTEX.x * 0.1) * 0.1; +} +``` + +### Fragment Shader + +The fragment shader runs for each pixel and determines its final color and transparency. + +```glsl +void fragment() { + // Set the pixel's color (RGB) + COLOR = vec3(1.0, 0.0, 0.0); // Red + + // Set the pixel's transparency (0.0 = transparent, 1.0 = opaque) + ALPHA = 1.0; +} +``` + +--- + +## Built-in Variables + +### Vertex Shader Variables + +#### Inputs (Read-Only) + +| Variable | Type | Description | +|----------|--------|---------------------------------| +| `VERTEX` | `vec3` | Local vertex position (x, y, z) | +| `COLOR` | `vec4` | Vertex color (RGBA) | +| `UV` | `vec2` | Texture coordinates (x, y) | + +#### Outputs + +| Variable | Type | Description | +|----------|------|-------------| + +### Fragment Shader Variables + +#### Inputs (Read-Only) + +| Variable | Type | Description | +|----------|--------|----------------------------------| +| `UV` | `vec2` | Interpolated texture coordinates | +| `COLOR` | `vec4` | Interpolated vertex color | + +#### Outputs (Write to these) + +| Variable | Type | Default | Description | +|----------|---------|-------------|-------------------------------| +| `COLOR` | `vec3` | `vec3(1.0)` | Base color of the pixel (RGB) | +| `ALPHA` | `float` | `1.0` | Transparency (0.0-1.0) | + +### Uniform Variables (Available in Both Shaders) + +| Variable | Type | Description | +|--------------------------|-------------|--------------------------------------------| +| `MODEL_MATRIX` | `mat4` | Transforms from local to world space | +| `VIEW_MATRIX` | `mat4` | Camera view transformation | +| `PROJECTION_MATRIX` | `mat4` | Projection transformation | +| `VIEW_PROJECTION_MATRIX` | `mat4` | Combined view × projection (optimized) | +| `TIME` | `float` | Elapsed time in seconds since start | +| `CAMERA_POSITION` | `vec3` | World position of the camera | +| `TEXTURE` | `sampler2D` | The default texture sampler | +| `SCREEN_SIZE` | `vec2` | Dimensions of the screen (width, height) | +| `COLOR` | `vec4` | Global color multiplier (RGBA) | +| `VIEWPORT_SIZE` | `vec2` | Dimensions of the viewport (width, height) | + +--- + +## Common Functions + +### Mathematical Functions + +```glsl +// Trigonometry +sin(x), cos(x), tan(x) +asin(x), acos(x), atan(y, x) + +// Power and roots +pow(x, y) // x^y +sqrt(x) // Square root +exp(x) // e^x +log(x) // Natural logarithm + +// Rounding and manipulation +abs(x) // Absolute value +floor(x) // Round down +ceil(x) // Round up +fract(x) // Fractional part (x - floor(x)) +mod(x, y) // Modulo (x % y) + +// Range functions +min(x, y) // Minimum value +max(x, y) // Maximum value +clamp(x, min, max) // Constrain x between min and max + +// Interpolation +mix(a, b, t) // Linear interpolation: a + (b-a) * t +step(edge, x) // Returns 0.0 if x < edge, else 1.0 +smoothstep(a, b, x) // Smooth interpolation between a and b +``` + +### Vector Functions + +```glsl +length(v) // Vector length/magnitude +distance(a, b) // Distance between two points +normalize(v) // Normalize vector to length 1 +dot(a, b) // Dot product +cross(a, b) // Cross product (vec3 only) +reflect(v, n) // Reflect vector v around normal n +``` + +### Texture Functions + +```glsl +texture(sampler, uv) // Sample texture at UV coordinates +textureSize(sampler, lod) // Get texture dimensions +``` + +### Additional Resources + +- [GLSL Reference Card](https://www.opengl.org/sdk/docs/reference_card/opengl45-reference-card.pdf) - Complete list of + built-in functions +- [Docs.gl](https://docs.gl/sl4/log) - Detailed OpenGL documentation + +--- + +## Custom Variables + +### Custom Uniforms + +Uniforms allow you to pass data from your game code to the shader: + +```glsl +uniform float myValue; +uniform vec3 myColor; +uniform sampler2D customTexture; + +void fragment() { + COLOR = myColor * myValue; + ALPHA = 1.0; +} +``` + +**Setting uniforms from code:** + +```cpp + float time_value = SDL_GetTicks() / 1000.0f; + rd->push_constant("myValue", &time_value, sizeof(float)); +``` + +### Custom Varyings + +Varyings pass data from the vertex shader to the fragment shader: + +```glsl +varying vec3 worldPos; +varying vec2 customUV; + +void vertex() { + // Calculate and pass world position + worldPos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; + customUV = UV * 2.0; + +} + +void fragment() { + // Use the interpolated world position + float dist = length(worldPos - CAMERA_POSITION); + COLOR = vec3(dist * 0.1) * texture(TEXTURE, customUV).rgb; + ALPHA = 1.0; +} +``` + +**How varyings work:** + +1. Set the value in the vertex shader for each vertex +2. GPU automatically interpolates values between vertices +3. Fragment shader receives the interpolated value for each pixel + +--- + +## Best Practices + +### Performance Tips + +1. **Minimize texture lookups**: Each `texture()` call is expensive. Store results in variables if you need to use them + multiple times. + +```glsl +// Good +vec4 tex = texture(TEXTURE, UV); +COLOR = tex.rgb * 0.5; +ALPHA = tex.a; + +// Bad +COLOR = texture(TEXTURE, UV).rgb * 0.5; +ALPHA = texture(TEXTURE, UV).a; // Samples texture twice! +``` + +2. **Use built-in functions**: Functions like `mix()`, `smoothstep()`, and `clamp()` are optimized by the GPU. + +3. **Avoid branching**: `if` statements can be slow. Use `step()` or `smoothstep()` instead when possible. + +```glsl +// Slower +if (UV.x > 0.5) { +COLOR = vec3(1.0); +} else { +COLOR = vec3(0.0); +} + +// Faster +COLOR = vec3(step(0.5, UV.x)); +``` + +### Debugging Tips + +1. **Visualize values**: Output variables as colors to see what's happening + +```glsl +COLOR = vec3(UV, 0.0); // Visualize UV coordinates +COLOR = vec3(fract(TIME)); // Visualize time +``` + +2. **Test incrementally**: Start with a simple shader and add complexity gradually + +3. **Use color channels**: Split complex calculations across RGB channels for debugging + +### Common Patterns + +**Time-based animation:** + +```glsl +float wave = sin(TIME * 2.0) * 0.5 + 0.5; // Oscillates between 0 and 1 +``` + +**Distance-based effects:** + +```glsl +float dist = length(UV - vec2(0.5)); // Distance from center +``` + +**Tiling textures:** + +```glsl +vec2 tiledUV = fract(UV * 4.0); // Tile 4x4 +``` + +--- + +## See Also + +- [Shader Examples](tests/) - Shader examples can be found in the tests directory +- [GLSL Reference](https://www.opengl.org/sdk/docs/reference_card/opengl45-reference-card.pdf) +- [Docs.gl](https://docs.gl/) - OpenGL documentation + +--- + +## Troubleshooting + +### Common Issues + +**"Shader failed to compile"** + +- Check for syntax errors (missing semicolons, mismatched brackets) +- Ensure all variables are declared before use + +**"Black screen / Nothing renders"** + +- Make sure `ALPHA` is set to a value > 0 +- Check that textures are loaded properly + +**"Shader compiles but looks wrong"** + +- Output intermediate values as colors for debugging +- Verify UV coordinates are in range [0, 1] +- Check that matrices are being multiplied in correct order \ No newline at end of file diff --git a/doxyfile b/doxyfile index 36df49f73..039749d05 100644 --- a/doxyfile +++ b/doxyfile @@ -950,7 +950,8 @@ WARN_LOGFILE = # Note: If this tag is empty the current directory is searched. INPUT = ./engine \ - README.md + README.md \ + docs/shader_reference.md # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index f1580cca8..e77774177 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -6,10 +6,8 @@ set(CMAKE_C_STANDARD 99) add_definitions(-DENGINE_VERSION="${ENGINE_VERSION}") -# Base sources file(GLOB_RECURSE ENGINE_SOURCES "private/*.cpp" "private/*.c") -# Exclude all platform-specific and .mm files initially list(FILTER ENGINE_SOURCES EXCLUDE REGEX "engine/private/core/platform/.*") list(FILTER ENGINE_SOURCES EXCLUDE REGEX "engine/private/core/renderer/.mm") diff --git a/engine/private/core/api/engine_api.cpp b/engine/private/core/api/engine_api.cpp deleted file mode 100644 index 02df6bc08..000000000 --- a/engine/private/core/api/engine_api.cpp +++ /dev/null @@ -1,221 +0,0 @@ -#pragma once -#include "core/api/engine_api.h" - -void generate_unique_name(flecs::entity& e, const char* default_name, const char* custom_name) { - if (custom_name) { - e.set_name(custom_name); - } else { - std::string unique_name = std::string(default_name) + "_" + std::to_string(e.id()); - e.set_name(unique_name.c_str()); - } -} - -GameObject create_camera_3d_entity(const char* name, const glm::vec3& position, const glm::vec3& rotation, float fov, float near_plane, - float far_plane) { - - - const auto& world = GEngine->get_world(); - - flecs::entity e = world.entity().add().set({position, rotation}); - - generate_unique_name(e, "Camera3D", name); - - const GameObject go(e); - - return go; -} - -GameObject create_directional_light_3d_entity(const char* name, const glm::vec3& direction, const glm::vec3& color, float intensity, - bool cast_shadows, float shadow_distance, float shadow_near, float shadow_far) { - const auto& world = GEngine->get_world(); - - flecs::entity e = world.entity().set({}).set( - {glm::normalize(direction), glm::vec3(color), intensity, cast_shadows, shadow_distance, shadow_near, shadow_far}); - - generate_unique_name(e, "DirectionalLight3D", name); - - const GameObject go(e); - - return go; -} - -GameObject create_spot_light_3d_entity(const char* name, const glm::vec3& position, const glm::vec3& direction, const glm::vec3& color, - float inner_cutoff, float outer_cutoff, float intensity) { - const auto& world = GEngine->get_world(); - - flecs::entity entity = world.entity() - .set({position}) - .set({glm::vec3(direction), glm::vec3(color), intensity, inner_cutoff, outer_cutoff}); - - generate_unique_name(entity, "SpotLight3D", name); - - const GameObject go(entity); - return go; -} - -GameObject create_mesh_3d_entity(const char* name, const char* path, const glm::vec3& position, const glm::vec3& rotation, - const glm::vec3& scale, const char* material_tag) { - - const auto renderer = GEngine->get_renderer(); - - if (!renderer->_meshes.contains(path)) { - MeshInstance3D mesh = AssimpLoader::load_mesh(path); - renderer->_meshes[path] = {mesh}; - } - - if (!renderer->_materials.contains(material_tag)) { - spdlog::error("Material '{}' not registered!", material_tag); - return GameObject{}; - } - - auto entity = GEngine->get_world() - .entity() - .set(Transform3D{position, rotation, scale}) - .set(MeshRef{&renderer->_meshes[path][0]}) - .set(MaterialRef{&renderer->_materials[material_tag][0]}); - - generate_unique_name(entity, "MeshInstance3D", name); - - spdlog::info("MeshInstance3D entity '{}' created with material '{}'.", name, material_tag); - - const GameObject go(entity); - - return go; -} - - -GameObject create_model_3d_entity(const char* name, const char* path, BlendMode blend_mode, const glm::vec3& position, - const glm::vec3& rotation, const glm::vec3& scale) { - - auto renderer = GEngine->get_renderer(); - - - if (!renderer->_meshes.contains(path) || !renderer->_materials.contains(path)) { - Model model = AssimpLoader::load_model(path); - - if (model.meshes.empty() || model.materials.empty()) { - spdlog::error("Failed to load model from path: {}", path); - return GameObject{}; - } - - renderer->_meshes[path] = model.meshes; - renderer->_materials[path] = model.materials; - } - - const auto& meshes = renderer->_meshes[path]; - auto& materials = renderer->_materials[path]; - - for (auto& material : materials) { - material.blend_mode = blend_mode; - material.update_feature_flags(); // Update flags after changing blend_mode - spdlog::debug("Material blend mode set to: {}, IBL enabled: {}", static_cast(blend_mode), material.use_ibl); - } - - auto entity = GEngine->get_world().entity(name); - - for (size_t i = 0; i < meshes.size(); ++i) { - entity.child().set(Transform3D{position, rotation, scale}).set(MeshRef{&meshes[i]}).set(MaterialRef{&materials[i]}); - } - - spdlog::info("MeshInstance3D entity '{}' created with {} mesh parts.", name, meshes.size()); - const GameObject go(entity); - - return go; -} - - -GameObject create_camera_2d_entity(const char* name, const glm::vec2& position, float rotation, float zoom) { - - const auto& world = GEngine->get_world(); - - Camera2D camera; - camera.zoom = zoom; - - flecs::entity e = world.entity().set(camera).set(Transform2D{position, glm::vec2(1.0f), rotation}); - - generate_unique_name(e, "Camera2D", name); - - const GameObject go(e); - - return go; -} - -GameObject create_rectangle_2d_entity(const char* name, const glm::vec2& position, const glm::vec2& size, const glm::vec4& color, - bool filled) { - const auto& world = GEngine->get_world(); - - flecs::entity e = world.entity().set(Transform2D{position}).set(Rectangle2D{size, color, filled}); - - generate_unique_name(e, "Rectangle2D", name); - - return GameObject(e); -} - -GameObject create_circle_2d_entity(const char* name, const glm::vec2& position, float radius, const glm::vec4& color, bool filled, - float thickness, int segments) { - const auto& world = GEngine->get_world(); - - flecs::entity e = world.entity().set(Transform2D{position}).set(Circle2D{radius, color, filled, thickness, segments}); - - generate_unique_name(e, "Circle2D", name); - - - return GameObject(e); -} - -GameObject create_line_2d_entity(const char* name, const glm::vec2& start, const glm::vec2& end, const glm::vec4& color, - float thickness) { - const auto& world = GEngine->get_world(); - - glm::vec2 relative_end = end - start; - flecs::entity e = world.entity().set(Transform2D{start}).set(Line2D{relative_end, color, thickness}); - - generate_unique_name(e, "Line2D", name); - - return GameObject(e); -} - -GameObject create_triangle_2d_entity(const char* name, const glm::vec2& p1, const glm::vec2& p2, const glm::vec2& p3, - const glm::vec4& color, bool filled) { - const auto& world = GEngine->get_world(); - - glm::vec2 relative_p2 = p2 - p1; - glm::vec2 relative_p3 = p3 - p1; - flecs::entity e = world.entity().set(Transform2D{p1}).set(Triangle2D{relative_p2, relative_p3, color, filled}); - - generate_unique_name(e, "Triangle2D", name); - - return GameObject(e); -} - -GameObject create_label_2d_entity(const char* name, const std::string& text, const glm::vec2& position, const glm::vec4& color, - float scale) { - const auto& world = GEngine->get_world(); - - flecs::entity e = world.entity().set(Transform2D{position}).set(Label2D{text, color, scale, "default"}); - - generate_unique_name(e, "Label2D", name); - - return GameObject(e); -} - -GameObject create_sprite_2d_entity(const char* name, const std::string& texture_path, const glm::vec2& position, const glm::vec2& size, - const glm::vec4& color) { - const auto& world = GEngine->get_world(); - - flecs::entity e = world.entity().set(Transform2D{position}).set(Sprite2D{texture_path, color, size}); - - generate_unique_name(e, "Sprite2D", name); - - spdlog::info("Sprite2D entity '{}' created with texture '{}'.", name, texture_path); - - return GameObject(e); -} - - - -void create_material(const char* name, Material material) { - material.update_feature_flags(); - GEngine->get_renderer()->_materials[name] = {material}; - spdlog::info("Material '{}' created and registered.", name); -} diff --git a/engine/private/core/component/comp_system.cpp b/engine/private/core/component/comp_system.cpp deleted file mode 100644 index 3c4f92ecb..000000000 --- a/engine/private/core/component/comp_system.cpp +++ /dev/null @@ -1,187 +0,0 @@ -#include "core/component/comp_system.h" - -#include "core/engine.h" - -// ============================================================================= -// 2D Rendering Systems -// ============================================================================= -void render_rectangles_2d_system() { - auto* renderer = GEngine->get_renderer(); - - GEngine->get_world().each([&](const Rectangle2D& rect, const Transform2D& transform) { - renderer->draw_rect_2d(transform.position, rect.size, rect.color, rect.filled); - }); -} - -void render_circles_2d_system() { - auto* renderer = GEngine->get_renderer(); - - GEngine->get_world().each([&](const Circle2D& circle, const Transform2D& transform) { - if (circle.filled) { - renderer->draw_circle_2d(transform.position, circle.radius, circle.color, true, circle.segments); - } else { - renderer->draw_circle_outline_2d(transform.position, circle.radius, circle.color, circle.thickness, circle.segments); - } - }); -} - -void render_lines_2d_system() { - auto* renderer = GEngine->get_renderer(); - - GEngine->get_world().each([&](const Line2D& line, const Transform2D& transform) { - glm::vec2 end = transform.position + line.end_point; - renderer->draw_line_2d(transform.position, end, line.color, line.thickness); - }); -} - -void render_triangles_2d_system() { - auto* renderer = GEngine->get_renderer(); - - GEngine->get_world().each([&](const Triangle2D& triangle, const Transform2D& transform) { - glm::vec2 p1 = transform.position; - glm::vec2 p2 = transform.position + triangle.point2; - glm::vec2 p3 = transform.position + triangle.point3; - renderer->draw_triangle_2d(p1, p2, p3, triangle.color, triangle.filled); - }); -} - -void render_labels_2d_system() { - auto* renderer = GEngine->get_renderer(); - - GEngine->get_world().each([&](const Label2D& label, const Transform2D& transform) { - renderer->draw_text_2d(label.text, transform.position, label.color, label.scale); - }); -} - -void render_sprites_2d_system() { - auto* renderer = GEngine->get_renderer(); - - GEngine->get_world().each([&](const Sprite2D& sprite, const Transform2D& transform) { - std::shared_ptr gpu_texture = nullptr; - - if (renderer->_textures.contains(sprite.texture_name)) { - gpu_texture = renderer->_textures[sprite.texture_name]; - } else { - - renderer->load_texture_from_file(sprite.texture_name); - if (renderer->_textures.contains(sprite.texture_name)) { - gpu_texture = renderer->_textures[sprite.texture_name]; - } - } - - if (!gpu_texture) { - spdlog::warn("Failed to load texture for sprite: {}", sprite.texture_name); - return; - } - - glm::vec2 size = sprite.size; - - - if (size.x == 0 || size.y == 0) { - size = glm::vec2(gpu_texture->get_width(), gpu_texture->get_height()); - } - - renderer->draw_texture_2d(gpu_texture, transform.position, size, {}, FlipMode::NONE, sprite.color); - }); -} - -void render_world_2d_system() { - auto* renderer = GEngine->get_renderer(); - - static Camera2D camera; - static Transform2D camera_transform; - - GEngine->get_world().each([&](const Camera2D& cam, const Transform2D& transform) { - camera = cam; - camera_transform = transform; - }); - - float v_width = static_cast(renderer->get_virtual_width()); - float v_height = static_cast(renderer->get_virtual_height()); - - camera.viewport_size = glm::vec2(v_width, v_height); - - renderer->begin_frame_2d(); - - render_rectangles_2d_system(); - render_circles_2d_system(); - render_lines_2d_system(); - render_triangles_2d_system(); - render_sprites_2d_system(); - render_labels_2d_system(); - - renderer->end_frame_2d(camera, camera_transform); -} - -glm::mat4 calculate_light_system(const glm::vec3& camer_pos, const glm::mat4& camera_view, const glm::mat4& camera_proj, - std::vector& directional_lights) { - - glm::mat4 light_space_matrix(1.0f); - - GEngine->get_world().each([&](flecs::entity e, Transform3D& t, DirectionalLight3D& light) { - directional_lights.push_back(light); - - if (light.castShadows && light_space_matrix == glm::mat4(1.0f)) { - - light_space_matrix = light.get_light_space_matrix(camer_pos); - - // Alternative: Frustum-fitted shadow map - // light_space_matrix = light.get_light_space_matrix(camera_view,camera_proj); - } - }); - - return light_space_matrix; -} - -// TODO: spotlight shadows??? -void calculate_spot_light_system(std::vector>& spot_lights) { - GEngine->get_world().each([&](flecs::entity e, Transform3D& t, SpotLight3D& light) { spot_lights.emplace_back(t, light); }); -} - -void render_world_3d_system() { - - static Camera3D main_camera; - static Transform3D camera_transform; - std::vector directionalLights; - std::vector> spotLights; - - GEngine->get_world().each([&](flecs::entity e, const Camera3D& cam, const Transform3D& transform) { - if (!e.has()) { - return; - } - - main_camera = cam; - camera_transform = transform; - }); - - calculate_spot_light_system(spotLights); - - const auto renderer = GEngine->get_renderer(); - - renderer->begin_frame(); - const auto query = GEngine->get_world().query(); - - query.each([&](const Transform3D& transform, const MeshRef& mesh, const MaterialRef& material) { - renderer->add_to_render_batch(transform, mesh, material); - renderer->add_to_shadow_batch(transform, mesh); - }); - - const glm::mat4 main_camera_view = main_camera.get_view(camera_transform); - const glm::mat4 main_camera_proj = main_camera.get_projection(renderer->get_virtual_width(), renderer->get_virtual_height()); - - const glm::mat4 light_space_matrix = - calculate_light_system(camera_transform.position, main_camera_view, main_camera_proj, directionalLights); - - - renderer->begin_shadow_pass(); - renderer->render_shadow_pass(light_space_matrix); - renderer->end_shadow_pass(); - - renderer->begin_render_target(); - renderer->render_main_target(main_camera, camera_transform, light_space_matrix, directionalLights, spotLights); - renderer->end_render_target(); - - renderer->begin_environment_pass(); - renderer->render_environment_pass(main_camera); - renderer->end_environment_pass(); -} diff --git a/engine/private/core/component/components.cpp b/engine/private/core/component/components.cpp deleted file mode 100644 index 827b53a75..000000000 --- a/engine/private/core/component/components.cpp +++ /dev/null @@ -1,266 +0,0 @@ -#include "core/component/components.h" - -#include "core/renderer/base_struct.h" - -glm::mat4 Transform3D::get_matrix() const { - glm::mat4 mat = glm::translate(glm::mat4(1.0f), position); - mat = glm::rotate(mat, rotation.x, glm::vec3(1, 0, 0)); - mat = glm::rotate(mat, rotation.y, glm::vec3(0, 1, 0)); - mat = glm::rotate(mat, rotation.z, glm::vec3(0, 0, 1)); - mat = glm::scale(mat, scale); - return mat; -} - - -void Camera3D::update_vectors() { - glm::vec3 f; - f.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch)); - f.y = sin(glm::radians(pitch)); - f.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch)); - front = glm::normalize(f); - - right = glm::normalize(glm::cross(front, world_up)); - up = glm::normalize(glm::cross(right, front)); -} - - -void Material::bind(Shader* shader) const { - shader->set_value("material.albedo", albedo); - shader->set_value("material.metallic", metallic); - shader->set_value("material.roughness", roughness); - shader->set_value("material.ao", ao); - shader->set_value("material.emissive", emissive); - shader->set_value("material.emissiveStrength", emissive_strength); - shader->set_value("material.ior", ior); - - shader->set_value("has_features", has_features); - - if ((has_features & HAS_ALBEDO_MAP) && albedo_map) { - glActiveTexture(GL_TEXTURE0 + ALBEDO_TEXTURE_UNIT); - glBindTexture(GL_TEXTURE_2D, albedo_map); - shader->set_value("ALBEDO_MAP", ALBEDO_TEXTURE_UNIT); - } else { - glActiveTexture(GL_TEXTURE0 + ALBEDO_TEXTURE_UNIT); - glBindTexture(GL_TEXTURE_2D, 0); - } - - if ((has_features & HAS_SPECULAR_MAP) && specular_map) { - glActiveTexture(GL_TEXTURE0 + SPECULAR_TEXTURE_UNIT); - glBindTexture(GL_TEXTURE_2D, specular_map); - shader->set_value("SPECULAR_MAP", SPECULAR_TEXTURE_UNIT); - } else { - glActiveTexture(GL_TEXTURE0 + SPECULAR_TEXTURE_UNIT); - glBindTexture(GL_TEXTURE_2D, 0); - } - - if ((has_features & HAS_METALLIC_MAP) && metallic_map) { - glActiveTexture(GL_TEXTURE0 + METALLIC_TEXTURE_UNIT); - glBindTexture(GL_TEXTURE_2D, metallic_map); - shader->set_value("METALLIC_MAP", METALLIC_TEXTURE_UNIT); - } else { - glActiveTexture(GL_TEXTURE0 + METALLIC_TEXTURE_UNIT); - glBindTexture(GL_TEXTURE_2D, 0); - } - - if ((has_features & HAS_ROUGHNESS_MAP) && roughness_map) { - glActiveTexture(GL_TEXTURE0 + ROUGHNESS_TEXTURE_UNIT); - glBindTexture(GL_TEXTURE_2D, roughness_map); - shader->set_value("ROUGHNESS_MAP", ROUGHNESS_TEXTURE_UNIT); - } else { - glActiveTexture(GL_TEXTURE0 + ROUGHNESS_TEXTURE_UNIT); - glBindTexture(GL_TEXTURE_2D, 0); - } - - if ((has_features & HAS_NORMAL_MAP) && normal_map) { - glActiveTexture(GL_TEXTURE0 + NORMAL_MAP_TEXTURE_UNIT); - glBindTexture(GL_TEXTURE_2D, normal_map); - shader->set_value("NORMAL_MAP", NORMAL_MAP_TEXTURE_UNIT); - } else { - glActiveTexture(GL_TEXTURE0 + NORMAL_MAP_TEXTURE_UNIT); - glBindTexture(GL_TEXTURE_2D, 0); - } - - if ((has_features & HAS_AO_MAP) && ao_map) { - glActiveTexture(GL_TEXTURE0 + AMBIENT_OCCLUSION_TEXTURE_UNIT); - glBindTexture(GL_TEXTURE_2D, ao_map); - shader->set_value("AO_MAP", AMBIENT_OCCLUSION_TEXTURE_UNIT); - } else { - glActiveTexture(GL_TEXTURE0 + AMBIENT_OCCLUSION_TEXTURE_UNIT); - glBindTexture(GL_TEXTURE_2D, 0); - } - - if ((has_features & HAS_EMISSIVE_MAP) && emissive_map) { - glActiveTexture(GL_TEXTURE0 + EMISSIVE_TEXTURE_UNIT); - glBindTexture(GL_TEXTURE_2D, emissive_map); - shader->set_value("EMISSIVE_MAP", EMISSIVE_TEXTURE_UNIT); - } else { - glActiveTexture(GL_TEXTURE0 + EMISSIVE_TEXTURE_UNIT); - glBindTexture(GL_TEXTURE_2D, 0); - } -} - - - -glm::mat4 Camera3D::get_view(const Transform3D& transform) const { - return glm::lookAt(transform.position, transform.position + front, up); -} - -glm::mat4 Camera3D::get_projection(int w, int h) const { - - if (w <= 0 || h <= 0) { - return glm::mat4(1.0f); - } - - float aspect = (float) w / (float) h; - - if (aspect <= 0.0f || std::isinf(aspect) || std::isnan(aspect)) { - return glm::mat4(1.0f); - } - - return glm::perspective(glm::radians(fov), aspect, 0.1f, view_distance); -} - -void Camera3D::move_forward(Transform3D& transform, float dt) { - transform.position += front * speed * dt; -} - -void Camera3D::move_backward(Transform3D& transform, float dt) { - transform.position -= front * speed * dt; -} - -void Camera3D::move_left(Transform3D& transform, float dt) { - transform.position -= right * speed * dt; -} - -void Camera3D::move_right(Transform3D& transform, float dt) { - transform.position += right * speed * dt; -} - -void Camera3D::look_at(float xoffset, float yoffset, float sensitivity) { - yaw += xoffset * sensitivity; - pitch += yoffset * sensitivity; - - pitch = std::clamp(pitch, -89.0f, 89.0f); - update_vectors(); -} - -void Camera3D::zoom(float yoffset) { - fov = std::clamp(fov - yoffset, 1.0f, 90.0f); -} - - -glm::mat4 DirectionalLight3D::get_light_space_matrix(glm::mat4 camera_view, glm::mat4 camera_proj) const { - glm::vec3 light_dir = glm::normalize(direction); - - glm::mat4 invCam = glm::inverse(camera_proj * camera_view); - - std::vector frustum_corners; - for (int x = 0; x < 2; ++x) { - for (int y = 0; y < 2; ++y) { - for (int z = 0; z < 2; ++z) { - glm::vec4 corner = invCam * glm::vec4(2.0f * x - 1.0f, 2.0f * y - 1.0f, 2.0f * z - 1.0f, 1.0f); - corner /= corner.w; - frustum_corners.push_back(glm::vec3(corner)); - } - } - } - - glm::vec3 scene_center(0.0f); - for (auto& c : frustum_corners) { - scene_center += c; - } - scene_center /= static_cast(frustum_corners.size()); - - glm::vec3 light_position = scene_center - light_dir * 500.0f; - - glm::mat4 lightView = glm::lookAt(light_position, scene_center, glm::vec3(0.0f, 0.0f, 1.0f)); - - glm::vec3 min_bounds(FLT_MAX); - glm::vec3 max_bounds(-FLT_MAX); - for (auto& corner : frustum_corners) { - auto ls = glm::vec3(lightView * glm::vec4(corner, 1.0f)); - min_bounds = glm::min(min_bounds, ls); - max_bounds = glm::max(max_bounds, ls); - } - - constexpr float extend = 200.f; - min_bounds.z -= extend; - max_bounds.z += extend; - - glm::mat4 orthoProj = glm::ortho(min_bounds.x, max_bounds.x, min_bounds.y, max_bounds.y, -max_bounds.z, -min_bounds.z); - - return orthoProj * lightView; -} - -glm::mat4 DirectionalLight3D::get_light_space_matrix(const glm::vec3& camera_position) const { - glm::vec3 light_dir = glm::normalize(direction); - - glm::vec3 light_pos = camera_position - light_dir * ((shadowFar - shadowNear) * 0.5f); - - glm::vec3 up; - if (glm::abs(light_dir.y) > 0.999f) { - - up = glm::vec3(0.0f, 0.0f, 1.0f); - } else { - - up = glm::vec3(0.0f, 1.0f, 0.0f); - } - - glm::mat4 lightView = glm::lookAt(light_pos, camera_position, up); - - glm::mat4 lightProj = glm::ortho( - -shadowOrthoSize, shadowOrthoSize, // left, right - -shadowOrthoSize, shadowOrthoSize, // bottom, top - shadowNear, shadowFar // near, far - ); - - return lightProj * lightView; -} - -glm::mat3 Transform2D::get_matrix() const { - glm::mat4 matrix(1.0f); - matrix = glm::translate(matrix, glm::vec3(-origin, 0.0f)); - - matrix = glm::scale(matrix, glm::vec3(scale, 1.0f)); - matrix = glm::rotate(matrix, rotation, glm::vec3(0.0f, 0.0f, 1.0f)); - matrix = glm::translate(matrix, glm::vec3(position + origin, 0.0f)); - - return matrix; -} - -glm::mat4 Camera2D::get_view_matrix() const { - glm::mat4 view(1.0f); - view = glm::translate(view, glm::vec3(-position, 0.0f)); - view = glm::rotate(view, rotation, glm::vec3(0.0f, 0.0f, 1.0f)); - view = glm::scale(view, glm::vec3(zoom, zoom, 1.0f)); - return view; -} - -glm::mat4 Camera2D::get_projection_matrix() const { - - if (viewport_size.x <= 0.0f || viewport_size.y <= 0.0f) { - return glm::mat4(1.0f); - } - - return glm::ortho(0.0f, viewport_size.x, viewport_size.y, 0.0f); -} - -void Camera2D::move(const glm::vec2& offset) { - position += offset; -} - -void Camera2D::rotate(float radians) { - rotation += radians; -} - -void Camera2D::zoom_by(float factor) { - zoom *= factor; -} - -void Camera2D::set_zoom(float new_zoom) { - zoom = new_zoom; -} - -void Camera2D::look_at(const glm::vec2& target) { - position = target; -} diff --git a/engine/private/core/component/game_object.cpp b/engine/private/core/component/game_object.cpp deleted file mode 100644 index 53badd610..000000000 --- a/engine/private/core/component/game_object.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "core/component/game_object.h" - - - -Uint32 GameObject::get_id() const { - return static_cast(_id.id()); -} - -bool GameObject::is_valid() const { - return _id.is_valid() && _id.is_alive(); -} - -const char* GameObject::get_name() const { - return _id.name().c_str(); -} - -void GameObject::set_name(const char* name) { - _id.set_name(name); -} - -bool GameObject::compare_tag(const char* tag) const { - return _id.has() && _id.name() == tag; -} - -void GameObject::free() const { - _id.destruct(); -} - -flecs::entity& GameObject::entity() { - return _id; -} - -const flecs::entity& GameObject::entity() const { - return _id; -} - -GameObject::operator flecs::entity() const { - return _id; -} diff --git a/engine/private/core/engine.cpp b/engine/private/core/engine.cpp deleted file mode 100644 index 073448d31..000000000 --- a/engine/private/core/engine.cpp +++ /dev/null @@ -1,570 +0,0 @@ -#include "core/engine.h" - -#include "core/component/comp_system.h" -#include "core/renderer/opengl/ogl_renderer.h" - -std::unique_ptr GEngine = std::make_unique(); - -nk_context* nk_ctx = nullptr; - -Renderer* create_renderer_internal(SDL_Window* window, EngineConfig& config) { - - - Renderer* renderer = nullptr; - switch (config.get_renderer_device().backend) { - case Backend::GL_COMPATIBILITY: - { - renderer = new OpenGLRenderer(); - break; - } - case Backend::VK_FORWARD: - spdlog::error("Vulkan backend is not yet supported"); - break; - case Backend::DIRECTX12: - spdlog::error("DirectX 12 backend is not yet supported"); - break; - case Backend::METAL: - spdlog::error("Metal backend is not yet supported"); - break; - case Backend::AUTO: - { - spdlog::error("SDL Renderer backend is not yet supported"); - break; - } - } - - const auto& win_size = config.get_window(); - - if (!renderer || !renderer->initialize(win_size.width, win_size.height, window)) { - spdlog::error("Renderer initialization failed, shutting down"); - - delete renderer; - - return nullptr; - } - - renderer->load_font("res/fonts/Default.ttf", 24, "default"); - renderer->load_font("res/fonts/Twemoji.ttf", 24, "emoji"); - - return renderer; -} - -void register_components(flecs::world& world) { - // 2D Components - world.component(); - world.component(); - world.component(); - world.component(); - world.component(); - world.component(); - world.component(); - - // 3D Components - world.component(); - world.component(); - world.component(); - world.component(); - world.component(); - world.component(); - - // Common Components - world.component(); - world.component(); - world.component(); - world.component(); - - world.component(); - world.component(); - world.component(); -} - -bool Engine::initialize(int window_w, int window_h, const char* title, Uint32 window_flags) { - - -#if defined(NDEBUG) - const auto LOG_LEVEL = spdlog::level::info; -#else - const auto LOG_LEVEL = spdlog::level::debug; -#endif - -#if defined(__ANDROID__) - auto android_sink = std::make_shared("GoliasEngine"); - std::vector sinks{android_sink}; -#else - auto console_sink = std::make_shared(); - auto file_sink = std::make_shared("logs/output.log", true); - std::vector sinks{console_sink, file_sink}; -#endif - - auto logger = std::make_shared("GoliasEngine", sinks.begin(), sinks.end()); - -#if defined(NDEBUG) - logger->set_level(spdlog::level::info); -#else - logger->set_level(spdlog::level::debug); -#endif - - logger->set_level(LOG_LEVEL); - logger->flush_on(LOG_LEVEL); - - spdlog::set_default_logger(logger); - - if (!_config.load()) { - spdlog::warn("Using default configuration values"); - } - - const auto& app_config = _config.get_application(); - - - if (app_config.is_fullscreen) { - window_flags |= SDL_WINDOW_FULLSCREEN; - } - - if (app_config.is_resizable) { - window_flags |= SDL_WINDOW_RESIZABLE; - } - - const auto& renderer_config = _config.get_renderer_device(); - - if (renderer_config.backend == Backend::GL_COMPATIBILITY) { - window_flags |= SDL_WINDOW_OPENGL; - - - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); - SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); - - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); - } - - auto& app_win = _config.get_window(); - - spdlog::info("Initializing {}, Version {}", ENGINE_NAME, ENGINE_VERSION_STR); - - spdlog::info("Project Configuration -> Window: ({}x{}), ApplicationName: {}, Version: {}, Package: {}", app_win.width, app_win.height, - app_config.name, app_config.version, app_config.package_name); - -#pragma region APP_METADATA - SDL_SetHint(SDL_HINT_ORIENTATIONS, _config.get_orientation_str()); - SDL_SetAppMetadata(app_config.name, app_config.version, app_config.package_name); -#pragma endregion - - - if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_GAMEPAD | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO)) { - spdlog::error("Engine initialization failed: {}", SDL_GetError()); - return false; - } - - - if (!TTF_Init()) { - spdlog::error("TTF_Init failed: {}", SDL_GetError()); - return false; - } - - - SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "1"); - - - app_win.width = window_w; - app_win.height = window_h; - - int driver_count = SDL_GetNumRenderDrivers(); - - if (driver_count < 1) { - spdlog::critical("No render drivers available"); - return false; - } - - std::string renderer_list; - renderer_list.reserve(driver_count * 16); - for (int i = 0; i < driver_count; ++i) { - const char* name = SDL_GetRenderDriver(i); - renderer_list += name; - renderer_list += (i < driver_count - 1) ? ", " : ""; - } - - spdlog::info("Available Backends Count {}, Options {}", driver_count, renderer_list.c_str()); - - - _window = SDL_CreateWindow(app_config.name, window_w, window_h, window_flags); - - if (!_window) { - spdlog::error("Window creation failed: {}", SDL_GetError()); - SDL_Quit(); - return false; - } - -#pragma region ENGINE_WINDOW_ICON - - int w, h, channels; - SDL_Surface* logo_surface = nullptr; - stbi_uc* logo_pixels = stbi_load((ASSETS_PATH + "icon.png").c_str(), &w, &h, &channels, 4); - - if (logo_pixels) { - logo_surface = SDL_CreateSurfaceFrom(w, h, SDL_PIXELFORMAT_RGBA32, logo_pixels, w * 4); - SDL_SetWindowIcon(_window, logo_surface); - SDL_DestroySurface(logo_surface); - stbi_image_free(logo_pixels); - } else { - spdlog::error("Failed to Load default `icon` Image"); - } - -#pragma endregion - - - spdlog::info("Backend selected: {}", _config.get_renderer_device().get_backend_str()); - - - // TODO: later we can add support for other renderers (Vulkan, OpenGL, etc.) - _renderer = create_renderer_internal(_window, _config); - - if (!_renderer) { - spdlog::error("Renderer creation failed, shutting down"); - SDL_DestroyWindow(_window); - SDL_Quit(); - return false; - } - - nk_ctx = nk_sdl_init(_window); - - _timer.start(); - - nk_font_atlas* atlas; - // struct nk_font* font; - - - nk_sdl_font_stash_begin(&atlas); - // font = nk_font_atlas_add_default(atlas,14,0); - nk_sdl_font_stash_end(); - - register_components(_world); - - SDL_ShowWindow(_window); // now shown after renderer setup - - is_running = true; - - return true; -} - -EngineConfig& Engine::get_config() { - return _config; -} - -Timer& Engine::get_timer() { - return _timer; -} - - -Renderer* Engine::get_renderer() const { - return _renderer; -} - -SDL_Window* Engine::get_window() const { - return _window; -} - -flecs::world& Engine::get_world() { - return _world; -} - - -void engine_draw_loop() { -#pragma region 3D_RENDERING_LOOP - render_world_3d_system(); -#pragma endregion - -#pragma region 2D_RENDERING_LOOP -render_world_2d_system(); -#pragma endregion - -#pragma region UI_RENDERING_LOOP - nk_sdl_render(NK_ANTI_ALIASING_ON, MAX_VERTEX_MEMORY, MAX_ELEMENT_MEMORY); -#pragma endregion - GEngine->get_renderer()->swap_chain(); -} - - -nk_bool show_render_targets = false; - - - -void draw_debug_ui() { - - if (nk_begin(nk_ctx, "Debug", nk_rect(10, 10, 400, 600), NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_TITLE)) { - nk_layout_row_dynamic(nk_ctx, 25, 1); - nk_label(nk_ctx, "System Monitor", NK_TEXT_CENTERED); - - if (nk_tree_push(nk_ctx, NK_TREE_NODE, "GPU", NK_MINIMIZED)) { - nk_layout_row_dynamic(nk_ctx, 20, 2); - - // TODO: Add your GPU data variables here - static float gpu_usage = 0.0f; - static float gpu_temp = 0.0f; - static float gpu_clock = 0.0f; - static float gpu_mem_used = 0.0f; - static float gpu_mem_total = 0.0f; - - nk_labelf(nk_ctx, NK_TEXT_LEFT, "Device: %s", "GPU_Name_Placeholder"); - - nk_layout_row_dynamic(nk_ctx, 2, 1); - nk_spacing(nk_ctx, 1); - - // GPU Memory - nk_layout_row_dynamic(nk_ctx, 18, 1); - nk_label(nk_ctx, "VRAM Usage:", NK_TEXT_LEFT); - - nk_layout_row_dynamic(nk_ctx, 20, 2); - nk_labelf(nk_ctx, NK_TEXT_LEFT, "%.2f MB / %.2f MB", gpu_mem_used, gpu_mem_total); - - nk_labelf(nk_ctx, NK_TEXT_LEFT, "%.1f%%", (gpu_mem_total > 0.0f) ? (gpu_mem_used / gpu_mem_total * 100.0f) : 0.0f); - - // Progress bar for VRAM - nk_layout_row_dynamic(nk_ctx, 20, 1); - nk_size vram_ratio = (gpu_mem_total > 0.0f) ? (gpu_mem_used / gpu_mem_total) : 0.0f; - nk_progress(nk_ctx, &vram_ratio, 100, NK_FIXED); - - - nk_tree_pop(nk_ctx); - } - - - // System Memory - if (nk_tree_push(nk_ctx, NK_TREE_NODE, "System Memory", NK_MAXIMIZED)) { - nk_layout_row_dynamic(nk_ctx, 20, 2); - - // TODO: Add your memory data variables here - static float ram_used = 0.0f; - static float ram_total = 0.0f; - static float ram_available = 0.0f; - - - nk_label(nk_ctx, "Used:", NK_TEXT_LEFT); - nk_labelf(nk_ctx, NK_TEXT_LEFT, "%.2f MB", ram_used); - - nk_label(nk_ctx, "Available:", NK_TEXT_LEFT); - nk_labelf(nk_ctx, NK_TEXT_LEFT, "%.2f MB", ram_available); - - // Progress bar for RAM - nk_layout_row_dynamic(nk_ctx, 20, 1); - nk_size ram_ratio = (ram_total > 0) ? (ram_used / ram_total) : 0; - nk_progress(nk_ctx, &ram_ratio, 100, NK_FIXED); - - nk_layout_row_dynamic(nk_ctx, 20, 2); - nk_labelf(nk_ctx, NK_TEXT_LEFT, "Usage: %.1f%%", (ram_total > 0.0f) ? (ram_used / ram_total * 100.0f) : 0.0f); - nk_labelf(nk_ctx, NK_TEXT_LEFT, "Total RAM: %.2f MB", ram_total); - - nk_tree_pop(nk_ctx); - } - - // Render Stats - if (nk_tree_push(nk_ctx, NK_TREE_NODE, "Render Stats", NK_MINIMIZED)) { - nk_layout_row_dynamic(nk_ctx, 20, 2); - - static int draw_calls = 0; - static int indices = 0; - static int vertices = 0; - nk_labelf(nk_ctx, NK_TEXT_LEFT, "Draw Calls %d", draw_calls); - nk_labelf(nk_ctx, NK_TEXT_LEFT, "Vertices: %d", vertices); - nk_labelf(nk_ctx, NK_TEXT_LEFT, "Indices: %d", indices); - nk_checkbox_label(nk_ctx, "Show Render Targets", &show_render_targets); - - nk_tree_pop(nk_ctx); - } - - - // Profiler Data - if (nk_tree_push(nk_ctx, NK_TREE_NODE, "Profiler", NK_MINIMIZED)) { - nk_layout_row_dynamic(nk_ctx, 18, 1); - nk_label(nk_ctx, "Frame Breakdown:", NK_TEXT_LEFT); - - // TODO: Add your profiler data here - struct ProfilingData { - const char* name; - float time_ms; - struct nk_color color; - }; - - static ProfilingData profile_data[] = {{"Update", 0.0f, nk_rgb(75, 150, 255)}, - {"Render", 0.0f, nk_rgb(255, 150, 75)}, - {"Physics", 0.0f, nk_rgb(150, 255, 75)}, - {"Animations", 0.0f, nk_rgb(255, 75, 150)}}; - - nk_layout_row_dynamic(nk_ctx, 2, 1); - nk_spacing(nk_ctx, 1); - - for (const auto& data : profile_data) { - nk_layout_row_dynamic(nk_ctx, 18, 2); - - nk_command_buffer* canvas = nk_window_get_canvas(nk_ctx); - struct nk_rect bounds; - if (nk_widget(&bounds, nk_ctx) != NK_WIDGET_INVALID) { - nk_fill_rect(canvas, nk_rect(bounds.x, bounds.y + 4, 10, 10), 2.0f, data.color); - } - - nk_labelf(nk_ctx, NK_TEXT_RIGHT, "%.2f ms", data.time_ms); - } - - nk_tree_pop(nk_ctx); - } - } - nk_end(nk_ctx); - - - if (show_render_targets) { - if (nk_begin(nk_ctx, "Render Targets", nk_rect(830, 10, 400, 600), - NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_SCALABLE | NK_WINDOW_TITLE)) { - nk_layout_row_dynamic(nk_ctx, 25, 1); - nk_label(nk_ctx, "Frame Buffer Outputs", NK_TEXT_CENTERED); - - const auto renderer = GEngine->get_renderer(); - - auto depth_tex = renderer->get_shadow_map_fbo()->get_depth_attachment_id(); - if (depth_tex != 0) { - nk_layout_row_dynamic(nk_ctx, 20, 1); - nk_label(nk_ctx, "Depth Buffer:", NK_TEXT_LEFT); - - nk_layout_row_dynamic(nk_ctx, 256, 1); - struct nk_image depth_img = nk_image_id((int) depth_tex); - nk_image(nk_ctx, depth_img); - } - - nk_end(nk_ctx); - } - } -} - - -void engine_core_loop() { - static bool mouse_captured = false; - static float mouse_dx = 0.0f; - static float mouse_dy = 0.0f; - - GEngine->get_timer().tick(); - - mouse_dx = 0.0f; - mouse_dy = 0.0f; - - nk_input_begin(nk_ctx); - while (SDL_PollEvent(&GEngine->event)) { - auto& ev = GEngine->event; - nk_sdl_handle_event(&ev); - - switch (ev.type) { - case SDL_EVENT_QUIT: - GEngine->is_running = false; - break; - - case SDL_EVENT_KEY_DOWN: - if (ev.key.scancode == SDL_SCANCODE_ESCAPE) { - mouse_captured = !mouse_captured; - SDL_SetWindowRelativeMouseMode(GEngine->get_window(), mouse_captured); - SDL_ShowCursor(); - } - if (ev.key.scancode == SDL_SCANCODE_F1) { - GEngine->get_config().is_debug = !GEngine->get_config().is_debug; - } - break; - - case SDL_EVENT_MOUSE_BUTTON_DOWN: - if (ev.button.button == SDL_BUTTON_RIGHT && !mouse_captured) { - mouse_captured = true; - SDL_SetWindowRelativeMouseMode(GEngine->get_window(), true); - SDL_HideCursor(); - } - break; - - case SDL_EVENT_MOUSE_MOTION: - if (mouse_captured) { - mouse_dx += static_cast(ev.motion.xrel); - mouse_dy += static_cast(ev.motion.yrel); - } - break; - - case SDL_EVENT_WINDOW_RESIZED: - { - - spdlog::debug("Window resized to {}x{}", ev.window.data1, ev.window.data2); - GEngine->get_config().get_window().resize(ev.window.data1, ev.window.data2); - - - break; - } - - default: - break; - } - } - nk_sdl_handle_grab(); - nk_input_end(nk_ctx); - - - const bool* scancodes = SDL_GetKeyboardState(nullptr); - - - GEngine->get_world().each([&](flecs::entity e, Transform3D& transform, Camera3D& camera) { - float dt = static_cast(GEngine->get_timer().delta); - - if (scancodes[SDL_SCANCODE_W]) { - camera.move_forward(transform, dt); - } - if (scancodes[SDL_SCANCODE_S]) { - camera.move_backward(transform, dt); - } - if (scancodes[SDL_SCANCODE_A]) { - camera.move_left(transform, dt); - } - if (scancodes[SDL_SCANCODE_D]) { - camera.move_right(transform, dt); - } - if (scancodes[SDL_SCANCODE_SPACE]) { - transform.position.y += camera.speed * dt; - } - if (scancodes[SDL_SCANCODE_LCTRL]) { - transform.position.y -= camera.speed * dt; - } - - camera.speed = scancodes[SDL_SCANCODE_LSHIFT] ? 150.0f : 50.0f; - - if (mouse_captured && (mouse_dx != 0.0f || mouse_dy != 0.0f)) { - constexpr float sensitivity = 0.1f; - camera.look_at(mouse_dx, -mouse_dy, sensitivity); - } - }); - - - // draw_editor_ui(); - // draw_entity_hierarchy(); - draw_debug_ui(); - - GEngine->get_world().progress(static_cast(GEngine->get_timer().delta)); - engine_draw_loop(); - - SDL_Delay(16); // Simple frame cap to ~60 FPS -} - - -void Engine::run() const { - -#if defined(SDL_PLATFORM_EMSCRIPTEN) - emscripten_set_main_loop(engine_core_loop, 60, 1); -#else - while (is_running) { - engine_core_loop(); - } - -#endif -} - - -Engine::~Engine() { - - delete _renderer; - - SDL_DestroyWindow(_window); - - nk_sdl_shutdown(); - - TTF_Quit(); - SDL_Quit(); -} diff --git a/engine/private/core/gstl/dictionary.cpp b/engine/private/core/gstl/dictionary.cpp new file mode 100644 index 000000000..1743d2c8e --- /dev/null +++ b/engine/private/core/gstl/dictionary.cpp @@ -0,0 +1,65 @@ +#include "core/gstl/dictionary.h" + + +size_t Dictionary::size() const { + return data.size(); +} + +bool Dictionary::is_empty() const { + return data.empty(); +} + +void Dictionary::clear() { + data.clear(); +} + +bool Dictionary::has(const String& key) const { + return data.find(key) != data.end(); +} + +bool Dictionary::erase(const String& key) { + return data.erase(key) > 0; +} + +Vector Dictionary::keys() const { + Vector result; + result.reserve(data.size()); + for (const auto& pair : data) { + result.push_back(pair.first); + } + return result; +} + +std::type_index Dictionary::get_type(const String& key) const { + auto it = data.find(key); + if (it == data.end()) { + return typeid(void); + } + return it->second.type(); +} + +void Dictionary::merge(const Dictionary& other, bool overwrite) { + for (const auto& pair : other.data) { + if (overwrite || !has(pair.first)) { + data[pair.first] = pair.second; + } + } +} + + +auto Dictionary::begin() { + return data.begin(); +} + +auto Dictionary::end() { + return data.end(); +} + +auto Dictionary::begin() const { + return data.begin(); +} + +auto Dictionary::end() const { + return data.end(); +} + diff --git a/engine/private/core/gstl/str.cpp b/engine/private/core/gstl/str.cpp new file mode 100644 index 000000000..ee94c8b7a --- /dev/null +++ b/engine/private/core/gstl/str.cpp @@ -0,0 +1,158 @@ +#include "../../../public/core/gstl/str.h" +#include + +String String::to_lower() const { + String result = *this; + std::transform(result.begin(), result.end(), result.begin(), ::tolower); + return result; +} + +String String::to_upper() const { + String result = *this; + std::transform(result.begin(), result.end(), result.begin(), ::toupper); + return result; +} + +String String::substr(size_t from, size_t len) const { + return String(std::string::substr(from, len)); +} + +int String::find(const String& what, int from) const { + size_t pos = std::string::find(what, from); + return (pos == std::string::npos) ? -1 : static_cast(pos); +} + +int String::find(char what, int from) const { + size_t pos = std::string::find(what, from); + return (pos == std::string::npos) ? -1 : static_cast(pos); +} + +int String::rfind(const String& what) const { + size_t pos = std::string::rfind(what); + return (pos == std::string::npos) ? -1 : static_cast(pos); +} + +int String::findn(const String& what, int from) const { + String lower_this = to_lower(); + String lower_what = what.to_lower(); + size_t pos = lower_this.std::string::find(lower_what, from); + return (pos == std::string::npos) ? -1 : static_cast(pos); +} + +bool String::begins_with(const String& text) const { + return std::string::find(text) == 0; +} + +bool String::ends_with(const String& text) const { + if (text.size() > size()) { + return false; + } + return compare(size() - text.size(), text.size(), text) == 0; +} + +bool String::contains(const String& what) const { + return std::string::find(what) != std::string::npos; +} + +String String::replace(const String& what, const String& forwhat) const { + String result = *this; + size_t pos = 0; + while ((pos = result.std::string::find(what, pos)) != std::string::npos) { + result.std::string::replace(pos, what.length(), forwhat); + pos += forwhat.length(); + } + return result; +} + +String String::replacen(const String& what, const String& forwhat) const { + String result = *this; + String lower_result = result.to_lower(); + String lower_what = what.to_lower(); + size_t pos = 0; + + while ((pos = lower_result.std::string::find(lower_what, pos)) != std::string::npos) { + result.std::string::replace(pos, what.length(), forwhat); + lower_result = result.to_lower(); + pos += forwhat.length(); + } + + return result; +} + +String String::strip_edges() const { + size_t start = find_first_not_of(" \t\n\r"); + if (start == std::string::npos) { + return String(""); + } + size_t end = find_last_not_of(" \t\n\r"); + return String(std::string::substr(start, end - start + 1)); +} + +String String::lstrip(const String& chars) const { + size_t start = find_first_not_of(chars); + + if (start == std::string::npos) { + return String(""); + } + + return String(std::string::substr(start)); +} + +String String::rstrip(const String& chars) const { + size_t end = find_last_not_of(chars); + if (end == std::string::npos) { + return String(""); + } + return String(std::string::substr(0, end + 1)); +} + +Vector String::split(const char delimiter) const { + Vector result; + size_t start = 0; + size_t end = this->find(delimiter); + + while (end != std::string::npos) { + result.push_back(this->substr(start, end - start)); + start = end + 1; + end = this->find(delimiter, start); + } + result.push_back(this->substr(start)); + return result; +} + +String String::join(const Vector& parts) const { + if (parts.is_empty()) return String(""); + + String result = parts[0]; + for (size_t i = 1; i < parts.size(); ++i) { + result += *this; + result += parts[i]; + } + + return result; +} + +String String::insert(size_t position, const String& what) const { + String result = *this; + result.std::string::insert(position, what); + return result; +} + +String String::reverse() const { + String result = *this; + std::reverse(result.begin(), result.end()); + return result; +} + + +size_t String::hash() const { + return std::hash{}(*this); +} + +String String::operator+(const String& other) const { + return String(static_cast(*this) + static_cast(other)); +} + +String String::operator+(const char* other) const { + return String(static_cast(*this) + other); +} diff --git a/engine/private/core/io/assimp_io.cpp b/engine/private/core/io/assimp_io.cpp deleted file mode 100644 index 796240786..000000000 --- a/engine/private/core/io/assimp_io.cpp +++ /dev/null @@ -1,122 +0,0 @@ -#include "core/io/assimp_io.h" -#include "core/io/file_system.h" - - -SDLIOStream::SDLIOStream(const std::string& path, const std::string& mode) - : m_path(path) { - - ModeFlags flags = ModeFlags::READ; - if (mode.find('w') != std::string::npos) { - flags = ModeFlags::WRITE; - } - - m_file = std::make_unique(path, flags); -} - -SDLIOStream::~SDLIOStream() { - // FileAccess destructor handles cleanup -} - -size_t SDLIOStream::Read(void* pvBuffer, size_t pSize, size_t pCount) { - if (!m_file || !m_file->is_open()) { - return 0; - } - - const auto& bytes = m_file->get_file_as_bytes(); - size_t bytesToRead = pSize * pCount; - size_t available = bytes.size() - m_position; - size_t toRead = std::min(bytesToRead, available); - - if (toRead > 0) { - SDL_memcpy(pvBuffer, bytes.data() + m_position, toRead); - m_position += toRead; - } - - return toRead / pSize; -} - -size_t SDLIOStream::Write(const void* pvBuffer, size_t pSize, size_t pCount) { - // Write not implemented for assets - return 0; -} - -aiReturn SDLIOStream::Seek(size_t pOffset, aiOrigin pOrigin) { - if (!m_file || !m_file->is_open()) { - return aiReturn_FAILURE; - } - - size_t fileSize = m_file->get_file_as_bytes().size(); - - switch (pOrigin) { - case aiOrigin_SET: - m_position = pOffset; - break; - case aiOrigin_CUR: - m_position += pOffset; - break; - case aiOrigin_END: - m_position = fileSize + pOffset; - break; - default: - return aiReturn_FAILURE; - } - - return aiReturn_SUCCESS; -} - -size_t SDLIOStream::Tell() const { - return m_position; -} - -size_t SDLIOStream::FileSize() const { - if (!m_file || !m_file->is_open()) { - return 0; - } - return m_file->get_file_as_bytes().size(); -} - -void SDLIOStream::Flush() { - // Nothing to flush for read-only files -} - -SDLIOSystem::SDLIOSystem(const std::string& base_path) - : m_base_path(base_path) { - // Ensure base path ends with separator - if (!m_base_path.empty() && m_base_path.back() != '/' && m_base_path.back() != '\\') { - m_base_path += '/'; - } -} - -SDLIOSystem::~SDLIOSystem() { - // Cleanup handled by unique_ptrs -} - -bool SDLIOSystem::Exists(const char* pFile) const { - std::string full_path = m_base_path + pFile; - FileAccess file(full_path, ModeFlags::READ); - return file.is_open(); -} - -char SDLIOSystem::getOsSeparator() const { -#ifdef _WIN32 - return '\\'; -#else - return '/'; -#endif -} - -Assimp::IOStream* SDLIOSystem::Open(const char* pFile, const char* pMode) { - std::string full_path = m_base_path + pFile; - - auto stream = new SDLIOStream(full_path, pMode); - if (!stream->m_file || !stream->m_file->is_open()) { - delete stream; - return nullptr; - } - - return stream; -} - -void SDLIOSystem::Close(Assimp::IOStream* pFile) { - delete pFile; -} \ No newline at end of file diff --git a/engine/private/core/io/file_system.cpp b/engine/private/core/io/file_system.cpp deleted file mode 100644 index baedadb31..000000000 --- a/engine/private/core/io/file_system.cpp +++ /dev/null @@ -1,221 +0,0 @@ -#include "core/io/file_system.h" - - - -std::string load_assets_file(const std::string& file_path) { - - spdlog::debug("Loading asset file: {}", file_path); - const auto buffer = load_file_into_memory(file_path); - - return std::string(buffer.begin(), buffer.end()); -} - - -std::vector load_file_into_memory(const std::string& file_path) { - - auto path = ASSETS_PATH + file_path; - - SDL_IOStream* file_rw = SDL_IOFromFile(path.c_str(), "rb"); - if (!file_rw) { - spdlog::error("Failed to open file {} , Error: {}", path.c_str(), SDL_GetError()); - return {}; - } - - Sint64 size = SDL_GetIOSize(file_rw); - if (size <= 0) { - spdlog::error("Failed to get file size {}, Error: {}", path.c_str(), SDL_GetError()); - SDL_CloseIO(file_rw); - return {}; - } - - std::vector buffer(size); - if (SDL_ReadIO(file_rw, buffer.data(), size) != size) { - spdlog::error("Failed to read file {}", path.c_str()); - SDL_CloseIO(file_rw); - return {}; - } - - SDL_CloseIO(file_rw); - - return buffer; -} - - -const char* get_mode_str(ModeFlags mode_flags) { - switch (mode_flags) { - case ModeFlags::READ: - return "rb"; - case ModeFlags::WRITE: - return "wb"; - case ModeFlags::READ_WRITE: - return "r+b"; - case ModeFlags::WRITE_READ: - return "w+b"; - default: - return "rb"; - } -} - -bool FileAccess::resolve_path(const std::string& file_path, ModeFlags mode_flags) { - - if (file_path.rfind("res://", 0) == 0) { - _file_path = ASSETS_PATH + file_path.substr(6); - } - else if (file_path.rfind("user://", 0) == 0) { - char* prefPath = SDL_GetPrefPath(ENGINE_DEFAULT_FOLDER_NAME, ENGINE_PACKAGE_NAME); - if (!prefPath) { - spdlog::error("Failed to get pref path: {}", SDL_GetError()); - return false; - } - - _file_path = std::string(prefPath) + file_path.substr(7); - SDL_free(prefPath); - - if (mode_flags == ModeFlags::WRITE || mode_flags == ModeFlags::READ_WRITE || mode_flags == ModeFlags::WRITE_READ) { - std::size_t slashPos = _file_path.find_last_of("/\\"); - if (slashPos != std::string::npos) { - const std::string dir = _file_path.substr(0, slashPos); - if (!SDL_CreateDirectory(dir.c_str())) { - spdlog::error("Failed to create directory {}, Error: {}", dir.c_str(),SDL_GetError()); - return false; - } - } - } - } - else { - _file_path = file_path; - } - - return true; -} - -bool FileAccess::open(const std::string& file_path, ModeFlags mode_flags) { - close(); - - if (!resolve_path(file_path, mode_flags)) { - return false; - } - - _file = SDL_IOFromFile(_file_path.c_str(), get_mode_str(mode_flags)); - if (!_file) { - spdlog::error("Failed to open file {}", _file_path.c_str()); - return false; - } - - return true; -} - -FileAccess::FileAccess(const std::string& file_path, ModeFlags mode_flags) { - open(file_path, mode_flags); -} - -FileAccess::~FileAccess() { - close(); -} - -bool FileAccess::is_open() const { - return _file != nullptr; -} - -std::vector FileAccess::get_file_as_bytes() { - std::vector buffer; - if (!_file) return buffer; - - Sint64 size = SDL_GetIOSize(_file); - if (size <= 0) return buffer; - - buffer.resize(size); - Sint64 read = SDL_ReadIO(_file, buffer.data(), size); - if (read != size) { - spdlog::error("Failed to read file %s", _file_path.c_str()); - } - - SDL_SeekIO(_file, 0, SDL_IO_SEEK_SET); // rewind - return buffer; -} - -std::string FileAccess::get_file_as_str() { - auto bytes = get_file_as_bytes(); - return {bytes.begin(), bytes.end()}; -} - -bool FileAccess::file_exists(const std::string& file_path) { - std::string path; - - if (file_path.rfind("res://", 0) == 0) { - path = ASSETS_PATH + file_path.substr(6); - } else if (file_path.rfind("user://", 0) == 0) { - char* prefPath = SDL_GetPrefPath("Golias", "com.golias.engine.app"); - if (!prefPath) return false; - path = std::string(prefPath) + file_path.substr(7); - SDL_free(prefPath); - } else { - path = ASSETS_PATH + file_path; - } - - SDL_IOStream* test = SDL_IOFromFile(path.c_str(), "rb"); - if (test) { - SDL_CloseIO(test); - return true; - } - return false; -} - -void FileAccess::seek(int length) { - if (_file) SDL_SeekIO(_file, length, SDL_IO_SEEK_SET); -} - -void FileAccess::seek_end(int position) { - if (_file) SDL_SeekIO(_file, position, SDL_IO_SEEK_END); -} - -std::string FileAccess::get_absolute_path() const { - return _file_path; -} - -std::string FileAccess::get_path() const { - std::string path = _file_path; - std::size_t pos = path.find("//"); - if (pos != std::string::npos && pos + 2 < path.size()) { - path = path.substr(pos + 2); - } - return path; -} - -bool FileAccess::store_string(const std::string& content) { - if (!_file) { - spdlog::error("File not open for writing: %s", _file_path.c_str()); - return false; - } - - Sint64 written = SDL_WriteIO(_file, content.data(), content.size()); - if (written != static_cast(content.size())) { - spdlog::error("Failed to write string to file %s", _file_path.c_str()); - return false; - } - - return true; -} - -bool FileAccess::store_bytes(const std::vector& content) { - if (!_file) { - spdlog::error("File not open for writing: %s", _file_path.c_str()); - return false; - } - if (content.empty()) return true; - - Sint64 written = SDL_WriteIO(_file, content.data(), content.size()); - if (written != static_cast(content.size())) { - spdlog::error("Failed to write bytes to file %s", _file_path.c_str()); - return false; - } - - return true; -} - -void FileAccess::close() { - if (_file) { - SDL_CloseIO(_file); - _file = nullptr; - } -} \ No newline at end of file diff --git a/engine/private/core/io/ma_io.cpp b/engine/private/core/io/ma_io.cpp deleted file mode 100644 index 525c00b70..000000000 --- a/engine/private/core/io/ma_io.cpp +++ /dev/null @@ -1,94 +0,0 @@ -#include "core/io/ma_io.h" - - -static ma_result sdl_vfs_onOpen(ma_vfs* pVFS, const char* pPath, ma_uint32 openMode, ma_vfs_file* pFile) { - if (!pVFS || !pPath || !pFile) { - return MA_INVALID_ARGS; - } - - if (openMode != MA_OPEN_MODE_READ) { - return MA_INVALID_ARGS; - } - - SDL_IOStream* rw = SDL_IOFromFile(pPath, "rb"); - if (!rw) { - return MA_DOES_NOT_EXIST; - } - - Ember_File* file = (Ember_File*) SDL_malloc(sizeof(Ember_File)); - if (!file) { - SDL_CloseIO(rw); - return MA_OUT_OF_MEMORY; - } - - file->stream = rw; - *pFile = file; - return MA_SUCCESS; -} - -static ma_result sdl_vfs_onRead(ma_vfs* pVFS, ma_vfs_file file, void* pBuffer, size_t size, size_t* pBytesRead) { - if (!file || !pBuffer || !pBytesRead) { - return MA_INVALID_ARGS; - } - - Ember_File* sdlFile = (Ember_File*) file; - size_t bytesRead = SDL_ReadIO(sdlFile->stream, pBuffer, size); - *pBytesRead = bytesRead; - - return (bytesRead > 0) ? MA_SUCCESS : MA_AT_END; -} - -static ma_result sdl_vfs_onSeek(ma_vfs* pVFS, ma_vfs_file file, ma_int64 offset, ma_seek_origin origin) { - if (!file) { - return MA_INVALID_ARGS; - } - - Ember_File* sdlFile = (Ember_File*) file; - SDL_IOWhence whence = (origin == ma_seek_origin_start) ? SDL_IO_SEEK_SET - : (origin == ma_seek_origin_current) ? SDL_IO_SEEK_CUR - : SDL_IO_SEEK_END; - - return (SDL_SeekIO(sdlFile->stream, offset, whence) < 0) ? MA_ERROR : MA_SUCCESS; -} - -static ma_result sdl_vfs_onTell(ma_vfs* pVFS, ma_vfs_file file, ma_int64* pCursor) { - if (!file || !pCursor) { - return MA_INVALID_ARGS; - } - - Ember_File* sdlFile = (Ember_File*) file; - ma_int64 pos = SDL_TellIO(sdlFile->stream); - if (pos < 0) { - return MA_ERROR; - } - - *pCursor = pos; - return MA_SUCCESS; -} - -static ma_result sdl_vfs_onClose(ma_vfs* pVFS, ma_vfs_file file) { - if (!file) { - return MA_INVALID_ARGS; - } - - Ember_File* sdlFile = (Ember_File*) file; - SDL_CloseIO(sdlFile->stream); - SDL_free(sdlFile); - - return MA_SUCCESS; -} - -ma_result ember_init_ma_vfs(MiniAudio_VFS* vfs) { - if (!vfs) { - return MA_INVALID_ARGS; - } - - vfs->base.onOpen = sdl_vfs_onOpen; - vfs->base.onRead = sdl_vfs_onRead; - vfs->base.onSeek = sdl_vfs_onSeek; - vfs->base.onTell = sdl_vfs_onTell; - vfs->base.onClose = sdl_vfs_onClose; - vfs->base.onWrite = NULL; - - return MA_SUCCESS; -} \ No newline at end of file diff --git a/engine/private/core/math/basis.cpp b/engine/private/core/math/basis.cpp new file mode 100644 index 000000000..84dcb9565 --- /dev/null +++ b/engine/private/core/math/basis.cpp @@ -0,0 +1,57 @@ +#include "core/math/basis.h" +#include "core/math/vector3.h" +#include "core/math/quaternion.h" + +// ============================================================================ +// Basis Implementation +// ============================================================================ + +Basis::Basis() : glm::mat3(1.0f) {} +Basis::Basis(float diagonal) : glm::mat3(diagonal) {} +Basis::Basis(const glm::mat3& m) : glm::mat3(m) {} + +glm::vec3& Basis::operator[](int index) { return glm::mat3::operator[](index); } +const glm::vec3& Basis::operator[](int index) const { return glm::mat3::operator[](index); } + +Basis Basis::transposed() const { + return glm::transpose(*this); +} + +void Basis::transpose() { + *this = glm::transpose(*this); +} + +Basis Basis::inverse() const { + return glm::inverse(*this); +} + +void Basis::invert() { + *this = glm::inverse(*this); +} + +float Basis::determinant() const { + return glm::determinant(*this); +} + +Vector3 Basis::xform(const Vector3& v) const { + return (*this) * v; +} + +Vector3 Basis::xform_inv(const Vector3& v) const { + return glm::inverse(*this) * v; +} + +Quaternion Basis::to_quaternion() const { + return glm::quat_cast(*this); +} + +Basis Basis::from_quaternion(const Quaternion& q) { + return glm::toMat3(q); +} + +Basis Basis::from_euler(const Vector3& euler) { + return glm::toMat3(Quaternion(euler)); +} + +const Basis Basis::IDENTITY = Basis(1.0f); + diff --git a/engine/private/core/math/math.cpp b/engine/private/core/math/math.cpp new file mode 100644 index 000000000..bc4a65fe5 --- /dev/null +++ b/engine/private/core/math/math.cpp @@ -0,0 +1,142 @@ +#include "core/math/math.h" +#include +#include +#include + +namespace math { + + + float sin(float x) { + return glm::sin(x); + } + + float cos(float x) { + return glm::cos(x); + } + + float tan(float x) { + return glm::tan(x); + } + + float asin(float x) { + return glm::asin(x); + } + + float acos(float x) { + return glm::acos(x); + } + + float atan(float x) { + return glm::atan(x); + } + + float atan2(float y, float x) { + return glm::atan(y, x); + } + + float sinh(float x) { + return glm::sinh(x); + } + + float cosh(float x) { + return glm::cosh(x); + } + + float tanh(float x) { + return glm::tanh(x); + } + + float exp(float x) { + return glm::exp(x); + } + + float log(float x) { + return glm::log(x); + } + + float log2(float x) { + return glm::log2(x); + } + + float log10(float x) { + return std::log10(x); + } + + float pow(float base, float exp) { + return glm::pow(base, exp); + } + + float sqrt(float x) { + return glm::sqrt(x); + } + + float cbrt(float x) { + return std::cbrt(x); + } + + + float floor(float x) { + return glm::floor(x); + } + + float ceil(float x) { + return glm::ceil(x); + } + + float round(float x) { + return glm::round(x); + } + + float trunc(float x) { + return glm::trunc(x); + } + + + + float abs(float x) { + return glm::abs(x); + } + + int abs(int x) { + return glm::abs(x); + } + + + float deg2rad(float degrees) { + return glm::radians(degrees); + } + + float rad2deg(float radians) { + return glm::degrees(radians); + } + + + + bool is_zero_approx(float x) { + return abs(x) < EPSILON; + } + + bool is_equal_approx(float a, float b) { + return abs(a - b) < EPSILON; + } + + + + float random(float min_val, float max_val) { + return min_val + static_cast(rand()) / (static_cast(RAND_MAX / (max_val - min_val))); + } + + int random(int min_val, int max_val) { + return min_val + (rand() % (max_val - min_val + 1)); + } + + + float snap(float value, float step) { + if (step != 0.0f) { + return floor(value / step + 0.5f) * step; + } + return value; + } + +} // namespace math + diff --git a/engine/private/core/math/matrix4.cpp b/engine/private/core/math/matrix4.cpp new file mode 100644 index 000000000..fd0b12af6 --- /dev/null +++ b/engine/private/core/math/matrix4.cpp @@ -0,0 +1,93 @@ +#include "core/math/matrix4.h" +#include "core/math/vector3.h" +#include "core/math/quaternion.h" +#include "core/math/basis.h" + +// ============================================================================ +// Matrix4 Implementation +// ============================================================================ + +Matrix4::Matrix4() : glm::mat4(1.0f) {} +Matrix4::Matrix4(float diagonal) : glm::mat4(diagonal) {} +Matrix4::Matrix4(const glm::mat4& m) : glm::mat4(m) {} + +glm::vec4& Matrix4::operator[](int index) { return glm::mat4::operator[](index); } +const glm::vec4& Matrix4::operator[](int index) const { return glm::mat4::operator[](index); } + +Matrix4 Matrix4::transposed() const { + return glm::transpose(*this); +} + +void Matrix4::transpose() { + *this = glm::transpose(*this); +} + +Matrix4 Matrix4::inverse() const { + return glm::inverse(*this); +} + +void Matrix4::invert() { + *this = glm::inverse(*this); +} + +float Matrix4::determinant() const { + return glm::determinant(*this); +} + +Vector3 Matrix4::xform(const Vector3& v) const { + glm::vec4 result = (*this) * glm::vec4(v.x, v.y, v.z, 1.0f); + return Vector3(result.x, result.y, result.z) / result.w; +} + +Vector3 Matrix4::xform_inv(const Vector3& v) const { + Matrix4 inv = glm::inverse(*this); + return inv.xform(v); +} + +Vector3 Matrix4::xform_normal(const Vector3& normal) const { + glm::vec4 result = (*this) * glm::vec4(normal.x, normal.y, normal.z, 0.0f); + return Vector3(result.x, result.y, result.z); +} + +Basis Matrix4::get_basis() const { + return Basis(glm::mat3(*this)); +} + +Vector3 Matrix4::get_translation() const { + return Vector3((*this)[3].x, (*this)[3].y, (*this)[3].z); +} + +Quaternion Matrix4::to_quaternion() const { + return glm::quat_cast(*this); +} + +Matrix4 Matrix4::translate(const Vector3& translation) { + return glm::translate(Matrix4(1.0f), translation); +} + +Matrix4 Matrix4::rotate(float angle, const Vector3& axis) { + return glm::rotate(Matrix4(1.0f), angle, axis); +} + +Matrix4 Matrix4::scale(const Vector3& scale_vec) { + return glm::scale(Matrix4(1.0f), scale_vec); +} + +Matrix4 Matrix4::look_at(const Vector3& eye, const Vector3& center, const Vector3& up) { + return glm::lookAt(eye, center, up); +} + +Matrix4 Matrix4::perspective(float fov, float aspect, float near_plane, float far_plane) { + return glm::perspective(fov, aspect, near_plane, far_plane); +} + +Matrix4 Matrix4::ortho(float left, float right, float bottom, float top, float near_plane, float far_plane) { + return glm::ortho(left, right, bottom, top, near_plane, far_plane); +} + +Matrix4 Matrix4::from_quaternion(const Quaternion& q) { + return glm::toMat4(q); +} + +const Matrix4 Matrix4::IDENTITY = Matrix4(1.0f); + diff --git a/engine/private/core/math/quaternion.cpp b/engine/private/core/math/quaternion.cpp new file mode 100644 index 000000000..dbf0b26d4 --- /dev/null +++ b/engine/private/core/math/quaternion.cpp @@ -0,0 +1,79 @@ +#include "core/math/quaternion.h" +#include "core/math/vector3.h" +#include "core/math/basis.h" +#include "core/math/matrix4.h" + +// ============================================================================ +// Quaternion Implementation +// ============================================================================ + +Quaternion::Quaternion() : glm::quat(1.0f, 0.0f, 0.0f, 0.0f) {} +Quaternion::Quaternion(float w, float x, float y, float z) : glm::quat(w, x, y, z) {} +Quaternion::Quaternion(const glm::quat& q) : glm::quat(q) {} +Quaternion::Quaternion(const glm::vec3& euler_angles) : glm::quat(euler_angles) {} + +float& Quaternion::operator[](int index) { return glm::quat::operator[](index); } +const float& Quaternion::operator[](int index) const { return glm::quat::operator[](index); } + +float Quaternion::length() const { + return glm::length(static_cast(*this)); +} + +Quaternion Quaternion::normalized() const { + return glm::normalize(static_cast(*this)); +} + +void Quaternion::normalize() { + *this = glm::normalize(static_cast(*this)); +} + +Quaternion Quaternion::conjugate() const { + return glm::conjugate(static_cast(*this)); +} + +Quaternion Quaternion::inverse() const { + return glm::inverse(static_cast(*this)); +} + +float Quaternion::dot(const Quaternion& other) const { + return glm::dot(static_cast(*this), static_cast(other)); +} + +Quaternion Quaternion::slerp(const Quaternion& other, float t) const { + return glm::slerp(static_cast(*this), static_cast(other), t); +} + +Vector3 Quaternion::xform(const Vector3& v) const { + return (*this) * v; +} + +Vector3 Quaternion::to_euler() const { + return glm::eulerAngles(*this); +} + +Matrix4 Quaternion::to_mat4() const { + return glm::toMat4(*this); +} + +Basis Quaternion::to_basis() const { + return glm::toMat3(*this); +} + +Quaternion Quaternion::from_axis_angle(const Vector3& axis, float angle) { + return glm::angleAxis(angle, axis); +} + +Quaternion Quaternion::from_euler(float pitch, float yaw, float roll) { + return Quaternion(Vector3(pitch, yaw, roll)); +} + +Quaternion Quaternion::from_euler(const Vector3& euler) { + return Quaternion(euler); +} + +Quaternion Quaternion::look_at(const Vector3& direction, const Vector3& up) { + return glm::quatLookAt(direction, up); +} + +const Quaternion Quaternion::IDENTITY = Quaternion(1.0f, 0.0f, 0.0f, 0.0f); + diff --git a/engine/private/core/math/vector2.cpp b/engine/private/core/math/vector2.cpp new file mode 100644 index 000000000..f74388ec4 --- /dev/null +++ b/engine/private/core/math/vector2.cpp @@ -0,0 +1,127 @@ +#include "core/math/vector2.h" +#include + +// ============================================================================ +// Vector2 Implementation +// ============================================================================ + +Vector2::Vector2() : glm::vec2(0.0f, 0.0f) {} +Vector2::Vector2(float x, float y) : glm::vec2(x, y) {} +Vector2::Vector2(float v) : glm::vec2(v, v) {} +Vector2::Vector2(const glm::vec2& v) : glm::vec2(v) {} + +float& Vector2::operator[](int index) { return glm::vec2::operator[](index); } +const float& Vector2::operator[](int index) const { return glm::vec2::operator[](index); } + +float Vector2::length() const { + return glm::length(static_cast(*this)); +} + +Vector2 Vector2::normalized() const { + return glm::normalize(static_cast(*this)); +} + +void Vector2::normalize() { + *this = glm::normalize(static_cast(*this)); +} + +float Vector2::dot(const Vector2& other) const { + return glm::dot(static_cast(*this), static_cast(other)); +} + +float Vector2::distance_to(const Vector2& other) const { + return glm::distance(static_cast(*this), static_cast(other)); +} + + +Vector2 Vector2::lerp(const Vector2& other, float t) const { + return Vector2(x + (other.x - x) * t, y + (other.y - y) * t); +} + +Vector2 Vector2::reflect(const Vector2& normal) const { + return glm::reflect(static_cast(*this), static_cast(normal)); +} + +Vector2 Vector2::refract(const Vector2& normal, float eta) const { + return glm::refract(static_cast(*this), static_cast(normal), eta); +} + +Vector2 Vector2::abs() const { + return Vector2(std::abs(x), std::abs(y)); +} + +Vector2 Vector2::sign() const { + return Vector2(x > 0.0f ? 1.0f : (x < 0.0f ? -1.0f : 0.0f), + y > 0.0f ? 1.0f : (y < 0.0f ? -1.0f : 0.0f)); +} + +Vector2 Vector2::floor() const { + return Vector2(std::floor(x), std::floor(y)); +} + +Vector2 Vector2::ceil() const { + return Vector2(std::ceil(x), std::ceil(y)); +} + +Vector2 Vector2::round() const { + return Vector2(std::round(x), std::round(y)); +} + +float Vector2::angle() const { + return std::atan2(y, x); +} + +float Vector2::angle_to(const Vector2& other) const { + return std::atan2(other.y - y, other.x - x); +} + +Vector2 Vector2::rotated(float angle) const { + float c = std::cos(angle); + float s = std::sin(angle); + return Vector2(x * c - y * s, x * s + y * c); +} + +bool Vector2::is_zero_approx() const { + return glm::length(static_cast(*this)) < 1e-6f; +} + +const Vector2 Vector2::ZERO = Vector2(0.0f, 0.0f); +const Vector2 Vector2::ONE = Vector2(1.0f, 1.0f); +const Vector2 Vector2::LEFT = Vector2(-1.0f, 0.0f); +const Vector2 Vector2::RIGHT = Vector2(1.0f, 0.0f); +const Vector2 Vector2::UP = Vector2(0.0f, -1.0f); +const Vector2 Vector2::DOWN = Vector2(0.0f, 1.0f); + +// ============================================================================ +// Vector2i Implementation +// ============================================================================ + +Vector2i::Vector2i() : glm::ivec2(0, 0) {} +Vector2i::Vector2i(int x, int y) : glm::ivec2(x, y) {} +Vector2i::Vector2i(int v) : glm::ivec2(v, v) {} +Vector2i::Vector2i(const glm::ivec2& v) : glm::ivec2(v) {} + +int& Vector2i::operator[](int index) { return glm::ivec2::operator[](index); } +const int& Vector2i::operator[](int index) const { return glm::ivec2::operator[](index); } + + +float Vector2i::length() const { + return glm::length(static_cast(*this)); +} + +Vector2i Vector2i::abs() const { + return Vector2i(std::abs(x), std::abs(y)); +} + +Vector2i Vector2i::sign() const { + return Vector2i(x > 0 ? 1 : (x < 0 ? -1 : 0), + y > 0 ? 1 : (y < 0 ? -1 : 0)); +} + +const Vector2i Vector2i::ZERO = Vector2i(0, 0); +const Vector2i Vector2i::ONE = Vector2i(1, 1); +const Vector2i Vector2i::LEFT = Vector2i(-1, 0); +const Vector2i Vector2i::RIGHT = Vector2i(1, 0); +const Vector2i Vector2i::UP = Vector2i(0, -1); +const Vector2i Vector2i::DOWN = Vector2i(0, 1); + diff --git a/engine/private/core/math/vector3.cpp b/engine/private/core/math/vector3.cpp new file mode 100644 index 000000000..7ff5b91c7 --- /dev/null +++ b/engine/private/core/math/vector3.cpp @@ -0,0 +1,126 @@ +#include "core/math/vector3.h" +#include + +// ============================================================================ +// Vector3 Implementation +// ============================================================================ + +Vector3::Vector3() : glm::vec3(0.0f, 0.0f, 0.0f) {} +Vector3::Vector3(float x, float y, float z) : glm::vec3(x, y, z) {} +Vector3::Vector3(float v) : glm::vec3(v, v, v) {} +Vector3::Vector3(const glm::vec3& v) : glm::vec3(v) {} + +float& Vector3::operator[](int index) { return glm::vec3::operator[](index); } +const float& Vector3::operator[](int index) const { return glm::vec3::operator[](index); } + +float Vector3::length() const { + return glm::length(static_cast(*this)); +} + +Vector3 Vector3::normalized() const { + return glm::normalize(static_cast(*this)); +} + +void Vector3::normalize() { + *this = glm::normalize(static_cast(*this)); +} + +float Vector3::dot(const Vector3& other) const { + return glm::dot(static_cast(*this), static_cast(other)); +} + +Vector3 Vector3::cross(const Vector3& other) const { + return glm::cross(static_cast(*this), static_cast(other)); +} + +float Vector3::distance_to(const Vector3& other) const { + return glm::distance(static_cast(*this), static_cast(other)); +} + + +Vector3 Vector3::lerp(const Vector3& other, float t) const { + return Vector3(x + (other.x - x) * t, + y + (other.y - y) * t, + z + (other.z - z) * t); +} + +Vector3 Vector3::reflect(const Vector3& normal) const { + return glm::reflect(static_cast(*this), static_cast(normal)); +} + +Vector3 Vector3::refract(const Vector3& normal, float eta) const { + return glm::refract(static_cast(*this), static_cast(normal), eta); +} + +Vector3 Vector3::abs() const { + return Vector3(std::abs(x), std::abs(y), std::abs(z)); +} + +Vector3 Vector3::sign() const { + return Vector3(x > 0.0f ? 1.0f : (x < 0.0f ? -1.0f : 0.0f), + y > 0.0f ? 1.0f : (y < 0.0f ? -1.0f : 0.0f), + z > 0.0f ? 1.0f : (z < 0.0f ? -1.0f : 0.0f)); +} + +Vector3 Vector3::floor() const { + return Vector3(std::floor(x), std::floor(y), std::floor(z)); +} + +Vector3 Vector3::ceil() const { + return Vector3(std::ceil(x), std::ceil(y), std::ceil(z)); +} + +Vector3 Vector3::round() const { + return Vector3(std::round(x), std::round(y), std::round(z)); +} + +bool Vector3::is_zero_approx() const { + return glm::length(static_cast(*this)) < 1e-6f; +} + + +const Vector3 Vector3::ZERO = Vector3(0.0f, 0.0f, 0.0f); +const Vector3 Vector3::ONE = Vector3(1.0f, 1.0f, 1.0f); +const Vector3 Vector3::LEFT = Vector3(-1.0f, 0.0f, 0.0f); +const Vector3 Vector3::RIGHT = Vector3(1.0f, 0.0f, 0.0f); +const Vector3 Vector3::UP = Vector3(0.0f, 1.0f, 0.0f); +const Vector3 Vector3::DOWN = Vector3(0.0f, -1.0f, 0.0f); +const Vector3 Vector3::FORWARD = Vector3(0.0f, 0.0f, -1.0f); +const Vector3 Vector3::BACK = Vector3(0.0f, 0.0f, 1.0f); + +// ============================================================================ +// Vector3i Implementation +// ============================================================================ + +Vector3i::Vector3i() : glm::ivec3(0, 0, 0) {} +Vector3i::Vector3i(int x, int y, int z) : glm::ivec3(x, y, z) {} +Vector3i::Vector3i(int v) : glm::ivec3(v, v, v) {} +Vector3i::Vector3i(const glm::ivec3& v) : glm::ivec3(v) {} + +int& Vector3i::operator[](int index) { return glm::ivec3::operator[](index); } +const int& Vector3i::operator[](int index) const { return glm::ivec3::operator[](index); } + + +float Vector3i::length() const { + return glm::length(static_cast(*this)); +} + +Vector3i Vector3i::abs() const { + return Vector3i(std::abs(x), std::abs(y), std::abs(z)); +} + +Vector3i Vector3i::sign() const { + return Vector3i(x > 0 ? 1 : (x < 0 ? -1 : 0), + y > 0 ? 1 : (y < 0 ? -1 : 0), + z > 0 ? 1 : (z < 0 ? -1 : 0)); +} + +const Vector3i Vector3i::ZERO = Vector3i(0, 0, 0); +const Vector3i Vector3i::ONE = Vector3i(1, 1, 1); +const Vector3i Vector3i::LEFT = Vector3i(-1, 0, 0); +const Vector3i Vector3i::RIGHT = Vector3i(1, 0, 0); +const Vector3i Vector3i::UP = Vector3i(0, 1, 0); +const Vector3i Vector3i::DOWN = Vector3i(0, -1, 0); +const Vector3i Vector3i::FORWARD = Vector3i(0, 0, -1); +const Vector3i Vector3i::BACK = Vector3i(0, 0, 1); + diff --git a/engine/private/core/math/vector4.cpp b/engine/private/core/math/vector4.cpp new file mode 100644 index 000000000..41a185abf --- /dev/null +++ b/engine/private/core/math/vector4.cpp @@ -0,0 +1,90 @@ +#include "core/math/vector4.h" +#include + +// ============================================================================ +// Vector4 Implementation +// ============================================================================ + +Vector4::Vector4() : glm::vec4(0.0f, 0.0f, 0.0f, 0.0f) {} +Vector4::Vector4(float x, float y, float z, float w) : glm::vec4(x, y, z, w) {} +Vector4::Vector4(float v) : glm::vec4(v, v, v, v) {} +Vector4::Vector4(const glm::vec4& v) : glm::vec4(v) {} + +float& Vector4::operator[](int index) { return glm::vec4::operator[](index); } +const float& Vector4::operator[](int index) const { return glm::vec4::operator[](index); } + +float Vector4::length() const { + return glm::length(static_cast(*this)); +} + +Vector4 Vector4::normalized() const { + return glm::normalize(static_cast(*this)); +} + +void Vector4::normalize() { + *this = glm::normalize(static_cast(*this)); +} + +float Vector4::dot(const Vector4& other) const { + return glm::dot(static_cast(*this), static_cast(other)); +} + +float Vector4::distance_to(const Vector4& other) const { + return glm::distance(static_cast(*this), static_cast(other)); +} + +Vector4 Vector4::lerp(const Vector4& other, float t) const { + return Vector4(x + (other.x - x) * t, + y + (other.y - y) * t, + z + (other.z - z) * t, + w + (other.w - w) * t); +} + +Vector4 Vector4::abs() const { + return Vector4(std::abs(x), std::abs(y), std::abs(z), std::abs(w)); +} + +Vector4 Vector4::floor() const { + return Vector4(std::floor(x), std::floor(y), std::floor(z), std::floor(w)); +} + +Vector4 Vector4::ceil() const { + return Vector4(std::ceil(x), std::ceil(y), std::ceil(z), std::ceil(w)); +} + +Vector4 Vector4::round() const { + return Vector4(std::round(x), std::round(y), std::round(z), std::round(w)); +} + +bool Vector4::is_zero_approx() const { + return length() < 1e-12f; +} + + +const Vector4 Vector4::ZERO = Vector4(0.0f, 0.0f, 0.0f, 0.0f); +const Vector4 Vector4::ONE = Vector4(1.0f, 1.0f, 1.0f, 1.0f); + +// ============================================================================ +// Vector4i Implementation +// ============================================================================ + +Vector4i::Vector4i() : glm::ivec4(0, 0, 0, 0) {} +Vector4i::Vector4i(int x, int y, int z, int w) : glm::ivec4(x, y, z, w) {} +Vector4i::Vector4i(int v) : glm::ivec4(v, v, v, v) {} +Vector4i::Vector4i(const glm::ivec4& v) : glm::ivec4(v) {} + +int& Vector4i::operator[](int index) { return glm::ivec4::operator[](index); } +const int& Vector4i::operator[](int index) const { return glm::ivec4::operator[](index); } + + +float Vector4i::length() const { + return glm::length(static_cast(*this)); +} + +Vector4i Vector4i::abs() const { + return Vector4i(std::abs(x), std::abs(y), std::abs(z), std::abs(w)); +} + +const Vector4i Vector4i::ZERO = Vector4i(0, 0, 0, 0); +const Vector4i Vector4i::ONE = Vector4i(1, 1, 1, 1); + diff --git a/engine/private/core/renderer/base_struct.cpp b/engine/private/core/renderer/base_struct.cpp deleted file mode 100644 index 77fbf2f9d..000000000 --- a/engine/private/core/renderer/base_struct.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "core/renderer/base_struct.h" - -TextureDesc GpuTexture::get_desc() const { - return _tex_desc; -} - -TextureFormat GpuTexture::get_format() const { - return _format; -} - -Uint32 GpuTexture::get_width() const { - return _width; -} - -Uint32 GpuTexture::get_height() const { - return _height; -} - -Uint32 GpuTexture::get_mip_levels() const { - return _mip_levels; -} - -Uint32 GpuTexture::get_id() const { - return _id; -} - -bool GpuTexture::load_from_file(const std::string& path, const TextureDesc& desc) { - int w, h, channels; - stbi_set_flip_vertically_on_load(true); - - unsigned char* data = nullptr; - - const bool is_hdr = stbi_is_hdr(path.c_str()); - - if (is_hdr) { - float* hdr_data = stbi_loadf(path.c_str(), &w, &h, &channels, 0); - data = reinterpret_cast(hdr_data); - } else { - data = stbi_load(path.c_str(), &w, &h, &channels, 0); - } - - if (!data) { - return false; - } - - TextureDesc final_desc = desc; - final_desc.width = w; - final_desc.height = h; - - if (final_desc.format == TextureFormat::UNKNOWN) { - if (is_hdr) { - final_desc.format = (channels == 4) ? TextureFormat::RGBA16F : TextureFormat::RGB16F; - } else { - switch (channels) { - case 1: - final_desc.format = TextureFormat::R8; - break; - case 2: - final_desc.format = TextureFormat::RG8; - break; - case 3: - final_desc.format = TextureFormat::RGB8; - break; - case 4: - final_desc.format = TextureFormat::RGBA8; - break; - default: - spdlog::error("Unsupported number of channels: {}", channels); - stbi_image_free(data); - break; - } - } - } - - - const bool success = create(data, final_desc); - - if (!success) { - spdlog::error("Failed to create texture from file: {}", path); - } - - stbi_image_free(data); - return success; -} - -bool GpuTexture::load_from_memory(const void* data, size_t size, const TextureDesc& desc) { - return false; -} diff --git a/engine/private/core/renderer/opengl/ogl_renderer.cpp b/engine/private/core/renderer/opengl/ogl_renderer.cpp deleted file mode 100644 index 2fbadcd5d..000000000 --- a/engine/private/core/renderer/opengl/ogl_renderer.cpp +++ /dev/null @@ -1,1423 +0,0 @@ -#include "core/renderer/opengl/ogl_renderer.h" - -#include "core/engine.h" - -void APIENTRY ogl_validation_layer(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, - const void* userParam) { - - if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) { - return; - } - - SDL_Log("ValidationLayer Type: 0x%x | Severity: 0x%x | ID: %u | Message: %s", type, severity, id, message); - - if (type == GL_DEBUG_TYPE_ERROR) { - GOLIAS_ASSERT_BREAK(); - } -} - - -GLuint load_cubemap_from_atlas(const std::string& atlas_path, CubemapOrientation orient = CubemapOrientation::DEFAULT) { - spdlog::debug("Loading cubemap atlas: {}", atlas_path); - - int W, H, channels; - unsigned char* pixels = stbi_load(atlas_path.c_str(), &W, &H, &channels, STBI_rgb_alpha); - - if (!pixels) { - spdlog::error("Failed to load cubemap atlas: {}", atlas_path); - return 0; - } - - spdlog::debug("Atlas loaded: {}x{} channels: {}", W, H, channels); - - if (W <= 0 || H <= 0) { - spdlog::error("Invalid atlas dimensions: {}x{}", W, H); - stbi_image_free(pixels); - return 0; - } - - // Detect layout - int face_w = 0, face_h = 0; - enum Layout { HORIZONTAL, VERTICAL, L_3x2, L_4x3_CROSS, UNKNOWN } layout = UNKNOWN; - - if (W % 6 == 0 && W / 6 == H) { - layout = HORIZONTAL; - face_w = W / 6; - face_h = H; - } else if (H % 6 == 0 && H / 6 == W) { - layout = VERTICAL; - face_w = W; - face_h = H / 6; - } else if (W % 3 == 0 && H % 2 == 0 && W / 3 == H / 2) { - layout = L_3x2; - face_w = W / 3; - face_h = H / 2; - } else if (W % 4 == 0 && H % 3 == 0 && W / 4 == H / 3) { - layout = L_4x3_CROSS; - face_w = W / 4; - face_h = H / 3; - } else { - spdlog::error("Unknown atlas layout: {}x{}", W, H); - stbi_image_free(pixels); - return 0; - } - - if (face_w <= 0 || face_h <= 0) { - spdlog::error("Invalid face dimensions: {}x{}", face_w, face_h); - stbi_image_free(pixels); - return 0; - } - - spdlog::debug("Detected layout: {}, Face size: {}x{}", static_cast(layout), face_w, face_h); - - // Define face rectangles based on layout - struct Rect { - int x, y, w, h; - }; - std::array face_rects; - - if (layout == HORIZONTAL) { - for (int i = 0; i < 6; ++i) { - face_rects[i] = {i * face_w, 0, face_w, face_h}; - } - } else if (layout == VERTICAL) { - for (int i = 0; i < 6; ++i) { - face_rects[i] = {0, i * face_h, face_w, face_h}; - } - } else if (layout == L_3x2) { - face_rects[0] = {0, 0, face_w, face_h}; // +X - face_rects[1] = {1 * face_w, 0, face_w, face_h}; // -X - face_rects[2] = {2 * face_w, 0, face_w, face_h}; // +Y - face_rects[3] = {0, 1 * face_h, face_w, face_h}; // -Y - face_rects[4] = {1 * face_w, 1 * face_h, face_w, face_h}; // +Z - face_rects[5] = {2 * face_w, 1 * face_h, face_w, face_h}; // -Z - } else { - // L_4x3_CROSS - face_rects[0] = {2 * face_w, 1 * face_h, face_w, face_h}; // +X - face_rects[1] = {0, 1 * face_h, face_w, face_h}; // -X - face_rects[2] = {1 * face_w, 0, face_w, face_h}; // +Y - face_rects[3] = {1 * face_w, 2 * face_h, face_w, face_h}; // -Y - face_rects[4] = {1 * face_w, 1 * face_h, face_w, face_h}; // +Z - face_rects[5] = {3 * face_w, 1 * face_h, face_w, face_h}; // -Z - } - - // Apply orientation adjustments - switch (orient) { - case CubemapOrientation::TOP: - std::swap(face_rects[2], face_rects[3]); // +Y <-> -Y - break; - case CubemapOrientation::BOTTOM: - std::swap(face_rects[2], face_rects[3]); - break; - case CubemapOrientation::FLIP_X: - std::swap(face_rects[0], face_rects[1]); - std::swap(face_rects[4], face_rects[5]); - break; - case CubemapOrientation::FLIP_Y: - std::swap(face_rects[2], face_rects[3]); - std::swap(face_rects[4], face_rects[5]); - break; - default: - break; - } - - // Validate rectangles - for (int i = 0; i < 6; ++i) { - const auto& r = face_rects[i]; - if (r.x < 0 || r.y < 0 || r.x + r.w > W || r.y + r.h > H) { - spdlog::error("Face rect {} out of bounds: x={} y={} w={} h={} (atlas: {}x{})", i, r.x, r.y, r.w, r.h, W, H); - stbi_image_free(pixels); - return 0; - } - } - - // Create cubemap texture - GLuint texture_id; - glGenTextures(1, &texture_id); - glBindTexture(GL_TEXTURE_CUBE_MAP, texture_id); - - constexpr int BYTES_PER_PIXEL = 4; - int pitch = W * BYTES_PER_PIXEL; - std::vector face_data(face_w * face_h * BYTES_PER_PIXEL); - - // Extract and upload each face - for (int i = 0; i < 6; ++i) { - const auto& r = face_rects[i]; - - // Copy face data row by row - for (int y = 0; y < r.h; ++y) { - int row = r.y + y; - unsigned char* src = pixels + (row * pitch) + (r.x * BYTES_PER_PIXEL); - unsigned char* dst = face_data.data() + (y * r.w * BYTES_PER_PIXEL); - std::memcpy(dst, src, r.w * BYTES_PER_PIXEL); - } - - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGBA, r.w, r.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, face_data.data()); - } - - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - glGenerateMipmap(GL_TEXTURE_CUBE_MAP); - - stbi_image_free(pixels); - - spdlog::info("Loaded cubemap atlas {} ({}x{}) Layout {} Face {}x{} Texture ID: {}", atlas_path, W, H, static_cast(layout), face_w, - face_h, texture_id); - - return texture_id; -} - - -WorldEnvironment3D* OpenGLRenderer::create_skybox_from_atlas(const std::string& atlas_path, CubemapOrientation orient, float brightness) { - - WorldEnvironment3D* world_environment = new WorldEnvironment3D(); - - constexpr float skybox_vertices[] = { - // positions - -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, - - -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, - - 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, - - -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, - - -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, - - -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f}; - - const std::vector indices = {0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4, 8, 9, 10, 10, 11, 8, - 12, 13, 14, 14, 15, 12, 16, 17, 18, 18, 19, 16, 20, 21, 22, 22, 23, 20}; - - world_environment->vertex_buffer = this->allocate_gpu_buffer(GpuBufferType::VERTEX); - world_environment->vertex_buffer->upload(skybox_vertices, sizeof(skybox_vertices)); - - world_environment->index_buffer = this->allocate_gpu_buffer(GpuBufferType::INDEX); - world_environment->index_buffer->upload(indices.data(), indices.size()); - - std::vector attributes = {{0, 3, DataType::FLOAT, false, 0}}; - - world_environment->vertex_layout = this->create_vertex_layout(world_environment->vertex_buffer.get(), - world_environment->index_buffer.get(), attributes, 3 * sizeof(float)); - - - spdlog::info("Skybox geometry initialized"); - - GLuint cubemap = load_cubemap_from_atlas(atlas_path, orient); - - if (cubemap == 0) { - spdlog::error("Failed to create skybox from atlas - loading failed"); - return world_environment; - } - - world_environment->texture = cubemap; - spdlog::info("Skybox created from atlas successfully"); - - // TODO: Refactor skybox to use GpuImage abstraction - - instance_buffer = allocate_gpu_buffer(GpuBufferType::STORAGE); - size_t buffer_size = MAX_INSTANCES * sizeof(glm::mat4); - instance_buffer->upload(nullptr, buffer_size); - - - return world_environment; -} - -void OpenGLRenderer::setup_instance_matrix_attribute(GpuVertexLayout* vao) { - vao->bind(); - instance_buffer->bind(); - - for (int i = 0; i < 4; i++) { - GLuint location = 3 + i; - glEnableVertexAttribArray(location); - glVertexAttribPointer(location, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*) (i * sizeof(glm::vec4))); - glVertexAttribDivisor(location, 1); - } - - vao->unbind(); -} - -void OpenGLRenderer::setup_lights(const std::vector& directional_lights, - const std::vector>& spot_lights) { - _default_shader->set_value("numDirLights", static_cast(directional_lights.size())); - if (!directional_lights.empty()) { - for (int i = 0; i < static_cast(directional_lights.size()); ++i) { - _default_shader->set_value(fmt::format("dirLights[{}].direction", i).c_str(), directional_lights[i].direction); - _default_shader->set_value(fmt::format("dirLights[{}].color", i).c_str(), - directional_lights[i].color * directional_lights[i].intensity); - _default_shader->set_value(fmt::format("dirLights[{}].cast_shadows", i).c_str(), directional_lights[i].castShadows ? 1 : 0); - } - } - - _default_shader->set_value("numSpotLights", static_cast(spot_lights.size())); - if (!spot_lights.empty()) { - for (int i = 0; i < static_cast(spot_lights.size()); ++i) { - const auto& [transform, light] = spot_lights[i]; - _default_shader->set_value(fmt::format("spotLights[{}].position", i).c_str(), transform.position); - _default_shader->set_value(fmt::format("spotLights[{}].direction", i).c_str(), light.direction); - _default_shader->set_value(fmt::format("spotLights[{}].color", i).c_str(), light.color * light.intensity); - _default_shader->set_value(fmt::format("spotLights[{}].inner_cut_off", i).c_str(), glm::cos(glm::radians(light.cutOff))); - _default_shader->set_value(fmt::format("spotLights[{}].outer_cut_off", i).c_str(), glm::cos(glm::radians(light.outerCutOff))); - } - } -} - -GLuint OpenGLRenderer::create_gl_texture(const unsigned char* data, int w, int h, int channels) { - GLuint texID = 0; - glGenTextures(1, &texID); - glBindTexture(GL_TEXTURE_2D, texID); - - GLenum format = GL_RGB; - if (channels == 1) { - format = GL_RED; - } else if (channels == 3) { - format = GL_RGB; - } else if (channels == 4) { - format = GL_RGBA; - } - - glTexImage2D(GL_TEXTURE_2D, 0, format, w, h, 0, format, GL_UNSIGNED_BYTE, data); - glGenerateMipmap(GL_TEXTURE_2D); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - return texID; -} - -OpenGLRenderer::~OpenGLRenderer() { - OpenGLRenderer::cleanup(); -} - - -bool OpenGLRenderer::initialize(int w, int h, SDL_Window* window) { - - _window = window; - -#if defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_EMSCRIPTEN) - - /* GLES 3.0 -> GLSL: 300 */ - SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); - -#elif defined(SDL_PLATFORM_WINDOWS) || defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_MACOS) - - /* OPENGL 3.3 -> GLSL: 330*/ - SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); - - #if defined(SDL_PLATFORM_MACOS) - SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); - #endif - - -#endif - - SDL_GLContext glContext = SDL_GL_CreateContext(GEngine->get_window()); - - if (!glContext) { - spdlog::critical("Failed to create GL context: {}", SDL_GetError()); - return false; - } - - -#if defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_EMSCRIPTEN) - - if (!gladLoadGLES2Loader((GLADloadproc) SDL_GL_GetProcAddress)) { - spdlog::critical("Failed to initialize GLAD (GLES_FUNCTIONS)"); - return false; - } - -#else - - if (!gladLoadGLLoader(reinterpret_cast(SDL_GL_GetProcAddress))) { - spdlog::critical("Failed to initialize GLAD (GL_FUNCTIONS)"); - return false; - } - -#endif - _context = glContext; - - SDL_GL_SetSwapInterval(0); - - const auto& viewport = GEngine->get_config().get_viewport(); - _virtual_width = viewport.width == 0 ? w : viewport.width; - _virtual_height = viewport.height == 0 ? h : viewport.height; - - GLint num_extensions = 0; - std::vector extensions; - glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions); - extensions.reserve(num_extensions); - - bool khr_debug_found = false; - - for (GLuint i = 0; i < num_extensions; i++) { - const char* ext = reinterpret_cast(glGetStringi(GL_EXTENSIONS, i)); - - - if (SDL_strcasecmp(ext, "GL_KHR_debug") == 0) { - spdlog::debug("KHR_debug extension supported, enabling validation layers"); - glEnable(GL_DEBUG_OUTPUT); - glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); - glDebugMessageCallback(ogl_validation_layer, nullptr); - khr_debug_found = true; - break; - } - } - - if (!khr_debug_found) { - spdlog::warn("KHR_debug extensions not supported, validation layers disabled"); - } - - int major, minor; - SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &major); - SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &minor); - - const char* glsl_version = reinterpret_cast(glGetString(GL_SHADING_LANGUAGE_VERSION)); - const char* gl_vendor = reinterpret_cast(glGetString(GL_VENDOR)); - const char* gl_renderer = reinterpret_cast(glGetString(GL_RENDERER)); - - spdlog::info("GLSL Version: {}", glsl_version); - spdlog::info("OpenGL Version: {}.{}", major, minor); - spdlog::info("OpenGL Vendor: {}", gl_vendor); - spdlog::info("OpenGL Renderer: {}", gl_renderer); - - glEnable(GL_DEPTH_TEST); - glViewport(0, 0, _virtual_width, _virtual_height); - - _default_shader = std::make_unique("shaders/opengl/default.vert", "shaders/opengl/default.frag"); - _default_shader->set_value("USE_IBL", false); - - _shadow_shader = std::make_unique("shaders/opengl/shadow.vert", "shaders/opengl/shadow.frag"); - _environment_shader = std::make_unique("shaders/opengl/skybox.vert", "shaders/opengl/skybox.frag"); - - FramebufferSpecification depth_spec; - depth_spec.height = 8192; - depth_spec.width = 8192; - depth_spec.attachments = {{FramebufferTextureFormat::DEPTH_COMPONENT}}; - - shadow_map_fbo = std::make_shared(depth_spec); - - FramebufferSpecification main_spec; - - main_spec.width = _virtual_width; - main_spec.height = _virtual_height; - main_spec.attachments = {{FramebufferTextureFormat::RGBA8}, {FramebufferTextureFormat::DEPTH24STENCIL8}}; - - main_fbo = std::make_shared(main_spec); - - initialize_2d_rendering(); - - _world_environment = create_skybox_from_atlas("res/environment_sky.png", CubemapOrientation::DEFAULT, 1.0f); - return true; -} - -std::shared_ptr OpenGLRenderer::create_gpu_texture(const std::string& path, const TextureDesc& desc) { - // Check if already cached - if (auto it = _textures.find(path); it != _textures.end()) { - spdlog::debug("Texture '{}' found in cache", path); - return it->second; - } - - auto image = std::make_shared(desc); - - // Load from file using the GpuImage abstraction - if (image->load_from_file(path, desc)) { - _textures[path] = image; - spdlog::info("Created and cached GpuImage: {}", path); - return image; - } - - spdlog::error("Failed to create GpuImage from: {}", path); - return nullptr; -} - -std::shared_ptr OpenGLRenderer::allocate_gpu_buffer(GpuBufferType type) { - return std::make_shared(type); -} - -std::shared_ptr OpenGLRenderer::create_vertex_layout(const GpuBuffer* vertex_buffer, const GpuBuffer* index_buffer, - const std::vector& attributes, Uint32 stride) { - - return std::make_unique(vertex_buffer, index_buffer, attributes, stride); -} - -GLuint OpenGLRenderer::load_texture_from_file(const std::string& path) { - if (auto it = _textures.find(path); it != _textures.end()) { - return it->second->get_id(); - } - - int w, h, channels; - unsigned char* data = stbi_load(path.c_str(), &w, &h, &channels, 0); - if (!data) { - spdlog::error("Failed to load texture: {}", path); - return 0; - } - - TextureDesc desc; - desc.width = w; - desc.height = h; - desc.format = channels == 4 ? TextureFormat::RGBA8 : channels == 3 ? TextureFormat::RGB8 : TextureFormat::R8; - desc.generate_mipmaps = true; - desc.min_filter = TextureFiltering::LINEAR_MIPMAP_LINEAR; - desc.mag_filter = TextureFiltering::LINEAR; - - auto gpu_tex = std::make_shared(desc); - if (!gpu_tex->create(data, desc)) { - spdlog::error("Failed to create GpuImage for: {}", path); - stbi_image_free(data); - return 0; - } - - stbi_image_free(data); - - _textures[path] = gpu_tex; - spdlog::info("Loaded and cached texture: {} (ID: {})", path, gpu_tex->get_id()); - - return gpu_tex->get_id(); -} - -GLuint OpenGLRenderer::load_embedded_texture(const unsigned char* buffer, size_t size, const std::string& name) { - std::string key = name.empty() ? "embedded_tex_" + std::to_string(reinterpret_cast(buffer)) : name; - - if (auto it = _textures.find(key); it != _textures.end()) { - return it->second->get_id(); - } - - int w, h, channels; - unsigned char* data = stbi_load_from_memory(buffer, (int) size, &w, &h, &channels, 0); - if (!data) { - spdlog::error("Failed to load texture from memory: {}", key); - return 0; - } - - // Create texture descriptor - TextureDesc desc; - desc.width = w; - desc.height = h; - desc.format = channels == 4 ? TextureFormat::RGBA8 : channels == 3 ? TextureFormat::RGB8 : TextureFormat::R8; - desc.generate_mipmaps = true; - desc.min_filter = TextureFiltering::LINEAR_MIPMAP_LINEAR; - desc.mag_filter = TextureFiltering::LINEAR; - - // Create GpuImage - auto image = std::make_shared(desc); - if (!image->create(data, desc)) { - spdlog::error("Failed to create GpuImage for embedded texture: {}", key); - stbi_image_free(data); - return 0; - } - - stbi_image_free(data); - - // Cache it - _textures[key] = image; - spdlog::info("Loaded embedded texture: {} (ID: {})", key, image->get_id()); - - return image->get_id(); -} - -void OpenGLRenderer::begin_frame() { - for (auto& [key, batch] : _instanced_batches) { - batch.clear(); - } - - for (auto& [mesh, batch] : _shadow_batches) { - batch.clear(); - } -} - -void OpenGLRenderer::begin_shadow_pass() { - shadow_map_fbo->bind(); - glEnable(GL_DEPTH_TEST); - glClear(GL_DEPTH_BUFFER_BIT); - _shadow_shader->activate(); -} - -void OpenGLRenderer::render_shadow_pass(const glm::mat4& light_space_matrix) { - _shadow_shader->set_value("LIGHT_MATRIX", light_space_matrix, 1); - - for (auto& [mesh_ptr, batch] : _shadow_batches) { - if (batch.model_matrices.empty()) { - continue; - } - - instance_buffer->bind(); - instance_buffer->upload(batch.model_matrices.data(), batch.model_matrices.size() * sizeof(glm::mat4)); - - setup_instance_matrix_attribute(batch.mesh->vertex_layout.get()); - - batch.mesh->vertex_layout->bind(); - glDrawElementsInstanced(GL_TRIANGLES, batch.mesh->index_count, GL_UNSIGNED_INT, 0, batch.model_matrices.size()); - } - - glBindVertexArray(0); -} - -void OpenGLRenderer::end_shadow_pass() { - shadow_map_fbo->unbind(); - glCullFace(GL_BACK); - glViewport(0, 0, _virtual_width, _virtual_height); - - glActiveTexture(GL_TEXTURE0 + SHADOW_TEXTURE_UNIT); - glBindTexture(GL_TEXTURE_2D, 0); - glActiveTexture(GL_TEXTURE0); -} - -void OpenGLRenderer::begin_render_target() { - main_fbo->bind(); - - int render_w = (_render_width > 0 && _render_height > 0) ? _render_width : _virtual_width; - int render_h = (_render_width > 0 && _render_height > 0) ? _render_height : _virtual_height; - - glViewport(0, 0, render_w, render_h); - - glClearColor(_world_environment->color.r, _world_environment->color.g, _world_environment->color.b, 1.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_LESS); - glDepthMask(GL_TRUE); - glDisable(GL_BLEND); - glBlendFunc(GL_ONE, GL_ZERO); - glCullFace(GL_BACK); - glEnable(GL_CULL_FACE); - glDisable(GL_SCISSOR_TEST); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glBindVertexArray(0); - glUseProgram(0); - - // TODO: Reset other states as needed - - glActiveTexture(GL_TEXTURE0); - - _default_shader->activate(); -} - -void OpenGLRenderer::render_main_target(const Camera3D& camera, const Transform3D& camera_transform, const glm::mat4& light_space_matrix, - const std::vector& directional_lights, - const std::vector>& spot_lights) { - - int render_w = (_render_width > 0 && _render_height > 0) ? _render_width : _virtual_width; - int render_h = (_render_width > 0 && _render_height > 0) ? _render_height : _virtual_height; - - glm::mat4 view = camera.get_view(camera_transform); - glm::mat4 projection = camera.get_projection(render_w, render_h); - - _default_shader->set_value("VIEW", view); - _default_shader->set_value("PROJECTION", projection); - _default_shader->set_value("LIGHT_MATRIX", light_space_matrix); - _default_shader->set_value("CAMERA_POSITION_WORLD", camera_transform.position); - - setup_lights(directional_lights, spot_lights); - - glActiveTexture(GL_TEXTURE0 + SHADOW_TEXTURE_UNIT); - glBindTexture(GL_TEXTURE_2D, shadow_map_fbo->get_depth_attachment_id()); - _default_shader->set_value("SHADOW_MAP", SHADOW_TEXTURE_UNIT); - - glActiveTexture(GL_TEXTURE0 + ENVIRONMENT_TEXTURE_UNIT); - glBindTexture(GL_TEXTURE_CUBE_MAP, _world_environment->texture); - _default_shader->set_value("ENVIRONMENT_MAP", ENVIRONMENT_TEXTURE_UNIT); - - render_opaque_pass(); - - render_transparent_pass(); -} - -void OpenGLRenderer::render_opaque_pass() { - int draw_calls = 0; - int total_instances = 0; - - - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_LESS); - glDepthMask(GL_TRUE); - glDisable(GL_BLEND); - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - - for (auto& [key, batch] : _instanced_batches) { - if (batch.model_matrices.empty() || !batch.material) { - continue; - } - - if (batch.material->blend_mode != BlendMode::OPAQUE) { - continue; - } - - draw_calls++; - total_instances += batch.model_matrices.size(); - - instance_buffer->bind(); - instance_buffer->upload(batch.model_matrices.data(), batch.model_matrices.size() * sizeof(glm::mat4)); - - batch.material->bind(_default_shader.get()); - - setup_instance_matrix_attribute(batch.mesh->vertex_layout.get()); - - batch.mesh->vertex_layout->bind(); - glDrawElementsInstanced(GL_TRIANGLES, batch.mesh->index_count, GL_UNSIGNED_INT, 0, batch.model_matrices.size()); - batch.mesh->vertex_layout->unbind(); - } - - // spdlog::debug("Opaque pass: {} draw calls, {} instances", draw_calls, total_instances); -} - -void OpenGLRenderer::render_transparent_pass() { - int draw_calls = 0; - int total_instances = 0; - - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_LESS); - glDepthMask(GL_FALSE); - glEnable(GL_BLEND); - glDisable(GL_CULL_FACE); - - for (auto& [key, batch] : _instanced_batches) { - if (batch.model_matrices.empty() || !batch.material) { - continue; - } - - - if (batch.material->blend_mode == BlendMode::OPAQUE) { - continue; - } - - switch (batch.material->blend_mode) { - case BlendMode::TRANSPARENT: - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glBlendEquation(GL_FUNC_ADD); - break; - case BlendMode::ADDITIVE: - glBlendFunc(GL_SRC_ALPHA, GL_ONE); - glBlendEquation(GL_FUNC_ADD); - break; - case BlendMode::MULTIPLY: - glBlendFunc(GL_DST_COLOR, GL_ZERO); - glBlendEquation(GL_FUNC_ADD); - break; - case BlendMode::SCREEN: - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR); - glBlendEquation(GL_FUNC_ADD); - break; - case BlendMode::OVERLAY: // Simple approximation - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glBlendEquation(GL_FUNC_ADD); - break; - default: - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - break; - } - - draw_calls++; - total_instances += batch.model_matrices.size(); - - instance_buffer->bind(); - instance_buffer->upload(batch.model_matrices.data(), batch.model_matrices.size() * sizeof(glm::mat4)); - - batch.material->bind(_default_shader.get()); - - setup_instance_matrix_attribute(batch.mesh->vertex_layout.get()); - - batch.mesh->vertex_layout->bind(); - glDrawElementsInstanced(GL_TRIANGLES, batch.mesh->index_count, GL_UNSIGNED_INT, 0, batch.model_matrices.size()); - batch.mesh->vertex_layout->unbind(); - } - - - glDepthMask(GL_TRUE); - glDisable(GL_BLEND); - glEnable(GL_CULL_FACE); - - // spdlog::debug("Transparent pass: {} draw calls, {} instances", draw_calls, total_instances); -} - -void OpenGLRenderer::end_render_target() { - // Don't blit yet - keep the framebuffer bound so 2D can render on top - // The blit will happen after 2D rendering is complete - // This allows compositing 2D on top of 3D -} - - -void OpenGLRenderer::begin_environment_pass() { - glDepthFunc(GL_LEQUAL); - _environment_shader->activate(); -} - -void OpenGLRenderer::render_environment_pass(const Camera3D& camera) { - auto camera_query = GEngine->get_world().query(); - const Transform3D& camera_transform = camera_query.first().get(); - - glm::mat4 view = glm::mat4(glm::mat3(camera.get_view(camera_transform))); - glm::mat4 projection = camera.get_projection(_virtual_width, _virtual_height); - - _environment_shader->set_value("VIEW", view); - _environment_shader->set_value("PROJECTION", projection); - - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_CUBE_MAP, _world_environment->texture); - _environment_shader->set_value("TEXTURE", 0); - - _world_environment->vertex_layout->bind(); - glDrawArrays(GL_TRIANGLES, 0, 36); - _world_environment->vertex_layout->unbind(); -} - -void OpenGLRenderer::end_environment_pass() { - glDepthFunc(GL_LESS); -} - -void OpenGLRenderer::add_to_render_batch(const Transform3D& transform, const MeshRef& mesh_ref, const MaterialRef& mat_ref) { - MeshMaterialKey key{mesh_ref.mesh, mat_ref.material}; - auto& batch = _instanced_batches[key]; - - batch.mesh = mesh_ref.mesh; - batch.material = mat_ref.material; - batch.model_matrices.push_back(transform.get_matrix()); -} - -void OpenGLRenderer::add_to_shadow_batch(const Transform3D& transform, const MeshRef& mesh_ref) { - auto& batch = _shadow_batches[mesh_ref.mesh]; - batch.mesh = mesh_ref.mesh; - batch.model_matrices.push_back(transform.get_matrix()); -} - - -void OpenGLRenderer::set_render_resolution(int width, int height) { - if (width <= 0 || height <= 0) { - spdlog::warn("Invalid render resolution: {}x{}", width, height); - return; - } - - _render_width = width; - _render_height = height; - - - main_fbo->resize(width, height); - - spdlog::debug("Set render resolution to {}x{} (Framebuffer resized, will scale to {}x{} on blit)", _render_width, _render_height, - _virtual_width, _virtual_height); -} - -void OpenGLRenderer::cleanup() { - - _textures.clear(); - - for (auto& [key, cached_tex] : _cached_text_textures) { - glDeleteTextures(1, &cached_tex.texture_id); - } - _cached_text_textures.clear(); - - // Clean up fonts - for (auto& [name, font] : _fonts) { - if (font) { - TTF_CloseFont(font); - } - } - _fonts.clear(); - - // TODO: create world enviroment entity - delete _world_environment; - _world_environment = nullptr; - - SDL_GL_DestroyContext(_context); -} - -void OpenGLRenderer::swap_chain() { - glBindFramebuffer(GL_READ_FRAMEBUFFER, main_fbo->get_fbo_id()); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - - int render_w = (_render_width > 0 && _render_height > 0) ? _render_width : _virtual_width; - int render_h = (_render_width > 0 && _render_height > 0) ? _render_height : _virtual_height; - - auto window_size = GEngine->get_config().get_window(); - - glBlitFramebuffer(0, 0, render_w, render_h, // source (main_fbo at render resolution) - 0, 0, window_size.width, window_size.height, // destination (screen at window resolution) - GL_COLOR_BUFFER_BIT, // copy color buffer - GL_NEAREST // nearest neighbor - ); - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - SDL_GL_SwapWindow(_window); -} - -// ============================================================================ -// 2D Rendering Implementation -// ============================================================================ - -void OpenGLRenderer::initialize_2d_rendering() { - - _shader_2d = std::make_unique("shaders/opengl/default_2d.vert", "shaders/opengl/default_2d.frag"); - - _vertex_buffer_2d = allocate_gpu_buffer(GpuBufferType::VERTEX); - _index_buffer_2d = allocate_gpu_buffer(GpuBufferType::INDEX); - - _vertex_buffer_2d->upload(nullptr, MAX_VERTICES_2D * sizeof(Vertex2D)); - _index_buffer_2d->upload(nullptr, MAX_INDICES_2D * sizeof(uint32_t)); - - std::vector attributes = { - {0, 2, DataType::FLOAT, false, offsetof(Vertex2D, position)}, // position - {1, 2, DataType::FLOAT, false, offsetof(Vertex2D, tex_coord)}, // tex_coord - {2, 4, DataType::FLOAT, false, offsetof(Vertex2D, color)} // color - }; - - _vertex_layout_2d = create_vertex_layout(_vertex_buffer_2d.get(), _index_buffer_2d.get(), attributes, sizeof(Vertex2D)); - - _batch_vertices_2d.reserve(MAX_VERTICES_2D); - _batch_indices_2d.reserve(MAX_INDICES_2D); - - - spdlog::info("2D rendering system initialized"); -} - - -void OpenGLRenderer::begin_frame_2d() { - - _draw_commands_2d.clear(); - _batch_vertices_2d.clear(); - _batch_indices_2d.clear(); -} - -void OpenGLRenderer::end_frame_2d(const Camera2D& camera, const Transform2D& camera_transform) { - - // Render to main_fbo (on top of 3D) - main_fbo->bind(); - - if (!_draw_commands_2d.empty()) { - glm::mat4 projection = camera.get_projection_matrix(); - glm::mat4 view = camera.get_view_matrix(); - glm::mat4 view_projection = projection * view; - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glBlendEquation(GL_FUNC_ADD); - - glDisable(GL_DEPTH_TEST); - glDepthMask(GL_FALSE); - glDisable(GL_CULL_FACE); - glDisable(GL_SCISSOR_TEST); - - _shader_2d->activate(); - _shader_2d->set_value("VIEW_PROJECTION", view_projection); - - uint32_t vertex_offset = 0; - uint32_t index_offset = 0; - - int draw_call = 0; - for (size_t i = 0; i < _draw_commands_2d.size(); ++i) { - const auto& cmd = _draw_commands_2d[i]; - - DrawMode2D current_mode = cmd.mode; - GLuint current_texture = cmd.texture_id; - bool current_use_texture = cmd.use_texture; - - _batch_vertices_2d.clear(); - _batch_indices_2d.clear(); - - size_t batch_start = i; - - for (size_t j = i; j < _draw_commands_2d.size(); ++j) { - const auto& batch_cmd = _draw_commands_2d[j]; - - bool can_batch = (batch_cmd.mode == current_mode && batch_cmd.texture_id == current_texture - && batch_cmd.use_texture == current_use_texture); - - if (batch_cmd.mode == DrawMode2D::CIRCLE_FILLED || batch_cmd.mode == DrawMode2D::CIRCLE_OUTLINE) { - can_batch = false; - } - - if (!can_batch && j > i) { - break; - } - - Uint32 base_vertex = _batch_vertices_2d.size(); - - _batch_vertices_2d.insert(_batch_vertices_2d.end(), batch_cmd.vertices.begin(), batch_cmd.vertices.end()); - - - for (auto idx : batch_cmd.indices) { - _batch_indices_2d.push_back(idx + base_vertex); - } - - i = j; - - if (batch_cmd.mode == DrawMode2D::CIRCLE_FILLED || batch_cmd.mode == DrawMode2D::CIRCLE_OUTLINE) { - break; - } - } - - if (!_batch_vertices_2d.empty()) { - render_batched_2d(current_mode, current_texture, current_use_texture, _draw_commands_2d[batch_start]); - } - - draw_call++; - } - - // spdlog::debug("2D Rendering complete: {} draw calls", draw_call); - } - - glBindVertexArray(0); - glActiveTexture(GL_TEXTURE0); - - glDisable(GL_BLEND); - glBlendFunc(GL_ONE, GL_ZERO); - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_LESS); - glDepthMask(GL_TRUE); - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - glDisable(GL_SCISSOR_TEST); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); -} - - -void OpenGLRenderer::render_batched_2d(DrawMode2D mode, GLuint texture_id, bool use_texture, const DrawCommand2D& first_cmd) { - if (_batch_vertices_2d.empty()) { - return; - } - - // Set draw mode - _shader_2d->set_value("DRAW_MODE", static_cast(mode)); - _shader_2d->set_value("USE_TEXTURE", use_texture); - - if (use_texture && texture_id != 0) { - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, texture_id); - _shader_2d->set_value("TEXTURE", 0); - } else { - // Ensure texture is not bound if not using it - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, 0); - } - - if (mode == DrawMode2D::CIRCLE_FILLED || mode == DrawMode2D::CIRCLE_OUTLINE) { - _shader_2d->set_value("CIRCLE_CENTER", first_cmd.position); - _shader_2d->set_value("CIRCLE_RADIUS", first_cmd.radius); - - if (mode == DrawMode2D::CIRCLE_OUTLINE) { - _shader_2d->set_value("CIRCLE_THICKNESS", first_cmd.thickness); - } - } - - _vertex_buffer_2d->bind(); - _vertex_buffer_2d->upload(_batch_vertices_2d.data(), _batch_vertices_2d.size() * sizeof(Vertex2D)); - - _index_buffer_2d->bind(); - _index_buffer_2d->upload(_batch_indices_2d.data(), _batch_indices_2d.size() * sizeof(uint32_t)); - - _vertex_layout_2d->bind(); - glDrawElements(GL_TRIANGLES, _batch_indices_2d.size(), GL_UNSIGNED_INT, 0); - _vertex_layout_2d->unbind(); -} - - -void OpenGLRenderer::draw_rect_2d(const glm::vec2& position, const glm::vec2& size, const glm::vec4& color, bool filled) { - DrawCommand2D cmd; - cmd.type = DrawType2D::RECTANGLE; - cmd.mode = filled ? DrawMode2D::FILLED : DrawMode2D::LINE; - cmd.color = color; - cmd.use_texture = false; - cmd.texture_id = 0; - cmd.position = position; - cmd.size = size; - cmd.filled = filled; - cmd.thickness = 1.0f; - - if (filled) { - - cmd.vertices = {{{position.x, position.y}, {0.0f, 0.0f}, color}, - {{position.x + size.x, position.y}, {1.0f, 0.0f}, color}, - {{position.x + size.x, position.y + size.y}, {1.0f, 1.0f}, color}, - {{position.x, position.y + size.y}, {0.0f, 1.0f}, color}}; - - cmd.indices = {0, 1, 2, 2, 3, 0}; - } else { - - float thickness = 1.0f; - glm::vec2 p1 = position; - glm::vec2 p2 = position + glm::vec2(size.x, 0); - glm::vec2 p3 = position + size; - glm::vec2 p4 = position + glm::vec2(0, size.y); - - // Create 4 thick lines - auto create_line = [&](const glm::vec2& start, const glm::vec2& end) { - glm::vec2 dir = glm::normalize(end - start); - glm::vec2 perp(-dir.y, dir.x); - glm::vec2 offset = perp * (thickness * 0.5f); - - uint32_t base = cmd.vertices.size(); - cmd.vertices.push_back({start - offset, {0, 0}, color}); - cmd.vertices.push_back({start + offset, {1, 0}, color}); - cmd.vertices.push_back({end + offset, {1, 1}, color}); - cmd.vertices.push_back({end - offset, {0, 1}, color}); - - cmd.indices.insert(cmd.indices.end(), {base, base + 1, base + 2, base + 2, base + 3, base}); - }; - - create_line(p1, p2); - create_line(p2, p3); - create_line(p3, p4); - create_line(p4, p1); - } - - _draw_commands_2d.push_back(cmd); -} - -void OpenGLRenderer::draw_texture_2d(const std::shared_ptr& texture, const glm::vec2& position, const glm::vec2& size, const Rect2D& src, - FlipMode flip, const glm::vec4& color) { - if (!texture) { - spdlog::warn("draw_texture_2d called with null texture"); - return; - } - - DrawCommand2D cmd; - cmd.type = DrawType2D::RECTANGLE; - cmd.mode = DrawMode2D::FILLED; - cmd.color = color; - cmd.use_texture = true; - cmd.texture_id = texture->get_id(); - cmd.position = position; - cmd.size = size; - cmd.filled = true; - - float u_min = 0.0f, v_min = 0.0f; - float u_max = 1.0f, v_max = 1.0f; - - if (!src.is_zero()) { - int tex_width = texture->get_width(); - int tex_height = texture->get_height(); - - if (tex_width > 0 && tex_height > 0) { - u_min = static_cast(src.x) / static_cast(tex_width); - v_min = static_cast(src.y) / static_cast(tex_height); - u_max = static_cast(src.x + src.width) / static_cast(tex_width); - v_max = static_cast(src.y + src.height) / static_cast(tex_height); - } - } - - if (flip == FlipMode::HORIZONTAL || flip == FlipMode::BOTH) { - std::swap(u_min, u_max); - } - - if (flip == FlipMode::VERTICAL || flip == FlipMode::BOTH) { - std::swap(v_min, v_max); - } - - cmd.vertices = {{{position.x, position.y}, {u_min, v_min}, color}, - {{position.x + size.x, position.y}, {u_max, v_min}, color}, - {{position.x + size.x, position.y + size.y}, {u_max, v_max}, color}, - {{position.x, position.y + size.y}, {u_min, v_max}, color}}; - - cmd.indices = {0, 1, 2, 2, 3, 0}; - - _draw_commands_2d.push_back(cmd); -} - -void OpenGLRenderer::draw_line_2d(const glm::vec2& start, const glm::vec2& end, const glm::vec4& color, float thickness) { - DrawCommand2D cmd; - cmd.type = DrawType2D::LINE; - cmd.mode = DrawMode2D::LINE; - cmd.color = color; - cmd.use_texture = false; - cmd.texture_id = 0; - cmd.thickness = thickness; - - glm::vec2 dir = glm::normalize(end - start); - glm::vec2 perp(-dir.y, dir.x); - glm::vec2 offset = perp * (thickness * 0.5f); - - cmd.vertices = { - {start - offset, {0, 0}, color}, {start + offset, {1, 0}, color}, {end + offset, {1, 1}, color}, {end - offset, {0, 1}, color}}; - - cmd.indices = {0, 1, 2, 2, 3, 0}; - - _draw_commands_2d.push_back(cmd); -} - -void OpenGLRenderer::draw_circle_2d(const glm::vec2& center, float radius, const glm::vec4& color, bool filled, int segments) { - DrawCommand2D cmd; - cmd.type = DrawType2D::CIRCLE; - cmd.mode = filled ? DrawMode2D::CIRCLE_FILLED : DrawMode2D::CIRCLE_OUTLINE; - cmd.color = color; - cmd.use_texture = false; - cmd.texture_id = 0; - cmd.position = center; - cmd.radius = radius; - cmd.segments = segments; - cmd.filled = filled; - cmd.thickness = 1.0f; - - glm::vec2 min = center - glm::vec2(radius); - glm::vec2 max = center + glm::vec2(radius); - - cmd.vertices = { - {{min.x, min.y}, {0, 0}, color}, {{max.x, min.y}, {1, 0}, color}, {{max.x, max.y}, {1, 1}, color}, {{min.x, max.y}, {0, 1}, color}}; - - cmd.indices = {0, 1, 2, 2, 3, 0}; - - _draw_commands_2d.push_back(cmd); -} - -void OpenGLRenderer::draw_circle_outline_2d(const glm::vec2& center, float radius, const glm::vec4& color, float thickness, int segments) { - - DrawCommand2D cmd; - cmd.type = DrawType2D::CIRCLE; - cmd.mode = DrawMode2D::CIRCLE_OUTLINE; - cmd.color = color; - cmd.use_texture = false; - cmd.texture_id = 0; - cmd.position = center; - cmd.radius = radius; - cmd.thickness = thickness; - cmd.segments = segments; - cmd.filled = false; - - float outer_radius = radius + thickness * 0.5f; - glm::vec2 min = center - glm::vec2(outer_radius); - glm::vec2 max = center + glm::vec2(outer_radius); - - cmd.vertices = { - {{min.x, min.y}, {0, 0}, color}, {{max.x, min.y}, {1, 0}, color}, {{max.x, max.y}, {1, 1}, color}, {{min.x, max.y}, {0, 1}, color}}; - - cmd.indices = {0, 1, 2, 2, 3, 0}; - - _draw_commands_2d.push_back(cmd); -} - -void OpenGLRenderer::draw_triangle_2d(const glm::vec2& p1, const glm::vec2& p2, const glm::vec2& p3, const glm::vec4& color, bool filled) { - DrawCommand2D cmd; - cmd.type = DrawType2D::TRIANGLE; - cmd.mode = filled ? DrawMode2D::FILLED : DrawMode2D::LINE; - cmd.color = color; - cmd.use_texture = false; - cmd.texture_id = 0; - cmd.filled = filled; - - if (filled) { - cmd.vertices = {{p1, {0, 0}, color}, {p2, {1, 0}, color}, {p3, {0.5f, 1}, color}}; - - cmd.indices = {0, 1, 2}; - } else { - constexpr float THICKNESS = 1.0f; - - auto create_line = [&](const glm::vec2& start, const glm::vec2& end) { - glm::vec2 dir = glm::normalize(end - start); - glm::vec2 perp(-dir.y, dir.x); - glm::vec2 offset = perp * (THICKNESS * 0.5f); - - uint32_t base = cmd.vertices.size(); - cmd.vertices.push_back({start - offset, {0, 0}, color}); - cmd.vertices.push_back({start + offset, {1, 0}, color}); - cmd.vertices.push_back({end + offset, {1, 1}, color}); - cmd.vertices.push_back({end - offset, {0, 1}, color}); - - cmd.indices.insert(cmd.indices.end(), {base, base + 1, base + 2, base + 2, base + 3, base}); - }; - - create_line(p1, p2); - create_line(p2, p3); - create_line(p3, p1); - } - - _draw_commands_2d.push_back(cmd); -} - -void OpenGLRenderer::draw_text_2d(const std::string& text, const glm::vec2& position, const glm::vec4& color, float scale) { - if (text.empty()) { - return; - } - - if (!_default_font) { - spdlog::warn("No default font loaded for text rendering"); - return; - } - - std::vector meshes; - - - size_t i = 0; - while (i < text.length()) { - unsigned char c = text[i]; - int char_len = 1; - bool is_emoji = false; - - if ((c & 0x80) == 0) { - char_len = 1; // ASCII - } else if ((c & 0xE0) == 0xC0) { - char_len = 2; - } else if ((c & 0xF0) == 0xE0) { - char_len = 3; - is_emoji = true; - } else if ((c & 0xF8) == 0xF0) { - char_len = 4; - is_emoji = true; - } - - TTF_Font* char_font = is_emoji && _emoji_font ? _emoji_font : _default_font; - - if (!meshes.empty() && meshes.back().font == char_font) { - meshes.back().text += text.substr(i, char_len); - } else { - meshes.push_back({text.substr(i, char_len), char_font, i}); - } - - i += char_len; - } - - float x_offset = 0.0f; - - for (const auto& mesh : meshes) { - Uint32 run_texture = render_text_to_texture(mesh.text, mesh.font); - if (run_texture == 0) { - continue; - } - - std::string cache_key = std::to_string(reinterpret_cast(mesh.font)) + "_" + mesh.text; - auto it = _cached_text_textures.find(cache_key); - if (it == _cached_text_textures.end()) { - continue; - } - - int run_width = it->second.width; - int run_height = it->second.height; - - - DrawCommand2D cmd; - cmd.type = DrawType2D::TEXT; - cmd.mode = DrawMode2D::TEXT; - cmd.color = color; - cmd.use_texture = true; - cmd.texture_id = run_texture; - - glm::vec2 run_pos = position + glm::vec2(x_offset, 0.0f); - glm::vec2 run_size = glm::vec2(run_width * scale, run_height * scale); - - cmd.position = run_pos; - cmd.size = run_size; - - cmd.vertices = {{{run_pos.x, run_pos.y}, {0.0f, 0.0f}, color}, - {{run_pos.x + run_size.x, run_pos.y}, {1.0f, 0.0f}, color}, - {{run_pos.x + run_size.x, run_pos.y + run_size.y}, {1.0f, 1.0f}, color}, - {{run_pos.x, run_pos.y + run_size.y}, {0.0f, 1.0f}, color}}; - - cmd.indices = {0, 1, 2, 2, 3, 0}; - - _draw_commands_2d.push_back(cmd); - - x_offset += run_width * scale; - } -} - -Uint32 OpenGLRenderer::render_text_to_texture(const std::string& text, TTF_Font* font) { - - const std::string cache_key = std::to_string(reinterpret_cast(font)) + "_" + text; - - auto it = _cached_text_textures.find(cache_key); - - if (it != _cached_text_textures.end()) { - return it->second.texture_id; - } - - - constexpr SDL_Color white_color = {255, 255, 255, 255}; - - SDL_Surface* surface = TTF_RenderText_Blended(font, text.c_str(), text.length(), white_color); - - if (!surface) { - spdlog::error("Failed to render text '{}': {}", text, SDL_GetError()); - return 0; - } - - SDL_Surface* rgba_surface = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32); - - SDL_DestroySurface(surface); - - if (!rgba_surface) { - spdlog::error("Failed to convert text surface to RGBA: {}", SDL_GetError()); - return 0; - } - - GLuint texture_id; - glGenTextures(1, &texture_id); - glBindTexture(GL_TEXTURE_2D, texture_id); - - - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); - - glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, rgba_surface->w, rgba_surface->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba_surface->pixels); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - // // Generate mipmaps for better quality at different scales - // glGenerateMipmap(GL_TEXTURE_2D); - - // GLfloat max_anisotropy = 0.0f; - // glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_anisotropy); - // if (max_anisotropy > 1.0f) { - // glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, std::min(max_anisotropy, 4.0f)); - // } - // - - int width = rgba_surface->w; - int height = rgba_surface->h; - - SDL_DestroySurface(rgba_surface); - - _cached_text_textures[cache_key] = {texture_id, width, height}; - - return texture_id; -} - -bool OpenGLRenderer::load_font(const std::string& font_path, int point_size, const std::string& font_name) { - std::string name = font_name.empty() ? font_path : font_name; - - if (_fonts.contains(name)) { - spdlog::warn("Font '{}' already loaded", name); - return true; - } - - TTF_Font* font = TTF_OpenFont(font_path.c_str(), point_size); - - if (!font) { - spdlog::error("Failed to load font '{}': {}", font_path, SDL_GetError()); - return false; - } - - TTF_SetFontHinting(font, TTF_HINTING_NORMAL); - - TTF_SetFontKerning(font, true); - - TTF_SetFontStyle(font, TTF_STYLE_NORMAL); - - TTF_SetFontDirection(font, TTF_DIRECTION_LTR); - - _fonts[name] = font; - - if (name.find("emoji") != std::string::npos || name.find("Emoji") != std::string::npos || name.find("Twemoji") != std::string::npos - || name.find("twemoji") != std::string::npos) { - _emoji_font = font; - spdlog::info("Loaded emoji font: '{}' ({}pt) as '{}'", font_path, point_size, name); - } else { - if (!_default_font) { - _default_font = font; - } - spdlog::info("Loaded text font: '{}' ({}pt) as '{}'", font_path, point_size, name); - } - - return true; -} diff --git a/engine/private/core/renderer/opengl/ogl_struct.cpp b/engine/private/core/renderer/opengl/ogl_struct.cpp deleted file mode 100644 index 536e4ad27..000000000 --- a/engine/private/core/renderer/opengl/ogl_struct.cpp +++ /dev/null @@ -1,630 +0,0 @@ -#include "core/renderer/opengl/ogl_struct.h" - -#include "core/io/assimp_io.h" - -#if defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_EMSCRIPTEN) -#define SHADER_HEADER "#version 300 es\nprecision highp float;\n\n" -#else -#define SHADER_HEADER "#version 330 core\n\n" -#endif - -bool validate_gl_shader(GLuint handle, GLenum op, bool is_program = false) { - GLint success = 0; - char infoLog[1024]; - - const char* op_str = (op == GL_COMPILE_STATUS) - ? "COMPILE" - : (op == GL_LINK_STATUS) - ? "LINK" - : (op == GL_VALIDATE_STATUS) - ? "VALIDATE" - : "UNKNOWN"; - - if (is_program) { - if (op == GL_VALIDATE_STATUS) { - glValidateProgram(handle); - } - - - glGetProgramiv(handle, op, &success); - if (!success) { - glGetProgramInfoLog(handle, sizeof(infoLog), nullptr, infoLog); - spdlog::critical("OPENGLSHADER::{}:ERROR: {}", op_str, infoLog); - return false; - } - - } else { - glGetShaderiv(handle, op, &success); - if (!success) { - glGetShaderInfoLog(handle, sizeof(infoLog), nullptr, infoLog); - spdlog::critical("OPENGLSHADER::{}:ERROR: {}", op_str, infoLog); - return false; - } - } - - return true; -} - -OpenglShader::OpenglShader(const std::string& vertex, const std::string& fragment) { - spdlog::info("OpenglShader::OpenglShader - Compiling Shaders Sources Vertex ({}) | Fragment ({})", vertex, fragment); - - - const std::string vertexSource = SHADER_HEADER + load_assets_file(vertex); - const std::string fragmentSource = SHADER_HEADER + load_assets_file(fragment); - - - Uint32 vs = compile_shader(GL_VERTEX_SHADER, vertexSource.c_str()); - Uint32 fs = compile_shader(GL_FRAGMENT_SHADER, fragmentSource.c_str()); - - Uint32 program = glCreateProgram(); - glAttachShader(program, vs); - glAttachShader(program, fs); - glLinkProgram(program); - - glUseProgram(program); - glUniform1i(glGetUniformLocation(program, "ALBEDO_MAP"), ALBEDO_TEXTURE_UNIT); - glUniform1i(glGetUniformLocation(program, "METALLIC_MAP"), METALLIC_TEXTURE_UNIT); - glUniform1i(glGetUniformLocation(program, "ROUGHNESS_MAP"), ROUGHNESS_TEXTURE_UNIT); - glUniform1i(glGetUniformLocation(program, "NORMAL_MAP"), NORMAL_MAP_TEXTURE_UNIT); - glUniform1i(glGetUniformLocation(program, "AO_MAP"), AMBIENT_OCCLUSION_TEXTURE_UNIT); - glUniform1i(glGetUniformLocation(program, "EMISSIVE_MAP"), EMISSIVE_TEXTURE_UNIT); - glUniform1i(glGetUniformLocation(program, "SHADOW_MAP"), SHADOW_TEXTURE_UNIT); - glUniform1i(glGetUniformLocation(program, "ENVIRONMENT_MAP"), ENVIRONMENT_TEXTURE_UNIT); - - const bool success = validate_gl_shader(program, GL_LINK_STATUS, true); - - // success &= validate_gl_shader(program, GL_VALIDATE_STATUS, true); - - if (!success) { - spdlog::critical("OpenglShader::OpenglShader - Shader program setup failed"); - glDeleteProgram(program); - program = 0; - exit(EXIT_FAILURE); - } - - glDeleteShader(vs); - glDeleteShader(fs); - - this->id = program; - -} - -Uint32 OpenglShader::compile_shader(Uint32 type, const char* source) { - spdlog::info("OpenglShader::CompileShader - Compiling shader of type {}", type == GL_VERTEX_SHADER ? "VERTEX" : "FRAGMENT"); - - - Uint32 shader = glCreateShader(type); - - glShaderSource(shader, 1, &source, nullptr); - glCompileShader(shader); - - bool success = validate_gl_shader(shader, GL_COMPILE_STATUS); - - - if (!success) { - spdlog::critical("OpenglShader::compile_shader - Shader compilation failed for type {}", - type == GL_VERTEX_SHADER ? "VERTEX" : "FRAGMENT"); - glDeleteShader(shader); - exit(EXIT_FAILURE); - - return 0; - } - - return shader; -} - -Uint32 OpenglShader::get_uniform_location(const std::string& name) { - if (_uniforms.find(name) != _uniforms.end()) { - return _uniforms[name]; - } - - Uint32 location = glGetUniformLocation(id, name.c_str()); - - if (location == -1) { - spdlog::warn("OpenglShader::get_uniform_location - Uniform {} not found in shader {}", name, id); - return location; - } - - _uniforms[name] = location; - return location; -} - -bool OpenglShader::is_valid() const { - - bool validated = glIsProgram(id); - return validated; -} - -void OpenglShader::activate() const { - glUseProgram(id); -} - -void OpenglShader::destroy() { - glDeleteProgram(id); -} - - -OpenglGpuVertexLayout::OpenglGpuVertexLayout(const GpuBuffer* vertex_buffer, const GpuBuffer* index_buffer, - const std::vector& attributes, uint32_t stride) { - glGenVertexArrays(1, &_vao); - glBindVertexArray(_vao); - - vertex_buffer->bind(); - - for (const auto& attr : attributes) { - GLenum gl_type = to_gl_type(attr.type); - glVertexAttribPointer( - attr.location, - attr.components, - gl_type, - attr.normalized ? GL_TRUE : GL_FALSE, - stride, - (void*) (uintptr_t) attr.offset - ); - glEnableVertexAttribArray(attr.location); - } - - if (index_buffer) { - index_buffer->bind(); - } - - glBindVertexArray(0); -} - -OpenglGpuVertexLayout::~OpenglGpuVertexLayout() { - if (_vao) - glDeleteVertexArrays(1, &_vao); -} - -void OpenglGpuVertexLayout::bind() const { - glBindVertexArray(_vao); -} - -void OpenglGpuVertexLayout::unbind() const { - glBindVertexArray(0); -} - -GLenum OpenglGpuVertexLayout::to_gl_type(DataType type) { - switch (type) { - case DataType::USHORT: - return GL_UNSIGNED_SHORT; - case DataType::FLOAT: - return GL_FLOAT; - case DataType::INT: - return GL_INT; - case DataType::UNSIGNED_INT: - return GL_UNSIGNED_INT; - default: - return GL_FLOAT; - } -} - -OpenglGpuBuffer::OpenglGpuBuffer(GpuBufferType type) : _buffer_type(type) { - - switch (type) { - case GpuBufferType::VERTEX: - _target = GL_ARRAY_BUFFER; - break; - case GpuBufferType::INDEX: - _target = GL_ELEMENT_ARRAY_BUFFER; - break; - case GpuBufferType::UNIFORM: - _target = GL_UNIFORM_BUFFER; - break; - default: - _target = GL_ARRAY_BUFFER; - break; - } - - - glGenBuffers(1, &_id); - - spdlog::info("OpenglGpuBuffer::OpenglGpuBuffer - Created GPU Buffer ID: {} of Type: {}", _id, - type == GpuBufferType::VERTEX - ? "VERTEX" - : type == GpuBufferType::INDEX - ? "INDEX" - : type == GpuBufferType::UNIFORM - ? "UNIFORM" - : "STORAGE"); -} - -OpenglGpuBuffer::~OpenglGpuBuffer() { - glDeleteBuffers(1, &_id); -} - -void OpenglGpuBuffer::bind() const { - glBindBuffer(_target, _id); -} - -void OpenglGpuBuffer::upload(const void* data, size_t size) { - bind(); - glBufferData(_target, size, data, GL_STATIC_DRAW); - _buffer_size = size; -} - -size_t OpenglGpuBuffer::size() const { - return _buffer_size; -} - -GpuBufferType OpenglGpuBuffer::type() const { - return _buffer_type; -} - -OpenglGpuTexture::OpenglGpuTexture(const TextureDesc& desc) : GpuTexture(desc) { - glGenTextures(1, &_texture_id); - _id = _texture_id; - spdlog::debug("OpenglGpuTexture::OpenglGpuTexture - Created texture ID: {}", _texture_id); -} - -OpenglGpuTexture::~OpenglGpuTexture() { - destroy(); -} - -bool OpenglGpuTexture::create(const void* p_data, const TextureDesc& desc) { - if (_texture_id == 0) { - glGenTextures(1, &_texture_id); - _id = _texture_id; - } - - bind(0); - - GLenum internal_format = to_gl_internal_format(desc.format); - GLenum format = to_gl_format(desc.format); - GLenum data_type = GL_UNSIGNED_BYTE; - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, to_gl_filter(desc.min_filter)); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, to_gl_filter(desc.mag_filter)); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, to_gl_wrap(desc.wrap_s)); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, to_gl_wrap(desc.wrap_t)); - - if (desc.anisotropy > 1.0f) { - float max_aniso = 0.0f; - glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY, &max_aniso); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, glm::min(desc.anisotropy, max_aniso)); - } - - if (desc.wrap_s == TextureWrap::CLAMP_TO_BORDER || desc.wrap_t == TextureWrap::CLAMP_TO_BORDER) { - float border_color[] = { desc.border_color.r, desc.border_color.g, desc.border_color.b, desc.border_color.a }; - glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color); - } - - glTexImage2D(GL_TEXTURE_2D, 0, internal_format, desc.width, desc.height, 0, format, data_type, p_data); - - if (desc.generate_mipmaps) { - glGenerateMipmap(GL_TEXTURE_2D); - - GLint mip_levels; - glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, &mip_levels); - _mip_levels = mip_levels; - } else { - _mip_levels = 1; - } - - _width = desc.width; - _height = desc.height; - _format = desc.format; - _tex_desc = desc; - - unbind(); - - spdlog::info("OpenglGpuTexture::create - Texture created (ID: {}, Size: {}x{}, Format: {})", - _texture_id, _width, _height, static_cast(desc.format)); - - return true; -} - -void OpenglGpuTexture::bind(Uint32 slot) const { - glActiveTexture(GL_TEXTURE0 + slot); - glBindTexture(GL_TEXTURE_2D, _texture_id); -} - -void OpenglGpuTexture::unbind() { - glBindTexture(GL_TEXTURE_2D, 0); -} - -void OpenglGpuTexture::destroy() { - if (_texture_id != 0) { - glDeleteTextures(1, &_texture_id); - _texture_id = 0; - _id = 0; - } -} - -GLenum OpenglGpuTexture::to_gl_format(TextureFormat format) { - switch (format) { - case TextureFormat::R8: - case TextureFormat::R16F: - case TextureFormat::R32F: - return GL_RED; - case TextureFormat::RG8: - case TextureFormat::RG16F: - case TextureFormat::RG32F: - return GL_RG; - case TextureFormat::RGB8: - case TextureFormat::RGB16F: - case TextureFormat::RGB32F: - case TextureFormat::SRGB8: - return GL_RGB; - case TextureFormat::RGBA8: - case TextureFormat::RGBA16F: - case TextureFormat::RGBA32F: - case TextureFormat::SRGB8_ALPHA8: - return GL_RGBA; - case TextureFormat::DEPTH24: - case TextureFormat::DEPTH32F: - return GL_DEPTH_COMPONENT; - case TextureFormat::DEPTH24_STENCIL8: - return GL_DEPTH_STENCIL; - default: - return GL_RGBA; - } -} - -GLenum OpenglGpuTexture::to_gl_internal_format(TextureFormat format) { - switch (format) { - case TextureFormat::R8: return GL_R8; - case TextureFormat::RG8: return GL_RG8; - case TextureFormat::RGB8: return GL_RGB8; - case TextureFormat::RGBA8: return GL_RGBA8; - case TextureFormat::R16F: return GL_R16F; - case TextureFormat::RG16F: return GL_RG16F; - case TextureFormat::RGB16F: return GL_RGB16F; - case TextureFormat::RGBA16F: return GL_RGBA16F; - case TextureFormat::R32F: return GL_R32F; - case TextureFormat::RG32F: return GL_RG32F; - case TextureFormat::RGB32F: return GL_RGB32F; - case TextureFormat::RGBA32F: return GL_RGBA32F; - case TextureFormat::DEPTH24: return GL_DEPTH_COMPONENT24; - case TextureFormat::DEPTH32F: return GL_DEPTH_COMPONENT32F; - case TextureFormat::DEPTH24_STENCIL8: return GL_DEPTH24_STENCIL8; - case TextureFormat::SRGB8: return GL_SRGB8; - case TextureFormat::SRGB8_ALPHA8: return GL_SRGB8_ALPHA8; - default: return GL_RGBA8; - } -} - -GLenum OpenglGpuTexture::to_gl_filter(TextureFiltering filter) { - switch (filter) { - case TextureFiltering::NEAREST: return GL_NEAREST; - case TextureFiltering::LINEAR: return GL_LINEAR; - case TextureFiltering::NEAREST_MIPMAP_NEAREST: return GL_NEAREST_MIPMAP_NEAREST; - case TextureFiltering::LINEAR_MIPMAP_NEAREST: return GL_LINEAR_MIPMAP_NEAREST; - case TextureFiltering::NEAREST_MIPMAP_LINEAR: return GL_NEAREST_MIPMAP_LINEAR; - case TextureFiltering::LINEAR_MIPMAP_LINEAR: return GL_LINEAR_MIPMAP_LINEAR; - default: return GL_LINEAR; - } -} - -GLenum OpenglGpuTexture::to_gl_wrap(TextureWrap wrap) { - switch (wrap) { - case TextureWrap::REPEAT: return GL_REPEAT; - case TextureWrap::MIRRORED_REPEAT: return GL_MIRRORED_REPEAT; - case TextureWrap::CLAMP_TO_EDGE: return GL_CLAMP_TO_EDGE; - case TextureWrap::CLAMP_TO_BORDER: return GL_CLAMP_TO_BORDER; - default: return GL_REPEAT; - } -} - - -OpenGLFramebuffer::OpenGLFramebuffer(const FramebufferSpecification& spec) : specification(spec) { - spdlog::info("OpenGLFramebuffer::OpenGLFramebuffer - Creating Framebuffer ({}x{})", spec.width, spec.height); - invalidate(); -} - -OpenGLFramebuffer::~OpenGLFramebuffer() { - cleanup(); -} - - -void OpenGLFramebuffer::invalidate() { - if (fbo) - cleanup(); - - if (specification.width == 0 || specification.height == 0) { - spdlog::error("OpenGLFramebuffer::invalidate - Invalid dimensions ({}x{})", - specification.width, specification.height); - return; - } - - spdlog::info("OpenGLFramebuffer::invalidate - Recreating Framebuffer ({}x{})", - specification.width, specification.height); - - glGenFramebuffers(1, &fbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - - bool hasDepthAttachment = false; - for (auto& attachment : specification.attachments.attachments) { - switch (attachment.format) { - case FramebufferTextureFormat::RGBA8: { - uint32_t tex; - glGenTextures(1, &tex); - glBindTexture(GL_TEXTURE_2D, tex); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, specification.width, specification.height, 0, - GL_RGBA, GL_UNSIGNED_BYTE, nullptr); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + color_attachments.size(), - GL_TEXTURE_2D, tex, 0); - color_attachments.push_back(tex); - break; - } - case FramebufferTextureFormat::DEPTH_COMPONENT: - case FramebufferTextureFormat::DEPTH24STENCIL8: { - hasDepthAttachment = true; - glGenTextures(1, &depth_attachment); - glBindTexture(GL_TEXTURE_2D, depth_attachment); - - if (attachment.format == FramebufferTextureFormat::DEPTH24STENCIL8) { - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, specification.width, specification.height, - 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, depth_attachment, 0); - } else { - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, specification.width, specification.height, - 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_attachment, 0); - } - - 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); - break; - } - default: - spdlog::warn("Unsupported framebuffer attachment format"); - break; - } - } - - if (color_attachments.empty()) { - glDrawBuffers(0, nullptr); - glReadBuffer(GL_NONE); - } else { - GLenum buffers[4] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3}; - glDrawBuffers((GLsizei) color_attachments.size(), buffers); - } - - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - if (status != GL_FRAMEBUFFER_COMPLETE) { - spdlog::critical("OpenGLFramebuffer::invalidate - Framebuffer is incomplete! Status: 0x{:x}", status); - - switch (status) { - case GL_FRAMEBUFFER_UNDEFINED: - spdlog::error(" -> GL_FRAMEBUFFER_UNDEFINED"); - break; - case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: - spdlog::error(" -> GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"); - break; - case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: - spdlog::error(" -> GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"); - break; - case GL_FRAMEBUFFER_UNSUPPORTED: - spdlog::error(" -> GL_FRAMEBUFFER_UNSUPPORTED"); - break; - } - } else { - spdlog::info("Framebuffer created successfully ({}x{})", specification.width, specification.height); - } - - glBindFramebuffer(GL_FRAMEBUFFER, 0); -} - - - -void OpenGLFramebuffer::bind() { - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glViewport(0, 0, specification.width, specification.height); -} - -void OpenGLFramebuffer::unbind() { - glBindFramebuffer(GL_FRAMEBUFFER, 0); -} - -void OpenGLFramebuffer::resize(unsigned int width, unsigned int height) { - specification.width = width; - specification.height = height; - invalidate(); -} - -Uint32 OpenGLFramebuffer::get_fbo_id() const { - return fbo; -} - -Uint32 OpenGLFramebuffer::get_color_attachment_id(size_t index) const { - return color_attachments[index]; -} - -Uint32 OpenGLFramebuffer::get_depth_attachment_id() const { - return depth_attachment; -} - -const FramebufferSpecification& OpenGLFramebuffer::get_specification() const { - return specification; -} - -void OpenGLFramebuffer::cleanup() { - if (fbo != 0) { - glDeleteFramebuffers(1, &fbo); - fbo = 0; - } - - for (auto& tex_id : color_attachments) { - if (tex_id != 0) { - glDeleteTextures(1, &tex_id); - } - } - color_attachments.clear(); - - if (depth_attachment != 0) { - glDeleteTextures(1, &depth_attachment); - depth_attachment = 0; - } -} - -OpenglShader::~OpenglShader() { - destroy(); -} - -Uint32 OpenglShader::get_id() const { - return id; -} - - -void OpenglShader::set_value(const char* name, float value) { - const Uint32 location = get_uniform_location(name); - glUniform1f(location, value); -} - -void OpenglShader::set_value(const char* name, int value) { - const Uint32 location = get_uniform_location(name); - glUniform1i(location, value); -} - -void OpenglShader::set_value(const char* name, Uint32 value) { - const Uint32 location = get_uniform_location(name); - glUniform1i(location, value); -} - -void OpenglShader::set_value(const char* name, const int* value, Uint32 count) { - const Uint32 location = get_uniform_location(name); - glUniform1iv(location, count, value); -} - -void OpenglShader::set_value(const char* name, const float* value, Uint32 count) { - const Uint32 location = get_uniform_location(name); - glUniform1fv(location, count, value); -} - - -void OpenglShader::set_value(const char* name, glm::mat4 value, Uint32 count) { - const Uint32 location = get_uniform_location(name); - glUniformMatrix4fv(location, count, GL_FALSE, glm::value_ptr(value)); -} - -void OpenglShader::set_value(const char* name, const glm::mat4* values, Uint32 count) { - if (values == nullptr || count == 0) { - spdlog::warn("OpenglShader::set_value - Invalid matrix array or count is zero"); - return; - } - - const Uint32 location = get_uniform_location(name); - glUniformMatrix4fv(location, count, GL_FALSE, glm::value_ptr(*values)); -} - -void OpenglShader::set_value(const char* name, glm::vec2 value, Uint32 count) { - const Uint32 location = get_uniform_location(name); - glUniform2fv(location, count, glm::value_ptr(value)); -} - -void OpenglShader::set_value(const char* name, glm::vec3 value, Uint32 count) { - const Uint32 location = get_uniform_location(name); - glUniform3fv(location, count, glm::value_ptr(value)); -} - -void OpenglShader::set_value(const char* name, glm::vec4 value, Uint32 count) { - const Uint32 location = get_uniform_location(name); - glUniform4fv(location, count, glm::value_ptr(value)); -} diff --git a/engine/private/core/renderer/renderer.cpp b/engine/private/core/renderer/renderer.cpp deleted file mode 100644 index 9f741ea83..000000000 --- a/engine/private/core/renderer/renderer.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include "core/renderer/renderer.h" - - -std::shared_ptr Renderer::get_shadow_map_fbo() const { - return shadow_map_fbo; -} - -std::shared_ptr Renderer::get_main_fbo() const { - return main_fbo; -} diff --git a/engine/private/core/system/timer.cpp b/engine/private/core/system/timer.cpp deleted file mode 100644 index cd1fd6711..000000000 --- a/engine/private/core/system/timer.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "core/system/timer.h" - - -int Timer::get_fps() const { - if (delta > 0) { - return static_cast(1.0 / delta); - } - - return 0; -} - -void Timer::start() { - now = SDL_GetPerformanceCounter(); - last = now; - delta = 0.0; - elapsed_time = 0.0; -} - -void Timer::tick() { - last = now; - now = SDL_GetPerformanceCounter(); - - Uint64 freq = SDL_GetPerformanceFrequency(); - delta = static_cast(now - last) / static_cast(freq); - elapsed_time += delta; -} diff --git a/engine/private/core/utility/obj_loader.cpp b/engine/private/core/utility/obj_loader.cpp deleted file mode 100644 index 4b4454518..000000000 --- a/engine/private/core/utility/obj_loader.cpp +++ /dev/null @@ -1,241 +0,0 @@ -#include "core/utility/obj_loader.h" - -#include "core/engine.h" -#include "core/io/assimp_io.h" - -MeshInstance3D AssimpLoader::load_mesh(const std::string& path) { - Model model = load_model(path); - return model.meshes.empty() ? MeshInstance3D() : model.meshes[0]; -} - -Model AssimpLoader::load_model(const std::string& path) { - Model model; - model.path = path; - - std::string path_str = path; - - std::size_t pos = path_str.find("//"); - if (pos != std::string::npos && pos + 2 < path_str.size()) { - path_str = path_str.substr(pos + 2); - } - - std::string base_dir = ASSETS_PATH; - - size_t last_slash = path_str.find_last_of("/\\"); - if (last_slash != std::string::npos) { - base_dir += path_str.substr(0, last_slash + 1); - } - - spdlog::info("Loading model: {} (base_dir: {})", path, base_dir); - - auto importer = std::make_shared(); - std::string ext = path_str.substr(path_str.find_last_of('.') + 1); - - auto ioSystem = new SDLIOSystem(base_dir); - importer->SetIOHandler(ioSystem); - - constexpr auto ASSIMP_FLAGS = aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_JoinIdenticalVertices | aiProcess_GenSmoothNormals - | aiProcess_OptimizeMeshes | aiProcess_OptimizeGraph; - - std::string filename = (last_slash != std::string::npos) ? path_str.substr(last_slash + 1) : path_str; - - const aiScene* scene = importer->ReadFile(filename, ASSIMP_FLAGS); - - - spdlog::info(" Meshes: {}, Materials: {}, Animations: {}", scene->mNumMeshes, scene->mNumMaterials, scene->mNumAnimations); - - const auto renderer = GEngine->get_renderer(); - - if (!renderer) { - spdlog::error("Renderer not initialized, cannot load model."); - exit(EXIT_FAILURE); - } - - for (unsigned int i = 0; i < scene->mNumMeshes; ++i) { - aiMesh* aiMesh = scene->mMeshes[i]; - spdlog::info(" Parsing Mesh({}) - Name {}", i, aiMesh->mName.C_Str()); - - MeshInstance3D mesh = create_mesh(aiMesh); - mesh.name = aiMesh->mName.C_Str(); - model.meshes.push_back(mesh); - - - Material material = load_material(scene, aiMesh, base_dir, *renderer); - model.materials.push_back(material); - spdlog::info(" Material [{}]: Albedo ({:.2f},{:.2f},{:.2f}) | Metallic {:.2f} | Roughness {:.2f} | AO {:.2f}", - aiMesh->mName.C_Str(), material.albedo.r, material.albedo.g, material.albedo.b, material.metallic, material.roughness, - material.ao); - - if (aiMesh->HasBones()) { - parse_bones(aiMesh, model); - } - } - - if (scene->HasAnimations()) { - parse_animations(scene, model); - } - - renderer->_meshes[path] = model.meshes; - renderer->_materials[path] = model.materials; - - return model; -} - -std::string AssimpLoader::get_directory(const std::string& path) { - size_t found = path.find_last_of("/\\"); - return (found != std::string::npos) ? path.substr(0, found + 1) : ""; -} - -MeshInstance3D AssimpLoader::create_mesh(aiMesh* aiMesh) { - std::vector vertices; - std::vector indices; - vertices.reserve(aiMesh->mNumVertices); - - for (unsigned int i = 0; i < aiMesh->mNumVertices; ++i) { - Vertex vertex; - - // Position - vertex.position = {aiMesh->mVertices[i].x, aiMesh->mVertices[i].y, aiMesh->mVertices[i].z}; - - // Normal - if (aiMesh->HasNormals()) { - vertex.normal = {aiMesh->mNormals[i].x, aiMesh->mNormals[i].y, aiMesh->mNormals[i].z}; - } else { - vertex.normal = {0.0f, 0.0f, 0.0f}; - } - - // UV - if (aiMesh->mTextureCoords[0]) { - vertex.uv = {aiMesh->mTextureCoords[0][i].x, aiMesh->mTextureCoords[0][i].y}; - } else { - vertex.uv = {0.0f, 0.0f}; - } - - vertices.push_back(vertex); - } - - // Process indices - for (unsigned int i = 0; i < aiMesh->mNumFaces; ++i) { - const aiFace& face = aiMesh->mFaces[i]; - indices.insert(indices.end(), face.mIndices, face.mIndices + face.mNumIndices); - } - - MeshInstance3D mesh; - mesh.index_count = indices.size(); - - const auto renderer = GEngine->get_renderer(); - - mesh.vertex_buffer = renderer->allocate_gpu_buffer(GpuBufferType::VERTEX); - mesh.vertex_buffer->upload(vertices.data(), vertices.size() * sizeof(Vertex)); - - mesh.index_buffer = renderer->allocate_gpu_buffer(GpuBufferType::INDEX); - mesh.index_buffer->upload(indices.data(), indices.size() * sizeof(unsigned int)); - - std::vector attributes = {{0, 3, DataType::FLOAT, false, offsetof(Vertex, position)}, - {1, 3, DataType::FLOAT, false, offsetof(Vertex, normal)}, - {2, 2, DataType::FLOAT, false, offsetof(Vertex, uv)}}; - - mesh.vertex_layout = renderer->create_vertex_layout(mesh.vertex_buffer.get(), mesh.index_buffer.get(), attributes, sizeof(Vertex)); - - spdlog::info(" Mesh created: {} vertices, {} triangles", aiMesh->mNumVertices, indices.size() / 3); - return mesh; -} - -Material AssimpLoader::load_material(const aiScene* scene, aiMesh* mesh, const std::string& directory, Renderer& renderer) { - Material material; - if (scene->mNumMaterials <= mesh->mMaterialIndex) { - return material; - } - - aiMaterial* aiMat = scene->mMaterials[mesh->mMaterialIndex]; - load_colors(aiMat, material); - load_textures(aiMat, scene, directory, renderer, material); - return material; -} - -void AssimpLoader::load_colors(aiMaterial* aiMat, Material& mat) { - aiColor3D color(1, 1, 1); - aiMat->Get(AI_MATKEY_COLOR_DIFFUSE, color); - mat.albedo = {color.r, color.g, color.b}; - - if (aiColor3D emissive(0, 0, 0); aiMat->Get(AI_MATKEY_COLOR_EMISSIVE, emissive) == AI_SUCCESS) { - mat.emissive = {emissive.r, emissive.g, emissive.b}; - } - - aiMat->Get(AI_MATKEY_EMISSIVE_INTENSITY, mat.emissive_strength); - aiMat->Get(AI_MATKEY_METALLIC_FACTOR, mat.metallic); - aiMat->Get(AI_MATKEY_ROUGHNESS_FACTOR, mat.roughness); - aiMat->Get(AI_MATKEY_REFRACTI, mat.ior); - aiMat->Get(AI_MATKEY_SPECULAR_FACTOR, mat.specular); -} - -void AssimpLoader::load_textures(aiMaterial* aiMat, const aiScene* scene, const std::string& dir, Renderer& renderer, Material& mat) { - auto load_tex = [&](aiTextureType type, GLuint& id, bool& flag, const char* name) { - if (aiMat->GetTextureCount(type) == 0) { - return; - } - - aiString texPath; - aiMat->GetTexture(type, 0, &texPath); - std::string texStr = texPath.C_Str(); - - if (texStr.empty()) { - return; - } - - if (texStr[0] == '*') { - int texIndex = std::atoi(texStr.c_str() + 1); - if (texIndex >= 0 && texIndex < (int) scene->mNumTextures) { - const aiTexture* embedded = scene->mTextures[texIndex]; - if (!embedded) { - return; - } - - if (embedded->mHeight == 0) { - id = renderer.load_embedded_texture(reinterpret_cast(embedded->pcData), embedded->mWidth); - } else { - spdlog::error(" Unsupported embedded texture format for texture: {}", name); - return; - } - - flag = (id != 0); - if (flag) { - spdlog::info(" Embedded Texture loaded: {}", name); - } - return; - } - } - - std::string tex_dir = dir + texStr; - id = renderer.load_texture_from_file(tex_dir); - flag = (id != 0); - if (flag) { - spdlog::info(" Texture loaded [{}]: {}", name, tex_dir); - } - }; - - load_tex(aiTextureType_DIFFUSE, mat.albedo_map, mat.use_albedo_map, "albedo"); - load_tex(aiTextureType_NORMALS, mat.normal_map, mat.use_normal_map, "normal"); - load_tex(aiTextureType_METALNESS, mat.metallic_map, mat.use_metallic_map, "metallic"); - load_tex(aiTextureType_DIFFUSE_ROUGHNESS, mat.roughness_map, mat.use_roughness_map, "roughness"); - load_tex(aiTextureType_AMBIENT_OCCLUSION, mat.ao_map, mat.use_ao_map, "ao"); - load_tex(aiTextureType_EMISSIVE, mat.emissive_map, mat.use_emissive_map, "emissive"); - - mat.update_feature_flags(); -} - -void AssimpLoader::parse_bones(aiMesh* mesh, Model& model) { - spdlog::info(" Bones: {}", mesh->mNumBones); - for (unsigned int i = 0; i < mesh->mNumBones; ++i) { - aiBone* bone = mesh->mBones[i]; - spdlog::info(" - Bone {}: {}", i, bone->mName.C_Str()); - } -} - -void AssimpLoader::parse_animations(const aiScene* scene, Model& model) { - spdlog::info(" Animations Count: {}", scene->mNumAnimations); - for (unsigned int i = 0; i < scene->mNumAnimations; ++i) { - const aiAnimation* anim = scene->mAnimations[i]; - spdlog::info(" Animation {}: {} | Duration: {:.2f} | FPS: {}", i, anim->mName.C_Str(), anim->mDuration, anim->mTicksPerSecond); - } -} diff --git a/engine/private/core/utility/project_config.cpp b/engine/private/core/utility/project_config.cpp deleted file mode 100644 index 1f3cf05ee..000000000 --- a/engine/private/core/utility/project_config.cpp +++ /dev/null @@ -1,375 +0,0 @@ -#include "core/utility/project_config.h" - -// ----------------------------------------------------------------------------- -// Viewport -// ----------------------------------------------------------------------------- -bool Viewport::load(const tinyxml2::XMLElement* root) { - - if (!root) { - spdlog::error("Failed to load Viewport Config - root element is null"); - return false; - } - - const auto viewport_element = root->FirstChildElement("viewport"); - - if (!viewport_element) { - spdlog::error("Failed to load Viewport Config - viewport element is null"); - return false; - } - - if (const auto size = viewport_element->FirstChildElement("size")) { - size->QueryIntAttribute("width", &width); - size->QueryIntAttribute("height", &height); - } else { - spdlog::error("Failed to load Viewport Config - size element is null"); - return false; - } - - if (const auto stretch = viewport_element->FirstChildElement("stretch")) { - - if (const char* mode_xml = stretch->Attribute("mode")) { - if (strcmp(mode_xml, "viewport") == 0) { - mode = ViewportMode::VIEWPORT; - } else if (strcmp(mode_xml, "canvas") == 0) { - mode = ViewportMode::CANVAS; - } else { - spdlog::error("Unknown viewport mode: {}", mode_xml); - return false; - } - } - - } else { - spdlog::error("Failed to load Viewport Config - stretch element is null"); - return false; - } - - viewport_element->QueryFloatAttribute("scale", &scale); - - return true; -} - -// ----------------------------------------------------------------------------- -// Environment -// ----------------------------------------------------------------------------- -bool Environment::load(const tinyxml2::XMLElement* root) { - - if (!root) { - spdlog::error("Failed to load Environment Config - root element is null"); - return false; - } - - const auto environment_element = root->FirstChildElement("environment"); - - if (!environment_element) { - spdlog::error("Failed to load Environment Config - environment element is null"); - return false; - } - - const auto clear_color_element = environment_element->FirstChildElement("clear_color"); - - if (clear_color_element) { - clear_color.x = clear_color_element->FloatAttribute("r", 0.0f); - clear_color.y = clear_color_element->FloatAttribute("g", 0.0f); - clear_color.z = clear_color_element->FloatAttribute("b", 0.0f); - clear_color.w = clear_color_element->FloatAttribute("a", 1.0f); - } else { - spdlog::error("Failed to load Environment Config - clear_color element is null"); - return false; - } - - return true; -} - -// ----------------------------------------------------------------------------- -// Application -// ----------------------------------------------------------------------------- -bool Application::load(const tinyxml2::XMLElement* root) { - - if (!root) { - spdlog::error("Failed to load Application Config - root element is null"); - return false; - } - - const auto app_element = root->FirstChildElement("application"); - - if (!app_element) { - spdlog::error("Failed to load Application Config - application element is null"); - return false; - } - - if (const auto name_element = app_element->FirstChildElement("name")) { - name = name_element->GetText(); - } else { - spdlog::warn("Failed to load Application Config - name element is null"); - } - - if (const auto version_element = app_element->FirstChildElement("version")) { - version = version_element->GetText(); - } else { - spdlog::warn("Failed to load Application Config - version element is null"); - } - - if (const auto identifier_element = app_element->FirstChildElement("identifier")) { - package_name = identifier_element->GetText(); - } else { - spdlog::warn("Failed to load Application Config - identifier element is null"); - } - - if (const auto icon_element = app_element->FirstChildElement("icon")) { - icon_path = icon_element->GetText(); - } else { - spdlog::warn("Failed to load Application Config - icon element is null"); - } - - if (const auto description_element = app_element->FirstChildElement("description")) { - description = description_element->GetText(); - } else { - spdlog::warn("Failed to load Application Config - description element is null"); - } - - if (const auto max_fps_element = app_element->FirstChildElement("max_fps")) { - max_fps_element->QueryIntText(&max_fps); - } else { - spdlog::warn("Failed to load Application Config - max_fps element is null"); - } - - if (const auto fullscreen_element = app_element->FirstChildElement("fullscreen")) { - fullscreen_element->QueryBoolText(&is_fullscreen); - } else { - spdlog::warn("Failed to load Application Config - fullscreen element is null"); - } - - if (const auto resizable_element = app_element->FirstChildElement("resizable")) { - resizable_element->QueryBoolText(&is_resizable); - } else { - spdlog::warn("Failed to load Application Config - resizable element is null"); - } - - return true; -} - -// ----------------------------------------------------------------------------- -// EngineConfig orientation -// ----------------------------------------------------------------------------- -const char* EngineConfig::get_orientation_str() const { - switch (orientation) { - case Orientation::LANDSCAPE_LEFT: - return "LandscapeLeft"; - case Orientation::LANDSCAPE_RIGHT: - return "LandscapeRight"; - case Orientation::PORTRAIT: - return "Portrait"; - case Orientation::PORTRAIT_UPSIDE_DOWN: - return "PortraitUpsideDown"; - default: - return "Portrait"; - } -} - -// ----------------------------------------------------------------------------- -// Performance -// ----------------------------------------------------------------------------- -bool Performance::load(const tinyxml2::XMLElement* root) { - const auto performance_element = root->FirstChildElement("performance"); - - if (const auto multithread_element = performance_element->FirstChildElement("multithreading")) { - multithread_element->QueryBoolText(&is_multithreaded); - } else { - spdlog::error("Failed to load Performance Config - multithreading element is null"); - return false; - } - - if (const auto worker_threads_element = performance_element->FirstChildElement("worker_threads")) { - worker_threads_element->QueryIntText(&worker_threads); - } else { - spdlog::error("Failed to load Performance Config - worker_threads element is null"); - return false; - } - - if (const auto physics_fps_element = performance_element->FirstChildElement("physics_fps")) { - physics_fps_element->QueryIntText(&physics_fps); - } else { - spdlog::error("Failed to load Performance Config - physics_fps element is null"); - return false; - } - - return true; -} - -// ----------------------------------------------------------------------------- -// RendererDevice -// ----------------------------------------------------------------------------- -bool RendererDevice::load(const tinyxml2::XMLElement* root) { - - const auto renderer_element = root->FirstChildElement("renderer"); - - if (const auto method_element = renderer_element->FirstChildElement("method")) { - const char* method_str = method_element->GetText(); - if (strcmp(method_str, "gl_compatibility") == 0) { - backend = Backend::GL_COMPATIBILITY; - } else if (strcmp(method_str, "metal") == 0) { - backend = Backend::METAL; - } else if (strcmp(method_str, "vk_forward") == 0) { - backend = Backend::VK_FORWARD; - } else if (strcmp(method_str, "directx12") == 0) { - backend = Backend::DIRECTX12; - } else if (strcmp(method_str, "auto") == 0) { - backend = Backend::AUTO; - } else { - spdlog::error("Unknown renderer method: {}", method_str); - return false; - } - } else { - spdlog::error("Failed to load Renderer Config - method element is null"); - return false; - } - - if (const auto filtering_element = renderer_element->FirstChildElement("texture_filter")) { - const char* filtering_str = filtering_element->GetText(); - if (strcmp(filtering_str, "nearest") == 0) { - texture_filtering = TextureFiltering::NEAREST; - } else if (strcmp(filtering_str, "linear") == 0) { - texture_filtering = TextureFiltering::LINEAR; - } else { - spdlog::error("Unknown texture filtering method: {}", filtering_str); - return false; - } - } else { - spdlog::error("Failed to load Renderer Config - texture_filter element is null"); - return false; - } - - return true; -} - -const char* RendererDevice::get_backend_str() const { - switch (backend) { - case Backend::GL_COMPATIBILITY: - return "OpenGL/ES 3.3 Compatibility"; - case Backend::METAL: - return "Metal"; - case Backend::VK_FORWARD: - return "Vulkan"; - case Backend::DIRECTX12: - return "DirectX 12"; - case Backend::AUTO: - return "Auto (SDL_RENDERER_GPU)"; - default: - return "Unknown"; - } -} - -// ----------------------------------------------------------------------------- -// Window -// ----------------------------------------------------------------------------- -bool Window::load(const tinyxml2::XMLElement* root) { - return true; -} - -void Window::resize(int w, int h) { - width = w; - height = h; -} - -// ----------------------------------------------------------------------------- -// EngineConfig -// ----------------------------------------------------------------------------- -bool EngineConfig::load() { - - const auto file = load_file_into_memory("project.xml"); - - if (_doc.Parse(file.data(), file.size()) != tinyxml2::XML_SUCCESS) { - spdlog::error("Failed to load Engine Config from {}, error: {}", "res/project.xml", _doc.ErrorStr()); - return false; - } - - const tinyxml2::XMLElement* config = _doc.FirstChildElement("config"); - - if (!config) { - spdlog::error("Failed to load Engine Config - config element is null"); - return false; - } - - if (const auto vsync_element = config->FirstChildElement("vsync")) { - vsync_element->QueryBoolText(&_is_vsync_enabled); - } - - if (const auto orientation_element = config->FirstChildElement("orientation")) { - if (const char* orientation_str = orientation_element->GetText()) { - if (strcmp(orientation_str, "landscape_left") == 0) { - orientation = Orientation::LANDSCAPE_LEFT; - } else if (strcmp(orientation_str, "landscape_right") == 0) { - orientation = Orientation::LANDSCAPE_RIGHT; - } else if (strcmp(orientation_str, "portrait") == 0) { - orientation = Orientation::PORTRAIT; - } else if (strcmp(orientation_str, "portrait_upside_down") == 0) { - orientation = Orientation::PORTRAIT_UPSIDE_DOWN; - } else { - spdlog::error("Unknown orientation type: {}", orientation_str); - return false; - } - } - } - - if (!_viewport.load(config)) { - spdlog::error("Failed to load Viewport Config"); - return false; - } - - if (!_environment.load(config)) { - spdlog::error("Failed to load Environment Config"); - return false; - } - - if (!_app.load(config)) { - spdlog::error("Failed to load Application Config"); - return false; - } - - if (!_renderer_device.load(config)) { - spdlog::error("Failed to load Renderer Device"); - return false; - } - - if (!_performance.load(config)) { - spdlog::error("Failed to load Performance Config"); - return false; - } - - return true; -} - -// ----------------------------------------------------------------------------- -// EngineConfig getters/setters -// ----------------------------------------------------------------------------- -RendererDevice& EngineConfig::get_renderer_device() { - return _renderer_device; -} - -Performance& EngineConfig::get_performance() { - return _performance; -} - -Application& EngineConfig::get_application() { - return _app; -} - -Environment& EngineConfig::get_environment() { - return _environment; -} - -Viewport& EngineConfig::get_viewport() { - return _viewport; -} - -Window& EngineConfig::get_window() { - return _window; -} - -bool EngineConfig::is_vsync() const { - return _is_vsync_enabled; -} - -void EngineConfig::set_vsync(bool enabled) { - _is_vsync_enabled = enabled; -} diff --git a/engine/private/drivers/gles3/rendering_device_gles3.cpp b/engine/private/drivers/gles3/rendering_device_gles3.cpp new file mode 100644 index 000000000..bc227b086 --- /dev/null +++ b/engine/private/drivers/gles3/rendering_device_gles3.cpp @@ -0,0 +1,945 @@ +#include "drivers/gles3/rendering_device_gles3.h" + +#include "spdlog/spdlog.h" + + +RenderingDeviceGLES3::~RenderingDeviceGLES3() { + shutdown(); +} + +bool RenderingDeviceGLES3::initialize(SDL_Window* sdl_window) { + + +#if defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_EMSCRIPTEN) + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + +#else + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + + #if defined(SDL_PLATFORM_MACOS) + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // OSX compatibility Bit + #endif + + +#endif + + gl_context = SDL_GL_CreateContext(sdl_window); + + if (!gl_context) { + spdlog::critical("Failed to create OpenGL/ES context: {}", SDL_GetError()); + return false; + } + + +#if defined(SDL_PLATFORM_ANDROID) && !defined(SDL_PLATFORM_IOS) && !defined(SDL_PLATFORM_EMSCRIPTEN) + if (!gladLoadGLES2Loader(reinterpret_cast(SDL_GL_GetProcAddress))) { + spdlog::error("Failed to initialize OpenGL Loader (GLAD)"); + return false; + } +#else + if (!gladLoadGLLoader(reinterpret_cast(SDL_GL_GetProcAddress))) { + spdlog::error("Failed to initialize OpenGLES Loader (GLAD)"); + return false; + } +#endif + + SDL_GL_SetSwapInterval(1); // TODO: make configurable (vsync on/off) + + glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + _window = sdl_window; + + int major,minor; + SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &major); + SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &minor); + spdlog::info("Initialized OpenGL/ES context version {}.{}", major, minor); + + const char* vendor = (const char*) glGetString(GL_VENDOR); + const char* renderer = (const char*) glGetString(GL_RENDERER); + + spdlog::info("GPU Vendor: {}", vendor); + spdlog::info("GPU Renderer: {}", renderer); + + return true; +} + +void RenderingDeviceGLES3::shutdown() { + for (auto& [id, shader] : shaders) { + glDeleteProgram((GLuint) shader.program); + } + + for (auto& [id, buffer] : buffers) { + GLuint buf = (GLuint) buffer.handle; + glDeleteBuffers(1, &buf); + } + + for (auto& [id, texture] : textures) { + GLuint tex = (GLuint) texture.handle; + glDeleteTextures(1, &tex); + } + + for (auto& [id, sampler] : samplers) { + GLuint samp = (GLuint) sampler.handle; + glDeleteSamplers(1, &samp); + } + + for (auto& [id, fb] : framebuffers) { + GLuint fbo = (GLuint) fb.handle; + glDeleteFramebuffers(1, &fbo); + } + + for (auto& [id, pipeline] : pipelines) { + GLuint vao = (GLuint) pipeline.handle; + glDeleteVertexArrays(1, &vao); + } + + SDL_GL_DestroyContext(gl_context); +} + +GLuint RenderingDeviceGLES3::compile_shader(GLenum type, const String& source) { + GLuint shader = glCreateShader(type); + const char* src = source.c_str(); + glShaderSource(shader, 1, &src, nullptr); + glCompileShader(shader); + + GLint success; + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + + if (!success) { + char log[512]; + glGetShaderInfoLog(shader, 512, nullptr, log); + spdlog::error("Shader compilation error: {}", log); + glDeleteShader(shader); + return 0; + } + + return shader; +} + +RID RenderingDeviceGLES3::shader_create_from_source(const String& vertex_src, const String& fragment_src) { + GLuint vs = compile_shader(GL_VERTEX_SHADER, vertex_src); + GLuint fs = compile_shader(GL_FRAGMENT_SHADER, fragment_src); + + if (!vs || !fs) { + return INVALID_RID; + } + + GLuint program = glCreateProgram(); + glAttachShader(program, vs); + glAttachShader(program, fs); + glLinkProgram(program); + + GLint success; + glGetProgramiv(program, GL_LINK_STATUS, &success); + if (!success) { + char log[512]; + glGetProgramInfoLog(program, 512, nullptr, log); + spdlog::error("Shader linking error: {}", log); + glDeleteProgram(program); + glDeleteShader(vs); + glDeleteShader(fs); + return INVALID_RID; + } + + glDeleteShader(vs); + glDeleteShader(fs); + + RID rid = allocate_rid(); + shaders[rid] = ShaderModule{program}; + return rid; +} + +void RenderingDeviceGLES3::shader_destroy(RID shader) { + auto it = shaders.find(shader); + if (it != shaders.end()) { + glDeleteProgram((GLuint) it->second.program); + shaders.erase(it); + } +} + +RID RenderingDeviceGLES3::buffer_create(size_t size, uint32_t usage_flags, const void* data) { + GLuint buffer; + glGenBuffers(1, &buffer); + + GLenum target = (usage_flags & (uint32_t) BufferUsage::BUFFER_USAGE_UNIFORM) ? GL_UNIFORM_BUFFER : GL_ARRAY_BUFFER; + if (usage_flags & (uint32_t) BufferUsage::BUFFER_USAGE_INDEX) { + target = GL_ELEMENT_ARRAY_BUFFER; + } + + glBindBuffer(target, buffer); + glBufferData(target, size, data, GL_DYNAMIC_DRAW); + glBindBuffer(target, 0); + + RID rid = allocate_rid(); + buffers[rid] = Buffer{buffer, size, usage_flags, (uint32_t) target}; + return rid; +} + +void RenderingDeviceGLES3::buffer_update(RID buffer, size_t offset, size_t size, const void* data) { + auto it = buffers.find(buffer); + if (it != buffers.end()) { + glBindBuffer((GLenum) it->second.target, (GLuint) it->second.handle); + glBufferSubData((GLenum) it->second.target, offset, size, data); + glBindBuffer((GLenum) it->second.target, 0); + } +} + +void RenderingDeviceGLES3::buffer_destroy(RID buffer) { + auto it = buffers.find(buffer); + if (it != buffers.end()) { + GLuint buf = (GLuint) it->second.handle; + glDeleteBuffers(1, &buf); + buffers.erase(it); + } +} + +GLenum RenderingDeviceGLES3::to_gl_format(DataFormat format, bool* is_int) { + if (is_int) { + *is_int = false; + } + + switch (format) { + case DataFormat::R8_UNORM: + return GL_R8; + case DataFormat::R8G8_UNORM: + return GL_RG8; + case DataFormat::R8G8B8_UNORM: + return GL_RGB8; + case DataFormat::R8G8B8A8_UNORM: + return GL_RGBA8; + case DataFormat::R32_SFLOAT: + return GL_R32F; + case DataFormat::R32G32_SFLOAT: + return GL_RG32F; + case DataFormat::R32G32B32_SFLOAT: + return GL_RGB32F; + case DataFormat::R32G32B32A32_SFLOAT: + return GL_RGBA32F; + case DataFormat::D24_UNORM_S8_UINT: + return GL_DEPTH24_STENCIL8; + case DataFormat::D32_SFLOAT: + return GL_DEPTH_COMPONENT32F; + default: + return GL_RGBA8; + } +} + +RID RenderingDeviceGLES3::texture_create(const TextureFormat& format, void* data) { + GLuint texture; + glGenTextures(1, &texture); + + GLenum target = GL_TEXTURE_2D; + if (format.type == TextureType::TEXTURE_TYPE_CUBEMAP) { + target = GL_TEXTURE_CUBE_MAP; + } else if (format.type == TextureType::TEXTURE_TYPE_2D_ARRAY) { + target = GL_TEXTURE_2D_ARRAY; + } else if (format.type == TextureType::TEXTURE_TYPE_3D) { + target = GL_TEXTURE_3D; + } + + glBindTexture(target, texture); + + GLenum internal_format = to_gl_format(format.format); + GLenum pixel_format = GL_RGBA; + GLenum pixel_type = GL_UNSIGNED_BYTE; + + if (format.format == DataFormat::R8G8B8_UNORM) { + internal_format = GL_RGB8; + pixel_format = GL_RGB; + } else if (format.format == DataFormat::R8G8_UNORM) { + internal_format = GL_RG8; + pixel_format = GL_RG; + } else if (format.format == DataFormat::R8_UNORM) { + internal_format = GL_R8; + pixel_format = GL_RED; + } else if (format.format == DataFormat::R8G8B8A8_UNORM) { + internal_format = GL_RGBA8; + pixel_format = GL_RGBA; + } + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + if (format.type == TextureType::TEXTURE_TYPE_2D) { + if (data == nullptr) { + printf("ERROR: texture_create called with null data!\n"); + glTexImage2D(GL_TEXTURE_2D, 0, internal_format, format.width, format.height, 0, pixel_format, pixel_type, nullptr); + } else { + glTexImage2D(GL_TEXTURE_2D, 0, internal_format, format.width, format.height, 0, pixel_format, pixel_type, data); + } + } + + + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_REPEAT); + glBindTexture(target, 0); + + RID rid = allocate_rid(); + textures[rid] = Texture{rid, texture, format}; + + return rid; +} + +void RenderingDeviceGLES3::texture_destroy(RID texture) { + auto it = textures.find(texture); + if (it != textures.end()) { + GLuint tex = (GLuint) it->second.handle; + glDeleteTextures(1, &tex); + textures.erase(it); + } +} + +void RenderingDeviceGLES3::get_texture_size(RID texture, uint32_t& width, uint32_t& height) { + auto it = textures.find(texture); + if (it != textures.end()) { + width = it->second.format.width; + height = it->second.format.height; + } else { + width = 0; + height = 0; + } +} + +uint32_t RenderingDeviceGLES3::texture_get_native_handle(RID texture) { + auto it = textures.find(texture); + if (it != textures.end()) { + return it->second.handle; + } + return 0; +} + +Texture RenderingDeviceGLES3::get_texture(RID texture) { + auto it = textures.find(texture); + if (it != textures.end()) { + return it->second; + } + return Texture{}; // Returns empty texture +} + +RID RenderingDeviceGLES3::pipeline_create(const PipelineState& state) { + GLuint vao; + glGenVertexArrays(1, &vao); + + RID rid = allocate_rid(); + pipelines[rid] = Pipeline{state, vao}; + return rid; +} + +void RenderingDeviceGLES3::pipeline_destroy(RID pipeline) { + auto it = pipelines.find(pipeline); + if (it != pipelines.end()) { + GLuint vao = (GLuint) it->second.handle; + glDeleteVertexArrays(1, &vao); + pipelines.erase(it); + } +} + +void RenderingDeviceGLES3::bind_pipeline(RID pipeline) { + auto it = pipelines.find(pipeline); + if (it == pipelines.end()) { + return; + } + + current_pipeline = pipeline; + const auto& pipe = it->second; + + glBindVertexArray((GLuint) pipe.handle); + + auto shader_it = shaders.find(pipe.state.shader); + if (shader_it != shaders.end()) { + glUseProgram(shader_it->second.program); + } + + apply_rasterization_state(pipe.state.rasterization); + apply_depth_stencil_state(pipe.state.depth_stencil); + apply_blend_state(pipe.state.blend_states); +} + +void RenderingDeviceGLES3::apply_rasterization_state(const RasterizationState& state) { + if (state.cull_mode == CullMode::NONE) { + glDisable(GL_CULL_FACE); + } else { + glEnable(GL_CULL_FACE); + GLenum mode = (state.cull_mode == CullMode::FRONT) ? GL_FRONT : (state.cull_mode == CullMode::BACK) ? GL_BACK : GL_FRONT_AND_BACK; + glCullFace(mode); + } + glFrontFace(state.front_face_ccw ? GL_CCW : GL_CW); +} + +void RenderingDeviceGLES3::apply_depth_stencil_state(const DepthStencilState& state) { + if (state.depth_test_enable) { + glEnable(GL_DEPTH_TEST); + glDepthFunc(to_gl_compare(state.depth_compare_op)); + glDepthMask(state.depth_write_enable ? GL_TRUE : GL_FALSE); + } else { + glDisable(GL_DEPTH_TEST); + } +} + +void RenderingDeviceGLES3::apply_blend_state(const Vector& states) { + + if (states.empty() || !states[0].enable) { + glDisable(GL_BLEND); + } else { + glEnable(GL_BLEND); + const auto& blend = states[0]; + glBlendFuncSeparate(to_gl_blend_factor(blend.src_color), to_gl_blend_factor(blend.dst_color), to_gl_blend_factor(blend.src_alpha), + to_gl_blend_factor(blend.dst_alpha)); + glBlendEquationSeparate(to_gl_blend_op(blend.color_op), to_gl_blend_op(blend.alpha_op)); + } +} + +GLenum RenderingDeviceGLES3::to_gl_compare(CompareOp op) { + switch (op) { + case CompareOp::NEVER: + return GL_NEVER; + case CompareOp::LESS: + return GL_LESS; + case CompareOp::EQUAL: + return GL_EQUAL; + case CompareOp::LESS_OR_EQUAL: + return GL_LEQUAL; + case CompareOp::GREATER: + return GL_GREATER; + case CompareOp::NOT_EQUAL: + return GL_NOTEQUAL; + case CompareOp::GREATER_OR_EQUAL: + return GL_GEQUAL; + case CompareOp::ALWAYS: + return GL_ALWAYS; + default: + return GL_LESS; + } +} + +GLenum RenderingDeviceGLES3::to_gl_blend_factor(BlendFactor factor) { + switch (factor) { + case BlendFactor::ZERO: + return GL_ZERO; + case BlendFactor::ONE: + return GL_ONE; + case BlendFactor::SRC_COLOR: + return GL_SRC_COLOR; + case BlendFactor::ONE_MINUS_SRC_COLOR: + return GL_ONE_MINUS_SRC_COLOR; + case BlendFactor::DST_COLOR: + return GL_DST_COLOR; + case BlendFactor::ONE_MINUS_DST_COLOR: + return GL_ONE_MINUS_DST_COLOR; + case BlendFactor::SRC_ALPHA: + return GL_SRC_ALPHA; + case BlendFactor::ONE_MINUS_SRC_ALPHA: + return GL_ONE_MINUS_SRC_ALPHA; + case BlendFactor::DST_ALPHA: + return GL_DST_ALPHA; + case BlendFactor::ONE_MINUS_DST_ALPHA: + return GL_ONE_MINUS_DST_ALPHA; + default: + return GL_ONE; + } +} + +GLenum RenderingDeviceGLES3::to_gl_blend_op(BlendOp op) { + switch (op) { + case BlendOp::ADD: + return GL_FUNC_ADD; + case BlendOp::SUBTRACT: + return GL_FUNC_SUBTRACT; + case BlendOp::REVERSE_SUBTRACT: + return GL_FUNC_REVERSE_SUBTRACT; + default: + return GL_FUNC_ADD; + } +} + +void RenderingDeviceGLES3::bind_vertex_buffers(const Vector& buffer_rids, const Vector& offsets) { + if (current_pipeline == INVALID_RID) { + return; + } + + auto pipe_it = pipelines.find(current_pipeline); + if (pipe_it == pipelines.end()) { + return; + } + + const auto& vertex_format = pipe_it->second.state.vertex_format; + + for (size_t i = 0; i < buffer_rids.size(); ++i) { + auto buf_it = buffers.find(buffer_rids[i]); + if (buf_it == buffers.end()) { + continue; + } + + glBindBuffer(GL_ARRAY_BUFFER, (GLuint) buf_it->second.handle); + + for (const auto& attr : vertex_format.attributes) { + glEnableVertexAttribArray(attr.location); + + GLint size = 4; + GLenum type = GL_FLOAT; + GLboolean normalized = GL_FALSE; + + switch (attr.format) { + case DataFormat::R8_UNORM: + size = 1; + type = GL_UNSIGNED_BYTE; + normalized = GL_TRUE; + break; + case DataFormat::R8G8_UNORM: + size = 2; + type = GL_UNSIGNED_BYTE; + normalized = GL_TRUE; + break; + case DataFormat::R8G8B8_UNORM: + size = 3; + type = GL_UNSIGNED_BYTE; + normalized = GL_TRUE; + break; + case DataFormat::R8G8B8A8_UNORM: + size = 4; + type = GL_UNSIGNED_BYTE; + normalized = GL_TRUE; + break; + case DataFormat::R16_SFLOAT: + size = 1; + type = GL_HALF_FLOAT; + normalized = GL_FALSE; + break; + case DataFormat::R16G16_SFLOAT: + size = 2; + type = GL_HALF_FLOAT; + normalized = GL_FALSE; + break; + case DataFormat::R16G16B16A16_SFLOAT: + size = 4; + type = GL_HALF_FLOAT; + normalized = GL_FALSE; + break; + case DataFormat::R32_SFLOAT: + size = 1; + type = GL_FLOAT; + normalized = GL_FALSE; + break; + case DataFormat::R32G32_SFLOAT: + size = 2; + type = GL_FLOAT; + normalized = GL_FALSE; + break; + case DataFormat::R32G32B32_SFLOAT: + size = 3; + type = GL_FLOAT; + normalized = GL_FALSE; + break; + case DataFormat::R32G32B32A32_SFLOAT: + size = 4; + type = GL_FLOAT; + normalized = GL_FALSE; + break; + default: + size = 4; + type = GL_FLOAT; + normalized = GL_FALSE; + break; + } + + size_t offset = attr.offset + (i < offsets.size() ? offsets[i] : 0); + glVertexAttribPointer(attr.location, size, type, normalized, vertex_format.stride, (void*) offset); + } + } +} + +void RenderingDeviceGLES3::bind_index_buffer(RID buffer, IndexType type, size_t offset) { + auto it = buffers.find(buffer); + if (it != buffers.end()) { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (GLuint) it->second.handle); + current_index_type = type; + } +} + +void RenderingDeviceGLES3::draw(uint32_t vertex_count, uint32_t instance_count, uint32_t first_vertex, uint32_t first_instance) { + if (current_pipeline == INVALID_RID) { + return; + } + auto it = pipelines.find(current_pipeline); + if (it == pipelines.end()) { + return; + } + + GLenum mode = to_gl_topology(it->second.state.topology); + glDrawArrays(mode, first_vertex, vertex_count); +} + +void RenderingDeviceGLES3::draw_indexed(uint32_t index_count, uint32_t instance_count, uint32_t first_index, int32_t vertex_offset, + uint32_t first_instance) { + if (current_pipeline == INVALID_RID) { + return; + } + auto it = pipelines.find(current_pipeline); + if (it == pipelines.end()) { + return; + } + + GLenum mode = to_gl_topology(it->second.state.topology); + GLenum type = (current_index_type == IndexType::UINT16) ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT; + size_t offset = first_index * (type == GL_UNSIGNED_SHORT ? 2 : 4); + glDrawElements(mode, index_count, type, (void*) offset); +} + +GLenum RenderingDeviceGLES3::to_gl_topology(PrimitiveTopology topology) { + switch (topology) { + case PrimitiveTopology::POINTS: + return GL_POINTS; + case PrimitiveTopology::LINES: + return GL_LINES; + case PrimitiveTopology::LINE_STRIP: + return GL_LINE_STRIP; + case PrimitiveTopology::TRIANGLES: + return GL_TRIANGLES; + case PrimitiveTopology::TRIANGLE_STRIP: + return GL_TRIANGLE_STRIP; + case PrimitiveTopology::TRIANGLE_FAN: + return GL_TRIANGLE_FAN; + default: + return GL_TRIANGLES; + } +} + +void RenderingDeviceGLES3::bind_uniform_buffer(uint32_t binding, RID buffer, size_t offset, size_t size) { + auto it = buffers.find(buffer); + if (it != buffers.end()) { + if (size == 0) { + size = it->second.size - offset; + } + glBindBufferRange(GL_UNIFORM_BUFFER, binding, (GLuint) it->second.handle, offset, size); + } +} + +void RenderingDeviceGLES3::bind_texture(uint32_t binding, RID texture, RID sampler) { + auto tex_it = textures.find(texture); + if (tex_it != textures.end()) { + glActiveTexture(GL_TEXTURE0 + binding); + GLenum target = GL_TEXTURE_2D; + if (tex_it->second.format.type == TextureType::TEXTURE_TYPE_CUBEMAP) { + target = GL_TEXTURE_CUBE_MAP; + } + + glBindTexture(target, (GLuint) tex_it->second.handle); + + if (sampler != INVALID_RID) { + auto samp_it = samplers.find(sampler); + if (samp_it != samplers.end()) { + glBindSampler(binding, (GLuint) samp_it->second.handle); + } + } + } +} + +RID RenderingDeviceGLES3::sampler_create(const SamplerState& state) { + GLuint sampler; + glGenSamplers(1, &sampler); + + glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, to_gl_filter(state.min_filter)); + glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, to_gl_filter(state.mag_filter)); + glSamplerParameteri(sampler, GL_TEXTURE_WRAP_S, to_gl_wrap(state.wrap_u)); + glSamplerParameteri(sampler, GL_TEXTURE_WRAP_T, to_gl_wrap(state.wrap_v)); + glSamplerParameteri(sampler, GL_TEXTURE_WRAP_R, to_gl_wrap(state.wrap_w)); + + if (state.compare_enabled) { + glSamplerParameteri(sampler, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); + glSamplerParameteri(sampler, GL_TEXTURE_COMPARE_FUNC, to_gl_compare(state.compare_op)); + } + + RID rid = allocate_rid(); + samplers[rid] = Sampler{sampler, state}; + return rid; +} + +void RenderingDeviceGLES3::sampler_destroy(RID sampler) { + auto it = samplers.find(sampler); + if (it != samplers.end()) { + GLuint samp = (GLuint) it->second.handle; + glDeleteSamplers(1, &samp); + samplers.erase(it); + } +} + +GLenum RenderingDeviceGLES3::to_gl_filter(TextureFilter filter) { + switch (filter) { + case TextureFilter::NEAREST: + return GL_NEAREST; + case TextureFilter::LINEAR: + return GL_LINEAR; + case TextureFilter::NEAREST_MIPMAP_NEAREST: + return GL_NEAREST_MIPMAP_NEAREST; + case TextureFilter::LINEAR_MIPMAP_NEAREST: + return GL_LINEAR_MIPMAP_NEAREST; + case TextureFilter::NEAREST_MIPMAP_LINEAR: + return GL_NEAREST_MIPMAP_LINEAR; + case TextureFilter::LINEAR_MIPMAP_LINEAR: + return GL_LINEAR_MIPMAP_LINEAR; + default: + return GL_LINEAR; + } +} + +GLenum RenderingDeviceGLES3::to_gl_wrap(TextureWrap wrap) { + switch (wrap) { + case TextureWrap::REPEAT: + return GL_REPEAT; + case TextureWrap::MIRRORED_REPEAT: + return GL_MIRRORED_REPEAT; + case TextureWrap::CLAMP_TO_EDGE: + return GL_CLAMP_TO_EDGE; + default: + return GL_REPEAT; + } +} + +RID RenderingDeviceGLES3::framebuffer_create(const Vector& attachments) { + GLuint fbo; + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + uint32_t width = 0, height = 0; + uint32_t color_attachment_idx = 0; + + for (const auto& attachment : attachments) { + auto tex_it = textures.find(attachment.texture); + if (tex_it == textures.end()) { + continue; + } + + const auto& tex = tex_it->second; + + if (width == 0) { + width = tex.format.width; + height = tex.format.height; + } + + GLenum attachment_point; + if (tex.format.depth_stencil) { + attachment_point = GL_DEPTH_STENCIL_ATTACHMENT; + } else { + attachment_point = GL_COLOR_ATTACHMENT0 + color_attachment_idx++; + } + + glFramebufferTexture2D(GL_FRAMEBUFFER, attachment_point, GL_TEXTURE_2D, (GLuint) tex.handle, attachment.mip_level); + } + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + if (status != GL_FRAMEBUFFER_COMPLETE) { + spdlog::error("Framebuffer incomplete: 0x{:x}", status); + glDeleteFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + return INVALID_RID; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + RID rid = allocate_rid(); + framebuffers[rid] = Framebuffer{fbo, attachments, width, height}; + return rid; +} + +void RenderingDeviceGLES3::framebuffer_destroy(RID framebuffer) { + auto it = framebuffers.find(framebuffer); + if (it != framebuffers.end()) { + GLuint fbo = (GLuint) it->second.handle; + glDeleteFramebuffers(1, &fbo); + framebuffers.erase(it); + } +} + +void RenderingDeviceGLES3::render_pass_begin(RID framebuffer, const Viewport& viewport, const Scissor& scissor) { + if (framebuffer == INVALID_RID) { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } else { + auto it = framebuffers.find(framebuffer); + if (it == framebuffers.end()) { + return; + } + + glBindFramebuffer(GL_FRAMEBUFFER, (GLuint) it->second.handle); + current_framebuffer = framebuffer; + + for (const auto& attachment : it->second.attachments) { + if (attachment.clear) { + auto tex_it = textures.find(attachment.texture); + if (tex_it != textures.end()) { + if (tex_it->second.format.depth_stencil) { + glClearDepth(attachment.clear_value.depth); + glClearStencil(attachment.clear_value.stencil); + glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + } else { + glClearColor(attachment.clear_value.color.r, attachment.clear_value.color.g, attachment.clear_value.color.b, + attachment.clear_value.color.a); + glClear(GL_COLOR_BUFFER_BIT); + } + } + } + } + } + + set_viewport(viewport); + set_scissor(scissor); +} + +void RenderingDeviceGLES3::render_pass_end() { + current_framebuffer = INVALID_RID; +} + +void RenderingDeviceGLES3::set_viewport(const Viewport& viewport) { + glViewport(static_cast(viewport.x), static_cast(viewport.y), static_cast(viewport.width), + static_cast(viewport.height)); + glDepthRangef(viewport.min_depth, viewport.max_depth); +} + +void RenderingDeviceGLES3::set_scissor(const Scissor& scissor) { + glEnable(GL_SCISSOR_TEST); + glScissor(scissor.x, scissor.y, scissor.width, scissor.height); +} + +void RenderingDeviceGLES3::clear_color(const glm::vec4& color) { + glClearColor(color.r, color.g, color.b, color.a); + glClear(GL_COLOR_BUFFER_BIT); +} + +void RenderingDeviceGLES3::clear_depth_stencil(float depth, uint32_t stencil) { + glClearDepth(depth); + glClearStencil(stencil); + glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); +} + +void RenderingDeviceGLES3::swap_buffers() { + SDL_GL_SwapWindow(_window); +} + +void RenderingDeviceGLES3::push_constant(const String& name, const void* data, size_t size) { + if (current_pipeline == INVALID_RID) { + return; + } + + auto pipe_it = pipelines.find(current_pipeline); + if (pipe_it == pipelines.end()) { + return; + } + + auto shader_it = shaders.find(pipe_it->second.state.shader); + if (shader_it == shaders.end()) { + return; + } + + auto& shader = shader_it->second; + GLint location = glGetUniformLocation(shader.program, name.c_str()); + + if (location == -1) { + return; + } + + if (size == sizeof(float)) { + glUniform1f(location, *static_cast(data)); + } else if (size == sizeof(glm::vec2)) { + glUniform2fv(location, 1, static_cast(data)); + } else if (size == sizeof(glm::vec3)) { + glUniform3fv(location, 1, static_cast(data)); + } else if (size == sizeof(glm::vec4)) { + glUniform4fv(location, 1, static_cast(data)); + } else if (size == sizeof(glm::mat4)) { + glUniformMatrix4fv(location, 1, GL_FALSE, static_cast(data)); + } else if (size == sizeof(glm::mat3)) { + glUniformMatrix3fv(location, 1, GL_FALSE, static_cast(data)); + } else if (size == sizeof(int)) { + glUniform1i(location, *static_cast(data)); + } +} + +void RenderingDeviceGLES3::texture_update(RID texture, uint32_t mip_level, uint32_t layer, const void* data, size_t size) { + auto it = textures.find(texture); + if (it == textures.end()) { + return; + } + + const auto& tex = it->second; + GLenum target = GL_TEXTURE_2D; + if (tex.format.type == TextureType::TEXTURE_TYPE_CUBEMAP) { + target = GL_TEXTURE_CUBE_MAP; + } + + glBindTexture(target, (GLuint) tex.handle); + + GLenum pixel_format = GL_RGBA; + GLenum pixel_type = GL_UNSIGNED_BYTE; + + if (tex.format.format == DataFormat::R8G8B8_UNORM) { + pixel_format = GL_RGB; + } else if (tex.format.format == DataFormat::R8G8_UNORM) { + pixel_format = GL_RG; + } else if (tex.format.format == DataFormat::R8_UNORM) { + pixel_format = GL_RED; + } + + if (tex.format.type == TextureType::TEXTURE_TYPE_2D) { + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexSubImage2D(GL_TEXTURE_2D, mip_level, 0, 0, tex.format.width >> mip_level, tex.format.height >> mip_level, pixel_format, + pixel_type, data); + } + + glBindTexture(target, 0); +} + +void RenderingDeviceGLES3::texture_generate_mipmaps(RID texture) { + auto it = textures.find(texture); + if (it == textures.end()) { + return; + } + + + GLenum target = GL_TEXTURE_2D; + if (it->second.format.type == TextureType::TEXTURE_TYPE_CUBEMAP) { + target = GL_TEXTURE_CUBE_MAP; + } + + glBindTexture(target, (GLuint) it->second.handle); + glGenerateMipmap(target); + glBindTexture(target, 0); +} + +void RenderingDeviceGLES3::begin_frame() { + // Placeholder for frame setup logic +} + +void RenderingDeviceGLES3::end_frame() { + // Placeholder for frame cleanup/present logic +} + +GLenum RenderingDeviceGLES3::to_gl_stencil_op(StencilOp op) { + switch (op) { + case StencilOp::KEEP: + return GL_KEEP; + case StencilOp::ZERO: + return GL_ZERO; + case StencilOp::REPLACE: + return GL_REPLACE; + case StencilOp::INCREMENT_AND_CLAMP: + return GL_INCR; + case StencilOp::DECREMENT_AND_CLAMP: + return GL_DECR; + case StencilOp::INVERT: + return GL_INVERT; + case StencilOp::INCREMENT_AND_WRAP: + return GL_INCR_WRAP; + case StencilOp::DECREMENT_AND_WRAP: + return GL_DECR_WRAP; + default: + return GL_KEEP; + } +} diff --git a/engine/private/drivers/sdl_gpu/rendering_device_sdl_gpu.cpp b/engine/private/drivers/sdl_gpu/rendering_device_sdl_gpu.cpp new file mode 100644 index 000000000..c334836d0 --- /dev/null +++ b/engine/private/drivers/sdl_gpu/rendering_device_sdl_gpu.cpp @@ -0,0 +1,1120 @@ +#include "drivers/sdl_gpu/rendering_device_sdl_gpu.h" + +#include "drivers/sdl_gpu/shaders/default_dxd12.h" +#include "drivers/sdl_gpu/shaders/default_vulkan.h" +#include "drivers/sdl_gpu/shaders/default_metal.h" + +RenderingDeviceSDL_GPU::~RenderingDeviceSDL_GPU() = default; + +bool RenderingDeviceSDL_GPU::initialize(SDL_Window* sdl_window) { + + + const int drivers = SDL_GetNumGPUDrivers(); + + for (int i = 0; i < drivers; ++i) { + const char* name = SDL_GetGPUDriver(i); + spdlog::debug("Available GPU Driver {}: {}", i, name); + } + + _device = SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_MSL | SDL_GPU_SHADERFORMAT_DXIL, true, nullptr); + + if (!_device) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create GPU device: %s", SDL_GetError()); + return false; + } + + const char* device_name = SDL_GetGPUDeviceDriver(_device); + SDL_Log("Using GPU Device Driver: %s", device_name); + if (!SDL_ClaimWindowForGPUDevice(_device, sdl_window)) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to claim window: %s", SDL_GetError()); + SDL_DestroyGPUDevice(_device); + _device = nullptr; + return false; + } + + _window = sdl_window; + + return true; +} + +void RenderingDeviceSDL_GPU::shutdown() { + if (!_device) { + return; + } + + SDL_WaitForGPUIdle(_device); + + for (auto& [rid, shader] : _shaders) { + if (shader.vertex) { + SDL_ReleaseGPUShader(_device, shader.vertex); + } + if (shader.fragment) { + SDL_ReleaseGPUShader(_device, shader.fragment); + } + } + _shaders.clear(); + + for (auto& [rid, buffer] : _buffers) { + if (buffer.handle) { + SDL_ReleaseGPUBuffer(_device, buffer.handle); + } + } + _buffers.clear(); + + for (auto& [rid, texture] : _textures) { + if (texture.handle) { + SDL_ReleaseGPUTexture(_device, texture.handle); + } + } + _textures.clear(); + + for (auto& [rid, sampler] : _samplers) { + if (sampler) { + SDL_ReleaseGPUSampler(_device, sampler); + } + } + _samplers.clear(); + + _framebuffers.clear(); + + for (auto& [rid, pipeline] : _pipelines) { + if (pipeline) { + SDL_ReleaseGPUGraphicsPipeline(_device, pipeline); + } + } + _pipelines.clear(); + + SDL_ReleaseWindowFromGPUDevice(_device, _window); + SDL_DestroyGPUDevice(_device); + _device = nullptr; +} + +// TODO: Implement shader compilation from source +RID RenderingDeviceSDL_GPU::shader_create_from_source(const String& vertex_src, const String& fragment_src) { + + if (!_device) { + return INVALID_RID; + } + + const SDL_GPUShaderFormat format = SDL_GetGPUShaderFormats(_device); + spdlog::info("GPU Shader Format: {} (SPIRV={}, DXIL={}, MSL={})", format, (format & SDL_GPU_SHADERFORMAT_SPIRV) != 0, + (format & SDL_GPU_SHADERFORMAT_DXIL) != 0, (format & SDL_GPU_SHADERFORMAT_MSL) != 0); + + if (format & SDL_GPU_SHADERFORMAT_SPIRV) { + return shader_create_from_bytecode(default_vertex_vulkan, sizeof(default_vertex_vulkan), default_fragment_vulkan, + sizeof(default_fragment_vulkan), 1, 1); + } + + if (format & SDL_GPU_SHADERFORMAT_MSL) { + return shader_create_from_bytecode(default_vertex_metal, sizeof(default_vertex_metal), default_fragment_metal, + sizeof(default_fragment_metal), 1, 1); + } + + if (format & SDL_GPU_SHADERFORMAT_DXIL) { + return shader_create_from_bytecode(default_vertex_dxd12, sizeof(default_vertex_dxd12), default_fragment_dxd12, + sizeof(default_fragment_dxd12), 1, 1); + } + + spdlog::error("No supported shader format found"); + return INVALID_RID; +} + +RID RenderingDeviceSDL_GPU::shader_create_from_bytecode(const void* vertex_bytecode, size_t vertex_size, const void* fragment_bytecode, + size_t fragment_size, uint32_t num_samplers, uint32_t num_uniform_buffers) { + if (!_device || !vertex_bytecode || !fragment_bytecode) { + return INVALID_RID; + } + + const SDL_GPUShaderFormat format = SDL_GetGPUShaderFormats(_device); + + + SDL_GPUShaderCreateInfo vertex_info = {}; + vertex_info.code = (const Uint8*) vertex_bytecode; + vertex_info.code_size = vertex_size; + vertex_info.entrypoint = "main"; + vertex_info.format = format; + vertex_info.stage = SDL_GPU_SHADERSTAGE_VERTEX; + vertex_info.num_samplers = 0; + vertex_info.num_storage_buffers = 0; + vertex_info.num_storage_textures = 0; + vertex_info.num_uniform_buffers = num_uniform_buffers; + + SDL_GPUShader* vertex_shader = SDL_CreateGPUShader(_device, &vertex_info); + if (!vertex_shader) { + spdlog::error("Failed to create vertex shader: {}", SDL_GetError()); + return INVALID_RID; + } + + SDL_GPUShaderCreateInfo fragment_info = {}; + fragment_info.code = (const Uint8*) fragment_bytecode; + fragment_info.code_size = fragment_size; + fragment_info.entrypoint = "main"; + fragment_info.format = format; + fragment_info.stage = SDL_GPU_SHADERSTAGE_FRAGMENT; + fragment_info.num_samplers = num_samplers; + fragment_info.num_storage_buffers = 0; + fragment_info.num_storage_textures = 0; + fragment_info.num_uniform_buffers = 0; + + SDL_GPUShader* fragment_shader = SDL_CreateGPUShader(_device, &fragment_info); + if (!fragment_shader) { + spdlog::error("Failed to create fragment shader: {}", SDL_GetError()); + SDL_ReleaseGPUShader(_device, vertex_shader); + return INVALID_RID; + } + + RID rid = allocate_rid(); + GPUShader shader; + shader.vertex = vertex_shader; + shader.fragment = fragment_shader; + _shaders[rid] = shader; + + return rid; +} + +void RenderingDeviceSDL_GPU::shader_destroy(RID shader) { + auto it = _shaders.find(shader); + + if (it != _shaders.end()) { + if (it->second.vertex) { + SDL_ReleaseGPUShader(_device, it->second.vertex); + } + if (it->second.fragment) { + SDL_ReleaseGPUShader(_device, it->second.fragment); + } + _shaders.erase(it); + } +} + +RID RenderingDeviceSDL_GPU::buffer_create(size_t size, uint32_t usage_flags, const void* data) { + SDL_GPUBufferCreateInfo create_info = {}; + create_info.usage = sdl_buffer_usage(usage_flags); + create_info.size = (Uint32) size; + + SDL_GPUBuffer* buffer = SDL_CreateGPUBuffer(_device, &create_info); + if (!buffer) { + spdlog::error("Failed to create buffer: {}", SDL_GetError()); + return INVALID_RID; + } + + if (data) { + SDL_GPUTransferBufferCreateInfo transfer_info = {}; + transfer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; + transfer_info.size = (Uint32) size; + + SDL_GPUTransferBuffer* transfer = SDL_CreateGPUTransferBuffer(_device, &transfer_info); + void* mapped = SDL_MapGPUTransferBuffer(_device, transfer, false); + memcpy(mapped, data, size); + SDL_UnmapGPUTransferBuffer(_device, transfer); + + SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(_device); + SDL_GPUCopyPass* copy_pass = SDL_BeginGPUCopyPass(cmd); + + SDL_GPUTransferBufferLocation src_loc = {}; + src_loc.transfer_buffer = transfer; + src_loc.offset = 0; + + SDL_GPUBufferRegion dst_region = {}; + dst_region.buffer = buffer; + dst_region.offset = 0; + dst_region.size = (Uint32) size; + + SDL_UploadToGPUBuffer(copy_pass, &src_loc, &dst_region, false); + SDL_EndGPUCopyPass(copy_pass); + SDL_SubmitGPUCommandBuffer(cmd); + + SDL_ReleaseGPUTransferBuffer(_device, transfer); + } + + RID rid = allocate_rid(); + _buffers[rid] = {buffer, size, usage_flags}; + return rid; +} + +void RenderingDeviceSDL_GPU::buffer_update(RID buffer, size_t offset, size_t size, const void* data) { + auto it = _buffers.find(buffer); + if (it == _buffers.end() || !data || size == 0) { + return; + } + + SDL_GPUTransferBufferCreateInfo transfer_info = {}; + transfer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; + transfer_info.size = (Uint32) size; + + SDL_GPUTransferBuffer* transfer = SDL_CreateGPUTransferBuffer(_device, &transfer_info); + if (!transfer) { + spdlog::error("Failed to create transfer buffer: {}", SDL_GetError()); + return; + } + + void* mapped = SDL_MapGPUTransferBuffer(_device, transfer, false); + if (!mapped) { + spdlog::error("Failed to map transfer buffer: {}", SDL_GetError()); + SDL_ReleaseGPUTransferBuffer(_device, transfer); + return; + } + + memcpy(mapped, data, size); + SDL_UnmapGPUTransferBuffer(_device, transfer); + + SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(_device); + if (!cmd) { + spdlog::error("Failed to acquire command buffer for buffer update: {}", SDL_GetError()); + SDL_ReleaseGPUTransferBuffer(_device, transfer); + return; + } + + SDL_GPUCopyPass* copy_pass = SDL_BeginGPUCopyPass(cmd); + + SDL_GPUTransferBufferLocation src_loc = {}; + src_loc.transfer_buffer = transfer; + src_loc.offset = 0; + + SDL_GPUBufferRegion dst_region = {}; + dst_region.buffer = it->second.handle; + dst_region.offset = (Uint32) offset; + dst_region.size = (Uint32) size; + + SDL_UploadToGPUBuffer(copy_pass, &src_loc, &dst_region, false); + SDL_EndGPUCopyPass(copy_pass); + + SDL_GPUFence* fence = SDL_SubmitGPUCommandBufferAndAcquireFence(cmd); + if (fence) { + SDL_WaitForGPUFences(_device, true, &fence, 1); + SDL_ReleaseGPUFence(_device, fence); + } + + SDL_ReleaseGPUTransferBuffer(_device, transfer); +} + + +void RenderingDeviceSDL_GPU::buffer_destroy(RID buffer) { + auto it = _buffers.find(buffer); + + if (it != _buffers.end()) { + if (it->second.handle) { + SDL_ReleaseGPUBuffer(_device, it->second.handle); + } + _buffers.erase(it); + } +} + +RID RenderingDeviceSDL_GPU::texture_create(const TextureFormat& format, void* data) { + SDL_GPUTextureCreateInfo create_info = {}; + + if (format.type == TextureType::TEXTURE_TYPE_3D) { + create_info.type = SDL_GPU_TEXTURETYPE_3D; + } else if (format.type == TextureType::TEXTURE_TYPE_CUBEMAP) { + create_info.type = SDL_GPU_TEXTURETYPE_CUBE; + } else { + create_info.type = SDL_GPU_TEXTURETYPE_2D; + } + + create_info.format = sdl_format(format.format); + create_info.width = format.width; + create_info.height = format.height; + create_info.layer_count_or_depth = (format.type == TextureType::TEXTURE_TYPE_3D) ? format.depth : format.array_layers; + create_info.num_levels = format.mipmaps == 0 ? 1 : format.mipmaps; + + switch (format.samples) { + case 2: + create_info.sample_count = SDL_GPU_SAMPLECOUNT_2; + break; + case 4: + create_info.sample_count = SDL_GPU_SAMPLECOUNT_4; + break; + case 8: + create_info.sample_count = SDL_GPU_SAMPLECOUNT_8; + break; + default: + create_info.sample_count = SDL_GPU_SAMPLECOUNT_1; + break; + } + + const bool is_multisample = (create_info.sample_count > SDL_GPU_SAMPLECOUNT_1); + + create_info.usage = 0; + if (!is_multisample) { + create_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER; + } + + if (format.depth_stencil) { + create_info.usage |= SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET; + } else { + create_info.usage |= SDL_GPU_TEXTUREUSAGE_COLOR_TARGET; + } + + SDL_GPUTexture* texture = SDL_CreateGPUTexture(_device, &create_info); + if (!texture) { + spdlog::error("Failed to create texture: {}", SDL_GetError()); + return INVALID_RID; + } + + RID rid = allocate_rid(); + GPUTexture gpu_tex; + gpu_tex.handle = texture; + gpu_tex.format = format; + _textures[rid] = gpu_tex; + + if (data) { + uint32_t bytes_per_pixel = 4; + size_t data_size = format.width * format.height * bytes_per_pixel; + texture_update(rid, 0, 0, data, data_size); + } + + return rid; +} + + +void RenderingDeviceSDL_GPU::texture_update(RID texture, uint32_t mip_level, uint32_t layer, const void* data, size_t size) { + + + auto it = _textures.find(texture); + if (it == _textures.end() || !data) { + return; + } + + SDL_GPUTransferBufferCreateInfo transfer_info = {}; + transfer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; + transfer_info.size = (Uint32) size; + + SDL_GPUTransferBuffer* transfer = SDL_CreateGPUTransferBuffer(_device, &transfer_info); + void* mapped = SDL_MapGPUTransferBuffer(_device, transfer, false); + memcpy(mapped, data, size); + SDL_UnmapGPUTransferBuffer(_device, transfer); + + SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(_device); + SDL_GPUCopyPass* copy_pass = SDL_BeginGPUCopyPass(cmd); + + SDL_GPUTextureTransferInfo src_info = {}; + src_info.transfer_buffer = transfer; + src_info.offset = 0; + + SDL_GPUTextureRegion dst_region = {}; + dst_region.texture = it->second.handle; + dst_region.mip_level = mip_level; + dst_region.layer = layer; + dst_region.x = 0; + dst_region.y = 0; + dst_region.z = 0; + dst_region.w = it->second.format.width >> mip_level; + dst_region.h = it->second.format.height >> mip_level; + dst_region.d = 1; + + SDL_UploadToGPUTexture(copy_pass, &src_info, &dst_region, false); + SDL_EndGPUCopyPass(copy_pass); + SDL_SubmitGPUCommandBuffer(cmd); + + SDL_ReleaseGPUTransferBuffer(_device, transfer); +} + +void RenderingDeviceSDL_GPU::texture_generate_mipmaps(RID texture) { + spdlog::error("texture_generate_mipmaps not implemented yet (SDL_GPU)"); +} + +void RenderingDeviceSDL_GPU::texture_destroy(RID texture) { + auto it = _textures.find(texture); + + if (it != _textures.end()) { + if (it->second.handle) { + SDL_ReleaseGPUTexture(_device, it->second.handle); + } + _textures.erase(it); + } +} + +void RenderingDeviceSDL_GPU::get_texture_size(RID texture, uint32_t& width, uint32_t& height) { + auto it = _textures.find(texture); + + if (it != _textures.end()) { + width = it->second.format.width; + height = it->second.format.height; + } +} + +uint32_t RenderingDeviceSDL_GPU::texture_get_native_handle(RID texture) { + auto it = _textures.find(texture); + return (it != _textures.end()) ? (uint32_t) (uintptr_t) it->second.handle : 0; +} + +Texture RenderingDeviceSDL_GPU::get_texture(RID texture) { + Texture result; + auto it = _textures.find(texture); + + if (it != _textures.end()) { + result.rid = texture; + result.handle = (uint32_t) (uintptr_t) it->second.handle; + result.format = it->second.format; + } + + return result; +} + +RID RenderingDeviceSDL_GPU::sampler_create(const SamplerState& state) { + SDL_GPUSamplerCreateInfo create_info = {}; + create_info.min_filter = sdl_filter(state.min_filter); + create_info.mag_filter = sdl_filter(state.mag_filter); + create_info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_LINEAR; + create_info.address_mode_u = sdl_wrap(state.wrap_u); + create_info.address_mode_v = sdl_wrap(state.wrap_v); + create_info.address_mode_w = sdl_wrap(state.wrap_w); + create_info.mip_lod_bias = state.lod_bias; + create_info.max_anisotropy = state.max_anisotropy; + create_info.compare_op = sdl_compare(state.compare_op); + create_info.min_lod = state.min_lod; + create_info.max_lod = state.max_lod; + create_info.enable_anisotropy = state.max_anisotropy > 1.0f; + create_info.enable_compare = state.compare_enabled; + + SDL_GPUSampler* sampler = SDL_CreateGPUSampler(_device, &create_info); + if (!sampler) { + spdlog::error("Failed to create sampler: {}", SDL_GetError()); + return INVALID_RID; + } + + RID rid = allocate_rid(); + _samplers[rid] = sampler; + return rid; +} + +void RenderingDeviceSDL_GPU::sampler_destroy(RID sampler) { + auto it = _samplers.find(sampler); + + if (it != _samplers.end()) { + if (it->second) { + SDL_ReleaseGPUSampler(_device, it->second); + } + _samplers.erase(it); + } +} + +RID RenderingDeviceSDL_GPU::framebuffer_create(const Vector& attachments) { + RID rid = allocate_rid(); + _framebuffers[rid] = attachments; + return rid; +} + +void RenderingDeviceSDL_GPU::framebuffer_destroy(RID framebuffer) { + _framebuffers.erase(framebuffer); +} + +RID RenderingDeviceSDL_GPU::pipeline_create(const PipelineState& state) { + auto shader_it = _shaders.find(state.shader); + if (shader_it == _shaders.end()) { + spdlog::error("pipeline_create: Shader {} not found", state.shader); + return INVALID_RID; + } + + SDL_GPUGraphicsPipelineCreateInfo create_info = {}; + create_info.vertex_shader = shader_it->second.vertex; + create_info.fragment_shader = shader_it->second.fragment; + + Vector vertex_buffers; + Vector vertex_attributes; + + SDL_GPUVertexBufferDescription vb_desc = {}; + vb_desc.slot = state.vertex_format.binding; + vb_desc.pitch = state.vertex_format.stride; + vb_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX; + vertex_buffers.push_back(vb_desc); + + for (const auto& attr : state.vertex_format.attributes) { + SDL_GPUVertexAttribute va = {}; + va.location = attr.location; + va.buffer_slot = state.vertex_format.binding; + va.format = sdl_vertex_format(attr.format); + va.offset = attr.offset; + vertex_attributes.push_back(va); + } + + SDL_GPUVertexInputState vertex_input = {}; + vertex_input.vertex_buffer_descriptions = vertex_buffers.data(); + vertex_input.num_vertex_buffers = (Uint32) vertex_buffers.size(); + vertex_input.vertex_attributes = vertex_attributes.data(); + vertex_input.num_vertex_attributes = (Uint32) vertex_attributes.size(); + create_info.vertex_input_state = vertex_input; + + create_info.primitive_type = sdl_topology(state.topology); + + SDL_GPURasterizerState rasterizer = {}; + rasterizer.fill_mode = sdl_fill(state.rasterization.polygon_mode); + rasterizer.cull_mode = sdl_cull(state.rasterization.cull_mode); + rasterizer.front_face = state.rasterization.front_face_ccw ? SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE : SDL_GPU_FRONTFACE_CLOCKWISE; + rasterizer.enable_depth_bias = state.rasterization.depth_bias_enable; + rasterizer.depth_bias_constant_factor = state.rasterization.depth_bias_constant; + rasterizer.depth_bias_slope_factor = state.rasterization.depth_bias_slope; + create_info.rasterizer_state = rasterizer; + + SDL_GPUDepthStencilState depth_stencil = {}; + depth_stencil.enable_depth_test = state.depth_stencil.depth_test_enable; + depth_stencil.enable_depth_write = state.depth_stencil.depth_write_enable; + depth_stencil.compare_op = sdl_compare(state.depth_stencil.depth_compare_op); + depth_stencil.enable_stencil_test = state.depth_stencil.stencil_test_enable; + + depth_stencil.back_stencil_state.fail_op = sdl_stencil(state.depth_stencil.stencil_fail_op); + depth_stencil.back_stencil_state.depth_fail_op = sdl_stencil(state.depth_stencil.stencil_depth_fail_op); + depth_stencil.back_stencil_state.pass_op = sdl_stencil(state.depth_stencil.stencil_pass_op); + depth_stencil.back_stencil_state.compare_op = sdl_compare(state.depth_stencil.stencil_compare_op); + depth_stencil.compare_mask = (Uint8) state.depth_stencil.stencil_compare_mask; + depth_stencil.write_mask = (Uint8) state.depth_stencil.stencil_write_mask; + depth_stencil.front_stencil_state = depth_stencil.back_stencil_state; + + create_info.depth_stencil_state = depth_stencil; + + Vector color_targets; + for (size_t i = 0; i < state.blend_states.size(); ++i) { + const auto& blend = state.blend_states[i]; + + SDL_GPUColorTargetDescription desc = {}; + desc.format = SDL_GetGPUSwapchainTextureFormat(_device, _window); + + SDL_GPUColorTargetBlendState bs = {}; + bs.enable_blend = blend.enable; + bs.src_color_blendfactor = sdl_blend_factor(blend.src_color); + bs.dst_color_blendfactor = sdl_blend_factor(blend.dst_color); + bs.color_blend_op = sdl_blend_op(blend.color_op); + bs.src_alpha_blendfactor = sdl_blend_factor(blend.src_alpha); + bs.dst_alpha_blendfactor = sdl_blend_factor(blend.dst_alpha); + bs.alpha_blend_op = sdl_blend_op(blend.alpha_op); + bs.color_write_mask = 0; + + if (blend.write_r) { + bs.color_write_mask |= SDL_GPU_COLORCOMPONENT_R; + } + if (blend.write_g) { + bs.color_write_mask |= SDL_GPU_COLORCOMPONENT_G; + } + if (blend.write_b) { + bs.color_write_mask |= SDL_GPU_COLORCOMPONENT_B; + } + if (blend.write_a) { + bs.color_write_mask |= SDL_GPU_COLORCOMPONENT_A; + } + + desc.blend_state = bs; + color_targets.push_back(desc); + } + + SDL_GPUGraphicsPipelineTargetInfo target_info = {}; + target_info.color_target_descriptions = color_targets.data(); + target_info.num_color_targets = (Uint32) color_targets.size(); + target_info.depth_stencil_format = SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT; + target_info.has_depth_stencil_target = state.depth_stencil.depth_test_enable; + + create_info.target_info = target_info; + + SDL_GPUGraphicsPipeline* pipeline = SDL_CreateGPUGraphicsPipeline(_device, &create_info); + if (!pipeline) { + spdlog::error("Failed to create pipeline: {}", SDL_GetError()); + return INVALID_RID; + } + + RID rid = allocate_rid(); + _pipelines[rid] = pipeline; + return rid; +} + +void RenderingDeviceSDL_GPU::pipeline_destroy(RID pipeline) { + auto it = _pipelines.find(pipeline); + + if (it != _pipelines.end()) { + if (it->second) { + SDL_ReleaseGPUGraphicsPipeline(_device, it->second); + } + _pipelines.erase(it); + } +} + +void RenderingDeviceSDL_GPU::begin_frame() { + _cmd_buffer = SDL_AcquireGPUCommandBuffer(_device); + if (!_cmd_buffer) { + spdlog::error("Failed to acquire command buffer: {}", SDL_GetError()); + } +} + +void RenderingDeviceSDL_GPU::end_frame() { + if (_render_pass) { + SDL_EndGPURenderPass(_render_pass); + _render_pass = nullptr; + } + + if (_cmd_buffer) { + if (!SDL_SubmitGPUCommandBuffer(_cmd_buffer)) { + spdlog::error("Failed to submit command buffer: {}", SDL_GetError()); + } + _cmd_buffer = nullptr; + } +} + + +void RenderingDeviceSDL_GPU::render_pass_begin(RID framebuffer, const Viewport& viewport, const Scissor& scissor) { + if (!_cmd_buffer) { + return; + } + + SDL_GPUColorTargetInfo color_targets[8] = {}; + Uint32 num_color_targets = 0; + SDL_GPUDepthStencilTargetInfo depth_target = {}; + bool has_depth = false; + + auto fb_it = _framebuffers.find(framebuffer); + if (fb_it != _framebuffers.end()) { + for (const auto& attachment : fb_it->second) { + auto tex_it = _textures.find(attachment.texture); + if (tex_it == _textures.end()) { + continue; + } + + if (tex_it->second.format.depth_stencil) { + depth_target.texture = tex_it->second.handle; + depth_target.load_op = attachment.clear ? SDL_GPU_LOADOP_CLEAR : SDL_GPU_LOADOP_LOAD; + depth_target.store_op = SDL_GPU_STOREOP_STORE; + depth_target.clear_depth = attachment.clear_value.depth; + depth_target.clear_stencil = (Uint8) attachment.clear_value.stencil; + depth_target.cycle = false; + has_depth = true; + } else { + if (num_color_targets < 8) { + color_targets[num_color_targets].texture = tex_it->second.handle; + color_targets[num_color_targets].mip_level = attachment.mip_level; + color_targets[num_color_targets].layer_or_depth_plane = attachment.layer; + color_targets[num_color_targets].load_op = attachment.clear ? SDL_GPU_LOADOP_CLEAR : SDL_GPU_LOADOP_LOAD; + color_targets[num_color_targets].store_op = SDL_GPU_STOREOP_STORE; + color_targets[num_color_targets].clear_color.r = attachment.clear_value.color.r; + color_targets[num_color_targets].clear_color.g = attachment.clear_value.color.g; + color_targets[num_color_targets].clear_color.b = attachment.clear_value.color.b; + color_targets[num_color_targets].clear_color.a = attachment.clear_value.color.a; + color_targets[num_color_targets].cycle = false; + num_color_targets++; + } + } + } + } else { + SDL_GPUTexture* swapchain = nullptr; + Uint32 width = 0, height = 0; + if (!SDL_AcquireGPUSwapchainTexture(_cmd_buffer, _window, &swapchain, &width, &height)) { + spdlog::error("Failed to acquire swapchain texture: {}", SDL_GetError()); + return; + } + + if (!swapchain) { + return; + } + + color_targets[0].texture = swapchain; + color_targets[0].load_op = SDL_GPU_LOADOP_CLEAR; + color_targets[0].store_op = SDL_GPU_STOREOP_STORE; + color_targets[0].clear_color.r = _clear_color.r; + color_targets[0].clear_color.g = _clear_color.g; + color_targets[0].clear_color.b = _clear_color.b; + color_targets[0].clear_color.a = _clear_color.a; + color_targets[0].cycle = true; + num_color_targets = 1; + } + + if (num_color_targets == 0 && !has_depth) { + return; + } + + _render_pass = SDL_BeginGPURenderPass(_cmd_buffer, color_targets, num_color_targets, has_depth ? &depth_target : nullptr); + + if (!_render_pass) { + spdlog::error("Failed to begin render pass: {}", SDL_GetError()); + return; + } + + _current_viewport = viewport; + set_viewport(viewport); + set_scissor(scissor); +} + +void RenderingDeviceSDL_GPU::render_pass_end() { + if (_render_pass) { + SDL_EndGPURenderPass(_render_pass); + _render_pass = nullptr; + } +} + + +void RenderingDeviceSDL_GPU::bind_pipeline(RID pipeline) { + if (!_render_pass) { + return; + } + + auto it = _pipelines.find(pipeline); + if (it == _pipelines.end()) { + return; + } + + SDL_BindGPUGraphicsPipeline(_render_pass, it->second); + _current_pipeline = pipeline; +} + +void RenderingDeviceSDL_GPU::bind_vertex_buffers(const Vector& buffers, const Vector& offsets) { + if (!_render_pass || buffers.empty()) { + return; + } + + Vector bindings; + bindings.reserve(buffers.size()); + + _bound_vertex_buffers.clear(); + _bound_vertex_buffers.reserve(buffers.size()); + + for (size_t i = 0; i < buffers.size(); ++i) { + auto it = _buffers.find(buffers[i]); + if (it == _buffers.end()) { + continue; + } + + SDL_GPUBufferBinding binding = {}; + binding.buffer = it->second.handle; + binding.offset = (i < offsets.size()) ? (Uint32) offsets[i] : 0; + bindings.push_back(binding); + + BoundVertexBuffer bvb; + bvb.buffer = it->second.handle; + bvb.offset = binding.offset; + _bound_vertex_buffers.push_back(bvb); + } + + if (!bindings.empty()) { + SDL_BindGPUVertexBuffers(_render_pass, 0, bindings.data(), (Uint32) bindings.size()); + } +} + +void RenderingDeviceSDL_GPU::bind_index_buffer(RID buffer, IndexType type, size_t offset) { + if (!_render_pass) { + return; + } + + auto it = _buffers.find(buffer); + if (it == _buffers.end()) { + return; + } + + SDL_GPUBufferBinding binding = {}; + binding.buffer = it->second.handle; + binding.offset = (Uint32) offset; + + _bound_index_buffer = it->second.handle; + _bound_index_type = sdl_index_type(type); + _bound_index_offset = (Uint32) offset; + + SDL_BindGPUIndexBuffer(_render_pass, &binding, sdl_index_type(type)); +} + +void RenderingDeviceSDL_GPU::bind_uniform_buffer(uint32_t binding, RID buffer, size_t offset, size_t size) { + // SDL GPU doesn't have traditional uniform buffer binding like OpenGL/Vulkan + // Instead it uses push constants or storage buffer binding + // For now, we'll skip this as uniform data will be handled via push_constant + // TODO: Implement storage buffer binding if needed +} + +void RenderingDeviceSDL_GPU::bind_texture(uint32_t binding, RID texture, RID sampler) { + if (!_render_pass) { + return; + } + + auto tex_it = _textures.find(texture); + auto sam_it = _samplers.find(sampler); + + if (tex_it == _textures.end() || sam_it == _samplers.end()) { + return; + } + + SDL_GPUTextureSamplerBinding tex_sampler_binding = {}; + tex_sampler_binding.texture = tex_it->second.handle; + tex_sampler_binding.sampler = sam_it->second; + + BoundTextureSampler bts; + bts.texture = tex_it->second.handle; + bts.sampler = sam_it->second; + _bound_textures[binding] = bts; + + SDL_BindGPUFragmentSamplers(_render_pass, binding, &tex_sampler_binding, 1); +} + +void RenderingDeviceSDL_GPU::push_constant(const String& name, const void* data, size_t size) { + if (!_cmd_buffer || !data || size == 0) { + return; + } + + // Push uniform data to vertex shader only + // The fragment shader uses samplers for textures, not uniform buffers + SDL_PushGPUVertexUniformData(_cmd_buffer, 0, data, (Uint32) size); +} + +void RenderingDeviceSDL_GPU::draw(uint32_t vertex_count, uint32_t instance_count, uint32_t first_vertex, uint32_t first_instance) { + if (!_render_pass) { + return; + } + + SDL_DrawGPUPrimitives(_render_pass, vertex_count, instance_count, first_vertex, first_instance); +} + +void RenderingDeviceSDL_GPU::draw_indexed(uint32_t index_count, uint32_t instance_count, uint32_t first_index, int32_t vertex_offset, + uint32_t first_instance) { + if (!_render_pass) { + return; + } + + SDL_DrawGPUIndexedPrimitives(_render_pass, index_count, instance_count, first_index, vertex_offset, first_instance); +} + +void RenderingDeviceSDL_GPU::set_viewport(const Viewport& viewport) { + if (!_render_pass) { + return; + } + + SDL_GPUViewport sdl_viewport = {}; + sdl_viewport.x = viewport.x; + sdl_viewport.y = viewport.y; + sdl_viewport.w = viewport.width; + sdl_viewport.h = viewport.height; + sdl_viewport.min_depth = viewport.min_depth; + sdl_viewport.max_depth = viewport.max_depth; + + SDL_SetGPUViewport(_render_pass, &sdl_viewport); + _current_viewport = viewport; +} + +void RenderingDeviceSDL_GPU::set_scissor(const Scissor& scissor) { + if (!_render_pass) { + return; + } + + SDL_Rect sdl_scissor = {}; + sdl_scissor.x = scissor.x; + sdl_scissor.y = scissor.y; + sdl_scissor.w = (int) scissor.width; + sdl_scissor.h = (int) scissor.height; + + SDL_SetGPUScissor(_render_pass, &sdl_scissor); +} + +void RenderingDeviceSDL_GPU::clear_color(const glm::vec4& color) { + // Store the clear color for use in render_pass_begin + _clear_color = color; +} +void RenderingDeviceSDL_GPU::clear_depth_stencil(float depth, uint32_t stencil) { + // In SDL GPU, clearing is handled at render pass begin via load_op = SDL_GPU_LOADOP_CLEAR + // This function is kept for API compatibility but the actual clear happens in render_pass_begin +} + +void RenderingDeviceSDL_GPU::swap_buffers() { + // In SDL GPU, swapchain presentation is handled automatically when the command buffer + // containing the swapchain texture is submitted via SDL_SubmitGPUCommandBuffer in end_frame() + // No explicit swap is needed +} + +SDL_GPUSamplerMipmapMode RenderingDeviceSDL_GPU::sdl_mipmap_mode(TextureFilter filter) { + switch (filter) { + case TextureFilter::NEAREST_MIPMAP_NEAREST: + case TextureFilter::LINEAR_MIPMAP_NEAREST: + return SDL_GPU_SAMPLERMIPMAPMODE_NEAREST; + case TextureFilter::NEAREST_MIPMAP_LINEAR: + case TextureFilter::LINEAR_MIPMAP_LINEAR: + return SDL_GPU_SAMPLERMIPMAPMODE_LINEAR; + default: + return SDL_GPU_SAMPLERMIPMAPMODE_LINEAR; + } +} + + +SDL_GPUTextureFormat RenderingDeviceSDL_GPU::sdl_format(DataFormat fmt) { + switch (fmt) { + case DataFormat::R8_UNORM: + return SDL_GPU_TEXTUREFORMAT_R8_UNORM; + case DataFormat::R8G8_UNORM: + return SDL_GPU_TEXTUREFORMAT_R8G8_UNORM; + case DataFormat::R8G8B8A8_UNORM: + return SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM; + case DataFormat::D24_UNORM_S8_UINT: + return SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT; + default: + return SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM; + } +} + +SDL_GPUVertexElementFormat RenderingDeviceSDL_GPU::sdl_vertex_format(DataFormat fmt) { + switch (fmt) { + case DataFormat::R32_SFLOAT: + return SDL_GPU_VERTEXELEMENTFORMAT_FLOAT; + case DataFormat::R32G32_SFLOAT: + return SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2; + case DataFormat::R32G32B32_SFLOAT: + return SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3; + case DataFormat::R32G32B32A32_SFLOAT: + return SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4; + case DataFormat::R32_UINT: + return SDL_GPU_VERTEXELEMENTFORMAT_UINT; + case DataFormat::R32G32_UINT: + return SDL_GPU_VERTEXELEMENTFORMAT_UINT2; + case DataFormat::R32G32B32_UINT: + return SDL_GPU_VERTEXELEMENTFORMAT_UINT3; + case DataFormat::R32G32B32A32_UINT: + return SDL_GPU_VERTEXELEMENTFORMAT_UINT4; + default: + return SDL_GPU_VERTEXELEMENTFORMAT_FLOAT; + } +} + +SDL_GPUFilter RenderingDeviceSDL_GPU::sdl_filter(TextureFilter filter) { + return (filter == TextureFilter::NEAREST) ? SDL_GPU_FILTER_NEAREST : SDL_GPU_FILTER_LINEAR; +} + +SDL_GPUSamplerAddressMode RenderingDeviceSDL_GPU::sdl_wrap(TextureWrap wrap) { + switch (wrap) { + case TextureWrap::REPEAT: + return SDL_GPU_SAMPLERADDRESSMODE_REPEAT; + case TextureWrap::MIRRORED_REPEAT: + return SDL_GPU_SAMPLERADDRESSMODE_MIRRORED_REPEAT; + case TextureWrap::CLAMP_TO_EDGE: + return SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE; + default: + return SDL_GPU_SAMPLERADDRESSMODE_REPEAT; + } +} + +SDL_GPUCompareOp RenderingDeviceSDL_GPU::sdl_compare(CompareOp op) { + switch (op) { + case CompareOp::NEVER: + return SDL_GPU_COMPAREOP_NEVER; + case CompareOp::LESS: + return SDL_GPU_COMPAREOP_LESS; + case CompareOp::EQUAL: + return SDL_GPU_COMPAREOP_EQUAL; + case CompareOp::LESS_OR_EQUAL: + return SDL_GPU_COMPAREOP_LESS_OR_EQUAL; + case CompareOp::GREATER: + return SDL_GPU_COMPAREOP_GREATER; + case CompareOp::NOT_EQUAL: + return SDL_GPU_COMPAREOP_NOT_EQUAL; + case CompareOp::GREATER_OR_EQUAL: + return SDL_GPU_COMPAREOP_GREATER_OR_EQUAL; + case CompareOp::ALWAYS: + return SDL_GPU_COMPAREOP_ALWAYS; + default: + return SDL_GPU_COMPAREOP_LESS; + } +} + +SDL_GPUStencilOp RenderingDeviceSDL_GPU::sdl_stencil(StencilOp op) { + switch (op) { + case StencilOp::KEEP: + return SDL_GPU_STENCILOP_KEEP; + case StencilOp::ZERO: + return SDL_GPU_STENCILOP_ZERO; + case StencilOp::REPLACE: + return SDL_GPU_STENCILOP_REPLACE; + case StencilOp::INCREMENT_AND_CLAMP: + return SDL_GPU_STENCILOP_INCREMENT_AND_CLAMP; + case StencilOp::DECREMENT_AND_CLAMP: + return SDL_GPU_STENCILOP_DECREMENT_AND_CLAMP; + case StencilOp::INVERT: + return SDL_GPU_STENCILOP_INVERT; + case StencilOp::INCREMENT_AND_WRAP: + return SDL_GPU_STENCILOP_INCREMENT_AND_WRAP; + case StencilOp::DECREMENT_AND_WRAP: + return SDL_GPU_STENCILOP_DECREMENT_AND_WRAP; + default: + return SDL_GPU_STENCILOP_KEEP; + } +} + +SDL_GPUBlendFactor RenderingDeviceSDL_GPU::sdl_blend_factor(BlendFactor f) { + switch (f) { + case BlendFactor::ZERO: + return SDL_GPU_BLENDFACTOR_ZERO; + case BlendFactor::ONE: + return SDL_GPU_BLENDFACTOR_ONE; + case BlendFactor::SRC_COLOR: + return SDL_GPU_BLENDFACTOR_SRC_COLOR; + case BlendFactor::ONE_MINUS_SRC_COLOR: + return SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_COLOR; + case BlendFactor::DST_COLOR: + return SDL_GPU_BLENDFACTOR_DST_COLOR; + case BlendFactor::ONE_MINUS_DST_COLOR: + return SDL_GPU_BLENDFACTOR_ONE_MINUS_DST_COLOR; + case BlendFactor::SRC_ALPHA: + return SDL_GPU_BLENDFACTOR_SRC_ALPHA; + case BlendFactor::ONE_MINUS_SRC_ALPHA: + return SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; + case BlendFactor::DST_ALPHA: + return SDL_GPU_BLENDFACTOR_DST_ALPHA; + case BlendFactor::ONE_MINUS_DST_ALPHA: + return SDL_GPU_BLENDFACTOR_ONE_MINUS_DST_ALPHA; + default: + return SDL_GPU_BLENDFACTOR_ONE; + } +} + +SDL_GPUBlendOp RenderingDeviceSDL_GPU::sdl_blend_op(BlendOp op) { + switch (op) { + case BlendOp::ADD: + return SDL_GPU_BLENDOP_ADD; + case BlendOp::SUBTRACT: + return SDL_GPU_BLENDOP_SUBTRACT; + case BlendOp::REVERSE_SUBTRACT: + return SDL_GPU_BLENDOP_REVERSE_SUBTRACT; + case BlendOp::MIN: + return SDL_GPU_BLENDOP_MIN; + case BlendOp::MAX: + return SDL_GPU_BLENDOP_MAX; + default: + return SDL_GPU_BLENDOP_ADD; + } +} + +SDL_GPUCullMode RenderingDeviceSDL_GPU::sdl_cull(CullMode mode) { + switch (mode) { + case CullMode::NONE: + return SDL_GPU_CULLMODE_NONE; + case CullMode::FRONT: + return SDL_GPU_CULLMODE_FRONT; + case CullMode::BACK: + return SDL_GPU_CULLMODE_BACK; + default: + return SDL_GPU_CULLMODE_BACK; + } +} + +SDL_GPUFillMode RenderingDeviceSDL_GPU::sdl_fill(PolygonMode mode) { + return (mode == PolygonMode::LINE) ? SDL_GPU_FILLMODE_LINE : SDL_GPU_FILLMODE_FILL; +} + +SDL_GPUPrimitiveType RenderingDeviceSDL_GPU::sdl_topology(PrimitiveTopology topo) { + switch (topo) { + case PrimitiveTopology::POINTS: + return SDL_GPU_PRIMITIVETYPE_POINTLIST; + case PrimitiveTopology::LINES: + return SDL_GPU_PRIMITIVETYPE_LINELIST; + case PrimitiveTopology::LINE_STRIP: + return SDL_GPU_PRIMITIVETYPE_LINESTRIP; + case PrimitiveTopology::TRIANGLES: + return SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; + case PrimitiveTopology::TRIANGLE_STRIP: + return SDL_GPU_PRIMITIVETYPE_TRIANGLESTRIP; + default: + return SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; + } +} + +SDL_GPUIndexElementSize RenderingDeviceSDL_GPU::sdl_index_type(IndexType type) { + return (type == IndexType::UINT16) ? SDL_GPU_INDEXELEMENTSIZE_16BIT : SDL_GPU_INDEXELEMENTSIZE_32BIT; +} + +SDL_GPUBufferUsageFlags RenderingDeviceSDL_GPU::sdl_buffer_usage(uint32_t flags) { + SDL_GPUBufferUsageFlags result = 0; + if (flags & (uint32_t) BufferUsage::BUFFER_USAGE_VERTEX) { + result |= SDL_GPU_BUFFERUSAGE_VERTEX; + } + if (flags & (uint32_t) BufferUsage::BUFFER_USAGE_INDEX) { + result |= SDL_GPU_BUFFERUSAGE_INDEX; + } + if (flags & (uint32_t) BufferUsage::BUFFER_USAGE_UNIFORM) { + result |= SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ; + } + return result; +} diff --git a/engine/private/servers/rendering/rendering_canvas.cpp b/engine/private/servers/rendering/rendering_canvas.cpp new file mode 100644 index 000000000..b4a325458 --- /dev/null +++ b/engine/private/servers/rendering/rendering_canvas.cpp @@ -0,0 +1,1045 @@ +#include "servers/rendering/rendering_canvas.h" + +#include "stdafx.h" + + +RenderingCanvas::RenderingCanvas(RenderingDevice* device) + : rd(device), shader(INVALID_RID), pipeline(INVALID_RID), vertex_buffer(INVALID_RID), index_buffer(INVALID_RID), + white_texture(INVALID_RID), default_sampler(INVALID_RID), current_blend_mode(BlendMode::ALPHA), scale_mode(ScaleMode::KEEP), + line_width(1.0f), window_width(800), window_height(600), viewport_width(800), viewport_height(600), is_drawing(false) { +} + +RenderingCanvas::~RenderingCanvas() { + shutdown(); +} + +bool RenderingCanvas::initialize(int width, int height) { + + window_width = width; + window_height = height; + viewport_width = width; + viewport_height = height; + + text_storage.emoji_font = Font(TTF_OpenFont((ASSETS_PATH + "fonts/Twemoji.ttf").c_str(), 24)); + + if (!text_storage.emoji_font.get_native_handle()) { + spdlog::warn("Failed to load emoji font (Twemoji.ttf), emoji support disabled: {}", SDL_GetError()); + } else { + spdlog::info("Loaded emoji font (Twemoji.ttf) for emoji support."); + } + + text_storage.default_font = Font(TTF_OpenFont((ASSETS_PATH + "fonts/Default.ttf").c_str(), 24)); + + if (!text_storage.default_font.get_native_handle()) { + spdlog::error("Failed to load Default font (Default.ttf): {}", SDL_GetError()); + return false; + } + + if (text_storage.emoji_font.get_native_handle()) { + if (!TTF_AddFallbackFont(text_storage.default_font.get_native_handle(), text_storage.emoji_font.get_native_handle())) { + spdlog::warn("Failed to add emoji fallback font: {}", SDL_GetError()); + } + } + + // TODO: we need to handle shader compilations (glslang, spirv-cross and dxcompiler) + shader = rd->shader_create_from_source(shaders::get_default_vertex_2d(), shaders::get_default_fragment_2d()); + + + if (shader == INVALID_RID) { + spdlog::error("Failed to create default shader for RenderingCanvas."); + return false; + } + + uint8_t white_pixel[] = {255, 255, 255, 255}; + white_texture = load_texture_from_memory(white_pixel, 1, 1, 4); + + SamplerState sampler_state; + sampler_state.min_filter = TextureFilter::LINEAR; + sampler_state.mag_filter = TextureFilter::LINEAR; + default_sampler = rd->sampler_create(sampler_state); + + const size_t max_vertices = 10000; + const size_t max_indices = 15000; + + vertex_buffer = rd->buffer_create(max_vertices * sizeof(Vertex), (uint32_t) BufferUsage::BUFFER_USAGE_VERTEX, nullptr); + + index_buffer = rd->buffer_create(max_indices * sizeof(uint16_t), (uint32_t) BufferUsage::BUFFER_USAGE_INDEX, nullptr); + + PipelineState pipeline_state; + pipeline_state.shader = shader; + pipeline_state.topology = PrimitiveTopology::TRIANGLES; + pipeline_state.vertex_format.stride = sizeof(Vertex); + pipeline_state.vertex_format.attributes = {{0, DataFormat::R32G32B32_SFLOAT, offsetof(Vertex, position)}, + {1, DataFormat::R32G32B32A32_SFLOAT, offsetof(Vertex, color)}, + {2, DataFormat::R32G32_SFLOAT, offsetof(Vertex, texcoord)}}; + pipeline_state.rasterization.cull_mode = CullMode::NONE; + pipeline_state.depth_stencil.depth_test_enable = false; + + BlendState blend; + blend.enable = true; + blend.src_color = BlendFactor::SRC_ALPHA; + blend.dst_color = BlendFactor::ONE_MINUS_SRC_ALPHA; + blend.src_alpha = BlendFactor::ONE; + blend.dst_alpha = BlendFactor::ONE_MINUS_SRC_ALPHA; + pipeline_state.blend_states.push_back(blend); + + pipeline = rd->pipeline_create(pipeline_state); + + if (pipeline == INVALID_RID) { + return false; + } + + reset_camera(); + return true; +} + +void RenderingCanvas::shutdown() { + if (pipeline != INVALID_RID) { + rd->pipeline_destroy(pipeline); + } + + if (vertex_buffer != INVALID_RID) { + rd->buffer_destroy(vertex_buffer); + } + + if (index_buffer != INVALID_RID) { + rd->buffer_destroy(index_buffer); + } + + if (white_texture != INVALID_RID) { + rd->texture_destroy(white_texture); + } + + if (default_sampler != INVALID_RID) { + rd->sampler_destroy(default_sampler); + } + + if (shader != INVALID_RID) { + rd->shader_destroy(shader); + } + + + for (auto& pair : custom_shader_pipelines) { + rd->pipeline_destroy(pair.second); + } + + for (auto& pair : text_storage.loaded_fonts) { + pair.second.destroy(); + } + + text_storage.loaded_fonts.clear(); + + text_storage.emoji_font.destroy(); + + text_storage.default_font.destroy(); + + custom_shader_pipelines.clear(); +} + +void RenderingCanvas::begin(const Color& color) { + int current_w, current_h; + + rd->get_drawable_size(current_w, current_h); + + if (current_w > 0 && current_h > 0 && (current_w != window_width || current_h != window_height)) { + window_width = current_w; + window_height = current_h; + viewport_width = current_w; + viewport_height = current_h; + } + + is_drawing = true; + vertices.clear(); + indices.clear(); + draw_commands.clear(); + + clear_color = color; +} + +void RenderingCanvas::end() { + int vp_x, vp_y, vp_w, vp_h; + calculate_viewport(viewport_width, viewport_height, vp_x, vp_y, vp_w, vp_h); + + Viewport viewport{(float) vp_x, (float) vp_y, (float) vp_w, (float) vp_h, 0.0f, 1.0f}; + Scissor scissor{0, 0, (uint32_t) viewport_width, (uint32_t) viewport_height}; + + if (!vertices.empty() && !indices.empty()) { + rd->buffer_update(vertex_buffer, 0, vertices.size() * sizeof(Vertex), vertices.data()); + rd->buffer_update(index_buffer, 0, indices.size() * sizeof(uint16_t), indices.data()); + } + + rd->begin_frame(); + rd->clear_color(clear_color.to_vec4()); + rd->render_pass_begin(INVALID_RID, viewport, scissor); + + flush(); + + rd->render_pass_end(); + rd->end_frame(); + + for (RID tex : text_storage.textures) { + unload_texture(tex); + } + + text_storage.textures.clear(); + + is_drawing = false; +} + +void RenderingCanvas::flush() { + if (vertices.empty() || indices.empty()) { + return; + } + + // Buffer updates already done in end() before render pass started + // flush(); + + rd->bind_pipeline(pipeline); + rd->bind_vertex_buffers({vertex_buffer}); + rd->bind_index_buffer(index_buffer, IndexType::UINT16); + + glm::mat4 vp = projection * view; + rd->push_constant("VIEW_PROJECTION_MATRIX", glm::value_ptr(vp), sizeof(glm::mat4)); + + for (const auto& cmd : draw_commands) { + RID tex = cmd.use_texture ? cmd.texture : white_texture; + + + RID sampler = default_sampler; + auto sampler_it = texture_samplers.find(tex); + + if (sampler_it != texture_samplers.end()) { + sampler = sampler_it->second; + } + + rd->bind_texture(0, tex, sampler); + + + rd->draw_indexed(cmd.index_count, 1, cmd.index_start, 0, 0); + } + + vertices.clear(); + indices.clear(); + draw_commands.clear(); +} + +void RenderingCanvas::draw_rect(float x, float y, float width, float height, const Color& color, float rotation) { + glm::mat4 transform = glm::translate(glm::mat4(1.0f), glm::vec3(x + width / 2, y + height / 2, 0)); + + if (rotation != 0.0f) { + transform = glm::rotate(transform, rotation, glm::vec3(0, 0, 1)); + } + + transform = glm::scale(transform, glm::vec3(width, height, 1)); + + add_quad(get_current_transform() * transform, color, white_texture, false); +} + +void RenderingCanvas::draw_rect_outlined(float x, float y, float width, float height, const Color& color, float thickness) { + + draw_rect(x, y, width, thickness, color); // TOP + + draw_rect(x, y + height - thickness, width, thickness, color); // BOTTOM + + draw_rect(x, y + thickness, thickness, height - 2 * thickness, color); // LEFT + + draw_rect(x + width - thickness, y + thickness, thickness, height - 2 * thickness, color); // RIGHT +} + +void RenderingCanvas::draw_circle(float x, float y, float radius, const Color& color, int segments) { + Vector points; + + for (int i = 0; i < segments; i++) { + float angle = (float) i / segments * 2.0f * 3.14159265359f; + points.push_back(glm::vec2(x + std::cos(angle) * radius, y + std::sin(angle) * radius)); + } + + draw_polygon(points, color); +} + +void RenderingCanvas::draw_circle_outlined(float x, float y, float radius, const Color& color, float thickness, int segments) { + if (segments < 3) { + segments = 32; + } + + const float angle_step = 2.0f * glm::pi() / segments; + + for (int i = 0; i < segments; i++) { + float angle1 = i * angle_step; + float angle2 = (i + 1) * angle_step; + + float x1 = x + glm::cos(angle1) * radius; + float y1 = y + glm::sin(angle1) * radius; + float x2 = x + glm::cos(angle2) * radius; + float y2 = y + glm::sin(angle2) * radius; + + draw_line(x1, y1, x2, y2, color, thickness); + } +} + +void RenderingCanvas::draw_arc(float x, float y, float radius, float start_angle, float end_angle, const Color& color, int segments) { + if (segments < 3) { + segments = 32; + } + + while (start_angle < 0.0f) { + start_angle += 2.0f * glm::pi(); + } + while (end_angle < 0.0f) { + end_angle += 2.0f * glm::pi(); + } + while (start_angle >= 2.0f * glm::pi()) { + start_angle -= 2.0f * glm::pi(); + } + while (end_angle >= 2.0f * glm::pi()) { + end_angle -= 2.0f * glm::pi(); + } + + float angle_span = end_angle - start_angle; + if (angle_span < 0.0f) { + angle_span += 2.0f * glm::pi(); + } + + int arc_segments = SDL_max(3, static_cast(segments * (angle_span / (2.0f * glm::pi())))); + + Vector points; + + points.push_back(glm::vec2(x, y)); + + for (int i = 0; i <= arc_segments; i++) { + float t = static_cast(i) / arc_segments; + float angle = start_angle + angle_span * t; + points.push_back(glm::vec2(x + glm::cos(angle) * radius, y + glm::sin(angle) * radius)); + } + + draw_polygon(points, color); +} + +void RenderingCanvas::get_texture_size(RID texture, uint32_t& out_width, uint32_t& out_height) { + if (texture == INVALID_RID) { + return; + } + + rd->get_texture_size(texture, out_width, out_height); +} + +Font RenderingCanvas::load_font_from_file(const char* filepath, int size) { + + if (text_storage.loaded_fonts.contains(filepath)) { + return text_storage.loaded_fonts.at(filepath); + } + + const Font font = Font(TTF_OpenFont(filepath, (float) size), size); + + if (!font.get_native_handle()) { + spdlog::error("Failed to load Font from file {}: {}", filepath, SDL_GetError()); + return {}; + } + + if (text_storage.emoji_font.get_native_handle()) { + if (!TTF_AddFallbackFont(font.get_native_handle(), text_storage.emoji_font.get_native_handle())) { + spdlog::warn("Failed to add emoji fallback font: {}", SDL_GetError()); + } + } + + text_storage.loaded_fonts[filepath] = font; + + spdlog::info("Loaded Font from file: {} (size {})", filepath, size); + + return font; +} + +void RenderingCanvas::set_blend_mode(BlendMode mode) { +} + +void RenderingCanvas::set_line_width(float width) { +} + +void RenderingCanvas::present() { + rd->swap_buffers(); +} + +RenderingDevice* RenderingCanvas::get_rendering_device() const { + return rd; +} + +void RenderingCanvas::setup_pipeline_for_blend_mode(BlendMode mode) { +} + +void RenderingCanvas::draw_line(float x1, float y1, float x2, float y2, const Color& color, float thickness) { + glm::vec2 dir = glm::normalize(glm::vec2(x2 - x1, y2 - y1)); + glm::vec2 perp(-dir.y, dir.x); + + const float half_thick = thickness * 0.5f; + + glm::vec2 p1 = glm::vec2(x1, y1) + perp * half_thick; + glm::vec2 p2 = glm::vec2(x1, y1) - perp * half_thick; + glm::vec2 p3 = glm::vec2(x2, y2) - perp * half_thick; + glm::vec2 p4 = glm::vec2(x2, y2) + perp * half_thick; + + draw_polygon({p1, p2, p3, p4}, color); +} + +void RenderingCanvas::draw_triangle(float x1, float y1, float x2, float y2, float x3, float y3, const Color& color) { + draw_polygon({glm::vec2(x1, y1), glm::vec2(x2, y2), glm::vec2(x3, y3)}, color); +} + +void RenderingCanvas::draw_polygon(const Vector& points, const Color& color) { + if (points.size() < 3) { + return; + } + + uint16_t base_vertex = vertices.size(); + glm::mat4 transform = get_current_transform(); + + for (const auto& p : points) { + glm::vec4 pos = transform * glm::vec4(p.x, p.y, 0, 1); + vertices.push_back({glm::vec3(pos), color.to_vec4(), glm::vec2(0, 0)}); + } + + for (size_t i = 1; i < points.size() - 1; i++) { + indices.push_back(base_vertex); + indices.push_back(base_vertex + i); + indices.push_back(base_vertex + i + 1); + } + + draw_commands.push_back( + {white_texture, (uint32_t) (indices.size() - (points.size() - 2) * 3), (uint32_t) ((points.size() - 2) * 3), false}); +} + +void RenderingCanvas::draw_texture(RID texture, float x, float y, float width, float height, const Color& tint, float rotation) { + + if (width == 0 || height == 0) { + uint32_t tex_w, tex_h; + rd->get_texture_size(texture, tex_w, tex_h); + width = (float) tex_w; + height = (float) tex_h; + } + + glm::mat4 transform = glm::translate(glm::mat4(1.0f), glm::vec3(x + width / 2, y + height / 2, 0)); + + if (rotation != 0.0f) { + transform = glm::rotate(transform, rotation, glm::vec3(0, 0, 1)); + } + + transform = glm::scale(transform, glm::vec3(width, height, 1)); + + add_quad(get_current_transform() * transform, tint, texture, true); +} + +void RenderingCanvas::add_quad(const glm::mat4& transform, const Color& color, RID texture, bool use_texture) { + uint16_t base_vertex = vertices.size(); + + glm::vec4 positions[] = {glm::vec4(-0.5f, -0.5f, 0, 1), glm::vec4(0.5f, -0.5f, 0, 1), glm::vec4(0.5f, 0.5f, 0, 1), + glm::vec4(-0.5f, 0.5f, 0, 1)}; + + glm::vec2 texcoords[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}}; + + for (int i = 0; i < 4; i++) { + glm::vec4 pos = transform * positions[i]; + vertices.push_back({glm::vec3(pos), color.to_vec4(), texcoords[i]}); + } + + indices.push_back(base_vertex + 0); + indices.push_back(base_vertex + 1); + indices.push_back(base_vertex + 2); + indices.push_back(base_vertex + 2); + indices.push_back(base_vertex + 3); + indices.push_back(base_vertex + 0); + + draw_commands.push_back({texture, (uint32_t) (indices.size() - 6), 6, use_texture}); +} + +void RenderingCanvas::set_camera(const glm::mat4& view_projection) { + projection = view_projection; + view = glm::mat4(1.0f); +} + +void RenderingCanvas::reset_camera() { + projection = glm::ortho(0.0f, (float) window_width, (float) window_height, 0.0f, -1.0f, 1.0f); + view = glm::mat4(1.0f); +} + +void RenderingCanvas::set_viewport_size(int width, int height) { + viewport_width = width; + viewport_height = height; +} + +void RenderingCanvas::set_scale_mode(ScaleMode mode) { + scale_mode = mode; +} + +void RenderingCanvas::calculate_viewport(int window_w, int window_h, int& out_x, int& out_y, int& out_w, int& out_h) { + switch (scale_mode) { + case ScaleMode::NONE: + out_x = (window_w - window_width) / 2; + out_y = (window_h - window_height) / 2; + out_w = window_width; + out_h = window_height; + break; + + case ScaleMode::KEEP: + { + float target_aspect = (float) window_width / (float) window_height; + float window_aspect = (float) window_w / (float) window_h; + + if (window_aspect > target_aspect) { + // pillarbox (black bars on sides) + out_h = window_h; + out_w = (int) (window_h * target_aspect); + out_x = (window_w - out_w) / 2; + out_y = 0; + } else { + // letterbox (black bars on top/bottom) + out_w = window_w; + out_h = (int) (window_w / target_aspect); + out_x = 0; + out_y = (window_h - out_h) / 2; + } + } + break; + + case ScaleMode::EXPAND: + out_x = 0; + out_y = 0; + out_w = window_w; + out_h = window_h; + break; + + default: + break; + } +} + +// void RenderingCanvas::push_transform(const glm::mat4& transform) { +// transform_stack.push_back(transform); +// } + +// void RenderingCanvas::pop_transform() { +// if (!transform_stack.empty()) { +// transform_stack.pop_back(); +// } +// } + +glm::mat4 RenderingCanvas::get_current_transform() const { + glm::mat4 result(1.0f); + + for (const auto& t : transform_stack) { + result = result * t; + } + + return result; +} + +RID RenderingCanvas::load_texture(const char* filepath) { + int width, height, channels; + stbi_uc* data = stbi_load(filepath, &width, &height, &channels, STBI_rgb_alpha); + + if (!data) { + spdlog::error("Failed to load texture '{}': {}", filepath, stbi_failure_reason()); + return INVALID_RID; + } + + spdlog::info("Loaded texture '{}' ({}x{})", filepath, width, height); + + TextureFormat format; + format.type = TextureType::TEXTURE_TYPE_2D; + format.format = DataFormat::R8G8B8A8_UNORM; + format.width = width; + format.height = height; + format.mipmaps = 1; + + RID rid = rd->texture_create(format, data); + + stbi_image_free(data); + + return rid; +} + +RID RenderingCanvas::load_texture(const char* filepath, const TextureDescription& desc) { + int width, height, channels; + stbi_uc* data = stbi_load(filepath, &width, &height, &channels, STBI_rgb_alpha); + + if (!data) { + spdlog::error("Failed to load texture '{}': {}", filepath, stbi_failure_reason()); + return INVALID_RID; + } + + + TextureFormat format; + format.type = TextureType::TEXTURE_TYPE_2D; + format.format = DataFormat::R8G8B8A8_UNORM; + format.width = width; + format.height = height; + format.mipmaps = desc.generate_mipmaps ? 1 : 0; + + RID texture = rd->texture_create(format, data); + + if (desc.generate_mipmaps && texture != INVALID_RID) { + rd->texture_generate_mipmaps(texture); + } + + stbi_image_free(data); + + if (texture != INVALID_RID) { + SamplerState sampler_state; + sampler_state.min_filter = desc.min_filter; + sampler_state.mag_filter = desc.mag_filter; + sampler_state.wrap_u = desc.wrap_u; + sampler_state.wrap_v = desc.wrap_v; + + RID sampler = rd->sampler_create(sampler_state); + texture_samplers[texture] = sampler; + } + + + spdlog::info("Loaded Texture '{}' Size ({}x{}) Description: mipmaps={}, min_filter={}, mag_filter={}, wrap_u={}, wrap_v={}", filepath, + width, height, desc.generate_mipmaps, static_cast(desc.min_filter), static_cast(desc.mag_filter), + static_cast(desc.wrap_u), static_cast(desc.wrap_v)); + + return texture; +} + +Texture RenderingCanvas::load_texture_from_file(const char* filepath) { + RID rid = load_texture(filepath); + + if (rid != INVALID_RID) { + return rd->get_texture(rid); + } + + return Texture{}; +} + +Texture RenderingCanvas::load_texture_from_file(const char* filepath, const TextureDescription& desc) { + RID rid = load_texture(filepath, desc); + + if (rid != INVALID_RID) { + return rd->get_texture(rid); + } + + return Texture{}; +} + + +RID RenderingCanvas::load_texture_from_memory(void* data, int width, int height, int channels) { + TextureFormat format; + format.type = TextureType::TEXTURE_TYPE_2D; + format.format = (channels == 4) ? DataFormat::R8G8B8A8_UNORM : DataFormat::R8G8B8_UNORM; + format.width = width; + format.height = height; + format.mipmaps = 1; + + return rd->texture_create(format, data); +} + +void RenderingCanvas::unload_texture(RID texture) { + auto it = texture_samplers.find(texture); + if (it != texture_samplers.end()) { + rd->sampler_destroy(it->second); + texture_samplers.erase(it); + } + rd->texture_destroy(texture); +} + +void RenderingCanvas::draw_text(Font font, float x, float y, const Color& color, const String& text) { + if (!font.get_native_handle()) { + spdlog::warn("DrawText called with null font!"); + return; + } + + if (text.empty()) { + return; + } + + SDL_Color text_color; + text_color.r = (uint8_t) (color.r * 255); + text_color.g = (uint8_t) (color.g * 255); + text_color.b = (uint8_t) (color.b * 255); + text_color.a = (uint8_t) (color.a * 255); + + SDL_Surface* text_surface = TTF_RenderText_Blended(font.get_native_handle(), text.data(), text.length(), text_color); + if (!text_surface) { + spdlog::error("Failed to render Text '{}': {}", text, SDL_GetError()); + return; + } + + SDL_Surface* rgba_surface = SDL_ConvertSurface(text_surface, SDL_PIXELFORMAT_RGBA32); + SDL_DestroySurface(text_surface); + + if (!rgba_surface) { + spdlog::error("Failed to convert Text surface to RGBA: {}", SDL_GetError()); + return; + } + + RID text_texture = load_texture_from_memory(rgba_surface->pixels, rgba_surface->w, rgba_surface->h, 4); + + if (text_texture == INVALID_RID) { + SDL_DestroySurface(rgba_surface); + spdlog::error("Failed to create Texture from Text surface"); + return; + } + + glm::mat4 transform = glm::translate(glm::mat4(1.0f), glm::vec3(x + rgba_surface->w / 2.0f, y + rgba_surface->h / 2.0f, 0)); + transform = glm::scale(transform, glm::vec3(rgba_surface->w, rgba_surface->h, 1)); + + + text_storage.textures.push_back(text_texture); + + add_quad(get_current_transform() * transform, Color::WHITE, text_texture, true); + + SDL_DestroySurface(rgba_surface); +} + +void RenderingCanvas::draw_text(float x, float y, const Color& color, const String& text) { + if (!text_storage.default_font.get_native_handle()) { + spdlog::warn("Default font not loaded, cannot draw text."); + return; + } + + draw_text(text_storage.default_font, x, y, color, text); +} + +RID RenderingCanvas::load_shader_from_source(const char* vertex_src, const char* fragment_src) { + return rd->shader_create_from_source(vertex_src, fragment_src); +} + +RID RenderingCanvas::load_shader_from_file(const char* filepath) { + ShaderSource parsed = load_shader_file(filepath); + + if (!parsed.is_loaded) { + spdlog::error("Failed to parse Shader file: {}", filepath); + return INVALID_RID; + } + + if (parsed.is_compute) { + spdlog::error("Compute Shaders not Supported for this Rendering: {}", filepath); + return INVALID_RID; + } + + spdlog::info("Creating shader from file: {}", filepath); + + shader = rd->shader_create_from_source(parsed.vertex_source.c_str(), parsed.fragment_source.c_str()); + + if (shader == INVALID_RID) { + spdlog::debug("=== VERTEX SHADER ===\n{}", parsed.vertex_source.c_str()); + spdlog::debug("=== FRAGMENT SHADER ===\n{}", parsed.fragment_source.c_str()); + spdlog::error("Failed to compile shader from file: {}", filepath); + } else { + spdlog::info("Shader compiled successfully: {} (RID={})", filepath, shader); + } + + return shader; +} + + +RID RenderingCanvas::load_shader_from_source(const char* source) { + ShaderSource parsed = parse_shader(source); + + return rd->shader_create_from_source(parsed.vertex_source, parsed.fragment_source); +} + +void RenderingCanvas::destroy_shader(RID shader) { + + auto it = custom_shader_pipelines.find(shader); + + if (it != custom_shader_pipelines.end()) { + rd->pipeline_destroy(it->second); + custom_shader_pipelines.erase(it); + } + + rd->shader_destroy(shader); +} + +RID RenderingCanvas::get_or_create_custom_pipeline(RID shader) { + + auto it = custom_shader_pipelines.find(shader); + + if (it != custom_shader_pipelines.end()) { + return it->second; + } + + PipelineState pipeline_state; + pipeline_state.shader = shader; + pipeline_state.topology = PrimitiveTopology::TRIANGLES; + pipeline_state.vertex_format.stride = sizeof(Vertex); + pipeline_state.vertex_format.attributes = {{0, DataFormat::R32G32B32_SFLOAT, offsetof(Vertex, position)}, + {1, DataFormat::R32G32B32A32_SFLOAT, offsetof(Vertex, color)}, + {2, DataFormat::R32G32_SFLOAT, offsetof(Vertex, texcoord)}}; + pipeline_state.rasterization.cull_mode = CullMode::NONE; + pipeline_state.depth_stencil.depth_test_enable = false; + + RID pso = rd->pipeline_create(pipeline_state); + + custom_shader_pipelines[shader] = pso; + + return pipeline; +} + +void RenderingCanvas::draw_custom(RID custom_shader, float x, float y, float width, float height, RID texture, const Color& color) { + if (!is_drawing) { + return; + } + + flush(); + + glm::mat4 transform = glm::translate(glm::mat4(1.0f), glm::vec3(x + width / 2, y + height / 2, 0)); + transform = glm::scale(transform, glm::vec3(width, height, 1)); + transform = get_current_transform() * transform; + + Vector custom_vertices; + Vector custom_indices; + + glm::vec4 positions[] = {glm::vec4(-0.5f, -0.5f, 0, 1), glm::vec4(0.5f, -0.5f, 0, 1), glm::vec4(0.5f, 0.5f, 0, 1), + glm::vec4(-0.5f, 0.5f, 0, 1)}; + glm::vec2 texcoords[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}}; + + for (int i = 0; i < 4; i++) { + glm::vec4 pos = transform * positions[i]; + custom_vertices.push_back({glm::vec3(pos), color.to_vec4(), texcoords[i]}); + } + + custom_indices = {0, 1, 2, 2, 3, 0}; + + + PipelineState custom_pipeline_state; + custom_pipeline_state.shader = custom_shader; + custom_pipeline_state.topology = PrimitiveTopology::TRIANGLES; + custom_pipeline_state.vertex_format.stride = sizeof(Vertex); + custom_pipeline_state.vertex_format.attributes = {{0, DataFormat::R32G32B32_SFLOAT, offsetof(Vertex, position)}, + {1, DataFormat::R32G32B32A32_SFLOAT, offsetof(Vertex, color)}, + {2, DataFormat::R32G32_SFLOAT, offsetof(Vertex, texcoord)}}; + + custom_pipeline_state.rasterization.cull_mode = CullMode::NONE; + custom_pipeline_state.depth_stencil.depth_test_enable = false; + + BlendState blend; + blend.enable = true; + blend.src_color = BlendFactor::SRC_ALPHA; + blend.dst_color = BlendFactor::ONE_MINUS_SRC_ALPHA; + blend.src_alpha = BlendFactor::ONE; + blend.dst_alpha = BlendFactor::ONE_MINUS_SRC_ALPHA; + custom_pipeline_state.blend_states.push_back(blend); + + RID custom_pipeline = rd->pipeline_create(custom_pipeline_state); + + rd->buffer_update(vertex_buffer, 0, custom_vertices.size() * sizeof(Vertex), custom_vertices.data()); + rd->buffer_update(index_buffer, 0, custom_indices.size() * sizeof(uint16_t), custom_indices.data()); + + rd->bind_pipeline(custom_pipeline); + rd->bind_vertex_buffers({vertex_buffer}); + rd->bind_index_buffer(index_buffer, IndexType::UINT16); + + glm::mat4 vp = projection * view; + + float time_value = SDL_GetTicks() / 1000.0f; + rd->push_constant("TIME", &time_value, sizeof(float)); + + if (texture != INVALID_RID) { + rd->bind_texture(0, texture, default_sampler); + int tex_unit = 0; + rd->push_constant("TEXTURE", &tex_unit, sizeof(int)); + } else { + rd->bind_texture(0, white_texture, default_sampler); + } + + rd->draw_indexed(6, 1, 0, 0, 0); + + rd->pipeline_destroy(custom_pipeline); + + rd->bind_pipeline(pipeline); +} + +void RenderingCanvas::draw_texture_ex(float x, float y, float width, float height, RID texture, const Rect& source, const Color& color, + float rotation, bool flip_h, bool flip_v, const CanvasMaterial* material) { + if (!is_drawing) { + return; + } + + RID final_texture = texture; + glm::vec4 final_color = color.to_vec4(); + + if (material) { + if (texture == INVALID_RID && material->get_texture() != INVALID_RID) { + final_texture = material->get_texture(); + } + + final_color *= material->get_color().to_vec4(); + } + + bool use_custom_shader = material && material->has_custom_shader(); + + if (use_custom_shader) { + flush(); + } + + glm::mat4 transform = glm::translate(glm::mat4(1.0f), glm::vec3(x + width / 2.0f, y + height / 2.0f, 0)); + + if (rotation != 0.0f) { + transform = glm::rotate(transform, rotation, glm::vec3(0, 0, 1)); + } + + transform = glm::scale(transform, glm::vec3(width, height, 1)); + transform = get_current_transform() * transform; + + glm::vec4 positions[] = {glm::vec4(-0.5f, -0.5f, 0, 1), glm::vec4(0.5f, -0.5f, 0, 1), glm::vec4(0.5f, 0.5f, 0, 1), + glm::vec4(-0.5f, 0.5f, 0, 1)}; + + glm::vec2 texcoords[4]; + float u_min = 0.0f, v_min = 0.0f, u_max = 1.0f, v_max = 1.0f; + + if (source.width > 0 && source.height > 0 && final_texture != INVALID_RID) { + uint32_t tex_width = 0, tex_height = 0; + rd->get_texture_size(final_texture, tex_width, tex_height); + + if (tex_width > 0 && tex_height > 0) { + u_min = source.x / (float) tex_width; + v_min = source.y / (float) tex_height; + u_max = (source.x + source.width) / (float) tex_width; + v_max = (source.y + source.height) / (float) tex_height; + } + } + + if (flip_h) { + float temp = u_min; + u_min = u_max; + u_max = temp; + } + + if (flip_v) { + float temp = v_min; + v_min = v_max; + v_max = temp; + } + + texcoords[0] = {u_min, v_min}; + texcoords[1] = {u_max, v_min}; + texcoords[2] = {u_max, v_max}; + texcoords[3] = {u_min, v_max}; + + if (use_custom_shader && material) { + + Vector custom_vertices; + Vector custom_indices; + + for (int i = 0; i < 4; i++) { + glm::vec4 pos = transform * positions[i]; + custom_vertices.push_back({glm::vec3(pos), final_color, texcoords[i]}); + } + + custom_indices = {0, 1, 2, 2, 3, 0}; + + RID shader_rid = material->get_shader(); + RID custom_pipeline = INVALID_RID; + + auto it = custom_shader_pipelines.find(shader_rid); + if (it != custom_shader_pipelines.end()) { + custom_pipeline = it->second; + } else { + PipelineState custom_pipeline_state; + custom_pipeline_state.shader = shader_rid; + custom_pipeline_state.topology = PrimitiveTopology::TRIANGLES; + custom_pipeline_state.vertex_format.stride = sizeof(Vertex); + custom_pipeline_state.vertex_format.attributes = {{0, DataFormat::R32G32B32_SFLOAT, offsetof(Vertex, position)}, + {1, DataFormat::R32G32B32A32_SFLOAT, offsetof(Vertex, color)}, + {2, DataFormat::R32G32_SFLOAT, offsetof(Vertex, texcoord)}}; + custom_pipeline_state.rasterization.cull_mode = CullMode::NONE; + custom_pipeline_state.depth_stencil.depth_test_enable = false; + + BlendState blend; + blend.enable = true; + blend.src_color = BlendFactor::SRC_ALPHA; + blend.dst_color = BlendFactor::ONE_MINUS_SRC_ALPHA; + blend.src_alpha = BlendFactor::ONE; + blend.dst_alpha = BlendFactor::ONE_MINUS_SRC_ALPHA; + custom_pipeline_state.blend_states.push_back(blend); + + custom_pipeline = rd->pipeline_create(custom_pipeline_state); + custom_shader_pipelines[shader_rid] = custom_pipeline; + + spdlog::info("Created custom Pipeline for Shader RID={}", shader_rid); + } + + rd->buffer_update(vertex_buffer, 0, custom_vertices.size() * sizeof(Vertex), custom_vertices.data()); + rd->buffer_update(index_buffer, 0, custom_indices.size() * sizeof(uint16_t), custom_indices.data()); + + rd->bind_pipeline(custom_pipeline); + rd->bind_vertex_buffers({vertex_buffer}); + rd->bind_index_buffer(index_buffer, IndexType::UINT16); + + glm::mat4 vp = projection * view; + rd->push_constant("MODEL_MATRIX", glm::value_ptr(transform), sizeof(glm::mat4)); + rd->push_constant("VIEW_MATRIX", glm::value_ptr(view), sizeof(glm::mat4)); + rd->push_constant("PROJECTION_MATRIX", glm::value_ptr(projection), sizeof(glm::mat4)); + rd->push_constant("VIEW_PROJECTION_MATRIX", glm::value_ptr(vp), sizeof(glm::mat4)); + + float time_value = SDL_GetTicks() / 1000.0f; /// TODO: get from engine DT + rd->push_constant("TIME", &time_value, sizeof(float)); + + int next_texture_unit = 1; + + for (const auto& [name, value] : material->get_uniforms()) { + std::visit( + [&](T0&& val) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + rd->push_constant(name, &val, sizeof(float)); + } else if constexpr (std::is_same_v) { + rd->push_constant(name, glm::value_ptr(val), sizeof(glm::vec2)); + } else if constexpr (std::is_same_v) { + rd->push_constant(name, glm::value_ptr(val), sizeof(glm::vec3)); + } else if constexpr (std::is_same_v) { + rd->push_constant(name, glm::value_ptr(val), sizeof(glm::vec4)); + } else if constexpr (std::is_same_v) { + rd->push_constant(name, &val, sizeof(int)); + } + }, + value); + } + + for (const auto& [name, tex_rid] : material->get_custom_textures()) { + if (tex_rid != INVALID_RID) { + rd->bind_texture(next_texture_unit, tex_rid, default_sampler); + rd->push_constant(name, &next_texture_unit, sizeof(int)); + next_texture_unit++; + } + } + + if (final_texture != INVALID_RID) { + rd->bind_texture(0, final_texture, default_sampler); + int tex_unit = 0; + rd->push_constant("TEXTURE", &tex_unit, sizeof(int)); + } else { + rd->bind_texture(0, white_texture, default_sampler); + } + + rd->draw_indexed(6, 1, 0, 0, 0); + + rd->bind_pipeline(pipeline); + } else { + for (int i = 0; i < 4; i++) { + glm::vec4 pos = transform * positions[i]; + vertices.push_back({glm::vec3(pos), final_color, texcoords[i]}); + } + + uint16_t base_index = (uint16_t) (vertices.size() - 4); + indices.push_back(base_index + 0); + indices.push_back(base_index + 1); + indices.push_back(base_index + 2); + indices.push_back(base_index + 2); + indices.push_back(base_index + 3); + indices.push_back(base_index + 0); + + RID tex = final_texture != INVALID_RID ? final_texture : white_texture; + + if (draw_commands.empty() || draw_commands.back().texture != tex) { + DrawCommand cmd{}; + cmd.texture = tex; + cmd.index_count = 6; + cmd.index_start = indices.size() - 6; + cmd.use_texture = true; + draw_commands.push_back(cmd); + } else { + draw_commands.back().index_count += 6; + } + } +} diff --git a/engine/private/servers/rendering/rendering_device.cpp b/engine/private/servers/rendering/rendering_device.cpp new file mode 100644 index 000000000..bda169a60 --- /dev/null +++ b/engine/private/servers/rendering/rendering_device.cpp @@ -0,0 +1,25 @@ +#include "servers/rendering/rendering_device.h" + + +const Color Color::WHITE = Color(1, 1, 1, 1); +const Color Color::BLACK = Color(0, 0, 0, 1); +const Color Color::RED = Color(1, 0, 0, 1); +const Color Color::GREEN = Color(0, 1, 0, 1); +const Color Color::BLUE = Color(0, 0, 1, 1); +const Color Color::YELLOW = Color(1, 1, 0, 1); +const Color Color::CYAN = Color(0, 1, 1, 1); +const Color Color::MAGENTA = Color(1, 0, 1, 1); + + +RID RIDAllocator::allocate_rid() { + return next_rid++; +} + +void RenderingDevice::get_drawable_size(int& width, int& height) { + if (_window) { + SDL_GetWindowSizeInPixels(_window, &width, &height); + } else { + width = 0; + height = 0; + } +} diff --git a/engine/private/servers/rendering/shader_material.cpp b/engine/private/servers/rendering/shader_material.cpp new file mode 100644 index 000000000..31a98c35a --- /dev/null +++ b/engine/private/servers/rendering/shader_material.cpp @@ -0,0 +1,55 @@ +#include "servers/rendering/shader_material.h" + + +CanvasMaterial& CanvasMaterial::set_shader(const RID shader_rid) { + shader = shader_rid; + return *this; +} + +CanvasMaterial& CanvasMaterial::set_color(const Color& col) { + color = col; + return *this; +} + +CanvasMaterial& CanvasMaterial::set_texture(const RID tex) { + texture = tex; + return *this; +} + +CanvasMaterial& CanvasMaterial::set_custom_texture(const char* name, RID tex) { + custom_textures[name] = tex; + return *this; +} + +RID CanvasMaterial::get_custom_texture(const char* uniform_name) const { + if (custom_textures.contains(uniform_name)) { + return custom_textures.at(uniform_name); + } + + return INVALID_RID; +} + +Color CanvasMaterial::get_color() const { + return color; +} + +RID CanvasMaterial::get_texture() const { + + return texture; +} + +RID CanvasMaterial::get_shader() const { + return shader; +} + +bool CanvasMaterial::has_custom_shader() const { + return shader != INVALID_RID; +} + +const HashMap& CanvasMaterial::get_uniforms() const { + return uniforms; +} + +const HashMap& CanvasMaterial::get_custom_textures() const { + return custom_textures; +} diff --git a/engine/private/servers/rendering/shader_preprocessor.cpp b/engine/private/servers/rendering/shader_preprocessor.cpp new file mode 100644 index 000000000..3d372480d --- /dev/null +++ b/engine/private/servers/rendering/shader_preprocessor.cpp @@ -0,0 +1,365 @@ +#include "servers/rendering/shader_preprocessor.h" + +std::string get_shader_header() { +#if defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_EMSCRIPTEN) + return "#version 300 es\nprecision highp float;\n\n"; +#else + return "#version 330 core\n\n"; +#endif +} + + +static void remove_builtin_declarations(std::string& shader_code) { + + // List of built-in varyings to remove (users shouldn't declare these) + const char* builtins[] = { + "out vec4 COLOR;", + "in vec4 COLOR;", + "out vec2 UV;", + "in vec2 UV;", + "out vec3 VERTEX;", + "in vec3 VERTEX;", + "varying COLOR;", + "varying UV;", + "varying VERTEX;" + }; + + for (const char* builtin : builtins) { + size_t pos = 0; + while ((pos = shader_code.find(builtin, pos)) != std::string::npos) { + // Find line start + size_t line_start = shader_code.rfind('\n', pos); + line_start = (line_start == std::string::npos) ? 0 : line_start + 1; + + // Find line end + size_t line_end = shader_code.find('\n', pos); + if (line_end == std::string::npos) { + line_end = shader_code.length(); + } else { + line_end++; // Include the newline + } + + shader_code.erase(line_start, line_end - line_start); + pos = line_start; + } + } +} + +static void remove_shader_type_directive(std::string& shader_code) { + // Remove "shader_type canvas_item;" or "shader_type spatial;" lines + const char* directives[] = { + "shader_type canvas_item;", + "shader_type spatial;" + }; + + for (const char* directive : directives) { + size_t pos = 0; + while ((pos = shader_code.find(directive, pos)) != std::string::npos) { + // Find line start + size_t line_start = shader_code.rfind('\n', pos); + line_start = (line_start == std::string::npos) ? 0 : line_start + 1; + + // Find line end + size_t line_end = shader_code.find('\n', pos); + if (line_end == std::string::npos) { + line_end = shader_code.length(); + } else { + line_end++; // Include the newline + } + + shader_code.erase(line_start, line_end - line_start); + pos = line_start; + } + } +} + + +static std::string inject_vertex_attributes(const std::string& shader_source) { + std::stringstream ss; + + ss << "// Vertex Attributes (auto-injected)\n"; + ss << "layout(location = 0) in vec3 a_vertex;\n"; + ss << "layout(location = 1) in vec4 a_color;\n"; + ss << "layout(location = 2) in vec2 a_uv;\n\n"; + + ss << "// Shader variables - writable in vertex shader\n"; + ss << "vec3 VERTEX;\n"; + ss << "vec4 COLOR;\n"; + ss << "vec2 UV;\n\n"; + + ss << "// Built-in Varyings (auto-injected)\n"; + ss << "out vec4 v_COLOR;\n"; + ss << "out vec2 v_UV;\n"; + ss << "out vec3 v_VERTEX;\n\n"; + + ss << shader_source; + + return ss.str(); +} + +static std::string inject_fragment_inputs(const std::string& shader_source) { + std::stringstream ss; + + ss << "// Built-in Varyings (auto-injected)\n"; + ss << "in vec4 v_COLOR;\n"; + ss << "in vec2 v_UV;\n"; + ss << "in vec3 v_VERTEX;\n\n"; + + ss << "// Shader variables\n"; + ss << "vec4 COLOR = vec4(1.0);\n"; + ss << "float ALPHA = 1.0;\n\n"; + + ss << "// Aliases for reading\n"; + ss << "#define UV v_UV\n"; + ss << "#define VERTEX v_VERTEX\n\n"; + + ss << shader_source; + + return ss.str(); +} + +static std::string inject_builtin_uniforms(const std::string& shader_source) { + std::stringstream ss; + + ss << "// Built-in Uniforms (auto-injected)\n"; + ss << "uniform mat4 MODEL_MATRIX;\n"; + ss << "uniform mat4 VIEW_MATRIX;\n"; + ss << "uniform mat4 PROJECTION_MATRIX;\n"; + ss << "uniform mat4 VIEW_PROJECTION_MATRIX;\n"; + ss << "uniform float TIME;\n"; + ss << "uniform vec3 CAMERA_POSITION;\n"; + ss << "uniform sampler2D TEXTURE;\n\n"; + ss << shader_source; + + return ss.str(); +} + +static std::string inject_fragment_outputs(const std::string& shader_source) { + std::stringstream ss; + + ss << "// Fragment Outputs (auto-injected)\n"; + ss << "out vec4 fragColor;\n\n"; + ss << shader_source; + + return ss.str(); +} + +// ============================================================================ +// Shader Parser - Processes custom shader syntax +// ============================================================================ +// Syntax: +// - shader_type canvas_item; -> 2D shader (supported) +// - shader_type spatial; -> 3D shader (not supported) +// - void vertex() {...} -> becomes vertex shader main() +// - void fragment() {...} -> becomes fragment shader main() +// - #[compute] tag for compute shaders +// ============================================================================ +ShaderSource parse_shader(const std::string& source) { + ShaderSource result; + + std::string shader_header; + + // ======================================== + // 1. Detect shader type + // ======================================== + const bool has_compute_tag = source.find("#[compute]") != std::string::npos; + const bool has_main = source.find("void main()") != std::string::npos; + const bool has_vertex = source.find("void vertex") != std::string::npos; + const bool has_fragment = source.find("void fragment") != std::string::npos; + + // Check for shader_type directives + const bool has_spatial = source.find("shader_type spatial") != std::string::npos; + const bool has_canvas_item = source.find("shader_type canvas_item") != std::string::npos; + + // Spatial shaders are not supported + if (has_spatial) { + spdlog::error("shader_type spatial is not supported"); + result.is_loaded = false; + return result; + } + + // Handle compute shaders (no processing needed) + if (has_compute_tag || (has_main && !has_vertex && !has_fragment)) { + + shader_header = "#version 450"; + + result.is_compute = true; + result.compute_source = shader_header + source; + result.is_loaded = true; + spdlog::info("Shader Type: Compute | Size: {} bytes", result.compute_source.size()); + return result; + } + + shader_header = get_shader_header(); + + // ======================================== + // 2. Add default GLSL version if missing + // ======================================== + if (source.find("#version") == std::string::npos) { + result.vertex_source.append(shader_header); + result.fragment_source.append(shader_header); + } + + // ======================================== + // 3. Validate shader has required functions + // ======================================== + const size_t vertex_pos = source.find("void vertex"); + const size_t fragment_pos = source.find("void fragment"); + + if (vertex_pos == std::string::npos || fragment_pos == std::string::npos) { + spdlog::error("Shader must define both 'void vertex()' and 'void fragment()'"); + result.is_loaded = false; + return result; + } + + // ======================================== + // 4. Split shader into sections + // ======================================== + // Extract common code (everything before first shader function) + const size_t first_shader_func = std::min(vertex_pos, fragment_pos); + std::string common_code = source.substr(0, first_shader_func); + + // Extract vertex shader code + const size_t vertex_end = fragment_pos; + std::string vertex_code = source.substr(vertex_pos, vertex_end - vertex_pos); + + // Extract fragment shader code + std::string fragment_code = source.substr(fragment_pos); + + // ======================================== + // 5. Inject built-ins + // ======================================== + // Clean common code - remove built-in declarations and shader_type directives + std::string clean_common_code = common_code; + remove_builtin_declarations(clean_common_code); + remove_shader_type_directive(clean_common_code); + + // Vertex shader gets: attributes + varyings + uniforms + std::string vertex_common = inject_builtin_uniforms(clean_common_code); + vertex_common = inject_vertex_attributes(vertex_common); + + // Fragment shader gets: varyings + uniforms + outputs + std::string fragment_common = inject_fragment_inputs(clean_common_code); + fragment_common = inject_builtin_uniforms(fragment_common); + + + // Convert "varying " keyword to "out " in vertex shader and "in " in fragment shader + size_t pos = 0; + while ((pos = vertex_common.find("varying ", pos)) != std::string::npos) { + vertex_common.replace(pos, 8, "out "); + pos += 4; + } + + pos = 0; + while ((pos = fragment_common.find("varying ", pos)) != std::string::npos) { + fragment_common.replace(pos, 8, "in "); + pos += 3; + } + + // handle traditional "out " declarations (convert to "in " for fragment) + pos = 0; + while ((pos = fragment_common.find("out ", pos)) != std::string::npos) { + fragment_common.replace(pos, 4, "in "); + pos += 3; + } + + // inject fragment outputs (after varying processing to avoid conversion) + fragment_common = inject_fragment_outputs(fragment_common); + + // ======================================== + // 6. Assemble final vertex shader + // ======================================== + result.vertex_source.append(vertex_common); + result.vertex_source.append("\n"); + result.vertex_source.append(vertex_code); + + // Replace "void vertex()" with "void main()" + size_t vertex_main_pos = result.vertex_source.find("void vertex()"); + if (vertex_main_pos != std::string::npos) { + result.vertex_source.replace(vertex_main_pos, 13, "void main()"); + + // Find the opening brace of main() and inject initialization + size_t main_start = result.vertex_source.find('{', vertex_main_pos); + if (main_start != std::string::npos) { + std::string init_code = + "\n // Initialize shader variables from attributes\n" + " VERTEX = a_vertex;\n" + " COLOR = a_color;\n" + " UV = a_uv;\n\n"; + result.vertex_source.insert(main_start + 1, init_code); + } + + size_t main_end = result.vertex_source.find_last_of('}'); + if (main_end != std::string::npos) { + std::string varying_passthrough = + " // Auto-pass built-in varyings\n" + " v_COLOR = COLOR;\n" + " v_UV = UV;\n" + " v_VERTEX = VERTEX;\n" + " \n" + " // Auto-inject gl_Position\n" + " gl_Position = VIEW_PROJECTION_MATRIX * vec4(VERTEX, 1.0);\n"; + result.vertex_source.insert(main_end, varying_passthrough); + } + } + + // ======================================== + // 8. Assemble final fragment shader + // ======================================== + result.fragment_source.append(fragment_common); + result.fragment_source.append("\n"); + result.fragment_source.append(fragment_code); + + // Replace "void fragment()" with "void main()" + size_t fragment_main_pos = result.fragment_source.find("void fragment()"); + if (fragment_main_pos != std::string::npos) { + result.fragment_source.replace(fragment_main_pos, 15, "void main()"); + + // Find the closing brace of main() and inject COLOR assignment to fragColor + size_t main_end = result.fragment_source.find_last_of('}'); + if (main_end != std::string::npos) { + std::string output_assignment = + " // Auto-assign COLOR and ALPHA to output\n" + " fragColor = vec4(COLOR.rgb, ALPHA);\n"; + result.fragment_source.insert(main_end, output_assignment); + } + } + + // ======================================== + // 9. Finalize and log metadata + // ======================================== + + spdlog::debug("=== Final Vertex Shader ===\n{}", result.vertex_source); + spdlog::debug("=== Final Fragment Shader ===\n{}", result.fragment_source); + + spdlog::info("Shader Type: {} | Vertex: {} bytes | Fragment: {} bytes", + has_canvas_item ? "canvas_item (2D)" : "Vertex/Fragment", + result.vertex_source.size(), result.fragment_source.size()); + result.is_loaded = true; + return result; +} + +ShaderSource load_shader_file(const char* filepath) { + ShaderSource result; + + SDL_IOStream* file = SDL_IOFromFile(filepath, "rb"); + if (!file) { + spdlog::error("Failed to open shader file: {}", filepath); + result.is_loaded = false; + return result; + } + + SDL_SeekIO(file, 0, SDL_IO_SEEK_END); + Sint64 file_size = SDL_TellIO(file); + SDL_SeekIO(file, 0, SDL_IO_SEEK_SET); + + + std::string shader_source; + shader_source.resize(file_size); + SDL_ReadIO(file, &shader_source[0], file_size); + SDL_CloseIO(file); + + spdlog::debug("Loaded shader file: {} ({} bytes)", filepath, file_size); + + return parse_shader(shader_source); +} \ No newline at end of file diff --git a/engine/private/stdafx.cpp b/engine/private/stdafx.cpp deleted file mode 100644 index d16e83ad4..000000000 --- a/engine/private/stdafx.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#define FLECS_CUSTOM_BUILD -#define FLECS_SYSTEM -#define FLECS_NO_LOG -#define FLECS_META -#define FLECS_CPP -#define FLECS_PIPELINE - - -#include "stdafx.h" - diff --git a/engine/public/core/api/engine_api.h b/engine/public/core/api/engine_api.h deleted file mode 100644 index d18b37ef6..000000000 --- a/engine/public/core/api/engine_api.h +++ /dev/null @@ -1,170 +0,0 @@ -#pragma once -#include "core/engine.h" - -/*! - * @brief Creates a 3D directional light entity with the specified properties. - * @param name The name of the entity. - * @param direction The direction vector of the light. - * @param color The color of the light. - * @param intensity The intensity of the light. - * @param cast_shadows Whether the light casts shadows. (default: true) - * @param shadow_near the near plane distance for shadow mapping. - * @param shadow_far the far plane distance for shadow mapping. - * @return The created GameObject representing the 3D directional light entity. - */ -GameObject create_directional_light_3d_entity(const char* name, const glm::vec3& direction = glm::vec3(0.0f, -1.0f, 0.0f), - const glm::vec3& color = glm::vec3(1.0f), float intensity = 1.0f, bool cast_shadows = true, - float shadow_distance = 200.0f, float shadow_near = 1.0f, float shadow_far = 500.0f); - -/*! - * @brief Creates a 3D spotlight entity with the specified properties. - * @param name The name of the entity. - * @param position The position of the spotlight in 3D space. - * @param direction The direction vector of the spotlight. - * @param color The color of the spotlight. - * @param inner_cutoff Inner cutoff angle in degrees. - * @param outer_cutoff Outer cutoff angle in degrees. - * @param intensity The intensity of the spotlight. - * @return The created GameObject representing the 3D spotlight entity. - */ -GameObject create_spot_light_3d_entity(const char* name, const glm::vec3& position = glm::vec3(0), - const glm::vec3& direction = glm::vec3(0, -1.0f, 0), - const glm::vec3& color = glm::vec3(1.0f, 1.0f, 1.0f), float inner_cutoff = 15.0f, - float outer_cutoff = 20.0f, float intensity = 1.0f); - - -/*! - * @brief Creates a 3D camera entity with the specified properties. - * @param name The name of the entity. - * @param position The position of the camera in 3D space. - * @param rotation The rotation of the camera in 3D space. - * @param fov The field of view of the camera in degrees. - * @param near_plane The near clipping plane distance. - * @param far_plane The far clipping plane distance. - * @return The created GameObject representing the 3D camera entity. - */ -GameObject create_camera_3d_entity(const char* name, const glm::vec3& position = glm::vec3(0), const glm::vec3& rotation = glm::vec3(0), - float fov = 45.0f, float near_plane = 0.1f, float far_plane = 5000.0f); - -/*! - * @brief Creates a 3D mesh entity from the specified file path with given properties. - * @param name The name of the entity. - * @param path The file path to the 3D mesh. - * @param position The position of the mesh in 3D space. - * @param rotation The rotation of the mesh in 3D space. - * @param scale The scale of the mesh in 3D space. - * @param material_tag The material tag to apply to the mesh. - * @return The created GameObject representing the 3D mesh entity. - */ -GameObject create_mesh_3d_entity(const char* name, const char* path, const glm::vec3& position = glm::vec3(0), - const glm::vec3& rotation = glm::vec3(0), const glm::vec3& scale = glm::vec3(1.0f), - const char* material_tag = "default_material"); - -/*! - * @brief Creates a 3D model entity from the specified file path with given properties. - * @param name The name of the entity. - * @param path The file path to the 3D model. - * @param blend_mode The blending mode for the model's material. - * @param position The position of the model in 3D space. - * @param rotation The rotation of the model in 3D space. - * @param scale The scale of the model in 3D space. - * @return The created GameObject representing the 3D model entity. - */ -GameObject create_model_3d_entity(const char* name, const char* path, BlendMode blend_mode = BlendMode::OPAQUE, - const glm::vec3& position = glm::vec3(0), const glm::vec3& rotation = glm::vec3(0), - const glm::vec3& scale = glm::vec3(1.0f)); - - -GameObject create_camera_2d_entity(const char* name, const glm::vec2& position = glm::vec2(0), float rotation = 0.0f, float zoom = 1.0f); - -/*! - * @brief Creates a 2D rectangle entity - * @param name The name of the entity - * @param position Position in 2D space - * @param size Size of the rectangle - * @param color Color of the rectangle - * @param filled Whether the rectangle is filled or outlined - * @return The created GameObject - */ -GameObject create_rectangle_2d_entity(const char* name, const glm::vec2& position = glm::vec2(0), - const glm::vec2& size = glm::vec2(100, 100), - const glm::vec4& color = glm::vec4(1.0f), bool filled = true); - -/*! - * @brief Creates a 2D circle entity - * @param name The name of the entity - * @param position Position in 2D space - * @param radius Radius of the circle - * @param color Color of the circle - * @param filled Whether the circle is filled or outlined - * @param thickness Thickness of the outline (only used when filled = false) - * @param segments Number of segments for circle smoothness - * @return The created GameObject - */ -GameObject create_circle_2d_entity(const char* name, const glm::vec2& position = glm::vec2(0), - float radius = 50.0f, const glm::vec4& color = glm::vec4(1.0f), - bool filled = true, float thickness = 1.0f, int segments = 32); - -/*! - * @brief Creates a 2D line entity - * @param name The name of the entity - * @param start Start position of the line - * @param end End position of the line - * @param color Color of the line - * @param thickness Thickness of the line - * @return The created GameObject - */ -GameObject create_line_2d_entity(const char* name, const glm::vec2& start = glm::vec2(0), - const glm::vec2& end = glm::vec2(100, 100), - const glm::vec4& color = glm::vec4(1.0f), float thickness = 1.0f); - -/*! - * @brief Creates a 2D triangle entity - * @param name The name of the entity - * @param p1 First point of the triangle - * @param p2 Second point of the triangle - * @param p3 Third point of the triangle - * @param color Color of the triangle - * @param filled Whether the triangle is filled - * @return The created GameObject - */ -GameObject create_triangle_2d_entity(const char* name, const glm::vec2& p1 = glm::vec2(0, 0), - const glm::vec2& p2 = glm::vec2(100, 0), - const glm::vec2& p3 = glm::vec2(50, 100), - const glm::vec4& color = glm::vec4(1.0f), bool filled = true); - -/*! - * @brief Creates a 2D text label entity - * @param name The name of the entity - * @param text The text to display - * @param position Position in 2D space - * @param color Color of the text - * @param scale Scale of the text - * @return The created GameObject - */ -GameObject create_label_2d_entity(const char* name, const std::string& text = "Text", - const glm::vec2& position = glm::vec2(0), - const glm::vec4& color = glm::vec4(1.0f), float scale = 1.0f); - -/*! - * @brief Creates a 2D sprite entity - * @param name The name of the entity - * @param texture_path Path to the texture file - * @param position Position in 2D space - * @param size Size of the sprite (0 = use texture size) - * @param color Tint color - * @return The created GameObject - */ -GameObject create_sprite_2d_entity(const char* name, const std::string& texture_path, - const glm::vec2& position = glm::vec2(0), - const glm::vec2& size = glm::vec2(0), - const glm::vec4& color = glm::vec4(1.0f)); - - - -/*! - * @brief Creates a material with the given name and properties. - * @param name - * @param material - */ -void create_material(const char* name, Material material); diff --git a/engine/public/core/component/comp_system.h b/engine/public/core/component/comp_system.h deleted file mode 100644 index e48175e96..000000000 --- a/engine/public/core/component/comp_system.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include "game_object.h" - -// 2D Rendering Systems -void render_rectangles_2d_system(); -void render_circles_2d_system(); -void render_lines_2d_system(); -void render_triangles_2d_system(); -void render_labels_2d_system(); -void render_sprites_2d_system(); - -void render_world_2d_system(); - -void render_world_3d_system(); \ No newline at end of file diff --git a/engine/public/core/component/components.h b/engine/public/core/component/components.h deleted file mode 100644 index 5eaa1f653..000000000 --- a/engine/public/core/component/components.h +++ /dev/null @@ -1,438 +0,0 @@ -#pragma once -#include "core/renderer/base_struct.h" -#include "stdafx.h" - -// win sh1t -#ifdef OPAQUE - #undef OPAQUE -#endif -#ifdef TRANSPARENT - #undef TRANSPARENT -#endif - -/** - * @brief Blending modes for materials - */ -enum class BlendMode { - OPAQUE, // No blending, fully opaque (default) - TRANSPARENT, // Alpha blending: src_alpha, 1 - src_alpha - ADDITIVE, // Additive blending: src_alpha, 1 - MULTIPLY, // Multiplicative blending: dst_color, 0 - SCREEN, // Screen blending: 1, 1 - src_color - OVERLAY // Overlay blending -}; - -/*! - * @brief Represents the world transformation matrix for a 3D object. - * @ingroup Components - */ -struct WorldTransform { - glm::mat4 matrix = glm::mat4(1.0f); -}; - -/*! - * @brief Represents material properties for 3D rendering. - * @ingroup Components - */ -class Transform3D { -public: - glm::vec3 position{0.0f}; - glm::vec3 rotation{0.0f}; - glm::vec3 scale{1.0f}; - - glm::mat4 get_matrix() const; -}; - -/*! - @brief 2D Transform component for position, scale, and rotation. - @ingroup Components -*/ -struct Transform2D { - glm::vec2 position = glm::vec2(0.0f); - glm::vec2 scale = glm::vec2(1.0f); - float rotation = 0.0f; - - glm::vec2 origin = glm::vec2(0.0f); - - Transform2D() = default; - - explicit Transform2D(const glm::vec2& pos, const glm::vec2& scl = glm::vec2(1.0f), float rot = 0.0f) - : position(pos), scale(scl), rotation(rot) { - } - - glm::mat3 get_matrix() const; -}; - -/*! - * @brief Represents a physics body for 2D or 3D physics simulations. - * Jolt Physics -> BodyID - * Box2D -> b2BodyId - * @ingroup Components - */ -struct PhysicsBody { - Uint32 id = 0; -}; - -/*! - * @brief Represents a 3D mesh instance with associated GPU buffers. - * @ingroup Components - */ -struct MeshInstance3D { - std::string name; - - std::shared_ptr vertex_buffer = nullptr; - std::shared_ptr index_buffer = nullptr; - std::shared_ptr vertex_layout = nullptr; - - int index_count = 0; -}; - -class Shader; - -/*! - * @brief Represents material properties for 3D rendering. - * @ingroup Components - */ -struct Material { - glm::vec3 albedo = glm::vec3(1.0f); - glm::vec3 specular = glm::vec3(0.0f); - float metallic = 0.0f; - float roughness = 0.2f; - float ao = 1.0f; /// Ambient Occlusion - - glm::vec3 emissive = glm::vec3(0.0f); - float emissive_strength = 1.0f; - float ior = 1.0f; // Index of Refraction - - BlendMode blend_mode = BlendMode::OPAQUE; - bool use_ibl = true; - - Uint32 albedo_map = 0; - Uint32 specular_map = 0; - Uint32 metallic_map = 0; - Uint32 roughness_map = 0; - Uint32 normal_map = 0; - Uint32 ao_map = 0; - Uint32 emissive_map = 0; - - bool use_albedo_map = false; - bool use_specular_map = false; - bool use_metallic_map = false; - bool use_roughness_map = false; - bool use_normal_map = false; - bool use_ao_map = false; - bool use_emissive_map = false; - - void bind(Shader* shader) const; - - void update_feature_flags() { - has_features = 0; - if (use_albedo_map) { - has_features |= HAS_ALBEDO_MAP; - } - if (use_specular_map) { - has_features |= HAS_SPECULAR_MAP; - } - if (use_metallic_map) { - has_features |= HAS_METALLIC_MAP; - } - if (use_roughness_map) { - has_features |= HAS_ROUGHNESS_MAP; - } - if (use_normal_map) { - has_features |= HAS_NORMAL_MAP; - } - if (use_ao_map) { - has_features |= HAS_AO_MAP; - } - if (use_emissive_map) { - has_features |= HAS_EMISSIVE_MAP; - } - if (use_ibl) { - has_features |= HAS_IBL; - } - } - - int has_features = 0; -}; - -struct Tag { - std::string_view name = ""; -}; - - -/*! - - @brief 3D Camera - - Position - - Zoom - - Rotation - - View matrix - - Projection matrix - - @ingroup Components - @version 0.0.1 - -*/ -struct Camera3D { - float yaw = -90.0f; // Z - float pitch = 0.0f; - float fov = 45.0f; - float speed = 5.0f; - float view_distance = 5000.f; - - explicit Camera3D() { - update_vectors(); - } - - glm::mat4 get_view(const Transform3D& transform) const; - - glm::mat4 get_projection(int w, int h) const; - - void move_forward(Transform3D& transform, float dt); - - void move_backward(Transform3D& transform, float dt); - - void move_left(Transform3D& transform, float dt); - - void move_right(Transform3D& transform, float dt); - - void look_at(float xoffset, float yoffset, float sensitivity = 0.1f); - - void zoom(float yoffset); - -private: - glm::vec3 front{0.0f, 0.0f, -1.0f}; - glm::vec3 up = glm::vec3(0.0f); - glm::vec3 right = glm::vec3(0.0f); - glm::vec3 world_up{0.0f, 1.0f, 0.0f}; - - void update_vectors(); -}; - -/*! - * @brief Represents a directional light in 3D space. - * @ingroup Components - */ -struct DirectionalLight3D { - glm::vec3 direction = {0.0f, -1.0f, 0.0f}; - glm::vec3 color = glm::vec3(1.0f); - float intensity = 1.0f; - bool castShadows = true; - - float shadowOrthoSize = 150.0f; - float shadowNear = 0.1f; // Near plane - float shadowFar = 5000.0f; // Far plane - - - glm::mat4 get_light_space_matrix(glm::mat4 camera_view, glm::mat4 camera_proj) const; - - glm::mat4 get_light_space_matrix(const glm::vec3& camera_position) const; -}; - - -/*! - * @brief Represents a spotlight in 3D space. - * @ingroup Components - */ -struct SpotLight3D { - glm::vec3 direction{0.0f, -1.0f, 0.0f}; - glm::vec3 color{1.0f}; - float intensity = 1.0f; - float cutOff = 12.5f; - float outerCutOff = 17.5f; -}; - - -/*! - * @brief Represents the world environment settings for 3D rendering. - * @ingroup Components - */ -struct WorldEnvironment3D { - Uint32 texture = 0; - - glm::vec3 color = glm::vec3(0.2, 0.3, 0.3); - std::shared_ptr vertex_buffer = nullptr; - std::shared_ptr index_buffer = nullptr; - std::shared_ptr vertex_layout = nullptr; - - float brightness = 1.0f; -}; - - -/*! - * @brief 2D Camera for orthographic projection. - * @ingroup Components - */ -class Camera2D { -public: - glm::vec2 position = glm::vec2(0.0f); - float rotation = 0.0f; - float zoom = 1.0f; - glm::vec2 viewport_size = glm::vec2(1.0f); /// 1.0 = use window size - - glm::mat4 get_view_matrix() const; - - glm::mat4 get_projection_matrix() const; - - void move(const glm::vec2& offset); - - void rotate(float radians); - - void zoom_by(float factor); - - void set_zoom(float new_zoom); - - void look_at(const glm::vec2& target); -}; - -/*! - * @brief Represents a native script component C++ class instance. - * @ingroup Components - */ -struct NativeScript { - void* instance = nullptr; -}; - -/*! - * @brief Represents a Lua script component. - * @ingroup Components - */ -struct LuaScript { - std::string script_path; - sol::state_view lua_state; -}; - -struct SpriteRenderer2D { - Uint32 texture_id = -1; // TODO: later shared_ptr - glm::vec4 color = glm::vec4(1.0f); -}; - -/*! - * @brief Represents a 3D model composed of multiple meshes and materials. - * @ingroup Components - */ -struct Model { - std::string path; - std::vector meshes; - std::vector materials; -}; - -/*! - * @brief Reference to a mesh used in rendering. - * @ingroup Core - */ -struct MeshRef { - const MeshInstance3D* mesh; -}; - -/*! - * @brief Reference to a material used in rendering. - * @ingroup Core - */ -struct MaterialRef { - const Material* material; -}; - -/*! - * @brief Tags namespace for engine components - * @ingroup Tags - */ -namespace tags { - struct Scene {}; - struct ActiveScene {}; - struct MainCamera {}; -} - -/*! - * @brief 2D Sprite component for rendering textures - * @ingroup Components - */ -struct Sprite2D { - std::string texture_name; - glm::vec4 color = glm::vec4(1.0f); - glm::vec2 size = glm::vec2(0.0f); // 0 = use texture size -}; - -/*! - * @brief 2D Text Label component - * @ingroup Components - */ -struct Label2D { - std::string text; - glm::vec4 color = glm::vec4(1.0f); - float scale = 1.0f; - std::string font = "default"; -}; - -/*! - * @brief Shape renderer for 2D primitives - * @ingroup Components - */ -enum class ShapeType { LINE, CIRCLE, RECTANGLE, CAPSULE, TRIANGLE }; - -struct Shape2D { - ShapeType type = ShapeType::RECTANGLE; - glm::vec4 color = glm::vec4(1.0f); - bool filled = true; - glm::vec2 size = glm::vec2(1.0f); - float radius = 10.0f; /// for circles - float thickness = 1.0f; /// for outlines -}; - -/*! - * @brief Rectangle 2D component - * @ingroup Components - */ -struct Rectangle2D { - glm::vec2 size = glm::vec2(100.0f, 100.0f); - glm::vec4 color = glm::vec4(1.0f); - bool filled = true; -}; - -/*! - * @brief Circle 2D component - * @ingroup Components - */ -struct Circle2D { - float radius = 50.0f; - glm::vec4 color = glm::vec4(1.0f); - bool filled = true; - float thickness = 1.0f; // Only used when filled = false - int segments = 32; -}; - -/*! - * @brief Line 2D component - * @ingroup Components - */ -struct Line2D { - glm::vec2 end_point = glm::vec2(100.0f, 100.0f); // Relative to entity position - glm::vec4 color = glm::vec4(1.0f); - float thickness = 1.0f; -}; - -/*! - * @brief Triangle 2D component - * @ingroup Components - */ -struct Triangle2D { - glm::vec2 point2 = glm::vec2(100.0f, 0.0f); // Relative to entity position - glm::vec2 point3 = glm::vec2(50.0f, 100.0f); // Relative to entity position - glm::vec4 color = glm::vec4(1.0f); - bool filled = true; -}; - -/*! - * @brief Follow component for camera following - * @ingroup Components - */ -struct Follow { - flecs::entity target; - glm::vec3 offset = glm::vec3(0.0f); - float smoothing = 0.1f; -}; - - - -struct SceneTag {}; diff --git a/engine/public/core/component/game_object.h b/engine/public/core/component/game_object.h deleted file mode 100644 index 2be97aae6..000000000 --- a/engine/public/core/component/game_object.h +++ /dev/null @@ -1,77 +0,0 @@ -#pragma once - -#include "components.h" - - - -/*! - * @brief Represents a game object in the ECS world. - * @ingroup Core - */ -class GameObject { -public: - - GameObject() = default; - - explicit GameObject(const flecs::world& world) : _id(world.entity()) { - } - - explicit GameObject(const flecs::entity entity) : _id(entity) { - } - - Uint32 get_id() const; - - bool is_valid() const; - - const char* get_name() const; - - void set_name(const char* name); - - bool compare_tag(const char* tag) const; - - template - T& add_component(Args&&... args); - - template - void add_component() { - _id.add(); - } - - template - void remove_component() { - _id.remove(); - } - - template - T* get_component() { - return const_cast(_id.try_get()); - } - - void free() const; - - - flecs::entity& entity(); - - const flecs::entity& entity() const; - - - operator flecs::entity() const; - -private: - flecs::entity _id; - -}; - - -template -T& GameObject::add_component(Args&&... args) { - if constexpr (sizeof...(Args) == 0) { - return _id.ensure(); - } else if constexpr (sizeof...(Args) == 1 && (std::is_same_v, T> && ...)) { - _id.set(std::forward(args)...); - return _id.ensure(); - } else { - _id.set(T(std::forward(args)...)); - return _id.ensure(); - } -} diff --git a/engine/public/core/engine.h b/engine/public/core/engine.h deleted file mode 100644 index ac21dafb7..000000000 --- a/engine/public/core/engine.h +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once -#include "core/renderer/opengl/ogl_renderer.h" -#include "core/system/timer.h" -#include "core/utility/project_config.h" -#include "core/utility/obj_loader.h" -#include "core/api/engine_api.h" - -/*! - @file engine.h - @brief Engine class definition. - - This file contains the definition of the Engine class, which serves as the core of the Ember Engine. It manages the main loop, window creation, rendering, and event handling. - - @version 0.0.1 - -*/ -class Engine { -public: - bool initialize(int window_w, int window_h, const char* title = "Golias Engine - Window", Uint32 window_flags = SDL_WINDOW_RESIZABLE); - - void run() const; - - Timer& get_timer(); - - Renderer* get_renderer() const; - - SDL_Window* get_window() const; - - EngineConfig& get_config(); - - flecs::world& get_world(); - - bool is_running = false; - - SDL_Event event; - - ~Engine(); - - -private: - EngineConfig _config = {}; - Timer _timer = {}; - flecs::world _world; - SDL_Window* _window = nullptr; - Renderer* _renderer = nullptr; - - -}; - -/*! - - @brief Core engine loop function. - - @note This function should never be called directly. It is used internally by the engine to handle events, update the world, and render frames. - - @version 0.0.1 -*/ -void engine_core_loop(); - -void engine_draw_loop(); - -/*! - - @brief Sets up the core systems in the provided Flecs world. - - This function registers core systems required for the engine's operation - - @param world Reference to the Flecs world where systems will be registered. - - @version 0.0.1 -*/ - -void engine_setup_systems(flecs::world& world); - -extern std::unique_ptr GEngine; \ No newline at end of file diff --git a/engine/public/core/gstl/dictionary.h b/engine/public/core/gstl/dictionary.h new file mode 100644 index 000000000..e72ec26ff --- /dev/null +++ b/engine/public/core/gstl/dictionary.h @@ -0,0 +1,74 @@ +#pragma once + +#include "str.h" +#include +#include + + +class Dictionary { + HashMap data; + +public: + Dictionary() = default; + + size_t size() const; + + bool is_empty() const; + + void clear(); + + template + void set(const String& key, const T& value) { + data[key] = value; + } + + template + T get(const String& key, const T& default_value = T{}) const { + auto it = data.find(key); + if (it == data.end()) { + return default_value; + } + + try { + return std::any_cast(it->second); + } catch (const std::bad_any_cast&) { + return default_value; + } + } + + bool has(const String& key) const; + + bool erase(const String& key); + + Vector keys() const; + + template + bool is_type(const String& key) const { + auto it = data.find(key); + + if (it == data.end()) { + return false; + } + + return it->second.type() == typeid(T); + } + + std::type_index get_type(const String& key) const; + + void merge(const Dictionary& other, bool overwrite = false); + + template + void for_each(Func func); + + auto begin(); + auto end(); + auto begin() const; + auto end() const; +}; + +template +inline void Dictionary::for_each(Func func) { + for (auto& pair : data) { + func(pair.first, pair.second); + } +} diff --git a/engine/public/core/gstl/locale.h b/engine/public/core/gstl/locale.h new file mode 100644 index 000000000..4a2ece60f --- /dev/null +++ b/engine/public/core/gstl/locale.h @@ -0,0 +1,879 @@ +#pragma once + +static const char *language_list[][2] = { + { "aa", "Afar" }, + { "ab", "Abkhazian" }, + { "ace", "Achinese" }, + { "ach", "Acoli" }, + { "ada", "Adangme" }, + { "ady", "Adyghe" }, + { "ae", "Avestan" }, + { "aeb", "Tunisian Arabic" }, + { "af", "Afrikaans" }, + { "afh", "Afrihili" }, + { "agq", "Aghem" }, + { "ain", "Ainu" }, + { "agr", "Aguaruna" }, + { "ak", "Akan" }, + { "akk", "Akkadian" }, + { "akz", "Alabama" }, + { "ale", "Aleut" }, + { "aln", "Gheg Albanian" }, + { "alt", "Southern Altai" }, + { "am", "Amharic" }, + { "an", "Aragonese" }, + { "ang", "Old English" }, + { "anp", "Angika" }, + { "ar", "Arabic" }, + { "arc", "Aramaic" }, + { "arn", "Mapudungun" }, + { "aro", "Araona" }, + { "arp", "Arapaho" }, + { "arq", "Algerian Arabic" }, + { "ars", "Najdi Arabic" }, + { "arw", "Arawak" }, + { "ary", "Moroccan Arabic" }, + { "arz", "Egyptian Arabic" }, + { "as", "Assamese" }, + { "asa", "Asu" }, + { "ase", "American Sign Language" }, + { "ast", "Asturian" }, + { "av", "Avaric" }, + { "avk", "Kotava" }, + { "awa", "Awadhi" }, + { "ayc", "Southern Aymara" }, + { "ay", "Aymara" }, + { "az", "Azerbaijani" }, + { "ba", "Bashkir" }, + { "bal", "Baluchi" }, + { "ban", "Balinese" }, + { "bar", "Bavarian" }, + { "bas", "Bassa" }, + { "bax", "Bamun" }, + { "bbc", "Batak Toba" }, + { "bbj", "Ghomala" }, + { "be", "Belarusian" }, + { "bej", "Beja" }, + { "bem", "Bemba" }, + { "ber", "Berber" }, + { "bew", "Betawi" }, + { "bez", "Bena" }, + { "bfd", "Bafut" }, + { "bfq", "Badaga" }, + { "bg", "Bulgarian" }, + { "bhb", "Bhili" }, + { "bgn", "Western Balochi" }, + { "bho", "Bhojpuri" }, + { "bi", "Bislama" }, + { "bik", "Bikol" }, + { "bin", "Bini" }, + { "bjn", "Banjar" }, + { "bkm", "Kom" }, + { "bla", "Siksika" }, + { "bm", "Bambara" }, + { "bn", "Bengali" }, + { "bo", "Tibetan" }, + { "bpy", "Bishnupriya" }, + { "bqi", "Bakhtiari" }, + { "br", "Breton" }, + { "brh", "Brahui" }, + { "brx", "Bodo" }, + { "bs", "Bosnian" }, + { "bss", "Akoose" }, + { "bua", "Buriat" }, + { "bug", "Buginese" }, + { "bum", "Bulu" }, + { "byn", "Bilin" }, + { "byv", "Medumba" }, + { "ca", "Catalan" }, + { "cad", "Caddo" }, + { "car", "Carib" }, + { "cay", "Cayuga" }, + { "cch", "Atsam" }, + { "ccp", "Chakma" }, + { "ce", "Chechen" }, + { "ceb", "Cebuano" }, + { "cgg", "Chiga" }, + { "ch", "Chamorro" }, + { "chb", "Chibcha" }, + { "chg", "Chagatai" }, + { "chk", "Chuukese" }, + { "chm", "Mari" }, + { "chn", "Chinook Jargon" }, + { "cho", "Choctaw" }, + { "chp", "Chipewyan" }, + { "chr", "Cherokee" }, + { "chy", "Cheyenne" }, + { "cic", "Chickasaw" }, + { "ckb", "Central Kurdish" }, + { "csb", "Kashubian" }, + { "cmn", "Mandarin Chinese" }, + { "co", "Corsican" }, + { "cop", "Coptic" }, + { "cps", "Capiznon" }, + { "cr", "Cree" }, + { "crh", "Crimean Tatar" }, + { "crs", "Seselwa Creole French" }, + { "cs", "Czech" }, + { "csb", "Kashubian" }, + { "cu", "Church Slavic" }, + { "cv", "Chuvash" }, + { "cy", "Welsh" }, + { "da", "Danish" }, + { "dak", "Dakota" }, + { "dar", "Dargwa" }, + { "dav", "Taita" }, + { "de", "German" }, + { "del", "Delaware" }, + { "den", "Slave" }, + { "dgr", "Dogrib" }, + { "din", "Dinka" }, + { "dje", "Zarma" }, + { "doi", "Dogri" }, + { "dsb", "Lower Sorbian" }, + { "dtp", "Central Dusun" }, + { "dua", "Duala" }, + { "dum", "Middle Dutch" }, + { "dv", "Dhivehi" }, + { "dyo", "Jola-Fonyi" }, + { "dyu", "Dyula" }, + { "dz", "Dzongkha" }, + { "dzg", "Dazaga" }, + { "ebu", "Embu" }, + { "ee", "Ewe" }, + { "efi", "Efik" }, + { "egl", "Emilian" }, + { "egy", "Ancient Egyptian" }, + { "eka", "Ekajuk" }, + { "el", "Greek" }, + { "elx", "Elamite" }, + { "en", "English" }, + { "enm", "Middle English" }, + { "eo", "Esperanto" }, + { "es", "Spanish" }, + { "esu", "Central Yupik" }, + { "et", "Estonian" }, + { "eu", "Basque" }, + { "ewo", "Ewondo" }, + { "ext", "Extremaduran" }, + { "fa", "Persian" }, + { "fan", "Fang" }, + { "fat", "Fanti" }, + { "ff", "Fulah" }, + { "fi", "Finnish" }, + { "fil", "Filipino" }, + { "fit", "Tornedalen Finnish" }, + { "fj", "Fijian" }, + { "fo", "Faroese" }, + { "fon", "Fon" }, + { "fr", "French" }, + { "frc", "Cajun French" }, + { "frm", "Middle French" }, + { "fro", "Old French" }, + { "frp", "Arpitan" }, + { "frr", "Northern Frisian" }, + { "frs", "Eastern Frisian" }, + { "fur", "Friulian" }, + { "fy", "Western Frisian" }, + { "ga", "Irish" }, + { "gaa", "Ga" }, + { "gag", "Gagauz" }, + { "gan", "Gan Chinese" }, + { "gay", "Gayo" }, + { "gba", "Gbaya" }, + { "gbz", "Zoroastrian Dari" }, + { "gd", "Scottish Gaelic" }, + { "gez", "Geez" }, + { "gil", "Gilbertese" }, + { "gl", "Galician" }, + { "glk", "Gilaki" }, + { "gmh", "Middle High German" }, + { "gn", "Guarani" }, + { "goh", "Old High German" }, + { "gom", "Goan Konkani" }, + { "gon", "Gondi" }, + { "gor", "Gorontalo" }, + { "got", "Gothic" }, + { "grb", "Grebo" }, + { "grc", "Ancient Greek" }, + { "gsw", "Swiss German" }, + { "gu", "Gujarati" }, + { "guc", "Wayuu" }, + { "gur", "Frafra" }, + { "guz", "Gusii" }, + { "gv", "Manx" }, + { "gwi", "Gwichʼin" }, + { "ha", "Hausa" }, + { "hai", "Haida" }, + { "hak", "Hakka Chinese" }, + { "haw", "Hawaiian" }, + { "he", "Hebrew" }, + { "hi", "Hindi" }, + { "hif", "Fiji Hindi" }, + { "hil", "Hiligaynon" }, + { "hit", "Hittite" }, + { "hmn", "Hmong" }, + { "ho", "Hiri Motu" }, + { "hne", "Chhattisgarhi" }, + { "hr", "Croatian" }, + { "hsb", "Upper Sorbian" }, + { "hsn", "Xiang Chinese" }, + { "ht", "Haitian" }, + { "hu", "Hungarian" }, + { "hup", "Hupa" }, + { "hus", "Huastec" }, + { "hy", "Armenian" }, + { "hz", "Herero" }, + { "ia", "Interlingua" }, + { "iba", "Iban" }, + { "ibb", "Ibibio" }, + { "id", "Indonesian" }, + { "ie", "Interlingue" }, + { "ig", "Igbo" }, + { "ii", "Sichuan Yi" }, + { "ik", "Inupiaq" }, + { "ilo", "Iloko" }, + { "inh", "Ingush" }, + { "io", "Ido" }, + { "is", "Icelandic" }, + { "it", "Italian" }, + { "iu", "Inuktitut" }, + { "izh", "Ingrian" }, + { "ja", "Japanese" }, + { "jam", "Jamaican Creole English" }, + { "jbo", "Lojban" }, + { "jgo", "Ngomba" }, + { "jmc", "Machame" }, + { "jpr", "Judeo-Persian" }, + { "jrb", "Judeo-Arabic" }, + { "jut", "Jutish" }, + { "jv", "Javanese" }, + { "ka", "Georgian" }, + { "kaa", "Kara-Kalpak" }, + { "kab", "Kabyle" }, + { "kac", "Kachin" }, + { "kaj", "Jju" }, + { "kam", "Kamba" }, + { "kaw", "Kawi" }, + { "kbd", "Kabardian" }, + { "kbl", "Kanembu" }, + { "kcg", "Tyap" }, + { "kde", "Makonde" }, + { "kea", "Kabuverdianu" }, + { "ken", "Kenyang" }, + { "kfo", "Koro" }, + { "kg", "Kongo" }, + { "kgp", "Kaingang" }, + { "kha", "Khasi" }, + { "kho", "Khotanese" }, + { "khq", "Koyra Chiini" }, + { "khw", "Khowar" }, + { "ki", "Kikuyu" }, + { "kiu", "Kirmanjki" }, + { "kj", "Kuanyama" }, + { "kk", "Kazakh" }, + { "kkj", "Kako" }, + { "kl", "Kalaallisut" }, + { "kln", "Kalenjin" }, + { "km", "Central Khmer" }, + { "kmb", "Kimbundu" }, + { "kn", "Kannada" }, + { "ko", "Korean" }, + { "koi", "Komi-Permyak" }, + { "kok", "Konkani" }, + { "kos", "Kosraean" }, + { "kpe", "Kpelle" }, + { "kr", "Kanuri" }, + { "krc", "Karachay-Balkar" }, + { "kri", "Krio" }, + { "krj", "Kinaray-a" }, + { "krl", "Karelian" }, + { "kru", "Kurukh" }, + { "ks", "Kashmiri" }, + { "ksb", "Shambala" }, + { "ksf", "Bafia" }, + { "ksh", "Colognian" }, + { "ku", "Kurdish" }, + { "kum", "Kumyk" }, + { "kut", "Kutenai" }, + { "kv", "Komi" }, + { "kw", "Cornish" }, + { "ky", "Kirghiz" }, + { "lag", "Langi" }, + { "la", "Latin" }, + { "lad", "Ladino" }, + { "lag", "Langi" }, + { "lah", "Lahnda" }, + { "lam", "Lamba" }, + { "lb", "Luxembourgish" }, + { "lez", "Lezghian" }, + { "lfn", "Lingua Franca Nova" }, + { "lg", "Ganda" }, + { "li", "Limburgan" }, + { "lij", "Ligurian" }, + { "liv", "Livonian" }, + { "lkt", "Lakota" }, + { "lmo", "Lombard" }, + { "ln", "Lingala" }, + { "lo", "Lao" }, + { "lol", "Mongo" }, + { "lou", "Louisiana Creole" }, + { "loz", "Lozi" }, + { "lrc", "Northern Luri" }, + { "lt", "Lithuanian" }, + { "ltg", "Latgalian" }, + { "lu", "Luba-Katanga" }, + { "lua", "Luba-Lulua" }, + { "lui", "Luiseno" }, + { "lun", "Lunda" }, + { "luo", "Luo" }, + { "lus", "Mizo" }, + { "luy", "Luyia" }, + { "lv", "Latvian" }, + { "lzh", "Literary Chinese" }, + { "lzz", "Laz" }, + { "mad", "Madurese" }, + { "maf", "Mafa" }, + { "mag", "Magahi" }, + { "mai", "Maithili" }, + { "mak", "Makasar" }, + { "man", "Mandingo" }, + { "mas", "Masai" }, + { "mde", "Maba" }, + { "mdf", "Moksha" }, + { "mdr", "Mandar" }, + { "men", "Mende" }, + { "mer", "Meru" }, + { "mfe", "Morisyen" }, + { "mg", "Malagasy" }, + { "mga", "Middle Irish" }, + { "mgh", "Makhuwa-Meetto" }, + { "mgo", "Metaʼ" }, + { "mh", "Marshallese" }, + { "mhr", "Eastern Mari" }, + { "mi", "Māori" }, + { "mic", "Mi'kmaq" }, + { "min", "Minangkabau" }, + { "miq", "Mískito" }, + { "mjw", "Karbi" }, + { "mk", "Macedonian" }, + { "ml", "Malayalam" }, + { "mn", "Mongolian" }, + { "mnc", "Manchu" }, + { "mni", "Manipuri" }, + { "mnw", "Mon" }, + { "mos", "Mossi" }, + { "moh", "Mohawk" }, + { "mr", "Marathi" }, + { "mrj", "Western Mari" }, + { "ms", "Malay" }, + { "mt", "Maltese" }, + { "mua", "Mundang" }, + { "mus", "Muscogee" }, + { "mwl", "Mirandese" }, + { "mwr", "Marwari" }, + { "mwv", "Mentawai" }, + { "my", "Burmese" }, + { "mye", "Myene" }, + { "myv", "Erzya" }, + { "mzn", "Mazanderani" }, + { "na", "Nauru" }, + { "nah", "Nahuatl" }, + { "nan", "Min Nan Chinese" }, + { "nap", "Neapolitan" }, + { "naq", "Nama" }, + { "nan", "Min Nan Chinese" }, + { "nb", "Norwegian Bokmål" }, + { "nd", "North Ndebele" }, + { "nds", "Low German" }, + { "ne", "Nepali" }, + { "new", "Newari" }, + { "nhn", "Central Nahuatl" }, + { "ng", "Ndonga" }, + { "nia", "Nias" }, + { "niu", "Niuean" }, + { "njo", "Ao Naga" }, + { "nl", "Dutch" }, + { "nmg", "Kwasio" }, + { "nn", "Norwegian Nynorsk" }, + { "nnh", "Ngiemboon" }, + { "nog", "Nogai" }, + { "non", "Old Norse" }, + { "nov", "Novial" }, + { "nqo", "N'ko" }, + { "nr", "South Ndebele" }, + { "nso", "Pedi" }, + { "nus", "Nuer" }, + { "nv", "Navajo" }, + { "nwc", "Classical Newari" }, + { "ny", "Nyanja" }, + { "nym", "Nyamwezi" }, + { "nyn", "Nyankole" }, + { "nyo", "Nyoro" }, + { "nzi", "Nzima" }, + { "oc", "Occitan" }, + { "oj", "Ojibwa" }, + { "om", "Oromo" }, + { "or", "Odia" }, + { "os", "Ossetic" }, + { "osa", "Osage" }, + { "ota", "Ottoman Turkish" }, + { "pa", "Panjabi" }, + { "pag", "Pangasinan" }, + { "pal", "Pahlavi" }, + { "pam", "Pampanga" }, + { "pap", "Papiamento" }, + { "pau", "Palauan" }, + { "pcd", "Picard" }, + { "pcm", "Nigerian Pidgin" }, + { "pdc", "Pennsylvania German" }, + { "pdt", "Plautdietsch" }, + { "peo", "Old Persian" }, + { "pfl", "Palatine German" }, + { "phn", "Phoenician" }, + { "pi", "Pali" }, + { "pl", "Polish" }, + { "pms", "Piedmontese" }, + { "pnt", "Pontic" }, + { "pon", "Pohnpeian" }, + { "pr", "Pirate" }, + { "prg", "Prussian" }, + { "pro", "Old Provençal" }, + { "prs", "Dari" }, + { "ps", "Pushto" }, + { "pt", "Portuguese" }, + { "qu", "Quechua" }, + { "quc", "K'iche" }, + { "qug", "Chimborazo Highland Quichua" }, + { "quy", "Ayacucho Quechua" }, + { "quz", "Cusco Quechua" }, + { "raj", "Rajasthani" }, + { "rap", "Rapanui" }, + { "rar", "Rarotongan" }, + { "rgn", "Romagnol" }, + { "rif", "Riffian" }, + { "rm", "Romansh" }, + { "rn", "Rundi" }, + { "ro", "Romanian" }, + { "rof", "Rombo" }, + { "rom", "Romany" }, + { "rtm", "Rotuman" }, + { "ru", "Russian" }, + { "rue", "Rusyn" }, + { "rug", "Roviana" }, + { "rup", "Aromanian" }, + { "rw", "Kinyarwanda" }, + { "rwk", "Rwa" }, + { "sa", "Sanskrit" }, + { "sad", "Sandawe" }, + { "sah", "Sakha" }, + { "sam", "Samaritan Aramaic" }, + { "saq", "Samburu" }, + { "sas", "Sasak" }, + { "sat", "Santali" }, + { "saz", "Saurashtra" }, + { "sba", "Ngambay" }, + { "sbp", "Sangu" }, + { "sc", "Sardinian" }, + { "scn", "Sicilian" }, + { "sco", "Scots" }, + { "sd", "Sindhi" }, + { "sdc", "Sassarese Sardinian" }, + { "sdh", "Southern Kurdish" }, + { "se", "Northern Sami" }, + { "see", "Seneca" }, + { "seh", "Sena" }, + { "sei", "Seri" }, + { "sel", "Selkup" }, + { "ses", "Koyraboro Senni" }, + { "sg", "Sango" }, + { "sga", "Old Irish" }, + { "sgs", "Samogitian" }, + { "sh", "Serbo-Croatian" }, + { "shi", "Tachelhit" }, + { "shn", "Shan" }, + { "shs", "Shuswap" }, + { "shu", "Chadian Arabic" }, + { "si", "Sinhala" }, + { "sid", "Sidamo" }, + { "sk", "Slovak" }, + { "sl", "Slovenian" }, + { "sli", "Lower Silesian" }, + { "sly", "Selayar" }, + { "sm", "Samoan" }, + { "sma", "Southern Sami" }, + { "smj", "Lule Sami" }, + { "smn", "Inari Sami" }, + { "sms", "Skolt Sami" }, + { "sn", "Shona" }, + { "snk", "Soninke" }, + { "so", "Somali" }, + { "sog", "Sogdien" }, + { "son", "Songhai" }, + { "sq", "Albanian" }, + { "sr", "Serbian" }, + { "srn", "Sranan Tongo" }, + { "srr", "Serer" }, + { "ss", "Swati" }, + { "ssy", "Saho" }, + { "st", "Southern Sotho" }, + { "stq", "Saterland Frisian" }, + { "su", "Sundanese" }, + { "suk", "Sukuma" }, + { "sus", "Susu" }, + { "sux", "Sumerian" }, + { "sv", "Swedish" }, + { "sw", "Swahili" }, + { "swb", "Comorian" }, + { "swc", "Congo Swahili" }, + { "syc", "Classical Syriac" }, + { "syr", "Syriac" }, + { "szl", "Silesian" }, + { "ta", "Tamil" }, + { "tcy", "Tulu" }, + { "te", "Telugu" }, + { "tem", "Timne" }, + { "teo", "Teso" }, + { "ter", "Tereno" }, + { "tet", "Tetum" }, + { "tg", "Tajik" }, + { "th", "Thai" }, + { "the", "Chitwania Tharu" }, + { "ti", "Tigrinya" }, + { "tig", "Tigre" }, + { "tiv", "Tiv" }, + { "tk", "Turkmen" }, + { "tkl", "Tokelau" }, + { "tkr", "Tsakhur" }, + { "tl", "Tagalog" }, + { "tlh", "Klingon" }, + { "tli", "Tlingit" }, + { "tly", "Talysh" }, + { "tmh", "Tamashek" }, + { "tn", "Tswana" }, + { "to", "Tongan" }, + { "tog", "Nyasa Tonga" }, + { "tpi", "Tok Pisin" }, + { "tr", "Turkish" }, + { "tru", "Turoyo" }, + { "trv", "Taroko" }, + { "ts", "Tsonga" }, + { "tsd", "Tsakonian" }, + { "tsi", "Tsimshian" }, + { "tt", "Tatar" }, + { "ttt", "Muslim Tat" }, + { "tum", "Tumbuka" }, + { "tvl", "Tuvalu" }, + { "tw", "Twi" }, + { "twq", "Tasawaq" }, + { "ty", "Tahitian" }, + { "tyv", "Tuvinian" }, + { "tzm", "Central Atlas Tamazight" }, + { "udm", "Udmurt" }, + { "ug", "Uyghur" }, + { "uga", "Ugaritic" }, + { "uk", "Ukrainian" }, + { "umb", "Umbundu" }, + { "unm", "Unami" }, + { "ur", "Urdu" }, + { "uz", "Uzbek" }, + { "vai", "Vai" }, + { "ve", "Venda" }, + { "vec", "Venetian" }, + { "vep", "Veps" }, + { "vi", "Vietnamese" }, + { "vls", "West Flemish" }, + { "vmf", "Main-Franconian" }, + { "vo", "Volapük" }, + { "vot", "Votic" }, + { "vro", "Võro" }, + { "vun", "Vunjo" }, + { "wa", "Walloon" }, + { "wae", "Walser" }, + { "wal", "Wolaytta" }, + { "war", "Waray" }, + { "was", "Washo" }, + { "wbp", "Warlpiri" }, + { "wo", "Wolof" }, + { "wuu", "Wu Chinese" }, + { "xal", "Kalmyk" }, + { "xh", "Xhosa" }, + { "xmf", "Mingrelian" }, + { "xog", "Soga" }, + { "yao", "Yao" }, + { "yap", "Yapese" }, + { "yav", "Yangben" }, + { "ybb", "Yemba" }, + { "yi", "Yiddish" }, + { "yo", "Yoruba" }, + { "yrl", "Nheengatu" }, + { "yue", "Yue Chinese" }, + { "yuw", "Papua New Guinea" }, + { "za", "Zhuang" }, + { "zap", "Zapotec" }, + { "zbl", "Blissymbols" }, + { "zea", "Zeelandic" }, + { "zen", "Zenaga" }, + { "zgh", "Standard Moroccan Tamazight" }, + { "zh", "Chinese" }, + { "zu", "Zulu" }, + { "zun", "Zuni" }, + { "zza", "Zaza" }, + { nullptr, nullptr } +}; + + +static const char *country_names[][2] = { + { "AD", "Andorra" }, + { "AE", "United Arab Emirates" }, + { "AF", "Afghanistan" }, + { "AG", "Antigua and Barbuda" }, + { "AI", "Anguilla" }, + { "AL", "Albania" }, + { "AM", "Armenia" }, + { "AO", "Angola" }, + { "AQ", "Antarctica" }, + { "AR", "Argentina" }, + { "AS", "American Samoa" }, + { "AT", "Austria" }, + { "AU", "Australia" }, + { "AW", "Aruba" }, + { "AX", "Åland Islands" }, + { "AZ", "Azerbaijan" }, + { "BA", "Bosnia and Herzegovina" }, + { "BB", "Barbados" }, + { "BD", "Bangladesh" }, + { "BE", "Belgium" }, + { "BF", "Burkina Faso" }, + { "BG", "Bulgaria" }, + { "BH", "Bahrain" }, + { "BI", "Burundi" }, + { "BJ", "Benin" }, + { "BL", "St. Barthélemy" }, + { "BM", "Bermuda" }, + { "BN", "Brunei" }, + { "BO", "Bolivia" }, + { "BQ", "Caribbean Netherlands" }, + { "BR", "Brazil" }, + { "BS", "Bahamas" }, + { "BT", "Bhutan" }, + { "BV", "Bouvet Island" }, + { "BW", "Botswana" }, + { "BY", "Belarus" }, + { "BZ", "Belize" }, + { "CA", "Canada" }, + { "CC", "Cocos (Keeling) Islands" }, + { "CD", "Congo - Kinshasa" }, + { "CF", "Central African Republic" }, + { "CG", "Congo - Brazzaville" }, + { "CH", "Switzerland" }, + { "CI", "Côte d'Ivoire" }, + { "CK", "Cook Islands" }, + { "CL", "Chile" }, + { "CM", "Cameroon" }, + { "CN", "China" }, + { "CO", "Colombia" }, + { "CR", "Costa Rica" }, + { "CU", "Cuba" }, + { "CV", "Cabo Verde" }, + { "CW", "Curaçao" }, + { "CX", "Christmas Island" }, + { "CY", "Cyprus" }, + { "CZ", "Czechia" }, + { "DE", "Germany" }, + { "DJ", "Djibouti" }, + { "DK", "Denmark" }, + { "DM", "Dominica" }, + { "DO", "Dominican Republic" }, + { "DZ", "Algeria" }, + { "EC", "Ecuador" }, + { "EE", "Estonia" }, + { "EG", "Egypt" }, + { "EH", "Western Sahara" }, + { "ER", "Eritrea" }, + { "ES", "Spain" }, + { "ET", "Ethiopia" }, + { "EU", "European Union" }, + { "EZ", "Eurozone" }, + { "FI", "Finland" }, + { "FJ", "Fiji" }, + { "FK", "Falkland Islands" }, + { "FM", "Micronesia" }, + { "FO", "Faroe Islands" }, + { "FR", "France" }, + { "GA", "Gabon" }, + { "GB", "United Kingdom" }, + { "GD", "Grenada" }, + { "GE", "Georgia" }, + { "GF", "French Guiana" }, + { "GG", "Guernsey" }, + { "GH", "Ghana" }, + { "GI", "Gibraltar" }, + { "GL", "Greenland" }, + { "GM", "Gambia" }, + { "GN", "Guinea" }, + { "GP", "Guadeloupe" }, + { "GQ", "Equatorial Guinea" }, + { "GR", "Greece" }, + { "GS", "South Georgia and South Sandwich Islands" }, + { "GT", "Guatemala" }, + { "GU", "Guam" }, + { "GW", "Guinea-Bissau" }, + { "GY", "Guyana" }, + { "HK", "Hong Kong" }, + { "HM", "Heard Island and McDonald Islands" }, + { "HN", "Honduras" }, + { "HR", "Croatia" }, + { "HT", "Haiti" }, + { "HU", "Hungary" }, + { "ID", "Indonesia" }, + { "IE", "Ireland" }, + { "IL", "Israel" }, + { "IM", "Isle of Man" }, + { "IN", "India" }, + { "IO", "British Indian Ocean Territory" }, + { "IQ", "Iraq" }, + { "IR", "Iran" }, + { "IS", "Iceland" }, + { "IT", "Italy" }, + { "JE", "Jersey" }, + { "JM", "Jamaica" }, + { "JO", "Jordan" }, + { "JP", "Japan" }, + { "KE", "Kenya" }, + { "KG", "Kyrgyzstan" }, + { "KH", "Cambodia" }, + { "KI", "Kiribati" }, + { "KM", "Comoros" }, + { "KN", "St. Kitts and Nevis" }, + { "KP", "North Korea" }, + { "KR", "South Korea" }, + { "KW", "Kuwait" }, + { "KY", "Cayman Islands" }, + { "KZ", "Kazakhstan" }, + { "LA", "Laos" }, + { "LB", "Lebanon" }, + { "LC", "St. Lucia" }, + { "LI", "Liechtenstein" }, + { "LK", "Sri Lanka" }, + { "LR", "Liberia" }, + { "LS", "Lesotho" }, + { "LT", "Lithuania" }, + { "LU", "Luxembourg" }, + { "LV", "Latvia" }, + { "LY", "Libya" }, + { "MA", "Morocco" }, + { "MC", "Monaco" }, + { "MD", "Moldova" }, + { "ME", "Montenegro" }, + { "MF", "St. Martin" }, + { "MG", "Madagascar" }, + { "MH", "Marshall Islands" }, + { "MK", "North Macedonia" }, + { "ML", "Mali" }, + { "MM", "Myanmar" }, + { "MN", "Mongolia" }, + { "MO", "Macao" }, + { "MP", "Northern Mariana Islands" }, + { "MQ", "Martinique" }, + { "MR", "Mauritania" }, + { "MS", "Montserrat" }, + { "MT", "Malta" }, + { "MU", "Mauritius" }, + { "MV", "Maldives" }, + { "MW", "Malawi" }, + { "MX", "Mexico" }, + { "MY", "Malaysia" }, + { "MZ", "Mozambique" }, + { "NA", "Namibia" }, + { "NC", "New Caledonia" }, + { "NE", "Niger" }, + { "NF", "Norfolk Island" }, + { "NG", "Nigeria" }, + { "NI", "Nicaragua" }, + { "NL", "Netherlands" }, + { "NO", "Norway" }, + { "NP", "Nepal" }, + { "NR", "Nauru" }, + { "NU", "Niue" }, + { "NZ", "New Zealand" }, + { "OM", "Oman" }, + { "PA", "Panama" }, + { "PE", "Peru" }, + { "PF", "French Polynesia" }, + { "PG", "Papua New Guinea" }, + { "PH", "Philippines" }, + { "PK", "Pakistan" }, + { "PL", "Poland" }, + { "PM", "St. Pierre and Miquelon" }, + { "PN", "Pitcairn Islands" }, + { "PR", "Puerto Rico" }, + { "PS", "Palestine" }, + { "PT", "Portugal" }, + { "PW", "Palau" }, + { "PY", "Paraguay" }, + { "QA", "Qatar" }, + { "RE", "Réunion" }, + { "RO", "Romania" }, + { "RS", "Serbia" }, + { "RU", "Russia" }, + { "RW", "Rwanda" }, + { "SA", "Saudi Arabia" }, + { "SB", "Solomon Islands" }, + { "SC", "Seychelles" }, + { "SD", "Sudan" }, + { "SE", "Sweden" }, + { "SG", "Singapore" }, + { "SH", "St. Helena, Ascension and Tristan da Cunha" }, + { "SI", "Slovenia" }, + { "SJ", "Svalbard and Jan Mayen" }, + { "SK", "Slovakia" }, + { "SL", "Sierra Leone" }, + { "SM", "San Marino" }, + { "SN", "Senegal" }, + { "SO", "Somalia" }, + { "SR", "Suriname" }, + { "SS", "South Sudan" }, + { "ST", "Sao Tome and Principe" }, + { "SV", "El Salvador" }, + { "SX", "Sint Maarten" }, + { "SY", "Syria" }, + { "SZ", "Eswatini" }, + { "TC", "Turks and Caicos Islands" }, + { "TD", "Chad" }, + { "TF", "French Southern Territories" }, + { "TG", "Togo" }, + { "TH", "Thailand" }, + { "TJ", "Tajikistan" }, + { "TK", "Tokelau" }, + { "TL", "Timor-Leste" }, + { "TM", "Turkmenistan" }, + { "TN", "Tunisia" }, + { "TO", "Tonga" }, + { "TR", "Turkey" }, + { "TT", "Trinidad and Tobago" }, + { "TV", "Tuvalu" }, + { "TW", "Taiwan" }, + { "TZ", "Tanzania" }, + { "UA", "Ukraine" }, + { "UG", "Uganda" }, + { "UM", "U.S. Outlying Islands" }, + { "US", "United States of America" }, + { "UY", "Uruguay" }, + { "UZ", "Uzbekistan" }, + { "VA", "Holy See" }, + { "VC", "St. Vincent and the Grenadines" }, + { "VE", "Venezuela" }, + { "VG", "British Virgin Islands" }, + { "VI", "U.S. Virgin Islands" }, + { "VN", "Viet Nam" }, + { "VU", "Vanuatu" }, + { "WF", "Wallis and Futuna" }, + { "WS", "Samoa" }, + { "YE", "Yemen" }, + { "YT", "Mayotte" }, + { "ZA", "South Africa" }, + { "ZM", "Zambia" }, + { "ZW", "Zimbabwe" }, + { nullptr, nullptr } /// English +}; \ No newline at end of file diff --git a/engine/public/core/gstl/str.h b/engine/public/core/gstl/str.h new file mode 100644 index 000000000..e33cc7487 --- /dev/null +++ b/engine/public/core/gstl/str.h @@ -0,0 +1,83 @@ +#pragma once + +#include "type_alias.h" +#include +#include "SDL3/SDL_stdinc.h" + + +class String : public std::string { +public: + using std::string::string; + + String(const std::string& str) : std::string(str) {} + + String(std::string&& str) : std::string(std::move(str)) {} + + size_t length() const { return size(); } + + bool is_empty() const { return empty(); } + + String to_lower() const; + + String to_upper() const; + + String substr(size_t from, size_t len = std::string::npos) const; + + int find(const String& what, int from = 0) const; + + int find(char what, int from =0) const; + + int rfind(const String& what) const; + + int findn(const String& what, int from = 0) const; + + bool begins_with(const String& text) const; + + bool ends_with(const String& text) const; + + bool contains(const String& what) const; + + String replace(const String& what, const String& forwhat) const; + + String replacen(const String& what, const String& forwhat) const; + + String strip_edges() const; + + String lstrip(const String& chars = " \t\n\r") const; + + String rstrip(const String& chars = " \t\n\r") const; + + Vector split(const char delimiter) const; + + String join(const Vector& parts) const; + + String insert(size_t position, const String& what) const; + + String reverse() const; + + template + static String format(const char* fmt, Args... args); + + size_t hash() const; + + String operator+(const String& other) const; + + String operator+(const char* other) const; +}; + + +namespace std { + template<> + struct hash { + size_t operator()(const String& s) const noexcept { + return hash{}(s); + } + }; +} + +template +inline String String::format(const char* fmt, Args... args) { + char buffer[1024]; + SDL_snprintf(buffer, sizeof(buffer), fmt, args...); + return buffer; +} diff --git a/engine/public/core/gstl/type_alias.h b/engine/public/core/gstl/type_alias.h new file mode 100644 index 000000000..083a24d8c --- /dev/null +++ b/engine/public/core/gstl/type_alias.h @@ -0,0 +1,53 @@ +#pragma once + +#include "vector.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +template +using Vector = TypedArray; + +template +using HashMap = std::unordered_map; + +template +using Map = std::map; + +template +using HashSet = std::unordered_set; + +template +using Set = std::set; + +template +using Array = std::array; + +template +using Queue = std::queue; + +template +using Stack = std::stack; + +template +using Ref = std::shared_ptr; + +template +using WeakRef = std::weak_ptr; + +template +using Scope = std::unique_ptr; + +template +using Option = std::optional; + +template +using Variant = std::variant; \ No newline at end of file diff --git a/engine/public/core/gstl/vector.h b/engine/public/core/gstl/vector.h new file mode 100644 index 000000000..d3a087418 --- /dev/null +++ b/engine/public/core/gstl/vector.h @@ -0,0 +1,313 @@ +#pragma once + +#include +#include +#include +#include +#include + +template +class TypedArray : public std::vector { +public: + using std::vector::vector; + + bool is_empty() const; + + void push_front(const T& value); + + void append(const T& value); + + void append_array(const TypedArray& other); + + void pop_front(); + + void erase(size_t position); + + void remove(size_t position); + + void remove_at(size_t position); + + T& get(size_t index); + + const T& get(size_t index) const; + + int find(const T& what, int from = 0) const; + + int rfind(const T& what) const; + + bool has(const T& what) const; + + int count(const T& what) const; + + TypedArray duplicate(bool deep = false) const; + + TypedArray slice(int begin_pos, int end_pos = INT32_MAX) const; + + void reverse(); + + void sort(); + + template + void sort(Func comparator); + + T min() const; + + T max() const; + + template + TypedArray filter(Func predicate) const; + + template + auto map(Func func) const; + + template + void for_each(Func func); + + template + U reduce(Func func, U initial_value) const; + + bool all(std::function predicate) const; + + bool any(std::function predicate) const; + + T& first(); + + const T& first() const; + + T& last(); + + const T& last() const; +}; + + +template +bool TypedArray::is_empty() const { + return this->empty(); +} + +template +void TypedArray::push_front(const T& value) { + this->insert(this->begin(), value); +} + +template +void TypedArray::append(const T& value) { + this->push_back(value); +} + +template +void TypedArray::append_array(const TypedArray& other) { + this->insert(this->end(), other.begin(), other.end()); +} + +template +void TypedArray::pop_front() { + if (!this->empty()) { + this->erase(this->begin()); + } +} + +template +void TypedArray::erase(size_t position) { + if (position < this->size()) { + std::vector::erase(this->begin() + position); + } +} + +template +void TypedArray::remove(size_t position) { + erase(position); +} + +template +void TypedArray::remove_at(size_t position) { + erase(position); +} + +template +T& TypedArray::get(size_t index) { + return (*this)[index]; +} + +template +const T& TypedArray::get(size_t index) const { + return (*this)[index]; +} + +template +int TypedArray::find(const T& what, int from) const { + for (size_t i = from; i < this->size(); ++i) { + if ((*this)[i] == what) { + return static_cast(i); + } + } + + return -1; +} + +template +int TypedArray::rfind(const T& what) const { + for (int i = this->size() - 1; i >= 0; --i) { + if ((*this)[i] == what) { + return i; + } + } + + return -1; +} + +template +bool TypedArray::has(const T& what) const { + return find(what) != -1; +} + +template +int TypedArray::count(const T& what) const { + int result = 0; + for (const auto& item : *this) { + if (item == what) { + ++result; + } + } + + return result; +} + +template +TypedArray TypedArray::duplicate(bool deep) const { + TypedArray result; + result.assign(this->begin(), this->end()); + return result; +} + +template +TypedArray TypedArray::slice(int begin_pos, int end_pos) const { + TypedArray result; + if (begin_pos < 0) { + begin_pos = 0; + } + + if (end_pos > static_cast(this->size())) { + end_pos = this->size(); + } + + if (begin_pos < end_pos) { + result.assign(this->begin() + begin_pos, this->begin() + end_pos); + } + + return result; +} + +template +void TypedArray::reverse() { + std::reverse(this->begin(), this->end()); +} + +template +void TypedArray::sort() { + std::sort(this->begin(), this->end()); +} + +template +template +void TypedArray::sort(Func comparator) { + std::sort(this->begin(), this->end(), comparator); +} + +template +T TypedArray::min() const { + if (this->empty()) { + throw std::runtime_error("Cannot find min of empty array"); + } + + return *std::min_element(this->begin(), this->end()); +} + +template +T TypedArray::max() const { + if (this->empty()) { + throw std::runtime_error("Cannot find max of empty array"); + } + + return *std::max_element(this->begin(), this->end()); +} + +template +template +TypedArray TypedArray::filter(Func predicate) const { + TypedArray result; + std::copy_if(this->begin(), this->end(), std::back_inserter(result), predicate); + return result; +} + +template +template +auto TypedArray::map(Func func) const { + using ReturnType = decltype(func(std::declval())); + TypedArray result; + result.reserve(this->size()); + std::transform(this->begin(), this->end(), std::back_inserter(result), func); + return result; +} + +template +template +void TypedArray::for_each(Func func) { + std::for_each(this->begin(), this->end(), func); +} + +template +template +U TypedArray::reduce(Func func, U initial_value) const { + U result = initial_value; + for (const auto& item : *this) { + result = func(result, item); + } + return result; +} + +template +bool TypedArray::all(std::function predicate) const { + return std::all_of(this->begin(), this->end(), predicate); +} + +template +bool TypedArray::any(std::function predicate) const { + return std::any_of(this->begin(), this->end(), predicate); +} + +template +T& TypedArray::first() { + if (this->empty()) { + throw std::out_of_range("Array is empty"); + } + + return this->front(); +} + +template +const T& TypedArray::first() const { + if (this->empty()) { + throw std::out_of_range("Array is empty"); + } + + return this->front(); +} + +template +T& TypedArray::last() { + if (this->empty()) { + throw std::out_of_range("Array is empty"); + } + + return this->back(); +} + +template +const T& TypedArray::last() const { + if (this->empty()) { + throw std::out_of_range("Array is empty"); + } + + return this->back(); +} \ No newline at end of file diff --git a/engine/public/core/io/assimp_io.h b/engine/public/core/io/assimp_io.h deleted file mode 100644 index 2884cbdd0..000000000 --- a/engine/public/core/io/assimp_io.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -#include "stdafx.h" - -#include -#include -#include -#include - -class FileAccess; - -/*! - * @brief Custom Assimp IOStream implementation using SDL file access - * @details Wraps FileAccess to provide Assimp with file reading capabilities - */ -class SDLIOStream : public Assimp::IOStream { -public: - SDLIOStream(const std::string& path, const std::string& mode); - ~SDLIOStream() override; - - size_t Read(void* pvBuffer, size_t pSize, size_t pCount) override; - size_t Write(const void* pvBuffer, size_t pSize, size_t pCount) override; - aiReturn Seek(size_t pOffset, aiOrigin pOrigin) override; - size_t Tell() const override; - size_t FileSize() const override; - void Flush() override; - - std::unique_ptr m_file; - std::string m_path; - size_t m_position = 0; -}; - -/*! - * @brief Custom Assimp IOSystem implementation for asset loading - * @details Allows Assimp to load models with external dependencies (like OBJ+MTL) - * through the engine's FileAccess system, supporting both desktop and Android - */ -class SDLIOSystem : public Assimp::IOSystem { -public: - explicit SDLIOSystem(const std::string& base_path); - ~SDLIOSystem() override; - - bool Exists(const char* pFile) const override; - char getOsSeparator() const override; - Assimp::IOStream* Open(const char* pFile, const char* pMode = "rb") override; - void Close(Assimp::IOStream* pFile) override; - -private: - std::string m_base_path; -}; \ No newline at end of file diff --git a/engine/public/core/io/file_system.h b/engine/public/core/io/file_system.h deleted file mode 100644 index c0a7b828a..000000000 --- a/engine/public/core/io/file_system.h +++ /dev/null @@ -1,174 +0,0 @@ -#pragma once -#include "stdafx.h" - -/*! - - @brief Loads a given file from `res` folder - - The file path is relative to `res` folder - - @ingroup FileSystem - @version 0.0.1 - @param file_path the path to the file in `res` folder - @return File content as `string` -*/ -std::string load_assets_file(const std::string& file_path); - -/*! - - @brief Loads a given file from `res` folder into memory - - The file path is relative to `res` folder - @ingroup FileSystem - - @version 0.0.2 - @param file_path the path to the file in `res` folder - @return File content as `vector` -*/ -std::vector load_file_into_memory(const std::string& file_path); - -/*! - * @brief Ember file (SDL_stream) - * - * - * @version 0.0.8 - */ -struct Ember_File { - SDL_IOStream* stream; -}; - - - -/** - * @brief Flags to indicate access modes. - * - * - * - READ : Read-only access - * - WRITE : Write-only access - * - READ_WRITE : Read + Write access (read first, then write) - * - WRITE_READ : Write + Read access (write first, then read) - * @ingroup FileSystem - */ -enum class ModeFlags { - READ, ///< Read-only access - WRITE, ///< Write-only access - READ_WRITE, ///< Read then Write access - WRITE_READ ///< Write then Read access -}; - -/** - * @class FileAccess - * @brief A RAII wrapper for SDL3 file operations supporting read/write access. - * - * - `res://` (assets) and `user://` (writable user data) paths. - * @ingroup FileSystem - */ -class FileAccess { -public: - /** - * @brief Construct and optionally open a file. - * @param file_path Path to the file (can be res:// or user://) - * @param mode_flags Mode of access (default: READ) - */ - explicit FileAccess(const std::string& file_path, ModeFlags mode_flags = ModeFlags::READ); - - /** - * @brief Default constructor (empty, file not opened) - */ - FileAccess() = default; - - /** - * @brief Destructor automatically closes the file if it is open - */ - ~FileAccess(); - - /** - * @brief Open a file for reading/writing. - * @param file_path Path to the file (res:// or user://) - * @param mode_flags Access mode - * - * @details By default, get from `res://` - * @return true if the file was successfully opened, false otherwise - */ - bool open(const std::string& file_path, ModeFlags mode_flags = ModeFlags::READ); - - /** - * @brief Check if the file is currently open. - * @return true if open, false otherwise - */ - [[nodiscard]] bool is_open() const; - - /** - * @brief Read the entire file as a vector of bytes. - * @return File contents as Array of Bytes. - */ - std::vector get_file_as_bytes(); - - /** - * @brief Check if a file exists at the given path. - * @param file_path Path to the file - * @return true if the file exists, false otherwise - */ - static bool file_exists(const std::string& file_path); - - /** - * @brief Read the entire file as a String. - * @return File contents as String - */ - std::string get_file_as_str(); - - /** - * @brief Get the absolute resolved path to the file. - * @return Full path on the filesystem - */ - [[nodiscard]] std::string get_absolute_path() const; - - /** - * @brief Get the path relative to the base (removes res:// or user:// prefix) - * @return Relative path string - */ - [[nodiscard]] std::string get_path() const; - - /** - * @brief Write a string to the file. - * @param content String content to write - * @return true if successful - */ - bool store_string(const std::string& content = ""); - - /** - * @brief Write a byte array to the file. - * @param content Vector of bytes to write - * @return true if successful - */ - bool store_bytes(const std::vector& content); - - /** - * @brief Move the file pointer to the given offset from the beginning. - * @param length Offset in bytes - */ - void seek(int length); - - /** - * @brief Move the file pointer relative to the end of the file. - * @param position Offset in bytes from the end - */ - void seek_end(int position); - - /** - * @brief Close the file if it is open. - */ - void close(); - - FileAccess(const FileAccess&) = delete; - - FileAccess& operator=(const FileAccess&) = delete; - - SDL_IOStream* get_handle() const { - return _file; - } - -private: - SDL_IOStream* _file = nullptr; ///< Internal SDL file handle - std::string _file_path; ///< Full resolved file path - - bool resolve_path(const std::string& file_path, ModeFlags mode_flags); -}; \ No newline at end of file diff --git a/engine/public/core/io/ma_io.h b/engine/public/core/io/ma_io.h deleted file mode 100644 index 714965174..000000000 --- a/engine/public/core/io/ma_io.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once -#include "file_system.h" - -/*! - * @brief MiniAudio VFS structure - * - * @version 0.0.5 - */ -struct MiniAudio_VFS { - ma_vfs_callbacks base; -}; - - -ma_result ember_init_ma_vfs(MiniAudio_VFS* vfs); \ No newline at end of file diff --git a/engine/public/core/math/basis.h b/engine/public/core/math/basis.h new file mode 100644 index 000000000..19fb3558d --- /dev/null +++ b/engine/public/core/math/basis.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +class Vector3; +class Quaternion; + +class Basis : public glm::mat3 { +public: + using glm::mat3::mat3; + + Basis(); + Basis(float diagonal); + Basis(const glm::mat3& m); + + glm::vec3& operator[](int index); + const glm::vec3& operator[](int index) const; + + Basis transposed() const; + void transpose(); + + Basis inverse() const; + void invert(); + + float determinant() const; + + Vector3 xform(const Vector3& v) const; + Vector3 xform_inv(const Vector3& v) const; + + Quaternion to_quaternion() const; + + static Basis from_quaternion(const Quaternion& q); + static Basis from_euler(const Vector3& euler); + + static const Basis IDENTITY; +}; + diff --git a/engine/public/core/math/math.h b/engine/public/core/math/math.h new file mode 100644 index 000000000..be3c210c8 --- /dev/null +++ b/engine/public/core/math/math.h @@ -0,0 +1,97 @@ +#pragma once +#define GLM_ENABLE_EXPERIMENTAL +#define GLM_FORCE_INTRINSICS +#include "core/math/vector2.h" +#include "core/math/vector3.h" +#include "core/math/vector4.h" +#include "core/math/quaternion.h" +#include "core/math/basis.h" +#include "core/math/matrix4.h" + +#include + +namespace math { + constexpr float PI = 3.14159265358979323846f; + constexpr float TAU = 6.28318530717958647692f; + constexpr float E = 2.71828182845904523536f; + constexpr float DEG2RAD = PI / 180.0f; + constexpr float RAD2DEG = 180.0f / PI; + constexpr float EPSILON = 1e-6f; + constexpr float INF = __builtin_huge_valf(); + + float sin(float x); + float cos(float x); + float tan(float x); + float asin(float x); + float acos(float x); + float atan(float x); + float atan2(float y, float x); + + float sinh(float x); + float cosh(float x); + float tanh(float x); + + float exp(float x); + float log(float x); + float log2(float x); + float log10(float x); + float pow(float base, float exp); + float sqrt(float x); + float cbrt(float x); + + float floor(float x); + float ceil(float x); + float round(float x); + float trunc(float x); + + template + inline T min(T a, T b) { return (a < b) ? a : b; } + + template + inline T max(T a, T b) { return (a > b) ? a : b; } + + template + inline T clamp(T value, T min_val, T max_val) { + return min(max(value, min_val), max_val); + } + + float abs(float x); + int abs(int x); + + template + inline T sign(T x) { return (x > T(0)) ? T(1) : ((x < T(0)) ? T(-1) : T(0)); } + + template + inline T lerp(T a, T b, float t) { + return a + (b - a) * t; + } + + template + inline T smoothstep(T edge0, T edge1, T x) { + T t = clamp((x - edge0) / (edge1 - edge0), T(0), T(1)); + return t * t * (T(3) - T(2) * t); + } + + float deg2rad(float degrees); + float rad2deg(float radians); + + bool is_zero_approx(float x); + bool is_equal_approx(float a, float b); + + float random(float min_val, float max_val); + int random(int min_val, int max_val); + + template + inline T move_toward(T current, T target, T delta) { + if (abs(target - current) <= delta) { + return target; + } + return current + sign(target - current) * delta; + } + + float snap(float value, float step); + +} + + + diff --git a/engine/public/core/math/matrix4.h b/engine/public/core/math/matrix4.h new file mode 100644 index 000000000..792207529 --- /dev/null +++ b/engine/public/core/math/matrix4.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +class Vector3; +class Quaternion; +class Basis; + +class Matrix4 : public glm::mat4 { +public: + using glm::mat4::mat4; + + Matrix4(); + Matrix4(float diagonal); + Matrix4(const glm::mat4& m); + + glm::vec4& operator[](int index); + const glm::vec4& operator[](int index) const; + + Matrix4 transposed() const; + void transpose(); + + Matrix4 inverse() const; + void invert(); + + float determinant() const; + + Vector3 xform(const Vector3& v) const; + Vector3 xform_inv(const Vector3& v) const; + Vector3 xform_normal(const Vector3& normal) const; + + Basis get_basis() const; + Vector3 get_translation() const; + Quaternion to_quaternion() const; + + static Matrix4 translate(const Vector3& translation); + static Matrix4 rotate(float angle, const Vector3& axis); + static Matrix4 scale(const Vector3& scale_vec); + static Matrix4 look_at(const Vector3& eye, const Vector3& center, const Vector3& up); + static Matrix4 perspective(float fov, float aspect, float near_plane, float far_plane); + static Matrix4 ortho(float left, float right, float bottom, float top, float near_plane, float far_plane); + static Matrix4 from_quaternion(const Quaternion& q); + + static const Matrix4 IDENTITY; +}; + + diff --git a/engine/public/core/math/quaternion.h b/engine/public/core/math/quaternion.h new file mode 100644 index 000000000..2cab547e3 --- /dev/null +++ b/engine/public/core/math/quaternion.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include + +class Vector3; +class Basis; +class Matrix4; + +class Quaternion : public glm::quat { +public: + using glm::quat::quat; + + Quaternion(); + Quaternion(float w, float x, float y, float z); + Quaternion(const glm::quat& q); + Quaternion(const glm::vec3& euler_angles); + + float& operator[](int index); + const float& operator[](int index) const; + + float length() const; + + Quaternion normalized() const; + void normalize(); + + Quaternion conjugate() const; + Quaternion inverse() const; + + float dot(const Quaternion& other) const; + + Quaternion slerp(const Quaternion& other, float t) const; + + Vector3 xform(const Vector3& v) const; + Vector3 to_euler() const; + Matrix4 to_mat4() const; + Basis to_basis() const; + + static Quaternion from_axis_angle(const Vector3& axis, float angle); + static Quaternion from_euler(float pitch, float yaw, float roll); + static Quaternion from_euler(const Vector3& euler); + static Quaternion look_at(const Vector3& direction, const Vector3& up); + + static const Quaternion IDENTITY; +}; + + diff --git a/engine/public/core/math/vector2.h b/engine/public/core/math/vector2.h new file mode 100644 index 000000000..9af3699ca --- /dev/null +++ b/engine/public/core/math/vector2.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +class Vector2 : public glm::vec2 { +public: + using glm::vec2::vec2; + + Vector2(); + Vector2(float x, float y); + Vector2(float v); + Vector2(const glm::vec2& v); + + float& operator[](int index); + const float& operator[](int index) const; + + float length() const; + + Vector2 normalized() const; + void normalize(); + + float dot(const Vector2& other) const; + float distance_to(const Vector2& other) const; + + Vector2 lerp(const Vector2& other, float t) const; + + Vector2 reflect(const Vector2& normal) const; + Vector2 refract(const Vector2& normal, float eta) const; + + Vector2 abs() const; + Vector2 sign() const; + + Vector2 floor() const; + Vector2 ceil() const; + Vector2 round() const; + + float angle() const; + float angle_to(const Vector2& other) const; + + Vector2 rotated(float angle) const; + + bool is_zero_approx() const; + + static const Vector2 ZERO; + static const Vector2 ONE; + static const Vector2 LEFT; + static const Vector2 RIGHT; + static const Vector2 UP; + static const Vector2 DOWN; +}; + +class Vector2i : public glm::ivec2 { +public: + using glm::ivec2::ivec2; + + Vector2i(); + Vector2i(int x, int y); + Vector2i(int v); + Vector2i(const glm::ivec2& v); + + int& operator[](int index); + const int& operator[](int index) const; + + float length() const; + + Vector2i abs() const; + Vector2i sign() const; + + static const Vector2i ZERO; + static const Vector2i ONE; + static const Vector2i LEFT; + static const Vector2i RIGHT; + static const Vector2i UP; + static const Vector2i DOWN; +}; + +// Aliases +using Vec2 = Vector2; +using Vec2i = Vector2i; + diff --git a/engine/public/core/math/vector3.h b/engine/public/core/math/vector3.h new file mode 100644 index 000000000..2aa7f26b8 --- /dev/null +++ b/engine/public/core/math/vector3.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +class Vector3 : public glm::vec3 { +public: + using glm::vec3::vec3; + + Vector3(); + Vector3(float x, float y, float z); + Vector3(float v); + Vector3(const glm::vec3& v); + + float& operator[](int index); + const float& operator[](int index) const; + + float length() const; + + Vector3 normalized() const; + void normalize(); + + float dot(const Vector3& other) const; + Vector3 cross(const Vector3& other) const; + + float distance_to(const Vector3& other) const; + + Vector3 lerp(const Vector3& other, float t) const; + + Vector3 reflect(const Vector3& normal) const; + Vector3 refract(const Vector3& normal, float eta) const; + + Vector3 abs() const; + Vector3 sign() const; + + Vector3 floor() const; + Vector3 ceil() const; + Vector3 round() const; + + bool is_zero_approx() const; + + static const Vector3 ZERO; + static const Vector3 ONE; + static const Vector3 LEFT; + static const Vector3 RIGHT; + static const Vector3 UP; + static const Vector3 DOWN; + static const Vector3 FORWARD; + static const Vector3 BACK; +}; + +class Vector3i : public glm::ivec3 { +public: + using glm::ivec3::ivec3; + + Vector3i(); + Vector3i(int x, int y, int z); + Vector3i(int v); + Vector3i(const glm::ivec3& v); + + int& operator[](int index); + const int& operator[](int index) const; + + float length() const; + + Vector3i abs() const; + Vector3i sign() const; + + static const Vector3i ZERO; + static const Vector3i ONE; + static const Vector3i LEFT; + static const Vector3i RIGHT; + static const Vector3i UP; + static const Vector3i DOWN; + static const Vector3i FORWARD; + static const Vector3i BACK; +}; + +using Vec3 = Vector3; +using Vec3i = Vector3i; + diff --git a/engine/public/core/math/vector4.h b/engine/public/core/math/vector4.h new file mode 100644 index 000000000..bbda407ea --- /dev/null +++ b/engine/public/core/math/vector4.h @@ -0,0 +1,59 @@ +#pragma once + +#include + +class Vector4 : public glm::vec4 { +public: + using glm::vec4::vec4; + + Vector4(); + Vector4(float x, float y, float z, float w); + Vector4(float v); + Vector4(const glm::vec4& v); + + float& operator[](int index); + const float& operator[](int index) const; + + float length() const; + + Vector4 normalized() const; + void normalize(); + + float dot(const Vector4& other) const; + + float distance_to(const Vector4& other) const; + + Vector4 lerp(const Vector4& other, float t) const; + + Vector4 abs() const; + Vector4 floor() const; + Vector4 ceil() const; + Vector4 round() const; + + bool is_zero_approx() const; + + static const Vector4 ZERO; + static const Vector4 ONE; +}; + +class Vector4i : public glm::ivec4 { +public: + using glm::ivec4::ivec4; + + Vector4i(); + Vector4i(int x, int y, int z, int w); + Vector4i(int v); + Vector4i(const glm::ivec4& v); + + int& operator[](int index); + const int& operator[](int index) const; + + float length() const; + + Vector4i abs() const; + + static const Vector4i ZERO; + static const Vector4i ONE; +}; + + diff --git a/engine/public/core/renderer/base_struct.h b/engine/public/core/renderer/base_struct.h deleted file mode 100644 index 456907a70..000000000 --- a/engine/public/core/renderer/base_struct.h +++ /dev/null @@ -1,440 +0,0 @@ -#pragma once -#include "core/io/file_system.h" - -enum class CubemapOrientation { - DEFAULT, - TOP, - BOTTOM, - FLIP_X, - FLIP_Y -}; - -enum class TextureFormat { - UNKNOWN, - R8, RG8, RGB8, RGBA8, - R16F, RG16F, RGB16F, RGBA16F, - R32F, RG32F, RGB32F, RGBA32F, - DEPTH24, DEPTH32F, DEPTH24_STENCIL8, - SRGB8, SRGB8_ALPHA8 -}; - -enum MaterialFeature { - HAS_ALBEDO_MAP = 1 << 0, - HAS_SPECULAR_MAP = 1 << 1, - HAS_METALLIC_MAP = 1 << 2, - HAS_ROUGHNESS_MAP= 1 << 3, - HAS_NORMAL_MAP = 1 << 4, - HAS_AO_MAP = 1 << 5, - HAS_EMISSIVE_MAP = 1 << 6, - HAS_IBL = 1 << 7 -}; - - -/** - * @brief Texture filtering modes. - * - * Determines how texture pixels are sampled when scaled or transformed. - * - * @version 0.0.5 - */ -enum class TextureFiltering { - NEAREST, - LINEAR, - NEAREST_MIPMAP_NEAREST, - LINEAR_MIPMAP_NEAREST, - NEAREST_MIPMAP_LINEAR, - LINEAR_MIPMAP_LINEAR -}; - -enum class TextureWrap { - REPEAT, - MIRRORED_REPEAT, - CLAMP_TO_EDGE, - CLAMP_TO_BORDER -}; - -enum class TextureUsage { - STATIC, - DYNAMIC, - RENDER_TARGET -}; - -struct TextureDesc { - uint32_t width = 0; - uint32_t height = 0; - TextureFormat format = TextureFormat::RGBA8; - TextureFiltering min_filter = TextureFiltering::LINEAR; - TextureFiltering mag_filter = TextureFiltering::LINEAR; - TextureWrap wrap_s = TextureWrap::REPEAT; - TextureWrap wrap_t = TextureWrap::REPEAT; - bool generate_mipmaps = false; - uint32_t mip_levels = 0; - TextureUsage usage = TextureUsage::STATIC; - float anisotropy = 1.0f; - glm::vec4 border_color = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); -}; - -/*! - - @brief Texture2D Abstract class - - Create texture from file or memory - - Bind the texture - - Get texture properties - - @version 0.0.1 - -*/ -class GpuTexture { -public: - - explicit GpuTexture(const TextureDesc& desc) : _width(desc.width), - _height(desc.height), - _format(desc.format), - _tex_desc(desc) { - } - - virtual ~GpuTexture() = default; - - virtual bool create(const void* p_data, const TextureDesc& desc) = 0; - - virtual void bind(Uint32 slot = 0) const = 0; - - virtual void unbind() =0; - - virtual void destroy() = 0; - - bool load_from_file(const std::string& path, const TextureDesc& desc); - - bool load_from_memory(const void* data, size_t size, const TextureDesc& desc); - - TextureDesc get_desc() const; - - TextureFormat get_format() const; - - Uint32 get_width() const; - - Uint32 get_height() const; - - Uint32 get_mip_levels() const; - - Uint32 get_id() const; - -protected: - Uint32 _width = 0; - Uint32 _height = 0; - Uint32 _mip_levels = 0; - TextureFormat _format = TextureFormat::RGBA8; - TextureDesc _tex_desc; - - Uint32 _id = 0; - -}; - - -/*! - - @brief GpuBuffer Abstract class - - Bind the buffer - - Upload data to the buffer - - Get buffer size - - @version 0.0.5 - -*/ -enum class GpuBufferType { - VERTEX, /// For Vertex Buffer Objects (VBOs) - INDEX, /// For Element Buffer Objects (EBOs) - UNIFORM, /// For Uniform Buffer Objects (UBOs) - STORAGE /// For Shader Storage Buffer Objects (SSBOs) -}; - -/*! - - @brief GpuBuffer Abstract class - - Bind the buffer - - Upload data to the buffer - - Get buffer size - - @version 0.0.5 - -*/ -class GpuBuffer { -public: - virtual ~GpuBuffer() = default; - - virtual void bind() const = 0; - - virtual void upload(const void* data, size_t size) = 0; - - virtual size_t size() const = 0; - - virtual GpuBufferType type() const = 0; - -}; - -enum class DataType { - USHORT, - FLOAT, - INT, - UNSIGNED_INT, -}; - - -struct VertexAttribute { - uint32_t location; - uint32_t components; - DataType type; - bool normalized; - uint32_t offset; -}; - -class GpuVertexLayout { -public: - virtual ~GpuVertexLayout() = default; - virtual void bind() const = 0; - virtual void unbind() const = 0; -}; - - -enum class FramebufferTextureFormat { - None = 0, - RGBA8, - RED_INTEGER, - DEPTH24STENCIL8, - DEPTH_COMPONENT -}; - -struct FramebufferTextureSpecification { - FramebufferTextureFormat format = FramebufferTextureFormat::None; -}; - -struct FramebufferAttachmentSpecification { - std::vector attachments; - FramebufferAttachmentSpecification() = default; - - FramebufferAttachmentSpecification(const std::initializer_list list) - : attachments(list) { - } -}; - -struct FramebufferSpecification { - unsigned int width = 0; - unsigned int height = 0; - FramebufferAttachmentSpecification attachments; - bool swap_chain_target = false; -}; - - -class Framebuffer { -public: - virtual ~Framebuffer() = default; - - virtual void bind() = 0; - virtual void unbind() = 0; - virtual void invalidate() = 0; - - virtual void resize(unsigned int width, unsigned int height) = 0; - - virtual Uint32 get_fbo_id() const { - return 0; - } - - virtual uint32_t get_color_attachment_id(size_t index = 0) const = 0; - virtual uint32_t get_depth_attachment_id() const = 0; - - virtual const FramebufferSpecification& get_specification() const = 0; -}; - - -/*! - - @brief Vertex structure - - Position - - Normal - - UV coordinates - - @version 0.0.1 - -*/ -struct Vertex { - glm::vec3 position; /// 3D position - glm::vec3 normal; /// 3D normal - glm::vec2 uv; /// 2D texture coordinates -}; - -constexpr int MAX_BONES = 250; - - -/*! - - @brief Shader Abstract class - - Compile the shader - - Bind the shader - - Send uniforms - - @version 0.0.2 - @param string vertex The shader source - @param string fragment The shader source -*/ -class Shader { -public: - Shader() = default; - - virtual ~Shader() = default; - - virtual void activate() const = 0; - - virtual void set_value(const char* name, float value) = 0; - - virtual void set_value(const char* name, int value) = 0; - - virtual void set_value(const char* name, Uint32 value) = 0; - - virtual void set_value(const char* name, glm::mat4 value, Uint32 count = 1) =0; - - virtual void set_value(const char* name, const int* value, Uint32 count = 1) =0; - - virtual void set_value(const char* name, const float* value, Uint32 count = 1) =0; - - virtual void set_value(const char* name, glm::vec2 value, Uint32 count = 1) =0; - - virtual void set_value(const char* name, glm::vec3 value, Uint32 count = 1) =0; - - virtual void set_value(const char* name, glm::vec4 value, Uint32 count = 1) =0; - - virtual void set_value(const char* name, const glm::mat4* value, Uint32 count = 1) =0; - - virtual void destroy() = 0; - - virtual Uint32 get_id() const = 0; - - virtual bool is_valid() const = 0; - -protected: - Uint32 id = 0; - - std::unordered_map _uniforms; -}; - -// Forward declaration -class Renderer; -struct MeshInstance3D; -struct Material; - -struct RenderBatch { - const MeshInstance3D* mesh; - const Material* material; - std::vector model_matrices; - - void clear() { - model_matrices.clear(); - } -}; - -struct MeshMaterialKey { - const MeshInstance3D* mesh; - const Material* material; - - bool operator==(const MeshMaterialKey& other) const { - return mesh == other.mesh && material == other.material; - } -}; - -struct MeshMaterialKeyHash { - std::size_t operator()(const MeshMaterialKey& key) const { - std::size_t h1 = std::hash{}(key.mesh); - std::size_t h2 = std::hash{}(key.material); - return h1 ^ (h2 << 1); - } -}; - -constexpr Uint32 MAX_VERTICES_2D = 65536; -constexpr Uint32 MAX_INDICES_2D = MAX_VERTICES_2D * 3; - -// ============================================================================ -// 2D Rendering Structures -// ============================================================================ - - -/*! - * @brief Rectangle structure for 2D rendering. - * @ingroup Core - */ -struct Rect2D { - float x = 0.0f; - float y = 0.0f; - float width = 0.0f; - float height = 0.0f; - - Rect2D() = default; - - bool is_zero() const { - return x == 0.0f && y == 0.0f && width == 0.0f && height == 0.0f; - } - - Rect2D(float w, float h) : width(w), height(h) { - } - - Rect2D(float x_, float y_, float w, float h) : x(x_), y(y_), width(w), height(h) { - } -}; - -enum class FlipMode { - NONE = 0, - HORIZONTAL = 1, - VERTICAL = 2, - BOTH = 3 -}; - -struct Vertex2D { - glm::vec2 position; - glm::vec2 tex_coord; - glm::vec4 color; -}; - -enum class DrawMode2D { - FILLED = 0, /// Filled shapes (triangles, rects) - LINE = 1, /// Lines - TEXT = 2, /// Text rendering - CIRCLE_FILLED = 3, /// Filled circles - CIRCLE_OUTLINE = 4 /// Circle outlines -}; - -enum class DrawType2D { - RECTANGLE, - CIRCLE, - LINE, - TRIANGLE, - TEXT -}; - -struct DrawCommand2D { - DrawType2D type; - DrawMode2D mode; - - std::vector vertices; - std::vector indices; - - glm::vec4 color; - Uint32 texture_id; - bool use_texture; - - // Shape-specific parameters - glm::vec2 position; - glm::vec2 size; // for rectangles - float radius; // for circles - float thickness; // for lines and circle outlines - int segments; // for circles - bool filled; - - // Text-specific - std::string text; - float text_scale; -}; - - -struct TextMesh { - std::string text; - TTF_Font* font; - size_t start_pos; -}; diff --git a/engine/public/core/renderer/opengl/ogl_renderer.h b/engine/public/core/renderer/opengl/ogl_renderer.h deleted file mode 100644 index 692da0afc..000000000 --- a/engine/public/core/renderer/opengl/ogl_renderer.h +++ /dev/null @@ -1,121 +0,0 @@ -#pragma once -#include "ogl_struct.h" - -class OpenGLRenderer final : public Renderer { -public: - ~OpenGLRenderer() override; - - bool initialize(int w, int h, SDL_Window* window) override; - - std::shared_ptr create_gpu_texture(const std::string& path, const TextureDesc& desc) override; - - std::shared_ptr allocate_gpu_buffer(GpuBufferType type) override; - - std::shared_ptr create_vertex_layout( - const GpuBuffer* vertex_buffer, - const GpuBuffer* index_buffer, - const std::vector& attributes, - Uint32 stride) override; - - Uint32 load_texture_from_file(const std::string& path) override; - - Uint32 load_embedded_texture(const unsigned char* buffer, size_t size, const std::string& name = "") override; - - void begin_frame() override; - - void begin_shadow_pass() override; - - void render_shadow_pass(const glm::mat4& light_space_matrix) override; - - void end_shadow_pass() override; - - void begin_render_target() override; - - void render_main_target(const Camera3D& camera, - const Transform3D& camera_transform, - const glm::mat4& light_space_matrix, - const std::vector& directional_lights, - const std::vector>& spot_lights) override; - - void end_render_target() override; - - void begin_environment_pass() override; - void render_environment_pass(const Camera3D& camera) override; - void end_environment_pass() override; - - void add_to_render_batch(const Transform3D& transform, const MeshRef& mesh_ref, const MaterialRef& mat_ref) override; - - void add_to_shadow_batch(const Transform3D& transform, const MeshRef& mesh_ref) override; - - - void set_render_resolution(int width, int height) override; - - void cleanup() override; - - void swap_chain() override; - - // ======================================================================== - // 2D Rendering Implementation - // ======================================================================== - void begin_frame_2d() override; - void end_frame_2d(const Camera2D& camera, const Transform2D& camera_transform) override; - - void draw_rect_2d(const glm::vec2& position, const glm::vec2& size, - const glm::vec4& color, bool filled = true) override; - - void draw_texture_2d(const std::shared_ptr& texture, - const glm::vec2& position, - const glm::vec2& size, - const Rect2D& src = {0,0,0,0}, - FlipMode flip = FlipMode::NONE, - const glm::vec4& color = glm::vec4(1.0f)) override; - - void draw_line_2d(const glm::vec2& start, const glm::vec2& end, - const glm::vec4& color, float thickness = 1.0f) override; - - void draw_circle_2d(const glm::vec2& center, float radius, - const glm::vec4& color, bool filled = true, - int segments = 32) override; - - void draw_circle_outline_2d(const glm::vec2& center, float radius, - const glm::vec4& color, float thickness = 1.0f, - int segments = 32) override; - - void draw_triangle_2d(const glm::vec2& p1, const glm::vec2& p2, const glm::vec2& p3, - const glm::vec4& color, bool filled = true) override; - - void draw_text_2d(const std::string& text, const glm::vec2& position, - const glm::vec4& color, float scale = 1.0f) override; - - bool load_font(const std::string& font_path, int point_size, const std::string& font_name = "") override; - - -private: - SDL_GLContext _context = nullptr; - - GLuint create_gl_texture(const unsigned char* data, int w, int h, int channels); - - WorldEnvironment3D* create_skybox_from_atlas(const std::string& atlas_path, - CubemapOrientation orient = CubemapOrientation::DEFAULT, - float brightness = 1.0f); - - - void setup_instance_matrix_attribute(GpuVertexLayout* vao); - - - void setup_lights(const std::vector& directional_lights, - const std::vector>& spot_lights); - - // Rendering passes for material blending - void render_opaque_pass(); - void render_transparent_pass(); - - void initialize_2d_rendering(); - - void render_batched_2d(DrawMode2D mode, GLuint texture_id, bool use_texture, - const DrawCommand2D& first_cmd); - - // Render text to texture (always white, color applied at render time) - GLuint render_text_to_texture(const std::string& text, TTF_Font* font); -}; - diff --git a/engine/public/core/renderer/opengl/ogl_struct.h b/engine/public/core/renderer/opengl/ogl_struct.h deleted file mode 100644 index 5cef64050..000000000 --- a/engine/public/core/renderer/opengl/ogl_struct.h +++ /dev/null @@ -1,199 +0,0 @@ -#pragma once -#include "core/io/assimp_io.h" -#include "core/io/assimp_io.h" -#include "core/io/assimp_io.h" -#include "core/io/assimp_io.h" -#include "core/io/assimp_io.h" -#include "core/io/assimp_io.h" -#include "core/io/assimp_io.h" -#include "core/io/assimp_io.h" -#include "core/io/assimp_io.h" -#include "core/io/assimp_io.h" -#include "core/renderer/renderer.h" -#include "core/renderer/base_struct.h" - -class OpenglGpuVertexLayout final: public GpuVertexLayout { -public: - - OpenglGpuVertexLayout( - const GpuBuffer* vertex_buffer, - const GpuBuffer* index_buffer, - const std::vector& attributes, - uint32_t stride); - - ~OpenglGpuVertexLayout() override; - - void bind() const override; - - void unbind() const override; - -private: - GLuint _vao = 0; - - static GLenum to_gl_type(DataType type); -}; - -class OpenglGpuBuffer final : public GpuBuffer { - -public: - OpenglGpuBuffer(GpuBufferType type); - - ~OpenglGpuBuffer() override; - - void bind() const override; - - void upload(const void* data, size_t size) override; - - size_t size() const override; - - GpuBufferType type() const override; - - -private: - GLuint _id = 0; - GpuBufferType _buffer_type = GpuBufferType::VERTEX; - size_t _buffer_size = 0; - GLenum _target = GL_ARRAY_BUFFER; - -}; - -class OpenglGpuTexture final : public GpuTexture { -public: - OpenglGpuTexture(const TextureDesc& desc); - - ~OpenglGpuTexture() override; - - bool create(const void* p_data, const TextureDesc& desc) override; - - void bind(Uint32 slot = 0) const override; - - void unbind() override; - - void destroy() override; - -private: - GLuint _texture_id = 0; - - static GLenum to_gl_format(TextureFormat format); - static GLenum to_gl_internal_format(TextureFormat format); - static GLenum to_gl_filter(TextureFiltering filter); - static GLenum to_gl_wrap(TextureWrap wrap); -}; - -class OpenGLFramebuffer final : public Framebuffer { - -public: - explicit OpenGLFramebuffer(const FramebufferSpecification& spec); - - ~OpenGLFramebuffer() override; - - void invalidate() override; - - void bind() override; - - void unbind() override; - - void resize(unsigned int width, unsigned int height) override; - - Uint32 get_fbo_id() const override; - - Uint32 get_color_attachment_id(size_t index = 0) const override; - - Uint32 get_depth_attachment_id() const override; - - const FramebufferSpecification& get_specification() const override; - - void cleanup(); - -private: - Uint32 fbo = 0; - FramebufferSpecification specification; - std::vector color_attachments; - uint32_t depth_attachment = 0; -}; - - -class OpenglShader final : public Shader { -public: - OpenglShader() = default; - ~OpenglShader() override; - - template - T get_value(const char* name); - - OpenglShader(const std::string& vertex, const std::string& fragment); - - void activate() const override; - - void set_value(const char* name, float value) override; - - void set_value(const char* name, int value) override; - - void set_value(const char* name, Uint32 value) override; - - void set_value(const char* name, glm::mat4 value, Uint32 count) override; - - void set_value(const char* name, const int* value, Uint32 count) override; - - void set_value(const char* name, const float* value, Uint32 count) override; - - void set_value(const char* name, glm::vec2 value, Uint32 count) override; - - void set_value(const char* name, glm::vec3 value, Uint32 count) override; - - void set_value(const char* name, glm::vec4 value, Uint32 count) override; - - void set_value(const char* name, const glm::mat4* values, Uint32 count) override; - - void destroy() override; - - unsigned int get_id() const override; - - bool is_valid() const override; - -private: - Uint32 get_uniform_location(const std::string& name); - - Uint32 compile_shader(Uint32 type, const char* source); -}; - - -template -inline T OpenglShader::get_value(const char* name) { - const unsigned int location = get_uniform_location(name); - - if (location == -1) { - printf("Shader variable not found: %s\n", name); - return T(); - } - - if constexpr (std::is_same_v) { - float value; - glGetUniformfv(id, location, &value); - return value; - } else if constexpr (std::is_same_v) { - int value; - glGetUniformiv(id, location, &value); - return value; - } else if constexpr (std::is_same_v) { - GLfloat data[2]; - glGetUniformfv(id, location, data); - return glm::vec2(data[0], data[1]); - } else if constexpr (std::is_same_v) { - GLfloat data[3]; - glGetUniformfv(id, location, data); - return glm::vec3(data[0], data[1], data[2]); - } else if constexpr (std::is_same_v) { - GLfloat data[4]; - glGetUniformfv(id, location, data); - return glm::vec4(data[0], data[1], data[2], data[3]); - } else if constexpr (std::is_same_v) { - GLfloat data[16]; - glGetUniformfv(id, location, data); - return glm::make_mat4(data); - } else { - printf("Unsupported type: %s\n", typeid(T).name()); - } - - return T(); -} diff --git a/engine/public/core/renderer/renderer.h b/engine/public/core/renderer/renderer.h deleted file mode 100644 index 72ee71d87..000000000 --- a/engine/public/core/renderer/renderer.h +++ /dev/null @@ -1,159 +0,0 @@ -#pragma once -#include "base_struct.h" -#include "core/component/game_object.h" - -constexpr int MAX_INSTANCES = 1000; -/*! - * @brief Abstract Renderer class defining the rendering API. - * @ingroup Core - */ -class Renderer { -public: - virtual ~Renderer() = default; - - virtual bool initialize(int width, int height, SDL_Window* window) = 0; - - virtual void cleanup() = 0; - - virtual std::shared_ptr create_gpu_texture(const std::string& path, const TextureDesc& desc) = 0; - - virtual std::shared_ptr allocate_gpu_buffer(GpuBufferType type) = 0; - - virtual std::shared_ptr create_vertex_layout(const GpuBuffer* vertex_buffer, const GpuBuffer* index_buffer, - const std::vector& attributes, Uint32 stride) = 0; - - virtual Uint32 load_texture_from_file(const std::string& path) = 0; - - virtual Uint32 load_embedded_texture(const unsigned char* buffer, size_t size, const std::string& name = "") = 0; - - virtual void set_render_resolution(int width, int height) = 0; - - virtual void begin_frame() = 0; - - virtual void begin_shadow_pass() = 0; - virtual void render_shadow_pass(const glm::mat4& light_space_matrix) = 0; - virtual void end_shadow_pass() = 0; - - virtual void begin_render_target() = 0; - virtual void render_main_target(const Camera3D& camera, const Transform3D& camera_transform, const glm::mat4& light_space_matrix, - const std::vector& directional_lights, - const std::vector>& spot_lights) = 0; - virtual void end_render_target() = 0; - - virtual void begin_environment_pass() = 0; - virtual void render_environment_pass(const Camera3D& camera) = 0; - virtual void end_environment_pass() = 0; - - virtual void add_to_render_batch(const Transform3D& transform, const MeshRef& mesh_ref, const MaterialRef& mat_ref) = 0; - - virtual void add_to_shadow_batch(const Transform3D& transform, const MeshRef& mesh_ref) = 0; - - - virtual void swap_chain() = 0; - - - // ======================================================================== - // 2D Rendering API - // ======================================================================== - virtual void begin_frame_2d() = 0; - virtual void end_frame_2d(const Camera2D& camera, const Transform2D& camera_transform) = 0; - - virtual void draw_rect_2d(const glm::vec2& position, const glm::vec2& size, const glm::vec4& color, bool filled = true) = 0; - - virtual void draw_texture_2d(const std::shared_ptr& texture, const glm::vec2& position, const glm::vec2& size, - const Rect2D& src = {0, 0, 0, 0}, FlipMode flip = FlipMode::NONE, - const glm::vec4& tint = glm::vec4(1.0f)) = 0; - - virtual void draw_line_2d(const glm::vec2& start, const glm::vec2& end, const glm::vec4& color, float thickness = 1.0f) = 0; - - virtual void draw_circle_2d(const glm::vec2& center, float radius, const glm::vec4& color, bool filled = true, int segments = 32) = 0; - - virtual void draw_circle_outline_2d(const glm::vec2& center, float radius, const glm::vec4& color, float thickness = 1.0f, - int segments = 32) = 0; - - virtual void draw_triangle_2d(const glm::vec2& p1, const glm::vec2& p2, const glm::vec2& p3, const glm::vec4& color, - bool filled = true) = 0; - - virtual void draw_text_2d(const std::string& text, const glm::vec2& position, const glm::vec4& color, float scale = 1.0f) = 0; - - template - void draw_text_2d_fmt(const glm::vec2& position, const glm::vec4& color, float scale, std::format_string fmt, Args&&... args) { - draw_text_2d(std::format(fmt, std::forward(args)...), position, color, scale); - } - - - virtual bool load_font(const std::string& font_path, int point_size, const std::string& font_name = "") = 0; - - - /// RESOURCES - std::unordered_map> _textures; - std::unordered_map> _meshes; - std::unordered_map> _materials; - - std::shared_ptr get_shadow_map_fbo() const; - - std::shared_ptr get_main_fbo() const; - - int get_virtual_width() const { - return _virtual_width; - } - - int get_virtual_height() const { - return _virtual_height; - } - -protected: - SDL_Window* _window = nullptr; - - // ======================================================================== - // 3D Rendering Resources - // ======================================================================== - std::unique_ptr _default_shader = nullptr; - std::unique_ptr _shadow_shader = nullptr; - std::unique_ptr _environment_shader = nullptr; - - WorldEnvironment3D* _world_environment = nullptr; - - std::shared_ptr shadow_map_fbo = nullptr; - std::shared_ptr main_fbo = nullptr; - - // Render dimensions (FBO and rendering target size) - int _virtual_width = 0, _virtual_height = 0; - - // Render resolution (for retro game scaling - if != virtual size, scale on blit) - int _render_width = 0, _render_height = 0; - - std::shared_ptr instance_buffer; - - std::unordered_map _instanced_batches; - - std::unordered_map _shadow_batches; - - // ======================================================================== - // 2D Rendering Resources - // ======================================================================== - std::unique_ptr _shader_2d; - std::shared_ptr _vertex_buffer_2d; - std::shared_ptr _index_buffer_2d; - std::shared_ptr _vertex_layout_2d; - - std::vector _draw_commands_2d; - std::vector _batch_vertices_2d; - std::vector _batch_indices_2d; - - - std::unordered_map _fonts; - TTF_Font* _default_font = nullptr; /// For regular text (auto-set on load) - TTF_Font* _emoji_font = nullptr; /// For emojis - auto-detected by name (Twemoji, emoji, etc.) - - - struct CachedTextTexture { - Uint32 texture_id; - int width; - int height; - }; - - std::unordered_map _cached_text_textures; - - std::shared_ptr _2d_framebuffer = nullptr; -}; diff --git a/engine/public/core/system/timer.h b/engine/public/core/system/timer.h deleted file mode 100644 index e182e457c..000000000 --- a/engine/public/core/system/timer.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include "stdafx.h" - -/*! -@file timer.h - @brief Timer class definition. - - This file contains the definition of the Timer class, which is used to measure time intervals and manage frame timing in the engine. - @ingroup Time - @version 0.0.1 - -*/ -class Timer { -public: - double delta; - double elapsed_time; - - int get_fps() const; - - void start(); - - void tick(); - -private: - Uint64 now = 0; - Uint64 last = 0; -}; diff --git a/engine/public/core/utility/obj_loader.h b/engine/public/core/utility/obj_loader.h deleted file mode 100644 index 49441a756..000000000 --- a/engine/public/core/utility/obj_loader.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include "core/renderer/renderer.h" - -/*! - * @brief Assimp Object Loader - * - * @note Loads 3D models using the Assimp library and converts them into engine-compatible structures. - * - * - */ -class AssimpLoader { -public: - static MeshInstance3D load_mesh(const std::string& path); - - static Model load_model(const std::string& path); - -private: - static std::string get_directory(const std::string& path); - - static MeshInstance3D create_mesh(aiMesh* aiMesh); - - static Material load_material(const aiScene* scene, aiMesh* mesh, const std::string& directory, Renderer& renderer); - - static void load_colors(aiMaterial* aiMat, Material& mat); - - static void load_textures(aiMaterial* aiMat, const aiScene* scene, const std::string& dir, Renderer& renderer, Material& mat); - - static void parse_bones(aiMesh* mesh, Model& model); - - - static void parse_animations(const aiScene* scene, Model& model); -}; diff --git a/engine/public/core/utility/project_config.h b/engine/public/core/utility/project_config.h deleted file mode 100644 index 79a938d6f..000000000 --- a/engine/public/core/utility/project_config.h +++ /dev/null @@ -1,218 +0,0 @@ -#pragma once - -#include "core/io/file_system.h" -#include "core/renderer/base_struct.h" - - -/** - * @brief Supported rendering backends for the engine. - * - * @note Not all backends were implemented. - * - * @ingroup Configuration - */ -enum class Backend { - /** - * @brief OpenGL-based compatibility profile. - * - * Desktop: OpenGL Core (3.3+) - * Mobile: OpenGL ES (3.0+) - * Web: WebGL 2/3 - */ - GL_COMPATIBILITY, - - /** - * @brief Vulkan-based high-performance profile. - * - * Desktop and Android (if supported). - */ - VK_FORWARD, - - /** - * @brief DirectX 12 backend (Windows + Xbox). - */ - DIRECTX12, - - /** - * @brief Metal backend (macOS + iOS). - */ - METAL, - - /** - * @brief Automatically choose the best backend for the platform. - */ - AUTO -}; - -/*! -* @brief Device orientation options. -* @ingroup Configuration -*/ -enum class Orientation { LANDSCAPE_LEFT, /// Landscape mode with home button on the right. - LANDSCAPE_RIGHT, /// Landscape mode with home button on the left. - PORTRAIT, /// Portrait mode with home button at the bottom. - PORTRAIT_UPSIDE_DOWN /// Portrait mode with home button at the top. -}; - -/*! -* @brief How the viewport handles aspect ratio. -* -* -* @ingroup Configuration -* -*/ -enum class AspectRatio { - NONE, /// No aspect ratio handling. - KEEP, /// Maintain aspect ratio, adding black bars if necessary. - EXPAND, /// Expand to fill the screen, potentially cropping content. -}; - -/*! - @brief Viewport rendering mode. - - VIEWPORT: Renders to a fixed-size viewport, maintaining aspect ratio. - - CANVAS : Renders to fill the entire window, stretching as needed. - - @ingroup Configuration -*/ -enum class ViewportMode { VIEWPORT, CANVAS }; - -/*! - * @brief Viewport configuration settings. - * @ingroup Configuration - */ -struct Viewport { - int width = 640; - int height = 320; - float scale = 1.0f; - - ViewportMode mode = ViewportMode::VIEWPORT; - AspectRatio aspect_ratio = AspectRatio::KEEP; - - bool load(const tinyxml2::XMLElement* root); -}; - -/*! - * @brief Environment settings such as clear color. - * @ingroup Configuration - */ -struct Environment { - glm::vec4 clear_color = {0.0f, 0.0f, 0.0f, 1.0f}; - - bool load(const tinyxml2::XMLElement* root); -}; - -/*! - * @brief Application metadata and window settings. - * @ingroup Configuration - */ -struct Application { - const char* name = "Ember Engine - Window"; - const char* version = "1.0"; - const char* package_name = "com.ember.engine.app"; // identifier - const char* icon_path = "res/icon.png"; - const char* description = "EEngine"; - int max_fps = 60; - - bool is_fullscreen = false; - bool is_resizable = true; - - bool load(const tinyxml2::XMLElement* root); -}; - -/*! - * @brief Performance-related settings. - * @ingroup Configuration - */ -struct Performance { - bool is_multithreaded = false; - int physics_fps = 60; - int worker_threads = -1; // Default to -1 (auto-detect based on CPU cores) - - bool load(const tinyxml2::XMLElement* root); -}; - -/*! - * @brief Renderer device settings. - * @ingroup Configuration - */ -struct RendererDevice { - Backend backend = Backend::AUTO; - TextureFiltering texture_filtering = TextureFiltering::NEAREST; - - bool load(const tinyxml2::XMLElement* root); - - [[nodiscard]] const char* get_backend_str() const; -}; - -enum class WindowMode { WINDOWED, /// Windowed mode. - MAXIMIZED, /// Maximized mode. - MINIMIZED, /// Minimized mode. - FULLSCREEN /// Fullscreen mode. -}; - -/*! - * @brief Window configuration settings. - * @ingroup Configuration - */ -struct Window { - int width = 1280; - int height = 720; - - WindowMode window_mode = WindowMode::WINDOWED; - - float dpi_scale = 1.0f; - - bool load(const tinyxml2::XMLElement* root); - - void resize(int w,int h); -}; - -/*! - * @brief Engine configuration loaded from `project.xml`. - * @ingroup Configuration - */ -struct EngineConfig { - - bool is_debug = false; - - Orientation orientation = Orientation::LANDSCAPE_LEFT; - - TextureFiltering texture_filtering = TextureFiltering::NEAREST; - - bool load(); - - const char* get_orientation_str() const; - - RendererDevice& get_renderer_device(); - - Performance& get_performance(); - - Application& get_application(); - - Environment& get_environment(); - - Viewport& get_viewport(); - - Window& get_window(); - - bool is_vsync() const; - - void set_vsync(bool enabled); - -private: - Application _app; - - Environment _environment; - - Viewport _viewport; - - RendererDevice _renderer_device; - - Performance _performance; - - Window _window; - - bool _is_vsync_enabled = true; - - tinyxml2::XMLDocument _doc = {}; -}; \ No newline at end of file diff --git a/engine/public/definitions.h b/engine/public/definitions.h index 0eb38ad8d..fb84e0dff 100644 --- a/engine/public/definitions.h +++ b/engine/public/definitions.h @@ -30,6 +30,7 @@ #define GOLIAS_ASSERT_BREAK() ((void)0) #endif + /*! * @defgroup Components * @defgroup Systems diff --git a/engine/public/drivers/gles3/rendering_device_gles3.h b/engine/public/drivers/gles3/rendering_device_gles3.h new file mode 100644 index 000000000..286bbc877 --- /dev/null +++ b/engine/public/drivers/gles3/rendering_device_gles3.h @@ -0,0 +1,97 @@ +#pragma once + +#include "servers/rendering/rendering_device.h" +#include "drivers/gles3/shaders/gles3.h" +#include + +/*! + @brief Rendering device implementation using OpenGL/ES 3. + + Backend: + - OpenGL ES 3.0+ + - OpenGL 3.3+ (Core Profile) + +*/ +class RenderingDeviceGLES3 final : public RenderingDevice { +public: + RenderingDeviceGLES3() = default; + ~RenderingDeviceGLES3() override; + + bool initialize(SDL_Window* sdl_window) override; + void shutdown() override; + + RID shader_create_from_source(const String& vertex_src, const String& fragment_src) override; + void shader_destroy(RID shader) override; + + RID buffer_create(size_t size, uint32_t usage_flags, const void* data = nullptr) override; + void buffer_update(RID buffer, size_t offset, size_t size, const void* data) override; + void buffer_destroy(RID buffer) override; + + RID texture_create(const TextureFormat& format, void* data = nullptr) override; + void texture_update(RID texture_rid, uint32_t mip_level, uint32_t layer, const void* data, size_t size) override; + void texture_generate_mipmaps(RID texture_rid) override; + void texture_destroy(RID texture_rid) override; + void get_texture_size(RID texture_rid, uint32_t& width, uint32_t& height) override; + uint32_t texture_get_native_handle(RID texture_rid) override; + Texture get_texture(RID texture_rid) override; + + RID sampler_create(const SamplerState& state) override; + void sampler_destroy(RID sampler) override; + + RID framebuffer_create(const Vector& attachments) override; + void framebuffer_destroy(RID framebuffer) override; + + RID pipeline_create(const PipelineState& state) override; + void pipeline_destroy(RID pipeline) override; + + void begin_frame() override; + void end_frame() override; + + void render_pass_begin(RID framebuffer, const Viewport& viewport, const Scissor& scissor) override; + void render_pass_end() override; + + void bind_pipeline(RID pipeline) override; + void bind_vertex_buffers(const Vector& buffers, const Vector& offsets = {}) override; + void bind_index_buffer(RID buffer, IndexType type, size_t offset = 0) override; + void bind_uniform_buffer(uint32_t binding, RID buffer, size_t offset = 0, size_t size = 0) override; + void bind_texture(uint32_t binding, RID texture, RID sampler) override; + + void push_constant(const String& name, const void* data, size_t size) override; + + void draw(uint32_t vertex_count, uint32_t instance_count = 1, uint32_t first_vertex = 0, uint32_t first_instance = 0) override; + void draw_indexed(uint32_t index_count, uint32_t instance_count = 1, uint32_t first_index = 0, int32_t vertex_offset = 0, + uint32_t first_instance = 0) override; + + void set_viewport(const Viewport& viewport) override; + void set_scissor(const Scissor& scissor) override; + void clear_color(const glm::vec4& color) override; + void clear_depth_stencil(float depth = 1.0f, uint32_t stencil = 0) override; + + void swap_buffers() override; +private: + HashMap shaders; + HashMap buffers; + HashMap textures; + HashMap samplers; + HashMap framebuffers; + HashMap pipelines; + + RID current_pipeline = INVALID_RID; + RID current_framebuffer = INVALID_RID; + IndexType current_index_type = IndexType::UINT16; + + SDL_GLContext gl_context = nullptr; + + GLuint compile_shader(GLenum type, const String& source); + GLenum to_gl_format(DataFormat format, bool* is_int = nullptr); + GLenum to_gl_filter(TextureFilter filter); + GLenum to_gl_wrap(TextureWrap wrap); + GLenum to_gl_compare(CompareOp op); + GLenum to_gl_stencil_op(StencilOp op); + GLenum to_gl_blend_factor(BlendFactor factor); + GLenum to_gl_blend_op(BlendOp op); + GLenum to_gl_topology(PrimitiveTopology topology); + void apply_rasterization_state(const RasterizationState& state); + void apply_depth_stencil_state(const DepthStencilState& state); + void apply_blend_state(const Vector& states); +}; diff --git a/engine/public/drivers/gles3/shaders/gles3.h b/engine/public/drivers/gles3/shaders/gles3.h new file mode 100644 index 000000000..b8ab4a43a --- /dev/null +++ b/engine/public/drivers/gles3/shaders/gles3.h @@ -0,0 +1,48 @@ +#pragma once + +#include "servers/rendering/shader_preprocessor.h" + + +namespace shaders { + inline std::string get_default_vertex_2d() { + std::string header = get_shader_header(); + return header + R"( +layout(location = 0) in vec3 a_vertex; +layout(location = 1) in vec4 a_color; +layout(location = 2) in vec2 a_uv; + +uniform mat4 VIEW_PROJECTION_MATRIX; + +out vec4 vColor; +out vec2 vTexCoord; + +void main() { + vColor = a_color; + vTexCoord = a_uv; + gl_Position = VIEW_PROJECTION_MATRIX * vec4(a_vertex, 1.0); +} +)"; + } + + inline std::string get_default_fragment_2d() { + std::string header = get_shader_header(); + return header + R"( +in vec4 vColor; +in vec2 vTexCoord; + +out vec4 COLOR; + +uniform sampler2D TEXTURE; + +void main() { + vec4 texColor = texture(TEXTURE, vTexCoord); + + if (texColor.a < 0.01) { + discard; + } + + COLOR = texColor * vColor; +} +)"; + } +} // namespace shaders diff --git a/engine/public/drivers/sdl_gpu/rendering_device_sdl_gpu.h b/engine/public/drivers/sdl_gpu/rendering_device_sdl_gpu.h new file mode 100644 index 000000000..e2b990abd --- /dev/null +++ b/engine/public/drivers/sdl_gpu/rendering_device_sdl_gpu.h @@ -0,0 +1,137 @@ +#pragma once +#include "servers/rendering/rendering_device.h" + +/*! + @brief Rendering device implementation using SDL_GPU. + + Backend: + - Direct3d12 + - Vulkan + - Metal + - Private + +*/ +class RenderingDeviceSDL_GPU final : public RenderingDevice { +public: + RenderingDeviceSDL_GPU() = default; + ~RenderingDeviceSDL_GPU() override; + + bool initialize(SDL_Window* sdl_window) override; + void shutdown() override; + + RID shader_create_from_source(const String& vertex_src, const String& fragment_src) override; + RID shader_create_from_bytecode(const void* vertex_bytecode, size_t vertex_size, + const void* fragment_bytecode, size_t fragment_size, + uint32_t num_samplers = 0, uint32_t num_uniform_buffers = 1); + void shader_destroy(RID shader) override; + + RID buffer_create(size_t size, uint32_t usage_flags, const void* data = nullptr) override; + void buffer_update(RID buffer, size_t offset, size_t size, const void* data) override; + void buffer_destroy(RID buffer) override; + + RID texture_create(const TextureFormat& format, void* data = nullptr) override; + void texture_update(RID texture, uint32_t mip_level, uint32_t layer, const void* data, size_t size) override; + void texture_generate_mipmaps(RID texture) override; + void texture_destroy(RID texture) override; + void get_texture_size(RID texture, uint32_t& width, uint32_t& height) override; + uint32_t texture_get_native_handle(RID texture) override; + Texture get_texture(RID texture) override; + + RID sampler_create(const SamplerState& state) override; + void sampler_destroy(RID sampler) override; + + RID framebuffer_create(const Vector& attachments) override; + void framebuffer_destroy(RID framebuffer) override; + + RID pipeline_create(const PipelineState& state) override; + void pipeline_destroy(RID pipeline) override; + + void begin_frame() override; + void end_frame() override; + + void render_pass_begin(RID framebuffer, const Viewport& viewport, const Scissor& scissor) override; + void render_pass_end() override; + + void bind_pipeline(RID pipeline) override; + void bind_vertex_buffers(const Vector& buffers, const Vector& offsets = {}) override; + void bind_index_buffer(RID buffer, IndexType type, size_t offset = 0) override; + void bind_uniform_buffer(uint32_t binding, RID buffer, size_t offset = 0, size_t size = 0) override; + void bind_texture(uint32_t binding, RID texture, RID sampler) override; + + void push_constant(const String& name, const void* data, size_t size) override; + + void draw(uint32_t vertex_count, uint32_t instance_count = 1, uint32_t first_vertex = 0, uint32_t first_instance = 0) override; + void draw_indexed(uint32_t index_count, uint32_t instance_count = 1, uint32_t first_index = 0, + int32_t vertex_offset = 0, uint32_t first_instance = 0) override; + + void set_viewport(const Viewport& viewport) override; + void set_scissor(const Scissor& scissor) override; + void clear_color(const glm::vec4& color) override; + void clear_depth_stencil(float depth = 1.0f, uint32_t stencil = 0) override; + + void swap_buffers() override; + +private: + SDL_GPUDevice* _device = nullptr; + SDL_GPUCommandBuffer* _cmd_buffer = nullptr; + SDL_GPURenderPass* _render_pass = nullptr; + + struct GPUShader { + SDL_GPUShader* vertex = nullptr; + SDL_GPUShader* fragment = nullptr; + }; + + struct GPUBuffer { + SDL_GPUBuffer* handle = nullptr; + size_t size = 0; + uint32_t usage = 0; + }; + + struct GPUTexture { + SDL_GPUTexture* handle = nullptr; + TextureFormat format; + }; + + HashMap _shaders; + HashMap _buffers; + HashMap _textures; + HashMap _samplers; + HashMap> _framebuffers; + HashMap _pipelines; + + RID _current_pipeline = INVALID_RID; + Viewport _current_viewport; + glm::vec4 _clear_color = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); + + struct BoundVertexBuffer { + SDL_GPUBuffer* buffer = nullptr; + Uint32 offset = 0; + }; + + Vector _bound_vertex_buffers; + + SDL_GPUBuffer* _bound_index_buffer = nullptr; + SDL_GPUIndexElementSize _bound_index_type = SDL_GPU_INDEXELEMENTSIZE_16BIT; + Uint32 _bound_index_offset = 0; + + struct BoundTextureSampler { + SDL_GPUTexture* texture = nullptr; + SDL_GPUSampler* sampler = nullptr; + }; + HashMap _bound_textures; + + SDL_GPUTextureFormat sdl_format(DataFormat fmt); + SDL_GPUVertexElementFormat sdl_vertex_format(DataFormat fmt); + SDL_GPUFilter sdl_filter(TextureFilter filter); + SDL_GPUSamplerMipmapMode sdl_mipmap_mode(TextureFilter filter); + SDL_GPUSamplerAddressMode sdl_wrap(TextureWrap wrap); + SDL_GPUCompareOp sdl_compare(CompareOp op); + SDL_GPUStencilOp sdl_stencil(StencilOp op); + SDL_GPUBlendFactor sdl_blend_factor(BlendFactor f); + SDL_GPUBlendOp sdl_blend_op(BlendOp op); + SDL_GPUCullMode sdl_cull(CullMode mode); + SDL_GPUFillMode sdl_fill(PolygonMode mode); + SDL_GPUPrimitiveType sdl_topology(PrimitiveTopology topo); + SDL_GPUIndexElementSize sdl_index_type(IndexType type); + SDL_GPUBufferUsageFlags sdl_buffer_usage(uint32_t flags); +}; \ No newline at end of file diff --git a/engine/public/drivers/sdl_gpu/shaders/default_dxd12.h b/engine/public/drivers/sdl_gpu/shaders/default_dxd12.h new file mode 100644 index 000000000..33f03556f --- /dev/null +++ b/engine/public/drivers/sdl_gpu/shaders/default_dxd12.h @@ -0,0 +1,706 @@ +#pragma once + +inline unsigned char default_fragment_dxd12[4044] = { + 0x44, 0x58, 0x42, 0x43, 0xAF, 0xFE, 0xCA, 0xA4, 0xB1, 0x8B, 0x57, 0x6E, + 0x61, 0x18, 0x9E, 0xB5, 0xF1, 0x62, 0x75, 0xF8, 0x01, 0x00, 0x00, 0x00, + 0xCC, 0x0F, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, + 0x4C, 0x00, 0x00, 0x00, 0xA8, 0x00, 0x00, 0x00, 0xE4, 0x00, 0x00, 0x00, + 0xD8, 0x01, 0x00, 0x00, 0x84, 0x08, 0x00, 0x00, 0xA0, 0x08, 0x00, 0x00, + 0x53, 0x46, 0x49, 0x30, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x49, 0x53, 0x47, 0x31, 0x54, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x54, 0x45, 0x58, 0x43, 0x4F, 0x4F, 0x52, 0x44, 0x00, 0x00, 0x00, 0x00, + 0x4F, 0x53, 0x47, 0x31, 0x34, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x53, 0x56, 0x5F, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x00, 0x00, 0x00, + 0x50, 0x53, 0x56, 0x30, 0xEC, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x00, 0x54, 0x45, 0x58, 0x43, 0x4F, 0x4F, 0x52, + 0x44, 0x00, 0x54, 0x45, 0x58, 0x43, 0x4F, 0x4F, 0x52, 0x44, 0x00, 0x6D, + 0x61, 0x69, 0x6E, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x44, 0x00, 0x03, 0x02, 0x00, 0x00, + 0x0A, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x42, 0x00, + 0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x44, 0x10, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x0F, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x54, 0x41, 0x54, 0xA4, 0x06, 0x00, 0x00, + 0x60, 0x00, 0x00, 0x00, 0xA9, 0x01, 0x00, 0x00, 0x44, 0x58, 0x49, 0x4C, + 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x8C, 0x06, 0x00, 0x00, + 0x42, 0x43, 0xC0, 0xDE, 0x21, 0x0C, 0x00, 0x00, 0xA0, 0x01, 0x00, 0x00, + 0x0B, 0x82, 0x20, 0x00, 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x07, 0x81, 0x23, 0x91, 0x41, 0xC8, 0x04, 0x49, 0x06, 0x10, 0x32, 0x39, + 0x92, 0x01, 0x84, 0x0C, 0x25, 0x05, 0x08, 0x19, 0x1E, 0x04, 0x8B, 0x62, + 0x80, 0x14, 0x45, 0x02, 0x42, 0x92, 0x0B, 0x42, 0xA4, 0x10, 0x32, 0x14, + 0x38, 0x08, 0x18, 0x4B, 0x0A, 0x32, 0x52, 0x88, 0x48, 0x90, 0x14, 0x20, + 0x43, 0x46, 0x88, 0xA5, 0x00, 0x19, 0x32, 0x42, 0xE4, 0x48, 0x0E, 0x90, + 0x91, 0x22, 0xC4, 0x50, 0x41, 0x51, 0x81, 0x8C, 0xE1, 0x83, 0xE5, 0x8A, + 0x04, 0x29, 0x46, 0x06, 0x51, 0x18, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x1B, 0x8C, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x40, 0x02, 0xA8, 0x0D, + 0x84, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x20, 0x6D, 0x30, 0x86, 0xFF, + 0xFF, 0xFF, 0xFF, 0x1F, 0x00, 0x09, 0xA8, 0x00, 0x49, 0x18, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x13, 0x82, 0x60, 0x42, 0x20, 0x4C, 0x08, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x89, 0x20, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, + 0x32, 0x22, 0x48, 0x09, 0x20, 0x64, 0x85, 0x04, 0x93, 0x22, 0xA4, 0x84, + 0x04, 0x93, 0x22, 0xE3, 0x84, 0xA1, 0x90, 0x14, 0x12, 0x4C, 0x8A, 0x8C, + 0x0B, 0x84, 0xA4, 0x4C, 0x10, 0x70, 0x23, 0x00, 0x25, 0x00, 0x14, 0x66, + 0x00, 0xE6, 0x08, 0xC0, 0x60, 0x8E, 0x00, 0x29, 0xC6, 0x20, 0x84, 0x14, + 0x42, 0xA6, 0x18, 0x80, 0x10, 0x52, 0x06, 0xA1, 0x39, 0x82, 0xA0, 0x0C, + 0x80, 0x28, 0x5A, 0x37, 0x0D, 0x97, 0x3F, 0x61, 0x0F, 0x21, 0xF9, 0x2B, + 0x21, 0xAD, 0xC4, 0xE4, 0x17, 0xB7, 0x8D, 0x8A, 0x31, 0xC6, 0x20, 0x54, + 0xEE, 0x19, 0x2E, 0x7F, 0xC2, 0x1E, 0x42, 0xF2, 0x43, 0xA0, 0x19, 0x16, + 0x02, 0x05, 0xAE, 0x30, 0x8D, 0xBC, 0x37, 0xC6, 0x18, 0x84, 0x90, 0x41, + 0xB0, 0x18, 0x8F, 0x14, 0x42, 0x14, 0xC9, 0x81, 0x80, 0x61, 0x04, 0x62, + 0x98, 0xA9, 0x0D, 0xC6, 0x81, 0x1D, 0xC2, 0x61, 0x1E, 0xE6, 0xC1, 0x0D, + 0x68, 0xA1, 0x1C, 0xF0, 0x81, 0x1E, 0xEA, 0x41, 0x1E, 0xCA, 0x41, 0x0E, + 0x48, 0x81, 0x0F, 0xEC, 0xA1, 0x1C, 0xC6, 0x81, 0x1E, 0xDE, 0x41, 0x1E, + 0xF8, 0xC0, 0x1C, 0xD8, 0xE1, 0x1D, 0xC2, 0x81, 0x1E, 0xD8, 0x00, 0x0C, + 0xE8, 0xC0, 0x0F, 0xC0, 0xC0, 0x0F, 0xF4, 0x40, 0x0F, 0xDA, 0x21, 0x1D, + 0xE0, 0x61, 0x1E, 0x7E, 0x81, 0x1E, 0xF2, 0x01, 0x1E, 0xCA, 0x01, 0x05, + 0x64, 0x26, 0x31, 0x18, 0x07, 0x76, 0x08, 0x87, 0x79, 0x98, 0x07, 0x37, + 0xA0, 0x85, 0x72, 0xC0, 0x07, 0x7A, 0xA8, 0x07, 0x79, 0x28, 0x07, 0x39, + 0x20, 0x05, 0x3E, 0xB0, 0x87, 0x72, 0x18, 0x07, 0x7A, 0x78, 0x07, 0x79, + 0xE0, 0x03, 0x73, 0x60, 0x87, 0x77, 0x08, 0x07, 0x7A, 0x60, 0x03, 0x30, + 0xA0, 0x03, 0x3F, 0x00, 0x03, 0x3F, 0x40, 0x42, 0xB5, 0x74, 0x6F, 0x92, + 0xA6, 0x88, 0x12, 0x26, 0x9F, 0x05, 0x98, 0x67, 0x21, 0x22, 0x76, 0x02, + 0x26, 0x02, 0x05, 0x84, 0x72, 0x22, 0x10, 0x00, 0x13, 0x14, 0x72, 0xC0, + 0x87, 0x74, 0x60, 0x87, 0x36, 0x68, 0x87, 0x79, 0x68, 0x03, 0x72, 0xC0, + 0x87, 0x0D, 0xAF, 0x50, 0x0E, 0x6D, 0xD0, 0x0E, 0x7A, 0x50, 0x0E, 0x6D, + 0x00, 0x0F, 0x7A, 0x30, 0x07, 0x72, 0xA0, 0x07, 0x73, 0x20, 0x07, 0x6D, + 0x90, 0x0E, 0x71, 0xA0, 0x07, 0x73, 0x20, 0x07, 0x6D, 0x90, 0x0E, 0x78, + 0xA0, 0x07, 0x73, 0x20, 0x07, 0x6D, 0x90, 0x0E, 0x71, 0x60, 0x07, 0x7A, + 0x30, 0x07, 0x72, 0xD0, 0x06, 0xE9, 0x30, 0x07, 0x72, 0xA0, 0x07, 0x73, + 0x20, 0x07, 0x6D, 0x90, 0x0E, 0x76, 0x40, 0x07, 0x7A, 0x60, 0x07, 0x74, + 0xD0, 0x06, 0xE6, 0x10, 0x07, 0x76, 0xA0, 0x07, 0x73, 0x20, 0x07, 0x6D, + 0x60, 0x0E, 0x73, 0x20, 0x07, 0x7A, 0x30, 0x07, 0x72, 0xD0, 0x06, 0xE6, + 0x60, 0x07, 0x74, 0xA0, 0x07, 0x76, 0x40, 0x07, 0x6D, 0xE0, 0x0E, 0x78, + 0xA0, 0x07, 0x71, 0x60, 0x07, 0x7A, 0x30, 0x07, 0x72, 0xA0, 0x07, 0x76, + 0x40, 0x07, 0x43, 0x9E, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x86, 0x3C, 0x06, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0C, 0x79, 0x10, 0x20, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xF2, 0x2C, 0x40, 0x00, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xE4, 0x81, 0x80, 0x00, 0x18, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xC8, 0x23, 0x01, 0x01, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x16, 0x08, 0x00, + 0x0F, 0x00, 0x00, 0x00, 0x32, 0x1E, 0x98, 0x14, 0x19, 0x11, 0x4C, 0x90, + 0x8C, 0x09, 0x26, 0x47, 0xC6, 0x04, 0x43, 0x22, 0x25, 0x30, 0x02, 0x50, + 0x0C, 0x45, 0x50, 0x12, 0x65, 0x50, 0x1E, 0xA5, 0x50, 0x34, 0x85, 0x40, + 0xA5, 0x24, 0x46, 0x00, 0x8A, 0xA0, 0x10, 0x0A, 0x84, 0xF0, 0x0C, 0x00, + 0xE9, 0x19, 0x00, 0xDA, 0x63, 0x21, 0x06, 0x11, 0x08, 0x04, 0xF2, 0x3C, + 0x00, 0x00, 0x00, 0x00, 0x79, 0x18, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00, + 0x1A, 0x03, 0x4C, 0x90, 0x46, 0x02, 0x13, 0xC4, 0x31, 0x20, 0xC3, 0x1B, + 0x43, 0x81, 0x93, 0x4B, 0xB3, 0x0B, 0xA3, 0x2B, 0x4B, 0x01, 0x89, 0x71, + 0xC1, 0x71, 0x81, 0x71, 0xA1, 0xB9, 0xC9, 0x81, 0x01, 0x41, 0x21, 0xB3, + 0x89, 0x11, 0xBB, 0x99, 0x31, 0xAB, 0xB1, 0x49, 0xD9, 0x10, 0x04, 0x13, + 0x04, 0xC2, 0x98, 0x20, 0x10, 0xC7, 0x06, 0x61, 0x20, 0x36, 0x08, 0x04, + 0x41, 0x01, 0x6E, 0x6E, 0x82, 0x40, 0x20, 0x1B, 0x86, 0x03, 0x21, 0x26, + 0x08, 0x58, 0xC5, 0xA4, 0xEE, 0x8B, 0xAE, 0x0C, 0x8F, 0xAE, 0x4E, 0xAE, + 0x6C, 0x82, 0x40, 0x24, 0x13, 0x04, 0x42, 0xD9, 0x20, 0x10, 0xCD, 0x86, + 0x84, 0x50, 0x16, 0x86, 0x18, 0x18, 0xC2, 0xD9, 0x10, 0x3C, 0x13, 0x04, + 0xCD, 0xA2, 0xF4, 0x55, 0xF7, 0x45, 0x57, 0x86, 0x47, 0x57, 0x27, 0x57, + 0xF6, 0x35, 0x17, 0xD6, 0x06, 0xC7, 0x56, 0x26, 0xB7, 0x01, 0x21, 0x22, + 0x89, 0x21, 0x06, 0x02, 0xD8, 0x10, 0x4C, 0x1B, 0x08, 0x08, 0x00, 0xA8, + 0x09, 0x82, 0x00, 0x6C, 0x00, 0x36, 0x0C, 0xC4, 0x75, 0x6D, 0x08, 0xB0, + 0x0D, 0xC3, 0x60, 0x65, 0x13, 0x84, 0xED, 0xDA, 0x10, 0x6C, 0x24, 0xDA, + 0xC2, 0xD2, 0xDC, 0x88, 0x50, 0x15, 0x61, 0x0D, 0x3D, 0x3D, 0x49, 0x11, + 0x4D, 0x10, 0x0A, 0x68, 0x82, 0x50, 0x44, 0x1B, 0x02, 0x62, 0x82, 0x50, + 0x48, 0x13, 0x84, 0x62, 0x9A, 0x20, 0x10, 0xCB, 0x04, 0x81, 0x60, 0x36, + 0x08, 0x64, 0x50, 0x06, 0x1B, 0x16, 0xC2, 0xFB, 0xC0, 0x20, 0x0C, 0xC4, + 0x60, 0x18, 0x03, 0x02, 0x0C, 0xCC, 0x60, 0x43, 0x30, 0x6C, 0x10, 0xC8, + 0x80, 0x0C, 0x36, 0x2C, 0x83, 0xF7, 0x81, 0x01, 0x1A, 0x88, 0xC1, 0x20, + 0x06, 0x03, 0x18, 0xA4, 0xC1, 0x06, 0xE1, 0x0C, 0xD4, 0x80, 0xC9, 0x94, + 0xD5, 0x17, 0x55, 0x98, 0xDC, 0x59, 0x19, 0xDD, 0x04, 0xA1, 0xA0, 0x36, + 0x2C, 0x04, 0x1B, 0x7C, 0x6D, 0x10, 0x06, 0x60, 0x30, 0x8C, 0x01, 0x01, + 0x06, 0x66, 0xB0, 0x21, 0x70, 0x83, 0x0D, 0xC3, 0x1A, 0xBC, 0x01, 0xB0, + 0xA1, 0xB0, 0x3A, 0x38, 0xA8, 0x00, 0x22, 0x62, 0x72, 0x61, 0x6E, 0x63, + 0x68, 0x65, 0x73, 0x34, 0xCC, 0xD8, 0xDE, 0xC2, 0xE8, 0xE6, 0x26, 0x08, + 0x44, 0xC3, 0x22, 0xCD, 0x6D, 0x8E, 0x6E, 0x6E, 0x82, 0x40, 0x38, 0x34, + 0xE6, 0xD2, 0xCE, 0xBE, 0xD8, 0xC8, 0x68, 0xCC, 0xA5, 0x9D, 0x7D, 0xCD, + 0xD1, 0x4D, 0x10, 0x88, 0x87, 0x08, 0x5D, 0x19, 0xDE, 0x97, 0xDB, 0x9B, + 0x5C, 0xDB, 0x06, 0x46, 0x0E, 0x86, 0x39, 0xA0, 0x83, 0x3A, 0xB0, 0x83, + 0x3B, 0x40, 0xF0, 0x20, 0x0F, 0xF4, 0x60, 0xA8, 0xC2, 0xC6, 0x66, 0xD7, + 0xE6, 0x92, 0x46, 0x56, 0xE6, 0x46, 0x37, 0x25, 0x08, 0xAA, 0x90, 0xE1, + 0xB9, 0xD8, 0x95, 0xC9, 0xCD, 0xA5, 0xBD, 0xB9, 0x4D, 0x09, 0x88, 0x26, + 0x64, 0x78, 0x2E, 0x76, 0x61, 0x6C, 0x76, 0x65, 0x72, 0x53, 0x82, 0xA2, + 0x0E, 0x19, 0x9E, 0xCB, 0x1C, 0x5A, 0x18, 0x59, 0x99, 0x5C, 0xD3, 0x1B, + 0x59, 0x19, 0xDB, 0x94, 0x00, 0x29, 0x43, 0x86, 0xE7, 0x22, 0x57, 0x36, + 0xF7, 0x56, 0x27, 0x37, 0x56, 0x36, 0x37, 0x25, 0xA0, 0x2A, 0x91, 0xE1, + 0xB9, 0xD0, 0xE5, 0xC1, 0x95, 0x05, 0xB9, 0xB9, 0xBD, 0xD1, 0x85, 0xD1, + 0xA5, 0xBD, 0xB9, 0xCD, 0x4D, 0x09, 0xB2, 0x3A, 0x64, 0x78, 0x2E, 0x76, + 0x69, 0x65, 0x77, 0x49, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x82, + 0xAD, 0x0E, 0x19, 0x9E, 0x4B, 0x99, 0x1B, 0x9D, 0x5C, 0x1E, 0xD4, 0x5B, + 0x9A, 0x1B, 0xDD, 0xDC, 0x94, 0x00, 0x0E, 0xBA, 0x90, 0xE1, 0xB9, 0x8C, + 0xBD, 0xD5, 0xB9, 0xD1, 0x95, 0xC9, 0xCD, 0x4D, 0x09, 0xF4, 0x00, 0x00, + 0x79, 0x18, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x33, 0x08, 0x80, 0x1C, + 0xC4, 0xE1, 0x1C, 0x66, 0x14, 0x01, 0x3D, 0x88, 0x43, 0x38, 0x84, 0xC3, + 0x8C, 0x42, 0x80, 0x07, 0x79, 0x78, 0x07, 0x73, 0x98, 0x71, 0x0C, 0xE6, + 0x00, 0x0F, 0xED, 0x10, 0x0E, 0xF4, 0x80, 0x0E, 0x33, 0x0C, 0x42, 0x1E, + 0xC2, 0xC1, 0x1D, 0xCE, 0xA1, 0x1C, 0x66, 0x30, 0x05, 0x3D, 0x88, 0x43, + 0x38, 0x84, 0x83, 0x1B, 0xCC, 0x03, 0x3D, 0xC8, 0x43, 0x3D, 0x8C, 0x03, + 0x3D, 0xCC, 0x78, 0x8C, 0x74, 0x70, 0x07, 0x7B, 0x08, 0x07, 0x79, 0x48, + 0x87, 0x70, 0x70, 0x07, 0x7A, 0x70, 0x03, 0x76, 0x78, 0x87, 0x70, 0x20, + 0x87, 0x19, 0xCC, 0x11, 0x0E, 0xEC, 0x90, 0x0E, 0xE1, 0x30, 0x0F, 0x6E, + 0x30, 0x0F, 0xE3, 0xF0, 0x0E, 0xF0, 0x50, 0x0E, 0x33, 0x10, 0xC4, 0x1D, + 0xDE, 0x21, 0x1C, 0xD8, 0x21, 0x1D, 0xC2, 0x61, 0x1E, 0x66, 0x30, 0x89, + 0x3B, 0xBC, 0x83, 0x3B, 0xD0, 0x43, 0x39, 0xB4, 0x03, 0x3C, 0xBC, 0x83, + 0x3C, 0x84, 0x03, 0x3B, 0xCC, 0xF0, 0x14, 0x76, 0x60, 0x07, 0x7B, 0x68, + 0x07, 0x37, 0x68, 0x87, 0x72, 0x68, 0x07, 0x37, 0x80, 0x87, 0x70, 0x90, + 0x87, 0x70, 0x60, 0x07, 0x76, 0x28, 0x07, 0x76, 0xF8, 0x05, 0x76, 0x78, + 0x87, 0x77, 0x80, 0x87, 0x5F, 0x08, 0x87, 0x71, 0x18, 0x87, 0x72, 0x98, + 0x87, 0x79, 0x98, 0x81, 0x2C, 0xEE, 0xF0, 0x0E, 0xEE, 0xE0, 0x0E, 0xF5, + 0xC0, 0x0E, 0xEC, 0x30, 0x03, 0x62, 0xC8, 0xA1, 0x1C, 0xE4, 0xA1, 0x1C, + 0xCC, 0xA1, 0x1C, 0xE4, 0xA1, 0x1C, 0xDC, 0x61, 0x1C, 0xCA, 0x21, 0x1C, + 0xC4, 0x81, 0x1D, 0xCA, 0x61, 0x06, 0xD6, 0x90, 0x43, 0x39, 0xC8, 0x43, + 0x39, 0x98, 0x43, 0x39, 0xC8, 0x43, 0x39, 0xB8, 0xC3, 0x38, 0x94, 0x43, + 0x38, 0x88, 0x03, 0x3B, 0x94, 0xC3, 0x2F, 0xBC, 0x83, 0x3C, 0xFC, 0x82, + 0x3B, 0xD4, 0x03, 0x3B, 0xB0, 0xC3, 0x8C, 0xC8, 0x21, 0x07, 0x7C, 0x70, + 0x03, 0x72, 0x10, 0x87, 0x73, 0x70, 0x03, 0x7B, 0x08, 0x07, 0x79, 0x60, + 0x87, 0x70, 0xC8, 0x87, 0x77, 0xA8, 0x07, 0x7A, 0x98, 0x81, 0x3C, 0xE4, + 0x80, 0x0F, 0x6E, 0x40, 0x0F, 0xE5, 0xD0, 0x0E, 0xF0, 0x30, 0x83, 0x81, + 0xC8, 0x01, 0x1F, 0xDC, 0x40, 0x1C, 0xE4, 0xA1, 0x1C, 0xC2, 0x61, 0x1D, + 0xDC, 0x40, 0x1C, 0xE4, 0x01, 0x00, 0x00, 0x00, 0x71, 0x20, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x56, 0x20, 0x0D, 0x97, 0xEF, 0x3C, 0xBE, 0x10, + 0x11, 0xC0, 0x44, 0x84, 0x40, 0x33, 0x2C, 0x84, 0x0D, 0x34, 0xC3, 0xE5, + 0x3B, 0x8F, 0x3F, 0x20, 0x92, 0x00, 0x44, 0x83, 0x05, 0x4C, 0xC3, 0xE5, + 0x3B, 0x8F, 0xBF, 0x38, 0xC0, 0x20, 0x36, 0x0F, 0x35, 0xF9, 0xC5, 0x6D, + 0x1B, 0x01, 0x34, 0x5C, 0xBE, 0xF3, 0xF8, 0x12, 0xC0, 0x3C, 0x0B, 0xE1, + 0x17, 0xB7, 0x6D, 0x02, 0xD5, 0x70, 0xF9, 0xCE, 0xE3, 0x4B, 0x93, 0x13, + 0x11, 0x28, 0x35, 0x3D, 0xD4, 0xE4, 0x17, 0xB7, 0x6D, 0x00, 0x04, 0x03, + 0x20, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x41, 0x53, 0x48, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDB, 0xEF, 0x2F, 0x4E, + 0x19, 0x19, 0xE7, 0x34, 0x6F, 0x14, 0xDA, 0xC0, 0xE4, 0xBE, 0xE2, 0xF7, + 0x44, 0x58, 0x49, 0x4C, 0x24, 0x07, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, + 0xC9, 0x01, 0x00, 0x00, 0x44, 0x58, 0x49, 0x4C, 0x00, 0x01, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x0C, 0x07, 0x00, 0x00, 0x42, 0x43, 0xC0, 0xDE, + 0x21, 0x0C, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x0B, 0x82, 0x20, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x07, 0x81, 0x23, 0x91, + 0x41, 0xC8, 0x04, 0x49, 0x06, 0x10, 0x32, 0x39, 0x92, 0x01, 0x84, 0x0C, + 0x25, 0x05, 0x08, 0x19, 0x1E, 0x04, 0x8B, 0x62, 0x80, 0x14, 0x45, 0x02, + 0x42, 0x92, 0x0B, 0x42, 0xA4, 0x10, 0x32, 0x14, 0x38, 0x08, 0x18, 0x4B, + 0x0A, 0x32, 0x52, 0x88, 0x48, 0x90, 0x14, 0x20, 0x43, 0x46, 0x88, 0xA5, + 0x00, 0x19, 0x32, 0x42, 0xE4, 0x48, 0x0E, 0x90, 0x91, 0x22, 0xC4, 0x50, + 0x41, 0x51, 0x81, 0x8C, 0xE1, 0x83, 0xE5, 0x8A, 0x04, 0x29, 0x46, 0x06, + 0x51, 0x18, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x1B, 0x8C, 0xE0, 0xFF, + 0xFF, 0xFF, 0xFF, 0x07, 0x40, 0x02, 0xA8, 0x0D, 0x84, 0xF0, 0xFF, 0xFF, + 0xFF, 0xFF, 0x03, 0x20, 0x6D, 0x30, 0x86, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, + 0x00, 0x09, 0xA8, 0x00, 0x49, 0x18, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x13, 0x82, 0x60, 0x42, 0x20, 0x4C, 0x08, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x89, 0x20, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x32, 0x22, 0x48, 0x09, + 0x20, 0x64, 0x85, 0x04, 0x93, 0x22, 0xA4, 0x84, 0x04, 0x93, 0x22, 0xE3, + 0x84, 0xA1, 0x90, 0x14, 0x12, 0x4C, 0x8A, 0x8C, 0x0B, 0x84, 0xA4, 0x4C, + 0x10, 0x74, 0x23, 0x00, 0x25, 0x00, 0x14, 0x66, 0x00, 0xE6, 0x08, 0xC0, + 0x60, 0x8E, 0x00, 0x29, 0xC6, 0x20, 0x84, 0x14, 0x42, 0xA6, 0x18, 0x80, + 0x10, 0x52, 0x06, 0xA1, 0x39, 0x82, 0xA0, 0x0C, 0x80, 0x28, 0x5A, 0x37, + 0x0D, 0x97, 0x3F, 0x61, 0x0F, 0x21, 0xF9, 0x2B, 0x21, 0xAD, 0xC4, 0xE4, + 0x17, 0xB7, 0x8D, 0x8A, 0x31, 0xC6, 0x20, 0x54, 0xEE, 0x19, 0x2E, 0x7F, + 0xC2, 0x1E, 0x42, 0xF2, 0x43, 0xA0, 0x19, 0x16, 0x02, 0x05, 0xAE, 0x30, + 0x8D, 0xBC, 0x37, 0xC6, 0x18, 0x84, 0x90, 0x41, 0xB0, 0x18, 0x8F, 0x14, + 0x42, 0x14, 0xC9, 0x81, 0x80, 0x61, 0x04, 0x62, 0x98, 0xA9, 0x0D, 0xC6, + 0x81, 0x1D, 0xC2, 0x61, 0x1E, 0xE6, 0xC1, 0x0D, 0x68, 0xA1, 0x1C, 0xF0, + 0x81, 0x1E, 0xEA, 0x41, 0x1E, 0xCA, 0x41, 0x0E, 0x48, 0x81, 0x0F, 0xEC, + 0xA1, 0x1C, 0xC6, 0x81, 0x1E, 0xDE, 0x41, 0x1E, 0xF8, 0xC0, 0x1C, 0xD8, + 0xE1, 0x1D, 0xC2, 0x81, 0x1E, 0xD8, 0x00, 0x0C, 0xE8, 0xC0, 0x0F, 0xC0, + 0xC0, 0x0F, 0xF4, 0x40, 0x0F, 0xDA, 0x21, 0x1D, 0xE0, 0x61, 0x1E, 0x7E, + 0x81, 0x1E, 0xF2, 0x01, 0x1E, 0xCA, 0x01, 0x05, 0x64, 0x26, 0x31, 0x18, + 0x07, 0x76, 0x08, 0x87, 0x79, 0x98, 0x07, 0x37, 0xA0, 0x85, 0x72, 0xC0, + 0x07, 0x7A, 0xA8, 0x07, 0x79, 0x28, 0x07, 0x39, 0x20, 0x05, 0x3E, 0xB0, + 0x87, 0x72, 0x18, 0x07, 0x7A, 0x78, 0x07, 0x79, 0xE0, 0x03, 0x73, 0x60, + 0x87, 0x77, 0x08, 0x07, 0x7A, 0x60, 0x03, 0x30, 0xA0, 0x03, 0x3F, 0x00, + 0x03, 0x3F, 0x40, 0x42, 0xB5, 0x74, 0x6F, 0x92, 0xA6, 0x88, 0x12, 0x26, + 0x9F, 0x05, 0x98, 0x67, 0x21, 0x22, 0x76, 0x02, 0x26, 0x02, 0x05, 0x84, + 0x72, 0x22, 0x90, 0x29, 0x00, 0x00, 0x00, 0x00, 0x13, 0x14, 0x72, 0xC0, + 0x87, 0x74, 0x60, 0x87, 0x36, 0x68, 0x87, 0x79, 0x68, 0x03, 0x72, 0xC0, + 0x87, 0x0D, 0xAF, 0x50, 0x0E, 0x6D, 0xD0, 0x0E, 0x7A, 0x50, 0x0E, 0x6D, + 0x00, 0x0F, 0x7A, 0x30, 0x07, 0x72, 0xA0, 0x07, 0x73, 0x20, 0x07, 0x6D, + 0x90, 0x0E, 0x71, 0xA0, 0x07, 0x73, 0x20, 0x07, 0x6D, 0x90, 0x0E, 0x78, + 0xA0, 0x07, 0x73, 0x20, 0x07, 0x6D, 0x90, 0x0E, 0x71, 0x60, 0x07, 0x7A, + 0x30, 0x07, 0x72, 0xD0, 0x06, 0xE9, 0x30, 0x07, 0x72, 0xA0, 0x07, 0x73, + 0x20, 0x07, 0x6D, 0x90, 0x0E, 0x76, 0x40, 0x07, 0x7A, 0x60, 0x07, 0x74, + 0xD0, 0x06, 0xE6, 0x10, 0x07, 0x76, 0xA0, 0x07, 0x73, 0x20, 0x07, 0x6D, + 0x60, 0x0E, 0x73, 0x20, 0x07, 0x7A, 0x30, 0x07, 0x72, 0xD0, 0x06, 0xE6, + 0x60, 0x07, 0x74, 0xA0, 0x07, 0x76, 0x40, 0x07, 0x6D, 0xE0, 0x0E, 0x78, + 0xA0, 0x07, 0x71, 0x60, 0x07, 0x7A, 0x30, 0x07, 0x72, 0xA0, 0x07, 0x76, + 0x40, 0x07, 0x43, 0x9E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x86, 0x3C, 0x06, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0C, 0x79, 0x10, 0x20, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xF2, 0x2C, 0x40, 0x00, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xE4, 0x81, 0x80, 0x00, 0x18, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xC8, 0x23, 0x01, 0x01, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x16, 0x08, 0x00, + 0x0E, 0x00, 0x00, 0x00, 0x32, 0x1E, 0x98, 0x14, 0x19, 0x11, 0x4C, 0x90, + 0x8C, 0x09, 0x26, 0x47, 0xC6, 0x04, 0x43, 0x22, 0x25, 0x30, 0x02, 0x50, + 0x10, 0xC5, 0x50, 0x04, 0x25, 0x51, 0x06, 0xE5, 0x41, 0xA5, 0x24, 0x46, + 0x00, 0x8A, 0xA0, 0x10, 0x0A, 0x84, 0xF0, 0x0C, 0x00, 0xE9, 0x19, 0x00, + 0xDA, 0x63, 0x21, 0x06, 0x11, 0x08, 0x04, 0xF2, 0x3C, 0x00, 0x00, 0x00, + 0x79, 0x18, 0x00, 0x00, 0x5A, 0x00, 0x00, 0x00, 0x1A, 0x03, 0x4C, 0x90, + 0x46, 0x02, 0x13, 0xC4, 0x31, 0x20, 0xC3, 0x1B, 0x43, 0x81, 0x93, 0x4B, + 0xB3, 0x0B, 0xA3, 0x2B, 0x4B, 0x01, 0x89, 0x71, 0xC1, 0x71, 0x81, 0x71, + 0xA1, 0xB9, 0xC9, 0x81, 0x01, 0x41, 0x21, 0xB3, 0x89, 0x11, 0xBB, 0x99, + 0x31, 0xAB, 0xB1, 0x49, 0xD9, 0x10, 0x04, 0x13, 0x04, 0xC2, 0x98, 0x20, + 0x10, 0xC7, 0x06, 0x61, 0x20, 0x26, 0x08, 0x04, 0xB2, 0x41, 0x18, 0x0C, + 0x0A, 0x70, 0x73, 0x13, 0x04, 0x22, 0xD9, 0x30, 0x20, 0x09, 0x31, 0x41, + 0xC0, 0x26, 0x02, 0x13, 0x04, 0x42, 0x99, 0x20, 0x10, 0xCB, 0x06, 0x81, + 0x70, 0x36, 0x24, 0xC4, 0xC2, 0x34, 0xC4, 0xD0, 0x10, 0xCF, 0x86, 0x00, + 0x9A, 0x20, 0x68, 0xD4, 0x06, 0x84, 0x90, 0x98, 0x86, 0x18, 0x08, 0x60, + 0x43, 0x30, 0x6D, 0x20, 0x22, 0x00, 0xA0, 0x26, 0x08, 0x5B, 0xB5, 0x21, + 0xB0, 0x26, 0x08, 0x02, 0x40, 0xA2, 0x2D, 0x2C, 0xCD, 0x8D, 0x08, 0x55, + 0x11, 0xD6, 0xD0, 0xD3, 0x93, 0x14, 0xD1, 0x04, 0xA1, 0x70, 0x26, 0x08, + 0xC5, 0xB3, 0x21, 0x20, 0x26, 0x08, 0x05, 0x34, 0x41, 0x28, 0xA2, 0x09, + 0x02, 0xC1, 0x4C, 0x10, 0x88, 0x66, 0x83, 0x00, 0x06, 0x61, 0xB0, 0x61, + 0x21, 0xB4, 0x8D, 0xEB, 0xBC, 0xE1, 0x23, 0x38, 0x31, 0xD8, 0x10, 0x0C, + 0x1B, 0x04, 0x30, 0x00, 0x83, 0x0D, 0xCB, 0xA0, 0x6D, 0x1C, 0x19, 0x78, + 0x83, 0x37, 0x70, 0x65, 0xB0, 0x41, 0x18, 0x03, 0x33, 0x60, 0x32, 0x65, + 0xF5, 0x45, 0x15, 0x26, 0x77, 0x56, 0x46, 0x37, 0x41, 0x28, 0xA4, 0x0D, + 0x0B, 0x81, 0x06, 0x5B, 0x1A, 0x74, 0xDC, 0xF0, 0x11, 0x9C, 0x18, 0x6C, + 0x08, 0xD4, 0x60, 0xC3, 0x70, 0x06, 0x6B, 0x00, 0x6C, 0x28, 0xB0, 0x8C, + 0x0D, 0x2A, 0xA0, 0x0A, 0x1B, 0x9B, 0x5D, 0x9B, 0x4B, 0x1A, 0x59, 0x99, + 0x1B, 0xDD, 0x94, 0x20, 0xA8, 0x42, 0x86, 0xE7, 0x62, 0x57, 0x26, 0x37, + 0x97, 0xF6, 0xE6, 0x36, 0x25, 0x20, 0x9A, 0x90, 0xE1, 0xB9, 0xD8, 0x85, + 0xB1, 0xD9, 0x95, 0xC9, 0x4D, 0x09, 0x8C, 0x3A, 0x64, 0x78, 0x2E, 0x73, + 0x68, 0x61, 0x64, 0x65, 0x72, 0x4D, 0x6F, 0x64, 0x65, 0x6C, 0x53, 0x82, + 0xA4, 0x0C, 0x19, 0x9E, 0x8B, 0x5C, 0xD9, 0xDC, 0x5B, 0x9D, 0xDC, 0x58, + 0xD9, 0xDC, 0x94, 0x80, 0xAA, 0x43, 0x86, 0xE7, 0x62, 0x97, 0x56, 0x76, + 0x97, 0x44, 0x36, 0x45, 0x17, 0x46, 0x57, 0x36, 0x25, 0xB0, 0xEA, 0x90, + 0xE1, 0xB9, 0x94, 0xB9, 0xD1, 0xC9, 0xE5, 0x41, 0xBD, 0xA5, 0xB9, 0xD1, + 0xCD, 0x4D, 0x09, 0xD8, 0x00, 0x00, 0x00, 0x00, 0x79, 0x18, 0x00, 0x00, + 0x51, 0x00, 0x00, 0x00, 0x33, 0x08, 0x80, 0x1C, 0xC4, 0xE1, 0x1C, 0x66, + 0x14, 0x01, 0x3D, 0x88, 0x43, 0x38, 0x84, 0xC3, 0x8C, 0x42, 0x80, 0x07, + 0x79, 0x78, 0x07, 0x73, 0x98, 0x71, 0x0C, 0xE6, 0x00, 0x0F, 0xED, 0x10, + 0x0E, 0xF4, 0x80, 0x0E, 0x33, 0x0C, 0x42, 0x1E, 0xC2, 0xC1, 0x1D, 0xCE, + 0xA1, 0x1C, 0x66, 0x30, 0x05, 0x3D, 0x88, 0x43, 0x38, 0x84, 0x83, 0x1B, + 0xCC, 0x03, 0x3D, 0xC8, 0x43, 0x3D, 0x8C, 0x03, 0x3D, 0xCC, 0x78, 0x8C, + 0x74, 0x70, 0x07, 0x7B, 0x08, 0x07, 0x79, 0x48, 0x87, 0x70, 0x70, 0x07, + 0x7A, 0x70, 0x03, 0x76, 0x78, 0x87, 0x70, 0x20, 0x87, 0x19, 0xCC, 0x11, + 0x0E, 0xEC, 0x90, 0x0E, 0xE1, 0x30, 0x0F, 0x6E, 0x30, 0x0F, 0xE3, 0xF0, + 0x0E, 0xF0, 0x50, 0x0E, 0x33, 0x10, 0xC4, 0x1D, 0xDE, 0x21, 0x1C, 0xD8, + 0x21, 0x1D, 0xC2, 0x61, 0x1E, 0x66, 0x30, 0x89, 0x3B, 0xBC, 0x83, 0x3B, + 0xD0, 0x43, 0x39, 0xB4, 0x03, 0x3C, 0xBC, 0x83, 0x3C, 0x84, 0x03, 0x3B, + 0xCC, 0xF0, 0x14, 0x76, 0x60, 0x07, 0x7B, 0x68, 0x07, 0x37, 0x68, 0x87, + 0x72, 0x68, 0x07, 0x37, 0x80, 0x87, 0x70, 0x90, 0x87, 0x70, 0x60, 0x07, + 0x76, 0x28, 0x07, 0x76, 0xF8, 0x05, 0x76, 0x78, 0x87, 0x77, 0x80, 0x87, + 0x5F, 0x08, 0x87, 0x71, 0x18, 0x87, 0x72, 0x98, 0x87, 0x79, 0x98, 0x81, + 0x2C, 0xEE, 0xF0, 0x0E, 0xEE, 0xE0, 0x0E, 0xF5, 0xC0, 0x0E, 0xEC, 0x30, + 0x03, 0x62, 0xC8, 0xA1, 0x1C, 0xE4, 0xA1, 0x1C, 0xCC, 0xA1, 0x1C, 0xE4, + 0xA1, 0x1C, 0xDC, 0x61, 0x1C, 0xCA, 0x21, 0x1C, 0xC4, 0x81, 0x1D, 0xCA, + 0x61, 0x06, 0xD6, 0x90, 0x43, 0x39, 0xC8, 0x43, 0x39, 0x98, 0x43, 0x39, + 0xC8, 0x43, 0x39, 0xB8, 0xC3, 0x38, 0x94, 0x43, 0x38, 0x88, 0x03, 0x3B, + 0x94, 0xC3, 0x2F, 0xBC, 0x83, 0x3C, 0xFC, 0x82, 0x3B, 0xD4, 0x03, 0x3B, + 0xB0, 0xC3, 0x8C, 0xC8, 0x21, 0x07, 0x7C, 0x70, 0x03, 0x72, 0x10, 0x87, + 0x73, 0x70, 0x03, 0x7B, 0x08, 0x07, 0x79, 0x60, 0x87, 0x70, 0xC8, 0x87, + 0x77, 0xA8, 0x07, 0x7A, 0x98, 0x81, 0x3C, 0xE4, 0x80, 0x0F, 0x6E, 0x40, + 0x0F, 0xE5, 0xD0, 0x0E, 0xF0, 0x30, 0x83, 0x81, 0xC8, 0x01, 0x1F, 0xDC, + 0x40, 0x1C, 0xE4, 0xA1, 0x1C, 0xC2, 0x61, 0x1D, 0xDC, 0x40, 0x1C, 0xE4, + 0x01, 0x00, 0x00, 0x00, 0x71, 0x20, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x56, 0x20, 0x0D, 0x97, 0xEF, 0x3C, 0xBE, 0x10, 0x11, 0xC0, 0x44, 0x84, + 0x40, 0x33, 0x2C, 0x84, 0x0D, 0x34, 0xC3, 0xE5, 0x3B, 0x8F, 0x3F, 0x20, + 0x92, 0x00, 0x44, 0x83, 0x05, 0x4C, 0xC3, 0xE5, 0x3B, 0x8F, 0xBF, 0x38, + 0xC0, 0x20, 0x36, 0x0F, 0x35, 0xF9, 0xC5, 0x6D, 0x1B, 0x01, 0x34, 0x5C, + 0xBE, 0xF3, 0xF8, 0x12, 0xC0, 0x3C, 0x0B, 0xE1, 0x17, 0xB7, 0x6D, 0x02, + 0xD5, 0x70, 0xF9, 0xCE, 0xE3, 0x4B, 0x93, 0x13, 0x11, 0x28, 0x35, 0x3D, + 0xD4, 0xE4, 0x17, 0xB7, 0x6D, 0x00, 0x04, 0x03, 0x20, 0x0D, 0x00, 0x00, + 0x61, 0x20, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x13, 0x04, 0x43, 0x2C, + 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xA4, 0x6A, 0x60, 0x04, + 0x80, 0xC8, 0x0C, 0x40, 0x21, 0x94, 0x42, 0xC9, 0x15, 0x5E, 0x91, 0x06, + 0x50, 0x29, 0x83, 0x12, 0xA0, 0x31, 0x46, 0xD0, 0x9A, 0x73, 0xCE, 0x7B, + 0x33, 0x00, 0x00, 0x00, 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x20, 0x75, + 0xC7, 0xB4, 0x6D, 0xCB, 0x88, 0x41, 0x02, 0x80, 0x20, 0x18, 0x48, 0x1E, + 0x52, 0x70, 0x1C, 0x33, 0x62, 0x90, 0x00, 0x20, 0x08, 0x06, 0xC6, 0x18, + 0x2C, 0x5E, 0x57, 0x31, 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x60, 0x90, + 0x01, 0xF3, 0x79, 0x46, 0x33, 0x62, 0x90, 0x00, 0x20, 0x08, 0x06, 0x46, + 0x19, 0x34, 0xDF, 0x77, 0x39, 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x60, + 0x98, 0x81, 0x03, 0x06, 0x60, 0x80, 0x3C, 0x23, 0x06, 0x09, 0x00, 0x82, + 0x60, 0x60, 0x9C, 0xC1, 0x13, 0x06, 0x61, 0x80, 0x41, 0x23, 0x06, 0x09, + 0x00, 0x82, 0x60, 0x60, 0xA0, 0x01, 0x24, 0x06, 0x62, 0xB0, 0x44, 0x23, + 0x06, 0x0F, 0x00, 0x82, 0x60, 0x00, 0x99, 0x81, 0x83, 0x1C, 0x46, 0x91, + 0x24, 0x63, 0x30, 0x06, 0x52, 0x32, 0x9A, 0x10, 0x00, 0xA3, 0x09, 0x42, + 0x30, 0x9A, 0x30, 0x08, 0xA3, 0x09, 0xC4, 0x30, 0x1C, 0x11, 0x3C, 0xC4, + 0x37, 0xCB, 0x10, 0x08, 0xC1, 0x88, 0x81, 0x01, 0x80, 0x20, 0x18, 0x2C, + 0x6D, 0x30, 0x69, 0xB3, 0x04, 0x82, 0x15, 0x8A, 0x7C, 0xAC, 0x50, 0xE4, + 0x63, 0x85, 0x22, 0x1F, 0x2B, 0x14, 0xF9, 0x8C, 0x18, 0x24, 0x00, 0x08, + 0x82, 0x01, 0x22, 0x07, 0x5A, 0x1B, 0xB4, 0x41, 0x19, 0x10, 0x23, 0x06, + 0x09, 0x00, 0x82, 0x60, 0x80, 0xC8, 0x81, 0xD6, 0x06, 0x6D, 0x50, 0x0D, + 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x80, 0xC8, 0x81, 0xD6, 0x06, 0x6D, + 0x40, 0x06, 0xC2, 0x88, 0x41, 0x02, 0x80, 0x20, 0x18, 0x20, 0x72, 0xA0, + 0xB5, 0x41, 0x1B, 0x58, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +inline unsigned char default_vertex_dxd12[4344] = { + 0x44, 0x58, 0x42, 0x43, 0xD2, 0x05, 0xFD, 0x75, 0x55, 0x43, 0x95, 0xF0, + 0x31, 0xD0, 0xDE, 0x48, 0x9D, 0x22, 0xB1, 0xC3, 0x01, 0x00, 0x00, 0x00, + 0xF8, 0x10, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, + 0x4C, 0x00, 0x00, 0x00, 0xC8, 0x00, 0x00, 0x00, 0x50, 0x01, 0x00, 0x00, + 0x8C, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x1C, 0x09, 0x00, 0x00, + 0x53, 0x46, 0x49, 0x30, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x49, 0x53, 0x47, 0x31, 0x74, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x45, 0x58, 0x43, + 0x4F, 0x4F, 0x52, 0x44, 0x00, 0x00, 0x00, 0x00, 0x4F, 0x53, 0x47, 0x31, + 0x80, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x68, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x0C, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x54, 0x45, 0x58, 0x43, 0x4F, 0x4F, 0x52, 0x44, 0x00, 0x53, 0x56, 0x5F, + 0x50, 0x6F, 0x73, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x00, 0x00, 0x00, 0x00, + 0x50, 0x53, 0x56, 0x30, 0x34, 0x01, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x01, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x34, 0x00, 0x00, 0x00, 0x00, 0x54, 0x45, 0x58, 0x43, 0x4F, 0x4F, 0x52, + 0x44, 0x00, 0x54, 0x45, 0x58, 0x43, 0x4F, 0x4F, 0x52, 0x44, 0x00, 0x54, + 0x45, 0x58, 0x43, 0x4F, 0x4F, 0x52, 0x44, 0x00, 0x54, 0x45, 0x58, 0x43, + 0x4F, 0x4F, 0x52, 0x44, 0x00, 0x54, 0x45, 0x58, 0x43, 0x4F, 0x4F, 0x52, + 0x44, 0x00, 0x6D, 0x61, 0x69, 0x6E, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x43, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x44, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x02, 0x42, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x44, 0x00, 0x03, 0x02, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x42, 0x00, 0x03, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x03, + 0x03, 0x04, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, + 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x53, 0x54, 0x41, 0x54, 0x6C, 0x06, 0x00, 0x00, + 0x60, 0x00, 0x01, 0x00, 0x9B, 0x01, 0x00, 0x00, 0x44, 0x58, 0x49, 0x4C, + 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x54, 0x06, 0x00, 0x00, + 0x42, 0x43, 0xC0, 0xDE, 0x21, 0x0C, 0x00, 0x00, 0x92, 0x01, 0x00, 0x00, + 0x0B, 0x82, 0x20, 0x00, 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x07, 0x81, 0x23, 0x91, 0x41, 0xC8, 0x04, 0x49, 0x06, 0x10, 0x32, 0x39, + 0x92, 0x01, 0x84, 0x0C, 0x25, 0x05, 0x08, 0x19, 0x1E, 0x04, 0x8B, 0x62, + 0x80, 0x14, 0x45, 0x02, 0x42, 0x92, 0x0B, 0x42, 0xA4, 0x10, 0x32, 0x14, + 0x38, 0x08, 0x18, 0x4B, 0x0A, 0x32, 0x52, 0x88, 0x48, 0x90, 0x14, 0x20, + 0x43, 0x46, 0x88, 0xA5, 0x00, 0x19, 0x32, 0x42, 0xE4, 0x48, 0x0E, 0x90, + 0x91, 0x22, 0xC4, 0x50, 0x41, 0x51, 0x81, 0x8C, 0xE1, 0x83, 0xE5, 0x8A, + 0x04, 0x29, 0x46, 0x06, 0x51, 0x18, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x1B, 0x8C, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x40, 0x02, 0xA8, 0x0D, + 0x84, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x20, 0x6D, 0x30, 0x86, 0xFF, + 0xFF, 0xFF, 0xFF, 0x1F, 0x00, 0x09, 0xA8, 0x00, 0x49, 0x18, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x13, 0x82, 0x60, 0x42, 0x20, 0x4C, 0x08, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x89, 0x20, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, + 0x32, 0x22, 0x48, 0x09, 0x20, 0x64, 0x85, 0x04, 0x93, 0x22, 0xA4, 0x84, + 0x04, 0x93, 0x22, 0xE3, 0x84, 0xA1, 0x90, 0x14, 0x12, 0x4C, 0x8A, 0x8C, + 0x0B, 0x84, 0xA4, 0x4C, 0x10, 0x68, 0x23, 0x00, 0x25, 0x00, 0x14, 0x66, + 0x00, 0xE6, 0x08, 0xC0, 0x60, 0x8E, 0x00, 0x29, 0xC6, 0x20, 0x84, 0x14, + 0x42, 0xA6, 0x18, 0x80, 0x10, 0x52, 0x06, 0xA1, 0xA3, 0x86, 0xCB, 0x9F, + 0xB0, 0x87, 0x90, 0x7C, 0x6E, 0xA3, 0x8A, 0x95, 0x98, 0xFC, 0xE2, 0xB6, + 0x11, 0x31, 0xC6, 0x18, 0x54, 0xEE, 0x19, 0x2E, 0x7F, 0xC2, 0x1E, 0x42, + 0xF2, 0x43, 0xA0, 0x19, 0x16, 0x02, 0x05, 0xAB, 0x10, 0x8A, 0x30, 0x42, + 0xAD, 0x14, 0x83, 0x8C, 0x31, 0xE8, 0xCD, 0x11, 0x04, 0xC5, 0x60, 0xA4, + 0x10, 0x12, 0x49, 0x0E, 0x04, 0x0C, 0x23, 0x10, 0x43, 0x12, 0xD4, 0xBB, + 0x0E, 0x47, 0x9A, 0x16, 0x00, 0x73, 0xA8, 0xC9, 0x77, 0x1B, 0xA4, 0x70, + 0x22, 0x66, 0x5B, 0x1C, 0x41, 0x41, 0x81, 0xA5, 0x9B, 0x0C, 0x04, 0x00, + 0x13, 0x14, 0x72, 0xC0, 0x87, 0x74, 0x60, 0x87, 0x36, 0x68, 0x87, 0x79, + 0x68, 0x03, 0x72, 0xC0, 0x87, 0x0D, 0xAF, 0x50, 0x0E, 0x6D, 0xD0, 0x0E, + 0x7A, 0x50, 0x0E, 0x6D, 0x00, 0x0F, 0x7A, 0x30, 0x07, 0x72, 0xA0, 0x07, + 0x73, 0x20, 0x07, 0x6D, 0x90, 0x0E, 0x71, 0xA0, 0x07, 0x73, 0x20, 0x07, + 0x6D, 0x90, 0x0E, 0x78, 0xA0, 0x07, 0x73, 0x20, 0x07, 0x6D, 0x90, 0x0E, + 0x71, 0x60, 0x07, 0x7A, 0x30, 0x07, 0x72, 0xD0, 0x06, 0xE9, 0x30, 0x07, + 0x72, 0xA0, 0x07, 0x73, 0x20, 0x07, 0x6D, 0x90, 0x0E, 0x76, 0x40, 0x07, + 0x7A, 0x60, 0x07, 0x74, 0xD0, 0x06, 0xE6, 0x10, 0x07, 0x76, 0xA0, 0x07, + 0x73, 0x20, 0x07, 0x6D, 0x60, 0x0E, 0x73, 0x20, 0x07, 0x7A, 0x30, 0x07, + 0x72, 0xD0, 0x06, 0xE6, 0x60, 0x07, 0x74, 0xA0, 0x07, 0x76, 0x40, 0x07, + 0x6D, 0xE0, 0x0E, 0x78, 0xA0, 0x07, 0x71, 0x60, 0x07, 0x7A, 0x30, 0x07, + 0x72, 0xA0, 0x07, 0x76, 0x40, 0x07, 0x43, 0x9E, 0x00, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86, 0x3C, 0x06, 0x10, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x79, 0x10, 0x20, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xF2, 0x34, + 0x40, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xE4, + 0x79, 0x80, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, + 0xC8, 0x23, 0x01, 0x01, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x16, 0x08, 0x00, 0x12, 0x00, 0x00, 0x00, 0x32, 0x1E, 0x98, 0x14, + 0x19, 0x11, 0x4C, 0x90, 0x8C, 0x09, 0x26, 0x47, 0xC6, 0x04, 0x43, 0x22, + 0x25, 0x30, 0x02, 0x50, 0x0C, 0x05, 0x18, 0x50, 0x04, 0x85, 0x50, 0x06, + 0xE5, 0x50, 0x12, 0xE5, 0x51, 0x10, 0x25, 0x57, 0x14, 0x54, 0x4A, 0x62, + 0x04, 0xA0, 0x0C, 0x0A, 0xA1, 0x08, 0xE8, 0xCE, 0x00, 0x10, 0x9E, 0x01, + 0xA0, 0x3C, 0x16, 0xA3, 0x30, 0x20, 0x3E, 0x80, 0xF8, 0x00, 0xE2, 0x03, + 0x10, 0x08, 0x04, 0x02, 0x81, 0x01, 0x00, 0x00, 0x79, 0x18, 0x00, 0x00, + 0x92, 0x00, 0x00, 0x00, 0x1A, 0x03, 0x4C, 0x90, 0x46, 0x02, 0x13, 0xC4, + 0x31, 0x20, 0xC3, 0x1B, 0x43, 0x81, 0x93, 0x4B, 0xB3, 0x0B, 0xA3, 0x2B, + 0x4B, 0x01, 0x89, 0x71, 0xC1, 0x71, 0x81, 0x71, 0xA1, 0xB9, 0xC9, 0x81, + 0x01, 0x41, 0x21, 0xB3, 0x89, 0x11, 0xBB, 0x99, 0x31, 0xAB, 0xB1, 0x49, + 0xD9, 0x10, 0x04, 0x13, 0x04, 0xC2, 0x98, 0x20, 0x10, 0xC7, 0x06, 0x61, + 0x20, 0x36, 0x08, 0x04, 0x41, 0xC1, 0x6E, 0x6E, 0x82, 0x40, 0x20, 0x1B, + 0x86, 0x03, 0x21, 0x26, 0x08, 0x58, 0x46, 0xA6, 0xCA, 0x2D, 0xCD, 0xEC, + 0x4D, 0xAE, 0x4D, 0x88, 0xED, 0x6D, 0x6C, 0x6D, 0x82, 0x40, 0x24, 0x1B, + 0x10, 0x42, 0x59, 0x06, 0x62, 0x60, 0x80, 0x0D, 0x41, 0xB3, 0x81, 0x00, + 0x00, 0x07, 0x98, 0x20, 0x5C, 0x18, 0xB5, 0xAF, 0x19, 0x98, 0x2F, 0xAB, + 0xA4, 0xA2, 0xAB, 0x2F, 0x28, 0xA9, 0x27, 0xA5, 0xA2, 0x21, 0xAA, 0xA4, + 0x27, 0xA7, 0xAF, 0xA6, 0x20, 0x2A, 0xA9, 0x24, 0xAC, 0x09, 0x02, 0xA1, + 0x4C, 0x10, 0x88, 0x65, 0xC3, 0x30, 0x4D, 0xC3, 0x04, 0x81, 0x60, 0x26, + 0x08, 0x44, 0x33, 0x41, 0x20, 0x9C, 0x0D, 0x08, 0x12, 0x49, 0x54, 0x45, + 0x58, 0xD7, 0x06, 0x81, 0xC1, 0x36, 0x0C, 0x04, 0x94, 0x4D, 0x10, 0x04, + 0x60, 0x03, 0xB0, 0x61, 0x20, 0x38, 0x6E, 0x43, 0xD0, 0x6D, 0x18, 0x86, + 0xCD, 0x9B, 0x20, 0x64, 0xDA, 0x86, 0x00, 0x0C, 0x48, 0xB4, 0x85, 0xA5, + 0xB9, 0x11, 0xA1, 0x2A, 0xC2, 0x1A, 0x7A, 0x7A, 0x92, 0x22, 0x9A, 0x20, + 0x14, 0xD3, 0x04, 0xA1, 0xA0, 0x36, 0x04, 0xC4, 0x04, 0xA1, 0xA8, 0x36, + 0x08, 0x95, 0xB5, 0x61, 0x21, 0xC6, 0x80, 0x0C, 0xCA, 0xC0, 0x0C, 0xCA, + 0x60, 0x38, 0x03, 0xA2, 0x0C, 0xD0, 0x60, 0x43, 0x30, 0x4C, 0x10, 0x0A, + 0x6B, 0x82, 0x40, 0x3C, 0x1B, 0x84, 0x8A, 0x0D, 0x36, 0x2C, 0xC3, 0x18, + 0x90, 0x41, 0x19, 0xA8, 0x41, 0x19, 0x0C, 0x6B, 0x30, 0x94, 0x41, 0x1B, + 0x6C, 0x08, 0xA4, 0x09, 0x42, 0x71, 0x6D, 0x10, 0xAA, 0x6A, 0xC3, 0x22, + 0x8D, 0x01, 0x19, 0x94, 0xC1, 0x1B, 0x94, 0xC1, 0x00, 0x07, 0x52, 0x19, + 0xC4, 0xC1, 0x86, 0x21, 0x0D, 0xDC, 0x40, 0x0E, 0x36, 0x2C, 0xC4, 0x18, + 0x90, 0x41, 0x19, 0x98, 0x01, 0x1C, 0x0C, 0x6B, 0x40, 0x94, 0x41, 0x1B, + 0x6C, 0x58, 0x86, 0x31, 0x20, 0x83, 0x32, 0x50, 0x03, 0x38, 0x18, 0xE0, + 0x60, 0x28, 0x83, 0x38, 0xE0, 0x32, 0x65, 0xF5, 0x05, 0xF5, 0x36, 0x97, + 0x46, 0x97, 0xF6, 0xE6, 0xB6, 0x61, 0x91, 0xEC, 0x80, 0x0C, 0xCE, 0xC0, + 0x0C, 0xD6, 0x60, 0x58, 0x03, 0xA9, 0x0C, 0xDA, 0x60, 0xC3, 0x40, 0x07, + 0x75, 0x70, 0x07, 0x1B, 0x86, 0x39, 0xC0, 0x03, 0x60, 0x43, 0xB1, 0x89, + 0x41, 0x1E, 0x3C, 0x00, 0x0D, 0x33, 0xB6, 0xB7, 0x30, 0xBA, 0xB9, 0x09, + 0x02, 0x01, 0xB1, 0x48, 0x73, 0x9B, 0xA3, 0x9B, 0x9B, 0x20, 0x10, 0x11, + 0x8D, 0xB9, 0xB4, 0xB3, 0x2F, 0x36, 0x32, 0x1A, 0x73, 0x69, 0x67, 0x5F, + 0x73, 0x74, 0x13, 0x04, 0x42, 0xDA, 0x80, 0xEC, 0x01, 0x1F, 0xF4, 0x81, + 0x1F, 0xFC, 0xC1, 0x05, 0x0A, 0xA1, 0x50, 0x85, 0x8D, 0xCD, 0xAE, 0xCD, + 0x25, 0x8D, 0xAC, 0xCC, 0x8D, 0x6E, 0x4A, 0x10, 0x54, 0x21, 0xC3, 0x73, + 0xB1, 0x2B, 0x93, 0x9B, 0x4B, 0x7B, 0x73, 0x9B, 0x12, 0x10, 0x4D, 0xC8, + 0xF0, 0x5C, 0xEC, 0xC2, 0xD8, 0xEC, 0xCA, 0xE4, 0xA6, 0x04, 0x45, 0x1D, + 0x32, 0x3C, 0x97, 0x39, 0xB4, 0x30, 0xB2, 0x32, 0xB9, 0xA6, 0x37, 0xB2, + 0x32, 0xB6, 0x29, 0x01, 0x52, 0x86, 0x0C, 0xCF, 0x45, 0xAE, 0x6C, 0xEE, + 0xAD, 0x4E, 0x6E, 0xAC, 0x6C, 0x6E, 0x4A, 0xE0, 0x54, 0x22, 0xC3, 0x73, + 0xA1, 0xCB, 0x83, 0x2B, 0x0B, 0x72, 0x73, 0x7B, 0xA3, 0x0B, 0xA3, 0x4B, + 0x7B, 0x73, 0x9B, 0x9B, 0x22, 0x64, 0x5E, 0x1D, 0x32, 0x3C, 0x17, 0xBB, + 0xB4, 0xB2, 0xBB, 0x24, 0xB2, 0x29, 0xBA, 0x30, 0xBA, 0xB2, 0x29, 0x01, + 0x18, 0xD4, 0x21, 0xC3, 0x73, 0x29, 0x73, 0xA3, 0x93, 0xCB, 0x83, 0x7A, + 0x4B, 0x73, 0xA3, 0x9B, 0x9B, 0x12, 0xE4, 0x41, 0x17, 0x32, 0x3C, 0x97, + 0xB1, 0xB7, 0x3A, 0x37, 0xBA, 0x32, 0xB9, 0xB9, 0x29, 0x41, 0x28, 0x00, + 0x79, 0x18, 0x00, 0x00, 0x4C, 0x00, 0x00, 0x00, 0x33, 0x08, 0x80, 0x1C, + 0xC4, 0xE1, 0x1C, 0x66, 0x14, 0x01, 0x3D, 0x88, 0x43, 0x38, 0x84, 0xC3, + 0x8C, 0x42, 0x80, 0x07, 0x79, 0x78, 0x07, 0x73, 0x98, 0x71, 0x0C, 0xE6, + 0x00, 0x0F, 0xED, 0x10, 0x0E, 0xF4, 0x80, 0x0E, 0x33, 0x0C, 0x42, 0x1E, + 0xC2, 0xC1, 0x1D, 0xCE, 0xA1, 0x1C, 0x66, 0x30, 0x05, 0x3D, 0x88, 0x43, + 0x38, 0x84, 0x83, 0x1B, 0xCC, 0x03, 0x3D, 0xC8, 0x43, 0x3D, 0x8C, 0x03, + 0x3D, 0xCC, 0x78, 0x8C, 0x74, 0x70, 0x07, 0x7B, 0x08, 0x07, 0x79, 0x48, + 0x87, 0x70, 0x70, 0x07, 0x7A, 0x70, 0x03, 0x76, 0x78, 0x87, 0x70, 0x20, + 0x87, 0x19, 0xCC, 0x11, 0x0E, 0xEC, 0x90, 0x0E, 0xE1, 0x30, 0x0F, 0x6E, + 0x30, 0x0F, 0xE3, 0xF0, 0x0E, 0xF0, 0x50, 0x0E, 0x33, 0x10, 0xC4, 0x1D, + 0xDE, 0x21, 0x1C, 0xD8, 0x21, 0x1D, 0xC2, 0x61, 0x1E, 0x66, 0x30, 0x89, + 0x3B, 0xBC, 0x83, 0x3B, 0xD0, 0x43, 0x39, 0xB4, 0x03, 0x3C, 0xBC, 0x83, + 0x3C, 0x84, 0x03, 0x3B, 0xCC, 0xF0, 0x14, 0x76, 0x60, 0x07, 0x7B, 0x68, + 0x07, 0x37, 0x68, 0x87, 0x72, 0x68, 0x07, 0x37, 0x80, 0x87, 0x70, 0x90, + 0x87, 0x70, 0x60, 0x07, 0x76, 0x28, 0x07, 0x76, 0xF8, 0x05, 0x76, 0x78, + 0x87, 0x77, 0x80, 0x87, 0x5F, 0x08, 0x87, 0x71, 0x18, 0x87, 0x72, 0x98, + 0x87, 0x79, 0x98, 0x81, 0x2C, 0xEE, 0xF0, 0x0E, 0xEE, 0xE0, 0x0E, 0xF5, + 0xC0, 0x0E, 0xEC, 0x30, 0x03, 0x62, 0xC8, 0xA1, 0x1C, 0xE4, 0xA1, 0x1C, + 0xCC, 0xA1, 0x1C, 0xE4, 0xA1, 0x1C, 0xDC, 0x61, 0x1C, 0xCA, 0x21, 0x1C, + 0xC4, 0x81, 0x1D, 0xCA, 0x61, 0x06, 0xD6, 0x90, 0x43, 0x39, 0xC8, 0x43, + 0x39, 0x98, 0x43, 0x39, 0xC8, 0x43, 0x39, 0xB8, 0xC3, 0x38, 0x94, 0x43, + 0x38, 0x88, 0x03, 0x3B, 0x94, 0xC3, 0x2F, 0xBC, 0x83, 0x3C, 0xFC, 0x82, + 0x3B, 0xD4, 0x03, 0x3B, 0xB0, 0xC3, 0x8C, 0xC8, 0x21, 0x07, 0x7C, 0x70, + 0x03, 0x72, 0x10, 0x87, 0x73, 0x70, 0x03, 0x7B, 0x08, 0x07, 0x79, 0x60, + 0x87, 0x70, 0xC8, 0x87, 0x77, 0xA8, 0x07, 0x7A, 0x98, 0x81, 0x3C, 0xE4, + 0x80, 0x0F, 0x6E, 0x40, 0x0F, 0xE5, 0xD0, 0x0E, 0xF0, 0x00, 0x00, 0x00, + 0x71, 0x20, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x36, 0xB0, 0x0D, 0x97, + 0xEF, 0x3C, 0xBE, 0x10, 0x50, 0x45, 0x41, 0x44, 0xA5, 0x03, 0x0C, 0x25, + 0x61, 0x00, 0x02, 0xE6, 0x17, 0xB7, 0x6D, 0x05, 0xD2, 0x70, 0xF9, 0xCE, + 0xE3, 0x0B, 0x11, 0x01, 0x4C, 0x44, 0x08, 0x34, 0xC3, 0x42, 0x58, 0xC0, + 0x34, 0x5C, 0xBE, 0xF3, 0xF8, 0x8B, 0x03, 0x0C, 0x62, 0xF3, 0x50, 0x93, + 0x5F, 0xDC, 0xB6, 0x09, 0x54, 0xC3, 0xE5, 0x3B, 0x8F, 0x2F, 0x4D, 0x4E, + 0x44, 0xA0, 0xD4, 0xF4, 0x50, 0x93, 0x5F, 0xDC, 0xB6, 0x11, 0x48, 0xC3, + 0xE5, 0x3B, 0x8F, 0x3F, 0x11, 0xD1, 0x84, 0x00, 0x11, 0xE6, 0x17, 0xB7, + 0x6D, 0x00, 0x04, 0x03, 0x20, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x41, 0x53, 0x48, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x79, 0x4C, 0xB5, 0xAB, 0x66, 0x48, 0xF5, 0x69, 0xB6, 0x29, 0x46, 0xAF, + 0x9B, 0xCF, 0x2D, 0x58, 0x44, 0x58, 0x49, 0x4C, 0xD4, 0x07, 0x00, 0x00, + 0x60, 0x00, 0x01, 0x00, 0xF5, 0x01, 0x00, 0x00, 0x44, 0x58, 0x49, 0x4C, + 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xBC, 0x07, 0x00, 0x00, + 0x42, 0x43, 0xC0, 0xDE, 0x21, 0x0C, 0x00, 0x00, 0xEC, 0x01, 0x00, 0x00, + 0x0B, 0x82, 0x20, 0x00, 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x07, 0x81, 0x23, 0x91, 0x41, 0xC8, 0x04, 0x49, 0x06, 0x10, 0x32, 0x39, + 0x92, 0x01, 0x84, 0x0C, 0x25, 0x05, 0x08, 0x19, 0x1E, 0x04, 0x8B, 0x62, + 0x80, 0x14, 0x45, 0x02, 0x42, 0x92, 0x0B, 0x42, 0xA4, 0x10, 0x32, 0x14, + 0x38, 0x08, 0x18, 0x4B, 0x0A, 0x32, 0x52, 0x88, 0x48, 0x90, 0x14, 0x20, + 0x43, 0x46, 0x88, 0xA5, 0x00, 0x19, 0x32, 0x42, 0xE4, 0x48, 0x0E, 0x90, + 0x91, 0x22, 0xC4, 0x50, 0x41, 0x51, 0x81, 0x8C, 0xE1, 0x83, 0xE5, 0x8A, + 0x04, 0x29, 0x46, 0x06, 0x51, 0x18, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x1B, 0x8C, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x40, 0x02, 0xA8, 0x0D, + 0x84, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x20, 0x6D, 0x30, 0x86, 0xFF, + 0xFF, 0xFF, 0xFF, 0x1F, 0x00, 0x09, 0xA8, 0x00, 0x49, 0x18, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x13, 0x82, 0x60, 0x42, 0x20, 0x4C, 0x08, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x89, 0x20, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, + 0x32, 0x22, 0x48, 0x09, 0x20, 0x64, 0x85, 0x04, 0x93, 0x22, 0xA4, 0x84, + 0x04, 0x93, 0x22, 0xE3, 0x84, 0xA1, 0x90, 0x14, 0x12, 0x4C, 0x8A, 0x8C, + 0x0B, 0x84, 0xA4, 0x4C, 0x10, 0x68, 0x23, 0x00, 0x25, 0x00, 0x14, 0x66, + 0x00, 0xE6, 0x08, 0xC0, 0x60, 0x8E, 0x00, 0x29, 0xC6, 0x20, 0x84, 0x14, + 0x42, 0xA6, 0x18, 0x80, 0x10, 0x52, 0x06, 0xA1, 0xA3, 0x86, 0xCB, 0x9F, + 0xB0, 0x87, 0x90, 0x7C, 0x6E, 0xA3, 0x8A, 0x95, 0x98, 0xFC, 0xE2, 0xB6, + 0x11, 0x31, 0xC6, 0x18, 0x54, 0xEE, 0x19, 0x2E, 0x7F, 0xC2, 0x1E, 0x42, + 0xF2, 0x43, 0xA0, 0x19, 0x16, 0x02, 0x05, 0xAB, 0x10, 0x8A, 0x30, 0x42, + 0xAD, 0x14, 0x83, 0x8C, 0x31, 0xE8, 0xCD, 0x11, 0x04, 0xC5, 0x60, 0xA4, + 0x10, 0x12, 0x49, 0x0E, 0x04, 0x0C, 0x23, 0x10, 0x43, 0x12, 0xD4, 0xBB, + 0x0E, 0x47, 0x9A, 0x16, 0x00, 0x73, 0xA8, 0xC9, 0x77, 0x1B, 0xA4, 0x70, + 0x22, 0x66, 0x5B, 0x1C, 0x41, 0x41, 0x81, 0xA5, 0x9B, 0x0C, 0x04, 0x00, + 0x13, 0x14, 0x72, 0xC0, 0x87, 0x74, 0x60, 0x87, 0x36, 0x68, 0x87, 0x79, + 0x68, 0x03, 0x72, 0xC0, 0x87, 0x0D, 0xAF, 0x50, 0x0E, 0x6D, 0xD0, 0x0E, + 0x7A, 0x50, 0x0E, 0x6D, 0x00, 0x0F, 0x7A, 0x30, 0x07, 0x72, 0xA0, 0x07, + 0x73, 0x20, 0x07, 0x6D, 0x90, 0x0E, 0x71, 0xA0, 0x07, 0x73, 0x20, 0x07, + 0x6D, 0x90, 0x0E, 0x78, 0xA0, 0x07, 0x73, 0x20, 0x07, 0x6D, 0x90, 0x0E, + 0x71, 0x60, 0x07, 0x7A, 0x30, 0x07, 0x72, 0xD0, 0x06, 0xE9, 0x30, 0x07, + 0x72, 0xA0, 0x07, 0x73, 0x20, 0x07, 0x6D, 0x90, 0x0E, 0x76, 0x40, 0x07, + 0x7A, 0x60, 0x07, 0x74, 0xD0, 0x06, 0xE6, 0x10, 0x07, 0x76, 0xA0, 0x07, + 0x73, 0x20, 0x07, 0x6D, 0x60, 0x0E, 0x73, 0x20, 0x07, 0x7A, 0x30, 0x07, + 0x72, 0xD0, 0x06, 0xE6, 0x60, 0x07, 0x74, 0xA0, 0x07, 0x76, 0x40, 0x07, + 0x6D, 0xE0, 0x0E, 0x78, 0xA0, 0x07, 0x71, 0x60, 0x07, 0x7A, 0x30, 0x07, + 0x72, 0xA0, 0x07, 0x76, 0x40, 0x07, 0x43, 0x9E, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86, 0x3C, 0x06, 0x10, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x79, 0x10, 0x20, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xF2, 0x34, + 0x40, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xE4, + 0x79, 0x80, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, + 0xC8, 0x23, 0x01, 0x01, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x16, 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x32, 0x1E, 0x98, 0x14, + 0x19, 0x11, 0x4C, 0x90, 0x8C, 0x09, 0x26, 0x47, 0xC6, 0x04, 0x43, 0x22, + 0x25, 0x30, 0x02, 0x50, 0x10, 0xC5, 0x50, 0x80, 0x01, 0x65, 0x50, 0x0E, + 0xE5, 0x51, 0x04, 0x54, 0x4A, 0x62, 0x04, 0xA0, 0x0C, 0x0A, 0xA1, 0x08, + 0x08, 0xCF, 0x00, 0x50, 0x1E, 0x8B, 0x51, 0x18, 0x10, 0x1F, 0x40, 0x7C, + 0x00, 0xF1, 0x01, 0x08, 0x04, 0x02, 0x81, 0xC0, 0x00, 0x00, 0x00, 0x00, + 0x79, 0x18, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x1A, 0x03, 0x4C, 0x90, + 0x46, 0x02, 0x13, 0xC4, 0x31, 0x20, 0xC3, 0x1B, 0x43, 0x81, 0x93, 0x4B, + 0xB3, 0x0B, 0xA3, 0x2B, 0x4B, 0x01, 0x89, 0x71, 0xC1, 0x71, 0x81, 0x71, + 0xA1, 0xB9, 0xC9, 0x81, 0x01, 0x41, 0x21, 0xB3, 0x89, 0x11, 0xBB, 0x99, + 0x31, 0xAB, 0xB1, 0x49, 0xD9, 0x10, 0x04, 0x13, 0x04, 0xC2, 0x98, 0x20, + 0x10, 0xC7, 0x06, 0x61, 0x20, 0x26, 0x08, 0x04, 0xB2, 0x41, 0x18, 0x0C, + 0x0A, 0x76, 0x73, 0x13, 0x04, 0x22, 0xD9, 0x30, 0x20, 0x09, 0x31, 0x41, + 0xC0, 0x28, 0x02, 0x13, 0x04, 0x42, 0xD9, 0x80, 0x10, 0x0B, 0x33, 0x10, + 0x43, 0x03, 0x6C, 0x08, 0x9C, 0x0D, 0x04, 0x00, 0x3C, 0xC0, 0x04, 0x21, + 0xAB, 0x36, 0x04, 0xD1, 0x04, 0x41, 0x00, 0x48, 0xB4, 0x85, 0xA5, 0xB9, + 0x11, 0xA1, 0x2A, 0xC2, 0x1A, 0x7A, 0x7A, 0x92, 0x22, 0x9A, 0x20, 0x14, + 0xCF, 0x04, 0xA1, 0x80, 0x36, 0x04, 0xC4, 0x04, 0xA1, 0x88, 0x26, 0x08, + 0xC4, 0x32, 0x41, 0x20, 0x98, 0x0D, 0x82, 0xB6, 0x6D, 0x58, 0x88, 0xCA, + 0xBA, 0xB0, 0x6B, 0xC8, 0x88, 0x8B, 0xDB, 0x10, 0x0C, 0x13, 0x84, 0x42, + 0x9A, 0x20, 0x10, 0xCD, 0x06, 0x41, 0x03, 0x83, 0x0D, 0xCB, 0x50, 0x59, + 0x97, 0x77, 0x0D, 0xDF, 0x70, 0x85, 0xC1, 0x04, 0x81, 0x70, 0x36, 0x04, + 0x63, 0x30, 0x41, 0x28, 0xA6, 0x0D, 0x82, 0xA6, 0x6D, 0x58, 0xC6, 0xA0, + 0xB2, 0x2E, 0x32, 0xB8, 0x86, 0x32, 0x18, 0x83, 0xCB, 0x0C, 0x36, 0x0C, + 0x9D, 0x18, 0x9C, 0xC1, 0x86, 0x85, 0xA8, 0xAC, 0x0B, 0x2B, 0x83, 0xE1, + 0x23, 0xAE, 0x30, 0xD8, 0xB0, 0x0C, 0x95, 0x75, 0x79, 0x65, 0x30, 0x94, + 0xC1, 0x70, 0x99, 0x01, 0x97, 0x29, 0xAB, 0x2F, 0xA8, 0xB7, 0xB9, 0x34, + 0xBA, 0xB4, 0x37, 0xB7, 0x0D, 0xCB, 0x18, 0xAC, 0x81, 0x95, 0x61, 0xDF, + 0xF0, 0x8D, 0xC1, 0x15, 0x06, 0x1B, 0x86, 0x34, 0x50, 0x03, 0x36, 0xD8, + 0x30, 0xA0, 0x41, 0x1B, 0x00, 0x1B, 0x8A, 0x89, 0x72, 0x03, 0x08, 0xA8, + 0xC2, 0xC6, 0x66, 0xD7, 0xE6, 0x92, 0x46, 0x56, 0xE6, 0x46, 0x37, 0x25, + 0x08, 0xAA, 0x90, 0xE1, 0xB9, 0xD8, 0x95, 0xC9, 0xCD, 0xA5, 0xBD, 0xB9, + 0x4D, 0x09, 0x88, 0x26, 0x64, 0x78, 0x2E, 0x76, 0x61, 0x6C, 0x76, 0x65, + 0x72, 0x53, 0x02, 0xA3, 0x0E, 0x19, 0x9E, 0xCB, 0x1C, 0x5A, 0x18, 0x59, + 0x99, 0x5C, 0xD3, 0x1B, 0x59, 0x19, 0xDB, 0x94, 0x20, 0x29, 0x43, 0x86, + 0xE7, 0x22, 0x57, 0x36, 0xF7, 0x56, 0x27, 0x37, 0x56, 0x36, 0x37, 0x25, + 0x78, 0xEA, 0x90, 0xE1, 0xB9, 0xD8, 0xA5, 0x95, 0xDD, 0x25, 0x91, 0x4D, + 0xD1, 0x85, 0xD1, 0x95, 0x4D, 0x09, 0xA2, 0x3A, 0x64, 0x78, 0x2E, 0x65, + 0x6E, 0x74, 0x72, 0x79, 0x50, 0x6F, 0x69, 0x6E, 0x74, 0x73, 0x53, 0x02, + 0x37, 0x00, 0x00, 0x00, 0x79, 0x18, 0x00, 0x00, 0x4C, 0x00, 0x00, 0x00, + 0x33, 0x08, 0x80, 0x1C, 0xC4, 0xE1, 0x1C, 0x66, 0x14, 0x01, 0x3D, 0x88, + 0x43, 0x38, 0x84, 0xC3, 0x8C, 0x42, 0x80, 0x07, 0x79, 0x78, 0x07, 0x73, + 0x98, 0x71, 0x0C, 0xE6, 0x00, 0x0F, 0xED, 0x10, 0x0E, 0xF4, 0x80, 0x0E, + 0x33, 0x0C, 0x42, 0x1E, 0xC2, 0xC1, 0x1D, 0xCE, 0xA1, 0x1C, 0x66, 0x30, + 0x05, 0x3D, 0x88, 0x43, 0x38, 0x84, 0x83, 0x1B, 0xCC, 0x03, 0x3D, 0xC8, + 0x43, 0x3D, 0x8C, 0x03, 0x3D, 0xCC, 0x78, 0x8C, 0x74, 0x70, 0x07, 0x7B, + 0x08, 0x07, 0x79, 0x48, 0x87, 0x70, 0x70, 0x07, 0x7A, 0x70, 0x03, 0x76, + 0x78, 0x87, 0x70, 0x20, 0x87, 0x19, 0xCC, 0x11, 0x0E, 0xEC, 0x90, 0x0E, + 0xE1, 0x30, 0x0F, 0x6E, 0x30, 0x0F, 0xE3, 0xF0, 0x0E, 0xF0, 0x50, 0x0E, + 0x33, 0x10, 0xC4, 0x1D, 0xDE, 0x21, 0x1C, 0xD8, 0x21, 0x1D, 0xC2, 0x61, + 0x1E, 0x66, 0x30, 0x89, 0x3B, 0xBC, 0x83, 0x3B, 0xD0, 0x43, 0x39, 0xB4, + 0x03, 0x3C, 0xBC, 0x83, 0x3C, 0x84, 0x03, 0x3B, 0xCC, 0xF0, 0x14, 0x76, + 0x60, 0x07, 0x7B, 0x68, 0x07, 0x37, 0x68, 0x87, 0x72, 0x68, 0x07, 0x37, + 0x80, 0x87, 0x70, 0x90, 0x87, 0x70, 0x60, 0x07, 0x76, 0x28, 0x07, 0x76, + 0xF8, 0x05, 0x76, 0x78, 0x87, 0x77, 0x80, 0x87, 0x5F, 0x08, 0x87, 0x71, + 0x18, 0x87, 0x72, 0x98, 0x87, 0x79, 0x98, 0x81, 0x2C, 0xEE, 0xF0, 0x0E, + 0xEE, 0xE0, 0x0E, 0xF5, 0xC0, 0x0E, 0xEC, 0x30, 0x03, 0x62, 0xC8, 0xA1, + 0x1C, 0xE4, 0xA1, 0x1C, 0xCC, 0xA1, 0x1C, 0xE4, 0xA1, 0x1C, 0xDC, 0x61, + 0x1C, 0xCA, 0x21, 0x1C, 0xC4, 0x81, 0x1D, 0xCA, 0x61, 0x06, 0xD6, 0x90, + 0x43, 0x39, 0xC8, 0x43, 0x39, 0x98, 0x43, 0x39, 0xC8, 0x43, 0x39, 0xB8, + 0xC3, 0x38, 0x94, 0x43, 0x38, 0x88, 0x03, 0x3B, 0x94, 0xC3, 0x2F, 0xBC, + 0x83, 0x3C, 0xFC, 0x82, 0x3B, 0xD4, 0x03, 0x3B, 0xB0, 0xC3, 0x8C, 0xC8, + 0x21, 0x07, 0x7C, 0x70, 0x03, 0x72, 0x10, 0x87, 0x73, 0x70, 0x03, 0x7B, + 0x08, 0x07, 0x79, 0x60, 0x87, 0x70, 0xC8, 0x87, 0x77, 0xA8, 0x07, 0x7A, + 0x98, 0x81, 0x3C, 0xE4, 0x80, 0x0F, 0x6E, 0x40, 0x0F, 0xE5, 0xD0, 0x0E, + 0xF0, 0x00, 0x00, 0x00, 0x71, 0x20, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x36, 0xB0, 0x0D, 0x97, 0xEF, 0x3C, 0xBE, 0x10, 0x50, 0x45, 0x41, 0x44, + 0xA5, 0x03, 0x0C, 0x25, 0x61, 0x00, 0x02, 0xE6, 0x17, 0xB7, 0x6D, 0x05, + 0xD2, 0x70, 0xF9, 0xCE, 0xE3, 0x0B, 0x11, 0x01, 0x4C, 0x44, 0x08, 0x34, + 0xC3, 0x42, 0x58, 0xC0, 0x34, 0x5C, 0xBE, 0xF3, 0xF8, 0x8B, 0x03, 0x0C, + 0x62, 0xF3, 0x50, 0x93, 0x5F, 0xDC, 0xB6, 0x09, 0x54, 0xC3, 0xE5, 0x3B, + 0x8F, 0x2F, 0x4D, 0x4E, 0x44, 0xA0, 0xD4, 0xF4, 0x50, 0x93, 0x5F, 0xDC, + 0xB6, 0x11, 0x48, 0xC3, 0xE5, 0x3B, 0x8F, 0x3F, 0x11, 0xD1, 0x84, 0x00, + 0x11, 0xE6, 0x17, 0xB7, 0x6D, 0x00, 0x04, 0x03, 0x20, 0x0D, 0x00, 0x00, + 0x61, 0x20, 0x00, 0x00, 0x8A, 0x00, 0x00, 0x00, 0x13, 0x04, 0x41, 0x2C, + 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x44, 0x4A, 0xA1, 0x10, + 0x66, 0x00, 0x8A, 0xAB, 0xEC, 0x4A, 0x8E, 0x4A, 0x09, 0x50, 0x1C, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x20, 0x65, + 0xC3, 0x72, 0x5D, 0xC1, 0x88, 0x41, 0x02, 0x80, 0x20, 0x18, 0x18, 0x1E, + 0x12, 0x61, 0xCF, 0x31, 0x62, 0x90, 0x00, 0x20, 0x08, 0x06, 0xC6, 0x97, + 0x48, 0x19, 0x81, 0x8C, 0x18, 0x24, 0x00, 0x08, 0x82, 0x81, 0x01, 0x06, + 0xCA, 0xA6, 0x45, 0xC9, 0x88, 0x41, 0x02, 0x80, 0x20, 0x18, 0x18, 0x61, + 0xB0, 0x70, 0x9B, 0xA1, 0x8C, 0x18, 0x24, 0x00, 0x08, 0x82, 0x81, 0x21, + 0x06, 0x4C, 0xC7, 0x41, 0xCB, 0x88, 0x41, 0x02, 0x80, 0x20, 0x18, 0x18, + 0x63, 0xD0, 0x78, 0xDD, 0xC4, 0x8C, 0x18, 0x24, 0x00, 0x08, 0x82, 0x81, + 0x41, 0x06, 0x8E, 0xE7, 0x55, 0xCD, 0x88, 0x41, 0x02, 0x80, 0x20, 0x18, + 0x18, 0x65, 0xF0, 0x7C, 0x9F, 0xE2, 0x8C, 0x18, 0x24, 0x00, 0x08, 0x82, + 0x81, 0x61, 0x06, 0x10, 0x18, 0x80, 0x01, 0xF5, 0x8C, 0x18, 0x1C, 0x00, + 0x08, 0x82, 0x41, 0x53, 0x06, 0x8E, 0x12, 0x06, 0xA3, 0x09, 0x01, 0x30, + 0x9A, 0x20, 0x04, 0xA3, 0x09, 0x83, 0x30, 0x9A, 0x40, 0x0C, 0x23, 0x06, + 0x07, 0x00, 0x82, 0x60, 0xD0, 0xA8, 0xC1, 0xF4, 0x9C, 0xC1, 0x68, 0x42, + 0x00, 0x8C, 0x26, 0x08, 0xC1, 0x68, 0xC2, 0x20, 0x8C, 0x26, 0x10, 0xC3, + 0x88, 0xC1, 0x01, 0x80, 0x20, 0x18, 0x34, 0x6F, 0x80, 0x51, 0x64, 0x30, + 0x9A, 0x10, 0x00, 0xA3, 0x09, 0x42, 0x30, 0x9A, 0x30, 0x08, 0xA3, 0x09, + 0xC4, 0x30, 0x62, 0x70, 0x00, 0x20, 0x08, 0x06, 0x0D, 0x1D, 0x74, 0x19, + 0x1B, 0x8C, 0x26, 0x04, 0xC0, 0x68, 0x82, 0x10, 0x8C, 0x26, 0x0C, 0xC2, + 0x68, 0x02, 0x31, 0xD8, 0x74, 0xC9, 0x67, 0xC4, 0x00, 0x01, 0x40, 0x10, + 0x0C, 0x9E, 0x3C, 0x20, 0x83, 0xEB, 0x09, 0x46, 0x0C, 0x10, 0x00, 0x04, + 0xC1, 0xE0, 0xD1, 0x83, 0x32, 0xB8, 0x96, 0xC0, 0x82, 0x03, 0x3A, 0x66, + 0x6D, 0xF2, 0x19, 0x31, 0x40, 0x00, 0x10, 0x04, 0x83, 0xA7, 0x0F, 0xD0, + 0x60, 0x93, 0x82, 0x11, 0x03, 0x04, 0x00, 0x41, 0x30, 0x78, 0xFC, 0x20, + 0x0D, 0x36, 0x27, 0xB0, 0x40, 0x81, 0x8E, 0x65, 0x9F, 0x7C, 0x46, 0x0C, + 0x10, 0x00, 0x04, 0xC1, 0xE0, 0x09, 0x05, 0x36, 0xF8, 0xAA, 0x60, 0xC4, + 0x00, 0x01, 0x40, 0x10, 0x0C, 0x1E, 0x51, 0x68, 0x83, 0x2F, 0x0A, 0x2C, + 0x68, 0xA0, 0x63, 0xDC, 0x18, 0xC8, 0x67, 0xC4, 0x00, 0x01, 0x40, 0x10, + 0x0C, 0x9E, 0x52, 0x80, 0x83, 0x31, 0xC0, 0x82, 0x11, 0x03, 0x04, 0x00, + 0x41, 0x30, 0x78, 0x4C, 0x21, 0x0E, 0xC6, 0x80, 0x0A, 0x2C, 0x80, 0xA0, + 0x33, 0x62, 0x90, 0x00, 0x20, 0x08, 0x06, 0x88, 0x2A, 0xD8, 0x41, 0x29, + 0x94, 0x02, 0x1F, 0xAC, 0xC1, 0x88, 0x41, 0x02, 0x80, 0x20, 0x18, 0x20, + 0xAA, 0x60, 0x07, 0xA5, 0x50, 0x0A, 0x70, 0xA0, 0x06, 0x23, 0x06, 0x09, + 0x00, 0x82, 0x60, 0x80, 0xA8, 0x82, 0x1D, 0x94, 0x42, 0x29, 0xE4, 0x41, + 0x1A, 0x8C, 0x18, 0x24, 0x00, 0x08, 0x82, 0x01, 0xA2, 0x0A, 0x76, 0x50, + 0x0A, 0xA5, 0xB0, 0x07, 0x68, 0x30, 0x62, 0x90, 0x00, 0x20, 0x08, 0x06, + 0x88, 0x2A, 0xD8, 0x81, 0x29, 0x94, 0x02, 0x1F, 0xB4, 0xC1, 0x88, 0x41, + 0x02, 0x80, 0x20, 0x18, 0x20, 0xAA, 0x60, 0x07, 0xA6, 0x50, 0x0A, 0x70, + 0xC0, 0x06, 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x80, 0xA8, 0x82, 0x1D, + 0xF8, 0x41, 0x29, 0xF0, 0x41, 0x33, 0x62, 0x90, 0x00, 0x20, 0x08, 0x06, + 0x88, 0x2A, 0xD8, 0x81, 0x1F, 0x94, 0x02, 0x1C, 0x24, 0x23, 0x06, 0x09, + 0x00, 0x82, 0x60, 0x80, 0xA8, 0x82, 0x1D, 0xF8, 0x41, 0x29, 0xE4, 0x41, + 0x31, 0x62, 0x90, 0x00, 0x20, 0x08, 0x06, 0x88, 0x2A, 0xD8, 0x81, 0x1F, + 0x94, 0xC2, 0x1E, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; diff --git a/engine/public/drivers/sdl_gpu/shaders/default_metal.h b/engine/public/drivers/sdl_gpu/shaders/default_metal.h new file mode 100644 index 000000000..1f37da66a --- /dev/null +++ b/engine/public/drivers/sdl_gpu/shaders/default_metal.h @@ -0,0 +1,68 @@ +#pragma once + + + +inline const char* default_vertex_metal = R"( +#include +#include + +using namespace metal; + +struct UniformBlock +{ +float4x4 VIEW_PROJECTION_MATRIX; +}; + +struct main0_out +{ +float4 vColor [[user(locn0)]]; +float2 vTexCoord [[user(locn1)]]; +float4 gl_Position [[position]]; +}; + +struct main0_in +{ +float3 a_vertex [[attribute(0)]]; +float4 a_color [[attribute(1)]]; +float2 a_uv [[attribute(2)]]; +}; + +vertex main0_out main0(main0_in in [[stage_in]], constant UniformBlock& _30 [[buffer(0)]]) +{ +main0_out out = {}; +out.vColor = in.a_color; +out.vTexCoord = in.a_uv; +out.gl_Position = _30.VIEW_PROJECTION_MATRIX * float4(in.a_vertex, 1.0); +return out; +})"; + +inline const char* default_fragment_metal = R"( +#include +#include + +using namespace metal; + +struct main0_out +{ +float4 FragColor [[color(0)]]; +}; + +struct main0_in +{ +float4 vColor [[user(locn0)]]; +float2 vTexCoord [[user(locn1)]]; +}; + +fragment main0_out main0(main0_in in [[stage_in]], texture2d u_texture [[texture(0)]], sampler u_textureSmplr [[sampler(0)]]) +{ +main0_out out = {}; +float4 tex = u_texture.sample(u_textureSmplr, in.vTexCoord); +if (tex.w < 0.100000001490116119384765625) +{ + discard_fragment(); +} +out.FragColor = tex * in.vColor; +return out; +})"; + + diff --git a/engine/public/drivers/sdl_gpu/shaders/default_vulkan.h b/engine/public/drivers/sdl_gpu/shaders/default_vulkan.h new file mode 100644 index 000000000..f81a0677a --- /dev/null +++ b/engine/public/drivers/sdl_gpu/shaders/default_vulkan.h @@ -0,0 +1,219 @@ +#pragma once + + +inline unsigned char default_vertex_vulkan[1536] = { + 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0B, 0x00, 0x0D, 0x00, + 0x2D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30, + 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E, 0x00, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0xC2, 0x01, 0x00, 0x00, + 0x04, 0x00, 0x0A, 0x00, 0x47, 0x4C, 0x5F, 0x47, 0x4F, 0x4F, 0x47, 0x4C, + 0x45, 0x5F, 0x63, 0x70, 0x70, 0x5F, 0x73, 0x74, 0x79, 0x6C, 0x65, 0x5F, + 0x6C, 0x69, 0x6E, 0x65, 0x5F, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, + 0x76, 0x65, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00, 0x47, 0x4C, 0x5F, 0x47, + 0x4F, 0x4F, 0x47, 0x4C, 0x45, 0x5F, 0x69, 0x6E, 0x63, 0x6C, 0x75, 0x64, + 0x65, 0x5F, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x00, + 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E, + 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x76, 0x43, 0x6F, 0x6C, 0x6F, 0x72, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, + 0x0B, 0x00, 0x00, 0x00, 0x61, 0x5F, 0x63, 0x6F, 0x6C, 0x6F, 0x72, 0x00, + 0x05, 0x00, 0x05, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x76, 0x54, 0x65, 0x78, + 0x43, 0x6F, 0x6F, 0x72, 0x64, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x61, 0x5F, 0x75, 0x76, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x06, 0x00, 0x16, 0x00, 0x00, 0x00, 0x67, 0x6C, 0x5F, 0x50, + 0x65, 0x72, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x06, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x67, 0x6C, 0x5F, 0x50, 0x6F, 0x73, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x00, + 0x06, 0x00, 0x07, 0x00, 0x16, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x67, 0x6C, 0x5F, 0x50, 0x6F, 0x69, 0x6E, 0x74, 0x53, 0x69, 0x7A, 0x65, + 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x07, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x67, 0x6C, 0x5F, 0x43, 0x6C, 0x69, 0x70, 0x44, + 0x69, 0x73, 0x74, 0x61, 0x6E, 0x63, 0x65, 0x00, 0x06, 0x00, 0x07, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x67, 0x6C, 0x5F, 0x43, + 0x75, 0x6C, 0x6C, 0x44, 0x69, 0x73, 0x74, 0x61, 0x6E, 0x63, 0x65, 0x00, + 0x05, 0x00, 0x03, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x06, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x55, 0x6E, 0x69, 0x66, + 0x6F, 0x72, 0x6D, 0x42, 0x6C, 0x6F, 0x63, 0x6B, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x09, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x56, 0x49, 0x45, 0x57, 0x5F, 0x50, 0x52, 0x4F, 0x4A, 0x45, 0x43, 0x54, + 0x49, 0x4F, 0x4E, 0x5F, 0x4D, 0x41, 0x54, 0x52, 0x49, 0x58, 0x00, 0x00, + 0x05, 0x00, 0x03, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x05, 0x00, 0x24, 0x00, 0x00, 0x00, 0x61, 0x5F, 0x76, 0x65, + 0x72, 0x74, 0x65, 0x78, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x47, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0F, 0x00, 0x00, 0x00, + 0x1E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x47, 0x00, 0x03, 0x00, 0x16, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x05, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x05, 0x00, 0x16, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x0B, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, + 0x1C, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x48, 0x00, 0x04, 0x00, + 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x05, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, + 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x1E, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, + 0x1E, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x47, 0x00, 0x04, 0x00, 0x24, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x03, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x0A, 0x00, 0x00, 0x00, + 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, + 0x0D, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x04, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x0D, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x0E, 0x00, 0x00, 0x00, + 0x0F, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, + 0x3B, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x1C, 0x00, 0x04, 0x00, 0x15, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x06, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x04, 0x00, 0x19, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x19, 0x00, 0x00, 0x00, + 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x04, 0x00, + 0x1B, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x1E, 0x00, 0x03, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x04, 0x00, 0x1D, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x1C, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x1D, 0x00, 0x00, 0x00, + 0x1E, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, + 0x1F, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x04, 0x00, 0x22, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, + 0x23, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x2B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x3F, 0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0xF8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, + 0x3E, 0x00, 0x03, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, + 0x3D, 0x00, 0x04, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x0F, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x1F, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, + 0x3D, 0x00, 0x04, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x25, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x51, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, + 0x25, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x50, 0x00, 0x07, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, + 0x91, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x1A, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x2C, 0x00, 0x00, 0x00, + 0x2B, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00 +}; + +inline unsigned char default_fragment_vulkan[988] = { + 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0B, 0x00, 0x0D, 0x00, + 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30, + 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E, 0x00, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0xC2, 0x01, 0x00, 0x00, + 0x04, 0x00, 0x0A, 0x00, 0x47, 0x4C, 0x5F, 0x47, 0x4F, 0x4F, 0x47, 0x4C, + 0x45, 0x5F, 0x63, 0x70, 0x70, 0x5F, 0x73, 0x74, 0x79, 0x6C, 0x65, 0x5F, + 0x6C, 0x69, 0x6E, 0x65, 0x5F, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, + 0x76, 0x65, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00, 0x47, 0x4C, 0x5F, 0x47, + 0x4F, 0x4F, 0x47, 0x4C, 0x45, 0x5F, 0x69, 0x6E, 0x63, 0x6C, 0x75, 0x64, + 0x65, 0x5F, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x00, + 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E, + 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x74, 0x65, 0x78, 0x00, 0x05, 0x00, 0x05, 0x00, 0x0D, 0x00, 0x00, 0x00, + 0x75, 0x5F, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x05, 0x00, 0x11, 0x00, 0x00, 0x00, 0x76, 0x54, 0x65, 0x78, + 0x43, 0x6F, 0x6F, 0x72, 0x64, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6F, 0x6C, 0x6F, + 0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x76, 0x43, 0x6F, 0x6C, 0x6F, 0x72, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, + 0x0D, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x47, 0x00, 0x04, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x1E, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x47, 0x00, 0x04, 0x00, 0x23, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x03, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x19, 0x00, 0x09, 0x00, + 0x0A, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x03, 0x00, + 0x0B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, + 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, + 0x3B, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x0F, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, + 0x3B, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x04, 0x00, 0x16, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x19, 0x00, 0x00, 0x00, 0xCD, 0xCC, 0xCC, 0x3D, 0x14, 0x00, 0x02, 0x00, + 0x1A, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x1F, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, + 0x1F, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x04, 0x00, 0x22, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x23, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x3B, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00, + 0x0E, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, + 0x0F, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x57, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x0E, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0xB8, 0x00, 0x05, 0x00, + 0x1A, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x19, 0x00, 0x00, 0x00, 0xF7, 0x00, 0x03, 0x00, 0x1D, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFA, 0x00, 0x04, 0x00, 0x1B, 0x00, 0x00, 0x00, + 0x1C, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, + 0x1C, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x01, 0x00, 0xF8, 0x00, 0x02, 0x00, + 0x1D, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x85, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, + 0x38, 0x00, 0x01, 0x00 +}; diff --git a/engine/public/servers/rendering/rendering_canvas.h b/engine/public/servers/rendering/rendering_canvas.h new file mode 100644 index 000000000..dd7584af3 --- /dev/null +++ b/engine/public/servers/rendering/rendering_canvas.h @@ -0,0 +1,158 @@ +#pragma once + +#include "drivers/gles3/rendering_device_gles3.h" +#include "drivers/sdl_gpu/rendering_device_sdl_gpu.h" +#include "servers/rendering/shader_material.h" +#include + + +class RenderingCanvas { +public: + explicit RenderingCanvas(RenderingDevice* device); + ~RenderingCanvas(); + + bool initialize(int window_width, int window_height); + void shutdown(); + + void begin(const Color& color = Color::BLACK); + void end(); + + void set_camera(const glm::mat4& view_projection); + void reset_camera(); + + void set_viewport_size(int width, int height); + void set_scale_mode(ScaleMode mode); + + + void draw_rect(float x, float y, float width, float height, const Color& color = Color::WHITE, float rotation = 0.0f); + + void draw_rect_outlined(float x, float y, float width, float height, const Color& color = Color::WHITE, float thickness = 1.0f); + + void draw_circle(float x, float y, float radius, const Color& color = Color::WHITE, int segments = 32); + + void draw_circle_outlined(float x, float y, float radius, const Color& color = Color::WHITE, float thickness = 1.0f, int segments = 32); + + void draw_arc(float x, float y, float radius, float start_angle, float end_angle, const Color& color = Color::WHITE, int segments = 32); + + void draw_line(float x1, float y1, float x2, float y2, const Color& color = Color::WHITE, float thickness = 1.0f); + + void draw_triangle(float x1, float y1, float x2, float y2, float x3, float y3, const Color& color = Color::WHITE); + + void draw_polygon(const Vector& points, const Color& color = Color::WHITE); + + void draw_texture(RID texture, float x, float y, float width = 0, float height = 0, const Color& tint = Color::WHITE, + float rotation = 0.0f); + + /// @deprecated Use draw_texture_ex with a CanvasMaterial (optional) instead + void draw_custom(RID shader, float x, float y, float width, float height, RID texture = INVALID_RID, const Color& color = Color::WHITE); + + void draw_texture_ex(float x, float y, float width, float height, RID texture = INVALID_RID, const Rect& source = {0, 0, 0, 0}, + const Color& color = Color::WHITE, float rotation = 0.0f, bool flip_h = false, bool flip_v = false, + const CanvasMaterial* material = nullptr); + + template + void draw_text(Font font, float x, float y, const Color& color, const char* format_str, Args&&... args); + + void draw_text(Font font, float x, float y, const Color& color, const String& text); + + void draw_text(float x, float y, const Color& color, const String& text); + + Font load_font_from_file(const char* filepath, int size); + + RID load_texture(const char* filepath); + RID load_texture(const char* filepath, const TextureDescription& desc); + Texture load_texture_from_file(const char* filepath); + Texture load_texture_from_file(const char* filepath, const TextureDescription& desc); + void get_texture_size(RID texture, uint32_t& out_width, uint32_t& out_height); + + RID load_texture_from_memory(void* data, int width, int height, int channels = 4); + void unload_texture(RID texture); + + RID load_shader_from_source(const char* vertex_src, const char* fragment_src); + RID load_shader_from_file(const char* filepath); + RID load_shader_from_source(const char* source); + void destroy_shader(RID shader); + + void set_blend_mode(BlendMode mode); + void set_line_width(float width); + + void present(); + + RenderingDevice* get_rendering_device() const; + +private: + struct Vertex { + glm::vec3 position; + glm::vec4 color; + glm::vec2 texcoord; + }; + + struct DrawCommand { + RID texture; + uint32_t index_start; + uint32_t index_count; + bool use_texture; + }; + + + RID shader; + RID pipeline; + RID vertex_buffer; + RID index_buffer; + RID white_texture; + RID default_sampler; + + std::unordered_map texture_samplers; + + Vector vertices; + Vector indices; + Vector draw_commands; + + glm::mat4 projection; + glm::mat4 view; + Vector transform_stack; + BlendMode current_blend_mode; + ScaleMode scale_mode; + float line_width; + Color clear_color; + + int window_width, window_height; /// Render resolution (internal) + int viewport_width, viewport_height; + bool is_drawing; + + HashMap custom_shader_pipelines; /// Shader RID -> pipeline RID + + struct TextStorage { + Vector textures; + HashMap loaded_fonts; + Font default_font = {}; + Font emoji_font = {}; + } text_storage; + + RenderingDevice* rd; + + void flush(); + void add_quad(const glm::mat4& transform, const Color& color, RID texture, bool use_texture); + glm::mat4 get_current_transform() const; + void calculate_viewport(int window_w, int window_h, int& out_x, int& out_y, int& out_w, int& out_h); + void setup_pipeline_for_blend_mode(BlendMode mode); + RID get_or_create_custom_pipeline(RID shader); +}; + + +template +void RenderingCanvas::draw_text(Font font, float x, float y, const Color& color, const char* format_str, Args&&... args) { + if (!font.get_native_handle()) { + spdlog::warn("DrawText called with null font!"); + return; + } + + std::string formatted_text; + if constexpr (sizeof...(args) > 0) { + formatted_text = std::vformat(format_str, std::make_format_args(args...)); + } else { + formatted_text = format_str; + } + + draw_text(font, x, y, color, formatted_text); +} diff --git a/engine/public/servers/rendering/rendering_device.h b/engine/public/servers/rendering/rendering_device.h new file mode 100644 index 000000000..451990d5c --- /dev/null +++ b/engine/public/servers/rendering/rendering_device.h @@ -0,0 +1,386 @@ +#pragma once + +#include "core/gstl/str.h" +#include "core/math/math.h" +#include "shader_preprocessor.h" +#include + +#include + + + +using RID = uint32_t; +constexpr RID INVALID_RID = -1; + +struct Shader { + RID handle = INVALID_RID; + +}; + +struct Font { + + Font() = default; + explicit Font(TTF_Font* ptr, int size = 24) : handle(ptr), size(size) { + } + + TTF_Font* get_native_handle() const { + return handle; + } + + + void get_text_size(const String& text, int* out_w, int* out_h) const { + if (!TTF_GetStringSize(handle, text.c_str(), text.length(), out_w, out_h)) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to get text size: %s", SDL_GetError()); + } + } + + int get_font_size_internal() const { + return TTF_GetFontHeight(handle); + } + + void destroy() { + if (handle) { + TTF_CloseFont(handle); + handle = nullptr; + } + } + +private: + TTF_Font* handle = nullptr; + int size = 16; + +}; + + +struct Color { + float r, g, b, a; + + Color(float r = 1.0f, float g = 1.0f, float b = 1.0f, float a = 1.0f) : r(r), g(g), b(b), a(a) { + } + + glm::vec4 to_vec4() const { + return glm::vec4(r, g, b, a); + } + + static const Color WHITE; + static const Color BLACK; + static const Color RED; + static const Color GREEN; + static const Color BLUE; + static const Color YELLOW; + static const Color CYAN; + static const Color MAGENTA; +}; + + +struct Rect { + float x, y, width, height; + Rect(float x = 0, float y = 0, float w = 0, float h = 0) : x(x), y(y), width(w), height(h) { + } +}; + + +enum class DataFormat { + R8_UNORM, + R8G8_UNORM, + R8G8B8_UNORM, + R8G8B8A8_UNORM, + R16_SFLOAT, + R16G16_SFLOAT, + R16G16B16A16_SFLOAT, + R32_SFLOAT, + R32G32_SFLOAT, + R32G32B32_SFLOAT, + R32G32B32A32_SFLOAT, + R32_UINT, + R32G32_UINT, + R32G32B32_UINT, + R32G32B32A32_UINT, + R32_SINT, + R32G32_SINT, + R32G32B32_SINT, + R32G32B32A32_SINT, + D24_UNORM_S8_UINT, + D32_SFLOAT +}; + +enum class TextureType { TEXTURE_TYPE_2D, TEXTURE_TYPE_2D_ARRAY, TEXTURE_TYPE_3D, TEXTURE_TYPE_CUBEMAP, TEXTURE_TYPE_CUBEMAP_ARRAY }; + +enum class TextureFilter { NEAREST, LINEAR, NEAREST_MIPMAP_NEAREST, LINEAR_MIPMAP_NEAREST, NEAREST_MIPMAP_LINEAR, LINEAR_MIPMAP_LINEAR }; + +enum class TextureWrap { REPEAT, MIRRORED_REPEAT, CLAMP_TO_EDGE, CLAMP_TO_BORDER }; + +enum class CompareOp { NEVER, LESS, EQUAL, LESS_OR_EQUAL, GREATER, NOT_EQUAL, GREATER_OR_EQUAL, ALWAYS }; + +enum class StencilOp { KEEP, ZERO, REPLACE, INCREMENT_AND_CLAMP, DECREMENT_AND_CLAMP, INVERT, INCREMENT_AND_WRAP, DECREMENT_AND_WRAP }; + +enum class BlendFactor { + ZERO, + ONE, + SRC_COLOR, + ONE_MINUS_SRC_COLOR, + DST_COLOR, + ONE_MINUS_DST_COLOR, + SRC_ALPHA, + ONE_MINUS_SRC_ALPHA, + DST_ALPHA, + ONE_MINUS_DST_ALPHA +}; + +enum class BlendOp { ADD, SUBTRACT, REVERSE_SUBTRACT, MIN, MAX }; + +enum class CullMode { NONE, FRONT, BACK, FRONT_AND_BACK }; + +enum class PolygonMode { FILL, LINE, POINT }; + +enum class PrimitiveTopology { POINTS, LINES, LINE_STRIP, TRIANGLES, TRIANGLE_STRIP, TRIANGLE_FAN }; + +enum class ShaderStage { VERTEX = 1 << 0, FRAGMENT = 1 << 1, COMPUTE = 1 << 2 }; + +enum class BufferUsage { + BUFFER_USAGE_VERTEX = 1 << 0, + BUFFER_USAGE_INDEX = 1 << 1, + BUFFER_USAGE_UNIFORM = 1 << 2, + BUFFER_USAGE_STORAGE = 1 << 3, + BUFFER_USAGE_TRANSFER_SRC = 1 << 4, + BUFFER_USAGE_TRANSFER_DST = 1 << 5 +}; + +bool operator&(uint32_t lhs, BufferUsage rhs); + +enum class IndexType { UINT16, UINT32 }; + +class RIDAllocator { +public: + RIDAllocator() = default; + virtual ~RIDAllocator() = default; + + RID allocate_rid(); + +protected: + RID next_rid = 1; +}; + +struct VertexAttribute { + uint32_t location; + DataFormat format; + uint32_t offset; +}; + +struct VertexFormat { + uint32_t binding = 0; + uint32_t stride = 0; + Vector attributes; +}; + +struct TextureFormat { + TextureType type = TextureType::TEXTURE_TYPE_2D; + DataFormat format = DataFormat::R8G8B8A8_UNORM; + uint32_t width = 1; + uint32_t height = 1; + uint32_t depth = 1; + uint32_t array_layers = 1; + uint32_t mipmaps = 1; + uint32_t samples = 1; + bool depth_stencil = false; +}; + +struct SamplerState { + TextureFilter min_filter = TextureFilter::LINEAR; + TextureFilter mag_filter = TextureFilter::LINEAR; + TextureWrap wrap_u = TextureWrap::REPEAT; + TextureWrap wrap_v = TextureWrap::REPEAT; + TextureWrap wrap_w = TextureWrap::REPEAT; + float max_anisotropy = 1.0f; + bool compare_enabled = false; + CompareOp compare_op = CompareOp::LESS; + float lod_bias = 0.0f; + float min_lod = 0.0f; + float max_lod = 1000.0f; +}; + +struct RasterizationState { + CullMode cull_mode = CullMode::BACK; + bool front_face_ccw = true; + PolygonMode polygon_mode = PolygonMode::FILL; + float line_width = 1.0f; + bool depth_clamp_enable = false; + bool depth_bias_enable = false; + float depth_bias_constant = 0.0f; + float depth_bias_slope = 0.0f; +}; + +struct DepthStencilState { + bool depth_test_enable = true; + bool depth_write_enable = true; + CompareOp depth_compare_op = CompareOp::LESS; + bool stencil_test_enable = false; + StencilOp stencil_fail_op = StencilOp::KEEP; + StencilOp stencil_depth_fail_op = StencilOp::KEEP; + StencilOp stencil_pass_op = StencilOp::KEEP; + CompareOp stencil_compare_op = CompareOp::ALWAYS; + uint32_t stencil_compare_mask = 0xFF; + uint32_t stencil_write_mask = 0xFF; + uint32_t stencil_reference = 0; +}; + +struct BlendState { + bool enable = false; + BlendFactor src_color = BlendFactor::ONE; + BlendFactor dst_color = BlendFactor::ZERO; + BlendOp color_op = BlendOp::ADD; + BlendFactor src_alpha = BlendFactor::ONE; + BlendFactor dst_alpha = BlendFactor::ZERO; + BlendOp alpha_op = BlendOp::ADD; + bool write_r = true, write_g = true, write_b = true, write_a = true; +}; + +struct PipelineState { + RID shader = INVALID_RID; + VertexFormat vertex_format; + PrimitiveTopology topology = PrimitiveTopology::TRIANGLES; + RasterizationState rasterization; + DepthStencilState depth_stencil; + Vector blend_states; + uint32_t color_attachment_count = 1; +}; + +struct Viewport { + float x = 0, y = 0, width = 800, height = 600; + float min_depth = 0.0f, max_depth = 1.0f; +}; + +struct Scissor { + int32_t x = 0, y = 0; + uint32_t width = 800, height = 600; +}; + +struct ClearValue { + glm::vec4 color = glm::vec4(0, 0, 0, 1); + float depth = 1.0f; + uint32_t stencil = 0; +}; + +struct RenderPassAttachment { + RID texture = INVALID_RID; + uint32_t mip_level = 0; + uint32_t layer = 0; + bool clear = true; + ClearValue clear_value; +}; + + +enum class BlendMode { NONE, ALPHA, ADD, MULTIPLY }; + +enum class ScaleMode { + NONE, /// No scaling - stretch to fit + KEEP, /// Keep aspect ratio - letterbox/pillarbox + EXPAND /// Expand to fill window - may stretch +}; + +struct TextureDescription { + TextureFilter min_filter = TextureFilter::LINEAR; + TextureFilter mag_filter = TextureFilter::LINEAR; + TextureWrap wrap_u = TextureWrap::REPEAT; + TextureWrap wrap_v = TextureWrap::REPEAT; + bool generate_mipmaps = false; +}; + +struct ShaderModule { + uint32_t program = 0; + HashMap uniform_locations; +}; + +struct Buffer { + uint32_t handle = 0; + size_t size = 0; + uint32_t usage_flags = 0; + uint32_t target = 0; +}; + +struct Texture { + RID rid = INVALID_RID; + uint32_t handle = 0; + TextureFormat format; +}; + +struct Sampler { + uint32_t handle = 0; + SamplerState state; +}; + +struct Framebuffer { + uint32_t handle = 0; + Vector attachments; + uint32_t width = 0; + uint32_t height = 0; +}; + +struct Pipeline { + PipelineState state; + uint32_t handle = 0; +}; +/*! + * @brief Abstract base class for rendering devices. + */ +class RenderingDevice : public RIDAllocator { +public: + virtual ~RenderingDevice() = default; + + virtual bool initialize(SDL_Window* sdl_window) = 0; + virtual void shutdown() = 0; + + virtual RID shader_create_from_source(const String& vertex_src, const String& fragment_src) = 0; + virtual void shader_destroy(RID shader) = 0; + + virtual RID buffer_create(size_t size, uint32_t usage_flags, const void* data = nullptr) = 0; + virtual void buffer_update(RID buffer, size_t offset, size_t size, const void* data) = 0; + virtual void buffer_destroy(RID buffer) = 0; + + virtual RID texture_create(const TextureFormat& format, void* data = nullptr) = 0; + virtual void texture_update(RID texture, uint32_t mip_level, uint32_t layer, const void* data, size_t size) = 0; + virtual void texture_generate_mipmaps(RID texture) = 0; + virtual void texture_destroy(RID texture) = 0; + virtual void get_texture_size(RID texture, uint32_t& width, uint32_t& height) = 0; + virtual uint32_t texture_get_native_handle(RID texture) = 0; + virtual Texture get_texture(RID texture) = 0; + + virtual RID sampler_create(const SamplerState& state) = 0; + virtual void sampler_destroy(RID sampler) = 0; + + virtual RID framebuffer_create(const Vector& attachments) = 0; + virtual void framebuffer_destroy(RID framebuffer) = 0; + + virtual RID pipeline_create(const PipelineState& state) = 0; + virtual void pipeline_destroy(RID pipeline) = 0; + + virtual void begin_frame() = 0; + virtual void end_frame() = 0; + + virtual void render_pass_begin(RID framebuffer, const Viewport& viewport, const Scissor& scissor) = 0; + virtual void render_pass_end() = 0; + + virtual void bind_pipeline(RID pipeline) = 0; + virtual void bind_vertex_buffers(const Vector& buffers, const Vector& offsets = {}) = 0; + virtual void bind_index_buffer(RID buffer, IndexType type, size_t offset = 0) = 0; + virtual void bind_uniform_buffer(uint32_t binding, RID buffer, size_t offset = 0, size_t size = 0) = 0; + virtual void bind_texture(uint32_t binding, RID texture, RID sampler) = 0; + + virtual void push_constant(const String& name, const void* data, size_t size) = 0; + + virtual void draw(uint32_t vertex_count, uint32_t instance_count = 1, uint32_t first_vertex = 0, uint32_t first_instance = 0) = 0; + virtual void draw_indexed(uint32_t index_count, uint32_t instance_count = 1, uint32_t first_index = 0, int32_t vertex_offset = 0, + uint32_t first_instance = 0) = 0; + + virtual void set_viewport(const Viewport& viewport) = 0; + virtual void set_scissor(const Scissor& scissor) = 0; + virtual void clear_color(const glm::vec4& color) = 0; + virtual void clear_depth_stencil(float depth = 1.0f, uint32_t stencil = 0) = 0; + + virtual void swap_buffers() = 0; + + virtual void get_drawable_size(int& width, int& height); + +protected: + SDL_Window* _window = nullptr; +}; diff --git a/engine/public/servers/rendering/shader_material.h b/engine/public/servers/rendering/shader_material.h new file mode 100644 index 000000000..c066e2541 --- /dev/null +++ b/engine/public/servers/rendering/shader_material.h @@ -0,0 +1,55 @@ +#pragma once +#include "core/gstl/str.h" +#include "servers/rendering/rendering_device.h" + +/// Variant type for shader parameter values +using UniformType = std::variant; + +class CanvasMaterial { +public: + CanvasMaterial() = default; + + explicit CanvasMaterial(const RID shader_rid) : shader(shader_rid) { + } + + CanvasMaterial& set_shader(RID shader_rid); + + CanvasMaterial& set_color(const Color& col); + + CanvasMaterial& set_texture(RID tex); + + CanvasMaterial& set_custom_texture(const char* name, RID tex); + + template + CanvasMaterial& set_shader_param(const char* uniform_name, const T& value); + + RID get_custom_texture(const char* uniform_name) const; + + RID get_shader() const; + + bool has_custom_shader() const; + + const HashMap& get_uniforms() const; + + const HashMap& get_custom_textures() const; + + Color get_color() const; + + RID get_texture() const; + +private: + + Color color = Color::WHITE; + RID texture = INVALID_RID; + + RID shader = INVALID_RID; + HashMap uniforms; + HashMap custom_textures; +}; + + +template +CanvasMaterial& CanvasMaterial::set_shader_param(const char* uniform_name, const T& value) { + uniforms[uniform_name] = value; + return *this; +} \ No newline at end of file diff --git a/engine/public/servers/rendering/shader_preprocessor.h b/engine/public/servers/rendering/shader_preprocessor.h new file mode 100644 index 000000000..543f2bf49 --- /dev/null +++ b/engine/public/servers/rendering/shader_preprocessor.h @@ -0,0 +1,19 @@ +#pragma once + +#include + + +// TODO: we must create a robust shader preprocessor with naming conventions and more features +struct ShaderSource { + std::string vertex_source; + std::string fragment_source; + std::string compute_source; + bool is_compute = false; + bool is_loaded = false; +}; + +ShaderSource parse_shader(const std::string& source); + +ShaderSource load_shader_file(const char* filepath); + +std::string get_shader_header(); diff --git a/engine/public/stdafx.h b/engine/public/stdafx.h index 5452e5dfb..680f1741c 100644 --- a/engine/public/stdafx.h +++ b/engine/public/stdafx.h @@ -15,8 +15,8 @@ #define FLECS_META #define FLECS_CPP #define FLECS_PIPELINE -#include -#include +// #include +// #include #include #include #include @@ -30,7 +30,7 @@ #include #include "json.hpp" -#include +// #include #include #include #include @@ -58,15 +58,13 @@ using Json = nlohmann::json; #if __ANDROID__ const std::filesystem::path BASE_PATH = ""; - #define ASSETS_PATH std::string("") +#define ASSETS_PATH std::string("") #elif __APPLE__ const std::filesystem::path BASE_PATH = SDL_GetBasePath(); - #define ASSETS_PATH (BASE_PATH / "res/").string() +#define ASSETS_PATH (BASE_PATH / "res/").string() #else const std::filesystem::path BASE_PATH = SDL_GetBasePath(); - #define ASSETS_PATH std::string("res/") +#define ASSETS_PATH std::string("res/") #endif -#include "nuklear.h" -#include "nuklear_sdl3_ogl3.h" -#include +#include \ No newline at end of file diff --git a/res/environment_sky.png b/res/environment_sky.png deleted file mode 100644 index 51e718085..000000000 Binary files a/res/environment_sky.png and /dev/null differ diff --git a/res/scripts/.gitkeep b/res/scripts/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/res/scripts/constants.lua b/res/scripts/constants.lua deleted file mode 100644 index 3aff7d44d..000000000 --- a/res/scripts/constants.lua +++ /dev/null @@ -1,530 +0,0 @@ --- THIS FILE IS FOR INTELLISENSE PURPOSES ONLY --- USAGE: require("constants") - --- SCANCODES -SCANCODE_UNKNOWN = 0 -SCANCODE_A = 4 -SCANCODE_B = 5 -SCANCODE_C = 6 -SCANCODE_D = 7 -SCANCODE_E = 8 -SCANCODE_F = 9 -SCANCODE_G = 10 -SCANCODE_H = 11 -SCANCODE_I = 12 -SCANCODE_J = 13 -SCANCODE_K = 14 -SCANCODE_L = 15 -SCANCODE_M = 16 -SCANCODE_N = 17 -SCANCODE_O = 18 -SCANCODE_P = 19 -SCANCODE_Q = 20 -SCANCODE_R = 21 -SCANCODE_S = 22 -SCANCODE_T = 23 -SCANCODE_U = 24 -SCANCODE_V = 25 -SCANCODE_W = 26 -SCANCODE_X = 27 -SCANCODE_Y = 28 -SCANCODE_Z = 29 -SCANCODE_1 = 30 -SCANCODE_2 = 31 -SCANCODE_3 = 32 -SCANCODE_4 = 33 -SCANCODE_5 = 34 -SCANCODE_6 = 35 -SCANCODE_7 = 36 -SCANCODE_8 = 37 -SCANCODE_9 = 38 -SCANCODE_0 = 39 -SCANCODE_RETURN = 40 -SCANCODE_ESCAPE = 41 -SCANCODE_BACKSPACE = 42 -SCANCODE_TAB = 43 -SCANCODE_SPACE = 44 -SCANCODE_MINUS = 45 -SCANCODE_EQUALS = 46 -SCANCODE_LEFTBRACKET = 47 -SCANCODE_RIGHTBRACKET = 48 -SCANCODE_BACKSLASH = 49 -SCANCODE_NONUSHASH = 50 -SCANCODE_SEMICOLON = 51 -SCANCODE_APOSTROPHE = 52 -SCANCODE_GRAVE = 53 -SCANCODE_COMMA = 54 -SCANCODE_PERIOD = 55 -SCANCODE_SLASH = 56 -SCANCODE_CAPSLOCK = 57 -SCANCODE_F1 = 58 -SCANCODE_F2 = 59 -SCANCODE_F3 = 60 -SCANCODE_F4 = 61 -SCANCODE_F5 = 62 -SCANCODE_F6 = 63 -SCANCODE_F7 = 64 -SCANCODE_F8 = 65 -SCANCODE_F9 = 66 -SCANCODE_F10 = 67 -SCANCODE_F11 = 68 -SCANCODE_F12 = 69 -SCANCODE_PRINTSCREEN = 70 -SCANCODE_SCROLLLOCK = 71 -SCANCODE_PAUSE = 72 -SCANCODE_INSERT = 73 -SCANCODE_HOME = 74 -SCANCODE_PAGEUP = 75 -SCANCODE_DELETE = 76 -SCANCODE_END = 77 -SCANCODE_PAGEDOWN = 78 -SCANCODE_RIGHT = 79 -SCANCODE_LEFT = 80 -SCANCODE_DOWN = 81 -SCANCODE_UP = 82 -SCANCODE_NUMLOCKCLEAR = 83 -SCANCODE_KP_DIVIDE = 84 -SCANCODE_KP_MULTIPLY = 85 -SCANCODE_KP_MINUS = 86 -SCANCODE_KP_PLUS = 87 -SCANCODE_KP_ENTER = 88 -SCANCODE_KP_1 = 89 -SCANCODE_KP_2 = 90 -SCANCODE_KP_3 = 91 -SCANCODE_KP_4 = 92 -SCANCODE_KP_5 = 93 -SCANCODE_KP_6 = 94 -SCANCODE_KP_7 = 95 -SCANCODE_KP_8 = 96 -SCANCODE_KP_9 = 97 -SCANCODE_KP_0 = 98 -SCANCODE_KP_PERIOD = 99 -SCANCODE_NONUSBACKSLASH = 100 -SCANCODE_APPLICATION = 101 -SCANCODE_POWER = 102 -SCANCODE_KP_EQUALS = 103 -SCANCODE_F13 = 104 -SCANCODE_F14 = 105 -SCANCODE_F15 = 106 -SCANCODE_F16 = 107 -SCANCODE_F17 = 108 -SCANCODE_F18 = 109 -SCANCODE_F19 = 110 -SCANCODE_F20 = 111 -SCANCODE_F21 = 112 -SCANCODE_F22 = 113 -SCANCODE_F23 = 114 -SCANCODE_F24 = 115 -SCANCODE_EXECUTE = 116 -SCANCODE_HELP = 117 -SCANCODE_MENU = 118 -SCANCODE_SELECT = 119 -SCANCODE_STOP = 120 -SCANCODE_AGAIN = 121 -SCANCODE_UNDO = 122 -SCANCODE_CUT = 123 -SCANCODE_COPY = 124 -SCANCODE_PASTE = 125 -SCANCODE_FIND = 126 -SCANCODE_MUTE = 127 -SCANCODE_VOLUMEUP = 128 -SCANCODE_VOLUMEDOWN = 129 -SCANCODE_KP_COMMA = 133 -SCANCODE_KP_EQUALSAS400 = 134 -SCANCODE_INTERNATIONAL1 = 135 -SCANCODE_INTERNATIONAL2 = 136 -SCANCODE_INTERNATIONAL3 = 137 -SCANCODE_INTERNATIONAL4 = 138 -SCANCODE_INTERNATIONAL5 = 139 -SCANCODE_INTERNATIONAL6 = 140 -SCANCODE_INTERNATIONAL7 = 141 -SCANCODE_INTERNATIONAL8 = 142 -SCANCODE_INTERNATIONAL9 = 143 -SCANCODE_LANG1 = 144 -SCANCODE_LANG2 = 145 -SCANCODE_LANG3 = 146 -SCANCODE_LANG4 = 147 -SCANCODE_LANG5 = 148 -SCANCODE_LANG6 = 149 -SCANCODE_LANG7 = 150 -SCANCODE_LANG8 = 151 -SCANCODE_LANG9 = 152 -SCANCODE_ALTERASE = 153 -SCANCODE_SYSREQ = 154 -SCANCODE_CANCEL = 155 -SCANCODE_CLEAR = 156 -SCANCODE_PRIOR = 157 -SCANCODE_RETURN2 = 158 -SCANCODE_SEPARATOR = 159 -SCANCODE_OUT = 160 -SCANCODE_OPER = 161 -SCANCODE_CLEARAGAIN = 162 -SCANCODE_CRSEL = 163 -SCANCODE_EXSEL = 164 -SCANCODE_KP_00 = 176 -SCANCODE_KP_000 = 177 -SCANCODE_THOUSANDSSEPARATOR = 178 -SCANCODE_DECIMALSEPARATOR = 179 -SCANCODE_CURRENCYUNIT = 180 -SCANCODE_CURRENCYSUBUNIT = 181 -SCANCODE_KP_LEFTPAREN = 182 -SCANCODE_KP_RIGHTPAREN = 183 -SCANCODE_KP_LEFTBRACE = 184 -SCANCODE_KP_RIGHTBRACE = 185 -SCANCODE_KP_TAB = 186 -SCANCODE_KP_BACKSPACE = 187 -SCANCODE_KP_A = 188 -SCANCODE_KP_B = 189 -SCANCODE_KP_C = 190 -SCANCODE_KP_D = 191 -SCANCODE_KP_E = 192 -SCANCODE_KP_F = 193 -SCANCODE_KP_XOR = 194 -SCANCODE_KP_POWER = 195 -SCANCODE_KP_PERCENT = 196 -SCANCODE_KP_LESS = 197 -SCANCODE_KP_GREATER = 198 -SCANCODE_KP_AMPERSAND = 199 -SCANCODE_KP_DBLAMPERSAND = 200 -SCANCODE_KP_VERTICALBAR = 201 -SCANCODE_KP_DBLVERTICALBAR = 202 -SCANCODE_KP_COLON = 203 -SCANCODE_KP_HASH = 204 -SCANCODE_KP_SPACE = 205 -SCANCODE_KP_AT = 206 -SCANCODE_KP_EXCLAM = 207 -SCANCODE_KP_MEMSTORE = 208 -SCANCODE_KP_MEMRECALL = 209 -SCANCODE_KP_MEMCLEAR = 210 -SCANCODE_KP_MEMADD = 211 -SCANCODE_KP_MEMSUBTRACT = 212 -SCANCODE_KP_MEMMULTIPLY = 213 -SCANCODE_KP_MEMDIVIDE = 214 -SCANCODE_KP_PLUSMINUS = 215 -SCANCODE_KP_CLEAR = 216 -SCANCODE_KP_CLEARENTRY = 217 -SCANCODE_KP_BINARY = 218 -SCANCODE_KP_OCTAL = 219 -SCANCODE_KP_DECIMAL = 220 -SCANCODE_KP_HEXADECIMAL = 221 -SCANCODE_LCTRL = 224 -SCANCODE_LSHIFT = 225 -SCANCODE_LALT = 226 -SCANCODE_LGUI = 227 -SCANCODE_RCTRL = 228 -SCANCODE_RSHIFT = 229 -SCANCODE_RALT = 230 -SCANCODE_RGUI = 231 -SCANCODE_MODE = 257 -SCANCODE_SLEEP = 258 -SCANCODE_WAKE = 259 -SCANCODE_CHANNEL_INCREMENT = 260 -SCANCODE_CHANNEL_DECREMENT = 261 -SCANCODE_MEDIA_PLAY = 262 -SCANCODE_MEDIA_PAUSE = 263 -SCANCODE_MEDIA_RECORD = 264 -SCANCODE_MEDIA_FAST_FORWARD = 265 -SCANCODE_MEDIA_REWIND = 266 -SCANCODE_MEDIA_NEXT_TRACK = 267 -SCANCODE_MEDIA_PREVIOUS_TRACK = 268 -SCANCODE_MEDIA_STOP = 269 -SCANCODE_MEDIA_EJECT = 270 -SCANCODE_MEDIA_PLAY_PAUSE = 271 -SCANCODE_MEDIA_SELECT = 272 -SCANCODE_AC_NEW = 273 -SCANCODE_AC_OPEN = 274 -SCANCODE_AC_CLOSE = 275 -SCANCODE_AC_EXIT = 276 -SCANCODE_AC_SAVE = 277 -SCANCODE_AC_PRINT = 278 -SCANCODE_AC_PROPERTIES = 279 -SCANCODE_AC_SEARCH = 280 -SCANCODE_AC_HOME = 281 -SCANCODE_AC_BACK = 282 -SCANCODE_AC_FORWARD = 283 -SCANCODE_AC_STOP = 284 -SCANCODE_AC_REFRESH = 285 -SCANCODE_AC_BOOKMARKS = 286 -SCANCODE_SOFTLEFT = 287 -SCANCODE_SOFTRIGHT = 288 -SCANCODE_CALL = 289 -SCANCODE_ENDCALL = 290 -SCANCODE_RESERVED = 400 -SCANCODE_COUNT = 512 - --- EVENTS -EVENT_FIRST = 0 -EVENT_QUIT = 256 -EVENT_TERMINATING = 257 -EVENT_LOW_MEMORY = 258 -EVENT_WILL_ENTER_BACKGROUND = 259 -EVENT_DID_ENTER_BACKGROUND = 260 -EVENT_WILL_ENTER_FOREGROUND = 261 -EVENT_DID_ENTER_FOREGROUND = 262 -EVENT_LOCALE_CHANGED = 263 -EVENT_SYSTEM_THEME_CHANGED = 264 -EVENT_DISPLAY_ORIENTATION = 337 -EVENT_DISPLAY_ADDED = 338 -EVENT_DISPLAY_REMOVED = 339 -EVENT_DISPLAY_MOVED = 340 -EVENT_DISPLAY_DESKTOP_MODE_CHANGED = 341 -EVENT_DISPLAY_CURRENT_MODE_CHANGED = 342 -EVENT_DISPLAY_CONTENT_SCALE_CHANGED = 343 -EVENT_DISPLAY_FIRST = 337 -EVENT_DISPLAY_LAST = 343 -EVENT_WINDOW_SHOWN = 514 -EVENT_WINDOW_HIDDEN = 515 -EVENT_WINDOW_EXPOSED = 516 -EVENT_WINDOW_MOVED = 517 -EVENT_WINDOW_RESIZED = 518 -EVENT_WINDOW_PIXEL_SIZE_CHANGED = 519 -EVENT_WINDOW_METAL_VIEW_RESIZED = 520 -EVENT_WINDOW_MINIMIZED = 521 -EVENT_WINDOW_MAXIMIZED = 522 -EVENT_WINDOW_RESTORED = 523 -EVENT_WINDOW_MOUSE_ENTER = 524 -EVENT_WINDOW_MOUSE_LEAVE = 525 -EVENT_WINDOW_FOCUS_GAINED = 526 -EVENT_WINDOW_FOCUS_LOST = 527 -EVENT_WINDOW_CLOSE_REQUESTED = 528 -EVENT_WINDOW_HIT_TEST = 529 -EVENT_WINDOW_ICCPROF_CHANGED = 530 -EVENT_WINDOW_DISPLAY_CHANGED = 531 -EVENT_WINDOW_DISPLAY_SCALE_CHANGED = 532 -EVENT_WINDOW_SAFE_AREA_CHANGED = 533 -EVENT_WINDOW_OCCLUDED = 534 -EVENT_WINDOW_ENTER_FULLSCREEN = 535 -EVENT_WINDOW_LEAVE_FULLSCREEN = 536 -EVENT_WINDOW_DESTROYED = 537 -EVENT_WINDOW_HDR_STATE_CHANGED = 538 -EVENT_WINDOW_FIRST = 514 -EVENT_WINDOW_LAST = 538 -EVENT_KEY_DOWN = 768 -EVENT_KEY_UP = 769 -EVENT_TEXT_EDITING = 770 -EVENT_TEXT_INPUT = 771 -EVENT_KEYMAP_CHANGED = 772 -EVENT_KEYBOARD_ADDED = 773 -EVENT_KEYBOARD_REMOVED = 774 -EVENT_TEXT_EDITING_CANDIDATES = 775 -EVENT_MOUSE_MOTION = 1024 -EVENT_MOUSE_BUTTON_DOWN = 1025 -EVENT_MOUSE_BUTTON_UP = 1026 -EVENT_MOUSE_WHEEL = 1027 -EVENT_MOUSE_ADDED = 1028 -EVENT_MOUSE_REMOVED = 1029 -EVENT_JOYSTICK_AXIS_MOTION = 1536 -EVENT_JOYSTICK_BALL_MOTION = 1537 -EVENT_JOYSTICK_HAT_MOTION = 1538 -EVENT_JOYSTICK_BUTTON_DOWN = 1539 -EVENT_JOYSTICK_BUTTON_UP = 1540 -EVENT_JOYSTICK_ADDED = 1541 -EVENT_JOYSTICK_REMOVED = 1542 -EVENT_JOYSTICK_BATTERY_UPDATED = 1543 -EVENT_JOYSTICK_UPDATE_COMPLETE = 1544 -EVENT_GAMEPAD_AXIS_MOTION = 1616 -EVENT_GAMEPAD_BUTTON_DOWN = 1617 -EVENT_GAMEPAD_BUTTON_UP = 1618 -EVENT_GAMEPAD_ADDED = 1619 -EVENT_GAMEPAD_REMOVED = 1620 -EVENT_GAMEPAD_REMAPPED = 1621 -EVENT_GAMEPAD_TOUCHPAD_DOWN = 1622 -EVENT_GAMEPAD_TOUCHPAD_MOTION = 1623 -EVENT_GAMEPAD_TOUCHPAD_UP = 1624 -EVENT_GAMEPAD_SENSOR_UPDATE = 1625 -EVENT_GAMEPAD_UPDATE_COMPLETE = 1626 -EVENT_GAMEPAD_STEAM_HANDLE_UPDATED = 1627 -EVENT_FINGER_DOWN = 1792 -EVENT_FINGER_UP = 1793 -EVENT_FINGER_MOTION = 1794 -EVENT_FINGER_CANCELED = 1795 -EVENT_CLIPBOARD_UPDATE = 2304 -EVENT_DROP_FILE = 4096 -EVENT_DROP_TEXT = 4097 -EVENT_DROP_BEGIN = 4098 -EVENT_DROP_COMPLETE = 4099 -EVENT_DROP_POSITION = 4100 -EVENT_AUDIO_DEVICE_ADDED = 4352 -EVENT_AUDIO_DEVICE_REMOVED = 4353 -EVENT_AUDIO_DEVICE_FORMAT_CHANGED = 4354 -EVENT_SENSOR_UPDATE = 4608 -EVENT_PEN_PROXIMITY_IN = 4864 -EVENT_PEN_PROXIMITY_OUT = 4865 -EVENT_PEN_DOWN = 4866 -EVENT_PEN_UP = 4867 -EVENT_PEN_BUTTON_DOWN = 4868 -EVENT_PEN_BUTTON_UP = 4869 -EVENT_PEN_MOTION = 4870 -EVENT_PEN_AXIS = 4871 -EVENT_CAMERA_DEVICE_ADDED = 5120 -EVENT_CAMERA_DEVICE_REMOVED = 5121 -EVENT_CAMERA_DEVICE_APPROVED = 5122 -EVENT_CAMERA_DEVICE_DENIED = 5123 -EVENT_RENDER_TARGETS_RESET = 8192 -EVENT_RENDER_DEVICE_RESET = 8193 -EVENT_RENDER_DEVICE_LOST = 8194 -EVENT_PRIVATE0 = 16384 -EVENT_PRIVATE1 = 16385 -EVENT_PRIVATE2 = 16386 -EVENT_PRIVATE3 = 16387 -EVENT_POLL_SENTINEL = 32512 -EVENT_USER = 32768 -EVENT_LAST = 65535 -EVENT_ENUM_PADDING = 2147483647 - ----@meta _ - ----SDL Mouse Button Constants ----@enum MouseButton -BUTTON = { - LEFT = 1, - MIDDLE = 2, - RIGHT = 3, - X1 = 4, - X2 = 5, -} - ----SDL Mouse Button Masks (for checking button state) ----@class MouseButtonMask -BUTTON_MASK = { - LEFT = 0x01, -- 1 << 0 - MIDDLE = 0x02, -- 1 << 1 - RIGHT = 0x04, -- 1 << 2 - X1 = 0x08, -- 1 << 3 - X2 = 0x10, -- 1 << 4 -} - ----Vector 2D ----@class vec2 ----@field x number ----@field y number - ----Vector 3D ----@class vec3 ----@field x number ----@field y number ----@field z number - ----Vector 4D / Color ----@class vec4 ----@field x number ----@field y number ----@field z number ----@field w number ----@field r number Color red channel ----@field g number Color green channel ----@field b number Color blue channel ----@field a number Color alpha channel - ----2D Transform component ----@class Transform2D ----@field position vec2 ----@field scale vec2 ----@field rotation number Rotation in radians - ----3D Transform component ----@class Transform3D ----@field position vec3 ----@field rotation vec3 Euler angles in radians ----@field scale vec3 - ----2D Shape component ----@class Shape2D ----@field color vec4 ----@field filled boolean - ----2D Label/Text component ----@class Label2D ----@field text string ----@field color vec4 ----@field font_name string ----@field font_size number - ----3D Camera component ----@class Camera3D ----@field position vec3 ----@field yaw number Yaw angle in degrees ----@field pitch number Pitch angle in degrees ----@field fov number Field of view in degrees ----@field speed number Movement speed ----@field view_distance number Far plane distance ----@field move_forward fun(self: Camera3D, delta_time: number) Move camera forward ----@field move_backward fun(self: Camera3D, delta_time: number) Move camera backward ----@field move_left fun(self: Camera3D, delta_time: number) Move camera left ----@field move_right fun(self: Camera3D, delta_time: number) Move camera right ----@field look_at fun(self: Camera3D, xoffset: number, yoffset: number, sensitivity?: number) Rotate camera ----@field zoom fun(self: Camera3D, yoffset: number) Zoom camera ----@field get_view_matrix fun(self: Camera3D): mat4 Get view matrix ----@field get_projection_matrix fun(self: Camera3D, width: integer, height: integer): mat4 Get projection matrix - ----4x4 Matrix ----@class mat4 - - ----@class Cube ----@field size number Size of the cube ----@field color vec4 Color of the cube - - ----Game entity with components ----@class Entity ----@field id integer Unique entity ID ----@field name string Entity name ----@field is_valid boolean Whether entity is still valid ----@field transform2d? Transform2D 2D transform component ----@field transform? Transform3D 3D transform component ----@field shape2d? Shape2D 2D shape component ----@field label2d? Label2D 2D label component ----@field camera? Camera3D 3D camera component ----@field cube? Cube 3D cube component ----@field has_component fun(self: Entity, component_name: string): boolean Check if entity has component ----@field add_component fun(self: Entity, component_name: string): boolean Add component to entity ----@field get_component fun(self: Entity, component_name: string): any Get component from entity ----@field remove_component fun(self: Entity, component_name: string): boolean Remove component from entity - ----Viewport configuration ----@class Viewport ----@field width integer Viewport width in pixels ----@field height integer Viewport height in pixels ----@field scale number Viewport scale factor - ----Window configuration ----@class Window ----@field width integer Window width in pixels ----@field height integer Window height in pixels ----@field dpi_scale number DPI scaling factor - ----Renderer device configuration ----@class RendererDevice ----@field backend string Rendering backend name ----@field texture_filtering string Texture filtering mode - ----Engine configuration ----@class EngineConfig ----@field get_viewport fun(self: EngineConfig): Viewport Get viewport configuration ----@field get_window fun(self: EngineConfig): Window Get window configuration ----@field get_renderer_device fun(self: EngineConfig): RendererDevice Get renderer device configuration - ----Main engine class ----@class Engine ----@field get_config fun(self: Engine): EngineConfig Get engine configuration - ----Global engine singleton ----@type Engine - ----Input handling ----@class Input ----@field get_mouse_position fun(): vec2 Get current mouse position ----@field is_key_pressed fun(key_code: integer): boolean Check if key is pressed - ----@type Input - ----Scene management ----@class Scene ----@field change_scene fun(scene_name: string) Change to a different scene ----@field get_entities_count fun(): integer Get total number of entities - ----@type Scene - ----Current entity (available in entity scripts) ----@type Entity \ No newline at end of file diff --git a/res/shaders/.gitkeep b/res/shaders/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/res/shaders/opengl/default.frag b/res/shaders/opengl/default.frag deleted file mode 100644 index 93bbb5898..000000000 --- a/res/shaders/opengl/default.frag +++ /dev/null @@ -1,577 +0,0 @@ -out vec4 COLOR; - -#define MAX_LIGHTS 50 - -in vec3 POSITION; -in vec3 NORMAL; -in vec2 UV; -in vec4 LIGHT_SPACE_POSITION; - -uniform vec3 CAMERA_POSITION_WORLD; - -struct DirectionalLight { - vec3 direction; - vec3 color; - bool cast_shadows; -}; - -uniform DirectionalLight dirLights[MAX_LIGHTS]; -uniform int numDirLights; - -struct SpotLight { - vec3 position; - vec3 direction; - vec3 color; - float inner_cut_off; - float outer_cut_off; -}; - -uniform SpotLight spotLights[MAX_LIGHTS]; -uniform int numSpotLights; - - -struct Material { - vec3 albedo; - vec3 specular; - float metallic; - float roughness; - float ao; - vec3 emissive; - float emissiveStrength; - float ior; // index of refraction (e.g. glass ~1.5, water ~1.33) -}; - -uniform Material material; - -// Texture samplers -uniform sampler2D ALBEDO_MAP; -uniform sampler2D SPECULAR_MAP; -uniform sampler2D METALLIC_MAP; -uniform sampler2D ROUGHNESS_MAP; -uniform sampler2D NORMAL_MAP; -uniform sampler2D AO_MAP; -uniform sampler2D EMISSIVE_MAP; -uniform sampler2D SHADOW_MAP; -uniform samplerCube ENVIRONMENT_MAP; // environment cubemap for reflection/refraction/IBL - -uniform int has_features; - -#define HAS_ALBEDO_MAP (1 << 0) -#define HAS_SPECULAR_MAP (1 << 1) -#define HAS_METALLIC_MAP (1 << 2) -#define HAS_ROUGHNESS_MAP (1 << 3) -#define HAS_NORMAL_MAP (1 << 4) -#define HAS_AO_MAP (1 << 5) -#define HAS_EMISSIVE_MAP (1 << 6) -#define HAS_IBL (1 << 7) - -#define HAS(flag) ((has_features & flag) != 0) - -const float PI = 3.14159265359; - -uniform float TIME; - -// ============================================================================ -// Shadow Mapping with PCF (Percentage Closer Filtering) -// ============================================================================ -// References: -// - https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping -float shadow_calculation(vec4 frag_pos_light_space, vec3 N, vec3 L) -{ - vec3 projCoords = frag_pos_light_space.xyz / frag_pos_light_space.w; - - // Transform from [-1,1] to [0,1] - projCoords = projCoords * 0.5 + 0.5; - - if (projCoords.x < 0.0 || projCoords.x > 1.0 || projCoords.y < 0.0 || projCoords.y > 1.0 || projCoords.z > 1.0) - return 0.0; - - float currentDepth = projCoords.z; - - float bias = max(0.001 * (1.0 - dot(N, L)), 0.0005); - - // PCF sample (3x3 kernel) - float shadow = 0.0; - vec2 texelSize = 1.0 / vec2(textureSize(SHADOW_MAP, 0)); - - for (int x = -1; x <= 1; ++x) - { - for (int y = -1; y <= 1; ++y) - { - vec2 offset = vec2(x, y) * texelSize; - float pcfDepth = texture(SHADOW_MAP, projCoords.xy + offset).r; - shadow += (currentDepth - bias > pcfDepth) ? 1.0 : 0.0; - } - } - - shadow /= 9.0; - - - shadow *= smoothstep(0.0, 1.0, projCoords.z); - - return shadow; -} - -// ============================================================================ -// PBR: Cook-Torrance Microfacet BRDF -// ============================================================================ -// The Cook-Torrance specular BRDF consists of three components: -// - D: Normal Distribution Function (NDF) - describes microfacet orientation -// - G: Geometry Function - describes self-shadowing/masking of microfacets -// - F: Fresnel Equation - describes light reflection at different angles -// -// References: -// - https://learnopengl.com/PBR/Theory -// - "Real Shading in Unreal Engine 4" by Brian Karis (Epic Games) - -// ============================================================================ -// NDF: Trowbridge-Reitz GGX Distribution (Improved) -// ============================================================================ -// Better energy conservation and numerical stability -float distribution_ggx(vec3 N, vec3 H, float roughness) -{ - float a = roughness * roughness; - float a2 = a * a; - float NdotH = max(dot(N, H), 0.0); - float NdotH2 = NdotH * NdotH; - - float nom = a2; - float denom = (NdotH2 * (a2 - 1.0) + 1.0); - denom = PI * denom * denom; - - return nom / max(denom, 0.0001); -} - -// ============================================================================ -// Geometry: Schlick-GGX (for direct lighting) -// ============================================================================ -// Describes microfacet self-shadowing (when some microfacets occlude others). -// Uses k = (roughness + 1)² / 8 for direct lighting (IBL uses different k). -float geometry_schlick_ggx(float NdotV, float roughness) -{ - float r = (roughness + 1.0); - float k = (r * r) / 8.0; - return NdotV / (NdotV * (1.0 - k) + k + 0.0001); -} - -// Smith Joint Masking-Shadowing Function (uncorrelated) -float geometry_smith(vec3 N, vec3 V, vec3 L, float roughness) -{ - float NdotV = max(dot(N, V), 0.0); - float NdotL = max(dot(N, L), 0.0); - float ggx2 = geometry_schlick_ggx(NdotV, roughness); - float ggx1 = geometry_schlick_ggx(NdotL, roughness); - return ggx1 * ggx2; -} - -// Height-Correlated Smith G (more accurate, recommended) -float geometry_smith_correlated(vec3 N, vec3 V, vec3 L, float roughness) -{ - float NdotV = max(dot(N, V), 0.0001); - float NdotL = max(dot(N, L), 0.0001); - - float a = roughness * roughness; - float a2 = a * a; - - float GGXV = NdotL * sqrt(NdotV * NdotV * (1.0 - a2) + a2); - float GGXL = NdotV * sqrt(NdotL * NdotL * (1.0 - a2) + a2); - - return 0.5 / max(GGXV + GGXL, 0.0001); -} - -// ============================================================================ -// Fresnel - Schlick approximation -// ============================================================================ -vec3 fresnel_schlick(float cosTheta, vec3 F0) -{ - return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); -} - -// Roughness-variant fresnel for more realistic grazing reflections -vec3 fresnel_schlick_roughness(float cosTheta, vec3 F0, float roughness) -{ - return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); -} - -// Diffuse Fresnel (Disney/Burley diffuse) -// Provides energy conservation for the diffuse term -float diffuse_fresnel(float NdotL, float NdotV, float LdotH, float roughness) -{ - float FD90 = 0.5 + 2.0 * LdotH * LdotH * roughness; - float FdV = 1.0 + (FD90 - 1.0) * pow(1.0 - NdotV, 5.0); - float FdL = 1.0 + (FD90 - 1.0) * pow(1.0 - NdotL, 5.0); - return FdV * FdL; -} - -// ============================================================================ -// PBR Light Contribution Calculation (Improved) -// ============================================================================ -// Calculates the Cook-Torrance BRDF for a given light direction and radiance. -// Returns the combined diffuse and specular contribution with better energy conservation. -vec3 calculate_pbr_contribution( - vec3 N, // Surface normal - vec3 V, // View direction - vec3 L, // Light direction - vec3 radiance, // Incoming light radiance - vec3 F0, // Base reflectance - vec3 albedo, // Surface albedo - float metallic, // Metallic factor - float roughness // Roughness factor -) -{ - vec3 H = normalize(V + L); - - float NdotV = max(dot(N, V), 0.0001); - float NdotL = max(dot(N, L), 0.0001); - float NdotH = max(dot(N, H), 0.0); - float HdotV = max(dot(H, V), 0.0); - float LdotH = max(dot(L, H), 0.0); - - // Cook-Torrance specular BRDF - float D = distribution_ggx(N, H, roughness); - float G = geometry_smith_correlated(N, V, L, roughness); // Use improved correlated version - vec3 F = fresnel_schlick(HdotV, F0); - - // Specular contribution - vec3 numerator = D * G * F; - float denominator = 4.0 * NdotV * NdotL; - vec3 specular = numerator / max(denominator, 0.0001); - - // Energy conservation: kS is specular, kD is diffuse - vec3 kS = F; - vec3 kD = vec3(1.0) - kS; - kD *= 1.0 - metallic; // Metals have no diffuse - - // Improved diffuse with Disney/Burley model - float diffuse_fresnel_factor = diffuse_fresnel(NdotL, NdotV, LdotH, roughness); - vec3 diffuse = (kD * albedo / PI) * diffuse_fresnel_factor; - - // Combine diffuse and specular - return (diffuse + specular) * radiance * NdotL; -} - -// ============================================================================ -// Normal Mapping (Tangent Space) -// ============================================================================ -// References: -// - https://learnopengl.com/Advanced-Lighting/Normal-Mapping -// - http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-13-normal-mapping/ -vec3 calculate_normal_map() -{ - vec3 tangentNormal = texture(NORMAL_MAP, UV).xyz * 2.0 - 1.0; - - vec3 Q1 = dFdx(POSITION); - vec3 Q2 = dFdy(POSITION); - vec2 st1 = dFdx(UV); - vec2 st2 = dFdy(UV); - - vec3 N = normalize(NORMAL); - vec3 T = normalize(Q1 * st2.t - Q2 * st1.t); - vec3 B = -normalize(cross(N, T)); - mat3 TBN = mat3(T, B, N); - - return normalize(TBN * tangentNormal); -} - - -// ============================================================================ -// Environment Reflections and Refractions -// ============================================================================ -// For highly reflective or refractive materials (glass, water, mirrors) - -vec3 sample_reflection(vec3 I, vec3 N, float roughness) -{ - vec3 R = reflect(I, N); - // Use roughness to determine blur level - float lod = roughness * 8.0; - return textureLod(ENVIRONMENT_MAP, R, lod).rgb; -} - -vec3 sample_refraction(vec3 I, vec3 N, float eta, float roughness) -{ - vec3 R = refract(I, N, eta); - // Slightly blur refracted environment based on roughness - float lod = roughness * 4.0; - - // Handle total internal reflection - if (dot(R, R) < 0.001) { - R = reflect(I, N); - } - - return textureLod(ENVIRONMENT_MAP, R, lod).rgb; -} - -// Calculate Fresnel effect for dielectric materials (glass, water) -// Returns the ratio of reflection vs refraction -float fresnel_dielectric(vec3 I, vec3 N, float ior) -{ - float cosi = clamp(dot(I, N), -1.0, 1.0); - float etai = 1.0; - float etat = ior; - - if (cosi > 0.0) { - float temp = etai; - etai = etat; - etat = temp; - } - - float sint = etai / etat * sqrt(max(0.0, 1.0 - cosi * cosi)); - - if (sint >= 1.0) { - return 1.0; // Total internal reflection - } - - float cost = sqrt(max(0.0, 1.0 - sint * sint)); - cosi = abs(cosi); - - // Fresnel equations for s and p polarized light - float Rs = ((etat * cosi) - (etai * cost)) / ((etat * cosi) + (etai * cost)); - float Rp = ((etai * cosi) - (etat * cost)) / ((etai * cosi) + (etat * cost)); - - return (Rs * Rs + Rp * Rp) / 2.0; -} - -// ============================================================================ -// Image-Based Lighting (IBL) using Environment Map -// ============================================================================ -// Approximates IBL by sampling the environment map at different mip levels -// Lower mip levels = sharper reflections (low roughness) -// Higher mip levels = blurrier reflections (high roughness) -// References: -// - https://learnopengl.com/PBR/IBL/Diffuse-irradiance -// - https://learnopengl.com/PBR/IBL/Specular-IBL -vec3 calculate_ibl( - vec3 N, - vec3 V, - vec3 F0, - vec3 albedo, - float metallic, - float roughness, - float ao -) -{ - vec3 R = reflect(-V, N); - - float NdotV = max(dot(N, V), 0.0); - vec3 F = fresnel_schlick_roughness(NdotV, F0, roughness); - - vec3 kS = F; - vec3 kD = (1.0 - kS) * (1.0 - metallic); - - // Diffuse IBL (irradiance) - const float DIFFUSE_MIP = 5.0; - vec3 irradiance = textureLod(ENVIRONMENT_MAP, N, DIFFUSE_MIP).rgb; - vec3 diffuse = kD * irradiance * albedo; - - // Specular IBL (prefiltered environment) - IMPROVED for sharper reflections - const float MAX_REFLECTION_LOD = 6.0; // Reduced for sharper reflections - float lod = roughness * MAX_REFLECTION_LOD; - vec3 prefilteredColor = textureLod(ENVIRONMENT_MAP, R, lod).rgb; - - // Better environmental BRDF approximation - vec2 envBRDF = vec2(1.0 - roughness, 1.0 - roughness); - - // Boost specular for metals - float metallic_boost = mix(1.0, 2.0, metallic); // Metals get 2x stronger reflections - vec3 specular = prefilteredColor * (F * envBRDF.x + envBRDF.y) * metallic_boost; - - return (diffuse + specular) * ao; -} - - -void main() -{ - vec4 albedoSample = texture(ALBEDO_MAP, UV); - if (albedoSample.a < 0.1) - discard; - - vec3 finalAlbedo = HAS(HAS_ALBEDO_MAP) ? pow(albedoSample.rgb, vec3(2.2)) : material.albedo; - float finalMetallic = material.metallic; - float finalRoughness = material.roughness; - float finalAO = material.ao; - - // Red = Ambient Occlusion, Green channel = roughness, Blue channel = metallic (glTF 2.0 format) - if (HAS(HAS_METALLIC_MAP)) { - vec3 mr = texture(METALLIC_MAP, UV).rgb; - finalAO = mr.r; - finalRoughness = mr.g; - finalMetallic = mr.b; - } - - // vec3 finalSpecular = material.specular; - // if (USE_SPECULAR_MAP) { - // vec3 specSample = texture(SPECULAR_MAP, UV).rgb; - // finalSpecular = pow(specSample, vec3(2.2)); // gamma-corrected - // } - - if (HAS(HAS_AO_MAP)) - finalAO = texture(AO_MAP, UV).r; - - if (HAS(HAS_ROUGHNESS_MAP)) - finalRoughness = texture(ROUGHNESS_MAP, UV).r; - - finalMetallic = clamp(finalMetallic, 0.0, 1.0); - finalRoughness = clamp(finalRoughness, 0.04, 1.0); - finalAO = clamp(finalAO, 0.0, 1.0); - - vec3 finalEmissive = material.emissive * material.emissiveStrength; - if (HAS(HAS_EMISSIVE_MAP)) { - vec3 emissiveSample = texture(EMISSIVE_MAP, UV).rgb; - finalEmissive = pow(emissiveSample, vec3(2.2)) * material.emissiveStrength; - } - - // --- Normal & View Direction --- - vec3 N = HAS(HAS_NORMAL_MAP) ? calculate_normal_map() : normalize(NORMAL); - vec3 V = normalize(CAMERA_POSITION_WORLD - POSITION); - vec3 I = normalize(POSITION - CAMERA_POSITION_WORLD); // incident ray for reflection/refraction - - // --- Base Reflectance --- - vec3 F0 = mix(vec3(0.04), finalAlbedo, finalMetallic); - // F0 = mix(F0, finalSpecular, 0.5); // blend specular color for non-metals - // TODO: IOR - - // --- Lighting --- - vec3 Lo = vec3(0.0); - - // Directional Lights - for (int i = 0; i < numDirLights; ++i) { - vec3 L = normalize(-dirLights[i].direction); - vec3 radiance = dirLights[i].color; - - // Calculate PBR contribution - vec3 contribution = calculate_pbr_contribution( - N, V, L, radiance, F0, finalAlbedo, finalMetallic, finalRoughness - ); - - float shadow = 1.0; - if (dirLights[i].cast_shadows) { - shadow = 1.0 - shadow_calculation(LIGHT_SPACE_POSITION, N, L); - } - - Lo += contribution * shadow; - } - - // Spot Lights - for (int i = 0; i < numSpotLights; ++i) { - vec3 L = normalize(spotLights[i].position - POSITION); - float dist = length(spotLights[i].position - POSITION); - float attenuation = 1.0 / (dist * dist); - - // Spotlight cone attenuation - float theta = dot(L, normalize(-spotLights[i].direction)); - float epsilon = spotLights[i].inner_cut_off - spotLights[i].outer_cut_off; - float intensity = clamp((theta - spotLights[i].outer_cut_off) / epsilon, 0.0, 1.0); - - vec3 radiance = spotLights[i].color * attenuation * intensity; - - // Calculate PBR contribution - vec3 contribution = calculate_pbr_contribution( - N, V, L, radiance, F0, finalAlbedo, finalMetallic, finalRoughness - ); - - Lo += contribution; - } - - // --- Image-Based Lighting (IBL) --- - vec3 ibl_contribution = vec3(0.0); - vec3 env_reflection = vec3(0.0); - vec3 env_refraction = vec3(0.0); - - if (HAS(HAS_IBL)) { - // Standard IBL for opaque materials - ibl_contribution = calculate_ibl(N, V, F0, finalAlbedo, finalMetallic, finalRoughness, finalAO) * 0.5; // Increased from 0.3 - - // Enhanced reflections for metallic materials (SIGNIFICANTLY BOOSTED) - if (finalMetallic > 0.5) { - vec3 R = reflect(I, N); - float lod = finalRoughness * 8.0; - vec3 metallic_reflection = textureLod(ENVIRONMENT_MAP, R, lod).rgb; - - float NdotV = max(dot(N, V), 0.0); - vec3 F = fresnel_schlick_roughness(NdotV, F0, finalRoughness); - - // MUCH stronger metallic reflections for chrome/mirror effect - float metallic_strength = mix(1.5, 3.0, finalMetallic); // More metallic = stronger reflection - float roughness_factor = 1.0 - finalRoughness * 0.3; // Less penalty for roughness - - env_reflection = metallic_reflection * F * roughness_factor * metallic_strength; - ibl_contribution += env_reflection * finalMetallic; - } - - // Refraction for transparent materials (when alpha < 1.0 and IOR > 1.0) - if (albedoSample.a < 0.99 && material.ior > 1.0) { - float eta = 1.0 / material.ior; - - // Calculate Fresnel for dielectric - float fresnelAmount = fresnel_dielectric(I, N, material.ior); - - // Sample both reflection and refraction - vec3 refl = sample_reflection(I, N, finalRoughness); - vec3 refr = sample_refraction(I, N, eta, finalRoughness); - - // Blend based on Fresnel and roughness - env_refraction = mix(refr, refl, fresnelAmount); - - // Apply to transparent parts - float transparency = 1.0 - albedoSample.a; - ibl_contribution = mix(ibl_contribution, env_refraction * 0.8, transparency * 0.8); - } - } - - // --- Ambient Light & Emission --- - // When IBL is disabled, use improved hemisphere ambient - vec3 ambient = vec3(0.0); - if (!HAS(HAS_IBL)) { - // Hemisphere ambient lighting approximation - // Sky color (top) and ground color (bottom) - vec3 sky_color = vec3(0.05, 0.05, 0.08); // Slightly blue - vec3 ground_color = vec3(0.02, 0.02, 0.02); // Dark ground - - // Blend based on normal direction (up = sky, down = ground) - float sky_factor = N.y * 0.5 + 0.5; // Remap -1..1 to 0..1 - vec3 ambient_color = mix(ground_color, sky_color, sky_factor); - - // Apply Fresnel for ambient (more realistic) - float NdotV = max(dot(N, V), 0.0); - vec3 F_ambient = fresnel_schlick_roughness(NdotV, F0, finalRoughness); - vec3 kD_ambient = (vec3(1.0) - F_ambient) * (1.0 - finalMetallic); - - ambient = ambient_color * finalAlbedo * kD_ambient * finalAO; - } - - vec3 color = ambient + Lo + ibl_contribution + finalEmissive; - - // ======================================================================== - // Exposure Control - // ======================================================================== - // Automatic exposure based on scene brightness - float luma = dot(color, vec3(0.2126, 0.7152, 0.0722)); - float auto_exposure = 1.0 / (1.0 + luma * 0.5); // Adaptive - float manual_exposure = 0.8; // Manual control - float exposure = mix(manual_exposure, auto_exposure, 0.3); // Blend - color *= exposure; - - // ======================================================================== - // Tone Mapping (Improved ACES) - // ======================================================================== - // ACES tone mapping provides better color preservation than Reinhard. - // Reference: Stephen Hill, "Aces Filmic Tone Mapping Curve" - // https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/ - - // Improved ACES with better midtones - const float a = 2.51; - const float b = 0.03; - const float c = 2.43; - const float d = 0.59; - const float e = 0.14; - color = clamp((color * (a * color + b)) / (color * (c * color + d) + e), 0.0, 1.0); - - // ======================================================================== - // Gamma Correction (Linear to sRGB) - // ======================================================================== - color = pow(color, vec3(1.0 / 2.2)); - - float ALPHA = albedoSample.a; - - COLOR = vec4(color, ALPHA); -} \ No newline at end of file diff --git a/res/shaders/opengl/default.vert b/res/shaders/opengl/default.vert deleted file mode 100644 index a17d028a1..000000000 --- a/res/shaders/opengl/default.vert +++ /dev/null @@ -1,23 +0,0 @@ -layout (location = 0) in vec3 a_position; -layout (location = 1) in vec3 a_normal; -layout (location = 2) in vec2 a_texCoord; - -// 3,4,5,6 (mat4 = 4 vec4 attributes) -layout (location = 3) in mat4 a_instance_model; // per-instance model matrix - -out vec3 POSITION; -out vec3 NORMAL; -out vec2 UV; -out vec4 LIGHT_SPACE_POSITION; - -uniform mat4 VIEW; -uniform mat4 PROJECTION; -uniform mat4 LIGHT_MATRIX; - -void main() { - POSITION = vec3(a_instance_model * vec4(a_position, 1.0)); - NORMAL = mat3(transpose(inverse(a_instance_model))) * a_normal; - UV = a_texCoord; - LIGHT_SPACE_POSITION = LIGHT_MATRIX * vec4(POSITION, 1.0); - gl_Position = PROJECTION * VIEW * vec4(POSITION, 1.0); -} \ No newline at end of file diff --git a/res/shaders/opengl/default_2d.frag b/res/shaders/opengl/default_2d.frag deleted file mode 100644 index 217733972..000000000 --- a/res/shaders/opengl/default_2d.frag +++ /dev/null @@ -1,64 +0,0 @@ -in vec2 v_texCoord; -in vec4 v_color; -in vec2 v_position; - -out vec4 COLOR; - - -uniform int DRAW_MODE; // 0=filled, 1=line, 2=text, 3=circle_filled, 4=circle_outline -uniform sampler2D TEXTURE; -uniform bool USE_TEXTURE; -uniform float LINE_THICKNESS; -uniform vec2 CIRCLE_CENTER; -uniform float CIRCLE_RADIUS; -uniform float CIRCLE_THICKNESS; // for outline - -const float SMOOTHING = 0.5; - -void main() { - vec4 finalColor = v_color; - - // Mode 0: Filled shape (triangle, rect) - if (DRAW_MODE == 0) { - if (USE_TEXTURE) { - vec4 texColor = texture(TEXTURE, v_texCoord); - finalColor = texColor * v_color; - } - } - // Mode 1: Line rendering (with thickness) - else if (DRAW_MODE == 1) { - - finalColor = v_color; - } - // Mode 2: Text rendering - else if (DRAW_MODE == 2) { - if (USE_TEXTURE) { - vec4 texColor = texture(TEXTURE, v_texCoord); - finalColor = texColor * v_color; - } - } - // Mode 3: Circle filled - else if (DRAW_MODE == 3) { - float dist = length(v_position - CIRCLE_CENTER); - float alpha = 1.0 - smoothstep(CIRCLE_RADIUS - SMOOTHING, CIRCLE_RADIUS + SMOOTHING, dist); - finalColor = vec4(v_color.rgb, v_color.a * alpha); - } - // Mode 4: Circle outline - else if (DRAW_MODE == 4) { - float dist = length(v_position - CIRCLE_CENTER); - float innerRadius = CIRCLE_RADIUS - CIRCLE_THICKNESS * 0.5; - float outerRadius = CIRCLE_RADIUS + CIRCLE_THICKNESS * 0.5; - - float outerAlpha = 1.0 - smoothstep(outerRadius - SMOOTHING, outerRadius + SMOOTHING, dist); - float innerAlpha = smoothstep(innerRadius - SMOOTHING, innerRadius + SMOOTHING, dist); - float alpha = outerAlpha * innerAlpha; - - finalColor = vec4(v_color.rgb, v_color.a * alpha); - } - - if (finalColor.a < 0.01) { - discard; - } - - COLOR = finalColor; -} diff --git a/res/shaders/opengl/default_2d.vert b/res/shaders/opengl/default_2d.vert deleted file mode 100644 index f6a9080e4..000000000 --- a/res/shaders/opengl/default_2d.vert +++ /dev/null @@ -1,16 +0,0 @@ -layout (location = 0) in vec2 a_position; -layout (location = 1) in vec2 a_texCoord; -layout (location = 2) in vec4 a_color; - -out vec2 v_texCoord; -out vec4 v_color; -out vec2 v_position; - -uniform mat4 VIEW_PROJECTION; - -void main() { - v_texCoord = a_texCoord; - v_color = a_color; - v_position = a_position; - gl_Position = VIEW_PROJECTION * vec4(a_position, 0.0, 1.0); -} diff --git a/res/shaders/opengl/shadow.frag b/res/shaders/opengl/shadow.frag deleted file mode 100644 index 4550068cc..000000000 --- a/res/shaders/opengl/shadow.frag +++ /dev/null @@ -1,4 +0,0 @@ -void main() { - // Depth is automatically written to gl_FragDepth - // No need to explicitly output anything for depth-only pass -} \ No newline at end of file diff --git a/res/shaders/opengl/shadow.vert b/res/shaders/opengl/shadow.vert deleted file mode 100644 index 396ad63ae..000000000 --- a/res/shaders/opengl/shadow.vert +++ /dev/null @@ -1,13 +0,0 @@ -layout (location = 0) in vec3 a_position; -layout (location = 1) in vec3 a_normal; -layout (location = 2) in vec2 a_texCoord; - -// 3,4,5,6 (mat4 = 4 vec4 attributes) -layout (location = 3) in mat4 a_instance_model; // per-instance model matrix - -uniform mat4 LIGHT_MATRIX; - -void main() { - vec3 WORLD_POSITION = vec3(a_instance_model * vec4(a_position, 1.0)); - gl_Position = LIGHT_MATRIX * vec4(WORLD_POSITION, 1.0); -} \ No newline at end of file diff --git a/res/shaders/opengl/skybox.frag b/res/shaders/opengl/skybox.frag deleted file mode 100644 index c8e5ae7ef..000000000 --- a/res/shaders/opengl/skybox.frag +++ /dev/null @@ -1,9 +0,0 @@ - -in vec3 UV; -out vec4 COLOR; - -uniform samplerCube TEXTURE; - -void main() { - COLOR = texture(TEXTURE, UV); -} \ No newline at end of file diff --git a/res/shaders/opengl/skybox.vert b/res/shaders/opengl/skybox.vert deleted file mode 100644 index 261ce840a..000000000 --- a/res/shaders/opengl/skybox.vert +++ /dev/null @@ -1,12 +0,0 @@ -layout(location = 0) in vec3 a_pos; - -out vec3 UV; - -uniform mat4 VIEW; -uniform mat4 PROJECTION; - -void main() { - UV = a_pos; - vec4 pos = PROJECTION * mat4(mat3(VIEW)) * vec4(a_pos, 1.0); - gl_Position = pos.xyww; -} \ No newline at end of file diff --git a/res/sounds/.gitkeep b/res/sounds/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/res/sprites/.gitkeep b/res/sprites/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/res/ui/icons/icons_64.png b/res/ui/icons/icons_64.png deleted file mode 100644 index 2a81ce364..000000000 Binary files a/res/ui/icons/icons_64.png and /dev/null differ diff --git a/runtime/main.cpp b/runtime/main.cpp deleted file mode 100644 index f222c1c6a..000000000 --- a/runtime/main.cpp +++ /dev/null @@ -1,235 +0,0 @@ -#include "core/engine.h" -#include - -const int WINDOW_WIDTH = 1280; -const int WINDOW_HEIGHT = 720; -int main(int argc, char* argv[]) { - - if (!GEngine->initialize(WINDOW_WIDTH, WINDOW_HEIGHT, "Golias Engine - Window")) { - spdlog::error("Engine initialization failed, exiting"); - return -1; - } - - - create_material("green_metal", Material{.albedo = glm::vec3(0, 1.0f, 0), .metallic = 0.5f, .roughness = 0.5f}); - - create_material("pink_emissive", Material{.albedo = glm::vec3(1.f, 0.f, 1.0), - .metallic = 1.f, - .roughness = 0.1f, - .emissive = glm::vec3(1, 0, 0), - .emissive_strength = 1.0f}); - - create_material("yellow", Material{.albedo = glm::vec3(1.0f, 1.0f, 0)}); - - create_material("cyan", Material{.albedo = glm::vec3(0.0f, 1.0f, 1.0)}); - - create_material("ground_gray", Material{.albedo = glm::vec3(0.5f, 0.5f, 0.5f), .metallic = 0.0f, .roughness = 1.0f}); - - create_material("red_rough", Material{.albedo = glm::vec3(1.f, 0.1f, 0.1f), .metallic = 0.0f, .roughness = 0.3f}); - - create_material("green_shiny", Material{.albedo = glm::vec3(0.1f, 0.8f, 0.1f), .metallic = 0.9f, .roughness = 0.1f}); - - create_material("blue_metal", Material{.albedo = glm::vec3(0.2f, 0.2f, 1.0f), .metallic = 0.3f, .roughness = 0.7f}); - - create_material("dark_ground", Material{.albedo = glm::vec3(0.1f, 0.1f, 0.1f), .metallic = 0.1f, .roughness = 0.9f}); - - create_material("random_default", Material{.albedo = glm::vec3(0.5f, 0.5f, 0.5f), .metallic = 0.2f, .roughness = 0.8f}); - - auto cam3d = create_camera_3d_entity(nullptr, glm::vec3(0, 2, 20), glm::vec3(-0.4f, 0, 0)); - - cam3d.add_component(); - - auto cam2d = create_camera_2d_entity(nullptr, glm::vec2(0, 0), 0.0f, 1.0f); - - // ============================================================================= - // 2D Test Entities (ECS-based) - // ============================================================================= - - - float margin_x = WINDOW_WIDTH * 0.05f; - float margin_y = WINDOW_HEIGHT * 0.05f; - - // Red filled rectangle (top-left) - create_rectangle_2d_entity("RedRect", - glm::vec2(margin_x, margin_y), - glm::vec2(WINDOW_WIDTH * 0.15f, WINDOW_HEIGHT * 0.2f), - glm::vec4(1, 0, 0, 1), - true); - - // Text labels - auto text = create_label_2d_entity("HelloWorld", - "Hello World!", - glm::vec2(margin_x + WINDOW_WIDTH * 0.2f, margin_y), - glm::vec4(1, 0, 0, 1), - 1.0f); - - - create_label_2d_entity("OlaMundo", - "Olá mundo", - glm::vec2(margin_x, margin_y + WINDOW_HEIGHT * 0.25f), - glm::vec4(1, 1, 1, 1), - 1.0f); - - create_label_2d_entity("RussianEmoji", - "Hello russian, мир! 🎮", - glm::vec2(margin_x + WINDOW_WIDTH * 0.08f, margin_y + WINDOW_HEIGHT * 0.15f), - glm::vec4(1, 1, 1, 1), - 1.0f); - - create_label_2d_entity("Emojis", - "😀🎮🚀💎✨", - glm::vec2(margin_x, margin_y + WINDOW_HEIGHT * 0.06f), - glm::vec4(1, 1, 1, 1), - 1.0f); - - // Green outlined rectangle (right-center) - create_rectangle_2d_entity("GreenRectOutline", - glm::vec2(WINDOW_WIDTH * 0.65f, WINDOW_HEIGHT * 0.3f), - glm::vec2(WINDOW_WIDTH * 0.25f, WINDOW_HEIGHT * 0.2f), - glm::vec4(0, 1, 0, 1), - false); - - // Blue diagonal line - create_line_2d_entity("BlueLine", - glm::vec2(margin_x, WINDOW_HEIGHT * 0.5f), - glm::vec2(WINDOW_WIDTH * 0.35f, WINDOW_HEIGHT * 0.65f), - glm::vec4(0, 0, 1, 1), - 3.0f); - - // Yellow filled circle (center-left) - create_circle_2d_entity("YellowCircle", - glm::vec2(WINDOW_WIDTH * 0.25f, WINDOW_HEIGHT * 0.5f), - WINDOW_HEIGHT * 0.1f, - glm::vec4(1, 1, 0, 1), - true, // filled - 1.0f, // thickness (not used when filled) - 32); - - // Magenta circle outline (center-right) - create_circle_2d_entity("MagentaCircleOutline", - glm::vec2(WINDOW_WIDTH * 0.65f, WINDOW_HEIGHT * 0.5f), - WINDOW_WIDTH * 0.1f, - glm::vec4(1, 0, 1, 1), - false, // not filled (outline) - 5.0f, // thickness - 32); - - // Cyan filled triangle (bottom-left) - create_triangle_2d_entity("CyanTriangle", - glm::vec2(margin_x, WINDOW_HEIGHT * 0.85f), - glm::vec2(margin_x + WINDOW_WIDTH * 0.1f, WINDOW_HEIGHT * 0.85f), - glm::vec2(margin_x + WINDOW_WIDTH * 0.05f, WINDOW_HEIGHT * 0.7f), - glm::vec4(0, 1, 1, 1), - true); - - // Orange outlined triangle (bottom-center) - create_triangle_2d_entity("OrangeTriangleOutline", - glm::vec2(margin_x + WINDOW_WIDTH * 0.15f, WINDOW_HEIGHT * 0.85f), - glm::vec2(margin_x + WINDOW_WIDTH * 0.25f, WINDOW_HEIGHT * 0.85f), - glm::vec2(margin_x + WINDOW_WIDTH * 0.2f, WINDOW_HEIGHT * 0.7f), - glm::vec4(1, 0.5, 0, 1), - false); - - // Sprite (bottom-right) - create_sprite_2d_entity("IconSprite", - "res/icon.png", - glm::vec2(WINDOW_WIDTH * 0.75f, WINDOW_HEIGHT * 0.7f), - glm::vec2(WINDOW_WIDTH * 0.1f, WINDOW_HEIGHT * 0.15f), - glm::vec4(1.0f)); - - // ============================================================================= - // 3D Test Entities - // ============================================================================= - - auto sunlight = create_directional_light_3d_entity("SunLight", glm::vec3(1.0f, -2.5f, 1.0f), // direction - glm::vec3(1.0f, 0.95f, 0.8f), // warm sunlight color - 1.0f, // intensity - true); // shadow far - - - // create_directional_light_3d_entity(glm::vec3(-0.3f, -1.0f, 0.2f), // direction - // glm::vec3(0.6f, 0.75f, 1.0f), // cool fill light color - // 0.8f, // lower intensity - // false); // no shadows - - - create_spot_light_3d_entity("RedLight", glm::vec3(-10, 5, -10), // position - glm::vec3(1, -1, 1), // direction - glm::vec3(1.0f, 0.0f, 0.0f), - 10.0f, // inner cutoff - 20.0f, // outer cutoff - 30.0f); // intensity - - - create_spot_light_3d_entity("PinkLight", glm::vec3(0, 5, 0), // position - glm::vec3(0, -1, 0), // direction - glm::vec3(1.0f, 0.0f, 1.0f), - 20.0f, // inner cutoff - 30.0f, // outer cutoff - 10.0f); // intensity - - - create_spot_light_3d_entity("GreenLight", glm::vec3(10, 5, 10), // position - glm::vec3(-1, -1, -1), // direction - glm::vec3(0.3f, 1.0f, 0.3f), - 15.0f, // inner cutoff - 25.0f, // outer cutoff - 50.0f); // intensity - - create_model_3d_entity("dmg_helmet", "res://sprites/obj/DamagedHelmet.glb", BlendMode::OPAQUE, glm::vec3(10, 1, -5)); - - create_model_3d_entity("nagon", "res://sprites/obj/nagonford/Nagonford_Animated.glb", BlendMode::OPAQUE, glm::vec3(0, 0, 0)); - - - // create_model_3d_entity("Sponza", "res://sprites/obj/sponza/Sponza.glb", BlendMode::OPAQUE, glm::vec3(0, -2, 0), glm::vec3(0), - // glm::vec3(0.1f)); - - create_mesh_3d_entity("Cylinder", "res://models/cylinder.obj", glm::vec3(0, 0, -15), glm::vec3(0), glm::vec3(1.0f), "green_metal"); - - create_mesh_3d_entity("Torus", "res://models/torus.obj", glm::vec3(0, 0, 5), glm::vec3(0), glm::vec3(1.0f), "pink_emissive"); - - create_mesh_3d_entity("Cone", "res://models/cone.obj", glm::vec3(0, 0, 15), glm::vec3(0), glm::vec3(1.0f), "yellow"); - - create_mesh_3d_entity("BlenderMonkey", "res://models/blender_monkey.obj", glm::vec3(-10, 0, 10), glm::vec3(0), glm::vec3(1.0f), "cyan"); - - create_mesh_3d_entity("Plane", "res://models/plane.obj", glm::vec3(0, -0.5, 0), glm::vec3(0), glm::vec3(10.0f), "ground_gray"); - - - std::mt19937 rng(std::random_device{}()); - std::uniform_real_distribution dist(-30.0f, 30.0f); - - // Create a 3x3x3 cube of windows with transparency - float spacing = 4.0f; // Space between windows - float start_offset = -(spacing * 2.0f) / 2.0f; // Center the cube - - int window_index = 0; - for (int x = 0; x < 3; ++x) { - for (int y = 0; y < 3; ++y) { - for (int z = 0; z < 3; ++z) { - float pos_x = start_offset + (x * spacing); - float pos_y = 1.0f + start_offset + (y * spacing); - float pos_z = start_offset + (z * spacing); - - std::string name = "window_" + std::to_string(window_index); - window_index++; - - create_model_3d_entity(name.c_str(), "res://sprites/obj/window/glassWindow.obj", BlendMode::TRANSPARENT, - glm::vec3(pos_x, pos_y, pos_z), glm::vec3(0), glm::vec3(1.0f)); - } - } - } - - - create_mesh_3d_entity("Red Cube", "res://models/cube.obj", glm::vec3(3, 0, 0), glm::vec3(0), glm::vec3(1.5f), "red_rough"); - - create_mesh_3d_entity("Metallic Sphere", "res://models/sphere.obj", glm::vec3(-3, 1, 0), glm::vec3(0), glm::vec3(1.5f), "green_shiny"); - - create_mesh_3d_entity("SmallCube", "res://models/cube.obj", glm::vec3(-3, 5, 10), glm::vec3(0), glm::vec3(0.5f), "blue_metal"); - - create_mesh_3d_entity("Ground", "res://models/cube.obj", glm::vec3(0, -5, 0), glm::vec3(0), glm::vec3(1000.0f, 0.1f, 1000.0f), - "dark_ground"); - - GEngine->run(); - - return 0; -} diff --git a/runtime/CMakeLists.txt b/sandbox/CMakeLists.txt similarity index 98% rename from runtime/CMakeLists.txt rename to sandbox/CMakeLists.txt index 821a677fa..f2c344aaa 100644 --- a/runtime/CMakeLists.txt +++ b/sandbox/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.18) -project(golias_runtime) +project(golias_sandbox) set(CMAKE_CXX_STANDARD 20) set(CMAKE_C_STANDARD 99) @@ -165,7 +165,7 @@ if (WIN32) ${CMAKE_SOURCE_DIR}/res ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/res) target_link_libraries(${PROJECT_NAME} PUBLIC opengl32 glu32) - + target_compile_options(${PROJECT_NAME} PRIVATE /bigobj) add_compile_options(-fsanitize=address) add_link_options(-fsanitize=address) endif () diff --git a/sandbox/main.cpp b/sandbox/main.cpp new file mode 100644 index 000000000..537397402 --- /dev/null +++ b/sandbox/main.cpp @@ -0,0 +1,128 @@ +#include "servers/rendering/rendering_canvas.h" +#include "stdafx.h" +#include + + +enum class RenderingDeviceType { COMPATIBILITY, FORWARD_PLUS }; + + +int main(int argc, char* argv[]) { + +#if defined(NDEBUG) + constexpr auto LOG_LEVEL = spdlog::level::info; +#else + constexpr auto LOG_LEVEL = spdlog::level::debug; +#endif + +#if defined(__ANDROID__) + auto android_sink = std::make_shared("GoliasEngine"); + std::vector sinks{android_sink}; +#else + auto console_sink = std::make_shared(); + auto file_sink = std::make_shared("logs/output.log", true); + std::vector sinks{console_sink, file_sink}; +#endif + + auto logger = std::make_shared("GoliasEngine", sinks.begin(), sinks.end()); + logger->set_level(LOG_LEVEL); + logger->flush_on(LOG_LEVEL); + spdlog::set_default_logger(logger); + + if (!SDL_Init(SDL_INIT_VIDEO)) { + SDL_Log("Failed to initialize SDL"); + return -1; + } + + SDL_Window* window = SDL_CreateWindow("Golias Engine", 1280, 720, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); + + if (!window) { + SDL_Log("Failed to create SDL window"); + return -1; + } + + TTF_Init(); + RenderingDeviceType device_type = RenderingDeviceType::FORWARD_PLUS; + + std::unique_ptr rd = nullptr; + if (device_type == RenderingDeviceType::COMPATIBILITY) { + rd = std::make_unique(); + spdlog::info("Using Compatibility Rendering Device"); + } else if (device_type == RenderingDeviceType::FORWARD_PLUS) { + rd = std::make_unique(); + spdlog::info("Using Forward+ Rendering Device"); + } else { + spdlog::error("Unknown Rendering Device Type"); + return -1; + } + + + if (!rd->initialize(window)) { + SDL_Log("Failed to initialize Rendering Device"); + return -1; + } + + RenderingCanvas renderer(rd.get()); + int drawable_w, drawable_h; + SDL_GetWindowSizeInPixels(window, &drawable_w, &drawable_h); + renderer.initialize(drawable_w, drawable_h); + renderer.set_viewport_size(drawable_w, drawable_h); + renderer.set_scale_mode(ScaleMode::EXPAND); + + + Texture icon_tex = renderer.load_texture_from_file("res/icon.png"); + Texture monsters_tex = renderer.load_texture_from_file("res/test/sprites/monsters.png"); + + TextureDescription pixel_desc; + pixel_desc.min_filter = TextureFilter::NEAREST; + pixel_desc.mag_filter = TextureFilter::NEAREST; + pixel_desc.wrap_u = TextureWrap::CLAMP_TO_EDGE; + pixel_desc.wrap_v = TextureWrap::CLAMP_TO_EDGE; + Texture pixel_tex = renderer.load_texture_from_file("res/test/sprites/monsters.png", pixel_desc); + + /// NOTE: Shader loading/compilation is not yet implemented for SDL_GPU + // RID wavy_shader = renderer.load_shader_from_file("res/test/shaders/wave.glsl"); + // RID rainbow_shader = renderer.load_shader_from_file("res/test/shaders/rainbow.glsl"); + // RID retro_shader = renderer.load_shader_from_file("res/test/shaders/retro.glsl"); + // + // auto wavy_material = CanvasMaterial(wavy_shader).set_shader_param("amplitude", 10.0f).set_shader_param("frequency", 5.0f); + // auto rainbow_material = CanvasMaterial(rainbow_shader).set_shader_param("speed", 2.0f); + // auto retro_material = CanvasMaterial(retro_shader).set_shader_param("pixel_size", 16.0f); + + Font mine_font = renderer.load_font_from_file("res/test/fonts/Minecraft.ttf", 24.0f); + + + bool running = true; + SDL_Event event; + while (running) { + while (SDL_PollEvent(&event)) { + if (event.type == SDL_EVENT_QUIT) { + running = false; + } + } + + renderer.begin(); + + renderer.draw_rect(100, 50, 30, 50, Color::WHITE); + renderer.draw_circle(300, 600, 75, Color::YELLOW); + renderer.draw_triangle(200, 400, 250, 300, 300, 400, Color::BLUE); + renderer.draw_arc(600, 400, 100, 0.0f, 3.14f, Color::RED, 32); + renderer.draw_circle_outlined(800, 200, 50, Color::MAGENTA, 3.0f, 32); + renderer.draw_line(400, 400, 600, 450, Color::GREEN, 5.0f); + renderer.draw_texture(monsters_tex.rid, 500, 50, 128, 128); + renderer.draw_texture(pixel_tex.rid, 650, 50, 128, 128); + renderer.draw_text(mine_font, 50, 200, Color::WHITE, "We Love Minecraft ❤️"); + + renderer.draw_text(150, 50, Color::WHITE, "Hello, Golias Engine!"); + + renderer.end(); + + renderer.present(); + + SDL_Delay(16); + } + + renderer.shutdown(); + TTF_Quit(); + SDL_Quit(); + return 0; +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 000000000..f5ed0c911 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,43 @@ +cmake_minimum_required(VERSION 3.20) + +project(golias_tests) + +include(FetchContent) +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG v1.14.0 +) + +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) + + +add_executable(golias_tests + test_strings.cpp + test_math.cpp +) + +target_link_libraries(golias_tests PRIVATE + GTest::gtest_main + engine +) + +target_include_directories(golias_tests PRIVATE + ${CMAKE_SOURCE_DIR}/engine +) + +target_compile_definitions(golias_tests PRIVATE + BUILD_GOLIAS_TESTS +) + + +add_custom_command(TARGET golias_tests POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_SOURCE_DIR}/res + $/res +) + + +include(GoogleTest) +gtest_discover_tests(golias_tests) diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 000000000..e3feec6f8 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,15 @@ +# Testing Guide for Golias Engine + +## Building and Running Tests + +### Build Tests +```bash +cmake --build --preset tests + +cmake --build --preset tests-build +``` + +### Run All Tests +```bash +ctest --test-dir build/default/ --gtest_color=1 +``` diff --git a/tests/example_canvas2d.cpp b/tests/example_canvas2d.cpp new file mode 100644 index 000000000..a23fce50b --- /dev/null +++ b/tests/example_canvas2d.cpp @@ -0,0 +1,303 @@ +#include "servers/rendering/rendering_canvas.h" +#include "stdafx.h" +#include + + +namespace golias { + + + class Camera2D { + public: + glm::vec2 position; + float rotation; + float zoom; + + Camera2D() : position(0.0f, 0.0f), rotation(0.0f), zoom(1.0f) { + } + + Camera2D(float x, float y, float zoom = 1.0f, float rotation = 0.0f) : position(x, y), rotation(rotation), zoom(zoom) { + } + + glm::mat4 get_view_matrix() const { + glm::mat4 view = glm::mat4(1.0f); + view = glm::translate(view, glm::vec3(-position.x, -position.y, 0.0f)); + view = glm::rotate(view, -rotation, glm::vec3(0.0f, 0.0f, 1.0f)); + view = glm::scale(view, glm::vec3(zoom, zoom, 1.0f)); + return view; + } + + glm::mat4 get_view_projection_matrix(float viewport_width, float viewport_height) const { + glm::mat4 projection = glm::ortho(0.0f, viewport_width, viewport_height, 0.0f, -1.0f, 1.0f); + return projection * get_view_matrix(); + } + + glm::vec2 screen_to_world(const glm::vec2& screen_pos, float viewport_width, float viewport_height) const { + glm::mat4 inv_vp = glm::inverse(get_view_projection_matrix(viewport_width, viewport_height)); + glm::vec4 world_pos = inv_vp * glm::vec4(screen_pos.x, screen_pos.y, 0.0f, 1.0f); + return glm::vec2(world_pos.x, world_pos.y); + } + + glm::vec2 world_to_screen(const glm::vec2& world_pos, float viewport_width, float viewport_height) const { + glm::mat4 vp = get_view_projection_matrix(viewport_width, viewport_height); + glm::vec4 screen_pos = vp * glm::vec4(world_pos.x, world_pos.y, 0.0f, 1.0f); + return glm::vec2(screen_pos.x, screen_pos.y); + } + + void move(float dx, float dy) { + position.x += dx; + position.y += dy; + } + + void look_at(float x, float y, float viewport_width, float viewport_height) { + position.x = x - viewport_width / (2.0f * zoom); + position.y = y - viewport_height / (2.0f * zoom); + } + }; + + +} // namespace golias + + +int main(int argc, char* argv[]) { + +#if defined(NDEBUG) + constexpr auto LOG_LEVEL = spdlog::level::info; +#else + constexpr auto LOG_LEVEL = spdlog::level::debug; +#endif + +#if defined(__ANDROID__) + auto android_sink = std::make_shared("GoliasEngine"); + std::vector sinks{android_sink}; +#else + auto console_sink = std::make_shared(); + auto file_sink = std::make_shared("logs/output.log", true); + std::vector sinks{console_sink, file_sink}; +#endif + + auto logger = std::make_shared("GoliasEngine", sinks.begin(), sinks.end()); + +#if defined(NDEBUG) + logger->set_level(spdlog::level::info); +#else + logger->set_level(spdlog::level::debug); +#endif + + logger->set_level(LOG_LEVEL); + logger->flush_on(LOG_LEVEL); + + spdlog::set_default_logger(logger); + + + SDL_Init(SDL_INIT_VIDEO); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + + SDL_Window* window = SDL_CreateWindow("Golias Engine", 1280, 720, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); + SDL_GLContext gl_context = SDL_GL_CreateContext(window); + + if (!gladLoadGLES2Loader(reinterpret_cast(SDL_GL_GetProcAddress))) { + spdlog::error("Failed to initialize OpenGL/ES Loader (GLAD)"); + return -1; + } + + SDL_GL_SetSwapInterval(0); + + if (!TTF_Init()) { + spdlog::error("Failed to initialize SDL_TTF: {}", SDL_GetError()); + return -1; + } + + golias::RenderingDeviceGLES3 rd; + rd.initialize(); + + + RenderingCanvas renderer(&rd); + renderer.initialize(1280, 720); + renderer.set_viewport_size(1280, 720); + renderer.set_scale_mode(ScaleMode::KEEP); + + RID my_texture = renderer.load_texture_from_file("res/icon.png"); + RID my_texture2 = renderer.load_texture_from_file("res/test/sprites/monsters.png"); + + TextureDescription pixel_art_desc; + pixel_art_desc.min_filter = TextureFilter::NEAREST; + pixel_art_desc.mag_filter = TextureFilter::NEAREST; + pixel_art_desc.wrap_u = TextureWrap::CLAMP_TO_EDGE; + pixel_art_desc.wrap_v = TextureWrap::CLAMP_TO_EDGE; + RID pixel_texture = renderer.load_texture_from_file("res/test/sprites/monsters.png", pixel_art_desc); + + TextureDescription smooth_desc; + smooth_desc.min_filter = TextureFilter::LINEAR; + smooth_desc.mag_filter = TextureFilter::LINEAR; + smooth_desc.wrap_u = TextureWrap::REPEAT; + smooth_desc.wrap_v = TextureWrap::REPEAT; + RID smooth_texture = renderer.load_texture_from_file("res/icon.png", smooth_desc); + + RID wavy_shader = renderer.create_shader_from_file("res/test/shaders/wave.glsl"); + RID rainbow_shader = renderer.create_shader_from_file("res/test/shaders/rainbow.glsl"); + RID retro_shader = renderer.create_shader_from_file("res/test/shaders/retro.glsl"); + + golias::Camera2D camera(0.0f, 0.0f, 1.0f); + bool use_camera = false; // Toggle with C key + + bool running = true; + SDL_Event event; + float time = 0.0f; + + TTF_Font* mine = renderer.load_font_from_file("res/test/fonts/Minecraft.ttf", 24.0f); + TTF_Font* font = renderer.load_font_from_file("res/test/fonts/Default.ttf", 24.0f); + + while (running) { + while (SDL_PollEvent(&event)) { + if (event.type == SDL_EVENT_QUIT || (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_ESCAPE)) { + running = false; + } + + if (event.type == SDL_EVENT_WINDOW_RESIZED) { + int new_width = event.window.data1; + int new_height = event.window.data2; + renderer.set_viewport_size(new_width, new_height); + } + + if (event.type == SDL_EVENT_KEY_DOWN) { + if (event.key.key == SDLK_1) { + renderer.set_scale_mode(ScaleMode::NONE); + printf("Scale Mode: NONE (no scaling, centered)\n"); + } else if (event.key.key == SDLK_2) { + renderer.set_scale_mode(ScaleMode::KEEP); + printf("Scale Mode: KEEP (aspect ratio preserved)\n"); + } else if (event.key.key == SDLK_3) { + renderer.set_scale_mode(ScaleMode::EXPAND); + printf("Scale Mode: EXPAND (fill window, may stretch)\n"); + } else if (event.key.key == SDLK_C) { + use_camera = !use_camera; + printf("Camera %s\n", use_camera ? "ENABLED" : "DISABLED"); + } + } + } + + + const bool* keys = SDL_GetKeyboardState(nullptr); + if (use_camera) { + float camera_speed = 200.0f * 0.016f; + if (keys[SDL_SCANCODE_LEFT]) { + camera.move(-camera_speed, 0); + } + if (keys[SDL_SCANCODE_RIGHT]) { + camera.move(camera_speed, 0); + } + if (keys[SDL_SCANCODE_UP]) { + camera.move(0, -camera_speed); + } + if (keys[SDL_SCANCODE_DOWN]) { + camera.move(0, camera_speed); + } + + if (keys[SDL_SCANCODE_EQUALS] || keys[SDL_SCANCODE_KP_PLUS]) { + camera.update_zoom(0.5f * 0.016f); + } + if (keys[SDL_SCANCODE_MINUS] || keys[SDL_SCANCODE_KP_MINUS]) { + camera.update_zoom(-0.5f * 0.016f); + } + + if (keys[SDL_SCANCODE_R]) { + camera.rotation += 1.0f * 0.016f; + } + if (keys[SDL_SCANCODE_T]) { + camera.rotation -= 1.0f * 0.016f; + } + } + + time += 0.016f; + + renderer.begin(Color(0.1f, 0.1f, 0.15f, 1.0f)); + + + if (use_camera) { + renderer.set_camera(camera.get_view_projection_matrix(1280, 720)); + } else { + renderer.reset_camera(); + } + + renderer.draw_rect(50, 50, 150, 100, Color::RED); + + renderer.draw_circle(400, 150, 60, Color::GREEN); + + renderer.draw_triangle(550, 50, 650, 50, 600, 150, Color::BLUE); + + renderer.draw_line(50, 250, 200, 250, Color::YELLOW, 3.0f); + renderer.draw_line(50, 280, 200, 320, Color::CYAN, 2.0f); + + renderer.draw_rect_outlined(250, 250, 120, 80, Color::MAGENTA, 2.0f); + renderer.draw_circle(450, 300, 50, Color::YELLOW); + + float rotation = time * 2.0f; + renderer.draw_rect(550, 250, 100, 100, Color::CYAN, rotation); + + renderer.draw_texture(my_texture2, 50, 400, 150, 150); + + renderer.draw_texture(my_texture, 250, 400, 120, 120, Color::WHITE, time * 3.0f); + + renderer.draw_texture(my_texture, 50, 50, 100, 100); + + renderer.draw_texture(pixel_texture, 900, 50, 128, 128); + renderer.draw_text(font, 900, 15, Color::WHITE, "NEAREST Filter"); + + + if (wavy_shader != INVALID_RID) { + renderer.draw_custom(wavy_shader, 200, 50, 100, 100, my_texture, Color::WHITE); + } + + if (rainbow_shader != INVALID_RID) { + renderer.draw_custom(rainbow_shader, 200, 170, 100, 100, INVALID_RID, Color::WHITE); + } + + if (retro_shader != INVALID_RID) { + renderer.draw_custom(retro_shader, 200, 290, 100, 100, my_texture, Color::WHITE); + } + + renderer.draw_texture_ex(700, 50, 80, 80, my_texture); + + renderer.draw_texture_ex(800, 50, 80, 80, my_texture, {0, 0, 0, 0}, Color::WHITE, 0.0f, true, false); + + renderer.draw_texture_ex(700, 150, 80, 80, my_texture, {0, 0, 0, 0}, Color::WHITE, 0.0f, false, true); + + renderer.draw_texture_ex(800, 150, 80, 80, my_texture, {0, 0, 0, 0}, Color::WHITE, 0.0f, true, true); + + renderer.draw_texture_ex(750, 270, 80, 80, my_texture, {0, 0, 0, 0}, Color::WHITE, time * 2.0f, false, false, wavy_shader); + + + renderer.draw_texture_ex(700, 380, 64, 64, pixel_texture, {0, 0, 32, 32}, Color::WHITE); + + renderer.draw_text(mine, 400, 50, Color::WHITE, "Hello World! 👋 Welcome!"); + renderer.draw_text(font, 400, 80, Color::WHITE, "Frame: {} 🎮", static_cast(time * 60)); + renderer.draw_text(font, 400, 110, Color::WHITE, "Position: ({}, {}) 📍", 123, 456); + renderer.draw_text(font, 400, 140, Color::WHITE, "Time: {:.2f}s ⏱️", time); + renderer.draw_text(font, 400, 170, Color::WHITE, "FPS: {} 🚀", 60); + renderer.draw_text(font, 400, 200, Color::WHITE, "Mixed: ABC 123 😀 🎉 🔥 ⭐ XYZ"); + + + renderer.draw_text(font, 400, 240, Color::GREEN, "Health: {}", 100); + renderer.draw_text(font, 400, 270, Color::RED, "Score: 999,999 🏆"); + + renderer.end(); + + SDL_GL_SwapWindow(window); + + SDL_Delay(16); + } + + + renderer.shutdown(); + rd.shutdown(); + + TTF_Quit(); + + SDL_GL_DestroyContext(gl_context); + SDL_DestroyWindow(window); + SDL_Quit(); + + return 0; +} diff --git a/tests/example_minimal_2d.cpp b/tests/example_minimal_2d.cpp deleted file mode 100644 index 097fc34c2..000000000 --- a/tests/example_minimal_2d.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "core/engine.h" -#include - - -#define WINDOW_W 1280 -#define WINDOW_H 720 - -int main(int argc, char* argv[]) { - - if (!GEngine->initialize(WINDOW_W, WINDOW_H)) { - return SDL_APP_FAILURE; - } - - - auto ui_icons = GEngine->get_renderer()->load_texture("ui_icons", "res://ui/icons/icons_64.png"); - - auto& world = GEngine->get_world(); - - - auto scene1 = world.entity("MenuScene").add().add(); - auto scene2 = world.entity("GameScene").add(); - - auto player = world.entity("Player") - .set({{100, 100}, {1, 1}, 50, 1}) - .set({ShapeType::RECTANGLE, {0, 1, 0, 1}, true, {50, 50}}) - .set