This commit is contained in:
Arthur Cuesta 2026-06-10 06:24:05 -03:00 committed by GitHub
commit 8dd6b94cc3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 96 additions and 14 deletions

View File

@ -86,17 +86,20 @@ ImageId TextureCache::GetNullImage(const vk::Format format) {
}
void TextureCache::ProcessDownloadImages() {
std::scoped_lock lock{mutex};
for (const ImageId image_id : download_images) {
DownloadImageMemory(image_id);
}
download_images.clear();
}
void TextureCache::DownloadImageMemory(ImageId image_id) {
std::optional<TextureCache::PendingImageDownload> TextureCache::ScheduleImageDownload(
ImageId image_id) {
Image& image = slot_images[image_id];
if (False(image.flags & ImageFlagBits::GpuModified)) {
return;
if (!image.SafeToDownload() || image.info.props.is_tiled || image.info.guest_address == 0) {
return {};
}
auto& download_buffer = buffer_cache.GetUtilityBuffer(MemoryUsage::Download);
const u32 download_size = image.info.pitch * image.info.size.height *
image.info.resources.layers * (image.info.num_bits / 8);
@ -118,17 +121,66 @@ void TextureCache::DownloadImageMemory(ImageId image_id) {
.imageOffset = {0, 0, 0},
.imageExtent = {image.info.size.width, image.info.size.height, 1},
};
scheduler.EndRendering();
const auto cmdbuf = scheduler.CommandBuffer();
image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {});
cmdbuf.copyImageToBuffer(image.GetImage(), vk::ImageLayout::eTransferSrcOptimal,
download_buffer.Handle(), image_download);
image.Download(std::span{&image_download, 1}, download_buffer.Handle(), offset, download_size);
scheduler.DeferPriorityOperation(
[this, device_addr = image.info.guest_address, download, download_size] {
Core::Memory::Instance()->TryWriteBacking(std::bit_cast<u8*>(device_addr), download,
download_size);
});
const u64 serial = ++image_download_serial;
latest_image_downloads[image.info.guest_address] = serial;
return PendingImageDownload{
.serial = serial,
.guest_address = image.info.guest_address,
.data = download,
.size = download_size,
};
}
void TextureCache::WritebackImageMemory(const PendingImageDownload& download) {
const auto it = latest_image_downloads.find(download.guest_address);
if (it == latest_image_downloads.end() || it->second != download.serial) {
return;
}
if (!Core::Memory::Instance()->TryWriteBacking(std::bit_cast<u8*>(download.guest_address),
download.data, download.size)) {
return;
}
latest_image_downloads.erase(it);
}
void TextureCache::DownloadImageMemory(ImageId image_id) {
const auto download = ScheduleImageDownload(image_id);
if (!download) {
return;
}
scheduler.DeferPriorityOperation([this, download = *download] {
std::scoped_lock lock{mutex};
WritebackImageMemory(download);
});
}
void TextureCache::SynchronizeGuestMemory(VAddr address, size_t size, const Image* excluded_image) {
if (!readback_linear_images) {
return;
}
boost::container::small_vector<PendingImageDownload, 8> downloads;
ForEachImageInRegion(address, size, [&](ImageId image_id, Image& image) {
if (&image == excluded_image) {
return;
}
if (auto download = ScheduleImageDownload(image_id)) {
downloads.push_back(*download);
download_images.erase(image_id);
}
});
if (downloads.empty()) {
return;
}
// The requested alias may be uploaded from guest memory immediately after this call. Wait for
// overlapping linear images to reach the download buffer before exposing their contents.
scheduler.Finish();
for (const auto& download : downloads) {
WritebackImageMemory(download);
}
}
void TextureCache::MarkAsMaybeDirty(ImageId image_id, Image& image) {
@ -557,6 +609,13 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_fmt) {
image_id = cache_id;
}
// A different representation may be uploaded from guest memory while an overlapping linear
// image still contains newer GPU-written data. Resolve pending writes before touching the
// overlap set.
if (!image_id) {
SynchronizeGuestMemory(info.guest_address, info.guest_size);
}
// Try to resolve overlaps (if any)
int view_mip{-1};
int view_slice{-1};
@ -721,6 +780,10 @@ void TextureCache::RefreshImage(Image& image) {
return;
}
// RefreshImage reuploads from guest memory. Make overlapping GPU-written linear aliases
// visible before obtaining the upload source.
SynchronizeGuestMemory(image.info.guest_address, image.info.guest_size, &image);
RENDERER_TRACE;
TRACE_HINT(fmt::format("{:x}:{:x}", image.info.guest_address, image.info.guest_size));

View File

@ -5,6 +5,7 @@
#include <condition_variable>
#include <mutex>
#include <optional>
#include <thread>
#include <unordered_set>
#include <boost/container/small_vector.hpp>
@ -271,9 +272,25 @@ private:
/// Gets or creates a null image for a particular format.
ImageId GetNullImage(vk::Format format);
/// Copies image memory back to CPU.
struct PendingImageDownload {
u64 serial;
VAddr guest_address;
u8* data;
u32 size;
};
/// Schedules a copy of linear image memory into the download buffer.
[[nodiscard]] std::optional<PendingImageDownload> ScheduleImageDownload(ImageId image_id);
/// Copies a completed image download into guest memory unless a newer download superseded it.
void WritebackImageMemory(const PendingImageDownload& download);
/// Copies image memory back to CPU asynchronously.
void DownloadImageMemory(ImageId image_id);
/// Makes GPU-written linear aliases visible before reuploading overlapping guest memory.
void SynchronizeGuestMemory(VAddr address, size_t size, const Image* excluded_image = nullptr);
/// Thread function for copying downloaded images out to CPU memory.
void DownloadedImagesThread(const std::stop_token& token);
@ -323,6 +340,8 @@ private:
tsl::robin_map<u64, Sampler> samplers;
tsl::robin_map<vk::Format, ImageId> null_images;
std::unordered_set<ImageId> download_images;
tsl::robin_map<VAddr, u64> latest_image_downloads;
u64 image_download_serial{};
u64 total_used_memory = 0;
u64 trigger_gc_memory = 0;
u64 pressure_gc_memory = 0;