Add GPUDevice.VideoOutputs with a list of attached monitors

This commit is contained in:
Wojtek Figat
2025-10-09 15:13:04 +02:00
parent 8cb67f017a
commit 2bf62cc54f
6 changed files with 143 additions and 19 deletions

View File

@@ -392,6 +392,8 @@ bool GPUDevice::Init()
LOG(Info, "Total graphics memory: {0}", Utilities::BytesToText(TotalGraphicsMemory));
if (!Limits.HasCompute)
LOG(Warning, "Compute Shaders are not supported");
for (const auto& videoOutput : VideoOutputs)
LOG(Info, "Video output '{0}' {1}x{2} {3} Hz", videoOutput.Name, videoOutput.Width, videoOutput.Height, videoOutput.RefreshRate);
Engine::RequestingExit.Bind<GPUDevice, &GPUDevice::OnRequestingExit>(this);
return false;
}
@@ -725,6 +727,7 @@ void GPUDevice::Draw()
void GPUDevice::Dispose()
{
RenderList::CleanupCache();
VideoOutputs.Resize(0);
VideoOutputModes.Resize(0);
}

View File

@@ -54,7 +54,40 @@ public:
};
/// <summary>
/// Describes a video output display mode.
/// Describes a video output display (monitor).
/// </summary>
API_STRUCT() struct VideoOutput
{
DECLARE_SCRIPTING_TYPE_NO_SPAWN(VideoOutputMode);
/// <summary>
/// The display name.
/// </summary>
API_FIELD() String Name;
/// <summary>
/// The native screen resolution width (in pixel).
/// </summary>
API_FIELD() uint32 Width = 0;
/// <summary>
/// The native screen resolution height (in pixel).
/// </summary>
API_FIELD() uint32 Height = 0;
/// <summary>
/// The maximum screen refresh rate (in hertz).
/// </summary>
API_FIELD() float RefreshRate = 0;
/// <summary>
/// Flag that indicates that monitor supports displaying High Dynamic Range colors.
/// </summary>
API_FIELD() bool HDR = false;
};
/// <summary>
/// Describes a video output display mode (monitor screen mode).
/// </summary>
API_STRUCT() struct VideoOutputMode
{
@@ -73,7 +106,12 @@ public:
/// <summary>
/// The screen refresh rate (in hertz).
/// </summary>
API_FIELD() uint32 RefreshRate;
API_FIELD() float RefreshRate;
/// <summary>
/// The index of the VideoOutput from the device monitors list.
/// </summary>
API_FIELD() int32 VideoOutputIndex;
};
/// <summary>
@@ -134,6 +172,11 @@ public:
/// </summary>
API_FIELD(ReadOnly) GPULimits Limits;
/// <summary>
/// The available video outputs (monitors).
/// </summary>
API_FIELD(ReadOnly) Array<VideoOutput> VideoOutputs;
/// <summary>
/// The available video output modes.
/// </summary>

View File

@@ -136,7 +136,11 @@ bool GPUBufferDX12::OnInit()
// Create resource
ID3D12Resource* resource;
#if PLATFORM_WINDOWS
D3D12_HEAP_FLAGS heapFlags = D3D12_HEAP_FLAG_CREATE_NOT_ZEROED;
#else
D3D12_HEAP_FLAGS heapFlags = D3D12_HEAP_FLAG_NONE;
#endif
D3D12_RESOURCE_STATES initialState = D3D12_RESOURCE_STATE_COMMON;
VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateCommittedResource(&heapProperties, heapFlags, &resourceDesc, initialState, nullptr, IID_PPV_ARGS(&resource)));

View File

