early-access version 3938

main
pineappleEA 2023-10-19 17:53:53 +02:00
parent 4d49e282ae
commit ec28817fce
36 changed files with 376 additions and 144 deletions

View File

@ -357,6 +357,9 @@ function(set_yuzu_qt_components)
if (ENABLE_QT_TRANSLATION) if (ENABLE_QT_TRANSLATION)
list(APPEND YUZU_QT_COMPONENTS2 LinguistTools) list(APPEND YUZU_QT_COMPONENTS2 LinguistTools)
endif() endif()
if (USE_DISCORD_PRESENCE)
list(APPEND YUZU_QT_COMPONENTS2 Network)
endif()
set(YUZU_QT_COMPONENTS ${YUZU_QT_COMPONENTS2} PARENT_SCOPE) set(YUZU_QT_COMPONENTS ${YUZU_QT_COMPONENTS2} PARENT_SCOPE)
endfunction(set_yuzu_qt_components) endfunction(set_yuzu_qt_components)

View File

@ -1,7 +1,7 @@
yuzu emulator early access yuzu emulator early access
============= =============
This is the source code for early-access 3932. This is the source code for early-access 3938.
## Legal Notice ## Legal Notice

View File

@ -204,6 +204,10 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz
// paused and we'll desync, so just play silence. // paused and we'll desync, so just play silence.
if (system.IsPaused() || system.IsShuttingDown()) { if (system.IsPaused() || system.IsShuttingDown()) {
if (system.IsShuttingDown()) { if (system.IsShuttingDown()) {
{
std::scoped_lock lk{release_mutex};
queued_buffers.store(0);
}
release_cv.notify_one(); release_cv.notify_one();
} }

View File

@ -45,6 +45,7 @@ SWITCHABLE(CpuAccuracy, true);
SWITCHABLE(FullscreenMode, true); SWITCHABLE(FullscreenMode, true);
SWITCHABLE(GpuAccuracy, true); SWITCHABLE(GpuAccuracy, true);
SWITCHABLE(Language, true); SWITCHABLE(Language, true);
SWITCHABLE(MemoryLayout, true);
SWITCHABLE(NvdecEmulation, false); SWITCHABLE(NvdecEmulation, false);
SWITCHABLE(Region, true); SWITCHABLE(Region, true);
SWITCHABLE(RendererBackend, true); SWITCHABLE(RendererBackend, true);
@ -61,6 +62,10 @@ SWITCHABLE(u32, false);
SWITCHABLE(u8, false); SWITCHABLE(u8, false);
SWITCHABLE(u8, true); SWITCHABLE(u8, true);
// Used in UISettings
// TODO see if we can move this to uisettings.cpp
SWITCHABLE(ConfirmStop, true);
#undef SETTING #undef SETTING
#undef SWITCHABLE #undef SWITCHABLE
#endif #endif

View File

@ -67,6 +67,7 @@ SWITCHABLE(CpuAccuracy, true);
SWITCHABLE(FullscreenMode, true); SWITCHABLE(FullscreenMode, true);
SWITCHABLE(GpuAccuracy, true); SWITCHABLE(GpuAccuracy, true);
SWITCHABLE(Language, true); SWITCHABLE(Language, true);
SWITCHABLE(MemoryLayout, true);
SWITCHABLE(NvdecEmulation, false); SWITCHABLE(NvdecEmulation, false);
SWITCHABLE(Region, true); SWITCHABLE(Region, true);
SWITCHABLE(RendererBackend, true); SWITCHABLE(RendererBackend, true);
@ -83,6 +84,10 @@ SWITCHABLE(u32, false);
SWITCHABLE(u8, false); SWITCHABLE(u8, false);
SWITCHABLE(u8, true); SWITCHABLE(u8, true);
// Used in UISettings
// TODO see if we can move this to uisettings.h
SWITCHABLE(ConfirmStop, true);
#undef SETTING #undef SETTING
#undef SWITCHABLE #undef SWITCHABLE
#endif #endif

View File

@ -133,6 +133,8 @@ ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid);
ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb); ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb);
ENUM(ConfirmStop, Ask_Always, Ask_Based_On_Game, Ask_Never);
ENUM(FullscreenMode, Borderless, Exclusive); ENUM(FullscreenMode, Borderless, Exclusive);
ENUM(NvdecEmulation, Off, Cpu, Gpu); ENUM(NvdecEmulation, Off, Cpu, Gpu);

View File

@ -407,13 +407,13 @@ protected:
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
const auto base = rp.PopRaw<ProfileBase>(); const auto base = rp.PopRaw<ProfileBase>();
const auto user_data = ctx.ReadBuffer(0); const auto image_data = ctx.ReadBufferA(0);
const auto image_data = ctx.ReadBuffer(1); // TODO: Check if this buffer is always provided const auto user_data = ctx.ReadBufferX(0);
LOG_DEBUG(Service_ACC, "called, username='{}', timestamp={:016X}, uuid=0x{}", LOG_INFO(Service_ACC, "called, username='{}', timestamp={:016X}, uuid=0x{}",
Common::StringFromFixedZeroTerminatedBuffer( Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(base.username.data()), base.username.size()), reinterpret_cast<const char*>(base.username.data()), base.username.size()),
base.timestamp, base.user_uuid.RawString()); base.timestamp, base.user_uuid.RawString());
if (user_data.size() < sizeof(UserData)) { if (user_data.size() < sizeof(UserData)) {
LOG_ERROR(Service_ACC, "UserData buffer too small!"); LOG_ERROR(Service_ACC, "UserData buffer too small!");

View File

@ -210,8 +210,8 @@ IDisplayController::IDisplayController(Core::System& system_)
{21, nullptr, "ClearAppletTransitionBuffer"}, {21, nullptr, "ClearAppletTransitionBuffer"},
{22, nullptr, "AcquireLastApplicationCaptureSharedBuffer"}, {22, nullptr, "AcquireLastApplicationCaptureSharedBuffer"},
{23, nullptr, "ReleaseLastApplicationCaptureSharedBuffer"}, {23, nullptr, "ReleaseLastApplicationCaptureSharedBuffer"},
{24, nullptr, "AcquireLastForegroundCaptureSharedBuffer"}, {24, &IDisplayController::AcquireLastForegroundCaptureSharedBuffer, "AcquireLastForegroundCaptureSharedBuffer"},
{25, nullptr, "ReleaseLastForegroundCaptureSharedBuffer"}, {25, &IDisplayController::ReleaseLastForegroundCaptureSharedBuffer, "ReleaseLastForegroundCaptureSharedBuffer"},
{26, &IDisplayController::AcquireCallerAppletCaptureSharedBuffer, "AcquireCallerAppletCaptureSharedBuffer"}, {26, &IDisplayController::AcquireCallerAppletCaptureSharedBuffer, "AcquireCallerAppletCaptureSharedBuffer"},
{27, &IDisplayController::ReleaseCallerAppletCaptureSharedBuffer, "ReleaseCallerAppletCaptureSharedBuffer"}, {27, &IDisplayController::ReleaseCallerAppletCaptureSharedBuffer, "ReleaseCallerAppletCaptureSharedBuffer"},
{28, nullptr, "TakeScreenShotOfOwnLayerEx"}, {28, nullptr, "TakeScreenShotOfOwnLayerEx"},
@ -239,6 +239,22 @@ void IDisplayController::TakeScreenShotOfOwnLayer(HLERequestContext& ctx) {
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
} }
void IDisplayController::AcquireLastForegroundCaptureSharedBuffer(HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
rb.Push(1U);
rb.Push(0);
}
void IDisplayController::ReleaseLastForegroundCaptureSharedBuffer(HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void IDisplayController::AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx) { void IDisplayController::AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called"); LOG_WARNING(Service_AM, "(STUBBED) called");

View File

@ -124,6 +124,8 @@ public:
private: private:
void GetCallerAppletCaptureImageEx(HLERequestContext& ctx); void GetCallerAppletCaptureImageEx(HLERequestContext& ctx);
void TakeScreenShotOfOwnLayer(HLERequestContext& ctx); void TakeScreenShotOfOwnLayer(HLERequestContext& ctx);
void AcquireLastForegroundCaptureSharedBuffer(HLERequestContext& ctx);
void ReleaseLastForegroundCaptureSharedBuffer(HLERequestContext& ctx);
void AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx); void AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx);
void ReleaseCallerAppletCaptureSharedBuffer(HLERequestContext& ctx); void ReleaseCallerAppletCaptureSharedBuffer(HLERequestContext& ctx);
}; };

