aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/renderer_vulkan
diff options
context:
space:
mode:
authorJonas <104419015+jojo-chaechae@users.noreply.github.com>2026-11-03 04:18:21 +0600
committerGitHub <noreply@github.com>2026-11-03 04:18:21 +0600
commit155bf34fc297b6b8e4c281ecf7ea5d9ce3140762 (patch)
treeef69b034a8bd3bfc710c182c52e34e0acd4b3735 /src/video_core/renderer_vulkan
parent05259375e0fb436c78882f8d9b95f9e2ce69af13 (diff)
parent9f4f01f76e8556941c0c0c3f57edc1d600ed23ea (diff)
Merge pull request #337 from Sssnowyyy/nx2-review-fixes
nx2: fix pokopia regressions, textures that handle shader computes, and more
Diffstat (limited to 'src/video_core/renderer_vulkan')
-rw-r--r--src/video_core/renderer_vulkan/fixed_pipeline_state.cpp2
-rw-r--r--src/video_core/renderer_vulkan/fixed_pipeline_state.h1
-rw-r--r--src/video_core/renderer_vulkan/vertex_repack.h35
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp17
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.h2
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pass.cpp5
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pass.h7
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pipeline.cpp25
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp22
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp24
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp45
11 files changed, 121 insertions, 64 deletions
diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
index d1f78c0034..76a564aae9 100644
--- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
+++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp
@@ -269,7 +269,7 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe
if (xfb_enabled) {
RefreshXfbState(xfb_state, regs);
}
- if (!dynamic_vertex_input && ExperimentalStaticVertexRepackEnabled()) {
+ if (!dynamic_vertex_input && features.has_static_vertex_repack) {
const u32 repack_mask = VertexRepackMask(regs);
// Refresh from guest values even when only the stride changed: offsets must not accumulate.
for (size_t index = 0; index < attributes.size(); ++index) {
diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.h b/src/video_core/renderer_vulkan/fixed_pipeline_state.h
index 36a4b4b7a2..1fe5cd75f3 100644
--- a/src/video_core/renderer_vulkan/fixed_pipeline_state.h
+++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.h
@@ -33,6 +33,7 @@ struct DynamicFeatures {
bool has_dynamic_state3_logic_op_enable;
bool has_dynamic_state3_line_stipple_enable;
bool has_dynamic_vertex_input;
+ bool has_static_vertex_repack;
bool has_color_write_enable;
bool has_provoking_vertex;
bool has_provoking_vertex_first_mode;
diff --git a/src/video_core/renderer_vulkan/vertex_repack.h b/src/video_core/renderer_vulkan/vertex_repack.h
index b0849c25c7..493eb41bd1 100644
--- a/src/video_core/renderer_vulkan/vertex_repack.h
+++ b/src/video_core/renderer_vulkan/vertex_repack.h
@@ -2,6 +2,7 @@
#pragma once
+#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <limits>
@@ -14,12 +15,21 @@ constexpr bool VertexRepackInputSupported(bool dynamic_input, bool experimental_
return dynamic_input || experimental_static;
}
-inline bool ExperimentalStaticVertexRepackEnabled() {
- static const bool enabled = [] {
- const char* value = std::getenv("EGLANTINE_EXPERIMENTAL_STATIC_VERTEX_REPACK");
- return value && std::strcmp(value, "1") == 0;
- }();
- return enabled;
+// Pipelines without dynamic vertex input repack when the device cannot fetch the 69-byte stride.
+// EGLANTINE_EXPERIMENTAL_STATIC_VERTEX_REPACK=1 forces the repack on, =0 forces it off.
+inline bool StaticVertexRepackSelected(const char* override_value, u32 stride_alignment) {
+ if (override_value && std::strcmp(override_value, "1") == 0) {
+ return true;
+ }
+ if (override_value && std::strcmp(override_value, "0") == 0) {
+ return false;
+ }
+ return stride_alignment > 1 && 69 % stride_alignment != 0;
+}
+
+inline bool StaticVertexRepackEnabled(u32 stride_alignment) {
+ return StaticVertexRepackSelected(std::getenv("EGLANTINE_EXPERIMENTAL_STATIC_VERTEX_REPACK"),
+ stride_alignment);
}
struct VertexRepackParameters {
@@ -31,8 +41,11 @@ struct VertexRepackParameters {
u32 dispatch_groups;
};
-inline std::optional<VertexRepackParameters> MakeVertexRepackParameters(
- u32 offset, u32 size, u64 alignment, u64 max_storage_range, u32 max_dispatch_groups) {
+inline std::optional<VertexRepackParameters> MakeVertexRepackParameters(u32 offset, u32 size,
+ u64 alignment,
+ u64 max_storage_range,
+ u32 max_dispatch_groups,
+ u64 buffer_size) {
if (size == 0 || alignment == 0) {
return std::nullopt;
}
@@ -40,9 +53,11 @@ inline std::optional<VertexRepackParameters> MakeVertexRepackParameters(
const u64 output_size = vertices * 72;
const u64 base = u64{offset} / alignment * alignment;
const u64 bias = offset - base;
- const u64 range = (bias + size + 3) / 4 * 4;
+ // The word-rounded range must not run past the end of the source buffer
+ const u64 available = buffer_size > base ? buffer_size - base : 0;
+ const u64 range = (std::min)((bias + size + 3) / 4 * 4, available);
const u64 groups = (vertices * 18 + 127) / 128;
- if (output_size > std::numeric_limits<u32>::max() ||
+ if (range < bias + size || output_size > std::numeric_limits<u32>::max() ||
range > std::numeric_limits<u32>::max() || output_size > max_storage_range ||
range > max_storage_range || groups > max_dispatch_groups) {
return std::nullopt;
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index 2449265509..43cb7d9414 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -365,6 +365,10 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m
LOG_INFO(Render_Vulkan, "69-byte vertex layout repacking {} ({})",
enable_vertex_repack ? "enabled" : "disabled",
"portable Vulkan layout fallback");
+ static_vertex_repack =
+ StaticVertexRepackEnabled(device.GetMinVertexInputBindingStrideAlignment());
+ LOG_INFO(Render_Vulkan, "Vertex repacking without dynamic vertex input {}",
+ static_vertex_repack ? "enabled" : "disabled");
if (enable_vertex_repack) {
vertex_repack_pass = std::make_unique<VertexRepackPass>(
device, scheduler, descriptor_pool, staging_pool, compute_pass_descriptor_queue);
@@ -387,14 +391,13 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m
u32 BufferCacheRuntime::SetVertexRepackLayout(const Tegra::Engines::Maxwell3D::Regs& regs,
bool dynamic_input) {
const u32 old_mask = vertex_repack_mask;
- const bool input_supported = VertexRepackInputSupported(
- dynamic_input, ExperimentalStaticVertexRepackEnabled());
+ const bool input_supported = VertexRepackInputSupported(dynamic_input, static_vertex_repack);
vertex_repack_mask = vertex_repack_pass && input_supported ? VertexRepackMask(regs) : 0;
if (vertex_repack_mask != old_mask) {
LOG_DEBUG(Render_Vulkan, "Vertex repack selection: mask={:#x}, dynamic_input={}",
vertex_repack_mask, dynamic_input);
}
- return old_mask | vertex_repack_mask;
+ return old_mask ^ vertex_repack_mask;
}
StagingBufferRef BufferCacheRuntime::UploadStagingBuffer(size_t size) {
@@ -657,7 +660,8 @@ void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset
return;
}
if (buffer != VK_NULL_HANDLE && size != 0 && IsVertexRepacked(index)) {
- const auto repacked = vertex_repack_pass->Assemble(buffer, offset, size);
+ const auto repacked =
+ vertex_repack_pass->Assemble(buffer, offset, size, u64{offset} + size);
if (!repacked) {
return;
}
@@ -702,7 +706,8 @@ void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bi
if (handle != VK_NULL_HANDLE && IsVertexRepacked(binding) && bindings.strides[i] == 69 &&
bindings.sizes[i] > 0) {
const auto repacked = vertex_repack_pass->Assemble(
- handle, static_cast<u32>(bindings.offsets[i]), static_cast<u32>(bindings.sizes[i]));
+ handle, static_cast<u32>(bindings.offsets[i]), static_cast<u32>(bindings.sizes[i]),
+ bindings.buffers[i]->SizeBytes());
if (!repacked) {
continue;
}
@@ -712,7 +717,7 @@ void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bi
bindings.strides[i] = 72;
static bool reported = false;
if (!reported) {
- LOG_WARNING(Render_Vulkan, "Experimental GPU vertex repacking active: 69 -> 72 bytes");
+ LOG_INFO(Render_Vulkan, "GPU vertex repacking active: 69 -> 72 bytes");
reported = true;
}
}
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h
index d3295ec6ed..f65b6daf2c 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.h
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h
@@ -110,6 +110,7 @@ public:
void TickFrame(Common::SlotVector<Buffer>& slot_buffers) noexcept;
+ /// Returns the mask of bindings whose repacked state changed.
u32 SetVertexRepackLayout(const Tegra::Engines::Maxwell3D::Regs& regs, bool dynamic_input);
bool IsVertexRepacked(u32 binding) const {
@@ -270,6 +271,7 @@ private:
std::unique_ptr<Uint8Pass> uint8_pass;
std::unique_ptr<VertexRepackPass> vertex_repack_pass;
u32 vertex_repack_mask{};
+ bool static_vertex_repack{};
QuadIndexedPass quad_index_pass;
MultiRangeBufferCache multi_range_buffers;
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
index cb8dcde42f..5902b7d8f5 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
@@ -335,10 +335,11 @@ VertexRepackPass::VertexRepackPass(const Device& device_, Scheduler& scheduler_,
scheduler{scheduler_}, staging_pool{staging_pool_}, descriptor_queue{descriptor_queue_} {}
std::optional<std::pair<VkBuffer, VkDeviceSize>> VertexRepackPass::Assemble(VkBuffer source,
- u32 offset, u32 size) {
+ u32 offset, u32 size,
+ u64 buffer_size) {
const auto parameters = MakeVertexRepackParameters(
offset, size, device.GetStorageBufferAlignment(), device.GetMaxStorageBufferRange(),
- device.GetMaxComputeWorkGroupCount()[0]);
+ device.GetMaxComputeWorkGroupCount()[0], buffer_size);
if (!parameters) {
LOG_ERROR(Render_Vulkan, "Vertex repack exceeds device limits: offset={}, size={}", offset, size);
return std::nullopt;
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.h b/src/video_core/renderer_vulkan/vk_compute_pass.h
index 27fffbbbb7..9687bfff91 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.h
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.h
@@ -60,9 +60,10 @@ class VertexRepackPass final : public ComputePass {
public:
VertexRepackPass(const Device& device, Scheduler& scheduler, DescriptorPool& descriptor_pool,
StagingBufferPool& staging_pool, ComputePassDescriptorQueue& descriptor_queue);
- /// Returns nothing when the range exceeds the device limits; the binding is then left as is.
- std::optional<std::pair<VkBuffer, VkDeviceSize>> Assemble(VkBuffer source, u32 offset,
- u32 size);
+ /// Returns nothing when the range exceeds the device limits or the source buffer; the binding
+ /// is then left as is.
+ std::optional<std::pair<VkBuffer, VkDeviceSize>> Assemble(VkBuffer source, u32 offset, u32 size,
+ u64 buffer_size);
private:
Scheduler& scheduler;
diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
index 1cfe391ba6..3a3861930c 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
@@ -176,19 +176,18 @@ bool ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
const auto& cbufs{qmd.const_buffer_config};
const bool via_header_index{qmd.linked_tsc != 0};
const auto read_handle{[&](const auto& desc, u32 index) {
- if constexpr (std::is_same_v<decltype(desc), const Shader::TextureDescriptor&>) {
- if (desc.handle_program != ~0U) {
- // Read by the shader from a storage buffer: computed for this dispatch.
- return TexturePair(VideoCommon::EvaluateTextureHandle(
- info, desc.handle_program, gpu_memory,
- [&](u32 cbuf) -> GPUVAddr {
- return cbuf < cbufs.size() &&
- ((qmd.const_buffer_enable_mask >> cbuf) & 1) != 0
- ? cbufs[cbuf].Address()
- : GPUVAddr{};
- }),
- via_header_index);
- }
+ if (desc.handle_program != ~0U) {
+ // Read by the shader from a storage buffer: computed for this dispatch.
+ return TexturePair(VideoCommon::EvaluateTextureHandle(
+ info, desc.handle_program, gpu_memory,
+ [&](u32 cbuf) -> GPUVAddr {
+ return cbuf < cbufs.size() &&
+ ((qmd.const_buffer_enable_mask >> cbuf) &
+ 1) != 0
+ ? cbufs[cbuf].Address()
+ : GPUVAddr{};
+ }),
+ via_header_index);
}
ASSERT(((qmd.const_buffer_enable_mask >> desc.cbuf_index) & 1) != 0);
const u32 index_offset{index << desc.size_shift};
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index fa273f6ef9..4bd2e2735e 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -400,18 +400,16 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
}
const auto& cbufs{maxwell3d->state.shader_stages[stage].const_buffers};
const auto read_handle{[&](const auto& desc, u32 index) {
- if constexpr (std::is_same_v<decltype(desc), const Shader::TextureDescriptor&>) {
- if (desc.handle_program != ~0U) {
- // Read by the shader from a storage buffer: computed for this draw.
- return TexturePair(VideoCommon::EvaluateTextureHandle(
- info, desc.handle_program, *gpu_memory,
- [&](u32 cbuf) -> GPUVAddr {
- return cbuf < cbufs.size() && cbufs[cbuf].enabled
- ? cbufs[cbuf].address
- : GPUVAddr{};
- }),
- via_header_index);
- }
+ if (desc.handle_program != ~0U) {
+ // Read by the shader from a storage buffer: computed for this draw.
+ return TexturePair(VideoCommon::EvaluateTextureHandle(
+ info, desc.handle_program, *gpu_memory,
+ [&](u32 cbuf) -> GPUVAddr {
+ return cbuf < cbufs.size() && cbufs[cbuf].enabled
+ ? cbufs[cbuf].address
+ : GPUVAddr{};
+ }),
+ via_header_index);
}
ASSERT(cbufs[desc.cbuf_index].enabled);
const u32 index_offset{index << desc.size_shift};
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index ff8e84b019..a3cd93484e 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -34,6 +34,7 @@
#include "video_core/renderer_vulkan/maxwell_to_vk.h"
#include "video_core/renderer_vulkan/pipeline_helper.h"
#include "video_core/renderer_vulkan/pipeline_statistics.h"
+#include "video_core/renderer_vulkan/vertex_repack.h"
#include "video_core/renderer_vulkan/vk_compute_pipeline.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
@@ -583,6 +584,8 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
dynamic_features.has_dynamic_vertex_input =
device.IsExtVertexInputDynamicStateSupported() &&
Settings::values.vertex_input_dynamic_state.GetValue();
+ dynamic_features.has_static_vertex_repack =
+ StaticVertexRepackEnabled(device.GetMinVertexInputBindingStrideAlignment());
dynamic_features.has_provoking_vertex = device.IsExtProvokingVertexSupported();
dynamic_features.has_provoking_vertex_first_mode =
@@ -600,9 +603,17 @@ constexpr u32 UsedPipelinesMagic = 0x31505545; // "EUP1"
// Pipelines the last two sessions used are built at boot. Each holds ~0.45 MB of driver memory:
// title 1 built 4,800 from eight sessions, 1.5 GB more than building them on first use (but that
// stalled frames 4x as often), while one session of its title screen uses ~1,500.
+// MoltenVK keeps the previous eight sessions: a pipeline built while playing goes through
+// SPIR-V -> MSL -> Metal, far more than the 2-3 ms the shorter window assumes, so each one
+// built at boot is a stutter avoided.
constexpr u8 UsedPipelinesMaxAge = 2;
+constexpr u8 UsedPipelinesMaxAgeMoltenVK = 8;
-std::unordered_map<u64, u8> LoadUsedPipelines(const std::filesystem::path& path) {
+u8 MaxUsedPipelineAge(const Device& device) {
+ return device.IsMoltenVK() ? UsedPipelinesMaxAgeMoltenVK : UsedPipelinesMaxAge;
+}
+
+std::unordered_map<u64, u8> LoadUsedPipelines(const std::filesystem::path& path, u8 max_age) {
std::unordered_map<u64, u8> used;
std::ifstream file{path, std::ios::binary};
u32 magic{}, count{};
@@ -618,7 +629,7 @@ std::unordered_map<u64, u8> LoadUsedPipelines(const std::filesystem::path& path)
!file.read(reinterpret_cast<char*>(&age), sizeof(age))) {
break;
}
- if (age < UsedPipelinesMaxAge) { // lists saved with a longer window
+ if (age < max_age) { // lists saved with a longer window
used.emplace(hash, age);
}
}
@@ -669,7 +680,7 @@ void PipelineCache::SaveUsedPipelines() {
next.emplace(hash, u8{0});
}
for (const auto& [hash, age] : used_pipelines) {
- if (age + 1 < UsedPipelinesMaxAge) {
+ if (age + 1 < MaxUsedPipelineAge(device)) {
next.try_emplace(hash, static_cast<u8>(age + 1));
}
}
@@ -744,7 +755,7 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
}
pipeline_cache_filename = base_dir / "vulkan.bin";
used_pipelines_filename = base_dir / "vulkan_used.bin";
- used_pipelines = LoadUsedPipelines(used_pipelines_filename);
+ used_pipelines = LoadUsedPipelines(used_pipelines_filename, MaxUsedPipelineAge(device));
used_pipelines_saved = std::chrono::steady_clock::now();
size_t skipped_pipelines = 0;
size_t queued_graphics = 0;
@@ -1004,6 +1015,11 @@ void PipelineCache::TickFrame() {
}
void PipelineCache::EvictUnusedPipelines() {
+ // Building a pipeline again on MoltenVK means a Metal compile mid-game, far dearer than the
+ // 2-3 ms below, so none are evicted there.
+ if (device.IsMoltenVK()) {
+ return;
+ }
// Each built pipeline holds ~0.45 MB of driver memory and none was ever destroyed, so it grew
// with every place a session went (title 1: 1,500 on its title screen alone). One unused for
// this long is destroyed; it takes 2-3 ms to build again from the driver's pipeline cache.
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index 30981fe413..928146027d 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -2350,23 +2350,31 @@ void TextureCacheRuntime::TickFrame() {
});
}
+namespace {
+
+// The host image of a texture; unbacked images are created without memory (see MakeImage).
+[[nodiscard]] vk::Image MakeHostImage(TextureCacheRuntime& runtime, const ImageInfo& info,
+ u32 host_level_offset, GPUVAddr gpu_addr, bool backed) {
+ const Device& device = runtime.device;
+ const bool sparse_tiles = !backed && UsesSparseTiles(device, info, gpu_addr);
+ return MakeImage(
+ device, runtime.memory_allocator, TrimmedHostInfo(device, info, host_level_offset),
+ IsHugeAstcArray(device, info) ? std::span<const VkFormat>{HugeAstcViewFormats}
+ : IsOpaqueBc1Astc(device, info) ? std::span<const VkFormat>{OpaqueAstcViewFormats}
+ : runtime.ViewFormats(info.format),
+ AstcHostFormat(device, info),
+ !backed && (UsesSparseLayers(device, info, gpu_addr) || sparse_tiles ||
+ IsUnwrittenSparse(device, info, gpu_addr)),
+ sparse_tiles && SparseTilesModeOf(device) == SparseTilesMode::Aliased);
+}
+
+} // Anonymous namespace
+
Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu_addr_,
VAddr cpu_addr_)
: VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime_.scheduler},
runtime{&runtime_},
- original_image(MakeImage(runtime_.device, runtime_.memory_allocator,
- TrimmedHostInfo(runtime_.device, info, host_level_offset),
- IsHugeAstcArray(runtime_.device, info)
- ? std::span<const VkFormat>{HugeAstcViewFormats}
- : IsOpaqueBc1Astc(runtime_.device, info)
- ? std::span<const VkFormat>{OpaqueAstcViewFormats}
- : runtime->ViewFormats(info.format),
- AstcHostFormat(runtime_.device, info),
- UsesSparseLayers(runtime_.device, info, gpu_addr_) ||
- UsesSparseTiles(runtime_.device, info, gpu_addr_) ||
- IsUnwrittenSparse(runtime_.device, info, gpu_addr_),
- UsesSparseTiles(runtime_.device, info, gpu_addr_) &&
- SparseTilesModeOf(runtime_.device) == SparseTilesMode::Aliased)),
+ original_image(MakeHostImage(runtime_, info, host_level_offset, gpu_addr_, false)),
aspect_mask(ImageAspectMask(info.format)) {
if (original_image.IsUnbacked()) {
flags |= VideoCommon::ImageFlagBits::Unbacked;
@@ -2377,6 +2385,17 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
flags |= VideoCommon::ImageFlagBits::SparseLayers;
} else if (UsesSparseTiles(runtime_.device, info, gpu_addr_) && InitSparseTiles()) {
flags |= VideoCommon::ImageFlagBits::SparseTiles;
+ } else if (UsesSparseTiles(runtime_.device, info, gpu_addr_) &&
+ SparseTilesModeOf(runtime_.device) == SparseTilesMode::Aliased) {
+ // The driver can't sample unbound memory (that is why the blocks were to share one),
+ // so an image whose tiles could not be set up is backed whole like any other.
+ LOG_WARNING(Render_Vulkan, "Sparse tiled image {:#x}: backing it whole", gpu_addr_);
+ original_image = MakeHostImage(runtime_, info, host_level_offset, gpu_addr_, true);
+ if (!original_image.IsUnbacked()) {
+ flags &= ~VideoCommon::ImageFlagBits::Unbacked;
+ } else if (texels <= (u64{1} << 28)) {
+ flags |= VideoCommon::ImageFlagBits::UnwrittenSparse;
+ }
} else if (texels <= (u64{1} << 28)) {
// Not too big to back (see MemoryAllocator::CreateImage): unbacked because nothing
// was written there yet, so the first write gets it replaced.