mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 22:06:59 +01:00
gpu: skip drains for CPU-only memory unmaps
This commit is contained in:
@@ -1962,6 +1962,11 @@ bool TextureCache::TouchMeta(uint64_t address, uint32_t slice, bool is_clear) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TextureCache::IsRegionRegistered(uint64_t address, uint64_t size) {
|
||||||
|
std::scoped_lock lock {m_lock};
|
||||||
|
return !FindImagesInRegion(address, size, false).empty();
|
||||||
|
}
|
||||||
|
|
||||||
void TextureCache::UnmapMemory(uint64_t address, uint64_t size) {
|
void TextureCache::UnmapMemory(uint64_t address, uint64_t size) {
|
||||||
if (!GuestRange {address, size}.Valid()) {
|
if (!GuestRange {address, size}.Valid()) {
|
||||||
EXIT("TextureCache: invalid unmap range\n");
|
EXIT("TextureCache: invalid unmap range\n");
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ public:
|
|||||||
uint32_t packed_clear);
|
uint32_t packed_clear);
|
||||||
void InvalidateMemory(uint64_t address, uint64_t size);
|
void InvalidateMemory(uint64_t address, uint64_t size);
|
||||||
void InvalidateMemoryFromGPU(uint64_t address, uint64_t size);
|
void InvalidateMemoryFromGPU(uint64_t address, uint64_t size);
|
||||||
|
[[nodiscard]] bool IsRegionRegistered(uint64_t address, uint64_t size);
|
||||||
|
|
||||||
[[nodiscard]] bool IsMeta(uint64_t address);
|
[[nodiscard]] bool IsMeta(uint64_t address);
|
||||||
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice);
|
[[nodiscard]] bool IsMetaCleared(uint64_t address, uint32_t slice);
|
||||||
|
|||||||
@@ -226,43 +226,37 @@ void CommandScheduler::PopPendingOperations() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CommandScheduler::DeferOperation(Common::UniqueFunction<void>&& operation) {
|
void CommandScheduler::DeferOperation(Common::UniqueFunction<void>&& operation) {
|
||||||
|
QueueOperation(std::move(operation), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandScheduler::DeferPriorityOperation(Common::UniqueFunction<void>&& operation) {
|
||||||
|
QueueOperation(std::move(operation), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandScheduler::QueueOperation(Common::UniqueFunction<void>&& operation, bool priority) {
|
||||||
CheckActive();
|
CheckActive();
|
||||||
EXIT_IF(!operation);
|
EXIT_IF(!operation);
|
||||||
std::unique_lock lock(m_operation_mutex);
|
std::unique_lock lock(m_operation_mutex);
|
||||||
if (m_operation_state == OperationState::Open) {
|
if (m_operation_state == OperationState::Open) {
|
||||||
m_pending_operations.push({std::move(operation), CurrentTick()});
|
auto& queue = priority ? m_priority_operations : m_pending_operations;
|
||||||
return;
|
queue.push({std::move(operation), CurrentTick()});
|
||||||
}
|
|
||||||
if (g_deferred_callback_scheduler == this) {
|
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
operation();
|
if (priority) {
|
||||||
|
m_operation_available.notify_one();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (g_deferred_callback_scheduler != this) {
|
||||||
m_operation_available.wait(lock,
|
m_operation_available.wait(lock,
|
||||||
[this] { return m_operation_state == OperationState::Closed; });
|
[this] { return m_operation_state == OperationState::Closed; });
|
||||||
|
}
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
operation();
|
operation();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandScheduler::DeferPriorityOperation(Common::UniqueFunction<void>&& operation) {
|
bool CommandScheduler::HasPendingPriorityOperations() {
|
||||||
CheckActive();
|
std::lock_guard lock(m_operation_mutex);
|
||||||
EXIT_IF(!operation);
|
return !m_priority_operations.empty() || m_priority_active;
|
||||||
std::unique_lock lock(m_operation_mutex);
|
|
||||||
if (m_operation_state == OperationState::Open) {
|
|
||||||
m_priority_operations.push({std::move(operation), CurrentTick()});
|
|
||||||
lock.unlock();
|
|
||||||
m_operation_available.notify_one();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (g_deferred_callback_scheduler == this) {
|
|
||||||
lock.unlock();
|
|
||||||
operation();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_operation_available.wait(lock,
|
|
||||||
[this] { return m_operation_state == OperationState::Closed; });
|
|
||||||
lock.unlock();
|
|
||||||
operation();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandScheduler::PriorityOperationsThread(std::stop_token stop) {
|
void CommandScheduler::PriorityOperationsThread(std::stop_token stop) {
|
||||||
|
|||||||
@@ -38,8 +38,10 @@ public:
|
|||||||
void PopPendingOperations();
|
void PopPendingOperations();
|
||||||
void DrainPriorityOperations();
|
void DrainPriorityOperations();
|
||||||
void WaitPriorityOperations(uint64_t tick);
|
void WaitPriorityOperations(uint64_t tick);
|
||||||
|
// Guest-memory completions use the priority queue; normal callbacks maintain GPU resources.
|
||||||
void DeferOperation(Common::UniqueFunction<void>&& operation);
|
void DeferOperation(Common::UniqueFunction<void>&& operation);
|
||||||
void DeferPriorityOperation(Common::UniqueFunction<void>&& operation);
|
void DeferPriorityOperation(Common::UniqueFunction<void>&& operation);
|
||||||
|
[[nodiscard]] bool HasPendingPriorityOperations();
|
||||||
[[nodiscard]] static bool InDeferredOperation() noexcept;
|
[[nodiscard]] static bool InDeferredOperation() noexcept;
|
||||||
|
|
||||||
[[nodiscard]] bool Active() const noexcept { return m_command.m_registers != nullptr; }
|
[[nodiscard]] bool Active() const noexcept { return m_command.m_registers != nullptr; }
|
||||||
@@ -82,6 +84,7 @@ private:
|
|||||||
|
|
||||||
void BeginNext();
|
void BeginNext();
|
||||||
void PriorityOperationsThread(std::stop_token stop);
|
void PriorityOperationsThread(std::stop_token stop);
|
||||||
|
void QueueOperation(Common::UniqueFunction<void>&& operation, bool priority);
|
||||||
void RunOperation(Common::UniqueFunction<void>&& operation);
|
void RunOperation(Common::UniqueFunction<void>&& operation);
|
||||||
|
|
||||||
MasterSemaphore m_master;
|
MasterSemaphore m_master;
|
||||||
|
|||||||
@@ -99,7 +99,12 @@ void RenderContext::UnmapMemory(uint64_t vaddr, uint64_t size) {
|
|||||||
vaddr, size);
|
vaddr, size);
|
||||||
}
|
}
|
||||||
const auto unmap = [this, vaddr, size] {
|
const auto unmap = [this, vaddr, size] {
|
||||||
if (m_command_scheduler.Active()) {
|
// Check cache ownership on the GPU thread. Guest-memory callbacks can still
|
||||||
|
// access a range with no cached data, so they must finish before it is unmapped.
|
||||||
|
if (m_command_scheduler.Active() &&
|
||||||
|
(m_buffer_cache.IsRegionRegistered(vaddr, size) ||
|
||||||
|
m_texture_cache.IsRegionRegistered(vaddr, size) ||
|
||||||
|
m_command_scheduler.HasPendingPriorityOperations())) {
|
||||||
const auto tick = m_command_scheduler.CurrentTick();
|
const auto tick = m_command_scheduler.CurrentTick();
|
||||||
m_command_scheduler.Finish();
|
m_command_scheduler.Finish();
|
||||||
m_command_scheduler.WaitPriorityOperations(tick);
|
m_command_scheduler.WaitPriorityOperations(tick);
|
||||||
|
|||||||
@@ -2341,6 +2341,7 @@ public:
|
|||||||
|
|
||||||
constexpr uint64_t base = 0x0000000200000000ull;
|
constexpr uint64_t base = 0x0000000200000000ull;
|
||||||
constexpr uint64_t page = 0x4000;
|
constexpr uint64_t page = 0x4000;
|
||||||
|
const auto cpu_only_tick = scheduler.CurrentTick();
|
||||||
resources.MapMemory(base, page * 4);
|
resources.MapMemory(base, page * 4);
|
||||||
resources.MapMemory(base + page * 2, page * 4);
|
resources.MapMemory(base + page * 2, page * 4);
|
||||||
Require("GpuMappedRangeLifecycle", "union",
|
Require("GpuMappedRangeLifecycle", "union",
|
||||||
@@ -2373,8 +2374,9 @@ public:
|
|||||||
resources.MapMemory(new_prt, page * 6);
|
resources.MapMemory(new_prt, page * 6);
|
||||||
Require("GpuMappedRangeLifecycle", "PRT replacement",
|
Require("GpuMappedRangeLifecycle", "PRT replacement",
|
||||||
!resources.IsMapped(old_prt, page * 4) &&
|
!resources.IsMapped(old_prt, page * 4) &&
|
||||||
resources.IsMapped(new_prt, page * 6),
|
resources.IsMapped(new_prt, page * 6) &&
|
||||||
"old-unmap/new-map did not replace full PRT coverage");
|
scheduler.CurrentTick() == cpu_only_tick,
|
||||||
|
"CPU-only unmaps submitted GPU work or lost full PRT coverage");
|
||||||
|
|
||||||
scheduler.Finish();
|
scheduler.Finish();
|
||||||
context.ShutdownGpu();
|
context.ShutdownGpu();
|
||||||
@@ -2818,10 +2820,20 @@ public:
|
|||||||
alignas(uint64_t) uint64_t interrupt_only_gds_label = UINT64_MAX;
|
alignas(uint64_t) uint64_t interrupt_only_gds_label = UINT64_MAX;
|
||||||
alignas(uint64_t) uint64_t cb_db_release_label = UINT64_MAX;
|
alignas(uint64_t) uint64_t cb_db_release_label = UINT64_MAX;
|
||||||
bool parser_kept_nonblocking_boundaries = false;
|
bool parser_kept_nonblocking_boundaries = false;
|
||||||
|
bool host_resource_retired = false;
|
||||||
|
bool cpu_unmap_did_not_submit = false;
|
||||||
std::jthread no_gpu_wait([&] {
|
std::jthread no_gpu_wait([&] {
|
||||||
gpu.SendCommandSync([&] {
|
gpu.SendCommandSync([&] {
|
||||||
processor->BufferInit();
|
processor->BufferInit();
|
||||||
const auto tick_before = gpu_scheduler.CurrentTick();
|
const auto tick_before = gpu_scheduler.CurrentTick();
|
||||||
|
gpu_scheduler.DeferOperation([&] { host_resource_retired = true; });
|
||||||
|
constexpr uint64_t cpu_only_address = 0x0000000200400000ull;
|
||||||
|
context.MapMemory(cpu_only_address, 0x4000);
|
||||||
|
context.UnmapMemory(cpu_only_address, 0x4000);
|
||||||
|
cpu_unmap_did_not_submit =
|
||||||
|
gpu_scheduler.CurrentTick() == tick_before &&
|
||||||
|
!host_resource_retired &&
|
||||||
|
!context.IsMapped(cpu_only_address, 0x4000);
|
||||||
processor->TriggerEvent(0x16u, 0u);
|
processor->TriggerEvent(0x16u, 0u);
|
||||||
auto packet = make_release_mem(0, 0, nullptr, 0);
|
auto packet = make_release_mem(0, 0, nullptr, 0);
|
||||||
Pm4Execution execution;
|
Pm4Execution execution;
|
||||||
@@ -2887,6 +2899,10 @@ public:
|
|||||||
no_gpu_wait.join();
|
no_gpu_wait.join();
|
||||||
gpu.SendCommandSync([&] { gpu_scheduler.Finish(); });
|
gpu.SendCommandSync([&] { gpu_scheduler.Finish(); });
|
||||||
m_runtime_context.device.destroySemaphore(blocked_timeline, nullptr);
|
m_runtime_context.device.destroySemaphore(blocked_timeline, nullptr);
|
||||||
|
Require("GpuCommandLane", "CPU-only unmap with pending host retirement",
|
||||||
|
parser_did_not_wait_gpu && cpu_unmap_did_not_submit &&
|
||||||
|
host_resource_retired,
|
||||||
|
"CPU-only unmap submitted or waited for unrelated native work");
|
||||||
Require("GpuCommandLane", "nonblocking GPU packets",
|
Require("GpuCommandLane", "nonblocking GPU packets",
|
||||||
parser_did_not_wait_gpu && parser_kept_nonblocking_boundaries,
|
parser_did_not_wait_gpu && parser_kept_nonblocking_boundaries,
|
||||||
"a cache event, GL2 writeback, DE counter, or interrupt boundary "
|
"a cache event, GL2 writeback, DE counter, or interrupt boundary "
|
||||||
@@ -3321,13 +3337,14 @@ public:
|
|||||||
"an unrelated unmap waited for a blocked PM4 submission");
|
"an unrelated unmap waited for a blocked PM4 submission");
|
||||||
|
|
||||||
auto &scheduler = context.GetCommandScheduler();
|
auto &scheduler = context.GetCommandScheduler();
|
||||||
std::atomic<bool> normal_completed{false};
|
std::atomic<bool> completion_published{false};
|
||||||
gpu.SendCommandSync(
|
gpu.SendCommandSync([&] {
|
||||||
[&] { scheduler.DeferOperation([&] { normal_completed = true; }); });
|
scheduler.DeferPriorityOperation([&] { completion_published = true; });
|
||||||
|
});
|
||||||
resources.MapMemory(empty_unmap_base, empty_unmap_size);
|
resources.MapMemory(empty_unmap_base, empty_unmap_size);
|
||||||
resources.UnmapMemory(empty_unmap_base, empty_unmap_size);
|
resources.UnmapMemory(empty_unmap_base, empty_unmap_size);
|
||||||
Require("GpuCommandLane", "unmap native completion",
|
Require("GpuCommandLane", "unmap native completion",
|
||||||
normal_completed.load() &&
|
completion_published.load() &&
|
||||||
!resources.IsMapped(empty_unmap_base, empty_unmap_size),
|
!resources.IsMapped(empty_unmap_base, empty_unmap_size),
|
||||||
"unmap returned before an earlier native guest-memory callback");
|
"unmap returned before an earlier native guest-memory callback");
|
||||||
|
|
||||||
@@ -4100,6 +4117,13 @@ public:
|
|||||||
cache.IsRegionRegistered(index_begin - 1, index_span + 2),
|
cache.IsRegionRegistered(index_begin - 1, index_span + 2),
|
||||||
"indexed lookup mishandled a half-open boundary, gap, or broad "
|
"indexed lookup mishandled a half-open boundary, gap, or broad "
|
||||||
"overlap");
|
"overlap");
|
||||||
|
const auto clean_unmap_tick = scheduler.CurrentTick();
|
||||||
|
resources.UnmapMemory(index_begin, index_page);
|
||||||
|
Require(name, "clean cached buffer unmap drain",
|
||||||
|
scheduler.CurrentTick() == clean_unmap_tick + 1 &&
|
||||||
|
!resources.IsMapped(index_begin, index_page),
|
||||||
|
"unmapping a clean cached buffer did not drain native work");
|
||||||
|
resources.MapMemory(index_begin, index_page);
|
||||||
const auto index_bridge =
|
const auto index_bridge =
|
||||||
cache.FindBuffer(index_begin + index_page - 1, index_page + 2);
|
cache.FindBuffer(index_begin + index_page - 1, index_page + 2);
|
||||||
Require(name, "registered-range merge",
|
Require(name, "registered-range merge",
|
||||||
@@ -7138,12 +7162,24 @@ public:
|
|||||||
Prospero::ImageType::kColor2D, {2048, 1, 1}, 1, 4, 1);
|
Prospero::ImageType::kColor2D, {2048, 1, 1}, 1, 4, 1);
|
||||||
const auto partial_unmap_image_id =
|
const auto partial_unmap_image_id =
|
||||||
texture_cache.FindImage(partial_unmap_image);
|
texture_cache.FindImage(partial_unmap_image);
|
||||||
texture_cache.UnmapMemory(partial_unmap_image.info.data.address, 0x1000);
|
const auto image_address = partial_unmap_image.info.data.address;
|
||||||
|
Require(name, "registered image boundaries",
|
||||||
|
texture_cache.IsRegionRegistered(image_address, 1) &&
|
||||||
|
texture_cache.IsRegionRegistered(image_address + 0x1fff, 1) &&
|
||||||
|
!texture_cache.IsRegionRegistered(image_address - 1, 1) &&
|
||||||
|
!texture_cache.IsRegionRegistered(image_address + 0x2000, 1) &&
|
||||||
|
texture_cache.IsRegionRegistered(image_address - 1, 0x2002),
|
||||||
|
"image lookup mishandled a partial overlap or adjacent range");
|
||||||
|
scheduler.Finish();
|
||||||
|
scheduler.DrainPriorityOperations();
|
||||||
|
const auto image_unmap_tick = scheduler.CurrentTick();
|
||||||
|
resources.UnmapMemory(image_address + 0x1000, 0x1000);
|
||||||
Require(name, "partial image unmap tracking",
|
Require(name, "partial image unmap tracking",
|
||||||
partial_unmap_image_id &&
|
partial_unmap_image_id &&
|
||||||
!texture_cache.FindImageFromRange(
|
scheduler.CurrentTick() == image_unmap_tick + 1 &&
|
||||||
partial_unmap_image.info.data.address, 0x2000, false),
|
!texture_cache.IsRegionRegistered(image_address, 0x2000) &&
|
||||||
"partial unmap left the deleted image's mapped tail tracked");
|
!texture_cache.FindImageFromRange(image_address, 0x2000, false),
|
||||||
|
"partial image unmap skipped its drain or retained the image");
|
||||||
|
|
||||||
constexpr uint64_t unformatted_alias_offset = 0x2500000;
|
constexpr uint64_t unformatted_alias_offset = 0x2500000;
|
||||||
auto unformatted_alias =
|
auto unformatted_alias =
|
||||||
|
|||||||
Reference in New Issue
Block a user