diff --git a/include/flip/application.h b/include/flip/application.h index 1a2d8d9..444a3bb 100644 --- a/include/flip/application.h +++ b/include/flip/application.h @@ -6,7 +6,8 @@ // flip::Application implementation. namespace flip { class Application; -} +struct Time; +} // namespace flip extern std::unique_ptr InstantiateApplication(); // Sokol forward declaration @@ -41,7 +42,7 @@ class Application { private: virtual bool Initialize(bool _headless) { return true; } - virtual LoopControl Update(float _time, float _dt, float _inv_dt) { + virtual LoopControl Update(const Time& _time) { return LoopControl::kContinue; } diff --git a/include/flip/camera.h b/include/flip/camera.h index b8de93b..956b51b 100644 --- a/include/flip/camera.h +++ b/include/flip/camera.h @@ -7,6 +7,8 @@ struct sapp_event; namespace flip { +struct Time; + // Camera view parameters, passed to the renderer. struct CameraView { float fov; @@ -19,7 +21,7 @@ class Camera { public: virtual ~Camera() = default; - virtual bool Update(float _time, float _dt, float _inv_dt) { return true; } + virtual bool Update(const Time& _time) { return true; } virtual bool Event(const sapp_event& _event) { return false; } virtual bool Menu() { return true; } diff --git a/include/flip/imdraw.h b/include/flip/imdraw.h index 415d834..0c7e8b0 100644 --- a/include/flip/imdraw.h +++ b/include/flip/imdraw.h @@ -21,8 +21,8 @@ struct ImMode { // Culling sg_cull_mode cull_mode = SG_CULLMODE_BACK; - // Blending - bool blending = false; + // Alpha blending + bool alpha_blending = false; bool alpha_test = false; bool alpha_to_coverage = false; }; diff --git a/include/flip/utils/loader.h b/include/flip/utils/loader.h index df7edf4..ffa129c 100644 --- a/include/flip/utils/loader.h +++ b/include/flip/utils/loader.h @@ -77,4 +77,4 @@ class SgAsyncImage { AsyncBuffer buffer_; }; -} // namespace flip \ No newline at end of file +} // namespace flip diff --git a/include/flip/utils/time.h b/include/flip/utils/time.h new file mode 100644 index 0000000..2eb921e --- /dev/null +++ b/include/flip/utils/time.h @@ -0,0 +1,35 @@ +#pragma once + +namespace flip { + +struct Time { + float elapsed; + float dt; + float inv_dt; +}; + +class TimeControl { + public: + Time Update(float _dt); + + bool Gui(); + + private: + // Current time, including scaling and freezes.. + float elapsed_ = 0.f; + + // Update time scale factor. + float scale_ = 1; + + // Update time freeze state. + bool freeze_ = false; + + // Fixed update rate, only applies to application update dt, not the real fps. + const float kFixedRateDefault = 60.f; + float fixed_rate_ = kFixedRateDefault; + + // Fixes update rat to a fixed value, instead of real_time. + bool fix_rate_ = false; +}; + +} // namespace flip \ No newline at end of file diff --git a/samples/imdraw/main.cpp b/samples/imdraw/main.cpp index 44d3094..beb9f5a 100644 --- a/samples/imdraw/main.cpp +++ b/samples/imdraw/main.cpp @@ -2,6 +2,7 @@ #include "flip/application.h" #include "flip/imdraw.h" +#include "flip/utils/time.h" // Implement the minimal flip::Application. class ImDraw : public flip::Application { @@ -9,13 +10,14 @@ class ImDraw : public flip::Application { ImDraw() : flip::Application(Settings{.title = "ImDraw"}) {} private: - virtual LoopControl Update(float _time, float _dt, float _inv_dt) override { - transform1_ = HMM_Rotate_RH(_time, HMM_Vec3{0, 1, 0}) * + virtual LoopControl Update(const flip::Time& _time) override { + auto elapsed = _time.elapsed; + transform1_ = HMM_Rotate_RH(elapsed, HMM_Vec3{0, 1, 0}) * HMM_Translate(HMM_Vec3{0, 6, 0}) * - HMM_Rotate_RH(HMM_PI / 4, - HMM_Vec3{std::cos(_time), std::sin(_time), 0}) * + HMM_Rotate_RH(HMM_PI / 4, HMM_Vec3{std::cos(elapsed), + std::sin(elapsed), 0}) * HMM_Scale(HMM_Vec3{3, 3, 3}); - transform2_ = transform1_ * HMM_Rotate_RH(_time, HMM_Vec3{0, 1, 0}); + transform2_ = transform1_ * HMM_Rotate_RH(elapsed, HMM_Vec3{0, 1, 0}); return LoopControl::kContinue; } @@ -41,7 +43,7 @@ class ImDraw : public flip::Application { transform1_, {.type = SG_PRIMITIVETYPE_TRIANGLE_STRIP, .cull_mode = SG_CULLMODE_NONE, - .blending = true}}; + .alpha_blending = true}}; drawer.color(flip::kYellow, .7f); diff --git a/samples/input/main.cpp b/samples/input/main.cpp index ab682c5..fe6cfb7 100644 --- a/samples/input/main.cpp +++ b/samples/input/main.cpp @@ -8,7 +8,7 @@ class Input : public flip::Application { Input() : flip::Application(Settings{.title = "Input"}) {} private: - virtual LoopControl Update(float _time, float _dt, float _inv_dt) override { + virtual LoopControl Update(const flip::Time& _time) override { if (keyboard_[SAPP_KEYCODE_Q].released()) { return LoopControl::kBreak; } diff --git a/samples/media/flip.xcf b/samples/media/flip.xcf new file mode 100644 index 0000000..b84c89e Binary files /dev/null and b/samples/media/flip.xcf differ diff --git a/samples/media/flip_grey.xcf b/samples/media/flip_grey.xcf new file mode 100644 index 0000000..d1f1403 Binary files /dev/null and b/samples/media/flip_grey.xcf differ diff --git a/samples/texture/main.cpp b/samples/texture/main.cpp index 52f7e8e..362467c 100644 --- a/samples/texture/main.cpp +++ b/samples/texture/main.cpp @@ -26,9 +26,9 @@ class Texture : public flip::Application { flip::kIdentity4, {.type = SG_PRIMITIVETYPE_TRIANGLE_STRIP, .cull_mode = SG_CULLMODE_NONE, - .blending = true, - .alpha_test = true, - .alpha_to_coverage = true}}; + .alpha_blending = alpha_blending_, + .alpha_test = alpha_test_, + .alpha_to_coverage = alpha_to_coverage_}}; if (texture_) { drawer.texture(image_.id(), sampler_.id()); @@ -56,6 +56,9 @@ class Texture : public flip::Application { if (ImGui::Checkbox("Linear filtering", &linear_)) { sampler_ = SetupSampler(linear_); } + ImGui::Checkbox("Enable alpha alpha_blending", &alpha_blending_); + ImGui::Checkbox("Enable alpha test", &alpha_test_); + ImGui::Checkbox("Enable alpha to coverage", &alpha_to_coverage_); ImGui::ColorEdit4("Color", color_.rgba); ImGui::EndMenu(); } @@ -70,6 +73,9 @@ class Texture : public flip::Application { flip::Color color_ = flip::kYellow; bool texture_ = true; bool linear_ = true; + bool alpha_blending_ = true; + bool alpha_test_ = true; + bool alpha_to_coverage_ = true; }; // Application instantiation function diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6dc335b..96013fe 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,6 +11,7 @@ add_library(flip ${PROJECT_SOURCE_DIR}/include/flip/utils/loader.h ${PROJECT_SOURCE_DIR}/include/flip/utils/profile.h ${PROJECT_SOURCE_DIR}/include/flip/utils/sokol_gfx.h + ${PROJECT_SOURCE_DIR}/include/flip/utils/time.h application.cpp impl/imdrawer.h impl/imdrawer.cpp @@ -28,6 +29,7 @@ add_library(flip utils/keyboard.cpp utils/loader.cpp utils/profile.cpp - utils/sokol_gfx.cpp) + utils/sokol_gfx.cpp + utils/time.cpp) target_include_directories(flip PUBLIC ${PROJECT_SOURCE_DIR}/include) target_link_libraries(flip sokol hmm stb_image) \ No newline at end of file diff --git a/src/application.cpp b/src/application.cpp index f9f8e27..28c33fd 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -8,6 +8,7 @@ #include "flip/camera.h" #include "flip/renderer.h" #include "flip/utils/profile.h" +#include "flip/utils/time.h" #include "impl/factory.h" // Sokol library @@ -138,14 +139,13 @@ class ApplicationCb { sfetch_dowork(); // Updates time. - const auto dt = static_cast(stm_sec(stm_laptime(&last_time_))); - const auto inv_dt = dt == 0.f ? 0.f : (1.f / dt); - const auto time = static_cast(stm_sec(last_time_)); - profile_frame_.push(dt * 1e3f); + const auto sys_dt = static_cast(stm_sec(stm_laptime(&last_time_))); + profile_frame_.push(sys_dt * 1e3f); + const auto time = time_control_.Update(sys_dt); { // Updates application Profile profile(profile_update_); - const auto control = application_->Update(time, dt, inv_dt); + const auto control = application_->Update(time); success &= control != Application::LoopControl::kBreakFailure; exit = control != Application::LoopControl::kContinue; } @@ -153,7 +153,7 @@ class ApplicationCb { // Renders application if (!headless_) { Profile profile(profile_render_); - success &= Display(time, dt, inv_dt); + success &= Display(time); } // Manages exit @@ -162,11 +162,11 @@ class ApplicationCb { } } - bool Display(float _time, float _dt, float _inv_dt) { + bool Display(const Time& _time) { assert(!headless_); bool success = true; - success &= camera_->Update(_time, _dt, _inv_dt); + success &= camera_->Update(_time); { // Default Rendering pass raii auto pass = Renderer::DefaultPass(*renderer_, camera_->GetCameraView()); @@ -208,18 +208,16 @@ class ApplicationCb { plot("Frame", profile_frame_); }; - static bool pop_up = false; if (ImGui::BeginMenu("Performance")) { - ImGui::MenuItem("Pop up", 0, &pop_up); - plot_all(); - ImGui::EndMenu(); - } - if (pop_up) { - ImGui::SetNextWindowSize(ImVec2(240, 320), ImGuiCond_Once); - if (ImGui::Begin("Performance", &pop_up, 0)) { + if (ImGui::TreeNodeEx("Time control")) { + time_control_.Gui(); + ImGui::TreePop(); + } + if (ImGui::TreeNodeEx("Performance")) { plot_all(); + ImGui::TreePop(); } - ImGui::End(); + ImGui::EndMenu(); } return true; } @@ -243,6 +241,7 @@ class ApplicationCb { ProfileRecord profile_frame_; // Time management + TimeControl time_control_; uint64_t last_time_ = 0; // Does application has a windows (head) diff --git a/src/extern/CMakeLists.txt b/src/extern/CMakeLists.txt index 84f0a62..55764bf 100644 --- a/src/extern/CMakeLists.txt +++ b/src/extern/CMakeLists.txt @@ -31,6 +31,7 @@ if(EMSCRIPTEN) target_compile_definitions(sokol PUBLIC SOKOL_GLES3) target_link_options(sokol PUBLIC -sUSE_WEBGL2=1) target_link_options(sokol PUBLIC -sMALLOC=emmalloc) + target_link_options(sokol PUBLIC -sALLOW_MEMORY_GROWTH=1) target_link_options(sokol PUBLIC -sFILESYSTEM=0) else() target_compile_definitions(sokol PUBLIC SOKOL_GLCORE33) @@ -51,7 +52,6 @@ endif() if(UNIX AND NOT APPLE AND NOT EMSCRIPTEN) find_package(X11 REQUIRED) - target_include_directories(sokol PRIVATE ${X11_INCLUDE_DIR}) target_link_libraries(sokol ${X11_LIBRARIES} Xi Xcursor) endif() diff --git a/src/impl/imdrawer.cpp b/src/impl/imdrawer.cpp index 75641a6..bae16d9 100644 --- a/src/impl/imdrawer.cpp +++ b/src/impl/imdrawer.cpp @@ -80,7 +80,7 @@ void ImDrawer::Begin(const HMM_Mat4& _view_proj, const HMM_Mat4& _transform, {.format = SG_VERTEXFORMAT_FLOAT4}, {.format = SG_VERTEXFORMAT_FLOAT2}}}, .depth = {.compare = _mode.z_compare, .write_enabled = _mode.z_write}, - .colors = {{.blend = {.enabled = _mode.blending, + .colors = {{.blend = {.enabled = _mode.alpha_blending, .src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA, .dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA diff --git a/src/impl/imdrawer.h b/src/impl/imdrawer.h index 3a89ae0..b7e2e70 100644 --- a/src/impl/imdrawer.h +++ b/src/impl/imdrawer.h @@ -9,7 +9,8 @@ namespace flip { inline bool operator==(ImMode const& _a, ImMode const& _b) noexcept { return _a.type == _b.type && _a.z_write == _b.z_write && _a.z_compare == _b.z_compare && _a.cull_mode == _b.cull_mode && - _a.blending == _b.blending && _a.alpha_test == _b.alpha_test && + _a.alpha_blending == _b.alpha_blending && + _a.alpha_test == _b.alpha_test && _a.alpha_to_coverage == _b.alpha_to_coverage; } @@ -18,7 +19,7 @@ struct ModeHash { auto hash = std::size_t(_mode.type) << 0 | std::size_t(_mode.z_write) << 3 | std::size_t(_mode.z_compare) << 4 | std::size_t(_mode.cull_mode) << 13 | - std::size_t(_mode.blending) << 15 | + std::size_t(_mode.alpha_blending) << 15 | std::size_t(_mode.alpha_test) << 16 | std::size_t(_mode.alpha_to_coverage) << 17; return hash; diff --git a/src/impl/orbit_camera.cpp b/src/impl/orbit_camera.cpp index 49d4cc7..341c36c 100644 --- a/src/impl/orbit_camera.cpp +++ b/src/impl/orbit_camera.cpp @@ -9,7 +9,7 @@ namespace flip { -bool OrbitCamera::Update(float _time, float _dt, float _inv_dt) { +bool OrbitCamera::Update(const Time& _time) { bool success = true; // Camera to eye vector diff --git a/src/impl/orbit_camera.h b/src/impl/orbit_camera.h index 47a2bc0..c5dd36d 100644 --- a/src/impl/orbit_camera.h +++ b/src/impl/orbit_camera.h @@ -11,7 +11,7 @@ class OrbitCamera : public Camera { virtual ~OrbitCamera() = default; protected: - virtual bool Update(float _time, float _dt, float _inv_dt) override; + virtual bool Update(const Time& _time) override; virtual bool Event(const sapp_event& _event) override; virtual bool Menu() override; diff --git a/src/impl/renderer_impl.cpp b/src/impl/renderer_impl.cpp index 6ddebe3..9c386ea 100644 --- a/src/impl/renderer_impl.cpp +++ b/src/impl/renderer_impl.cpp @@ -185,7 +185,7 @@ bool RendererImpl::DrawGrids(std::span _transforms, {.type = SG_PRIMITIVETYPE_TRIANGLE_STRIP, .z_write = false, .cull_mode = SG_CULLMODE_NONE, - .blending = true}}; + .alpha_blending = true}}; drawer.color(.5f, .7f, .8f, .6f); drawer.vertex(corner.X, corner.Y, corner.Z); diff --git a/src/utils/time.cpp b/src/utils/time.cpp new file mode 100644 index 0000000..764cd4e --- /dev/null +++ b/src/utils/time.cpp @@ -0,0 +1,39 @@ +#include "flip/utils/time.h" + +#include "imgui/imgui.h" + +namespace flip { + +Time TimeControl::Update(float _dt) { + auto dt = 0.f; + if (!freeze_) { + if (fix_rate_) { + dt = scale_ / fixed_rate_; + } else { + dt = _dt * scale_; + } + } + elapsed_ += dt; + + return {elapsed_, dt, dt == 0.f ? 0.f : (1.f / dt)}; +} + +bool TimeControl::Gui() { + ImGui::Checkbox("Freeze", &freeze_); + ImGui::SameLine(); + ImGui::Checkbox("Fix update rate", &fix_rate_); + if (ImGui::Button("Reset")) { + fixed_rate_ = kFixedRateDefault; + scale_ = 1.f; + } + ImGui::SameLine(); + if (fix_rate_) { + ImGui::SliderFloat("Update rate", &fixed_rate_, 1.f, 200.f, "%.2f fps"); + } else { + ImGui::SliderFloat("Time scale", &scale_, -10.f, 10.f, "%.2f", + ImGuiSliderFlags_Logarithmic); + } + return true; +} + +} // namespace flip \ No newline at end of file