rhi: Enhance the hdr info struct and add a manual test

...while sharing the related code between the d3d backends.

The isHardCodedDefaults flag is not used in practice and is
now removed. Add the luminance behavior and SDR white level
(for Windows) to help creating portable 2D/3D renderers that
composite SDR and HDR content. (sadly the Windows HDR and Apple
EDR behavior is different, as usual)

The new test application is expected to run with the command-line
argument "scrgb" or "sdr". It allows seeing SDR content correction
(on Windows) in action, and has some simple HDR 3D content with
a basic, optional tonemapper. Also shows the platform-dependent
HDR-related screen info. With some helpful tooltips even.

Additionally, it needs a .hdr file after the 'file' argument.
The usual -d, -D, -v, etc. arguments apply to select the 3D API.

For example, to run with D3D12 in HDR mode:

hdr -D scrgb file image.hdr

The same in non-HDR mode:

hdr -D sdr file image.hdr

Change-Id: I7fdfc7054cc0352bc99398fc1c7b1e2f0874421f
Reviewed-by: Christian Strømme <christian.stromme@qt.io>
This commit is contained in:
Laszlo Agocs 2023-08-01 14:40:41 +02:00
parent 78bc1a9a25
commit 547b9da7ad
23 changed files with 883 additions and 223 deletions

View File

