diff --git a/src/core/hle/service/nx2_gsv.h b/src/core/hle/service/nx2_gsv.h index 62cce1dbfc..89f8dbe426 100644 --- a/src/core/hle/service/nx2_gsv.h +++ b/src/core/hle/service/nx2_gsv.h @@ -3,6 +3,17 @@ #pragma once +#include +#include +#include +#include + +// Time of the last large movie frame the Switch 2 video decoder output (EDEN_MKW_MOVIE_UNPACED). +inline std::atomic& Nx2MkwMovieFrameClock() { + static std::atomic clock{0}; + return clock; +} + #include #include #include @@ -1815,6 +1826,22 @@ class INx2Vi2Session final : public ServiceFramework { if (!Settings::values.use_speed_limit.GetValue() || Settings::SpeedLimit() == 0) { return; } + // EDEN_MKW_MOVIE_UNPACED=1: no present pacing while a large (>= 1920 wide) movie plays. + // The pacing sleep starves Mario Kart World's movie player: its 2560x1440 cup intros ran + // at ~70% speed with stuttering sound (~35% with the game's 30 FPS mode). + static const bool mkw_movie_unpaced = [] { + const char* v = std::getenv("EDEN_MKW_MOVIE_UNPACED"); + return v != nullptr && v[0] == '1'; + }(); + if (mkw_movie_unpaced) { + const auto mkw_last = Nx2MkwMovieFrameClock().load(std::memory_order_relaxed); + const auto mkw_now = std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count(); + if (mkw_last != 0 && mkw_now - mkw_last < 250'000'000) { + m_next_vblank = std::chrono::steady_clock::now(); + return; + } + } static const u32 forced_interval = [] { const char* value = std::getenv("EDEN_NX2_SWAP_INTERVAL"); return value ? static_cast(std::strtoul(value, nullptr, 10)) : 0U; @@ -2721,6 +2748,10 @@ class INx2VideoSession final : public ServiceFramework { std::optional fence; if (const auto it = m_release_fences.find(image); it != m_release_fences.end()) { fence = it->second; + if (m_width >= 1920) { + Nx2MkwMovieFrameClock().store(std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count(), std::memory_order_relaxed); + } m_release_fences.erase(it); } lk.unlock(); diff --git a/src/video_core/mkw_wave_workaround.h b/src/video_core/mkw_wave_workaround.h new file mode 100644 index 0000000000..02f6dd9d92 --- /dev/null +++ b/src/video_core/mkw_wave_workaround.h @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#pragma once + +#include +#include + +#include "shader_recompiler/frontend/ampere/program_override.h" + +namespace VideoCommon { + +// Experimental, opt-in workaround for a missing ground-height map. The original wave +// amplitudes and water exclusion mask still control the resulting displacement. +inline bool ApplyMkwZeroDepthFallback(std::span source, + Shader::Ampere::LoweredProgram& program) { + if (!program || source.size_bytes() != 0x3b0 || + Shader::Ampere::NativeProgramKey(source) != 0x5a4f3726590efb17ULL || + program.source_instruction_offsets.size() != source.size()) { + return false; + } + constexpr size_t next_source = 0x2d0 / sizeof(Shader::Ampere::Instruction); + const size_t insert = program.source_instruction_offsets[next_source]; + if (insert > program.instructions.size()) { + return false; + } + // At this point R8=(water-ground)*20, R9=water and R4=saturate(R8). + // Both R8 and R9 zero identifies the entirely empty pair, including neither + // real shallows (equal nonzero heights) nor dry ground (negative depth). + constexpr u64 always = u64{7} << 16; + constexpr std::array fallback{ + // LOP.OR R250, R8, R9 (PT destination; no live predicate is changed). + 0x5c40000000000000ULL | always | 250 | (u64{8} << 8) | (u64{9} << 20) | + (u64{1} << 41) | (u64{7} << 48), + // ISET.EQ.U32.AND with float-result mode: 1.0 if empty, otherwise 0. + 0x5b50000000000000ULL | always | 250 | (u64{250} << 8) | (u64{255} << 20) | + (u64{7} << 39) | (u64{1} << 44) | (u64{2} << 49), + // Empty input has R4=0; other input ORs with zero and stays unchanged. + 0x5c40000000000000ULL | always | 4 | (u64{4} << 8) | (u64{250} << 20) | + (u64{1} << 41) | (u64{7} << 48), + }; + program.instructions.insert(program.instructions.begin() + insert, fallback.begin(), + fallback.end()); + // This exact shader has no branches or calls; its predicated EXITs need no relocation. + for (size_t index = next_source; index < program.source_instruction_offsets.size(); ++index) { + program.source_instruction_offsets[index] += static_cast(fallback.size()); + } + return true; +} + +} // namespace VideoCommon diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 45bdfd3a03..c7e0828c2a 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -714,12 +714,13 @@ GraphicsPipeline* PipelineCache::CurrentGraphicsPipeline() { return CurrentGraphicsPipelineSlowPath(); } -ComputePipeline* PipelineCache::CurrentComputePipeline() { +ComputePipeline* PipelineCache::CurrentComputePipeline(u64& native_key) { const ShaderInfo* const shader{ComputeShader()}; if (!shader) { return nullptr; } + native_key = shader->lowered_native ? shader->lowered_native->native_key : 0; const auto& qmd{kepler_compute->launch_description}; const ComputePipelineCacheKey key{ .unique_hash = shader->unique_hash, @@ -749,7 +750,11 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading } const auto shader_dir{Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir)}; - const auto base_dir{shader_dir / fmt::format("{:016x}", title_id)}; + auto base_dir{shader_dir / fmt::format("{:016x}", title_id)}; + if (const char* enabled = std::getenv("EDEN_MKW_ZERO_DEPTH_FALLBACK"); + enabled != nullptr && enabled[0] == '1') { + base_dir /= "mkw_zero_depth_v1"; + } if (!Common::FS::CreateDir(shader_dir) || !Common::FS::CreateDir(base_dir)) { LOG_ERROR(Common_Filesystem, "Failed to create pipeline cache directories"); return; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index 05c9ddce65..51b2682046 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -124,7 +124,7 @@ class PipelineCache : public VideoCommon::ShaderCache { void TickFrame(); - [[nodiscard]] ComputePipeline* CurrentComputePipeline(); + [[nodiscard]] ComputePipeline* CurrentComputePipeline(u64& native_key); void LoadDiskResources(u64 title_id, std::stop_token stop_loading, const VideoCore::DiskResourceLoadCallback& callback); diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index e9bd271b38..6832194e28 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -48,6 +48,7 @@ #include "video_core/renderer_vulkan/vk_update_descriptor.h" #include "video_core/shader_cache.h" #include "video_core/texture_cache/texture_cache_base.h" +#include "video_core/textures/texture.h" #include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_wrapper.h" #include "video_core/nx2_diag.h" @@ -1045,7 +1046,8 @@ void RasterizerVulkan::DispatchCompute() { FlushWork(); gpu_memory->FlushCaching(); - ComputePipeline* const pipeline{pipeline_cache.CurrentComputePipeline()}; + u64 native_key{}; + ComputePipeline* const pipeline{pipeline_cache.CurrentComputePipeline(native_key)}; if (!pipeline) { if (kepler_compute->IsNativeMode()) throw Shader::RuntimeError("Native compute pipeline unavailable"); return; @@ -1063,6 +1065,24 @@ void RasterizerVulkan::DispatchCompute() { } const auto& qmd{kepler_compute->launch_description}; + const auto publish_collision_map = [&] { + static const bool enabled = [] { + const char* value = std::getenv("EDEN_MKW_WATER_FEEDBACK"); + return value != nullptr && value[0] == '1'; + }(); + if (!enabled || native_key != 0x6e0ae200ee9aa220ULL || + (qmd.const_buffer_enable_mask & 1) == 0) { + return; + } + const auto& constants = qmd.const_buffer_config[0]; + if (constants.size < 0x3c) { + return; + } + // WaterSurfaceCollisionHeightMap exposes its output handle in CB0. + const u32 handle = gpu_memory->Read(constants.Address() + 0x38); + const u32 index = Tegra::Texture::TexturePair(handle, qmd.linked_tsc != 0).first; + texture_cache.PublishComputeCollisionImage(index); + }; auto indirect_address = kepler_compute->GetIndirectComputeAddress(); if (indirect_address) { // DispatchIndirect @@ -1079,6 +1099,7 @@ void RasterizerVulkan::DispatchCompute() { cmdbuf.DispatchIndirect(indirect_buffer, indirect_offset); }); SubmitNx2ListCopies(); + publish_collision_map(); return; } const std::array dim{qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z}; @@ -1102,6 +1123,7 @@ void RasterizerVulkan::DispatchCompute() { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); }); SubmitNx2ListCopies(); + publish_collision_map(); // Log compute dispatch if (GPU::Logging::IsActive() && diff --git a/src/video_core/shader_environment.cpp b/src/video_core/shader_environment.cpp index 9479e3dd7e..d5834a8bc5 100644 --- a/src/video_core/shader_environment.cpp +++ b/src/video_core/shader_environment.cpp @@ -34,6 +34,7 @@ #include "shader_recompiler/frontend/ampere/program_address_map.h" #include "video_core/engines/kepler_compute.h" #include "video_core/memory_manager.h" +#include "video_core/mkw_wave_workaround.h" #include "video_core/nx2_diag.h" #include "video_core/shader_environment.h" #include "video_core/texture_cache/format_lookup_table.h" @@ -524,7 +525,12 @@ std::shared_ptr GenericEnvironment::LowerNativeCode(u32 program_base, lowered.error_offset, lowered.error, words[0] & 0xfff, words[0], words[1], words[2], words[3]); } + if (const char* enabled = std::getenv("EDEN_MKW_ZERO_DEPTH_FALLBACK"); + enabled != nullptr && enabled[0] == '1') { + VideoCommon::ApplyMkwZeroDepthFallback(native, lowered); + } auto result = std::make_shared(); + result->native_key = Shader::Ampere::NativeProgramKey(native); result->texture_bound = lowered.required_texture_buffer; result->source_size = native_header_size + lowered.source_size; auto& lowered_code = result->code; diff --git a/src/video_core/shader_environment.h b/src/video_core/shader_environment.h index 30d59955af..f6100c8453 100644 --- a/src/video_core/shader_environment.h +++ b/src/video_core/shader_environment.h @@ -32,6 +32,7 @@ namespace VideoCommon { /// A native (Switch 2) program after capture and lowering to Maxwell code. The shader cache keeps /// it so the environments built for the program's pipelines don't capture and lower it again. struct LoweredNativeCode { + u64 native_key{}; /// Shader header, lowered instructions with Maxwell scheduling words, then the native /// address map (what the environment's `code` used to be filled with). std::vector code; diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index ea82ab02e7..482b102c1a 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -485,6 +485,32 @@ void TextureCache

