Skip to content

[Switch] Use the display's resolution, 1080p when docked - #25

Open
ferrolho wants to merge 1 commit into
Homebrodot:main/3.5from
ferrolho:feat/switch-docked-1080p
Open

[Switch] Use the display's resolution, 1080p when docked#25
ferrolho wants to merge 1 commit into
Homebrodot:main/3.5from
ferrolho:feat/switch-docked-1080p

Conversation

@ferrolho

@ferrolho ferrolho commented Aug 25, 2026

Copy link
Copy Markdown

The problem

The Switch outputs 1080p to a TV, but a libnx app only gets a 1080p framebuffer if it asks for one. It has to size its NWindow and notice when the operating mode changes. This port never did, so it always rendered 720p and let the console's scaler stretch that to the television.

Four getters returned hardcoded sizes, one of them marked unfinished:

ContextGLSwitchEGL::get_window_width()  -> 1280
ContextGLSwitchEGL::get_window_height() -> 720   // todo: stub
OS_Switch::get_window_size()            -> Size2(1280, 720)
OS_Switch::get_video_mode()             -> VideoMode(1280, 720)

get_window_size() is the one that matters most. SceneTree compares it against the previous frame's value every iteration and rebuilds the root viewport when it changes. A constant there means a resize can never propagate, whatever else the port does. It also means the engine side needs no changes: once the port reports the real size, the existing resize path handles the rest.

The change

  • Size the NWindow with nwindowSetDimensions() before creating the EGL surface, based on appletGetOperationMode(): 1920x1080 docked, 1280x720 handheld.
  • Report what EGL actually returned (eglQuerySurface) instead of what we asked for.
  • Poll appletGetOperationMode() once per frame in OS_Switch::run(). On a change, rebuild the window surface and update current_videomode. libnx has no notification for docking, so polling is the only option. The value is cached in the applet service, so it is not an IPC call.
  • Implement get_screen_size(), which previously fell through to the base class.

The surface rebuild keeps the EGL context, and falls back to recreating the previous size if the new one fails. A refused resize leaves the engine with a working surface rather than none.

Testing

Tested on a v1 Switch with Atmosphère over several sessions:

  • 1920x1080 docked, 1280x720 handheld
  • docking and undocking while running resizes live, no restart needed
  • works on a heavy scene (a 300k blade grass field) as well as light ones

Note for project authors

A project that renders 3D into a sub-viewport and blits it gets sharper 2D and UI at no extra 3D cost, because the sub-viewport keeps its own size. A project that renders 3D straight to the root viewport will shade 2.25x the pixels when docked. I can add a project setting to opt out if you would prefer that.

The window size was hardcoded in four getters, so a docked console rendered 720p and left the system scaler to stretch it to the TV. OS_Switch::get_window_size() was the load-bearing one: SceneTree polls it every iteration and rebuilds the root viewport when it changes, so a constant there meant a resize could never propagate.

Size the NWindow from appletGetOperationMode() before creating the EGL surface, report what eglQuerySurface actually returns, and poll the operation mode each frame to rebuild the surface when the console is docked or undocked.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

1 participant