Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions .github/workflows/ci-freebsd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ jobs:
devel/pkgconf \
ftp/curl \
graphics/libdrm \
graphics/vulkan-headers \
graphics/vulkan-loader \
graphics/wayland \
lang/python312 \
multimedia/libva \
Expand Down
15 changes: 15 additions & 0 deletions cmake/compile_definitions/linux.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,21 @@ if(LIBVA_FOUND)
"${CMAKE_SOURCE_DIR}/src/platform/linux/vaapi.cpp")
endif()

# vulkan video encoding (via FFmpeg)
if(${SUNSHINE_ENABLE_VULKAN})
find_package(Vulkan REQUIRED)
else()
set(Vulkan_FOUND OFF)
endif()
if(Vulkan_FOUND)
list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_BUILD_VULKAN=1)
include_directories(SYSTEM ${Vulkan_INCLUDE_DIRS})
list(APPEND PLATFORM_LIBRARIES ${Vulkan_LIBRARIES})
list(APPEND PLATFORM_TARGET_FILES
"${CMAKE_SOURCE_DIR}/src/platform/linux/vulkan_encode.h"
"${CMAKE_SOURCE_DIR}/src/platform/linux/vulkan_encode.cpp")
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 @@ -60,6 +60,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
5 changes: 5 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -2126,6 +2126,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
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 @@ -63,6 +64,7 @@ makedepends=(
'make'
'nodejs'
'npm'
'vulkan-headers'
)

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

Expand Down Expand Up @@ -146,6 +148,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 @@ -162,6 +165,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 @@ -94,6 +94,8 @@ class Sunshine < Formula
depends_on "pipewire"
depends_on "pulseaudio"
depends_on "systemd"
depends_on "vulkan-headers"
depends_on "vulkan-loader"
depends_on "wayland"
end

Expand Down
5 changes: 5 additions & 0 deletions scripts/linux_build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ function add_arch_deps() {
'openssl'
'opus'
'udev'
'vulkan-headers'
'vulkan-icd-loader'
'wayland'
)

