Skip to content

Commit 799c909

Browse files
committed
feat: add render video functionality, WIP
only available under linux with many hacks atm, so still heaviliy in WIP
1 parent 2ab83e7 commit 799c909

File tree

12 files changed

+524
-7
lines changed

12 files changed

+524
-7
lines changed

src/game/game.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,20 @@ Game::Game(
1212
u32 simulation_frequency,
1313
const ui::Layout& layout,
1414
bool is_top_level
15+
)
16+
: Game{ service_provider, input, starting_parameters, std::make_shared<LocalClock>(simulation_frequency),
17+
layout, is_top_level } { }
18+
19+
Game::Game(
20+
ServiceProvider* const service_provider,
21+
const std::shared_ptr<input::GameInput>& input,
22+
const tetrion::StartingParameters& starting_parameters,
23+
const std::shared_ptr<ClockSource>& clock_source,
24+
const ui::Layout& layout,
25+
bool is_top_level
1526
)
1627
: ui::Widget{ layout, ui::WidgetType::Component, is_top_level },
17-
m_clock_source{ std::make_unique<LocalClock>(simulation_frequency) },
28+
m_clock_source{ clock_source },
1829
m_input{ input } {
1930

2031

src/game/game.hpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ struct Game : public ui::Widget {
1212
private:
1313
using TetrionHeaders = std::vector<recorder::TetrionHeader>;
1414

15-
std::unique_ptr<ClockSource> m_clock_source;
15+
std::shared_ptr<ClockSource> m_clock_source;
1616
SimulationStep m_simulation_step_index{ 0 };
1717
std::unique_ptr<Tetrion> m_tetrion;
1818
std::shared_ptr<input::GameInput> m_input;
@@ -28,6 +28,15 @@ struct Game : public ui::Widget {
2828
bool is_top_level
2929
);
3030

31+
OOPETRIS_GRAPHICS_EXPORTED explicit Game(
32+
ServiceProvider* service_provider,
33+
const std::shared_ptr<input::GameInput>& input,
34+
const tetrion::StartingParameters& starting_parameters,
35+
const std::shared_ptr<ClockSource>& clock_source,
36+
const ui::Layout& layout,
37+
bool is_top_level
38+
);
39+
3140
OOPETRIS_GRAPHICS_EXPORTED void update() override;
3241

3342
OOPETRIS_GRAPHICS_EXPORTED void render(const ServiceProvider& service_provider) const override;

src/graphics/meson.build

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,26 @@ graphics_src_files += files(
88
'text.hpp',
99
'texture.cpp',
1010
'texture.hpp',
11+
'video_renderer.cpp',
12+
'video_renderer.hpp',
1113
'window.cpp',
1214
'window.hpp',
1315
)
16+
17+
18+
# TODO: make this optional
19+
if host_machine.system() == 'darwin'
20+
graphics_src_files += files(
21+
'video_renderer_mac.cpp',
22+
)
23+
elif host_machine.system() == 'linux'
24+
graphics_src_files += files(
25+
'video_renderer_linux.cpp',
26+
)
27+
elif host_machine.system() == 'windows'
28+
graphics_src_files += files(
29+
'video_renderer_windows.cpp',
30+
)
31+
else
32+
error('unsuported system for video rendering: ' + host_machine.system())
33+
endif

src/graphics/video_renderer.cpp

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
2+
3+
#include "video_renderer.hpp"
4+
5+
#include <fmt/format.h>
6+
7+
VideoRenderer::VideoRenderer(
8+
ServiceProvider* service_provider,
9+
const std::filesystem::path& recording_path,
10+
shapes::UPoint size
11+
)
12+
: m_main_provider{ service_provider },
13+
m_size{ size } {
14+
auto* surface = SDL_CreateRGBSurface(0, static_cast<int>(size.x), static_cast<int>(size.y), 32, 0, 0, 0, 0);
15+
16+
if (surface == nullptr) {
17+
throw std::runtime_error{ fmt::format("Failed creating a SDL RGB Surface: {}", SDL_GetError()) };
18+
}
19+
20+
m_surface.reset(surface);
21+
auto renderer = Renderer::get_software_renderer(m_surface);
22+
23+
m_renderer = std::make_unique<Renderer>(std::move(renderer));
24+
25+
initialize_games(recording_path);
26+
}
27+
28+
void VideoRenderer::initialize_games(const std::filesystem::path& recording_path) {
29+
30+
auto [parameters, information] = input::get_game_parameters_for_replay(this, recording_path);
31+
32+
auto layout = ui::FullScreenLayout{
33+
shapes::URect{ { 0, 0 }, m_size }
34+
};
35+
36+
std::vector<ui::Layout> layouts{};
37+
layouts.reserve(parameters.size());
38+
39+
if (parameters.empty()) {
40+
throw std::runtime_error("An empty recording file isn't supported");
41+
} else if (parameters.size() == 1) { // NOLINT(readability-else-after-return,llvm-else-after-return)
42+
layouts.push_back(ui::RelativeLayout{ layout, 0.02, 0.01, 0.96, 0.98 });
43+
} else if (parameters.size() == 2) {
44+
layouts.push_back(ui::RelativeLayout{ layout, 0.02, 0.01, 0.46, 0.98 });
45+
layouts.push_back(ui::RelativeLayout{ layout, 0.52, 0.01, 0.46, 0.98 });
46+
} else {
47+
48+
//TODO(Totto): support bigger layouts than just 2
49+
throw std::runtime_error("At the moment only replays from up to two players are supported");
50+
}
51+
52+
m_clock = std::make_shared<ManualClock>();
53+
54+
55+
for (decltype(parameters.size()) i = 0; i < parameters.size(); ++i) {
56+
auto [input, starting_parameters] = std::move(parameters.at(i));
57+
58+
m_games.emplace_back(
59+
std::make_unique<Game>(this, std::move(input), starting_parameters, m_clock, layouts.at(i), false)
60+
);
61+
}
62+
}
63+
64+
65+
VideoRenderer::~VideoRenderer() {
66+
if (m_surface) {
67+
SDL_FreeSurface(m_surface.get());
68+
}
69+
}
70+
71+
72+
std::optional<std::string> VideoRenderer::render(
73+
const std::string& destination_path,
74+
u32 fps,
75+
const std::function<void(double progress)>& progress_callback
76+
) {
77+
78+
auto backend = VideoRendererBackend{ destination_path };
79+
80+
if (auto result = backend.setup(fps, m_size); result.has_value()) {
81+
return fmt::format("No video renderer backend available: {}", result.value());
82+
}
83+
84+
auto all_games_finished = [this]() -> bool {
85+
for (const auto& game : m_games) {
86+
if (not game->is_game_finished()) {
87+
return false;
88+
}
89+
}
90+
91+
return true;
92+
};
93+
94+
//TODO: this is just a dummy thing atm, change that
95+
double progress = 0.0;
96+
97+
while (all_games_finished()) {
98+
progress_callback(progress);
99+
100+
for (const auto& game : m_games) {
101+
game->update();
102+
game->render(*this);
103+
}
104+
105+
backend.add_frame(m_surface.get());
106+
m_clock->increment_simulation_step_index();
107+
108+
progress += 0.1;
109+
110+
progress_callback(progress);
111+
}
112+
113+
backend.finish(false);
114+
return std::nullopt;
115+
}
116+
117+
118+
// implementation of ServiceProvider
119+
120+
[[nodiscard]] EventDispatcher& VideoRenderer::event_dispatcher() {
121+
return m_main_provider->event_dispatcher();
122+
}
123+
124+
[[nodiscard]] const EventDispatcher& VideoRenderer::event_dispatcher() const {
125+
return m_main_provider->event_dispatcher();
126+
}
127+
128+
FontManager& VideoRenderer::font_manager() {
129+
return m_main_provider->font_manager();
130+
}
131+
132+
[[nodiscard]] const FontManager& VideoRenderer::font_manager() const {
133+
return m_main_provider->font_manager();
134+
}
135+
136+
CommandLineArguments& VideoRenderer::command_line_arguments() {
137+
return m_main_provider->command_line_arguments();
138+
}
139+
140+
[[nodiscard]] const CommandLineArguments& VideoRenderer::command_line_arguments() const {
141+
return m_main_provider->command_line_arguments();
142+
}
143+
144+
SettingsManager& VideoRenderer::settings_manager() {
145+
return m_main_provider->settings_manager();
146+
}
147+
148+
[[nodiscard]] const SettingsManager& VideoRenderer::settings_manager() const {
149+
return m_main_provider->settings_manager();
150+
}
151+
152+
MusicManager& VideoRenderer::music_manager() {
153+
return m_main_provider->music_manager();
154+
}
155+
156+
[[nodiscard]] const MusicManager& VideoRenderer::music_manager() const {
157+
return m_main_provider->music_manager();
158+
}
159+
160+
[[nodiscard]] const Renderer& VideoRenderer::renderer() const {
161+
return *m_renderer;
162+
}
163+
164+
[[nodiscard]] const Window& VideoRenderer::window() const {
165+
return m_main_provider->window();
166+
}
167+
168+
[[nodiscard]] Window& VideoRenderer::window() {
169+
return m_main_provider->window();
170+
}
171+
172+
[[nodiscard]] input::InputManager& VideoRenderer::input_manager() {
173+
return m_main_provider->input_manager();
174+
}
175+
176+
[[nodiscard]] const input::InputManager& VideoRenderer::input_manager() const {
177+
return m_main_provider->input_manager();
178+
}
179+
180+
[[nodiscard]] const std::unique_ptr<lobby::API>& VideoRenderer::api() const {
181+
return m_main_provider->api();
182+
}
183+
184+
#if defined(_HAVE_DISCORD_SDK)
185+
186+
[[nodiscard]] std::optional<DiscordInstance>& VideoRenderer::discord_instance() {
187+
return m_main_provider->discord_instance();
188+
}
189+
190+
[[nodiscard]] const std::optional<DiscordInstance>& VideoRenderer::discord_instance() const {
191+
return m_main_provider->discord_instance();
192+
}
193+
194+
#endif

src/graphics/video_renderer.hpp

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
2+
3+
#pragma once
4+
5+
#include <SDL.h>
6+
#include <memory>
7+
8+
#include "game/game.hpp"
9+
#include "graphics/rect.hpp"
10+
#include "helper/windows.hpp"
11+
#include "manager/service_provider.hpp"
12+
#include "renderer.hpp"
13+
14+
struct VideoRenderer : ServiceProvider {
15+
private:
16+
std::unique_ptr<SDL_Surface> m_surface;
17+
std::unique_ptr<Renderer> m_renderer;
18+
std::vector<std::unique_ptr<Game>> m_games;
19+
ServiceProvider* m_main_provider;
20+
shapes::UPoint m_size;
21+
std::shared_ptr<ManualClock> m_clock;
22+
23+
void initialize_games(const std::filesystem::path& recording_path);
24+
25+
public:
26+
OOPETRIS_GRAPHICS_EXPORTED explicit VideoRenderer(
27+
ServiceProvider* service_provider,
28+
const std::filesystem::path& recording_path,
29+
shapes::UPoint size
30+
);
31+
32+
std::optional<std::string>
33+
render(const std::string& destination_path, u32 fps, const std::function<void(double progress)>& progress_callback);
34+
35+
~VideoRenderer();
36+
37+
38+
// implementation of ServiceProvider
39+
40+
[[nodiscard]] EventDispatcher& event_dispatcher() override;
41+
42+
[[nodiscard]] const EventDispatcher& event_dispatcher() const override;
43+
44+
FontManager& font_manager() override;
45+
46+
[[nodiscard]] const FontManager& font_manager() const override;
47+
48+
CommandLineArguments& command_line_arguments() override;
49+
50+
[[nodiscard]] const CommandLineArguments& command_line_arguments() const override;
51+
52+
SettingsManager& settings_manager() override;
53+
54+
[[nodiscard]] const SettingsManager& settings_manager() const override;
55+
56+
MusicManager& music_manager() override;
57+
58+
[[nodiscard]] const MusicManager& music_manager() const override;
59+
60+
[[nodiscard]] const Renderer& renderer() const override;
61+
62+
[[nodiscard]] const Window& window() const override;
63+
64+
[[nodiscard]] Window& window() override;
65+
66+
[[nodiscard]] input::InputManager& input_manager() override;
67+
68+
[[nodiscard]] const input::InputManager& input_manager() const override;
69+
70+
[[nodiscard]] const std::unique_ptr<lobby::API>& api() const override;
71+
72+
#if defined(_HAVE_DISCORD_SDK)
73+
74+
[[nodiscard]] std::optional<DiscordInstance>& discord_instance() override;
75+
76+
[[nodiscard]] const std::optional<DiscordInstance>& discord_instance() const override;
77+
78+
#endif
79+
};
80+
81+
82+
struct Decoder;
83+
84+
85+
//TODO(Totto): also support library and not only subprocess call
86+
// See e.g. https://github.com/Raveler/ffmpeg-cpp
87+
struct VideoRendererBackend {
88+
private:
89+
std::string m_destination_path;
90+
std::unique_ptr<Decoder> m_decoder;
91+
92+
public:
93+
OOPETRIS_GRAPHICS_EXPORTED explicit VideoRendererBackend(const std::filesystem::path& destination_path);
94+
95+
OOPETRIS_GRAPHICS_EXPORTED ~VideoRendererBackend();
96+
97+
OOPETRIS_GRAPHICS_EXPORTED [[nodiscard]] std::optional<std::string> setup(u32 fps, shapes::UPoint size);
98+
99+
bool add_frame(SDL_Surface* surface);
100+
101+
bool finish(bool cancel);
102+
};

0 commit comments

Comments
 (0)