Integer Scaling + Screen-Level Linear Interpolation Options (#1976)

This commit is contained in:
xander-will 2025-06-13 05:59:15 -04:00 committed by GitHub
parent 7e7f645f37
commit 71341a27e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 60 additions and 41 deletions

View File

@ -99,6 +99,8 @@ Option<int> SkipFrame("ta.skip");
Option<int> MaxThreads("pvr.MaxThreads", 3);
Option<int> AutoSkipFrame("pvr.AutoSkipFrame", 0);
Option<int> RenderResolution("rend.Resolution", 480);
Option<bool> IntegerScale("rend.IntegerScale", false);
Option<bool> LinearInterpolation("rend.LinearInterpolation", true);
Option<bool> VSync("rend.vsync", true);
Option<int64_t> PixelBufferSize("rend.PixelBufferSize", 512_MB);
Option<int> AnisotropicFiltering("rend.AnisotropicFiltering", 1);

View File

@ -460,6 +460,8 @@ extern Option<int> SkipFrame;
extern Option<int> MaxThreads;
extern Option<int> AutoSkipFrame; // 0: none, 1: some, 2: more
extern Option<int> RenderResolution;
extern Option<bool> IntegerScale;
extern Option<bool> LinearInterpolation;
extern Option<bool> VSync;
extern Option<int64_t> PixelBufferSize;
extern Option<int> AnisotropicFiltering;

View File

@ -559,14 +559,12 @@ void DX11Renderer::displayFramebuffer()
std::swap(shiftX, shiftY);
renderAR = 1 / renderAR;
}
float screenAR = (float)outwidth / outheight;
int dy = 0;
int dx = 0;
if (renderAR > screenAR)
dy = (int)roundf(outheight * (1 - screenAR / renderAR) / 2.f);
else
dx = (int)roundf(outwidth * (1 - renderAR / screenAR) / 2.f);
// handles the rotation on its own, so never pass config::Rotate90
getWindowboxDimensions(outwidth, outheight, renderAR, dx, dy, false);
float x = (float)dx;
float y = (float)dy;
float w = (float)(outwidth - 2 * dx);
@ -581,7 +579,7 @@ void DX11Renderer::displayFramebuffer()
x += shiftX;
y += shiftY;
deviceContext->OMSetBlendState(blendStates.getState(false), nullptr, 0xffffffff);
quad->draw(fbTextureView, samplers->getSampler(config::TextureFiltering != 1), nullptr, x, y, w, h, config::Rotate90);
quad->draw(fbTextureView, samplers->getSampler(config::LinearInterpolation), nullptr, x, y, w, h, config::Rotate90);
#endif
}

View File

