summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLaszlo Agocs <laszlo.agocs@qt.io>2023-08-01 14:40:41 +0200
committerLaszlo Agocs <laszlo.agocs@qt.io>2023-08-03 12:17:25 +0200
commit547b9da7ad818446a2eeb80985959c9dee7089fb (patch)
tree1ce4481222e84334c4dbf59276b867cff91d2fbe
parent78bc1a9a25602ba898b1f84a6c41c3804758f6cd (diff)
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>
-rw-r--r--src/gui/CMakeLists.txt3
-rw-r--r--src/gui/rhi/qrhi.cpp140
-rw-r--r--src/gui/rhi/qrhi.h9
-rw-r--r--src/gui/rhi/qrhid3d11.cpp48
-rw-r--r--src/gui/rhi/qrhid3d12.cpp48
-rw-r--r--src/gui/rhi/qrhid3dhelpers.cpp160
-rw-r--r--src/gui/rhi/qrhid3dhelpers_p.h72
-rw-r--r--src/gui/rhi/qrhimetal.mm6
-rw-r--r--tests/manual/rhi/hdr/CMakeLists.txt20
-rw-r--r--tests/manual/rhi/hdr/buildshaders.bat2
-rw-r--r--tests/manual/rhi/hdr/hdr.cpp448
-rw-r--r--tests/manual/rhi/hdr/hdrtexture.frag44
-rw-r--r--tests/manual/rhi/hdr/hdrtexture.frag.qsbbin0 -> 2742 bytes
-rw-r--r--tests/manual/rhi/hdr/hdrtexture.vert22
-rw-r--r--tests/manual/rhi/hdr/hdrtexture.vert.qsbbin0 -> 1471 bytes
-rw-r--r--tests/manual/rhi/rhi.pro40
-rw-r--r--tests/manual/rhi/shared/examplefw.h8
-rw-r--r--tests/manual/rhi/shared/imgui/imgui.frag18
-rw-r--r--tests/manual/rhi/shared/imgui/imgui.frag.qsbbin1671 -> 2596 bytes
-rw-r--r--tests/manual/rhi/shared/imgui/imgui.vert1
-rw-r--r--tests/manual/rhi/shared/imgui/imgui.vert.qsbbin1494 -> 1532 bytes
-rw-r--r--tests/manual/rhi/shared/imgui/qrhiimgui.cpp10
-rw-r--r--tests/manual/rhi/shared/imgui/qrhiimgui_p.h7
23 files changed, 883 insertions, 223 deletions
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 9a92c3aed2..8c7fd791cd 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -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
diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp
index 255209a9b0..67265ebf46 100644
--- a/src/gui/rhi/qrhi.cpp
+++ b/src/gui/rhi/qrhi.cpp
@@ -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;
}
diff --git a/src/gui/rhi/qrhi.h b/src/gui/rhi/qrhi.h
index 2d5ee7346e..2a4c16b881 100644
--- a/src/gui/rhi/qrhi.h
+++ b/src/gui/rhi/qrhi.h
@@ -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);
diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp
index ce6104d3c4..8e6dd24dc5 100644
--- a/src/gui/rhi/qrhid3d11.cpp
+++ b/src/gui/rhi/qrhid3d11.cpp
@@ -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) {
diff --git a/src/gui/rhi/qrhid3d12.cpp b/src/gui/rhi/qrhid3d12.cpp
index 0b5d7e3875..90dbc53acb 100644
--- a/src/gui/rhi/qrhid3d12.cpp
+++ b/src/gui/rhi/qrhid3d12.cpp
@@ -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) {
diff --git a/src/gui/rhi/qrhid3dhelpers.cpp b/src/gui/rhi/qrhid3dhelpers.cpp
new file mode 100644
index 0000000000..3ee5ae5832
--- /dev/null
+++ b/src/gui/rhi/qrhid3dhelpers.cpp
@@ -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
diff --git a/src/gui/rhi/qrhid3dhelpers_p.h b/src/gui/rhi/qrhid3dhelpers_p.h
index 07fcddb7f7..f4cf6deaed 100644
--- a/src/gui/rhi/qrhid3dhelpers_p.h
+++ b/src/gui/rhi/qrhid3dhelpers_p.h
@@ -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
diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm
index cafb7ff7cb..95a954fab5 100644
--- a/src/gui/rhi/qrhimetal.mm
+++ b/src/gui/rhi/qrhimetal.mm
@@ -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
}
diff --git a/tests/manual/rhi/hdr/CMakeLists.txt b/tests/manual/rhi/hdr/CMakeLists.txt
new file mode 100644
index 0000000000..cf6e7662a4
--- /dev/null
+++ b/tests/manual/rhi/hdr/CMakeLists.txt
@@ -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)
diff --git a/tests/manual/rhi/hdr/buildshaders.bat b/tests/manual/rhi/hdr/buildshaders.bat
new file mode 100644
index 0000000000..7710c85f83
--- /dev/null
+++ b/tests/manual/rhi/hdr/buildshaders.bat
@@ -0,0 +1,2 @@
+qsb --qt6 hdrtexture.vert -o hdrtexture.vert.qsb
+qsb --qt6 hdrtexture.frag -o hdrtexture.frag.qsb
diff --git a/tests/manual/rhi/hdr/hdr.cpp b/tests/manual/rhi/hdr/hdr.cpp
new file mode 100644
index 0000000000..ab51b7a8e8
--- /dev/null
+++ b/tests/manual/rhi/hdr/hdr.cpp
@@ -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();
+ }
+}
diff --git a/tests/manual/rhi/hdr/hdrtexture.frag b/tests/manual/rhi/hdr/hdrtexture.frag
new file mode 100644
index 0000000000..9d5e12005a
--- /dev/null
+++ b/tests/manual/rhi/hdr/hdrtexture.frag
@@ -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);
+}
diff --git a/tests/manual/rhi/hdr/hdrtexture.frag.qsb b/tests/manual/rhi/hdr/hdrtexture.frag.qsb
new file mode 100644
index 0000000000..441b660f0b
--- /dev/null
+++ b/tests/manual/rhi/hdr/hdrtexture.frag.qsb
Binary files differ
diff --git a/tests/manual/rhi/hdr/hdrtexture.vert b/tests/manual/rhi/hdr/hdrtexture.vert
new file mode 100644
index 0000000000..336d5f8cfb
--- /dev/null
+++ b/tests/manual/rhi/hdr/hdrtexture.vert
@@ -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;
+}
diff --git a/tests/manual/rhi/hdr/hdrtexture.vert.qsb b/tests/manual/rhi/hdr/hdrtexture.vert.qsb
new file mode 100644
index 0000000000..bcfb0da273
--- /dev/null
+++ b/tests/manual/rhi/hdr/hdrtexture.vert.qsb
Binary files differ
diff --git a/tests/manual/rhi/rhi.pro b/tests/manual/rhi/rhi.pro
deleted file mode 100644
index c688c6aa17..0000000000
--- a/tests/manual/rhi/rhi.pro
+++ /dev/null
@@ -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
-}
diff --git a/tests/manual/rhi/shared/examplefw.h b/tests/manual/rhi/shared/examplefw.h
index 2bab0a9e68..d0fa903859 100644
--- a/tests/manual/rhi/shared/examplefw.h
+++ b/tests/manual/rhi/shared/examplefw.h
@@ -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();
diff --git a/tests/manual/rhi/shared/imgui/imgui.frag b/tests/manual/rhi/shared/imgui/imgui.frag
index 51bd615deb..6169dd639f 100644
--- a/tests/manual/rhi/shared/imgui/imgui.frag
+++ b/tests/manual/rhi/shared/imgui/imgui.frag
@@ -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;
}
diff --git a/tests/manual/rhi/shared/imgui/imgui.frag.qsb b/tests/manual/rhi/shared/imgui/imgui.frag.qsb
index 2abf236f56..09b1e44697 100644
--- a/tests/manual/rhi/shared/imgui/imgui.frag.qsb
+++ b/tests/manual/rhi/shared/imgui/imgui.frag.qsb
Binary files differ
diff --git a/tests/manual/rhi/shared/imgui/imgui.vert b/tests/manual/rhi/shared/imgui/imgui.vert
index bb24a22c13..45510ea0fe 100644
--- a/tests/manual/rhi/shared/imgui/imgui.vert
+++ b/tests/manual/rhi/shared/imgui/imgui.vert
@@ -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()
diff --git a/tests/manual/rhi/shared/imgui/imgui.vert.qsb b/tests/manual/rhi/shared/imgui/imgui.vert.qsb
index 0f2300f676..3ee5a7b716 100644
--- a/tests/manual/rhi/shared/imgui/imgui.vert.qsb
+++ b/tests/manual/rhi/shared/imgui/imgui.vert.qsb
Binary files differ
diff --git a/tests/manual/rhi/shared/imgui/qrhiimgui.cpp b/tests/manual/rhi/shared/imgui/qrhiimgui.cpp
index d4330238b9..b5f39b020f 100644
--- a/tests/manual/rhi/shared/imgui/qrhiimgui.cpp
+++ b/tests/manual/rhi/shared/imgui/qrhiimgui.cpp
@@ -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]]);
diff --git a/tests/manual/rhi/shared/imgui/qrhiimgui_p.h b/tests/manual/rhi/shared/imgui/qrhiimgui_p.h
index 31782144bf..5605578c6c 100644
--- a/tests/manual/rhi/shared/imgui/qrhiimgui_p.h
+++ b/tests/manual/rhi/shared/imgui/qrhiimgui_p.h
@@ -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();