mirror of https://git.citron-emu.org/citron/emu
renderer/friend: Improve reversed depth handling and Friend service
This commit makes two significant improvements: 1. Vulkan renderer: - Detect and properly handle reversed depth buffers (clear_depth < 0.5) - Force depth write enable when needed with reversed depth - Use GREATER_OR_EQUAL comparison for reversed depth scenarios - Fix transparency issues in games like Civilization 7 by adjusting blend factors - Add detailed logging for depth buffer operations 2. Friend service: - Implement previously stubbed functions including EnsureFriendListAvailable and EnsureBlockedUserListAvailable - Add proper event signaling to prevent games from hanging - Implement Cancel function for improved compatibility - Update copyright notice for the Citron project These changes improve compatibility with modern games using reversed depth buffers and prevent hangs in titles that rely on Friend service functionality. Co-authored-by: m33ts4k0z <m33ts4k0z@citron-emu.org> Co-committed-by: m33ts4k0z <m33ts4k0z@citron-emu.org> Signed-off-by: Zephyron <zephyron@citron-emu.org>
This commit is contained in:
parent
5d952717ff
commit
0dac3c1dbd
|
@ -1,4 +1,5 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
@ -28,12 +29,12 @@ public:
|
||||||
{10102, nullptr, "UpdateFriendInfo"},
|
{10102, nullptr, "UpdateFriendInfo"},
|
||||||
{10110, nullptr, "GetFriendProfileImage"},
|
{10110, nullptr, "GetFriendProfileImage"},
|
||||||
{10120, &IFriendService::CheckFriendListAvailability, "CheckFriendListAvailability"},
|
{10120, &IFriendService::CheckFriendListAvailability, "CheckFriendListAvailability"},
|
||||||
{10121, nullptr, "EnsureFriendListAvailable"},
|
{10121, &IFriendService::EnsureFriendListAvailable, "EnsureFriendListAvailable"},
|
||||||
{10200, nullptr, "SendFriendRequestForApplication"},
|
{10200, nullptr, "SendFriendRequestForApplication"},
|
||||||
{10211, nullptr, "AddFacedFriendRequestForApplication"},
|
{10211, nullptr, "AddFacedFriendRequestForApplication"},
|
||||||
{10400, &IFriendService::GetBlockedUserListIds, "GetBlockedUserListIds"},
|
{10400, &IFriendService::GetBlockedUserListIds, "GetBlockedUserListIds"},
|
||||||
{10420, &IFriendService::CheckBlockedUserListAvailability, "CheckBlockedUserListAvailability"},
|
{10420, &IFriendService::CheckBlockedUserListAvailability, "CheckBlockedUserListAvailability"},
|
||||||
{10421, nullptr, "EnsureBlockedUserListAvailable"},
|
{10421, &IFriendService::EnsureBlockedUserListAvailable, "EnsureBlockedUserListAvailable"},
|
||||||
{10500, nullptr, "GetProfileList"},
|
{10500, nullptr, "GetProfileList"},
|
||||||
{10600, nullptr, "DeclareOpenOnlinePlaySession"},
|
{10600, nullptr, "DeclareOpenOnlinePlaySession"},
|
||||||
{10601, &IFriendService::DeclareCloseOnlinePlaySession, "DeclareCloseOnlinePlaySession"},
|
{10601, &IFriendService::DeclareCloseOnlinePlaySession, "DeclareCloseOnlinePlaySession"},
|
||||||
|
@ -166,11 +167,27 @@ private:
|
||||||
|
|
||||||
LOG_WARNING(Service_Friend, "(STUBBED) called, uuid=0x{}", uuid.RawString());
|
LOG_WARNING(Service_Friend, "(STUBBED) called, uuid=0x{}", uuid.RawString());
|
||||||
|
|
||||||
|
// Signal the completion event to unblock any waiting threads
|
||||||
|
completion_event->Signal();
|
||||||
|
|
||||||
IPC::ResponseBuilder rb{ctx, 3};
|
IPC::ResponseBuilder rb{ctx, 3};
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
rb.Push(true);
|
rb.Push(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EnsureFriendListAvailable(HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
const auto uuid{rp.PopRaw<Common::UUID>()};
|
||||||
|
|
||||||
|
LOG_WARNING(Service_Friend, "(STUBBED) EnsureFriendListAvailable called, uuid=0x{}", uuid.RawString());
|
||||||
|
|
||||||
|
// Signal the completion event to unblock any waiting threads
|
||||||
|
completion_event->Signal();
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
void GetBlockedUserListIds(HLERequestContext& ctx) {
|
void GetBlockedUserListIds(HLERequestContext& ctx) {
|
||||||
// This is safe to stub, as there should be no adverse consequences from reporting no
|
// This is safe to stub, as there should be no adverse consequences from reporting no
|
||||||
// blocked users.
|
// blocked users.
|
||||||
|
@ -186,11 +203,45 @@ private:
|
||||||
|
|
||||||
LOG_WARNING(Service_Friend, "(STUBBED) called, uuid=0x{}", uuid.RawString());
|
LOG_WARNING(Service_Friend, "(STUBBED) called, uuid=0x{}", uuid.RawString());
|
||||||
|
|
||||||
|
// Signal the completion event to unblock any waiting threads
|
||||||
|
completion_event->Signal();
|
||||||
|
|
||||||
IPC::ResponseBuilder rb{ctx, 3};
|
IPC::ResponseBuilder rb{ctx, 3};
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
rb.Push(true);
|
rb.Push(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EnsureBlockedUserListAvailable(HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp{ctx};
|
||||||
|
const auto uuid{rp.PopRaw<Common::UUID>()};
|
||||||
|
|
||||||
|
LOG_WARNING(Service_Friend, "(STUBBED) EnsureBlockedUserListAvailable called, uuid=0x{}", uuid.RawString());
|
||||||
|
|
||||||
|
// Signal the completion event to unblock any waiting threads
|
||||||
|
completion_event->Signal();
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetReceivedFriendInvitationCountCache(HLERequestContext& ctx) {
|
||||||
|
LOG_DEBUG(Service_Friend, "(STUBBED) called, check in out");
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 3};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
rb.Push(0); // Zero invitations
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cancel(HLERequestContext& ctx) {
|
||||||
|
LOG_WARNING(Service_Friend, "Cancel called - returning immediately");
|
||||||
|
|
||||||
|
// Signal the completion event to unblock any waiting threads
|
||||||
|
completion_event->Signal();
|
||||||
|
|
||||||
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
void DeclareCloseOnlinePlaySession(HLERequestContext& ctx) {
|
void DeclareCloseOnlinePlaySession(HLERequestContext& ctx) {
|
||||||
// Stub used by Splatoon 2
|
// Stub used by Splatoon 2
|
||||||
LOG_WARNING(Service_Friend, "(STUBBED) called");
|
LOG_WARNING(Service_Friend, "(STUBBED) called");
|
||||||
|
@ -248,14 +299,6 @@ private:
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetReceivedFriendInvitationCountCache(HLERequestContext& ctx) {
|
|
||||||
LOG_DEBUG(Service_Friend, "(STUBBED) called, check in out");
|
|
||||||
|
|
||||||
IPC::ResponseBuilder rb{ctx, 3};
|
|
||||||
rb.Push(ResultSuccess);
|
|
||||||
rb.Push(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
KernelHelpers::ServiceContext service_context;
|
KernelHelpers::ServiceContext service_context;
|
||||||
|
|
||||||
Kernel::KEvent* completion_event;
|
Kernel::KEvent* completion_event;
|
||||||
|
@ -287,6 +330,9 @@ private:
|
||||||
void GetEvent(HLERequestContext& ctx) {
|
void GetEvent(HLERequestContext& ctx) {
|
||||||
LOG_DEBUG(Service_Friend, "called");
|
LOG_DEBUG(Service_Friend, "called");
|
||||||
|
|
||||||
|
// Signal the notification event to unblock any waiting threads
|
||||||
|
notification_event->Signal();
|
||||||
|
|
||||||
IPC::ResponseBuilder rb{ctx, 2, 1};
|
IPC::ResponseBuilder rb{ctx, 2, 1};
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
rb.PushCopyObjects(notification_event->GetReadableEvent());
|
rb.PushCopyObjects(notification_event->GetReadableEvent());
|
||||||
|
@ -363,10 +409,11 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
void Module::Interface::CreateFriendService(HLERequestContext& ctx) {
|
void Module::Interface::CreateFriendService(HLERequestContext& ctx) {
|
||||||
|
LOG_DEBUG(Service_Friend, "CreateFriendService called");
|
||||||
|
|
||||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
rb.PushIpcInterface<IFriendService>(system);
|
rb.PushIpcInterface<IFriendService>(system);
|
||||||
LOG_DEBUG(Service_Friend, "called");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Module::Interface::CreateNotificationService(HLERequestContext& ctx) {
|
void Module::Interface::CreateNotificationService(HLERequestContext& ctx) {
|
||||||
|
|
|
@ -369,6 +369,9 @@ void RasterizerVulkan::Clear(u32 layer_count) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only log the clear depth value - there's no depth or stencil members in ClearSurface
|
||||||
|
LOG_INFO(Render_Vulkan, "Clear depth value: {}", regs.clear_depth);
|
||||||
|
|
||||||
std::scoped_lock lock{texture_cache.mutex};
|
std::scoped_lock lock{texture_cache.mutex};
|
||||||
texture_cache.UpdateRenderTargets(true);
|
texture_cache.UpdateRenderTargets(true);
|
||||||
const Framebuffer* const framebuffer = texture_cache.GetFramebuffer();
|
const Framebuffer* const framebuffer = texture_cache.GetFramebuffer();
|
||||||
|
@ -974,7 +977,8 @@ void RasterizerVulkan::UpdateDynamicStates() {
|
||||||
bool hasFloat = std::any_of(
|
bool hasFloat = std::any_of(
|
||||||
regs.vertex_attrib_format.begin(), regs.vertex_attrib_format.end(),
|
regs.vertex_attrib_format.begin(), regs.vertex_attrib_format.end(),
|
||||||
[](const auto& attrib) {
|
[](const auto& attrib) {
|
||||||
return attrib.type == Tegra::Engines::Maxwell3D::Regs::VertexAttribute::Type::Float;
|
return attrib.type ==
|
||||||
|
Tegra::Engines::Maxwell3D::Regs::VertexAttribute::Type::Float;
|
||||||
});
|
});
|
||||||
|
|
||||||
// For AMD drivers, disable logic_op if a float attribute is present.
|
// For AMD drivers, disable logic_op if a float attribute is present.
|
||||||
|
@ -1286,8 +1290,33 @@ void RasterizerVulkan::UpdateDepthWriteEnable(Tegra::Engines::Maxwell3D::Regs& r
|
||||||
if (!state_tracker.TouchDepthWriteEnable()) {
|
if (!state_tracker.TouchDepthWriteEnable()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
scheduler.Record([enable = regs.depth_write_enabled](vk::CommandBuffer cmdbuf) {
|
|
||||||
cmdbuf.SetDepthWriteEnableEXT(enable);
|
// Check if we're likely in a reversed depth scenario (clear depth near 0)
|
||||||
|
const bool likely_reversed_depth = (regs.clear_depth < 0.5f);
|
||||||
|
|
||||||
|
// Get the original depth write enable state
|
||||||
|
bool depth_write_enable = regs.depth_write_enabled != 0;
|
||||||
|
bool modified = false;
|
||||||
|
|
||||||
|
// Only force enable depth writes when:
|
||||||
|
// 1. We're using reversed depth (clear_depth < 0.5)
|
||||||
|
// 2. Depth testing is enabled
|
||||||
|
// 3. Original depth writes are disabled
|
||||||
|
// This selectively fixes the issue without affecting other games
|
||||||
|
if (likely_reversed_depth && regs.depth_test_enable && !depth_write_enable) {
|
||||||
|
depth_write_enable = true;
|
||||||
|
modified = true;
|
||||||
|
LOG_INFO(Render_Vulkan, "Reversed depth buffer with disabled depth writes detected, "
|
||||||
|
"enabling depth writes for improved visibility");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log when depth write is being modified
|
||||||
|
LOG_INFO(Render_Vulkan, "Depth write set to: {} ({}){}",
|
||||||
|
depth_write_enable ? "enabled" : "disabled", regs.depth_write_enabled ? "1" : "0",
|
||||||
|
modified ? " -> modified" : "");
|
||||||
|
|
||||||
|
scheduler.Record([depth_write_enable](vk::CommandBuffer cmdbuf) {
|
||||||
|
cmdbuf.SetDepthWriteEnableEXT(depth_write_enable);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1371,7 +1400,27 @@ void RasterizerVulkan::UpdateDepthCompareOp(Tegra::Engines::Maxwell3D::Regs& reg
|
||||||
if (!state_tracker.TouchDepthCompareOp()) {
|
if (!state_tracker.TouchDepthCompareOp()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
scheduler.Record([func = regs.depth_test_func](vk::CommandBuffer cmdbuf) {
|
|
||||||
|
// Get the original depth comparison function requested by the game
|
||||||
|
auto original_func = regs.depth_test_func;
|
||||||
|
auto func_to_use = original_func;
|
||||||
|
|
||||||
|
// Check for clear depth - we'll use this to determine how to handle depth
|
||||||
|
const bool likely_reversed_depth = (regs.clear_depth < 0.5f);
|
||||||
|
|
||||||
|
// For reversed depth (clear_depth near 0), ALWAYS use GREATER_OR_EQUAL
|
||||||
|
// For normal depth (clear_depth near 1), ALWAYS use LESS_OR_EQUAL
|
||||||
|
if (likely_reversed_depth) {
|
||||||
|
func_to_use =
|
||||||
|
Maxwell::ComparisonOp::GreaterEqual_GL; // Maxwell::ComparisonOp::GreaterEqual_GL;
|
||||||
|
LOG_INFO(Render_Vulkan,
|
||||||
|
"Reversed depth buffer detected, using GREATER_OR_EQUAL for improved visibility");
|
||||||
|
} else {
|
||||||
|
func_to_use = regs.depth_test_func;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the modified depth comparison function
|
||||||
|
scheduler.Record([func = func_to_use](vk::CommandBuffer cmdbuf) {
|
||||||
cmdbuf.SetDepthCompareOpEXT(MaxwellToVK::ComparisonOp(func));
|
cmdbuf.SetDepthCompareOpEXT(MaxwellToVK::ComparisonOp(func));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1476,13 +1525,38 @@ void RasterizerVulkan::UpdateBlending(Tegra::Engines::Maxwell3D::Regs& regs) {
|
||||||
std::array<VkColorBlendEquationEXT, Maxwell::NumRenderTargets> setup_blends{};
|
std::array<VkColorBlendEquationEXT, Maxwell::NumRenderTargets> setup_blends{};
|
||||||
for (size_t index = 0; index < Maxwell::NumRenderTargets; index++) {
|
for (size_t index = 0; index < Maxwell::NumRenderTargets; index++) {
|
||||||
const auto blend_setup = [&]<typename T>(const T& guest_blend) {
|
const auto blend_setup = [&]<typename T>(const T& guest_blend) {
|
||||||
|
// Check if we're likely in a reversed depth scenario (clear depth near 0),
|
||||||
|
// which would indicate we're in Civilization 7 where models have transparency
|
||||||
|
// issues
|
||||||
|
const bool likely_reversed_depth = (regs.clear_depth < 0.5f);
|
||||||
|
|
||||||
auto& host_blend = setup_blends[index];
|
auto& host_blend = setup_blends[index];
|
||||||
host_blend.srcColorBlendFactor = MaxwellToVK::BlendFactor(guest_blend.color_source);
|
|
||||||
host_blend.dstColorBlendFactor = MaxwellToVK::BlendFactor(guest_blend.color_dest);
|
if (likely_reversed_depth) {
|
||||||
host_blend.colorBlendOp = MaxwellToVK::BlendEquation(guest_blend.color_op);
|
// For reversed depth scenarios (likely Civilization 7), force ONE/ZERO blend
|
||||||
host_blend.srcAlphaBlendFactor = MaxwellToVK::BlendFactor(guest_blend.alpha_source);
|
// factors to ensure models render as fully opaque
|
||||||
host_blend.dstAlphaBlendFactor = MaxwellToVK::BlendFactor(guest_blend.alpha_dest);
|
host_blend.srcColorBlendFactor =
|
||||||
host_blend.alphaBlendOp = MaxwellToVK::BlendEquation(guest_blend.alpha_op);
|
MaxwellToVK::BlendFactor(guest_blend.color_source);
|
||||||
|
host_blend.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||||
|
host_blend.colorBlendOp = MaxwellToVK::BlendEquation(guest_blend.color_op);
|
||||||
|
host_blend.srcAlphaBlendFactor =
|
||||||
|
MaxwellToVK::BlendFactor(guest_blend.alpha_source);
|
||||||
|
host_blend.dstAlphaBlendFactor =
|
||||||
|
MaxwellToVK::BlendFactor(guest_blend.alpha_dest);
|
||||||
|
host_blend.alphaBlendOp = MaxwellToVK::BlendEquation(guest_blend.alpha_op);
|
||||||
|
} else {
|
||||||
|
// For normal depth scenarios, use the game's requested blend factors
|
||||||
|
host_blend.srcColorBlendFactor =
|
||||||
|
MaxwellToVK::BlendFactor(guest_blend.color_source);
|
||||||
|
host_blend.dstColorBlendFactor =
|
||||||
|
MaxwellToVK::BlendFactor(guest_blend.color_dest);
|
||||||
|
host_blend.colorBlendOp = MaxwellToVK::BlendEquation(guest_blend.color_op);
|
||||||
|
host_blend.srcAlphaBlendFactor =
|
||||||
|
MaxwellToVK::BlendFactor(guest_blend.alpha_source);
|
||||||
|
host_blend.dstAlphaBlendFactor =
|
||||||
|
MaxwellToVK::BlendFactor(guest_blend.alpha_dest);
|
||||||
|
host_blend.alphaBlendOp = MaxwellToVK::BlendEquation(guest_blend.alpha_op);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if (!regs.blend_per_target_enabled) {
|
if (!regs.blend_per_target_enabled) {
|
||||||
blend_setup(regs.blend);
|
blend_setup(regs.blend);
|
||||||
|
|
Loading…
Reference in New Issue