Skip to content

Commit

Permalink
DirectX HDR support wip
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxwellGengYF committed Dec 17, 2024
1 parent 08b5ae5 commit a01ab1b
Show file tree
Hide file tree
Showing 18 changed files with 264 additions and 90 deletions.
50 changes: 50 additions & 0 deletions include/luisa/backends/ext/dx_hdr_ext.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#pragma once
#include <luisa/runtime/rhi/device_interface.h>

namespace luisa::compute {
class DXHDRExt : public DeviceExtension {
public:
enum class DisplayCurve {
sRGB = 0,// The display expects an sRGB signal.
ST2084, // The display expects an HDR10 signal.
None // The display expects a linear signal.
};
enum class SwapChainBitDepth {
_8 = 0,
_10,
_16,
SwapChainBitDepthCount
};
struct DisplayChromaticities {
float RedX;
float RedY;
float GreenX;
float GreenY;
float BlueX;
float BlueY;
float WhiteX;
float WhiteY;
};
struct DXSwapchainOption {
uint64_t window;
uint2 size;
PixelStorage storage = PixelStorage::HALF4;
bool wants_vsync = true;
uint back_buffer_count = 2;
};

[[nodiscard]] virtual SwapchainCreationInfo create_swapchain(
const DXSwapchainOption &option,
uint64_t stream_handle) noexcept = 0;
virtual void set_hdr_meta_data(
uint64_t swapchain_handle,
float max_output_nits = 1000.0f,
float min_output_nits = 0.001f,
float max_cll = 2000.0f,
float max_fall = 500.0f,
const DXHDRExt::DisplayChromaticities *custom_chroma = nullptr) noexcept = 0;
static constexpr luisa::string_view name = "DXHDRExt";
protected:
~DXHDRExt() = default;
};
}// namespace luisa::compute
13 changes: 11 additions & 2 deletions src/backends/dx/DXApi/LCDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <Resource/BottomAccel.h>
#include <Resource/TopAccel.h>
#include <DXApi/LCSwapChain.h>
#include <DXApi/dx_hdr_ext.hpp>
#include "ext.h"
#include "../../common/hlsl/hlsl_codegen.h"
#include <luisa/ast/function_builder.h>
Expand Down Expand Up @@ -97,6 +98,14 @@ LCDevice::LCDevice(Context &&ctx, DeviceConfig const *settings)
[](DeviceExtension *ext) {
delete static_cast<DxDirectMLExt *>(ext);
});
exts.try_emplace(
DXHDRExt::name,
[](LCDevice *device) -> DeviceExtension * {
return new DXHDRExtImpl(device);
},
[](DeviceExtension *ext) {
delete static_cast<DXHDRExtImpl *>(ext);
});
#ifdef LCDX_ENABLE_CUDA
exts.try_emplace(
DxCudaInterop::name,
Expand Down Expand Up @@ -305,7 +314,7 @@ ShaderCreationInfo LCDevice::create_shader(const ShaderOption &option, Function
mask |= (1 << 1);
}
// use default control flow
constexpr uint compiler_version = 202403u; // dxc version at march 2024
constexpr uint compiler_version = 202403u;// dxc version at march 2024
mask |= (1 << 2);
mask |= compiler_version << 3u;
auto code = hlsl::CodegenUtility{}.Codegen(kernel, option.native_include, mask, false);
Expand Down Expand Up @@ -465,7 +474,7 @@ SwapchainCreationInfo LCDevice::create_swapchain(const SwapchainOption &option,
reinterpret_cast<HWND>(option.window),
option.size.x,
option.size.y,
option.wants_hdr,
option.wants_hdr ? DXGI_FORMAT_R16G16B16A16_FLOAT : DXGI_FORMAT_R8G8B8A8_UNORM,
option.wants_vsync,
option.back_buffer_count);
info.handle = resource_to_handle(res);
Expand Down
9 changes: 5 additions & 4 deletions src/backends/dx/DXApi/LCSwapChain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ LCSwapChain::LCSwapChain(
HWND windowHandle,
uint width,
uint height,
bool allowHDR,
DXGI_FORMAT format,
bool vsync,
uint backBufferCount)
: Resource(device), vsync(vsync) {
this->format = format;
auto frameCount = backBufferCount + 1;
vstd::push_back_func(
m_renderTargets,
Expand All @@ -23,7 +24,7 @@ LCSwapChain::LCSwapChain(
swapChainDesc.BufferCount = frameCount;
swapChainDesc.Width = width;
swapChainDesc.Height = height;
swapChainDesc.Format = allowHDR ? DXGI_FORMAT_R16G16B16A16_FLOAT : DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.Format = format;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
if (!vsync)
Expand All @@ -39,7 +40,7 @@ LCSwapChain::LCSwapChain(
nullptr,
nullptr,
&localSwap));
swapChain = DxPtr(static_cast<IDXGISwapChain3 *>(localSwap), true);
swapChain = DxPtr(static_cast<IDXGISwapChain4 *>(localSwap), true);
}
for (uint32_t n = 0; n < frameCount; n++) {
ThrowIfFailed(swapChain->GetBuffer(n, IID_PPV_ARGS(&m_renderTargets[n].rt)));
Expand All @@ -50,7 +51,7 @@ LCSwapChain::LCSwapChain(
LCSwapChain::LCSwapChain(
PixelStorage &storage,
Device *device,
IDXGISwapChain3 *swapChain,
IDXGISwapChain4 *swapChain,
bool vsync)
: Resource(device),
swapChain(swapChain, false),
Expand Down
7 changes: 4 additions & 3 deletions src/backends/dx/DXApi/LCSwapChain.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ namespace lc::dx {
class LCSwapChain : public Resource {
public:
vstd::vector<SwapChain> m_renderTargets;
DxPtr<IDXGISwapChain3> swapChain;
DxPtr<IDXGISwapChain4> swapChain;
uint64 frameIndex = 0;
DXGI_FORMAT format;
bool vsync;
Tag GetTag() const override { return Tag::SwapChain; }
LCSwapChain(
Expand All @@ -20,13 +21,13 @@ class LCSwapChain : public Resource {
HWND windowHandle,
uint width,
uint height,
bool allowHDR,
DXGI_FORMAT format,
bool vsync,
uint backBufferCount);
LCSwapChain(
PixelStorage& storage,
Device* device,
IDXGISwapChain3* swapChain,
IDXGISwapChain4* swapChain,
bool vsync);
};
}// namespace lc::dx
165 changes: 165 additions & 0 deletions src/backends/dx/DXApi/dx_hdr_ext.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
#include <DXApi/dx_hdr_ext.hpp>
#include <DXApi/TypeCheck.h>
#include <d3d12.h>
#include <dxgi1_5.h>
#include <DXApi/LCSwapChain.h>
#include <Resource/TextureBase.h>
namespace lc::dx {
using namespace luisa::compute;
namespace dx_hdr_ext_detail {

DXHDRExt::DisplayCurve EnsureSwapChainColorSpace(
IDXGISwapChain4 *swapChain,
DXGI_COLOR_SPACE_TYPE &currentSwapChainColorSpace,
DXHDRExt::SwapChainBitDepth swapChainBitDepth,
bool enableST2084) {
DXHDRExt::DisplayCurve result{DXHDRExt::DisplayCurve::None};
DXGI_COLOR_SPACE_TYPE colorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
switch (swapChainBitDepth) {
case DXHDRExt::SwapChainBitDepth::_8:
result = DXHDRExt::DisplayCurve::sRGB;
break;

case DXHDRExt::SwapChainBitDepth::_10:
colorSpace = enableST2084 ? DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 : DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
result = enableST2084 ? DXHDRExt::DisplayCurve::ST2084 : DXHDRExt::DisplayCurve::sRGB;
break;

case DXHDRExt::SwapChainBitDepth::_16:
colorSpace = DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709;
result = DXHDRExt::DisplayCurve::None;
break;
}

if (currentSwapChainColorSpace != colorSpace) {
UINT colorSpaceSupport = 0;
if (SUCCEEDED(swapChain->CheckColorSpaceSupport(colorSpace, &colorSpaceSupport)) &&
((colorSpaceSupport & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT) == DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT)) {
ThrowIfFailed(swapChain->SetColorSpace1(colorSpace));
currentSwapChainColorSpace = colorSpace;
}
}
return result;
}
void SetHDRMetaData(
LCSwapChain *swapchain,
bool hdr_support,
float MaxOutputNits /*=1000.0f*/,
float MinOutputNits /*=0.001f*/,
float MaxCLL /*=2000.0f*/,
float MaxFALL /*=500.0f*/,
const DXHDRExt::DisplayChromaticities *Chroma) {
if (!swapchain) {
return;
}

// Clean the hdr metadata if the display doesn't support HDR
if (!hdr_support) {
ThrowIfFailed(swapchain->swapChain->SetHDRMetaData(DXGI_HDR_METADATA_TYPE_NONE, 0, nullptr));
return;
}

static const DXHDRExt::DisplayChromaticities DisplayChromaticityList[] =
{
{0.64000f, 0.33000f, 0.30000f, 0.60000f, 0.15000f, 0.06000f, 0.31270f, 0.32900f},// Display Gamut Rec709
{0.70800f, 0.29200f, 0.17000f, 0.79700f, 0.13100f, 0.04600f, 0.31270f, 0.32900f},// Display Gamut Rec2020
};

// Select the chromaticity based on HDR format of the DWM.
DXGI_COLOR_SPACE_TYPE colorSpace = DXGI_COLOR_SPACE_CUSTOM;
DXHDRExt::SwapChainBitDepth hitDepth;
switch (swapchain->format) {
case DXGI_FORMAT_R10G10B10A2_TYPELESS:
case DXGI_FORMAT_R10G10B10A2_UNORM:
case DXGI_FORMAT_R10G10B10A2_UINT:
case DXGI_FORMAT_R11G11B10_FLOAT:
hitDepth = DXHDRExt::SwapChainBitDepth::_10;
break;
case DXGI_FORMAT_R16G16B16A16_FLOAT:
case DXGI_FORMAT_R16G16B16A16_UNORM:
case DXGI_FORMAT_R16G16B16A16_SNORM:
case DXGI_FORMAT_R16G16B16A16_SINT:
case DXGI_FORMAT_R16G16B16A16_UINT:
case DXGI_FORMAT_R16G16B16A16_TYPELESS:
hitDepth = DXHDRExt::SwapChainBitDepth::_16;
break;
default:
hitDepth = DXHDRExt::SwapChainBitDepth::_8;
break;
}

EnsureSwapChainColorSpace(swapchain->swapChain, colorSpace, hitDepth, hdr_support);
int selectedChroma = 0;
if (swapchain->format == DXGI_FORMAT_R16G16B16A16_FLOAT && colorSpace == DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709) {
selectedChroma = 0;
} else if (hitDepth == DXHDRExt::SwapChainBitDepth::_10 && colorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) {
selectedChroma = 1;
} else {
// Reset the metadata since this is not a supported HDR format.
ThrowIfFailed(swapchain->swapChain->SetHDRMetaData(DXGI_HDR_METADATA_TYPE_NONE, 0, nullptr));
return;
}

// Set HDR meta data
if (!Chroma)
Chroma = &DisplayChromaticityList[selectedChroma];
DXGI_HDR_METADATA_HDR10 HDR10MetaData = {};
HDR10MetaData.RedPrimary[0] = static_cast<UINT16>(Chroma->RedX * 50000.0f);
HDR10MetaData.RedPrimary[1] = static_cast<UINT16>(Chroma->RedY * 50000.0f);
HDR10MetaData.GreenPrimary[0] = static_cast<UINT16>(Chroma->GreenX * 50000.0f);
HDR10MetaData.GreenPrimary[1] = static_cast<UINT16>(Chroma->GreenY * 50000.0f);
HDR10MetaData.BluePrimary[0] = static_cast<UINT16>(Chroma->BlueX * 50000.0f);
HDR10MetaData.BluePrimary[1] = static_cast<UINT16>(Chroma->BlueY * 50000.0f);
HDR10MetaData.WhitePoint[0] = static_cast<UINT16>(Chroma->WhiteX * 50000.0f);
HDR10MetaData.WhitePoint[1] = static_cast<UINT16>(Chroma->WhiteY * 50000.0f);
HDR10MetaData.MaxMasteringLuminance = static_cast<UINT>(MaxOutputNits * 10000.0f);
HDR10MetaData.MinMasteringLuminance = static_cast<UINT>(MinOutputNits * 10000.0f);
HDR10MetaData.MaxContentLightLevel = static_cast<UINT16>(MaxCLL);
HDR10MetaData.MaxFrameAverageLightLevel = static_cast<UINT16>(MaxFALL);
ThrowIfFailed(swapchain->swapChain->SetHDRMetaData(DXGI_HDR_METADATA_TYPE_HDR10, sizeof(DXGI_HDR_METADATA_HDR10), &HDR10MetaData));
}
}// namespace dx_hdr_ext_detail
DXHDRExtImpl::DXHDRExtImpl(LCDevice *lc_device) : _lc_device(lc_device) {}
DXHDRExtImpl::~DXHDRExtImpl() = default;

SwapchainCreationInfo DXHDRExtImpl::create_swapchain(
const DXSwapchainOption &option,
uint64_t stream_handle) noexcept {
auto queue = reinterpret_cast<CmdQueueBase *>(stream_handle);
if (queue->Tag() != CmdQueueTag::MainCmd) [[unlikely]] {
LUISA_ERROR("swapchain not allowed in Direct-Storage.");
}
SwapchainCreationInfo info;
auto res = new LCSwapChain(
&(_lc_device->nativeDevice),
&reinterpret_cast<LCCmdBuffer *>(stream_handle)->queue,
_lc_device->nativeDevice.defaultAllocator.get(),
reinterpret_cast<HWND>(option.window),
option.size.x,
option.size.y,
static_cast<DXGI_FORMAT>(TextureBase::ToGFXFormat(pixel_storage_to_format<float>(option.storage))),
option.wants_vsync,
option.back_buffer_count);
info.handle = resource_to_handle(res);
info.native_handle = res->swapChain.Get();
info.storage = option.storage;
return info;
}

void DXHDRExtImpl::set_hdr_meta_data(
uint64_t swapchain_handle,
float max_output_nits,
float min_output_nits,
float max_cll,
float max_fall,
const DXHDRExt::DisplayChromaticities *custom_chroma) noexcept {
dx_hdr_ext_detail::SetHDRMetaData(
reinterpret_cast<LCSwapChain *>(swapchain_handle),
true,
max_output_nits,
min_output_nits,
max_cll,
max_fall,
custom_chroma);
}
}// namespace lc::dx
21 changes: 21 additions & 0 deletions src/backends/dx/DXApi/dx_hdr_ext.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#pragma once
#include <luisa/backends/ext/dx_hdr_ext.h>
#include <DXApi/LCDevice.h>
namespace lc::dx {
class DXHDRExtImpl : public luisa::compute::DXHDRExt {
LCDevice *_lc_device;
public:
DXHDRExtImpl(LCDevice *lc_device);
~DXHDRExtImpl();
SwapchainCreationInfo create_swapchain(
const DXSwapchainOption &option,
uint64_t stream_handle) noexcept override;
void set_hdr_meta_data(
uint64_t swapchain_handle,
float max_output_nits = 1000.0f,
float min_output_nits = 0.001f,
float max_cll = 2000.0f,
float max_fall = 500.0f,
const DXHDRExt::DisplayChromaticities *custom_chroma = nullptr) noexcept override;
};
}// namespace lc::dx
2 changes: 1 addition & 1 deletion src/backends/dx/DXApi/ext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ SwapchainCreationInfo DxNativeResourceExt::register_external_swapchain(
auto res = new LCSwapChain(
info.storage,
dx_device,
reinterpret_cast<IDXGISwapChain3 *>(swapchain_ptr),
reinterpret_cast<IDXGISwapChain4 *>(swapchain_ptr),
vsync);
info.handle = reinterpret_cast<uint64_t>(res);
info.native_handle = swapchain_ptr;
Expand Down
2 changes: 1 addition & 1 deletion src/backends/dx/DXRuntime/CommandAllocator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ void CommandAllocator::Execute(
}
}
}
void CommandAllocator::ExecuteAndPresent(CommandQueue *queue, ID3D12Fence *fence, uint64 fenceIndex, IDXGISwapChain3 *swapchain, bool vsync) {
void CommandAllocator::ExecuteAndPresent(CommandQueue *queue, ID3D12Fence *fence, uint64 fenceIndex, IDXGISwapChain4 *swapchain, bool vsync) {
auto present = [&]() {
if (vsync) {
ThrowIfFailed(swapchain->Present(1, 0));
Expand Down
2 changes: 1 addition & 1 deletion src/backends/dx/DXRuntime/CommandAllocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class CommandAllocator final : public vstd::IOperatorNewBase {
~CommandAllocator();
CommandBuffer *GetBuffer() const;
void Execute(CommandQueue *queue, ID3D12Fence *fence, uint64 fenceIndex);
void ExecuteAndPresent(CommandQueue *queue, ID3D12Fence *fence, uint64 fenceIndex, IDXGISwapChain3 *swapchain, bool vsync);
void ExecuteAndPresent(CommandQueue *queue, ID3D12Fence *fence, uint64 fenceIndex, IDXGISwapChain4 *swapchain, bool vsync);
void Complete(CommandQueue *queue, ID3D12Fence *fence, uint64 fenceIndex);
DefaultBuffer const *AllocateScratchBuffer(size_t targetSize);
BufferView GetTempReadbackBuffer(uint64 size, size_t align = 0);
Expand Down
2 changes: 1 addition & 1 deletion src/backends/dx/DXRuntime/CommandQueue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ void CommandQueue::ExecuteEmptyCallbacks(AllocatorPtr &&alloc, vstd::vector<vstd
waitCv.notify_one();
}

void CommandQueue::ExecuteAndPresent(AllocatorPtr &&alloc, IDXGISwapChain3 *swapChain, bool vsync) {
void CommandQueue::ExecuteAndPresent(AllocatorPtr &&alloc, IDXGISwapChain4 *swapChain, bool vsync) {
auto curFrame = ++lastFrame;
alloc->ExecuteAndPresent(this, cmdFence.Get(), curFrame, swapChain, vsync);
mtx.lock();
Expand Down
2 changes: 1 addition & 1 deletion src/backends/dx/DXRuntime/CommandQueue.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class CommandQueue : vstd::IOperatorNewBase {
void ExecuteCallbacks(AllocatorPtr &&alloc, vstd::vector<vstd::function<void()>> &&callbacks);
void ExecuteEmpty(AllocatorPtr &&alloc);
void ExecuteEmptyCallbacks(AllocatorPtr &&alloc, vstd::vector<vstd::function<void()>> &&callbacks);
void ExecuteAndPresent(AllocatorPtr &&alloc, IDXGISwapChain3 *swapChain, bool vsync);
void ExecuteAndPresent(AllocatorPtr &&alloc, IDXGISwapChain4 *swapChain, bool vsync);
void Complete(uint64 fence);
void Complete();
void ForceSync(
Expand Down
Loading

0 comments on commit a01ab1b

Please sign in to comment.