Artemis-Switch: A streamed frame from host to screen
Artemis-Switch is a fork in the Moonlight streaming family. The host runs the game; the client receives compressed video and audio while sending controller input back. Rendering a frame is only one part of that round trip.
Repository languagesC++ 53.78%11
Shares calculated from GitHub Linguist bytes.
| Language | % | Bytes |
|---|---|---|
| C++ | 2,312,036 | |
| C | 1,428,795 | |
| Java | 244,691 | |
| CMake | 150,826 | |
| Objective-C++ | 83,846 | |
| Shell | 32,086 |
5 more languages
| Swift | 0.38% | 16,275 |
| Python | 0.35% | 15,226 |
| GLSL | 0.34% | 14,440 |
| Makefile | 0.02% | 662 |
| PowerShell | 0.01% | 271 |
Overview
Why it exists
Hardware-accelerated GameStream/Moonlight client in C++ with custom NVN display frame pacing for zero-stutter video.
Best fit
Ultra-low latency PC game streaming directly to Nintendo Switch handheld mode.
Getting started
These commands come from the repository documentation. They were not run during this review.
git clone --recursive https://github.com/antoinebou12/Artemis-Switch.git
cd Artemis-Switch
cmake -B build/switch -DCMAKE_BUILD_TYPE=Release -DPLATFORM_SWITCH=ON -DUSE_DEKO3D=ON
cmake --build build/switch --target Moonlight.nro --parallelpython tests/i18n_consistency_test.py
python tests/release_contract_test.py
cmake -S tests -B build/tests -DCMAKE_BUILD_TYPE=Debug
cmake --build build/tests --parallel
ctest --test-dir build/tests --output-on-failurepython -m pip install ruff==0.12.10
python scripts/lint.py
cmake -S tests -B build/lint -DCMAKE_BUILD_TYPE=Debug -DARTEMIS_STRICT_WARNINGS=ON -DARTEMIS_ENABLE_CLANG_TIDY=ON
cmake --build build/lint --parallelcmake -S ci/integration -B build/integration -DCMAKE_BUILD_TYPE=Debug
cmake --build build/integration --parallel
ctest --test-dir build/integration --output-on-failurecd android-project./build_libromfs_generator.shexport JAVA_HOME=/Applications/Android\ Studio.app/Contents/jbr/Contents/Home
export ANDROID_SDK_ROOT=~/Library/Android/sdk
./gradlew assembleDebug
./gradlew installDebugRepository references
Excerpts from the repository’s guides, in their original language. Links open the inspected source revision.
Prerequisites and first run
Install the Artemis Switch NRO
docs/NRO_INSTALL.md
Use one of these files from dist/nro:
- Easy install: extract Artemis-Switch-SD.zip to the root of the Switch SD card.
- Manual install: copy Artemis-Switch.nro to sdmc:/switch/Artemis-Switch/Artemis-Switch.nro. Launch the Homebrew Menu with title redirection/full RAM mode, then select Artemis Switch. Applet mode is not supported for reliable streaming. The ZIP contains only the ready-to-copy switch/Artemis-Switch/Artemis-Switch.nro layout. Apollo and Sunshine both use the normal Moonlight/GameStream pairing flow.
Install
docs/source/install.md
Easy install
docs/source/install.md
Manual install
docs/source/install.md
Configuration and usage
Recommended host and clients
README.md
Client (device)
README.md
| Client | Device | Notes |
|---|---|---|
| Artemis Switch (this app) | Nintendo Switch | Moonlight-Switch fork with FSR/RCAS, stream profiles, and Switch presentation. |
| Artemide | Android | Highly recommended on Android: ultra-low latency (ULL), direct presentation, built-in FSR upscaling. FSR Performance / Balanced / Quality presets inspired this fork. |
| Artemis (Moonlight Noir) | Android | Apollo’s companion client (virtual display / host UX inspiration). |
| Moonlight | PC, Android, iOS, … | Official clients. |
| Moonlight V+ | Android | Alternative Android fork (frame-gen and extra host tools). |
| Why this stack: Vibeshine, Vibepollo, Apollo, or GameStream-enabled Punktfunk on the PC plus Artemide on Android (and Artemis Switch on the docked/handheld Switch) is the closest to a console-like session: the host manages display behavior, while the client stays low-latency and can… |
Development and contributions
Developer build
README.md
Prerequisites: Git, CMake 3.20 or newer, and a standard devkitPro devkitA64 environment. Clone recursively so every dependency under extern/ is initialized: Repository layout
| Path | Purpose |
|---|---|
| app/src/ | Application, streaming, renderer, input, and feature implementation |
| app/include/ | UI and application declarations |
| resources/ | Borealis XML, images, shaders, and translations |
| tests/ | Portable unit and contract tests |
| ci/integration/ | Cross-feature integration test build |
| docs/ | GitHub Pages source and engineering notes |
| .github/workflows/ | Build, test, sanitizer, and release automation |
| extern/ | Pinned third-party dependencies and Git submodules |
| Keep Switch-specific behavior behind the existing platform and capability gates. | |
| Do not make project-specific changes inside vendored extern/ dependencies. | |
| Moonlight.nro and Moonlight.elf remain… |
Developer validation
README.md
Source documentation
README.mddocs/README.mddocs/NRO_INSTALL.mddocs/source/install.mdapp/platforms/android-project/README.mdapp/platforms/android-project/README_ZH.md
6 documents read from 6 candidates.
Features and components 2
- Session and interface
- The Borealis interface handles host selection and settings. MoonlightSession coordinates streaming, while the Switch decoder-and-render provider chooses concrete video and audio implementations. UI state and media processing have different lifecycles.
- Platform-specific output
- The provider constructs FFmpegVideoDecoder. With BOREALIS_USE_DEKO3D it selects ArtemisDKVideoRenderer, while other build flags select other renderers. Switch audio uses AudrenAudioRenderer. These are explicit build-time choices.
Architecture
The components and their connections remain available as text. The diagram adds an interactive view.
- GameStream host
- Streaming session
- FFmpeg video decoder
- deko3d renderer
- Switch display
- Controller input
Links verified in the repository.
Mermaid Interactive diagram (loads on open)
System overview of the components and connections verified in the repository.

