diff --git a/src/xenia/gpu/trace_writer.cc b/src/xenia/gpu/trace_writer.cc index 5b19efeec..51fab9763 100644 --- a/src/xenia/gpu/trace_writer.cc +++ b/src/xenia/gpu/trace_writer.cc @@ -47,6 +47,7 @@ bool TraceWriter::Open(const std::wstring& path, uint32_t title_id) { header.title_id = title_id; fwrite(&header, sizeof(header), 1, file_); + cached_memory_reads_.clear(); return true; } @@ -58,6 +59,8 @@ void TraceWriter::Flush() { void TraceWriter::Close() { if (file_) { + cached_memory_reads_.clear(); + fflush(file_); fclose(file_); file_ = nullptr; @@ -132,6 +135,31 @@ void TraceWriter::WriteMemoryRead(uint32_t base_ptr, size_t length) { WriteMemoryCommand(TraceCommandType::kMemoryRead, base_ptr, length); } +void TraceWriter::WriteMemoryReadCached(uint32_t base_ptr, size_t length) { + if (!file_) { + return; + } + + // HACK: length is guaranteed to be within 32-bits (guest memory) + uint64_t key = uint64_t(base_ptr) << 32 | uint64_t(length); + if (cached_memory_reads_.find(key) == cached_memory_reads_.end()) { + WriteMemoryCommand(TraceCommandType::kMemoryRead, base_ptr, length); + cached_memory_reads_.insert(key); + } +} + +void TraceWriter::WriteMemoryReadCachedNop(uint32_t base_ptr, size_t length) { + if (!file_) { + return; + } + + // HACK: length is guaranteed to be within 32-bits (guest memory) + uint64_t key = uint64_t(base_ptr) << 32 | uint64_t(length); + if (cached_memory_reads_.find(key) == cached_memory_reads_.end()) { + cached_memory_reads_.insert(key); + } +} + void TraceWriter::WriteMemoryWrite(uint32_t base_ptr, size_t length) { if (!file_) { return; diff --git a/src/xenia/gpu/trace_writer.h b/src/xenia/gpu/trace_writer.h index ee3131660..474d846f5 100644 --- a/src/xenia/gpu/trace_writer.h +++ b/src/xenia/gpu/trace_writer.h @@ -10,6 +10,7 @@ #ifndef XENIA_GPU_TRACE_WRITER_H_ #define XENIA_GPU_TRACE_WRITER_H_ +#include #include #include "xenia/base/filesystem.h" @@ -36,6 +37,8 @@ class TraceWriter { void WritePacketStart(uint32_t base_ptr, uint32_t count); void WritePacketEnd(); void WriteMemoryRead(uint32_t base_ptr, size_t length); + void WriteMemoryReadCached(uint32_t base_ptr, size_t length); + void WriteMemoryReadCachedNop(uint32_t base_ptr, size_t length); void WriteMemoryWrite(uint32_t base_ptr, size_t length); void WriteEvent(EventCommand::Type event_type); @@ -43,6 +46,7 @@ class TraceWriter { void WriteMemoryCommand(TraceCommandType type, uint32_t base_ptr, size_t length); + std::set cached_memory_reads_; uint8_t* membase_; FILE* file_; diff --git a/src/xenia/gpu/vulkan/texture_cache.cc b/src/xenia/gpu/vulkan/texture_cache.cc index 2ba8fa44f..81f598b3e 100644 --- a/src/xenia/gpu/vulkan/texture_cache.cc +++ b/src/xenia/gpu/vulkan/texture_cache.cc @@ -348,9 +348,8 @@ TextureCache::Texture* TextureCache::AllocateTexture( // Check the device limits for the format before we create it. VkFormatProperties props; vkGetPhysicalDeviceFormatProperties(*device_, format, &props); - uint32_t required_flags = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | - VK_FORMAT_FEATURE_BLIT_DST_BIT | - VK_FORMAT_FEATURE_BLIT_SRC_BIT; + uint32_t required_flags = + VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT; if ((props.optimalTilingFeatures & required_flags) != required_flags) { // Texture needs conversion on upload to a native format. // assert_always(); @@ -484,10 +483,13 @@ TextureCache::Texture* TextureCache::Demand(const TextureInfo& texture_info, if (it->second->texture_info == texture_info) { if (it->second->pending_invalidation) { // This texture has been invalidated! - Scavenge(); + RemoveInvalidatedTextures(); break; } + trace_writer_->WriteMemoryReadCached(texture_info.guest_address, + texture_info.input_length); + return it->second; } } @@ -501,6 +503,12 @@ TextureCache::Texture* TextureCache::Demand(const TextureInfo& texture_info, texture->texture_info.size_2d.logical_width && texture_info.size_2d.logical_height == texture->texture_info.size_2d.logical_height) { + if (texture->pending_invalidation) { + // Texture invalidated! Remove. + RemoveInvalidatedTextures(); + break; + } + // Exact match. // TODO: Lazy match (at an offset) // Upgrade this texture to a full texture. @@ -511,6 +519,10 @@ TextureCache::Texture* TextureCache::Demand(const TextureInfo& texture_info, memory_->CancelAccessWatch(texture->access_watch_handle); } + // Tell the trace writer to cache this memory but don't read it + trace_writer_->WriteMemoryReadCachedNop(texture_info.guest_address, + texture_info.input_length); + texture->access_watch_handle = memory_->AddPhysicalAccessWatch( texture_info.guest_address, texture_info.input_length, cpu::MMIOHandler::kWatchWrite, @@ -548,6 +560,9 @@ TextureCache::Texture* TextureCache::Demand(const TextureInfo& texture_info, return nullptr; } + trace_writer_->WriteMemoryRead(texture_info.guest_address, + texture_info.input_length); + bool uploaded = false; switch (texture_info.dimension) { case Dimension::k1D: { @@ -1479,9 +1494,6 @@ bool TextureCache::SetupTextureBinding(VkCommandBuffer command_buffer, uint16_t swizzle = static_cast(fetch.swizzle); auto view = DemandView(texture, swizzle); - trace_writer_->WriteMemoryRead(texture_info.guest_address, - texture_info.input_length); - auto image_info = &update_set_info->image_infos[update_set_info->image_write_count]; auto image_write = @@ -1507,35 +1519,7 @@ bool TextureCache::SetupTextureBinding(VkCommandBuffer command_buffer, return true; } -void TextureCache::ClearCache() { - // TODO(DrChat): Nuke everything. -} - -void TextureCache::Scavenge() { - // Close any open descriptor pool batches - if (descriptor_pool_->has_open_batch()) { - descriptor_pool_->EndBatch(); - } - - // Free unused descriptor sets - // TODO(DrChat): These sets could persist across frames, we just need a smart - // way to detect if they're unused and free them. - texture_bindings_.clear(); - descriptor_pool_->Scavenge(); - staging_buffer_.Scavenge(); - - // Kill all pending delete textures. - if (!pending_delete_textures_.empty()) { - for (auto it = pending_delete_textures_.begin(); - it != pending_delete_textures_.end();) { - if (!FreeTexture(*it)) { - break; - } - - it = pending_delete_textures_.erase(it); - } - } - +void TextureCache::RemoveInvalidatedTextures() { // Clean up any invalidated textures. invalidated_textures_mutex_.lock(); std::vector& invalidated_textures = *invalidated_textures_; @@ -1574,6 +1558,37 @@ void TextureCache::Scavenge() { invalidated_resolve_textures_mutex_.unlock(); } +void TextureCache::ClearCache() { + // TODO(DrChat): Nuke everything. +} + +void TextureCache::Scavenge() { + // Close any open descriptor pool batches + if (descriptor_pool_->has_open_batch()) { + descriptor_pool_->EndBatch(); + } + + // Free unused descriptor sets + // TODO(DrChat): These sets could persist across frames, we just need a smart + // way to detect if they're unused and free them. + texture_bindings_.clear(); + descriptor_pool_->Scavenge(); + staging_buffer_.Scavenge(); + + // Kill all pending delete textures. + RemoveInvalidatedTextures(); + if (!pending_delete_textures_.empty()) { + for (auto it = pending_delete_textures_.begin(); + it != pending_delete_textures_.end();) { + if (!FreeTexture(*it)) { + break; + } + + it = pending_delete_textures_.erase(it); + } + } +} + } // namespace vulkan } // namespace gpu } // namespace xe diff --git a/src/xenia/gpu/vulkan/texture_cache.h b/src/xenia/gpu/vulkan/texture_cache.h index 3f8653136..6a7aa95ca 100644 --- a/src/xenia/gpu/vulkan/texture_cache.h +++ b/src/xenia/gpu/vulkan/texture_cache.h @@ -180,6 +180,9 @@ class TextureCache { UpdateSetInfo* update_set_info, const Shader::TextureBinding& binding); + // Removes invalidated textures from the cache, queues them for delete. + void RemoveInvalidatedTextures(); + Memory* memory_ = nullptr; RegisterFile* register_file_ = nullptr;