Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/core/hle/service/nx2_gsv.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvdrv/nvdrv_interface.h"
#include "core/hle/service/nx2_stub_interface.h"
#include "core/hle/service/nx2_movie_pacing.h"
#include "core/hle/service/sm/sm.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h"
Expand Down Expand Up @@ -1448,6 +1449,7 @@ class INx2GpuSession final : public ServiceFramework<INx2GpuSession> {
// shared with the vi2 session so presents can find an image's memory. Command meanings follow
// the shared changes.patch (IGraphicsSupervisor), re-checked against title 4's SDK.
struct Nx2NativeObjects {
MkwMovieActivity mkw_movie_activity;
struct Memory {
u64 size{};
u64 address{}; // CPU (== GPU) address once bound by 114/423
Expand Down Expand Up @@ -1851,6 +1853,11 @@ class INx2Vi2Session final : public ServiceFramework<INx2Vi2Session> {
if (!Settings::values.use_speed_limit.GetValue() || Settings::SpeedLimit() == 0) {
return;
}
const auto movie_now = std::chrono::steady_clock::now();
if (m_objects->mkw_movie_activity.Active(movie_now)) {
m_next_vblank = movie_now;
return;
}
static const u32 forced_interval = [] {
const char* value = std::getenv("EDEN_NX2_SWAP_INTERVAL");
return value ? static_cast<u32>(std::strtoul(value, nullptr, 10)) : 0U;
Expand Down Expand Up @@ -2511,6 +2518,9 @@ class INx2VideoSession final : public ServiceFramework<INx2VideoSession> {
m_cv.notify_all();
m_worker.join();
}
if (m_movie_program_id == 0x0400C3F00006E000ULL && m_width >= 1920) {
m_objects->mkw_movie_activity.Reset();
}
// The ready timer runs on the HostTiming thread: clear its event under its lock first.
{
std::scoped_lock lk{m_ready->mutex};
Expand Down Expand Up @@ -2632,6 +2642,8 @@ class INx2VideoSession final : public ServiceFramework<INx2VideoSession> {
m_width = w[5];
m_height = w[6];
m_clock_offset.reset();
const auto* process = ctx.GetThread().GetOwnerProcess();
m_movie_program_id = process ? process->GetProgramId() : 0;
}
if (!m_worker.joinable()) {
m_worker = std::thread{[this] { DecodeLoop(); }};
Expand Down Expand Up @@ -2917,6 +2929,9 @@ class INx2VideoSession final : public ServiceFramework<INx2VideoSession> {
m_decoded.clear();
m_last_decoded_pts = 0;
m_dropped_in_a_row = 0;
if (MkwMovieActivity::IsLargeMovie(m_movie_program_id, m_width)) {
m_objects->mkw_movie_activity.Reset();
}
if (decoder) {
lk.unlock();
decoder->Reset();
Expand All @@ -2942,6 +2957,10 @@ class INx2VideoSession final : public ServiceFramework<INx2VideoSession> {
const auto [width, height] = WriteImage(decoder.get(), image, frame, bytes);
lk.lock();
// Even an image written during a flush counts: the client waits for its value.
if (!m_flush && frame.picture && frame.picture->frame &&
MkwMovieActivity::IsLargeMovie(m_movie_program_id, frame.picture->width)) {
m_objects->mkw_movie_activity.RecordFrame(std::chrono::steady_clock::now());
}
m_outputs.push_back({image, width, height, frame.pts, frame.end});
if (frame.end) {
m_stream_ended = true;
Expand Down Expand Up @@ -3119,6 +3138,7 @@ class INx2VideoSession final : public ServiceFramework<INx2VideoSession> {
std::string m_codec;
u32 m_width{};
u32 m_height{};
u64 m_movie_program_id{};
u32 m_handed{}; ///< Output images handed out (registered or released) so far
u64 m_frames_out{};
u64 m_frames_dropped{};
Expand Down
42 changes: 42 additions & 0 deletions src/core/hle/service/nx2_movie_pacing.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include <atomic>
#include <chrono>

#include "common/common_types.h"

namespace Service {

class MkwMovieActivity {
public:
using Clock = std::chrono::steady_clock;

static constexpr bool IsLargeMovie(u64 program_id, u32 width) {
return program_id == 0x0400C3F00006E000ULL && width >= 1920;
}

void RecordFrame(Clock::time_point now) {
last_frame.store(Ticks(now), std::memory_order_relaxed);
}

void Reset() {
last_frame.store(0, std::memory_order_relaxed);
}

bool Active(Clock::time_point now) const {
const s64 last = last_frame.load(std::memory_order_relaxed);
const s64 elapsed = Ticks(now) - last;
return last != 0 && elapsed >= 0 && elapsed < 250'000'000;
}

private:
static s64 Ticks(Clock::time_point now) {
return std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count();
}

std::atomic<s64> last_frame{};
};

} // namespace Service
1 change: 1 addition & 0 deletions src/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ add_executable(tests
core/internal_network/socket_proxy.cpp
core/nup_writer.cpp
core/nx2_debug_server.cpp
core/nx2_movie_pacing.cpp
core/priority_queue.cpp
core/internal_network/network.cpp
video_core/frame_dump_schedule.cpp
Expand Down
28 changes: 28 additions & 0 deletions src/tests/core/nx2_movie_pacing.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: GPL-3.0-or-later

#include <catch2/catch_test_macros.hpp>

#include "core/hle/service/nx2_movie_pacing.h"

TEST_CASE("MKW movie pacing is limited to recent large frames in the current session", "[nx2]") {
using Service::MkwMovieActivity;
using namespace std::chrono_literals;
CHECK(MkwMovieActivity::IsLargeMovie(0x0400C3F00006E000ULL, 2560));
CHECK_FALSE(MkwMovieActivity::IsLargeMovie(0x0400C3F00006E000ULL, 640));
CHECK_FALSE(MkwMovieActivity::IsLargeMovie(0x0400123400000000ULL, 2560));

MkwMovieActivity activity;
MkwMovieActivity other_session;
const MkwMovieActivity::Clock::time_point start{1s};
CHECK_FALSE(activity.Active(start));
activity.RecordFrame(start);
CHECK(activity.Active(start));
CHECK(activity.Active(start + 249ms));
CHECK_FALSE(activity.Active(start + 250ms));
CHECK_FALSE(activity.Active(start - 1ms));
CHECK_FALSE(other_session.Active(start));
activity.RecordFrame(start + 200ms);
CHECK(activity.Active(start + 300ms));
activity.Reset();
CHECK_FALSE(activity.Active(start + 300ms));
}
50 changes: 50 additions & 0 deletions src/video_core/mkw_wave_workaround.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

#include <array>
#include <span>

#include "shader_recompiler/frontend/ampere/program_override.h"

namespace VideoCommon {

// Workaround for a missing ground-height map in this exact shader. The original wave
// amplitudes and water exclusion mask still control the resulting displacement.
inline bool ApplyMkwZeroDepthFallback(std::span<const Shader::Ampere::Instruction> 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() ||
(next_source != 0 && program.source_instruction_offsets[next_source - 1] > insert)) {
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<u64, 3> 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<u32>(fallback.size());
}
return true;
}

} // namespace VideoCommon
8 changes: 6 additions & 2 deletions src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -756,12 +756,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_program_key : 0;
const auto& qmd{kepler_compute->launch_description};
const ComputePipelineCacheKey key{
.unique_hash = shader->unique_hash,
Expand Down Expand Up @@ -832,7 +833,10 @@ 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 (title_id == 0x0400C3F00006E000ULL) {
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;
Expand Down
2 changes: 1 addition & 1 deletion src/video_core/renderer_vulkan/vk_pipeline_cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,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);
Expand Down
20 changes: 19 additions & 1 deletion src/video_core/renderer_vulkan/vk_rasterizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,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_debug_control.h"
Expand Down Expand Up @@ -1256,7 +1257,8 @@ void RasterizerVulkan::DispatchCompute() {
frame_dump.DrainIfFull();
}

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;
Expand All @@ -1273,6 +1275,20 @@ void RasterizerVulkan::DispatchCompute() {
return;
}
const auto& qmd{kepler_compute->launch_description};
const auto publish_collision_map = [&] {
if (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<u32>(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 (frame_dump.Capturing()) {
const u64 shader_hash = pipeline->ShaderHash();
Expand Down Expand Up @@ -1348,6 +1364,7 @@ void RasterizerVulkan::DispatchCompute() {
cmdbuf.DispatchIndirect(indirect_buffer, indirect_offset);
});
SubmitNx2ListCopies();
publish_collision_map();
return;
}
const std::array<u32, 3> dim{qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z};
Expand All @@ -1364,6 +1381,7 @@ void RasterizerVulkan::DispatchCompute() {
cmdbuf.Dispatch(dim[0], dim[1], dim[2]);
});
SubmitNx2ListCopies();
publish_collision_map();

// Log compute dispatch
if (GPU::Logging::IsActive() &&
Expand Down
2 changes: 2 additions & 0 deletions src/video_core/shader_environment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "video_core/memory_manager.h"
#include "video_core/nx2_debug_control.h"
#include "video_core/nx2_diag.h"
#include "video_core/mkw_wave_workaround.h"
#include "video_core/shader_environment.h"
#include "video_core/texture_cache/format_lookup_table.h"
#include "video_core/textures/texture.h"
Expand Down Expand Up @@ -630,6 +631,7 @@ std::shared_ptr<const LoweredNativeCode> GenericEnvironment::LowerNativeCode(u32
program_base, lowered.error_offset, lowered.error, words[0] & 0xfff, words[0],
words[1], words[2], words[3]);
}
VideoCommon::ApplyMkwZeroDepthFallback(native, lowered);
auto result = std::make_shared<LoweredNativeCode>();
result->native_program_key = Shader::Ampere::NativeProgramKey(native);
static const bool hash_for_indirect_reset = [] {
Expand Down
24 changes: 24 additions & 0 deletions src/video_core/texture_cache/texture_cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,30 @@ void TextureCache<P>::MarkModification(ImageId id) noexcept {
MarkModification(slot_images[id]);
}

template <class P>
bool TextureCache<P>::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 <class P>
void TextureCache<P>::FillImageViews(std::span<ImageViewInOut> views, bool compute, bool blacklist) {
bool has_blacklisted = false;
Expand Down
3 changes: 3 additions & 0 deletions src/video_core/texture_cache/texture_cache_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@ class TextureCache : public VideoCommon::ChannelSetupCaches<TextureCacheChannelI
/// Get the imageview from the graphics descriptor table in the specified index
[[nodiscard]] ImageView& GetImageView(u32 index) noexcept;

// Publish the CPU-readable collision image. Caller holds the cache mutex.
bool PublishComputeCollisionImage(u32 index);

/// Mark an image as modified from the GPU
void MarkModification(ImageId id) noexcept;

Expand Down