pcsx2/common/ReadbackSpinManager.cpp

231 lines
7.1 KiB
C++

/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "ReadbackSpinManager.h"
#include <algorithm>
static bool EventIsReadback(const ReadbackSpinManager::Event& event)
{
return event.size < 0;
}
static bool EventIsDraw(const ReadbackSpinManager::Event& event)
{
return !EventIsReadback(event);
}
static bool IsCompleted(const ReadbackSpinManager::Event& event)
{
return event.begin != event.end;
}
static int Similarity(const std::vector<ReadbackSpinManager::Event>& a, std::vector<ReadbackSpinManager::Event>& b)
{
u32 a_num_readbacks = std::count_if(a.begin(), a.end(), EventIsReadback);
u32 b_num_readbacks = std::count_if(b.begin(), b.end(), EventIsReadback);
int score = 0x10 - abs(static_cast<int>(a.size() - b.size()));
if (a_num_readbacks == b_num_readbacks)
score += 0x10000;
auto a_idx = a.begin();
auto b_idx = b.begin();
while (a_idx != a.end() && b_idx != b.end())
{
if (EventIsReadback(*a_idx) && EventIsReadback(*b_idx))
{
// Same number of events between readbacks
score += 0x1000;
}
// Try to match up on readbacks
else if (EventIsReadback(*a_idx))
{
b_idx++;
continue;
}
else if (EventIsReadback(*b_idx))
{
a_idx++;
continue;
}
else if (a_idx->size == b_idx->size)
{
// Same size
score += 0x100;
}
else if (a_idx->size / 2 <= b_idx->size && b_idx->size / 2 <= a_idx->size)
{
// Similar size
score += 0x10;
}
a_idx++;
b_idx++;
continue;
}
// Both hit the end at the same time
if (a_idx == a.end() && b_idx == b.end())
score += 0x1000;
return score;
}
static u32 PrevFrameNo(u32 frame, size_t total_frames)
{
s32 prev_frame = frame - 1;
if (prev_frame < 0)
prev_frame = total_frames - 1;
return prev_frame;
}
static u32 NextFrameNo(u32 frame, size_t total_frames)
{
u32 next_frame = frame + 1;
if (next_frame >= total_frames)
next_frame = 0;
return next_frame;
}
void ReadbackSpinManager::ReadbackRequested()
{
Event ev = {};
ev.size = -1;
m_frames[m_current_frame].push_back(ev);
// Advance reference frame idx to the next readback
while (m_frames[m_reference_frame].size() > m_reference_frame_idx &&
!EventIsReadback(m_frames[m_reference_frame][m_reference_frame_idx]))
{
m_reference_frame_idx++;
}
// ...and past it
if (m_frames[m_reference_frame].size() > m_reference_frame_idx)
m_reference_frame_idx++;
}
void ReadbackSpinManager::NextFrame()
{
u32 prev_frame_0 = PrevFrameNo(m_current_frame, std::size(m_frames));
u32 prev_frame_1 = PrevFrameNo(prev_frame_0, std::size(m_frames));
int similarity_0 = Similarity(m_frames[m_current_frame], m_frames[prev_frame_0]);
int similarity_1 = Similarity(m_frames[m_current_frame], m_frames[prev_frame_1]);
if (similarity_1 > similarity_0)
m_reference_frame = prev_frame_0;
else
m_reference_frame = m_current_frame;
m_reference_frame_idx = 0;
m_current_frame = NextFrameNo(m_current_frame, std::size(m_frames));
m_frames[m_current_frame].clear();
}
ReadbackSpinManager::DrawSubmittedReturn ReadbackSpinManager::DrawSubmitted(u64 size)
{
DrawSubmittedReturn out = {};
u32 idx = m_frames[m_current_frame].size();
out.id = idx | m_current_frame << 28;
Event ev = {};
ev.size = size;
m_frames[m_current_frame].push_back(ev);
if (m_reference_frame != m_current_frame &&
m_frames[m_reference_frame].size() > m_reference_frame_idx &&
EventIsDraw(m_frames[m_reference_frame][m_reference_frame_idx]))
{
auto find_next_draw = [this](u32 frame) -> Event* {
auto next = std::find_if(m_frames[frame].begin() + m_reference_frame_idx + 1,
m_frames[frame].end(),
EventIsDraw);
bool found = next != m_frames[frame].end();
if (!found)
{
u32 next_frame = NextFrameNo(frame, std::size(m_frames));
next = std::find_if(m_frames[next_frame].begin(), m_frames[next_frame].end(), EventIsDraw);
found = next != m_frames[next_frame].end();
}
return found ? &*next : nullptr;
};
Event* cur_draw = &m_frames[m_reference_frame][m_reference_frame_idx];
Event* next_draw = find_next_draw(m_reference_frame);
const bool is_one_frame_back = m_reference_frame == PrevFrameNo(m_current_frame, std::size(m_frames));
if ((!next_draw || !IsCompleted(*cur_draw) || !IsCompleted(*next_draw)) && is_one_frame_back)
{
// Last frame's timing data hasn't arrived, try the same spot in the frame before
u32 two_back = PrevFrameNo(m_reference_frame, std::size(m_frames));
if (m_frames[two_back].size() > m_reference_frame_idx &&
EventIsDraw(m_frames[two_back][m_reference_frame_idx]))
{
cur_draw = &m_frames[two_back][m_reference_frame_idx];
next_draw = find_next_draw(two_back);
}
}
if (next_draw && IsCompleted(*cur_draw) && IsCompleted(*next_draw) && m_spins_per_unit_time != 0)
{
u64 cur_size = cur_draw->size;
bool is_similar = cur_size / 2 <= size && size / 2 <= cur_size;
if (is_similar) // Only recommend spins if we're somewhat confident in what's going on
{
s32 current_draw_time = cur_draw->end - cur_draw->begin;
s32 gap = next_draw->begin - cur_draw->end;
// Give an extra bit of space for the draw to take a bit longer (we'll go with 1/8 longer)
s32 fill = gap - (current_draw_time >> 3);
if (fill > 0)
out.recommended_spin = static_cast<u32>(static_cast<double>(fill) * m_spins_per_unit_time);
}
}
m_reference_frame_idx++;
}
if (m_spins_per_unit_time == 0)
{
// Recommend some spinning so that we can get timing data
out.recommended_spin = 128;
}
return out;
}
void ReadbackSpinManager::DrawCompleted(u32 id, u32 begin_time, u32 end_time)
{
u32 frame_id = id >> 28;
u32 frame_off = id & ((1 << 28) - 1);
if (frame_id < std::size(m_frames) && frame_off < m_frames[frame_id].size())
{
Event& ev = m_frames[frame_id][frame_off];
ev.begin = begin_time;
ev.end = end_time;
}
}
void ReadbackSpinManager::SpinCompleted(u32 cycles, u32 begin_time, u32 end_time)
{
double elapsed = static_cast<double>(end_time - begin_time);
constexpr double decay = 15.0 / 16.0;
// Obviously it'll vary from GPU to GPU, but in my testing,
// both a Radeon Pro 5600M and Intel UHD 630 spin at about 100ns/cycle
// Note: We assume spin time is some constant times the number of cycles
// Obviously as the number of cycles gets really low, a constant offset may start being noticeable
// But this is not the case as low as 512 cycles (~50µs) on the GPUs listed above
m_total_spin_cycles = m_total_spin_cycles * decay + cycles;
m_total_spin_time = m_total_spin_time * decay + elapsed;
m_spins_per_unit_time = m_total_spin_cycles / m_total_spin_time;
}