Merge branch 'bmd-scaling' into 'master'

Added support for `Video -> resolution scale = 0.1 to 1.0`.

See merge request OpenMW/openmw!4896
This commit is contained in:
bmdhacks 2025-09-20 14:43:43 +00:00
commit 0f2a79fa79
15 changed files with 138 additions and 19 deletions

View File

@ -96,6 +96,10 @@ namespace MWLua
api["setViewDistance"] api["setViewDistance"]
= [renderingManager](const FiniteFloat d) { renderingManager->setViewDistance(d, true); }; = [renderingManager](const FiniteFloat d) { renderingManager->setViewDistance(d, true); };
api["getResolutionScale"] = [renderingManager]() { return renderingManager->getResolutionScale(); };
api["setResolutionScale"]
= [renderingManager](const FiniteFloat scale) { renderingManager->setResolutionScale(scale); };
api["getViewTransform"] = [camera]() { return LuaUtil::TransformM{ camera->getViewMatrix() }; }; api["getViewTransform"] = [camera]() { return LuaUtil::TransformM{ camera->getViewMatrix() }; };
api["viewportToWorldVector"] = [camera, renderingManager](osg::Vec2f pos) -> osg::Vec3f { api["viewportToWorldVector"] = [camera, renderingManager](osg::Vec2f pos) -> osg::Vec3f {

View File

@ -125,6 +125,8 @@ namespace MWRender
, mSamples(Settings::video().mAntialiasing) , mSamples(Settings::video().mAntialiasing)
, mPingPongCull(new PingPongCull(this)) , mPingPongCull(new PingPongCull(this))
, mDistortionCallback(new DistortionCallback) , mDistortionCallback(new DistortionCallback)
, mScaledViewportStateSet(new osg::StateSet)
, mScaledViewport(new osg::Viewport)
{ {
auto& shaderManager = mRendering.getResourceSystem()->getSceneManager()->getShaderManager(); auto& shaderManager = mRendering.getResourceSystem()->getSceneManager()->getShaderManager();
@ -148,6 +150,8 @@ namespace MWRender
mHUDCamera->setCullCallback(new HUDCullCallback); mHUDCamera->setCullCallback(new HUDCullCallback);
mViewer->getCamera()->addCullCallback(mPingPongCull); mViewer->getCamera()->addCullCallback(mPingPongCull);
mScaledViewportStateSet->setAttribute(mScaledViewport);
// resolves the multisampled depth buffer and optionally draws an additional depth postpass // resolves the multisampled depth buffer and optionally draws an additional depth postpass
mTransparentDepthPostPass mTransparentDepthPostPass
= new TransparentDepthBinCallback(mRendering.getResourceSystem()->getSceneManager()->getShaderManager(), = new TransparentDepthBinCallback(mRendering.getResourceSystem()->getSceneManager()->getShaderManager(),
@ -276,13 +280,26 @@ namespace MWRender
void PostProcessor::traverse(osg::NodeVisitor& nv) void PostProcessor::traverse(osg::NodeVisitor& nv)
{ {
size_t frameId = nv.getTraversalNumber() % 2; size_t frameId = nv.getTraversalNumber() % 2;
bool pushedStateSet = false;
if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR)
cull(frameId, static_cast<osgUtil::CullVisitor*>(&nv)); {
osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(&nv);
mScaledViewport->setViewport(0, 0, renderWidth(), renderHeight());
cv->pushStateSet(mScaledViewportStateSet.get());
pushedStateSet = true;
cull(frameId, cv);
}
else if (nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR) else if (nv.getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR)
update(frameId); update(frameId);
osg::Group::traverse(nv); osg::Group::traverse(nv);
if (pushedStateSet)
{
static_cast<osgUtil::CullVisitor*>(&nv)->popStateSet();
}
} }
void PostProcessor::cull(size_t frameId, osgUtil::CullVisitor* cv) void PostProcessor::cull(size_t frameId, osgUtil::CullVisitor* cv)
@ -567,8 +584,11 @@ namespace MWRender
std::vector<Fx::Types::RenderTarget> attachmentsToDirty; std::vector<Fx::Types::RenderTarget> attachmentsToDirty;
for (const auto& technique : mTechniques) // Filter techniques to only include valid ones
std::vector<std::shared_ptr<Fx::Technique>> validTechniques;
for (auto techIt = mTechniques.begin(); techIt != mTechniques.end(); ++techIt)
{ {
const auto& technique = *techIt;
if (!technique->isValid()) if (!technique->isValid())
continue; continue;
@ -578,6 +598,12 @@ namespace MWRender
<< technique->getGLSLVersion() << " which is unsupported by your hardware."; << technique->getGLSLVersion() << " which is unsupported by your hardware.";
continue; continue;
} }
validTechniques.push_back(technique);
}
for (size_t techIdx = 0; techIdx < validTechniques.size(); ++techIdx)
{
const auto& technique = validTechniques[techIdx];
Fx::DispatchNode node; Fx::DispatchNode node;
@ -635,8 +661,10 @@ namespace MWRender
uniform->mName.c_str(), *type, uniform->getNumElements())); uniform->mName.c_str(), *type, uniform->getNumElements()));
} }
for (const auto& pass : technique->getPasses()) const auto& passes = technique->getPasses();
for (size_t passIdx = 0; passIdx < passes.size(); ++passIdx)
{ {
const auto& pass = passes[passIdx];
int subTexUnit = texUnit; int subTexUnit = texUnit;
Fx::DispatchNode::SubPass subPass; Fx::DispatchNode::SubPass subPass;
@ -644,6 +672,8 @@ namespace MWRender
node.mHandle = technique; node.mHandle = technique;
bool isFinalPass = (techIdx == validTechniques.size() - 1 && passIdx == passes.size() - 1);
if (!pass->getTarget().empty()) if (!pass->getTarget().empty())
{ {
auto& renderTarget = technique->getRenderTargetsMap()[pass->getTarget()]; auto& renderTarget = technique->getRenderTargetsMap()[pass->getTarget()];
@ -676,6 +706,10 @@ namespace MWRender
attachmentsToDirty.push_back(Fx::Types::RenderTarget(renderTarget)); attachmentsToDirty.push_back(Fx::Types::RenderTarget(renderTarget));
} }
} }
else if (!isFinalPass)
{
subPass.mStateSet->setAttribute(new osg::Viewport(0, 0, renderWidth(), renderHeight()));
}
for (const auto& name : pass->getRenderTargets()) for (const auto& name : pass->getRenderTargets())
{ {
@ -858,16 +892,16 @@ namespace MWRender
int PostProcessor::renderWidth() const int PostProcessor::renderWidth() const
{ {
if (Stereo::getStereo()) float scale = static_cast<float>(Settings::video().mResolutionScale);
return Stereo::Manager::instance().eyeResolution().x(); int baseWidth = Stereo::getStereo() ? Stereo::Manager::instance().eyeResolution().x() : mWidth;
return mWidth; return std::max(1, static_cast<int>(baseWidth * scale));
} }
int PostProcessor::renderHeight() const int PostProcessor::renderHeight() const
{ {
if (Stereo::getStereo()) float scale = static_cast<float>(Settings::video().mResolutionScale);
return Stereo::Manager::instance().eyeResolution().y(); int baseHeight = Stereo::getStereo() ? Stereo::Manager::instance().eyeResolution().y() : mHeight;
return mHeight; return std::max(1, static_cast<int>(baseHeight * scale));
} }
void PostProcessor::triggerShaderReload() void PostProcessor::triggerShaderReload()

View File

@ -268,6 +268,8 @@ namespace MWRender
std::array<osg::ref_ptr<PingPongCanvas>, 2> mCanvases; std::array<osg::ref_ptr<PingPongCanvas>, 2> mCanvases;
osg::ref_ptr<TransparentDepthBinCallback> mTransparentDepthPostPass; osg::ref_ptr<TransparentDepthBinCallback> mTransparentDepthPostPass;
osg::ref_ptr<DistortionCallback> mDistortionCallback; osg::ref_ptr<DistortionCallback> mDistortionCallback;
osg::ref_ptr<osg::StateSet> mScaledViewportStateSet;
osg::ref_ptr<osg::Viewport> mScaledViewport;
Fx::DispatchArray mTemplateData; Fx::DispatchArray mTemplateData;
}; };