Expand Down Expand Up @@ -247,6 +249,7 @@ function add_debian_based_deps() {
"libxfixes-dev" # X11
"libxrandr-dev" # X11
"libxtst-dev" # X11
"libvulkan-dev" # Vulkan
"ninja-build"
"npm" # web-ui
"systemd"
Expand Down Expand Up @@ -326,6 +329,8 @@ function add_fedora_deps() {
"pipewire-devel"
"pulseaudio-libs-devel"
"rpm-build" # if you want to build an RPM binary package
"vulkan-headers"
"vulkan-loader-devel"
"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 @@ -1116,6 +1121,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
15 changes: 14 additions & 1 deletion src/platform/linux/kmsgrab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "src/utility.h"
#include "src/video.h"
#include "vaapi.h"
#include "vulkan_encode.h"
#include "wayland.h"

using namespace std::literals;
Expand Down Expand Up @@ -1238,6 +1239,12 @@ namespace platf {
}
#endif

#ifdef SUNSHINE_BUILD_VULKAN
if (mem_type == mem_type_e::vulkan) {
return vk::make_avcodec_encode_device_ram(width, height);
}
#endif

#ifdef SUNSHINE_BUILD_CUDA
if (mem_type == mem_type_e::cuda) {
return cuda::make_avcodec_encode_device(width, height, false);
Expand Down Expand Up @@ -1370,6 +1377,12 @@ namespace platf {
}
#endif

#ifdef SUNSHINE_BUILD_VULKAN
if (mem_type == mem_type_e::vulkan) {
return vk::make_avcodec_encode_device_vram(width, height, img_offset_x, img_offset_y);
}
#endif

#ifdef SUNSHINE_BUILD_CUDA
if (mem_type == mem_type_e::cuda) {
return cuda::make_avcodec_gl_encode_device(width, height, img_offset_x, img_offset_y);
Expand Down Expand Up @@ -1515,7 +1528,7 @@ namespace platf {
} // namespace kms

std::shared_ptr<display_t> kms_display(mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
if (hwdevice_type == mem_type_e::vaapi || hwdevice_type == mem_type_e::cuda) {
if (hwdevice_type == mem_type_e::vaapi || hwdevice_type == mem_type_e::cuda || hwdevice_type == mem_type_e::vulkan) {
auto disp = std::make_shared<kms::display_vram_t>(hwdevice_type);

if (!disp->init(display_name, config)) {
Expand Down
10 changes: 9 additions & 1 deletion src/platform/linux/portalgrab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "src/platform/common.h"
#include "src/video.h"
#include "vaapi.h"
#include "vulkan_encode.h"
#include "wayland.h"

namespace {
Expand Down Expand Up @@ -805,6 +806,7 @@ namespace portal {
// On hybrid GPU systems (Intel+NVIDIA), DMA-BUFs come from the Intel GPU and cannot
// be imported into CUDA, so we fall back to memory buffers in that case.
bool use_dmabuf = n_dmabuf_infos > 0 && (mem_type == platf::mem_type_e::vaapi ||
mem_type == platf::mem_type_e::vulkan ||
(mem_type == platf::mem_type_e::cuda && display_is_nvidia));
if (use_dmabuf) {
for (int i = 0; i < n_dmabuf_infos; i++) {
Expand Down Expand Up @@ -1315,6 +1317,12 @@ namespace portal {
}
#endif

#ifdef SUNSHINE_BUILD_VULKAN
if (mem_type == platf::mem_type_e::vulkan) {
return vk::make_avcodec_encode_device_vram(width, height, 0, 0);
}
#endif

#ifdef SUNSHINE_BUILD_CUDA
if (mem_type == platf::mem_type_e::cuda) {
if (display_is_nvidia && n_dmabuf_infos > 0) {
Expand Down Expand Up @@ -1456,7 +1464,7 @@ namespace portal {
namespace platf {
std::shared_ptr<display_t> portal_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
using enum platf::mem_type_e;
if (hwdevice_type != system && hwdevice_type != vaapi && hwdevice_type != cuda) {
if (hwdevice_type != system && hwdevice_type != vaapi && hwdevice_type != cuda && hwdevice_type != vulkan) {
BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv;
return nullptr;
}
Expand Down
50 changes: 50 additions & 0 deletions src/platform/linux/shaders/rgb2nv12.comp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#version 450

layout(local_size_x = 16, local_size_y = 16) in;

layout(set = 0, binding = 0) uniform sampler2D rgb_in;
layout(set = 0, binding = 1, r8) uniform writeonly image2D y_out;
layout(set = 0, binding = 2, rg8) uniform writeonly image2D uv_out;

layout(push_constant) uniform PushConstants {
vec4 color_vec_y;
vec4 color_vec_u;
vec4 color_vec_v;
vec2 range_y;
vec2 range_uv;
ivec2 src_offset;
ivec2 src_size;
ivec2 dst_size;
} pc;

void main() {
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
if (pos.x >= pc.dst_size.x || pos.y >= pc.dst_size.y)
return;

// Precompute scaling factors
vec2 inv_tex = 1.0 / vec2(textureSize(rgb_in, 0));
vec2 scale = vec2(pc.src_size) / vec2(pc.dst_size);

// Map output pixel to input texture coordinate
vec2 uv = (vec2(pc.src_offset) + (vec2(pos) + 0.5) * scale) * inv_tex;
vec3 rgb = texture(rgb_in, uv).rgb;

// Y plane
float y = dot(pc.color_vec_y.xyz, rgb) + pc.color_vec_y.w;
imageStore(y_out, pos, vec4(y * pc.range_y.x + pc.range_y.y, 0, 0, 0));

// UV plane (half resolution, one thread per 2x2 block)
if ((pos.x & 1) == 0 && (pos.y & 1) == 0) {
vec2 step = scale * inv_tex;

// Average 2x1 horizontal pair for chroma subsampling
vec3 avg = (rgb + texture(rgb_in, uv + vec2(step.x, 0)).rgb) * 0.5;

float cb = dot(pc.color_vec_u.xyz, avg) + pc.color_vec_u.w;
float cr = dot(pc.color_vec_v.xyz, avg) + pc.color_vec_v.w;

imageStore(uv_out, pos >> 1, vec4(cb * pc.range_uv.x + pc.range_uv.y,
cr * pc.range_uv.x + pc.range_uv.y, 0, 0));
}
}
Binary file added src/platform/linux/shaders/rgb2nv12.spv
Binary file not shown.
Loading