flowchart LR
n0["GameStream host"]
n1["Streaming session"]
n2["FFmpeg video decoder"]
n3["deko3d renderer"]
n4["Switch display"]
n5["Controller input"]
n0 --> n1
n1 --> n2
n2 --> n3
n3 --> n4
n5 --> n1
n1 --> n0
Text version of the diagram
- GameStream host → Streaming session
- Streaming session → FFmpeg video decoder
- FFmpeg video decoder → deko3d renderer
- deko3d renderer → Switch display
- Controller input → Streaming session
- Streaming session → GameStream host
Follow one path through the code
Read the provider methods to see which backend is actually compiled, then follow the session’s decoded frames into that renderer. Network, decode and presentation delays need separate measurements to explain a slow stream.
Types and file structure
651 declared types, with 12 in the base-relationship overview.
Type diagram
classDiagram
direction TB
class t27b200e5e641["AboutTab"] {
}
class t1d8fa37de9f9["ActiveRemoteRoute"] {
<<struct>>
}
class t76b3bc05a24d["AddHostTab"] {
findHost()
stopSearchHost()
connectHost()
fillSearchBox()
}
class t71f8fd94394b["AppCell"] {
setFavorite()
updateFavoriteAction()
}
class tcb6c23872ee9["AppListView"] {
onLayout()
willAppear()
draw()
blockInput()
}
class t5416fcf29d4d["ArtemisSettingsTab"] {
refreshValues()
editWidth()
editHeight()
refreshRemoteAccessRows()
}
class taa0a005beea4["ButtonSelectingDialog"] {
open()
reloadLabel()
buttonsText()
draw()
}
class t0cab5ff3d09b["DiscoveryCandidate"] {
<<struct>>
isDirect()
}
class t9649c53d1d2d["FavoriteTab"] {
refreshIfNeeded()
setRefreshNeeded()
updateAppList()
}
class tae773a498879["ClickGestureRecognizer"] {
recognitionLoop()
}
class t03d6e2ef334c["FingersGestureRecognizer"] {
recognitionLoop()
}
class t96b64c4eb4d8["TwoFingerScrollState"] {
<<struct>>
}
class b66d2434ea20a["brls::Box"] {
<<unresolved_base>>
}
b66d2434ea20a <|-- t27b200e5e641
class b12e371ab4244["brls::Box"] {
<<unresolved_base>>
}
b12e371ab4244 <|-- t76b3bc05a24d
class b9fb6426c89d6["Box"] {
<<unresolved_base>>
}
b9fb6426c89d6 <|-- t71f8fd94394b
class bd9fae1c39e24["Box"] {
<<unresolved_base>>
}
bd9fae1c39e24 <|-- tcb6c23872ee9
class bc8de2cffa73c["brls::Box"] {
<<unresolved_base>>
}
bc8de2cffa73c <|-- t5416fcf29d4d
class b4629afd0f9ec["Dialog"] {
<<unresolved_base>>
}
b4629afd0f9ec <|-- taa0a005beea4
class b3def5b4c744e["brls::Box"] {
<<unresolved_base>>
}
b3def5b4c744e <|-- t9649c53d1d2d
class b1c2ec6b5bcbd["brls::GestureRecognizer"] {
<<unresolved_base>>
}
b1c2ec6b5bcbd <|-- tae773a498879
class b905c043cc5ee["brls::GestureRecognizer"] {
<<unresolved_base>>
}
b905c043cc5ee <|-- t03d6e2ef334c
Unresolved bases remain labelled; full signatures are not reconstructed.
Type index
AboutTab(class) ·app/include/about_tab.hpp:13Declared bases:
brls::Box.ActiveRemoteRoute(struct) ·app/include/active_remote_route.hpp:4AddHostTab(class) ·app/include/add_host_tab.hpp:21Methods in the overview:
findHost, stopSearchHost, connectHost, fillSearchBox.Declared bases:
brls::Box.AppCell(class) ·app/include/app_cell.hpp:15Methods in the overview:
setFavorite, updateFavoriteAction.Declared bases:
Box.AppListView(class) ·app/include/app_list_view.hpp:22Methods in the overview:
onLayout, willAppear, draw, blockInput.Declared bases:
Box.ArtemisSettingsTab(class) ·app/include/artemis_settings_tab.hpp:8Methods in the overview:
refreshValues, editWidth, editHeight, refreshRemoteAccessRows.Declared bases:
brls::Box.ButtonSelectingDialog(class) ·app/include/button_selecting_dialog.hpp:14Methods in the overview:
open, reloadLabel, buttonsText, draw.Declared bases:
Dialog.DiscoveryCandidate(struct) ·app/include/discovery_candidate.hpp:4Methods in the overview:
isDirect.FavoriteTab(class) ·app/include/favorite_tab.hpp:14Methods in the overview:
refreshIfNeeded, setRefreshNeeded, updateAppList.Declared bases:
brls::Box.ClickGestureRecognizer(class) ·app/include/gestures/click_gesture_recognizer.hpp:13Methods in the overview:
recognitionLoop.Declared bases:
brls::GestureRecognizer.FingersGestureRecognizer(class) ·app/include/gestures/fingers_gesture_recognizer.hpp:13Methods in the overview:
recognitionLoop.Declared bases:
brls::GestureRecognizer.TwoFingerScrollState(struct) ·app/include/gestures/two_finger_scroll_recognizer.hpp:12
Reference
616 / 616 eligible files read (962 in the tree); 0 oversized, 0 failed reads and 88 syntax diagnostics.
What the scan covers
Known dependency/build folders, credential files and binaries are excluded. The scan covers READMEs, recognized manifests and parser-supported source files.
Linguist statistics cover the whole repository and may include other languages.
File map
flowchart TD
root["Artemis-Switch"]
f0["app/src/main.cpp"]
root --> f0
f1["app/src/streaming/MoonlightSession.cpp"]
root --> f1
f2["app/src/streaming/switch/SwitchMoonlightSessionDecoderAndRenderProvider.cpp"]
root --> f2
f3["CMakeLists.txt"]
root --> f3
f4["README.md"]
root --> f4
f5["build_libromfs_generator.sh"]
root --> f5
f6["docker-compose.yml"]
root --> f6
f7["pyproject.toml"]
root --> f7
f8["scripts/docker-build-nro.sh"]
root --> f8
f9["scripts/lint.py"]
root --> f9
f10["scripts/package-linux.sh"]
root --> f10
f11["scripts/package-release-source.sh"]
root --> f11
f12["scripts/psv-dev.sh"]
root --> f12
f13["tests/CMakeLists.txt"]
root --> f13
f14["tests/active_session_guard_test.cpp"]
root --> f14
f15["tests/add_host_peer_filter_test.cpp"]
root --> f15
Files to read 3
References
- GitHubSource repository
- READMERepository documentation
- Artemis Switch: following a frame from the network to the screenBlog article
- Handheld streaming and homebrew — Artemis-Switch, moonlight-N3DS, sm64coopdx-switchBlog article