aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/texture_cache
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-29 03:39:52 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-29 03:39:52 -0400
commite17fd9cef116a088f080ca502951e500864a58b6 (patch)
treee7ff65f614622515d6a2e4263521909eea89a5d8 /src/video_core/texture_cache
parentea6c1193fbfa95e586bb6d129dc684163a12cb3e (diff)
parent80c79333fe6fd28a31b65b997f1cbf2987711c91 (diff)
Merge Switch 2 work from local Eden (nx2 at b0dd3fe)
Diffstat (limited to 'src/video_core/texture_cache')
-rw-r--r--src/video_core/texture_cache/accelerated_swizzle.cpp2
-rw-r--r--src/video_core/texture_cache/accelerated_swizzle.h2
-rw-r--r--src/video_core/texture_cache/format_lookup_table.cpp1
-rw-r--r--src/video_core/texture_cache/image_base.h6
-rw-r--r--src/video_core/texture_cache/image_info.cpp15
-rw-r--r--src/video_core/texture_cache/image_info.h14
-rw-r--r--src/video_core/texture_cache/pending_download.h58
-rw-r--r--src/video_core/texture_cache/texture_cache.h385
-rw-r--r--src/video_core/texture_cache/texture_cache_base.h36
-rw-r--r--src/video_core/texture_cache/util.cpp62
-rw-r--r--src/video_core/texture_cache/util.h6
11 files changed, 527 insertions, 60 deletions
diff --git a/src/video_core/texture_cache/accelerated_swizzle.cpp b/src/video_core/texture_cache/accelerated_swizzle.cpp
index 4c3f724d79..8ae0ed218d 100644
--- a/src/video_core/texture_cache/accelerated_swizzle.cpp
+++ b/src/video_core/texture_cache/accelerated_swizzle.cpp
@@ -37,6 +37,7 @@ BlockLinearSwizzle2DParams MakeBlockLinearSwizzle2DParams(const SwizzleParameter
.x_shift = GOB_SIZE_SHIFT + block.height + block.depth,
.block_height = block.height,
.block_height_mask = (1U << block.height) - 1,
+ .native_tiling = info.native_tiling,
};
}
@@ -63,6 +64,7 @@ BlockLinearSwizzle3DParams MakeBlockLinearSwizzle3DParams(const SwizzleParameter
.block_height_mask = (1U << block.height) - 1,
.block_depth = block.depth,
.block_depth_mask = (1U << block.depth) - 1,
+ .native_tiling = info.native_tiling,
};
}
diff --git a/src/video_core/texture_cache/accelerated_swizzle.h b/src/video_core/texture_cache/accelerated_swizzle.h
index d4250da689..8296950c38 100644
--- a/src/video_core/texture_cache/accelerated_swizzle.h
+++ b/src/video_core/texture_cache/accelerated_swizzle.h
@@ -20,6 +20,7 @@ struct BlockLinearSwizzle2DParams {
u32 x_shift;
u32 block_height;
u32 block_height_mask;
+ u32 native_tiling;
};
struct BlockLinearSwizzle3DParams {
@@ -33,6 +34,7 @@ struct BlockLinearSwizzle3DParams {
u32 block_height_mask;
u32 block_depth;
u32 block_depth_mask;
+ u32 native_tiling;
};
[[nodiscard]] BlockLinearSwizzle2DParams MakeBlockLinearSwizzle2DParams(
diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp
index d6e019cddf..20bb96aae3 100644
--- a/src/video_core/texture_cache/format_lookup_table.cpp
+++ b/src/video_core/texture_cache/format_lookup_table.cpp
@@ -157,6 +157,7 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red,
return PixelFormat::S8_UINT_D24_UNORM;
case Hash(TextureFormat::G24R8, UINT, UNORM, UNORM, UNORM, LINEAR):
return PixelFormat::S8_UINT_D24_UNORM;
+ case Hash(TextureFormat::S8Z24, UNORM, UINT, UNORM, UNORM, LINEAR):
case Hash(TextureFormat::S8Z24, UNORM, UINT, UINT, UINT, LINEAR):
return PixelFormat::D24_UNORM_S8_UINT;
case Hash(TextureFormat::Z32_X24S8, FLOAT, UINT, UNORM, UNORM, LINEAR):
diff --git a/src/video_core/texture_cache/image_base.h b/src/video_core/texture_cache/image_base.h
index 79fc8505d6..47bab9617a 100644
--- a/src/video_core/texture_cache/image_base.h
+++ b/src/video_core/texture_cache/image_base.h
@@ -45,6 +45,11 @@ enum class ImageFlagBits : u32 {
AsynchronousDecode = 1 << 16,
IsDecoding = 1 << 17, ///< Is currently being decoded asynchronously.
+ Unbacked = 1 << 18, ///< Host image has no memory (too big to back); never upload/download.
+ /// The host image keeps only mip levels >= host_level_offset (its decoded copy would not fit
+ /// in VRAM); uploads skip the rest, downloads are never done.
+ TrimmedLevels = 1 << 19,
+ ZeroFilled = 1 << 20, ///< Cleared to zero blocks before its first asynchronous decode.
};
DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits)
@@ -93,6 +98,7 @@ struct ImageBase {
u32 unswizzled_size_bytes = 0;
u32 converted_size_bytes = 0;
u32 scale_rating = 0;
+ u32 host_level_offset = 0; ///< Guest mip level held by host mip 0 (see TrimmedLevels).
u64 scale_tick = 0;
bool has_scaled = false;
diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp
index 8b59eb6841..7e86bf00cf 100644
--- a/src/video_core/texture_cache/image_info.cpp
+++ b/src/video_core/texture_cache/image_info.cpp
@@ -4,9 +4,13 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <algorithm>
+#include <array>
+#include <vector>
#include <fmt/ranges.h>
#include "common/assert.h"
+#include "common/logging.h"
#include "common/settings.h"
#include "video_core/surface.h"
#include "video_core/texture_cache/format_lookup_table.h"
@@ -109,14 +113,23 @@ ImageInfo::ImageInfo(const TICEntry& config) noexcept {
size.height = config.Height();
resources.layers = config.BaseLayer() + config.Depth() * 6;
break;
- case TextureType::Texture3D:
+ case TextureType::Texture3D: {
ASSERT(config.BaseLayer() == 0);
type = ImageType::e3D;
size.width = config.Width();
size.height = config.Height();
size.depth = config.Depth();
resources.layers = 1;
+ // A few distinct 3D descriptors, raw (to check Switch 2 volume fields).
+ static std::vector<std::array<u64, 4>> logged;
+ if (logged.size() < 16 && std::find(logged.begin(), logged.end(), config.raw) == logged.end()) {
+ logged.push_back(config.raw);
+ LOG_INFO(HW_GPU, "3D TIC {:016x} {:016x} {:016x} {:016x}: {}x{}x{} block_depth {} levels {}",
+ config.raw[0], config.raw[1], config.raw[2], config.raw[3], size.width, size.height,
+ size.depth, config.block_depth.Value(), config.max_mip_level.Value() + 1);
+ }
break;
+ }
case TextureType::Texture1DBuffer:
type = ImageType::Buffer;
size.width = config.Width();
diff --git a/src/video_core/texture_cache/image_info.h b/src/video_core/texture_cache/image_info.h
index eb490a6424..79237aa482 100644
--- a/src/video_core/texture_cache/image_info.h
+++ b/src/video_core/texture_cache/image_info.h
@@ -14,6 +14,17 @@ namespace VideoCommon {
using Tegra::Texture::TICEntry;
using VideoCore::Surface::PixelFormat;
+// Native descriptors address the selected view directly. The legacy NVN
+// layer-base bookkeeping is not an additional array prefix for this ABI.
+inline TICEntry TextureViewHeader(TICEntry entry, bool native) {
+ if (native) {
+ entry.layer_base_0_2.Assign(0);
+ entry.layer_base_3_7.Assign(0);
+ entry.layer_base_8_10.Assign(0);
+ }
+ return entry;
+}
+
struct ImageInfo {
ImageInfo() = default;
explicit ImageInfo(const TICEntry& config) noexcept;
@@ -37,11 +48,14 @@ struct ImageInfo {
u32 maybe_unaligned_layer_stride = 0;
u32 num_samples = 1;
u32 tile_width_spacing = 0;
+ bool native_tiling = false;
bool rescaleable = false;
bool downscaleable = false;
bool forced_flushed = false;
bool dma_downloaded = false;
bool is_sparse = false;
+ // ASTC data whose blocks carry no alpha: re-compressed to BC1 instead of BC3 (half the size).
+ bool astc_opaque = false;
};
} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/pending_download.h b/src/video_core/texture_cache/pending_download.h
new file mode 100644
index 0000000000..52a48e82d3
--- /dev/null
+++ b/src/video_core/texture_cache/pending_download.h
@@ -0,0 +1,58 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+#include <algorithm>
+#include <memory>
+#include <unordered_set>
+#include <cstddef>
+#include <utility>
+#include "common/slot_vector.h"
+
+namespace VideoCommon {
+
+template <class Image>
+struct PendingImageDownload {
+ bool is_swizzle;
+ size_t async_buffer_id;
+ Common::SlotId object_id;
+ std::shared_ptr<Image> retired_image{};
+
+ Image& GetImage(Common::SlotVector<Image>& images) const {
+ return retired_image ? *retired_image : images[object_id];
+ }
+};
+
+// Whole-image copies are recorded together at commit time. Repeated requests
+// for the same live image therefore observe the same contents. Keep the first
+// occurrence so reverse CPU writeback order remains unchanged for aliases.
+template <class Image, class Downloads>
+size_t DeduplicatePendingImageDownloads(Downloads& downloads, Common::SlotVector<Image>& images) {
+ std::unordered_set<const Image*> seen;
+ const size_t previous_size = downloads.size();
+ const auto end = std::remove_if(downloads.begin(), downloads.end(), [&](const auto& download) {
+ return download.is_swizzle && !seen.insert(&download.GetImage(images)).second;
+ });
+ downloads.erase(end, downloads.end());
+ return previous_size - downloads.size();
+}
+
+// Cache slots may be recycled before queued GPU downloads are submitted or
+// consumed. Keep the actual image, including its metadata, until those finish.
+template <class Image, class Downloads, class Batches>
+bool RetainPendingImageDownloads(Common::SlotId id, Image& image, Downloads& pending,
+ Batches& submitted) {
+ std::shared_ptr<Image> retained;
+ const auto retain = [&](auto& downloads) {
+ for (auto& download : downloads) {
+ if (!download.is_swizzle || download.object_id != id || download.retired_image) continue;
+ if (!retained) retained = std::make_shared<Image>(std::move(image));
+ download.retired_image = retained;
+ }
+ };
+ retain(pending);
+ for (auto& downloads : submitted) retain(downloads);
+ return retained != nullptr;
+}
+
+} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index e92e54f8ce..fb8adef161 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -6,7 +6,11 @@
#pragma once
+#include <algorithm>
+#include <cstdlib>
#include <limits>
+#include <map>
+#include <vector>
#include <optional>
#include <bit>
#include "common/container/unordered_map.h"
@@ -24,6 +28,7 @@
#include "video_core/texture_cache/samples_helper.h"
#include "video_core/texture_cache/texture_cache_base.h"
#include "video_core/texture_cache/util.h"
+#include "video_core/nx2_profile.h"
#include "video_core/textures/decoders.h"
namespace VideoCommon {
@@ -76,6 +81,7 @@ TextureCache<P>::TextureCache(Runtime& runtime_, Tegra::MaxwellDeviceMemoryManag
critical_memory = DEFAULT_CRITICAL_MEMORY + 1_GiB;
minimum_memory = 0;
}
+ ApplyThresholdOverrides();
const bool gpu_unswizzle_enabled = Settings::values.gpu_unswizzle_enabled.GetValue();
@@ -148,6 +154,7 @@ void TextureCache<P>::RunGarbageCollector() {
if (True(image.flags & ImageFlagBits::Tracked)) {
UntrackImage(image, image_id);
}
+ ++(True(image.flags & ImageFlagBits::CostlyLoad) ? gc_costly_deletes : gc_deletes);
UnregisterImage(image_id);
DeleteImage(image_id, image.scale_tick > frame_tick + 5);
if (aggressive_mode && total_used_memory < critical_memory) {
@@ -168,11 +175,118 @@ void TextureCache<P>::RunGarbageCollector() {
}
template <class P>
+void TextureCache<P>::ApplyThresholdOverrides(bool log) {
+ // EDEN_NX2_TC_EXPECTED_MIB / EDEN_NX2_TC_CRITICAL_MIB override the collector's thresholds.
+ for (const auto& [name, target] : {std::pair{"EDEN_NX2_TC_EXPECTED_MIB", &expected_memory},
+ std::pair{"EDEN_NX2_TC_CRITICAL_MIB", &critical_memory}}) {
+ if (const char* value = std::getenv(name)) {
+ *target = std::strtoull(value, nullptr, 10) << 20;
+ }
+ }
+ if (log) {
+ LOG_INFO(HW_GPU, "Texture cache thresholds: expected {} MiB, critical {} MiB, minimum {} MiB",
+ expected_memory >> 20, critical_memory >> 20, minimum_memory >> 20);
+ }
+}
+
+template <class P>
void TextureCache<P>::TickFrame() {
+ if constexpr (HAS_DEVICE_MEMORY_INFO) {
+ // Switch 2 titles (native tiling) stream far more texture data than Eden's thresholds
+ // were tuned for: title 1's attract loop alone keeps ~5.3 GB resident, so the collector
+ // sat in aggressive mode evicting decoded ASTC (and decoding it again). Total use is
+ // measured from the driver, so collect only near the device budget instead.
+ if (!native_thresholds_applied && gpu_memory != nullptr && gpu_memory->UsesNativeTiling()) {
+ native_thresholds_applied = true;
+ const u64 device_local = runtime.GetDeviceLocalMemory();
+ critical_memory = (std::max)(critical_memory, device_local - 256_MiB);
+ expected_memory = (std::max)(expected_memory, device_local - 1280_MiB);
+ ApplyThresholdOverrides();
+ native_critical_memory = critical_memory;
+ native_expected_memory = expected_memory;
+ }
+ // Follow the driver's budget (re-read now and then): it shrinks when other programs use
+ // VRAM (another game: 7.0 -> 6.4 GB), and collecting only past it means the driver pages
+ // textures out. It is also the room to use: Eden's device memory figure is capped near
+ // 7 GB, which on a 12 GB card (11.2 GB budget) kept title 1 evicting textures with 3 GB
+ // unused. EDEN_NX2_TC_*_MIB overrides still win.
+ if constexpr (requires { runtime.GetDeviceMemoryBudget(); }) {
+ if (native_thresholds_applied && budget_check_frames++ % 60 == 0) {
+ const u64 budget = runtime.GetDeviceMemoryBudget();
+ if (budget > 2_GiB) {
+ const u64 old_critical = critical_memory;
+ critical_memory = budget - 768_MiB;
+ expected_memory = budget - 1536_MiB;
+ // Log only real changes (the budget moves by a few MiB all the time).
+ const u64 change = critical_memory > old_critical
+ ? critical_memory - old_critical
+ : old_critical - critical_memory;
+ ApplyThresholdOverrides(change >= 256_MiB);
+ }
+ }
+ }
+ }
// If we can obtain the memory info, use it instead of the estimate.
if (runtime.CanReportMemoryUsage()) {
total_used_memory = runtime.GetDeviceMemoryUsage();
}
+ // Every 600 frames: device memory in use against the collector's thresholds, and what the
+ // images themselves account for (VRAM pressure makes the collector thrash).
+ if (static u64 frames{}; ++frames % 600 == 0) {
+ u64 image_bytes{};
+ size_t images{};
+ std::vector<std::pair<u64, const Image*>> largest;
+ std::map<u32, std::pair<u64, u32>> by_format;
+ for (const auto [image_id, image] : slot_images) {
+ const u64 bytes = GetScaledImageSizeBytes(*image);
+ image_bytes += bytes;
+ ++images;
+ largest.emplace_back(bytes, image);
+ auto& format = by_format[static_cast<u32>(image->info.format)];
+ format.first += bytes;
+ ++format.second;
+ }
+ LOG_INFO(HW_GPU, "Texture cache: device memory {} MiB (expected {} MiB, critical {} MiB), {} images {} MiB",
+ total_used_memory >> 20, expected_memory >> 20, critical_memory >> 20, images,
+ image_bytes >> 20);
+ std::partial_sort(largest.begin(), largest.begin() + std::min<size_t>(12, largest.size()),
+ largest.end(), [](const auto& a, const auto& b) { return a.first > b.first; });
+ for (size_t i = 0; i < std::min<size_t>(12, largest.size()); ++i) {
+ const Image& image = *largest[i].second;
+ LOG_INFO(HW_GPU, " {} MiB: {:#x} {}x{}x{} layers {} levels {} format {} type {} flags {:#x}",
+ largest[i].first >> 20, image.gpu_addr, image.info.size.width, image.info.size.height,
+ image.info.size.depth, image.info.resources.layers, image.info.resources.levels,
+ static_cast<u32>(image.info.format), static_cast<u32>(image.info.type),
+ static_cast<u32>(image.flags));
+ }
+ std::vector<std::pair<u64, u32>> formats;
+ for (const auto& [format, total] : by_format) formats.emplace_back(total.first, format);
+ std::sort(formats.rbegin(), formats.rend());
+ for (size_t i = 0; i < std::min<size_t>(8, formats.size()); ++i) {
+ LOG_INFO(HW_GPU, " format {}: {} MiB in {} images", formats[i].second, formats[i].first >> 20,
+ by_format[formats[i].second].second);
+ }
+ if constexpr (requires { runtime.LogMemoryStatistics(); }) {
+ runtime.LogMemoryStatistics();
+ }
+ LOG_INFO(HW_GPU, "Texture cache last 600 frames: created {} images, deleted {} (collector {}, {} costly), {} async decodes, ASTC {} opaque / {} alpha",
+ images_created, images_deleted, gc_deletes + gc_costly_deletes, gc_costly_deletes,
+ async_decodes_queued, astc_opaque_images, astc_alpha_images);
+ astc_opaque_images = astc_alpha_images = 0;
+ std::vector<std::pair<u32, u64>> shapes;
+ for (const auto& [key, value] : created_shapes) shapes.emplace_back(value.first, key);
+ std::sort(shapes.rbegin(), shapes.rend());
+ for (size_t i = 0; i < std::min<size_t>(10, shapes.size()); ++i) {
+ const u64 key = shapes[i].second;
+ LOG_INFO(HW_GPU, " created {}x: format {} type {} levels {} layers {} {}x{} (last at {:#x})",
+ shapes[i].first, key >> 56, (key >> 52) & 0xF, (key >> 48) & 0xF,
+ (key >> 36) & 0xFFF, (key >> 18) & 0x3FFFF, key & 0x3FFFF,
+ created_shapes[key].second);
+ }
+ created_shapes.clear();
+ gc_deletes = gc_costly_deletes = async_decodes_queued = 0;
+ images_created = images_deleted = 0;
+ }
if (total_used_memory > minimum_memory) {
RunGarbageCollector();
}
@@ -597,7 +711,7 @@ void TextureCache<P>::WriteMemory(DAddr cpu_addr, size_t size) {
template <class P>
bool TextureCache<P>::IsDownloadable(const ImageBase& image) const noexcept {
- if (!image.IsSafeGpuCopy()) {
+ if (!image.IsSafeGpuCopy() || True(image.flags & (ImageFlagBits::Unbacked | ImageFlagBits::TrimmedLevels))) {
return false;
}
if (image.info.num_samples == 1) {
@@ -714,6 +828,14 @@ bool TextureCache<P>::BlitImage(const Tegra::Engines::Fermi2D::Surface& dst,
Image& dst_image = slot_images[dst_id];
Image& src_image = slot_images[src_id];
+ if (True(dst_image.flags & ImageFlagBits::Unbacked)) {
+ static u32 logged{};
+ if (logged++ < 64) {
+ LOG_INFO(HW_GPU, "2D blit into unbacked image {:#x}: dst {:#x} {}x{} from {:#x} {}x{}",
+ dst_image.gpu_addr, dst.Address(), dst.width, dst.height, src.Address(),
+ src.width, src.height);
+ }
+ }
bool is_src_rescaled = True(src_image.flags & ImageFlagBits::Rescaled);
bool is_dst_rescaled = True(dst_image.flags & ImageFlagBits::Rescaled);
@@ -803,6 +925,17 @@ std::pair<typename P::ImageView*, bool> TextureCache<P>::TryFindFramebufferImage
if (it == page_table.end()) {
return {};
}
+ const auto view_format = [&]() {
+ switch (config.pixel_format) {
+ case Service::android::PixelFormat::Rgb565:
+ return PixelFormat::R5G6B5_UNORM;
+ case Service::android::PixelFormat::Bgra8888:
+ return PixelFormat::B8G8R8A8_UNORM;
+ default:
+ return PixelFormat::A8B8G8R8_UNORM;
+ }
+ }();
+
const auto& image_map_ids = it->second;
boost::container::small_vector<ImageId, 4> valid_image_ids;
for (const ImageMapId map_id : image_map_ids) {
@@ -814,20 +947,17 @@ std::pair<typename P::ImageView*, bool> TextureCache<P>::TryFindFramebufferImage
if (image.image_view_ids.empty()) {
continue;
}
+ if (!VideoCore::Surface::IsViewCompatible(image.info.format, view_format, false, true)) {
+ static u32 rejected_alias_count{};
+ if (std::getenv("EDEN_NATIVE_DRAW_DIAGNOSTICS") && rejected_alias_count++ < 8) {
+ LOG_WARNING(HW_GPU, "Ignoring incompatible framebuffer alias: address={:#x}, image={}, view={}",
+ cpu_addr, image.info.format, view_format);
+ }
+ continue;
+ }
valid_image_ids.push_back(map.image_id);
}
- const auto view_format = [&]() {
- switch (config.pixel_format) {
- case Service::android::PixelFormat::Rgb565:
- return PixelFormat::R5G6B5_UNORM;
- case Service::android::PixelFormat::Bgra8888:
- return PixelFormat::B8G8R8A8_UNORM;
- default:
- return PixelFormat::A8B8G8R8_UNORM;
- }
- }();
-
const auto GetImageViewForFramebuffer = [&](ImageId image_id) {
ImageViewInfo info{ImageViewType::e2D, view_format};
if (config.blending == Tegra::BlendMode::Opaque) {
@@ -860,6 +990,11 @@ bool TextureCache<P>::HasUncommittedFlushes() const noexcept {
}
template <class P>
+bool TextureCache<P>::HasAsyncFlushWork() const noexcept {
+ return !uncommitted_downloads.empty() || !uncommitted_async_buffers.empty();
+}
+
+template <class P>
bool TextureCache<P>::ShouldWaitAsyncFlushes() const noexcept {
return !committed_downloads.empty() && !committed_downloads.front().empty();
}
@@ -869,6 +1004,7 @@ void TextureCache<P>::CommitAsyncFlushes() {
// This is intentionally passing the value by copy
if constexpr (IMPLEMENTS_ASYNC_DOWNLOADS) {
auto& download_ids = uncommitted_downloads;
+ const size_t redundant_images = DeduplicatePendingImageDownloads(download_ids, slot_images);
if (download_ids.empty()) {
committed_downloads.emplace_back(std::move(uncommitted_downloads));
uncommitted_downloads.clear();
@@ -882,17 +1018,32 @@ void TextureCache<P>::CommitAsyncFlushes() {
for (PendingDownload& download_info : download_ids) {
if (download_info.is_swizzle) {
total_size_bytes +=
- Common::AlignUp(slot_images[download_info.object_id].unswizzled_size_bytes, 64);
+ Common::AlignUp(download_info.GetImage(slot_images).unswizzled_size_bytes, 64);
any_none_dma = true;
download_info.async_buffer_id = last_async_buffer_id;
}
}
if (any_none_dma) {
+ if (std::getenv("EDEN_NATIVE_DRAW_DIAGNOSTICS") && total_size_bytes >= 64_MiB) {
+ LOG_WARNING(HW_GPU, "Large image readback batch: bytes={}, entries={}, duplicates_removed={}, submitted_batches={}, retained_buffers={}",
+ total_size_bytes, download_ids.size(), redundant_images,
+ committed_downloads.size(), async_buffers_death_ring.size());
+ size_t traced_images = 0;
+ for (const auto& download : download_ids) {
+ if (!download.is_swizzle || traced_images++ >= 8) continue;
+ const auto& image = download.GetImage(slot_images);
+ LOG_WARNING(HW_GPU, "Readback image: gpu={:#x}, cpu={:#x}, format={}, size={}x{}x{}, levels={}, layers={}, layer_stride={}, guest_bytes={}, download_bytes={}",
+ image.gpu_addr, image.cpu_addr, image.info.format,
+ image.info.size.width, image.info.size.height, image.info.size.depth,
+ image.info.resources.levels, image.info.resources.layers,
+ image.info.layer_stride, image.guest_size_bytes, image.unswizzled_size_bytes);
+ }
+ }
auto download_map = runtime.DownloadStagingBuffer(total_size_bytes, true);
for (const PendingDownload& download_info : download_ids) {
if (download_info.is_swizzle) {
- Image& image = slot_images[download_info.object_id];
+ Image& image = download_info.GetImage(slot_images);
const auto copies = FullDownloadCopies(image.info);
image.DownloadMemory(download_map, copies);
download_map.offset += Common::AlignUp(image.unswizzled_size_bytes, 64);
@@ -925,7 +1076,7 @@ void TextureCache<P>::PopAsyncFlushes() {
auto& download_info = download_ids[i - 1];
auto& download_buffer = download_map[download_info.async_buffer_id];
if (download_info.is_swizzle) {
- const ImageBase& image = slot_images[download_info.object_id];
+ const ImageBase& image = download_info.GetImage(slot_images);
const auto copies = FullDownloadCopies(image.info);
download_buffer.offset -= Common::AlignUp(image.unswizzled_size_bytes, 64);
std::span<u8> download_span =
@@ -954,7 +1105,7 @@ void TextureCache<P>::PopAsyncFlushes() {
size_t total_size_bytes = 0;
for (const PendingDownload& download_info : download_ids) {
if (download_info.is_swizzle) {
- total_size_bytes += slot_images[download_info.object_id].unswizzled_size_bytes;
+ total_size_bytes += download_info.GetImage(slot_images).unswizzled_size_bytes;
}
}
auto download_map = runtime.DownloadStagingBuffer(total_size_bytes);
@@ -963,7 +1114,7 @@ void TextureCache<P>::PopAsyncFlushes() {
if (!download_info.is_swizzle) {
continue;
}
- Image& image = slot_images[download_info.object_id];
+ Image& image = download_info.GetImage(slot_images);
const auto copies = FullDownloadCopies(image.info);
image.DownloadMemory(download_map, copies);
download_map.offset += image.unswizzled_size_bytes;
@@ -976,7 +1127,7 @@ void TextureCache<P>::PopAsyncFlushes() {
if (!download_info.is_swizzle) {
continue;
}
- const ImageBase& image = slot_images[download_info.object_id];
+ const ImageBase& image = download_info.GetImage(slot_images);
const auto copies = FullDownloadCopies(image.info);
SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, download_span, swizzle_data_buffer);
download_map.offset += image.unswizzled_size_bytes;
@@ -1117,11 +1268,18 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) {
// Only upload modified images
return;
}
+ VideoCommon::Nx2Profile::Scope profile_scope{VideoCommon::Nx2Profile::Zone::TextureUpload};
image.flags &= ~ImageFlagBits::CpuModified;
TrackImage(image, image_id);
+ if (True(image.flags & ImageFlagBits::Unbacked)) {
+ // Nothing to upload into (and staging it could take tens of GB).
+ runtime.TransitionImageLayout(image);
+ return;
+ }
+
if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) {
LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented");
runtime.TransitionImageLayout(image);
@@ -1156,6 +1314,24 @@ void TextureCache<P>::UploadImageContents(Image& image, StagingBuffer& staging)
const std::span<u8> mapped_span = staging.mapped_span;
const GPUVAddr gpu_addr = image.gpu_addr;
+ if (True(image.flags & ImageFlagBits::AcceleratedUpload) && image.host_level_offset != 0) {
+ // Only levels >= host_level_offset exist on the host: read each layer's tail of the mip
+ // chain into a compact buffer (layer stride = that tail's size) and decode those levels.
+ auto uploads = FullUploadSwizzles(image.info);
+ const size_t level_offset = uploads[image.host_level_offset].buffer_offset;
+ const size_t tail = image.info.layer_stride - level_offset;
+ for (s32 layer = 0; layer < image.info.resources.layers; ++layer) {
+ gpu_memory->ReadBlock(gpu_addr + layer * image.info.layer_stride + level_offset,
+ mapped_span.data() + layer * tail, tail,
+ VideoCommon::CacheType::NoTextureCache);
+ }
+ uploads.erase(uploads.begin(), uploads.begin() + image.host_level_offset);
+ for (auto& upload : uploads) {
+ upload.buffer_offset -= level_offset;
+ }
+ runtime.AccelerateImageUpload(image, staging, uploads, 0, 0);
+ return;
+ }
if (True(image.flags & ImageFlagBits::AcceleratedUpload)) {
gpu_memory->ReadBlock(gpu_addr, mapped_span.data(), mapped_span.size_bytes(),
VideoCommon::CacheType::NoTextureCache);
@@ -1178,7 +1354,18 @@ void TextureCache<P>::UploadImageContents(Image& image, StagingBuffer& staging)
}
template <class P>
-ImageViewId TextureCache<P>::CreateImageView(const TICEntry& config) {
+ImageViewId TextureCache<P>::CreateImageView(const TICEntry& raw_config) {
+ const bool native = gpu_memory->UsesNativeTiling();
+ if (native && raw_config.BaseLayer() && std::getenv("EDEN_NATIVE_DRAW_DIAGNOSTICS")) {
+ static u32 texture_view_traces{};
+ if (texture_view_traces++ < 8) {
+ LOG_WARNING(HW_GPU, "Native texture view header: address={:#x}, dimensions={}x{}x{}, type={}, legacy_layer={}, raw={:016x}/{:016x}/{:016x}/{:016x}",
+ raw_config.Address(), raw_config.Width(), raw_config.Height(), raw_config.Depth(),
+ static_cast<u32>(raw_config.texture_type.Value()), raw_config.BaseLayer(),
+ raw_config.raw[0], raw_config.raw[1], raw_config.raw[2], raw_config.raw[3]);
+ }
+ }
+ const TICEntry config = TextureViewHeader(raw_config, native);
const ImageInfo info(config);
if (info.type == ImageType::Buffer) {
const ImageViewInfo view_info(config, 0);
@@ -1213,12 +1400,9 @@ ImageId TextureCache<P>::FindOrInsertImage(const ImageInfo& info, GPUVAddr gpu_a
template <class P>
ImageId TextureCache<P>::FindImage(const ImageInfo& info, GPUVAddr gpu_addr,
RelaxedOptions options) {
- std::optional<DAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
+ const std::optional<DAddr> cpu_addr = ImageCpuAddress(gpu_addr, CalculateGuestSizeInBytes(info));
if (!cpu_addr) {
- cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr, CalculateGuestSizeInBytes(info));
- if (!cpu_addr) {
- return ImageId{};
- }
+ return ImageId{};
}
const bool broken_views =
runtime.HasBrokenTextureViewFormats() || True(options & RelaxedOptions::ForceBrokenViews);
@@ -1344,9 +1528,10 @@ u64 TextureCache<P>::GetScaledImageSizeBytes(const ImageBase& image) {
template <class P>
void TextureCache<P>::QueueAsyncDecode(Image& image, ImageId image_id) {
UNIMPLEMENTED_IF(False(image.flags & ImageFlagBits::Converted));
- LOG_INFO(HW_GPU, "Queuing async texture decode");
+ LOG_DEBUG(HW_GPU, "Queuing async texture decode {:#x}", image.gpu_addr);
image.flags |= ImageFlagBits::IsDecoding;
+ ++async_decodes_queued;
auto decode = std::make_unique<AsyncDecodeContext>();
auto* decode_ptr = decode.get();
decode->image_id = image_id;
@@ -1356,6 +1541,18 @@ void TextureCache<P>::QueueAsyncDecode(Image& image, ImageId image_id) {
Tegra::Memory::GpuGuestMemory<u8, Tegra::Memory::GuestMemoryFlags::UnsafeRead> swizzle_data(*gpu_memory, image.gpu_addr, image.guest_size_bytes, &swizzle_data_buffer);
auto copies = UnswizzleImage(*gpu_memory, image.gpu_addr, image.info, swizzle_data, local_unswizzle_data_buffer);
const size_t out_size = MapSizeBytes(image);
+ if (False(image.flags & ImageFlagBits::ZeroFilled)) {
+ // Until the decode lands (a second or more while title 1 streams ~40 textures a second)
+ // a new image showed whatever its memory last held, often other textures. Zero blocks
+ // read as opaque black (BC1) or transparent (BC3) instead.
+ image.flags |= ImageFlagBits::ZeroFilled;
+ auto zero_copies = copies;
+ ConvertedLayout(image.info, zero_copies);
+ auto staging = runtime.UploadStagingBuffer(out_size);
+ std::memset(staging.mapped_span.data(), 0, out_size);
+ image.UploadMemory(staging, zero_copies);
+ runtime.InsertUploadMemoryBarrier();
+ }
auto func = [out_size, copies, info = image.info,
input = std::move(local_unswizzle_data_buffer),
@@ -1392,12 +1589,26 @@ void TextureCache<P>::TickAsyncDecode() {
auto i = async_decodes.begin();
while (i != async_decodes.end()) {
auto* async_decode = i->get();
- std::unique_lock lock{async_decode->mutex};
- if (!async_decode->complete) {
- ++i;
+ {
+ // Release the lock before the context is erased below: unlocking it afterwards wrote
+ // into freed memory (heap corruption, crashes minutes into title 1).
+ std::unique_lock lock{async_decode->mutex};
+ if (!async_decode->complete) {
+ ++i;
+ continue;
+ }
+ }
+ if (async_decode->cancelled) {
+ i = async_decodes.erase(i);
continue;
}
Image& image = slot_images[async_decode->image_id];
+ if (async_decode->decoded_data.size() > MapSizeBytes(image)) {
+ LOG_ERROR(HW_GPU, "Async decode of {} bytes for a {}-byte image at {:#x}, dropped",
+ async_decode->decoded_data.size(), MapSizeBytes(image), image.gpu_addr);
+ i = async_decodes.erase(i);
+ continue;
+ }
auto staging = runtime.UploadStagingBuffer(MapSizeBytes(image));
std::memcpy(staging.mapped_span.data(), async_decode->decoded_data.data(),
async_decode->decoded_data.size());
@@ -1513,20 +1724,42 @@ bool TextureCache<P>::ScaleDown(Image& image) {
}
template <class P>
+std::optional<DAddr> TextureCache<P>::ImageCpuAddress(GPUVAddr gpu_addr, size_t size) const {
+ if (const std::optional<DAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr)) {
+ return cpu_addr;
+ }
+ if (const std::optional<DAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr, size)) {
+ return cpu_addr;
+ }
+ // No CPU backing (title 1 renders a bloom chain at GPU 0xf0000-0x15b600): a stand-in address
+ // fixed by the GPU address, at or above ~(1 << 40) so TrackImage skips it. A fresh address per
+ // lookup made every use create another image (thousands a minute, never found again).
+ constexpr GPUVAddr StandInRange = 1ULL << 40;
+ if (gpu_addr >= StandInRange || size > StandInRange - gpu_addr - 1) {
+ return std::nullopt;
+ }
+ return ~(1ULL << 40ULL) + gpu_addr;
+}
+
+template <class P>
ImageId TextureCache<P>::InsertImage(const ImageInfo& info, GPUVAddr gpu_addr,
RelaxedOptions options) {
- std::optional<DAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
+ std::optional<DAddr> cpu_addr = ImageCpuAddress(gpu_addr, CalculateGuestSizeInBytes(info));
if (!cpu_addr) {
- const auto size = CalculateGuestSizeInBytes(info);
- cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr, size);
- if (!cpu_addr) {
- const DAddr fake_addr = ~(1ULL << 40ULL) + virtual_invalid_space;
- virtual_invalid_space += Common::AlignUp(size, 32);
- cpu_addr = std::optional<DAddr>(fake_addr);
- }
+ const DAddr fake_addr = ~(1ULL << 40ULL) + virtual_invalid_space;
+ virtual_invalid_space += Common::AlignUp(CalculateGuestSizeInBytes(info), 32);
+ cpu_addr = std::optional<DAddr>(fake_addr);
}
ASSERT_MSG(cpu_addr, "Tried to insert an image to an invalid gpu_addr={:#x}", gpu_addr);
- const ImageId image_id = JoinImages(info, gpu_addr, *cpu_addr);
+ ImageInfo storage_info = info;
+ storage_info.native_tiling = gpu_memory->UsesNativeTiling();
+ if (storage_info.native_tiling && info.type == ImageType::e3D) {
+ // Switch 2 3D images (lighting/fog volumes) are rare: log each one's shape.
+ LOG_INFO(HW_GPU, "3D image at {:#x}: {}x{}x{} levels {} format {} block {}x{}x{}",
+ gpu_addr, info.size.width, info.size.height, info.size.depth, info.resources.levels,
+ static_cast<u32>(info.format), info.block.width, info.block.height, info.block.depth);
+ }
+ const ImageId image_id = JoinImages(storage_info, gpu_addr, *cpu_addr);
const Image& image = slot_images[image_id];
// Using "image.gpu_addr" instead of "gpu_addr" is important because it might be different
const auto [it, is_new] = image_allocs_table.try_emplace(image.gpu_addr);
@@ -1634,10 +1867,36 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, DA
}
}
+ if constexpr (requires { runtime.UseOpaqueAstcFormat(); }) {
+ // Opaque ASTC goes to BC1 rather than BC3 when it is re-compressed (the Vulkan runtime
+ // decides). The guest data is already there when an image is first used.
+ constexpr size_t MaxScanBytes = 64_MiB;
+ if (IsPixelFormatASTC(new_info.format) && runtime.UseOpaqueAstcFormat()) {
+ const size_t scan_bytes = CalculateGuestSizeInBytes(new_info);
+ if (scan_bytes <= MaxScanBytes) {
+ astc_scan_buffer.resize(scan_bytes);
+ gpu_memory->ReadBlockUnsafe(gpu_addr, astc_scan_buffer.data(), scan_bytes);
+ new_info.astc_opaque = AstcDataLooksOpaque(astc_scan_buffer);
+ ++(new_info.astc_opaque ? astc_opaque_images : astc_alpha_images);
+ }
+ }
+ }
const ImageId new_image_id = slot_images.insert(runtime, new_info, gpu_addr, cpu_addr);
Image& new_image = slot_images[new_image_id];
new_image.allocation_tick = frame_tick;
+ {
+ // Which shapes are being created (periodic log): format, type, levels, layers, size.
+ const u64 key = (u64{static_cast<u32>(new_info.format)} << 56) |
+ (u64{static_cast<u32>(new_info.type)} << 52) |
+ (u64{static_cast<u32>(new_info.resources.levels) & 0xF} << 48) |
+ (u64{static_cast<u32>(new_info.resources.layers) & 0xFFF} << 36) |
+ (u64{new_info.size.width & 0x3FFFF} << 18) | (new_info.size.height & 0x3FFFF);
+ auto& shape = created_shapes[key];
+ ++shape.first;
+ shape.second = gpu_addr;
+ ++images_created;
+ }
if (!gpu_memory->IsContinuousRange(new_image.gpu_addr, new_image.guest_size_bytes) &&
new_info.is_sparse) {
@@ -1833,12 +2092,9 @@ std::optional<typename TextureCache<P>::BlitImages> TextureCache<P>::GetBlitImag
template <class P>
ImageId TextureCache<P>::FindDMAImage(const ImageInfo& info, GPUVAddr gpu_addr) {
- std::optional<DAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
+ const std::optional<DAddr> cpu_addr = ImageCpuAddress(gpu_addr, CalculateGuestSizeInBytes(info));
if (!cpu_addr) {
- cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr, CalculateGuestSizeInBytes(info));
- if (!cpu_addr) {
- return ImageId{};
- }
+ return ImageId{};
}
ImageId image_id{};
boost::container::small_vector<ImageId, 8> image_ids;
@@ -1859,6 +2115,13 @@ ImageId TextureCache<P>::FindDMAImage(const ImageInfo& info, GPUVAddr gpu_addr)
return true;
}
} else if (IsSubCopy(info, existing_image, gpu_addr)) {
+ if (True(existing_image.flags & ImageFlagBits::Unbacked)) {
+ static u32 logged{};
+ if (logged++ < 64) {
+ LOG_INFO(HW_GPU, "DMA copy at {:#x} ({}x{}) into unbacked image {:#x}", gpu_addr,
+ info.size.width, info.size.height, existing_image.gpu_addr);
+ }
+ }
image_id = existing_image_id;
image_ids.push_back(existing_image_id);
return true;
@@ -1954,6 +2217,13 @@ ImageViewId TextureCache<P>::FindRenderTargetView(const ImageInfo& info, GPUVAdd
base = image.TryFindBase(gpu_addr).value();
}
const s32 layers = image.info.type == ImageType::e3D ? info.size.depth : info.resources.layers;
+ if (True(image.flags & ImageFlagBits::Unbacked)) {
+ static u32 logged{};
+ if (logged++ < 64) {
+ LOG_INFO(HW_GPU, "Render target in unbacked image {:#x} at {:#x}: {}x{} level {} layer {}",
+ image.gpu_addr, gpu_addr, info.size.width, info.size.height, base.level, base.layer);
+ }
+ }
const SubresourceRange range{
.base = base,
.extent = {.levels = 1, .layers = layers},
@@ -2136,6 +2406,13 @@ ImageViewId TextureCache<P>::FindOrEmplaceImageView(ImageId image_id, const Imag
if (const ImageViewId image_view_id = image.FindView(info); image_view_id) {
return image_view_id;
}
+ if (True(image.flags & ImageFlagBits::Unbacked)) {
+ // How the game uses an image too large to back (e.g. a sparse virtual texture).
+ LOG_INFO(HW_GPU, "View of unbacked image {:#x}: type {} format {} levels {}+{} layers {}+{}",
+ image.gpu_addr, static_cast<u32>(info.type), static_cast<u32>(info.format),
+ info.range.base.level, info.range.extent.levels, info.range.base.layer,
+ info.range.extent.layers);
+ }
const ImageViewId image_view_id = slot_image_views.insert(runtime, info, image_id, image, slot_images);
image.InsertView(info, image_view_id);
return image_view_id;
@@ -2316,6 +2593,16 @@ void TextureCache<P>::UntrackImage(ImageBase& image, ImageId image_id) {
template <class P>
void TextureCache<P>::DeleteImage(ImageId image_id, bool immediate_delete) {
ImageBase& image = slot_images[image_id];
+ ++images_deleted;
+ // Drop every decode still queued for it, whatever IsDecoding says: an image re-decoded while
+ // a decode was in flight has two, and the first to finish clears the flag. A decode left
+ // behind was uploaded into the next image to take this slot (another texture's data: garbage
+ // textures, and a staging overrun when that image was smaller).
+ for (auto& async_decode : async_decodes) {
+ if (async_decode->image_id == image_id) {
+ async_decode->cancelled = true;
+ }
+ }
if (image.HasScaled()) {
total_used_memory -= GetScaledImageSizeBytes(image);
}
@@ -2385,7 +2672,11 @@ void TextureCache<P>::DeleteImage(ImageId image_id, bool immediate_delete) {
}
slot_image_views.erase(image_view_id);
}
- if (!immediate_delete) {
+ const bool retained = RetainPendingImageDownloads(image_id, slot_images[image_id],
+ uncommitted_downloads, committed_downloads);
+ if (retained) {
+ LOG_INFO(Render_Vulkan, "Retained image for queued download: id={}", image_id.Value());
+ } else if (!immediate_delete) {
sentenced_images.Push(std::move(slot_images[image_id]));
}
slot_images.erase(image_id);
@@ -2537,6 +2828,11 @@ template <class P>
void TextureCache<P>::CopyImage(ImageId dst_id, ImageId src_id, std::vector<ImageCopy> copies) {
Image& dst = slot_images[dst_id];
Image& src = slot_images[src_id];
+ if (dst.info.astc_opaque != src.info.astc_opaque) {
+ // BC1 and BC3 blocks differ in size, so the host can't copy between them; both hold the
+ // same guest data, which is uploaded from guest memory instead.
+ return;
+ }
const bool is_rescaled = True(src.flags & ImageFlagBits::Rescaled);
if (is_rescaled) {
ASSERT(True(dst.flags & ImageFlagBits::Rescaled));
@@ -2628,7 +2924,8 @@ void TextureCache<P>::BindRenderTarget(ImageViewId* old_id, ImageViewId new_id)
}
if (new_id) {
const ImageViewBase& old_view = slot_image_views[new_id];
- if (True(old_view.flags & ImageViewFlagBits::PreemtiveDownload)) {
+ if (True(old_view.flags & ImageViewFlagBits::PreemtiveDownload) &&
+ False(slot_images[old_view.image_id].flags & (ImageFlagBits::Unbacked | ImageFlagBits::TrimmedLevels))) {
const PendingDownload new_download{true, 0, old_view.image_id};
uncommitted_downloads.emplace_back(new_download);
}
diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h
index 8a2318cc72..11ac8a2421 100644
--- a/src/video_core/texture_cache/texture_cache_base.h
+++ b/src/video_core/texture_cache/texture_cache_base.h
@@ -9,6 +9,7 @@
#include <atomic>
#include <deque>
#include <limits>
+#include <map>
#include <mutex>
#include <span>
#include <type_traits>
@@ -22,12 +23,14 @@
#include <queue>
#include "common/common_types.h"
+#include "common/spin_recursive_mutex.h"
#include "common/hash.h"
#include "common/literals.h"
#include "common/lru_cache.h"
#include <ranges>
#include "common/scratch_buffer.h"
#include "common/slot_vector.h"
+#include "video_core/texture_cache/pending_download.h"
#include "common/thread_worker.h"
#include "video_core/compatible_formats.h"
#include "video_core/control/channel_state_cache.h"
@@ -67,6 +70,7 @@ struct AsyncDecodeContext {
boost::container::small_vector<BufferImageCopy, 16> copies;
std::mutex mutex;
std::atomic_bool complete;
+ bool cancelled{}; ///< The image was deleted meanwhile: drop the result (GPU thread only).
};
using TextureCacheGPUMap = ::Common::unordered_map<u64, std::vector<ImageId>, Common::IdentityHash<u64>>;
@@ -118,6 +122,21 @@ class TextureCache : public VideoCommon::ChannelSetupCaches<TextureCacheChannelI
static constexpr s64 TARGET_THRESHOLD = 4_GiB;
#endif
+ // Periodic statistics (TickFrame).
+ u64 gc_deletes{};
+ u64 gc_costly_deletes{};
+ u64 async_decodes_queued{};
+ u64 images_created{};
+ bool native_thresholds_applied{};
+ u64 native_critical_memory{};
+ u64 native_expected_memory{};
+ u64 budget_check_frames{};
+ u64 astc_opaque_images{};
+ u64 astc_alpha_images{};
+ std::vector<u8> astc_scan_buffer;
+ u64 images_deleted{};
+ std::map<u64, std::pair<u32, GPUVAddr>> created_shapes;
+
static constexpr s64 DEFAULT_EXPECTED_MEMORY = 1_GiB + 125_MiB;
static constexpr s64 DEFAULT_CRITICAL_MEMORY = 1_GiB + 625_MiB;
static constexpr size_t GC_EMERGENCY_COUNTS = 2;
@@ -226,6 +245,9 @@ public:
/// Return true when there are uncommitted images to be downloaded
[[nodiscard]] bool HasUncommittedFlushes() const noexcept;
+ /// Whether CommitAsyncFlushes would commit anything (downloads or their buffers).
+ [[nodiscard]] bool HasAsyncFlushWork() const noexcept;
+
/// Return true when the caller should wait for async downloads
[[nodiscard]] bool ShouldWaitAsyncFlushes() const noexcept;
@@ -258,7 +280,7 @@ public:
/// Prepare an image to be used
void PrepareImage(ImageId image_id, bool is_modification, bool invalidate);
- std::recursive_mutex mutex;
+ Common::SpinRecursiveMutex mutex;
private:
/// Iterate over all page indices in a range
@@ -448,6 +470,12 @@ private:
DAddr virtual_invalid_space{};
+ /// Applies EDEN_NX2_TC_EXPECTED_MIB / EDEN_NX2_TC_CRITICAL_MIB and logs the thresholds.
+ void ApplyThresholdOverrides(bool log = true);
+
+ /// The CPU address an image at gpu_addr is keyed by: its backing, or a fixed stand-in.
+ [[nodiscard]] std::optional<DAddr> ImageCpuAddress(GPUVAddr gpu_addr, size_t size) const;
+
bool has_deleted_images = false;
bool is_rescaling = false;
u64 total_used_memory = 0;
@@ -463,11 +491,7 @@ private:
size_t size;
};
- struct PendingDownload {
- bool is_swizzle;
- size_t async_buffer_id;
- Common::SlotId object_id;
- };
+ using PendingDownload = PendingImageDownload<Image>;
Common::SlotVector<Image> slot_images;
Common::SlotVector<ImageMapView> slot_map_views;
diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp
index e55d0752ec..ffc1d59ec3 100644
--- a/src/video_core/texture_cache/util.cpp
+++ b/src/video_core/texture_cache/util.cpp
@@ -11,6 +11,7 @@
#include <algorithm>
#include <array>
+#include <cstring>
#include <numeric>
#include <optional>
#include <span>
@@ -439,7 +440,7 @@ template <u32 GOB_EXTENT>
}
layers = 1;
} else {
- layers = (std::max)(resources.layers, info.resources.layers + base->layer);
+ layers = (std::max)(info.resources.layers, resources.layers + base->layer);
}
return OverlapResult{
.gpu_addr = overlap.gpu_addr,
@@ -566,7 +567,7 @@ void SwizzleBlockLinearImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr
dst(gpu_memory, gpu_addr + guest_offset, subresource_size, &tmp_buffer);
SwizzleTexture(dst, src, bytes_per_block, num_tiles.width, num_tiles.height,
- num_tiles.depth, block.height, block.depth);
+ num_tiles.depth, block.height, block.depth, 1, gpu_memory.UsesNativeTiling());
}
host_offset += host_bytes_per_layer;
@@ -603,6 +604,10 @@ u32 CalculateUnswizzledSizeBytes(const ImageInfo& info) noexcept {
return NumBlocksPerLayer(info, tile_size) * info.resources.layers * BytesPerBlock(info.format);
}
+bool AstcDataLooksOpaque(std::span<const u8> data) noexcept {
+ return Tegra::Texture::ASTC::IsOpaque(data);
+}
+
u32 CalculateConvertedSizeBytes(const ImageInfo& info) noexcept {
if (info.type == ImageType::Buffer) {
return info.size.width * BytesPerBlock(info.format);
@@ -611,8 +616,10 @@ u32 CalculateConvertedSizeBytes(const ImageInfo& info) noexcept {
if (IsPixelFormatASTC(info.format) && Settings::values.astc_recompression.GetValue() !=
Settings::AstcRecompression::Uncompressed) {
const u32 bpp_div =
- Settings::values.astc_recompression.GetValue() == Settings::AstcRecompression::Bc1 ? 2
- : 1;
+ Settings::values.astc_recompression.GetValue() == Settings::AstcRecompression::Bc1 ||
+ info.astc_opaque
+ ? 2
+ : 1;
// NumBlocksPerLayer doesn't account for this correctly, so we have to do it manually.
u32 output_size = 0;
for (s32 i = 0; i < info.resources.levels; i++) {
@@ -913,7 +920,7 @@ boost::container::small_vector<BufferImageCopy, 16> UnswizzleImage(Tegra::Memory
const std::span<u8> dst = output.subspan(host_offset);
const std::span<const u8> src = input.subspan(guest_offset + guest_layer_offset);
UnswizzleTexture(dst, src, 1U << bpp_log2, num_tiles.width, num_tiles.height,
- num_tiles.depth, block.height, block.depth, stride_alignment);
+ num_tiles.depth, block.height, block.depth, stride_alignment, gpu_memory.UsesNativeTiling());
guest_layer_offset += layer_stride;
host_offset += host_bytes_per_layer;
}
@@ -922,6 +929,35 @@ boost::container::small_vector<BufferImageCopy, 16> UnswizzleImage(Tegra::Memory
return copies;
}
+void ConvertedLayout(const ImageInfo& info, std::span<BufferImageCopy> copies) {
+ // Same offsets and sizes as ConvertImage below.
+ const auto recompression = Settings::values.astc_recompression.GetValue();
+ const bool astc = IsPixelFormatASTC(info.format);
+ u64 output_offset = 0;
+ for (BufferImageCopy& copy : copies) {
+ const Extent3D mip_size = AdjustMipSize(info.size, copy.image_subresource.base_level);
+ const u64 layers = static_cast<u64>(copy.image_subresource.num_layers) * copy.image_extent.depth;
+ u64 size;
+ if (astc && recompression == Settings::AstcRecompression::Uncompressed) {
+ size = u64{copy.image_extent.width} * copy.image_extent.height * layers *
+ BytesPerBlock(PixelFormat::A8B8G8R8_UNORM);
+ } else if (astc) {
+ const u64 bpp_div =
+ recompression == Settings::AstcRecompression::Bc1 || info.astc_opaque ? 2 : 1;
+ size = u64{Common::AlignUp(copy.image_extent.width, 4U)} *
+ Common::AlignUp(copy.image_extent.height, 4U) * layers / bpp_div;
+ } else {
+ size = u64{copy.image_extent.width} * copy.image_extent.height * layers *
+ ConvertedBytesPerBlock(info.format);
+ }
+ copy.buffer_offset = output_offset;
+ copy.buffer_size = size;
+ copy.buffer_row_length = mip_size.width;
+ copy.buffer_image_height = mip_size.height;
+ output_offset += size;
+ }
+}
+
void ConvertImage(std::span<const u8> input, const ImageInfo& info, std::span<u8> output,
std::span<BufferImageCopy> copies) {
u32 output_offset = 0;
@@ -955,10 +991,11 @@ void ConvertImage(std::span<const u8> input, const ImageInfo& info, std::span<u8
} else if (astc) {
// BC1 uses 0.5 bytes per texel
// BC3 uses 1 byte per texel
- const auto compress = recompression_setting == Settings::AstcRecompression::Bc1
- ? Tegra::Texture::BCN::CompressBC1
- : Tegra::Texture::BCN::CompressBC3;
- const auto bpp_div = recompression_setting == Settings::AstcRecompression::Bc1 ? 2 : 1;
+ const bool bc1 =
+ recompression_setting == Settings::AstcRecompression::Bc1 || info.astc_opaque;
+ const auto compress =
+ bc1 ? Tegra::Texture::BCN::CompressBC1 : Tegra::Texture::BCN::CompressBC3;
+ const auto bpp_div = bc1 ? 2 : 1;
const u32 plane_dim = copy.image_extent.width * copy.image_extent.height;
const u32 level_size = plane_dim * copy.image_extent.depth *
@@ -1298,6 +1335,13 @@ void DeduceBlitImages(ImageInfo& dst_info, ImageInfo& src_info, const ImageBase*
}
u32 MapSizeBytes(const ImageBase& image) {
+ if (True(image.flags & ImageFlagBits::AcceleratedUpload) && image.host_level_offset != 0) {
+ // Each layer's mip chain from host_level_offset on (see UploadImageContents).
+ const auto uploads = FullUploadSwizzles(image.info);
+ const u32 tail = image.info.layer_stride -
+ static_cast<u32>(uploads[image.host_level_offset].buffer_offset);
+ return tail * image.info.resources.layers;
+ }
if (True(image.flags & ImageFlagBits::AcceleratedUpload)) {
return image.guest_size_bytes;
} else if (True(image.flags & ImageFlagBits::Converted)) {
diff --git a/src/video_core/texture_cache/util.h b/src/video_core/texture_cache/util.h
index 590d85b042..0381d2d24b 100644
--- a/src/video_core/texture_cache/util.h
+++ b/src/video_core/texture_cache/util.h
@@ -36,6 +36,9 @@ struct OverlapResult {
[[nodiscard]] u32 CalculateConvertedSizeBytes(const ImageInfo& info) noexcept;
+/// True if no 16-byte ASTC block in `data` decodes to alpha below 1.
+[[nodiscard]] bool AstcDataLooksOpaque(std::span<const u8> data) noexcept;
+
[[nodiscard]] u32 CalculateLayerStride(const ImageInfo& info) noexcept;
[[nodiscard]] u32 CalculateLayerSize(const ImageInfo& info) noexcept;
@@ -69,6 +72,9 @@ struct OverlapResult {
Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr, const ImageInfo& info,
std::span<const u8> input, std::span<u8> output);
+/// Rewrites `copies` (from UnswizzleImage) to the layout ConvertImage produces, without converting.
+void ConvertedLayout(const ImageInfo& info, std::span<BufferImageCopy> copies);
+
void ConvertImage(std::span<const u8> input, const ImageInfo& info, std::span<u8> output,
std::span<BufferImageCopy> copies);