diff --git a/core/cfg/option.cpp b/core/cfg/option.cpp index 5f08d1845..709a888f1 100644 --- a/core/cfg/option.cpp +++ b/core/cfg/option.cpp @@ -99,6 +99,8 @@ Option SkipFrame("ta.skip"); Option MaxThreads("pvr.MaxThreads", 3); Option AutoSkipFrame("pvr.AutoSkipFrame", 0); Option RenderResolution("rend.Resolution", 480); +Option IntegerScale("rend.IntegerScale", false); +Option LinearInterpolation("rend.LinearInterpolation", true); Option VSync("rend.vsync", true); Option PixelBufferSize("rend.PixelBufferSize", 512_MB); Option AnisotropicFiltering("rend.AnisotropicFiltering", 1); diff --git a/core/cfg/option.h b/core/cfg/option.h index 3710e8673..5383810e2 100644 --- a/core/cfg/option.h +++ b/core/cfg/option.h @@ -460,6 +460,8 @@ extern Option SkipFrame; extern Option MaxThreads; extern Option AutoSkipFrame; // 0: none, 1: some, 2: more extern Option RenderResolution; +extern Option IntegerScale; +extern Option LinearInterpolation; extern Option VSync; extern Option PixelBufferSize; extern Option AnisotropicFiltering; diff --git a/core/rend/dx11/dx11_renderer.cpp b/core/rend/dx11/dx11_renderer.cpp index 998cceb56..bffc99dfe 100644 --- a/core/rend/dx11/dx11_renderer.cpp +++ b/core/rend/dx11/dx11_renderer.cpp @@ -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 } diff --git a/core/rend/dx9/d3d_renderer.cpp b/core/rend/dx9/d3d_renderer.cpp index 1d85829d8..5d9bdcee6 100644 --- a/core/rend/dx9/d3d_renderer.cpp +++ b/core/rend/dx9/d3d_renderer.cpp @@ -263,7 +263,7 @@ void D3DRenderer::RenderFramebuffer(const FramebufferInfo& info) { ReadFramebuffer(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 projection = glm::translate(glm::vec3(-1.f / settings.display.width, 1.f / settings.display.height, 0)); diff --git a/core/rend/gles/gldraw.cpp b/core/rend/gles/gldraw.cpp index 6dff2a82a..6218b19fb 100644 --- a/core/rend/gles/gldraw.cpp +++ b/core/rend/gles/gldraw.cpp @@ -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 } diff --git a/core/rend/transform_matrix.h b/core/rend/transform_matrix.h index d79327272..1274e71f3 100644 --- a/core/rend/transform_matrix.h +++ b/core/rend/transform_matrix.h @@ -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; diff --git a/core/rend/vulkan/vulkan_context.cpp b/core/rend/vulkan/vulkan_context.cpp index 1a9f3c3ce..2c15c2902 100644 --- a/core/rend/vulkan/vulkan_context.cpp +++ b/core/rend/vulkan/vulkan_context.cpp @@ -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 diff --git a/core/ui/gui.cpp b/core/ui/gui.cpp index 9f6a36fee..105adc1b7 100644 --- a/core/ui/gui.cpp +++ b/core/ui/gui.cpp @@ -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, diff --git a/shell/libretro/option.cpp b/shell/libretro/option.cpp index 7f9799a36..3290aa336 100644 --- a/shell/libretro/option.cpp +++ b/shell/libretro/option.cpp @@ -82,6 +82,8 @@ Option SkipFrame(CORE_OPTION_NAME "_frame_skipping"); Option MaxThreads("", 3); Option AutoSkipFrame(CORE_OPTION_NAME "_auto_skip_frame", 0); Option RenderResolution("", 480); +Option IntegerScale(""); +Option LinearInterpolation("", true); Option VSync("", true); Option ThreadedRendering(CORE_OPTION_NAME "_threaded_rendering", true); Option AnisotropicFiltering(CORE_OPTION_NAME "_anisotropic_filtering");