I'm trying to replicate the method of scaling I usually use for low resolution games in SDL. The process goes like this:

  • Make a render target texture (something like a viewport texture in Godot, I guess) that is game_width*3 by game_height*3, where game width and height are some fixed value like 480x270.
  • Render the game to this texture at a scale of 3 with filtering disabled, this gives you "fat" pixels 3x3 in size on correct pixel boundaries.
  • Render this texture to the screen with texture filtering enabled. This gives fat pixels on the screen with very slight blended transitions between the fat pixels.

I use this method because it avoid the uneven pixel size of scaling a game up with filtering disabled, and the extreme blurriness of scaling with filtering enabled from the original resolution. The transitions between the fat pixels is so slight that most people don't even notice them, yet it can be scaled almost to any resolution without major warping.

Do people not use this method in Godot? I've seen many forum posts and videos about this, but this method is not described and I can't figure out how to do this in Godot. I've tried a number of things, such as the project settings (viewport width and height with stretch mode) and rendering to a sub viewport to a viewport texture and displaying the viewport texture with a texture rect.

So I worked on a plug-in that has all the pieces to to do this. You can look at the video here (the 2D part is at the end):

The code is open source, MIT license, so you can download/fork and use it as a base.

https://github.com/cybereality/godot-super-scaling

However, what you want could be done easier if you want to do it from scratch. A render target is called a Viewport in Godot. A Godot app always has 1 Viewport (this is the hidden root node). This node should always be rendered at native resolution (if you want to do anything complex). Then you create a second Viewport to hold the entire game. You can do this in the editor, by making a ViewportContainer, then a Viewport as a child, and then the whole game as a child of that. However, it makes moving objects in the editor difficult. If you look at my code, I generate the second Viewport dynamically, which means you don't have to edit the scene at all (and also simplifies if you have multiple levels, you don't have to do the same thing over and over again). This Viewport would be at the low resolution, like 480x270. Then you have a shader that samples the low res Viewport and writes to the texture of the real root Viewport. You can do the upscaling / downscaling / filtering all in one shader. This will be the fastest thing I think. My code is a little complex because of the upscaling algorithm, but all the setup code in GDScript should be the same (or maybe a few small changes). You can just take my plug-in and replace the shader. Or use it for inspiration.

    cybereality Thanks for the reply, I hadn't even considered using shaders. I usually just use the texture sampler to get this done.

    But disregard everything, because this is already set up for you in Godot 3. In the display/window settings, I just set my size to 1920x1080 and scale to 4 and stretch move on viewport and it just worked. But the thing is I swear I tried that and there was no filtering on the output.

    Regardless, I got this working without viewports and texture rects and shaders and inherited scenes and the big mess that I made earlier.

    Ah, I see. Yes, that is what most people do but I assumed you had tried that. With shaders you can get different looks, though. Like at the end of my video you see how it looks like a Sega Genesis game. So if you want to simulate a CRT screen, that can be useful. But for pixel art just using the viewport stretch works and is easier.

    6 days later

    Okay, some clarity now. Godot is, in fact, not set up to do this by default. What I was seeing was DPI scaling done by Windows. I did get this working well, though. I'm rendering to a viewport a fixed size of 480x270 and rendering that to the screen using a ViewportContainer. The ViewportContainer's material utilized CptPotato's shader to handle the smooth scaling, and it works flawlessly.

    I don't really know why this isn't a feature out of the box. It just needs a "pixel art" scaling mode that does this for you. I see so many posts and comments about this, it seems to be a common point of confusion.

    Actually it does work like this. It's just kind of confusing, and they should have it as a default option. I can look into making a feature request, cause this is really common for pixel art games and it's very confusing.

    Basically, set the game window to the size of your artwork (like 480x270). Then do all the layout and tilemapping at this size. Then, once you have the basic layout working, go into the project settings under Window and set the scaled resolution. For example, for 2x scale, put in 960x540. Then scroll down to Stretch, select Mode Viewport, Aspect at Keep, and put Shrink as the scale you chose earlier 2 (or 2x).

    Then when you play, the window will be sized at 200% of the original size, with pixel perfect 2x2 pixels for each 1 pixel. And if you expand the window or go fullscreen, then it will use linear scaling to stretch the rendered buffer to the native screen resolution. I believe this is exactly what you want, and it works like this in Godot 3.x and 4.0, I just tried it. Here is the project.

    https://godotforums.org/assets/files/2022-09-15/1663230863-829760-pixelscaling.zip

      cybereality I think you fell into the same trap as I did. Godot doesn't use linear texture sampling on the viewport texture. Uncheck display/window/dpi/allow_hidpi and look again. The confusion I was having was caused by DPI scaling on one computer and no DPI scaling on another computer, which is why I could've sworn there was no texture filtering the first time I tried it but there was when I tried again. The DPI scaling was making it look like it was using linear texture sampling to scale the viewport texture, but Godot was using nearest to scale up to the expected window size, then Windows was using linear to scale up to the DPI-scaled window size. This is the worst of both worlds, inconsistent fat pixel sizes and blurriness, plus inconsistent behavior of different machines.

      I've tried enabling filtering on the sampler for the root viewport with get_tree().root.get_texture().flags |= Texture.FLAG_FILTER, but I think the viewport is rendered to the screen differently than other textures in the engine, because that doesn't work.

      I've included my solution in your project to show you want I mean, because I feel like I'm not explaining myself well.

      Godot really, really needs a way to scale the root viewport using a shader like this available with a checkbox or in the scaling modes. You have no idea how many indie games have incorrect scaling of pixel art and it drives me crazy and Godot is just setting people up to repeat this mistake.

      Oh, and I forgot to put the URL I found the shader in the shader source, I found it here.

        Both projects look the same to me, but maybe that is because I'm on Linux. In any case, I do understand what you are saying, I'm just not seeing a problem on my machine.