View File

@ -23,6 +23,17 @@
#include "core/hle/service/ipc_helpers.h" #include "core/hle/service/ipc_helpers.h"
#include "core/memory.h" #include "core/memory.h"
namespace {
static thread_local std::array read_buffer_data_a{
Common::ScratchBuffer<u8>(),
Common::ScratchBuffer<u8>(),
};
static thread_local std::array read_buffer_data_x{
Common::ScratchBuffer<u8>(),
Common::ScratchBuffer<u8>(),
};
} // Anonymous namespace
namespace Service { namespace Service {
SessionRequestHandler::SessionRequestHandler(Kernel::KernelCore& kernel_, const char* service_name_) SessionRequestHandler::SessionRequestHandler(Kernel::KernelCore& kernel_, const char* service_name_)
@ -328,26 +339,57 @@ std::vector<u8> HLERequestContext::ReadBufferCopy(std::size_t buffer_index) cons
} }
} }
std::span<const u8> HLERequestContext::ReadBufferA(std::size_t buffer_index) const {
static thread_local std::array read_buffer_a{
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
};
ASSERT_OR_EXECUTE_MSG(
BufferDescriptorA().size() > buffer_index, { return {}; },
"BufferDescriptorA invalid buffer_index {}", buffer_index);
auto& read_buffer = read_buffer_a[buffer_index];
return read_buffer.Read(BufferDescriptorA()[buffer_index].Address(),
BufferDescriptorA()[buffer_index].Size(),
&read_buffer_data_a[buffer_index]);
}
std::span<const u8> HLERequestContext::ReadBufferX(std::size_t buffer_index) const {
static thread_local std::array read_buffer_x{
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
};
ASSERT_OR_EXECUTE_MSG(
BufferDescriptorX().size() > buffer_index, { return {}; },
"BufferDescriptorX invalid buffer_index {}", buffer_index);
auto& read_buffer = read_buffer_x[buffer_index];
return read_buffer.Read(BufferDescriptorX()[buffer_index].Address(),
BufferDescriptorX()[buffer_index].Size(),
&read_buffer_data_x[buffer_index]);
}
std::span<const u8> HLERequestContext::ReadBuffer(std::size_t buffer_index) const { std::span<const u8> HLERequestContext::ReadBuffer(std::size_t buffer_index) const {
static thread_local std::array read_buffer_a{ static thread_local std::array read_buffer_a{
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0), Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0), Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
}; };
static thread_local std::array read_buffer_data_a{
Common::ScratchBuffer<u8>(),
Common::ScratchBuffer<u8>(),
};
static thread_local std::array read_buffer_x{ static thread_local std::array read_buffer_x{
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0), Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0), Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::SafeRead>(memory, 0, 0),
}; };
static thread_local std::array read_buffer_data_x{
Common::ScratchBuffer<u8>(),
Common::ScratchBuffer<u8>(),
};
const bool is_buffer_a{BufferDescriptorA().size() > buffer_index && const bool is_buffer_a{BufferDescriptorA().size() > buffer_index &&
BufferDescriptorA()[buffer_index].Size()}; BufferDescriptorA()[buffer_index].Size()};
const bool is_buffer_x{BufferDescriptorX().size() > buffer_index &&
BufferDescriptorX()[buffer_index].Size()};
if (is_buffer_a && is_buffer_x) {
LOG_WARNING(Input, "Both buffer descriptors are available a.size={}, x.size={}",
BufferDescriptorA()[buffer_index].Size(),
BufferDescriptorX()[buffer_index].Size());
}
if (is_buffer_a) { if (is_buffer_a) {
ASSERT_OR_EXECUTE_MSG( ASSERT_OR_EXECUTE_MSG(
BufferDescriptorA().size() > buffer_index, { return {}; }, BufferDescriptorA().size() > buffer_index, { return {}; },

View File

@ -253,6 +253,12 @@ public:
return domain_message_header.has_value(); return domain_message_header.has_value();
} }
/// Helper function to get a span of a buffer using the buffer descriptor A
[[nodiscard]] std::span<const u8> ReadBufferA(std::size_t buffer_index = 0) const;
/// Helper function to get a span of a buffer using the buffer descriptor X
[[nodiscard]] std::span<const u8> ReadBufferX(std::size_t buffer_index = 0) const;
/// Helper function to get a span of a buffer using the appropriate buffer descriptor /// Helper function to get a span of a buffer using the appropriate buffer descriptor
[[nodiscard]] std::span<const u8> ReadBuffer(std::size_t buffer_index = 0) const; [[nodiscard]] std::span<const u8> ReadBuffer(std::size_t buffer_index = 0) const;

View File

@ -58,14 +58,8 @@ private:
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
const auto process_id = rp.PopRaw<u64>(); const auto process_id = rp.PopRaw<u64>();
const auto data1 = ctx.ReadBuffer(0); const auto data1 = ctx.ReadBufferA(0);
const auto data2 = [&ctx] { const auto data2 = ctx.ReadBufferX(0);
if (ctx.CanReadBuffer(1)) {
return ctx.ReadBuffer(1);
}
return std::span<const u8>{};
}();
LOG_DEBUG(Service_PREPO, LOG_DEBUG(Service_PREPO,
"called, type={:02X}, process_id={:016X}, data1_size={:016X}, data2_size={:016X}", "called, type={:02X}, process_id={:016X}, data1_size={:016X}, data2_size={:016X}",
@ -85,14 +79,8 @@ private:
const auto user_id = rp.PopRaw<u128>(); const auto user_id = rp.PopRaw<u128>();
const auto process_id = rp.PopRaw<u64>(); const auto process_id = rp.PopRaw<u64>();
const auto data1 = ctx.ReadBuffer(0); const auto data1 = ctx.ReadBufferA(0);
const auto data2 = [&ctx] { const auto data2 = ctx.ReadBufferX(0);
if (ctx.CanReadBuffer(1)) {
return ctx.ReadBuffer(1);
}
return std::span<const u8>{};
}();
LOG_DEBUG(Service_PREPO, LOG_DEBUG(Service_PREPO,
"called, type={:02X}, user_id={:016X}{:016X}, process_id={:016X}, " "called, type={:02X}, user_id={:016X}{:016X}, process_id={:016X}, "
@ -137,14 +125,8 @@ private:
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
const auto title_id = rp.PopRaw<u64>(); const auto title_id = rp.PopRaw<u64>();
const auto data1 = ctx.ReadBuffer(0); const auto data1 = ctx.ReadBufferA(0);
const auto data2 = [&ctx] { const auto data2 = ctx.ReadBufferX(0);
if (ctx.CanReadBuffer(1)) {
return ctx.ReadBuffer(1);
}
return std::span<const u8>{};
}();
LOG_DEBUG(Service_PREPO, "called, title_id={:016X}, data1_size={:016X}, data2_size={:016X}", LOG_DEBUG(Service_PREPO, "called, title_id={:016X}, data1_size={:016X}, data2_size={:016X}",
title_id, data1.size(), data2.size()); title_id, data1.size(), data2.size());
@ -161,14 +143,8 @@ private:
const auto user_id = rp.PopRaw<u128>(); const auto user_id = rp.PopRaw<u128>();
const auto title_id = rp.PopRaw<u64>(); const auto title_id = rp.PopRaw<u64>();
const auto data1 = ctx.ReadBuffer(0); const auto data1 = ctx.ReadBufferA(0);
const auto data2 = [&ctx] { const auto data2 = ctx.ReadBufferX(0);
if (ctx.CanReadBuffer(1)) {
return ctx.ReadBuffer(1);
}
return std::span<const u8>{};
}();
LOG_DEBUG(Service_PREPO, LOG_DEBUG(Service_PREPO,
"called, user_id={:016X}{:016X}, title_id={:016X}, data1_size={:016X}, " "called, user_id={:016X}{:016X}, title_id={:016X}, data1_size={:016X}, "

View File

@ -338,6 +338,7 @@ void UDPClient::StartCommunication(std::size_t client, const std::string& host,
for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) { for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) {
const PadIdentifier identifier = GetPadIdentifier(client * PADS_PER_CLIENT + index); const PadIdentifier identifier = GetPadIdentifier(client * PADS_PER_CLIENT + index);
PreSetController(identifier); PreSetController(identifier);
PreSetMotion(identifier, 0);
} }
} }

View File

@ -46,8 +46,8 @@ TEST_CASE("UniqueFunction", "[common]") {
Noisy noisy; Noisy noisy;
REQUIRE(noisy.state == "Default constructed"); REQUIRE(noisy.state == "Default constructed");
Common::UniqueFunction<void> func = [noisy = std::move(noisy)] { Common::UniqueFunction<void> func = [noisy_inner = std::move(noisy)] {
REQUIRE(noisy.state == "Move constructed"); REQUIRE(noisy_inner.state == "Move constructed");
}; };
REQUIRE(noisy.state == "Moved away"); REQUIRE(noisy.state == "Moved away");
func(); func();
@ -101,7 +101,7 @@ TEST_CASE("UniqueFunction", "[common]") {
}; };
Foo object{&num_destroyed}; Foo object{&num_destroyed};
{ {
Common::UniqueFunction<void> func = [object = std::move(object)] {}; Common::UniqueFunction<void> func = [object_inner = std::move(object)] {};
REQUIRE(num_destroyed == 0); REQUIRE(num_destroyed == 0);
} }
REQUIRE(num_destroyed == 1); REQUIRE(num_destroyed == 1);

View File

@ -48,8 +48,14 @@ void DrawManager::ProcessMethodCall(u32 method, u32 argument) {
SetInlineIndexBuffer(regs.inline_index_4x8.index3); SetInlineIndexBuffer(regs.inline_index_4x8.index3);
break; break;
case MAXWELL3D_REG_INDEX(vertex_array_instance_first): case MAXWELL3D_REG_INDEX(vertex_array_instance_first):
DrawArrayInstanced(regs.vertex_array_instance_first.topology.Value(),
regs.vertex_array_instance_first.start.Value(),
regs.vertex_array_instance_first.count.Value(), false);
break;
case MAXWELL3D_REG_INDEX(vertex_array_instance_subsequent): { case MAXWELL3D_REG_INDEX(vertex_array_instance_subsequent): {
LOG_WARNING(HW_GPU, "(STUBBED) called"); DrawArrayInstanced(regs.vertex_array_instance_subsequent.topology.Value(),
regs.vertex_array_instance_subsequent.start.Value(),
regs.vertex_array_instance_subsequent.count.Value(), true);
break; break;
} }
case MAXWELL3D_REG_INDEX(draw_texture.src_y0): { case MAXWELL3D_REG_INDEX(draw_texture.src_y0): {
@ -84,6 +90,22 @@ void DrawManager::DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 ve
ProcessDraw(false, num_instances); ProcessDraw(false, num_instances);
} }
void DrawManager::DrawArrayInstanced(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
bool subsequent) {
draw_state.topology = topology;
draw_state.vertex_buffer.first = vertex_first;
draw_state.vertex_buffer.count = vertex_count;
if (!subsequent) {
draw_state.instance_count = 1;
}
draw_state.base_instance = draw_state.instance_count - 1;
draw_state.draw_mode = DrawMode::Instance;
draw_state.instance_count++;
ProcessDraw(false, 1);
}
void DrawManager::DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count, void DrawManager::DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count,
u32 base_index, u32 base_instance, u32 num_instances) { u32 base_index, u32 base_instance, u32 num_instances) {
const auto& regs{maxwell3d->regs}; const auto& regs{maxwell3d->regs};

View File

@ -66,6 +66,8 @@ public:
void DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count, void DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
u32 base_instance, u32 num_instances); u32 base_instance, u32 num_instances);
void DrawArrayInstanced(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
bool subsequent);
void DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count, u32 base_index, void DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count, u32 base_index,
u32 base_instance, u32 num_instances); u32 base_instance, u32 num_instances);

View File

@ -86,7 +86,10 @@ public:
uncommitted_operations.emplace_back(std::move(func)); uncommitted_operations.emplace_back(std::move(func));
} }
pending_operations.emplace_back(std::move(uncommitted_operations)); pending_operations.emplace_back(std::move(uncommitted_operations));
QueueFence(new_fence); {
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
QueueFence(new_fence);
}
if (!delay_fence) { if (!delay_fence) {
func(); func();
} }

