From 210dc17be40931b67750d528f2156a9d8e90b7b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Ko=C5=82odziejski?= Date: Thu, 12 Feb 2026 12:58:07 +0100 Subject: [PATCH] feat(d3d11, dxmt): gamma support --- src/d3d11/d3d11_swapchain.cpp | 8 ++++++ src/dxgi/dxgi_output.cpp | 52 ++++++++++++++++++++++++++++------- src/dxgi/dxgi_output.hpp | 13 +++++++++ src/dxmt/dxmt_command.metal | 10 +++++++ src/dxmt/dxmt_presenter.cpp | 47 +++++++++++++++++++++++++++++-- src/dxmt/dxmt_presenter.hpp | 16 ++++++++++- 6 files changed, 132 insertions(+), 14 deletions(-) create mode 100644 src/dxgi/dxgi_output.hpp diff --git a/src/d3d11/d3d11_swapchain.cpp b/src/d3d11/d3d11_swapchain.cpp index a0d32eec..fc2ac256 100644 --- a/src/d3d11/d3d11_swapchain.cpp +++ b/src/d3d11/d3d11_swapchain.cpp @@ -4,10 +4,12 @@ #include "d3d11_private.h" #include "dxgi_interfaces.h" #include "dxgi_object.hpp" +#include "dxgi_output.hpp" #include "d3d11_context.hpp" #include "dxmt_context.hpp" #include "dxmt_hud_state.hpp" #include "dxmt_statistics.hpp" +#include "dxmt_presenter.hpp" #include "log/log.hpp" #include "d3d11_resource.hpp" #include "d3d11_device.hpp" @@ -281,6 +283,8 @@ class MTLD3D11SwapChain final : public MTLDXGISubObjectchangeGammaRamp(nullptr); + return S_OK; } @@ -601,6 +605,10 @@ class MTLD3D11SwapChain final : public MTLDXGISubObjectGetDXMTDevice().queue(); auto chunk = cmd_queue.CurrentChunk(); chunk->signal_frame_latency_fence_ = cmd_queue.CurrentFrameSeq(); + auto output = static_cast(target_.ptr()); + if (output) { + presenter->changeGammaRamp(output->GetGammaRamp()); + } if constexpr (EnableMetalFX) { chunk->emitcc([ this, vsync_duration, backbuffer = backbuffer_->texture(), diff --git a/src/dxgi/dxgi_output.cpp b/src/dxgi/dxgi_output.cpp index 18275389..e722fe3b 100644 --- a/src/dxgi/dxgi_output.cpp +++ b/src/dxgi/dxgi_output.cpp @@ -4,9 +4,10 @@ #include "com/com_guid.hpp" #include "com/com_pointer.hpp" #include "dxgi_interfaces.h" -#include "dxgi_object.hpp" +#include "dxgi_output.hpp" #include "dxgi_options.hpp" #include "dxmt_format.hpp" +#include "dxmt_presenter.hpp" #include "log/log.hpp" #include "util_string.hpp" #include "wsi_monitor.hpp" @@ -134,17 +135,28 @@ void FilterModesByDesc(std::vector &Modes, } } -class MTLDXGIOutput : public MTLDXGIObject { +class MTLDXGIOutputImpl : public MTLDXGIOutput { public: - MTLDXGIOutput(IMTLDXGIAdapter *adapter, HMONITOR monitor, DxgiOptions &options) + MTLDXGIOutputImpl(IMTLDXGIAdapter *adapter, HMONITOR monitor, DxgiOptions &options) : adapter_(adapter), monitor_(monitor), options_(options) { WMTGetDisplayDescription(monitor_ == wsi::getDefaultMonitor() ? WMTGetPrimaryDisplayId() : WMTGetSecondaryDisplayId(), &native_desc_); + for (uint32_t i = 0; i < DXMT_GAMMA_CP_COUNT; i++) { + gamma_ramp_.red[i] = gamma_ramp_.green[i] = gamma_ramp_.blue[i] = + float(i) / float(DXMT_GAMMA_CP_COUNT - 1); + } + gamma_ramp_.version = 0; } - ~MTLDXGIOutput() {} + ~MTLDXGIOutputImpl() {} + + DXMTGammaRamp *STDMETHODCALLTYPE GetGammaRamp() override { + if (gamma_ramp_.version == 0) + return nullptr; + return &gamma_ramp_; + } HRESULT STDMETHODCALLTYPE @@ -287,22 +299,42 @@ class MTLDXGIOutput : public MTLDXGIObject { gamma_caps->ScaleAndOffsetSupported = false; gamma_caps->MaxConvertedValue = 1.0f; gamma_caps->MinConvertedValue = 0.0f; - gamma_caps->NumGammaControlPoints = 1; + gamma_caps->NumGammaControlPoints = DXMT_GAMMA_CP_COUNT; + for (uint32_t i = 0; i < gamma_caps->NumGammaControlPoints; i++) + gamma_caps->ControlPointPositions[i] = float(i) / float(DXMT_GAMMA_CP_COUNT - 1); return S_OK; } HRESULT STDMETHODCALLTYPE SetGammaControl(const DXGI_GAMMA_CONTROL *gamma_control) final { - ERR("Not implemented"); - return E_NOTIMPL; + if (gamma_control == nullptr) + return E_NOTIMPL; + + for (uint32_t i = 0; i < DXMT_GAMMA_CP_COUNT; i++) { + gamma_ramp_.red[i] = gamma_control->GammaCurve[i].Red; + gamma_ramp_.green[i] = gamma_control->GammaCurve[i].Green; + gamma_ramp_.blue[i] = gamma_control->GammaCurve[i].Blue; + } + gamma_ramp_.version++; + return S_OK; } HRESULT STDMETHODCALLTYPE GetGammaControl(DXGI_GAMMA_CONTROL *gamma_control) final { - ERR("Not implemented"); - return E_NOTIMPL; + if (gamma_control == nullptr) + return E_NOTIMPL; + + gamma_control->Scale = { 1.0f, 1.0f, 1.0f }; + gamma_control->Offset = { 0.0f, 0.0f, 0.0f }; + for (uint32_t i = 0; i < DXMT_GAMMA_CP_COUNT; i++) { + gamma_control->GammaCurve[i].Red = gamma_ramp_.red[i]; + gamma_control->GammaCurve[i].Green = gamma_ramp_.green[i]; + gamma_control->GammaCurve[i].Blue = gamma_ramp_.blue[i]; + } + + return S_OK; } HRESULT @@ -609,7 +641,7 @@ class MTLDXGIOutput : public MTLDXGIObject { }; Com CreateOutput(IMTLDXGIAdapter *pAadapter, HMONITOR monitor, DxgiOptions &options) { - return Com::transfer(new MTLDXGIOutput(pAadapter, monitor, options)); + return Com::transfer(new MTLDXGIOutputImpl(pAadapter, monitor, options)); }; } // namespace dxmt \ No newline at end of file diff --git a/src/dxgi/dxgi_output.hpp b/src/dxgi/dxgi_output.hpp new file mode 100644 index 00000000..c907eaea --- /dev/null +++ b/src/dxgi/dxgi_output.hpp @@ -0,0 +1,13 @@ +#pragma once +#include "dxgi_interfaces.h" +#include "dxgi_object.hpp" +#include "dxmt_presenter.hpp" + +namespace dxmt { + +struct MTLDXGIOutput : public MTLDXGIObject { + DXMTGammaRamp gamma_ramp_; + virtual DXMTGammaRamp *STDMETHODCALLTYPE GetGammaRamp() = 0; +}; + +} // namespace dxmt diff --git a/src/dxmt/dxmt_command.metal b/src/dxmt/dxmt_command.metal index 206f907e..033e189c 100644 --- a/src/dxmt/dxmt_command.metal +++ b/src/dxmt/dxmt_command.metal @@ -280,6 +280,7 @@ constexpr constant uint kPresentFCIndex_BackbufferIsSRGB = 0x103; constexpr constant uint kPresentFCIndex_HDRPQ = 0x101; constexpr constant uint kPresentFCIndex_WithHDRMetadata = 0x102; constexpr constant uint kPresentFCIndex_BackbufferIsMS = 0x104; +constexpr constant uint kPresentFCIndex_GammaEnabled = 0x105; constant bool present_backbuffer_size_matched [[function_constant(kPresentFCIndex_BackbufferSizeMatched)]]; constant bool present_backbuffer_is_srgb [[function_constant(kPresentFCIndex_BackbufferIsSRGB)]]; @@ -287,8 +288,10 @@ constant bool present_hdr_pq [[function_constant(kPresentFCIndex_HDRPQ)]]; constant bool present_with_hdr_metadata [[function_constant(kPresentFCIndex_WithHDRMetadata)]]; constant bool present_backbuffer_is_ms [[function_constant(kPresentFCIndex_BackbufferIsMS)]]; constant bool present_backbuffer_is_not_ms = !present_backbuffer_is_ms; +constant bool present_gamma_enabled [[function_constant(kPresentFCIndex_GammaEnabled)]]; constexpr sampler s(coord::normalized); +constexpr sampler gamma_sampler(coord::normalized, filter::linear, address::clamp_to_edge); struct DXMTPresentMetadata { float edr_scale; @@ -304,6 +307,7 @@ float3 to_srgb(float3 linear) { present_data input [[stage_in]], texture2d source [[texture(0), function_constant(present_backbuffer_is_not_ms)]], texture2d_ms source_ms [[texture(0), function_constant(present_backbuffer_is_ms)]], + texture2d gamma_lut [[texture(1), function_constant(present_gamma_enabled)]], constant DXMTPresentMetadata& meta [[buffer(0)]] ) { float4 output = float4(0); @@ -319,6 +323,12 @@ float3 to_srgb(float3 linear) { : source.sample(s, input.uv); } float3 output_rgb = output.xyz; + if (present_gamma_enabled && !present_hdr_pq && !present_with_hdr_metadata) { + output_rgb = float3( + gamma_lut.sample(gamma_sampler, float2(output_rgb.r, 0.5)).r, + gamma_lut.sample(gamma_sampler, float2(output_rgb.g, 0.5)).g, + gamma_lut.sample(gamma_sampler, float2(output_rgb.b, 0.5)).b); + } float edr_scale = meta.edr_scale; if (present_backbuffer_is_srgb) output_rgb = to_srgb(output_rgb); diff --git a/src/dxmt/dxmt_presenter.cpp b/src/dxmt/dxmt_presenter.cpp index 1bd4fcef..37fb691b 100644 --- a/src/dxmt/dxmt_presenter.cpp +++ b/src/dxmt/dxmt_presenter.cpp @@ -19,6 +19,19 @@ Presenter::Presenter(WMT::Device device, WMT::MetalLayer layer, InternalCommandL layer_props_.display_sync_enabled = false; layer_props_.framebuffer_only = false; // how strangely setting it true results in worse performance layer_props_.contents_scale = layer_props_.contents_scale * scale_factor; + + WMTTextureInfo texture_info; + texture_info.type = WMTTextureType2D; + texture_info.pixel_format = WMTPixelFormatRGBA32Float; + texture_info.usage = WMTTextureUsageShaderRead; + texture_info.options = WMTResourceStorageModeShared; + texture_info.width = DXMT_GAMMA_CP_COUNT; + texture_info.height = 1; + texture_info.depth = 1; + texture_info.mipmap_level_count = 1; + texture_info.sample_count = 1; + texture_info.array_length = 1; + gamma_lut_texture_ = device.newTexture(texture_info); } bool @@ -64,6 +77,29 @@ Presenter::changeHDRMetadata(const WMTHDRMetadata *metadata) { } } +void +Presenter::changeGammaRamp(const DXMTGammaRamp *gamma_ramp) { + if (!gamma_ramp && gamma_version_) { + gamma_version_ = 0; + pso_valid.clear(); + } + if (gamma_ramp && gamma_version_ != gamma_ramp->version) { + gamma_version_ = gamma_ramp->version; + for (uint32_t i = 0; i < DXMT_GAMMA_CP_COUNT; i++) { + gamma_lut_rgba_[i * 4 + 0] = std::clamp(gamma_ramp->red[i], 0.f, 1.f); + gamma_lut_rgba_[i * 4 + 1] = std::clamp(gamma_ramp->green[i], 0.f, 1.f); + gamma_lut_rgba_[i * 4 + 2] = std::clamp(gamma_ramp->blue[i], 0.f, 1.f); + gamma_lut_rgba_[i * 4 + 3] = 1.0f; + } + gamma_lut_texture_.replaceRegion( + {0, 0, 0}, {DXMT_GAMMA_CP_COUNT, 1, 1}, 0, 0, + gamma_lut_rgba_.data(), + DXMT_GAMMA_CP_COUNT * sizeof(float) * 4, + 0); + pso_valid.clear(); + } +} + Presenter::PresentState Presenter::synchronizeLayerProperties() { uint64_t display_setting_version = 0; @@ -84,7 +120,7 @@ Presenter::synchronizeLayerProperties() { : nullptr; if (unlikely(!pso_valid.test_and_set())) { frame_presented_.wait(frame_requested_); - buildRenderPipelineState(final_colorspace == WMTColorSpaceHDR_PQ, is_hdr && hdr_metadata != nullptr, sample_count_ > 1); + buildRenderPipelineState(final_colorspace == WMTColorSpaceHDR_PQ, is_hdr && hdr_metadata != nullptr, sample_count_ > 1, gamma_version_ != 0); layer_.setProps(layer_props_); layer_.setColorSpace(final_colorspace); } @@ -128,6 +164,7 @@ Presenter::encodeCommands( if (fence) encoder.waitForFence(fence, WMTRenderStageFragment); encoder.setFragmentTexture(backbuffer, 0); + encoder.setFragmentTexture(gamma_lut_texture_, 1); double width = layer_props_.drawable_width; double height = layer_props_.drawable_height; @@ -151,15 +188,16 @@ constexpr uint32_t kPresentFCIndex_HDRPQ = 0x101; constexpr uint32_t kPresentFCIndex_WithHDRMetadata = 0x102; constexpr uint32_t kPresentFCIndex_BackbufferIsSRGB = 0x103; constexpr uint32_t kPresentFCIndex_BackbufferIsMS = 0x104; +constexpr uint32_t kPresentFCIndex_GammaEnabled = 0x105; void -Presenter::buildRenderPipelineState(bool is_pq, bool with_hdr_metadata, bool is_ms) { +Presenter::buildRenderPipelineState(bool is_pq, bool with_hdr_metadata, bool is_ms, bool gamma_enable) { auto pool = WMT::MakeAutoreleasePool(); auto library = lib_.getLibrary(); uint32_t true_data = true, false_data = false; - WMTFunctionConstant constants[5]; + WMTFunctionConstant constants[6]; constants[0].data.set(&true_data); constants[0].type = WMTDataTypeBool; constants[0].index = kPresentFCIndex_BackbufferSizeMatched; @@ -175,6 +213,9 @@ Presenter::buildRenderPipelineState(bool is_pq, bool with_hdr_metadata, bool is_ constants[4].data.set(is_ms ? &true_data : &false_data); constants[4].type = WMTDataTypeBool; constants[4].index = kPresentFCIndex_BackbufferIsMS; + constants[5].data.set(gamma_enable ? &true_data : &false_data); + constants[5].type = WMTDataTypeBool; + constants[5].index = kPresentFCIndex_GammaEnabled; WMT::Reference error; auto vs_present_quad = library.newFunction("vs_present_quad"); diff --git a/src/dxmt/dxmt_presenter.hpp b/src/dxmt/dxmt_presenter.hpp index 08171f56..6a864f62 100644 --- a/src/dxmt/dxmt_presenter.hpp +++ b/src/dxmt/dxmt_presenter.hpp @@ -14,6 +14,15 @@ struct DXMTPresentMetadata { float max_display_luminance; }; +constexpr uint32_t DXMT_GAMMA_CP_COUNT = 1024; + +struct DXMTGammaRamp { + float red[DXMT_GAMMA_CP_COUNT]; + float green[DXMT_GAMMA_CP_COUNT]; + float blue[DXMT_GAMMA_CP_COUNT]; + uint64_t version; +}; + class Presenter : public RcObject { public: Presenter( @@ -28,6 +37,8 @@ class Presenter : public RcObject { void changeHDRMetadata(const WMTHDRMetadata *metadata); + void changeGammaRamp(const DXMTGammaRamp *gamma_ramp); + class PresentState { public: DXMTPresentMetadata metadata; @@ -58,7 +69,7 @@ class Presenter : public RcObject { encodeCommands(WMT::CommandBuffer cmdbuf, WMT::Fence fence, WMT::Texture backbuffer, DXMTPresentMetadata metadata); private: - void buildRenderPipelineState(bool is_pq, bool with_hdr_metadata, bool is_ms); + void buildRenderPipelineState(bool is_pq, bool with_hdr_metadata, bool is_ms, bool gamma_enable); WMT::Device device_; WMT::MetalLayer layer_; @@ -73,6 +84,9 @@ class Presenter : public RcObject { WMTColorSpace display_colorspace_ = WMTColorSpaceSRGB; WMTHDRMetadata display_hdr_metadata_; WMTEDRValue display_edr_value_{0.0, 1.0}; + uint64_t gamma_version_ = 0; + std::array gamma_lut_rgba_; + WMT::Reference gamma_lut_texture_; WMT::Reference present_blit_; WMT::Reference present_scale_; std::atomic_flag pso_valid = 0;