7 Commits

Author SHA1 Message Date
dijunkun
288bb96e0c [feat] add implementation for WGC capture on virtual screens 2025-10-21 17:30:36 +08:00
dijunkun
5fed09c1aa [ci] only close inactive issues with no labels 2025-10-21 11:37:25 +08:00
dijunkun
8e499772f9 [ci] add GitHub Actions workflow to automatically close inactive issues 2025-10-21 11:29:31 +08:00
dijunkun
0da812e7e9 [chore] update README 2025-10-20 21:51:09 +08:00
dijunkun
3d67b6e9d6 [chore] update README 2025-10-20 11:18:35 +08:00
dijunkun
506b2893c6 [chore] update README 2025-10-20 11:04:41 +08:00
Junkun Di
56abe9e690 [chore] update README 2025-10-20 09:55:59 +08:00
12 changed files with 919 additions and 243 deletions

View File

@@ -1,4 +1,4 @@
name: Build and Release CrossDesk
name: Build and Release
on:
push:

42
.github/workflows/close-issue.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: Close Inactive Issues
on:
schedule:
# run every day at midnight
- cron: "0 0 * * *"
jobs:
close_inactive_issues:
runs-on: ubuntu-latest
steps:
- name: Check inactive issues and close them
uses: actions/github-script@v6
with:
script: |
const { data: issues } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
});
const now = new Date().getTime();
const inactivePeriod = 7 * 24 * 60 * 60 * 1000; // 7 days
for (const issue of issues) {
const lastUpdated = new Date(issue.updated_at).getTime();
// if the issue hasn't been updated in the past week, close it
if (now - lastUpdated > inactivePeriod && issue.labels.length === 0) {
console.log(`Closing inactive issue: ${issue.number} (No labels)`);
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
state: 'closed',
});
} else if (issue.labels.length === 0) {
console.log(`Skipping issue ${issue.number} (No labels) as it has been recently updated.`);
} else {
console.log(`Skipping issue ${issue.number} (Has labels).`);
}
}

View File