@ -263,7 +263,7 @@ void D3DRenderer::RenderFramebuffer(const FramebufferInfo& info)
{
ReadFramebuffer<BGRAPacker>(info, pb, width, height);
}
if (dcfbTexture)
{
D3DSURFACE_DESC desc;
@ -1216,13 +1216,10 @@ void D3DRenderer::displayFramebuffer()
{
devCache.SetRenderState(D3DRS_SCISSORTESTENABLE, FALSE);
device->ColorFill(backbuffer, 0, D3DCOLOR_ARGB(255, VO_BORDER_COL._red, VO_BORDER_COL._green, VO_BORDER_COL._blue));
float screenAR = (float)settings.display.width / settings.display.height;
int dx = 0;
int dy = 0;
if (aspectRatio > screenAR)
dy = (int)roundf(settings.display.height * (1 - screenAR / aspectRatio) / 2.f);
else
dx = (int)roundf(settings.display.width * (1 - aspectRatio / screenAR) / 2.f);
getWindowboxDimensions(settings.display.width, settings.display.height, aspectRatio, dx, dy, config::Rotate90);
float shiftX, shiftY;
getVideoShift(shiftX, shiftY);
@ -1231,7 +1228,7 @@ void D3DRenderer::displayFramebuffer()
RECT rs { 0, 0, (long)width, (long)height };
RECT rd { dx, dy, settings.display.width - dx, settings.display.height - dy };
device->StretchRect(framebufferSurface, &rs, backbuffer, &rd,
config::TextureFiltering == 1 ? D3DTEXF_POINT : D3DTEXF_LINEAR); // This can fail if window is minimized
config::LinearInterpolation ? D3DTEXF_LINEAR : D3DTEXF_POINT); // This can fail if window is minimized
}
else
{
@ -1241,8 +1238,8 @@ void D3DRenderer::displayFramebuffer()
device->SetRenderState(D3DRS_ZENABLE, FALSE);
device->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
device->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
device->SetSamplerState(0, D3DSAMP_MINFILTER, config::TextureFiltering == 1 ? D3DTEXF_POINT : D3DTEXF_LINEAR);
device->SetSamplerState(0, D3DSAMP_MAGFILTER, config::TextureFiltering == 1 ? D3DTEXF_POINT : D3DTEXF_LINEAR);
device->SetSamplerState(0, D3DSAMP_MINFILTER, config::LinearInterpolation ? D3DTEXF_LINEAR : D3DTEXF_POINT);
device->SetSamplerState(0, D3DSAMP_MAGFILTER, config::LinearInterpolation ? D3DTEXF_LINEAR : D3DTEXF_POINT);
glm::mat4 identity = glm::identity<glm::mat4>();
glm::mat4 projection = glm::translate(glm::vec3(-1.f / settings.display.width, 1.f / settings.display.height, 0));

View File

@ -777,17 +777,11 @@ bool OpenGLRenderer::renderLastFrame()
GlFramebuffer *framebuffer = gl.ofbo2.ready ? gl.ofbo2.framebuffer.get() : gl.ofbo.framebuffer.get();
if (framebuffer == nullptr)
return false;
glcache.Disable(GL_SCISSOR_TEST);
float screenAR = (float)settings.display.width / settings.display.height;
float renderAR = gl.ofbo.aspectRatio;
int dx = 0;
int dy = 0;
if (renderAR > screenAR)
dy = (int)roundf(settings.display.height * (1 - screenAR / renderAR) / 2.f);
else
dx = (int)roundf(settings.display.width * (1 - renderAR / screenAR) / 2.f);
glcache.Disable(GL_SCISSOR_TEST);
getWindowboxDimensions(settings.display.width, settings.display.height, gl.ofbo.aspectRatio, dx, dy, config::Rotate90);
if (gl.bogusBlitFramebuffer || config::Rotate90)
{
@ -795,8 +789,8 @@ bool OpenGLRenderer::renderLastFrame()
glBindFramebuffer(GL_FRAMEBUFFER, gl.ofbo.origFbo);
glcache.ClearColor(VO_BORDER_COL.red(), VO_BORDER_COL.green(), VO_BORDER_COL.blue(), 1.f);
glClear(GL_COLOR_BUFFER_BIT);
glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, config::TextureFiltering == 1 ? GL_NEAREST : GL_LINEAR);
glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, config::TextureFiltering == 1 ? GL_NEAREST : GL_LINEAR);
glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, config::LinearInterpolation ? GL_LINEAR : GL_NEAREST);
glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, config::LinearInterpolation ? GL_LINEAR : GL_NEAREST);
float *vertices = nullptr;
if (gl.ofbo.shiftX != 0 || gl.ofbo.shiftY != 0)
{
@ -824,7 +818,7 @@ bool OpenGLRenderer::renderLastFrame()
glClear(GL_COLOR_BUFFER_BIT);
glBlitFramebuffer(-gl.ofbo.shiftX, -gl.ofbo.shiftY, framebuffer->getWidth() - gl.ofbo.shiftX, framebuffer->getHeight() - gl.ofbo.shiftY,
dx, settings.display.height - dy, settings.display.width - dx, dy,
GL_COLOR_BUFFER_BIT, config::TextureFiltering == 1 ? GL_NEAREST : GL_LINEAR);
GL_COLOR_BUFFER_BIT, config::LinearInterpolation ? GL_LINEAR : GL_NEAREST);
glBindFramebuffer(GL_FRAMEBUFFER, gl.ofbo.origFbo);
#endif
}

View File

