From cac63e64f315647346602e584abc6929ae4b79fd Mon Sep 17 00:00:00 2001 From: Totto16 Date: Thu, 7 Nov 2024 21:23:17 +0100 Subject: [PATCH 01/51] feat: add render recording button, WIP --- src/graphics/renderer.cpp | 32 +++++++++++---- src/graphics/renderer.hpp | 12 ++++++ .../recording_component.cpp | 41 +++++++++++++++---- .../recording_selector/recording_selector.cpp | 2 + 4 files changed, 71 insertions(+), 16 deletions(-) diff --git a/src/graphics/renderer.cpp b/src/graphics/renderer.cpp index 5eb09c8f9..bdf333a59 100644 --- a/src/graphics/renderer.cpp +++ b/src/graphics/renderer.cpp @@ -5,8 +5,21 @@ //TODO(Totto): assert return values of all sdl functions + +Renderer::Renderer(SDL_Renderer* renderer) : m_renderer{ renderer } { + + if (m_renderer == nullptr) { + throw helper::InitializationError{ fmt::format("Failed creating a SDL Renderer: {}", SDL_GetError()) }; + } + + auto result = SDL_SetRenderDrawBlendMode(m_renderer, SDL_BLENDMODE_BLEND); + if (result < 0) { + throw helper::InitializationError{ fmt::format("Failed in setting BlendMode on Renderer: {}", SDL_GetError()) }; + } +} + Renderer::Renderer(const Window& window, const VSync v_sync) - : m_renderer{ SDL_CreateRenderer( + : Renderer{ SDL_CreateRenderer( window.get_sdl_window(), -1, (v_sync == VSync::Enabled ? SDL_RENDERER_PRESENTVSYNC : 0) | SDL_RENDERER_TARGETTEXTURE @@ -16,19 +29,20 @@ Renderer::Renderer(const Window& window, const VSync v_sync) | SDL_RENDERER_ACCELERATED #endif ) } { +} - if (m_renderer == nullptr) { - throw helper::InitializationError{ fmt::format("Failed creating a SDL Renderer: {}", SDL_GetError()) }; - } +Renderer Renderer::get_software_renderer(std::unique_ptr& surface) { + return Renderer{ SDL_CreateSoftwareRenderer(surface.get()) }; +} - auto result = SDL_SetRenderDrawBlendMode(m_renderer, SDL_BLENDMODE_BLEND); - if (result < 0) { - throw helper::InitializationError{ fmt::format("Failed in setting BlendMode on Renderer: {}", SDL_GetError()) }; - } +Renderer::Renderer(Renderer&& other) noexcept : m_renderer{ other.m_renderer } { + other.m_renderer = nullptr; } Renderer::~Renderer() { - SDL_DestroyRenderer(m_renderer); + if (m_renderer != nullptr) { + SDL_DestroyRenderer(m_renderer); + } } void Renderer::set_draw_color(const Color& color) const { diff --git a/src/graphics/renderer.hpp b/src/graphics/renderer.hpp index 13dd0d962..16ca3eaab 100644 --- a/src/graphics/renderer.hpp +++ b/src/graphics/renderer.hpp @@ -22,10 +22,22 @@ struct Renderer final { private: SDL_Renderer* m_renderer; + OOPETRIS_GRAPHICS_EXPORTED explicit Renderer(SDL_Renderer* renderer); + + public: OOPETRIS_GRAPHICS_EXPORTED explicit Renderer(const Window& window, VSync v_sync); + + OOPETRIS_GRAPHICS_EXPORTED static Renderer get_software_renderer(std::unique_ptr& surface); + OOPETRIS_GRAPHICS_EXPORTED Renderer(const Renderer&) = delete; OOPETRIS_GRAPHICS_EXPORTED Renderer& operator=(const Renderer&) = delete; + + + OOPETRIS_GRAPHICS_EXPORTED Renderer(Renderer&& other) noexcept; + + OOPETRIS_GRAPHICS_EXPORTED Renderer& operator=(Renderer&& other) = default; + OOPETRIS_GRAPHICS_EXPORTED ~Renderer(); OOPETRIS_GRAPHICS_EXPORTED void set_draw_color(const Color& color) const; diff --git a/src/scenes/recording_selector/recording_component.cpp b/src/scenes/recording_selector/recording_component.cpp index ba8c87a32..0f94c723f 100644 --- a/src/scenes/recording_selector/recording_component.cpp +++ b/src/scenes/recording_selector/recording_component.cpp @@ -5,6 +5,7 @@ #include "manager/font.hpp" #include "manager/resource_manager.hpp" #include "recording_component.hpp" +#include "ui/components/text_button.hpp" #include "ui/widget.hpp" #include @@ -20,23 +21,47 @@ custom_ui::RecordingComponent::RecordingComponent( ui::Focusable{focus_helper.focus_id()}, ui::Hoverable{layout.get_rect()}, m_main_layout{ utils::SizeIdentity<2>(), focus_helper.focus_id(), - ui::Direction::Vertical, - std::array{ 0.6 }, ui::RelativeMargin{layout.get_rect(), ui::Direction::Vertical,0.05}, std::pair{ 0.05, 0.03 }, + ui::Direction::Horizontal, + std::array{ 0.9 }, ui::RelativeMargin{layout.get_rect(), ui::Direction::Vertical,0.05}, std::pair{ 0.05, 0.03 }, layout,false },m_metadata{std::move(metadata)}{ - m_main_layout.add( + + auto text_layout_index = m_main_layout.add( + utils::SizeIdentity<2>(), focus_helper.focus_id(), ui::Direction::Vertical, std::array{ 0.6 }, + ui::RelativeMargin{ layout.get_rect(), ui::Direction::Vertical, 0.05 }, + std::pair{ 0.05, 0.03 } + ); + + auto* text_layout = m_main_layout.get(text_layout_index); + + + m_main_layout.add( + service_provider, "Render", service_provider->font_manager().get(FontId::Default), Color::white(), + focus_helper.focus_id(), + [](const ui::TextButton&) -> bool { + //TODO + + return false; + }, + std::pair{ 0.95, 0.85 }, + ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center }, + std::pair{ 0.1, 0.1 } + ); + + text_layout->add( service_provider, "name: ?", service_provider->font_manager().get(FontId::Default), Color::white(), std::pair{ 0.5, 0.5 }, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center } ); - const auto information_layout_index = m_main_layout.add( + + const auto information_layout_index = text_layout->add( utils::SizeIdentity<3>(), focus_helper.focus_id(), ui::Direction::Horizontal, std::array{ 0.33, 0.66 }, ui::AbsolutMargin{ 10 }, std::pair{ 0.05, 0.03 } ); - auto* const information_layout = m_main_layout.get(information_layout_index); + auto* const information_layout = text_layout->get(information_layout_index); information_layout->add( @@ -104,9 +129,11 @@ helper::BoolWrapper> custom_ui::Reco [[nodiscard]] std::tuple custom_ui::RecordingComponent::get_texts() { - auto* name_text = m_main_layout.get(0); + auto* text_layout = m_main_layout.get(0); + + auto* name_text = text_layout->get(0); - auto* information_layout = m_main_layout.get(1); + auto* information_layout = text_layout->get(1); auto* source_text = information_layout->get(0); auto* date_text = information_layout->get(1); diff --git a/src/scenes/recording_selector/recording_selector.cpp b/src/scenes/recording_selector/recording_selector.cpp index 9f82116bf..5353807ea 100644 --- a/src/scenes/recording_selector/recording_selector.cpp +++ b/src/scenes/recording_selector/recording_selector.cpp @@ -218,6 +218,8 @@ namespace scenes { auto focus_helper = ui::FocusHelper{ 3 }; + //TODO(Totto): sort by date, get the date from additional information or the file creation date + for (const auto& metadata : metadata_vector) { scroll_layout->add( ui::RelativeItemSize{ scroll_layout->layout(), 0.2 }, m_service_provider, std::ref(focus_helper), From e2dc40a982d653c5d33c5d9ab517b3fe8b617895 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Thu, 7 Nov 2024 23:55:14 +0100 Subject: [PATCH 02/51] feat: add render video functionality, WIP only available under linux with many hacks atm, so still heaviliy in WIP --- src/game/game.cpp | 13 +- src/game/game.hpp | 11 +- src/graphics/meson.build | 20 ++ src/graphics/video_renderer.cpp | 194 ++++++++++++++++++ src/graphics/video_renderer.hpp | 102 +++++++++ src/graphics/video_renderer_linux.cpp | 126 ++++++++++++ src/helper/clock_source.cpp | 29 +++ src/helper/clock_source.hpp | 16 ++ .../recording_component.cpp | 2 +- .../recording_selector/recording_selector.cpp | 16 +- src/scenes/replay_game/replay_game.hpp | 1 - src/ui/layouts/focus_layout.cpp | 1 - 12 files changed, 524 insertions(+), 7 deletions(-) create mode 100644 src/graphics/video_renderer.cpp create mode 100644 src/graphics/video_renderer.hpp create mode 100644 src/graphics/video_renderer_linux.cpp diff --git a/src/game/game.cpp b/src/game/game.cpp index c44ee8bdd..d0e05fd97 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -12,9 +12,20 @@ Game::Game( u32 simulation_frequency, const ui::Layout& layout, bool is_top_level +) + : Game{ service_provider, input, starting_parameters, std::make_shared(simulation_frequency), + layout, is_top_level } { } + +Game::Game( + ServiceProvider* const service_provider, + const std::shared_ptr& input, + const tetrion::StartingParameters& starting_parameters, + const std::shared_ptr& clock_source, + const ui::Layout& layout, + bool is_top_level ) : ui::Widget{ layout, ui::WidgetType::Component, is_top_level }, - m_clock_source{ std::make_unique(simulation_frequency) }, + m_clock_source{ clock_source }, m_input{ input } { diff --git a/src/game/game.hpp b/src/game/game.hpp index 0535edb8c..a9762f56c 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -12,7 +12,7 @@ struct Game : public ui::Widget { private: using TetrionHeaders = std::vector; - std::unique_ptr m_clock_source; + std::shared_ptr m_clock_source; SimulationStep m_simulation_step_index{ 0 }; std::unique_ptr m_tetrion; std::shared_ptr m_input; @@ -28,6 +28,15 @@ struct Game : public ui::Widget { bool is_top_level ); + OOPETRIS_GRAPHICS_EXPORTED explicit Game( + ServiceProvider* service_provider, + const std::shared_ptr& input, + const tetrion::StartingParameters& starting_parameters, + const std::shared_ptr& clock_source, + const ui::Layout& layout, + bool is_top_level + ); + OOPETRIS_GRAPHICS_EXPORTED void update() override; OOPETRIS_GRAPHICS_EXPORTED void render(const ServiceProvider& service_provider) const override; diff --git a/src/graphics/meson.build b/src/graphics/meson.build index 098f66806..6805e20a0 100644 --- a/src/graphics/meson.build +++ b/src/graphics/meson.build @@ -8,6 +8,26 @@ graphics_src_files += files( 'text.hpp', 'texture.cpp', 'texture.hpp', + 'video_renderer.cpp', + 'video_renderer.hpp', 'window.cpp', 'window.hpp', ) + + +# TODO: make this optional +if host_machine.system() == 'darwin' +graphics_src_files += files( + 'video_renderer_mac.cpp', +) +elif host_machine.system() == 'linux' + graphics_src_files += files( + 'video_renderer_linux.cpp', +) +elif host_machine.system() == 'windows' + graphics_src_files += files( + 'video_renderer_windows.cpp', +) +else + error('unsuported system for video rendering: ' + host_machine.system()) +endif diff --git a/src/graphics/video_renderer.cpp b/src/graphics/video_renderer.cpp new file mode 100644 index 000000000..c90fdb269 --- /dev/null +++ b/src/graphics/video_renderer.cpp @@ -0,0 +1,194 @@ + + +#include "video_renderer.hpp" + +#include + +VideoRenderer::VideoRenderer( + ServiceProvider* service_provider, + const std::filesystem::path& recording_path, + shapes::UPoint size +) + : m_main_provider{ service_provider }, + m_size{ size } { + auto* surface = SDL_CreateRGBSurface(0, static_cast(size.x), static_cast(size.y), 32, 0, 0, 0, 0); + + if (surface == nullptr) { + throw std::runtime_error{ fmt::format("Failed creating a SDL RGB Surface: {}", SDL_GetError()) }; + } + + m_surface.reset(surface); + auto renderer = Renderer::get_software_renderer(m_surface); + + m_renderer = std::make_unique(std::move(renderer)); + + initialize_games(recording_path); +} + +void VideoRenderer::initialize_games(const std::filesystem::path& recording_path) { + + auto [parameters, information] = input::get_game_parameters_for_replay(this, recording_path); + + auto layout = ui::FullScreenLayout{ + shapes::URect{ { 0, 0 }, m_size } + }; + + std::vector layouts{}; + layouts.reserve(parameters.size()); + + if (parameters.empty()) { + throw std::runtime_error("An empty recording file isn't supported"); + } else if (parameters.size() == 1) { // NOLINT(readability-else-after-return,llvm-else-after-return) + layouts.push_back(ui::RelativeLayout{ layout, 0.02, 0.01, 0.96, 0.98 }); + } else if (parameters.size() == 2) { + layouts.push_back(ui::RelativeLayout{ layout, 0.02, 0.01, 0.46, 0.98 }); + layouts.push_back(ui::RelativeLayout{ layout, 0.52, 0.01, 0.46, 0.98 }); + } else { + + //TODO(Totto): support bigger layouts than just 2 + throw std::runtime_error("At the moment only replays from up to two players are supported"); + } + + m_clock = std::make_shared(); + + + for (decltype(parameters.size()) i = 0; i < parameters.size(); ++i) { + auto [input, starting_parameters] = std::move(parameters.at(i)); + + m_games.emplace_back( + std::make_unique(this, std::move(input), starting_parameters, m_clock, layouts.at(i), false) + ); + } +} + + +VideoRenderer::~VideoRenderer() { + if (m_surface) { + SDL_FreeSurface(m_surface.get()); + } +} + + +std::optional VideoRenderer::render( + const std::string& destination_path, + u32 fps, + const std::function& progress_callback +) { + + auto backend = VideoRendererBackend{ destination_path }; + + if (auto result = backend.setup(fps, m_size); result.has_value()) { + return fmt::format("No video renderer backend available: {}", result.value()); + } + + auto all_games_finished = [this]() -> bool { + for (const auto& game : m_games) { + if (not game->is_game_finished()) { + return false; + } + } + + return true; + }; + + //TODO: this is just a dummy thing atm, change that + double progress = 0.0; + + while (all_games_finished()) { + progress_callback(progress); + + for (const auto& game : m_games) { + game->update(); + game->render(*this); + } + + backend.add_frame(m_surface.get()); + m_clock->increment_simulation_step_index(); + + progress += 0.1; + + progress_callback(progress); + } + + backend.finish(false); + return std::nullopt; +} + + +// implementation of ServiceProvider + +[[nodiscard]] EventDispatcher& VideoRenderer::event_dispatcher() { + return m_main_provider->event_dispatcher(); +} + +[[nodiscard]] const EventDispatcher& VideoRenderer::event_dispatcher() const { + return m_main_provider->event_dispatcher(); +} + +FontManager& VideoRenderer::font_manager() { + return m_main_provider->font_manager(); +} + +[[nodiscard]] const FontManager& VideoRenderer::font_manager() const { + return m_main_provider->font_manager(); +} + +CommandLineArguments& VideoRenderer::command_line_arguments() { + return m_main_provider->command_line_arguments(); +} + +[[nodiscard]] const CommandLineArguments& VideoRenderer::command_line_arguments() const { + return m_main_provider->command_line_arguments(); +} + +SettingsManager& VideoRenderer::settings_manager() { + return m_main_provider->settings_manager(); +} + +[[nodiscard]] const SettingsManager& VideoRenderer::settings_manager() const { + return m_main_provider->settings_manager(); +} + +MusicManager& VideoRenderer::music_manager() { + return m_main_provider->music_manager(); +} + +[[nodiscard]] const MusicManager& VideoRenderer::music_manager() const { + return m_main_provider->music_manager(); +} + +[[nodiscard]] const Renderer& VideoRenderer::renderer() const { + return *m_renderer; +} + +[[nodiscard]] const Window& VideoRenderer::window() const { + return m_main_provider->window(); +} + +[[nodiscard]] Window& VideoRenderer::window() { + return m_main_provider->window(); +} + +[[nodiscard]] input::InputManager& VideoRenderer::input_manager() { + return m_main_provider->input_manager(); +} + +[[nodiscard]] const input::InputManager& VideoRenderer::input_manager() const { + return m_main_provider->input_manager(); +} + +[[nodiscard]] const std::unique_ptr& VideoRenderer::api() const { + return m_main_provider->api(); +} + +#if defined(_HAVE_DISCORD_SDK) + +[[nodiscard]] std::optional& VideoRenderer::discord_instance() { + return m_main_provider->discord_instance(); +} + +[[nodiscard]] const std::optional& VideoRenderer::discord_instance() const { + return m_main_provider->discord_instance(); +} + +#endif diff --git a/src/graphics/video_renderer.hpp b/src/graphics/video_renderer.hpp new file mode 100644 index 000000000..e6f03ae4b --- /dev/null +++ b/src/graphics/video_renderer.hpp @@ -0,0 +1,102 @@ + + +#pragma once + +#include +#include + +#include "game/game.hpp" +#include "graphics/rect.hpp" +#include "helper/windows.hpp" +#include "manager/service_provider.hpp" +#include "renderer.hpp" + +struct VideoRenderer : ServiceProvider { +private: + std::unique_ptr m_surface; + std::unique_ptr m_renderer; + std::vector> m_games; + ServiceProvider* m_main_provider; + shapes::UPoint m_size; + std::shared_ptr m_clock; + + void initialize_games(const std::filesystem::path& recording_path); + +public: + OOPETRIS_GRAPHICS_EXPORTED explicit VideoRenderer( + ServiceProvider* service_provider, + const std::filesystem::path& recording_path, + shapes::UPoint size + ); + + std::optional + render(const std::string& destination_path, u32 fps, const std::function& progress_callback); + + ~VideoRenderer(); + + + // implementation of ServiceProvider + + [[nodiscard]] EventDispatcher& event_dispatcher() override; + + [[nodiscard]] const EventDispatcher& event_dispatcher() const override; + + FontManager& font_manager() override; + + [[nodiscard]] const FontManager& font_manager() const override; + + CommandLineArguments& command_line_arguments() override; + + [[nodiscard]] const CommandLineArguments& command_line_arguments() const override; + + SettingsManager& settings_manager() override; + + [[nodiscard]] const SettingsManager& settings_manager() const override; + + MusicManager& music_manager() override; + + [[nodiscard]] const MusicManager& music_manager() const override; + + [[nodiscard]] const Renderer& renderer() const override; + + [[nodiscard]] const Window& window() const override; + + [[nodiscard]] Window& window() override; + + [[nodiscard]] input::InputManager& input_manager() override; + + [[nodiscard]] const input::InputManager& input_manager() const override; + + [[nodiscard]] const std::unique_ptr& api() const override; + +#if defined(_HAVE_DISCORD_SDK) + + [[nodiscard]] std::optional& discord_instance() override; + + [[nodiscard]] const std::optional& discord_instance() const override; + +#endif +}; + + +struct Decoder; + + +//TODO(Totto): also support library and not only subprocess call +// See e.g. https://github.com/Raveler/ffmpeg-cpp +struct VideoRendererBackend { +private: + std::string m_destination_path; + std::unique_ptr m_decoder; + +public: + OOPETRIS_GRAPHICS_EXPORTED explicit VideoRendererBackend(const std::filesystem::path& destination_path); + + OOPETRIS_GRAPHICS_EXPORTED ~VideoRendererBackend(); + + OOPETRIS_GRAPHICS_EXPORTED [[nodiscard]] std::optional setup(u32 fps, shapes::UPoint size); + + bool add_frame(SDL_Surface* surface); + + bool finish(bool cancel); +}; diff --git a/src/graphics/video_renderer_linux.cpp b/src/graphics/video_renderer_linux.cpp new file mode 100644 index 000000000..cae919eb7 --- /dev/null +++ b/src/graphics/video_renderer_linux.cpp @@ -0,0 +1,126 @@ + +#include "video_renderer.hpp" + +#include +#include + +#include + +#include +#include +#include +#include + +struct Decoder { + int pipe; + pid_t pid; +}; + +// inspired by: https://github.com/tsoding/musializer/blob/762a729ff69ba1f984b0f2604e0eac08af46327c/src/ffmpeg_linux.c +VideoRendererBackend::VideoRendererBackend(const std::filesystem::path& destination_path) + : m_destination_path{ destination_path }, + m_decoder{ nullptr } { } + +VideoRendererBackend::~VideoRendererBackend() = default; + +namespace { + + constexpr const int READ_END = 0; + constexpr const int WRITE_END = 1; + +} // namespace + + +std::optional VideoRendererBackend::setup(u32 fps, shapes::UPoint size) { + + std::array pipefd = { 0, 0 }; + + if (pipe(pipefd.data()) < 0) { + return fmt::format("FFMPEG: Could not create a pipe: {}", strerror(errno)); + } + + pid_t child = fork(); + if (child < 0) { + return fmt::format("FFMPEG: could not fork a child: {}", strerror(errno)); + } + + if (child == 0) { + if (dup2(pipefd.at(READ_END), STDIN_FILENO) < 0) { + std::cerr << "FFMPEG CHILD: could not reopen read end of pipe as stdin: " << strerror(errno) << "\n"; + std::exit(1); + } + close(pipefd[WRITE_END]); + + std::string resolution = fmt::format("{}x{}", size.x, size.y); + + std::string framerate = fmt::format("{}", fps); + + //TODO(Totto): support audio, that loops the music as in the main game + int ret = + execlp("ffmpeg", "ffmpeg", "-loglevel", "verbose", "-y", + + "-f", "rawvideo", "-pix_fmt", "rgba", "-s", resolution.c_str(), "-r", framerate.c_str(), "-i", + "-", "-c:v", "libx264", "-vb", "2500k", "-c:a", "aac", "-ab", "200k", "-pix_fmt", "yuv420p", + m_destination_path.c_str(), static_cast(nullptr)); + if (ret < 0) { + std::cerr << "FFMPEG CHILD: could not run ffmpeg as a child process: " << strerror(errno) << "\n"; + std::exit(1); + } + UNREACHABLE(); + std::exit(1); + } + + if (close(pipefd[READ_END]) < 0) { + spdlog::error("FFMPEG: could not close read end of the pipe on the parent's end: {}", strerror(errno)); + } + + m_decoder = std::make_unique(pipefd[WRITE_END], child); + return std::nullopt; +} + +bool VideoRendererBackend::add_frame(SDL_Surface* surface) { + + for (size_t y = surface->h; y > 0; --y) { + if (write(m_decoder->pipe, surface->pixels, surface->h * surface->pitch) < 0) { + spdlog::error("FFMPEG: failed to write into ffmpeg pipe: {}", strerror(errno)); + return false; + } + } + return true; +} + +bool VideoRendererBackend::finish(bool cancel) { + + + if (close(m_decoder->pipe) < 0) { + spdlog::warn("FFMPEG: could not close write end of the pipe on the parent's end: {}", strerror(errno)); + } + + if (cancel) + kill(m_decoder->pid, SIGKILL); + + while (true) { + int wstatus = 0; + if (waitpid(m_decoder->pid, &wstatus, 0) < 0) { + spdlog::error("FFMPEG: could not wait for ffmpeg child process to finish: {}", strerror(errno)); + return false; + } + + if (WIFEXITED(wstatus)) { + int exit_status = WEXITSTATUS(wstatus); + if (exit_status != 0) { + spdlog::error("FFMPEG: ffmpeg exited with code {}", exit_status); + return false; + } + + return true; + } + + if (WIFSIGNALED(wstatus)) { + spdlog::error("FFMPEG: ffmpeg got terminated by {}", strsignal(WTERMSIG(wstatus))); + return false; + } + } + + UNREACHABLE(); +} diff --git a/src/helper/clock_source.cpp b/src/helper/clock_source.cpp index 385d8247c..00e39ae8d 100644 --- a/src/helper/clock_source.cpp +++ b/src/helper/clock_source.cpp @@ -1,4 +1,5 @@ #include "helper/clock_source.hpp" +#include #include #include @@ -45,3 +46,31 @@ double LocalClock::resume() { spdlog::info("resuming clock (duration of pause: {} s)", duration); return duration; } + + +ManualClock::ManualClock() = default; + +[[nodiscard]] SimulationStep ManualClock::simulation_step_index() const { + return m_simulation_step_index; +} + +bool ManualClock::can_be_paused() { + return false; +} + +void ManualClock::pause() { + UNREACHABLE(); +} + +double ManualClock::resume() { + UNREACHABLE(); +} + + +void ManualClock::increment_simulation_step_index() { + m_simulation_step_index++; +} + +void ManualClock::set_simulation_step_index(SimulationStep index) { + m_simulation_step_index = index; +} diff --git a/src/helper/clock_source.hpp b/src/helper/clock_source.hpp index bd3ad3c5f..404ba5494 100644 --- a/src/helper/clock_source.hpp +++ b/src/helper/clock_source.hpp @@ -40,3 +40,19 @@ struct LocalClock : public ClockSource { OOPETRIS_GRAPHICS_EXPORTED void pause() override; OOPETRIS_GRAPHICS_EXPORTED double resume() override; }; + + +struct ManualClock : public ClockSource { +private: + SimulationStep m_simulation_step_index{ 0 }; + +public: + OOPETRIS_GRAPHICS_EXPORTED explicit ManualClock(); + OOPETRIS_GRAPHICS_EXPORTED [[nodiscard]] SimulationStep simulation_step_index() const override; + OOPETRIS_GRAPHICS_EXPORTED bool can_be_paused() override; + OOPETRIS_GRAPHICS_EXPORTED void pause() override; + OOPETRIS_GRAPHICS_EXPORTED double resume() override; + + OOPETRIS_GRAPHICS_EXPORTED void increment_simulation_step_index(); + OOPETRIS_GRAPHICS_EXPORTED void set_simulation_step_index(SimulationStep index); +}; diff --git a/src/scenes/recording_selector/recording_component.cpp b/src/scenes/recording_selector/recording_component.cpp index 0f94c723f..bf7fbe861 100644 --- a/src/scenes/recording_selector/recording_component.cpp +++ b/src/scenes/recording_selector/recording_component.cpp @@ -40,7 +40,7 @@ custom_ui::RecordingComponent::RecordingComponent( service_provider, "Render", service_provider->font_manager().get(FontId::Default), Color::white(), focus_helper.focus_id(), [](const ui::TextButton&) -> bool { - //TODO + //TODO: do rendering here, allow hover and click in this situation, it doesn't work as of now return false; }, diff --git a/src/scenes/recording_selector/recording_selector.cpp b/src/scenes/recording_selector/recording_selector.cpp index 5353807ea..fea5ee945 100644 --- a/src/scenes/recording_selector/recording_selector.cpp +++ b/src/scenes/recording_selector/recording_selector.cpp @@ -8,6 +8,7 @@ #include +#include "graphics/video_renderer.hpp" #include "graphics/window.hpp" #include "helper/constants.hpp" #include "helper/graphic_utils.hpp" @@ -90,6 +91,18 @@ namespace scenes { // action is a reference to a structure inside m_next_command, so resetting it means, we need to copy everything out of it m_next_command = std::nullopt; + auto ren = VideoRenderer{ + m_service_provider, recording_path, shapes::UPoint{ 1280, 720 } + }; + + //TODO: do this in a seperate thread + ren.render("test.mp4", 60, [](double progress) { + spdlog::info("Progress: {}", progress); + }); + + //TODO: do this in a seperate scene, with a loading bar + return UpdateResult{ SceneUpdate::StopUpdating, std::nullopt }; + /* return UpdateResult{ SceneUpdate::StopUpdating, Scene::RawSwitch{ "ReplayGame", @@ -97,7 +110,7 @@ namespace scenes { m_service_provider, ui::FullScreenLayout{ m_service_provider->window() }, recording_path ) } - }; + }; */ } #if defined(_HAVE_FILE_DIALOGS) @@ -137,7 +150,6 @@ namespace scenes { bool RecordingSelector::handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) { - if (const auto event_result = m_main_layout.handle_event(input_manager, event); event_result) { if (const auto additional = event_result.get_additional(); additional.has_value() and additional.value().first == ui::EventHandleType::RequestAction) { diff --git a/src/scenes/replay_game/replay_game.hpp b/src/scenes/replay_game/replay_game.hpp index c452edcdc..589eaabf9 100644 --- a/src/scenes/replay_game/replay_game.hpp +++ b/src/scenes/replay_game/replay_game.hpp @@ -9,7 +9,6 @@ namespace scenes { private: enum class NextScene : u8 { Pause, Settings }; - std::optional m_next_scene; std::vector> m_games; diff --git a/src/ui/layouts/focus_layout.cpp b/src/ui/layouts/focus_layout.cpp index aa87dd5e0..251542fc6 100644 --- a/src/ui/layouts/focus_layout.cpp +++ b/src/ui/layouts/focus_layout.cpp @@ -98,7 +98,6 @@ ui::Widget::EventHandleResult ui::FocusLayout::handle_focus_change_events( Widget::EventHandleResult handled = false; - if (m_focus_id.has_value()) { const auto& widget = m_widgets.at(focusable_index_by_id(m_focus_id.value())); From 1519f0496f677dde569c683fdeb7b4bfb37bae01 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Fri, 8 Nov 2024 01:06:21 +0100 Subject: [PATCH 03/51] fix: fix a few minor issues with the video render --- src/graphics/sdl_context.cpp | 13 +++++++++++++ src/graphics/video_renderer.cpp | 8 +++++--- src/graphics/video_renderer_linux.cpp | 8 +++----- src/helper/clock_source.cpp | 2 +- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/graphics/sdl_context.cpp b/src/graphics/sdl_context.cpp index e0c426f1d..b86f5e72d 100644 --- a/src/graphics/sdl_context.cpp +++ b/src/graphics/sdl_context.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #if defined(__CONSOLE__) #include "helper/console_helpers.hpp" @@ -21,6 +22,18 @@ SdlContext::SdlContext() { throw helper::InitializationError{ fmt::format("Failed in initializing sdl: {}", SDL_GetError()) }; } + + // when using gdb / lldb to debug and you click something with the mouse, no other application can use the mouse, which is annoying, so not using this feature in debug mode +#if !defined(NDEBUG) + const auto hint_mouse_result = SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0"); + + if (hint_mouse_result != SDL_TRUE) { + // this is non fatal, so not returning + spdlog::error("Failed to set the SDL_HINT_MOUSE_AUTO_CAPTURE hint: {}", SDL_GetError()); + } +#endif + + if (TTF_Init() < 0) { throw helper::InitializationError{ fmt::format("Failed in initializing sdl ttf: {}", TTF_GetError()) }; } diff --git a/src/graphics/video_renderer.cpp b/src/graphics/video_renderer.cpp index c90fdb269..622f519a7 100644 --- a/src/graphics/video_renderer.cpp +++ b/src/graphics/video_renderer.cpp @@ -94,12 +94,14 @@ std::optional VideoRenderer::render( //TODO: this is just a dummy thing atm, change that double progress = 0.0; - while (all_games_finished()) { + while (not all_games_finished()) { progress_callback(progress); for (const auto& game : m_games) { - game->update(); - game->render(*this); + if (not game->is_game_finished()) { + game->update(); + game->render(*this); + } } backend.add_frame(m_surface.get()); diff --git a/src/graphics/video_renderer_linux.cpp b/src/graphics/video_renderer_linux.cpp index cae919eb7..623aa4341 100644 --- a/src/graphics/video_renderer_linux.cpp +++ b/src/graphics/video_renderer_linux.cpp @@ -80,11 +80,9 @@ std::optional VideoRendererBackend::setup(u32 fps, shapes::UPoint s bool VideoRendererBackend::add_frame(SDL_Surface* surface) { - for (size_t y = surface->h; y > 0; --y) { - if (write(m_decoder->pipe, surface->pixels, surface->h * surface->pitch) < 0) { - spdlog::error("FFMPEG: failed to write into ffmpeg pipe: {}", strerror(errno)); - return false; - } + if (write(m_decoder->pipe, surface->pixels, static_cast(surface->h) * surface->pitch) < 0) { + spdlog::error("FFMPEG: failed to write into ffmpeg pipe: {}", strerror(errno)); + return false; } return true; } diff --git a/src/helper/clock_source.cpp b/src/helper/clock_source.cpp index 00e39ae8d..ab88da659 100644 --- a/src/helper/clock_source.cpp +++ b/src/helper/clock_source.cpp @@ -68,7 +68,7 @@ double ManualClock::resume() { void ManualClock::increment_simulation_step_index() { - m_simulation_step_index++; + ++m_simulation_step_index; } void ManualClock::set_simulation_step_index(SimulationStep index) { From 8a9829ab80e3d4a4a1c31164591445acb47b770c Mon Sep 17 00:00:00 2001 From: Totto16 Date: Fri, 8 Nov 2024 01:15:37 +0100 Subject: [PATCH 04/51] cleanup: de-duplicate code for layout generation --- src/game/layout.cpp | 23 +++++++++++++++++++++++ src/game/layout.hpp | 15 +++++++++++++++ src/game/meson.build | 3 +++ src/graphics/video_renderer.cpp | 18 ++---------------- src/scenes/replay_game/replay_game.cpp | 17 ++--------------- 5 files changed, 45 insertions(+), 31 deletions(-) create mode 100644 src/game/layout.cpp create mode 100644 src/game/layout.hpp diff --git a/src/game/layout.cpp b/src/game/layout.cpp new file mode 100644 index 000000000..622b81bf4 --- /dev/null +++ b/src/game/layout.cpp @@ -0,0 +1,23 @@ + +#include "./layout.hpp" + +std::vector game::get_layouts_for(u32 players, const ui::Layout& layout) { + + std::vector layouts{}; + layouts.reserve(players); + + if (players == 0) { + throw std::runtime_error("An empty recording file isn't supported"); + } else if (players == 1) { // NOLINT(readability-else-after-return,llvm-else-after-return) + layouts.push_back(ui::RelativeLayout{ layout, 0.02, 0.01, 0.96, 0.98 }); + } else if (players == 2) { + layouts.push_back(ui::RelativeLayout{ layout, 0.02, 0.01, 0.46, 0.98 }); + layouts.push_back(ui::RelativeLayout{ layout, 0.52, 0.01, 0.46, 0.98 }); + } else { + + //TODO(Totto): support bigger layouts than just 2 + throw std::runtime_error("At the moment only replays from up to two players are supported"); + } + + return layouts; +} diff --git a/src/game/layout.hpp b/src/game/layout.hpp new file mode 100644 index 000000000..a21598654 --- /dev/null +++ b/src/game/layout.hpp @@ -0,0 +1,15 @@ + + +#pragma once + +#include + +#include "helper/windows.hpp" +#include "ui/layout.hpp" + + +namespace game { + OOPETRIS_GRAPHICS_EXPORTED [[nodiscard]] std::vector + get_layouts_for(u32 players, const ui::Layout& layout); + +} diff --git a/src/game/meson.build b/src/game/meson.build index 9b5f0f6c9..32290bea0 100644 --- a/src/game/meson.build +++ b/src/game/meson.build @@ -9,6 +9,9 @@ graphics_src_files += files( 'graphic_helpers.hpp', 'grid.cpp', 'grid.hpp', + 'layout.cpp', + 'layout.hpp', + 'rotation.cpp', 'rotation.hpp', 'simulated_tetrion.cpp', diff --git a/src/graphics/video_renderer.cpp b/src/graphics/video_renderer.cpp index 622f519a7..22b9fcef6 100644 --- a/src/graphics/video_renderer.cpp +++ b/src/graphics/video_renderer.cpp @@ -1,6 +1,7 @@ #include "video_renderer.hpp" +#include "game/layout.hpp" #include @@ -33,25 +34,10 @@ void VideoRenderer::initialize_games(const std::filesystem::path& recording_path shapes::URect{ { 0, 0 }, m_size } }; - std::vector layouts{}; - layouts.reserve(parameters.size()); - - if (parameters.empty()) { - throw std::runtime_error("An empty recording file isn't supported"); - } else if (parameters.size() == 1) { // NOLINT(readability-else-after-return,llvm-else-after-return) - layouts.push_back(ui::RelativeLayout{ layout, 0.02, 0.01, 0.96, 0.98 }); - } else if (parameters.size() == 2) { - layouts.push_back(ui::RelativeLayout{ layout, 0.02, 0.01, 0.46, 0.98 }); - layouts.push_back(ui::RelativeLayout{ layout, 0.52, 0.01, 0.46, 0.98 }); - } else { - - //TODO(Totto): support bigger layouts than just 2 - throw std::runtime_error("At the moment only replays from up to two players are supported"); - } + std::vector layouts = game::get_layouts_for(parameters.size(), layout); m_clock = std::make_shared(); - for (decltype(parameters.size()) i = 0; i < parameters.size(); ++i) { auto [input, starting_parameters] = std::move(parameters.at(i)); diff --git a/src/scenes/replay_game/replay_game.cpp b/src/scenes/replay_game/replay_game.cpp index 75f0a2df8..37239d4ec 100644 --- a/src/scenes/replay_game/replay_game.cpp +++ b/src/scenes/replay_game/replay_game.cpp @@ -1,6 +1,7 @@ #include "replay_game.hpp" #include "../single_player_game/game_over.hpp" #include "../single_player_game/pause.hpp" +#include "game/layout.hpp" #include "helper/constants.hpp" #include "helper/graphic_utils.hpp" #include "helper/music_utils.hpp" @@ -21,21 +22,7 @@ namespace scenes { auto [parameters, information] = input::get_game_parameters_for_replay(service_provider, recording_path); - std::vector layouts{}; - layouts.reserve(parameters.size()); - - if (parameters.empty()) { - throw std::runtime_error("An empty recording file isn't supported"); - } else if (parameters.size() == 1) { // NOLINT(readability-else-after-return,llvm-else-after-return) - layouts.push_back(ui::RelativeLayout{ layout, 0.02, 0.01, 0.96, 0.98 }); - } else if (parameters.size() == 2) { - layouts.push_back(ui::RelativeLayout{ layout, 0.02, 0.01, 0.46, 0.98 }); - layouts.push_back(ui::RelativeLayout{ layout, 0.52, 0.01, 0.46, 0.98 }); - } else { - - //TODO(Totto): support bigger layouts than just 2 - throw std::runtime_error("At the moment only replays from up to two players are supported"); - } + std::vector layouts = game::get_layouts_for(parameters.size(), layout); u32 simulation_frequency = constants::simulation_frequency; if (const auto stored_simulation_frequency = information.get_if("simulation_frequency"); From e54008676a745c80d531603a08e392d1452dec5d Mon Sep 17 00:00:00 2001 From: Totto16 Date: Fri, 8 Nov 2024 01:46:46 +0100 Subject: [PATCH 05/51] fix: fix the color format of the sdl surface + do a correct sdl render loop, so that the image gets cleared correctly --- src/graphics/video_renderer.cpp | 4 ++++ src/graphics/video_renderer_linux.cpp | 9 ++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/graphics/video_renderer.cpp b/src/graphics/video_renderer.cpp index 22b9fcef6..6c6dcb694 100644 --- a/src/graphics/video_renderer.cpp +++ b/src/graphics/video_renderer.cpp @@ -83,6 +83,8 @@ std::optional VideoRenderer::render( while (not all_games_finished()) { progress_callback(progress); + m_renderer->clear(); + for (const auto& game : m_games) { if (not game->is_game_finished()) { game->update(); @@ -90,6 +92,8 @@ std::optional VideoRenderer::render( } } + m_renderer->present(); + backend.add_frame(m_surface.get()); m_clock->increment_simulation_step_index(); diff --git a/src/graphics/video_renderer_linux.cpp b/src/graphics/video_renderer_linux.cpp index 623aa4341..60d527a2b 100644 --- a/src/graphics/video_renderer_linux.cpp +++ b/src/graphics/video_renderer_linux.cpp @@ -57,11 +57,10 @@ std::optional VideoRendererBackend::setup(u32 fps, shapes::UPoint s //TODO(Totto): support audio, that loops the music as in the main game int ret = - execlp("ffmpeg", "ffmpeg", "-loglevel", "verbose", "-y", - - "-f", "rawvideo", "-pix_fmt", "rgba", "-s", resolution.c_str(), "-r", framerate.c_str(), "-i", - "-", "-c:v", "libx264", "-vb", "2500k", "-c:a", "aac", "-ab", "200k", "-pix_fmt", "yuv420p", - m_destination_path.c_str(), static_cast(nullptr)); + execlp("ffmpeg", "ffmpeg", "-loglevel", "verbose", "-y", "-f", "rawvideo", "-pix_fmt", "bgra", "-s", + resolution.c_str(), "-r", framerate.c_str(), "-i", "-", "-c:v", "libx264", "-vb", "2500k", + "-c:a", "aac", "-ab", "200k", "-pix_fmt", "yuv420p", m_destination_path.c_str(), + static_cast(nullptr)); if (ret < 0) { std::cerr << "FFMPEG CHILD: could not run ffmpeg as a child process: " << strerror(errno) << "\n"; std::exit(1); From fd769ec0c2029ea73f3d6ce3d208fada3d063949 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Sun, 10 Nov 2024 23:05:18 +0100 Subject: [PATCH 06/51] feat: WIP; add windows ffmpeg / video backend implementation --- src/graphics/video_renderer_windows.cpp | 133 ++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 src/graphics/video_renderer_windows.cpp diff --git a/src/graphics/video_renderer_windows.cpp b/src/graphics/video_renderer_windows.cpp new file mode 100644 index 000000000..02aff2aee --- /dev/null +++ b/src/graphics/video_renderer_windows.cpp @@ -0,0 +1,133 @@ + +#include "video_renderer.hpp" + +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX + +#include + + +struct Decoder { + HANDLE hProcess; + HANDLE hPipeWrite; +}; + +// inspired by: https://github.com/tsoding/musializer/blob/762a729ff69ba1f984b0f2604e0eac08af46327c/src/ffmpeg_windows.c +VideoRendererBackend::VideoRendererBackend(const std::filesystem::path& destination_path) + : m_destination_path{ destination_path }, + m_decoder{ nullptr } { } + +VideoRendererBackend::~VideoRendererBackend() = default; + + +std::optional VideoRendererBackend::setup(u32 fps, shapes::UPoint size) { + + HANDLE pipe_read; + HANDLE pipe_write; + + SECURITY_ATTRIBUTES saAttr = { 0 }; + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + + if (!CreatePipe(&pipe_read, &pipe_write, &saAttr, 0)) { + return fmt::format("FFMPEG: Could not create pipe. System Error Code: {}", GetLastError()); + } + + if (!SetHandleInformation(pipe_write, HANDLE_FLAG_INHERIT, 0)) { + return fmt::format( + "FFMPEG: Could not mark write pipe as non-inheritable. System Error Code: {}", GetLastError() + ); + } + + // https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output + + STARTUPINFO siStartInfo; + ZeroMemory(&siStartInfo, sizeof(siStartInfo)); + siStartInfo.cb = sizeof(STARTUPINFO); + // NOTE: theoretically setting NULL to std handles should not be a problem + // https://docs.microsoft.com/en-us/windows/console/getstdhandle?redirectedfrom=MSDN#attachdetach-behavior + siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); + if (siStartInfo.hStdError == INVALID_HANDLE_VALUE) { + return fmt::format( + "FFMPEG: Could get standard error handle for the child. System Error Code: {}", GetLastError() + ); + } + siStartInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + if (siStartInfo.hStdOutput == INVALID_HANDLE_VALUE) { + return fmt::format( + "FFMPEG: Could get standard output handle for the child. System Error Code: {}", GetLastError() + ); + } + siStartInfo.hStdInput = pipe_read; + siStartInfo.dwFlags |= STARTF_USESTDHANDLES; + + PROCESS_INFORMATION piProcInfo; + ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); + + //TODO: do this in better c++ fashion and deduplicate this + char cmd_buffer[1024 * 2]; + snprintf( + cmd_buffer, sizeof(cmd_buffer), + "ffmpeg.exe -loglevel verbose -y -f rawvideo -pix_fmt rgba -s %dx%d -r %d -i - -i \"%s\" -c:v libx264 -vb " + "2500k -c:a aac -ab 200k -pix_fmt yuv420p output.mp4", + (int) width, (int) height, (int) fps, sound_file_path + ); + + if (!CreateProcess(NULL, cmd_buffer, NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo)) { + CloseHandle(pipe_write); + CloseHandle(pipe_read); + + return fmt::format("FFMPEG: Could not create child process. System Error Code: {}", GetLastError()); + } + + CloseHandle(pipe_read); + CloseHandle(piProcInfo.hThread); + + m_decoder = std::make_unique(piProcInfo.hProcess, pipe_write); + return std::nullopt; +} + +bool VideoRendererBackend::add_frame(SDL_Surface* surface) { + DWORD written{}; + + if (not WriteFile( + m_decoder->hPipeWrite, surface->pixels, static_cast(surface->h) * surface->pitch, &written, NULL + )) { + spdlog::error("FFMPEG: failed to write into ffmpeg pipe. System Error Code: {}", GetLastError()); + return false; + } + return true; +} + +bool VideoRendererBackend::finish(bool cancel) { + + FlushFileBuffers(m_decoder->hPipeWrite); + CloseHandle(m_decoder->hPipeWrite); + + if (cancel) { + TerminateProcess(m_decoder->hProcess, 1); + } + + if (WaitForSingleObject(m_decoder->hProcess, INFINITE) == WAIT_FAILED) { + spdlog::error("FFMPEG: could not wait on child process. System Error Code: {}", GetLastError()); + CloseHandle(m_decoder->hProcess); + return false; + } + + DWORD exit_status{}; + if (GetExitCodeProcess(m_decoder->hProcess, &exit_status) == 0) { + spdlog::error("FFMPEG: could not get process exit code. System Error Code: {}", GetLastError()); + CloseHandle(m_decoder->hProcess); + return false; + } + + if (exit_status != 0) { + spdlog::error("FFMPEG: command exited with exit code {}", exit_status); + CloseHandle(m_decoder->hProcess); + return false; + } + + CloseHandle(m_decoder->hProcess); + + return true; +} From 2f4f15088d364cb86cc2abd57b722220b5418a13 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Sun, 10 Nov 2024 23:23:43 +0100 Subject: [PATCH 07/51] fix: use the same render arguments for linux and windows --- src/graphics/video_renderer.cpp | 16 +++++++++++++- src/graphics/video_renderer.hpp | 5 ++++- src/graphics/video_renderer_linux.cpp | 17 ++++++++------- src/graphics/video_renderer_windows.cpp | 28 ++++++++++++++++--------- 4 files changed, 47 insertions(+), 19 deletions(-) diff --git a/src/graphics/video_renderer.cpp b/src/graphics/video_renderer.cpp index 6c6dcb694..15472dc2e 100644 --- a/src/graphics/video_renderer.cpp +++ b/src/graphics/video_renderer.cpp @@ -93,7 +93,7 @@ std::optional VideoRenderer::render( } m_renderer->present(); - + backend.add_frame(m_surface.get()); m_clock->increment_simulation_step_index(); @@ -106,6 +106,20 @@ std::optional VideoRenderer::render( return std::nullopt; } +std::vector +VideoRendererBackend::get_encoding_paramaters(u32 fps, shapes::UPoint size, std::filesystem::path destination_path) { + + std::string resolution = fmt::format("{}x{}", size.x, size.y); + + std::string framerate = fmt::format("{}", fps); + + return { + "-loglevel", "verbose", "-y", "-f", "rawvideo", "-pix_fmt", "bgra", "-s", + resolution, "-r", framerate, "-i", "-", "-c:v", "libx264", "-vb", + "2500k", "-c:a", "aac", "-ab", "200k", "-pix_fmt", "yuv420p", destination_path.string(), + }; +} + // implementation of ServiceProvider diff --git a/src/graphics/video_renderer.hpp b/src/graphics/video_renderer.hpp index e6f03ae4b..678c86e08 100644 --- a/src/graphics/video_renderer.hpp +++ b/src/graphics/video_renderer.hpp @@ -86,9 +86,12 @@ struct Decoder; // See e.g. https://github.com/Raveler/ffmpeg-cpp struct VideoRendererBackend { private: - std::string m_destination_path; + std::filesystem::path m_destination_path; std::unique_ptr m_decoder; + [[nodiscard]] static std::vector + get_encoding_paramaters(u32 fps, shapes::UPoint size, std::filesystem::path destination_path); + public: OOPETRIS_GRAPHICS_EXPORTED explicit VideoRendererBackend(const std::filesystem::path& destination_path); diff --git a/src/graphics/video_renderer_linux.cpp b/src/graphics/video_renderer_linux.cpp index 60d527a2b..7ac6c4637 100644 --- a/src/graphics/video_renderer_linux.cpp +++ b/src/graphics/video_renderer_linux.cpp @@ -51,16 +51,19 @@ std::optional VideoRendererBackend::setup(u32 fps, shapes::UPoint s } close(pipefd[WRITE_END]); - std::string resolution = fmt::format("{}x{}", size.x, size.y); - std::string framerate = fmt::format("{}", fps); + auto paramaters = VideoRendererBackend::get_encoding_paramaters(fps, size, m_destination_path); + + std::vector args = { "ffmpeg" }; + for (const auto& parameter : paramaters) { + args.push_back(parameter.c_str()); + } + + args.push_back(nullptr); + //TODO(Totto): support audio, that loops the music as in the main game - int ret = - execlp("ffmpeg", "ffmpeg", "-loglevel", "verbose", "-y", "-f", "rawvideo", "-pix_fmt", "bgra", "-s", - resolution.c_str(), "-r", framerate.c_str(), "-i", "-", "-c:v", "libx264", "-vb", "2500k", - "-c:a", "aac", "-ab", "200k", "-pix_fmt", "yuv420p", m_destination_path.c_str(), - static_cast(nullptr)); + int ret = execvp("ffmpeg", args.data()); if (ret < 0) { std::cerr << "FFMPEG CHILD: could not run ffmpeg as a child process: " << strerror(errno) << "\n"; std::exit(1); diff --git a/src/graphics/video_renderer_windows.cpp b/src/graphics/video_renderer_windows.cpp index 02aff2aee..a3d770479 100644 --- a/src/graphics/video_renderer_windows.cpp +++ b/src/graphics/video_renderer_windows.cpp @@ -64,16 +64,24 @@ std::optional VideoRendererBackend::setup(u32 fps, shapes::UPoint s PROCESS_INFORMATION piProcInfo; ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); - //TODO: do this in better c++ fashion and deduplicate this - char cmd_buffer[1024 * 2]; - snprintf( - cmd_buffer, sizeof(cmd_buffer), - "ffmpeg.exe -loglevel verbose -y -f rawvideo -pix_fmt rgba -s %dx%d -r %d -i - -i \"%s\" -c:v libx264 -vb " - "2500k -c:a aac -ab 200k -pix_fmt yuv420p output.mp4", - (int) width, (int) height, (int) fps, sound_file_path - ); - - if (!CreateProcess(NULL, cmd_buffer, NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo)) { + + auto paramaters = VideoRendererBackend::get_encoding_paramaters(fps, size, m_destination_path); + + std::stringstream args = "ffmpeg.exe"; + for (const auto& parameter : paramaters) { + args += " "; + if (paramater.find(" ") != std::string::npos) { + args += "\""; + args += paramater; + args += "\""; + + } else { + args += paramater; + } + } + + + if (!CreateProcess(NULL, result.string().c_str(), NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo)) { CloseHandle(pipe_write); CloseHandle(pipe_read); From 3a9718961ca0d30cb7108e19753ddc8cf396939f Mon Sep 17 00:00:00 2001 From: "Totto16 (Windows VM)" Date: Sun, 10 Nov 2024 23:53:49 +0100 Subject: [PATCH 08/51] fix: fix windows compilation --- src/game/layout.cpp | 2 +- src/game/layout.hpp | 4 +-- src/graphics/video_renderer_windows.cpp | 35 +++++++++++++++++++------ 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/game/layout.cpp b/src/game/layout.cpp index 622b81bf4..0402cfb84 100644 --- a/src/game/layout.cpp +++ b/src/game/layout.cpp @@ -1,7 +1,7 @@ #include "./layout.hpp" -std::vector game::get_layouts_for(u32 players, const ui::Layout& layout) { +std::vector game::get_layouts_for(std::size_t players, const ui::Layout& layout) { std::vector layouts{}; layouts.reserve(players); diff --git a/src/game/layout.hpp b/src/game/layout.hpp index a21598654..77b28a775 100644 --- a/src/game/layout.hpp +++ b/src/game/layout.hpp @@ -6,10 +6,10 @@ #include "helper/windows.hpp" #include "ui/layout.hpp" - +#include namespace game { OOPETRIS_GRAPHICS_EXPORTED [[nodiscard]] std::vector - get_layouts_for(u32 players, const ui::Layout& layout); + get_layouts_for(std::size_t players, const ui::Layout& layout); } diff --git a/src/graphics/video_renderer_windows.cpp b/src/graphics/video_renderer_windows.cpp index a3d770479..566ec4aec 100644 --- a/src/graphics/video_renderer_windows.cpp +++ b/src/graphics/video_renderer_windows.cpp @@ -6,6 +6,7 @@ #include +#include struct Decoder { HANDLE hProcess; @@ -67,21 +68,39 @@ std::optional VideoRendererBackend::setup(u32 fps, shapes::UPoint s auto paramaters = VideoRendererBackend::get_encoding_paramaters(fps, size, m_destination_path); - std::stringstream args = "ffmpeg.exe"; + std::stringstream args{}; + args << "ffmpeg.exe"; + for (const auto& parameter : paramaters) { - args += " "; - if (paramater.find(" ") != std::string::npos) { - args += "\""; - args += paramater; - args += "\""; + args << " "; + if (parameter.find(" ") != std::string::npos) { + args << "\""; + args << parameter; + args << "\""; } else { - args += paramater; + args << parameter; } } + std::string result = args.str(); + auto str_size = result.size(); + + using UniqueCharArray = std::unique_ptr>; + + UniqueCharArray raw_args{ new char[str_size + 1], [](const char* const char_value) { + if (char_value == nullptr) { + return; + } + + delete[] char_value; // NOLINT(cppcoreguidelines-owning-memory) + } }; + + std::memcpy(raw_args.get(), result.c_str(), str_size); + raw_args.get()[str_size] = '\0'; + - if (!CreateProcess(NULL, result.string().c_str(), NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo)) { + if (!CreateProcess(NULL, raw_args.get(), NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo)) { CloseHandle(pipe_write); CloseHandle(pipe_read); From bd032458ef6be3942f3866a1e01dc31b3072a01b Mon Sep 17 00:00:00 2001 From: Totto16 Date: Mon, 11 Nov 2024 00:46:27 +0100 Subject: [PATCH 09/51] fix: fix linux build --- src/graphics/video_renderer.cpp | 11 +++++++---- src/graphics/video_renderer.hpp | 4 ++-- src/graphics/video_renderer_linux.cpp | 6 +++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/graphics/video_renderer.cpp b/src/graphics/video_renderer.cpp index 15472dc2e..1d6020ae4 100644 --- a/src/graphics/video_renderer.cpp +++ b/src/graphics/video_renderer.cpp @@ -106,12 +106,15 @@ std::optional VideoRenderer::render( return std::nullopt; } -std::vector -VideoRendererBackend::get_encoding_paramaters(u32 fps, shapes::UPoint size, std::filesystem::path destination_path) { +std::vector VideoRendererBackend::get_encoding_paramaters( + u32 fps, + shapes::UPoint size, + const std::filesystem::path& destination_path +) { - std::string resolution = fmt::format("{}x{}", size.x, size.y); + const std::string resolution = fmt::format("{}x{}", size.x, size.y); - std::string framerate = fmt::format("{}", fps); + const std::string framerate = fmt::format("{}", fps); return { "-loglevel", "verbose", "-y", "-f", "rawvideo", "-pix_fmt", "bgra", "-s", diff --git a/src/graphics/video_renderer.hpp b/src/graphics/video_renderer.hpp index 678c86e08..95491df35 100644 --- a/src/graphics/video_renderer.hpp +++ b/src/graphics/video_renderer.hpp @@ -32,7 +32,7 @@ struct VideoRenderer : ServiceProvider { std::optional render(const std::string& destination_path, u32 fps, const std::function& progress_callback); - ~VideoRenderer(); + ~VideoRenderer() override; // implementation of ServiceProvider @@ -90,7 +90,7 @@ struct VideoRendererBackend { std::unique_ptr m_decoder; [[nodiscard]] static std::vector - get_encoding_paramaters(u32 fps, shapes::UPoint size, std::filesystem::path destination_path); + get_encoding_paramaters(u32 fps, shapes::UPoint size, const std::filesystem::path& destination_path); public: OOPETRIS_GRAPHICS_EXPORTED explicit VideoRendererBackend(const std::filesystem::path& destination_path); diff --git a/src/graphics/video_renderer_linux.cpp b/src/graphics/video_renderer_linux.cpp index 7ac6c4637..f234d3c57 100644 --- a/src/graphics/video_renderer_linux.cpp +++ b/src/graphics/video_renderer_linux.cpp @@ -60,10 +60,10 @@ std::optional VideoRendererBackend::setup(u32 fps, shapes::UPoint s } args.push_back(nullptr); - - //TODO(Totto): support audio, that loops the music as in the main game - int ret = execvp("ffmpeg", args.data()); + int ret = + execvp("ffmpeg", + const_cast(args.data())); // NOLINT(cppcoreguidelines-pro-type-const-cast) if (ret < 0) { std::cerr << "FFMPEG CHILD: could not run ffmpeg as a child process: " << strerror(errno) << "\n"; std::exit(1); From 44cdebb33cab59daf06eac1c69c73e74a42c0438 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Mon, 11 Nov 2024 01:24:40 +0100 Subject: [PATCH 10/51] fix: fix ffmpeg usage for vide rendering make the build system mor robust allow the usage of embedded ffmpeg (WIP) --- meson.options | 7 ++ src/graphics/meson.build | 44 ++++++++----- src/graphics/video_renderer_embedded.cpp | 34 ++++++++++ ...erer_linux.cpp => video_renderer_unix.cpp} | 0 src/helper/graphic_utils.cpp | 8 +++ tools/dependencies/meson.build | 64 +++++++++++++++++++ 6 files changed, 141 insertions(+), 16 deletions(-) create mode 100644 src/graphics/video_renderer_embedded.cpp rename src/graphics/{video_renderer_linux.cpp => video_renderer_unix.cpp} (100%) diff --git a/meson.options b/meson.options index 9902188b2..2b4d536cb 100644 --- a/meson.options +++ b/meson.options @@ -25,3 +25,10 @@ option( value: false, description: 'if you only want to build the libs, enable this', ) + +option( + 'use_embedded_ffmpeg', + type: 'feature', + value: 'auto', + description: 'embed ffmpeg to render recording videos', +) diff --git a/src/graphics/meson.build b/src/graphics/meson.build index 6805e20a0..c5c516563 100644 --- a/src/graphics/meson.build +++ b/src/graphics/meson.build @@ -8,26 +8,38 @@ graphics_src_files += files( 'text.hpp', 'texture.cpp', 'texture.hpp', - 'video_renderer.cpp', - 'video_renderer.hpp', 'window.cpp', 'window.hpp', ) +if replay_video_rendering_enabled -# TODO: make this optional -if host_machine.system() == 'darwin' -graphics_src_files += files( - 'video_renderer_mac.cpp', -) -elif host_machine.system() == 'linux' - graphics_src_files += files( - 'video_renderer_linux.cpp', -) -elif host_machine.system() == 'windows' graphics_src_files += files( - 'video_renderer_windows.cpp', -) -else - error('unsuported system for video rendering: ' + host_machine.system()) + 'video_renderer.cpp', + 'video_renderer.hpp', + ) + + if use_embedded_ffmpeg + graphics_src_files += files( + 'video_renderer_embedded.cpp', + ) + else + + if host_machine.system() == 'darwin' or host_machine.system() == 'linux' + graphics_src_files += files( + 'video_renderer_unix.cpp', + ) + elif host_machine.system() == 'windows' + graphics_src_files += files( + 'video_renderer_windows.cpp', + ) + else + error( + 'unhandled system for video rendering withour embedding: ' + + host_machine.system(), + ) + endif + + endif + endif diff --git a/src/graphics/video_renderer_embedded.cpp b/src/graphics/video_renderer_embedded.cpp new file mode 100644 index 000000000..3c1a0fbfd --- /dev/null +++ b/src/graphics/video_renderer_embedded.cpp @@ -0,0 +1,34 @@ + + +#include "video_renderer.hpp" + +extern "C" { +#include +#include +#include +#include +} + + +struct Decoder { + int pipe; + pid_t pid; +}; + +// general information and usage from: https://friendlyuser.github.io/posts/tech/cpp/Using_FFmpeg_in_C++_A_Comprehensive_Guide/ +VideoRendererBackend::VideoRendererBackend(const std::filesystem::path& destination_path) + : m_destination_path{ destination_path }, + m_decoder{ nullptr } { } + +VideoRendererBackend::~VideoRendererBackend() = default; + + +std::optional VideoRendererBackend::setup(u32 fps, shapes::UPoint size) { + + m_decoder = std::make_unique(); + return std::nullopt; +} + +bool VideoRendererBackend::add_frame(SDL_Surface* surface) { } + +bool VideoRendererBackend::finish(bool cancel) { } diff --git a/src/graphics/video_renderer_linux.cpp b/src/graphics/video_renderer_unix.cpp similarity index 100% rename from src/graphics/video_renderer_linux.cpp rename to src/graphics/video_renderer_unix.cpp diff --git a/src/helper/graphic_utils.cpp b/src/helper/graphic_utils.cpp index 2bafe4c36..cc1a98d24 100644 --- a/src/helper/graphic_utils.cpp +++ b/src/helper/graphic_utils.cpp @@ -25,6 +25,14 @@ std::vector utils::supported_features() { features.emplace_back("discord integration"); #endif +#if defined(_ENABLE_REPLAY_RENDERING) +#if defined(_FFMPEG_USE_EMBEDDED) + features.emplace_back("replay video rendering (embedded)"); +#else + features.emplace_back("replay video rendering (system)"); +#endif +#endif + return features; } diff --git a/tools/dependencies/meson.build b/tools/dependencies/meson.build index ba0ffbf57..c8afd63bc 100644 --- a/tools/dependencies/meson.build +++ b/tools/dependencies/meson.build @@ -456,6 +456,70 @@ if build_application endif + use_embedded_ffmpeg = get_option('use_embedded_ffmpeg') + replay_video_rendering_enabled = true + + if host_machine.system() == 'darwin' or host_machine.system() == 'linux' or host_machine.system() == 'windows' + + use_embedded_ffmpeg = use_embedded_ffmpeg.enable_auto_if(is_flatpak_build).enabled() + + # on the desktop we only use the embedded ffmpeg, if we require it, we prefer the command line tool, that is installed on the systne, except for flatpak builds + if use_embedded_ffmpeg + ffmpeg_dep_names = ['libavutil', 'libavcodec', 'libavformat', 'libavfilter'] + ffmpeg_deps = [] + found_all_ffmpeg_deps = true + + foreach ffmpeg_dep_name : ffmpeg_dep_names + ffmpeg_dep = dependency(ffmpeg_dep_name, required: false) + if not ffmpeg_dep.found() + found_all_ffmpeg_deps = false + break + endif + + ffmpeg_deps += ffmpeg_dep + + endforeach + + message( + 'ffmpeg deps: ' + + (found_all_ffmpeg_deps ? 'FOUND' : 'NOT ALL FOUND'), + ) + + replay_video_rendering_enabled = found_all_ffmpeg_deps + if replay_video_rendering_enabled + graphics_lib += { + 'deps': [graphics_lib.get('deps'), ffmpeg_deps], + } + endif + + endif + + else + if meson.is_cross_build() and host_machine.system() == 'android' + error('TODO') + + else + error( + 'unhandled system for video rendering: ' + + host_machine.system(), + ) + endif + endif + + if replay_video_rendering_enabled + _set_flags = ['-D_ENABLE_REPLAY_RENDERING'] + if use_embedded_ffmpeg + _set_flags += ['-D_FFMPEG_USE_EMBEDDED'] + endif + + graphics_lib += { + 'compile_args': [graphics_lib.get('compile_args'), _set_flags], + } + + _set_flags = 0 + + endif + if is_flatpak_build app_name = 'io.github.openbrickprotocolfoundation.oopetris' From 33b4d45553a6907cb09aabd9a8cadba97bd6b400 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Mon, 11 Nov 2024 01:28:08 +0100 Subject: [PATCH 11/51] feat: add function to determine, if ffmpeg is supported at runtime --- src/graphics/video_renderer.hpp | 2 ++ src/graphics/video_renderer_embedded.cpp | 4 ++++ src/graphics/video_renderer_unix.cpp | 6 ++++++ src/graphics/video_renderer_windows.cpp | 5 +++++ 4 files changed, 17 insertions(+) diff --git a/src/graphics/video_renderer.hpp b/src/graphics/video_renderer.hpp index 95491df35..ef122254d 100644 --- a/src/graphics/video_renderer.hpp +++ b/src/graphics/video_renderer.hpp @@ -97,6 +97,8 @@ struct VideoRendererBackend { OOPETRIS_GRAPHICS_EXPORTED ~VideoRendererBackend(); + OOPETRIS_GRAPHICS_EXPORTED static void is_supported_async(const std::function& callback); + OOPETRIS_GRAPHICS_EXPORTED [[nodiscard]] std::optional setup(u32 fps, shapes::UPoint size); bool add_frame(SDL_Surface* surface); diff --git a/src/graphics/video_renderer_embedded.cpp b/src/graphics/video_renderer_embedded.cpp index 3c1a0fbfd..610db68b3 100644 --- a/src/graphics/video_renderer_embedded.cpp +++ b/src/graphics/video_renderer_embedded.cpp @@ -22,6 +22,10 @@ VideoRendererBackend::VideoRendererBackend(const std::filesystem::path& destinat VideoRendererBackend::~VideoRendererBackend() = default; +void VideoRendererBackend::is_supported_async(const std::function& callback) { + callback(true); +} + std::optional VideoRendererBackend::setup(u32 fps, shapes::UPoint size) { diff --git a/src/graphics/video_renderer_unix.cpp b/src/graphics/video_renderer_unix.cpp index f234d3c57..478e3beb6 100644 --- a/src/graphics/video_renderer_unix.cpp +++ b/src/graphics/video_renderer_unix.cpp @@ -31,6 +31,12 @@ namespace { } // namespace +void VideoRendererBackend::is_supported_async(const std::function& callback) { + //TODO: detect if we have the ffmpeg executable on the path + callback(false); +} + + std::optional VideoRendererBackend::setup(u32 fps, shapes::UPoint size) { std::array pipefd = { 0, 0 }; diff --git a/src/graphics/video_renderer_windows.cpp b/src/graphics/video_renderer_windows.cpp index 566ec4aec..20ba02dfc 100644 --- a/src/graphics/video_renderer_windows.cpp +++ b/src/graphics/video_renderer_windows.cpp @@ -20,6 +20,11 @@ VideoRendererBackend::VideoRendererBackend(const std::filesystem::path& destinat VideoRendererBackend::~VideoRendererBackend() = default; +void VideoRendererBackend::is_supported_async(const std::function& callback) { + //TODO: detect if we have the ffmpeg executable on the path + callback(false); +} + std::optional VideoRendererBackend::setup(u32 fps, shapes::UPoint size) { From d36d102c3338dc51736f9b1cc9b463126e6cfaef Mon Sep 17 00:00:00 2001 From: Totto16 Date: Mon, 11 Nov 2024 02:09:53 +0100 Subject: [PATCH 12/51] fix: support android embedded ffmpeg --- platforms/build-android.sh | 46 ++++++++++++++++++++++++++ tools/dependencies/meson.build | 59 ++++++++++++++++++++++++++++++++-- 2 files changed, 103 insertions(+), 2 deletions(-) diff --git a/platforms/build-android.sh b/platforms/build-android.sh index 1560c186e..16b684d05 100755 --- a/platforms/build-android.sh +++ b/platforms/build-android.sh @@ -344,6 +344,52 @@ for INDEX in "${ARCH_KEYS_INDEX[@]}"; do cd "$LAST_DIR" + ## build ffmpeg for android (using https://github.com/Javernaut/ffmpeg-android-maker) + + LAST_DIR="$PWD" + + cd "$SYS_ROOT" + + BUILD_DIR_FFMPEG="build-ffmpeg" + + BUILD_FFMPEG_FILE="$SYS_ROOT/$BUILD_DIR_FFMPEG/build_succesfull.meta" + + if [ "$COMPILE_TYPE" == "complete_rebuild" ] || ! [ -e "$BUILD_FFMPEG_FILE" ]; then + + mkdir -p "$BUILD_DIR_FFMPEG" + + cd "$BUILD_DIR_FFMPEG" + + FFMPEG_MAKER_DIR="maker" + + if ! [ -e "$FFMPEG_MAKER_DIR" ]; then + + git clone https://github.com/Javernaut/ffmpeg-android-maker.git "$FFMPEG_MAKER_DIR" + + cd "$FFMPEG_MAKER_DIR" + else + cd "$FFMPEG_MAKER_DIR" + + git pull + + fi + + ./ffmpeg-android-maker.sh "--target-abis=$ARCH" "--android-api-level=$SDK_VERSION" + + FFMPEG_MAKER_OUTPUT_DIR="output" + + ls -lsa "$FFMPEG_MAKER_OUTPUT_DIR" + + find "$FFMPEG_MAKER_OUTPUT_DIR/include/" -maxdepth 3 -mindepth 2 -type d -exec cp -r {} "$SYS_ROOT/usr/include/" \; + + find "$FFMPEG_MAKER_OUTPUT_DIR/lib/" -type f -exec cp -r {} "$SYS_ROOT/usr/lib/" \; + + touch "$BUILD_FFMPEG_FILE" + + fi + + cd "$LAST_DIR" + ## END of manual build of dependencies MESON_CPU_FAMILY=$ARCH diff --git a/tools/dependencies/meson.build b/tools/dependencies/meson.build index c8afd63bc..75dbc5037 100644 --- a/tools/dependencies/meson.build +++ b/tools/dependencies/meson.build @@ -495,8 +495,63 @@ if build_application endif else - if meson.is_cross_build() and host_machine.system() == 'android' - error('TODO') + if meson.is_cross_build() + + use_embedded_ffmpeg = use_embedded_ffmpeg.allowed() + + if not use_embedded_ffmpeg + error('only embedded ffmpeg is supported in cross builds') + endif + + if host_machine.system() == 'android' + ffmpeg_can_be_supported = true + + elif host_machine.system() == 'switch' + ffmpeg_can_be_supported = true + elif host_machine.system() == '3ds' + ffmpeg_can_be_supported = false + else + + error( + 'unhandled cross built system for video rendering: ' + + host_machine.system(), + ) + endif + + if not ffmpeg_can_be_supported + replay_video_rendering_enabled = false + else + + ffmpeg_dep_names = ['avutil', 'avcodec', 'avformat', 'avfilter'] + ffmpeg_deps = [] + found_all_ffmpeg_deps = true + + c = meson.get_compiler('c') + + foreach ffmpeg_dep_name : ffmpeg_dep_names + ffmpeg_dep = c.find_library(ffmpeg_dep_name, required: false) + if not ffmpeg_dep.found() + found_all_ffmpeg_deps = false + break + endif + + ffmpeg_deps += ffmpeg_dep + + endforeach + + message( + 'ffmpeg deps: ' + + (found_all_ffmpeg_deps ? 'FOUND' : 'NOT ALL FOUND'), + ) + + replay_video_rendering_enabled = found_all_ffmpeg_deps + if replay_video_rendering_enabled + graphics_lib += { + 'deps': [graphics_lib.get('deps'), ffmpeg_deps], + } + endif + + endif else error( From e8c675d5e6749242431a1bf9ca836608286a12a0 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Tue, 12 Nov 2024 01:42:55 +0100 Subject: [PATCH 13/51] feat: add embedded ffmpeg encoding WIP, it nearly works --- src/graphics/video_renderer.cpp | 16 +- src/graphics/video_renderer.hpp | 4 +- src/graphics/video_renderer_embedded.cpp | 365 ++++++++++++++++++++++- src/helper/c_helpers.hpp | 50 ++++ src/helper/meson.build | 1 + tools/dependencies/meson.build | 4 + 6 files changed, 429 insertions(+), 11 deletions(-) create mode 100644 src/helper/c_helpers.hpp diff --git a/src/graphics/video_renderer.cpp b/src/graphics/video_renderer.cpp index 1d6020ae4..3c9041a48 100644 --- a/src/graphics/video_renderer.cpp +++ b/src/graphics/video_renderer.cpp @@ -94,7 +94,10 @@ std::optional VideoRenderer::render( m_renderer->present(); - backend.add_frame(m_surface.get()); + if (not backend.add_frame(m_surface.get())) { + break; + } + m_clock->increment_simulation_step_index(); progress += 0.1; @@ -102,7 +105,9 @@ std::optional VideoRenderer::render( progress_callback(progress); } - backend.finish(false); + if (not backend.finish(false)) { + return "Renderer failed"; + } return std::nullopt; } @@ -117,9 +122,10 @@ std::vector VideoRendererBackend::get_encoding_paramaters( const std::string framerate = fmt::format("{}", fps); return { - "-loglevel", "verbose", "-y", "-f", "rawvideo", "-pix_fmt", "bgra", "-s", - resolution, "-r", framerate, "-i", "-", "-c:v", "libx264", "-vb", - "2500k", "-c:a", "aac", "-ab", "200k", "-pix_fmt", "yuv420p", destination_path.string(), + "-loglevel", "verbose", "-y", "-f", "rawvideo", + "-pix_fmt", "bgra", "-s", resolution, "-r", + framerate, "-i", "-", "-c:v", "libx264", + "-crf", "20", "-pix_fmt", "yuv420p", destination_path.string(), }; } diff --git a/src/graphics/video_renderer.hpp b/src/graphics/video_renderer.hpp index ef122254d..4caa2a9ad 100644 --- a/src/graphics/video_renderer.hpp +++ b/src/graphics/video_renderer.hpp @@ -101,7 +101,7 @@ struct VideoRendererBackend { OOPETRIS_GRAPHICS_EXPORTED [[nodiscard]] std::optional setup(u32 fps, shapes::UPoint size); - bool add_frame(SDL_Surface* surface); + [[nodiscard]] bool add_frame(SDL_Surface* surface); - bool finish(bool cancel); + [[nodiscard]] bool finish(bool cancel); }; diff --git a/src/graphics/video_renderer_embedded.cpp b/src/graphics/video_renderer_embedded.cpp index 610db68b3..cb7a6f2f5 100644 --- a/src/graphics/video_renderer_embedded.cpp +++ b/src/graphics/video_renderer_embedded.cpp @@ -1,14 +1,20 @@ +#include "helper/c_helpers.hpp" +#include "helper/constants.hpp" +#include "helper/git_helper.hpp" #include "video_renderer.hpp" extern "C" { #include #include #include -#include +#include } +#include +#include +#include struct Decoder { int pipe; @@ -16,23 +22,374 @@ struct Decoder { }; // general information and usage from: https://friendlyuser.github.io/posts/tech/cpp/Using_FFmpeg_in_C++_A_Comprehensive_Guide/ +// and https://trac.ffmpeg.org/wiki/Using%20libav* +// and https://github.com/FFmpeg/FFmpeg/blob/master/doc/examples/transcode.c VideoRendererBackend::VideoRendererBackend(const std::filesystem::path& destination_path) : m_destination_path{ destination_path }, m_decoder{ nullptr } { } VideoRendererBackend::~VideoRendererBackend() = default; +namespace { + + constexpr const int READ_END = 0; + constexpr const int WRITE_END = 1; + + constexpr const size_t BUF_LEN = 1024; + + std::string av_error_to_string(int errnum) { + auto* buf = new char[BUF_LEN]; + auto* buff_res = av_make_error_string(buf, BUF_LEN, errnum); + if (buff_res == nullptr) { + return "Unknown error"; + } + + std::string result{ buff_res }; + + delete[] buf; + + return result; + } + + std::optional + start_encoding(u32 fps, shapes::UPoint size, const std::filesystem::path& destination_path) { + + ScopeDeferMultiple scope_defer{}; + + // "-loglevel verbose" + av_log_set_level(AV_LOG_VERBOSE); + + // input setup + + AVFormatContext* input_format_ctx = avformat_alloc_context(); + if (input_format_ctx == nullptr) { + return fmt::format("Cannot allocate an input format context"); + } + + scope_defer.add([input_format_ctx](void*) { avformat_free_context(input_format_ctx); }, nullptr); + + const std::string resolution = fmt::format("{}x{}", size.x, size.y); + + const std::string framerate = fmt::format("{}", fps); + + // "-f rawvideo" + const AVInputFormat* input_fmt = av_find_input_format("rawvideo"); + + if (input_fmt == nullptr) { + return "Couldn't find input format"; + } + + AVDictionary* input_options = nullptr; + // "-pix_fmt bgra" + av_dict_set(&input_options, "pixel_format", "bgra", 0); + // "-s {resolution}" + av_dict_set(&input_options, "video_size", resolution.c_str(), 0); + // "-r {framerate}" + av_dict_set(&input_options, "framerate", framerate.c_str(), 0); + + // "-i -" + auto av_input_ret = avformat_open_input(&input_format_ctx, "fd:", input_fmt, &input_options); + if (av_input_ret != 0) { + return fmt::format("Could not open input file stdin: {}", av_error_to_string(av_input_ret)); + } + + scope_defer.add([&input_format_ctx](void*) { avformat_close_input(&input_format_ctx); }, nullptr); + + AVDictionaryEntry* unrecognized_key_inp = av_dict_get(input_options, "", nullptr, AV_DICT_IGNORE_SUFFIX); + if (unrecognized_key_inp != nullptr) { + return fmt::format("Option {} not recognized by the demuxer", unrecognized_key_inp->key); + } + + av_dict_free(&input_options); + + auto av_stream_info_ret = avformat_find_stream_info(input_format_ctx, nullptr); + if (av_stream_info_ret < 0) { + return fmt::format("Cannot find stream information: {}", av_error_to_string(av_stream_info_ret)); + } + + + /* select the video stream */ + const AVCodec* input_decoder = nullptr; + auto video_stream_index = av_find_best_stream(input_format_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &input_decoder, 0); + if (video_stream_index < 0) { + return fmt::format( + "Cannot find a video stream in the input file: {}", av_error_to_string(video_stream_index) + ); + } + + AVStream* input_video_stream = input_format_ctx->streams[video_stream_index]; + + AVCodecContext* input_codec_context = avcodec_alloc_context3(input_decoder); + if (input_codec_context == nullptr) { + return fmt::format("Cannot allocate a input codec context"); + } + + scope_defer.add([&input_codec_context](void*) { avcodec_free_context(&input_codec_context); }, nullptr); + + auto codec_paramaters_ret = avcodec_parameters_to_context(input_codec_context, input_video_stream->codecpar); + if (codec_paramaters_ret < 0) { + return fmt::format( + "Cannot set the input codec context paramaters: {}", av_error_to_string(codec_paramaters_ret) + ); + } + + /* Inform the decoder about the timebase for the packet timestamps. + * This is highly recommended, but not mandatory. */ + input_codec_context->pkt_timebase = input_video_stream->time_base; + + //NOTE: we also could set this to the provided u32 , but this also uses that and converts it to the expected format + input_codec_context->framerate = av_guess_frame_rate(input_format_ctx, input_video_stream, nullptr); + + auto codec_open_ret = avcodec_open2(input_codec_context, input_decoder, nullptr); + if (codec_open_ret != 0) { + return fmt::format("Cannot initializer the codec for the input: {}", av_error_to_string(codec_open_ret)); + } + + av_dump_format(input_format_ctx, 0, "fd:", 0); + + // output setup + + // "-c:v libx264" (h264) + const AVCodec* output_encoder = avcodec_find_encoder(AV_CODEC_ID_H264); + if (output_encoder == nullptr) { + return "Cannot find encoder h264"; + } + + + AVFormatContext* output_format_ctx = avformat_alloc_context(); + if (output_format_ctx == nullptr) { + return fmt::format("Cannot allocate an output format context"); + } + + scope_defer.add([output_format_ctx](void*) { avformat_free_context(output_format_ctx); }, nullptr); + + auto av_output_ret = + avformat_alloc_output_context2(&output_format_ctx, nullptr, "mp4", destination_path.c_str()); + + if (av_output_ret < 0) { + return fmt::format("Could not alloc output file {}: {}", destination_path.string(), av_output_ret); + } + + std::string encoder_metadata_name = fmt::format( + "{} v{} ({}) {}", constants::program_name.string(), constants::version.string(), utils::git_commit(), + LIBAVFORMAT_IDENT + ); + + av_dict_set(&output_format_ctx->metadata, "encoder", encoder_metadata_name.c_str(), 0); + + AVStream* out_stream = avformat_new_stream(output_format_ctx, nullptr); + if (out_stream == nullptr) { + return fmt::format("Cannot allocate an output stream"); + } + + AVCodecContext* output_codec_context = avcodec_alloc_context3(output_encoder); + if (out_stream == nullptr) { + return fmt::format("Cannot allocate an output codec context"); + } + + scope_defer.add([&output_codec_context](void*) { avcodec_free_context(&output_codec_context); }, nullptr); + + output_codec_context->height = input_codec_context->height; + output_codec_context->width = input_codec_context->width; + output_codec_context->sample_aspect_ratio = input_codec_context->sample_aspect_ratio; + + /* video time_base can be set to whatever is handy and supported by encoder */ + output_codec_context->time_base = av_inv_q(input_codec_context->framerate); + + AVDictionary* output_options = nullptr; + // "-pix_fmt yuv420p" + av_dict_set(&output_options, "pixel_format", "yuv420p", 0); + // "-crf 20" + av_dict_set(&output_options, "crf", "20", 0); + + auto codec_open_out_ret = avcodec_open2(output_codec_context, output_encoder, &output_options); + if (codec_open_out_ret != 0) { + return fmt::format( + "Cannot initializer the codec for the output: {}", av_error_to_string(codec_open_out_ret) + ); + } + + AVDictionaryEntry* unrecognized_key_outp = av_dict_get(output_options, "", nullptr, AV_DICT_IGNORE_SUFFIX); + if (unrecognized_key_outp != nullptr) { + return fmt::format("Option {} not recognized by the muxer", unrecognized_key_outp->key); + } + + av_dict_free(&output_options); + + auto codec_params_ret = avcodec_parameters_from_context(out_stream->codecpar, output_codec_context); + if (codec_params_ret < 0) { + return fmt::format( + "Failed to copy encoder parameters to output stream: {}\n", av_error_to_string(codec_params_ret) + ); + } + + + out_stream->time_base = output_codec_context->time_base; + + std::string stream_encoder_metadata_name = fmt::format( + "{} v{} ({}) {} {}", constants::program_name.string(), constants::version.string(), utils::git_commit(), + LIBAVCODEC_IDENT, output_encoder->name + ); + + av_dict_set(&out_stream->metadata, "encoder", stream_encoder_metadata_name.c_str(), 0); + + av_dump_format(output_format_ctx, 0, destination_path.c_str(), 1); + + // now do the actual work + + auto file_open_ret = avio_open(&output_format_ctx->pb, destination_path.c_str(), AVIO_FLAG_WRITE); + if (file_open_ret < 0) { + return fmt::format( + "Could not open output file '{}': {}", destination_path.string(), av_error_to_string(file_open_ret) + ); + } + + scope_defer.add([&output_format_ctx](void*) { avio_closep(&output_format_ctx->pb); }, nullptr); + + auto header_ret = avformat_write_header(output_format_ctx, nullptr); + if (header_ret < 0) { + return fmt::format( + "Error occurred when opening output file to write headers: {}", av_error_to_string(header_ret) + ); + } + + AVPacket* pkt = av_packet_alloc(); + if (pkt == nullptr) { + return "Could not allocate AVPacket"; + } + + scope_defer.add([&pkt](void*) { av_packet_free(&pkt); }, nullptr); + + while (true) { + auto read_ret = av_read_frame(input_format_ctx, pkt); + if (read_ret < 0) + break; + + + auto send_pkt_ret = avcodec_send_packet(output_codec_context, pkt); + if (send_pkt_ret < 0) { + return fmt::format("Decoding failed: {}", av_error_to_string(send_pkt_ret)); + } + + int write_ret = 0; + + while (write_ret >= 0) { + write_ret = avcodec_receive_packet(output_codec_context, pkt); + if (write_ret == AVERROR(EAGAIN) || write_ret == AVERROR_EOF) { + break; + + } else if (write_ret < 0) { + return fmt::format("Encoding a frame failed: {}", av_error_to_string(write_ret)); + } + + /* rescale output packet timestamp values from codec to stream timebase */ + av_packet_rescale_ts(pkt, output_codec_context->time_base, out_stream->time_base); + pkt->stream_index = out_stream->index; + + /* Write the compressed frame to the media file. */ + write_ret = av_interleaved_write_frame(output_format_ctx, pkt); + /* pkt is now blank (av_interleaved_write_frame() takes ownership of + * its contents and resets pkt), so that no unreferencing is necessary. + * This would be different if one used av_write_frame(). */ + if (write_ret < 0) { + return fmt::format("Writing an output packet failed: {}", av_error_to_string(write_ret)); + } + } + + // reset the packe, so that it's ready for the next cycle + av_packet_unref(pkt); + } + + av_write_trailer(output_format_ctx); + + return std::nullopt; + } + +} // namespace + + void VideoRendererBackend::is_supported_async(const std::function& callback) { callback(true); } std::optional VideoRendererBackend::setup(u32 fps, shapes::UPoint size) { + std::array pipefd = { 0, 0 }; + + if (pipe(pipefd.data()) < 0) { + return fmt::format("FFMPEG: Could not create a pipe: {}", strerror(errno)); + } + + pid_t child = fork(); + if (child < 0) { + return fmt::format("FFMPEG: could not fork a child: {}", strerror(errno)); + } + + if (child == 0) { + if (dup2(pipefd.at(READ_END), STDIN_FILENO) < 0) { + std::cerr << "FFMPEG CHILD: could not reopen read end of pipe as stdin: " << strerror(errno) << "\n"; + std::exit(1); + } + close(pipefd[WRITE_END]); + + auto result = start_encoding(fps, size, m_destination_path); + + if (result.has_value()) { + std::cerr << "FFMPEG CHILD: could not run embedded ffmpeg as a child process: " << result.value() << "\n"; + std::exit(1); + } + + std::exit(0); + } - m_decoder = std::make_unique(); + if (close(pipefd[READ_END]) < 0) { + spdlog::error("FFMPEG: could not close read end of the pipe on the parent's end: {}", strerror(errno)); + } + + m_decoder = std::make_unique(pipefd[WRITE_END], child); return std::nullopt; } -bool VideoRendererBackend::add_frame(SDL_Surface* surface) { } +bool VideoRendererBackend::add_frame(SDL_Surface* surface) { + if (write(m_decoder->pipe, surface->pixels, static_cast(surface->h) * surface->pitch) < 0) { + spdlog::error("FFMPEG: failed to write into ffmpeg pipe: {}", strerror(errno)); + return false; + } + return true; +} + +bool VideoRendererBackend::finish(bool cancel) { + + + if (close(m_decoder->pipe) < 0) { + spdlog::warn("FFMPEG: could not close write end of the pipe on the parent's end: {}", strerror(errno)); + } + + if (cancel) + kill(m_decoder->pid, SIGKILL); -bool VideoRendererBackend::finish(bool cancel) { } + while (true) { + int wstatus = 0; + if (waitpid(m_decoder->pid, &wstatus, 0) < 0) { + spdlog::error("FFMPEG: could not wait for ffmpeg child process to finish: {}", strerror(errno)); + return false; + } + + if (WIFEXITED(wstatus)) { + int exit_status = WEXITSTATUS(wstatus); + if (exit_status != 0) { + spdlog::error("FFMPEG: ffmpeg exited with code {}", exit_status); + return false; + } + + return true; + } + + if (WIFSIGNALED(wstatus)) { + spdlog::error("FFMPEG: ffmpeg got terminated by {}", strsignal(WTERMSIG(wstatus))); + return false; + } + } + + UNREACHABLE(); +} diff --git a/src/helper/c_helpers.hpp b/src/helper/c_helpers.hpp new file mode 100644 index 000000000..86e875c99 --- /dev/null +++ b/src/helper/c_helpers.hpp @@ -0,0 +1,50 @@ + + +#pragma once + +#include + +template +struct ScopeDefer { +private: + using CallbackType = std::function; + const CallbackType m_callback; + const Arg m_cleanup_value; + +public: + ScopeDefer(const ScopeDefer&) = delete; // no copy constructor + ScopeDefer& operator=(const ScopeDefer&) = delete; // no self-assignments (aka copy assignment) + + ScopeDefer(CallbackType&& callback, Arg cleanup_value) + : m_callback{ std::move(callback) }, + m_cleanup_value{ cleanup_value } { } + + ~ScopeDefer() { + this->m_callback(this->m_cleanup_value); + } +}; + + +template +struct ScopeDeferMultiple { +private: + using CallbackType = std::function; + std::vector> m_values{}; + +public: + ScopeDeferMultiple(const ScopeDeferMultiple&) = delete; // no copy constructor + ScopeDeferMultiple& operator=(const ScopeDeferMultiple&) = delete; // no self-assignments (aka copy assignment) + + ScopeDeferMultiple() = default; + + void add(CallbackType&& callback, Arg cleanup_value) { + m_values.emplace_back(std::move(callback), std::move(cleanup_value)); + } + + ~ScopeDeferMultiple() { + for (auto it = m_values.rbegin(); it != m_values.rend(); ++it) { + const auto& [callback, value] = *it; + callback(value); + } + } +}; diff --git a/src/helper/meson.build b/src/helper/meson.build index ca607430c..f2fd9bd32 100644 --- a/src/helper/meson.build +++ b/src/helper/meson.build @@ -1,4 +1,5 @@ graphics_src_files += files( + 'c_helpers.hpp', 'clock_source.cpp', 'clock_source.hpp', 'console_helpers.cpp', diff --git a/tools/dependencies/meson.build b/tools/dependencies/meson.build index 75dbc5037..eb4f1c8fc 100644 --- a/tools/dependencies/meson.build +++ b/tools/dependencies/meson.build @@ -465,6 +465,10 @@ if build_application # on the desktop we only use the embedded ffmpeg, if we require it, we prefer the command line tool, that is installed on the systne, except for flatpak builds if use_embedded_ffmpeg + if host_machine.system() == 'windows' + error('Embedded ffmpeg is still WIP on windows') + endif + ffmpeg_dep_names = ['libavutil', 'libavcodec', 'libavformat', 'libavfilter'] ffmpeg_deps = [] found_all_ffmpeg_deps = true From 80f4b205913e65af2fd9317e06ac3c06fdf57a49 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Tue, 12 Nov 2024 19:09:37 +0100 Subject: [PATCH 14/51] feat: add method to set thread name this is inspired by sdl and helps while debugging --- src/executables/game/application.cpp | 1 + src/helper/graphic_utils.cpp | 23 +++++++++++++++++++++++ src/helper/graphic_utils.hpp | 13 ++----------- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/executables/game/application.cpp b/src/executables/game/application.cpp index 605b6a5eb..a7b957f2c 100644 --- a/src/executables/game/application.cpp +++ b/src/executables/game/application.cpp @@ -462,6 +462,7 @@ void Application::initialize() { const auto start_time = SDL_GetTicks64(); std::future load_everything_thread = std::async(std::launch::async, [this] { + utils::set_thread_name("oopetris loading"); this->m_settings_manager = std::make_unique(this); this->m_settings_manager->add_callback([this](const auto& settings) { this->reload_api(settings); }); diff --git a/src/helper/graphic_utils.cpp b/src/helper/graphic_utils.cpp index cc1a98d24..3d7db0cd0 100644 --- a/src/helper/graphic_utils.cpp +++ b/src/helper/graphic_utils.cpp @@ -203,3 +203,26 @@ void utils::exit(int status_code) { std::exit(status_code); #endif } + +// inspired by SDL_SYS_SetupThread also uses that code for most platforms +OOPETRIS_GRAPHICS_EXPORTED void utils::set_thread_name(const char* name) { + +#if defined(__APPLE__) || defined(__linux__) || defined(__ANDROID__) || defined(FLATPAK_BUILD) + if (pthread_setname_np(pthread_self(), name) == ERANGE) { + char namebuf[16] = {}; /* Limited to 16 chars (with 0 byte) */ + memcpy(namebuf, name, 15); + namebuf[15] = '\0'; + pthread_setname_np(pthread_self(), namebuf); + } +#elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + std::wstring name_w{}; + for (std::size_t i = 0; i < strlen(name); ++i) { + result += name[i]; + } + + SetThreadDescription(GetCurrentThread(), name_w.c_str()); + +#else + UNUSED(name); +#endif +} diff --git a/src/helper/graphic_utils.hpp b/src/helper/graphic_utils.hpp index 0c525a982..a27b9adb0 100644 --- a/src/helper/graphic_utils.hpp +++ b/src/helper/graphic_utils.hpp @@ -38,18 +38,9 @@ namespace utils { OOPETRIS_GRAPHICS_EXPORTED [[nodiscard]] std::optional create_directory(const std::filesystem::path& folder, bool recursive); -// this needs some special handling, so the macro is defined here -#if defined(_MSC_VER) -#if defined(OOPETRIS_LIBRARY_GRAPHICS_TYPE) && OOPETRIS_LIBRARY_GRAPHICS_TYPE == 0 - -#else - -#endif -#else - -#endif - + OOPETRIS_GRAPHICS_EXPORTED void set_thread_name(const char* name); +// this needs some special handling, so the macro is defined here #if defined(_MSC_VER) #if defined(OOPETRIS_LIBRARY_GRAPHICS_TYPE) && OOPETRIS_LIBRARY_GRAPHICS_TYPE == 0 #if defined(OOPETRIS_LIBRARY_GRAPHICS_EXPORT) From 7595ddf3188e956f5f4fb937ec6d7653a0bc374a Mon Sep 17 00:00:00 2001 From: Totto16 Date: Tue, 12 Nov 2024 19:11:36 +0100 Subject: [PATCH 15/51] fix: don't include iostream in this increase compilation speed, since not everyone that uses this core header needs iostream, those who need it inlcude it themselves now --- src/discord/core.cpp | 2 +- src/helper/clock_source.cpp | 1 + src/input/controller_input.cpp | 2 +- src/input/guid.cpp | 1 + src/input/keyboard_input.cpp | 1 + src/input/touch_input.cpp | 1 + src/libs/core/helper/color.hpp | 1 + src/libs/core/helper/utils.hpp | 2 -- src/lobby/api.cpp | 1 - src/lobby/credentials/secret.cpp | 2 ++ src/manager/sdl_key.cpp | 2 +- src/ui/hoverable.cpp | 57 ++++++++++++++++++++++++++++++++ src/ui/hoverable.hpp | 55 ++++-------------------------- src/ui/layout.cpp | 1 + src/ui/layouts/focus_layout.cpp | 2 +- src/ui/meson.build | 2 ++ 16 files changed, 78 insertions(+), 55 deletions(-) create mode 100644 src/ui/hoverable.cpp diff --git a/src/discord/core.cpp b/src/discord/core.cpp index 6a5e32c6b..b28ee212c 100644 --- a/src/discord/core.cpp +++ b/src/discord/core.cpp @@ -5,9 +5,9 @@ #include "./core.hpp" #include +#include #include - [[nodiscard]] std::string constants::discord ::get_asset_key(constants::discord::ArtAsset asset) { switch (asset) { diff --git a/src/helper/clock_source.cpp b/src/helper/clock_source.cpp index ab88da659..8d3fae60f 100644 --- a/src/helper/clock_source.cpp +++ b/src/helper/clock_source.cpp @@ -3,6 +3,7 @@ #include #include +#include #include namespace { diff --git a/src/input/controller_input.cpp b/src/input/controller_input.cpp index 5841a542f..82b583a6c 100644 --- a/src/input/controller_input.cpp +++ b/src/input/controller_input.cpp @@ -6,7 +6,7 @@ #include "input/joystick_input.hpp" #include "manager/sdl_controller_key.hpp" - +#include #include input::ControllerInput::ControllerInput( diff --git a/src/input/guid.cpp b/src/input/guid.cpp index 40ab78bf5..41b0b2777 100644 --- a/src/input/guid.cpp +++ b/src/input/guid.cpp @@ -6,6 +6,7 @@ #include #include +#include sdl::GUID::GUID(const SDL_GUID& data) : m_guid{} { std::ranges::copy(data.data, std::begin(m_guid)); diff --git a/src/input/keyboard_input.cpp b/src/input/keyboard_input.cpp index da37ce6e1..199fbc162 100644 --- a/src/input/keyboard_input.cpp +++ b/src/input/keyboard_input.cpp @@ -1,4 +1,5 @@ #include +#include #include "input/game_input.hpp" #include "input/input.hpp" diff --git a/src/input/touch_input.cpp b/src/input/touch_input.cpp index b7654f01d..115ef5b07 100644 --- a/src/input/touch_input.cpp +++ b/src/input/touch_input.cpp @@ -6,6 +6,7 @@ #include "touch_input.hpp" #include +#include #include #include #include diff --git a/src/libs/core/helper/color.hpp b/src/libs/core/helper/color.hpp index 120199033..f31c940d1 100644 --- a/src/libs/core/helper/color.hpp +++ b/src/libs/core/helper/color.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include diff --git a/src/libs/core/helper/utils.hpp b/src/libs/core/helper/utils.hpp index a2289f5c9..065ae1266 100644 --- a/src/libs/core/helper/utils.hpp +++ b/src/libs/core/helper/utils.hpp @@ -7,8 +7,6 @@ #include #include #include -#include -#include #include #include #include diff --git a/src/lobby/api.cpp b/src/lobby/api.cpp index 72dcc7aa3..e26092dda 100644 --- a/src/lobby/api.cpp +++ b/src/lobby/api.cpp @@ -119,7 +119,6 @@ void lobby::API::check_url( //TODO(Totto): is this done correctly std::ignore = std::async(std::launch::async, [url, callback = std::move(callback), service_provider] { auto result = lobby::API::get_api(service_provider, url); - callback(result.has_value()); }); } diff --git a/src/lobby/credentials/secret.cpp b/src/lobby/credentials/secret.cpp index 8529dd941..70f435954 100644 --- a/src/lobby/credentials/secret.cpp +++ b/src/lobby/credentials/secret.cpp @@ -5,6 +5,8 @@ #include #include +#include + namespace { namespace secrets::constants { diff --git a/src/manager/sdl_key.cpp b/src/manager/sdl_key.cpp index a5e6e20da..6b685cdc0 100644 --- a/src/manager/sdl_key.cpp +++ b/src/manager/sdl_key.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -16,7 +17,6 @@ #include #include - sdl::Key::Key(SDL_KeyCode keycode, UnderlyingModifierType modifiers) : m_keycode{ keycode }, m_modifiers{ modifiers } { } diff --git a/src/ui/hoverable.cpp b/src/ui/hoverable.cpp new file mode 100644 index 000000000..cc6998d80 --- /dev/null +++ b/src/ui/hoverable.cpp @@ -0,0 +1,57 @@ + +#include "hoverable.hpp" + +#include + +ui::Hoverable::Hoverable(const shapes::URect& fill_rect) : m_fill_rect{ fill_rect } { }; + +ui::Hoverable::~Hoverable() = default; + +[[nodiscard]] bool ui::Hoverable::is_hovered() const { + return m_is_hovered; +} + +[[nodiscard]] const shapes::URect& ui::Hoverable::fill_rect() const { + return m_fill_rect; +} + +[[nodiscard]] helper::BoolWrapper +ui::Hoverable::detect_hover(const std::shared_ptr& input_manager, const SDL_Event& event) { + + + if (const auto result = input_manager->get_pointer_event(event); result.has_value()) { + if (result->is_in(m_fill_rect)) { + + on_hover(); + + switch (result->event()) { + case input::PointerEvent::PointerDown: + return { true, ActionType::Clicked }; + case input::PointerEvent::PointerUp: + return { true, ActionType::Released }; + case input::PointerEvent::Motion: + return { true, ActionType::Hover }; + case input::PointerEvent::Wheel: + return false; + + default: + UNREACHABLE(); + } + } + + on_unhover(); + return false; + } + + return false; +} + + +void ui::Hoverable::on_hover() { + m_is_hovered = true; +} + +//TODO(Totto): this has to be used correctly, a click or focus change isn't an event, where an unhover needs to happen! +void ui::Hoverable::on_unhover() { + m_is_hovered = false; +} diff --git a/src/ui/hoverable.hpp b/src/ui/hoverable.hpp index 02a6f7f39..fc6316a3f 100644 --- a/src/ui/hoverable.hpp +++ b/src/ui/hoverable.hpp @@ -19,65 +19,24 @@ namespace ui { public: - explicit Hoverable(const shapes::URect& fill_rect) - : m_fill_rect{ fill_rect } { + explicit Hoverable(const shapes::URect& fill_rect); - }; Hoverable(const Hoverable&) = delete; Hoverable(Hoverable&&) = delete; Hoverable& operator=(const Hoverable&) = delete; Hoverable& operator=(Hoverable&&) = delete; - virtual ~Hoverable() = default; + virtual ~Hoverable(); - [[nodiscard]] auto is_hovered() const { - return m_is_hovered; - } - [[nodiscard]] const shapes::URect& fill_rect() const { - return m_fill_rect; - } + [[nodiscard]] bool is_hovered() const; + [[nodiscard]] const shapes::URect& fill_rect() const; [[nodiscard]] helper::BoolWrapper - detect_hover(const std::shared_ptr& input_manager, const SDL_Event& event) { + detect_hover(const std::shared_ptr& input_manager, const SDL_Event& event); + void on_hover(); - if (const auto result = input_manager->get_pointer_event(event); result.has_value()) { - if (result->is_in(m_fill_rect)) { - - on_hover(); - - switch (result->event()) { - case input::PointerEvent::PointerDown: - return { true, ActionType::Clicked }; - case input::PointerEvent::PointerUp: - return { true, ActionType::Released }; - case input::PointerEvent::Motion: - return { true, ActionType::Hover }; - case input::PointerEvent::Wheel: - return false; - - default: - UNREACHABLE(); - } - } - - on_unhover(); - return false; - } - - - return false; - } - - - void on_hover() { - m_is_hovered = true; - } - - //TODO(Totto): this has to be used correctly, a click or focus change isn't an event, where an unhover needs to happen! - void on_unhover() { - m_is_hovered = false; - } + void on_unhover(); }; diff --git a/src/ui/layout.cpp b/src/ui/layout.cpp index bb3e50fc9..862f79b34 100644 --- a/src/ui/layout.cpp +++ b/src/ui/layout.cpp @@ -1,6 +1,7 @@ #include "ui/layout.hpp" +#include [[nodiscard]] u32 ui::get_horizontal_alignment_offset(const Layout& layout, AlignmentHorizontal alignment, u32 width) { switch (alignment) { diff --git a/src/ui/layouts/focus_layout.cpp b/src/ui/layouts/focus_layout.cpp index 251542fc6..35bbbc044 100644 --- a/src/ui/layouts/focus_layout.cpp +++ b/src/ui/layouts/focus_layout.cpp @@ -4,9 +4,9 @@ #include "input/input.hpp" #include "ui/widget.hpp" +#include #include - ui::FocusLayout::FocusLayout(const Layout& layout, u32 focus_id, FocusOptions options, bool is_top_level) : Widget{ layout, WidgetType::Container, is_top_level }, Focusable{ focus_id }, diff --git a/src/ui/meson.build b/src/ui/meson.build index 3c896191c..69ac80b7c 100644 --- a/src/ui/meson.build +++ b/src/ui/meson.build @@ -1,5 +1,7 @@ graphics_src_files += files( 'focusable.hpp', + 'hoverable.cpp', + 'hoverable.hpp', 'layout.cpp', 'layout.hpp', 'widget.cpp', From 858433b445175527af5da6d30ca5318c7b083f3c Mon Sep 17 00:00:00 2001 From: Totto16 Date: Wed, 13 Nov 2024 01:13:47 +0100 Subject: [PATCH 16/51] fix: finalize the embedded ffmpeg encoder --- src/graphics/video_renderer_embedded.cpp | 255 +++++++++++++++-------- tools/dependencies/meson.build | 16 +- 2 files changed, 183 insertions(+), 88 deletions(-) diff --git a/src/graphics/video_renderer_embedded.cpp b/src/graphics/video_renderer_embedded.cpp index cb7a6f2f5..910ecbc5d 100644 --- a/src/graphics/video_renderer_embedded.cpp +++ b/src/graphics/video_renderer_embedded.cpp @@ -3,6 +3,7 @@ #include "helper/c_helpers.hpp" #include "helper/constants.hpp" #include "helper/git_helper.hpp" +#include "helper/graphic_utils.hpp" #include "video_renderer.hpp" extern "C" { @@ -10,15 +11,18 @@ extern "C" { #include #include #include +#include } #include +#include #include #include struct Decoder { int pipe; - pid_t pid; + std::future> encoding_thread; + std::atomic should_cancel; }; // general information and usage from: https://friendlyuser.github.io/posts/tech/cpp/Using_FFmpeg_in_C++_A_Comprehensive_Guide/ @@ -51,8 +55,13 @@ namespace { return result; } - std::optional - start_encoding(u32 fps, shapes::UPoint size, const std::filesystem::path& destination_path) { + std::optional start_encoding( + u32 fps, + shapes::UPoint size, + const std::filesystem::path& destination_path, + int input_fd, + const std::unique_ptr& decoder + ) { ScopeDeferMultiple scope_defer{}; @@ -66,8 +75,6 @@ namespace { return fmt::format("Cannot allocate an input format context"); } - scope_defer.add([input_format_ctx](void*) { avformat_free_context(input_format_ctx); }, nullptr); - const std::string resolution = fmt::format("{}x{}", size.x, size.y); const std::string framerate = fmt::format("{}", fps); @@ -87,8 +94,10 @@ namespace { // "-r {framerate}" av_dict_set(&input_options, "framerate", framerate.c_str(), 0); - // "-i -" - auto av_input_ret = avformat_open_input(&input_format_ctx, "fd:", input_fmt, &input_options); + std::string input_url = fmt::format("pipe:{}", input_fd); + + // "-i pipe:{fd}" + auto av_input_ret = avformat_open_input(&input_format_ctx, input_url.c_str(), input_fmt, &input_options); if (av_input_ret != 0) { return fmt::format("Could not open input file stdin: {}", av_error_to_string(av_input_ret)); } @@ -108,7 +117,7 @@ namespace { } - /* select the video stream */ + // select the video stream const AVCodec* input_decoder = nullptr; auto video_stream_index = av_find_best_stream(input_format_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &input_decoder, 0); if (video_stream_index < 0) { @@ -129,7 +138,7 @@ namespace { auto codec_paramaters_ret = avcodec_parameters_to_context(input_codec_context, input_video_stream->codecpar); if (codec_paramaters_ret < 0) { return fmt::format( - "Cannot set the input codec context paramaters: {}", av_error_to_string(codec_paramaters_ret) + "Cannot set the input codec context parameters: {}", av_error_to_string(codec_paramaters_ret) ); } @@ -137,7 +146,7 @@ namespace { * This is highly recommended, but not mandatory. */ input_codec_context->pkt_timebase = input_video_stream->time_base; - //NOTE: we also could set this to the provided u32 , but this also uses that and converts it to the expected format + //NOTE: we also could set this to the provided u32, but this also uses that and converts it to the expected format (fractional) input_codec_context->framerate = av_guess_frame_rate(input_format_ctx, input_video_stream, nullptr); auto codec_open_ret = avcodec_open2(input_codec_context, input_decoder, nullptr); @@ -145,7 +154,7 @@ namespace { return fmt::format("Cannot initializer the codec for the input: {}", av_error_to_string(codec_open_ret)); } - av_dump_format(input_format_ctx, 0, "fd:", 0); + av_dump_format(input_format_ctx, 0, input_url.c_str(), 0); // output setup @@ -192,8 +201,9 @@ namespace { output_codec_context->height = input_codec_context->height; output_codec_context->width = input_codec_context->width; output_codec_context->sample_aspect_ratio = input_codec_context->sample_aspect_ratio; + output_codec_context->framerate = input_codec_context->framerate; - /* video time_base can be set to whatever is handy and supported by encoder */ + // video time_base can be set to whatever is handy and supported by encoder output_codec_context->time_base = av_inv_q(input_codec_context->framerate); AVDictionary* output_options = nullptr; @@ -202,6 +212,9 @@ namespace { // "-crf 20" av_dict_set(&output_options, "crf", "20", 0); + av_dict_set(&output_options, "video_size", resolution.c_str(), 0); + + auto codec_open_out_ret = avcodec_open2(output_codec_context, output_encoder, &output_options); if (codec_open_out_ret != 0) { return fmt::format( @@ -260,47 +273,143 @@ namespace { scope_defer.add([&pkt](void*) { av_packet_free(&pkt); }, nullptr); + AVFrame* decode_frame = av_frame_alloc(); + + if (decode_frame == nullptr) { + return "Could not allocate decode AVFrame"; + } + + scope_defer.add([&decode_frame](void*) { av_frame_free(&decode_frame); }, nullptr); + + + decode_frame->format = input_codec_context->pix_fmt; + decode_frame->width = input_codec_context->width; + decode_frame->height = input_codec_context->height; + + auto frame_buffer_ret = av_frame_get_buffer(decode_frame, 0); + if (frame_buffer_ret < 0) { + return fmt::format("Could not allocate decode frame buffer: {}", av_error_to_string(frame_buffer_ret)); + } + + AVFrame* encode_frame = av_frame_alloc(); + + if (encode_frame == nullptr) { + return "Could not allocate encode AVFrame"; + } + + scope_defer.add([&encode_frame](void*) { av_frame_free(&encode_frame); }, nullptr); + + + encode_frame->format = output_codec_context->pix_fmt; + encode_frame->width = output_codec_context->width; + encode_frame->height = output_codec_context->height; + + auto outp_frame_buffer_ret = av_frame_get_buffer(encode_frame, 0); + if (outp_frame_buffer_ret < 0) { + return fmt::format("Could not allocate encode frame buffer: {}", av_error_to_string(outp_frame_buffer_ret)); + } + + // allocate conversion context (for frame conversion) + SwsContext* sws_ctx = sws_getContext( + input_codec_context->width, input_codec_context->height, input_codec_context->pix_fmt, + output_codec_context->width, output_codec_context->height, output_codec_context->pix_fmt, SWS_BICUBIC, + nullptr, nullptr, nullptr + ); + if (sws_ctx == nullptr) { + return "Could not allocate conversion context"; + } + while (true) { - auto read_ret = av_read_frame(input_format_ctx, pkt); - if (read_ret < 0) - break; + // check atomic bool, if we are cancelled + // NOTE: the video is garbage after this, since we don't close it correctly (which isn't the intention of this) + if (decoder->should_cancel) { + return std::nullopt; + } + // retrieve unencoded (raw) packet from input + auto read_frame_ret = av_read_frame(input_format_ctx, pkt); + if (read_frame_ret == AVERROR_EOF) { + break; + } else if (read_frame_ret < 0) { + return fmt::format("Receiving a frame from the input failed: {}", av_error_to_string(read_frame_ret)); + } - auto send_pkt_ret = avcodec_send_packet(output_codec_context, pkt); - if (send_pkt_ret < 0) { + // send raw packet in packet to decoder + auto send_pkt_ret = avcodec_send_packet(input_codec_context, pkt); + if (send_pkt_ret != 0) { + if (send_pkt_ret == AVERROR(EAGAIN)) { + return "Decoding failed: Output was not read correctly"; + } return fmt::format("Decoding failed: {}", av_error_to_string(send_pkt_ret)); } - int write_ret = 0; + int read_ret = 0; - while (write_ret >= 0) { - write_ret = avcodec_receive_packet(output_codec_context, pkt); - if (write_ret == AVERROR(EAGAIN) || write_ret == AVERROR_EOF) { + // encode and write as much frames as possible + while (read_ret >= 0) { + + // get decoded frame, if one is present + read_ret = avcodec_receive_frame(input_codec_context, decode_frame); + if (read_ret == AVERROR(EAGAIN) || read_ret == AVERROR_EOF) { break; + } else if (read_ret < 0) { + return fmt::format("Receiving a frame from the decoder failed: {}", av_error_to_string(read_ret)); + } - } else if (write_ret < 0) { - return fmt::format("Encoding a frame failed: {}", av_error_to_string(write_ret)); + // convert to correct output pixel format + read_ret = sws_scale_frame(sws_ctx, encode_frame, decode_frame); + if (read_ret < 0) { + return fmt::format("Frame conversion failed: {}", av_error_to_string(read_ret)); } - /* rescale output packet timestamp values from codec to stream timebase */ - av_packet_rescale_ts(pkt, output_codec_context->time_base, out_stream->time_base); - pkt->stream_index = out_stream->index; + // copy the pts from the decoded frame + encode_frame->pts = decode_frame->pts; - /* Write the compressed frame to the media file. */ - write_ret = av_interleaved_write_frame(output_format_ctx, pkt); - /* pkt is now blank (av_interleaved_write_frame() takes ownership of + // encode decoded and converted frame with output encoder + read_ret = avcodec_send_frame(output_codec_context, encode_frame); + if (read_ret != 0) { + return fmt::format("Encoding failed: {}", av_error_to_string(read_ret)); + } + + int write_ret = 0; + + // write all encoded packets + while (write_ret >= 0) { + + // get encoded packet, if one is present + write_ret = avcodec_receive_packet(output_codec_context, pkt); + if (write_ret == AVERROR(EAGAIN) || write_ret == AVERROR_EOF) { + break; + } else if (write_ret < 0) { + return fmt::format( + "Receiving a packet from the encoder failed: {}", av_error_to_string(write_ret) + ); + } + + // prepare packet for muxing + pkt->stream_index = out_stream->index; + + // rescale output packet timestamp values from codec to stream timebase + av_packet_rescale_ts(pkt, output_codec_context->time_base, out_stream->time_base); + + + // Write the compressed packet (frame inside that) to the media file. + write_ret = av_interleaved_write_frame(output_format_ctx, pkt); + /* pkt is now blank (av_interleaved_write_frame() takes ownership of * its contents and resets pkt), so that no unreferencing is necessary. * This would be different if one used av_write_frame(). */ - if (write_ret < 0) { - return fmt::format("Writing an output packet failed: {}", av_error_to_string(write_ret)); + if (write_ret < 0) { + return fmt::format("Writing an output packet failed: {}", av_error_to_string(write_ret)); + } } } - - // reset the packe, so that it's ready for the next cycle - av_packet_unref(pkt); } - av_write_trailer(output_format_ctx); + + auto trailer_ret = av_write_trailer(output_format_ctx); + if (trailer_ret != 0) { + return fmt::format("Writing the trailer failed: {}", av_error_to_string(trailer_ret)); + } return std::nullopt; } @@ -317,42 +426,33 @@ std::optional VideoRendererBackend::setup(u32 fps, shapes::UPoint s std::array pipefd = { 0, 0 }; if (pipe(pipefd.data()) < 0) { - return fmt::format("FFMPEG: Could not create a pipe: {}", strerror(errno)); + return fmt::format("Could not create a pipe: {}", strerror(errno)); } - pid_t child = fork(); - if (child < 0) { - return fmt::format("FFMPEG: could not fork a child: {}", strerror(errno)); - } + std::future> encoding_thread = + std::async(std::launch::async, [pipefd, fps, size, this] -> std::optional { + utils::set_thread_name("ffmpeg encoder"); + auto result = start_encoding(fps, size, this->m_destination_path, pipefd[READ_END], this->m_decoder); - if (child == 0) { - if (dup2(pipefd.at(READ_END), STDIN_FILENO) < 0) { - std::cerr << "FFMPEG CHILD: could not reopen read end of pipe as stdin: " << strerror(errno) << "\n"; - std::exit(1); - } - close(pipefd[WRITE_END]); - - auto result = start_encoding(fps, size, m_destination_path); + if (close(pipefd[READ_END]) < 0) { + spdlog::warn("could not close read end of the pipe: {}", strerror(errno)); + } - if (result.has_value()) { - std::cerr << "FFMPEG CHILD: could not run embedded ffmpeg as a child process: " << result.value() << "\n"; - std::exit(1); - } + if (result.has_value()) { + return fmt::format("ffmpeg error: {}", result.value()); + } - std::exit(0); - } + return std::nullopt; + }); - if (close(pipefd[READ_END]) < 0) { - spdlog::error("FFMPEG: could not close read end of the pipe on the parent's end: {}", strerror(errno)); - } - m_decoder = std::make_unique(pipefd[WRITE_END], child); + m_decoder = std::make_unique(pipefd[WRITE_END], std::move(encoding_thread), false); return std::nullopt; } bool VideoRendererBackend::add_frame(SDL_Surface* surface) { if (write(m_decoder->pipe, surface->pixels, static_cast(surface->h) * surface->pitch) < 0) { - spdlog::error("FFMPEG: failed to write into ffmpeg pipe: {}", strerror(errno)); + spdlog::error("failed to write into ffmpeg pipe: {}", strerror(errno)); return false; } return true; @@ -360,36 +460,19 @@ bool VideoRendererBackend::add_frame(SDL_Surface* surface) { bool VideoRendererBackend::finish(bool cancel) { + if (cancel) { + m_decoder->should_cancel = true; + } if (close(m_decoder->pipe) < 0) { - spdlog::warn("FFMPEG: could not close write end of the pipe on the parent's end: {}", strerror(errno)); + spdlog::warn("could not close write end of the pipe: {}", strerror(errno)); } - if (cancel) - kill(m_decoder->pid, SIGKILL); - - while (true) { - int wstatus = 0; - if (waitpid(m_decoder->pid, &wstatus, 0) < 0) { - spdlog::error("FFMPEG: could not wait for ffmpeg child process to finish: {}", strerror(errno)); - return false; - } - - if (WIFEXITED(wstatus)) { - int exit_status = WEXITSTATUS(wstatus); - if (exit_status != 0) { - spdlog::error("FFMPEG: ffmpeg exited with code {}", exit_status); - return false; - } - - return true; - } - - if (WIFSIGNALED(wstatus)) { - spdlog::error("FFMPEG: ffmpeg got terminated by {}", strsignal(WTERMSIG(wstatus))); - return false; - } + m_decoder->encoding_thread.wait(); + auto result = m_decoder->encoding_thread.get(); + if (result.has_value()) { + spdlog::error("FFMPEG error: {}", result.value()); + return false; } - - UNREACHABLE(); + return true; } diff --git a/tools/dependencies/meson.build b/tools/dependencies/meson.build index eb4f1c8fc..18a7e906a 100644 --- a/tools/dependencies/meson.build +++ b/tools/dependencies/meson.build @@ -469,7 +469,13 @@ if build_application error('Embedded ffmpeg is still WIP on windows') endif - ffmpeg_dep_names = ['libavutil', 'libavcodec', 'libavformat', 'libavfilter'] + ffmpeg_dep_names = [ + 'libavutil', + 'libavcodec', + 'libavformat', + 'libavfilter', + 'libswscale', + ] ffmpeg_deps = [] found_all_ffmpeg_deps = true @@ -526,7 +532,13 @@ if build_application replay_video_rendering_enabled = false else - ffmpeg_dep_names = ['avutil', 'avcodec', 'avformat', 'avfilter'] + ffmpeg_dep_names = [ + 'avutil', + 'avcodec', + 'avformat', + 'avfilter', + 'swscale', + ] ffmpeg_deps = [] found_all_ffmpeg_deps = true From a56c446b00ec4ea242f619d41e1ce6706d897043 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Wed, 13 Nov 2024 01:13:59 +0100 Subject: [PATCH 17/51] fix: name main thread --- src/executables/game/application.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/executables/game/application.cpp b/src/executables/game/application.cpp index a7b957f2c..e41e233f6 100644 --- a/src/executables/game/application.cpp +++ b/src/executables/game/application.cpp @@ -456,13 +456,15 @@ void Application::loop_entry_emscripten() { void Application::initialize() { + utils::set_thread_name("oopetris"); auto loading_screen_arg = scenes::LoadingScreen{ this }; const auto start_time = SDL_GetTicks64(); std::future load_everything_thread = std::async(std::launch::async, [this] { - utils::set_thread_name("oopetris loading"); + utils::set_thread_name("loading"); + this->m_settings_manager = std::make_unique(this); this->m_settings_manager->add_callback([this](const auto& settings) { this->reload_api(settings); }); From b8fdc0da51a859410c672eedd0e3a8623af05361 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Wed, 13 Nov 2024 01:21:50 +0100 Subject: [PATCH 18/51] fix: small improvements on the ffmpeg embedded encoder --- src/graphics/video_renderer_embedded.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/graphics/video_renderer_embedded.cpp b/src/graphics/video_renderer_embedded.cpp index 910ecbc5d..59cc1e118 100644 --- a/src/graphics/video_renderer_embedded.cpp +++ b/src/graphics/video_renderer_embedded.cpp @@ -25,9 +25,8 @@ struct Decoder { std::atomic should_cancel; }; -// general information and usage from: https://friendlyuser.github.io/posts/tech/cpp/Using_FFmpeg_in_C++_A_Comprehensive_Guide/ -// and https://trac.ffmpeg.org/wiki/Using%20libav* -// and https://github.com/FFmpeg/FFmpeg/blob/master/doc/examples/transcode.c +// general information and usage from: https://ffmpeg.org//doxygen/trunk/index.html +// and https://github.com/FFmpeg/FFmpeg/blob/master/doc/examples/README VideoRendererBackend::VideoRendererBackend(const std::filesystem::path& destination_path) : m_destination_path{ destination_path }, m_decoder{ nullptr } { } @@ -405,7 +404,10 @@ namespace { } } + // flush encoder and decoder + // this is not necessary atm, but may be necessary in the future + // write the trailer, some video containers require this, like e.g. mp4 auto trailer_ret = av_write_trailer(output_format_ctx); if (trailer_ret != 0) { return fmt::format("Writing the trailer failed: {}", av_error_to_string(trailer_ret)); From d76ac22f910a200a50288b79f2033794edc2da85 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Wed, 13 Nov 2024 01:34:25 +0100 Subject: [PATCH 19/51] fix: ci, add ANDROID_SDK_HOME env variable --- .github/workflows/android.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index a8adbea82..04f024a2b 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -50,6 +50,7 @@ jobs: - name: Build native libraries run: | + export ANDROID_SDK_HOME="$HOME/.android/sdk" bash ./platforms/build-android.sh ${{ matrix.config.arch }} complete_rebuild release cp -r ./assets/ platforms/android/app/src/main From f1ae15ed6dc91602d1b4614a12bc1a461da20617 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Wed, 13 Nov 2024 01:36:43 +0100 Subject: [PATCH 20/51] fix: fix multiple compile errors --- src/graphics/video_renderer_embedded.cpp | 2 ++ src/helper/graphic_utils.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/graphics/video_renderer_embedded.cpp b/src/graphics/video_renderer_embedded.cpp index 59cc1e118..205176088 100644 --- a/src/graphics/video_renderer_embedded.cpp +++ b/src/graphics/video_renderer_embedded.cpp @@ -406,6 +406,8 @@ namespace { // flush encoder and decoder // this is not necessary atm, but may be necessary in the future + //TODO(Totto): do it nevertheless + //NOTE: this is the case, since we send whole frames at once, trough the pipe, so if that changes, the video might get corrupted or miss a frame at the end // write the trailer, some video containers require this, like e.g. mp4 auto trailer_ret = av_write_trailer(output_format_ctx); diff --git a/src/helper/graphic_utils.cpp b/src/helper/graphic_utils.cpp index 3d7db0cd0..7fd1e32d0 100644 --- a/src/helper/graphic_utils.cpp +++ b/src/helper/graphic_utils.cpp @@ -217,7 +217,7 @@ OOPETRIS_GRAPHICS_EXPORTED void utils::set_thread_name(const char* name) { #elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) std::wstring name_w{}; for (std::size_t i = 0; i < strlen(name); ++i) { - result += name[i]; + name_w += name[i]; } SetThreadDescription(GetCurrentThread(), name_w.c_str()); From 3ab1d9778b42430a35db4dd1bd1c1a86f0c532f2 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Wed, 13 Nov 2024 12:26:30 +0100 Subject: [PATCH 21/51] fix: fix c macros extension warning on gcc --- src/graphics/video_renderer_embedded.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/graphics/video_renderer_embedded.cpp b/src/graphics/video_renderer_embedded.cpp index 205176088..4998862a7 100644 --- a/src/graphics/video_renderer_embedded.cpp +++ b/src/graphics/video_renderer_embedded.cpp @@ -6,7 +6,13 @@ #include "helper/graphic_utils.hpp" #include "video_renderer.hpp" +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#endif + extern "C" { + #include #include #include @@ -14,6 +20,7 @@ extern "C" { #include } + #include #include #include @@ -421,6 +428,10 @@ namespace { } // namespace +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + void VideoRendererBackend::is_supported_async(const std::function& callback) { callback(true); } From 2e0b99e05d8bbbd3290bbac13ed431c1ad2a4426 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Wed, 13 Nov 2024 12:27:22 +0100 Subject: [PATCH 22/51] fix: remove unnecessary line in android build script --- platforms/build-android.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/platforms/build-android.sh b/platforms/build-android.sh index 16b684d05..a59c1945f 100755 --- a/platforms/build-android.sh +++ b/platforms/build-android.sh @@ -378,8 +378,6 @@ for INDEX in "${ARCH_KEYS_INDEX[@]}"; do FFMPEG_MAKER_OUTPUT_DIR="output" - ls -lsa "$FFMPEG_MAKER_OUTPUT_DIR" - find "$FFMPEG_MAKER_OUTPUT_DIR/include/" -maxdepth 3 -mindepth 2 -type d -exec cp -r {} "$SYS_ROOT/usr/include/" \; find "$FFMPEG_MAKER_OUTPUT_DIR/lib/" -type f -exec cp -r {} "$SYS_ROOT/usr/lib/" \; From f8e70e2a963569b45d9a2e02b6b4455c0244718e Mon Sep 17 00:00:00 2001 From: Totto16 Date: Wed, 13 Nov 2024 12:27:40 +0100 Subject: [PATCH 23/51] fix: fix missing libraries on switch cross build --- tools/dependencies/meson.build | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/tools/dependencies/meson.build b/tools/dependencies/meson.build index 18a7e906a..0ca57c1c0 100644 --- a/tools/dependencies/meson.build +++ b/tools/dependencies/meson.build @@ -513,11 +513,23 @@ if build_application error('only embedded ffmpeg is supported in cross builds') endif + ffmpeg_dep_names = [ + 'avutil', + 'avcodec', + 'avformat', + 'avfilter', + 'swscale', + ] + if host_machine.system() == 'android' ffmpeg_can_be_supported = true elif host_machine.system() == 'switch' ffmpeg_can_be_supported = true + ffmpeg_dep_names += [ + 'dav1d', + 'swresample', + ] elif host_machine.system() == '3ds' ffmpeg_can_be_supported = false else @@ -532,13 +544,6 @@ if build_application replay_video_rendering_enabled = false else - ffmpeg_dep_names = [ - 'avutil', - 'avcodec', - 'avformat', - 'avfilter', - 'swscale', - ] ffmpeg_deps = [] found_all_ffmpeg_deps = true From 81817545986893ab38a31e741de4333d991fc10c Mon Sep 17 00:00:00 2001 From: Totto16 Date: Wed, 13 Nov 2024 13:24:13 +0100 Subject: [PATCH 24/51] build: fix building on android use system pkg-config and set correct pkg-config path, to find dependencies like e.g. libavformat include those dependencies correctly in the apk --- platforms/android/app/jni/Android.mk | 38 +++++++++++++++++++++++++++- platforms/build-android.sh | 12 ++++++--- tools/dependencies/meson.build | 27 ++++++++++---------- 3 files changed, 59 insertions(+), 18 deletions(-) diff --git a/platforms/android/app/jni/Android.mk b/platforms/android/app/jni/Android.mk index 86754b76d..f58c146c6 100644 --- a/platforms/android/app/jni/Android.mk +++ b/platforms/android/app/jni/Android.mk @@ -68,6 +68,42 @@ LOCAL_SRC_FILES := $(shell find "${SUBPROJECTS_PATH}" -name libkeyutils.so) include $(PREBUILT_SHARED_LIBRARY) +include $(CLEAR_VARS) +LOCAL_MODULE := libavutil +LOCAL_SRC_FILES := $(shell meson introspect --dependencies "${BUILD_PATH}" | jq -r ".[] | select(.name==\"libavutil\") | .link_args | .[]") +include $(PREBUILT_SHARED_LIBRARY) + + +include $(CLEAR_VARS) +LOCAL_MODULE := libavcodec +LOCAL_SRC_FILES := $(shell meson introspect --dependencies "${BUILD_PATH}" | jq -r ".[] | select(.name==\"libavcodec\") | .link_args | .[]") +include $(PREBUILT_SHARED_LIBRARY) + + +include $(CLEAR_VARS) +LOCAL_MODULE := libavformat +LOCAL_SRC_FILES := $(shell meson introspect --dependencies "${BUILD_PATH}" | jq -r ".[] | select(.name==\"libavformat\") | .link_args | .[]") +include $(PREBUILT_SHARED_LIBRARY) + + +include $(CLEAR_VARS) +LOCAL_MODULE := libavfilter +LOCAL_SRC_FILES := $(shell meson introspect --dependencies "${BUILD_PATH}" | jq -r ".[] | select(.name==\"libavfilter\") | .link_args | .[]") +include $(PREBUILT_SHARED_LIBRARY) + + +include $(CLEAR_VARS) +LOCAL_MODULE := libswscale +LOCAL_SRC_FILES := $(shell meson introspect --dependencies "${BUILD_PATH}" | jq -r ".[] | select(.name==\"libswscale\") | .link_args | .[]") +include $(PREBUILT_SHARED_LIBRARY) + + +include $(CLEAR_VARS) +LOCAL_MODULE := libswresample +LOCAL_SRC_FILES := $(shell meson introspect --dependencies "${BUILD_PATH}" | jq -r ".[] | select(.name==\"libswresample\") | .link_args | .[]") +include $(PREBUILT_SHARED_LIBRARY) + + include $(CLEAR_VARS) LOCAL_MODULE := oopetris_core LIB_PATH := $(BUILD_PATH)/src/libs/core @@ -99,7 +135,7 @@ include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := main -LOCAL_SHARED_LIBRARIES := SDL2 sdl2_ttf freetype png16 sdl2_mixer vorbis vorbisfile ogg sdl2_image fmt keyutils oopetris_core oopetris_recordings oopetris_graphics oopetris +LOCAL_SHARED_LIBRARIES := SDL2 sdl2_ttf freetype png16 sdl2_mixer vorbis vorbisfile ogg sdl2_image fmt keyutils oopetris_core oopetris_recordings oopetris_graphics oopetris libavutil libavcodec libavformat libavfilter libswscale libswresample LOCAL_LDLIBS := -ldl -lGLESv1_CM -lGLESv2 -lOpenSLES -llog -landroid LOCAL_LDFLAGS := -Wl,--no-undefined include $(BUILD_SHARED_LIBRARY) diff --git a/platforms/build-android.sh b/platforms/build-android.sh index a59c1945f..deba6ade9 100755 --- a/platforms/build-android.sh +++ b/platforms/build-android.sh @@ -114,8 +114,9 @@ for INDEX in "${ARCH_KEYS_INDEX[@]}"; do export BIN_DIR="$HOST_ROOT/bin" export PATH="$BIN_DIR:$PATH" - LIB_PATH="${SYS_ROOT}/usr/lib/$ARM_TRIPLE:${SYS_ROOT}/usr/lib/$ARM_TRIPLE/${SDK_VERSION}" - INC_PATH="${SYS_ROOT}/usr/include" + export LIB_PATH="${SYS_ROOT}/usr/lib/$ARM_TRIPLE:${SYS_ROOT}/usr/lib/$ARM_TRIPLE/${SDK_VERSION}" + export INC_PATH="${SYS_ROOT}/usr/include" + export PKG_CONFIG_PATH="${SYS_ROOT}/usr/lib/pkgconfig/" export LIBRARY_PATH="$SYS_ROOT/usr/lib/$ARM_NAME_TRIPLE/$SDK_VERSION" @@ -382,6 +383,8 @@ for INDEX in "${ARCH_KEYS_INDEX[@]}"; do find "$FFMPEG_MAKER_OUTPUT_DIR/lib/" -type f -exec cp -r {} "$SYS_ROOT/usr/lib/" \; + find "build/" -maxdepth 5 -mindepth 4 -type f -name "*.pc" -exec cp -r {} "$SYS_ROOT/usr/lib/pkgconfig/" \; + touch "$BUILD_FFMPEG_FILE" fi @@ -437,7 +440,7 @@ prefix = '$SYS_ROOT' libdir = '$LIB_PATH' [properties] -pkg_config_libdir = '$SYS_ROOT/usr/lib/pkgconfig' +pkg_config_libdir = '$PKG_CONFIG_PATH' sys_root = '${SYS_ROOT}' EOF @@ -480,7 +483,8 @@ EOF --cross-file "./platforms/crossbuild-android-$ARM_TARGET_ARCH.ini" \ "-Dbuildtype=$BUILDTYPE" \ -Dsdl2:use_hidapi=enabled \ - -Dclang_libcpp=disabled + -Dclang_libcpp=disabled \ + -Duse_embedded_ffmpeg=enabled fi diff --git a/tools/dependencies/meson.build b/tools/dependencies/meson.build index 0ca57c1c0..c3c08a2fd 100644 --- a/tools/dependencies/meson.build +++ b/tools/dependencies/meson.build @@ -456,14 +456,15 @@ if build_application endif - use_embedded_ffmpeg = get_option('use_embedded_ffmpeg') + use_embedded_ffmpeg_option = get_option('use_embedded_ffmpeg') + use_embedded_ffmpeg = false replay_video_rendering_enabled = true if host_machine.system() == 'darwin' or host_machine.system() == 'linux' or host_machine.system() == 'windows' - use_embedded_ffmpeg = use_embedded_ffmpeg.enable_auto_if(is_flatpak_build).enabled() + use_embedded_ffmpeg = use_embedded_ffmpeg_option.enable_auto_if(is_flatpak_build).enabled() - # on the desktop we only use the embedded ffmpeg, if we require it, we prefer the command line tool, that is installed on the systne, except for flatpak builds + # on the desktop we only use the embedded ffmpeg, if we require it, we prefer the command line tool, that is installed on the system, except for flatpak builds if use_embedded_ffmpeg if host_machine.system() == 'windows' error('Embedded ffmpeg is still WIP on windows') @@ -480,7 +481,7 @@ if build_application found_all_ffmpeg_deps = true foreach ffmpeg_dep_name : ffmpeg_dep_names - ffmpeg_dep = dependency(ffmpeg_dep_name, required: false) + ffmpeg_dep = dependency(ffmpeg_dep_name, required: use_embedded_ffmpeg_option) if not ffmpeg_dep.found() found_all_ffmpeg_deps = false break @@ -507,18 +508,19 @@ if build_application else if meson.is_cross_build() - use_embedded_ffmpeg = use_embedded_ffmpeg.allowed() + use_embedded_ffmpeg = use_embedded_ffmpeg_option.allowed() if not use_embedded_ffmpeg error('only embedded ffmpeg is supported in cross builds') endif ffmpeg_dep_names = [ - 'avutil', - 'avcodec', - 'avformat', - 'avfilter', - 'swscale', + 'libavutil', + 'libavcodec', + 'libavformat', + 'libavfilter', + 'libswscale', + 'libswresample', ] if host_machine.system() == 'android' @@ -527,8 +529,7 @@ if build_application elif host_machine.system() == 'switch' ffmpeg_can_be_supported = true ffmpeg_dep_names += [ - 'dav1d', - 'swresample', + 'libdav1d', ] elif host_machine.system() == '3ds' ffmpeg_can_be_supported = false @@ -550,7 +551,7 @@ if build_application c = meson.get_compiler('c') foreach ffmpeg_dep_name : ffmpeg_dep_names - ffmpeg_dep = c.find_library(ffmpeg_dep_name, required: false) + ffmpeg_dep = dependency(ffmpeg_dep_name, required: use_embedded_ffmpeg_option) if not ffmpeg_dep.found() found_all_ffmpeg_deps = false break From 4f9e6c68e089ac21c02cc39600c6b70792161c3c Mon Sep 17 00:00:00 2001 From: Totto16 Date: Wed, 13 Nov 2024 13:27:20 +0100 Subject: [PATCH 25/51] build: add explicit ffmpeg embed options to 3ds and switch builds, --- platforms/build-3ds.sh | 3 ++- platforms/build-switch.sh | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/platforms/build-3ds.sh b/platforms/build-3ds.sh index d1b714055..e65641b89 100755 --- a/platforms/build-3ds.sh +++ b/platforms/build-3ds.sh @@ -260,7 +260,8 @@ if [ "$COMPILE_TYPE" == "complete_rebuild" ] || [ ! -e "$BUILD_DIR" ]; then -Dcurl:unittests=disabled \ -Dcurl:bearer-auth=enabled \ -Dcurl:brotli=enabled \ - -Dcurl:libz=enabled + -Dcurl:libz=enabled \ + -Duse_embedded_ffmpeg=disabled fi diff --git a/platforms/build-switch.sh b/platforms/build-switch.sh index 55810e6ac..4196ac27e 100755 --- a/platforms/build-switch.sh +++ b/platforms/build-switch.sh @@ -157,7 +157,8 @@ if [ "$COMPILE_TYPE" == "complete_rebuild" ] || [ ! -e "$BUILD_DIR" ]; then -Dcurl:unittests=disabled \ -Dcurl:bearer-auth=enabled \ -Dcurl:brotli=enabled \ - -Dcurl:libz=enabled + -Dcurl:libz=enabled \ + -Duse_embedded_ffmpeg=enabled fi From 568f02c8483e2099c2e45352a4efdbd7d1ee739c Mon Sep 17 00:00:00 2001 From: Totto16 Date: Wed, 13 Nov 2024 13:35:00 +0100 Subject: [PATCH 26/51] feat: make loglevel of ffmpeg dependent on buildtype (debug or not) --- src/graphics/video_renderer.cpp | 28 ++++++++++++++++++++---- src/graphics/video_renderer_embedded.cpp | 7 +++++- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/graphics/video_renderer.cpp b/src/graphics/video_renderer.cpp index 3c9041a48..87f507e9b 100644 --- a/src/graphics/video_renderer.cpp +++ b/src/graphics/video_renderer.cpp @@ -122,10 +122,30 @@ std::vector VideoRendererBackend::get_encoding_paramaters( const std::string framerate = fmt::format("{}", fps); return { - "-loglevel", "verbose", "-y", "-f", "rawvideo", - "-pix_fmt", "bgra", "-s", resolution, "-r", - framerate, "-i", "-", "-c:v", "libx264", - "-crf", "20", "-pix_fmt", "yuv420p", destination_path.string(), + "-loglevel", +#if !defined(NDEBUG) + "verbose", +#else + "warning", +#endif + "-y", // always overwrite video + "-f", + "rawvideo", + "-pix_fmt", + "bgra", + "-s", + resolution, + "-r", + framerate, + "-i", + "-", + "-c:v", + "libx264", + "-crf", + "20", + "-pix_fmt", + "yuv420p", + destination_path.string(), }; } diff --git a/src/graphics/video_renderer_embedded.cpp b/src/graphics/video_renderer_embedded.cpp index 4998862a7..a10a33c88 100644 --- a/src/graphics/video_renderer_embedded.cpp +++ b/src/graphics/video_renderer_embedded.cpp @@ -71,9 +71,13 @@ namespace { ScopeDeferMultiple scope_defer{}; +#if !defined(NDEBUG) // "-loglevel verbose" av_log_set_level(AV_LOG_VERBOSE); - +#else + // "-loglevel warning" + av_log_set_level(AV_LOG_WARNING); +#endif // input setup AVFormatContext* input_format_ctx = avformat_alloc_context(); @@ -100,6 +104,7 @@ namespace { // "-r {framerate}" av_dict_set(&input_options, "framerate", framerate.c_str(), 0); + // see: https://ffmpeg.org/ffmpeg-protocols.html std::string input_url = fmt::format("pipe:{}", input_fd); // "-i pipe:{fd}" From b1c9fa6a8474615cb96c55660338c4489161eb6f Mon Sep 17 00:00:00 2001 From: Totto16 Date: Wed, 13 Nov 2024 14:53:07 +0100 Subject: [PATCH 27/51] fix: correctly implement set_thread_name on macos --- src/helper/graphic_utils.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/helper/graphic_utils.cpp b/src/helper/graphic_utils.cpp index 7fd1e32d0..e78bf9dc5 100644 --- a/src/helper/graphic_utils.cpp +++ b/src/helper/graphic_utils.cpp @@ -207,7 +207,14 @@ void utils::exit(int status_code) { // inspired by SDL_SYS_SetupThread also uses that code for most platforms OOPETRIS_GRAPHICS_EXPORTED void utils::set_thread_name(const char* name) { -#if defined(__APPLE__) || defined(__linux__) || defined(__ANDROID__) || defined(FLATPAK_BUILD) +#if defined(__APPLE__) || defined(__MACOSX__) + if (pthread_setname_np(name) == ERANGE) { + char namebuf[16] = {}; /* Limited to 16 chars (with 0 byte) */ + memcpy(namebuf, name, 15); + namebuf[15] = '\0'; + pthread_setname_np(namebuf); + } +#elif defined(__linux__) || defined(__ANDROID__) || defined(FLATPAK_BUILD) if (pthread_setname_np(pthread_self(), name) == ERANGE) { char namebuf[16] = {}; /* Limited to 16 chars (with 0 byte) */ memcpy(namebuf, name, 15); From abc8f90bee99580d71f2eb25a064005b71a41292 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Wed, 13 Nov 2024 14:56:35 +0100 Subject: [PATCH 28/51] fix: build mingw correctly in ffmepg encoder --- src/graphics/meson.build | 4 ++-- src/graphics/video_renderer_windows.cpp | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/graphics/meson.build b/src/graphics/meson.build index c5c516563..c571daf31 100644 --- a/src/graphics/meson.build +++ b/src/graphics/meson.build @@ -24,8 +24,8 @@ if replay_video_rendering_enabled 'video_renderer_embedded.cpp', ) else - - if host_machine.system() == 'darwin' or host_machine.system() == 'linux' + cpp = meson.get_compiler('cpp') + if host_machine.system() == 'darwin' or host_machine.system() == 'linux' or (cpp.get_id() == 'gcc' and host_machine.system() == 'windows') graphics_src_files += files( 'video_renderer_unix.cpp', ) diff --git a/src/graphics/video_renderer_windows.cpp b/src/graphics/video_renderer_windows.cpp index 20ba02dfc..8315044f1 100644 --- a/src/graphics/video_renderer_windows.cpp +++ b/src/graphics/video_renderer_windows.cpp @@ -2,7 +2,9 @@ #include "video_renderer.hpp" #define WIN32_LEAN_AND_MEAN +#ifndef NOMINMAX #define NOMINMAX +#endif #include From e849eb1227a7b222f99889658c0d668034a9aba5 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Wed, 13 Nov 2024 15:02:43 +0100 Subject: [PATCH 29/51] fix: switch build, replace dependency< name of required libdav1d to dav1d --- tools/dependencies/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/dependencies/meson.build b/tools/dependencies/meson.build index c3c08a2fd..99fe34fef 100644 --- a/tools/dependencies/meson.build +++ b/tools/dependencies/meson.build @@ -529,7 +529,7 @@ if build_application elif host_machine.system() == 'switch' ffmpeg_can_be_supported = true ffmpeg_dep_names += [ - 'libdav1d', + 'dav1d', ] elif host_machine.system() == '3ds' ffmpeg_can_be_supported = false From f83633f912816f85067baa036950d48af2f11f16 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Wed, 13 Nov 2024 15:11:33 +0100 Subject: [PATCH 30/51] ci: lint, install correct ffmpeg dev packages --- .github/workflows/lint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ad4814afe..a03cb1e39 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -32,9 +32,9 @@ jobs: - name: Prepare compile_commands.json run: | sudo apt-get update - sudo apt-get install ninja-build libsdl2-2.0-0 libsdl2-dev libsdl2-ttf* libsdl2-mixer* libsdl2-image* desktop-file-utils -y --no-install-recommends + sudo apt-get install ninja-build libsdl2-2.0-0 libsdl2-dev libsdl2-ttf* libsdl2-mixer* libsdl2-image* desktop-file-utils libavutil-dev libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev -y --no-install-recommends - meson setup build -Dbuildtype=release -Dclang_libcpp=disabled -Dtests=true + meson setup build -Dbuildtype=release -Dclang_libcpp=disabled -Dtests=true -Duse_embedded_ffmpeg=enabled meson compile -C build git_version.hpp - uses: cpp-linter/cpp-linter-action@v2 From bd2505627c2695500bd5bd67dd7435df759f54a9 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Wed, 13 Nov 2024 23:13:40 +0100 Subject: [PATCH 31/51] fix: fix switch build if the socket variant really works is questionable, but it compiles at least --- src/graphics/video_renderer_embedded.cpp | 76 ++++++++++++++++++++---- 1 file changed, 63 insertions(+), 13 deletions(-) diff --git a/src/graphics/video_renderer_embedded.cpp b/src/graphics/video_renderer_embedded.cpp index a10a33c88..315bd7c86 100644 --- a/src/graphics/video_renderer_embedded.cpp +++ b/src/graphics/video_renderer_embedded.cpp @@ -26,8 +26,23 @@ extern "C" { #include #include +#if defined(__NINTENDO_CONSOLE__) && defined(__SWITCH__) +#include +#include +#include +#include +#include +#include + +#ifndef INADDR_LOOPBACK +// 127.0.0.1 +#define INADDR_LOOPBACK (static_cast(0x7f000001)) +#endif + +#endif + struct Decoder { - int pipe; + int input_fd; std::future> encoding_thread; std::atomic should_cancel; }; @@ -65,7 +80,7 @@ namespace { u32 fps, shapes::UPoint size, const std::filesystem::path& destination_path, - int input_fd, + const std::string& input_url, const std::unique_ptr& decoder ) { @@ -104,10 +119,8 @@ namespace { // "-r {framerate}" av_dict_set(&input_options, "framerate", framerate.c_str(), 0); - // see: https://ffmpeg.org/ffmpeg-protocols.html - std::string input_url = fmt::format("pipe:{}", input_fd); - // "-i pipe:{fd}" + // "-i {input_url}" auto av_input_ret = avformat_open_input(&input_format_ctx, input_url.c_str(), input_fmt, &input_options); if (av_input_ret != 0) { return fmt::format("Could not open input file stdin: {}", av_error_to_string(av_input_ret)); @@ -333,7 +346,7 @@ namespace { while (true) { // check atomic bool, if we are cancelled // NOTE: the video is garbage after this, since we don't close it correctly (which isn't the intention of this) - if (decoder->should_cancel) { + if (decoder && decoder->should_cancel) { return std::nullopt; } @@ -443,19 +456,38 @@ void VideoRendererBackend::is_supported_async(const std::function& c std::optional VideoRendererBackend::setup(u32 fps, shapes::UPoint size) { + +// see: https://ffmpeg.org/ffmpeg-protocols.html +#if defined(__NINTENDO_CONSOLE__) && defined(__SWITCH__) + int socket_fd = socket(AF_INET, SOCK_STREAM, 0); + if (socket_fd < 0) { + return fmt::format("Could not create a UNIX socket: {}", strerror(errno)); + } + + u16 port = 1045; + std::string input_url = fmt::format("tcp://localhost:{}?listen=1", port); + int close_fd = -1; + +#else std::array pipefd = { 0, 0 }; if (pipe(pipefd.data()) < 0) { return fmt::format("Could not create a pipe: {}", strerror(errno)); } + int close_fd = pipefd[READ_END]; + int input_fd = pipefd[WRITE_END]; + std::string input_url = fmt::format("pipe:{}", close_fd); +#endif std::future> encoding_thread = - std::async(std::launch::async, [pipefd, fps, size, this] -> std::optional { + std::async(std::launch::async, [close_fd, input_url, fps, size, this] -> std::optional { utils::set_thread_name("ffmpeg encoder"); - auto result = start_encoding(fps, size, this->m_destination_path, pipefd[READ_END], this->m_decoder); + auto result = start_encoding(fps, size, this->m_destination_path, input_url, this->m_decoder); - if (close(pipefd[READ_END]) < 0) { - spdlog::warn("could not close read end of the pipe: {}", strerror(errno)); + if (close_fd >= 0) { + if (close(close_fd) < 0) { + spdlog::warn("could not close read end of the pipe: {}", strerror(errno)); + } } if (result.has_value()) { @@ -465,16 +497,34 @@ std::optional VideoRendererBackend::setup(u32 fps, shapes::UPoint s return std::nullopt; }); +#if defined(__NINTENDO_CONSOLE__) && defined(__SWITCH__) + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + + // localhost + addr.sin_addr.s_addr = INADDR_LOOPBACK; + + int input_fd = connect(socket_fd, reinterpret_cast(&addr), sizeof(addr)); + if (input_fd < 0) { + return fmt::format("Could not connect to a TCP socket: {}", strerror(errno)); + } +#endif + + m_decoder = std::make_unique(input_fd, std::move(encoding_thread), false); - m_decoder = std::make_unique(pipefd[WRITE_END], std::move(encoding_thread), false); return std::nullopt; } bool VideoRendererBackend::add_frame(SDL_Surface* surface) { - if (write(m_decoder->pipe, surface->pixels, static_cast(surface->h) * surface->pitch) < 0) { + + if (write(m_decoder->input_fd, surface->pixels, static_cast(surface->h) * surface->pitch) < 0) { spdlog::error("failed to write into ffmpeg pipe: {}", strerror(errno)); return false; } + return true; } @@ -484,7 +534,7 @@ bool VideoRendererBackend::finish(bool cancel) { m_decoder->should_cancel = true; } - if (close(m_decoder->pipe) < 0) { + if (close(m_decoder->input_fd) < 0) { spdlog::warn("could not close write end of the pipe: {}", strerror(errno)); } From d85e44b177881376379f93e4192c10d212a0873d Mon Sep 17 00:00:00 2001 From: Totto16 Date: Thu, 14 Nov 2024 00:08:01 +0100 Subject: [PATCH 32/51] fix: allow render button to be pressed this required some refactoring, each RequestAction EventHandleType returns a void*, that cAn contain any data, in the render case it's a reference to the selector, which has a recordings path --- src/game/game.cpp | 2 +- src/game/grid.cpp | 2 +- src/game/grid.hpp | 2 +- src/game/tetrion.cpp | 2 +- src/scenes/online_lobby/online_lobby.cpp | 7 +- .../recording_selector/recording_chooser.cpp | 2 +- .../recording_component.cpp | 94 ++++++++++++++++--- .../recording_component.hpp | 1 + .../recording_selector/recording_selector.cpp | 60 ++++++++---- .../recording_selector/recording_selector.hpp | 1 + .../settings_menu/color_setting_row.cpp | 17 ++-- src/scenes/settings_menu/settings_menu.cpp | 6 +- src/scenes/settings_menu/settings_menu.hpp | 1 + src/ui/components/abstract_slider.hpp | 4 +- src/ui/components/button.hpp | 4 +- src/ui/components/color_picker.cpp | 8 +- src/ui/components/image_view.cpp | 2 +- src/ui/components/label.cpp | 2 +- src/ui/components/link_label.cpp | 2 +- src/ui/components/textinput.cpp | 7 +- src/ui/layouts/focus_layout.cpp | 13 ++- src/ui/widget.hpp | 4 +- 22 files changed, 174 insertions(+), 69 deletions(-) diff --git a/src/game/game.cpp b/src/game/game.cpp index d0e05fd97..02104cc92 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -75,7 +75,7 @@ void Game::render(const ServiceProvider& service_provider) const { m_tetrion->render(service_provider); } -[[nodiscard]] helper::BoolWrapper> +[[nodiscard]] ui::Widget::EventHandleResult Game::handle_event(const std::shared_ptr& /*input_manager*/, const SDL_Event& /*event*/) { return false; } diff --git a/src/game/grid.cpp b/src/game/grid.cpp index d13652149..e30c49080 100644 --- a/src/game/grid.cpp +++ b/src/game/grid.cpp @@ -41,7 +41,7 @@ void Grid::render(const ServiceProvider& service_provider) const { draw_playing_field_background(service_provider); } -[[nodiscard]] helper::BoolWrapper> +[[nodiscard]] ui::Widget::EventHandleResult Grid::handle_event(const std::shared_ptr& /*input_manager*/, const SDL_Event& /*event*/) { return false; } diff --git a/src/game/grid.hpp b/src/game/grid.hpp index 447f61d1a..a44dc69c7 100644 --- a/src/game/grid.hpp +++ b/src/game/grid.hpp @@ -32,7 +32,7 @@ struct Grid final : public ui::Widget { OOPETRIS_GRAPHICS_EXPORTED void render(const ServiceProvider& service_provider) const override; - OOPETRIS_GRAPHICS_EXPORTED [[nodiscard]] helper::BoolWrapper> + OOPETRIS_GRAPHICS_EXPORTED [[nodiscard]] Widget::EventHandleResult handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; private: diff --git a/src/game/tetrion.cpp b/src/game/tetrion.cpp index 9fce86f4d..514a297f5 100644 --- a/src/game/tetrion.cpp +++ b/src/game/tetrion.cpp @@ -110,7 +110,7 @@ void Tetrion::render(const ServiceProvider& service_provider) const { } } -[[nodiscard]] helper::BoolWrapper> +[[nodiscard]] ui::Widget::EventHandleResult Tetrion::handle_event(const std::shared_ptr& /*input_manager*/, const SDL_Event& /*event*/) { return false; } diff --git a/src/scenes/online_lobby/online_lobby.cpp b/src/scenes/online_lobby/online_lobby.cpp index 072f33402..db33246a5 100644 --- a/src/scenes/online_lobby/online_lobby.cpp +++ b/src/scenes/online_lobby/online_lobby.cpp @@ -121,10 +121,11 @@ namespace scenes { if (const auto additional = event_result.get_additional(); additional.has_value()) { const auto value = additional.value(); - if (value.first == ui::EventHandleType::RequestAction) { + if (std::get<0>(value) == ui::EventHandleType::RequestAction) { - if (auto text_input = utils::is_child_class(value.second); text_input.has_value()) { + if (auto text_input = utils::is_child_class(std::get<1>(value)); + text_input.has_value()) { spdlog::info("Pressed Enter on TextInput {}", text_input.value()->get_text()); if (text_input.value()->has_focus()) { @@ -138,7 +139,7 @@ namespace scenes { } throw helper::FatalError( - fmt::format("Unsupported Handle Type: {}", magic_enum::enum_name(additional->first)) + fmt::format("Unsupported Handle Type: {}", magic_enum::enum_name(std::get<0>(value))) ); } diff --git a/src/scenes/recording_selector/recording_chooser.cpp b/src/scenes/recording_selector/recording_chooser.cpp index 4ea7d2b03..ea75bf441 100644 --- a/src/scenes/recording_selector/recording_chooser.cpp +++ b/src/scenes/recording_selector/recording_chooser.cpp @@ -95,7 +95,7 @@ void custom_ui::RecordingFileChooser::render(const ServiceProvider& service_prov m_main_grid.render(service_provider); } -helper::BoolWrapper> custom_ui::RecordingFileChooser::handle_event( +ui::Widget::EventHandleResult custom_ui::RecordingFileChooser::handle_event( const std::shared_ptr& input_manager, const SDL_Event& event ) { diff --git a/src/scenes/recording_selector/recording_component.cpp b/src/scenes/recording_selector/recording_component.cpp index bf7fbe861..1e450e1a4 100644 --- a/src/scenes/recording_selector/recording_component.cpp +++ b/src/scenes/recording_selector/recording_component.cpp @@ -10,6 +10,9 @@ #include +#if defined(_ENABLE_REPLAY_RENDERING) +#include "graphics/video_renderer.hpp" +#endif custom_ui::RecordingComponent::RecordingComponent( ServiceProvider* service_provider, @@ -24,7 +27,7 @@ custom_ui::RecordingComponent::RecordingComponent( ui::Direction::Horizontal, std::array{ 0.9 }, ui::RelativeMargin{layout.get_rect(), ui::Direction::Vertical,0.05}, std::pair{ 0.05, 0.03 }, layout,false - },m_metadata{std::move(metadata)}{ + },m_metadata{std::move(metadata)},m_current_focus_id{m_main_layout.focus_id()}{ auto text_layout_index = m_main_layout.add( @@ -36,19 +39,26 @@ custom_ui::RecordingComponent::RecordingComponent( auto* text_layout = m_main_layout.get(text_layout_index); - m_main_layout.add( + auto render_button_index = m_main_layout.add( service_provider, "Render", service_provider->font_manager().get(FontId::Default), Color::white(), - focus_helper.focus_id(), - [](const ui::TextButton&) -> bool { - //TODO: do rendering here, allow hover and click in this situation, it doesn't work as of now - - return false; - }, + focus_helper.focus_id(), [](const ui::TextButton&) -> bool { return false; }, std::pair{ 0.95, 0.85 }, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center }, std::pair{ 0.1, 0.1 } ); + auto* render_button = m_main_layout.get(render_button_index); + + render_button->disable(); + +#if defined(_ENABLE_REPLAY_RENDERING) + VideoRendererBackend::is_supported_async([render_button](bool is_supported) { + if (is_supported) { + render_button->enable(); + } + }); +#endif + text_layout->add( service_provider, "name: ?", service_provider->font_manager().get(FontId::Default), Color::white(), std::pair{ 0.5, 0.5 }, @@ -96,24 +106,80 @@ void custom_ui::RecordingComponent::render(const ServiceProvider& service_provid m_main_layout.render(service_provider); } -helper::BoolWrapper> custom_ui::RecordingComponent::handle_event( +ui::Widget::EventHandleResult custom_ui::RecordingComponent::handle_event( const std::shared_ptr& input_manager, const SDL_Event& event ) { + auto* render_button = m_main_layout.get(1); + if (has_focus() and input_manager->get_navigation_event(event) == input::NavigationEvent::OK) { - return { - true, - { ui::EventHandleType::RequestAction, this } - }; + if (m_current_focus_id == m_main_layout.focus_id()) { + return { + true, + { ui::EventHandleType::RequestAction, this, nullptr } + }; + } + + if (m_current_focus_id == render_button->focus_id()) { + return { + true, + { ui::EventHandleType::RequestAction, render_button, nullptr } + }; + } + + spdlog::error("Recording selector has invalid focused element: {}", m_current_focus_id); + } + + if (has_focus() + and (input_manager->get_navigation_event(event) == input::NavigationEvent::LEFT + or input_manager->get_navigation_event(event) == input::NavigationEvent::RIGHT)) { + + if (m_current_focus_id == m_main_layout.focus_id()) { + m_current_focus_id = render_button->focus_id(); + return true; + } + + if (m_current_focus_id == render_button->focus_id()) { + m_current_focus_id = m_main_layout.focus_id(); + return true; + } + + spdlog::error("Recording selector has invalid focused element: {}", m_current_focus_id); } if (const auto hover_result = detect_hover(input_manager, event); hover_result) { + + + if (const auto render_button_hover_result = render_button->detect_hover(input_manager, event); + render_button_hover_result) { + + this->on_unhover(); + + if (render_button_hover_result.is(ui::ActionType::Clicked)) { + + if (not has_focus()) { + return { + true, + { ui::EventHandleType::RequestFocus, this, nullptr } + }; + } + + return { + true, + { ui::EventHandleType::RequestAction, render_button, this } + }; + } + + return true; + } + if (hover_result.is(ui::ActionType::Clicked)) { + return { true, - { has_focus() ? ui::EventHandleType::RequestAction : ui::EventHandleType::RequestFocus, this } + { has_focus() ? ui::EventHandleType::RequestAction : ui::EventHandleType::RequestFocus, this, nullptr } }; } return true; diff --git a/src/scenes/recording_selector/recording_component.hpp b/src/scenes/recording_selector/recording_component.hpp index 5e812c935..602778157 100644 --- a/src/scenes/recording_selector/recording_component.hpp +++ b/src/scenes/recording_selector/recording_component.hpp @@ -35,6 +35,7 @@ namespace custom_ui { private: ui::TileLayout m_main_layout; data::RecordingMetadata m_metadata; + u32 m_current_focus_id; public: OOPETRIS_GRAPHICS_EXPORTED explicit RecordingComponent( diff --git a/src/scenes/recording_selector/recording_selector.cpp b/src/scenes/recording_selector/recording_selector.cpp index fea5ee945..9e396864e 100644 --- a/src/scenes/recording_selector/recording_selector.cpp +++ b/src/scenes/recording_selector/recording_selector.cpp @@ -6,9 +6,6 @@ #include "recording_chooser.hpp" #endif -#include - -#include "graphics/video_renderer.hpp" #include "graphics/window.hpp" #include "helper/constants.hpp" #include "helper/graphic_utils.hpp" @@ -20,10 +17,16 @@ #include "ui/layout.hpp" #include "ui/layouts/scroll_layout.hpp" #include "ui/widget.hpp" +#include #include #include +#if defined(_ENABLE_REPLAY_RENDERING) +#include "graphics/video_renderer.hpp" +#endif + + namespace scenes { using namespace details::recording::selector; //NOLINT(google-build-using-namespace) @@ -91,27 +94,50 @@ namespace scenes { // action is a reference to a structure inside m_next_command, so resetting it means, we need to copy everything out of it m_next_command = std::nullopt; + return UpdateResult{ + SceneUpdate::StopUpdating, + Scene::RawSwitch{ "ReplayGame", + std::make_unique( + m_service_provider, ui::FullScreenLayout{ m_service_provider->window() }, + recording_path + ) } + }; + } + + +#if defined(_ENABLE_REPLAY_RENDERING) + if (auto render_button = utils::is_child_class(action.widget); + render_button.has_value()) { + + + auto recording_component = utils::is_child_class( + reinterpret_cast< //NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + ui::Widget*>(action.data) + ); + if (not recording_component.has_value()) { + throw std::runtime_error( + "Requested action on render button has invalid data, this is a fatal " + "error" + ); + } + + const auto recording_path = recording_component.value()->metadata().path; + auto ren = VideoRenderer{ m_service_provider, recording_path, shapes::UPoint{ 1280, 720 } }; //TODO: do this in a seperate thread ren.render("test.mp4", 60, [](double progress) { - spdlog::info("Progress: {}", progress); + // spdlog::info("Progress: {}", progress); + UNUSED(progress); }); - //TODO: do this in a seperate scene, with a loading bar return UpdateResult{ SceneUpdate::StopUpdating, std::nullopt }; - /* - return UpdateResult{ - SceneUpdate::StopUpdating, - Scene::RawSwitch{ "ReplayGame", - std::make_unique( - m_service_provider, ui::FullScreenLayout{ m_service_provider->window() }, - recording_path - ) } - }; */ } +#endif + + #if defined(_HAVE_FILE_DIALOGS) if (auto recording_file_chooser = @@ -152,8 +178,10 @@ namespace scenes { if (const auto event_result = m_main_layout.handle_event(input_manager, event); event_result) { if (const auto additional = event_result.get_additional(); - additional.has_value() and additional.value().first == ui::EventHandleType::RequestAction) { - m_next_command = Command{ Action(additional.value().second) }; + additional.has_value() and std::get<0>(additional.value()) == ui::EventHandleType::RequestAction) { + m_next_command = Command{ + Action{ .widget = std::get<1>(additional.value()), .data = std::get<2>(additional.value()) } + }; } return true; diff --git a/src/scenes/recording_selector/recording_selector.hpp b/src/scenes/recording_selector/recording_selector.hpp index f7ce46483..968c62e77 100644 --- a/src/scenes/recording_selector/recording_selector.hpp +++ b/src/scenes/recording_selector/recording_selector.hpp @@ -13,6 +13,7 @@ namespace details::recording::selector { struct Action { ui::Widget* widget; + void* data; }; struct Command { diff --git a/src/scenes/settings_menu/color_setting_row.cpp b/src/scenes/settings_menu/color_setting_row.cpp index cfe8d9812..082af61bc 100644 --- a/src/scenes/settings_menu/color_setting_row.cpp +++ b/src/scenes/settings_menu/color_setting_row.cpp @@ -54,7 +54,8 @@ void detail::ColorSettingRectangle::render(const ServiceProvider& service_provid //TODO(Totto): maybe use a dynamic color, to have some contrast? service_provider.renderer().draw_rect_outline(m_fill_rect, Color::white()); } -helper::BoolWrapper> detail::ColorSettingRectangle::handle_event( + +ui::Widget::EventHandleResult detail::ColorSettingRectangle::handle_event( const std::shared_ptr& input_manager, const SDL_Event& event ) { @@ -64,7 +65,7 @@ helper::BoolWrapper> detail::ColorSe if (has_focus() and navigation_event == input::NavigationEvent::OK) { return { true, - { ui::EventHandleType::RequestAction, this } + { ui::EventHandleType::RequestAction, this, nullptr } }; } @@ -73,7 +74,7 @@ helper::BoolWrapper> detail::ColorSe if (hover_result.is(ui::ActionType::Clicked)) { return { true, - { ui::EventHandleType::RequestAction, this } + { ui::EventHandleType::RequestAction, this, nullptr } }; } return true; @@ -174,20 +175,22 @@ void custom_ui::ColorSettingRow::render(const ServiceProvider& service_provider) m_main_layout.render(service_provider); } -helper::BoolWrapper> custom_ui::ColorSettingRow::handle_event( +ui::Widget::EventHandleResult custom_ui::ColorSettingRow::handle_event( const std::shared_ptr& input_manager, const SDL_Event& event ) { const auto result = m_main_layout.handle_event(input_manager, event); if (const auto additional = result.get_additional(); additional.has_value()) { - if (additional->first == ui::EventHandleType::RequestAction) { + if (std::get<0>(additional.value()) == ui::EventHandleType::RequestAction) { return { result, - { ui::EventHandleType::RequestAction, this } + { ui::EventHandleType::RequestAction, this, nullptr } }; } - throw helper::FatalError(fmt::format("Unsupported Handle Type: {}", magic_enum::enum_name(additional->first))); + throw helper::FatalError( + fmt::format("Unsupported Handle Type: {}", magic_enum::enum_name(std::get<0>(additional.value()))) + ); } return result; diff --git a/src/scenes/settings_menu/settings_menu.cpp b/src/scenes/settings_menu/settings_menu.cpp index b5f1c7c1b..40af64a2c 100644 --- a/src/scenes/settings_menu/settings_menu.cpp +++ b/src/scenes/settings_menu/settings_menu.cpp @@ -248,8 +248,10 @@ namespace scenes { bool SettingsMenu::handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) { if (const auto event_result = m_main_layout.handle_event(input_manager, event); event_result) { if (const auto additional = event_result.get_additional(); - additional.has_value() and additional.value().first == ui::EventHandleType::RequestAction) { - m_next_command = Command{ Action{ additional.value().second } }; + additional.has_value() and std::get<0>(additional.value()) == ui::EventHandleType::RequestAction) { + m_next_command = Command{ + Action{ .widget = std::get<1>(additional.value()), .data = std::get<2>(additional.value()) } + }; } return true; diff --git a/src/scenes/settings_menu/settings_menu.hpp b/src/scenes/settings_menu/settings_menu.hpp index 831dbe347..5dd1cdfd4 100644 --- a/src/scenes/settings_menu/settings_menu.hpp +++ b/src/scenes/settings_menu/settings_menu.hpp @@ -19,6 +19,7 @@ namespace details::settings::menu { struct Action { ui::Widget* widget; + void* data; }; struct Command { diff --git a/src/ui/components/abstract_slider.hpp b/src/ui/components/abstract_slider.hpp index 549342643..6b15c9bfa 100644 --- a/src/ui/components/abstract_slider.hpp +++ b/src/ui/components/abstract_slider.hpp @@ -170,7 +170,7 @@ namespace ui { handled = { true, - { ui::EventHandleType::RequestFocus, this } + { ui::EventHandleType::RequestFocus, this, nullptr } }; } else if (pointer_event->is_in(m_slider_rect)) { @@ -180,7 +180,7 @@ namespace ui { handled = { true, - { ui::EventHandleType::RequestFocus, this } + { ui::EventHandleType::RequestFocus, this, nullptr } }; } diff --git a/src/ui/components/button.hpp b/src/ui/components/button.hpp index e89ae33ff..814163330 100644 --- a/src/ui/components/button.hpp +++ b/src/ui/components/button.hpp @@ -102,7 +102,7 @@ namespace ui { if (on_clicked()) { return { true, - { ui::EventHandleType::RequestAction, this } + { ui::EventHandleType::RequestAction, this, nullptr } }; } return true; @@ -114,7 +114,7 @@ namespace ui { if (on_clicked()) { return { true, - { ui::EventHandleType::RequestAction, this } + { ui::EventHandleType::RequestAction, this, nullptr } }; } } diff --git a/src/ui/components/color_picker.cpp b/src/ui/components/color_picker.cpp index 7d52d150e..b9bdaa7a0 100644 --- a/src/ui/components/color_picker.cpp +++ b/src/ui/components/color_picker.cpp @@ -148,7 +148,7 @@ void detail::ColorCanvas::draw_pseudo_circle(const ServiceProvider& service_prov renderer.draw_self_computed_circle(center, diameter, circle_color); } -helper::BoolWrapper> +ui::Widget::EventHandleResult detail::ColorCanvas::handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) { Widget::EventHandleResult handled = false; @@ -164,7 +164,7 @@ detail::ColorCanvas::handle_event(const std::shared_ptr& in SDL_CaptureMouse(SDL_TRUE); handled = { true, - { ui::EventHandleType::RequestFocus, this } + { ui::EventHandleType::RequestFocus, this, nullptr } }; } } else if (pointer_event == input::PointerEvent::PointerUp) { @@ -457,7 +457,7 @@ void ui::ColorPicker::render(const ServiceProvider& service_provider) const { m_color_text->render(service_provider); } -helper::BoolWrapper> +ui::Widget::EventHandleResult ui::ColorPicker::handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) { auto handled = m_color_slider->handle_event(input_manager, event); @@ -487,7 +487,7 @@ ui::ColorPicker::handle_event(const std::shared_ptr& input_ if (handled) { if (const auto additional = handled.get_additional(); additional.has_value()) { - switch (additional.value().first) { + switch (std::get<0>(additional.value())) { case ui::EventHandleType::RequestFocus: if (not m_color_text->has_focus()) { m_color_text->focus(); diff --git a/src/ui/components/image_view.cpp b/src/ui/components/image_view.cpp index d6f44e15b..e861f13eb 100644 --- a/src/ui/components/image_view.cpp +++ b/src/ui/components/image_view.cpp @@ -29,7 +29,7 @@ void ui::ImageView::render(const ServiceProvider& service_provider) const { service_provider.renderer().draw_texture(m_image, m_fill_rect); } -helper::BoolWrapper> +ui::Widget::EventHandleResult ui::ImageView::handle_event(const std::shared_ptr& /*input_manager*/, const SDL_Event& /*event*/) { return false; } diff --git a/src/ui/components/label.cpp b/src/ui/components/label.cpp index 622318de3..00271a4d8 100644 --- a/src/ui/components/label.cpp +++ b/src/ui/components/label.cpp @@ -26,7 +26,7 @@ void ui::Label::render(const ServiceProvider& service_provider) const { m_text.render(service_provider); } -helper::BoolWrapper> +ui::Widget::EventHandleResult ui::Label::handle_event(const std::shared_ptr& /*input_manager*/, const SDL_Event& /*event*/) { return false; } diff --git a/src/ui/components/link_label.cpp b/src/ui/components/link_label.cpp index 0dff0ffd5..c8cf1a7a2 100644 --- a/src/ui/components/link_label.cpp +++ b/src/ui/components/link_label.cpp @@ -56,7 +56,7 @@ void ui::LinkLabel::render(const ServiceProvider& service_provider) const { } } -helper::BoolWrapper> +ui::Widget::EventHandleResult ui::LinkLabel::handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) { if (const auto hover_result = detect_hover(input_manager, event); hover_result) { if (hover_result.is(ActionType::Clicked)) { diff --git a/src/ui/components/textinput.cpp b/src/ui/components/textinput.cpp index 2223980b6..2a24961f6 100644 --- a/src/ui/components/textinput.cpp +++ b/src/ui/components/textinput.cpp @@ -105,8 +105,7 @@ void ui::TextInput::render(const ServiceProvider& service_provider) const { } } -helper::BoolWrapper> -ui::TextInput::handle_event( //NOLINT(readability-function-cognitive-complexity) +ui::Widget::EventHandleResult ui::TextInput::handle_event( //NOLINT(readability-function-cognitive-complexity) const std::shared_ptr& input_manager, const SDL_Event& event ) { @@ -116,7 +115,7 @@ ui::TextInput::handle_event( //NOLINT(readability-function-cognitive-complexity) if (hover_result.is(ActionType::Clicked)) { return { true, - { EventHandleType::RequestFocus, this } + { EventHandleType::RequestFocus, this, nullptr } }; } @@ -130,7 +129,7 @@ ui::TextInput::handle_event( //NOLINT(readability-function-cognitive-complexity) on_unfocus(); return { true, - { EventHandleType::RequestAction, this } + { EventHandleType::RequestAction, this, nullptr } }; } //TODO(Totto): in some cases this is caught before that, and never triggered diff --git a/src/ui/layouts/focus_layout.cpp b/src/ui/layouts/focus_layout.cpp index 35bbbc044..86eeed38b 100644 --- a/src/ui/layouts/focus_layout.cpp +++ b/src/ui/layouts/focus_layout.cpp @@ -135,7 +135,7 @@ ui::FocusLayout::handle_event_result(const std::optional auto value = result.value(); - switch (value.first) { + switch (std::get<0>(value)) { case ui::EventHandleType::RequestFocus: { const auto focusable = as_focusable(widget); if (not focusable.has_value()) { @@ -160,7 +160,8 @@ ui::FocusLayout::handle_event_result(const std::optional // if the layout itself has not focus, it needs focus itself too if (not has_focus()) { - return ui::Widget::InnerState{ ui::EventHandleType::RequestFocus, value.second }; + return ui::Widget::InnerState{ ui::EventHandleType::RequestFocus, std::get<1>(value), + std::get<2>(value) }; } @@ -191,12 +192,14 @@ ui::FocusLayout::handle_event_result(const std::optional const auto test_forward = try_set_next_focus(FocusChangeDirection::Forward); if (not test_forward) { if (m_options.wrap_around) { - return ui::Widget::InnerState{ ui::EventHandleType::RequestUnFocus, value.second }; + return ui::Widget::InnerState{ ui::EventHandleType::RequestUnFocus, std::get<1>(value), + std::get<2>(value) }; } const auto test_backwards = try_set_next_focus(FocusChangeDirection::Backward); if (not test_backwards) { - return ui::Widget::InnerState{ ui::EventHandleType::RequestUnFocus, value.second }; + return ui::Widget::InnerState{ ui::EventHandleType::RequestUnFocus, std::get<1>(value), + std::get<2>(value) }; } } @@ -204,7 +207,7 @@ ui::FocusLayout::handle_event_result(const std::optional } case ui::EventHandleType::RequestAction: { // just forward it - return ui::Widget::InnerState{ ui::EventHandleType::RequestAction, value.second }; + return ui::Widget::InnerState{ ui::EventHandleType::RequestAction, std::get<1>(value), std::get<2>(value) }; } default: UNREACHABLE(); diff --git a/src/ui/widget.hpp b/src/ui/widget.hpp index 28684771a..67b395f58 100644 --- a/src/ui/widget.hpp +++ b/src/ui/widget.hpp @@ -8,7 +8,7 @@ #include "ui/layout.hpp" #include -#include +#include namespace ui { @@ -23,7 +23,7 @@ namespace ui { bool m_top_level; public: - using InnerState = std::pair; + using InnerState = std::tuple; using EventHandleResult = helper::BoolWrapper; explicit Widget(const Layout& layout, WidgetType type, bool is_top_level) From 4e10330d269143029d867dfdcd1e2ea47ef116c6 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Thu, 14 Nov 2024 00:36:43 +0100 Subject: [PATCH 33/51] feat: split spinner and loading screen into two separate parts, so that the spinner can be reused --- src/scenes/loading_screen/loading_screen.cpp | 91 +------------ src/scenes/loading_screen/loading_screen.hpp | 10 +- .../recording_selector/recording_render.hpp | 33 +++++ src/ui/components/meson.build | 4 +- src/ui/components/spinner.cpp | 121 ++++++++++++++++++ src/ui/components/spinner.hpp | 34 +++++ 6 files changed, 200 insertions(+), 93 deletions(-) create mode 100644 src/scenes/recording_selector/recording_render.hpp create mode 100644 src/ui/components/spinner.cpp create mode 100644 src/ui/components/spinner.hpp diff --git a/src/scenes/loading_screen/loading_screen.cpp b/src/scenes/loading_screen/loading_screen.cpp index 47b6157ce..b570d0998 100644 --- a/src/scenes/loading_screen/loading_screen.cpp +++ b/src/scenes/loading_screen/loading_screen.cpp @@ -13,38 +13,15 @@ #include + scenes::LoadingScreen::LoadingScreen(ServiceProvider* service_provider) - : m_segments{ - { Mino{ grid::GridPoint{ 0, 0 }, helper::TetrominoType::J }, 1.0 }, - { Mino{ grid::GridPoint{ 1, 0 }, helper::TetrominoType::L }, 1.0 }, - { Mino{ grid::GridPoint{ 2, 0 }, helper::TetrominoType::I }, 1.0 }, - { Mino{ grid::GridPoint{ 2, 1 }, helper::TetrominoType::O }, 1.0 }, - { Mino{ grid::GridPoint{ 2, 2 }, helper::TetrominoType::S }, 1.0 }, - { Mino{ grid::GridPoint{ 1, 2 }, helper::TetrominoType::T }, 1.0 }, - { Mino{ grid::GridPoint{ 0, 2 }, helper::TetrominoType::I }, 1.0 }, - { Mino{ grid::GridPoint{ 0, 1 }, helper::TetrominoType::Z }, 1.0 }, -},m_logo{logo::get_logo(service_provider)} { - - const auto [total_x_tiles, total_y_tiles] = utils::get_orientation() == utils::Orientation::Landscape - ? std::pair{ 17, 9 } - : std::pair{ 9, 17 }; - - constexpr auto loading_segments_size = 3; + : m_logo{ logo::get_logo(service_provider) }, + m_spinner{ ui::FullScreenLayout{ service_provider->window() }, true } { const auto& window = service_provider->window(); const auto layout = window.size(); - const u32 tile_size_x = layout.x / total_x_tiles; - const u32 tile_size_y = layout.y / total_y_tiles; - - m_tile_size = std::min(tile_size_y, tile_size_x); - - const shapes::UPoint grid_start_offset = { (total_x_tiles - loading_segments_size) / 2, - (total_y_tiles - loading_segments_size) / 2 }; - - m_start_offset = grid_start_offset * m_tile_size; - constexpr const auto logo_width_percentage = 0.8; constexpr const auto start_x = (1.0 - logo_width_percentage) / 2.0; @@ -58,70 +35,14 @@ scenes::LoadingScreen::LoadingScreen(ServiceProvider* service_provider) m_logo_rect = ui::RelativeLayout(window, start_x, 0.05, logo_width_percentage, logo_height_percentage).get_rect(); } -namespace { - [[nodiscard]] double elapsed_time() { - return static_cast(SDL_GetTicks64()) / 1000.0; - } -} // namespace - void scenes::LoadingScreen::update() { - - constexpr const auto speed = std::numbers::pi_v * 1.0; - constexpr const auto amplitude = 1.1; - constexpr const auto scale_offset = 1.3; - - const auto length = m_segments.size(); - const auto length_d = static_cast(length); - - const auto time = elapsed_time(); - - for (size_t i = 0; i < length; ++i) { - - auto& segment = m_segments.at(i); - - auto& scale = std::get<1>(segment); - - const auto offset = std::numbers::pi_v * 2.0 * static_cast(length - i - 1) / length_d; - - scale = std::min((amplitude * std::sin((time * speed) + offset)) + scale_offset, 1.0); - } - // + m_spinner.update(); } void scenes::LoadingScreen::render(const ServiceProvider& service_provider) const { - - service_provider.renderer().draw_rect_filled(service_provider.window().screen_rect(), Color::black()); + // NOTE: this already fills the background + m_spinner.render(service_provider); service_provider.renderer().draw_texture(m_logo, m_logo_rect); - - constexpr const auto scale_threshold = 0.25; - - for (const auto& [mino, scale] : m_segments) { - if (scale >= scale_threshold) { - const auto original_scale = - static_cast(m_tile_size) / static_cast(grid::original_tile_size); - - - const auto tile_size = static_cast(static_cast(m_tile_size) * scale); - - helper::graphics::render_mino( - mino, service_provider, MinoTransparency::Solid, original_scale, - [this, tile_size](const grid::GridPoint& point) -> auto { - return this->to_screen_coords(point, tile_size); - }, - { tile_size, tile_size } - ); - } - - //TODO(Totto): render text here, but than we need to load the fonts before this, not in the loading thread (not that they take that long) - } -} - - -[[nodiscard]] shapes::UPoint scenes::LoadingScreen::to_screen_coords(const grid::GridPoint& point, u32 tile_size) - const { - const auto start_edge = m_start_offset + point.cast() * m_tile_size; - const auto inner_offset = m_tile_size - (tile_size / 2); - return start_edge + shapes::UPoint{ inner_offset, inner_offset }; } diff --git a/src/scenes/loading_screen/loading_screen.hpp b/src/scenes/loading_screen/loading_screen.hpp index 02dd81cd4..2c2e2859e 100644 --- a/src/scenes/loading_screen/loading_screen.hpp +++ b/src/scenes/loading_screen/loading_screen.hpp @@ -5,6 +5,7 @@ #include "../logo/logo.hpp" #include "graphics/rect.hpp" #include "manager/service_provider.hpp" +#include "ui/components/spinner.hpp" #include @@ -12,12 +13,10 @@ namespace scenes { struct LoadingScreen { private: - std::vector> m_segments; Texture m_logo; - shapes::URect m_logo_rect; - u32 m_tile_size; - shapes::UPoint m_start_offset; + shapes::URect m_logo_rect; + ui::IndeterminateSpinner m_spinner; public: OOPETRIS_GRAPHICS_EXPORTED explicit LoadingScreen(ServiceProvider* service_provider); @@ -25,9 +24,6 @@ namespace scenes { OOPETRIS_GRAPHICS_EXPORTED void update(); OOPETRIS_GRAPHICS_EXPORTED void render(const ServiceProvider& service_provider) const; - - private: - [[nodiscard]] shapes::UPoint to_screen_coords(const grid::GridPoint& point, u32 tile_size) const; }; } // namespace scenes diff --git a/src/scenes/recording_selector/recording_render.hpp b/src/scenes/recording_selector/recording_render.hpp new file mode 100644 index 000000000..02dd81cd4 --- /dev/null +++ b/src/scenes/recording_selector/recording_render.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "../logo/logo.hpp" +#include "graphics/rect.hpp" +#include "manager/service_provider.hpp" + +#include + +namespace scenes { + + struct LoadingScreen { + private: + std::vector> m_segments; + Texture m_logo; + shapes::URect m_logo_rect; + + u32 m_tile_size; + shapes::UPoint m_start_offset; + + public: + OOPETRIS_GRAPHICS_EXPORTED explicit LoadingScreen(ServiceProvider* service_provider); + + OOPETRIS_GRAPHICS_EXPORTED void update(); + + OOPETRIS_GRAPHICS_EXPORTED void render(const ServiceProvider& service_provider) const; + + private: + [[nodiscard]] shapes::UPoint to_screen_coords(const grid::GridPoint& point, u32 tile_size) const; + }; + +} // namespace scenes diff --git a/src/ui/components/meson.build b/src/ui/components/meson.build index bdc68421b..e65171638 100644 --- a/src/ui/components/meson.build +++ b/src/ui/components/meson.build @@ -13,8 +13,10 @@ graphics_src_files += files( 'link_label.hpp', 'slider.cpp', 'slider.hpp', + 'spinner.cpp', + 'spinner.hpp', 'text_button.cpp', 'text_button.hpp', 'textinput.cpp', - 'textinput.hpp', + 'textinput.cpp', ) diff --git a/src/ui/components/spinner.cpp b/src/ui/components/spinner.cpp new file mode 100644 index 000000000..5847873c7 --- /dev/null +++ b/src/ui/components/spinner.cpp @@ -0,0 +1,121 @@ +#include +#include + +#include "game/graphic_helpers.hpp" +#include "graphics/renderer.hpp" +#include "helper/platform.hpp" +#include "manager/service_provider.hpp" +#include "spinner.hpp" +#include "ui/layout.hpp" + +#include + +ui::IndeterminateSpinner::IndeterminateSpinner( + const Layout& layout, + bool is_top_level +):Widget{ + layout, WidgetType::Component, is_top_level +}, + m_segments{ + { Mino{ grid::GridPoint{ 0, 0 }, helper::TetrominoType::J }, 1.0 }, + { Mino{ grid::GridPoint{ 1, 0 }, helper::TetrominoType::L }, 1.0 }, + { Mino{ grid::GridPoint{ 2, 0 }, helper::TetrominoType::I }, 1.0 }, + { Mino{ grid::GridPoint{ 2, 1 }, helper::TetrominoType::O }, 1.0 }, + { Mino{ grid::GridPoint{ 2, 2 }, helper::TetrominoType::S }, 1.0 }, + { Mino{ grid::GridPoint{ 1, 2 }, helper::TetrominoType::T }, 1.0 }, + { Mino{ grid::GridPoint{ 0, 2 }, helper::TetrominoType::I }, 1.0 }, + { Mino{ grid::GridPoint{ 0, 1 }, helper::TetrominoType::Z }, 1.0 }, + }{ + + const auto [total_x_tiles, total_y_tiles] = utils::get_orientation() == utils::Orientation::Landscape + ? std::pair{ 17, 9 } + : std::pair{ 9, 17 }; + + constexpr auto loading_segments_size = 3; + + const auto layout_rect = layout.get_rect(); + const auto layout_size = shapes::UPoint{ layout_rect.width(), layout_rect.height() }; + + const u32 tile_size_x = layout_size.x / total_x_tiles; + const u32 tile_size_y = layout_size.y / total_y_tiles; + + m_tile_size = std::min(tile_size_y, tile_size_x); + + const shapes::UPoint grid_start_offset = { (total_x_tiles - loading_segments_size) / 2, + (total_y_tiles - loading_segments_size) / 2 }; + + m_start_offset = grid_start_offset * m_tile_size; +} + +namespace { + [[nodiscard]] double elapsed_time() { + return static_cast(SDL_GetTicks64()) / 1000.0; + } +} // namespace + + +void ui::IndeterminateSpinner::update() { + + constexpr const auto speed = std::numbers::pi_v * 1.0; + constexpr const auto amplitude = 1.1; + constexpr const auto scale_offset = 1.3; + + const auto length = m_segments.size(); + const auto length_d = static_cast(length); + + const auto time = elapsed_time(); + + for (size_t i = 0; i < length; ++i) { + + auto& segment = m_segments.at(i); + + auto& scale = std::get<1>(segment); + + const auto offset = std::numbers::pi_v * 2.0 * static_cast(length - i - 1) / length_d; + + scale = std::min((amplitude * std::sin((time * speed) + offset)) + scale_offset, 1.0); + } + // +} + +void ui::IndeterminateSpinner::render(const ServiceProvider& service_provider) const { + + service_provider.renderer().draw_rect_filled(layout().get_rect(), Color::black()); + + constexpr const auto scale_threshold = 0.25; + + for (const auto& [mino, scale] : m_segments) { + if (scale >= scale_threshold) { + const auto original_scale = + static_cast(m_tile_size) / static_cast(grid::original_tile_size); + + + const auto tile_size = static_cast(static_cast(m_tile_size) * scale); + + helper::graphics::render_mino( + mino, service_provider, MinoTransparency::Solid, original_scale, + [this, tile_size](const grid::GridPoint& point) -> auto { + return this->to_screen_coords(point, tile_size); + }, + { tile_size, tile_size } + ); + } + + //TODO(Totto): render text here, but than we need to load the fonts before this, not in the loading thread (not that they take that long) + } +} + + +[[nodiscard]] shapes::UPoint ui::IndeterminateSpinner::to_screen_coords(const grid::GridPoint& point, u32 tile_size) + const { + const auto start_edge = m_start_offset + point.cast() * m_tile_size; + const auto inner_offset = m_tile_size - (tile_size / 2); + return start_edge + shapes::UPoint{ inner_offset, inner_offset }; +} + +ui::Widget::EventHandleResult ui::IndeterminateSpinner::handle_event( + const std::shared_ptr& /* input_manager */, + const SDL_Event& /* event */ +) { + return false; +} diff --git a/src/ui/components/spinner.hpp b/src/ui/components/spinner.hpp new file mode 100644 index 000000000..11e71efc4 --- /dev/null +++ b/src/ui/components/spinner.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include "graphics/rect.hpp" +#include "manager/service_provider.hpp" +#include "ui/widget.hpp" + +#include + +namespace ui { + + struct IndeterminateSpinner final : public Widget { + private: + std::vector> m_segments; + + u32 m_tile_size; + shapes::UPoint m_start_offset; + + public: + OOPETRIS_GRAPHICS_EXPORTED explicit IndeterminateSpinner(const Layout& layout, bool is_top_level); + + OOPETRIS_GRAPHICS_EXPORTED void update() override; + + OOPETRIS_GRAPHICS_EXPORTED void render(const ServiceProvider& service_provider) const override; + + OOPETRIS_GRAPHICS_EXPORTED [[nodiscard]] EventHandleResult + handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; + + private: + [[nodiscard]] shapes::UPoint to_screen_coords(const grid::GridPoint& point, u32 tile_size) const; + }; + +} // namespace ui From 4d4d717653c1261c8d0544c129d369eee933167c Mon Sep 17 00:00:00 2001 From: Totto16 Date: Thu, 14 Nov 2024 01:04:11 +0100 Subject: [PATCH 34/51] fix: explicitly move all bool helper values, instead of copying unnecessary --- src/libs/core/helper/bool_wrapper.hpp | 13 +++++++++++++ src/scenes/recording_selector/recording_chooser.cpp | 2 +- src/scenes/settings_menu/color_setting_row.cpp | 2 +- src/ui/layouts/focus_layout.cpp | 4 ++-- src/ui/layouts/scroll_layout.cpp | 2 +- 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/libs/core/helper/bool_wrapper.hpp b/src/libs/core/helper/bool_wrapper.hpp index 32c41eb7e..5233e8159 100644 --- a/src/libs/core/helper/bool_wrapper.hpp +++ b/src/libs/core/helper/bool_wrapper.hpp @@ -20,6 +20,19 @@ namespace helper { BoolWrapper(bool value, const std::optional& additional) : m_value{ value }, m_additional{ additional } { } + BoolWrapper(const BoolWrapper& other) = delete; + + BoolWrapper& operator=(const BoolWrapper& other) = delete; + + BoolWrapper(BoolWrapper&& other) noexcept + : m_value{ other.m_value }, + m_additional{ std::move(other.m_additional) } { + other.m_value = false; + other.m_additional = std::nullopt; + } + + BoolWrapper& operator=(BoolWrapper&& other) noexcept = default; + const std::optional& get_additional() const { return m_additional; } diff --git a/src/scenes/recording_selector/recording_chooser.cpp b/src/scenes/recording_selector/recording_chooser.cpp index ea75bf441..2db9b8c70 100644 --- a/src/scenes/recording_selector/recording_chooser.cpp +++ b/src/scenes/recording_selector/recording_chooser.cpp @@ -101,7 +101,7 @@ ui::Widget::EventHandleResult custom_ui::RecordingFileChooser::handle_event( ) { //TODO(Totto): this double nested component can't correctly detect focus changes (since the checking for a focus change only occurs at one level deep) //TODO(Totto): allow horizontal RIGHT <-> LEFT focus change on horizontal focus_layouts - if (const auto handled = m_main_grid.handle_event(input_manager, event); handled) { + if (auto handled = m_main_grid.handle_event(input_manager, event); handled) { return handled; } diff --git a/src/scenes/settings_menu/color_setting_row.cpp b/src/scenes/settings_menu/color_setting_row.cpp index 082af61bc..2890e7b09 100644 --- a/src/scenes/settings_menu/color_setting_row.cpp +++ b/src/scenes/settings_menu/color_setting_row.cpp @@ -179,7 +179,7 @@ ui::Widget::EventHandleResult custom_ui::ColorSettingRow::handle_event( const std::shared_ptr& input_manager, const SDL_Event& event ) { - const auto result = m_main_layout.handle_event(input_manager, event); + auto result = m_main_layout.handle_event(input_manager, event); if (const auto additional = result.get_additional(); additional.has_value()) { if (std::get<0>(additional.value()) == ui::EventHandleType::RequestAction) { return { diff --git a/src/ui/layouts/focus_layout.cpp b/src/ui/layouts/focus_layout.cpp index 86eeed38b..91b9b5805 100644 --- a/src/ui/layouts/focus_layout.cpp +++ b/src/ui/layouts/focus_layout.cpp @@ -70,7 +70,7 @@ ui::Widget::EventHandleResult ui::FocusLayout::handle_focus_change_button_events const SDL_Event& event ) { - Widget::EventHandleResult handled = false; + ui::Widget::EventHandleResult handled = false; const auto navigation_action = input_manager->get_navigation_event(event); @@ -96,7 +96,7 @@ ui::Widget::EventHandleResult ui::FocusLayout::handle_focus_change_events( return false; } - Widget::EventHandleResult handled = false; + ui::Widget::EventHandleResult handled{ false }; if (m_focus_id.has_value()) { const auto& widget = m_widgets.at(focusable_index_by_id(m_focus_id.value())); diff --git a/src/ui/layouts/scroll_layout.cpp b/src/ui/layouts/scroll_layout.cpp index 8e46ce698..58ba6db78 100644 --- a/src/ui/layouts/scroll_layout.cpp +++ b/src/ui/layouts/scroll_layout.cpp @@ -240,7 +240,7 @@ ui::Widget::EventHandleResult ui::ScrollLayout::handle_focus_change_events( return false; } - Widget::EventHandleResult handled = false; + Widget::EventHandleResult handled{ false }; if (m_focus_id.has_value()) { From 5b1d7b954803684d794c88eb79b50dff97a6188a Mon Sep 17 00:00:00 2001 From: Totto16 Date: Thu, 14 Nov 2024 01:21:37 +0100 Subject: [PATCH 35/51] fix: use struct instead of std::tuple, to silence gcc uninitialized error it is also better, than using std::get<> everywhere --- src/scenes/online_lobby/online_lobby.cpp | 8 +++---- .../recording_selector/recording_selector.cpp | 4 ++-- .../settings_menu/color_setting_row.cpp | 6 +++--- src/scenes/settings_menu/settings_menu.cpp | 4 ++-- src/ui/components/color_picker.cpp | 2 +- src/ui/layouts/focus_layout.cpp | 21 ++++++++++++------- src/ui/widget.hpp | 7 ++++++- 7 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/scenes/online_lobby/online_lobby.cpp b/src/scenes/online_lobby/online_lobby.cpp index db33246a5..a5038b5f7 100644 --- a/src/scenes/online_lobby/online_lobby.cpp +++ b/src/scenes/online_lobby/online_lobby.cpp @@ -121,11 +121,9 @@ namespace scenes { if (const auto additional = event_result.get_additional(); additional.has_value()) { const auto value = additional.value(); - if (std::get<0>(value) == ui::EventHandleType::RequestAction) { + if (value.handle_type == ui::EventHandleType::RequestAction) { - - if (auto text_input = utils::is_child_class(std::get<1>(value)); - text_input.has_value()) { + if (auto text_input = utils::is_child_class(value.widget); text_input.has_value()) { spdlog::info("Pressed Enter on TextInput {}", text_input.value()->get_text()); if (text_input.value()->has_focus()) { @@ -139,7 +137,7 @@ namespace scenes { } throw helper::FatalError( - fmt::format("Unsupported Handle Type: {}", magic_enum::enum_name(std::get<0>(value))) + fmt::format("Unsupported Handle Type: {}", magic_enum::enum_name(value.handle_type)) ); } diff --git a/src/scenes/recording_selector/recording_selector.cpp b/src/scenes/recording_selector/recording_selector.cpp index 9e396864e..0dfb3f393 100644 --- a/src/scenes/recording_selector/recording_selector.cpp +++ b/src/scenes/recording_selector/recording_selector.cpp @@ -178,9 +178,9 @@ namespace scenes { if (const auto event_result = m_main_layout.handle_event(input_manager, event); event_result) { if (const auto additional = event_result.get_additional(); - additional.has_value() and std::get<0>(additional.value()) == ui::EventHandleType::RequestAction) { + additional.has_value() and additional.value().handle_type == ui::EventHandleType::RequestAction) { m_next_command = Command{ - Action{ .widget = std::get<1>(additional.value()), .data = std::get<2>(additional.value()) } + Action{ .widget = additional.value().widget, .data = additional.value().data } }; } diff --git a/src/scenes/settings_menu/color_setting_row.cpp b/src/scenes/settings_menu/color_setting_row.cpp index 2890e7b09..289ef1783 100644 --- a/src/scenes/settings_menu/color_setting_row.cpp +++ b/src/scenes/settings_menu/color_setting_row.cpp @@ -181,15 +181,15 @@ ui::Widget::EventHandleResult custom_ui::ColorSettingRow::handle_event( ) { auto result = m_main_layout.handle_event(input_manager, event); if (const auto additional = result.get_additional(); additional.has_value()) { - if (std::get<0>(additional.value()) == ui::EventHandleType::RequestAction) { + if (additional.value().handle_type == ui::EventHandleType::RequestAction) { return { result, - { ui::EventHandleType::RequestAction, this, nullptr } + { .handle_type = ui::EventHandleType::RequestAction, .widget = this, .data = nullptr } }; } throw helper::FatalError( - fmt::format("Unsupported Handle Type: {}", magic_enum::enum_name(std::get<0>(additional.value()))) + fmt::format("Unsupported Handle Type: {}", magic_enum::enum_name(additional.value().handle_type)) ); } diff --git a/src/scenes/settings_menu/settings_menu.cpp b/src/scenes/settings_menu/settings_menu.cpp index 40af64a2c..8c04bb55e 100644 --- a/src/scenes/settings_menu/settings_menu.cpp +++ b/src/scenes/settings_menu/settings_menu.cpp @@ -248,9 +248,9 @@ namespace scenes { bool SettingsMenu::handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) { if (const auto event_result = m_main_layout.handle_event(input_manager, event); event_result) { if (const auto additional = event_result.get_additional(); - additional.has_value() and std::get<0>(additional.value()) == ui::EventHandleType::RequestAction) { + additional.has_value() and additional.value().handle_type == ui::EventHandleType::RequestAction) { m_next_command = Command{ - Action{ .widget = std::get<1>(additional.value()), .data = std::get<2>(additional.value()) } + Action{ .widget = additional.value().widget, .data = additional->data } }; } diff --git a/src/ui/components/color_picker.cpp b/src/ui/components/color_picker.cpp index b9bdaa7a0..5042be90f 100644 --- a/src/ui/components/color_picker.cpp +++ b/src/ui/components/color_picker.cpp @@ -487,7 +487,7 @@ ui::ColorPicker::handle_event(const std::shared_ptr& input_ if (handled) { if (const auto additional = handled.get_additional(); additional.has_value()) { - switch (std::get<0>(additional.value())) { + switch (additional.value().handle_type) { case ui::EventHandleType::RequestFocus: if (not m_color_text->has_focus()) { m_color_text->focus(); diff --git a/src/ui/layouts/focus_layout.cpp b/src/ui/layouts/focus_layout.cpp index 91b9b5805..9234bc37f 100644 --- a/src/ui/layouts/focus_layout.cpp +++ b/src/ui/layouts/focus_layout.cpp @@ -135,7 +135,7 @@ ui::FocusLayout::handle_event_result(const std::optional auto value = result.value(); - switch (std::get<0>(value)) { + switch (value.handle_type) { case ui::EventHandleType::RequestFocus: { const auto focusable = as_focusable(widget); if (not focusable.has_value()) { @@ -160,8 +160,9 @@ ui::FocusLayout::handle_event_result(const std::optional // if the layout itself has not focus, it needs focus itself too if (not has_focus()) { - return ui::Widget::InnerState{ ui::EventHandleType::RequestFocus, std::get<1>(value), - std::get<2>(value) }; + return ui::Widget::InnerState{ .handle_type = ui::EventHandleType::RequestFocus, + .widget = value.widget, + .data = value.data }; } @@ -192,14 +193,16 @@ ui::FocusLayout::handle_event_result(const std::optional const auto test_forward = try_set_next_focus(FocusChangeDirection::Forward); if (not test_forward) { if (m_options.wrap_around) { - return ui::Widget::InnerState{ ui::EventHandleType::RequestUnFocus, std::get<1>(value), - std::get<2>(value) }; + return ui::Widget::InnerState{ .handle_type = ui::EventHandleType::RequestUnFocus, + .widget = value.widget, + .data = value.data }; } const auto test_backwards = try_set_next_focus(FocusChangeDirection::Backward); if (not test_backwards) { - return ui::Widget::InnerState{ ui::EventHandleType::RequestUnFocus, std::get<1>(value), - std::get<2>(value) }; + return ui::Widget::InnerState{ .handle_type = ui::EventHandleType::RequestUnFocus, + .widget = value.widget, + .data = value.data }; } } @@ -207,7 +210,9 @@ ui::FocusLayout::handle_event_result(const std::optional } case ui::EventHandleType::RequestAction: { // just forward it - return ui::Widget::InnerState{ ui::EventHandleType::RequestAction, std::get<1>(value), std::get<2>(value) }; + return ui::Widget::InnerState{ .handle_type = ui::EventHandleType::RequestAction, + .widget = value.widget, + .data = value.data }; } default: UNREACHABLE(); diff --git a/src/ui/widget.hpp b/src/ui/widget.hpp index 67b395f58..2210adfca 100644 --- a/src/ui/widget.hpp +++ b/src/ui/widget.hpp @@ -23,7 +23,12 @@ namespace ui { bool m_top_level; public: - using InnerState = std::tuple; + struct InnerState { + ui::EventHandleType handle_type; + Widget* widget; + void* data; + }; + using EventHandleResult = helper::BoolWrapper; explicit Widget(const Layout& layout, WidgetType type, bool is_top_level) From a4d8854580605e4407e990cb6cf27a3a761eabbf Mon Sep 17 00:00:00 2001 From: Totto16 Date: Thu, 14 Nov 2024 01:57:53 +0100 Subject: [PATCH 36/51] fix: use windows ffmpeg fro mingw --- src/graphics/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/graphics/meson.build b/src/graphics/meson.build index c571daf31..40a0608fb 100644 --- a/src/graphics/meson.build +++ b/src/graphics/meson.build @@ -25,7 +25,7 @@ if replay_video_rendering_enabled ) else cpp = meson.get_compiler('cpp') - if host_machine.system() == 'darwin' or host_machine.system() == 'linux' or (cpp.get_id() == 'gcc' and host_machine.system() == 'windows') + if host_machine.system() == 'darwin' or host_machine.system() == 'linux' graphics_src_files += files( 'video_renderer_unix.cpp', ) From bff8e6119723a93e7b94772d10b54a312128fd85 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Thu, 14 Nov 2024 01:58:10 +0100 Subject: [PATCH 37/51] fix: don't use deprecated incldue header for stringstream --- src/graphics/video_renderer_windows.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/graphics/video_renderer_windows.cpp b/src/graphics/video_renderer_windows.cpp index 8315044f1..6f923d1fa 100644 --- a/src/graphics/video_renderer_windows.cpp +++ b/src/graphics/video_renderer_windows.cpp @@ -8,7 +8,7 @@ #include -#include +#include struct Decoder { HANDLE hProcess; From e4bd4d44894d143804f15f8307d7a601716148f1 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Thu, 14 Nov 2024 02:00:20 +0100 Subject: [PATCH 38/51] fix: windows: correctly initialize SECURITY_ATTRIBUTES struct --- src/graphics/video_renderer_windows.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/graphics/video_renderer_windows.cpp b/src/graphics/video_renderer_windows.cpp index 6f923d1fa..0e6f47895 100644 --- a/src/graphics/video_renderer_windows.cpp +++ b/src/graphics/video_renderer_windows.cpp @@ -33,9 +33,10 @@ std::optional VideoRendererBackend::setup(u32 fps, shapes::UPoint s HANDLE pipe_read; HANDLE pipe_write; - SECURITY_ATTRIBUTES saAttr = { 0 }; - saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); - saAttr.bInheritHandle = TRUE; + SECURITY_ATTRIBUTES saAttr = { .nLength = sizeof(SECURITY_ATTRIBUTES), + .lpSecurityDescriptor = nullptr, + .bInheritHandle = TRUE }; + if (!CreatePipe(&pipe_read, &pipe_write, &saAttr, 0)) { return fmt::format("FFMPEG: Could not create pipe. System Error Code: {}", GetLastError()); From 48317d6b52e432c837787a614af218fff719b628 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Thu, 14 Nov 2024 02:12:16 +0100 Subject: [PATCH 39/51] chore: update wrappers to latest main of each wrapper --- wrapper/c | 2 +- wrapper/haskell | 2 +- wrapper/javascript | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/wrapper/c b/wrapper/c index e23c63466..7e2e962ad 160000 --- a/wrapper/c +++ b/wrapper/c @@ -1 +1 @@ -Subproject commit e23c634661692ffb259e43c6a359e4c0d54bb247 +Subproject commit 7e2e962adf8109a772ba9498cd29772f63579e27 diff --git a/wrapper/haskell b/wrapper/haskell index ba08e7196..98a661152 160000 --- a/wrapper/haskell +++ b/wrapper/haskell @@ -1 +1 @@ -Subproject commit ba08e719698217c3bac2d8c04fd48ba7c0477576 +Subproject commit 98a66115282942f9465b94e80f21f13be7d8e8c0 diff --git a/wrapper/javascript b/wrapper/javascript index 01b3759cd..9708a7fd5 160000 --- a/wrapper/javascript +++ b/wrapper/javascript @@ -1 +1 @@ -Subproject commit 01b3759cdf54492164b08694e0701c63a8aa97dd +Subproject commit 9708a7fd5e42d966007ce2c6f09a821751f68d39 From 804d0c77744bdc70ec3e086de31bc9b9326af063 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Thu, 14 Nov 2024 02:51:53 +0100 Subject: [PATCH 40/51] ci: lint, don't include windows specific file --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a03cb1e39..2c669e97e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -52,7 +52,7 @@ jobs: tidy-checks: '' step-summary: true file-annotations: true - ignore: subprojects|build|android|assets|recordings|docs|toolchains|platforms|wrapper|src/libs/core/hash-library|tests|src/helper/web_utils.*|src/lobby/web_client.*|src/lobby/curl_client.* + ignore: subprojects|build|android|assets|recordings|docs|toolchains|platforms|wrapper|src/libs/core/hash-library|tests|src/graphics/video_renderer_windows.*|src/helper/web_utils.*|src/lobby/web_client.*|src/lobby/curl_client.* - name: Fail CI run if linter checks failed if: steps.linter.outputs.checks-failed != 0 From e5a93c7cbe78141c5a720533d4fe6f73ca7f8c70 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Thu, 14 Nov 2024 02:54:06 +0100 Subject: [PATCH 41/51] fix: fix clang-tidy errors --- src/graphics/video_renderer.hpp | 2 +- src/graphics/video_renderer_embedded.cpp | 45 +++++++++++-------- src/graphics/video_renderer_unix.cpp | 23 +++++----- src/graphics/video_renderer_windows.cpp | 4 +- .../recording_component.cpp | 12 ++--- .../settings_menu/color_setting_row.cpp | 4 +- src/ui/components/color_picker.cpp | 2 +- src/ui/components/textinput.cpp | 4 +- 8 files changed, 53 insertions(+), 43 deletions(-) diff --git a/src/graphics/video_renderer.hpp b/src/graphics/video_renderer.hpp index 4caa2a9ad..04bb36f6b 100644 --- a/src/graphics/video_renderer.hpp +++ b/src/graphics/video_renderer.hpp @@ -93,7 +93,7 @@ struct VideoRendererBackend { get_encoding_paramaters(u32 fps, shapes::UPoint size, const std::filesystem::path& destination_path); public: - OOPETRIS_GRAPHICS_EXPORTED explicit VideoRendererBackend(const std::filesystem::path& destination_path); + OOPETRIS_GRAPHICS_EXPORTED explicit VideoRendererBackend(std::filesystem::path destination_path); OOPETRIS_GRAPHICS_EXPORTED ~VideoRendererBackend(); diff --git a/src/graphics/video_renderer_embedded.cpp b/src/graphics/video_renderer_embedded.cpp index 315bd7c86..617363c29 100644 --- a/src/graphics/video_renderer_embedded.cpp +++ b/src/graphics/video_renderer_embedded.cpp @@ -49,34 +49,34 @@ struct Decoder { // general information and usage from: https://ffmpeg.org//doxygen/trunk/index.html // and https://github.com/FFmpeg/FFmpeg/blob/master/doc/examples/README -VideoRendererBackend::VideoRendererBackend(const std::filesystem::path& destination_path) - : m_destination_path{ destination_path }, +VideoRendererBackend::VideoRendererBackend(std::filesystem::path destination_path) + : m_destination_path{ std::move(destination_path) }, m_decoder{ nullptr } { } VideoRendererBackend::~VideoRendererBackend() = default; namespace { - constexpr const int READ_END = 0; - constexpr const int WRITE_END = 1; + constexpr const int read_end = 0; + constexpr const int write_end = 1; - constexpr const size_t BUF_LEN = 1024; + constexpr const size_t buf_len = 1024; std::string av_error_to_string(int errnum) { - auto* buf = new char[BUF_LEN]; - auto* buff_res = av_make_error_string(buf, BUF_LEN, errnum); + auto* buf = new char[buf_len]; //NOLINT(cppcoreguidelines-owning-memory) + auto* buff_res = av_make_error_string(buf, buf_len, errnum); if (buff_res == nullptr) { return "Unknown error"; } std::string result{ buff_res }; - delete[] buf; + delete[] buf; //NOLINT(cppcoreguidelines-owning-memory) return result; } - std::optional start_encoding( + std::optional start_encoding( //NOLINT(readability-function-cognitive-complexity) u32 fps, shapes::UPoint size, const std::filesystem::path& destination_path, @@ -150,7 +150,8 @@ namespace { ); } - AVStream* input_video_stream = input_format_ctx->streams[video_stream_index]; + AVStream* input_video_stream = + input_format_ctx->streams[video_stream_index]; //NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) AVCodecContext* input_codec_context = avcodec_alloc_context3(input_decoder); if (input_codec_context == nullptr) { @@ -203,7 +204,7 @@ namespace { return fmt::format("Could not alloc output file {}: {}", destination_path.string(), av_output_ret); } - std::string encoder_metadata_name = fmt::format( + const std::string encoder_metadata_name = fmt::format( "{} v{} ({}) {}", constants::program_name.string(), constants::version.string(), utils::git_commit(), LIBAVFORMAT_IDENT ); @@ -263,7 +264,7 @@ namespace { out_stream->time_base = output_codec_context->time_base; - std::string stream_encoder_metadata_name = fmt::format( + const std::string stream_encoder_metadata_name = fmt::format( "{} v{} ({}) {} {}", constants::program_name.string(), constants::version.string(), utils::git_commit(), LIBAVCODEC_IDENT, output_encoder->name ); @@ -354,7 +355,9 @@ namespace { auto read_frame_ret = av_read_frame(input_format_ctx, pkt); if (read_frame_ret == AVERROR_EOF) { break; - } else if (read_frame_ret < 0) { + } + + if (read_frame_ret < 0) { return fmt::format("Receiving a frame from the input failed: {}", av_error_to_string(read_frame_ret)); } @@ -376,7 +379,9 @@ namespace { read_ret = avcodec_receive_frame(input_codec_context, decode_frame); if (read_ret == AVERROR(EAGAIN) || read_ret == AVERROR_EOF) { break; - } else if (read_ret < 0) { + } + + if (read_ret < 0) { return fmt::format("Receiving a frame from the decoder failed: {}", av_error_to_string(read_ret)); } @@ -404,7 +409,9 @@ namespace { write_ret = avcodec_receive_packet(output_codec_context, pkt); if (write_ret == AVERROR(EAGAIN) || write_ret == AVERROR_EOF) { break; - } else if (write_ret < 0) { + } + + if (write_ret < 0) { return fmt::format( "Receiving a packet from the encoder failed: {}", av_error_to_string(write_ret) ); @@ -474,13 +481,13 @@ std::optional VideoRendererBackend::setup(u32 fps, shapes::UPoint s if (pipe(pipefd.data()) < 0) { return fmt::format("Could not create a pipe: {}", strerror(errno)); } - int close_fd = pipefd[READ_END]; - int input_fd = pipefd[WRITE_END]; - std::string input_url = fmt::format("pipe:{}", close_fd); + const int close_fd = pipefd[read_end]; + const int input_fd = pipefd[write_end]; + const std::string input_url = fmt::format("pipe:{}", close_fd); #endif std::future> encoding_thread = - std::async(std::launch::async, [close_fd, input_url, fps, size, this] -> std::optional { + std::async(std::launch::async, [close_fd, input_url, fps, size, this]() -> std::optional { utils::set_thread_name("ffmpeg encoder"); auto result = start_encoding(fps, size, this->m_destination_path, input_url, this->m_decoder); diff --git a/src/graphics/video_renderer_unix.cpp b/src/graphics/video_renderer_unix.cpp index 478e3beb6..c7fa0bb26 100644 --- a/src/graphics/video_renderer_unix.cpp +++ b/src/graphics/video_renderer_unix.cpp @@ -17,16 +17,16 @@ struct Decoder { }; // inspired by: https://github.com/tsoding/musializer/blob/762a729ff69ba1f984b0f2604e0eac08af46327c/src/ffmpeg_linux.c -VideoRendererBackend::VideoRendererBackend(const std::filesystem::path& destination_path) - : m_destination_path{ destination_path }, +VideoRendererBackend::VideoRendererBackend(std::filesystem::path destination_path) + : m_destination_path{ std::move(destination_path) }, m_decoder{ nullptr } { } VideoRendererBackend::~VideoRendererBackend() = default; namespace { - constexpr const int READ_END = 0; - constexpr const int WRITE_END = 1; + constexpr const int read_end = 0; + constexpr const int write_end = 1; } // namespace @@ -45,17 +45,17 @@ std::optional VideoRendererBackend::setup(u32 fps, shapes::UPoint s return fmt::format("FFMPEG: Could not create a pipe: {}", strerror(errno)); } - pid_t child = fork(); + const pid_t child = fork(); if (child < 0) { return fmt::format("FFMPEG: could not fork a child: {}", strerror(errno)); } if (child == 0) { - if (dup2(pipefd.at(READ_END), STDIN_FILENO) < 0) { + if (dup2(pipefd.at(read_end), STDIN_FILENO) < 0) { std::cerr << "FFMPEG CHILD: could not reopen read end of pipe as stdin: " << strerror(errno) << "\n"; std::exit(1); } - close(pipefd[WRITE_END]); + close(pipefd[write_end]); auto paramaters = VideoRendererBackend::get_encoding_paramaters(fps, size, m_destination_path); @@ -67,7 +67,7 @@ std::optional VideoRendererBackend::setup(u32 fps, shapes::UPoint s args.push_back(nullptr); //TODO(Totto): support audio, that loops the music as in the main game - int ret = + const int ret = execvp("ffmpeg", const_cast(args.data())); // NOLINT(cppcoreguidelines-pro-type-const-cast) if (ret < 0) { @@ -78,11 +78,11 @@ std::optional VideoRendererBackend::setup(u32 fps, shapes::UPoint s std::exit(1); } - if (close(pipefd[READ_END]) < 0) { + if (close(pipefd[read_end]) < 0) { spdlog::error("FFMPEG: could not close read end of the pipe on the parent's end: {}", strerror(errno)); } - m_decoder = std::make_unique(pipefd[WRITE_END], child); + m_decoder = std::make_unique(pipefd[write_end], child); return std::nullopt; } @@ -102,8 +102,9 @@ bool VideoRendererBackend::finish(bool cancel) { spdlog::warn("FFMPEG: could not close write end of the pipe on the parent's end: {}", strerror(errno)); } - if (cancel) + if (cancel) { kill(m_decoder->pid, SIGKILL); + } while (true) { int wstatus = 0; diff --git a/src/graphics/video_renderer_windows.cpp b/src/graphics/video_renderer_windows.cpp index 0e6f47895..9eb24d204 100644 --- a/src/graphics/video_renderer_windows.cpp +++ b/src/graphics/video_renderer_windows.cpp @@ -16,8 +16,8 @@ struct Decoder { }; // inspired by: https://github.com/tsoding/musializer/blob/762a729ff69ba1f984b0f2604e0eac08af46327c/src/ffmpeg_windows.c -VideoRendererBackend::VideoRendererBackend(const std::filesystem::path& destination_path) - : m_destination_path{ destination_path }, +VideoRendererBackend::VideoRendererBackend(std::filesystem::path destination_path) + : m_destination_path{ std::move(destination_path) }, m_decoder{ nullptr } { } VideoRendererBackend::~VideoRendererBackend() = default; diff --git a/src/scenes/recording_selector/recording_component.cpp b/src/scenes/recording_selector/recording_component.cpp index 1e450e1a4..34326068d 100644 --- a/src/scenes/recording_selector/recording_component.cpp +++ b/src/scenes/recording_selector/recording_component.cpp @@ -117,14 +117,14 @@ ui::Widget::EventHandleResult custom_ui::RecordingComponent::handle_event( if (m_current_focus_id == m_main_layout.focus_id()) { return { true, - { ui::EventHandleType::RequestAction, this, nullptr } + { .handle_type = ui::EventHandleType::RequestAction, .widget = this, .data = nullptr } }; } if (m_current_focus_id == render_button->focus_id()) { return { true, - { ui::EventHandleType::RequestAction, render_button, nullptr } + { .handle_type = ui::EventHandleType::RequestAction, .widget = render_button, .data = nullptr } }; } @@ -162,13 +162,13 @@ ui::Widget::EventHandleResult custom_ui::RecordingComponent::handle_event( if (not has_focus()) { return { true, - { ui::EventHandleType::RequestFocus, this, nullptr } + { .handle_type = ui::EventHandleType::RequestFocus, .widget = this, .data = nullptr } }; } return { true, - { ui::EventHandleType::RequestAction, render_button, this } + { .handle_type = ui::EventHandleType::RequestAction, .widget = render_button, .data = this } }; } @@ -179,7 +179,9 @@ ui::Widget::EventHandleResult custom_ui::RecordingComponent::handle_event( return { true, - { has_focus() ? ui::EventHandleType::RequestAction : ui::EventHandleType::RequestFocus, this, nullptr } + { .handle_type = has_focus() ? ui::EventHandleType::RequestAction : ui::EventHandleType::RequestFocus, + .widget = this, + .data = nullptr } }; } return true; diff --git a/src/scenes/settings_menu/color_setting_row.cpp b/src/scenes/settings_menu/color_setting_row.cpp index 289ef1783..1920133e2 100644 --- a/src/scenes/settings_menu/color_setting_row.cpp +++ b/src/scenes/settings_menu/color_setting_row.cpp @@ -65,7 +65,7 @@ ui::Widget::EventHandleResult detail::ColorSettingRectangle::handle_event( if (has_focus() and navigation_event == input::NavigationEvent::OK) { return { true, - { ui::EventHandleType::RequestAction, this, nullptr } + { .handle_type = ui::EventHandleType::RequestAction, .widget = this, .data = nullptr } }; } @@ -74,7 +74,7 @@ ui::Widget::EventHandleResult detail::ColorSettingRectangle::handle_event( if (hover_result.is(ui::ActionType::Clicked)) { return { true, - { ui::EventHandleType::RequestAction, this, nullptr } + { .handle_type = ui::EventHandleType::RequestAction, .widget = this, .data = nullptr } }; } return true; diff --git a/src/ui/components/color_picker.cpp b/src/ui/components/color_picker.cpp index 5042be90f..5e8550b2c 100644 --- a/src/ui/components/color_picker.cpp +++ b/src/ui/components/color_picker.cpp @@ -164,7 +164,7 @@ detail::ColorCanvas::handle_event(const std::shared_ptr& in SDL_CaptureMouse(SDL_TRUE); handled = { true, - { ui::EventHandleType::RequestFocus, this, nullptr } + { .handle_type = ui::EventHandleType::RequestFocus, .widget = this, .data = nullptr } }; } } else if (pointer_event == input::PointerEvent::PointerUp) { diff --git a/src/ui/components/textinput.cpp b/src/ui/components/textinput.cpp index 2a24961f6..7d0dc7992 100644 --- a/src/ui/components/textinput.cpp +++ b/src/ui/components/textinput.cpp @@ -115,7 +115,7 @@ ui::Widget::EventHandleResult ui::TextInput::handle_event( //NOLINT(readability- if (hover_result.is(ActionType::Clicked)) { return { true, - { EventHandleType::RequestFocus, this, nullptr } + { .handle_type = EventHandleType::RequestFocus, .widget = this, .data = nullptr } }; } @@ -129,7 +129,7 @@ ui::Widget::EventHandleResult ui::TextInput::handle_event( //NOLINT(readability- on_unfocus(); return { true, - { EventHandleType::RequestAction, this, nullptr } + { .handle_type = EventHandleType::RequestAction, .widget = this, .data = nullptr } }; } //TODO(Totto): in some cases this is caught before that, and never triggered From 79eba25c3a4888eb90b19a14ccd147719ac7b5c4 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Tue, 19 Nov 2024 21:12:13 +0100 Subject: [PATCH 42/51] feat: add web support for ffmpeg --- platforms/build-android.sh | 2 +- platforms/build-web.sh | 150 ++++++++++++++++++++++++++++++-- src/graphics/video_renderer.cpp | 13 +++ src/graphics/video_renderer.hpp | 7 ++ tools/dependencies/meson.build | 34 +++++--- 5 files changed, 189 insertions(+), 17 deletions(-) diff --git a/platforms/build-android.sh b/platforms/build-android.sh index deba6ade9..8c69d372e 100755 --- a/platforms/build-android.sh +++ b/platforms/build-android.sh @@ -353,7 +353,7 @@ for INDEX in "${ARCH_KEYS_INDEX[@]}"; do BUILD_DIR_FFMPEG="build-ffmpeg" - BUILD_FFMPEG_FILE="$SYS_ROOT/$BUILD_DIR_FFMPEG/build_succesfull.meta" + BUILD_FFMPEG_FILE="$SYS_ROOT/$BUILD_DIR_FFMPEG/build_successfull.meta" if [ "$COMPILE_TYPE" == "complete_rebuild" ] || ! [ -e "$BUILD_FFMPEG_FILE" ]; then diff --git a/platforms/build-web.sh b/platforms/build-web.sh index cd5f6905a..1685042d7 100755 --- a/platforms/build-web.sh +++ b/platforms/build-web.sh @@ -17,18 +17,18 @@ fi EMSCRIPTEN_UPSTREAM_ROOT="$EMSCRIPTEN_ROOT/upstream/emscripten" -EMSCRIPTEN_PACTH_FILE="$EMSCRIPTEN_UPSTREAM_ROOT/.patched_manually.meta" +EMSCRIPTEN_PATCH_FILE="$EMSCRIPTEN_UPSTREAM_ROOT/.patched_manually.meta" PATCH_DIR="platforms/emscripten" -if ! [ -e "$EMSCRIPTEN_PACTH_FILE" ]; then +if ! [ -e "$EMSCRIPTEN_PATCH_FILE" ]; then ##TODO: upstream those patches # see: https://github.com/emscripten-core/emscripten/pull/18379 # and: https://github.com/emscripten-core/emscripten/pull/22946 git apply --unsafe-paths -p1 --directory="$EMSCRIPTEN_UPSTREAM_ROOT" "$PATCH_DIR/sdl2_image_port.diff" - touch "$EMSCRIPTEN_PACTH_FILE" + touch "$EMSCRIPTEN_PATCH_FILE" fi # git apply path @@ -41,7 +41,7 @@ embuilder build sdl2-mt harfbuzz-mt freetype zlib sdl2_ttf mpg123 "sdl2_mixer-mp export EMSCRIPTEN_SYS_ROOT="$EMSCRIPTEN_UPSTREAM_ROOT/cache/sysroot" -export BUILD_DIR="build-web" +EMSCRIPTEN_SYS_LIB_DIR="$EMSCRIPTEN_SYS_ROOT/lib/wasm32-emscripten" export CC="emcc" export CXX="em++" @@ -50,12 +50,151 @@ export RANLIB="emranlib" export STRIP="emstrip" export NM="emnm" +EMSCRIPTEN_PORT_BUILD_DIR="$EMSCRIPTEN_UPSTREAM_ROOT/cache/ports" + +BUILD_DIR_FFMPEG="build-ffmpeg" + +BUILD_FFMPEG_FILE="$EMSCRIPTEN_PORT_BUILD_DIR/$BUILD_DIR_FFMPEG/build_successfull.meta" + +# build the ffmpeg dependencies +# taken from: https://dev.to/alfg/ffmpeg-webassembly-2cbl +# modifed to fit the style of this project + some manual modifications +if [ "$COMPILE_TYPE" == "complete_rebuild" ] || ! [ -e "$BUILD_FFMPEG_FILE" ]; then + + LAST_DIR="$PWD" + + cd "$EMSCRIPTEN_PORT_BUILD_DIR" + + mkdir -p "$BUILD_DIR_FFMPEG" + + cd "$BUILD_DIR_FFMPEG" + + LIBX264_DIR="x264-src" + + if ! [ -e "$LIBX264_DIR" ]; then + + LIBX264_DIR_VERSION="20191217-2245-stable" + + wget "https://download.videolan.org/pub/videolan/x264/snapshots/x264-snapshot-${LIBX264_DIR_VERSION}.tar.bz2" + + tar xvfj "x264-snapshot-${LIBX264_DIR_VERSION}.tar.bz2" + + mv "x264-snapshot-${LIBX264_DIR_VERSION}" "$LIBX264_DIR" + fi + + cd "$LIBX264_DIR" + + BUILD_LIBX264_FILE="build_successfull.meta" + + if ! [ -e "$BUILD_LIBX264_FILE" ]; then + + emconfigure ./configure \ + --enable-static \ + --disable-cli \ + --disable-asm \ + --extra-cflags="-sUSE_PTHREADS=1" \ + --host=i686-gnu \ + --sysroot="$EMSCRIPTEN_SYS_ROOT" \ + --prefix="$EMSCRIPTEN_SYS_ROOT" \ + --libdir="$EMSCRIPTEN_SYS_LIB_DIR" + + emmake make -j + + emmake make install + + touch "$BUILD_LIBX264_FILE" + + fi + + cd .. + + FFMPEG_CLONE_DIR="ffmpeg-src" + + GIT_FFMPEG_TAG="n7.1" + + if ! [ -e "$FFMPEG_CLONE_DIR" ]; then + + git clone https://github.com/FFmpeg/FFmpeg "$FFMPEG_CLONE_DIR" + + cd "$FFMPEG_CLONE_DIR" + + git checkout "$GIT_FFMPEG_TAG" + + else + cd "$FFMPEG_CLONE_DIR" + + git checkout "$GIT_FFMPEG_TAG" + + fi + + FFMPEG_COMMON_FLAGS="-pthread -sUSE_PTHREADS=1" + + FFMPEG_LINK_FLAGS="$COMMON_FLAGS -sWASM=1 -sALLOW_MEMORY_GROWTH=1 -sASSERTIONS=1 -sERROR_ON_UNDEFINED_SYMBOLS=1" + + ##TODO: add --disable-debug, in release mode + + # Configure and build FFmpeg with emscripten. + # Disable all programs and only enable features we will use. + # https://github.com/FFmpeg/FFmpeg/blob/master/configure + emconfigure ./configure \ + --disable-asm \ + --disable-x86asm \ + --disable-inline-asm \ + --disable-stripping \ + --target-os=none \ + --arch=x86_32 \ + --enable-cross-compile \ + --disable-doc \ + --disable-programs \ + --disable-sdl2 \ + --disable-all \ + --enable-avcodec \ + --enable-avformat \ + --enable-avfilter \ + --enable-avdevice \ + --enable-avutil \ + --enable-swresample \ + --enable-swscale \ + --enable-filters \ + --enable-protocol="pipe,tcp" \ + --enable-decoder=h264 \ + --enable-encoder="h264"\ + --enable-demuxer="mp4,raw_video" \ + --enable-muxer="mp4" \ + --enable-gpl \ + --enable-libx264 \ + --extra-cflags="$FFMPEG_COMMON_FLAGS" \ + --extra-cxxflags="$FFMPEG_COMMON_FLAGS" \ + --extra-ldflags="$FFMPEG_LINK_FLAGS" \ + --sysroot="$EMSCRIPTEN_SYS_ROOT" \ + --prefix="$EMSCRIPTEN_SYS_ROOT" \ + --libdir="$EMSCRIPTEN_SYS_LIB_DIR" \ + --nm="$NM" \ + --ar="$AR" \ + --cc="$CC" \ + --cxx="$CXX" \ + --objcc="$CC" \ + --dep-cc="$CC" + + emmake make -j + + emmake make install + + touch "$BUILD_FFMPEG_FILE" + + cd "$LAST_DIR" + +fi + +export BUILD_DIR="build-web" + export ARCH="wasm32" export CPU_ARCH="wasm32" export ENDIANESS="little" export ROMFS="platforms/romfs" +#TODO: differentiate between release and debug mode, disable -sASSERTIONS and other debbug utilities export PACKAGE_FLAGS="'--use-port=sdl2', '--use-port=harfbuzz', '--use-port=freetype', '--use-port=zlib', '--use-port=sdl2_ttf', '--use-port=mpg123', '--use-port=sdl2_mixer', '-sSDL2_MIXER_FORMATS=[\"mp3\"]','--use-port=libpng', '--use-port=sdl2_image','-sSDL2_IMAGE_FORMATS=[\"png\",\"svg\"]', '--use-port=icu'" export COMMON_FLAGS="'-fexceptions', '-pthread', '-sUSE_PTHREADS=1', '-sEXCEPTION_CATCHING_ALLOWED=[..]', $PACKAGE_FLAGS" @@ -154,7 +293,8 @@ if [ "$COMPILE_TYPE" == "complete_rebuild" ] || [ ! -e "$BUILD_DIR" ]; then --cross-file "$CROSS_FILE" \ "-Dbuildtype=$BUILDTYPE" \ -Ddefault_library=static \ - -Dtests=false + -Dtests=false \ + -Duse_embedded_ffmpeg=enabled fi diff --git a/src/graphics/video_renderer.cpp b/src/graphics/video_renderer.cpp index 87f507e9b..501f8b292 100644 --- a/src/graphics/video_renderer.cpp +++ b/src/graphics/video_renderer.cpp @@ -227,3 +227,16 @@ MusicManager& VideoRenderer::music_manager() { } #endif + + +#if defined(__EMSCRIPTEN__) + +[[nodiscard]] web::WebContext& VideoRenderer::web_context() { + return m_main_provider->web_context(); +} + +[[nodiscard]] const web::WebContext& VideoRenderer::web_context() const { + return m_main_provider->web_context(); +} + +#endif diff --git a/src/graphics/video_renderer.hpp b/src/graphics/video_renderer.hpp index 04bb36f6b..d3b2b09b1 100644 --- a/src/graphics/video_renderer.hpp +++ b/src/graphics/video_renderer.hpp @@ -75,6 +75,13 @@ struct VideoRenderer : ServiceProvider { [[nodiscard]] const std::optional& discord_instance() const override; +#endif + +#if defined(__EMSCRIPTEN__) + + [[nodiscard]] web::WebContext& web_context() override; + [[nodiscard]] const web::WebContext& web_context() const override; + #endif }; diff --git a/tools/dependencies/meson.build b/tools/dependencies/meson.build index 99fe34fef..7f9c09066 100644 --- a/tools/dependencies/meson.build +++ b/tools/dependencies/meson.build @@ -471,11 +471,11 @@ if build_application endif ffmpeg_dep_names = [ - 'libavutil', - 'libavcodec', - 'libavformat', - 'libavfilter', - 'libswscale', + 'avutil', + 'avcodec', + 'avformat', + 'avfilter', + 'swscale', ] ffmpeg_deps = [] found_all_ffmpeg_deps = true @@ -515,12 +515,12 @@ if build_application endif ffmpeg_dep_names = [ - 'libavutil', - 'libavcodec', - 'libavformat', - 'libavfilter', - 'libswscale', - 'libswresample', + 'avutil', + 'avcodec', + 'avformat', + 'avfilter', + 'swscale', + 'swresample', ] if host_machine.system() == 'android' @@ -533,6 +533,18 @@ if build_application ] elif host_machine.system() == '3ds' ffmpeg_can_be_supported = false + elif host_machine.system() == 'emscripten' + ffmpeg_can_be_supported = true + + ffmpeg_dep_names += [ + 'x264', + ] + + foreach ffmpeg_dep_name : ffmpeg_dep_names + ffmpeg_dep = cpp.find_library(ffmpeg_dep_name, required: use_embedded_ffmpeg_option) + meson.override_dependency(ffmpeg_dep_name, ffmpeg_dep) + endforeach + else error( From b23934b054218639b333c210712dd5a054682e2b Mon Sep 17 00:00:00 2001 From: Totto16 Date: Tue, 19 Nov 2024 22:24:36 +0100 Subject: [PATCH 43/51] fix: enabled correct encoders etc. for web ffmpeg usage also correctly install ffmpeg libraries pkgconfig files, so that no mapping via cpp.find_library() is needed --- platforms/build-web.sh | 13 ++++++++----- tools/dependencies/meson.build | 19 ++++++------------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/platforms/build-web.sh b/platforms/build-web.sh index 1685042d7..d51ed392a 100755 --- a/platforms/build-web.sh +++ b/platforms/build-web.sh @@ -42,6 +42,7 @@ embuilder build sdl2-mt harfbuzz-mt freetype zlib sdl2_ttf mpg123 "sdl2_mixer-mp export EMSCRIPTEN_SYS_ROOT="$EMSCRIPTEN_UPSTREAM_ROOT/cache/sysroot" EMSCRIPTEN_SYS_LIB_DIR="$EMSCRIPTEN_SYS_ROOT/lib/wasm32-emscripten" +EMSCRIPTEN_SYS_PKGCONFIG_DIR="$EMSCRIPTEN_SYS_ROOT/lib/pkgconfig" export CC="emcc" export CXX="em++" @@ -96,7 +97,8 @@ if [ "$COMPILE_TYPE" == "complete_rebuild" ] || ! [ -e "$BUILD_FFMPEG_FILE" ]; t --host=i686-gnu \ --sysroot="$EMSCRIPTEN_SYS_ROOT" \ --prefix="$EMSCRIPTEN_SYS_ROOT" \ - --libdir="$EMSCRIPTEN_SYS_LIB_DIR" + --libdir="$EMSCRIPTEN_SYS_LIB_DIR" \ + --pkgconfigdir="$EMSCRIPTEN_SYS_PKGCONFIG_DIR" emmake make -j @@ -156,10 +158,10 @@ if [ "$COMPILE_TYPE" == "complete_rebuild" ] || ! [ -e "$BUILD_FFMPEG_FILE" ]; t --enable-swresample \ --enable-swscale \ --enable-filters \ - --enable-protocol="pipe,tcp" \ - --enable-decoder=h264 \ - --enable-encoder="h264"\ - --enable-demuxer="mp4,raw_video" \ + --enable-protocol="file,pipe,tcp" \ + --enable-decoder="rawvideo" \ + --enable-encoder="libx264" \ + --enable-demuxer="rawvideo" \ --enable-muxer="mp4" \ --enable-gpl \ --enable-libx264 \ @@ -169,6 +171,7 @@ if [ "$COMPILE_TYPE" == "complete_rebuild" ] || ! [ -e "$BUILD_FFMPEG_FILE" ]; t --sysroot="$EMSCRIPTEN_SYS_ROOT" \ --prefix="$EMSCRIPTEN_SYS_ROOT" \ --libdir="$EMSCRIPTEN_SYS_LIB_DIR" \ + --pkgconfigdir="$EMSCRIPTEN_SYS_PKGCONFIG_DIR" \ --nm="$NM" \ --ar="$AR" \ --cc="$CC" \ diff --git a/tools/dependencies/meson.build b/tools/dependencies/meson.build index 7f9c09066..a8474f8f6 100644 --- a/tools/dependencies/meson.build +++ b/tools/dependencies/meson.build @@ -515,12 +515,12 @@ if build_application endif ffmpeg_dep_names = [ - 'avutil', - 'avcodec', - 'avformat', - 'avfilter', - 'swscale', - 'swresample', + 'libavutil', + 'libavcodec', + 'libavformat', + 'libavfilter', + 'libswscale', + 'libswresample', ] if host_machine.system() == 'android' @@ -535,16 +535,9 @@ if build_application ffmpeg_can_be_supported = false elif host_machine.system() == 'emscripten' ffmpeg_can_be_supported = true - ffmpeg_dep_names += [ 'x264', ] - - foreach ffmpeg_dep_name : ffmpeg_dep_names - ffmpeg_dep = cpp.find_library(ffmpeg_dep_name, required: use_embedded_ffmpeg_option) - meson.override_dependency(ffmpeg_dep_name, ffmpeg_dep) - endforeach - else error( From 41a121d3ee5b927c04d5d3941a904d13fb6831d5 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Tue, 19 Nov 2024 22:39:08 +0100 Subject: [PATCH 44/51] fix: web, use IDBFS to persist files correctly this makes recordings and similar things persistent across reloads --- platforms/build-web.sh | 6 ++++-- src/executables/game/main.cpp | 16 ++++++++++++++-- src/helper/graphic_utils.cpp | 2 +- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/platforms/build-web.sh b/platforms/build-web.sh index d51ed392a..1ebdc3ccc 100755 --- a/platforms/build-web.sh +++ b/platforms/build-web.sh @@ -36,6 +36,8 @@ fi # shellcheck disable=SC1091 EMSDK_QUIET=1 source "$EMSCRIPTEN_ROOT/emsdk_env.sh" >/dev/null +PTHREAD_POOL_SIZE="8" + ## build theneeded dependencies embuilder build sdl2-mt harfbuzz-mt freetype zlib sdl2_ttf mpg123 "sdl2_mixer-mp3-mt" libpng-mt "sdl2_image:formats=png,svg:mt=1" icu-mt @@ -131,7 +133,7 @@ if [ "$COMPILE_TYPE" == "complete_rebuild" ] || ! [ -e "$BUILD_FFMPEG_FILE" ]; t FFMPEG_COMMON_FLAGS="-pthread -sUSE_PTHREADS=1" - FFMPEG_LINK_FLAGS="$COMMON_FLAGS -sWASM=1 -sALLOW_MEMORY_GROWTH=1 -sASSERTIONS=1 -sERROR_ON_UNDEFINED_SYMBOLS=1" + FFMPEG_LINK_FLAGS="$COMMON_FLAGS -sWASM=1 -sALLOW_MEMORY_GROWTH=1 -sASSERTIONS=1 -sERROR_ON_UNDEFINED_SYMBOLS=1 -sPTHREAD_POOL_SIZE=$PTHREAD_POOL_SIZE" ##TODO: add --disable-debug, in release mode @@ -203,7 +205,7 @@ export PACKAGE_FLAGS="'--use-port=sdl2', '--use-port=harfbuzz', '--use-port=free export COMMON_FLAGS="'-fexceptions', '-pthread', '-sUSE_PTHREADS=1', '-sEXCEPTION_CATCHING_ALLOWED=[..]', $PACKAGE_FLAGS" # TODO see if ALLOW_MEMORY_GROWTH is needed, but if we load ttf's and music it likely is and we don't have to debug OOm crashes, that aren't handled by some third party library, which is painful -export LINK_FLAGS="$COMMON_FLAGS, '-sEXPORT_ALL=1', '-sUSE_WEBGPU=1', '-sWASM=1', '-sALLOW_MEMORY_GROWTH=1', '-sASSERTIONS=1','-sERROR_ON_UNDEFINED_SYMBOLS=1', '-sFETCH=1', '-sEXIT_RUNTIME=1'" +export LINK_FLAGS="$COMMON_FLAGS, '-sEXPORT_ALL=1', '-sUSE_WEBGPU=1', '-sWASM=1', '-sALLOW_MEMORY_GROWTH=1', '-sASSERTIONS=1','-sERROR_ON_UNDEFINED_SYMBOLS=1', '-sFETCH=1', '-sEXIT_RUNTIME=1', '-sPTHREAD_POOL_SIZE=$PTHREAD_POOL_SIZE','-lidbfs.js'" export COMPILE_FLAGS="$COMMON_FLAGS ,'-DAUDIO_PREFER_MP3'" export CROSS_FILE="./platforms/crossbuild-web.ini" diff --git a/src/executables/game/main.cpp b/src/executables/game/main.cpp index 4800a171c..c67146892 100644 --- a/src/executables/game/main.cpp +++ b/src/executables/game/main.cpp @@ -48,7 +48,19 @@ namespace { #endif -#if !(defined(__EMSCRIPTEN__)) +#if defined(__EMSCRIPTEN__) + + // See: https://emscripten.org/docs/api_reference/Filesystem-API.html#filesystem-api-idbfs + EM_ASM(FS.mkdir('/persistent'); FS.mount(IDBFS, { autoPersist: true }, '/persistent'); FS.syncfs( + true, + function(err) { + if (err) { + console.error(err); + } + } + );); + +#endif const auto logs_path = utils::get_root_folder() / "logs"; @@ -64,7 +76,7 @@ namespace { fmt::format("{}/oopetris.log", logs_path.string()), 1024 * 1024 * 10, 5, true )); } -#endif + auto combined_logger = std::make_shared("combined_logger", begin(sinks), end(sinks)); spdlog::set_default_logger(combined_logger); diff --git a/src/helper/graphic_utils.cpp b/src/helper/graphic_utils.cpp index e78bf9dc5..5bb47e4bd 100644 --- a/src/helper/graphic_utils.cpp +++ b/src/helper/graphic_utils.cpp @@ -48,7 +48,7 @@ std::vector utils::supported_features() { } return std::filesystem::path{ std::string{ pref_path } }; #elif defined(__EMSCRIPTEN__) - return std::filesystem::path{ "/" }; + return std::filesystem::path{ "/persistent/" }; #elif defined(__CONSOLE__) // this is in the sdcard of the switch / 3ds , since internal storage is read-only for applications! return std::filesystem::path{ "." }; From 1a1eeee607bed66329595632c55e3a23fb0ca294 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Tue, 19 Nov 2024 22:40:04 +0100 Subject: [PATCH 45/51] fix: use correct ffmpeg libraries, as the pkgconfig names start with lib --- tools/dependencies/meson.build | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/dependencies/meson.build b/tools/dependencies/meson.build index a8474f8f6..4cdb811f1 100644 --- a/tools/dependencies/meson.build +++ b/tools/dependencies/meson.build @@ -471,11 +471,11 @@ if build_application endif ffmpeg_dep_names = [ - 'avutil', - 'avcodec', - 'avformat', - 'avfilter', - 'swscale', + 'libavutil', + 'libavcodec', + 'libavformat', + 'libavfilter', + 'libswscale', ] ffmpeg_deps = [] found_all_ffmpeg_deps = true From f529ff679093a4f4e23adf48e7b98b953c8ecf50 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Tue, 19 Nov 2024 23:25:00 +0100 Subject: [PATCH 46/51] fix: correctly get the locally built x264 dependency --- platforms/build-web.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/platforms/build-web.sh b/platforms/build-web.sh index 1ebdc3ccc..08302ed9f 100755 --- a/platforms/build-web.sh +++ b/platforms/build-web.sh @@ -46,6 +46,8 @@ export EMSCRIPTEN_SYS_ROOT="$EMSCRIPTEN_UPSTREAM_ROOT/cache/sysroot" EMSCRIPTEN_SYS_LIB_DIR="$EMSCRIPTEN_SYS_ROOT/lib/wasm32-emscripten" EMSCRIPTEN_SYS_PKGCONFIG_DIR="$EMSCRIPTEN_SYS_ROOT/lib/pkgconfig" +export PKG_CONFIG_PATH="$EMSCRIPTEN_SYS_PKGCONFIG_DIR:$EMSCRIPTEN_SYS_LIB_DIR/pkgconfig" + export CC="emcc" export CXX="em++" export AR="emar" @@ -99,8 +101,7 @@ if [ "$COMPILE_TYPE" == "complete_rebuild" ] || ! [ -e "$BUILD_FFMPEG_FILE" ]; t --host=i686-gnu \ --sysroot="$EMSCRIPTEN_SYS_ROOT" \ --prefix="$EMSCRIPTEN_SYS_ROOT" \ - --libdir="$EMSCRIPTEN_SYS_LIB_DIR" \ - --pkgconfigdir="$EMSCRIPTEN_SYS_PKGCONFIG_DIR" + --libdir="$EMSCRIPTEN_SYS_LIB_DIR" emmake make -j From 66f4f9ae9b53604f6aad5d83f97d3ce246bb0506 Mon Sep 17 00:00:00 2001 From: Totto16 Date: Wed, 20 Nov 2024 17:40:26 +0100 Subject: [PATCH 47/51] fix: fix ffmpeg web build --- platforms/build-web.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/platforms/build-web.sh b/platforms/build-web.sh index 08302ed9f..729007d4e 100755 --- a/platforms/build-web.sh +++ b/platforms/build-web.sh @@ -46,7 +46,7 @@ export EMSCRIPTEN_SYS_ROOT="$EMSCRIPTEN_UPSTREAM_ROOT/cache/sysroot" EMSCRIPTEN_SYS_LIB_DIR="$EMSCRIPTEN_SYS_ROOT/lib/wasm32-emscripten" EMSCRIPTEN_SYS_PKGCONFIG_DIR="$EMSCRIPTEN_SYS_ROOT/lib/pkgconfig" -export PKG_CONFIG_PATH="$EMSCRIPTEN_SYS_PKGCONFIG_DIR:$EMSCRIPTEN_SYS_LIB_DIR/pkgconfig" +export PKG_CONFIG_PATH="$EMSCRIPTEN_SYS_PKGCONFIG_DIR" export CC="emcc" export CXX="em++" @@ -101,12 +101,16 @@ if [ "$COMPILE_TYPE" == "complete_rebuild" ] || ! [ -e "$BUILD_FFMPEG_FILE" ]; t --host=i686-gnu \ --sysroot="$EMSCRIPTEN_SYS_ROOT" \ --prefix="$EMSCRIPTEN_SYS_ROOT" \ - --libdir="$EMSCRIPTEN_SYS_LIB_DIR" + --libdir="$EMSCRIPTEN_SYS_LIB_DIR" emmake make -j emmake make install + # move pkgconfig file into correct folder + + find "$EMSCRIPTEN_SYS_LIB_DIR/pkgconfig/" -name "*.pc" -exec mv {} "$EMSCRIPTEN_SYS_PKGCONFIG_DIR/" \; + touch "$BUILD_LIBX264_FILE" fi From 1b1440b79b157bde5451ac9f6deeb065155587da Mon Sep 17 00:00:00 2001 From: Totto16 Date: Wed, 20 Nov 2024 18:43:07 +0100 Subject: [PATCH 48/51] fix: fix 3ds build --- platforms/build-3ds.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/platforms/build-3ds.sh b/platforms/build-3ds.sh index e65641b89..d1b714055 100755 --- a/platforms/build-3ds.sh +++ b/platforms/build-3ds.sh @@ -260,8 +260,7 @@ if [ "$COMPILE_TYPE" == "complete_rebuild" ] || [ ! -e "$BUILD_DIR" ]; then -Dcurl:unittests=disabled \ -Dcurl:bearer-auth=enabled \ -Dcurl:brotli=enabled \ - -Dcurl:libz=enabled \ - -Duse_embedded_ffmpeg=disabled + -Dcurl:libz=enabled fi From 151ba5fa215529e1b2501fcd1b281d77eeb6115f Mon Sep 17 00:00:00 2001 From: Totto16 Date: Tue, 3 Dec 2024 00:03:29 +0100 Subject: [PATCH 49/51] fix: ffmpeg android build: - fix asm issue + add libx264 --- platforms/build-android.sh | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/platforms/build-android.sh b/platforms/build-android.sh index 8c69d372e..920461f99 100755 --- a/platforms/build-android.sh +++ b/platforms/build-android.sh @@ -147,6 +147,16 @@ for INDEX in "${ARCH_KEYS_INDEX[@]}"; do find "$HOST_ROOT/sysroot/usr/lib/$ARM_NAME_TRIPLE/$SDK_VERSION/" -maxdepth 1 -name "*.o" -exec ln -s "{}" "${SYS_ROOT:?}/usr/lib/" \; + # TODO: remove this temporary fix: + # see: https://github.com/android/ndk/issues/2107 + if [ "$ARCH_VERSION" = "armv7a" ]; then + sed -i -e 's/asm(/__asm__(/g' "$HOST_ROOT/sysroot/usr/include/arm-linux-androideabi/asm/swab.h" + elif [ "$ARCH_VERSION" = "i686" ]; then + sed -i -e 's/asm(/__asm__(/g' "$HOST_ROOT/sysroot/usr/include/i686-linux-android/asm/swab.h" + elif [ "$ARCH_VERSION" = "x86_64" ]; then + sed -i -e 's/asm(/__asm__(/g' "$HOST_ROOT/sysroot/usr/include/x86_64-linux-android/asm/swab.h" + fi + cd "$LAST_DIR" fi @@ -271,7 +281,6 @@ for INDEX in "${ARCH_KEYS_INDEX[@]}"; do -DBUILD_SHARED_LIBS=OFF \ -DINSTALL_PKGCONFIG_MODULES=ON - cmake --build . cmake --install . @@ -375,7 +384,7 @@ for INDEX in "${ARCH_KEYS_INDEX[@]}"; do fi - ./ffmpeg-android-maker.sh "--target-abis=$ARCH" "--android-api-level=$SDK_VERSION" + ./ffmpeg-android-maker.sh "--target-abis=$ARCH" "--android-api-level=$SDK_VERSION" --enable-libx264 FFMPEG_MAKER_OUTPUT_DIR="output" From 64b363e3eb89d74a2e0134dbda3010f456bff23e Mon Sep 17 00:00:00 2001 From: Totto16 Date: Tue, 3 Dec 2024 00:36:45 +0100 Subject: [PATCH 50/51] fix: fix web build after an upstream patch the local patch files needed to be adjusted --- wrapper/c | 2 +- wrapper/haskell | 2 +- wrapper/javascript | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/wrapper/c b/wrapper/c index 7e2e962ad..d710881b8 160000 --- a/wrapper/c +++ b/wrapper/c @@ -1 +1 @@ -Subproject commit 7e2e962adf8109a772ba9498cd29772f63579e27 +Subproject commit d710881b8845f5d1f98841f6544b8505da303b39 diff --git a/wrapper/haskell b/wrapper/haskell index 98a661152..ba08e7196 160000 --- a/wrapper/haskell +++ b/wrapper/haskell @@ -1 +1 @@ -Subproject commit 98a66115282942f9465b94e80f21f13be7d8e8c0 +Subproject commit ba08e719698217c3bac2d8c04fd48ba7c0477576 diff --git a/wrapper/javascript b/wrapper/javascript index 9708a7fd5..01b3759cd 160000 --- a/wrapper/javascript +++ b/wrapper/javascript @@ -1 +1 @@ -Subproject commit 9708a7fd5e42d966007ce2c6f09a821751f68d39 +Subproject commit 01b3759cdf54492164b08694e0701c63a8aa97dd From 6990489763c89b92b9e0fac6c70ead053cf8589f Mon Sep 17 00:00:00 2001 From: Totto16 Date: Tue, 3 Dec 2024 00:38:49 +0100 Subject: [PATCH 51/51] ci: fix android build for x86_64, install nasm, which is needed for the ffmpeg build --- .github/workflows/android.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 04f024a2b..56da2d267 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -30,10 +30,10 @@ jobs: run: | pip install meson --break-system-packages - - name: Setup ninja + - name: Setup dependencies run: | sudo apt-get update - sudo apt-get install ninja-build jq -y --no-install-recommends + sudo apt-get install ninja-build jq nasm -y --no-install-recommends - name: Setup JDK uses: actions/setup-java@v4