// SPDX-FileCopyrightText: 2026 Tomasz Kramkowski // SPDX-License-Identifier: CC0-1.0 #include #include #include #define ASSERT_SIZE(type, expected) \ static_assert(sizeof (type) == (expected), #type " size must be " #expected) #define ASSERT_OFFSET(type, field, expected) \ static_assert( \ offsetof(type, field) == (expected), \ #type "." #field " offset must be " #expected \ ) ASSERT_SIZE(void *, 8); enum { VK_PIPELINE_STAGE_ALL_COMMANDS_BIT = 0x00010000 }; typedef struct { uintptr_t semaphore; uint64_t value; } SemaphoreEntry; ASSERT_SIZE(SemaphoreEntry, 16); ASSERT_OFFSET(SemaphoreEntry, value, 8); // vr::IVRSharedResource struct SharedResource { const struct SharedResourceVtable *vtable; }; typedef struct Semaphore *GetSemaphore(struct SharedResource *); struct SharedResourceVtable { unsigned char unknown_000[0x30]; GetSemaphore *get_semaphore; }; ASSERT_OFFSET(struct SharedResourceVtable, get_semaphore, 0x30); typedef uint64_t SemaphoreGetU64(struct Semaphore *); struct SemaphoreVtable { unsigned char unknown_000[0x40]; SemaphoreGetU64 *get_handle; unsigned char unknown_048[0x70 - 0x48]; SemaphoreGetU64 *get_value; }; ASSERT_OFFSET(struct SemaphoreVtable, get_handle, 0x40); ASSERT_OFFSET(struct SemaphoreVtable, get_value, 0x70); // vr::IVRSharedSemaphore struct Semaphore { const struct SemaphoreVtable *vtable; }; // vr::IVRSharedImage struct Image { const void *vtable; }; struct Texture { unsigned char unknown_000[0x48]; struct Image *image; }; ASSERT_OFFSET(struct Texture, image, 0x48); struct Batch { unsigned char unknown_000[0xf0]; SemaphoreEntry *waits; SemaphoreEntry *waits_end; unsigned char unknown_100[0x140 - 0x100]; uint32_t *wait_stages; unsigned char unknown_148[0x398 - 0x148]; SemaphoreEntry *signals; SemaphoreEntry *signals_end; }; ASSERT_OFFSET(struct Batch, waits, 0xf0); ASSERT_OFFSET(struct Batch, waits_end, 0xf8); ASSERT_OFFSET(struct Batch, wait_stages, 0x140); ASSERT_OFFSET(struct Batch, signals, 0x398); ASSERT_OFFSET(struct Batch, signals_end, 0x3a0); struct RendererContext { unsigned char unknown_000[8]; struct Batch *batch; }; ASSERT_OFFSET(struct RendererContext, batch, 8); // CFacetVRRenderer struct Renderer { unsigned char unknown_000[0x12b0]; struct RendererContext *context; }; ASSERT_OFFSET(struct Renderer, context, 0x12b0); extern void append_wait( struct Batch *batch, uintptr_t semaphore, uint32_t stages, uint64_t value ) __attribute__((visibility("hidden"))); // The patch calls this function at the beginning of a method in the "Facet" // `renderer` which is called, among other things, on `texture`s that the current // submission batch will use as shader inputs (e.g. eye textures). void register_image_wait(struct Renderer *renderer, struct Texture *texture) { // First we need the completion semaphore for the texture. struct Image *image = texture->image; if (!image) return; // The vbase offset entry for converting `vr::IVRSharedImage *` to // `vr::IVRSharedResource *` is located at `image->vtable - 0x50`. ptrdiff_t resource_offset = *(const ptrdiff_t *)((const char *)image->vtable - 0x50); struct SharedResource *resource = (void *)((char *)image + resource_offset); struct Semaphore *semaphore = resource->vtable->get_semaphore(resource); if (!semaphore) return; uintptr_t handle = semaphore->vtable->get_handle(semaphore); if (!handle) return; // The current batch that is being prepared, and that may need the image // semaphore as a dependency. struct Batch *batch = renderer->context->batch; // If the texture we got called on has the same completion semaphore as one // of the completion signals of this batch, then making the batch depend on // the semaphore could create a loop, and that would be bad. for (SemaphoreEntry *p = batch->signals; p != batch->signals_end; p++) { if (p->semaphore == handle) return; } // If there's already a wait on this texture's semaphore, ensure it will // block all work in this batch and that the value is at least as high as the // semaphore value required for this texture to be ready. uint64_t value = semaphore->vtable->get_value(semaphore); uint32_t *stages = batch->wait_stages; for (SemaphoreEntry *p = batch->waits; p != batch->waits_end; p++, stages++) { if (p->semaphore != handle) continue; if (p->value < value) p->value = value; *stages |= VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; return; } // Otherwise append a wait on this texture's completion semaphore. append_wait(batch, handle, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, value); }