diff --git a/Source/Core/Core/Config/GraphicsSettings.cpp b/Source/Core/Core/Config/GraphicsSettings.cpp index 4d18bd17ac..4dfe819beb 100644 --- a/Source/Core/Core/Config/GraphicsSettings.cpp +++ b/Source/Core/Core/Config/GraphicsSettings.cpp @@ -70,8 +70,9 @@ const Info GFX_DUMP_PIXEL_FORMAT{{System::GFX, "Settings", "DumpPix const Info GFX_DUMP_ENCODER{{System::GFX, "Settings", "DumpEncoder"}, ""}; const Info GFX_DUMP_PATH{{System::GFX, "Settings", "DumpPath"}, ""}; const Info GFX_BITRATE_KBPS{{System::GFX, "Settings", "BitrateKbps"}, 25000}; -const Info GFX_INTERNAL_RESOLUTION_FRAME_DUMPS{ - {System::GFX, "Settings", "InternalResolutionFrameDumps"}, false}; +const Info GFX_FRAME_DUMPS_RESOLUTION_TYPE{ + {System::GFX, "Settings", "FrameDumpsResolutionType"}, + FrameDumpResolutionType::XFBAspectRatioCorrectedResolution}; const Info GFX_PNG_COMPRESSION_LEVEL{{System::GFX, "Settings", "PNGCompressionLevel"}, 6}; const Info GFX_ENABLE_GPU_TEXTURE_DECODING{ {System::GFX, "Settings", "EnableGPUTextureDecoding"}, false}; diff --git a/Source/Core/Core/Config/GraphicsSettings.h b/Source/Core/Core/Config/GraphicsSettings.h index d641ce4aaa..bccc5ae293 100644 --- a/Source/Core/Core/Config/GraphicsSettings.h +++ b/Source/Core/Core/Config/GraphicsSettings.h @@ -14,6 +14,7 @@ enum class TextureFilteringMode : int; enum class OutputResamplingMode : int; enum class ColorCorrectionRegion : int; enum class TriState : int; +enum class FrameDumpResolutionType : int; namespace Config { @@ -67,7 +68,7 @@ extern const Info GFX_DUMP_PIXEL_FORMAT; extern const Info GFX_DUMP_ENCODER; extern const Info GFX_DUMP_PATH; extern const Info GFX_BITRATE_KBPS; -extern const Info GFX_INTERNAL_RESOLUTION_FRAME_DUMPS; +extern const Info GFX_FRAME_DUMPS_RESOLUTION_TYPE; extern const Info GFX_PNG_COMPRESSION_LEVEL; extern const Info GFX_ENABLE_GPU_TEXTURE_DECODING; extern const Info GFX_ENABLE_PIXEL_LIGHTING; diff --git a/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp b/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp index 298c79832b..bad83a6947 100644 --- a/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp +++ b/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp @@ -137,21 +137,24 @@ void AdvancedWidget::CreateWidgets() auto* dump_layout = new QGridLayout(); dump_box->setLayout(dump_layout); - m_use_fullres_framedumps = new ConfigBool(tr("Dump at Internal Resolution"), - Config::GFX_INTERNAL_RESOLUTION_FRAME_DUMPS); + m_frame_dumps_resolution_type = + new ConfigChoice({tr("Window Resolution"), tr("Aspect Ratio Corrected Internal Resolution"), + tr("Raw Internal Resolution")}, + Config::GFX_FRAME_DUMPS_RESOLUTION_TYPE); m_dump_use_ffv1 = new ConfigBool(tr("Use Lossless Codec (FFV1)"), Config::GFX_USE_FFV1); m_dump_bitrate = new ConfigInteger(0, 1000000, Config::GFX_BITRATE_KBPS, 1000); m_png_compression_level = new ConfigInteger(0, 9, Config::GFX_PNG_COMPRESSION_LEVEL); - dump_layout->addWidget(m_use_fullres_framedumps, 0, 0); + dump_layout->addWidget(new QLabel(tr("Resolution Type:")), 0, 0); + dump_layout->addWidget(m_frame_dumps_resolution_type, 0, 1); #if defined(HAVE_FFMPEG) - dump_layout->addWidget(m_dump_use_ffv1, 0, 1); - dump_layout->addWidget(new QLabel(tr("Bitrate (kbps):")), 1, 0); - dump_layout->addWidget(m_dump_bitrate, 1, 1); + dump_layout->addWidget(m_dump_use_ffv1, 1, 0); + dump_layout->addWidget(new QLabel(tr("Bitrate (kbps):")), 2, 0); + dump_layout->addWidget(m_dump_bitrate, 2, 1); #endif - dump_layout->addWidget(new QLabel(tr("PNG Compression Level:")), 2, 0); + dump_layout->addWidget(new QLabel(tr("PNG Compression Level:")), 3, 0); m_png_compression_level->SetTitle(tr("PNG Compression Level")); - dump_layout->addWidget(m_png_compression_level, 2, 1); + dump_layout->addWidget(m_png_compression_level, 3, 1); // Misc. auto* misc_box = new QGroupBox(tr("Misc")); @@ -340,11 +343,21 @@ void AdvancedWidget::AddDescriptions() static const char TR_LOAD_GRAPHICS_MODS_DESCRIPTION[] = QT_TR_NOOP("Loads graphics mods from User/Load/GraphicsMods/.

If " "unsure, leave this unchecked."); - static const char TR_INTERNAL_RESOLUTION_FRAME_DUMPING_DESCRIPTION[] = QT_TR_NOOP( - "Creates frame dumps and screenshots at the internal resolution of the renderer, rather than " - "the size of the window it is displayed within.

If the aspect ratio is widescreen, " - "the output image will be scaled horizontally to preserve the vertical resolution.

" - "If unsure, leave this unchecked."); + static const char TR_FRAME_DUMPS_RESOLUTION_TYPE_DESCRIPTION[] = QT_TR_NOOP( + "Selects how frame dumps (videos) and screenshots are going to be captured.
If the game " + "or window resolution change during a recording, multiple video files might be created.
" + "Note that color correction and cropping are always ignored by the captures." + "

Window Resolution: Uses the output window resolution (without black bars)." + "
This is a simple dumping option that will capture the image more or less as you see it." + "
Aspect Ratio Corrected Internal Resolution: " + "Uses the Internal Resolution (XFB size), and corrects it by the target aspect ratio.
" + "This option will consistently dump at the specified Internal Resolution " + "regardless of how the image is displayed during recording." + "
Raw Internal Resolution: Uses the Internal Resolution (XFB size) " + "without correcting it with the target aspect ratio.
" + "This will provide a clean dump without any aspect ratio correction so users have as raw as " + "possible input for external editing software.

If unsure, leave " + "this at \"Aspect Ratio Corrected Internal Resolution\"."); #if defined(HAVE_FFMPEG) static const char TR_USE_FFV1_DESCRIPTION[] = QT_TR_NOOP("Encodes frame dumps using the FFV1 codec.

If " @@ -435,7 +448,7 @@ void AdvancedWidget::AddDescriptions() m_dump_xfb_target->SetDescription(tr(TR_DUMP_XFB_DESCRIPTION)); m_disable_vram_copies->SetDescription(tr(TR_DISABLE_VRAM_COPIES_DESCRIPTION)); m_enable_graphics_mods->SetDescription(tr(TR_LOAD_GRAPHICS_MODS_DESCRIPTION)); - m_use_fullres_framedumps->SetDescription(tr(TR_INTERNAL_RESOLUTION_FRAME_DUMPING_DESCRIPTION)); + m_frame_dumps_resolution_type->SetDescription(tr(TR_FRAME_DUMPS_RESOLUTION_TYPE_DESCRIPTION)); #ifdef HAVE_FFMPEG m_dump_use_ffv1->SetDescription(tr(TR_USE_FFV1_DESCRIPTION)); #endif diff --git a/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.h b/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.h index 3d89b04ef0..6cd0e18fdb 100644 --- a/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.h +++ b/Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.h @@ -60,7 +60,7 @@ private: // Frame dumping ConfigBool* m_dump_use_ffv1; - ConfigBool* m_use_fullres_framedumps; + ConfigChoice* m_frame_dumps_resolution_type; ConfigInteger* m_dump_bitrate; ConfigInteger* m_png_compression_level; diff --git a/Source/Core/VideoCommon/FrameDumper.cpp b/Source/Core/VideoCommon/FrameDumper.cpp index db4ef5205e..30f6a40e10 100644 --- a/Source/Core/VideoCommon/FrameDumper.cpp +++ b/Source/Core/VideoCommon/FrameDumper.cpp @@ -18,6 +18,9 @@ #include "VideoCommon/Present.h" #include "VideoCommon/VideoConfig.h" +// The video encoder needs the image to be a multiple of x samples. +static constexpr int VIDEO_ENCODER_LCM = 4; + static bool DumpFrameToPNG(const FrameData& frame, const std::string& file_name) { return Common::ConvertRGBAToRGBAndSavePNG(file_name, frame.data, frame.width, frame.height, @@ -354,6 +357,13 @@ bool FrameDumper::IsFrameDumping() const return false; } +int FrameDumper::GetRequiredResolutionLeastCommonMultiple() const +{ + if (Config::Get(Config::MAIN_MOVIE_DUMP_FRAMES)) + return VIDEO_ENCODER_LCM; + return 1; +} + void FrameDumper::DoState(PointerWrap& p) { #ifdef HAVE_FFMPEG diff --git a/Source/Core/VideoCommon/FrameDumper.h b/Source/Core/VideoCommon/FrameDumper.h index 859a4140e3..abf3bac38a 100644 --- a/Source/Core/VideoCommon/FrameDumper.h +++ b/Source/Core/VideoCommon/FrameDumper.h @@ -33,6 +33,7 @@ public: void SaveScreenshot(std::string filename); bool IsFrameDumping() const; + int GetRequiredResolutionLeastCommonMultiple() const; void DoState(PointerWrap& p); diff --git a/Source/Core/VideoCommon/Present.cpp b/Source/Core/VideoCommon/Present.cpp index 0cc19d6e14..d6794a5d54 100644 --- a/Source/Core/VideoCommon/Present.cpp +++ b/Source/Core/VideoCommon/Present.cpp @@ -25,9 +25,6 @@ std::unique_ptr g_presenter; -// The video encoder needs the image to be a multiple of x samples. -static constexpr int VIDEO_ENCODER_LCM = 4; - namespace VideoCommon { // Stretches the native/internal analog resolution aspect ratio from ~4:3 to ~16:9 @@ -212,18 +209,61 @@ void Presenter::ProcessFrameDumping(u64 ticks) const if (g_frame_dumper->IsFrameDumping() && m_xfb_entry) { MathUtil::Rectangle target_rect; - if (!g_ActiveConfig.bInternalResolutionFrameDumps && !g_gfx->IsHeadless()) + switch (g_ActiveConfig.frame_dumps_resolution_type) { - target_rect = GetTargetRectangle(); + default: + case FrameDumpResolutionType::WindowResolution: + { + if (!g_gfx->IsHeadless()) + { + target_rect = GetTargetRectangle(); + break; + } + [[fallthrough]]; } - else + case FrameDumpResolutionType::XFBAspectRatioCorrectedResolution: { - int width, height; - std::tie(width, height) = - CalculateOutputDimensions(m_xfb_rect.GetWidth(), m_xfb_rect.GetHeight()); - target_rect = MathUtil::Rectangle(0, 0, width, height); + target_rect = m_xfb_rect; + const bool allow_stretch = false; + auto [float_width, float_height] = + ScaleToDisplayAspectRatio(m_xfb_rect.GetWidth(), m_xfb_rect.GetHeight(), allow_stretch); + const float draw_aspect_ratio = CalculateDrawAspectRatio(allow_stretch); + auto [int_width, int_height] = + FindClosestIntegerResolution(float_width, float_height, draw_aspect_ratio); + target_rect = MathUtil::Rectangle(0, 0, int_width, int_height); + break; + } + case FrameDumpResolutionType::XFBRawResolution: + { + target_rect = m_xfb_rect; + break; + } } + int width = target_rect.GetWidth(); + int height = target_rect.GetHeight(); + + const int resolution_lcm = g_frame_dumper->GetRequiredResolutionLeastCommonMultiple(); + + // Ensure divisibility by the dumper LCM and a min of 1 to make it compatible with all the + // video encoders. Note that this is theoretically only necessary when recording videos and not + // screenshots. + // We always scale positively to make sure the least amount of information is lost. + // + // TODO: this should be added as black padding on the edges by the frame dumper. + if ((width % resolution_lcm) != 0 || width == 0) + width += resolution_lcm - (width % resolution_lcm); + if ((height % resolution_lcm) != 0 || height == 0) + height += resolution_lcm - (height % resolution_lcm); + + // Remove any black borders, there would be no point in including them in the recording + target_rect.left = 0; + target_rect.top = 0; + target_rect.right = width; + target_rect.bottom = height; + + // TODO: any scaling done by this won't be gamma corrected, + // we should either apply post processing as well, or port its gamma correction code g_frame_dumper->DumpCurrentFrame(m_xfb_entry->texture.get(), m_xfb_rect, target_rect, ticks, m_frame_count); } @@ -347,7 +387,8 @@ float Presenter::CalculateDrawAspectRatio(bool allow_stretch) const if (aspect_mode == AspectMode::Stretch) return (static_cast(m_backbuffer_width) / static_cast(m_backbuffer_height)); - auto& vi = Core::System::GetInstance().GetVideoInterface(); + // The actual aspect ratio of the XFB texture is irrelevant, the VI one is the one that matters + const auto& vi = Core::System::GetInstance().GetVideoInterface(); const float source_aspect_ratio = vi.GetAspectRatio(); // This will scale up the source ~4:3 resolution to its equivalent ~16:9 resolution @@ -538,7 +579,7 @@ void Presenter::UpdateDrawRectangle() // Don't know if there is a better place for this code so there isn't a 1 frame delay if (g_ActiveConfig.bWidescreenHack) { - auto& vi = Core::System::GetInstance().GetVideoInterface(); + const auto& vi = Core::System::GetInstance().GetVideoInterface(); float source_aspect_ratio = vi.GetAspectRatio(); // If the game is meant to be in widescreen (or forced to), // scale the source aspect ratio to it. @@ -582,9 +623,10 @@ void Presenter::UpdateDrawRectangle() // Crop the picture to a standard aspect ratio. (if enabled) auto [crop_width, crop_height] = ApplyStandardAspectCrop(draw_width, draw_height); + const float crop_aspect_ratio = crop_width / crop_height; // scale the picture to fit the rendering window - if (win_aspect_ratio >= crop_width / crop_height) + if (win_aspect_ratio >= crop_aspect_ratio) { // the window is flatter than the picture draw_width *= win_height / crop_height; @@ -604,18 +646,7 @@ void Presenter::UpdateDrawRectangle() int int_draw_width; int int_draw_height; - if (g_frame_dumper->IsFrameDumping()) - { - // ensure divisibility by "VIDEO_ENCODER_LCM" to make it compatible with all the video encoders. - // Note that this is theoretically only necessary when recording videos and not screenshots. - draw_width = - std::ceil(draw_width) - static_cast(std::ceil(draw_width)) % VIDEO_ENCODER_LCM; - draw_height = - std::ceil(draw_height) - static_cast(std::ceil(draw_height)) % VIDEO_ENCODER_LCM; - int_draw_width = static_cast(draw_width); - int_draw_height = static_cast(draw_height); - } - else if (g_ActiveConfig.aspect_mode != AspectMode::Raw || !m_xfb_entry) + if (g_ActiveConfig.aspect_mode != AspectMode::Raw || !m_xfb_entry) { // Find the best integer resolution: the closest aspect ratio with the least black bars. // This should have no influence if "AspectMode::Stretch" is active. @@ -666,6 +697,7 @@ std::tuple Presenter::ScaleToDisplayAspectRatio(const int width, c std::tuple Presenter::CalculateOutputDimensions(int width, int height, bool allow_stretch) const { + // Protect against zero width and height, a minimum of 1 will do width = std::max(width, 1); height = std::max(height, 1); @@ -700,14 +732,6 @@ std::tuple Presenter::CalculateOutputDimensions(int width, int height, height = static_cast(std::ceil(scaled_height)); } - if (g_frame_dumper->IsFrameDumping()) - { - // UpdateDrawRectangle() makes sure that the rendered image is divisible by "VIDEO_ENCODER_LCM" - // for video encoders, so do that here too to match it - width -= width % VIDEO_ENCODER_LCM; - height -= height % VIDEO_ENCODER_LCM; - } - return std::make_tuple(width, height); } diff --git a/Source/Core/VideoCommon/Present.h b/Source/Core/VideoCommon/Present.h index 9723e1aa19..3f8f43a687 100644 --- a/Source/Core/VideoCommon/Present.h +++ b/Source/Core/VideoCommon/Present.h @@ -107,10 +107,13 @@ private: void OnBackBufferSizeChanged(); + // Scales a raw XFB resolution to the target (display) aspect ratio, + // also accounting for crop and other minor adjustments std::tuple CalculateOutputDimensions(int width, int height, bool allow_stretch = true) const; std::tuple ApplyStandardAspectCrop(float width, float height, bool allow_stretch = true) const; + // Scales a raw XFB resolution to the target (display) aspect ratio std::tuple ScaleToDisplayAspectRatio(int width, int height, bool allow_stretch = true) const; diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp index e11776d356..0266cb9c19 100644 --- a/Source/Core/VideoCommon/VideoConfig.cpp +++ b/Source/Core/VideoCommon/VideoConfig.cpp @@ -128,7 +128,7 @@ void VideoConfig::Refresh() sDumpEncoder = Config::Get(Config::GFX_DUMP_ENCODER); sDumpPath = Config::Get(Config::GFX_DUMP_PATH); iBitrateKbps = Config::Get(Config::GFX_BITRATE_KBPS); - bInternalResolutionFrameDumps = Config::Get(Config::GFX_INTERNAL_RESOLUTION_FRAME_DUMPS); + frame_dumps_resolution_type = Config::Get(Config::GFX_FRAME_DUMPS_RESOLUTION_TYPE); bEnableGPUTextureDecoding = Config::Get(Config::GFX_ENABLE_GPU_TEXTURE_DECODING); bPreferVSForLinePointExpansion = Config::Get(Config::GFX_PREFER_VS_FOR_LINE_POINT_EXPANSION); bEnablePixelLighting = Config::Get(Config::GFX_ENABLE_PIXEL_LIGHTING); diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h index d7ab34f142..1e5f03d0e5 100644 --- a/Source/Core/VideoCommon/VideoConfig.h +++ b/Source/Core/VideoCommon/VideoConfig.h @@ -80,6 +80,16 @@ enum class TriState : int Auto }; +enum class FrameDumpResolutionType : int +{ + // Window resolution (not including potential back buffer black borders) + WindowResolution, + // The aspect ratio corrected XFB resolution (XFB pixels might not have been square) + XFBAspectRatioCorrectedResolution, + // The raw unscaled XFB resolution (based on "internal resolution" scale) + XFBRawResolution, +}; + // Bitmask containing information about which configuration has changed for the backend. enum ConfigChangeBits : u32 { @@ -189,7 +199,8 @@ struct VideoConfig final std::string sDumpEncoder; std::string sDumpFormat; std::string sDumpPath; - bool bInternalResolutionFrameDumps = false; + FrameDumpResolutionType frame_dumps_resolution_type = + FrameDumpResolutionType::XFBAspectRatioCorrectedResolution; bool bBorderlessFullscreen = false; bool bEnableGPUTextureDecoding = false; bool bPreferVSForLinePointExpansion = false;