mirror of
https://github.com/ayozetr/bloodborne_linux.git
synced 2026-11-07 17:02:01 -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]>
249 lines
11 KiB
C++
249 lines
11 KiB
C++
// SPDX-FileCopyrightText: Copyright 2026 bbport contributors
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
// bbport render: Rasterizer diagnostics: traced shader inputs, the images of a pass, and the
|
|
// watched draws (BB_WATCH_*). Split out of rasterizer.cpp (2026-10-26).
|
|
|
|
|
|
#include <mutex>
|
|
#include "shim/bb_env.h"
|
|
#include <set>
|
|
#include <string>
|
|
#include <filesystem>
|
|
#include "render/raster_internal.h"
|
|
#include <vk_mem_alloc.h>
|
|
|
|
|
|
namespace Vulkan {
|
|
|
|
namespace {
|
|
/// BB_PASS_DUMP=addr,addr (hex guest addresses of render targets) with BB_PASS_DUMP_TRIGGER=<file>
|
|
/// (diagnostics, the flicker): while the file exists, every 8th row of those images is saved at
|
|
/// each display pass (BB_DUMP_DIR/pNNNNN_<addr>_<w>x<h>_<bits>.raw), little enough not to change
|
|
/// the frame's timing much.
|
|
std::vector<VAddr> PassDumpAddresses() {
|
|
std::vector<VAddr> out;
|
|
if (const char* env = bb_env("BB_PASS_DUMP")) {
|
|
for (const char* at = env; *at;) {
|
|
char* end = nullptr;
|
|
out.push_back(std::strtoull(at, &end, 16));
|
|
at = *end ? end + 1 : end;
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
} // namespace
|
|
|
|
void Rasterizer::DumpTracedInputs() {
|
|
if (traced_inputs.empty()) {
|
|
return;
|
|
}
|
|
static const std::string dir = [] {
|
|
const char* env = bb_env("BB_DUMP_DIR");
|
|
return std::string{env && env[0] ? env : "out/dump"};
|
|
}();
|
|
VkDeviceSize total = 0;
|
|
for (const auto& r : traced_inputs) total += r.size;
|
|
const VkBufferCreateInfo buffer_ci{.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
|
.size = total,
|
|
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT};
|
|
const VmaAllocationCreateInfo alloc_ci{
|
|
.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT,
|
|
.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST};
|
|
VkBuffer buffer{};
|
|
VmaAllocation allocation{};
|
|
VmaAllocationInfo info{};
|
|
if (vmaCreateBuffer(instance.GetAllocator(), &buffer_ci, &alloc_ci, &buffer, &allocation,
|
|
&info) != VK_SUCCESS) {
|
|
traced_inputs.clear();
|
|
return;
|
|
}
|
|
scheduler.EndRendering();
|
|
auto ranges = traced_inputs;
|
|
scheduler.Record([ranges, buffer](vk::CommandBuffer cmd) {
|
|
const vk::MemoryBarrier2 before{
|
|
.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands,
|
|
.srcAccessMask = vk::AccessFlagBits2::eMemoryWrite,
|
|
.dstStageMask = vk::PipelineStageFlagBits2::eTransfer,
|
|
.dstAccessMask = vk::AccessFlagBits2::eTransferRead,
|
|
};
|
|
cmd.pipelineBarrier2({.memoryBarrierCount = 1, .pMemoryBarriers = &before});
|
|
VkDeviceSize at = 0;
|
|
for (const auto& r : ranges) {
|
|
cmd.copyBuffer(r.buffer, buffer, vk::BufferCopy{r.offset, at, r.size});
|
|
at += r.size;
|
|
}
|
|
});
|
|
std::string header;
|
|
for (const auto& r : traced_inputs) {
|
|
header += fmt::format("{:x}+{:x}:{} ", (u64)(VkBuffer)r.buffer, r.offset, r.size);
|
|
}
|
|
char path[512];
|
|
std::snprintf(path, sizeof(path), "%s/g%06llu.bin", dir.c_str(),
|
|
(unsigned long long)traced_inputs_frame);
|
|
scheduler.DeferPriorityOperation([allocator = instance.GetAllocator(), buffer, allocation, info,
|
|
total, file = std::string{path}, header] {
|
|
vmaInvalidateAllocation(allocator, allocation, 0, VK_WHOLE_SIZE);
|
|
if (FILE* f = std::fopen(file.c_str(), "wb")) {
|
|
const u32 n = u32(header.size());
|
|
std::fwrite(&n, 4, 1, f);
|
|
std::fwrite(header.data(), 1, n, f);
|
|
std::fwrite(info.pMappedData, 1, total, f);
|
|
std::fclose(f);
|
|
}
|
|
vmaDestroyBuffer(allocator, buffer, allocation);
|
|
});
|
|
traced_inputs.clear();
|
|
}
|
|
|
|
void Rasterizer::DumpPassImages(char tag) {
|
|
static const std::vector<VAddr> addresses = PassDumpAddresses();
|
|
static const char* trigger = bb_env("BB_PASS_DUMP_TRIGGER");
|
|
if (addresses.empty() || !trigger || !std::filesystem::exists(trigger)) {
|
|
return;
|
|
}
|
|
static const std::string dir = [] {
|
|
const char* env = bb_env("BB_DUMP_DIR");
|
|
return std::string{env && env[0] ? env : "out/dump"};
|
|
}();
|
|
static u32 indices[2] = {};
|
|
const u32 n = indices[tag == 'p' ? 0 : 1]++;
|
|
std::printf("Pass dump %c %u: GPU frame %llu\n", tag, n,
|
|
(unsigned long long)BbStats::gpu_frames.load(std::memory_order_relaxed));
|
|
scheduler.EndRendering();
|
|
for (const VAddr address : addresses) {
|
|
VideoCore::Image* found = nullptr;
|
|
texture_cache.ForEachImageInRegion(address, 1, [&](VideoCore::ImageId, VideoCore::Image& image) {
|
|
if (image.info.guest_address == address &&
|
|
(!found || image.info.size.width > found->info.size.width)) {
|
|
found = ℑ
|
|
}
|
|
});
|
|
if (!found || found->info.num_bits == 0) {
|
|
continue;
|
|
}
|
|
const u32 width = found->info.size.width, height = found->info.size.height;
|
|
const u32 bytes = found->info.num_bits / 8;
|
|
static const u32 step = [] {
|
|
const char* env = bb_env("BB_PASS_DUMP_STEP");
|
|
return env ? std::max(1, std::atoi(env)) : 8;
|
|
}();
|
|
const u32 rows = (height + step - 1) / step;
|
|
const VkDeviceSize size = VkDeviceSize(width) * rows * bytes;
|
|
const VkBufferCreateInfo buffer_ci{.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
|
.size = size,
|
|
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT};
|
|
const VmaAllocationCreateInfo alloc_ci{
|
|
.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT,
|
|
.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST};
|
|
VkBuffer buffer{};
|
|
VmaAllocation allocation{};
|
|
VmaAllocationInfo info{};
|
|
if (vmaCreateBuffer(instance.GetAllocator(), &buffer_ci, &alloc_ci, &buffer, &allocation,
|
|
&info) != VK_SUCCESS) {
|
|
continue;
|
|
}
|
|
runtime.Transit(found, vk::ImageLayout::eTransferSrcOptimal,
|
|
vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eTransferRead);
|
|
runtime.FlushBarriers();
|
|
const vk::Image image = found->GetImage();
|
|
const vk::ImageAspectFlags aspect = found->aspect_mask & ~vk::ImageAspectFlagBits::eStencil;
|
|
scheduler.Record([=](vk::CommandBuffer cmd) {
|
|
std::vector<vk::BufferImageCopy> regions;
|
|
regions.reserve(rows);
|
|
for (u32 r = 0; r < rows; ++r) {
|
|
regions.push_back({
|
|
.bufferOffset = VkDeviceSize(r) * width * bytes,
|
|
.imageSubresource = {aspect, 0, 0, 1},
|
|
.imageOffset = {0, s32(r * step), 0},
|
|
.imageExtent = {width, 1, 1},
|
|
});
|
|
}
|
|
cmd.copyImageToBuffer(image, vk::ImageLayout::eTransferSrcOptimal, buffer, regions);
|
|
});
|
|
char path[512];
|
|
std::snprintf(path, sizeof(path), "%s/%c%05u_%llx_%ux%u_%u.raw", dir.c_str(), tag, n,
|
|
(unsigned long long)address, width, rows, found->info.num_bits);
|
|
scheduler.DeferPriorityOperation([allocator = instance.GetAllocator(), buffer, allocation,
|
|
info, size, file = std::string{path}] {
|
|
vmaInvalidateAllocation(allocator, allocation, 0, VK_WHOLE_SIZE);
|
|
if (FILE* f = std::fopen(file.c_str(), "wb")) {
|
|
std::fwrite(info.pMappedData, 1, size, f);
|
|
std::fclose(f);
|
|
}
|
|
vmaDestroyBuffer(allocator, buffer, allocation);
|
|
});
|
|
}
|
|
}
|
|
|
|
void Rasterizer::WatchDraw(const GraphicsPipeline* pipeline, const RenderState& state) {
|
|
// GPU thread only. Per (vs, ps, first target): the last fields seen and how many draws in a
|
|
// row had them. A change after a stable run is printed with the frame number, and so is the
|
|
// change back, which marks a one-frame anomaly (docs/Flicker_video_2026-10-17.ru.md).
|
|
const auto& regs = Regs();
|
|
const auto& vs = pipeline->GetStage(Shader::SwStage::Vertex);
|
|
const auto* ps = pipeline->GetStages()[u32(Shader::SwStage::Fragment)];
|
|
const VAddr target = cb_descs[0].first ? texture_cache.GetImage(cb_descs[0].first).info.guest_address : 0;
|
|
const u64 key = vs.pgm_hash * 0x9E3779B97F4A7C15ull ^ (ps ? ps->pgm_hash : 0) ^ target;
|
|
const auto bits = [](float f) { return u64(std::bit_cast<u32>(f)); };
|
|
std::vector<u64> fields;
|
|
const auto& vp = regs.viewports[0];
|
|
fields.push_back(bits(vp.xscale) << 32 | bits(vp.xoffset));
|
|
fields.push_back(bits(vp.yscale) << 32 | bits(vp.yoffset));
|
|
fields.push_back(u64(std::bit_cast<u32>(regs.viewport_control)) << 32 | u64(regs.IsClipDisabled()));
|
|
const auto& sc = regs.screen_scissor;
|
|
fields.push_back(u64(u16(sc.top_left_x)) << 48 | u64(u16(sc.top_left_y)) << 32 |
|
|
u64(u16(sc.bottom_right_x)) << 16 | u16(sc.bottom_right_y));
|
|
fields.push_back(bits(target_scale[0]) << 32 | bits(target_scale[1]));
|
|
fields.push_back(bits(push_data.xoffset) << 32 | bits(push_data.yoffset));
|
|
fields.push_back(u64(state.width) << 32 | state.height);
|
|
static const bool textures = bb_env("BB_DRAW_WATCH_TEXTURES") != nullptr;
|
|
for (const auto id : textures ? bound_images : decltype(bound_images){}) {
|
|
const auto& info = texture_cache.GetImage(id).info;
|
|
fields.push_back(info.guest_address);
|
|
fields.push_back(u64(info.size.width) << 32 | info.size.height);
|
|
}
|
|
struct Seen {
|
|
std::vector<u64> fields;
|
|
std::vector<u64> stable_fields;
|
|
u32 run = 0;
|
|
u64 changed_frame = 0;
|
|
};
|
|
static std::unordered_map<u64, Seen> seen;
|
|
static u32 printed = 0;
|
|
auto& s = seen[key];
|
|
const u64 frame = FrameCapture::Flips();
|
|
if (s.fields == fields) {
|
|
++s.run;
|
|
return;
|
|
}
|
|
const auto print = [&](const char* what) {
|
|
if (printed++ > 4000) {
|
|
return;
|
|
}
|
|
std::string text = fmt::format("Draw watch: frame {} {} vs {:016x} ps {:016x} target {:#x}:",
|
|
frame, what, vs.pgm_hash, ps ? ps->pgm_hash : 0, target);
|
|
const size_t n = std::max(fields.size(), s.fields.size());
|
|
for (size_t i = 0; i < n; ++i) {
|
|
const u64 now = i < fields.size() ? fields[i] : 0;
|
|
const u64 was = i < s.fields.size() ? s.fields[i] : 0;
|
|
if (now != was) {
|
|
text += fmt::format(" [{}] {:#x}->{:#x}", i, was, now);
|
|
}
|
|
}
|
|
std::printf("%s\n", text.c_str());
|
|
};
|
|
if (s.run >= 8) {
|
|
// A stable run broke.
|
|
s.stable_fields = s.fields;
|
|
s.changed_frame = frame;
|
|
print("changed");
|
|
} else if (!s.stable_fields.empty() && fields == s.stable_fields && frame - s.changed_frame <= 2) {
|
|
print("back after a short change");
|
|
s.stable_fields.clear();
|
|
}
|
|
s.fields = std::move(fields);
|
|
s.run = 0;
|
|
}
|
|
|
|
} // namespace Vulkan
|