@ -406,7 +406,8 @@ qt_internal_extend_target(Gui CONDITION WIN32
platform/windows/qwindowsguieventdispatcher.cpp platform/windows/qwindowsguieventdispatcher_p.h
platform/windows/qwindowsmimeconverter.h platform/windows/qwindowsmimeconverter.cpp
platform/windows/qwindowsnativeinterface.cpp
rhi/qrhid3d11.cpp rhi/qrhid3d11_p.h rhi/qrhid3dhelpers_p.h
rhi/qrhid3d11.cpp rhi/qrhid3d11_p.h
rhi/qrhid3dhelpers.cpp rhi/qrhid3dhelpers_p.h
rhi/vs_test_p.h
rhi/qrhid3d12.cpp rhi/qrhid3d12_p.h
rhi/cs_mipmap_p.h

View File

@ -7361,11 +7361,12 @@ QRhiRenderTarget *QRhiSwapChain::currentFrameRenderTarget(StereoTargetBuffer tar
\brief Describes the high dynamic range related information of the
swapchain's associated output.
To perform tonemapping, one often needs to know the maximum luminance of
the display the swapchain's window is associated with. While this is often
made user-configurable, it can be highly useful to set defaults based on
the values reported by the display itself, thus providing a decent starting
point.
To perform HDR-compatible tonemapping, where the target range is not [0,1],
one often needs to know the maximum luminance of the display the
swapchain's window is associated with. While this is often made
user-configurable (think brightness, gamma and similar settings in games),
it can be highly useful to set defaults based on the values reported by the
display itself, thus providing a decent starting point.
There are some problems however: the information is exposed in different
forms on different platforms, whereas with cross-platform graphics APIs
@ -7373,11 +7374,6 @@ QRhiRenderTarget *QRhiSwapChain::currentFrameRenderTarget(StereoTargetBuffer tar
information is not in the scope of the API (and may rather be retrievable
via other platform-specific means, if any).
The struct returned from QRhiSwapChain::hdrInfo() contains either some
hard-coded defaults, indicated by the \c isHardCodedDefaults field, or real
values received from an API such as DXGI (IDXGIOutput6) or Cocoa
(NSScreen). The default is 1000 nits for maximum luminance.
With Metal on macOS/iOS, there is no luminance values exposed in the
platform APIs. Instead, the maximum color component value, that would be
1.0 in a non-HDR setup, is provided. The \c limitsType field indicates what
@ -7386,8 +7382,21 @@ QRhiRenderTarget *QRhiSwapChain::currentFrameRenderTarget(StereoTargetBuffer tar
fit.
With an API like Vulkan, where there is no way to get such information, the
values are always the built-in defaults and \c isHardCodedDefaults is
always true.
values are always the built-in defaults.
Therefore, the struct returned from QRhiSwapChain::hdrInfo() contains
either some hard-coded defaults or real values received from an API such as
DXGI (IDXGIOutput6) or Cocoa (NSScreen). When no platform queries are
available (or needs using platform facilities out of scope for QRhi), the
hard-coded defaults are a maximum luminance of 1000 nits and an SDR white
level of 200.
The struct also exposes the presumed luminance behavior of the platform and
its compositor, to indicate what a color component value of 1.0 is treated
as in a HDR color buffer. In some cases it will be necessary to perform
color correction of non-HDR content composited with HDR content. To enable
this, the SDR white level is queried from the system on some platforms
(Windows) and exposed here.
\note This is a RHI API with limited compatibility guarantees, see \l QRhi
for details.
@ -7406,16 +7415,20 @@ QRhiRenderTarget *QRhiSwapChain::currentFrameRenderTarget(StereoTargetBuffer tar
*/
/*!
\variable QRhiSwapChainHdrInfo::isHardCodedDefaults
\enum QRhiSwapChainHdrInfo::LuminanceBehavior
Set to true when the data in the QRhiSwapChainHdrInfo consists entirely of
the hard-coded default values, for example because there is no way to query
the relevant information with a given graphics API or platform. (or because
querying it can be achieved only by means, e.g. platform APIs in some other
area, that are out of scope for the QRhi layer of the Qt graphics stack to
handle)
\value SceneReferred Indicates that the color value of 1.0 is interpreted
as 80 nits. This is the behavior of HDR-enabled windows with the Windows
compositor. See
\l{https://learn.microsoft.com/en-us/windows/win32/direct3darticles/high-dynamic-range}{this
page} for more information on HDR on Windows.
\sa QRhiSwapChain::hdrInfo()
\value DisplayReferred Indicates that the color value of 1.0 is interpreted
as the value of the SDR white. (which can be e.g. 200 nits, but will vary
depending on screen brightness) This is the behavior of HDR-enabled windows
on Apple platforms. See
\l{https://developer.apple.com/documentation/metal/hdr_content/displaying_hdr_content_in_a_metal_layer}{this
page} for more information on Apple's EDR system.
*/
/*!
@ -7445,7 +7458,27 @@ QRhiRenderTarget *QRhiSwapChain::currentFrameRenderTarget(StereoTargetBuffer tar
} luminanceInNits;
\endcode
Whereas for macOS/iOS, the current maximum and potential maximum color
On Windows the minimum and maximum luminance depends on the screen
brightness. While not relevant for desktops, on laptops the screen
brightness may change at any time. Increasing brightness implies decreased
maximum luminance. In addition, the results may also be dependent on the
HDR Content Brightness set in Windows Settings' System/Display/HDR view,
if there is such a setting.
Note however that the changes made to the laptop screen's brightness or in
the system settings while the application is running are not necessarily
reflected in the returned values, meaning calling hdrInfo() again may still
return the same luminance range as before for the rest of the process'
lifetime. The exact behavior is up to DXGI and Qt has no control over it.
\note The Windows compositor works in scene-referred mode for HDR content.
A color component value of 1.0 corresponds to a luminance of 80 nits. When
rendering non-HDR content (e.g. 2D UI elements), the correction of the
white level is often necessary. (e.g., outputting the fragment color (1, 1,
1) will likely lead to showing a shade of white that is too dim on-screen)
See \l sdrWhiteLevel.
For macOS/iOS, the current maximum and potential maximum color
component values are provided:
\code
@ -7455,14 +7488,62 @@ QRhiRenderTarget *QRhiSwapChain::currentFrameRenderTarget(StereoTargetBuffer tar
} colorComponentValue;
\endcode
The value may depend on the screen brightness, which on laptops means that
the result may change in the next call to hdrInfo() if the brightness was
changed in the meantime. The maximum screen brightness implies a maximum
color value of 1.0.
\note Apple's EDR is display-referred. 1.0 corresponds to a luminance level
of SDR white (e.g. 200 nits), the value of which varies based on the screen
brightness and possibly other settings. The exact luminance value for that,
or the maximum luminance of the display, are not exposed to the
applications.
\note It has been observed that the color component values are not set to
the correct larger-than-1 value right away on startup on some macOS
systems, but the values tend to change during or after the first frame.
\sa QRhiSwapChain::hdrInfo()
*/
/*!
\variable QRhiSwapChainHdrInfo::luminanceBehavior
Describes the platform's presumed behavior with regards to color values.
\sa sdrWhiteLevel
*/
/*!
\variable QRhiSwapChainHdrInfo::sdrWhiteLevel
On Windows this is the dynamic SDR white level in nits. The value is
dependent on the screen brightness (on laptops), and the SDR or HDR Content
Brightness settings in the Windows settings' System/Display/HDR view.
To perform white level correction for non-HDR (SDR) content, such as 2D UI
elemenents, multiply the final color with sdrWhiteLevel / 80.0 whenever
\l luminanceBehavior is SceneReferred. (assuming Windows and a linear
extended sRGB (scRGB) color space)
On other platforms the value is always a pre-defined value, 200. This may
not match the system's actual SDR white level, but the value of this
variable is not relevant in practice when the \l luminanceBehavior is
DisplayReferred, because then the color component value of 1.0 refers to
the SDR white by default.
\sa luminanceBehavior
*/
/*!
\return the HDR information for the associated display.
The returned struct is always the default one if createOrResize() has not
been successfully called yet.
Do not assume that this is a cheap operation. Depending on the platform,
this function makes various platform queries which may have a performance
impact.
\note Can be called before createOrResize() as long as the window is
\l{setWindow()}{set}.
\note What happens when moving a window with an initialized swapchain
between displays (HDR to HDR with different characteristics, HDR to SDR,
@ -7477,10 +7558,11 @@ QRhiRenderTarget *QRhiSwapChain::currentFrameRenderTarget(StereoTargetBuffer tar
QRhiSwapChainHdrInfo QRhiSwapChain::hdrInfo()
{
QRhiSwapChainHdrInfo info;
info.isHardCodedDefaults = true;
info.limitsType = QRhiSwapChainHdrInfo::LuminanceInNits;
info.limits.luminanceInNits.minLuminance = 0.0f;
info.limits.luminanceInNits.maxLuminance = 1000.0f;
info.luminanceBehavior = QRhiSwapChainHdrInfo::SceneReferred;
info.sdrWhiteLevel = 200.0f;
return info;
}
@ -7488,7 +7570,7 @@ QRhiSwapChainHdrInfo QRhiSwapChain::hdrInfo()
QDebug operator<<(QDebug dbg, const QRhiSwapChainHdrInfo &info)
{
QDebugStateSaver saver(dbg);
dbg.nospace() << "QRhiSwapChainHdrInfo(" << (info.isHardCodedDefaults ? "with hard-coded defaults" : "queried from system");
dbg.nospace() << "QRhiSwapChainHdrInfo(";
switch (info.limitsType) {
case QRhiSwapChainHdrInfo::LuminanceInNits:
dbg.nospace() << " minLuminance=" << info.limits.luminanceInNits.minLuminance
@ -7499,6 +7581,14 @@ QDebug operator<<(QDebug dbg, const QRhiSwapChainHdrInfo &info)
dbg.nospace() << " maxPotentialColorComponentValue=" << info.limits.colorComponentValue.maxPotentialColorComponentValue;
break;
}
switch (info.luminanceBehavior) {
case QRhiSwapChainHdrInfo::SceneReferred:
dbg.nospace() << " scene-referred, SDR white level=" << info.sdrWhiteLevel;
break;
case QRhiSwapChainHdrInfo::DisplayReferred:
dbg.nospace() << " display-referred";
break;
}
dbg.nospace() << ')';
return dbg;
}

View File

@ -1480,11 +1480,16 @@ Q_DECLARE_TYPEINFO(QRhiGraphicsPipeline::TargetBlend, Q_RELOCATABLE_TYPE);
struct QRhiSwapChainHdrInfo
{
bool isHardCodedDefaults;
enum LimitsType {
LuminanceInNits,
ColorComponentValue
};
enum LuminanceBehavior {
SceneReferred,
DisplayReferred
};
LimitsType limitsType;
union {
struct {
@ -1496,6 +1501,8 @@ struct QRhiSwapChainHdrInfo
float maxPotentialColorComponentValue;
} colorComponentValue;
} limits;
LuminanceBehavior luminanceBehavior;
float sdrWhiteLevel;
};
Q_DECLARE_TYPEINFO(QRhiSwapChainHdrInfo, Q_RELOCATABLE_TYPE);

View File

@ -4863,44 +4863,6 @@ QSize QD3D11SwapChain::surfacePixelSize()
return m_window->size() * m_window->devicePixelRatio();
}
static bool output6ForWindow(QWindow *w, IDXGIAdapter1 *adapter, IDXGIOutput6 **result)
{
bool ok = false;
QRect wr = w->geometry();
wr = QRect(wr.topLeft() * w->devicePixelRatio(), wr.size() * w->devicePixelRatio());
const QPoint center = wr.center();
IDXGIOutput *currentOutput = nullptr;
IDXGIOutput *output = nullptr;
for (UINT i = 0; adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND; ++i) {
DXGI_OUTPUT_DESC desc;
output->GetDesc(&desc);
const RECT r = desc.DesktopCoordinates;
const QRect dr(QPoint(r.left, r.top), QPoint(r.right - 1, r.bottom - 1));
if (dr.contains(center)) {
currentOutput = output;
break;
} else {
output->Release();
}
}
if (currentOutput) {
ok = SUCCEEDED(currentOutput->QueryInterface(__uuidof(IDXGIOutput6), reinterpret_cast<void **>(result)));
currentOutput->Release();
}
return ok;
}
static bool outputDesc1ForWindow(QWindow *w, IDXGIAdapter1 *adapter, DXGI_OUTPUT_DESC1 *result)
{
bool ok = false;
IDXGIOutput6 *out6 = nullptr;
if (output6ForWindow(w, adapter, &out6)) {
ok = SUCCEEDED(out6->GetDesc1(result));
out6->Release();
}
return ok;
}
bool QD3D11SwapChain::isFormatSupported(Format f)
{
if (f == SDR)
@ -4913,7 +4875,7 @@ bool QD3D11SwapChain::isFormatSupported(Format f)
QRHI_RES_RHI(QRhiD3D11);
DXGI_OUTPUT_DESC1 desc1;
if (outputDesc1ForWindow(m_window, rhiD->activeAdapter, &desc1)) {
if (QRhiD3D::outputDesc1ForWindow(m_window, rhiD->activeAdapter, &desc1)) {
if (desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020)
return f == QRhiSwapChain::HDRExtendedSrgbLinear || f == QRhiSwapChain::HDR10;
}
@ -4924,14 +4886,16 @@ bool QD3D11SwapChain::isFormatSupported(Format f)
QRhiSwapChainHdrInfo QD3D11SwapChain::hdrInfo()
{
QRhiSwapChainHdrInfo info = QRhiSwapChain::hdrInfo();
// Must use m_window, not window, given this may be called before createOrResize().
if (m_window) {
QRHI_RES_RHI(QRhiD3D11);
DXGI_OUTPUT_DESC1 hdrOutputDesc;
if (outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc)) {
info.isHardCodedDefaults = false;
if (QRhiD3D::outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc)) {
info.limitsType = QRhiSwapChainHdrInfo::LuminanceInNits;
info.limits.luminanceInNits.minLuminance = hdrOutputDesc.MinLuminance;
info.limits.luminanceInNits.maxLuminance = hdrOutputDesc.MaxLuminance;
info.luminanceBehavior = QRhiSwapChainHdrInfo::SceneReferred; // 1.0 = 80 nits
info.sdrWhiteLevel = QRhiD3D::sdrWhiteLevelInNits(hdrOutputDesc);
}
}
return info;
@ -5058,7 +5022,7 @@ bool QD3D11SwapChain::createOrResize()
DXGI_COLOR_SPACE_TYPE hdrColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; // SDR
DXGI_OUTPUT_DESC1 hdrOutputDesc;
if (outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc) && m_format != SDR) {
if (QRhiD3D::outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc) && m_format != SDR) {
// https://docs.microsoft.com/en-us/windows/win32/direct3darticles/high-dynamic-range
if (hdrOutputDesc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) {
switch (m_format) {

View File

@ -6066,44 +6066,6 @@ QSize QD3D12SwapChain::surfacePixelSize()
return m_window->size() * m_window->devicePixelRatio();
}
static bool output6ForWindow(QWindow *w, IDXGIAdapter1 *adapter, IDXGIOutput6 **result)
{
bool ok = false;
QRect wr = w->geometry();
wr = QRect(wr.topLeft() * w->devicePixelRatio(), wr.size() * w->devicePixelRatio());
const QPoint center = wr.center();
IDXGIOutput *currentOutput = nullptr;
IDXGIOutput *output = nullptr;
for (UINT i = 0; adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND; ++i) {
DXGI_OUTPUT_DESC desc;
output->GetDesc(&desc);
const RECT r = desc.DesktopCoordinates;
const QRect dr(QPoint(r.left, r.top), QPoint(r.right - 1, r.bottom - 1));
if (dr.contains(center)) {
currentOutput = output;
break;
} else {
output->Release();
}
}
if (currentOutput) {
ok = SUCCEEDED(currentOutput->QueryInterface(__uuidof(IDXGIOutput6), reinterpret_cast<void **>(result)));
currentOutput->Release();
}
return ok;
}
static bool outputDesc1ForWindow(QWindow *w, IDXGIAdapter1 *adapter, DXGI_OUTPUT_DESC1 *result)
{
bool ok = false;
IDXGIOutput6 *out6 = nullptr;
if (output6ForWindow(w, adapter, &out6)) {
ok = SUCCEEDED(out6->GetDesc1(result));
out6->Release();
}
return ok;
}
bool QD3D12SwapChain::isFormatSupported(Format f)
{
if (f == SDR)
@ -6116,7 +6078,7 @@ bool QD3D12SwapChain::isFormatSupported(Format f)
QRHI_RES_RHI(QRhiD3D12);
DXGI_OUTPUT_DESC1 desc1;
if (outputDesc1ForWindow(m_window, rhiD->activeAdapter, &desc1)) {
if (QRhiD3D::outputDesc1ForWindow(m_window, rhiD->activeAdapter, &desc1)) {
if (desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020)
return f == QRhiSwapChain::HDRExtendedSrgbLinear || f == QRhiSwapChain::HDR10;
}
@ -6127,14 +6089,16 @@ bool QD3D12SwapChain::isFormatSupported(Format f)
QRhiSwapChainHdrInfo QD3D12SwapChain::hdrInfo()
{
QRhiSwapChainHdrInfo info = QRhiSwapChain::hdrInfo();
// Must use m_window, not window, given this may be called before createOrResize().
if (m_window) {
QRHI_RES_RHI(QRhiD3D12);
DXGI_OUTPUT_DESC1 hdrOutputDesc;
if (outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc)) {
info.isHardCodedDefaults = false;
if (QRhiD3D::outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc)) {
info.limitsType = QRhiSwapChainHdrInfo::LuminanceInNits;
info.limits.luminanceInNits.minLuminance = hdrOutputDesc.MinLuminance;
info.limits.luminanceInNits.maxLuminance = hdrOutputDesc.MaxLuminance;
info.luminanceBehavior = QRhiSwapChainHdrInfo::SceneReferred; // 1.0 = 80 nits
info.sdrWhiteLevel = QRhiD3D::sdrWhiteLevelInNits(hdrOutputDesc);
}
}
return info;
@ -6177,7 +6141,7 @@ void QD3D12SwapChain::chooseFormats()
hdrColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; // SDR
DXGI_OUTPUT_DESC1 hdrOutputDesc;
QRHI_RES_RHI(QRhiD3D12);
if (outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc) && m_format != SDR) {
if (QRhiD3D::outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc) && m_format != SDR) {
// https://docs.microsoft.com/en-us/windows/win32/direct3darticles/high-dynamic-range
if (hdrOutputDesc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) {
switch (m_format) {

View File

@ -0,0 +1,160 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qrhid3dhelpers_p.h"
QT_BEGIN_NAMESPACE
namespace QRhiD3D {
bool output6ForWindow(QWindow *w, IDXGIAdapter1 *adapter, IDXGIOutput6 **result)
{
bool ok = false;
QRect wr = w->geometry();
wr = QRect(wr.topLeft() * w->devicePixelRatio(), wr.size() * w->devicePixelRatio());
const QPoint center = wr.center();
IDXGIOutput *currentOutput = nullptr;
IDXGIOutput *output = nullptr;
for (UINT i = 0; adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND; ++i) {
DXGI_OUTPUT_DESC desc;
output->GetDesc(&desc);
const RECT r = desc.DesktopCoordinates;
const QRect dr(QPoint(r.left, r.top), QPoint(r.right - 1, r.bottom - 1));
if (dr.contains(center)) {
currentOutput = output;
break;
} else {
output->Release();
}
}
if (currentOutput) {
ok = SUCCEEDED(currentOutput->QueryInterface(__uuidof(IDXGIOutput6), reinterpret_cast<void **>(result)));
currentOutput->Release();
}
return ok;
}
bool outputDesc1ForWindow(QWindow *w, IDXGIAdapter1 *adapter, DXGI_OUTPUT_DESC1 *result)
{
bool ok = false;
IDXGIOutput6 *out6 = nullptr;
if (output6ForWindow(w, adapter, &out6)) {
ok = SUCCEEDED(out6->GetDesc1(result));
out6->Release();
}
return ok;
}
float sdrWhiteLevelInNits(const DXGI_OUTPUT_DESC1 &outputDesc)
{
QVector<DISPLAYCONFIG_PATH_INFO> pathInfos;
uint32_t pathInfoCount, modeInfoCount;
LONG result;
do {
if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &pathInfoCount, &modeInfoCount) == ERROR_SUCCESS) {
pathInfos.resize(pathInfoCount);
QVector<DISPLAYCONFIG_MODE_INFO> modeInfos(modeInfoCount);
result = QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &pathInfoCount, pathInfos.data(), &modeInfoCount, modeInfos.data(), nullptr);
} else {
return 200.0f;
}
} while (result == ERROR_INSUFFICIENT_BUFFER);
MONITORINFOEX monitorInfo = {};
monitorInfo.cbSize = sizeof(monitorInfo);
GetMonitorInfo(outputDesc.Monitor, &monitorInfo);
for (const DISPLAYCONFIG_PATH_INFO &info : pathInfos) {
DISPLAYCONFIG_SOURCE_DEVICE_NAME deviceName = {};
deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
deviceName.header.size = sizeof(deviceName);
deviceName.header.adapterId = info.sourceInfo.adapterId;
deviceName.header.id = info.sourceInfo.id;
if (DisplayConfigGetDeviceInfo(&deviceName.header) == ERROR_SUCCESS) {
if (!wcscmp(monitorInfo.szDevice, deviceName.viewGdiDeviceName)) {
DISPLAYCONFIG_SDR_WHITE_LEVEL whiteLevel = {};
whiteLevel.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL;
whiteLevel.header.size = sizeof(DISPLAYCONFIG_SDR_WHITE_LEVEL);
whiteLevel.header.adapterId = info.targetInfo.adapterId;
whiteLevel.header.id = info.targetInfo.id;
if (DisplayConfigGetDeviceInfo(&whiteLevel.header) == ERROR_SUCCESS)
return whiteLevel.SDRWhiteLevel * 80 / 1000.0f;
}
}
}
return 200.0f;
}
pD3DCompile resolveD3DCompile()
{
for (const wchar_t *libraryName : {L"D3DCompiler_47", L"D3DCompiler_43"}) {
QSystemLibrary library(libraryName);
if (library.load()) {
if (auto symbol = library.resolve("D3DCompile"))
return reinterpret_cast<pD3DCompile>(symbol);
} else {
qWarning("Failed to load D3DCompiler_47/43.dll");
}
}
return nullptr;
}
IDCompositionDevice *createDirectCompositionDevice()
{
QSystemLibrary dcomplib(QStringLiteral("dcomp"));
typedef HRESULT (__stdcall *DCompositionCreateDeviceFuncPtr)(
_In_opt_ IDXGIDevice *dxgiDevice,
_In_ REFIID iid,
_Outptr_ void **dcompositionDevice);
DCompositionCreateDeviceFuncPtr func = reinterpret_cast<DCompositionCreateDeviceFuncPtr>(
dcomplib.resolve("DCompositionCreateDevice"));
if (!func) {
qWarning("Unable to resolve DCompositionCreateDevice, perhaps dcomp.dll is missing?");
return nullptr;
}
IDCompositionDevice *device = nullptr;
HRESULT hr = func(nullptr, __uuidof(IDCompositionDevice), reinterpret_cast<void **>(&device));
if (FAILED(hr)) {
qWarning("Failed to create Direct Composition device: %s",
qPrintable(QSystemError::windowsComString(hr)));
return nullptr;
}
return device;
}
#ifdef QRHI_D3D12_HAS_DXC
std::pair<IDxcCompiler *, IDxcLibrary *> createDxcCompiler()
{
QSystemLibrary dxclib(QStringLiteral("dxcompiler"));
// this will not be in the system library location, hence onlySystemDirectory==false
if (!dxclib.load(false)) {
qWarning("Failed to load dxcompiler.dll");
return {};
}
DxcCreateInstanceProc func = reinterpret_cast<DxcCreateInstanceProc>(dxclib.resolve("DxcCreateInstance"));
if (!func) {
qWarning("Unable to resolve DxcCreateInstance");
return {};
}
IDxcCompiler *compiler = nullptr;
HRESULT hr = func(CLSID_DxcCompiler, __uuidof(IDxcCompiler), reinterpret_cast<void**>(&compiler));
if (FAILED(hr)) {
qWarning("Failed to create dxc compiler instance: %s",
qPrintable(QSystemError::windowsComString(hr)));
return {};
}
IDxcLibrary *library = nullptr;
hr = func(CLSID_DxcLibrary, __uuidof(IDxcLibrary), reinterpret_cast<void**>(&library));
if (FAILED(hr)) {
qWarning("Failed to create dxc library instance: %s",
qPrintable(QSystemError::windowsComString(hr)));
return {};
}
return { compiler, library };
}
#endif
} // namespace
QT_END_NAMESPACE

View File

@ -18,6 +18,7 @@
#include <QtCore/private/qsystemlibrary_p.h>
#include <QtCore/private/qsystemerror_p.h>
#include <dxgi1_6.h>
#include <dcomp.h>
#include <d3dcompiler.h>
@ -30,73 +31,16 @@ QT_BEGIN_NAMESPACE
namespace QRhiD3D {
inline pD3DCompile resolveD3DCompile()
{
for (const wchar_t *libraryName : {L"D3DCompiler_47", L"D3DCompiler_43"}) {
QSystemLibrary library(libraryName);
if (library.load()) {
if (auto symbol = library.resolve("D3DCompile"))
return reinterpret_cast<pD3DCompile>(symbol);
} else {
qWarning("Failed to load D3DCompiler_47/43.dll");
}
}
return nullptr;
}
bool output6ForWindow(QWindow *w, IDXGIAdapter1 *adapter, IDXGIOutput6 **result);
bool outputDesc1ForWindow(QWindow *w, IDXGIAdapter1 *adapter, DXGI_OUTPUT_DESC1 *result);
float sdrWhiteLevelInNits(const DXGI_OUTPUT_DESC1 &outputDesc);
inline IDCompositionDevice *createDirectCompositionDevice()
{
QSystemLibrary dcomplib(QStringLiteral("dcomp"));
typedef HRESULT (__stdcall *DCompositionCreateDeviceFuncPtr)(
_In_opt_ IDXGIDevice *dxgiDevice,
_In_ REFIID iid,
_Outptr_ void **dcompositionDevice);
DCompositionCreateDeviceFuncPtr func = reinterpret_cast<DCompositionCreateDeviceFuncPtr>(
dcomplib.resolve("DCompositionCreateDevice"));
if (!func) {
qWarning("Unable to resolve DCompositionCreateDevice, perhaps dcomp.dll is missing?");
return nullptr;
}
IDCompositionDevice *device = nullptr;
HRESULT hr = func(nullptr, __uuidof(IDCompositionDevice), reinterpret_cast<void **>(&device));
if (FAILED(hr)) {
qWarning("Failed to create Direct Composition device: %s",
qPrintable(QSystemError::windowsComString(hr)));
return nullptr;
}
return device;
}
pD3DCompile resolveD3DCompile();
IDCompositionDevice *createDirectCompositionDevice();
#ifdef QRHI_D3D12_HAS_DXC
inline std::pair<IDxcCompiler *, IDxcLibrary *> createDxcCompiler()
{
QSystemLibrary dxclib(QStringLiteral("dxcompiler"));
// this will not be in the system library location, hence onlySystemDirectory==false
if (!dxclib.load(false)) {
qWarning("Failed to load dxcompiler.dll");
return {};
}
DxcCreateInstanceProc func = reinterpret_cast<DxcCreateInstanceProc>(dxclib.resolve("DxcCreateInstance"));
if (!func) {
qWarning("Unable to resolve DxcCreateInstance");
return {};
}
IDxcCompiler *compiler = nullptr;
HRESULT hr = func(CLSID_DxcCompiler, __uuidof(IDxcCompiler), reinterpret_cast<void**>(&compiler));
if (FAILED(hr)) {
qWarning("Failed to create dxc compiler instance: %s",
qPrintable(QSystemError::windowsComString(hr)));
return {};
}
IDxcLibrary *library = nullptr;
hr = func(CLSID_DxcLibrary, __uuidof(IDxcLibrary), reinterpret_cast<void**>(&library));
if (FAILED(hr)) {
qWarning("Failed to create dxc library instance: %s",
qPrintable(QSystemError::windowsComString(hr)));
return {};
}
return { compiler, library };
}
std::pair<IDxcCompiler *, IDxcLibrary *> createDxcCompiler();
#endif
} // namespace

View File

@ -6402,7 +6402,9 @@ QRhiSwapChainHdrInfo QMetalSwapChain::hdrInfo()
QRhiSwapChainHdrInfo info;
info.limitsType = QRhiSwapChainHdrInfo::ColorComponentValue;
info.limits.colorComponentValue.maxColorComponentValue = 1;
info.isHardCodedDefaults = true;
info.limits.colorComponentValue.maxPotentialColorComponentValue = 1;
info.luminanceBehavior = QRhiSwapChainHdrInfo::DisplayReferred; // 1.0 = SDR white
info.sdrWhiteLevel = 200; // typical value, but dummy (don't know the real one); won't matter due to being display-referred
if (m_window) {
// Must use m_window, not window, given this may be called before createOrResize().
@ -6411,14 +6413,12 @@ QRhiSwapChainHdrInfo QMetalSwapChain::hdrInfo()
NSScreen *screen = view.window.screen;
info.limits.colorComponentValue.maxColorComponentValue = screen.maximumExtendedDynamicRangeColorComponentValue;
info.limits.colorComponentValue.maxPotentialColorComponentValue = screen.maximumPotentialExtendedDynamicRangeColorComponentValue;
info.isHardCodedDefaults = false;
#else
if (@available(iOS 16.0, *)) {
UIView *view = reinterpret_cast<UIView *>(m_window->winId());
UIScreen *screen = view.window.windowScene.screen;
info.limits.colorComponentValue.maxColorComponentValue = view.window.windowScene.screen.currentEDRHeadroom;
info.limits.colorComponentValue.maxPotentialColorComponentValue = screen.potentialEDRHeadroom;
info.isHardCodedDefaults = false;
}
#endif
}

View File

@ -0,0 +1,20 @@
qt_internal_add_manual_test(hdr
GUI
SOURCES
hdr.cpp
LIBRARIES
Qt::Gui
Qt::GuiPrivate
)
qt_internal_add_resource(hdr "hdr"
PREFIX
"/"
FILES
"hdrtexture.vert.qsb"
"hdrtexture.frag.qsb"
)
set(imgui_base ../shared/imgui)
set(imgui_target hdr)
include(${imgui_base}/imgui.cmakeinc)

View File

@ -0,0 +1,2 @@
qsb --qt6 hdrtexture.vert -o hdrtexture.vert.qsb
qsb --qt6 hdrtexture.frag -o hdrtexture.frag.qsb

View File

@ -0,0 +1,448 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
// Test application for HDR with scRGB.
// Launch with the argument "scrgb" or "sdr", perhaps side-by-side even.
#define EXAMPLEFW_PREINIT
#define EXAMPLEFW_IMGUI
#include "../shared/examplefw.h"
#include "../shared/cube.h"
QByteArray loadHdr(const QString &fn, QSize *size)
{
QFile f(fn);
if (!f.open(QIODevice::ReadOnly)) {
qWarning("Failed to open %s", qPrintable(fn));
return QByteArray();
}
char sig[256];
f.read(sig, 11);
if (strncmp(sig, "#?RADIANCE\n", 11))
return QByteArray();
QByteArray buf = f.readAll();
const char *p = buf.constData();
const char *pEnd = p + buf.size();
// Process lines until the empty one.
QByteArray line;
while (p < pEnd) {
char c = *p++;
if (c == '\n') {
if (line.isEmpty())
break;
if (line.startsWith(QByteArrayLiteral("FORMAT="))) {
const QByteArray format = line.mid(7).trimmed();
if (format != QByteArrayLiteral("32-bit_rle_rgbe")) {
qWarning("HDR format '%s' is not supported", format.constData());
return QByteArray();
}
}
line.clear();
} else {
line.append(c);
}
}
if (p == pEnd) {
qWarning("Malformed HDR image data at property strings");
return QByteArray();
}
// Get the resolution string.
while (p < pEnd) {
char c = *p++;
if (c == '\n')
break;
line.append(c);
}
if (p == pEnd) {
qWarning("Malformed HDR image data at resolution string");
return QByteArray();
}
int w = 0, h = 0;
// We only care about the standard orientation.
if (!sscanf(line.constData(), "-Y %d +X %d", &h, &w)) {
qWarning("Unsupported HDR resolution string '%s'", line.constData());
return QByteArray();
}
if (w <= 0 || h <= 0) {
qWarning("Invalid HDR resolution");
return QByteArray();
}
// output is RGBA32F
const int blockSize = 4 * sizeof(float);
QByteArray data;
data.resize(w * h * blockSize);
typedef unsigned char RGBE[4];
RGBE *scanline = new RGBE[w];
for (int y = 0; y < h; ++y) {
if (pEnd - p < 4) {
qWarning("Unexpected end of HDR data");
delete[] scanline;
return QByteArray();
}
scanline[0][0] = *p++;
scanline[0][1] = *p++;
scanline[0][2] = *p++;
scanline[0][3] = *p++;
if (scanline[0][0] == 2 && scanline[0][1] == 2 && scanline[0][2] < 128) {
// new rle, the first pixel was a dummy
for (int channel = 0; channel < 4; ++channel) {
for (int x = 0; x < w && p < pEnd; ) {
unsigned char c = *p++;
if (c > 128) { // run
if (p < pEnd) {
int repCount = c & 127;
c = *p++;
while (repCount--)
scanline[x++][channel] = c;
}
} else { // not a run
while (c-- && p < pEnd)
scanline[x++][channel] = *p++;
}
}
}
} else {
// old rle
scanline[0][0] = 2;
int bitshift = 0;
int x = 1;
while (x < w && pEnd - p >= 4) {
scanline[x][0] = *p++;
scanline[x][1] = *p++;
scanline[x][2] = *p++;
scanline[x][3] = *p++;
if (scanline[x][0] == 1 && scanline[x][1] == 1 && scanline[x][2] == 1) { // run
int repCount = scanline[x][3] << bitshift;
while (repCount--) {
memcpy(scanline[x], scanline[x - 1], 4);
++x;
}
bitshift += 8;
} else { // not a run
++x;
bitshift = 0;
}
}
}
// adjust for -Y orientation
float *fp = reinterpret_cast<float *>(data.data() + (h - 1 - y) * blockSize * w);
for (int x = 0; x < w; ++x) {
float d = qPow(2.0f, float(scanline[x][3]) - 128.0f);
float r = scanline[x][0] / 256.0f * d;
float g = scanline[x][1] / 256.0f * d;
float b = scanline[x][2] / 256.0f * d;
float a = 1.0f;
*fp++ = r;
*fp++ = g;
*fp++ = b;
*fp++ = a;
}
}
delete[] scanline;
*size = QSize(w, h);
return data;
}
struct {
QMatrix4x4 winProj;
QList<QRhiResource *> releasePool;
QRhiResourceUpdateBatch *initialUpdates = nullptr;
QRhiBuffer *vbuf = nullptr;
QRhiBuffer *ubuf = nullptr;
QRhiTexture *tex = nullptr;
QRhiSampler *sampler = nullptr;
QRhiShaderResourceBindings *srb = nullptr;
QRhiGraphicsPipeline *ps = nullptr;
bool showDemoWindow = true;
QVector3D rotation;
bool usingHDRWindow;
bool adjustSDR = false;
float SDRWhiteLevelInNits = 200.0f;
bool tonemapHDR = false;
float tonemapInMax = 2.5f;
float tonemapOutMax = 0.0f;
QString imageFile;
} d;
void preInit()
{
QStringList args = QCoreApplication::arguments();
if (args.contains("scrgb")) {
d.usingHDRWindow = true;
swapchainFormat = QRhiSwapChain::HDRExtendedSrgbLinear;
} else if (args.contains("sdr")) {
d.usingHDRWindow = false;
swapchainFormat = QRhiSwapChain::SDR;
} else {
qFatal("Missing command line argument, specify scrgb or sdr");
}
if (args.contains("file")) {
d.imageFile = args[args.indexOf("file") + 1];
qDebug("Using HDR image file %s", qPrintable(d.imageFile));
} else {
qFatal("Missing command line argument, specify 'file' followed by a .hdr file. "
"Download for example the original .exr from https://viewer.openhdr.org/i/5fcb9a595812624a99d24c62/linear "
"and use ImageMagick's 'convert' to convert from .exr to .hdr");
}
}
void Window::customInit()
{
if (!m_r->isTextureFormatSupported(QRhiTexture::RGBA32F))
qWarning("RGBA32F texture format is not supported");
d.initialUpdates = m_r->nextResourceUpdateBatch();
d.vbuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(cube));
d.vbuf->create();
d.releasePool << d.vbuf;
d.initialUpdates->uploadStaticBuffer(d.vbuf, cube);
d.ubuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64 + 4 * 4);
d.ubuf->create();
d.releasePool << d.ubuf;
qint32 flip = 1;
d.initialUpdates->updateDynamicBuffer(d.ubuf, 64, 4, &flip);
qint32 doLinearToSRGBInShader = !d.usingHDRWindow;
d.initialUpdates->updateDynamicBuffer(d.ubuf, 68, 4, &doLinearToSRGBInShader);
QSize size;
QByteArray floatData = loadHdr(d.imageFile, &size);
d.tex = m_r->newTexture(QRhiTexture::RGBA32F, size);
d.releasePool << d.tex;
d.tex->create();
QRhiTextureUploadDescription desc({ 0, 0, { floatData.constData(), quint32(floatData.size()) } });
d.initialUpdates->uploadTexture(d.tex, desc);
d.sampler = m_r->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
d.releasePool << d.sampler;
d.sampler->create();
d.srb = m_r->newShaderResourceBindings();
d.releasePool << d.srb;
d.srb->setBindings({
QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.ubuf),
QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, d.tex, d.sampler)
});
d.srb->create();
d.ps = m_r->newGraphicsPipeline();
d.releasePool << d.ps;
d.ps->setCullMode(QRhiGraphicsPipeline::Back);
const QRhiShaderStage stages[] = {
{ QRhiShaderStage::Vertex, getShader(QLatin1String(":/hdrtexture.vert.qsb")) },
{ QRhiShaderStage::Fragment, getShader(QLatin1String(":/hdrtexture.frag.qsb")) }
};
d.ps->setShaderStages(stages, stages + 2);
QRhiVertexInputLayout inputLayout;
inputLayout.setBindings({
{ 3 * sizeof(float) },
{ 2 * sizeof(float) }
});
inputLayout.setAttributes({
{ 0, 0, QRhiVertexInputAttribute::Float3, 0 },
{ 1, 1, QRhiVertexInputAttribute::Float2, 0 }
});
d.ps->setVertexInputLayout(inputLayout);
d.ps->setShaderResourceBindings(d.srb);
d.ps->setRenderPassDescriptor(m_rp);
d.ps->create();
}
void Window::customRelease()
{
qDeleteAll(d.releasePool);
d.releasePool.clear();
}
void Window::customRender()
{
if (d.tonemapOutMax == 0.0f) {
QRhiSwapChainHdrInfo info = m_sc->hdrInfo();
switch (info.limitsType) {
case QRhiSwapChainHdrInfo::LuminanceInNits:
d.tonemapOutMax = info.limits.luminanceInNits.maxLuminance / 80.0f;
break;
case QRhiSwapChainHdrInfo::ColorComponentValue:
// because on macOS it changes dynamically when starting up, so retry in next frame if it's still just 1.0
if (info.limits.colorComponentValue.maxColorComponentValue > 1.0f)
d.tonemapOutMax = info.limits.colorComponentValue.maxColorComponentValue;
break;
}
}
QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer();
QRhiResourceUpdateBatch *u = m_r->nextResourceUpdateBatch();
if (d.initialUpdates) {
u->merge(d.initialUpdates);
d.initialUpdates->release();
d.initialUpdates = nullptr;
}
QMatrix4x4 mvp = m_proj;
mvp.rotate(d.rotation.x(), 1, 0, 0);
mvp.rotate(d.rotation.y(), 0, 1, 0);
mvp.rotate(d.rotation.z(), 0, 0, 1);
u->updateDynamicBuffer(d.ubuf, 0, 64, mvp.constData());
if (d.usingHDRWindow && d.tonemapHDR) {
u->updateDynamicBuffer(d.ubuf, 72, 4, &d.tonemapInMax);
u->updateDynamicBuffer(d.ubuf, 76, 4, &d.tonemapOutMax);
} else {
float zero[2] = {};
u->updateDynamicBuffer(d.ubuf, 72, 8, zero);
}
QColor clearColor = Qt::green; // sRGB
if (d.usingHDRWindow && d.adjustSDR) {
float sdrMultiplier = d.SDRWhiteLevelInNits / 80.0f; // scRGB 1.0 = 80 nits (and linear gamma)
clearColor = QColor::fromRgbF(clearColor.redF() * sdrMultiplier,
clearColor.greenF() * sdrMultiplier,
clearColor.blueF() * sdrMultiplier,
1.0f);
}
const QSize outputSizeInPixels = m_sc->currentPixelSize();
cb->beginPass(m_sc->currentFrameRenderTarget(), clearColor, { 1.0f, 0 }, u);
cb->setGraphicsPipeline(d.ps);
cb->setViewport(QRhiViewport(0, 0, outputSizeInPixels.width(), outputSizeInPixels.height()));
cb->setShaderResources();
const QRhiCommandBuffer::VertexInput vbufBindings[] = {
{ d.vbuf, 0 },
{ d.vbuf, quint32(36 * 3 * sizeof(float)) }
};
cb->setVertexInput(0, 2, vbufBindings);
cb->draw(36);
m_imguiRenderer->render();
cb->endPass();
}
static void addTip(const char *s)
{
ImGui::SameLine();
ImGui::TextDisabled("(?)");
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(300);
ImGui::TextUnformatted(s);
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
}
void Window::customGui()
{
ImGui::SetNextWindowBgAlpha(1.0f);
ImGui::SetNextWindowPos(ImVec2(10, 420), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(800, 300), ImGuiCond_FirstUseEver);
ImGui::Begin("HDR test");
if (d.usingHDRWindow) {
ImGui::Text("The window is now scRGB (extended *linear* sRGB) + FP16 color buffer,\n"
"the ImGui UI and the green background are considered SDR content,\n"
"the cube is using a HDR texture.");
ImGui::Checkbox("Adjust SDR content", &d.adjustSDR);
addTip("Multiplies fragment colors for non-HDR content with sdr_white_level / 80. "
"Not relevant with macOS (due to EDR being display-referred).");
if (d.adjustSDR) {
ImGui::SliderFloat("SDR white level (nits)", &d.SDRWhiteLevelInNits, 0.0f, 1000.0f);
imguiHDRMultiplier = d.SDRWhiteLevelInNits / 80.0f; // scRGB 1.0 = 80 nits (and linear gamma)
} else {
imguiHDRMultiplier = 1.0f; // 0 would mean linear to sRGB; don't want that with HDR
}
ImGui::Separator();
ImGui::Checkbox("Tonemap HDR content", &d.tonemapHDR);
addTip("Perform some most basic Reinhard tonemapping (changed to suit HDR) on the 3D content (the cube). "
"Display max luminance is set to the max color component value (macOS) or max luminance in nits / 80 (Windows) by default.");
if (d.tonemapHDR) {
ImGui::SliderFloat("Content max luminance\n(color component value)", &d.tonemapInMax, 0.0f, 20.0f);
ImGui::SliderFloat("Display max luminance\n(color component value)", &d.tonemapOutMax, 0.0f, 20.0f);
}
} else {
ImGui::Text("The window is standard dynamic range (no HDR, so non-linear sRGB effectively).\n"
"Here we just do linear -> sRGB for everything (UI, textured cube)\n"
"at the end of the pipeline, while the Qt::green background is already sRGB.");
}
ImGui::End();
ImGui::SetNextWindowPos(ImVec2(850, 560), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(420, 140), ImGuiCond_FirstUseEver);
ImGui::Begin("Misc");
float *rot = reinterpret_cast<float *>(&d.rotation);
ImGui::SliderFloat("Rotation X", &rot[0], 0.0f, 360.0f);
ImGui::SliderFloat("Rotation Y", &rot[1], 0.0f, 360.0f);
ImGui::SliderFloat("Rotation Z", &rot[2], 0.0f, 360.0f);
ImGui::End();
if (d.usingHDRWindow) {
ImGui::SetNextWindowPos(ImVec2(850, 10), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(420, 180), ImGuiCond_FirstUseEver);
ImGui::Begin("Actual platform info");
QRhiSwapChainHdrInfo info = m_sc->hdrInfo();
switch (info.limitsType) {
case QRhiSwapChainHdrInfo::LuminanceInNits:
ImGui::Text("Platform provides luminance in nits");
addTip("Windows/D3D: On laptops this will be screen brightness dependent. Increasing brightness implies the max luminance decreases. "
"It also seems to be affected by HDR Content Brightness in the Settings, if there is one (e.g. on laptops; not to be confused with SDR Content Brightess). "
"(note that the DXGI query does not seem to return changed values if there are runtime changes unless restarting the app).");
ImGui::Text("Min luminance: %.4f\nMax luminance: %.4f",
info.limits.luminanceInNits.minLuminance,
info.limits.luminanceInNits.maxLuminance);
break;
case QRhiSwapChainHdrInfo::ColorComponentValue:
ImGui::Text("Platform provides color component values");
addTip("macOS/Metal: On laptops this will be screen brightness dependent. Increasing brightness decreases the max color value. "
"Max brightness may bring it down to 1.0.");
ImGui::Text("maxColorComponentValue: %.4f\nmaxPotentialColorComponentValue: %.4f",
info.limits.colorComponentValue.maxColorComponentValue,
info.limits.colorComponentValue.maxPotentialColorComponentValue);
break;
}
ImGui::Separator();
switch (info.luminanceBehavior) {
case QRhiSwapChainHdrInfo::SceneReferred:
ImGui::Text("Luminance behavior is scene-referred");
break;
case QRhiSwapChainHdrInfo::DisplayReferred:
ImGui::Text("Luminance behavior is display-referred");
break;
}
addTip("Windows (DWM) HDR is scene-referred: 1.0 = 80 nits.\n\n"
"Apple EDR is display-referred: the value of 1.0 refers to whatever the system's current SDR white level is (100, 200, ... nits depending on the brightness).");
if (info.luminanceBehavior == QRhiSwapChainHdrInfo::SceneReferred) {
ImGui::Text("SDR white level: %.4f nits", info.sdrWhiteLevel);
addTip("On Windows this is queried from DISPLAYCONFIG_SDR_WHITE_LEVEL. "
"Affected by the slider in the Windows Settings (System/Display/HDR/[S|H]DR Content Brightness). "
"With max screen brightness (laptops) the value will likely be the same as the max luminance.");
}
ImGui::End();
}
}

View File

@ -0,0 +1,44 @@
#version 440
layout(location = 0) in vec2 v_texcoord;
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
mat4 mvp;
int flip;
int sdr;
float tonemapInMax;
float tonemapOutMax;
} ubuf;
layout(binding = 1) uniform sampler2D tex;
vec3 linearToSRGB(vec3 color)
{
vec3 S1 = sqrt(color);
vec3 S2 = sqrt(S1);
vec3 S3 = sqrt(S2);
return 0.585122381 * S1 + 0.783140355 * S2 - 0.368262736 * S3;
}
// https://learn.microsoft.com/en-us/windows/win32/direct3darticles/high-dynamic-range
vec3 simpleReinhardTonemapper(vec3 c, float inMax, float outMax)
{
c /= vec3(inMax);
c.r = c.r / (1 + c.r);
c.g = c.g / (1 + c.g);
c.b = c.b / (1 + c.b);
c *= vec3(outMax);
return c;
}
void main()
{
vec4 c = texture(tex, v_texcoord);
if (ubuf.tonemapInMax != 0)
c.rgb = simpleReinhardTonemapper(c.rgb, ubuf.tonemapInMax, ubuf.tonemapOutMax);
else if (ubuf.sdr != 0)
c.rgb = linearToSRGB(c.rgb);
fragColor = vec4(c.rgb * c.a, c.a);
}

Binary file not shown.

View File

@ -0,0 +1,22 @@
#version 440
layout(location = 0) in vec4 position;
layout(location = 1) in vec2 texcoord;
layout(location = 0) out vec2 v_texcoord;
layout(std140, binding = 0) uniform buf {
mat4 mvp;
int flip;
int sdr;
float tonemapInMaxNits;
float tonemapOutMaxNits;
};
void main()
{
v_texcoord = vec2(texcoord.x, texcoord.y);
if (flip != 0)
v_texcoord.y = 1.0 - v_texcoord.y;
gl_Position = mvp * position;
}

Binary file not shown.

View File

@ -1,40 +0,0 @@
TEMPLATE = subdirs
SUBDIRS += \
hellominimalcrossgfxtriangle \
compressedtexture_bc1 \
compressedtexture_bc1_subupload \
texuploads \
msaatexture \
msaarenderbuffer \
cubemap \
cubemap_scissor \
cubemap_render \
multiwindow \
multiwindow_threaded \
triquadcube \
offscreen \
floattexture \
float16texture_with_compute \
mrt \
shadowmap \
computebuffer \
computeimage \
instancing \
noninstanced \
tex3d \
texturearray \
polygonmode \
tessellation \
geometryshader \
stenciloutline \
stereo \
tex1d \
displacement \
imguirenderer \
multiview
qtConfig(widgets) {
SUBDIRS += \
qrhiprof
}

View File

@ -80,6 +80,8 @@ QRhi::BeginFrameFlags beginFrameFlags;
QRhi::EndFrameFlags endFrameFlags;
bool transparentBackground = false;
bool debugLayer = true;
QRhiSwapChain::Format swapchainFormat = QRhiSwapChain::SDR;
float imguiHDRMultiplier = 0.0f;
class Window : public QWindow
{
@ -280,6 +282,10 @@ void Window::init()
m_sc->setDepthStencil(m_ds);
m_sc->setSampleCount(sampleCount);
m_sc->setFlags(scFlags);
if (!m_sc->isFormatSupported(swapchainFormat))
qWarning("Swapchain reports that requested format %d is not supported", int(swapchainFormat));
else
m_sc->setFormat(swapchainFormat);
m_rp = m_sc->newCompatibleRenderPassDescriptor();
m_sc->setRenderPassDescriptor(m_rp);
@ -398,7 +404,7 @@ void Window::render()
QMatrix4x4 guiMvp = m_r->clipSpaceCorrMatrix();
guiMvp.ortho(0, outputSizeInPixels.width() / dpr, outputSizeInPixels.height() / dpr, 0, 1, -1);
m_imguiRenderer->prepare(m_r, rt, cb, guiMvp, 1.0f);
m_imguiRenderer->prepare(m_r, rt, cb, guiMvp, 1.0f, imguiHDRMultiplier);
#endif
customRender();

View File

@ -8,14 +8,30 @@ layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
mat4 mvp;
float opacity;
// Windows HDR: set to SDR_white_level_in_nits / 80
// macOS/iOS EDR: set to 1.0
// No HDR: set to 0.0, will do linear to sRGB at the end then.
float hdrWhiteLevelMult;
};
layout(binding = 1) uniform sampler2D tex;
vec3 linearToSRGB(vec3 color)
{
vec3 S1 = sqrt(color);
vec3 S2 = sqrt(S1);
vec3 S3 = sqrt(S2);
return 0.585122381 * S1 + 0.783140355 * S2 - 0.368262736 * S3;
}
void main()
{
vec4 c = v_color * texture(tex, v_texcoord);
c.a *= opacity;
c.rgb *= c.a;
if (hdrWhiteLevelMult > 0.0)
c.rgb *= hdrWhiteLevelMult;
else
c.rgb = linearToSRGB(c.rgb);
c.rgb *= c.a; // premultiplied alpha
fragColor = c;
}

View File

@ -10,6 +10,7 @@ layout(location = 1) out vec4 v_color;
layout(std140, binding = 0) uniform buf {
mat4 mvp;
float opacity;
float sdrMult;
};
void main()

View File

@ -48,7 +48,12 @@ void QRhiImguiRenderer::releaseResources()
m_rhi = nullptr;
}
void QRhiImguiRenderer::prepare(QRhi *rhi, QRhiRenderTarget *rt, QRhiCommandBuffer *cb, const QMatrix4x4 &mvp, float opacity)
void QRhiImguiRenderer::prepare(QRhi *rhi,
QRhiRenderTarget *rt,
QRhiCommandBuffer *cb,
const QMatrix4x4 &mvp,
float opacity,
float hdrWhiteLevelMultiplierOrZeroForSDRsRGB)
{
if (!m_rhi) {
m_rhi = rhi;
@ -89,7 +94,7 @@ void QRhiImguiRenderer::prepare(QRhi *rhi, QRhiRenderTarget *rt, QRhiCommandBuff
}
if (!m_ubuf) {
m_ubuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64 + 4));
m_ubuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64 + 4 + 4));
m_ubuf->setName(QByteArrayLiteral("imgui uniform buffer"));
if (!m_ubuf->create())
return;
@ -201,6 +206,7 @@ void QRhiImguiRenderer::prepare(QRhi *rhi, QRhiRenderTarget *rt, QRhiCommandBuff
u->updateDynamicBuffer(m_ubuf.get(), 0, 64, mvp.constData());
u->updateDynamicBuffer(m_ubuf.get(), 64, 4, &opacity);
u->updateDynamicBuffer(m_ubuf.get(), 68, 4, &hdrWhiteLevelMultiplierOrZeroForSDRsRGB);
for (int i = 0; i < texturesNeedUpdate.count(); ++i) {
Texture &t(m_textures[texturesNeedUpdate[i]]);

View File

@ -45,7 +45,12 @@ public:
StaticRenderData sf;
FrameRenderData f;
void prepare(QRhi *rhi, QRhiRenderTarget *rt, QRhiCommandBuffer *cb, const QMatrix4x4 &mvp, float opacity);
void prepare(QRhi *rhi,
QRhiRenderTarget *rt,
QRhiCommandBuffer *cb,
const QMatrix4x4 &mvp,
float opacity = 1.0f,
float hdrWhiteLevelMultiplierOrZeroForSDRsRGB = 0.0f);
void render();
void releaseResources();