Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e5e3307
Add Vulkan video encoder for Linux
neatnoise Feb 19, 2026
2fed3a6
fix: correct Vulkan VBR rc_mode value from 3 to 4 per Vulkan spec
neatnoise Feb 19, 2026
8f66c60
fix: correct VBR default in web config.html from 3 to 4
neatnoise Feb 20, 2026
bb56f0c
feat(linux): improve vulkan encoder, add rgb2nv12 shader
neatnoise Feb 27, 2026
2f5d27e
fix: address review feedback - ordering, translations, remove ifdef g…
neatnoise Feb 27, 2026
24b6521
vulkan: disable SEI/AUD units to match VAAPI behavior
neatnoise Feb 27, 2026
12ee4da
vulkan: optimize encoder frame path
neatnoise Mar 1, 2026
44d63dd
vulkan: use 2x1 horizontal chroma subsampling
neatnoise Mar 1, 2026
9d0e0b8
vulkan: cursor compositing for KMS, 10-bit format support
neatnoise Mar 1, 2026
477f101
vulkan: fix multi-plane DMA-BUF import and chroma subsampling
neatnoise Mar 3, 2026
6c2a6c6
vulkan: add VRAM encoding support to wlgrab, clean up portalgrab and …
neatnoise Mar 4, 2026
cfd1040
vulkan: add HDR/P010 support, rename shader to rgb2yuv
neatnoise Mar 4, 2026
f228c40
fix(vulkan): enable VBV buffer limit to prevent bitrate excursions
neatnoise Mar 8, 2026
61c0d4b
vulkan: fix VBR bitrate overshoot by clearing rc_min_rate
neatnoise Mar 8, 2026
2dccc85
vulkan: reduce VBR bitrate overshoots
neatnoise Mar 11, 2026
8aa8ef8
refactor: address code smells in vulkan encoder and related files
neatnoise Mar 14, 2026
1c62090
vulkan: revert rc_max_rate cap to match Mar 8 overshoot state
neatnoise Mar 14, 2026
779d5f2
vulkan: remove NO_RC_BUF_LIMIT from h264_vulkan to match Mar 8 state
neatnoise Mar 14, 2026
bf7b484
fix: resolve CI lint and test failures for Vulkan encoder
neatnoise Mar 17, 2026
0c115e7
Sort vk_rc keys alphabetically and reorder vulkan/vaapi sections
neatnoise Mar 18, 2026
f6f9702
Fix y_invert handling for Vulkan encoder with wlroots GLES2 capture
neatnoise Mar 18, 2026
3dcf7e2
Fix corrupted image (colored boxes) in wlroots Vulkan capture
neatnoise Mar 18, 2026
c97a6e4
Build Vulkan shaders at compile time instead of shipping pre-compiled…
neatnoise Mar 18, 2026
fd36a50
fix: use Vulkan headers from build-deps, improve portability
neatnoise Mar 26, 2026
0872a09
style: move vulkan.h include to top of file
neatnoise Mar 29, 2026
e3cd494
fix: use find_program for shader compiler, fix packaging deps
neatnoise Mar 29, 2026
dac503b
fix: cmake-lint errors, clang-format, and alphabetical locale keys
neatnoise Mar 30, 2026
be4f918
fix: cmake-lint E1120 and C0307 in binary_to_c.cmake
neatnoise Mar 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci-freebsd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ jobs:
devel/pkgconf \
ftp/curl \
graphics/libdrm \
graphics/shaderc \
graphics/vulkan-headers \
graphics/vulkan-loader \
graphics/wayland \
lang/python314 \
multimedia/libva \
Expand Down
67 changes: 67 additions & 0 deletions cmake/compile_definitions/linux.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,73 @@ if(LIBVA_FOUND)
"${CMAKE_SOURCE_DIR}/src/platform/linux/vaapi.cpp")
endif()

# vulkan video encoding (via FFmpeg)
if(${SUNSHINE_ENABLE_VULKAN})
# use Vulkan headers from build-deps submodule (system headers may be too old, e.g. Ubuntu 22.04)
set(VULKAN_HEADERS_DIR "${CMAKE_SOURCE_DIR}/third-party/build-deps/third-party/FFmpeg/Vulkan-Headers/include")
if(NOT EXISTS "${VULKAN_HEADERS_DIR}/vulkan/vulkan.h")
message(FATAL_ERROR "Vulkan headers not found in build-deps submodule")
endif()

find_library(VULKAN_LIBRARY NAMES vulkan vulkan-1)
if(NOT VULKAN_LIBRARY)
message(FATAL_ERROR "libvulkan not found")
endif()

