mirror of
https://github.com/ayozetr/bloodborne_linux.git
synced 2026-11-11 01:11:44 -10:00
+24









deadinside
Qiming-Liu
AndiMajore
adds39939
iannitram
maiconjsv
whp199
DominickCobb-rs
Sarxzer
wheat32
ArenaMMORPG
SrShadowy
Gustavo Bomfim
Teefpyre
SrDeTs
IgorWarzocha
itachi-re
Bainianzzz
hideoneric
guitaripod
unTanana
andrR89
ayozetr
JuanraCM
SaeedShow
msisovic
axpico
Aryxa
Obrikash
noelmiller
AyrtonSanabio
Supermedo
Awaken
Claude Opus 5.5
0fc2921c45
- Windows 10/11: native bb-probe.exe (llvm-mingw / MSYS2 CLANG64), WPF launcher in five languages; online module online\bbnet20.dll with both pools (Seamless Hunt + BBSC, The Hunter's Dream with Discord sign-in) and BBSC's local game server; mods and XML patches from the launcher (junctions and hard links without Developer Mode); DLSS / DLSS FG, FSR 4.1.1 and FSR 4.1.2 driver models, FSR 4 from AMD's SDK 2.4 DLLs; NVAPI power profile on NVIDIA. - The old memory model of 0.3 removed; the game's graphics heaps grow on demand. - Faster: occlusion queries counted only inside the game's open queries (164 -> 189 FPS in the heaviest view), ring dedup and copy-shader filter (less PCIe traffic), PGO retrained, Steam Deck draw preparation, FSR 3.1 FG shaders. - Fixes: cloth fluttering above 30 FPS (#58), point-light shadow wedges, HUD item-icon grid, vertex explosions across heap pieces, FPS drop after a save switch, FG ghosts, crash 0x2169edc (#336), FSR 3.1 protected memory (#85), Wayland HiDPI, launcher translations (#381). - New: AA menu (FXAA2_GS, SMAA), FSR 1, GT-VBAO, AMD Anti-Lag 2, trophies.jsonl, shader recipes before play, live resolution by default. - Packages: Windows zip, AppImage, and a Linux folder (tar.zst) with the online module open. - Removed: the shadp2p online module, LEGO Dimensions toy pad and NGS2 code. - Credits: docs/CONTRIBUTORS_0.7.md (pull requests since 0.4 and outside work). Co-authored-by: Qiming-Liu <[email protected]> Co-authored-by: AndiMajore <[email protected]> Co-authored-by: adds39939 <[email protected]> Co-authored-by: iannitram <[email protected]> Co-authored-by: maiconjsv <[email protected]> Co-authored-by: whp199 <[email protected]> Co-authored-by: DominickCobb-rs <[email protected]> Co-authored-by: Sarxzer <[email protected]> Co-authored-by: wheat32 <[email protected]> Co-authored-by: ArenaMMORPG <[email protected]> Co-authored-by: SrShadowy <[email protected]> Co-authored-by: Gustavo Bomfim <[email protected]> Co-authored-by: Teefpyre <[email protected]> Co-authored-by: SrDeTs <[email protected]> Co-authored-by: IgorWarzocha <[email protected]> Co-authored-by: itachi-re <[email protected]> Co-authored-by: Bainianzzz <[email protected]> Co-authored-by: hideoneric <[email protected]> Co-authored-by: guitaripod <[email protected]> Co-authored-by: unTanana <[email protected]> Co-authored-by: andrR89 <[email protected]> Co-authored-by: ayozetr <[email protected]> Co-authored-by: JuanraCM <[email protected]> Co-authored-by: SaeedShow <[email protected]> Co-authored-by: msisovic <[email protected]> Co-authored-by: axpico <[email protected]> Co-authored-by: Aryxa <[email protected]> Co-authored-by: Obrikash <[email protected]> Co-authored-by: noelmiller <[email protected]> Co-authored-by: AyrtonSanabio <[email protected]> Co-authored-by: Supermedo <[email protected]> Co-authored-by: Awaken <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]>
168 lines
6.6 KiB
C++
168 lines
6.6 KiB
C++
// SPDX-FileCopyrightText: Copyright 2026 bbport contributors
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include <algorithm>
|
|
#include "shim/bb_env.h"
|
|
#include <array>
|
|
#include <cstdlib>
|
|
|
|
#include "render/vk_gpu_profiler.h"
|
|
#include "vulkan/instance.h"
|
|
#include "render/scheduler.h"
|
|
|
|
namespace Vulkan {
|
|
|
|
void GpuProfiler::Init(const Instance& instance, Scheduler& scheduler) {
|
|
const char* env = bb_env("BB_GPU_PROFILE");
|
|
if (!env || env[0] != '1' || instance_ptr) {
|
|
return;
|
|
}
|
|
instance_ptr = new GpuProfiler(instance, scheduler);
|
|
}
|
|
|
|
GpuProfiler::GpuProfiler(const Instance& instance, Scheduler& scheduler_)
|
|
: device{instance.GetDevice()}, scheduler{scheduler_} {
|
|
const vk::QueryPoolCreateInfo info = {
|
|
.queryType = vk::QueryType::eTimestamp,
|
|
.queryCount = NumSlices * SliceQueries,
|
|
};
|
|
pool = Check<"create timestamp query pool">(device.createQueryPoolUnique(info));
|
|
device.resetQueryPool(*pool, 0, NumSlices * SliceQueries);
|
|
period_ns = instance.GetPhysicalDevice().getProperties().limits.timestampPeriod;
|
|
for (auto& k : keys) {
|
|
k.reserve(SliceQueries);
|
|
}
|
|
std::printf("GPU profile: on (timestamp period %.2f ns)\n", period_ns);
|
|
}
|
|
|
|
void GpuProfiler::WriteTimestamp(u64 key, bool end_rendering) {
|
|
if (used[slice] + 1 >= SliceQueries) {
|
|
return; // the frame's slice is full: the rest of the frame goes to the last label
|
|
}
|
|
// Outside render passes: radv_CmdWriteTimestamp2 crashed inside some. Marks sit where a
|
|
// pass, dispatch or submission ends anyway.
|
|
if (end_rendering) {
|
|
scheduler.EndRendering();
|
|
}
|
|
const u32 query = slice * SliceQueries + used[slice]++;
|
|
keys[slice].push_back(key);
|
|
current = key;
|
|
scheduler.Record([pool = *pool, query](vk::CommandBuffer cmdbuf) {
|
|
cmdbuf.writeTimestamp2(vk::PipelineStageFlagBits2::eAllCommands, pool, query);
|
|
});
|
|
}
|
|
|
|
void GpuProfiler::BeginFrame() {
|
|
// Close the frame: one more timestamp without a label.
|
|
if (used[slice] > 0 && used[slice] < SliceQueries) {
|
|
scheduler.EndRendering();
|
|
const u32 query = slice * SliceQueries + used[slice]++;
|
|
scheduler.Record([pool = *pool, query](vk::CommandBuffer cmdbuf) {
|
|
cmdbuf.writeTimestamp2(vk::PipelineStageFlagBits2::eAllCommands, pool, query);
|
|
});
|
|
pending[slice] = true;
|
|
}
|
|
slice = (slice + 1) % NumSlices;
|
|
// The oldest slice: its frame was submitted four frames ago.
|
|
if (pending[slice]) {
|
|
Collect(slice);
|
|
}
|
|
if (used[slice]) {
|
|
device.resetQueryPool(*pool, slice * SliceQueries, used[slice]);
|
|
}
|
|
used[slice] = 0;
|
|
keys[slice].clear();
|
|
pending[slice] = false;
|
|
Print();
|
|
}
|
|
|
|
void GpuProfiler::Collect(u32 which) {
|
|
const u32 count = used[which];
|
|
std::vector<u64> stamps(count);
|
|
// Four frames on this is complete unless the GPU lags that far: then wait for it.
|
|
const auto result = device.getQueryPoolResults(
|
|
*pool, which * SliceQueries, count, count * sizeof(u64), stamps.data(), sizeof(u64),
|
|
vk::QueryResultFlagBits::e64 | vk::QueryResultFlagBits::eWait);
|
|
if (result != vk::Result::eSuccess) {
|
|
return;
|
|
}
|
|
for (u32 i = 0; i + 1 < count; ++i) {
|
|
const double ms = double(stamps[i + 1] - stamps[i]) * period_ns / 1e6;
|
|
auto& total = totals[keys[which][i]];
|
|
total.ms += ms;
|
|
++total.segments;
|
|
if (keys[which][i] >= SubmissionGapKey && keys[which][i] < SubmissionGapKey + 0x20) {
|
|
gaps.push_back(ms);
|
|
}
|
|
}
|
|
++frames;
|
|
}
|
|
|
|
void GpuProfiler::Print() {
|
|
const auto now = std::chrono::steady_clock::now();
|
|
if (now - window < std::chrono::seconds(5) || frames == 0) {
|
|
return;
|
|
}
|
|
window = now;
|
|
std::vector<std::pair<u64, Total>> sorted(totals.begin(), totals.end());
|
|
std::ranges::sort(sorted, [](const auto& a, const auto& b) { return a.second.ms > b.second.ms; });
|
|
double sum = 0;
|
|
for (const auto& [key, total] : sorted) {
|
|
sum += total.ms;
|
|
}
|
|
std::printf("GPU profile: %.2f ms/frame over %llu frames, %zu labels\n", sum / frames,
|
|
static_cast<unsigned long long>(frames), sorted.size());
|
|
// BB_GPU_PROFILE_TOP=n: the n heaviest labels (30).
|
|
static const size_t top = [] {
|
|
const char* env = bb_env("BB_GPU_PROFILE_TOP");
|
|
return env && *env ? size_t(std::strtoul(env, nullptr, 10)) : size_t(30);
|
|
}();
|
|
for (size_t i = 0; i < std::min<size_t>(sorted.size(), top); ++i) {
|
|
const auto& [key, total] = sorted[i];
|
|
std::printf(" %6.3f ms/frame %5.1f/frame %s\n", total.ms / frames,
|
|
double(total.segments) / frames, described[key].c_str());
|
|
}
|
|
// BB_GPU_PROFILE_DRAWS=1: all labelled draws together, by what one draw of the pair costs.
|
|
{
|
|
struct Bin { double ms = 0; u64 draws = 0; u64 pairs = 0; };
|
|
std::array<Bin, 5> bins{}; // < 5 us, < 20, < 50, < 150, more
|
|
for (const auto& [key, total] : sorted) {
|
|
if (described[key].rfind("draw vs", 0) != 0 || total.segments == 0) {
|
|
continue;
|
|
}
|
|
const double us = total.ms * 1000.0 / double(total.segments);
|
|
auto& bin = bins[us < 5 ? 0 : us < 20 ? 1 : us < 50 ? 2 : us < 150 ? 3 : 4];
|
|
bin.ms += total.ms;
|
|
bin.draws += total.segments;
|
|
++bin.pairs;
|
|
}
|
|
if (bins[0].draws + bins[1].draws + bins[2].draws + bins[3].draws + bins[4].draws != 0) {
|
|
static constexpr const char* names[5] = {"< 5 us", "5-20 us", "20-50 us", "50-150 us", "> 150 us"};
|
|
std::printf(" draws by cost of one:");
|
|
for (size_t b = 0; b < bins.size(); ++b) {
|
|
std::printf(" %s: %.2f ms %.0f draws (%llu pairs)", names[b], bins[b].ms / frames,
|
|
double(bins[b].draws) / frames, (unsigned long long)bins[b].pairs);
|
|
}
|
|
std::printf("\n");
|
|
}
|
|
}
|
|
if (!gaps.empty()) {
|
|
// The gaps one by one: the presenter's own submissions (outside this scheduler) fall in
|
|
// them too, so a steady gap per frame is its work, scattered long ones are waits.
|
|
std::ranges::sort(gaps);
|
|
const auto at = [&](double q) { return gaps[size_t(q * double(gaps.size() - 1))]; };
|
|
double over = 0;
|
|
for (const double gap : gaps) {
|
|
over += gap >= 0.25 ? gap : 0;
|
|
}
|
|
std::printf(" submission gaps: %zu, median %.3f ms, p90 %.3f, max %.3f; %.3f ms/frame "
|
|
"in gaps of 0.25 ms or more\n",
|
|
gaps.size(), at(0.5), at(0.9), gaps.back(), over / frames);
|
|
gaps.clear();
|
|
}
|
|
totals.clear();
|
|
frames = 0;
|
|
}
|
|
|
|
} // namespace Vulkan
|