Files
thejerf--baijiSteamVR/Source/UnitTests/VideoCommon/CullingCodeFinderTest.cpp
T
2026-04-11 18:11:02 +02:00

267 lines
11 KiB
C++

// Copyright 2026 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <gtest/gtest.h>
#include <array>
#include <string>
#include <vector>
#include "Common/CommonPaths.h"
#include "Common/FileUtil.h"
#include "Core/State.h"
#include "VideoCommon/CullingCodeFinder.h"
TEST(CullingCodeFinder, BuildCandidatesFromSymbolInfoFiltersSortsAndLimits)
{
std::vector<CullingCodeFinder::CandidateSource> sources = {
{"SkipNotFunction", 0x80003000, false, true, true},
{"SkipInvalidEntry", 0x80003010, true, false, true},
{"SkipInvalidStub", 0x80003020, true, true, false},
{"SkipBeforeStart", 0x80003030, true, true, true},
{"RenderSky", 0x80003120, true, true, true},
{"renderAlpha", 0x80003080, true, true, true},
{"RenderBeta", 0x80003040, true, true, true},
};
const auto filtered =
CullingCodeFinder::BuildCandidatesFromSymbolInfo(sources, "render", 2, 0x80003040,
0x80003100);
ASSERT_EQ(filtered.size(), 2u);
EXPECT_EQ(filtered[0].symbol_name, "RenderBeta");
EXPECT_EQ(filtered[0].address, 0x80003040u);
EXPECT_EQ(filtered[1].symbol_name, "renderAlpha");
EXPECT_EQ(filtered[1].address, 0x80003080u);
}
TEST(CullingCodeFinder, BuildARCodeLinesMatchesKnownHydraPatterns)
{
const auto i_ninja =
CullingCodeFinder::BuildARCodeLines(0x80052FE8, CullingCodeFinder::PatchTemplateKind::OriR0True);
EXPECT_EQ(i_ninja[0], "04052FE8 60000001");
EXPECT_EQ(i_ninja[1], "04052FEC 4E800020");
const auto metroid =
CullingCodeFinder::BuildARCodeLines(0x80345C68, CullingCodeFinder::PatchTemplateKind::LiR3True);
EXPECT_EQ(metroid[0], "04345C68 38600001");
EXPECT_EQ(metroid[1], "04345C6C 4E800020");
EXPECT_EQ(CullingCodeFinder::BuildARCodeText(0x80345C68,
CullingCodeFinder::PatchTemplateKind::LiR3True),
"04345C68 38600001\n04345C6C 4E800020");
}
TEST(CullingCodeFinder, AdvancePositionAfterCrashSkipsToNextPendingTest)
{
CullingCodeFinder::ScanConfig config;
config.enabled_templates = {true, false, true, true};
size_t candidate_index = 0;
size_t template_index = 2;
ASSERT_TRUE(
CullingCodeFinder::AdvancePositionAfterCrash(config, 2, &candidate_index, &template_index));
EXPECT_EQ(candidate_index, 0u);
EXPECT_EQ(template_index, 3u);
candidate_index = 0;
template_index = 3;
ASSERT_TRUE(
CullingCodeFinder::AdvancePositionAfterCrash(config, 2, &candidate_index, &template_index));
EXPECT_EQ(candidate_index, 1u);
EXPECT_EQ(template_index, 0u);
candidate_index = 1;
template_index = 3;
EXPECT_FALSE(
CullingCodeFinder::AdvancePositionAfterCrash(config, 2, &candidate_index, &template_index));
}
TEST(CullingCodeFinder, ManifestRoundTripPreservesResumeState)
{
const std::string temp_dir = File::CreateTempDir();
ASSERT_FALSE(temp_dir.empty());
const std::string manifest_path = temp_dir + DIR_SEP "manifest.ini";
CullingCodeFinder::ManifestData manifest;
manifest.game_id = "GM8E01";
manifest.map_path = temp_dir + DIR_SEP "GM8E01.map";
manifest.state = CullingCodeFinder::ScanState::Paused;
manifest.config.savestate_slot = 2;
manifest.config.settle_frames = 7;
manifest.config.min_draw_call_increase = 3;
manifest.config.min_triangle_increase = 11;
manifest.config.min_object_increase = 2;
manifest.config.timeout_seconds = 9;
manifest.config.candidate_limit = 15;
manifest.config.start_address = 0x80005200;
manifest.config.end_address = 0x80005300;
manifest.config.turbo_during_scan = false;
manifest.config.mute_audio_during_scan = false;
manifest.config.auto_capture_reference_state = false;
manifest.config.symbol_filter = "Cull";
manifest.config.output_folder = temp_dir + DIR_SEP "shots";
manifest.config.enabled_templates = {true, false, true, false};
manifest.baseline = {10, 20, 30, 40};
manifest.baseline_set = true;
manifest.candidates = {{"CheckCull", 0x80001000}, {"RenderCull", 0x80002000}};
manifest.next_candidate_index = 1;
manifest.next_template_index = 2;
manifest.has_last_tested_position = true;
manifest.last_tested_address = 0x80002000;
manifest.last_tested_template_index = 2;
CullingCodeFinder::ScanResult result;
result.symbol_name = "RenderCull";
result.address = 0x80345C68;
result.template_kind = CullingCodeFinder::PatchTemplateKind::LiR3True;
result.baseline = {10, 20, 30, 40};
result.measured = {15, 35, 45, 50};
result.draw_call_delta = 5;
result.triangle_delta = 15;
result.prim_delta = 15;
result.object_delta = 10;
result.caused_timeout = false;
result.screenshot_path = temp_dir + DIR_SEP "shots" DIR_SEP "04345C68_li.png";
result.ar_code_text = "04345C68 38600001\n04345C6C 4E800020";
manifest.results.push_back(result);
std::string error;
ASSERT_TRUE(CullingCodeFinder::SaveManifest(manifest_path, manifest, &error)) << error;
CullingCodeFinder::ManifestData loaded;
ASSERT_TRUE(CullingCodeFinder::LoadManifest(manifest_path, &loaded, &error)) << error;
EXPECT_EQ(loaded.game_id, manifest.game_id);
EXPECT_EQ(loaded.map_path, manifest.map_path);
EXPECT_EQ(loaded.state, manifest.state);
EXPECT_EQ(loaded.config.savestate_slot, manifest.config.savestate_slot);
EXPECT_EQ(loaded.config.settle_frames, manifest.config.settle_frames);
EXPECT_EQ(loaded.config.min_draw_call_increase, manifest.config.min_draw_call_increase);
EXPECT_EQ(loaded.config.min_triangle_increase, manifest.config.min_triangle_increase);
EXPECT_EQ(loaded.config.min_object_increase, manifest.config.min_object_increase);
EXPECT_EQ(loaded.config.timeout_seconds, manifest.config.timeout_seconds);
EXPECT_EQ(loaded.config.candidate_limit, manifest.config.candidate_limit);
EXPECT_EQ(loaded.config.start_address, manifest.config.start_address);
EXPECT_EQ(loaded.config.end_address, manifest.config.end_address);
EXPECT_EQ(loaded.config.turbo_during_scan, manifest.config.turbo_during_scan);
EXPECT_EQ(loaded.config.mute_audio_during_scan, manifest.config.mute_audio_during_scan);
EXPECT_EQ(loaded.config.auto_capture_reference_state,
manifest.config.auto_capture_reference_state);
EXPECT_EQ(loaded.config.symbol_filter, manifest.config.symbol_filter);
EXPECT_EQ(loaded.config.output_folder, manifest.config.output_folder);
EXPECT_EQ(loaded.config.enabled_templates, manifest.config.enabled_templates);
EXPECT_EQ(loaded.baseline.draw_calls, manifest.baseline.draw_calls);
EXPECT_EQ(loaded.baseline.triangles_drawn, manifest.baseline.triangles_drawn);
EXPECT_EQ(loaded.baseline.prims, manifest.baseline.prims);
EXPECT_EQ(loaded.baseline.drawn_objects, manifest.baseline.drawn_objects);
EXPECT_TRUE(loaded.baseline_set);
EXPECT_EQ(loaded.next_candidate_index, 1u);
EXPECT_EQ(loaded.next_template_index, 2u);
EXPECT_TRUE(loaded.has_last_tested_position);
EXPECT_EQ(loaded.last_tested_address, 0x80002000u);
EXPECT_EQ(loaded.last_tested_template_index, 2u);
ASSERT_EQ(loaded.candidates.size(), 2u);
EXPECT_EQ(loaded.candidates[1].symbol_name, "RenderCull");
EXPECT_EQ(loaded.candidates[1].address, 0x80002000u);
ASSERT_EQ(loaded.results.size(), 1u);
EXPECT_EQ(loaded.results[0].symbol_name, result.symbol_name);
EXPECT_EQ(loaded.results[0].address, result.address);
EXPECT_EQ(loaded.results[0].template_kind, result.template_kind);
EXPECT_EQ(loaded.results[0].draw_call_delta, result.draw_call_delta);
EXPECT_EQ(loaded.results[0].triangle_delta, result.triangle_delta);
EXPECT_EQ(loaded.results[0].object_delta, result.object_delta);
EXPECT_FALSE(loaded.results[0].caused_timeout);
EXPECT_EQ(loaded.results[0].screenshot_path, result.screenshot_path);
EXPECT_EQ(loaded.results[0].ar_code_text, result.ar_code_text);
File::DeleteDirRecursively(temp_dir);
}
TEST(CullingCodeFinder, ManifestRoundTripPreservesLastTestedPositionWithoutResults)
{
const std::string temp_dir = File::CreateTempDir();
ASSERT_FALSE(temp_dir.empty());
const std::string manifest_path = temp_dir + DIR_SEP "manifest.ini";
CullingCodeFinder::ManifestData manifest;
manifest.game_id = "GNJEAF";
manifest.map_path = temp_dir + DIR_SEP "GNJEAF.map";
manifest.state = CullingCodeFinder::ScanState::Scanning;
manifest.config.output_folder = temp_dir;
manifest.config.enabled_templates = {true, true, true, true};
manifest.baseline = {23, 0, 0, 0};
manifest.baseline_set = true;
manifest.candidates = {{"zz_80052fe8_", 0x80052FE8}, {"zz_80053010_", 0x80053010}};
manifest.next_candidate_index = 0;
manifest.next_template_index = 2;
manifest.has_last_tested_position = true;
manifest.last_tested_address = 0x80052FE8;
manifest.last_tested_template_index = 2;
std::string error;
ASSERT_TRUE(CullingCodeFinder::SaveManifest(manifest_path, manifest, &error)) << error;
CullingCodeFinder::ManifestData loaded;
ASSERT_TRUE(CullingCodeFinder::LoadManifest(manifest_path, &loaded, &error)) << error;
EXPECT_TRUE(File::Exists(manifest_path));
EXPECT_TRUE(loaded.results.empty());
EXPECT_EQ(loaded.next_candidate_index, 0u);
EXPECT_EQ(loaded.next_template_index, 2u);
EXPECT_TRUE(loaded.has_last_tested_position);
EXPECT_EQ(loaded.last_tested_address, 0x80052FE8u);
EXPECT_EQ(loaded.last_tested_template_index, 2u);
ASSERT_EQ(loaded.candidates.size(), 2u);
EXPECT_EQ(loaded.candidates[0].address, 0x80052FE8u);
File::DeleteDirRecursively(temp_dir);
}
TEST(CullingCodeFinder, ManifestRoundTripPreservesStoppedState)
{
const std::string temp_dir = File::CreateTempDir();
ASSERT_FALSE(temp_dir.empty());
const std::string manifest_path = temp_dir + DIR_SEP "manifest.ini";
CullingCodeFinder::ManifestData manifest;
manifest.game_id = "GNJEAF";
manifest.state = CullingCodeFinder::ScanState::Stopped;
manifest.config.output_folder = temp_dir;
manifest.baseline_set = true;
manifest.candidates = {{"zz_80052fe8_", 0x80052FE8}};
manifest.next_candidate_index = 0;
manifest.next_template_index = 2;
manifest.has_last_tested_position = true;
manifest.last_tested_address = 0x80052FE8;
manifest.last_tested_template_index = 2;
std::string error;
ASSERT_TRUE(CullingCodeFinder::SaveManifest(manifest_path, manifest, &error)) << error;
CullingCodeFinder::ManifestData loaded;
ASSERT_TRUE(CullingCodeFinder::LoadManifest(manifest_path, &loaded, &error)) << error;
EXPECT_EQ(loaded.state, CullingCodeFinder::ScanState::Stopped);
EXPECT_TRUE(loaded.has_last_tested_position);
EXPECT_EQ(loaded.last_tested_address, 0x80052FE8u);
EXPECT_EQ(loaded.last_tested_template_index, 2u);
File::DeleteDirRecursively(temp_dir);
}
TEST(State, SaveOptionsDefaultsMatchExpectedCompressionBehavior)
{
State::SaveOptions default_options;
EXPECT_TRUE(default_options.compress);
EXPECT_FALSE(default_options.wait_for_completion);
State::SaveOptions capture_options;
capture_options.compress = false;
capture_options.wait_for_completion = true;
EXPECT_FALSE(capture_options.compress);
EXPECT_TRUE(capture_options.wait_for_completion);
}