# prefer glslc, fall back to glslangValidator
find_program(GLSLC_EXECUTABLE glslc)
if(NOT GLSLC_EXECUTABLE)
find_program(GLSLANG_EXECUTABLE glslangValidator)
endif()
if(NOT GLSLC_EXECUTABLE AND NOT GLSLANG_EXECUTABLE)
message(FATAL_ERROR "Vulkan shader compiler not found (need glslc or glslangValidator)")
endif()

list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_BUILD_VULKAN=1)
include_directories(SYSTEM ${VULKAN_HEADERS_DIR})
list(APPEND PLATFORM_LIBRARIES ${VULKAN_LIBRARY})
list(APPEND PLATFORM_TARGET_FILES
"${CMAKE_SOURCE_DIR}/src/platform/linux/vulkan_encode.h"
"${CMAKE_SOURCE_DIR}/src/platform/linux/vulkan_encode.cpp")

# compile GLSL -> SPIR-V -> C include at build time
set(VULKAN_SHADER_DIR "${CMAKE_BINARY_DIR}/generated-src/shaders")
set(VULKAN_SHADER_SOURCE "${SUNSHINE_SOURCE_ASSETS_DIR}/linux/assets/shaders/vulkan/rgb2yuv.comp")
set(VULKAN_SHADER_SPV "${VULKAN_SHADER_DIR}/rgb2yuv.spv")
set(VULKAN_SHADER_DATA "${VULKAN_SHADER_DIR}/rgb2yuv.spv.inc")

file(MAKE_DIRECTORY "${VULKAN_SHADER_DIR}")

if(GLSLC_EXECUTABLE)
add_custom_command(
OUTPUT "${VULKAN_SHADER_SPV}"
COMMAND ${GLSLC_EXECUTABLE} -O "${VULKAN_SHADER_SOURCE}" -o "${VULKAN_SHADER_SPV}"
DEPENDS "${VULKAN_SHADER_SOURCE}"
COMMENT "Compiling Vulkan shader rgb2yuv.comp (glslc)"
VERBATIM)
else()
add_custom_command(
OUTPUT "${VULKAN_SHADER_SPV}"
COMMAND ${GLSLANG_EXECUTABLE} -V -o "${VULKAN_SHADER_SPV}" "${VULKAN_SHADER_SOURCE}"
DEPENDS "${VULKAN_SHADER_SOURCE}"
COMMENT "Compiling Vulkan shader rgb2yuv.comp (glslangValidator)"
VERBATIM)
endif()

add_custom_command(
OUTPUT "${VULKAN_SHADER_DATA}"
COMMAND ${CMAKE_COMMAND} -DSPV_FILE=${VULKAN_SHADER_SPV} -DOUT_FILE=${VULKAN_SHADER_DATA}
-P "${CMAKE_SOURCE_DIR}/cmake/scripts/binary_to_c.cmake"
DEPENDS "${VULKAN_SHADER_SPV}"
COMMENT "Generating C include from rgb2yuv.spv"
VERBATIM)

add_custom_target(vulkan_shaders
DEPENDS "${VULKAN_SHADER_DATA}"
COMMENT "Vulkan shader compilation")
set(SUNSHINE_TARGET_DEPENDENCIES ${SUNSHINE_TARGET_DEPENDENCIES} vulkan_shaders)
endif()

