diff --git a/src/xenia/gpu/shared_memory.cc b/src/xenia/gpu/shared_memory.cc index 3f70a20c2..87bf4ca93 100644 --- a/src/xenia/gpu/shared_memory.cc +++ b/src/xenia/gpu/shared_memory.cc @@ -416,6 +416,34 @@ bool SharedMemory::RequestRange(uint32_t start, uint32_t length) { return UploadRanges(uploads, current_upload_range); } +bool SharedMemory::IsRangeValid(uint32_t start, uint32_t length) const { + if (!length) { + return true; + } + if (start > kBufferSize || (kBufferSize - start) < length) { + return false; + } + + const uint32_t page_first = start >> page_size_log2_; + const uint32_t page_last = (start + length - 1) >> page_size_log2_; + const uint32_t block_first = page_first >> 6; + const uint32_t block_last = page_last >> 6; + + for (uint32_t i = block_first; i <= block_last; ++i) { + uint64_t valid_mask = UINT64_MAX; + if (i == block_first) { + valid_mask &= ~((uint64_t(1) << (page_first & 63)) - 1); + } + if (i == block_last && (page_last & 63) != 63) { + valid_mask &= (uint64_t(1) << ((page_last & 63) + 1)) - 1; + } + if ((system_page_flags_valid_[i] & valid_mask) != valid_mask) { + return false; + } + } + return true; +} + template XE_FORCEINLINE XE_NOALIAS static T mod_shift_left(T value, uint32_t by) { #if XE_ARCH_AMD64 == 1 diff --git a/src/xenia/gpu/shared_memory.h b/src/xenia/gpu/shared_memory.h index 23f92011d..2b5675789 100644 --- a/src/xenia/gpu/shared_memory.h +++ b/src/xenia/gpu/shared_memory.h @@ -72,6 +72,10 @@ class SharedMemory { // ensures the host GPU memory backing the range are resident. Returns true if // the range has been fully updated and is usable. bool RequestRange(uint32_t start, uint32_t length); + // Returns whether every page in the range is currently valid in the host GPU + // memory copy. Hold the global critical region if relying on this for state + // transitions such as watch installation. + bool IsRangeValid(uint32_t start, uint32_t length) const; void TryFindUploadRange(const uint32_t& block_first, const uint32_t& block_last, diff --git a/src/xenia/gpu/texture_cache.cc b/src/xenia/gpu/texture_cache.cc index 8341764f1..5e012c9a6 100644 --- a/src/xenia/gpu/texture_cache.cc +++ b/src/xenia/gpu/texture_cache.cc @@ -517,23 +517,38 @@ TextureCache::Texture::~Texture() { texture_cache_.UpdateTexturesTotalHostMemoryUsage(0, host_memory_usage_); } -void TextureCache::Texture::MakeUpToDateAndWatch( +bool TextureCache::Texture::MakeUpToDateAndWatch( const global_unique_lock_type& global_lock) { SharedMemory& shared_memory = texture_cache().shared_memory(); - if (base_outdated_) { + const bool watch_base = base_outdated_; + const bool watch_mips = mips_outdated_; + assert_true(global_lock.owns_lock()); + if (watch_base && + !shared_memory.IsRangeValid( + key().base_page << 12, xe::align(GetGuestBaseSize(), UINT32_C(16)))) { + return false; + } + if (watch_mips && + !shared_memory.IsRangeValid( + key().mip_page << 12, xe::align(GetGuestMipsSize(), UINT32_C(16)))) { + return false; + } + + if (watch_base) { assert_not_zero(GetGuestBaseSize()); base_outdated_ = false; base_watch_handle_ = shared_memory.WatchMemoryRange( key().base_page << 12, GetGuestBaseSize(), TextureCache::WatchCallback, this, nullptr, 0); } - if (mips_outdated_) { + if (watch_mips) { assert_not_zero(GetGuestMipsSize()); mips_outdated_ = false; mips_watch_handle_ = shared_memory.WatchMemoryRange( key().mip_page << 12, GetGuestMipsSize(), TextureCache::WatchCallback, this, nullptr, 1); } + return true; } void TextureCache::Texture::MarkAsUsed() { @@ -781,7 +796,9 @@ void TextureCache::LoadTexturesData(Texture** textures, uint32_t n_textures) { // resolves as well to detect when the CPU wants to reuse the memory for a // regular texture or a vertex buffer, and thus the scaled resolve version // is not up to date anymore. - texture->MakeUpToDateAndWatch(crit); + if (!texture->MakeUpToDateAndWatch(crit)) { + continue; + } texture->LogAction("Loaded"); } @@ -861,7 +878,9 @@ bool TextureCache::LoadTextureData(Texture& texture) { // resolves as well to detect when the CPU wants to reuse the memory for a // regular texture or a vertex buffer, and thus the scaled resolve version is // not up to date anymore. - texture.MakeUpToDateAndWatch(global_critical_region_.Acquire()); + if (!texture.MakeUpToDateAndWatch(global_critical_region_.Acquire())) { + return false; + } texture.LogAction("Loaded"); diff --git a/src/xenia/gpu/texture_cache.h b/src/xenia/gpu/texture_cache.h index aa2f946e8..c1e6ca5c7 100644 --- a/src/xenia/gpu/texture_cache.h +++ b/src/xenia/gpu/texture_cache.h @@ -274,7 +274,7 @@ class TextureCache { // not). bool base_outdated_lockless() const { return base_outdated_; } bool mips_outdated_lockless() const { return mips_outdated_; } - void MakeUpToDateAndWatch(const global_unique_lock_type& global_lock); + bool MakeUpToDateAndWatch(const global_unique_lock_type& global_lock); void WatchCallback(const global_unique_lock_type& global_lock, bool is_mip);