// // Created by loki on 1/12/20. // #include #include #include #include #include "sunshine/main.h" #include "common.h" namespace platf { using namespace std::literals; } namespace platf::dxgi { template void Release(T *dxgi) { dxgi->Release(); } using factory1_t = util::safe_ptr>; using dxgi_t = util::safe_ptr>; using dxgi1_t = util::safe_ptr>; using device_t = util::safe_ptr>; using device_ctx_t = util::safe_ptr>; using adapter_t = util::safe_ptr>; using output_t = util::safe_ptr>; using output1_t = util::safe_ptr>; using dup_t = util::safe_ptr>; using texture2d_t = util::safe_ptr>; using resource_t = util::safe_ptr>; extern const char *format_str[]; class duplication_t { public: dup_t dup; bool has_frame {}; capture_e next_frame(DXGI_OUTDUPL_FRAME_INFO &frame_info, resource_t::pointer *res_p) { auto capture_status = release_frame(); if(capture_status != capture_e::ok) { return capture_status; } auto status = dup->AcquireNextFrame(1000, &frame_info, res_p); switch(status) { case S_OK: has_frame = true; return capture_e::ok; case DXGI_ERROR_WAIT_TIMEOUT: return capture_e::timeout; case WAIT_ABANDONED: case DXGI_ERROR_ACCESS_LOST: case DXGI_ERROR_ACCESS_DENIED: return capture_e::reinit; default: BOOST_LOG(error) << "Couldn't acquire next frame [0x"sv << util::hex(status).to_string_view(); return capture_e::error; } } capture_e reset(dup_t::pointer dup_p = dup_t::pointer()) { auto capture_status = release_frame(); dup.reset(dup_p); return capture_status; } capture_e release_frame() { if(!has_frame) { return capture_e::ok; } auto status = dup->ReleaseFrame(); switch (status) { case S_OK: has_frame = false; return capture_e::ok; case DXGI_ERROR_WAIT_TIMEOUT: return capture_e::timeout; case WAIT_ABANDONED: case DXGI_ERROR_ACCESS_LOST: case DXGI_ERROR_ACCESS_DENIED: has_frame = false; return capture_e::reinit; default: BOOST_LOG(error) << "Couldn't release frame [0x"sv << util::hex(status).to_string_view(); return capture_e::error; } } ~duplication_t() { release_frame(); } }; class display_t; struct img_t : public ::platf::img_t { ~img_t() override { delete[] data; data = nullptr; } }; struct cursor_t { std::vector img_data; DXGI_OUTDUPL_POINTER_SHAPE_INFO shape_info; int x, y; bool visible; }; void blend_cursor_monochrome(const cursor_t &cursor, img_t &img) { int height = cursor.shape_info.Height / 2; int width = cursor.shape_info.Width; int pitch = cursor.shape_info.Pitch; // img cursor.y < 0, skip parts of the cursor.img_data auto cursor_skip_y = std::min(0, cursor.y); auto cursor_skip_x = std::min(0, cursor.x); auto img_skip_y = std::max(0, cursor.y); auto img_skip_x = std::max(0, cursor.x); if(cursor_skip_y > height || cursor_skip_x > width) { return; } height -= cursor_skip_y; width -= cursor_skip_x; auto cursor_img_data = cursor.img_data.data() + cursor_skip_y * pitch; int delta_height = std::min(height, std::max(0, img.height - img_skip_y)); int delta_width = std::min(width, std::max(0, img.width - img_skip_x)); auto pixels_per_byte = width / pitch; auto bytes_per_row = delta_width / pixels_per_byte; auto img_data = (int*)img.data; for(int i = 0; i < delta_height; ++i) { auto and_mask = &cursor_img_data[i * pitch]; auto xor_mask = &cursor_img_data[(i + height) * pitch]; auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x]; auto skip_y = cursor_skip_y; for(int x = 0; x < bytes_per_row; ++x) { for(auto bit = 0u; bit < 8; ++bit) { if(skip_y > 0) { --skip_y; continue; } int and_ = *and_mask & (1 << (7 - bit)) ? -1 : 0; int xor_ = *xor_mask & (1 << (7 - bit)) ? -1 : 0; *img_pixel_p &= and_; *img_pixel_p ^= xor_; ++img_pixel_p; } ++and_mask; ++xor_mask; } } } void blend_cursor_color(const cursor_t &cursor, img_t &img) { int height = cursor.shape_info.Height; int width = cursor.shape_info.Width; int pitch = cursor.shape_info.Pitch; // img cursor.y < 0, skip parts of the cursor.img_data auto cursor_skip_y = std::min(0, cursor.y); auto cursor_skip_x = std::min(0, cursor.x); auto img_skip_y = std::max(0, cursor.y); auto img_skip_x = std::max(0, cursor.x); if(cursor_skip_y > height || cursor_skip_x > width) { return; } height -= cursor_skip_y; width -= cursor_skip_x; auto cursor_img_data = (int*)&cursor.img_data[cursor_skip_y * pitch]; int delta_height = std::min(height, std::max(0, img.height - img_skip_y)); int delta_width = std::min(width, std::max(0, img.width - img_skip_x)); auto img_data = (int*)img.data; for(int i = 0; i < delta_height; ++i) { auto cursor_begin = &cursor_img_data[i * width + cursor_skip_x]; auto cursor_end = &cursor_begin[delta_width]; auto img_pixel_p = &img_data[(i + img_skip_y) * (img.row_pitch / img.pixel_pitch) + img_skip_x]; std::for_each(cursor_begin, cursor_end, [&](int cursor_pixel) { auto colors_out = (std::uint8_t*)&cursor_pixel; auto colors_in = (std::uint8_t*)img_pixel_p; //TODO: When use of IDXGIOutput5 is implemented, support different color formats auto alpha = colors_out[3]; if(alpha == 255) { *img_pixel_p = cursor_pixel; } else { colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255/2) / 255; colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255/2) / 255; colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255/2) / 255; } ++img_pixel_p; }); } } void blend_cursor(const cursor_t &cursor, img_t &img) { switch(cursor.shape_info.Type) { case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR: blend_cursor_color(cursor, img); break; case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME: blend_cursor_monochrome(cursor, img); break; case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR: default: BOOST_LOG(warning) << "Unsupported cursor format ["sv << cursor.shape_info.Type << ']'; } } class display_t : public ::platf::display_t { public: capture_e snapshot(::platf::img_t *img_base, bool cursor_visible) override { auto img = (img_t *) img_base; HRESULT status; DXGI_OUTDUPL_FRAME_INFO frame_info; resource_t::pointer res_p {}; auto capture_status = dup.next_frame(frame_info, &res_p); resource_t res{res_p}; if (capture_status != capture_e::ok) { return capture_status; } if (frame_info.PointerShapeBufferSize > 0) { auto &img_data = cursor.img_data; img_data.resize(frame_info.PointerShapeBufferSize); UINT dummy; status = dup.dup->GetFramePointerShape(img_data.size(), img_data.data(), &dummy, &cursor.shape_info); if (FAILED(status)) { BOOST_LOG(error) << "Failed to get new pointer shape [0x"sv << util::hex(status).to_string_view() << ']'; return capture_e::error; } } if(frame_info.LastMouseUpdateTime.QuadPart) { cursor.x = frame_info.PointerPosition.Position.x; cursor.y = frame_info.PointerPosition.Position.y; cursor.visible = frame_info.PointerPosition.Visible; } // If frame has been updated if (frame_info.LastPresentTime.QuadPart != 0) { { texture2d_t::pointer src_p {}; status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src_p); texture2d_t src{src_p}; if (FAILED(status)) { BOOST_LOG(error) << "Couldn't query interface [0x"sv << util::hex(status).to_string_view() << ']'; return capture_e::error; } //Copy from GPU to CPU device_ctx->CopyResource(texture.get(), src.get()); } if(current_img.pData) { device_ctx->Unmap(texture.get(), 0); current_img.pData = nullptr; } status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, ¤t_img); if (FAILED(status)) { BOOST_LOG(error) << "Failed to map texture [0x"sv << util::hex(status).to_string_view() << ']'; return capture_e::error; } } const bool update_flag = frame_info.LastMouseUpdateTime.QuadPart || frame_info.LastPresentTime.QuadPart != 0 || frame_info.PointerShapeBufferSize > 0; if(!update_flag) { return capture_e::timeout; } if(img->width != width || img->height != height) { delete[] img->data; img->data = new std::uint8_t[height * current_img.RowPitch]; img->width = width; img->height = height; img->row_pitch = current_img.RowPitch; } std::copy_n((std::uint8_t*)current_img.pData, height * current_img.RowPitch, (std::uint8_t*)img->data); if(cursor_visible && cursor.visible) { blend_cursor(cursor, *img); } return capture_e::ok; } std::unique_ptr<::platf::img_t> alloc_img() override { auto img = std::make_unique(); img->data = nullptr; img->height = 0; img->width = 0; img->row_pitch = 0; img->pixel_pitch = 4; return img; } int init() { /* Uncomment when use of IDXGIOutput5 is implemented std::call_once(windows_cpp_once_flag, []() { DECLARE_HANDLE(DPI_AWARENESS_CONTEXT); const auto DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = ((DPI_AWARENESS_CONTEXT)-4); typedef BOOL (*User32_SetProcessDpiAwarenessContext)(DPI_AWARENESS_CONTEXT value); auto user32 = LoadLibraryA("user32.dll"); auto f = (User32_SetProcessDpiAwarenessContext)GetProcAddress(user32, "SetProcessDpiAwarenessContext"); if(f) { f(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); } FreeLibrary(user32); }); */ current_img.pData = nullptr; // current_img is not yet mapped dxgi::factory1_t::pointer factory_p {}; dxgi::adapter_t::pointer adapter_p {}; dxgi::output_t::pointer output_p {}; dxgi::device_t::pointer device_p {}; dxgi::device_ctx_t::pointer device_ctx_p {}; HRESULT status; status = CreateDXGIFactory1(IID_IDXGIFactory1, (void**)&factory_p); factory.reset(factory_p); if(FAILED(status)) { BOOST_LOG(error) << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } for(int x = 0; factory_p->EnumAdapters1(x, &adapter_p) != DXGI_ERROR_NOT_FOUND; ++x) { dxgi::adapter_t adapter_tmp { adapter_p }; for(int y = 0; adapter_tmp->EnumOutputs(y, &output_p) != DXGI_ERROR_NOT_FOUND; ++y) { dxgi::output_t output_tmp {output_p }; DXGI_OUTPUT_DESC desc; output_tmp->GetDesc(&desc); if(desc.AttachedToDesktop) { output = std::move(output_tmp); width = desc.DesktopCoordinates.right - desc.DesktopCoordinates.left; height = desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top; } } if(output) { adapter = std::move(adapter_tmp); break; } } if(!output) { BOOST_LOG(error) << "Failed to locate an output device"sv; return -1; } D3D_FEATURE_LEVEL featureLevels[] { D3D_FEATURE_LEVEL_12_1, D3D_FEATURE_LEVEL_12_0, D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3, D3D_FEATURE_LEVEL_9_2, D3D_FEATURE_LEVEL_9_1 }; status = adapter->QueryInterface(IID_IDXGIAdapter, (void**)&adapter_p); if(FAILED(status)) { BOOST_LOG(error) << "Failed to query IDXGIAdapter interface"sv; return -1; } status = D3D11CreateDevice( adapter_p, D3D_DRIVER_TYPE_UNKNOWN, nullptr, D3D11_CREATE_DEVICE_VIDEO_SUPPORT, featureLevels, sizeof(featureLevels) / sizeof(D3D_FEATURE_LEVEL), D3D11_SDK_VERSION, &device_p, &feature_level, &device_ctx_p); adapter_p->Release(); device.reset(device_p); device_ctx.reset(device_ctx_p); if(FAILED(status)) { BOOST_LOG(error) << "Failed to create D3D11 device [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } DXGI_ADAPTER_DESC adapter_desc; adapter->GetDesc(&adapter_desc); BOOST_LOG(info) << "Device Description : "sv << adapter_desc.Description << std::endl << "Device Vendor ID : 0x"sv << util::hex(adapter_desc.VendorId).to_string_view() << std::endl << "Device Device ID : 0x"sv << util::hex(adapter_desc.DeviceId).to_string_view() << std::endl << "Device Video Mem : "sv << adapter_desc.DedicatedVideoMemory / 1048576 << " MiB"sv << std::endl << "Device Sys Mem : "sv << adapter_desc.DedicatedSystemMemory / 1048576 << " MiB"sv << std::endl << "Share Sys Mem : "sv << adapter_desc.SharedSystemMemory / 1048576 << " MiB"sv << std::endl << "Feature Level : 0x"sv << util::hex(feature_level).to_string_view() << std::endl << "Capture size : "sv << width << 'x' << height; // Bump up thread priority { dxgi::dxgi_t::pointer dxgi_p {}; status = device->QueryInterface(IID_IDXGIDevice, (void**)&dxgi_p); dxgi::dxgi_t dxgi { dxgi_p }; if(FAILED(status)) { BOOST_LOG(error) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } dxgi->SetGPUThreadPriority(7); } // Try to reduce latency { dxgi::dxgi1_t::pointer dxgi_p {}; status = device->QueryInterface(IID_IDXGIDevice, (void**)&dxgi_p); dxgi::dxgi1_t dxgi { dxgi_p }; if(FAILED(status)) { BOOST_LOG(error) << "Failed to query DXGI interface from device [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } dxgi->SetMaximumFrameLatency(1); } //FIXME: Duplicate output on RX580 in combination with DOOM (2016) --> BSOD //TODO: Use IDXGIOutput5 for improved performance { dxgi::output1_t::pointer output1_p {}; status = output->QueryInterface(IID_IDXGIOutput1, (void**)&output1_p); dxgi::output1_t output1 {output1_p }; if(FAILED(status)) { BOOST_LOG(error) << "Failed to query IDXGIOutput1 from the output"sv; return -1; } // We try this twice, in case we still get an error on reinitialization for(int x = 0; x < 2; ++x) { dxgi::dup_t::pointer dup_p {}; status = output1->DuplicateOutput((IUnknown*)device.get(), &dup_p); if(SUCCEEDED(status)) { dup.reset(dup_p); break; } std::this_thread::sleep_for(200ms); } if(FAILED(status)) { BOOST_LOG(error) << "DuplicateOutput Failed [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } } DXGI_OUTDUPL_DESC dup_desc; dup.dup->GetDesc(&dup_desc); format = dup_desc.ModeDesc.Format; BOOST_LOG(debug) << "Source format ["sv << format_str[dup_desc.ModeDesc.Format] << ']'; D3D11_TEXTURE2D_DESC t {}; t.Width = width; t.Height = height; t.MipLevels = 1; t.ArraySize = 1; t.SampleDesc.Count = 1; t.Usage = D3D11_USAGE_STAGING; t.Format = format; t.CPUAccessFlags = D3D11_CPU_ACCESS_READ; dxgi::texture2d_t::pointer tex_p {}; status = device->CreateTexture2D(&t, nullptr, &tex_p); texture.reset(tex_p); if(FAILED(status)) { BOOST_LOG(error) << "Failed to create texture [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } // map the texture simply to get the pitch and stride status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, ¤t_img); if(FAILED(status)) { BOOST_LOG(error) << "Error: Failed to map the texture [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } return 0; } ~display_t() override { if(current_img.pData) { device_ctx->Unmap(texture.get(), 0); current_img.pData = nullptr; } } factory1_t factory; adapter_t adapter; output_t output; device_t device; device_ctx_t device_ctx; duplication_t dup; cursor_t cursor; texture2d_t texture; int width, height; DXGI_FORMAT format; D3D_FEATURE_LEVEL feature_level; D3D11_MAPPED_SUBRESOURCE current_img; }; const char *format_str[] = { "DXGI_FORMAT_UNKNOWN", "DXGI_FORMAT_R32G32B32A32_TYPELESS", "DXGI_FORMAT_R32G32B32A32_FLOAT", "DXGI_FORMAT_R32G32B32A32_UINT", "DXGI_FORMAT_R32G32B32A32_SINT", "DXGI_FORMAT_R32G32B32_TYPELESS", "DXGI_FORMAT_R32G32B32_FLOAT", "DXGI_FORMAT_R32G32B32_UINT", "DXGI_FORMAT_R32G32B32_SINT", "DXGI_FORMAT_R16G16B16A16_TYPELESS", "DXGI_FORMAT_R16G16B16A16_FLOAT", "DXGI_FORMAT_R16G16B16A16_UNORM", "DXGI_FORMAT_R16G16B16A16_UINT", "DXGI_FORMAT_R16G16B16A16_SNORM", "DXGI_FORMAT_R16G16B16A16_SINT", "DXGI_FORMAT_R32G32_TYPELESS", "DXGI_FORMAT_R32G32_FLOAT", "DXGI_FORMAT_R32G32_UINT", "DXGI_FORMAT_R32G32_SINT", "DXGI_FORMAT_R32G8X24_TYPELESS", "DXGI_FORMAT_D32_FLOAT_S8X24_UINT", "DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS", "DXGI_FORMAT_X32_TYPELESS_G8X24_UINT", "DXGI_FORMAT_R10G10B10A2_TYPELESS", "DXGI_FORMAT_R10G10B10A2_UNORM", "DXGI_FORMAT_R10G10B10A2_UINT", "DXGI_FORMAT_R11G11B10_FLOAT", "DXGI_FORMAT_R8G8B8A8_TYPELESS", "DXGI_FORMAT_R8G8B8A8_UNORM", "DXGI_FORMAT_R8G8B8A8_UNORM_SRGB", "DXGI_FORMAT_R8G8B8A8_UINT", "DXGI_FORMAT_R8G8B8A8_SNORM", "DXGI_FORMAT_R8G8B8A8_SINT", "DXGI_FORMAT_R16G16_TYPELESS", "DXGI_FORMAT_R16G16_FLOAT", "DXGI_FORMAT_R16G16_UNORM", "DXGI_FORMAT_R16G16_UINT", "DXGI_FORMAT_R16G16_SNORM", "DXGI_FORMAT_R16G16_SINT", "DXGI_FORMAT_R32_TYPELESS", "DXGI_FORMAT_D32_FLOAT", "DXGI_FORMAT_R32_FLOAT", "DXGI_FORMAT_R32_UINT", "DXGI_FORMAT_R32_SINT", "DXGI_FORMAT_R24G8_TYPELESS", "DXGI_FORMAT_D24_UNORM_S8_UINT", "DXGI_FORMAT_R24_UNORM_X8_TYPELESS", "DXGI_FORMAT_X24_TYPELESS_G8_UINT", "DXGI_FORMAT_R8G8_TYPELESS", "DXGI_FORMAT_R8G8_UNORM", "DXGI_FORMAT_R8G8_UINT", "DXGI_FORMAT_R8G8_SNORM", "DXGI_FORMAT_R8G8_SINT", "DXGI_FORMAT_R16_TYPELESS", "DXGI_FORMAT_R16_FLOAT", "DXGI_FORMAT_D16_UNORM", "DXGI_FORMAT_R16_UNORM", "DXGI_FORMAT_R16_UINT", "DXGI_FORMAT_R16_SNORM", "DXGI_FORMAT_R16_SINT", "DXGI_FORMAT_R8_TYPELESS", "DXGI_FORMAT_R8_UNORM", "DXGI_FORMAT_R8_UINT", "DXGI_FORMAT_R8_SNORM", "DXGI_FORMAT_R8_SINT", "DXGI_FORMAT_A8_UNORM", "DXGI_FORMAT_R1_UNORM", "DXGI_FORMAT_R9G9B9E5_SHAREDEXP", "DXGI_FORMAT_R8G8_B8G8_UNORM", "DXGI_FORMAT_G8R8_G8B8_UNORM", "DXGI_FORMAT_BC1_TYPELESS", "DXGI_FORMAT_BC1_UNORM", "DXGI_FORMAT_BC1_UNORM_SRGB", "DXGI_FORMAT_BC2_TYPELESS", "DXGI_FORMAT_BC2_UNORM", "DXGI_FORMAT_BC2_UNORM_SRGB", "DXGI_FORMAT_BC3_TYPELESS", "DXGI_FORMAT_BC3_UNORM", "DXGI_FORMAT_BC3_UNORM_SRGB", "DXGI_FORMAT_BC4_TYPELESS", "DXGI_FORMAT_BC4_UNORM", "DXGI_FORMAT_BC4_SNORM", "DXGI_FORMAT_BC5_TYPELESS", "DXGI_FORMAT_BC5_UNORM", "DXGI_FORMAT_BC5_SNORM", "DXGI_FORMAT_B5G6R5_UNORM", "DXGI_FORMAT_B5G5R5A1_UNORM", "DXGI_FORMAT_B8G8R8A8_UNORM", "DXGI_FORMAT_B8G8R8X8_UNORM", "DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM", "DXGI_FORMAT_B8G8R8A8_TYPELESS", "DXGI_FORMAT_B8G8R8A8_UNORM_SRGB", "DXGI_FORMAT_B8G8R8X8_TYPELESS", "DXGI_FORMAT_B8G8R8X8_UNORM_SRGB", "DXGI_FORMAT_BC6H_TYPELESS", "DXGI_FORMAT_BC6H_UF16", "DXGI_FORMAT_BC6H_SF16", "DXGI_FORMAT_BC7_TYPELESS", "DXGI_FORMAT_BC7_UNORM", "DXGI_FORMAT_BC7_UNORM_SRGB", "DXGI_FORMAT_AYUV", "DXGI_FORMAT_Y410", "DXGI_FORMAT_Y416", "DXGI_FORMAT_NV12", "DXGI_FORMAT_P010", "DXGI_FORMAT_P016", "DXGI_FORMAT_420_OPAQUE", "DXGI_FORMAT_YUY2", "DXGI_FORMAT_Y210", "DXGI_FORMAT_Y216", "DXGI_FORMAT_NV11", "DXGI_FORMAT_AI44", "DXGI_FORMAT_IA44", "DXGI_FORMAT_P8", "DXGI_FORMAT_A8P8", "DXGI_FORMAT_B4G4R4A4_UNORM", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "DXGI_FORMAT_P208", "DXGI_FORMAT_V208", "DXGI_FORMAT_V408" }; } namespace platf { std::shared_ptr display() { auto disp = std::make_unique(); if (disp->init()) { return nullptr; } return disp; } }