@ -284,6 +284,33 @@ inline static float getDCFramebufferAspectRatio()
return aspectRatio * config::ScreenStretching / 100.f;
}
inline static void getWindowboxDimensions(int outwidth, int outheight, float renderAR, int& dx, int& dy, bool rotate) {
if (config::IntegerScale) {
int fbh = config::RenderResolution;
int fbw = (int)((rotate ? 1 / renderAR : renderAR) * fbh);
if (rotate)
std::swap(fbw, fbh);
int scale = std::min(outwidth / fbw, outheight / fbh);
if (scale == 0) {
scale = std::max(fbw / outwidth, fbh / outheight) + 1;
dx = (outwidth - fbw / scale) / 2;
dy = (outheight - fbh / scale) / 2;
}
else {
dx = (outwidth - fbw * scale) / 2;
dy = (outheight - fbh * scale) / 2;
}
}
else {
float screenAR = (float)outwidth / outheight;
if (renderAR > screenAR)
dy = (int)roundf(outheight * (1 - screenAR / renderAR) / 2.f);
else
dx = (int)roundf(outwidth * (1 - renderAR / screenAR) / 2.f);
}
}
inline static void getVideoShift(float& x, float& y)
{
const bool vga = FB_R_CTRL.vclk_div == 1;

View File

@ -1007,21 +1007,17 @@ void VulkanContext::DrawFrame(vk::ImageView imageView, const vk::Extent2D& exten
else
quadPipeline->BindPipeline(commandBuffer);
float screenAR = (float)width / height;
float dx = 0;
float dy = 0;
if (aspectRatio > screenAR)
dy = height * (1 - screenAR / aspectRatio) / 2;
else
dx = width * (1 - aspectRatio / screenAR) / 2;
int dx = 0;
int dy = 0;
getWindowboxDimensions(width, height, aspectRatio, dx, dy, config::Rotate90);
vk::Viewport viewport(dx, dy, width - dx * 2, height - dy * 2);
commandBuffer.setViewport(0, viewport);
commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(dx, dy), vk::Extent2D(width - dx * 2, height - dy * 2)));
if (config::Rotate90)
quadRotateDrawer->Draw(commandBuffer, imageView, vtx, config::TextureFiltering == 1);
quadRotateDrawer->Draw(commandBuffer, imageView, vtx, !config::LinearInterpolation);
else
quadDrawer->Draw(commandBuffer, imageView, vtx, config::TextureFiltering == 1);
quadDrawer->Draw(commandBuffer, imageView, vtx, !config::LinearInterpolation);
}
void VulkanContext::WaitIdle() const

View File

@ -2827,7 +2827,8 @@ static void gui_settings_video()
ImGui::Text("Internal Resolution");
ImGui::SameLine();
ShowHelpMarker("Internal render resolution. Higher is better, but more demanding on the GPU. Values higher than your display resolution (but no more than double your display resolution) can be used for supersampling, which provides high-quality antialiasing without reducing sharpness.");
OptionCheckbox("Integer Scaling", config::IntegerScale, "Scales the output by the maximum integer multiple allowed by the display resolution.");
OptionCheckbox("Linear Interpolation", config::LinearInterpolation, "Scales the output with linear interpolation. Will use nearest neighbor interpolation otherwise. Disable with integer scaling.");
#ifndef TARGET_IPHONE
OptionCheckbox("VSync", config::VSync, "Synchronizes the frame rate with the screen refresh rate. Recommended");
if (isVulkan(config::RendererType))
@ -2854,11 +2855,11 @@ static void gui_settings_video()
OptionCheckbox("Widescreen", config::Widescreen,
"Draw geometry outside of the normal 4:3 aspect ratio. May produce graphical glitches in the revealed areas.\nAspect Fit and shows the full 16:9 content.");
{
DisabledScope scope(!config::Widescreen);
DisabledScope scope(!config::Widescreen || config::IntegerScale);
ImGui::Indent();
OptionCheckbox("Super Widescreen", config::SuperWidescreen,
"Use the full width of the screen or window when its aspect ratio is greater than 16:9.\nAspect Fill and remove black bars.");
"Use the full width of the screen or window when its aspect ratio is greater than 16:9.\nAspect Fill and remove black bars. Not compatible with integer scaling.");
ImGui::Unindent();
}
OptionCheckbox("Widescreen Game Cheats", config::WidescreenGameHacks,

View File

@ -82,6 +82,8 @@ Option<int> SkipFrame(CORE_OPTION_NAME "_frame_skipping");
Option<int> MaxThreads("", 3);
Option<int> AutoSkipFrame(CORE_OPTION_NAME "_auto_skip_frame", 0);
Option<int> RenderResolution("", 480);
Option<bool> IntegerScale("");
Option<bool> LinearInterpolation("", true);
Option<bool> VSync("", true);
Option<bool> ThreadedRendering(CORE_OPTION_NAME "_threaded_rendering", true);
Option<int> AnisotropicFiltering(CORE_OPTION_NAME "_anisotropic_filtering");