Browse Source
Merge pull request #3409 from ReinUsesLisp/host-queries
Merge pull request #3409 from ReinUsesLisp/host-queries
query_cache: Implement a query cache and query 21 (samples passed)nce_cpp
committed by
GitHub
18 changed files with 1043 additions and 51 deletions
-
5src/video_core/CMakeLists.txt
-
77src/video_core/engines/maxwell_3d.cpp
-
42src/video_core/engines/maxwell_3d.h
-
359src/video_core/query_cache.h
-
12src/video_core/rasterizer_interface.h
-
120src/video_core/renderer_opengl/gl_query_cache.cpp
-
78src/video_core/renderer_opengl/gl_query_cache.h
-
32src/video_core/renderer_opengl/gl_rasterizer.cpp
-
41src/video_core/renderer_opengl/gl_rasterizer.h
-
17src/video_core/renderer_opengl/gl_resource_manager.cpp
-
25src/video_core/renderer_opengl/gl_resource_manager.h
-
10src/video_core/renderer_vulkan/vk_device.cpp
-
122src/video_core/renderer_vulkan/vk_query_cache.cpp
-
104src/video_core/renderer_vulkan/vk_query_cache.h
-
21src/video_core/renderer_vulkan/vk_rasterizer.cpp
-
6src/video_core/renderer_vulkan/vk_rasterizer.h
-
8src/video_core/renderer_vulkan/vk_scheduler.cpp
-
15src/video_core/renderer_vulkan/vk_scheduler.h
@ -0,0 +1,359 @@ |
|||
// 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 <mutex> |
|||
#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 current != nullptr; |
|||
} |
|||
|
|||
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 QueryPool> |
|||
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) { |
|||
std::unique_lock lock{mutex}; |
|||
FlushAndRemoveRegion(addr, size); |
|||
} |
|||
|
|||
void FlushRegion(CacheAddr addr, std::size_t size) { |
|||
std::unique_lock lock{mutex}; |
|||
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) { |
|||
std::unique_lock lock{mutex}; |
|||
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() { |
|||
std::unique_lock lock{mutex}; |
|||
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) { |
|||
std::unique_lock lock{mutex}; |
|||
Stream(type).Reset(); |
|||
} |
|||
|
|||
/// Disable all active streams. Expected to be called at the end of a command buffer. |
|||
void DisableStreams() { |
|||
std::unique_lock lock{mutex}; |
|||
for (auto& stream : streams) { |
|||
stream.Update(false); |
|||
} |
|||
} |
|||
|
|||
/// 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)]; |
|||
} |
|||
|
|||
/// Returns the counter stream of the specified type. |
|||
const CounterStream& Stream(VideoCore::QueryType type) const { |
|||
return streams[static_cast<std::size_t>(type)]; |
|||
} |
|||
|
|||
protected: |
|||
std::array<QueryPool, VideoCore::NumQueryTypes> query_pools; |
|||
|
|||
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.GetCacheAddr(); |
|||
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.GetCacheAddr() == addr; }); |
|||
return found != std::end(contents) ? &*found : nullptr; |
|||
} |
|||
|
|||
static constexpr std::uintptr_t PAGE_SIZE = 4096; |
|||
static constexpr unsigned PAGE_SHIFT = 12; |
|||
|
|||
Core::System& system; |
|||
VideoCore::RasterizerInterface& rasterizer; |
|||
|
|||
std::recursive_mutex mutex; |
|||
|
|||
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_)}, depth{dependency ? (dependency->Depth() + 1) : 0} { |
|||
// Avoid nesting too many dependencies to avoid a stack overflow when these are deleted. |
|||
constexpr u64 depth_threshold = 96; |
|||
if (depth > depth_threshold) { |
|||
depth = 0; |
|||
base_result = dependency->Query(); |
|||
dependency = nullptr; |
|||
} |
|||
} |
|||
virtual ~HostCounterBase() = default; |
|||
|
|||
/// Returns the current value of the query. |
|||
u64 Query() { |
|||
if (result) { |
|||
return *result; |
|||
} |
|||
|
|||
u64 value = BlockingQuery() + base_result; |
|||
if (dependency) { |
|||
value += dependency->Query(); |
|||
dependency = nullptr; |
|||
} |
|||
|
|||
result = value; |
|||
return *result; |
|||
} |
|||
|
|||
/// Returns true when flushing this query will potentially wait. |
|||
bool WaitPending() const noexcept { |
|||
return result.has_value(); |
|||
} |
|||
|
|||
u64 Depth() const noexcept { |
|||
return depth; |
|||
} |
|||
|
|||
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. |
|||
u64 depth; ///< Number of nested dependencies. |
|||
u64 base_result = 0; ///< Equivalent to nested dependencies value. |
|||
}; |
|||
|
|||
template <class HostCounter> |
|||
class CachedQueryBase { |
|||
public: |
|||
explicit CachedQueryBase(VAddr cpu_addr, u8* host_ptr) |
|||
: cpu_addr{cpu_addr}, host_ptr{host_ptr} {} |
|||
virtual ~CachedQueryBase() = default; |
|||
|
|||
CachedQueryBase(CachedQueryBase&&) noexcept = default; |
|||
CachedQueryBase(const CachedQueryBase&) = delete; |
|||
|
|||
CachedQueryBase& operator=(CachedQueryBase&&) noexcept = default; |
|||
CachedQueryBase& operator=(const CachedQueryBase&) = delete; |
|||
|
|||
/// 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 GetCacheAddr() const noexcept { |
|||
return ToCacheAddr(host_ptr); |
|||
} |
|||
|
|||
u64 SizeInBytes() const noexcept { |
|||
return SizeInBytes(timestamp.has_value()); |
|||
} |
|||
|
|||
static constexpr u64 SizeInBytes(bool with_timestamp) noexcept { |
|||
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 |
|||
@ -0,0 +1,120 @@ |
|||
// Copyright 2019 yuzu Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <algorithm>
|
|||
#include <cstring>
|
|||
#include <memory>
|
|||
#include <unordered_map>
|
|||
#include <utility>
|
|||
#include <vector>
|
|||
|
|||
#include <glad/glad.h>
|
|||
|
|||
#include "common/assert.h"
|
|||
#include "core/core.h"
|
|||
#include "video_core/engines/maxwell_3d.h"
|
|||
#include "video_core/memory_manager.h"
|
|||
#include "video_core/renderer_opengl/gl_query_cache.h"
|
|||
#include "video_core/renderer_opengl/gl_rasterizer.h"
|
|||
|
|||
namespace OpenGL { |
|||
|
|||
namespace { |
|||
|
|||
constexpr std::array<GLenum, VideoCore::NumQueryTypes> QueryTargets = {GL_SAMPLES_PASSED}; |
|||
|
|||
constexpr GLenum GetTarget(VideoCore::QueryType type) { |
|||
return QueryTargets[static_cast<std::size_t>(type)]; |
|||
} |
|||
|
|||
} // Anonymous namespace
|
|||
|
|||
QueryCache::QueryCache(Core::System& system, RasterizerOpenGL& gl_rasterizer) |
|||
: VideoCommon::QueryCacheBase< |
|||
QueryCache, CachedQuery, CounterStream, HostCounter, |
|||
std::vector<OGLQuery>>{system, |
|||
static_cast<VideoCore::RasterizerInterface&>(gl_rasterizer)}, |
|||
gl_rasterizer{gl_rasterizer} {} |
|||
|
|||
QueryCache::~QueryCache() = default; |
|||
|
|||
OGLQuery QueryCache::AllocateQuery(VideoCore::QueryType type) { |
|||
auto& reserve = query_pools[static_cast<std::size_t>(type)]; |
|||
OGLQuery query; |
|||
if (reserve.empty()) { |
|||
query.Create(GetTarget(type)); |
|||
return query; |
|||
} |
|||
|
|||
query = std::move(reserve.back()); |
|||
reserve.pop_back(); |
|||
return query; |
|||
} |
|||
|
|||
void QueryCache::Reserve(VideoCore::QueryType type, OGLQuery&& query) { |
|||
query_pools[static_cast<std::size_t>(type)].push_back(std::move(query)); |
|||
} |
|||
|
|||
bool QueryCache::AnyCommandQueued() const noexcept { |
|||
return gl_rasterizer.AnyCommandQueued(); |
|||
} |
|||
|
|||
HostCounter::HostCounter(QueryCache& cache, std::shared_ptr<HostCounter> dependency, |
|||
VideoCore::QueryType type) |
|||
: VideoCommon::HostCounterBase<QueryCache, HostCounter>{std::move(dependency)}, cache{cache}, |
|||
type{type}, query{cache.AllocateQuery(type)} { |
|||
glBeginQuery(GetTarget(type), query.handle); |
|||
} |
|||
|
|||
HostCounter::~HostCounter() { |
|||
cache.Reserve(type, std::move(query)); |
|||
} |
|||
|
|||
void HostCounter::EndQuery() { |
|||
if (!cache.AnyCommandQueued()) { |
|||
// There are chances a query waited on without commands (glDraw, glClear, glDispatch). Not
|
|||
// having any of these causes a lock. glFlush is considered a command, so we can safely wait
|
|||
// for this. Insert to the OpenGL command stream a flush.
|
|||
glFlush(); |
|||
} |
|||
glEndQuery(GetTarget(type)); |
|||
} |
|||
|
|||
u64 HostCounter::BlockingQuery() const { |
|||
GLint64 value; |
|||
glGetQueryObjecti64v(query.handle, GL_QUERY_RESULT, &value); |
|||
return static_cast<u64>(value); |
|||
} |
|||
|
|||
CachedQuery::CachedQuery(QueryCache& cache, VideoCore::QueryType type, VAddr cpu_addr, u8* host_ptr) |
|||
: VideoCommon::CachedQueryBase<HostCounter>{cpu_addr, host_ptr}, cache{&cache}, type{type} {} |
|||
|
|||
CachedQuery::CachedQuery(CachedQuery&& rhs) noexcept |
|||
: VideoCommon::CachedQueryBase<HostCounter>(std::move(rhs)), cache{rhs.cache}, type{rhs.type} {} |
|||
|
|||
CachedQuery& CachedQuery::operator=(CachedQuery&& rhs) noexcept { |
|||
VideoCommon::CachedQueryBase<HostCounter>::operator=(std::move(rhs)); |
|||
cache = rhs.cache; |
|||
type = rhs.type; |
|||
return *this; |
|||
} |
|||
|
|||
void CachedQuery::Flush() { |
|||
// Waiting for a query while another query of the same target is enabled locks Nvidia's driver.
|
|||
// To avoid this disable and re-enable keeping the dependency stream.
|
|||
// But we only have to do this if we have pending waits to be done.
|
|||
auto& stream = cache->Stream(type); |
|||
const bool slice_counter = WaitPending() && stream.IsEnabled(); |
|||
if (slice_counter) { |
|||
stream.Update(false); |
|||
} |
|||
|
|||
VideoCommon::CachedQueryBase<HostCounter>::Flush(); |
|||
|
|||
if (slice_counter) { |
|||
stream.Update(true); |
|||
} |
|||
} |
|||
|
|||
} // namespace OpenGL
|
|||
@ -0,0 +1,78 @@ |
|||
// Copyright 2019 yuzu Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <array> |
|||
#include <memory> |
|||
#include <vector> |
|||
|
|||
#include "common/common_types.h" |
|||
#include "video_core/query_cache.h" |
|||
#include "video_core/rasterizer_interface.h" |
|||
#include "video_core/renderer_opengl/gl_resource_manager.h" |
|||
|
|||
namespace Core { |
|||
class System; |
|||
} |
|||
|
|||
namespace OpenGL { |
|||
|
|||
class CachedQuery; |
|||
class HostCounter; |
|||
class QueryCache; |
|||
class RasterizerOpenGL; |
|||
|
|||
using CounterStream = VideoCommon::CounterStreamBase<QueryCache, HostCounter>; |
|||
|
|||
class QueryCache final : public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream, |
|||
HostCounter, std::vector<OGLQuery>> { |
|||
public: |
|||
explicit QueryCache(Core::System& system, RasterizerOpenGL& rasterizer); |
|||
~QueryCache(); |
|||
|
|||
OGLQuery AllocateQuery(VideoCore::QueryType type); |
|||
|
|||
void Reserve(VideoCore::QueryType type, OGLQuery&& query); |
|||
|
|||
bool AnyCommandQueued() const noexcept; |
|||
|
|||
private: |
|||
RasterizerOpenGL& gl_rasterizer; |
|||
}; |
|||
|
|||
class HostCounter final : public VideoCommon::HostCounterBase<QueryCache, HostCounter> { |
|||
public: |
|||
explicit HostCounter(QueryCache& cache, std::shared_ptr<HostCounter> dependency, |
|||
VideoCore::QueryType type); |
|||
~HostCounter(); |
|||
|
|||
void EndQuery(); |
|||
|
|||
private: |
|||
u64 BlockingQuery() const override; |
|||
|
|||
QueryCache& cache; |
|||
const VideoCore::QueryType type; |
|||
OGLQuery query; |
|||
}; |
|||
|
|||
class CachedQuery final : public VideoCommon::CachedQueryBase<HostCounter> { |
|||
public: |
|||
explicit CachedQuery(QueryCache& cache, VideoCore::QueryType type, VAddr cpu_addr, |
|||
u8* host_ptr); |
|||
CachedQuery(CachedQuery&& rhs) noexcept; |
|||
CachedQuery(const CachedQuery&) = delete; |
|||
|
|||
CachedQuery& operator=(CachedQuery&& rhs) noexcept; |
|||
CachedQuery& operator=(const CachedQuery&) = delete; |
|||
|
|||
void Flush() override; |
|||
|
|||
private: |
|||
QueryCache* cache; |
|||
VideoCore::QueryType type; |
|||
}; |
|||
|
|||
} // namespace OpenGL |
|||
@ -0,0 +1,122 @@ |
|||
// Copyright 2020 yuzu Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <algorithm>
|
|||
#include <cstddef>
|
|||
#include <cstdint>
|
|||
#include <utility>
|
|||
#include <vector>
|
|||
|
|||
#include "video_core/renderer_vulkan/declarations.h"
|
|||
#include "video_core/renderer_vulkan/vk_device.h"
|
|||
#include "video_core/renderer_vulkan/vk_query_cache.h"
|
|||
#include "video_core/renderer_vulkan/vk_resource_manager.h"
|
|||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
|||
|
|||
namespace Vulkan { |
|||
|
|||
namespace { |
|||
|
|||
constexpr std::array QUERY_TARGETS = {vk::QueryType::eOcclusion}; |
|||
|
|||
constexpr vk::QueryType GetTarget(VideoCore::QueryType type) { |
|||
return QUERY_TARGETS[static_cast<std::size_t>(type)]; |
|||
} |
|||
|
|||
} // Anonymous namespace
|
|||
|
|||
QueryPool::QueryPool() : VKFencedPool{GROW_STEP} {} |
|||
|
|||
QueryPool::~QueryPool() = default; |
|||
|
|||
void QueryPool::Initialize(const VKDevice& device_, VideoCore::QueryType type_) { |
|||
device = &device_; |
|||
type = type_; |
|||
} |
|||
|
|||
std::pair<vk::QueryPool, std::uint32_t> QueryPool::Commit(VKFence& fence) { |
|||
std::size_t index; |
|||
do { |
|||
index = CommitResource(fence); |
|||
} while (usage[index]); |
|||
usage[index] = true; |
|||
|
|||
return {*pools[index / GROW_STEP], static_cast<std::uint32_t>(index % GROW_STEP)}; |
|||
} |
|||
|
|||
void QueryPool::Allocate(std::size_t begin, std::size_t end) { |
|||
usage.resize(end); |
|||
|
|||
const auto dev = device->GetLogical(); |
|||
const u32 size = static_cast<u32>(end - begin); |
|||
const vk::QueryPoolCreateInfo query_pool_ci({}, GetTarget(type), size, {}); |
|||
pools.push_back(dev.createQueryPoolUnique(query_pool_ci, nullptr, device->GetDispatchLoader())); |
|||
} |
|||
|
|||
void QueryPool::Reserve(std::pair<vk::QueryPool, std::uint32_t> query) { |
|||
const auto it = |
|||
std::find_if(std::begin(pools), std::end(pools), |
|||
[query_pool = query.first](auto& pool) { return query_pool == *pool; }); |
|||
ASSERT(it != std::end(pools)); |
|||
|
|||
const std::ptrdiff_t pool_index = std::distance(std::begin(pools), it); |
|||
usage[pool_index * GROW_STEP + static_cast<std::ptrdiff_t>(query.second)] = false; |
|||
} |
|||
|
|||
VKQueryCache::VKQueryCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer, |
|||
const VKDevice& device, VKScheduler& scheduler) |
|||
: VideoCommon::QueryCacheBase<VKQueryCache, CachedQuery, CounterStream, HostCounter, |
|||
QueryPool>{system, rasterizer}, |
|||
device{device}, scheduler{scheduler} { |
|||
for (std::size_t i = 0; i < static_cast<std::size_t>(VideoCore::NumQueryTypes); ++i) { |
|||
query_pools[i].Initialize(device, static_cast<VideoCore::QueryType>(i)); |
|||
} |
|||
} |
|||
|
|||
VKQueryCache::~VKQueryCache() = default; |
|||
|
|||
std::pair<vk::QueryPool, std::uint32_t> VKQueryCache::AllocateQuery(VideoCore::QueryType type) { |
|||
return query_pools[static_cast<std::size_t>(type)].Commit(scheduler.GetFence()); |
|||
} |
|||
|
|||
void VKQueryCache::Reserve(VideoCore::QueryType type, |
|||
std::pair<vk::QueryPool, std::uint32_t> query) { |
|||
query_pools[static_cast<std::size_t>(type)].Reserve(query); |
|||
} |
|||
|
|||
HostCounter::HostCounter(VKQueryCache& cache, std::shared_ptr<HostCounter> dependency, |
|||
VideoCore::QueryType type) |
|||
: VideoCommon::HostCounterBase<VKQueryCache, HostCounter>{std::move(dependency)}, cache{cache}, |
|||
type{type}, query{cache.AllocateQuery(type)}, ticks{cache.Scheduler().Ticks()} { |
|||
const auto dev = cache.Device().GetLogical(); |
|||
cache.Scheduler().Record([dev, query = query](vk::CommandBuffer cmdbuf, auto& dld) { |
|||
dev.resetQueryPoolEXT(query.first, query.second, 1, dld); |
|||
cmdbuf.beginQuery(query.first, query.second, vk::QueryControlFlagBits::ePrecise, dld); |
|||
}); |
|||
} |
|||
|
|||
HostCounter::~HostCounter() { |
|||
cache.Reserve(type, query); |
|||
} |
|||
|
|||
void HostCounter::EndQuery() { |
|||
cache.Scheduler().Record([query = query](auto cmdbuf, auto& dld) { |
|||
cmdbuf.endQuery(query.first, query.second, dld); |
|||
}); |
|||
} |
|||
|
|||
u64 HostCounter::BlockingQuery() const { |
|||
if (ticks >= cache.Scheduler().Ticks()) { |
|||
cache.Scheduler().Flush(); |
|||
} |
|||
|
|||
const auto dev = cache.Device().GetLogical(); |
|||
const auto& dld = cache.Device().GetDispatchLoader(); |
|||
u64 value; |
|||
dev.getQueryPoolResults(query.first, query.second, 1, sizeof(value), &value, sizeof(value), |
|||
vk::QueryResultFlagBits::e64 | vk::QueryResultFlagBits::eWait, dld); |
|||
return value; |
|||
} |
|||
|
|||
} // namespace Vulkan
|
|||
@ -0,0 +1,104 @@ |
|||
// Copyright 2020 yuzu Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <cstddef> |
|||
#include <cstdint> |
|||
#include <memory> |
|||
#include <utility> |
|||
#include <vector> |
|||
|
|||
#include "common/common_types.h" |
|||
#include "video_core/query_cache.h" |
|||
#include "video_core/renderer_vulkan/declarations.h" |
|||
#include "video_core/renderer_vulkan/vk_resource_manager.h" |
|||
|
|||
namespace VideoCore { |
|||
class RasterizerInterface; |
|||
} |
|||
|
|||
namespace Vulkan { |
|||
|
|||
class CachedQuery; |
|||
class HostCounter; |
|||
class VKDevice; |
|||
class VKQueryCache; |
|||
class VKScheduler; |
|||
|
|||
using CounterStream = VideoCommon::CounterStreamBase<VKQueryCache, HostCounter>; |
|||
|
|||
class QueryPool final : public VKFencedPool { |
|||
public: |
|||
explicit QueryPool(); |
|||
~QueryPool() override; |
|||
|
|||
void Initialize(const VKDevice& device, VideoCore::QueryType type); |
|||
|
|||
std::pair<vk::QueryPool, std::uint32_t> Commit(VKFence& fence); |
|||
|
|||
void Reserve(std::pair<vk::QueryPool, std::uint32_t> query); |
|||
|
|||
protected: |
|||
void Allocate(std::size_t begin, std::size_t end) override; |
|||
|
|||
private: |
|||
static constexpr std::size_t GROW_STEP = 512; |
|||
|
|||
const VKDevice* device = nullptr; |
|||
VideoCore::QueryType type = {}; |
|||
|
|||
std::vector<UniqueQueryPool> pools; |
|||
std::vector<bool> usage; |
|||
}; |
|||
|
|||
class VKQueryCache final |
|||
: public VideoCommon::QueryCacheBase<VKQueryCache, CachedQuery, CounterStream, HostCounter, |
|||
QueryPool> { |
|||
public: |
|||
explicit VKQueryCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer, |
|||
const VKDevice& device, VKScheduler& scheduler); |
|||
~VKQueryCache(); |
|||
|
|||
std::pair<vk::QueryPool, std::uint32_t> AllocateQuery(VideoCore::QueryType type); |
|||
|
|||
void Reserve(VideoCore::QueryType type, std::pair<vk::QueryPool, std::uint32_t> query); |
|||
|
|||
const VKDevice& Device() const noexcept { |
|||
return device; |
|||
} |
|||
|
|||
VKScheduler& Scheduler() const noexcept { |
|||
return scheduler; |
|||
} |
|||
|
|||
private: |
|||
const VKDevice& device; |
|||
VKScheduler& scheduler; |
|||
}; |
|||
|
|||
class HostCounter final : public VideoCommon::HostCounterBase<VKQueryCache, HostCounter> { |
|||
public: |
|||
explicit HostCounter(VKQueryCache& cache, std::shared_ptr<HostCounter> dependency, |
|||
VideoCore::QueryType type); |
|||
~HostCounter(); |
|||
|
|||
void EndQuery(); |
|||
|
|||
private: |
|||
u64 BlockingQuery() const override; |
|||
|
|||
VKQueryCache& cache; |
|||
const VideoCore::QueryType type; |
|||
const std::pair<vk::QueryPool, std::uint32_t> query; |
|||
const u64 ticks; |
|||
}; |
|||
|
|||
class CachedQuery : public VideoCommon::CachedQueryBase<HostCounter> { |
|||
public: |
|||
explicit CachedQuery(VKQueryCache&, VideoCore::QueryType, VAddr cpu_addr, u8* host_ptr) |
|||
: VideoCommon::CachedQueryBase<HostCounter>{cpu_addr, host_ptr} {} |
|||
}; |
|||
|
|||
} // namespace Vulkan |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue