mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-10-04 11:37:44 +00:00
343 lines
9.6 KiB
C++
343 lines
9.6 KiB
C++
#include "dxgi_session.h"
|
|
|
|
#include <dwmapi.h>
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <iostream>
|
|
|
|
namespace {
|
|
|
|
bool intersectRectChecked(const RECT& lhs, const RECT& rhs, RECT& result) {
|
|
return IntersectRect(&result, &lhs, &rhs) != FALSE;
|
|
}
|
|
|
|
int evenFloor(int value) {
|
|
return value > 1 ? (value & ~1) : value;
|
|
}
|
|
|
|
RECT getExtendedWindowBounds(HWND hwnd) {
|
|
RECT bounds{};
|
|
if (SUCCEEDED(DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &bounds, sizeof(bounds)))) {
|
|
return bounds;
|
|
}
|
|
|
|
GetWindowRect(hwnd, &bounds);
|
|
return bounds;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
DxgiSession::DxgiSession() {}
|
|
|
|
DxgiSession::~DxgiSession() {
|
|
stopCapture();
|
|
}
|
|
|
|
bool DxgiSession::findOutputForMonitor(HMONITOR monitor, ComPtr<IDXGIAdapter1>& adapter, ComPtr<IDXGIOutput1>& output) {
|
|
ComPtr<IDXGIFactory1> factory;
|
|
HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&factory));
|
|
if (FAILED(hr)) {
|
|
std::cerr << "ERROR: CreateDXGIFactory1 failed: 0x" << std::hex << hr << std::endl;
|
|
return false;
|
|
}
|
|
|
|
for (UINT adapterIndex = 0;; ++adapterIndex) {
|
|
ComPtr<IDXGIAdapter1> candidateAdapter;
|
|
hr = factory->EnumAdapters1(adapterIndex, &candidateAdapter);
|
|
if (hr == DXGI_ERROR_NOT_FOUND) {
|
|
break;
|
|
}
|
|
if (FAILED(hr)) {
|
|
continue;
|
|
}
|
|
|
|
for (UINT outputIndex = 0;; ++outputIndex) {
|
|
ComPtr<IDXGIOutput> candidateOutput;
|
|
hr = candidateAdapter->EnumOutputs(outputIndex, &candidateOutput);
|
|
if (hr == DXGI_ERROR_NOT_FOUND) {
|
|
break;
|
|
}
|
|
if (FAILED(hr)) {
|
|
continue;
|
|
}
|
|
|
|
DXGI_OUTPUT_DESC desc{};
|
|
if (FAILED(candidateOutput->GetDesc(&desc))) {
|
|
continue;
|
|
}
|
|
|
|
if (!desc.AttachedToDesktop || desc.Monitor != monitor) {
|
|
continue;
|
|
}
|
|
|
|
ComPtr<IDXGIOutput1> output1;
|
|
hr = candidateOutput.As(&output1);
|
|
if (FAILED(hr)) {
|
|
continue;
|
|
}
|
|
|
|
adapter = candidateAdapter;
|
|
output = output1;
|
|
outputDesc_ = desc;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool DxgiSession::createD3DDevice(IDXGIAdapter1* adapter) {
|
|
UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
|
|
D3D_FEATURE_LEVEL featureLevels[] = {
|
|
D3D_FEATURE_LEVEL_11_1,
|
|
D3D_FEATURE_LEVEL_11_0,
|
|
};
|
|
|
|
HRESULT hr = D3D11CreateDevice(
|
|
adapter,
|
|
D3D_DRIVER_TYPE_UNKNOWN,
|
|
nullptr,
|
|
creationFlags,
|
|
featureLevels,
|
|
ARRAYSIZE(featureLevels),
|
|
D3D11_SDK_VERSION,
|
|
&d3dDevice_,
|
|
nullptr,
|
|
&d3dContext_);
|
|
|
|
if (FAILED(hr)) {
|
|
std::cerr << "ERROR: D3D11CreateDevice failed: 0x" << std::hex << hr << std::endl;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DxgiSession::createDuplication(IDXGIOutput1* output) {
|
|
HRESULT hr = output->DuplicateOutput(d3dDevice_.Get(), &duplication_);
|
|
if (FAILED(hr)) {
|
|
std::cerr << "ERROR: DuplicateOutput failed: 0x" << std::hex << hr << std::endl;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DxgiSession::createCaptureTexture() {
|
|
if (captureWidth_ <= 0 || captureHeight_ <= 0) {
|
|
return false;
|
|
}
|
|
|
|
D3D11_TEXTURE2D_DESC desc{};
|
|
desc.Width = static_cast<UINT>(captureWidth_);
|
|
desc.Height = static_cast<UINT>(captureHeight_);
|
|
desc.MipLevels = 1;
|
|
desc.ArraySize = 1;
|
|
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
|
|
desc.SampleDesc.Count = 1;
|
|
desc.Usage = D3D11_USAGE_DEFAULT;
|
|
|
|
HRESULT hr = d3dDevice_->CreateTexture2D(&desc, nullptr, &captureTexture_);
|
|
if (FAILED(hr)) {
|
|
std::cerr << "ERROR: CreateTexture2D failed: 0x" << std::hex << hr << std::endl;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DxgiSession::initializeFullMonitorRect() {
|
|
const int width = outputDesc_.DesktopCoordinates.right - outputDesc_.DesktopCoordinates.left;
|
|
const int height = outputDesc_.DesktopCoordinates.bottom - outputDesc_.DesktopCoordinates.top;
|
|
|
|
captureWidth_ = evenFloor(width);
|
|
captureHeight_ = evenFloor(height);
|
|
if (captureWidth_ <= 0 || captureHeight_ <= 0) {
|
|
return false;
|
|
}
|
|
|
|
captureRect_.left = 0;
|
|
captureRect_.top = 0;
|
|
captureRect_.right = captureWidth_;
|
|
captureRect_.bottom = captureHeight_;
|
|
return true;
|
|
}
|
|
|
|
bool DxgiSession::updateWindowCaptureRect() {
|
|
if (!captureWindow_ || !windowHandle_) {
|
|
return false;
|
|
}
|
|
|
|
RECT windowRect = getExtendedWindowBounds(windowHandle_);
|
|
RECT monitorRect = outputDesc_.DesktopCoordinates;
|
|
RECT clippedRect{};
|
|
if (!intersectRectChecked(windowRect, monitorRect, clippedRect)) {
|
|
return false;
|
|
}
|
|
|
|
int left = clippedRect.left - monitorRect.left;
|
|
int top = clippedRect.top - monitorRect.top;
|
|
const int monitorWidth = monitorRect.right - monitorRect.left;
|
|
const int monitorHeight = monitorRect.bottom - monitorRect.top;
|
|
|
|
left = std::clamp(left, 0, std::max(0, monitorWidth - captureWidth_));
|
|
top = std::clamp(top, 0, std::max(0, monitorHeight - captureHeight_));
|
|
|
|
captureRect_.left = left;
|
|
captureRect_.top = top;
|
|
captureRect_.right = left + captureWidth_;
|
|
captureRect_.bottom = top + captureHeight_;
|
|
return true;
|
|
}
|
|
|
|
bool DxgiSession::initializeWindowRect(HWND hwnd) {
|
|
windowHandle_ = hwnd;
|
|
captureWindow_ = true;
|
|
|
|
RECT windowRect = getExtendedWindowBounds(hwnd);
|
|
RECT monitorRect = outputDesc_.DesktopCoordinates;
|
|
RECT clippedRect{};
|
|
if (!intersectRectChecked(windowRect, monitorRect, clippedRect)) {
|
|
return false;
|
|
}
|
|
|
|
captureWidth_ = evenFloor(clippedRect.right - clippedRect.left);
|
|
captureHeight_ = evenFloor(clippedRect.bottom - clippedRect.top);
|
|
if (captureWidth_ <= 0 || captureHeight_ <= 0) {
|
|
return false;
|
|
}
|
|
|
|
return updateWindowCaptureRect();
|
|
}
|
|
|
|
bool DxgiSession::initializeForMonitor(HMONITOR monitor, int fps) {
|
|
fps_ = fps;
|
|
frameIntervalHns_ = 10000000LL / fps_;
|
|
|
|
ComPtr<IDXGIAdapter1> adapter;
|
|
ComPtr<IDXGIOutput1> output;
|
|
if (!findOutputForMonitor(monitor, adapter, output)) {
|
|
std::cerr << "ERROR: Failed to find DXGI output for monitor" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
if (!createD3DDevice(adapter.Get())) {
|
|
return false;
|
|
}
|
|
|
|
if (!createDuplication(output.Get())) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DxgiSession::initialize(HMONITOR monitor, int fps) {
|
|
captureWindow_ = false;
|
|
windowHandle_ = nullptr;
|
|
|
|
if (!initializeForMonitor(monitor, fps)) {
|
|
return false;
|
|
}
|
|
|
|
return initializeFullMonitorRect() && createCaptureTexture();
|
|
}
|
|
|
|
bool DxgiSession::initialize(HWND hwnd, int fps) {
|
|
HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
|
|
if (!monitor) {
|
|
std::cerr << "ERROR: MonitorFromWindow failed" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
if (!initializeForMonitor(monitor, fps)) {
|
|
return false;
|
|
}
|
|
|
|
return initializeWindowRect(hwnd) && createCaptureTexture();
|
|
}
|
|
|
|
void DxgiSession::setFrameCallback(FrameCallback callback) {
|
|
frameCallback_ = std::move(callback);
|
|
}
|
|
|
|
bool DxgiSession::startCapture() {
|
|
if (!duplication_ || !captureTexture_) {
|
|
return false;
|
|
}
|
|
|
|
capturing_ = true;
|
|
lastFrameTimeHns_ = 0;
|
|
captureThread_ = std::thread(&DxgiSession::captureLoop, this);
|
|
return true;
|
|
}
|
|
|
|
void DxgiSession::stopCapture() {
|
|
capturing_ = false;
|
|
|
|
if (captureThread_.joinable()) {
|
|
captureThread_.join();
|
|
}
|
|
|
|
duplication_.Reset();
|
|
captureTexture_.Reset();
|
|
}
|
|
|
|
int64_t DxgiSession::nowHns() const {
|
|
return std::chrono::duration_cast<std::chrono::duration<int64_t, std::ratio<1, 10000000>>>(
|
|
std::chrono::steady_clock::now().time_since_epoch()).count();
|
|
}
|
|
|
|
void DxgiSession::captureLoop() {
|
|
while (capturing_) {
|
|
DXGI_OUTDUPL_FRAME_INFO frameInfo{};
|
|
ComPtr<IDXGIResource> desktopResource;
|
|
HRESULT hr = duplication_->AcquireNextFrame(100, &frameInfo, &desktopResource);
|
|
|
|
if (hr == DXGI_ERROR_WAIT_TIMEOUT) {
|
|
continue;
|
|
}
|
|
|
|
if (FAILED(hr)) {
|
|
if (hr != DXGI_ERROR_ACCESS_LOST) {
|
|
std::cerr << "ERROR: AcquireNextFrame failed: 0x" << std::hex << hr << std::endl;
|
|
}
|
|
break;
|
|
}
|
|
|
|
ComPtr<ID3D11Texture2D> sourceTexture;
|
|
hr = desktopResource.As(&sourceTexture);
|
|
if (SUCCEEDED(hr) && sourceTexture) {
|
|
if (!captureWindow_ || updateWindowCaptureRect()) {
|
|
const int64_t timestampHns = nowHns();
|
|
if (lastFrameTimeHns_ == 0 || (timestampHns - lastFrameTimeHns_) >= (frameIntervalHns_ * 7 / 10)) {
|
|
D3D11_BOX sourceBox{};
|
|
sourceBox.left = static_cast<UINT>(captureRect_.left);
|
|
sourceBox.top = static_cast<UINT>(captureRect_.top);
|
|
sourceBox.front = 0;
|
|
sourceBox.right = static_cast<UINT>(captureRect_.right);
|
|
sourceBox.bottom = static_cast<UINT>(captureRect_.bottom);
|
|
sourceBox.back = 1;
|
|
|
|
d3dContext_->CopySubresourceRegion(
|
|
captureTexture_.Get(),
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
sourceTexture.Get(),
|
|
0,
|
|
&sourceBox);
|
|
|
|
if (frameCallback_) {
|
|
lastFrameTimeHns_ = timestampHns;
|
|
frameCallback_(captureTexture_.Get(), timestampHns);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
duplication_->ReleaseFrame();
|
|
}
|
|
} |