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 5eb09c8f..bdf333a5 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 13dd0d96..16ca3eaa 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 ba8c87a3..0f94c723 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 9f82116b..5353807e 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 c44ee8bd..d0e05fd9 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 0535edb8..a9762f56 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 098f6680..6805e20a 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 00000000..c90fdb26 --- /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 00000000..e6f03ae4 --- /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 00000000..cae919eb --- /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 385d8247..00e39ae8 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 bd3ad3c5..404ba549 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 0f94c723..bf7fbe86 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 5353807e..fea5ee94 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 c452edcd..589eaabf 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 aa87dd5e..251542fc 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 e0c426f1..b86f5e72 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 c90fdb26..622f519a 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 cae919eb..623aa434 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 00e39ae8..ab88da65 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 00000000..622b81bf --- /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 00000000..a2159865 --- /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 9b5f0f6c..32290bea 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 622f519a..22b9fcef 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 75f0a2df..37239d4e 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 22b9fcef..6c6dcb69 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 623aa434..60d527a2 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 00000000..02aff2ae --- /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 6c6dcb69..15472dc2 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 e6f03ae4..678c86e0 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 60d527a2..7ac6c463 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 02aff2ae..a3d77047 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 622b81bf..0402cfb8 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 a2159865..77b28a77 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 a3d77047..566ec4ae 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 15472dc2..1d6020ae 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 678c86e0..95491df3 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 7ac6c463..f234d3c5 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 9902188b..2b4d536c 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 6805e20a..c5c51656 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 00000000..3c1a0fbf --- /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 2bafe4c3..cc1a98d2 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 ba0ffbf5..c8afd63b 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 95491df3..ef122254 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 3c1a0fbf..610db68b 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 f234d3c5..478e3beb 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 566ec4ae..20ba02df 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 1560c186..16b684d0 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 c8afd63b..75dbc503 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 1d6020ae..3c9041a4 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 ef122254..4caa2a9a 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 610db68b..cb7a6f2f 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 00000000..86e875c9 --- /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 ca607430..f2fd9bd3 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 75dbc503..eb4f1c8f 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 605b6a5e..a7b957f2 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 cc1a98d2..3d7db0cd 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 0c525a98..a27b9adb 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 6a5e32c6..b28ee212 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 ab88da65..8d3fae60 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 5841a542..82b583a6 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 40ab78bf..41b0b277 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 da37ce6e..199fbc16 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 b7654f01..115ef5b0 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 12019903..f31c940d 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 a2289f5c..065ae126 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 72dcc7aa..e26092dd 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 8529dd94..70f43595 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 a5e6e20d..6b685cdc 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 00000000..cc6998d8 --- /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 02a6f7f3..fc6316a3 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 bb3e50fc..862f79b3 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 251542fc..35bbbc04 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 3c896191..69ac80b7 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 cb7a6f2f..910ecbc5 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 eb4f1c8f..18a7e906 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 a7b957f2..e41e233f 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 910ecbc5..59cc1e11 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 a8adbea8..04f024a2 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 59cc1e11..20517608 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 3d7db0cd..7fd1e32d 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 20517608..4998862a 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 16b684d0..a59c1945 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 18a7e906..0ca57c1c 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 86754b76..f58c146c 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 a59c1945..deba6ade 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 0ca57c1c..c3c08a2f 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 d1b71405..e65641b8 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 55810e6a..4196ac27 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 3c9041a4..87f507e9 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 4998862a..a10a33c8 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 7fd1e32d..e78bf9dc 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 c5c51656..c571daf3 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 20ba02df..8315044f 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 c3c08a2f..99fe34fe 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 ad4814af..a03cb1e3 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 a10a33c8..315bd7c8 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 d0e05fd9..02104cc9 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 d1365214..e30c4908 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 447f61d1..a44dc69c 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 9fce86f4..514a297f 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 072f3340..db33246a 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 4ea7d2b0..ea75bf44 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 bf7fbe86..1e450e1a 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 5e812c93..60277815 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 fea5ee94..9e396864 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 f7ce4648..968c62e7 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 cfe8d981..082af61b 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 b5f1c7c1..40af64a2 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 831dbe34..5dd1cdfd 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 54934264..6b15c9bf 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 e89ae33f..81416333 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 7d52d150..b9bdaa7a 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 d6f44e15..e861f13e 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 622318de..00271a4d 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 0dff0ffd..c8cf1a7a 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 2223980b..2a24961f 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 35bbbc04..86eeed38 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 28684771..67b395f5 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 47b6157c..b570d099 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 02dd81cd..2c2e2859 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 00000000..02dd81cd --- /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 bdc68421..e6517163 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 00000000..5847873c --- /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 00000000..11e71efc --- /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 32c41eb7..5233e815 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 ea75bf44..2db9b8c7 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 082af61b..2890e7b0 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 86eeed38..91b9b580 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 8e46ce69..58ba6db7 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 db33246a..a5038b5f 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 9e396864..0dfb3f39 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 2890e7b0..289ef178 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 40af64a2..8c04bb55 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 b9bdaa7a..5042be90 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 91b9b580..9234bc37 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 67b395f5..2210adfc 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 c571daf3..40a0608f 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 8315044f..6f923d1f 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 6f923d1f..0e6f4789 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 e23c6346..7e2e962a 160000 --- a/wrapper/c +++ b/wrapper/c @@ -1 +1 @@ -Subproject commit e23c634661692ffb259e43c6a359e4c0d54bb247 +Subproject commit 7e2e962adf8109a772ba9498cd29772f63579e27 diff --git a/wrapper/haskell b/wrapper/haskell index ba08e719..98a66115 160000 --- a/wrapper/haskell +++ b/wrapper/haskell @@ -1 +1 @@ -Subproject commit ba08e719698217c3bac2d8c04fd48ba7c0477576 +Subproject commit 98a66115282942f9465b94e80f21f13be7d8e8c0 diff --git a/wrapper/javascript b/wrapper/javascript index 01b3759c..9708a7fd 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 a03cb1e3..2c669e97 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 4caa2a9a..04bb36f6 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 315bd7c8..617363c2 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 478e3beb..c7fa0bb2 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 0e6f4789..9eb24d20 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 1e450e1a..34326068 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 289ef178..1920133e 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 5042be90..5e8550b2 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 2a24961f..7d0dc799 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 deba6ade..8c69d372 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 cd5f6905..1685042d 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 87f507e9..501f8b29 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 04bb36f6..d3b2b09b 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 99fe34fe..7f9c0906 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 1685042d..d51ed392 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 7f9c0906..a8474f8f 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 d51ed392..1ebdc3cc 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 4800a171..c6714689 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 e78bf9dc..5bb47e4b 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 a8474f8f..4cdb811f 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 1ebdc3cc..08302ed9 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 08302ed9..729007d4 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 e65641b8..d1b71405 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 8c69d372..920461f9 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 7e2e962a..d710881b 160000 --- a/wrapper/c +++ b/wrapper/c @@ -1 +1 @@ -Subproject commit 7e2e962adf8109a772ba9498cd29772f63579e27 +Subproject commit d710881b8845f5d1f98841f6544b8505da303b39 diff --git a/wrapper/haskell b/wrapper/haskell index 98a66115..ba08e719 160000 --- a/wrapper/haskell +++ b/wrapper/haskell @@ -1 +1 @@ -Subproject commit 98a66115282942f9465b94e80f21f13be7d8e8c0 +Subproject commit ba08e719698217c3bac2d8c04fd48ba7c0477576 diff --git a/wrapper/javascript b/wrapper/javascript index 9708a7fd..01b3759c 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 04f024a2..56da2d26 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