View File

@ -137,16 +137,6 @@ bool Codec::CreateGpuAvDevice() {
break; break;
} }
if ((config->methods & HW_CONFIG_METHOD) != 0 && config->device_type == type) { if ((config->methods & HW_CONFIG_METHOD) != 0 && config->device_type == type) {
#if defined(__unix__)
// Some linux decoding backends are reported to crash with this config method
// TODO(ameerj): Properly support this method
if ((config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX) != 0) {
// skip zero-copy decoders, we don't currently support them
LOG_DEBUG(Service_NVDRV, "Skipping decoder {} with unsupported capability {}.",
av_hwdevice_get_type_name(type), config->methods);
continue;
}
#endif
LOG_INFO(Service_NVDRV, "Using {} GPU decoder", av_hwdevice_get_type_name(type)); LOG_INFO(Service_NVDRV, "Using {} GPU decoder", av_hwdevice_get_type_name(type));
av_codec_ctx->pix_fmt = config->pix_fmt; av_codec_ctx->pix_fmt = config->pix_fmt;
return true; return true;

View File

@ -19,6 +19,7 @@ set(SHADER_FILES
block_linear_unswizzle_2d.comp block_linear_unswizzle_2d.comp
block_linear_unswizzle_3d.comp block_linear_unswizzle_3d.comp
convert_abgr8_to_d24s8.frag convert_abgr8_to_d24s8.frag
convert_abgr8_to_d32f.frag
convert_d32f_to_abgr8.frag convert_d32f_to_abgr8.frag
convert_d24s8_to_abgr8.frag convert_d24s8_to_abgr8.frag
convert_depth_to_float.frag convert_depth_to_float.frag

View File

@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#version 450
layout(binding = 0) uniform sampler2D color_texture;
void main() {
ivec2 coord = ivec2(gl_FragCoord.xy);
vec4 color = texelFetch(color_texture, coord, 0).abgr;
float value = color.a * (color.r + color.g + color.b) / 3.0f;
gl_FragDepth = value;
}

View File

@ -9,6 +9,6 @@ layout(location = 0) out vec4 color;
void main() { void main() {
ivec2 coord = ivec2(gl_FragCoord.xy); ivec2 coord = ivec2(gl_FragCoord.xy);
float depth = textureLod(depth_tex, coord, 0).r; float depth = texelFetch(depth_tex, coord, 0).r;
color = vec4(depth, depth, depth, 1.0); color = vec4(depth, depth, depth, 1.0);
} }

View File

@ -8,6 +8,7 @@
#include "common/settings.h" #include "common/settings.h"
#include "video_core/host_shaders/blit_color_float_frag_spv.h" #include "video_core/host_shaders/blit_color_float_frag_spv.h"
#include "video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h" #include "video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h"
#include "video_core/host_shaders/convert_abgr8_to_d32f_frag_spv.h"
#include "video_core/host_shaders/convert_d24s8_to_abgr8_frag_spv.h" #include "video_core/host_shaders/convert_d24s8_to_abgr8_frag_spv.h"
#include "video_core/host_shaders/convert_d32f_to_abgr8_frag_spv.h" #include "video_core/host_shaders/convert_d32f_to_abgr8_frag_spv.h"
#include "video_core/host_shaders/convert_depth_to_float_frag_spv.h" #include "video_core/host_shaders/convert_depth_to_float_frag_spv.h"
@ -434,6 +435,7 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)), convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)),
convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)), convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)),
convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)), convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)),
convert_abgr8_to_d32f_frag(BuildShader(device, CONVERT_ABGR8_TO_D32F_FRAG_SPV)),
convert_d32f_to_abgr8_frag(BuildShader(device, CONVERT_D32F_TO_ABGR8_FRAG_SPV)), convert_d32f_to_abgr8_frag(BuildShader(device, CONVERT_D32F_TO_ABGR8_FRAG_SPV)),
convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)), convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)),
convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)), convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)),
@ -559,6 +561,13 @@ void BlitImageHelper::ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer,
Convert(*convert_abgr8_to_d24s8_pipeline, dst_framebuffer, src_image_view); Convert(*convert_abgr8_to_d24s8_pipeline, dst_framebuffer, src_image_view);
} }
void BlitImageHelper::ConvertABGR8ToD32F(const Framebuffer* dst_framebuffer,
const ImageView& src_image_view) {
ConvertPipelineDepthTargetEx(convert_abgr8_to_d32f_pipeline, dst_framebuffer->RenderPass(),
convert_abgr8_to_d32f_frag);
Convert(*convert_abgr8_to_d32f_pipeline, dst_framebuffer, src_image_view);
}
void BlitImageHelper::ConvertD32FToABGR8(const Framebuffer* dst_framebuffer, void BlitImageHelper::ConvertD32FToABGR8(const Framebuffer* dst_framebuffer,
ImageView& src_image_view) { ImageView& src_image_view) {
ConvertPipelineColorTargetEx(convert_d32f_to_abgr8_pipeline, dst_framebuffer->RenderPass(), ConvertPipelineColorTargetEx(convert_d32f_to_abgr8_pipeline, dst_framebuffer->RenderPass(),

View File

@ -67,6 +67,8 @@ public:
void ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view); void ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
void ConvertABGR8ToD32F(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
void ConvertD32FToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view); void ConvertD32FToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
void ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view); void ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
@ -130,6 +132,7 @@ private:
vk::ShaderModule convert_depth_to_float_frag; vk::ShaderModule convert_depth_to_float_frag;
vk::ShaderModule convert_float_to_depth_frag; vk::ShaderModule convert_float_to_depth_frag;
vk::ShaderModule convert_abgr8_to_d24s8_frag; vk::ShaderModule convert_abgr8_to_d24s8_frag;
vk::ShaderModule convert_abgr8_to_d32f_frag;
vk::ShaderModule convert_d32f_to_abgr8_frag; vk::ShaderModule convert_d32f_to_abgr8_frag;
vk::ShaderModule convert_d24s8_to_abgr8_frag; vk::ShaderModule convert_d24s8_to_abgr8_frag;
vk::ShaderModule convert_s8d24_to_abgr8_frag; vk::ShaderModule convert_s8d24_to_abgr8_frag;
@ -149,6 +152,7 @@ private:
vk::Pipeline convert_d16_to_r16_pipeline; vk::Pipeline convert_d16_to_r16_pipeline;
vk::Pipeline convert_r16_to_d16_pipeline; vk::Pipeline convert_r16_to_d16_pipeline;
vk::Pipeline convert_abgr8_to_d24s8_pipeline; vk::Pipeline convert_abgr8_to_d24s8_pipeline;
vk::Pipeline convert_abgr8_to_d32f_pipeline;
vk::Pipeline convert_d32f_to_abgr8_pipeline; vk::Pipeline convert_d32f_to_abgr8_pipeline;
vk::Pipeline convert_d24s8_to_abgr8_pipeline; vk::Pipeline convert_d24s8_to_abgr8_pipeline;
vk::Pipeline convert_s8d24_to_abgr8_pipeline; vk::Pipeline convert_s8d24_to_abgr8_pipeline;

View File

@ -132,12 +132,16 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
const bool use_accelerated = const bool use_accelerated =
rasterizer.AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride); rasterizer.AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride);
const bool is_srgb = use_accelerated && screen_info.is_srgb; const bool is_srgb = use_accelerated && screen_info.is_srgb;
RenderScreenshot(*framebuffer, use_accelerated);
Frame* frame = present_manager.GetRenderFrame(); {
blit_screen.DrawToSwapchain(frame, *framebuffer, use_accelerated, is_srgb); std::scoped_lock lock{rasterizer.LockCaches()};
scheduler.Flush(*frame->render_ready); RenderScreenshot(*framebuffer, use_accelerated);
present_manager.Present(frame);
Frame* frame = present_manager.GetRenderFrame();
blit_screen.DrawToSwapchain(frame, *framebuffer, use_accelerated, is_srgb);
scheduler.Flush(*frame->render_ready);
present_manager.Present(frame);
}
gpu.RendererFrameEndNotify(); gpu.RendererFrameEndNotify();
rasterizer.TickFrame(); rasterizer.TickFrame();