@@ -1,6 +1,15 @@
# CrossDesk
[English](README_EN.md) / [中文](README.md)
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey.svg)]()
[![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)
[![GitHub last commit](https://img.shields.io/github/last-commit/kunkundi/crossdesk)](https://github.com/kunkundi/crossdesk/commits/self-hosted-server)
[![Build Status](https://github.com/kunkundi/crossdesk/actions/workflows/build.yaml/badge.svg)](https://github.com/kunkundi/crossdesk/actions)
[![Docker Pulls](https://img.shields.io/docker/pulls/crossdesk/crossdesk-server)](https://hub.docker.com/r/crossdesk/crossdesk-server/tags)
[![GitHub issues](https://img.shields.io/github/issues/kunkundi/crossdesk.svg)]()
[![GitHub stars](https://img.shields.io/github/stars/kunkundi/crossdesk.svg?style=social)]()
[![GitHub forks](https://img.shields.io/github/forks/kunkundi/crossdesk.svg?style=social)]()
[ [English](README_EN.md) / 中文 ]
![sup_example](https://github.com/user-attachments/assets/eeb64fbe-1f07-4626-be1c-b77396beb905)
@@ -46,24 +55,45 @@ git submodule init
git submodule update
xmake b crossdesk
xmake b -vy crossdesk
```
#### 无 CUDA 环境下的开发支持
对于未安装 **CUDA 环境** 的Linux开发者这里提供了预配置的 [Ubuntu 22.04 Docker 镜像](https://hub.docker.com/r/crossdesk/ubuntu22.04)。
该镜像内置必要的构建依赖,可在容器中开箱即用,无需额外配置即可直接编译项目。
运行
```
xmake r crossdesk
```
### 无 CUDA 环境下的开发支持
对于未安装 **CUDA 环境** 的 Linux 开发者,这里提供了预配置的 [Ubuntu 22.04 Docker 镜像](https://hub.docker.com/r/crossdesk/ubuntu22.04)。该镜像内置必要的构建依赖,可在容器中开箱即用,无需额外配置即可直接编译项目。
进入容器,下载工程后执行:
```
export CUDA_PATH=/usr/local/cuda
export XMAKE_GLOBALDIR=/data
xmake b --root crossdesk
xmake b --root -vy crossdesk
```
运行
对于未安装 **CUDA 环境** 的 Windows 开发者,执行下面的命令安装 CUDA 编译环境:
```
xmake r crossdesk
xmake require -vy "cuda 12.6.3"
```
安装完成后执行:
```
xmake require --info "cuda 12.6.3"
```
输出如下:
<img width="860" height="226" alt="Image" src="https://github.com/user-attachments/assets/999ac365-581a-4b9a-806e-05eb3e4cf44d" />
根据上述输出获取到 CUDA 的安装目录,即 installdir 指向的位置。将 CUDA_PATH 加入系统环境变量,或在终端中输入:
```
set CUDA_PATH=path_to_cuda_installdir
```
重新执行:
```
xmake b -vy crossdesk
```
#### 注意
@@ -230,7 +260,7 @@ echo " Server certificate: $SERVER_CERT"
执行
```
chmod +x generate_certs.sh
./generate_certs.sh 服务器网IP
./generate_certs.sh 服务器网IP
# 例如 ./generate_certs.sh 111.111.111.111
```

View File

@@ -1,6 +1,15 @@
# CrossDesk
[中文](README.md) / [English](README_EN.md)
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey.svg)]()
[![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)
[![GitHub last commit](https://img.shields.io/github/last-commit/kunkundi/crossdesk)](https://github.com/kunkundi/crossdesk/commits/self-hosted-server)
[![Build Status](https://github.com/kunkundi/crossdesk/actions/workflows/build.yaml/badge.svg)](https://github.com/kunkundi/crossdesk/actions)
[![Docker Pulls](https://img.shields.io/docker/pulls/crossdesk/crossdesk-server)](https://hub.docker.com/r/crossdesk/crossdesk-server/tags)
[![GitHub issues](https://img.shields.io/github/issues/kunkundi/crossdesk.svg)]()
[![GitHub stars](https://img.shields.io/github/stars/kunkundi/crossdesk.svg?style=social)]()
[![GitHub forks](https://img.shields.io/github/forks/kunkundi/crossdesk.svg?style=social)]()
[ [中文](README.md) / English ]
![sup_example](https://github.com/user-attachments/assets/3f17d8f3-7c4a-4b63-bae4-903363628687)
@@ -46,12 +55,17 @@ git submodule init
git submodule update
xmake b crossdesk
xmake b -vy crossdesk
```
Run:
```
xmake r crossdesk
```
#### Development Without CUDA Environment
For developers who do not have a **CUDA environment** installed, a preconfigured [Ubuntu 22.04 Docker image](https://hub.docker.com/r/crossdesk/ubuntu22.04) is provided.
For **Linux** developers who do not have a **CUDA environment** installed, a preconfigured [Ubuntu 22.04 Docker image](https://hub.docker.com/r/crossdesk/ubuntu22.04) is provided.
This image comes with all required build dependencies and allows you to build the project directly inside the container without any additional setup.
After entering the container, download the project and run:
@@ -59,12 +73,29 @@ After entering the container, download the project and run:
export CUDA_PATH=/usr/local/cuda
export XMAKE_GLOBALDIR=/data
xmake b --root crossdesk
xmake b --root -vy crossdesk
```
Run:
For **Windows** developers without a **CUDA environment** installed, run the following command to install the CUDA build environment:
```
xmake r crossdesk
xmake require -vy "cuda 12.6.3"
```
After the installation is complete, execute:
```
xmake require --info "cuda 12.6.3"
```
The output will look like this:
<img width="860" height="226" alt="Image" src="https://github.com/user-attachments/assets/999ac365-581a-4b9a-806e-05eb3e4cf44d" />
From the output above, locate the CUDA installation directory — this is the path pointed to by installdir.
Add this path to your system environment variable CUDA_PATH, or set it in the terminal using:
```
set CUDA_PATH=path_to_cuda_installdir:
```
Then re-run:
```
xmake b -vy crossdesk
```
#### Notice

View File

@@ -12,7 +12,7 @@
static std::vector<DisplayInfo> gs_display_list;
std::string WideToUtf8(const wchar_t *wideStr) {
std::string WideToUtf8(const wchar_t* wideStr) {
int size_needed = WideCharToMultiByte(CP_UTF8, 0, wideStr, -1, nullptr, 0,
nullptr, nullptr);
std::string result(size_needed, 0);
@@ -31,14 +31,14 @@ BOOL WINAPI EnumMonitorProc(HMONITOR hmonitor, [[maybe_unused]] HDC hdc,
if (monitor_info_.dwFlags & MONITORINFOF_PRIMARY) {
gs_display_list.insert(
gs_display_list.begin(),
{(void *)hmonitor, WideToUtf8(monitor_info_.szDevice),
{(void*)hmonitor, WideToUtf8(monitor_info_.szDevice),
(monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false,
monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top,
monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom});
*(HMONITOR *)data = hmonitor;
*(HMONITOR*)data = hmonitor;
} else {
gs_display_list.push_back(DisplayInfo(
(void *)hmonitor, WideToUtf8(monitor_info_.szDevice),
(void*)hmonitor, WideToUtf8(monitor_info_.szDevice),
(monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false,
monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top,
monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom));
@@ -81,7 +81,7 @@ bool ScreenCapturerWgc::IsWgcSupported() {
/* no contract for IGraphicsCaptureItemInterop, verify 10.0.18362.0 */
return winrt::Windows::Foundation::Metadata::ApiInformation::
IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 8);
} catch (const winrt::hresult_error &) {
} catch (const winrt::hresult_error&) {
return false;
} catch (...) {
return false;
@@ -115,7 +115,7 @@ int ScreenCapturerWgc::Init(const int fps, cb_desktop_data cb) {
}
for (int i = 0; i < display_info_list_.size(); i++) {
const auto &display = display_info_list_[i];
const auto& display = display_info_list_[i];
LOG_INFO(
"index: {}, display name: {}, is primary: {}, bounds: ({}, {}) - "
"({}, {})",
@@ -243,26 +243,28 @@ int ScreenCapturerWgc::SwitchTo(int monitor_index) {
return 0;
}
void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame &frame,
void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame& frame,
int id) {
if (on_data_) {
if (!nv12_frame_) {
nv12_frame_ = new unsigned char[frame.width * frame.height * 3 / 2];
}
libyuv::ARGBToNV12((const uint8_t *)frame.data, frame.width * 4,
(uint8_t *)nv12_frame_, frame.width,
(uint8_t *)(nv12_frame_ + frame.width * frame.height),
frame.width, frame.width, frame.height);
on_data_(nv12_frame_, frame.width * frame.height * 3 / 2, frame.width,
frame.height, display_info_list_[id].name.c_str());
if (!on_data_) {
return;
}
if (!nv12_frame_) {
nv12_frame_ = new unsigned char[frame.width * frame.height * 3 / 2];
}
libyuv::ARGBToNV12((const uint8_t*)frame.data, frame.width * 4,
(uint8_t*)nv12_frame_, frame.width,
(uint8_t*)(nv12_frame_ + frame.width * frame.height),
frame.width, frame.width, frame.height);
on_data_(nv12_frame_, frame.width * frame.height * 3 / 2, frame.width,
frame.height, display_info_list_[id].name.c_str());
}
void ScreenCapturerWgc::CleanUp() {
if (inited_) {
for (auto &session : sessions_) {
for (auto& session : sessions_) {
if (session.session_) {
session.session_->Stop();
}

View File

@@ -43,6 +43,8 @@ class ScreenCapturerWgc : public ScreenCapturer,
std::vector<DisplayInfo> display_info_list_;
int monitor_index_ = 0;
HWND hwnd_ = nullptr;
private:
class WgcSessionInfo {
public:
@@ -63,6 +65,9 @@ class ScreenCapturerWgc : public ScreenCapturer,
unsigned char* nv12_frame_ = nullptr;
unsigned char* nv12_frame_scaled_ = nullptr;
private:
bool CreateHiddenWindow();
};
#endif

View File

@@ -0,0 +1,68 @@
#ifndef _SCREEN_CAPTURER_WGC_H_
#define _SCREEN_CAPTURER_WGC_H_
#include <atomic>
#include <functional>
#include <string>
#include <thread>
#include <vector>
#include "screen_capturer.h"
#include "wgc_session.h"
#include "wgc_session_impl.h"
class ScreenCapturerWgc : public ScreenCapturer,
public WgcSession::wgc_session_observer {
public:
ScreenCapturerWgc();
~ScreenCapturerWgc();
public:
bool IsWgcSupported();
int Init(const int fps, cb_desktop_data cb) override;
int Destroy() override;
int Start() override;
int Stop() override;
int Pause(int monitor_index) override;
int Resume(int monitor_index) override;
std::vector<DisplayInfo> GetDisplayInfoList() { return display_info_list_; }
int SwitchTo(int monitor_index);
void OnFrame(const WgcSession::wgc_session_frame& frame, int id);
protected:
void CleanUp();
private:
HMONITOR monitor_;
MONITORINFOEX monitor_info_;
std::vector<DisplayInfo> display_info_list_;
int monitor_index_ = 0;
private:
class WgcSessionInfo {
public:
std::unique_ptr<WgcSession> session_;
bool inited_ = false;
bool running_ = false;
bool paused_ = false;
};
std::vector<WgcSessionInfo> sessions_;
std::atomic_bool running_;
std::atomic_bool inited_;
int fps_ = 60;
cb_desktop_data on_data_ = nullptr;
unsigned char* nv12_frame_ = nullptr;
unsigned char* nv12_frame_scaled_ = nullptr;
};
#endif

View File

@@ -0,0 +1,187 @@
#include <Windows.h>
#include <d3d11_4.h>
#include <iostream>
#include <thread>
#include "libyuv.h"
#include "rd_log.h"
#include "screen_capturer_wgc.h"
// Dummy window proc for hidden window
static LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT msg, WPARAM wParam,
LPARAM lParam) {
return DefWindowProc(hwnd, msg, wParam, lParam);
}
// ======================= 构造函数 / 析构函数 =======================
ScreenCapturerWgc::ScreenCapturerWgc()
: monitor_(nullptr),
hwnd_(nullptr),
monitor_index_(0),
running_(false),
inited_(false),
fps_(60),
on_data_(nullptr),
nv12_frame_(nullptr),
nv12_frame_scaled_(nullptr) {}
ScreenCapturerWgc::~ScreenCapturerWgc() {
Stop();
CleanUp();
if (nv12_frame_) {
delete[] nv12_frame_;
nv12_frame_ = nullptr;
}
if (nv12_frame_scaled_) {
delete[] nv12_frame_scaled_;
nv12_frame_scaled_ = nullptr;
}
}
// ======================= 隐藏窗口创建 =======================
bool ScreenCapturerWgc::CreateHiddenWindow() {
const wchar_t kClassName[] = L"ScreenCapturerHiddenWindow";
WNDCLASSW wc = {};
wc.lpfnWndProc = DummyWndProc;
wc.hInstance = GetModuleHandle(nullptr);
wc.lpszClassName = kClassName;
if (!RegisterClassW(&wc)) {
std::cerr << "Failed to register dummy window class\n";
return false;
}
hwnd_ = CreateWindowW(kClassName, L"", WS_OVERLAPPEDWINDOW, 0, 0, 1, 1,
nullptr, nullptr, wc.hInstance, nullptr);
if (!hwnd_) {
std::cerr << "Failed to create dummy window\n";
return false;
}
ShowWindow(hwnd_, SW_HIDE);
return true;
}
// ======================= 初始化 =======================
int ScreenCapturerWgc::Init(const int fps, cb_desktop_data cb) {
if (inited_) return 0;
fps_ = fps;
on_data_ = cb;
// 创建隐藏窗口
if (!CreateHiddenWindow()) {
return -1;
}
// 初始化 WGC Session
sessions_.push_back(
{std::make_unique<WgcSessionImpl>(0), false, false, false});
sessions_.back().session_->RegisterObserver(this);
int error = sessions_.back().session_->Initialize(hwnd_);
if (error != 0) {
std::cerr << "WGC session init failed\n";
return error;
}
sessions_[0].inited_ = true;
inited_ = true;
return 0;
}
int ScreenCapturerWgc::Destroy() {
Stop();
CleanUp();
return 0;
}
int ScreenCapturerWgc::Pause(int monitor_index) {
// 目前只支持隐藏窗口,所以忽略 monitor_index
if (!running_) return -1;
sessions_[0].session_->Pause();
sessions_[0].paused_ = true;
return 0;
}
int ScreenCapturerWgc::Resume(int monitor_index) {
if (!running_) return -1;
sessions_[0].session_->Resume();
sessions_[0].paused_ = false;
return 0;
}
int ScreenCapturerWgc::SwitchTo(int monitor_index) {
// 单隐藏窗口模式,不支持切换
return 0;
}
// ======================= 开始 =======================
int ScreenCapturerWgc::Start() {
if (!inited_) return -1;
if (running_) return 0;
if (sessions_.empty()) return -1;
sessions_[0].session_->Start();
sessions_[0].running_ = true;
running_ = true;
return 0;
}
// ======================= 停止 =======================
int ScreenCapturerWgc::Stop() {
if (!running_) return 0;
if (!sessions_.empty()) {
sessions_[0].session_->Stop();
sessions_[0].running_ = false;
}
running_ = false;
if (hwnd_) {
DestroyWindow(hwnd_);
hwnd_ = nullptr;
}
inited_ = false;
sessions_.clear();
return 0;
}
// ======================= 帧回调 =======================
void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame& frame,
int id) {
if (!on_data_) {
return;
}
if (!nv12_frame_) {
nv12_frame_ = new unsigned char[frame.width * frame.height * 3 / 2];
}
libyuv::ARGBToNV12((const uint8_t*)frame.data, frame.width * 4,
(uint8_t*)nv12_frame_, frame.width,
(uint8_t*)(nv12_frame_ + frame.width * frame.height),
frame.width, frame.width, frame.height);
on_data_(nv12_frame_, frame.width * frame.height * 3 / 2, frame.width,
frame.height, "hidden_window");
}
// ======================= 清理 =======================
void ScreenCapturerWgc::CleanUp() {
if (inited_) {
for (auto& session : sessions_) {
if (session.session_) {
session.session_->Stop();
}
}
sessions_.clear();
}
}

View File

@@ -7,6 +7,8 @@
#include <iostream>
#include <memory>
#include "rd_log.h"
#define CHECK_INIT \
if (!is_initialized_) { \
std::cout << "AE_NEED_INIT" << std::endl; \
@@ -20,122 +22,118 @@
extern "C" {
HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice(
::IDXGIDevice *dxgiDevice, ::IInspectable **graphicsDevice);
::IDXGIDevice* dxgiDevice, ::IInspectable** graphicsDevice);
}
WgcSessionImpl::WgcSessionImpl(int id) : id_(id) {}
WgcSessionImpl::~WgcSessionImpl() {
Stop();
CleanUp();
try {
Stop();
CleanUp();
} catch (...) {
}
}
void WgcSessionImpl::Release() { delete this; }
int WgcSessionImpl::Initialize(HWND hwnd) {
std::lock_guard locker(lock_);
std::scoped_lock locker(lock_);
target_.hwnd = hwnd;
target_.is_window = true;
return Initialize();
}
int WgcSessionImpl::Initialize(HMONITOR hmonitor) {
std::lock_guard locker(lock_);
std::scoped_lock locker(lock_);
target_.hmonitor = hmonitor;
target_.is_window = false;
return Initialize();
}
void WgcSessionImpl::RegisterObserver(wgc_session_observer *observer) {
std::lock_guard locker(lock_);
void WgcSessionImpl::RegisterObserver(wgc_session_observer* observer) {
std::scoped_lock locker(lock_);
observer_ = observer;
}
int WgcSessionImpl::Start() {
std::lock_guard locker(lock_);
std::scoped_lock locker(lock_);
CHECK_INIT;
if (is_running_) return 0;
int error = 1;
CHECK_INIT;
try {
if (!capture_session_) {
auto current_size = capture_item_.Size();
capture_framepool_ =
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool::
CreateFreeThreaded(d3d11_direct_device_,
winrt::Windows::Graphics::DirectX::
DirectXPixelFormat::B8G8R8A8UIntNormalized,
2, current_size);
capture_session_ = capture_framepool_.CreateCaptureSession(capture_item_);
capture_frame_size_ = current_size;
capture_framepool_trigger_ = capture_framepool_.FrameArrived(
winrt::auto_revoke, {this, &WgcSessionImpl::OnFrame});
capture_close_trigger_ = capture_item_.Closed(
winrt::auto_revoke, {this, &WgcSessionImpl::OnClosed});
if (!capture_item_) {
std::cout << "AE_NO_CAPTURE_ITEM" << std::endl;
LOG_ERROR("No capture item");
return 2;
}
if (!capture_framepool_) throw std::exception();
auto current_size = capture_item_.Size();
capture_framepool_ =
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool::
CreateFreeThreaded(d3d11_direct_device_,
winrt::Windows::Graphics::DirectX::
DirectXPixelFormat::B8G8R8A8UIntNormalized,
2, current_size);
is_running_ = true;
capture_session_ = capture_framepool_.CreateCaptureSession(capture_item_);
capture_frame_size_ = current_size;
// we do not need to crate a thread to enter a message loop coz we use
// CreateFreeThreaded instead of Create to create a capture frame pool,
// we need to test the performance later
// loop_ = std::thread(std::bind(&WgcSessionImpl::message_func, this));
capture_session_.StartCapture();
capture_framepool_trigger_ = capture_framepool_.FrameArrived(
winrt::auto_revoke, {this, &WgcSessionImpl::OnFrame});
capture_close_trigger_ = capture_item_.Closed(
winrt::auto_revoke, {this, &WgcSessionImpl::OnClosed});
capture_session_.IsCursorCaptureEnabled(false);
capture_session_.StartCapture();
error = 0;
} catch (winrt::hresult_error) {
std::cout << "AE_WGC_CREATE_CAPTURER_FAILED" << std::endl;
is_running_ = true;
is_paused_ = false;
return 0;
} catch (winrt::hresult_error const& e) {
LOG_ERROR("Create WGC Capture Failed: {}", winrt::to_string(e.message()));
return 86;
} catch (...) {
return 86;
}
return error;
}
int WgcSessionImpl::Stop() {
std::lock_guard locker(lock_);
std::scoped_lock locker(lock_);
CHECK_INIT;
if (!is_running_) return 0;
is_running_ = false;
// if (loop_.joinable()) loop_.join();
if (capture_framepool_trigger_) capture_framepool_trigger_.revoke();
if (capture_session_) {
capture_session_.Close();
capture_session_ = nullptr;
try {
if (capture_framepool_trigger_) capture_framepool_trigger_.revoke();
if (capture_close_trigger_) capture_close_trigger_.revoke();
if (capture_session_) {
capture_session_.Close();
capture_session_ = nullptr;
}
if (capture_framepool_) {
capture_framepool_.Close();
capture_framepool_ = nullptr;
}
} catch (...) {
}
return 0;
}
int WgcSessionImpl::Pause() {
std::lock_guard locker(lock_);
is_paused_ = true;
std::scoped_lock locker(lock_);
CHECK_INIT;
is_paused_ = true;
return 0;
}
int WgcSessionImpl::Resume() {
std::lock_guard locker(lock_);
is_paused_ = false;
std::scoped_lock locker(lock_);
CHECK_INIT;
is_paused_ = false;
return 0;
}
@@ -143,19 +141,15 @@ auto WgcSessionImpl::CreateD3D11Device() {
winrt::com_ptr<ID3D11Device> d3d_device;
HRESULT hr;
WINRT_ASSERT(!d3d_device);
D3D_DRIVER_TYPE type = D3D_DRIVER_TYPE_HARDWARE;
UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
hr = D3D11CreateDevice(nullptr, type, nullptr, flags, nullptr, 0,
D3D11_SDK_VERSION, d3d_device.put(), nullptr, nullptr);
hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, flags,
nullptr, 0, D3D11_SDK_VERSION, d3d_device.put(),
nullptr, nullptr);
if (DXGI_ERROR_UNSUPPORTED == hr) {
// change D3D_DRIVER_TYPE
type = D3D_DRIVER_TYPE_WARP;
hr = D3D11CreateDevice(nullptr, type, nullptr, flags, nullptr, 0,
D3D11_SDK_VERSION, d3d_device.put(), nullptr,
nullptr);
hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, flags,
nullptr, 0, D3D11_SDK_VERSION, d3d_device.put(),
nullptr, nullptr);
}
winrt::check_hresult(hr);
@@ -168,26 +162,28 @@ auto WgcSessionImpl::CreateD3D11Device() {
}
auto WgcSessionImpl::CreateCaptureItemForWindow(HWND hwnd) {
auto activation_factory = winrt::get_activation_factory<
winrt::Windows::Graphics::Capture::GraphicsCaptureItem>();
auto interop_factory = activation_factory.as<IGraphicsCaptureItemInterop>();
winrt::Windows::Graphics::Capture::GraphicsCaptureItem item = {nullptr};
auto interop_factory =
winrt::get_activation_factory<
winrt::Windows::Graphics::Capture::GraphicsCaptureItem>()
.as<IGraphicsCaptureItemInterop>();
winrt::Windows::Graphics::Capture::GraphicsCaptureItem item{nullptr};
interop_factory->CreateForWindow(
hwnd,
winrt::guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(),
reinterpret_cast<void **>(winrt::put_abi(item)));
reinterpret_cast<void**>(winrt::put_abi(item)));
return item;
}
auto WgcSessionImpl::CreateCaptureItemForMonitor(HMONITOR hmonitor) {
auto activation_factory = winrt::get_activation_factory<
winrt::Windows::Graphics::Capture::GraphicsCaptureItem>();
auto interop_factory = activation_factory.as<IGraphicsCaptureItemInterop>();
winrt::Windows::Graphics::Capture::GraphicsCaptureItem item = {nullptr};
auto interop_factory =
winrt::get_activation_factory<
winrt::Windows::Graphics::Capture::GraphicsCaptureItem>()
.as<IGraphicsCaptureItemInterop>();
winrt::Windows::Graphics::Capture::GraphicsCaptureItem item{nullptr};
interop_factory->CreateForMonitor(
hmonitor,
winrt::guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(),
reinterpret_cast<void **>(winrt::put_abi(item)));
reinterpret_cast<void**>(winrt::put_abi(item)));
return item;
}
@@ -196,13 +192,10 @@ HRESULT WgcSessionImpl::CreateMappedTexture(
unsigned int height) {
D3D11_TEXTURE2D_DESC src_desc;
src_texture->GetDesc(&src_desc);
D3D11_TEXTURE2D_DESC map_desc;
map_desc.Width = width == 0 ? src_desc.Width : width;
map_desc.Height = height == 0 ? src_desc.Height : height;
map_desc.MipLevels = src_desc.MipLevels;
map_desc.ArraySize = src_desc.ArraySize;
map_desc.Format = src_desc.Format;
map_desc.SampleDesc = src_desc.SampleDesc;
D3D11_TEXTURE2D_DESC map_desc = src_desc;
map_desc.Width = width ? width : src_desc.Width;
map_desc.Height = height ? height : src_desc.Height;
map_desc.Usage = D3D11_USAGE_STAGING;
map_desc.BindFlags = 0;
map_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
@@ -216,163 +209,101 @@ HRESULT WgcSessionImpl::CreateMappedTexture(
}
void WgcSessionImpl::OnFrame(
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const &sender,
[[maybe_unused]] winrt::Windows::Foundation::IInspectable const &args) {
std::lock_guard locker(lock_);
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const& sender,
[[maybe_unused]] winrt::Windows::Foundation::IInspectable const& args) {
if (!is_running_ || is_paused_) return;
std::scoped_lock locker(lock_);
auto is_new_size = false;
if (!observer_) return;
{
auto frame = sender.TryGetNextFrame();
auto frame_size = frame.ContentSize();
auto frame = sender.TryGetNextFrame();
if (!frame) return;
if (frame_size.Width != capture_frame_size_.Width ||
frame_size.Height != capture_frame_size_.Height) {
// The thing we have been capturing has changed size.
// We need to resize our swap chain first, then blit the pixels.
// After we do that, retire the frame and then recreate our frame pool.
is_new_size = true;
capture_frame_size_ = frame_size;
}
// copy to mapped texture
{
if (is_paused_) {
return;
}
auto frame_captured =
GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface());
if (!d3d11_texture_mapped_ || is_new_size)
CreateMappedTexture(frame_captured);
d3d11_device_context_->CopyResource(d3d11_texture_mapped_.get(),
frame_captured.get());
D3D11_MAPPED_SUBRESOURCE map_result;
HRESULT hr = d3d11_device_context_->Map(
d3d11_texture_mapped_.get(), 0, D3D11_MAP_READ,
0 /*coz we use CreateFreeThreaded, so we cant use flags
D3D11_MAP_FLAG_DO_NOT_WAIT*/
,
&map_result);
if (FAILED(hr)) {
OutputDebugStringW(
(L"map resource failed: " + std::to_wstring(hr)).c_str());
}
// copy data from map_result.pData
if (map_result.pData && observer_) {
observer_->OnFrame(
wgc_session_frame{static_cast<unsigned int>(frame_size.Width),
static_cast<unsigned int>(frame_size.Height),
map_result.RowPitch,
const_cast<const unsigned char *>(
(unsigned char *)map_result.pData)},
id_);
}
d3d11_device_context_->Unmap(d3d11_texture_mapped_.get(), 0);
}
}
if (is_new_size) {
auto frame_size = frame.ContentSize();
bool size_changed = (frame_size.Width != capture_frame_size_.Width ||
frame_size.Height != capture_frame_size_.Height);
if (size_changed) {
capture_frame_size_ = frame_size;
capture_framepool_.Recreate(d3d11_direct_device_,
winrt::Windows::Graphics::DirectX::
DirectXPixelFormat::B8G8R8A8UIntNormalized,
2, capture_frame_size_);
}
auto frame_surface =
GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface());
if (!d3d11_texture_mapped_ || size_changed)
CreateMappedTexture(frame_surface, frame_size.Width, frame_size.Height);
d3d11_device_context_->CopyResource(d3d11_texture_mapped_.get(),
frame_surface.get());
D3D11_MAPPED_SUBRESOURCE map_result;
HRESULT hr = d3d11_device_context_->Map(d3d11_texture_mapped_.get(), 0,
D3D11_MAP_READ, 0, &map_result);
if (SUCCEEDED(hr)) {
wgc_session_frame frame_info{
static_cast<unsigned int>(frame_size.Width),
static_cast<unsigned int>(frame_size.Height), map_result.RowPitch,
reinterpret_cast<const unsigned char*>(map_result.pData)};
observer_->OnFrame(frame_info, id_);
d3d11_device_context_->Unmap(d3d11_texture_mapped_.get(), 0);
}
}
void WgcSessionImpl::OnClosed(
winrt::Windows::Graphics::Capture::GraphicsCaptureItem const &,
winrt::Windows::Foundation::IInspectable const &) {
winrt::Windows::Graphics::Capture::GraphicsCaptureItem const&,
winrt::Windows::Foundation::IInspectable const&) {
OutputDebugStringW(L"WgcSessionImpl::OnClosed");
Stop();
}
int WgcSessionImpl::Initialize() {
if (is_initialized_) return 0;
if (!(d3d11_direct_device_ = CreateD3D11Device())) {
d3d11_direct_device_ = CreateD3D11Device();
if (!d3d11_direct_device_) {
std::cout << "AE_D3D_CREATE_DEVICE_FAILED" << std::endl;
return 1;
}
try {
if (target_.is_window)
capture_item_ = CreateCaptureItemForWindow(target_.hwnd);
else
capture_item_ = CreateCaptureItemForMonitor(target_.hmonitor);
capture_item_ = target_.is_window
? CreateCaptureItemForWindow(target_.hwnd)
: CreateCaptureItemForMonitor(target_.hmonitor);
// Set up
auto d3d11_device =
auto d3d_device =
GetDXGIInterfaceFromObject<ID3D11Device>(d3d11_direct_device_);
d3d11_device->GetImmediateContext(d3d11_device_context_.put());
} catch (winrt::hresult_error) {
std::cout << "AE_WGC_CREATE_CAPTURER_FAILED" << std::endl;
return 86;
d3d_device->GetImmediateContext(d3d11_device_context_.put());
} catch (...) {
LOG_ERROR("AE_WGC_CREATE_CAPTURER_FAILED");
return 86;
}
is_initialized_ = true;
return 0;
}
void WgcSessionImpl::CleanUp() {
std::lock_guard locker(lock_);
auto expected = false;
if (cleaned_.compare_exchange_strong(expected, true)) {
capture_close_trigger_.revoke();
capture_framepool_trigger_.revoke();
std::scoped_lock locker(lock_);
if (cleaned_.exchange(true)) return;
try {
if (capture_framepool_trigger_) capture_framepool_trigger_.revoke();
if (capture_close_trigger_) capture_close_trigger_.revoke();
if (capture_framepool_) capture_framepool_.Close();
if (capture_session_) capture_session_.Close();
capture_framepool_ = nullptr;
capture_session_ = nullptr;
capture_item_ = nullptr;
is_initialized_ = false;
} catch (...) {
}
capture_framepool_ = nullptr;
capture_session_ = nullptr;
capture_item_ = nullptr;
d3d11_texture_mapped_ = nullptr;
d3d11_device_context_ = nullptr;
d3d11_direct_device_ = nullptr;
is_initialized_ = false;
is_running_ = false;
}
LRESULT CALLBACK WindowProc(HWND window, UINT message, WPARAM w_param,
LPARAM l_param) {
return DefWindowProc(window, message, w_param, l_param);
}
// void WgcSessionImpl::message_func() {
// const std::wstring kClassName = L"am_fake_window";
// WNDCLASS wc = {};
// wc.style = CS_HREDRAW | CS_VREDRAW;
// wc.lpfnWndProc = DefWindowProc;
// wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
// wc.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW);
// wc.lpszClassName = kClassName.c_str();
// if (!::RegisterClassW(&wc)) return;
// hwnd_ = ::CreateWindowW(kClassName.c_str(), nullptr, WS_OVERLAPPEDWINDOW,
// 0,
// 0, 0, 0, nullptr, nullptr, nullptr, nullptr);
// MSG msg;
// while (is_running_) {
// while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
// if (!is_running_) break;
// TranslateMessage(&msg);
// DispatchMessage(&msg);
// }
// Sleep(10);
// }
// ::CloseWindow(hwnd_);
// ::DestroyWindow(hwnd_);
// }

View File

@@ -0,0 +1,380 @@
#include "wgc_session_impl.h"
#include <Windows.Graphics.Capture.Interop.h>
#include <atomic>
#include <functional>
#include <iostream>
#include <memory>
#include "rd_log.h"
#define CHECK_INIT \
if (!is_initialized_) { \
std::cout << "AE_NEED_INIT" << std::endl; \
return 4; \
}
#define CHECK_CLOSED \
if (cleaned_.load() == true) { \
throw winrt::hresult_error(RO_E_CLOSED); \
}
extern "C" {
HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice(
::IDXGIDevice* dxgiDevice, ::IInspectable** graphicsDevice);
}
WgcSessionImpl::WgcSessionImpl(int id) : id_(id) {}
WgcSessionImpl::~WgcSessionImpl() {
Stop();
CleanUp();
}
void WgcSessionImpl::Release() { delete this; }
int WgcSessionImpl::Initialize(HWND hwnd) {
std::lock_guard locker(lock_);
target_.hwnd = hwnd;
target_.is_window = true;
return Initialize();
}
int WgcSessionImpl::Initialize(HMONITOR hmonitor) {
std::lock_guard locker(lock_);
target_.hmonitor = hmonitor;
target_.is_window = false;
return Initialize();
}
void WgcSessionImpl::RegisterObserver(wgc_session_observer* observer) {
std::lock_guard locker(lock_);
observer_ = observer;
}
int WgcSessionImpl::Start() {
std::lock_guard locker(lock_);
if (is_running_) return 0;
int error = 1;
CHECK_INIT;
try {
if (!capture_session_) {
auto current_size = capture_item_.Size();
capture_framepool_ =
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool::
CreateFreeThreaded(d3d11_direct_device_,
winrt::Windows::Graphics::DirectX::
DirectXPixelFormat::B8G8R8A8UIntNormalized,
2, current_size);
capture_session_ = capture_framepool_.CreateCaptureSession(capture_item_);
capture_frame_size_ = current_size;
capture_framepool_trigger_ = capture_framepool_.FrameArrived(
winrt::auto_revoke, {this, &WgcSessionImpl::OnFrame});
capture_close_trigger_ = capture_item_.Closed(
winrt::auto_revoke, {this, &WgcSessionImpl::OnClosed});
}
if (!capture_framepool_) throw std::exception();
is_running_ = true;
// we do not need to crate a thread to enter a message loop coz we use
// CreateFreeThreaded instead of Create to create a capture frame pool,
// we need to test the performance later
// loop_ = std::thread(std::bind(&WgcSessionImpl::message_func, this));
capture_session_.StartCapture();
capture_session_.IsCursorCaptureEnabled(false);
error = 0;
} catch (winrt::hresult_error) {
LOG_ERROR("Create WGC Capture Failed");
return 86;
} catch (...) {
return 86;
}
return error;
}
int WgcSessionImpl::Stop() {
std::lock_guard locker(lock_);
CHECK_INIT;
is_running_ = false;
// if (loop_.joinable()) loop_.join();
if (capture_framepool_trigger_) capture_framepool_trigger_.revoke();
if (capture_session_) {
capture_session_.Close();
capture_session_ = nullptr;
}
return 0;
}
int WgcSessionImpl::Pause() {
std::lock_guard locker(lock_);
is_paused_ = true;
CHECK_INIT;
return 0;
}
int WgcSessionImpl::Resume() {
std::lock_guard locker(lock_);
is_paused_ = false;
CHECK_INIT;
return 0;
}
auto WgcSessionImpl::CreateD3D11Device() {
winrt::com_ptr<ID3D11Device> d3d_device;
HRESULT hr;
WINRT_ASSERT(!d3d_device);
D3D_DRIVER_TYPE type = D3D_DRIVER_TYPE_HARDWARE;
UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
hr = D3D11CreateDevice(nullptr, type, nullptr, flags, nullptr, 0,
D3D11_SDK_VERSION, d3d_device.put(), nullptr, nullptr);
if (DXGI_ERROR_UNSUPPORTED == hr) {
// change D3D_DRIVER_TYPE
type = D3D_DRIVER_TYPE_WARP;
hr = D3D11CreateDevice(nullptr, type, nullptr, flags, nullptr, 0,
D3D11_SDK_VERSION, d3d_device.put(), nullptr,
nullptr);
}
winrt::check_hresult(hr);
winrt::com_ptr<::IInspectable> d3d11_device;
winrt::check_hresult(CreateDirect3D11DeviceFromDXGIDevice(
d3d_device.as<IDXGIDevice>().get(), d3d11_device.put()));
return d3d11_device
.as<winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice>();
}
auto WgcSessionImpl::CreateCaptureItemForWindow(HWND hwnd) {
auto activation_factory = winrt::get_activation_factory<
winrt::Windows::Graphics::Capture::GraphicsCaptureItem>();
auto interop_factory = activation_factory.as<IGraphicsCaptureItemInterop>();
winrt::Windows::Graphics::Capture::GraphicsCaptureItem item = {nullptr};
interop_factory->CreateForWindow(
hwnd,
winrt::guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(),
reinterpret_cast<void**>(winrt::put_abi(item)));
return item;
}
auto WgcSessionImpl::CreateCaptureItemForMonitor(HMONITOR hmonitor) {
auto activation_factory = winrt::get_activation_factory<
winrt::Windows::Graphics::Capture::GraphicsCaptureItem>();
auto interop_factory = activation_factory.as<IGraphicsCaptureItemInterop>();
winrt::Windows::Graphics::Capture::GraphicsCaptureItem item = {nullptr};
interop_factory->CreateForMonitor(
hmonitor,
winrt::guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(),
reinterpret_cast<void**>(winrt::put_abi(item)));
return item;
}
HRESULT WgcSessionImpl::CreateMappedTexture(
winrt::com_ptr<ID3D11Texture2D> src_texture, unsigned int width,
unsigned int height) {
D3D11_TEXTURE2D_DESC src_desc;
src_texture->GetDesc(&src_desc);
D3D11_TEXTURE2D_DESC map_desc;
map_desc.Width = width == 0 ? src_desc.Width : width;
map_desc.Height = height == 0 ? src_desc.Height : height;
map_desc.MipLevels = src_desc.MipLevels;
map_desc.ArraySize = src_desc.ArraySize;
map_desc.Format = src_desc.Format;
map_desc.SampleDesc = src_desc.SampleDesc;
map_desc.Usage = D3D11_USAGE_STAGING;
map_desc.BindFlags = 0;
map_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
map_desc.MiscFlags = 0;
auto d3dDevice =
GetDXGIInterfaceFromObject<ID3D11Device>(d3d11_direct_device_);
return d3dDevice->CreateTexture2D(&map_desc, nullptr,
d3d11_texture_mapped_.put());
}
void WgcSessionImpl::OnFrame(
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const& sender,
[[maybe_unused]] winrt::Windows::Foundation::IInspectable const& args) {
std::lock_guard locker(lock_);
auto is_new_size = false;
{
auto frame = sender.TryGetNextFrame();
auto frame_size = frame.ContentSize();
if (frame_size.Width != capture_frame_size_.Width ||
frame_size.Height != capture_frame_size_.Height) {
// The thing we have been capturing has changed size.
// We need to resize our swap chain first, then blit the pixels.
// After we do that, retire the frame and then recreate our frame pool.
is_new_size = true;
capture_frame_size_ = frame_size;
}
// copy to mapped texture
{
if (is_paused_) {
return;
}
auto frame_captured =
GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface());
if (!d3d11_texture_mapped_ || is_new_size)
CreateMappedTexture(frame_captured);
d3d11_device_context_->CopyResource(d3d11_texture_mapped_.get(),
frame_captured.get());
D3D11_MAPPED_SUBRESOURCE map_result;
HRESULT hr = d3d11_device_context_->Map(
d3d11_texture_mapped_.get(), 0, D3D11_MAP_READ,
0 /*coz we use CreateFreeThreaded, so we cant use flags
D3D11_MAP_FLAG_DO_NOT_WAIT*/
,
&map_result);
if (FAILED(hr)) {
OutputDebugStringW(
(L"map resource failed: " + std::to_wstring(hr)).c_str());
}
// copy data from map_result.pData
if (map_result.pData && observer_) {
observer_->OnFrame(
wgc_session_frame{static_cast<unsigned int>(frame_size.Width),
static_cast<unsigned int>(frame_size.Height),
map_result.RowPitch,
const_cast<const unsigned char*>(
(unsigned char*)map_result.pData)},
id_);
}
d3d11_device_context_->Unmap(d3d11_texture_mapped_.get(), 0);
}
}
if (is_new_size) {
capture_framepool_.Recreate(d3d11_direct_device_,
winrt::Windows::Graphics::DirectX::
DirectXPixelFormat::B8G8R8A8UIntNormalized,
2, capture_frame_size_);
}
}
void WgcSessionImpl::OnClosed(
winrt::Windows::Graphics::Capture::GraphicsCaptureItem const&,
winrt::Windows::Foundation::IInspectable const&) {
OutputDebugStringW(L"WgcSessionImpl::OnClosed");
}
int WgcSessionImpl::Initialize() {
if (is_initialized_) return 0;
if (!(d3d11_direct_device_ = CreateD3D11Device())) {
std::cout << "AE_D3D_CREATE_DEVICE_FAILED" << std::endl;
return 1;
}
try {
if (target_.is_window)
capture_item_ = CreateCaptureItemForWindow(target_.hwnd);
else
capture_item_ = CreateCaptureItemForMonitor(target_.hmonitor);
// Set up
auto d3d11_device =
GetDXGIInterfaceFromObject<ID3D11Device>(d3d11_direct_device_);
d3d11_device->GetImmediateContext(d3d11_device_context_.put());
} catch (winrt::hresult_error) {
LOG_ERROR("AE_WGC_CREATE_CAPTURER_FAILED");
return 86;
} catch (...) {
return 86;
}
is_initialized_ = true;
return 0;
}
void WgcSessionImpl::CleanUp() {
std::lock_guard locker(lock_);
auto expected = false;
if (cleaned_.compare_exchange_strong(expected, true)) {
capture_close_trigger_.revoke();
capture_framepool_trigger_.revoke();
if (capture_framepool_) capture_framepool_.Close();
if (capture_session_) capture_session_.Close();
capture_framepool_ = nullptr;
capture_session_ = nullptr;
capture_item_ = nullptr;
is_initialized_ = false;
}
}
LRESULT CALLBACK WindowProc(HWND window, UINT message, WPARAM w_param,
LPARAM l_param) {
return DefWindowProc(window, message, w_param, l_param);
}
// void WgcSessionImpl::message_func() {
// const std::wstring kClassName = L"am_fake_window";
// WNDCLASS wc = {};
// wc.style = CS_HREDRAW | CS_VREDRAW;
// wc.lpfnWndProc = DefWindowProc;
// wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
// wc.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW);
// wc.lpszClassName = kClassName.c_str();
// if (!::RegisterClassW(&wc)) return;
// hwnd_ = ::CreateWindowW(kClassName.c_str(), nullptr, WS_OVERLAPPEDWINDOW,
// 0,
// 0, 0, 0, nullptr, nullptr, nullptr, nullptr);
// MSG msg;
// while (is_running_) {
// while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
// if (!is_running_) break;
// TranslateMessage(&msg);
// DispatchMessage(&msg);
// }
// Sleep(10);
// }
// ::CloseWindow(hwnd_);
// ::DestroyWindow(hwnd_);
// }