mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
Add a per-frame texture work log line and two ASTC upload changes, all off by default.
texup_log_ms logs a "texup:" line for each frame whose texture work on the render or replay thread reaches that many milliseconds. The line splits the time into texture creation, staging, storage copies, ASTC recording, scratch buffers and new device memory, to show where an area load's hitch goes. astc_scratch_keep keeps the ASTC decoder's scratch buffer across upload batches instead of making a new 64 MiB buffer for each batch. astc_arena stages GPU-decoded ASTC blocks in the staging arena instead of a buffer per texture. Both aim at the allocation churn seen during area loads and stay off until a play test measures them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -124,6 +124,12 @@
|
||||
# With astc = gpu: the compute shader reads the blocks in the game's tiled layout, so the CPU only copies the
|
||||
# texture (0 = the CPU untiles the blocks first).
|
||||
# astc_gpu_deswizzle = 1
|
||||
# With astc = gpu: 1 keeps the decoder's scratch buffer from one upload batch to the next instead of making it again for
|
||||
# each, to shorten the hitches when an area loads (0 = one per batch; off until a play test measures it).
|
||||
# astc_scratch_keep = 0
|
||||
# With astc = gpu: 1 stages a texture's ASTC blocks in the shared staging ranges instead of a buffer of their own
|
||||
# (0 = a buffer each; off until a play test measures it).
|
||||
# astc_arena = 0
|
||||
# Host zstd decoder in place of the game's (0 = the game's), and a check that decodes every call both ways and logs
|
||||
# the differences (1 = on; slow).
|
||||
# zstd_native = 1
|
||||
@@ -463,3 +469,7 @@
|
||||
# 1: the frame-time summary also splits the replay by op kind (draw, bind, transfer), which says where a stalled frame
|
||||
# went. The clock reads cost a fraction of a millisecond a frame, so it is off by default.
|
||||
# replay_time = 0
|
||||
# A frame whose texture work on the render thread (new textures, uploads, upload submits) takes at least this many ms
|
||||
# gets a "texup:" line in the log that splits it: creation, staging, storage copy, ASTC recording, scratch buffers and
|
||||
# new device memory (up to 400 lines; 0: none; 16 catches the area-change hitches).
|
||||
# texup_log_ms = 0
|
||||
|
||||
@@ -64,7 +64,8 @@ static const char *const known[] = {
|
||||
"VK_PRESENT", "VK_RECONVERGE", "VK_RECORDER", "VK_SSBO_ALIGN", "VK_SUBGROUP", "VK_SUBGROUP_PIN", "VK_VALIDATION", "VOLUME", "VRAM_HEADROOM", "VS_OUTS",
|
||||
"WALK_KEY", "WALK_SCALE", "WB_EXACT", "WB_FENCE_LIMIT", "WB_MERGE", "WINDOW_SIZE", "XLATE", "XLATE_ID", "XLATE_KEEP", "XLATE_PROCS",
|
||||
"ZSTD_CHECK", "ZSTD_NATIVE",
|
||||
"BIND_REUSE", "DYN_DELTA", "STG_ARENA", "SUBMIT_BATCH", "ASTC_GPU_DESWIZZLE", "DYN_RES", "DYN_RES_TARGET", "DYN_RES_STEP",
|
||||
"BIND_REUSE", "DYN_DELTA", "STG_ARENA", "SUBMIT_BATCH", "ASTC_GPU_DESWIZZLE", "ASTC_SCRATCH_KEEP", "ASTC_ARENA",
|
||||
"TEXUP_LOG_MS", "DYN_RES", "DYN_RES_TARGET", "DYN_RES_STEP",
|
||||
"RECORDER_CORE", "RECORDER_PRIO", "REPLAY_TIME",
|
||||
};
|
||||
|
||||
@@ -104,7 +105,7 @@ static const struct { const char *key, *section; } sections[] = {
|
||||
{"DYN_RES", "performance"}, {"DYN_RES_TARGET", "performance"}, {"DYN_RES_STEP", "performance"},
|
||||
{"RECORDER_CORE", "performance"}, {"RECORDER_PRIO", "performance"}, {"BIND_REUSE", "performance"},
|
||||
{"DYN_DELTA", "performance"}, {"STG_ARENA", "performance"}, {"SUBMIT_BATCH", "performance"},
|
||||
{"ASTC_GPU_DESWIZZLE", "performance"},
|
||||
{"ASTC_GPU_DESWIZZLE", "performance"}, {"ASTC_SCRATCH_KEEP", "performance"}, {"ASTC_ARENA", "performance"},
|
||||
{"TIMER_1MS", "performance"}, {"PIPE_WAIT", "performance"}, {"PIPE_SKIP", "performance"},
|
||||
{"LIB_PREBUILD", "performance"}, {"LIB_PREBUILD_THREADS", "performance"}, {"PIPE_PREBUILD", "performance"},
|
||||
{"PREWARM_XLATE", "performance"}, {"GPL", "performance"}, {"ASTC", "performance"}, {"MEMORY", "performance"},
|
||||
@@ -114,7 +115,7 @@ static const struct { const char *key, *section; } sections[] = {
|
||||
{"VK_DYN_VERTEX", "performance"}, {"VK_DYN_A2C", "performance"}, {"VK_SUBGROUP_PIN", "performance"}, {"VK_RECONVERGE", "performance"},
|
||||
{"TRACE", "diagnostics"}, {"FLIGHT", "diagnostics"}, {"PASS_CRUMBS", "diagnostics"}, {"FLIGHT_SPIKE_MS", "diagnostics"},
|
||||
{"WB_FENCE_LIMIT", "diagnostics"}, {"WB_MERGE", "diagnostics"}, {"WB_EXACT", "diagnostics"},
|
||||
{"MV_SURVEY", "diagnostics"},
|
||||
{"MV_SURVEY", "diagnostics"}, {"TEXUP_LOG_MS", "diagnostics"},
|
||||
};
|
||||
|
||||
// The settings the menu shows: the value in effect when the key is unset, and what the key accepts. The only place a
|
||||
@@ -203,6 +204,7 @@ static const CfgMeta metas[] = {
|
||||
{"ASTC", "auto", "auto|gpu|native|cpu", RESTART}, {"MEMORY", "auto", "auto|import|mirror|verify", RESTART},
|
||||
{"TEX_ALIAS", "1", "0|1", RESTART}, {"TEX_VRAM_PRESSURE", "90", "0..100", RESTART}, {"ASTC_BC", "", "0|1", RESTART}, {"ZSTD_NATIVE", "1", "0|1", RESTART}, {"ZSTD_CHECK", "0", "0|1", RESTART},
|
||||
{"STG_ARENA", "1", "0|1", RESTART}, {"ASTC_GPU_DESWIZZLE", "1", "0|1", RESTART},
|
||||
{"ASTC_SCRATCH_KEEP", "0", "0|1", RESTART}, {"ASTC_ARENA", "0", "0|1", RESTART}, // vk_astc.c scratch_get, vk_texture.c astc_arena
|
||||
// render fixes
|
||||
{"LOD_GUARD", "1", "0|1", RESTART}, {"FIX_C0", "1", "0|1", RESTART}, {"FIX_ZERO", "1", "0|1", RESTART},
|
||||
{"BIND_SKIP", "1", "0|1", RESTART}, {"SHADER_MEMO", "1", "0|1", RESTART}, {"PIPE_MEMO", "1", "0|1", RESTART}, {"TEX_MEMO", "1", "0|1", RESTART},
|
||||
@@ -218,6 +220,7 @@ static const CfgMeta metas[] = {
|
||||
// diagnostics
|
||||
{"TRACE", "", NULL, RESTART}, {"FLIGHT", "0", "0|1", RESTART}, {"FLIGHT_SPIKE_MS", "100", "0..60000", RESTART},
|
||||
{"WB_FENCE_LIMIT", "65536", "0..1073741824", RESTART}, {"WB_MERGE", "1", "0|1", RESTART}, {"WB_EXACT", "1", "0|1", RESTART},
|
||||
{"TEXUP_LOG_MS", "0", "0..60000", RESTART}, // vk_texture.c vk_texstat_frame
|
||||
{"PASS_CRUMBS", "", "0|1", RESTART}, // vk_crumbs.c pass marks; unset: VK_AMD_buffer_marker only
|
||||
{"INPUT_RECORD", "", NULL, RESTART},
|
||||
};
|
||||
|
||||
@@ -181,6 +181,8 @@ static void ft_report(uint64_t now, const char *tag) {
|
||||
}
|
||||
|
||||
__attribute__((weak)) void gfx_exit_log(void) {}
|
||||
// The renderer's per-frame texture work (vk_texture.c): closes the frame's sums; d is the frame's interval.
|
||||
__attribute__((weak)) void vk_texstat_frame(uint64_t frame, uint64_t d) { (void)frame, (void)d; }
|
||||
|
||||
void perf_summary_flush(void) { // at exit (prof_write)
|
||||
if (summary_t0) {
|
||||
@@ -193,6 +195,7 @@ void perf_summary_flush(void) { // at exit (prof_write)
|
||||
// Returns the replay time since the previous present.
|
||||
static uint64_t ft_frame(uint64_t frame, uint64_t now, uint64_t d) {
|
||||
int64_t rep = plat_xchg64(&frame_replay_ns, 0);
|
||||
vk_texstat_frame(frame, d);
|
||||
if (!summary_t0) { // the first present starts the period; there is no interval yet
|
||||
summary_t0 = now;
|
||||
ft.t0 = now;
|
||||
|
||||
10
runtime/vk.h
10
runtime/vk.h
@@ -359,6 +359,16 @@ void *vk_staging(VkDeviceSize size, VkBuffer *buf, VmaAllocation *alloc);
|
||||
// between vk_upload_begin and vk_upload_end; a large request gets a buffer of its own at offset 0.
|
||||
void *vk_staging_at(VkDeviceSize size, VkBuffer *buf, VkDeviceSize *off);
|
||||
uint64_t vk_upload_flush(int wait);
|
||||
// Texture work on the render and replay threads, summed per frame (vk_texture.c vk_texstat_frame, the "texup:" line of
|
||||
// NX2NAVI_TEXUP_LOG_MS). vk_texstat_time adds the time since t0 (kern_now_ns) and one event; vk_texstat_add adds events
|
||||
// and bytes. Other threads are not counted. CREATE: new texture entries (image, memory, first transition); UPLOAD: the
|
||||
// whole upload, of which STAGE (staging range or buffer), COPY (storage into staging: copy, untile, CPU decode) and ASTC
|
||||
// (vk_astc_record, of which SCRATCH: a new scratch buffer); STG_NEW: staging buffers made by vmaCreateBuffer; DEVMEM: new
|
||||
// VkDeviceMemory of 32 MiB or more (VMA blocks, dedicated allocations); FLUSH: vk_upload_flush (submit, retire, frees).
|
||||
enum { VK_TS_CREATE, VK_TS_UPLOAD, VK_TS_STAGE, VK_TS_STG_NEW, VK_TS_COPY, VK_TS_ASTC, VK_TS_SCRATCH, VK_TS_DEVMEM,
|
||||
VK_TS_FLUSH, VK_TS_N };
|
||||
void vk_texstat_time(int kind, uint64_t t0);
|
||||
void vk_texstat_add(int kind, uint64_t n, uint64_t bytes);
|
||||
// Deferred destruction: freed once the upload batches recorded so far and the timeline value last noted have completed.
|
||||
void vk_defer_image(VkImage img, VmaAllocation alloc); // alloc NULL: vkDestroyImage only
|
||||
void vk_defer_view(VkImageView view);
|
||||
|
||||
@@ -436,15 +436,43 @@ static void mem_barrier(VkCommandBuffer cb, VkPipelineStageFlags2 src, VkAccessF
|
||||
// The shared scratch and the command buffer it was last recorded into. An upload batch's command buffer is used again
|
||||
// only after that batch completed (vk_memory.c: completed batches go to the spare list), so the same handle means the
|
||||
// open batch, ordered by a barrier, or a finished one. g_lock held.
|
||||
static struct { VkBuffer buf; VmaAllocation alloc; VkDeviceSize size; VkCommandBuffer cb; VkDevice dev; } S;
|
||||
static struct { VkBuffer buf; VmaAllocation alloc; VkDeviceSize size; VkCommandBuffer cb; VkDevice dev; uint64_t big_ns; } S;
|
||||
static uint64_t n_scratch_new, n_scratch_reuse;
|
||||
|
||||
// A scratch of at least need bytes for recording into cb; *reused: earlier work in cb may still use it. 0 on failure.
|
||||
// NX2NAVI_ASTC_SCRATCH_KEEP=1 (default 0 until a play test measured it): the scratch outlives its upload batch. Every
|
||||
// batch goes to gfx_queue, so the reuse barrier at the start of the next batch's first chunk (vk_astc_record) orders it
|
||||
// after the earlier batches' copies and dispatches, as it does between two textures of one batch; it then also waits
|
||||
// for their writes (WAW). With
|
||||
// a scratch per batch, each submission that uploaded an ASTC texture made a new 64 MiB device buffer and deferred the
|
||||
// old one (324 in one Kokiri Forest to Hyrule Field run), and one that found no room in a VMA block allocated a new
|
||||
// block on the replay thread during an area load (8-19 ms per block on NVIDIA, vk_headroom.c). A scratch grown past
|
||||
// SCRATCH_MIN for a large texture goes back to SCRATCH_MIN once no texture needed more for SCRATCH_SHRINK_NS.
|
||||
// 0: a scratch per batch.
|
||||
#define SCRATCH_SHRINK_NS 5000000000ull
|
||||
static int scratch_keep(void) {
|
||||
static int keep = -1;
|
||||
if (keep < 0) {
|
||||
keep = cfg_bool("NX2NAVI_ASTC_SCRATCH_KEEP");
|
||||
rt_log("vk: ASTC scratch %s (NX2NAVI_ASTC_SCRATCH_KEEP=%d)", keep ? "kept across upload batches" : "made per upload batch",
|
||||
keep);
|
||||
}
|
||||
return keep;
|
||||
}
|
||||
|
||||
// A scratch of at least need bytes for recording into cb; *reused: earlier work (in cb, or with the keep rule in an
|
||||
// earlier batch) may still use it. 0 on failure.
|
||||
static VkBuffer scratch_get(VkCtx *c, VkCommandBuffer cb, VkDeviceSize need, int *reused) {
|
||||
int keep = scratch_keep();
|
||||
plat_mutex_lock(&g_lock);
|
||||
*reused = S.buf && S.dev == c->device && S.cb == cb && S.size >= need;
|
||||
uint64_t now = kern_now_ns();
|
||||
if (need > SCRATCH_MIN) S.big_ns = now;
|
||||
// with the keep rule, a grown scratch is replaced by a small one after a while without large textures
|
||||
int shrink = keep && S.size > SCRATCH_MIN && now - S.big_ns > SCRATCH_SHRINK_NS;
|
||||
*reused = S.buf && S.dev == c->device && (S.cb == cb || keep) && S.size >= need && !shrink;
|
||||
if (!*reused) {
|
||||
uint64_t t0 = now;
|
||||
VkDeviceSize size = need < SCRATCH_MIN ? SCRATCH_MIN : need;
|
||||
if (keep && size > SCRATCH_MIN) size = (size + (32ull << 20) - 1) & ~((32ull << 20) - 1);
|
||||
VkBufferCreateInfo bi = {.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .size = size,
|
||||
.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
|
||||
VK_BUFFER_USAGE_TRANSFER_DST_BIT};
|
||||
@@ -459,6 +487,7 @@ static VkBuffer scratch_get(VkCtx *c, VkCommandBuffer cb, VkDeviceSize need, int
|
||||
if (S.buf && S.dev == c->device) vk_defer_buffer(S.buf, S.alloc);
|
||||
S.buf = b, S.alloc = a, S.size = size, S.dev = c->device;
|
||||
n_scratch_new++;
|
||||
vk_texstat_time(VK_TS_SCRATCH, t0);
|
||||
} else {
|
||||
n_scratch_reuse++;
|
||||
}
|
||||
@@ -578,7 +607,8 @@ int vk_astc_record(VkCommandBuffer cb, VkBuffer src, VkDeviceSize src_size, cons
|
||||
vk_crumb(cb, VK_CRUMB_ASTC, ++n_chunks);
|
||||
if (chunks || reused) // the previous chunk's (or texture's) dispatches and copies are done with the scratch
|
||||
mem_barrier(cb, VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
|
||||
VK_ACCESS_2_TRANSFER_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_READ_BIT,
|
||||
VK_ACCESS_2_TRANSFER_READ_BIT | VK_ACCESS_2_SHADER_STORAGE_READ_BIT |
|
||||
(scratch_keep() ? VK_ACCESS_2_TRANSFER_WRITE_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT : 0),
|
||||
VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
|
||||
VK_ACCESS_2_TRANSFER_WRITE_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT);
|
||||
vkCmdCopyBuffer(cb, src, sb, (uint32_t)(i1 - i0), bcp);
|
||||
|
||||
@@ -42,7 +42,10 @@ static void VKAPI_PTR on_allocate(VmaAllocator a, uint32_t type, VkDeviceMemory
|
||||
plat_inc64(&H.made);
|
||||
return;
|
||||
}
|
||||
if (size >= 32ull << 20) plat_inc64(&H.other);
|
||||
if (size >= 32ull << 20) {
|
||||
plat_inc64(&H.other);
|
||||
vk_texstat_add(VK_TS_DEVMEM, 1, size); // counted when the render or replay thread made it
|
||||
}
|
||||
plat_or64(&H.types, (int64_t)(1ull << type));
|
||||
plat_event_set(&H.ev);
|
||||
}
|
||||
|
||||
@@ -2667,6 +2667,7 @@ void *vk_staging(VkDeviceSize size, VkBuffer *buf, VmaAllocation *alloc) {
|
||||
VmaAllocation a;
|
||||
VkResult r = vmaCreateBuffer(c->vma, &bi, &aci, &b, &a, &info);
|
||||
if (r != VK_SUCCESS) rt_fatal("vk: staging buffer of %llu bytes failed (%d)", (unsigned long long)size, (int)r);
|
||||
vk_texstat_add(VK_TS_STG_NEW, 1, bi.size);
|
||||
*buf = b;
|
||||
if (alloc) *alloc = a; // the caller destroys it (vmaDestroyBuffer) after vk_upload_flush(1)
|
||||
else {
|
||||
@@ -2704,6 +2705,7 @@ void *vk_staging_at(VkDeviceSize size, VkBuffer *buf, VkDeviceSize *off) {
|
||||
if (vmaCreateBuffer(c->vma, &bi, &aci, &k.buf, &k.alloc, &info) != VK_SUCCESS)
|
||||
return vk_staging(size, buf, NULL);
|
||||
k.map = info.pMappedData;
|
||||
vk_texstat_add(VK_TS_STG_NEW, 1, ARENA_CHUNK);
|
||||
if (arena_made++ < 16) rt_log("vk: staging arena chunk %llu made (%llu MiB)", (unsigned long long)arena_made,
|
||||
(unsigned long long)(ARENA_CHUNK >> 20));
|
||||
}
|
||||
@@ -2719,6 +2721,7 @@ void *vk_staging_at(VkDeviceSize size, VkBuffer *buf, VkDeviceSize *off) {
|
||||
}
|
||||
|
||||
uint64_t vk_upload_flush(int wait) {
|
||||
uint64_t f0 = kern_now_ns();
|
||||
plat_once(&cmd_once, cmd_init, NULL);
|
||||
plat_rmutex_lock(&cmd_lock);
|
||||
VkCtx *c = vk_ctx();
|
||||
@@ -2749,6 +2752,7 @@ uint64_t vk_upload_flush(int wait) {
|
||||
}
|
||||
if (c) collect_locked(c);
|
||||
plat_rmutex_unlock(&cmd_lock);
|
||||
vk_texstat_time(VK_TS_FLUSH, f0);
|
||||
return seq;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "vk.h"
|
||||
#include "vk_dlaa.h"
|
||||
#include "vk_exec.h"
|
||||
#include "prof.h"
|
||||
#include "platform/platform.h"
|
||||
|
||||
typedef struct {
|
||||
@@ -892,6 +893,53 @@ static void sweep_pressure(void) { // tex_lock held
|
||||
if (rel_bytes > COMMIT_LOW_KEEP && commit_is_low()) drop_stale(COMMIT_LOW_KEEP, &n_commit_drops);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- per-frame texture work (vk.h VK_TS_*)
|
||||
// Sums for the frame being made, by the render and replay threads (atomics: both may add at once). vk_texstat_frame,
|
||||
// called at each present (perf.c ft_frame), takes them; a frame whose texture work (creation, upload and upload flush)
|
||||
// reaches NX2NAVI_TEXUP_LOG_MS gets a "texup:" line, so an area change's hitch shows where its time went (default 0:
|
||||
// no lines; 16 is a useful value).
|
||||
static volatile int64_t ts_ns[VK_TS_N], ts_n[VK_TS_N], ts_bytes[VK_TS_N];
|
||||
static int ts_log_ms = -1;
|
||||
|
||||
static int ts_thread(void) {
|
||||
uint32_t tid = plat_thread_id();
|
||||
return tid == prof_render_tid || tid == prof_replay_tid;
|
||||
}
|
||||
|
||||
void vk_texstat_time(int kind, uint64_t t0) {
|
||||
if (!t0 || !ts_thread()) return;
|
||||
plat_add64(&ts_ns[kind], (int64_t)(kern_now_ns() - t0));
|
||||
plat_inc64(&ts_n[kind]);
|
||||
}
|
||||
|
||||
void vk_texstat_add(int kind, uint64_t n, uint64_t bytes) {
|
||||
if (!ts_thread()) return;
|
||||
plat_add64(&ts_n[kind], (int64_t)n);
|
||||
plat_add64(&ts_bytes[kind], (int64_t)bytes);
|
||||
}
|
||||
|
||||
void vk_texstat_frame(uint64_t frame, uint64_t d) {
|
||||
if (ts_log_ms < 0) {
|
||||
ts_log_ms = cfg_int("NX2NAVI_TEXUP_LOG_MS");
|
||||
if (ts_log_ms < 0) ts_log_ms = 0;
|
||||
}
|
||||
int64_t ns[VK_TS_N], n[VK_TS_N], b[VK_TS_N];
|
||||
for (int k = 0; k < VK_TS_N; k++)
|
||||
ns[k] = plat_xchg64(&ts_ns[k], 0), n[k] = plat_xchg64(&ts_n[k], 0), b[k] = plat_xchg64(&ts_bytes[k], 0);
|
||||
double work = (ns[VK_TS_CREATE] + ns[VK_TS_UPLOAD] + ns[VK_TS_FLUSH]) / 1e6;
|
||||
static int32_t n_lines;
|
||||
if (!ts_log_ms || work < ts_log_ms || ++n_lines > 400) return;
|
||||
rt_log("texup: frame %llu: %.1f ms frame, texture work %.1f ms; created %lld in %.1f ms; uploads %lld (%.1f MiB) in "
|
||||
"%.1f ms: staging %.1f ms (%lld new buffers, %.1f MiB), storage copy %.1f ms, ASTC record %.1f ms (%lld "
|
||||
"scratch buffers made in %.1f ms); upload flush %lld in %.1f ms; new device memory of 32 MiB or more %lld "
|
||||
"(%.0f MiB)",
|
||||
(unsigned long long)frame, d / 1e6, work, (long long)n[VK_TS_CREATE], ns[VK_TS_CREATE] / 1e6,
|
||||
(long long)n[VK_TS_UPLOAD], b[VK_TS_UPLOAD] / 1048576.0, ns[VK_TS_UPLOAD] / 1e6, ns[VK_TS_STAGE] / 1e6,
|
||||
(long long)n[VK_TS_STG_NEW], b[VK_TS_STG_NEW] / 1048576.0, ns[VK_TS_COPY] / 1e6, ns[VK_TS_ASTC] / 1e6,
|
||||
(long long)n[VK_TS_SCRATCH], ns[VK_TS_SCRATCH] / 1e6, (long long)n[VK_TS_FLUSH], ns[VK_TS_FLUSH] / 1e6,
|
||||
(long long)n[VK_TS_DEVMEM], b[VK_TS_DEVMEM] / 1048576.0);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- uploads
|
||||
static uint32_t texel_alpha_one(int format, uint32_t *elem) {
|
||||
switch (format) {
|
||||
@@ -1017,6 +1065,22 @@ static int astc_bl_regions(const Tex *e, uint32_t levels, uint32_t layers, int b
|
||||
return 1;
|
||||
}
|
||||
|
||||
// NX2NAVI_ASTC_ARENA=1 (default 0 until a play test measured it): the raw ASTC blocks for the GPU decoder stage in a
|
||||
// range of the staging arena (vk_staging_at) like the other formats, not in a VkBuffer of their own. vk_astc_record
|
||||
// reads its source only through a buffer copy at each region's src, so the range's offset is added to the regions. A
|
||||
// buffer per texture cost a vmaCreateBuffer and, after the batch, a vmaDestroyBuffer each; an area load uploads
|
||||
// thousands. The check mode keeps its own buffer (astc_check reads the blocks at the regions' src offsets). 0: a buffer
|
||||
// per texture.
|
||||
static int astc_arena(void) {
|
||||
static int on = -1;
|
||||
if (on < 0) {
|
||||
on = cfg_bool("NX2NAVI_ASTC_ARENA");
|
||||
rt_log("vk: ASTC blocks for the GPU decoder stage in %s (NX2NAVI_ASTC_ARENA=%d)",
|
||||
on ? "the staging arena" : "a buffer per texture", on);
|
||||
}
|
||||
return on;
|
||||
}
|
||||
|
||||
// Copies the whole storage into the image (every level and layer the image has).
|
||||
static void upload(Tex *e) {
|
||||
static volatile int32_t n_bad, n_undecoded, n_split;
|
||||
@@ -1073,10 +1137,14 @@ static void upload(Tex *e) {
|
||||
if (gdsw) size = t->L.size; // the storage as it is (one part: the GPU decoder takes every region at once)
|
||||
if (k0) parts++;
|
||||
VkCommandBuffer cb = vk_upload_begin();
|
||||
// The CPU path copies from a range of the staging arena (vk_staging_at, its offset added to the regions below);
|
||||
// the GPU decoder binds its source buffer whole, so it keeps a buffer of its own.
|
||||
// The part stages in a range of the staging arena (vk_staging_at, its offset added to the regions below), or in
|
||||
// a buffer of its own at offset 0 when larger than an arena range; the GPU decoder takes one with
|
||||
// NX2NAVI_ASTC_ARENA=0 or in the check mode (astc_arena).
|
||||
VkDeviceSize soff = 0;
|
||||
uint8_t *stg = agpu ? vk_staging(size, &sb, NULL) : vk_staging_at(size, &sb, &soff);
|
||||
uint64_t s0 = kern_now_ns();
|
||||
uint8_t *stg = agpu && (acheck || !astc_arena()) ? vk_staging(size, &sb, NULL) : vk_staging_at(size, &sb, &soff);
|
||||
vk_texstat_time(VK_TS_STAGE, s0);
|
||||
uint64_t c0 = kern_now_ns(); // the storage copies and decodes of this part (VK_TS_COPY)
|
||||
if (acheck) rmap = vk_staging(decoded, &rbuf, &ralloc);
|
||||
// The part's copies out of the storage, with it held mapped (mem_read_hold): the game releases a texture's
|
||||
// memory on its own thread, and a decommit in the middle of a copy faulted (tex_bl_copy). Only the copies run
|
||||
@@ -1137,16 +1205,19 @@ static void upload(Tex *e) {
|
||||
free(tmp);
|
||||
}
|
||||
free(tmps);
|
||||
vk_texstat_time(VK_TS_COPY, c0);
|
||||
if (acheck) {
|
||||
raw_copy = malloc(size);
|
||||
memcpy(raw_copy, stg, size);
|
||||
}
|
||||
barrier(cb, ALL_RW, VK_PIPELINE_STAGE_2_COPY_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT);
|
||||
if (agpu) {
|
||||
for (k = 0; k < n; k++) ar[k].src += soff; // a range of the arena (astc_arena); 0 in a buffer of its own
|
||||
uint64_t a0 = perf_begin();
|
||||
checked = vk_astc_record(cb, sb, size, ar, (int)n, e->f.astc_bw, e->f.astc_bh, e->f.astc_srgb, e->f.astc_bc,
|
||||
e->pub.image, e->pub.aspect, rbuf);
|
||||
perf_end(PERF_ASTC, a0);
|
||||
vk_texstat_time(VK_TS_ASTC, a0);
|
||||
if (!checked)
|
||||
log_limited(&n_undecoded, "ASTC texture %#llx (fmt %#x %ux%u) not decoded on the GPU: left as it was",
|
||||
(unsigned long long)e->pub.addr, d->format, d->width, d->height);
|
||||
@@ -1190,6 +1261,8 @@ static void upload(Tex *e) {
|
||||
e->uploaded = 1;
|
||||
e->n_up++;
|
||||
perf_end(PERF_TEX, p0);
|
||||
vk_texstat_time(VK_TS_UPLOAD, u0);
|
||||
vk_texstat_add(VK_TS_UPLOAD, 0, t->L.size);
|
||||
}
|
||||
|
||||
// Whether e's storage fills no 4 KiB page of its own, so the write tracking can only see writes to pages it shares.
|
||||
@@ -1368,6 +1441,7 @@ static Tex *entry_of(const tex_t *t) {
|
||||
Tex *e = nvn_hm_get(&by_key, &k);
|
||||
if (!e) {
|
||||
if (create_held(&k)) return NULL;
|
||||
uint64_t c0 = kern_now_ns(); // the entry's creation (VK_TS_CREATE): image, memory, first transition
|
||||
if (tex_alias < 0) {
|
||||
tex_alias = cfg_bool("NX2NAVI_TEX_ALIAS");
|
||||
rt_log("vk: texture aliases: %s", tex_alias ? "a new image takes the live image's content" : "a new image starts empty");
|
||||
@@ -1410,6 +1484,7 @@ static Tex *entry_of(const tex_t *t) {
|
||||
nvn_hm_put(&by_key, &k, e);
|
||||
n_site[1]++;
|
||||
tex_gen_bump(k.addr);
|
||||
vk_texstat_time(VK_TS_CREATE, c0);
|
||||
}
|
||||
if (e->released) e->released = 0, nreleased--, rel_unlink(e), n_site[2]++, tex_gen_bump(k.addr);
|
||||
e->t = *t;
|
||||
|
||||
Reference in New Issue
Block a user