Browse Source
query_cache: Abstract OpenGL implementation
query_cache: Abstract OpenGL implementation
Abstract the current OpenGL implementation into the VideoCommon namespace and reimplement it on top of that. Doing this avoids repeating code and logic in the Vulkan implementation.pull/15/merge
4 changed files with 394 additions and 339 deletions
-
1src/video_core/CMakeLists.txt
-
323src/video_core/query_cache.h
-
287src/video_core/renderer_opengl/gl_query_cache.cpp
-
122src/video_core/renderer_opengl/gl_query_cache.h
@ -0,0 +1,323 @@ |
|||
// Copyright 2020 yuzu Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <algorithm> |
|||
#include <array> |
|||
#include <cstring> |
|||
#include <iterator> |
|||
#include <memory> |
|||
#include <optional> |
|||
#include <unordered_map> |
|||
#include <vector> |
|||
|
|||
#include "common/assert.h" |
|||
#include "core/core.h" |
|||
#include "video_core/engines/maxwell_3d.h" |
|||
#include "video_core/gpu.h" |
|||
#include "video_core/memory_manager.h" |
|||
#include "video_core/rasterizer_interface.h" |
|||
|
|||
namespace VideoCommon { |
|||
|
|||
template <class QueryCache, class HostCounter> |
|||
class CounterStreamBase { |
|||
public: |
|||
explicit CounterStreamBase(QueryCache& cache, VideoCore::QueryType type) |
|||
: cache{cache}, type{type} {} |
|||
|
|||
/// Updates the state of the stream, enabling or disabling as needed. |
|||
void Update(bool enabled) { |
|||
if (enabled) { |
|||
Enable(); |
|||
} else { |
|||
Disable(); |
|||
} |
|||
} |
|||
|
|||
/// Resets the stream to zero. It doesn't disable the query after resetting. |
|||
void Reset() { |
|||
if (current) { |
|||
current->EndQuery(); |
|||
|
|||
// Immediately start a new query to avoid disabling its state. |
|||
current = cache.Counter(nullptr, type); |
|||
} |
|||
last = nullptr; |
|||
} |
|||
|
|||
/// Returns the current counter slicing as needed. |
|||
std::shared_ptr<HostCounter> Current() { |
|||
if (!current) { |
|||
return nullptr; |
|||
} |
|||
current->EndQuery(); |
|||
last = std::move(current); |
|||
current = cache.Counter(last, type); |
|||
return last; |
|||
} |
|||
|
|||
/// Returns true when the counter stream is enabled. |
|||
bool IsEnabled() const { |
|||
return static_cast<bool>(current); |
|||
} |
|||
|
|||
private: |
|||
/// Enables the stream. |
|||
void Enable() { |
|||
if (current) { |
|||
return; |
|||
} |
|||
current = cache.Counter(last, type); |
|||
} |
|||
|
|||
// Disables the stream. |
|||
void Disable() { |
|||
if (current) { |
|||
current->EndQuery(); |
|||
} |
|||
last = std::exchange(current, nullptr); |
|||
} |
|||
|
|||
QueryCache& cache; |
|||
const VideoCore::QueryType type; |
|||
|
|||
std::shared_ptr<HostCounter> current; |
|||
std::shared_ptr<HostCounter> last; |
|||
}; |
|||
|
|||
template <class QueryCache, class CachedQuery, class CounterStream, class HostCounter> |
|||
class QueryCacheBase { |
|||
public: |
|||
explicit QueryCacheBase(Core::System& system, VideoCore::RasterizerInterface& rasterizer) |
|||
: system{system}, rasterizer{rasterizer}, streams{{CounterStream{ |
|||
static_cast<QueryCache&>(*this), |
|||
VideoCore::QueryType::SamplesPassed}}} {} |
|||
|
|||
void InvalidateRegion(CacheAddr addr, std::size_t size) { |
|||
FlushAndRemoveRegion(addr, size); |
|||
} |
|||
|
|||
void FlushRegion(CacheAddr addr, std::size_t size) { |
|||
FlushAndRemoveRegion(addr, size); |
|||
} |
|||
|
|||
/** |
|||
* Records a query in GPU mapped memory, potentially marked with a timestamp. |
|||
* @param gpu_addr GPU address to flush to when the mapped memory is read. |
|||
* @param type Query type, e.g. SamplesPassed. |
|||
* @param timestamp Timestamp, when empty the flushed query is assumed to be short. |
|||
*/ |
|||
void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) { |
|||
auto& memory_manager = system.GPU().MemoryManager(); |
|||
const auto host_ptr = memory_manager.GetPointer(gpu_addr); |
|||
|
|||
CachedQuery* query = TryGet(ToCacheAddr(host_ptr)); |
|||
if (!query) { |
|||
const auto cpu_addr = memory_manager.GpuToCpuAddress(gpu_addr); |
|||
ASSERT_OR_EXECUTE(cpu_addr, return;); |
|||
|
|||
query = Register(type, *cpu_addr, host_ptr, timestamp.has_value()); |
|||
} |
|||
|
|||
query->BindCounter(Stream(type).Current(), timestamp); |
|||
} |
|||
|
|||
/// Updates counters from GPU state. Expected to be called once per draw, clear or dispatch. |
|||
void UpdateCounters() { |
|||
const auto& regs = system.GPU().Maxwell3D().regs; |
|||
Stream(VideoCore::QueryType::SamplesPassed).Update(regs.samplecnt_enable); |
|||
} |
|||
|
|||
/// Resets a counter to zero. It doesn't disable the query after resetting. |
|||
void ResetCounter(VideoCore::QueryType type) { |
|||
Stream(type).Reset(); |
|||
} |
|||
|
|||
/// Returns a new host counter. |
|||
std::shared_ptr<HostCounter> Counter(std::shared_ptr<HostCounter> dependency, |
|||
VideoCore::QueryType type) { |
|||
return std::make_shared<HostCounter>(static_cast<QueryCache&>(*this), std::move(dependency), |
|||
type); |
|||
} |
|||
|
|||
/// Returns the counter stream of the specified type. |
|||
CounterStream& Stream(VideoCore::QueryType type) { |
|||
return streams[static_cast<std::size_t>(type)]; |
|||
} |
|||
|
|||
private: |
|||
/// Flushes a memory range to guest memory and removes it from the cache. |
|||
void FlushAndRemoveRegion(CacheAddr addr, std::size_t size) { |
|||
const u64 addr_begin = static_cast<u64>(addr); |
|||
const u64 addr_end = addr_begin + static_cast<u64>(size); |
|||
const auto in_range = [addr_begin, addr_end](CachedQuery& query) { |
|||
const u64 cache_begin = query.CacheAddr(); |
|||
const u64 cache_end = cache_begin + query.SizeInBytes(); |
|||
return cache_begin < addr_end && addr_begin < cache_end; |
|||
}; |
|||
|
|||
const u64 page_end = addr_end >> PAGE_SHIFT; |
|||
for (u64 page = addr_begin >> PAGE_SHIFT; page <= page_end; ++page) { |
|||
const auto& it = cached_queries.find(page); |
|||
if (it == std::end(cached_queries)) { |
|||
continue; |
|||
} |
|||
auto& contents = it->second; |
|||
for (auto& query : contents) { |
|||
if (!in_range(query)) { |
|||
continue; |
|||
} |
|||
rasterizer.UpdatePagesCachedCount(query.CpuAddr(), query.SizeInBytes(), -1); |
|||
query.Flush(); |
|||
} |
|||
contents.erase(std::remove_if(std::begin(contents), std::end(contents), in_range), |
|||
std::end(contents)); |
|||
} |
|||
} |
|||
|
|||
/// Registers the passed parameters as cached and returns a pointer to the stored cached query. |
|||
CachedQuery* Register(VideoCore::QueryType type, VAddr cpu_addr, u8* host_ptr, bool timestamp) { |
|||
rasterizer.UpdatePagesCachedCount(cpu_addr, CachedQuery::SizeInBytes(timestamp), 1); |
|||
const u64 page = static_cast<u64>(ToCacheAddr(host_ptr)) >> PAGE_SHIFT; |
|||
return &cached_queries[page].emplace_back(static_cast<QueryCache&>(*this), type, cpu_addr, |
|||
host_ptr); |
|||
} |
|||
|
|||
/// Tries to a get a cached query. Returns nullptr on failure. |
|||
CachedQuery* TryGet(CacheAddr addr) { |
|||
const u64 page = static_cast<u64>(addr) >> PAGE_SHIFT; |
|||
const auto it = cached_queries.find(page); |
|||
if (it == std::end(cached_queries)) { |
|||
return nullptr; |
|||
} |
|||
auto& contents = it->second; |
|||
const auto found = std::find_if(std::begin(contents), std::end(contents), |
|||
[addr](auto& query) { return query.CacheAddr() == addr; }); |
|||
return found != std::end(contents) ? &*found : nullptr; |
|||
} |
|||
|
|||
static constexpr std::uintptr_t PAGE_SIZE = 4096; |
|||
static constexpr int PAGE_SHIFT = 12; |
|||
|
|||
Core::System& system; |
|||
VideoCore::RasterizerInterface& rasterizer; |
|||
|
|||
std::unordered_map<u64, std::vector<CachedQuery>> cached_queries; |
|||
|
|||
std::array<CounterStream, VideoCore::NumQueryTypes> streams; |
|||
}; |
|||
|
|||
template <class QueryCache, class HostCounter> |
|||
class HostCounterBase { |
|||
public: |
|||
explicit HostCounterBase(std::shared_ptr<HostCounter> dependency) |
|||
: dependency{std::move(dependency)} {} |
|||
|
|||
/// Returns the current value of the query. |
|||
u64 Query() { |
|||
if (result) { |
|||
return *result; |
|||
} |
|||
|
|||
u64 value = BlockingQuery(); |
|||
if (dependency) { |
|||
value += dependency->Query(); |
|||
} |
|||
|
|||
return *(result = value); |
|||
} |
|||
|
|||
/// Returns true when flushing this query will potentially wait. |
|||
bool WaitPending() const noexcept { |
|||
return result.has_value(); |
|||
} |
|||
|
|||
protected: |
|||
/// Returns the value of query from the backend API blocking as needed. |
|||
virtual u64 BlockingQuery() const = 0; |
|||
|
|||
private: |
|||
std::shared_ptr<HostCounter> dependency; ///< Counter to add to this value. |
|||
std::optional<u64> result; ///< Filled with the already returned value. |
|||
}; |
|||
|
|||
template <class HostCounter> |
|||
class CachedQueryBase { |
|||
public: |
|||
explicit CachedQueryBase(VAddr cpu_addr, u8* host_ptr) |
|||
: cpu_addr{cpu_addr}, host_ptr{host_ptr} {} |
|||
|
|||
CachedQueryBase(CachedQueryBase&& rhs) noexcept |
|||
: cpu_addr{rhs.cpu_addr}, host_ptr{rhs.host_ptr}, counter{std::move(rhs.counter)}, |
|||
timestamp{rhs.timestamp} {} |
|||
|
|||
CachedQueryBase(const CachedQueryBase&) = delete; |
|||
|
|||
CachedQueryBase& operator=(CachedQueryBase&& rhs) noexcept { |
|||
cpu_addr = rhs.cpu_addr; |
|||
host_ptr = rhs.host_ptr; |
|||
counter = std::move(rhs.counter); |
|||
timestamp = rhs.timestamp; |
|||
return *this; |
|||
} |
|||
|
|||
/// Flushes the query to guest memory. |
|||
virtual void Flush() { |
|||
// When counter is nullptr it means that it's just been reseted. We are supposed to write a |
|||
// zero in these cases. |
|||
const u64 value = counter ? counter->Query() : 0; |
|||
std::memcpy(host_ptr, &value, sizeof(u64)); |
|||
|
|||
if (timestamp) { |
|||
std::memcpy(host_ptr + TIMESTAMP_OFFSET, &*timestamp, sizeof(u64)); |
|||
} |
|||
} |
|||
|
|||
/// Binds a counter to this query. |
|||
void BindCounter(std::shared_ptr<HostCounter> counter_, std::optional<u64> timestamp_) { |
|||
if (counter) { |
|||
// If there's an old counter set it means the query is being rewritten by the game. |
|||
// To avoid losing the data forever, flush here. |
|||
Flush(); |
|||
} |
|||
counter = std::move(counter_); |
|||
timestamp = timestamp_; |
|||
} |
|||
|
|||
VAddr CpuAddr() const noexcept { |
|||
return cpu_addr; |
|||
} |
|||
|
|||
CacheAddr CacheAddr() const noexcept { |
|||
return ToCacheAddr(host_ptr); |
|||
} |
|||
|
|||
u64 SizeInBytes() const noexcept { |
|||
return SizeInBytes(timestamp.has_value()); |
|||
} |
|||
|
|||
static u64 SizeInBytes(bool with_timestamp) { |
|||
return with_timestamp ? LARGE_QUERY_SIZE : SMALL_QUERY_SIZE; |
|||
} |
|||
|
|||
protected: |
|||
/// Returns true when querying the counter may potentially block. |
|||
bool WaitPending() const noexcept { |
|||
return counter && counter->WaitPending(); |
|||
} |
|||
|
|||
private: |
|||
static constexpr std::size_t SMALL_QUERY_SIZE = 8; // Query size without timestamp. |
|||
static constexpr std::size_t LARGE_QUERY_SIZE = 16; // Query size with timestamp. |
|||
static constexpr std::intptr_t TIMESTAMP_OFFSET = 8; // Timestamp offset in a large query. |
|||
|
|||
VAddr cpu_addr; ///< Guest CPU address. |
|||
u8* host_ptr; ///< Writable host pointer. |
|||
std::shared_ptr<HostCounter> counter; ///< Host counter to query, owns the dependency tree. |
|||
std::optional<u64> timestamp; ///< Timestamp to flush to guest memory. |
|||
}; |
|||
|
|||
} // namespace VideoCommon |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue