video_out: HDR support (#2381)

* Initial HDR support

* fix for crashes when debug tools used
This commit is contained in:
psucien 2025-02-09 15:54:54 +01:00 committed by GitHub
parent fb0871dbc8
commit 8f2883a388
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 186 additions and 16 deletions

View file

@ -10,16 +10,23 @@ layout (binding = 0) uniform sampler2D texSampler;
layout(push_constant) uniform settings {
float gamma;
bool hdr;
} pp;
const float cutoff = 0.0031308, a = 1.055, b = 0.055, d = 12.92;
vec3 gamma(vec3 rgb)
{
return mix(a * pow(rgb, vec3(1.0 / (2.4 + 1.0 - pp.gamma))) - b, d * rgb / pp.gamma, lessThan(rgb, vec3(cutoff)));
vec3 gamma(vec3 rgb) {
return mix(
a * pow(rgb, vec3(1.0 / (2.4 + 1.0 - pp.gamma))) - b,
d * rgb / pp.gamma,
lessThan(rgb, vec3(cutoff))
);
}
void main()
{
void main() {
vec4 color_linear = texture(texSampler, uv);
color = vec4(gamma(color_linear.rgb), color_linear.a);
if (pp.hdr) {
color = color_linear;
} else {
color = vec4(gamma(color_linear.rgb), color_linear.a);
}
}

View file

@ -160,6 +160,10 @@ std::vector<const char*> GetInstanceExtensions(Frontend::WindowSystemType window
extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
}
if (Config::allowHDR()) {
extensions.push_back(VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME);
}
if (enable_debug_utils) {
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}

View file

@ -397,6 +397,7 @@ void Presenter::RecreateFrame(Frame* frame, u32 width, u32 height) {
frame->height = height;
frame->imgui_texture = ImGui::Vulkan::AddTexture(view, vk::ImageLayout::eShaderReadOnlyOptimal);
frame->is_hdr = swapchain.GetHDR();
}
Frame* Presenter::PrepareLastFrame() {
@ -562,7 +563,8 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop)
if (image_id != VideoCore::NULL_IMAGE_ID) {
const auto& image = texture_cache.GetImage(image_id);
const auto extent = image.info.size;
if (frame->width != extent.width || frame->height != extent.height) {
if (frame->width != extent.width || frame->height != extent.height ||
frame->is_hdr != swapchain.GetHDR()) {
RecreateFrame(frame, extent.width, extent.height);
}
}
@ -913,7 +915,7 @@ Frame* Presenter::GetRenderFrame() {
}
// Initialize default frame image
if (frame->width == 0 || frame->height == 0) {
if (frame->width == 0 || frame->height == 0 || frame->is_hdr != swapchain.GetHDR()) {
RecreateFrame(frame, 1920, 1080);
}

View file

@ -31,6 +31,7 @@ struct Frame {
vk::Fence present_done;
vk::Semaphore ready_semaphore;
u64 ready_tick;
bool is_hdr{false};
ImTextureID imgui_texture;
};
@ -46,6 +47,7 @@ class Rasterizer;
class Presenter {
struct PostProcessSettings {
float gamma = 1.0f;
bool hdr = false;
};
public:
@ -102,6 +104,18 @@ public:
return *rasterizer.get();
}
bool IsHDRSupported() const {
return swapchain.HasHDR();
}
void SetHDR(bool enable) {
if (!IsHDRSupported()) {
return;
}
swapchain.SetHDR(enable);
pp_settings.hdr = enable;
}
private:
void CreatePostProcessPipeline();
Frame* PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop = true);

View file

@ -4,6 +4,7 @@
#include <algorithm>
#include <limits>
#include "common/assert.h"
#include "common/config.h"
#include "common/logging/log.h"
#include "imgui/renderer/imgui_core.h"
#include "sdl_window.h"
@ -12,8 +13,13 @@
namespace Vulkan {
Swapchain::Swapchain(const Instance& instance_, const Frontend::WindowSDL& window)
: instance{instance_}, surface{CreateSurface(instance.GetInstance(), window)} {
static constexpr vk::SurfaceFormatKHR SURFACE_FORMAT_HDR = {
.format = vk::Format::eA2B10G10R10UnormPack32,
.colorSpace = vk::ColorSpaceKHR::eHdr10St2084EXT,
};
Swapchain::Swapchain(const Instance& instance_, const Frontend::WindowSDL& window_)
: instance{instance_}, window{window_}, surface{CreateSurface(instance.GetInstance(), window)} {
FindPresentFormat();
Create(window.GetWidth(), window.GetHeight());
@ -57,11 +63,12 @@ void Swapchain::Create(u32 width_, u32 height_) {
const u32 queue_family_indices_count = exclusive ? 1u : 2u;
const vk::SharingMode sharing_mode =
exclusive ? vk::SharingMode::eExclusive : vk::SharingMode::eConcurrent;
const auto format = needs_hdr ? SURFACE_FORMAT_HDR : surface_format;
const vk::SwapchainCreateInfoKHR swapchain_info = {
.surface = surface,
.minImageCount = image_count,
.imageFormat = surface_format.format,
.imageColorSpace = surface_format.colorSpace,
.imageFormat = format.format,
.imageColorSpace = format.colorSpace,
.imageExtent = extent,
.imageArrayLayers = 1,
.imageUsage = vk::ImageUsageFlagBits::eColorAttachment |
@ -86,10 +93,28 @@ void Swapchain::Create(u32 width_, u32 height_) {
}
void Swapchain::Recreate(u32 width_, u32 height_) {
LOG_DEBUG(Render_Vulkan, "Recreate the swapchain: width={} height={}", width_, height_);
LOG_DEBUG(Render_Vulkan, "Recreate the swapchain: width={} height={} HDR={}", width_, height_,
needs_hdr);
Create(width_, height_);
}
void Swapchain::SetHDR(bool hdr) {
if (needs_hdr == hdr) {
return;
}
auto result = instance.GetDevice().waitIdle();
if (result != vk::Result::eSuccess) {
LOG_WARNING(ImGui, "Failed to wait for Vulkan device idle on mode change: {}",
vk::to_string(result));
}
needs_hdr = hdr;
Recreate(width, height);
ImGui::Core::OnSurfaceFormatChange(needs_hdr ? SURFACE_FORMAT_HDR.format
: surface_format.format);
}
bool Swapchain::AcquireNextImage() {
vk::Device device = instance.GetDevice();
vk::Result result =
@ -144,6 +169,16 @@ void Swapchain::FindPresentFormat() {
ASSERT_MSG(formats_result == vk::Result::eSuccess, "Failed to query surface formats: {}",
vk::to_string(formats_result));
// Check if the device supports HDR formats. Here we care of Rec.2020 PQ only as it is expected
// game output. Other variants as e.g. linear Rec.2020 will require additional color space
// rotation
supports_hdr =
std::find_if(formats.begin(), formats.end(), [](const vk::SurfaceFormatKHR& format) {
return format == SURFACE_FORMAT_HDR;
}) != formats.end();
// Also make sure that user allowed us to use HDR
supports_hdr &= Config::allowHDR();
// If there is a single undefined surface format, the device doesn't care, so we'll just use
// RGBA sRGB.
if (formats[0].format == vk::Format::eUndefined) {
@ -262,7 +297,7 @@ void Swapchain::SetupImages() {
auto [im_view_result, im_view] = device.createImageView(vk::ImageViewCreateInfo{
.image = images[i],
.viewType = vk::ImageViewType::e2D,
.format = surface_format.format,
.format = needs_hdr ? SURFACE_FORMAT_HDR.format : surface_format.format,
.subresourceRange =
{
.aspectMask = vk::ImageAspectFlagBits::eColor,

View file

@ -82,6 +82,16 @@ public:
return present_ready[image_index];
}
bool HasHDR() const {
return supports_hdr;
}
void SetHDR(bool hdr);
bool GetHDR() const {
return needs_hdr;
}
private:
/// Selects the best available swapchain image format
void FindPresentFormat();
@ -100,6 +110,7 @@ private:
private:
const Instance& instance;
const Frontend::WindowSDL& window;
vk::SwapchainKHR swapchain{};
vk::SurfaceKHR surface{};
vk::SurfaceFormatKHR surface_format;
@ -117,6 +128,8 @@ private:
u32 image_index = 0;
u32 frame_index = 0;
bool needs_recreation = true;
bool needs_hdr = false; // The game requested HDR swapchain
bool supports_hdr = false; // SC supports HDR output
};
} // namespace Vulkan

View file

@ -22,6 +22,7 @@ static vk::Format ConvertPixelFormat(const VideoOutFormat format) {
return vk::Format::eR8G8B8A8Srgb;
case VideoOutFormat::A2R10G10B10:
case VideoOutFormat::A2R10G10B10Srgb:
case VideoOutFormat::A2R10G10B10Bt2020Pq:
return vk::Format::eA2R10G10B10UnormPack32;
default:
break;