Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions rpcs3/Emu/RSX/Common/texture_cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -3740,6 +3740,13 @@ namespace rsx
}
}

// MM flush before commit below. For performance reasons, only flush when writing to CELL memory.
if (rsx::classify_location(dst.rsx_address) == CELL_GCM_LOCATION_MAIN)
{
const auto mm_flush_range = utils::address_range64::start_length(reinterpret_cast<u64>(dst.pixels), dst_payload_length);
rsx::mm_flush({ mm_flush_range });
}

// Commit any pending writes before we do the transfer. Writes will be done on super_ptr so locking beforehand is ok.
m_rtts.prepare_transfer_target(cmd, dst_subres.surface, rsx::surface_access::transfer_write, std::forward<Args>(extras)...);
}
Expand Down
10 changes: 8 additions & 2 deletions rpcs3/Emu/RSX/Common/texture_cache_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,14 @@ namespace rsx

if (src_is_render_target)
{
// Attempt to optimize...
if (dst_dimensions.width == 1280 || dst_dimensions.width == 2560) [[likely]]
// Attempt to optimize for performance...
if (get_location(dst_range.start) == CELL_GCM_LOCATION_MAIN)
{
// Don't guess when working with main memory
const auto min_fitted_height = utils::aligned_div(dst_range.length(), dst_pitch);
dst_dimensions.height = std::min(dst_dimensions.height, min_fitted_height);
}
else if (dst_dimensions.width == 1280 || dst_dimensions.width == 2560)
{
// Optimizations table based on common width/height pairings. If we guess wrong, the upload resolver will fix it anyway
// TODO: Add more entries based on empirical data
Expand Down
146 changes: 121 additions & 25 deletions rpcs3/Emu/RSX/Host/MM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,113 @@ namespace rsx
rsx::simple_array<MM_block> g_deferred_mprotect_queue;
shared_mutex g_mprotect_queue_lock;

void mm_flush_mprotect_queue_internal()
void mm_sanitize_queue_internal()
{
for (const auto& block : g_deferred_mprotect_queue)
u32 w = 0, r = 0;
for (; r < g_deferred_mprotect_queue.size(); ++r)
{
auto& block = g_deferred_mprotect_queue[r];
if (!block.range.valid())
{
continue;
}
g_deferred_mprotect_queue[w++] = block;
}
g_deferred_mprotect_queue.resize(w);
}

void mm_flush_mprotect_queue_internal(u32 count)
{
AUDIT(count <= g_deferred_mprotect_queue.size());

for (u32 i = 0; i < count; ++i)
{
auto& block = g_deferred_mprotect_queue[i];
ensure(block.range.valid());

utils::memory_protect(reinterpret_cast<void*>(block.range.start), block.range.length(), block.prot);
block.range.invalidate();
}

const u32 remaining = g_deferred_mprotect_queue.size() - count;
if (!remaining)
{
g_deferred_mprotect_queue.clear();
return;
}

// Pop processed entries from the queue
mm_sanitize_queue_internal();
}

// Reverse scan to find the latest overlapping MM conflict. The result is a count of prefix blocks.
template <typename F>
u32 mm_find_conflict_internal(F&& predicate)
{
for (u32 i = g_deferred_mprotect_queue.size(); i > 0; --i)
{
if (std::invoke(predicate, g_deferred_mprotect_queue[i - 1]))
{
return i;
}
}

g_deferred_mprotect_queue.clear();
return 0;
}

void mm_defer_mprotect_internal(u64 start, u64 length, utils::protection prot)
{
// We could stack and merge requests here, but that is more trouble than it is truly worth.
// A fresh call to memory_protect only takes a few nanoseconds of setup overhead, it is not worth the risk of hanging because of conflicts.
g_deferred_mprotect_queue.push_back({ utils::address_range64::start_length(start, length), prot });
const auto range = utils::address_range64::start_length(start, length);
bool has_invalid = false;
bool is_merged = false;

// Attempt a merge first. The queue length is short but the time taken to run mprotect is very high in comparison.
for (auto it = g_deferred_mprotect_queue.rbegin();
it != g_deferred_mprotect_queue.rend();
++it)
{
auto& block = *it;
if (!block.touches(range))
{
continue;
}

if (block.prot != prot)
{
// Optimization. If our new range swallows the old one, replace it.
if (block.range.inside(range))
{
block.range.invalidate();
has_invalid = true;
continue;
}

if (!block.overlaps(range))
{
// Adjacent. Skip.
continue;
}

// Preserve ordering. Do not proceed with merge.
break;
}

block.merge(range);
is_merged = true;
break;
}

if (has_invalid)
{
mm_sanitize_queue_internal();
}

if (is_merged)
{
return;
}

g_deferred_mprotect_queue.push_back({ range, prot });
}

void mm_protect(void* ptr, u64 length, utils::protection prot)
Expand All @@ -47,13 +139,24 @@ namespace rsx

if (prot == utils::protection::rw || prot == utils::protection::wx)
{
// Basically an unlock op. Flush if any overlap is detected
for (const auto& block : g_deferred_mprotect_queue)
// Basically an unlock op. Flush the conflicting prefix block if any overlap is detected.
if (u32 count = mm_find_conflict_internal(FN(x.overlaps(range))))
{
if (block.overlaps(range))
// Check for degenerate ranges that we'll be crushing
for (auto pblock = &g_deferred_mprotect_queue[count - 1];
count > 0 && pblock->range.inside(range);
count--, pblock--)
{
mm_flush_mprotect_queue_internal();
break;
pblock->range.invalidate();
}

if (count)
{
mm_flush_mprotect_queue_internal(count);
}
else
{
mm_sanitize_queue_internal();
}
}

Expand All @@ -68,7 +171,7 @@ namespace rsx
void mm_flush()
{
std::lock_guard lock(g_mprotect_queue_lock);
mm_flush_mprotect_queue_internal();
mm_flush_mprotect_queue_internal(g_deferred_mprotect_queue.size());
}

void mm_flush(u32 vm_address)
Expand All @@ -80,31 +183,24 @@ namespace rsx
}

const auto addr = reinterpret_cast<u64>(vm::base(vm_address));
for (const auto& block : g_deferred_mprotect_queue)
if (const u32 count = mm_find_conflict_internal(FN(x.overlaps(addr))))
{
if (block.overlaps(addr))
{
mm_flush_mprotect_queue_internal();
return;
}
mm_flush_mprotect_queue_internal(count);
}
}

void mm_flush(const rsx::simple_array<utils::address_range64>& ranges)
{
std::lock_guard lock(g_mprotect_queue_lock);
if (g_deferred_mprotect_queue.empty())
if (g_deferred_mprotect_queue.empty() || ranges.empty())
{
return;
}

for (const auto& block : g_deferred_mprotect_queue)
const auto block_overlaps_ranges = [&](const MM_block& block) { return ranges.any(FN(block.overlaps(x))); };
if (const u32 count = mm_find_conflict_internal(block_overlaps_ranges))
{
if (ranges.any(FN(block.overlaps(x))))
{
mm_flush_mprotect_queue_internal();
return;
}
mm_flush_mprotect_queue_internal(count);
}
}

Expand Down
14 changes: 14 additions & 0 deletions rpcs3/Emu/RSX/Host/MM.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ namespace rsx
utils::address_range64 range;
utils::protection prot;

inline void merge(const utils::address_range64& other)
{
AUDIT(other.valid());
range = utils::address_range64::start_end(
std::min(range.start, other.start),
std::max(range.end, other.end)
);
}

inline bool overlaps(const utils::address_range64& test) const
{
return range.overlaps(test);
Expand All @@ -22,6 +31,11 @@ namespace rsx
{
return range.overlaps(addr);
}

inline bool touches(const utils::address_range64& test) const
{
return range.touches(test);
}
};

enum mm_backend_ctrl : u32
Expand Down
1 change: 1 addition & 0 deletions rpcs3/tests/rpcs3_test.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
<ClCompile Include="test_fmt.cpp" />
<ClCompile Include="test_rsx_cfg.cpp" />
<ClCompile Include="test_rsx_fp_asm.cpp" />
<ClCompile Include="test_rsx_mm_queue.cpp" />
<ClCompile Include="test_simple_array.cpp" />
<ClCompile Include="test_address_range.cpp" />
<ClCompile Include="test_sys_fs.cpp" />
Expand Down
Loading
Loading