View File

@ -198,7 +198,7 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
if (!pipeline) { if (!pipeline) {
return; return;
} }
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; std::scoped_lock lock{LockCaches()};
// update engine as channel may be different. // update engine as channel may be different.
pipeline->SetEngine(maxwell3d, gpu_memory); pipeline->SetEngine(maxwell3d, gpu_memory);
pipeline->Configure(is_indexed); pipeline->Configure(is_indexed);
@ -708,6 +708,7 @@ void RasterizerVulkan::TiledCacheBarrier() {
} }
void RasterizerVulkan::FlushCommands() { void RasterizerVulkan::FlushCommands() {
std::scoped_lock lock{LockCaches()};
if (draw_counter == 0) { if (draw_counter == 0) {
return; return;
} }
@ -805,6 +806,7 @@ void RasterizerVulkan::FlushWork() {
if ((++draw_counter & 7) != 7) { if ((++draw_counter & 7) != 7) {
return; return;
} }
std::scoped_lock lock{LockCaches()};
if (draw_counter < DRAWS_TO_DISPATCH) { if (draw_counter < DRAWS_TO_DISPATCH) {
// Send recorded tasks to the worker thread // Send recorded tasks to the worker thread
scheduler.DispatchWork(); scheduler.DispatchWork();
@ -1499,7 +1501,7 @@ void RasterizerVulkan::UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs)
void RasterizerVulkan::InitializeChannel(Tegra::Control::ChannelState& channel) { void RasterizerVulkan::InitializeChannel(Tegra::Control::ChannelState& channel) {
CreateChannel(channel); CreateChannel(channel);
{ {
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; std::scoped_lock lock{LockCaches()};
texture_cache.CreateChannel(channel); texture_cache.CreateChannel(channel);
buffer_cache.CreateChannel(channel); buffer_cache.CreateChannel(channel);
} }
@ -1512,7 +1514,7 @@ void RasterizerVulkan::BindChannel(Tegra::Control::ChannelState& channel) {
const s32 channel_id = channel.bind_id; const s32 channel_id = channel.bind_id;
BindToChannel(channel_id); BindToChannel(channel_id);
{ {
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; std::scoped_lock lock{LockCaches()};
texture_cache.BindToChannel(channel_id); texture_cache.BindToChannel(channel_id);
buffer_cache.BindToChannel(channel_id); buffer_cache.BindToChannel(channel_id);
} }
@ -1525,7 +1527,7 @@ void RasterizerVulkan::BindChannel(Tegra::Control::ChannelState& channel) {
void RasterizerVulkan::ReleaseChannel(s32 channel_id) { void RasterizerVulkan::ReleaseChannel(s32 channel_id) {
EraseChannel(channel_id); EraseChannel(channel_id);
{ {
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; std::scoped_lock lock{LockCaches()};
texture_cache.EraseChannel(channel_id); texture_cache.EraseChannel(channel_id);
buffer_cache.EraseChannel(channel_id); buffer_cache.EraseChannel(channel_id);
} }

View File

@ -133,6 +133,10 @@ public:
void ReleaseChannel(s32 channel_id) override; void ReleaseChannel(s32 channel_id) override;
std::scoped_lock<std::recursive_mutex, std::recursive_mutex> LockCaches() {
return std::scoped_lock{buffer_cache.mutex, texture_cache.mutex};
}
private: private:
static constexpr size_t MAX_TEXTURES = 192; static constexpr size_t MAX_TEXTURES = 192;
static constexpr size_t MAX_IMAGES = 48; static constexpr size_t MAX_IMAGES = 48;

View File

@ -1194,6 +1194,11 @@ void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, Im
return blit_image_helper.ConvertD16ToR16(dst, src_view); return blit_image_helper.ConvertD16ToR16(dst, src_view);
} }
break; break;
case PixelFormat::A8B8G8R8_SRGB:
if (src_view.format == PixelFormat::D32_FLOAT) {
return blit_image_helper.ConvertD32FToABGR8(dst, src_view);
}
break;
case PixelFormat::A8B8G8R8_UNORM: case PixelFormat::A8B8G8R8_UNORM:
if (src_view.format == PixelFormat::S8_UINT_D24_UNORM) { if (src_view.format == PixelFormat::S8_UINT_D24_UNORM) {
return blit_image_helper.ConvertD24S8ToABGR8(dst, src_view); return blit_image_helper.ConvertD24S8ToABGR8(dst, src_view);
@ -1205,6 +1210,16 @@ void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, Im
return blit_image_helper.ConvertD32FToABGR8(dst, src_view); return blit_image_helper.ConvertD32FToABGR8(dst, src_view);
} }
break; break;
case PixelFormat::B8G8R8A8_SRGB:
if (src_view.format == PixelFormat::D32_FLOAT) {
return blit_image_helper.ConvertD32FToABGR8(dst, src_view);
}
break;
case PixelFormat::B8G8R8A8_UNORM:
if (src_view.format == PixelFormat::D32_FLOAT) {
return blit_image_helper.ConvertD32FToABGR8(dst, src_view);
}
break;
case PixelFormat::R32_FLOAT: case PixelFormat::R32_FLOAT:
if (src_view.format == PixelFormat::D32_FLOAT) { if (src_view.format == PixelFormat::D32_FLOAT) {
return blit_image_helper.ConvertD32ToR32(dst, src_view); return blit_image_helper.ConvertD32ToR32(dst, src_view);
@ -1222,6 +1237,12 @@ void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, Im
} }
break; break;
case PixelFormat::D32_FLOAT: case PixelFormat::D32_FLOAT:
if (src_view.format == PixelFormat::A8B8G8R8_UNORM ||
src_view.format == PixelFormat::B8G8R8A8_UNORM ||
src_view.format == PixelFormat::A8B8G8R8_SRGB ||
src_view.format == PixelFormat::B8G8R8A8_SRGB) {
return blit_image_helper.ConvertABGR8ToD32F(dst, src_view);
}
if (src_view.format == PixelFormat::R32_FLOAT) { if (src_view.format == PixelFormat::R32_FLOAT) {
return blit_image_helper.ConvertR32ToD32(dst, src_view); return blit_image_helper.ConvertR32ToD32(dst, src_view);
} }
@ -2034,7 +2055,7 @@ void TextureCacheRuntime::TransitionImageLayout(Image& image) {
}, },
}; };
scheduler.RequestOutsideRenderPassOperationContext(); scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([barrier = barrier](vk::CommandBuffer cmdbuf) { scheduler.Record([barrier](vk::CommandBuffer cmdbuf) {
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, barrier); VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, barrier);
}); });

View File

@ -68,6 +68,7 @@ struct LevelInfo {
Extent2D tile_size; Extent2D tile_size;
u32 bpp_log2; u32 bpp_log2;
u32 tile_width_spacing; u32 tile_width_spacing;
u32 num_levels;
}; };
[[nodiscard]] constexpr u32 AdjustTileSize(u32 shift, u32 unit_factor, u32 dimension) { [[nodiscard]] constexpr u32 AdjustTileSize(u32 shift, u32 unit_factor, u32 dimension) {
@ -118,11 +119,11 @@ template <u32 GOB_EXTENT>
} }
[[nodiscard]] constexpr Extent3D AdjustMipBlockSize(Extent3D num_tiles, Extent3D block_size, [[nodiscard]] constexpr Extent3D AdjustMipBlockSize(Extent3D num_tiles, Extent3D block_size,
u32 level) { u32 level, u32 num_levels) {
return { return {
.width = AdjustMipBlockSize<GOB_SIZE_X>(num_tiles.width, block_size.width, level), .width = AdjustMipBlockSize<GOB_SIZE_X>(num_tiles.width, block_size.width, level),
.height = AdjustMipBlockSize<GOB_SIZE_Y>(num_tiles.height, block_size.height, level), .height = AdjustMipBlockSize<GOB_SIZE_Y>(num_tiles.height, block_size.height, level),
.depth = level == 0 .depth = level == 0 && num_levels == 1
? block_size.depth ? block_size.depth
: AdjustMipBlockSize<GOB_SIZE_Z>(num_tiles.depth, block_size.depth, level), : AdjustMipBlockSize<GOB_SIZE_Z>(num_tiles.depth, block_size.depth, level),
}; };
@ -166,7 +167,7 @@ template <u32 GOB_EXTENT>
} }
[[nodiscard]] constexpr Extent3D TileShift(const LevelInfo& info, u32 level) { [[nodiscard]] constexpr Extent3D TileShift(const LevelInfo& info, u32 level) {
if (level == 0) { if (level == 0 && info.num_levels == 1) {
return Extent3D{ return Extent3D{
.width = info.block.width, .width = info.block.width,
.height = info.block.height, .height = info.block.height,
@ -257,7 +258,7 @@ template <u32 GOB_EXTENT>
} }
[[nodiscard]] constexpr LevelInfo MakeLevelInfo(PixelFormat format, Extent3D size, Extent3D block, [[nodiscard]] constexpr LevelInfo MakeLevelInfo(PixelFormat format, Extent3D size, Extent3D block,
u32 tile_width_spacing) { u32 tile_width_spacing, u32 num_levels) {
const u32 bytes_per_block = BytesPerBlock(format); const u32 bytes_per_block = BytesPerBlock(format);
return { return {
.size = .size =
@ -270,16 +271,18 @@ template <u32 GOB_EXTENT>
.tile_size = DefaultBlockSize(format), .tile_size = DefaultBlockSize(format),
.bpp_log2 = BytesPerBlockLog2(bytes_per_block), .bpp_log2 = BytesPerBlockLog2(bytes_per_block),
.tile_width_spacing = tile_width_spacing, .tile_width_spacing = tile_width_spacing,
.num_levels = num_levels,
}; };
} }
[[nodiscard]] constexpr LevelInfo MakeLevelInfo(const ImageInfo& info) { [[nodiscard]] constexpr LevelInfo MakeLevelInfo(const ImageInfo& info) {
return MakeLevelInfo(info.format, info.size, info.block, info.tile_width_spacing); return MakeLevelInfo(info.format, info.size, info.block, info.tile_width_spacing,
info.resources.levels);
} }
[[nodiscard]] constexpr u32 CalculateLevelOffset(PixelFormat format, Extent3D size, Extent3D block, [[nodiscard]] constexpr u32 CalculateLevelOffset(PixelFormat format, Extent3D size, Extent3D block,
u32 tile_width_spacing, u32 level) { u32 tile_width_spacing, u32 level) {
const LevelInfo info = MakeLevelInfo(format, size, block, tile_width_spacing); const LevelInfo info = MakeLevelInfo(format, size, block, tile_width_spacing, level);
u32 offset = 0; u32 offset = 0;
for (u32 current_level = 0; current_level < level; ++current_level) { for (u32 current_level = 0; current_level < level; ++current_level) {
offset += CalculateLevelSize(info, current_level); offset += CalculateLevelSize(info, current_level);
@ -466,7 +469,7 @@ template <u32 GOB_EXTENT>
}; };
const u32 bpp_log2 = BytesPerBlockLog2(info.format); const u32 bpp_log2 = BytesPerBlockLog2(info.format);
const u32 alignment = StrideAlignment(num_tiles, info.block, bpp_log2, info.tile_width_spacing); const u32 alignment = StrideAlignment(num_tiles, info.block, bpp_log2, info.tile_width_spacing);
const Extent3D mip_block = AdjustMipBlockSize(num_tiles, info.block, 0); const Extent3D mip_block = AdjustMipBlockSize(num_tiles, info.block, 0, info.resources.levels);
return Extent3D{ return Extent3D{
.width = Common::AlignUpLog2(num_tiles.width, alignment), .width = Common::AlignUpLog2(num_tiles.width, alignment),
.height = Common::AlignUpLog2(num_tiles.height, GOB_SIZE_Y_SHIFT + mip_block.height), .height = Common::AlignUpLog2(num_tiles.height, GOB_SIZE_Y_SHIFT + mip_block.height),
@ -533,7 +536,8 @@ void SwizzleBlockLinearImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr
UNIMPLEMENTED_IF(copy.image_extent != level_size); UNIMPLEMENTED_IF(copy.image_extent != level_size);
const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
const Extent3D block = AdjustMipBlockSize(num_tiles, level_info.block, level); const Extent3D block =
AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels);
size_t host_offset = copy.buffer_offset; size_t host_offset = copy.buffer_offset;
@ -698,7 +702,7 @@ u32 CalculateLevelStrideAlignment(const ImageInfo& info, u32 level) {
const Extent2D tile_size = DefaultBlockSize(info.format); const Extent2D tile_size = DefaultBlockSize(info.format);
const Extent3D level_size = AdjustMipSize(info.size, level); const Extent3D level_size = AdjustMipSize(info.size, level);
const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
const Extent3D block = AdjustMipBlockSize(num_tiles, info.block, level); const Extent3D block = AdjustMipBlockSize(num_tiles, info.block, level, info.resources.levels);
const u32 bpp_log2 = BytesPerBlockLog2(info.format); const u32 bpp_log2 = BytesPerBlockLog2(info.format);
return StrideAlignment(num_tiles, block, bpp_log2, info.tile_width_spacing); return StrideAlignment(num_tiles, block, bpp_log2, info.tile_width_spacing);
} }
@ -887,7 +891,8 @@ boost::container::small_vector<BufferImageCopy, 16> UnswizzleImage(Tegra::Memory
.image_extent = level_size, .image_extent = level_size,
}; };
const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
const Extent3D block = AdjustMipBlockSize(num_tiles, level_info.block, level); const Extent3D block =
AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels);
const u32 stride_alignment = StrideAlignment(num_tiles, info.block, gob, bpp_log2); const u32 stride_alignment = StrideAlignment(num_tiles, info.block, gob, bpp_log2);
size_t guest_layer_offset = 0; size_t guest_layer_offset = 0;
@ -1041,7 +1046,7 @@ Extent3D MipBlockSize(const ImageInfo& info, u32 level) {
const Extent2D tile_size = DefaultBlockSize(info.format); const Extent2D tile_size = DefaultBlockSize(info.format);
const Extent3D level_size = AdjustMipSize(info.size, level); const Extent3D level_size = AdjustMipSize(info.size, level);
const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
return AdjustMipBlockSize(num_tiles, level_info.block, level); return AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels);
} }
boost::container::small_vector<SwizzleParameters, 16> FullUploadSwizzles(const ImageInfo& info) { boost::container::small_vector<SwizzleParameters, 16> FullUploadSwizzles(const ImageInfo& info) {
@ -1063,7 +1068,8 @@ boost::container::small_vector<SwizzleParameters, 16> FullUploadSwizzles(const I
for (s32 level = 0; level < num_levels; ++level) { for (s32 level = 0; level < num_levels; ++level) {
const Extent3D level_size = AdjustMipSize(size, level); const Extent3D level_size = AdjustMipSize(size, level);
const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
const Extent3D block = AdjustMipBlockSize(num_tiles, level_info.block, level); const Extent3D block =
AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels);
params[level] = SwizzleParameters{ params[level] = SwizzleParameters{
.num_tiles = num_tiles, .num_tiles = num_tiles,
.block = block, .block = block,
@ -1292,11 +1298,11 @@ u32 MapSizeBytes(const ImageBase& image) {
} }
} }
static_assert(CalculateLevelSize(LevelInfo{{1920, 1080, 1}, {0, 2, 0}, {1, 1}, 2, 0}, 0) == static_assert(CalculateLevelSize(LevelInfo{{1920, 1080, 1}, {0, 2, 0}, {1, 1}, 2, 0, 1}, 0) ==
0x7f8000); 0x7f8000);
static_assert(CalculateLevelSize(LevelInfo{{32, 32, 1}, {0, 0, 4}, {1, 1}, 4, 0}, 0) == 0x40000); static_assert(CalculateLevelSize(LevelInfo{{32, 32, 1}, {0, 0, 4}, {1, 1}, 4, 0, 1}, 0) == 0x40000);
static_assert(CalculateLevelSize(LevelInfo{{128, 8, 1}, {0, 4, 0}, {1, 1}, 4, 0}, 0) == 0x40000); static_assert(CalculateLevelSize(LevelInfo{{128, 8, 1}, {0, 4, 0}, {1, 1}, 4, 0, 1}, 0) == 0x40000);
static_assert(CalculateLevelOffset(PixelFormat::R8_SINT, {1920, 1080, 1}, {0, 2, 0}, 0, 7) == static_assert(CalculateLevelOffset(PixelFormat::R8_SINT, {1920, 1080, 1}, {0, 2, 0}, 0, 7) ==
0x2afc00); 0x2afc00);

View File

@ -384,7 +384,7 @@ if (USE_DISCORD_PRESENCE)
discord_impl.cpp discord_impl.cpp
discord_impl.h discord_impl.h
) )
target_link_libraries(yuzu PRIVATE DiscordRPC::discord-rpc httplib::httplib) target_link_libraries(yuzu PRIVATE DiscordRPC::discord-rpc httplib::httplib Qt${QT_MAJOR_VERSION}::Network)
target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE) target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE)
endif() endif()

View File

@ -128,8 +128,8 @@ const std::array<UISettings::Shortcut, 22> Config::default_hotkeys{{
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral(""), Qt::WindowShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral("R+Plus+Minus"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"), QStringLiteral(""), Qt::WindowShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"), QStringLiteral("L+Plus+Minus"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut, false}},

View File

@ -115,17 +115,9 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
for (std::size_t i = 0; i < player_tabs.size(); ++i) { for (std::size_t i = 0; i < player_tabs.size(); ++i) {
player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i])); player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i]));
player_tabs[i]->layout()->addWidget(player_controllers[i]); player_tabs[i]->layout()->addWidget(player_controllers[i]);
connect(player_controllers[i], &ConfigureInputPlayer::Connected, [&, i](bool is_connected) { connect(player_connected[i], &QCheckBox::clicked, [this, i](int checked) {
// Ensures that the controllers are always connected in sequential order // Ensures that the controllers are always connected in sequential order
if (is_connected) { this->propagateMouseClickOnPlayers(i, checked, true);
for (std::size_t index = 0; index <= i; ++index) {
player_connected[index]->setChecked(is_connected);
}
} else {
for (std::size_t index = i; index < player_tabs.size(); ++index) {
player_connected[index]->setChecked(is_connected);
}
}
}); });
connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this, connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this,
&ConfigureInput::UpdateAllInputDevices); &ConfigureInput::UpdateAllInputDevices);
@ -183,6 +175,30 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
LoadConfiguration(); LoadConfiguration();
} }
void ConfigureInput::propagateMouseClickOnPlayers(size_t player_index, bool checked, bool origin) {
// Origin has already been toggled
if (!origin) {
player_connected[player_index]->setChecked(checked);
}
if (checked) {
// Check all previous buttons when checked
if (player_index > 0) {
propagateMouseClickOnPlayers(player_index - 1, checked, false);
}
} else {
// Unchecked all following buttons when unchecked
if (player_index < player_tabs.size() - 1) {
// Reconnect current player if it was the last one checked
// (player number was reduced by more than one)
if (origin && player_connected[player_index + 1]->checkState() == Qt::Checked) {
player_connected[player_index]->setCheckState(Qt::Checked);
}
propagateMouseClickOnPlayers(player_index + 1, checked, false);
}
}
}
QList<QWidget*> ConfigureInput::GetSubTabs() const { QList<QWidget*> ConfigureInput::GetSubTabs() const {
return { return {
ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5, ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5,

View File

@ -56,6 +56,7 @@ private:
void UpdateDockedState(bool is_handheld); void UpdateDockedState(bool is_handheld);
void UpdateAllInputDevices(); void UpdateAllInputDevices();
void UpdateAllInputProfiles(std::size_t player_index); void UpdateAllInputProfiles(std::size_t player_index);
void propagateMouseClickOnPlayers(size_t player_index, bool origin, bool checked);
/// Load configuration settings. /// Load configuration settings.
void LoadConfiguration(); void LoadConfiguration();

View File

@ -157,6 +157,7 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
INSERT(UISettings, select_user_on_boot, "Prompt for user on game boot", ""); INSERT(UISettings, select_user_on_boot, "Prompt for user on game boot", "");
INSERT(UISettings, pause_when_in_background, "Pause emulation when in background", ""); INSERT(UISettings, pause_when_in_background, "Pause emulation when in background", "");
INSERT(UISettings, confirm_before_closing, "Confirm exit while emulation is running", ""); INSERT(UISettings, confirm_before_closing, "Confirm exit while emulation is running", "");
INSERT(UISettings, confirm_before_stopping, "Confirm before stopping emulation", "");
INSERT(UISettings, hide_mouse, "Hide mouse on inactivity", ""); INSERT(UISettings, hide_mouse, "Hide mouse on inactivity", "");
INSERT(UISettings, controller_applet_disabled, "Disable controller applet", ""); INSERT(UISettings, controller_applet_disabled, "Disable controller applet", "");
@ -383,6 +384,13 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) {
translations->insert( translations->insert(
{Settings::EnumMetadata<Settings::ConsoleMode>::Index(), {Settings::EnumMetadata<Settings::ConsoleMode>::Index(),
{PAIR(ConsoleMode, Docked, "Docked"), PAIR(ConsoleMode, Handheld, "Handheld")}}); {PAIR(ConsoleMode, Docked, "Docked"), PAIR(ConsoleMode, Handheld, "Handheld")}});
translations->insert(
{Settings::EnumMetadata<Settings::ConfirmStop>::Index(),
{
PAIR(ConfirmStop, Ask_Always, "Always ask (Default)"),
PAIR(ConfirmStop, Ask_Based_On_Game, "Only if game specifies not to stop"),
PAIR(ConfirmStop, Ask_Never, "Never ask"),
}});
#undef PAIR #undef PAIR
#undef CTX_PAIR #undef CTX_PAIR

View File

@ -211,7 +211,7 @@ void GMainWindow::ShowTelemetryCallout() {
tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous " tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous "
"data is collected</a> to help improve yuzu. " "data is collected</a> to help improve yuzu. "
"<br/><br/>Would you like to share your usage data with us?"); "<br/><br/>Would you like to share your usage data with us?");
if (QMessageBox::question(this, tr("Telemetry"), telemetry_message) != QMessageBox::Yes) { if (!question(this, tr("Telemetry"), telemetry_message)) {
Settings::values.enable_telemetry = false; Settings::values.enable_telemetry = false;
system->ApplySettings(); system->ApplySettings();
} }
@ -2420,9 +2420,8 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT
} }
}(); }();
if (QMessageBox::question(this, tr("Remove Entry"), entry_question, if (!question(this, tr("Remove Entry"), entry_question, QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes | QMessageBox::No, QMessageBox::No)) {
QMessageBox::No) != QMessageBox::Yes) {
return; return;
} }
@ -2521,8 +2520,8 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ
} }
}(); }();
if (QMessageBox::question(this, tr("Remove File"), question, QMessageBox::Yes | QMessageBox::No, if (!GMainWindow::question(this, tr("Remove File"), question,
QMessageBox::No) != QMessageBox::Yes) { QMessageBox::Yes | QMessageBox::No, QMessageBox::No)) {
return; return;
} }
@ -3409,10 +3408,13 @@ void GMainWindow::OnRestartGame() {
if (!system->IsPoweredOn()) { if (!system->IsPoweredOn()) {
return; return;
} }
// Make a copy since ShutdownGame edits game_path
const auto current_game = QString(current_game_path); if (ConfirmShutdownGame()) {
ShutdownGame(); // Make a copy since ShutdownGame edits game_path
BootGame(current_game); const auto current_game = QString(current_game_path);
ShutdownGame();
BootGame(current_game);
}
} }
void GMainWindow::OnPauseGame() { void GMainWindow::OnPauseGame() {
@ -3434,18 +3436,39 @@ void GMainWindow::OnPauseContinueGame() {
} }
void GMainWindow::OnStopGame() { void GMainWindow::OnStopGame() {
if (system->GetExitLocked() && !ConfirmForceLockedExit()) { if (ConfirmShutdownGame()) {
return; play_time_manager->Stop();
// Update game list to show new play time
game_list->PopulateAsync(UISettings::values.game_dirs);
if (OnShutdownBegin()) {
OnShutdownBeginDialog();
} else {
OnEmulationStopped();
}
} }
}
play_time_manager->Stop(); bool GMainWindow::ConfirmShutdownGame() {
// Update game list to show new play time if (UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Always) {
game_list->PopulateAsync(UISettings::values.game_dirs); if (system->GetExitLocked()) {
if (OnShutdownBegin()) { if (!ConfirmForceLockedExit()) {
OnShutdownBeginDialog(); return false;
}
} else {
if (!ConfirmChangeGame()) {
return false;
}
}
} else { } else {
OnEmulationStopped(); if (UISettings::values.confirm_before_stopping.GetValue() ==
ConfirmStop::Ask_Based_On_Game &&
system->GetExitLocked()) {
if (!ConfirmForceLockedExit()) {
return false;
}
}
} }
return true;
} }
void GMainWindow::OnLoadComplete() { void GMainWindow::OnLoadComplete() {
@ -3825,22 +3848,11 @@ void GMainWindow::OnTasRecord() {
const bool is_recording = input_subsystem->GetTas()->Record(); const bool is_recording = input_subsystem->GetTas()->Record();
if (!is_recording) { if (!is_recording) {
is_tas_recording_dialog_active = true; is_tas_recording_dialog_active = true;
ControllerNavigation* controller_navigation =
new ControllerNavigation(system->HIDCore(), this); bool answer = question(this, tr("TAS Recording"), tr("Overwrite file of player 1?"),
// Use QMessageBox instead of question so we can link controller navigation QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
QMessageBox* box_dialog = new QMessageBox();
box_dialog->setWindowTitle(tr("TAS Recording")); input_subsystem->GetTas()->SaveRecording(answer);
box_dialog->setText(tr("Overwrite file of player 1?"));
box_dialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No);
box_dialog->setDefaultButton(QMessageBox::Yes);
connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
[box_dialog](Qt::Key key) {
QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
QCoreApplication::postEvent(box_dialog, event);
});
int res = box_dialog->exec();
controller_navigation->UnloadController();
input_subsystem->GetTas()->SaveRecording(res == QMessageBox::Yes);
is_tas_recording_dialog_active = false; is_tas_recording_dialog_active = false;
} }
OnTasStateChanged(); OnTasStateChanged();
@ -4081,6 +4093,29 @@ void GMainWindow::OnLoadAmiibo() {
LoadAmiibo(filename); LoadAmiibo(filename);
} }
bool GMainWindow::question(QWidget* parent, const QString& title, const QString& text,
QMessageBox::StandardButtons buttons,
QMessageBox::StandardButton defaultButton) {
QMessageBox* box_dialog = new QMessageBox(parent);
box_dialog->setWindowTitle(title);
box_dialog->setText(text);
box_dialog->setStandardButtons(buttons);
box_dialog->setDefaultButton(defaultButton);
ControllerNavigation* controller_navigation =
new ControllerNavigation(system->HIDCore(), box_dialog);
connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
[box_dialog](Qt::Key key) {
QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
QCoreApplication::postEvent(box_dialog, event);
});
int res = box_dialog->exec();
controller_navigation->UnloadController();
return res == QMessageBox::Yes;
}
void GMainWindow::LoadAmiibo(const QString& filename) { void GMainWindow::LoadAmiibo(const QString& filename) {
auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo(); auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo();
const QString title = tr("Error loading Amiibo data"); const QString title = tr("Error loading Amiibo data");
@ -4814,8 +4849,7 @@ bool GMainWindow::ConfirmClose() {
return true; return true;
} }
const auto text = tr("Are you sure you want to close yuzu?"); const auto text = tr("Are you sure you want to close yuzu?");
const auto answer = QMessageBox::question(this, tr("yuzu"), text); return question(this, tr("yuzu"), text);
return answer != QMessageBox::No;
} }
void GMainWindow::closeEvent(QCloseEvent* event) { void GMainWindow::closeEvent(QCloseEvent* event) {
@ -4908,11 +4942,11 @@ bool GMainWindow::ConfirmChangeGame() {
if (emu_thread == nullptr) if (emu_thread == nullptr)
return true; return true;
const auto answer = QMessageBox::question( // Use custom question to link controller navigation
return question(
this, tr("yuzu"), this, tr("yuzu"),
tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."), tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No); QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
return answer != QMessageBox::No;
} }
bool GMainWindow::ConfirmForceLockedExit() { bool GMainWindow::ConfirmForceLockedExit() {
@ -4922,8 +4956,7 @@ bool GMainWindow::ConfirmForceLockedExit() {
const auto text = tr("The currently running application has requested yuzu to not exit.\n\n" const auto text = tr("The currently running application has requested yuzu to not exit.\n\n"
"Would you like to bypass this and exit anyway?"); "Would you like to bypass this and exit anyway?");
const auto answer = QMessageBox::question(this, tr("yuzu"), text); return question(this, tr("yuzu"), text);
return answer != QMessageBox::No;
} }
void GMainWindow::RequestGameExit() { void GMainWindow::RequestGameExit() {

View File

@ -7,6 +7,7 @@
#include <optional> #include <optional>
#include <QMainWindow> #include <QMainWindow>
#include <QMessageBox>
#include <QTimer> #include <QTimer>
#include <QTranslator> #include <QTranslator>
@ -15,6 +16,7 @@
#include "input_common/drivers/tas_input.h" #include "input_common/drivers/tas_input.h"
#include "yuzu/compatibility_list.h" #include "yuzu/compatibility_list.h"
#include "yuzu/hotkeys.h" #include "yuzu/hotkeys.h"
#include "yuzu/util/controller_navigation.h"
#ifdef __unix__ #ifdef __unix__
#include <QVariant> #include <QVariant>
@ -424,6 +426,11 @@ private:
bool CheckSystemArchiveDecryption(); bool CheckSystemArchiveDecryption();
bool CheckFirmwarePresence(); bool CheckFirmwarePresence();
void ConfigureFilesystemProvider(const std::string& filepath); void ConfigureFilesystemProvider(const std::string& filepath);
/**
* Open (or not) the right confirm dialog based on current setting and game exit lock
* @returns true if the player confirmed or the settings do no require it
*/
bool ConfirmShutdownGame();
QString GetTasStateDescription() const; QString GetTasStateDescription() const;
bool CreateShortcut(const std::string& shortcut_path, const std::string& title, bool CreateShortcut(const std::string& shortcut_path, const std::string& title,
@ -431,6 +438,17 @@ private:
const std::string& command, const std::string& arguments, const std::string& command, const std::string& arguments,
const std::string& categories, const std::string& keywords); const std::string& categories, const std::string& keywords);
/**
* Mimic the behavior of QMessageBox::question but link controller navigation to the dialog
* The only difference is that it returns a boolean.
*
* @returns true if buttons contains QMessageBox::Yes and the user clicks on the "Yes" button.
*/
bool question(QWidget* parent, const QString& title, const QString& text,
QMessageBox::StandardButtons buttons =
QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No),
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
std::unique_ptr<Ui::MainWindow> ui; std::unique_ptr<Ui::MainWindow> ui;
std::unique_ptr<Core::System> system; std::unique_ptr<Core::System> system;

View File

@ -16,7 +16,9 @@
#include "common/settings_enums.h" #include "common/settings_enums.h"
using Settings::Category; using Settings::Category;
using Settings::ConfirmStop;
using Settings::Setting; using Settings::Setting;
using Settings::SwitchableSetting;
#ifndef CANNOT_EXPLICITLY_INSTANTIATE #ifndef CANNOT_EXPLICITLY_INSTANTIATE
namespace Settings { namespace Settings {
@ -94,6 +96,15 @@ struct Values {
Setting<bool> confirm_before_closing{ Setting<bool> confirm_before_closing{
linkage, true, "confirmClose", Category::UiGeneral, Settings::Specialization::Default, linkage, true, "confirmClose", Category::UiGeneral, Settings::Specialization::Default,
true, true}; true, true};
SwitchableSetting<ConfirmStop> confirm_before_stopping{linkage,
ConfirmStop::Ask_Always,
"confirmStop",
Category::UiGeneral,
Settings::Specialization::Default,
true,
true};
Setting<bool> first_start{linkage, true, "firstStart", Category::Ui}; Setting<bool> first_start{linkage, true, "firstStart", Category::Ui};
Setting<bool> pause_when_in_background{linkage, Setting<bool> pause_when_in_background{linkage,
false, false,