From c652d57d386ec464baafa394b412f69b8a7ce3fe Mon Sep 17 00:00:00 2001 From: ns6089 <61738816+ns6089@users.noreply.github.com> Date: Sat, 19 Aug 2023 12:40:31 +0300 Subject: [PATCH] Initial implementation of yuv444 encoding --- src/nvenc/nvenc_base.cpp | 89 +++-- src/nvenc/nvenc_base.h | 40 +- src/nvenc/nvenc_d3d11.cpp | 64 +--- src/nvenc/nvenc_d3d11.h | 21 +- src/nvenc/nvenc_d3d11_native.cpp | 67 ++++ src/nvenc/nvenc_d3d11_native.h | 28 ++ src/nvenc/nvenc_d3d11_on_cuda.cpp | 263 ++++++++++++++ src/nvenc/nvenc_d3d11_on_cuda.h | 83 +++++ src/nvenc/nvenc_utils.cpp | 12 + src/nvhttp.cpp | 17 +- src/platform/common.h | 6 + src/platform/windows/display_vram.cpp | 343 ++++++++++++++---- src/rtsp.cpp | 2 + src/video.cpp | 128 +++++-- src/video.h | 19 +- src/video_colorspace.cpp | 105 ++++++ src/video_colorspace.h | 2 + .../convert_yuv444_packed_ayuv_ps.hlsl | 3 + .../convert_yuv444_packed_ayuv_ps_linear.hlsl | 3 + .../directx/convert_yuv444_packed_vs.hlsl | 10 + .../convert_yuv444_packed_y410_ps.hlsl | 4 + .../convert_yuv444_packed_y410_ps_linear.hlsl | 4 + ...4_packed_y410_ps_perceptual_quantizer.hlsl | 4 + .../directx/convert_yuv444_planar_ps.hlsl | 4 + .../convert_yuv444_planar_ps_linear.hlsl | 4 + ...yuv444_planar_ps_perceptual_quantizer.hlsl | 4 + .../directx/convert_yuv444_planar_vs.hlsl | 33 ++ .../shaders/directx/include/base_vs.hlsl | 2 +- .../directx/include/base_vs_types.hlsl | 4 + .../include/convert_yuv444_ps_base.hlsl | 39 ++ third-party/moonlight-common-c | 2 +- 31 files changed, 1192 insertions(+), 217 deletions(-) create mode 100644 src/nvenc/nvenc_d3d11_native.cpp create mode 100644 src/nvenc/nvenc_d3d11_native.h create mode 100644 src/nvenc/nvenc_d3d11_on_cuda.cpp create mode 100644 src/nvenc/nvenc_d3d11_on_cuda.h create mode 100644 src_assets/windows/assets/shaders/directx/convert_yuv444_packed_ayuv_ps.hlsl create mode 100644 src_assets/windows/assets/shaders/directx/convert_yuv444_packed_ayuv_ps_linear.hlsl create mode 100644 src_assets/windows/assets/shaders/directx/convert_yuv444_packed_vs.hlsl create mode 100644 src_assets/windows/assets/shaders/directx/convert_yuv444_packed_y410_ps.hlsl create mode 100644 src_assets/windows/assets/shaders/directx/convert_yuv444_packed_y410_ps_linear.hlsl create mode 100644 src_assets/windows/assets/shaders/directx/convert_yuv444_packed_y410_ps_perceptual_quantizer.hlsl create mode 100644 src_assets/windows/assets/shaders/directx/convert_yuv444_planar_ps.hlsl create mode 100644 src_assets/windows/assets/shaders/directx/convert_yuv444_planar_ps_linear.hlsl create mode 100644 src_assets/windows/assets/shaders/directx/convert_yuv444_planar_ps_perceptual_quantizer.hlsl create mode 100644 src_assets/windows/assets/shaders/directx/convert_yuv444_planar_vs.hlsl create mode 100644 src_assets/windows/assets/shaders/directx/include/convert_yuv444_ps_base.hlsl diff --git a/src/nvenc/nvenc_base.cpp b/src/nvenc/nvenc_base.cpp index b9eba5a04df..1bc377aa346 100644 --- a/src/nvenc/nvenc_base.cpp +++ b/src/nvenc/nvenc_base.cpp @@ -81,9 +81,8 @@ namespace { namespace nvenc { - nvenc_base::nvenc_base(NV_ENC_DEVICE_TYPE device_type, void *device): - device_type(device_type), - device(device) { + nvenc_base::nvenc_base(NV_ENC_DEVICE_TYPE device_type): + device_type(device_type) { } nvenc_base::~nvenc_base() { @@ -111,19 +110,19 @@ namespace nvenc { session_params.deviceType = device_type; session_params.apiVersion = minimum_api_version; if (nvenc_failed(nvenc->nvEncOpenEncodeSessionEx(&session_params, &encoder))) { - BOOST_LOG(error) << "NvEncOpenEncodeSessionEx failed: " << last_error_string; + BOOST_LOG(error) << "NvEnc: NvEncOpenEncodeSessionEx() failed: " << last_nvenc_error_string; return false; } uint32_t encode_guid_count = 0; if (nvenc_failed(nvenc->nvEncGetEncodeGUIDCount(encoder, &encode_guid_count))) { - BOOST_LOG(error) << "NvEncGetEncodeGUIDCount failed: " << last_error_string; + BOOST_LOG(error) << "NvEnc: NvEncGetEncodeGUIDCount() failed: " << last_nvenc_error_string; return false; }; std::vector encode_guids(encode_guid_count); if (nvenc_failed(nvenc->nvEncGetEncodeGUIDs(encoder, encode_guids.data(), encode_guids.size(), &encode_guid_count))) { - BOOST_LOG(error) << "NvEncGetEncodeGUIDs failed: " << last_error_string; + BOOST_LOG(error) << "NvEnc: NvEncGetEncodeGUIDs() failed: " << last_nvenc_error_string; return false; } @@ -172,7 +171,7 @@ namespace nvenc { }; auto buffer_is_yuv444 = [&]() { - return buffer_format == NV_ENC_BUFFER_FORMAT_YUV444 || buffer_format == NV_ENC_BUFFER_FORMAT_YUV444_10BIT; + return buffer_format == NV_ENC_BUFFER_FORMAT_AYUV || buffer_format == NV_ENC_BUFFER_FORMAT_YUV444_10BIT; }; { @@ -216,7 +215,7 @@ namespace nvenc { NV_ENC_PRESET_CONFIG preset_config = { min_struct_version(NV_ENC_PRESET_CONFIG_VER), { min_struct_version(NV_ENC_CONFIG_VER, 7, 8) } }; if (nvenc_failed(nvenc->nvEncGetEncodePresetConfigEx(encoder, init_params.encodeGUID, init_params.presetGUID, init_params.tuningInfo, &preset_config))) { - BOOST_LOG(error) << "NvEncGetEncodePresetConfigEx failed: " << last_error_string; + BOOST_LOG(error) << "NvEnc: NvEncGetEncodePresetConfigEx() failed: " << last_nvenc_error_string; return false; } @@ -278,7 +277,7 @@ namespace nvenc { } }; - auto fill_h264_hevc_vui = [&colorspace](auto &vui_config) { + auto fill_h264_hevc_vui = [&](auto &vui_config) { vui_config.videoSignalTypePresentFlag = 1; vui_config.videoFormat = NV_ENC_VUI_VIDEO_FORMAT_UNSPECIFIED; vui_config.videoFullRangeFlag = colorspace.full_range; @@ -286,7 +285,7 @@ namespace nvenc { vui_config.colourPrimaries = colorspace.primaries; vui_config.transferCharacteristics = colorspace.tranfer_function; vui_config.colourMatrix = colorspace.matrix; - vui_config.chromaSampleLocationFlag = 1; + vui_config.chromaSampleLocationFlag = buffer_is_yuv444() ? 0 : 1; vui_config.chromaSampleLocationTop = 0; vui_config.chromaSampleLocationBot = 0; }; @@ -327,7 +326,9 @@ namespace nvenc { auto &format_config = enc_config.encodeCodecConfig.av1Config; format_config.repeatSeqHdr = 1; format_config.idrPeriod = NVENC_INFINITE_GOPLENGTH; - format_config.chromaFormatIDC = 1; // YUV444 not supported by NVENC yet + if (buffer_is_yuv444()) { + format_config.chromaFormatIDC = 3; + } format_config.enableBitstreamPadding = config.insert_filler_data; if (buffer_is_10bit()) { format_config.inputPixelBitDepthMinus8 = 2; @@ -337,7 +338,7 @@ namespace nvenc { format_config.transferCharacteristics = colorspace.tranfer_function; format_config.matrixCoefficients = colorspace.matrix; format_config.colorRange = colorspace.full_range; - format_config.chromaSamplePosition = 1; + format_config.chromaSamplePosition = buffer_is_yuv444() ? 0 : 1; set_ref_frames(format_config.maxNumRefFramesInDPB, format_config.numFwdRefs, 8); set_minqp_if_enabled(config.min_qp_av1); @@ -354,7 +355,7 @@ namespace nvenc { init_params.encodeConfig = &enc_config; if (nvenc_failed(nvenc->nvEncInitializeEncoder(encoder, &init_params))) { - BOOST_LOG(error) << "NvEncInitializeEncoder failed: " << last_error_string; + BOOST_LOG(error) << "NvEnc: NvEncInitializeEncoder() failed: " << last_nvenc_error_string; return false; } @@ -362,14 +363,14 @@ namespace nvenc { NV_ENC_EVENT_PARAMS event_params = { min_struct_version(NV_ENC_EVENT_PARAMS_VER) }; event_params.completionEvent = async_event_handle; if (nvenc_failed(nvenc->nvEncRegisterAsyncEvent(encoder, &event_params))) { - BOOST_LOG(error) << "NvEncRegisterAsyncEvent failed: " << last_error_string; + BOOST_LOG(error) << "NvEnc: NvEncRegisterAsyncEvent() failed: " << last_nvenc_error_string; return false; } } NV_ENC_CREATE_BITSTREAM_BUFFER create_bitstream_buffer = { min_struct_version(NV_ENC_CREATE_BITSTREAM_BUFFER_VER) }; if (nvenc_failed(nvenc->nvEncCreateBitstreamBuffer(encoder, &create_bitstream_buffer))) { - BOOST_LOG(error) << "NvEncCreateBitstreamBuffer failed: " << last_error_string; + BOOST_LOG(error) << "NvEnc: NvEncCreateBitstreamBuffer() failed: " << last_nvenc_error_string; return false; } output_bitstream = create_bitstream_buffer.bitstreamBuffer; @@ -384,8 +385,13 @@ namespace nvenc { } { + auto video_format_string = client_config.videoFormat == 0 ? "H.264 " : + client_config.videoFormat == 1 ? "HEVC " : + client_config.videoFormat == 2 ? "AV1 " : + " "; std::string extra; if (init_params.enableEncodeAsync) extra += " async"; + if (buffer_is_yuv444()) extra += " yuv444"; if (buffer_is_10bit()) extra += " 10-bit"; if (enc_config.rcParams.multiPass != NV_ENC_MULTI_PASS_DISABLED) extra += " two-pass"; if (config.vbv_percentage_increase > 0 && get_encoder_cap(NV_ENC_CAPS_SUPPORT_CUSTOM_VBV_BUF_SIZE)) extra += " vbv+" + std::to_string(config.vbv_percentage_increase); @@ -394,7 +400,8 @@ namespace nvenc { if (enc_config.rcParams.enableAQ) extra += " spatial-aq"; if (enc_config.rcParams.enableMinQP) extra += " qpmin=" + std::to_string(enc_config.rcParams.minQP.qpInterP); if (config.insert_filler_data) extra += " filler-data"; - BOOST_LOG(info) << "NvEnc: created encoder " << quality_preset_string_from_guid(init_params.presetGUID) << extra; + + BOOST_LOG(info) << "NvEnc: created encoder " << video_format_string << quality_preset_string_from_guid(init_params.presetGUID) << extra; } encoder_state = {}; @@ -405,20 +412,28 @@ namespace nvenc { void nvenc_base::destroy_encoder() { if (output_bitstream) { - nvenc->nvEncDestroyBitstreamBuffer(encoder, output_bitstream); + if (nvenc_failed(nvenc->nvEncDestroyBitstreamBuffer(encoder, output_bitstream))) { + BOOST_LOG(error) << "NvEnc: NvEncDestroyBitstreamBuffer() failed: " << last_nvenc_error_string; + } output_bitstream = nullptr; } if (encoder && async_event_handle) { NV_ENC_EVENT_PARAMS event_params = { min_struct_version(NV_ENC_EVENT_PARAMS_VER) }; event_params.completionEvent = async_event_handle; - nvenc->nvEncUnregisterAsyncEvent(encoder, &event_params); + if (nvenc_failed(nvenc->nvEncUnregisterAsyncEvent(encoder, &event_params))) { + BOOST_LOG(error) << "NvEnc: NvEncUnregisterAsyncEvent() failed: " << last_nvenc_error_string; + } } if (registered_input_buffer) { - nvenc->nvEncUnregisterResource(encoder, registered_input_buffer); + if (nvenc_failed(nvenc->nvEncUnregisterResource(encoder, registered_input_buffer))) { + BOOST_LOG(error) << "NvEnc: NvEncUnregisterResource() failed: " << last_nvenc_error_string; + } registered_input_buffer = nullptr; } if (encoder) { - nvenc->nvEncDestroyEncoder(encoder); + if (nvenc_failed(nvenc->nvEncDestroyEncoder(encoder))) { + BOOST_LOG(error) << "NvEnc: NvEncDestroyEncoder() failed: " << last_nvenc_error_string; + } encoder = nullptr; } @@ -435,14 +450,24 @@ namespace nvenc { assert(registered_input_buffer); assert(output_bitstream); + if (!synchronize_input_buffer()) { + BOOST_LOG(error) << "NvEnc: failed to synchronize input buffer"; + return {}; + } + NV_ENC_MAP_INPUT_RESOURCE mapped_input_buffer = { min_struct_version(NV_ENC_MAP_INPUT_RESOURCE_VER) }; mapped_input_buffer.registeredResource = registered_input_buffer; if (nvenc_failed(nvenc->nvEncMapInputResource(encoder, &mapped_input_buffer))) { - BOOST_LOG(error) << "NvEncMapInputResource failed: " << last_error_string; + BOOST_LOG(error) << "NvEnc: NvEncMapInputResource() failed: " << last_nvenc_error_string; return {}; } - auto unmap_guard = util::fail_guard([&] { nvenc->nvEncUnmapInputResource(encoder, &mapped_input_buffer); }); + auto unmap_guard = util::fail_guard([&] { + if (nvenc_failed(nvenc->nvEncUnmapInputResource(encoder, &mapped_input_buffer))) { + // Unmapping CUDA registered surface always fails due to driver bug + // BOOST_LOG(error) << "NvEnc: NvEncUnmapInputResource() failed: " << last_nvenc_error_string; + } + }); NV_ENC_PIC_PARAMS pic_params = { min_struct_version(NV_ENC_PIC_PARAMS_VER, 4, 6) }; pic_params.inputWidth = encoder_params.width; @@ -456,7 +481,7 @@ namespace nvenc { pic_params.completionEvent = async_event_handle; if (nvenc_failed(nvenc->nvEncEncodePicture(encoder, &pic_params))) { - BOOST_LOG(error) << "NvEncEncodePicture failed: " << last_error_string; + BOOST_LOG(error) << "NvEnc: NvEncEncodePicture() failed: " << last_nvenc_error_string; return {}; } @@ -470,7 +495,7 @@ namespace nvenc { } if (nvenc_failed(nvenc->nvEncLockBitstream(encoder, &lock_bitstream))) { - BOOST_LOG(error) << "NvEncLockBitstream failed: " << last_error_string; + BOOST_LOG(error) << "NvEnc: NvEncLockBitstream() failed: " << last_nvenc_error_string; return {}; } @@ -494,7 +519,7 @@ namespace nvenc { } if (nvenc_failed(nvenc->nvEncUnlockBitstream(encoder, lock_bitstream.outputBitstream))) { - BOOST_LOG(error) << "NvEncUnlockBitstream failed: " << last_error_string; + BOOST_LOG(error) << "NvEnc: NvEncUnlockBitstream() failed: " << last_nvenc_error_string; } if (config::sunshine.min_log_level <= 1) { @@ -539,7 +564,7 @@ namespace nvenc { for (auto i = first_frame; i <= last_frame; i++) { if (nvenc_failed(nvenc->nvEncInvalidateRefFrames(encoder, i))) { - BOOST_LOG(error) << "NvEncInvalidateRefFrames " << i << " failed: " << last_error_string; + BOOST_LOG(error) << "NvEnc: NvEncInvalidateRefFrames() " << i << " failed: " << last_nvenc_error_string; return false; } } @@ -580,20 +605,22 @@ namespace nvenc { nvenc_status_case(NV_ENC_ERR_RESOURCE_REGISTER_FAILED); nvenc_status_case(NV_ENC_ERR_RESOURCE_NOT_REGISTERED); nvenc_status_case(NV_ENC_ERR_RESOURCE_NOT_MAPPED); - // Newer versions of sdk may add more constants, look for them the end of NVENCSTATUS enum + // Newer versions of sdk may add more constants, look for them at the end of NVENCSTATUS enum #undef nvenc_status_case default: return std::to_string(status); } }; - last_error_string.clear(); + last_nvenc_error_string.clear(); if (status != NV_ENC_SUCCESS) { + /* This API function gives broken strings more often than not if (nvenc && encoder) { - last_error_string = nvenc->nvEncGetLastErrorString(encoder); - if (!last_error_string.empty()) last_error_string += " "; + last_nvenc_error_string = nvenc->nvEncGetLastErrorString(encoder); + if (!last_nvenc_error_string.empty()) last_nvenc_error_string += " "; } - last_error_string += status_string(status); + */ + last_nvenc_error_string += status_string(status); return true; } diff --git a/src/nvenc/nvenc_base.h b/src/nvenc/nvenc_base.h index 2d012ef8da8..a11f3244007 100644 --- a/src/nvenc/nvenc_base.h +++ b/src/nvenc/nvenc_base.h @@ -13,7 +13,7 @@ namespace nvenc { class nvenc_base { public: - nvenc_base(NV_ENC_DEVICE_TYPE device_type, void *device); + nvenc_base(NV_ENC_DEVICE_TYPE device_type); virtual ~nvenc_base(); nvenc_base(const nvenc_base &) = delete; @@ -33,12 +33,37 @@ namespace nvenc { invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame); protected: + /** + * @brief Required. Used for loading NvEnc library and setting `nvenc` variable with `NvEncodeAPICreateInstance()`. + * Called during `create_encoder()` if `nvenc` variable is not initialized. + * @return `true` on success, `false` on error + */ virtual bool init_library() = 0; + /** + * @brief Required. Used for creating outside-facing input surface, + * registering this surface with `nvenc->nvEncRegisterResource()` and setting `registered_input_buffer` variable. + * Called during `create_encoder()`. + * @return `true` on success, `false` on error + */ virtual bool create_and_register_input_buffer() = 0; + /** + * @brief Optional. Override if you must perform additional operations on the registered input surface in the beginning of `encode_frame()`. + * Typically used for interop copy. + * @return `true` on success, `false` on error + */ + virtual bool + synchronize_input_buffer() { return true; } + + /** + * @brief Optional. Override if you want to create encoder in async mode. + * In this case must also set `async_event_handle` variable. + * @param timeout_ms Wait timeout in milliseconds + * @return `true` on success, `false` on timeout or error + */ virtual bool wait_for_async_event(uint32_t timeout_ms) { return false; } @@ -57,9 +82,6 @@ namespace nvenc { min_struct_version(uint32_t version, uint32_t v11_struct_version = 0, uint32_t v12_struct_version = 0); const NV_ENC_DEVICE_TYPE device_type; - void *const device; - - std::unique_ptr nvenc; void *encoder = nullptr; @@ -71,11 +93,13 @@ namespace nvenc { bool rfi = false; } encoder_params; - // Derived classes set these variables - NV_ENC_REGISTERED_PTR registered_input_buffer = nullptr; - void *async_event_handle = nullptr; + std::string last_nvenc_error_string; - std::string last_error_string; + // Derived classes set these variables + void *device = nullptr; // set in constructor or init_library() + std::shared_ptr nvenc; // set in init_library() + NV_ENC_REGISTERED_PTR registered_input_buffer = nullptr; // set in create_and_register_input_buffer() + void *async_event_handle = nullptr; // (optional) set in constructor or init_library(), must override wait_for_async_event() private: NV_ENC_OUTPUT_PTR output_bitstream = nullptr; diff --git a/src/nvenc/nvenc_d3d11.cpp b/src/nvenc/nvenc_d3d11.cpp index cb33a1801af..f0b5dd1e7c4 100644 --- a/src/nvenc/nvenc_d3d11.cpp +++ b/src/nvenc/nvenc_d3d11.cpp @@ -1,39 +1,23 @@ -#include "src/logging.h" - #ifdef _WIN32 #include "nvenc_d3d11.h" - #include "nvenc_utils.h" - namespace nvenc { - nvenc_d3d11::nvenc_d3d11(ID3D11Device *d3d_device): - nvenc_base(NV_ENC_DEVICE_TYPE_DIRECTX, d3d_device), - d3d_device(d3d_device) { - } - nvenc_d3d11::~nvenc_d3d11() { - if (encoder) destroy_encoder(); - if (dll) { FreeLibrary(dll); dll = NULL; } } - ID3D11Texture2D * - nvenc_d3d11::get_input_texture() { - return d3d_input_texture.GetInterfacePtr(); - } - bool nvenc_d3d11::init_library() { if (dll) return true; #ifdef _WIN64 - auto dll_name = "nvEncodeAPI64.dll"; + constexpr auto dll_name = "nvEncodeAPI64.dll"; #else - auto dll_name = "nvEncodeAPI.dll"; + constexpr auto dll_name = "nvEncodeAPI.dll"; #endif if ((dll = LoadLibraryEx(dll_name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32))) { @@ -41,7 +25,7 @@ namespace nvenc { auto new_nvenc = std::make_unique(); new_nvenc->version = min_struct_version(NV_ENCODE_API_FUNCTION_LIST_VER); if (nvenc_failed(create_instance(new_nvenc.get()))) { - BOOST_LOG(error) << "NvEncodeAPICreateInstance failed: " << last_error_string; + BOOST_LOG(error) << "NvEnc: NvEncodeAPICreateInstance() failed: " << last_nvenc_error_string; } else { nvenc = std::move(new_nvenc); @@ -49,11 +33,11 @@ namespace nvenc { } } else { - BOOST_LOG(error) << "No NvEncodeAPICreateInstance in " << dll_name; + BOOST_LOG(error) << "NvEnc: No NvEncodeAPICreateInstance() in " << dll_name; } } else { - BOOST_LOG(debug) << "Couldn't load NvEnc library " << dll_name; + BOOST_LOG(debug) << "NvEnc: Couldn't load NvEnc library " << dll_name; } if (dll) { @@ -64,43 +48,5 @@ namespace nvenc { return false; } - bool - nvenc_d3d11::create_and_register_input_buffer() { - if (!d3d_input_texture) { - D3D11_TEXTURE2D_DESC desc = {}; - desc.Width = encoder_params.width; - desc.Height = encoder_params.height; - desc.MipLevels = 1; - desc.ArraySize = 1; - desc.Format = dxgi_format_from_nvenc_format(encoder_params.buffer_format); - desc.SampleDesc.Count = 1; - desc.Usage = D3D11_USAGE_DEFAULT; - desc.BindFlags = D3D11_BIND_RENDER_TARGET; - if (d3d_device->CreateTexture2D(&desc, nullptr, &d3d_input_texture) != S_OK) { - BOOST_LOG(error) << "NvEnc: couldn't create input texture"; - return false; - } - } - - if (!registered_input_buffer) { - NV_ENC_REGISTER_RESOURCE register_resource = { min_struct_version(NV_ENC_REGISTER_RESOURCE_VER, 3, 4) }; - register_resource.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_DIRECTX; - register_resource.width = encoder_params.width; - register_resource.height = encoder_params.height; - register_resource.resourceToRegister = d3d_input_texture.GetInterfacePtr(); - register_resource.bufferFormat = encoder_params.buffer_format; - register_resource.bufferUsage = NV_ENC_INPUT_IMAGE; - - if (nvenc_failed(nvenc->nvEncRegisterResource(encoder, ®ister_resource))) { - BOOST_LOG(error) << "NvEncRegisterResource failed: " << last_error_string; - return false; - } - - registered_input_buffer = register_resource.registeredResource; - } - - return true; - } - } // namespace nvenc #endif diff --git a/src/nvenc/nvenc_d3d11.h b/src/nvenc/nvenc_d3d11.h index ef1b8d4c232..a3d9d5a82e1 100644 --- a/src/nvenc/nvenc_d3d11.h +++ b/src/nvenc/nvenc_d3d11.h @@ -10,25 +10,24 @@ namespace nvenc { _COM_SMARTPTR_TYPEDEF(ID3D11Device, IID_ID3D11Device); _COM_SMARTPTR_TYPEDEF(ID3D11Texture2D, IID_ID3D11Texture2D); + _COM_SMARTPTR_TYPEDEF(IDXGIDevice, IID_IDXGIDevice); + _COM_SMARTPTR_TYPEDEF(IDXGIAdapter, IID_IDXGIAdapter); - class nvenc_d3d11 final: public nvenc_base { + class nvenc_d3d11: public nvenc_base { public: - nvenc_d3d11(ID3D11Device *d3d_device); - ~nvenc_d3d11(); + nvenc_d3d11(NV_ENC_DEVICE_TYPE device_type): + nvenc_base(device_type) {} - ID3D11Texture2D * - get_input_texture(); + virtual ~nvenc_d3d11(); - private: - bool - init_library() override; + virtual ID3D11Texture2D * + get_input_texture() = 0; + protected: bool - create_and_register_input_buffer() override; + init_library() override; HMODULE dll = NULL; - const ID3D11DevicePtr d3d_device; - ID3D11Texture2DPtr d3d_input_texture; }; } // namespace nvenc diff --git a/src/nvenc/nvenc_d3d11_native.cpp b/src/nvenc/nvenc_d3d11_native.cpp new file mode 100644 index 00000000000..d9a12e69e3b --- /dev/null +++ b/src/nvenc/nvenc_d3d11_native.cpp @@ -0,0 +1,67 @@ +#ifdef _WIN32 + #include "nvenc_d3d11_native.h" + + #include "nvenc_utils.h" + +namespace nvenc { + + nvenc_d3d11_native::nvenc_d3d11_native(ID3D11Device *d3d_device): + nvenc_d3d11(NV_ENC_DEVICE_TYPE_DIRECTX), + d3d_device(d3d_device) { + device = d3d_device; + } + + nvenc_d3d11_native::~nvenc_d3d11_native() { + if (encoder) destroy_encoder(); + } + + ID3D11Texture2D * + nvenc_d3d11_native::get_input_texture() { + return d3d_input_texture.GetInterfacePtr(); + } + + bool + nvenc_d3d11_native::create_and_register_input_buffer() { + if (encoder_params.buffer_format == NV_ENC_BUFFER_FORMAT_YUV444_10BIT) { + BOOST_LOG(error) << "NvEnc: 10-bit 4:4:4 encoding is incompatible with D3D11 surface formats, use CUDA interop"; + return false; + } + + if (!d3d_input_texture) { + D3D11_TEXTURE2D_DESC desc = {}; + desc.Width = encoder_params.width; + desc.Height = encoder_params.height; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = dxgi_format_from_nvenc_format(encoder_params.buffer_format); + desc.SampleDesc.Count = 1; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_RENDER_TARGET; + if (d3d_device->CreateTexture2D(&desc, nullptr, &d3d_input_texture) != S_OK) { + BOOST_LOG(error) << "NvEnc: couldn't create input texture"; + return false; + } + } + + if (!registered_input_buffer) { + NV_ENC_REGISTER_RESOURCE register_resource = { min_struct_version(NV_ENC_REGISTER_RESOURCE_VER, 3, 4) }; + register_resource.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_DIRECTX; + register_resource.width = encoder_params.width; + register_resource.height = encoder_params.height; + register_resource.resourceToRegister = d3d_input_texture.GetInterfacePtr(); + register_resource.bufferFormat = encoder_params.buffer_format; + register_resource.bufferUsage = NV_ENC_INPUT_IMAGE; + + if (nvenc_failed(nvenc->nvEncRegisterResource(encoder, ®ister_resource))) { + BOOST_LOG(error) << "NvEnc: NvEncRegisterResource() failed: " << last_nvenc_error_string; + return false; + } + + registered_input_buffer = register_resource.registeredResource; + } + + return true; + } + +} // namespace nvenc +#endif diff --git a/src/nvenc/nvenc_d3d11_native.h b/src/nvenc/nvenc_d3d11_native.h new file mode 100644 index 00000000000..e7f9f898f73 --- /dev/null +++ b/src/nvenc/nvenc_d3d11_native.h @@ -0,0 +1,28 @@ +#pragma once +#ifdef _WIN32 + + #include + #include + + #include "nvenc_d3d11.h" + +namespace nvenc { + + class nvenc_d3d11_native final: public nvenc_d3d11 { + public: + nvenc_d3d11_native(ID3D11Device *d3d_device); + ~nvenc_d3d11_native(); + + ID3D11Texture2D * + get_input_texture() override; + + private: + bool + create_and_register_input_buffer() override; + + const ID3D11DevicePtr d3d_device; + ID3D11Texture2DPtr d3d_input_texture; + }; + +} // namespace nvenc +#endif diff --git a/src/nvenc/nvenc_d3d11_on_cuda.cpp b/src/nvenc/nvenc_d3d11_on_cuda.cpp new file mode 100644 index 00000000000..bda206b0c76 --- /dev/null +++ b/src/nvenc/nvenc_d3d11_on_cuda.cpp @@ -0,0 +1,263 @@ +#ifdef _WIN32 + #include "nvenc_d3d11_on_cuda.h" + + #include "nvenc_utils.h" + +namespace nvenc { + + nvenc_d3d11_on_cuda::nvenc_d3d11_on_cuda(ID3D11Device *d3d_device): + nvenc_d3d11(NV_ENC_DEVICE_TYPE_CUDA), + d3d_device(d3d_device) { + } + + nvenc_d3d11_on_cuda::~nvenc_d3d11_on_cuda() { + if (encoder) destroy_encoder(); + + if (cuda_context) { + { + auto autopop_context = push_context(); + + if (cuda_d3d_input_texture) { + if (cuda_failed(cuda_functions.cuGraphicsUnregisterResource(cuda_d3d_input_texture))) { + BOOST_LOG(error) << "NvEnc: cuGraphicsUnregisterResource() failed: error " << last_cuda_error; + } + cuda_d3d_input_texture = nullptr; + } + + if (cuda_surface) { + if (cuda_failed(cuda_functions.cuMemFree(cuda_surface))) { + BOOST_LOG(error) << "NvEnc: cuMemFree() failed: error " << last_cuda_error; + } + cuda_surface = 0; + } + } + + if (cuda_failed(cuda_functions.cuCtxDestroy(cuda_context))) { + BOOST_LOG(error) << "NvEnc: cuCtxDestroy() failed: error " << last_cuda_error; + } + cuda_context = nullptr; + } + + if (cuda_functions.dll) { + FreeLibrary(cuda_functions.dll); + cuda_functions = {}; + } + } + + ID3D11Texture2D * + nvenc_d3d11_on_cuda::get_input_texture() { + return d3d_input_texture.GetInterfacePtr(); + } + + bool + nvenc_d3d11_on_cuda::init_library() { + if (!nvenc_d3d11::init_library()) return false; + + constexpr auto dll_name = "nvcuda.dll"; + + if ((cuda_functions.dll = LoadLibraryEx(dll_name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32))) { + auto load_function = [&](T &location, auto symbol) -> bool { + location = (T) GetProcAddress(cuda_functions.dll, symbol); + return location != nullptr; + }; + if (!load_function(cuda_functions.cuInit, "cuInit") || + !load_function(cuda_functions.cuD3D11GetDevice, "cuD3D11GetDevice") || + !load_function(cuda_functions.cuCtxCreate, "cuCtxCreate_v2") || + !load_function(cuda_functions.cuCtxDestroy, "cuCtxDestroy_v2") || + !load_function(cuda_functions.cuCtxPushCurrent, "cuCtxPushCurrent_v2") || + !load_function(cuda_functions.cuCtxPopCurrent, "cuCtxPopCurrent_v2") || + !load_function(cuda_functions.cuMemAllocPitch, "cuMemAllocPitch_v2") || + !load_function(cuda_functions.cuMemFree, "cuMemFree_v2") || + !load_function(cuda_functions.cuGraphicsD3D11RegisterResource, "cuGraphicsD3D11RegisterResource") || + !load_function(cuda_functions.cuGraphicsUnregisterResource, "cuGraphicsUnregisterResource") || + !load_function(cuda_functions.cuGraphicsMapResources, "cuGraphicsMapResources") || + !load_function(cuda_functions.cuGraphicsUnmapResources, "cuGraphicsUnmapResources") || + !load_function(cuda_functions.cuGraphicsSubResourceGetMappedArray, "cuGraphicsSubResourceGetMappedArray") || + !load_function(cuda_functions.cuMemcpy2D, "cuMemcpy2D_v2")) { + BOOST_LOG(error) << "NvEnc: missing CUDA functions in " << dll_name; + FreeLibrary(cuda_functions.dll); + cuda_functions = {}; + } + } + else { + BOOST_LOG(debug) << "NvEnc: couldn't load CUDA dynamic library " << dll_name; + } + + if (cuda_functions.dll) { + IDXGIDevicePtr dxgi_device; + IDXGIAdapterPtr dxgi_adapter; + if (d3d_device && + SUCCEEDED(d3d_device->QueryInterface(IID_PPV_ARGS(&dxgi_device))) && + SUCCEEDED(dxgi_device->GetAdapter(&dxgi_adapter))) { + CUdevice cuda_device; + if (cuda_succeeded(cuda_functions.cuInit(0)) && + cuda_succeeded(cuda_functions.cuD3D11GetDevice(&cuda_device, dxgi_adapter)) && + cuda_succeeded(cuda_functions.cuCtxCreate(&cuda_context, CU_CTX_SCHED_BLOCKING_SYNC, cuda_device)) && + cuda_succeeded(cuda_functions.cuCtxPopCurrent(&cuda_context))) { + device = cuda_context; + } + else { + BOOST_LOG(error) << "NvEnc: couldn't create CUDA interop context: error " << last_cuda_error; + } + } + else { + BOOST_LOG(error) << "NvEnc: couldn't get DXGI adapter for CUDA interop"; + } + } + + return device != nullptr; + } + + bool + nvenc_d3d11_on_cuda::create_and_register_input_buffer() { + if (encoder_params.buffer_format != NV_ENC_BUFFER_FORMAT_YUV444_10BIT) { + BOOST_LOG(error) << "NvEnc: CUDA interop is expected to be used only for 10-bit 4:4:4 encoding"; + return false; + } + + if (!d3d_input_texture) { + D3D11_TEXTURE2D_DESC desc = {}; + desc.Width = encoder_params.width; + desc.Height = encoder_params.height * 3; // Planar YUV + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = dxgi_format_from_nvenc_format(encoder_params.buffer_format); + desc.SampleDesc.Count = 1; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_RENDER_TARGET; + + if (d3d_device->CreateTexture2D(&desc, nullptr, &d3d_input_texture) != S_OK) { + BOOST_LOG(error) << "NvEnc: couldn't create input texture"; + return false; + } + } + + { + auto autopop_context = push_context(); + if (!autopop_context) return false; + + if (!cuda_d3d_input_texture) { + if (cuda_failed(cuda_functions.cuGraphicsD3D11RegisterResource( + &cuda_d3d_input_texture, + d3d_input_texture, + CU_GRAPHICS_REGISTER_FLAGS_NONE))) { + BOOST_LOG(error) << "NvEnc: cuGraphicsD3D11RegisterResource() failed: error " << last_cuda_error; + return false; + } + } + + if (!cuda_surface) { + if (cuda_failed(cuda_functions.cuMemAllocPitch( + &cuda_surface, + &cuda_surface_pitch, + // Planar 16-bit YUV + encoder_params.width * 2, + encoder_params.height * 3, 16))) { + BOOST_LOG(error) << "NvEnc: cuMemAllocPitch() failed: error " << last_cuda_error; + return false; + } + } + } + + if (!registered_input_buffer) { + NV_ENC_REGISTER_RESOURCE register_resource = { min_struct_version(NV_ENC_REGISTER_RESOURCE_VER, 3, 4) }; + register_resource.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR; + register_resource.width = encoder_params.width; + register_resource.height = encoder_params.height; + register_resource.pitch = cuda_surface_pitch; + register_resource.resourceToRegister = (void *) cuda_surface; + register_resource.bufferFormat = encoder_params.buffer_format; + register_resource.bufferUsage = NV_ENC_INPUT_IMAGE; + + if (nvenc_failed(nvenc->nvEncRegisterResource(encoder, ®ister_resource))) { + BOOST_LOG(error) << "NvEnc: NvEncRegisterResource() failed: " << last_nvenc_error_string; + return false; + } + + registered_input_buffer = register_resource.registeredResource; + } + + return true; + } + + bool + nvenc_d3d11_on_cuda::synchronize_input_buffer() { + auto autopop_context = push_context(); + if (!autopop_context) return false; + + if (cuda_failed(cuda_functions.cuGraphicsMapResources(1, &cuda_d3d_input_texture, 0))) { + BOOST_LOG(error) << "NvEnc: cuGraphicsMapResources() failed: error " << last_cuda_error; + return false; + } + + auto unmap = [&]() -> bool { + if (cuda_failed(cuda_functions.cuGraphicsUnmapResources(1, &cuda_d3d_input_texture, 0))) { + BOOST_LOG(error) << "NvEnc: cuGraphicsUnmapResources() failed: error " << last_cuda_error; + return false; + } + return true; + }; + auto unmap_guard = util::fail_guard(unmap); + + CUarray input_texture_array; + if (cuda_failed(cuda_functions.cuGraphicsSubResourceGetMappedArray(&input_texture_array, cuda_d3d_input_texture, 0, 0))) { + BOOST_LOG(error) << "NvEnc: cuGraphicsSubResourceGetMappedArray() failed: error " << last_cuda_error; + return false; + } + + { + CUDA_MEMCPY2D copy_params = {}; + copy_params.srcMemoryType = CU_MEMORYTYPE_ARRAY; + copy_params.srcArray = input_texture_array; + copy_params.dstMemoryType = CU_MEMORYTYPE_DEVICE; + copy_params.dstDevice = cuda_surface; + copy_params.dstPitch = cuda_surface_pitch; + // Planar 16-bit YUV + copy_params.WidthInBytes = encoder_params.width * 2; + copy_params.Height = encoder_params.height * 3; + + if (cuda_failed(cuda_functions.cuMemcpy2D(©_params))) { + BOOST_LOG(error) << "NvEnc: cuMemcpy2D() failed: error " << last_cuda_error; + return false; + } + } + + unmap_guard.disable(); + return unmap(); + } + + bool + nvenc_d3d11_on_cuda::cuda_succeeded(CUresult result) { + last_cuda_error = result; + return result == CUDA_SUCCESS; + } + + bool + nvenc_d3d11_on_cuda::cuda_failed(CUresult result) { + last_cuda_error = result; + return result != CUDA_SUCCESS; + } + + nvenc_d3d11_on_cuda::autopop_context::~autopop_context() { + if (pushed_context) { + CUcontext popped_context; + if (parent.cuda_failed(parent.cuda_functions.cuCtxPopCurrent(&popped_context))) { + BOOST_LOG(error) << "NvEnc: cuCtxPopCurrent() failed: error " << parent.last_cuda_error; + } + } + } + + nvenc_d3d11_on_cuda::autopop_context + nvenc_d3d11_on_cuda::push_context() { + if (cuda_context && + cuda_succeeded(cuda_functions.cuCtxPushCurrent(cuda_context))) { + return { *this, cuda_context }; + } + else { + BOOST_LOG(error) << "NvEnc: cuCtxPushCurrent() failed: error " << last_cuda_error; + return { *this, nullptr }; + } + } + +} // namespace nvenc +#endif diff --git a/src/nvenc/nvenc_d3d11_on_cuda.h b/src/nvenc/nvenc_d3d11_on_cuda.h new file mode 100644 index 00000000000..300ed7b88bf --- /dev/null +++ b/src/nvenc/nvenc_d3d11_on_cuda.h @@ -0,0 +1,83 @@ +#pragma once +#ifdef _WIN32 + + #include "nvenc_d3d11.h" + + #include + +namespace nvenc { + + class nvenc_d3d11_on_cuda final: public nvenc_d3d11 { + public: + nvenc_d3d11_on_cuda(ID3D11Device *d3d_device); + ~nvenc_d3d11_on_cuda(); + + ID3D11Texture2D * + get_input_texture() override; + + private: + bool + init_library() override; + + bool + create_and_register_input_buffer() override; + + bool + synchronize_input_buffer() override; + + bool + cuda_succeeded(CUresult result); + + bool + cuda_failed(CUresult result); + + struct autopop_context { + autopop_context(nvenc_d3d11_on_cuda &parent, CUcontext pushed_context): + parent(parent), + pushed_context(pushed_context) { + } + + ~autopop_context(); + + operator bool() const { + return pushed_context != nullptr; + } + + nvenc_d3d11_on_cuda &parent; + CUcontext pushed_context = nullptr; + }; + + autopop_context + push_context(); + + HMODULE dll = NULL; + const ID3D11DevicePtr d3d_device; + ID3D11Texture2DPtr d3d_input_texture; + + struct { + tcuInit *cuInit; + tcuD3D11GetDevice *cuD3D11GetDevice; + tcuCtxCreate_v2 *cuCtxCreate; + tcuCtxDestroy_v2 *cuCtxDestroy; + tcuCtxPushCurrent_v2 *cuCtxPushCurrent; + tcuCtxPopCurrent_v2 *cuCtxPopCurrent; + tcuMemAllocPitch_v2 *cuMemAllocPitch; + tcuMemFree_v2 *cuMemFree; + tcuGraphicsD3D11RegisterResource *cuGraphicsD3D11RegisterResource; + tcuGraphicsUnregisterResource *cuGraphicsUnregisterResource; + tcuGraphicsMapResources *cuGraphicsMapResources; + tcuGraphicsUnmapResources *cuGraphicsUnmapResources; + tcuGraphicsSubResourceGetMappedArray *cuGraphicsSubResourceGetMappedArray; + tcuMemcpy2D_v2 *cuMemcpy2D; + HMODULE dll; + } cuda_functions = {}; + + CUresult last_cuda_error = CUDA_SUCCESS; + CUcontext cuda_context = nullptr; + CUgraphicsResource cuda_d3d_input_texture = nullptr; + CUdeviceptr cuda_surface = 0; + size_t cuda_surface_pitch = 0; + }; + +} // namespace nvenc +#endif diff --git a/src/nvenc/nvenc_utils.cpp b/src/nvenc/nvenc_utils.cpp index 1b8b7ec9f10..3199c9972a0 100644 --- a/src/nvenc/nvenc_utils.cpp +++ b/src/nvenc/nvenc_utils.cpp @@ -14,6 +14,12 @@ namespace nvenc { case NV_ENC_BUFFER_FORMAT_NV12: return DXGI_FORMAT_NV12; + case NV_ENC_BUFFER_FORMAT_AYUV: + return DXGI_FORMAT_AYUV; + + case NV_ENC_BUFFER_FORMAT_YUV444_10BIT: + return DXGI_FORMAT_R16_UINT; + default: return DXGI_FORMAT_UNKNOWN; } @@ -29,6 +35,12 @@ namespace nvenc { case platf::pix_fmt_e::p010: return NV_ENC_BUFFER_FORMAT_YUV420_10BIT; + case platf::pix_fmt_e::ayuv: + return NV_ENC_BUFFER_FORMAT_AYUV; + + case platf::pix_fmt_e::yuv444p16: + return NV_ENC_BUFFER_FORMAT_YUV444_10BIT; + default: return NV_ENC_BUFFER_FORMAT_UNDEFINED; } diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index bd8434e5534..9a1db7e4655 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -732,17 +732,32 @@ namespace nvhttp { } uint32_t codec_mode_flags = SCM_H264; + if (video::last_encoder_probe_supported_yuv444_for_codec[0]) { + codec_mode_flags |= SCM_H264_HIGH8_444; + } if (video::active_hevc_mode >= 2) { codec_mode_flags |= SCM_HEVC; + if (video::last_encoder_probe_supported_yuv444_for_codec[1]) { + codec_mode_flags |= SCM_HEVC_REXT8_444; + } } if (video::active_hevc_mode >= 3) { codec_mode_flags |= SCM_HEVC_MAIN10; + if (video::last_encoder_probe_supported_yuv444_for_codec[1]) { + codec_mode_flags |= SCM_HEVC_REXT10_444; + } } if (video::active_av1_mode >= 2) { codec_mode_flags |= SCM_AV1_MAIN8; + if (video::last_encoder_probe_supported_yuv444_for_codec[2]) { + codec_mode_flags |= SCM_AV1_HIGH8_444; + } } if (video::active_av1_mode >= 3) { codec_mode_flags |= SCM_AV1_MAIN10; + if (video::last_encoder_probe_supported_yuv444_for_codec[2]) { + codec_mode_flags |= SCM_AV1_HIGH10_444; + } } tree.put("root.ServerCodecModeSupport", codec_mode_flags); @@ -818,7 +833,7 @@ namespace nvhttp { for (auto &proc : proc::proc.get_apps()) { pt::ptree app; - app.put("IsHdrSupported"s, video::active_hevc_mode == 3 ? 1 : 0); + app.put("IsHdrSupported"s, video::active_hevc_mode >= 3 ? 1 : 0); app.put("AppTitle"s, proc.name); app.put("ID", proc.id); diff --git a/src/platform/common.h b/src/platform/common.h index e7e72334703..02d46358439 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -206,6 +206,9 @@ namespace platf { yuv420p10, nv12, p010, + ayuv, + yuv444p16, + y410, unknown }; @@ -220,6 +223,9 @@ namespace platf { _CONVERT(yuv420p10); _CONVERT(nv12); _CONVERT(p010); + _CONVERT(ayuv); + _CONVERT(yuv444p16); + _CONVERT(y410); _CONVERT(unknown); } #undef _CONVERT diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp index be144c9844f..36f2ffb4508 100644 --- a/src/platform/windows/display_vram.cpp +++ b/src/platform/windows/display_vram.cpp @@ -17,7 +17,8 @@ extern "C" { #include "src/config.h" #include "src/logging.h" #include "src/nvenc/nvenc_config.h" -#include "src/nvenc/nvenc_d3d11.h" +#include "src/nvenc/nvenc_d3d11_native.h" +#include "src/nvenc/nvenc_d3d11_on_cuda.h" #include "src/nvenc/nvenc_utils.h" #include "src/video.h" @@ -110,6 +111,16 @@ namespace platf::dxgi { blob_t convert_yuv420_planar_y_ps_linear_hlsl; blob_t convert_yuv420_planar_y_ps_perceptual_quantizer_hlsl; blob_t convert_yuv420_planar_y_vs_hlsl; + blob_t convert_yuv444_packed_ayuv_ps_hlsl; + blob_t convert_yuv444_packed_ayuv_ps_linear_hlsl; + blob_t convert_yuv444_packed_vs_hlsl; + blob_t convert_yuv444_planar_ps_hlsl; + blob_t convert_yuv444_planar_ps_linear_hlsl; + blob_t convert_yuv444_planar_ps_perceptual_quantizer_hlsl; + blob_t convert_yuv444_packed_y410_ps_hlsl; + blob_t convert_yuv444_packed_y410_ps_linear_hlsl; + blob_t convert_yuv444_packed_y410_ps_perceptual_quantizer_hlsl; + blob_t convert_yuv444_planar_vs_hlsl; blob_t cursor_ps_hlsl; blob_t cursor_ps_normalize_white_hlsl; blob_t cursor_vs_hlsl; @@ -402,18 +413,62 @@ namespace platf::dxgi { return -1; } - device_ctx->OMSetRenderTargets(1, &nv12_Y_rt, nullptr); - device_ctx->VSSetShader(scene_vs.get(), nullptr, 0); - device_ctx->PSSetShader(img.format == DXGI_FORMAT_R16G16B16A16_FLOAT ? convert_Y_fp16_ps.get() : convert_Y_ps.get(), nullptr, 0); - device_ctx->RSSetViewports(1, &outY_view); - device_ctx->PSSetShaderResources(0, 1, &img_ctx.encoder_input_res); - device_ctx->Draw(3, 0); + if (format == DXGI_FORMAT_AYUV || format == DXGI_FORMAT_Y410) { + device_ctx->OMSetRenderTargets(1, &yuv444_rt, nullptr); + device_ctx->VSSetShader(convert_YUV_vs.get(), nullptr, 0); + device_ctx->PSSetShader(img.format == DXGI_FORMAT_R16G16B16A16_FLOAT ? convert_YUV_fp16_ps.get() : convert_YUV_ps.get(), nullptr, 0); + + if (!rt_cleared) { + device_ctx->RSSetViewports(1, outYUV_views_for_clear.data()); + auto black = create_black_texture_for_render_target_clear(); + if (black) { + device_ctx->PSSetShaderResources(0, 1, &black); + device_ctx->Draw(3, 0); + } + rt_cleared = true; + } - device_ctx->OMSetRenderTargets(1, &nv12_UV_rt, nullptr); - device_ctx->VSSetShader(convert_UV_vs.get(), nullptr, 0); - device_ctx->PSSetShader(img.format == DXGI_FORMAT_R16G16B16A16_FLOAT ? convert_UV_fp16_ps.get() : convert_UV_ps.get(), nullptr, 0); - device_ctx->RSSetViewports(1, &outUV_view); - device_ctx->Draw(3, 0); + device_ctx->RSSetViewports(1, outYUV_views.data()); + device_ctx->PSSetShaderResources(0, 1, &img_ctx.encoder_input_res); + device_ctx->Draw(3, 0); + } + else if (format == DXGI_FORMAT_R16_UINT) { + device_ctx->OMSetRenderTargets(1, &yuv444_rt, nullptr); + device_ctx->VSSetShader(convert_YUV_vs.get(), nullptr, 0); + device_ctx->PSSetShader(img.format == DXGI_FORMAT_R16G16B16A16_FLOAT ? convert_YUV_fp16_ps.get() : convert_YUV_ps.get(), nullptr, 0); + + if (!rt_cleared) { + assert(outYUV_views_for_clear.size() == 3); + device_ctx->RSSetViewports(3, outYUV_views_for_clear.data()); + auto black = create_black_texture_for_render_target_clear(); + if (black) { + device_ctx->PSSetShaderResources(0, 1, &black); + device_ctx->Draw(9, 0); // vertex shader spreads 9 vertices across 3 viewports + } + rt_cleared = true; + } + + assert(outYUV_views.size() == 3); + device_ctx->RSSetViewports(3, outYUV_views.data()); + device_ctx->PSSetShaderResources(0, 1, &img_ctx.encoder_input_res); + device_ctx->Draw(9, 0); // vertex shader spreads 9 vertices across 3 viewports + } + else { + assert(rt_cleared); + + device_ctx->OMSetRenderTargets(1, &nv12_Y_rt, nullptr); + device_ctx->VSSetShader(scene_vs.get(), nullptr, 0); + device_ctx->PSSetShader(img.format == DXGI_FORMAT_R16G16B16A16_FLOAT ? convert_Y_fp16_ps.get() : convert_Y_ps.get(), nullptr, 0); + device_ctx->RSSetViewports(1, &outY_view); + device_ctx->PSSetShaderResources(0, 1, &img_ctx.encoder_input_res); + device_ctx->Draw(3, 0); + + device_ctx->OMSetRenderTargets(1, &nv12_UV_rt, nullptr); + device_ctx->VSSetShader(convert_UV_vs.get(), nullptr, 0); + device_ctx->PSSetShader(img.format == DXGI_FORMAT_R16G16B16A16_FLOAT ? convert_UV_fp16_ps.get() : convert_UV_ps.get(), nullptr, 0); + device_ctx->RSSetViewports(1, &outUV_view); + device_ctx->Draw(3, 0); + } // Release encoder mutex to allow capture code to reuse this image img_ctx.encoder_mutex->ReleaseSync(0); @@ -429,6 +484,12 @@ namespace platf::dxgi { apply_colorspace(const ::video::sunshine_colorspace_t &colorspace) { auto color_vectors = ::video::color_vectors_from_colorspace(colorspace); + if (format == DXGI_FORMAT_AYUV || + format == DXGI_FORMAT_R16_UINT || + format == DXGI_FORMAT_Y410) { + color_vectors = ::video::new_color_vectors_from_colorspace(colorspace); + } + if (!color_vectors) { BOOST_LOG(error) << "No vector data for colorspace"sv; return; @@ -440,6 +501,7 @@ namespace platf::dxgi { return; } + device_ctx->VSSetConstantBuffers(3, 1, &color_matrix); device_ctx->PSSetConstantBuffers(0, 1, &color_matrix); this->color_matrix = std::move(color_matrix); } @@ -467,6 +529,13 @@ namespace platf::dxgi { outY_view = D3D11_VIEWPORT { offsetX, offsetY, out_width_f, out_height_f, 0.0f, 1.0f }; outUV_view = D3D11_VIEWPORT { offsetX / 2, offsetY / 2, out_width_f / 2, out_height_f / 2, 0.0f, 1.0f }; + outYUV_views = { outY_view, outY_view, outY_view }; // Planar YUV + outYUV_views[1].TopLeftY += out_height; // U plane + outYUV_views[2].TopLeftY += 2 * out_height; // V plane + auto outY_view_for_clear = D3D11_VIEWPORT { 0, 0, (float) out_width, (float) out_height, 0.0f, 1.0f }; + outYUV_views_for_clear = { outY_view_for_clear, outY_view_for_clear, outY_view_for_clear }; + outYUV_views_for_clear[1].TopLeftY += out_height; + outYUV_views_for_clear[2].TopLeftY += 2 * out_height; float subsample_offset_in[16 / sizeof(float)] { 1.0f / (float) out_width_f, 1.0f / (float) out_height_f }; // aligned to 16-byte subsample_offset = make_buffer(device.get(), subsample_offset_in); @@ -488,30 +557,69 @@ namespace platf::dxgi { device_ctx->VSSetConstantBuffers(1, 1, &rotation); } - D3D11_RENDER_TARGET_VIEW_DESC nv12_rt_desc { - format == DXGI_FORMAT_P010 ? DXGI_FORMAT_R16_UNORM : DXGI_FORMAT_R8_UNORM, - D3D11_RTV_DIMENSION_TEXTURE2D - }; + if (format == DXGI_FORMAT_AYUV) { + D3D11_RENDER_TARGET_VIEW_DESC rt_desc = {}; + rt_desc.Format = DXGI_FORMAT_R8G8B8A8_UINT; + rt_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; - auto status = device->CreateRenderTargetView(output_texture.get(), &nv12_rt_desc, &nv12_Y_rt); - if (FAILED(status)) { - BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; + auto status = device->CreateRenderTargetView(output_texture.get(), &rt_desc, &yuv444_rt); + if (FAILED(status)) { + BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + rt_cleared = false; // can't use ClearRenderTargetView(), will clear on first convert() } + else if (format == DXGI_FORMAT_R16_UINT) { + D3D11_RENDER_TARGET_VIEW_DESC rt_desc = {}; + rt_desc.Format = DXGI_FORMAT_R16_UINT; + rt_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; - nv12_rt_desc.Format = (format == DXGI_FORMAT_P010) ? DXGI_FORMAT_R16G16_UNORM : DXGI_FORMAT_R8G8_UNORM; + auto status = device->CreateRenderTargetView(output_texture.get(), &rt_desc, &yuv444_rt); + if (FAILED(status)) { + BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + rt_cleared = false; // can't use ClearRenderTargetView(), will clear on first convert() + } + else if (format == DXGI_FORMAT_Y410) { + D3D11_RENDER_TARGET_VIEW_DESC rt_desc = {}; + rt_desc.Format = DXGI_FORMAT_R10G10B10A2_UINT; + rt_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; - status = device->CreateRenderTargetView(output_texture.get(), &nv12_rt_desc, &nv12_UV_rt); - if (FAILED(status)) { - BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; + auto status = device->CreateRenderTargetView(output_texture.get(), &rt_desc, &yuv444_rt); + if (FAILED(status)) { + BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + rt_cleared = false; // can't use ClearRenderTargetView(), will clear on first convert() } + else { + D3D11_RENDER_TARGET_VIEW_DESC nv12_rt_desc { + format == DXGI_FORMAT_P010 ? DXGI_FORMAT_R16_UNORM : DXGI_FORMAT_R8_UNORM, + D3D11_RTV_DIMENSION_TEXTURE2D + }; + + auto status = device->CreateRenderTargetView(output_texture.get(), &nv12_rt_desc, &nv12_Y_rt); + if (FAILED(status)) { + BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + nv12_rt_desc.Format = (format == DXGI_FORMAT_P010) ? DXGI_FORMAT_R16G16_UNORM : DXGI_FORMAT_R8G8_UNORM; - // Clear the RTVs to ensure the aspect ratio padding is black - const float y_black[] = { 0.0f, 0.0f, 0.0f, 0.0f }; - device_ctx->ClearRenderTargetView(nv12_Y_rt.get(), y_black); - const float uv_black[] = { 0.5f, 0.5f, 0.5f, 0.5f }; - device_ctx->ClearRenderTargetView(nv12_UV_rt.get(), uv_black); + status = device->CreateRenderTargetView(output_texture.get(), &nv12_rt_desc, &nv12_UV_rt); + if (FAILED(status)) { + BOOST_LOG(error) << "Failed to create render target view [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + // Clear the RTVs to ensure the aspect ratio padding is black + const float y_black[] = { 0.0f, 0.0f, 0.0f, 0.0f }; + device_ctx->ClearRenderTargetView(nv12_Y_rt.get(), y_black); + const float uv_black[] = { 0.5f, 0.5f, 0.5f, 0.5f }; + device_ctx->ClearRenderTargetView(nv12_UV_rt.get(), uv_black); + rt_cleared = true; + } return 0; } @@ -556,61 +664,91 @@ namespace platf::dxgi { BOOST_LOG(warning) << "Failed to increase encoding GPU thread priority. Please run application as administrator for optimal performance."; } - format = (pix_fmt == pix_fmt_e::nv12 ? DXGI_FORMAT_NV12 : DXGI_FORMAT_P010); - status = device->CreateVertexShader(convert_yuv420_planar_y_vs_hlsl->GetBufferPointer(), convert_yuv420_planar_y_vs_hlsl->GetBufferSize(), nullptr, &scene_vs); - if (status) { - BOOST_LOG(error) << "Failed to create scene vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } + switch (pix_fmt) { + case pix_fmt_e::nv12: + format = DXGI_FORMAT_NV12; + break; - status = device->CreateVertexShader(convert_yuv420_packed_uv_type0_vs_hlsl->GetBufferPointer(), convert_yuv420_packed_uv_type0_vs_hlsl->GetBufferSize(), nullptr, &convert_UV_vs); - if (status) { - BOOST_LOG(error) << "Failed to create convertUV vertex shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; + case pix_fmt_e::p010: + format = DXGI_FORMAT_P010; + break; + + case pix_fmt_e::ayuv: + format = DXGI_FORMAT_AYUV; + break; + + case pix_fmt_e::yuv444p16: + format = DXGI_FORMAT_R16_UINT; + break; + + case pix_fmt_e::y410: + format = DXGI_FORMAT_Y410; + break; + + default: + BOOST_LOG(error) << "DXGI backend doesn't support requested pixel format"; + return -1; } +#define create_vertex_shader_helper(x, y) \ + if ((status = device->CreateVertexShader(x->GetBufferPointer(), x->GetBufferSize(), nullptr, &y))) { \ + BOOST_LOG(error) << "Failed to create vertex shader " << #x << " [0x" << util::hex(status).to_string_view() << "]"; \ + return -1; \ + } +#define create_pixel_shader_helper(x, y) \ + if ((status = device->CreatePixelShader(x->GetBufferPointer(), x->GetBufferSize(), nullptr, &y))) { \ + BOOST_LOG(error) << "Failed to create pixel shader " << #x << " [0x" << util::hex(status).to_string_view() << "]"; \ + return -1; \ + } + + create_vertex_shader_helper(convert_yuv420_planar_y_vs_hlsl, scene_vs); + create_vertex_shader_helper(convert_yuv420_packed_uv_type0_vs_hlsl, convert_UV_vs); + create_pixel_shader_helper(convert_yuv420_planar_y_ps_hlsl, convert_Y_ps); + create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_hlsl, convert_UV_ps); + // If the display is in HDR and we're streaming HDR, we'll be converting scRGB to SMPTE 2084 PQ. if (format == DXGI_FORMAT_P010 && display->is_hdr()) { - status = device->CreatePixelShader(convert_yuv420_planar_y_ps_perceptual_quantizer_hlsl->GetBufferPointer(), convert_yuv420_planar_y_ps_perceptual_quantizer_hlsl->GetBufferSize(), nullptr, &convert_Y_fp16_ps); - if (status) { - BOOST_LOG(error) << "Failed to create convertY pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - status = device->CreatePixelShader(convert_yuv420_packed_uv_type0_ps_perceptual_quantizer_hlsl->GetBufferPointer(), convert_yuv420_packed_uv_type0_ps_perceptual_quantizer_hlsl->GetBufferSize(), nullptr, &convert_UV_fp16_ps); - if (status) { - BOOST_LOG(error) << "Failed to create convertUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } + create_pixel_shader_helper(convert_yuv420_planar_y_ps_perceptual_quantizer_hlsl, convert_Y_fp16_ps); + create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_perceptual_quantizer_hlsl, convert_UV_fp16_ps); } else { // If the display is in Advanced Color mode, the desktop format will be scRGB FP16. // scRGB uses linear gamma, so we must use our linear to sRGB conversion shaders. - status = device->CreatePixelShader(convert_yuv420_planar_y_ps_linear_hlsl->GetBufferPointer(), convert_yuv420_planar_y_ps_linear_hlsl->GetBufferSize(), nullptr, &convert_Y_fp16_ps); - if (status) { - BOOST_LOG(error) << "Failed to create convertY pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } + create_pixel_shader_helper(convert_yuv420_planar_y_ps_linear_hlsl, convert_Y_fp16_ps); + create_pixel_shader_helper(convert_yuv420_packed_uv_type0_ps_linear_hlsl, convert_UV_fp16_ps); + } - status = device->CreatePixelShader(convert_yuv420_packed_uv_type0_ps_linear_hlsl->GetBufferPointer(), convert_yuv420_packed_uv_type0_ps_linear_hlsl->GetBufferSize(), nullptr, &convert_UV_fp16_ps); - if (status) { - BOOST_LOG(error) << "Failed to create convertUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; + if (format == DXGI_FORMAT_R16_UINT) { + // Planar 16-bit YUV 4:4:4, 10 most significant bits store the value + create_vertex_shader_helper(convert_yuv444_planar_vs_hlsl, convert_YUV_vs); + create_pixel_shader_helper(convert_yuv444_planar_ps_hlsl, convert_YUV_ps); + if (display->is_hdr()) { + create_pixel_shader_helper(convert_yuv444_planar_ps_perceptual_quantizer_hlsl, convert_YUV_fp16_ps); + } + else { + create_pixel_shader_helper(convert_yuv444_planar_ps_linear_hlsl, convert_YUV_fp16_ps); } } - - // These shaders consume standard 8-bit sRGB input - status = device->CreatePixelShader(convert_yuv420_planar_y_ps_hlsl->GetBufferPointer(), convert_yuv420_planar_y_ps_hlsl->GetBufferSize(), nullptr, &convert_Y_ps); - if (status) { - BOOST_LOG(error) << "Failed to create convertY pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; + else if (format == DXGI_FORMAT_AYUV) { + // Packed 8-bit YUV 4:4:4 + create_vertex_shader_helper(convert_yuv444_packed_vs_hlsl, convert_YUV_vs); + create_pixel_shader_helper(convert_yuv444_packed_ayuv_ps_hlsl, convert_YUV_ps); + create_pixel_shader_helper(convert_yuv444_packed_ayuv_ps_linear_hlsl, convert_YUV_fp16_ps); + } + else if (format == DXGI_FORMAT_Y410) { + // Packed 10-bit YUV 4:4:4 + create_vertex_shader_helper(convert_yuv444_packed_vs_hlsl, convert_YUV_vs); + create_pixel_shader_helper(convert_yuv444_packed_y410_ps_hlsl, convert_YUV_ps); + if (display->is_hdr()) { + create_pixel_shader_helper(convert_yuv444_packed_y410_ps_perceptual_quantizer_hlsl, convert_YUV_fp16_ps); + } + else { + create_pixel_shader_helper(convert_yuv444_packed_y410_ps_linear_hlsl, convert_YUV_fp16_ps); + } } - status = device->CreatePixelShader(convert_yuv420_packed_uv_type0_ps_hlsl->GetBufferPointer(), convert_yuv420_packed_uv_type0_ps_hlsl->GetBufferSize(), nullptr, &convert_UV_ps); - if (status) { - BOOST_LOG(error) << "Failed to create convertUV pixel shader [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } +#undef create_vertex_shader_helper +#undef create_pixel_shader_helper auto default_color_vectors = ::video::color_vectors_from_colorspace(::video::colorspace_e::rec601, false); if (!default_color_vectors) { @@ -623,6 +761,7 @@ namespace platf::dxgi { BOOST_LOG(error) << "Failed to create color matrix buffer"sv; return -1; } + device_ctx->VSSetConstantBuffers(3, 1, &color_matrix); device_ctx->PSSetConstantBuffers(0, 1, &color_matrix); this->display = std::dynamic_pointer_cast(display); @@ -653,7 +792,7 @@ namespace platf::dxgi { device_ctx->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu); device_ctx->PSSetSamplers(0, 1, &sampler_linear); - device_ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + device_ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); return 0; } @@ -725,6 +864,41 @@ namespace platf::dxgi { return 0; } + shader_res_t + create_black_texture_for_render_target_clear() { + constexpr auto width = 32; + constexpr auto height = 32; + + D3D11_TEXTURE2D_DESC texture_desc = {}; + texture_desc.Width = width; + texture_desc.Height = height; + texture_desc.MipLevels = 1; + texture_desc.ArraySize = 1; + texture_desc.SampleDesc.Count = 1; + texture_desc.Usage = D3D11_USAGE_IMMUTABLE; + texture_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + texture_desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + + std::vector mem(4 * width * height, 0); + D3D11_SUBRESOURCE_DATA texture_data = { mem.data(), 4 * width, 0 }; + + texture2d_t texture; + auto status = device->CreateTexture2D(&texture_desc, &texture_data, &texture); + if (FAILED(status)) { + BOOST_LOG(error) << "Failed to create black texture [0x" << util::hex(status).to_string_view() << "]"; + return {}; + } + + shader_res_t resource_view; + status = device->CreateShaderResourceView(texture.get(), nullptr, &resource_view); + if (FAILED(status)) { + BOOST_LOG(error) << "Failed to create black texture resource view [0x" << util::hex(status).to_string_view() << "]"; + return {}; + } + + return resource_view; + } + ::video::color_t *color_p; buf_t subsample_offset; @@ -733,8 +907,10 @@ namespace platf::dxgi { blend_t blend_disable; sampler_state_t sampler_linear; + render_target_t yuv444_rt; render_target_t nv12_Y_rt; render_target_t nv12_UV_rt; + bool rt_cleared = false; // d3d_img_t::id -> encoder_img_ctx_t // These store the encoder textures for each img_t that passes through @@ -749,10 +925,14 @@ namespace platf::dxgi { ps_t convert_UV_fp16_ps; ps_t convert_Y_ps; ps_t convert_Y_fp16_ps; + vs_t convert_YUV_vs; + ps_t convert_YUV_ps; + ps_t convert_YUV_fp16_ps; vs_t scene_vs; D3D11_VIEWPORT outY_view; D3D11_VIEWPORT outUV_view; + std::array outYUV_views, outYUV_views_for_clear; DXGI_FORMAT format; @@ -871,7 +1051,12 @@ namespace platf::dxgi { if (base.init(display, adapter_p, pix_fmt)) return false; - nvenc_d3d = std::make_unique(base.device.get()); + if (pix_fmt == pix_fmt_e::yuv444p16) { + nvenc_d3d = std::make_unique(base.device.get()); + } + else { + nvenc_d3d = std::make_unique(base.device.get()); + } nvenc = nvenc_d3d.get(); return true; @@ -1409,7 +1594,7 @@ namespace platf::dxgi { device_ctx->OMSetBlendState(blend_disable.get(), nullptr, 0xFFFFFFFFu); device_ctx->PSSetSamplers(0, 1, &sampler_linear); - device_ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + device_ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); return 0; } @@ -1746,6 +1931,16 @@ namespace platf::dxgi { compile_pixel_shader_helper(convert_yuv420_planar_y_ps_linear); compile_pixel_shader_helper(convert_yuv420_planar_y_ps_perceptual_quantizer); compile_vertex_shader_helper(convert_yuv420_planar_y_vs); + compile_pixel_shader_helper(convert_yuv444_packed_ayuv_ps); + compile_pixel_shader_helper(convert_yuv444_packed_ayuv_ps_linear); + compile_vertex_shader_helper(convert_yuv444_packed_vs); + compile_pixel_shader_helper(convert_yuv444_planar_ps); + compile_pixel_shader_helper(convert_yuv444_planar_ps_linear); + compile_pixel_shader_helper(convert_yuv444_planar_ps_perceptual_quantizer); + compile_pixel_shader_helper(convert_yuv444_packed_y410_ps); + compile_pixel_shader_helper(convert_yuv444_packed_y410_ps_linear); + compile_pixel_shader_helper(convert_yuv444_packed_y410_ps_perceptual_quantizer); + compile_vertex_shader_helper(convert_yuv444_planar_vs); compile_pixel_shader_helper(cursor_ps); compile_pixel_shader_helper(cursor_ps_normalize_white); compile_vertex_shader_helper(cursor_vs); diff --git a/src/rtsp.cpp b/src/rtsp.cpp index 044b8fbbe8c..5915e1c24af 100644 --- a/src/rtsp.cpp +++ b/src/rtsp.cpp @@ -984,6 +984,7 @@ namespace rtsp_stream { args.try_emplace("x-nv-aqos.qosTrafficType"sv, "4"sv); args.try_emplace("x-ml-video.configuredBitrateKbps"sv, "0"sv); args.try_emplace("x-ss-general.encryptionEnabled"sv, "0"sv); + args.try_emplace("x-ss-video[0].chromaSamplingType"sv, "0"sv); stream::config_t config; @@ -1019,6 +1020,7 @@ namespace rtsp_stream { config.monitor.encoderCscMode = util::from_view(args.at("x-nv-video[0].encoderCscMode"sv)); config.monitor.videoFormat = util::from_view(args.at("x-nv-vqos[0].bitStreamFormat"sv)); config.monitor.dynamicRange = util::from_view(args.at("x-nv-video[0].dynamicRangeMode"sv)); + config.monitor.chromaSamplingType = util::from_view(args.at("x-ss-video[0].chromaSamplingType"sv)); configuredBitrateKbps = util::from_view(args.at("x-ml-video.configuredBitrateKbps"sv)); } diff --git a/src/video.cpp b/src/video.cpp index 95783dc725b..d4517cd7e83 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -274,6 +274,7 @@ namespace video { NO_RC_BUF_LIMIT = 1 << 7, // Don't set rc_buffer_size REF_FRAMES_INVALIDATION = 1 << 8, // Support reference frames invalidation ALWAYS_REPROBE = 1 << 9, // This is an encoder of last resort and we want to aggressively probe for a better one + YUV444_SUPPORT = 1 << 10, // Encoder may support 4:4:4 chroma sampling depending on hardware }; class avcodec_encode_session_t: public encode_session_t { @@ -447,7 +448,8 @@ namespace video { "nvenc"sv, std::make_unique( platf::mem_type_e::dxgi, - platf::pix_fmt_e::nv12, platf::pix_fmt_e::p010), + platf::pix_fmt_e::nv12, platf::pix_fmt_e::p010, + platf::pix_fmt_e::ayuv, platf::pix_fmt_e::yuv444p16), { // Common options {}, @@ -484,7 +486,7 @@ namespace video { std::nullopt, // QP rate control fallback "h264_nvenc"s, }, - PARALLEL_ENCODING | REF_FRAMES_INVALIDATION // flags + PARALLEL_ENCODING | REF_FRAMES_INVALIDATION | YUV444_SUPPORT // flags }; #elif !defined(__APPLE__) encoder_t nvenc { @@ -498,6 +500,7 @@ namespace video { AV_PIX_FMT_CUDA, #endif AV_PIX_FMT_NV12, AV_PIX_FMT_P010, + AV_PIX_FMT_NONE, AV_PIX_FMT_NONE, #ifdef _WIN32 dxgi_init_avcodec_hardware_input_buffer #else @@ -581,6 +584,7 @@ namespace video { AV_HWDEVICE_TYPE_D3D11VA, AV_HWDEVICE_TYPE_QSV, AV_PIX_FMT_QSV, AV_PIX_FMT_NV12, AV_PIX_FMT_P010, + AV_PIX_FMT_VUYX, AV_PIX_FMT_XV30, dxgi_init_avcodec_hardware_input_buffer), { // Common options @@ -653,7 +657,7 @@ namespace video { std::nullopt, // QP rate control fallback "h264_qsv"s, }, - PARALLEL_ENCODING | CBR_WITH_VBR | RELAXED_COMPLIANCE | NO_RC_BUF_LIMIT + PARALLEL_ENCODING | CBR_WITH_VBR | RELAXED_COMPLIANCE | NO_RC_BUF_LIMIT | YUV444_SUPPORT }; encoder_t amdvce { @@ -662,6 +666,7 @@ namespace video { AV_HWDEVICE_TYPE_D3D11VA, AV_HWDEVICE_TYPE_NONE, AV_PIX_FMT_D3D11, AV_PIX_FMT_NV12, AV_PIX_FMT_P010, + AV_PIX_FMT_NONE, AV_PIX_FMT_NONE, dxgi_init_avcodec_hardware_input_buffer), { // Common options @@ -737,6 +742,7 @@ namespace video { AV_HWDEVICE_TYPE_NONE, AV_HWDEVICE_TYPE_NONE, AV_PIX_FMT_NONE, AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P10, + AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV444P10, nullptr), { // libsvtav1 takes different presets than libx264/libx265. @@ -792,7 +798,7 @@ namespace video { std::nullopt, // QP rate control fallback "libx264"s, }, - H264_ONLY | PARALLEL_ENCODING | ALWAYS_REPROBE + H264_ONLY | PARALLEL_ENCODING | ALWAYS_REPROBE | YUV444_SUPPORT }; #ifdef __linux__ @@ -802,6 +808,7 @@ namespace video { AV_HWDEVICE_TYPE_VAAPI, AV_HWDEVICE_TYPE_NONE, AV_PIX_FMT_VAAPI, AV_PIX_FMT_NV12, AV_PIX_FMT_P010, + AV_PIX_FMT_NONE, AV_PIX_FMT_NONE, vaapi_init_avcodec_hardware_input_buffer), { // Common options @@ -870,6 +877,7 @@ namespace video { AV_HWDEVICE_TYPE_VIDEOTOOLBOX, AV_HWDEVICE_TYPE_NONE, AV_PIX_FMT_VIDEOTOOLBOX, AV_PIX_FMT_NV12, AV_PIX_FMT_P010, + AV_PIX_FMT_NONE, AV_PIX_FMT_NONE, vt_init_avcodec_hardware_input_buffer), { // Common options @@ -940,6 +948,7 @@ namespace video { int active_hevc_mode; int active_av1_mode; bool last_encoder_probe_supported_ref_frames_invalidation = false; + std::array last_encoder_probe_supported_yuv444_for_codec = {}; void reset_display(std::shared_ptr &disp, const platf::mem_type_e &type, const std::string &display_name, const config_t &config) { @@ -1395,6 +1404,11 @@ namespace video { return nullptr; } + if (config.chromaSamplingType > 0 && !video_format[encoder_t::YUV444]) { + BOOST_LOG(error) << video_format.name << ": YUV 4:4:4 not supported"sv; + return nullptr; + } + auto codec = avcodec_find_encoder_by_name(video_format.name.c_str()); if (!codec) { BOOST_LOG(error) << "Couldn't open ["sv << video_format.name << ']'; @@ -1403,7 +1417,11 @@ namespace video { } auto colorspace = encode_device->colorspace; - auto sw_fmt = (colorspace.bit_depth == 10) ? platform_formats->avcodec_pix_fmt_10bit : platform_formats->avcodec_pix_fmt_8bit; + auto sw_fmt = (colorspace.bit_depth == 8 && config.chromaSamplingType == 0) ? platform_formats->avcodec_pix_fmt_8bit : + (colorspace.bit_depth == 8 && config.chromaSamplingType == 1) ? platform_formats->avcodec_pix_fmt_yuv444_8bit : + (colorspace.bit_depth == 10 && config.chromaSamplingType == 0) ? platform_formats->avcodec_pix_fmt_10bit : + (colorspace.bit_depth == 10 && config.chromaSamplingType == 1) ? platform_formats->avcodec_pix_fmt_yuv444_10bit : + AV_PIX_FMT_NONE; // Allow up to 1 retry to apply the set of fallback options. // @@ -1420,16 +1438,25 @@ namespace video { switch (config.videoFormat) { case 0: - ctx->profile = FF_PROFILE_H264_HIGH; + // 10-bit h264 encoding is not supported by our streaming protocol + assert(!config.dynamicRange); + ctx->profile = (config.chromaSamplingType == 1) ? FF_PROFILE_H264_HIGH_444 : FF_PROFILE_H264_HIGH; break; case 1: - ctx->profile = config.dynamicRange ? FF_PROFILE_HEVC_MAIN_10 : FF_PROFILE_HEVC_MAIN; + if (config.chromaSamplingType == 1) { + // HEVC uses the same RExt profile for both 8 and 10 bit YUV 4:4:4 encoding + ctx->profile = FF_PROFILE_HEVC_REXT; + } + else { + ctx->profile = config.dynamicRange ? FF_PROFILE_HEVC_MAIN_10 : FF_PROFILE_HEVC_MAIN; + } break; case 2: // AV1 supports both 8 and 10 bit encoding with the same Main profile - ctx->profile = FF_PROFILE_AV1_MAIN; + // but YUV 4:4:4 sampling requires High profile + ctx->profile = (config.chromaSamplingType == 1) ? FF_PROFILE_AV1_HIGH : FF_PROFILE_AV1_MAIN; break; } @@ -1874,7 +1901,24 @@ namespace video { std::unique_ptr result; auto colorspace = colorspace_from_client_config(config, disp.is_hdr()); - auto pix_fmt = (colorspace.bit_depth == 10) ? encoder.platform_formats->pix_fmt_10bit : encoder.platform_formats->pix_fmt_8bit; + + platf::pix_fmt_e pix_fmt; + if (config.chromaSamplingType == 1) { + // YUV 4:4:4 + if (!(encoder.flags & YUV444_SUPPORT)) { + // Encoder can't support YUV 4:4:4 regardless of hardware capabilities + return {}; + } + pix_fmt = (colorspace.bit_depth == 10) ? + encoder.platform_formats->pix_fmt_yuv444_10bit : + encoder.platform_formats->pix_fmt_yuv444_8bit; + } + else { + // YUV 4:2:0 + pix_fmt = (colorspace.bit_depth == 10) ? + encoder.platform_formats->pix_fmt_10bit : + encoder.platform_formats->pix_fmt_8bit; + } if (dynamic_cast(encoder.platform_formats.get())) { result = disp.make_avcodec_encode_device(pix_fmt); @@ -2299,8 +2343,8 @@ namespace video { encoder.av1.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 }; + config_t config_max_ref_frames { 1920, 1080, 60, 1000, 1, 1, 1, 0, 0, 0 }; + config_t config_autoselect { 1920, 1080, 60, 1000, 1, 0, 1, 0, 0, 0 }; // If the encoder isn't supported at all (not even H.264), bail early reset_display(disp, encoder.platform_formats->dev_type, config::video.output_name, config_autoselect); @@ -2343,6 +2387,14 @@ namespace video { encoder.h264[encoder_t::REF_FRAMES_RESTRICT] = max_ref_frames_h264 >= 0; encoder.h264[encoder_t::PASSED] = true; + if (encoder.flags & YUV444_SUPPORT) { + config_t config_h264_autoselect_yuv444 { 1920, 1080, 60, 1000, 1, 0, 1, 0, 0, 1 }; + encoder.h264[encoder_t::YUV444] = validate_config(disp, encoder, config_h264_autoselect_yuv444); + } + else { + encoder.h264[encoder_t::YUV444] = false; + } + if (test_hevc) { config_max_ref_frames.videoFormat = 1; config_autoselect.videoFormat = 1; @@ -2419,35 +2471,41 @@ namespace video { encoder.av1.capabilities.reset(); } - std::vector> configs { - { encoder_t::DYNAMIC_RANGE, { 1920, 1080, 60, 1000, 1, 0, 3, 1, 1 } }, - }; + // Test HDR and YUV444 support + { + const config_t generic_hdr_config = { 1920, 1080, 60, 1000, 1, 0, 3, 1, 1, 0 }; + + auto test_hdr_and_yuv444 = [&](auto &flag_map, auto video_format) { + auto config = generic_hdr_config; + config.videoFormat = video_format; + + if (!flag_map[encoder_t::PASSED]) return; - for (auto &[flag, config] : configs) { - auto h264 = config; - auto hevc = config; - auto av1 = config; + // Test 4:4:4 HDR first. If 4:4:4 is supported, 4:2:0 should also be supported. + // Skip if H.264 failed the 4:4:4 SDR validation. + config.chromaSamplingType = 1; + if (encoder.h264[encoder_t::YUV444] && validate_config(disp, encoder, config) >= 0) { + flag_map[encoder_t::DYNAMIC_RANGE] = true; + flag_map[encoder_t::YUV444] = true; + return; + } - h264.videoFormat = 0; - hevc.videoFormat = 1; - av1.videoFormat = 2; + // Test 4:2:0 HDR + config.chromaSamplingType = 0; + flag_map[encoder_t::DYNAMIC_RANGE] = validate_config(disp, encoder, config) >= 0; + }; // Reset the display since we're switching from SDR to HDR - reset_display(disp, encoder.platform_formats->dev_type, config::video.output_name, config); + reset_display(disp, encoder.platform_formats->dev_type, config::video.output_name, generic_hdr_config); if (!disp) { return false; } // HDR is not supported with H.264. Don't bother even trying it. - encoder.h264[flag] = flag != encoder_t::DYNAMIC_RANGE && validate_config(disp, encoder, h264) >= 0; + encoder.h264[encoder_t::DYNAMIC_RANGE] = false; - if (encoder.hevc[encoder_t::PASSED]) { - encoder.hevc[flag] = validate_config(disp, encoder, hevc) >= 0; - } - - if (encoder.av1[encoder_t::PASSED]) { - encoder.av1[flag] = validate_config(disp, encoder, av1) >= 0; - } + test_hdr_and_yuv444(encoder.hevc, 1); + test_hdr_and_yuv444(encoder.av1, 2); } encoder.h264[encoder_t::VUI_PARAMETERS] = encoder.h264[encoder_t::VUI_PARAMETERS] && !config::sunshine.flags[config::flag::FORCE_VIDEO_HEADER_REPLACE]; @@ -2611,6 +2669,12 @@ namespace video { auto &encoder = *chosen_encoder; last_encoder_probe_supported_ref_frames_invalidation = (encoder.flags & REF_FRAMES_INVALIDATION); + last_encoder_probe_supported_yuv444_for_codec[0] = encoder.h264[encoder_t::PASSED] && + encoder.h264[encoder_t::YUV444]; + last_encoder_probe_supported_yuv444_for_codec[1] = encoder.hevc[encoder_t::PASSED] && + encoder.hevc[encoder_t::YUV444]; + last_encoder_probe_supported_yuv444_for_codec[2] = encoder.av1[encoder_t::PASSED] && + encoder.av1[encoder_t::YUV444]; BOOST_LOG(debug) << "------ h264 ------"sv; for (int x = 0; x < encoder_t::MAX_FLAGS; ++x) { @@ -2799,6 +2863,10 @@ namespace video { platf::pix_fmt_e map_pix_fmt(AVPixelFormat fmt) { switch (fmt) { + case AV_PIX_FMT_VUYX: + return platf::pix_fmt_e::ayuv; + case AV_PIX_FMT_XV30: + return platf::pix_fmt_e::y410; case AV_PIX_FMT_YUV420P10: return platf::pix_fmt_e::yuv420p10; case AV_PIX_FMT_YUV420P: diff --git a/src/video.h b/src/video.h index ba80474669f..74858e75600 100644 --- a/src/video.h +++ b/src/video.h @@ -39,6 +39,7 @@ namespace video { virtual ~encoder_platform_formats_t() = default; platf::mem_type_e dev_type; platf::pix_fmt_e pix_fmt_8bit, pix_fmt_10bit; + platf::pix_fmt_e pix_fmt_yuv444_8bit, pix_fmt_yuv444_10bit; }; struct encoder_platform_formats_avcodec: encoder_platform_formats_t { @@ -50,21 +51,28 @@ namespace video { const AVPixelFormat &avcodec_dev_pix_fmt, const AVPixelFormat &avcodec_pix_fmt_8bit, const AVPixelFormat &avcodec_pix_fmt_10bit, + const AVPixelFormat &avcodec_pix_fmt_yuv444_8bit, + const AVPixelFormat &avcodec_pix_fmt_yuv444_10bit, const init_buffer_function_t &init_avcodec_hardware_input_buffer_function): avcodec_base_dev_type { avcodec_base_dev_type }, avcodec_derived_dev_type { avcodec_derived_dev_type }, avcodec_dev_pix_fmt { avcodec_dev_pix_fmt }, avcodec_pix_fmt_8bit { avcodec_pix_fmt_8bit }, avcodec_pix_fmt_10bit { avcodec_pix_fmt_10bit }, + avcodec_pix_fmt_yuv444_8bit { avcodec_pix_fmt_yuv444_8bit }, + avcodec_pix_fmt_yuv444_10bit { avcodec_pix_fmt_yuv444_10bit }, init_avcodec_hardware_input_buffer { init_avcodec_hardware_input_buffer_function } { dev_type = map_base_dev_type(avcodec_base_dev_type); pix_fmt_8bit = map_pix_fmt(avcodec_pix_fmt_8bit); pix_fmt_10bit = map_pix_fmt(avcodec_pix_fmt_10bit); + pix_fmt_yuv444_8bit = map_pix_fmt(avcodec_pix_fmt_yuv444_8bit); + pix_fmt_yuv444_10bit = map_pix_fmt(avcodec_pix_fmt_yuv444_10bit); } AVHWDeviceType avcodec_base_dev_type, avcodec_derived_dev_type; AVPixelFormat avcodec_dev_pix_fmt; AVPixelFormat avcodec_pix_fmt_8bit, avcodec_pix_fmt_10bit; + AVPixelFormat avcodec_pix_fmt_yuv444_8bit, avcodec_pix_fmt_yuv444_10bit; init_buffer_function_t init_avcodec_hardware_input_buffer; }; @@ -73,10 +81,14 @@ namespace video { encoder_platform_formats_nvenc( const platf::mem_type_e &dev_type, const platf::pix_fmt_e &pix_fmt_8bit, - const platf::pix_fmt_e &pix_fmt_10bit) { + const platf::pix_fmt_e &pix_fmt_10bit, + const platf::pix_fmt_e &pix_fmt_yuv444_8bit, + const platf::pix_fmt_e &pix_fmt_yuv444_10bit) { encoder_platform_formats_t::dev_type = dev_type; encoder_platform_formats_t::pix_fmt_8bit = pix_fmt_8bit; encoder_platform_formats_t::pix_fmt_10bit = pix_fmt_10bit; + encoder_platform_formats_t::pix_fmt_yuv444_8bit = pix_fmt_yuv444_8bit; + encoder_platform_formats_t::pix_fmt_yuv444_10bit = pix_fmt_yuv444_10bit; } }; @@ -87,6 +99,7 @@ namespace video { REF_FRAMES_RESTRICT, // Set maximum reference frames CBR, // Some encoders don't support CBR, if not supported --> attempt constant quantatication parameter instead DYNAMIC_RANGE, // hdr + YUV444, // 4:4:4 VUI_PARAMETERS, // AMD encoder with VAAPI doesn't add VUI parameters to SPS MAX_FLAGS }; @@ -101,6 +114,7 @@ namespace video { _CONVERT(REF_FRAMES_RESTRICT); _CONVERT(CBR); _CONVERT(DYNAMIC_RANGE); + _CONVERT(YUV444); _CONVERT(VUI_PARAMETERS); _CONVERT(MAX_FLAGS); } @@ -312,11 +326,14 @@ namespace video { /* Encoding color depth (bit depth): 0 - 8-bit, 1 - 10-bit HDR encoding activates when color depth is higher than 8-bit and the display which is being captured is operating in HDR mode */ int dynamicRange; + + int chromaSamplingType; // 0 - 4:2:0, 1 - 4:4:4 }; extern int active_hevc_mode; extern int active_av1_mode; extern bool last_encoder_probe_supported_ref_frames_invalidation; + extern std::array last_encoder_probe_supported_yuv444_for_codec; // 0 - H.264, 1 - HEVC, 2 - AV1 void capture( diff --git a/src/video_colorspace.cpp b/src/video_colorspace.cpp index 4f5955eed7e..6931027b9a5 100644 --- a/src/video_colorspace.cpp +++ b/src/video_colorspace.cpp @@ -178,4 +178,109 @@ namespace video { return result; } + const color_t * + new_color_vectors_from_colorspace(const sunshine_colorspace_t &colorspace) { + constexpr auto generate_color_vectors = [](const sunshine_colorspace_t &colorspace) -> color_t { + double Kr, Kb; + switch (colorspace.colorspace) { + case colorspace_e::rec601: + Kr = 0.299; + Kb = 0.114; + break; + case colorspace_e::rec709: + default: + Kr = 0.2126; + Kb = 0.0722; + break; + case colorspace_e::bt2020: + case colorspace_e::bt2020sdr: + Kr = 0.2627; + Kb = 0.0593; + break; + }; + double Kg = 1.0 - Kr - Kb; + + double y_mult, y_add; + double uv_mult, uv_add; + + // "Matrix coefficients" section of ITU-T H.273 + if (colorspace.full_range) { + y_mult = (1 << colorspace.bit_depth) - 1; + y_add = 0; + uv_mult = (1 << colorspace.bit_depth) - 1; + uv_add = (1 << (colorspace.bit_depth - 1)); + } + else { + y_mult = (1 << (colorspace.bit_depth - 8)) * 219; + y_add = (1 << (colorspace.bit_depth - 8)) * 16; + uv_mult = (1 << (colorspace.bit_depth - 8)) * 224; + uv_add = (1 << (colorspace.bit_depth - 8)) * 128; + } + + // For rounding + y_add += 0.5; + uv_add += 0.5; + + color_t color_vectors; + + color_vectors.color_vec_y[0] = Kr * y_mult; + color_vectors.color_vec_y[1] = Kg * y_mult; + color_vectors.color_vec_y[2] = Kb * y_mult; + color_vectors.color_vec_y[3] = y_add; + + color_vectors.color_vec_u[0] = -0.5 * Kr / (1.0 - Kb) * uv_mult; + color_vectors.color_vec_u[1] = -0.5 * Kg / (1.0 - Kb) * uv_mult; + color_vectors.color_vec_u[2] = 0.5 * uv_mult; + color_vectors.color_vec_u[3] = uv_add; + + color_vectors.color_vec_v[0] = 0.5 * uv_mult; + color_vectors.color_vec_v[1] = -0.5 * Kg / (1.0 - Kr) * uv_mult; + color_vectors.color_vec_v[2] = -0.5 * Kb / (1.0 - Kr) * uv_mult; + color_vectors.color_vec_v[3] = uv_add; + + // Unused + color_vectors.range_y[0] = 1; + color_vectors.range_y[1] = 0; + color_vectors.range_uv[0] = 1; + color_vectors.range_uv[1] = 0; + + return color_vectors; + }; + + static constexpr color_t colors[] = { + generate_color_vectors({ colorspace_e::rec601, false, 8 }), + generate_color_vectors({ colorspace_e::rec601, true, 8 }), + generate_color_vectors({ colorspace_e::rec601, false, 10 }), + generate_color_vectors({ colorspace_e::rec601, true, 10 }), + generate_color_vectors({ colorspace_e::rec709, false, 8 }), + generate_color_vectors({ colorspace_e::rec709, true, 8 }), + generate_color_vectors({ colorspace_e::rec709, false, 10 }), + generate_color_vectors({ colorspace_e::rec709, true, 10 }), + generate_color_vectors({ colorspace_e::bt2020, false, 8 }), + generate_color_vectors({ colorspace_e::bt2020, true, 8 }), + generate_color_vectors({ colorspace_e::bt2020, false, 10 }), + generate_color_vectors({ colorspace_e::bt2020, true, 10 }), + }; + + const color_t *result = nullptr; + + switch (colorspace.colorspace) { + case colorspace_e::rec601: + result = &colors[0]; + break; + case colorspace_e::rec709: + default: + result = &colors[4]; + break; + case colorspace_e::bt2020: + case colorspace_e::bt2020sdr: + result = &colors[8]; + break; + }; + + if (colorspace.bit_depth == 10) result += 2; + if (colorspace.full_range) result += 1; + + return result; + } } // namespace video diff --git a/src/video_colorspace.h b/src/video_colorspace.h index 858914ce6ed..9d1b1e3aa32 100644 --- a/src/video_colorspace.h +++ b/src/video_colorspace.h @@ -53,4 +53,6 @@ namespace video { const color_t * color_vectors_from_colorspace(colorspace_e colorspace, bool full_range); + const color_t * + new_color_vectors_from_colorspace(const sunshine_colorspace_t &colorspace); } // namespace video diff --git a/src_assets/windows/assets/shaders/directx/convert_yuv444_packed_ayuv_ps.hlsl b/src_assets/windows/assets/shaders/directx/convert_yuv444_packed_ayuv_ps.hlsl new file mode 100644 index 00000000000..73c45e9b9cd --- /dev/null +++ b/src_assets/windows/assets/shaders/directx/convert_yuv444_packed_ayuv_ps.hlsl @@ -0,0 +1,3 @@ +#include "include/convert_base.hlsl" + +#include "include/convert_yuv444_ps_base.hlsl" diff --git a/src_assets/windows/assets/shaders/directx/convert_yuv444_packed_ayuv_ps_linear.hlsl b/src_assets/windows/assets/shaders/directx/convert_yuv444_packed_ayuv_ps_linear.hlsl new file mode 100644 index 00000000000..820e5128f67 --- /dev/null +++ b/src_assets/windows/assets/shaders/directx/convert_yuv444_packed_ayuv_ps_linear.hlsl @@ -0,0 +1,3 @@ +#include "include/convert_linear_base.hlsl" + +#include "include/convert_yuv444_ps_base.hlsl" diff --git a/src_assets/windows/assets/shaders/directx/convert_yuv444_packed_vs.hlsl b/src_assets/windows/assets/shaders/directx/convert_yuv444_packed_vs.hlsl new file mode 100644 index 00000000000..33e481453ed --- /dev/null +++ b/src_assets/windows/assets/shaders/directx/convert_yuv444_packed_vs.hlsl @@ -0,0 +1,10 @@ +cbuffer rotate_texture_steps_cbuffer : register(b1) { + int rotate_texture_steps; +}; + +#include "include/base_vs.hlsl" + +vertex_t main_vs(uint vertex_id : SV_VertexID) +{ + return generate_fullscreen_triangle_vertex(vertex_id, rotate_texture_steps); +} diff --git a/src_assets/windows/assets/shaders/directx/convert_yuv444_packed_y410_ps.hlsl b/src_assets/windows/assets/shaders/directx/convert_yuv444_packed_y410_ps.hlsl new file mode 100644 index 00000000000..b84c661783b --- /dev/null +++ b/src_assets/windows/assets/shaders/directx/convert_yuv444_packed_y410_ps.hlsl @@ -0,0 +1,4 @@ +#include "include/convert_base.hlsl" + +#define Y410 +#include "include/convert_yuv444_ps_base.hlsl" diff --git a/src_assets/windows/assets/shaders/directx/convert_yuv444_packed_y410_ps_linear.hlsl b/src_assets/windows/assets/shaders/directx/convert_yuv444_packed_y410_ps_linear.hlsl new file mode 100644 index 00000000000..f7dbbcb6ee8 --- /dev/null +++ b/src_assets/windows/assets/shaders/directx/convert_yuv444_packed_y410_ps_linear.hlsl @@ -0,0 +1,4 @@ +#include "include/convert_linear_base.hlsl" + +#define Y410 +#include "include/convert_yuv444_ps_base.hlsl" diff --git a/src_assets/windows/assets/shaders/directx/convert_yuv444_packed_y410_ps_perceptual_quantizer.hlsl b/src_assets/windows/assets/shaders/directx/convert_yuv444_packed_y410_ps_perceptual_quantizer.hlsl new file mode 100644 index 00000000000..1682be7b632 --- /dev/null +++ b/src_assets/windows/assets/shaders/directx/convert_yuv444_packed_y410_ps_perceptual_quantizer.hlsl @@ -0,0 +1,4 @@ +#include "include/convert_perceptual_quantizer_base.hlsl" + +#define Y410 +#include "include/convert_yuv444_ps_base.hlsl" diff --git a/src_assets/windows/assets/shaders/directx/convert_yuv444_planar_ps.hlsl b/src_assets/windows/assets/shaders/directx/convert_yuv444_planar_ps.hlsl new file mode 100644 index 00000000000..d6cca979e37 --- /dev/null +++ b/src_assets/windows/assets/shaders/directx/convert_yuv444_planar_ps.hlsl @@ -0,0 +1,4 @@ +#include "include/convert_base.hlsl" + +#define PLANAR_VIEWPORTS +#include "include/convert_yuv444_ps_base.hlsl" diff --git a/src_assets/windows/assets/shaders/directx/convert_yuv444_planar_ps_linear.hlsl b/src_assets/windows/assets/shaders/directx/convert_yuv444_planar_ps_linear.hlsl new file mode 100644 index 00000000000..46032651e40 --- /dev/null +++ b/src_assets/windows/assets/shaders/directx/convert_yuv444_planar_ps_linear.hlsl @@ -0,0 +1,4 @@ +#include "include/convert_linear_base.hlsl" + +#define PLANAR_VIEWPORTS +#include "include/convert_yuv444_ps_base.hlsl" diff --git a/src_assets/windows/assets/shaders/directx/convert_yuv444_planar_ps_perceptual_quantizer.hlsl b/src_assets/windows/assets/shaders/directx/convert_yuv444_planar_ps_perceptual_quantizer.hlsl new file mode 100644 index 00000000000..d390e81eb28 --- /dev/null +++ b/src_assets/windows/assets/shaders/directx/convert_yuv444_planar_ps_perceptual_quantizer.hlsl @@ -0,0 +1,4 @@ +#include "include/convert_perceptual_quantizer_base.hlsl" + +#define PLANAR_VIEWPORTS +#include "include/convert_yuv444_ps_base.hlsl" diff --git a/src_assets/windows/assets/shaders/directx/convert_yuv444_planar_vs.hlsl b/src_assets/windows/assets/shaders/directx/convert_yuv444_planar_vs.hlsl new file mode 100644 index 00000000000..566da5d8cf8 --- /dev/null +++ b/src_assets/windows/assets/shaders/directx/convert_yuv444_planar_vs.hlsl @@ -0,0 +1,33 @@ +cbuffer rotate_texture_steps_cbuffer : register(b1) { + int rotate_texture_steps; +}; + +cbuffer color_matrix_cbuffer : register(b3) { + float4 color_vec_y; + float4 color_vec_u; + float4 color_vec_v; + float2 range_y; + float2 range_uv; +}; + +#define PLANAR_VIEWPORTS +#include "include/base_vs.hlsl" + +vertex_t main_vs(uint vertex_id : SV_VertexID) +{ + vertex_t output = generate_fullscreen_triangle_vertex(vertex_id % 3, rotate_texture_steps); + + output.viewport = vertex_id / 3; + + if (output.viewport == 0) { + output.color_vec = color_vec_y; + } + else if (output.viewport == 1) { + output.color_vec = color_vec_u; + } + else { + output.color_vec = color_vec_v; + } + + return output; +} diff --git a/src_assets/windows/assets/shaders/directx/include/base_vs.hlsl b/src_assets/windows/assets/shaders/directx/include/base_vs.hlsl index c04fad39018..c39e7c6f80b 100644 --- a/src_assets/windows/assets/shaders/directx/include/base_vs.hlsl +++ b/src_assets/windows/assets/shaders/directx/include/base_vs.hlsl @@ -19,7 +19,7 @@ vertex_t generate_fullscreen_triangle_vertex(uint vertex_id, int rotate_texture_ output.viewpoint_pos = float4(-1, 3, 0, 1); tex_coord = float2(0, -1); } - else if (vertex_id == 2) { + else { output.viewpoint_pos = float4(3, -1, 0, 1); tex_coord = float2(2, 1); } diff --git a/src_assets/windows/assets/shaders/directx/include/base_vs_types.hlsl b/src_assets/windows/assets/shaders/directx/include/base_vs_types.hlsl index 9e4b28f18fb..cf755c5a0ff 100644 --- a/src_assets/windows/assets/shaders/directx/include/base_vs_types.hlsl +++ b/src_assets/windows/assets/shaders/directx/include/base_vs_types.hlsl @@ -9,4 +9,8 @@ struct vertex_t #else float2 tex_coord : TEXCOORD; #endif +#ifdef PLANAR_VIEWPORTS + uint viewport : SV_ViewportArrayIndex; + nointerpolation float4 color_vec : COLOR0; +#endif }; diff --git a/src_assets/windows/assets/shaders/directx/include/convert_yuv444_ps_base.hlsl b/src_assets/windows/assets/shaders/directx/include/convert_yuv444_ps_base.hlsl new file mode 100644 index 00000000000..2aa8401e532 --- /dev/null +++ b/src_assets/windows/assets/shaders/directx/include/convert_yuv444_ps_base.hlsl @@ -0,0 +1,39 @@ +Texture2D image : register(t0); +SamplerState def_sampler : register(s0); + +#ifndef PLANAR_VIEWPORTS +cbuffer color_matrix_cbuffer : register(b0) { + float4 color_vec_y; + float4 color_vec_u; + float4 color_vec_v; + float2 range_y; + float2 range_uv; +}; +#endif + +#include "include/base_vs_types.hlsl" + +#ifdef PLANAR_VIEWPORTS +uint main_ps(vertex_t input) : SV_Target +#else +uint4 main_ps(vertex_t input) : SV_Target +#endif +{ + float3 rgb = CONVERT_FUNCTION(image.Sample(def_sampler, input.tex_coord, 0).rgb); + +#ifdef PLANAR_VIEWPORTS + // Planar R16, 10 most significant bits store the value + return uint(dot(input.color_vec.xyz, rgb) + input.color_vec.w) << 6; +#else + float y = dot(color_vec_y.xyz, rgb) + color_vec_y.w; + float u = dot(color_vec_u.xyz, rgb) + color_vec_u.w; + float v = dot(color_vec_v.xyz, rgb) + color_vec_v.w; + +#ifdef Y410 + return uint4(u, y, v, 0); +#else + // AYUV + return uint4(v, u, y, 0); +#endif +#endif +} diff --git a/third-party/moonlight-common-c b/third-party/moonlight-common-c index 8599b6042a4..3010f873f58 160000 --- a/third-party/moonlight-common-c +++ b/third-party/moonlight-common-c @@ -1 +1 @@ -Subproject commit 8599b6042a4ba27749b0f94134dd614b4328a9bc +Subproject commit 3010f873f589721bb761ea95fb6cc7ff34f4e024