# wayland
if(${SUNSHINE_ENABLE_WAYLAND})
find_package(Wayland REQUIRED)
Expand Down
2 changes: 2 additions & 0 deletions cmake/prep/options.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ elseif(UNIX) # Linux
"Enable KMS grab if available." ON)
option(SUNSHINE_ENABLE_VAAPI
"Enable building vaapi specific code." ON)
option(SUNSHINE_ENABLE_VULKAN
"Enable Vulkan video encoding." ON)
option(SUNSHINE_ENABLE_WAYLAND
"Enable building wayland specific code." ON)
option(SUNSHINE_ENABLE_X11
Expand Down
33 changes: 33 additions & 0 deletions cmake/scripts/binary_to_c.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# binary_to_c.cmake - Convert a binary file to a C uint32_t initializer list.
# Input: SPV_FILE - path to SPIR-V binary
# Output: OUT_FILE - path to write C initializer (e.g. {0x07230203, ...})

file(READ "${SPV_FILE}" data HEX)
string(LENGTH "${data}" hex_len)
math(EXPR num_bytes "${hex_len} / 2")
math(EXPR num_words "${num_bytes} / 4")
math(EXPR last "${num_words} - 1")

set(_out "{")
foreach(idx IN RANGE 0 ${last})
math(EXPR off "${idx} * 8")
math(EXPR off1 "${off} + 2")
math(EXPR off2 "${off} + 4")
math(EXPR off3 "${off} + 6")
string(SUBSTRING "${data}" ${off} 2 b0)
string(SUBSTRING "${data}" ${off1} 2 b1)
string(SUBSTRING "${data}" ${off2} 2 b2)
string(SUBSTRING "${data}" ${off3} 2 b3)
# little-endian to uint32_t
string(APPEND _out "0x${b3}${b2}${b1}${b0}")
if(NOT idx EQUAL last)
string(APPEND _out ",")
endif()
math(EXPR col "(${idx} + 1) % 8")
if(col EQUAL 0)
string(APPEND _out "\n")
endif()
endforeach()
string(APPEND _out "}\n")

file(WRITE "${OUT_FILE}" "${_out}")
100 changes: 100 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -2155,6 +2155,11 @@ editing the `conf` file in a text editor. Use the examples as reference.
<td>vaapi</td>
<td>Use VA-API (AMD, Intel)</td>
</tr>
<tr>
<td>vulkan</td>
<td>Use Vulkan encoder (AMD, Intel, NVIDIA).
@note{Applies to Linux only.}</td>
</tr>
<tr>
<td>software</td>
<td>Encoding occurs on the CPU</td>
Expand Down Expand Up @@ -2920,6 +2925,101 @@ editing the `conf` file in a text editor. Use the examples as reference.
</tr>
</table>

## Vulkan Encoder

### vk_tune

<table>
<tr>
<td>Description</td>
<td colspan="2">
Encoder tuning preset. Low latency modes reduce encoding delay at the cost of quality.
@note{This option only applies when using Vulkan [encoder](#encoder).}
</td>
</tr>
<tr>
<td>Default</td>
<td colspan="2">@code{}
2
@endcode</td>
</tr>
<tr>
<td>Example</td>
<td colspan="2">@code{}
vk_tune = 1
@endcode</td>
</tr>
<tr>
<td>Options</td>
<td>0 (default)</td>
<td>Let the driver decide</td>
</tr>
<tr>
<td></td>
<td>1 (hq)</td>
<td>High Quality</td>
</tr>
<tr>
<td></td>
<td>2 (ll)</td>
<td>Low Latency</td>
</tr>
<tr>
<td></td>
<td>3 (ull)</td>
<td>Ultra Low Latency</td>
</tr>
<tr>
<td></td>
<td>4 (lossless)</td>
<td>Lossless</td>
</tr>
</table>

### vk_rc_mode

<table>
<tr>
<td>Description</td>
<td colspan="2">
Rate control mode for encoding. Auto lets the driver decide.
@note{This option only applies when using Vulkan [encoder](#encoder).}
</td>
</tr>
<tr>
<td>Default</td>
<td colspan="2">@code{}
4
@endcode</td>
</tr>
<tr>
<td>Example</td>
<td colspan="2">@code{}
vk_rc_mode = 2
@endcode</td>
</tr>
<tr>
<td>Options</td>
<td>0</td>
<td>Auto (driver decides)</td>
</tr>
<tr>
<td></td>
<td>1</td>
<td>CQP (Constant QP)</td>
</tr>
<tr>
<td></td>
<td>2</td>
<td>CBR (Constant Bitrate)</td>
</tr>
<tr>
<td></td>
<td>4</td>
<td>VBR (Variable Bitrate)</td>
</tr>
</table>

## Software Encoder

### sw_preset
Expand Down
2 changes: 2 additions & 0 deletions packaging/linux/Arch/PKGBUILD
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ depends=(
'openssl'
'opus'
'udev'
'vulkan-icd-loader'
'which'
)

Expand All @@ -65,6 +66,7 @@ makedepends=(
'npm'
'python-jinja' # required by the glad OpenGL/EGL loader generator
'python-setuptools' # required for glad OpenGL/EGL loader generated, v2.0.0
'shaderc'
)

checkdepends=(
Expand Down
5 changes: 5 additions & 0 deletions packaging/linux/copr/Sunshine.spec
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,15 @@ BuildRequires: openssl-devel
BuildRequires: pipewire-devel
BuildRequires: rpm-build
BuildRequires: systemd-rpm-macros
BuildRequires: vulkan-loader-devel
BuildRequires: wget
BuildRequires: which

%if 0%{?fedora}
# Fedora-specific BuildRequires
BuildRequires: appstream
# BuildRequires: boost-devel >= 1.86.0
BuildRequires: glslc
BuildRequires: libappstream-glib
%if 0%{fedora} > 43
# needed for npm from nvm
Expand Down Expand Up @@ -91,6 +93,7 @@ BuildRequires: npm
BuildRequires: python311
BuildRequires: python311-Jinja2
BuildRequires: python311-setuptools
BuildRequires: shaderc
BuildRequires: udev
# for unit tests
BuildRequires: xvfb-run
Expand Down Expand Up @@ -157,6 +160,7 @@ Requires: libX11 >= 1.7.3.1
Requires: numactl-libs >= 2.0.14
Requires: openssl >= 3.0.2
Requires: pulseaudio-libs >= 10.0
Requires: vulkan-loader
%endif

%if 0%{?suse_version}
Expand All @@ -173,6 +177,7 @@ Requires: libX11-6
Requires: libnuma1
Requires: libopenssl3
Requires: libpulse0
Requires: vulkan-loader
%endif

%description
Expand Down
2 changes: 2 additions & 0 deletions packaging/sunshine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ class Sunshine < Formula
depends_on "pango"
depends_on "pipewire"
depends_on "pulseaudio"
depends_on "shaderc"
depends_on "systemd"
depends_on "vulkan-loader"
depends_on "wayland"

# Jinja2 is required at build time by the glad OpenGL/EGL loader generator (Linux only).
Expand Down
6 changes: 6 additions & 0 deletions scripts/linux_build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,9 @@ function add_arch_deps() {
'opus'
'python-jinja' # glad OpenGL/EGL loader generator
'python-setuptools' # glad OpenGL/EGL loader generated, v2.0.0
'shaderc'
'udev'
'vulkan-icd-loader'
'wayland'
)

Expand Down Expand Up @@ -249,6 +251,8 @@ function add_debian_based_deps() {
"libxfixes-dev" # X11
"libxrandr-dev" # X11
"libxtst-dev" # X11
"libvulkan-dev" # Vulkan
"glslang-tools" # Vulkan shader compiler
"ninja-build"
"npm" # web-ui
"python3-jinja2" # glad OpenGL/EGL loader generator
Expand Down Expand Up @@ -332,6 +336,8 @@ function add_fedora_deps() {
"python3-jinja2" # glad OpenGL/EGL loader generator
"python3-setuptools" # glad OpenGL/EGL loader generated, v2.0.0
"rpm-build" # if you want to build an RPM binary package
"vulkan-loader-devel"
"glslc"
"wget" # necessary for cuda install with `run` file
"which" # necessary for cuda install with `run` file
"xorg-x11-server-Xvfb" # necessary for headless unit testing
Expand Down
8 changes: 8 additions & 0 deletions src/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,11 @@ namespace config {
false, // strict_rc_buffer
}, // vaapi

{
2, // vk.tune (default: ll - low latency)
4, // vk.rc_mode (default: vbr)
},

{}, // capture
{}, // encoder
{}, // adapter_name
Expand Down Expand Up @@ -1137,6 +1142,9 @@ namespace config {

bool_f(vars, "vaapi_strict_rc_buffer", video.vaapi.strict_rc_buffer);

int_f(vars, "vk_tune", video.vk.tune);
int_f(vars, "vk_rc_mode", video.vk.rc_mode);

string_f(vars, "capture", video.capture);
string_f(vars, "encoder", video.encoder);
string_f(vars, "adapter_name", video.adapter_name);
Expand Down
5 changes: 5 additions & 0 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ namespace config {
bool strict_rc_buffer;
} vaapi;

struct {
int tune; // 0=default, 1=hq, 2=ll, 3=ull, 4=lossless
int rc_mode; // 0=driver, 1=cqp, 2=cbr, 4=vbr
} vk;

std::string capture;
std::string encoder;
std::string adapter_name;
Expand Down
1 change: 1 addition & 0 deletions src/platform/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ namespace platf {
dxgi, ///< DXGI
cuda, ///< CUDA
videotoolbox, ///< VideoToolbox
vulkan, ///< Vulkan
unknown ///< Unknown
};

Expand Down
3 changes: 3 additions & 0 deletions src/platform/linux/graphics.h
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,9 @@ namespace egl {
// Increment sequence when new rgb_t needs to be created
std::uint64_t sequence;

// Frame is vertically flipped (GL convention)
bool y_invert {false};

// PipeWire metadata
std::optional<uint64_t> pts;
std::optional<uint64_t> seq;
Expand Down
Loading
Loading