mirror of
https://github.com/BillyOutlast/bloodborne_pc.git
synced 2026-10-12 02:25:51 +00:00
Steam Deck and weak GPUs - Outputs other than 1080p render at the preset size again (startup patch, as in 0.1); live resolution changes are now a setting (live_resolution=0|1|auto, off by default). - Texture cache on integrated GPUs: thresholds follow the driver's current memory budget. The old budget (~1 GB on the Deck) kept the collector in its aggressive mode, evicting textures used 2-3 frames ago on every submission. - Evicted GPU-written images are written back synchronously, while their pages are still protected (a deferred write could land over memory the game had reused). - run.sh uses bash builtins only (the AppImage has no sed: the game did not start on NixOS). Rendering - Live scaling also reduces the game's half-resolution post-processing (960x540, including the bloom mip chain, one proxy per level). - TAA: history of thin surfaces missed by a jitter phase is kept when nothing moves (railings, window bars: static-camera flicker p99.9 -45%). - Scene texture LOD bias log2(render/output) in G-buffer passes while upscaling. - Present on its own thread and at most one frame of GPU run-ahead (even frame pacing). - Pipeline cache rebuilt instead of disabled when the shader profile changes. - GPU idle only after the draw pipeline and deferred fences are done (work_retired). - Recompiler fixes backported from shadPS4; integer interpolation fix for pre-Turing NVIDIA. - Sharpness up to 2.0 (an extra RCAS pass); native-resolution TAA mode. Launcher, mods, patches - English launcher (follows the system language), loose-file mods with load order, third-party shadPS4 XML patches, 60/90 FPS patches with pointer slots fixed. - AppImage: host NVIDIA Vulkan driver support, MangoHud, no FSR 4.1.1 models (BB_PACKAGE_FSR411=1 bundles local ones). Tools: route recording/replay (F9), consecutive frame dumps, scene scaling diagnostics. Details: docs/CHANGES_2026-10-02.md, docs/CHANGES_2026-10-03.md. Co-Authored-By: Claude Opus 5.5 <[email protected]>
202 lines
11 KiB
C++
202 lines
11 KiB
C++
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
// Exercises the production target/resolve path on Vulkan (Lavapipe works, no game/window).
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include "common/slot_vector.h"
|
|
#include "video_core/renderer_vulkan/vk_instance.h"
|
|
#include "video_core/renderer_vulkan/vk_runtime.h"
|
|
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
|
#include "video_core/renderer_vulkan/vk_scene_resolution.h"
|
|
#include "video_core/texture_cache/blit_helper.h"
|
|
#include <vk_mem_alloc.h>
|
|
|
|
int main() {
|
|
using namespace Vulkan;
|
|
Instance instance(0, false);
|
|
// Calls emitted by this executable use a local dispatcher. libbbgpu.so initializes
|
|
// its own default dispatcher while constructing Instance.
|
|
static vk::detail::DynamicLoader loader;
|
|
vk::detail::DispatchLoaderDynamic dispatch;
|
|
dispatch.init(
|
|
loader.getProcAddress<PFN_vkGetInstanceProcAddr>("vkGetInstanceProcAddr"));
|
|
dispatch.init(instance.GetInstance());
|
|
dispatch.init(instance.GetDevice());
|
|
Scheduler scheduler(instance);
|
|
Runtime runtime(instance, scheduler);
|
|
Common::SlotVector<VideoCore::ImageView> views;
|
|
Common::SlotVector<VideoCore::Image> images;
|
|
VideoCore::ImageInfo ci;
|
|
ci.size = {1920,1080,1};
|
|
ci.resources = {1,1};
|
|
ci.type = AmdGpu::ImageType::Color2D;
|
|
ci.pixel_format = vk::Format::eR8G8B8A8Unorm;
|
|
ci.num_bits = 32;
|
|
const auto color_id = images.insert(instance, runtime, views, ci);
|
|
auto di = ci;
|
|
di.pixel_format = vk::Format::eD32SfloatS8Uint;
|
|
di.props.is_depth = di.props.has_stencil = true;
|
|
const auto depth_id = images.insert(instance, runtime, views, di);
|
|
SceneTargets targets(instance, scheduler, runtime, [&](VideoCore::ImageId id, u64 uid) {
|
|
if (!images.is_allocated(id)) return static_cast<VideoCore::Image*>(nullptr);
|
|
return !uid || images[id].image_uid == uid ? &images[id] : nullptr;
|
|
});
|
|
const bool color_eligible = targets.Eligible(images[color_id]);
|
|
const bool depth_eligible = targets.Eligible(images[depth_id]);
|
|
std::fprintf(stderr, "Scene target eligibility: color=%d depth=%d\n",
|
|
color_eligible, depth_eligible);
|
|
if (!color_eligible || !depth_eligible) {
|
|
std::puts("Scene targets: unsupported format blit, live scaling skipped");
|
|
return 0;
|
|
}
|
|
|
|
VkBuffer readback{};
|
|
VmaAllocation allocation{};
|
|
VmaAllocationInfo ai{};
|
|
const VkBufferCreateInfo bi{.sType=VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
|
.size=16, .usage=VK_BUFFER_USAGE_TRANSFER_DST_BIT};
|
|
const VmaAllocationCreateInfo ac{.flags=VMA_ALLOCATION_CREATE_MAPPED_BIT |
|
|
VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT, .usage=VMA_MEMORY_USAGE_AUTO,
|
|
.requiredFlags=VK_MEMORY_PROPERTY_HOST_COHERENT_BIT};
|
|
assert(vmaCreateBuffer(instance.GetAllocator(), &bi, &ac, &readback, &allocation, &ai) == VK_SUCCESS);
|
|
const auto pixel = [&](vk::Image image, vk::ImageAspectFlagBits aspect,
|
|
vk::ImageLayout layout, u32 w, u32 h) {
|
|
scheduler.EndRendering();
|
|
const auto cmd = scheduler.CommandBuffer();
|
|
const vk::MemoryBarrier2 b{.srcStageMask=vk::PipelineStageFlagBits2::eAllCommands,
|
|
.srcAccessMask=vk::AccessFlagBits2::eMemoryWrite,
|
|
.dstStageMask=vk::PipelineStageFlagBits2::eTransfer,
|
|
.dstAccessMask=vk::AccessFlagBits2::eTransferRead};
|
|
cmd.pipelineBarrier2({.memoryBarrierCount=1,.pMemoryBarriers=&b}, dispatch);
|
|
const vk::BufferImageCopy region{.imageSubresource={aspect,0,0,1},
|
|
.imageOffset={s32(w-1),s32(h-1),0},.imageExtent={1,1,1}};
|
|
cmd.copyImageToBuffer(image, layout, readback, region, dispatch);
|
|
const vk::MemoryBarrier2 host{.srcStageMask=vk::PipelineStageFlagBits2::eTransfer,
|
|
.srcAccessMask=vk::AccessFlagBits2::eTransferWrite,
|
|
.dstStageMask=vk::PipelineStageFlagBits2::eHost,
|
|
.dstAccessMask=vk::AccessFlagBits2::eHostRead};
|
|
cmd.pipelineBarrier2({.memoryBarrierCount=1,.pMemoryBarriers=&host}, dispatch);
|
|
scheduler.Finish();
|
|
u32 result;
|
|
std::memcpy(&result, ai.pMappedData, 4);
|
|
return result;
|
|
};
|
|
VideoCore::ImageViewInfo cv;
|
|
cv.format = ci.pixel_format;
|
|
auto dv = cv;
|
|
dv.format = di.pixel_format;
|
|
const auto clear_native = [&](u32 rgba, u32 stencil = 7) {
|
|
vk::ClearValue clear{};
|
|
clear.color.float32 = std::array{float(rgba & 255)/255, float((rgba>>8)&255)/255,
|
|
float((rgba>>16)&255)/255, 1.0f};
|
|
runtime.ClearImage(&images[color_id], {}, clear);
|
|
runtime.Transit(&images[depth_id], vk::ImageLayout::eTransferDstOptimal,
|
|
vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eTransferWrite);
|
|
runtime.FlushBarriers();
|
|
scheduler.CommandBuffer().clearDepthStencilImage(
|
|
images[depth_id].GetImage(), vk::ImageLayout::eTransferDstOptimal,
|
|
vk::ClearDepthStencilValue{0.25f, stencil},
|
|
vk::ImageSubresourceRange{vk::ImageAspectFlagBits::eDepth |
|
|
vk::ImageAspectFlagBits::eStencil, 0, 1, 0, 1}, dispatch);
|
|
};
|
|
constexpr u32 stencil_values[] = {0, 255, 128, 85, 170, 7, 13, 255, 0, 170};
|
|
u32 stencil_index = 0;
|
|
for (int preset : {3,1,4,2,0,3,0,1,0,3}) {
|
|
const u32 copied_stencil = stencil_values[stencil_index++];
|
|
const u32 resolved_stencil = copied_stencil ^ 255;
|
|
const auto output = stencil_index > 7 ? SceneResolution::Size{3840,2160}
|
|
: SceneResolution::Size{};
|
|
const auto size = SceneResolution::ForPreset(preset, output);
|
|
targets.SetSize(size);
|
|
assert(SceneResolution::Unpack(SceneResolution::Pack(size)) == size);
|
|
clear_native(0xffff0000, copied_stencil); // blue
|
|
auto c = targets.Read(color_id,cv);
|
|
assert(pixel(c.image,vk::ImageAspectFlagBits::eColor,c.layout,size.width,size.height)==0xffff0000);
|
|
auto d = targets.Read(depth_id,dv);
|
|
const u32 depth_bits = pixel(d.image,vk::ImageAspectFlagBits::eDepth,d.layout,size.width,size.height);
|
|
assert(std::bit_cast<float>(depth_bits)==0.25f);
|
|
assert((pixel(d.image,vk::ImageAspectFlagBits::eStencil,d.layout,size.width,size.height)&255)==copied_stencil);
|
|
|
|
c = targets.Attachment(color_id,cv);
|
|
d = targets.Attachment(depth_id,dv);
|
|
auto cmd = scheduler.CommandBuffer();
|
|
const vk::RenderingAttachmentInfo color{.imageView=c.view,.imageLayout=c.layout,
|
|
.loadOp=vk::AttachmentLoadOp::eClear,.storeOp=vk::AttachmentStoreOp::eStore,
|
|
.clearValue=vk::ClearValue{.color={.float32=std::array{1.f,0.f,0.f,1.f}}}};
|
|
const vk::RenderingAttachmentInfo depth{.imageView=d.view,.imageLayout=d.layout,
|
|
.loadOp=vk::AttachmentLoadOp::eClear,.storeOp=vk::AttachmentStoreOp::eStore,
|
|
.clearValue=vk::ClearValue{.depthStencil={0.75f,resolved_stencil}}};
|
|
cmd.beginRendering({.renderArea={{0,0},{size.width,size.height}},.layerCount=1,
|
|
.colorAttachmentCount=1,.pColorAttachments=&color,
|
|
.pDepthAttachment=&depth,.pStencilAttachment=&depth}, dispatch);
|
|
cmd.endRendering(dispatch);
|
|
runtime.Transit(&images[color_id],vk::ImageLayout::eTransferSrcOptimal,
|
|
vk::PipelineStageFlagBits2::eTransfer,vk::AccessFlagBits2::eTransferRead);
|
|
runtime.FlushBarriers();
|
|
assert(pixel(images[color_id].GetImage(),vk::ImageAspectFlagBits::eColor,
|
|
vk::ImageLayout::eTransferSrcOptimal,1920,1080)==0xff0000ff);
|
|
runtime.Transit(&images[depth_id],vk::ImageLayout::eTransferSrcOptimal,
|
|
vk::PipelineStageFlagBits2::eTransfer,vk::AccessFlagBits2::eTransferRead);
|
|
runtime.FlushBarriers();
|
|
assert(std::bit_cast<float>(pixel(images[depth_id].GetImage(),vk::ImageAspectFlagBits::eDepth,
|
|
vk::ImageLayout::eTransferSrcOptimal,1920,1080))==0.75f);
|
|
assert((pixel(images[depth_id].GetImage(),vk::ImageAspectFlagBits::eStencil,
|
|
vk::ImageLayout::eTransferSrcOptimal,1920,1080)&255)==resolved_stencil);
|
|
clear_native(0xff00ff00); // overwrite after reduced render: proxy must be invalidated
|
|
c=targets.Read(color_id,cv);
|
|
assert(pixel(c.image,vk::ImageAspectFlagBits::eColor,c.layout,size.width,size.height)==0xff00ff00);
|
|
std::printf("Scene targets: %ux%u color/depth/stencil roundtrip PASS\n",size.width,size.height);
|
|
}
|
|
|
|
// Half-resolution mip chain (the game's bloom pyramid): one proxy per level, scaled by the
|
|
// scene's factor, resolved into its own native level.
|
|
auto hi = ci;
|
|
hi.size = {960, 540, 1};
|
|
hi.resources = {4, 1};
|
|
const auto half_id = images.insert(instance, runtime, views, hi);
|
|
const auto scene = SceneResolution::ForPreset(4); // 640x360 (x3 of 1080p)
|
|
targets.SetSize(scene);
|
|
auto& half = images[half_id];
|
|
assert(targets.Eligible(half) && !targets.EligibleScene(half));
|
|
assert((targets.ProxySize(half, 0) == SceneResolution::Size{320, 180}));
|
|
assert((targets.ProxySize(half, 2) == SceneResolution::Size{80, 45}));
|
|
auto lv = cv;
|
|
lv.range.base.level = 2;
|
|
const auto proxy = targets.Attachment(half_id, lv);
|
|
{
|
|
auto cmd = scheduler.CommandBuffer();
|
|
const vk::RenderingAttachmentInfo color{.imageView=proxy.view,.imageLayout=proxy.layout,
|
|
.loadOp=vk::AttachmentLoadOp::eClear,.storeOp=vk::AttachmentStoreOp::eStore,
|
|
.clearValue=vk::ClearValue{.color={.float32=std::array{0.f,1.f,0.f,1.f}}}};
|
|
cmd.beginRendering({.renderArea={{0,0},{80,45}},.layerCount=1,
|
|
.colorAttachmentCount=1,.pColorAttachments=&color}, dispatch);
|
|
cmd.endRendering(dispatch);
|
|
}
|
|
assert(targets.ProxyCurrent(half, 2, 1) && !targets.ProxyCurrent(half, 0, 4));
|
|
// A native access resolves the level: its last texel (239, 134) is the proxy's colour.
|
|
runtime.Transit(&half, vk::ImageLayout::eTransferSrcOptimal,
|
|
vk::PipelineStageFlagBits2::eTransfer, vk::AccessFlagBits2::eTransferRead);
|
|
runtime.FlushBarriers();
|
|
{
|
|
scheduler.EndRendering();
|
|
const auto cmd = scheduler.CommandBuffer();
|
|
const vk::MemoryBarrier2 b{.srcStageMask=vk::PipelineStageFlagBits2::eAllCommands,
|
|
.srcAccessMask=vk::AccessFlagBits2::eMemoryWrite,
|
|
.dstStageMask=vk::PipelineStageFlagBits2::eTransfer,
|
|
.dstAccessMask=vk::AccessFlagBits2::eTransferRead};
|
|
cmd.pipelineBarrier2({.memoryBarrierCount=1,.pMemoryBarriers=&b}, dispatch);
|
|
const vk::BufferImageCopy region{.imageSubresource={vk::ImageAspectFlagBits::eColor,2,0,1},
|
|
.imageOffset={239,134,0},.imageExtent={1,1,1}};
|
|
cmd.copyImageToBuffer(half.GetImage(), vk::ImageLayout::eTransferSrcOptimal, readback,
|
|
region, dispatch);
|
|
scheduler.Finish();
|
|
u32 result;
|
|
std::memcpy(&result, ai.pMappedData, 4);
|
|
assert(result == 0xff00ff00);
|
|
}
|
|
std::puts("Scene targets: half-resolution mip level proxy roundtrip PASS");
|
|
scheduler.Finish();
|
|
vmaDestroyBuffer(instance.GetAllocator(),readback,allocation);
|
|
}
|