Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(capture): add AMD Display Capture for AFMF #3171

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

# VSCode IDE
.vscode/
.vs/

# build directories
build/
Expand Down
1 change: 1 addition & 0 deletions cmake/compile_definitions/windows.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ set(PLATFORM_TARGET_FILES
"${CMAKE_SOURCE_DIR}/src/platform/windows/display_vram.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/windows/display_ram.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/windows/display_wgc.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/windows/display_amd.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/windows/audio.cpp"
"${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/src/ViGEmClient.cpp"
"${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/include/ViGEm/Client.h"
Expand Down
57 changes: 57 additions & 0 deletions src/platform/windows/display.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
*/
#pragma once

#include <AMF/core/CurrentTime.h>
#include <AMF/core/Factory.h>
#include <d3d11.h>
#include <d3d11_4.h>
#include <d3dcommon.h>
Expand Down Expand Up @@ -31,6 +33,17 @@
dxgi->Release();
}

/**
* Windows DLL loader function helper for AMD Display Capture
* @param item library dll
*/
inline
void
FreeLibraryHelper(void *item) {
FreeLibrary((HMODULE) item);

Check warning on line 43 in src/platform/windows/display.h

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display.h#L42-L43

Added lines #L42 - L43 were not covered by tests
}
radugrecu97 marked this conversation as resolved.
Show resolved Hide resolved

using hmodule_t = util::safe_ptr<void, FreeLibraryHelper>;

Check warning on line 46 in src/platform/windows/display.h

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display.h#L46

Added line #L46 was not covered by tests
using factory1_t = util::safe_ptr<IDXGIFactory1, Release<IDXGIFactory1>>;
using dxgi_t = util::safe_ptr<IDXGIDevice, Release<IDXGIDevice>>;
using dxgi1_t = util::safe_ptr<IDXGIDevice1, Release<IDXGIDevice1>>;
Expand Down Expand Up @@ -177,6 +190,8 @@
int height_before_rotation;

int client_frame_rate;
int adapter_index;
int output_index;

DXGI_FORMAT capture_format;
D3D_FEATURE_LEVEL feature_level;
Expand Down Expand Up @@ -429,4 +444,46 @@
capture_e
release_snapshot() override;
};

class amd_capture_t {

public:
amd_capture_t();
~amd_capture_t();

int
init(display_base_t *display, const ::video::config_t &config, int output_index);
capture_e
next_frame(std::chrono::milliseconds timeout, amf::AMFData** out);
capture_e
release_frame();

hmodule_t amfrt_lib;
amf_uint64 amf_version;
amf::AMFFactory *amf_factory;

amf::AMFContextPtr context;
amf::AMFComponentPtr captureComp;
amf::AMFSurfacePtr capturedSurface;
amf_int64 capture_format;
AMFSize resolution;
};


/**
* Display backend that uses AMD Display Capture with a hardware encoder.
* Main purpose is to capture AMD Fluid Motion Frames (AFMF)
*/
class display_amd_vram_t: public display_vram_t {
amd_capture_t dup;

public:
int
init(const ::video::config_t &config, const std::string &display_name);
capture_e
snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor_visible) override;
capture_e
release_snapshot() override;
};

} // namespace platf::dxgi
183 changes: 183 additions & 0 deletions src/platform/windows/display_amd.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/**
* @file src/platform/windows/display_amd.cpp
* @brief Display capture implementation using AMD Direct Capture
*/

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavutil/hwcontext_d3d11va.h>
}

#include "display.h"
#include "misc.h"
#include "src/config.h"
#include "src/main.h"
#include "src/video.h"

#include <AMF/components/DisplayCapture.h>
#include <AMF/components/VideoConverter.h>
#include <AMF/core/Trace.h>

namespace platf {
using namespace std::literals;
}

