Skip to content

Apple: Add tvOS platform support#118332

Draft
stuartcarnie wants to merge 2 commits intogodotengine:masterfrom
stuartcarnie:tvos_platform
Draft

Apple: Add tvOS platform support#118332
stuartcarnie wants to merge 2 commits intogodotengine:masterfrom
stuartcarnie:tvos_platform

Conversation

@stuartcarnie
Copy link
Copy Markdown
Contributor

@stuartcarnie stuartcarnie commented Apr 9, 2026

appletv@2x Summary

Depends on

tvOS platform support for Godot.

What's included?

  • ✅ Debug and release templates for tvOS
  • ✅ Platform support in the Godot editor
  • ✅ Platform exporter for tvOS
  • ✅ Controller via SDL support
  • 🚫 Simulator support1
  • 🚫 OpenGL ES2; selects to Metal + mobile renderer automatically if OpenGL is configured

Outstanding Issues

Menu / back button support

tvOS has a similar behaviour to Android with a back (Siri Remote) / menu button (game controllers) that navigate back towards the home screen. We can leverage the existing APIs for Android:

API Comment
quit_on_go_back controls whether menu / back is delivered as an event to the application
WINDOW_EVENT_GO_BACK_REQUEST Sent when quit_on_go_back is false
NOTIFICATION_WM_GO_BACK_REQUEST Sent when quit_on_go_back is false

A suggested flow would be that a game would set quit_on_go_back to false whilst in play, so that when a user presses back / menu, the game intercepts and displays a pause menu to the user. Whilst the pause menu is active, the game should set quit_on_go_back to true, so that if the user presses back / home again, the game naturally exits to the home screen, providing the correct HIG experience for users.

⚠️ Quit / Exit behaviour

Exiting an app is prohibited on Apple embedded platforms, namely iOS, visionOS and tvOS. The OS manages the lifetime and applications are suspended by user action, such as pressing the Menu / back button. These platforms ignore the return value of Main::iteration in their C++ implementations, where returning true normally signals the process should exit. If an Godot application quits, it puts the entire game into an inconsistent state, as SceneTree::_quit is now true, and certain processes no longer run correctly. There is no way for an end user or a Godot developer to fix this without force-quitting, and would be perceived as bugs by users.

My recommendation is that that the following APIs and behaviours be changed for APPLE_EMBEDDED platforms only:

API Comments
SceneTree::quit(exit_code) Log a warning and do nothing
Node::_process If it returns true, log warning and do nothing
Node::_physics_process If it returns true, log warning do nothing
SceneTree::quit_on_go_back always false for tvOS

Footnotes

  1. Metal simulator device features do not meet the minimum requirements for the mobile or forward+ renderers

  2. tvOS only supports OpenGL ES 2.0; Godot requires ES 3.0

@dsnopek
Copy link
Copy Markdown
Contributor

dsnopek commented Apr 9, 2026

  • 🚫 OpenGL ES2; selects to Metal + mobile renderer automatically if OpenGL is configured

I doubt I'll be building anything for tvOS anytime soon, so this is mostly out of curiosity: if you build with ANGLE is it possible to use the compatibility renderer?

The visionOS XR module only supports the Mobile renderer for now, the Forward+ renderer is not supported.

To use the visionOS XR module you must set the new 'application/app_role' export setting to Immersive. You can choose if you want passthrough or not by the new 'application/immersion_style' export option.

Then, initialize the visionOS XR module in a script:

```
    var interface = XRServer.find_interface("visionOS")
    if interface and interface.initialize():
        var viewport : Viewport = get_viewport()
        viewport.use_xr = true
        viewport.vrs_mode = Viewport.VRS_XR
        viewport.use_hdr_2d = true
```

