mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-17 19:00:25 +02:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3aad697ebe | ||
|
|
c04ce7fd00 | ||
|
|
3f06b1b9d8 | ||
|
|
1b2d058a04 | ||
|
|
b5fa53ed95 | ||
|
|
1f2c10a3ca | ||
|
|
12f1155752 |
No files matched your search
@@ -123,3 +123,29 @@ page of buttons shows this layout on the Frame.
|
||||
making up of the missed frames showed as a doubled picture when turning
|
||||
the head. Keep the Frame at 72 Hz, or lower `vr.resolution_scale` (about
|
||||
0.85) for 90.
|
||||
- 2026-10-05: doubled pictures when turning at 72 Hz too, the game at the
|
||||
edge of its frame (12.5 of 13.9 ms; SteamVR reprojecting 2-8% of the
|
||||
frames). The time was not the drawing's but waits: the layer copies read
|
||||
the GL state back (glGet*) and released each image at once, and with
|
||||
Mesa's GL thread (zink) every read waits for the thread to run all that
|
||||
is queued, every release for the GPU (the left eye's copy took 4.5-7 ms
|
||||
of the frame, the right one's, just after it, 0.7). On SteamVR the copies
|
||||
are now deferred (host_xr.c): no state read back (the game sets its own
|
||||
again), the images released together as the frame ends, and the eyes'
|
||||
work flushed to the GPU as soon as they are copied.
|
||||
`debug.xr_deferred_copies` = 0 brings back the old copies for comparing,
|
||||
1 defers them on any runtime; `vr.log_performance` logs the frame's parts
|
||||
and, with the compositor's counters, each copy's time.
|
||||
- 2026-10-05 later: the doubling was the eyes not fusing (each eye sharp
|
||||
alone) with multiview, and gone drawing the eyes one by one, which is now
|
||||
SteamVR's default (`debug.multiview_steamvr` for multiview). The per-eye
|
||||
corrections were checked against the projection (x, y from the top after
|
||||
the flip, the eyes' offsets in clip units) and agree with the Quest, where
|
||||
multiview fuses; the Frame's eyes are wider and differ from each other
|
||||
more, so a driver giving the right eye the left one's correction (zink on
|
||||
Turnip: ViewIndex, Turnip's multi-position output) would show there. For
|
||||
finding it: a self-test of the driver's views when the layers are made
|
||||
(logcat "multiview: self-test"), a frame whose views are turned apart
|
||||
drawn one by one, the views' shape in the log as it changes ("VR views:"),
|
||||
and `debug.gl_environment` ("TU_DEBUG=nomultipos", "ZINK_DEBUG=nobgc",
|
||||
"MESA_SHADER_CACHE_DISABLE=true") set before the driver starts.
|
||||
@@ -119,7 +119,9 @@ headset before the game started, and set HALO_VR); 0 on failure */
|
||||
int host_xr_initialize(struct halo_xr_info *info, float wanted_refresh_rate);
|
||||
/* waits for the next frame and reports the predicted poses */
|
||||
int host_xr_begin_frame(struct halo_xr_frame *frame);
|
||||
/* copies a render target texture (0: clears) into a layer's image */
|
||||
/* copies a render target texture (0: clears) into a layer's image: 0 not
|
||||
copied, 1 copied with the GL state put back, 2 copied with the copy's GL
|
||||
state left (the deferred copies, host_xr.c), which the game sets again */
|
||||
int host_xr_copy_layer(int layer, unsigned int texture, int clear_alpha);
|
||||
/* the same for part of the texture: left, top, right, bottom, 0..1 from its
|
||||
top left */
|
||||
|
||||
@@ -205,6 +205,24 @@ static double config_number(const char *path, const char *key, double fallback)
|
||||
return value;
|
||||
}
|
||||
|
||||
/* a string from config.toml into text (size bytes), or "" when it is not
|
||||
set */
|
||||
static void config_text(const char *path, const char *key, char *text, size_t size)
|
||||
{
|
||||
toml_result_t result = toml_parse_file_ex(path);
|
||||
|
||||
text[0] = 0;
|
||||
if (!result.ok)
|
||||
return;
|
||||
{
|
||||
toml_datum_t datum = toml_seek(result.toptab, key);
|
||||
|
||||
if (datum.type == TOML_STRING && datum.u.s)
|
||||
snprintf(text, size, "%s", datum.u.s);
|
||||
}
|
||||
toml_free(result);
|
||||
}
|
||||
|
||||
/* POSIX TZ for the current local offset (the guest's musl has no zone
|
||||
database) */
|
||||
static void time_zone(char *buffer, size_t size)
|
||||
@@ -337,6 +355,34 @@ static void *game_main(void *unused)
|
||||
host_logf(HOST_LOG_INFO, "Mesa's GL thread %s (debug.gl_thread)", gl_thread != 0.0 ? "on" : "off");
|
||||
}
|
||||
}
|
||||
{
|
||||
/* (debug.xr_deferred_copies: the headset's layer copies deferred,
|
||||
host_xr.c, or one by one; -1 as the runtime has it, deferred on
|
||||
SteamVR) */
|
||||
double deferred = config_number(path, "debug.xr_deferred_copies", -1.0);
|
||||
|
||||
if (deferred >= 0.0)
|
||||
setenv("HALO_XR_DEFERRED", deferred != 0.0 ? "1" : "0", 1);
|
||||
}
|
||||
{
|
||||
/* (debug.gl_environment: "NAME=value ..." for the GL driver, set
|
||||
before it starts: the Steam Frame's Mesa (zink on Turnip) reads
|
||||
ZINK_DEBUG, TU_DEBUG, MESA_SHADER_CACHE_DISABLE and the like) */
|
||||
char text[512];
|
||||
char *item, *rest = NULL;
|
||||
|
||||
config_text(path, "debug.gl_environment", text, sizeof(text));
|
||||
for (item = strtok_r(text, " ;", &rest); item; item = strtok_r(NULL, " ;", &rest))
|
||||
{
|
||||
char *equals = strchr(item, '=');
|
||||
|
||||
if (!equals || equals == item)
|
||||
continue;
|
||||
*equals = 0;
|
||||
setenv(item, equals + 1, 1);
|
||||
host_logf(HOST_LOG_INFO, "GL driver environment: %s=%s (debug.gl_environment)", item, equals + 1);
|
||||
}
|
||||
}
|
||||
{
|
||||
int eye_width, eye_height;
|
||||
|
||||
|
||||
+85
-18
@@ -58,6 +58,7 @@ struct xr_swapchain
|
||||
GLuint framebuffers[MAXIMUM_SWAPCHAIN_IMAGES];
|
||||
uint32_t acquired_index;
|
||||
int filled; /* this frame */
|
||||
int held; /* acquired, released only as the frame ends (xr.deferred) */
|
||||
};
|
||||
|
||||
struct xr_hand_actions
|
||||
@@ -136,7 +137,18 @@ static struct
|
||||
double copy_seconds[HALO_XR_LAYER_COUNT];
|
||||
double end_seconds;
|
||||
double acquire_seconds;
|
||||
double release_seconds;
|
||||
int timed_frames;
|
||||
/* The copies without waits in the frame (SteamVR: HALO_XR_DEFERRED,
|
||||
debug.xr_deferred_copies). Each copy otherwise reads the GL state back
|
||||
to restore it (glGet*), and releases its image at once: with Mesa's GL
|
||||
thread (the Steam Frame's zink) each read waits for the thread to run
|
||||
everything queued, and each release for the GPU to finish it, so the
|
||||
game's one thread stood still for the eyes' whole drawing in the middle
|
||||
of every frame. Deferred, a copy leaves its GL state for the game to set
|
||||
again (host_xr_copy_layer's 2) and keeps its image until the frame ends,
|
||||
where all are released at once. */
|
||||
int deferred;
|
||||
int has_refresh_rate;
|
||||
PFN_xrRequestDisplayRefreshRateFB request_refresh_rate;
|
||||
PFN_xrGetDisplayRefreshRateFB get_refresh_rate;
|
||||
@@ -1283,6 +1295,16 @@ int host_xr_initialize(struct halo_xr_info *info, float wanted_refresh_rate)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
/* (deferred on SteamVR; HALO_XR_DEFERRED "1" or "0" says otherwise:
|
||||
debug.xr_deferred_copies, host_main.c) */
|
||||
const char *choice = getenv("HALO_XR_DEFERRED");
|
||||
|
||||
xr.deferred = choice && *choice ? atoi(choice) != 0 : xr.steamvr;
|
||||
host_logf(HOST_LOG_INFO, "xr: layer copies %s", xr.deferred ?
|
||||
"deferred (no waits for the GL thread or the GPU in the frame)" : "restored and released one by one");
|
||||
}
|
||||
|
||||
info->eye_width = xr.eye_width;
|
||||
info->eye_height = xr.eye_height;
|
||||
info->overlay_width = xr.eye_width;
|
||||
@@ -1481,23 +1503,27 @@ static int copy_layer_region(int layer, uint32_t texture, int clear_alpha, float
|
||||
return 0;
|
||||
swapchain = &xr.swapchains[layer];
|
||||
if (swapchain->filled)
|
||||
return 1;
|
||||
if (!xr_ok(xrAcquireSwapchainImage(swapchain->handle, &acquire, &swapchain->acquired_index),
|
||||
"xrAcquireSwapchainImage"))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return xr.deferred ? 2 : 1;
|
||||
wait.timeout = XR_INFINITE_DURATION;
|
||||
{
|
||||
double start = xr.has_metrics ? seconds_now() : 0.0;
|
||||
|
||||
if (!xr_ok(xrAcquireSwapchainImage(swapchain->handle, &acquire, &swapchain->acquired_index),
|
||||
"xrAcquireSwapchainImage"))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (!xr_ok(xrWaitSwapchainImage(swapchain->handle, &wait), "xrWaitSwapchainImage"))
|
||||
return 0;
|
||||
if (xr.has_metrics)
|
||||
xr.acquire_seconds += seconds_now() - start;
|
||||
}
|
||||
|
||||
gl_save(&saved);
|
||||
/* (deferred: nothing read back; the game sets its state again) */
|
||||
if (xr.deferred)
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
else
|
||||
gl_save(&saved);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, swapchain->framebuffers[swapchain->acquired_index]);
|
||||
glViewport(0, 0, swapchain->width, swapchain->height);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
@@ -1562,13 +1588,22 @@ static int copy_layer_region(int layer, uint32_t texture, int clear_alpha, float
|
||||
glBindVertexArray(xr.vertex_array);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glBindSampler(0, xr.sampler);
|
||||
glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, &swizzle_r);
|
||||
glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, &swizzle_b);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_RED);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_BLUE);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, swizzle_r);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, swizzle_b);
|
||||
if (xr.deferred)
|
||||
{
|
||||
/* (the game's render targets, all a copy reads, have no
|
||||
swizzle: only its uploaded textures get one, xbox_textures.c) */
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, &swizzle_r);
|
||||
glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, &swizzle_b);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_RED);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_BLUE);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, swizzle_r);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, swizzle_b);
|
||||
}
|
||||
if (clear_alpha == HALO_XR_COPY_OPAQUE)
|
||||
{
|
||||
/* an opaque layer: the game's alpha channel holds other things */
|
||||
@@ -1582,13 +1617,42 @@ static int copy_layer_region(int layer, uint32_t texture, int clear_alpha, float
|
||||
glClearColor(0.0f, 0.0f, 0.0f, clear_alpha ? 1.0f : 0.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
swapchain->filled = 1;
|
||||
if (xr.deferred)
|
||||
{
|
||||
swapchain->held = 1;
|
||||
/* (the eyes, most of the frame's work, on to the GPU while the
|
||||
game draws the rest) */
|
||||
if (layer == HALO_XR_LAYER_EYE_RIGHT)
|
||||
glFlush();
|
||||
return 2;
|
||||
}
|
||||
gl_restore(&saved);
|
||||
|
||||
xrReleaseSwapchainImage(swapchain->handle, &release);
|
||||
swapchain->filled = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* deferred: the images the frame's copies kept, released before it ends */
|
||||
static void release_held(void)
|
||||
{
|
||||
double start = xr.has_metrics ? seconds_now() : 0.0;
|
||||
int layer;
|
||||
|
||||
for (layer = 0; layer < HALO_XR_LAYER_COUNT; layer++)
|
||||
{
|
||||
XrSwapchainImageReleaseInfo release = { XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO };
|
||||
struct xr_swapchain *swapchain = &xr.swapchains[layer];
|
||||
|
||||
if (!swapchain->held)
|
||||
continue;
|
||||
swapchain->held = 0;
|
||||
xr_ok(xrReleaseSwapchainImage(swapchain->handle, &release), "xrReleaseSwapchainImage");
|
||||
}
|
||||
if (xr.has_metrics)
|
||||
xr.release_seconds += seconds_now() - start;
|
||||
}
|
||||
|
||||
/* The Steam Frame: Android in a container, without XR_EXT_performance_settings
|
||||
to ask for the clocks, and the scheduler free to put the game's one busy
|
||||
thread, which simulates and draws each frame, on a slow core (its two
|
||||
@@ -1659,6 +1723,7 @@ int host_xr_end_frame(const struct halo_xr_submit *submit)
|
||||
if (!xr.frame_begun)
|
||||
return 0;
|
||||
xr.frame_begun = 0;
|
||||
release_held();
|
||||
|
||||
if (xr.frame_state.shouldRender && submit->eyes && xr.views_valid &&
|
||||
xr.swapchains[HALO_XR_LAYER_EYE_LEFT].filled && xr.swapchains[HALO_XR_LAYER_EYE_RIGHT].filled)
|
||||
@@ -1836,9 +1901,11 @@ int host_xr_end_frame(const struct halo_xr_submit *submit)
|
||||
strcat(copies, item);
|
||||
xr.copy_seconds[timed] = 0.0;
|
||||
}
|
||||
host_logf(HOST_LOG_INFO, "xr: per frame ms: copies (layer:ms)%s; of them waiting for images %.2f; "
|
||||
"xrEndFrame %.2f", copies, xr.acquire_seconds * 1e3 / frames, xr.end_seconds * 1e3 / frames);
|
||||
xr.acquire_seconds = xr.end_seconds = 0.0;
|
||||
host_logf(HOST_LOG_INFO, "xr: per frame ms: copies (layer:ms)%s; of them acquiring images %.2f; "
|
||||
"releasing them at the end %.2f; xrEndFrame %.2f (copies %s)", copies,
|
||||
xr.acquire_seconds * 1e3 / frames, xr.release_seconds * 1e3 / frames, xr.end_seconds * 1e3 / frames,
|
||||
xr.deferred ? "deferred" : "one by one");
|
||||
xr.acquire_seconds = xr.release_seconds = xr.end_seconds = 0.0;
|
||||
xr.timed_frames = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,9 @@ void halo_vr_multiview_sky(int sky);
|
||||
/* the shared camera's projection's [2][2] and [3][2], for what the game
|
||||
projects itself (sprites, lens flare tests) */
|
||||
void halo_vr_multiview_projection(float projection_22, float projection_32);
|
||||
/* lens flares are being drawn: placed by their depth, though not depth
|
||||
tested */
|
||||
void halo_vr_multiview_lens_flares(int drawing);
|
||||
|
||||
/* clears the back buffer to transparent black for the overlay, after the
|
||||
eyes; menus_active chooses where the overlay is shown */
|
||||
|
||||
+130
-1
@@ -915,6 +915,10 @@ static struct
|
||||
/* the shared camera's projection's depth, as the game projects a point
|
||||
itself (rasterizer_widget_project_billboard): z = [0] + [1] / depth */
|
||||
float projection_depth[2];
|
||||
/* what the game projects itself has its depth in z though not depth
|
||||
tested (lens flares: d3d8_multiview_projected_depth) */
|
||||
BOOL projected_depth;
|
||||
BOOL tested; /* multiview_self_test */
|
||||
} multiview;
|
||||
|
||||
/* the bound targets are the layers */
|
||||
@@ -962,6 +966,102 @@ int d3d8_multiview_supported(void)
|
||||
return device.gl_ready && xgpu_capabilities.multiview;
|
||||
}
|
||||
|
||||
/* Whether the driver gives each view its own correction, as the game's
|
||||
vertex programs take it (nv2a_vsh.c: a row of an array uniform picked by
|
||||
gl_ViewID_OVR): a point drawn into two 2x1 layers, the left pixel's in view
|
||||
0 and the right one's in view 1 by their rows, then read back. A driver
|
||||
that gave both views the first one's would draw the right eye as the left
|
||||
eye sees, which no two eyes fuse. Once, as the layers are first made. */
|
||||
static BOOL multiview_self_test(void)
|
||||
{
|
||||
static const char vertex_source[] =
|
||||
"#version 300 es\n"
|
||||
"#extension GL_OVR_multiview2 : require\n"
|
||||
"layout(num_views = 2) in;\n"
|
||||
"uniform vec4 rows[4];\n"
|
||||
"void main()\n"
|
||||
"{\n"
|
||||
"\tint row = int(gl_ViewID_OVR) * 2;\n"
|
||||
"\tvec4 position = vec4(0.0, 0.0, 0.5, 1.0);\n"
|
||||
"\tposition.x = dot(vec3(position.xw, 1.0), rows[row].xyz);\n"
|
||||
"\tgl_Position = position;\n"
|
||||
"\tgl_PointSize = 1.0;\n"
|
||||
"}\n";
|
||||
static const char fragment_source[] =
|
||||
"#version 300 es\n"
|
||||
"precision mediump float;\n"
|
||||
"out vec4 color;\n"
|
||||
"void main()\n"
|
||||
"{\n"
|
||||
"\tcolor = vec4(1.0);\n"
|
||||
"}\n";
|
||||
/* (x: view 0 to the left pixel's centre, view 1 to the right one's) */
|
||||
static const float rows[4][4] =
|
||||
{
|
||||
{ 1.0f, 0.0f, -0.5f, 0.0f }, { 1.0f, 0.0f, 0.0f, 0.0f },
|
||||
{ 1.0f, 0.0f, 0.5f, 0.0f }, { 1.0f, 0.0f, 0.0f, 0.0f },
|
||||
};
|
||||
static const GLenum draw_buffer = GL_COLOR_ATTACHMENT0;
|
||||
GLuint vertex = compile_shader(GL_VERTEX_SHADER, vertex_source, "multiview test vertex");
|
||||
GLuint fragment = compile_shader(GL_FRAGMENT_SHADER, fragment_source, "multiview test fragment");
|
||||
GLuint program, texture, framebuffer;
|
||||
GLint linked = 0;
|
||||
unsigned char pixels[2][8];
|
||||
BOOL passed;
|
||||
int layer;
|
||||
|
||||
if (!vertex || !fragment)
|
||||
return TRUE;
|
||||
program = glCreateProgram();
|
||||
glAttachShader(program, vertex);
|
||||
glAttachShader(program, fragment);
|
||||
glLinkProgram(program);
|
||||
glDeleteShader(vertex);
|
||||
glDeleteShader(fragment);
|
||||
glGetProgramiv(program, GL_LINK_STATUS, &linked);
|
||||
if (!linked)
|
||||
{
|
||||
platform_log("multiview: the self-test's program does not link; not tested");
|
||||
return TRUE;
|
||||
}
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
|
||||
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 2, 1, 2);
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
|
||||
glGenFramebuffers(1, &framebuffer);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
|
||||
glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0, 0, 2);
|
||||
glDrawBuffers(1, &draw_buffer);
|
||||
glViewport(0, 0, 2, 1);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glDisable(GL_BLEND);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
glDisable(GL_CULL_FACE);
|
||||
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glUseProgram(program);
|
||||
glUniform4fv(glGetUniformLocation(program, "rows"), 4, &rows[0][0]);
|
||||
glBindVertexArray(device.vertex_array);
|
||||
glDrawArrays(GL_POINTS, 0, 1);
|
||||
memset(pixels, 0, sizeof(pixels));
|
||||
for (layer = 0; layer < 2; layer++)
|
||||
{
|
||||
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0, layer);
|
||||
glReadPixels(0, 0, 2, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels[layer]);
|
||||
}
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glDeleteFramebuffers(1, &framebuffer);
|
||||
glDeleteTextures(1, &texture);
|
||||
xgpu_gl_state_invalidate();
|
||||
/* (view 0 lit only the left pixel, view 1 only the right one) */
|
||||
passed = pixels[0][0] > 128 && pixels[0][4] < 128 && pixels[1][0] < 128 && pixels[1][4] > 128;
|
||||
platform_log("multiview: self-test %s (view 0 drew %d %d, view 1 %d %d: each its own pixel)",
|
||||
passed ? "passed" : "FAILED", pixels[0][0], pixels[0][4], pixels[1][0], pixels[1][4]);
|
||||
return passed;
|
||||
}
|
||||
|
||||
/* rows: per view, the correction of x and of y (nv2a_vsh.c); resolve_rect:
|
||||
where each eye's picture lies in the shared camera's */
|
||||
int d3d8_multiview_begin(const float rows[4][4], const float resolve_rect[2][4])
|
||||
@@ -970,6 +1070,16 @@ int d3d8_multiview_begin(const float rows[4][4], const float resolve_rect[2][4])
|
||||
|
||||
if (!d3d8_multiview_supported() || !(back_buffer = render_target_get(&device.back_buffer)))
|
||||
return FALSE;
|
||||
if (!multiview.tested)
|
||||
{
|
||||
/* (failed: the eyes drawn one by one from now on, vr.c) */
|
||||
multiview.tested = TRUE;
|
||||
if (!multiview_self_test())
|
||||
{
|
||||
xgpu_capabilities.multiview = FALSE;
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
if (!multiview.framebuffer || multiview.width != back_buffer->target.gl_width ||
|
||||
multiview.height != back_buffer->target.gl_height)
|
||||
{
|
||||
@@ -1016,6 +1126,14 @@ int d3d8_multiview_begin(const float rows[4][4], const float resolve_rect[2][4])
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* the draws from now on, depth tested or not, have their depth in z (lens
|
||||
flares, drawn over the scene: a weapon's own, half a metre away, stood at
|
||||
infinity in stereo, a ball each eye saw elsewhere) */
|
||||
void d3d8_multiview_projected_depth(int projected)
|
||||
{
|
||||
multiview.projected_depth = projected != 0;
|
||||
}
|
||||
|
||||
/* the projection's depth for what the game projects itself: z = a + b /
|
||||
depth for a point that far along the view */
|
||||
void d3d8_multiview_depth(float a, float b)
|
||||
@@ -2938,7 +3056,7 @@ static struct program_entry *prepare_draw(BOOL immediate)
|
||||
|
||||
depth[0] = multiview.projection_depth[0];
|
||||
depth[1] = multiview.projection_depth[1];
|
||||
depth[2] = has_depth && D3D__RenderState[D3DRS_ZENABLE] ? 1.0f : 0.0f;
|
||||
depth[2] = (has_depth && D3D__RenderState[D3DRS_ZENABLE]) || multiview.projected_depth ? 1.0f : 0.0f;
|
||||
depth[3] = 0.0f;
|
||||
uniform_vec4(entry->mv_depth, entry->mv_depth_value, depth, 1);
|
||||
}
|
||||
@@ -4046,6 +4164,17 @@ unsigned int d3d8_back_buffer_texture(void)
|
||||
return back_buffer ? back_buffer->target.texture : 0;
|
||||
}
|
||||
|
||||
/* the headset's copy left GL as it drew (host_xr_copy_layer's 2, the
|
||||
deferred copies): every value is set again, and the vertex arrays, which
|
||||
the cache does not hold, are bound again */
|
||||
void d3d8_host_state_changed(void)
|
||||
{
|
||||
if (!device.gl_ready)
|
||||
return;
|
||||
xgpu_gl_state_invalidate();
|
||||
glBindVertexArray(device.vertex_array);
|
||||
}
|
||||
|
||||
/* clears the whole back buffer, its alpha too, and the depth buffer */
|
||||
void d3d8_clear_back_buffer(float red, float green, float blue, float alpha)
|
||||
{
|
||||
|
||||
@@ -911,6 +911,11 @@ static struct
|
||||
int failed;
|
||||
} art[MAXIMUM_ART];
|
||||
static long art_count;
|
||||
/* the lowest and highest texture the art stands for: every texture the
|
||||
renderer binds asks (xbox_textures.c), every frame of a game too, and the
|
||||
menus' lie together, made as the map loaded, mostly apart from the map's
|
||||
(in its texture cache) */
|
||||
static unsigned long art_lowest, art_highest;
|
||||
|
||||
void halo_menus_art_register(void const *texture, char const *png)
|
||||
{
|
||||
@@ -944,6 +949,10 @@ void halo_menus_art_register(void const *texture, char const *png)
|
||||
memset(&art[index], 0, sizeof(art[index]));
|
||||
art[index].data = data;
|
||||
art[index].png = strdup(png);
|
||||
if (art_count == 1 || data < art_lowest)
|
||||
art_lowest = data;
|
||||
if (art_count == 1 || data > art_highest)
|
||||
art_highest = data;
|
||||
}
|
||||
|
||||
void halo_menus_art_forget(void)
|
||||
@@ -957,12 +966,15 @@ void halo_menus_art_forget(void)
|
||||
glDeleteTextures(1, &art[index].texture);
|
||||
}
|
||||
art_count = 0;
|
||||
art_lowest = art_highest = 0;
|
||||
}
|
||||
|
||||
unsigned int menu_art_texture(unsigned long data, unsigned long *levels)
|
||||
{
|
||||
long index;
|
||||
|
||||
if (!art_count || data < art_lowest || data > art_highest)
|
||||
return 0;
|
||||
for (index = 0; index < art_count; index++)
|
||||
{
|
||||
if (art[index].data == data)
|
||||
|
||||
@@ -505,9 +505,21 @@ static const struct config_setting config_settings[] =
|
||||
"machines of twice it), to test the netcode as over the internet; 0 none." },
|
||||
{ "debug.network_loss", _config_real, "0.0", "HALO_NETWORK_LOSS", _environment_value, _platform_all,
|
||||
"Percent of datagrams received that are dropped, for the same; 0 none." },
|
||||
{ "debug.multiview_steamvr", _config_boolean, "false", "HALO_MULTIVIEW_STEAMVR", _environment_value,
|
||||
_platform_android,
|
||||
"On SteamVR (the Steam Frame) the eyes are drawn one by one, whatever\n"
|
||||
"vr.multiview says: true draws them at once there too, for testing." },
|
||||
{ "debug.gl_thread", _config_integer, "-1", "HALO_GL_THREAD", _environment_value, _platform_android,
|
||||
"Mesa's GL thread (its drivers: the Steam Frame's): 1 on, 0 off, -1 as the\n"
|
||||
"driver chooses. Read when the app starts." },
|
||||
{ "debug.gl_environment", _config_string, "\"\"", "HALO_GL_ENVIRONMENT", _environment_value, _platform_android,
|
||||
"Variables for the GL driver, \"NAME=value NAME2=value\", set before it starts\n"
|
||||
"(the Steam Frame's Mesa: ZINK_DEBUG, TU_DEBUG, ...), for trying. Read when\n"
|
||||
"the app starts." },
|
||||
{ "debug.xr_deferred_copies", _config_integer, "-1", "HALO_XR_DEFERRED", _environment_value, _platform_android,
|
||||
"The headset's layer copies: 1 deferred (no waits for the GL thread or the\n"
|
||||
"GPU in the middle of the frame), 0 each one restored and released at once,\n"
|
||||
"-1 as the runtime needs (deferred on SteamVR). Read when the app starts." },
|
||||
{ "debug.test_input", _config_string, "\"\"", "HALO_TEST_INPUT", _environment_value, _platform_all,
|
||||
"\"bot:<seed>\" plays controller 1 with a scripted pattern (automated\n"
|
||||
"network tests); \"look:<seed>\" stands still, only turning and looking\n"
|
||||
|
||||
+102
-15
@@ -236,9 +236,20 @@ int d3d8_multiview_supported(void);
|
||||
int d3d8_multiview_begin(const float rows[4][4], const float resolve_rect[2][4]);
|
||||
void d3d8_multiview_rows(const float rows[4][4]);
|
||||
void d3d8_multiview_depth(float a, float b);
|
||||
void d3d8_multiview_projected_depth(int projected);
|
||||
unsigned int d3d8_multiview_end(void);
|
||||
void d3d8_clear_back_buffer(float red, float green, float blue, float alpha);
|
||||
void d3d8_clear_back_buffer_region(float left, float top, float right, float bottom);
|
||||
void d3d8_host_state_changed(void);
|
||||
|
||||
/* a layer copied (host_xr_copy_layer): 2 is a deferred copy, which left GL
|
||||
as it drew, for the renderer to set again */
|
||||
static int vr_copied(int result)
|
||||
{
|
||||
if (result == 2)
|
||||
d3d8_host_state_changed();
|
||||
return result != 0;
|
||||
}
|
||||
|
||||
/* The motion tracker, the HUD's one element at its bottom left, moves from
|
||||
the HUD to the left wrist (host_xr.c draws it there): this part of the
|
||||
@@ -503,6 +514,16 @@ void halo_vr_initialize(void)
|
||||
vr.active = 1;
|
||||
platform_log("VR: %s, eyes %dx%d at %.0f Hz", vr.info.system_name, vr.info.eye_width, vr.info.eye_height,
|
||||
vr.info.refresh_rate);
|
||||
/* SteamVR (the Steam Frame): each eye drawn apart. With both drawn at
|
||||
once the eyes' pictures did not fuse, now and then: each alone was
|
||||
sharp, the two together doubled as the head turned (2026-10-05), and
|
||||
one by one costs it no more (its time goes to the driver either way).
|
||||
debug.multiview_steamvr draws them at once there again, for finding why */
|
||||
if (vr.multiview && strstr(vr.info.system_name, "SteamVR") && !config_boolean("debug.multiview_steamvr"))
|
||||
{
|
||||
vr.multiview = 0;
|
||||
platform_log("VR: SteamVR: the eyes drawn one by one (debug.multiview_steamvr)");
|
||||
}
|
||||
}
|
||||
|
||||
int halo_vr_active(void)
|
||||
@@ -1312,9 +1333,8 @@ void halo_vr_present(unsigned int back_buffer_texture)
|
||||
if (vr.overlay_mode == HALO_XR_OVERLAY_HEAD_LOCKED && vr.motion_tracker_on_wrist && vr.aim_with_controller)
|
||||
{
|
||||
/* the motion tracker to the wrist, and out of the HUD */
|
||||
submit.wrist = host_xr_copy_layer_region(HALO_XR_LAYER_WRIST, back_buffer_texture, HALO_XR_COPY_ALPHA,
|
||||
WRIST_REGION_LEFT,
|
||||
WRIST_REGION_TOP, WRIST_REGION_RIGHT, WRIST_REGION_BOTTOM);
|
||||
submit.wrist = vr_copied(host_xr_copy_layer_region(HALO_XR_LAYER_WRIST, back_buffer_texture,
|
||||
HALO_XR_COPY_ALPHA, WRIST_REGION_LEFT, WRIST_REGION_TOP, WRIST_REGION_RIGHT, WRIST_REGION_BOTTOM));
|
||||
submit.wrist_size = vr.motion_tracker_size;
|
||||
submit.wrist_turn = vr.motion_tracker_turn;
|
||||
submit.wrist_right = vr.motion_tracker_right;
|
||||
@@ -1345,8 +1365,8 @@ void halo_vr_present(unsigned int back_buffer_texture)
|
||||
submit.scope_size = vr.scope_size;
|
||||
submit.scope_pose = vr.scope_pose;
|
||||
}
|
||||
host_xr_copy_layer(HALO_XR_LAYER_OVERLAY, back_buffer_texture,
|
||||
vr.overlay_blend ? HALO_XR_COPY_ALPHA : HALO_XR_COPY_OPAQUE);
|
||||
vr_copied(host_xr_copy_layer(HALO_XR_LAYER_OVERLAY, back_buffer_texture,
|
||||
vr.overlay_blend ? HALO_XR_COPY_ALPHA : HALO_XR_COPY_OPAQUE));
|
||||
host_xr_end_frame(&submit);
|
||||
vr.last_overlay_mode = vr.overlay_mode;
|
||||
vr.last_frame_stereo = submit.eyes;
|
||||
@@ -1427,6 +1447,63 @@ static int multiview_drawing;
|
||||
about the camera, as though infinitely far: no eye stands aside of it */
|
||||
static float multiview_rows[2][4][4];
|
||||
|
||||
/* The one camera between the eyes holds only while their views look the
|
||||
same way (the corrections move each eye's picture aside, and scale it, but
|
||||
cannot turn it): a frame whose views are turned apart, which a runtime may
|
||||
report after the start (an eye's calibration), draws the eyes one by one.
|
||||
The views' shape and how far apart they turn go to the log as they change. */
|
||||
#define MULTIVIEW_PARALLEL_DEGREES 0.05f
|
||||
|
||||
static int views_parallel(void)
|
||||
{
|
||||
static const float local_forward[3] = { 0.0f, 0.0f, -1.0f };
|
||||
static const float local_up[3] = { 0.0f, 1.0f, 0.0f };
|
||||
static float logged[HALO_XR_EYE_COUNT][4];
|
||||
static float logged_degrees = -1.0f;
|
||||
static unsigned long long logged_us;
|
||||
const struct halo_xr_view *views = vr.frame.views;
|
||||
float forward[2][3], up[2][3], along, degrees;
|
||||
int eye, changed = 0;
|
||||
unsigned long long now;
|
||||
|
||||
for (eye = 0; eye < HALO_XR_EYE_COUNT; eye++)
|
||||
{
|
||||
quaternion_rotate(views[eye].pose.orientation, local_forward, forward[eye]);
|
||||
quaternion_rotate(views[eye].pose.orientation, local_up, up[eye]);
|
||||
}
|
||||
along = fminf(forward[0][0] * forward[1][0] + forward[0][1] * forward[1][1] + forward[0][2] * forward[1][2],
|
||||
up[0][0] * up[1][0] + up[0][1] * up[1][1] + up[0][2] * up[1][2]);
|
||||
degrees = acosf(fminf(1.0f, along)) * 180.0f / PI;
|
||||
|
||||
for (eye = 0; eye < HALO_XR_EYE_COUNT; eye++)
|
||||
{
|
||||
const float shape[4] = { views[eye].tangent_left, views[eye].tangent_right, views[eye].tangent_down,
|
||||
views[eye].tangent_up };
|
||||
int edge;
|
||||
|
||||
for (edge = 0; edge < 4; edge++)
|
||||
changed |= fabsf(shape[edge] - logged[eye][edge]) > 0.005f;
|
||||
}
|
||||
changed |= fabsf(degrees - logged_degrees) > 0.02f;
|
||||
now = now_us();
|
||||
if (changed && now - logged_us > 5000000ULL)
|
||||
{
|
||||
platform_log("VR views: %.3f degrees apart; left eye %.3f..%.3f, %.3f..%.3f; right eye %.3f..%.3f, %.3f..%.3f",
|
||||
degrees, views[0].tangent_left, views[0].tangent_right, views[0].tangent_down, views[0].tangent_up,
|
||||
views[1].tangent_left, views[1].tangent_right, views[1].tangent_down, views[1].tangent_up);
|
||||
for (eye = 0; eye < HALO_XR_EYE_COUNT; eye++)
|
||||
{
|
||||
logged[eye][0] = views[eye].tangent_left;
|
||||
logged[eye][1] = views[eye].tangent_right;
|
||||
logged[eye][2] = views[eye].tangent_down;
|
||||
logged[eye][3] = views[eye].tangent_up;
|
||||
}
|
||||
logged_degrees = degrees;
|
||||
logged_us = now;
|
||||
}
|
||||
return degrees <= MULTIVIEW_PARALLEL_DEGREES;
|
||||
}
|
||||
|
||||
int halo_vr_multiview(void)
|
||||
{
|
||||
int available = vr.active && vr.multiview && d3d8_multiview_supported();
|
||||
@@ -1436,7 +1513,7 @@ int halo_vr_multiview(void)
|
||||
platform_log("VR: both eyes %s", available ? "in one draw (multiview)" : "drawn one by one (no multiview)");
|
||||
vr.multiview_logged = 1;
|
||||
}
|
||||
return available && halo_vr_stereo_frame();
|
||||
return available && halo_vr_stereo_frame() && views_parallel();
|
||||
}
|
||||
|
||||
void halo_vr_center_camera(const float game_position[3], float position[3], float forward[3], float up[3],
|
||||
@@ -1544,6 +1621,15 @@ void halo_vr_multiview_projection(float projection_22, float projection_32)
|
||||
d3d8_multiview_depth(-projection_22, projection_32);
|
||||
}
|
||||
|
||||
/* lens flares are being drawn (rasterizer_lights.c): each eye's by the
|
||||
flare's depth, as the flare's test was, not at infinity (a weapon's own,
|
||||
the plasma pistol's at its prongs, half a metre away: a ball each eye saw
|
||||
elsewhere); the sun's are far enough for infinity either way */
|
||||
void halo_vr_multiview_lens_flares(int drawing)
|
||||
{
|
||||
d3d8_multiview_projected_depth(drawing && multiview_drawing);
|
||||
}
|
||||
|
||||
/* the sky is being drawn (render.c): as though infinitely far */
|
||||
void halo_vr_multiview_sky(int sky)
|
||||
{
|
||||
@@ -1559,9 +1645,9 @@ void halo_vr_capture_multiview(void)
|
||||
if (!multiview_drawing)
|
||||
return;
|
||||
multiview_drawing = 0;
|
||||
if (host_xr_copy_layer_array(HALO_XR_LAYER_EYE_LEFT, layers, 0))
|
||||
if (vr_copied(host_xr_copy_layer_array(HALO_XR_LAYER_EYE_LEFT, layers, 0)))
|
||||
vr.eyes_captured |= 1;
|
||||
if (host_xr_copy_layer_array(HALO_XR_LAYER_EYE_RIGHT, layers, 1))
|
||||
if (vr_copied(host_xr_copy_layer_array(HALO_XR_LAYER_EYE_RIGHT, layers, 1)))
|
||||
vr.eyes_captured |= 2;
|
||||
if (vr.eyes_start_us)
|
||||
vr.eyes_us += now_us() - vr.eyes_start_us;
|
||||
@@ -1571,7 +1657,7 @@ void halo_vr_capture_eye(int eye)
|
||||
{
|
||||
if (!vr.active || eye < 0 || eye >= HALO_XR_EYE_COUNT)
|
||||
return;
|
||||
if (host_xr_copy_layer(HALO_XR_LAYER_EYE_LEFT + eye, d3d8_back_buffer_texture(), 1))
|
||||
if (vr_copied(host_xr_copy_layer(HALO_XR_LAYER_EYE_LEFT + eye, d3d8_back_buffer_texture(), 1)))
|
||||
vr.eyes_captured |= 1 << eye;
|
||||
if (eye == HALO_XR_EYE_COUNT - 1 && vr.eyes_start_us)
|
||||
vr.eyes_us += now_us() - vr.eyes_start_us;
|
||||
@@ -2402,9 +2488,9 @@ void halo_vr_capture_names(void)
|
||||
{
|
||||
if (vr.name_count > 0)
|
||||
{
|
||||
vr.names_captured = host_xr_copy_layer_region(HALO_XR_LAYER_NAMES, d3d8_back_buffer_texture(),
|
||||
vr.names_captured = vr_copied(host_xr_copy_layer_region(HALO_XR_LAYER_NAMES, d3d8_back_buffer_texture(),
|
||||
HALO_XR_COPY_ALPHA, NAME_SLOT_LEFT / 640.0f, 0.0f, (NAME_SLOT_LEFT + NAME_SLOT_WIDTH) / 640.0f,
|
||||
HALO_XR_MAXIMUM_NAMES * NAME_SLOT_HEIGHT / 480.0f);
|
||||
HALO_XR_MAXIMUM_NAMES * NAME_SLOT_HEIGHT / 480.0f));
|
||||
}
|
||||
d3d8_clear_back_buffer(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
@@ -2512,8 +2598,8 @@ int halo_vr_scope_pass(void)
|
||||
void halo_vr_capture_scope(float left, float top, float right, float bottom)
|
||||
{
|
||||
vr.scope_pass = 0;
|
||||
vr.scope_captured = host_xr_copy_layer_region(HALO_XR_LAYER_SCOPE, d3d8_back_buffer_texture(), HALO_XR_COPY_DISC,
|
||||
left, top, right, bottom);
|
||||
vr.scope_captured = vr_copied(host_xr_copy_layer_region(HALO_XR_LAYER_SCOPE, d3d8_back_buffer_texture(),
|
||||
HALO_XR_COPY_DISC, left, top, right, bottom));
|
||||
}
|
||||
|
||||
int halo_vr_hide_crosshair(void)
|
||||
@@ -2530,9 +2616,9 @@ int halo_vr_crosshair_apart(void)
|
||||
|
||||
void halo_vr_capture_crosshair(void)
|
||||
{
|
||||
vr.crosshair_captured = host_xr_copy_layer_region(HALO_XR_LAYER_CROSSHAIR, d3d8_back_buffer_texture(),
|
||||
vr.crosshair_captured = vr_copied(host_xr_copy_layer_region(HALO_XR_LAYER_CROSSHAIR, d3d8_back_buffer_texture(),
|
||||
HALO_XR_COPY_ALPHA, CROSSHAIR_REGION_LEFT, CROSSHAIR_REGION_TOP, CROSSHAIR_REGION_RIGHT,
|
||||
CROSSHAIR_REGION_BOTTOM);
|
||||
CROSSHAIR_REGION_BOTTOM));
|
||||
d3d8_clear_back_buffer(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
@@ -2567,6 +2653,7 @@ void halo_vr_center_camera(const float game_position[3], float position[3], floa
|
||||
}
|
||||
void halo_vr_capture_multiview(void) {}
|
||||
void halo_vr_multiview_sky(int sky) { (void)sky; }
|
||||
void halo_vr_multiview_lens_flares(int drawing) { (void)drawing; }
|
||||
void halo_vr_multiview_projection(float projection_22, float projection_32)
|
||||
{
|
||||
(void)projection_22; (void)projection_32;
|
||||
|
||||
@@ -98,6 +98,7 @@ symbols in this file:
|
||||
#include "rasterizer_console_vars.h"
|
||||
#include "objects/widgets/widget_types.h"
|
||||
#include "main/main.h"
|
||||
#include "halo_vr.h" /* port: VR's stereo of the flares */
|
||||
#include <xtl.h>
|
||||
#include "rasterizer/xbox/rasterizer_xbox.h"
|
||||
#include "main/main.h"
|
||||
@@ -869,6 +870,9 @@ void rasterizer_lens_flares_draw(
|
||||
{
|
||||
short lens_flare_index;
|
||||
|
||||
/* port: in VR's both eyes at once, each eye's flare where its depth
|
||||
puts it (port/linux/src/vr.c) */
|
||||
halo_vr_multiview_lens_flares(TRUE);
|
||||
rasterizer_widget_begin(_widget_type_internal_sprite, 0);
|
||||
|
||||
for (lens_flare_index = 0; lens_flare_index < local_lens_flare_count; lens_flare_index++)
|
||||
@@ -1123,6 +1127,7 @@ void rasterizer_lens_flares_draw(
|
||||
|
||||
rasterizer_set_stencil_mode(RASTERIZER_STENCIL_MODE_NONE);
|
||||
rasterizer_widget_end();
|
||||
halo_vr_multiview_lens_flares(FALSE);
|
||||
|
||||
if (rasterizer_debug_options.lens_flare_sun_glow_enabled)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user