namespace platf::dxgi {
amd_capture_t::amd_capture_t() {

Check warning on line 26 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L26

Added line #L26 was not covered by tests
}

amd_capture_t::~amd_capture_t() {
AMF_RESULT result;

Check warning on line 30 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L29-L30

Added lines #L29 - L30 were not covered by tests

// Before terminating the Display Capture component, we need to drain the remaining frames
result = captureComp->Drain();

Check warning on line 33 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L33

Added line #L33 was not covered by tests
if (result == AMF_OK) {
do {
result = captureComp->QueryOutput((amf::AMFData**) &capturedSurface);
Sleep(1);

Check warning on line 37 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L35-L37

Added lines #L35 - L37 were not covered by tests
} while (result != AMF_EOF);
}
captureComp->Terminate();

Check warning on line 40 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L40

Added line #L40 was not covered by tests

context->Terminate();
captureComp = nullptr;
context = nullptr;
capturedSurface = nullptr;

Check warning on line 45 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L42-L45

Added lines #L42 - L45 were not covered by tests
}

capture_e
amd_capture_t::release_frame() {

Check warning on line 49 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L49

Added line #L49 was not covered by tests
if (capturedSurface != nullptr)
{
capturedSurface = nullptr;

Check warning on line 52 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L52

Added line #L52 was not covered by tests
}

return capture_e::ok;

Check warning on line 55 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L55

Added line #L55 was not covered by tests
}

/**
* @brief Get the next frame from the producer thread.
* If not available, the capture thread blocks until one is, or the wait times out.
* @param timeout how long to wait for the next frame
* @param out pointer to AMFSurfacePtr
*/
capture_e
amd_capture_t::next_frame(std::chrono::milliseconds timeout, amf::AMFData** out) {
release_frame();

Check warning on line 66 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L65-L66

Added lines #L65 - L66 were not covered by tests

AMF_RESULT result;
auto capture_start = std::chrono::steady_clock::now();
do {
result = captureComp->QueryOutput(out);

Check warning on line 71 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L68-L71

Added lines #L68 - L71 were not covered by tests
if (result == AMF_REPEAT) {
if (std::chrono::steady_clock::now() - capture_start >= timeout) {
return platf::capture_e::timeout;
}
Sleep(1);

Check warning on line 76 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L76

Added line #L76 was not covered by tests
}
} while (result == AMF_REPEAT);

if (result != AMF_OK) {
return capture_e::timeout;
}
return capture_e::ok;
}


int
amd_capture_t::init(display_base_t *display, const ::video::config_t &config, int output_index) {

Check warning on line 88 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L88

Added line #L88 was not covered by tests
// We have to load AMF before calling the base init() because we will need it loaded
// when our test_capture() function is called.
amfrt_lib.reset(LoadLibraryW(AMF_DLL_NAME));
if (!amfrt_lib) {
// Probably not an AMD GPU system
return -1;
}

auto fn_AMFQueryVersion = (AMFQueryVersion_Fn) GetProcAddress((HMODULE) amfrt_lib.get(), AMF_QUERY_VERSION_FUNCTION_NAME);
auto fn_AMFInit = (AMFInit_Fn) GetProcAddress((HMODULE) amfrt_lib.get(), AMF_INIT_FUNCTION_NAME);

Check warning on line 98 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L97-L98

Added lines #L97 - L98 were not covered by tests

if (!fn_AMFQueryVersion || !fn_AMFInit) {
BOOST_LOG(error) << "Missing required AMF function!"sv;
return -1;

Check warning on line 102 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L102

Added line #L102 was not covered by tests
}

auto result = fn_AMFQueryVersion(&amf_version);

Check warning on line 105 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L105

Added line #L105 was not covered by tests
if (result != AMF_OK) {
BOOST_LOG(error) << "AMFQueryVersion() failed: "sv << result;
return -1;

Check warning on line 108 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L108

Added line #L108 was not covered by tests
}

// We don't support anything older than AMF 1.4.30. We'll gracefully fall back to DDAPI.
if (amf_version < AMF_MAKE_FULL_VERSION(1, 4, 30, 0)) {
BOOST_LOG(warning) << "AMD Direct Capture is not supported on AMF version"sv
<< AMF_GET_MAJOR_VERSION(amf_version) << '.'
<< AMF_GET_MINOR_VERSION(amf_version) << '.'
<< AMF_GET_SUBMINOR_VERSION(amf_version) << '.'
<< AMF_GET_BUILD_VERSION(amf_version);
BOOST_LOG(warning) << "Consider updating your AMD graphics driver for better capture performance!"sv;
return -1;

Check warning on line 119 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L119

Added line #L119 was not covered by tests
}

// Initialize AMF library
result = fn_AMFInit(AMF_FULL_VERSION, &amf_factory);

Check warning on line 123 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L123

Added line #L123 was not covered by tests
if (result != AMF_OK) {
BOOST_LOG(error) << "AMFInit() failed: "sv << result;
return -1;

Check warning on line 126 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L126

Added line #L126 was not covered by tests
}

DXGI_ADAPTER_DESC adapter_desc;
display->adapter->GetDesc(&adapter_desc);

Check warning on line 130 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L129-L130

Added lines #L129 - L130 were not covered by tests

// Bail if this is not an AMD GPU
if (adapter_desc.VendorId != 0x1002) {
return -1;
}

// Create the capture context
result = amf_factory->CreateContext(&context);

Check warning on line 138 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L138

Added line #L138 was not covered by tests

if (result != AMF_OK) {
BOOST_LOG(error) << "CreateContext() failed: "sv << result;
return -1;

Check warning on line 142 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L142

Added line #L142 was not covered by tests
}

// Associate the context with our ID3D11Device. This will enable multithread protection on the device.
result = context->InitDX11(display->device.get());

Check warning on line 146 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L146

Added line #L146 was not covered by tests
if (result != AMF_OK) {
BOOST_LOG(error) << "InitDX11() failed: "sv << result;
return -1;

Check warning on line 149 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L149

Added line #L149 was not covered by tests
}

display->capture_format = DXGI_FORMAT_UNKNOWN;

Check warning on line 152 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L152

Added line #L152 was not covered by tests

// Create the DisplayCapture component
result = amf_factory->CreateComponent(context, AMFDisplayCapture, &(captureComp));

Check warning on line 155 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L155

Added line #L155 was not covered by tests
if (result != AMF_OK) {
BOOST_LOG(error) << "CreateComponent(AMFDisplayCapture) failed: "sv << result;
return -1;

Check warning on line 158 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L158

Added line #L158 was not covered by tests
}

// Set parameters for non-blocking capture
captureComp->SetProperty(AMF_DISPLAYCAPTURE_MONITOR_INDEX, output_index);
captureComp->SetProperty(AMF_DISPLAYCAPTURE_FRAMERATE, AMFConstructRate(config.framerate, 1));
captureComp->SetProperty(AMF_DISPLAYCAPTURE_MODE, AMF_DISPLAYCAPTURE_MODE_WAIT_FOR_PRESENT);
captureComp->SetProperty(AMF_DISPLAYCAPTURE_DUPLICATEOUTPUT, true);

Check warning on line 165 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L162-L165

Added lines #L162 - L165 were not covered by tests

// Initialize capture
result = captureComp->Init(amf::AMF_SURFACE_UNKNOWN, 0, 0);

Check warning on line 168 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L168

Added line #L168 was not covered by tests
if (result != AMF_OK) {
BOOST_LOG(error) << "DisplayCapture::Init() failed: "sv << result;
return -1;

Check warning on line 171 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L171

Added line #L171 was not covered by tests
}

captureComp->GetProperty(AMF_DISPLAYCAPTURE_FORMAT, &(capture_format));
captureComp->GetProperty(AMF_DISPLAYCAPTURE_RESOLUTION, &(resolution));

Check warning on line 175 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L174-L175

Added lines #L174 - L175 were not covered by tests
BOOST_LOG(info) << "Desktop resolution ["sv << resolution.width << 'x' << resolution.height << ']';

BOOST_LOG(info) << "Using AMD Direct Capture API for display capture"sv;

return 0;

Check warning on line 180 in src/platform/windows/display_amd.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_amd.cpp#L180

Added line #L180 was not covered by tests
}

} // namespace platf::dxgi
12 changes: 12 additions & 0 deletions src/platform/windows/display_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,7 @@
}

