From 17e9b803db3ff3125450d798b95dd87aec345eb2 Mon Sep 17 00:00:00 2001 From: loki Date: Wed, 22 Apr 2020 00:07:26 +0300 Subject: [PATCH 1/4] Display cursor type color with nvenc --- sunshine/platform/windows_dxgi.cpp | 172 +++++++++++++++++++++++++++-- 1 file changed, 165 insertions(+), 7 deletions(-) diff --git a/sunshine/platform/windows_dxgi.cpp b/sunshine/platform/windows_dxgi.cpp index 7832d127..4417bf2f 100644 --- a/sunshine/platform/windows_dxgi.cpp +++ b/sunshine/platform/windows_dxgi.cpp @@ -138,6 +138,12 @@ struct cursor_t { bool visible; }; +struct gpu_cursor_t { + texture2d_t texture; + + LONG width, height; +}; + void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) { int height = cursor.shape_info.Height / 2; int width = cursor.shape_info.Width; @@ -290,8 +296,63 @@ void blend_cursor(const cursor_t &cursor, img_t &img) { } } +std::vector make_cursor_image(std::vector &&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; + + std::vector cursor_img; + cursor_img.resize(shape_info.Width * shape_info.Height * 4); + std::fill_n((std::uint32_t*)cursor_img.data(), cursor_img.size() / sizeof(std::uint32_t), 0x99888888); + + 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) { + LONG x = ((float)rel_x) / in_width * out_width; + LONG y = ((float)rel_y) / in_height * out_height; + + // Ensure it's within bounds + auto left = std::min(out_width, std::max(0, x)); + auto top = std::min(out_height, std::max(0, y)); + auto right = std::max(0, std::min(out_width, x + cursor_width)); + auto bottom = std::max(0, std::min(out_height, y + cursor_height)); + + RECT rect { left, top, right, bottom }; + ctx->VideoProcessorSetStreamDestRect(processor.get(), 1, TRUE, &rect); + + cursor_visible = visible; + } + + 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 = ((float)width) / in_width * out_width; + cursor_height = ((float)height) / in_height * out_height; + + return 0; + } + int convert(platf::img_t &img_base) override { auto &img = (img_d3d_t&)img_base; @@ -302,17 +363,19 @@ public: 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() << ']'; + 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 }; + 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, 1, &stream); + 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; @@ -333,8 +396,15 @@ public: ) { 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)) { @@ -401,13 +471,16 @@ public: D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC output_desc { D3D11_VPOV_DIMENSION_TEXTURE2D, 0 }; video::processor_out_t::pointer processor_out_p; - status = device->CreateVideoProcessorOutputView(tex_p, processor_e.get(), &output_desc, &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; @@ -417,6 +490,11 @@ public: 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; @@ -426,6 +504,16 @@ public: 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; + + float out_width, out_height; + float in_width, in_height; + + std::vector *hwdevices_p; }; class display_base_t : public ::platf::display_t { @@ -797,11 +885,72 @@ public: return capture_status; } - const bool update_flag = frame_info.AccumulatedFrames != 0 || frame_info.LastPresentTime.QuadPart != 0; + 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 {}; + + std::vector img_data; + img_data.resize(frame_info.PointerShapeBufferSize); + + UINT dummy; + status = dup.dup->GetFramePointerShape(img_data.size(), img_data.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 { + cursor_img.data(), + 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); @@ -891,7 +1040,7 @@ public: return nullptr; } - auto hwdevice = std::make_shared(); + auto hwdevice = std::make_shared(&hwdevices); auto ret = hwdevice->init( shared_from_this(), @@ -905,8 +1054,17 @@ public: 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; } + + gpu_cursor_t cursor; + std::vector hwdevices; }; const char *format_str[] = { From 519f7a8bf18962050f44bfe3457d0694f6113014 Mon Sep 17 00:00:00 2001 From: loki Date: Wed, 22 Apr 2020 22:55:33 +0300 Subject: [PATCH 2/4] convert pointer shape monochrome to color --- sunshine/platform/windows_dxgi.cpp | 75 ++++++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 8 deletions(-) diff --git a/sunshine/platform/windows_dxgi.cpp b/sunshine/platform/windows_dxgi.cpp index 4417bf2f..a2966831 100644 --- a/sunshine/platform/windows_dxgi.cpp +++ b/sunshine/platform/windows_dxgi.cpp @@ -296,7 +296,7 @@ void blend_cursor(const cursor_t &cursor, img_t &img) { } } -std::vector make_cursor_image(std::vector &&img_data, DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info) { +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: @@ -307,9 +307,69 @@ std::vector make_cursor_image(std::vector &&img_data shape_info.Height /= 2; - std::vector cursor_img; - cursor_img.resize(shape_info.Width * shape_info.Height * 4); - std::fill_n((std::uint32_t*)cursor_img.data(), cursor_img.size() / sizeof(std::uint32_t), 0x99888888); + 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; } @@ -896,11 +956,10 @@ public: if(frame_info.PointerShapeBufferSize > 0) { DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info {}; - std::vector img_data; - img_data.resize(frame_info.PointerShapeBufferSize); + util::buffer_t img_data { frame_info.PointerShapeBufferSize }; UINT dummy; - status = dup.dup->GetFramePointerShape(img_data.size(), img_data.data(), &dummy, &shape_info); + 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() << ']'; @@ -910,7 +969,7 @@ public: auto cursor_img = make_cursor_image(std::move(img_data), shape_info); D3D11_SUBRESOURCE_DATA data { - cursor_img.data(), + std::begin(cursor_img), 4 * shape_info.Width, 0 }; From 2e52402e2750ab85b793783e7efb02b1273c3a20 Mon Sep 17 00:00:00 2001 From: loki Date: Thu, 23 Apr 2020 00:09:27 +0300 Subject: [PATCH 3/4] Correctly truncate cursor image --- sunshine/platform/windows_dxgi.cpp | 38 ++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/sunshine/platform/windows_dxgi.cpp b/sunshine/platform/windows_dxgi.cpp index a2966831..39841074 100644 --- a/sunshine/platform/windows_dxgi.cpp +++ b/sunshine/platform/windows_dxgi.cpp @@ -380,17 +380,25 @@ public: hwdevice_t() = delete; void set_cursor_pos(LONG rel_x, LONG rel_y, bool visible) { - LONG x = ((float)rel_x) / in_width * out_width; - LONG y = ((float)rel_y) / in_height * out_height; + 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 = std::min(out_width, std::max(0, x)); - auto top = std::min(out_height, std::max(0, y)); - auto right = std::max(0, std::min(out_width, x + cursor_width)); - auto bottom = std::max(0, std::min(out_height, y + cursor_height)); + 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)); - RECT rect { left, top, right, bottom }; - ctx->VideoProcessorSetStreamDestRect(processor.get(), 1, TRUE, &rect); + 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); cursor_visible = visible; } @@ -407,8 +415,10 @@ public: cursor_in.reset(processor_in_p); - cursor_width = ((float)width) / in_width * out_width; - cursor_height = ((float)height) / in_height * out_height; + 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; } @@ -568,10 +578,12 @@ public: video::processor_in_t cursor_in; bool cursor_visible; - LONG cursor_width, cursor_height; - float out_width, out_height; - float in_width, in_height; + 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; }; From fa489531b0f5fd67a745796451e71cf4cb00d4da Mon Sep 17 00:00:00 2001 From: loki Date: Thu, 23 Apr 2020 00:23:40 +0300 Subject: [PATCH 4/4] Don't access video device ctx merely for setting cursor invisible --- sunshine/platform/windows_dxgi.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sunshine/platform/windows_dxgi.cpp b/sunshine/platform/windows_dxgi.cpp index 39841074..cf414872 100644 --- a/sunshine/platform/windows_dxgi.cpp +++ b/sunshine/platform/windows_dxgi.cpp @@ -380,6 +380,12 @@ public: 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; @@ -399,8 +405,6 @@ public: ctx->VideoProcessorSetStreamSourceRect(processor.get(), 1, TRUE, &rect_in); ctx->VideoProcessorSetStreamDestRect(processor.get(), 1, TRUE, &rect_out); - - cursor_visible = visible; } int set_cursor_texture(texture2d_t::pointer texture, LONG width, LONG height) {