Implementation details:
- The visionOS platform now has two different execution paths implemented by the `GodotWindowScene` and `CompositorServicesImmersiveSpace` scenes in `app_visionos.swift`. The `application/app_role` export setting controls which scene is used.
- The visionOS XR interface tries to be as close to the OpenXR interface as possible, to keep main renderer code changes to a minimum. It adopts Compositor Services and ARKit APIs, which is how you render Metal content on visionOS.
- We obtain the head pose twice, once in `process()` in the game thread so scripts can use it if needed, and another from the render thread in set_frame(), so the rendered pose is accurate.
- The projection matrices returned by visionOS have an inverse depth correction applied (visionOS uses the [0, 1] z space, but Godot expects the [-1, 1] z space until the rendering step).
- The `rasterizationRateMap` (the structure that supports foveation on visionOS) is provided through the `get_vrs_texture()` function, using the new `XR_VRS_TEXTURE_FORMAT_RASTERIZATION_RATE_MAP` texture type. It's passed through the renderer when creating passes/subpasses, to be ultimately set by the Metal driver.
- Apple Vision Pro's' minimum supported near plane is `0.1`. There's a runtime error message that shows if you try to use a lower near plane.
- The Metal driver has a new dummy `SurfaceCompositorServices`, which replaces `SurfaceLayer` when running in immersive mode. The reason for this is that the Compositor Services API needs to do a `cp_drawable_encode_present()` step with the `MTLCommandBuffer` used by the renderer, and this seemed the most natural way of overriding the `present()` call normally done by the Metal driver.

Co-Authored-By: huisedenanhai <winser@pku.edu.cn>
Co-Authored-By: Stuart Carnie <stuart.carnie@gmail.com>
@stuartcarnie stuartcarnie force-pushed the tvos_platform branch 3 times, most recently from 99af3a2 to 700817e Compare April 11, 2026 07:07
@stuartcarnie
Copy link
Copy Markdown
Contributor Author

stuartcarnie commented Apr 11, 2026

@stuartcarnie stuartcarnie force-pushed the tvos_platform branch 4 times, most recently from 3bfb01c to d63baff Compare April 11, 2026 08:00
@stuartcarnie
Copy link
Copy Markdown
Contributor Author


float DisplayServerTVOS::_screen_potential_edr_headroom() const {
UIScreen *screen = _get_ui_screen();
ERR_FAIL_NULL_V(screen, false);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ERR_FAIL_NULL_V(screen, false);
ERR_FAIL_NULL_V(screen, 0.0);

Or some appropriate value


float DisplayServerTVOS::_screen_current_edr_headroom() const {
UIScreen *screen = _get_ui_screen();
ERR_FAIL_NULL_V(screen, false);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ERR_FAIL_NULL_V(screen, false);
ERR_FAIL_NULL_V(screen, 0.0);

Same here

Error err = OK;
Ref<Image> img = _load_icon_or_splash_image(icon_path, &err);
if (err != OK || img.is_null() || img->is_empty()) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Invalid icon (%s): '%s'.", info.preset_key, icon_path));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these be translated, like so:

Suggested change
add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Invalid icon (%s): '%s'.", info.preset_key, icon_path));
add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat(TTR("Invalid icon (%s): '%s'."), info.preset_key, icon_path));

err = img->save_png(p_iconset_dir + exp_name);
}
if (err) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Failed to export icon (%s): '%s'.", info.preset_key, icon_path));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Error err = OK;
Ref<Image> img = _load_icon_or_splash_image(icon_path, &err);
if (err != OK || img.is_null() || img->is_empty()) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Invalid icon (%s): '%s'.", info.preset_key, icon_path));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

return ERR_UNCONFIGURED;
} else if (info.force_opaque && img->detect_alpha() != Image::ALPHA_NONE) {
if (resize_warning) {
add_message(EXPORT_MESSAGE_WARNING, TTR("Export Icons"), vformat("Icon (%s) must be opaque.", info.preset_key));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

err = new_img->save_png(p_iconset_dir + exp_name);
} else if (img->get_width() != target_width || img->get_height() != target_height) {
if (resize_warning) {
add_message(EXPORT_MESSAGE_WARNING, TTR("Export Icons"), vformat("Icon (%s): '%s' has incorrect size %s and was automatically resized to %s.", info.preset_key, icon_path, img->get_size(), Vector2i(target_width, target_height)));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

}

if (err) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Failed to export icon (%s): '%s'.", info.preset_key, icon_path));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

@stuartcarnie
Copy link
Copy Markdown
Contributor Author

Thanks for the review, @AThousandShips – I'll correct those issues

@shiena
Copy link
Copy Markdown
Contributor

shiena commented Apr 18, 2026

Related to #118702. This PR adds tvOS (and visionOS as a dependency), so it will likely also need to update the per-platform template download list in export_template_manager.cpp introduced by #117072.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add tvOS (Apple TV) support

7 participants