#14947·pcsx2

[Feature Request]: Option to filter 2D images (uploads, native frames, 2D textures) with bilinear or xBR when upscaling

Author: Forgottenshadow89Created Sep 12, 2026Updated Sep 15, 2026
LabelsEnhancement / Feature RequestFR: Awaiting Consideration

Description

When running games above native resolution, a lot of 2D stuff in PCSX2 still comes out as blocks of replicated pixels: FMVs, pre-rendered backgrounds, HUD icons, menus, anything the game draws as flat sprites. I've been digging into why, and it comes down to three separate paths in the hardware renderer, none of which gets any filtering by default:

  1. Images the CPU writes straight into memory that the texture cache tracks as a render target (IPU decoded video frames, pre-rendered backgrounds, software rendered 2D). GSTextureCache::Target::Update stretches them into the upscaled target with nearest. This used to be bilinear until 2b49614df in 2023, when it was moved behind the "Bilinear Dirty Upscale" hardware fix, so today you only get it back by enabling manual hardware fixes.
  2. Frames that Native Scaling keeps at native resolution (Dragon Quest VIII, Shadow of the Colossus and everything else with nativeScaling in the GameDB). They either get bilinear'd back up or are presented as they are.
  3. The most common one: textures used by flat 2D draws. A 512x448 FMV frame drawn as one full screen sprite, a 64x64 HUD icon, etc. are native resolution textures sampled with whatever the game asked for, which is usually nearest. "Bilinear (Forced)" helps a bit but it's all or nothing and it also touches 3D.

What I'd like is a single option in Graphics > Rendering, "Filter 2D Images", with three values: Off (default, exactly today's behaviour), Bilinear and xBR. It should work on its own, not tied to the Bilinear Dirty Upscale hack, and it should only touch 2D:

  • Bilinear: the uploads in (1) are stretched bilinearly (only 16/24/32bpp rects, 4/8bpp uploads are usually indexed data being moved around and are left alone), and flat 2D draws (sprite class, or triangles with constant Z) are sampled bilinearly even when the game asked for nearest.
  • xBR: same, but with Hyllian's xBR. The upload staging texture is xBR upscaled once and then copied per rect so write masks and the RTA alpha correction stay as they are, native resolution targets going back to the upscaled size and native frames reaching the display get the same treatment, and the hash cache textures used by flat 2D draws get a lazily created xBR upscaled copy (up to 6x, bounded per texture) that is sampled bilinearly.
  • Never touched: 3D geometry, render target sources, GPU palette (indexed) textures, depth, texture shuffles, HD texture replacements. Non-integer upscale factors fall back to bilinear for the upload part.

I have all of this working in a branch of my fork, as a single commit on top of current master: https://github.com/Forgottenshadow89/pcsx2/tree/filter2d . There's a FILTER2D_NOTES.md in there that explains every file that's touched and why. The shader is Hyllian's xBR (MIT), added as a new ShaderConvert entry for D3D11/12, OpenGL, Vulkan and Metal; it needs no constant buffer because it derives the scale from the texture coordinate derivatives.

About why this is an issue and not a PR: I'm not a PCSX2 contributor and the code in that branch was written with the help of an AI assistant (Claude Fable 5.1), with me directing it and testing everything on real games and GS dumps. Your contribution guidelines don't allow that for new contributors, so I'm not going to submit it as a PR. I'm leaving it here in case the idea itself is useful to the team; anyone is welcome to take the branch, rewrite it properly or just use it as a reference for where the problem is.

Reason

Right now the only way to get smooth 2D at high internal resolutions is Bilinear (Forced) plus the Bilinear Dirty Upscale hack behind manual hardware fixes, and even then FMVs drawn as sprites and HUDs in native scaling games stay blocky, while 3D gets blurred too. Other emulators (Vba-M, PPSSPP, Flycast, Project64, SNES9x, etc) offer this kind of 2D filtering as a normal option and it makes a big difference in games with pre-rendered backgrounds, videos and pixel art HUDs, without any accuracy cost when it's off.

Concretely, in my testing: Dragon Quest VIII's intro video (uploaded as 16x16 macroblocks and drawn as a 1:1 sprite) goes from hard 6x blocks to clean edges, Shadow of the Colossus' HUD icons get smooth outlines while the 3D stays pixel identical between Off and xBR, and games that stream pre-rendered backgrounds into the framebuffer stop looking like they're running at native resolution.

Examples

DuckStation's "Texture Filtering" and "Sprite Texture Filtering" options (xBR, JINC2 and so on, applied to 2D/sprites separately from 3D), and PPSSPP's texture upscaling (xBRZ 2x-5x) are the closest equivalents. My fork branch above is a working implementation for PCSX2 itself.