rsx: Implement RSX reports area access detection and optimize around it

- If nobody is reading RSX reports, do not be in a hurry to write them
- Requires HLE of some methods (cellGcmGetTimestamp) to function correctly
This commit is contained in:
kd-11 2022-05-11 00:58:59 +03:00 committed by kd-11
parent 34220ec447
commit 9a1e6cc3e8
5 changed files with 273 additions and 88 deletions

View File

@ -960,7 +960,7 @@ bool GLGSRender::on_access_violation(u32 address, bool is_writing)
if (!result.violation_handled)
{
return false;
return zcull_ctrl->on_access_violation(address);
}
if (result.num_flushable > 0)

View File

@ -2603,15 +2603,7 @@ namespace rsx
{
if (zcull_ctrl->has_pending())
{
if (g_cfg.video.relaxed_zcull_sync)
{
// Emit zcull sync hint and update; guarantees results to be written shortly after this event
zcull_ctrl->update(this, 0, true);
}
else
{
zcull_ctrl->sync(this);
}
zcull_ctrl->sync(this);
}
// Fragment constants may have been updated

View File

@ -3,6 +3,18 @@
namespace rsx
{
static inline std::string_view location_tostring(u32 location)
{
ensure(location < 2);
const char* location_names[] = {"CELL_GCM_LOCATION_LOCAL", "CELL_GCM_LOCATION_MAIN"};
return location_names[location];
}
static inline u32 classify_location(u32 address)
{
return (address >= rsx::constants::local_mem_base) ? CELL_GCM_LOCATION_LOCAL : CELL_GCM_LOCATION_MAIN;
}
namespace reports
{
ZCULL_control::ZCULL_control()
@ -166,6 +178,8 @@ namespace rsx
break;
}
on_report_enqueued(sink);
ptimer->async_tasks_pending++;
if (m_statistics_map[m_statistics_tag_id].result != 0)
@ -308,12 +322,14 @@ namespace rsx
}
rsx::reservation_lock<true> lock(sink, 16);
vm::_ref<atomic_t<CellGcmReportData>>(sink).store({ timestamp, value, 0 });
auto report = vm::get_super_ptr<atomic_t<CellGcmReportData>>(sink);
report->store({ timestamp, value, 0 });
}
void ZCULL_control::write(queued_report_write* writer, u64 timestamp, u32 value)
{
write(writer->sink, timestamp, writer->type, value);
on_report_completed(writer->sink);
for (auto& addr : writer->sink_alias)
{
@ -352,94 +368,109 @@ namespace rsx
void ZCULL_control::sync(::rsx::thread* ptimer)
{
if (!m_pending_writes.empty())
if (m_pending_writes.empty())
{
// Quick reverse scan to push commands ahead of time
for (auto It = m_pending_writes.rbegin(); It != m_pending_writes.rend(); ++It)
// Nothing to do
return;
}
if (!m_critical_reports_in_flight)
{
// Valid call, but nothing important queued up
return;
}
if (g_cfg.video.relaxed_zcull_sync)
{
update(ptimer, 0, true);
return;
}
// Quick reverse scan to push commands ahead of time
for (auto It = m_pending_writes.rbegin(); It != m_pending_writes.rend(); ++It)
{
if (It->query && It->query->num_draws)
{
if (It->query && It->query->num_draws)
if (It->query->sync_tag > m_sync_tag)
{
if (It->query->sync_tag > m_sync_tag)
{
// rsx_log.trace("[Performance warning] Query hint emit during sync command.");
ptimer->sync_hint(FIFO_hint::hint_zcull_sync, It->query);
}
break;
}
}
u32 processed = 0;
const bool has_unclaimed = (m_pending_writes.back().sink == 0);
// Write all claimed reports unconditionally
for (auto& writer : m_pending_writes)
{
if (!writer.sink)
break;
auto query = writer.query;
auto& counter = m_statistics_map[writer.counter_tag];
if (query)
{
ensure(query->pending);
const bool implemented = (writer.type == CELL_GCM_ZPASS_PIXEL_CNT || writer.type == CELL_GCM_ZCULL_STATS3);
const bool have_result = counter.result && !g_cfg.video.precise_zpass_count;
if (implemented && !have_result && query->num_draws)
{
get_occlusion_query_result(query);
counter.result += query->result;
}
else
{
// Already have a hit, no need to retest
discard_occlusion_query(query);
}
free_query(query);
// rsx_log.trace("[Performance warning] Query hint emit during sync command.");
ptimer->sync_hint(FIFO_hint::hint_zcull_sync, It->query);
}
retire(ptimer, &writer, counter.result);
processed++;
break;
}
}
u32 processed = 0;
const bool has_unclaimed = (m_pending_writes.back().sink == 0);
// Write all claimed reports unconditionally
for (auto& writer : m_pending_writes)
{
if (!writer.sink)
break;
auto query = writer.query;
auto& counter = m_statistics_map[writer.counter_tag];
if (query)
{
ensure(query->pending);
const bool implemented = (writer.type == CELL_GCM_ZPASS_PIXEL_CNT || writer.type == CELL_GCM_ZCULL_STATS3);
const bool have_result = counter.result && !g_cfg.video.precise_zpass_count;
if (implemented && !have_result && query->num_draws)
{
get_occlusion_query_result(query);
counter.result += query->result;
}
else
{
// Already have a hit, no need to retest
discard_occlusion_query(query);
}
free_query(query);
}
if (!has_unclaimed)
retire(ptimer, &writer, counter.result);
processed++;
}
if (!has_unclaimed)
{
ensure(processed == m_pending_writes.size());
m_pending_writes.clear();
}
else
{
auto remaining = m_pending_writes.size() - processed;
ensure(remaining > 0);
if (remaining == 1)
{
ensure(processed == m_pending_writes.size());
m_pending_writes.clear();
m_pending_writes[0] = std::move(m_pending_writes.back());
m_pending_writes.resize(1);
}
else
{
auto remaining = m_pending_writes.size() - processed;
ensure(remaining > 0);
if (remaining == 1)
{
m_pending_writes[0] = std::move(m_pending_writes.back());
m_pending_writes.resize(1);
}
else
{
std::move(m_pending_writes.begin() + processed, m_pending_writes.end(), m_pending_writes.begin());
m_pending_writes.resize(remaining);
}
std::move(m_pending_writes.begin() + processed, m_pending_writes.end(), m_pending_writes.begin());
m_pending_writes.resize(remaining);
}
//Delete all statistics caches but leave the current one
for (auto It = m_statistics_map.begin(); It != m_statistics_map.end(); )
{
if (It->first == m_statistics_tag_id)
++It;
else
It = m_statistics_map.erase(It);
}
//Decrement jobs counter
ptimer->async_tasks_pending -= processed;
}
//Delete all statistics caches but leave the current one
for (auto It = m_statistics_map.begin(); It != m_statistics_map.end(); )
{
if (It->first == m_statistics_tag_id)
++It;
else
It = m_statistics_map.erase(It);
}
//Decrement jobs counter
ptimer->async_tasks_pending -= processed;
}
void ZCULL_control::update(::rsx::thread* ptimer, u32 sync_address, bool hint)
@ -486,7 +517,7 @@ namespace rsx
m_next_tsc = m_tsc + min_zcull_tick_us;
// Schedule a queue flush if needed
if (!g_cfg.video.relaxed_zcull_sync &&
if (!g_cfg.video.relaxed_zcull_sync && m_critical_reports_in_flight &&
front.query && front.query->num_draws && front.query->sync_tag > m_sync_tag)
{
const auto elapsed = m_tsc - front.query->timestamp;
@ -624,7 +655,27 @@ namespace rsx
}
if (!sync_address || !query)
{
return result_none;
}
// Disable optimizations across the accessed range if reports reside within
{
std::scoped_lock lock(m_pages_mutex);
const auto location1 = rsx::classify_location(memory_address);
const auto location2 = rsx::classify_location(memory_end - 1);
if (!m_pages_accessed[location1])
{
disable_optimizations(ptimer, location1);
}
if (!m_pages_accessed[location2])
{
disable_optimizations(ptimer, location2);
}
}
if (!(flags & sync_defer_copy))
{
@ -650,6 +701,7 @@ namespace rsx
flags32_t ZCULL_control::read_barrier(class ::rsx::thread* ptimer, u32 memory_address, occlusion_query_info* query)
{
// Called by cond render control. Internal RSX usage, do not disable optimizations
while (query->pending && !Emu.IsStopped())
{
update(ptimer, memory_address);
@ -729,6 +781,127 @@ namespace rsx
return bytes_to_write;
}
void ZCULL_control::on_report_enqueued(vm::addr_t address)
{
const auto location = (address >= rsx::constants::local_mem_base) ? CELL_GCM_LOCATION_LOCAL : CELL_GCM_LOCATION_MAIN;
std::scoped_lock lock(m_pages_mutex);
if (!m_pages_accessed[location]) [[ likely ]]
{
const auto page_address = static_cast<u32>(address) & ~0xfff;
auto& page = m_locked_pages[location][page_address];
page.add_ref();
if (page.prot == utils::protection::rw)
{
utils::memory_protect(vm::base(page_address), 4096, utils::protection::no);
page.prot = utils::protection::no;
}
}
else
{
m_critical_reports_in_flight++;
}
}
void ZCULL_control::on_report_completed(vm::addr_t address)
{
const auto location = (address >= rsx::constants::local_mem_base) ? CELL_GCM_LOCATION_LOCAL : CELL_GCM_LOCATION_MAIN;
if (!m_pages_accessed[location])
{
const auto page_address = static_cast<u32>(address) & ~0xfff;
std::scoped_lock lock(m_pages_mutex);
if (auto found = m_locked_pages[location].find(page_address);
found != m_locked_pages[location].end())
{
auto& page = found->second;
ensure(page.has_refs());
page.release();
if (!page.has_refs())
{
if (page.prot != utils::protection::rw)
{
utils::memory_protect(vm::base(page_address), 4096, utils::protection::rw);
}
m_locked_pages[location].erase(page_address);
}
}
}
if (m_pages_accessed[location])
{
m_critical_reports_in_flight--;
}
}
void ZCULL_control::disable_optimizations(::rsx::thread* ptimer, u32 location)
{
// Externally synchronized
rsx_log.warning("Reports area at location %s was accessed. ZCULL optimizations will be disabled.", location_tostring(location));
m_pages_accessed[location] = true;
// Flush all pending writes
m_critical_reports_in_flight += 0x100000;
sync(ptimer);
m_critical_reports_in_flight -= 0x100000;
// Unlock pages
for (auto& p : m_locked_pages[location])
{
const auto this_address = p.first;
auto& page = p.second;
if (page.prot != utils::protection::rw)
{
utils::memory_protect(vm::base(this_address), 4096, utils::protection::rw);
page.prot = utils::protection::rw;
}
while (page.has_refs())
{
m_critical_reports_in_flight++;
page.release();
}
}
m_locked_pages[location].clear();
}
bool ZCULL_control::on_access_violation(u32 address)
{
const auto page_address = address & ~0xfff;
const auto location = rsx::classify_location(address);
if (m_pages_accessed[location])
{
// Already faulted, no locks possible
return false;
}
{
reader_lock lock(m_pages_mutex);
if (auto found = m_locked_pages[location].find(page_address);
found != m_locked_pages[location].end())
{
lock.upgrade();
auto& fault_page = m_locked_pages[location][page_address];
if (fault_page.prot != utils::protection::rw)
{
disable_optimizations(rsx::get_current_renderer(), location);
return true;
}
}
}
return false;
}
// Conditional rendering helpers
void conditional_render_eval::reset()

View File

@ -1,7 +1,9 @@
#pragma once
#include <util/types.hpp>
#include <util/vm.hpp>
#include <Emu/Memory/vm.h>
#include "rsx_utils.h"
#include <vector>
@ -51,6 +53,11 @@ namespace rsx
u32 reserved;
};
struct MMIO_page_data_t : public rsx::ref_counted
{
utils::protection prot = utils::protection::rw;
};
enum sync_control
{
sync_none = 0,
@ -60,6 +67,16 @@ namespace rsx
class ZCULL_control
{
private:
std::unordered_map<u32, MMIO_page_data_t> m_locked_pages[2];
atomic_t<bool> m_pages_accessed[2] = { false, false };
atomic_t<s32> m_critical_reports_in_flight = { 0 };
shared_mutex m_pages_mutex;
void on_report_enqueued(vm::addr_t address);
void on_report_completed(vm::addr_t address);
void disable_optimizations(class ::rsx::thread* ptimer, u32 location);
protected:
// Delay before a report update operation is forced to retire
const u32 max_zcull_delay_us = 300;
@ -153,6 +170,9 @@ namespace rsx
// Copies queries in range rebased from source range to destination range
u32 copy_reports_to(u32 start, u32 range, u32 dest);
// Check paging issues
bool on_access_violation(u32 address);
// Backend methods (optional, will return everything as always visible by default)
virtual void begin_occlusion_query(occlusion_query_info* /*query*/) {}
virtual void end_occlusion_query(occlusion_query_info* /*query*/) {}

View File

@ -789,7 +789,7 @@ bool VKGSRender::on_access_violation(u32 address, bool is_writing)
if (!result.violation_handled)
{
return false;
return zcull_ctrl->on_access_violation(address);
}
if (result.num_flushable > 0)