diff --git a/nx2navi.ini.example b/nx2navi.ini.example index ef994527..87ccfb53 100644 --- a/nx2navi.ini.example +++ b/nx2navi.ini.example @@ -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 diff --git a/runtime/cfg.c b/runtime/cfg.c index fdc4ceef..2c99b1cf 100644 --- a/runtime/cfg.c +++ b/runtime/cfg.c @@ -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}, }; diff --git a/runtime/perf.c b/runtime/perf.c index 9a2d1495..6774e60e 100644 --- a/runtime/perf.c +++ b/runtime/perf.c @@ -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; diff --git a/runtime/vk.h b/runtime/vk.h index 498728b5..159d0ca6 100644 --- a/runtime/vk.h +++ b/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); diff --git a/runtime/vk_astc.c b/runtime/vk_astc.c index f6af26c3..a19a276f 100644 --- a/runtime/vk_astc.c +++ b/runtime/vk_astc.c @@ -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); diff --git a/runtime/vk_headroom.c b/runtime/vk_headroom.c index 6067c6ae..a5f9699b 100644 --- a/runtime/vk_headroom.c +++ b/runtime/vk_headroom.c @@ -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); } diff --git a/runtime/vk_memory.c b/runtime/vk_memory.c index 197b06c8..97cc385a 100644 --- a/runtime/vk_memory.c +++ b/runtime/vk_memory.c @@ -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; } diff --git a/runtime/vk_texture.c b/runtime/vk_texture.c index c416b687..217ec08e 100644 --- a/runtime/vk_texture.c +++ b/runtime/vk_texture.c @@ -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;