aboutsummaryrefslogtreecommitdiffstats
path: root/steamvr/image-wait/image_wait.c
blob: e8112c39d895548fa86e1b761b0efb6b27000bb7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
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);
}