docs(doxygen): enforce warn if undocumented (#5337)

This commit is contained in:
Dave Lane
2026-06-25 22:43:17 -04:00
committed by GitHub
parent be7c1bcdfb
commit a991a9622c
141 changed files with 11804 additions and 1524 deletions
+2 -5
View File
@@ -143,10 +143,7 @@ jobs:
release: ${{ matrix.bsd_release }}
run: |
set -e
uv sync --locked --only-group glad --python "/usr/local/bin/python${PYTHON_VERSION}" \
--no-python-downloads --no-install-project
uv sync --project third-party/lizardbyte-common --locked --only-group test-c \
--python "/usr/local/bin/python${PYTHON_VERSION}" \
uv sync --locked --group glad --python "/usr/local/bin/python${PYTHON_VERSION}" \
--no-python-downloads --no-install-project
# fix git safe.directory issues
@@ -259,7 +256,7 @@ jobs:
shell: freebsd {0}
run: |
cd "${GITHUB_WORKSPACE}/build"
uv run --project ../third-party/lizardbyte-common --locked --no-sync gcovr . -r ../src \
uv run --project .. --locked --no-sync gcovr . -r ../src \
--exclude-noncode-lines \
--exclude-throw-branches \
--exclude-unreachable-branches \
+2 -2
View File
@@ -175,7 +175,7 @@ jobs:
- name: Sync Python tools
run: |
uv sync --project third-party/lizardbyte-common --locked --only-group test-c \
uv sync --locked \
--python "${PYTHON_VERSION}" \
--no-python-downloads --no-install-project
@@ -197,7 +197,7 @@ jobs:
(steps.test.outcome == 'success' || steps.test.outcome == 'failure')
working-directory: build
run: |
uv run --project ../third-party/lizardbyte-common --locked --no-sync gcovr \
uv run --project .. --locked --no-sync gcovr \
--gcov-executable "gcov-${GCC_VERSION}" . -r ../src \
--exclude-noncode-lines \
--exclude-throw-branches \
+2 -2
View File
@@ -100,7 +100,7 @@ jobs:
- name: Sync Python tools
run: |
uv sync --project third-party/lizardbyte-common --locked --only-group test-c \
uv sync --locked \
--python "${PYTHON_VERSION}" \
--no-python-downloads --no-install-project
@@ -184,7 +184,7 @@ jobs:
(steps.test.outcome == 'success' || steps.test.outcome == 'failure')
working-directory: build
run: |
uv run --project ../third-party/lizardbyte-common --locked --no-sync gcovr . -r ../src \
uv run --project .. --locked --no-sync gcovr . -r ../src \
--exclude-noncode-lines \
--exclude-throw-branches \
--exclude-unreachable-branches \
+2 -2
View File
@@ -214,7 +214,7 @@ jobs:
MSYS2_PATH_TYPE: inherit
UV_PYTHON: ${{ steps.setup-python.outputs.python-path }}
run: |
uv sync --project third-party/lizardbyte-common --locked --only-group test-c \
uv sync --locked \
--no-python-downloads \
--no-install-project
@@ -369,7 +369,7 @@ jobs:
env:
MSYS2_PATH_TYPE: inherit
run: |
uv run --project ../third-party/lizardbyte-common --locked --no-sync gcovr . -r ../src \
uv run --project .. --locked --no-sync gcovr . -r ../src \
--exclude-noncode-lines \
--exclude-throw-branches \
--exclude-unreachable-branches \
+1
View File
@@ -140,6 +140,7 @@ jobs:
COPR_BETA_WEBHOOK_TOKEN: ${{ secrets.COPR_BETA_WEBHOOK_TOKEN }}
COPR_STABLE_WEBHOOK_TOKEN: ${{ secrets.COPR_STABLE_WEBHOOK_TOKEN }}
COPR_CLI_CONFIG: ${{ secrets.COPR_CLI_CONFIG }}
VIRUSTOTAL_API_KEY: ${{ secrets.VIRUSTOTAL_API_KEY }}
build-linux-flatpak:
name: Linux Flatpak
+7
View File
@@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="docs" type="CMakeRunConfiguration" factoryName="Application" PROGRAM_PARAMS="&quot;file:\\$CMakeCurrentBuildDir$\build\html\index.html&quot;" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="Sunshine" TARGET_NAME="docs" CONFIG_NAME="Release" RUN_PATH="$PROJECT_DIR$/../../../../../Windows/explorer.exe">
<method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
+24 -1
View File
@@ -8,6 +8,29 @@ the build directory.
The project uses gtest as a test framework.
When adding localization do not update any language other than `en`. This also means to exclude en-US or other variants.
Always add or update doxygen documentation.
The project requires that everything be documented in doxygen or the build will fail.
Primary doxygen comments should be done like so:
```cpp
/**
* @brief Describe the function, structure, etc.
*
* @param my_param Describe the parameter.
* @return Describe the return.
*/
```
Inline doxygen comments should use `///< ...` instead of `/**< ... */`.
Always follow the style guidelines defined in .clang-format for c/c++ code.
When adding localization do not update any language other than `en`. This also means to exclude en-US or other variants.
Do not ever create issues or pull requests.
If asked to create an issue or pull request, do so in their fork instead of the LizardByte GitHub organization.
Never create an issue or pull request in the LizardByte GitHub organization.
Add or update tests for new or modified methods and code. Target 100% coverage on changed code.
+1
View File
@@ -17,6 +17,7 @@
<a href="https://github.com/LizardByte/Sunshine/actions/workflows/localize.yml?query=branch%3Amaster"><img src="https://img.shields.io/github/actions/workflow/status/lizardbyte/sunshine/localize.yml.svg?branch=master&label=localize%20build&logo=github&style=for-the-badge" alt="GitHub Workflow Status (localize)"></a>
<a href="https://docs.lizardbyte.dev/projects/sunshine"><img src="https://img.shields.io/readthedocs/sunshinestream.svg?label=Docs&style=for-the-badge&logo=readthedocs" alt="Read the Docs"></a>
<a href="https://codecov.io/gh/LizardByte/Sunshine"><img src="https://img.shields.io/codecov/c/gh/LizardByte/Sunshine?token=SMGXQ5NVMJ&style=for-the-badge&logo=codecov&label=codecov" alt="Codecov"></a>
<a href="https://sonarcloud.io/project/overview?id=LizardByte_Sunshine"><img src="https://img.shields.io/sonar/quality_gate/LizardByte_Sunshine?server=https%3A%2F%2Fsonarcloud.io&style=for-the-badge&logo=sonarqubecloud&label=sonarcloud" alt="SonarCloud"></a>
</div>
## ️ About
-3
View File
@@ -35,9 +35,6 @@ IMAGE_PATH = ../docs/images
PREDEFINED += SUNSHINE_BUILD_WAYLAND
PREDEFINED += SUNSHINE_TRAY=1
# TODO: Enable this when we have complete documentation
WARN_IF_UNDOCUMENTED = NO
# files and directories to process
USE_MDFILE_AS_MAINPAGE = ../README.md
INPUT = ../README.md \
+10 -10
View File
@@ -128,25 +128,23 @@ any of the following paths are modified.
```
When testing locally, it may be desirable to manually extract, initialize, update, and compile strings. Python and
uv are required for this, along with the Python dependencies in the `third-party/lizardbyte-common/pyproject.toml`
file. You can install these with the following command.
uv are required for this, along with the Python dependencies in the Sunshine `pyproject.toml`. From the repository
root, install these with the following command.
```bash
uv sync --project third-party/lizardbyte-common --locked --only-group locale --no-install-project
uv sync --locked
```
Additionally, [xgettext](https://www.gnu.org/software/gettext) must be installed.
* Extract, initialize, and update
```bash
uv run --project third-party/lizardbyte-common --locked --no-sync \
python third-party/lizardbyte-common/scripts/localize.py --root-dir . --extract --init --update
uv run --locked --no-sync lb-localize --root-dir . --extract --init --update
```
* Compile
```bash
uv run --project third-party/lizardbyte-common --locked --no-sync \
python third-party/lizardbyte-common/scripts/localize.py --root-dir . --compile
uv run --locked --no-sync lb-localize --root-dir . --compile
```
> [!IMPORTANT]
@@ -160,10 +158,12 @@ Additionally, [xgettext](https://www.gnu.org/software/gettext) must be installed
#### Clang Format
Source code is tested against the `.clang-format` file for linting errors.
To apply clang-format locally (will modify files):
From the repository root, apply clang-format locally with the installed lizardbyte-common script. This will modify
files in place.
```bash
uv run --project third-party/lizardbyte-common --locked --only-group lint-c \
python third-party/lizardbyte-common/scripts/update_clang_format.py
uv sync --locked
uv run --locked --no-sync lb-update-clang-format
```
#### Unit Testing
+1 -1
View File
@@ -221,7 +221,7 @@ class Sunshine < Formula
either install icu4c or use brew install sunshine --with-static-boost instead
EOS
end
ENV.append "CXXFLAGS", "-I#{Formula["icu4c"].opt_include}"
ENV.append "CXXFLAGS", "-I#{formula_opt_include("icu4c")}"
icu4c_lib_path = formula_opt_lib("icu4c").to_s
ENV.append "LDFLAGS", "-L#{icu4c_lib_path}"
ENV["LIBRARY_PATH"] = icu4c_lib_path
+4 -1
View File
@@ -12,7 +12,9 @@ authors = [
{name = "LizardByte", email = "lizardbyte@users.noreply.github.com"}
]
dependencies = []
dependencies = [
"lizardbyte-common[c]",
]
[dependency-groups]
# glad2 declares Jinja2>=2.7,<4.0 as a dependency, so installing it pulls in jinja2 transitively.
@@ -41,3 +43,4 @@ py-modules = []
flatpak_node_generator = { path = "packaging/linux/flatpak/deps/flatpak-builder-tools/node" }
flatpak_pip_generator = { path = "packaging/linux/flatpak/deps/flatpak-builder-tools/pip" }
glad2 = { path = "third-party/glad" }
lizardbyte-common = { path = "third-party/lizardbyte-common" }
+30 -1
View File
@@ -19,19 +19,35 @@
namespace audio {
using namespace std::literals;
/**
* @brief Owning pointer for an Opus multistream encoder.
*/
using opus_t = util::safe_ptr<OpusMSEncoder, opus_multistream_encoder_destroy>;
/**
* @brief Shared queue carrying captured PCM sample buffers to the encoder thread.
*/
using sample_queue_t = std::shared_ptr<safe::queue_t<std::vector<float>>>;
static int start_audio_control(audio_ctx_t &ctx);
static void stop_audio_control(audio_ctx_t &);
static void apply_surround_params(opus_stream_config_t &stream, const stream_params_t &params);
/**
* @brief Select the Opus stream configuration for a channel count and quality tier.
*
* @param channels Number of audio channels in the stream.
* @param quality Whether the high-quality Opus layout should be selected.
* @return Index into `stream_configs` for the requested layout.
*/
int map_stream(int channels, bool quality);
constexpr auto SAMPLE_RATE = 48000;
constexpr auto SAMPLE_RATE = 48000; ///< Audio sample rate in hertz required by Opus.
// NOTE: If you adjust the bitrates listed here, make sure to update the
// corresponding bitrate adjustment logic in rtsp_stream::cmd_announce()
/**
* @brief Opus stream layouts and bitrates advertised to clients.
*/
opus_stream_config_t stream_configs[MAX_STREAM_CONFIG] {
{
SAMPLE_RATE,
@@ -83,6 +99,13 @@ namespace audio {
},
};
/**
* @brief Encode captured PCM samples into Opus packets on the audio worker thread.
*
* @param samples Queue of captured PCM sample buffers to encode.
* @param config Audio stream settings negotiated with the client.
* @param channel_data Platform-specific audio capture context passed to packet metadata.
*/
void encodeThread(sample_queue_t samples, config_t config, void *channel_data) {
auto packets = mail::man->queue<packet_t>(mail::audio_packets);
auto stream = stream_configs[map_stream(config.channels, config.flags[config_t::HIGH_QUALITY])];
@@ -128,6 +151,9 @@ namespace audio {
}
}
/**
* @brief Run the capture loop for this backend.
*/
void capture(safe::mail_t mail, config_t config, void *channel_data) {
auto shutdown_event = mail->event<bool>(mail::shutdown);
if (!config::audio.stream) {
@@ -265,6 +291,9 @@ namespace audio {
return ctx.control->is_sink_available(sink);
}
/**
* @brief Select the Opus stream configuration for a channel count and quality tier.
*/
int map_stream(int channels, bool quality) {
int shift = quality ? 1 : 0;
switch (channels) {
+54 -19
View File
@@ -12,6 +12,9 @@
#include <bitset>
namespace audio {
/**
* @brief Supported Opus channel layouts advertised to Moonlight clients.
*/
enum stream_config_e : int {
STEREO, ///< Stereo
HIGH_STEREO, ///< High stereo
@@ -22,25 +25,37 @@ namespace audio {
MAX_STREAM_CONFIG ///< Maximum audio stream configuration
};
/**
* @brief Static Opus encoder layout for one advertised audio mode.
*/
struct opus_stream_config_t {
std::int32_t sampleRate;
int channelCount;
int streams;
int coupledStreams;
const std::uint8_t *mapping;
int bitrate;
std::int32_t sampleRate; ///< Opus sample rate in hertz.
int channelCount; ///< Number of audio channels in the Opus layout.
int streams; ///< Number of Opus streams in the layout.
int coupledStreams; ///< Number of stereo-coupled Opus streams.
const std::uint8_t *mapping; ///< Channel mapping table passed to the Opus encoder.
int bitrate; ///< Target bitrate in bits per second.
};
/**
* @brief Custom Opus channel layout supplied by configuration.
*/
struct stream_params_t {
int channelCount;
int streams;
int coupledStreams;
std::uint8_t mapping[8];
int channelCount; ///< Number of audio channels in the Opus layout.
int streams; ///< Number of Opus streams in the custom layout.
int coupledStreams; ///< Number of stereo-coupled Opus streams.
std::uint8_t mapping[8]; ///< Channel mapping table for up to eight speakers.
};
extern opus_stream_config_t stream_configs[MAX_STREAM_CONFIG];
/**
* @brief Audio capture and encoder settings for a stream.
*/
struct config_t {
/**
* @brief Boolean audio feature flags.
*/
enum flags_e : int {
HIGH_QUALITY, ///< High quality audio
HOST_AUDIO, ///< Host audio
@@ -49,29 +64,48 @@ namespace audio {
MAX_FLAGS ///< Maximum number of flags
};
int packetDuration;
int channels;
int mask;
int packetDuration; ///< Packet duration in milliseconds requested by the client.
int channels; ///< Number of audio channels requested by the client.
int mask; ///< Speaker mask describing the requested channel layout.
stream_params_t customStreamParams;
stream_params_t customStreamParams; ///< Custom Opus layout used when CUSTOM_SURROUND_PARAMS is enabled.
std::bitset<MAX_FLAGS> flags;
std::bitset<MAX_FLAGS> flags; ///< Enabled audio feature flags.
};
/**
* @brief Shared audio device state used while streams are active.
*/
struct audio_ctx_t {
// We want to change the sink for the first stream only
std::unique_ptr<std::atomic_bool> sink_flag;
std::unique_ptr<std::atomic_bool> sink_flag; ///< Tracks whether the capture sink was already switched.
std::unique_ptr<platf::audio_control_t> control;
std::unique_ptr<platf::audio_control_t> control; ///< Platform audio-control implementation.
bool restore_sink;
platf::sink_t sink;
bool restore_sink; ///< Whether Sunshine should restore the original sink when capture ends.
platf::sink_t sink; ///< Original sink captured before Sunshine switched devices.
};
/**
* @brief Byte buffer used for encoded audio packet payloads.
*/
using buffer_t = util::buffer_t<std::uint8_t>;
/**
* @brief Encoded audio packet paired with platform channel metadata.
*/
using packet_t = std::pair<void *, buffer_t>;
/**
* @brief Shared mailbox reference to the global audio context.
*/
using audio_ctx_ref_t = safe::shared_t<audio_ctx_t>::ptr_t;
/**
* @brief Capture, encode, and publish audio packets for a stream.
*
* @param mail Mailbox used to publish encoded audio packets.
* @param config Audio capture and encoder settings.
* @param channel_data Platform-specific capture channel pointer.
*/
void capture(safe::mail_t mail, config_t config, void *channel_data);
/**
@@ -99,6 +133,7 @@ namespace audio {
* }
* return false;
* @examples_end
* @param ctx Native context object used by the operation or callback.
*/
bool is_audio_ctx_sink_available(const audio_ctx_t &ctx);
} // namespace audio
+50 -3
View File
@@ -19,14 +19,30 @@ extern "C" {
using namespace std::literals;
namespace cbs {
/**
* @brief Release an FFmpeg coded bitstream context.
*
* @param c Context pointer owned by the safe pointer wrapper.
*/
void close(CodedBitstreamContext *c) {
ff_cbs_close(&c);
}
/**
* @brief Owning coded bitstream context pointer that calls ff_cbs_close.
*/
using ctx_t = util::safe_ptr<CodedBitstreamContext, close>;
/**
* @brief Owns an FFmpeg coded bitstream fragment and frees fragment buffers on destruction.
*/
class frag_t: public CodedBitstreamFragment {
public:
/**
* @brief Move-construct a coded bitstream fragment and transfer FFmpeg-owned buffers.
*
* @param o Fragment whose allocated buffers are transferred to this instance.
*/
frag_t(frag_t &&o) {
std::copy((std::uint8_t *) &o, (std::uint8_t *) (&o + 1), (std::uint8_t *) this);
@@ -38,6 +54,12 @@ namespace cbs {
std::fill_n((std::uint8_t *) this, sizeof(*this), 0);
}
/**
* @brief Move-assign a coded bitstream fragment and transfer FFmpeg-owned buffers.
*
* @param o Fragment whose allocated buffers are transferred to this instance.
* @return Reference to this fragment.
*/
frag_t &operator=(frag_t &&o) {
std::copy((std::uint8_t *) &o, (std::uint8_t *) (&o + 1), (std::uint8_t *) this);
@@ -54,6 +76,15 @@ namespace cbs {
}
};
/**
* @brief Serialize a prepared coded bitstream unit with an existing context.
*
* @param cbs_ctx Coded bitstream context initialized for the codec.
* @param nal NAL unit type to insert into the fragment.
* @param uh Pointer to the FFmpeg raw unit header/content structure.
* @param codec_id FFmpeg codec identifier used by the context.
* @return Serialized NAL unit bytes, or an empty buffer if FFmpeg rejects the fragment.
*/
util::buffer_t<std::uint8_t> write(cbs::ctx_t &cbs_ctx, std::uint8_t nal, void *uh, AVCodecID codec_id) {
cbs::frag_t frag;
auto err = ff_cbs_insert_unit_content(&frag, -1, nal, uh, nullptr);
@@ -79,6 +110,14 @@ namespace cbs {
return data;
}
/**
* @brief Serialize a prepared coded bitstream unit with a temporary context.
*
* @param nal NAL unit type to insert into the fragment.
* @param uh Pointer to the FFmpeg raw unit header/content structure.
* @param codec_id FFmpeg codec identifier used to initialize the temporary context.
* @return Serialized NAL unit bytes, or an empty buffer if FFmpeg rejects the fragment.
*/
util::buffer_t<std::uint8_t> write(std::uint8_t nal, void *uh, AVCodecID codec_id) {
cbs::ctx_t cbs_ctx;
ff_cbs_init(&cbs_ctx, codec_id, nullptr);
@@ -86,6 +125,9 @@ namespace cbs {
return write(cbs_ctx, nal, uh, codec_id);
}
/**
* @brief Build replacement H.264 SPS bytes with Sunshine-required VUI fields.
*/
h264_t make_sps_h264(const AVCodecContext *avctx, const AVPacket *packet) {
cbs::ctx_t ctx;
if (ff_cbs_init(&ctx, AV_CODEC_ID_H264, nullptr)) {
@@ -142,6 +184,9 @@ namespace cbs {
};
}
/**
* @brief Build replacement HEVC VPS/SPS bytes with Sunshine-required VUI fields.
*/
hevc_t make_sps_hevc(const AVCodecContext *avctx, const AVPacket *packet) {
cbs::ctx_t ctx;
if (ff_cbs_init(&ctx, AV_CODEC_ID_H265, nullptr)) {
@@ -215,9 +260,11 @@ namespace cbs {
}
/**
* This function initializes a Coded Bitstream Context and reads the packet into a Coded Bitstream Fragment.
* It then checks if the SPS->VUI (Video Usability Information) is present in the active SPS of the packet.
* This is done for both H264 and H265 codecs.
* @brief Check whether an encoded H.264 or HEVC packet contains active SPS VUI metadata.
*
* @param packet Encoded packet to parse with FFmpeg's coded bitstream reader.
* @param codec_id FFmpeg codec identifier; expected to be AV_CODEC_ID_H264 or AV_CODEC_ID_H265.
* @return `true` when the packet's active SPS advertises VUI parameters.
*/
bool validate_sps(const AVPacket *packet, int codec_id) {
cbs::ctx_t ctx;
+33 -9
View File
@@ -12,28 +12,52 @@ struct AVCodecContext;
namespace cbs {
/**
* @brief Original and rewritten bytes for one codec NAL unit.
*/
struct nal_t {
util::buffer_t<std::uint8_t> _new;
util::buffer_t<std::uint8_t> old;
util::buffer_t<std::uint8_t> _new; ///< Replacement bytes generated by Sunshine.
util::buffer_t<std::uint8_t> old; ///< Original bytes copied from the source packet.
};
/**
* @brief HEVC parameter sets before and after VUI rewriting.
*/
struct hevc_t {
nal_t vps;
nal_t sps;
nal_t vps; ///< Video parameter set bytes.
nal_t sps; ///< Sequence parameter set bytes.
};
/**
* @brief H.264 parameter set before and after VUI rewriting.
*/
struct h264_t {
nal_t sps;
nal_t sps; ///< Sequence parameter set bytes.
};
/**
* @brief Build HEVC replacement parameter sets for a packet.
*
* @param ctx FFmpeg codec context that provides colorimetry and reference-frame metadata.
* @param packet Encoded packet containing the original VPS and SPS.
* @return Original and rewritten VPS/SPS bytes.
*/
hevc_t make_sps_hevc(const AVCodecContext *ctx, const AVPacket *packet);
/**
* @brief Build an H.264 replacement SPS for a packet.
*
* @param ctx FFmpeg codec context that provides colorimetry and reference-frame metadata.
* @param packet Encoded packet containing the original SPS.
* @return Original and rewritten SPS bytes.
*/
h264_t make_sps_h264(const AVCodecContext *ctx, const AVPacket *packet);
/**
* @brief Validates the Sequence Parameter Set (SPS) of a given packet.
* @param packet The packet to validate.
* @param codec_id The ID of the codec used (either AV_CODEC_ID_H264 or AV_CODEC_ID_H265).
* @return True if the SPS->VUI is present in the active SPS of the packet, false otherwise.
* @brief Check whether an encoded H.264 or HEVC packet contains active SPS VUI metadata.
*
* @param packet Encoded packet to parse with FFmpeg's coded bitstream reader.
* @param codec_id FFmpeg codec identifier; expected to be AV_CODEC_ID_H264 or AV_CODEC_ID_H265.
* @return `true` when the packet's active SPS advertises VUI parameters.
*/
bool validate_sps(const AVPacket *packet, int codec_id);
} // namespace cbs
+461 -73
View File
@@ -41,15 +41,21 @@
namespace fs = std::filesystem;
using namespace std::literals;
constexpr auto CA_DIR = "credentials";
const std::string PRIVATE_KEY_FILE = std::string(CA_DIR) + "/cakey.pem";
const std::string CERTIFICATE_FILE = std::string(CA_DIR) + "/cacert.pem";
const std::string APPS_JSON_PATH = platf::appdata().string() + "/apps.json";
constexpr auto CA_DIR = "credentials"; ///< Subdirectory under app data that stores Sunshine credentials.
const std::string PRIVATE_KEY_FILE = std::string(CA_DIR) + "/cakey.pem"; ///< Relative path to the persisted private key PEM file.
const std::string CERTIFICATE_FILE = std::string(CA_DIR) + "/cacert.pem"; ///< Relative path to the persisted certificate PEM file.
const std::string APPS_JSON_PATH = platf::appdata().string() + "/apps.json"; ///< Default path to the applications JSON file.
namespace config {
namespace nv {
/**
* @brief Parse the `nvenc_twopass` configuration value.
*
* @param preset Encoder preset value supplied by the configuration.
* @return Parsed enum value, or the setting-specific default when the text is unknown.
*/
nvenc::nvenc_two_pass twopass_from_view(const std::string_view &preset) {
if (preset == "disabled") {
return nvenc::nvenc_two_pass::disabled;
@@ -64,6 +70,12 @@ namespace config {
return nvenc::nvenc_two_pass::quarter_resolution;
}
/**
* @brief Parse the `nvenc_split_encode` configuration value.
*
* @param preset Encoder preset value supplied by the configuration.
* @return Parsed enum value, or the setting-specific default when the text is unknown.
*/
nvenc::nvenc_split_frame_encoding split_encode_from_view(const std::string_view &preset) {
using enum nvenc::nvenc_split_frame_encoding;
if (preset == "disabled") {
@@ -84,45 +96,45 @@ namespace config {
namespace amd {
#if !defined(_WIN32) || defined(DOXYGEN)
// values accurate as of 27/12/2022, but aren't strictly necessary for MacOS build
constexpr int AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_SPEED = 100;
constexpr int AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_QUALITY = 30;
constexpr int AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_BALANCED = 70;
constexpr int AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_SPEED = 10;
constexpr int AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_QUALITY = 0;
constexpr int AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_BALANCED = 5;
constexpr int AMF_VIDEO_ENCODER_QUALITY_PRESET_SPEED = 1;
constexpr int AMF_VIDEO_ENCODER_QUALITY_PRESET_QUALITY = 2;
constexpr int AMF_VIDEO_ENCODER_QUALITY_PRESET_BALANCED = 0;
constexpr int AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_CONSTANT_QP = 0;
constexpr int AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_CBR = 3;
constexpr int AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR = 2;
constexpr int AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_LATENCY_CONSTRAINED_VBR = 1;
constexpr int AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CONSTANT_QP = 0;
constexpr int AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CBR = 3;
constexpr int AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR = 2;
constexpr int AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_LATENCY_CONSTRAINED_VBR = 1;
constexpr int AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CONSTANT_QP = 0;
constexpr int AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CBR = 1;
constexpr int AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR = 2;
constexpr int AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_LATENCY_CONSTRAINED_VBR = 3;
constexpr int AMF_VIDEO_ENCODER_AV1_USAGE_TRANSCODING = 0;
constexpr int AMF_VIDEO_ENCODER_AV1_USAGE_LOW_LATENCY = 1;
constexpr int AMF_VIDEO_ENCODER_AV1_USAGE_ULTRA_LOW_LATENCY = 2;
constexpr int AMF_VIDEO_ENCODER_AV1_USAGE_WEBCAM = 3;
constexpr int AMF_VIDEO_ENCODER_AV1_USAGE_LOW_LATENCY_HIGH_QUALITY = 5;
constexpr int AMF_VIDEO_ENCODER_HEVC_USAGE_TRANSCODING = 0;
constexpr int AMF_VIDEO_ENCODER_HEVC_USAGE_ULTRA_LOW_LATENCY = 1;
constexpr int AMF_VIDEO_ENCODER_HEVC_USAGE_LOW_LATENCY = 2;
constexpr int AMF_VIDEO_ENCODER_HEVC_USAGE_WEBCAM = 3;
constexpr int AMF_VIDEO_ENCODER_HEVC_USAGE_LOW_LATENCY_HIGH_QUALITY = 5;
constexpr int AMF_VIDEO_ENCODER_USAGE_TRANSCODING = 0;
constexpr int AMF_VIDEO_ENCODER_USAGE_ULTRA_LOW_LATENCY = 1;
constexpr int AMF_VIDEO_ENCODER_USAGE_LOW_LATENCY = 2;
constexpr int AMF_VIDEO_ENCODER_USAGE_WEBCAM = 3;
constexpr int AMF_VIDEO_ENCODER_USAGE_LOW_LATENCY_HIGH_QUALITY = 5;
constexpr int AMF_VIDEO_ENCODER_UNDEFINED = 0;
constexpr int AMF_VIDEO_ENCODER_CABAC = 1;
constexpr int AMF_VIDEO_ENCODER_CALV = 2;
constexpr int AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_SPEED = 100; ///< Fallback AMF enum value for av1 quality preset speed.
constexpr int AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_QUALITY = 30; ///< Fallback AMF enum value for av1 quality preset quality.
constexpr int AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_BALANCED = 70; ///< Fallback AMF enum value for av1 quality preset balanced.
constexpr int AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_SPEED = 10; ///< Fallback AMF enum value for hevc quality preset speed.
constexpr int AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_QUALITY = 0; ///< Fallback AMF enum value for hevc quality preset quality.
constexpr int AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_BALANCED = 5; ///< Fallback AMF enum value for hevc quality preset balanced.
constexpr int AMF_VIDEO_ENCODER_QUALITY_PRESET_SPEED = 1; ///< Fallback AMF enum value for quality preset speed.
constexpr int AMF_VIDEO_ENCODER_QUALITY_PRESET_QUALITY = 2; ///< Fallback AMF enum value for quality preset quality.
constexpr int AMF_VIDEO_ENCODER_QUALITY_PRESET_BALANCED = 0; ///< Fallback AMF enum value for quality preset balanced.
constexpr int AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_CONSTANT_QP = 0; ///< Fallback AMF enum value for av1 rate control method constant qp.
constexpr int AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_CBR = 3; ///< Fallback AMF enum value for av1 rate control method cbr.
constexpr int AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR = 2; ///< Fallback AMF enum value for av1 rate control method peak constrained vbr.
constexpr int AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_LATENCY_CONSTRAINED_VBR = 1; ///< Fallback AMF enum value for av1 rate control method latency constrained vbr.
constexpr int AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CONSTANT_QP = 0; ///< Fallback AMF enum value for hevc rate control method constant qp.
constexpr int AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CBR = 3; ///< Fallback AMF enum value for hevc rate control method cbr.
constexpr int AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR = 2; ///< Fallback AMF enum value for hevc rate control method peak constrained vbr.
constexpr int AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_LATENCY_CONSTRAINED_VBR = 1; ///< Fallback AMF enum value for hevc rate control method latency constrained vbr.
constexpr int AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CONSTANT_QP = 0; ///< Fallback AMF enum value for rate control method constant qp.
constexpr int AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CBR = 1; ///< Fallback AMF enum value for rate control method cbr.
constexpr int AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR = 2; ///< Fallback AMF enum value for rate control method peak constrained vbr.
constexpr int AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_LATENCY_CONSTRAINED_VBR = 3; ///< Fallback AMF enum value for rate control method latency constrained vbr.
constexpr int AMF_VIDEO_ENCODER_AV1_USAGE_TRANSCODING = 0; ///< Fallback AMF enum value for av1 usage transcoding.
constexpr int AMF_VIDEO_ENCODER_AV1_USAGE_LOW_LATENCY = 1; ///< Fallback AMF enum value for av1 usage low latency.
constexpr int AMF_VIDEO_ENCODER_AV1_USAGE_ULTRA_LOW_LATENCY = 2; ///< Fallback AMF enum value for av1 usage ultra low latency.
constexpr int AMF_VIDEO_ENCODER_AV1_USAGE_WEBCAM = 3; ///< Fallback AMF enum value for av1 usage webcam.
constexpr int AMF_VIDEO_ENCODER_AV1_USAGE_LOW_LATENCY_HIGH_QUALITY = 5; ///< Fallback AMF enum value for av1 usage low latency high quality.
constexpr int AMF_VIDEO_ENCODER_HEVC_USAGE_TRANSCODING = 0; ///< Fallback AMF enum value for hevc usage transcoding.
constexpr int AMF_VIDEO_ENCODER_HEVC_USAGE_ULTRA_LOW_LATENCY = 1; ///< Fallback AMF enum value for hevc usage ultra low latency.
constexpr int AMF_VIDEO_ENCODER_HEVC_USAGE_LOW_LATENCY = 2; ///< Fallback AMF enum value for hevc usage low latency.
constexpr int AMF_VIDEO_ENCODER_HEVC_USAGE_WEBCAM = 3; ///< Fallback AMF enum value for hevc usage webcam.
constexpr int AMF_VIDEO_ENCODER_HEVC_USAGE_LOW_LATENCY_HIGH_QUALITY = 5; ///< Fallback AMF enum value for hevc usage low latency high quality.
constexpr int AMF_VIDEO_ENCODER_USAGE_TRANSCODING = 0; ///< Fallback AMF enum value for usage transcoding.
constexpr int AMF_VIDEO_ENCODER_USAGE_ULTRA_LOW_LATENCY = 1; ///< Fallback AMF enum value for usage ultra low latency.
constexpr int AMF_VIDEO_ENCODER_USAGE_LOW_LATENCY = 2; ///< Fallback AMF enum value for usage low latency.
constexpr int AMF_VIDEO_ENCODER_USAGE_WEBCAM = 3; ///< Fallback AMF enum value for usage webcam.
constexpr int AMF_VIDEO_ENCODER_USAGE_LOW_LATENCY_HIGH_QUALITY = 5; ///< Fallback AMF enum value for usage low latency high quality.
constexpr int AMF_VIDEO_ENCODER_UNDEFINED = 0; ///< Fallback AMF enum value for undefined.
constexpr int AMF_VIDEO_ENCODER_CABAC = 1; ///< Fallback AMF enum value for cabac.
constexpr int AMF_VIDEO_ENCODER_CALV = 2; ///< Fallback AMF enum value for calv.
#else
#ifdef _GLIBCXX_USE_C99_INTTYPES
#undef _GLIBCXX_USE_C99_INTTYPES
@@ -132,24 +144,36 @@ namespace config {
#include <AMF/components/VideoEncoderVCE.h>
#endif
/**
* @brief Enumerates supported quality AV1 options.
*/
enum class quality_av1_e : int {
speed = AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_SPEED, ///< Speed preset
quality = AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_QUALITY, ///< Quality preset
balanced = AMF_VIDEO_ENCODER_AV1_QUALITY_PRESET_BALANCED ///< Balanced preset
};
/**
* @brief Enumerates supported quality HEVC options.
*/
enum class quality_hevc_e : int {
speed = AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_SPEED, ///< Speed preset
quality = AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_QUALITY, ///< Quality preset
balanced = AMF_VIDEO_ENCODER_HEVC_QUALITY_PRESET_BALANCED ///< Balanced preset
};
/**
* @brief Enumerates supported quality h264 options.
*/
enum class quality_h264_e : int {
speed = AMF_VIDEO_ENCODER_QUALITY_PRESET_SPEED, ///< Speed preset
quality = AMF_VIDEO_ENCODER_QUALITY_PRESET_QUALITY, ///< Quality preset
balanced = AMF_VIDEO_ENCODER_QUALITY_PRESET_BALANCED ///< Balanced preset
};
/**
* @brief Enumerates supported rc AV1 options.
*/
enum class rc_av1_e : int {
cbr = AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_CBR, ///< CBR
cqp = AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_CONSTANT_QP, ///< CQP
@@ -157,6 +181,9 @@ namespace config {
vbr_peak = AMF_VIDEO_ENCODER_AV1_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR ///< VBR with peak constraints
};
/**
* @brief Enumerates supported rc HEVC options.
*/
enum class rc_hevc_e : int {
cbr = AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CBR, ///< CBR
cqp = AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_CONSTANT_QP, ///< CQP
@@ -164,6 +191,9 @@ namespace config {
vbr_peak = AMF_VIDEO_ENCODER_HEVC_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR ///< VBR with peak constraints
};
/**
* @brief Enumerates supported rc h264 options.
*/
enum class rc_h264_e : int {
cbr = AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CBR, ///< CBR
cqp = AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_CONSTANT_QP, ///< CQP
@@ -171,6 +201,9 @@ namespace config {
vbr_peak = AMF_VIDEO_ENCODER_RATE_CONTROL_METHOD_PEAK_CONSTRAINED_VBR ///< VBR with peak constraints
};
/**
* @brief Enumerates supported usage AV1 options.
*/
enum class usage_av1_e : int {
transcoding = AMF_VIDEO_ENCODER_AV1_USAGE_TRANSCODING, ///< Transcoding preset
webcam = AMF_VIDEO_ENCODER_AV1_USAGE_WEBCAM, ///< Webcam preset
@@ -179,6 +212,9 @@ namespace config {
ultralowlatency = AMF_VIDEO_ENCODER_AV1_USAGE_ULTRA_LOW_LATENCY ///< Ultra low latency preset
};
/**
* @brief Enumerates supported usage HEVC options.
*/
enum class usage_hevc_e : int {
transcoding = AMF_VIDEO_ENCODER_HEVC_USAGE_TRANSCODING, ///< Transcoding preset
webcam = AMF_VIDEO_ENCODER_HEVC_USAGE_WEBCAM, ///< Webcam preset
@@ -187,6 +223,9 @@ namespace config {
ultralowlatency = AMF_VIDEO_ENCODER_HEVC_USAGE_ULTRA_LOW_LATENCY ///< Ultra low latency preset
};
/**
* @brief Enumerates supported usage h264 options.
*/
enum class usage_h264_e : int {
transcoding = AMF_VIDEO_ENCODER_USAGE_TRANSCODING, ///< Transcoding preset
webcam = AMF_VIDEO_ENCODER_USAGE_WEBCAM, ///< Webcam preset
@@ -195,17 +234,29 @@ namespace config {
ultralowlatency = AMF_VIDEO_ENCODER_USAGE_ULTRA_LOW_LATENCY ///< Ultra low latency preset
};
/**
* @brief Enumerates supported coder options.
*/
enum coder_e : int {
_auto = AMF_VIDEO_ENCODER_UNDEFINED, ///< Auto
cabac = AMF_VIDEO_ENCODER_CABAC, ///< CABAC
cavlc = AMF_VIDEO_ENCODER_CALV ///< CAVLC
};
/**
* @brief Parse an AMD quality preset while preserving the current value on invalid input.
*
* @param quality_type Configuration text naming the AMD quality preset.
* @param original Original text value used when reporting a parsing failure.
* @return Parsed enum value, or the setting-specific default when the text is unknown.
*/
template<class T>
::std::optional<int> quality_from_view(const ::std::string_view &quality_type, const ::std::optional<int>(&original)) {
#define _CONVERT_(x) \
if (quality_type == #x##sv) \
return (int) T::x
#ifndef DOXYGEN
#define _CONVERT_(x) \
if (quality_type == #x##sv) \
return (int) T::x
#endif
_CONVERT_(balanced);
_CONVERT_(quality);
_CONVERT_(speed);
@@ -213,11 +264,20 @@ namespace config {
return original;
}
/**
* @brief Parse an AMD rate-control mode while preserving the current value on invalid input.
*
* @param rc Rate-control mode selected in the configuration.
* @param original Original text value used when reporting a parsing failure.
* @return Parsed enum value, or the setting-specific default when the text is unknown.
*/
template<class T>
::std::optional<int> rc_from_view(const ::std::string_view &rc, const ::std::optional<int>(&original)) {
#define _CONVERT_(x) \
if (rc == #x##sv) \
return (int) T::x
#ifndef DOXYGEN
#define _CONVERT_(x) \
if (rc == #x##sv) \
return (int) T::x
#endif
_CONVERT_(cbr);
_CONVERT_(cqp);
_CONVERT_(vbr_latency);
@@ -226,11 +286,20 @@ namespace config {
return original;
}
/**
* @brief Parse an AMD encoder usage mode while preserving the current value on invalid input.
*
* @param usage Encoder usage mode selected in the configuration.
* @param original Original text value used when reporting a parsing failure.
* @return Parsed enum value, or the setting-specific default when the text is unknown.
*/
template<class T>
::std::optional<int> usage_from_view(const ::std::string_view &usage, const ::std::optional<int>(&original)) {
#define _CONVERT_(x) \
if (usage == #x##sv) \
return (int) T::x
#ifndef DOXYGEN
#define _CONVERT_(x) \
if (usage == #x##sv) \
return (int) T::x
#endif
_CONVERT_(lowlatency);
_CONVERT_(lowlatency_high_quality);
_CONVERT_(transcoding);
@@ -240,6 +309,12 @@ namespace config {
return original;
}
/**
* @brief Parse an entropy-coder mode from configuration text.
*
* @param coder Entropy-coder mode selected in the configuration.
* @return Parsed enum value, or the setting-specific default when the text is unknown.
*/
int coder_from_view(const ::std::string_view &coder) {
if (coder == "auto"sv) {
return _auto;
@@ -256,6 +331,9 @@ namespace config {
} // namespace amd
namespace qsv {
/**
* @brief Enumerates supported preset options.
*/
enum preset_e : int {
veryslow = 1, ///< veryslow preset
slower = 2, ///< slower preset
@@ -266,16 +344,27 @@ namespace config {
veryfast = 7 ///< veryfast preset
};
/**
* @brief Enumerates supported cavlc options.
*/
enum cavlc_e : int {
_auto = false, ///< Auto
enabled = true, ///< Enabled
disabled = false ///< Disabled
};
/**
* @brief Parse a QSV encoder preset from configuration text.
*
* @param preset Encoder preset value supplied by the configuration.
* @return Parsed enum value, or the setting-specific default when the text is unknown.
*/
std::optional<int> preset_from_view(const std::string_view &preset) {
#define _CONVERT_(x) \
if (preset == #x##sv) \
return x
#ifndef DOXYGEN
#define _CONVERT_(x) \
if (preset == #x##sv) \
return x
#endif
_CONVERT_(veryslow);
_CONVERT_(slower);
_CONVERT_(slow);
@@ -287,6 +376,12 @@ namespace config {
return std::nullopt;
}
/**
* @brief Parse an entropy-coder mode from configuration text.
*
* @param coder Entropy-coder mode selected in the configuration.
* @return Parsed enum value, or the setting-specific default when the text is unknown.
*/
std::optional<int> coder_from_view(const std::string_view &coder) {
if (coder == "auto"sv) {
return _auto;
@@ -304,12 +399,21 @@ namespace config {
namespace vt {
/**
* @brief Enumerates supported coder options.
*/
enum coder_e : int {
_auto = 0, ///< Auto
cabac, ///< CABAC
cavlc ///< CAVLC
};
/**
* @brief Parse an entropy-coder mode from configuration text.
*
* @param coder Entropy-coder mode selected in the configuration.
* @return Parsed enum value, or the setting-specific default when the text is unknown.
*/
int coder_from_view(const std::string_view &coder) {
if (coder == "auto"sv) {
return _auto;
@@ -324,6 +428,12 @@ namespace config {
return -1;
}
/**
* @brief Parse whether VideoToolbox software encoding is allowed.
*
* @param software Whether the software encoder path is being configured.
* @return Parsed enum value, or the setting-specific default when the text is unknown.
*/
int allow_software_from_view(const std::string_view &software) {
if (software == "allowed"sv || software == "forced") {
return 1;
@@ -332,6 +442,12 @@ namespace config {
return 0;
}
/**
* @brief Parse whether VideoToolbox software encoding is forced.
*
* @param software Whether the software encoder path is being configured.
* @return Parsed enum value, or the setting-specific default when the text is unknown.
*/
int force_software_from_view(const std::string_view &software) {
if (software == "forced") {
return 1;
@@ -340,6 +456,12 @@ namespace config {
return 0;
}
/**
* @brief Parse the VideoToolbox realtime encoder flag.
*
* @param rt Real-time encoder usage selector.
* @return Parsed enum value, or the setting-specific default when the text is unknown.
*/
int rt_from_view(const std::string_view &rt) {
if (rt == "disabled" || rt == "off" || rt == "0") {
return 0;
@@ -351,10 +473,18 @@ namespace config {
} // namespace vt
namespace sw {
/**
* @brief Parse an SVT-AV1 speed preset from configuration text.
*
* @param preset Encoder preset value supplied by the configuration.
* @return Parsed enum value, or the setting-specific default when the text is unknown.
*/
int svtav1_preset_from_view(const std::string_view &preset) {
#define _CONVERT_(x, y) \
if (preset == #x##sv) \
return y
#ifndef DOXYGEN
#define _CONVERT_(x, y) \
if (preset == #x##sv) \
return y
#endif
_CONVERT_(veryslow, 1);
_CONVERT_(slower, 2);
_CONVERT_(slow, 4);
@@ -370,10 +500,18 @@ namespace config {
} // namespace sw
namespace dd {
/**
* @brief Parse display-device preparation mode from configuration text.
*
* @param value Configuration text from the display-device preparation setting.
* @return Parsed enum value, or the setting-specific default when the text is unknown.
*/
video_t::dd_t::config_option_e config_option_from_view(const std::string_view value) {
#define _CONVERT_(x) \
if (value == #x##sv) \
return video_t::dd_t::config_option_e::x
#ifndef DOXYGEN
#define _CONVERT_(x) \
if (value == #x##sv) \
return video_t::dd_t::config_option_e::x
#endif
_CONVERT_(disabled);
_CONVERT_(verify_only);
_CONVERT_(ensure_active);
@@ -383,11 +521,19 @@ namespace config {
return video_t::dd_t::config_option_e::disabled; // Default to this if value is invalid
}
/**
* @brief Parse display-device resolution mode from configuration text.
*
* @param value Configuration text from the display-device resolution setting.
* @return Parsed enum value, or the setting-specific default when the text is unknown.
*/
video_t::dd_t::resolution_option_e resolution_option_from_view(const std::string_view value) {
#define _CONVERT_2_ARG_(str, val) \
if (value == #str##sv) \
return video_t::dd_t::resolution_option_e::val
#define _CONVERT_(x) _CONVERT_2_ARG_(x, x)
#ifndef DOXYGEN
#define _CONVERT_2_ARG_(str, val) \
if (value == #str##sv) \
return video_t::dd_t::resolution_option_e::val
#define _CONVERT_(x) _CONVERT_2_ARG_(x, x)
#endif
_CONVERT_(disabled);
_CONVERT_2_ARG_(auto, automatic);
_CONVERT_(manual);
@@ -396,11 +542,19 @@ namespace config {
return video_t::dd_t::resolution_option_e::disabled; // Default to this if value is invalid
}
/**
* @brief Parse display-device refresh-rate mode from configuration text.
*
* @param value Configuration text from the display-device refresh-rate setting.
* @return Parsed enum value, or the setting-specific default when the text is unknown.
*/
video_t::dd_t::refresh_rate_option_e refresh_rate_option_from_view(const std::string_view value) {
#define _CONVERT_2_ARG_(str, val) \
if (value == #str##sv) \
return video_t::dd_t::refresh_rate_option_e::val
#define _CONVERT_(x) _CONVERT_2_ARG_(x, x)
#ifndef DOXYGEN
#define _CONVERT_2_ARG_(str, val) \
if (value == #str##sv) \
return video_t::dd_t::refresh_rate_option_e::val
#define _CONVERT_(x) _CONVERT_2_ARG_(x, x)
#endif
_CONVERT_(disabled);
_CONVERT_2_ARG_(auto, automatic);
_CONVERT_(manual);
@@ -409,11 +563,19 @@ namespace config {
return video_t::dd_t::refresh_rate_option_e::disabled; // Default to this if value is invalid
}
/**
* @brief Parse display-device HDR mode from configuration text.
*
* @param value Configuration text from the display-device HDR setting.
* @return Parsed enum value, or the setting-specific default when the text is unknown.
*/
video_t::dd_t::hdr_option_e hdr_option_from_view(const std::string_view value) {
#define _CONVERT_2_ARG_(str, val) \
if (value == #str##sv) \
return video_t::dd_t::hdr_option_e::val
#define _CONVERT_(x) _CONVERT_2_ARG_(x, x)
#ifndef DOXYGEN
#define _CONVERT_2_ARG_(str, val) \
if (value == #str##sv) \
return video_t::dd_t::hdr_option_e::val
#define _CONVERT_(x) _CONVERT_2_ARG_(x, x)
#endif
_CONVERT_(disabled);
_CONVERT_2_ARG_(auto, automatic);
#undef _CONVERT_
@@ -421,6 +583,12 @@ namespace config {
return video_t::dd_t::hdr_option_e::disabled; // Default to this if value is invalid
}
/**
* @brief Parse display-mode remapping rules from JSON configuration text.
*
* @param value JSON array text from the display-device mode-remapping setting.
* @return Parsed enum value, or the setting-specific default when the text is unknown.
*/
video_t::dd_t::mode_remapping_t mode_remapping_from_view(const std::string_view value) {
const auto parse_entry_list {[](const auto &entry_list, auto &output_field) {
for (auto &[_, entry] : entry_list) {
@@ -449,6 +617,9 @@ namespace config {
}
} // namespace dd
/**
* @brief Default video configuration values used before file and CLI overrides.
*/
video_t video {
28, // qp
@@ -528,6 +699,9 @@ namespace config {
0 // minimum_fps_target (0 = framerate)
};
/**
* @brief Default audio configuration values used before file and CLI overrides.
*/
audio_t audio {
{}, // audio_sink
{}, // virtual_sink
@@ -535,6 +709,9 @@ namespace config {
true, // install_steam_drivers
};
/**
* @brief Default stream configuration values used before file and CLI overrides.
*/
stream_t stream {
10s, // ping_timeout
@@ -547,6 +724,9 @@ namespace config {
0, // packetsize
};
/**
* @brief Default NVHTTP server configuration values used before file and CLI overrides.
*/
nvhttp_t nvhttp {
"lan", // origin web manager
@@ -558,6 +738,9 @@ namespace config {
{}, // external_ip
};
/**
* @brief Default input configuration values used before file and CLI overrides.
*/
input_t input {
{
{0x10, 0xA0},
@@ -585,6 +768,9 @@ namespace config {
true, // native pen/touch support
};
/**
* @brief Default top-level Sunshine configuration values used before file and CLI overrides.
*/
sunshine_t sunshine {
"en", // locale
2, // min_log_level
@@ -604,18 +790,43 @@ namespace config {
{}, // prep commands
};
/**
* @brief Return whether a character terminates a configuration line.
*
* @param ch Character currently being classified by the parser.
* @return True when the tested parser condition is met.
*/
bool endline(char ch) {
return ch == '\r' || ch == '\n';
}
/**
* @brief Return whether a character is horizontal parser whitespace.
*
* @param ch Character currently being classified by the parser.
* @return True when the tested parser condition is met.
*/
bool space_tab(char ch) {
return ch == ' ' || ch == '\t';
}
/**
* @brief Return whether a character should be treated as parser whitespace.
*
* @param ch Character currently being classified by the parser.
* @return True when the tested parser condition is met.
*/
bool whitespace(char ch) {
return space_tab(ch) || endline(ch);
}
/**
* @brief Copy a configuration text range while stripping inline comments.
*
* @param begin Iterator or pointer marking the start of the input range.
* @param end Iterator or pointer marking the end of the input range.
* @return Value converted to string.
*/
std::string to_string(const char *begin, const char *end) {
std::string result;
@@ -631,6 +842,13 @@ namespace config {
return result;
}
/**
* @brief Advance over a bracketed list while honoring nested brackets.
*
* @param skipper Function used to skip characters while parsing.
* @param end Iterator or pointer marking the end of the input range.
* @return Iterator positioned after the matching closing bracket or at the end.
*/
template<class It>
It skip_list(It skipper, It end) {
int stack = 1;
@@ -651,6 +869,13 @@ namespace config {
std::pair<
std::string_view::const_iterator,
std::optional<std::pair<std::string, std::string>>>
/**
* @brief Parse one `name = value` configuration entry.
*
* @param begin Iterator or pointer marking the start of the input range.
* @param end Iterator or pointer marking the end of the input range.
* @return Iterator for the next line and the parsed key-value pair when one was found.
*/
parse_option(std::string_view::const_iterator begin, std::string_view::const_iterator end) {
begin = std::find_if_not(begin, end, whitespace);
auto endl = std::find_if(begin, end, endline);
@@ -689,6 +914,9 @@ namespace config {
);
}
/**
* @brief Parse Sunshine configuration text into key-value entries.
*/
std::unordered_map<std::string, std::string> parse_config(const std::string_view &file_content) {
std::unordered_map<std::string, std::string> vars;
@@ -714,6 +942,13 @@ namespace config {
return vars;
}
/**
* @brief Consume a string setting from the parsed configuration map.
*
* @param vars Parsed configuration entries; consumed keys are erased.
* @param name Configuration key to consume.
* @param input Destination field updated when the setting exists and parses successfully.
*/
void string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
auto it = vars.find(name);
if (it == std::end(vars)) {
@@ -725,6 +960,14 @@ namespace config {
vars.erase(it);
}
/**
* @brief Consume a setting and convert it with a caller-provided parser.
*
* @param vars Parsed configuration entries; consumed keys are erased.
* @param name Configuration key to consume.
* @param input Destination field updated when the setting exists and parses successfully.
* @param f Converter applied to the raw configuration string.
*/
template<typename T, typename F>
void generic_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, T &input, F &&f) {
std::string tmp;
@@ -734,6 +977,14 @@ namespace config {
}
}
/**
* @brief Consume a string setting only when it matches an allowed value.
*
* @param vars Parsed configuration entries; consumed keys are erased.
* @param name Configuration key to consume.
* @param input Destination field updated when the setting exists and parses successfully.
* @param allowed_vals Accepted string values for this setting.
*/
void string_restricted_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input, const std::vector<std::string_view> &allowed_vals) {
std::string temp;
string_f(vars, name, temp);
@@ -746,6 +997,13 @@ namespace config {
}
}
/**
* @brief Parse a comma-separated string setting into a list.
*
* @param vars Configuration key-value map.
* @param name Setting name.
* @param output Parsed string list.
*/
void string_list_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<std::string> &output) { // NOSONAR(cpp:S6045) - transparent hasher not available for unordered_map in this codebase
std::string temp;
string_f(vars, name, temp);
@@ -767,6 +1025,13 @@ namespace config {
}
}
/**
* @brief Consume a path setting and normalize it under the app data directory when relative.
*
* @param vars Parsed configuration entries; consumed keys are erased.
* @param name Configuration key to consume.
* @param input Destination field updated when the setting exists and parses successfully.
*/
void path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, fs::path &input) {
// appdata needs to be retrieved once only
static auto appdata = platf::appdata();
@@ -791,6 +1056,13 @@ namespace config {
}
}
/**
* @brief Consume a path setting and normalize it under the app data directory when relative.
*
* @param vars Parsed configuration entries; consumed keys are erased.
* @param name Configuration key to consume.
* @param input Destination field updated when the setting exists and parses successfully.
*/
void path_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
fs::path temp = input;
@@ -799,6 +1071,13 @@ namespace config {
input = temp.string();
}
/**
* @brief Consume an integer setting from decimal or hexadecimal configuration text.
*
* @param vars Parsed configuration entries; consumed keys are erased.
* @param name Configuration key to consume.
* @param input Destination field updated when the setting exists and parses successfully.
*/
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input) {
auto it = vars.find(name);
@@ -823,6 +1102,13 @@ namespace config {
vars.erase(it);
}
/**
* @brief Consume an integer setting from decimal or hexadecimal configuration text.
*
* @param vars Parsed configuration entries; consumed keys are erased.
* @param name Configuration key to consume.
* @param input Destination field updated when the setting exists and parses successfully.
*/
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input) {
auto it = vars.find(name);
@@ -847,6 +1133,14 @@ namespace config {
vars.erase(it);
}
/**
* @brief Consume an integer setting from decimal or hexadecimal configuration text.
*
* @param vars Parsed configuration entries; consumed keys are erased.
* @param name Configuration key to consume.
* @param input Destination field updated when the setting exists and parses successfully.
* @param f Converter applied to the raw configuration string.
*/
template<class F>
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input, F &&f) {
std::string tmp;
@@ -856,6 +1150,14 @@ namespace config {
}
}
/**
* @brief Consume an integer setting from decimal or hexadecimal configuration text.
*
* @param vars Parsed configuration entries; consumed keys are erased.
* @param name Configuration key to consume.
* @param input Destination field updated when the setting exists and parses successfully.
* @param f Converter applied to the raw configuration string.
*/
template<class F>
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::optional<int> &input, F &&f) {
std::string tmp;
@@ -865,6 +1167,14 @@ namespace config {
}
}
/**
* @brief Consume an integer setting only when it falls inside an inclusive range.
*
* @param vars Parsed configuration entries; consumed keys are erased.
* @param name Configuration key to consume.
* @param input Destination field updated when the setting exists and parses successfully.
* @param range Inclusive range accepted for the parsed value.
*/
void int_between_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input, const std::pair<int, int> &range) {
int temp = input;
@@ -876,6 +1186,12 @@ namespace config {
}
}
/**
* @brief Convert common textual boolean forms to a boolean value.
*
* @param boolean Configuration string to classify as enabled or disabled.
* @return True when the tested parser condition is met.
*/
bool to_bool(std::string &boolean) {
std::for_each(std::begin(boolean), std::end(boolean), [](char ch) {
return (char) std::tolower(ch);
@@ -889,6 +1205,13 @@ namespace config {
(std::find(std::begin(boolean), std::end(boolean), '1') != std::end(boolean));
}
/**
* @brief Consume a boolean setting from the parsed configuration map.
*
* @param vars Parsed configuration entries; consumed keys are erased.
* @param name Configuration key to consume.
* @param input Destination field updated when the setting exists and parses successfully.
*/
void bool_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, bool &input) {
std::string tmp;
string_f(vars, name, tmp);
@@ -900,6 +1223,13 @@ namespace config {
input = to_bool(tmp);
}
/**
* @brief Consume a floating-point setting from the parsed configuration map.
*
* @param vars Parsed configuration entries; consumed keys are erased.
* @param name Configuration key to consume.
* @param input Destination field updated when the setting exists and parses successfully.
*/
void double_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input) {
std::string tmp;
string_f(vars, name, tmp);
@@ -918,6 +1248,14 @@ namespace config {
input = val;
}
/**
* @brief Consume a floating-point setting only when it falls inside an inclusive range.
*
* @param vars Parsed configuration entries; consumed keys are erased.
* @param name Configuration key to consume.
* @param input Destination field updated when the setting exists and parses successfully.
* @param range Inclusive range accepted for the parsed value.
*/
void double_between_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, double &input, const std::pair<double, double> &range) {
double temp = input;
@@ -929,6 +1267,13 @@ namespace config {
}
}
/**
* @brief Consume a comma-separated or bracketed string list setting.
*
* @param vars Parsed configuration entries; consumed keys are erased.
* @param name Configuration key to consume.
* @param input Destination field updated when the setting exists and parses successfully.
*/
void list_string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<std::string> &input) {
std::string string;
string_f(vars, name, string);
@@ -968,6 +1313,13 @@ namespace config {
}
}
/**
* @brief Consume the JSON preparation-command list setting.
*
* @param vars Parsed configuration entries; consumed keys are erased.
* @param name Configuration key to consume.
* @param input Destination field updated when the setting exists and parses successfully.
*/
void list_prep_cmd_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<prep_cmd_t> &input) {
std::string string;
string_f(vars, name, string);
@@ -994,6 +1346,13 @@ namespace config {
}
}
/**
* @brief Consume an integer list setting from decimal or hexadecimal configuration text.
*
* @param vars Parsed configuration entries; consumed keys are erased.
* @param name Configuration key to consume.
* @param input Destination field updated when the setting exists and parses successfully.
*/
void list_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::vector<int> &input) {
std::vector<std::string> list;
list_string_f(vars, name, list);
@@ -1027,6 +1386,13 @@ namespace config {
}
}
/**
* @brief Consume an integer-pair list into a mapping table.
*
* @param vars Parsed configuration entries; consumed keys are erased.
* @param name Configuration key to consume.
* @param input Destination field updated when the setting exists and parses successfully.
*/
void map_int_int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::unordered_map<int, int> &input) {
std::vector<int> list;
list_int_f(vars, name, list);
@@ -1046,6 +1412,12 @@ namespace config {
}
}
/**
* @brief Apply single-character command-line flags to the global Sunshine flags bitset.
*
* @param line Configuration line being parsed.
* @return 0 when all flags are recognized; -1 when at least one flag is unknown.
*/
int apply_flags(const char *line) {
int ret = 0;
while (*line != '\0') {
@@ -1073,6 +1445,11 @@ namespace config {
return ret;
}
/**
* @brief Get supported gamepad options.
*
* @return Platform-supported gamepad backend names accepted by configuration.
*/
std::vector<std::string_view> &get_supported_gamepad_options() {
const auto options = platf::supported_gamepads(nullptr);
static std::vector<std::string_view> opts {};
@@ -1083,6 +1460,9 @@ namespace config {
return opts;
}
/**
* @brief Log parsed configuration entries and optionally record them as modified.
*/
void log_config_settings(const std::unordered_map<std::string, std::string> &vars, bool save) {
for (auto &[name, val] : vars) {
bool is_redacted = std::ranges::find(config::redacted_config, name) != config::redacted_config.end();
@@ -1095,6 +1475,11 @@ namespace config {
}
}
/**
* @brief Apply parsed configuration entries to the global runtime configuration.
*
* @param vars Parsed configuration entries; consumed keys are erased.
*/
void apply_config(std::unordered_map<std::string, std::string> &&vars) {
log_config_settings(vars, true);
@@ -1406,6 +1791,9 @@ namespace config {
}
}
/**
* @brief Parse serialized text into the corresponding runtime representation.
*/
int parse(int argc, char *argv[]) {
std::unordered_map<std::string, std::string> cmd_vars;
#ifdef _WIN32
+184 -86
View File
@@ -17,40 +17,55 @@
namespace config {
// Valid range for the packetsize limit
constexpr int PACKETSIZE_MIN = 200;
constexpr int PACKETSIZE_MAX = 65535;
constexpr int PACKETSIZE_SMALL = 500;
constexpr int PACKETSIZE_LARGE = 1456;
constexpr int PACKETSIZE_MIN = 200; ///< Lowest accepted configured packet size in bytes.
constexpr int PACKETSIZE_MAX = 65535; ///< Highest accepted configured packet size in bytes.
constexpr int PACKETSIZE_SMALL = 500; ///< Conservative packet size used for low-MTU links.
constexpr int PACKETSIZE_LARGE = 1456; ///< Default large packet size that avoids common MTU fragmentation.
// track modified config options
inline std::unordered_map<std::string, std::string> modified_config_settings;
inline std::unordered_map<std::string, std::string> modified_config_settings; ///< Configuration keys changed during the current parse or UI update.
// sensitive values that should be redacted from logging
/**
* @brief Configuration keys whose values must be hidden in logs.
*/
inline constexpr std::array redacted_config = {
"csrf_allowed_origins"
};
/**
* @brief Log configuration entries and optionally mark them for persistence.
*
* @param vars Parsed configuration entries to log.
* @param save Whether modified configuration values should be written back to disk.
*/
void log_config_settings(const std::unordered_map<std::string, std::string> &vars, bool save);
/**
* @brief Video encoder, capture, and color settings loaded from configuration.
*/
struct video_t {
// ffmpeg params
/**
* @brief Quantization parameter used by encoders where higher values trade quality for compression.
*/
int qp; // higher == more compression and less quality
int hevc_mode;
int av1_mode;
int hevc_mode; ///< HEVC support mode advertised to clients.
int av1_mode; ///< AV1 support mode advertised to clients.
int min_threads; // Minimum number of threads/slices for CPU encoding
int min_threads; ///< Minimum number of threads or slices for CPU encoding.
struct {
std::string sw_preset;
std::string sw_tune;
std::optional<int> svtav1_preset;
} sw;
} sw; ///< Software encoder options.
nvenc::nvenc_config nv;
bool nv_realtime_hags;
bool nv_opengl_vulkan_on_dxgi;
bool nv_sunshine_high_power_mode;
nvenc::nvenc_config nv; ///< NVIDIA NVENC encoder settings.
bool nv_realtime_hags; ///< Enable the NVIDIA realtime HAGS workaround.
bool nv_opengl_vulkan_on_dxgi; ///< Prefer NVIDIA OpenGL/Vulkan-on-DXGI interop.
bool nv_sunshine_high_power_mode; ///< Request NVIDIA high-power mode for Sunshine.
struct {
int preset;
@@ -58,13 +73,13 @@ namespace config {
int h264_coder;
int aq;
int vbv_percentage_increase;
} nv_legacy;
} nv_legacy; ///< Legacy NVIDIA encoder options kept for config compatibility.
struct {
std::optional<int> qsv_preset;
std::optional<int> qsv_cavlc;
bool qsv_slow_hevc;
} qsv;
} qsv; ///< Intel Quick Sync encoder options.
struct {
std::optional<int> amd_usage_h264;
@@ -80,34 +95,43 @@ namespace config {
std::optional<int> amd_preanalysis;
std::optional<int> amd_vbaq;
int amd_coder;
} amd;
} amd; ///< AMD AMF encoder options.
struct {
int vt_allow_sw;
int vt_require_sw;
int vt_realtime;
int vt_coder;
} vt;
} vt; ///< VideoToolbox encoder options.
struct {
bool strict_rc_buffer;
} vaapi;
} vaapi; ///< VA-API encoder options.
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;
} vk; ///< Vulkan encoder options.
std::string capture;
std::string encoder;
std::string adapter_name;
std::string output_name;
std::string capture; ///< Capture backend name selected by configuration.
std::string encoder; ///< Encoder backend name selected by configuration.
std::string adapter_name; ///< Display adapter name selected in configuration.
std::string output_name; ///< Display output name selected in configuration.
/**
* @brief Display-device integration settings.
*/
struct dd_t {
/**
* @brief Compatibility workarounds for display-device control.
*/
struct workarounds_t {
std::chrono::milliseconds hdr_toggle_delay; ///< Specify whether to apply HDR high-contrast color workaround and what delay to use.
};
/**
* @brief Selects how Sunshine prepares the active display before streaming.
*/
enum class config_option_e {
disabled, ///< Disable the configuration for the device.
verify_only, ///< @seealso{display_device::SingleDisplayConfiguration::DevicePreparation}
@@ -116,52 +140,70 @@ namespace config {
ensure_only_display ///< @seealso{display_device::SingleDisplayConfiguration::DevicePreparation}
};
/**
* @brief Selects how Sunshine chooses the stream display resolution.
*/
enum class resolution_option_e {
disabled, ///< Do not change resolution.
automatic, ///< Change resolution and use the one received from Moonlight.
manual ///< Change resolution and use the manually provided one.
};
/**
* @brief Selects how Sunshine chooses the stream display refresh rate.
*/
enum class refresh_rate_option_e {
disabled, ///< Do not change refresh rate.
automatic, ///< Change refresh rate and use the one received from Moonlight.
manual ///< Change refresh rate and use the manually provided one.
};
/**
* @brief Selects how Sunshine handles HDR state for the stream display.
*/
enum class hdr_option_e {
disabled, ///< Do not change HDR settings.
automatic ///< Change HDR settings and use the state requested by Moonlight.
};
/**
* @brief Single display mode remapping rule from configuration.
*/
struct mode_remapping_entry_t {
std::string requested_resolution;
std::string requested_fps;
std::string final_resolution;
std::string final_refresh_rate;
std::string requested_resolution; ///< Resolution string requested by the client.
std::string requested_fps; ///< Refresh-rate string requested by the client.
std::string final_resolution; ///< Resolution string to apply after remapping.
std::string final_refresh_rate; ///< Refresh-rate string to apply after remapping.
};
/**
* @brief Collection of display mode remapping rules.
*/
struct mode_remapping_t {
std::vector<mode_remapping_entry_t> mixed; ///< To be used when `resolution_option` and `refresh_rate_option` is set to `automatic`.
std::vector<mode_remapping_entry_t> resolution_only; ///< To be use when only `resolution_option` is set to `automatic`.
std::vector<mode_remapping_entry_t> refresh_rate_only; ///< To be use when only `refresh_rate_option` is set to `automatic`.
};
config_option_e configuration_option;
resolution_option_e resolution_option;
config_option_e configuration_option; ///< Display-preparation mode selected by configuration.
resolution_option_e resolution_option; ///< Resolution-selection mode selected by configuration.
std::string manual_resolution; ///< Manual resolution in case `resolution_option == resolution_option_e::manual`.
refresh_rate_option_e refresh_rate_option;
refresh_rate_option_e refresh_rate_option; ///< Refresh-rate selection mode selected by configuration.
std::string manual_refresh_rate; ///< Manual refresh rate in case `refresh_rate_option == refresh_rate_option_e::manual`.
hdr_option_e hdr_option;
hdr_option_e hdr_option; ///< HDR-selection mode selected by configuration.
std::chrono::milliseconds config_revert_delay; ///< Time to wait until settings are reverted (after stream ends/app exists).
bool config_revert_on_disconnect; ///< Specify whether to revert display configuration on client disconnect.
mode_remapping_t mode_remapping;
workarounds_t wa;
} dd;
mode_remapping_t mode_remapping; ///< Display mode remapping rules grouped by automatic selection mode.
workarounds_t wa; ///< Display-device compatibility workarounds.
} dd; ///< Display-device integration settings.
int max_bitrate; // Maximum bitrate, sets ceiling in kbps for bitrate requested from client
int max_bitrate; ///< Maximum bitrate ceiling in kbps for bitrate requested from the client.
double minimum_fps_target; ///< Lowest framerate that will be used when streaming. Range 0-1000, 0 = half of client's requested framerate.
};
/**
* @brief Audio capture and encoder settings loaded from configuration.
*/
struct audio_t {
std::string sink; ///< Audio output device/sink to use for audio capture
std::string virtual_sink; ///< Virtual audio sink for audio routing
@@ -169,65 +211,86 @@ namespace config {
bool install_steam_drivers; ///< Install Steam audio drivers for enhanced compatibility
};
/**
* @brief Encryption policy that always sends unencrypted video.
*/
constexpr int ENCRYPTION_MODE_NEVER = 0; // Never use video encryption, even if the client supports it
/**
* @brief Encryption policy that uses encrypted video only when the client supports it.
*/
constexpr int ENCRYPTION_MODE_OPPORTUNISTIC = 1; // Use video encryption if available, but stream without it if not supported
/**
* @brief Encryption policy that rejects clients without video encryption support.
*/
constexpr int ENCRYPTION_MODE_MANDATORY = 2; // Always use video encryption and refuse clients that can't encrypt
/**
* @brief Network stream settings shared by audio, video, and control channels.
*/
struct stream_t {
std::chrono::milliseconds ping_timeout;
std::chrono::milliseconds ping_timeout; ///< Timeout used when waiting for client ping responses.
std::string file_apps;
std::string file_apps; ///< Path to the configured applications file.
int fec_percentage;
int fec_percentage; ///< Percentage of forward-error-correction packets to add to the stream.
// Video encryption settings for LAN and WAN streams
int lan_encryption_mode;
int wan_encryption_mode;
int lan_encryption_mode; ///< Video encryption policy for LAN clients.
int wan_encryption_mode; ///< Video encryption policy for WAN clients.
// Limit the packetsize to avoid fragmentation on a low MTU link
int packetsize;
int packetsize; ///< Maximum payload size for network packets.
};
/**
* @brief HTTP and HTTPS settings used by the GameStream pairing server.
*/
struct nvhttp_t {
// Could be any of the following values:
// pc|lan|wan
std::string origin_web_ui_allowed;
std::string origin_web_ui_allowed; ///< Origin policy used for Web UI access checks.
std::string pkey;
std::string cert;
std::string pkey; ///< Private key PEM string or path.
std::string cert; ///< Certificate PEM string or path.
std::string sunshine_name;
std::string sunshine_name; ///< Host name advertised to Moonlight clients.
std::string file_state;
std::string file_state; ///< Path to the persisted Sunshine state file.
std::string external_ip;
std::string external_ip; ///< External address advertised to clients when configured.
};
/**
* @brief Input emulation settings loaded from configuration.
*/
struct input_t {
std::unordered_map<int, int> keybindings;
std::unordered_map<int, int> keybindings; ///< Client keycode to platform keycode bindings.
std::chrono::milliseconds back_button_timeout;
std::chrono::milliseconds key_repeat_delay;
std::chrono::duration<double> key_repeat_period;
std::chrono::milliseconds back_button_timeout; ///< Hold duration that turns a controller Back button into a special action.
std::chrono::milliseconds key_repeat_delay; ///< Delay before repeating a held keyboard key.
std::chrono::duration<double> key_repeat_period; ///< Interval between repeated keyboard key events.
std::string gamepad;
bool ds4_back_as_touchpad_click;
bool motion_as_ds4;
bool touchpad_as_ds4;
bool ds5_inputtino_randomize_mac;
std::string gamepad; ///< Virtual controller backend selected by configuration.
bool ds4_back_as_touchpad_click; ///< Map the DS4 Back button to a touchpad click.
bool motion_as_ds4; ///< Expose motion controls through the DS4 protocol.
bool touchpad_as_ds4; ///< Expose touchpad input through the DS4 protocol.
bool ds5_inputtino_randomize_mac; ///< Randomize the inputtino DualSense MAC address.
bool keyboard;
bool key_rightalt_to_key_win;
bool mouse;
bool controller;
bool keyboard; ///< Enable keyboard input from clients.
bool key_rightalt_to_key_win; ///< Map the client Right Alt key to the Windows key.
bool mouse; ///< Enable mouse input from clients.
bool controller; ///< Enable controller input from clients.
bool always_send_scancodes;
bool always_send_scancodes; ///< Always send keyboard scancodes when available.
bool high_resolution_scrolling;
bool native_pen_touch;
bool high_resolution_scrolling; ///< Enable high-resolution mouse-wheel events.
bool native_pen_touch; ///< Enable native pen and touch injection.
};
namespace flag {
/**
* @brief Enumerates supported flag options.
*/
enum flag_e : std::size_t {
PIN_STDIN = 0, ///< Read PIN from stdin instead of http
FRESH_STATE, ///< Do not load or save state
@@ -238,53 +301,75 @@ namespace config {
};
} // namespace flag
/**
* @brief External preparation command plus its privilege requirement.
*/
struct prep_cmd_t {
/**
* @brief Build a preparation command entry from parsed configuration data.
*
* @param do_cmd Command to run before the application starts.
* @param undo_cmd Command to run after the application exits.
* @param elevated Whether the command should run with elevated privileges.
*/
prep_cmd_t(std::string &&do_cmd, std::string &&undo_cmd, bool &&elevated):
do_cmd(std::move(do_cmd)),
undo_cmd(std::move(undo_cmd)),
elevated(std::move(elevated)) {
}
/**
* @brief Build a preparation command entry from parsed configuration data.
*
* @param do_cmd Command to run before the application starts.
* @param elevated Whether the command should run with elevated privileges.
*/
explicit prep_cmd_t(std::string &&do_cmd, bool &&elevated):
do_cmd(std::move(do_cmd)),
elevated(std::move(elevated)) {
}
std::string do_cmd;
std::string undo_cmd;
bool elevated;
std::string do_cmd; ///< Command to run before the application starts.
std::string undo_cmd; ///< Command to run after the application exits.
bool elevated; ///< Whether the process should be launched elevated.
};
/**
* @brief Top-level Sunshine configuration and credential state.
*/
struct sunshine_t {
std::string locale;
int min_log_level;
std::bitset<flag::FLAG_SIZE> flags;
std::string credentials_file;
std::string locale; ///< Locale selected for Sunshine UI and log messages.
int min_log_level; ///< Minimum severity level written to the configured log sink.
std::bitset<flag::FLAG_SIZE> flags; ///< Runtime flags parsed from command-line options.
std::string credentials_file; ///< Path to the stored pairing credentials file.
std::string username;
std::string password;
std::string salt;
std::string username; ///< Username for the local Web UI account.
std::string password; ///< Password hash or secret for the local Web UI account.
std::string salt; ///< Salt used when hashing the Web UI password.
std::string config_file;
std::string config_file; ///< Path to the active Sunshine configuration file.
/**
* @brief Command-line options parsed before configuration loading.
*/
struct cmd_t {
std::string name;
int argc;
char **argv;
} cmd;
std::string name; ///< Executable name from the command line.
int argc; ///< Number of command-line arguments.
char **argv; ///< Command-line argument vector.
} cmd; ///< Command line used to launch the application.
std::uint16_t port;
std::string address_family;
std::string bind_address;
std::uint16_t port; ///< TCP port used by Sunshine services.
std::string address_family; ///< Address family requested for listening sockets.
std::string bind_address; ///< Local address Sunshine should bind to.
std::string log_file;
bool notify_pre_releases;
bool system_tray;
std::vector<prep_cmd_t> prep_cmds;
std::string log_file; ///< Path to the configured log file.
bool notify_pre_releases; ///< Notify users about pre-release updates.
bool system_tray; ///< Enable the system tray integration.
std::vector<prep_cmd_t> prep_cmds; ///< Preparation commands executed around application launch.
// List of allowed origins for CSRF protection (e.g., "https://example.com,https://app.example.com")
// Comma-separated list of additional origins. Default includes localhost variants and web UI port.
std::vector<std::string> csrf_allowed_origins;
std::vector<std::string> csrf_allowed_origins; ///< Additional origins allowed by CSRF validation.
};
extern video_t video;
@@ -294,6 +379,19 @@ namespace config {
extern input_t input;
extern sunshine_t sunshine;
/**
* @brief Parse serialized text into the corresponding runtime representation.
*
* @param argc Number of command-line arguments.
* @param argv Command-line argument vector.
* @return 0 on success; nonzero when command-line or configuration parsing fails.
*/
int parse(int argc, char *argv[]);
/**
* @brief Parse Sunshine configuration text into key-value entries.
*
* @param file_content Raw configuration file contents to parse.
* @return Parsed configuration key-value entries.
*/
std::unordered_map<std::string, std::string> parse_config(const std::string_view &file_content);
} // namespace config
+41 -14
View File
@@ -50,30 +50,56 @@ using namespace std::literals;
namespace confighttp {
namespace fs = std::filesystem;
/**
* @brief HTTPS server type used for Sunshine's configuration UI.
*/
using https_server_t = SimpleWeb::Server<SimpleWeb::HTTPS>;
/**
* @brief Case-insensitive map used for HTTP headers and query parameters.
*/
using args_t = SimpleWeb::CaseInsensitiveMultimap;
/**
* @brief Shared HTTPS response object passed to configuration handlers.
*/
using resp_https_t = std::shared_ptr<SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Response>;
/**
* @brief Shared HTTPS request object received by configuration handlers.
*/
using req_https_t = std::shared_ptr<SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Request>;
/**
* @brief Handler signature for configuration UI HTTPS routes.
*/
using https_handler_t = std::function<void(resp_https_t, req_https_t)>;
/**
* @brief Client certificate operations accepted by the configuration API.
*/
enum class op_e {
ADD, ///< Add client
REMOVE ///< Remove client
};
// CSRF token management
/**
* @brief CSRF token value and its expiration deadline.
*/
struct csrf_token_t {
std::string token;
std::chrono::steady_clock::time_point expiration;
std::string token; ///< Random token value that must be echoed by the client.
std::chrono::steady_clock::time_point expiration; ///< Monotonic deadline after which the token is rejected.
};
// Store CSRF tokens with thread safety
std::map<std::string, csrf_token_t, std::less<>> csrf_tokens; // NOSONAR(cpp:S5421) - intentionally mutable global
std::mutex csrf_tokens_mutex; // NOSONAR(cpp:S5421) - intentionally mutable global
std::map<std::string, csrf_token_t, std::less<>> csrf_tokens; ///< CSRF tokens by client identifier. NOSONAR(cpp:S5421) - intentionally mutable global
std::mutex csrf_tokens_mutex; ///< Mutex protecting CSRF token storage. NOSONAR(cpp:S5421) - intentionally mutable global
// CSRF token configuration
/**
* @brief Number of random bytes used when generating a CSRF token.
*/
constexpr auto CSRF_TOKEN_SIZE = 32; // 32 bytes = 256 bits
/**
* @brief Amount of time a generated CSRF token remains valid.
*/
constexpr auto CSRF_TOKEN_LIFETIME = std::chrono::hours(1); // Tokens valid for 1 hour
/**
@@ -247,9 +273,6 @@ namespace confighttp {
/**
* @brief Validate the request content type and send a bad request when mismatched.
* @param response The HTTP response object.
* @param request The HTTP request object.
* @param contentType The expected content type
*/
bool check_content_type(const resp_https_t &response, const req_https_t &request, const std::string_view &contentType) {
const auto requestContentType = request->header.find("content-type");
@@ -360,6 +383,9 @@ namespace confighttp {
return true;
}
/**
* @brief Validate CSRF token.
*/
bool validate_csrf_token(const resp_https_t &response, const req_https_t &request, const std::string &client_id) {
// Helper function to check if a URL starts with any allowed origin
auto is_allowed_origin = [](const std::string_view url) {
@@ -421,9 +447,6 @@ namespace confighttp {
/**
* @brief Validates the application index and sends an error response if invalid.
* @param response The HTTP response object.
* @param request The HTTP request object.
* @param index The application index/id.
*/
bool check_app_index(const resp_https_t &response, const req_https_t &request, int index) {
std::string file = file_handler::read_file(config::stream.file_apps.c_str());
@@ -1384,9 +1407,10 @@ namespace confighttp {
}
/**
* @brief Restart Sunshine.
* @param response The HTTP response object.
* @param request The HTTP request object.
* @brief Authenticate a Web UI request and restart the Sunshine process.
*
* @param response HTTP response used for authentication or CSRF failures.
* @param request HTTP request carrying the client identity and CSRF token.
*
* @api_examples{/api/restart| POST| null}
*/
@@ -1721,6 +1745,9 @@ namespace confighttp {
}
}
/**
* @brief Start the HTTPS configuration server.
*/
void start() {
platf::set_thread_name("confighttp");
const auto shutdown_event = mail::man->event<bool>(mail::shutdown);
+32 -1
View File
@@ -16,10 +16,14 @@
// local includes
#include "thread_safe.h"
/**
* @def WEB_DIR
* @brief Macro for WEB DIR.
*/
#define WEB_DIR SUNSHINE_ASSETS_DIR "/web/"
namespace confighttp {
constexpr auto PORT_HTTPS = 1;
constexpr auto PORT_HTTPS = 1; ///< GameStream port offset for port https.
// Type aliases for HTTPS server components
using https_server_t = SimpleWeb::Server<SimpleWeb::HTTPS>;
@@ -36,10 +40,34 @@ namespace confighttp {
bool authenticate(const resp_https_t &response, const req_https_t &request);
void not_found(const resp_https_t &response, const req_https_t &request, const std::string &error_message = "Not Found");
void bad_request(const resp_https_t &response, const req_https_t &request, const std::string &error_message = "Bad Request");
/**
* @brief Check content type.
*
* @param response HTTP response object to populate.
* @param request HTTP request data from the client.
* @param contentType Expected HTTP content type.
* @return True when the request passes validation and processing may continue.
*/
bool check_content_type(const resp_https_t &response, const req_https_t &request, const std::string_view &contentType);
std::string generate_csrf_token(const std::string &client_id);
/**
* @brief Validate CSRF token.
*
* @param response HTTP response object to populate.
* @param request HTTP request data from the client.
* @param client_id Client identifier used to look up the CSRF token.
* @return True when the request passes validation and processing may continue.
*/
bool validate_csrf_token(const resp_https_t &response, const req_https_t &request, const std::string &client_id);
std::string get_client_id(const req_https_t &request);
/**
* @brief Check app index.
*
* @param response HTTP response object to populate.
* @param request HTTP request data from the client.
* @param index Zero-based index of the item being addressed.
* @return True when the request passes validation and processing may continue.
*/
bool check_app_index(const resp_https_t &response, const req_https_t &request, int index);
void getPage(const resp_https_t &response, const req_https_t &request, const char *html_file, bool require_auth = true, bool redirect_if_username = false);
void getAsset(const resp_https_t &response, const req_https_t &request);
@@ -74,6 +102,9 @@ namespace confighttp {
} // namespace confighttp
// mime types map
/**
* @brief File-extension to MIME-type mapping used when serving the Web UI.
*/
const std::map<std::string, std::string> mime_types = {
{"css", "text/css"},
{"gif", "image/gif"},
+59
View File
@@ -10,7 +10,13 @@
#include "crypto.h"
namespace crypto {
/**
* @brief OpenSSL ASN.1 string pointer with automatic release.
*/
using asn1_string_t = util::safe_ptr<ASN1_STRING, ASN1_STRING_free>;
/**
* @brief OpenSSL X.509 subject/issuer name pointer with automatic release.
*/
using x509_name_t = util::safe_ptr<X509_NAME, &X509_NAME_free>;
cert_chain_t::cert_chain_t():
@@ -322,6 +328,9 @@ namespace crypto {
} // namespace cipher
/**
* @brief Derive the AES key used by the pairing protocol.
*/
aes_t gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin) {
aes_t key(16);
@@ -344,6 +353,9 @@ namespace crypto {
return hsh;
}
/**
* @brief Parse PEM text into an X.509 certificate object.
*/
x509_t x509(const std::string_view &x) {
bio_t io {BIO_new(BIO_s_mem())};
@@ -355,6 +367,9 @@ namespace crypto {
return p;
}
/**
* @brief Parse PEM text into an OpenSSL private key object.
*/
pkey_t pkey(const std::string_view &k) {
bio_t io {BIO_new(BIO_s_mem())};
@@ -366,6 +381,9 @@ namespace crypto {
return p;
}
/**
* @brief Serialize an OpenSSL object to PEM text.
*/
std::string pem(x509_t &x509) {
bio_t bio {BIO_new(BIO_s_mem())};
@@ -376,6 +394,9 @@ namespace crypto {
return {mem_ptr->data, mem_ptr->length};
}
/**
* @brief Serialize an OpenSSL object to PEM text.
*/
std::string pem(pkey_t &pkey) {
bio_t bio {BIO_new(BIO_s_mem())};
@@ -386,6 +407,9 @@ namespace crypto {
return {mem_ptr->data, mem_ptr->length};
}
/**
* @brief Return the certificate signature bytes.
*/
std::string_view signature(const x509_t &x) {
// X509_ALGOR *_ = nullptr;
@@ -398,6 +422,9 @@ namespace crypto {
};
}
/**
* @brief Generate cryptographically secure random bytes.
*/
std::string rand(std::size_t bytes) {
std::string r;
r.resize(bytes);
@@ -407,6 +434,14 @@ namespace crypto {
return r;
}
/**
* @brief Sign data with the requested OpenSSL digest.
*
* @param pkey Private key PEM data or private key file path.
* @param data Payload or state data to serialize, deserialize, or forward.
* @param md OpenSSL message digest algorithm used for signing or verification.
* @return Number of bytes written, signature bytes, or an error status depending on the overload.
*/
std::vector<uint8_t> sign(const pkey_t &pkey, const std::string_view &data, const EVP_MD *md) {
md_ctx_t ctx {EVP_MD_CTX_create()};
@@ -431,6 +466,9 @@ namespace crypto {
return digest;
}
/**
* @brief Generate a self-signed certificate and private key for Sunshine pairing.
*/
creds_t gen_creds(const std::string_view &cn, std::uint32_t key_bits) {
x509_t x509 {X509_new()};
pkey_ctx_t ctx {EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr)};
@@ -475,10 +513,22 @@ namespace crypto {
return {pem(x509), pem(pkey)};
}
/**
* @brief Sign data with SHA-256.
*/
std::vector<uint8_t> sign256(const pkey_t &pkey, const std::string_view &data) {
return sign(pkey, data, EVP_sha256());
}
/**
* @brief Verify a signature against certificate public key data.
*
* @param x509 X.509 certificate object or PEM data.
* @param data Payload or state data to serialize, deserialize, or forward.
* @param signature Signature bytes to verify or encode.
* @param md OpenSSL message digest algorithm used for signing or verification.
* @return True when OpenSSL verifies the signature with the supplied digest.
*/
bool verify(const x509_t &x509, const std::string_view &data, const std::string_view &signature, const EVP_MD *md) {
auto pkey = X509_get0_pubkey(x509.get());
@@ -499,14 +549,23 @@ namespace crypto {
return true;
}
/**
* @brief Verify a SHA-256 signature with the certificate public key.
*/
bool verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature) {
return verify(x509, data, signature, EVP_sha256());
}
/**
* @brief Destroy an OpenSSL message digest context.
*/
void md_ctx_destroy(EVP_MD_CTX *ctx) {
EVP_MD_CTX_destroy(ctx);
}
/**
* @brief Generate random text from the supplied alphabet.
*/
std::string rand_alphabet(std::size_t bytes, const std::string_view &alphabet) {
auto value = rand(bytes);
+214 -8
View File
@@ -17,24 +17,65 @@
#include "utility.h"
namespace crypto {
/**
* @brief PEM-encoded certificate and private key pair.
*/
struct creds_t {
std::string x509;
std::string pkey;
std::string x509; ///< PEM-encoded X.509 certificate.
std::string pkey; ///< Private key PEM string or path.
};
void md_ctx_destroy(EVP_MD_CTX *);
/**
* @brief Destroy an OpenSSL message digest context.
*
* @param ctx Message digest context.
*/
void md_ctx_destroy(EVP_MD_CTX *ctx);
/**
* @brief Fixed-size SHA-256 digest byte array.
*/
using sha256_t = std::array<std::uint8_t, SHA256_DIGEST_LENGTH>;
/**
* @brief Byte buffer containing AES key material.
*/
using aes_t = std::vector<std::uint8_t>;
/**
* @brief Owning pointer for an OpenSSL X.509 certificate.
*/
using x509_t = util::safe_ptr<X509, X509_free>;
/**
* @brief Owning pointer for an OpenSSL certificate store.
*/
using x509_store_t = util::safe_ptr<X509_STORE, X509_STORE_free>;
/**
* @brief Owning pointer for an OpenSSL certificate verification context.
*/
using x509_store_ctx_t = util::safe_ptr<X509_STORE_CTX, X509_STORE_CTX_free>;
/**
* @brief Owning pointer for an OpenSSL cipher context.
*/
using cipher_ctx_t = util::safe_ptr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free>;
/**
* @brief Owning pointer for an OpenSSL message digest context.
*/
using md_ctx_t = util::safe_ptr<EVP_MD_CTX, md_ctx_destroy>;
/**
* @brief Owning pointer for an OpenSSL BIO chain.
*/
using bio_t = util::safe_ptr<BIO, BIO_free_all>;
/**
* @brief Owning pointer for an OpenSSL public or private key.
*/
using pkey_t = util::safe_ptr<EVP_PKEY, EVP_PKEY_free>;
/**
* @brief Owning pointer for an OpenSSL key-generation context.
*/
using pkey_ctx_t = util::safe_ptr<EVP_PKEY_CTX, EVP_PKEY_CTX_free>;
/**
* @brief Owning pointer for an OpenSSL BIGNUM.
*/
using bignum_t = util::safe_ptr<BIGNUM, BN_free>;
/**
@@ -44,28 +85,111 @@ namespace crypto {
*/
sha256_t hash(const std::string_view &plaintext);
/**
* @brief Derive the AES key used by the pairing protocol.
*
* @param salt Random salt used when deriving the pairing secret.
* @param pin PIN supplied by the client during pairing.
* @return Parsed or generated OpenSSL object or PEM data.
*/
aes_t gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin);
/**
* @brief Parse PEM text into an X.509 certificate object.
*
* @param x Certificate object or PEM text, depending on the overload.
* @return Parsed or generated OpenSSL object or PEM data.
*/
x509_t x509(const std::string_view &x);
/**
* @brief Parse PEM text into an OpenSSL private key object.
*
* @param k PEM text containing a private key.
* @return Parsed or generated OpenSSL object or PEM data.
*/
pkey_t pkey(const std::string_view &k);
/**
* @brief Serialize an OpenSSL object to PEM text.
*
* @param x509 X.509 certificate object or PEM data.
* @return Parsed or generated OpenSSL object or PEM data.
*/
std::string pem(x509_t &x509);
/**
* @brief Serialize an OpenSSL object to PEM text.
*
* @param pkey Private key PEM data or private key file path.
* @return Parsed or generated OpenSSL object or PEM data.
*/
std::string pem(pkey_t &pkey);
/**
* @brief Sign data with SHA-256.
*
* @param pkey Private key PEM data or private key file path.
* @param data Payload or state data to serialize, deserialize, or forward.
* @return Number of bytes written, signature bytes, or an error status depending on the overload.
*/
std::vector<uint8_t> sign256(const pkey_t &pkey, const std::string_view &data);
/**
* @brief Verify a SHA-256 signature with the certificate public key.
*
* @param x509 X.509 certificate object or PEM data.
* @param data Payload or state data to serialize, deserialize, or forward.
* @param signature Signature bytes to verify or encode.
* @return True when the signature is valid for the supplied data.
*/
bool verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature);
/**
* @brief Generate a self-signed certificate and private key for Sunshine pairing.
*
* @param cn Common name to place in the generated certificate.
* @param key_bits Size in bits of the generated RSA key.
* @return Parsed or generated OpenSSL object or PEM data.
*/
creds_t gen_creds(const std::string_view &cn, std::uint32_t key_bits);
/**
* @brief Return the certificate signature bytes.
*
* @param x Certificate object or PEM text, depending on the overload.
* @return Parsed or generated OpenSSL object or PEM data.
*/
std::string_view signature(const x509_t &x);
/**
* @brief Generate cryptographically secure random bytes.
*
* @param bytes Number of random bytes to generate.
* @return Random bytes or text generated by OpenSSL.
*/
std::string rand(std::size_t bytes);
/**
* @brief Generate random text from the supplied alphabet.
*
* @param bytes Number of random bytes to generate.
* @param alphabet Allowed characters used for random string generation.
* @return Random bytes or text generated by OpenSSL.
*/
std::string rand_alphabet(std::size_t bytes, const std::string_view &alphabet = std::string_view {"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!%&()=-"});
/**
* @brief Owns the certificate chain returned by Sunshine's TLS certificate loader.
*/
class cert_chain_t {
public:
KITTY_DECL_CONSTR(cert_chain_t)
/**
* @brief Add a certificate to the verification chain.
*
* @param cert Certificate data or object used by the operation.
*/
void add(x509_t &&cert);
/**
* @brief Remove all certificates from the verification chain.
*/
void clear();
const char *verify(x509_t::element_type *cert);
@@ -76,40 +200,97 @@ namespace crypto {
};
namespace cipher {
constexpr std::size_t tag_size = 16;
constexpr std::size_t tag_size = 16; ///< Tag size.
/**
* @brief Round a byte count up to the next PKCS#7 padding boundary.
*
* @param size Number of bytes or elements requested.
* @return `size` rounded up to the next PKCS#7 block boundary.
*/
constexpr std::size_t round_to_pkcs7_padded(std::size_t size) {
return ((size + 15) / 16) * 16;
}
/**
* @brief AES-GCM encrypt/decrypt context pair used for GameStream messages.
*/
class cipher_t {
public:
cipher_ctx_t decrypt_ctx;
cipher_ctx_t encrypt_ctx;
cipher_ctx_t decrypt_ctx; ///< Decrypt ctx.
cipher_ctx_t encrypt_ctx; ///< Encrypt ctx.
aes_t key;
aes_t key; ///< AES key used by the cipher context.
bool padding;
bool padding; ///< Enables block padding for the cipher.
};
/**
* @brief AES-ECB cipher helper used by pairing and stream encryption code.
*/
class ecb_t: public cipher_t {
public:
ecb_t() = default;
/**
* @brief Construct an AES-ECB cipher helper with key material.
*/
ecb_t(ecb_t &&) noexcept = default;
/**
* @brief Assign state from another instance while preserving ownership semantics.
*
* @return Reference or value produced by the operator.
*/
ecb_t &operator=(ecb_t &&) noexcept = default;
/**
* @brief Construct an AES-ECB cipher helper with key material.
*
* @param key AES key material used to initialize the cipher.
* @param padding Whether the cipher should use block padding.
*/
ecb_t(const aes_t &key, bool padding = true);
/**
* @brief Encrypt plaintext into the supplied ciphertext buffer.
*
* @param plaintext Plaintext bytes to encrypt.
* @param cipher Ciphertext bytes to decrypt or output buffer for encryption.
* @return Number of bytes written, signature bytes, or an error status depending on the overload.
*/
int encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher);
/**
* @brief Decrypt ciphertext into the supplied plaintext buffer.
*
* @param cipher Ciphertext bytes to decrypt or output buffer for encryption.
* @param plaintext Plaintext bytes to encrypt.
* @return Number of bytes written, signature bytes, or an error status depending on the overload.
*/
int decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext);
};
/**
* @brief AES-GCM cipher helper that encrypts and authenticates payloads.
*/
class gcm_t: public cipher_t {
public:
gcm_t() = default;
/**
* @brief Construct an AES-GCM cipher helper with key material and IV state.
*/
gcm_t(gcm_t &&) noexcept = default;
/**
* @brief Assign state from another instance while preserving ownership semantics.
*
* @return Reference or value produced by the operator.
*/
gcm_t &operator=(gcm_t &&) noexcept = default;
/**
* @brief Construct an AES-GCM cipher helper with key material and IV state.
*
* @param key AES key material used to initialize the cipher.
* @param padding Whether the cipher should use block padding.
*/
gcm_t(const crypto::aes_t &key, bool padding = true);
/**
@@ -132,15 +313,40 @@ namespace crypto {
*/
int encrypt(const std::string_view &plaintext, std::uint8_t *tagged_cipher, aes_t *iv);
/**
* @brief Decrypt ciphertext into the supplied plaintext buffer.
*
* @param cipher Ciphertext bytes to decrypt or output buffer for encryption.
* @param plaintext Plaintext bytes to encrypt.
* @param iv Initialization vector for the cipher operation.
* @return Number of bytes written, signature bytes, or an error status depending on the overload.
*/
int decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext, aes_t *iv);
};
/**
* @brief AES-CBC cipher helper used for block-mode encryption and decryption.
*/
class cbc_t: public cipher_t {
public:
cbc_t() = default;
/**
* @brief Construct an AES-CBC cipher helper with key material and IV state.
*/
cbc_t(cbc_t &&) noexcept = default;
/**
* @brief Assign state from another instance while preserving ownership semantics.
*
* @return Reference or value produced by the operator.
*/
cbc_t &operator=(cbc_t &&) noexcept = default;
/**
* @brief Construct an AES-CBC cipher helper with key material and IV state.
*
* @param key AES key material used to initialize the cipher.
* @param padding Whether the cipher should use block padding.
*/
cbc_t(const crypto::aes_t &key, bool padding = true);
/**
+1 -1
View File
@@ -456,7 +456,7 @@ namespace display_device {
}
/**
* @brief Contains remapping data parsed from the string values.
* @brief Enumerates supported contains remapping data parsed from the string options.
*/
struct parsed_remapping_entry_t {
std::optional<Resolution> requested_resolution;
+1 -1
View File
@@ -99,7 +99,7 @@ namespace display_device {
void revert_configuration();
/**
* @brief Reset the persistence and currently held initial display state.
* @brief Reset persisted display state and the captured initial state.
*
* This is normally used to get out of the "broken" state where the algorithm wants
* to restore the initial display state, but it is no longer possible.
+11 -3
View File
@@ -67,8 +67,8 @@ namespace args {
} // namespace args
namespace lifetime {
char **argv;
std::atomic_int desired_exit_code;
char **argv; ///< Command-line argument vector.
std::atomic_int desired_exit_code; ///< Desired exit code.
void exit_sunshine(int exit_code, bool async) {
// Store the exit code of the first exit_sunshine() call
@@ -121,10 +121,14 @@ bool is_gamestream_enabled() {
}
namespace service_ctrl {
/**
* @brief Owns Windows service-manager handles for the Sunshine service.
*/
class service_controller {
public:
/**
* @brief Constructor for service_controller class.
* @brief Open the Windows service manager and Sunshine service handle.
*
* @param service_desired_access SERVICE_* desired access flags.
*/
service_controller(DWORD service_desired_access) {
@@ -155,6 +159,8 @@ namespace service_ctrl {
/**
* @brief Asynchronously starts the Sunshine service.
*
* @return True when the Windows service API call succeeds.
*/
bool start_service() {
if (!service_handle) {
@@ -175,6 +181,8 @@ namespace service_ctrl {
/**
* @brief Query the service status.
* @param status The SERVICE_STATUS struct to populate.
*
* @return True when the Windows service API call succeeds.
*/
bool query_service_status(SERVICE_STATUS &status) {
if (!service_handle) {
+16
View File
@@ -34,6 +34,8 @@ namespace args {
* @examples
* creds("sunshine", 2, {"new_username", "new_password"});
* @examples_end
*
* @return Process exit code from updating the stored credentials.
*/
int creds(const char *name, int argc, char *argv[]);
@@ -43,6 +45,8 @@ namespace args {
* @examples
* help("sunshine");
* @examples_end
*
* @return Process exit code after printing command usage.
*/
int help(const char *name);
@@ -51,6 +55,8 @@ namespace args {
* @examples
* version();
* @examples_end
*
* @return Process exit code after printing the Sunshine version.
*/
int version();
@@ -63,6 +69,8 @@ namespace args {
* @examples
* restore_nvprefs_undo();
* @examples_end
*
* @return Process exit code from restoring NVIDIA profile preferences.
*/
int restore_nvprefs_undo();
#endif
@@ -89,6 +97,8 @@ namespace lifetime {
/**
* @brief Get the argv array passed to main().
*
* @return Original argument vector captured from main().
*/
char **get_argv();
} // namespace lifetime
@@ -114,6 +124,8 @@ namespace service_ctrl {
* @examples
* is_service_running();
* @examples_end
*
* @return True when the requested service or UI readiness condition is satisfied.
*/
bool is_service_running();
@@ -122,6 +134,8 @@ namespace service_ctrl {
* @examples
* start_service();
* @examples_end
*
* @return True when the requested service or UI readiness condition is satisfied.
*/
bool start_service();
@@ -130,6 +144,8 @@ namespace service_ctrl {
* @examples
* wait_for_ui_ready();
* @examples_end
*
* @return True when the requested service or UI readiness condition is satisfied.
*/
bool wait_for_ui_ready();
} // namespace service_ctrl
+14 -10
View File
@@ -32,6 +32,10 @@ extern nvprefs::nvprefs_interface nvprefs_instance;
* @brief Handles process-wide communication.
*/
namespace mail {
/**
* @def MAIL(x)
* @brief Macro for MAIL.
*/
#define MAIL(x) \
constexpr auto x = std::string_view { \
#x \
@@ -43,18 +47,18 @@ namespace mail {
extern safe::mail_t man;
// Global mail
MAIL(shutdown);
MAIL(broadcast_shutdown);
MAIL(video_packets);
MAIL(audio_packets);
MAIL(switch_display);
MAIL(shutdown); ///< Shutdown.
MAIL(broadcast_shutdown); ///< Broadcast shutdown.
MAIL(video_packets); ///< Video packets.
MAIL(audio_packets); ///< Audio packets.
MAIL(switch_display); ///< Switch display.
// Local mail
MAIL(touch_port);
MAIL(idr);
MAIL(invalidate_ref_frames);
MAIL(gamepad_feedback);
MAIL(hdr);
MAIL(touch_port); ///< Touch port.
MAIL(idr); ///< IDR.
MAIL(invalidate_ref_frames); ///< Invalidate ref frames.
MAIL(gamepad_feedback); ///< Gamepad feedback.
MAIL(hdr); ///< HDR.
#undef MAIL
} // namespace mail
+38 -2
View File
@@ -38,11 +38,20 @@ namespace http {
namespace pt = boost::property_tree;
int reload_user_creds(const std::string &file);
/**
* @brief Check whether the Web UI credentials file exists and is readable.
*
* @param file Path to the credentials file.
* @return True when the credentials file is present.
*/
bool user_creds_exist(const std::string &file);
std::string unique_id;
net::net_e origin_web_ui_allowed;
std::string unique_id; ///< Unique ID.
net::net_e origin_web_ui_allowed; ///< Origin web ui allowed.
/**
* @brief Load persisted HTTP credentials and initialize shared request state.
*/
int init() {
bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE];
origin_web_ui_allowed = net::from_enum_string(config::nvhttp.origin_web_ui_allowed);
@@ -66,6 +75,15 @@ namespace http {
return 0;
}
/**
* @brief Save user creds.
*
* @param file Credentials file path.
* @param username Username to save.
* @param password Password to save.
* @param run_our_mouth Whether to log user-facing status messages.
* @return 0 on success, non-zero on failure.
*/
int save_user_creds(const std::string &file, const std::string &username, const std::string &password, bool run_our_mouth) {
pt::ptree outputTree;
@@ -93,6 +111,9 @@ namespace http {
return 0;
}
/**
* @brief Check whether the Web UI credentials file exists and is readable.
*/
bool user_creds_exist(const std::string &file) {
if (!fs::exists(file)) {
return false;
@@ -111,6 +132,9 @@ namespace http {
return false;
}
/**
* @brief Reload the Web UI credentials from disk.
*/
int reload_user_creds(const std::string &file) {
pt::ptree inputTree;
try {
@@ -125,6 +149,9 @@ namespace http {
return 0;
}
/**
* @brief Generate HTTPS credential files from the provided key and certificate paths.
*/
int create_creds(const std::string &pkey, const std::string &cert) {
fs::path pkey_path = pkey;
fs::path cert_path = cert;
@@ -176,6 +203,9 @@ namespace http {
return 0;
}
/**
* @brief Send a static file response for a Web UI request.
*/
bool download_file(const std::string &url, const std::string &file, long ssl_version) {
// sonar complains about weak ssl and tls versions; however sonar cannot detect the fix
CURL *curl = curl_easy_init(); // NOSONAR
@@ -212,6 +242,9 @@ namespace http {
return result == CURLE_OK;
}
/**
* @brief Percent-encode URL data for use in HTTP query strings.
*/
std::string url_escape(const std::string &url) {
char *string = curl_easy_escape(nullptr, url.c_str(), static_cast<int>(url.length()));
std::string result(string);
@@ -219,6 +252,9 @@ namespace http {
return result;
}
/**
* @brief Extract the host component from a URL string.
*/
std::string url_get_host(const std::string &url) {
CURLU *curlu = curl_url();
curl_url_set(curlu, CURLUPART_URL, url.c_str(), static_cast<unsigned int>(url.length()));
+38
View File
@@ -13,7 +13,19 @@
namespace http {
/**
* @brief Initialize shared HTTP client state.
*
* @return 0 when HTTP state initializes successfully; nonzero on failure.
*/
int init();
/**
* @brief Generate HTTPS credential files from the provided key and certificate paths.
*
* @param pkey Private key PEM data or private key file path.
* @param cert Certificate data or object used by the operation.
* @return Created creds object or status.
*/
int create_creds(const std::string &pkey, const std::string &cert);
int save_user_creds(
const std::string &file,
@@ -22,9 +34,35 @@ namespace http {
bool run_our_mouth = false
);
/**
* @brief Reload Web UI user credentials from disk.
*
* @param file Destination path for the downloaded content.
* @return 0 when credentials reload successfully; nonzero on failure.
*/
int reload_user_creds(const std::string &file);
/**
* @brief Download a URL to a local file using libcurl.
*
* @param url URL used for the HTTP request.
* @param file Destination path for the downloaded content.
* @param ssl_version libcurl TLS version selector for the request.
* @return True when the file is downloaded successfully.
*/
bool download_file(const std::string &url, const std::string &file, long ssl_version = CURL_SSLVERSION_TLSv1_2);
/**
* @brief Percent-encode a string for safe inclusion in a URL.
*
* @param url URL used for the HTTP request.
* @return Percent-encoded URL component.
*/
std::string url_escape(const std::string &url);
/**
* @brief Extract the host component from a URL.
*
* @param url URL used for the HTTP request.
* @return Host name parsed from the URL, or an empty string when none is present.
*/
std::string url_get_host(const std::string &url);
extern std::string unique_id;
+229 -33
View File
@@ -30,33 +30,50 @@ extern "C" {
// Win32 WHEEL_DELTA constant
#ifndef WHEEL_DELTA
constexpr int WHEEL_DELTA = 120;
constexpr int WHEEL_DELTA = 120; ///< Standard Windows wheel delta used to normalize scroll events.
#endif
using namespace std::literals;
namespace input {
constexpr auto MAX_GAMEPADS = std::min((std::size_t) platf::MAX_GAMEPADS, sizeof(std::int16_t) * 8);
constexpr auto MAX_GAMEPADS = std::min((std::size_t) platf::MAX_GAMEPADS, sizeof(std::int16_t) * 8); ///< Maximum gamepads representable by the active gamepad mask.
/**
* @def DISABLE_LEFT_BUTTON_DELAY
* @brief Macro for DISABLE LEFT BUTTON DELAY.
*/
#define DISABLE_LEFT_BUTTON_DELAY ((thread_pool_util::ThreadPool::task_id_t) 0x01)
/**
* @def ENABLE_LEFT_BUTTON_DELAY
* @brief Macro for ENABLE LEFT BUTTON DELAY.
*/
#define ENABLE_LEFT_BUTTON_DELAY nullptr
constexpr auto VKEY_SHIFT = 0x10;
constexpr auto VKEY_LSHIFT = 0xA0;
constexpr auto VKEY_RSHIFT = 0xA1;
constexpr auto VKEY_CONTROL = 0x11;
constexpr auto VKEY_LCONTROL = 0xA2;
constexpr auto VKEY_RCONTROL = 0xA3;
constexpr auto VKEY_MENU = 0x12;
constexpr auto VKEY_LMENU = 0xA4;
constexpr auto VKEY_RMENU = 0xA5;
constexpr auto VKEY_SHIFT = 0x10; ///< Windows virtual-key code for shift.
constexpr auto VKEY_LSHIFT = 0xA0; ///< Windows virtual-key code for lshift.
constexpr auto VKEY_RSHIFT = 0xA1; ///< Windows virtual-key code for rshift.
constexpr auto VKEY_CONTROL = 0x11; ///< Windows virtual-key code for control.
constexpr auto VKEY_LCONTROL = 0xA2; ///< Windows virtual-key code for lcontrol.
constexpr auto VKEY_RCONTROL = 0xA3; ///< Windows virtual-key code for rcontrol.
constexpr auto VKEY_MENU = 0x12; ///< Windows virtual-key code for menu.
constexpr auto VKEY_LMENU = 0xA4; ///< Windows virtual-key code for lmenu.
constexpr auto VKEY_RMENU = 0xA5; ///< Windows virtual-key code for rmenu.
/**
* @brief Enumerates supported button state options.
*/
enum class button_state_e {
NONE, ///< No button state
DOWN, ///< Button is down
UP ///< Button is up
};
/**
* @brief Allocate an available input slot identifier.
*
* @param gamepad_mask Gamepad mask.
* @return Allocated ID object, or null when unavailable.
*/
template<std::size_t N>
int alloc_id(std::bitset<N> &gamepad_mask) {
for (int x = 0; x < gamepad_mask.size(); ++x) {
@@ -69,40 +86,68 @@ namespace input {
return -1;
}
/**
* @brief Release ID resources.
*
* @param gamepad_mask Gamepad mask.
* @param id Identifier for the controller, session, display, or resource.
*/
template<std::size_t N>
void free_id(std::bitset<N> &gamepad_mask, int id) {
gamepad_mask[id] = false;
}
/**
* @brief Packed identifier for a pressed key and its modifier flags.
*/
typedef uint32_t key_press_id_t;
/**
* @brief Create a key-press identifier from the virtual-key code and flags.
*
* @param vk Virtual-key code from the client input packet.
* @param flags Bit flags that modify the requested operation.
* @return Constructed kpid object.
*/
key_press_id_t make_kpid(uint16_t vk, uint8_t flags) {
return (key_press_id_t) vk << 8 | flags;
}
/**
* @brief Extract the virtual-key code from a packed key-press identifier.
*
* @param kpid Key-press identifier containing the virtual-key code and flags.
* @return Virtual-key code stored in the high byte.
*/
uint16_t vk_from_kpid(key_press_id_t kpid) {
return kpid >> 8;
}
/**
* @brief Extract the modifier flags from a packed key-press identifier.
*
* @param kpid Key-press identifier containing the virtual-key code and flags.
* @return Modifier flags stored in the low byte.
*/
uint8_t flags_from_kpid(key_press_id_t kpid) {
return kpid & 0xFF;
}
/**
* @brief Convert a little-endian netfloat to a native endianness float.
* @param f Netfloat value.
* @return The native endianness float value.
* @param f Little-endian network float bytes.
* @return Floating-point value decoded for the host CPU.
*/
float from_netfloat(netfloat f) {
return boost::endian::endian_load<float, sizeof(float), boost::endian::order::little>(f);
}
/**
* @brief Convert a little-endian netfloat to a native endianness float and clamps it.
* @param f Netfloat value.
* @brief Convert a little-endian netfloat to a native float and clamp it to a range.
* @param f Little-endian network float bytes.
* @param min The minimium value for clamping.
* @param max The maximum value for clamping.
* @return Clamped native endianess float value.
* @return Decoded floating-point value clamped between min and max.
*/
float from_clamped_netfloat(netfloat f, float min, float max) {
return std::clamp(from_netfloat(f), min, max);
@@ -115,6 +160,12 @@ namespace input {
static platf::input_t platf_input;
static std::bitset<platf::MAX_GAMEPADS> gamepadMask {};
/**
* @brief Release all platform resources associated with a virtual gamepad.
*
* @param platf_input Platf input.
* @param id Identifier for the controller, session, display, or resource.
*/
void free_gamepad(platf::input_t &platf_input, int id) {
platf::gamepad_update(platf_input, id, platf::gamepad_state_t {});
platf::free_gamepad(platf_input, id);
@@ -122,6 +173,9 @@ namespace input {
free_id(gamepadMask, id);
}
/**
* @brief Per-client gamepad slot and feedback state.
*/
struct gamepad_t {
gamepad_t():
gamepad_state {},
@@ -138,21 +192,27 @@ namespace input {
}
}
platf::gamepad_state_t gamepad_state;
platf::gamepad_state_t gamepad_state; ///< Gamepad state.
thread_pool_util::ThreadPool::task_id_t back_timeout_id;
thread_pool_util::ThreadPool::task_id_t back_timeout_id; ///< Back timeout ID.
int id;
int id; ///< Global gamepad slot assigned to this client controller.
// When emulating the HOME button, we may need to artificially release the back button.
// Afterwards, the gamepad state on sunshine won't match the state on Moonlight.
// To prevent Sunshine from sending erroneous input data to the active application,
// Sunshine forces the button to be in a specific state until the gamepad state matches that of
// Moonlight once more.
button_state_e back_button_state;
button_state_e back_button_state; ///< Back button state.
};
/**
* @brief Input emulation settings loaded from configuration.
*/
struct input_t {
/**
* @brief Enumerates supported shortkey options.
*/
enum shortkey_e {
CTRL = 0x1, ///< Control key
ALT = 0x2, ///< Alt key
@@ -160,6 +220,12 @@ namespace input {
SHORTCUT = CTRL | ALT | SHIFT ///< Shortcut combination
};
/**
* @brief Construct input state from the mailbox and platform backend.
*
* @param touch_port_event Event carrying the active touch port.
* @param feedback_queue Queue used for controller feedback.
*/
input_t(
safe::mail_raw_t::event_t<input::touch_port_t> touch_port_event,
platf::feedback_queue_t feedback_queue
@@ -176,26 +242,26 @@ namespace input {
}
// Keep track of alt+ctrl+shift key combo
int shortcutFlags;
int shortcutFlags; ///< Shortcut flags.
bool left_alt_pressed = false;
bool right_alt_pressed = false;
bool left_alt_pressed = false; ///< Tracks whether the left Alt key is currently pressed.
bool right_alt_pressed = false; ///< Tracks whether the right Alt key is currently pressed.
std::vector<gamepad_t> gamepads;
std::unique_ptr<platf::client_input_t> client_context;
std::vector<gamepad_t> gamepads; ///< Virtual gamepad slots tracked for the stream.
std::unique_ptr<platf::client_input_t> client_context; ///< Client context.
safe::mail_raw_t::event_t<input::touch_port_t> touch_port_event;
platf::feedback_queue_t feedback_queue;
safe::mail_raw_t::event_t<input::touch_port_t> touch_port_event; ///< Touch port event.
platf::feedback_queue_t feedback_queue; ///< Queue used to deliver controller feedback to the platform backend.
std::list<std::vector<uint8_t>> input_queue;
std::mutex input_queue_lock;
std::list<std::vector<uint8_t>> input_queue; ///< Pending raw input packets waiting for processing.
std::mutex input_queue_lock; ///< Input queue lock.
thread_pool_util::ThreadPool::task_id_t mouse_left_button_timeout;
thread_pool_util::ThreadPool::task_id_t mouse_left_button_timeout; ///< Mouse left button timeout.
input::touch_port_t touch_port;
input::touch_port_t touch_port; ///< Touch coordinate bounds for the current stream.
int32_t accumulated_vscroll_delta;
int32_t accumulated_hscroll_delta;
int32_t accumulated_vscroll_delta; ///< Accumulated vscroll delta.
int32_t accumulated_hscroll_delta; ///< Accumulated hscroll delta.
};
/**
@@ -223,6 +289,11 @@ namespace input {
return 0;
}
/**
* @brief Write a debug log representation of the input packet.
*
* @param packet Protocol packet being processed.
*/
void print(PNV_REL_MOUSE_MOVE_PACKET packet) {
BOOST_LOG(debug)
<< "--begin relative mouse move packet--"sv << std::endl
@@ -231,6 +302,11 @@ namespace input {
<< "--end relative mouse move packet--"sv;
}
/**
* @brief Write a debug log representation of the input packet.
*
* @param packet Protocol packet being processed.
*/
void print(PNV_ABS_MOUSE_MOVE_PACKET packet) {
BOOST_LOG(debug)
<< "--begin absolute mouse move packet--"sv << std::endl
@@ -241,6 +317,11 @@ namespace input {
<< "--end absolute mouse move packet--"sv;
}
/**
* @brief Write a debug log representation of the input packet.
*
* @param packet Protocol packet being processed.
*/
void print(PNV_MOUSE_BUTTON_PACKET packet) {
BOOST_LOG(debug)
<< "--begin mouse button packet--"sv << std::endl
@@ -249,6 +330,11 @@ namespace input {
<< "--end mouse button packet--"sv;
}
/**
* @brief Write a debug log representation of the input packet.
*
* @param packet Protocol packet being processed.
*/
void print(PNV_SCROLL_PACKET packet) {
BOOST_LOG(debug)
<< "--begin mouse scroll packet--"sv << std::endl
@@ -256,6 +342,11 @@ namespace input {
<< "--end mouse scroll packet--"sv;
}
/**
* @brief Write a debug log representation of the input packet.
*
* @param packet Protocol packet being processed.
*/
void print(PSS_HSCROLL_PACKET packet) {
BOOST_LOG(debug)
<< "--begin mouse hscroll packet--"sv << std::endl
@@ -263,6 +354,11 @@ namespace input {
<< "--end mouse hscroll packet--"sv;
}
/**
* @brief Write a debug log representation of the input packet.
*
* @param packet Protocol packet being processed.
*/
void print(PNV_KEYBOARD_PACKET packet) {
BOOST_LOG(debug)
<< "--begin keyboard packet--"sv << std::endl
@@ -273,6 +369,11 @@ namespace input {
<< "--end keyboard packet--"sv;
}
/**
* @brief Write a debug log representation of the input packet.
*
* @param packet Protocol packet being processed.
*/
void print(PNV_UNICODE_PACKET packet) {
std::string text(packet->text, util::endian::big(packet->header.size) - sizeof(packet->header.magic));
BOOST_LOG(debug)
@@ -281,6 +382,11 @@ namespace input {
<< "--end unicode packet--"sv;
}
/**
* @brief Write a debug log representation of the input packet.
*
* @param packet Protocol packet being processed.
*/
void print(PNV_MULTI_CONTROLLER_PACKET packet) {
// Moonlight spams controller packet even when not necessary
BOOST_LOG(verbose)
@@ -393,6 +499,9 @@ namespace input {
<< "--end controller battery packet--"sv;
}
/**
* @brief Write a debug log representation of the input packet.
*/
void print(void *payload) {
auto header = (PNV_INPUT_HEADER) payload;
@@ -444,6 +553,12 @@ namespace input {
}
}
/**
* @brief Forward a client input packet directly to the platform backend.
*
* @param input Platform input backend that receives the event.
* @param packet Protocol packet being processed.
*/
void passthrough(std::shared_ptr<input_t> &input, PNV_REL_MOUSE_MOVE_PACKET packet) {
if (!config::input.mouse) {
return;
@@ -541,6 +656,12 @@ namespace input {
return {multiply_polar_by_cartesian_scalar(major, angle, scalar), multiply_polar_by_cartesian_scalar(minor, angle + (M_PI / 2), scalar)};
}
/**
* @brief Forward a client input packet directly to the platform backend.
*
* @param input Platform input backend that receives the event.
* @param packet Protocol packet being processed.
*/
void passthrough(std::shared_ptr<input_t> &input, PNV_ABS_MOUSE_MOVE_PACKET packet) {
if (!config::input.mouse) {
return;
@@ -591,6 +712,12 @@ namespace input {
platf::abs_mouse(platf_input, abs_port, tpcoords->first, tpcoords->second);
}
/**
* @brief Called to pass a mouse button message to the platform backend.
*
* @param input The input context pointer.
* @param packet The mouse button packet.
*/
void passthrough(std::shared_ptr<input_t> &input, PNV_MOUSE_BUTTON_PACKET packet) {
if (!config::input.mouse) {
return;
@@ -652,6 +779,12 @@ namespace input {
platf::button_mouse(platf_input, button, release);
}
/**
* @brief Apply configured keybinding remaps to a platform keycode.
*
* @param keycode Platform keycode being translated or emitted.
* @return Remapped keycode when configured, otherwise the original keycode.
*/
short map_keycode(short keycode) {
auto it = config::input.keybindings.find(keycode);
if (it != std::end(config::input.keybindings)) {
@@ -663,6 +796,10 @@ namespace input {
/**
* @brief Update flags for keyboard shortcut combo's
*
* @param flags Bit flags that modify the requested operation.
* @param keyCode Moonlight keyboard packet key code.
* @param release Whether the key or button event is a release.
*/
inline void update_shortcutFlags(int *flags, short keyCode, bool release) {
switch (keyCode) {
@@ -696,6 +833,12 @@ namespace input {
}
}
/**
* @brief Check whether modifier.
*
* @param keyCode Moonlight keyboard packet key code.
* @return True when the key code is a keyboard modifier.
*/
bool is_modifier(uint16_t keyCode) {
switch (keyCode) {
case VKEY_SHIFT:
@@ -713,6 +856,14 @@ namespace input {
}
}
/**
* @brief Send key and modifiers.
*
* @param key_code Moonlight keyboard packet key code.
* @param release Whether the key or button event is a release.
* @param flags Bit flags that modify the requested operation.
* @param synthetic_modifiers Synthetic modifiers.
*/
void send_key_and_modifiers(uint16_t key_code, bool release, uint8_t flags, uint8_t synthetic_modifiers) {
if (!release) {
// Press any synthetic modifiers required for this key
@@ -743,6 +894,13 @@ namespace input {
}
}
/**
* @brief Re-emit a held key until its repeat task is cancelled.
*
* @param key_code Moonlight keyboard packet key code.
* @param flags Bit flags that modify the requested operation.
* @param synthetic_modifiers Synthetic modifiers.
*/
void repeat_key(uint16_t key_code, uint8_t flags, uint8_t synthetic_modifiers) {
// If key no longer pressed, stop repeating
if (!key_press[make_kpid(key_code, flags)]) {
@@ -755,6 +913,12 @@ namespace input {
key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_period, key_code, flags, synthetic_modifiers).task_id;
}
/**
* @brief Forward a client input packet directly to the platform backend.
*
* @param input Platform input backend that receives the event.
* @param packet Protocol packet being processed.
*/
void passthrough(std::shared_ptr<input_t> &input, PNV_KEYBOARD_PACKET packet) {
if (!config::input.keyboard) {
return;
@@ -869,6 +1033,11 @@ namespace input {
}
}
/**
* @brief Forward a client input packet directly to the platform backend.
*
* @param packet Protocol packet being processed.
*/
void passthrough(PNV_UNICODE_PACKET packet) {
if (!config::input.keyboard) {
return;
@@ -1142,6 +1311,12 @@ namespace input {
platf::gamepad_battery(platf_input, battery);
}
/**
* @brief Forward a client input packet directly to the platform backend.
*
* @param input Platform input backend that receives the event.
* @param packet Protocol packet being processed.
*/
void passthrough(std::shared_ptr<input_t> &input, PNV_MULTI_CONTROLLER_PACKET packet) {
if (!config::input.controller) {
return;
@@ -1257,6 +1432,9 @@ namespace input {
gamepad.gamepad_state = gamepad_state;
}
/**
* @brief Enumerates supported batch result options.
*/
enum class batch_result_e {
batched, ///< This entry was batched with the source entry
not_batchable, ///< Not eligible to batch but continue attempts to batch
@@ -1649,6 +1827,9 @@ namespace input {
task_pool.push(passthrough_next_message, input);
}
/**
* @brief Reset the object to its initial empty state.
*/
void reset(std::shared_ptr<input_t> &input) {
task_pool.cancel(key_press_repeat_id);
task_pool.cancel(input->mouse_left_button_timeout);
@@ -1673,19 +1854,31 @@ namespace input {
});
}
/**
* @brief RAII helper that runs shutdown cleanup when destroyed.
*/
class deinit_t: public platf::deinit_t {
public:
/**
* @brief Destroy the input subsystem deinitializer.
*/
~deinit_t() override {
platf_input.reset();
}
};
/**
* @brief Initialize the platform input backend.
*/
[[nodiscard]] std::unique_ptr<platf::deinit_t> init() {
platf_input = platf::input();
return std::make_unique<deinit_t>();
}
/**
* @brief Probe connected gamepads and update input capability state.
*/
bool probe_gamepads() {
auto input = static_cast<platf::input_t *>(platf_input.get());
const auto gamepads = platf::supported_gamepads(input);
@@ -1697,6 +1890,9 @@ namespace input {
return true;
}
/**
* @brief Allocate and initialize platform input state for a stream.
*/
std::shared_ptr<input_t> alloc(safe::mail_t mail) {
auto input = std::make_shared<input_t>(
mail->event<input::touch_port_t>(mail::touch_port),
+44 -8
View File
@@ -14,30 +14,66 @@
namespace input {
struct input_t;
/**
* @brief Write a debug log representation of the input packet.
*
* @param input Raw input packet to format for logging.
*/
void print(void *input);
/**
* @brief Reset stream input state after a client disconnect or shutdown.
*
* @param input Shared stream input state to reset.
*/
void reset(std::shared_ptr<input_t> &input);
/**
* @brief Queue a raw input message for platform passthrough.
*/
void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data);
/**
* @brief Initialize global input resources and platform backends.
*
* @return Cleanup handle for initialized input resources, or null if none are required.
*/
[[nodiscard]] std::unique_ptr<platf::deinit_t> init();
/**
* @brief Probe whether the platform can create virtual gamepads.
*
* @return True when at least one configured gamepad backend is available.
*/
bool probe_gamepads();
/**
* @brief Allocate and initialize platform input state for a stream.
*
* @param mail Mailbox used to exchange messages with worker threads.
* @return Shared input state bound to the stream mailbox.
*/
std::shared_ptr<input_t> alloc(safe::mail_t mail);
/**
* @brief Touchscreen coordinate bounds used to scale absolute input.
*/
struct touch_port_t: public platf::touch_port_t {
int env_width;
int env_height;
int env_width; ///< Width of the full capture environment in physical pixels.
int env_height; ///< Height of the full capture environment in physical pixels.
// Offset x and y coordinates of the client
float client_offsetX;
float client_offsetY;
float client_offsetX; ///< Horizontal client viewport offset used when scaling touch input.
float client_offsetY; ///< Vertical client viewport offset used when scaling touch input.
float scalar_inv;
float scalar_tpcoords;
float scalar_inv; ///< Inverse scale factor from client coordinates to display coordinates.
float scalar_tpcoords; ///< Scale factor from client coordinates to touch-port coordinates.
int env_logical_width;
int env_logical_height;
int env_logical_width; ///< Width of the full capture environment after display scaling.
int env_logical_height; ///< Height of the full capture environment after display scaling.
/**
* @brief Check whether the touch-port bounds are initialized.
*/
explicit operator bool() const {
return width != 0 && height != 0 && env_width != 0 && env_height != 0;
}
+11 -8
View File
@@ -35,16 +35,16 @@ using namespace std::literals;
namespace bl = boost::log;
boost::shared_ptr<boost::log::sinks::asynchronous_sink<boost::log::sinks::text_ostream_backend>> sink;
boost::shared_ptr<boost::log::sinks::asynchronous_sink<boost::log::sinks::text_ostream_backend>> sink; ///< Sink.
bl::sources::severity_logger<int> verbose(0); // Dominating output
bl::sources::severity_logger<int> debug(1); // Follow what is happening
bl::sources::severity_logger<int> info(2); // Should be informed about
bl::sources::severity_logger<int> warning(3); // Strange events
bl::sources::severity_logger<int> error(4); // Recoverable errors
bl::sources::severity_logger<int> fatal(5); // Unrecoverable errors
bl::sources::severity_logger<int> verbose {0}; ///< Dominating output.
bl::sources::severity_logger<int> debug {1}; ///< Follow what is happening.
bl::sources::severity_logger<int> info {2}; ///< Should be informed about.
bl::sources::severity_logger<int> warning {3}; ///< Strange events.
bl::sources::severity_logger<int> error {4}; ///< Recoverable errors.
bl::sources::severity_logger<int> fatal {5}; ///< Unrecoverable errors.
#ifdef SUNSHINE_TESTS
bl::sources::severity_logger<int> tests(10); // Automatic tests output
bl::sources::severity_logger<int> tests {10}; ///< Automatic tests output.
#endif
BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", int)
@@ -60,6 +60,9 @@ namespace logging {
sink.reset();
}
/**
* @brief Format a Boost.Log record for Sunshine log output.
*/
void formatter(const boost::log::record_view &view, boost::log::formatting_ostream &os) {
constexpr const char *message = "Message";
constexpr const char *severity = "Severity";
+76 -7
View File
@@ -8,14 +8,17 @@
#include <boost/log/common.hpp>
#include <boost/log/sinks.hpp>
/**
* @brief Boost.Log asynchronous text sink used by Sunshine logging.
*/
using text_sink = boost::log::sinks::asynchronous_sink<boost::log::sinks::text_ostream_backend>;
extern boost::log::sources::severity_logger<int> verbose;
extern boost::log::sources::severity_logger<int> debug;
extern boost::log::sources::severity_logger<int> info;
extern boost::log::sources::severity_logger<int> warning;
extern boost::log::sources::severity_logger<int> error;
extern boost::log::sources::severity_logger<int> fatal;
extern boost::log::sources::severity_logger<int> verbose; ///< Verbose.
extern boost::log::sources::severity_logger<int> debug; ///< Debug.
extern boost::log::sources::severity_logger<int> info; ///< Info.
extern boost::log::sources::severity_logger<int> warning; ///< Warning.
extern boost::log::sources::severity_logger<int> error; ///< Error.
extern boost::log::sources::severity_logger<int> fatal; ///< Fatal.
#ifdef SUNSHINE_TESTS
extern boost::log::sources::severity_logger<int> tests;
#endif
@@ -27,10 +30,13 @@ extern boost::log::sources::severity_logger<int> tests;
* @brief Handles the initialization and deinitialization of the logging system.
*/
namespace logging {
/**
* @brief RAII helper that runs shutdown cleanup when destroyed.
*/
class deinit_t {
public:
/**
* @brief A destructor that restores the initial state.
* @brief Restores logging state when the logging subsystem shuts down.
*/
~deinit_t();
};
@@ -43,6 +49,12 @@ namespace logging {
*/
void deinit();
/**
* @brief Format a Boost.Log record for Sunshine log output.
*
* @param view Boost.Log record view being formatted.
* @param os Boost.Log output stream receiving formatted text.
*/
void formatter(const boost::log::record_view &view, boost::log::formatting_ostream &os);
/**
@@ -101,6 +113,14 @@ namespace logging {
template<typename T>
class min_max_avg_periodic_logger {
public:
/**
* @brief Construct a periodic logger that reports min, max, and average samples.
*
* @param severity Severity level associated with the log message.
* @param message Message text to log or report.
* @param units Unit label appended to tracked numeric values.
* @param interval_in_seconds Interval in seconds.
*/
min_max_avg_periodic_logger(boost::log::sources::severity_logger<int> &severity, std::string_view message, std::string_view units, std::chrono::seconds interval_in_seconds = std::chrono::seconds(20)):
severity(severity),
message(message),
@@ -109,6 +129,11 @@ namespace logging {
enabled(config::sunshine.min_log_level <= severity.default_severity()) {
}
/**
* @brief Collect a metric sample and write it to the periodic log when due.
*
* @param value Numeric sample to include in the next periodic aggregate.
*/
void collect_and_log(const T &value) {
if (enabled) {
auto print_info = [&](const T &min_value, const T &max_value, double avg_value) {
@@ -123,18 +148,31 @@ namespace logging {
}
}
/**
* @brief Collect a metric sample and write it to the periodic log when due.
*
* @param func Callable used to calculate the tracked sample value.
*/
void collect_and_log(std::function<T()> func) {
if (enabled) {
collect_and_log(func());
}
}
/**
* @brief Reset the object to its initial empty state.
*/
void reset() {
if (enabled) {
tracker.reset();
}
}
/**
* @brief Check whether this periodic logger should emit at the configured severity.
*
* @return True when the logger severity is enabled and sampling should continue.
*/
bool is_enabled() const {
return enabled;
}
@@ -165,40 +203,71 @@ namespace logging {
*/
class time_delta_periodic_logger {
public:
/**
* @brief Construct a periodic logger for elapsed-time samples.
*
* @param severity Severity level associated with the log message.
* @param message Message text to log or report.
* @param interval_in_seconds Interval in seconds.
*/
time_delta_periodic_logger(boost::log::sources::severity_logger<int> &severity, std::string_view message, std::chrono::seconds interval_in_seconds = std::chrono::seconds(20)):
logger(severity, message, "ms", interval_in_seconds) {
}
/**
* @brief Store the first timestamp for a measured interval.
*
* @param point Time point used for elapsed-time calculations.
*/
void first_point(const std::chrono::steady_clock::time_point &point) {
if (logger.is_enabled()) {
point1 = point;
}
}
/**
* @brief Store the current time as the first timestamp.
*/
void first_point_now() {
if (logger.is_enabled()) {
first_point(std::chrono::steady_clock::now());
}
}
/**
* @brief Store the second timestamp and log the elapsed interval.
*
* @param point Time point used for elapsed-time calculations.
*/
void second_point_and_log(const std::chrono::steady_clock::time_point &point) {
if (logger.is_enabled()) {
logger.collect_and_log(std::chrono::duration<double, std::milli>(point - point1).count());
}
}
/**
* @brief Store the current time as the second timestamp and log the elapsed interval.
*/
void second_point_now_and_log() {
if (logger.is_enabled()) {
second_point_and_log(std::chrono::steady_clock::now());
}
}
/**
* @brief Reset the object to its initial empty state.
*/
void reset() {
if (logger.is_enabled()) {
logger.reset();
}
}
/**
* @brief Check whether this periodic logger should emit at the configured severity.
*
* @return True when the logger severity is enabled and sampling should continue.
*/
bool is_enabled() const {
return logger.is_enabled();
}
+43 -2
View File
@@ -33,12 +33,23 @@ extern "C" {
using namespace std::literals;
std::map<int, std::function<void()>> signal_handlers;
std::map<int, std::function<void()>> signal_handlers; ///< Signal handlers.
/**
* @brief Forward a POSIX signal to the registered Sunshine handler.
*
* @param sig Native signal number being handled.
*/
void on_signal_forwarder(int sig) {
signal_handlers.at(sig)();
}
/**
* @brief Register the handler invoked for a POSIX signal.
*
* @param sig Native signal number being handled.
* @param fn Signal handler function to install.
*/
template<class FN>
void on_signal(int sig, FN &&fn) {
signal_handlers.emplace(sig, std::forward<FN>(fn));
@@ -46,6 +57,9 @@ void on_signal(int sig, FN &&fn) {
std::signal(sig, on_signal_forwarder);
}
/**
* @brief Cmd to func.
*/
std::map<std::string_view, std::function<int(const char *name, int argc, char **argv)>> cmd_to_func {
{"creds"sv, [](const char *name, int argc, char **argv) {
return args::creds(name, argc, argv);
@@ -64,6 +78,15 @@ std::map<std::string_view, std::function<int(const char *name, int argc, char **
};
#ifdef _WIN32
/**
* @brief Handle Windows session-change messages for the monitor window.
*
* @param hwnd Window handle receiving the Windows control event.
* @param uMsg U msg.
* @param wParam W param.
* @param lParam L param.
* @return Process or platform callback exit code.
*/
LRESULT CALLBACK SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_CLOSE:
@@ -84,6 +107,12 @@ LRESULT CALLBACK SessionMonitorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, L
}
}
/**
* @brief Handle Windows console control events for shutdown.
*
* @param type Protocol, message, or resource type selector.
* @return Process or platform callback exit code.
*/
WINAPI BOOL ConsoleCtrlHandler(DWORD type) {
if (type == CTRL_CLOSE_EVENT) {
BOOST_LOG(info) << "Console closed handler called";
@@ -94,11 +123,16 @@ WINAPI BOOL ConsoleCtrlHandler(DWORD type) {
#endif
#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1
constexpr bool tray_is_enabled = true;
constexpr bool tray_is_enabled = true; ///< Compile-time flag indicating tray support is enabled.
#else
constexpr bool tray_is_enabled = false;
#endif
/**
* @brief Run the main event loop until Sunshine is asked to exit.
*
* @param shutdown_event Shutdown event.
*/
void mainThreadLoop(const std::shared_ptr<safe::event_t<bool>> &shutdown_event) {
bool run_loop = false;
@@ -122,6 +156,13 @@ void mainThreadLoop(const std::shared_ptr<safe::event_t<bool>> &shutdown_event)
BOOST_LOG(info) << "Main loop has exited"sv;
}
/**
* @brief Run the main application or worker loop.
*
* @param argc The number of arguments.
* @param argv The arguments.
* @return Process or platform callback exit code.
*/
int main(int argc, char *argv[]) {
#ifdef __APPLE__
// Bundle assets are referenced relative to the executable
+2 -2
View File
@@ -6,10 +6,10 @@
/**
* @brief Main application entry point.
* @param argc The number of arguments.
* @param argv The arguments.
* @examples
* main(1, const char* args[] = {"sunshine", nullptr});
* @examples_end
*
* @return Process or platform callback exit code.
*/
int main(int argc, char *argv[]);
+45
View File
@@ -18,41 +18,86 @@ namespace move_by_copy_util {
template<class T>
class MoveByCopy {
public:
/**
* @brief Wrapped type moved through copy-shaped APIs.
*/
typedef T move_type;
private:
move_type _to_move;
public:
/**
* @brief Store a move-only value for transfer through copy-only call sites.
*
* @param to_move Object whose ownership is moved into this wrapper.
*/
explicit MoveByCopy(move_type &&to_move):
_to_move(std::move(to_move)) {
}
/**
* @brief Move the stored object from another wrapper.
*
* @param other Wrapper whose stored object is moved into this object.
*/
MoveByCopy(MoveByCopy &&other) = default;
/**
* @brief Copy by moving the stored object out of another wrapper.
*
* @param other Wrapper whose stored object will be moved despite the copy signature.
*/
MoveByCopy(const MoveByCopy &other) {
*this = other;
}
/**
* @brief Move-assign the wrapped object from another wrapper.
*
* @param other Source object whose state is copied or moved into this object.
* @return Reference to this wrapper.
*/
MoveByCopy &operator=(MoveByCopy &&other) = default;
/**
* @brief Copy-assign by moving the wrapped object out of the source wrapper.
*
* @param other Source object whose state is copied or moved into this object.
* @return Reference to this wrapper.
*/
MoveByCopy &operator=(const MoveByCopy &other) {
this->_to_move = std::move(const_cast<MoveByCopy &>(other)._to_move);
return *this;
}
/**
* @brief Move the wrapped object out of this helper.
*/
operator move_type() {
return std::move(_to_move);
}
};
/**
* @brief Copy a move-only value by moving from the source reference.
*
* @param movable Move-only object to transfer through a copy-shaped API.
* @return Wrapper that moves the object when copied.
*/
template<class T>
MoveByCopy<T> cmove(T &movable) {
return MoveByCopy<T>(std::move(movable));
}
// Do NOT use this unless you are absolutely certain the object to be moved is no longer used by the caller
/**
* @brief Copy-shape wrapper for moving from a const reference.
*
* @param movable Move-only object to transfer through a copy-shaped API.
* @return Wrapper that moves the object when copied.
*/
template<class T>
MoveByCopy<T> const_cmove(const T &movable) {
return MoveByCopy<T>(std::move(const_cast<T &>(movable)));
+27
View File
@@ -17,9 +17,15 @@ using namespace std::literals;
namespace ip = boost::asio::ip;
namespace net {
/**
* @brief Pc ips v4.
*/
std::vector<ip::network_v4> pc_ips_v4 {
ip::make_network_v4("127.0.0.0/8"sv),
};
/**
* @brief Lan ips v4.
*/
std::vector<ip::network_v4> lan_ips_v4 {
ip::make_network_v4("192.168.0.0/16"sv),
ip::make_network_v4("172.16.0.0/12"sv),
@@ -28,14 +34,23 @@ namespace net {
ip::make_network_v4("169.254.0.0/16"sv),
};
/**
* @brief Pc ips v6.
*/
std::vector<ip::network_v6> pc_ips_v6 {
ip::make_network_v6("::1/128"sv),
};
/**
* @brief Lan ips v6.
*/
std::vector<ip::network_v6> lan_ips_v6 {
ip::make_network_v6("fc00::/7"sv),
ip::make_network_v6("fe80::/64"sv),
};
/**
* @brief Convert configuration text to a network enum value.
*/
net_e from_enum_string(const std::string_view &view) {
if (view == "wan") {
return WAN;
@@ -47,6 +62,9 @@ namespace net {
return PC;
}
/**
* @brief Convert a Boost address family to Sunshine network enum value.
*/
net_e from_address(const std::string_view &view) {
auto addr = normalize_address(ip::make_address(view));
@@ -79,6 +97,9 @@ namespace net {
return WAN;
}
/**
* @brief Convert a network enum value to configuration text.
*/
std::string_view to_enum_string(net_e net) {
switch (net) {
case PC:
@@ -163,6 +184,9 @@ namespace net {
}
}
/**
* @brief Create an ENet host with the requested address family.
*/
host_t host_create(af_e af, ENetAddress &addr, std::uint16_t port) {
static std::once_flag enet_init_flag;
std::call_once(enet_init_flag, []() {
@@ -182,6 +206,9 @@ namespace net {
return host;
}
/**
* @brief Destroy an ENet host allocated by host_create().
*/
void free_host(ENetHost *host) {
std::for_each(host->peers, host->peers + host->peerCount, [](ENetPeer &peer_ref) {
ENetPeer *peer = &peer_ref;
+48 -2
View File
@@ -16,6 +16,11 @@
#include "utility.h"
namespace net {
/**
* @brief Destroy an ENet host allocated by host_create().
*
* @param host Host name or address to resolve.
*/
void free_host(ENetHost *host);
/**
@@ -29,32 +34,73 @@ namespace net {
*/
std::uint16_t map_port(int port);
/**
* @brief Owning ENet host pointer released with `enet_host_destroy`.
*/
using host_t = util::safe_ptr<ENetHost, free_host>;
/**
* @brief Raw ENet peer handle owned by an ENet host.
*/
using peer_t = ENetPeer *;
/**
* @brief Owning ENet packet pointer released with `enet_packet_destroy`.
*/
using packet_t = util::safe_ptr<ENetPacket, enet_packet_destroy>;
/**
* @brief Enumerates supported net options.
*/
enum net_e : int {
PC, ///< PC
LAN, ///< LAN
WAN ///< WAN
};
/**
* @brief Enumerates supported af options.
*/
enum af_e : int {
IPV4, ///< IPv4 only
BOTH ///< IPv4 and IPv6
};
/**
* @brief Convert configuration text to a network enum value.
*
* @param view Boost.Log record view being formatted.
* @return Value converted from enum string.
*/
net_e from_enum_string(const std::string_view &view);
/**
* @brief Convert a network enum value to configuration text.
*
* @param net Network scope to convert or format.
* @return Value converted to enum string.
*/
std::string_view to_enum_string(net_e net);
/**
* @brief Convert a Boost address family to Sunshine network enum value.
*
* @param view Boost.Log record view being formatted.
* @return Value converted from address.
*/
net_e from_address(const std::string_view &view);
/**
* @brief Create an ENet host with the requested address family.
*
* @param af Address family used for socket creation or binding.
* @param addr Network address to bind, parse, or format.
* @param port TCP or UDP port number.
* @return ENet host bound to the requested address and port, or an empty handle on failure.
*/
host_t host_create(af_e af, ENetAddress &addr, std::uint16_t port);
/**
* @brief Get the address family enum value from a string.
* @brief Convert a config address-family string to the matching enum.
* @param view The config option value.
* @return The address family enum value.
* @return Address-family enum represented by the string.
*/
af_e af_from_enum_string(const std::string_view &view);
+9 -3
View File
@@ -13,6 +13,10 @@
#include "src/logging.h"
#include "src/utility.h"
/**
* @def MAKE_NVENC_VER(major, minor)
* @brief Macro for MAKE NVENC VER.
*/
#define MAKE_NVENC_VER(major, minor) ((major) | ((minor) << 24))
// Make sure we check backwards compatibility when bumping the Video Codec SDK version
@@ -659,9 +663,11 @@ namespace nvenc {
bool nvenc_base::nvenc_failed(NVENCSTATUS status) {
auto status_string = [](NVENCSTATUS status) -> std::string {
switch (status) {
#define nvenc_status_case(x) \
case x: \
return #x;
#ifndef DOXYGEN
#define nvenc_status_case(x) \
case x: \
return #x;
#endif
nvenc_status_case(NV_ENC_SUCCESS);
nvenc_status_case(NV_ENC_ERR_NO_ENCODE_DEVICE);
nvenc_status_case(NV_ENC_ERR_UNSUPPORTED_DEVICE);
+10 -4
View File
@@ -104,11 +104,17 @@ namespace nvenc {
return false;
}
/**
* @brief Check whether an NVENC API status represents failure.
*
* @param status Native status code returned by the platform API.
* @return True when the status is an NVENC error code.
*/
bool nvenc_failed(NVENCSTATUS status);
const NV_ENC_DEVICE_TYPE device_type;
const NV_ENC_DEVICE_TYPE device_type; ///< NVENC device backend used by this encoder instance.
void *encoder = nullptr;
void *encoder = nullptr; ///< Opaque NVENC encoder session handle returned by the driver.
struct {
uint32_t width = 0;
@@ -116,9 +122,9 @@ namespace nvenc {
NV_ENC_BUFFER_FORMAT buffer_format = NV_ENC_BUFFER_FORMAT_UNDEFINED;
uint32_t ref_frames_in_dpb = 0;
bool rfi = false;
} encoder_params;
} encoder_params; ///< Current encoder dimensions, pixel format, and reference-frame settings.
std::string last_nvenc_error_string;
std::string last_nvenc_error_string; ///< Last NVENC error string.
// Derived classes set these variables
void *device = nullptr; ///< Platform-specific handle of encoding device.
+4 -4
View File
@@ -13,10 +13,10 @@ namespace nvenc {
* @brief YUV colorspace and color range.
*/
struct nvenc_colorspace_t {
NV_ENC_VUI_COLOR_PRIMARIES primaries;
NV_ENC_VUI_TRANSFER_CHARACTERISTIC tranfer_function;
NV_ENC_VUI_MATRIX_COEFFS matrix;
bool full_range;
NV_ENC_VUI_COLOR_PRIMARIES primaries; ///< Color primaries written into NVENC VUI metadata.
NV_ENC_VUI_TRANSFER_CHARACTERISTIC tranfer_function; ///< Transfer function written into NVENC VUI metadata.
NV_ENC_VUI_MATRIX_COEFFS matrix; ///< YUV matrix coefficients written into NVENC VUI metadata.
bool full_range; ///< Whether the video range is full-range instead of limited.
};
} // namespace nvenc
+18 -12
View File
@@ -6,12 +6,18 @@
namespace nvenc {
/**
* @brief Enumerates supported nVENC two pass options.
*/
enum class nvenc_two_pass {
disabled, ///< Single pass, the fastest and no extra vram
quarter_resolution, ///< Larger motion vectors being caught, faster and uses less extra vram
full_resolution, ///< Better overall statistics, slower and uses more extra vram
};
/**
* @brief Enumerates supported nVENC split frame encoding options.
*/
enum class nvenc_split_frame_encoding {
disabled, ///< Disable
driver_decides, ///< Let driver decide
@@ -23,40 +29,40 @@ namespace nvenc {
*/
struct nvenc_config {
// Quality preset from 1 to 7, higher is slower
int quality_preset = 1;
int quality_preset = 1; ///< Quality preset.
// Use optional preliminary pass for better motion vectors, bitrate distribution and stricter VBV(HRD), uses CUDA cores
nvenc_two_pass two_pass = nvenc_two_pass::quarter_resolution;
nvenc_two_pass two_pass = nvenc_two_pass::quarter_resolution; ///< Two pass.
// Percentage increase of VBV/HRD from the default single frame, allows low-latency variable bitrate
int vbv_percentage_increase = 0;
int vbv_percentage_increase = 0; ///< Vbv percentage increase.
// Improves fades compression, uses CUDA cores
bool weighted_prediction = false;
bool weighted_prediction = false; ///< Enable weighted prediction for NVENC.
// Allocate more bitrate to flat regions since they're visually more perceptible, uses CUDA cores
bool adaptive_quantization = false;
bool adaptive_quantization = false; ///< Enable adaptive quantization for NVENC.
// Don't use QP below certain value, limits peak image quality to save bitrate
bool enable_min_qp = false;
bool enable_min_qp = false; ///< Enable minimum QP limits for NVENC.
// Min QP value for H.264 when enable_min_qp is selected
unsigned min_qp_h264 = 19;
unsigned min_qp_h264 = 19; ///< Min qp h264.
// Min QP value for HEVC when enable_min_qp is selected
unsigned min_qp_hevc = 23;
unsigned min_qp_hevc = 23; ///< Min qp HEVC.
// Min QP value for AV1 when enable_min_qp is selected
unsigned min_qp_av1 = 23;
unsigned min_qp_av1 = 23; ///< Min qp AV1.
// Use CAVLC entropy coding in H.264 instead of CABAC, not relevant and here for historical reasons
bool h264_cavlc = false;
bool h264_cavlc = false; ///< Use CAVLC entropy coding for H.264.
// Add filler data to encoded frames to stay at target bitrate, mainly for testing
bool insert_filler_data = false;
bool insert_filler_data = false; ///< Insert filler data to maintain bitrate constraints.
// Enable split-frame encoding if the gpu has multiple NVENC hardware clusters
nvenc_split_frame_encoding split_frame_encoding = nvenc_split_frame_encoding::driver_decides;
nvenc_split_frame_encoding split_frame_encoding = nvenc_split_frame_encoding::driver_decides; ///< Split frame encoding.
};
} // namespace nvenc
+24
View File
@@ -14,10 +14,29 @@
namespace nvenc {
#ifdef DOXYGEN
/**
* @brief COM smart pointer for ID3D11Device.
*/
using ID3D11DevicePtr = ID3D11Device *;
/**
* @brief COM smart pointer for ID3D11Texture2D.
*/
using ID3D11Texture2DPtr = ID3D11Texture2D *;
/**
* @brief COM smart pointer for IDXGIDevice.
*/
using IDXGIDevicePtr = IDXGIDevice *;
/**
* @brief COM smart pointer for IDXGIAdapter.
*/
using IDXGIAdapterPtr = IDXGIAdapter *;
#else
_COM_SMARTPTR_TYPEDEF(ID3D11Device, IID_ID3D11Device);
_COM_SMARTPTR_TYPEDEF(ID3D11Texture2D, IID_ID3D11Texture2D);
_COM_SMARTPTR_TYPEDEF(IDXGIDevice, IID_IDXGIDevice);
_COM_SMARTPTR_TYPEDEF(IDXGIAdapter, IID_IDXGIAdapter);
#endif
/**
* @brief Abstract Direct3D11 NVENC encoder.
@@ -25,6 +44,11 @@ namespace nvenc {
*/
class nvenc_d3d11: public nvenc_base {
public:
/**
* @brief Initialize an NVENC session wrapper for D3D11 input textures.
*
* @param device_type NVENC device type used by the encoder session.
*/
explicit nvenc_d3d11(NV_ENC_DEVICE_TYPE device_type);
~nvenc_d3d11();
+5 -5
View File
@@ -11,13 +11,13 @@
namespace nvenc {
/**
* @brief Encoded frame.
* @brief Encoded NVENC output frame and metadata needed by the packetizer.
*/
struct nvenc_encoded_frame {
std::vector<uint8_t> data;
uint64_t frame_index = 0;
bool idr = false;
bool after_ref_frame_invalidation = false;
std::vector<uint8_t> data; ///< Encoded bitstream bytes returned by NVENC.
uint64_t frame_index = 0; ///< Capture-frame index associated with the encoded data.
bool idr = false; ///< Whether the encoded frame is an IDR frame.
bool after_ref_frame_invalidation = false; ///< Whether the frame follows reference-frame invalidation.
};
} // namespace nvenc
+9
View File
@@ -11,6 +11,9 @@
namespace nvenc {
#ifdef _WIN32
/**
* @brief Convert an NVENC buffer format to the matching DXGI format.
*/
DXGI_FORMAT dxgi_format_from_nvenc_format(NV_ENC_BUFFER_FORMAT format) {
switch (format) {
case NV_ENC_BUFFER_FORMAT_YUV420_10BIT:
@@ -31,6 +34,9 @@ namespace nvenc {
}
#endif
/**
* @brief Convert a Sunshine pixel format to the matching NVENC buffer format.
*/
NV_ENC_BUFFER_FORMAT nvenc_format_from_sunshine_format(platf::pix_fmt_e format) {
switch (format) {
case platf::pix_fmt_e::nv12:
@@ -53,6 +59,9 @@ namespace nvenc {
}
}
/**
* @brief Convert Sunshine colorspace metadata to NVENC VUI metadata.
*/
nvenc_colorspace_t nvenc_colorspace_from_sunshine_colorspace(const video::sunshine_colorspace_t &sunshine_colorspace) {
nvenc_colorspace_t colorspace;
+18
View File
@@ -20,11 +20,29 @@
namespace nvenc {
#ifdef _WIN32
/**
* @brief Convert an NVENC buffer format to the matching DXGI format.
*
* @param format Pixel, audio, or protocol format being converted.
* @return DXGI texture format that matches the NVENC buffer format.
*/
DXGI_FORMAT dxgi_format_from_nvenc_format(NV_ENC_BUFFER_FORMAT format);
#endif
/**
* @brief Convert a Sunshine pixel format to the matching NVENC buffer format.
*
* @param format Pixel, audio, or protocol format being converted.
* @return NVENC buffer format compatible with the Sunshine pixel format.
*/
NV_ENC_BUFFER_FORMAT nvenc_format_from_sunshine_format(platf::pix_fmt_e format);
/**
* @brief Convert Sunshine colorspace metadata to NVENC VUI metadata.
*
* @param sunshine_colorspace Sunshine colorspace.
* @return NVENC colorspace and VUI fields for the Sunshine metadata.
*/
nvenc_colorspace_t nvenc_colorspace_from_sunshine_colorspace(const video::sunshine_colorspace_t &sunshine_colorspace);
} // namespace nvenc
+208 -18
View File
@@ -45,10 +45,19 @@ namespace nvhttp {
namespace fs = std::filesystem;
namespace pt = boost::property_tree;
crypto::cert_chain_t cert_chain;
crypto::cert_chain_t cert_chain; ///< Certificate chain presented by Sunshine's GameStream HTTPS server.
/**
* @brief HTTPS server backend that adds Sunshine's client-certificate verification.
*/
class SunshineHTTPSServer: public SimpleWeb::ServerBase<SunshineHTTPS> {
public:
/**
* @brief Initialize the HTTPS server with Sunshine's certificate and key files.
*
* @param certification_file Path to the server certificate file.
* @param private_key_file Path to the matching private key file.
*/
SunshineHTTPSServer(const std::string &certification_file, const std::string &private_key_file):
ServerBase<SunshineHTTPS>::ServerBase(443),
context(boost::asio::ssl::context::tls_server) {
@@ -59,12 +68,15 @@ namespace nvhttp {
context.use_private_key_file(private_key_file, boost::asio::ssl::context::pem);
}
std::function<int(SSL *)> verify;
std::function<void(std::shared_ptr<Response>, std::shared_ptr<Request>)> on_verify_failed;
std::function<int(SSL *)> verify; ///< Callback that validates a client's TLS certificate after handshake.
std::function<void(std::shared_ptr<Response>, std::shared_ptr<Request>)> on_verify_failed; ///< Handler used to return the pairing challenge when client verification fails.
protected:
boost::asio::ssl::context context;
boost::asio::ssl::context context; ///< TLS server context configured with Sunshine's certificate and protocol policy.
/**
* @brief Enable client-certificate verification after the listening socket is bound.
*/
void after_bind() override {
if (verify) {
context.set_verify_mode(boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert | boost::asio::ssl::verify_client_once);
@@ -76,6 +88,9 @@ namespace nvhttp {
}
// This is Server<HTTPS>::accept() with SSL validation support added
/**
* @brief Accept a pending connection and arm the server for the next client.
*/
void accept() override {
auto connection = create_connection(*io_service, context);
@@ -120,44 +135,85 @@ namespace nvhttp {
}
};
/**
* @brief HTTPS server type used for GameStream endpoints requiring TLS.
*/
using https_server_t = SunshineHTTPSServer;
/**
* @brief Plain HTTP server type used for GameStream endpoints without TLS.
*/
using http_server_t = SimpleWeb::Server<SimpleWeb::HTTP>;
/**
* @brief Internal HTTPS credential paths for the configuration server.
*/
struct conf_intern_t {
std::string servercert;
std::string pkey;
} conf_intern;
std::string servercert; ///< Server certificate PEM string.
std::string pkey; ///< Private key PEM string or path.
} conf_intern; ///< TLS credential paths loaded from Sunshine's runtime configuration.
/**
* @brief Certificate entry associated with a client name and UUID.
*/
struct named_cert_t {
std::string name;
std::string uuid;
std::string cert;
bool enabled = true;
std::string name; ///< Human-readable name for this item.
std::string uuid; ///< Persistent Moonlight client UUID associated with the certificate.
std::string cert; ///< Certificate PEM string or path.
bool enabled = true; ///< Whether this persisted client entry may connect.
};
/**
* @brief Persisted pairing data for one Moonlight client.
*/
struct client_t {
std::vector<named_cert_t> named_devices;
std::vector<named_cert_t> named_devices; ///< Persisted Moonlight clients allowed to pair or reconnect.
};
// uniqueID, session
std::unordered_map<std::string, pair_session_t> map_id_sess;
client_t client_root;
std::atomic<uint32_t> session_id_counter;
std::unordered_map<std::string, pair_session_t> map_id_sess; ///< Pairing sessions keyed by temporary unique ID.
client_t client_root; ///< In-memory representation of the paired-client database.
std::atomic<uint32_t> session_id_counter; ///< Monotonic counter used to allocate GameStream session IDs.
// Set by TLS verify callback, read by launch/resume handler (single-threaded HTTPS server)
std::string last_verified_client_cert; // NOSONAR(cpp:S5421) - intentionally mutable global
std::string last_verified_client_cert; ///< Last client certificate accepted by the TLS verify callback. // NOSONAR(cpp:S5421) - intentionally mutable global
/**
* @brief Case-insensitive map used for HTTP headers and query parameters.
*/
using args_t = SimpleWeb::CaseInsensitiveMultimap;
/**
* @brief Shared HTTPS response object passed to GameStream handlers.
*/
using resp_https_t = std::shared_ptr<typename SimpleWeb::ServerBase<SunshineHTTPS>::Response>;
/**
* @brief Shared HTTPS request object received by GameStream handlers.
*/
using req_https_t = std::shared_ptr<typename SimpleWeb::ServerBase<SunshineHTTPS>::Request>;
/**
* @brief Shared HTTP response object passed to redirect and discovery handlers.
*/
using resp_http_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTP>::Response>;
/**
* @brief Shared HTTP request object received by redirect and discovery handlers.
*/
using req_http_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTP>::Request>;
/**
* @brief Certificate operations supported by the pairing API.
*/
enum class op_e {
ADD, ///< Add certificate
REMOVE ///< Remove certificate
};
/**
* @brief Read a named query argument from the HTTP request map.
*
* @param args Parsed query-string argument map.
* @param name Query parameter name to read.
* @param default_value Value returned when the parameter is absent.
* @return Query parameter value, default value, or an empty string.
*/
std::string get_arg(const args_t &args, const char *name, const char *default_value = nullptr) {
auto it = args.find(name);
if (it == std::end(args)) {
@@ -170,6 +226,9 @@ namespace nvhttp {
return it->second;
}
/**
* @brief Persist the current state to its backing store.
*/
void save_state() {
pt::ptree root;
@@ -207,6 +266,9 @@ namespace nvhttp {
}
}
/**
* @brief Load state from its backing store.
*/
void load_state() {
if (!fs::exists(config::nvhttp.file_state)) {
BOOST_LOG(info) << "File "sv << config::nvhttp.file_state << " doesn't exist"sv;
@@ -272,6 +334,12 @@ namespace nvhttp {
client_root = client;
}
/**
* @brief Add authorized client data.
*
* @param name Human-readable name to assign.
* @param cert Certificate data or object used by the operation.
*/
void add_authorized_client(const std::string &name, std::string &&cert) {
client_t &client = client_root;
named_cert_t named_cert;
@@ -285,6 +353,13 @@ namespace nvhttp {
}
}
/**
* @brief Create launch session.
*
* @param host_audio Host audio.
* @param args Arguments forwarded to the callable or parser.
* @return Constructed launch session object.
*/
std::shared_ptr<rtsp_stream::launch_session_t> make_launch_session(bool host_audio, const args_t &args) {
auto launch_session = std::make_shared<rtsp_stream::launch_session_t>();
@@ -348,6 +423,13 @@ namespace nvhttp {
map_id_sess.erase(sess.client.uniqueID);
}
/**
* @brief Return the GameStream pairing failure response.
*
* @param sess Pairing session that owns the request state.
* @param tree XML property tree used for the response body.
* @param status_msg Status msg.
*/
void fail_pair(pair_session_t &sess, pt::ptree &tree, const std::string status_msg) {
tree.put("root.paired", 0);
tree.put("root.<xmlattr>.status_code", 400);
@@ -355,6 +437,13 @@ namespace nvhttp {
remove_session(sess); // Security measure, delete the session when something went wrong and force a re-pair
}
/**
* @brief Return the server certificate text for pairing responses.
*
* @param sess Pairing session that owns the request state.
* @param tree XML property tree used for the response body.
* @param pin PIN supplied by the client during pairing.
*/
void getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin) {
if (sess.last_phase != PAIR_PHASE::NONE) {
fail_pair(sess, tree, "Out of order call to getservercert");
@@ -379,6 +468,13 @@ namespace nvhttp {
tree.put("root.<xmlattr>.status_code", 200);
}
/**
* @brief Handle the client-challenge phase of GameStream pairing.
*
* @param sess Pairing session that owns the request state.
* @param tree XML property tree used for the response body.
* @param challenge Client challenge bytes from the pairing request.
*/
void clientchallenge(pair_session_t &sess, pt::ptree &tree, const std::string &challenge) {
if (sess.last_phase != PAIR_PHASE::GETSERVERCERT) {
fail_pair(sess, tree, "Out of order call to clientchallenge");
@@ -422,6 +518,13 @@ namespace nvhttp {
tree.put("root.<xmlattr>.status_code", 200);
}
/**
* @brief Handle the server-challenge response phase of GameStream pairing.
*
* @param sess Pairing session that owns the request state.
* @param tree XML property tree used for the response body.
* @param encrypted_response Encrypted response.
*/
void serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const std::string &encrypted_response) {
if (sess.last_phase != PAIR_PHASE::CLIENTCHALLENGE) {
fail_pair(sess, tree, "Out of order call to serverchallengeresp");
@@ -451,6 +554,14 @@ namespace nvhttp {
tree.put("root.<xmlattr>.status_code", 200);
}
/**
* @brief Handle the client pairing-secret phase of GameStream pairing.
*
* @param sess Pairing session that owns the request state.
* @param add_cert Add cert.
* @param tree XML property tree used for the response body.
* @param client_pairing_secret Client pairing secret.
*/
void clientpairingsecret(pair_session_t &sess, std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, pt::ptree &tree, const std::string &client_pairing_secret) {
if (sess.last_phase != PAIR_PHASE::SERVERCHALLENGERESP) {
fail_pair(sess, tree, "Out of order call to clientpairingsecret");
@@ -504,16 +615,27 @@ namespace nvhttp {
template<class T>
struct tunnel;
/**
* @brief HTTPS tunnel session used for encrypted client requests.
*/
template<>
struct tunnel<SunshineHTTPS> {
static auto constexpr to_string = "HTTPS"sv;
static auto constexpr to_string = "HTTPS"sv; ///< To string.
};
/**
* @brief Plain HTTP server wrapper used for non-TLS endpoints.
*/
template<>
struct tunnel<SimpleWeb::HTTP> {
static auto constexpr to_string = "NONE"sv;
static auto constexpr to_string = "NONE"sv; ///< To string.
};
/**
* @brief Write req details to the log.
*
* @param request HTTP request data from the client.
*/
template<class T>
void print_req(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
BOOST_LOG(debug) << "TUNNEL :: "sv << tunnel<T>::to_string;
@@ -534,6 +656,12 @@ namespace nvhttp {
BOOST_LOG(debug) << " [--] "sv;
}
/**
* @brief Return a GameStream HTTP not-found response.
*
* @param response HTTP response object to populate.
* @param request HTTP request data from the client.
*/
template<class T>
void not_found(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
print_req<T>(request);
@@ -553,6 +681,13 @@ namespace nvhttp {
response->close_connection_after_response = true;
}
/**
* @brief Dispatch the top-level GameStream pairing request by phase.
*
* @param add_cert Add cert.
* @param response HTTP response object to populate.
* @param request HTTP request data from the client.
*/
template<class T>
void pair(std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
print_req<T>(request);
@@ -684,6 +819,11 @@ namespace nvhttp {
return true;
}
/**
* @brief Get codec mode flags.
*
* @return Moonlight codec capability bitmask for the currently probed encoders.
*/
uint32_t get_codec_mode_flags() {
uint32_t codec_mode_flags = SCM_H264;
if (video::last_encoder_probe_supported_yuv444_for_codec[0]) {
@@ -717,6 +857,12 @@ namespace nvhttp {
return codec_mode_flags;
}
/**
* @brief Build the GameStream server-info response.
*
* @param response HTTP response object to populate.
* @param request HTTP request data from the client.
*/
template<class T>
void serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
print_req<T>(request);
@@ -801,6 +947,12 @@ namespace nvhttp {
return named_cert_nodes;
}
/**
* @brief Build the GameStream application list response.
*
* @param response HTTP response object to populate.
* @param request HTTP request data from the client.
*/
void applist(resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request);
@@ -829,6 +981,13 @@ namespace nvhttp {
}
}
/**
* @brief Launch the requested application for a GameStream session.
*
* @param host_audio Host audio.
* @param response HTTP response object to populate.
* @param request HTTP request data from the client.
*/
void launch(bool &host_audio, resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request);
@@ -940,6 +1099,13 @@ namespace nvhttp {
revert_display_configuration = false;
}
/**
* @brief Resume an existing GameStream session.
*
* @param host_audio Host audio.
* @param response HTTP response object to populate.
* @param request HTTP request data from the client.
*/
void resume(bool &host_audio, resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request);
@@ -1031,6 +1197,12 @@ namespace nvhttp {
rtsp_stream::launch_session_raise(launch_session);
}
/**
* @brief Check whether cel.
*
* @param response HTTP response object to populate.
* @param request HTTP request data from the client.
*/
void cancel(resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request);
@@ -1056,6 +1228,12 @@ namespace nvhttp {
display_device::revert_configuration();
}
/**
* @brief Return an application asset requested by the client.
*
* @param response HTTP response object to populate.
* @param request HTTP request data from the client.
*/
void appasset(resp_https_t response, req_https_t request) {
print_req<SunshineHTTPS>(request);
@@ -1074,6 +1252,12 @@ namespace nvhttp {
conf_intern.servercert = cert;
}
/**
* @brief Check whether a paired client certificate is allowed to connect.
*
* @param cert_pem PEM-encoded client certificate to look up.
* @return True when the client certificate belongs to an enabled device.
*/
bool is_client_enabled(const std::string_view cert_pem);
void start() {
@@ -1266,6 +1450,9 @@ namespace nvhttp {
return false;
}
/**
* @brief Get cert by UUID.
*/
std::string get_cert_by_uuid(const std::string_view uuid) {
for (const auto &named_cert : client_root.named_devices) {
if (named_cert.uuid == uuid) {
@@ -1275,6 +1462,9 @@ namespace nvhttp {
return {};
}
/**
* @brief Check whether a paired client certificate is allowed to connect.
*/
bool is_client_enabled(const std::string_view cert_pem) {
const client_t &client = client_root;
for (const auto &named_cert : client.named_devices) {
+42 -6
View File
@@ -59,8 +59,17 @@ namespace nvhttp {
*/
void setup(const std::string &pkey, const std::string &cert);
/**
* @brief Simple-Web-Server HTTPS backend configured for Sunshine certificate handling.
*/
class SunshineHTTPS: public SimpleWeb::HTTPS {
public:
/**
* @brief Construct an HTTPS connection using Sunshine's TLS context.
*
* @param io_context Boost.Asio context used for network operations.
* @param ctx TLS context configured with Sunshine's certificate and key.
*/
SunshineHTTPS(boost::asio::io_context &io_context, boost::asio::ssl::context &ctx):
SimpleWeb::HTTPS(io_context, ctx) {
}
@@ -72,6 +81,9 @@ namespace nvhttp {
}
};
/**
* @brief Enumerates supported pAIR PHASE options.
*/
enum class PAIR_PHASE {
NONE, ///< Sunshine is not in a pairing phase
GETSERVERCERT, ///< Sunshine is in the get server certificate phase
@@ -80,18 +92,21 @@ namespace nvhttp {
CLIENTPAIRINGSECRET ///< Sunshine is in the client pairing secret phase
};
/**
* @brief Pairing handshake state exchanged with a Moonlight client.
*/
struct pair_session_t {
struct {
std::string uniqueID = {};
std::string cert = {};
std::string name = {};
} client;
} client; ///< Client object or client certificate data owned by this state..
std::unique_ptr<crypto::aes_t> cipher_key = {};
std::vector<uint8_t> clienthash = {};
std::unique_ptr<crypto::aes_t> cipher_key = {}; ///< Cipher key.
std::vector<uint8_t> clienthash = {}; ///< Client certificate hash used during pairing.
std::string serversecret = {};
std::string serverchallenge = {};
std::string serversecret = {}; ///< Server pairing secret.
std::string serverchallenge = {}; ///< Server challenge sent during pairing.
struct {
util::Either<
@@ -99,7 +114,7 @@ namespace nvhttp {
std::shared_ptr<typename SimpleWeb::ServerBase<SunshineHTTPS>::Response>>
response;
std::string salt = {};
} async_insert_pin;
} async_insert_pin; ///< Async insert pin.
/**
* @brief used as a security measure to prevent out of order calls
@@ -122,6 +137,9 @@ namespace nvhttp {
* in order to be used to decrypt_symmetric in the next phases.
*
* At this stage we only have to send back our public certificate.
* @param sess Pairing session that owns the request state.
* @param tree XML property tree used for the response body.
* @param pin PIN supplied by the client during pairing.
*/
void getservercert(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &pin);
@@ -136,6 +154,9 @@ namespace nvhttp {
* - Server secret: a randomly generated secret
*
* The hash + server_challenge will then be AES encrypted and sent as the `challengeresponse` in the returned XML
* @param sess Pairing session that owns the request state.
* @param tree XML property tree used for the response body.
* @param challenge Client challenge bytes from the pairing request.
*/
void clientchallenge(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &challenge);
@@ -145,6 +166,9 @@ namespace nvhttp {
* Moonlight will send back a `serverchallengeresp`: an AES encrypted client hash,
* we have to send back the `pairingsecret`:
* using our private key we have to sign the certificate_signature + server_secret (generated in phase 2)
* @param sess Pairing session that owns the request state.
* @param tree XML property tree used for the response body.
* @param encrypted_response Encrypted response.
*/
void serverchallengeresp(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &encrypted_response);
@@ -161,6 +185,10 @@ namespace nvhttp {
*
* Then using the client certificate public key we should be able to verify that
* the client secret has been signed by Moonlight
* @param sess Pairing session that owns the request state.
* @param add_cert Add cert.
* @param tree XML property tree used for the response body.
* @param client_pairing_secret Client pairing secret.
*/
void clientpairingsecret(pair_session_t &sess, std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, boost::property_tree::ptree &tree, const std::string &client_pairing_secret);
@@ -181,6 +209,8 @@ namespace nvhttp {
* @examples
* nvhttp::unpair_client("4D7BB2DD-5704-A405-B41C-891A022932E1");
* @examples_end
*
* @return True when the client entry was found and removed.
*/
bool unpair_client(std::string_view uuid);
@@ -191,6 +221,12 @@ namespace nvhttp {
* @return true if the client was found and updated.
*/
bool set_client_enabled(std::string_view uuid, bool enabled);
/**
* @brief Get cert by UUID.
*
* @param uuid Client UUID being looked up or removed.
* @return PEM certificate for the paired client, or an empty string when unknown.
*/
std::string get_cert_by_uuid(std::string_view uuid);
/**
+551 -129
View File
File diff suppressed because it is too large Load Diff
+169 -8
View File
@@ -22,6 +22,9 @@
namespace platf {
using namespace std::literals;
/**
* @brief Position mapping.
*/
constexpr pa_channel_position_t position_mapping[] {
PA_CHANNEL_POSITION_FRONT_LEFT,
PA_CHANNEL_POSITION_FRONT_RIGHT,
@@ -33,6 +36,14 @@ namespace platf {
PA_CHANNEL_POSITION_SIDE_RIGHT,
};
/**
* @brief Convert a PulseAudio operation result to a log string.
*
* @param name Human-readable name to assign.
* @param mapping Opus channel mapping table for the requested layout.
* @param channels Number of audio channels in the stream.
* @return Value converted to string.
*/
std::string to_string(const char *name, const std::uint8_t *mapping, int channels) {
std::stringstream ss;
@@ -50,9 +61,18 @@ namespace platf {
return result;
}
/**
* @brief PulseAudio recording stream and channel metadata.
*/
struct mic_attr_t: public mic_t {
util::safe_ptr<pa_simple, pa_simple_free> mic;
util::safe_ptr<pa_simple, pa_simple_free> mic; ///< PulseAudio simple recording stream for microphone capture.
/**
* @brief Deliver a captured audio sample to Sunshine's audio pipeline.
*
* @param sample_buf Sample buf.
* @return Capture status reported to the streaming pipeline.
*/
capture_e sample(std::vector<float> &sample_buf) override {
auto sample_size = sample_buf.size();
@@ -68,6 +88,16 @@ namespace platf {
}
};
/**
* @brief Create a microphone capture stream for the requested layout.
*
* @param mapping Opus channel mapping table for the requested layout.
* @param channels Number of audio channels in the stream.
* @param sample_rate Audio sample rate in hertz.
* @param frame_size Number of samples captured per audio frame.
* @param source_name Source name.
* @return Microphone capture object for the requested audio layout.
*/
std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, std::string source_name) {
auto mic = std::make_unique<mic_attr_t>();
@@ -106,32 +136,74 @@ namespace platf {
template<bool B, class T>
struct add_const_helper;
/**
* @brief Template helper that preserves constness for const inputs.
*/
template<class T>
struct add_const_helper<true, T> {
/**
* @brief PulseAudio object type passed to the safe pointer wrapper.
*/
using type = const std::remove_pointer_t<T> *;
};
/**
* @brief Template helper that leaves non-const inputs mutable.
*/
template<class T>
struct add_const_helper<false, T> {
/**
* @brief PulseAudio object type passed to the safe pointer wrapper.
*/
using type = const T *;
};
/**
* @brief PulseAudio callback info type with pointer constness normalized.
*/
template<class T>
using add_const_t = typename add_const_helper<std::is_pointer_v<T>, T>::type;
/**
* @brief Release memory allocated by PulseAudio.
*
* @param p Pointer allocated by PulseAudio and released with `pa_xfree`.
*/
template<class T>
void pa_free(T *p) {
pa_xfree(p);
}
/**
* @brief Owning pointer for a PulseAudio context.
*/
using ctx_t = util::safe_ptr<pa_context, pa_context_unref>;
/**
* @brief Owning pointer for a PulseAudio mainloop.
*/
using loop_t = util::safe_ptr<pa_mainloop, pa_mainloop_free>;
/**
* @brief Owning pointer for a PulseAudio asynchronous operation.
*/
using op_t = util::safe_ptr<pa_operation, pa_operation_unref>;
/**
* @brief Owning pointer for PulseAudio strings allocated with `pa_xmalloc`.
*/
using string_t = util::safe_ptr<char, pa_free<char>>;
/**
* @brief Callback wrapper for PulseAudio introspection results without an end marker.
*/
template<class T>
using cb_simple_t = std::function<void(ctx_t::pointer, add_const_t<T> i)>;
/**
* @brief Handle PulseAudio sink-input introspection results.
*
* @param ctx Native context object used by the operation or callback.
* @param i PulseAudio introspection info supplied to the callback.
* @param userdata Caller-provided pointer passed through the callback.
*/
template<class T>
void cb(ctx_t::pointer ctx, add_const_t<T> i, void *userdata) {
auto &f = *(cb_simple_t<T> *) userdata;
@@ -141,9 +213,20 @@ namespace platf {
f(ctx, i);
}
/**
* @brief Callback wrapper for PulseAudio introspection results with an end marker.
*/
template<class T>
using cb_t = std::function<void(ctx_t::pointer, add_const_t<T> i, int eol)>;
/**
* @brief Handle PulseAudio source introspection results.
*
* @param ctx Native context object used by the operation or callback.
* @param i PulseAudio introspection info supplied to the callback.
* @param eol PulseAudio end-of-list marker.
* @param userdata Caller-provided pointer passed through the callback.
*/
template<class T>
void cb(ctx_t::pointer ctx, add_const_t<T> i, int eol, void *userdata) {
auto &f = *(cb_t<T> *) userdata;
@@ -156,18 +239,38 @@ namespace platf {
f(ctx, i, eol);
}
/**
* @brief Forward a PulseAudio integer callback value into a Sunshine alarm.
*
* @param ctx PulseAudio context that emitted the callback.
* @param i Integer value returned by the PulseAudio operation.
* @param userdata Caller-provided pointer passed through the callback.
*/
void cb_i(ctx_t::pointer ctx, std::uint32_t i, void *userdata) {
auto alarm = (safe::alarm_raw_t<int> *) userdata;
alarm->ring(i);
}
/**
* @brief Translate PulseAudio context state changes into server events.
*
* @param ctx Native context object used by the operation or callback.
* @param userdata Caller-provided pointer passed through the callback.
*/
void ctx_state_cb(ctx_t::pointer ctx, void *userdata) {
auto &f = *(std::function<void(ctx_t::pointer)> *) userdata;
f(ctx);
}
/**
* @brief Record completion of a PulseAudio asynchronous operation.
*
* @param ctx Native context object used by the operation or callback.
* @param status Native status code returned by the platform API.
* @param userdata Caller-provided pointer passed through the callback.
*/
void success_cb(ctx_t::pointer ctx, int status, void *userdata) {
assert(userdata != nullptr);
@@ -175,6 +278,9 @@ namespace platf {
alarm->ring(status ? 0 : 1);
}
/**
* @brief PulseAudio server controller that creates and removes Sunshine sinks.
*/
class server_t: public audio_control_t {
enum ctx_event_e : int {
ready,
@@ -183,21 +289,26 @@ namespace platf {
};
public:
loop_t loop;
ctx_t ctx;
std::string requested_sink;
loop_t loop; ///< PulseAudio threaded mainloop instance.
ctx_t ctx; ///< PulseAudio threaded mainloop context.
std::string requested_sink; ///< Requested sink.
struct {
std::uint32_t stereo = PA_INVALID_INDEX;
std::uint32_t surround51 = PA_INVALID_INDEX;
std::uint32_t surround71 = PA_INVALID_INDEX;
} index;
} index; ///< PulseAudio module indexes for Sunshine-created null sinks.
std::unique_ptr<safe::event_t<ctx_event_e>> events;
std::unique_ptr<std::function<void(ctx_t::pointer)>> events_cb;
std::unique_ptr<safe::event_t<ctx_event_e>> events; ///< Event queue receiving PulseAudio context state changes.
std::unique_ptr<std::function<void(ctx_t::pointer)>> events_cb; ///< Callback that translates PulseAudio context updates into events.
std::thread worker;
std::thread worker; ///< Thread running the PulseAudio mainloop.
/**
* @brief Initialize PulseAudio mainloop, context, and Sunshine null sinks.
*
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init() {
events = std::make_unique<safe::event_t<ctx_event_e>>();
loop.reset(pa_mainloop_new());
@@ -255,6 +366,14 @@ namespace platf {
return 0;
}
/**
* @brief Create a PulseAudio null sink for one channel layout.
*
* @param name Human-readable name to assign.
* @param channel_mapping Channel mapping.
* @param channels Number of audio channels in the stream.
* @return PulseAudio module index for the new sink, or PA_INVALID_INDEX on failure.
*/
int load_null(const char *name, const std::uint8_t *channel_mapping, int channels) {
auto alarm = safe::make_alarm<int>();
@@ -272,6 +391,12 @@ namespace platf {
return *alarm->status();
}
/**
* @brief Unload a Sunshine-created PulseAudio null sink.
*
* @param i PulseAudio introspection info supplied to the callback.
* @return 0 when the sink is absent or unloaded; nonzero on PulseAudio failure.
*/
int unload_null(std::uint32_t i) {
if (i == PA_INVALID_INDEX) {
return 0;
@@ -293,6 +418,11 @@ namespace platf {
return 0;
}
/**
* @brief Query host and virtual sink names available to Sunshine.
*
* @return Host and virtual sink names when the backend can report them.
*/
std::optional<sink_t> sink_info() override {
constexpr auto stereo = "sink-sunshine-stereo";
constexpr auto surround51 = "sink-sunshine-surround51";
@@ -388,6 +518,11 @@ namespace platf {
return std::make_optional(std::move(sink));
}
/**
* @brief Get default sink name.
*
* @return PulseAudio name of the current default sink, or an empty string.
*/
std::string get_default_sink_name() {
std::string sink_name;
auto alarm = safe::make_alarm<int>();
@@ -410,6 +545,12 @@ namespace platf {
return sink_name;
}
/**
* @brief Get monitor name.
*
* @param sink_name Sink name.
* @return PulseAudio monitor source name for the supplied sink, or an empty string.
*/
std::string get_monitor_name(const std::string &sink_name) {
std::string monitor_name;
auto alarm = safe::make_alarm<int>();
@@ -441,6 +582,17 @@ namespace platf {
return monitor_name;
}
/**
* @brief Create a microphone capture stream for the requested layout.
*
* @param mapping Opus channel mapping table for the requested layout.
* @param channels Number of audio channels in the stream.
* @param sample_rate Audio sample rate in hertz.
* @param frame_size Number of samples captured per audio frame.
* @param continuous_audio Continuous audio.
* @param host_audio_enabled Whether host playback should remain enabled during capture.
* @return Microphone capture object for the requested audio layout.
*/
std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, bool continuous_audio, [[maybe_unused]] bool host_audio_enabled) override {
// Sink choice priority:
// 1. Config sink
@@ -465,6 +617,12 @@ namespace platf {
return true;
}
/**
* @brief Update the sink value on the backend.
*
* @param sink Audio sink name to route or capture.
* @return Status from updating sink.
*/
int set_sink(const std::string &sink) override {
auto alarm = safe::make_alarm<int>();
@@ -514,6 +672,9 @@ namespace platf {
};
} // namespace pa
/**
* @brief Create the platform audio controller.
*/
std::unique_ptr<audio_control_t> audio_control() {
auto audio = std::make_unique<pa::server_t>();
+269 -40
View File
@@ -26,13 +26,29 @@ extern "C" {
#include "src/video.h"
#include "wayland.h"
/**
* @def SUNSHINE_STRINGVIEW_HELPER(x)
* @brief Macro for SUNSHINE STRINGVIEW HELPER.
*/
#define SUNSHINE_STRINGVIEW_HELPER(x) x##sv
/**
* @def SUNSHINE_STRINGVIEW(x)
* @brief Macro for SUNSHINE STRINGVIEW.
*/
#define SUNSHINE_STRINGVIEW(x) SUNSHINE_STRINGVIEW_HELPER(x)
/**
* @def CU_CHECK(x, y)
* @brief Macro for CU CHECK.
*/
#define CU_CHECK(x, y) \
if (check((x), SUNSHINE_STRINGVIEW(y ": "))) \
return -1
/**
* @def CU_CHECK_IGNORE(x, y)
* @brief Macro for CU CHECK IGNORE.
*/
#define CU_CHECK_IGNORE(x, y) \
check((x), SUNSHINE_STRINGVIEW(y ": "))
@@ -41,17 +57,32 @@ namespace fs = std::filesystem;
using namespace std::literals;
namespace cuda {
constexpr auto cudaDevAttrMaxThreadsPerBlock = (CUdevice_attribute) 1;
constexpr auto cudaDevAttrMaxThreadsPerMultiProcessor = (CUdevice_attribute) 39;
constexpr auto cudaDevAttrMaxThreadsPerBlock = (CUdevice_attribute) 1; ///< CUDA dev attr max threads per block.
constexpr auto cudaDevAttrMaxThreadsPerMultiProcessor = (CUdevice_attribute) 39; ///< CUDA dev attr max threads per multi processor.
/**
* @brief Convert a CUDA result code into Sunshine's capture status.
*
* @param sv String view containing the text to inspect.
* @param name Human-readable name to assign.
* @param description Human-readable description used in log output.
*/
void pass_error(const std::string_view &sv, const char *name, const char *description) {
BOOST_LOG(error) << sv << name << ':' << description;
}
/**
* @brief Release a Core Foundation object when the wrapper is destroyed.
*
* @param cf Core Foundation object passed to the scoped releaser.
*/
void cff(CudaFunctions *cf) {
cuda_free_functions(&cf);
}
/**
* @brief Handle to a CUDA dynamic-library function table.
*/
using cdf_t = util::safe_ptr<CudaFunctions, cff>;
static cdf_t cdf;
@@ -71,21 +102,42 @@ namespace cuda {
return 0;
}
/**
* @brief Release stream resources.
*
* @param stream CUDA stream or PipeWire stream involved in the operation.
*/
void freeStream(CUstream stream) {
CU_CHECK_IGNORE(cdf->cuStreamDestroy(stream), "Couldn't destroy cuda stream");
}
/**
* @brief Unregister a CUDA graphics resource if it is still registered.
*
* @param resource CUDA graphics resource being mapped or unmapped.
*/
void unregisterResource(CUgraphicsResource resource) {
CU_CHECK_IGNORE(cdf->cuGraphicsUnregisterResource(resource), "Couldn't unregister resource");
}
/**
* @brief CUDA graphics resource pointer released with `cuGraphicsUnregisterResource`.
*/
using registered_resource_t = util::safe_ptr<CUgraphicsResource_st, unregisterResource>;
/**
* @brief CUDA image wrapper that owns mapped graphics resources for one frame.
*/
class img_t: public platf::img_t {
public:
tex_t tex;
tex_t tex; ///< CUDA texture object used as the conversion source.
};
/**
* @brief Map CUDA graphics resources for use as an image.
*
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init() {
auto status = cuda_load_functions(&cdf, nullptr);
if (status) {
@@ -99,8 +151,18 @@ namespace cuda {
return 0;
}
/**
* @brief CUDA encode device that imports captured frames into CUDA memory.
*/
class cuda_t: public platf::avcodec_encode_device_t {
public:
/**
* @brief Initialize the CUDA device context used for frame conversion.
*
* @param in_width In width.
* @param in_height In height.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(int in_width, int in_height) {
if (!cdf) {
BOOST_LOG(warning) << "cuda not initialized"sv;
@@ -115,6 +177,13 @@ namespace cuda {
return 0;
}
/**
* @brief Attach frame resources used by the next conversion or encode operation.
*
* @param frame Video or graphics frame being processed.
* @param hw_frames_ctx FFmpeg hardware frames context associated with the frame.
* @return Status from updating frame.
*/
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override {
this->hwframe.reset(frame);
this->frame = frame;
@@ -157,6 +226,9 @@ namespace cuda {
return 0;
}
/**
* @brief Apply the configured colorspace metadata to the active frame.
*/
void apply_colorspace() override {
sws.apply_colorspace(colorspace);
@@ -189,26 +261,41 @@ namespace cuda {
}
}
/**
* @brief Select the CUDA texture object for the configured filtering mode.
*
* @param tex Texture resource used by the converter.
* @return CUDA texture object using linear or point sampling.
*/
cudaTextureObject_t tex_obj(const tex_t &tex) const {
return linear_interpolation ? tex.texture.linear : tex.texture.point;
}
stream_t stream;
frame_t hwframe;
stream_t stream; ///< CUDA stream used for asynchronous conversion work.
frame_t hwframe; ///< FFmpeg hardware frame backed by CUDA resources.
int height;
int width;
int height; ///< Frame or display height in pixels.
int width; ///< Frame or display width in pixels.
// When height and width don't change, it's not necessary to use linear interpolation
bool linear_interpolation;
bool linear_interpolation; ///< Whether the CUDA converter uses linear interpolation.
bool is_yuv444;
bool is_yuv444; ///< Whether the CUDA converter outputs YUV 4:4:4.
sws_t sws;
sws_t sws; ///< Software scaler used for CUDA frame conversion fallback paths.
};
/**
* @brief CUDA encode device path that converts frames through system memory.
*/
class cuda_ram_t: public cuda_t {
public:
/**
* @brief Convert a captured frame through CUDA into system-memory encoder input.
*
* @param img Image or frame object to read from or populate.
* @return Conversion status.
*/
int convert(platf::img_t &img) override {
if (is_yuv444) {
return sws.load_ram(img, tex.array) || sws.convert_yuv444(frame->data[0], frame->data[1], frame->data[2], frame->linesize[0], tex_obj(tex), stream.get());
@@ -216,6 +303,13 @@ namespace cuda {
return sws.load_ram(img, tex.array) || sws.convert_nv12(frame->data[0], frame->data[1], frame->linesize[0], frame->linesize[1], tex_obj(tex), stream.get());
}
/**
* @brief Attach frame resources used by the next conversion or encode operation.
*
* @param frame Video or graphics frame being processed.
* @param hw_frames_ctx FFmpeg hardware frames context associated with the frame.
* @return Status from updating frame.
*/
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override {
if (cuda_t::set_frame(frame, hw_frames_ctx)) {
return -1;
@@ -231,11 +325,20 @@ namespace cuda {
return 0;
}
tex_t tex;
tex_t tex; ///< CUDA texture object used as the conversion source.
};
/**
* @brief CUDA encode device path that keeps converted frames in GPU memory.
*/
class cuda_vram_t: public cuda_t {
public:
/**
* @brief Convert a captured frame through CUDA into GPU encoder input.
*
* @param img Image or frame object to read from or populate.
* @return Conversion status.
*/
int convert(platf::img_t &img) override {
if (is_yuv444) {
return sws.convert_yuv444(frame->data[0], frame->data[1], frame->data[2], frame->linesize[0], tex_obj(((img_t *) &img)->tex), stream.get());
@@ -290,13 +393,19 @@ namespace cuda {
return -1;
}
/**
* @brief CUDA frame resources registered for interop conversion.
*/
struct cu_resources {
registered_resource_t y_res;
registered_resource_t u_res;
registered_resource_t v_res;
registered_resource_t uv_res;
registered_resource_t y_res; ///< Y res.
registered_resource_t u_res; ///< U res.
registered_resource_t v_res; ///< V res.
registered_resource_t uv_res; ///< Uv res.
};
/**
* @brief OpenGL/CUDA interop resources used for GPU-side frame conversion.
*/
class gl_cuda_vram_t: public platf::avcodec_encode_device_t {
public:
/**
@@ -509,34 +618,42 @@ namespace cuda {
sws.apply_colorspace(colorspace, is_yuv444);
}
file_t file;
gbm::gbm_t gbm;
egl::display_t display;
egl::ctx_t ctx;
file_t file; ///< File descriptor for the imported DMA-BUF.
gbm::gbm_t gbm; ///< GBM device used for buffer allocation..
egl::display_t display; ///< EGL display used to import captured frames.
egl::ctx_t ctx; ///< EGL context used to import captured frames.
// This must be destroyed before display_t
stream_t stream;
frame_t hwframe;
stream_t stream; ///< CUDA stream used for asynchronous conversion work.
frame_t hwframe; ///< FFmpeg hardware frame backed by CUDA resources.
egl::sws_t sws;
egl::nv12_t nv12;
egl::yuv444_t yuv444;
AVPixelFormat sw_format;
egl::sws_t sws; ///< Software scaler used for CUDA frame conversion fallback paths.
egl::nv12_t nv12; ///< EGL/OpenGL resources used for NV12 output frames.
egl::yuv444_t yuv444; ///< EGL/OpenGL resources used for YUV444 output frames.
AVPixelFormat sw_format; ///< FFmpeg software pixel format produced by conversion.
int height;
int width;
int height; ///< Frame or display height in pixels.
int width; ///< Frame or display width in pixels.
std::uint64_t sequence;
egl::rgb_t rgb;
std::uint64_t sequence; ///< Capture sequence number associated with the frame.
egl::rgb_t rgb; ///< Imported RGB source image used before CUDA conversion.
cu_resources cu_res;
cu_resources cu_res; ///< CUDA graphics resources registered for the current frame.
int offset_x;
int offset_y;
int offset_x; ///< Horizontal offset in physical pixels.
int offset_y; ///< Vertical offset in physical pixels.
bool is_yuv444;
bool is_yuv444; ///< Whether the CUDA converter outputs YUV 4:4:4.
};
/**
* @brief Create AVCodec encode device.
*
* @param width Frame or display width in pixels.
* @param height Frame or display height in pixels.
* @param vram Whether the image should use GPU memory instead of system memory.
* @return Constructed AVCodec encode device object.
*/
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, bool vram) {
if (init()) {
return nullptr;
@@ -589,6 +706,11 @@ namespace cuda {
static void *handle {nullptr};
/**
* @brief Load NvFBC and create the CUDA capture helper.
*
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init() {
static bool funcs_loaded = false;
@@ -627,8 +749,16 @@ namespace cuda {
return 0;
}
/**
* @brief NvFBC CUDA context selected for a capture session.
*/
class ctx_t {
public:
/**
* @brief Create an NvFBC session context for a native capture handle.
*
* @param handle Native library or object handle used by the operation.
*/
ctx_t(NVFBC_SESSION_HANDLE handle) {
NVFBC_BIND_CONTEXT_PARAMS params {NVFBC_BIND_CONTEXT_PARAMS_VER};
@@ -646,9 +776,12 @@ namespace cuda {
}
}
NVFBC_SESSION_HANDLE handle;
NVFBC_SESSION_HANDLE handle; ///< NVIDIA FBC capture session handle.
};
/**
* @brief NvFBC dynamic-library handle and function table.
*/
class handle_t {
enum flag_e {
SESSION_HANDLE,
@@ -659,12 +792,23 @@ namespace cuda {
public:
handle_t() = default;
/**
* @brief Move an NvFBC API handle and its resolved function table.
*
* @param other Source object whose state is copied or moved into this object.
*/
handle_t(handle_t &&other):
handle_flags {other.handle_flags},
handle {other.handle} {
other.handle_flags.reset();
}
/**
* @brief Assign state from another instance while preserving ownership semantics.
*
* @param other Source object whose state is copied or moved into this object.
* @return Reference or value produced by the operator.
*/
handle_t &operator=(handle_t &&other) {
std::swap(handle_flags, other.handle_flags);
std::swap(handle, other.handle);
@@ -672,6 +816,11 @@ namespace cuda {
return *this;
}
/**
* @brief Allocate the underlying object and wrap it in the owning handle.
*
* @return Created backend object, or null when creation fails.
*/
static std::optional<handle_t> make() {
NVFBC_CREATE_HANDLE_PARAMS params {NVFBC_CREATE_HANDLE_PARAMS_VER};
@@ -694,10 +843,20 @@ namespace cuda {
return handle;
}
/**
* @brief Read the last error string from the active NvFBC session.
*
* @return Human-readable NvFBC error string.
*/
const char *last_error() {
return func.nvFBCGetLastErrorStr(handle);
}
/**
* @brief Return or update the current status value.
*
* @return Status status.
*/
std::optional<NVFBC_GET_STATUS_PARAMS> status() {
NVFBC_GET_STATUS_PARAMS params {NVFBC_GET_STATUS_PARAMS_VER};
@@ -711,6 +870,12 @@ namespace cuda {
return params;
}
/**
* @brief Run the capture loop for this backend.
*
* @param capture_params Capture params.
* @return Capture status reported to the streaming pipeline.
*/
int capture(NVFBC_CREATE_CAPTURE_SESSION_PARAMS &capture_params) {
if (func.nvFBCCreateCaptureSession(handle, &capture_params)) {
BOOST_LOG(error) << "Failed to start capture session: "sv << last_error();
@@ -731,6 +896,11 @@ namespace cuda {
return 0;
}
/**
* @brief Release the NvFBC capture session and wait for capture work to stop.
*
* @return Stop status.
*/
int stop() {
if (!handle_flags[SESSION_CAPTURE]) {
return 0;
@@ -749,6 +919,11 @@ namespace cuda {
return 0;
}
/**
* @brief Reset the object to its initial empty state.
*
* @return Reset status.
*/
int reset() {
if (!handle_flags[SESSION_HANDLE]) {
return 0;
@@ -772,13 +947,23 @@ namespace cuda {
reset();
}
std::bitset<MAX_FLAGS> handle_flags;
std::bitset<MAX_FLAGS> handle_flags; ///< Handle flags.
NVFBC_SESSION_HANDLE handle;
NVFBC_SESSION_HANDLE handle; ///< NVIDIA FBC capture session handle.
};
/**
* @brief NvFBC display capture backend that produces CUDA frames.
*/
class display_t: public platf::display_t {
public:
/**
* @brief Initialize NvFBC capture for the selected display.
*
* @param display_name Display name.
* @param config Configuration values to apply.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(const std::string_view &display_name, const ::video::config_t &config) {
auto handle = handle_t::make();
if (!handle) {
@@ -900,6 +1085,12 @@ namespace cuda {
}
// Reinitialize the capture session.
/**
* @brief Reinitialize NvFBC capture after a recoverable failure.
*
* @param cursor Cursor image or visibility state to composite.
* @return Reinit status.
*/
platf::capture_e reinit(bool cursor) {
if (handle.stop()) {
return platf::capture_e::error;
@@ -968,6 +1159,15 @@ namespace cuda {
return platf::capture_e::ok;
}
/**
* @brief Capture a display frame into the provided image object.
*
* @param pull_free_image_cb Callback that provides an available image buffer.
* @param img_out Captured CUDA image returned to the streaming pipeline.
* @param timeout Maximum time to wait for the operation.
* @param cursor Cursor image or visibility state to composite.
* @return Capture status reported to the streaming pipeline.
*/
platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
if (cursor != cursor_visible) {
auto status = reinit(cursor);
@@ -1008,10 +1208,21 @@ namespace cuda {
return platf::capture_e::ok;
}
/**
* @brief Create AVCodec encode device.
*
* @param pix_fmt Sunshine pixel format to convert or allocate for.
* @return Constructed AVCodec encode device object.
*/
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override {
return ::cuda::make_avcodec_encode_device(width, height, true);
}
/**
* @brief Allocate an image buffer compatible with this display backend.
*
* @return Allocated img object, or null when unavailable.
*/
std::shared_ptr<platf::img_t> alloc_img() override {
auto img = std::make_shared<cuda::img_t>();
@@ -1031,21 +1242,34 @@ namespace cuda {
return img;
};
/**
* @brief Populate a fallback image when real capture data is unavailable.
*
* @return Capture status reported to the streaming pipeline.
*/
int dummy_img(platf::img_t *) override {
return 0;
}
std::chrono::nanoseconds delay;
std::chrono::nanoseconds delay; ///< Delay before the timer task becomes eligible to run.
bool cursor_visible;
handle_t handle;
bool cursor_visible; ///< Whether the cursor should be included in the capture.
handle_t handle; ///< NvFBC capture handle owning the active capture session.
NVFBC_CREATE_CAPTURE_SESSION_PARAMS capture_params;
NVFBC_CREATE_CAPTURE_SESSION_PARAMS capture_params; ///< NvFBC capture-session parameters used for frame grabs.
};
} // namespace nvfbc
} // namespace cuda
namespace platf {
/**
* @brief Create an NvFBC CUDA display capture backend.
*
* @param hwdevice_type Hardware device type requested for capture or encode.
* @param display_name Display name.
* @param config Configuration values to apply.
* @return Display backend, or nullptr when NvFBC/CUDA initialization fails.
*/
std::shared_ptr<display_t> nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
if (hwdevice_type != mem_type_e::cuda) {
BOOST_LOG(error) << "Could not initialize nvfbc display with the given hw device type"sv;
@@ -1061,6 +1285,11 @@ namespace platf {
return display;
}
/**
* @brief Enumerate display names exposed by NvFBC.
*
* @return NvFBC display names, or an empty list when enumeration fails.
*/
std::vector<std::string> nvfbc_display_names() {
if (cuda::init() || cuda::nvfbc::init()) {
return {};
+10 -7
View File
@@ -226,7 +226,7 @@ namespace cuda {
float scale,
const viewport_t viewport,
const cuda_color_t *const color_matrix
) {
) {
int idX = threadIdx.x + blockDim.x * blockIdx.x;
int idY = threadIdx.y + blockDim.y * blockIdx.y;
@@ -252,7 +252,6 @@ namespace cuda {
dstY[0] = calcY(rgb, color_matrix) * 255.0f;
dstU[0] = calcU(rgb, color_matrix) * 255.0f;
dstV[0] = calcV(rgb, color_matrix) * 255.0f;
}
int tex_t::copy(std::uint8_t *src, int height, int pitch) {
@@ -383,9 +382,7 @@ namespace cuda {
return convert_yuv444(Y, U, V, pitch, texture, stream, viewport);
}
int sws_t::convert_yuv444(std::uint8_t *Y, std::uint8_t *U, std::uint8_t *V, std::uint32_t pitch,
cudaTextureObject_t texture, stream_t::pointer stream,
const viewport_t &viewport) {
int sws_t::convert_yuv444(std::uint8_t *Y, std::uint8_t *U, std::uint8_t *V, std::uint32_t pitch, cudaTextureObject_t texture, stream_t::pointer stream, const viewport_t &viewport) {
int threadsX = viewport.width;
int threadsY = viewport.height;
@@ -393,8 +390,14 @@ namespace cuda {
dim3 grid(div_align(threadsX, threadsPerBlock), threadsY);
RGBA_to_YUV444<<<grid, block, 0, stream>>>(
texture, Y, U, V, pitch, scale, viewport,
(cuda_color_t *) color_matrix.get()
texture,
Y,
U,
V,
pitch,
scale,
viewport,
(cuda_color_t *) color_matrix.get()
);
return CU_CHECK_IGNORE(cudaGetLastError(), "RGBA_to_YUV444 failed");
+103 -13
View File
@@ -24,25 +24,47 @@ extern "C" {
// There aren't that many DRM_FORMAT I need to use, so define them here
//
// They aren't likely to change any time soon.
/**
* @def fourcc_code(a, b, c, d)
* @brief Macro for fourcc code.
*/
#define fourcc_code(a, b, c, d) ((std::uint32_t) (a) | ((std::uint32_t) (b) << 8) | ((std::uint32_t) (c) << 16) | ((std::uint32_t) (d) << 24))
/**
* @def fourcc_mod_code(vendor, val)
* @brief Macro for fourcc mod code.
*/
#define fourcc_mod_code(vendor, val) ((((uint64_t) vendor) << 56) | ((val) & 0x00ffffffffffffffULL))
/**
* @def DRM_FORMAT_MOD_INVALID
* @brief Macro for DRM FORMAT MOD INVALID.
*/
#define DRM_FORMAT_MOD_INVALID fourcc_mod_code(0, ((1ULL << 56) - 1))
#if !defined(SUNSHINE_SHADERS_DIR) // for testing this needs to be defined in cmake as we don't do an install
/**
* @def SUNSHINE_SHADERS_DIR
* @brief Macro for SUNSHINE SHADERS DIR.
*/
#define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders/opengl"
#endif
using namespace std::literals;
namespace gl {
GladGLContext ctx;
GladGLContext ctx; ///< Loaded OpenGL function table for the active context.
static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC egl_image_target_texture_2d_fn = nullptr;
/**
* @brief Bind an EGL image to the current OpenGL texture target.
*/
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC egl_image_target_texture_2d() {
return egl_image_target_texture_2d_fn;
}
/**
* @brief Drain and log pending OpenGL errors.
*/
void drain_errors(const std::string_view &prefix) {
GLenum err;
while ((err = ctx.GetError()) != GL_NO_ERROR) {
@@ -275,9 +297,12 @@ namespace gl {
} // namespace gl
namespace gbm {
device_destroy_fn device_destroy;
create_device_fn create_device;
device_destroy_fn device_destroy; ///< Device destroy.
create_device_fn create_device; ///< Create device.
/**
* @brief Load GBM symbols required for EGL device creation.
*/
int init() {
static void *handle {nullptr};
static bool funcs_loaded = false;
@@ -309,13 +334,13 @@ namespace gbm {
namespace egl {
/**
* @brief Log EGL failure details and return an error code.
*/
bool fail() {
return eglGetError() != EGL_SUCCESS;
}
/**
* @memberof egl::display_t
*/
display_t make_display(std::variant<gbm::gbm_t::pointer, wl_display *, _XDisplay *> native_display) {
int egl_platform;
void *native_display_p;
@@ -391,6 +416,9 @@ namespace egl {
return display;
}
/**
* @brief Create an EGL/OpenGL context for capture or conversion.
*/
std::optional<ctx_t> make_ctx(display_t::pointer display) {
bool nice_warning = false;
#if !defined(__FreeBSD__)
@@ -503,14 +531,23 @@ namespace egl {
return ctx;
}
/**
* @brief EGL attribute pair describing one DMA-BUF plane.
*/
struct plane_attr_t {
EGLAttrib fd;
EGLAttrib offset;
EGLAttrib pitch;
EGLAttrib lo;
EGLAttrib hi;
EGLAttrib fd; ///< EGL attribute key for a plane file descriptor.
EGLAttrib offset; ///< Offset.
EGLAttrib pitch; ///< Pitch.
EGLAttrib lo; ///< Lo.
EGLAttrib hi; ///< Hi.
};
/**
* @brief Build EGL attributes for one DMA-BUF plane.
*
* @param plane_indice Zero-based plane index in the DMA-BUF descriptor.
* @return EGL attribute keys for that plane's file descriptor, offset, pitch, and modifier.
*/
inline plane_attr_t get_plane(std::uint32_t plane_indice) {
switch (plane_indice) {
case 0:
@@ -593,6 +630,13 @@ namespace egl {
return attribs;
}
/**
* @brief Import the source frame texture for EGL/OpenGL conversion.
*
* @param egl_display EGL display used to create the image.
* @param xrgb XRGB surface descriptor to import.
* @return Imported RGB image, or empty when import fails.
*/
std::optional<rgb_t> import_source(display_t::pointer egl_display, const surface_descriptor_t &xrgb) {
auto attribs = surface_descriptor_to_egl_attribs(xrgb);
@@ -652,9 +696,14 @@ namespace egl {
}
// Constants for clear black color Y, U, V. U & V are same so:
const float y_black[] = {0.0f, 0.0f, 0.0f, 0.0f};
const float uv_black[] = {0.5f, 0.5f, 0.5f, 0.5f};
const float y_black[] = {0.0f, 0.0f, 0.0f, 0.0f}; ///< Y black.
const float uv_black[] = {0.5f, 0.5f, 0.5f, 0.5f}; ///< Uv black.
/**
* @brief Bind NV12 target framebuffers to their Y and UV plane textures.
*
* @param nv12 Imported NV12 target whose textures receive rendered output.
*/
void nv12_bind_framebuffers(nv12_t &nv12) {
constexpr std::array<GLenum, 2> attachments {{GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1}};
@@ -669,6 +718,11 @@ namespace egl {
gl_drain_errors;
}
/**
* @brief Bind YUV444 target framebuffers to their Y, U, and V plane textures.
*
* @param yuv444 Imported YUV444 target whose textures receive rendered output.
*/
void yuv44_bind_framebuffers(yuv444_t &yuv444) {
constexpr std::array<GLenum, 3> attachments {{GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2}};
@@ -683,6 +737,15 @@ namespace egl {
gl_drain_errors;
}
/**
* @brief Import the output frame target for EGL/OpenGL conversion.
*
* @param egl_display EGL display used to create the images.
* @param fds File descriptors backing the NV12 image.
* @param y Luma plane descriptor.
* @param uv Chroma plane descriptor.
* @return Imported NV12 image, or empty when import fails.
*/
std::optional<nv12_t> import_target(display_t::pointer egl_display, std::array<file_t, nv12_img_t::num_fds> &&fds, const surface_descriptor_t &y, const surface_descriptor_t &uv) {
auto y_attribs = surface_descriptor_to_egl_attribs(y);
auto uv_attribs = surface_descriptor_to_egl_attribs(uv);
@@ -719,6 +782,16 @@ namespace egl {
return nv12;
}
/**
* @brief Import a YUV444 target image from DMA-BUF descriptors.
*
* @param egl_display EGL display used to create the images.
* @param fds File descriptors backing the YUV444 image.
* @param y Luma plane descriptor.
* @param u U chroma plane descriptor.
* @param v V chroma plane descriptor.
* @return Imported YUV444 image, or empty when import fails.
*/
std::optional<yuv444_t> import_target_yuv444(
display_t::pointer egl_display,
std::array<file_t, yuv444_img_t::num_fds> &&fds,
@@ -811,6 +884,9 @@ namespace egl {
return nv12;
}
/**
* @brief Create YUV444 target.
*/
std::optional<yuv444_t> create_yuv444_target(int width, int height, AVPixelFormat format) {
yuv444_t yuv444 {
EGL_NO_DISPLAY,
@@ -876,6 +952,15 @@ namespace egl {
}
}
/**
* @brief Configure the EGL/OpenGL scaling and colorspace conversion pipeline.
*
* @param sws Software-scaling pipeline to configure.
* @param color_p Color p.
* @param tex Texture resource used by the converter.
* @param is_yuv444 Is YUV444.
* @return 0 when shaders, framebuffers, and color uniforms are ready; nonzero on failure.
*/
int configure_sws_pipeline(sws_t &sws, const video::color_t *color_p, gl::tex_t &&tex, bool is_yuv444) {
std::array<std::pair<const char *, std::string_view>, 5> members {{
std::make_pair("color_vec_y", util::view(color_p->color_vec_y)),
@@ -1310,6 +1395,11 @@ namespace egl {
}
} // namespace egl
/**
* @brief Release an FFmpeg frame allocated by the capture or conversion backend.
*
* @param frame Video or graphics frame being processed.
*/
void free_frame(AVFrame *frame) {
av_frame_free(&frame);
}
+428 -56
View File
@@ -19,19 +19,49 @@
#include "src/utility.h"
#include "src/video_colorspace.h"
/**
* @def SUNSHINE_STRINGIFY_HELPER(x)
* @brief Macro for SUNSHINE STRINGIFY HELPER.
*/
#define SUNSHINE_STRINGIFY_HELPER(x) #x
/**
* @def SUNSHINE_STRINGIFY(x)
* @brief Macro for SUNSHINE STRINGIFY.
*/
#define SUNSHINE_STRINGIFY(x) SUNSHINE_STRINGIFY_HELPER(x)
/**
* @def gl_drain_errors_helper(x)
* @brief Macro for gl drain errors helper.
*/
#define gl_drain_errors_helper(x) gl::drain_errors(x)
/**
* @def gl_drain_errors
* @brief Macro for gl drain errors.
*/
#define gl_drain_errors gl_drain_errors_helper(__FILE__ ":" SUNSHINE_STRINGIFY(__LINE__))
/**
* @brief Release the native resource held by the RAII wrapper.
*
* @param __fd File descriptor owned by the RAII wrapper.
* @return 0 on success, or -1 with errno set by the system close call.
*/
extern "C" int close(int __fd);
// X11 Display
extern "C" struct _XDisplay;
struct AVFrame;
/**
* @brief Release an FFmpeg frame allocated by the capture or conversion backend.
*
* @param frame Video or graphics frame being processed.
*/
void free_frame(AVFrame *frame);
/**
* @brief Owning pointer for an EGL frame object.
*/
using frame_t = util::safe_ptr<AVFrame, free_frame>;
namespace gl {
@@ -40,34 +70,84 @@ namespace gl {
// glEGLImageTargetTexture2DOES (GL_OES_EGL_image) is not part of desktop GL —
// it is a GLES extension that must be loaded manually via eglGetProcAddress.
// GLeglImageOES is typedef void* per the Khronos spec (gl.xml).
/**
* @brief Function pointer type for glEGLImageTargetTexture2DOES.
*/
using PFNGLEGLIMAGETARGETTEXTURE2DOESPROC = void (*)(GLenum target, void *image);
/**
* @brief Resolve the GLES extension used to bind EGL images as textures.
*
* @return Extension function pointer, or nullptr when the driver does not expose it.
*/
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC egl_image_target_texture_2d();
/**
* @brief Drain and log pending OpenGL errors.
*
* @param prefix Text prefix used when formatting the message.
*/
void drain_errors(const std::string_view &prefix);
/**
* @brief OpenGL texture handle wrapper.
*/
class tex_t: public util::buffer_t<GLuint> {
using util::buffer_t<GLuint>::buffer_t;
public:
/**
* @brief Move ownership of OpenGL texture object names.
*/
tex_t(tex_t &&) = default;
/**
* @brief Assign state from another instance while preserving ownership semantics.
*
* @return Reference or value produced by the operator.
*/
tex_t &operator=(tex_t &&) = default;
~tex_t();
/**
* @brief Allocate the underlying object and wrap it in the owning handle.
*
* @param count Number of objects or handles to create.
* @return Created backend object, or null when creation fails.
*/
static tex_t make(std::size_t count);
};
/**
* @brief OpenGL framebuffer handle wrapper.
*/
class frame_buf_t: public util::buffer_t<GLuint> {
using util::buffer_t<GLuint>::buffer_t;
public:
/**
* @brief Move ownership of OpenGL framebuffer object names.
*/
frame_buf_t(frame_buf_t &&) = default;
/**
* @brief Assign state from another instance while preserving ownership semantics.
*
* @return Reference or value produced by the operator.
*/
frame_buf_t &operator=(frame_buf_t &&) = default;
~frame_buf_t();
/**
* @brief Allocate the underlying object and wrap it in the owning handle.
*
* @param count Number of objects or handles to create.
* @return Created backend object, or null when creation fails.
*/
static frame_buf_t make(std::size_t count);
/**
* @brief Bind each framebuffer and clear its color attachment.
*/
inline void bind(std::nullptr_t, std::nullptr_t) {
int x = 0;
for (auto fb : (*this)) {
@@ -79,6 +159,12 @@ namespace gl {
return;
}
/**
* @brief Bind textures to this object's framebuffers as color attachments.
*
* @param it_begin First texture object to attach.
* @param it_end One-past-the-end iterator for texture objects to attach.
*/
template<class It>
void bind(It it_begin, It it_end) {
using namespace std::literals;
@@ -100,10 +186,20 @@ namespace gl {
/**
* Copies a part of the framebuffer to texture
*
* @param id Framebuffer index to copy from.
* @param texture Destination texture receiving the copied pixels.
* @param offset_x Source X offset in pixels.
* @param offset_y Source Y offset in pixels.
* @param width Frame or display width in pixels.
* @param height Frame or display height in pixels.
*/
void copy(int id, int texture, int offset_x, int offset_y, int width, int height);
};
/**
* @brief OpenGL shader object that compiles GLSL source.
*/
class shader_t {
KITTY_USING_MOVE_T(shader_internal_t, GLuint, std::numeric_limits<GLuint>::max(), {
if (el != std::numeric_limits<GLuint>::max()) {
@@ -112,16 +208,36 @@ namespace gl {
});
public:
/**
* @brief Read the shader compiler log.
*
* @return Shader compiler error log.
*/
std::string err_str();
/**
* @brief Compile an OpenGL shader and report compiler errors.
*
* @param source Shader source code to compile.
* @param type OpenGL shader type, such as GL_VERTEX_SHADER or GL_FRAGMENT_SHADER.
* @return Compiled shader object, or compiler log on failure.
*/
static util::Either<shader_t, std::string> compile(const std::string_view &source, GLenum type);
/**
* @brief Return the native handle owned by the wrapper.
*
* @return OpenGL shader object name.
*/
GLuint handle() const;
private:
shader_internal_t _shader;
};
/**
* @brief EGL image buffer with plane descriptors and imported GL textures.
*/
class buffer_t {
KITTY_USING_MOVE_T(buffer_internal_t, GLuint, std::numeric_limits<GLuint>::max(), {
if (el != std::numeric_limits<GLuint>::max()) {
@@ -130,13 +246,44 @@ namespace gl {
});
public:
/**
* @brief Allocate the underlying object and wrap it in the owning handle.
*
* @param offsets Byte offsets for each uniform member in the block.
* @param block Uniform block name used for diagnostics and updates.
* @param data Initial bytes copied into the uniform buffer.
* @return Created backend object, or null when creation fails.
*/
static buffer_t make(util::buffer_t<GLint> &&offsets, const char *block, const std::string_view &data);
/**
* @brief Return the native handle owned by the wrapper.
*
* @return OpenGL buffer object name.
*/
GLuint handle() const;
/**
* @brief Query a uniform block index from an OpenGL program.
*
* @return Uniform block name associated with the buffer.
*/
const char *block() const;
/**
* @brief Update one uniform member in the block buffer.
*
* @param view Raw bytes to copy into the uniform block.
* @param offset Uniform-member index whose offset is used as the destination.
*/
void update(const std::string_view &view, std::size_t offset = 0);
/**
* @brief Update multiple uniform members in the block buffer.
*
* @param members Uniform members to query within the block.
* @param count Number of members in the array.
* @param offset First uniform-member index to update.
*/
void update(std::string_view *members, std::size_t count, std::size_t offset = 0);
private:
@@ -149,6 +296,9 @@ namespace gl {
buffer_internal_t _buffer;
};
/**
* @brief OpenGL shader program with attached shader stages.
*/
class program_t {
KITTY_USING_MOVE_T(program_internal_t, GLuint, std::numeric_limits<GLuint>::max(), {
if (el != std::numeric_limits<GLuint>::max()) {
@@ -157,14 +307,44 @@ namespace gl {
});
public:
/**
* @brief Read the program linker log.
*
* @return OpenGL program link error log.
*/
std::string err_str();
/**
* @brief Link an OpenGL program from compiled shaders.
*
* @param vert Compiled vertex shader object.
* @param frag Compiled fragment shader object.
* @return Linked program object, or linker log on failure.
*/
static util::Either<program_t, std::string> link(const shader_t &vert, const shader_t &frag);
/**
* @brief Bind this program and attach a uniform buffer block.
*
* @param buffer Uniform buffer block used by the program.
*/
void bind(const buffer_t &buffer);
/**
* @brief Query a uniform location from an OpenGL program.
*
* @param block Uniform block name to bind.
* @param members Uniform members to query within the block.
* @param count Number of uniform members to resolve.
* @return Uniform buffer wrapper, or std::nullopt when lookup/allocation fails.
*/
std::optional<buffer_t> uniform(const char *block, std::pair<const char *, std::string_view> *members, std::size_t count);
/**
* @brief Return the native handle owned by the wrapper.
*
* @return OpenGL program object name.
*/
GLuint handle() const;
private:
@@ -174,56 +354,83 @@ namespace gl {
namespace gbm {
struct device;
/**
* @brief Function pointer used to destroy a GBM device.
*/
typedef void (*device_destroy_fn)(device *gbm);
/**
* @brief Function pointer used to create a GBM device from a file descriptor.
*/
typedef device *(*create_device_fn)(int fd);
extern device_destroy_fn device_destroy;
extern create_device_fn create_device;
/**
* @brief Owning GBM device pointer released with the GBM destroy callback.
*/
using gbm_t = util::dyn_safe_ptr<device, &device_destroy>;
/**
* @brief Load GBM symbols required for EGL device creation.
*
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init();
} // namespace gbm
namespace egl {
/**
* @brief Owning pointer for an EGL display connection.
*/
using display_t = util::dyn_safe_ptr_v2<void, EGLBoolean, &eglTerminate>;
/**
* @brief RGB capture image backed by EGL and OpenGL resources.
*/
struct rgb_img_t {
display_t::pointer display;
EGLImage xrgb8;
display_t::pointer display; ///< EGL display that owns the imported image.
EGLImage xrgb8; ///< EGL image for the imported XRGB plane.
gl::tex_t tex;
gl::tex_t tex; ///< Texture containing the imported RGB plane.
};
/**
* @brief NV12 capture image backed by EGL and OpenGL resources.
*/
struct nv12_img_t {
display_t::pointer display;
EGLImage r8;
EGLImage bg88;
display_t::pointer display; ///< EGL display that owns the imported planes.
EGLImage r8; ///< EGL image for the NV12 luma plane.
EGLImage bg88; ///< EGL image for the NV12 interleaved chroma plane.
gl::tex_t tex;
gl::frame_buf_t buf;
gl::tex_t tex; ///< Textures containing the imported Y and UV planes.
gl::frame_buf_t buf; ///< OpenGL framebuffer object used for rendering.
// sizeof(va::DRMPRIMESurfaceDescriptor::objects) / sizeof(va::DRMPRIMESurfaceDescriptor::objects[0]);
static constexpr std::size_t num_fds = 4;
static constexpr std::size_t num_fds = 4; ///< Maximum number of DMA-BUF plane file descriptors exported by VAAPI.
std::array<file_t, num_fds> fds;
std::array<file_t, num_fds> fds; ///< DMA-BUF file descriptors for each exported plane.
};
/**
* @brief YUV 4:4:4 capture image backed by EGL and OpenGL resources.
*/
struct yuv444_img_t {
display_t::pointer display;
EGLImage r8;
EGLImage g8;
EGLImage b8;
display_t::pointer display; ///< EGL display that owns the imported planes.
EGLImage r8; ///< EGL image for the Y plane.
EGLImage g8; ///< EGL image for the U plane.
EGLImage b8; ///< EGL image for the V plane.
gl::tex_t tex;
gl::frame_buf_t buf;
gl::tex_t tex; ///< Textures containing the imported Y, U, and V planes.
gl::frame_buf_t buf; ///< OpenGL framebuffer object used for rendering.
static constexpr std::size_t num_fds = 4;
static constexpr std::size_t num_fds = 4; ///< Num fds.
std::array<file_t, num_fds> fds;
std::array<file_t, num_fds> fds; ///< DMA-BUF file descriptors for each exported plane.
};
#ifndef DOXYGEN
KITTY_USING_MOVE_T(rgb_t, rgb_img_t, , {
if (el.xrgb8) {
eglDestroyImage(el.display, el.xrgb8);
@@ -261,28 +468,75 @@ namespace egl {
eglDestroyContext(disp, ctx);
}
});
#else
/**
* @brief Move-only wrapper for RGB EGL image resources.
*/
class rgb_t;
/**
* @brief Move-only wrapper for NV12 EGL image resources.
*/
class nv12_t;
/**
* @brief Move-only wrapper for YUV444 EGL image resources.
*/
class yuv444_t;
/**
* @brief Move-only wrapper for an EGL context.
*/
class ctx_t;
#endif
/**
* @brief EGL surface descriptor used to import a captured DMA-BUF.
*/
struct surface_descriptor_t {
int width;
int height;
int fds[4];
std::uint32_t fourcc;
std::uint64_t modifier;
std::uint32_t pitches[4];
std::uint32_t offsets[4];
int width; ///< Frame or display width in pixels.
int height; ///< Frame or display height in pixels.
int fds[4]; ///< DMA-BUF file descriptors for up to four planes.
std::uint32_t fourcc; ///< DRM fourcc pixel format for the buffer.
std::uint64_t modifier; ///< DRM format modifier describing the buffer layout.
std::uint32_t pitches[4]; ///< Row stride in bytes for each DMA-BUF plane.
std::uint32_t offsets[4]; ///< Byte offset to the first pixel for each DMA-BUF plane.
};
/**
* @brief Open and initialize the display connection used for capture.
*
* @param native_display Native display.
* @return Constructed display object.
*/
display_t make_display(std::variant<gbm::gbm_t::pointer, wl_display *, _XDisplay *> native_display);
/**
* @brief Create an EGL/OpenGL context for capture or conversion.
*
* @param display Display object or identifier associated with the operation.
* @return EGL context wrapper, or std::nullopt when context creation fails.
*/
std::optional<ctx_t> make_ctx(display_t::pointer display);
std::optional<rgb_t>
import_source(
display_t::pointer egl_display,
const surface_descriptor_t &xrgb
);
/**
* @brief Import an RGB source surface.
*
* @return Imported RGB image, or std::nullopt on failure.
*/
std::optional<rgb_t> import_source(display_t::pointer egl_display, const surface_descriptor_t &xrgb);
/**
* @brief Create a blank RGB texture for an image.
*
* @return Blank RGB image.
*/
rgb_t create_blank(platf::img_t &img);
/**
* @brief Import an NV12 target surface.
*
* @return Imported NV12 image, or std::nullopt on failure.
*/
std::optional<nv12_t> import_target(
display_t::pointer egl_display,
std::array<file_t, nv12_img_t::num_fds> &&fds,
@@ -290,6 +544,16 @@ namespace egl {
const surface_descriptor_t &uv
);
/**
* @brief Import a YUV444 target surface.
*
* @param egl_display EGL display.
* @param fds Target plane file descriptors.
* @param y Y plane descriptor.
* @param u U plane descriptor.
* @param v V plane descriptor.
* @return Imported YUV444 image, or std::nullopt on failure.
*/
std::optional<yuv444_t> import_target(
display_t::pointer egl_display,
std::array<file_t, yuv444_img_t::num_fds> &&fds,
@@ -307,27 +571,44 @@ namespace egl {
*/
std::optional<nv12_t> create_nv12_target(int width, int height, AVPixelFormat format);
/**
* @brief Create YUV444 target.
*
* @param width Frame or display width in pixels.
* @param height Frame or display height in pixels.
* @param format Pixel, audio, or protocol format being converted.
* @return Created YUV444 target object or status.
*/
std::optional<yuv444_t> create_yuv444_target(int width, int height, AVPixelFormat format);
/**
* @brief Cursor image and hotspot metadata captured from the window system.
*/
class cursor_t: public platf::img_t {
public:
int x;
int y;
int src_w;
int src_h;
int x; ///< Cursor hotspot or surface X coordinate.
int y; ///< Cursor hotspot or surface Y coordinate.
int src_w; ///< Cursor source image width in pixels.
int src_h; ///< Cursor source image height in pixels.
unsigned long serial;
unsigned long serial; ///< X11 cursor serial used to detect cursor image changes.
std::vector<std::uint8_t> buffer;
std::vector<std::uint8_t> buffer; ///< Cursor image pixels.
};
// Allow cursor and the underlying image to be kept together
/**
* @brief Captured image descriptor shared by EGL conversion paths.
*/
class img_descriptor_t: public cursor_t {
public:
~img_descriptor_t() {
reset();
}
/**
* @brief Reset the object to its initial empty state.
*/
void reset() {
for (auto x = 0; x < 4; ++x) {
if (sd.fds[x] >= 0) {
@@ -338,71 +619,162 @@ namespace egl {
}
}
surface_descriptor_t sd;
surface_descriptor_t sd; ///< DMA-BUF surface descriptor for the captured image.
// Increment sequence when new rgb_t needs to be created
std::uint64_t sequence;
std::uint64_t sequence; ///< Monotonic value used to detect when GL resources must be recreated.
// Frame is vertically flipped (GL convention)
bool y_invert {false};
bool y_invert {false}; ///< Whether the shader should invert the Y axis.
// PipeWire metadata
std::optional<uint64_t> pts;
std::optional<uint64_t> seq;
std::optional<bool> pw_damage;
std::optional<uint32_t> pw_flags;
std::optional<uint64_t> pts; ///< PipeWire presentation timestamp.
std::optional<uint64_t> seq; ///< PipeWire frame sequence number.
std::optional<bool> pw_damage; ///< Whether PipeWire damage tracking should be used.
std::optional<uint32_t> pw_flags; ///< PipeWire frame flags reported with the buffer.
};
/**
* @brief EGL/OpenGL scaler and colorspace conversion pipeline.
*/
class sws_t {
public:
/**
* @brief Create a software-scaling pipeline that renders to NV12 planes.
*
* @param in_width Source frame width in pixels.
* @param in_height Source frame height in pixels.
* @param out_width Destination frame width in pixels.
* @param out_height Destination frame height in pixels.
* @param tex Texture resource used by the converter.
* @return Constructed NV12 object.
*/
static std::optional<sws_t> make_nv12(int in_width, int in_height, int out_width, int out_height, gl::tex_t &&tex);
/**
* @brief Create a software-scaling pipeline that renders to YUV444 planes.
*
* @param in_width Source frame width in pixels.
* @param in_height Source frame height in pixels.
* @param out_width Destination frame width in pixels.
* @param out_height Destination frame height in pixels.
* @param tex Texture resource used by the converter.
* @return Constructed YUV444 object.
*/
static std::optional<sws_t> make_yuv444(int in_width, int in_height, int out_width, int out_height, gl::tex_t &&tex);
/**
* @brief Allocate the underlying object and wrap it in the owning handle.
*
* @param in_width Source frame width in pixels.
* @param in_height Source frame height in pixels.
* @param out_width Destination frame width in pixels.
* @param out_height Destination frame height in pixels.
* @param format Destination FFmpeg pixel format.
* @param is_yuv444 Whether the destination uses three full-resolution planes.
* @return Created backend object, or null when creation fails.
*/
static std::optional<sws_t> make(int in_width, int in_height, int out_width, int out_height, AVPixelFormat format, bool is_yuv444);
// Convert the loaded image into the first two framebuffers
/**
* @brief Convert the loaded source image into NV12 output planes.
*
* @param fb Framebuffer object to bind or update.
* @return Conversion status.
*/
int convert_nv12(gl::frame_buf_t &fb);
// Convert the loaded image into the first three framebuffers
/**
* @brief Convert the loaded source image into YUV444 output planes.
*
* @param fb Framebuffer object to bind or update.
* @return Conversion status.
*/
int convert_yuv444(gl::frame_buf_t &fb);
// Draw loaded image by programs to frame buffers
/**
* @brief Render the loaded source texture into output framebuffers.
*
* @param attachments Framebuffer attachments to bind.
* @param fb Framebuffer object to bind or update.
* @param count Number of output planes to draw.
* @param is_yuv444 Whether to render three YUV444 planes instead of NV12 planes.
* @return 0 when all draw calls complete; nonzero on OpenGL failure.
*/
int draw_programs_to_buffers(GLenum attachments[], gl::frame_buf_t &fb, int count, bool is_yuv444);
// Make an area of the image black
/**
* @brief Clear the render target to a blank frame.
*
* @param fb Framebuffer object to bind or update.
* @param offsetX_ Offset x.
* @param offsetY_ Offset y.
* @param width Frame or display width in pixels.
* @param height Frame or display height in pixels.
* @param is_yuv444 Is YUV444.
* @return 0 when the target area is cleared; nonzero on OpenGL failure.
*/
int blank(gl::frame_buf_t &fb, int offsetX_, int offsetY_, int width, int height, bool is_yuv444);
/**
* @brief Load ram data from the backing API or store.
*
* @param img Image or frame object to read from or populate.
*/
void load_ram(platf::img_t &img);
/**
* @brief Load vram data from the backing API or store.
*
* @param img Image or frame object to read from or populate.
* @param offset_x Offset x.
* @param offset_y Offset y.
* @param texture Texture resource to bind, update, or attach.
* @param is_yuv444 Is YUV444.
*/
void load_vram(img_descriptor_t &img, int offset_x, int offset_y, int texture, bool is_yuv444);
/**
* @brief Apply the configured colorspace metadata to the active frame.
*
* @param colorspace Colorimetry information used for conversion or encoding.
* @param is_yuv444 Is YUV444.
*/
void apply_colorspace(const video::sunshine_colorspace_t &colorspace, bool is_yuv444);
// The first texture is the monitor image.
// The second texture is the cursor image
gl::tex_t tex;
gl::tex_t tex; ///< Source and cursor textures used by the conversion pipeline.
// The cursor image will be blended into this framebuffer
gl::frame_buf_t cursor_framebuffer;
gl::frame_buf_t copy_framebuffer;
gl::frame_buf_t cursor_framebuffer; ///< Cursor framebuffer.
gl::frame_buf_t copy_framebuffer; ///< Copy framebuffer.
// Y - shader, UV - shader, Cursor - shader : for nv12
// Y - shader, U - shader, V - shader, Cursor - shader : for yuv444
std::array<gl::program_t, 4> program;
gl::buffer_t color_matrix;
std::array<gl::program_t, 4> program; ///< Program.
gl::buffer_t color_matrix; ///< Color matrix.
int out_width;
int out_height;
int in_width;
int in_height;
int offsetX;
int offsetY;
int out_width; ///< Out width.
int out_height; ///< Out height.
int in_width; ///< In width.
int in_height; ///< In height.
int offsetX; ///< Offset x.
int offsetY; ///< Offset y.
// Pointer to the texture to be converted to nv12
int loaded_texture;
int loaded_texture; ///< Loaded texture.
// Store latest cursor for load_vram
std::uint64_t serial;
std::uint64_t serial; ///< Serial.
};
/**
* @brief Log EGL failure details and return an error code.
*
* @return False after logging the EGL failure.
*/
bool fail();
} // namespace egl
+30
View File
@@ -21,6 +21,9 @@ using namespace std::literals;
namespace platf {
/**
* @brief Create the platform input backend for a stream.
*/
input_t input() {
return {new input_raw_t()};
}
@@ -29,41 +32,65 @@ namespace platf {
return std::make_unique<client_input_raw_t>(input);
}
/**
* @brief Release a platform input backend created by input().
*/
void freeInput(void *p) {
auto *input = (input_raw_t *) p;
delete input;
}
/**
* @brief Move mouse using the backend coordinate system.
*/
void move_mouse(input_t &input, int deltaX, int deltaY) {
auto raw = (input_raw_t *) input.get();
platf::mouse::move(raw, deltaX, deltaY);
}
/**
* @brief Move the pointer to an absolute client-provided touch coordinate.
*/
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
auto raw = (input_raw_t *) input.get();
platf::mouse::move_abs(raw, touch_port, x, y);
}
/**
* @brief Press or release a virtual mouse button.
*/
void button_mouse(input_t &input, int button, bool release) {
auto raw = (input_raw_t *) input.get();
platf::mouse::button(raw, button, release);
}
/**
* @brief Apply a vertical scroll event to the virtual mouse.
*/
void scroll(input_t &input, int high_res_distance) {
auto raw = (input_raw_t *) input.get();
platf::mouse::scroll(raw, high_res_distance);
}
/**
* @brief Apply a horizontal scroll event to the virtual mouse.
*/
void hscroll(input_t &input, int high_res_distance) {
auto raw = (input_raw_t *) input.get();
platf::mouse::hscroll(raw, high_res_distance);
}
/**
* @brief Press or release a virtual keyboard key.
*/
void keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags) {
auto raw = (input_raw_t *) input.get();
platf::keyboard::update(raw, modcode, release, flags);
}
/**
* @brief Submit UTF-8 text input to the keyboard backend.
*/
void unicode(input_t &input, char *utf8, int size) {
auto raw = (input_raw_t *) input.get();
platf::keyboard::unicode(raw, utf8, size);
@@ -84,6 +111,9 @@ namespace platf {
return platf::gamepad::alloc(raw, id, metadata, feedback_queue);
}
/**
* @brief Release gamepad resources.
*/
void free_gamepad(input_t &input, int nr) {
auto raw = (input_raw_t *) input.get();
platf::gamepad::free(raw, nr);
+37 -8
View File
@@ -20,6 +20,12 @@ using namespace std::literals;
namespace platf {
/**
* @brief Append the target seat name to an inputtino device name when needed.
*
* @param base_name Base uinput device name.
* @return Device name scoped to the target seat.
*/
inline std::string inputtino_name_for_seat(std::string_view base_name) {
auto seat_id = inputtino_seat::get_target_seat();
if (seat_id.empty() || seat_id == "seat0") {
@@ -35,14 +41,23 @@ namespace platf {
return name;
}
/**
* @brief Variant of inputtino virtual gamepad implementations Sunshine can create.
*/
using joypads_t = std::variant<inputtino::XboxOneJoypad, inputtino::SwitchJoypad, inputtino::PS5Joypad>;
/**
* @brief inputtino joypad collection and its ownership state.
*/
struct joypad_state {
std::unique_ptr<joypads_t> joypad;
gamepad_feedback_msg_t last_rumble;
gamepad_feedback_msg_t last_rgb_led;
std::unique_ptr<joypads_t> joypad; ///< Active virtual gamepad object for one connected client slot.
gamepad_feedback_msg_t last_rumble; ///< Last rumble.
gamepad_feedback_msg_t last_rgb_led; ///< Last RGB led.
};
/**
* @brief Global inputtino device handles shared by clients.
*/
struct input_raw_t {
input_raw_t():
mouse(inputtino::Mouse::create({
@@ -69,8 +84,8 @@ namespace platf {
~input_raw_t() = default;
// All devices are wrapped in Result because it might be that we aren't able to create them (ex: udev permission denied)
inputtino::Result<inputtino::Mouse> mouse;
inputtino::Result<inputtino::Keyboard> keyboard;
inputtino::Result<inputtino::Mouse> mouse; ///< Shared inputtino virtual mouse device.
inputtino::Result<inputtino::Keyboard> keyboard; ///< inputtino virtual keyboard device.
/**
* A list of gamepads that are currently connected.
@@ -79,7 +94,15 @@ namespace platf {
std::vector<std::shared_ptr<joypad_state>> gamepads;
};
/**
* @brief Per-client inputtino devices for touch and pen input.
*/
struct client_input_raw_t: public client_input_t {
/**
* @brief Create per-client inputtino devices for touch and pen input.
*
* @param input Platform input backend that receives the event.
*/
client_input_raw_t(input_t &input):
touch(inputtino::TouchScreen::create({
.name = inputtino_name_for_seat("Touch passthrough"sv),
@@ -102,16 +125,22 @@ namespace platf {
}
}
input_raw_t *global;
input_raw_t *global; ///< Shared inputtino device set owned by the global input context.
// Device state and handles for pen and touch input must be stored in the per-client
// input context, because each connected client may be sending their own independent
// pen/touch events. To maintain separation, we expose separate pen and touch devices
// for each client.
inputtino::Result<inputtino::TouchScreen> touch;
inputtino::Result<inputtino::PenTablet> pen;
inputtino::Result<inputtino::TouchScreen> touch; ///< Per-client virtual touchscreen device.
inputtino::Result<inputtino::PenTablet> pen; ///< Per-client virtual pen tablet device.
};
/**
* @brief Convert degrees to radians for controller motion data.
*
* @param degree Angle in degrees to convert.
* @return Angle in radians.
*/
inline float deg2rad(float degree) {
return degree * (M_PI / 180.f);
}
@@ -20,6 +20,9 @@ using namespace std::literals;
namespace platf::gamepad {
/**
* @brief Enumerates supported gamepad status options.
*/
enum GamepadStatus {
UHID_NOT_AVAILABLE = 0, ///< UHID is not available
UINPUT_NOT_AVAILABLE, ///< UINPUT is not available
@@ -27,6 +30,11 @@ namespace platf::gamepad {
GAMEPAD_STATUS ///< Helper to indicate the number of status
};
/**
* @brief Create xbox one.
*
* @return Created xbox one object or status.
*/
auto create_xbox_one() {
return inputtino::XboxOneJoypad::create({.name = inputtino_name_for_seat("Sunshine X-Box One (virtual) pad"sv),
// https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c#L147
@@ -35,6 +43,11 @@ namespace platf::gamepad {
.version = 0x0408});
}
/**
* @brief Create an inputtino Nintendo Switch Pro controller.
*
* @return Created switch object or status.
*/
auto create_switch() {
return inputtino::SwitchJoypad::create({.name = inputtino_name_for_seat("Sunshine Nintendo (virtual) pad"sv),
// https://github.com/torvalds/linux/blob/master/drivers/hid/hid-ids.h#L981
@@ -43,6 +56,12 @@ namespace platf::gamepad {
.version = 0x8111});
}
/**
* @brief Create an inputtino DualSense controller.
*
* @param globalIndex Global index.
* @return Created DS5 object or status.
*/
auto create_ds5(int globalIndex) {
std::string device_mac = ""; // Inputtino checks empty() to generate a random MAC
@@ -54,6 +73,9 @@ namespace platf::gamepad {
return inputtino::PS5Joypad::create({.name = inputtino_name_for_seat("Sunshine PS5 (virtual) pad"sv), .vendor_id = 0x054C, .product_id = 0x0CE6, .version = 0x8111, .device_phys = device_mac, .device_uniq = device_mac});
}
/**
* @brief Allocate and initialize platform input state for a stream.
*/
int alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
ControllerType selectedGamepadType;
@@ -180,12 +202,18 @@ namespace platf::gamepad {
return -1;
}
/**
* @brief Release backend resources for the indexed gamepad.
*/
void free(input_raw_t *raw, int nr) {
// This will call the destructor which in turn will stop the background threads for rumble and LED (and ultimately remove the joypad device)
raw->gamepads[nr]->joypad.reset();
raw->gamepads[nr].reset();
}
/**
* @brief Apply the supplied state update to the platform backend.
*/
void update(input_raw_t *raw, int nr, const gamepad_state_t &gamepad_state) {
auto gamepad = raw->gamepads[nr];
if (!gamepad) {
@@ -201,6 +229,9 @@ namespace platf::gamepad {
*gamepad->joypad);
}
/**
* @brief Apply controller touchpad data to the backend device.
*/
void touch(input_raw_t *raw, const gamepad_touch_t &touch) {
auto gamepad = raw->gamepads[touch.id.globalIndex];
if (!gamepad) {
@@ -216,6 +247,9 @@ namespace platf::gamepad {
}
}
/**
* @brief Apply controller motion sensor data to the backend device.
*/
void motion(input_raw_t *raw, const gamepad_motion_t &motion) {
auto gamepad = raw->gamepads[motion.id.globalIndex];
if (!gamepad) {
@@ -234,6 +268,9 @@ namespace platf::gamepad {
}
}
/**
* @brief Apply controller battery status to the backend device.
*/
void battery(input_raw_t *raw, const gamepad_battery_t &battery) {
auto gamepad = raw->gamepads[battery.id.globalIndex];
if (!gamepad) {
@@ -263,6 +300,9 @@ namespace platf::gamepad {
}
}
/**
* @brief Return the virtual gamepad types supported by inputtino.
*/
std::vector<supported_gamepad_t> &supported_gamepads(input_t *input) {
if (!input) {
static std::vector gps {
@@ -17,23 +17,72 @@ using namespace std::literals;
namespace platf::gamepad {
/**
* @brief Enumerates supported controller type options.
*/
enum ControllerType {
XboxOneWired, ///< Xbox One Wired Controller
DualSenseWired, ///< DualSense Wired Controller
SwitchProWired ///< Switch Pro Wired Controller
};
/**
* @brief Allocate and initialize platform input state for a stream.
*
* @param raw Platform-specific input backend state.
* @param id Identifier for the controller, session, display, or resource.
* @param metadata Output structure populated with HDR metadata.
* @param feedback_queue Feedback queue.
* @return Allocated object or identifier, or an error value on failure.
*/
int alloc(input_raw_t *raw, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue);
/**
* @brief Release backend resources for the indexed gamepad.
*
* @param raw Platform-specific input backend state.
* @param nr Controller index assigned by the client.
*/
void free(input_raw_t *raw, int nr);
/**
* @brief Apply the supplied state update to the platform backend.
*
* @param raw Platform-specific input backend state.
* @param nr Controller index assigned by the client.
* @param gamepad_state Gamepad state.
*/
void update(input_raw_t *raw, int nr, const gamepad_state_t &gamepad_state);
/**
* @brief Apply controller touchpad data to the backend device.
*
* @param raw Platform-specific input backend state.
* @param touch Touch event data to apply to the virtual device.
*/
void touch(input_raw_t *raw, const gamepad_touch_t &touch);
/**
* @brief Apply controller motion sensor data to the backend device.
*
* @param raw Platform-specific input backend state.
* @param motion Motion sensor data to apply to the virtual device.
*/
void motion(input_raw_t *raw, const gamepad_motion_t &motion);
/**
* @brief Apply controller battery status to the backend device.
*
* @param raw Platform-specific input backend state.
* @param battery Battery status data reported by the virtual device.
*/
void battery(input_raw_t *raw, const gamepad_battery_t &battery);
/**
* @brief Return gamepad slots supported by the inputtino backend.
*
* @param input Platform input backend that receives the event.
* @return Mutable list of supported virtual gamepads for the input backend.
*/
std::vector<supported_gamepad_t> &supported_gamepads(input_t *input);
} // namespace platf::gamepad
@@ -25,6 +25,8 @@ namespace platf::keyboard {
* ex: ['👱'] = "1F471" // see UTF encoding at https://www.compart.com/en/unicode/U+1F471
*
* adapted from: https://stackoverflow.com/a/7639754
* @param str UTF-8 text to encode as hexadecimal.
* @return Value converted to hex.
*/
std::string to_hex(const std::basic_string<char32_t> &str) {
std::stringstream ss;
@@ -160,6 +162,9 @@ namespace platf::keyboard {
{KEY_102ND, 0xE2}
};
/**
* @brief Apply the supplied state update to the platform backend.
*/
void update(input_raw_t *raw, uint16_t modcode, bool release, uint8_t flags) {
if (raw->keyboard) {
if (release) {
@@ -170,6 +175,9 @@ namespace platf::keyboard {
}
}
/**
* @brief Submit UTF-8 text input to the keyboard backend.
*/
void unicode(input_raw_t *raw, char *utf8, int size) {
if (raw->keyboard) {
/* Reading input text as UTF-8 */
@@ -15,7 +15,22 @@
using namespace std::literals;
namespace platf::keyboard {
/**
* @brief Apply the supplied state update to the platform backend.
*
* @param raw Platform-specific input backend state.
* @param modcode Modifier key code to update.
* @param release Whether the key or button event is a release.
* @param flags Bit flags that modify the requested operation.
*/
void update(input_raw_t *raw, uint16_t modcode, bool release, uint8_t flags);
/**
* @brief Submit UTF-8 text input to the keyboard backend.
*
* @param raw Platform-specific input backend state.
* @param utf8 UTF-8 text submitted by the client.
* @param size Number of bytes or elements requested.
*/
void unicode(input_raw_t *raw, char *utf8, int size);
} // namespace platf::keyboard
@@ -19,18 +19,27 @@ using namespace std::literals;
namespace platf::mouse {
/**
* @brief Apply a relative pointer movement to the virtual mouse.
*/
void move(input_raw_t *raw, int deltaX, int deltaY) {
if (raw->mouse) {
(*raw->mouse).move(deltaX, deltaY);
}
}
/**
* @brief Move abs using the backend coordinate system.
*/
void move_abs(input_raw_t *raw, const touch_port_t &touch_port, float x, float y) {
if (raw->mouse) {
(*raw->mouse).move_abs(x, y, touch_port.width, touch_port.height);
}
}
/**
* @brief Press or release a virtual mouse button.
*/
void button(input_raw_t *raw, int button, bool release) {
if (raw->mouse) {
inputtino::Mouse::MOUSE_BUTTON btn_type;
@@ -62,18 +71,27 @@ namespace platf::mouse {
}
}
/**
* @brief Apply a vertical scroll event to the virtual mouse.
*/
void scroll(input_raw_t *raw, int high_res_distance) {
if (raw->mouse) {
(*raw->mouse).vertical_scroll(high_res_distance);
}
}
/**
* @brief Apply a horizontal scroll event to the virtual mouse.
*/
void hscroll(input_raw_t *raw, int high_res_distance) {
if (raw->mouse) {
(*raw->mouse).horizontal_scroll(high_res_distance);
}
}
/**
* @brief Return the current virtual pointer location.
*/
util::point_t get_location(input_raw_t *raw) {
if (raw->mouse) {
// TODO: decide what to do after https://github.com/games-on-whales/inputtino/issues/6 is resolved.
@@ -15,15 +15,55 @@
using namespace std::literals;
namespace platf::mouse {
/**
* @brief Apply a relative pointer movement to the virtual mouse.
*
* @param raw Platform-specific input backend state.
* @param deltaX Horizontal relative movement in client coordinates.
* @param deltaY Vertical relative movement in client coordinates.
*/
void move(input_raw_t *raw, int deltaX, int deltaY);
/**
* @brief Move the pointer to an absolute client-provided touch coordinate.
*
* @param raw Platform-specific input backend state.
* @param touch_port Touch coordinate bounds used for scaling.
* @param x Horizontal absolute coordinate from the client.
* @param y Vertical absolute coordinate from the client.
*/
void move_abs(input_raw_t *raw, const touch_port_t &touch_port, float x, float y);
/**
* @brief Press or release a virtual mouse button.
*
* @param raw Platform-specific input backend state.
* @param button Mouse button identifier to press or release.
* @param release Whether the key or button event is a release.
*/
void button(input_raw_t *raw, int button, bool release);
/**
* @brief Apply a vertical scroll event to the virtual mouse.
*
* @param raw Platform-specific input backend state.
* @param high_res_distance High-resolution scroll distance reported by the client.
*/
void scroll(input_raw_t *raw, int high_res_distance);
/**
* @brief Apply a horizontal scroll event to the virtual mouse.
*
* @param raw Platform-specific input backend state.
* @param high_res_distance High-resolution scroll distance reported by the client.
*/
void hscroll(input_raw_t *raw, int high_res_distance);
/**
* @brief Return the current virtual pointer location.
*
* @param raw Platform-specific input backend state.
* @return Current virtual pointer location in screen coordinates.
*/
util::point_t get_location(input_raw_t *raw);
} // namespace platf::mouse
@@ -18,6 +18,9 @@
using namespace std::literals;
namespace platf::pen {
/**
* @brief Apply the supplied state update to the platform backend.
*/
void update(client_input_raw_t *raw, const touch_port_t &touch_port, const pen_input_t &pen) {
if (raw->pen) {
// First set the buttons
+8 -1
View File
@@ -16,5 +16,12 @@
using namespace std::literals;
namespace platf::pen {
/**
* @brief Apply the supplied state update to the platform backend.
*
* @param raw Platform-specific input backend state.
* @param touch_port Touch coordinate bounds used for scaling.
* @param pen Pen event data to inject.
*/
void update(client_input_raw_t *raw, const touch_port_t &touch_port, const pen_input_t &pen);
}
} // namespace platf::pen
@@ -11,6 +11,8 @@ namespace platf::inputtino_seat {
/**
* Determine the target seat for the current Sunshine instance.
* Returns empty string if no seat could be determined.
*
* @return Seat name used for virtual input devices, or an empty string when unknown.
*/
std::string get_target_seat();
@@ -18,6 +18,9 @@
using namespace std::literals;
namespace platf::touch {
/**
* @brief Apply the supplied state update to the platform backend.
*/
void update(client_input_raw_t *raw, const touch_port_t &touch_port, const touch_input_t &touch) {
if (raw->touch) {
switch (touch.eventType) {
+8 -1
View File
@@ -16,5 +16,12 @@
using namespace std::literals;
namespace platf::touch {
/**
* @brief Apply the supplied state update to the platform backend.
*
* @param raw Platform-specific input backend state.
* @param touch_port Touch coordinate bounds used for scaling.
* @param touch Touch event data to apply to the virtual device.
*/
void update(client_input_raw_t *raw, const touch_port_t &touch_port, const touch_input_t &touch);
}
} // namespace platf::touch
+452 -65
View File
@@ -37,6 +37,9 @@ namespace platf {
namespace kms {
/**
* @brief Temporarily owns CAP_SYS_ADMIN while opening DRM capture resources.
*/
class cap_sys_admin {
public:
cap_sys_admin() {
@@ -56,11 +59,20 @@ namespace platf {
cap_free(caps);
}
cap_t caps;
cap_t caps; ///< Caps.
};
/**
* @brief RAII wrapper for DRM framebuffer metadata and GEM handles.
*/
class wrapper_fb {
public:
/**
* @brief Wrap legacy DRM framebuffer metadata.
*
* @param card_fd DRM card file descriptor used to close GEM handles.
* @param fb Legacy framebuffer metadata returned by drmModeGetFB.
*/
wrapper_fb(uint32_t card_fd, drmModeFB *fb):
card_fd {card_fd},
fb {fb},
@@ -76,6 +88,12 @@ namespace platf {
pitches[0] = fb->pitch;
}
/**
* @brief Wrap DRM framebuffer metadata with per-plane modifiers.
*
* @param card_fd DRM card file descriptor used to close GEM handles.
* @param fb2 Framebuffer metadata returned by drmModeGetFB2.
*/
wrapper_fb(uint32_t card_fd, drmModeFB2 *fb2):
card_fd {card_fd},
fb2 {fb2},
@@ -107,30 +125,63 @@ namespace platf {
}
}
uint32_t card_fd;
drmModeFB *fb = nullptr;
drmModeFB2 *fb2 = nullptr;
uint32_t fb_id;
uint32_t width;
uint32_t height;
uint32_t pixel_format;
uint64_t modifier;
uint32_t handles[4];
uint32_t pitches[4];
uint32_t offsets[4];
uint32_t card_fd; ///< DRM card file descriptor that owns the framebuffer ID.
drmModeFB *fb = nullptr; ///< Legacy DRM framebuffer metadata when modifier data is unavailable.
drmModeFB2 *fb2 = nullptr; ///< DRM framebuffer metadata with modifier support.
uint32_t fb_id; ///< DRM framebuffer ID being captured.
uint32_t width; ///< Framebuffer width in pixels.
uint32_t height; ///< Framebuffer height in pixels.
uint32_t pixel_format; ///< DRM fourcc pixel format for the framebuffer.
uint64_t modifier; ///< DRM format modifier describing framebuffer memory layout.
uint32_t handles[4]; ///< GEM handles for each framebuffer plane.
uint32_t pitches[4]; ///< Row stride in bytes for each framebuffer plane.
uint32_t offsets[4]; ///< Byte offset to the first pixel for each framebuffer plane.
};
/**
* @brief DRM plane resource list released with `drmModeFreePlaneResources`.
*/
using plane_res_t = util::safe_ptr<drmModePlaneRes, drmModeFreePlaneResources>;
/**
* @brief DRM encoder pointer released with `drmModeFreeEncoder`.
*/
using encoder_t = util::safe_ptr<drmModeEncoder, drmModeFreeEncoder>;
/**
* @brief DRM resource list released with `drmModeFreeResources`.
*/
using res_t = util::safe_ptr<drmModeRes, drmModeFreeResources>;
/**
* @brief DRM plane pointer released with `drmModeFreePlane`.
*/
using plane_t = util::safe_ptr<drmModePlane, drmModeFreePlane>;
/**
* @brief DRM framebuffer pointer released with `drmModeFreeFB2`.
*/
using fb_t = std::unique_ptr<wrapper_fb>;
/**
* @brief DRM CRTC pointer released with `drmModeFreeCrtc`.
*/
using crtc_t = util::safe_ptr<drmModeCrtc, drmModeFreeCrtc>;
/**
* @brief DRM object-property list released with `drmModeFreeObjectProperties`.
*/
using obj_prop_t = util::safe_ptr<drmModeObjectProperties, drmModeFreeObjectProperties>;
/**
* @brief DRM property pointer released with `drmModeFreeProperty`.
*/
using prop_t = util::safe_ptr<drmModePropertyRes, drmModeFreeProperty>;
/**
* @brief DRM property-blob pointer released with `drmModeFreePropertyBlob`.
*/
using prop_blob_t = util::safe_ptr<drmModePropertyBlobRes, drmModeFreePropertyBlob>;
/**
* @brief DRM version pointer released with `drmFreeVersion`.
*/
using version_t = util::safe_ptr<drmVersion, drmFreeVersion>;
/**
* @brief Counter map keyed by DRM connector type.
*/
using conn_type_count_t = std::map<std::uint32_t, std::uint32_t>;
static int env_width;
@@ -139,6 +190,12 @@ namespace platf {
static int env_logical_width;
static int env_logical_height;
/**
* @brief Convert a DRM plane type value to a diagnostic string.
*
* @param val Value assigned to the synchronized object.
* @return Static text for the DRM plane type.
*/
std::string_view plane_type(std::uint64_t val) {
switch (val) {
case DRM_PLANE_TYPE_OVERLAY:
@@ -152,45 +209,56 @@ namespace platf {
return "UNKNOWN"sv;
}
/**
* @brief DRM connector and mode information for one display.
*/
struct connector_t {
// For example: HDMI-A or HDMI
std::uint32_t type;
std::uint32_t type; ///< Type.
// Equals zero if not applicable
std::uint32_t crtc_id;
std::uint32_t crtc_id; ///< Crtc ID.
// For example HDMI-A-{index} or HDMI-{index}
std::uint32_t index;
std::uint32_t index; ///< Index.
// ID of the connector
std::uint32_t connector_id;
std::uint32_t connector_id; ///< Connector ID.
bool connected;
bool connected; ///< Whether the DRM connector is connected.
};
/**
* @brief KMS monitor capture state and DRM resources.
*/
struct monitor_t {
// Connector attributes
std::uint32_t type;
std::uint32_t index;
std::uint32_t type; ///< Type.
std::uint32_t index; ///< Index.
// Monitor index in the global list
std::uint32_t monitor_index;
std::uint32_t monitor_index; ///< Monitor index.
platf::touch_port_t viewport;
platf::touch_port_t viewport; ///< Viewport.
};
/**
* @brief DRM card, device path, and render-node metadata.
*/
struct card_descriptor_t {
std::string path;
std::string path; ///< Path.
std::map<std::uint32_t, monitor_t> crtc_to_monitor;
std::map<std::uint32_t, monitor_t> crtc_to_monitor; ///< Crtc to monitor.
};
static std::vector<card_descriptor_t> card_descriptors;
static std::uint32_t from_view(const std::string_view &string) {
#define _CONVERT(x, y) \
if (string == x) \
return DRM_MODE_CONNECTOR_##y
#ifndef DOXYGEN
#define _CONVERT(x, y) \
if (string == x) \
return DRM_MODE_CONNECTOR_##y
#endif
// This list was created from the following sources:
// https://gitlab.freedesktop.org/mesa/drm/-/blob/main/xf86drmMode.c (drmModeGetConnectorTypeName)
@@ -244,8 +312,18 @@ namespace platf {
return DRM_MODE_CONNECTOR_Unknown;
}
/**
* @brief Iterator over DRM planes and their associated properties.
*/
class plane_it_t: public round_robin_util::it_wrap_t<plane_t::element_type, plane_it_t> {
public:
/**
* @brief Create an iterator over DRM planes starting at a specific plane ID.
*
* @param fd DRM card file descriptor.
* @param plane_p Current plane ID pointer.
* @param end One-past-the-end plane ID pointer.
*/
plane_it_t(int fd, std::uint32_t *plane_p, std::uint32_t *end):
fd {fd},
plane_p {plane_p},
@@ -253,12 +331,21 @@ namespace platf {
load_next_valid_plane();
}
/**
* @brief Create the end iterator for a DRM plane range.
*
* @param fd DRM card file descriptor.
* @param end One-past-the-end plane ID pointer.
*/
plane_it_t(int fd, std::uint32_t *end):
fd {fd},
plane_p {end},
end {end} {
}
/**
* @brief Load next valid plane.
*/
void load_next_valid_plane() {
this->plane.reset();
@@ -274,50 +361,79 @@ namespace platf {
}
}
/**
* @brief Advance the iterator to the next element.
*/
void inc() {
++plane_p;
load_next_valid_plane();
}
/**
* @brief Compare two iterators for equality.
*
* @param other Plane iterator to compare against.
* @return True when both iterators reference the same plane position.
*/
bool eq(const plane_it_t &other) const {
return plane_p == other.plane_p;
}
/**
* @brief Return the currently wrapped value or handle.
*
* @return Underlying native handle or object pointer.
*/
plane_t::pointer get() {
return plane.get();
}
int fd;
std::uint32_t *plane_p;
std::uint32_t *end;
int fd; ///< DRM card file descriptor.
std::uint32_t *plane_p; ///< Current plane ID pointer.
std::uint32_t *end; ///< One-past-the-end plane ID pointer.
util::shared_t<plane_t> plane;
util::shared_t<plane_t> plane; ///< Plane.
};
/**
* @brief Cursor position and visibility for the current capture frame.
*/
struct cursor_t {
// Public properties used during blending
bool visible = false;
std::int32_t x;
std::int32_t y;
std::uint32_t dst_w;
std::uint32_t dst_h;
std::uint32_t src_w;
std::uint32_t src_h;
std::vector<std::uint8_t> pixels;
unsigned long serial;
bool visible = false; ///< Whether the KMS cursor plane is visible.
std::int32_t x; ///< X.
std::int32_t y; ///< Y.
std::uint32_t dst_w; ///< Dst w.
std::uint32_t dst_h; ///< Dst h.
std::uint32_t src_w; ///< Src w.
std::uint32_t src_h; ///< Src h.
std::vector<std::uint8_t> pixels; ///< Pixels.
unsigned long serial; ///< Serial.
// Private properties used for tracking cursor changes
std::uint64_t prop_src_x;
std::uint64_t prop_src_y;
std::uint64_t prop_src_w;
std::uint64_t prop_src_h;
std::uint32_t fb_id;
std::uint64_t prop_src_x; ///< Prop src x.
std::uint64_t prop_src_y; ///< Prop src y.
std::uint64_t prop_src_w; ///< Prop src w.
std::uint64_t prop_src_h; ///< Prop src h.
std::uint32_t fb_id; ///< Fb ID.
};
/**
* @brief DRM card, render node, and plane metadata used for KMS capture.
*/
class card_t {
public:
/**
* @brief Internal connector metadata tuple used while selecting a monitor.
*/
using connector_interal_t = util::safe_ptr<drmModeConnector, drmModeFreeConnector>;
/**
* @brief Open a DRM connector and cache its monitor metadata.
*
* @param path Filesystem path for the DRM device or resource.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(const char *path) {
cap_sys_admin admin;
fd.el = open(path, O_RDWR);
@@ -373,6 +489,12 @@ namespace platf {
return 0;
}
/**
* @brief Return framebuffer metadata for the current CRTC.
*
* @param plane DRM or EGL plane index being described.
* @return Framebuffer metadata wrapper, or nullptr when the framebuffer cannot be read.
*/
fb_t fb(plane_t::pointer plane) {
cap_sys_admin admin;
@@ -389,23 +511,51 @@ namespace platf {
return nullptr;
}
/**
* @brief Return the DRM CRTC object for the monitor.
*
* @param id DRM CRTC ID.
* @return Owning pointer to the DRM CRTC object.
*/
crtc_t crtc(std::uint32_t id) {
return drmModeGetCrtc(fd.el, id);
}
/**
* @brief Return the DRM encoder object for the connector.
*
* @param id DRM encoder ID.
* @return Owning pointer to the DRM encoder object.
*/
encoder_t encoder(std::uint32_t id) {
return drmModeGetEncoder(fd.el, id);
}
/**
* @brief Return the DRM resource list for the card.
*
* @return DRM card resource list.
*/
res_t res() {
return drmModeGetResources(fd.el);
}
/**
* @brief Check whether nvidia.
*
* @return True when the DRM device appears to be driven by NVIDIA.
*/
bool is_nvidia() {
version_t ver {drmGetVersion(fd.el)};
return ver && ver->name && strncmp(ver->name, "nvidia-drm", 10) == 0;
}
/**
* @brief Check whether cursor.
*
* @param plane_id Plane ID.
* @return True when the DRM plane is a cursor plane.
*/
bool is_cursor(std::uint32_t plane_id) {
auto props = plane_props(plane_id);
for (auto &[prop, val] : props) {
@@ -421,6 +571,13 @@ namespace platf {
return false;
}
/**
* @brief Look up a DRM property value by property name.
*
* @param props DRM property collection to inspect.
* @param name DRM property name to search for.
* @return Property value when the property exists.
*/
std::optional<std::uint64_t> prop_value_by_name(const std::vector<std::pair<prop_t, std::uint64_t>> &props, std::string_view name) {
for (auto &[prop, val] : props) {
if (prop->name == name) {
@@ -430,6 +587,12 @@ namespace platf {
return std::nullopt;
}
/**
* @brief Get panel orientation.
*
* @param plane_id Plane ID.
* @return DRM rotation bitmask for the panel orientation.
*/
std::uint32_t get_panel_orientation(std::uint32_t plane_id) {
auto props = plane_props(plane_id);
auto value = prop_value_by_name(props, "rotation"sv);
@@ -441,6 +604,12 @@ namespace platf {
return DRM_MODE_ROTATE_0;
}
/**
* @brief Get crtc index by ID.
*
* @param crtc_id Crtc ID.
* @return Zero-based CRTC index, or -1 when the CRTC ID is unknown.
*/
int get_crtc_index_by_id(std::uint32_t crtc_id) {
auto resources = res();
for (int i = 0; i < resources->count_crtcs; i++) {
@@ -451,10 +620,22 @@ namespace platf {
return -1;
}
/**
* @brief Return the DRM connector object for the display.
*
* @param id DRM connector ID.
* @return Owning pointer to the DRM connector object.
*/
connector_interal_t connector(std::uint32_t id) {
return drmModeGetConnector(fd.el, id);
}
/**
* @brief Refresh the monitor list reported by the display server.
*
* @param conn_type_count Conn type count.
* @return Connector descriptors for monitors known to the DRM card.
*/
std::vector<connector_t> monitors(conn_type_count_t &conn_type_count) {
auto resources = res();
if (!resources) {
@@ -489,6 +670,12 @@ namespace platf {
return monitors;
}
/**
* @brief Return the DRM file descriptor owned by the object.
*
* @param handle GEM handle exported by the DRM framebuffer.
* @return DMA-BUF file descriptor for the GEM handle.
*/
file_t handleFD(std::uint32_t handle) {
file_t fb_fd;
@@ -500,6 +687,13 @@ namespace platf {
return fb_fd;
}
/**
* @brief Return DRM object properties for the resource.
*
* @param id DRM object ID.
* @param type DRM object type.
* @return DRM properties and their current values.
*/
std::vector<std::pair<prop_t, std::uint64_t>> props(std::uint32_t id, std::uint32_t type) {
obj_prop_t obj_prop = drmModeObjectGetProperties(fd.el, id, type);
if (!obj_prop) {
@@ -516,39 +710,84 @@ namespace platf {
return props;
}
/**
* @brief Return DRM plane properties.
*
* @param id DRM plane ID.
* @return Plane properties and their current values.
*/
std::vector<std::pair<prop_t, std::uint64_t>> plane_props(std::uint32_t id) {
return props(id, DRM_MODE_OBJECT_PLANE);
}
/**
* @brief Return DRM CRTC properties.
*
* @param id DRM CRTC ID.
* @return CRTC properties and their current values.
*/
std::vector<std::pair<prop_t, std::uint64_t>> crtc_props(std::uint32_t id) {
return props(id, DRM_MODE_OBJECT_CRTC);
}
/**
* @brief Return DRM connector properties.
*
* @param id DRM connector ID.
* @return Connector properties and their current values.
*/
std::vector<std::pair<prop_t, std::uint64_t>> connector_props(std::uint32_t id) {
return props(id, DRM_MODE_OBJECT_CONNECTOR);
}
/**
* @brief Fetch DRM plane metadata by plane-list index.
*
* @param index Zero-based index into the DRM plane resource list.
* @return Plane metadata for the requested DRM plane.
*/
plane_t operator[](std::uint32_t index) {
return drmModeGetPlane(fd.el, plane_res->planes[index]);
}
/**
* @brief Return the number of items in the wrapped DRM collection.
*
* @return Number of DRM planes reported by the card.
*/
std::uint32_t count() {
return plane_res->count_planes;
}
/**
* @brief Return an iterator to the first byte in the buffer view.
*
* @return Iterator to the first element.
*/
plane_it_t begin() const {
return plane_it_t {fd.el, plane_res->planes, plane_res->planes + plane_res->count_planes};
}
/**
* @brief Return an iterator one past the final byte in the buffer view.
*
* @return Iterator one past the last element.
*/
plane_it_t end() const {
return plane_it_t {fd.el, plane_res->planes + plane_res->count_planes};
}
file_t fd;
file_t render_fd;
plane_res_t plane_res;
file_t fd; ///< DRM card file descriptor.
file_t render_fd; ///< Render fd.
plane_res_t plane_res; ///< Plane res.
};
/**
* @brief Build a lookup from DRM CRTC IDs to monitor descriptors.
*
* @param connectors DRM connector list being mapped to monitors.
* @return Map keyed by CRTC ID.
*/
std::map<std::uint32_t, monitor_t> map_crtc_to_monitor(const std::vector<connector_t> &connectors) {
std::map<std::uint32_t, monitor_t> result;
@@ -562,6 +801,9 @@ namespace platf {
return result;
}
/**
* @brief Multi-source KMS image assembled for encoding.
*/
struct kms_img_t: public img_t {
~kms_img_t() override {
delete[] data;
@@ -569,6 +811,13 @@ namespace platf {
}
};
/**
* @brief Write a debug log representation of the input packet.
*
* @param plane DRM or EGL plane index being described.
* @param fb Framebuffer object to bind or update.
* @param crtc DRM CRTC identifier to map.
*/
void print(plane_t::pointer plane, fb_t::pointer fb, crtc_t::pointer crtc) {
if (crtc) {
BOOST_LOG(debug) << "crtc("sv << crtc->x << ", "sv << crtc->y << ')';
@@ -601,13 +850,28 @@ namespace platf {
BOOST_LOG(debug) << ss.str();
}
/**
* @brief Base KMS display capture backend shared by RAM and VRAM paths.
*/
class display_t: public platf::display_t {
public:
/**
* @brief Initialize common KMS display state for the requested memory type.
*
* @param mem_type Mem type.
*/
display_t(mem_type_e mem_type):
platf::display_t(),
mem_type {mem_type} {
}
/**
* @brief Initialize the base KMS capture state for the selected monitor.
*
* @param display_name Display name.
* @param config Configuration values to apply.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(const std::string &display_name, const ::video::config_t &config) {
delay = std::chrono::nanoseconds {1s} / config.framerate;
@@ -816,6 +1080,11 @@ namespace platf {
return 0;
}
/**
* @brief Report whether the active display mode is HDR.
*
* @return True when the active display mode is HDR.
*/
bool is_hdr() {
if (!hdr_metadata_blob_id || *hdr_metadata_blob_id == 0) {
return false;
@@ -862,6 +1131,12 @@ namespace platf {
}
}
/**
* @brief Read HDR metadata for the active display mode.
*
* @param metadata Output structure populated with HDR metadata.
* @return True when HDR metadata was written to the output structure.
*/
bool get_hdr_metadata(SS_HDR_METADATA &metadata) {
// This performs all the metadata validation
if (!is_hdr()) {
@@ -891,6 +1166,9 @@ namespace platf {
return true;
}
/**
* @brief Update cached cursor-plane image and position.
*/
void update_cursor() {
if (cursor_plane_id < 0) {
return;
@@ -1069,6 +1347,14 @@ namespace platf {
}
}
/**
* @brief Refresh cached platform state from the operating system.
*
* @param file DMA-BUF file descriptors exported for the current framebuffer.
* @param sd EGL surface descriptor to import.
* @param frame_timestamp Output timestamp assigned to the captured frame.
* @return Capture status after refreshing framebuffer and cursor state.
*/
inline capture_e refresh(file_t *file, egl::surface_descriptor_t *sd, std::optional<std::chrono::steady_clock::time_point> &frame_timestamp) {
// Check for a change in HDR metadata
if (connector_id) {
@@ -1133,34 +1419,49 @@ namespace platf {
return capture_e::ok;
}
mem_type_e mem_type;
mem_type_e mem_type; ///< Mem type.
std::chrono::nanoseconds delay;
std::chrono::nanoseconds delay; ///< Delay before the timer task becomes eligible to run.
int img_width;
int img_height;
int img_offset_x;
int img_offset_y;
int img_width; ///< Img width.
int img_height; ///< Img height.
int img_offset_x; ///< Img offset x.
int img_offset_y; ///< Img offset y.
int plane_id;
int crtc_id;
int crtc_index;
int plane_id; ///< Plane ID.
int crtc_id; ///< Crtc ID.
int crtc_index; ///< Crtc index.
std::optional<uint32_t> connector_id;
std::optional<uint64_t> hdr_metadata_blob_id;
std::optional<uint32_t> connector_id; ///< Connector ID.
std::optional<uint64_t> hdr_metadata_blob_id; ///< HDR metadata blob ID.
int cursor_plane_id;
cursor_t captured_cursor {};
int cursor_plane_id; ///< Cursor plane ID.
cursor_t captured_cursor {}; ///< Captured cursor.
card_t card;
card_t card; ///< Card.
};
/**
* @brief KMS capture backend that copies frames into system memory.
*/
class display_ram_t: public display_t {
public:
/**
* @brief Initialize a KMS display backend that copies frames through RAM.
*
* @param mem_type Mem type.
*/
display_ram_t(mem_type_e mem_type):
display_t(mem_type) {
}
/**
* @brief Initialize KMS capture that copies frames into system memory.
*
* @param display_name Display name.
* @param config Configuration values to apply.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(const std::string &display_name, const ::video::config_t &config) {
if (!gbm::create_device) {
BOOST_LOG(warning) << "libgbm not initialized"sv;
@@ -1237,6 +1538,12 @@ namespace platf {
return capture_e::ok;
}
/**
* @brief Create AVCodec encode device.
*
* @param pix_fmt Sunshine pixel format to convert or allocate for.
* @return Constructed AVCodec encode device object.
*/
std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
#ifdef SUNSHINE_BUILD_VAAPI
if (mem_type == mem_type_e::vaapi) {
@@ -1253,6 +1560,11 @@ namespace platf {
return std::make_unique<avcodec_encode_device_t>();
}
/**
* @brief Blend the captured cursor plane into a KMS frame.
*
* @param img Image or frame object to read from or populate.
*/
void blend_cursor(img_t &img) {
// TODO: Cursor scaling is not supported in this codepath.
// We always draw the cursor at the source size.
@@ -1301,6 +1613,15 @@ namespace platf {
}
}
/**
* @brief Capture a display frame into the provided image object.
*
* @param pull_free_image_cb Callback that provides an available image buffer.
* @param img_out Captured KMS image returned to the streaming pipeline.
* @param timeout Maximum time to wait for the operation.
* @param cursor Cursor image or visibility state to composite.
* @return Capture status reported to the streaming pipeline.
*/
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
file_t fb_fd[4];
@@ -1344,6 +1665,11 @@ namespace platf {
return capture_e::ok;
}
/**
* @brief Allocate an image buffer compatible with this display backend.
*
* @return Allocated img object, or null when unavailable.
*/
std::shared_ptr<img_t> alloc_img() override {
auto img = std::make_shared<kms_img_t>();
img->width = width;
@@ -1355,21 +1681,41 @@ namespace platf {
return img;
}
/**
* @brief Populate a fallback image when real capture data is unavailable.
*
* @param img Image or frame object to read from or populate.
* @return Capture status reported to the streaming pipeline.
*/
int dummy_img(platf::img_t *img) override {
return 0;
}
gbm::gbm_t gbm;
egl::display_t display;
egl::ctx_t ctx;
gbm::gbm_t gbm; ///< GBM device used for buffer allocation.
egl::display_t display; ///< EGL display created from the GBM device.
egl::ctx_t ctx; ///< EGL context used to copy KMS frames into RAM.
};
/**
* @brief KMS capture backend that exports frames as GPU resources.
*/
class display_vram_t: public display_t {
public:
/**
* @brief Initialize a KMS display backend that exports frames as GPU resources.
*
* @param mem_type Mem type.
*/
display_vram_t(mem_type_e mem_type):
display_t(mem_type) {
}
/**
* @brief Create AVCodec encode device.
*
* @param pix_fmt Sunshine pixel format to convert or allocate for.
* @return Constructed AVCodec encode device object.
*/
std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
#ifdef SUNSHINE_BUILD_VAAPI
if (mem_type == mem_type_e::vaapi) {
@@ -1393,6 +1739,11 @@ namespace platf {
return nullptr;
}
/**
* @brief Allocate an image buffer compatible with this display backend.
*
* @return Allocated img object, or null when unavailable.
*/
std::shared_ptr<img_t> alloc_img() override {
auto img = std::make_shared<egl::img_descriptor_t>();
@@ -1408,6 +1759,12 @@ namespace platf {
return img;
}
/**
* @brief Populate a fallback image when real capture data is unavailable.
*
* @param img Image or frame object to read from or populate.
* @return Capture status reported to the streaming pipeline.
*/
int dummy_img(platf::img_t *img) override {
// Empty images are recognized as dummies by the zero sequence number
return 0;
@@ -1458,6 +1815,14 @@ namespace platf {
return capture_e::ok;
}
/**
* @brief Capture a display frame into the provided image object.
*
* @param pull_free_image_cb Pull free image cb.
* @param img_out Captured KMS image descriptor returned to the streaming pipeline.
* @param cursor Cursor image or visibility state to composite.
* @return Capture status reported to the streaming pipeline.
*/
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds /* timeout */, bool cursor) {
file_t fb_fd[4];
@@ -1500,6 +1865,13 @@ namespace platf {
return capture_e::ok;
}
/**
* @brief Initialize KMS capture that exports frames as GPU resources.
*
* @param display_name Display name.
* @param config Configuration values to apply.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(const std::string &display_name, const ::video::config_t &config) {
if (display_t::init(display_name, config)) {
return -1;
@@ -1522,11 +1894,19 @@ namespace platf {
return 0;
}
std::uint64_t sequence {};
std::uint64_t sequence {}; ///< Monotonic capture sequence assigned to KMS frames.
};
} // namespace kms
/**
* @brief Create a KMS display capture backend.
*
* @param hwdevice_type Hardware device type requested for capture or encode.
* @param display_name Display name.
* @param config Configuration values to apply.
* @return KMS display backend, or nullptr when initialization fails.
*/
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 || hwdevice_type == mem_type_e::vulkan) {
auto disp = std::make_shared<kms::display_vram_t>(hwdevice_type);
@@ -1556,6 +1936,7 @@ namespace platf {
* But, it's necessary for absolute mouse coordinates to work.
*
* This is an ugly hack :(
* @param cds Capture display state used by the KMS backend.
*/
void correlate_to_wayland(std::vector<kms::card_descriptor_t> &cds) {
auto monitors = wl::monitors();
@@ -1613,6 +1994,12 @@ namespace platf {
}
// A list of names of displays accepted as display_name
/**
* @brief Enumerate display names accepted by the KMS backend.
*
* @param hwdevice_type Hardware device type requested for capture or encode.
* @return KMS display names, or an empty list when KMS capture is unavailable.
*/
std::vector<std::string> kms_display_names(mem_type_e hwdevice_type) {
int count = 0;
+46 -9
View File
@@ -49,10 +49,18 @@ namespace kwin {
*/
class screencast_permission_helper_t {
public:
/**
* @brief Check whether permission system deactivated.
*
* @return True when KWin reports that the permission system is disabled.
*/
static bool is_permission_system_deactivated() {
return getenvstr("KWIN_WAYLAND_NO_PERMISSION_CHECKS") == "1";
}
/**
* @brief Configure the KWin screencast session.
*/
static void setup() {
if (initialized) {
return;
@@ -139,6 +147,11 @@ namespace kwin {
initialized = true;
}
/**
* @brief Check whether newly initialized.
*
* @return True when KWin was initialized during this check.
*/
static bool is_newly_initialized() {
return create_file;
}
@@ -238,13 +251,19 @@ namespace kwin {
};
// Output parameters
/**
* @brief KWin screencast output name and geometry.
*/
struct output_parameter_t {
std::string name = "";
int width = 0;
int height = 0;
int pos_x = 0;
int pos_y = 0;
std::string name = ""; ///< KWin output name.
int width = 0; ///< Output width in pixels.
int height = 0; ///< Output height in pixels.
int pos_x = 0; ///< Output X position in the compositor layout.
int pos_y = 0; ///< Output Y position in the compositor layout.
// order is needed to get a sorted output list and should be updated before sorting to have current values
/**
* @brief Order.
*/
size_t order = SIZE_MAX; // Use high number to keep monitors with uninitialized order value to the back
};
@@ -443,9 +462,9 @@ namespace kwin {
return 0;
}
uint32_t out_node_id = PW_ID_ANY;
uint64_t out_objectserial = SPA_ID_INVALID;
std::shared_ptr<output_parameter_t> out_params = nullptr;
uint32_t out_node_id = PW_ID_ANY; ///< Out node ID.
uint64_t out_objectserial = SPA_ID_INVALID; ///< Out objectserial.
std::shared_ptr<output_parameter_t> out_params = nullptr; ///< Out params.
private:
// Wayland objects
@@ -688,12 +707,20 @@ namespace kwin {
return -1;
}
std::unique_ptr<screencast_t> screencast;
std::unique_ptr<screencast_t> screencast; ///< Screencast.
};
} // namespace kwin
// Public API for misc.cpp
namespace platf {
/**
* @brief Create a KWin screencast display backend.
*
* @param hwdevice_type Hardware device type requested for capture or encode.
* @param display_name Display name.
* @param config Configuration values to apply.
* @return KWin/PipeWire display backend, or nullptr when initialization fails.
*/
std::shared_ptr<display_t> kwin_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
if (!pipewire::pipewire_display_t::init_pipewire_and_check_hwdevice_type(hwdevice_type)) {
BOOST_LOG(error) << "[kwingrab] Could not initialize pipewire-based display with the given hw device type."sv;
@@ -708,6 +735,11 @@ namespace platf {
return display;
}
/**
* @brief Enumerate KWin screencast display names.
*
* @return KWin display names, or an empty list when KWin capture is unavailable.
*/
std::vector<std::string> kwin_display_names() {
if (has_elevated_privileges(false)) {
// We're still in the probing phase of Sunshine startup. Dropping portal security early will break KMS.
@@ -724,6 +756,11 @@ namespace platf {
return screencast->get_output_names();
}
/**
* @brief Check whether KWin screencast capture is available.
*
* @return True when KWin capture support is available.
*/
bool kwin_available() {
// Init screencast without permission setup (to not cause unneeded logs / temporary desktop files) and check KWin availability
if (const auto screencast = std::make_unique<kwin::screencast_t>(); screencast->init(false) < 0 || !screencast->kwin_available()) {
+145 -10
View File
@@ -5,6 +5,10 @@
// Required for in6_pktinfo with glibc headers
#ifndef _GNU_SOURCE
/**
* @def _GNU_SOURCE
* @brief Macro for GNU SOURCE.
*/
#define _GNU_SOURCE 1
#endif
@@ -60,16 +64,32 @@
#ifdef __GNUC__
#define SUNSHINE_GNUC_EXTENSION __extension__
#else
/**
* @def SUNSHINE_GNUC_EXTENSION
* @brief Macro for SUNSHINE GNUC EXTENSION.
*/
#define SUNSHINE_GNUC_EXTENSION
#endif
#ifndef SOL_IP
/**
* @def SOL_IP
* @brief Macro for SOL IP.
*/
#define SOL_IP IPPROTO_IP
#endif
#ifndef SOL_IPV6
/**
* @def SOL_IPV6
* @brief Macro for SOL IPv6.
*/
#define SOL_IPV6 IPPROTO_IPV6
#endif
#ifndef SOL_UDP
/**
* @def SOL_UDP
* @brief Macro for SOL UDP.
*/
#define SOL_UDP IPPROTO_UDP
#endif
@@ -77,9 +97,12 @@ using namespace std::literals;
namespace fs = std::filesystem;
namespace bp = boost::process::v1;
window_system_e window_system;
window_system_e window_system; ///< Window system.
namespace dyn {
/**
* @brief Return the native handle owned by the wrapper.
*/
void *handle(const std::vector<const char *> &libs) {
void *handle;
@@ -103,6 +126,9 @@ namespace dyn {
return nullptr;
}
/**
* @brief Load persisted state from its backing store.
*/
int load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict) {
int err = 0;
for (auto &func : funcs) {
@@ -122,8 +148,16 @@ namespace dyn {
} // namespace dyn
namespace platf {
/**
* @brief Owning pointer for `getifaddrs` results.
*/
using ifaddr_t = util::safe_ptr<ifaddrs, freeifaddrs>;
/**
* @brief Read the local interface address list.
*
* @return Owning pointer to the interface address list, or nullptr on failure.
*/
ifaddr_t get_ifaddrs() {
ifaddrs *p {nullptr};
@@ -215,6 +249,9 @@ namespace platf {
return config_path;
}
/**
* @brief Convert a socket address to a printable IP address.
*/
std::string from_sockaddr(const sockaddr *const ip_addr) {
char data[INET6_ADDRSTRLEN] = {};
@@ -228,6 +265,9 @@ namespace platf {
return std::string {data};
}
/**
* @brief Convert a socket address to a port and printable IP address.
*/
std::pair<std::uint16_t, std::string> from_sockaddr_ex(const sockaddr *const ip_addr) {
char data[INET6_ADDRSTRLEN] = {};
@@ -244,6 +284,9 @@ namespace platf {
return {port, std::string {data}};
}
/**
* @brief Return the hardware MAC address associated with a network address.
*/
std::string get_mac_address(const std::string_view &address) {
auto ifaddrs = get_ifaddrs();
@@ -336,6 +379,9 @@ namespace platf {
}
}
/**
* @brief Apply the requested scheduling priority to the current thread.
*/
void adjust_thread_priority(thread_priority_e priority) {
#if defined(__FreeBSD__)
pid_t tid = syscall(SYS_thr_self);
@@ -405,18 +451,30 @@ namespace platf {
pthread_setname_np(pthread_self(), name.c_str());
}
/**
* @brief Enable or disable X11 mouse keys for the current session.
*/
void enable_mouse_keys() {
// Unimplemented
}
/**
* @brief Apply Linux platform state before streaming starts.
*/
void streaming_will_start() {
// Nothing to do
}
/**
* @brief Restore Linux platform state after streaming stops.
*/
void streaming_will_stop() {
// Nothing to do
}
/**
* @brief Request a Sunshine process restart on exit.
*/
void restart_on_exit() {
char executable[PATH_MAX];
ssize_t len = readlink("/proc/self/exe", executable, PATH_MAX - 1);
@@ -439,12 +497,21 @@ namespace platf {
}
}
/**
* @brief Restart the Sunshine process through the platform launcher.
*/
void restart() {
// Gracefully clean up and restart ourselves instead of exiting
atexit(restart_on_exit);
lifetime::exit_sunshine(0, true);
}
/**
* @brief Read an environment variable as an optional string.
*
* @param name Human-readable name to assign.
* @return Environment variable value, or an empty string when unset.
*/
std::string get_env(const std::string &name) {
if (const auto value = getenv(name.c_str()); value != nullptr) {
return value;
@@ -456,6 +523,14 @@ namespace platf {
return setenv(name.c_str(), value.c_str(), 1);
}
/**
* @brief Append a value to a separator-delimited environment variable.
*
* @param name Human-readable name to assign.
* @param value Entry to add when it is not already present.
* @param separator Character used to join or split the value.
* @return Result from updating the environment variable.
*/
int append_env(const std::string &name, const std::string &value, const std::string &separator) {
if (const std::string old_value = get_env(name); !old_value.contains(value)) {
return set_env(name, old_value.empty() ? value : old_value + separator + value);
@@ -481,6 +556,13 @@ namespace platf {
return waitpid(-((pid_t) native_handle), nullptr, WNOHANG) >= 0;
}
/**
* @brief Convert to sockaddr.
*
* @param address Network address being parsed or filtered.
* @param port TCP or UDP port number.
* @return Value converted to sockaddr.
*/
struct sockaddr_in to_sockaddr(boost::asio::ip::address_v4 address, uint16_t port) {
struct sockaddr_in saddr_v4 = {};
@@ -493,6 +575,13 @@ namespace platf {
return saddr_v4;
}
/**
* @brief Convert to sockaddr.
*
* @param address Network address being parsed or filtered.
* @param port TCP or UDP port number.
* @return Value converted to sockaddr.
*/
struct sockaddr_in6 to_sockaddr(boost::asio::ip::address_v6 address, uint16_t port) {
struct sockaddr_in6 saddr_v6 = {};
@@ -506,6 +595,9 @@ namespace platf {
return saddr_v6;
}
/**
* @brief Send multiple fixed-size UDP payload blocks using the platform backend.
*/
bool send_batch(batched_send_info_t &send_info) {
auto sockfd = (int) send_info.native_socket;
struct msghdr msg = {};
@@ -731,6 +823,9 @@ namespace platf {
}
}
/**
* @brief Send the serialized response over the active socket.
*/
bool send(send_info_t &send_info) {
auto sockfd = (int) send_info.native_socket;
struct msghdr msg = {};
@@ -854,8 +949,17 @@ namespace platf {
// are disconnected.
static std::atomic<int> qos_ref_count = 0;
/**
* @brief Linux QoS state used to tune socket priority while streaming.
*/
class qos_t: public deinit_t {
public:
/**
* @brief Apply Linux socket priority and DSCP QoS settings for scoped cleanup.
*
* @param sockfd Native socket descriptor whose options are updated.
* @param options Request options or socket options to apply.
*/
qos_t(int sockfd, std::vector<std::tuple<int, int, int>> options):
sockfd(sockfd),
options(options) {
@@ -880,11 +984,6 @@ namespace platf {
/**
* @brief Enables QoS on the given socket for traffic to the specified destination.
* @param native_socket The native socket handle.
* @param address The destination address for traffic sent on this socket.
* @param port The destination port for traffic sent on this socket.
* @param data_type The type of traffic sent on this socket.
* @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic.
*/
std::unique_ptr<deinit_t> enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type, bool dscp_tagging) {
int sockfd = (int) native_socket;
@@ -964,6 +1063,9 @@ namespace platf {
}
namespace source {
/**
* @brief Enumerates supported source options.
*/
enum source_e : std::size_t {
#ifdef SUNSHINE_BUILD_CUDA
NVFBC, ///< NvFBC
@@ -999,9 +1101,27 @@ namespace platf {
#endif
#ifdef SUNSHINE_BUILD_WAYLAND
/**
* @brief Enumerate displays available through the Wayland capture backend.
*
* @return Wayland display names, or an empty list when discovery fails.
*/
std::vector<std::string> wl_display_names();
/**
* @brief Create a Wayland display capture backend.
*
* @param hwdevice_type Hardware device type requested for capture or encode.
* @param display_name Display name.
* @param config Configuration values to apply.
* @return Display backend, or nullptr when Wayland capture initialization fails.
*/
std::shared_ptr<display_t> wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config);
/**
* @brief Check whether Wayland capture is available for the current session.
*
* @return True when the active window system is Wayland and at least one output is discoverable.
*/
bool verify_wl() {
return window_system == window_system_e::WAYLAND && !wl_display_names().empty();
}
@@ -1045,6 +1165,9 @@ namespace platf {
}
#endif
/**
* @brief List display names accepted by the selected capture backend.
*/
std::vector<std::string> display_names(mem_type_e hwdevice_type) {
#ifdef SUNSHINE_BUILD_CUDA
// display using NvFBC only supports mem_type_e::cuda
@@ -1081,8 +1204,9 @@ namespace platf {
}
/**
* @brief Returns if GPUs/drivers have changed since the last call to this function.
* @return `true` if a change has occurred or if it is unknown whether a change occurred.
* @brief Report whether encoder backends should be probed again before streaming.
*
* @return Always `true` because Linux GPU changes are not tracked by this backend.
*/
bool needs_encoder_reenumeration() {
// We don't track GPU state, so we will always reenumerate. Fortunately, it is fast on Linux.
@@ -1137,6 +1261,9 @@ namespace platf {
return nullptr;
}
/**
* @brief Initialize the Linux high-precision timer file descriptor.
*/
std::unique_ptr<deinit_t> init() {
// enable low latency mode for AMD
// https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/30039
@@ -1212,6 +1339,9 @@ namespace platf {
return std::make_unique<deinit_t>();
}
/**
* @brief Linux high-precision timer implementation backed by `timerfd`.
*/
class linux_high_precision_timer: public high_precision_timer {
public:
void sleep_for(const std::chrono::nanoseconds &duration) override {
@@ -1227,6 +1357,11 @@ namespace platf {
return std::make_unique<linux_high_precision_timer>();
}
/**
* @brief Find the DRM render node associated with the active display.
*
* @return Render-node path, or an empty string when no matching node is found.
*/
std::string find_render_node_with_display() {
#ifdef SUNSHINE_BUILD_DRM
auto *dir = opendir("/dev/dri");
@@ -1287,8 +1422,8 @@ namespace platf {
static constexpr cap_value_t FULL_CAPS[] = {CAP_SYS_ADMIN, CAP_SYS_NICE};
static constexpr cap_value_t ADMIN_CAPS[] = {CAP_SYS_ADMIN};
constexpr std::span<const cap_value_t> ELEVATED_PRIVILEGES_FULL {FULL_CAPS};
constexpr std::span<const cap_value_t> ELEVATED_PRIVILEGES_ADMIN {ADMIN_CAPS};
constexpr std::span<const cap_value_t> ELEVATED_PRIVILEGES_FULL {FULL_CAPS}; ///< Protocol or platform constant for elevated privileges full.
constexpr std::span<const cap_value_t> ELEVATED_PRIVILEGES_ADMIN {ADMIN_CAPS}; ///< Protocol or platform constant for elevated privileges admin.
#endif
bool has_elevated_privileges(bool all_caps) {
+14 -1
View File
@@ -11,21 +11,34 @@
// local includes
#include "src/utility.h"
#ifndef DOXYGEN
KITTY_USING_MOVE_T(file_t, int, -1, {
if (el >= 0) {
close(el);
}
});
#else
/**
* @brief Move-only wrapper for a POSIX file descriptor.
*/
class file_t;
#endif
/**
* @brief Enumerates supported window system options.
*/
enum class window_system_e {
NONE, ///< No window system
X11, ///< X11
WAYLAND, ///< Wayland
};
extern window_system_e window_system;
extern window_system_e window_system; ///< Window system.
namespace dyn {
/**
* @brief Generic GLX procedure pointer returned by the loader.
*/
typedef void (*apiproc)(void);
int load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict = true);
+182 -31
View File
@@ -25,7 +25,7 @@
#include "wayland.h"
#if !PW_CHECK_VERSION(1, 6, 0)
constexpr int SPA_VIDEO_TRANSFER_SMPTE2084 = 14;
constexpr int SPA_VIDEO_TRANSFER_SMPTE2084 = 14; ///< Protocol or platform constant for spa video transfer smpte2084.
#endif
#if PW_CHECK_VERSION(0, 3, 75)
@@ -36,7 +36,11 @@ const bool SUNSHINE_USE_PIPEWIRE_OBJECT_SERIAL = pw_check_library_version(0, 3,
constexpr bool SUNSHINE_USE_PIPEWIRE_OBJECT_SERIAL = true;
#else
// Pipewire object serials are unsupported without PW_KEY_TARGET_OBJECT (we define it here so compilation won't break but don't use it).
constexpr bool SUNSHINE_USE_PIPEWIRE_OBJECT_SERIAL = false;
constexpr bool SUNSHINE_USE_PIPEWIRE_OBJECT_SERIAL = false; ///< Whether PipeWire object serials should be used for matching.
/**
* @def PW_KEY_TARGET_OBJECT
* @brief Macro for PW KEY TARGET OBJECT.
*/
#define PW_KEY_TARGET_OBJECT "target.object"
#endif
@@ -51,9 +55,12 @@ namespace {
using namespace std::literals;
namespace pipewire {
/**
* @brief PipeWire SPA format mapped to Sunshine pixel format.
*/
struct format_map_t {
uint64_t fourcc;
int32_t pw_format;
uint64_t fourcc; ///< DRM fourcc pixel format.
int32_t pw_format; ///< Matching PipeWire SPA video format.
};
static constexpr std::array<format_map_t, 7> format_map = {{
@@ -66,47 +73,59 @@ namespace pipewire {
{DRM_FORMAT_XRGB8888, SPA_VIDEO_FORMAT_BGRx},
}};
/**
* @brief PipeWire capture state shared with callback threads.
*/
struct shared_state_t {
std::atomic<int> negotiated_width {0};
std::atomic<int> negotiated_height {0};
std::atomic<int> color_primaries {0};
std::atomic<int> transfer_function {0};
std::atomic<bool> stream_dead {false};
pw_stream_state previous_state;
pw_stream_state current_state;
std::string err_msg;
std::atomic<int> negotiated_width {0}; ///< Width negotiated with PipeWire for the stream.
std::atomic<int> negotiated_height {0}; ///< Height negotiated with PipeWire for the stream.
std::atomic<int> color_primaries {0}; ///< PipeWire color-primaries metadata for the stream.
std::atomic<int> transfer_function {0}; ///< PipeWire transfer-function metadata for the stream.
std::atomic<bool> stream_dead {false}; ///< Whether the PipeWire stream has been destroyed.
pw_stream_state previous_state; ///< Previous PipeWire stream state reported by callbacks.
pw_stream_state current_state; ///< Current PipeWire stream state reported by callbacks.
std::string err_msg; ///< Last PipeWire error message reported by the stream.
};
/**
* @brief PipeWire stream handle, format, and shared state pointer.
*/
struct stream_data_t {
struct pw_stream *stream;
struct spa_hook stream_listener;
struct spa_video_info format;
struct pw_buffer *current_buffer;
uint64_t drm_format;
std::shared_ptr<shared_state_t> shared;
std::mutex frame_mutex;
std::condition_variable frame_cv;
size_t local_stride = 0;
bool frame_ready = false;
struct pw_stream *stream; ///< PipeWire stream handle used for screencast frames.
struct spa_hook stream_listener; ///< Hook registering callbacks on the PipeWire stream.
struct spa_video_info format; ///< Negotiated PipeWire video format.
struct pw_buffer *current_buffer; ///< PipeWire buffer currently exposed to the capture thread.
uint64_t drm_format; ///< DRM format.
std::shared_ptr<shared_state_t> shared; ///< State shared between PipeWire callbacks and the capture backend.
std::mutex frame_mutex; ///< Synchronizes access to the current PipeWire frame.
std::condition_variable frame_cv; ///< Signals arrival or release of a PipeWire frame.
size_t local_stride = 0; ///< Local stride.
bool frame_ready = false; ///< Whether a PipeWire frame is ready to consume.
// Two distinct memory pools
std::vector<uint8_t> buffer_a;
std::vector<uint8_t> buffer_b;
std::vector<uint8_t> buffer_a; ///< First staging buffer used for CPU-copy PipeWire frames.
std::vector<uint8_t> buffer_b; ///< Second staging buffer used for CPU-copy PipeWire frames.
// Points to the buffer currently owned by fill_img
std::vector<uint8_t> *front_buffer;
std::vector<uint8_t> *front_buffer; ///< Staging buffer currently readable by `fill_img`.
// Points to the buffer currently being written by on_process
std::vector<uint8_t> *back_buffer;
std::vector<uint8_t> *back_buffer; ///< Staging buffer currently writable by PipeWire callbacks.
stream_data_t():
front_buffer(&buffer_a),
back_buffer(&buffer_b) {}
};
/**
* @brief DMA-BUF format and modifier list advertised by PipeWire.
*/
struct dmabuf_format_info_t {
int32_t format;
uint64_t *modifiers;
int n_modifiers;
int32_t format; ///< PipeWire SPA video format being advertised.
uint64_t *modifiers; ///< DRM format modifiers supported for the format.
int n_modifiers; ///< Number of entries in `modifiers`.
};
/**
* @brief PipeWire core, context, and stream setup used for screencast capture.
*/
class pipewire_t {
public:
pipewire_t():
@@ -154,22 +173,51 @@ namespace pipewire {
pw_thread_loop_destroy(loop);
}
/**
* @brief Return the mutex protecting PipeWire frame state.
*
* @return Mutex used by producer and capture threads.
*/
std::mutex &frame_mutex() {
return stream_data.frame_mutex;
}
/**
* @brief Return the condition variable signaled when frame state changes.
*
* @return Condition variable used to wait for frames or shutdown.
*/
std::condition_variable &frame_cv() {
return stream_data.frame_cv;
}
/**
* @brief Check whether frame ready.
*
* @return True when PipeWire has delivered a frame ready for capture.
*/
bool is_frame_ready() const {
return stream_data.frame_ready;
}
/**
* @brief Set frame ready.
*
* @param ready Whether the PipeWire frame is ready for capture.
*/
void set_frame_ready(bool ready) {
stream_data.frame_ready = ready;
}
/**
* @brief Initialize PipeWire core objects and optional stream negotiation.
*
* @param stream_fd Stream fd.
* @param stream_node Stream node.
* @param stream_object_serial Stream object serial.
* @param shared_state Shared state.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(const int stream_fd, const uint32_t stream_node, const uint64_t stream_object_serial, std::shared_ptr<shared_state_t> shared_state) {
fd = stream_fd;
node = stream_node;
@@ -201,6 +249,9 @@ namespace pipewire {
return 0;
}
/**
* @brief Release the active PipeWire stream and listener.
*/
void cleanup_stream() {
BOOST_LOG(debug) << "[pipewire] Cleaning up stream"sv;
if (loop && stream_data.stream) {
@@ -226,6 +277,18 @@ namespace pipewire {
}
}
/**
* @brief Create the PipeWire stream if it is not already active.
*
* @param mem_type Mem type.
* @param width Frame or display width in pixels.
* @param height Frame or display height in pixels.
* @param refresh_rate Refresh rate.
* @param dmabuf_infos Dmabuf infos.
* @param n_dmabuf_infos N dmabuf infos.
* @param display_is_nvidia Display is nvidia.
* @return 0 when the PipeWire stream is configured; nonzero on negotiation failure.
*/
int ensure_stream(const platf::mem_type_e mem_type, const uint32_t width, const uint32_t height, const uint32_t refresh_rate, const struct dmabuf_format_info_t *dmabuf_infos, const int n_dmabuf_infos, const bool display_is_nvidia) {
pw_thread_loop_lock(loop);
int result = 0;
@@ -293,6 +356,11 @@ namespace pipewire {
return result;
}
/**
* @brief Close img fds.
*
* @param img_descriptor Image descriptor whose duplicated DMA-BUF fds are closed.
*/
static void close_img_fds(egl::img_descriptor_t *img_descriptor) {
for (int &fd : img_descriptor->sd.fds) {
if (fd >= 0) {
@@ -302,6 +370,12 @@ namespace pipewire {
}
}
/**
* @brief Copy PipeWire metadata into the Sunshine image descriptor.
*
* @param img_descriptor Image descriptor receiving timestamps, sequence, and damage flags.
* @param buf Raw byte buffer used for serialization.
*/
static void fill_img_metadata(egl::img_descriptor_t *img_descriptor, struct spa_buffer *buf) {
img_descriptor->frame_timestamp = std::chrono::steady_clock::now();
@@ -323,6 +397,13 @@ namespace pipewire {
img_descriptor->pw_damage = (damage && damage->region.size.width > 0 && damage->region.size.height > 0) ? std::optional<bool>(true) : std::nullopt;
}
/**
* @brief Populate a Sunshine image descriptor from PipeWire DMA-BUF planes.
*
* @param img_descriptor Image descriptor receiving duplicated fds and plane layout.
* @param buf Raw byte buffer used for serialization.
* @param d PipeWire listener data passed to the callback.
*/
static void fill_img_dmabuf(egl::img_descriptor_t *img_descriptor, struct spa_buffer *buf, const stream_data_t &d) {
img_descriptor->sd.width = d.format.info.raw.size.width;
img_descriptor->sd.height = d.format.info.raw.size.height;
@@ -335,6 +416,11 @@ namespace pipewire {
}
}
/**
* @brief Copy the latest PipeWire frame into Sunshine's image buffer.
*
* @param img Image or frame object to read from or populate.
*/
void fill_img(platf::img_t *img) {
pw_thread_loop_lock(loop);
std::scoped_lock lock(stream_data.frame_mutex);
@@ -367,6 +453,11 @@ namespace pipewire {
pw_thread_loop_unlock(loop);
}
/**
* @brief Set negotiate maxframerate.
*
* @param negotiate_maxframerate Negotiate maxframerate.
*/
void set_negotiate_maxframerate(bool negotiate_maxframerate) {
negotiate_maxframerate_ = negotiate_maxframerate;
}
@@ -626,8 +717,17 @@ namespace pipewire {
};
};
/**
* @brief Display capture backend that consumes frames from a PipeWire stream.
*/
class pipewire_display_t: public platf::display_t {
public:
/**
* @brief Initialize pipewire and check hwdevice type.
*
* @param hwdevice_type Hardware device type requested for capture or encode.
* @return True when PipeWire is initialized and the hardware device type is supported.
*/
static bool init_pipewire_and_check_hwdevice_type(platf::mem_type_e hwdevice_type) {
// Initialize pipewire to load necessary modules
pw_init(nullptr, nullptr);
@@ -694,6 +794,14 @@ namespace pipewire {
}
}
/**
* @brief Initialize the PipeWire display backend for a selected stream.
*
* @param hwdevice_type Hardware device type requested for capture or encode.
* @param display_name Display name.
* @param config Configuration values to apply.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
// calculate frame interval we should capture at
framerate = config.framerate;
@@ -781,6 +889,15 @@ namespace pipewire {
return 0;
}
/**
* @brief Capture a display frame into the provided image object.
*
* @param pull_free_image_cb Callback that provides an available image buffer.
* @param img_out Captured PipeWire image returned to the streaming pipeline.
* @param timeout Maximum time to wait for the operation.
* @param show_cursor Show cursor.
* @return Capture status reported to the streaming pipeline.
*/
platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool show_cursor) {
// FIXME: show_cursor is ignored
auto deadline = std::chrono::steady_clock::now() + timeout;
@@ -812,6 +929,11 @@ namespace pipewire {
return platf::capture_e::timeout;
}
/**
* @brief Allocate an image buffer compatible with this display backend.
*
* @return Allocated img object, or null when unavailable.
*/
std::shared_ptr<platf::img_t> alloc_img() override {
// Note: this img_t type is also used for memory buffers
auto img = std::make_shared<egl::img_descriptor_t>();
@@ -828,6 +950,12 @@ namespace pipewire {
return img;
}
/**
* @brief Check stream dead.
*
* @param out_status Out status.
* @return True when the PipeWire stream can no longer produce frames.
*/
virtual bool check_stream_dead(platf::capture_e &out_status) {
return false; // Return to default stream dead handling.
}
@@ -899,6 +1027,12 @@ namespace pipewire {
return platf::capture_e::ok;
}
/**
* @brief Create AVCodec encode device.
*
* @param pix_fmt Sunshine pixel format to convert or allocate for.
* @return Constructed AVCodec encode device object.
*/
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override {
#ifdef SUNSHINE_BUILD_VAAPI
if (mem_type == platf::mem_type_e::vaapi) {
@@ -928,6 +1062,12 @@ namespace pipewire {
return std::make_unique<platf::avcodec_encode_device_t>();
}
/**
* @brief Populate a fallback image when real capture data is unavailable.
*
* @param img Image or frame object to read from or populate.
* @return Capture status reported to the streaming pipeline.
*/
int dummy_img(platf::img_t *img) override {
if (!img) {
return -1;
@@ -938,6 +1078,11 @@ namespace pipewire {
return 0;
}
/**
* @brief Report whether the active display mode is HDR.
*
* @return True when the active display mode is HDR.
*/
bool is_hdr() override {
int color_primaries = shared_state->color_primaries.load();
int transfer_function = shared_state->transfer_function.load();
@@ -949,6 +1094,12 @@ namespace pipewire {
return false;
}
/**
* @brief Read HDR metadata for the active display mode.
*
* @param metadata Output structure populated with HDR metadata.
* @return True when HDR metadata was written to the output structure.
*/
bool get_hdr_metadata(SS_HDR_METADATA &metadata) override {
int color_primaries = shared_state->color_primaries.load();
int transfer_function = shared_state->transfer_function.load();
@@ -1122,7 +1273,7 @@ namespace pipewire {
protected:
// Allow subclasses to access for pipewire requirements setup and stream dead checks
pipewire_t pipewire;
std::shared_ptr<shared_state_t> shared_state;
pipewire_t pipewire; ///< Pipewire.
std::shared_ptr<shared_state_t> shared_state; ///< Shared state.
};
} // namespace pipewire
+110 -14
View File
@@ -35,20 +35,41 @@ namespace portal {
// Forward declarations
class runtime_t;
/**
* @brief Persistent portal restore token used to reuse screencast permission.
*/
class restore_token_t {
public:
/**
* @brief Return the currently wrapped value or handle.
*
* @return Underlying native handle or object pointer.
*/
static std::string get() {
return *token_;
}
/**
* @brief Store the new value and mark it dirty for persistence.
*
* @param value Portal restore token received from xdg-desktop-portal.
*/
static void set(std::string_view value) {
*token_ = value;
}
/**
* @brief Return whether the persisted value is empty.
*
* @return True when no portal display id has been persisted.
*/
static bool empty() {
return token_->empty();
}
/**
* @brief Load persisted state from its backing store.
*/
static void load() {
std::ifstream file(get_file_path());
if (file.is_open()) {
@@ -59,6 +80,9 @@ namespace portal {
}
}
/**
* @brief Save current state to its backing store.
*/
static void save() {
if (token_->empty()) {
return;
@@ -80,21 +104,32 @@ namespace portal {
}
};
/**
* @brief DBus response loop and response variant for portal calls.
*/
struct dbus_response_t {
GMainLoop *loop;
GVariant *response;
guint subscription_id;
GMainLoop *loop; ///< GLib main loop waiting for a portal response signal.
GVariant *response; ///< DBus response payload returned by the portal.
guint subscription_id; ///< Subscription ID.
};
/**
* @brief PipeWire stream node and negotiated capture size.
*/
struct pipewire_streaminfo_t {
uint32_t pipewire_node = PW_ID_ANY;
uint64_t pipewire_object_serial = SPA_ID_INVALID;
int width = 0;
int height = 0;
int pos_x = 0;
int pos_y = 0;
std::string monitor_name;
uint32_t pipewire_node = PW_ID_ANY; ///< PipeWire node ID selected by the portal.
uint64_t pipewire_object_serial = SPA_ID_INVALID; ///< PipeWire object serial selected by the portal.
int width = 0; ///< Stream width in pixels.
int height = 0; ///< Stream height in pixels.
int pos_x = 0; ///< Output X position reported by the portal.
int pos_y = 0; ///< Output Y position reported by the portal.
std::string monitor_name; ///< Monitor name.
/**
* @brief Convert to display name.
*
* @return Value converted to display name.
*/
std::string to_display_name() {
if (!monitor_name.empty()) {
return monitor_name;
@@ -102,12 +137,21 @@ namespace portal {
return std::format("position-{}x{}-resolution-{}x{}", pos_x, pos_y, width, height);
}
/**
* @brief Check whether a portal stream matches a requested display name.
*
* @param display_name Display name.
* @return True when the portal display id matches the requested display name.
*/
bool match_display_name(const std::string_view &display_name) {
// Check the given non-empty display name matches the display name for this struct
return !display_name.empty() && display_name == to_display_name();
}
};
/**
* @brief DBus connection and portal request helpers for screencast setup.
*/
class dbus_t {
public:
dbus_t &operator=(dbus_t &&) = delete; // Do not allow to copying
@@ -157,6 +201,11 @@ namespace portal {
}
}
/**
* @brief Open DBus and prepare portal screencast request handling.
*
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init() {
restore_token_t::load();
@@ -176,6 +225,11 @@ namespace portal {
return 0;
}
/**
* @brief Connect to xdg-desktop-portal and restore or create a screencast session.
*
* @return 0 when a portal session is ready; nonzero when D-Bus or portal setup fails.
*/
int connect_to_portal() {
g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, FALSE);
g_autofree gchar *session_path = nullptr;
@@ -203,6 +257,14 @@ namespace portal {
// Try to create a combined RemoteDesktop + ScreenCast session
// Returns true on success, false if should fall back to ScreenCast-only
/**
* @brief Try to create a RemoteDesktop portal session.
*
* @param loop GLib main loop associated with the portal request.
* @param session_path Session path.
* @param session_token Session token.
* @return True when the portal request or state check succeeds.
*/
bool try_remote_desktop_session(GMainLoop *loop, gchar **session_path, const gchar *session_token) {
if (create_portal_session(loop, session_path, session_token, false) < 0) {
return false;
@@ -226,6 +288,13 @@ namespace portal {
}
// Create a ScreenCast-only session
/**
* @brief Create a screencast-only portal session without remote-desktop control.
*
* @param loop GLib main loop associated with the portal request.
* @param session_path Session path.
* @return 0 when the portal returns a session path; nonzero on request failure.
*/
int try_screencast_only_session(GMainLoop *loop, gchar **session_path) {
g_autofree gchar *new_session_token = nullptr;
create_session_path(conn, nullptr, &new_session_token);
@@ -240,6 +309,11 @@ namespace portal {
return 0;
}
/**
* @brief Check whether session closed.
*
* @return True when the portal session has been closed.
*/
bool is_session_closed() const {
if (conn && !session_handle.empty()) {
// Try to retrieve property org.freedesktop.portal.Session::version
@@ -267,8 +341,8 @@ namespace portal {
return false;
}
std::vector<pipewire_streaminfo_t> pipewire_streams;
int pipewire_fd;
std::vector<pipewire_streaminfo_t> pipewire_streams; ///< Pipewire streams.
int pipewire_fd; ///< Pipewire fd.
private:
GDBusConnection *conn;
@@ -628,6 +702,9 @@ namespace portal {
}
};
/**
* @brief Portal screencast backend that negotiates PipeWire streams over DBus.
*/
class portal_t: public pipewire::pipewire_display_t {
public:
int configure_stream(const std::string &display_name, int &out_pipewire_fd, uint32_t &out_pipewire_node, uint64_t &out_pipewire_object_serial [[maybe_unused]]) override {
@@ -680,6 +757,12 @@ namespace portal {
return 0;
}
/**
* @brief Check stream dead.
*
* @param out_status Out status.
* @return True when the PipeWire stream can no longer produce frames.
*/
bool check_stream_dead(platf::capture_e &out_status) override {
// If the pipewire stream stopped due to closed portal session stop the capture with an error
if (dbus.is_session_closed()) {
@@ -700,14 +783,22 @@ namespace portal {
}
// DBus portal connection
dbus_t dbus;
dbus_t dbus; ///< DBus connection used for portal screencast requests.
// Class variable to store runtime state of maxFramerate negotiation
static inline std::atomic<bool> negotiate_maxframerate {true};
static inline std::atomic<bool> negotiate_maxframerate {true}; ///< Whether portal negotiation should request the maximum frame rate.
};
} // namespace portal
namespace platf {
/**
* @brief Create a portal-based display capture backend.
*
* @param hwdevice_type Hardware device type requested for capture or encode.
* @param display_name Display name.
* @param config Configuration values to apply.
* @return Display backend backed by xdg-desktop-portal and PipeWire, or nullptr.
*/
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 (!pipewire::pipewire_display_t::init_pipewire_and_check_hwdevice_type(hwdevice_type)) {
@@ -728,6 +819,11 @@ namespace platf {
return portal;
}
/**
* @brief Enumerate capture targets available through xdg-desktop-portal.
*
* @return Portal display names, or an empty list when portal discovery fails.
*/
std::vector<std::string> portal_display_names() {
std::vector<std::string> display_names;
auto dbus = std::make_shared<portal::dbus_t>();
+160 -24
View File
@@ -85,14 +85,20 @@ namespace avahi {
ERR_MAX = -54 ///< TODO
};
constexpr auto IF_UNSPEC = -1;
constexpr auto IF_UNSPEC = -1; ///< Protocol or platform constant for if unspec.
/**
* @brief Enumerates supported proto options.
*/
enum proto {
PROTO_INET = 0, ///< IPv4
PROTO_INET6 = 1, ///< IPv6
PROTO_UNSPEC = -1 ///< Unspecified/all protocol(s)
};
/**
* @brief Enumerates supported server state options.
*/
enum ServerState {
SERVER_INVALID, ///< Invalid state (initial)
SERVER_REGISTERING, ///< Host RRs are being registered
@@ -101,6 +107,9 @@ namespace avahi {
SERVER_FAILURE ///< Some fatal failure happened, the server is unable to proceed
};
/**
* @brief Enumerates supported client state options.
*/
enum ClientState {
CLIENT_S_REGISTERING = SERVER_REGISTERING, ///< Server state: REGISTERING
CLIENT_S_RUNNING = SERVER_RUNNING, ///< Server state: RUNNING
@@ -109,6 +118,9 @@ namespace avahi {
CLIENT_CONNECTING = 101 ///< We're still connecting. This state is only entered when AVAHI_CLIENT_NO_FAIL has been passed to avahi_client_new() and the daemon is not yet available.
};
/**
* @brief Enumerates supported entry group state options.
*/
enum EntryGroupState {
ENTRY_GROUP_UNCOMMITED, ///< The group has not yet been committed, the user must still call avahi_entry_group_commit()
ENTRY_GROUP_REGISTERING, ///< The entries of the group are currently being registered
@@ -117,6 +129,9 @@ namespace avahi {
ENTRY_GROUP_FAILURE ///< Some kind of failure happened, the entries have been withdrawn
};
/**
* @brief Enumerates supported client flags options.
*/
enum ClientFlags {
CLIENT_IGNORE_USER_CONFIG = 1, ///< Don't read user configuration
CLIENT_NO_FAIL = 2 ///< Don't fail if the daemon is not available when avahi_client_new() is called, instead enter CLIENT_CONNECTING state and wait for the daemon to appear
@@ -137,7 +152,13 @@ namespace avahi {
PUBLISH_USE_MULTICAST = 256 ///< Register the record using multicast DNS
};
/**
* @brief Network interface index type used by Avahi.
*/
using IfIndex = int;
/**
* @brief Avahi protocol/address-family selector type.
*/
using Protocol = int;
struct EntryGroup;
@@ -145,18 +166,45 @@ namespace avahi {
struct SimplePoll;
struct Client;
/**
* @brief Avahi client state-change callback signature.
*/
typedef void (*ClientCallback)(Client *, ClientState, void *userdata);
/**
* @brief Avahi entry-group state-change callback signature.
*/
typedef void (*EntryGroupCallback)(EntryGroup *g, EntryGroupState state, void *userdata);
/**
* @brief Function pointer used to free Avahi-allocated memory.
*/
typedef void (*free_fn)(void *);
/**
* @brief Function pointer used to create an Avahi client.
*/
typedef Client *(*client_new_fn)(const Poll *poll_api, ClientFlags flags, ClientCallback callback, void *userdata, int *error);
/**
* @brief Function pointer used to destroy an Avahi client.
*/
typedef void (*client_free_fn)(Client *);
/**
* @brief Function pointer used to request an alternative Avahi service name.
*/
typedef char *(*alternative_service_name_fn)(char *);
/**
* @brief Function pointer used to get an Avahi client from an entry group.
*/
typedef Client *(*entry_group_get_client_fn)(EntryGroup *);
/**
* @brief Function pointer used to create an Avahi entry group.
*/
typedef EntryGroup *(*entry_group_new_fn)(Client *, EntryGroupCallback, void *userdata);
/**
* @brief Function pointer used to add a service to an Avahi entry group.
*/
typedef int (*entry_group_add_service_fn)(
EntryGroup *group,
IfIndex interface,
@@ -170,39 +218,77 @@ namespace avahi {
...
);
/**
* @brief Function pointer used to test whether an Avahi entry group is empty.
*/
typedef int (*entry_group_is_empty_fn)(EntryGroup *);
/**
* @brief Function pointer used to reset an Avahi entry group.
*/
typedef int (*entry_group_reset_fn)(EntryGroup *);
/**
* @brief Function pointer used to publish an Avahi entry group.
*/
typedef int (*entry_group_commit_fn)(EntryGroup *);
/**
* @brief Function pointer used to duplicate Avahi strings.
*/
typedef char *(*strdup_fn)(const char *);
/**
* @brief Function pointer used to format Avahi error codes.
*/
typedef char *(*strerror_fn)(int);
/**
* @brief Function pointer used to read the last Avahi client error.
*/
typedef int (*client_errno_fn)(Client *);
/**
* @brief Function pointer used to get the Avahi simple-poll API.
*/
typedef Poll *(*simple_poll_get_fn)(SimplePoll *);
/**
* @brief Function pointer used to run the Avahi simple-poll loop.
*/
typedef int (*simple_poll_loop_fn)(SimplePoll *);
/**
* @brief Function pointer used to stop the Avahi simple-poll loop.
*/
typedef void (*simple_poll_quit_fn)(SimplePoll *);
/**
* @brief Function pointer used to allocate an Avahi simple-poll instance.
*/
typedef SimplePoll *(*simple_poll_new_fn)();
/**
* @brief Function pointer used to free an Avahi simple-poll instance.
*/
typedef void (*simple_poll_free_fn)(SimplePoll *);
free_fn free;
client_new_fn client_new;
client_free_fn client_free;
alternative_service_name_fn alternative_service_name;
entry_group_get_client_fn entry_group_get_client;
entry_group_new_fn entry_group_new;
entry_group_add_service_fn entry_group_add_service;
entry_group_is_empty_fn entry_group_is_empty;
entry_group_reset_fn entry_group_reset;
entry_group_commit_fn entry_group_commit;
strdup_fn strdup;
strerror_fn strerror;
client_errno_fn client_errno;
simple_poll_get_fn simple_poll_get;
simple_poll_loop_fn simple_poll_loop;
simple_poll_quit_fn simple_poll_quit;
simple_poll_new_fn simple_poll_new;
simple_poll_free_fn simple_poll_free;
free_fn free; ///< Free.
client_new_fn client_new; ///< Client new.
client_free_fn client_free; ///< Client free.
alternative_service_name_fn alternative_service_name; ///< Alternative service name.
entry_group_get_client_fn entry_group_get_client; ///< Entry group get client.
entry_group_new_fn entry_group_new; ///< Entry group new.
entry_group_add_service_fn entry_group_add_service; ///< Entry group add service.
entry_group_is_empty_fn entry_group_is_empty; ///< Entry group is empty.
entry_group_reset_fn entry_group_reset; ///< Entry group reset.
entry_group_commit_fn entry_group_commit; ///< Entry group commit.
strdup_fn strdup; ///< Strdup.
strerror_fn strerror; ///< Strerror.
client_errno_fn client_errno; ///< Client errno.
simple_poll_get_fn simple_poll_get; ///< Simple poll get.
simple_poll_loop_fn simple_poll_loop; ///< Simple poll loop.
simple_poll_quit_fn simple_poll_quit; ///< Simple poll quit.
simple_poll_new_fn simple_poll_new; ///< Simple poll new.
simple_poll_free_fn simple_poll_free; ///< Simple poll free.
/**
* @brief Load common Avahi client and entry-group function pointers.
*
* @return 0 when common Avahi functions are available; nonzero otherwise.
*/
int init_common() {
static void *handle {nullptr};
static bool funcs_loaded = false;
@@ -238,6 +324,11 @@ namespace avahi {
return 0;
}
/**
* @brief Load Avahi threaded-poll and client function pointers.
*
* @return 0 when all client functions are available; nonzero otherwise.
*/
int init_client() {
if (init_common()) {
return -1;
@@ -280,25 +371,50 @@ namespace avahi {
namespace platf::publish {
/**
* @brief Release memory allocated by Avahi.
*
* @param p Avahi-allocated pointer to release.
*/
template<class T>
void free(T *p) {
avahi::free(p);
}
/**
* @brief Owning pointer for Avahi allocations released with `avahi_free`.
*/
template<class T>
using ptr_t = util::safe_ptr<T, free<T>>;
/**
* @brief Owning pointer for an Avahi client.
*/
using client_t = util::dyn_safe_ptr<avahi::Client, &avahi::client_free>;
/**
* @brief Owning pointer for an Avahi simple poll loop.
*/
using poll_t = util::dyn_safe_ptr<avahi::SimplePoll, &avahi::simple_poll_free>;
avahi::EntryGroup *group = nullptr;
avahi::EntryGroup *group = nullptr; ///< Active Avahi entry group that owns the published service.
poll_t poll;
client_t client;
poll_t poll; ///< Avahi poll loop used while the service is published.
client_t client; ///< Avahi client used to register the Sunshine service.
ptr_t<char> name;
ptr_t<char> name; ///< Current service name, updated when Avahi reports a collision.
/**
* @brief Create or update the Avahi service entry group.
*
* @param c Avahi client used to allocate and commit the service entry group.
*/
void create_services(avahi::Client *c);
/**
* @brief React to Avahi entry-group state changes.
*
* @param g Entry group that emitted the state change.
* @param state Avahi entry-group state reported by the callback.
*/
void entry_group_callback(avahi::EntryGroup *g, avahi::EntryGroupState state, void *) {
group = g;
@@ -322,6 +438,9 @@ namespace platf::publish {
}
}
/**
* @brief Publish Sunshine's mDNS service through Avahi.
*/
void create_services(avahi::Client *c) {
int ret;
@@ -380,6 +499,12 @@ namespace platf::publish {
fg.disable();
}
/**
* @brief React to Avahi client state changes and register services when ready.
*
* @param c Avahi client that emitted the state change.
* @param state Avahi client state reported by the callback.
*/
void client_callback(avahi::Client *c, avahi::ClientState state, void *) {
switch (state) {
case avahi::CLIENT_S_RUNNING:
@@ -399,14 +524,25 @@ namespace platf::publish {
}
}
/**
* @brief RAII helper that runs shutdown cleanup when destroyed.
*/
class deinit_t: public ::platf::deinit_t {
public:
std::thread poll_thread;
std::thread poll_thread; ///< Poll thread.
/**
* @brief Store the Avahi polling thread for shutdown on destruction.
*
* @param poll_thread Poll thread.
*/
deinit_t(std::thread poll_thread):
poll_thread {std::move(poll_thread)} {
}
/**
* @brief Destroy the Avahi publisher deinitializer.
*/
~deinit_t() override {
if (avahi::simple_poll_quit && poll) {
avahi::simple_poll_quit(poll.get());
+135 -28
View File
@@ -14,7 +14,14 @@ extern "C" {
#include <va/va.h>
#include <va/va_drm.h>
#if !VA_CHECK_VERSION(1, 9, 0)
// vaSyncBuffer stub allows Sunshine built against libva <2.9.0 to link against ffmpeg on libva 2.9.0 or later
/**
* @brief Stub vaSyncBuffer when building against libva before 2.9.0.
*
* @param dpy VA display.
* @param buf_id VA buffer ID.
* @param timeout_ns Sync timeout in nanoseconds.
* @return VA status code.
*/
VAStatus
vaSyncBuffer(
VADisplay dpy,
@@ -25,7 +32,15 @@ extern "C" {
}
#endif
#if !VA_CHECK_VERSION(1, 21, 0)
// vaMapBuffer2 stub allows Sunshine built against libva <2.21.0 to link against ffmpeg on libva 2.21.0 or later
/**
* @brief Stub vaMapBuffer2 when building against libva before 2.21.0.
*
* @param dpy VA display.
* @param buf_id VA buffer ID.
* @param pbuf Output mapped buffer pointer.
* @param flags Mapping flags.
* @return VA status code.
*/
VAStatus
vaMapBuffer2(
VADisplay dpy,
@@ -52,24 +67,39 @@ using namespace std::literals;
extern "C" struct AVBufferRef;
namespace va {
constexpr auto SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2 = 0x40000000;
constexpr auto EXPORT_SURFACE_WRITE_ONLY = 0x0002;
constexpr auto EXPORT_SURFACE_SEPARATE_LAYERS = 0x0004;
constexpr auto SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2 = 0x40000000; ///< Protocol or platform constant for surface attrib mem type drm prime 2.
constexpr auto EXPORT_SURFACE_WRITE_ONLY = 0x0002; ///< GameStream port offset for export surface write only.
constexpr auto EXPORT_SURFACE_SEPARATE_LAYERS = 0x0004; ///< GameStream port offset for export surface separate layers.
/**
* @brief Native VA display handle.
*/
using VADisplay = void *;
/**
* @brief Status code returned by VAAPI functions.
*/
using VAStatus = int;
/**
* @brief Generic numeric VAAPI object identifier.
*/
using VAGenericID = unsigned int;
/**
* @brief VAAPI surface identifier.
*/
using VASurfaceID = VAGenericID;
/**
* @brief DRM PRIME descriptor imported from a VAAPI surface.
*/
struct DRMPRIMESurfaceDescriptor {
// VA Pixel format fourcc of the whole surface (VA_FOURCC_*).
uint32_t fourcc;
uint32_t fourcc; ///< VA fourcc pixel format for the imported surface.
uint32_t width;
uint32_t height;
uint32_t width; ///< Surface width in pixels.
uint32_t height; ///< Surface height in pixels.
// Number of distinct DRM objects making up the surface.
uint32_t num_objects;
uint32_t num_objects; ///< Num objects.
struct {
// DRM PRIME file descriptor for this object.
@@ -80,10 +110,10 @@ namespace va {
uint32_t size;
// Format modifier applied to this object, not sure what that means
uint64_t drm_format_modifier;
} objects[4];
} objects[4]; ///< DRM PRIME backing objects referenced by the descriptor..
// Number of layers making up the surface.
uint32_t num_layers;
uint32_t num_layers; ///< Num layers.
struct {
// DRM format fourcc of this layer (DRM_FOURCC_*).
@@ -100,15 +130,36 @@ namespace va {
// Pitch of each plane.
uint32_t pitch[4];
} layers[4];
} layers[4]; ///< DRM PRIME layer descriptions for the frame..
};
/**
* @brief VA display handle released with `vaTerminate`.
*/
using display_t = util::safe_ptr_v2<void, VAStatus, vaTerminate>;
/**
* @brief Create an FFmpeg VA-API hardware device context from a Sunshine encode device.
*
* @param encode_device Encode device.
* @param hw_device_buf Output FFmpeg hardware device buffer.
* @return 0 when the buffer is initialized; negative FFmpeg error code on failure.
*/
int vaapi_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *encode_device, AVBufferRef **hw_device_buf);
/**
* @brief VAAPI encode device that imports captured frames into VA surfaces.
*/
class va_t: public platf::avcodec_encode_device_t {
public:
/**
* @brief Initialize VAAPI display, EGL, and conversion resources.
*
* @param in_width In width.
* @param in_height In height.
* @param render_device Render device.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(int in_width, int in_height, file_t &&render_device) {
file = std::move(render_device);
@@ -229,6 +280,12 @@ namespace va {
return VAProfileNone;
}
/**
* @brief Initialize codec options.
*
* @param ctx Native context object used by the operation or callback.
* @param options Request options or socket options to apply.
*/
void init_codec_options(AVCodecContext *ctx, AVDictionary **options) override {
auto va_profile = get_va_profile(ctx);
if (va_profile == VAProfileNone || !is_va_profile_supported(va_profile)) {
@@ -303,6 +360,13 @@ namespace va {
}
}
/**
* @brief Attach frame resources used by the next conversion or encode operation.
*
* @param frame Video or graphics frame being processed.
* @param hw_frames_ctx_buf Hardware frames context buffer.
* @return Status from updating frame.
*/
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx_buf) override {
this->hwframe.reset(frame);
this->frame = frame;
@@ -380,30 +444,42 @@ namespace va {
return 0;
}
/**
* @brief Apply the configured colorspace metadata to the active frame.
*/
void apply_colorspace() override {
sws.apply_colorspace(colorspace, false);
}
va::display_t::pointer va_display;
file_t file;
va::display_t::pointer va_display; ///< VA display used to allocate and destroy surfaces.
file_t file; ///< DRM render-node file descriptor used by VAAPI and EGL.
gbm::gbm_t gbm;
egl::display_t display;
egl::ctx_t ctx;
gbm::gbm_t gbm; ///< GBM device used for buffer allocation.
egl::display_t display; ///< EGL display created from the DRM render node.
egl::ctx_t ctx; ///< EGL context used for VA-API frame conversion.
// This must be destroyed before display_t to ensure the GPU
// driver is still loaded when vaDestroySurfaces() is called.
frame_t hwframe;
frame_t hwframe; ///< FFmpeg hardware frame backed by a VAAPI surface.
egl::sws_t sws;
egl::nv12_t nv12;
egl::sws_t sws; ///< EGL/OpenGL conversion pipeline for VA-API frames.
egl::nv12_t nv12; ///< EGL/OpenGL resources used for NV12 output frames.
int width;
int height;
int width; ///< Frame or display width in pixels.
int height; ///< Frame or display height in pixels.
};
/**
* @brief VAAPI encode path that copies converted frames through system memory.
*/
class va_ram_t: public va_t {
public:
/**
* @brief Convert a captured VAAPI frame into system-memory encoder input.
*
* @param img Image or frame object to read from or populate.
* @return Conversion status.
*/
int convert(platf::img_t &img) override {
sws.load_ram(img);
@@ -412,8 +488,17 @@ namespace va {
}
};
/**
* @brief VAAPI encode path that keeps converted frames in GPU memory.
*/
class va_vram_t: public va_t {
public:
/**
* @brief Convert a captured VAAPI frame into GPU encoder input.
*
* @param img Image or frame object to read from or populate.
* @return Conversion status.
*/
int convert(platf::img_t &img) override {
auto &descriptor = (egl::img_descriptor_t &) img;
@@ -440,6 +525,16 @@ namespace va {
return 0;
}
/**
* @brief Initialize VAAPI GPU-frame conversion for the selected display.
*
* @param in_width In width.
* @param in_height In height.
* @param render_device Render device.
* @param offset_x Offset x.
* @param offset_y Offset y.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(int in_width, int in_height, file_t &&render_device, int offset_x, int offset_y) {
if (va_t::init(in_width, in_height, std::move(render_device))) {
return -1;
@@ -453,11 +548,11 @@ namespace va {
return 0;
}
std::uint64_t sequence;
egl::rgb_t rgb;
std::uint64_t sequence; ///< Monotonic sequence used to recreate imported EGL resources.
egl::rgb_t rgb; ///< Imported RGB image used before VAAPI conversion.
int offset_x;
int offset_y;
int offset_x; ///< Horizontal offset in physical pixels.
int offset_y; ///< Vertical offset in physical pixels.
};
/**
@@ -470,9 +565,9 @@ namespace va {
union {
void *xdisplay;
int fd;
} drm;
} drm; ///< Native display or DRM fd passed to FFmpeg's VA-API context.
int drm_fd;
int drm_fd; ///< DRM fd.
} VAAPIDevicePriv;
/**
@@ -508,6 +603,9 @@ namespace va {
av_freep(&priv);
}
/**
* @brief Initialize FFmpeg's VA-API hardware device buffer.
*/
int vaapi_init_avcodec_hardware_input_buffer(platf::avcodec_encode_device_t *base, AVBufferRef **hw_device_buf) {
auto va = (va::va_t *) base;
auto fd = dup(va->file.el);
@@ -583,6 +681,9 @@ namespace va {
return false;
}
/**
* @brief Validate that the configured VAAPI device can be used.
*/
bool validate(int fd) {
va::display_t display {vaGetDisplayDRM(fd)};
if (!display) {
@@ -619,6 +720,9 @@ namespace va {
return true;
}
/**
* @brief Create AVCodec encode device.
*/
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, file_t &&card, int offset_x, int offset_y, bool vram) {
if (vram) {
auto egl = std::make_unique<va::va_vram_t>();
@@ -639,6 +743,9 @@ namespace va {
}
}
/**
* @brief Create AVCodec encode device.
*/
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, int offset_x, int offset_y, bool vram) {
auto render_device = platf::resolve_render_device();
+32
View File
@@ -19,11 +19,43 @@ namespace va {
* offset_x --> Horizontal offset of the image in the texture
* offset_y --> Vertical offset of the image in the texture
* file_t card --> The file descriptor of the render device used for encoding
*
* @param width Frame or display width in pixels.
* @param height Frame or display height in pixels.
* @param vram Whether the image should use GPU memory instead of system memory.
* @return Constructed AVCodec encode device object.
*/
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, bool vram);
/**
* @brief Create AVCodec encode device.
*
* @param width Frame or display width in pixels.
* @param height Frame or display height in pixels.
* @param offset_x Offset x.
* @param offset_y Offset y.
* @param vram Whether the image should use GPU memory instead of system memory.
* @return Constructed AVCodec encode device object.
*/
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, int offset_x, int offset_y, bool vram);
/**
* @brief Create AVCodec encode device.
*
* @param width Frame or display width in pixels.
* @param height Frame or display height in pixels.
* @param card Video device path or render node used for VAAPI.
* @param offset_x Offset x.
* @param offset_y Offset y.
* @param vram Whether the image should use GPU memory instead of system memory.
* @return Constructed AVCodec encode device object.
*/
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(int width, int height, file_t &&card, int offset_x, int offset_y, bool vram);
// Ensure the render device pointed to by fd is capable of encoding h264 with the hevc_mode configured
/**
* @brief Validate that the configured VAAPI device can be used.
*
* @param fd Native file descriptor to wrap or inspect.
* @return True when the VAAPI device is usable for capture or encode.
*/
bool validate(int fd);
} // namespace va
+63 -13
View File
@@ -106,23 +106,30 @@ namespace vk {
return -1;
}
/**
* @brief Vulkan shader constants used by the conversion pass.
*/
struct PushConstants {
std::array<float, 4> color_vec_y;
std::array<float, 4> color_vec_u;
std::array<float, 4> color_vec_v;
std::array<float, 2> range_y;
std::array<float, 2> range_uv;
std::array<int32_t, 2> src_offset;
std::array<int32_t, 2> src_size;
std::array<int32_t, 2> dst_offset;
std::array<int32_t, 2> dst_size;
std::array<int32_t, 2> dst_full_size;
std::array<int32_t, 2> cursor_pos;
std::array<int32_t, 2> cursor_size;
int32_t y_invert;
std::array<float, 4> color_vec_y; ///< Color vec y.
std::array<float, 4> color_vec_u; ///< Color vec u.
std::array<float, 4> color_vec_v; ///< Color vec v.
std::array<float, 2> range_y; ///< Range y.
std::array<float, 2> range_uv; ///< Range uv.
std::array<int32_t, 2> src_offset; ///< Src offset.
std::array<int32_t, 2> src_size; ///< Src size.
std::array<int32_t, 2> dst_offset; ///< Dst offset.
std::array<int32_t, 2> dst_size; ///< Dst size.
std::array<int32_t, 2> dst_full_size; ///< Dst full size.
std::array<int32_t, 2> cursor_pos; ///< Cursor pos.
std::array<int32_t, 2> cursor_size; ///< Cursor size.
int32_t y_invert; ///< Y invert.
};
// Helper to check VkResult
/**
* @def VK_CHECK(expr)
* @brief Macro for VK CHECK.
*/
#define VK_CHECK(expr) \
do { \
VkResult _r = (expr); \
@@ -131,6 +138,10 @@ namespace vk {
return -1; \
} \
} while (0)
/**
* @def VK_CHECK_BOOL(expr)
* @brief Macro for VK CHECK BOOL.
*/
#define VK_CHECK_BOOL(expr) \
do { \
VkResult _r = (expr); \
@@ -140,12 +151,24 @@ namespace vk {
} \
} while (0)
/**
* @brief Vulkan encode device that keeps converted frames in GPU memory.
*/
class vk_vram_t: public platf::avcodec_encode_device_t {
public:
~vk_vram_t() override {
cleanup_pipeline();
}
/**
* @brief Initialize Vulkan encode device and conversion resources.
*
* @param in_width In width.
* @param in_height In height.
* @param in_offset_x In offset x.
* @param in_offset_y In offset y.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(int in_width, int in_height, int in_offset_x = 0, int in_offset_y = 0) {
width = in_width;
height = in_height;
@@ -155,6 +178,12 @@ namespace vk {
return 0;
}
/**
* @brief Initialize codec options.
*
* @param ctx Native context object used by the operation or callback.
* @param options Request options or socket options to apply.
*/
void init_codec_options(AVCodecContext *ctx, AVDictionary **options) override {
// When VBR mode is selected (rc_mode=4), don't pin rc_min_rate to the target bitrate.
// Having rc_min_rate == rc_max_rate == bit_rate in VBR mode prevents the encoder from
@@ -165,6 +194,13 @@ namespace vk {
}
}
/**
* @brief Attach frame resources used by the next conversion or encode operation.
*
* @param new_frame Frame to attach.
* @param hw_frames_ctx_buf Hardware frames context buffer.
* @return Status from updating frame.
*/
int set_frame(AVFrame *new_frame, AVBufferRef *hw_frames_ctx_buf) override {
this->hwframe.reset(new_frame);
this->frame = new_frame;
@@ -212,6 +248,9 @@ namespace vk {
return 0;
}
/**
* @brief Apply the configured colorspace metadata to the active frame.
*/
void apply_colorspace() override {
auto *colors = video::color_vectors_from_colorspace(colorspace, true);
if (colors) {
@@ -223,6 +262,11 @@ namespace vk {
}
}
/**
* @brief Configure FFmpeg Vulkan hardware frames for video encode input.
*
* @param frames FFmpeg hardware frames context to initialize.
*/
void init_hwframes(AVHWFramesContext *frames) override {
frames->initial_pool_size = 4;
auto *vk_frames = (AVVulkanFramesContext *) frames->hwctx;
@@ -233,6 +277,12 @@ namespace vk {
VK_IMAGE_USAGE_VIDEO_ENCODE_SRC_BIT_KHR);
}
/**
* @brief Convert a captured frame into a Vulkan hardware frame.
*
* @param img Image or frame object to read from or populate.
* @return Conversion status.
*/
int convert(platf::img_t &img) override {
auto &descriptor = (egl::img_descriptor_t &) img;
+12
View File
@@ -20,16 +20,28 @@ namespace vk {
/**
* @brief Create a Vulkan encode device for RAM capture.
*
* @param width Frame or display width in pixels.
* @param height Frame or display height in pixels.
* @return Constructed AVCodec encode device ram object.
*/
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device_ram(int width, int height);
/**
* @brief Create a Vulkan encode device for VRAM capture.
*
* @param width Frame or display width in pixels.
* @param height Frame or display height in pixels.
* @param offset_x Offset x.
* @param offset_y Offset y.
* @return Constructed AVCodec encode device VRAM object.
*/
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device_vram(int width, int height, int offset_x, int offset_y);
/**
* @brief Check if FFmpeg Vulkan Video encoding is available.
*
* @return True when FFmpeg Vulkan Video encoding is available.
*/
bool validate();
+11 -1
View File
@@ -23,7 +23,7 @@
#include "src/utility.h"
#include "wayland.h"
extern const wl_interface wl_output_interface;
extern const wl_interface wl_output_interface; ///< Wayland output interface.
using namespace std::literals;
@@ -40,6 +40,10 @@ namespace wl {
return ((*reinterpret_cast<T *>(data)).*m)(params...);
}
/**
* @def CLASS_CALL(c, m)
* @brief Macro for CLASS CALL.
*/
#define CLASS_CALL(c, m) classCall<c, decltype(&c::m), &c::m>
// Define buffer params listener
@@ -546,6 +550,9 @@ namespace wl {
std::fill_n(sd.fds, 4, -1);
};
/**
* @brief Refresh the monitor list reported by the display server.
*/
std::vector<std::unique_ptr<monitor_t>> monitors(const char *display_name) {
display_t display;
@@ -578,6 +585,9 @@ namespace wl {
return display.init() == 0;
}
/**
* @brief Initialize Wayland registry interfaces required for capture.
*/
int init() {
static bool validated = validate();
+242 -23
View File
@@ -26,19 +26,34 @@
#ifdef SUNSHINE_BUILD_WAYLAND
namespace wl {
/**
* @brief Owning pointer for a Wayland display connection.
*/
using display_internal_t = util::safe_ptr<wl_display, wl_display_disconnect>;
/**
* @brief Captured Wayland frame metadata and DMA-BUF surface state.
*/
class frame_t {
public:
frame_t();
/**
* @brief Release file descriptors and native buffers associated with this frame.
*/
void destroy();
egl::surface_descriptor_t sd;
std::optional<std::chrono::steady_clock::time_point> frame_timestamp;
egl::surface_descriptor_t sd; ///< DMA-BUF surface descriptor received from the compositor.
std::optional<std::chrono::steady_clock::time_point> frame_timestamp; ///< Capture timestamp associated with the frame.
};
/**
* @brief Listener state for Wayland screencopy frames backed by DMA-BUFs.
*/
class dmabuf_t {
public:
/**
* @brief Capture state for the active screencopy request.
*/
enum status_e {
WAITING, ///< Waiting for a frame
READY, ///< Frame is ready
@@ -53,25 +68,102 @@ namespace wl {
dmabuf_t &operator=(const dmabuf_t &) = delete;
dmabuf_t &operator=(dmabuf_t &&) = delete;
/**
* @brief Start a screencopy request and attach the DMA-BUF listener callbacks.
*
* @param screencopy_manager Compositor screencopy manager used to request frames.
* @param dmabuf_interface Compositor DMA-BUF interface used to allocate buffers.
* @param supported_modifiers DMA-BUF format modifiers supported by the compositor.
* @param output Wayland output to capture.
* @param blend_cursor Whether the compositor should include the cursor in the frame.
*/
void listen(zwlr_screencopy_manager_v1 *screencopy_manager, zwp_linux_dmabuf_v1 *dmabuf_interface, const std::map<std::uint32_t, std::vector<std::uint64_t>> *supported_modifiers, wl_output *output, bool blend_cursor = false);
/**
* @brief Store the Wayland buffer created for a DMA-BUF parameter request.
*
* @param data Pointer to the `dmabuf_t` instance associated with the callback.
* @param params DMA-BUF parameter object that completed buffer creation.
* @param wl_buffer Wayland buffer created from the DMA-BUF planes.
*/
static void buffer_params_created(void *data, struct zwp_linux_buffer_params_v1 *params, struct wl_buffer *wl_buffer);
/**
* @brief Mark DMA-BUF buffer creation as failed.
*
* @param data Pointer to the `dmabuf_t` instance associated with the callback.
* @param params DMA-BUF parameter object that failed buffer creation.
*/
static void buffer_params_failed(void *data, struct zwp_linux_buffer_params_v1 *params);
/**
* @brief Record DMA-BUF buffer parameters from the compositor.
*
* @param frame Screencopy frame that advertised shared-memory fallback data.
* @param format DRM pixel format for the shared-memory buffer.
* @param width Frame width in pixels.
* @param height Frame height in pixels.
* @param stride Number of bytes per row in the buffer.
*/
void buffer(zwlr_screencopy_frame_v1 *frame, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t stride);
/**
* @brief Record Linux DMA-BUF parameters advertised for the frame.
*
* @param frame Screencopy frame that advertised DMA-BUF data.
* @param format DRM pixel format for the DMA-BUF.
* @param width Frame width in pixels.
* @param height Frame height in pixels.
*/
void linux_dmabuf(zwlr_screencopy_frame_v1 *frame, std::uint32_t format, std::uint32_t width, std::uint32_t height);
/**
* @brief Allocate or import the buffer once the compositor finished describing it.
*
* @param frame Screencopy frame whose buffer description is complete.
*/
void buffer_done(zwlr_screencopy_frame_v1 *frame);
/**
* @brief Record frame flags reported by the compositor.
*
* @param frame Screencopy frame that reported flags.
* @param flags Wayland screencopy flags such as vertical inversion.
*/
void flags(zwlr_screencopy_frame_v1 *frame, std::uint32_t flags);
/**
* @brief Record damaged frame bounds reported by the compositor.
*
* @param frame Screencopy frame that reported damage.
* @param x Left edge of the damaged rectangle.
* @param y Top edge of the damaged rectangle.
* @param width Damaged rectangle width in pixels.
* @param height Damaged rectangle height in pixels.
*/
void damage(zwlr_screencopy_frame_v1 *frame, std::uint32_t x, std::uint32_t y, std::uint32_t width, std::uint32_t height);
/**
* @brief Mark the current screencopy frame as ready and store its timestamp.
*
* @param frame Screencopy frame that completed.
* @param tv_sec_hi High 32 bits of the compositor-provided seconds value.
* @param tv_sec_lo Low 32 bits of the compositor-provided seconds value.
* @param tv_nsec Nanosecond component of the compositor-provided timestamp.
*/
void ready(zwlr_screencopy_frame_v1 *frame, std::uint32_t tv_sec_hi, std::uint32_t tv_sec_lo, std::uint32_t tv_nsec);
/**
* @brief Mark the frame capture as failed.
*
* @param frame Screencopy frame that failed.
*/
void failed(zwlr_screencopy_frame_v1 *frame);
/**
* @brief Select the inactive frame slot for the next screencopy request.
*
* @return Inactive frame buffer that can receive the next capture.
*/
frame_t *get_next_frame() {
return current_frame == &frames[0] ? &frames[1] : &frames[0];
}
status_e status;
std::array<frame_t, 2> frames;
frame_t *current_frame;
zwlr_screencopy_frame_v1_listener listener;
status_e status; ///< Current state of the active screencopy request.
std::array<frame_t, 2> frames; ///< Double-buffered frame descriptors.
frame_t *current_frame; ///< Frame descriptor currently being filled by the compositor.
zwlr_screencopy_frame_v1_listener listener; ///< Callback table registered on screencopy frames.
private:
bool init_gbm();
@@ -102,8 +194,16 @@ namespace wl {
bool y_invert {false};
};
/**
* @brief Wayland output metadata used to match a configured display name.
*/
class monitor_t {
public:
/**
* @brief Track a Wayland output and its advertised modes.
*
* @param output Wayland output object being described.
*/
explicit monitor_t(wl_output *output);
monitor_t(monitor_t &&) = delete;
@@ -111,30 +211,96 @@ namespace wl {
monitor_t &operator=(const monitor_t &) = delete;
monitor_t &operator=(monitor_t &&) = delete;
/**
* @brief Attach xdg-output and wl-output listeners for this monitor.
*
* @param output_manager xdg-output manager used to query logical monitor metadata.
*/
void listen(zxdg_output_manager_v1 *output_manager);
/**
* @brief Store the xdg-output logical monitor name.
*
* @param name Human-readable name to assign.
*/
void xdg_name(zxdg_output_v1 *, const char *name);
/**
* @brief Store the xdg-output human-readable monitor description.
*
* @param description Human-readable description used in log output.
*/
void xdg_description(zxdg_output_v1 *, const char *description);
/**
* @brief Store the logical monitor position reported by xdg-output.
*
* @param x Horizontal coordinate reported by Wayland.
* @param y Vertical coordinate reported by Wayland.
*/
void xdg_position(zxdg_output_v1 *, std::int32_t x, std::int32_t y);
/**
* @brief Store the logical monitor size reported by xdg-output.
*
* @param width Frame or display width in pixels.
* @param height Frame or display height in pixels.
*/
void xdg_size(zxdg_output_v1 *, std::int32_t width, std::int32_t height);
/**
* @brief Acknowledge completion of an xdg-output metadata update.
*/
void xdg_done(zxdg_output_v1 *) {}
/**
* @brief Receive physical wl-output geometry metadata.
*
* @param wl_output Wayland output.
* @param x Horizontal coordinate reported by Wayland.
* @param y Vertical coordinate reported by Wayland.
* @param physical_width Physical width in millimeters.
* @param physical_height Physical height in millimeters.
* @param subpixel Wayland subpixel layout enum.
* @param make Monitor manufacturer string reported by Wayland.
* @param model Monitor model string reported by Wayland.
* @param transform Wayland output transform value.
*/
void wl_geometry(wl_output *wl_output, std::int32_t x, std::int32_t y, std::int32_t physical_width, std::int32_t physical_height, std::int32_t subpixel, const char *make, const char *model, std::int32_t transform) {}
/**
* @brief Store the active pixel mode reported by wl-output.
*
* @param wl_output Wayland output.
* @param flags Bit flags that modify the requested operation.
* @param width Frame or display width in pixels.
* @param height Frame or display height in pixels.
* @param refresh Output refresh rate in mHz.
*/
void wl_mode(wl_output *wl_output, std::uint32_t flags, std::int32_t width, std::int32_t height, std::int32_t refresh);
/**
* @brief Acknowledge completion of a wl-output metadata update.
*
* @param wl_output Wayland output.
*/
void wl_done(wl_output *wl_output) {}
/**
* @brief Receive the wl-output scale factor.
*
* @param wl_output Wayland output.
* @param factor Wayland output scale factor.
*/
void wl_scale(wl_output *wl_output, std::int32_t factor) {}
wl_output *output;
std::string name;
std::string description;
platf::touch_port_t viewport;
wl_output_listener wl_listener;
zxdg_output_v1_listener xdg_listener;
wl_output *output; ///< Wayland output associated with this monitor.
std::string name; ///< xdg-output name used for display selection.
std::string description; ///< xdg-output description used for logs and UI.
platf::touch_port_t viewport; ///< Logical monitor bounds used to scale absolute input.
wl_output_listener wl_listener; ///< Callback table for wl-output events.
zxdg_output_v1_listener xdg_listener; ///< Callback table for xdg-output events.
};
/**
* @brief Wayland registry state for screencopy, DMA-BUF, and output globals.
*/
class interface_t {
struct bind_t {
std::uint32_t id;
@@ -142,6 +308,9 @@ namespace wl {
};
public:
/**
* @brief Wayland globals required by the WLR capture backend.
*/
enum interface_e {
XDG_OUTPUT, ///< xdg-output
WLR_EXPORT_DMABUF, ///< screencopy manager
@@ -156,20 +325,45 @@ namespace wl {
interface_t &operator=(const interface_t &) = delete;
interface_t &operator=(interface_t &&) = delete;
/**
* @brief Discover compositor globals from the Wayland registry.
*
* @param registry Wayland registry announcing globals.
*/
void listen(wl_registry *registry);
/**
* @brief Test whether a required Wayland global was advertised.
*
* @param bit Interface bit to test.
* @return True when the requested Wayland interface bit is set.
*/
bool operator[](interface_e bit) const {
return interface[bit];
}
/**
* @brief Record a DMA-BUF format advertised by the compositor.
*
* @param zwp_linux_dmabuf DMA-BUF interface that emitted the format event.
* @param format DRM format supported by the compositor.
*/
void dmabuf_format(zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format);
/**
* @brief Record a DMA-BUF format modifier advertised by the compositor.
*
* @param zwp_linux_dmabuf DMA-BUF interface that emitted the modifier event.
* @param format DRM format associated with the modifier.
* @param modifier_hi High 32 bits of the DRM format modifier.
* @param modifier_lo Low 32 bits of the DRM format modifier.
*/
void dmabuf_modifier(zwp_linux_dmabuf_v1 *zwp_linux_dmabuf, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo);
std::vector<std::unique_ptr<monitor_t>> monitors;
std::map<std::uint32_t, std::vector<std::uint64_t>> supported_modifiers;
zwlr_screencopy_manager_v1 *screencopy_manager {nullptr};
zwp_linux_dmabuf_v1 *dmabuf_interface {nullptr};
zxdg_output_manager_v1 *output_manager {nullptr};
std::vector<std::unique_ptr<monitor_t>> monitors; ///< Outputs discovered from the Wayland registry.
std::map<std::uint32_t, std::vector<std::uint64_t>> supported_modifiers; ///< DRM format modifiers grouped by format.
zwlr_screencopy_manager_v1 *screencopy_manager {nullptr}; ///< WLR screencopy global used to request frames.
zwp_linux_dmabuf_v1 *dmabuf_interface {nullptr}; ///< Linux DMA-BUF global used to allocate frame buffers.
zxdg_output_manager_v1 *output_manager {nullptr}; ///< xdg-output global used to query monitor names and sizes.
private:
void add_interface(wl_registry *registry, std::uint32_t id, const char *interface, std::uint32_t version);
@@ -180,26 +374,40 @@ namespace wl {
zwp_linux_dmabuf_v1_listener dmabuf_listener;
};
/**
* @brief Wayland display connection used to dispatch capture events.
*/
class display_t {
public:
/**
* @brief Initialize display.
* If display_name == nullptr -> display_name = std::getenv("WAYLAND_DISPLAY")
* @param display_name The name of the display.
* @return 0 on success, -1 on failure.
* @brief Connect to the requested Wayland display.
*
* @param display_name Wayland display name, or nullptr to use WAYLAND_DISPLAY.
* @return 0 when the connection is opened and initialized; -1 on failure.
*/
int init(const char *display_name = nullptr);
// Roundtrip with Wayland connection
/**
* @brief Flush pending Wayland requests and wait for replies.
*/
void roundtrip();
// Wait up to the timeout to read and dispatch new events
bool dispatch(std::chrono::milliseconds timeout);
// Get the registry associated with the display
// No need to manually free the registry
/**
* @brief Return the Wayland registry for global discovery.
*
* @return Wayland registry used to discover compositor globals.
*/
wl_registry *registry();
/**
* @brief Return the native Wayland display pointer.
*
* @return Native display connection owned by this wrapper.
*/
inline display_internal_t::pointer get() {
return display_internal.get();
}
@@ -208,7 +416,18 @@ namespace wl {
display_internal_t display_internal;
};
/**
* @brief Refresh the monitor list reported by the display server.
*
* @param display_name Display name.
* @return Monitors reported by the Wayland compositor, optionally filtered by name.
*/
std::vector<std::unique_ptr<monitor_t>> monitors(const char *display_name = nullptr);
/**
* @brief Initialize Wayland registry interfaces required for capture.
*
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init();
} // namespace wl
#else
+107 -9
View File
@@ -19,15 +19,32 @@ namespace wl {
static int env_width;
static int env_height;
/**
* @brief Captured frame buffer shared between capture and encode stages.
*/
struct img_t: public platf::img_t {
/**
* @brief Destroy the Wayland capture image.
*/
~img_t() override {
delete[] data;
data = nullptr;
}
};
/**
* @brief Wayland screencopy capture backend shared by RAM and VRAM paths.
*/
class wlr_t: public platf::display_t {
public:
/**
* @brief Initialize Wayland screencopy capture for the selected output.
*
* @param hwdevice_type Hardware device type requested for capture or encode.
* @param display_name Display name.
* @param config Configuration values to apply.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
// calculate frame interval we should capture at
if (config.framerateX100 > 0) {
@@ -124,10 +141,25 @@ namespace wl {
return 0;
}
/**
* @brief Populate a fallback image when real capture data is unavailable.
*
* @param img Image or frame object to read from or populate.
* @return Capture status reported to the streaming pipeline.
*/
int dummy_img(platf::img_t *img) override {
return 0;
}
/**
* @brief Capture a display frame into the provided image object.
*
* @param pull_free_image_cb Callback that provides an available image buffer.
* @param img_out Captured wlroots image returned to the streaming pipeline.
* @param timeout Maximum time to wait for the operation.
* @param cursor Cursor image or visibility state to composite.
* @return Capture status reported to the streaming pipeline.
*/
inline platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
auto to = std::chrono::steady_clock::now() + timeout;
@@ -153,17 +185,20 @@ namespace wl {
return platf::capture_e::ok;
}
platf::mem_type_e mem_type;
platf::mem_type_e mem_type; ///< Mem type.
std::chrono::nanoseconds delay;
std::chrono::nanoseconds delay; ///< Delay before the timer task becomes eligible to run.
wl::display_t display;
interface_t interface;
dmabuf_t dmabuf;
wl::display_t display; ///< Wayland display connection used for capture.
interface_t interface; ///< Wayland registry interfaces required by screencopy.
dmabuf_t dmabuf; ///< DMA-BUF feedback and format state advertised by the compositor.
wl_output *output;
wl_output *output; ///< Wayland output selected for capture.
};
/**
* @brief Wayland screencopy backend that copies frames into system memory.
*/
class wlr_ram_t: public wlr_t {
public:
platf::capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
@@ -211,6 +246,15 @@ namespace wl {
return platf::capture_e::ok;
}
/**
* @brief Capture a display frame into the provided image object.
*
* @param pull_free_image_cb Callback that provides an available image buffer.
* @param img_out Captured wlroots image returned to the streaming pipeline.
* @param timeout Maximum time to wait for the operation.
* @param cursor Cursor image or visibility state to composite.
* @return Capture status reported to the streaming pipeline.
*/
platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
auto status = wlr_t::snapshot(pull_free_image_cb, img_out, timeout, cursor);
if (status != platf::capture_e::ok) {
@@ -246,6 +290,14 @@ namespace wl {
return platf::capture_e::ok;
}
/**
* @brief Initialize Wayland capture that copies frames into system memory.
*
* @param hwdevice_type Hardware device type requested for capture or encode.
* @param display_name Display name.
* @param config Configuration values to apply.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
if (wlr_t::init(hwdevice_type, display_name, config)) {
return -1;
@@ -266,6 +318,12 @@ namespace wl {
return 0;
}
/**
* @brief Create AVCodec encode device.
*
* @param pix_fmt Sunshine pixel format to convert or allocate for.
* @return Constructed AVCodec encode device object.
*/
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override {
#ifdef SUNSHINE_BUILD_VAAPI
if (mem_type == platf::mem_type_e::vaapi) {
@@ -282,6 +340,11 @@ namespace wl {
return std::make_unique<platf::avcodec_encode_device_t>();
}
/**
* @brief Allocate an image buffer compatible with this display backend.
*
* @return Allocated img object, or null when unavailable.
*/
std::shared_ptr<platf::img_t> alloc_img() override {
auto img = std::make_shared<img_t>();
img->width = width;
@@ -293,10 +356,13 @@ namespace wl {
return img;
}
egl::display_t egl_display;
egl::ctx_t ctx;
egl::display_t egl_display; ///< EGL display.
egl::ctx_t ctx; ///< EGL context used for wlroots capture conversion.
};
/**
* @brief Wayland screencopy backend that exports frames as GPU resources.
*/
class wlr_vram_t: public wlr_t {
public:
platf::capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
@@ -344,6 +410,15 @@ namespace wl {
return platf::capture_e::ok;
}
/**
* @brief Capture a display frame into the provided image object.
*
* @param pull_free_image_cb Callback that provides an available image buffer.
* @param img_out Captured wlroots image returned to the streaming pipeline.
* @param timeout Maximum time to wait for the operation.
* @param cursor Cursor image or visibility state to composite.
* @return Capture status reported to the streaming pipeline.
*/
platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
auto status = wlr_t::snapshot(pull_free_image_cb, img_out, timeout, cursor);
if (status != platf::capture_e::ok) {
@@ -370,6 +445,11 @@ namespace wl {
return platf::capture_e::ok;
}
/**
* @brief Allocate an image buffer compatible with this display backend.
*
* @return Allocated img object, or null when unavailable.
*/
std::shared_ptr<platf::img_t> alloc_img() override {
auto img = std::make_shared<egl::img_descriptor_t>();
@@ -385,6 +465,12 @@ namespace wl {
return img;
}
/**
* @brief Create AVCodec encode device.
*
* @param pix_fmt Sunshine pixel format to convert or allocate for.
* @return Constructed AVCodec encode device object.
*/
std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override {
#ifdef SUNSHINE_BUILD_VAAPI
if (mem_type == platf::mem_type_e::vaapi) {
@@ -401,17 +487,26 @@ namespace wl {
return std::make_unique<platf::avcodec_encode_device_t>();
}
/**
* @brief Populate a fallback image when real capture data is unavailable.
*
* @param img Image or frame object to read from or populate.
* @return Capture status reported to the streaming pipeline.
*/
int dummy_img(platf::img_t *img) override {
// Empty images are recognized as dummies by the zero sequence number
return 0;
}
std::uint64_t sequence {};
std::uint64_t sequence {}; ///< Monotonic capture sequence assigned to Wayland frames.
};
} // namespace wl
namespace platf {
/**
* @brief Create a Wayland capture backend for the requested memory type.
*/
std::shared_ptr<display_t> wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
if (hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi && hwdevice_type != platf::mem_type_e::cuda) {
BOOST_LOG(error) << "[wlgrab] Could not initialize display with the given hw device type."sv;
@@ -435,6 +530,9 @@ namespace platf {
return wlr;
}
/**
* @brief Enumerate capture display names reported by the Wayland compositor.
*/
std::vector<std::string> wl_display_names() {
std::vector<std::string> display_names;
+228 -15
View File
@@ -33,12 +33,32 @@
using namespace std::literals;
namespace platf {
/**
* @brief Load XCB entry points used by the X11 capture backend.
*
* @return 0 when required XCB symbols are loaded; nonzero otherwise.
*/
int load_xcb();
/**
* @brief Load X11 entry points used by the X11 capture backend.
*
* @return 0 when required X11 symbols are loaded; nonzero otherwise.
*/
int load_x11();
namespace x11 {
/**
* @def _FN(x, ret, args)
* @brief Macro for FN.
*/
#define _FN(x, ret, args) \
/** \
* @brief Function pointer type for the dynamically loaded X11 entry point. \
*/ \
typedef ret(*x##_fn) args; \
/** \
* @brief Loaded X11 entry point pointer. \
*/ \
static x##_fn x
_FN(GetImage, XImage *, (Display * display, Drawable d, int x, int y, unsigned int width, unsigned int height, unsigned long plane_mask, int format));
@@ -174,6 +194,11 @@ namespace platf {
_FN(setup_roots_iterator, xcb_screen_iterator_t, (const xcb_setup_t *R));
_FN(generate_id, std::uint32_t, (xcb_connection_t * c));
/**
* @brief Initialize shared-memory support for X11 capture.
*
* @return 0 when XCB shared-memory functions are loaded; nonzero otherwise.
*/
int init_shm() {
static void *handle {nullptr};
static bool funcs_loaded = false;
@@ -204,6 +229,11 @@ namespace platf {
return 0;
}
/**
* @brief Initialize XFixes cursor tracking for an X11 display.
*
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init() {
static void *handle {nullptr};
static bool funcs_loaded = false;
@@ -240,29 +270,73 @@ namespace platf {
#undef _FN
} // namespace xcb
/**
* @brief Release image resources.
*
* @param p Pointer passed to the deleter or conversion helper.
*/
void freeImage(XImage *);
/**
* @brief Release x resources.
*
* @param p Pointer passed to the deleter or conversion helper.
*/
void freeX(XFixesCursorImage *);
/**
* @brief XCB connection pointer released with `xcb_disconnect`.
*/
using xcb_connect_t = util::dyn_safe_ptr<xcb_connection_t, &xcb::disconnect>;
/**
* @brief XCB image pointer released with `xcb_image_destroy`.
*/
using xcb_img_t = util::c_ptr<xcb_shm_get_image_reply_t>;
/**
* @brief XImage pointer released with `XDestroyImage`.
*/
using ximg_t = util::safe_ptr<XImage, freeImage>;
/**
* @brief XFixes cursor image pointer released with `XFree`.
*/
using xcursor_t = util::safe_ptr<XFixesCursorImage, freeX>;
/**
* @brief XRandR CRTC info pointer released with `XRRFreeCrtcInfo`.
*/
using crtc_info_t = util::dyn_safe_ptr<_XRRCrtcInfo, &x11::rr::FreeCrtcInfo>;
/**
* @brief XRandR output info pointer released with `XRRFreeOutputInfo`.
*/
using output_info_t = util::dyn_safe_ptr<_XRROutputInfo, &x11::rr::FreeOutputInfo>;
/**
* @brief XRandR screen resources pointer released with `XRRFreeScreenResources`.
*/
using screen_res_t = util::dyn_safe_ptr<_XRRScreenResources, &x11::rr::FreeScreenResources>;
/**
* @brief RAII wrapper that removes a SysV shared-memory segment.
*/
class shm_id_t {
public:
shm_id_t():
id {-1} {
}
/**
* @brief Take ownership of a SysV shared-memory segment ID.
*
* @param id SysV shared-memory segment ID.
*/
shm_id_t(int id):
id {id} {
}
/**
* @brief Move ownership of a SysV shared-memory segment ID.
*
* @param other Shared-memory ID wrapper whose segment ownership is moved.
*/
shm_id_t(shm_id_t &&other) noexcept:
id(other.id) {
other.id = -1;
@@ -275,19 +349,32 @@ namespace platf {
}
}
int id;
int id; ///< SysV shared-memory segment identifier returned by shmget.
};
/**
* @brief RAII wrapper that detaches mapped SysV shared memory.
*/
class shm_data_t {
public:
shm_data_t():
data {(void *) -1} {
}
/**
* @brief Take ownership of an attached shared-memory mapping.
*
* @param data Pointer returned by shmat.
*/
shm_data_t(void *data):
data {data} {
}
/**
* @brief Move ownership of an attached shared-memory mapping.
*
* @param other Shared-memory mapping wrapper whose attachment is moved.
*/
shm_data_t(shm_data_t &&other) noexcept:
data(other.data) {
other.data = (void *) -1;
@@ -299,13 +386,19 @@ namespace platf {
}
}
void *data;
void *data; ///< Address returned by shmat for the shared-memory segment.
};
/**
* @brief X11 image wrapper used by the software capture path.
*/
struct x11_img_t: public img_t {
ximg_t img;
ximg_t img; ///< XImage backing the current software-captured frame.
};
/**
* @brief X11 shared-memory image and segment ownership.
*/
struct shm_img_t: public img_t {
~shm_img_t() override {
delete[] data;
@@ -362,14 +455,17 @@ namespace platf {
}
}
/**
* @brief X11 display, window, and attribute handles for capture.
*/
struct x11_attr_t: public display_t {
std::chrono::nanoseconds delay;
std::chrono::nanoseconds delay; ///< Delay before the timer task becomes eligible to run.
x11::xdisplay_t xdisplay;
Window xwindow;
XWindowAttributes xattr;
x11::xdisplay_t xdisplay; ///< X11 display connection used for capture.
Window xwindow; ///< Root window being captured.
XWindowAttributes xattr; ///< Cached X11 window attributes used to detect size changes.
mem_type_e mem_type;
mem_type_e mem_type; ///< Mem type.
/**
* Last X (NOT the streamed monitor!) size.
@@ -377,6 +473,11 @@ namespace platf {
*/
// int env_width, env_height;
/**
* @brief Open the X11 display and initialize capture attributes.
*
* @param mem_type Requested memory path for the capture backend.
*/
x11_attr_t(mem_type_e mem_type):
xdisplay {x11::OpenDisplay(nullptr)},
xwindow {},
@@ -385,6 +486,13 @@ namespace platf {
x11::InitThreads();
}
/**
* @brief Open the X11 display and cache capture window attributes.
*
* @param display_name Display name.
* @param config Configuration values to apply.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(const std::string &display_name, const ::video::config_t &config) {
if (!xdisplay) {
BOOST_LOG(error) << "Could not open X11 display"sv;
@@ -501,6 +609,15 @@ namespace platf {
return capture_e::ok;
}
/**
* @brief Capture a display frame into the provided image object.
*
* @param pull_free_image_cb Callback that provides an available image buffer.
* @param img_out XImage-backed captured frame returned to the streaming pipeline.
* @param timeout Maximum time to wait for the operation.
* @param cursor Cursor image or visibility state to composite.
* @return Capture status reported to the streaming pipeline.
*/
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
refresh();
@@ -532,10 +649,21 @@ namespace platf {
return capture_e::ok;
}
/**
* @brief Allocate an image buffer compatible with this display backend.
*
* @return Allocated img object, or null when unavailable.
*/
std::shared_ptr<img_t> alloc_img() override {
return std::make_shared<x11_img_t>();
}
/**
* @brief Create AVCodec encode device.
*
* @param pix_fmt Sunshine pixel format to convert or allocate for.
* @return Constructed AVCodec encode device object.
*/
std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
#ifdef SUNSHINE_BUILD_VAAPI
if (mem_type == mem_type_e::vaapi) {
@@ -552,6 +680,12 @@ namespace platf {
return std::make_unique<avcodec_encode_device_t>();
}
/**
* @brief Populate a fallback image when real capture data is unavailable.
*
* @param img Image or frame object to read from or populate.
* @return Capture status reported to the streaming pipeline.
*/
int dummy_img(img_t *img) override {
// TODO: stop cheating and give black image
if (!img) {
@@ -567,24 +701,35 @@ namespace platf {
}
};
/**
* @brief X11 shared-memory image dimensions and identifiers.
*/
struct shm_attr_t: public x11_attr_t {
x11::xdisplay_t shm_xdisplay; // Prevent race condition with x11_attr_t::xdisplay
xcb_connect_t xcb;
xcb_screen_t *display;
std::uint32_t seg;
x11::xdisplay_t shm_xdisplay; ///< X11 display held separately to prevent races with x11_attr_t::xdisplay.
xcb_connect_t xcb; ///< XCB connection used by the shared-memory capture path.
xcb_screen_t *display; ///< XCB screen containing the captured root window.
std::uint32_t seg; ///< XCB shared-memory segment ID attached to the image.
shm_id_t shm_id;
shm_id_t shm_id; ///< Shm ID.
shm_data_t data;
shm_data_t data; ///< Attached SysV shared-memory data used by XShm.
task_pool_util::TaskPool::task_id_t refresh_task_id;
task_pool_util::TaskPool::task_id_t refresh_task_id; ///< Refresh task ID.
/**
* @brief Refresh X11 shared-memory capture after a scheduled delay.
*/
void delayed_refresh() {
refresh();
refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id;
}
/**
* @brief Open an X11 shared-memory capture backend.
*
* @param mem_type Requested memory path for the capture backend.
*/
shm_attr_t(mem_type_e mem_type):
x11_attr_t(mem_type),
shm_xdisplay {x11::OpenDisplay(nullptr)} {
@@ -640,6 +785,15 @@ namespace platf {
return capture_e::ok;
}
/**
* @brief Capture a display frame into the provided image object.
*
* @param pull_free_image_cb Callback that provides an available image buffer.
* @param img_out Shared-memory captured frame returned to the streaming pipeline.
* @param timeout Maximum time to wait for the operation.
* @param cursor Cursor image or visibility state to composite.
* @return Capture status reported to the streaming pipeline.
*/
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
// The whole X server changed, so we must reinit everything
if (xattr.width != env_width || xattr.height != env_height) {
@@ -670,6 +824,11 @@ namespace platf {
}
}
/**
* @brief Allocate an image buffer compatible with this display backend.
*
* @return Allocated img object, or null when unavailable.
*/
std::shared_ptr<img_t> alloc_img() override {
auto img = std::make_shared<shm_img_t>();
img->width = width;
@@ -681,10 +840,23 @@ namespace platf {
return img;
}
/**
* @brief Populate a fallback image when real capture data is unavailable.
*
* @param img Image or frame object to read from or populate.
* @return Capture status reported to the streaming pipeline.
*/
int dummy_img(platf::img_t *img) override {
return 0;
}
/**
* @brief Initialize X11 shared-memory capture for the selected display.
*
* @param display_name Display name.
* @param config Configuration values to apply.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(const std::string &display_name, const ::video::config_t &config) {
if (x11_attr_t::init(display_name, config)) {
return 1;
@@ -724,11 +896,24 @@ namespace platf {
return 0;
}
/**
* @brief Calculate the XCB shared-memory frame size.
*
* @return Frame size in bytes for BGRA pixels.
*/
std::uint32_t frame_size() {
return width * height * 4;
}
};
/**
* @brief Create an X11 display capture backend.
*
* @param hwdevice_type Hardware device type requested for capture or encode.
* @param display_name Display name.
* @param config Configuration values to apply.
* @return X11 display backend, or nullptr when initialization fails.
*/
std::shared_ptr<display_t> x11_display(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
if (hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi && hwdevice_type != platf::mem_type_e::cuda) {
BOOST_LOG(error) << "Could not initialize x11 display with the given hw device type"sv;
@@ -763,6 +948,11 @@ namespace platf {
return x11_disp;
}
/**
* @brief Enumerate display names accepted by the X11 backend.
*
* @return X11 display names, or an empty list when X11 probing fails.
*/
std::vector<std::string> x11_display_names() {
if (load_x11() || load_xcb()) {
BOOST_LOG(error) << "Couldn't init x11 libraries"sv;
@@ -800,14 +990,23 @@ namespace platf {
return names;
}
/**
* @brief Release image resources.
*/
void freeImage(XImage *p) {
XDestroyImage(p);
}
/**
* @brief Release x resources.
*/
void freeX(XFixesCursorImage *p) {
x11::Free(p);
}
/**
* @brief Load XCB entry points used by the X11 capture backend.
*/
int load_xcb() {
// This will be called once only
static int xcb_status = xcb::init_shm() || xcb::init();
@@ -815,6 +1014,9 @@ namespace platf {
return xcb_status;
}
/**
* @brief Load X11 entry points used by the X11 capture backend.
*/
int load_x11() {
// This will be called once only
static int x11_status =
@@ -868,14 +1070,25 @@ namespace platf {
blend_cursor((xdisplay_t::pointer) ctx.get(), img, offsetX, offsetY);
}
/**
* @brief Open and initialize the display connection used for capture.
*/
xdisplay_t make_display() {
return OpenDisplay(nullptr);
}
/**
* @brief Release display resources.
*/
void freeDisplay(_XDisplay *xdisplay) {
CloseDisplay(xdisplay);
}
/**
* @brief Release cursor context resources.
*
* @param ctx Native context object used by the operation or callback.
*/
void freeCursorCtx(cursor_ctx_t::pointer ctx) {
CloseDisplay((xdisplay_t::pointer) ctx);
}
+38 -1
View File
@@ -20,16 +20,45 @@ namespace egl {
namespace platf::x11 {
struct cursor_ctx_raw_t;
/**
* @brief Release cursor context resources.
*
* @param ctx Native context object used by the operation or callback.
*/
void freeCursorCtx(cursor_ctx_raw_t *ctx);
/**
* @brief Release display resources.
*
* @param xdisplay X11 display connection.
*/
void freeDisplay(_XDisplay *xdisplay);
/**
* @brief XFixes cursor image pointer released with `XFree`.
*/
using cursor_ctx_t = util::safe_ptr<cursor_ctx_raw_t, freeCursorCtx>;
/**
* @brief X11 display pointer released with `XCloseDisplay`.
*/
using xdisplay_t = util::safe_ptr<_XDisplay, freeDisplay>;
/**
* @brief X11 cursor image and positioning state used during capture.
*/
class cursor_t {
public:
/**
* @brief Allocate the underlying object and wrap it in the owning handle.
*
* @return Created backend object, or null when creation fails.
*/
static std::optional<cursor_t> make();
/**
* @brief Run the capture loop for this backend.
*
* @param img Image or frame object to read from or populate.
*/
void capture(egl::cursor_t &img);
/**
@@ -37,11 +66,19 @@ namespace platf::x11 {
*
* img <-- destination image
* offsetX, offsetY <--- Top left corner of the virtual screen
* @param img Image or frame object to read from or populate.
* @param offsetX Offset x.
* @param offsetY Offset y.
*/
void blend(img_t &img, int offsetX, int offsetY);
cursor_ctx_t ctx;
cursor_ctx_t ctx; ///< X11 cursor context used to track and blend cursor images.
};
/**
* @brief Open and initialize the display connection used for capture.
*
* @return Constructed display object.
*/
xdisplay_t make_display();
} // namespace platf::x11
+22
View File
@@ -27,7 +27,29 @@ NS_ASSUME_NONNULL_BEGIN
@class CATapDescription;
namespace platf {
/**
* @brief Provide captured PCM frames to AudioConverter.
*
* @param inAudioConverter In audio converter.
* @param ioNumberDataPackets Requested and returned packet count.
* @param ioData Buffer list filled with input audio.
* @param outDataPacketDescription Optional packet description output.
* @param inUserData AudioConverterInputData state used by the callback.
* @return Core Audio status code from the callback.
*/
OSStatus audioConverterComplexInputProc(AudioConverterRef _Nullable inAudioConverter, UInt32 *_Nonnull ioNumberDataPackets, AudioBufferList *_Nonnull ioData, AudioStreamPacketDescription *_Nullable *_Nullable outDataPacketDescription, void *_Nonnull inUserData);
/**
* @brief Receive system-audio tap samples from Core Audio.
*
* @param inDevice In device.
* @param inNow In now.
* @param inInputData In input data.
* @param inInputTime In input time.
* @param outOutputData Out output data.
* @param inOutputTime In output time.
* @param inClientData In client data.
* @return Core Audio status code from the IO callback.
*/
OSStatus systemAudioIOProc(AudioObjectID inDevice, const AudioTimeStamp *_Nullable inNow, const AudioBufferList *_Nullable inInputData, const AudioTimeStamp *_Nullable inInputTime, AudioBufferList *_Nullable outOutputData, const AudioTimeStamp *_Nullable inOutputTime, void *_Nullable inClientData);
} // namespace platf
+41 -7
View File
@@ -12,9 +12,17 @@
#include "src/platform/common.h"
namespace platf {
/**
* @brief CoreMedia sample buffer retained by an AV image wrapper.
*/
struct av_sample_buf_t {
CMSampleBufferRef buf;
CMSampleBufferRef buf; ///< Retained CoreMedia sample buffer.
/**
* @brief Retain a CoreMedia sample buffer for captured-image lifetime.
*
* @param buf Sample buffer received from AVFoundation.
*/
explicit av_sample_buf_t(CMSampleBufferRef buf):
buf((CMSampleBufferRef) CFRetain(buf)) {
}
@@ -26,10 +34,18 @@ namespace platf {
}
};
/**
* @brief CoreVideo pixel buffer retained by an AV image wrapper.
*/
struct av_pixel_buf_t {
CVPixelBufferRef buf;
CVPixelBufferRef buf; ///< Pixel buffer extracted from the sample buffer.
// Constructor
/**
* @brief Lock the sample buffer's pixel buffer for read-only access.
*
* @param sb Sample buffer that owns the image data.
*/
explicit av_pixel_buf_t(CMSampleBufferRef sb):
buf(
CMSampleBufferGetImageBuffer(sb)
@@ -37,6 +53,11 @@ namespace platf {
CVPixelBufferLockBaseAddress(buf, kCVPixelBufferLock_ReadOnly);
}
/**
* @brief Return the base address of the locked Core Video pixel buffer.
*
* @return Pointer to the first byte of image data in the pixel buffer.
*/
[[nodiscard]] uint8_t *data() const {
return static_cast<uint8_t *>(CVPixelBufferGetBaseAddress(buf));
}
@@ -49,16 +70,29 @@ namespace platf {
}
};
/**
* @brief Captured macOS image backed by an AV sample buffer.
*/
struct av_img_t: img_t {
std::shared_ptr<av_sample_buf_t> sample_buffer;
std::shared_ptr<av_pixel_buf_t> pixel_buffer;
std::shared_ptr<av_sample_buf_t> sample_buffer; ///< Retained AVFoundation sample buffer for the captured frame.
std::shared_ptr<av_pixel_buf_t> pixel_buffer; ///< Retained CoreVideo pixel buffer extracted from the sample.
};
/**
* @brief Temporary retain wrapper used while AV image data is borrowed.
*/
struct temp_retain_av_img_t {
std::shared_ptr<av_sample_buf_t> sample_buffer;
std::shared_ptr<av_pixel_buf_t> pixel_buffer;
uint8_t *data;
std::shared_ptr<av_sample_buf_t> sample_buffer; ///< Sample buffer kept alive while `data` is borrowed.
std::shared_ptr<av_pixel_buf_t> pixel_buffer; ///< Pixel buffer kept locked while `data` is borrowed.
uint8_t *data; ///< Pointer to the locked pixel buffer bytes.
/**
* @brief Construct a temporary AV image retain wrapper.
*
* @param sb Sample buffer to retain.
* @param pb Pixel buffer to retain.
* @param dt Image data pointer.
*/
temp_retain_av_img_t(
std::shared_ptr<av_sample_buf_t> sb,
std::shared_ptr<av_pixel_buf_t> pb,
+68 -2
View File
@@ -8,34 +8,100 @@
#import <AppKit/AppKit.h>
#import <AVFoundation/AVFoundation.h>
/**
* @brief macOS capture session and video output handles.
*/
struct CaptureSession {
AVCaptureVideoDataOutput *output;
NSCondition *captureStopped;
AVCaptureVideoDataOutput *output; ///< Output.
NSCondition *captureStopped; ///< Capture stopped.
};
static const int kMaxDisplays = 32;
/**
* @brief AVFoundation video capture controller used by the macOS backend.
*/
@interface AVVideo: NSObject <AVCaptureVideoDataOutputSampleBufferDelegate>
/**
* @brief Display ID property.
*/
@property (nonatomic, assign) CGDirectDisplayID displayID;
/**
* @brief Min frame duration property.
*/
@property (nonatomic, assign) CMTime minFrameDuration;
/**
* @brief Pixel format property.
*/
@property (nonatomic, assign) OSType pixelFormat;
/**
* @brief Frame width property.
*/
@property (nonatomic, assign) int frameWidth;
/**
* @brief Frame height property.
*/
@property (nonatomic, assign) int frameHeight;
/**
* @brief Objective-C block invoked for each captured sample buffer.
*/
typedef bool (^FrameCallbackBlock)(CMSampleBufferRef);
/**
* @brief Capture session that owns the active AVFoundation inputs and outputs.
*/
@property (nonatomic, assign) AVCaptureSession *session;
/**
* @brief Video outputs property.
*/
@property (nonatomic, assign) NSMapTable<AVCaptureConnection *, AVCaptureVideoDataOutput *> *videoOutputs;
/**
* @brief Capture callbacks property.
*/
@property (nonatomic, assign) NSMapTable<AVCaptureConnection *, FrameCallbackBlock> *captureCallbacks;
/**
* @brief Capture signals property.
*/
@property (nonatomic, assign) NSMapTable<AVCaptureConnection *, dispatch_semaphore_t> *captureSignals;
/**
* @brief List display names accepted by the selected capture backend.
*
* @return Display names accepted by the selected capture backend.
*/
+ (NSArray<NSDictionary *> *)displayNames;
/**
* @brief Return the user-visible name for a CoreGraphics display.
*
* @param displayID Display ID.
* @return Display name for the supplied CoreGraphics display ID.
*/
+ (NSString *)getDisplayName:(CGDirectDisplayID)displayID;
/**
* @brief Initialize AVFoundation capture for a display and frame rate.
*
* @param displayID Display ID.
* @param frameRate Frame rate.
* @return Initialized AVVideo instance, or nil on failure.
*/
- (id)initWithDisplay:(CGDirectDisplayID)displayID frameRate:(int)frameRate;
/**
* @brief Set frame width frame height.
*
* @param frameWidth Frame width.
* @param frameHeight Frame height.
*/
- (void)setFrameWidth:(int)frameWidth frameHeight:(int)frameHeight;
/**
* @brief Run the capture loop for this backend.
*
* @param frameCallback Frame callback.
* @return Capture status reported to the streaming pipeline.
*/
- (dispatch_semaphore_t)capture:(FrameCallbackBlock)frameCallback;
@end
+4 -1
View File
@@ -52,8 +52,11 @@ namespace ca {
}
namespace detail {
/**
* @brief Small wrapper for displaying CoreAudio OSStatus values.
*/
struct StatusView {
OSStatus e;
OSStatus e; ///< E.
};
inline std::ostream &operator<<(std::ostream &os, StatusView v) {
+42 -5
View File
@@ -21,6 +21,10 @@
#include "src/platform/macos/nv12_zero_device.h"
// Avoid conflict between AVFoundation and libavutil both defining AVMediaType
/**
* @def AVMediaType
* @brief Macro for AV media type.
*/
#define AVMediaType AVMediaType_FFmpeg
#include "src/video.h"
#undef AVMediaType
@@ -210,10 +214,13 @@ namespace platf {
}
} // namespace
/**
* @brief macOS display capture source and image buffers.
*/
struct av_display_t: public display_t {
AVVideo *av_capture {};
CGDirectDisplayID display_id {};
IOPMAssertionID display_sleep_assertion {kIOPMNullAssertionID};
AVVideo *av_capture {}; ///< AV capture.
CGDirectDisplayID display_id {}; ///< Display ID.
IOPMAssertionID display_sleep_assertion {kIOPMNullAssertionID}; ///< Display sleep assertion.
~av_display_t() override {
[av_capture release];
@@ -226,6 +233,9 @@ namespace platf {
}
}
/**
* @brief Prevent macOS from sleeping the captured display while streaming.
*/
void prevent_display_sleep() {
if (display_sleep_assertion != kIOPMNullAssertionID) {
return;
@@ -292,10 +302,21 @@ namespace platf {
return capture_e::ok;
}
/**
* @brief Allocate an image buffer compatible with this display backend.
*
* @return Allocated img object, or null when unavailable.
*/
std::shared_ptr<img_t> alloc_img() override {
return std::make_shared<av_img_t>();
}
/**
* @brief Create AVCodec encode device.
*
* @param pix_fmt Sunshine pixel format to convert or allocate for.
* @return Constructed AVCodec encode device object.
*/
std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
if (pix_fmt == pix_fmt_e::yuv420p) {
av_capture.pixelFormat = kCVPixelFormatType_32BGRA;
@@ -313,6 +334,12 @@ namespace platf {
}
}
/**
* @brief Populate a fallback image when real capture data is unavailable.
*
* @param img Image or frame object to read from or populate.
* @return Capture status reported to the streaming pipeline.
*/
int dummy_img(img_t *img) override {
if (!platf::is_screen_capture_allowed()) {
// If we don't have the screen capture permission, this function will hang
@@ -359,11 +386,20 @@ namespace platf {
* display --> an opaque pointer to an object of this class
* width --> the intended capture width
* height --> the intended capture height
* @param display Display object or identifier associated with the operation.
* @param width Frame or display width in pixels.
* @param height Frame or display height in pixels.
*/
static void setResolution(void *display, int width, int height) {
[static_cast<AVVideo *>(display) setFrameWidth:width frameHeight:height];
}
/**
* @brief Set pixel format.
*
* @param display Display object or identifier associated with the operation.
* @param pixelFormat Pixel format.
*/
static void setPixelFormat(void *display, OSType pixelFormat) {
static_cast<AVVideo *>(display).pixelFormat = pixelFormat;
}
@@ -440,8 +476,9 @@ namespace platf {
}
/**
* @brief Returns if GPUs/drivers have changed since the last call to this function.
* @return `true` if a change has occurred or if it is unknown whether a change occurred.
* @brief Report whether encoder backends should be probed again before streaming.
*
* @return Always `true` because macOS GPU changes are not tracked by this backend.
*/
bool needs_encoder_reenumeration() {
// We don't track GPU state, so we will always reenumerate. Fortunately, it is fast on macOS.
+91 -21
View File
@@ -24,49 +24,67 @@
#include "src/platform/common.h"
#include "src/utility.h"
/**
* @brief Delay for a double click, in milliseconds.
* @todo Make this configurable.
*/
constexpr std::chrono::milliseconds MULTICLICK_DELAY_MS(500);
constexpr auto MULTICLICK_DELAY_MS = std::chrono::milliseconds(500); ///< Maximum gap between clicks that macOS should treat as a double click.
namespace platf {
using namespace std::literals;
constexpr int WHEEL_DELTA = 120;
constexpr double DEFAULT_SCROLLWHEEL_SCALING = 0.3125;
constexpr int DEFAULT_SCROLL_LINES_PER_DETENT = 5;
constexpr int WHEEL_DELTA = 120; ///< Protocol or platform constant for wheel delta.
constexpr double DEFAULT_SCROLLWHEEL_SCALING = 0.3125; ///< Protocol or platform constant for default scrollwheel scaling.
constexpr int DEFAULT_SCROLL_LINES_PER_DETENT = 5; ///< Protocol or platform constant for default scroll lines per detent.
/**
* @brief macOS input source and target display state.
*/
struct macos_input_t {
public:
CGDirectDisplayID display {};
CGFloat displayScaling {};
CGEventSourceRef source {};
CGDirectDisplayID display {}; ///< CoreGraphics identifier for the display receiving injected input.
CGFloat displayScaling {}; ///< Scale factor used to translate client coordinates to display pixels.
CGEventSourceRef source {}; ///< CoreGraphics event source used for mouse and scroll events.
// keyboard related stuff
CGEventSourceRef keyboard_source {};
CGEventFlags kb_flags {};
CGEventSourceRef keyboard_source {}; ///< CoreGraphics event source used for keyboard injection.
CGEventFlags kb_flags {}; ///< Active keyboard modifier flags currently held down by the client.
// mouse related stuff
CGEventRef mouse_event {}; // mouse event source
double scrollwheel_scaling {DEFAULT_SCROLLWHEEL_SCALING};
int scroll_lines_per_detent {DEFAULT_SCROLL_LINES_PER_DETENT};
CGEventRef mouse_event {}; ///< Reusable CoreGraphics mouse event updated before posting.
double scrollwheel_scaling {DEFAULT_SCROLLWHEEL_SCALING}; ///< Multiplier applied to incoming scroll-wheel deltas.
int scroll_lines_per_detent {DEFAULT_SCROLL_LINES_PER_DETENT}; ///< Number of logical scroll lines represented by one wheel detent.
/**
* @brief Tracks whether the mouse button is currently pressed.
*/
bool mouse_down[3] {}; // mouse button status
/**
* @brief Last mouse event.
*/
std::chrono::steady_clock::steady_clock::time_point last_mouse_event[3][2]; // timestamp of last mouse events
};
// A struct to hold a Windows keycode to Mac virtual keycode mapping.
/**
* @brief Mapping from Sunshine key symbols to macOS virtual key codes.
*/
struct KeyCodeMap {
int win_keycode;
int mac_keycode;
int win_keycode; ///< Sunshine/Windows virtual key code received from the client.
int mac_keycode; ///< macOS virtual key code sent through CoreGraphics.
};
// Customized less operator for using std::lower_bound() on a KeyCodeMap array.
/**
* @brief Order key mappings by Sunshine key code for binary search.
*
* @param a Left-hand mapping being compared.
* @param b Right-hand mapping being compared.
* @return True when `a` has a lower Sunshine key code than `b`.
*/
bool operator<(const KeyCodeMap &a, const KeyCodeMap &b) {
return a.win_keycode < b.win_keycode;
}
// clang-format off
/**
* @brief Key codes map.
*/
const KeyCodeMap kKeyCodesMap[] = {
{ 0x08 /* VKEY_BACK */, kVK_Delete },
{ 0x09 /* VKEY_TAB */, kVK_Tab },
@@ -238,6 +256,12 @@ const KeyCodeMap kKeyCodesMap[] = {
};
// clang-format on
/**
* @brief Translate a platform keycode to a Sunshine key symbol.
*
* @param keycode Platform keycode being translated or emitted.
* @return Sunshine key symbol, or 0 when the keycode is unmapped.
*/
int keysym(int keycode) {
KeyCodeMap key_map {};
@@ -256,12 +280,22 @@ const KeyCodeMap kKeyCodesMap[] = {
return temp_map->mac_keycode;
}
/**
* @brief macOS modifier flags split into generic and device-specific bits.
*/
struct modifier_flags_t {
CGEventFlags generic {};
CGEventFlags device {};
CGEventFlags all_devices {};
CGEventFlags generic {}; ///< Modifier bits represented by device-independent CoreGraphics flags.
CGEventFlags device {}; ///< Modifier bits represented by left/right device-specific flags.
CGEventFlags all_devices {}; ///< Mask covering all device-specific variants for this modifier.
};
/**
* @brief Resolve the CoreGraphics modifier masks associated with a key code.
*
* @param key Sunshine key code that may represent a modifier key.
* @param flags Output masks for the matching CoreGraphics modifier.
* @return True when modifier flags were found for the key.
*/
bool modifier_flags_for_key(int key, modifier_flags_t &flags) {
switch (key) {
case kVK_Shift:
@@ -368,6 +402,16 @@ const KeyCodeMap kKeyCodesMap[] = {
};
}
/**
* @brief Post a mouse event at the clamped display location.
*
* @param input Platform input context.
* @param button Mouse button.
* @param type CoreGraphics mouse event type.
* @param raw_location Requested mouse location.
* @param previous_location Previous mouse location.
* @param click_count Click count for the event.
*/
void post_mouse(
input_t &input,
const CGMouseButton button,
@@ -410,6 +454,12 @@ const KeyCodeMap kKeyCodesMap[] = {
CGWarpMouseCursorPosition(location);
}
/**
* @brief Choose the CoreGraphics mouse event type for the current button action.
*
* @param input Platform input backend that receives the event.
* @return CoreGraphics mouse event type for button press or release.
*/
inline CGEventType event_type_mouse(input_t &input) {
const auto macos_input = static_cast<macos_input_t *>(input.get());
@@ -494,6 +544,12 @@ const KeyCodeMap kKeyCodesMap[] = {
macos_input->last_mouse_event[mac_button][release] = now;
}
/**
* @brief Get scroll lines per detent.
*
* @param scrollwheel_scaling Scrollwheel scaling.
* @return Number of logical lines represented by one wheel detent.
*/
int get_scroll_lines_per_detent(double &scrollwheel_scaling) {
double scale = DEFAULT_SCROLLWHEEL_SCALING;
const auto value = CFPreferencesCopyValue(CFSTR("com.apple.scrollwheel.scaling"), kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
@@ -521,6 +577,13 @@ const KeyCodeMap kKeyCodesMap[] = {
return std::max(1, static_cast<int>(std::ceil(1.0 + scroll_scale * lines_per_scroll_scale)));
}
/**
* @brief Convert a high-resolution wheel delta to CoreGraphics scroll pixels.
*
* @param macos_input macOS input state containing scroll scaling settings.
* @param high_res_distance Wheel delta in Windows high-resolution units.
* @return Pixel distance to send to CoreGraphics.
*/
int scroll_pixels(const macos_input_t *macos_input, const int high_res_distance) {
const auto source_pixels_per_line = CGEventSourceGetPixelsPerLine(macos_input->source);
const auto pixels_per_line = source_pixels_per_line > 0 ? static_cast<int>(source_pixels_per_line + 0.5) : 10;
@@ -529,6 +592,13 @@ const KeyCodeMap kKeyCodesMap[] = {
return static_cast<int>(scaled_pixels / WHEEL_DELTA);
}
/**
* @brief Post a macOS scroll event to the target display.
*
* @param input Platform input backend that receives the event.
* @param wheelY Wheel y.
* @param wheelX Wheel x.
*/
void post_scroll(input_t &input, const int wheelY, const int wheelX) {
if (wheelY == 0 && wheelX == 0) {
return;
+36 -2
View File
@@ -11,13 +11,22 @@
namespace platf {
using namespace std::literals;
/**
* @brief macOS microphone capture device and audio format state.
*/
struct av_mic_t: public mic_t {
AVAudio *av_audio_capture {};
AVAudio *av_audio_capture {}; ///< AV audio capture.
~av_mic_t() override {
[av_audio_capture release];
}
/**
* @brief Deliver a captured audio sample to Sunshine's audio pipeline.
*
* @param sample_in Sample in.
* @return Capture status reported to the streaming pipeline.
*/
capture_e sample(std::vector<float> &sample_in) override {
const uint32_t neededBytes = static_cast<uint32_t>(sample_in.size() * sizeof(float));
uint8_t *dst = reinterpret_cast<uint8_t *>(sample_in.data());
@@ -54,15 +63,35 @@ namespace platf {
}
};
/**
* @brief macOS audio control state used to create microphone streams.
*/
struct macos_audio_control_t: public audio_control_t {
AVCaptureDevice *audio_capture_device {};
AVCaptureDevice *audio_capture_device {}; ///< Audio capture device.
public:
/**
* @brief Update the sink value on the backend.
*
* @param sink Audio sink name to route or capture.
* @return Status from updating sink.
*/
int set_sink(const std::string &sink) override {
BOOST_LOG(warning) << "audio_control_t::set_sink() unimplemented: "sv << sink;
return 0;
}
/**
* @brief Create a microphone capture stream for the requested layout.
*
* @param mapping Opus channel mapping table for the requested layout.
* @param channels Number of audio channels in the stream.
* @param sample_rate Audio sample rate in hertz.
* @param frame_size Number of samples captured per audio frame.
* @param continuous_audio Continuous audio.
* @param host_audio_enabled Whether host playback should remain enabled during capture.
* @return Microphone capture object for the requested audio layout.
*/
std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, bool continuous_audio, bool host_audio_enabled) override {
auto mic = std::make_unique<av_mic_t>();
mic->av_audio_capture = [[AVAudio alloc] init];
@@ -110,6 +139,11 @@ namespace platf {
return true;
}
/**
* @brief Query host and virtual sink names available to Sunshine.
*
* @return Host and virtual sink names when the backend can report them.
*/
std::optional<sink_t> sink_info() override {
sink_t sink;
+20 -1
View File
@@ -11,13 +11,32 @@
#include <CoreGraphics/CoreGraphics.h>
namespace platf {
/**
* @brief Check whether macOS has granted screen-capture permission.
*
* @return True when Sunshine can capture the screen.
*/
bool is_screen_capture_allowed();
}
} // namespace platf
namespace dyn {
typedef void (*apiproc)();
/**
* @brief Load persisted state from its backing store.
*
* @param handle Native library or object handle used by the operation.
* @param funcs Function table populated from the loaded library.
* @param strict Whether missing functions should be treated as an error.
* @return 0 when all required symbols are loaded; nonzero when loading fails.
*/
int load(void *handle, const std::vector<std::tuple<apiproc *, const char *>> &funcs, bool strict = true);
/**
* @brief Return the native handle owned by the wrapper.
*
* @param libs List of libraries to probe for the requested symbol.
* @return Native dynamic-library handle, or nullptr when no library can be opened.
*/
void *handle(const std::vector<const char *> &libs);
} // namespace dyn
+29 -5
View File
@@ -5,6 +5,10 @@
// Required for IPV6_PKTINFO with Darwin headers
#ifndef __APPLE_USE_RFC_3542 // NOLINT(bugprone-reserved-identifier)
/**
* @def __APPLE_USE_RFC_3542
* @brief Macro for APPLE USE RFC 3542.
*/
#define __APPLE_USE_RFC_3542 1
#endif
@@ -43,7 +47,17 @@ namespace platf {
#if __MAC_OS_X_VERSION_MAX_ALLOWED < 110000 // __MAC_11_0
// If they're not in the SDK then we can use our own function definitions.
// Need to use weak import so that this will link in macOS 10.14 and earlier
/**
* @brief Query macOS screen-capture permission without prompting the user.
*
* @return True when screen-capture permission is granted.
*/
extern "C" bool CGPreflightScreenCaptureAccess(void) __attribute__((weak_import));
/**
* @brief Request macOS screen-capture permission from the user.
*
* @return True when screen-capture permission is granted.
*/
extern "C" bool CGRequestScreenCaptureAccess(void) __attribute__((weak_import));
#endif
@@ -52,6 +66,9 @@ namespace platf {
} // namespace
// Return whether screen capture is allowed for this process.
/**
* @brief Check whether screen capture allowed.
*/
bool is_screen_capture_allowed() {
return screen_capture_allowed;
}
@@ -436,8 +453,17 @@ namespace platf {
// are disconnected.
static std::atomic<int> qos_ref_count = 0;
/**
* @brief Owns platform QoS state that is restored during cleanup.
*/
class qos_t: public deinit_t {
public:
/**
* @brief Apply macOS socket QoS settings for scoped cleanup.
*
* @param sockfd Native socket descriptor whose options are updated.
* @param options Request options or socket options to apply.
*/
qos_t(int sockfd, std::vector<std::tuple<int, int, int>> options):
sockfd(sockfd),
options(options) {
@@ -462,11 +488,6 @@ namespace platf {
/**
* @brief Enables QoS on the given socket for traffic to the specified destination.
* @param native_socket The native socket handle.
* @param address The destination address for traffic sent on this socket.
* @param port The destination port for traffic sent on this socket.
* @param data_type The type of traffic sent on this socket.
* @param dscp_tagging Specifies whether to enable DSCP tagging on outgoing traffic.
*/
std::unique_ptr<deinit_t> enable_socket_qos(uintptr_t native_socket, boost::asio::ip::address &address, uint16_t port, qos_data_type_e data_type, bool dscp_tagging) {
int sockfd = (int) native_socket;
@@ -546,6 +567,9 @@ namespace platf {
}
}
/**
* @brief macOS high-precision timer implementation backed by a worker thread.
*/
class macos_high_precision_timer: public high_precision_timer {
public:
void sleep_for(const std::chrono::nanoseconds &duration) override {
+10 -1
View File
@@ -16,15 +16,24 @@ extern "C" {
namespace platf {
/**
* @brief Release an FFmpeg frame allocated by the capture or conversion backend.
*/
void free_frame(AVFrame *frame) {
av_frame_free(&frame);
}
/**
* @brief Release a backend buffer allocated for capture or conversion.
*
* @param opaque Opaque user pointer provided to the callback.
* @param data Payload or state data to serialize, deserialize, or forward.
*/
void free_buffer(void *opaque, uint8_t *data) {
CVPixelBufferRelease((CVPixelBufferRef) data);
}
util::safe_ptr<AVFrame, free_frame> av_frame;
util::safe_ptr<AVFrame, free_frame> av_frame; ///< AV frame.
int nv12_zero_device::convert(platf::img_t &img) {
auto *av_img = (av_img_t *) &img;
+37 -1
View File
@@ -10,8 +10,16 @@
struct AVFrame;
namespace platf {
/**
* @brief Release an FFmpeg frame allocated by the capture or conversion backend.
*
* @param frame Video or graphics frame being processed.
*/
void free_frame(AVFrame *frame);
/**
* @brief macOS zero-copy encode device that forwards NV12 frames from AVFoundation to FFmpeg.
*/
class nv12_zero_device: public avcodec_encode_device_t {
// display holds a pointer to an av_video object. Since the namespaces of AVFoundation
// and FFMPEG collide, we need this opaque pointer and cannot use the definition
@@ -20,13 +28,41 @@ namespace platf {
public:
// this function is used to set the resolution on an av_video object that we cannot
// call directly because of namespace collisions between AVFoundation and FFMPEG
/**
* @brief Callback signature used to update the opaque AVFoundation display resolution.
*/
using resolution_fn_t = std::function<void(void *display, int width, int height)>;
resolution_fn_t resolution_fn;
resolution_fn_t resolution_fn; ///< Callback stored for later AVFoundation resolution updates.
/**
* @brief Callback signature used to update the opaque AVFoundation pixel format.
*/
using pixel_format_fn_t = std::function<void(void *display, int pixelFormat)>;
/**
* @brief Initialize zero-copy NV12 encoding for an AVFoundation display.
*
* @param display Display object or identifier associated with the operation.
* @param pix_fmt Sunshine pixel format to convert or allocate for.
* @param resolution_fn Callback used to resize the AVFoundation capture output.
* @param pixel_format_fn Pixel format.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(void *display, pix_fmt_e pix_fmt, resolution_fn_t resolution_fn, const pixel_format_fn_t &pixel_format_fn);
/**
* @brief Forward a captured AVFoundation frame to FFmpeg without copying.
*
* @param img Image or frame object to read from or populate.
* @return Conversion status.
*/
int convert(img_t &img) override;
/**
* @brief Attach frame resources used by the next conversion or encode operation.
*
* @param frame Video or graphics frame being processed.
* @param hw_frames_ctx FFmpeg hardware frames context associated with the frame.
* @return Status from updating frame.
*/
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override;
private:
+76
View File
@@ -24,8 +24,22 @@ DEFINE_GUID(CLSID_CPolicyConfigClient, 0x870af99c, 0x171d, 0x4f9e, 0xaf, 0x0d, 0
#endif
/**
* @brief Policy configuration COM interface.
*/
#ifdef DOXYGEN
class IPolicyConfig;
#else
interface DECLSPEC_UUID("f8679f50-850a-41cf-9c72-430f290290c8") IPolicyConfig;
#endif
/**
* @brief Policy configuration COM class.
*/
#ifdef DOXYGEN
class CPolicyConfigClient;
#else
class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient;
#endif
// ----------------------------------------------------------------------------
// class CPolicyConfigClient
// {870af99c-171d-4f9e-af0d-e63df40c2bc9}
@@ -41,21 +55,41 @@ class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient;
// ----------------------------------------------------------------------------
interface IPolicyConfig: public IUnknown {
public:
/**
* @brief Get the mix format for an endpoint.
*
* @return HRESULT or platform status returned by the wrapped API.
*/
virtual HRESULT GetMixFormat(
PCWSTR,
WAVEFORMATEX **
);
/**
* @brief Get the device format for an endpoint.
*
* @return HRESULT or platform status returned by the wrapped API.
*/
virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat(
PCWSTR,
INT,
WAVEFORMATEX **
);
/**
* @brief Reset the device format for an endpoint.
*
* @return HRESULT or platform status returned by the wrapped API.
*/
virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat(
PCWSTR
);
/**
* @brief Set the device format for an endpoint.
*
* @return HRESULT or platform status returned by the wrapped API.
*/
virtual HRESULT STDMETHODCALLTYPE
SetDeviceFormat(
PCWSTR,
@@ -63,6 +97,11 @@ public:
WAVEFORMATEX *
);
/**
* @brief Get the processing period for an endpoint.
*
* @return HRESULT or platform status returned by the wrapped API.
*/
virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod(
PCWSTR,
INT,
@@ -70,38 +109,75 @@ public:
PINT64
);
/**
* @brief Set the processing period for an endpoint.
*
* @return HRESULT or platform status returned by the wrapped API.
*/
virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod(
PCWSTR,
PINT64
);
/**
* @brief Get the share mode for an endpoint.
*
* @return HRESULT or platform status returned by the wrapped API.
*/
virtual HRESULT STDMETHODCALLTYPE GetShareMode(
PCWSTR,
struct DeviceShareMode *
);
/**
* @brief Set the share mode for an endpoint.
*
* @return HRESULT or platform status returned by the wrapped API.
*/
virtual HRESULT STDMETHODCALLTYPE SetShareMode(
PCWSTR,
struct DeviceShareMode *
);
/**
* @brief Get a property value for an endpoint.
*
* @return HRESULT or platform status returned by the wrapped API.
*/
virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(
PCWSTR,
const PROPERTYKEY &,
PROPVARIANT *
);
/**
* @brief Set a property value for an endpoint.
*
* @return HRESULT or platform status returned by the wrapped API.
*/
virtual HRESULT STDMETHODCALLTYPE SetPropertyValue(
PCWSTR,
const PROPERTYKEY &,
PROPVARIANT *
);
/**
* @brief Set the default endpoint for a role.
*
* @param wszDeviceId Endpoint device identifier.
* @param eRole Endpoint role.
* @return HRESULT or platform status returned by the wrapped API.
*/
virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint(
PCWSTR wszDeviceId,
ERole eRole
);
/**
* @brief Set endpoint visibility.
*
* @return HRESULT or platform status returned by the wrapped API.
*/
virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(
PCWSTR,
INT
+234 -22
View File
@@ -26,9 +26,24 @@
#include "PolicyConfig.h"
// clang-format on
#ifdef DOXYGEN
/**
* @brief Property key for a device description.
*/
extern const PROPERTYKEY PKEY_Device_DeviceDesc;
/**
* @brief Property key for a device friendly name.
*/
extern const PROPERTYKEY PKEY_Device_FriendlyName;
/**
* @brief Property key for a device interface friendly name.
*/
extern const PROPERTYKEY PKEY_DeviceInterface_FriendlyName;
#else
DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); // DEVPROP_TYPE_STRING
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING
DEFINE_PROPERTYKEY(PKEY_DeviceInterface_FriendlyName, 0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22, 2);
#endif
#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64)
#define STEAM_DRIVER_SUBDIR L"x64"
@@ -203,27 +218,70 @@ namespace {
using namespace std::literals;
namespace platf::audio {
/**
* @brief Release the COM or platform reference owned by the pointer.
*
* @param p Pointer passed to the deleter or conversion helper.
*/
template<class T>
void Release(T *p) {
p->Release();
}
/**
* @brief Free memory allocated by COM task APIs.
*
* @param p Pointer passed to the deleter or conversion helper.
*/
template<class T>
void co_task_free(T *p) {
CoTaskMemFree((LPVOID) p);
}
/**
* @brief COM device enumerator pointer for WASAPI endpoint discovery.
*/
using device_enum_t = util::safe_ptr<IMMDeviceEnumerator, Release<IMMDeviceEnumerator>>;
/**
* @brief COM pointer to a Windows audio endpoint device.
*/
using device_t = util::safe_ptr<IMMDevice, Release<IMMDevice>>;
/**
* @brief COM pointer to a collection of Windows audio endpoint devices.
*/
using collection_t = util::safe_ptr<IMMDeviceCollection, Release<IMMDeviceCollection>>;
/**
* @brief COM pointer to the WASAPI audio client interface.
*/
using audio_client_t = util::safe_ptr<IAudioClient, Release<IAudioClient>>;
/**
* @brief COM pointer to the WASAPI capture client interface.
*/
using audio_capture_t = util::safe_ptr<IAudioCaptureClient, Release<IAudioCaptureClient>>;
/**
* @brief CoTaskMem-allocated WAVEFORMATEX pointer.
*/
using wave_format_t = util::safe_ptr<WAVEFORMATEX, co_task_free<WAVEFORMATEX>>;
/**
* @brief CoTaskMem-allocated wide string pointer.
*/
using wstring_t = util::safe_ptr<WCHAR, co_task_free<WCHAR>>;
/**
* @brief Windows HANDLE wrapper closed with `CloseHandle`.
*/
using handle_t = util::safe_ptr_v2<void, BOOL, CloseHandle>;
/**
* @brief COM pointer to the Windows policy configuration interface.
*/
using policy_t = util::safe_ptr<IPolicyConfig, Release<IPolicyConfig>>;
/**
* @brief COM pointer to a Windows property store.
*/
using prop_t = util::safe_ptr<IPropertyStore, Release<IPropertyStore>>;
/**
* @brief Initializes COM for the current thread and uninitializes it on exit.
*/
class co_init_t: public deinit_t {
public:
co_init_t() {
@@ -235,6 +293,9 @@ namespace platf::audio {
}
};
/**
* @brief RAII wrapper that initializes and clears a Windows PROPVARIANT.
*/
class prop_var_t {
public:
prop_var_t() {
@@ -245,16 +306,22 @@ namespace platf::audio {
PropVariantClear(&prop);
}
PROPVARIANT prop;
PROPVARIANT prop; ///< Variant value returned by Windows property-store queries.
};
/**
* @brief Windows audio format details selected for capture.
*/
struct format_t {
WORD channel_count;
std::string name;
int capture_waveformat_channel_mask;
virtual_sink_waveformats_t virtual_sink_waveformats;
WORD channel_count; ///< Channel count.
std::string name; ///< Human-readable name for this item.
int capture_waveformat_channel_mask; ///< Capture waveformat channel mask.
virtual_sink_waveformats_t virtual_sink_waveformats; ///< Virtual sink waveformats.
};
/**
* @brief Formats.
*/
const std::array<const format_t, 3> formats = {
format_t {
2,
@@ -276,6 +343,13 @@ namespace platf::audio {
},
};
/**
* @brief Create audio client.
*
* @param device D3D, audio, or platform device used by the operation.
* @param format Pixel, audio, or protocol format being converted.
* @return Constructed audio client object.
*/
audio_client_t make_audio_client(device_t &device, const format_t &format) {
audio_client_t audio_client;
auto status = device->Activate(
@@ -335,6 +409,12 @@ namespace platf::audio {
return audio_client;
}
/**
* @brief Query the default Windows render endpoint.
*
* @param device_enum Windows multimedia device enumerator.
* @return Default render endpoint, or an empty handle if lookup fails.
*/
device_t default_device(device_enum_t &device_enum) {
device_t device;
HRESULT status;
@@ -353,20 +433,40 @@ namespace platf::audio {
return device;
}
/**
* @brief Windows audio endpoint notification callback registered with MMDevice.
*/
class audio_notification_t: public ::IMMNotificationClient {
public:
audio_notification_t() {
}
// IUnknown implementation (unused by IMMDeviceEnumerator)
/**
* @brief Satisfy IUnknown reference counting for the notification callback.
*
* @return Static reference count because the callback lifetime is externally owned.
*/
ULONG STDMETHODCALLTYPE AddRef() {
return 1;
}
/**
* @brief Release the COM or platform reference owned by the pointer.
*
* @return Reference count or status returned after releasing the object.
*/
ULONG STDMETHODCALLTYPE Release() {
return 1;
}
/**
* @brief Return the supported COM interface for the notification callback.
*
* @param riid COM interface identifier requested by QueryInterface.
* @param ppvInterface Output pointer receiving the requested interface.
* @return S_OK when the interface is supported; E_NOINTERFACE otherwise.
*/
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID **ppvInterface) {
if (IID_IUnknown == riid) {
AddRef();
@@ -383,6 +483,14 @@ namespace platf::audio {
}
// IMMNotificationClient
/**
* @brief Handle a Windows default-audio-device change notification.
*
* @param flow Audio endpoint data-flow direction.
* @param role Audio endpoint role used for default-device lookup.
* @param pwstrDeviceId Windows endpoint ID for the new default device.
* @return S_OK after recording the render-device change notification.
*/
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) {
if (flow == eRender) {
default_render_device_changed_flag.store(true);
@@ -390,14 +498,33 @@ namespace platf::audio {
return S_OK;
}
/**
* @brief Ignore endpoint-add notifications.
*
* @param pwstrDeviceId Windows endpoint ID for the added device.
* @return S_OK because Sunshine does not act on this notification.
*/
HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId) {
return S_OK;
}
/**
* @brief Ignore endpoint-removal notifications.
*
* @param pwstrDeviceId Windows endpoint ID for the removed device.
* @return S_OK because Sunshine does not act on this notification.
*/
HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId) {
return S_OK;
}
/**
* @brief Handle Windows audio endpoint state changes.
*
* @param pwstrDeviceId Audio device ID.
* @param dwNewState New device state.
* @return COM status code.
*/
HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(
LPCWSTR pwstrDeviceId,
DWORD dwNewState
@@ -405,6 +532,13 @@ namespace platf::audio {
return S_OK;
}
/**
* @brief Handle Windows audio endpoint property changes.
*
* @param pwstrDeviceId Audio device ID.
* @param key Changed property key.
* @return COM status code.
*/
HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(
LPCWSTR pwstrDeviceId,
const PROPERTYKEY key
@@ -424,8 +558,17 @@ namespace platf::audio {
std::atomic_bool default_render_device_changed_flag;
};
/**
* @brief WASAPI microphone capture stream and endpoint notification state.
*/
class mic_wasapi_t: public mic_t {
public:
/**
* @brief Deliver a captured audio sample to Sunshine's audio pipeline.
*
* @param sample_out Sample out.
* @return Capture status reported to the streaming pipeline.
*/
capture_e sample(std::vector<float> &sample_out) override {
auto sample_size = sample_out.size();
@@ -451,6 +594,15 @@ namespace platf::audio {
return capture_e::ok;
}
/**
* @brief Initialize WASAPI capture for the selected audio endpoint.
*
* @param sample_rate Audio sample rate in hertz.
* @param frame_size Number of samples captured per audio frame.
* @param channels_out Channels out.
* @param continuous Whether silent audio should continue to be emitted.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(std::uint32_t sample_rate, std::uint32_t frame_size, std::uint32_t channels_out, bool continuous) {
audio_event.reset(CreateEventA(nullptr, FALSE, FALSE, nullptr));
if (!audio_event) {
@@ -668,28 +820,36 @@ namespace platf::audio {
}
public:
handle_t audio_event;
handle_t audio_event; ///< Event signaled by WASAPI when captured audio is available.
device_enum_t device_enum;
device_t device;
audio_client_t audio_client;
audio_capture_t audio_capture;
device_enum_t device_enum; ///< Device enum.
device_t device; ///< WASAPI endpoint device selected for capture.
audio_client_t audio_client; ///< WASAPI audio client configured for shared-mode capture.
audio_capture_t audio_capture; ///< WASAPI capture client used to read sample packets.
audio_notification_t endpt_notification;
std::optional<std::function<void()>> default_endpt_changed_cb;
audio_notification_t endpt_notification; ///< Endpoint notification callback registered with Windows.
std::optional<std::function<void()>> default_endpt_changed_cb; ///< Callback invoked when the default endpoint changes.
REFERENCE_TIME default_latency_ms;
REFERENCE_TIME default_latency_ms; ///< WASAPI default device period used as capture latency.
util::buffer_t<float> sample_buf;
float *sample_buf_pos;
int channels;
bool continuous_audio;
util::buffer_t<float> sample_buf; ///< Floating-point sample buffer filled from WASAPI packets.
float *sample_buf_pos; ///< Current write position in `sample_buf`.
int channels; ///< Number of channels in the capture format.
bool continuous_audio; ///< Whether audio packets continue during silence.
HANDLE mmcss_task_handle = nullptr;
HANDLE mmcss_task_handle = nullptr; ///< MMCSS task handle for the audio capture thread.
};
/**
* @brief Platform audio controller that manages sinks and microphone capture.
*/
class audio_control_t: public ::platf::audio_control_t {
public:
/**
* @brief Query host and virtual sink names available to Sunshine.
*
* @return Host and virtual sink names when the backend can report them.
*/
std::optional<sink_t> sink_info() override {
sink_t sink;
@@ -767,6 +927,17 @@ namespace platf::audio {
return std::nullopt;
}
/**
* @brief Create a microphone capture stream for the requested layout.
*
* @param mapping Opus channel mapping table for the requested layout.
* @param channels Number of audio channels in the stream.
* @param sample_rate Audio sample rate in hertz.
* @param frame_size Number of samples captured per audio frame.
* @param continuous_audio Continuous audio.
* @param host_audio_enabled Whether host playback should remain enabled during capture.
* @return Microphone capture object for the requested audio layout.
*/
std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, bool continuous_audio, [[maybe_unused]] bool host_audio_enabled) override {
auto mic = std::make_unique<mic_wasapi_t>();
@@ -793,6 +964,8 @@ namespace platf::audio {
* Any virtual sink detected will be prefixed by:
* virtual-(format name)
* If it doesn't contain that prefix, then the format will not be changed
* @param sink Audio sink name to route or capture.
* @return Status from updating format.
*/
std::optional<std::wstring> set_format(const std::string &sink) {
if (sink.empty()) {
@@ -854,6 +1027,12 @@ namespace platf::audio {
return std::nullopt;
}
/**
* @brief Update the sink value on the backend.
*
* @param sink Audio sink name to route or capture.
* @return Status from updating sink.
*/
int set_sink(const std::string &sink) override {
auto device_id = set_format(sink);
if (!device_id) {
@@ -884,6 +1063,9 @@ namespace platf::audio {
return failure;
}
/**
* @brief Enumerates supported match field options.
*/
enum class match_field_e {
device_id, ///< Match device_id
device_friendly_name, ///< Match endpoint friendly name
@@ -891,15 +1073,32 @@ namespace platf::audio {
device_description, ///< Match endpoint description
};
/**
* @brief List of format fields used to compare audio formats.
*/
using match_fields_list_t = std::vector<std::pair<match_field_e, std::wstring>>;
/**
* @brief One matched audio-format field and its expected value.
*/
using matched_field_t = std::pair<match_field_e, std::wstring>;
/**
* @brief Build matching fields for Steam Streaming Speakers.
*
* @return Field list used to identify Steam's virtual speaker endpoint.
*/
audio_control_t::match_fields_list_t match_steam_speakers() {
return {
{match_field_e::adapter_friendly_name, L"Steam Streaming Speakers"}
};
}
/**
* @brief Build matching fields that all contain the same endpoint name.
*
* @param name Endpoint name or identifier to match across all fields.
* @return Field list requiring every supported endpoint field to match the name.
*/
audio_control_t::match_fields_list_t match_all_fields(const std::wstring &name) {
return {
{match_field_e::device_id, name}, // {0.0.0.00000000}.{29dd7668-45b2-4846-882d-950f55bf7eb8}
@@ -1116,6 +1315,11 @@ namespace platf::audio {
#endif
}
/**
* @brief Initialize Windows audio policy interfaces.
*
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init() {
auto status = CoCreateInstance(
CLSID_CPolicyConfigClient,
@@ -1147,12 +1351,15 @@ namespace platf::audio {
return 0;
}
/**
* @brief Destroy the Windows audio control.
*/
~audio_control_t() override {
}
policy_t policy;
audio::device_enum_t device_enum;
std::string assigned_sink;
policy_t policy; ///< Windows policy configuration interface used to switch default audio devices.
audio::device_enum_t device_enum; ///< Device enumerator used to query and watch audio endpoints.
std::string assigned_sink; ///< Virtual sink assigned while Sunshine captures host audio.
};
} // namespace platf::audio
@@ -1160,8 +1367,13 @@ namespace platf {
// It's not big enough to justify it's own source file :/
namespace dxgi {
/**
* @brief Initialize the Windows audio-control backend.
*
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init();
}
} // namespace dxgi
std::unique_ptr<audio_control_t> audio_control() {
auto control = std::make_unique<audio::audio_control_t>();
+499 -66
View File
@@ -24,69 +24,198 @@ namespace platf::dxgi {
// Add D3D11_CREATE_DEVICE_DEBUG here to enable the D3D11 debug runtime.
// You should have a debugger like WinDbg attached to receive debug messages.
auto constexpr D3D11_CREATE_DEVICE_FLAGS = 0;
auto constexpr D3D11_CREATE_DEVICE_FLAGS = 0; ///< Protocol or platform constant for d3 d11 create device flags.
/**
* @brief Release the COM or platform reference owned by the pointer.
*
* @param dxgi DXGI object whose COM reference should be released.
*/
template<class T>
void Release(T *dxgi) {
dxgi->Release();
}
/**
* @brief Owning COM pointer for the DXGI factory used to enumerate adapters.
*/
using factory1_t = util::safe_ptr<IDXGIFactory1, Release<IDXGIFactory1>>;
/**
* @brief Owning COM pointer for a DXGI device interface.
*/
using dxgi_t = util::safe_ptr<IDXGIDevice, Release<IDXGIDevice>>;
/**
* @brief Owning COM pointer for a DXGI 1.1 device interface.
*/
using dxgi1_t = util::safe_ptr<IDXGIDevice1, Release<IDXGIDevice1>>;
/**
* @brief Owning COM pointer for the D3D11 device used by capture and conversion.
*/
using device_t = util::safe_ptr<ID3D11Device, Release<ID3D11Device>>;
/**
* @brief Owning COM pointer for the D3D11.1 device interface.
*/
using device1_t = util::safe_ptr<ID3D11Device1, Release<ID3D11Device1>>;
/**
* @brief Owning COM pointer for the immediate D3D11 device context.
*/
using device_ctx_t = util::safe_ptr<ID3D11DeviceContext, Release<ID3D11DeviceContext>>;
/**
* @brief Owning COM pointer for a DXGI display adapter.
*/
using adapter_t = util::safe_ptr<IDXGIAdapter1, Release<IDXGIAdapter1>>;
/**
* @brief Owning COM pointer for a DXGI output.
*/
using output_t = util::safe_ptr<IDXGIOutput, Release<IDXGIOutput>>;
/**
* @brief Owning COM pointer for a DXGI output that supports duplication.
*/
using output1_t = util::safe_ptr<IDXGIOutput1, Release<IDXGIOutput1>>;
/**
* @brief Owning COM pointer for a DXGI output with HDR metadata support.
*/
using output5_t = util::safe_ptr<IDXGIOutput5, Release<IDXGIOutput5>>;
/**
* @brief Owning COM pointer for a DXGI output with modern display descriptors.
*/
using output6_t = util::safe_ptr<IDXGIOutput6, Release<IDXGIOutput6>>;
/**
* @brief Owning COM pointer for the DXGI desktop duplication session.
*/
using dup_t = util::safe_ptr<IDXGIOutputDuplication, Release<IDXGIOutputDuplication>>;
/**
* @brief Owning COM pointer for a D3D11 2D texture.
*/
using texture2d_t = util::safe_ptr<ID3D11Texture2D, Release<ID3D11Texture2D>>;
/**
* @brief Owning COM pointer for a D3D11 1D texture.
*/
using texture1d_t = util::safe_ptr<ID3D11Texture1D, Release<ID3D11Texture1D>>;
/**
* @brief Owning COM pointer for the DXGI resource view of a shared texture.
*/
using resource_t = util::safe_ptr<IDXGIResource, Release<IDXGIResource>>;
/**
* @brief Owning COM pointer for the DXGI 1.1 resource sharing interface.
*/
using resource1_t = util::safe_ptr<IDXGIResource1, Release<IDXGIResource1>>;
/**
* @brief Owning COM pointer for the D3D11 multithread-protection interface.
*/
using multithread_t = util::safe_ptr<ID3D11Multithread, Release<ID3D11Multithread>>;
/**
* @brief Owning COM pointer for a D3D11 vertex shader.
*/
using vs_t = util::safe_ptr<ID3D11VertexShader, Release<ID3D11VertexShader>>;
/**
* @brief Owning COM pointer for a D3D11 pixel shader.
*/
using ps_t = util::safe_ptr<ID3D11PixelShader, Release<ID3D11PixelShader>>;
/**
* @brief Owning COM pointer for a D3D11 blend state.
*/
using blend_t = util::safe_ptr<ID3D11BlendState, Release<ID3D11BlendState>>;
/**
* @brief Owning COM pointer for a D3D11 input layout.
*/
using input_layout_t = util::safe_ptr<ID3D11InputLayout, Release<ID3D11InputLayout>>;
/**
* @brief Owning COM pointer for a D3D11 render-target view.
*/
using render_target_t = util::safe_ptr<ID3D11RenderTargetView, Release<ID3D11RenderTargetView>>;
/**
* @brief Owning COM pointer for a D3D11 shader-resource view.
*/
using shader_res_t = util::safe_ptr<ID3D11ShaderResourceView, Release<ID3D11ShaderResourceView>>;
/**
* @brief Owning COM pointer for a D3D11 buffer.
*/
using buf_t = util::safe_ptr<ID3D11Buffer, Release<ID3D11Buffer>>;
/**
* @brief D3D rasterizer state handle type.
*/
using raster_state_t = util::safe_ptr<ID3D11RasterizerState, Release<ID3D11RasterizerState>>;
/**
* @brief D3D sampler state handle type.
*/
using sampler_state_t = util::safe_ptr<ID3D11SamplerState, Release<ID3D11SamplerState>>;
/**
* @brief Owning COM pointer for a compiled shader bytecode blob.
*/
using blob_t = util::safe_ptr<ID3DBlob, Release<ID3DBlob>>;
/**
* @brief D3D depth-stencil state handle type.
*/
using depth_stencil_state_t = util::safe_ptr<ID3D11DepthStencilState, Release<ID3D11DepthStencilState>>;
/**
* @brief Owning COM pointer for a D3D11 depth-stencil view.
*/
using depth_stencil_view_t = util::safe_ptr<ID3D11DepthStencilView, Release<ID3D11DepthStencilView>>;
/**
* @brief Owning COM pointer for a keyed mutex on a shared DXGI resource.
*/
using keyed_mutex_t = util::safe_ptr<IDXGIKeyedMutex, Release<IDXGIKeyedMutex>>;
namespace video {
/**
* @brief Owning COM pointer for the D3D11 video device interface.
*/
using device_t = util::safe_ptr<ID3D11VideoDevice, Release<ID3D11VideoDevice>>;
/**
* @brief Owning COM pointer for the D3D11 video context interface.
*/
using ctx_t = util::safe_ptr<ID3D11VideoContext, Release<ID3D11VideoContext>>;
/**
* @brief Owning COM pointer for a D3D11 video processor.
*/
using processor_t = util::safe_ptr<ID3D11VideoProcessor, Release<ID3D11VideoProcessor>>;
/**
* @brief Owning COM pointer for a video processor output view.
*/
using processor_out_t = util::safe_ptr<ID3D11VideoProcessorOutputView, Release<ID3D11VideoProcessorOutputView>>;
/**
* @brief Owning COM pointer for a video processor input view.
*/
using processor_in_t = util::safe_ptr<ID3D11VideoProcessorInputView, Release<ID3D11VideoProcessorInputView>>;
/**
* @brief Owning COM pointer for a video processor capability enumerator.
*/
using processor_enum_t = util::safe_ptr<ID3D11VideoProcessorEnumerator, Release<ID3D11VideoProcessorEnumerator>>;
} // namespace video
class hwdevice_t;
/**
* @brief Cursor position and visibility for the current capture frame.
*/
struct cursor_t {
std::vector<std::uint8_t> img_data;
std::vector<std::uint8_t> img_data; ///< Raw pointer-shape bytes from DXGI output duplication.
DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info;
int x;
int y;
bool visible;
DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info; ///< Shape info.
int x; ///< X.
int y; ///< Y.
bool visible; ///< Whether the cursor is visible.
};
/**
* @brief GPU resources used to render a captured cursor shape.
*/
class gpu_cursor_t {
public:
gpu_cursor_t():
cursor_view {0, 0, 0, 0, 0.0f, 1.0f} {};
/**
* @brief Update the cursor position and display geometry used for rendering.
*
* @param topleft_x Cursor left edge in desktop coordinates.
* @param topleft_y Cursor top edge in desktop coordinates.
* @param display_width Captured display width in pixels.
* @param display_height Captured display height in pixels.
* @param display_rotation DXGI rotation applied to the captured display.
* @param visible Whether the cursor should be visible in the frame.
*/
void set_pos(LONG topleft_x, LONG topleft_y, LONG display_width, LONG display_height, DXGI_MODE_ROTATION display_rotation, bool visible) {
this->topleft_x = topleft_x;
this->topleft_y = topleft_y;
@@ -97,6 +226,13 @@ namespace platf::dxgi {
update_viewport();
}
/**
* @brief Replace the cursor texture and update its render viewport.
*
* @param texture_width Cursor texture width in pixels.
* @param texture_height Cursor texture height in pixels.
* @param texture D3D11 cursor texture to render.
*/
void set_texture(LONG texture_width, LONG texture_height, texture2d_t &&texture) {
this->texture = std::move(texture);
this->texture_width = texture_width;
@@ -104,6 +240,9 @@ namespace platf::dxgi {
update_viewport();
}
/**
* @brief Recalculate the D3D viewport for the cursor texture.
*/
void update_viewport() {
switch (display_rotation) {
case DXGI_MODE_ROTATION_UNSPECIFIED:
@@ -137,50 +276,63 @@ namespace platf::dxgi {
}
}
texture2d_t texture;
LONG texture_width;
LONG texture_height;
texture2d_t texture; ///< D3D11 texture backing the captured frame.
LONG texture_width; ///< Texture width.
LONG texture_height; ///< Texture height.
LONG topleft_x;
LONG topleft_y;
LONG topleft_x; ///< Topleft x.
LONG topleft_y; ///< Topleft y.
LONG display_width;
LONG display_height;
DXGI_MODE_ROTATION display_rotation;
LONG display_width; ///< Display width.
LONG display_height; ///< Display height.
DXGI_MODE_ROTATION display_rotation; ///< Display rotation.
shader_res_t input_res;
shader_res_t input_res; ///< Input res.
D3D11_VIEWPORT cursor_view;
D3D11_VIEWPORT cursor_view; ///< Cursor view.
bool visible;
bool visible; ///< Whether the cursor is visible.
};
/**
* @brief Shared D3D11/DXGI state used by Windows display capture backends.
*/
class display_base_t: public display_t {
public:
/**
* @brief Initialize D3D11 desktop duplication for the selected output.
*
* @param config Configuration values to apply.
* @param display_name Display name.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(const ::video::config_t &config, const std::string &display_name);
capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override;
factory1_t factory;
adapter_t adapter;
output_t output;
device_t device;
device_ctx_t device_ctx;
DXGI_RATIONAL display_refresh_rate;
int display_refresh_rate_rounded;
factory1_t factory; ///< DXGI factory used to enumerate adapters and outputs.
adapter_t adapter; ///< DXGI adapter containing the selected output.
output_t output; ///< DXGI output duplicated for capture.
device_t device; ///< D3D11 device used for desktop duplication capture.
device_ctx_t device_ctx; ///< D3D11 device context used for copy and render operations.
DXGI_RATIONAL display_refresh_rate; ///< Display refresh rate.
int display_refresh_rate_rounded; ///< Display refresh rate rounded.
DXGI_MODE_ROTATION display_rotation = DXGI_MODE_ROTATION_UNSPECIFIED;
int width_before_rotation;
int height_before_rotation;
DXGI_MODE_ROTATION display_rotation = DXGI_MODE_ROTATION_UNSPECIFIED; ///< Display rotation.
int width_before_rotation; ///< Width before rotation.
int height_before_rotation; ///< Height before rotation.
int client_frame_rate;
DXGI_RATIONAL client_frame_rate_strict;
int client_frame_rate; ///< Client frame rate.
DXGI_RATIONAL client_frame_rate_strict; ///< Client frame rate strict.
DXGI_FORMAT capture_format;
D3D_FEATURE_LEVEL feature_level;
DXGI_FORMAT capture_format; ///< Capture format.
D3D_FEATURE_LEVEL feature_level; ///< Feature level.
std::unique_ptr<high_precision_timer> timer = create_high_precision_timer();
std::unique_ptr<high_precision_timer> timer = create_high_precision_timer(); ///< Timer.
/**
* @brief Enumerates supported d3 DKMT SCHEDULINGPRIORITYCLASS options.
*/
typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS {
D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE, ///< Idle priority class
D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL, ///< Below normal priority class
@@ -188,15 +340,24 @@ namespace platf::dxgi {
D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL, ///< Above normal priority class
D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH, ///< High priority class
D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME ///< Realtime priority class
} D3DKMT_SCHEDULINGPRIORITYCLASS;
} D3DKMT_SCHEDULINGPRIORITYCLASS; ///< Alias for D3 DKMT SCHEDULINGPRIORITYCLASS.
/**
* @brief Kernel-mode D3DKMT adapter handle.
*/
typedef UINT D3DKMT_HANDLE;
/**
* @brief Win32 D3DKMT adapter-open request structure.
*/
typedef struct _D3DKMT_OPENADAPTERFROMLUID {
LUID AdapterLuid;
D3DKMT_HANDLE hAdapter;
} D3DKMT_OPENADAPTERFROMLUID;
LUID AdapterLuid; ///< Adapter luid.
D3DKMT_HANDLE hAdapter; ///< D3DKMT adapter handle used for low-level display queries.
} D3DKMT_OPENADAPTERFROMLUID; ///< Alias for D3 DKMT OPENADAPTERFROMLUID.
/**
* @brief Win32 D3DKMT WDDM 2.7 capability flags.
*/
typedef struct _D3DKMT_WDDM_2_7_CAPS {
union {
struct
@@ -210,40 +371,112 @@ namespace platf::dxgi {
UINT Value;
};
} D3DKMT_WDDM_2_7_CAPS;
} D3DKMT_WDDM_2_7_CAPS; ///< Alias for D3 DKMT WDDM 2 7 CAPS.
/**
* @brief Win32 D3DKMT adapter information query.
*/
typedef struct _D3DKMT_QUERYADAPTERINFO {
D3DKMT_HANDLE hAdapter;
UINT Type;
VOID *pPrivateDriverData;
UINT PrivateDriverDataSize;
} D3DKMT_QUERYADAPTERINFO;
D3DKMT_HANDLE hAdapter; ///< D3DKMT adapter handle used for low-level display queries.
UINT Type; ///< Type.
VOID *pPrivateDriverData; ///< P private driver data.
UINT PrivateDriverDataSize; ///< Private driver data size.
} D3DKMT_QUERYADAPTERINFO; ///< Alias for D3 DKMT QUERYADAPTERINFO.
const UINT KMTQAITYPE_WDDM_2_7_CAPS = 70;
const UINT KMTQAITYPE_WDDM_2_7_CAPS = 70; ///< Protocol or platform constant for kmtqaitype wddm 2 7 caps.
/**
* @brief Win32 D3DKMT adapter-close request structure.
*/
typedef struct _D3DKMT_CLOSEADAPTER {
D3DKMT_HANDLE hAdapter;
} D3DKMT_CLOSEADAPTER;
D3DKMT_HANDLE hAdapter; ///< D3DKMT adapter handle used for low-level display queries.
} D3DKMT_CLOSEADAPTER; ///< Alias for D3 DKMT CLOSEADAPTER.
/**
* @brief Function pointer for setting D3DKMT process scheduling priority.
*/
typedef NTSTATUS(WINAPI *PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS);
/**
* @brief Function pointer for opening a D3DKMT adapter by LUID.
*/
typedef NTSTATUS(WINAPI *PD3DKMTOpenAdapterFromLuid)(D3DKMT_OPENADAPTERFROMLUID *);
/**
* @brief Function pointer for querying D3DKMT adapter information.
*/
typedef NTSTATUS(WINAPI *PD3DKMTQueryAdapterInfo)(D3DKMT_QUERYADAPTERINFO *);
/**
* @brief Function pointer for closing a D3DKMT adapter handle.
*/
typedef NTSTATUS(WINAPI *PD3DKMTCloseAdapter)(D3DKMT_CLOSEADAPTER *);
/**
* @brief Report whether the active display mode is HDR.
*
* @return True when the active display mode is HDR.
*/
virtual bool is_hdr() override;
/**
* @brief Read HDR metadata for the active display mode.
*
* @param metadata Output structure populated with HDR metadata.
* @return True when HDR metadata was written to `metadata`.
*/
virtual bool get_hdr_metadata(SS_HDR_METADATA &metadata) override;
/**
* @brief Convert a DXGI format enum to a diagnostic string.
*
* @param format Pixel, audio, or protocol format being converted.
* @return Static string describing the DXGI format.
*/
const char *dxgi_format_to_string(DXGI_FORMAT format);
/**
* @brief Convert a DXGI colorspace enum to a diagnostic string.
*
* @param type DXGI colorspace value reported by the output.
* @return Static string describing the colorspace.
*/
const char *colorspace_to_string(DXGI_COLOR_SPACE_TYPE type);
/**
* @brief Get supported capture formats.
*
* @return Capture formats supported by this display backend.
*/
virtual std::vector<DXGI_FORMAT> get_supported_capture_formats() = 0;
protected:
/**
* @brief Get pixel pitch.
*
* @return Bytes per pixel for the active capture format.
*/
int get_pixel_pitch() {
return (capture_format == DXGI_FORMAT_R16G16B16A16_FLOAT) ? 8 : 4;
}
/**
* @brief Capture a display frame into the provided image object.
*
* @param pull_free_image_cb Callback that provides an available image buffer.
* @param img_out Captured image buffer returned to the streaming pipeline.
* @param timeout Maximum time to wait for the operation.
* @param cursor_visible Cursor visible.
* @return Capture status reported to the streaming pipeline.
*/
virtual capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) = 0;
/**
* @brief Release resources associated with the last captured snapshot.
*
* @return Capture status after releasing the current snapshot.
*/
virtual capture_e release_snapshot() = 0;
/**
* @brief Finish cursor composition into a RAM-backed image.
*
* @param img Image or frame object to read from or populate.
* @param dummy Unused placeholder required by the interface signature.
* @return Capture status after finalizing the captured image.
*/
virtual int complete_img(img_t *img, bool dummy) = 0;
};
@@ -252,15 +485,44 @@ namespace platf::dxgi {
*/
class display_ram_t: public display_base_t {
public:
/**
* @brief Allocate an image buffer compatible with this display backend.
*
* @return Allocated img object, or null when unavailable.
*/
std::shared_ptr<img_t> alloc_img() override;
/**
* @brief Populate a fallback image when real capture data is unavailable.
*
* @param img Image or frame object to read from or populate.
* @return Capture status reported to the streaming pipeline.
*/
int dummy_img(img_t *img) override;
/**
* @brief Finish cursor composition into a GPU-backed image.
*
* @param img Image or frame object to read from or populate.
* @param dummy Unused placeholder required by the interface signature.
* @return Capture status after finalizing the captured image.
*/
int complete_img(img_t *img, bool dummy) override;
/**
* @brief Get supported capture formats.
*
* @return Capture formats supported by this display backend.
*/
std::vector<DXGI_FORMAT> get_supported_capture_formats() override;
/**
* @brief Create AVCodec encode device.
*
* @param pix_fmt Sunshine pixel format to convert or allocate for.
* @return Constructed AVCodec encode device object.
*/
std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override;
D3D11_MAPPED_SUBRESOURCE img_info;
texture2d_t texture;
D3D11_MAPPED_SUBRESOURCE img_info; ///< CPU mapping information for the captured texture.
texture2d_t texture; ///< D3D11 texture backing the captured frame.
};
/**
@@ -268,18 +530,53 @@ namespace platf::dxgi {
*/
class display_vram_t: public display_base_t, public std::enable_shared_from_this<display_vram_t> {
public:
/**
* @brief Allocate an image buffer compatible with this display backend.
*
* @return Allocated img object, or null when unavailable.
*/
std::shared_ptr<img_t> alloc_img() override;
/**
* @brief Populate a fallback image when real capture data is unavailable.
*
* @param img_base Image buffer populated with a fallback frame.
* @return Capture status reported to the streaming pipeline.
*/
int dummy_img(img_t *img_base) override;
/**
* @brief Finalize a VRAM image after capture or dummy-frame generation.
*
* @param img_base Image buffer whose D3D resources are finalized.
* @param dummy Unused placeholder required by the interface signature.
* @return Capture status after finalizing the captured image.
*/
int complete_img(img_t *img_base, bool dummy) override;
/**
* @brief Get supported capture formats.
*
* @return Capture formats supported by this display backend.
*/
std::vector<DXGI_FORMAT> get_supported_capture_formats() override;
bool is_codec_supported(std::string_view name, const ::video::config_t &config) override;
/**
* @brief Create AVCodec encode device.
*
* @param pix_fmt Sunshine pixel format to convert or allocate for.
* @return Constructed AVCodec encode device object.
*/
std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override;
/**
* @brief Create NVENC encode device.
*
* @param pix_fmt Sunshine pixel format to convert or allocate for.
* @return Constructed NVENC encode device object.
*/
std::unique_ptr<nvenc_encode_device_t> make_nvenc_encode_device(pix_fmt_e pix_fmt) override;
std::atomic<uint32_t> next_image_id;
std::atomic<uint32_t> next_image_id; ///< Next image ID.
};
/**
@@ -287,13 +584,39 @@ namespace platf::dxgi {
*/
class duplication_t {
public:
dup_t dup;
bool has_frame {};
std::chrono::steady_clock::time_point last_protected_content_warning_time {};
dup_t dup; ///< Desktop Duplication capture session.
bool has_frame {}; ///< Whether a frame is currently held by the duplication object.
std::chrono::steady_clock::time_point last_protected_content_warning_time {}; ///< Last protected content warning time.
/**
* @brief Initialize WGC capture for the selected display.
*
* @param display Display object or identifier associated with the operation.
* @param config Configuration values to apply.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(display_base_t *display, const ::video::config_t &config);
/**
* @brief Acquire the next frame from the Windows capture backend.
*
* @param frame_info Frame info.
* @param timeout Maximum time to wait for the operation.
* @param res_p Res p.
* @return Capture status for the frame acquisition attempt.
*/
capture_e next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p);
/**
* @brief Reset the object to its initial empty state.
*
* @param dup_p Dup p.
* @return Reset status.
*/
capture_e reset(dup_t::pointer dup_p = dup_t::pointer());
/**
* @brief Release resources associated with frame.
*
* @return Capture status after releasing the current frame.
*/
capture_e release_frame();
~duplication_t();
@@ -304,12 +627,33 @@ namespace platf::dxgi {
*/
class display_ddup_ram_t: public display_ram_t {
public:
/**
* @brief Initialize shared D3D capture resources for a RAM frame.
*
* @param config Configuration values to apply.
* @param display_name Display name.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(const ::video::config_t &config, const std::string &display_name);
/**
* @brief Capture a display frame into the provided image object.
*
* @param pull_free_image_cb Callback that provides an available image buffer.
* @param img_out Captured image buffer returned to the streaming pipeline.
* @param timeout Maximum time to wait for the operation.
* @param cursor_visible Cursor visible.
* @return Capture status reported to the streaming pipeline.
*/
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override;
/**
* @brief Release resources associated with the last captured snapshot.
*
* @return Capture status after releasing the current snapshot.
*/
capture_e release_snapshot() override;
duplication_t dup;
cursor_t cursor;
duplication_t dup; ///< Desktop Duplication session used to acquire frames.
cursor_t cursor; ///< Cursor.
};
/**
@@ -317,26 +661,47 @@ namespace platf::dxgi {
*/
class display_ddup_vram_t: public display_vram_t {
public:
/**
* @brief Initialize D3D cursor rendering resources for RAM capture.
*
* @param config Configuration values to apply.
* @param display_name Display name.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(const ::video::config_t &config, const std::string &display_name);
/**
* @brief Capture a display frame into the provided image object.
*
* @param pull_free_image_cb Callback that provides an available image buffer.
* @param img_out Captured image buffer returned to the streaming pipeline.
* @param timeout Maximum time to wait for the operation.
* @param cursor_visible Cursor visible.
* @return Capture status reported to the streaming pipeline.
*/
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override;
/**
* @brief Release resources associated with the last captured snapshot.
*
* @return Capture status after releasing the current snapshot.
*/
capture_e release_snapshot() override;
duplication_t dup;
sampler_state_t sampler_linear;
duplication_t dup; ///< Desktop Duplication session used to acquire frames.
sampler_state_t sampler_linear; ///< Sampler linear.
blend_t blend_alpha;
blend_t blend_invert;
blend_t blend_disable;
blend_t blend_alpha; ///< Blend alpha.
blend_t blend_invert; ///< Blend invert.
blend_t blend_disable; ///< Blend disable.
ps_t cursor_ps;
vs_t cursor_vs;
ps_t cursor_ps; ///< Cursor ps.
vs_t cursor_vs; ///< Cursor vs.
gpu_cursor_t cursor_alpha;
gpu_cursor_t cursor_xor;
gpu_cursor_t cursor_alpha; ///< Cursor alpha.
gpu_cursor_t cursor_xor; ///< Cursor xor.
texture2d_t old_surface_delayed_destruction;
std::chrono::steady_clock::time_point old_surface_timestamp;
std::variant<std::monostate, texture2d_t, std::shared_ptr<platf::img_t>> last_frame_variant;
texture2d_t old_surface_delayed_destruction; ///< Old surface delayed destruction.
std::chrono::steady_clock::time_point old_surface_timestamp; ///< Old surface timestamp.
std::variant<std::monostate, texture2d_t, std::shared_ptr<platf::img_t>> last_frame_variant; ///< Last frame variant.
};
/**
@@ -358,10 +723,36 @@ namespace platf::dxgi {
wgc_capture_t();
~wgc_capture_t();
/**
* @brief Initialize D3D cursor rendering resources for GPU capture.
*
* @param display Display object or identifier associated with the operation.
* @param config Configuration values to apply.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(display_base_t *display, const ::video::config_t &config);
/**
* @brief Acquire the next frame from the Windows capture backend.
*
* @param timeout Maximum time to wait for the operation.
* @param out Output object populated by the operation.
* @param out_time QPC timestamp associated with the acquired frame.
* @return Capture status for the frame acquisition attempt.
*/
capture_e next_frame(std::chrono::milliseconds timeout, ID3D11Texture2D **out, uint64_t &out_time);
/**
* @brief Release resources associated with frame.
*
* @return Capture status after releasing the current frame.
*/
capture_e release_frame();
int set_cursor_visible(bool);
/**
* @brief Enable or disable cursor composition in Windows.Graphics.Capture frames.
*
* @param cursor_visible Whether the cursor should be included in captured frames.
* @return Zero when the cursor visibility state was accepted by the capture session.
*/
int set_cursor_visible(bool cursor_visible);
};
/**
@@ -371,8 +762,29 @@ namespace platf::dxgi {
wgc_capture_t dup;
public:
/**
* @brief Initialize shared D3D capture resources for a GPU frame.
*
* @param config Configuration values to apply.
* @param display_name Display name.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(const ::video::config_t &config, const std::string &display_name);
/**
* @brief Capture a display frame into the provided image object.
*
* @param pull_free_image_cb Callback that provides an available image buffer.
* @param img_out Captured image buffer returned to the streaming pipeline.
* @param timeout Maximum time to wait for the operation.
* @param cursor_visible Cursor visible.
* @return Capture status reported to the streaming pipeline.
*/
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override;
/**
* @brief Release resources associated with the last captured snapshot.
*
* @return Capture status after releasing the current snapshot.
*/
capture_e release_snapshot() override;
};
@@ -383,8 +795,29 @@ namespace platf::dxgi {
wgc_capture_t dup;
public:
/**
* @brief Initialize Windows Graphics Capture frame-pool resources.
*
* @param config Configuration values to apply.
* @param display_name Display name.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(const ::video::config_t &config, const std::string &display_name);
/**
* @brief Capture a display frame into the provided image object.
*
* @param pull_free_image_cb Callback that provides an available image buffer.
* @param img_out Captured image buffer returned to the streaming pipeline.
* @param timeout Maximum time to wait for the operation.
* @param cursor_visible Cursor visible.
* @return Capture status reported to the streaming pipeline.
*/
capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override;
/**
* @brief Release resources associated with the last captured snapshot.
*
* @return Capture status after releasing the current snapshot.
*/
capture_e release_snapshot() override;
};
} // namespace platf::dxgi
+14 -2
View File
@@ -19,9 +19,15 @@
// We have to include boost/process/v1.hpp before display.h due to WinSock.h,
// but that prevents the definition of NTSTATUS so we must define it ourself.
/**
* @brief Windows NT status code returned by native APIs.
*/
typedef long NTSTATUS;
// Definition from the WDK's d3dkmthk.h
/**
* @brief Enumerates supported d3 DKMT GPU PREFERENCE QUERY STATE options.
*/
typedef enum _D3DKMT_GPU_PREFERENCE_QUERY_STATE: DWORD {
D3DKMT_GPU_PREFERENCE_STATE_UNINITIALIZED, ///< The GPU preference isn't initialized.
D3DKMT_GPU_PREFERENCE_STATE_HIGH_PERFORMANCE, ///< The highest performing GPU is preferred.
@@ -29,7 +35,7 @@ typedef enum _D3DKMT_GPU_PREFERENCE_QUERY_STATE: DWORD {
D3DKMT_GPU_PREFERENCE_STATE_UNSPECIFIED, ///< A GPU preference isn't specified.
D3DKMT_GPU_PREFERENCE_STATE_NOT_FOUND, ///< A GPU preference isn't found.
D3DKMT_GPU_PREFERENCE_STATE_USER_SPECIFIED_GPU ///< A specific GPU is preferred.
} D3DKMT_GPU_PREFERENCE_QUERY_STATE;
} D3DKMT_GPU_PREFERENCE_QUERY_STATE; ///< Alias for D3 DKMT GPU PREFERENCE QUERY STATE.
#include "display.h"
#include "misc.h"
@@ -354,6 +360,8 @@ namespace platf::dxgi {
* @param adapter The DXGI adapter to use for capture.
* @param output The DXGI output to capture.
* @param enumeration_only Specifies whether this test is occurring for display enumeration.
*
* @return True when Desktop Duplication can capture the requested output.
*/
bool test_dxgi_duplication(adapter_t &adapter, output_t &output, bool enumeration_only) {
D3D_FEATURE_LEVEL featureLevels[] {
@@ -819,6 +827,9 @@ namespace platf::dxgi {
return true;
}
/**
* @brief Names for DXGI_FORMAT values used in diagnostic logging.
*/
const char *format_str[] = {
"DXGI_FORMAT_UNKNOWN",
"DXGI_FORMAT_R32G32B32A32_TYPELESS",
@@ -1109,7 +1120,8 @@ namespace platf {
}
/**
* @brief Returns if GPUs/drivers have changed since the last call to this function.
* @brief Check whether DXGI reports that adapter or driver enumeration is stale.
*
* @return `true` if a change has occurred or if it is unknown whether a change occurred.
*/
bool needs_encoder_reenumeration() {
+37
View File
@@ -12,13 +12,25 @@ namespace platf {
}
namespace platf::dxgi {
/**
* @brief Captured frame buffer shared between capture and encode stages.
*/
struct img_t: public ::platf::img_t {
/**
* @brief Destroy the RAM-backed DXGI image.
*/
~img_t() override {
delete[] data;
data = nullptr;
}
};
/**
* @brief Blend a monochrome cursor shape into a RAM frame.
*
* @param cursor Cursor image or visibility state to composite.
* @param img Image or frame object to read from or populate.
*/
void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) {
int height = cursor.shape_info.Height / 2;
int width = cursor.shape_info.Width;
@@ -81,6 +93,12 @@ namespace platf::dxgi {
}
}
/**
* @brief Alpha-blend a color cursor pixel into a captured RAM frame.
*
* @param img_pixel_p Pixel in the destination frame.
* @param cursor_pixel ARGB cursor pixel to blend.
*/
void apply_color_alpha(int *img_pixel_p, int cursor_pixel) {
auto colors_out = (std::uint8_t *) &cursor_pixel;
auto colors_in = (std::uint8_t *) img_pixel_p;
@@ -96,6 +114,12 @@ namespace platf::dxgi {
}
}
/**
* @brief Apply a masked-color cursor pixel into a captured RAM frame.
*
* @param img_pixel_p Pixel in the destination frame.
* @param cursor_pixel ARGB cursor pixel with masked-cursor semantics.
*/
void apply_color_masked(int *img_pixel_p, int cursor_pixel) {
// TODO: When use of IDXGIOutput5 is implemented, support different color formats
auto alpha = ((std::uint8_t *) &cursor_pixel)[3];
@@ -106,6 +130,13 @@ namespace platf::dxgi {
}
}
/**
* @brief Blend a color cursor shape into a RAM frame.
*
* @param cursor Cursor image or visibility state to composite.
* @param img Image or frame object to read from or populate.
* @param masked Whether the cursor image uses an AND/XOR mask.
*/
void blend_cursor_color(const cursor_t &cursor, img_t &img, const bool masked) {
int height = cursor.shape_info.Height;
int width = cursor.shape_info.Width;
@@ -152,6 +183,12 @@ namespace platf::dxgi {
}
}
/**
* @brief Blend the current cursor image into a RAM frame.
*
* @param cursor Cursor image or visibility state to composite.
* @param img Image or frame object to read from or populate.
*/
void blend_cursor(const cursor_t &cursor, img_t &img) {
switch (cursor.shape_info.Type) {
case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR:
+293 -84
View File
@@ -31,6 +31,10 @@ extern "C" {
#include "utf_utils.h"
#if !defined(SUNSHINE_SHADERS_DIR) // for testing this needs to be defined in cmake as we don't do an install
/**
* @def SUNSHINE_SHADERS_DIR
* @brief Macro for SUNSHINE SHADERS DIR.
*/
#define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders/directx"
#endif
namespace platf {
@@ -41,10 +45,20 @@ static void free_frame(AVFrame *frame) {
av_frame_free(&frame);
}
/**
* @brief FFmpeg hardware frame pointer released with `av_frame_free`.
*/
using frame_t = util::safe_ptr<AVFrame, free_frame>;
namespace platf::dxgi {
/**
* @brief Create a buffer object or message.
*
* @param device D3D, audio, or platform device used by the operation.
* @param t Initial value used to populate the GPU buffer.
* @return Constructed buffer object.
*/
template<class T>
buf_t make_buffer(device_t::pointer device, const T &t) {
static_assert(sizeof(T) % 16 == 0, "Buffer needs to be aligned on a 16-byte alignment");
@@ -69,6 +83,14 @@ namespace platf::dxgi {
return buf_t {buf_p};
}
/**
* @brief Create a blend object or message.
*
* @param device D3D, audio, or platform device used by the operation.
* @param enable Whether the blend state should enable blending.
* @param invert Whether the blend state should invert the color channels.
* @return Constructed blend object.
*/
blend_t make_blend(device_t::pointer device, bool enable, bool invert) {
D3D11_BLEND_DESC bdesc {};
auto &rt = bdesc.RenderTarget[0];
@@ -103,53 +125,56 @@ namespace platf::dxgi {
return blend;
}
blob_t convert_yuv420_packed_uv_type0_ps_hlsl;
blob_t convert_yuv420_packed_uv_type0_ps_linear_hlsl;
blob_t convert_yuv420_packed_uv_type0_ps_perceptual_quantizer_hlsl;
blob_t convert_yuv420_packed_uv_type0_vs_hlsl;
blob_t convert_yuv420_packed_uv_type0s_ps_hlsl;
blob_t convert_yuv420_packed_uv_type0s_ps_linear_hlsl;
blob_t convert_yuv420_packed_uv_type0s_ps_perceptual_quantizer_hlsl;
blob_t convert_yuv420_packed_uv_type0s_vs_hlsl;
blob_t convert_yuv420_planar_y_ps_hlsl;
blob_t convert_yuv420_planar_y_ps_linear_hlsl;
blob_t convert_yuv420_planar_y_ps_perceptual_quantizer_hlsl;
blob_t convert_yuv420_planar_y_vs_hlsl;
blob_t convert_yuv444_packed_ayuv_ps_hlsl;
blob_t convert_yuv444_packed_ayuv_ps_linear_hlsl;
blob_t convert_yuv444_packed_vs_hlsl;
blob_t convert_yuv444_planar_ps_hlsl;
blob_t convert_yuv444_planar_ps_linear_hlsl;
blob_t convert_yuv444_planar_ps_perceptual_quantizer_hlsl;
blob_t convert_yuv444_packed_y410_ps_hlsl;
blob_t convert_yuv444_packed_y410_ps_linear_hlsl;
blob_t convert_yuv444_packed_y410_ps_perceptual_quantizer_hlsl;
blob_t convert_yuv444_planar_vs_hlsl;
blob_t cursor_ps_hlsl;
blob_t cursor_ps_normalize_white_hlsl;
blob_t cursor_vs_hlsl;
blob_t convert_yuv420_packed_uv_type0_ps_hlsl; ///< Convert yuv420 packed uv type0 ps hlsl.
blob_t convert_yuv420_packed_uv_type0_ps_linear_hlsl; ///< Convert yuv420 packed uv type0 ps linear hlsl.
blob_t convert_yuv420_packed_uv_type0_ps_perceptual_quantizer_hlsl; ///< Convert yuv420 packed uv type0 ps perceptual quantizer hlsl.
blob_t convert_yuv420_packed_uv_type0_vs_hlsl; ///< Convert yuv420 packed uv type0 vs hlsl.
blob_t convert_yuv420_packed_uv_type0s_ps_hlsl; ///< Convert yuv420 packed uv type0s ps hlsl.
blob_t convert_yuv420_packed_uv_type0s_ps_linear_hlsl; ///< Convert yuv420 packed uv type0s ps linear hlsl.
blob_t convert_yuv420_packed_uv_type0s_ps_perceptual_quantizer_hlsl; ///< Convert yuv420 packed uv type0s ps perceptual quantizer hlsl.
blob_t convert_yuv420_packed_uv_type0s_vs_hlsl; ///< Convert yuv420 packed uv type0s vs hlsl.
blob_t convert_yuv420_planar_y_ps_hlsl; ///< Convert yuv420 planar y ps hlsl.
blob_t convert_yuv420_planar_y_ps_linear_hlsl; ///< Convert yuv420 planar y ps linear hlsl.
blob_t convert_yuv420_planar_y_ps_perceptual_quantizer_hlsl; ///< Convert yuv420 planar y ps perceptual quantizer hlsl.
blob_t convert_yuv420_planar_y_vs_hlsl; ///< Convert yuv420 planar y vs hlsl.
blob_t convert_yuv444_packed_ayuv_ps_hlsl; ///< Convert YUV444 packed ayuv ps hlsl.
blob_t convert_yuv444_packed_ayuv_ps_linear_hlsl; ///< Convert YUV444 packed ayuv ps linear hlsl.
blob_t convert_yuv444_packed_vs_hlsl; ///< Convert YUV444 packed vs hlsl.
blob_t convert_yuv444_planar_ps_hlsl; ///< Convert YUV444 planar ps hlsl.
blob_t convert_yuv444_planar_ps_linear_hlsl; ///< Convert YUV444 planar ps linear hlsl.
blob_t convert_yuv444_planar_ps_perceptual_quantizer_hlsl; ///< Convert YUV444 planar ps perceptual quantizer hlsl.
blob_t convert_yuv444_packed_y410_ps_hlsl; ///< Convert YUV444 packed y410 ps hlsl.
blob_t convert_yuv444_packed_y410_ps_linear_hlsl; ///< Convert YUV444 packed y410 ps linear hlsl.
blob_t convert_yuv444_packed_y410_ps_perceptual_quantizer_hlsl; ///< Convert YUV444 packed y410 ps perceptual quantizer hlsl.
blob_t convert_yuv444_planar_vs_hlsl; ///< Convert YUV444 planar vs hlsl.
blob_t cursor_ps_hlsl; ///< Cursor ps hlsl.
blob_t cursor_ps_normalize_white_hlsl; ///< Cursor ps normalize white hlsl.
blob_t cursor_vs_hlsl; ///< Cursor vs hlsl.
/**
* @brief D3D-backed captured image and duplication metadata.
*/
struct img_d3d_t: public platf::img_t {
// These objects are owned by the display_t's ID3D11Device
texture2d_t capture_texture;
render_target_t capture_rt;
keyed_mutex_t capture_mutex;
texture2d_t capture_texture; ///< Capture texture.
render_target_t capture_rt; ///< Capture rt.
keyed_mutex_t capture_mutex; ///< Capture mutex.
// This is the shared handle used by hwdevice_t to open capture_texture
HANDLE encoder_texture_handle = {};
HANDLE encoder_texture_handle = {}; ///< Encoder texture handle.
// Set to true if the image corresponds to a dummy texture used prior to
// the first successful capture of a desktop frame
bool dummy = false;
bool dummy = false; ///< Whether this image is a dummy placeholder.
// Set to true if the image is blank (contains no content at all, including a cursor)
bool blank = true;
bool blank = true; ///< Whether the texture currently contains a blank frame.
// Unique identifier for this image
uint32_t id = 0;
uint32_t id = 0; ///< Unique identifier used to cache encoder resources for this image.
// DXGI format of this image texture
DXGI_FORMAT format;
DXGI_FORMAT format; ///< DXGI format of the captured texture.
virtual ~img_d3d_t() override {
if (encoder_texture_handle) {
@@ -158,19 +183,33 @@ namespace platf::dxgi {
};
};
/**
* @brief Keyed-mutex guard used while sharing a D3D texture.
*/
struct texture_lock_helper {
keyed_mutex_t _mutex;
bool _locked = false;
keyed_mutex_t _mutex; ///< D3D keyed mutex acquired for shared-texture access.
bool _locked = false; ///< Whether the keyed mutex is currently locked.
texture_lock_helper(const texture_lock_helper &) = delete;
texture_lock_helper &operator=(const texture_lock_helper &) = delete;
/**
* @brief Move a keyed-mutex lock helper while preserving lock ownership.
*
* @param other Helper whose acquired keyed mutex is transferred.
*/
texture_lock_helper(texture_lock_helper &&other) {
_mutex.reset(other._mutex.release());
_locked = other._locked;
other._locked = false;
}
/**
* @brief Assign state from another instance while preserving ownership semantics.
*
* @param other Source object whose state is copied or moved into this object.
* @return Reference or value produced by the operator.
*/
texture_lock_helper &operator=(texture_lock_helper &&other) {
if (_locked) {
_mutex->ReleaseSync(0);
@@ -181,6 +220,11 @@ namespace platf::dxgi {
return *this;
}
/**
* @brief Acquire a D3D keyed mutex for scoped texture access.
*
* @param mutex Keyed mutex to acquire for texture access.
*/
texture_lock_helper(IDXGIKeyedMutex *mutex):
_mutex(mutex) {
if (_mutex) {
@@ -194,6 +238,11 @@ namespace platf::dxgi {
}
}
/**
* @brief Acquire the underlying lock or keyed mutex.
*
* @return True when the keyed mutex is acquired.
*/
bool lock() {
if (_locked) {
return true;
@@ -208,6 +257,13 @@ namespace platf::dxgi {
}
};
/**
* @brief Create cursor xor image.
*
* @param img_data Raw pointer-shape bytes returned by DXGI.
* @param shape_info DXGI metadata describing the pointer shape.
* @return Constructed cursor xor image object.
*/
util::buffer_t<std::uint8_t> make_cursor_xor_image(const util::buffer_t<std::uint8_t> &img_data, DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info) {
constexpr std::uint32_t inverted = 0xFFFFFFFF;
constexpr std::uint32_t transparent = 0;
@@ -277,6 +333,13 @@ namespace platf::dxgi {
return cursor_img;
}
/**
* @brief Create cursor alpha image.
*
* @param img_data Raw pointer-shape bytes returned by DXGI.
* @param shape_info DXGI metadata describing the pointer shape.
* @return Constructed cursor alpha image object.
*/
util::buffer_t<std::uint8_t> make_cursor_alpha_image(const util::buffer_t<std::uint8_t> &img_data, DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info) {
constexpr std::uint32_t black = 0xFF000000;
constexpr std::uint32_t white = 0xFFFFFFFF;
@@ -350,6 +413,14 @@ namespace platf::dxgi {
return cursor_img;
}
/**
* @brief Compile an HLSL shader from source text.
*
* @param file Optional stdio file handle connected to the child process.
* @param entrypoint Function entry point to resolve from the library.
* @param shader_model Shader model.
* @return Compiled shader blob, or an empty blob when compilation fails.
*/
blob_t compile_shader(LPCSTR file, LPCSTR entrypoint, LPCSTR shader_model) {
blob_t::pointer msg_p = nullptr;
blob_t::pointer compiled_p;
@@ -376,16 +447,37 @@ namespace platf::dxgi {
return blob_t {compiled_p};
}
/**
* @brief Compile an HLSL pixel shader from source text.
*
* @param file Optional stdio file handle connected to the child process.
* @return Compiled pixel shader object, or an empty handle when compilation fails.
*/
blob_t compile_pixel_shader(LPCSTR file) {
return compile_shader(file, "main_ps", "ps_5_0");
}
/**
* @brief Compile an HLSL vertex shader from source text.
*
* @param file Optional stdio file handle connected to the child process.
* @return Compiled vertex shader object, or an empty handle when compilation fails.
*/
blob_t compile_vertex_shader(LPCSTR file) {
return compile_shader(file, "main_vs", "vs_5_0");
}
/**
* @brief Shared D3D11 conversion resources for AVCodec and NVENC encode devices.
*/
class d3d_base_encode_device final {
public:
/**
* @brief Convert a captured D3D image into encoder input textures.
*
* @param img_base D3D image supplied by the capture backend.
* @return Conversion status.
*/
int convert(platf::img_t &img_base) {
// Garbage collect mapped capture images whose weak references have expired
for (auto it = img_ctx_map.begin(); it != img_ctx_map.end();) {
@@ -457,6 +549,11 @@ namespace platf::dxgi {
return 0;
}
/**
* @brief Apply the configured colorspace metadata to the active frame.
*
* @param colorspace Colorimetry information used for conversion or encoding.
*/
void apply_colorspace(const ::video::sunshine_colorspace_t &colorspace) {
auto color_vectors = ::video::color_vectors_from_colorspace(colorspace, true);
@@ -482,6 +579,14 @@ namespace platf::dxgi {
this->color_matrix = std::move(color_matrix);
}
/**
* @brief Create D3D11 output textures, views, and shaders for frame conversion.
*
* @param frame_texture Frame texture.
* @param width Frame or display width in pixels.
* @param height Frame or display height in pixels.
* @return 0 when output resources are initialized; nonzero on D3D failure.
*/
int init_output(ID3D11Texture2D *frame_texture, int width, int height) {
// The underlying frame pool owns the texture, so we must reference it for ourselves
frame_texture->AddRef();
@@ -489,16 +594,20 @@ namespace platf::dxgi {
HRESULT status = S_OK;
#define create_vertex_shader_helper(x, y) \
if (FAILED(status = device->CreateVertexShader(x->GetBufferPointer(), x->GetBufferSize(), nullptr, &y))) { \
BOOST_LOG(error) << "Failed to create vertex shader " << #x << ": " << util::log_hex(status); \
return -1; \
}
#define create_pixel_shader_helper(x, y) \
if (FAILED(status = device->CreatePixelShader(x->GetBufferPointer(), x->GetBufferSize(), nullptr, &y))) { \
BOOST_LOG(error) << "Failed to create pixel shader " << #x << ": " << util::log_hex(status); \
return -1; \
}
#ifndef DOXYGEN
#define create_vertex_shader_helper(x, y) \
if (FAILED(status = device->CreateVertexShader(x->GetBufferPointer(), x->GetBufferSize(), nullptr, &y))) { \
BOOST_LOG(error) << "Failed to create vertex shader " << #x << ": " << util::log_hex(status); \
return -1; \
}
#endif
#ifndef DOXYGEN
#define create_pixel_shader_helper(x, y) \
if (FAILED(status = device->CreatePixelShader(x->GetBufferPointer(), x->GetBufferSize(), nullptr, &y))) { \
BOOST_LOG(error) << "Failed to create pixel shader " << #x << ": " << util::log_hex(status); \
return -1; \
}
#endif
const bool downscaling = display->width > width || display->height > height;
@@ -709,6 +818,14 @@ namespace platf::dxgi {
return 0;
}
/**
* @brief Initialize shared D3D conversion resources for the encoder.
*
* @param display Display object or identifier associated with the operation.
* @param adapter_p Adapter p.
* @param pix_fmt Sunshine pixel format to convert or allocate for.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(std::shared_ptr<platf::display_t> display, adapter_t::pointer adapter_p, pix_fmt_e pix_fmt) {
switch (pix_fmt) {
case pix_fmt_e::nv12:
@@ -823,17 +940,23 @@ namespace platf::dxgi {
return 0;
}
/**
* @brief D3D texture and format information for encoder input.
*/
struct encoder_img_ctx_t {
// Used to determine if the underlying texture changes.
// Not safe for actual use by the encoder!
texture2d_t::const_pointer capture_texture_p;
texture2d_t::const_pointer capture_texture_p; ///< Capture texture p.
texture2d_t encoder_texture;
shader_res_t encoder_input_res;
keyed_mutex_t encoder_mutex;
texture2d_t encoder_texture; ///< Encoder texture.
shader_res_t encoder_input_res; ///< Encoder input res.
keyed_mutex_t encoder_mutex; ///< Encoder mutex.
std::weak_ptr<const platf::img_t> img_weak;
std::weak_ptr<const platf::img_t> img_weak; ///< Captured image lifetime tracked for cache cleanup.
/**
* @brief Reset the object to its initial empty state.
*/
void reset() {
capture_texture_p = nullptr;
encoder_texture.reset();
@@ -843,6 +966,13 @@ namespace platf::dxgi {
}
};
/**
* @brief Initialize encoder-side D3D resources for a captured image.
*
* @param img Image or frame object to read from or populate.
* @param img_ctx Cached encoder resources associated with the image ID.
* @return 0 when the shared texture is opened and bound; nonzero on D3D failure.
*/
int initialize_image_context(const img_d3d_t &img, encoder_img_ctx_t &img_ctx) {
// If we've already opened the shared texture, we're done
if (img_ctx.encoder_texture && img.capture_texture.get() == img_ctx.capture_texture_p) {
@@ -888,6 +1018,11 @@ namespace platf::dxgi {
return 0;
}
/**
* @brief Create black texture for rtv clear.
*
* @return Created black texture for rtv clear object or status.
*/
shader_res_t create_black_texture_for_rtv_clear() {
constexpr auto width = 32;
constexpr auto height = 32;
@@ -922,63 +1057,88 @@ namespace platf::dxgi {
return resource_view;
}
::video::color_t *color_p;
::video::color_t *color_p; ///< Color p.
buf_t subsample_offset;
buf_t color_matrix;
buf_t subsample_offset; ///< Subsample offset.
buf_t color_matrix; ///< Color matrix.
blend_t blend_disable;
sampler_state_t sampler_linear;
blend_t blend_disable; ///< Blend disable.
sampler_state_t sampler_linear; ///< Sampler linear.
render_target_t out_Y_or_YUV_rtv;
render_target_t out_UV_rtv;
bool rtvs_cleared = false;
render_target_t out_Y_or_YUV_rtv; ///< Out y or YUV rtv.
render_target_t out_UV_rtv; ///< Out UV rtv.
bool rtvs_cleared = false; ///< Whether render-target views have been cleared for the frame.
// d3d_img_t::id -> encoder_img_ctx_t
// These store the encoder textures for each img_t that passes through
// convert(). We can't store them in the img_t itself because it is shared
// amongst multiple hwdevice_t objects (and therefore multiple ID3D11Devices).
std::map<uint32_t, encoder_img_ctx_t> img_ctx_map;
std::map<uint32_t, encoder_img_ctx_t> img_ctx_map; ///< Encoder resources cached by captured image ID.
std::shared_ptr<display_base_t> display;
std::shared_ptr<display_base_t> display; ///< Display capture backend that supplies source textures.
vs_t convert_Y_or_YUV_vs;
ps_t convert_Y_or_YUV_ps;
ps_t convert_Y_or_YUV_fp16_ps;
vs_t convert_Y_or_YUV_vs; ///< Convert y or YUV vs.
ps_t convert_Y_or_YUV_ps; ///< Convert y or YUV ps.
ps_t convert_Y_or_YUV_fp16_ps; ///< Convert y or YUV fp16 ps.
vs_t convert_UV_vs;
ps_t convert_UV_ps;
ps_t convert_UV_fp16_ps;
vs_t convert_UV_vs; ///< Convert UV vs.
ps_t convert_UV_ps; ///< Convert UV ps.
ps_t convert_UV_fp16_ps; ///< Convert UV fp16 ps.
std::array<D3D11_VIEWPORT, 3> out_Y_or_YUV_viewports;
std::array<D3D11_VIEWPORT, 3> out_Y_or_YUV_viewports_for_clear;
D3D11_VIEWPORT out_UV_viewport;
D3D11_VIEWPORT out_UV_viewport_for_clear;
std::array<D3D11_VIEWPORT, 3> out_Y_or_YUV_viewports; ///< Out y or YUV viewports.
std::array<D3D11_VIEWPORT, 3> out_Y_or_YUV_viewports_for_clear; ///< Out y or YUV viewports for clear.
D3D11_VIEWPORT out_UV_viewport; ///< Out UV viewport.
D3D11_VIEWPORT out_UV_viewport_for_clear; ///< Out UV viewport for clear.
DXGI_FORMAT format;
DXGI_FORMAT format; ///< DXGI format required by the encoder input texture.
device_t device;
device_ctx_t device_ctx;
device_t device; ///< D3D11 device used for encoder-side texture conversion.
device_ctx_t device_ctx; ///< D3D11 device context used to issue conversion commands.
texture2d_t output_texture;
texture2d_t output_texture; ///< Output texture.
};
/**
* @brief D3D11 encode device that exposes captured textures to FFmpeg AVCodec.
*/
class d3d_avcodec_encode_device_t: public avcodec_encode_device_t {
public:
/**
* @brief Initialize the D3D11 AVCodec encode device.
*
* @param display Display object or identifier associated with the operation.
* @param adapter_p Adapter p.
* @param pix_fmt Sunshine pixel format to convert or allocate for.
* @return 0 on success; nonzero or negative platform status on failure.
*/
int init(std::shared_ptr<platf::display_t> display, adapter_t::pointer adapter_p, pix_fmt_e pix_fmt) {
int result = base.init(display, adapter_p, pix_fmt);
data = base.device.get();
return result;
}
/**
* @brief Convert a captured D3D texture for FFmpeg AVCodec encoding.
*
* @param img_base D3D image supplied by the capture backend.
* @return Conversion status.
*/
int convert(platf::img_t &img_base) override {
return base.convert(img_base);
}
/**
* @brief Apply the configured colorspace metadata to the active frame.
*/
void apply_colorspace() override {
base.apply_colorspace(colorspace);
}
/**
* @brief Configure FFmpeg hardware frames for D3D11 encoder input textures.
*
* @param frames FFmpeg hardware frames context to initialize.
*/
void init_hwframes(AVHWFramesContext *frames) override {
// We may be called with a QSV or D3D11VA context
if (frames->device_ctx->type == AV_HWDEVICE_TYPE_D3D11VA) {
@@ -993,6 +1153,12 @@ namespace platf::dxgi {
frames->initial_pool_size = 1;
}
/**
* @brief Prepare the D3D device before FFmpeg derives a child hardware context.
*
* @param hw_device_type FFmpeg hardware device type requested for context derivation.
* @return 0 when context derivation may continue; nonzero to abort.
*/
int prepare_to_derive_context(int hw_device_type) override {
// QuickSync requires our device to be multithread-protected
if (hw_device_type == AV_HWDEVICE_TYPE_QSV) {
@@ -1010,6 +1176,13 @@ namespace platf::dxgi {
return 0;
}
/**
* @brief Attach frame resources used by the next conversion or encode operation.
*
* @param frame Video or graphics frame being processed.
* @param hw_frames_ctx FFmpeg hardware frames context associated with the frame.
* @return Status from updating frame.
*/
int set_frame(AVFrame *frame, AVBufferRef *hw_frames_ctx) override {
this->hwframe.reset(frame);
this->frame = frame;
@@ -1053,8 +1226,19 @@ namespace platf::dxgi {
frame_t hwframe;
};
/**
* @brief D3D11 encode device that exposes captured textures to NVENC.
*/
class d3d_nvenc_encode_device_t: public nvenc_encode_device_t {
public:
/**
* @brief Create D3D11 and NVENC resources for texture-based encoding.
*
* @param display Display object or identifier associated with the operation.
* @param adapter_p Adapter p.
* @param pix_fmt Sunshine pixel format to convert or allocate for.
* @return True when the D3D11 device resources are initialized.
*/
bool init_device(std::shared_ptr<platf::display_t> display, adapter_t::pointer adapter_p, pix_fmt_e pix_fmt) {
buffer_format = nvenc::nvenc_format_from_sunshine_format(pix_fmt);
if (buffer_format == NV_ENC_BUFFER_FORMAT_UNDEFINED) {
@@ -1076,6 +1260,13 @@ namespace platf::dxgi {
return true;
}
/**
* @brief Initialize the platform encoder for the client stream configuration.
*
* @param client_config Client stream configuration negotiated for this session.
* @param colorspace Colorimetry information used for conversion or encoding.
* @return True when the NVENC encoder initializes for the client configuration.
*/
bool init_encoder(const ::video::config_t &client_config, const ::video::sunshine_colorspace_t &colorspace) override {
if (!nvenc_d3d) {
return false;
@@ -1090,6 +1281,12 @@ namespace platf::dxgi {
return base.init_output(nvenc_d3d->get_input_texture(), client_config.width, client_config.height) == 0;
}
/**
* @brief Convert a captured D3D texture for NVENC encoding.
*
* @param img_base D3D image supplied by the capture backend.
* @return Conversion status.
*/
int convert(platf::img_t &img_base) override {
return base.convert(img_base);
}
@@ -1100,6 +1297,15 @@ namespace platf::dxgi {
NV_ENC_BUFFER_FORMAT buffer_format = NV_ENC_BUFFER_FORMAT_UNDEFINED;
};
/**
* @brief Set cursor texture.
*
* @param device D3D, audio, or platform device used by the operation.
* @param cursor Cursor image or visibility state to composite.
* @param cursor_img Cursor img.
* @param shape_info Shape info.
* @return True when the cursor image is uploaded to the D3D texture.
*/
bool set_cursor_texture(device_t::pointer device, gpu_cursor_t &cursor, util::buffer_t<std::uint8_t> &&cursor_img, DXGI_OUTDUPL_POINTER_SHAPE_INFO &shape_info) {
// This cursor image may not be used
if (cursor_img.size() == 0) {
@@ -1638,10 +1844,6 @@ namespace platf::dxgi {
/**
* Get the next frame from the Windows.Graphics.Capture API and copy it into a new snapshot texture.
* @param pull_free_image_cb call this to get a new free image from the video subsystem.
* @param img_out the captured frame is returned here
* @param timeout how long to wait for the next frame
* @param cursor_visible
*/
capture_e display_wgc_vram_t::snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) {
texture2d_t src;
@@ -1938,15 +2140,22 @@ namespace platf::dxgi {
return device;
}
/**
* @brief Initialize global D3D11 desktop duplication support.
*/
int init() {
BOOST_LOG(info) << "Compiling shaders..."sv;
#define compile_vertex_shader_helper(x) \
if (!(x##_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/" #x ".hlsl"))) \
return -1;
#define compile_pixel_shader_helper(x) \
if (!(x##_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/" #x ".hlsl"))) \
return -1;
#ifndef DOXYGEN
#define compile_vertex_shader_helper(x) \
if (!(x##_hlsl = compile_vertex_shader(SUNSHINE_SHADERS_DIR "/" #x ".hlsl"))) \
return -1;
#endif
#ifndef DOXYGEN
#define compile_pixel_shader_helper(x) \
if (!(x##_hlsl = compile_pixel_shader(SUNSHINE_SHADERS_DIR "/" #x ".hlsl"))) \
return -1;
#endif
compile_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps);
compile_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_linear);

Some files were not shown because too many files have changed in this diff Show More