aboutsummaryrefslogtreecommitdiff
path: root/src/video_core
diff options
context:
space:
mode:
Diffstat (limited to 'src/video_core')
-rw-r--r--src/video_core/CMakeLists.txt1
-rw-r--r--src/video_core/gpu_thread.cpp15
-rw-r--r--src/video_core/nx2_profile.h149
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp109
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp30
-rw-r--r--src/video_core/texture_cache/texture_cache.h2
6 files changed, 260 insertions, 46 deletions
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index d17deacacd..513a644166 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -103,6 +103,7 @@ add_library(video_core STATIC
native_qmd.h
native_query_trace.h
nx2.h
+ nx2_profile.h
present.h
pte_kind.h
query_cache/bank_base.h
diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp
index 793e8e9eeb..cf4e159806 100644
--- a/src/video_core/gpu_thread.cpp
+++ b/src/video_core/gpu_thread.cpp
@@ -17,6 +17,7 @@
#include "video_core/dma_pusher.h"
#include "video_core/gpu.h"
#include "video_core/gpu_thread.h"
+#include "video_core/nx2_profile.h"
#include "video_core/host1x/host1x.h"
#include "video_core/renderer_base.h"
@@ -38,29 +39,41 @@ void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Fronten
auto current_context = context.Acquire();
CommandDataContainer next;
+ namespace Profile = VideoCommon::Nx2Profile;
while (!stop_token.stop_requested()) {
+ Profile::Report();
if (scheduler.HasPendingChannels()) {
// Suspended channels wait on semaphores the CPU releases: poll them meanwhile.
- scheduler.ResumePending(system.GPU());
+ {
+ Profile::Scope scope{Profile::Zone::ResumePending};
+ scheduler.ResumePending(system.GPU());
+ }
if (!state.queue.TryPop(next)) {
+ Profile::Scope scope{Profile::Zone::PendingSleep};
std::this_thread::sleep_for(std::chrono::milliseconds{1});
continue;
}
} else {
+ Profile::Scope scope{Profile::Zone::Idle};
state.queue.PopWait(next, stop_token);
}
if (stop_token.stop_requested()) {
break;
}
if (auto* submit_list = std::get_if<SubmitListCommand>(&next.data)) {
+ Profile::Scope scope{Profile::Zone::Push};
scheduler.Push(system.GPU(), submit_list->channel, std::move(submit_list->entries));
} else if (std::holds_alternative<GPUTickCommand>(next.data)) {
+ Profile::Scope scope{Profile::Zone::Tick};
system.GPU().TickWork();
} else if (auto* run = std::get_if<RunCommand>(&next.data)) {
+ Profile::Scope scope{Profile::Zone::Run};
run->func();
} else if (const auto* flush = std::get_if<FlushRegionCommand>(&next.data)) {
+ Profile::Scope scope{Profile::Zone::FlushInvalidate};
renderer.ReadRasterizer()->FlushRegion(flush->addr, flush->size);
} else if (const auto* invalidate = std::get_if<InvalidateRegionCommand>(&next.data)) {
+ Profile::Scope scope{Profile::Zone::FlushInvalidate};
renderer.ReadRasterizer()->OnCacheInvalidation(invalidate->addr, invalidate->size);
} else {
ASSERT(false);
diff --git a/src/video_core/nx2_profile.h b/src/video_core/nx2_profile.h
new file mode 100644
index 0000000000..051403c982
--- /dev/null
+++ b/src/video_core/nx2_profile.h
@@ -0,0 +1,149 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <chrono>
+#include <cstdlib>
+#include <string>
+
+#include "common/common_types.h"
+#include "common/logging.h"
+
+// EDEN_NX2_GPU_PROFILE: time where the GPU thread spends its time. Each zone counts its own
+// (exclusive) time, nested zones excluded; every 10 s the thread logs each zone's share.
+namespace VideoCommon::Nx2Profile {
+
+enum class Zone : u32 {
+ Other, ///< GPU thread time outside every zone below (register writes, command parsing)
+ Idle,
+ PendingSleep,
+ ResumePending,
+ Push,
+ Tick,
+ Run,
+ FlushInvalidate,
+ Macro,
+ Draw,
+ Clear,
+ Compute,
+ FlushWork,
+ PipelineLookup,
+ Configure,
+ SyncDescriptors,
+ ReadHandles,
+ FillImageViews,
+ TextureBuffers,
+ UpdateBuffers,
+ DescriptorAcquire,
+ BindStageBuffers,
+ PushDescriptors,
+ RenderTargets,
+ ConfigureDraw,
+ DynamicState,
+ DrawRecord,
+ TextureUpload,
+ Count,
+};
+
+inline constexpr std::array<const char*, static_cast<size_t>(Zone::Count)> zone_names{
+ "other", "idle", "pending sleep", "resume pending", "push",
+ "tick", "run", "flush/inval", "macro", "draw",
+ "clear", "compute", "flush work", "pipeline lookup", "configure",
+ "sync descriptors", "read handles", "fill image views", "texture buffers", "update buffers",
+ "descriptor acquire", "bind stage buffers", "push descriptors", "render targets",
+ "configure draw", "dynamic state", "draw record", "texture upload",
+};
+
+inline bool Enabled() {
+ static const bool enabled = std::getenv("EDEN_NX2_GPU_PROFILE") != nullptr;
+ return enabled;
+}
+
+inline u64 Now() {
+ return static_cast<u64>(std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::steady_clock::now().time_since_epoch())
+ .count());
+}
+
+struct ThreadData {
+ std::array<u64, static_cast<size_t>(Zone::Count)> exclusive_ns{};
+ std::array<u64, static_cast<size_t>(Zone::Count)> count{};
+ u64 child_ns{}; ///< Time of the zones nested in the current one
+ u64 window_start{};
+};
+
+inline thread_local ThreadData t_data;
+
+class Scope {
+public:
+ explicit Scope(Zone zone_) : zone{zone_}, enabled{Enabled()} {
+ if (!enabled) {
+ return;
+ }
+ parent_child_ns = t_data.child_ns;
+ t_data.child_ns = 0;
+ start = Now();
+ }
+
+ ~Scope() {
+ if (!enabled) {
+ return;
+ }
+ const u64 elapsed = Now() - start;
+ auto& data = t_data;
+ const auto index = static_cast<size_t>(zone);
+ data.exclusive_ns[index] += elapsed - data.child_ns;
+ ++data.count[index];
+ data.child_ns = parent_child_ns + elapsed;
+ }
+
+ Scope(const Scope&) = delete;
+ Scope& operator=(const Scope&) = delete;
+
+private:
+ Zone zone;
+ bool enabled;
+ u64 start{};
+ u64 parent_child_ns{};
+};
+
+/// Called by the GPU thread between commands: logs and resets the totals every 10 s.
+inline void Report() {
+ if (!Enabled()) {
+ return;
+ }
+ auto& data = t_data;
+ const u64 now = Now();
+ if (data.window_start == 0) {
+ data.window_start = now;
+ data.child_ns = 0;
+ return;
+ }
+ const u64 window = now - data.window_start;
+ if (window < 10'000'000'000ULL) {
+ return;
+ }
+ // Time in no zone at all is the wall time minus the zones' totals.
+ u64 zoned{};
+ for (size_t i = 1; i < data.exclusive_ns.size(); ++i) {
+ zoned += data.exclusive_ns[i];
+ }
+ data.exclusive_ns[0] = window > zoned ? window - zoned : 0;
+ std::string line;
+ for (size_t i = 0; i < data.exclusive_ns.size(); ++i) {
+ if (data.exclusive_ns[i] == 0 && data.count[i] == 0) {
+ continue;
+ }
+ // Per second of wall time: milliseconds spent and calls made.
+ const double ms = static_cast<double>(data.exclusive_ns[i]) * 1000.0 / window;
+ const double calls = static_cast<double>(data.count[i]) * 1e9 / window;
+ line += fmt::format(" | {} {:.1f} ms ({:.0f}/s)", zone_names[i], ms, calls);
+ }
+ LOG_INFO(HW_GPU, "GPU thread per second:{}", line);
+ data = {};
+ data.window_start = now;
+}
+
+} // namespace VideoCommon::Nx2Profile
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index 172ee4dcaa..f737ad9f70 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -29,6 +29,7 @@
#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/gpu_logging/gpu_logging.h"
#include "common/settings.h"
+#include "video_core/nx2_profile.h"
#if defined(_MSC_VER) && defined(NDEBUG)
#define LAMBDA_FORCEINLINE [[msvc::forceinline]]
@@ -355,7 +356,11 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
views.reserve(num_image_elements);
samplers.reserve(num_textures);
- texture_cache.SynchronizeDescriptors(false);
+ namespace Profile = VideoCommon::Nx2Profile;
+ {
+ Profile::Scope scope{Profile::Zone::SyncDescriptors};
+ texture_cache.SynchronizeDescriptors(false);
+ }
buffer_cache.SetUniformBuffersState(enabled_uniform_buffer_masks, &uniform_buffer_sizes);
@@ -432,24 +437,31 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
return true;
}};
- if constexpr (Spec::enabled_stages[0]) {
- config_stage(0);
- }
- if constexpr (Spec::enabled_stages[1]) {
- config_stage(1);
- }
- if constexpr (Spec::enabled_stages[2]) {
- config_stage(2);
- }
- if constexpr (Spec::enabled_stages[3]) {
- config_stage(3);
- }
- if constexpr (Spec::enabled_stages[4]) {
- config_stage(4);
+ {
+ Profile::Scope scope{Profile::Zone::ReadHandles};
+ if constexpr (Spec::enabled_stages[0]) {
+ config_stage(0);
+ }
+ if constexpr (Spec::enabled_stages[1]) {
+ config_stage(1);
+ }
+ if constexpr (Spec::enabled_stages[2]) {
+ config_stage(2);
+ }
+ if constexpr (Spec::enabled_stages[3]) {
+ config_stage(3);
+ }
+ if constexpr (Spec::enabled_stages[4]) {
+ config_stage(4);
+ }
}
ASSERT(views.size() == num_image_elements);
ASSERT(samplers.size() == num_textures);
- texture_cache.FillImageViews(std::span(views.data(), views.size()), false, Spec::has_images);
+ {
+ Profile::Scope scope{Profile::Zone::FillImageViews};
+ texture_cache.FillImageViews(std::span(views.data(), views.size()), false,
+ Spec::has_images);
+ }
VideoCommon::ImageViewInOut* texture_buffer_it{views.data()};
const auto bind_stage_info{[&](size_t stage) LAMBDA_FORCEINLINE {
@@ -493,39 +505,54 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
texture_buffer_it += Shader::NumDescriptors(info.image_descriptors);
}
}};
- if constexpr (Spec::enabled_stages[0]) {
- bind_stage_info(0);
- }
- if constexpr (Spec::enabled_stages[1]) {
- bind_stage_info(1);
- }
- if constexpr (Spec::enabled_stages[2]) {
- bind_stage_info(2);
- }
- if constexpr (Spec::enabled_stages[3]) {
- bind_stage_info(3);
- }
- if constexpr (Spec::enabled_stages[4]) {
- bind_stage_info(4);
+ {
+ Profile::Scope scope{Profile::Zone::TextureBuffers};
+ if constexpr (Spec::enabled_stages[0]) {
+ bind_stage_info(0);
+ }
+ if constexpr (Spec::enabled_stages[1]) {
+ bind_stage_info(1);
+ }
+ if constexpr (Spec::enabled_stages[2]) {
+ bind_stage_info(2);
+ }
+ if constexpr (Spec::enabled_stages[3]) {
+ bind_stage_info(3);
+ }
+ if constexpr (Spec::enabled_stages[4]) {
+ bind_stage_info(4);
+ }
}
if (regs.transform_feedback_enabled != 0) {
scheduler.RequestOutsideRenderPassOperationContext();
}
- buffer_cache.UpdateGraphicsBuffers(is_indexed);
- buffer_cache.BindHostGeometryBuffers(is_indexed);
+ {
+ Profile::Scope scope{Profile::Zone::UpdateBuffers};
+ buffer_cache.UpdateGraphicsBuffers(is_indexed);
+ buffer_cache.BindHostGeometryBuffers(is_indexed);
+ }
- guest_descriptor_queue.Acquire(scheduler, num_descriptor_entries, uses_descriptor_buffer);
+ {
+ Profile::Scope scope{Profile::Zone::DescriptorAcquire};
+ guest_descriptor_queue.Acquire(scheduler, num_descriptor_entries, uses_descriptor_buffer);
+ }
RescalingPushConstant rescaling;
RenderAreaPushConstant render_area;
const VideoCommon::SamplerId* samplers_it{samplers.data()};
const VideoCommon::ImageViewInOut* views_it{views.data()};
const auto prepare_stage{[&](size_t stage) LAMBDA_FORCEINLINE {
- buffer_cache.BindHostStageBuffers(stage);
- PushImageDescriptors(texture_cache, guest_descriptor_queue, stage_infos[stage], rescaling,
- samplers_it, views_it);
+ {
+ Profile::Scope scope{Profile::Zone::BindStageBuffers};
+ buffer_cache.BindHostStageBuffers(stage);
+ }
+ {
+ Profile::Scope scope{Profile::Zone::PushDescriptors};
+ PushImageDescriptors(texture_cache, guest_descriptor_queue, stage_infos[stage],
+ rescaling, samplers_it, views_it);
+ }
const auto& info{stage_infos[stage]};
if (info.uses_render_area) {
render_area.uses_render_area = true;
@@ -555,12 +582,16 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) {
buffer_cache.runtime.PostCopyBarrier();
buffer_cache.any_buffer_uploaded = false;
}
- texture_cache.UpdateRenderTargets(false);
- texture_cache.CheckFeedbackLoop(std::span<const VideoCommon::ImageViewInOut>{views.data(),
- views.size()});
+ {
+ Profile::Scope scope{Profile::Zone::RenderTargets};
+ texture_cache.UpdateRenderTargets(false);
+ texture_cache.CheckFeedbackLoop(
+ std::span<const VideoCommon::ImageViewInOut>{views.data(), views.size()});
+ }
if (IsBuilt() && !pipeline) {
return false;
}
+ Profile::Scope scope{Profile::Zone::ConfigureDraw};
return ConfigureDraw(rescaling, render_area);
}
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 609ac12fb4..decfb0d136 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -49,6 +49,7 @@
#include "video_core/texture_cache/texture_cache_base.h"
#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
+#include "video_core/nx2_profile.h"
namespace Vulkan {
@@ -253,8 +254,12 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
LOG_DEBUG(HW_GPU, "Native graphics draw failed; refusing submission completion");
}
};
- FlushWork();
- gpu_memory->FlushCaching();
+ namespace Profile = VideoCommon::Nx2Profile;
+ {
+ Profile::Scope scope{Profile::Zone::FlushWork};
+ FlushWork();
+ gpu_memory->FlushCaching();
+ }
const auto report_failure = [&](std::string_view reason) {
static std::set<std::tuple<u64, u64, std::string_view>> seen;
@@ -266,7 +271,10 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
regs.rt[0].Address(), regs.rt[0].width, regs.rt[0].height);
}
};
- GraphicsPipeline* const pipeline{pipeline_cache.CurrentGraphicsPipeline()};
+ GraphicsPipeline* const pipeline{[&] {
+ Profile::Scope scope{Profile::Zone::PipelineLookup};
+ return pipeline_cache.CurrentGraphicsPipeline();
+ }()};
if (!pipeline) {
report_failure("no pipeline");
return;
@@ -274,17 +282,23 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
// update engine as channel may be different.
pipeline->SetEngine(maxwell3d, gpu_memory);
- if (!pipeline->Configure(is_indexed)) {
+ if (Profile::Scope scope{Profile::Zone::Configure}; !pipeline->Configure(is_indexed)) {
report_failure("configure failed");
return;
}
- UpdateDynamicStates();
+ {
+ Profile::Scope scope{Profile::Zone::DynamicState};
+ UpdateDynamicStates();
+ }
query_cache.NotifySegment(true);
HandleTransformFeedback();
query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64, maxwell3d->regs.zpass_pixel_count_enable);
- draw_func();
+ {
+ Profile::Scope scope{Profile::Zone::DrawRecord};
+ draw_func();
+ }
submitted = true;
static u32 native_capture_count{};
if (maxwell3d->GetNativeMacroEngine().native_turing &&
@@ -412,6 +426,7 @@ void TraceWindowTarget(const Tegra::Engines::Maxwell3D& maxwell3d, Tegra::Memory
} // Anonymous namespace
void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) {
+ VideoCommon::Nx2Profile::Scope profile_scope{VideoCommon::Nx2Profile::Zone::Draw};
TraceWindowTarget(*maxwell3d, *gpu_memory, is_indexed ? "draw-indexed" : "draw",
is_indexed ? maxwell3d->draw_manager.draw_state.index_buffer.count
: maxwell3d->draw_manager.draw_state.vertex_buffer.count);
@@ -554,6 +569,7 @@ void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) {
}
void RasterizerVulkan::DrawIndirect() {
+ VideoCommon::Nx2Profile::Scope profile_scope{VideoCommon::Nx2Profile::Zone::Draw};
const auto& params = maxwell3d->draw_manager.indirect_state;
buffer_cache.SetDrawIndirect(&params);
PrepareDraw(params.is_indexed, [this, &params] {
@@ -659,6 +675,7 @@ void RasterizerVulkan::DrawTexture() {
}
void RasterizerVulkan::Clear(u32 layer_count) {
+ VideoCommon::Nx2Profile::Scope profile_scope{VideoCommon::Nx2Profile::Zone::Clear};
if (Tegra::Engines::g_nx2_trace_draws_after_upscale > 0) {
const auto& regs = maxwell3d->regs;
LOG_INFO(HW_GPU, "after upscale: clear rt0 {:#x} {}x{} fmt {:#x} surface {:#x} colour {} {} {} {}",
@@ -852,6 +869,7 @@ void RasterizerVulkan::Clear(u32 layer_count) {
}
void RasterizerVulkan::DispatchCompute() {
+ VideoCommon::Nx2Profile::Scope profile_scope{VideoCommon::Nx2Profile::Zone::Compute};
FlushWork();
gpu_memory->FlushCaching();
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 75df31dc27..ad1804877a 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -28,6 +28,7 @@
#include "video_core/texture_cache/samples_helper.h"
#include "video_core/texture_cache/texture_cache_base.h"
#include "video_core/texture_cache/util.h"
+#include "video_core/nx2_profile.h"
#include "video_core/textures/decoders.h"
namespace VideoCommon {
@@ -1251,6 +1252,7 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) {
// Only upload modified images
return;
}
+ VideoCommon::Nx2Profile::Scope profile_scope{VideoCommon::Nx2Profile::Zone::TextureUpload};
image.flags &= ~ImageFlagBits::CpuModified;