Compare commits

..

5 Commits

Author SHA1 Message Date
Mehmet Cambaz fd2e15ee73 ci: run the recompiler unit tests (#447)
Build and run the CPU-only resource_tracking, scalar_provenance, resource_materialization, and shader_vertex_metadata tests on Windows, macOS, and Linux. Each test executable is excluded from the default build, so add it to the explicit CMake targets and the matching CTest selection. Leave the Vulkan-dependent shader_recompiler_compute suite for a separate CI setup.
2026-09-26 03:00:01 +04:00
Saadi 4e8d8c849d tests: add LeastRecentlyUsedCache regression tests (#671)
* launcher: keep primary update feed answer when the fallback request fails

When the primary feed is reachable but its tag differs from the build tag,
the result was discarded and the GitHub API fallback was consulted. If the
fallback request failed (api.github.com rate-limits unauthenticated
requests), the launcher reported an error even though the primary feed had
already returned a usable update answer with a working release URL. Keep
the primary feed answer and show it if the fallback fails.

* emulator: validate numeric CLI arguments

--screen-width, --screen-height, --gpu, and --vblank-frequency were parsed
with Common::ToInt32/ToInt64, which ignores std::from_chars errors and
returns 0, so values like "--screen-width abc" or "--gpu xyz" were
silently accepted: zero screen size, or GPU 0 selected instead of auto
(-1). Reject invalid values with an error message like the other options,
using from_chars-based helpers (ParseUint32/ParseInt32); reject zero
screen dimensions.

* tests: add LeastRecentlyUsedCache regression tests

Cover the LRU cache walk order, tick filtering in ForEachItemBelow,
early-exit boolean callbacks, Touch reordering and same-tick skips,
Free, and freed-id reuse by later inserts. Modeled on BitArrayTests.

* tests: use a tick cutoff that detects a stale newest-item tick

Collect(cache, 35) also visits c when Touch(c, 35) failed to update
its tick (30 <= 35). Use a cutoff of 30 and expect only b.

* Run LRU cache tests in CI

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-26 02:42:31 +04:00
Matthew Hurley 1e438136fa Build save_data_memory_tests from the kyty_tests aggregate (#798)
Build save_data_memory_tests with kyty_tests

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-26 02:39:20 +04:00
Emil 313b60e922 Capture PCM at set time when multiple AudioOut2 ports share a buffer (#826)
* capture PCM at set time when ports share a buffer

* Snapshot size limit and open device check

* Fall back to PCM pointer on stale snapshot

* audio: own AudioOut2 PCM at attribute set time

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-09-26 02:19:49 +04:00
Hamza Shakir cda236dbb3 Shader recompiler: accept V_NOT_B32 SDWA partial dst with any source; allow 64 buffers
SDWA source extraction and destination insertion are independent. Cover the reported word1-to-word0 preserve form and byte-source partial destinations in compute tests. Verify 64 distinct compute buffers survive resource tracking and binding layout, and remove the obsolete CFG rejection.

(cherry picked from commit 9e74df7dc0)
2026-09-26 01:58:55 +04:00
13 changed files with 393 additions and 41 deletions
+13 -6
View File
@@ -112,12 +112,15 @@ jobs:
- name: Build
shell: cmd
run: |
cmake --build _Build/windows --target launcher audio_out2_port_tests ime_dialog_tests virtual_memory_allocation_tests --parallel
cmake --build _Build/windows --target launcher ^
audio_out2_port_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests ^
resource_tracking_tests scalar_provenance_tests ^
resource_materialization_tests shader_vertex_metadata_tests --parallel
- name: Test
shell: cmd
run: |
ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|ime_dialog|virtual_memory_allocation)$"
ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|ime_dialog|lru_cache|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|virtual_memory_allocation)$"
- name: Install
shell: cmd
@@ -198,14 +201,16 @@ jobs:
shell: bash
run: |
cmake --build _Build/macos \
--target launcher audio_out2_port_tests ime_dialog_tests virtual_memory_allocation_tests \
--target launcher audio_out2_port_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \
resource_tracking_tests scalar_provenance_tests \
resource_materialization_tests shader_vertex_metadata_tests \
--parallel
- name: Test
shell: bash
run: |
ctest --test-dir _Build/macos --output-on-failure \
-R '^(audio_out2_port|ime_dialog|virtual_memory_allocation)$'
-R '^(audio_out2_port|ime_dialog|lru_cache|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|virtual_memory_allocation)$'
- name: Install
shell: bash
@@ -379,14 +384,16 @@ jobs:
run: |
cmake --build _Build/linux \
--target launcher page_manager_tests memory_tracker_tests \
audio_out2_port_tests ime_dialog_tests virtual_memory_allocation_tests \
audio_out2_port_tests ime_dialog_tests lru_cache_tests virtual_memory_allocation_tests \
resource_tracking_tests scalar_provenance_tests \
resource_materialization_tests shader_vertex_metadata_tests \
--parallel
- name: Test
shell: bash
run: |
ctest --test-dir _Build/linux --output-on-failure \
-R '^(audio_out2_port|ime_dialog|page_manager|memory_tracker|virtual_memory_allocation)$'
-R '^(audio_out2_port|ime_dialog|lru_cache|memory_tracker|page_manager|resource_tracking|scalar_provenance|resource_materialization|shader_vertex_metadata|virtual_memory_allocation)$'
- name: Install
shell: bash
+8
View File
@@ -411,6 +411,11 @@ add_executable(bit_array_tests EXCLUDE_FROM_ALL
)
target_include_directories(bit_array_tests PRIVATE ${inc_headers})
add_executable(lru_cache_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/LruCacheTests.cpp"
)
target_include_directories(lru_cache_tests PRIVATE ${inc_headers})
add_executable(ime_dialog_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/ImeDialogTests.cpp"
"${KYTY_SOURCE_DIR}/libs/imeCommon.cpp"
@@ -555,6 +560,7 @@ if(BUILD_TESTING)
add_test(NAME memory_tracker COMMAND $<TARGET_FILE:memory_tracker_tests>)
add_test(NAME page_manager COMMAND $<TARGET_FILE:page_manager_tests>)
add_test(NAME bit_array COMMAND $<TARGET_FILE:bit_array_tests>)
add_test(NAME lru_cache COMMAND $<TARGET_FILE:lru_cache_tests>)
add_test(NAME shader_vertex_metadata COMMAND $<TARGET_FILE:shader_vertex_metadata_tests>)
add_test(NAME resource_materialization COMMAND $<TARGET_FILE:resource_materialization_tests>)
add_test(NAME resource_tracking COMMAND $<TARGET_FILE:resource_tracking_tests>)
@@ -619,6 +625,7 @@ if(BUILD_TESTING)
memory_tracker_tests
page_manager_tests
bit_array_tests
lru_cache_tests
shader_vertex_metadata_tests
resource_materialization_tests
resource_tracking_tests
@@ -629,6 +636,7 @@ if(BUILD_TESTING)
virtual_memory_allocation_tests
audio_out2_port_tests
ngs2_sampler_tests
save_data_memory_tests
)
endif()
@@ -522,8 +522,10 @@ constexpr Vop1SdwaRule VOP1_SDWA_RULES[] = {
{Opcode::V_CVT_F32_U32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
{Opcode::V_CVT_F32_I32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
{Opcode::V_CVT_F32_UBYTE0, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
// The source field extract and the destination insert are independent (Translate.cpp), so a
// partial destination may combine with any source selector.
{Opcode::V_NOT_B32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), SdwaSelBytes() | SdwaSelWords(),
SdwaSelFull(), false},
SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), false},
{Opcode::V_FFBL_B32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false},
{Opcode::V_CVT_F32_F16, SdwaSelWords() | SdwaSelFull(), 0, 0, true},
{Opcode::V_CVT_F16_F32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), SdwaSelWords(),
+1 -1
View File
@@ -435,7 +435,7 @@ struct BindingLayout {
};
struct ShaderInfo {
static constexpr uint32_t MaxBuffers = 32;
static constexpr uint32_t MaxBuffers = 64;
static constexpr uint32_t MaxImages = 64;
static constexpr uint32_t MaxSamplers = 32;
static constexpr uint32_t MaxSampledPairs = 64;
+7
View File
@@ -3,6 +3,8 @@
#include <QNetworkAccessManager>
#include <QObject>
#include <QString>
#include <QUrl>
class QByteArray;
class QWidget;
@@ -29,6 +31,11 @@ private:
QWidget* m_parent = nullptr;
QNetworkAccessManager m_network;
bool m_checking_updates = false;
// Answer from the primary update feed, kept while the GitHub fallback feed is
// being consulted, so it can still be shown if the fallback request fails.
QString m_primary_tag;
QUrl m_primary_page_url;
};
#endif // UPDATE_CHECKER_H
+22 -4
View File
@@ -84,12 +84,30 @@ void UpdateChecker::FetchUpdateInfo(const char* url, bool fallback, bool manual)
}
reply->deleteLater();
if (!fallback &&
(!info.error.isEmpty() || info.tag != QString::fromLatin1(KYTY_RELEASE_TAG))) {
FetchUpdateInfo(FALLBACK_FEED_URL, true, manual);
return;
if (!fallback) {
const bool up_to_date =
info.error.isEmpty() && info.tag == QString::fromLatin1(KYTY_RELEASE_TAG);
if (!up_to_date) {
if (info.error.isEmpty()) {
// The primary feed is reachable but disagrees with the build tag
// (e.g. a stale mirror); remember its answer in case the fallback
// request also fails.
m_primary_tag = info.tag;
m_primary_page_url = info.page_url;
}
FetchUpdateInfo(FALLBACK_FEED_URL, true, manual);
return;
}
}
if (fallback && !info.error.isEmpty() && !m_primary_tag.isEmpty()) {
// The fallback feed failed, but the primary feed already returned a
// usable answer; prefer it over reporting a network error.
info = {m_primary_tag, m_primary_page_url, {}};
}
m_primary_tag.clear();
m_primary_page_url.clear();
m_checking_updates = false;
emit CheckingChanged(false);
ShowUpdateResult(info, manual);
+22 -12
View File
@@ -188,7 +188,7 @@ struct AudioOut2PortStateEntry {
uint32_t samples_num = 512;
AudioInternal::Format audio_format = AudioInternal::Format::Unknown;
int audio_handle = 0;
const void* pcm_data = nullptr;
std::vector<uint8_t> pcm_data;
};
struct AudioOut2SpeakerArrayState {
@@ -341,11 +341,17 @@ static AudioOut2PortStateEntry* audioout2_find_port_locked(AudioOut2PortHandle p
return nullptr;
}
static size_t audioout2_pcm_size(const AudioOut2PortStateEntry& state) {
const auto bytes_per_sample = (state.data_format & 0x7fu) == 1 ? sizeof(int16_t) : sizeof(float);
return static_cast<size_t>(state.samples_num) *
audioout2_data_format_channels(state.data_format) * bytes_per_sample;
}
static bool audioout2_context_has_queueable_device(AudioOut2ContextHandle ctx) {
Common::LockGuard lock(g_audioout2_port_mutex);
for (const auto& state: g_audioout2_ports) {
if (state.used && state.context == ctx && state.audio_handle > 0 &&
state.pcm_data != nullptr && AudioInternal::AudioOutHasDevice(state.audio_handle)) {
!state.pcm_data.empty() && AudioInternal::AudioOutHasDevice(state.audio_handle)) {
return true;
}
}
@@ -353,24 +359,23 @@ static bool audioout2_context_has_queueable_device(AudioOut2ContextHandle ctx) {
}
static void audioout2_queue_context_audio(AudioOut2ContextHandle ctx, bool blocking) {
// The backend consumes the buffers synchronously, but can block while pacing SDL's queue.
// Keep both PCM storage and port handles alive until it returns.
std::vector<AudioInternal::OutputParam> params;
params.reserve(AudioInternal::OUT_PORTS_MAX);
g_audioout2_port_mutex.Lock();
Common::LockGuard lock(g_audioout2_port_mutex);
for (const auto& state: g_audioout2_ports) {
if (state.used && state.context == ctx && state.audio_handle > 0 &&
state.pcm_data != nullptr && params.size() < AudioInternal::OUT_PORTS_MAX) {
params.push_back(AudioInternal::OutputParam {state.audio_handle, state.pcm_data});
!state.pcm_data.empty() && params.size() < AudioInternal::OUT_PORTS_MAX) {
params.push_back(AudioInternal::OutputParam {state.audio_handle, state.pcm_data.data()});
}
}
g_audioout2_port_mutex.Unlock();
if (params.empty()) {
return;
if (!params.empty()) {
(void)AudioInternal::AudioOutOutputs(params.data(), static_cast<uint32_t>(params.size()),
blocking);
}
(void)AudioInternal::AudioOutOutputs(params.data(), static_cast<uint32_t>(params.size()),
blocking);
}
static void audioout2_close_audio_handle(int audio_handle) {
@@ -679,7 +684,12 @@ int KYTY_SYSV_ABI AudioOut2PortSetAttributes(AudioOut2PortHandle port,
if (has_pcm) {
g_audioout2_port_mutex.Lock();
if (auto* state = audioout2_find_port_locked(port); state != nullptr) {
state->pcm_data = pcm_data;
if (pcm_data != nullptr && state->audio_format != AudioInternal::Format::Unknown) {
const auto* bytes = static_cast<const uint8_t*>(pcm_data);
state->pcm_data.assign(bytes, bytes + audioout2_pcm_size(*state));
} else {
state->pcm_data.clear();
}
}
g_audioout2_port_mutex.Unlock();
}
+38 -6
View File
@@ -133,6 +133,26 @@ static bool ParseConsoleLanguage(const std::string& value, uint32_t& out) {
return true;
}
static bool ParseUint32(const std::string& value, uint32_t& out) {
uint32_t number = 0;
auto [end, error] = std::from_chars(value.data(), value.data() + value.size(), number);
if (error != std::errc {} || end != value.data() + value.size()) {
return false;
}
out = number;
return true;
}
static bool ParseInt32(const std::string& value, int32_t& out) {
int32_t number = 0;
auto [end, error] = std::from_chars(value.data(), value.data() + value.size(), number);
if (error != std::errc {} || end != value.data() + value.size()) {
return false;
}
out = number;
return true;
}
static bool ParseUserId(const std::string& value, int32_t& out) {
int32_t user_id = 0;
auto [end, error] = std::from_chars(value.data(), value.data() + value.size(), user_id);
@@ -246,9 +266,17 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
}
options.game_patch = path;
} else if (arg == "--screen-width") {
options.config.screen_width = static_cast<uint32_t>(Common::ToInt32(value));
if (!ParseUint32(value, options.config.screen_width) ||
options.config.screen_width == 0) {
::printf("invalid screen width: %s\n", value.c_str());
return false;
}
} else if (arg == "--screen-height") {
options.config.screen_height = static_cast<uint32_t>(Common::ToInt32(value));
if (!ParseUint32(value, options.config.screen_height) ||
options.config.screen_height == 0) {
::printf("invalid screen height: %s\n", value.c_str());
return false;
}
} else if (arg == "--user-name") {
if (value.empty() || value.size() > Config::MAX_USER_NAME_LENGTH) {
::printf("invalid user name: must contain 1-%zu bytes\n",
@@ -269,11 +297,15 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
return false;
}
} else if (arg == "--gpu") {
options.config.gpu_index = Common::ToInt32(value);
if (!ParseInt32(value, options.config.gpu_index)) {
::printf("invalid gpu index: %s\n", value.c_str());
return false;
}
} else if (arg == "--vblank-frequency") {
const int32_t vblank_frequency = Common::ToInt32(value);
options.config.vblank_frequency =
static_cast<uint32_t>(vblank_frequency < 0 ? 0 : vblank_frequency);
if (!ParseUint32(value, options.config.vblank_frequency)) {
::printf("invalid vblank frequency: %s\n", value.c_str());
return false;
}
} else if (arg == "--console-language") {
if (!ParseConsoleLanguage(value, options.config.console_language)) {
::printf("invalid console language: %s\n", value.c_str());
+65 -9
View File
@@ -8,6 +8,7 @@
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <thread>
#include <vector>
@@ -16,14 +17,16 @@ namespace {
namespace AudioOut2 = Libs::Audio::AudioOut2;
std::mutex g_device_mutex;
std::condition_variable g_device_cv;
std::vector<int> g_live_devices;
std::vector<int> g_device_backed_handles;
std::vector<bool> g_output_blocking;
int g_next_device = 1;
int g_open_waiters = 0;
bool g_block_opens = false;
std::mutex g_device_mutex;
std::condition_variable g_device_cv;
std::vector<int> g_live_devices;
std::vector<int> g_device_backed_handles;
std::vector<bool> g_output_blocking;
std::vector<std::vector<uint8_t>> g_output_pcm;
size_t g_capture_bytes = 0;
int g_next_device = 1;
int g_open_waiters = 0;
bool g_block_opens = false;
void Check(bool value, const char* text) {
if (!value) {
@@ -146,6 +149,17 @@ std::vector<bool> OutputCalls() {
return g_output_blocking;
}
void CaptureOutputPcm(size_t bytes) {
std::lock_guard lock(g_device_mutex);
g_capture_bytes = bytes;
g_output_pcm.clear();
}
std::vector<std::vector<uint8_t>> OutputPcm() {
std::lock_guard lock(g_device_mutex);
return g_output_pcm;
}
void TestSlotReuse() {
const auto context = CreateContext();
const auto param = MakeParam();
@@ -327,6 +341,41 @@ void TestHandleWithoutPcmDoesNotBypassQueue() {
AudioOut2::AudioOut2ContextDestroy(context);
}
void TestPcmCopiedBeforeScratchBufferReuse() {
const auto context = CreateContext();
const auto param = MakeParam();
AudioOut2::AudioOut2PortHandle first = 0;
AudioOut2::AudioOut2PortHandle second = 0;
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(&param), &first) == OK,
"first port create failed");
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(&param), &second) == OK,
"second port create failed");
std::vector<float> scratch(512 * 2);
std::vector<float> first_pcm(scratch.size(), 0.25f);
std::vector<float> second_pcm(scratch.size(), -0.5f);
std::copy(first_pcm.begin(), first_pcm.end(), scratch.begin());
SetPcm(first, scratch.data());
std::copy(second_pcm.begin(), second_pcm.end(), scratch.begin());
SetPcm(second, scratch.data());
std::fill(scratch.begin(), scratch.end(), 0.0f);
const auto pcm_bytes = scratch.size() * sizeof(float);
CaptureOutputPcm(pcm_bytes);
Check(AudioOut2::AudioOut2ContextPush(context, 1) == OK, "shared-buffer push failed");
const auto output = OutputPcm();
Check(output.size() == 2, "shared-buffer push did not output both ports");
Check(std::memcmp(output[0].data(), first_pcm.data(), pcm_bytes) == 0,
"first port lost PCM when scratch buffer was reused");
Check(std::memcmp(output[1].data(), second_pcm.data(), pcm_bytes) == 0,
"second port lost PCM when scratch buffer was reused");
CaptureOutputPcm(0);
AudioOut2::AudioOut2PortDestroy(first);
AudioOut2::AudioOut2PortDestroy(second);
AudioOut2::AudioOut2ContextDestroy(context);
}
} // namespace
namespace Libs::Audio::AudioInternal {
@@ -363,9 +412,15 @@ bool AudioOutHasDevice(int handle) {
g_device_backed_handles.end();
}
uint32_t AudioOutOutputs(const OutputParam* /*params*/, uint32_t /*num*/, bool blocking) {
uint32_t AudioOutOutputs(const OutputParam* params, uint32_t num, bool blocking) {
std::lock_guard lock(g_device_mutex);
g_output_blocking.push_back(blocking);
if (g_capture_bytes != 0) {
for (uint32_t i = 0; i < num; i++) {
const auto* bytes = static_cast<const uint8_t*>(params[i].data);
g_output_pcm.emplace_back(bytes, bytes + g_capture_bytes);
}
}
return 0;
}
@@ -389,6 +444,7 @@ int main() {
TestFloat12ChannelPortOutputsPcm();
TestAsynchronousDevicePushKeepsQueueBounded();
TestHandleWithoutPcmDoesNotBypassQueue();
TestPcmCopiedBeforeScratchBufferReuse();
std::printf("AudioOut2PortTests: all cases passed\n");
return 0;
}
+156
View File
@@ -0,0 +1,156 @@
#include "common/lruCache.h"
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <vector>
namespace {
void Check(bool value, const char *message) {
if (!value) {
std::fprintf(stderr, "LruCacheTests: failed: %s\n", message);
std::abort();
}
}
// Collect the objects reachable through ForEachItemBelow(), in visit order.
std::vector<std::string> Collect(Common::LeastRecentlyUsedCache<std::string, uint64_t> &cache,
uint64_t tick) {
std::vector<std::string> visited;
cache.ForEachItemBelow(tick, [&](const std::string &object) { visited.push_back(object); });
return visited;
}
void TestInsertAndVisitOrder() {
Common::LeastRecentlyUsedCache<std::string, uint64_t> cache;
const auto a = cache.Insert("a", 10);
const auto b = cache.Insert("b", 20);
const auto c = cache.Insert("c", 30);
Check(a != b && b != c && a != c, "Insert returned duplicate ids");
const auto all = Collect(cache, UINT64_MAX);
Check(all.size() == 3 && all[0] == "a" && all[1] == "b" && all[2] == "c",
"inserted items are not visited in insertion order");
const auto below = Collect(cache, 20);
Check(below.size() == 2 && below[0] == "a" && below[1] == "b",
"ForEachItemBelow visited an item above the tick");
const auto none = Collect(cache, 5);
Check(none.empty(), "ForEachItemBelow visited items below an empty tick");
}
void TestEarlyExitCallback() {
Common::LeastRecentlyUsedCache<std::string, uint64_t> cache;
(void)cache.Insert("a", 10);
(void)cache.Insert("b", 20);
(void)cache.Insert("c", 30);
std::vector<std::string> visited;
cache.ForEachItemBelow(UINT64_MAX, [&](const std::string &object) {
visited.push_back(object);
return true; // stop after the first item
});
Check(visited.size() == 1 && visited[0] == "a", "boolean callback did not stop the walk");
std::vector<std::string> all_visited;
cache.ForEachItemBelow(UINT64_MAX, [&](const std::string &object) {
all_visited.push_back(object);
return false;
});
Check(all_visited.size() == 3, "false-returning callback did not visit every item");
}
void TestTouchReordersAndSkips() {
Common::LeastRecentlyUsedCache<std::string, uint64_t> cache;
const auto a = cache.Insert("a", 10);
const auto b = cache.Insert("b", 20);
const auto c = cache.Insert("c", 30);
// Touching the last item must not change the order.
cache.Touch(c, 35);
auto order = Collect(cache, UINT64_MAX);
Check(order.size() == 3 && order[0] == "a" && order[1] == "b" && order[2] == "c",
"touching the newest item changed the order");
// An older or equal tick must be ignored.
cache.Touch(a, 10);
cache.Touch(a, 5);
order = Collect(cache, UINT64_MAX);
Check(order[0] == "a" && order[1] == "b" && order[2] == "c",
"an ignored touch changed the order");
// Touching the oldest item moves it to the newest end.
cache.Touch(a, 40);
order = Collect(cache, UINT64_MAX);
Check(order.size() == 3 && order[0] == "b" && order[1] == "c" && order[2] == "a",
"touching an item did not move it to the newest end");
// The touched item now carries its new tick for tick filtering
// (a=40, b=20, c=35 after the touches above). A cutoff of 30 detects
// a stale c tick: c is visited only if Touch(c, 35) updated it.
const auto below = Collect(cache, 30);
Check(below.size() == 1 && below[0] == "b",
"touched item did not adopt its new tick");
// Touch the middle item.
cache.Touch(b, 45);
order = Collect(cache, UINT64_MAX);
Check(order.size() == 3 && order[0] == "c" && order[1] == "a" && order[2] == "b",
"touching a middle item did not move it to the end");
}
void TestFreeAndIdReuse() {
Common::LeastRecentlyUsedCache<std::string, uint64_t> cache;
const auto a = cache.Insert("a", 10);
const auto b = cache.Insert("b", 20);
const auto c = cache.Insert("c", 30);
// Free the first and the last items.
cache.Free(a);
cache.Free(c);
auto order = Collect(cache, UINT64_MAX);
Check(order.size() == 1 && order[0] == "b", "Free left a freed item in the walk");
// Freed ids must be reused by later inserts.
const auto d = cache.Insert("d", 40);
Check(d == a || d == c, "Insert did not reuse a freed id");
const auto e = cache.Insert("e", 50);
Check(e != d && (e == a || e == c), "Insert reused the same freed id twice");
order = Collect(cache, UINT64_MAX);
Check(order.size() == 3 && order[0] == "b" && order[1] == "d" && order[2] == "e",
"reused ids are not visited in tick order");
// Touching a reused id must not disturb the other items.
cache.Touch(d, 60);
order = Collect(cache, UINT64_MAX);
Check(order.size() == 3 && order[0] == "b" && order[1] == "e" && order[2] == "d",
"touching a reused id changed the wrong items");
// Free everything, then rebuild.
cache.Free(b);
cache.Free(d);
cache.Free(e);
Check(Collect(cache, UINT64_MAX).empty(), "freeing every item left items in the walk");
const auto again = cache.Insert("again", 70);
const auto order2 = Collect(cache, UINT64_MAX);
Check(order2.size() == 1 && order2[0] == "again", "insert after a full free failed");
cache.Touch(again, 70);
Check(Collect(cache, 70).size() == 1, "touching a rebuilt item failed");
}
} // namespace
int main() {
TestInsertAndVisitOrder();
TestEarlyExitCallback();
TestTouchReordersAndSkips();
TestFreeAndIdReuse();
std::puts("LruCacheTests: all cases passed");
return 0;
}
+25
View File
@@ -2815,6 +2815,31 @@ void TestGraphicsPushConstantLayout() {
}
void TestResourceLimitIsTransactional() {
Fixture accepted;
MemoryInfo accepted_memory;
accepted_memory.kind = ResourceKind::Buffer;
for (uint32_t index = 0; index < ShaderInfo::MaxBuffers; index++) {
const auto handle = accepted.Buffer(
{Value(index), Value(index + 1u), Value(index + 2u), Value(index + 3u)},
index * 4u);
accepted.Emit(ValueOpcode::LoadBufferU32,
{handle, Value(0u), Value(0u), Value(0u), Value(true)},
accepted.AddMemory(accepted_memory, index * 4u));
}
accepted.PlanAndTrack();
Check(accepted.program.info.buffers.size() == 64u &&
accepted.program.descriptor_sources.size() == 64u &&
accepted.program.memory_info.back().resource == 63u,
"compute shader did not retain all 64 distinct buffers");
ShaderComputeInputInfo compute{};
CollectShaderInfo(accepted.program, {.compute = &compute});
AllocateBindings(accepted.program);
const auto *binding = FindBinding(accepted.program.bindings,
DescriptorBindingKind::Buffers);
Check(binding != nullptr && binding->resources.size() == 64u &&
accepted.program.bindings.memory_offset_count == 64u,
"compute shader binding layout truncated the 64 buffers");
Fixture fixture;
MemoryInfo memory;
memory.kind = ResourceKind::Buffer;
+33
View File
@@ -18937,6 +18937,38 @@ TestCase Vop1SdwaNotPreservesHighWordDestination() {
return test;
}
TestCase Vop1SdwaNotPartialSourcesAndDestinations() {
using O = ShaderOpcode;
std::vector<u32> code;
AppendBufferLoadDword(&code, 8, 30);
AppendVMovLiteral(&code, 28, 0xabcd5555u);
code.push_back(EncodeVop1(0x37, 28, 249));
code.push_back(EncodeVop1Sdwa(8, 4, 2, 5)); // v28.word0 = ~v8.word1, preserve word1
AppendStoreVgpr(&code, 28, 0);
AppendVMovLiteral(&code, 3, 0xa1b2c3d4u);
code.push_back(EncodeVop1(0x37, 3, 249));
code.push_back(EncodeVop1Sdwa(8, 2, 2, 0)); // v3.byte2 = ~v8.byte0, preserve others
AppendStoreVgpr(&code, 3, 1);
code.push_back(EncodeVop1(0x37, 4, 249));
code.push_back(EncodeVop1Sdwa(8, 0, 0, 0)); // v4.byte0 = ~v8.byte0, zero others
AppendStoreVgpr(&code, 4, 2);
AppendEnd(&code);
TestCase test;
test.name = "Vop1SdwaNotPartialSourcesAndDestinations";
test.code = std::move(code);
test.initial = {0x12345678u, 0u, 0u};
test.expected = {0xabcdedcbu, 0xa187c3d4u, 0x00000087u};
test.opcodes = {O::BUFFER_LOAD_DWORD, O::V_MOV_B32, O::V_NOT_B32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.decoded_counts = {{"V_NOT_B32", 3u}};
test.required_spirv = {"OpBitFieldUExtract", "OpNot"};
return test;
}
TestCase Vop1SdwaMovByteDestinations() {
using O = ShaderOpcode;
@@ -28641,6 +28673,7 @@ std::vector<TestCase> MakeCases() {
AddCase(Vop1SdwaFfblCapturedHighWordSource);
AddCase(Vop1SdwaNotCapturedByte0Source);
AddCase(Vop1SdwaNotPreservesHighWordDestination);
AddCase(Vop1SdwaNotPartialSourcesAndDestinations);
AddCase(Vop1SdwaMovByteDestinations);
AddCase(Vop2SdwaSubNcExactByte2Destination);
AddCase(Vop2SdwaAddNcCapturedHighWordDestination);
-2
View File
@@ -3523,8 +3523,6 @@ void TestNewShaderRecompilerVop1SdwaNotDestination() {
check_rejected(0x00260400u, "V_NOT_B32 SDWA accepted source absolute");
check_rejected(0x00062400u, "V_NOT_B32 SDWA accepted clamp");
check_rejected(0x00064400u, "V_NOT_B32 SDWA accepted output modifier");
check_rejected(0x00000400u,
"V_NOT_B32 SDWA partial destination accepted a byte source");
}
void TestNewShaderRecompilerBootB16PackedAndSdwaOpcodes() {