diff --git a/CMakeLists.txt b/CMakeLists.txt index 788c9438..01e933ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,9 +57,12 @@ if(WIN32) include_directories( ViGEmClient/include) set(PLATFORM_TARGET_FILES - sunshine/platform/windows.cpp - sunshine/platform/windows_dxgi.cpp - sunshine/platform/windows_wasapi.cpp + sunshine/platform/windows/input.cpp + sunshine/platform/windows/display.h + sunshine/platform/windows/display_base.cpp + sunshine/platform/windows/display_vram.cpp + sunshine/platform/windows/display_ram.cpp + sunshine/platform/windows/audio.cpp ViGEmClient/src/ViGEmClient.cpp ViGEmClient/include/ViGEm/Client.h ViGEmClient/include/ViGEm/Common.h @@ -82,8 +85,8 @@ else() find_package(X11 REQUIRED) set(PLATFORM_TARGET_FILES - sunshine/platform/linux.cpp - sunshine/platform/linux_evdev.cpp) + sunshine/platform/linux/display.cpp + sunshine/platform/linux/input.cpp) set(PLATFORM_LIBRARIES Xfixes diff --git a/assets/sunshine.conf b/assets/sunshine.conf index daaf000b..3fb5cf17 100644 --- a/assets/sunshine.conf +++ b/assets/sunshine.conf @@ -105,20 +105,64 @@ # Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually # worth it to gain the use of more CPU cores for encoding. The ideal value is the lowest # value that can reliably encode at your desired streaming settings on your hardware. -# min_threads = 2 +# min_threads = 1 # Allows the client to request HEVC Main or HEVC Main10 video streams. -# HEVC is more CPU-intensive to encode, so enabling this may reduce performance. -# If set to 0 (default), Sunshine will not advertise support for HEVC -# If set to 1, Sunshine will advertise support for HEVC Main profile -# If set to 2, Sunshine will advertise support for HEVC Main and Main10 (HDR) profiles -# hevc_mode = 2 +# HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using software encoding. +# If set to 0 (default), Sunshine will specify support for HEVC based on encoder +# If set to 1, Sunshine will not advertise support for HEVC +# If set to 2, Sunshine will advertise support for HEVC Main profile +# If set to 3, Sunshine will advertise support for HEVC Main and Main10 (HDR) profiles +# hevc_mode = 0 +# Force a specific encoder, otherwise Sunshine will use the first encoder that is available +# supported encoders: +# nvenc +# software +# +# encoder = nvenc +##################################### Software ##################################### # See x264 --fullhelp for the different presets -# preset = superfast -# tune = zerolatency +# sw_preset = superfast +# sw_tune = zerolatency # + +##################################### NVENC ##################################### +###### presets ########### +# default +# hp -- high performance +# hq -- high quality +# slow -- hq 2 passes +# medium -- hq 1 pass +# fast -- hp 1 pass +# bd +# ll -- low latency +# llhq +# llhp +# lossless +# losslesshp +########################## +# nv_preset = llhq # +####### rate control ##### +# auto -- let ffmpeg decide rate control +# constqp -- constant QP mode +# vbr -- variable bitrate +# cbr -- constant bitrate +# cbr_hq -- cbr high quality +# cbr_ld_hq -- cbr low delay high quality +# vbr_hq -- vbr high quality +########################## +# nv_rc = auto + +###### h264 entropy ###### +# auto -- let ffmpeg nvenc decide the entropy encoding +# cabac +# cavlc +########################## +# nv_coder = auto + + ############################################## # Some configurable parameters, are merely toggles for specific features # The first occurrence turns it on, the second occurence turns it off, the third occurence turns it on again, etc, etc diff --git a/pre-compiled b/pre-compiled index 51f776db..afd9a9bb 160000 --- a/pre-compiled +++ b/pre-compiled @@ -1 +1 @@ -Subproject commit 51f776dbd4b2ead239a966406447d12f7e942636 +Subproject commit afd9a9bbfc6ee1a064b0c1f9210bc20b2170c416 diff --git a/sunshine/config.cpp b/sunshine/config.cpp index 5eeabf71..2b027e1c 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -15,17 +15,97 @@ #define APPS_JSON_PATH SUNSHINE_ASSETS_DIR "/" APPS_JSON namespace config { using namespace std::literals; + +namespace nv { +enum preset_e : int { + _default = 0, + slow, + medium, + fast, + hp, + hq, + bd, + ll_default, + llhq, + llhp, + lossless_default, // lossless presets must be the last ones + lossless_hp, +}; + +enum rc_e : int { + constqp = 0x0, /**< Constant QP mode */ + vbr = 0x1, /**< Variable bitrate mode */ + cbr = 0x2, /**< Constant bitrate mode */ + cbr_ld_hq = 0x8, /**< low-delay CBR, high quality */ + cbr_hq = 0x10, /**< CBR, high quality (slower) */ + vbr_hq = 0x20 /**< VBR, high quality (slower) */ +}; + +enum coder_e : int { + _auto = 0, + cabac, + cavlc +}; + +std::optional preset_from_view(const std::string_view &preset) { +#define _CONVERT_(x) if(preset == #x##sv) return x + _CONVERT_(slow); + _CONVERT_(medium); + _CONVERT_(fast); + _CONVERT_(hp); + _CONVERT_(bd); + _CONVERT_(ll_default); + _CONVERT_(llhq); + _CONVERT_(llhp); + _CONVERT_(lossless_default); + _CONVERT_(lossless_hp); + if(preset == "default"sv) return _default; +#undef _CONVERT_ + return std::nullopt; +} + +std::optional rc_from_view(const std::string_view &rc) { +#define _CONVERT_(x) if(rc == #x##sv) return x + _CONVERT_(constqp); + _CONVERT_(vbr); + _CONVERT_(cbr); + _CONVERT_(cbr_hq); + _CONVERT_(vbr_hq); + _CONVERT_(cbr_ld_hq); +#undef _CONVERT_ + return std::nullopt; +} + +int coder_from_view(const std::string_view &coder) { + if(coder == "auto"sv) return _auto; + if(coder == "cabac"sv || coder == "ac"sv) return cabac; + if(coder == "cavlc"sv || coder == "vlc"sv) return cavlc; + + return -1; +} +} + video_t video { 0, // crf 28, // qp - 2, // min_threads - 0, // hevc_mode - "superfast"s, // preset - "zerolatency"s, // tune + + 1, // min_threads + { + "superfast"s, // preset + "zerolatency"s, // tune + }, // software + + { + nv::llhq, + std::nullopt, + -1 + }, // nv + + {}, // encoder {}, // adapter_name - {} // output_name + {} // output_name }; audio_t audio {}; @@ -138,6 +218,37 @@ void int_f(std::unordered_map &vars, const std::string vars.erase(it); } +void int_f(std::unordered_map &vars, const std::string &name, std::optional &input) { + auto it = vars.find(name); + + if(it == std::end(vars)) { + return; + } + + auto &val = it->second; + input = util::from_chars(&val[0], &val[0] + val.size()); + + vars.erase(it); +} + +template +void int_f(std::unordered_map &vars, const std::string &name, int &input, F &&f) { + std::string tmp; + string_f(vars, name, tmp); + if(!tmp.empty()) { + input = f(tmp); + } +} + +template +void int_f(std::unordered_map &vars, const std::string &name, std::optional &input, F &&f) { + std::string tmp; + string_f(vars, name, tmp); + if(!tmp.empty()) { + input = f(tmp); + } +} + void int_between_f(std::unordered_map &vars, const std::string &name, int &input, const std::pair &range) { int temp = input; @@ -149,6 +260,28 @@ void int_between_f(std::unordered_map &vars, const std } } +bool to_bool(std::string &boolean) { + std::for_each(std::begin(boolean), std::end(boolean), [](char ch) { return (char)std::tolower(ch); }); + + return + boolean == "true"sv || + boolean == "yes"sv || + boolean == "enable"sv || + (std::find(std::begin(boolean), std::end(boolean), '1') != std::end(boolean)); +} +void bool_f(std::unordered_map &vars, const std::string &name, int &input) { + std::string tmp; + string_restricted_f(vars, name, tmp, { + "enable"sv, "dis" + }); + + if(tmp.empty()) { + return; + } + + input = to_bool(tmp) ? 1 : 0; +} + void print_help(const char *name) { std::cout << "Usage: "sv << name << " [options] [/path/to/configuration_file]"sv << std::endl << @@ -190,10 +323,14 @@ void apply_config(std::unordered_map &&vars) { int_f(vars, "qp", video.qp); int_f(vars, "min_threads", video.min_threads); int_between_f(vars, "hevc_mode", video.hevc_mode, { - 0, 2 + 0, 3 }); - string_f(vars, "preset", video.preset); - string_f(vars, "tune", video.tune); + string_f(vars, "sw_preset", video.sw.preset); + string_f(vars, "sw_tune", video.sw.tune); + int_f(vars, "nv_preset", video.nv.preset, nv::preset_from_view); + int_f(vars, "nv_rc", video.nv.preset, nv::rc_from_view); + int_f(vars, "nv_coder", video.nv.coder, nv::coder_from_view); + string_f(vars, "encoder", video.encoder); string_f(vars, "adapter_name", video.adapter_name); string_f(vars, "output_name", video.output_name); diff --git a/sunshine/config.h b/sunshine/config.h index 93af19ee..419a5933 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -4,6 +4,7 @@ #include #include #include +#include namespace config { struct video_t { @@ -11,12 +12,21 @@ struct video_t { int crf; // higher == more compression and less quality int qp; // higher == more compression and less quality, ignored if crf != 0 - int min_threads; // Minimum number of threads/slices for CPU encoding - int hevc_mode; - std::string preset; - std::string tune; + int min_threads; // Minimum number of threads/slices for CPU encoding + struct { + std::string preset; + std::string tune; + } sw; + + struct { + std::optional preset; + std::optional rc; + int coder; + } nv; + + std::string encoder; std::string adapter_name; std::string output_name; }; diff --git a/sunshine/main.cpp b/sunshine/main.cpp index 6d0e6e65..c21f81fc 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -140,7 +140,9 @@ int main(int argc, char *argv[]) { auto deinit_guard = platf::init(); input::init(); reed_solomon_init(); - video::init(); + if(video::init()) { + return 2; + } task_pool.start(1); diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index fcbd69bb..9fcd038e 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -168,7 +168,15 @@ void update_id_client(const std::string &uniqueID, std::string &&cert, op_e op) } void getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin) { - auto salt = util::from_hex>(sess.async_insert_pin.salt, true); + if(sess.async_insert_pin.salt.size() < 32) { + tree.put("root.paired", 0); + tree.put("root..status_code", 400); + return; + } + + std::string_view salt_view { sess.async_insert_pin.salt.data(), 32 }; + + auto salt = util::from_hex>(salt_view, true); auto key = crypto::gen_aes_key(*salt, pin); sess.cipher_key = std::make_unique(key); @@ -464,13 +472,13 @@ void serverinfo(std::shared_ptr::Response> res tree.put("root.GfeVersion", GFE_VERSION); tree.put("root.uniqueid", unique_id); tree.put("root.mac", platf::get_mac_address(request->local_endpoint_address())); - tree.put("root.MaxLumaPixelsHEVC", config::video.hevc_mode > 0 ? "1869449984" : "0"); + tree.put("root.MaxLumaPixelsHEVC", config::video.hevc_mode > 1 ? "1869449984" : "0"); tree.put("root.LocalIP", request->local_endpoint_address()); - if(config::video.hevc_mode == 2) { + if(config::video.hevc_mode == 3) { tree.put("root.ServerCodecModeSupport", "3843"); } - else if(config::video.hevc_mode == 1) { + else if(config::video.hevc_mode == 2) { tree.put("root.ServerCodecModeSupport", "259"); } else { @@ -484,7 +492,7 @@ void serverinfo(std::shared_ptr::Response> res auto current_appid = proc::proc.running(); tree.put("root.PairStatus", pair_status); tree.put("root.currentgame", current_appid >= 0 ? current_appid + 1 : 0); - tree.put("root.state", "_SERVER_BUSY"); + tree.put("root.state", current_appid >= 0 ? "_SERVER_BUSY" : "_SERVER_FREE"); std::ostringstream data; @@ -522,7 +530,7 @@ void applist(resp_https_t response, req_https_t request) { for(auto &proc : proc::proc.get_apps()) { pt::ptree app; - app.put("IsHdrSupported"s, config::video.hevc_mode == 2 ? 1 : 0); + app.put("IsHdrSupported"s, config::video.hevc_mode == 3 ? 1 : 0); app.put("AppTitle"s, proc.name); app.put("ID"s, ++x); diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index fc35ab75..140b54b1 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -6,6 +6,7 @@ #define SUNSHINE_COMMON_H #include +#include #include "sunshine/utility.h" struct sockaddr; @@ -28,6 +29,20 @@ constexpr std::uint16_t B = 0x2000; constexpr std::uint16_t X = 0x4000; constexpr std::uint16_t Y = 0x8000; +enum class dev_type_e { + none, + dxgi, + unknown +}; + +enum class pix_fmt_e { + yuv420p, + yuv420p10, + nv12, + p010, + unknown +}; + struct gamepad_state_t { std::uint16_t buttonFlags; std::uint8_t lt; @@ -58,6 +73,19 @@ public: virtual ~img_t() = default; }; +struct hwdevice_t { + void *data {}; + platf::img_t *img {}; + + virtual int convert(platf::img_t &img) { + return -1; + } + + virtual void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) {}; + + virtual ~hwdevice_t() = default; +}; + enum class capture_e : int { ok, reinit, @@ -67,24 +95,18 @@ enum class capture_e : int { class display_t { public: - virtual capture_e snapshot(img_t *img, bool cursor) = 0; + virtual capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor) = 0; virtual std::shared_ptr alloc_img() = 0; - virtual int dummy_img(img_t *img, int &dummy_data_p) { - img->row_pitch = 4; - img->height = 1; - img->width = 1; - img->pixel_pitch = 4; - img->data = (std::uint8_t*)&dummy_data_p; + virtual int dummy_img(img_t *img) = 0; - return 0; - } - - virtual std::shared_ptr get_hwdevice() { - return nullptr; + virtual std::shared_ptr make_hwdevice(int width, int height, pix_fmt_e pix_fmt) { + return std::make_shared(); } virtual ~display_t() = default; + + int width, height; }; class mic_t { @@ -105,7 +127,7 @@ std::string from_sockaddr(const sockaddr *const); std::pair from_sockaddr_ex(const sockaddr *const); std::unique_ptr microphone(std::uint32_t sample_rate); -std::shared_ptr display(int hwdevice_type); +std::shared_ptr display(dev_type_e hwdevice_type); input_t input(); void move_mouse(input_t &input, int deltaX, int deltaY); diff --git a/sunshine/platform/linux.cpp b/sunshine/platform/linux/display.cpp similarity index 90% rename from sunshine/platform/linux.cpp rename to sunshine/platform/linux/display.cpp index 46f043e2..17ef98a1 100644 --- a/sunshine/platform/linux.cpp +++ b/sunshine/platform/linux/display.cpp @@ -2,7 +2,7 @@ // Created by loki on 6/21/19. // -#include "common.h" +#include "sunshine/platform/common.h" #include #include @@ -145,14 +145,22 @@ struct x11_attr_t : public display_t { xwindow = DefaultRootWindow(xdisplay.get()); refresh(); + + width = xattr.width; + height = xattr.height; } void refresh() { XGetWindowAttributes(xdisplay.get(), xwindow, &xattr); } - capture_e snapshot(img_t *img_out_base, bool cursor) override { + capture_e snapshot(img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) override { refresh(); + + if(width != xattr.width || height != xattr.height) { + return capture_e::reinit; + } + XImage *img { XGetImage( xdisplay.get(), xwindow, @@ -180,6 +188,11 @@ struct x11_attr_t : public display_t { return std::make_shared(); } + int dummy_img(img_t *img) override { + snapshot(img, 0s, true); + return 0; + } + xdisplay_t xdisplay; Window xwindow; XWindowAttributes xattr; @@ -210,8 +223,8 @@ struct shm_attr_t : public x11_attr_t { while(!task_pool.cancel(refresh_task_id)); } - capture_e snapshot(img_t *img, bool cursor) override { - if(display->width_in_pixels != xattr.width || display->height_in_pixels != xattr.height) { + capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor) override { + if(width != xattr.width || height != xattr.height) { return capture_e::reinit; } @@ -219,7 +232,7 @@ struct shm_attr_t : public x11_attr_t { xcb.get(), display->root, 0, 0, - display->width_in_pixels, display->height_in_pixels, + width, height, ~0, XCB_IMAGE_FORMAT_Z_PIXMAP, seg, @@ -232,16 +245,6 @@ struct shm_attr_t : public x11_attr_t { return capture_e::reinit; } - if(img->width != display->width_in_pixels || img->height != display->height_in_pixels) { - delete[] img->data; - - img->data = new std::uint8_t[frame_size()]; - img->width = display->width_in_pixels; - img->height = display->height_in_pixels; - img->pixel_pitch = 4; - img->row_pitch = img->width * img->pixel_pitch; - } - std::copy_n((std::uint8_t*)data.data, frame_size(), img->data); if(cursor) { @@ -252,13 +255,18 @@ struct shm_attr_t : public x11_attr_t { } std::shared_ptr alloc_img() override { - return std::make_shared(); + auto img = std::make_shared(); + img->width = width; + img->height = height; + img->pixel_pitch = 4; + img->row_pitch = img->pixel_pitch * width; + img->data = new std::uint8_t[height * img->row_pitch]; + + return img; } - int dummy_img(platf::img_t *img, int &) override { - auto dummy_data_p = new int[1]; - - return platf::display_t::dummy_img(img, *dummy_data_p); + int dummy_img(platf::img_t *img) override { + return 0; } int init() { @@ -293,11 +301,14 @@ struct shm_attr_t : public x11_attr_t { return -1; } + width = display->width_in_pixels; + height = display->height_in_pixels; + return 0; } std::uint32_t frame_size() { - return display->height_in_pixels * display->width_in_pixels * 4; + return width * height * 4; } }; @@ -331,7 +342,11 @@ std::shared_ptr shm_display() { return shm; } -std::shared_ptr display(int hwdevice_type) { +std::shared_ptr display(platf::dev_type_e hwdevice_type) { + if(hwdevice_type != platf::dev_type_e::none) { + return nullptr; + } + auto shm_disp = shm_display(); if(!shm_disp) { diff --git a/sunshine/platform/linux_evdev.cpp b/sunshine/platform/linux/input.cpp similarity index 99% rename from sunshine/platform/linux_evdev.cpp rename to sunshine/platform/linux/input.cpp index af9e39ed..9de79069 100644 --- a/sunshine/platform/linux_evdev.cpp +++ b/sunshine/platform/linux/input.cpp @@ -9,7 +9,7 @@ #include #include -#include "common.h" +#include "sunshine/platform/common.h" #include "sunshine/main.h" #include "sunshine/utility.h" diff --git a/sunshine/platform/windows_wasapi.cpp b/sunshine/platform/windows/audio.cpp similarity index 99% rename from sunshine/platform/windows_wasapi.cpp rename to sunshine/platform/windows/audio.cpp index 97412106..8d824b70 100644 --- a/sunshine/platform/windows_wasapi.cpp +++ b/sunshine/platform/windows/audio.cpp @@ -12,7 +12,7 @@ #include "sunshine/config.h" #include "sunshine/main.h" -#include "common.h" +#include "sunshine/platform/common.h" const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); diff --git a/sunshine/platform/windows/display.h b/sunshine/platform/windows/display.h new file mode 100644 index 00000000..662115e8 --- /dev/null +++ b/sunshine/platform/windows/display.h @@ -0,0 +1,116 @@ +// +// Created by loki on 4/23/20. +// + +#ifndef SUNSHINE_DISPLAY_H +#define SUNSHINE_DISPLAY_H + +#include +#include +#include +#include +#include + +#include "sunshine/utility.h" +#include "sunshine/platform/common.h" + +namespace platf::dxgi { +extern const char *format_str[]; + +template +void Release(T *dxgi) { + dxgi->Release(); +} + +using factory1_t = util::safe_ptr>; +using dxgi_t = util::safe_ptr>; +using dxgi1_t = util::safe_ptr>; +using device_t = util::safe_ptr>; +using device_ctx_t = util::safe_ptr>; +using adapter_t = util::safe_ptr>; +using output_t = util::safe_ptr>; +using output1_t = util::safe_ptr>; +using dup_t = util::safe_ptr>; +using texture2d_t = util::safe_ptr>; +using resource_t = util::safe_ptr>; +using multithread_t = util::safe_ptr>; + +namespace video { +using device_t = util::safe_ptr>; +using ctx_t = util::safe_ptr>; +using processor_t = util::safe_ptr>; +using processor_out_t = util::safe_ptr>; +using processor_in_t = util::safe_ptr>; +using processor_enum_t = util::safe_ptr>; +} + +class hwdevice_t; +struct cursor_t { + std::vector img_data; + + DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info; + int x, y; + bool visible; +}; + +struct gpu_cursor_t { + texture2d_t texture; + + LONG width, height; +}; + +class duplication_t { +public: + dup_t dup; + bool has_frame {}; + + capture_e next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p); + capture_e reset(dup_t::pointer dup_p = dup_t::pointer()); + capture_e release_frame(); + + ~duplication_t(); +}; + +class display_base_t : public display_t { +public: + int init(); + + factory1_t factory; + adapter_t adapter; + output_t output; + device_t device; + device_ctx_t device_ctx; + duplication_t dup; + + DXGI_FORMAT format; + D3D_FEATURE_LEVEL feature_level; +}; + +class display_ram_t : public display_base_t { +public: + capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) override; + std::shared_ptr alloc_img() override; + int dummy_img(img_t *img) override; + + int init(); + + cursor_t cursor; + D3D11_MAPPED_SUBRESOURCE img_info; + texture2d_t texture; +}; + +class display_vram_t : public display_base_t, public std::enable_shared_from_this { +public: + capture_e snapshot(img_t *img, std::chrono::milliseconds timeout, bool cursor_visible) override; + + std::shared_ptr alloc_img() override; + int dummy_img(img_t *img_base) override; + + std::shared_ptr make_hwdevice(int width, int height, pix_fmt_e pix_fmt) override; + + gpu_cursor_t cursor; + std::vector hwdevices; +}; +} + +#endif \ No newline at end of file diff --git a/sunshine/platform/windows/display_base.cpp b/sunshine/platform/windows/display_base.cpp new file mode 100644 index 00000000..4b42880c --- /dev/null +++ b/sunshine/platform/windows/display_base.cpp @@ -0,0 +1,420 @@ +// +// Created by loki on 1/12/20. +// + +#include + +#include "sunshine/config.h" +#include "sunshine/main.h" +#include "sunshine/platform/common.h" + +#include "display.h" + +namespace platf { +using namespace std::literals; +} +namespace platf::dxgi { +capture_e duplication_t::next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, std::chrono::milliseconds timeout, resource_t::pointer *res_p) { + auto capture_status = release_frame(); + if(capture_status != capture_e::ok) { + return capture_status; + } + + auto status = dup->AcquireNextFrame(timeout.count(), &frame_info, res_p); + + switch(status) { + case S_OK: + has_frame = true; + return capture_e::ok; + case DXGI_ERROR_WAIT_TIMEOUT: + return capture_e::timeout; + case WAIT_ABANDONED: + case DXGI_ERROR_ACCESS_LOST: + case DXGI_ERROR_ACCESS_DENIED: + return capture_e::reinit; + default: + BOOST_LOG(error) << "Couldn't acquire next frame [0x"sv << util::hex(status).to_string_view(); + return capture_e::error; + } +} + +capture_e duplication_t::reset(dup_t::pointer dup_p) { + auto capture_status = release_frame(); + + dup.reset(dup_p); + + return capture_status; +} + +capture_e duplication_t::release_frame() { + if(!has_frame) { + return capture_e::ok; + } + + auto status = dup->ReleaseFrame(); + switch (status) { + case S_OK: + has_frame = false; + return capture_e::ok; + case DXGI_ERROR_WAIT_TIMEOUT: + return capture_e::timeout; + case WAIT_ABANDONED: + case DXGI_ERROR_ACCESS_LOST: + case DXGI_ERROR_ACCESS_DENIED: + has_frame = false; + return capture_e::reinit; + default: + BOOST_LOG(error) << "Couldn't release frame [0x"sv << util::hex(status).to_string_view(); + return capture_e::error; + } +} + +duplication_t::~duplication_t() { + release_frame(); +} + +int display_base_t::init() { +/* Uncomment when use of IDXGIOutput5 is implemented + std::call_once(windows_cpp_once_flag, []() { + DECLARE_HANDLE(DPI_AWARENESS_CONTEXT); + const auto DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = ((DPI_AWARENESS_CONTEXT)-4); + + typedef BOOL (*User32_SetProcessDpiAwarenessContext)(DPI_AWARENESS_CONTEXT value); + + auto user32 = LoadLibraryA("user32.dll"); + auto f = (User32_SetProcessDpiAwarenessContext)GetProcAddress(user32, "SetProcessDpiAwarenessContext"); + if(f) { + f(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + } + + FreeLibrary(user32); + }); +*/ + dxgi::factory1_t::pointer factory_p {}; + dxgi::adapter_t::pointer adapter_p {}; + dxgi::output_t::pointer output_p {}; + dxgi::device_t::pointer device_p {}; + dxgi::device_ctx_t::pointer device_ctx_p {}; + + HRESULT status; + + status = CreateDXGIFactory1(IID_IDXGIFactory1, (void**)&factory_p); + factory.reset(factory_p); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + std::wstring_convert, wchar_t> converter; + + auto adapter_name = converter.from_bytes(config::video.adapter_name); + auto output_name = converter.from_bytes(config::video.output_name); + + for(int x = 0; factory_p->EnumAdapters1(x, &adapter_p) != DXGI_ERROR_NOT_FOUND; ++x) { + dxgi::adapter_t adapter_tmp { adapter_p }; + + DXGI_ADAPTER_DESC1 adapter_desc; + adapter_tmp->GetDesc1(&adapter_desc); + + if(!adapter_name.empty() && adapter_desc.Description != adapter_name) { + continue; + } + + for(int y = 0; adapter_tmp->EnumOutputs(y, &output_p) != DXGI_ERROR_NOT_FOUND; ++y) { + dxgi::output_t output_tmp {output_p }; + + DXGI_OUTPUT_DESC desc; + output_tmp->GetDesc(&desc); + + if(!output_name.empty() && desc.DeviceName != output_name) { + continue; + } + + if(desc.AttachedToDesktop) { + output = std::move(output_tmp); + + width = desc.DesktopCoordinates.right - desc.DesktopCoordinates.left; + height = desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top; + } + } + + if(output) { + adapter = std::move(adapter_tmp); + break; + } + } + + if(!output) { + BOOST_LOG(error) << "Failed to locate an output device"sv; + return -1; + } + + D3D_FEATURE_LEVEL featureLevels[] { + D3D_FEATURE_LEVEL_12_1, + D3D_FEATURE_LEVEL_12_0, + D3D_FEATURE_LEVEL_11_1, + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_10_0, + D3D_FEATURE_LEVEL_9_3, + D3D_FEATURE_LEVEL_9_2, + D3D_FEATURE_LEVEL_9_1 + }; + + status = adapter->QueryInterface(IID_IDXGIAdapter, (void**)&adapter_p); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to query IDXGIAdapter interface"sv; + + return -1; + } + + status = D3D11CreateDevice( + adapter_p, + D3D_DRIVER_TYPE_UNKNOWN, + nullptr, + D3D11_CREATE_DEVICE_VIDEO_SUPPORT, + featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL), + D3D11_SDK_VERSION, + &device_p, + &feature_level, + &device_ctx_p); + + adapter_p->Release(); + + device.reset(device_p); + device_ctx.reset(device_ctx_p); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create D3D11 device [0x"sv << util::hex(status).to_string_view() << ']'; + + return -1; + } + + DXGI_ADAPTER_DESC adapter_desc; + adapter->GetDesc(&adapter_desc); + + auto description = converter.to_bytes(adapter_desc.Description); + BOOST_LOG(info) + << std::endl + << "Device Description : " << description << std::endl + << "Device Vendor ID : 0x"sv << util::hex(adapter_desc.VendorId).to_string_view() << std::endl + << "Device Device ID : 0x"sv << util::hex(adapter_desc.DeviceId).to_string_view() << std::endl + << "Device Video Mem : "sv << adapter_desc.DedicatedVideoMemory / 1048576 << " MiB"sv << std::endl + << "Device Sys Mem : "sv << adapter_desc.DedicatedSystemMemory / 1048576 << " MiB"sv << std::endl + << "Share Sys Mem : "sv << adapter_desc.SharedSystemMemory / 1048576 << " MiB"sv << std::endl + << "Feature Level : 0x"sv << util::hex(feature_level).to_string_view() << std::endl + << "Capture size : "sv << width << 'x' << height; + + // Bump up thread priority + { + dxgi::dxgi_t::pointer dxgi_p {}; + status = device->QueryInterface(IID_IDXGIDevice, (void**)&dxgi_p); + dxgi::dxgi_t dxgi { dxgi_p }; + + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + dxgi->SetGPUThreadPriority(7); + } + + // Try to reduce latency + { + dxgi::dxgi1_t::pointer dxgi_p {}; + status = device->QueryInterface(IID_IDXGIDevice, (void**)&dxgi_p); + dxgi::dxgi1_t dxgi { dxgi_p }; + + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + dxgi->SetMaximumFrameLatency(1); + } + + //FIXME: Duplicate output on RX580 in combination with DOOM (2016) --> BSOD + //TODO: Use IDXGIOutput5 for improved performance + { + dxgi::output1_t::pointer output1_p {}; + status = output->QueryInterface(IID_IDXGIOutput1, (void**)&output1_p); + dxgi::output1_t output1 {output1_p }; + + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to query IDXGIOutput1 from the output"sv; + return -1; + } + + // We try this twice, in case we still get an error on reinitialization + for(int x = 0; x < 2; ++x) { + dxgi::dup_t::pointer dup_p {}; + status = output1->DuplicateOutput((IUnknown*)device.get(), &dup_p); + if(SUCCEEDED(status)) { + dup.reset(dup_p); + break; + } + std::this_thread::sleep_for(200ms); + } + + if(FAILED(status)) { + BOOST_LOG(error) << "DuplicateOutput Failed [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + } + + DXGI_OUTDUPL_DESC dup_desc; + dup.dup->GetDesc(&dup_desc); + + format = dup_desc.ModeDesc.Format; + + BOOST_LOG(debug) << "Source format ["sv << format_str[dup_desc.ModeDesc.Format] << ']'; + + return 0; +} + +const char *format_str[] = { + "DXGI_FORMAT_UNKNOWN", + "DXGI_FORMAT_R32G32B32A32_TYPELESS", + "DXGI_FORMAT_R32G32B32A32_FLOAT", + "DXGI_FORMAT_R32G32B32A32_UINT", + "DXGI_FORMAT_R32G32B32A32_SINT", + "DXGI_FORMAT_R32G32B32_TYPELESS", + "DXGI_FORMAT_R32G32B32_FLOAT", + "DXGI_FORMAT_R32G32B32_UINT", + "DXGI_FORMAT_R32G32B32_SINT", + "DXGI_FORMAT_R16G16B16A16_TYPELESS", + "DXGI_FORMAT_R16G16B16A16_FLOAT", + "DXGI_FORMAT_R16G16B16A16_UNORM", + "DXGI_FORMAT_R16G16B16A16_UINT", + "DXGI_FORMAT_R16G16B16A16_SNORM", + "DXGI_FORMAT_R16G16B16A16_SINT", + "DXGI_FORMAT_R32G32_TYPELESS", + "DXGI_FORMAT_R32G32_FLOAT", + "DXGI_FORMAT_R32G32_UINT", + "DXGI_FORMAT_R32G32_SINT", + "DXGI_FORMAT_R32G8X24_TYPELESS", + "DXGI_FORMAT_D32_FLOAT_S8X24_UINT", + "DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS", + "DXGI_FORMAT_X32_TYPELESS_G8X24_UINT", + "DXGI_FORMAT_R10G10B10A2_TYPELESS", + "DXGI_FORMAT_R10G10B10A2_UNORM", + "DXGI_FORMAT_R10G10B10A2_UINT", + "DXGI_FORMAT_R11G11B10_FLOAT", + "DXGI_FORMAT_R8G8B8A8_TYPELESS", + "DXGI_FORMAT_R8G8B8A8_UNORM", + "DXGI_FORMAT_R8G8B8A8_UNORM_SRGB", + "DXGI_FORMAT_R8G8B8A8_UINT", + "DXGI_FORMAT_R8G8B8A8_SNORM", + "DXGI_FORMAT_R8G8B8A8_SINT", + "DXGI_FORMAT_R16G16_TYPELESS", + "DXGI_FORMAT_R16G16_FLOAT", + "DXGI_FORMAT_R16G16_UNORM", + "DXGI_FORMAT_R16G16_UINT", + "DXGI_FORMAT_R16G16_SNORM", + "DXGI_FORMAT_R16G16_SINT", + "DXGI_FORMAT_R32_TYPELESS", + "DXGI_FORMAT_D32_FLOAT", + "DXGI_FORMAT_R32_FLOAT", + "DXGI_FORMAT_R32_UINT", + "DXGI_FORMAT_R32_SINT", + "DXGI_FORMAT_R24G8_TYPELESS", + "DXGI_FORMAT_D24_UNORM_S8_UINT", + "DXGI_FORMAT_R24_UNORM_X8_TYPELESS", + "DXGI_FORMAT_X24_TYPELESS_G8_UINT", + "DXGI_FORMAT_R8G8_TYPELESS", + "DXGI_FORMAT_R8G8_UNORM", + "DXGI_FORMAT_R8G8_UINT", + "DXGI_FORMAT_R8G8_SNORM", + "DXGI_FORMAT_R8G8_SINT", + "DXGI_FORMAT_R16_TYPELESS", + "DXGI_FORMAT_R16_FLOAT", + "DXGI_FORMAT_D16_UNORM", + "DXGI_FORMAT_R16_UNORM", + "DXGI_FORMAT_R16_UINT", + "DXGI_FORMAT_R16_SNORM", + "DXGI_FORMAT_R16_SINT", + "DXGI_FORMAT_R8_TYPELESS", + "DXGI_FORMAT_R8_UNORM", + "DXGI_FORMAT_R8_UINT", + "DXGI_FORMAT_R8_SNORM", + "DXGI_FORMAT_R8_SINT", + "DXGI_FORMAT_A8_UNORM", + "DXGI_FORMAT_R1_UNORM", + "DXGI_FORMAT_R9G9B9E5_SHAREDEXP", + "DXGI_FORMAT_R8G8_B8G8_UNORM", + "DXGI_FORMAT_G8R8_G8B8_UNORM", + "DXGI_FORMAT_BC1_TYPELESS", + "DXGI_FORMAT_BC1_UNORM", + "DXGI_FORMAT_BC1_UNORM_SRGB", + "DXGI_FORMAT_BC2_TYPELESS", + "DXGI_FORMAT_BC2_UNORM", + "DXGI_FORMAT_BC2_UNORM_SRGB", + "DXGI_FORMAT_BC3_TYPELESS", + "DXGI_FORMAT_BC3_UNORM", + "DXGI_FORMAT_BC3_UNORM_SRGB", + "DXGI_FORMAT_BC4_TYPELESS", + "DXGI_FORMAT_BC4_UNORM", + "DXGI_FORMAT_BC4_SNORM", + "DXGI_FORMAT_BC5_TYPELESS", + "DXGI_FORMAT_BC5_UNORM", + "DXGI_FORMAT_BC5_SNORM", + "DXGI_FORMAT_B5G6R5_UNORM", + "DXGI_FORMAT_B5G5R5A1_UNORM", + "DXGI_FORMAT_B8G8R8A8_UNORM", + "DXGI_FORMAT_B8G8R8X8_UNORM", + "DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM", + "DXGI_FORMAT_B8G8R8A8_TYPELESS", + "DXGI_FORMAT_B8G8R8A8_UNORM_SRGB", + "DXGI_FORMAT_B8G8R8X8_TYPELESS", + "DXGI_FORMAT_B8G8R8X8_UNORM_SRGB", + "DXGI_FORMAT_BC6H_TYPELESS", + "DXGI_FORMAT_BC6H_UF16", + "DXGI_FORMAT_BC6H_SF16", + "DXGI_FORMAT_BC7_TYPELESS", + "DXGI_FORMAT_BC7_UNORM", + "DXGI_FORMAT_BC7_UNORM_SRGB", + "DXGI_FORMAT_AYUV", + "DXGI_FORMAT_Y410", + "DXGI_FORMAT_Y416", + "DXGI_FORMAT_NV12", + "DXGI_FORMAT_P010", + "DXGI_FORMAT_P016", + "DXGI_FORMAT_420_OPAQUE", + "DXGI_FORMAT_YUY2", + "DXGI_FORMAT_Y210", + "DXGI_FORMAT_Y216", + "DXGI_FORMAT_NV11", + "DXGI_FORMAT_AI44", + "DXGI_FORMAT_IA44", + "DXGI_FORMAT_P8", + "DXGI_FORMAT_A8P8", + "DXGI_FORMAT_B4G4R4A4_UNORM", + + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + + "DXGI_FORMAT_P208", + "DXGI_FORMAT_V208", + "DXGI_FORMAT_V408" +}; + +} + +namespace platf { +std::shared_ptr display(dev_type_e hwdevice_type) { + if(hwdevice_type == dev_type_e::dxgi) { + auto disp = std::make_shared(); + + if(!disp->init()) { + return disp; + } + } + else if(hwdevice_type == dev_type_e::none) { + auto disp = std::make_shared(); + + if(!disp->init()) { + return disp; + } + } + + return nullptr; +} +} diff --git a/sunshine/platform/windows/display_ram.cpp b/sunshine/platform/windows/display_ram.cpp new file mode 100644 index 00000000..2f26eca5 --- /dev/null +++ b/sunshine/platform/windows/display_ram.cpp @@ -0,0 +1,301 @@ +#include "sunshine/main.h" +#include "display.h" + +namespace platf { +using namespace std::literals; +} + +namespace platf::dxgi { +struct img_t : public ::platf::img_t { + ~img_t() override { + delete[] data; + data = nullptr; + } +}; + +void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) { + int height = cursor.shape_info.Height / 2; + int width = cursor.shape_info.Width; + int pitch = cursor.shape_info.Pitch; + + // img cursor.{x,y} < 0, skip parts of the cursor.img_data + auto cursor_skip_y = -std::min(0, cursor.y); + auto cursor_skip_x = -std::min(0, cursor.x); + + // img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data + auto cursor_truncate_y = std::max(0, cursor.y - img.height); + auto cursor_truncate_x = std::max(0, cursor.x - img.width); + + auto cursor_width = width - cursor_skip_x - cursor_truncate_x; + auto cursor_height = height - cursor_skip_y - cursor_truncate_y; + + if(cursor_height > height || cursor_width > width) { + return; + } + + auto img_skip_y = std::max(0, cursor.y); + auto img_skip_x = std::max(0, cursor.x); + + auto cursor_img_data = cursor.img_data.data() + cursor_skip_y * pitch; + + int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y)); + int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x)); + + auto pixels_per_byte = width / pitch; + auto bytes_per_row = delta_width / pixels_per_byte; + + auto img_data = (int*)img.data; + for(int i = 0; i < delta_height; ++i) { + auto and_mask = &cursor_img_data[i * pitch]; + auto xor_mask = &cursor_img_data[(i + height) * pitch]; + + auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x]; + + auto skip_x = cursor_skip_x; + for(int x = 0; x < bytes_per_row; ++x) { + for(auto bit = 0u; bit < 8; ++bit) { + if(skip_x > 0) { + --skip_x; + + continue; + } + + int and_ = *and_mask & (1 << (7 - bit)) ? -1 : 0; + int xor_ = *xor_mask & (1 << (7 - bit)) ? -1 : 0; + + *img_pixel_p &= and_; + *img_pixel_p ^= xor_; + + ++img_pixel_p; + } + + ++and_mask; + ++xor_mask; + } + } +} + +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; + + //TODO: When use of IDXGIOutput5 is implemented, support different color formats + auto alpha = colors_out[3]; + if(alpha == 255) { + *img_pixel_p = cursor_pixel; + } + else { + colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255/2) / 255; + colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255/2) / 255; + colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255/2) / 255; + } +} + +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]; + if(alpha == 0xFF) { + *img_pixel_p ^= cursor_pixel; + } + else { + *img_pixel_p = cursor_pixel; + } +} + +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; + int pitch = cursor.shape_info.Pitch; + + // img cursor.y < 0, skip parts of the cursor.img_data + auto cursor_skip_y = -std::min(0, cursor.y); + auto cursor_skip_x = -std::min(0, cursor.x); + + // img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data + auto cursor_truncate_y = std::max(0, cursor.y - img.height); + auto cursor_truncate_x = std::max(0, cursor.x - img.width); + + auto img_skip_y = std::max(0, cursor.y); + auto img_skip_x = std::max(0, cursor.x); + + auto cursor_width = width - cursor_skip_x - cursor_truncate_x; + auto cursor_height = height - cursor_skip_y - cursor_truncate_y; + + if(cursor_height > height || cursor_width > width) { + return; + } + + auto cursor_img_data = (int*)&cursor.img_data[cursor_skip_y * pitch]; + + int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y)); + int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x)); + + auto img_data = (int*)img.data; + + for(int i = 0; i < delta_height; ++i) { + auto cursor_begin = &cursor_img_data[i * cursor.shape_info.Width + cursor_skip_x]; + auto cursor_end = &cursor_begin[delta_width]; + + auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x]; + std::for_each(cursor_begin, cursor_end, [&](int cursor_pixel) { + if(masked) { + apply_color_masked(img_pixel_p, cursor_pixel); + } + else { + apply_color_alpha(img_pixel_p, cursor_pixel); + } + ++img_pixel_p; + }); + } +} + +void blend_cursor(const cursor_t &cursor, img_t &img) { + switch(cursor.shape_info.Type) { + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: + blend_cursor_color(cursor, img, false); + break; + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME: + blend_cursor_monochrome(cursor, img); + break; + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: + blend_cursor_color(cursor, img, true); + break; + default: + BOOST_LOG(warning) << "Unsupported cursor format ["sv << cursor.shape_info.Type << ']'; + } +} + +capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) { + auto img = (img_t*)img_base; + + HRESULT status; + + DXGI_OUTDUPL_FRAME_INFO frame_info; + + resource_t::pointer res_p {}; + auto capture_status = dup.next_frame(frame_info, timeout, &res_p); + resource_t res{res_p}; + + if (capture_status != capture_e::ok) { + return capture_status; + } + + if(frame_info.PointerShapeBufferSize > 0) { + auto &img_data = cursor.img_data; + + img_data.resize(frame_info.PointerShapeBufferSize); + + UINT dummy; + status = dup.dup->GetFramePointerShape(img_data.size(), img_data.data(), &dummy, &cursor.shape_info); + if (FAILED(status)) { + BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']'; + + return capture_e::error; + } + } + + if(frame_info.LastMouseUpdateTime.QuadPart) { + cursor.x = frame_info.PointerPosition.Position.x; + cursor.y = frame_info.PointerPosition.Position.y; + cursor.visible = frame_info.PointerPosition.Visible; + } + + // If frame has been updated + if (frame_info.LastPresentTime.QuadPart != 0) { + { + texture2d_t::pointer src_p {}; + status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src_p); + texture2d_t src{src_p}; + + if (FAILED(status)) { + BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + + //Copy from GPU to CPU + device_ctx->CopyResource(texture.get(), src.get()); + } + + if(img_info.pData) { + device_ctx->Unmap(texture.get(), 0); + img_info.pData = nullptr; + } + + status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info); + if (FAILED(status)) { + BOOST_LOG(error) << "Failed to map texture [0x"sv << util::hex(status).to_string_view() << ']'; + + return capture_e::error; + } + } + + const bool mouse_update = + (frame_info.LastMouseUpdateTime.QuadPart || frame_info.PointerShapeBufferSize > 0) && + (cursor_visible && cursor.visible); + + const bool update_flag = frame_info.LastPresentTime.QuadPart != 0 || mouse_update; + + if(!update_flag) { + return capture_e::timeout; + } + + std::copy_n((std::uint8_t*)img_info.pData, height * img_info.RowPitch, (std::uint8_t*)img->data); + + if(cursor_visible && cursor.visible) { + blend_cursor(cursor, *img); + } + + return capture_e::ok; +} + +std::shared_ptr display_ram_t::alloc_img() { + auto img = std::make_shared(); + + img->pixel_pitch = 4; + img->row_pitch = img->pixel_pitch * width; + img->width = width; + img->height = height; + img->data = new std::uint8_t[img->row_pitch * height]; + + return img; +} + +int display_ram_t::dummy_img(platf::img_t *img) { + return 0; +} + +int display_ram_t::init() { + if(display_base_t::init()) { + return -1; + } + + D3D11_TEXTURE2D_DESC t {}; + t.Width = width; + t.Height = height; + t.MipLevels = 1; + t.ArraySize = 1; + t.SampleDesc.Count = 1; + t.Usage = D3D11_USAGE_STAGING; + t.Format = format; + t.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + + dxgi::texture2d_t::pointer tex_p {}; + auto status = device->CreateTexture2D(&t, nullptr, &tex_p); + + texture.reset(tex_p); + + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create texture [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + // map the texture simply to get the pitch and stride + status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to map the texture [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + return 0; +} +} \ No newline at end of file diff --git a/sunshine/platform/windows/display_vram.cpp b/sunshine/platform/windows/display_vram.cpp new file mode 100644 index 00000000..d73e6584 --- /dev/null +++ b/sunshine/platform/windows/display_vram.cpp @@ -0,0 +1,503 @@ +#include "sunshine/main.h" +#include "display.h" + +namespace platf { +using namespace std::literals; +} + +namespace platf::dxgi { +struct img_d3d_t : public platf::img_t { + std::shared_ptr display; + texture2d_t texture; + + ~img_d3d_t() override = default; +}; + +util::buffer_t make_cursor_image(util::buffer_t &&img_data, DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info) { + switch(shape_info.Type) { + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: + case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: + return std::move(img_data); + default: + break; + } + + shape_info.Height /= 2; + + util::buffer_t cursor_img { shape_info.Width * shape_info.Height * 4 }; + + auto bytes = shape_info.Pitch * shape_info.Height; + auto pixel_begin = (std::uint32_t*)std::begin(cursor_img); + auto pixel_data = pixel_begin; + auto and_mask = std::begin(img_data); + auto xor_mask = std::begin(img_data) + bytes; + + for(auto x = 0; x < bytes; ++x) { + for(auto c = 7; c >= 0; --c) { + auto bit = 1 << c; + auto color_type = ((*and_mask & bit) ? 1 : 0) + ((*xor_mask & bit) ? 2 : 0); + + constexpr std::uint32_t black = 0xFF000000; + constexpr std::uint32_t white = 0xFFFFFFFF; + constexpr std::uint32_t transparent = 0; + switch(color_type) { + case 0: //black + *pixel_data = black; + break; + case 2: //white + *pixel_data = white; + break; + case 1: //transparent + { + *pixel_data = transparent; + + break; + } + case 3: //inverse + { + auto top_p = pixel_data - shape_info.Width; + auto left_p = pixel_data - 1; + auto right_p = pixel_data + 1; + auto bottom_p = pixel_data + shape_info.Width; + + // Get the x coordinate of the pixel + auto column = (pixel_data - pixel_begin) % shape_info.Width != 0; + + if(top_p >= pixel_begin && *top_p == transparent) { + *top_p = black; + } + + if(column != 0 && left_p >= pixel_begin && *left_p == transparent) { + *left_p = black; + } + + if(bottom_p < (std::uint32_t*)std::end(cursor_img)) { + *bottom_p = black; + } + + if(column != shape_info.Width -1) { + *right_p = black; + } + *pixel_data = white; + } + } + + ++pixel_data; + } + ++and_mask; + ++xor_mask; + } + + return cursor_img; +} + +class hwdevice_t : public platf::hwdevice_t { +public: + hwdevice_t(std::vector *hwdevices_p) : hwdevices_p { hwdevices_p } {} + hwdevice_t() = delete; + + void set_cursor_pos(LONG rel_x, LONG rel_y, bool visible) { + cursor_visible = visible; + + if(!visible) { + return; + } + + LONG x = ((double)rel_x) * out_width / (double)in_width; + LONG y = ((double)rel_y) * out_height / (double)in_height; + + // Ensure it's within bounds + auto left_out = std::min(out_width, std::max(0, x)); + auto top_out = std::min(out_height, std::max(0, y)); + auto right_out = std::max(0, std::min(out_width, x + cursor_scaled_width)); + auto bottom_out = std::max(0, std::min(out_height, y + cursor_scaled_height)); + + auto left_in = std::max(0, -rel_x); + auto top_in = std::max(0, -rel_y); + auto right_in = std::min(in_width - rel_x, cursor_width); + auto bottom_in = std::min(in_height - rel_y, cursor_height); + + RECT rect_in { left_in, top_in, right_in, bottom_in }; + RECT rect_out { left_out, top_out, right_out, bottom_out }; + + ctx->VideoProcessorSetStreamSourceRect(processor.get(), 1, TRUE, &rect_in); + ctx->VideoProcessorSetStreamDestRect(processor.get(), 1, TRUE, &rect_out); + } + + int set_cursor_texture(texture2d_t::pointer texture, LONG width, LONG height) { + D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC input_desc = { 0, (D3D11_VPIV_DIMENSION)D3D11_VPIV_DIMENSION_TEXTURE2D, { 0, 0 } }; + + video::processor_in_t::pointer processor_in_p; + auto status = device->CreateVideoProcessorInputView(texture, processor_e.get(), &input_desc, &processor_in_p); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create cursor VideoProcessorInputView [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + cursor_in.reset(processor_in_p); + + cursor_width = width; + cursor_height = height; + cursor_scaled_width = ((double)width) / in_width * out_width; + cursor_scaled_height = ((double)height) / in_height * out_height; + + return 0; + } + + int convert(platf::img_t &img_base) override { + auto &img = (img_d3d_t&)img_base; + + auto it = texture_to_processor_in.find(img.texture.get()); + if(it == std::end(texture_to_processor_in)) { + D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC input_desc = { 0, (D3D11_VPIV_DIMENSION)D3D11_VPIV_DIMENSION_TEXTURE2D, { 0, 0 } }; + + video::processor_in_t::pointer processor_in_p; + auto status = device->CreateVideoProcessorInputView(img.texture.get(), processor_e.get(), &input_desc, &processor_in_p); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create VideoProcessorInputView [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + it = texture_to_processor_in.emplace(img.texture.get(), processor_in_p).first; + } + auto &processor_in = it->second; + + D3D11_VIDEO_PROCESSOR_STREAM stream[] { + { TRUE, 0, 0, 0, 0, nullptr, processor_in.get(), nullptr }, + { TRUE, 0, 0, 0, 0, nullptr, cursor_in.get(), nullptr } + }; + + auto status = ctx->VideoProcessorBlt(processor.get(), processor_out.get(), 0, cursor_visible ? 2 : 1, stream); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed size and color conversion [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + return 0; + } + + void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) override { + colorspace |= (color_range >> 4); + ctx->VideoProcessorSetOutputColorSpace(processor.get(), (D3D11_VIDEO_PROCESSOR_COLOR_SPACE*)&colorspace); + } + + int init( + std::shared_ptr display, device_t::pointer device_p, device_ctx_t::pointer device_ctx_p, + int in_width, int in_height, int out_width, int out_height, + pix_fmt_e pix_fmt + ) { + HRESULT status; + + cursor_visible = false; + + platf::hwdevice_t::img = &img; + + this->out_width = out_width; + this->out_height = out_height; + this->in_width = in_width; + this->in_height = in_height; + + video::device_t::pointer vdevice_p; + status = device_p->QueryInterface(IID_ID3D11VideoDevice, (void**)&vdevice_p); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to query ID3D11VideoDevice interface [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + device.reset(vdevice_p); + + video::ctx_t::pointer ctx_p; + status = device_ctx_p->QueryInterface(IID_ID3D11VideoContext, (void**)&ctx_p); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to query ID3D11VideoContext interface [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + ctx.reset(ctx_p); + + D3D11_VIDEO_PROCESSOR_CONTENT_DESC contentDesc { + D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE, + { 1, 1 }, (UINT)in_width, (UINT)in_height, + { 1, 1 }, (UINT)out_width, (UINT)out_height, + D3D11_VIDEO_USAGE_OPTIMAL_QUALITY + }; + + video::processor_enum_t::pointer vp_e_p; + status = device->CreateVideoProcessorEnumerator(&contentDesc, &vp_e_p); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create video processor enumerator [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + processor_e.reset(vp_e_p); + + video::processor_t::pointer processor_p; + status = device->CreateVideoProcessor(processor_e.get(), 0, &processor_p); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create video processor [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + processor.reset(processor_p); + + D3D11_TEXTURE2D_DESC t {}; + t.Width = out_width; + t.Height = out_height; + t.MipLevels = 1; + t.ArraySize = 1; + t.SampleDesc.Count = 1; + t.Usage = D3D11_USAGE_DEFAULT; + t.Format = pix_fmt == pix_fmt_e::nv12 ? DXGI_FORMAT_NV12 : DXGI_FORMAT_P010; + t.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_VIDEO_ENCODER; + + dxgi::texture2d_t::pointer tex_p {}; + status = device_p->CreateTexture2D(&t, nullptr, &tex_p); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create video output texture [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + img.texture.reset(tex_p); + img.display = std::move(display); + img.width = out_width; + img.height = out_height; + img.data = (std::uint8_t*)tex_p; + img.row_pitch = out_width; + img.pixel_pitch = 1; + + D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC output_desc { D3D11_VPOV_DIMENSION_TEXTURE2D, 0 }; + video::processor_out_t::pointer processor_out_p; + status = device->CreateVideoProcessorOutputView(img.texture.get(), processor_e.get(), &output_desc, &processor_out_p); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create VideoProcessorOutputView [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + processor_out.reset(processor_out_p); + + // Tell video processor alpha values need to be enabled + ctx->VideoProcessorSetStreamAlpha(processor.get(), 1, TRUE, 1.0f); + + device_p->AddRef(); + data = device_p; + return 0; + } + + ~hwdevice_t() override { + if(data) { + ((ID3D11Device*)data)->Release(); + } + + auto it = std::find(std::begin(*hwdevices_p), std::end(*hwdevices_p), this); + if(it != std::end(*hwdevices_p)) { + hwdevices_p->erase(it); + } + } + + img_d3d_t img; + video::device_t device; + video::ctx_t ctx; + video::processor_enum_t processor_e; + video::processor_t processor; + video::processor_out_t processor_out; + std::unordered_map texture_to_processor_in; + + video::processor_in_t cursor_in; + + bool cursor_visible; + + LONG cursor_width, cursor_height; + LONG cursor_scaled_width, cursor_scaled_height; + + LONG in_width, in_height; + double out_width, out_height; + + std::vector *hwdevices_p; +}; + +capture_e display_vram_t::snapshot(platf::img_t *img_base, std::chrono::milliseconds timeout, bool cursor_visible) { + auto img = (img_d3d_t*)img_base; + + HRESULT status; + + DXGI_OUTDUPL_FRAME_INFO frame_info; + + resource_t::pointer res_p {}; + auto capture_status = dup.next_frame(frame_info, timeout, &res_p); + resource_t res{res_p}; + + if (capture_status != capture_e::ok) { + return capture_status; + } + + const bool update_flag = + frame_info.AccumulatedFrames != 0 || frame_info.LastPresentTime.QuadPart != 0 || + frame_info.LastMouseUpdateTime.QuadPart != 0 || frame_info.PointerShapeBufferSize > 0; + + if(!update_flag) { + return capture_e::timeout; + } + + if(frame_info.PointerShapeBufferSize > 0) { + DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info {}; + + util::buffer_t img_data { frame_info.PointerShapeBufferSize }; + + UINT dummy; + status = dup.dup->GetFramePointerShape(img_data.size(), std::begin(img_data), &dummy, &shape_info); + if (FAILED(status)) { + BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']'; + + return capture_e::error; + } + + auto cursor_img = make_cursor_image(std::move(img_data), shape_info); + + D3D11_SUBRESOURCE_DATA data { + std::begin(cursor_img), + 4 * shape_info.Width, + 0 + }; + + // Create texture for cursor + D3D11_TEXTURE2D_DESC t {}; + t.Width = shape_info.Width; + t.Height = cursor_img.size() / data.SysMemPitch; + t.MipLevels = 1; + t.ArraySize = 1; + t.SampleDesc.Count = 1; + t.Usage = D3D11_USAGE_DEFAULT; + t.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + t.BindFlags = D3D11_BIND_RENDER_TARGET; + + dxgi::texture2d_t::pointer tex_p {}; + auto status = device->CreateTexture2D(&t, &data, &tex_p); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create dummy texture [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + texture2d_t texture { tex_p }; + + for(auto *hwdevice : hwdevices) { + if(hwdevice->set_cursor_texture(tex_p, t.Width, t.Height)) { + return capture_e::error; + } + } + + cursor.texture = std::move(texture); + cursor.width = t.Width; + cursor.height = t.Height; + } + + if(frame_info.LastMouseUpdateTime.QuadPart) { + for(auto *hwdevice : hwdevices) { + hwdevice->set_cursor_pos(frame_info.PointerPosition.Position.x, frame_info.PointerPosition.Position.y, frame_info.PointerPosition.Visible && cursor_visible); + } + } + + texture2d_t::pointer src_p {}; + status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src_p); + + if (FAILED(status)) { + BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']'; + return capture_e::error; + } + + texture2d_t src { src_p }; + device_ctx->CopyResource(img->texture.get(), src.get()); + + return capture_e::ok; +} + +std::shared_ptr display_vram_t::alloc_img() { + auto img = std::make_shared(); + + D3D11_TEXTURE2D_DESC t {}; + t.Width = width; + t.Height = height; + t.MipLevels = 1; + t.ArraySize = 1; + t.SampleDesc.Count = 1; + t.Usage = D3D11_USAGE_DEFAULT; + t.Format = format; + t.BindFlags = D3D11_BIND_RENDER_TARGET; + + dxgi::texture2d_t::pointer tex_p {}; + auto status = device->CreateTexture2D(&t, nullptr, &tex_p); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create img buf texture [0x"sv << util::hex(status).to_string_view() << ']'; + return nullptr; + } + + img->data = (std::uint8_t*)tex_p; + img->row_pitch = 0; + img->pixel_pitch = 4; + img->width = 0; + img->height = 0; + img->texture.reset(tex_p); + img->display = shared_from_this(); + + return img; +} + +int display_vram_t::dummy_img(platf::img_t *img_base) { + auto img = (img_d3d_t*)img_base; + + img->row_pitch = width * 4; + auto dummy_data = std::make_unique(width * height); + D3D11_SUBRESOURCE_DATA data { + dummy_data.get(), + (UINT)img->row_pitch, + 0 + }; + + D3D11_TEXTURE2D_DESC t {}; + t.Width = width; + t.Height = height; + t.MipLevels = 1; + t.ArraySize = 1; + t.SampleDesc.Count = 1; + t.Usage = D3D11_USAGE_DEFAULT; + t.Format = format; + t.BindFlags = D3D11_BIND_RENDER_TARGET; + + dxgi::texture2d_t::pointer tex_p {}; + auto status = device->CreateTexture2D(&t, &data, &tex_p); + if(FAILED(status)) { + BOOST_LOG(error) << "Failed to create dummy texture [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + img->data = (std::uint8_t*)tex_p; + img->texture.reset(tex_p); + img->height = height; + img->width = width; + img->pixel_pitch = 4; + + return 0; +} + +std::shared_ptr display_vram_t::make_hwdevice(int width, int height, pix_fmt_e pix_fmt) { + if(pix_fmt != platf::pix_fmt_e::nv12 && pix_fmt != platf::pix_fmt_e::p010) { + BOOST_LOG(error) << "display_vram_t doesn't support pixel format ["sv << (int)pix_fmt << ']'; + + return nullptr; + } + + auto hwdevice = std::make_shared(&hwdevices); + + auto ret = hwdevice->init( + shared_from_this(), + device.get(), + device_ctx.get(), + this->width, this->height, + width, height, + pix_fmt); + + if(ret) { + return nullptr; + } + + if(cursor.texture && hwdevice->set_cursor_texture(cursor.texture.get(), cursor.width, cursor.height)) { + return nullptr; + } + + hwdevices.emplace_back(hwdevice.get()); + + return hwdevice; +} +} \ No newline at end of file diff --git a/sunshine/platform/windows.cpp b/sunshine/platform/windows/input.cpp similarity index 98% rename from sunshine/platform/windows.cpp rename to sunshine/platform/windows/input.cpp index 98436ec7..ba0c2338 100755 --- a/sunshine/platform/windows.cpp +++ b/sunshine/platform/windows/input.cpp @@ -10,7 +10,7 @@ #include #include "sunshine/main.h" -#include "common.h" +#include "sunshine/platform/common.h" namespace platf { using namespace std::literals; @@ -331,6 +331,10 @@ void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) { } } +int thread_priority() { + return SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST) ? 0 : 1; +} + void freeInput(void *p) { auto vigem = (vigem_t*)p; diff --git a/sunshine/platform/windows_dxgi.cpp b/sunshine/platform/windows_dxgi.cpp deleted file mode 100644 index 5362b08d..00000000 --- a/sunshine/platform/windows_dxgi.cpp +++ /dev/null @@ -1,770 +0,0 @@ -// -// Created by loki on 1/12/20. -// - -extern "C" { -#include -} - -#include -#include -#include -#include - -#include - -#include "sunshine/config.h" -#include "sunshine/main.h" -#include "common.h" - -namespace platf { -using namespace std::literals; -} -namespace platf::dxgi { -template -void Release(T *dxgi) { - dxgi->Release(); -} - -using factory1_t = util::safe_ptr>; -using dxgi_t = util::safe_ptr>; -using dxgi1_t = util::safe_ptr>; -using device_t = util::safe_ptr>; -using device_ctx_t = util::safe_ptr>; -using adapter_t = util::safe_ptr>; -using output_t = util::safe_ptr>; -using output1_t = util::safe_ptr>; -using dup_t = util::safe_ptr>; -using texture2d_t = util::safe_ptr>; -using resource_t = util::safe_ptr>; - -extern const char *format_str[]; - -class duplication_t { -public: - dup_t dup; - bool has_frame {}; - - capture_e next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, resource_t::pointer *res_p) { - auto capture_status = release_frame(); - if(capture_status != capture_e::ok) { - return capture_status; - } - - auto status = dup->AcquireNextFrame(1000, &frame_info, res_p); - - switch(status) { - case S_OK: - has_frame = true; - return capture_e::ok; - case DXGI_ERROR_WAIT_TIMEOUT: - return capture_e::timeout; - case WAIT_ABANDONED: - case DXGI_ERROR_ACCESS_LOST: - case DXGI_ERROR_ACCESS_DENIED: - return capture_e::reinit; - default: - BOOST_LOG(error) << "Couldn't acquire next frame [0x"sv << util::hex(status).to_string_view(); - return capture_e::error; - } - } - - capture_e reset(dup_t::pointer dup_p = dup_t::pointer()) { - auto capture_status = release_frame(); - - dup.reset(dup_p); - - return capture_status; - } - - capture_e release_frame() { - if(!has_frame) { - return capture_e::ok; - } - - auto status = dup->ReleaseFrame(); - switch (status) { - case S_OK: - has_frame = false; - return capture_e::ok; - case DXGI_ERROR_WAIT_TIMEOUT: - return capture_e::timeout; - case WAIT_ABANDONED: - case DXGI_ERROR_ACCESS_LOST: - case DXGI_ERROR_ACCESS_DENIED: - has_frame = false; - return capture_e::reinit; - default: - BOOST_LOG(error) << "Couldn't release frame [0x"sv << util::hex(status).to_string_view(); - return capture_e::error; - } - } - - ~duplication_t() { - release_frame(); - } -}; - -class display_t; -struct img_t : public ::platf::img_t { - ~img_t() override { - delete[] data; - data = nullptr; - } -}; - -struct cursor_t { - std::vector img_data; - - DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info; - int x, y; - bool visible; -}; - -void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) { - int height = cursor.shape_info.Height / 2; - int width = cursor.shape_info.Width; - int pitch = cursor.shape_info.Pitch; - - // img cursor.{x,y} < 0, skip parts of the cursor.img_data - auto cursor_skip_y = -std::min(0, cursor.y); - auto cursor_skip_x = -std::min(0, cursor.x); - - // img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data - auto cursor_truncate_y = std::max(0, cursor.y - img.height); - auto cursor_truncate_x = std::max(0, cursor.x - img.width); - - auto cursor_width = width - cursor_skip_x - cursor_truncate_x; - auto cursor_height = height - cursor_skip_y - cursor_truncate_y; - - if(cursor_height > height || cursor_width > width) { - return; - } - - auto img_skip_y = std::max(0, cursor.y); - auto img_skip_x = std::max(0, cursor.x); - - auto cursor_img_data = cursor.img_data.data() + cursor_skip_y * pitch; - - int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y)); - int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x)); - - auto pixels_per_byte = width / pitch; - auto bytes_per_row = delta_width / pixels_per_byte; - - auto img_data = (int*)img.data; - for(int i = 0; i < delta_height; ++i) { - auto and_mask = &cursor_img_data[i * pitch]; - auto xor_mask = &cursor_img_data[(i + height) * pitch]; - - auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x]; - - auto skip_x = cursor_skip_x; - for(int x = 0; x < bytes_per_row; ++x) { - for(auto bit = 0u; bit < 8; ++bit) { - if(skip_x > 0) { - --skip_x; - - continue; - } - - int and_ = *and_mask & (1 << (7 - bit)) ? -1 : 0; - int xor_ = *xor_mask & (1 << (7 - bit)) ? -1 : 0; - - *img_pixel_p &= and_; - *img_pixel_p ^= xor_; - - ++img_pixel_p; - } - - ++and_mask; - ++xor_mask; - } - } -} - -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; - - //TODO: When use of IDXGIOutput5 is implemented, support different color formats - auto alpha = colors_out[3]; - if(alpha == 255) { - *img_pixel_p = cursor_pixel; - } - else { - colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255/2) / 255; - colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255/2) / 255; - colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255/2) / 255; - } -} - -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]; - if(alpha == 0xFF) { - *img_pixel_p ^= cursor_pixel; - } - else { - *img_pixel_p = cursor_pixel; - } -} - -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; - int pitch = cursor.shape_info.Pitch; - - // img cursor.y < 0, skip parts of the cursor.img_data - auto cursor_skip_y = -std::min(0, cursor.y); - auto cursor_skip_x = -std::min(0, cursor.x); - - // img cursor.{x,y} > img.{x,y}, truncate parts of the cursor.img_data - auto cursor_truncate_y = std::max(0, cursor.y - img.height); - auto cursor_truncate_x = std::max(0, cursor.x - img.width); - - auto img_skip_y = std::max(0, cursor.y); - auto img_skip_x = std::max(0, cursor.x); - - auto cursor_width = width - cursor_skip_x - cursor_truncate_x; - auto cursor_height = height - cursor_skip_y - cursor_truncate_y; - - if(cursor_height > height || cursor_width > width) { - return; - } - - auto cursor_img_data = (int*)&cursor.img_data[cursor_skip_y * pitch]; - - int delta_height = std::min(cursor_height - cursor_truncate_y, std::max(0, img.height - img_skip_y)); - int delta_width = std::min(cursor_width - cursor_truncate_x, std::max(0, img.width - img_skip_x)); - - auto img_data = (int*)img.data; - - for(int i = 0; i < delta_height; ++i) { - auto cursor_begin = &cursor_img_data[i * cursor.shape_info.Width + cursor_skip_x]; - auto cursor_end = &cursor_begin[delta_width]; - - auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x]; - std::for_each(cursor_begin, cursor_end, [&](int cursor_pixel) { - if(masked) { - apply_color_masked(img_pixel_p, cursor_pixel); - } - else { - apply_color_alpha(img_pixel_p, cursor_pixel); - } - ++img_pixel_p; - }); - } -} - -void blend_cursor(const cursor_t &cursor, img_t &img) { - switch(cursor.shape_info.Type) { - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: - blend_cursor_color(cursor, img, false); - break; - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME: - blend_cursor_monochrome(cursor, img); - break; - case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: - blend_cursor_color(cursor, img, true); - break; - default: - BOOST_LOG(warning) << "Unsupported cursor format ["sv << cursor.shape_info.Type << ']'; - } -} - -class display_t : public ::platf::display_t, public std::enable_shared_from_this { -public: - capture_e snapshot(::platf::img_t *img_base, bool cursor_visible) override { - auto img = (img_t*)img_base; - - HRESULT status; - - DXGI_OUTDUPL_FRAME_INFO frame_info; - - resource_t::pointer res_p {}; - auto capture_status = dup.next_frame(frame_info, &res_p); - resource_t res{res_p}; - - if (capture_status != capture_e::ok) { - return capture_status; - } - - if(frame_info.PointerShapeBufferSize > 0) { - auto &img_data = cursor.img_data; - - img_data.resize(frame_info.PointerShapeBufferSize); - - UINT dummy; - status = dup.dup->GetFramePointerShape(img_data.size(), img_data.data(), &dummy, &cursor.shape_info); - if (FAILED(status)) { - BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']'; - - return capture_e::error; - } - } - - if(frame_info.LastMouseUpdateTime.QuadPart) { - cursor.x = frame_info.PointerPosition.Position.x; - cursor.y = frame_info.PointerPosition.Position.y; - cursor.visible = frame_info.PointerPosition.Visible; - } - - // If frame has been updated - if (frame_info.LastPresentTime.QuadPart != 0) { - { - texture2d_t::pointer src_p {}; - status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src_p); - texture2d_t src{src_p}; - - if (FAILED(status)) { - BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']'; - return capture_e::error; - } - - //Copy from GPU to CPU - device_ctx->CopyResource(texture.get(), src.get()); - } - - if(img_info.pData) { - device_ctx->Unmap(texture.get(), 0); - img_info.pData = nullptr; - } - - status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info); - if (FAILED(status)) { - BOOST_LOG(error) << "Failed to map texture [0x"sv << util::hex(status).to_string_view() << ']'; - - return capture_e::error; - } - } - - const bool mouse_update = - (frame_info.LastMouseUpdateTime.QuadPart || frame_info.PointerShapeBufferSize > 0) && - (cursor_visible && cursor.visible); - - const bool update_flag = frame_info.LastPresentTime.QuadPart != 0 || mouse_update; - - if(!update_flag) { - return capture_e::timeout; - } - - if(img->width != width || img->height != height) { - delete[] img->data; - img->data = new std::uint8_t[height * img_info.RowPitch]; - - img->width = width; - img->height = height; - img->row_pitch = img_info.RowPitch; - } - - std::copy_n((std::uint8_t*)img_info.pData, height * img_info.RowPitch, (std::uint8_t*)img->data); - - if(cursor_visible && cursor.visible) { - blend_cursor(cursor, *img); - } - - return capture_e::ok; - } - - std::shared_ptr<::platf::img_t> alloc_img() override { - auto img = std::make_shared(); - - img->data = nullptr; - img->row_pitch = 0; - img->pixel_pitch = 4; - img->width = 0; - img->height = 0; - - return img; - } - - int dummy_img(platf::img_t *img, int &) override { - auto dummy_data_p = new int[1]; - - return platf::display_t::dummy_img(img, *dummy_data_p); - } - - int init() { -/* Uncomment when use of IDXGIOutput5 is implemented - std::call_once(windows_cpp_once_flag, []() { - DECLARE_HANDLE(DPI_AWARENESS_CONTEXT); - const auto DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = ((DPI_AWARENESS_CONTEXT)-4); - - typedef BOOL (*User32_SetProcessDpiAwarenessContext)(DPI_AWARENESS_CONTEXT value); - - auto user32 = LoadLibraryA("user32.dll"); - auto f = (User32_SetProcessDpiAwarenessContext)GetProcAddress(user32, "SetProcessDpiAwarenessContext"); - if(f) { - f(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); - } - - FreeLibrary(user32); - }); -*/ - dxgi::factory1_t::pointer factory_p {}; - dxgi::adapter_t::pointer adapter_p {}; - dxgi::output_t::pointer output_p {}; - dxgi::device_t::pointer device_p {}; - dxgi::device_ctx_t::pointer device_ctx_p {}; - - HRESULT status; - - status = CreateDXGIFactory1(IID_IDXGIFactory1, (void**)&factory_p); - factory.reset(factory_p); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - std::wstring_convert, wchar_t> converter; - - auto adapter_name = converter.from_bytes(config::video.adapter_name); - auto output_name = converter.from_bytes(config::video.output_name); - - for(int x = 0; factory_p->EnumAdapters1(x, &adapter_p) != DXGI_ERROR_NOT_FOUND; ++x) { - dxgi::adapter_t adapter_tmp { adapter_p }; - - DXGI_ADAPTER_DESC1 adapter_desc; - adapter_tmp->GetDesc1(&adapter_desc); - - if(!adapter_name.empty() && adapter_desc.Description != adapter_name) { - continue; - } - - for(int y = 0; adapter_tmp->EnumOutputs(y, &output_p) != DXGI_ERROR_NOT_FOUND; ++y) { - dxgi::output_t output_tmp {output_p }; - - DXGI_OUTPUT_DESC desc; - output_tmp->GetDesc(&desc); - - if(!output_name.empty() && desc.DeviceName != output_name) { - continue; - } - - if(desc.AttachedToDesktop) { - output = std::move(output_tmp); - - width = desc.DesktopCoordinates.right - desc.DesktopCoordinates.left; - height = desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top; - } - } - - if(output) { - adapter = std::move(adapter_tmp); - break; - } - } - - if(!output) { - BOOST_LOG(error) << "Failed to locate an output device"sv; - return -1; - } - - D3D_FEATURE_LEVEL featureLevels[] { - D3D_FEATURE_LEVEL_12_1, - D3D_FEATURE_LEVEL_12_0, - D3D_FEATURE_LEVEL_11_1, - D3D_FEATURE_LEVEL_11_0, - D3D_FEATURE_LEVEL_10_1, - D3D_FEATURE_LEVEL_10_0, - D3D_FEATURE_LEVEL_9_3, - D3D_FEATURE_LEVEL_9_2, - D3D_FEATURE_LEVEL_9_1 - }; - - status = adapter->QueryInterface(IID_IDXGIAdapter, (void**)&adapter_p); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to query IDXGIAdapter interface"sv; - - return -1; - } - - status = D3D11CreateDevice( - adapter_p, - D3D_DRIVER_TYPE_UNKNOWN, - nullptr, - D3D11_CREATE_DEVICE_VIDEO_SUPPORT, - featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL), - D3D11_SDK_VERSION, - &device_p, - &feature_level, - &device_ctx_p); - - adapter_p->Release(); - - device.reset(device_p); - device_ctx.reset(device_ctx_p); - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create D3D11 device [0x"sv << util::hex(status).to_string_view() << ']'; - - return -1; - } - - - DXGI_ADAPTER_DESC adapter_desc; - adapter->GetDesc(&adapter_desc); - - auto description = converter.to_bytes(adapter_desc.Description); - BOOST_LOG(info) << std::endl - << "Device Description : " << description << std::endl - << "Device Vendor ID : 0x"sv << util::hex(adapter_desc.VendorId).to_string_view() << std::endl - << "Device Device ID : 0x"sv << util::hex(adapter_desc.DeviceId).to_string_view() << std::endl - << "Device Video Mem : "sv << adapter_desc.DedicatedVideoMemory / 1048576 << " MiB"sv << std::endl - << "Device Sys Mem : "sv << adapter_desc.DedicatedSystemMemory / 1048576 << " MiB"sv << std::endl - << "Share Sys Mem : "sv << adapter_desc.SharedSystemMemory / 1048576 << " MiB"sv << std::endl - << "Feature Level : 0x"sv << util::hex(feature_level).to_string_view() << std::endl - << "Capture size : "sv << width << 'x' << height; - - // Bump up thread priority - { - dxgi::dxgi_t::pointer dxgi_p {}; - status = device->QueryInterface(IID_IDXGIDevice, (void**)&dxgi_p); - dxgi::dxgi_t dxgi { dxgi_p }; - - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - dxgi->SetGPUThreadPriority(7); - } - - // Try to reduce latency - { - dxgi::dxgi1_t::pointer dxgi_p {}; - status = device->QueryInterface(IID_IDXGIDevice, (void**)&dxgi_p); - dxgi::dxgi1_t dxgi { dxgi_p }; - - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - dxgi->SetMaximumFrameLatency(1); - } - - //FIXME: Duplicate output on RX580 in combination with DOOM (2016) --> BSOD - //TODO: Use IDXGIOutput5 for improved performance - { - dxgi::output1_t::pointer output1_p {}; - status = output->QueryInterface(IID_IDXGIOutput1, (void**)&output1_p); - dxgi::output1_t output1 {output1_p }; - - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to query IDXGIOutput1 from the output"sv; - return -1; - } - - // We try this twice, in case we still get an error on reinitialization - for(int x = 0; x < 2; ++x) { - dxgi::dup_t::pointer dup_p {}; - status = output1->DuplicateOutput((IUnknown*)device.get(), &dup_p); - if(SUCCEEDED(status)) { - dup.reset(dup_p); - break; - } - std::this_thread::sleep_for(200ms); - } - - if(FAILED(status)) { - BOOST_LOG(error) << "DuplicateOutput Failed [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - } - - DXGI_OUTDUPL_DESC dup_desc; - dup.dup->GetDesc(&dup_desc); - - format = dup_desc.ModeDesc.Format; - - BOOST_LOG(debug) << "Source format ["sv << format_str[dup_desc.ModeDesc.Format] << ']'; - - D3D11_TEXTURE2D_DESC t {}; - t.Width = width; - t.Height = height; - t.MipLevels = 1; - t.ArraySize = 1; - t.SampleDesc.Count = 1; - t.Usage = D3D11_USAGE_STAGING; - t.Format = format; - t.CPUAccessFlags = D3D11_CPU_ACCESS_READ; - - dxgi::texture2d_t::pointer tex_p {}; - status = device->CreateTexture2D(&t, nullptr, &tex_p); - - texture.reset(tex_p); - - if(FAILED(status)) { - BOOST_LOG(error) << "Failed to create texture [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - // map the texture simply to get the pitch and stride - status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info); - if(FAILED(status)) { - BOOST_LOG(error) << "Error: Failed to map the texture [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - return 0; - } - - factory1_t factory; - adapter_t adapter; - output_t output; - device_t device; - device_ctx_t device_ctx; - duplication_t dup; - cursor_t cursor; - texture2d_t texture; - - int width, height; - - DXGI_FORMAT format; - D3D_FEATURE_LEVEL feature_level; - D3D11_MAPPED_SUBRESOURCE img_info; -}; - -const char *format_str[] = { - "DXGI_FORMAT_UNKNOWN", - "DXGI_FORMAT_R32G32B32A32_TYPELESS", - "DXGI_FORMAT_R32G32B32A32_FLOAT", - "DXGI_FORMAT_R32G32B32A32_UINT", - "DXGI_FORMAT_R32G32B32A32_SINT", - "DXGI_FORMAT_R32G32B32_TYPELESS", - "DXGI_FORMAT_R32G32B32_FLOAT", - "DXGI_FORMAT_R32G32B32_UINT", - "DXGI_FORMAT_R32G32B32_SINT", - "DXGI_FORMAT_R16G16B16A16_TYPELESS", - "DXGI_FORMAT_R16G16B16A16_FLOAT", - "DXGI_FORMAT_R16G16B16A16_UNORM", - "DXGI_FORMAT_R16G16B16A16_UINT", - "DXGI_FORMAT_R16G16B16A16_SNORM", - "DXGI_FORMAT_R16G16B16A16_SINT", - "DXGI_FORMAT_R32G32_TYPELESS", - "DXGI_FORMAT_R32G32_FLOAT", - "DXGI_FORMAT_R32G32_UINT", - "DXGI_FORMAT_R32G32_SINT", - "DXGI_FORMAT_R32G8X24_TYPELESS", - "DXGI_FORMAT_D32_FLOAT_S8X24_UINT", - "DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS", - "DXGI_FORMAT_X32_TYPELESS_G8X24_UINT", - "DXGI_FORMAT_R10G10B10A2_TYPELESS", - "DXGI_FORMAT_R10G10B10A2_UNORM", - "DXGI_FORMAT_R10G10B10A2_UINT", - "DXGI_FORMAT_R11G11B10_FLOAT", - "DXGI_FORMAT_R8G8B8A8_TYPELESS", - "DXGI_FORMAT_R8G8B8A8_UNORM", - "DXGI_FORMAT_R8G8B8A8_UNORM_SRGB", - "DXGI_FORMAT_R8G8B8A8_UINT", - "DXGI_FORMAT_R8G8B8A8_SNORM", - "DXGI_FORMAT_R8G8B8A8_SINT", - "DXGI_FORMAT_R16G16_TYPELESS", - "DXGI_FORMAT_R16G16_FLOAT", - "DXGI_FORMAT_R16G16_UNORM", - "DXGI_FORMAT_R16G16_UINT", - "DXGI_FORMAT_R16G16_SNORM", - "DXGI_FORMAT_R16G16_SINT", - "DXGI_FORMAT_R32_TYPELESS", - "DXGI_FORMAT_D32_FLOAT", - "DXGI_FORMAT_R32_FLOAT", - "DXGI_FORMAT_R32_UINT", - "DXGI_FORMAT_R32_SINT", - "DXGI_FORMAT_R24G8_TYPELESS", - "DXGI_FORMAT_D24_UNORM_S8_UINT", - "DXGI_FORMAT_R24_UNORM_X8_TYPELESS", - "DXGI_FORMAT_X24_TYPELESS_G8_UINT", - "DXGI_FORMAT_R8G8_TYPELESS", - "DXGI_FORMAT_R8G8_UNORM", - "DXGI_FORMAT_R8G8_UINT", - "DXGI_FORMAT_R8G8_SNORM", - "DXGI_FORMAT_R8G8_SINT", - "DXGI_FORMAT_R16_TYPELESS", - "DXGI_FORMAT_R16_FLOAT", - "DXGI_FORMAT_D16_UNORM", - "DXGI_FORMAT_R16_UNORM", - "DXGI_FORMAT_R16_UINT", - "DXGI_FORMAT_R16_SNORM", - "DXGI_FORMAT_R16_SINT", - "DXGI_FORMAT_R8_TYPELESS", - "DXGI_FORMAT_R8_UNORM", - "DXGI_FORMAT_R8_UINT", - "DXGI_FORMAT_R8_SNORM", - "DXGI_FORMAT_R8_SINT", - "DXGI_FORMAT_A8_UNORM", - "DXGI_FORMAT_R1_UNORM", - "DXGI_FORMAT_R9G9B9E5_SHAREDEXP", - "DXGI_FORMAT_R8G8_B8G8_UNORM", - "DXGI_FORMAT_G8R8_G8B8_UNORM", - "DXGI_FORMAT_BC1_TYPELESS", - "DXGI_FORMAT_BC1_UNORM", - "DXGI_FORMAT_BC1_UNORM_SRGB", - "DXGI_FORMAT_BC2_TYPELESS", - "DXGI_FORMAT_BC2_UNORM", - "DXGI_FORMAT_BC2_UNORM_SRGB", - "DXGI_FORMAT_BC3_TYPELESS", - "DXGI_FORMAT_BC3_UNORM", - "DXGI_FORMAT_BC3_UNORM_SRGB", - "DXGI_FORMAT_BC4_TYPELESS", - "DXGI_FORMAT_BC4_UNORM", - "DXGI_FORMAT_BC4_SNORM", - "DXGI_FORMAT_BC5_TYPELESS", - "DXGI_FORMAT_BC5_UNORM", - "DXGI_FORMAT_BC5_SNORM", - "DXGI_FORMAT_B5G6R5_UNORM", - "DXGI_FORMAT_B5G5R5A1_UNORM", - "DXGI_FORMAT_B8G8R8A8_UNORM", - "DXGI_FORMAT_B8G8R8X8_UNORM", - "DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM", - "DXGI_FORMAT_B8G8R8A8_TYPELESS", - "DXGI_FORMAT_B8G8R8A8_UNORM_SRGB", - "DXGI_FORMAT_B8G8R8X8_TYPELESS", - "DXGI_FORMAT_B8G8R8X8_UNORM_SRGB", - "DXGI_FORMAT_BC6H_TYPELESS", - "DXGI_FORMAT_BC6H_UF16", - "DXGI_FORMAT_BC6H_SF16", - "DXGI_FORMAT_BC7_TYPELESS", - "DXGI_FORMAT_BC7_UNORM", - "DXGI_FORMAT_BC7_UNORM_SRGB", - "DXGI_FORMAT_AYUV", - "DXGI_FORMAT_Y410", - "DXGI_FORMAT_Y416", - "DXGI_FORMAT_NV12", - "DXGI_FORMAT_P010", - "DXGI_FORMAT_P016", - "DXGI_FORMAT_420_OPAQUE", - "DXGI_FORMAT_YUY2", - "DXGI_FORMAT_Y210", - "DXGI_FORMAT_Y216", - "DXGI_FORMAT_NV11", - "DXGI_FORMAT_AI44", - "DXGI_FORMAT_IA44", - "DXGI_FORMAT_P8", - "DXGI_FORMAT_A8P8", - "DXGI_FORMAT_B4G4R4A4_UNORM", - - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - - "DXGI_FORMAT_P208", - "DXGI_FORMAT_V208", - "DXGI_FORMAT_V408" -}; - -} - -namespace platf { -std::shared_ptr display(int hwdevice_type) { - if(hwdevice_type != AV_HWDEVICE_TYPE_NONE) { - return nullptr; - } - - auto disp = std::make_shared(); - - if (disp->init()) { - return nullptr; - } - - return disp; -} -} diff --git a/sunshine/round_robin.h b/sunshine/round_robin.h old mode 100755 new mode 100644 index b3ae4475..47e125db --- a/sunshine/round_robin.h +++ b/sunshine/round_robin.h @@ -118,6 +118,14 @@ public: } } + void dec() { + if(_pos == _begin) { + _pos = _end; + } + + --_pos; + } + bool eq(const round_robin_t &other) const { return *_pos == *other._pos; } diff --git a/sunshine/rtsp.cpp b/sunshine/rtsp.cpp index 565b3cde..70aac786 100644 --- a/sunshine/rtsp.cpp +++ b/sunshine/rtsp.cpp @@ -97,7 +97,7 @@ public: std::vector full_payload; auto old_msg = std::move(_queue_packet); - TUPLE_2D_REF(_, old_packet, old_msg); + auto &old_packet = old_msg.second; std::string_view new_payload{(char *) packet->data, packet->dataLength}; std::string_view old_payload{(char *) old_packet->data, old_packet->dataLength}; @@ -274,7 +274,7 @@ void cmd_describe(rtsp_server_t *server, net::peer_t peer, msg_t&& req) { option.content = const_cast(seqn_str.c_str()); std::string_view payload; - if(config::video.hevc_mode == 0) { + if(config::video.hevc_mode == 1) { payload = "surround-params=NONE"sv; } else { @@ -404,7 +404,7 @@ void cmd_announce(rtsp_server_t *server, net::peer_t peer, msg_t &&req) { return; } - if(config.monitor.videoFormat != 0 && config::video.hevc_mode == 0) { + if(config.monitor.videoFormat != 0 && config::video.hevc_mode == 1) { BOOST_LOG(warning) << "HEVC is disabled, yet the client requested HEVC"sv; respond(server->host(), peer, &option, 400, "BAD REQUEST", req->sequenceNumber, {}); diff --git a/sunshine/stream.cpp b/sunshine/stream.cpp index c692d7c0..08899f80 100644 --- a/sunshine/stream.cpp +++ b/sunshine/stream.cpp @@ -494,8 +494,11 @@ void controlBroadcastThread(safe::signal_t *shutdown_event, control_server_t *se server->send(std::string_view {(char*)payload.data(), payload.size()}); - shutdown_event->raise(true); - continue; + auto lg = server->_map_addr_session.lock(); + for(auto pos = std::begin(*server->_map_addr_session); pos != std::end(*server->_map_addr_session); ++pos) { + auto session = pos->second.second; + session->shutdown_event.raise(true); + } } server->iterate(500ms); @@ -614,8 +617,6 @@ void videoBroadcastThread(safe::signal_t *shutdown_event, udp::socket &sock, vid frame_new = "\000\000\000\001("sv; } - assert(std::search(std::begin(payload), std::end(payload), std::begin(frame_new), std::end(frame_new)) == - std::end(payload)); payload_new = replace(payload, frame_old, frame_new); payload = {(char *) payload_new.data(), payload_new.size()}; } diff --git a/sunshine/thread_safe.h b/sunshine/thread_safe.h index c593fcff..1d4f12e6 100644 --- a/sunshine/thread_safe.h +++ b/sunshine/thread_safe.h @@ -33,7 +33,7 @@ public: // pop and view shoud not be used interchangebly status_t pop() { - std::unique_lock ul{_lock}; + std::unique_lock ul{ _lock }; if (!_continue) { return util::false_v; @@ -55,7 +55,7 @@ public: // pop and view shoud not be used interchangebly template status_t pop(std::chrono::duration delay) { - std::unique_lock ul{_lock}; + std::unique_lock ul{ _lock }; if (!_continue) { return util::false_v; @@ -74,7 +74,7 @@ public: // pop and view shoud not be used interchangebly const status_t &view() { - std::unique_lock ul{_lock}; + std::unique_lock ul{ _lock }; if (!_continue) { return util::false_v; @@ -98,7 +98,7 @@ public: } void stop() { - std::lock_guard lg{_lock}; + std::lock_guard lg{ _lock }; _continue = false; @@ -106,7 +106,7 @@ public: } void reset() { - std::lock_guard lg{_lock}; + std::lock_guard lg{ _lock }; _continue = true; @@ -118,8 +118,8 @@ public: } private: - bool _continue{true}; - status_t _status; + bool _continue { true }; + status_t _status { util::false_v }; std::condition_variable _cv; std::mutex _lock; @@ -170,7 +170,7 @@ public: } status_t pop() { - std::unique_lock ul{_lock}; + std::unique_lock ul{ _lock }; if (!_continue) { return util::false_v; @@ -191,11 +191,12 @@ public: } std::vector &unsafe() { + std::lock_guard { _lock }; return _queue; } void stop() { - std::lock_guard lg{_lock}; + std::lock_guard lg{ _lock }; _continue = false; @@ -208,7 +209,7 @@ public: private: - bool _continue{true}; + bool _continue{ true }; std::mutex _lock; std::condition_variable _cv; @@ -274,9 +275,8 @@ public: void release() { std::lock_guard lg { owner->_lock }; - auto c = owner->_count.fetch_sub(1, std::memory_order_acquire); - if(c - 1 == 0) { + if(!--owner->_count) { owner->_destruct(*get()); (*this)->~element_type(); } @@ -296,10 +296,9 @@ public: template shared_t(FC && fc, FD &&fd) : _construct { std::forward(fc) }, _destruct { std::forward(fd) } {} [[nodiscard]] ptr_t ref() { - auto c = _count.fetch_add(1, std::memory_order_acquire); - if(!c) { - std::lock_guard lg { _lock }; + std::lock_guard lg { _lock }; + if(!_count++) { new(_object_buf.data()) element_type; if(_construct(*reinterpret_cast(_object_buf.data()))) { return ptr_t { nullptr }; @@ -314,7 +313,7 @@ private: std::array _object_buf; - std::atomic _count; + std::uint32_t _count; std::mutex _lock; }; diff --git a/sunshine/utility.h b/sunshine/utility.h index 1ac3d5f6..19f9a985 100644 --- a/sunshine/utility.h +++ b/sunshine/utility.h @@ -436,6 +436,80 @@ public: } }; + +template +class wrap_ptr { +public: + using element_type = T; + using pointer = element_type*; + using reference = element_type&; + + wrap_ptr() : _own_ptr { false }, _p { nullptr } {} + wrap_ptr(pointer p) : _own_ptr { false }, _p { p } {} + wrap_ptr(std::unique_ptr &&uniq_p) : _own_ptr { true }, _p { uniq_p.release() } {} + wrap_ptr(wrap_ptr &&other) : _own_ptr { other._own_ptr }, _p { other._p } { + other._own_ptr = false; + } + + wrap_ptr &operator=(wrap_ptr &&other) noexcept { + if(_own_ptr) { + delete _p; + } + + _p = other._p; + + _own_ptr = other._own_ptr; + other._own_ptr = false; + + return *this; + } + + template + wrap_ptr &operator=(std::unique_ptr &&uniq_ptr) { + static_assert(std::is_base_of_v, "element_type must be base class of V"); + _own_ptr = true; + _p = uniq_ptr.release(); + + return *this; + } + + wrap_ptr &operator=(pointer p) { + if(_own_ptr) { + delete _p; + } + + _p = p; + _own_ptr = false; + + return *this; + } + + ~wrap_ptr() { + if(_own_ptr) { + delete _p; + } + + _own_ptr = false; + } + + const reference operator*() const { + return *_p; + } + reference operator*() { + return *_p; + } + const pointer operator->() const { + return _p; + } + pointer operator->() { + return _p; + } + +private: + bool _own_ptr; + pointer _p; +}; + template class buffer_t { public: diff --git a/sunshine/video.cpp b/sunshine/video.cpp index eeddcfc4..c11c353d 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -4,6 +4,7 @@ #include #include +#include extern "C" { #include @@ -16,6 +17,12 @@ extern "C" { #include "video.h" #include "main.h" +#ifdef _WIN32 +extern "C" { +#include +} +#endif + namespace video { using namespace std::literals; @@ -35,20 +42,102 @@ void free_packet(AVPacket *packet) { av_packet_free(&packet); } +namespace nv { + +enum class profile_h264_e : int { + baseline, + main, + high, + high_444p, +}; + +enum class profile_hevc_e : int { + main, + main_10, + rext, +}; +} + using ctx_t = util::safe_ptr; -using codec_t = util::safe_ptr_v2; using frame_t = util::safe_ptr; using buffer_t = util::safe_ptr; using sws_t = util::safe_ptr; using img_event_t = std::shared_ptr>>; -void sw_img_to_frame(sws_t &sws, platf::img_t &img, frame_t &frame); -void nv_d3d_img_to_frame(sws_t &sws, platf::img_t &img, frame_t &frame); +platf::dev_type_e map_dev_type(AVHWDeviceType type); +platf::pix_fmt_e map_pix_fmt(AVPixelFormat fmt); + +void sw_img_to_frame(const platf::img_t &img, frame_t &frame); +void nv_d3d_img_to_frame(const platf::img_t &img, frame_t &frame); +util::Either nv_d3d_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx); + +util::Either make_hwdevice_ctx(AVHWDeviceType type, void *hwdevice_ctx); +int hwframe_ctx(ctx_t &ctx, buffer_t &hwdevice, AVPixelFormat format); + +class swdevice_t : public platf::hwdevice_t { +public: + int convert(platf::img_t &img) override { + auto frame = (AVFrame *)data; + + av_frame_make_writable(frame); + + const int linesizes[2] { + img.row_pitch, 0 + }; + + int ret = sws_scale(sws.get(), (std::uint8_t*const*)&img.data, linesizes, 0, img.height, frame->data, frame->linesize); + if(ret <= 0) { + BOOST_LOG(fatal) << "Couldn't convert image to required format and/or size"sv; + + return -1; + } + + return 0; + } + + virtual void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) { + sws_setColorspaceDetails(sws.get(), + sws_getCoefficients(SWS_CS_DEFAULT), 0, + sws_getCoefficients(colorspace), color_range -1, + 0, 1 << 16, 1 << 16 + ); + } + + int init(int in_width, int in_height, int out_width, int out_height, AVFrame *frame, AVPixelFormat format) { + sws.reset(sws_getContext( + in_width, in_height, AV_PIX_FMT_BGR0, + out_width, out_height, format, + SWS_LANCZOS | SWS_ACCURATE_RND, + nullptr, nullptr, nullptr + )); + data = frame; + + return sws ? 0 : -1; + } + + ~swdevice_t() override {} + + sws_t sws; +}; struct encoder_t { + std::string_view name; + enum flag_e { + PASSED, // Is supported + REF_FRAMES_RESTRICT, // Set maximum reference frames + REF_FRAMES_AUTOSELECT, // Allow encoder to select maximum reference frames (If !REF_FRAMES_RESTRICT --> REF_FRAMES_AUTOSELECT) + DYNAMIC_RANGE, + MAX_FLAGS + }; + struct option_t { + KITTY_DEFAULT_CONSTR(option_t) + option_t(const option_t &) = default; + std::string name; - std::variant value; + std::variant*, std::string, std::string*> value; + + option_t(std::string &&name, decltype(value) &&value) : name { std::move(name) }, value { std::move(value) } {} }; struct { @@ -58,86 +147,88 @@ struct encoder_t { } profile; AVHWDeviceType dev_type; + AVPixelFormat dev_pix_fmt; - AVPixelFormat pix_fmt; + AVPixelFormat static_pix_fmt; + AVPixelFormat dynamic_pix_fmt; struct { std::vector options; + std::optional crf, qp; + std::string name; + std::bitset capabilities; + + bool operator[](flag_e flag) const { + return capabilities[(std::size_t)flag]; + } + + std::bitset::reference operator[](flag_e flag) { + return capabilities[(std::size_t)flag]; + } } hevc, h264; bool system_memory; + bool hevc_mode; - std::function img_to_frame; + std::function img_to_frame; + std::function(platf::hwdevice_t *hwdevice)> make_hwdevice_ctx; }; -struct session_t { - buffer_t hwdevice; +class session_t { +public: + session_t() = default; + session_t(ctx_t &&ctx, frame_t &&frame, util::wrap_ptr &&device) : + ctx { std::move(ctx) }, frame { std::move(frame) }, device { std::move(device) } {} + + session_t(session_t &&other) : + ctx { std::move(other.ctx) }, frame { std::move(other.frame) }, device { std::move(other.device) } {} + + // Ensure objects are destroyed in the correct order + session_t &operator=(session_t &&other) { + device = std::move(other.device); + frame = std::move(other.frame); + ctx = std::move(other.ctx); + + return *this; + } ctx_t ctx; - codec_t codec_handle; - frame_t frame; - - AVPixelFormat sw_format; - int sws_color_format; + util::wrap_ptr device; }; -static encoder_t nvenc { - { 2, 0, 1 }, - AV_HWDEVICE_TYPE_D3D11VA, - AV_PIX_FMT_D3D11, - { - { {"force-idr"s, 1} }, "nvenc_hevc"s - }, - { - { {"force-idr"s, 1} }, "nvenc_h264"s - }, - false, - - nv_d3d_img_to_frame - - // D3D11Device +struct sync_session_ctx_t { + safe::signal_t *shutdown_event; + safe::signal_t *join_event; + packet_queue_t packets; + idr_event_t idr_events; + config_t config; + int frame_nr; + int key_frame_nr; + void *channel_data; }; -static encoder_t software { - { FF_PROFILE_H264_HIGH, FF_PROFILE_HEVC_MAIN, FF_PROFILE_HEVC_MAIN_10 }, - AV_HWDEVICE_TYPE_NONE, - AV_PIX_FMT_NONE, - { - // x265's Info SEI is so long that it causes the IDR picture data to be - // kicked to the 2nd packet in the frame, breaking Moonlight's parsing logic. - // It also looks like gop_size isn't passed on to x265, so we have to set - // 'keyint=-1' in the parameters ourselves. - { - { "x265-params"s, "info=0:keyint=-1"s }, - { "preset"s, &config::video.preset }, - { "tune"s, &config::video.tune } - }, "libx265"s - }, - { - { - { "preset"s, &config::video.preset }, - { "tune"s, &config::video.tune } - }, "libx264"s - }, - true, +struct sync_session_t { + sync_session_ctx_t *ctx; + + std::chrono::steady_clock::time_point next_frame; + std::chrono::nanoseconds delay; - sw_img_to_frame - - // nullptr + platf::img_t *img_tmp; + std::shared_ptr hwdevice; + session_t session; }; -static std::vector encoders { - nvenc, software -}; +using encode_session_ctx_queue_t = safe::queue_t; +using encode_e = platf::capture_e; struct capture_ctx_t { img_event_t images; std::chrono::nanoseconds delay; }; -struct capture_thread_ctx_t { +struct capture_thread_async_ctx_t { std::shared_ptr> capture_ctx_queue; std::thread capture_thread; @@ -146,10 +237,107 @@ struct capture_thread_ctx_t { util::sync_t> display_wp; }; -[[nodiscard]] codec_t open_codec(ctx_t &ctx, AVCodec *codec, AVDictionary **options) { - avcodec_open2(ctx.get(), codec, options); +struct capture_thread_sync_ctx_t { + encode_session_ctx_queue_t encode_session_ctx_queue; +}; - return codec_t { ctx.get() }; +int start_capture_sync(capture_thread_sync_ctx_t &ctx); +void end_capture_sync(capture_thread_sync_ctx_t &ctx); +int start_capture_async(capture_thread_async_ctx_t &ctx); +void end_capture_async(capture_thread_async_ctx_t &ctx); + +// Keep a reference counter to ensure the capture thread only runs when other threads have a reference to the capture thread +auto capture_thread_async = safe::make_shared(start_capture_async, end_capture_async); +auto capture_thread_sync = safe::make_shared(start_capture_sync, end_capture_sync); + +#ifdef _WIN32 +static encoder_t nvenc { + "nvenc"sv, + { (int)nv::profile_h264_e::high, (int)nv::profile_hevc_e::main, (int)nv::profile_hevc_e::main_10 }, + AV_HWDEVICE_TYPE_D3D11VA, + AV_PIX_FMT_D3D11, + AV_PIX_FMT_NV12, AV_PIX_FMT_P010, + { + { + { "forced-idr"s, 1 }, + { "zerolatency"s, 1 }, + { "preset"s, &config::video.nv.preset }, + { "rc"s, &config::video.nv.rc } + }, + std::nullopt, std::nullopt, + "hevc_nvenc"s, + }, + { + { + { "forced-idr"s, 1 }, + { "zerolatency"s, 1 }, + { "preset"s, &config::video.nv.preset }, + { "rc"s, &config::video.nv.rc }, + { "coder"s, &config::video.nv.coder } + }, + std::nullopt, std::make_optional({"qp"s, &config::video.qp}), + "h264_nvenc"s + }, + false, + true, + + nv_d3d_img_to_frame, + nv_d3d_make_hwdevice_ctx +}; +#endif + +static encoder_t software { + "software"sv, + { FF_PROFILE_H264_HIGH, FF_PROFILE_HEVC_MAIN, FF_PROFILE_HEVC_MAIN_10 }, + AV_HWDEVICE_TYPE_NONE, + AV_PIX_FMT_NONE, + AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P10, + { + // x265's Info SEI is so long that it causes the IDR picture data to be + // kicked to the 2nd packet in the frame, breaking Moonlight's parsing logic. + // It also looks like gop_size isn't passed on to x265, so we have to set + // 'keyint=-1' in the parameters ourselves. + { + { "x265-params"s, "info=0:keyint=-1"s }, + { "preset"s, &config::video.sw.preset }, + { "tune"s, &config::video.sw.tune } + }, + std::make_optional("crf"s, &config::video.crf), std::make_optional("qp"s, &config::video.qp), + "libx265"s + }, + { + { + { "preset"s, &config::video.sw.preset }, + { "tune"s, &config::video.sw.tune } + }, + std::make_optional("crf"s, &config::video.crf), std::make_optional("qp"s, &config::video.qp), + "libx264"s + }, + true, + false, + + sw_img_to_frame, + nullptr +}; + +static std::vector encoders { +#ifdef _WIN32 + nvenc, +#endif + software +}; + +void reset_display(std::shared_ptr &disp, AVHWDeviceType type) { + // We try this twice, in case we still get an error on reinitialization + for(int x = 0; x < 2; ++x) { + disp.reset(); + disp = platf::display(map_dev_type(type)); + if(disp) { + break; + } + + std::this_thread::sleep_for(200ms); + } } void captureThread( @@ -174,16 +362,15 @@ void captureThread( std::chrono::nanoseconds delay = 1s; - auto disp = platf::display(encoder.dev_type); + auto disp = platf::display(map_dev_type(encoder.dev_type)); if(!disp) { return; } display_wp = disp; std::vector> imgs(12); - auto round_robin = util::make_round_robin>(std::begin(imgs) +1, std::end(imgs)); + auto round_robin = util::make_round_robin>(std::begin(imgs), std::end(imgs)); - int dummy_data = 0; for(auto &img : imgs) { img = disp->alloc_img(); if(!img) { @@ -191,28 +378,27 @@ void captureThread( return; } } - auto &dummy_img = imgs.front(); - disp->dummy_img(dummy_img.get(), dummy_data); + + if(auto capture_ctx = capture_ctx_queue->pop()) { + capture_ctxs.emplace_back(std::move(*capture_ctx)); + + delay = capture_ctxs.back().delay; + } auto next_frame = std::chrono::steady_clock::now(); while(capture_ctx_queue->running()) { while(capture_ctx_queue->peek()) { capture_ctxs.emplace_back(std::move(*capture_ctx_queue->pop())); - // Temporary image to ensure something is send to Moonlight even if no frame has been captured yet. - capture_ctxs.back().images->raise(dummy_img); - delay = std::min(delay, capture_ctxs.back().delay); } auto now = std::chrono::steady_clock::now(); - if(next_frame > now) { - std::this_thread::sleep_until(next_frame); - } - next_frame += delay; auto &img = *round_robin++; - auto status = disp->snapshot(img.get(), display_cursor); + while(img.use_count() > 1) {} + + auto status = disp->snapshot(img.get(), 1000ms, display_cursor); switch (status) { case platf::capture_e::reinit: { reinit_event.raise(true); @@ -222,24 +408,15 @@ void captureThread( img.reset(); } - // We try this twice, in case we still get an error on reinitialization - for(int x = 0; x < 2; ++x) { - // Some classes of display cannot have multiple instances at once - disp.reset(); + // Some classes of display cannot have multiple instances at once + disp.reset(); - // display_wp is modified in this thread only - while(!display_wp->expired()) { - std::this_thread::sleep_for(100ms); - } - - disp = platf::display(encoder.dev_type); - if(disp) { - break; - } - - std::this_thread::sleep_for(200ms); + // display_wp is modified in this thread only + while(!display_wp->expired()) { + std::this_thread::sleep_for(100ms); } + reset_display(disp, encoder.dev_type); if(!disp) { return; } @@ -253,14 +430,14 @@ void captureThread( return; } } - disp->dummy_img(dummy_img.get(), dummy_data); reinit_event.reset(); continue; } case platf::capture_e::error: - return; + return; case platf::capture_e::timeout: + std::this_thread::sleep_for(1ms); continue; case platf::capture_e::ok: break; @@ -285,49 +462,23 @@ void captureThread( capture_ctx->images->raise(img); ++capture_ctx; }) + + if(next_frame > now) { + std::this_thread::sleep_until(next_frame); + } + next_frame += delay; } } -util::Either hwdevice_ctx(AVHWDeviceType type) { - buffer_t ctx; - - AVBufferRef *ref; - auto err = av_hwdevice_ctx_create(&ref, type, nullptr, nullptr, 0); - - ctx.reset(ref); - if(err < 0) { - return err; - } - - return ctx; -} - -int hwframe_ctx(ctx_t &ctx, buffer_t &hwdevice, AVPixelFormat format) { - buffer_t frame_ref { av_hwframe_ctx_alloc(hwdevice.get())}; - - auto frame_ctx = (AVHWFramesContext*)frame_ref->data; - frame_ctx->format = ctx->pix_fmt; - frame_ctx->sw_format = format; - frame_ctx->height = ctx->height; - frame_ctx->width = ctx->width; - frame_ctx->initial_pool_size = 20; - - if(auto err = av_hwframe_ctx_init(frame_ref.get()); err < 0) { - return err; - } - - ctx->hw_frames_ctx = av_buffer_ref(frame_ref.get()); - - return 0; -} - int encode(int64_t frame_nr, ctx_t &ctx, frame_t &frame, packet_queue_t &packets, void *channel_data) { frame->pts = frame_nr; /* send the frame to the encoder */ auto ret = avcodec_send_frame(ctx.get(), frame.get()); if (ret < 0) { - BOOST_LOG(error) << "Could not send a frame for encoding"sv; + char err_str[AV_ERROR_MAX_STRING_SIZE] {0}; + BOOST_LOG(error) << "Could not send a frame for encoding: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, ret); + return -1; } @@ -349,32 +500,19 @@ int encode(int64_t frame_nr, ctx_t &ctx, frame_t &frame, packet_queue_t &packets return 0; } -int start_capture(capture_thread_ctx_t &capture_thread_ctx) { - capture_thread_ctx.encoder_p = &software; - capture_thread_ctx.reinit_event.reset(); - - capture_thread_ctx.capture_ctx_queue = std::make_shared>(); - - capture_thread_ctx.capture_thread = std::thread { - captureThread, - capture_thread_ctx.capture_ctx_queue, - std::ref(capture_thread_ctx.display_wp), - std::ref(capture_thread_ctx.reinit_event), - std::ref(*capture_thread_ctx.encoder_p) - }; - - return 0; -} -void end_capture(capture_thread_ctx_t &capture_thread_ctx) { - capture_thread_ctx.capture_ctx_queue->stop(); - - capture_thread_ctx.capture_thread.join(); -} - -std::optional make_session(const encoder_t &encoder, const config_t &config, void *device_ctx) { +std::optional make_session(const encoder_t &encoder, const config_t &config, int width, int height, platf::hwdevice_t *hwdevice) { bool hardware = encoder.dev_type != AV_HWDEVICE_TYPE_NONE; auto &video_format = config.videoFormat == 0 ? encoder.h264 : encoder.hevc; + if(!video_format[encoder_t::PASSED]) { + BOOST_LOG(error) << encoder.name << ": "sv << video_format.name << " mode not supported"sv; + return std::nullopt; + } + + if(config.dynamicRange && !video_format[encoder_t::DYNAMIC_RANGE]) { + BOOST_LOG(error) << video_format.name << ": dynamic range not supported"sv; + return std::nullopt; + } auto codec = avcodec_find_encoder_by_name(video_format.name.c_str()); if(!codec) { @@ -383,22 +521,7 @@ std::optional make_session(const encoder_t &encoder, const config_t return std::nullopt; } - buffer_t hwdevice; - if(hardware) { - auto buf_or_error = hwdevice_ctx(encoder.dev_type); - if(buf_or_error.has_right()) { - auto err = buf_or_error.right(); - - char err_str[AV_ERROR_MAX_STRING_SIZE] {0}; - BOOST_LOG(error) << "Failed to create FFMpeg "sv << video_format.name << ": "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err); - - return std::nullopt;; - } - - hwdevice = std::move(buf_or_error.left()); - } - - ctx_t ctx {avcodec_alloc_context3(codec) }; + ctx_t ctx { avcodec_alloc_context3(codec) }; ctx->width = config.width; ctx->height = config.height; ctx->time_base = AVRational{1, config.framerate}; @@ -421,8 +544,13 @@ std::optional make_session(const encoder_t &encoder, const config_t ctx->gop_size = std::numeric_limits::max(); ctx->keyint_min = ctx->gop_size; - // Some client decoders have limits on the number of reference frames - ctx->refs = config.numRefFrames; + if(config.numRefFrames == 0) { + ctx->refs = video_format[encoder_t::REF_FRAMES_AUTOSELECT] ? 0 : 16; + } + else { + // Some client decoders have limits on the number of reference frames + ctx->refs = video_format[encoder_t::REF_FRAMES_RESTRICT] ? config.numRefFrames : 0; + } ctx->flags |= (AV_CODEC_FLAG_CLOSED_GOP | AV_CODEC_FLAG_LOW_DELAY); ctx->flags2 |= AV_CODEC_FLAG2_FAST; @@ -459,23 +587,27 @@ std::optional make_session(const encoder_t &encoder, const config_t AVPixelFormat sw_fmt; if(config.dynamicRange == 0) { - sw_fmt = AV_PIX_FMT_YUV420P; + sw_fmt = encoder.static_pix_fmt; } else { - sw_fmt = AV_PIX_FMT_YUV420P10; + sw_fmt = encoder.dynamic_pix_fmt; } + buffer_t hwdevice_ctx; if(hardware) { - ctx->pix_fmt = encoder.pix_fmt; - - ((AVHWFramesContext *)ctx->hw_frames_ctx->data)->device_ctx = (AVHWDeviceContext*)device_ctx; - - if(auto err = hwframe_ctx(ctx, hwdevice, sw_fmt); err < 0) { - char err_str[AV_ERROR_MAX_STRING_SIZE] {0}; - BOOST_LOG(error) << "Failed to initialize hardware frame: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err) << std::endl; + ctx->pix_fmt = encoder.dev_pix_fmt; + auto buf_or_error = encoder.make_hwdevice_ctx(hwdevice); + if(buf_or_error.has_right()) { return std::nullopt; } + + hwdevice_ctx = std::move(buf_or_error.left()); + if(hwframe_ctx(ctx, hwdevice_ctx, sw_fmt)) { + return std::nullopt; + } + + ctx->slices = config.slicesPerFrame; } else /* software */ { ctx->pix_fmt = sw_fmt; @@ -484,65 +616,79 @@ std::optional make_session(const encoder_t &encoder, const config_t // most efficient encode, but we may want to provide more slices than // requested to ensure we have enough parallelism for good performance. ctx->slices = std::max(config.slicesPerFrame, config::video.min_threads); - ctx->thread_type = FF_THREAD_SLICE; - ctx->thread_count = ctx->slices; } + ctx->thread_type = FF_THREAD_SLICE; + ctx->thread_count = ctx->slices; + AVDictionary *options {nullptr}; - for(auto &option : video_format.options) { + auto handle_option = [&options](const encoder_t::option_t &option) { std::visit(util::overloaded { [&](int v) { av_dict_set_int(&options, option.name.c_str(), v, 0); }, [&](int *v) { av_dict_set_int(&options, option.name.c_str(), *v, 0); }, + [&](std::optional *v) { if(*v) av_dict_set_int(&options, option.name.c_str(), **v, 0); }, [&](const std::string &v) { av_dict_set(&options, option.name.c_str(), v.c_str(), 0); }, - [&](std::string *v) { av_dict_set(&options, option.name.c_str(), v->c_str(), 0); } + [&](std::string *v) { if(!v->empty()) av_dict_set(&options, option.name.c_str(), v->c_str(), 0); } }, option.value); + }; + + for(auto &option : video_format.options) { + handle_option(option); } if(config.bitrate > 500) { auto bitrate = config.bitrate * 1000; ctx->rc_max_rate = bitrate; - ctx->rc_buffer_size = bitrate / 100; + ctx->rc_buffer_size = bitrate / config.framerate; ctx->bit_rate = bitrate; ctx->rc_min_rate = bitrate; } - else if(config::video.crf != 0) { - av_dict_set_int(&options, "crf", config::video.crf, 0); + else if(video_format.crf && config::video.crf != 0) { + handle_option(*video_format.crf); + } + else if(video_format.qp) { + handle_option(*video_format.qp); } else { - av_dict_set_int(&options, "qp", config::video.qp, 0); + BOOST_LOG(error) << "Couldn't set video quality: encoder "sv << encoder.name << " doesn't support either crf or qp"sv; + return std::nullopt; } - av_dict_set(&options, "preset", config::video.preset.c_str(), 0); - av_dict_set(&options, "tune", config::video.tune.c_str(), 0); - - auto codec_handle = open_codec(ctx, codec, &options); + avcodec_open2(ctx.get(), codec, &options); frame_t frame {av_frame_alloc() }; frame->format = ctx->pix_fmt; - frame->width = ctx->width; + frame->width = ctx->width; frame->height = ctx->height; if(hardware) { - auto err = av_hwframe_get_buffer(ctx->hw_frames_ctx, frame.get(), 0); - if(err < 0) { - char err_str[AV_ERROR_MAX_STRING_SIZE] {0}; - BOOST_LOG(error) << "Coudn't create hardware frame: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err) << std::endl; - - return std::nullopt; - } + frame->hw_frames_ctx = av_buffer_ref(ctx->hw_frames_ctx); } - else { + else /* software */ { av_frame_get_buffer(frame.get(), 0); } + util::wrap_ptr device; + + if(!hwdevice->data) { + auto device_tmp = std::make_unique(); + + if(device_tmp->init(width, height, config.width, config.height, frame.get(), sw_fmt)) { + return std::nullopt; + } + + device = std::move(device_tmp); + } + else { + device = hwdevice; + } + + device->set_colorspace(sws_color_space, ctx->color_range); return std::make_optional(session_t { - std::move(hwdevice), std::move(ctx), - std::move(codec_handle), std::move(frame), - sw_fmt, - sws_color_space + std::move(device) }); } @@ -553,25 +699,19 @@ void encode_run( idr_event_t idr_events, img_event_t images, config_t config, - platf::display_t &display, + int width, int height, + platf::hwdevice_t *hwdevice, safe::signal_t &reinit_event, const encoder_t &encoder, void *channel_data) { - auto hwdevice = display.get_hwdevice(); - auto session = make_session(encoder, config, hwdevice.get()); + auto session = make_session(encoder, config, width, height, hwdevice); if(!session) { return; } auto delay = std::chrono::floor(1s) / config.framerate; - auto img_width = 0; - auto img_height = 0; - - // Initiate scaling context with correct height and width - sws_t sws; - auto next_frame = std::chrono::steady_clock::now(); while(true) { if(shutdown_event->peek() || reinit_event.peek() || !images->running()) { @@ -582,8 +722,11 @@ void encode_run( session->frame->pict_type = AV_PICTURE_TYPE_I; auto event = idr_events->pop(); - TUPLE_2D_REF(_, end, *event); + if(!event) { + return; + } + auto end = event->second; frame_nr = end; key_frame_nr = end + config.framerate; } @@ -597,28 +740,9 @@ void encode_run( // When Moonlight request an IDR frame, send frames even if there is no new captured frame if(frame_nr > (key_frame_nr + config.framerate) || images->peek()) { if(auto img = images->pop(delay)) { - if(encoder.system_memory) { - auto new_width = img->width; - auto new_height = img->height; + session->device->convert(*img); - if(img_width != new_width || img_height != new_height) { - img_width = new_width; - img_height = new_height; - - sws.reset( - sws_getContext( - img_width, img_height, AV_PIX_FMT_BGR0, - session->ctx->width, session->ctx->height, session->sw_format, - SWS_LANCZOS | SWS_ACCURATE_RND, - nullptr, nullptr, nullptr)); - - sws_setColorspaceDetails(sws.get(), sws_getCoefficients(SWS_CS_DEFAULT), 0, - sws_getCoefficients(session->sws_color_format), config.encoderCscMode & 0x1, - 0, 1 << 16, 1 << 16); - } - } - - encoder.img_to_frame(sws, *img, session->frame); + encoder.img_to_frame(*session->device->img, session->frame); } else if(images->running()) { continue; @@ -627,29 +751,226 @@ void encode_run( break; } } - + if(encode(frame_nr++, session->ctx, session->frame, packets, channel_data)) { - BOOST_LOG(fatal) << "Could not encode video packet"sv; - log_flush(); - std::abort(); + BOOST_LOG(error) << "Could not encode video packet"sv; + return; } session->frame->pict_type = AV_PICTURE_TYPE_NONE; } } -void capture( +std::optional make_synced_session(platf::display_t *disp, const encoder_t &encoder, platf::img_t &img, sync_session_ctx_t &ctx) { + sync_session_t encode_session; + + encode_session.ctx = &ctx; + encode_session.next_frame = std::chrono::steady_clock::now(); + + encode_session.delay = std::chrono::nanoseconds { 1s } / ctx.config.framerate; + + auto pix_fmt = ctx.config.dynamicRange == 0 ? map_pix_fmt(encoder.static_pix_fmt) : map_pix_fmt(encoder.dynamic_pix_fmt); + auto hwdevice = disp->make_hwdevice(ctx.config.width, ctx.config.height, pix_fmt); + if(!hwdevice) { + return std::nullopt; + } + + auto session = make_session(encoder, ctx.config, img.width, img.height, hwdevice.get()); + if(!session) { + return std::nullopt; + } + + encode_session.img_tmp = &img; + encode_session.hwdevice = std::move(hwdevice); + encode_session.session = std::move(*session); + + return std::move(encode_session); +} + +encode_e encode_run_sync(std::vector> &synced_session_ctxs, encode_session_ctx_queue_t &encode_session_ctx_queue) { + const auto &encoder = encoders.front(); + + std::shared_ptr disp; + reset_display(disp, encoder.dev_type); + if(!disp) { + return encode_e::error; + } + + std::vector> imgs(12); + for(auto &img : imgs) { + img = disp->alloc_img(); + } + + auto round_robin = util::make_round_robin>(std::begin(imgs), std::end(imgs)); + + auto dummy_img = disp->alloc_img(); + auto img_tmp = dummy_img.get(); + if(disp->dummy_img(img_tmp)) { + return encode_e::error; + } + + std::vector synced_sessions; + for(auto &ctx : synced_session_ctxs) { + auto synced_session = make_synced_session(disp.get(), encoder, *dummy_img, *ctx); + if(!synced_session) { + return encode_e::error; + } + + synced_sessions.emplace_back(std::move(*synced_session)); + } + + auto next_frame = std::chrono::steady_clock::now(); + while(encode_session_ctx_queue.running()) { + while(encode_session_ctx_queue.peek()) { + auto encode_session_ctx = encode_session_ctx_queue.pop(); + if(!encode_session_ctx) { + return encode_e::ok; + } + + synced_session_ctxs.emplace_back(std::make_unique(std::move(*encode_session_ctx))); + + auto encode_session = make_synced_session(disp.get(), encoder, *dummy_img, *synced_session_ctxs.back()); + if(!encode_session) { + return encode_e::error; + } + + synced_sessions.emplace_back(std::move(*encode_session)); + + next_frame = std::chrono::steady_clock::now(); + } + + auto delay = std::max(0ms, std::chrono::duration_cast(next_frame - std::chrono::steady_clock::now())); + + auto status = disp->snapshot(round_robin->get(), delay, display_cursor); + switch(status) { + case platf::capture_e::reinit: + case platf::capture_e::error: + return status; + case platf::capture_e::timeout: + break; + case platf::capture_e::ok: + img_tmp = round_robin->get(); + ++round_robin; + break; + } + + auto now = std::chrono::steady_clock::now(); + + next_frame = now + 1s; + KITTY_WHILE_LOOP(auto pos = std::begin(synced_sessions), pos != std::end(synced_sessions), { + auto ctx = pos->ctx; + if(ctx->shutdown_event->peek()) { + // Let waiting thread know it can delete shutdown_event + ctx->join_event->raise(true); + + pos = synced_sessions.erase(pos); + synced_session_ctxs.erase(std::find_if(std::begin(synced_session_ctxs), std::end(synced_session_ctxs), [&ctx_p=ctx](auto &ctx) { + return ctx.get() == ctx_p; + })); + + if(synced_sessions.empty()) { + return encode_e::ok; + } + + continue; + } + + if(ctx->idr_events->peek()) { + pos->session.frame->pict_type = AV_PICTURE_TYPE_I; + + auto event = ctx->idr_events->pop(); + auto end = event->second; + + ctx->frame_nr = end; + ctx->key_frame_nr = end + ctx->config.framerate; + } + else if(ctx->frame_nr == ctx->key_frame_nr) { + pos->session.frame->pict_type = AV_PICTURE_TYPE_I; + } + + if(img_tmp) { + pos->img_tmp = img_tmp; + } + + auto timeout = now > pos->next_frame; + if(timeout) { + pos->next_frame += pos->delay; + } + + next_frame = std::min(next_frame, pos->next_frame); + + if(!timeout) { + ++pos; + continue; + } + + if(pos->img_tmp) { + if(pos->hwdevice->convert(*pos->img_tmp)) { + BOOST_LOG(error) << "Could not convert image"sv; + ctx->shutdown_event->raise(true); + + continue; + } + pos->img_tmp = nullptr; + + encoder.img_to_frame(*pos->hwdevice->img, pos->session.frame); + } + + if(encode(ctx->frame_nr++, pos->session.ctx, pos->session.frame, ctx->packets, ctx->channel_data)) { + BOOST_LOG(error) << "Could not encode video packet"sv; + ctx->shutdown_event->raise(true); + + continue; + } + + pos->session.frame->pict_type = AV_PICTURE_TYPE_NONE; + + ++pos; + }) + + img_tmp = nullptr; + } + + return encode_e::ok; +} + +void captureThreadSync() { + auto ref = capture_thread_sync.ref(); + + std::vector> synced_session_ctxs; + + auto &ctx = ref->encode_session_ctx_queue; + auto lg = util::fail_guard([&]() { + ctx.stop(); + + for(auto &ctx : synced_session_ctxs) { + ctx->shutdown_event->raise(true); + ctx->join_event->raise(true); + } + + for(auto &ctx : ctx.unsafe()) { + ctx.shutdown_event->raise(true); + ctx.join_event->raise(true); + } + }); + + while(encode_run_sync(synced_session_ctxs, ctx) == encode_e::reinit); +} + +void capture_async( safe::signal_t *shutdown_event, - packet_queue_t packets, - idr_event_t idr_events, - config_t config, + packet_queue_t &packets, + idr_event_t &idr_events, + config_t &config, void *channel_data) { auto images = std::make_shared(); + auto lg = util::fail_guard([&]() { + images->stop(); + shutdown_event->raise(true); + }); - // Keep a reference counter to ensure the Fcapture thread only runs when other threads have a reference to the capture thread - static auto capture_thread = safe::make_shared(start_capture, end_capture); - auto ref = capture_thread.ref(); + auto ref = capture_thread_async.ref(); if(!ref) { return; } @@ -665,7 +986,13 @@ void capture( int frame_nr = 1; int key_frame_nr = 1; + while(!shutdown_event->peek() && images->running()) { + // Wait for the main capture event when the display is being reinitialized + if(ref->reinit_event.peek()) { + std::this_thread::sleep_for(100ms); + continue; + } // Wait for the display to be ready std::shared_ptr display; { @@ -677,45 +1004,78 @@ void capture( display = ref->display_wp->lock(); } - encode_run(frame_nr, key_frame_nr, shutdown_event, packets, idr_events, images, config, *display, ref->reinit_event, *ref->encoder_p, channel_data); - } + auto pix_fmt = config.dynamicRange == 0 ? platf::pix_fmt_e::yuv420p : platf::pix_fmt_e::yuv420p10; + auto hwdevice = display->make_hwdevice(config.width, config.height, pix_fmt); + if(!hwdevice) { + return; + } - images->stop(); + auto dummy_img = display->alloc_img(); + if(display->dummy_img(dummy_img.get())) { + return; + } + images->raise(std::move(dummy_img)); + + encode_run( + frame_nr, key_frame_nr, + shutdown_event, + packets, idr_events, images, + config, display->width, display->height, + hwdevice.get(), + ref->reinit_event, *ref->encoder_p, + channel_data); + } } -bool validate_config(const encoder_t &encoder, const config_t &config, platf::display_t &disp) { - // Ensure everything but software fails succesfully, it's not ready yet - if(encoder.dev_type != AV_HWDEVICE_TYPE_NONE) { +void capture( + safe::signal_t *shutdown_event, + packet_queue_t packets, + idr_event_t idr_events, + config_t config, + void *channel_data) { + + idr_events->raise(std::make_pair(0, 1)); + if(encoders.front().system_memory) { + capture_async(shutdown_event, packets, idr_events, config, channel_data); + } + else { + safe::signal_t join_event; + auto ref = capture_thread_sync.ref(); + ref->encode_session_ctx_queue.raise(sync_session_ctx_t { + shutdown_event, &join_event, packets, idr_events, config, 1, 1, channel_data + }); + + // Wait for join signal + join_event.view(); + } +} + +bool validate_config(std::shared_ptr &disp, const encoder_t &encoder, const config_t &config) { + reset_display(disp, encoder.dev_type); + if(!disp) { return false; } - auto hwdevice = disp.get_hwdevice(); + auto pix_fmt = config.dynamicRange == 0 ? map_pix_fmt(encoder.static_pix_fmt) : map_pix_fmt(encoder.dynamic_pix_fmt); + auto hwdevice = disp->make_hwdevice(config.width, config.height, pix_fmt); + if(!hwdevice) { + return false; + } - auto session = make_session(encoder, config, hwdevice.get()); + auto session = make_session(encoder, config, disp->width, disp->height, hwdevice.get()); if(!session) { return false; } - int dummy_data; - auto img = disp.alloc_img(); - disp.dummy_img(img.get(), dummy_data); - - sws_t sws; - if(encoder.system_memory) { - sws.reset(sws_getContext( - img->width, img->height, AV_PIX_FMT_BGR0, - session->ctx->width, session->ctx->height, session->sw_format, - SWS_LANCZOS | SWS_ACCURATE_RND, - nullptr, nullptr, nullptr)); - - sws_setColorspaceDetails(sws.get(), sws_getCoefficients(SWS_CS_DEFAULT), 0, - sws_getCoefficients(session->sws_color_format), config.encoderCscMode & 0x1, - 0, 1 << 16, 1 << 16); - - + auto img = disp->alloc_img(); + if(disp->dummy_img(img.get())) { + return false; + } + if(session->device->convert(*img)) { + return false; } - encoder.img_to_frame(sws, *img, session->frame); + encoder.img_to_frame(*hwdevice->img, session->frame); session->frame->pict_type = AV_PICTURE_TYPE_I; @@ -727,79 +1087,252 @@ bool validate_config(const encoder_t &encoder, const config_t &config, platf::di return true; } -bool validate_encoder(const encoder_t &encoder) { - config_t config_h264 { - 1920, 1080, - 60, - 1000, - 1, - 1, - 1, - 0, - 0 - }; +bool validate_encoder(encoder_t &encoder) { + std::shared_ptr disp; - config_t config_hevc { - 1920, 1080, - 60, - 1000, - 1, - 1, - 1, - 1, - 1 - }; + auto force_hevc = config::video.hevc_mode >= 2; + auto test_hevc = force_hevc || (config::video.hevc_mode == 0 && encoder.hevc_mode); - auto disp = platf::display(encoder.dev_type); - if(!disp) { + encoder.h264.capabilities.set(); + encoder.hevc.capabilities.set(); + + // First, test encoder viability + config_t config_max_ref_frames { 1920, 1080, 60, 1000, 1, 1, 1, 0, 0 }; + config_t config_autoselect { 1920, 1080, 60, 1000, 1, 0, 1, 0, 0 }; + + auto max_ref_frames_h264 = validate_config(disp, encoder, config_max_ref_frames); + auto autoselect_h264 = validate_config(disp, encoder, config_autoselect); + + if(!max_ref_frames_h264 && !autoselect_h264) { return false; } - return - validate_config(encoder, config_h264, *disp) && - validate_config(encoder, config_hevc, *disp); + encoder.h264[encoder_t::REF_FRAMES_RESTRICT] = max_ref_frames_h264; + encoder.h264[encoder_t::REF_FRAMES_AUTOSELECT] = autoselect_h264; + encoder.h264[encoder_t::PASSED] = true; + + if(test_hevc) { + config_max_ref_frames.videoFormat = 1; + config_autoselect.videoFormat = 1; + + auto max_ref_frames_hevc = validate_config(disp, encoder, config_max_ref_frames); + auto autoselect_hevc = validate_config(disp, encoder, config_autoselect); + + // If HEVC must be supported, but it is not supported + if(force_hevc && !max_ref_frames_hevc && !autoselect_hevc) { + return false; + } + + encoder.hevc[encoder_t::REF_FRAMES_RESTRICT] = max_ref_frames_hevc; + encoder.hevc[encoder_t::REF_FRAMES_AUTOSELECT] = autoselect_hevc; + } + encoder.hevc[encoder_t::PASSED] = test_hevc; + + std::vector> configs { + { encoder_t::DYNAMIC_RANGE, { 1920, 1080, 60, 1000, 1, 0, 1, 1, 1 } } + }; + for(auto &[flag, config] : configs) { + auto h264 = config; + auto hevc = config; + + h264.videoFormat = 0; + hevc.videoFormat = 1; + + encoder.h264[flag] = validate_config(disp, encoder, h264); + if(test_hevc && encoder.hevc[encoder_t::PASSED]) { + encoder.hevc[flag] = validate_config(disp, encoder, hevc); + } + } + + return true; } -void init() { +int init() { KITTY_WHILE_LOOP(auto pos = std::begin(encoders), pos != std::end(encoders), { - if(!validate_encoder(*pos)) { + if( + (!config::video.encoder.empty() && pos->name != config::video.encoder) || + !validate_encoder(*pos) || + (config::video.hevc_mode == 3 && !pos->hevc[encoder_t::DYNAMIC_RANGE]) + ) { pos = encoders.erase(pos); continue; } - ++pos; + break; }) - for(auto &encoder : encoders) { - BOOST_LOG(info) << "Found encoder ["sv << encoder.h264.name << ", "sv << encoder.hevc.name << ']'; + if(encoders.empty()) { + if(config::video.encoder.empty()) { + BOOST_LOG(fatal) << "Couldn't find any encoder"sv; + } + else { + BOOST_LOG(fatal) << "Couldn't find any encoder matching ["sv << config::video.encoder << ']'; + } + + return -1; } + + auto &encoder = encoders.front(); + if(encoder.hevc[encoder_t::PASSED]) { + BOOST_LOG(info) << "Found encoder "sv << encoder.name << ": ["sv << encoder.h264.name << ", "sv << encoder.hevc.name << ']'; + } + else { + BOOST_LOG(info) << "Found encoder "sv << encoder.name << ": ["sv << encoder.h264.name << ']'; + } + + if(config::video.hevc_mode == 0) { + config::video.hevc_mode = encoder.hevc[encoder_t::PASSED] ? (encoder.hevc[encoder_t::DYNAMIC_RANGE] ? 3 : 2) : 1; + } + + return 0; } -void sw_img_to_frame(sws_t &sws, platf::img_t &img, frame_t &frame) { - av_frame_make_writable(frame.get()); +util::Either make_hwdevice_ctx(AVHWDeviceType type, void *hwdevice) { + buffer_t ctx; - const int linesizes[2] { - img.row_pitch, 0 - }; + int err; + if(hwdevice) { + ctx.reset(av_hwdevice_ctx_alloc(type)); + ((AVHWDeviceContext*)ctx.get())->hwctx = hwdevice; - int ret = sws_scale(sws.get(), (std::uint8_t*const*)&img.data, linesizes, 0, img.height, frame->data, frame->linesize); - if(ret <= 0) { - BOOST_LOG(fatal) << "Couldn't convert image to required format and/or size"sv; - - log_flush(); - std::abort(); + err = av_hwdevice_ctx_init(ctx.get()); } + else { + AVBufferRef *ref {}; + err = av_hwdevice_ctx_create(&ref, type, nullptr, nullptr, 0); + ctx.reset(ref); + } + + if(err < 0) { + return err; + } + + return ctx; } -void nv_d3d_img_to_frame(sws_t &sws, platf::img_t &img, frame_t &frame) { +int hwframe_ctx(ctx_t &ctx, buffer_t &hwdevice, AVPixelFormat format) { + buffer_t frame_ref { av_hwframe_ctx_alloc(hwdevice.get())}; + + auto frame_ctx = (AVHWFramesContext*)frame_ref->data; + frame_ctx->format = ctx->pix_fmt; + frame_ctx->sw_format = format; + frame_ctx->height = ctx->height; + frame_ctx->width = ctx->width; + frame_ctx->initial_pool_size = 0; + + if(auto err = av_hwframe_ctx_init(frame_ref.get()); err < 0) { + return err; + } + + ctx->hw_frames_ctx = av_buffer_ref(frame_ref.get()); + + return 0; +} + +void sw_img_to_frame(const platf::img_t &img, frame_t &frame) {} + +#ifdef _WIN32 +void nv_d3d_img_to_frame(const platf::img_t &img, frame_t &frame) { + if(img.data == frame->data[0]) { + return; + } + + // Need to have something refcounted + if(!frame->buf[0]) { + frame->buf[0] = av_buffer_allocz(sizeof(AVD3D11FrameDescriptor)); + } + + auto desc = (AVD3D11FrameDescriptor*)frame->buf[0]->data; + desc->texture = (ID3D11Texture2D*)img.data; + desc->index = 0; + frame->data[0] = img.data; frame->data[1] = 0; frame->linesize[0] = img.row_pitch; - frame->linesize[1] = 0; frame->height = img.height; frame->width = img.width; } + +util::Either nv_d3d_make_hwdevice_ctx(platf::hwdevice_t *hwdevice_ctx) { + buffer_t ctx_buf { av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_D3D11VA) }; + auto ctx = (AVD3D11VADeviceContext*)((AVHWDeviceContext*)ctx_buf->data)->hwctx; + + std::fill_n((std::uint8_t*)ctx, sizeof(AVD3D11VADeviceContext), 0); + + auto device = (ID3D11Device*)hwdevice_ctx->data; + device->AddRef(); + ctx->device = device; + + auto err = av_hwdevice_ctx_init(ctx_buf.get()); + if(err) { + char err_str[AV_ERROR_MAX_STRING_SIZE] {0}; + BOOST_LOG(error) << "Failed to create FFMpeg nvenc: "sv << av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, err); + + return err; + } + + return ctx_buf; +} +#endif + +int start_capture_async(capture_thread_async_ctx_t &capture_thread_ctx) { + capture_thread_ctx.encoder_p = &encoders.front(); + capture_thread_ctx.reinit_event.reset(); + + capture_thread_ctx.capture_ctx_queue = std::make_shared>(); + + capture_thread_ctx.capture_thread = std::thread { + captureThread, + capture_thread_ctx.capture_ctx_queue, + std::ref(capture_thread_ctx.display_wp), + std::ref(capture_thread_ctx.reinit_event), + std::ref(*capture_thread_ctx.encoder_p) + }; + + return 0; +} +void end_capture_async(capture_thread_async_ctx_t &capture_thread_ctx) { + capture_thread_ctx.capture_ctx_queue->stop(); + + capture_thread_ctx.capture_thread.join(); +} + +int start_capture_sync(capture_thread_sync_ctx_t &ctx) { + std::thread { &captureThreadSync }.detach(); + return 0; +} +void end_capture_sync(capture_thread_sync_ctx_t &ctx) {} + +platf::dev_type_e map_dev_type(AVHWDeviceType type) { + switch(type) { + case AV_HWDEVICE_TYPE_D3D11VA: + return platf::dev_type_e::dxgi; + case AV_PICTURE_TYPE_NONE: + return platf::dev_type_e::none; + default: + return platf::dev_type_e::unknown; + } + + return platf::dev_type_e::unknown; +} + +platf::pix_fmt_e map_pix_fmt(AVPixelFormat fmt) { + switch(fmt) { + case AV_PIX_FMT_YUV420P10: + return platf::pix_fmt_e::yuv420p10; + case AV_PIX_FMT_YUV420P: + return platf::pix_fmt_e::yuv420p; + case AV_PIX_FMT_NV12: + return platf::pix_fmt_e::nv12; + case AV_PIX_FMT_P010: + return platf::pix_fmt_e::p010; + default: + return platf::pix_fmt_e::unknown; + } + + return platf::pix_fmt_e::unknown; +} } diff --git a/sunshine/video.h b/sunshine/video.h index fc3c1426..e4560e05 100644 --- a/sunshine/video.h +++ b/sunshine/video.h @@ -57,7 +57,7 @@ void capture( config_t config, void *channel_data); -void init(); +int init(); } #endif //SUNSHINE_VIDEO_H