video_core: MCI boot fixes and DMA multisized components support

Add workarounds for Marvel Cosmic Invasion boot issues:
- Skip first 2 compute dispatches (xbzk@eden-emu.dev)
- Clamp staging buffers to 2GB to prevent Vulkan failures (xbzk@eden-emu.dev)
- Validate staging buffer sizes before uploads (xbzk@eden-emu.dev)

Also improve DMA engine to support multisized components (1-4 bytes)
instead of hardcoded 4-byte components.

Co-authored-by: xbzk <xbzk@eden-emu.dev>
Signed-off-by: Zephyron <zephyron@citron-emu.org>
This commit is contained in:
Zephyron
2025-12-13 09:49:04 +10:00
parent b770abeea2
commit 7db12d7e80
6 changed files with 79 additions and 13 deletions

View File

@@ -14,9 +14,8 @@ private:
public: public:
static constexpr u64 FinalFantasyTactics = 0x010038B015560000ULL; static constexpr u64 FinalFantasyTactics = 0x010038B015560000ULL;
// Base title ID for Little Nightmares 3 (covers both 0x010066101A55A800 and 0x010066101A55A000)
// The base title ID is obtained by masking with 0xFFFFFFFFFFFFE000
static constexpr u64 LittleNightmares3Base = 0x010066101A55A000ULL; static constexpr u64 LittleNightmares3Base = 0x010066101A55A000ULL;
static constexpr u64 MarvelCosmicInvasion = 0x010059D020C26000ULL;
}; };
} // namespace UICommon } // namespace UICommon

View File

@@ -9,6 +9,7 @@
#include <numeric> #include <numeric>
#include "common/range_sets.inc" #include "common/range_sets.inc"
#include "citron/util/title_ids.h"
#include "video_core/buffer_cache/buffer_cache_base.h" #include "video_core/buffer_cache/buffer_cache_base.h"
#include "video_core/guest_memory.h" #include "video_core/guest_memory.h"
#include "video_core/host1x/gpu_device_memory_manager.h" #include "video_core/host1x/gpu_device_memory_manager.h"
@@ -1512,6 +1513,16 @@ void BufferCache<P>::MappedUploadMemory([[maybe_unused]] Buffer& buffer,
if constexpr (USE_MEMORY_MAPS) { if constexpr (USE_MEMORY_MAPS) {
auto upload_staging = runtime.UploadStagingBuffer(total_size_bytes); auto upload_staging = runtime.UploadStagingBuffer(total_size_bytes);
const std::span<u8> staging_pointer = upload_staging.mapped_span; const std::span<u8> staging_pointer = upload_staging.mapped_span;
// Validate staging buffer size to prevent buffer overruns
// This can happen if the requested size exceeds driver limits (e.g., 2GB)
// Only apply this workaround for Marvel Cosmic Invasion
if (program_id == UICommon::TitleID::MarvelCosmicInvasion &&
staging_pointer.size() < total_size_bytes) {
// Staging buffer is too small, skip this upload to avoid corruption
return;
}
for (BufferCopy& copy : copies) { for (BufferCopy& copy : copies) {
u8* const src_pointer = staging_pointer.data() + copy.src_offset; u8* const src_pointer = staging_pointer.data() + copy.src_offset;
const DAddr device_addr = buffer.CpuAddr() + copy.dst_offset; const DAddr device_addr = buffer.CpuAddr() + copy.dst_offset;

View File

@@ -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 "common/algorithm.h" #include "common/algorithm.h"
@@ -104,19 +105,46 @@ void MaxwellDMA::Launch() {
} }
} }
} else { } else {
// TODO: allow multisized components.
auto& accelerate = rasterizer->AccessAccelerateDMA(); auto& accelerate = rasterizer->AccessAccelerateDMA();
const bool is_const_a_dst = regs.remap_const.dst_x == RemapConst::Swizzle::CONST_A; const bool is_const_a_dst = regs.remap_const.dst_x == RemapConst::Swizzle::CONST_A;
if (regs.launch_dma.remap_enable != 0 && is_const_a_dst) { if (regs.launch_dma.remap_enable != 0 && is_const_a_dst) {
ASSERT(regs.remap_const.component_size_minus_one == 3); // Support multisized components (1-4 bytes per component)
accelerate.BufferClear(regs.offset_out, regs.line_length_in, // component_size_minus_one: 0=1 byte, 1=2 bytes, 2=3 bytes, 3=4 bytes
regs.remap_const.remap_consta_value); const u32 component_size = regs.remap_const.component_size_minus_one + 1;
read_buffer.resize_destructive(regs.line_length_in * sizeof(u32)); const u32 num_dst_components = regs.remap_const.num_dst_components_minus_one + 1;
std::span<u32> span(reinterpret_cast<u32*>(read_buffer.data()), regs.line_length_in); const u32 bytes_per_element = num_dst_components * component_size;
std::ranges::fill(span, regs.remap_const.remap_consta_value); const u32 total_size = regs.line_length_in * bytes_per_element;
memory_manager.WriteBlockUnsafe(regs.offset_out,
reinterpret_cast<u8*>(read_buffer.data()), // Use accelerated buffer clear if available and matches the simple case
regs.line_length_in * sizeof(u32)); // (4-byte components, single component per element)
if (component_size == sizeof(u32) && num_dst_components == 1) {
accelerate.BufferClear(regs.offset_out, regs.line_length_in,
regs.remap_const.remap_consta_value);
}
// Prepare buffer with properly sized components
// Each element contains num_dst_components, each of component_size bytes
// The constant value is decomposed into bytes and written to each component
read_buffer.resize_destructive(total_size);
u8* const buffer_ptr = read_buffer.data();
const u32 constant_value = regs.remap_const.remap_consta_value;
// Fill buffer: for each element, write num_dst_components of component_size bytes
// Each component gets the same constant value, decomposed according to component_size
for (u32 element = 0; element < regs.line_length_in; ++element) {
u8* element_ptr = buffer_ptr + (element * bytes_per_element);
// Write each component with the constant value
for (u32 comp = 0; comp < num_dst_components; ++comp) {
u8* component_ptr = element_ptr + (comp * component_size);
// Extract bytes from constant value in little-endian order
for (u32 byte = 0; byte < component_size; ++byte) {
component_ptr[byte] = static_cast<u8>((constant_value >> (byte * 8)) & 0xFF);
}
}
}
memory_manager.WriteBlockUnsafe(regs.offset_out, buffer_ptr, total_size);
} else { } else {
memory_manager.FlushCaching(); memory_manager.FlushCaching();
const auto convert_linear_2_blocklinear_addr = [](u64 address) { const auto convert_linear_2_blocklinear_addr = [](u64 address) {

View File

@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2019 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 <algorithm> #include <algorithm>
@@ -37,6 +38,7 @@
#include "video_core/texture_cache/texture_cache_base.h" #include "video_core/texture_cache/texture_cache_base.h"
#include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_wrapper.h" #include "video_core/vulkan_common/vulkan_wrapper.h"
#include "citron/util/title_ids.h"
namespace Vulkan { namespace Vulkan {
@@ -487,6 +489,15 @@ void RasterizerVulkan::Clear(u32 layer_count) {
} }
void RasterizerVulkan::DispatchCompute() { void RasterizerVulkan::DispatchCompute() {
// Skip first 2 dispatches for Marvel Cosmic Invasion to fix boot issues
if (program_id == UICommon::TitleID::MarvelCosmicInvasion) {
static u32 dispatch_count = 0;
if (dispatch_count < 2) {
dispatch_count++;
return;
}
}
FlushWork(); FlushWork();
gpu_memory->FlushCaching(); gpu_memory->FlushCaching();
@@ -1604,6 +1615,7 @@ void RasterizerVulkan::InitializeChannel(Tegra::Control::ChannelState& channel)
void RasterizerVulkan::BindChannel(Tegra::Control::ChannelState& channel) { void RasterizerVulkan::BindChannel(Tegra::Control::ChannelState& channel) {
const s32 channel_id = channel.bind_id; const s32 channel_id = channel.bind_id;
staging_pool.SetProgramId(channel.program_id);
BindToChannel(channel_id); BindToChannel(channel_id);
{ {
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};

View File

@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm> #include <algorithm>
@@ -16,6 +17,7 @@
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
#include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_wrapper.h" #include "video_core/vulkan_common/vulkan_wrapper.h"
#include "citron/util/title_ids.h"
namespace Vulkan { namespace Vulkan {
namespace { namespace {
@@ -235,7 +237,15 @@ std::optional<StagingBufferRef> StagingBufferPool::TryGetReservedBuffer(size_t s
StagingBufferRef StagingBufferPool::CreateStagingBuffer(size_t size, MemoryUsage usage, StagingBufferRef StagingBufferPool::CreateStagingBuffer(size_t size, MemoryUsage usage,
bool deferred) { bool deferred) {
const u32 log2 = Common::Log2Ceil64(size); u32 log2 = Common::Log2Ceil64(size);
// Only apply this workaround for Marvel Cosmic Invasion
if (program_id == UICommon::TitleID::MarvelCosmicInvasion) {
static constexpr u32 MAX_STAGING_BUFFER_LOG2 = 31U;
// Calculate log2 of requested size, but clamp to maximum to prevent overflow
// This ensures we still round up to the next power of 2, but cap at 2GB
log2 = std::min(log2, MAX_STAGING_BUFFER_LOG2);
}
VkBufferCreateInfo buffer_ci = { VkBufferCreateInfo buffer_ci = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr, .pNext = nullptr,

View File

@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#pragma once #pragma once
@@ -44,6 +45,10 @@ public:
u64 GetMemoryUsage() const; u64 GetMemoryUsage() const;
void SetProgramId(u64 program_id_) {
program_id = program_id_;
}
private: private:
struct StreamBufferCommit { struct StreamBufferCommit {
size_t upper_bound; size_t upper_bound;
@@ -121,6 +126,7 @@ private:
size_t current_delete_level = 0; size_t current_delete_level = 0;
u64 buffer_index = 0; u64 buffer_index = 0;
u64 unique_ids{}; u64 unique_ids{};
u64 program_id{};
}; };
} // namespace Vulkan } // namespace Vulkan