diff --git a/rpcs3/Emu/RSX/GL/GLPresent.cpp b/rpcs3/Emu/RSX/GL/GLPresent.cpp index 187e0f3b4d..12398b7d61 100644 --- a/rpcs3/Emu/RSX/GL/GLPresent.cpp +++ b/rpcs3/Emu/RSX/GL/GLPresent.cpp @@ -1,9 +1,12 @@ #include "stdafx.h" #include "GLGSRender.h" #include "Emu/Cell/Modules/cellVideoOut.h" +#include "util/video_provider.h" LOG_CHANNEL(screenshot_log, "SCREENSHOT"); +extern atomic_t g_recording_mode; + namespace gl { namespace debug @@ -232,10 +235,8 @@ void GLGSRender::flip(const rsx::display_flip_info_t& info) if (image_to_flip) { - if (m_frame->screenshot_toggle) + if (m_frame->screenshot_toggle || (g_recording_mode != recording_mode::stopped && m_frame->can_consume_frame())) { - m_frame->screenshot_toggle = false; - std::vector sshot_frame(buffer_height * buffer_width * 4); gl::pixel_pack_settings pack_settings{}; @@ -246,10 +247,19 @@ void GLGSRender::flip(const rsx::display_flip_info_t& info) else glGetTextureImageEXT(image_to_flip, GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, sshot_frame.data()); - if (GLenum err; (err = glGetError()) != GL_NO_ERROR) + if (GLenum err = glGetError(); err != GL_NO_ERROR) + { screenshot_log.error("Failed to capture image: 0x%x", err); - else + } + else if (m_frame->screenshot_toggle) + { + m_frame->screenshot_toggle = false; m_frame->take_screenshot(std::move(sshot_frame), buffer_width, buffer_height, false); + } + else + { + m_frame->present_frame(sshot_frame, buffer_width, buffer_height, false); + } } const areai screen_area = coordi({}, { static_cast(buffer_width), static_cast(buffer_height) }); diff --git a/rpcs3/Emu/RSX/GSFrameBase.h b/rpcs3/Emu/RSX/GSFrameBase.h index f68c6d4aa3..721a1a0358 100644 --- a/rpcs3/Emu/RSX/GSFrameBase.h +++ b/rpcs3/Emu/RSX/GSFrameBase.h @@ -30,5 +30,7 @@ public: virtual display_handle_t handle() const = 0; atomic_t screenshot_toggle = false; + virtual bool can_consume_frame() const = 0; + virtual void present_frame(std::vector& data, const u32 width, const u32 height, bool is_bgra) const = 0; virtual void take_screenshot(const std::vector sshot_data, const u32 sshot_width, const u32 sshot_height, bool is_bgra) = 0; }; diff --git a/rpcs3/Emu/RSX/VK/VKPresent.cpp b/rpcs3/Emu/RSX/VK/VKPresent.cpp index 58a0965b25..a173e187ba 100644 --- a/rpcs3/Emu/RSX/VK/VKPresent.cpp +++ b/rpcs3/Emu/RSX/VK/VKPresent.cpp @@ -7,6 +7,9 @@ #include "upscalers/bilinear_pass.hpp" #include "upscalers/fsr_pass.h" #include "util/asm.hpp" +#include "util/video_provider.h" + +extern atomic_t g_recording_mode; void VKGSRender::reinitialize_swapchain() { @@ -667,10 +670,8 @@ void VKGSRender::flip(const rsx::display_flip_info_t& info) m_upscaler->scale_output(*m_current_command_buffer, image_to_flip, target_image, target_layout, rgn, UPSCALE_AND_COMMIT | UPSCALE_DEFAULT_VIEW); } - if (m_frame->screenshot_toggle) + if (m_frame->screenshot_toggle || (g_recording_mode != recording_mode::stopped && m_frame->can_consume_frame())) { - m_frame->screenshot_toggle = false; - const usz sshot_size = buffer_height * buffer_width * 4; vk::buffer sshot_vkbuf(*m_device, utils::align(sshot_size, 0x100000), m_device->get_memory_mapping().host_visible_coherent, @@ -702,7 +703,16 @@ void VKGSRender::flip(const rsx::display_flip_info_t& info) sshot_vkbuf.unmap(); const bool is_bgra = image_to_flip->format() == VK_FORMAT_B8G8R8A8_UNORM; - m_frame->take_screenshot(std::move(sshot_frame), buffer_width, buffer_height, is_bgra); + + if (m_frame->screenshot_toggle) + { + m_frame->screenshot_toggle = false; + m_frame->take_screenshot(std::move(sshot_frame), buffer_width, buffer_height, is_bgra); + } + else + { + m_frame->present_frame(sshot_frame, buffer_width, buffer_height, is_bgra); + } } } diff --git a/rpcs3/rpcs3qt/gs_frame.cpp b/rpcs3/rpcs3qt/gs_frame.cpp index 66792e87a4..9e8e364cfc 100644 --- a/rpcs3/rpcs3qt/gs_frame.cpp +++ b/rpcs3/rpcs3qt/gs_frame.cpp @@ -5,6 +5,7 @@ #include "Utilities/Timer.h" #include "Utilities/date_time.h" #include "Utilities/File.h" +#include "util/video_provider.h" #include "Emu/System.h" #include "Emu/system_config.h" #include "Emu/system_progress.hpp" @@ -13,6 +14,7 @@ #include "Emu/Cell/Modules/cellVideoOut.h" #include "Emu/RSX/rsx_utils.h" #include "Emu/RSX/Overlays/overlay_message.h" +#include "Emu/Io/recording_config.h" #include #include @@ -54,6 +56,7 @@ LOG_CHANNEL(gui_log, "GUI"); extern atomic_t g_user_asked_for_frame_capture; extern atomic_t g_disable_frame_limit; +extern atomic_t g_recording_mode; atomic_t g_game_window_focused = false; @@ -78,6 +81,14 @@ gs_frame::gs_frame(QScreen* screen, const QRect& geometry, const QIcon& appIcon, m_window_title = Emu.GetFormattedTitle(0); + if (!g_cfg_recording.load()) + { + gui_log.notice("Could not load recording config. Using defaults."); + } + + g_fxo->need(); + m_video_encoder = std::make_shared(); + if (!appIcon.isNull()) { setIcon(appIcon); @@ -322,6 +333,94 @@ void gs_frame::keyPressEvent(QKeyEvent *keyEvent) } break; } + case Qt::Key_F11: + { + utils::video_provider& video_provider = g_fxo->get(); + + if (g_recording_mode == recording_mode::cell) + { + gui_log.warning("A video recorder is already in use by cell. Regular recording can not proceed."); + m_video_encoder->stop(); + break; + } + + if (g_recording_mode.exchange(recording_mode::stopped) == recording_mode::rpcs3) + { + m_video_encoder->stop(); + + if (!video_provider.set_image_sink(nullptr, recording_mode::rpcs3)) + { + gui_log.warning("The video provider could not release the image sink. A sink with higher priority must have been set."); + } + + rsx::overlays::queue_message(tr("Recording stopped: %0").arg(QString::fromStdString(m_video_encoder->path())).toStdString()); + } + else + { + m_video_encoder->stop(); + + const std::string& id = Emu.GetTitleID(); + std::string video_path = fs::get_config_dir() + "recordings/"; + if (!id.empty()) + { + video_path += id + "/"; + } + + if (!fs::create_path(video_path) && fs::g_tls_error != fs::error::exist) + { + screenshot_log.error("Failed to create recordings path \"%s\" : %s", video_path, fs::g_tls_error); + break; + } + + if (!id.empty()) + { + video_path += id + "_"; + } + + video_path += "recording_" + date_time::current_time_narrow<'_'>() + ".mp4"; + + utils::video_encoder::frame_format output_format{}; + output_format.av_pixel_format = static_cast(g_cfg_recording.pixel_format.get()); + output_format.width = g_cfg_recording.width; + output_format.height = g_cfg_recording.height; + output_format.pitch = g_cfg_recording.width * 4; + + m_video_encoder->set_path(video_path); + m_video_encoder->set_framerate(g_cfg_recording.framerate); + m_video_encoder->set_video_bitrate(g_cfg_recording.video_bps); + m_video_encoder->set_video_codec(g_cfg_recording.video_codec); + m_video_encoder->set_max_b_frames(g_cfg_recording.max_b_frames); + m_video_encoder->set_gop_size(g_cfg_recording.gop_size); + m_video_encoder->set_output_format(output_format); + m_video_encoder->set_sample_rate(0); // TODO + m_video_encoder->set_audio_bitrate(0); // TODO + m_video_encoder->set_audio_codec(0); // TODO + m_video_encoder->encode(); + + if (m_video_encoder->has_error) + { + rsx::overlays::queue_message(tr("Recording not possible").toStdString()); + m_video_encoder->stop(); + break; + } + + if (!video_provider.set_image_sink(m_video_encoder, recording_mode::rpcs3)) + { + gui_log.warning("The video provider could not set the image sink. A sink with higher priority must have been set."); + rsx::overlays::queue_message(tr("Recording not possible").toStdString()); + m_video_encoder->stop(); + break; + } + + video_provider.set_pause_time(0); + + g_recording_mode = recording_mode::rpcs3; + + rsx::overlays::queue_message(tr("Recording started").toStdString()); + } + + break; + } case Qt::Key_F12: { screenshot_toggle = true; @@ -577,6 +676,18 @@ void gs_frame::flip(draw_context_t, bool /*skip_frame*/) } } +bool gs_frame::can_consume_frame() const +{ + utils::video_provider& video_provider = g_fxo->get(); + return video_provider.can_consume_frame(); +} + +void gs_frame::present_frame(std::vector& data, const u32 width, const u32 height, bool is_bgra) const +{ + utils::video_provider& video_provider = g_fxo->get(); + video_provider.present_frame(data, width, height, is_bgra); +} + void gs_frame::take_screenshot(std::vector data, const u32 sshot_width, const u32 sshot_height, bool is_bgra) { std::thread( diff --git a/rpcs3/rpcs3qt/gs_frame.h b/rpcs3/rpcs3qt/gs_frame.h index 0d57b02e7a..06bdc5eec3 100644 --- a/rpcs3/rpcs3qt/gs_frame.h +++ b/rpcs3/rpcs3qt/gs_frame.h @@ -2,6 +2,8 @@ #include "util/types.hpp" #include "util/atomic.hpp" +#include "util/media_utils.h" +#include "util/video_provider.h" #include "Emu/RSX/GSFrameBase.h" #include @@ -52,6 +54,8 @@ private: u32 m_hide_mouse_idletime = 2000; // ms bool m_flip_showed_frame = false; + std::shared_ptr m_video_encoder{}; + public: explicit gs_frame(QScreen* screen, const QRect& geometry, const QIcon& appIcon, std::shared_ptr gui_settings); ~gs_frame(); @@ -73,6 +77,8 @@ public: */ bool get_mouse_lock_state(); + bool can_consume_frame() const override; + void present_frame(std::vector& data, const u32 width, const u32 height, bool is_bgra) const override; void take_screenshot(std::vector data, const u32 sshot_width, const u32 sshot_height, bool is_bgra) override; protected: