mirror of
https://github.com/ayozetr/bloodborne_linux.git
synced 2026-11-09 09:19:42 -10:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c34fe3a97c | ||
|
|
23730cd9c1 | ||
|
|
f48d860e7f | ||
|
|
c1020650e9 |
No files matched your search
@@ -149,4 +149,7 @@ if [[ ${1:-} == --test ]]; then
|
||||
rm -rf out/atlas-features && out/atlas-features-test out/atlas-features
|
||||
"${CXX:-c++}" "${atlas_cxx[@]}" tests/test_atlas_i18n.cpp -o out/atlas-i18n-test
|
||||
out/atlas-i18n-test
|
||||
# When a mirror is made again over its valid blocks only (gpu/layer/layer_mirror_policy.h).
|
||||
"${CXX:-c++}" -std=c++20 -O2 -g -UNDEBUG -Wall -Wextra -Werror -Igpu tests/test_layer_mirror_policy.cpp -o out/layer-mirror-policy-test
|
||||
out/layer-mirror-policy-test
|
||||
fi
|
||||
+6
-2
@@ -67,7 +67,8 @@ Obrikash and noelmiller. What each one did:
|
||||
- **[Mrsuss60](https://github.com/Mrsuss60)**: the gestures menu from the keyboard, a deleted
|
||||
image's slot, and the log of failed memory reservations.
|
||||
- **[SrShadowy](https://github.com/SrShadowy)**: the on-screen keyboard for the character's name,
|
||||
and the shader compiling notice.
|
||||
the shader compiling notice, and compacting the memory copies in VRAM.
|
||||
- **[Wooloomooloo2](https://github.com/Wooloomooloo2)**: the game starting on integrated Intel Arc/Xe GPUs.
|
||||
- **[Qiming-Liu](https://github.com/Qiming-Liu)**: the Simplified Chinese translation.
|
||||
- **[andrecabralgit](https://github.com/andrecabralgit)**: the game's audio at full level.
|
||||
- **[Jgamrot](https://github.com/Jgamrot)**: the in-game menu's checkboxes and sliders.
|
||||
@@ -98,7 +99,7 @@ Obrikash and noelmiller. What each one did:
|
||||
- **[n3vrs0ft](https://github.com/n3vrs0ft)**: the "Transaction failed" crash on Seamless Hunt, the logs that stopped a BBSC guest from following a stranger's world, and testing BBSC 0.0.2 with a Windows player.
|
||||
- **[cappp](https://github.com/cappp)**: the "Transaction failed" crash on Seamless Hunt.
|
||||
- **[simo1730](https://github.com/simo1730)**: the Trophies page not finding the dump's files.
|
||||
- **[Nahnahchi](https://github.com/Nahnahchi)**: logs and tests of a GPU freeze in a boss fight on NVIDIA, a crash in Yahar'gul, stretched models, a crash on the Steam Deck, effects turning black, rainbow textures and crashes in the game's memory manager.
|
||||
- **[Nahnahchi](https://github.com/Nahnahchi)**: logs and tests of a GPU freeze in a boss fight on NVIDIA, a crash in Yahar'gul, stretched models, a crash on the Steam Deck, effects turning black, rainbow textures, crashes in the game's memory manager, frame rate drops on NVIDIA, and Exit to Desktop with online play.
|
||||
- **[thomasgarafano1](https://github.com/thomasgarafano1)**: Hunter's Atlas not preparing the map with a GOTY dump, and testing the fix.
|
||||
- **[TacticalBill](https://github.com/TacticalBill)**: marking trophies already earned on the PS4.
|
||||
- **[holydingus](https://github.com/holydingus)**: BBSC's items not given to a new character.
|
||||
@@ -107,3 +108,6 @@ Obrikash and noelmiller. What each one did:
|
||||
- **[BlazinKarp](https://github.com/BlazinKarp)**: the Steam overlay in the game.
|
||||
- **[molnar-a](https://github.com/molnar-a)**: a crash on the Steam Deck after lamp travel, and testing the fix.
|
||||
- **[Aman9das](https://github.com/Aman9das)**: asking for bbport 0.6's keyboard layout.
|
||||
- **[eberhardweber](https://github.com/eberhardweber)**: logs and tests of AMD FX processors, fullscreen on Wayland with several monitors, and the AppImage's version in Gear Lever.
|
||||
- **[GitBire](https://github.com/GitBire)**: the storage box with keyboard and mouse.
|
||||
- **[npc-xc](https://github.com/npc-xc)**: logs of frame rate drops on NVIDIA.
|
||||
+1
-1
@@ -319,7 +319,7 @@ Defaults to change on a machine where they misbehave, workarounds, and switches
|
||||
| `BB_MIRROR_POOL_MB` | =N: retired mirrors kept for new ones |
|
||||
| `BB_MIRROR_REPROMOTE_SECONDS` | =N (default 0): blocks sent back are not copied again that soon |
|
||||
| `BB_MIRROR_UNBOUND_SECONDS` | =N (0 never): blocks no binding named for N seconds leave VRAM |
|
||||
| `BB_MIRROR_VRAM_ONLY` | =1: mirrors in VRAM or not at all |
|
||||
| `BB_MIRROR_VRAM_ONLY` | =1 / 0: mirrors in VRAM or not at all, or in system memory too when VRAM is full (default: VRAM only on cards over 4 GiB) |
|
||||
| `BB_MOTION_LARGE` | =0: bone palettes of 16-64 KiB (the large beasts) get camera motion vectors only, not object motion |
|
||||
| `BB_MOTION_VERTICES_MI` | =N (1-8): object motion vertex buffer in Mi vertices |
|
||||
| `BB_NET_MODULE` | =path: the online module to load (default gpu/libbbnet20.so beside bb-probe) |
|
||||
|
||||
@@ -337,6 +337,13 @@ add_executable(camera-matrices-test EXCLUDE_FROM_ALL ../tests/test_camera_matric
|
||||
target_compile_options(camera-matrices-test PRIVATE -UNDEBUG -Wall -Wextra -Werror)
|
||||
target_include_directories(camera-matrices-test PRIVATE ..)
|
||||
|
||||
# When a mirror is made again over its valid blocks only (gpu/layer/layer_mirror_policy.h). Pure,
|
||||
# no Vulkan device: the regression test for the default run of 0.7 that compacted nothing at all.
|
||||
# From the SrShadowy fork.
|
||||
add_executable(layer-mirror-policy-test EXCLUDE_FROM_ALL ../tests/test_layer_mirror_policy.cpp)
|
||||
target_compile_options(layer-mirror-policy-test PRIVATE -UNDEBUG -Wall -Wextra -Werror)
|
||||
target_include_directories(layer-mirror-policy-test PRIVATE .)
|
||||
|
||||
add_executable(scene-resolution-test EXCLUDE_FROM_ALL ../tests/test_scene_resolution.cpp ../tests/test_gpu_runtime_stubs.cpp)
|
||||
target_include_directories(scene-resolution-test PRIVATE . shim ${SHADER_INCLUDE} third_party/gcn/include)
|
||||
# The test's Vulkan-Hpp inline methods have a different set of platform extensions
|
||||
|
||||
@@ -550,7 +550,8 @@ private:
|
||||
void LayerProcessIdle();
|
||||
/// VRAM near the texture collector's critical mark: idle mirrors go back in place.
|
||||
void LayerYieldToImages(u32 now);
|
||||
/// Makes mirrors with memory left without valid blocks again over those blocks only.
|
||||
/// Makes mirrors with memory left without valid blocks again over those blocks only
|
||||
/// (gpu/layer/layer_mirror_policy.h: the per-mirror threshold and the segments).
|
||||
u64 LayerCompactMirrors(u64 wanted);
|
||||
/// The GPU writes [address, address + size) in the submission being recorded (write ticks).
|
||||
void NoteWriteTick(VAddr address, u64 size);
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
#include "bblayer_gpu_memory.h"
|
||||
#include "bblayer_write_traps.h"
|
||||
#include "layer/buffer_cache_layer_internal.h"
|
||||
#include "layer/layer_mirror_policy.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
@@ -347,6 +348,7 @@ bool BufferCache::LayerMergeAcross(u64 first, u64 end) {
|
||||
BbStats::residency_alloc_bytes.fetch_sub(old.size, std::memory_order_relaxed);
|
||||
layer_retired.push_back({std::move(node.mapped()), scheduler.CurrentTick()});
|
||||
}
|
||||
layer_mirror_born.erase(old.base); // it is gone: its start may be reused by another mirror
|
||||
}
|
||||
Layer().AddMirror(start, bytes, merged->Handle(), merged->BufferDeviceAddress(), merged.get());
|
||||
layer_mirrors.emplace(start, std::move(merged));
|
||||
@@ -416,6 +418,7 @@ u64 BufferCache::LayerPromoteInto(u64 first, u64 end, u64 lo, u64 hi,
|
||||
BbStats::residency_alloc_bytes.fetch_sub(old.size, std::memory_order_relaxed);
|
||||
layer_retired.push_back({std::move(node.mapped()), scheduler.CurrentTick()});
|
||||
}
|
||||
layer_mirror_born.erase(old.base); // it is gone: its start may be reused by another mirror
|
||||
}
|
||||
Layer().AddMirror(start, bytes, target->Handle(), target->BufferDeviceAddress(), target);
|
||||
layer_mirrors.emplace(start, std::move(mirror));
|
||||
@@ -555,6 +558,7 @@ void BufferCache::LayerDemote(u64 first, u64 end, bool copy_back, int reason) {
|
||||
BbStats::residency_alloc_bytes.fetch_sub(mirror.size, std::memory_order_relaxed);
|
||||
layer_retired.push_back({std::move(node.mapped()), scheduler.CurrentTick()});
|
||||
}
|
||||
layer_mirror_born.erase(mirror.base); // it is gone: its start may be reused by another mirror
|
||||
}
|
||||
++layer_generation;
|
||||
}
|
||||
@@ -637,10 +641,22 @@ std::unique_ptr<Buffer> BufferCache::LayerNewMirror(VAddr start, u64 bytes) {
|
||||
}
|
||||
++mirror_pool_misses;
|
||||
}
|
||||
// BB_MIRROR_VRAM_ONLY=1: VRAM or none (VMA puts "mirrors" in system memory when VRAM is full).
|
||||
// Off by default: VMA's budget check refused them at 1.5-1.65 GB of a 1.77-1.83 GB budget on
|
||||
// the 2 GB laptop, the geometry was read over the bus and the game fell to 8-29 FPS (2026-10-26).
|
||||
static const bool system_ok = !bb_env_on("BB_MIRROR_VRAM_ONLY");
|
||||
// VRAM or none: without it VMA puts a mirror in system memory when VRAM is full, and there it
|
||||
// stays until its blocks are retired, so the GPU reads that geometry over the bus until a
|
||||
// reload (#42: an RTX 4060 at 17-27 FPS in the same places; BB_MIRROR_VRAM_ONLY=1 fixed it).
|
||||
// A mirror refused instead is made again later, when there is room. On by default on cards
|
||||
// with more than 4 GiB; small cards keep the fallback: VMA's budget check refused mirrors at
|
||||
// 1.5-1.65 GB of a 1.77-1.83 GB budget on the 2 GB laptop, the geometry was read over the bus
|
||||
// and the game fell to 8-29 FPS (2026-10-26). BB_MIRROR_VRAM_ONLY=1 / 0 forces either.
|
||||
static const bool system_ok = [this] {
|
||||
const bool vram_only = bb_env_on("BB_MIRROR_VRAM_ONLY") ? true
|
||||
: bb_env_off("BB_MIRROR_VRAM_ONLY") ? false
|
||||
: instance.GetTotalMemoryBudget() > (u64{4} << 30);
|
||||
std::printf("Layer memory: mirrors %s (BB_MIRROR_VRAM_ONLY)\n",
|
||||
vram_only ? "in VRAM only, made again later when it is full"
|
||||
: "in system memory too when VRAM is full");
|
||||
return !vram_only;
|
||||
}();
|
||||
std::optional<VramOnlyScope> vram_only;
|
||||
if (!system_ok) {
|
||||
vram_only.emplace();
|
||||
@@ -756,11 +772,15 @@ void BufferCache::LayerMaintain() {
|
||||
return u32(std::max<long>(env && *env ? std::strtol(env, nullptr, 10) : 1, 1));
|
||||
}();
|
||||
static u32 compacted_second = 0;
|
||||
// Waste no compaction could free (valid blocks closer than its Bridge): not tried again until
|
||||
// more of it comes (the mirrors were made again every second, 0 MiB freed).
|
||||
// Waste no compaction could free last time: it is not tried again until more of it comes,
|
||||
// or kRetrySeconds pass. The time matters because the mirrors that were too young (or whose
|
||||
// waste sits between their valid blocks) become eligible as they grow older and lose blocks;
|
||||
// without it a run with nothing to compact rescanned every mirror every second for nothing.
|
||||
static u64 stuck_waste = 0;
|
||||
static u32 retry_after = 0;
|
||||
if (const u32 second = BbStats::coarse_second.load(std::memory_order_relaxed);
|
||||
compact_at != 0 && second - compacted_second >= compact_every) {
|
||||
const bool retry_due = second >= retry_after;
|
||||
compacted_second = second;
|
||||
u64 waste = 0;
|
||||
for (const auto& [start, mirror] : layer_mirrors) {
|
||||
@@ -769,11 +789,23 @@ void BufferCache::LayerMaintain() {
|
||||
[&](u64 a, u64 b) { valid += b - a; });
|
||||
waste += mirror->SizeBytes() - (valid << block_shift);
|
||||
}
|
||||
// The floor only goes down with the waste: the growth is what justifies another look.
|
||||
if (waste < stuck_waste) {
|
||||
stuck_waste = waste;
|
||||
}
|
||||
if (waste >= compact_at && waste >= stuck_waste + compact_at / 2) {
|
||||
stuck_waste = LayerCompactMirrors(waste) == 0 ? waste : 0;
|
||||
// Either more waste came (compact_at / 2 of it), or the retry is due: the same waste
|
||||
// seen again later may now be in mirrors old enough to be made again.
|
||||
if (waste >= compact_at &&
|
||||
(retry_due || waste >= stuck_waste + compact_at / 2)) {
|
||||
// One call takes a bounded share of the waste: the copies it costs then spread over
|
||||
// the seconds that follow instead of landing in one long frame.
|
||||
if (LayerCompactMirrors(LayerPolicy::CompactBudget(waste)) == 0) {
|
||||
stuck_waste = waste;
|
||||
retry_after = second + LayerPolicy::kRetrySeconds;
|
||||
} else {
|
||||
stuck_waste = 0;
|
||||
retry_after = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Retired mirrors the GPU is done with. BB_LAYER_CHECK=1: no page table entry may still point
|
||||
@@ -1106,10 +1138,11 @@ void BufferCache::NoteDirectUse(VAddr address, u64 size) {
|
||||
|
||||
u64 BufferCache::LayerCompactMirrors(u64 wanted) {
|
||||
// Mirror memory without valid blocks (sent back in place, unmapped, handed out anew, the room
|
||||
// to grow) stays allocated as long as its mirror lives. Mirrors with at least MinWaste of it,
|
||||
// the most first, are made again over their valid blocks only: one new mirror per run of
|
||||
// valid blocks (runs closer than Bridge stay in one), their data copied over, the old mirror
|
||||
// retired as in a merge. No copy is lost.
|
||||
// to grow) stays allocated as long as its mirror lives. Mirrors with enough of it, the most
|
||||
// first, are made again over their valid blocks only: one new mirror per run of valid blocks
|
||||
// (the heads and tails always come off; a hole between two runs pays for the mirror it needs,
|
||||
// see LayerPolicy::MirrorSegments), their data copied over, the old mirror retired as in a
|
||||
// merge. No copy is lost.
|
||||
// Mirrors younger than BB_MIRROR_COMPACT_AGE seconds (30) are left alone unless VRAM is past the
|
||||
// texture collector's critical mark: on the 2 GB laptop compaction cut the room a mirror had
|
||||
// just grown, the next run grew it again (80, 52, 112, 64 MiB within seconds), 411 mirrors of
|
||||
@@ -1122,19 +1155,30 @@ u64 BufferCache::LayerCompactMirrors(u64 wanted) {
|
||||
const u64 critical = BbStats::gc_critical_bytes.load(std::memory_order_relaxed);
|
||||
const bool emergency =
|
||||
critical != 0 && BbStats::gc_used_bytes.load(std::memory_order_relaxed) >= critical;
|
||||
constexpr u64 MinWaste = 8_MB, Bridge = 4_MB;
|
||||
std::vector<std::pair<u64, VAddr>> wasteful; // bytes without valid blocks, start
|
||||
// Why a pass could not act, for the log: a default run that compacts nothing has to say which
|
||||
// filter stopped it (the age gate, the per-mirror threshold, waste between valid blocks, no
|
||||
// room), or every acceptance run ends in the same guess.
|
||||
size_t young = 0, below = 0, whole = 0, noroom = 0;
|
||||
u32 oldest_young = 0; // the age of the mirror the age gate held back the longest
|
||||
for (const auto& [start, mirror] : layer_mirrors) {
|
||||
if (const auto born = layer_mirror_born.find(start);
|
||||
!emergency && born != layer_mirror_born.end() && now - born->second < min_age) {
|
||||
++young;
|
||||
oldest_young = std::max(oldest_young, now - born->second);
|
||||
continue;
|
||||
}
|
||||
const u64 lo = start >> block_shift, hi = (start + mirror->SizeBytes()) >> block_shift;
|
||||
const u64 size = mirror->SizeBytes();
|
||||
const u64 lo = start >> block_shift, hi = (start + size) >> block_shift;
|
||||
u64 valid = 0;
|
||||
Layer().ForEachValid(lo, hi, [&](u64 a, u64 b) { valid += b - a; });
|
||||
const u64 waste = mirror->SizeBytes() - (valid << block_shift);
|
||||
if (waste >= MinWaste) {
|
||||
const u64 waste = size - (valid << block_shift);
|
||||
// The threshold follows the mirror's size: a fixed 8 MiB was more than a whole mirror of
|
||||
// the default run (7.8 MiB), so nothing was ever compacted there.
|
||||
if (waste >= LayerPolicy::MirrorMinWaste(size)) {
|
||||
wasteful.emplace_back(waste, start);
|
||||
} else {
|
||||
++below;
|
||||
}
|
||||
}
|
||||
std::ranges::sort(wasteful, std::greater{});
|
||||
@@ -1146,20 +1190,19 @@ u64 BufferCache::LayerCompactMirrors(u64 wanted) {
|
||||
}
|
||||
const u64 old_size = layer_mirrors.at(start)->SizeBytes();
|
||||
const u64 lo = start >> block_shift, hi = (start + old_size) >> block_shift;
|
||||
std::vector<std::pair<u64, u64>> segments;
|
||||
u64 remade = 0;
|
||||
Layer().ForEachValid(lo, hi, [&](u64 a, u64 b) {
|
||||
if (!segments.empty() && ((a - segments.back().second) << block_shift) < Bridge) {
|
||||
segments.back().second = b;
|
||||
} else {
|
||||
segments.emplace_back(a, b);
|
||||
}
|
||||
});
|
||||
for (const auto& [a, b] : segments) {
|
||||
remade += (b - a) << block_shift;
|
||||
}
|
||||
if (old_size - remade < MinWaste) {
|
||||
continue; // its waste lies between valid blocks: copies for nothing
|
||||
std::vector<LayerPolicy::Segment> runs;
|
||||
Layer().ForEachValid(lo, hi, [&](u64 a, u64 b) { runs.push_back({a, b}); });
|
||||
const auto segments = LayerPolicy::MirrorSegments(
|
||||
runs, block_shift, LayerPolicy::MirrorMinHole(old_size),
|
||||
LayerPolicy::kMaxSegmentsPerMirror);
|
||||
const u64 remade = LayerPolicy::SegmentsBytes(segments, block_shift);
|
||||
// What it would actually free. The per-mirror threshold is asked again here: a mirror
|
||||
// whose waste lies between its valid blocks comes back whole (the head and the tail are
|
||||
// all it can drop), and copying it for a few KiB is the failure of 2026-10-25 (411
|
||||
// mirrors in 8 minutes for 0.4 GB of data on the 2 GB laptop).
|
||||
if (old_size - remade < LayerPolicy::MirrorMinWaste(old_size)) {
|
||||
++whole;
|
||||
continue;
|
||||
}
|
||||
// The new mirrors first: without room in VRAM for all of them this one stays as it is.
|
||||
std::vector<std::unique_ptr<Buffer>> made;
|
||||
@@ -1173,6 +1216,7 @@ u64 BufferCache::LayerCompactMirrors(u64 wanted) {
|
||||
made.push_back(std::move(mirror));
|
||||
}
|
||||
if (made.size() != segments.size()) {
|
||||
++noroom;
|
||||
continue;
|
||||
}
|
||||
auto node = layer_mirrors.extract(start);
|
||||
@@ -1213,6 +1257,19 @@ u64 BufferCache::LayerCompactMirrors(u64 wanted) {
|
||||
std::printf("Layer memory: %zu mirrors made again over their valid blocks, "
|
||||
"%llu MiB freed\n",
|
||||
compacted, (unsigned long long)(freed >> 20));
|
||||
} else if (!layer_mirrors.empty()) {
|
||||
// Nothing came back, and the run needs to say why instead of leaving it to the next
|
||||
// acceptance run to guess: too young (the age gate), under the per-mirror threshold, waste
|
||||
// all between valid blocks (it would come back whole), or no room for the new mirrors.
|
||||
// Rate limited: the VRAM pressure path can call this every frame while it is short.
|
||||
static u32 printed = 0; // coarse seconds; 0 = not yet
|
||||
if (printed == 0 || now - printed >= 10) {
|
||||
printed = now;
|
||||
std::printf("Layer memory: nothing compacted: %zu young (age gate %u s, oldest %u s), "
|
||||
"%zu under the per-mirror waste, %zu waste between valid blocks, %zu without "
|
||||
"room\n",
|
||||
young, min_age, oldest_young, below, whole, noroom);
|
||||
}
|
||||
}
|
||||
return freed;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 bbport contributors
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// bbport: when a mirror is worth making again over its valid blocks only (BufferCache::
|
||||
// LayerCompactMirrors), and from which of its valid runs. Pure: no Vulkan, no guest, no
|
||||
// environment, so the tests cover it without a device (tests/test_layer_mirror_policy.cpp).
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
namespace VideoCore::LayerPolicy {
|
||||
|
||||
/// A run of blocks: [first, end), both in the layer's blocks.
|
||||
struct Segment {
|
||||
u64 first = 0;
|
||||
u64 end = 0;
|
||||
|
||||
bool operator==(const Segment&) const = default;
|
||||
};
|
||||
|
||||
/// The waste (bytes without valid blocks) a mirror of `bytes` must have before it is made again:
|
||||
/// a quarter of it, between 1 and 8 MiB. A mirror of 4 MiB therefore needs 1 MiB of waste, one of
|
||||
/// 8 MiB needs 2 MiB, and from 32 MiB up it is 8 MiB.
|
||||
///
|
||||
/// The fixed 8 MiB of 0.7 was the size of a whole mirror on the run it was chosen for: 223-225
|
||||
/// mirrors of 1,750 MiB (7.8 MiB each) held 612 MiB of waste between them (2.7 MiB each), so not
|
||||
/// one of them ever reached it and nothing was compacted in the whole run (default run without any
|
||||
/// BB_MIRROR_* variable, 2026-11-05).
|
||||
inline u64 MirrorMinWaste(u64 bytes) {
|
||||
return std::clamp<u64>(bytes / 4, 1_MB, 8_MB);
|
||||
}
|
||||
|
||||
/// A hole (bytes without valid blocks between two valid runs of a mirror) is only split out into
|
||||
/// a mirror of its own when it is worth one: a thirty-second of the mirror, between 256 KiB and
|
||||
/// 4 MiB. Below that the copy into the extra mirror costs more than the hole frees -- the run of
|
||||
/// 2026-10-25 made 411 mirrors in 8 minutes for 0.4 GB of data that way.
|
||||
inline u64 MirrorMinHole(u64 bytes) {
|
||||
return std::clamp<u64>(bytes / 32, 256_KB, 4_MB);
|
||||
}
|
||||
|
||||
/// The most mirrors one compacted mirror becomes. The holes that have to be kept to stay under it
|
||||
/// are the smallest ones: a mirror split into a hundred pieces costs more to bind than its waste.
|
||||
constexpr u32 kMaxSegmentsPerMirror = 8;
|
||||
|
||||
/// The waste one extra mirror has to free to be worth making: below it the split costs (a copy of
|
||||
/// the data and one more binding range) more than it gives back.
|
||||
constexpr u64 kSegmentPays = 1_MB;
|
||||
|
||||
/// The segments a mirror is made again from, given its valid runs.
|
||||
///
|
||||
/// `runs` must be sorted and must not overlap. The heads and tails (the waste before the first run
|
||||
/// and after the last) are always dropped: that costs no extra mirror, only the copy it already
|
||||
/// makes. A hole between two runs is split out when it is at least `min_hole`, while the pieces
|
||||
/// stay under `max_segments`, and only when the waste freed pays for the extra mirrors
|
||||
/// (`kSegmentPays` each). So a mirror whose waste is scattered in small holes between its data
|
||||
/// comes back as one segment and the caller sees no gain -- which is the right answer: it would
|
||||
/// have been copies for nothing.
|
||||
///
|
||||
/// The smallest holes are the ones kept when a limit is reached.
|
||||
inline std::vector<Segment> MirrorSegments(const std::vector<Segment>& runs, u64 shift,
|
||||
u64 min_hole, u32 max_segments) {
|
||||
std::vector<Segment> segments;
|
||||
if (runs.empty()) {
|
||||
return segments;
|
||||
}
|
||||
// The head and the tail are dropped for free: they come off without an extra mirror.
|
||||
const std::size_t holes = runs.size() - 1;
|
||||
const auto gap = [&](std::size_t i) {
|
||||
return (runs[i + 1].first - runs[i].end) << shift;
|
||||
};
|
||||
const u32 limit = std::max<u32>(max_segments, 1);
|
||||
// Holes worth a mirror of their own, smallest first when some have to go back.
|
||||
std::vector<std::size_t> candidates;
|
||||
for (std::size_t i = 0; i < holes; ++i) {
|
||||
if (gap(i) >= min_hole) {
|
||||
candidates.push_back(i);
|
||||
}
|
||||
}
|
||||
std::ranges::sort(candidates, {}, gap);
|
||||
// The biggest holes first, each as long as the waste it frees pays for the extra mirror it
|
||||
// costs (kSegmentPays each). The head and the tail are free: they come off without one.
|
||||
std::vector<bool> split(holes, false);
|
||||
u64 freed = 0;
|
||||
u32 pieces = 1;
|
||||
for (auto it = candidates.rbegin(); it != candidates.rend(); ++it) {
|
||||
const u32 next = pieces + 1;
|
||||
if (next > limit || freed + gap(*it) < u64(next - 1) * kSegmentPays) {
|
||||
break; // sorted biggest first: nothing after this one pays either
|
||||
}
|
||||
split[*it] = true;
|
||||
freed += gap(*it);
|
||||
pieces = next;
|
||||
}
|
||||
Segment current{runs.front().first, runs.front().end};
|
||||
for (std::size_t i = 0; i < holes; ++i) {
|
||||
if (split[i]) {
|
||||
segments.push_back(current);
|
||||
current = Segment{runs[i + 1].first, runs[i + 1].end};
|
||||
} else {
|
||||
current.end = runs[i + 1].end;
|
||||
}
|
||||
}
|
||||
segments.push_back(current);
|
||||
return segments;
|
||||
}
|
||||
|
||||
/// The bytes the segments hold, to compare with the mirror's size.
|
||||
inline u64 SegmentsBytes(const std::vector<Segment>& segments, u64 shift) {
|
||||
u64 bytes = 0;
|
||||
for (const auto& segment : segments) {
|
||||
bytes += (segment.end - segment.first) << shift;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// After a compaction that freed nothing, the next attempt waits this long: the mirrors it looked
|
||||
/// at could not be made again (their waste all lies between valid blocks, or they are too young),
|
||||
/// and rescanning them every second changes nothing while costing the whole scan.
|
||||
constexpr u32 kRetrySeconds = 30;
|
||||
|
||||
/// The waste one compaction call takes, so the copies it costs are spread over the seconds that
|
||||
/// follow instead of landing in one long frame. The pressure path passes its own need instead.
|
||||
inline u64 CompactBudget(u64 waste) {
|
||||
return std::min<u64>(waste, 32_MB);
|
||||
}
|
||||
|
||||
} // namespace VideoCore::LayerPolicy
|
||||
@@ -261,7 +261,7 @@ BB_ENV(BB_MIRROR_MERGE_GROWTH, TUNING, "=0: a mirror made over neighbours grows
|
||||
BB_ENV(BB_MIRROR_POOL_MB, TUNING, "=N: retired mirrors kept for new ones")
|
||||
BB_ENV(BB_MIRROR_REPROMOTE_SECONDS, TUNING, "=N (default 0): blocks sent back are not copied again that soon")
|
||||
BB_ENV(BB_MIRROR_UNBOUND_SECONDS, TUNING, "=N (0 never): blocks no binding named for N seconds leave VRAM")
|
||||
BB_ENV(BB_MIRROR_VRAM_ONLY, TUNING, "=1: mirrors in VRAM or not at all")
|
||||
BB_ENV(BB_MIRROR_VRAM_ONLY, TUNING, "=1 / 0: mirrors in VRAM or not at all, or in system memory too when VRAM is full (default: VRAM only on cards over 4 GiB)")
|
||||
BB_ENV(BB_MOTION_LARGE, TUNING, "=0: bone palettes of 16-64 KiB (the large beasts) get camera motion vectors only, not object motion")
|
||||
BB_ENV(BB_MOTION_VERTICES_MI, TUNING, "=N (1-8): object motion vertex buffer in Mi vertices")
|
||||
BB_ENV(BB_NET_MODULE, TUNING, "=path: the online module to load (default gpu/libbbnet20.so beside bb-probe)")
|
||||
|
||||
+42
-14
@@ -863,7 +863,7 @@ EN = {
|
||||
'Вылеты при загрузке областей': 'Crashes while loading areas',
|
||||
'Интерфейс, пропадавший не в 1080p, векторы движения больших чудовищ и ошибка памяти': 'The interface vanishing outside 1080p, motion vectors for the large beasts and a memory fault',
|
||||
'Вылеты при загрузке некоторых карт, меню жестов с клавиатуры и изменения изображения для апскейлера': 'Crashes when loading some maps, the gestures menu from the keyboard, and image changes for the upscaler',
|
||||
'Экранная клавиатура для имени персонажа и уведомление о компиляции шейдеров': "The on-screen keyboard for the character's name and the shader compiling notice",
|
||||
'Экранная клавиатура для имени персонажа, уведомление о компиляции шейдеров и уплотнение копий памяти в видеопамяти': "The on-screen keyboard for the character's name, the shader compiling notice and compacting the memory copies in VRAM",
|
||||
"Быстрая загрузка после смерти и перемещения (bbhost) и сервер The Hunter's Dream": "Fast loading after a death or travel (bbhost) and The Hunter's Dream server",
|
||||
'Сетевые библиотеки игры по сети и сервер для своего кооператива': 'The online play libraries and the server for your own co-op',
|
||||
'Основа поддержки DLSS и модель FSR 4 без шлейфов за персонажами': 'The groundwork for DLSS, and the FSR 4 model without trails behind characters',
|
||||
@@ -890,7 +890,11 @@ EN = {
|
||||
'Вылет «Transaction failed» на Seamless Hunt': 'The "Transaction failed" crash on Seamless Hunt',
|
||||
'Вылет «Transaction failed» на Seamless Hunt, журналы, по которым гость BBSC перестал следовать за чужим хозяином, и проверки BBSC 0.0.2 с игроком на Windows': 'The "Transaction failed" crash on Seamless Hunt, the logs that stopped a BBSC guest from following a stranger\'s world, and testing BBSC 0.0.2 with a Windows player',
|
||||
'Страница трофеев, не находившая файлы дампа': "The Trophies page not finding the dump's files",
|
||||
'Журналы и проверки: зависание GPU в битве с боссом на NVIDIA, вылет в Яхар’гуле, растянутые модели, вылет на Steam Deck, чёрные эффекты, радужные текстуры и вылеты в менеджере памяти игры': "Logs and tests: a GPU freeze in a boss fight on NVIDIA, a crash in Yahar'gul, stretched models, a crash on the Steam Deck, effects turning black, rainbow textures and crashes in the game's memory manager",
|
||||
'Журналы и проверки: зависание GPU в битве с боссом на NVIDIA, вылет в Яхар’гуле, растянутые модели, вылет на Steam Deck, чёрные эффекты, радужные текстуры, вылеты в менеджере памяти игры, падения частоты кадров на NVIDIA и выход на рабочий стол при игре по сети': "Logs and tests: a GPU freeze in a boss fight on NVIDIA, a crash in Yahar'gul, stretched models, a crash on the Steam Deck, effects turning black, rainbow textures, crashes in the game's memory manager, frame rate drops on NVIDIA and Exit to Desktop with online play",
|
||||
'Запуск игры на встроенных видеокартах Intel Arc/Xe': 'The game starting on integrated Intel Arc/Xe GPUs',
|
||||
'Журналы и проверки: процессоры AMD FX, полноэкранный режим в Wayland с несколькими мониторами и версия AppImage в Gear Lever': "Logs and tests: AMD FX processors, fullscreen on Wayland with several monitors and the AppImage's version in Gear Lever",
|
||||
'Хранилище с клавиатурой и мышью': 'The storage box with keyboard and mouse',
|
||||
'Журналы падения частоты кадров на NVIDIA': 'Logs of frame rate drops on NVIDIA',
|
||||
'Отметка трофеев, уже полученных на PS4': 'Marking trophies already earned on the PS4',
|
||||
'Предметы BBSC, которые не выдавались новому персонажу': "BBSC's items not given to a new character",
|
||||
'Пустое окно лаунчера при запуске': "The launcher's blank window at start",
|
||||
@@ -1949,7 +1953,7 @@ PT_BR = {
|
||||
'Вылеты при загрузке областей': 'Travamentos ao carregar áreas',
|
||||
'Интерфейс, пропадавший не в 1080p, векторы движения больших чудовищ и ошибка памяти': 'A interface que sumia fora de 1080p, vetores de movimento das grandes feras e uma falha de memória',
|
||||
'Вылеты при загрузке некоторых карт, меню жестов с клавиатуры и изменения изображения для апскейлера': 'Travamentos ao carregar alguns mapas, o menu de gestos pelo teclado e mudanças de imagem para o upscaler',
|
||||
'Экранная клавиатура для имени персонажа и уведомление о компиляции шейдеров': 'O teclado na tela para o nome do personagem e o aviso de compilação de shaders',
|
||||
'Экранная клавиатура для имени персонажа, уведомление о компиляции шейдеров и уплотнение копий памяти в видеопамяти': 'O teclado na tela para o nome do personagem, o aviso de compilação de shaders e a compactação das cópias de memória na VRAM',
|
||||
"Быстрая загрузка после смерти и перемещения (bbhost) и сервер The Hunter's Dream": "Carregamento rápido após morrer ou viajar (bbhost) e o servidor The Hunter's Dream",
|
||||
'Сетевые библиотеки игры по сети и сервер для своего кооператива': 'As bibliotecas do jogo online e o servidor para o seu próprio cooperativo',
|
||||
'Основа поддержки DLSS и модель FSR 4 без шлейфов за персонажами': 'A base do suporte a DLSS e o modelo do FSR 4 sem rastros atrás dos personagens',
|
||||
@@ -1976,7 +1980,11 @@ PT_BR = {
|
||||
'Вылет «Transaction failed» на Seamless Hunt': 'O travamento "Transaction failed" no Seamless Hunt',
|
||||
'Вылет «Transaction failed» на Seamless Hunt, журналы, по которым гость BBSC перестал следовать за чужим хозяином, и проверки BBSC 0.0.2 с игроком на Windows': 'O crash "Transaction failed" no Seamless Hunt, os logs que fizeram o convidado do BBSC parar de seguir o mundo de um estranho e os testes do BBSC 0.0.2 com um jogador no Windows',
|
||||
'Страница трофеев, не находившая файлы дампа': 'A página de troféus que não encontrava os arquivos do dump',
|
||||
'Журналы и проверки: зависание GPU в битве с боссом на NVIDIA, вылет в Яхар’гуле, растянутые модели, вылет на Steam Deck, чёрные эффекты, радужные текстуры и вылеты в менеджере памяти игры': "Logs e testes: um congelamento da GPU numa luta contra chefe na NVIDIA, um crash em Yahar'gul, modelos esticados, um crash no Steam Deck, efeitos ficando pretos, texturas arco-íris e crashes no gerenciador de memória do jogo",
|
||||
'Журналы и проверки: зависание GPU в битве с боссом на NVIDIA, вылет в Яхар’гуле, растянутые модели, вылет на Steam Deck, чёрные эффекты, радужные текстуры, вылеты в менеджере памяти игры, падения частоты кадров на NVIDIA и выход на рабочий стол при игре по сети': "Logs e testes: um congelamento da GPU numa luta contra chefe na NVIDIA, um crash em Yahar'gul, modelos esticados, um crash no Steam Deck, efeitos ficando pretos, texturas arco-íris, crashes no gerenciador de memória do jogo, quedas de FPS na NVIDIA e Sair para a área de trabalho jogando online",
|
||||
'Запуск игры на встроенных видеокартах Intel Arc/Xe': 'O jogo iniciando em GPUs integradas Intel Arc/Xe',
|
||||
'Журналы и проверки: процессоры AMD FX, полноэкранный режим в Wayland с несколькими мониторами и версия AppImage в Gear Lever': 'Logs e testes: processadores AMD FX, tela cheia no Wayland com vários monitores e a versão da AppImage no Gear Lever',
|
||||
'Хранилище с клавиатурой и мышью': 'O baú de armazenamento com teclado e mouse',
|
||||
'Журналы падения частоты кадров на NVIDIA': 'Logs de quedas de FPS na NVIDIA',
|
||||
'Отметка трофеев, уже полученных на PS4': 'Marcar troféus já conquistados no PS4',
|
||||
'Предметы BBSC, которые не выдавались новому персонажу': 'Itens do BBSC que não eram dados a um novo personagem',
|
||||
'Пустое окно лаунчера при запуске': 'A janela em branco do launcher ao iniciar',
|
||||
@@ -2950,7 +2958,7 @@ ES = {
|
||||
'Вылеты при загрузке областей': 'Cierres al cargar zonas',
|
||||
'Интерфейс, пропадавший не в 1080p, векторы движения больших чудовищ и ошибка памяти': 'La interfaz que desaparecía fuera de 1080p, los vectores de movimiento de las bestias grandes y un fallo de memoria',
|
||||
'Вылеты при загрузке некоторых карт, меню жестов с клавиатуры и изменения изображения для апскейлера': 'Cierres al cargar algunos mapas, el menú de gestos desde el teclado y cambios de imagen para el escalador',
|
||||
'Экранная клавиатура для имени персонажа и уведомление о компиляции шейдеров': 'El teclado en pantalla para el nombre del personaje y el aviso de compilación de shaders',
|
||||
'Экранная клавиатура для имени персонажа, уведомление о компиляции шейдеров и уплотнение копий памяти в видеопамяти': 'El teclado en pantalla para el nombre del personaje, el aviso de compilación de shaders y la compactación de las copias de memoria en la VRAM',
|
||||
"Быстрая загрузка после смерти и перемещения (bbhost) и сервер The Hunter's Dream": "Cargas rápidas tras morir o viajar (bbhost) y el servidor The Hunter's Dream",
|
||||
'Сетевые библиотеки игры по сети и сервер для своего кооператива': 'Las librerías del juego online y el servidor para el cooperativo propio',
|
||||
'Основа поддержки DLSS и модель FSR 4 без шлейфов за персонажами': 'La base del soporte de DLSS y el modelo de FSR 4 sin estelas tras los personajes',
|
||||
@@ -2977,7 +2985,11 @@ ES = {
|
||||
'Вылет «Transaction failed» на Seamless Hunt': 'El cierre «Transaction failed» en Seamless Hunt',
|
||||
'Вылет «Transaction failed» на Seamless Hunt, журналы, по которым гость BBSC перестал следовать за чужим хозяином, и проверки BBSC 0.0.2 с игроком на Windows': 'El cierre «Transaction failed» en Seamless Hunt, los registros que hicieron que el invitado de BBSC dejara de seguir el mundo de un desconocido y las pruebas de BBSC 0.0.2 con un jugador en Windows',
|
||||
'Страница трофеев, не находившая файлы дампа': 'La página de trofeos que no encontraba los archivos del volcado',
|
||||
'Журналы и проверки: зависание GPU в битве с боссом на NVIDIA, вылет в Яхар’гуле, растянутые модели, вылет на Steam Deck, чёрные эффекты, радужные текстуры и вылеты в менеджере памяти игры': "Registros y pruebas: un cuelgue de la GPU en un jefe con NVIDIA, un cierre en Yahar'gul, modelos estirados, un cierre en Steam Deck, efectos que se vuelven negros, texturas arcoíris y cierres en el gestor de memoria del juego",
|
||||
'Журналы и проверки: зависание GPU в битве с боссом на NVIDIA, вылет в Яхар’гуле, растянутые модели, вылет на Steam Deck, чёрные эффекты, радужные текстуры, вылеты в менеджере памяти игры, падения частоты кадров на NVIDIA и выход на рабочий стол при игре по сети': "Registros y pruebas: un cuelgue de la GPU en un jefe con NVIDIA, un cierre en Yahar'gul, modelos estirados, un cierre en Steam Deck, efectos que se vuelven negros, texturas arcoíris, cierres en el gestor de memoria del juego, bajadas de FPS con NVIDIA y Salir al escritorio jugando online",
|
||||
'Запуск игры на встроенных видеокартах Intel Arc/Xe': 'El juego arrancando en gráficas integradas Intel Arc/Xe',
|
||||
'Журналы и проверки: процессоры AMD FX, полноэкранный режим в Wayland с несколькими мониторами и версия AppImage в Gear Lever': 'Registros y pruebas: procesadores AMD FX, pantalla completa en Wayland con varios monitores y la versión de la AppImage en Gear Lever',
|
||||
'Хранилище с клавиатурой и мышью': 'El almacén con teclado y ratón',
|
||||
'Журналы падения частоты кадров на NVIDIA': 'Registros de bajadas de FPS con NVIDIA',
|
||||
'Отметка трофеев, уже полученных на PS4': 'Marcar trofeos ya conseguidos en PS4',
|
||||
'Предметы BBSC, которые не выдавались новому персонажу': 'Los objetos de BBSC que no recibía un personaje nuevo',
|
||||
'Пустое окно лаунчера при запуске': 'La ventana en blanco del launcher al abrirlo',
|
||||
@@ -3951,7 +3963,7 @@ IT = {
|
||||
'Вылеты при загрузке областей': 'Crash durante il caricamento delle aree',
|
||||
'Интерфейс, пропадавший не в 1080p, векторы движения больших чудовищ и ошибка памяти': 'L’interfaccia che spariva fuori da 1080p, i vettori di movimento delle grandi bestie e un errore di memoria',
|
||||
'Вылеты при загрузке некоторых карт, меню жестов с клавиатуры и изменения изображения для апскейлера': "Crash al caricamento di alcune mappe, il menu dei gesti da tastiera e modifiche all'immagine per l'upscaler",
|
||||
'Экранная клавиатура для имени персонажа и уведомление о компиляции шейдеров': 'La tastiera su schermo per il nome del personaggio e l’avviso di compilazione degli shader',
|
||||
'Экранная клавиатура для имени персонажа, уведомление о компиляции шейдеров и уплотнение копий памяти в видеопамяти': 'La tastiera su schermo per il nome del personaggio, l’avviso di compilazione degli shader e la compattazione delle copie di memoria nella VRAM',
|
||||
"Быстрая загрузка после смерти и перемещения (bbhost) и сервер The Hunter's Dream": "Caricamenti rapidi dopo una morte o un viaggio (bbhost) e il server The Hunter's Dream",
|
||||
'Сетевые библиотеки игры по сети и сервер для своего кооператива': 'Le librerie del gioco online e il server per la propria cooperativa',
|
||||
'Основа поддержки DLSS и модель FSR 4 без шлейфов за персонажами': 'La base del supporto DLSS e il modello FSR 4 senza scie dietro i personaggi',
|
||||
@@ -3978,7 +3990,11 @@ IT = {
|
||||
'Вылет «Transaction failed» на Seamless Hunt': 'La chiusura "Transaction failed" su Seamless Hunt',
|
||||
'Вылет «Transaction failed» на Seamless Hunt, журналы, по которым гость BBSC перестал следовать за чужим хозяином, и проверки BBSC 0.0.2 с игроком на Windows': "Il crash «Transaction failed» su Seamless Hunt, i log che hanno fatto smettere all'ospite di BBSC di seguire il mondo di uno sconosciuto e i test di BBSC 0.0.2 con un giocatore su Windows",
|
||||
'Страница трофеев, не находившая файлы дампа': 'La pagina dei trofei che non trovava i file del dump',
|
||||
'Журналы и проверки: зависание GPU в битве с боссом на NVIDIA, вылет в Яхар’гуле, растянутые модели, вылет на Steam Deck, чёрные эффекты, радужные текстуры и вылеты в менеджере памяти игры': "Log e test: un blocco della GPU in una battaglia contro un boss su NVIDIA, un crash a Yahar'gul, modelli stirati, un crash su Steam Deck, effetti che diventano neri, texture arcobaleno e crash nel gestore della memoria del gioco",
|
||||
'Журналы и проверки: зависание GPU в битве с боссом на NVIDIA, вылет в Яхар’гуле, растянутые модели, вылет на Steam Deck, чёрные эффекты, радужные текстуры, вылеты в менеджере памяти игры, падения частоты кадров на NVIDIA и выход на рабочий стол при игре по сети': "Log e test: un blocco della GPU in una battaglia contro un boss su NVIDIA, un crash a Yahar'gul, modelli stirati, un crash su Steam Deck, effetti che diventano neri, texture arcobaleno, crash nel gestore della memoria del gioco, cali di FPS su NVIDIA ed Esci al desktop giocando online",
|
||||
'Запуск игры на встроенных видеокартах Intel Arc/Xe': 'Il gioco che si avvia sulle GPU integrate Intel Arc/Xe',
|
||||
'Журналы и проверки: процессоры AMD FX, полноэкранный режим в Wayland с несколькими мониторами и версия AppImage в Gear Lever': "Log e test: processori AMD FX, schermo intero su Wayland con più monitor e la versione dell'AppImage in Gear Lever",
|
||||
'Хранилище с клавиатурой и мышью': 'Il deposito con tastiera e mouse',
|
||||
'Журналы падения частоты кадров на NVIDIA': 'Log dei cali di FPS su NVIDIA',
|
||||
'Отметка трофеев, уже полученных на PS4': 'Segnare i trofei già ottenuti su PS4',
|
||||
'Предметы BBSC, которые не выдавались новому персонажу': 'Gli oggetti di BBSC non dati a un nuovo personaggio',
|
||||
'Пустое окно лаунчера при запуске': "La finestra vuota del launcher all'avvio",
|
||||
@@ -4952,7 +4968,7 @@ FR = {
|
||||
'Вылеты при загрузке областей': 'Plantages au chargement des zones',
|
||||
'Интерфейс, пропадавший не в 1080p, векторы движения больших чудовищ и ошибка памяти': 'L’interface qui disparaissait hors 1080p, les vecteurs de mouvement des grandes bêtes et une erreur mémoire',
|
||||
'Вылеты при загрузке некоторых карт, меню жестов с клавиатуры и изменения изображения для апскейлера': "Plantages au chargement de certaines cartes, le menu des gestes au clavier et des changements d'image pour l'upscaler",
|
||||
'Экранная клавиатура для имени персонажа и уведомление о компиляции шейдеров': 'Le clavier à l’écran pour le nom du personnage et l’avis de compilation des shaders',
|
||||
'Экранная клавиатура для имени персонажа, уведомление о компиляции шейдеров и уплотнение копий памяти в видеопамяти': 'Le clavier à l’écran pour le nom du personnage, l’avis de compilation des shaders et le compactage des copies de mémoire en VRAM',
|
||||
"Быстрая загрузка после смерти и перемещения (bbhost) и сервер The Hunter's Dream": "Chargements rapides après une mort ou un voyage (bbhost) et le serveur The Hunter's Dream",
|
||||
'Сетевые библиотеки игры по сети и сервер для своего кооператива': 'Les bibliothèques du jeu en ligne et le serveur pour votre propre coopération',
|
||||
'Основа поддержки DLSS и модель FSR 4 без шлейфов за персонажами': 'La base de la prise en charge de DLSS et le modèle FSR 4 sans traînées derrière les personnages',
|
||||
@@ -4979,7 +4995,11 @@ FR = {
|
||||
'Вылет «Transaction failed» на Seamless Hunt': 'Le plantage « Transaction failed » sur Seamless Hunt',
|
||||
'Вылет «Transaction failed» на Seamless Hunt, журналы, по которым гость BBSC перестал следовать за чужим хозяином, и проверки BBSC 0.0.2 с игроком на Windows': "Le plantage « Transaction failed » sur Seamless Hunt, les journaux qui ont empêché l'invité de BBSC de suivre le monde d'un inconnu, et les tests de BBSC 0.0.2 avec un joueur sous Windows",
|
||||
'Страница трофеев, не находившая файлы дампа': 'La page des trophées qui ne trouvait pas les fichiers du dump',
|
||||
'Журналы и проверки: зависание GPU в битве с боссом на NVIDIA, вылет в Яхар’гуле, растянутые модели, вылет на Steam Deck, чёрные эффекты, радужные текстуры и вылеты в менеджере памяти игры': "Journaux et tests : un gel du GPU pendant un combat de boss sur NVIDIA, un plantage à Yahar'gul, des modèles étirés, un plantage sur Steam Deck, des effets devenus noirs, des textures arc-en-ciel et des plantages dans le gestionnaire de mémoire du jeu",
|
||||
'Журналы и проверки: зависание GPU в битве с боссом на NVIDIA, вылет в Яхар’гуле, растянутые модели, вылет на Steam Deck, чёрные эффекты, радужные текстуры, вылеты в менеджере памяти игры, падения частоты кадров на NVIDIA и выход на рабочий стол при игре по сети': "Journaux et tests : un gel du GPU pendant un combat de boss sur NVIDIA, un plantage à Yahar'gul, des modèles étirés, un plantage sur Steam Deck, des effets devenus noirs, des textures arc-en-ciel, des plantages dans le gestionnaire de mémoire du jeu, des chutes de FPS sur NVIDIA et Quitter vers le bureau en jouant en ligne",
|
||||
'Запуск игры на встроенных видеокартах Intel Arc/Xe': 'Le jeu qui démarre sur les GPU intégrés Intel Arc/Xe',
|
||||
'Журналы и проверки: процессоры AMD FX, полноэкранный режим в Wayland с несколькими мониторами и версия AppImage в Gear Lever': "Journaux et tests : processeurs AMD FX, plein écran sous Wayland avec plusieurs écrans et la version de l'AppImage dans Gear Lever",
|
||||
'Хранилище с клавиатурой и мышью': 'Le coffre de stockage au clavier et à la souris',
|
||||
'Журналы падения частоты кадров на NVIDIA': 'Journaux de chutes de FPS sur NVIDIA',
|
||||
'Отметка трофеев, уже полученных на PS4': 'Marquer les trophées déjà obtenus sur PS4',
|
||||
'Предметы BBSC, которые не выдавались новому персонажу': 'Les objets de BBSC non donnés à un nouveau personnage',
|
||||
'Пустое окно лаунчера при запуске': 'La fenêtre vide du launcher au démarrage',
|
||||
@@ -5953,7 +5973,7 @@ DE = {
|
||||
'Вылеты при загрузке областей': 'Abstürze beim Laden von Gebieten',
|
||||
'Интерфейс, пропадавший не в 1080p, векторы движения больших чудовищ и ошибка памяти': 'Die Oberfläche, die außerhalb von 1080p verschwand, Bewegungsvektoren der großen Bestien und ein Speicherfehler',
|
||||
'Вылеты при загрузке некоторых карт, меню жестов с клавиатуры и изменения изображения для апскейлера': 'Abstürze beim Laden einiger Karten, das Gestenmenü per Tastatur und Bildänderungen für den Upscaler',
|
||||
'Экранная клавиатура для имени персонажа и уведомление о компиляции шейдеров': 'Die Bildschirmtastatur für den Charakternamen und der Hinweis zum Kompilieren der Shader',
|
||||
'Экранная клавиатура для имени персонажа, уведомление о компиляции шейдеров и уплотнение копий памяти в видеопамяти': 'Die Bildschirmtastatur für den Charakternamen, der Hinweis zum Kompilieren der Shader und das Verdichten der Speicherkopien im VRAM',
|
||||
"Быстрая загрузка после смерти и перемещения (bbhost) и сервер The Hunter's Dream": "Schnelles Laden nach Tod oder Reise (bbhost) und der Server The Hunter's Dream",
|
||||
'Сетевые библиотеки игры по сети и сервер для своего кооператива': 'Die Bibliotheken für das Online-Spiel und der Server für den eigenen Koop',
|
||||
'Основа поддержки DLSS и модель FSR 4 без шлейфов за персонажами': 'Die Grundlage für DLSS und das FSR-4-Modell ohne Schlieren hinter Figuren',
|
||||
@@ -5980,7 +6000,11 @@ DE = {
|
||||
'Вылет «Transaction failed» на Seamless Hunt': 'Der Absturz „Transaction failed“ auf Seamless Hunt',
|
||||
'Вылет «Transaction failed» на Seamless Hunt, журналы, по которым гость BBSC перестал следовать за чужим хозяином, и проверки BBSC 0.0.2 с игроком на Windows': 'Der Absturz „Transaction failed“ auf Seamless Hunt, die Logs, durch die ein BBSC-Gast nicht mehr der Welt eines Fremden folgt, und Tests von BBSC 0.0.2 mit einem Windows-Spieler',
|
||||
'Страница трофеев, не находившая файлы дампа': 'Die Trophäenseite, die die Dateien des Dumps nicht fand',
|
||||
'Журналы и проверки: зависание GPU в битве с боссом на NVIDIA, вылет в Яхар’гуле, растянутые модели, вылет на Steam Deck, чёрные эффекты, радужные текстуры и вылеты в менеджере памяти игры': "Logs und Tests: ein GPU-Hänger in einem Bosskampf auf NVIDIA, ein Absturz in Yahar'gul, verzerrte Modelle, ein Absturz auf dem Steam Deck, schwarz werdende Effekte, Regenbogentexturen und Abstürze in der Speicherverwaltung des Spiels",
|
||||
'Журналы и проверки: зависание GPU в битве с боссом на NVIDIA, вылет в Яхар’гуле, растянутые модели, вылет на Steam Deck, чёрные эффекты, радужные текстуры, вылеты в менеджере памяти игры, падения частоты кадров на NVIDIA и выход на рабочий стол при игре по сети': "Logs und Tests: ein GPU-Hänger in einem Bosskampf auf NVIDIA, ein Absturz in Yahar'gul, verzerrte Modelle, ein Absturz auf dem Steam Deck, schwarz werdende Effekte, Regenbogentexturen, Abstürze in der Speicherverwaltung des Spiels, FPS-Einbrüche auf NVIDIA und Zum Desktop beenden im Online-Spiel",
|
||||
'Запуск игры на встроенных видеокартах Intel Arc/Xe': 'Der Spielstart auf integrierten Intel-Arc/Xe-GPUs',
|
||||
'Журналы и проверки: процессоры AMD FX, полноэкранный режим в Wayland с несколькими мониторами и версия AppImage в Gear Lever': 'Logs und Tests: AMD-FX-Prozessoren, Vollbild unter Wayland mit mehreren Monitoren und die Version des AppImage in Gear Lever',
|
||||
'Хранилище с клавиатурой и мышью': 'Die Lagerkiste mit Tastatur und Maus',
|
||||
'Журналы падения частоты кадров на NVIDIA': 'Logs von FPS-Einbrüchen auf NVIDIA',
|
||||
'Отметка трофеев, уже полученных на PS4': 'Auf der PS4 bereits erhaltene Trophäen markieren',
|
||||
'Предметы BBSC, которые не выдавались новому персонажу': 'BBSC-Gegenstände, die ein neuer Charakter nicht bekam',
|
||||
'Пустое окно лаунчера при запуске': 'Das leere Launcher-Fenster beim Start',
|
||||
@@ -6923,7 +6947,7 @@ ZH_CN = {
|
||||
'Вылеты при загрузке областей': '加载区域时的崩溃',
|
||||
'Интерфейс, пропадавший не в 1080p, векторы движения больших чудовищ и ошибка памяти': '非 1080p 下消失的界面、大型野兽的运动矢量与一处内存错误',
|
||||
'Вылеты при загрузке некоторых карт, меню жестов с клавиатуры и изменения изображения для апскейлера': '加载部分地图时的崩溃、用键盘打开的手势菜单,以及超分辨率的画面改动',
|
||||
'Экранная клавиатура для имени персонажа и уведомление о компиляции шейдеров': '角色名的屏幕键盘与着色器编译提示',
|
||||
'Экранная клавиатура для имени персонажа, уведомление о компиляции шейдеров и уплотнение копий памяти в видеопамяти': '角色名的屏幕键盘、着色器编译提示,以及显存中内存副本的压缩',
|
||||
"Быстрая загрузка после смерти и перемещения (bbhost) и сервер The Hunter's Dream": "死亡或传送后的快速加载(bbhost)与 The Hunter's Dream 服务器",
|
||||
'Сетевые библиотеки игры по сети и сервер для своего кооператива': '联机库与自建合作服务器',
|
||||
'Основа поддержки DLSS и модель FSR 4 без шлейфов за персонажами': 'DLSS 支持的基础,以及角色身后无拖影的 FSR 4 模型',
|
||||
@@ -6950,7 +6974,11 @@ ZH_CN = {
|
||||
'Вылет «Transaction failed» на Seamless Hunt': 'Seamless Hunt 上的“Transaction failed”崩溃',
|
||||
'Вылет «Transaction failed» на Seamless Hunt, журналы, по которым гость BBSC перестал следовать за чужим хозяином, и проверки BBSC 0.0.2 с игроком на Windows': 'Seamless Hunt 上的“Transaction failed”崩溃、让 BBSC 访客不再跟随陌生人世界的日志,以及与 Windows 玩家一起测试 BBSC 0.0.2',
|
||||
'Страница трофеев, не находившая файлы дампа': '找不到转储文件的奖杯页面',
|
||||
'Журналы и проверки: зависание GPU в битве с боссом на NVIDIA, вылет в Яхар’гуле, растянутые модели, вылет на Steam Deck, чёрные эффекты, радужные текстуры и вылеты в менеджере памяти игры': "日志与测试:NVIDIA 上首领战中的 GPU 卡死、Yahar'gul 的崩溃、被拉伸的模型、Steam Deck 上的崩溃、变黑的特效、彩虹色纹理,以及游戏内存管理器中的崩溃",
|
||||
'Журналы и проверки: зависание GPU в битве с боссом на NVIDIA, вылет в Яхар’гуле, растянутые модели, вылет на Steam Deck, чёрные эффекты, радужные текстуры, вылеты в менеджере памяти игры, падения частоты кадров на NVIDIA и выход на рабочий стол при игре по сети': "日志与测试:NVIDIA 上首领战中的 GPU 卡死、Yahar'gul 的崩溃、被拉伸的模型、Steam Deck 上的崩溃、变黑的特效、彩虹色纹理、游戏内存管理器中的崩溃、NVIDIA 上的帧率下降,以及联机时的退出到桌面",
|
||||
'Запуск игры на встроенных видеокартах Intel Arc/Xe': '游戏在 Intel Arc/Xe 集成显卡上启动',
|
||||
'Журналы и проверки: процессоры AMD FX, полноэкранный режим в Wayland с несколькими мониторами и версия AppImage в Gear Lever': '日志与测试:AMD FX 处理器、Wayland 多显示器下的全屏,以及 Gear Lever 中显示的 AppImage 版本',
|
||||
'Хранилище с клавиатурой и мышью': '使用键盘和鼠标操作储物箱',
|
||||
'Журналы падения частоты кадров на NVIDIA': 'NVIDIA 上帧率下降的日志',
|
||||
'Отметка трофеев, уже полученных на PS4': '标记已在 PS4 上获得的奖杯',
|
||||
'Предметы BBSC, которые не выдавались новому персонажу': '新角色拿不到 BBSC 物品的问题',
|
||||
'Пустое окно лаунчера при запуске': '启动时启动器窗口空白的问题',
|
||||
|
||||
@@ -1031,8 +1031,9 @@ THANKS = (
|
||||
_github("GoncaloLobo0", "Вылеты при загрузке областей"),
|
||||
_github("bmy", "Векторы движения больших чудовищ и ошибка памяти"),
|
||||
_github("Mrsuss60", "Меню жестов с клавиатуры, слот удалённого изображения и журнал неудачных резервирований памяти"),
|
||||
_github("SrShadowy", "Экранная клавиатура для имени персонажа и уведомление о компиляции "
|
||||
"шейдеров"),
|
||||
_github("SrShadowy", "Экранная клавиатура для имени персонажа, уведомление о компиляции "
|
||||
"шейдеров и уплотнение копий памяти в видеопамяти"),
|
||||
_github("Wooloomooloo2", "Запуск игры на встроенных видеокартах Intel Arc/Xe"),
|
||||
_github("droogie", "Быстрая загрузка после смерти и перемещения (bbhost) и сервер "
|
||||
"The Hunter's Dream"),
|
||||
_github("Wozzardman", "Сервер для своего кооператива и бесшовный призыв BBSC"),
|
||||
@@ -1079,7 +1080,8 @@ THANKS = (
|
||||
_github("simo1730", "Страница трофеев, не находившая файлы дампа"),
|
||||
_github("Nahnahchi", "Журналы и проверки: зависание GPU в битве с боссом на NVIDIA, вылет "
|
||||
"в Яхар’гуле, растянутые модели, вылет на Steam Deck, чёрные эффекты, "
|
||||
"радужные текстуры и вылеты в менеджере памяти игры"),
|
||||
"радужные текстуры, вылеты в менеджере памяти игры, падения частоты "
|
||||
"кадров на NVIDIA и выход на рабочий стол при игре по сети"),
|
||||
_github("thomasgarafano1", "Hunter's Atlas, не готовивший карту с дампом GOTY, и проверка исправления"),
|
||||
_github("TacticalBill", "Отметка трофеев, уже полученных на PS4"),
|
||||
_github("holydingus", "Предметы BBSC, которые не выдавались новому персонажу"),
|
||||
@@ -1088,6 +1090,10 @@ THANKS = (
|
||||
_github("BlazinKarp", "Оверлей Steam в игре"),
|
||||
_github("molnar-a", "Вылет на Steam Deck после путешествия через фонарь и проверка исправления"),
|
||||
_github("Aman9das", "Просьба о раскладке клавиатуры из bbport 0.6"),
|
||||
_github("eberhardweber", "Журналы и проверки: процессоры AMD FX, полноэкранный режим в "
|
||||
"Wayland с несколькими мониторами и версия AppImage в Gear Lever"),
|
||||
_github("GitBire", "Хранилище с клавиатурой и мышью"),
|
||||
_github("npc-xc", "Журналы падения частоты кадров на NVIDIA"),
|
||||
)),
|
||||
("Другие благодарности", (
|
||||
("Dommo, goomab", "Иконки кнопок Xbox и Switch (Nexus Mods)",
|
||||
|
||||
@@ -14,7 +14,7 @@ import re
|
||||
import tempfile
|
||||
import urllib.request
|
||||
|
||||
VERSION = "0.70-linux.3"
|
||||
VERSION = "0.70-linux.4"
|
||||
|
||||
|
||||
def _build():
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 bbport contributors
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// When a mirror is made again over its valid blocks only (gpu/layer/layer_mirror_policy.h).
|
||||
//
|
||||
// The default run of 0.7 without any BB_MIRROR_* variable compacted nothing for its whole length:
|
||||
// 223-225 mirrors of 1,750 MiB, 7.8 MiB each, with 612 MiB of waste between them (2.7 MiB each).
|
||||
// The fixed 8 MiB a mirror had to waste was more than a whole mirror of that run, so no mirror was
|
||||
// ever eligible. These tests pin the per-mirror share that replaced it, and the segments a mirror
|
||||
// comes back as.
|
||||
#include "layer/layer_mirror_policy.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using VideoCore::LayerPolicy::CompactBudget;
|
||||
using VideoCore::LayerPolicy::kMaxSegmentsPerMirror;
|
||||
using VideoCore::LayerPolicy::kRetrySeconds;
|
||||
using VideoCore::LayerPolicy::kSegmentPays;
|
||||
using VideoCore::LayerPolicy::MirrorMinHole;
|
||||
using VideoCore::LayerPolicy::MirrorMinWaste;
|
||||
using VideoCore::LayerPolicy::MirrorSegments;
|
||||
using VideoCore::LayerPolicy::Segment;
|
||||
using VideoCore::LayerPolicy::SegmentsBytes;
|
||||
|
||||
constexpr u64 kShift = 16; // the layer's block: 64 KiB
|
||||
constexpr u64 kBlocksPerMiB = 16; // 64 KiB blocks in a MiB
|
||||
|
||||
void TestMinWasteFollowsSize() {
|
||||
// Never below 1 MiB: a copy costs what it frees otherwise.
|
||||
assert(MirrorMinWaste(0) == 1_MB);
|
||||
assert(MirrorMinWaste(1_MB) == 1_MB);
|
||||
assert(MirrorMinWaste(4_MB) == 1_MB);
|
||||
// A quarter of the mirror while that is small enough.
|
||||
assert(MirrorMinWaste(8_MB) == 2_MB);
|
||||
assert(MirrorMinWaste(16_MB) == 4_MB);
|
||||
// 31 MiB / 4 is 7.75 MiB, still under the 8 MiB cap.
|
||||
assert(MirrorMinWaste(31_MB) == 31_MB / 4);
|
||||
// And at most 8 MiB, the whole per-mirror threshold of 0.7.
|
||||
assert(MirrorMinWaste(32_MB) == 8_MB);
|
||||
assert(MirrorMinWaste(1_GB) == 8_MB);
|
||||
|
||||
// The run that exposed it: 223 mirrors of 1,750 MiB hold 612 MiB of waste, 7.85 MiB and
|
||||
// 2.74 MiB a mirror. Under the fixed 8 MiB none qualified; under the share all of them do.
|
||||
const u64 mirror = 7'850'000; // 7.85 MiB
|
||||
const u64 waste = 2'870'000; // 2.74 MiB
|
||||
assert(MirrorMinWaste(mirror) < waste);
|
||||
assert(waste < 8_MB); // what the old fixed threshold asked for
|
||||
}
|
||||
|
||||
void TestMinHoleFollowsSize() {
|
||||
assert(MirrorMinHole(1_MB) == 256_KB);
|
||||
assert(MirrorMinHole(8_MB) == 256_KB);
|
||||
assert(MirrorMinHole(64_MB) == 2_MB);
|
||||
assert(MirrorMinHole(1_GB) == 4_MB);
|
||||
}
|
||||
|
||||
void TestNoRuns() {
|
||||
assert(MirrorSegments({}, kShift, 1_MB, kMaxSegmentsPerMirror).empty());
|
||||
}
|
||||
|
||||
void TestOneRunStaysOneSegment() {
|
||||
const std::vector<Segment> runs{{0, 100}};
|
||||
const auto segments = MirrorSegments(runs, kShift, 1_MB, kMaxSegmentsPerMirror);
|
||||
assert(segments.size() == 1);
|
||||
assert(segments[0].first == 0 && segments[0].end == 100);
|
||||
}
|
||||
|
||||
void TestHeadAndTailComeOffForFree() {
|
||||
// Valid [16, 64) in a mirror of 128 blocks (8 MiB): a head of 1 MiB and a tail of 4 MiB. Both
|
||||
// come off without an extra mirror, so all 5 MiB come back whatever the per-hole minimum says.
|
||||
const std::vector<Segment> runs{{16, 64}};
|
||||
for (const u64 min_hole : {u64{256_KB}, u64{2_MB}, u64{4_MB}}) {
|
||||
const auto segments = MirrorSegments(runs, kShift, min_hole, kMaxSegmentsPerMirror);
|
||||
assert(segments.size() == 1);
|
||||
assert(segments[0].first == 16 && segments[0].end == 64);
|
||||
assert((128 << kShift) - SegmentsBytes(segments, kShift) == 5_MB);
|
||||
}
|
||||
}
|
||||
|
||||
void TestHoleIsSplitOnlyWhenItPays() {
|
||||
// An 8 MiB mirror with two 64-block runs and a hole between them.
|
||||
const std::vector<Segment> two{{0, 64}, {72, 128}}; // the hole is 8 blocks = 512 KiB
|
||||
// The hole is under the extra mirror's worth (kSegmentPays): kept, so one segment and the
|
||||
// caller frees nothing beyond the ends.
|
||||
const auto kept = MirrorSegments(two, kShift, 256_KB, kMaxSegmentsPerMirror);
|
||||
assert(kept.size() == 1);
|
||||
assert(kept[0].first == 0 && kept[0].end == 128);
|
||||
assert(SegmentsBytes(kept, kShift) == 128 << kShift);
|
||||
|
||||
// A 2 MiB hole does pay for the mirror it costs: split in two.
|
||||
const std::vector<Segment> wide{{0, 64}, {96, 160}}; // the hole is 32 blocks = 2 MiB
|
||||
const auto split = MirrorSegments(wide, kShift, 256_KB, kMaxSegmentsPerMirror);
|
||||
assert(split.size() == 2);
|
||||
assert(split[0].first == 0 && split[0].end == 64);
|
||||
assert(split[1].first == 96 && split[1].end == 160);
|
||||
assert(SegmentsBytes(split, kShift) == (128 << kShift));
|
||||
assert((160 << kShift) - SegmentsBytes(split, kShift) == 2_MB);
|
||||
}
|
||||
|
||||
void TestWasteBetweenValidBlocksFreesNothing() {
|
||||
// Many small holes: the mirror comes back whole, so the caller skips it. This is the case the
|
||||
// old `continue` decided for every mirror.
|
||||
std::vector<Segment> runs;
|
||||
u64 at = 0;
|
||||
for (u32 i = 0; i < 40; ++i) {
|
||||
runs.push_back({at, at + 16});
|
||||
at += 16 + 1; // a 64 KiB hole after each 1 MiB run
|
||||
}
|
||||
const u64 size = runs.back().end << kShift;
|
||||
const auto segments = MirrorSegments(runs, kShift, MirrorMinHole(size), kMaxSegmentsPerMirror);
|
||||
assert(SegmentsBytes(segments, kShift) == size);
|
||||
}
|
||||
|
||||
void TestSegmentCapKeepsTheSmallestHoles() {
|
||||
// Twenty runs of 64 blocks with holes of 1..19 MiB between them.
|
||||
std::vector<Segment> runs;
|
||||
std::vector<u64> holes;
|
||||
u64 at = 0;
|
||||
for (u32 i = 1; i <= 20; ++i) {
|
||||
runs.push_back({at, at + 64});
|
||||
holes.push_back(u64(i) * kBlocksPerMiB);
|
||||
at += 64 + u64(i) * kBlocksPerMiB;
|
||||
}
|
||||
holes.pop_back(); // 19 holes between 20 runs
|
||||
|
||||
const auto segments = MirrorSegments(runs, kShift, 256_KB, kMaxSegmentsPerMirror);
|
||||
assert(segments.size() == kMaxSegmentsPerMirror);
|
||||
// Nothing is lost: every valid block is inside one of the new mirrors, and they do not
|
||||
// overlap.
|
||||
std::size_t next = 0;
|
||||
for (const auto& segment : segments) {
|
||||
assert(segment.first < segment.end);
|
||||
while (next < runs.size() && runs[next].first >= segment.first &&
|
||||
runs[next].end <= segment.end) {
|
||||
++next;
|
||||
}
|
||||
}
|
||||
assert(next == runs.size());
|
||||
for (std::size_t i = 0; i + 1 < segments.size(); ++i) {
|
||||
assert(segments[i].end <= segments[i + 1].first);
|
||||
}
|
||||
assert(segments.front().first == runs.front().first);
|
||||
assert(segments.back().end == runs.back().end);
|
||||
|
||||
// Each split is a hole, and the holes split are the biggest ones: with 8 pieces at most, 7
|
||||
// splits remain and they go to the 7 biggest holes (13..19 MiB).
|
||||
std::vector<u64> split_holes;
|
||||
for (std::size_t i = 0; i + 1 < segments.size(); ++i) {
|
||||
split_holes.push_back(segments[i + 1].first - segments[i].end);
|
||||
}
|
||||
const std::vector<u64> expected(holes.end() - 7, holes.end());
|
||||
assert(split_holes == expected);
|
||||
}
|
||||
|
||||
void TestRunsAreNotSplitForNothing() {
|
||||
// The guard against the opposite failure (0.7 made 411 mirrors in 8 minutes for 0.4 GB of data
|
||||
// on the 2 GB laptop): holes of a handful of blocks never pay for the mirrors they would add,
|
||||
// so the runs stay in one segment.
|
||||
const std::vector<Segment> runs{{0, 1}, {2, 3}, {4, 6}};
|
||||
for (const u32 limit : {kMaxSegmentsPerMirror, 1u, 0u}) {
|
||||
const auto segments = MirrorSegments(runs, kShift, 0, limit);
|
||||
assert(segments.size() == 1);
|
||||
assert(segments[0].first == 0 && segments[0].end == 6);
|
||||
// Nothing freed between the valid blocks: only the (absent) head and tail come off.
|
||||
assert(SegmentsBytes(segments, kShift) == (6 << kShift));
|
||||
}
|
||||
}
|
||||
|
||||
void TestRetryAndBudget() {
|
||||
assert(kRetrySeconds == 30);
|
||||
// The per-split minimum the two tests above rely on.
|
||||
assert(kSegmentPays == 1_MB);
|
||||
// A small waste is taken whole; a large one is capped so the copies spread over the seconds
|
||||
// that follow instead of landing in one long frame.
|
||||
assert(CompactBudget(1_MB) == 1_MB);
|
||||
assert(CompactBudget(32_MB) == 32_MB);
|
||||
assert(CompactBudget(612_MB) == 32_MB);
|
||||
assert(CompactBudget(0) == 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
TestMinWasteFollowsSize();
|
||||
TestMinHoleFollowsSize();
|
||||
TestNoRuns();
|
||||
TestOneRunStaysOneSegment();
|
||||
TestHeadAndTailComeOffForFree();
|
||||
TestHoleIsSplitOnlyWhenItPays();
|
||||
TestWasteBetweenValidBlocksFreesNothing();
|
||||
TestSegmentCapKeepsTheSmallestHoles();
|
||||
TestRunsAreNotSplitForNothing();
|
||||
TestRetryAndBudget();
|
||||
std::printf("layer-mirror-policy-test: all checks passed\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user