Compare commits

..
Author SHA1 Message Date
Cameron Gutman b100e674dd HACK: Wait longer before rendering a frame 2026-07-12 22:45:07 -05:00
Cameron Gutman c0c4d60565 Update AppImage libva to 2.24.1 2026-07-08 23:58:15 -05:00
Cameron Gutman 8b18d58aa0 Add workaround for interoperability issue with out-of-tree FFmpeg zero-copy v4l2m2m patches and libplacebo
Fixes #1786
2026-07-08 23:40:08 -05:00
dependabot[bot] 5e1ac6df87 Bump moonlight-common-c/moonlight-common-c from 1f76427 to 2ea4775
Bumps [moonlight-common-c/moonlight-common-c](https://github.com/moonlight-stream/moonlight-common-c) from `1f76427` to `2ea4775`.
- [Commits](https://github.com/moonlight-stream/moonlight-common-c/compare/1f764276e848ae2ec7815ef90d1c1748272e074a...2ea47752c3051d72a64bcca190024e8b354fa1ef)

---
updated-dependencies:
- dependency-name: moonlight-common-c/moonlight-common-c
  dependency-version: 2ea47752c3051d72a64bcca190024e8b354fa1ef
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-07-05 16:52:09 -04:00
Cameron Gutman 0c8970364c Switch to upstream nanors with native SIMD and GFNI runtime dispatching 2026-07-05 00:14:34 -04:00
Cameron Gutman 1516464f82 Modernize ASAN flags for MSVC 2026-07-04 15:37:21 -04:00
Cameron Gutman fd34a1fd28 Disable the text outline when line wrapping is required 2026-07-01 23:43:40 -05:00
Cameron Gutman 9067db8915 Switch to UTF-8 for overlay text rendering 2026-07-01 23:24:31 -05:00
MiraandCameron Gutman 177f845183 Add outline to stream overlay text (#1887)
Co-authored-by: Cameron Gutman <[email protected]>
2026-07-01 23:03:45 -05:00
Cameron Gutman c9b03f80f8 Partially revert "Use GL_NEAREST when possible without degrading quality"
The changes to GL_NEAREST for overlay textures and disabling alpha blending in SDL renderer are left intact.

This reverts commit 0f49dca4c0.
2026-07-01 21:56:54 -05:00
Cameron Gutman a27589fe35 Update SDL to 3.4.12 2026-07-01 21:47:49 -05:00
Cameron Gutman 561d824814 Move HDR toggle into the Basic Settings panel 2026-07-01 21:41:05 -05:00
14 changed files with 161 additions and 101 deletions
+2 -2
View File
@@ -42,7 +42,7 @@ jobs:
uses: actions/checkout@v5
with:
repository: libsdl-org/SDL
ref: a5e02a0927a2dd999718c1903f2d59440319ca99
ref: release-3.4.12
path: deps/SDL
- name: Build SDL3
@@ -86,7 +86,7 @@ jobs:
uses: actions/checkout@v5
with:
repository: intel/libva
ref: 2.23.0
ref: 2.24.1
path: deps/libva
- name: Build libva
+46 -43
View File
@@ -812,38 +812,64 @@ Flickable {
ToolTip.text: qsTr("Fullscreen generally provides the best performance, but borderless windowed may work better with features like macOS Spaces, Alt+Tab, screenshot tools, on-screen overlays, etc.")
}
CheckBox {
id: vsyncCheck
Row {
spacing: 5
width: parent.width
hoverEnabled: true
text: qsTr("V-Sync")
font.pointSize: 12
checked: StreamingPreferences.enableVsync
onCheckedChanged: {
StreamingPreferences.enableVsync = checked
CheckBox {
id: vsyncCheck
hoverEnabled: true
text: qsTr("V-Sync")
font.pointSize: 12
checked: StreamingPreferences.enableVsync
onCheckedChanged: {
StreamingPreferences.enableVsync = checked
}
ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.visible: hovered
ToolTip.text: qsTr("Disabling V-Sync allows sub-frame rendering latency, but it can display visible tearing")
}
ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.visible: hovered
ToolTip.text: qsTr("Disabling V-Sync allows sub-frame rendering latency, but it can display visible tearing")
CheckBox {
id: framePacingCheck
hoverEnabled: true
text: qsTr("Frame pacing")
font.pointSize: 12
enabled: StreamingPreferences.enableVsync
checked: StreamingPreferences.enableVsync && StreamingPreferences.framePacing
onCheckedChanged: {
StreamingPreferences.framePacing = checked
}
ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.visible: hovered
ToolTip.text: qsTr("Frame pacing reduces micro-stutter by delaying frames that come in too early")
}
}
CheckBox {
id: framePacingCheck
id: enableHdr
width: parent.width
hoverEnabled: true
text: qsTr("Frame pacing")
font.pointSize: 12
enabled: StreamingPreferences.enableVsync
checked: StreamingPreferences.enableVsync && StreamingPreferences.framePacing
text: qsTr("Enable HDR")
font.pointSize: 12
enabled: SystemProperties.supportsHdr
checked: enabled && StreamingPreferences.enableHdr
onCheckedChanged: {
StreamingPreferences.framePacing = checked
StreamingPreferences.enableHdr = checked
}
// Updating StreamingPreferences.videoCodecConfig is handled above
ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.visible: hovered
ToolTip.text: qsTr("Frame pacing reduces micro-stutter by delaying frames that come in too early")
ToolTip.text: enabled ?
qsTr("The stream will be HDR-capable, but some games may require an HDR monitor on your host PC to enable HDR mode.")
:
qsTr("HDR streaming is not supported on this PC.")
}
}
}
@@ -1630,29 +1656,6 @@ Flickable {
}
}
CheckBox {
id: enableHdr
width: parent.width
text: qsTr("Enable HDR")
font.pointSize: 12
enabled: SystemProperties.supportsHdr
checked: enabled && StreamingPreferences.enableHdr
onCheckedChanged: {
StreamingPreferences.enableHdr = checked
}
// Updating StreamingPreferences.videoCodecConfig is handled above
ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.visible: hovered
ToolTip.text: enabled ?
qsTr("The stream will be HDR-capable, but some games may require an HDR monitor on your host PC to enable HDR mode.")
:
qsTr("HDR streaming is not supported on this PC.")
}
CheckBox {
id: enableYUV444
width: parent.width
+1 -1
View File
@@ -808,7 +808,7 @@
<message>
<location filename="../gui/SettingsView.qml" line="718"/>
<source>Use Default (%1 Mbps)</source>
<translation>Utiliser la valeur par défaut (%1 Mbps)</translation>
<translation>Utiliser la valuer par défaut (%1 Mbps)</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="753"/>
+21 -21
View File
@@ -66,12 +66,12 @@
<message>
<location filename="../gui/CliPair.qml" line="12"/>
<source>Pairing... Please enter &apos;%1&apos; on %2.</source>
<translation>Парування... Будь ласка, введіть «%1» на %2.</translation>
<translation>З&apos;єднання... Будь ласка, введіть &apos;%1&apos; на %2.</translation>
</message>
<message>
<location filename="../gui/CliPair.qml" line="81"/>
<source>Pairing completed successfully</source>
<translation>Парування успішно завершено</translation>
<translation>З&apos;єднання успішно завершено</translation>
</message>
</context>
<context>
@@ -129,12 +129,12 @@
<message>
<location filename="../gui/computermodel.cpp" line="62"/>
<source>Paired</source>
<translation>Парований</translation>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/computermodel.cpp" line="65"/>
<source>Unpaired</source>
<translation>Не парований</translation>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/computermodel.cpp" line="72"/>
@@ -184,7 +184,7 @@
<message>
<location filename="../gui/computermodel.cpp" line="81"/>
<source>Pair State: %1</source>
<translation>Стан парування: %1</translation>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/computermodel.cpp" line="82"/>
@@ -392,7 +392,7 @@
<message>
<location filename="../cli/pair.cpp" line="80"/>
<source>%1 is already paired</source>
<translation>%1 вже паровано</translation>
<translation>%1 вже з&apos;єднано</translation>
</message>
<message>
<location filename="../cli/pair.cpp" line="109"/>
@@ -405,7 +405,7 @@
<location filename="../cli/quitstream.cpp" line="88"/>
<location filename="../cli/startstream.cpp" line="89"/>
<source>Computer %1 has not been paired. Please open Moonlight to pair before streaming.</source>
<translation>Не паровано з комп&apos;ютером %1. Будь ласка, відкрийте Moonlight для парування перед тим як запускати трансляцію.</translation>
<translation>З ком&apos;ютером %1 не було створено пари. Будь ласка відкрийте Moonlight щоб створити пару перед тим як запускати трансляцію.</translation>
</message>
<message>
<location filename="../cli/quitstream.cpp" line="102"/>
@@ -421,7 +421,7 @@
<message>
<location filename="../cli/listapps.cpp" line="108"/>
<source>Computer %1 has not been paired. Please open Moonlight to pair before retrieving games list.</source>
<translation>Не паровано з комп&apos;ютером %1. Будь ласка, запустіть Moonlight для парування перед запитом списку ігор.</translation>
<translation>Комп&apos;ютер %1 не було з&apos;єднано. Будь ласка, запустіть Moonlight для з&apos;єднання перед тим, як отримати список ігор.</translation>
</message>
</context>
<context>
@@ -611,7 +611,7 @@
<message>
<location filename="../streaming/session.cpp" line="1131"/>
<source>Your host PC doesn&apos;t support YUV 4:4:4 streaming for selected video codec.</source>
<translation>Ваш хост-ПК не підтримує транслювання YUV 4:4:4 для вибраного відео кодека.</translation>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../streaming/session.cpp" line="1146"/>
@@ -674,7 +674,7 @@
<message>
<location filename="../gui/SettingsView.qml" line="116"/>
<source>Resolution and FPS</source>
<translation>Роздільність та частота кадрів</translation>
<translation>Роздільність та FPS</translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="124"/>
@@ -770,7 +770,7 @@
<message>
<location filename="../gui/SettingsView.qml" line="183"/>
<source>Native (Excluding Notch)</source>
<translation>Нативний (Без вирізу)</translation>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="718"/>
@@ -801,12 +801,12 @@
<message>
<location filename="../gui/SettingsView.qml" line="836"/>
<source>Frame pacing</source>
<translation>Вирівнювання інтервалу між кадрами</translation>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="846"/>
<source>Frame pacing reduces micro-stutter by delaying frames that come in too early</source>
<translation>Вирівнювання інтервалу між кадрами покращує плавність трансляції, затримуючи кадри, що надходять зарано.</translation>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="856"/>
@@ -836,7 +836,7 @@
<message>
<location filename="../gui/SettingsView.qml" line="923"/>
<source>You must restart any game currently in progress for this setting to take effect</source>
<translation>Потрібно перезапустити будь-яку активну гру, щоб зміни застосувалися.</translation>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="929"/>
@@ -926,7 +926,7 @@
<message>
<location filename="../gui/SettingsView.qml" line="1740"/>
<source>Show performance stats while streaming</source>
<translation>Показувати показники продуктивності під час трансляції</translation>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1750"/>
@@ -936,12 +936,12 @@
<message>
<location filename="../gui/SettingsView.qml" line="1751"/>
<source>You can toggle it at any time while streaming using Ctrl+Alt+Shift+S or Select+L1+R1+X.</source>
<translation>Можна перемкнути у будь-який момент під час трансляції використавши Ctrl+Alt+Shift+S або Select+L1+R1+X.</translation>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1752"/>
<source>The performance overlay is not supported on Steam Link or Raspberry Pi.</source>
<translation>Оверлей продуктивності не підтримується на Steam Link та Raspberry Pi.</translation>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1340"/>
@@ -971,12 +971,12 @@
<message>
<location filename="../gui/SettingsView.qml" line="1456"/>
<source>Swap A/B and X/Y gamepad buttons</source>
<translation>Поміняти місцями A/B та X/Y на контролері</translation>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1466"/>
<source>This switches gamepads into a Nintendo-style button layout</source>
<translation>Міняє місцями кнопки на контролери на подобу контролерів Nintendo.</translation>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1472"/>
@@ -991,7 +991,7 @@
<message>
<location filename="../gui/SettingsView.qml" line="1320"/>
<source>You can toggle this while streaming using Ctrl+Alt+Shift+M.</source>
<translation>Можна перемкнути під час трансляції натиснувши Ctrl+Alt+Shift+M.</translation>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="1490"/>
@@ -1052,7 +1052,7 @@
<location filename="../gui/SettingsView.qml" line="512"/>
<location filename="../gui/SettingsView.qml" line="622"/>
<source>Custom (%1 FPS)</source>
<translation>Користувацька (%1 К/с)</translation>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/SettingsView.qml" line="527"/>
+12 -20
View File
@@ -627,6 +627,8 @@ bool EGLRenderer::setupVideoRenderingState() {
glGenTextures(EGL_MAX_PLANES, m_Textures);
for (size_t i = 0; i < EGL_MAX_PLANES; ++i) {
glBindTexture(GL_TEXTURE_EXTERNAL_OES, m_Textures[i]);
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
@@ -784,16 +786,6 @@ void EGLRenderer::renderFrame(AVFrame* frame)
}
}
int drawableWidth, drawableHeight;
SDL_GL_GetDrawableSize(m_Window, &drawableWidth, &drawableHeight);
SDL_Rect src, dst;
src.x = src.y = dst.x = dst.y = 0;
src.w = frame->width;
src.h = frame->height;
dst.w = drawableWidth;
dst.h = drawableHeight;
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
ssize_t plane_count = m_Backend->exportEGLImages(frame, m_EGLDisplay, imgs);
if (plane_count < 0)
return;
@@ -801,16 +793,6 @@ void EGLRenderer::renderFrame(AVFrame* frame)
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_EXTERNAL_OES, m_Textures[i]);
m_glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, imgs[i]);
// Use GL_NEAREST to reduce sampling if the video region is a multiple of the frame size
if (dst.w % frame->width == 0 && dst.h % frame->height == 0) {
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
else {
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
}
// We already called glClear() after last frame's SDL_GL_SwapWindow()
@@ -819,7 +801,17 @@ void EGLRenderer::renderFrame(AVFrame* frame)
glClear(GL_COLOR_BUFFER_BIT);
}
int drawableWidth, drawableHeight;
SDL_GL_GetDrawableSize(m_Window, &drawableWidth, &drawableHeight);
// Set the viewport to the size of the aspect-ratio-scaled video
SDL_Rect src, dst;
src.x = src.y = dst.x = dst.y = 0;
src.w = frame->width;
src.h = frame->height;
dst.w = drawableWidth;
dst.h = drawableHeight;
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
glViewport(dst.x, dst.y, dst.w, dst.h);
glUseProgram(m_ShaderProgram);
@@ -10,6 +10,7 @@
#include <SDL_vulkan.h>
extern "C" {
#include <libavutil/hwcontext_drm.h>
#include <libavutil/hwcontext_vulkan.h>
}
@@ -942,6 +943,8 @@ void PlVkRenderer::waitToRender()
}
endRenderTiming();
#endif
SDL_Delay(10);
}
}
@@ -1146,6 +1149,24 @@ bool PlVkRenderer::testRenderFrame(AVFrame *frame)
}
#endif
if (frame->format == AV_PIX_FMT_DRM_PRIME) {
auto drmFrame = (AVDRMFrameDescriptor*)frame->data[0];
// This can happen with out-of-tree FFmpeg patches if the V4L2
// format lacks a mapping to a DRM format.
if (drmFrame->nb_layers == 0) {
return false;
}
// Current versions of libplacebo only support one plane per layer
// and will assert if provided a frame that violates this constraint.
for (int i = 0; i < drmFrame->nb_layers; i++) {
if (drmFrame->layers[i].nb_planes != 1) {
return false;
}
}
}
// Test if the frame can be mapped to libplacebo
pl_frame mappedFrame;
if (!mapAvFrameToPlacebo(frame, &mappedFrame)) {
@@ -576,11 +576,6 @@ ReadbackRetry:
// Ensure the viewport is set to the desired video region
SDL_RenderSetViewport(m_Renderer, &dst);
// Use nearest pixel sampling if the video region size is a multiple of the frame size
SDL_SetTextureScaleMode(m_Texture,
dst.w % frame->width == 0 && dst.h % frame->height == 0 ?
SDL_ScaleModeNearest : SDL_ScaleModeLinear);
// Draw the video content itself
SDL_RenderCopy(m_Renderer, m_Texture, nullptr, nullptr);
+50 -4
View File
@@ -149,10 +149,12 @@ void OverlayManager::notifyOverlayUpdated(OverlayType type)
(void**)&m_Overlays[type].surface,
m_Overlays[type].enabled ?
// The _Wrapped variant is required for line breaks to work
TTF_RenderText_Blended_Wrapped(m_Overlays[type].font,
m_Overlays[type].text,
m_Overlays[type].color,
1024)
RenderTextOutlinedWrapped(m_Overlays[type].font,
m_Overlays[type].text,
m_Overlays[type].color,
{0, 0, 0, 255},
4,
1024)
: nullptr);
// Notify the renderer
@@ -163,3 +165,47 @@ void OverlayManager::notifyOverlayUpdated(OverlayType type)
SDL_FreeSurface(oldSurface);
}
}
SDL_Surface* OverlayManager::RenderTextOutlinedWrapped(TTF_Font* font, const char* text, SDL_Color textColor, SDL_Color outlineColor, int outlineWidth, int wrapWidth) {
if (text == nullptr || text[0] == '\0') {
return nullptr;
}
int oldOutline = TTF_GetFontOutline(font);
TTF_SetFontOutline(font, outlineWidth);
// Verify that the string won't require wrapping (which could cause the outline and the text
// to diverge due to different wrapping positions).
//
// FIXME: We do this rather than just disabling wrapping entirely (wrapWidth = 0) because we
// need further testing to ensure that all renderers can handle non-NPOT overlay textures.
for (const QString& line : QString(text).split('\n')) {
int extent, count;
if (TTF_MeasureUTF8(font, line.toUtf8(), wrapWidth, &extent, &count) == 0 && count < line.size()) {
// If it requires wrapping, render it without the outline
TTF_SetFontOutline(font, oldOutline);
return TTF_RenderUTF8_Blended_Wrapped(font, text, textColor, wrapWidth);
}
}
// Draw text twice, but outline is a bit bigger
auto outlineSurface = TTF_RenderUTF8_Blended_Wrapped(font, text, outlineColor, wrapWidth);
TTF_SetFontOutline(font, 0);
auto textSurface = TTF_RenderUTF8_Blended_Wrapped(font, text, textColor, wrapWidth);
TTF_SetFontOutline(font, oldOutline);
if (outlineSurface == nullptr || textSurface == nullptr) {
SDL_FreeSurface(outlineSurface);
SDL_FreeSurface(textSurface);
return nullptr;
}
// Merge the texts
SDL_Rect dst = { outlineWidth, outlineWidth, textSurface->w, textSurface->h };
SDL_BlitSurface(textSurface, nullptr, outlineSurface, &dst);
SDL_FreeSurface(textSurface);
return outlineSurface;
}
+1
View File
@@ -41,6 +41,7 @@ public:
private:
void notifyOverlayUpdated(OverlayType type);
SDL_Surface* RenderTextOutlinedWrapped(TTF_Font* font, const char* text, SDL_Color textColor, SDL_Color outlineColor, int outlineWidth, int wrapWidth);
struct {
bool enabled;
+1 -1
View File
@@ -26,7 +26,7 @@ CONFIG(release, debug|release) {
# Enable ASan for Windows
#QMAKE_CFLAGS += -fsanitize=address
#QMAKE_CXXFLAGS += -fsanitize=address
#QMAKE_LFLAGS += -incremental:no -wholearchive:clang_rt.asan_dynamic-x86_64.lib -wholearchive:clang_rt.asan_dynamic_runtime_thunk-x86_64.lib
#QMAKE_LFLAGS += -incremental:no
# Propagate environment variable flags
QMAKE_CFLAGS += $$(CFLAGS)
+3 -1
View File
@@ -50,7 +50,9 @@ SOURCES += \
$$ENET_DIR/protocol.c \
$$ENET_DIR/unix.c \
$$ENET_DIR/win32.c \
$$COMMON_C_DIR/src/rswrapper.c \
$$COMMON_C_DIR/nanors/deps/obl/oblas_common.c \
$$COMMON_C_DIR/nanors/deps/obl/oblas_lite.c \
$$COMMON_C_DIR/nanors/rs.c \
$$COMMON_C_DIR/src/AudioStream.c \
$$COMMON_C_DIR/src/ByteBuffer.c \
$$COMMON_C_DIR/src/Connection.c \
+1 -1
View File
@@ -4,7 +4,7 @@ $Organization = "moonlight-stream"
$PrebuiltRepo = "moonlight-qt-deps"
$TargetDir = Join-Path $PSScriptRoot "libs\windows"
$Assets = @("windows-x64.zip", "windows-ARM64.zip")
$Tag = "v7.2"
$Tag = "v8"
if (Test-Path $TargetDir) {
Write-Host "Cleaning target directory..." -ForegroundColor Cyan
+1 -1
View File
@@ -7,7 +7,7 @@ import shutil
ORGANIZATION = "moonlight-stream"
PREBUILT_REPO = "moonlight-qt-deps"
TAG = "v7.2"
TAG = "v8"
def get_platform_config():
system = platform.system()