if (desc.AttachedToDesktop && test_dxgi_duplication(adapter_tmp, output_tmp, false)) {
output_index = y;

Check warning on line 568 in src/platform/windows/display_base.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_base.cpp#L568

Added line #L568 was not covered by tests
output = std::move(output_tmp);

offset_x = desc.DesktopCoordinates.left;
Expand Down Expand Up @@ -593,6 +594,7 @@
}

if (output) {
adapter_index = x;

Check warning on line 597 in src/platform/windows/display_base.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_base.cpp#L597

Added line #L597 was not covered by tests
adapter = std::move(adapter_tmp);
break;
}
Expand Down Expand Up @@ -1052,6 +1054,16 @@
*/
std::shared_ptr<display_t>
display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
if (config::video.capture == "amd" || config::video.capture.empty()) {
if (hwdevice_type == mem_type_e::dxgi) {
auto disp = std::make_shared<dxgi::display_amd_vram_t>();

Check warning on line 1059 in src/platform/windows/display_base.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_base.cpp#L1059

Added line #L1059 was not covered by tests

if (!disp->init(config, display_name)) {
return disp;

Check warning on line 1062 in src/platform/windows/display_base.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/windows/display_base.cpp#L1062

Added line #L1062 was not covered by tests
}
}
}

if (config::video.capture == "ddx" || config::video.capture.empty()) {
if (hwdevice_type == mem_type_e::dxgi) {
auto disp = std::make_shared<dxgi::display_ddup_vram_t>();
Expand Down
Loading
Loading