Scaling question

I want to make a simple change in a game to eliminate fuzzy non-square pixels when up-scaling to screen size. The game’s native resolution is 320 by 200 and my screen is 1920 by 1080. This is scaled up as nearest neighbor which appears fuzzy looks inconsistent. To fix this, I would like to first scale the source resolution up with a whole number (320*6 by 200*6) and then scale it down with filtering (1728 by 1080). How can I do that?

Nearest neighbor shouldn’t appear “fuzzy”, but blocky instead. Are you using SDL_Renderer?

True, it’s a typo. Yes, here’s the related code:

(I’ve set SDL_HINT_RENDER_SCALE_QUALITY to nearest)

And this is how I want it to look like…
zlodej-menu

Setting SDL_HINT_RENDER_SCALE_QUALITY to nearest should give you exactly what you want. What platform are you using?

“nearest” doesn’t look good with floating point numbers, hence why I want to apply linear filter to anti-alias the sub-pixels (upscale with nearest with a whole number, downscale with linear to target size). GNU/Linux.

I have implemented something similar in the past with SDL 1.2 using SDL_Surface.

First I upscaled the original low-res image to an integer multiple using nearest-neighbour interpolation to get it close* to the target size.

Then I scaled the upscaled image to the target size using bilinear interpolation.

This makes all pixels look the same size but without making it more blurry than necessary.

* You can either choose to scale to a larger size than the target size and then scale down, or to a smaller size and scale up. The former gives a better result but is slower (at least on the CPU). The visual difference seems to matter most when the target size is only a little bigger than the original size. With scaling x5 or more I would hardly see the difference.

If you scale directly to the target size using bilinear interpolation the pixels will look too blurry.

If you scale directly to the target size using nearest-neighbour interpolation some pixel rows and columns will be bigger than others (unless you scale to an exact integer multiple of the original size) which doesn’t look good (although it becomes less noticeable with higher scaling factors).

But as I said, I used SDL_Surface to do this. I wrote the nearest-neighbour and bilinear interpolation code myself. Unfortunately, I don’t know how to do this with SDL_Texture (to take advantage of the GPU acceleration) but hopefully someone else knows.

I’ve implemented this for my Commander Keen clone project: it basically involved creating a “scaled texture” which is both a render target (SDL_TEXTUREACCESS_TARGET) and bilinearly scaled (SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1") before creating the texture). This texture’s size should be an integer multiple of the game’s “native” resolution (320×200).

Then, use SDL_SetRenderTarget() to set this texture as your render target — and make sure any other textures you’re rendering from have SDL_HINT_RENDER_SCALE_QUALITY == 0. You can then set the render target back to the actual screen when you’re done rendering (with SDL_SetRenderTarget(…, 0)), and SDL_RenderCopy() the result to fill the screen.

My full code is here (as well as the equivalent code for OpenGL and Vulkan), though there’s a fair bit of stuff specific to my project that makes it a somewhat more complicated example than would be ideal.

You can set the scale mode for individual textures using SDL_SetTextureScaleMode() so you don’t have to mess with setting hints and trying to create textures in certain order.

So render to the texture with whatever mode you want, then set the scale mode to nearest to draw it.

Thank you for all the feedback @sjr, @Peter87 and @ David_Gow (wow, Omnispeak developer here)! I’ve ended up opening a bug report on the GitHub project page and linking the answer from here, as it’s fairly bit more complex than I hoped it would be.

Keep in mind that the VGA 320x200 (mode 13h) resolution that was common for DOS games back in the day used non-square pixels. The closest square pixel mode was 320x240.

But the issue of uneven pixel heights when blowing up to modern monitor resolutions has always been there. Stuff like DOSBox just kind of doesn’t deal with it; you can either have bilinear filtering and get “fuzzy” pixels, or nearest-neighbor filtering and get old fashioned blocky pixels but not all with the exact same height. (Modern pixel-art games sidestep it by choosing something that divides evenly into the user’s monitor’s native resolution, or they render at the monitor’s native resolution and just scale up the individual artwork)

In any case, render to your 320x200 render target texture with the scaling mode set to LINEAR. Change it to NEAREST and render to another render target texture of appropriate size, to get your scaled up version. With the scale mode of that second target texture set to LINEAR, draw it to the screen. However, understand that this linear downscaling will probably still give you fuzzy pixels.

Thank you, I’m aware of these. The reason why I didn’t mention the 4:3 aspect correction is that the game art seems to be mostly drawn with square pixels despite being rendered as 320x240 (pictures), so it looks more natural without it. I expected a simple solution that I can implement in 2-3 lines maybe, but it turned out to be much more difficult than that, so I’m just sticking with nearest.

Yeah, a lot of DOS games running at 320x200 just did their artwork as square pixels