@@ -307,6 +307,32 @@ bool GPUDeviceDX12::Init()
LOG(Info, "Hardware Version: {0}", hwVer);
updateFrameEvents();
// Setup display output
auto& videoOutput = VideoOutputs.AddOne();
videoOutput.Name = hwVer;
ComPtr<IDXGIDevice1> dxgiDevice;
VALIDATE_DIRECTX_CALL(_device->QueryInterface(IID_GRAPHICS_PPV_ARGS(&dxgiDevice)));
ComPtr<IDXGIAdapter> dxgiAdapter;
VALIDATE_DIRECTX_CALL(dxgiDevice->GetAdapter(dxgiAdapter.GetAddressOf()));
ComPtr<IDXGIOutput> dxgiOutput;
VALIDATE_DIRECTX_CALL(dxgiAdapter->EnumOutputs(0, dxgiOutput.GetAddressOf()));
DXGI_FORMAT backbufferFormat = RenderToolsDX::ToDxgiFormat(GPU_BACK_BUFFER_PIXEL_FORMAT);
UINT modesCount = 0;
VALIDATE_DIRECTX_CALL(dxgiOutput->GetDisplayModeList(backbufferFormat, 0, &modesCount, NULL));
Array<DXGIXBOX_MODE_DESC> modes;
modes.Resize((int32)modesCount);
VALIDATE_DIRECTX_CALL(dxgiOutput->GetDisplayModeListX(backbufferFormat, 0, &modesCount, modes.Get()));
for (const DXGIXBOX_MODE_DESC& mode : modes)
{
if (mode.Width > videoOutput.Width)
{
videoOutput.Width = mode.Width;
videoOutput.Height = mode.Height;
}
videoOutput.RefreshRate = Math::Max(videoOutput.RefreshRate, mode.RefreshRate.Numerator / (float)mode.RefreshRate.Denominator);
}
modes.Resize(0);
#if PLATFORM_GDK
GDKPlatform::Suspended.Bind<GPUDeviceDX12, &GPUDeviceDX12::OnSuspended>(this);
GDKPlatform::Resumed.Bind<GPUDeviceDX12, &GPUDeviceDX12::OnResumed>(this);
@@ -943,6 +969,7 @@ void GPUDeviceDX12::updateFrameEvents()
dxgiAdapter->GetDesc(&_adapter->Description);
ComPtr<IDXGIOutput> dxgiOutput;
VALIDATE_DIRECTX_CALL(dxgiAdapter->EnumOutputs(0, dxgiOutput.GetAddressOf()));
// TODO: support 120/40/30/24 fps
VALIDATE_DIRECTX_CALL(_device->SetFrameIntervalX(dxgiOutput.Get(), D3D12XBOX_FRAME_INTERVAL_60_HZ, DX12_BACK_BUFFER_COUNT - 1u, D3D12XBOX_FRAME_INTERVAL_FLAG_NONE));
VALIDATE_DIRECTX_CALL(_device->ScheduleFrameEventX(D3D12XBOX_FRAME_EVENT_ORIGIN, 0U, nullptr, D3D12XBOX_SCHEDULE_FRAME_EVENT_FLAG_NONE));
}

View File

@@ -159,7 +159,11 @@ bool GPUTextureDX12::OnInit()
initialState = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
// Create texture
#if PLATFORM_WINDOWS
D3D12_HEAP_FLAGS heapFlags = useRTV || useDSV ? D3D12_HEAP_FLAG_CREATE_NOT_ZEROED : D3D12_HEAP_FLAG_NONE;
#else
D3D12_HEAP_FLAGS heapFlags = D3D12_HEAP_FLAG_NONE;
#endif
auto result = device->CreateCommittedResource(&heapProperties, heapFlags, &resourceDesc, initialState, clearValuePtr, IID_PPV_ARGS(&resource));
LOG_DIRECTX_RESULT_WITH_RETURN(result, true);

View File

