video_core: Add fractional scaling support

This commit is contained in:
ptyfyre 2026-04-06 11:32:38 +02:00
parent 06a535f50e
commit a4a47392dd
24 changed files with 294 additions and 136 deletions

View file

@ -18,7 +18,7 @@ enum class IntSetting(
CAMERA_OUTER_LEFT_FLIP(SettingKeys.camera_outer_left_flip(), Settings.SECTION_CAMERA, 0),
CAMERA_OUTER_RIGHT_FLIP(SettingKeys.camera_outer_right_flip(), Settings.SECTION_CAMERA, 0),
GRAPHICS_API(SettingKeys.graphics_api(), Settings.SECTION_RENDERER, 1),
RESOLUTION_FACTOR(SettingKeys.resolution_factor(), Settings.SECTION_RENDERER, 1),
RESOLUTION_FACTOR(SettingKeys.resolution_factor(), Settings.SECTION_RENDERER, 100),
STEREOSCOPIC_3D_MODE(SettingKeys.render_3d(), Settings.SECTION_RENDERER, 2),
STEREOSCOPIC_3D_DEPTH(SettingKeys.factor_3d(), Settings.SECTION_RENDERER, 0),
STEPS_PER_HOUR(SettingKeys.steps_per_hour(), Settings.SECTION_SYSTEM, 0),

View file

@ -150,6 +150,12 @@ void Config::ReadValues() {
ReadSetting("Renderer", Settings::values.use_hw_shader);
ReadSetting("Renderer", Settings::values.use_shader_jit);
ReadSetting("Renderer", Settings::values.resolution_factor);
{
const u32 rf = Settings::values.resolution_factor.GetValue();
if (rf >= 1 && rf <= 10) {
Settings::values.resolution_factor.SetValue(rf * 100);
}
}
ReadSetting("Renderer", Settings::values.use_disk_shader_cache);
ReadSetting("Renderer", Settings::values.use_vsync);
ReadSetting("Renderer", Settings::values.texture_filter);

View file

@ -374,29 +374,49 @@
<string-array name="resolutionFactorNames">
<item>@string/internal_resolution_setting_auto</item>
<item>@string/internal_resolution_setting_0_5x</item>
<item>@string/internal_resolution_setting_1x</item>
<item>@string/internal_resolution_setting_1_5x</item>
<item>@string/internal_resolution_setting_2x</item>
<item>@string/internal_resolution_setting_2_5x</item>
<item>@string/internal_resolution_setting_3x</item>
<item>@string/internal_resolution_setting_3_5x</item>
<item>@string/internal_resolution_setting_4x</item>
<item>@string/internal_resolution_setting_4_5x</item>
<item>@string/internal_resolution_setting_5x</item>
<item>@string/internal_resolution_setting_5_5x</item>
<item>@string/internal_resolution_setting_6x</item>
<item>@string/internal_resolution_setting_6_5x</item>
<item>@string/internal_resolution_setting_7x</item>
<item>@string/internal_resolution_setting_7_5x</item>
<item>@string/internal_resolution_setting_8x</item>
<item>@string/internal_resolution_setting_8_5x</item>
<item>@string/internal_resolution_setting_9x</item>
<item>@string/internal_resolution_setting_9_5x</item>
<item>@string/internal_resolution_setting_10x</item>
</string-array>
<integer-array name="resolutionFactorValues">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
<item>8</item>
<item>9</item>
<item>10</item>
<item>50</item>
<item>100</item>
<item>150</item>
<item>200</item>
<item>250</item>
<item>300</item>
<item>350</item>
<item>400</item>
<item>450</item>
<item>500</item>
<item>550</item>
<item>600</item>
<item>650</item>
<item>700</item>
<item>750</item>
<item>800</item>
<item>850</item>
<item>900</item>
<item>950</item>
<item>1000</item>
</integer-array>
<string-array name="countries">

View file

@ -284,15 +284,25 @@
<string name="internal_resolution">Internal Resolution</string>
<string name="internal_resolution_description">Specifies the resolution used to render at. A high resolution will improve visual quality a lot but is also quite heavy on performance and might cause glitches in certain applications.</string>
<string name="internal_resolution_setting_auto">Auto (Screen Size)</string>
<string name="internal_resolution_setting_0_5x">0.5x Native (200x120)</string>
<string name="internal_resolution_setting_1x">Native (400x240)</string>
<string name="internal_resolution_setting_1_5x">1.5x Native (600x360)</string>
<string name="internal_resolution_setting_2x">2x Native (800x480)</string>
<string name="internal_resolution_setting_2_5x">2.5x Native (1000x600)</string>
<string name="internal_resolution_setting_3x">3x Native (1200x720)</string>
<string name="internal_resolution_setting_3_5x">3.5x Native (1400x840)</string>
<string name="internal_resolution_setting_4x">4x Native (1600x960)</string>
<string name="internal_resolution_setting_4_5x">4.5x Native (1800x1080)</string>
<string name="internal_resolution_setting_5x">5x Native (2000x1200)</string>
<string name="internal_resolution_setting_5_5x">5.5x Native (2200x1320)</string>
<string name="internal_resolution_setting_6x">6x Native (2400x1440)</string>
<string name="internal_resolution_setting_6_5x">6.5x Native (2600x1560)</string>
<string name="internal_resolution_setting_7x">7x Native (2800x1680)</string>
<string name="internal_resolution_setting_7_5x">7.5x Native (3000x1800)</string>
<string name="internal_resolution_setting_8x">8x Native (3200x1920)</string>
<string name="internal_resolution_setting_8_5x">8.5x Native (3400x2040)</string>
<string name="internal_resolution_setting_9x">9x Native (3600x2160)</string>
<string name="internal_resolution_setting_9_5x">9.5x Native (3800x2280)</string>
<string name="internal_resolution_setting_10x">10x Native (4000x2400)</string>
<string name="performance_warning">Turning off this setting will significantly reduce emulation performance! For the best experience, it is recommended that you leave this setting enabled.</string>
<string name="debug_warning">Warning: Modifying these settings will slow emulation</string>

View file

@ -363,19 +363,29 @@ static constexpr retro_core_option_v2_definition option_definitions[] = {
nullptr,
config::category::graphics,
{
{ "1", "1x (Native 400x240)" },
{ "2", "2x (800x480)" },
{ "3", "3x (1200x720)" },
{ "4", "4x (1600x960)" },
{ "5", "5x (2000x1200)" },
{ "6", "6x (2400x1440)" },
{ "7", "7x (2800x1680)" },
{ "8", "8x (3200x1920)" },
{ "9", "9x (3600x2160)" },
{ "10", "10x (4000x2400)" },
{ "50", "0.5x (200x120)" },
{ "100", "1x Native (400x240)" },
{ "150", "1.5x (600x360)" },
{ "200", "2x (800x480)" },
{ "250", "2.5x (1000x600)" },
{ "300", "3x (1200x720)" },
{ "350", "3.5x (1400x840)" },
{ "400", "4x (1600x960)" },
{ "450", "4.5x (1800x1080)" },
{ "500", "5x (2000x1200)" },
{ "550", "5.5x (2200x1320)" },
{ "600", "6x (2400x1440)" },
{ "650", "6.5x (2600x1560)" },
{ "700", "7x (2800x1680)" },
{ "750", "7.5x (3000x1800)" },
{ "800", "8x (3200x1920)" },
{ "850", "8.5x (3400x2040)" },
{ "900", "9x (3600x2160)" },
{ "950", "9.5x (3800x2280)" },
{ "1000", "10x (4000x2400)" },
{ nullptr, nullptr }
},
"1"
"100"
},
{
config::graphics::texture_filter,
@ -922,7 +932,7 @@ static void ParseGraphicsOptions(void) {
LibRetro::FetchVariable(config::graphics::use_disk_shader_cache, config::enabled) ==
config::enabled;
auto resolution = LibRetro::FetchVariable(config::graphics::resolution_factor, "1");
auto resolution = LibRetro::FetchVariable(config::graphics::resolution_factor, "100");
Settings::values.resolution_factor = std::stoi(resolution);
Settings::values.texture_filter =

View file

@ -239,7 +239,7 @@ LayoutGeometry ComputeLayoutGeometry() {
unsigned baseY;
bool emulated_pointer = true;
float scaling = Settings::values.resolution_factor.GetValue();
float scaling = Settings::values.resolution_factor.GetValue() / 100.0f;
bool swapped = Settings::values.swap_screen.GetValue();
switch (Settings::values.layout_option.GetValue()) {
@ -264,7 +264,7 @@ LayoutGeometry ComputeLayoutGeometry() {
baseY = Core::kScreenTopHeight;
}
if (scaling < 4) {
if (scaling < 4.0f) {
// Unfortunately, to get this aspect ratio correct (and have non-blurry 1x scaling),
// we have to have a pretty large buffer for the minimum ratio.
baseX *= 4;

View file

@ -708,6 +708,14 @@ void QtConfig::ReadRendererValues() {
ReadGlobalSetting(Settings::values.use_vsync);
ReadGlobalSetting(Settings::values.use_display_refresh_rate_detection);
ReadGlobalSetting(Settings::values.resolution_factor);
{
const u32 rf = Settings::values.resolution_factor.GetValue();
if (rf >= 1 && rf <= 10) {
LOG_WARNING(Frontend, "Migrating old resolution factor value of {} to new value of {}",
rf, rf * 100);
Settings::values.resolution_factor.SetValue(rf * 100);
}
}
ReadGlobalSetting(Settings::values.use_integer_scaling);
ReadGlobalSetting(Settings::values.frame_limit);
ReadGlobalSetting(Settings::values.turbo_limit);
@ -828,6 +836,16 @@ void QtConfig::ReadUIValues() {
ReadBasicSetting(UISettings::values.enable_discord_presence);
#endif
ReadBasicSetting(UISettings::values.screenshot_resolution_factor);
{
const u16 srf = UISettings::values.screenshot_resolution_factor.GetValue();
if (srf >= 1 && srf <= 10) {
LOG_WARNING(
Frontend,
"Migrating old screenshot resolution factor value of {} to new value of {}",
srf, srf * 100);
UISettings::values.screenshot_resolution_factor.SetValue(srf * 100);
}
}
ReadUILayoutValues();
ReadUIGameListValues();

View file

@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <array>
#include <QColorDialog>
#include "citra_qt/configuration/configuration_shared.h"
#include "citra_qt/configuration/configure_enhancements.h"
@ -11,6 +13,19 @@
#include "video_core/renderer_opengl/post_processing_opengl.h"
#endif
static constexpr std::array<u32, 21> RESOLUTION_FACTOR_VALUES = {
0, 50, 100, 150, 200, 250, 300, 350, 400, 450, 500,
550, 600, 650, 700, 750, 800, 850, 900, 950, 1000,
};
static int ResolutionFactorToIndex(u32 value) {
const auto it =
std::find(RESOLUTION_FACTOR_VALUES.begin(), RESOLUTION_FACTOR_VALUES.end(), value);
return (it != RESOLUTION_FACTOR_VALUES.end())
? static_cast<int>(std::distance(RESOLUTION_FACTOR_VALUES.begin(), it))
: 0;
}
ConfigureEnhancements::ConfigureEnhancements(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureEnhancements>()) {
ui->setupUi(this);
@ -42,15 +57,20 @@ ConfigureEnhancements::~ConfigureEnhancements() = default;
void ConfigureEnhancements::SetConfiguration() {
if (!Settings::IsConfiguringGlobal()) {
ConfigurationShared::SetPerGameSetting(ui->resolution_factor_combobox,
&Settings::values.resolution_factor);
if (Settings::values.resolution_factor.UsingGlobal()) {
ui->resolution_factor_combobox->setCurrentIndex(ConfigurationShared::USE_GLOBAL_INDEX);
} else {
ui->resolution_factor_combobox->setCurrentIndex(
ResolutionFactorToIndex(Settings::values.resolution_factor.GetValue()) +
ConfigurationShared::USE_GLOBAL_OFFSET);
}
ConfigurationShared::SetPerGameSetting(ui->texture_filter_combobox,
&Settings::values.texture_filter);
ConfigurationShared::SetHighlight(ui->widget_texture_filter,
!Settings::values.texture_filter.UsingGlobal());
} else {
ui->resolution_factor_combobox->setCurrentIndex(
Settings::values.resolution_factor.GetValue());
ResolutionFactorToIndex(Settings::values.resolution_factor.GetValue()));
ui->texture_filter_combobox->setCurrentIndex(
static_cast<int>(Settings::values.texture_filter.GetValue()));
}
@ -109,8 +129,13 @@ void ConfigureEnhancements::RetranslateUI() {
}
void ConfigureEnhancements::ApplyConfiguration() {
ConfigurationShared::ApplyPerGameSetting(&Settings::values.resolution_factor,
ui->resolution_factor_combobox);
ConfigurationShared::ApplyPerGameSetting(
&Settings::values.resolution_factor, ui->resolution_factor_combobox, [](s32 index) -> s32 {
if (index >= 0 && index < static_cast<s32>(RESOLUTION_FACTOR_VALUES.size())) {
return static_cast<s32>(RESOLUTION_FACTOR_VALUES[index]);
}
return 0;
});
Settings::values.render_3d =
static_cast<Settings::StereoRenderOption>(ui->render_3d_combobox->currentIndex());
Settings::values.swap_eyes_3d = ui->swap_eyes_3d->isChecked();
@ -187,7 +212,8 @@ void ConfigureEnhancements::SetupPerGameUI() {
ConfigurationShared::SetColoredComboBox(
ui->resolution_factor_combobox, ui->widget_resolution,
static_cast<int>(Settings::values.resolution_factor.GetValue(true)));
ResolutionFactorToIndex(
static_cast<u32>(Settings::values.resolution_factor.GetValue(true))));
ConfigurationShared::SetColoredComboBox(
ui->texture_filter_combobox, ui->widget_texture_filter,

View file

@ -51,13 +51,23 @@
<item>
<widget class="QComboBox" name="resolution_factor_combobox">
<item>
<property name="text">
<string>Auto (Window Size)</string>
<property name="text">
<string>Auto (Window Size)</string>
</property>
</item>
<item>
<property name="text">
<string>0.5x Native (200x120)</string>
</property>
</item>
<item>
<property name="text">
<string>Native (400x240)</string>
</property>
</item>
<item>
<property name="text">
<string>Native (400x240)</string>
<string>1.5x Native (600x360)</string>
</property>
</item>
<item>
@ -65,41 +75,81 @@
<string>2x Native (800x480)</string>
</property>
</item>
<item>
<property name="text">
<string>2.5x Native (1000x600)</string>
</property>
</item>
<item>
<property name="text">
<string>3x Native (1200x720)</string>
</property>
</item>
<item>
<property name="text">
<string>3.5x Native (1400x840)</string>
</property>
</item>
<item>
<property name="text">
<string>4x Native (1600x960)</string>
</property>
</item>
<item>
<property name="text">
<string>4.5x Native (1800x1080)</string>
</property>
</item>
<item>
<property name="text">
<string>5x Native (2000x1200)</string>
</property>
</item>
<item>
<property name="text">
<string>5.5x Native (2200x1320)</string>
</property>
</item>
<item>
<property name="text">
<string>6x Native (2400x1440)</string>
</property>
</item>
<item>
<property name="text">
<string>6.5x Native (2600x1560)</string>
</property>
</item>
<item>
<property name="text">
<string>7x Native (2800x1680)</string>
</property>
</item>
<item>
<property name="text">
<string>7.5x Native (3000x1800)</string>
</property>
</item>
<item>
<property name="text">
<string>8x Native (3200x1920)</string>
</property>
</item>
<item>
<property name="text">
<string>8.5x Native (3400x2040)</string>
</property>
</item>
<item>
<property name="text">
<string>9x Native (3600x2160)</string>
</property>
</item>
<item>
<property name="text">
<string>9.5x Native (3800x2280)</string>
</property>
</item>
<item>
<property name="text">
<string>10x Native (4000x2400)</string>

View file

@ -529,7 +529,7 @@ struct Values {
SwitchableSetting<bool> use_display_refresh_rate_detection{
true, Keys::use_display_refresh_rate_detection};
Setting<bool> use_shader_jit{true, Keys::use_shader_jit};
SwitchableSetting<u32, true> resolution_factor{1, 0, 10, Keys::resolution_factor};
SwitchableSetting<u32, true> resolution_factor{100, 0, 1000, Keys::resolution_factor};
SwitchableSetting<bool> use_integer_scaling{false, Keys::use_integer_scaling};
SwitchableSetting<double, true> frame_limit{100, 0, 1000, Keys::frame_limit};
SwitchableSetting<double, true> turbo_limit{200, 0, 1000, Keys::turbo_limit};

View file

@ -375,7 +375,7 @@ FramebufferLayout CustomFrameLayout(u32 width, u32 height, bool is_swapped, bool
FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondary,
bool is_portrait) {
u32 width, height, gap;
gap = (int)(Settings::values.screen_gap.GetValue()) * res_scale;
gap = (Settings::values.screen_gap.GetValue() * res_scale) / 100;
FramebufferLayout layout;
if (is_portrait) {
@ -395,18 +395,18 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
break;
case Settings::PortraitLayoutOption::PortraitTopFullWidth:
width = Core::kScreenTopWidth * res_scale;
width = (Core::kScreenTopWidth * res_scale) / 100;
// clang-format off
height = (static_cast<int>(Core::kScreenTopHeight + Core::kScreenBottomHeight * 1.25) *
res_scale) + gap;
height = (static_cast<u32>(Core::kScreenTopHeight + Core::kScreenBottomHeight * 1.25f) *
res_scale) / 100 + gap;
// clang-format on
layout =
PortraitTopFullFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
Settings::values.upright_screen.GetValue());
break;
case Settings::PortraitLayoutOption::PortraitOriginal:
width = Core::kScreenTopWidth * res_scale;
height = (Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale;
width = (Core::kScreenTopWidth * res_scale) / 100;
height = ((Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale) / 100;
layout = PortraitOriginalLayout(width, height, Settings::values.swap_screen.GetValue());
break;
}
@ -428,11 +428,11 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
case Settings::LayoutOption::SingleScreen: {
const bool swap_screens = is_secondary || Settings::values.swap_screen.GetValue();
if (swap_screens) {
width = Core::kScreenBottomWidth * res_scale;
height = Core::kScreenBottomHeight * res_scale;
width = (Core::kScreenBottomWidth * res_scale) / 100;
height = (Core::kScreenBottomHeight * res_scale) / 100;
} else {
width = Core::kScreenTopWidth * res_scale;
height = Core::kScreenTopHeight * res_scale;
width = (Core::kScreenTopWidth * res_scale) / 100;
height = (Core::kScreenTopHeight * res_scale) / 100;
}
if (Settings::values.upright_screen.GetValue()) {
std::swap(width, height);
@ -459,11 +459,11 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
Settings::values.small_screen_position.GetValue() ==
Settings::SmallScreenPosition::BelowLarge) {
// vertical, so height is sum of heights, width is larger of widths
width = std::max(largeWidth, smallWidth) * res_scale;
height = (largeHeight + smallHeight) * res_scale + gap;
width = (static_cast<u32>(std::max(largeWidth, smallWidth)) * res_scale) / 100;
height = ((largeHeight + smallHeight) * res_scale) / 100 + gap;
} else {
width = (largeWidth + smallWidth) * res_scale + gap;
height = std::max(largeHeight, smallHeight) * res_scale;
width = ((largeWidth + smallWidth) * res_scale) / 100 + gap;
height = (static_cast<u32>(std::max(largeHeight, smallHeight)) * res_scale) / 100;
}
if (Settings::values.upright_screen.GetValue()) {
@ -476,8 +476,8 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
break;
}
case Settings::LayoutOption::SideScreen:
width = (Core::kScreenTopWidth + Core::kScreenBottomWidth) * res_scale + gap;
height = Core::kScreenTopHeight * res_scale;
width = ((Core::kScreenTopWidth + Core::kScreenBottomWidth) * res_scale) / 100 + gap;
height = (Core::kScreenTopHeight * res_scale) / 100;
if (Settings::values.upright_screen.GetValue()) {
std::swap(width, height);
@ -487,7 +487,7 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
Settings::SmallScreenPosition::MiddleRight);
break;
case Settings::LayoutOption::HybridScreen:
height = Core::kScreenTopHeight * res_scale;
height = (Core::kScreenTopHeight * res_scale) / 100;
if (Settings::values.swap_screen.GetValue()) {
width = Core::kScreenBottomWidth;
@ -495,7 +495,7 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
width = Core::kScreenTopWidth;
}
// 2.25f comes from HybridScreenLayout's scale_factor value.
width = static_cast<int>((width + (Core::kScreenTopWidth / 2.25f)) * res_scale);
width = static_cast<u32>((width + (Core::kScreenTopWidth / 2.25f)) * res_scale) / 100;
if (Settings::values.upright_screen.GetValue()) {
std::swap(width, height);
@ -506,8 +506,8 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
break;
case Settings::LayoutOption::Default:
default:
width = Core::kScreenTopWidth * res_scale;
height = (Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale + gap;
width = (Core::kScreenTopWidth * res_scale) / 100;
height = ((Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale) / 100 + gap;
if (Settings::values.upright_screen.GetValue()) {
std::swap(width, height);

View file

@ -69,7 +69,7 @@ public:
Common::Rectangle<u32> surfaces_rect)
: res_cache{res_cache_}, fb{fb_} {
const u32 res_scale = fb->Scale();
const u32 height = surfaces_rect.GetHeight() / res_scale;
const u32 height = (surfaces_rect.GetHeight() * 100) / res_scale;
// Determine the draw rectangle (render area + scissor)
Common::Rectangle viewport_rect = regs.GetViewportRect();
@ -77,45 +77,53 @@ public:
viewport_rect = viewport_rect.VerticalMirror(height);
}
draw_rect.left =
std::clamp<s32>(static_cast<s32>(surfaces_rect.left) + viewport_rect.left * res_scale,
surfaces_rect.left, surfaces_rect.right);
draw_rect.top =
std::clamp<s32>(static_cast<s32>(surfaces_rect.bottom) + viewport_rect.top * res_scale,
surfaces_rect.bottom, surfaces_rect.top);
draw_rect.right =
std::clamp<s32>(static_cast<s32>(surfaces_rect.left) + viewport_rect.right * res_scale,
surfaces_rect.left, surfaces_rect.right);
draw_rect.left = std::clamp<s32>(static_cast<s32>(surfaces_rect.left) +
(viewport_rect.left * res_scale) / 100,
surfaces_rect.left, surfaces_rect.right);
draw_rect.top = std::clamp<s32>(static_cast<s32>(surfaces_rect.bottom) +
(viewport_rect.top * res_scale) / 100,
surfaces_rect.bottom, surfaces_rect.top);
draw_rect.right = std::clamp<s32>(static_cast<s32>(surfaces_rect.left) +
(viewport_rect.right * res_scale) / 100,
surfaces_rect.left, surfaces_rect.right);
draw_rect.bottom = std::clamp<s32>(static_cast<s32>(surfaces_rect.bottom) +
viewport_rect.bottom * res_scale,
(viewport_rect.bottom * res_scale) / 100,
surfaces_rect.bottom, surfaces_rect.top);
// Update viewport
viewport.x = static_cast<s32>(surfaces_rect.left) + viewport_rect.left * res_scale;
viewport.y = static_cast<s32>(surfaces_rect.bottom) + viewport_rect.bottom * res_scale;
viewport.width = static_cast<s32>(viewport_rect.GetWidth() * res_scale);
viewport.height = static_cast<s32>(viewport_rect.GetHeight() * res_scale);
viewport.x = static_cast<s32>(surfaces_rect.left) + (viewport_rect.left * res_scale) / 100;
viewport.y =
static_cast<s32>(surfaces_rect.bottom) + (viewport_rect.bottom * res_scale) / 100;
viewport.width = static_cast<s32>((viewport_rect.GetWidth() * res_scale) / 100);
viewport.height = static_cast<s32>((viewport_rect.GetHeight() * res_scale) / 100);
// Scissor checks are window-, not viewport-relative, which means that if the cached texture
// sub-rect changes, the scissor bounds also need to be updated.
scissor_rect.left = static_cast<s32>(surfaces_rect.left + regs.scissor_test.x1 * res_scale);
scissor_rect.left =
static_cast<s32>(surfaces_rect.left + (regs.scissor_test.x1 * res_scale) / 100);
scissor_rect.bottom =
static_cast<s32>(surfaces_rect.bottom + regs.scissor_test.y1 * res_scale);
static_cast<s32>(surfaces_rect.bottom + (regs.scissor_test.y1 * res_scale) / 100);
// x2, y2 have +1 added to cover the entire pixel area, otherwise you might get cracks when
// scaling or doing multisampling.
scissor_rect.right =
static_cast<s32>(surfaces_rect.left + (regs.scissor_test.x2 + 1) * res_scale);
static_cast<s32>(surfaces_rect.left + ((regs.scissor_test.x2 + 1) * res_scale) / 100);
scissor_rect.top =
static_cast<s32>(surfaces_rect.bottom + (regs.scissor_test.y2 + 1) * res_scale);
static_cast<s32>(surfaces_rect.bottom + ((regs.scissor_test.y2 + 1) * res_scale) / 100);
if (flip_rect) {
scissor_rect = scissor_rect.VerticalMirror(height);
scissor_rect = scissor_rect.VerticalMirror(surfaces_rect.GetHeight());
}
}
~FramebufferHelper() {
const Common::Rectangle draw_rect_unscaled{draw_rect / fb->Scale()};
const u32 scale = fb->Scale();
const Common::Rectangle<u32> draw_rect_unscaled{
(draw_rect.left * 100) / scale,
(draw_rect.top * 100) / scale,
(draw_rect.right * 100) / scale,
(draw_rect.bottom * 100) / scale,
};
const auto invalidate = [&](SurfaceId surface_id, u32 level) {
const auto& surface = res_cache->GetSurface(surface_id);
const SurfaceInterval interval = surface.GetSubRectInterval(draw_rect_unscaled, level);

View file

@ -213,7 +213,7 @@ bool RasterizerCache<T>::AccelerateTextureCopy(const Pica::DisplayTransferConfig
const SurfaceParams src_info = slot_surfaces[src_surface_id];
if (output_gap != 0 &&
(output_width != src_info.BytesInPixels(src_rect.GetWidth() / src_info.res_scale) *
(output_width != src_info.BytesInPixels((src_rect.GetWidth() * 100) / src_info.res_scale) *
(src_info.is_tiled ? 8 : 1) ||
output_gap % src_info.BytesInPixels(src_info.is_tiled ? 64 : 1) != 0)) {
return false;
@ -221,10 +221,10 @@ bool RasterizerCache<T>::AccelerateTextureCopy(const Pica::DisplayTransferConfig
SurfaceParams dst_params = src_info;
dst_params.addr = config.GetPhysicalOutputAddress();
dst_params.width = src_rect.GetWidth() / src_info.res_scale;
dst_params.width = (src_rect.GetWidth() * 100) / src_info.res_scale;
dst_params.stride =
dst_params.width + src_info.PixelsInBytes(src_info.is_tiled ? output_gap / 8 : output_gap);
dst_params.height = src_rect.GetHeight() / src_info.res_scale;
dst_params.height = (src_rect.GetHeight() * 100) / src_info.res_scale;
dst_params.res_scale = src_info.res_scale;
dst_params.UpdateParams();
@ -559,7 +559,7 @@ SurfaceId RasterizerCache<T>::GetTextureSurface(const Pica::Texture::TextureInfo
params.levels = max_level + 1;
params.is_tiled = true;
params.pixel_format = PixelFormatFromTextureFormat(info.format);
params.res_scale = filter != Settings::TextureFilter::NoFilter ? resolution_scale_factor : 1;
params.res_scale = filter != Settings::TextureFilter::NoFilter ? resolution_scale_factor : 100;
params.UpdateParams();
const u32 min_width = info.width >> max_level;
@ -616,7 +616,7 @@ typename T::Surface& RasterizerCache<T>::GetTextureCube(const TextureCubeConfig&
};
info.SetDefaultStride();
u32 res_scale = 1;
u32 res_scale = 100;
for (u32 i = 0; i < addresses.size(); i++) {
if (!addresses[i]) {
continue;

View file

@ -1,4 +1,4 @@
// Copyright 2022 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -98,7 +98,9 @@ Common::Rectangle<u32> SurfaceParams::GetSubRect(const SurfaceParams& sub_surfac
}
Common::Rectangle<u32> SurfaceParams::GetScaledSubRect(const SurfaceParams& sub_surface) const {
return GetSubRect(sub_surface) * res_scale;
auto rect = GetSubRect(sub_surface);
return {(rect.left * res_scale) / 100, (rect.top * res_scale) / 100,
(rect.right * res_scale) / 100, (rect.bottom * res_scale) / 100};
}
SurfaceParams SurfaceParams::FromInterval(SurfaceInterval interval) const {

View file

@ -1,4 +1,4 @@
// Copyright 2022 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -64,11 +64,18 @@ public:
}
[[nodiscard]] u32 GetScaledWidth() const noexcept {
return width * res_scale;
return (width * res_scale) / 100;
}
[[nodiscard]] u32 GetScaledHeight() const noexcept {
return height * res_scale;
return (height * res_scale) / 100;
}
/// Returns the scaled version of an unscaled rectangle using the 100-based res_scale
[[nodiscard]] Common::Rectangle<u32> ScaleRect(
const Common::Rectangle<u32>& rect) const noexcept {
return {(rect.left * res_scale) / 100, (rect.top * res_scale) / 100,
(rect.right * res_scale) / 100, (rect.bottom * res_scale) / 100};
}
[[nodiscard]] Common::Rectangle<u32> GetRect(u32 level = 0) const noexcept {
@ -103,7 +110,7 @@ public:
u32 height = 0;
u32 stride = 0;
u32 levels = 1;
u32 res_scale = 1;
u32 res_scale = 100;
bool is_tiled = false;
TextureType texture_type = TextureType::Texture2D;

View file

@ -20,13 +20,13 @@ RendererBase::~RendererBase() = default;
u32 RendererBase::GetResolutionScaleFactor() {
const auto graphics_api = Settings::values.graphics_api.GetValue();
if (graphics_api == Settings::GraphicsAPI::Software) {
// Software renderer always render at native resolution
return 1;
// Software renderer always renders at native resolution
return 100;
}
const u32 scale_factor = Settings::values.resolution_factor.GetValue();
return scale_factor != 0 ? scale_factor
: render_window.GetFramebufferLayout().GetScalingRatio();
: render_window.GetFramebufferLayout().GetScalingRatio() * 100;
}
void RendererBase::UpdateCurrentFramebufferLayout(bool is_portrait_mode) {

View file

@ -265,7 +265,7 @@ void BlitHelper::FilterXbrz(Surface& surface, const VideoCore::TextureBlit& blit
const OpenGLState prev_state = OpenGLState::GetCurState();
SCOPE_EXIT({ prev_state.Apply(); });
state.texture_units[0].texture_2d = surface.Handle(0);
glProgramUniform1f(xbrz_program.handle, 2, static_cast<GLfloat>(surface.res_scale));
glProgramUniform1f(xbrz_program.handle, 2, static_cast<GLfloat>(surface.res_scale) / 100.0f);
SetParams(xbrz_program, surface.RealExtent(false), blit.src_rect);
Draw(xbrz_program, surface.Handle(), draw_fbo.handle, blit.dst_level, blit.dst_rect);
}

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -328,7 +328,7 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& param
texture_type == VideoCore::TextureType::CubeMap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
textures[0] = MakeHandle(target, width, height, levels, tuple, DebugName(false));
if (res_scale != 1) {
if (res_scale != 100) {
textures[1] = MakeHandle(target, GetScaledWidth(), GetScaledHeight(), levels, tuple,
DebugName(true, false));
}
@ -349,7 +349,7 @@ Surface::Surface(TextureRuntime& runtime, const VideoCore::SurfaceBase& surface,
material = mat;
textures[0] = MakeHandle(target, mat->width, mat->height, levels, tuple, DebugName(false));
if (res_scale != 1) {
if (res_scale != 100) {
textures[1] = MakeHandle(target, mat->width, mat->height, levels, DEFAULT_TUPLE,
DebugName(true, true));
}
@ -406,9 +406,9 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload,
.src_level = upload.texture_level,
.dst_level = upload.texture_level,
.src_rect = upload.texture_rect,
.dst_rect = upload.texture_rect * res_scale,
.dst_rect = ScaleRect(upload.texture_rect),
};
if (res_scale != 1 && !runtime->blit_helper.Filter(*this, blit)) {
if (res_scale != 100 && !runtime->blit_helper.Filter(*this, blit)) {
BlitScale(blit, true);
}
}
@ -440,7 +440,7 @@ void Surface::UploadCustom(const VideoCore::Material* material, u32 level) {
.src_rect = filter_rect,
.dst_rect = filter_rect,
};
if (res_scale != 1 && !runtime->blit_helper.Filter(*this, blit)) {
if (res_scale != 100 && !runtime->blit_helper.Filter(*this, blit)) {
BlitScale(blit, true);
}
for (u32 i = 1; i < VideoCore::MAX_MAPS; i++) {
@ -463,11 +463,11 @@ void Surface::Download(const VideoCore::BufferTextureCopy& download,
glPixelStorei(GL_UNPACK_ROW_LENGTH, unscaled_width);
// Scale down upscaled data before downloading it
if (res_scale != 1) {
if (res_scale != 100) {
const VideoCore::TextureBlit blit = {
.src_level = download.texture_level,
.dst_level = download.texture_level,
.src_rect = download.texture_rect * res_scale,
.src_rect = ScaleRect(download.texture_rect),
.dst_rect = download.texture_rect,
};
BlitScale(blit, false);
@ -554,7 +554,7 @@ void Surface::Attach(GLenum target, u32 level, u32 layer, bool scaled) {
}
void Surface::ScaleUp(u32 new_scale) {
if (res_scale == new_scale || new_scale == 1) {
if (res_scale == new_scale || new_scale == 100) {
return;
}
@ -603,7 +603,7 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferParams& params,
const Surface* color, const Surface* depth)
: VideoCore::FramebufferParams{params},
res_scale{color ? color->res_scale : (depth ? depth->res_scale : 1u)} {
res_scale{color ? color->res_scale : (depth ? depth->res_scale : 100u)} {
if (shadow_rendering && !color) {
return;
@ -624,9 +624,9 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferPa
if (shadow_rendering) {
glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH,
color->width * res_scale);
(color->width * res_scale) / 100);
glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT,
color->height * res_scale);
(color->height * res_scale) / 100);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
0);

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -182,7 +182,7 @@ public:
}
private:
u32 res_scale{1};
u32 res_scale{100};
std::array<GLuint, 2> attachments{};
OGLFramebuffer framebuffer;
};

View file

@ -556,10 +556,11 @@ void RendererOpenGL::DrawSingleScreen(const ScreenInfo& screen_info, float x, fl
const u32 scale_factor = GetResolutionScaleFactor();
const GLuint sampler = samplers[Settings::values.filter_mode.GetValue()].handle;
glUniform4f(uniform_i_resolution, static_cast<float>(screen_info.texture.width * scale_factor),
static_cast<float>(screen_info.texture.height * scale_factor),
1.0f / static_cast<float>(screen_info.texture.width * scale_factor),
1.0f / static_cast<float>(screen_info.texture.height * scale_factor));
glUniform4f(uniform_i_resolution,
static_cast<float>((screen_info.texture.width * scale_factor) / 100.0f),
static_cast<float>((screen_info.texture.height * scale_factor) / 100.0f),
100.0f / static_cast<float>(screen_info.texture.width * scale_factor),
100.0f / static_cast<float>(screen_info.texture.height * scale_factor));
glUniform4f(uniform_o_resolution, h, w, 1.0f / h, 1.0f / w);
state.texture_units[0].texture_2d = screen_info.display_texture;
state.texture_units[0].sampler = sampler;
@ -627,10 +628,10 @@ void RendererOpenGL::DrawSingleScreenStereo(const ScreenInfo& screen_info_l,
const u32 scale_factor = GetResolutionScaleFactor();
const GLuint sampler = samplers[Settings::values.filter_mode.GetValue()].handle;
glUniform4f(uniform_i_resolution,
static_cast<float>(screen_info_l.texture.width * scale_factor),
static_cast<float>(screen_info_l.texture.height * scale_factor),
1.0f / static_cast<float>(screen_info_l.texture.width * scale_factor),
1.0f / static_cast<float>(screen_info_l.texture.height * scale_factor));
static_cast<float>((screen_info_l.texture.width * scale_factor) / 100.0f),
static_cast<float>((screen_info_l.texture.height * scale_factor) / 100.0f),
100.0f / static_cast<float>(screen_info_l.texture.width * scale_factor),
100.0f / static_cast<float>(screen_info_l.texture.height * scale_factor));
glUniform4f(uniform_o_resolution, h, w, 1.0f / h, 1.0f / w);
state.texture_units[0].texture_2d = screen_info_l.display_texture;
state.texture_units[1].texture_2d = screen_info_r.display_texture;

View file

@ -783,10 +783,10 @@ void RendererVulkan::DrawSingleScreen(u32 screen_id, float x, float y, float w,
const u32 scale_factor = GetResolutionScaleFactor();
draw_info.i_resolution =
Common::MakeVec(static_cast<f32>(screen_info.texture.width * scale_factor),
static_cast<f32>(screen_info.texture.height * scale_factor),
1.0f / static_cast<f32>(screen_info.texture.width * scale_factor),
1.0f / static_cast<f32>(screen_info.texture.height * scale_factor));
Common::MakeVec(static_cast<f32>((screen_info.texture.width * scale_factor) / 100.0f),
static_cast<f32>((screen_info.texture.height * scale_factor) / 100.0f),
100.0f / static_cast<f32>(screen_info.texture.width * scale_factor),
100.0f / static_cast<f32>(screen_info.texture.height * scale_factor));
draw_info.o_resolution = Common::MakeVec(h, w, 1.0f / h, 1.0f / w);
draw_info.screen_id_l = screen_id;
@ -855,10 +855,10 @@ void RendererVulkan::DrawSingleScreenStereo(u32 screen_id_l, u32 screen_id_r, fl
const u32 scale_factor = GetResolutionScaleFactor();
draw_info.i_resolution =
Common::MakeVec(static_cast<f32>(screen_info_l.texture.width * scale_factor),
static_cast<f32>(screen_info_l.texture.height * scale_factor),
1.0f / static_cast<f32>(screen_info_l.texture.width * scale_factor),
1.0f / static_cast<f32>(screen_info_l.texture.height * scale_factor));
Common::MakeVec(static_cast<f32>((screen_info_l.texture.width * scale_factor) / 100.0f),
static_cast<f32>((screen_info_l.texture.height * scale_factor) / 100.0f),
100.0f / static_cast<f32>(screen_info_l.texture.width * scale_factor),
100.0f / static_cast<f32>(screen_info_l.texture.height * scale_factor));
draw_info.o_resolution = Common::MakeVec(h, w, 1.0f / h, 1.0f / w);
draw_info.screen_id_l = screen_id_l;
draw_info.screen_id_r = screen_id_r;

View file

@ -759,7 +759,7 @@ void BlitHelper::FilterPass(Surface& surface, vk::Pipeline pipeline, vk::Pipelin
},
};
renderpass_cache.BeginRendering(render_pass);
const float src_scale = static_cast<float>(surface.GetResScale());
const float src_scale = static_cast<float>(surface.GetResScale()) / 100.0f;
// Calculate normalized texture coordinates like OpenGL does
const auto src_extent = surface.RealExtent(false); // Get unscaled texture extent
const float tex_scale_x =
@ -845,7 +845,7 @@ void BlitHelper::FilterPassThreeTextures(Surface& surface, vk::Pipeline pipeline
};
renderpass_cache.BeginRendering(render_pass);
const float src_scale = static_cast<float>(surface.GetResScale());
const float src_scale = static_cast<float>(surface.GetResScale()) / 100.0f;
// Calculate normalized texture coordinates like OpenGL does
const auto src_extent = surface.RealExtent(false); // Get unscaled texture extent
const float tex_scale_x =

View file

@ -766,14 +766,14 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& param
traits.aspect, need_format_list, DebugName(false));
raw_images[num_images++] = handles[Type::Base].image;
if (res_scale != 1) {
if (res_scale != 100) {
handles[Type::Scaled].Create(GetScaledWidth(), GetScaledHeight(), levels, texture_type,
format, usage, flags, traits.aspect, need_format_list,
DebugName(true));
raw_images[num_images++] = handles[Type::Scaled].image;
}
current = res_scale != 1 ? Type::Scaled : Type::Base;
current = res_scale != 100 ? Type::Scaled : Type::Base;
runtime.renderpass_cache.EndRendering();
scheduler.Record([raw_images, num_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) {
@ -810,7 +810,7 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceBase& surface
flags, traits.aspect, false, debug_name);
raw_images[num_images++] = handles[Type::Base].image;
if (res_scale != 1) {
if (res_scale != 100) {
handles[Type::Scaled].Create(mat->width, mat->height, levels, texture_type,
vk::Format::eR8G8B8A8Unorm, traits.usage, flags, traits.aspect,
false, debug_name);
@ -822,7 +822,7 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceBase& surface
raw_images[num_images++] = handles[Type::Custom].image;
}
current = res_scale != 1 ? Type::Scaled : Type::Base;
current = res_scale != 100 ? Type::Scaled : Type::Base;
runtime.renderpass_cache.EndRendering();
scheduler.Record([raw_images, num_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) {
@ -910,14 +910,14 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload,
runtime.upload_buffer.Commit(staging.size);
if (res_scale != 1) {
if (res_scale != 100) {
ASSERT_MSG(handles[Type::Scaled], "Scaled allocation missing during upload");
const VideoCore::TextureBlit blit = {
.src_level = upload.texture_level,
.dst_level = upload.texture_level,
.src_rect = upload.texture_rect,
.dst_rect = upload.texture_rect * res_scale,
.dst_rect = ScaleRect(upload.texture_rect),
};
if ((type != SurfaceType::Color && type != SurfaceType::Texture) ||
!runtime.blit_helper.Filter(*this, blit)) {
@ -1013,11 +1013,11 @@ void Surface::Download(const VideoCore::BufferTextureCopy& download,
return;
}
if (res_scale != 1) {
if (res_scale != 100) {
const VideoCore::TextureBlit blit = {
.src_level = download.texture_level,
.dst_level = download.texture_level,
.src_rect = download.texture_rect * res_scale,
.src_rect = ScaleRect(download.texture_rect),
.dst_rect = download.texture_rect,
};
@ -1086,7 +1086,7 @@ void Surface::Download(const VideoCore::BufferTextureCopy& download,
}
void Surface::ScaleUp(u32 new_scale) {
if (res_scale == new_scale || new_scale == 1) {
if (res_scale == new_scale || new_scale == 100) {
return;
}
@ -1437,7 +1437,7 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferParams& params,
Surface* color, Surface* depth)
: VideoCore::FramebufferParams{params}, instance{runtime.GetInstance()},
res_scale{color ? color->res_scale : (depth ? depth->res_scale : 1u)} {
res_scale{color ? color->res_scale : (depth ? depth->res_scale : 100u)} {
auto& renderpass_cache = runtime.GetRenderpassCache();
if (shadow_rendering && !color) {
return;

View file

@ -305,7 +305,7 @@ public:
formats(std::exchange(
other.formats, {VideoCore::PixelFormat::Invalid, VideoCore::PixelFormat::Invalid})),
width(std::exchange(other.width, 0)), height(std::exchange(other.height, 0)),
res_scale(std::exchange(other.res_scale, 1)) {}
res_scale(std::exchange(other.res_scale, 100)) {}
Framebuffer& operator=(Framebuffer&& other) noexcept {
VideoCore::FramebufferParams::operator=(std::move(other));
@ -319,7 +319,7 @@ public:
{VideoCore::PixelFormat::Invalid, VideoCore::PixelFormat::Invalid});
width = std::exchange(other.width, 0);
height = std::exchange(other.height, 0);
res_scale = std::exchange(other.res_scale, 1);
res_scale = std::exchange(other.res_scale, 100);
return *this;
}
@ -364,7 +364,7 @@ private:
VideoCore::PixelFormat::Invalid};
u32 width{};
u32 height{};
u32 res_scale{1};
u32 res_scale{100};
};
class Sampler {