diff --git a/defects/cemu-0001/patch/cemu-0001.patch b/defects/cemu-0001/patch/cemu-0001.patch new file mode 100644 index 000000000..ef3878a31 --- /dev/null +++ b/defects/cemu-0001/patch/cemu-0001.patch @@ -0,0 +1,28 @@ +# UNDF: UNDF-2026-XXXXXXXXX +--- a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.h ++++ b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.h +@@ -19,7 +19,7 @@ class LatteTextureViewVk : public LatteTextureView + LatteTextureVk* GetBaseImage() const { return (LatteTextureVk*)baseTexture; } + +- void AddDescriptorSetReference(struct VkDescriptorSetInfo* dsInfo) { if (std::find(list_descriptorSets.begin(), list_descriptorSets.end(), dsInfo) == list_descriptorSets.end()) list_descriptorSets.emplace_back(dsInfo); }; +- void RemoveDescriptorSetReference(struct VkDescriptorSetInfo* dsInfo) { list_descriptorSets.erase(std::remove(list_descriptorSets.begin(), list_descriptorSets.end(), dsInfo), list_descriptorSets.end()); }; ++ void AddDescriptorSetReference(struct VkDescriptorSetInfo* dsInfo) { list_descriptorSets.insert(dsInfo); }; ++ void RemoveDescriptorSetReference(struct VkDescriptorSetInfo* dsInfo) { list_descriptorSets.erase(dsInfo); }; + + private: +@@ -38,7 +38,7 @@ private: + VkDevice m_device; + VkFormat m_format; +- std::vector list_descriptorSets; // list of descriptors sets referencing this view ++ std::unordered_set list_descriptorSets; // list of descriptors sets referencing this view + + uint64 m_uniqueId; + }; +--- a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp ++++ b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp +@@ -75,8 +75,8 @@ LatteTextureViewVk::~LatteTextureViewVk() + { +- while (!list_descriptorSets.empty()) +- delete list_descriptorSets[0]; ++ while (!list_descriptorSets.empty()) ++ delete *list_descriptorSets.begin(); diff --git a/defects/cemu-0001/test/cemu_0001_test b/defects/cemu-0001/test/cemu_0001_test new file mode 100755 index 000000000..f3d9b2340 Binary files /dev/null and b/defects/cemu-0001/test/cemu_0001_test differ diff --git a/defects/cemu-0001/test/cemu_0001_test.cpp b/defects/cemu-0001/test/cemu_0001_test.cpp new file mode 100644 index 000000000..f2255757a --- /dev/null +++ b/defects/cemu-0001/test/cemu_0001_test.cpp @@ -0,0 +1,97 @@ +// cemu-0001: LatteTextureViewVk::list_descriptorSets vector -> unordered_set +// Tests that AddDescriptorSetReference is O(1) amortized, not O(D). + +#include +#include +#include +#include +#include + +// Simulates the original defect: std::vector with linear scan for uniqueness +struct DefectRef { + std::vector refs; + + void add(int* p) { + for (auto* r : refs) + if (r == p) return; + refs.push_back(p); + } + + void remove(int* p) { + refs.erase(std::remove(refs.begin(), refs.end(), p), refs.end()); + } +}; + +// Fixed implementation: unordered_set, O(1) amortized +struct FixedRef { + std::unordered_set refs; + + void add(int* p) { + refs.insert(p); + } + + void remove(int* p) { + refs.erase(p); + } +}; + +static long long bench_ns(auto fn) { + auto t0 = std::chrono::steady_clock::now(); + fn(); + auto t1 = std::chrono::steady_clock::now(); + return std::chrono::duration_cast(t1 - t0).count(); +} + +int main() { + // Correctness test: both implementations maintain unique set + DefectRef defect; + FixedRef fixed; + + const int N = 500; + int ptrs[N]; + for (int i = 0; i < N; i++) { + defect.add(&ptrs[i]); + fixed.add(&ptrs[i]); + } + // Duplicate add should not grow the collection + for (int i = 0; i < N; i++) { + defect.add(&ptrs[i]); + fixed.add(&ptrs[i]); + } + assert(defect.refs.size() == N); + assert(fixed.refs.size() == N); + + // Remove half + for (int i = 0; i < N / 2; i++) { + defect.remove(&ptrs[i]); + fixed.remove(&ptrs[i]); + } + assert(defect.refs.size() == N / 2); + assert(fixed.refs.size() == N / 2); + + // Performance: add D = 1000 unique pointers to each, measure time + const int D = 1000; + int ds_ptrs[D]; + + DefectRef defect2; + auto t_defect = bench_ns([&]() { + for (int i = 0; i < D; i++) + defect2.add(&ds_ptrs[i]); + }); + + FixedRef fixed2; + auto t_fixed = bench_ns([&]() { + for (int i = 0; i < D; i++) + fixed2.add(&ds_ptrs[i]); + }); + + double ratio = (double)t_defect / (double)t_fixed; + printf("cemu-0001: defect=%lldns fixed=%lldns ratio=%.1fx (D=%d)\n", + (long long)t_defect, (long long)t_fixed, ratio, D); + + // Fixed should be significantly faster for large D + assert(ratio > 2.0 && "Fixed implementation not measurably faster — check test"); + + printf("cemu-0001: PASS\n"); + return 0; +} diff --git a/defects/cemu-0002/patch/cemu-0002.patch b/defects/cemu-0002/patch/cemu-0002.patch new file mode 100644 index 000000000..1a73bb737 --- /dev/null +++ b/defects/cemu-0002/patch/cemu-0002.patch @@ -0,0 +1,50 @@ +# UNDF: UNDF-2026-XXXXXXXXX +--- a/src/Cafe/GraphicPack/GraphicPack2.h ++++ b/src/Cafe/GraphicPack/GraphicPack2.h +@@ -35,10 +35,10 @@ struct FILTER_SETTINGS + sint32 width = -1; + sint32 height = -1; + sint32 depth = -1; + MEM1_FILTER inMEM1 = MEM1_FILTER::BOTH; +- std::vector format_whitelist{}; +- std::vector format_blacklist{}; +- std::vector tilemode_whitelist{}; +- std::vector tilemode_blacklist{}; ++ std::unordered_set format_whitelist{}; ++ std::unordered_set format_blacklist{}; ++ std::unordered_set tilemode_whitelist{}; ++ std::unordered_set tilemode_blacklist{}; + } filter_settings; +--- a/src/Cafe/HW/Latte/Core/LatteTexture.cpp ++++ b/src/Cafe/HW/Latte/Core/LatteTexture.cpp +@@ -1243,13 +1243,13 @@ void LatteTexture_init() +- if (!rule.filter_settings.format_whitelist.empty() && std::find(rule.filter_settings.format_whitelist.begin(), rule.filter_settings.format_whitelist.end(), (uint32)format) == rule.filter_settings.format_whitelist.end()) ++ if (!rule.filter_settings.format_whitelist.empty() && rule.filter_settings.format_whitelist.find((sint32)format) == rule.filter_settings.format_whitelist.end()) + continue; + +- if (!rule.filter_settings.format_blacklist.empty() && std::find(rule.filter_settings.format_blacklist.begin(), rule.filter_settings.format_blacklist.end(), (uint32)format) != rule.filter_settings.format_blacklist.end()) ++ if (!rule.filter_settings.format_blacklist.empty() && rule.filter_settings.format_blacklist.find((sint32)format) != rule.filter_settings.format_blacklist.end()) + continue; + +- if (!rule.filter_settings.tilemode_whitelist.empty() && std::find(rule.filter_settings.tilemode_whitelist.begin(), rule.filter_settings.tilemode_whitelist.end(), (int)tileMode) == rule.filter_settings.tilemode_whitelist.end()) ++ if (!rule.filter_settings.tilemode_whitelist.empty() && rule.filter_settings.tilemode_whitelist.find((sint32)tileMode) == rule.filter_settings.tilemode_whitelist.end()) + continue; + +- if (!rule.filter_settings.tilemode_blacklist.empty() && std::find(rule.filter_settings.tilemode_blacklist.begin(), rule.filter_settings.tilemode_blacklist.end(), (int)tileMode) != rule.filter_settings.tilemode_blacklist.end()) ++ if (!rule.filter_settings.tilemode_blacklist.empty() && rule.filter_settings.tilemode_blacklist.find((sint32)tileMode) != rule.filter_settings.tilemode_blacklist.end()) + continue; +--- a/src/Cafe/GraphicPack/GraphicPack2.cpp ++++ b/src/Cafe/GraphicPack/GraphicPack2.cpp +@@ -978,12 +978,16 @@ void GraphicPack2::_loadTextureRules(IniParser& parser) +- rule.filter_settings.format_whitelist = ParseList(parser, rules, "formats"); +- rule.filter_settings.format_blacklist = ParseList(parser, rules, "formatsExcluded"); +- rule.filter_settings.tilemode_whitelist = ParseList(parser, rules, "tilemodes"); +- rule.filter_settings.tilemode_blacklist = ParseList(parser, rules, "tilemodesExcluded"); ++ auto fmt_wl = ParseList(parser, rules, "formats"); ++ rule.filter_settings.format_whitelist = std::unordered_set(fmt_wl.begin(), fmt_wl.end()); ++ auto fmt_bl = ParseList(parser, rules, "formatsExcluded"); ++ rule.filter_settings.format_blacklist = std::unordered_set(fmt_bl.begin(), fmt_bl.end()); ++ auto tm_wl = ParseList(parser, rules, "tilemodes"); ++ rule.filter_settings.tilemode_whitelist = std::unordered_set(tm_wl.begin(), tm_wl.end()); ++ auto tm_bl = ParseList(parser, rules, "tilemodesExcluded"); ++ rule.filter_settings.tilemode_blacklist = std::unordered_set(tm_bl.begin(), tm_bl.end()); diff --git a/defects/cemu-0002/test/cemu_0002_test b/defects/cemu-0002/test/cemu_0002_test new file mode 100755 index 000000000..94d9a371c Binary files /dev/null and b/defects/cemu-0002/test/cemu_0002_test differ diff --git a/defects/cemu-0002/test/cemu_0002_test.cpp b/defects/cemu-0002/test/cemu_0002_test.cpp new file mode 100644 index 000000000..00f4ca875 --- /dev/null +++ b/defects/cemu-0002/test/cemu_0002_test.cpp @@ -0,0 +1,104 @@ +// cemu-0002: GraphicPack2 FILTER_SETTINGS format/tilemode list membership O(F) -> O(1) +// Per-texture-creation std::find in active graphic pack rules replaced with unordered_set lookup. + +#include +#include +#include +#include +#include + +// Simulates defect: vector + std::find membership check, called per texture creation +struct DefectFilter { + std::vector format_whitelist; + std::vector tilemode_whitelist; + + bool matches(int format, int tilemode) const { + if (!format_whitelist.empty()) { + if (std::find(format_whitelist.begin(), format_whitelist.end(), format) + == format_whitelist.end()) + return false; + } + if (!tilemode_whitelist.empty()) { + if (std::find(tilemode_whitelist.begin(), tilemode_whitelist.end(), tilemode) + == tilemode_whitelist.end()) + return false; + } + return true; + } +}; + +// Fixed: unordered_set membership, O(1) per lookup +struct FixedFilter { + std::unordered_set format_whitelist; + std::unordered_set tilemode_whitelist; + + bool matches(int format, int tilemode) const { + if (!format_whitelist.empty()) { + if (format_whitelist.find(format) == format_whitelist.end()) + return false; + } + if (!tilemode_whitelist.empty()) { + if (tilemode_whitelist.find(tilemode) == tilemode_whitelist.end()) + return false; + } + return true; + } +}; + +static long long bench_ns(auto fn) { + auto t0 = std::chrono::steady_clock::now(); + fn(); + auto t1 = std::chrono::steady_clock::now(); + return std::chrono::duration_cast(t1 - t0).count(); +} + +int main() { + // Build filters with F = 200 format entries and T = 50 tilemode entries + // (large texture packs may enumerate many formats and tile modes) + const int F = 200; + const int T = 50; + const int TEXTURES = 10000; // simulate 10k texture creations + + DefectFilter defect; + FixedFilter fixed; + + for (int i = 0; i < F; i++) { + defect.format_whitelist.push_back(i * 3 + 1); // scattered to avoid trivial branch pred + fixed.format_whitelist.insert(i * 3 + 1); + } + for (int i = 0; i < T; i++) { + defect.tilemode_whitelist.push_back(i * 5 + 2); + fixed.tilemode_whitelist.insert(i * 5 + 2); + } + + // Correctness: both should agree on membership + for (int f = 0; f < F * 4; f++) { + for (int t = 0; t < T * 6; t++) { + bool d = defect.matches(f, t); + bool x = fixed.matches(f, t); + assert(d == x && "mismatch between defect and fixed"); + } + } + + // Performance: TEXTURES texture lookups, scattered format/tilemode queries + volatile bool sink = false; + + auto t_defect = bench_ns([&]() { + for (int i = 0; i < TEXTURES; i++) + sink ^= defect.matches(i % (F * 4), i % (T * 6)); + }); + + auto t_fixed = bench_ns([&]() { + for (int i = 0; i < TEXTURES; i++) + sink ^= fixed.matches(i % (F * 4), i % (T * 6)); + }); + + double ratio = (double)t_defect / (double)t_fixed; + printf("cemu-0002: defect=%lldns fixed=%lldns ratio=%.1fx (F=%d T=%d textures=%d)\n", + (long long)t_defect, (long long)t_fixed, ratio, F, T, TEXTURES); + + assert(ratio > 1.5 && "Fixed implementation not measurably faster — check test"); + + printf("cemu-0002: PASS\n"); + return 0; +}