Fix vertex buffer ranges crossing memory mappings

This commit is contained in:
nmzik committed 2026-07-29 21:05:24 +02:00
1 parent 832bc84100
commit cc76827e63
4 files changed
+159 -47

No files matched your search

+96 -47
View File
@@ -18,15 +18,16 @@
#include "graphics/host_gpu/renderer/depthRenderTarget.h" #include "graphics/host_gpu/renderer/depthRenderTarget.h"
#include "graphics/host_gpu/renderer/pipeline/descriptorCache.h" #include "graphics/host_gpu/renderer/pipeline/descriptorCache.h"
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h" #include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h" #include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
#include "graphics/host_gpu/renderer/pipeline/shaderSubgroup.h" #include "graphics/host_gpu/renderer/pipeline/shaderSubgroup.h"
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vulkanCommon.h" #include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/recompiler/ir/ResourceMaterialization.h" #include "graphics/shader/recompiler/ir/ResourceMaterialization.h"
#include "graphics/shader/recompiler/ir/ShaderIR.h" #include "graphics/shader/recompiler/ir/ShaderIR.h"
#include "graphics/shader/shader.h" #include "graphics/shader/shader.h"
#include "kernel/eventQueue.h" #include "kernel/eventQueue.h"
#include "kernel/memory.h"
#include "kernel/pthread.h" #include "kernel/pthread.h"
#include "libs/errno.h" #include "libs/errno.h"
@@ -221,8 +222,7 @@ static void LogDrawTargetState(const char* draw_name, const RenderColorInfo& col
LogMrtState(draw_name, buffer, ps_input_info); LogMrtState(draw_name, buffer, ps_input_info);
} }
static void LogDrawInputState(const RenderCommandBuffer& buffer, static void LogDrawInputState(const RenderCommandBuffer& buffer, const RenderColorInfo& color,
const RenderColorInfo& color,
const ShaderVertexInputInfo& vs_input_info, const ShaderVertexInputInfo& vs_input_info,
uint32_t index_type_and_size, uint32_t index_count, uint32_t index_type_and_size, uint32_t index_count,
const void* index_addr) { const void* index_addr) {
@@ -512,8 +512,7 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
auto& target = colors[i]; auto& target = colors[i];
EXIT_IF(!target.image_id); EXIT_IF(!target.image_id);
const auto old_image = cache.ResolveOwner(target.image_id); const auto old_image = cache.ResolveOwner(target.image_id);
if (old_image == nullptr || if (old_image == nullptr || (!old_image->registered && !old_image->info.data.Empty()) ||
(!old_image->registered && !old_image->info.data.Empty()) ||
old_image->binding.needs_rebind) { old_image->binding.needs_rebind) {
if (old_image != nullptr) { if (old_image != nullptr) {
old_image->binding = {}; old_image->binding = {};
@@ -531,8 +530,7 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
attachment_samples, target.samples); attachment_samples, target.samples);
} }
const auto& view = target.desc.view_info; const auto& view = target.desc.view_info;
const auto layout = const auto layout = image.binding.is_bound ? vk::ImageLayout::eGeneral
image.binding.is_bound ? vk::ImageLayout::eGeneral
: vk::ImageLayout::eColorAttachmentOptimal; : vk::ImageLayout::eColorAttachmentOptimal;
image.Transit(layout, image.Transit(layout,
vk::AccessFlagBits2::eColorAttachmentRead | vk::AccessFlagBits2::eColorAttachmentRead |
@@ -561,8 +559,7 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
depth.depth_meta_clear_enable = depth.depth_meta_clear_enable =
depth.htile && depth.htile &&
cache.IsMetaCleared(depth.htile_buffer_vaddr, depth.desc.view_info.base_layer); cache.IsMetaCleared(depth.htile_buffer_vaddr, depth.desc.view_info.base_layer);
depth.depth_load_clear_enable = depth.depth_load_clear_enable = depth.depth_clear_enable || depth.depth_meta_clear_enable;
depth.depth_clear_enable || depth.depth_meta_clear_enable;
if (depth.depth_meta_clear_enable && if (depth.depth_meta_clear_enable &&
!cache.TouchMeta(depth.htile_buffer_vaddr, depth.desc.view_info.base_layer, false)) { !cache.TouchMeta(depth.htile_buffer_vaddr, depth.desc.view_info.base_layer, false)) {
EXIT("failed to consume HTile clear state\n"); EXIT("failed to consume HTile clear state\n");
@@ -572,8 +569,8 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
if (attachment_samples == 0) { if (attachment_samples == 0) {
attachment_samples = depth.samples; attachment_samples = depth.samples;
} else if (attachment_samples != depth.samples) { } else if (attachment_samples != depth.samples) {
EXIT("mixed color/depth sample counts are unsupported: %u and %u\n", EXIT("mixed color/depth sample counts are unsupported: %u and %u\n", attachment_samples,
attachment_samples, depth.samples); depth.samples);
} }
const auto layout = depth_attachment_layout(depth); const auto layout = depth_attachment_layout(depth);
const auto writes = depth.AttachmentWriteAspects(); const auto writes = depth.AttachmentWriteAspects();
@@ -595,11 +592,9 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
attachment.image_layout = layout; attachment.image_layout = layout;
attachment.clear_value[0] = std::bit_cast<uint32_t>(depth.depth_clear_value); attachment.clear_value[0] = std::bit_cast<uint32_t>(depth.depth_clear_value);
attachment.clear_value[1] = depth.stencil_clear_value; attachment.clear_value[1] = depth.stencil_clear_value;
attachment.has_depth = attachment.has_depth = static_cast<bool>(aspects & vk::ImageAspectFlagBits::eDepth);
static_cast<bool>(aspects & vk::ImageAspectFlagBits::eDepth);
attachment.depth_clear = depth.depth_load_clear_enable; attachment.depth_clear = depth.depth_load_clear_enable;
attachment.has_stencil = attachment.has_stencil = static_cast<bool>(aspects & vk::ImageAspectFlagBits::eStencil);
static_cast<bool>(aspects & vk::ImageAspectFlagBits::eStencil);
attachment.stencil_clear = depth.stencil_clear_enable; attachment.stencil_clear = depth.stencil_clear_enable;
} }
if (attachment_samples == 0 || if (attachment_samples == 0 ||
@@ -685,6 +680,85 @@ static uint64_t VertexBufferDescriptorSize(const ShaderVertexInputBuffer& buffer
: buffer.num_records); : buffer.num_records);
} }
struct VertexBufferRange {
uint64_t base_address = 0;
uint64_t requested_end = 0;
uint64_t acquired_end = 0;
BufferBinding binding;
[[nodiscard]] uint64_t RequestedSize() const { return requested_end - base_address; }
};
static std::vector<BufferBinding> AcquireVertexBuffers(RenderCommandBuffer& buffer,
const ShaderVertexInputInfo& vs_input_info) {
// Collect the non-empty guest vertex ranges.
std::vector<VertexBufferRange> ranges;
ranges.reserve(vs_input_info.buffers_num);
for (int i = 0; i < vs_input_info.buffers_num; i++) {
const auto& vertex = vs_input_info.buffers[i];
const auto size = VertexBufferDescriptorSize(vertex);
if (size == 0) {
continue;
}
if (vertex.addr == 0 || size > UINT64_MAX - vertex.addr) {
EXIT("invalid vertex buffer range: addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vertex.addr, size);
}
ranges.push_back({vertex.addr, vertex.addr + size});
}
std::ranges::sort(ranges, [](const VertexBufferRange& left, const VertexBufferRange& right) {
return left.base_address < right.base_address;
});
// Merge overlapping or touching ranges before acquiring host buffers.
std::vector<VertexBufferRange> merged_ranges;
merged_ranges.reserve(ranges.size());
for (const auto& range: ranges) {
if (!merged_ranges.empty() && merged_ranges.back().requested_end >= range.base_address) {
merged_ranges.back().requested_end =
std::max(merged_ranges.back().requested_end, range.requested_end);
continue;
}
merged_ranges.push_back(range);
}
auto& cache = buffer.GetContext().GetBufferCache();
for (auto& range: merged_ranges) {
// PPSA20298
const auto size =
Libs::LibKernel::Memory::ClampRangeSize(range.base_address, range.RequestedSize());
range.acquired_end = range.base_address + size;
range.binding = cache.ObtainBuffer(buffer, range.base_address, size);
}
// Rebuild slot bindings, offsetting non-empty slots into their acquired merged range.
std::vector<BufferBinding> bindings;
bindings.reserve(vs_input_info.buffers_num);
for (int i = 0; i < vs_input_info.buffers_num; i++) {
const auto& vertex = vs_input_info.buffers[i];
const auto size = VertexBufferDescriptorSize(vertex);
if (size == 0) {
auto owner = cache.ObtainNullBuffer();
bindings.push_back({owner, owner->Handle(), 0});
continue;
}
const auto range = std::ranges::find_if(merged_ranges, [&](const VertexBufferRange& value) {
return vertex.addr >= value.base_address && vertex.addr < value.acquired_end;
});
if (range == merged_ranges.end()) {
EXIT("vertex buffer address is outside the acquired range: addr=0x%016" PRIx64 "\n",
vertex.addr);
}
auto binding = range->binding;
binding.offset += vertex.addr - range->base_address;
bindings.push_back(std::move(binding));
}
return bindings;
}
static void SetDrawDebugPhase(RenderCommandBuffer& buffer, uint64_t submit_id, static void SetDrawDebugPhase(RenderCommandBuffer& buffer, uint64_t submit_id,
const DrawCallInfo& draw, uint32_t phase) { const DrawCallInfo& draw, uint32_t phase) {
EXIT_IF(draw.name == nullptr); EXIT_IF(draw.name == nullptr);
@@ -823,37 +897,13 @@ static std::vector<BufferBinding> PrepareVertexBuffers(uint64_t
(void)submit_id; (void)submit_id;
LogDrawPhase(draw.name, "PrepareVertexBuffers"); LogDrawPhase(draw.name, "PrepareVertexBuffers");
std::vector<BufferBinding> bindings; return AcquireVertexBuffers(buffer, vs_input_info);
bindings.reserve(vs_input_info.buffers_num);
for (int i = 0; i < vs_input_info.buffers_num; i++) {
const auto& b = vs_input_info.buffers[i];
const auto size = VertexBufferDescriptorSize(b);
if (size == 0) {
auto owner = buffer.GetContext().GetBufferCache().ObtainNullBuffer();
bindings.push_back({owner, owner->Handle(), 0});
} else {
bindings.push_back(
buffer.GetContext().GetBufferCache().ObtainBuffer(buffer, b.addr, size));
}
}
return bindings;
} }
static void RebindVertexBuffers(RenderCommandBuffer& buffer, static void RebindVertexBuffers(RenderCommandBuffer& buffer,
const ShaderVertexInputInfo& vs_input_info, const ShaderVertexInputInfo& vs_input_info,
std::vector<BufferBinding>& bindings) { std::vector<BufferBinding>& bindings) {
EXIT_IF(bindings.size() != static_cast<size_t>(vs_input_info.buffers_num)); bindings = AcquireVertexBuffers(buffer, vs_input_info);
for (int i = 0; i < vs_input_info.buffers_num; i++) {
const auto& vertex = vs_input_info.buffers[i];
const auto size = VertexBufferDescriptorSize(vertex);
if (size == 0) {
auto owner = buffer.GetContext().GetBufferCache().ObtainNullBuffer();
bindings[i] = {owner, owner->Handle(), 0};
} else {
bindings[i] =
buffer.GetContext().GetBufferCache().ObtainBuffer(buffer, vertex.addr, size);
}
}
} }
static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffer, static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffer,
@@ -1228,8 +1278,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer,
} }
// NOLINTNEXTLINE(readability-function-cognitive-complexity) // NOLINTNEXTLINE(readability-function-cognitive-complexity)
void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, uint32_t index_count,
uint32_t index_count,
uint32_t flags, uint32_t render_target_slice_offset, uint32_t flags, uint32_t render_target_slice_offset,
uint32_t instance_count, uint32_t first_vertex, uint32_t instance_count, uint32_t first_vertex,
uint32_t first_instance) { uint32_t first_instance) {
@@ -1290,7 +1339,8 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer,
instance_count, first_instance}; instance_count, first_instance};
DrawRenderState state {}; DrawRenderState state {};
if (!PrepareDrawRenderState(submit_id, buffer, draw, render_target_slice_offset, false, state)) { if (!PrepareDrawRenderState(submit_id, buffer, draw, render_target_slice_offset, false,
state)) {
ResetBindings(); ResetBindings();
return; return;
} }
@@ -1369,8 +1419,7 @@ bool RenderExecutor::ResolveColorTargets(uint64_t submit_id, RenderCommandBuffer
cache.MarkGpuWritten(dst.image_id); cache.MarkGpuWritten(dst.image_id);
auto& source = cache.GetImage(src.image_id); auto& source = cache.GetImage(src.image_id);
auto& destination = cache.GetImage(dst.image_id); auto& destination = cache.GetImage(dst.image_id);
destination.Resolve(source, destination.Resolve(source, {src.base_mip_level, 1, src.base_array_layer, 1},
{src.base_mip_level, 1, src.base_array_layer, 1},
{dst.base_mip_level, 1, dst.base_array_layer, 1}); {dst.base_mip_level, 1, dst.base_array_layer, 1});
return true; return true;
} }
+53
View File
@@ -528,6 +528,42 @@ public:
return false; return false;
} }
uint64_t ClampRangeSize(uint64_t virtual_addr, uint64_t size) {
Common::LockGuard lock(m_mutex);
if (virtual_addr == 0 || size == 0 || size > UINT64_MAX - virtual_addr) {
return 0;
}
auto vma = std::upper_bound(
m_ranges.begin(), m_ranges.end(), virtual_addr,
[](uint64_t value, const Range& range) { return value < range.start; });
if (vma == m_ranges.begin()) {
return 0;
}
--vma;
const auto vma_end = End(vma->start, vma->size);
if (virtual_addr < vma->start || virtual_addr >= vma_end ||
!IsCommittedRangeType(vma->type)) {
return 0;
}
uint64_t clamped_size = std::min(size, vma_end - virtual_addr);
uint64_t expected = virtual_addr + clamped_size;
++vma;
while (vma != m_ranges.end() && vma->start == expected && IsCommittedRangeType(vma->type) &&
clamped_size < size) {
const auto chunk = std::min(size - clamped_size, vma->size);
clamped_size += chunk;
expected += chunk;
++vma;
}
return clamped_size;
}
uint64_t CountPageTableEntries(bool gpu) { uint64_t CountPageTableEntries(bool gpu) {
Common::LockGuard lock(m_mutex); Common::LockGuard lock(m_mutex);
@@ -884,6 +920,23 @@ bool TryReadBacking(uint64_t vaddr, void* data, uint64_t size) {
g_direct_memory_backing->TryReadBacking(vaddr, data, size); g_direct_memory_backing->TryReadBacking(vaddr, data, size);
} }
uint64_t ClampRangeSize(uint64_t vaddr, uint64_t size) {
EXIT_IF(g_virtual_ranges == nullptr);
const auto clamped_size = g_virtual_ranges->ClampRangeSize(vaddr, size);
if (clamped_size == 0) {
EXIT("Memory: attempted to access invalid address 0x%016" PRIx64 " with size 0x%016" PRIx64
"\n",
vaddr, size);
}
if (clamped_size != size) {
LOGF("Memory: clamped buffer range addr=0x%016" PRIx64 " size=0x%016" PRIx64
" to 0x%016" PRIx64 "\n",
vaddr, size, clamped_size);
}
return clamped_size;
}
void WriteBacking(uint64_t vaddr, const void* data, uint64_t size) noexcept { void WriteBacking(uint64_t vaddr, const void* data, uint64_t size) noexcept {
if (!TryWriteBacking(vaddr, data, size)) { if (!TryWriteBacking(vaddr, data, size)) {
EXIT("Memory: required direct-backing write failed, addr=0x%016" PRIx64 EXIT("Memory: required direct-backing write failed, addr=0x%016" PRIx64
+1
View File
@@ -103,6 +103,7 @@ void RegisterCallbacks(callback_func_t alloc_func, callback_func_t
void SetFlexibleMemorySize(uint64_t size); void SetFlexibleMemorySize(uint64_t size);
bool TryWriteBacking(uint64_t vaddr, const void* data, uint64_t size); bool TryWriteBacking(uint64_t vaddr, const void* data, uint64_t size);
bool TryReadBacking(uint64_t vaddr, void* data, uint64_t size); bool TryReadBacking(uint64_t vaddr, void* data, uint64_t size);
[[nodiscard]] uint64_t ClampRangeSize(uint64_t vaddr, uint64_t size);
void WriteBacking(uint64_t vaddr, const void* data, uint64_t size) noexcept; void WriteBacking(uint64_t vaddr, const void* data, uint64_t size) noexcept;
void InvalidateMemory(uint64_t vaddr, uint64_t size); void InvalidateMemory(uint64_t vaddr, uint64_t size);
void InstallGpuResources(Graphics::GpuResourceManager* resources) noexcept; void InstallGpuResources(Graphics::GpuResourceManager* resources) noexcept;
+9
View File
@@ -405,6 +405,10 @@ void TestDirectMapQueryOffsetAndPartialMunmap() {
"TryReadBacking should reject a range crossing an unmapped span"); "TryReadBacking should reject a range crossing an unmapped span");
Check(test, rejected_read == transaction_sentinel, Check(test, rejected_read == transaction_sentinel,
"failed backing reads must not modify a destination prefix"); "failed backing reads must not modify a destination prefix");
Check(test,
Libs::LibKernel::Memory::ClampRangeSize(base + SceKernelPageSize - 0xf30, 0x1560) ==
0xf30,
"ClampRangeSize did not stop at an unmapped span");
info = Query(test, base + SceKernelPageSize, SceKernelVqFindNext); info = Query(test, base + SceKernelPageSize, SceKernelVqFindNext);
ExpectRange(test, info, base + SceKernelPageSize * 2, base + SceKernelPageSize * 4, ExpectRange(test, info, base + SceKernelPageSize * 2, base + SceKernelPageSize * 4,
@@ -473,6 +477,11 @@ void TestMunmapAcrossAdjacentFlexibleMappings() {
&right, SceKernelPageSize, SceKernelProtCpuRw, SceKernelMapFixed, "adjacent_right"), &right, SceKernelPageSize, SceKernelProtCpuRw, SceKernelMapFixed, "adjacent_right"),
"KernelMapNamedFlexibleMemory(right)"); "KernelMapNamedFlexibleMemory(right)");
Check(test,
Libs::LibKernel::Memory::ClampRangeSize(base + SceKernelPageSize - 0x100, 0x200) ==
0x200,
"ClampRangeSize did not cross adjacent committed mappings");
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize * 2), CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize * 2),
"KernelMunmap(adjacent mappings)"); "KernelMunmap(adjacent mappings)");
Check(test, AvailableFlexibleMemory(test) == baseline, Check(test, AvailableFlexibleMemory(test) == baseline,