From e0e2e4f325a93f598df2ebd1c10e362317bbcdc7 Mon Sep 17 00:00:00 2001 From: vsaint1 Date: Sun, 23 Nov 2025 22:25:33 -0300 Subject: [PATCH 01/71] good bye --- engine/CMakeLists.txt | 2 - engine/private/core/api/engine_api.cpp | 221 --- engine/private/core/component/comp_system.cpp | 187 --- engine/private/core/component/components.cpp | 266 --- engine/private/core/component/game_object.cpp | 39 - engine/private/core/engine.cpp | 570 ------- engine/private/core/io/assimp_io.cpp | 122 -- engine/private/core/io/file_system.cpp | 221 --- engine/private/core/io/ma_io.cpp | 94 -- engine/private/core/renderer/base_struct.cpp | 88 - .../core/renderer/opengl/ogl_renderer.cpp | 1423 ----------------- .../core/renderer/opengl/ogl_struct.cpp | 630 -------- engine/private/core/renderer/renderer.cpp | 10 - engine/private/core/system/timer.cpp | 26 - engine/private/core/utility/obj_loader.cpp | 241 --- .../private/core/utility/project_config.cpp | 375 ----- engine/private/stdafx.cpp | 10 - engine/public/core/api/engine_api.h | 170 -- engine/public/core/component/comp_system.h | 15 - engine/public/core/component/components.h | 438 ----- engine/public/core/component/game_object.h | 77 - engine/public/core/engine.h | 75 - engine/public/core/io/assimp_io.h | 50 - engine/public/core/io/file_system.h | 174 -- engine/public/core/io/ma_io.h | 14 - engine/public/core/renderer/base_struct.h | 440 ----- .../core/renderer/opengl/ogl_renderer.h | 121 -- .../public/core/renderer/opengl/ogl_struct.h | 199 --- engine/public/core/renderer/renderer.h | 159 -- engine/public/core/system/timer.h | 28 - engine/public/core/utility/obj_loader.h | 33 - engine/public/core/utility/project_config.h | 218 --- engine/public/definitions.h | 43 - engine/public/stdafx.h | 72 - runtime/main.cpp | 516 +++--- 35 files changed, 325 insertions(+), 7042 deletions(-) delete mode 100644 engine/private/core/api/engine_api.cpp delete mode 100644 engine/private/core/component/comp_system.cpp delete mode 100644 engine/private/core/component/components.cpp delete mode 100644 engine/private/core/component/game_object.cpp delete mode 100644 engine/private/core/engine.cpp delete mode 100644 engine/private/core/io/assimp_io.cpp delete mode 100644 engine/private/core/io/file_system.cpp delete mode 100644 engine/private/core/io/ma_io.cpp delete mode 100644 engine/private/core/renderer/base_struct.cpp delete mode 100644 engine/private/core/renderer/opengl/ogl_renderer.cpp delete mode 100644 engine/private/core/renderer/opengl/ogl_struct.cpp delete mode 100644 engine/private/core/renderer/renderer.cpp delete mode 100644 engine/private/core/system/timer.cpp delete mode 100644 engine/private/core/utility/obj_loader.cpp delete mode 100644 engine/private/core/utility/project_config.cpp delete mode 100644 engine/private/stdafx.cpp delete mode 100644 engine/public/core/api/engine_api.h delete mode 100644 engine/public/core/component/comp_system.h delete mode 100644 engine/public/core/component/components.h delete mode 100644 engine/public/core/component/game_object.h delete mode 100644 engine/public/core/engine.h delete mode 100644 engine/public/core/io/assimp_io.h delete mode 100644 engine/public/core/io/file_system.h delete mode 100644 engine/public/core/io/ma_io.h delete mode 100644 engine/public/core/renderer/base_struct.h delete mode 100644 engine/public/core/renderer/opengl/ogl_renderer.h delete mode 100644 engine/public/core/renderer/opengl/ogl_struct.h delete mode 100644 engine/public/core/renderer/renderer.h delete mode 100644 engine/public/core/system/timer.h delete mode 100644 engine/public/core/utility/obj_loader.h delete mode 100644 engine/public/core/utility/project_config.h delete mode 100644 engine/public/definitions.h delete mode 100644 engine/public/stdafx.h 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/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/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/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/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/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 deleted file mode 100644 index 0eb38ad8d..000000000 --- a/engine/public/definitions.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once -#define ENGINE_NAME "GOLIAS_ENGINE" -#define ENGINE_VERSION_STR "0.0.5" - -#define ENGINE_DEFAULT_FOLDER_NAME "Golias Engine" -#define ENGINE_PACKAGE_NAME "com.golias.engine.app" - -#define ALBEDO_TEXTURE_UNIT 0 -#define SPECULAR_TEXTURE_UNIT 1 -#define METALLIC_TEXTURE_UNIT 2 -#define ROUGHNESS_TEXTURE_UNIT 3 -#define NORMAL_MAP_TEXTURE_UNIT 4 -#define AMBIENT_OCCLUSION_TEXTURE_UNIT 5 -#define EMISSIVE_TEXTURE_UNIT 6 -#define SHADOW_TEXTURE_UNIT 7 -#define ENVIRONMENT_TEXTURE_UNIT 8 - -#define MAX_VERTEX_MEMORY (512 * 1024) -#define MAX_ELEMENT_MEMORY (128 * 1024) - -#if !defined(NDEBUG) - #if defined(_MSC_VER) - #define GOLIAS_ASSERT_BREAK() __debugbreak() - #elif defined(__clang__) || defined(__GNUC__) - #define GOLIAS_ASSERT_BREAK() __builtin_trap() - #else - #define GOLIAS_ASSERT_BREAK() std::abort() - #endif -#else - #define GOLIAS_ASSERT_BREAK() ((void)0) -#endif - -/*! -* @defgroup Components -* @defgroup Systems -* @defgroup Core -* @defgroup Tags -* @defgroup FileSystem -* @defgroup Configuration -* @defgroup Rendering -* @defgroup Time -* @defgroup Logging -*/ diff --git a/engine/public/stdafx.h b/engine/public/stdafx.h deleted file mode 100644 index 5452e5dfb..000000000 --- a/engine/public/stdafx.h +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once -#define WIN32_LEAN_AND_MEAN -#define NOMINMAX -#include "definitions.h" -#include -#include -#include - -// #include -#include - -#define FLECS_CUSTOM_BUILD -#define FLECS_SYSTEM -#define FLECS_NO_LOG -#define FLECS_META -#define FLECS_CPP -#define FLECS_PIPELINE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "json.hpp" -#include -#include -#include -#include -#include -#include - -#define GLM_ENABLE_EXPERIMENTAL -#include -#include - -#include - -#if defined(SDL_PLATFORM_EMSCRIPTEN) - #include -#endif - -#include -#include -#include -#include -#include -#include - -using Json = nlohmann::json; - -#if __ANDROID__ -const std::filesystem::path BASE_PATH = ""; - #define ASSETS_PATH std::string("") -#elif __APPLE__ -const std::filesystem::path BASE_PATH = SDL_GetBasePath(); - #define ASSETS_PATH (BASE_PATH / "res/").string() -#else -const std::filesystem::path BASE_PATH = SDL_GetBasePath(); - #define ASSETS_PATH std::string("res/") -#endif - -#include "nuklear.h" -#include "nuklear_sdl3_ogl3.h" -#include diff --git a/runtime/main.cpp b/runtime/main.cpp index f222c1c6a..e8bad5dde 100644 --- a/runtime/main.cpp +++ b/runtime/main.cpp @@ -1,235 +1,369 @@ -#include "core/engine.h" +#include "stdafx.h" +#include "scene/2d/renderer_canvas.h" #include -const int WINDOW_WIDTH = 1280; -const int WINDOW_HEIGHT = 720; + +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 (!GEngine->initialize(WINDOW_WIDTH, WINDOW_HEIGHT, "Golias Engine - Window")) { - spdlog::error("Engine initialization failed, exiting"); +#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(1); + + if (!TTF_Init()) { + spdlog::error("Failed to initialize SDL_TTF: {}", SDL_GetError()); + return -1; + } + + TTF_Font* font = TTF_OpenFont("res/fonts/Default.ttf", 24.0f); + if (!font) { + spdlog::error("Failed to load Font: {}", SDL_GetError()); + TTF_Quit(); return -1; } + TTF_Font* mine = TTF_OpenFont("res/fonts/Minecraft.ttf", 24.0f); - create_material("green_metal", Material{.albedo = glm::vec3(0, 1.0f, 0), .metallic = 0.5f, .roughness = 0.5f}); + TTF_Font* emoji_font = TTF_OpenFont("res/fonts/Twemoji.ttf", 24.0f); - 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}); + if (!emoji_font) { + spdlog::warn("Failed to load emoji font, emojis may not display: {}", SDL_GetError()); + }else { + TTF_AddFallbackFont(mine, emoji_font); + TTF_AddFallbackFont(font, emoji_font); + } - 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}); + golias::RenderingDeviceGLES3 rd; + rd.initialize(); - 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}); + golias::Renderer2D renderer(&rd); + renderer.initialize(1280, 720); + renderer.set_viewport_size(1280, 720); + renderer.set_scale_mode(golias::ScaleMode::KEEP); - 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); + golias::RID my_texture = renderer.load_texture_from_file("res/icon.png"); + golias::RID my_texture2 = renderer.load_texture_from_file("res/monsters.png"); - // 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); + golias::TextureDescription pixel_art_desc; + pixel_art_desc.min_filter = golias::TextureFilter::NEAREST; + pixel_art_desc.mag_filter = golias::TextureFilter::NEAREST; + pixel_art_desc.wrap_u = golias::TextureWrap::CLAMP_TO_EDGE; + pixel_art_desc.wrap_v = golias::TextureWrap::CLAMP_TO_EDGE; + golias::RID pixel_texture = renderer.load_texture_from_file("res/monsters.png", pixel_art_desc); + golias::TextureDescription smooth_desc; + smooth_desc.min_filter = golias::TextureFilter::LINEAR; + smooth_desc.mag_filter = golias::TextureFilter::LINEAR; + smooth_desc.wrap_u = golias::TextureWrap::REPEAT; + smooth_desc.wrap_v = golias::TextureWrap::REPEAT; + golias::RID smooth_texture = renderer.load_texture_from_file("res/icon.png", smooth_desc); - 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); + golias::RID wavy_shader = renderer.create_shader_from_file("res/shaders/wave.glsl"); + golias::RID rainbow_shader = renderer.create_shader_from_file("res/shaders/rainbow.glsl"); + golias::RID retro_shader = renderer.create_shader_from_file("res/shaders/retro.glsl"); - 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); + golias::Camera2D camera(0.0f, 0.0f, 1.0f); + bool use_camera = false; // Toggle with C key - create_label_2d_entity("Emojis", - "😀🎮🚀💎✨", - glm::vec2(margin_x, margin_y + WINDOW_HEIGHT * 0.06f), - glm::vec4(1, 1, 1, 1), - 1.0f); + bool running = true; + SDL_Event event; + float time = 0.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); + 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; + } - // 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); + 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); + } - // 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); + if (event.type == SDL_EVENT_KEY_DOWN) { + if (event.key.key == SDLK_1) { + renderer.set_scale_mode(golias::ScaleMode::NONE); + printf("Scale Mode: NONE (no scaling, centered)\n"); + } else if (event.key.key == SDLK_2) { + renderer.set_scale_mode(golias::ScaleMode::KEEP); + printf("Scale Mode: KEEP (aspect ratio preserved)\n"); + } else if (event.key.key == SDLK_3) { + renderer.set_scale_mode(golias::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"); + } + } + } - // 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); + // Camera controls (Arrow keys and +/- for zoom, R for rotation) + 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); + } - // 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); + if (keys[SDL_SCANCODE_EQUALS] || keys[SDL_SCANCODE_KP_PLUS]) { + camera.zoom += 0.5f * 0.016f; + if (camera.zoom > 3.0f) { + camera.zoom = 3.0f; + } + } + if (keys[SDL_SCANCODE_MINUS] || keys[SDL_SCANCODE_KP_MINUS]) { + camera.zoom -= 0.5f * 0.016f; + if (camera.zoom < 0.1f) { + camera.zoom = 0.1f; + } + } - // 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); + if (keys[SDL_SCANCODE_R]) { + camera.rotation += 1.0f * 0.016f; + } + if (keys[SDL_SCANCODE_T]) { + camera.rotation -= 1.0f * 0.016f; + } + } - // 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"); + time += 0.016f; + renderer.begin(golias::Color(0.1f, 0.1f, 0.15f, 1.0f)); - 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 + if (use_camera) { + renderer.set_camera(camera.get_view_projection_matrix(1280, 720)); + } else { + renderer.reset_camera(); + } - 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); + // Draw a red rectangle + renderer.draw_rect(50, 50, 150, 100, golias::Color::RED); + // + // // Draw a green circle + renderer.draw_circle(400, 150, 60, golias::Color::GREEN); + // + // // Draw a blue triangle + renderer.draw_triangle(550, 50, 650, 50, 600, 150, golias::Color::BLUE); + // + // // Draw some lines + renderer.draw_line(50, 250, 200, 250, golias::Color::YELLOW, 3.0f); + renderer.draw_line(50, 280, 200, 320, golias::Color::CYAN, 2.0f); + // + // // Draw outlined shapes + renderer.draw_rect_outlined(250, 250, 120, 80, golias::Color::MAGENTA, 2.0f); + renderer.draw_circle(450, 300, 50, golias::Color::YELLOW); + // + // // Draw rotating rectangle + float rotation = time * 2.0f; + renderer.draw_rect(550, 250, 100, 100, golias::Color::CYAN, rotation); + // + // // Draw textured quad + renderer.draw_texture(my_texture2, 50, 400, 150, 150); + // + // // Draw rotating textured quad + renderer.draw_texture(my_texture, 250, 400, 120, 120, golias::Color::WHITE, time * 3.0f); + // + // Draw textured quad (normal) + renderer.draw_texture(my_texture, 50, 50, 100, 100); + + renderer.draw_texture(pixel_texture, 900, 50, 128, 128); + renderer.draw_text(font, 900, 15, golias::Color::WHITE, "NEAREST Filter"); + + + // Custom shader examples (only draw if shaders loaded successfully) + // Draw them in a vertical column to make them easy to see + if (wavy_shader != golias::INVALID_RID) { + renderer.draw_custom(wavy_shader, 200, 50, 100, 100, my_texture, golias::Color::WHITE); + } - std::string name = "window_" + std::to_string(window_index); - window_index++; + if (rainbow_shader != golias::INVALID_RID) { + renderer.draw_custom(rainbow_shader, 200, 170, 100, 100, golias::INVALID_RID, golias::Color::WHITE); + } - 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)); - } + if (retro_shader != golias::INVALID_RID) { + renderer.draw_custom(retro_shader, 200, 290, 100, 100, my_texture, golias::Color::WHITE); } + + // New draw_quad examples showcasing extended parameters + // Example 1: Simple quad with default shader + renderer.draw_texture_ex(700, 50, 80, 80, my_texture); + + // Example 2: Horizontally flipped quad + renderer.draw_texture_ex(800, 50, 80, 80, my_texture, {0, 0, 0, 0}, golias::Color::WHITE, 0.0f, true, false); + + // Example 3: Vertically flipped quad + renderer.draw_texture_ex(700, 150, 80, 80, my_texture, {0, 0, 0, 0}, golias::Color::WHITE, 0.0f, false, true); + + // Example 4: Both flipped + renderer.draw_texture_ex(800, 150, 80, 80, my_texture, {0, 0, 0, 0}, golias::Color::WHITE, 0.0f, true, true); + + // Example 5: Rotating quad with custom shader + renderer.draw_texture_ex(750, 270, 80, 80, my_texture, {0, 0, 0, 0}, golias::Color::WHITE, time * 2.0f, false, false, wavy_shader); + + + renderer.draw_texture_ex(700, 380, 64, 64, pixel_texture, {0, 0, 32, 32}, golias::Color::WHITE); + + renderer.draw_text(mine, 400, 50, golias::Color::WHITE, "Hello World! 👋 Welcome!"); + renderer.draw_text(font, 400, 80, golias::Color::WHITE, "Frame: {} 🎮", static_cast(time * 60)); + renderer.draw_text(font, 400, 110, golias::Color::WHITE, "Position: ({}, {}) 📍", 123, 456); + renderer.draw_text(font, 400, 140, golias::Color::WHITE, "Time: {:.2f}s ⏱️", time); + renderer.draw_text(font, 400, 170, golias::Color::WHITE, "FPS: {} 🚀", 60); + renderer.draw_text(font, 400, 200, golias::Color::WHITE, "Mixed: ABC 123 😀 🎉 🔥 ⭐ XYZ"); + + + renderer.draw_text(font, 400, 240, golias::Color::GREEN, "Health: {}", 100); + renderer.draw_text(font, 400, 270, golias::Color::RED, "Score: 999,999 🏆"); + + renderer.end(); + + SDL_GL_SwapWindow(window); + + SDL_Delay(16); + } + + + if (wavy_shader != golias::INVALID_RID) { + renderer.destroy_shader(wavy_shader); + } + + if (rainbow_shader != golias::INVALID_RID) { + renderer.destroy_shader(rainbow_shader); } + if (retro_shader != golias::INVALID_RID) { + renderer.destroy_shader(retro_shader); + } - create_mesh_3d_entity("Red Cube", "res://models/cube.obj", glm::vec3(3, 0, 0), glm::vec3(0), glm::vec3(1.5f), "red_rough"); + renderer.shutdown(); + rd.shutdown(); - 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"); + if (font) { + TTF_CloseFont(font); + } + + if (emoji_font) { + TTF_CloseFont(emoji_font); + } - 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"); + TTF_Quit(); - GEngine->run(); + SDL_GL_DestroyContext(gl_context); + SDL_DestroyWindow(window); + SDL_Quit(); return 0; } From 9bb602241069082335bf142564fbc9c68c6f2166 Mon Sep 17 00:00:00 2001 From: vsaint1 Date: Sun, 23 Nov 2025 23:35:38 -0300 Subject: [PATCH 02/71] cooking v14 --- editor/CMakeLists.txt | 1 + .../drivers/gles3/rendering_device_gles3.cpp | 856 +++++++++++++++++ .../servers/rendering/rendering_canvas.cpp | 858 ++++++++++++++++++ .../servers/rendering/rendering_device.cpp | 6 + .../servers/rendering/shader_preprocessor.cpp | 111 +++ engine/public/definitions.h | 43 + .../drivers/gles3/rendering_device_gles3.h | 121 +++ .../servers/rendering/rendering_canvas.h | 232 +++++ .../servers/rendering/rendering_device.h | 323 +++++++ .../servers/rendering/shader_preprocessor.h | 17 + engine/public/stdafx.h | 72 ++ res/shaders/rainbow.glsl | 38 + res/shaders/retro.glsl | 42 + res/shaders/wave.glsl | 32 + tests/example_canvas2d.cpp | 326 +++++++ tests/example_minimal_2d.cpp | 49 - tests/example_minimal_3d.cpp | 60 -- tests/example_orbit_cubes_3d.cpp | 47 - tests/res/scripts/constants.lua | 530 ----------- tests/res/scripts/test.lua | 60 -- tests/res/scripts/test_2d.lua | 31 - tests/test_engine.cpp | 9 - tests/test_engine_config.cpp | 19 - tests/test_hello.cpp | 6 - tests/test_lua_binding.cpp | 35 - 25 files changed, 3078 insertions(+), 846 deletions(-) create mode 100644 editor/CMakeLists.txt create mode 100644 engine/private/drivers/gles3/rendering_device_gles3.cpp create mode 100644 engine/private/servers/rendering/rendering_canvas.cpp create mode 100644 engine/private/servers/rendering/rendering_device.cpp create mode 100644 engine/private/servers/rendering/shader_preprocessor.cpp create mode 100644 engine/public/definitions.h create mode 100644 engine/public/drivers/gles3/rendering_device_gles3.h create mode 100644 engine/public/servers/rendering/rendering_canvas.h create mode 100644 engine/public/servers/rendering/rendering_device.h create mode 100644 engine/public/servers/rendering/shader_preprocessor.h create mode 100644 engine/public/stdafx.h create mode 100644 res/shaders/rainbow.glsl create mode 100644 res/shaders/retro.glsl create mode 100644 res/shaders/wave.glsl create mode 100644 tests/example_canvas2d.cpp delete mode 100644 tests/example_minimal_2d.cpp delete mode 100644 tests/example_minimal_3d.cpp delete mode 100644 tests/example_orbit_cubes_3d.cpp delete mode 100644 tests/res/scripts/constants.lua delete mode 100644 tests/res/scripts/test.lua delete mode 100644 tests/res/scripts/test_2d.lua delete mode 100644 tests/test_engine.cpp delete mode 100644 tests/test_engine_config.cpp delete mode 100644 tests/test_hello.cpp delete mode 100644 tests/test_lua_binding.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt new file mode 100644 index 000000000..f87f5c14c --- /dev/null +++ b/editor/CMakeLists.txt @@ -0,0 +1 @@ +# TODO \ No newline at end of file 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..ed68f6f34 --- /dev/null +++ b/engine/private/drivers/gles3/rendering_device_gles3.cpp @@ -0,0 +1,856 @@ +#include "drivers/gles3/rendering_device_gles3.h" +#include + +using namespace golias; + + +RenderingDeviceGLES3::~RenderingDeviceGLES3() { + shutdown(); +} + + +bool RenderingDeviceGLES3::initialize() { + glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + return true; +} + +void RenderingDeviceGLES3::shutdown() { + for (auto& [id, shader] : shaders) { + glDeleteProgram(shader.program); + } + + for (auto& [id, buffer] : buffers) { + glDeleteBuffers(1, &buffer.buffer); + } + + for (auto& [id, texture] : textures) { + glDeleteTextures(1, &texture.texture); + } + + for (auto& [id, sampler] : samplers) { + glDeleteSamplers(1, &sampler.sampler); + } + + for (auto& [id, fb] : framebuffers) { + glDeleteFramebuffers(1, &fb.framebuffer); + } + + for (auto& [id, pipeline] : pipelines) { + glDeleteVertexArrays(1, &pipeline.vao); + } +} + +GLuint RenderingDeviceGLES3::compile_shader(GLenum type, const std::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); + printf("Shader compilation error: %s\n", log); + glDeleteShader(shader); + return 0; + } + return shader; +} + +RID RenderingDeviceGLES3::shader_create_from_source(const std::string& vertex_src, const std::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); + printf("Shader linking error: %s\n", log); + glDeleteProgram(program); + glDeleteShader(vs); + glDeleteShader(fs); + return INVALID_RID; + } + + glDeleteShader(vs); + glDeleteShader(fs); + + RID rid = allocate_rid(); + shaders[rid] = ShaderData{program}; + return rid; +} + +void RenderingDeviceGLES3::shader_destroy(RID shader) { + auto it = shaders.find(shader); + if (it != shaders.end()) { + glDeleteProgram(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::UNIFORM) ? GL_UNIFORM_BUFFER : GL_ARRAY_BUFFER; + if (usage_flags & (uint32_t) BufferUsage::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] = BufferData{buffer, size, usage_flags, 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(it->second.target, it->second.buffer); + glBufferSubData(it->second.target, offset, size, data); + glBindBuffer(it->second.target, 0); + } +} + +void RenderingDeviceGLES3::buffer_destroy(RID buffer) { + auto it = buffers.find(buffer); + if (it != buffers.end()) { + glDeleteBuffers(1, &it->second.buffer); + 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::TYPE_CUBE) { + target = GL_TEXTURE_CUBE_MAP; + } else if (format.type == TextureType::TYPE_2D_ARRAY) { + target = GL_TEXTURE_2D_ARRAY; + } else if (format.type == TextureType::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::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] = TextureData{texture, format}; + + + return rid; +} + +void RenderingDeviceGLES3::texture_destroy(RID texture) { + auto it = textures.find(texture); + if (it != textures.end()) { + glDeleteTextures(1, &it->second.texture); + textures.erase(it); + } +} + +void RenderingDeviceGLES3::texture_get_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; + } +} + +RID RenderingDeviceGLES3::pipeline_create(const PipelineState& state) { + GLuint vao; + glGenVertexArrays(1, &vao); + + RID rid = allocate_rid(); + pipelines[rid] = PipelineData{state, vao}; + return rid; +} + +void RenderingDeviceGLES3::pipeline_destroy(RID pipeline) { + auto it = pipelines.find(pipeline); + if (it != pipelines.end()) { + glDeleteVertexArrays(1, &it->second.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(pipe.vao); + + 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 std::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 std::vector& buffer_rids, const std::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, buf_it->second.buffer); + + for (const auto& attr : vertex_format.attributes) { + glEnableVertexAttribArray(attr.location); + + // Derive attribute size/type/normalized from DataFormat + 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, it->second.buffer); + 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, it->second.buffer, 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::TYPE_CUBE) { + target = GL_TEXTURE_CUBE_MAP; + } + glBindTexture(target, tex_it->second.texture); + + if (sampler != INVALID_RID) { + auto samp_it = samplers.find(sampler); + if (samp_it != samplers.end()) { + glBindSampler(binding, samp_it->second.sampler); + } + } + } +} + +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] = SamplerData{sampler, state}; + return rid; +} + +void RenderingDeviceGLES3::sampler_destroy(RID sampler) { + auto it = samplers.find(sampler); + if (it != samplers.end()) { + glDeleteSamplers(1, &it->second.sampler); + 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 std::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, tex.texture, attachment.mip_level); + } + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + printf("Framebuffer incomplete: 0x%x\n", status); + glDeleteFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + return INVALID_RID; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + RID rid = allocate_rid(); + framebuffers[rid] = FramebufferData{fbo, attachments, width, height}; + return rid; +} + +void RenderingDeviceGLES3::framebuffer_destroy(RID framebuffer) { + auto it = framebuffers.find(framebuffer); + if (it != framebuffers.end()) { + glDeleteFramebuffers(1, &it->second.framebuffer); + 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, it->second.framebuffer); + current_framebuffer = framebuffer; + + // Clear attachments if requested + 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::push_constant(const std::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::TYPE_CUBE) { + target = GL_TEXTURE_CUBE_MAP; + } + + glBindTexture(target, tex.texture); + + 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::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::TYPE_CUBE) { + target = GL_TEXTURE_CUBE_MAP; + } + + glBindTexture(target, it->second.texture); + 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/servers/rendering/rendering_canvas.cpp b/engine/private/servers/rendering/rendering_canvas.cpp new file mode 100644 index 000000000..ff58f5aca --- /dev/null +++ b/engine/private/servers/rendering/rendering_canvas.cpp @@ -0,0 +1,858 @@ +#include "servers/rendering/rendering_canvas.h" + +using namespace golias; + + +namespace shaders { + const char* default_vertex_2d = R"( +#version 300 es +precision highp float; + +layout(location = 0) in vec3 aPosition; +layout(location = 1) in vec4 aColor; +layout(location = 2) in vec2 aTexCoord; + +uniform mat4 uViewProjection; + +out vec4 vColor; +out vec2 vTexCoord; + +void main() { + vColor = aColor; + vTexCoord = aTexCoord; + gl_Position = uViewProjection * vec4(aPosition, 1.0); +} +)"; + + const char* default_fragment_2d = R"( +#version 300 es +precision highp float; + +in vec4 vColor; +in vec2 vTexCoord; + +out vec4 fragColor; + +uniform sampler2D uTexture; +uniform int uUseTexture; + +void main() { + vec4 texColor = texture(uTexture, vTexCoord); + + if (texColor.a < 0.01) { + discard; + } + + fragColor = texColor * vColor; +} +)"; +} // namespace shaders + + +RenderingCanvas::RenderingCanvas(golias::RenderingDevice* device) + : rd(device), shader(golias::INVALID_RID), pipeline(golias::INVALID_RID), vertex_buffer(golias::INVALID_RID), + index_buffer(golias::INVALID_RID), white_texture(golias::INVALID_RID), default_sampler(golias::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; + + emoji_font = TTF_OpenFont("res/fonts/Twemoji.ttf", 24); + + if (!emoji_font) { + 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."); + } + + shader = rd->shader_create_from_source(shaders::default_vertex_2d, shaders::default_fragment_2d); + + if (shader == golias::INVALID_RID) { + return false; + } + + uint8_t white_pixel[] = {255, 255, 255, 255}; + white_texture = load_texture_from_memory(white_pixel, 1, 1, 4); + + golias::SamplerState sampler_state; + sampler_state.min_filter = golias::TextureFilter::LINEAR; + sampler_state.mag_filter = golias::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) golias::BufferUsage::VERTEX, nullptr); + + index_buffer = rd->buffer_create(max_indices * sizeof(uint16_t), (uint32_t) golias::BufferUsage::INDEX, nullptr); + + golias::PipelineState pipeline_state; + pipeline_state.shader = shader; + pipeline_state.topology = golias::PrimitiveTopology::TRIANGLES; + pipeline_state.vertex_format.stride = sizeof(Vertex); + pipeline_state.vertex_format.attributes = {{0, golias::DataFormat::R32G32B32_SFLOAT, offsetof(Vertex, position)}, + {1, golias::DataFormat::R32G32B32A32_SFLOAT, offsetof(Vertex, color)}, + {2, golias::DataFormat::R32G32_SFLOAT, offsetof(Vertex, texcoord)}}; + pipeline_state.rasterization.cull_mode = golias::CullMode::NONE; + pipeline_state.depth_stencil.depth_test_enable = false; + + golias::BlendState blend; + blend.enable = true; + blend.src_color = golias::BlendFactor::SRC_ALPHA; + blend.dst_color = golias::BlendFactor::ONE_MINUS_SRC_ALPHA; + blend.src_alpha = golias::BlendFactor::ONE; + blend.dst_alpha = golias::BlendFactor::ONE_MINUS_SRC_ALPHA; + pipeline_state.blend_states.push_back(blend); + + pipeline = rd->pipeline_create(pipeline_state); + + if (pipeline == golias::INVALID_RID) { + return false; + } + + emoji_font = TTF_OpenFont("res/fonts/Twemoji.ttf", 24.0f); + + if (!emoji_font) { + spdlog::warn("Failed to load emoji font, emojis may not display: {}", SDL_GetError()); + } + + reset_camera(); + return true; +} + +void RenderingCanvas::shutdown() { + if (pipeline != golias::INVALID_RID) { + rd->pipeline_destroy(pipeline); + } + + if (vertex_buffer != golias::INVALID_RID) { + rd->buffer_destroy(vertex_buffer); + } + + if (index_buffer != golias::INVALID_RID) { + rd->buffer_destroy(index_buffer); + } + + if (white_texture != golias::INVALID_RID) { + rd->texture_destroy(white_texture); + } + + if (default_sampler != golias::INVALID_RID) { + rd->sampler_destroy(default_sampler); + } + + if (shader != golias::INVALID_RID) { + rd->shader_destroy(shader); + } + + + for (auto& pair : custom_shader_pipelines) { + rd->pipeline_destroy(pair.second); + } + + for (auto& pair : loaded_fonts) { + if (pair.second) { + TTF_CloseFont(pair.second); + } + } + + loaded_fonts.clear(); + + if (emoji_font) { + TTF_CloseFont(emoji_font); + emoji_font = nullptr; + } + + custom_shader_pipelines.clear(); +} + +void RenderingCanvas::begin(const Color& clear_color) { + is_drawing = true; + vertices.clear(); + indices.clear(); + draw_commands.clear(); + + int vp_x, vp_y, vp_w, vp_h; + calculate_viewport(viewport_width, viewport_height, vp_x, vp_y, vp_w, vp_h); + + golias::Viewport viewport{(float) vp_x, (float) vp_y, (float) vp_w, (float) vp_h, 0.0f, 1.0f}; + golias::Scissor scissor{0, 0, (uint32_t) viewport_width, (uint32_t) viewport_height}; + + rd->render_pass_begin(golias::INVALID_RID, viewport, scissor); + rd->clear_color(clear_color.to_vec4()); +} + +void RenderingCanvas::end() { + flush(); + + rd->render_pass_end(); + + is_drawing = false; +} + +void RenderingCanvas::flush() { + if (vertices.empty() || indices.empty()) { + return; + } + + 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->bind_pipeline(pipeline); + rd->bind_vertex_buffers({vertex_buffer}); + rd->bind_index_buffer(index_buffer, golias::IndexType::UINT16); + + glm::mat4 vp = projection * view; + rd->push_constant("uViewProjection", glm::value_ptr(vp), sizeof(glm::mat4)); + + + int tex_unit = 0; + rd->push_constant("uTexture", &tex_unit, sizeof(int)); + + 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); + + // int use_tex = cmd.use_texture ? 1 : 0; + // rd->push_constant("uUseTexture", &use_tex, sizeof(int)); + + + 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) { + std::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) { +} + +void RenderingCanvas::draw_texture_rect(RID texture, const Rect& dest, const Rect& source, const Color& tint, float rotation) { +} + +TTF_Font* RenderingCanvas::load_font_from_file(const char* filepath, int size) { + + if (loaded_fonts.contains(filepath)) { + return loaded_fonts.at(filepath); + } + + TTF_Font* font = TTF_OpenFont(filepath, (float)size); + + if (!font) { + spdlog::error("Failed to load Font from file {}: {}", filepath, SDL_GetError()); + return nullptr; + } + + if (emoji_font) { + if (!TTF_AddFallbackFont(font, emoji_font)) { + spdlog::warn("Failed to add emoji fallback font: {}", SDL_GetError()); + } + } + + 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::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 std::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(golias::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->texture_get_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, golias::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; +} + +golias::RID RenderingCanvas::load_texture_from_file(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 golias::INVALID_RID; + } + + spdlog::info("Loaded texture '{}' ({}x{})", filepath, width, height); + + golias::TextureFormat format; + format.type = golias::TextureType::TYPE_2D; + format.format = golias::DataFormat::R8G8B8A8_UNORM; + format.width = width; + format.height = height; + format.mipmaps = 1; + + golias::RID rid = rd->texture_create(format, data); + + stbi_image_free(data); + + return rid; +} + +golias::RID RenderingCanvas::load_texture_from_file(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 golias::INVALID_RID; + } + + + golias::TextureFormat format; + format.type = golias::TextureType::TYPE_2D; + format.format = golias::DataFormat::R8G8B8A8_UNORM; + format.width = width; + format.height = height; + format.mipmaps = desc.generate_mipmaps ? 1 : 0; + + golias::RID texture = rd->texture_create(format, data); + + if (desc.generate_mipmaps && texture != golias::INVALID_RID) { + rd->texture_generate_mipmaps(texture); + } + + stbi_image_free(data); + + if (texture != golias::INVALID_RID) { + golias::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; + + golias::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; +} + + +golias::RID RenderingCanvas::load_texture_from_memory(void* data, int width, int height, int channels) { + golias::TextureFormat format; + format.type = golias::TextureType::TYPE_2D; + format.format = (channels == 4) ? golias::DataFormat::R8G8B8A8_UNORM : golias::DataFormat::R8G8B8_UNORM; + format.width = width; + format.height = height; + format.mipmaps = 1; + + return rd->texture_create(format, data); +} + +void RenderingCanvas::unload_texture(golias::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); +} + + +golias::RID RenderingCanvas::create_shader(const char* vertex_src, const char* fragment_src) { + return rd->shader_create_from_source(vertex_src, fragment_src); +} + +golias::RID RenderingCanvas::create_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 golias::INVALID_RID; + } + + if (parsed.is_compute) { + spdlog::error("Compute Shaders not Supported for this Rendering: {}", filepath); + return golias::INVALID_RID; + } + + spdlog::info("Creating shader from file: {}", filepath); + + golias::RID shader = rd->shader_create_from_source(parsed.vertex_source.c_str(), parsed.fragment_source.c_str()); + + if (shader == golias::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; +} + +golias::RID RenderingCanvas::create_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(golias::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); +} + +golias::RID RenderingCanvas::get_or_create_custom_pipeline(golias::RID shader) { + + auto it = custom_shader_pipelines.find(shader); + + if (it != custom_shader_pipelines.end()) { + return it->second; + } + + golias::PipelineState pipeline_state; + pipeline_state.shader = shader; + pipeline_state.topology = golias::PrimitiveTopology::TRIANGLES; + pipeline_state.vertex_format.stride = sizeof(Vertex); + pipeline_state.vertex_format.attributes = {{0, golias::DataFormat::R32G32B32_SFLOAT, offsetof(Vertex, position)}, + {1, golias::DataFormat::R32G32B32A32_SFLOAT, offsetof(Vertex, color)}, + {2, golias::DataFormat::R32G32_SFLOAT, offsetof(Vertex, texcoord)}}; + pipeline_state.rasterization.cull_mode = golias::CullMode::NONE; + pipeline_state.depth_stencil.depth_test_enable = false; +} + +void RenderingCanvas::draw_custom(golias::RID custom_shader, float x, float y, float width, float height, golias::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; + + std::vector custom_vertices; + std::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}; + + + golias::PipelineState custom_pipeline_state; + custom_pipeline_state.shader = custom_shader; + custom_pipeline_state.topology = golias::PrimitiveTopology::TRIANGLES; + custom_pipeline_state.vertex_format.stride = sizeof(Vertex); + custom_pipeline_state.vertex_format.attributes = {{0, golias::DataFormat::R32G32B32_SFLOAT, offsetof(Vertex, position)}, + {1, golias::DataFormat::R32G32B32A32_SFLOAT, offsetof(Vertex, color)}, + {2, golias::DataFormat::R32G32_SFLOAT, offsetof(Vertex, texcoord)}}; + + custom_pipeline_state.rasterization.cull_mode = golias::CullMode::NONE; + custom_pipeline_state.depth_stencil.depth_test_enable = false; + + golias::BlendState blend; + blend.enable = true; + blend.src_color = golias::BlendFactor::SRC_ALPHA; + blend.dst_color = golias::BlendFactor::ONE_MINUS_SRC_ALPHA; + blend.src_alpha = golias::BlendFactor::ONE; + blend.dst_alpha = golias::BlendFactor::ONE_MINUS_SRC_ALPHA; + custom_pipeline_state.blend_states.push_back(blend); + + golias::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, golias::IndexType::UINT16); + + glm::mat4 vp = projection * view; + rd->push_constant("uViewProjection", glm::value_ptr(vp), sizeof(glm::mat4)); + + float time_value = SDL_GetTicks() / 1000.0f; + rd->push_constant("uTime", &time_value, sizeof(float)); + + if (texture != golias::INVALID_RID) { + rd->bind_texture(0, texture, default_sampler); + int tex_unit = 0; + rd->push_constant("uTexture", &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, golias::RID texture, const Rect& source, + const Color& color, float rotation, bool flip_h, bool flip_v, golias::RID shader) { + if (!is_drawing) { + return; + } + + bool use_custom_shader = shader != golias::INVALID_RID; + + 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 && texture != golias::INVALID_RID) { + uint32_t tex_width = 0, tex_height = 0; + rd->texture_get_size(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) { + std::vector custom_vertices; + std::vector custom_indices; + + 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}; + + golias::PipelineState custom_pipeline_state; + custom_pipeline_state.shader = shader; + custom_pipeline_state.topology = golias::PrimitiveTopology::TRIANGLES; + custom_pipeline_state.vertex_format.stride = sizeof(Vertex); + custom_pipeline_state.vertex_format.attributes = {{0, golias::DataFormat::R32G32B32_SFLOAT, offsetof(Vertex, position)}, + {1, golias::DataFormat::R32G32B32A32_SFLOAT, offsetof(Vertex, color)}, + {2, golias::DataFormat::R32G32_SFLOAT, offsetof(Vertex, texcoord)}}; + custom_pipeline_state.rasterization.cull_mode = golias::CullMode::NONE; + custom_pipeline_state.depth_stencil.depth_test_enable = false; + + golias::BlendState blend; + blend.enable = true; + blend.src_color = golias::BlendFactor::SRC_ALPHA; + blend.dst_color = golias::BlendFactor::ONE_MINUS_SRC_ALPHA; + blend.src_alpha = golias::BlendFactor::ONE; + blend.dst_alpha = golias::BlendFactor::ONE_MINUS_SRC_ALPHA; + custom_pipeline_state.blend_states.push_back(blend); + + golias::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, golias::IndexType::UINT16); + + glm::mat4 vp = projection * view; + rd->push_constant("uViewProjection", glm::value_ptr(vp), sizeof(glm::mat4)); + + float time_value = SDL_GetTicks() / 1000.0f; + rd->push_constant("uTime", &time_value, sizeof(float)); + + if (texture != golias::INVALID_RID) { + rd->bind_texture(0, texture, default_sampler); + int tex_unit = 0; + rd->push_constant("uTexture", &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); + } else { + for (int i = 0; i < 4; i++) { + glm::vec4 pos = transform * positions[i]; + vertices.push_back({glm::vec3(pos), color.to_vec4(), 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); + + golias::RID tex = texture != golias::INVALID_RID ? 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..fbb3ac117 --- /dev/null +++ b/engine/private/servers/rendering/rendering_device.cpp @@ -0,0 +1,6 @@ +#include "servers/rendering/rendering_device.h" + + +golias::RID golias::RIDAllocator::allocate_rid() { + return next_rid++; +} diff --git a/engine/private/servers/rendering/shader_preprocessor.cpp b/engine/private/servers/rendering/shader_preprocessor.cpp new file mode 100644 index 000000000..de7ffad43 --- /dev/null +++ b/engine/private/servers/rendering/shader_preprocessor.cpp @@ -0,0 +1,111 @@ +#include "servers/rendering/shader_preprocessor.h" +#include + +ShaderSource parse_shader(const std::string& source) { + ShaderSource result; + + if (source.find("void main()") != std::string::npos || source.find("#[compute]") != std::string::npos) { + result.is_compute = true; + result.compute_source = source; + result.is_loaded = true; + return result; + } + + 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 have both vertex() and fragment()"); + result.is_loaded = false; + return result; + } + + size_t first_main = std::min(vertex_pos, fragment_pos); + std::string common_code = source.substr(0, first_main); + + size_t vertex_end = fragment_pos; + std::string vertex_code = source.substr(vertex_pos, vertex_end - vertex_pos); + + std::string fragment_code = source.substr(fragment_pos); + + std::string vertex_common = common_code; + std::string fragment_common = common_code; + + size_t pos = 0; + while ((pos = fragment_common.find("out vec", pos)) != std::string::npos) { + size_t name_start = fragment_common.find_first_not_of(" \t", pos + 7); // after "out vec" + if (name_start != std::string::npos) { + size_t var_start = fragment_common.find(' ', name_start); + if (var_start != std::string::npos) { + var_start = fragment_common.find_first_not_of(" \t", var_start); + if (var_start != std::string::npos && fragment_common[var_start] == 'v') { + fragment_common.replace(pos, 3, "in "); + } + } + } + pos += 3; + } + + + pos = 0; + while ((pos = vertex_common.find("out vec4 COLOR", pos)) != std::string::npos) { + size_t line_start = vertex_common.rfind('\n', pos); + if (line_start == std::string::npos) { + line_start = 0; + } else { + line_start++; + } + + size_t line_end = vertex_common.find(';', pos); + if (line_end != std::string::npos) { + line_end = vertex_common.find('\n', line_end); + if (line_end != std::string::npos) { + vertex_common.erase(line_start, line_end - line_start + 1); + pos = line_start; + } else { + break; + } + } else { + break; + } + } + + result.vertex_source = vertex_common + "\n" + vertex_code; + size_t vm_pos = result.vertex_source.find("void vertex()"); + if (vm_pos != std::string::npos) { + result.vertex_source.replace(vm_pos, 13, "void main() "); + } + + result.fragment_source = fragment_common + "\n" + fragment_code; + size_t fm_pos = result.fragment_source.find("void fragment()"); + if (fm_pos != std::string::npos) { + result.fragment_source.replace(fm_pos, 15, "void main() "); + } + + spdlog::info("Shader Metadata: Vertex size = {} | Fragment size = {} | Compute: {}", result.vertex_source.size(), result.fragment_source.size(),result.is_compute); + 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 source file: {}", filepath); + result.is_loaded = false; + return result; + } + + SDL_SeekIO(file,0,SDL_IO_SEEK_END); + Sint64 size = SDL_TellIO(file); + SDL_SeekIO(file,0,SDL_IO_SEEK_SET); + + std::string source; + source.resize(size); + SDL_ReadIO(file, &source[0], size); + SDL_CloseIO(file); + + return parse_shader(source); +} diff --git a/engine/public/definitions.h b/engine/public/definitions.h new file mode 100644 index 000000000..0eb38ad8d --- /dev/null +++ b/engine/public/definitions.h @@ -0,0 +1,43 @@ +#pragma once +#define ENGINE_NAME "GOLIAS_ENGINE" +#define ENGINE_VERSION_STR "0.0.5" + +#define ENGINE_DEFAULT_FOLDER_NAME "Golias Engine" +#define ENGINE_PACKAGE_NAME "com.golias.engine.app" + +#define ALBEDO_TEXTURE_UNIT 0 +#define SPECULAR_TEXTURE_UNIT 1 +#define METALLIC_TEXTURE_UNIT 2 +#define ROUGHNESS_TEXTURE_UNIT 3 +#define NORMAL_MAP_TEXTURE_UNIT 4 +#define AMBIENT_OCCLUSION_TEXTURE_UNIT 5 +#define EMISSIVE_TEXTURE_UNIT 6 +#define SHADOW_TEXTURE_UNIT 7 +#define ENVIRONMENT_TEXTURE_UNIT 8 + +#define MAX_VERTEX_MEMORY (512 * 1024) +#define MAX_ELEMENT_MEMORY (128 * 1024) + +#if !defined(NDEBUG) + #if defined(_MSC_VER) + #define GOLIAS_ASSERT_BREAK() __debugbreak() + #elif defined(__clang__) || defined(__GNUC__) + #define GOLIAS_ASSERT_BREAK() __builtin_trap() + #else + #define GOLIAS_ASSERT_BREAK() std::abort() + #endif +#else + #define GOLIAS_ASSERT_BREAK() ((void)0) +#endif + +/*! +* @defgroup Components +* @defgroup Systems +* @defgroup Core +* @defgroup Tags +* @defgroup FileSystem +* @defgroup Configuration +* @defgroup Rendering +* @defgroup Time +* @defgroup Logging +*/ 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..1ca47cdc6 --- /dev/null +++ b/engine/public/drivers/gles3/rendering_device_gles3.h @@ -0,0 +1,121 @@ +#pragma once + +#include "servers/rendering/rendering_device.h" +#include + +namespace golias { + + class RenderingDeviceGLES3 final : public RenderingDevice { + public: + RenderingDeviceGLES3() = default; + ~RenderingDeviceGLES3() override; + + bool initialize() override; + void shutdown() override; + + RID shader_create_from_source(const std::string& vertex_src, const std::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, 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 texture_get_size(RID texture, uint32_t& width, uint32_t& height) override; + + RID sampler_create(const SamplerState& state) override; + void sampler_destroy(RID sampler) override; + + RID framebuffer_create(const std::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 std::vector& buffers, const std::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 std::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; + + private: + struct ShaderData { + GLuint program = 0; + std::unordered_map uniform_locations; + }; + + struct BufferData { + GLuint buffer = 0; + size_t size = 0; + uint32_t usage_flags = 0; + GLenum target = GL_ARRAY_BUFFER; + }; + + struct TextureData { + GLuint texture = 0; + TextureFormat format; + }; + + struct SamplerData { + GLuint sampler = 0; + SamplerState state; + }; + + struct FramebufferData { + GLuint framebuffer = 0; + std::vector attachments; + uint32_t width = 0, height = 0; + }; + + struct PipelineData { + PipelineState state; + GLuint vao = 0; + }; + + std::unordered_map shaders; + std::unordered_map buffers; + std::unordered_map textures; + std::unordered_map samplers; + std::unordered_map framebuffers; + std::unordered_map pipelines; + + + RID current_pipeline = INVALID_RID; + RID current_framebuffer = INVALID_RID; + IndexType current_index_type = IndexType::UINT16; + + GLuint compile_shader(GLenum type, const std::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 std::vector& states); + }; + +} // namespace golias diff --git a/engine/public/servers/rendering/rendering_canvas.h b/engine/public/servers/rendering/rendering_canvas.h new file mode 100644 index 000000000..e953a3cd9 --- /dev/null +++ b/engine/public/servers/rendering/rendering_canvas.h @@ -0,0 +1,232 @@ +#pragma once +#include "drivers/gles3/rendering_device_gles3.h" +#include +#include + +namespace golias { + + class RenderingCanvas { + public: + explicit RenderingCanvas(RenderingDevice* device); + ~RenderingCanvas(); + + bool initialize(int window_width, int window_height); + void shutdown(); + + void begin(const Color& clear_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 push_transform(const glm::mat4& transform); + void pop_transform(); + + 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_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 std::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); + + void draw_texture_rect(RID texture, const Rect& dest, const Rect& source, const Color& tint = Color::WHITE, + float rotation = 0.0f); + + TTF_Font* load_font_from_file(const char* filepath, int size); + + RID load_texture_from_file(const char* filepath); + RID load_texture_from_file(const char* filepath, const TextureDescription& desc); + + RID load_texture_from_memory(void* data, int width, int height, int channels = 4); + void unload_texture(RID texture); + + template + void draw_text(TTF_Font* font, float x, float y, const Color& color, const char* format_str, Args&&... args); + void draw_text(TTF_Font* font, float x, float y, const Color& color, const std::string& text); + + RID create_shader(const char* vertex_src, const char* fragment_src); + RID create_shader_from_file(const char* filepath); + RID create_shader_from_source(const char* source); + void destroy_shader(RID shader); + + 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, RID shader = INVALID_RID); + + + void set_blend_mode(BlendMode mode); + void set_line_width(float width); + + 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; + }; + + RenderingDevice* rd; + + RID shader; + RID pipeline; + RID vertex_buffer; + RID index_buffer; + RID white_texture; + RID default_sampler; + + std::unordered_map texture_samplers; + + std::vector vertices; + std::vector indices; + std::vector draw_commands; + + glm::mat4 projection; + glm::mat4 view; + std::vector transform_stack; + BlendMode current_blend_mode; + ScaleMode scale_mode; + float line_width; + int window_width, window_height; /// Render resolution (internal) + int viewport_width, viewport_height; + bool is_drawing; + + std::unordered_map custom_shader_pipelines; /// Shader RID -> pipeline RID + + + TTF_Font* emoji_font = nullptr; + std::unordered_map loaded_fonts; + + 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 +inline void RenderingCanvas::draw_text(TTF_Font* font, float x, float y, const Color& color, const char* format_str, Args&&... args) { + if (!font) { + 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); +} + +inline void RenderingCanvas::draw_text(TTF_Font* font, float x, float y, const Color& color, const std::string& text) { + if (!font) { + 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, 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; + } + + + golias::RID text_texture = load_texture_from_memory(rgba_surface->pixels, rgba_surface->w, rgba_surface->h, 4); + + if (text_texture == golias::INVALID_RID) { + SDL_DestroySurface(rgba_surface); + spdlog::error("Failed to create Texture from Text surface"); + return; + } + + flush(); + + std::vector text_vertices; + std::vector text_indices; + + glm::mat4 transform = glm::translate(glm::mat4(1.0f), glm::vec3(x, y, 0)); + transform = get_current_transform() * transform; + + glm::vec4 positions[] = {glm::vec4(0, 0, 0, 1), glm::vec4(static_cast(rgba_surface->w), 0, 0, 1), + glm::vec4(static_cast(rgba_surface->w), static_cast(rgba_surface->h), 0, 1), + glm::vec4(0, static_cast(rgba_surface->h), 0, 1)}; + + glm::vec2 texcoords[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}}; + glm::vec4 tint_color = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f); // White - color is already in texture + + for (int i = 0; i < 4; i++) { + glm::vec4 pos = transform * positions[i]; + text_vertices.push_back({glm::vec3(pos), tint_color, texcoords[i]}); + } + + text_indices = {0, 1, 2, 2, 3, 0}; + + rd->buffer_update(vertex_buffer, 0, text_vertices.size() * sizeof(Vertex), text_vertices.data()); + rd->buffer_update(index_buffer, 0, text_indices.size() * sizeof(uint16_t), text_indices.data()); + + rd->bind_pipeline(pipeline); + rd->bind_vertex_buffers({vertex_buffer}); + rd->bind_index_buffer(index_buffer, golias::IndexType::UINT16); + + glm::mat4 vp = projection * view; + rd->push_constant("uViewProjection", glm::value_ptr(vp), sizeof(glm::mat4)); + + rd->bind_texture(0, text_texture, default_sampler); + int tex_unit = 0; + rd->push_constant("uTexture", &tex_unit, sizeof(int)); + + + rd->draw_indexed(6, 1, 0, 0, 0); + + + unload_texture(text_texture); + SDL_DestroySurface(rgba_surface); +} +} // namespace golias + + + diff --git a/engine/public/servers/rendering/rendering_device.h b/engine/public/servers/rendering/rendering_device.h new file mode 100644 index 000000000..6991d6bfc --- /dev/null +++ b/engine/public/servers/rendering/rendering_device.h @@ -0,0 +1,323 @@ +#pragma once + +#include "shader_preprocessor.h" +#include + +#include +#include +#include +#include +#include + +#define GLM_ENABLE_EXPERIMENTAL + +#include + + +namespace golias { + + 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; + }; + + inline const Color Color::WHITE = Color(1, 1, 1, 1); + inline const Color Color::BLACK = Color(0, 0, 0, 1); + inline const Color Color::RED = Color(1, 0, 0, 1); + inline const Color Color::GREEN = Color(0, 1, 0, 1); + inline const Color Color::BLUE = Color(0, 0, 1, 1); + inline const Color Color::YELLOW = Color(1, 1, 0, 1); + inline const Color Color::CYAN = Color(0, 1, 1, 1); + inline const Color Color::MAGENTA = Color(1, 0, 1, 1); + + 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 { TYPE_2D, TYPE_2D_ARRAY, TYPE_3D, TYPE_CUBE, TYPE_CUBE_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 { + VERTEX = 1 << 0, + INDEX = 1 << 1, + UNIFORM = 1 << 2, + STORAGE = 1 << 3, + TRANSFER_SRC = 1 << 4, + TRANSFER_DST = 1 << 5 + }; + + enum class IndexType { UINT16, UINT32 }; + + using RID = uint32_t; + constexpr RID INVALID_RID = -1; + + + 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; + std::vector attributes; + }; + + struct TextureFormat { + TextureType type = TextureType::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; + std::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; + }; + + class RenderingDevice : public RIDAllocator { + public: + virtual ~RenderingDevice() = default; + + virtual bool initialize() = 0; + virtual void shutdown() = 0; + + virtual RID shader_create_from_source(const std::string& vertex_src, const std::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 texture_get_size(RID texture, uint32_t& width, uint32_t& height) = 0; + + virtual RID sampler_create(const SamplerState& state) = 0; + virtual void sampler_destroy(RID sampler) = 0; + + virtual RID framebuffer_create(const std::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 std::vector& buffers, const std::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 std::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; + }; + +} // namespace golias diff --git a/engine/public/servers/rendering/shader_preprocessor.h b/engine/public/servers/rendering/shader_preprocessor.h new file mode 100644 index 000000000..fe21e2745 --- /dev/null +++ b/engine/public/servers/rendering/shader_preprocessor.h @@ -0,0 +1,17 @@ +#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); diff --git a/engine/public/stdafx.h b/engine/public/stdafx.h new file mode 100644 index 000000000..4ff969818 --- /dev/null +++ b/engine/public/stdafx.h @@ -0,0 +1,72 @@ +#pragma once +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include "definitions.h" +#include +#include +#include + +// #include +#include + +#define FLECS_CUSTOM_BUILD +#define FLECS_SYSTEM +#define FLECS_NO_LOG +#define FLECS_META +#define FLECS_CPP +#define FLECS_PIPELINE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "json.hpp" +#include +#include +#include +#include +#include +#include + +#define GLM_ENABLE_EXPERIMENTAL +#include +#include + +#include + +#if defined(SDL_PLATFORM_EMSCRIPTEN) + #include +#endif + +#include +#include +#include +#include +#include +#include + +using Json = nlohmann::json; + +#if __ANDROID__ +const std::filesystem::path BASE_PATH = ""; +#define ASSETS_PATH std::string("") +#elif __APPLE__ +const std::filesystem::path BASE_PATH = SDL_GetBasePath(); +#define ASSETS_PATH (BASE_PATH / "res/").string() +#else +const std::filesystem::path BASE_PATH = SDL_GetBasePath(); +#define ASSETS_PATH std::string("res/") +#endif + +#include "nuklear.h" +#include "nuklear_sdl3_ogl3.h" +#include \ No newline at end of file diff --git a/res/shaders/rainbow.glsl b/res/shaders/rainbow.glsl new file mode 100644 index 000000000..910136d59 --- /dev/null +++ b/res/shaders/rainbow.glsl @@ -0,0 +1,38 @@ +#version 300 es +precision highp float; + +// Common inputs +layout(location = 0) in vec3 aPosition; +layout(location = 1) in vec4 aColor; +layout(location = 2) in vec2 aTexCoord; + +uniform mat4 uViewProjection; +uniform float uTime; + +// Varyings +out vec4 vColor; +out vec2 vTexCoord; + + +out vec4 COLOR; + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +void vertex() { + vColor = aColor; + vTexCoord = aTexCoord; + gl_Position = uViewProjection * vec4(aPosition, 1.0); +} + +void fragment() { + + float hue = fract(vTexCoord.x * 2.0 + vTexCoord.y + uTime * 0.3); + vec3 rainbow = hsv2rgb(vec3(hue, 0.8, 1.0)); + + COLOR = vec4(rainbow, 1.0) * vColor; +} + diff --git a/res/shaders/retro.glsl b/res/shaders/retro.glsl new file mode 100644 index 000000000..24a7be847 --- /dev/null +++ b/res/shaders/retro.glsl @@ -0,0 +1,42 @@ +#version 300 es +precision highp float; + +layout(location = 0) in vec3 aPosition; +layout(location = 1) in vec4 aColor; +layout(location = 2) in vec2 aTexCoord; + +uniform mat4 uViewProjection; +uniform float uTime; +uniform sampler2D uTexture; + +out vec4 vColor; +out vec2 vTexCoord; + + +out vec4 COLOR; + +void vertex() { + vColor = aColor; + vTexCoord = aTexCoord; + + + vec3 pos = aPosition; + pos.z += sin(uTime * 2.0 + aPosition.x * 0.1) * 0.1; + + gl_Position = uViewProjection * vec4(pos, 1.0); +} + +void fragment() { + vec2 uv = vTexCoord; + + float pixelSize = 16.0; + uv = floor(uv * pixelSize) / pixelSize; + + vec4 texColor = texture(uTexture, uv); + + + float scanline = sin(vTexCoord.y * 300.0) * 0.1 + 0.9; + + COLOR = texColor * vColor * scanline; +} + diff --git a/res/shaders/wave.glsl b/res/shaders/wave.glsl new file mode 100644 index 000000000..4abdf3ee4 --- /dev/null +++ b/res/shaders/wave.glsl @@ -0,0 +1,32 @@ +#version 300 es +precision highp float; + +layout(location = 0) in vec3 aPosition; +layout(location = 1) in vec4 aColor; +layout(location = 2) in vec2 aTexCoord; + +uniform mat4 uViewProjection; +uniform float uTime; +uniform sampler2D uTexture; + +out vec4 vColor; +out vec2 vTexCoord; + +out vec4 COLOR; + +void vertex() { + vColor = aColor; + vTexCoord = aTexCoord; + gl_Position = uViewProjection * vec4(aPosition, 1.0); +} + +void fragment() { + vec2 uv = vTexCoord; + + uv.x += sin(uv.y * 10.0 + uTime * 3.0) * 0.05; + uv.y += cos(uv.x * 10.0 + uTime * 2.0) * 0.03; + + vec4 texColor = texture(uTexture, uv); + COLOR = texColor * vColor; +} + diff --git a/tests/example_canvas2d.cpp b/tests/example_canvas2d.cpp new file mode 100644 index 000000000..139c9eb75 --- /dev/null +++ b/tests/example_canvas2d.cpp @@ -0,0 +1,326 @@ +#include "../engine/public/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(); + + + golias::RenderingCanvas renderer(&rd); + renderer.initialize(1280, 720); + renderer.set_viewport_size(1280, 720); + renderer.set_scale_mode(golias::ScaleMode::KEEP); + + golias::RID my_texture = renderer.load_texture_from_file("res/icon.png"); + golias::RID my_texture2 = renderer.load_texture_from_file("res/monsters.png"); + + golias::TextureDescription pixel_art_desc; + pixel_art_desc.min_filter = golias::TextureFilter::NEAREST; + pixel_art_desc.mag_filter = golias::TextureFilter::NEAREST; + pixel_art_desc.wrap_u = golias::TextureWrap::CLAMP_TO_EDGE; + pixel_art_desc.wrap_v = golias::TextureWrap::CLAMP_TO_EDGE; + golias::RID pixel_texture = renderer.load_texture_from_file("res/monsters.png", pixel_art_desc); + + golias::TextureDescription smooth_desc; + smooth_desc.min_filter = golias::TextureFilter::LINEAR; + smooth_desc.mag_filter = golias::TextureFilter::LINEAR; + smooth_desc.wrap_u = golias::TextureWrap::REPEAT; + smooth_desc.wrap_v = golias::TextureWrap::REPEAT; + golias::RID smooth_texture = renderer.load_texture_from_file("res/icon.png", smooth_desc); + + golias::RID wavy_shader = renderer.create_shader_from_file("res/shaders/wave.glsl"); + golias::RID rainbow_shader = renderer.create_shader_from_file("res/shaders/rainbow.glsl"); + golias::RID retro_shader = renderer.create_shader_from_file("res/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/fonts/Minecraft.ttf", 24.0f); + TTF_Font* font = renderer.load_font_from_file( "res/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(golias::ScaleMode::NONE); + printf("Scale Mode: NONE (no scaling, centered)\n"); + } else if (event.key.key == SDLK_2) { + renderer.set_scale_mode(golias::ScaleMode::KEEP); + printf("Scale Mode: KEEP (aspect ratio preserved)\n"); + } else if (event.key.key == SDLK_3) { + renderer.set_scale_mode(golias::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.zoom += 0.5f * 0.016f; + if (camera.zoom > 3.0f) { + camera.zoom = 3.0f; + } + } + if (keys[SDL_SCANCODE_MINUS] || keys[SDL_SCANCODE_KP_MINUS]) { + camera.zoom -= 0.5f * 0.016f; + if (camera.zoom < 0.1f) { + camera.zoom = 0.1f; + } + } + + 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(golias::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, golias::Color::RED); + + renderer.draw_circle(400, 150, 60, golias::Color::GREEN); + + renderer.draw_triangle(550, 50, 650, 50, 600, 150, golias::Color::BLUE); + + renderer.draw_line(50, 250, 200, 250, golias::Color::YELLOW, 3.0f); + renderer.draw_line(50, 280, 200, 320, golias::Color::CYAN, 2.0f); + + renderer.draw_rect_outlined(250, 250, 120, 80, golias::Color::MAGENTA, 2.0f); + renderer.draw_circle(450, 300, 50, golias::Color::YELLOW); + + float rotation = time * 2.0f; + renderer.draw_rect(550, 250, 100, 100, golias::Color::CYAN, rotation); + + renderer.draw_texture(my_texture2, 50, 400, 150, 150); + + renderer.draw_texture(my_texture, 250, 400, 120, 120, golias::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, golias::Color::WHITE, "NEAREST Filter"); + + + if (wavy_shader != golias::INVALID_RID) { + renderer.draw_custom(wavy_shader, 200, 50, 100, 100, my_texture, golias::Color::WHITE); + } + + if (rainbow_shader != golias::INVALID_RID) { + renderer.draw_custom(rainbow_shader, 200, 170, 100, 100, golias::INVALID_RID, golias::Color::WHITE); + } + + if (retro_shader != golias::INVALID_RID) { + renderer.draw_custom(retro_shader, 200, 290, 100, 100, my_texture, golias::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}, golias::Color::WHITE, 0.0f, true, false); + + renderer.draw_texture_ex(700, 150, 80, 80, my_texture, {0, 0, 0, 0}, golias::Color::WHITE, 0.0f, false, true); + + renderer.draw_texture_ex(800, 150, 80, 80, my_texture, {0, 0, 0, 0}, golias::Color::WHITE, 0.0f, true, true); + + renderer.draw_texture_ex(750, 270, 80, 80, my_texture, {0, 0, 0, 0}, golias::Color::WHITE, time * 2.0f, false, false, wavy_shader); + + + renderer.draw_texture_ex(700, 380, 64, 64, pixel_texture, {0, 0, 32, 32}, golias::Color::WHITE); + + renderer.draw_text(mine, 400, 50, golias::Color::WHITE, "Hello World! 👋 Welcome!"); + renderer.draw_text(font, 400, 80, golias::Color::WHITE, "Frame: {} 🎮", static_cast(time * 60)); + renderer.draw_text(font, 400, 110, golias::Color::WHITE, "Position: ({}, {}) 📍", 123, 456); + renderer.draw_text(font, 400, 140, golias::Color::WHITE, "Time: {:.2f}s ⏱️", time); + renderer.draw_text(font, 400, 170, golias::Color::WHITE, "FPS: {} 🚀", 60); + renderer.draw_text(font, 400, 200, golias::Color::WHITE, "Mixed: ABC 123 😀 🎉 🔥 ⭐ XYZ"); + + + renderer.draw_text(font, 400, 240, golias::Color::GREEN, "Health: {}", 100); + renderer.draw_text(font, 400, 270, golias::Color::RED, "Score: 999,999 🏆"); + + renderer.end(); + + SDL_GL_SwapWindow(window); + + SDL_Delay(16); + } + + + if (wavy_shader != golias::INVALID_RID) { + renderer.destroy_shader(wavy_shader); + } + + if (rainbow_shader != golias::INVALID_RID) { + renderer.destroy_shader(rainbow_shader); + } + + if (retro_shader != golias::INVALID_RID) { + renderer.destroy_shader(retro_shader); + } + + 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