mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-22 11:56:58 +02:00
add hdr display support
This commit is contained in:
+9
-10
@@ -303,28 +303,27 @@ list(APPEND kyty_emulator_src "${fault_buffer_shader_header}")
|
|||||||
list(APPEND kyty_emulator_src ${gpu_tiler_shader_headers})
|
list(APPEND kyty_emulator_src ${gpu_tiler_shader_headers})
|
||||||
|
|
||||||
set(gpu_blit_generated_dir "${PROJECT_BINARY_DIR}/gpu_blit_shaders")
|
set(gpu_blit_generated_dir "${PROJECT_BINARY_DIR}/gpu_blit_shaders")
|
||||||
set(gpu_blit_shader_sources
|
function(add_gpu_blit_shader shader_name shader_file)
|
||||||
"${gpu_tiler_shader_dir}/gpu_blit_fs_triangle.vert"
|
set(shader_source "${gpu_tiler_shader_dir}/${shader_file}")
|
||||||
"${gpu_tiler_shader_dir}/gpu_blit_color_to_ms_depth.frag"
|
|
||||||
"${gpu_tiler_shader_dir}/gpu_video_out_overlay.frag"
|
|
||||||
)
|
|
||||||
foreach(shader_source IN LISTS gpu_blit_shader_sources)
|
|
||||||
get_filename_component(shader_name "${shader_source}" NAME_WE)
|
|
||||||
set(shader_spv "${gpu_blit_generated_dir}/${shader_name}.spv")
|
set(shader_spv "${gpu_blit_generated_dir}/${shader_name}.spv")
|
||||||
set(shader_header "${gpu_blit_generated_dir}/${shader_name}_spv.h")
|
set(shader_header "${gpu_blit_generated_dir}/${shader_name}_spv.h")
|
||||||
string(TOUPPER "${shader_name}_SPV" shader_symbol)
|
string(TOUPPER "${shader_name}_SPV" shader_symbol)
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
OUTPUT "${shader_header}"
|
OUTPUT "${shader_header}"
|
||||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_blit_generated_dir}"
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${gpu_blit_generated_dir}"
|
||||||
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.3 -Os
|
COMMAND "${KYTY_GLSLANG_VALIDATOR}" -V --target-env vulkan1.3 -Os ${ARGN}
|
||||||
"-I${gpu_tiler_shader_dir}" -o "${shader_spv}" "${shader_source}"
|
"-I${gpu_tiler_shader_dir}" -o "${shader_spv}" "${shader_source}"
|
||||||
COMMAND ${CMAKE_COMMAND} -DINPUT=${shader_spv} -DOUTPUT=${shader_header}
|
COMMAND ${CMAKE_COMMAND} -DINPUT=${shader_spv} -DOUTPUT=${shader_header}
|
||||||
-DSYMBOL=${shader_symbol} -P "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
|
-DSYMBOL=${shader_symbol} -P "${KYTY_SOURCE_DIR}/embed_spirv.cmake"
|
||||||
DEPENDS "${shader_source}"
|
DEPENDS "${shader_source}"
|
||||||
VERBATIM
|
VERBATIM
|
||||||
)
|
)
|
||||||
list(APPEND gpu_blit_shader_headers "${shader_header}")
|
set(gpu_blit_shader_headers ${gpu_blit_shader_headers} "${shader_header}" PARENT_SCOPE)
|
||||||
endforeach()
|
endfunction()
|
||||||
|
add_gpu_blit_shader(gpu_blit_fs_triangle gpu_blit_fs_triangle.vert)
|
||||||
|
add_gpu_blit_shader(gpu_blit_color_to_ms_depth gpu_blit_color_to_ms_depth.frag)
|
||||||
|
add_gpu_blit_shader(gpu_video_out_overlay gpu_video_out_overlay.frag)
|
||||||
|
add_gpu_blit_shader(gpu_imgui_hdr gpu_video_out_overlay.frag -DIMGUI_HDR=1)
|
||||||
list(APPEND kyty_emulator_src ${gpu_blit_shader_headers})
|
list(APPEND kyty_emulator_src ${gpu_blit_shader_headers})
|
||||||
|
|
||||||
list(APPEND kyty_emulator_src
|
list(APPEND kyty_emulator_src
|
||||||
|
|||||||
@@ -72,6 +72,10 @@ bool FullscreenEnabled() {
|
|||||||
return g_config->fullscreen_enabled;
|
return g_config->fullscreen_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool HdrEnabled() {
|
||||||
|
return g_config->hdr_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
bool HideCursorEnabled() {
|
bool HideCursorEnabled() {
|
||||||
return g_config->hide_cursor_enabled;
|
return g_config->hide_cursor_enabled;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ struct ConfigOptions {
|
|||||||
PresentMode present_mode = PresentMode::Mailbox;
|
PresentMode present_mode = PresentMode::Mailbox;
|
||||||
int32_t gpu_index = -1;
|
int32_t gpu_index = -1;
|
||||||
bool fullscreen_enabled = false;
|
bool fullscreen_enabled = false;
|
||||||
|
bool hdr_enabled = false;
|
||||||
bool hide_cursor_enabled = false;
|
bool hide_cursor_enabled = false;
|
||||||
bool splash_enabled = true;
|
bool splash_enabled = true;
|
||||||
bool vr_enabled = false;
|
bool vr_enabled = false;
|
||||||
@@ -100,6 +101,7 @@ uint32_t GetControllerVibrationIntensity();
|
|||||||
PresentMode GetPresentMode();
|
PresentMode GetPresentMode();
|
||||||
int32_t GetGpuIndex();
|
int32_t GetGpuIndex();
|
||||||
bool FullscreenEnabled();
|
bool FullscreenEnabled();
|
||||||
|
bool HdrEnabled();
|
||||||
bool HideCursorEnabled();
|
bool HideCursorEnabled();
|
||||||
bool VrEnabled();
|
bool VrEnabled();
|
||||||
bool AmdCpuEnabled();
|
bool AmdCpuEnabled();
|
||||||
|
|||||||
@@ -362,8 +362,17 @@ struct VideoOutFormatPolicy {
|
|||||||
};
|
};
|
||||||
|
|
||||||
inline constexpr uint64_t VIDEO_OUT_PIXEL_FORMAT_R10_G10_B10_A2_BT2100_PQ = 0x8100070422000000ull;
|
inline constexpr uint64_t VIDEO_OUT_PIXEL_FORMAT_R10_G10_B10_A2_BT2100_PQ = 0x8100070422000000ull;
|
||||||
|
inline constexpr uint64_t VIDEO_OUT_PIXEL_FORMAT_B10_G10_R10_A2_BT2100_PQ = 0x8100070400000000ull;
|
||||||
|
|
||||||
inline constexpr std::array<VideoOutFormatPolicy, 7> VIDEO_OUT_FORMAT_POLICIES {{
|
[[nodiscard]] constexpr bool IsHdrVideoOutPixelFormat(uint64_t format) {
|
||||||
|
switch (format) {
|
||||||
|
case VIDEO_OUT_PIXEL_FORMAT_R10_G10_B10_A2_BT2100_PQ:
|
||||||
|
case VIDEO_OUT_PIXEL_FORMAT_B10_G10_R10_A2_BT2100_PQ: return true;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr std::array<VideoOutFormatPolicy, 8> VIDEO_OUT_FORMAT_POLICIES {{
|
||||||
{0x8000000022000000ull,
|
{0x8000000022000000ull,
|
||||||
{vk::Format::eR8G8B8A8Srgb, Prospero::BufferFormat::k8_8_8_8Srgb, 4, false}},
|
{vk::Format::eR8G8B8A8Srgb, Prospero::BufferFormat::k8_8_8_8Srgb, 4, false}},
|
||||||
{0x8000000000000000ull,
|
{0x8000000000000000ull,
|
||||||
@@ -374,6 +383,8 @@ inline constexpr std::array<VideoOutFormatPolicy, 7> VIDEO_OUT_FORMAT_POLICIES {
|
|||||||
{vk::Format::eA2R10G10B10UnormPack32, Prospero::BufferFormat::k10_10_10_2UNorm, 4, false}},
|
{vk::Format::eA2R10G10B10UnormPack32, Prospero::BufferFormat::k10_10_10_2UNorm, 4, false}},
|
||||||
{VIDEO_OUT_PIXEL_FORMAT_R10_G10_B10_A2_BT2100_PQ,
|
{VIDEO_OUT_PIXEL_FORMAT_R10_G10_B10_A2_BT2100_PQ,
|
||||||
{vk::Format::eA2B10G10R10UnormPack32, Prospero::BufferFormat::k10_10_10_2UNorm, 4, false}},
|
{vk::Format::eA2B10G10R10UnormPack32, Prospero::BufferFormat::k10_10_10_2UNorm, 4, false}},
|
||||||
|
{VIDEO_OUT_PIXEL_FORMAT_B10_G10_R10_A2_BT2100_PQ,
|
||||||
|
{vk::Format::eA2R10G10B10UnormPack32, Prospero::BufferFormat::k10_10_10_2UNorm, 4, false}},
|
||||||
{0xc001000622000000ull,
|
{0xc001000622000000ull,
|
||||||
{vk::Format::eR16G16B16A16Sfloat, Prospero::BufferFormat::k16_16_16_16Float, 8, false}},
|
{vk::Format::eR16G16B16A16Sfloat, Prospero::BufferFormat::k16_16_16_16Float, 8, false}},
|
||||||
{0xc001000600000000ull,
|
{0xc001000600000000ull,
|
||||||
|
|||||||
@@ -1,15 +1,53 @@
|
|||||||
#version 450
|
#version 450
|
||||||
|
|
||||||
layout(location = 0) in vec2 uv;
|
|
||||||
layout(location = 0) out vec4 color;
|
layout(location = 0) out vec4 color;
|
||||||
|
|
||||||
|
vec3 SdrToHdr10(vec3 rgb, bool source_linear) {
|
||||||
|
rgb = clamp(rgb, 0.0, 1.0);
|
||||||
|
vec3 linear = source_linear ? rgb :
|
||||||
|
mix(rgb / 12.92, pow((rgb + 0.055) / 1.055, vec3(2.4)),
|
||||||
|
greaterThan(rgb, vec3(0.04045)));
|
||||||
|
// Convert primaries and scale 250-nit reference white relative to 10,000 nits.
|
||||||
|
const mat3 to_rec2020 = (250.0 / 10000.0) *
|
||||||
|
mat3(0.627404, 0.069097, 0.016391,
|
||||||
|
0.329283, 0.919540, 0.088013,
|
||||||
|
0.043313, 0.011362, 0.895595);
|
||||||
|
vec3 powered = pow(to_rec2020 * linear, vec3(2610.0 / 16384.0));
|
||||||
|
return pow((3424.0 / 4096.0 + (2413.0 / 128.0) * powered) /
|
||||||
|
(1.0 + (2392.0 / 128.0) * powered), vec3(2523.0 / 32.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef IMGUI_HDR
|
||||||
|
layout(set = 0, binding = 0) uniform texture2D image;
|
||||||
|
layout(set = 1, binding = 0) uniform sampler image_sampler;
|
||||||
|
layout(location = 0) in struct { vec4 Color; vec2 UV; } In;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 sampled = In.Color * texture(sampler2D(image, image_sampler), In.UV);
|
||||||
|
color = vec4(SdrToHdr10(sampled.rgb, false), sampled.a);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
layout(location = 0) in vec2 uv;
|
||||||
layout(binding = 0) uniform sampler2D source;
|
layout(binding = 0) uniform sampler2D source;
|
||||||
layout(push_constant) uniform AlphaControl {
|
layout(push_constant) uniform AlphaControl {
|
||||||
uint premultiplied;
|
uint flags; // bit 0: premultiplied, bit 1: HDR target, bit 2: PQ source, bit 3: linear source
|
||||||
};
|
};
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
color = texture(source, uv);
|
const uint HDR_TARGET = 2u;
|
||||||
if (premultiplied == 0u) {
|
const uint PQ_SOURCE = 4u;
|
||||||
color.rgb *= color.a;
|
vec4 sampled = texture(source, uv);
|
||||||
|
bool premultiplied = (flags & 1u) != 0u;
|
||||||
|
bool convert_to_hdr = (flags & (HDR_TARGET | PQ_SOURCE)) == HDR_TARGET;
|
||||||
|
if (convert_to_hdr && premultiplied) {
|
||||||
|
sampled.rgb = sampled.a > 0.0 ? sampled.rgb / sampled.a : vec3(0.0);
|
||||||
}
|
}
|
||||||
|
if (convert_to_hdr) {
|
||||||
|
sampled.rgb = SdrToHdr10(sampled.rgb, (flags & 8u) != 0u);
|
||||||
|
}
|
||||||
|
if (!premultiplied || convert_to_hdr) {
|
||||||
|
sampled.rgb *= sampled.a;
|
||||||
|
}
|
||||||
|
color = sampled;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -28,6 +28,14 @@ vk::Format VulkanFormat(Prospero::BufferFormat guest_format);
|
|||||||
void RequireVulkanSuccess(vk::Result result, const char* operation);
|
void RequireVulkanSuccess(vk::Result result, const char* operation);
|
||||||
vk::ShaderModule CompileSPV(std::span<const uint32_t> code, vk::Device device);
|
vk::ShaderModule CompileSPV(std::span<const uint32_t> code, vk::Device device);
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr bool IsHdrSwapchainFormat(vk::Format format) {
|
||||||
|
switch (format) {
|
||||||
|
case vk::Format::eA2B10G10R10UnormPack32:
|
||||||
|
case vk::Format::eA2R10G10B10UnormPack32: return true;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template <typename Handle, typename... Args>
|
template <typename Handle, typename... Args>
|
||||||
void SetVulkanObjectNameF(vk::Device device, Handle handle, fmt::format_string<Args...> format,
|
void SetVulkanObjectNameF(vk::Device device, Handle handle, fmt::format_string<Args...> format,
|
||||||
Args&&... args) {
|
Args&&... args) {
|
||||||
|
|||||||
@@ -26,11 +26,13 @@ public:
|
|||||||
~Presenter();
|
~Presenter();
|
||||||
KYTY_CLASS_NO_COPY(Presenter);
|
KYTY_CLASS_NO_COPY(Presenter);
|
||||||
|
|
||||||
[[nodiscard]] Frame& PrepareFrame(CommandBuffer& command, const ImageInfo& info);
|
[[nodiscard]] Frame& PrepareFrame(CommandBuffer& command, const ImageInfo& info,
|
||||||
|
bool hdr = false);
|
||||||
[[nodiscard]] Frame& PrepareBlankFrame(uint32_t width, uint32_t height, bool opaque,
|
[[nodiscard]] Frame& PrepareBlankFrame(uint32_t width, uint32_t height, bool opaque,
|
||||||
CommandBuffer* producer = nullptr);
|
CommandBuffer* producer = nullptr);
|
||||||
[[nodiscard]] bool PresentLastFrame();
|
[[nodiscard]] bool PresentLastFrame();
|
||||||
[[nodiscard]] bool IsGuestPaused() const noexcept;
|
[[nodiscard]] bool IsGuestPaused() const noexcept;
|
||||||
|
[[nodiscard]] bool IsHdrSupported() const noexcept;
|
||||||
[[nodiscard]] bool NeedsSystemOverlayRefresh() const noexcept;
|
[[nodiscard]] bool NeedsSystemOverlayRefresh() const noexcept;
|
||||||
[[nodiscard]] RenderContext& Renderer() const noexcept;
|
[[nodiscard]] RenderContext& Renderer() const noexcept;
|
||||||
void Present(Frame& frame);
|
void Present(Frame& frame);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "common/file.h"
|
#include "common/file.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/stringUtils.h"
|
#include "common/stringUtils.h"
|
||||||
|
#include "gpu_blit_shaders/gpu_imgui_hdr_spv.h"
|
||||||
#include "graphics/host_gpu/graphicContext.h"
|
#include "graphics/host_gpu/graphicContext.h"
|
||||||
#include "graphics/presentation/ddsImage.h"
|
#include "graphics/presentation/ddsImage.h"
|
||||||
#include "imgui.h"
|
#include "imgui.h"
|
||||||
@@ -890,6 +891,11 @@ struct SystemOverlay::Impl {
|
|||||||
info.PipelineInfoMain.PipelineRenderingCreateInfo.colorAttachmentCount = 1;
|
info.PipelineInfoMain.PipelineRenderingCreateInfo.colorAttachmentCount = 1;
|
||||||
info.PipelineInfoMain.PipelineRenderingCreateInfo.pColorAttachmentFormats = &color_format;
|
info.PipelineInfoMain.PipelineRenderingCreateInfo.pColorAttachmentFormats = &color_format;
|
||||||
info.CheckVkResultFn = CheckVulkanResult;
|
info.CheckVkResultFn = CheckVulkanResult;
|
||||||
|
if (IsHdrSwapchainFormat(format)) {
|
||||||
|
info.CustomShaderFragCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
||||||
|
info.CustomShaderFragCreateInfo.codeSize = sizeof(GPU_IMGUI_HDR_SPV);
|
||||||
|
info.CustomShaderFragCreateInfo.pCode = GPU_IMGUI_HDR_SPV;
|
||||||
|
}
|
||||||
EXIT_IF(!ImGui_ImplVulkan_Init(&info));
|
EXIT_IF(!ImGui_ImplVulkan_Init(&info));
|
||||||
vulkan_initialized = true;
|
vulkan_initialized = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,6 +272,7 @@ public:
|
|||||||
void Init(uint32_t width, uint32_t height);
|
void Init(uint32_t width, uint32_t height);
|
||||||
FlipQueue& GetFlipQueue() { return m_flip_queue; }
|
FlipQueue& GetFlipQueue() { return m_flip_queue; }
|
||||||
Graphics::RenderContext& Renderer() const noexcept { return m_renderer; }
|
Graphics::RenderContext& Renderer() const noexcept { return m_renderer; }
|
||||||
|
bool IsHdrSupported() const noexcept { return m_presenter.IsHdrSupported(); }
|
||||||
|
|
||||||
void VblankBegin();
|
void VblankBegin();
|
||||||
void VblankEnd();
|
void VblankEnd();
|
||||||
@@ -558,14 +559,6 @@ Graphics::ImageInfo BufferAttributeGroup::ImageInfo(const VideoOutBuffer& buffer
|
|||||||
if (!Graphics::DecodeVideoOutPixelFormat(attribute.pixel_format, pixel_format)) {
|
if (!Graphics::DecodeVideoOutPixelFormat(attribute.pixel_format, pixel_format)) {
|
||||||
EXIT("unsupported video-out pixel format: 0x%016" PRIx64 "\n", attribute.pixel_format);
|
EXIT("unsupported video-out pixel format: 0x%016" PRIx64 "\n", attribute.pixel_format);
|
||||||
}
|
}
|
||||||
if (attribute.pixel_format == Graphics::VIDEO_OUT_PIXEL_FORMAT_R10_G10_B10_A2_BT2100_PQ) {
|
|
||||||
static std::atomic_flag warned = ATOMIC_FLAG_INIT;
|
|
||||||
if (!warned.test_and_set(std::memory_order_relaxed)) {
|
|
||||||
Log::WriteToConsoleAndLog(fmt::format(
|
|
||||||
"Warning: HDR-to-SDR conversion is not implemented; displaying PQ video-out "
|
|
||||||
"pixels unchanged (format=0x{:016x}).\n", attribute.pixel_format));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const auto tile_mode = Graphics::Prospero::TileMode::kRenderTarget;
|
const auto tile_mode = Graphics::Prospero::TileMode::kRenderTarget;
|
||||||
Graphics::ImageInfo info {};
|
Graphics::ImageInfo info {};
|
||||||
info.data = {buffer.data_address, 0};
|
info.data = {buffer.data_address, 0};
|
||||||
@@ -1071,31 +1064,31 @@ void FlipQueue::Prepare(uint64_t request_id, Graphics::CommandBuffer& buffer) {
|
|||||||
uint32_t height = 0;
|
uint32_t height = 0;
|
||||||
bool current = false;
|
bool current = false;
|
||||||
bool premultiplied_alpha = false;
|
bool premultiplied_alpha = false;
|
||||||
|
bool hdr = false;
|
||||||
{
|
{
|
||||||
Common::LockGuard lock(cfg->mutex);
|
Common::LockGuard lock(cfg->mutex);
|
||||||
current = cfg->opened && !cfg->closing && cfg->generation == generation;
|
current = cfg->opened && !cfg->closing && cfg->generation == generation;
|
||||||
if (current) {
|
if (current && special) {
|
||||||
if (special) {
|
width = cfg->width;
|
||||||
width = cfg->width;
|
height = cfg->height;
|
||||||
height = cfg->height;
|
} else if (current) {
|
||||||
} else {
|
const auto& surface = cfg->buffers[index];
|
||||||
const auto& surface = cfg->buffers[index];
|
if (!surface.Occupied()) {
|
||||||
if (!surface.Occupied()) {
|
EXIT("cannot prepare flip from an unregistered surface, id=%" PRIu64
|
||||||
EXIT("cannot prepare flip from an unregistered surface, id=%" PRIu64
|
" index=%d\n",
|
||||||
" index=%d\n",
|
request_id, index);
|
||||||
request_id, index);
|
|
||||||
}
|
|
||||||
if (surface.group_index >= VIDEO_OUT_BUFFER_ATTRIBUTE_NUM_MAX ||
|
|
||||||
!cfg->groups[surface.group_index].occupied) {
|
|
||||||
EXIT("video-out surface references an unavailable attribute group, id=%" PRIu64
|
|
||||||
" index=%d group=%d\n",
|
|
||||||
request_id, index, surface.group_index);
|
|
||||||
}
|
|
||||||
source_info = cfg->groups[surface.group_index].ImageInfo(surface);
|
|
||||||
premultiplied_alpha =
|
|
||||||
(cfg->groups[surface.group_index].attribute.option &
|
|
||||||
VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_ALPHA_PREMULTIPLIED) != 0;
|
|
||||||
}
|
}
|
||||||
|
if (surface.group_index >= VIDEO_OUT_BUFFER_ATTRIBUTE_NUM_MAX ||
|
||||||
|
!cfg->groups[surface.group_index].occupied) {
|
||||||
|
EXIT("video-out surface references an unavailable attribute group, id=%" PRIu64
|
||||||
|
" index=%d group=%d\n",
|
||||||
|
request_id, index, surface.group_index);
|
||||||
|
}
|
||||||
|
const auto& group = cfg->groups[surface.group_index];
|
||||||
|
source_info = group.ImageInfo(surface);
|
||||||
|
hdr = Graphics::IsHdrVideoOutPixelFormat(group.attribute.pixel_format);
|
||||||
|
premultiplied_alpha = (group.attribute.option &
|
||||||
|
VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_ALPHA_PREMULTIPLIED) != 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!current) {
|
if (!current) {
|
||||||
@@ -1108,12 +1101,18 @@ void FlipQueue::Prepare(uint64_t request_id, Graphics::CommandBuffer& buffer) {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
static std::atomic_flag warned = ATOMIC_FLAG_INIT;
|
||||||
|
if (hdr && !m_presenter.IsHdrSupported() && !warned.test_and_set(std::memory_order_relaxed)) {
|
||||||
|
Log::WriteToConsoleAndLog(
|
||||||
|
"Warning: HDR output is unavailable; HDR-to-SDR conversion is not implemented, "
|
||||||
|
"displaying PQ video-out pixels unchanged.\n");
|
||||||
|
}
|
||||||
Graphics::Presenter::Frame* frame = nullptr;
|
Graphics::Presenter::Frame* frame = nullptr;
|
||||||
if (special) {
|
if (special) {
|
||||||
frame = &m_presenter.PrepareBlankFrame(width, height, index == VIDEO_OUT_BUFFER_INDEX_BLACK,
|
frame = &m_presenter.PrepareBlankFrame(width, height, index == VIDEO_OUT_BUFFER_INDEX_BLACK,
|
||||||
&buffer);
|
&buffer);
|
||||||
} else {
|
} else {
|
||||||
frame = &m_presenter.PrepareFrame(buffer, source_info);
|
frame = &m_presenter.PrepareFrame(buffer, source_info, hdr);
|
||||||
}
|
}
|
||||||
|
|
||||||
Common::LockGuard lock(m_mutex);
|
Common::LockGuard lock(m_mutex);
|
||||||
@@ -1790,11 +1789,14 @@ KYTY_SYSV_ABI int VideoOutGetOutputStatus(int handle, VideoOutOutputStatus* stat
|
|||||||
|
|
||||||
int32_t attribute3 = 0;
|
int32_t attribute3 = 0;
|
||||||
Loader::SystemContentParamSfoGetInt("ATTRIBUTE3", &attribute3);
|
Loader::SystemContentParamSfoGetInt("ATTRIBUTE3", &attribute3);
|
||||||
|
int32_t attribute = 0;
|
||||||
|
const bool hdr_allowed = !Loader::SystemContentParamSfoGetInt("ATTRIBUTE", &attribute) ||
|
||||||
|
(attribute & 0x20000000) != 0;
|
||||||
ctx->mutex.Lock();
|
ctx->mutex.Lock();
|
||||||
// Primary output reports 4K unless param.json Video-out Info enables resolution detection.
|
// Primary output reports 4K unless param.json Video-out Info enables resolution detection.
|
||||||
status->resolution =
|
status->resolution =
|
||||||
((attribute3 & 4) != 0 && ctx->width < 3840 && ctx->height < 2160 ? 1u : 2u);
|
((attribute3 & 4) != 0 && ctx->width < 3840 && ctx->height < 2160 ? 1u : 2u);
|
||||||
status->dynamicRange = 1;
|
status->dynamicRange = hdr_allowed && DriverState().IsHdrSupported() ? 2u : 1u;
|
||||||
status->refreshRate =
|
status->refreshRate =
|
||||||
(ctx->output_mode == VIDEO_OUT_OUTPUT_MODE_119_88HZ || Config::GetVblankFrequency() >= 119
|
(ctx->output_mode == VIDEO_OUT_OUTPUT_MODE_119_88HZ || Config::GetVblankFrequency() >= 119
|
||||||
? VIDEO_OUT_REFRESH_RATE_119_88HZ
|
? VIDEO_OUT_REFRESH_RATE_119_88HZ
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <atomic>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -31,6 +32,8 @@ struct Presenter::Frame {
|
|||||||
vk::ImageView view = nullptr;
|
vk::ImageView view = nullptr;
|
||||||
uint64_t present_tick = 0;
|
uint64_t present_tick = 0;
|
||||||
bool busy = false;
|
bool busy = false;
|
||||||
|
bool hdr = false;
|
||||||
|
bool blank = false;
|
||||||
|
|
||||||
void Configure(GraphicContext& graphics, vk::Extent2D extent, vk::Format format);
|
void Configure(GraphicContext& graphics, vk::Extent2D extent, vk::Format format);
|
||||||
void Transit(vk::CommandBuffer command, vk::ImageLayout layout, vk::AccessFlags2 access);
|
void Transit(vk::CommandBuffer command, vk::ImageLayout layout, vk::AccessFlags2 access);
|
||||||
@@ -274,14 +277,24 @@ public:
|
|||||||
return static_cast<uint32_t>(m_images.size());
|
return static_cast<uint32_t>(m_images.size());
|
||||||
}
|
}
|
||||||
[[nodiscard]] vk::Format Format() const noexcept { return m_format; }
|
[[nodiscard]] vk::Format Format() const noexcept { return m_format; }
|
||||||
|
[[nodiscard]] bool IsHdrSupported() const noexcept { return m_hdr_supported.load(); }
|
||||||
|
bool SetHdr(bool hdr) {
|
||||||
|
const bool changed = m_hdr_active != (hdr && IsHdrSupported());
|
||||||
|
m_hdr_requested = hdr;
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
vk::SurfaceFormatKHR SelectFormat();
|
||||||
void Destroy();
|
void Destroy();
|
||||||
void DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& layer);
|
void DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& layer);
|
||||||
|
|
||||||
WindowContext& m_window;
|
WindowContext& m_window;
|
||||||
vk::SwapchainKHR m_handle = nullptr;
|
vk::SwapchainKHR m_handle = nullptr;
|
||||||
vk::Format m_format = vk::Format::eUndefined;
|
vk::Format m_format = vk::Format::eUndefined;
|
||||||
|
std::atomic_bool m_hdr_supported {false};
|
||||||
|
bool m_hdr_requested = false;
|
||||||
|
bool m_hdr_active = false;
|
||||||
vk::Extent2D m_extent {};
|
vk::Extent2D m_extent {};
|
||||||
// Drawable pixel size observed when this swapchain was created.
|
// Drawable pixel size observed when this swapchain was created.
|
||||||
vk::Extent2D m_window_extent {};
|
vk::Extent2D m_window_extent {};
|
||||||
@@ -346,6 +359,40 @@ struct Presenter::Impl {
|
|||||||
std::atomic<uint64_t> presented_overlay_revision {0};
|
std::atomic<uint64_t> presented_overlay_revision {0};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
vk::SurfaceFormatKHR Swapchain::SelectFormat() {
|
||||||
|
const auto& formats = m_window.surface_capabilities.formats;
|
||||||
|
auto& graphics = m_window.graphic_ctx;
|
||||||
|
const auto hdr_format = std::ranges::find_if(formats, [&](const auto& candidate) {
|
||||||
|
return candidate.colorSpace == vk::ColorSpaceKHR::eHdr10St2084EXT &&
|
||||||
|
IsHdrSwapchainFormat(candidate.format) &&
|
||||||
|
static_cast<bool>(
|
||||||
|
graphics.GetFormatProperties(candidate.format).optimalTilingFeatures &
|
||||||
|
vk::FormatFeatureFlagBits::eBlitDst);
|
||||||
|
});
|
||||||
|
m_hdr_supported = Config::HdrEnabled() && hdr_format != formats.end();
|
||||||
|
m_hdr_active = m_hdr_requested && IsHdrSupported();
|
||||||
|
if (m_hdr_active) {
|
||||||
|
return *hdr_format;
|
||||||
|
}
|
||||||
|
if (Config::HdrEnabled() && !IsHdrSupported()) {
|
||||||
|
LOGF("HDR10 surface unavailable; using SDR output\n");
|
||||||
|
}
|
||||||
|
if (formats.size() == 1 && formats.front().format == vk::Format::eUndefined) {
|
||||||
|
return {vk::Format::eR8G8B8A8Unorm, vk::ColorSpaceKHR::eSrgbNonlinear};
|
||||||
|
}
|
||||||
|
for (const auto& candidate: formats) {
|
||||||
|
if (candidate.colorSpace != vk::ColorSpaceKHR::eSrgbNonlinear) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
switch (candidate.format) {
|
||||||
|
case vk::Format::eB8G8R8A8Unorm:
|
||||||
|
case vk::Format::eR8G8B8A8Unorm: return candidate;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXIT("no supported UNORM swapchain format\n");
|
||||||
|
}
|
||||||
|
|
||||||
void Swapchain::Create() {
|
void Swapchain::Create() {
|
||||||
auto& graphics = m_window.graphic_ctx;
|
auto& graphics = m_window.graphic_ctx;
|
||||||
EXIT_IF(graphics.device == nullptr);
|
EXIT_IF(graphics.device == nullptr);
|
||||||
@@ -385,18 +432,7 @@ void Swapchain::Create() {
|
|||||||
? vk::CompositeAlphaFlagBitsKHR::eOpaque
|
? vk::CompositeAlphaFlagBitsKHR::eOpaque
|
||||||
: vk::CompositeAlphaFlagBitsKHR::eInherit;
|
: vk::CompositeAlphaFlagBitsKHR::eInherit;
|
||||||
|
|
||||||
vk::SurfaceFormatKHR format {vk::Format::eR8G8B8A8Unorm, vk::ColorSpaceKHR::eSrgbNonlinear};
|
const auto format = SelectFormat();
|
||||||
if (surface.formats.size() != 1 || surface.formats.front().format != vk::Format::eUndefined) {
|
|
||||||
const auto it = std::find_if(surface.formats.begin(), surface.formats.end(),
|
|
||||||
[](const vk::SurfaceFormatKHR& candidate) {
|
|
||||||
return candidate.format == vk::Format::eB8G8R8A8Unorm ||
|
|
||||||
candidate.format == vk::Format::eR8G8B8A8Unorm;
|
|
||||||
});
|
|
||||||
if (it == surface.formats.end()) {
|
|
||||||
EXIT("no supported UNORM swapchain format\n");
|
|
||||||
}
|
|
||||||
format = *it;
|
|
||||||
}
|
|
||||||
m_format = format.format;
|
m_format = format.format;
|
||||||
const auto swapchain_features = graphics.GetFormatProperties(m_format).optimalTilingFeatures;
|
const auto swapchain_features = graphics.GetFormatProperties(m_format).optimalTilingFeatures;
|
||||||
if (!static_cast<bool>(swapchain_features & vk::FormatFeatureFlagBits::eBlitDst)) {
|
if (!static_cast<bool>(swapchain_features & vk::FormatFeatureFlagBits::eBlitDst)) {
|
||||||
@@ -436,6 +472,7 @@ void Swapchain::Create() {
|
|||||||
RequireVulkanSuccess(graphics.device.createSwapchainKHR(&create_info, nullptr, &m_handle),
|
RequireVulkanSuccess(graphics.device.createSwapchainKHR(&create_info, nullptr, &m_handle),
|
||||||
"vkCreateSwapchainKHR");
|
"vkCreateSwapchainKHR");
|
||||||
EXIT_IF(m_handle == nullptr);
|
EXIT_IF(m_handle == nullptr);
|
||||||
|
LOGF("Vulkan presentation: %s\n", m_hdr_active ? "HDR10 (PQ)" : "SDR");
|
||||||
|
|
||||||
m_images = EnumerateVulkan<vk::Image>(
|
m_images = EnumerateVulkan<vk::Image>(
|
||||||
"vkGetSwapchainImagesKHR", [&](uint32_t* count, vk::Image* images) {
|
"vkGetSwapchainImagesKHR", [&](uint32_t* count, vk::Image* images) {
|
||||||
@@ -692,9 +729,11 @@ void Swapchain::DrawOverlay(vk::CommandBuffer command, const Presenter::Layer& l
|
|||||||
write.descriptorType = vk::DescriptorType::eCombinedImageSampler;
|
write.descriptorType = vk::DescriptorType::eCombinedImageSampler;
|
||||||
write.pImageInfo = ℑ
|
write.pImageInfo = ℑ
|
||||||
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, m_overlay_layout, 0, 1, &write);
|
command.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, m_overlay_layout, 0, 1, &write);
|
||||||
const uint32_t premultiplied = layer.premultiplied_alpha;
|
const uint32_t flags = (layer.premultiplied_alpha ? 1u : 0u) |
|
||||||
|
(m_hdr_active ? 2u : 0u) | (frame.hdr ? 4u : 0u) |
|
||||||
|
(frame.image.format == vk::Format::eR16G16B16A16Sfloat ? 8u : 0u);
|
||||||
command.pushConstants(m_overlay_layout, vk::ShaderStageFlagBits::eFragment, 0,
|
command.pushConstants(m_overlay_layout, vk::ShaderStageFlagBits::eFragment, 0,
|
||||||
sizeof(premultiplied), &premultiplied);
|
sizeof(flags), &flags);
|
||||||
command.bindPipeline(vk::PipelineBindPoint::eGraphics, m_overlay_pipeline);
|
command.bindPipeline(vk::PipelineBindPoint::eGraphics, m_overlay_pipeline);
|
||||||
vk::RenderingAttachmentInfo attachment {};
|
vk::RenderingAttachmentInfo attachment {};
|
||||||
attachment.imageView = m_image_views[m_image_index];
|
attachment.imageView = m_image_views[m_image_index];
|
||||||
@@ -868,7 +907,7 @@ Presenter::Presenter(WindowContext& window): m_impl(std::make_unique<Impl>(windo
|
|||||||
|
|
||||||
Presenter::~Presenter() = default;
|
Presenter::~Presenter() = default;
|
||||||
|
|
||||||
Presenter::Frame& Presenter::PrepareFrame(CommandBuffer& buffer, const ImageInfo& info) {
|
Presenter::Frame& Presenter::PrepareFrame(CommandBuffer& buffer, const ImageInfo& info, bool hdr) {
|
||||||
KYTY_PROFILER_FUNCTION();
|
KYTY_PROFILER_FUNCTION();
|
||||||
EXIT_IF(buffer.IsInvalid());
|
EXIT_IF(buffer.IsInvalid());
|
||||||
auto frame_format = info.pixel_format;
|
auto frame_format = info.pixel_format;
|
||||||
@@ -878,6 +917,8 @@ Presenter::Frame& Presenter::PrepareFrame(CommandBuffer& buffer, const ImageInfo
|
|||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
auto* frame = m_impl->frames.Acquire({info.extent.width, info.extent.height}, frame_format);
|
auto* frame = m_impl->frames.Acquire({info.extent.width, info.extent.height}, frame_format);
|
||||||
|
frame->hdr = hdr;
|
||||||
|
frame->blank = false;
|
||||||
Common::LockGuard render_lock(m_impl->renderer.GetMutex());
|
Common::LockGuard render_lock(m_impl->renderer.GetMutex());
|
||||||
auto& image = m_impl->ResolveSurface(info);
|
auto& image = m_impl->ResolveSurface(info);
|
||||||
if (image.backing.format == vk::Format::eUndefined) {
|
if (image.backing.format == vk::Format::eUndefined) {
|
||||||
@@ -894,6 +935,8 @@ Presenter::Frame& Presenter::PrepareBlankFrame(uint32_t width, uint32_t height,
|
|||||||
KYTY_PROFILER_FUNCTION();
|
KYTY_PROFILER_FUNCTION();
|
||||||
auto format = m_impl->frames.GetFormat();
|
auto format = m_impl->frames.GetFormat();
|
||||||
auto* frame = m_impl->frames.Acquire({width, height}, format);
|
auto* frame = m_impl->frames.Acquire({width, height}, format);
|
||||||
|
frame->hdr = false;
|
||||||
|
frame->blank = true;
|
||||||
Common::LockGuard render_lock(m_impl->renderer.GetMutex());
|
Common::LockGuard render_lock(m_impl->renderer.GetMutex());
|
||||||
frame->Configure(m_impl->window.graphic_ctx, {width, height}, format);
|
frame->Configure(m_impl->window.graphic_ctx, {width, height}, format);
|
||||||
vk::ClearColorValue clear {};
|
vk::ClearColorValue clear {};
|
||||||
@@ -922,6 +965,10 @@ bool Presenter::IsGuestPaused() const noexcept {
|
|||||||
return m_impl->window.loop.paused.load(std::memory_order_acquire);
|
return m_impl->window.loop.paused.load(std::memory_order_acquire);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Presenter::IsHdrSupported() const noexcept {
|
||||||
|
return m_impl->swapchain.IsHdrSupported();
|
||||||
|
}
|
||||||
|
|
||||||
bool Presenter::NeedsSystemOverlayRefresh() const noexcept {
|
bool Presenter::NeedsSystemOverlayRefresh() const noexcept {
|
||||||
const auto visual = GetSystemOverlayVisualState();
|
const auto visual = GetSystemOverlayVisualState();
|
||||||
return visual.active ||
|
return visual.active ||
|
||||||
@@ -967,8 +1014,12 @@ void Presenter::Impl::Present() {
|
|||||||
KYTY_PROFILER_FUNCTION();
|
KYTY_PROFILER_FUNCTION();
|
||||||
|
|
||||||
const auto overlay_visual = GetSystemOverlayVisualState();
|
const auto overlay_visual = GetSystemOverlayVisualState();
|
||||||
|
// Keep black/blank flips in the current color space; only real main-bus frames switch it.
|
||||||
|
const auto* main_frame = layers[0].frame;
|
||||||
|
const bool hdr_changed = main_frame != nullptr && !main_frame->blank &&
|
||||||
|
swapchain.SetHdr(main_frame->hdr);
|
||||||
// Some window systems keep presenting an old swapchain after a resize.
|
// Some window systems keep presenting an old swapchain after a resize.
|
||||||
if (swapchain.NeedsResize()) {
|
if (hdr_changed || swapchain.NeedsResize()) {
|
||||||
RecoverSwapchain(Swapchain::Status::Recreate);
|
RecoverSwapchain(Swapchain::Status::Recreate);
|
||||||
}
|
}
|
||||||
for (uint32_t attempt = 0; attempt < 2; attempt++) {
|
for (uint32_t attempt = 0; attempt < 2; attempt++) {
|
||||||
|
|||||||
@@ -685,6 +685,11 @@ static void VulkanGetExtensions(VulkanExtensions& r) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
r.enable_validation_layers = Config::VulkanValidationEnabled();
|
r.enable_validation_layers = Config::VulkanValidationEnabled();
|
||||||
|
if (Config::HdrEnabled() &&
|
||||||
|
HasExtension(r.available_extensions, VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME) &&
|
||||||
|
!HasExtension(r.required_extensions, VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME)) {
|
||||||
|
r.required_extensions.push_back(VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
if (HasExtension(r.available_extensions, VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) {
|
if (HasExtension(r.available_extensions, VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) {
|
||||||
r.required_extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
r.required_extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||||
|
|||||||
@@ -180,6 +180,12 @@
|
|||||||
<property name="text"><string>Accurate texture synchronization</string></property>
|
<property name="text"><string>Accurate texture synchronization</string></property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="8" column="1">
|
||||||
|
<widget class="QCheckBox" name="checkBox_hdr">
|
||||||
|
<property name="toolTip"><string>Enable HDR10 output for supported games. Requires HDR enabled on the host display.</string></property>
|
||||||
|
<property name="text"><string>Enable HDR display support</string></property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ public:
|
|||||||
PresentMode present_mode = PresentMode::Mailbox;
|
PresentMode present_mode = PresentMode::Mailbox;
|
||||||
int gpu_index = -1;
|
int gpu_index = -1;
|
||||||
bool fullscreen_enabled = false;
|
bool fullscreen_enabled = false;
|
||||||
|
bool hdr_enabled = false;
|
||||||
bool hide_cursor_enabled = false;
|
bool hide_cursor_enabled = false;
|
||||||
bool readback_linear_images = false;
|
bool readback_linear_images = false;
|
||||||
bool sync_raw_image_buffers = false;
|
bool sync_raw_image_buffers = false;
|
||||||
@@ -152,6 +153,7 @@ public:
|
|||||||
present_mode = other.present_mode;
|
present_mode = other.present_mode;
|
||||||
gpu_index = other.gpu_index;
|
gpu_index = other.gpu_index;
|
||||||
fullscreen_enabled = other.fullscreen_enabled;
|
fullscreen_enabled = other.fullscreen_enabled;
|
||||||
|
hdr_enabled = other.hdr_enabled;
|
||||||
hide_cursor_enabled = other.hide_cursor_enabled;
|
hide_cursor_enabled = other.hide_cursor_enabled;
|
||||||
readback_linear_images = other.readback_linear_images;
|
readback_linear_images = other.readback_linear_images;
|
||||||
sync_raw_image_buffers = other.sync_raw_image_buffers;
|
sync_raw_image_buffers = other.sync_raw_image_buffers;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ QVariantMap Configuration::GameSettings() const {
|
|||||||
KYTY_CFG_SET(present_mode);
|
KYTY_CFG_SET(present_mode);
|
||||||
KYTY_CFG_SET(gpu_index);
|
KYTY_CFG_SET(gpu_index);
|
||||||
KYTY_CFG_SET(fullscreen_enabled);
|
KYTY_CFG_SET(fullscreen_enabled);
|
||||||
|
KYTY_CFG_SET(hdr_enabled);
|
||||||
KYTY_CFG_SET(hide_cursor_enabled);
|
KYTY_CFG_SET(hide_cursor_enabled);
|
||||||
KYTY_CFG_SET(readback_linear_images);
|
KYTY_CFG_SET(readback_linear_images);
|
||||||
KYTY_CFG_SET(sync_raw_image_buffers);
|
KYTY_CFG_SET(sync_raw_image_buffers);
|
||||||
@@ -55,6 +56,7 @@ void Configuration::ReadGameSettingsValues(const Settings& s) {
|
|||||||
present_mode = PresentMode::Mailbox;
|
present_mode = PresentMode::Mailbox;
|
||||||
}
|
}
|
||||||
KYTY_CFG_GET(fullscreen_enabled);
|
KYTY_CFG_GET(fullscreen_enabled);
|
||||||
|
KYTY_CFG_GET(hdr_enabled);
|
||||||
KYTY_CFG_GET(hide_cursor_enabled);
|
KYTY_CFG_GET(hide_cursor_enabled);
|
||||||
KYTY_CFG_GET(readback_linear_images);
|
KYTY_CFG_GET(readback_linear_images);
|
||||||
KYTY_CFG_GET(sync_raw_image_buffers);
|
KYTY_CFG_GET(sync_raw_image_buffers);
|
||||||
|
|||||||
@@ -273,6 +273,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
|
|||||||
info.gpu_index >= 0 && info.gpu_index < m_ui->comboBox_gpu->count() - 1 ? info.gpu_index + 1
|
info.gpu_index >= 0 && info.gpu_index < m_ui->comboBox_gpu->count() - 1 ? info.gpu_index + 1
|
||||||
: 0);
|
: 0);
|
||||||
m_ui->checkBox_fullscreen->setChecked(info.fullscreen_enabled);
|
m_ui->checkBox_fullscreen->setChecked(info.fullscreen_enabled);
|
||||||
|
m_ui->checkBox_hdr->setChecked(info.hdr_enabled);
|
||||||
m_ui->checkBox_hide_cursor->setChecked(info.hide_cursor_enabled);
|
m_ui->checkBox_hide_cursor->setChecked(info.hide_cursor_enabled);
|
||||||
m_ui->checkBox_readback->setChecked(info.readback_linear_images);
|
m_ui->checkBox_readback->setChecked(info.readback_linear_images);
|
||||||
m_ui->checkBox_sync_raw_image_buffers->setChecked(info.sync_raw_image_buffers);
|
m_ui->checkBox_sync_raw_image_buffers->setChecked(info.sync_raw_image_buffers);
|
||||||
@@ -434,6 +435,7 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui, boo
|
|||||||
TextToEnum<Configuration::PresentMode>(ui.comboBox_present_mode->currentText());
|
TextToEnum<Configuration::PresentMode>(ui.comboBox_present_mode->currentText());
|
||||||
info.gpu_index = ui.comboBox_gpu->currentIndex() - 1;
|
info.gpu_index = ui.comboBox_gpu->currentIndex() - 1;
|
||||||
info.fullscreen_enabled = ui.checkBox_fullscreen->isChecked();
|
info.fullscreen_enabled = ui.checkBox_fullscreen->isChecked();
|
||||||
|
info.hdr_enabled = ui.checkBox_hdr->isChecked();
|
||||||
info.hide_cursor_enabled = ui.checkBox_hide_cursor->isChecked();
|
info.hide_cursor_enabled = ui.checkBox_hide_cursor->isChecked();
|
||||||
info.readback_linear_images = ui.checkBox_readback->isChecked();
|
info.readback_linear_images = ui.checkBox_readback->isChecked();
|
||||||
info.sync_raw_image_buffers = ui.checkBox_sync_raw_image_buffers->isChecked();
|
info.sync_raw_image_buffers = ui.checkBox_sync_raw_image_buffers->isChecked();
|
||||||
|
|||||||
@@ -245,6 +245,9 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
|
|||||||
if (info.fullscreen_enabled) {
|
if (info.fullscreen_enabled) {
|
||||||
args << "--fullscreen";
|
args << "--fullscreen";
|
||||||
}
|
}
|
||||||
|
if (info.hdr_enabled) {
|
||||||
|
args << "--hdr";
|
||||||
|
}
|
||||||
if (info.hide_cursor_enabled) {
|
if (info.hide_cursor_enabled) {
|
||||||
args << "--hide-cursor";
|
args << "--hide-cursor";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,6 +164,8 @@ void Psf::OpenJson(const std::filesystem::path& file_name) {
|
|||||||
add_string_param("contentId", "CONTENT_ID");
|
add_string_param("contentId", "CONTENT_ID");
|
||||||
add_string_param("contentVersion", "APP_VER");
|
add_string_param("contentVersion", "APP_VER");
|
||||||
add_string_param("appVersion", "APP_VER");
|
add_string_param("appVersion", "APP_VER");
|
||||||
|
AddParamInt("ATTRIBUTE", 0);
|
||||||
|
add_int_param("attribute", "ATTRIBUTE");
|
||||||
add_int_param("attribute3", "ATTRIBUTE3");
|
add_int_param("attribute3", "ATTRIBUTE3");
|
||||||
add_int_param("userDefinedParam1", "USER_DEFINED_PARAM_1");
|
add_int_param("userDefinedParam1", "USER_DEFINED_PARAM_1");
|
||||||
add_int_param("userDefinedParam2", "USER_DEFINED_PARAM_2");
|
add_int_param("userDefinedParam2", "USER_DEFINED_PARAM_2");
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ static void PrintUsage() {
|
|||||||
::printf(
|
::printf(
|
||||||
" --gpu <index> Vulkan physical device index. Default: auto.\n");
|
" --gpu <index> Vulkan physical device index. Default: auto.\n");
|
||||||
::printf(" --fullscreen Run in borderless desktop fullscreen.\n");
|
::printf(" --fullscreen Run in borderless desktop fullscreen.\n");
|
||||||
|
::printf(" --hdr Enable HDR10 output when supported.\n");
|
||||||
::printf(
|
::printf(
|
||||||
" --hide-cursor Hide the cursor after 2 s idle. Default: off.\n");
|
" --hide-cursor Hide the cursor after 2 s idle. Default: off.\n");
|
||||||
::printf(" --no-splash Disable the startup splash screen.\n");
|
::printf(" --no-splash Disable the startup splash screen.\n");
|
||||||
@@ -215,6 +216,11 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (arg == "--hdr") {
|
||||||
|
options.config.hdr_enabled = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (arg == "--hide-cursor") {
|
if (arg == "--hide-cursor") {
|
||||||
options.config.hide_cursor_enabled = true;
|
options.config.hide_cursor_enabled = true;
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
Reference in New Issue
Block a user