mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 13:56:59 +01:00
graphics: share temporary staging for oversized image uploads
This commit is contained in:
+31
-23
@@ -27,6 +27,26 @@ namespace {
|
|||||||
constexpr uint64_t MiB = 1024 * 1024;
|
constexpr uint64_t MiB = 1024 * 1024;
|
||||||
constexpr uint64_t GdsBufferSize = 64 * 1024;
|
constexpr uint64_t GdsBufferSize = 64 * 1024;
|
||||||
|
|
||||||
|
std::pair<Buffer*, uint64_t> UploadMemory(GraphicContext& graphics, CommandScheduler& scheduler,
|
||||||
|
StreamBuffer& staging, uint64_t size, uint64_t alignment,
|
||||||
|
vk::BufferUsageFlags usage, const auto& write) {
|
||||||
|
const auto [mapped, offset] = staging.Map(size, alignment);
|
||||||
|
if (mapped != nullptr) {
|
||||||
|
write(mapped, offset);
|
||||||
|
staging.Commit();
|
||||||
|
return {&staging, offset};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Large transfers use the same one-time host allocation as ordinary buffer uploads.
|
||||||
|
auto temporary = std::make_unique<Buffer>(graphics, scheduler, MemoryUsage::Upload, 0,
|
||||||
|
usage, size);
|
||||||
|
write(temporary->Mapped().data(), 0);
|
||||||
|
temporary->Flush(0, size);
|
||||||
|
auto* buffer = temporary.get();
|
||||||
|
scheduler.DeferOperation([owner = std::move(temporary)]() mutable { owner.reset(); });
|
||||||
|
return {buffer, 0};
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void BufferCache::WriteDataBuffer(Buffer& buffer, uint64_t address, const void* source,
|
void BufferCache::WriteDataBuffer(Buffer& buffer, uint64_t address, const void* source,
|
||||||
@@ -447,28 +467,15 @@ vk::Buffer BufferCache::UploadCopies(Buffer& buffer, std::span<vk::BufferCopy> c
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto [mapped, base_offset] = m_staging_buffer.Map(total_size, 4);
|
const auto source = UploadMemory(m_graphics, m_scheduler, m_staging_buffer, total_size, 4,
|
||||||
if (mapped != nullptr) {
|
vk::BufferUsageFlagBits::eTransferSrc, [&](uint8_t* mapped, uint64_t base_offset) {
|
||||||
for (auto& copy: copies) {
|
for (auto& copy: copies) {
|
||||||
const auto address = buffer.CpuAddress() + copy.dstOffset;
|
const auto address = buffer.CpuAddress() + copy.dstOffset;
|
||||||
std::memcpy(mapped + copy.srcOffset, reinterpret_cast<const void*>(address), copy.size);
|
std::memcpy(mapped + copy.srcOffset, reinterpret_cast<const void*>(address), copy.size);
|
||||||
copy.srcOffset += base_offset;
|
copy.srcOffset += base_offset;
|
||||||
}
|
}
|
||||||
m_staging_buffer.Commit();
|
});
|
||||||
return m_staging_buffer.Handle();
|
return source.first->Handle();
|
||||||
}
|
|
||||||
|
|
||||||
auto temporary = std::make_unique<Buffer>(m_graphics, m_scheduler, MemoryUsage::Upload, 0,
|
|
||||||
vk::BufferUsageFlagBits::eTransferSrc, total_size);
|
|
||||||
for (const auto& copy: copies) {
|
|
||||||
const auto address = buffer.CpuAddress() + copy.dstOffset;
|
|
||||||
std::memcpy(temporary->Mapped().data() + copy.srcOffset,
|
|
||||||
reinterpret_cast<const void*>(address), copy.size);
|
|
||||||
}
|
|
||||||
temporary->Flush(0, total_size);
|
|
||||||
const auto handle = temporary->Handle();
|
|
||||||
m_scheduler.DeferOperation([owner = std::move(temporary)]() mutable { owner.reset(); });
|
|
||||||
return handle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<Buffer*, uint64_t> BufferCache::ObtainBuffer(uint64_t vaddr, uint64_t size,
|
std::pair<Buffer*, uint64_t> BufferCache::ObtainBuffer(uint64_t vaddr, uint64_t size,
|
||||||
@@ -521,12 +528,13 @@ std::pair<Buffer*, uint64_t> BufferCache::ObtainBufferForImage(uint64_t vaddr, u
|
|||||||
return ObtainBuffer(vaddr, size, false, false);
|
return ObtainBuffer(vaddr, size, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto [staging, stage_offset] = m_staging_buffer.Map(size, 16);
|
return UploadMemory(m_graphics, m_scheduler, m_staging_buffer, size, 16,
|
||||||
if (staging == nullptr || !Libs::LibKernel::Memory::TryReadSparseBacking(vaddr, staging, size)) {
|
vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eStorageBuffer,
|
||||||
EXIT("BufferCache: failed to read mapped guest image backing\n");
|
[&](uint8_t* mapped, uint64_t) {
|
||||||
}
|
if (!Libs::LibKernel::Memory::TryReadSparseBacking(vaddr, mapped, size)) {
|
||||||
m_staging_buffer.Commit();
|
EXIT("BufferCache: failed to read mapped guest image backing\n");
|
||||||
return {&m_staging_buffer, stage_offset};
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void BufferCache::FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds) {
|
void BufferCache::FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds) {
|
||||||
|
|||||||
@@ -5261,6 +5261,85 @@ public:
|
|||||||
std::printf("[gpu] %-32s ok\n", name);
|
std::printf("[gpu] %-32s ok\n", name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CheckLargeSparseImageUpload() {
|
||||||
|
constexpr const char *name = "LargeSparseImageUpload";
|
||||||
|
constexpr uint64_t size = 576ull << 20, ring_size = 512ull << 20, page = 0x10000;
|
||||||
|
constexpr uint64_t base = 0x0000000250000000ull;
|
||||||
|
constexpr std::array<uint64_t, 3> mapped_offsets{0, ring_size, size - page};
|
||||||
|
EnsureRuntimeContext();
|
||||||
|
RenderContext context(m_runtime_context);
|
||||||
|
auto &scheduler = context.GetCommandScheduler();
|
||||||
|
HW::Context registers{};
|
||||||
|
HW::UserConfig user_config{};
|
||||||
|
HW::Shader shaders{};
|
||||||
|
scheduler.Begin(registers, user_config, shaders);
|
||||||
|
context.InitializeGpu(nullptr);
|
||||||
|
void *reservation = reinterpret_cast<void *>(base);
|
||||||
|
Require(name, "sparse reservation", LibKernel::Memory::KernelReserveVirtualRange(
|
||||||
|
&reservation, size, 0x10, page) == 0, "large sparse reservation failed");
|
||||||
|
int64_t direct = -1;
|
||||||
|
Require(name, "physical pages", LibKernel::Memory::KernelAllocateDirectMemory(
|
||||||
|
0, LibKernel::Memory::KernelGetDirectMemorySize(), 3u * page, page, 0, &direct) == 0,
|
||||||
|
"sparse physical allocation failed");
|
||||||
|
for (u32 index = 0; index < mapped_offsets.size(); ++index) {
|
||||||
|
void *mapped = reinterpret_cast<void *>(base + mapped_offsets[index]);
|
||||||
|
Require(name, "map sparse page", LibKernel::Memory::KernelMapDirectMemory(
|
||||||
|
&mapped, page, 0x3, 0x10, direct + index * page, page) == 0,
|
||||||
|
"sparse page mapping failed");
|
||||||
|
auto *words = static_cast<u32 *>(mapped);
|
||||||
|
std::fill_n(words, page / sizeof(u32), 0u);
|
||||||
|
words[0] = 0x00020001u + index * 0x100u;
|
||||||
|
words[1] = 0x00040003u + index * 0x100u;
|
||||||
|
words[page / sizeof(u32) - 1u] = 0xabcdef01u;
|
||||||
|
context.MapMemory(base + mapped_offsets[index], page);
|
||||||
|
}
|
||||||
|
auto &cache = context.GetBufferCache();
|
||||||
|
// Exercise the ordinary ring before requesting the captured 576-MiB footprint.
|
||||||
|
const auto small = cache.ObtainBufferForImage(base, page);
|
||||||
|
auto readback = CreateHostBuffer(name, 8u * sizeof(u32),
|
||||||
|
vk::BufferUsageFlagBits::eTransferDst, {});
|
||||||
|
const vk::BufferCopy small_copy{small.second, 0, sizeof(u32)};
|
||||||
|
scheduler.Current().Handle().copyBuffer(small.first->Handle(), readback.buffer, 1, &small_copy);
|
||||||
|
const auto large = cache.ObtainBufferForImage(base, size);
|
||||||
|
Require(name, "temporary image source", large.first != nullptr && large.first != small.first &&
|
||||||
|
large.first->Size() == size && small.first->Size() == ring_size,
|
||||||
|
"oversized image upload resized the ring or truncated its source");
|
||||||
|
constexpr std::array<uint64_t, 5> probes{0, page, ring_size - 4u, ring_size, size - 4u};
|
||||||
|
std::array<vk::BufferCopy, probes.size()> copies{};
|
||||||
|
for (u32 index = 0; index < probes.size(); ++index)
|
||||||
|
copies[index] = {large.second + probes[index], (index + 1u) * sizeof(u32), sizeof(u32)};
|
||||||
|
scheduler.Current().Handle().copyBuffer(large.first->Handle(), readback.buffer,
|
||||||
|
copies.size(), copies.data());
|
||||||
|
// A compute consumer requires the same storage-buffer capability as the detiler.
|
||||||
|
const auto converted = TextureCacheTestAccess::Tiler(context.GetTextureCache()).TransformColor(
|
||||||
|
{large.first->Handle(), large.second + ring_size, 8u}, ColorTransform::SwapBgra16, true);
|
||||||
|
const vk::BufferCopy converted_copy{converted.offset, 6u * sizeof(u32), 8u};
|
||||||
|
scheduler.Current().Handle().copyBuffer(converted.buffer, readback.buffer, 1, &converted_copy);
|
||||||
|
vk::BufferMemoryBarrier barrier{};
|
||||||
|
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||||
|
barrier.dstAccessMask = vk::AccessFlagBits::eHostRead;
|
||||||
|
barrier.srcQueueFamilyIndex = barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||||
|
barrier.buffer = readback.buffer;
|
||||||
|
barrier.size = readback.size;
|
||||||
|
scheduler.Current().Handle().pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
|
||||||
|
vk::PipelineStageFlagBits::eHost, {}, 0, nullptr, 1, &barrier, 0, nullptr);
|
||||||
|
scheduler.Finish();
|
||||||
|
Require(name, "sparse upload and deferred lifetime",
|
||||||
|
ReadBuffer(name, readback, 8) == std::vector<u32>{0x00020001u, 0x00020001u, 0u, 0u,
|
||||||
|
0x00020101u, 0xabcdef01u, 0x00020103u, 0x00040101u},
|
||||||
|
"upload lost sparse holes, boundary bytes, compute access, or in-flight lifetime");
|
||||||
|
DestroyBuffer(&readback);
|
||||||
|
for (const auto offset : mapped_offsets) context.UnmapMemory(base + offset, page);
|
||||||
|
scheduler.Finish();
|
||||||
|
context.ShutdownGpu();
|
||||||
|
Require(name, "release mappings", LibKernel::Memory::KernelMunmap(base, size) == 0,
|
||||||
|
"sparse mappings release failed");
|
||||||
|
Require(name, "release physical pages",
|
||||||
|
LibKernel::Memory::KernelReleaseDirectMemory(direct, 3u * page) == 0,
|
||||||
|
"sparse physical release failed");
|
||||||
|
std::printf("[gpu] %-32s ok\n", name);
|
||||||
|
}
|
||||||
|
|
||||||
void CheckUnifiedTextureCacheFlow() {
|
void CheckUnifiedTextureCacheFlow() {
|
||||||
constexpr const char *name = "UnifiedTextureCacheFlow";
|
constexpr const char *name = "UnifiedTextureCacheFlow";
|
||||||
constexpr uintptr_t base = 0x0000000200600000ull;
|
constexpr uintptr_t base = 0x0000000200600000ull;
|
||||||
@@ -44889,6 +44968,11 @@ int main(int argc, char **argv) {
|
|||||||
CheckPm4RewindResume(vulkan.RuntimeRenderer());
|
CheckPm4RewindResume(vulkan.RuntimeRenderer());
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
if (argc == 2 && std::strcmp(argv[1], "--image-upload-only") == 0) {
|
||||||
|
VulkanHarness vulkan;
|
||||||
|
vulkan.CheckLargeSparseImageUpload();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (argc == 2 && std::strcmp(argv[1], "--image-overlap-only") == 0) {
|
if (argc == 2 && std::strcmp(argv[1], "--image-overlap-only") == 0) {
|
||||||
CheckDepthAttachmentWrites();
|
CheckDepthAttachmentWrites();
|
||||||
CheckDepthFeedbackAspects();
|
CheckDepthFeedbackAspects();
|
||||||
@@ -45186,6 +45270,7 @@ int main(int argc, char **argv) {
|
|||||||
vulkan.CheckNativeIndirectDispatch();
|
vulkan.CheckNativeIndirectDispatch();
|
||||||
CheckComputeLdsLimit(vulkan);
|
CheckComputeLdsLimit(vulkan);
|
||||||
vulkan.CheckBdaPageTableUploads();
|
vulkan.CheckBdaPageTableUploads();
|
||||||
|
vulkan.CheckLargeSparseImageUpload();
|
||||||
vulkan.CheckUnifiedTextureCacheFlow();
|
vulkan.CheckUnifiedTextureCacheFlow();
|
||||||
vulkan.CheckUnifiedImageViewCache();
|
vulkan.CheckUnifiedImageViewCache();
|
||||||
vulkan.CheckPackedTextureComponents();
|
vulkan.CheckPackedTextureComponents();
|
||||||
|
|||||||
Reference in New Issue
Block a user