aboutsummaryrefslogtreecommitdiffstats
path: root/steamvr/image-wait/image_wait.c
diff options
context:
space:
mode:
Diffstat (limited to 'steamvr/image-wait/image_wait.c')
-rw-r--r--steamvr/image-wait/image_wait.c148
1 files changed, 148 insertions, 0 deletions
diff --git a/steamvr/image-wait/image_wait.c b/steamvr/image-wait/image_wait.c
new file mode 100644
index 0000000..e8112c3
--- /dev/null
+++ b/steamvr/image-wait/image_wait.c
@@ -0,0 +1,148 @@
+// SPDX-FileCopyrightText: 2026 Tomasz Kramkowski <tomasz@kramkow.ski>
+// SPDX-License-Identifier: CC0-1.0
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#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);
+}