@@ -7,6 +7,7 @@
#include "GPUDeviceDX.h"
#include "Engine/Core/Types/DateTime.h"
#include "Engine/Core/Types/StringBuilder.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Graphics/GPUDevice.h"
#include "IncludeDirectXHeaders.h"
#include <winerror.h>
@@ -21,6 +22,9 @@ typedef void* LPCDLGTEMPLATE;
#pragma comment(lib, "SetupAPI.lib")
#endif
#define DISPLAY_DEVICE_ACTIVE 0x00000001
#define DISPLAY_DEVICE_MIRRORING_DRIVER 0x00000008
namespace Windows
{
typedef struct _devicemodeW
@@ -84,7 +88,18 @@ namespace Windows
DWORD dmPanningHeight;
} DEVMODEW, *PDEVMODEW, *NPDEVMODEW, *LPDEVMODEW;
typedef struct _DISPLAY_DEVICEW
{
DWORD cb;
WCHAR DeviceName[32];
WCHAR DeviceString[128];
DWORD StateFlags;
WCHAR DeviceID[128];
WCHAR DeviceKey[128];
} DISPLAY_DEVICEW, *PDISPLAY_DEVICEW, *LPDISPLAY_DEVICEW;
WIN_API BOOL WIN_API_CALLCONV EnumDisplaySettingsW(LPCWSTR lpszDeviceName, DWORD iModeNum, DEVMODEW* lpDevMode);
WIN_API BOOL WIN_API_CALLCONV EnumDisplayDevicesW(LPCWSTR lpDevice, DWORD iDevNum, PDISPLAY_DEVICEW lpDisplayDevice, DWORD dwFlags);
}
// @formatter:off
@@ -590,13 +605,14 @@ void GPUAdapterDX::SetDriverVersion(Version& ver)
DriverVersion = ver;
}
#if PLATFORM_WINDOWS
void GPUDeviceDX::UpdateOutputs(IDXGIAdapter* adapter)
{
#if PLATFORM_WINDOWS
// Collect output devices
PROFILE_CPU();
uint32 outputIdx = 0;
ComPtr<IDXGIOutput> output;
DXGI_FORMAT defaultBackbufferFormat = RenderToolsDX::ToDxgiFormat(GPU_BACK_BUFFER_PIXEL_FORMAT);
DXGI_FORMAT backbufferFormat = RenderToolsDX::ToDxgiFormat(GPU_BACK_BUFFER_PIXEL_FORMAT);
Array<DXGI_MODE_DESC> modeDesc;
while (adapter->EnumOutputs(outputIdx, &output) != DXGI_ERROR_NOT_FOUND)
{
@@ -606,7 +622,7 @@ void GPUDeviceDX::UpdateOutputs(IDXGIAdapter* adapter)
output->GetDesc(&outputDX11.Desc);
uint32 numModes = 0;
HRESULT hr = output->GetDisplayModeList(defaultBackbufferFormat, 0, &numModes, nullptr);
HRESULT hr = output->GetDisplayModeList(backbufferFormat, 0, &numModes, nullptr);
if (FAILED(hr))
{
LOG(Warning, "Error while enumerating adapter output video modes.");
@@ -614,7 +630,7 @@ void GPUDeviceDX::UpdateOutputs(IDXGIAdapter* adapter)
}
modeDesc.Resize(numModes, false);
hr = output->GetDisplayModeList(defaultBackbufferFormat, 0, &numModes, modeDesc.Get());
hr = output->GetDisplayModeList(backbufferFormat, 0, &numModes, modeDesc.Get());
if (FAILED(hr))
{
LOG(Warning, "Error while enumerating adapter output video modes.");
@@ -635,16 +651,10 @@ void GPUDeviceDX::UpdateOutputs(IDXGIAdapter* adapter)
break;
}
}
if (!foundVideoMode)
{
outputDX11.VideoModes.Add(mode);
// Collect only from the main monitor
if (Outputs.Count() == 1)
{
VideoOutputModes.Add({ mode.Width, mode.Height, (uint32)(mode.RefreshRate.Numerator / (float)mode.RefreshRate.Denominator) });
}
VideoOutputModes.Add({ mode.Width, mode.Height, mode.RefreshRate.Numerator / (float)mode.RefreshRate.Denominator, VideoOutputs.Count() });
}
}
@@ -659,24 +669,57 @@ void GPUDeviceDX::UpdateOutputs(IDXGIAdapter* adapter)
devMode.dmDriverExtra = 0;
Windows::EnumDisplaySettingsW(monitorInfo.szDevice, ((DWORD)-1), &devMode);
// Initialize display mode for Desktop
DXGI_MODE_DESC currentMode;
currentMode.Width = devMode.dmPelsWidth;
currentMode.Height = devMode.dmPelsHeight;
bool useDefaultRefreshRate = 1 == devMode.dmDisplayFrequency || 0 == devMode.dmDisplayFrequency;
currentMode.RefreshRate.Numerator = useDefaultRefreshRate ? 0 : devMode.dmDisplayFrequency;
currentMode.RefreshRate.Denominator = useDefaultRefreshRate ? 0 : 1;
currentMode.Format = defaultBackbufferFormat;
currentMode.Format = backbufferFormat;
currentMode.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
currentMode.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
if (output->FindClosestMatchingMode(&currentMode, &outputDX11.DesktopViewMode, nullptr) != S_OK)
outputDX11.DesktopViewMode = currentMode;
float refreshRate = outputDX11.DesktopViewMode.RefreshRate.Numerator / (float)outputDX11.DesktopViewMode.RefreshRate.Denominator;
LOG(Info, "Video output '{0}' {1}x{2} {3} Hz", outputDX11.Desc.DeviceName, devMode.dmPelsWidth, devMode.dmPelsHeight, refreshRate);
// Add video output
auto& videoOutput = VideoOutputs.AddOne();
videoOutput.Width = devMode.dmPelsWidth;
videoOutput.Height = devMode.dmPelsHeight;
videoOutput.RefreshRate = outputDX11.DesktopViewMode.RefreshRate.Numerator / (float)outputDX11.DesktopViewMode.RefreshRate.Denominator;
// Query display device name
Windows::DISPLAY_DEVICEW displayDevice;
displayDevice.cb = sizeof(displayDevice);
DWORD monitorIndex = 0;
while (Windows::EnumDisplayDevicesW(outputDX11.Desc.DeviceName, monitorIndex, &displayDevice, 0))
{
if (displayDevice.StateFlags & DISPLAY_DEVICE_ACTIVE && !(displayDevice.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER))
{
StringView id(displayDevice.DeviceID);
videoOutput.Name = id.Substring(8, id.Substring(9).Find(TEXT('\\')) + 1);
break;
}
monitorIndex++;
}
if (videoOutput.Name.IsEmpty())
videoOutput.Name = outputDX11.Desc.DeviceName;
#ifdef __IDXGIOutput6_INTERFACE_DEFINED__
// Query HDR support
ComPtr<IDXGIOutput6> output6;
if (SUCCEEDED(output->QueryInterface(IID_PPV_ARGS(&output6))))
{
DXGI_OUTPUT_DESC1 outputDesc;
output6->GetDesc1(&outputDesc);
videoOutput.HDR = outputDesc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
}
#endif
outputIdx++;
}
#endif
}
#endif
#endif