mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 22:06:59 +01:00
graphics: count occlusion queries on the host GPU behind --occlusion-queries
This commit is contained in:
@@ -549,6 +549,11 @@ add_executable(jpeg_enc_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/JpegEncTests.c
|
|||||||
target_link_libraries(jpeg_enc_tests common fmt::fmt)
|
target_link_libraries(jpeg_enc_tests common fmt::fmt)
|
||||||
target_include_directories(jpeg_enc_tests PRIVATE ${inc_headers})
|
target_include_directories(jpeg_enc_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
|
add_executable(occlusion_queries_tests EXCLUDE_FROM_ALL
|
||||||
|
"${KYTY_TESTS_DIR}/OcclusionQueriesTests.cpp")
|
||||||
|
target_link_libraries(occlusion_queries_tests common fmt::fmt Vulkan::Headers)
|
||||||
|
target_include_directories(occlusion_queries_tests PRIVATE ${inc_headers})
|
||||||
|
|
||||||
add_executable(ces_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/CesTests.cpp")
|
add_executable(ces_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/CesTests.cpp")
|
||||||
target_link_libraries(ces_tests common fmt::fmt SDL3::SDL3-static)
|
target_link_libraries(ces_tests common fmt::fmt SDL3::SDL3-static)
|
||||||
target_include_directories(ces_tests PRIVATE ${inc_headers})
|
target_include_directories(ces_tests PRIVATE ${inc_headers})
|
||||||
@@ -662,6 +667,7 @@ if(BUILD_TESTING)
|
|||||||
add_test(NAME png_enc COMMAND $<TARGET_FILE:png_enc_tests>)
|
add_test(NAME png_enc COMMAND $<TARGET_FILE:png_enc_tests>)
|
||||||
add_test(NAME jpeg_dec COMMAND $<TARGET_FILE:jpeg_dec_tests>)
|
add_test(NAME jpeg_dec COMMAND $<TARGET_FILE:jpeg_dec_tests>)
|
||||||
add_test(NAME jpeg_enc COMMAND $<TARGET_FILE:jpeg_enc_tests>)
|
add_test(NAME jpeg_enc COMMAND $<TARGET_FILE:jpeg_enc_tests>)
|
||||||
|
add_test(NAME occlusion_queries COMMAND $<TARGET_FILE:occlusion_queries_tests>)
|
||||||
add_test(NAME ces COMMAND $<TARGET_FILE:ces_tests>)
|
add_test(NAME ces COMMAND $<TARGET_FILE:ces_tests>)
|
||||||
add_test(NAME save_data_dialog COMMAND $<TARGET_FILE:save_data_dialog_tests>)
|
add_test(NAME save_data_dialog COMMAND $<TARGET_FILE:save_data_dialog_tests>)
|
||||||
add_test(NAME http_uri_parse COMMAND $<TARGET_FILE:http_uri_parse_tests>)
|
add_test(NAME http_uri_parse COMMAND $<TARGET_FILE:http_uri_parse_tests>)
|
||||||
@@ -754,6 +760,7 @@ if(BUILD_TESTING)
|
|||||||
png_enc_tests
|
png_enc_tests
|
||||||
jpeg_dec_tests
|
jpeg_dec_tests
|
||||||
jpeg_enc_tests
|
jpeg_enc_tests
|
||||||
|
occlusion_queries_tests
|
||||||
ces_tests
|
ces_tests
|
||||||
save_data_dialog_tests
|
save_data_dialog_tests
|
||||||
http_uri_parse_tests
|
http_uri_parse_tests
|
||||||
|
|||||||
@@ -197,6 +197,10 @@ bool TessellationEnabled() {
|
|||||||
return g_config->tessellation_enabled;
|
return g_config->tessellation_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool OcclusionQueriesEnabled() {
|
||||||
|
return g_config->occlusion_queries_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
bool TrophyEnabled() {
|
bool TrophyEnabled() {
|
||||||
return g_config->trophy_enabled;
|
return g_config->trophy_enabled;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ struct ConfigOptions {
|
|||||||
bool readback_tiled_images = false;
|
bool readback_tiled_images = false;
|
||||||
bool sync_raw_image_buffers = false;
|
bool sync_raw_image_buffers = false;
|
||||||
bool tessellation_enabled = false;
|
bool tessellation_enabled = false;
|
||||||
|
bool occlusion_queries_enabled = false;
|
||||||
bool trophy_enabled = true;
|
bool trophy_enabled = true;
|
||||||
uint32_t trophy_duration_seconds = 6;
|
uint32_t trophy_duration_seconds = 6;
|
||||||
bool perf_overlay_enabled = false;
|
bool perf_overlay_enabled = false;
|
||||||
@@ -154,6 +155,7 @@ bool ReadbackLinearImagesEnabled();
|
|||||||
bool ReadbackTiledImagesEnabled();
|
bool ReadbackTiledImagesEnabled();
|
||||||
bool SyncRawImageBuffersEnabled();
|
bool SyncRawImageBuffersEnabled();
|
||||||
bool TessellationEnabled();
|
bool TessellationEnabled();
|
||||||
|
bool OcclusionQueriesEnabled();
|
||||||
bool TrophyEnabled();
|
bool TrophyEnabled();
|
||||||
uint32_t GetTrophyDurationSeconds();
|
uint32_t GetTrophyDurationSeconds();
|
||||||
bool PlayGoHackEnabled();
|
bool PlayGoHackEnabled();
|
||||||
|
|||||||
@@ -237,6 +237,8 @@ void CommandProcessor::BufferInit() {
|
|||||||
|
|
||||||
void CommandProcessor::BufferFlush() {
|
void CommandProcessor::BufferFlush() {
|
||||||
GetScheduler().Flush();
|
GetScheduler().Flush();
|
||||||
|
// The occlusion counters reach a guest that polls them without waiting for a later fence.
|
||||||
|
m_renderer.GetOcclusionQueries().Publish();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandProcessor::BufferWait() {
|
void CommandProcessor::BufferWait() {
|
||||||
@@ -803,6 +805,7 @@ void CommandProcessor::SetPredication(uint32_t condition, uint32_t op, uint32_t
|
|||||||
return;
|
return;
|
||||||
case 0x01: {
|
case 0x01: {
|
||||||
EXIT_NOT_IMPLEMENTED(address == nullptr);
|
EXIT_NOT_IMPLEMENTED(address == nullptr);
|
||||||
|
m_renderer.GetOcclusionQueries().Publish();
|
||||||
// One begin/end pair per DB; bit 63 marks each counter ready.
|
// One begin/end pair per DB; bit 63 marks each counter ready.
|
||||||
constexpr uint64_t ready_bit = 1ull << 63u;
|
constexpr uint64_t ready_bit = 1ull << 63u;
|
||||||
const auto* results = reinterpret_cast<const volatile uint64_t*>(address);
|
const auto* results = reinterpret_cast<const volatile uint64_t*>(address);
|
||||||
@@ -1047,6 +1050,8 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
|
|||||||
|
|
||||||
EXIT_NOT_IMPLEMENTED(cache_policy != 0x00000000);
|
EXIT_NOT_IMPLEMENTED(cache_policy != 0x00000000);
|
||||||
EXIT_NOT_IMPLEMENTED(event_write_dest != 0x00000000);
|
EXIT_NOT_IMPLEMENTED(event_write_dest != 0x00000000);
|
||||||
|
// Dumped occlusion counters are written before any end-of-pipe signal reaches the guest.
|
||||||
|
m_renderer.GetOcclusionQueries().Publish();
|
||||||
|
|
||||||
bool with_interrupt = false;
|
bool with_interrupt = false;
|
||||||
switch (interrupt_selector) {
|
switch (interrupt_selector) {
|
||||||
@@ -1213,6 +1218,7 @@ void CommandProcessor::WriteAtEndOfPipe64(uint32_t cache_policy, uint32_t event_
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CommandProcessor::TriggerEopEventAtEndOfPipe(uint32_t interrupt_context_id) {
|
void CommandProcessor::TriggerEopEventAtEndOfPipe(uint32_t interrupt_context_id) {
|
||||||
|
m_renderer.GetOcclusionQueries().Publish();
|
||||||
(void)CurrentBuffer().Handle();
|
(void)CurrentBuffer().Handle();
|
||||||
GetScheduler().DeferPriorityOperation(
|
GetScheduler().DeferPriorityOperation(
|
||||||
[&renderer = m_renderer, event_id = m_interrupt_event_id, interrupt_context_id] {
|
[&renderer = m_renderer, event_id = m_interrupt_event_id, interrupt_context_id] {
|
||||||
@@ -1270,15 +1276,19 @@ void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index,
|
|||||||
"\n",
|
"\n",
|
||||||
event_index, event_address);
|
event_index, event_address);
|
||||||
}
|
}
|
||||||
|
if (auto& queries = m_renderer.GetOcclusionQueries(); queries.Enabled()) {
|
||||||
|
queries.Dump(event_address);
|
||||||
|
break;
|
||||||
|
}
|
||||||
static std::once_flag warning_once;
|
static std::once_flag warning_once;
|
||||||
std::call_once(warning_once, [] {
|
std::call_once(warning_once, [] {
|
||||||
std::printf("Warning: game uses occlusion queries, which are currently treated as "
|
std::printf("Warning: game uses occlusion queries, which are treated as always "
|
||||||
"always visible; GPU usage may be higher and FPS may be lower.\n");
|
"visible without --occlusion-queries; GPU usage may be higher and FPS "
|
||||||
|
"may be lower.\n");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Until host occlusion queries are implemented, publish an always-visible result. The
|
// Without host occlusion queries, publish an always-visible result. The PS5 layout
|
||||||
// PS5 layout contains one interleaved begin/end pair per DB, and bit 63 marks a result
|
// contains one interleaved begin/end pair per DB, and bit 63 marks a result ready.
|
||||||
// ready.
|
|
||||||
constexpr uint64_t ready_bit = 1ull << 63u;
|
constexpr uint64_t ready_bit = 1ull << 63u;
|
||||||
constexpr uint64_t counter_mask = ready_bit - 1u;
|
constexpr uint64_t counter_mask = ready_bit - 1u;
|
||||||
auto* results = reinterpret_cast<volatile uint64_t*>(event_address);
|
auto* results = reinterpret_cast<volatile uint64_t*>(event_address);
|
||||||
@@ -1298,6 +1308,7 @@ void CommandProcessor::Flip() {
|
|||||||
if (GraphicsRunDebugDumpEnabled()) {
|
if (GraphicsRunDebugDumpEnabled()) {
|
||||||
LOGF("CommandProcessor::Flip()\n");
|
LOGF("CommandProcessor::Flip()\n");
|
||||||
}
|
}
|
||||||
|
m_renderer.GetOcclusionQueries().Publish();
|
||||||
|
|
||||||
auto& command = CurrentBuffer();
|
auto& command = CurrentBuffer();
|
||||||
auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode,
|
auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode,
|
||||||
@@ -1309,8 +1320,6 @@ void CommandProcessor::Flip() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CommandProcessor::Flip(void* dst_gpu_addr, uint32_t value) {
|
void CommandProcessor::Flip(void* dst_gpu_addr, uint32_t value) {
|
||||||
auto& command = CurrentBuffer();
|
|
||||||
|
|
||||||
if (GraphicsRunDebugDumpEnabled()) {
|
if (GraphicsRunDebugDumpEnabled()) {
|
||||||
LOGF("CommandProcessor::Flip()\n"
|
LOGF("CommandProcessor::Flip()\n"
|
||||||
"\t dst_gpu_addr = 0x%016" PRIx64 "\n"
|
"\t dst_gpu_addr = 0x%016" PRIx64 "\n"
|
||||||
@@ -1319,13 +1328,9 @@ void CommandProcessor::Flip(void* dst_gpu_addr, uint32_t value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EXIT_IF(dst_gpu_addr == nullptr);
|
EXIT_IF(dst_gpu_addr == nullptr);
|
||||||
|
m_renderer.GetOcclusionQueries().Publish();
|
||||||
std::memcpy(dst_gpu_addr, &value, sizeof(value));
|
std::memcpy(dst_gpu_addr, &value, sizeof(value));
|
||||||
auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode,
|
Flip();
|
||||||
m_flip.flip_arg);
|
|
||||||
(void)command.Handle();
|
|
||||||
GetScheduler().DeferPriorityOperation(
|
|
||||||
[&renderer = m_renderer, request] { renderer.GetVideoOut().CompleteFlip(request); });
|
|
||||||
GetScheduler().Flush();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandProcessor::FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache_action,
|
void CommandProcessor::FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache_action,
|
||||||
@@ -1345,6 +1350,7 @@ void CommandProcessor::FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache
|
|||||||
EXIT("unknown event type\n");
|
EXIT("unknown event type\n");
|
||||||
}
|
}
|
||||||
EXIT_IF(dst_gpu_addr == nullptr);
|
EXIT_IF(dst_gpu_addr == nullptr);
|
||||||
|
m_renderer.GetOcclusionQueries().Publish();
|
||||||
std::memcpy(dst_gpu_addr, &value, sizeof(value));
|
std::memcpy(dst_gpu_addr, &value, sizeof(value));
|
||||||
auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode,
|
auto request = Sync::PrepareVideoOutFlip(command, m_flip.handle, m_flip.index, m_flip.flip_mode,
|
||||||
m_flip.flip_arg);
|
m_flip.flip_arg);
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ struct GraphicContext {
|
|||||||
bool provoking_vertex_last_enabled = false;
|
bool provoking_vertex_last_enabled = false;
|
||||||
bool maintenance8_enabled = false;
|
bool maintenance8_enabled = false;
|
||||||
bool conditional_rendering_enabled = false;
|
bool conditional_rendering_enabled = false;
|
||||||
|
bool occlusion_queries_enabled = false;
|
||||||
|
bool occlusion_query_precise_enabled = false;
|
||||||
bool supports_block_texel_view = false;
|
bool supports_block_texel_view = false;
|
||||||
bool mesh_shader_enabled = false;
|
bool mesh_shader_enabled = false;
|
||||||
vk::PhysicalDeviceMeshShaderPropertiesEXT mesh_shader_properties = {};
|
vk::PhysicalDeviceMeshShaderPropertiesEXT mesh_shader_properties = {};
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
#include "graphics/host_gpu/renderer/occlusionQueries.h"
|
||||||
|
|
||||||
|
#include "graphics/host_gpu/graphicContext.h"
|
||||||
|
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace Libs::Graphics {
|
||||||
|
|
||||||
|
// Queries no dump has read yet; past them a draw counts one sample.
|
||||||
|
constexpr uint32_t QUERY_COUNT = 16384;
|
||||||
|
|
||||||
|
OcclusionQueries::OcclusionQueries(GraphicContext& graphics, CommandScheduler& scheduler)
|
||||||
|
: m_scheduler(scheduler) {
|
||||||
|
if (!graphics.occlusion_queries_enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_device = graphics.device;
|
||||||
|
const vk::QueryPoolCreateInfo info {.queryType = vk::QueryType::eOcclusion,
|
||||||
|
.queryCount = QUERY_COUNT};
|
||||||
|
RequireVulkanSuccess(m_device.createQueryPool(&info, nullptr, &m_pool), "vkCreateQueryPool");
|
||||||
|
m_device.resetQueryPool(m_pool, 0, QUERY_COUNT);
|
||||||
|
m_results.resize(QUERY_COUNT);
|
||||||
|
if (graphics.occlusion_query_precise_enabled) {
|
||||||
|
m_control = vk::QueryControlFlagBits::ePrecise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OcclusionQueries::~OcclusionQueries() {
|
||||||
|
if (m_pool) {
|
||||||
|
m_device.destroyQueryPool(m_pool, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t OcclusionQueries::BeginQuery(vk::CommandBuffer command) {
|
||||||
|
if (m_begun - m_read == QUERY_COUNT) {
|
||||||
|
m_uncounted++;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
const auto query = static_cast<uint32_t>(m_begun++ % QUERY_COUNT);
|
||||||
|
command.beginQuery(m_pool, query, m_control);
|
||||||
|
return static_cast<int32_t>(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OcclusionQueries::Dump(uint64_t address) {
|
||||||
|
// Begins dump to the results, ends 8 bytes into them.
|
||||||
|
if ((address & 8u) == 0) {
|
||||||
|
m_open++;
|
||||||
|
} else if (m_open != 0) {
|
||||||
|
m_open--;
|
||||||
|
}
|
||||||
|
m_dumps.push_back({address, m_scheduler.CurrentTick(), m_begun, m_uncounted});
|
||||||
|
}
|
||||||
|
|
||||||
|
void OcclusionQueries::PublishDumps() {
|
||||||
|
if (const auto last = m_dumps.back().queries; last != m_read) {
|
||||||
|
m_scheduler.Wait(m_dumps.back().tick);
|
||||||
|
// The unread queries wrap around the pool at most once.
|
||||||
|
for (uint64_t read = m_read; read < last;) {
|
||||||
|
const auto first = static_cast<uint32_t>(read % QUERY_COUNT);
|
||||||
|
const auto count =
|
||||||
|
static_cast<uint32_t>(std::min<uint64_t>(last - read, QUERY_COUNT - first));
|
||||||
|
RequireVulkanSuccess(
|
||||||
|
m_device.getQueryPoolResults(m_pool, first, count, count * sizeof(uint64_t),
|
||||||
|
m_results.data() + first, sizeof(uint64_t),
|
||||||
|
vk::QueryResultFlagBits::e64),
|
||||||
|
"vkGetQueryPoolResults");
|
||||||
|
m_device.resetQueryPool(m_pool, first, count);
|
||||||
|
read += count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto& dump: m_dumps) {
|
||||||
|
for (; m_read < dump.queries; m_read++) {
|
||||||
|
m_samples += m_results[m_read % QUERY_COUNT];
|
||||||
|
}
|
||||||
|
WriteZPassCounters(reinterpret_cast<volatile uint64_t*>(dump.address),
|
||||||
|
m_samples + dump.uncounted, (dump.address & 8u) == 0);
|
||||||
|
}
|
||||||
|
m_dumps.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Libs::Graphics
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_OCCLUSIONQUERIES_H_
|
||||||
|
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_OCCLUSIONQUERIES_H_
|
||||||
|
|
||||||
|
#include "common/common.h"
|
||||||
|
#include "graphics/host_gpu/vulkanCommon.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Libs::Graphics {
|
||||||
|
|
||||||
|
class CommandScheduler;
|
||||||
|
struct GraphicContext;
|
||||||
|
|
||||||
|
// The guest's query results (16-byte aligned): a begin/end pair of running z-pass counters per DB,
|
||||||
|
// 16 bytes apart; bit 63 marks a written counter. A query's samples are the sum of end - begin.
|
||||||
|
constexpr uint32_t OCCLUSION_QUERY_DBS = 16;
|
||||||
|
constexpr uint64_t OCCLUSION_QUERY_READY = 1ull << 63u;
|
||||||
|
|
||||||
|
// A dump of the running count, all of it in DB 0. A begin also clears its end counters.
|
||||||
|
inline void WriteZPassCounters(volatile uint64_t* results, uint64_t samples, bool begin) {
|
||||||
|
for (uint32_t db = 0; db < OCCLUSION_QUERY_DBS; db++) {
|
||||||
|
results[db * 2] = OCCLUSION_QUERY_READY | (db == 0 ? samples : 0);
|
||||||
|
if (begin) {
|
||||||
|
results[db * 2 + 1] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The guest's z-pass counters from host occlusion queries: a draw counts its samples while a guest
|
||||||
|
// query is open, and the dumps' counters are written before the guest can see the GPU ran them.
|
||||||
|
class OcclusionQueries {
|
||||||
|
public:
|
||||||
|
OcclusionQueries(GraphicContext& graphics, CommandScheduler& scheduler);
|
||||||
|
~OcclusionQueries();
|
||||||
|
KYTY_CLASS_NO_COPY(OcclusionQueries);
|
||||||
|
|
||||||
|
[[nodiscard]] bool Enabled() const { return m_pool != nullptr; }
|
||||||
|
|
||||||
|
// In the draw's rendering: the query that counts its samples, or -1.
|
||||||
|
[[nodiscard]] int32_t Begin(vk::CommandBuffer command) {
|
||||||
|
return m_open == 0 ? -1 : BeginQuery(command);
|
||||||
|
}
|
||||||
|
void End(vk::CommandBuffer command, int32_t query) const {
|
||||||
|
if (query >= 0) {
|
||||||
|
command.endQuery(m_pool, static_cast<uint32_t>(query));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A PIXEL_PIPE_STAT_DUMP to address, after the draws recorded so far.
|
||||||
|
void Dump(uint64_t address);
|
||||||
|
// Waits for the GPU to run the dumps and writes their counters.
|
||||||
|
void Publish() {
|
||||||
|
if (!m_dumps.empty()) {
|
||||||
|
PublishDumps();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct PendingDump {
|
||||||
|
uint64_t address;
|
||||||
|
uint64_t tick;
|
||||||
|
uint64_t queries;
|
||||||
|
uint64_t uncounted;
|
||||||
|
};
|
||||||
|
|
||||||
|
int32_t BeginQuery(vk::CommandBuffer command);
|
||||||
|
void PublishDumps();
|
||||||
|
|
||||||
|
CommandScheduler& m_scheduler;
|
||||||
|
vk::Device m_device;
|
||||||
|
vk::QueryPool m_pool;
|
||||||
|
vk::QueryControlFlags m_control;
|
||||||
|
std::vector<PendingDump> m_dumps;
|
||||||
|
std::vector<uint64_t> m_results;
|
||||||
|
uint32_t m_open = 0;
|
||||||
|
uint64_t m_begun = 0;
|
||||||
|
uint64_t m_read = 0;
|
||||||
|
uint64_t m_samples = 0;
|
||||||
|
uint64_t m_uncounted = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Libs::Graphics
|
||||||
|
|
||||||
|
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_OCCLUSIONQUERIES_H_
|
||||||
@@ -16,7 +16,7 @@ RenderContext::RenderContext(GraphicContext& graphics)
|
|||||||
m_pipeline_cache(graphics, &m_image_heap), m_sampler_cache(graphics),
|
m_pipeline_cache(graphics, &m_image_heap), m_sampler_cache(graphics),
|
||||||
m_buffer_cache(graphics, m_command_scheduler, m_page_manager, m_texture_cache),
|
m_buffer_cache(graphics, m_command_scheduler, m_page_manager, m_texture_cache),
|
||||||
m_texture_cache(graphics, m_command_scheduler, m_page_manager, m_buffer_cache),
|
m_texture_cache(graphics, m_command_scheduler, m_page_manager, m_buffer_cache),
|
||||||
m_image_heap(*this, m_page_manager) {
|
m_image_heap(*this, m_page_manager), m_occlusion_queries(graphics, m_command_scheduler) {
|
||||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "graphics/host_gpu/renderer/cache/samplerCache.h"
|
#include "graphics/host_gpu/renderer/cache/samplerCache.h"
|
||||||
#include "graphics/host_gpu/renderer/cache/textureCache.h"
|
#include "graphics/host_gpu/renderer/cache/textureCache.h"
|
||||||
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
||||||
|
#include "graphics/host_gpu/renderer/occlusionQueries.h"
|
||||||
#include "graphics/host_gpu/renderer/pipeline/descriptorHeap.h"
|
#include "graphics/host_gpu/renderer/pipeline/descriptorHeap.h"
|
||||||
#include "graphics/host_gpu/renderer/pipeline/imageHeap.h"
|
#include "graphics/host_gpu/renderer/pipeline/imageHeap.h"
|
||||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||||
@@ -49,6 +50,7 @@ public:
|
|||||||
BufferCache& GetBufferCache() { return m_buffer_cache; }
|
BufferCache& GetBufferCache() { return m_buffer_cache; }
|
||||||
TextureCache& GetTextureCache() { return m_texture_cache; }
|
TextureCache& GetTextureCache() { return m_texture_cache; }
|
||||||
RenderExecutor& GetRenderExecutor() { return m_render_executor; }
|
RenderExecutor& GetRenderExecutor() { return m_render_executor; }
|
||||||
|
OcclusionQueries& GetOcclusionQueries() { return m_occlusion_queries; }
|
||||||
|
|
||||||
[[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept;
|
[[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept;
|
||||||
[[nodiscard]] bool InvalidateMemory(uint64_t vaddr, uint64_t size);
|
[[nodiscard]] bool InvalidateMemory(uint64_t vaddr, uint64_t size);
|
||||||
@@ -79,6 +81,7 @@ private:
|
|||||||
BufferCache m_buffer_cache;
|
BufferCache m_buffer_cache;
|
||||||
TextureCache m_texture_cache;
|
TextureCache m_texture_cache;
|
||||||
ImageHeap m_image_heap;
|
ImageHeap m_image_heap;
|
||||||
|
OcclusionQueries m_occlusion_queries;
|
||||||
mutable std::shared_mutex m_mapped_ranges_mutex;
|
mutable std::shared_mutex m_mapped_ranges_mutex;
|
||||||
RangeSet m_mapped_ranges;
|
RangeSet m_mapped_ranges;
|
||||||
std::unique_ptr<GuestGpu> m_gpu;
|
std::unique_ptr<GuestGpu> m_gpu;
|
||||||
|
|||||||
@@ -1284,6 +1284,8 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
|||||||
if (!draw.IsIndexed()) {
|
if (!draw.IsIndexed()) {
|
||||||
SetDrawDebugPhase(buffer, submit_id, draw, 0x500u);
|
SetDrawDebugPhase(buffer, submit_id, draw, 0x500u);
|
||||||
}
|
}
|
||||||
|
auto& occlusion_queries = m_context.GetOcclusionQueries();
|
||||||
|
const int32_t occlusion_query = occlusion_queries.Begin(vk_buffer);
|
||||||
if (mesh_active) {
|
if (mesh_active) {
|
||||||
vk_buffer.drawMeshTasksEXT(mesh_groups, draw.instance_count, 1);
|
vk_buffer.drawMeshTasksEXT(mesh_groups, draw.instance_count, 1);
|
||||||
} else if (args_buffer.first == nullptr) {
|
} else if (args_buffer.first == nullptr) {
|
||||||
@@ -1295,6 +1297,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
|||||||
vk_buffer.drawIndirect(args_buffer.first->Handle(), args_buffer.second,
|
vk_buffer.drawIndirect(args_buffer.first->Handle(), args_buffer.second,
|
||||||
emit.indirect.count, emit.indirect.stride);
|
emit.indirect.count, emit.indirect.stride);
|
||||||
}
|
}
|
||||||
|
occlusion_queries.End(vk_buffer, occlusion_query);
|
||||||
Common::DebugCounters::Add(Common::DebugCounters::Counter::Draws);
|
Common::DebugCounters::Add(Common::DebugCounters::Counter::Draws);
|
||||||
Common::DebugCounters::Add(Common::DebugCounters::Counter::DrawInstances, draw.instance_count);
|
Common::DebugCounters::Add(Common::DebugCounters::Counter::DrawInstances, draw.instance_count);
|
||||||
|
|
||||||
|
|||||||
@@ -655,6 +655,16 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
|||||||
vk::SampleCountFlagBits::e1;
|
vk::SampleCountFlagBits::e1;
|
||||||
}
|
}
|
||||||
graphics.sample_rate_shading_enabled = true;
|
graphics.sample_rate_shading_enabled = true;
|
||||||
|
// Occlusion queries reset their queries from the host and count samples exactly where they can.
|
||||||
|
graphics.occlusion_queries_enabled =
|
||||||
|
Config::OcclusionQueriesEnabled() && supported_features12.hostQueryReset;
|
||||||
|
graphics.occlusion_query_precise_enabled =
|
||||||
|
graphics.occlusion_queries_enabled && supported_features2.features.occlusionQueryPrecise;
|
||||||
|
features12.hostQueryReset = graphics.occlusion_queries_enabled;
|
||||||
|
device_features.occlusionQueryPrecise = graphics.occlusion_query_precise_enabled;
|
||||||
|
LOGF("Vulkan occlusion queries: %s, precise: %s\n",
|
||||||
|
graphics.occlusion_queries_enabled ? "true" : "false",
|
||||||
|
graphics.occlusion_query_precise_enabled ? "true" : "false");
|
||||||
device_features.shaderInt64 = VK_TRUE;
|
device_features.shaderInt64 = VK_TRUE;
|
||||||
device_features.shaderFloat64 =
|
device_features.shaderFloat64 =
|
||||||
supported_features2.features.shaderFloat64 &&
|
supported_features2.features.shaderFloat64 &&
|
||||||
|
|||||||
@@ -180,6 +180,12 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="8" column="1">
|
<item row="8" column="1">
|
||||||
|
<widget class="QCheckBox" name="checkBox_occlusion_queries">
|
||||||
|
<property name="toolTip"><string>Count the samples of occlusion queries on the GPU, so hidden lights and objects stay hidden. Costs some performance.</string></property>
|
||||||
|
<property name="text"><string>Enable occlusion queries</string></property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="9" column="1">
|
||||||
<widget class="QCheckBox" name="checkBox_hdr">
|
<widget class="QCheckBox" name="checkBox_hdr">
|
||||||
<property name="toolTip"><string>Enable HDR10 output for supported games. Requires HDR enabled on the host display.</string></property>
|
<property name="toolTip"><string>Enable HDR10 output for supported games. Requires HDR enabled on the host display.</string></property>
|
||||||
<property name="text"><string>Enable HDR display support</string></property>
|
<property name="text"><string>Enable HDR display support</string></property>
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ public:
|
|||||||
bool readback_tiled_images = false;
|
bool readback_tiled_images = false;
|
||||||
bool sync_raw_image_buffers = false;
|
bool sync_raw_image_buffers = false;
|
||||||
bool tessellation_enabled = false;
|
bool tessellation_enabled = false;
|
||||||
|
bool occlusion_queries_enabled = false;
|
||||||
bool trophy_enabled = true;
|
bool trophy_enabled = true;
|
||||||
int trophy_duration_seconds = 6;
|
int trophy_duration_seconds = 6;
|
||||||
bool perf_overlay_enabled = false;
|
bool perf_overlay_enabled = false;
|
||||||
@@ -169,6 +170,7 @@ public:
|
|||||||
readback_tiled_images = other.readback_tiled_images;
|
readback_tiled_images = other.readback_tiled_images;
|
||||||
sync_raw_image_buffers = other.sync_raw_image_buffers;
|
sync_raw_image_buffers = other.sync_raw_image_buffers;
|
||||||
tessellation_enabled = other.tessellation_enabled;
|
tessellation_enabled = other.tessellation_enabled;
|
||||||
|
occlusion_queries_enabled = other.occlusion_queries_enabled;
|
||||||
trophy_enabled = other.trophy_enabled;
|
trophy_enabled = other.trophy_enabled;
|
||||||
trophy_duration_seconds = other.trophy_duration_seconds;
|
trophy_duration_seconds = other.trophy_duration_seconds;
|
||||||
perf_overlay_enabled = other.perf_overlay_enabled;
|
perf_overlay_enabled = other.perf_overlay_enabled;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ QVariantMap Configuration::GameSettings() const {
|
|||||||
KYTY_CFG_SET(readback_tiled_images);
|
KYTY_CFG_SET(readback_tiled_images);
|
||||||
KYTY_CFG_SET(sync_raw_image_buffers);
|
KYTY_CFG_SET(sync_raw_image_buffers);
|
||||||
KYTY_CFG_SET(tessellation_enabled);
|
KYTY_CFG_SET(tessellation_enabled);
|
||||||
|
KYTY_CFG_SET(occlusion_queries_enabled);
|
||||||
KYTY_CFG_SET(trophy_enabled);
|
KYTY_CFG_SET(trophy_enabled);
|
||||||
KYTY_CFG_SET(trophy_duration_seconds);
|
KYTY_CFG_SET(trophy_duration_seconds);
|
||||||
KYTY_CFG_SET(perf_overlay_enabled);
|
KYTY_CFG_SET(perf_overlay_enabled);
|
||||||
@@ -66,6 +67,7 @@ void Configuration::ReadGameSettingsValues(const Settings& s) {
|
|||||||
KYTY_CFG_GET(readback_tiled_images);
|
KYTY_CFG_GET(readback_tiled_images);
|
||||||
KYTY_CFG_GET(sync_raw_image_buffers);
|
KYTY_CFG_GET(sync_raw_image_buffers);
|
||||||
KYTY_CFG_GET(tessellation_enabled);
|
KYTY_CFG_GET(tessellation_enabled);
|
||||||
|
KYTY_CFG_GET(occlusion_queries_enabled);
|
||||||
trophy_enabled = s.value("trophy_enabled", trophy_enabled).toBool();
|
trophy_enabled = s.value("trophy_enabled", trophy_enabled).toBool();
|
||||||
KYTY_CFG_GET(trophy_duration_seconds);
|
KYTY_CFG_GET(trophy_duration_seconds);
|
||||||
KYTY_CFG_GET(perf_overlay_enabled);
|
KYTY_CFG_GET(perf_overlay_enabled);
|
||||||
|
|||||||
@@ -289,6 +289,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
|
|||||||
m_ui->checkBox_readback_tiled->setChecked(info.readback_tiled_images);
|
m_ui->checkBox_readback_tiled->setChecked(info.readback_tiled_images);
|
||||||
m_ui->checkBox_sync_raw_image_buffers->setChecked(info.sync_raw_image_buffers);
|
m_ui->checkBox_sync_raw_image_buffers->setChecked(info.sync_raw_image_buffers);
|
||||||
m_ui->checkBox_tessellation->setChecked(info.tessellation_enabled);
|
m_ui->checkBox_tessellation->setChecked(info.tessellation_enabled);
|
||||||
|
m_ui->checkBox_occlusion_queries->setChecked(info.occlusion_queries_enabled);
|
||||||
m_ui->checkBox_skip_notice_screen->setChecked(info.skip_notice_screen);
|
m_ui->checkBox_skip_notice_screen->setChecked(info.skip_notice_screen);
|
||||||
m_ui->checkBox_network_connected->setChecked(info.network_connected);
|
m_ui->checkBox_network_connected->setChecked(info.network_connected);
|
||||||
m_ui->checkBox_trophy_notifications->setChecked(info.trophy_enabled);
|
m_ui->checkBox_trophy_notifications->setChecked(info.trophy_enabled);
|
||||||
@@ -483,6 +484,7 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui, boo
|
|||||||
info.readback_tiled_images = ui.checkBox_readback_tiled->isChecked();
|
info.readback_tiled_images = ui.checkBox_readback_tiled->isChecked();
|
||||||
info.sync_raw_image_buffers = ui.checkBox_sync_raw_image_buffers->isChecked();
|
info.sync_raw_image_buffers = ui.checkBox_sync_raw_image_buffers->isChecked();
|
||||||
info.tessellation_enabled = ui.checkBox_tessellation->isChecked();
|
info.tessellation_enabled = ui.checkBox_tessellation->isChecked();
|
||||||
|
info.occlusion_queries_enabled = ui.checkBox_occlusion_queries->isChecked();
|
||||||
info.skip_notice_screen = ui.checkBox_skip_notice_screen->isChecked();
|
info.skip_notice_screen = ui.checkBox_skip_notice_screen->isChecked();
|
||||||
info.network_connected = ui.checkBox_network_connected->isChecked();
|
info.network_connected = ui.checkBox_network_connected->isChecked();
|
||||||
info.trophy_enabled = ui.checkBox_trophy_notifications->isChecked();
|
info.trophy_enabled = ui.checkBox_trophy_notifications->isChecked();
|
||||||
|
|||||||
@@ -287,6 +287,7 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
|
|||||||
AddFlag(args, info.skip_notice_screen, "--skip-notice-screen");
|
AddFlag(args, info.skip_notice_screen, "--skip-notice-screen");
|
||||||
AddFlag(args, info.network_connected, "--network-connected");
|
AddFlag(args, info.network_connected, "--network-connected");
|
||||||
AddFlag(args, info.tessellation_enabled, "--tessellation");
|
AddFlag(args, info.tessellation_enabled, "--tessellation");
|
||||||
|
AddFlag(args, info.occlusion_queries_enabled, "--occlusion-queries");
|
||||||
args << "--vblank-frequency" << QString::number(info.vblank_frequency);
|
args << "--vblank-frequency" << QString::number(info.vblank_frequency);
|
||||||
args << "--console-language" << QString::number(info.console_language);
|
args << "--console-language" << QString::number(info.console_language);
|
||||||
AddFlag(args, info.vulkan_validation_enabled, "--vulkan-validation");
|
AddFlag(args, info.vulkan_validation_enabled, "--vulkan-validation");
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ static void PrintUsage() {
|
|||||||
" Implies --vulkan-validation; very slow.\n");
|
" Implies --vulkan-validation; very slow.\n");
|
||||||
::printf(" --shader-validation Enable shader validation.\n");
|
::printf(" --shader-validation Enable shader validation.\n");
|
||||||
::printf(" --tessellation Draw tessellation patches; skipped by default.\n");
|
::printf(" --tessellation Draw tessellation patches; skipped by default.\n");
|
||||||
|
::printf(" --occlusion-queries Count occlusion query samples on the GPU.\n");
|
||||||
::printf(" --shader-optimization-type <value> None, Size, or Performance.\n");
|
::printf(" --shader-optimization-type <value> None, Size, or Performance.\n");
|
||||||
::printf(" --shader-log-direction <value> Silent, Console, or File.\n");
|
::printf(" --shader-log-direction <value> Silent, Console, or File.\n");
|
||||||
::printf(" --shader-log-folder <path> Shader log output folder.\n");
|
::printf(" --shader-log-folder <path> Shader log output folder.\n");
|
||||||
@@ -202,6 +203,7 @@ static constexpr Flag FLAGS[] = {
|
|||||||
{"--gpu-assisted-validation", &ConfigOptions::gpu_assisted_validation_enabled},
|
{"--gpu-assisted-validation", &ConfigOptions::gpu_assisted_validation_enabled},
|
||||||
{"--shader-validation", &ConfigOptions::shader_validation_enabled},
|
{"--shader-validation", &ConfigOptions::shader_validation_enabled},
|
||||||
{"--tessellation", &ConfigOptions::tessellation_enabled},
|
{"--tessellation", &ConfigOptions::tessellation_enabled},
|
||||||
|
{"--occlusion-queries", &ConfigOptions::occlusion_queries_enabled},
|
||||||
{"--command-buffer-dump", &ConfigOptions::command_buffer_dump_enabled},
|
{"--command-buffer-dump", &ConfigOptions::command_buffer_dump_enabled},
|
||||||
{"--graphics-debug-dump", &ConfigOptions::graphics_debug_dump_enabled},
|
{"--graphics-debug-dump", &ConfigOptions::graphics_debug_dump_enabled},
|
||||||
{"--profile", &ConfigOptions::profiler_enabled},
|
{"--profile", &ConfigOptions::profiler_enabled},
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
#include "graphics/host_gpu/renderer/occlusionQueries.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
using namespace Libs::Graphics;
|
||||||
|
|
||||||
|
#define CHECK(condition) \
|
||||||
|
do { \
|
||||||
|
if (!(condition)) { \
|
||||||
|
std::fprintf(stderr, "OcclusionQueriesTests:%d: %s\n", __LINE__, #condition); \
|
||||||
|
std::abort(); \
|
||||||
|
} \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
using Results = std::array<uint64_t, OCCLUSION_QUERY_DBS * 2>;
|
||||||
|
|
||||||
|
// The samples a query passed, as ZPass predication sums them; -1 before every counter is written.
|
||||||
|
int64_t Passed(const Results& results) {
|
||||||
|
int64_t samples = 0;
|
||||||
|
for (uint32_t db = 0; db < OCCLUSION_QUERY_DBS; db++) {
|
||||||
|
const auto begin = results[db * 2];
|
||||||
|
const auto end = results[db * 2 + 1];
|
||||||
|
if ((begin & end & OCCLUSION_QUERY_READY) == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
samples += static_cast<int64_t>(end - begin);
|
||||||
|
}
|
||||||
|
return samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCounters() {
|
||||||
|
Results results {};
|
||||||
|
WriteZPassCounters(results.data(), 100, true);
|
||||||
|
CHECK(Passed(results) == -1);
|
||||||
|
WriteZPassCounters(results.data() + 1, 100, false);
|
||||||
|
CHECK(Passed(results) == 0);
|
||||||
|
WriteZPassCounters(results.data() + 1, 142, false);
|
||||||
|
CHECK(Passed(results) == 42);
|
||||||
|
CHECK(results[0] == (OCCLUSION_QUERY_READY | 100) && results[30] == OCCLUSION_QUERY_READY);
|
||||||
|
|
||||||
|
// A begin clears the end a previous query wrote late, so the query is not ready until its own.
|
||||||
|
WriteZPassCounters(results.data(), 142, true);
|
||||||
|
CHECK(Passed(results) == -1);
|
||||||
|
WriteZPassCounters(results.data() + 1, 150, false);
|
||||||
|
CHECK(Passed(results) == 8);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
TestCounters();
|
||||||
|
std::puts("Occlusion query tests passed");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user