::MarkModification(ImageId id) noexcept { MarkModification(slot_images[id]); } +template +bool TextureCache

::PublishComputeCollisionImage(u32 index) { + const auto view_id = VisitImageView(index, true); + if (!view_id || !slot_image_views[view_id].image_id) { + return false; + } + Image& image = slot_images[slot_image_views[view_id].image_id]; + const auto& info = image.info; + // The title writes a CPU-readable 256x256 pitch-linear SNORM height map. + if (info.type != ImageType::Linear || info.format != PixelFormat::R16G16B16A16_SNORM || + info.size.width != 256 || info.size.height != 256 || info.size.depth != 1 || + info.pitch != 2048 || info.resources.layers != 1 || info.resources.levels != 1 || + !IsDownloadable(image) || + !gpu_memory->IsFullyMappedRange(image.gpu_addr, image.guest_size_bytes)) { + return false; + } + const auto copies = FullDownloadCopies(info); + auto map = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes); + image.DownloadMemory(map, copies); + runtime.Finish(); + SwizzleImage(*gpu_memory, image.gpu_addr, info, copies, map.mapped_span, swizzle_data_buffer); + image.flags &= ~ImageFlagBits::GpuModified; + return true; +} + + template void TextureCache

::FillImageViews(std::span views, bool compute, bool blacklist) { bool has_blacklisted = false; diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index 5847cae7ba..85711272c7 100644 --- a/src/video_core/texture_cache/texture_cache_base.h +++ b/src/video_core/texture_cache/texture_cache_base.h @@ -228,6 +228,9 @@ class TextureCache : public VideoCommon::ChannelSetupCaches