mirror of
https://github.com/simplysanatori/KartFrame.git
synced 2026-10-17 11:01:37 +02:00
Mock: MOCK_XR_DUMP writes released images as PPM; mockrun KF_MOCKRUN_INTRO=1 shows the first-run guide
MOCK_XR_DUMP=<dir> (Vulkan) copies the image being released at the MOCK_XR_DUMP_AT seconds (MOCK_XR_DUMP_FRAMES consecutive releases per swapchain) into <dir>/t<s>-sc<id>-<n>.ppm, every 4th pixel, after waiting for the app's queue inside xrReleaseSwapchainImage. The waits cost a few refreshes at each dump time. KF_MOCKRUN_INTRO=1 leaves welcome/tutorial unseeded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
57526ee843
commit
02a64fd4ab
5 files changed
+165
-6
No files matched your search
@@ -9,7 +9,8 @@
|
||||
# ~/.cache/kf-mockrun), KF_XRMOCK_DIR (where the mock is built; default one per checkout under
|
||||
# ~/.cache/kf-xrmock, rebuilt when its sources change), KF_MOCKRUN_VR (extra lines for the [vr]
|
||||
# section, e.g. 'frame_pacing = "pipelined"'), KF_MOCKRUN_RENDER_SCALE (render_scale, default 1.0;
|
||||
# 1.25 is the panel's native 2160 per eye). The game reads its user state from
|
||||
# 1.25 is the panel's native 2160 per eye), KF_MOCKRUN_INTRO=1 (the first-run welcome and controller
|
||||
# guide show instead of being seeded done). The game reads its user state from
|
||||
# $XDG_DATA_HOME/WiiCompiled (Config.toml, NAND, Cache), which this script points at the scratch dir
|
||||
# and seeds with a Config.toml naming the extracted game (~/Games/KartFrame/data/game or --game) and
|
||||
# the Frame's VR defaults (render_scale 1.0, VR interpolation auto, plain menu background).
|
||||
@@ -63,9 +64,8 @@ required = $([ $FLAT = 1 ] && echo false || echo true)
|
||||
render_scale = ${KF_MOCKRUN_RENDER_SCALE:-1.0}
|
||||
frame_interpolation_fps = 1
|
||||
menu_shader_quality = 0
|
||||
# No first-run welcome or tutorial: the attract-mode demo race starts on its own.
|
||||
welcome_complete = true
|
||||
tutorial_completed = 3
|
||||
$([ "${KF_MOCKRUN_INTRO:-0}" = 1 ] && echo "# KF_MOCKRUN_INTRO=1: the first-run welcome and controller guide show." || printf '%s\n' \
|
||||
"# No first-run welcome or tutorial: the attract-mode demo race starts on its own." "welcome_complete = true" "tutorial_completed = 3")
|
||||
${KF_MOCKRUN_VR:-}
|
||||
TOML
|
||||
LOG=$OUT/logs/$(date +%Y%m%d-%H%M%S)-$([ $FLAT = 1 ] && echo flat || echo mock).log
|
||||
|
||||
@@ -40,6 +40,10 @@
|
||||
// default none); an INTERACTION_PROFILE_CHANGED
|
||||
// event follows the first FOCUSED, and suggested
|
||||
// bindings for it are traced
|
||||
// MOCK_XR_DUMP=<dir> Vulkan: write released images as PPM files (every 4th pixel)
|
||||
// <dir>/t<seconds>-sc<id>-<n>.ppm: at the seconds in
|
||||
// MOCK_XR_DUMP_AT=<s>,<s>... (default every 10 s), the next
|
||||
// MOCK_XR_DUMP_FRAMES=<n> (1) releases of every swapchain
|
||||
// The graphics API is chosen by the app's binding (xrmock_gfx.h).
|
||||
//
|
||||
// Checks (ERROR lines in the trace, and a summary on stderr when the session
|
||||
@@ -97,6 +101,7 @@ typedef struct {
|
||||
int acquired, next;
|
||||
int waited; // of the images acquired, how many were waited for
|
||||
int released; // an image was released since the last xrEndFrame
|
||||
int dumpMark, dumped; // MOCK_XR_DUMP: the dump time this chain is on, images written for it
|
||||
} Chain;
|
||||
static Chain gChains[MAXSC];
|
||||
static int gErrors = 0; // ERROR lines in the trace
|
||||
@@ -348,6 +353,41 @@ static XrResult XRAPI_CALL mWaitImage(XrSwapchain sc, const XrSwapchainImageWait
|
||||
c->waited++;
|
||||
return XR_SUCCESS;
|
||||
}
|
||||
// MOCK_XR_DUMP (see the top): the image being released, written at the dump times.
|
||||
static void maybeDump(Chain* c, XrSwapchain sc) {
|
||||
static const char* dir = NULL;
|
||||
static double at[64];
|
||||
static int atCount = -1, frames = 1;
|
||||
if (atCount < 0) {
|
||||
dir = getenv("MOCK_XR_DUMP");
|
||||
atCount = 0;
|
||||
const char* list = getenv("MOCK_XR_DUMP_AT");
|
||||
for (const char* q = list; q && *q && atCount < 64;) {
|
||||
at[atCount++] = atof(q);
|
||||
q = strchr(q, ',');
|
||||
if (q) q++;
|
||||
}
|
||||
if (!atCount)
|
||||
for (; atCount < 64; atCount++) at[atCount] = 10.0 * (atCount + 1);
|
||||
if (getenv("MOCK_XR_DUMP_FRAMES")) frames = atoi(getenv("MOCK_XR_DUMP_FRAMES"));
|
||||
}
|
||||
if (!dir || !gGfx || !gGfx->dump_image || !gT0) return;
|
||||
double t = (double)(nowNs() - gT0) / 1e9;
|
||||
int mark = 0;
|
||||
while (mark < atCount && at[mark] <= t) mark++;
|
||||
if (mark == 0) return;
|
||||
if (c->dumpMark != mark) {
|
||||
c->dumpMark = mark;
|
||||
c->dumped = 0;
|
||||
}
|
||||
if (c->dumped >= frames || t - at[mark - 1] > 5.0) return;
|
||||
int idx = ((c->next - c->acquired) % c->count + c->count) % c->count;
|
||||
char path[512];
|
||||
snprintf(path, sizeof(path), "%s/t%03d-sc%d-%02d.ppm", dir, (int)at[mark - 1], chainId(sc), c->dumped);
|
||||
if (gGfx->dump_image(c->images, idx, c->w, c->h, c->format, 4, path)) trace("dump sc%d %s", chainId(sc), path);
|
||||
c->dumped++;
|
||||
}
|
||||
|
||||
static XrResult XRAPI_CALL mRelease(XrSwapchain sc, const XrSwapchainImageReleaseInfo* ri) {
|
||||
(void)ri;
|
||||
Chain* c = chainOf(sc);
|
||||
@@ -389,6 +429,7 @@ static XrResult XRAPI_CALL mRelease(XrSwapchain sc, const XrSwapchainImageReleas
|
||||
gErrors++;
|
||||
}
|
||||
}
|
||||
maybeDump(c, sc);
|
||||
c->acquired--;
|
||||
c->waited--;
|
||||
c->released = 1;
|
||||
|
||||
@@ -43,6 +43,9 @@ typedef struct MockGfx {
|
||||
int (*poll_snapshots)(int wait);
|
||||
// How many releases the exact check covered.
|
||||
int (*snapshots_checked)(void);
|
||||
// MOCK_XR_DUMP: writes image `index` (waits for the app's queue first) as a PPM, every
|
||||
// `step`-th pixel. Null: not available.
|
||||
int (*dump_image)(void* images, int index, int width, int height, int64_t format, int step, const char* path);
|
||||
} MockGfx;
|
||||
|
||||
extern const MockGfx kMockGfxGles;
|
||||
|
||||
@@ -74,4 +74,4 @@ static XrResult fillImages(void* images, int count, XrSwapchainImageBaseHeader*
|
||||
return XR_SUCCESS;
|
||||
}
|
||||
|
||||
const MockGfx kMockGfxGles = {"gles", ownsBinding, describeBinding, getProc, formats, formatOf, createImages, fillImages, NULL, NULL, NULL, NULL};
|
||||
const MockGfx kMockGfxGles = {"gles", ownsBinding, describeBinding, getProc, formats, formatOf, createImages, fillImages, NULL, NULL, NULL, NULL, NULL};
|
||||
@@ -132,6 +132,8 @@ static void* createImages(int64_t format, uint64_t usage, int width, int height,
|
||||
if (usage & XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT) u |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||
if (usage & XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) u |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||
if (usage & XR_SWAPCHAIN_USAGE_TRANSFER_SRC_BIT) u |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
// MOCK_XR_DUMP copies released images out.
|
||||
if (!depth && getenv("MOCK_XR_DUMP")) u |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
if (usage & XR_SWAPCHAIN_USAGE_TRANSFER_DST_BIT) u |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
if (usage & XR_SWAPCHAIN_USAGE_SAMPLED_BIT) u |= VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
// The compositor samples every image.
|
||||
@@ -332,5 +334,118 @@ static void releaseSnapshot(void* images, int index) {
|
||||
|
||||
static int snapshotsChecked(void) { return gChecked; }
|
||||
|
||||
// MOCK_XR_DUMP: inside xrReleaseSwapchainImage (the app holds its queue): wait for the queue,
|
||||
// copy the image (COLOR_ATTACHMENT_OPTIMAL, as the app must leave it) into a host buffer and
|
||||
// write every `step`-th pixel as a binary PPM. 8-bit RGBA / BGRA formats only.
|
||||
static int dumpImage(void* images, int index, int width, int height, int64_t format, int step, const char* path) {
|
||||
if (!gQueue || !gDevice) return 0;
|
||||
int bgra = format == VK_FORMAT_B8G8R8A8_SRGB || format == VK_FORMAT_B8G8R8A8_UNORM;
|
||||
if (!bgra && format != VK_FORMAT_R8G8B8A8_SRGB && format != VK_FORMAT_R8G8B8A8_UNORM) return 0;
|
||||
VkImage image = ((VkImage*)images)[index];
|
||||
VkDeviceSize size = (VkDeviceSize)width * height * 4;
|
||||
VkBufferCreateInfo bci;
|
||||
memset(&bci, 0, sizeof(bci));
|
||||
bci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
bci.size = size;
|
||||
bci.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
VkBuffer buf;
|
||||
if (DEV(vkCreateBuffer)(gDevice, &bci, NULL, &buf) != VK_SUCCESS) return 0;
|
||||
VkMemoryRequirements req;
|
||||
DEV(vkGetBufferMemoryRequirements)(gDevice, buf, &req);
|
||||
VkPhysicalDeviceMemoryProperties mp;
|
||||
INST(vkGetPhysicalDeviceMemoryProperties)(gPhys, &mp);
|
||||
uint32_t type = UINT32_MAX;
|
||||
const VkMemoryPropertyFlags want = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
||||
for (uint32_t t = 0; t < mp.memoryTypeCount; t++)
|
||||
if ((req.memoryTypeBits & (1u << t)) && (mp.memoryTypes[t].propertyFlags & want) == want) {
|
||||
type = t;
|
||||
break;
|
||||
}
|
||||
VkDeviceMemory mem = VK_NULL_HANDLE;
|
||||
VkMemoryAllocateInfo mai;
|
||||
memset(&mai, 0, sizeof(mai));
|
||||
mai.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
mai.allocationSize = req.size;
|
||||
mai.memoryTypeIndex = type;
|
||||
if (type == UINT32_MAX || DEV(vkAllocateMemory)(gDevice, &mai, NULL, &mem) != VK_SUCCESS) {
|
||||
DEV(vkDestroyBuffer)(gDevice, buf, NULL);
|
||||
return 0;
|
||||
}
|
||||
DEV(vkBindBufferMemory)(gDevice, buf, mem, 0);
|
||||
VkCommandPoolCreateInfo pci;
|
||||
memset(&pci, 0, sizeof(pci));
|
||||
pci.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
||||
pci.queueFamilyIndex = gQueueFamily;
|
||||
VkCommandPool pool;
|
||||
DEV(vkCreateCommandPool)(gDevice, &pci, NULL, &pool);
|
||||
VkCommandBufferAllocateInfo ai;
|
||||
memset(&ai, 0, sizeof(ai));
|
||||
ai.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
ai.commandPool = pool;
|
||||
ai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
ai.commandBufferCount = 1;
|
||||
VkCommandBuffer cb;
|
||||
DEV(vkAllocateCommandBuffers)(gDevice, &ai, &cb);
|
||||
VkCommandBufferBeginInfo bi;
|
||||
memset(&bi, 0, sizeof(bi));
|
||||
bi.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||
DEV(vkBeginCommandBuffer)(cb, &bi);
|
||||
VkImageMemoryBarrier b;
|
||||
memset(&b, 0, sizeof(b));
|
||||
b.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
b.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
b.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
b.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
b.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
b.srcQueueFamilyIndex = b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
b.image = image;
|
||||
b.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
b.subresourceRange.levelCount = 1;
|
||||
b.subresourceRange.layerCount = 1;
|
||||
DEV(vkCmdPipelineBarrier)(cb, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, &b);
|
||||
VkBufferImageCopy region;
|
||||
memset(®ion, 0, sizeof(region));
|
||||
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
region.imageSubresource.layerCount = 1;
|
||||
region.imageExtent.width = (uint32_t)width;
|
||||
region.imageExtent.height = (uint32_t)height;
|
||||
region.imageExtent.depth = 1;
|
||||
DEV(vkCmdCopyImageToBuffer)(cb, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buf, 1, ®ion);
|
||||
b.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
b.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT;
|
||||
b.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
b.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
DEV(vkCmdPipelineBarrier)(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, NULL, 0, NULL, 1, &b);
|
||||
DEV(vkEndCommandBuffer)(cb);
|
||||
VkSubmitInfo si;
|
||||
memset(&si, 0, sizeof(si));
|
||||
si.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
si.commandBufferCount = 1;
|
||||
si.pCommandBuffers = &cb;
|
||||
int ok = DEV(vkQueueSubmit)(gQueue, 1, &si, VK_NULL_HANDLE) == VK_SUCCESS && DEV(vkQueueWaitIdle)(gQueue) == VK_SUCCESS;
|
||||
void* map = NULL;
|
||||
if (ok && DEV(vkMapMemory)(gDevice, mem, 0, VK_WHOLE_SIZE, 0, &map) == VK_SUCCESS) {
|
||||
FILE* f = fopen(path, "wb");
|
||||
if (f) {
|
||||
int ow = (width + step - 1) / step, oh = (height + step - 1) / step;
|
||||
fprintf(f, "P6\n%d %d\n255\n", ow, oh);
|
||||
const unsigned char* px = (const unsigned char*)map;
|
||||
for (int y = 0; y < height; y += step)
|
||||
for (int x = 0; x < width; x += step) {
|
||||
const unsigned char* q = px + ((size_t)y * width + x) * 4;
|
||||
unsigned char rgb[3] = {bgra ? q[2] : q[0], q[1], bgra ? q[0] : q[2]};
|
||||
fwrite(rgb, 1, 3, f);
|
||||
}
|
||||
fclose(f);
|
||||
} else ok = 0;
|
||||
DEV(vkUnmapMemory)(gDevice, mem);
|
||||
}
|
||||
DEV(vkDestroyCommandPool)(gDevice, pool, NULL);
|
||||
DEV(vkDestroyBuffer)(gDevice, buf, NULL);
|
||||
DEV(vkFreeMemory)(gDevice, mem, NULL);
|
||||
return ok;
|
||||
}
|
||||
|
||||
const MockGfx kMockGfxVk = {"vk", ownsBinding, describeBinding, getProc, formats, formatOf, createImages, fillImages, releaseWaitMs,
|
||||
releaseSnapshot, pollSnapshots, snapshotsChecked};
|
||||
releaseSnapshot, pollSnapshots, snapshotsChecked, dumpImage};
|
||||
Reference in new issue
Block a user