View File

@ -1,5 +1,6 @@
#include "renderingmanager.hpp" #include "renderingmanager.hpp"
#include <algorithm>
#include <cstdlib> #include <cstdlib>
#include <limits> #include <limits>
@ -1382,15 +1383,18 @@ namespace MWRender
mSharedUniformStateUpdater->setNear(mNearClip); mSharedUniformStateUpdater->setNear(mNearClip);
mSharedUniformStateUpdater->setFar(mViewDistance); mSharedUniformStateUpdater->setFar(mViewDistance);
float scale = static_cast<float>(Settings::video().mResolutionScale);
if (Stereo::getStereo()) if (Stereo::getStereo())
{ {
auto res = Stereo::Manager::instance().eyeResolution(); auto res = Stereo::Manager::instance().eyeResolution();
mSharedUniformStateUpdater->setScreenRes(res.x(), res.y()); mSharedUniformStateUpdater->setScreenRes(res.x() * scale, res.y() * scale);
Stereo::Manager::instance().setMasterProjectionMatrix(mPerViewUniformStateUpdater->getProjectionMatrix()); Stereo::Manager::instance().setMasterProjectionMatrix(mPerViewUniformStateUpdater->getProjectionMatrix());
} }
else else
{ {
mSharedUniformStateUpdater->setScreenRes(width, height); mSharedUniformStateUpdater->setScreenRes(width * scale, height * scale);
} }
// Since our fog is not radial yet, we should take FOV in account, otherwise terrain near viewing distance may // Since our fog is not radial yet, we should take FOV in account, otherwise terrain near viewing distance may
@ -1520,6 +1524,10 @@ namespace MWRender
{ {
updateProjection = true; updateProjection = true;
} }
else if (it->first == "Video" && it->second == "resolution scale")
{
mPostProcessor->resize();
}
else if (it->first == "Camera" && it->second == "viewing distance") else if (it->first == "Camera" && it->second == "viewing distance")
{ {
setViewDistance(Settings::camera().mViewingDistance); setViewDistance(Settings::camera().mViewingDistance);
@ -1610,6 +1618,22 @@ namespace MWRender
updateProjectionMatrix(); updateProjectionMatrix();
} }
float RenderingManager::getResolutionScale() const
{
return Settings::video().mResolutionScale;
}
void RenderingManager::setResolutionScale(float scale)
{
scale = std::clamp(scale, 0.1f, 1.0f);
Settings::video().mResolutionScale.set(scale);
if (mPostProcessor)
{
mPostProcessor->resize();
}
}
float RenderingManager::getTerrainHeightAt(const osg::Vec3f& pos, ESM::RefId worldspace) float RenderingManager::getTerrainHeightAt(const osg::Vec3f& pos, ESM::RefId worldspace)
{ {
return getWorldspaceChunkMgr(worldspace).mTerrain->getHeightAt(pos); return getWorldspaceChunkMgr(worldspace).mTerrain->getHeightAt(pos);

View File

@ -230,6 +230,9 @@ namespace MWRender
void setViewDistance(float distance, bool delay = false); void setViewDistance(float distance, bool delay = false);
float getResolutionScale() const;
void setResolutionScale(float scale);
float getTerrainHeightAt(const osg::Vec3f& pos, ESM::RefId worldspace); float getTerrainHeightAt(const osg::Vec3f& pos, ESM::RefId worldspace);
// camera stuff // camera stuff

View File

@ -22,6 +22,8 @@ namespace Settings
SettingValue<int> mResolutionX{ mIndex, "Video", "resolution x", makeMaxSanitizerInt(1) }; SettingValue<int> mResolutionX{ mIndex, "Video", "resolution x", makeMaxSanitizerInt(1) };
SettingValue<int> mResolutionY{ mIndex, "Video", "resolution y", makeMaxSanitizerInt(1) }; SettingValue<int> mResolutionY{ mIndex, "Video", "resolution y", makeMaxSanitizerInt(1) };
SettingValue<float> mResolutionScale{ mIndex, "Video", "resolution scale",
makeClampStrictMaxSanitizerFloat(0.1, 1.0) };
SettingValue<WindowMode> mWindowMode{ mIndex, "Video", "window mode" }; SettingValue<WindowMode> mWindowMode{ mIndex, "Video", "window mode" };
SettingValue<int> mScreen{ mIndex, "Video", "screen", makeMaxSanitizerInt(0) }; SettingValue<int> mScreen{ mIndex, "Video", "screen", makeMaxSanitizerInt(0) };
SettingValue<bool> mMinimizeOnFocusLoss{ mIndex, "Video", "minimize on focus loss" }; SettingValue<bool> mMinimizeOnFocusLoss{ mIndex, "Video", "minimize on focus loss" };

View File

@ -23,6 +23,23 @@ Video Settings
Larger values produce more detailed images within the constraints of your graphics hardware, Larger values produce more detailed images within the constraints of your graphics hardware,
but may reduce the frame rate. but may reduce the frame rate.
.. omw-setting::
:title: resolution scale
:type: float
:range: 0.1 to 1.0
:default: 1.0
:location: :bdg-info:`In Game > Options > Video`
This setting controls the internal rendering resolution as a percentage of the window resolution.
Values below 1.0 render the game at a lower resolution and upscale to the window size.
For example, 0.5 on a 2560x1440 display would render at 1280x720 internally,
providing substantial performance improvements on GPU-limited systems while maintaining UI elements at full resolution.
.. note::
This setting is primarily intended for low-powered devices with high-DPI displays
(such as retro handhelds or low-powered Android phones). On desktop systems with adequate GPUs,
this should typically remain at 1.0 for optimal image quality.
.. omw-setting:: .. omw-setting::
:title: window mode :title: window mode
:type: int :type: int

View File

@ -176,6 +176,7 @@ ReflectionShaderDetailTerrain: "Terrain"
ReflectionShaderDetailWorld: "Welt" ReflectionShaderDetailWorld: "Welt"
Refraction: "Lichtbrechung" Refraction: "Lichtbrechung"
ResetControls: "Tastenbelegungen zurücksetzen" ResetControls: "Tastenbelegungen zurücksetzen"
ResolutionScale: "Auflösungsskalierung"
Screenshot: "Screenshot" Screenshot: "Screenshot"
Scripts: "Skripte" Scripts: "Skripte"
ScriptsDisabled: "Laden Sie einen Spielstand, um auf die Skripteinstellungen zugreifen zu können." ScriptsDisabled: "Laden Sie einen Spielstand, um auf die Skripteinstellungen zugreifen zu können."

View File

@ -176,6 +176,7 @@ ReflectionShaderDetailTerrain: "Terrain"
ReflectionShaderDetailWorld: "World" ReflectionShaderDetailWorld: "World"
Refraction: "Refraction" Refraction: "Refraction"
ResetControls: "Reset Controls" ResetControls: "Reset Controls"
ResolutionScale: "Resolution Scale"
Screenshot: "Screenshot" Screenshot: "Screenshot"
Scripts: "Scripts" Scripts: "Scripts"
ScriptsDisabled: "Load a game to access script settings." ScriptsDisabled: "Load a game to access script settings."

View File

@ -176,6 +176,7 @@ ReflectionShaderDetailTerrain: "Terrain"
ReflectionShaderDetailWorld: "Monde" ReflectionShaderDetailWorld: "Monde"
Refraction: "Réfraction" Refraction: "Réfraction"
ResetControls: "Réinitialiser les contrôles" ResetControls: "Réinitialiser les contrôles"
ResolutionScale: "Échelle de résolution"
Screenshot: "Capture d'écran" Screenshot: "Capture d'écran"
Scripts: "Scripts" Scripts: "Scripts"
ScriptsDisabled: "Chargez une sauvegarde pour accéder aux paramètres des scripts." ScriptsDisabled: "Chargez une sauvegarde pour accéder aux paramètres des scripts."

View File

@ -176,6 +176,7 @@ ReflectionShaderDetailTerrain: "Teren"
ReflectionShaderDetailWorld: "Świat" ReflectionShaderDetailWorld: "Świat"
Refraction: "Załamanie światła" Refraction: "Załamanie światła"
ResetControls: "Przywróć sterowanie" ResetControls: "Przywróć sterowanie"
ResolutionScale: "Skalowanie rozdzielczości"
Screenshot: "Zrzut ekranu" Screenshot: "Zrzut ekranu"
Scripts: "Skrypty" Scripts: "Skrypty"
ScriptsDisabled: "Wczytaj grę, aby uzyskać dostęp do ustawień skryptów." ScriptsDisabled: "Wczytaj grę, aby uzyskać dostęp do ustawień skryptów."

View File

@ -176,6 +176,7 @@ ReflectionShaderDetailTerrain: "Ландшафт"
ReflectionShaderDetailWorld: "Мир" ReflectionShaderDetailWorld: "Мир"
Refraction: "Рефракция" Refraction: "Рефракция"
ResetControls: "Сбросить" ResetControls: "Сбросить"
ResolutionScale: "Масштаб разрешения"
Screenshot: "Снимок экрана" Screenshot: "Снимок экрана"
Scripts: "Скрипты" Scripts: "Скрипты"
ScriptsDisabled: "Загрузите игру, чтобы получить доступ к настройкам скриптов." ScriptsDisabled: "Загрузите игру, чтобы получить доступ к настройкам скриптов."

View File

@ -177,6 +177,7 @@ ReflectionShaderDetailTerrain: "Terräng"
ReflectionShaderDetailWorld: "Värld" ReflectionShaderDetailWorld: "Värld"
Refraction: "Refraktion" Refraction: "Refraktion"
ResetControls: "Återställ kontroller" ResetControls: "Återställ kontroller"
ResolutionScale: "Upplösningsskala"
Screenshot: "Skärmdump" Screenshot: "Skärmdump"
Scripts: "Skript" Scripts: "Skript"
ScriptsDisabled: "Ladda ett spel för att nå skriptinställningar." ScriptsDisabled: "Ladda ett spel för att nå skriptinställningar."

View File

@ -327,9 +327,33 @@
<Property key="Caption" value="#{OMWEngine:WindowModeHint}"/> <Property key="Caption" value="#{OMWEngine:WindowModeHint}"/>
</Widget> </Widget>
<Widget type="TextBox" skin="NormalText" position="0 238 352 18" align="Left Top" name="FovText"> <Widget type="TextBox" skin="NormalText" position="0 258 352 18" align="Left Top" name="ResolutionScaleText">
<Property key="Caption" value="#{OMWEngine:ResolutionScale}"/>
</Widget> </Widget>
<Widget type="ScrollBar" skin="MW_HScroll" position="0 262 352 18" align="HStretch Top"> <Widget type="ScrollBar" skin="MW_HScroll" position="0 282 352 18" align="HStretch Top">
<Property key="Range" value="10000"/>
<Property key="Page" value="100"/>
<UserString key="SettingType" value="Slider"/>
<UserString key="SettingCategory" value="Video"/>
<UserString key="SettingName" value="resolution scale"/>
<UserString key="SettingValueType" value="Float"/>
<UserString key="SettingMin" value="0.1"/>
<UserString key="SettingMax" value="1.0"/>
<UserString key="SettingLabelWidget" value="ResolutionScaleText"/>
<UserString key="SettingLabelCaption" value="#{OMWEngine:ResolutionScale} (%s)"/>
</Widget>
<Widget type="TextBox" skin="SandText" position="0 306 352 18" align="Left Top">
<Property key="Caption" value="10%"/>
<Property key="TextAlign" value="Left"/>
</Widget>
<Widget type="TextBox" skin="SandText" position="0 306 352 18" align="Right Top">
<Property key="Caption" value="100%"/>
<Property key="TextAlign" value="Right"/>
</Widget>
<Widget type="TextBox" skin="NormalText" position="0 334 352 18" align="Left Top" name="FovText">
</Widget>
<Widget type="ScrollBar" skin="MW_HScroll" position="0 358 352 18" align="HStretch Top">
<Property key="Range" value="81"/> <Property key="Range" value="81"/>
<Property key="Page" value="1"/> <Property key="Page" value="1"/>
<UserString key="SettingType" value="Slider"/> <UserString key="SettingType" value="Slider"/>
@ -341,18 +365,18 @@
<UserString key="SettingLabelWidget" value="FovText"/> <UserString key="SettingLabelWidget" value="FovText"/>
<UserString key="SettingLabelCaption" value="#{OMWEngine:FieldOfView} (%s)"/> <UserString key="SettingLabelCaption" value="#{OMWEngine:FieldOfView} (%s)"/>
</Widget> </Widget>
<Widget type="TextBox" skin="SandText" position="0 286 352 18" align="Left Top"> <Widget type="TextBox" skin="SandText" position="0 382 352 18" align="Left Top">
<Property key="Caption" value="#{OMWEngine:FieldOfViewLow}"/> <Property key="Caption" value="#{OMWEngine:FieldOfViewLow}"/>
<Property key="TextAlign" value="Left"/> <Property key="TextAlign" value="Left"/>
</Widget> </Widget>
<Widget type="TextBox" skin="SandText" position="0 286 352 18" align="Right Top"> <Widget type="TextBox" skin="SandText" position="0 382 352 18" align="Right Top">
<Property key="Caption" value="#{OMWEngine:FieldOfViewHigh}"/> <Property key="Caption" value="#{OMWEngine:FieldOfViewHigh}"/>
<Property key="TextAlign" value="Right"/> <Property key="TextAlign" value="Right"/>
</Widget> </Widget>
<Widget type="TextBox" skin="NormalText" position="0 308 352 18" align="Left Top" name="GammaText"> <Widget type="TextBox" skin="NormalText" position="0 404 352 18" align="Left Top" name="GammaText">
<Property key="Caption" value="#{OMWEngine:GammaCorrection}"/> <Property key="Caption" value="#{OMWEngine:GammaCorrection}"/>
</Widget> </Widget>
<Widget type="ScrollBar" skin="MW_HScroll" position="0 332 352 18" align="HStretch Top" name="GammaSlider"> <Widget type="ScrollBar" skin="MW_HScroll" position="0 428 352 18" align="HStretch Top" name="GammaSlider">
<Property key="Range" value="10000"/> <Property key="Range" value="10000"/>
<Property key="Page" value="300"/> <Property key="Page" value="300"/>
<UserString key="SettingType" value="Slider"/> <UserString key="SettingType" value="Slider"/>
@ -362,11 +386,11 @@
<UserString key="SettingMin" value="0.1"/> <UserString key="SettingMin" value="0.1"/>
<UserString key="SettingMax" value="3.0"/> <UserString key="SettingMax" value="3.0"/>
</Widget> </Widget>
<Widget type="TextBox" skin="SandText" position="0 356 352 18" align="Left Top" name="GammaTextDark"> <Widget type="TextBox" skin="SandText" position="0 452 352 18" align="Left Top" name="GammaTextDark">
<Property key="Caption" value="#{OMWEngine:GammaDark}"/> <Property key="Caption" value="#{OMWEngine:GammaDark}"/>
<Property key="TextAlign" value="Left"/> <Property key="TextAlign" value="Left"/>
</Widget> </Widget>
<Widget type="TextBox" skin="SandText" position="0 356 352 18" align="Right Top" name="GammaTextLight"> <Widget type="TextBox" skin="SandText" position="0 452 352 18" align="Right Top" name="GammaTextLight">
<Property key="Caption" value="#{OMWEngine:GammaLight}"/> <Property key="Caption" value="#{OMWEngine:GammaLight}"/>
<Property key="TextAlign" value="Right"/> <Property key="TextAlign" value="Right"/>
</Widget> </Widget>

View File

@ -639,6 +639,9 @@ doppler factor = 0.25
resolution x = 800 resolution x = 800
resolution y = 600 resolution y = 600
# Resolution scaling
resolution scale = 1.0
# Specify the window mode. # Specify the window mode.
# 0 = Fullscreen, 1 = Windowed Fullscreen, 2 = Windowed # 0 = Fullscreen, 1 = Windowed Fullscreen, 2 = Windowed
window mode = 2 window mode = 2