Skip to content
Merged
15 changes: 12 additions & 3 deletions src/platform/windows/display.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ using device_ctx_t = util::safe_ptr<ID3D11DeviceContext, Release<ID3D11
using adapter_t = util::safe_ptr<IDXGIAdapter1, Release<IDXGIAdapter1>>;
using output_t = util::safe_ptr<IDXGIOutput, Release<IDXGIOutput>>;
using output1_t = util::safe_ptr<IDXGIOutput1, Release<IDXGIOutput1>>;
using output5_t = util::safe_ptr<IDXGIOutput5, Release<IDXGIOutput5>>;
using dup_t = util::safe_ptr<IDXGIOutputDuplication, Release<IDXGIOutputDuplication>>;
using texture2d_t = util::safe_ptr<ID3D11Texture2D, Release<ID3D11Texture2D>>;
using texture1d_t = util::safe_ptr<ID3D11Texture1D, Release<ID3D11Texture1D>>;
Expand Down Expand Up @@ -118,7 +119,7 @@ class display_base_t : public display_t {
device_ctx_t device_ctx;
duplication_t dup;

DXGI_FORMAT format;
DXGI_FORMAT capture_format;
D3D_FEATURE_LEVEL feature_level;

typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS {
Expand All @@ -134,8 +135,13 @@ class display_base_t : public display_t {

protected:
int get_pixel_pitch() {
return (format == DXGI_FORMAT_R16G16B16A16_FLOAT) ? 8 : 4;
return (capture_format == DXGI_FORMAT_R16G16B16A16_FLOAT) ? 8 : 4;
}

const char *dxgi_format_to_string(DXGI_FORMAT format);

virtual int complete_img(img_t *img, bool dummy) = 0;
virtual std::vector<DXGI_FORMAT> get_supported_sdr_capture_formats() = 0;
};

class display_ram_t : public display_base_t {
Expand All @@ -146,6 +152,8 @@ class display_ram_t : public display_base_t {

std::shared_ptr<img_t> alloc_img() override;
int dummy_img(img_t *img) override;
int complete_img(img_t *img, bool dummy) override;
std::vector<DXGI_FORMAT> get_supported_sdr_capture_formats() override;

int init(int framerate, const std::string &display_name);

Expand All @@ -161,6 +169,8 @@ class display_vram_t : public display_base_t, public std::enable_shared_from_thi

std::shared_ptr<img_t> alloc_img() override;
int dummy_img(img_t *img_base) override;
int complete_img(img_t *img_base, bool dummy) override;
std::vector<DXGI_FORMAT> get_supported_sdr_capture_formats() override;

int init(int framerate, const std::string &display_name);

Expand All @@ -174,7 +184,6 @@ class display_vram_t : public display_base_t, public std::enable_shared_from_thi
ps_t scene_ps;
vs_t scene_vs;

texture2d_t src;
gpu_cursor_t cursor;
};
} // namespace platf::dxgi
Expand Down
74 changes: 51 additions & 23 deletions src/platform/windows/display_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <cmath>
#include <codecvt>
#include <initguid.h>

#include "display.h"
#include "misc.h"
Expand Down Expand Up @@ -79,22 +80,21 @@ duplication_t::~duplication_t() {
}

int display_base_t::init(int framerate, const std::string &display_name) {
/* Uncomment when use of IDXGIOutput5 is implemented
std::once_flag windows_cpp_once_flag;

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");
auto f = (User32_SetProcessDpiAwarenessContext)GetProcAddress(user32, "SetProcessDpiAwarenessContext");
if(f) {
f(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
}

FreeLibrary(user32);
});
*/

// Ensure we can duplicate the current display
syncThreadDesktop();
Expand Down Expand Up @@ -291,44 +291,68 @@ int display_base_t::init(int framerate, const std::string &display_name) {
}

//FIXME: Duplicate output on RX580 in combination with DOOM (2016) --> BSOD
//TODO: Use IDXGIOutput5 for improved performance
{
dxgi::output1_t output1 {};
dxgi::output5_t output5 {};

// IDXGIOutput5 is optional, but can provide improved performance and wide color support
status = output->QueryInterface(IID_IDXGIOutput5, (void **)&output5);
if(FAILED(status)) {
BOOST_LOG(warning) << "Failed to query IDXGIOutput5 from the output"sv;
}

status = output->QueryInterface(IID_IDXGIOutput1, (void **)&output1);
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) {
status = output1->DuplicateOutput((IUnknown *)device.get(), &dup.dup);
if(SUCCEEDED(status)) {
break;
if(output5) {
// Ask the display implementation which formats it supports
auto supported_formats = get_supported_sdr_capture_formats();
if(supported_formats.empty()) {
BOOST_LOG(warning) << "No compatible capture formats for this encoder"sv;
return -1;
}

// We try this twice, in case we still get an error on reinitialization
for(int x = 0; x < 2; ++x) {
status = output5->DuplicateOutput1((IUnknown *)device.get(), 0, supported_formats.size(), supported_formats.data(), &dup.dup);
if(SUCCEEDED(status)) {
break;
}
std::this_thread::sleep_for(200ms);
}

if(FAILED(status)) {
BOOST_LOG(warning) << "DuplicateOutput1 Failed [0x"sv << util::hex(status).to_string_view() << ']';
}
std::this_thread::sleep_for(200ms);
}

if(FAILED(status)) {
BOOST_LOG(error) << "DuplicateOutput Failed [0x"sv << util::hex(status).to_string_view() << ']';
return -1;
if(!output5 || FAILED(status)) {
for(int x = 0; x < 2; ++x) {
status = output1->DuplicateOutput((IUnknown *)device.get(), &dup.dup);
if(SUCCEEDED(status)) {
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);

BOOST_LOG(info) << "Desktop resolution ["sv << dup_desc.ModeDesc.Width << 'x' << dup_desc.ModeDesc.Height << ']';
BOOST_LOG(info) << "Desktop format ["sv << format_str[dup_desc.ModeDesc.Format] << ']';
BOOST_LOG(info) << "Desktop format ["sv << dxgi_format_to_string(dup_desc.ModeDesc.Format) << ']';

// For IDXGIOutput1::DuplicateOutput(), the format of the desktop image we receive from AcquireNextFrame() is
// converted to DXGI_FORMAT_B8G8R8A8_UNORM, even if the current mode (as returned in dup_desc) differs.
// See https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/desktop-dup-api for details.
//
// TODO: When we implement IDXGIOutput5, we will need to actually call AcquireNextFrame(), then call GetDesc()
// on the the texture we receive to determine which format in our list that it has decided to use.
format = DXGI_FORMAT_B8G8R8A8_UNORM;
BOOST_LOG(info) << "Capture format ["sv << format_str[format] << ']';
// Capture format will be determined from the first call to AcquireNextFrame()
capture_format = DXGI_FORMAT_UNKNOWN;

return 0;
}
Expand Down Expand Up @@ -458,6 +482,10 @@ const char *format_str[] = {
"DXGI_FORMAT_V408"
};

const char *display_base_t::dxgi_format_to_string(DXGI_FORMAT format) {
return format_str[format];
}

} // namespace platf::dxgi

namespace platf {
Expand Down
149 changes: 107 additions & 42 deletions src/platform/windows/display_ram.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,14 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise
return capture_status;
}

const bool mouse_update_flag = frame_info.LastMouseUpdateTime.QuadPart != 0 || frame_info.PointerShapeBufferSize > 0;
const bool frame_update_flag = frame_info.AccumulatedFrames != 0 || frame_info.LastPresentTime.QuadPart != 0;
const bool update_flag = mouse_update_flag || frame_update_flag;

if(!update_flag) {
return capture_e::timeout;
}

if(frame_info.PointerShapeBufferSize > 0) {
auto &img_data = cursor.img_data;

Expand All @@ -230,8 +238,7 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise
cursor.visible = frame_info.PointerPosition.Visible;
}

// If frame has been updated
if(frame_info.LastPresentTime.QuadPart != 0) {
if(frame_update_flag) {
{
texture2d_t src {};
status = res->QueryInterface(IID_ID3D11Texture2D, (void **)&src);
Expand All @@ -241,35 +248,82 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise
return capture_e::error;
}

D3D11_TEXTURE2D_DESC desc;
src->GetDesc(&desc);

// If we don't know the capture format yet, grab it from this texture and create the staging texture
if(capture_format == DXGI_FORMAT_UNKNOWN) {
capture_format = desc.Format;
BOOST_LOG(info) << "Capture format ["sv << dxgi_format_to_string(capture_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 = capture_format;
t.CPUAccessFlags = D3D11_CPU_ACCESS_READ;

auto status = device->CreateTexture2D(&t, nullptr, &texture);

if(FAILED(status)) {
BOOST_LOG(error) << "Failed to create staging texture [0x"sv << util::hex(status).to_string_view() << ']';
return capture_e::error;
}
}

// It's possible for our display enumeration to race with mode changes and result in
// mismatched image pool and desktop texture sizes. If this happens, just reinit again.
if(desc.Width != width || desc.Height != height) {
BOOST_LOG(info) << "Capture size changed ["sv << width << 'x' << height << " -> "sv << desc.Width << 'x' << desc.Height << ']';
return capture_e::reinit;
}

// It's also possible for the capture format to change on the fly. If that happens,
// reinitialize capture to try format detection again and create new images.
if(capture_format != desc.Format) {
BOOST_LOG(info) << "Capture format changed ["sv << dxgi_format_to_string(capture_format) << " -> "sv << dxgi_format_to_string(desc.Format) << ']';
return capture_e::reinit;
}

//Copy from GPU to CPU
device_ctx->CopyResource(texture.get(), src.get());
}
}

if(img_info.pData) {
device_ctx->Unmap(texture.get(), 0);
img_info.pData = nullptr;
}
// If we don't know the final capture format yet, encode a dummy image
if(capture_format == DXGI_FORMAT_UNKNOWN) {
BOOST_LOG(debug) << "Capture format is still unknown. Encoding a blank image"sv;

if(dummy_img(img)) {
return capture_e::error;
}
}
else {
// Map the staging texture for CPU access (making it inaccessible for the GPU)
status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info);
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to map texture [0x"sv << util::hex(status).to_string_view() << ']';

return capture_e::error;
}
}

const bool mouse_update =
(frame_info.LastMouseUpdateTime.QuadPart || frame_info.PointerShapeBufferSize > 0) &&
(cursor_visible && cursor.visible);
// Now that we know the capture format, we can finish creating the image
if(complete_img(img, false)) {
device_ctx->Unmap(texture.get(), 0);
img_info.pData = nullptr;
return capture_e::error;
}

const bool update_flag = frame_info.LastPresentTime.QuadPart != 0 || mouse_update;
std::copy_n((std::uint8_t *)img_info.pData, height * img_info.RowPitch, (std::uint8_t *)img->data);

if(!update_flag) {
return capture_e::timeout;
// Unmap the staging texture to allow GPU access again
device_ctx->Unmap(texture.get(), 0);
img_info.pData = nullptr;
}

std::copy_n((std::uint8_t *)img_info.pData, height * img_info.RowPitch, (std::uint8_t *)img->data);

if(cursor_visible && cursor.visible) {
blend_cursor(cursor, *img);
}
Expand All @@ -280,45 +334,56 @@ capture_e display_ram_t::snapshot(::platf::img_t *img_base, std::chrono::millise
std::shared_ptr<platf::img_t> display_ram_t::alloc_img() {
auto img = std::make_shared<img_t>();

img->pixel_pitch = get_pixel_pitch();
img->row_pitch = img_info.RowPitch;
img->width = width;
img->height = height;
img->data = new std::uint8_t[img->row_pitch * height];
// Initialize fields that are format-independent
img->width = width;
img->height = height;

return img;
}

int display_ram_t::dummy_img(platf::img_t *img) {
return 0;
}

int display_ram_t::init(int framerate, const std::string &display_name) {
if(display_base_t::init(framerate, display_name)) {
int display_ram_t::complete_img(platf::img_t *img, bool dummy) {
// If this is not a dummy image, we must know the format by now
if(!dummy && capture_format == DXGI_FORMAT_UNKNOWN) {
BOOST_LOG(error) << "display_ram_t::complete_img() called with unknown capture format!";
return -1;
}

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;
img->pixel_pitch = get_pixel_pitch();

if(dummy && !img->row_pitch) {
// Assume our dummy image will have no padding
img->row_pitch = img->pixel_pitch * img->width;
}

// Reallocate the image buffer if the pitch changes
if(!dummy && img->row_pitch != img_info.RowPitch) {
img->row_pitch = img_info.RowPitch;
delete img->data;
img->data = nullptr;
}

auto status = device->CreateTexture2D(&t, nullptr, &texture);
if(!img->data) {
img->data = new std::uint8_t[img->row_pitch * height];
}

if(FAILED(status)) {
BOOST_LOG(error) << "Failed to create texture [0x"sv << util::hex(status).to_string_view() << ']';
return 0;
}

int display_ram_t::dummy_img(platf::img_t *img) {
if(complete_img(img, true)) {
return -1;
}

// map the texture simply to get the pitch and stride
status = device_ctx->Map(texture.get(), 0, D3D11_MAP_READ, 0, &img_info);
if(FAILED(status)) {
BOOST_LOG(error) << "Failed to map the texture [0x"sv << util::hex(status).to_string_view() << ']';
std::fill_n((std::uint8_t *)img->data, height * img->row_pitch, 0);
return 0;
}

std::vector<DXGI_FORMAT> display_ram_t::get_supported_sdr_capture_formats() {
return std::vector { DXGI_FORMAT_B8G8R8A8_UNORM };
}

int display_ram_t::init(int framerate, const std::string &display_name) {
if(display_base_t::init(framerate, display_name)) {
return -1;
}

Expand Down
Loading