mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-24 09:00:30 +02:00
Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3aad697ebe | ||
|
|
c04ce7fd00 | ||
|
|
3f06b1b9d8 | ||
|
|
1b2d058a04 | ||
|
|
b5fa53ed95 | ||
|
|
1f2c10a3ca | ||
|
|
12f1155752 | ||
|
|
24e4123882 | ||
|
|
8466846d45 | ||
|
|
bc580cf1cf | ||
|
|
3d575f32ed | ||
|
|
727b9ca80d |
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)
|
||||
@@ -326,6 +344,45 @@ static void *game_main(void *unused)
|
||||
ranges: the headset's runtime, loaded by the probe, maps its libraries
|
||||
wherever there is room, the Xbox window at 0x80000000 included */
|
||||
snprintf(path, sizeof(path), "%s/config.toml", data_root);
|
||||
{
|
||||
/* (debug.gl_thread: Mesa's GL thread on or off, as the driver's
|
||||
mesa_glthread, before the GL context is made; -1 leaves it be) */
|
||||
double gl_thread = config_number(path, "debug.gl_thread", -1.0);
|
||||
|
||||
if (gl_thread >= 0.0)
|
||||
{
|
||||
setenv("mesa_glthread", gl_thread != 0.0 ? "true" : "false", 1);
|
||||
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;
|
||||
|
||||
|
||||
+277
-18
@@ -38,6 +38,8 @@ picture; the copy flips them to GL's bottom-up images.
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <sched.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define XR_USE_PLATFORM_ANDROID
|
||||
#define XR_USE_GRAPHICS_API_OPENGL_ES
|
||||
@@ -56,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
|
||||
@@ -119,6 +122,33 @@ static struct
|
||||
int left_grip_valid;
|
||||
|
||||
int has_performance_settings;
|
||||
/* (XR_META_performance_metrics: the compositor's counts, in the log,
|
||||
for telling frames shown late from the game's own pace) */
|
||||
int has_metrics;
|
||||
PFN_xrQueryPerformanceMetricsCounterMETA query_metric;
|
||||
XrPath metric_paths[16];
|
||||
uint32_t metric_count;
|
||||
struct timespec metrics_logged;
|
||||
/* (SteamVR: the frames' thread kept on the fast cores, xr_fast_cores) */
|
||||
int steamvr;
|
||||
int cores_pinned;
|
||||
/* (with the metrics: the time the copies into the layers' images and
|
||||
xrEndFrame took, since the last line, and the frames) */
|
||||
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;
|
||||
@@ -1080,6 +1110,9 @@ int host_xr_probe(float resolution_scale, int *eye_width, int *eye_height)
|
||||
xr.has_refresh_rate = has_extension(extensions, extension_count, XR_FB_DISPLAY_REFRESH_RATE_EXTENSION_NAME);
|
||||
if (xr.has_refresh_rate)
|
||||
enabled[enabled_count++] = XR_FB_DISPLAY_REFRESH_RATE_EXTENSION_NAME;
|
||||
xr.has_metrics = has_extension(extensions, extension_count, XR_META_PERFORMANCE_METRICS_EXTENSION_NAME);
|
||||
if (xr.has_metrics)
|
||||
enabled[enabled_count++] = XR_META_PERFORMANCE_METRICS_EXTENSION_NAME;
|
||||
|
||||
android.applicationVM = machine;
|
||||
android.applicationActivity = activity;
|
||||
@@ -1113,6 +1146,7 @@ int host_xr_probe(float resolution_scale, int *eye_width, int *eye_height)
|
||||
if (XR_SUCCEEDED(xrGetInstanceProperties(xr.instance, &instance)))
|
||||
{
|
||||
xr.quad_alpha_from_colour = strstr(instance.runtimeName, "SteamVR") != NULL;
|
||||
xr.steamvr = xr.quad_alpha_from_colour;
|
||||
host_logf(HOST_LOG_INFO, "xr: runtime %s", instance.runtimeName);
|
||||
}
|
||||
if (choice)
|
||||
@@ -1226,6 +1260,25 @@ int host_xr_initialize(struct halo_xr_info *info, float wanted_refresh_rate)
|
||||
xr.get_refresh_rate(xr.session, &info->refresh_rate);
|
||||
}
|
||||
|
||||
if (xr.has_metrics)
|
||||
{
|
||||
PFN_xrEnumeratePerformanceMetricsCounterPathsMETA enumerate = NULL;
|
||||
PFN_xrSetPerformanceMetricsStateMETA set_state = NULL;
|
||||
XrPerformanceMetricsStateMETA state = { XR_TYPE_PERFORMANCE_METRICS_STATE_META };
|
||||
|
||||
xrGetInstanceProcAddr(xr.instance, "xrEnumeratePerformanceMetricsCounterPathsMETA",
|
||||
(PFN_xrVoidFunction *)&enumerate);
|
||||
xrGetInstanceProcAddr(xr.instance, "xrSetPerformanceMetricsStateMETA", (PFN_xrVoidFunction *)&set_state);
|
||||
xrGetInstanceProcAddr(xr.instance, "xrQueryPerformanceMetricsCounterMETA",
|
||||
(PFN_xrVoidFunction *)&xr.query_metric);
|
||||
state.enabled = XR_TRUE;
|
||||
xr.has_metrics = enumerate && set_state && xr.query_metric &&
|
||||
XR_SUCCEEDED(enumerate(xr.instance, 16, &xr.metric_count, xr.metric_paths)) &&
|
||||
XR_SUCCEEDED(set_state(xr.session, &state));
|
||||
host_logf(HOST_LOG_INFO, "xr: performance metrics %s (%u counters)", xr.has_metrics ? "on" : "off",
|
||||
xr.has_metrics ? xr.metric_count : 0);
|
||||
}
|
||||
|
||||
if (xr.has_performance_settings)
|
||||
{
|
||||
/* the game's one busy thread and its GPU work both need the clocks
|
||||
@@ -1242,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;
|
||||
@@ -1405,8 +1468,30 @@ int host_xr_copy_layer_array(int layer, uint32_t texture, int array_layer)
|
||||
|
||||
/* copies part of a render target (left, top, right, bottom, 0..1 from its
|
||||
top left) into a layer's image */
|
||||
static double seconds_now(void)
|
||||
{
|
||||
struct timespec now;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
return (double)now.tv_sec + (double)now.tv_nsec * 1e-9;
|
||||
}
|
||||
|
||||
static int copy_layer_region(int layer, uint32_t texture, int clear_alpha, float left, float top, float right,
|
||||
float bottom);
|
||||
|
||||
int host_xr_copy_layer_region(int layer, uint32_t texture, int clear_alpha, float left, float top, float right,
|
||||
float bottom)
|
||||
{
|
||||
double start = xr.has_metrics ? seconds_now() : 0.0;
|
||||
int copied = copy_layer_region(layer, texture, clear_alpha, left, top, right, bottom);
|
||||
|
||||
if (xr.has_metrics && layer >= 0 && layer < HALO_XR_LAYER_COUNT)
|
||||
xr.copy_seconds[layer] += seconds_now() - start;
|
||||
return copied;
|
||||
}
|
||||
|
||||
static int copy_layer_region(int layer, uint32_t texture, int clear_alpha, float left, float top, float right,
|
||||
float bottom)
|
||||
{
|
||||
struct xr_swapchain *swapchain;
|
||||
XrSwapchainImageAcquireInfo acquire = { XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO };
|
||||
@@ -1418,17 +1503,27 @@ int host_xr_copy_layer_region(int layer, uint32_t texture, int clear_alpha, floa
|
||||
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;
|
||||
if (!xr_ok(xrWaitSwapchainImage(swapchain->handle, &wait), "xrWaitSwapchainImage"))
|
||||
return 0;
|
||||
{
|
||||
double start = xr.has_metrics ? seconds_now() : 0.0;
|
||||
|
||||
gl_save(&saved);
|
||||
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;
|
||||
}
|
||||
|
||||
/* (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);
|
||||
@@ -1493,13 +1588,22 @@ int host_xr_copy_layer_region(int layer, uint32_t texture, int clear_alpha, floa
|
||||
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 */
|
||||
@@ -1513,13 +1617,95 @@ int host_xr_copy_layer_region(int layer, uint32_t texture, int clear_alpha, floa
|
||||
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
|
||||
2.3 GHz ones) now and then: frames late to the compositor, shown twice,
|
||||
the picture doubled as the head turns. The thread that ends the frames is
|
||||
kept on the cores no slower than nine tenths of the fastest. */
|
||||
static void xr_fast_cores(void)
|
||||
{
|
||||
cpu_set_t set;
|
||||
long fastest = 0, speeds[CPU_SETSIZE];
|
||||
int cpu, count = 0;
|
||||
char line[128] = "";
|
||||
|
||||
for (cpu = 0; cpu < CPU_SETSIZE && cpu < 64; cpu++)
|
||||
{
|
||||
char path[96];
|
||||
FILE *file;
|
||||
|
||||
speeds[cpu] = 0;
|
||||
snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq", cpu);
|
||||
file = fopen(path, "r");
|
||||
if (!file)
|
||||
continue;
|
||||
if (fscanf(file, "%ld", &speeds[cpu]) != 1)
|
||||
speeds[cpu] = 0;
|
||||
fclose(file);
|
||||
if (speeds[cpu] > fastest)
|
||||
fastest = speeds[cpu];
|
||||
}
|
||||
if (!fastest)
|
||||
{
|
||||
host_logf(HOST_LOG_INFO, "xr: the cores' speeds unknown: the frames' thread left where it runs");
|
||||
return;
|
||||
}
|
||||
CPU_ZERO(&set);
|
||||
for (cpu = 0; cpu < CPU_SETSIZE && cpu < 64; cpu++)
|
||||
{
|
||||
if (speeds[cpu] && speeds[cpu] * 10 >= fastest * 9)
|
||||
{
|
||||
char item[8];
|
||||
|
||||
CPU_SET(cpu, &set);
|
||||
count++;
|
||||
snprintf(item, sizeof(item), " %d", cpu);
|
||||
strcat(line, item);
|
||||
}
|
||||
}
|
||||
if (count && sched_setaffinity(0, sizeof(set), &set) == 0)
|
||||
host_logf(HOST_LOG_INFO, "xr: the frames' thread on the fast cores:%s (fastest %ld MHz)", line, fastest / 1000);
|
||||
else
|
||||
host_logf(HOST_LOG_INFO, "xr: the frames' thread could not be kept on the fast cores");
|
||||
}
|
||||
|
||||
int host_xr_end_frame(const struct halo_xr_submit *submit)
|
||||
{
|
||||
XrFrameEndInfo end = { XR_TYPE_FRAME_END_INFO };
|
||||
@@ -1537,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)
|
||||
@@ -1662,11 +1849,83 @@ int host_xr_end_frame(const struct halo_xr_submit *submit)
|
||||
}
|
||||
}
|
||||
|
||||
if (xr.steamvr && !xr.cores_pinned)
|
||||
{
|
||||
xr.cores_pinned = 1;
|
||||
if (!getenv("HALO_XR_ANY_CORE"))
|
||||
xr_fast_cores();
|
||||
}
|
||||
/* (every five seconds, the compositor's counters, and the core the
|
||||
frames' thread is on) */
|
||||
if (xr.has_metrics)
|
||||
{
|
||||
struct timespec now;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
if (now.tv_sec - xr.metrics_logged.tv_sec >= 5)
|
||||
{
|
||||
char line[768] = "";
|
||||
uint32_t counter;
|
||||
|
||||
xr.metrics_logged = now;
|
||||
for (counter = 0; counter < xr.metric_count && counter < 16; counter++)
|
||||
{
|
||||
XrPerformanceMetricsCounterMETA value = { XR_TYPE_PERFORMANCE_METRICS_COUNTER_META };
|
||||
char name[XR_MAX_PATH_LENGTH];
|
||||
uint32_t length = 0;
|
||||
char item[96];
|
||||
|
||||
if (!XR_SUCCEEDED(xr.query_metric(xr.session, xr.metric_paths[counter], &value)) ||
|
||||
!XR_SUCCEEDED(xrPathToString(xr.instance, xr.metric_paths[counter], sizeof(name), &length, name)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
snprintf(item, sizeof(item), " %s=%g", strrchr(name, '/') ? strrchr(name, '/') + 1 : name,
|
||||
(value.counterFlags & XR_PERFORMANCE_METRICS_COUNTER_FLOAT_VALUE_VALID_BIT_META) ?
|
||||
(double)value.floatValue : (double)value.uintValue);
|
||||
if (strlen(line) + strlen(item) < sizeof(line))
|
||||
strcat(line, item);
|
||||
}
|
||||
host_logf(HOST_LOG_INFO, "xr: metrics%s, on core %d", line, sched_getcpu());
|
||||
if (xr.timed_frames)
|
||||
{
|
||||
double frames = (double)xr.timed_frames;
|
||||
char copies[256] = "";
|
||||
int timed;
|
||||
|
||||
for (timed = 0; timed < HALO_XR_LAYER_COUNT; timed++)
|
||||
{
|
||||
char item[24];
|
||||
|
||||
snprintf(item, sizeof(item), " %d:%.2f", timed, xr.copy_seconds[timed] * 1e3 / frames);
|
||||
strcat(copies, item);
|
||||
xr.copy_seconds[timed] = 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
end.displayTime = xr.frame_state.predictedDisplayTime;
|
||||
end.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
|
||||
end.layerCount = layer_count;
|
||||
end.layers = layers;
|
||||
return xr_ok(xrEndFrame(xr.session, &end), "xrEndFrame");
|
||||
{
|
||||
double start = xr.has_metrics ? seconds_now() : 0.0;
|
||||
int ended = xr_ok(xrEndFrame(xr.session, &end), "xrEndFrame");
|
||||
|
||||
if (xr.has_metrics)
|
||||
{
|
||||
xr.end_seconds += seconds_now() - start;
|
||||
xr.timed_frames++;
|
||||
}
|
||||
return ended;
|
||||
}
|
||||
}
|
||||
|
||||
void host_xr_set_vignette(float strength)
|
||||
|
||||
+231
-32
@@ -117,6 +117,10 @@ enum
|
||||
|
||||
/* ---------- structures */
|
||||
|
||||
/* (how many things to pick up it gives up at once, and for how long) */
|
||||
#define BOT_IGNORED_ITEMS 4
|
||||
#define BOT_IGNORE_ITEM_TICKS 600
|
||||
|
||||
struct bot
|
||||
{
|
||||
long player_index;
|
||||
@@ -141,8 +145,20 @@ struct bot
|
||||
while on that account: following a person, a thing to pick up */
|
||||
short path_failures;
|
||||
long follow_again_time;
|
||||
long ignored_item_index;
|
||||
long ignored_item_time;
|
||||
/* things to pick up given up for a while (no way to them, or there and
|
||||
not taken: on a ledge, out of reach), and how long it has stood by the
|
||||
one it goes for */
|
||||
long ignored_items[BOT_IGNORED_ITEMS];
|
||||
long ignored_item_times[BOT_IGNORED_ITEMS];
|
||||
short ignored_item_next;
|
||||
/* (the nearest it has come to the thing it goes for, and when) */
|
||||
long item_progress_index;
|
||||
real item_best_distance;
|
||||
long item_best_time;
|
||||
/* stuck again and again about the same place (as at the foot of a ramp
|
||||
or behind its rail): how often since when, to give the goal up */
|
||||
short stuck_count;
|
||||
long stuck_count_time;
|
||||
real wander_yaw;
|
||||
short weave_phase;
|
||||
/* where it is going, and the way there */
|
||||
@@ -231,6 +247,7 @@ static struct
|
||||
long switches;
|
||||
long stucks;
|
||||
long long_stucks;
|
||||
long gave_up;
|
||||
long fail_start;
|
||||
long fail_goal;
|
||||
long fail_search;
|
||||
@@ -540,18 +557,40 @@ the level between them at its knees */
|
||||
static boolean bot_clear_walk(
|
||||
long unit_index,
|
||||
real_point3d const *from,
|
||||
real_point3d const *to)
|
||||
real_point3d const *to,
|
||||
boolean wide)
|
||||
{
|
||||
struct collision_result collision;
|
||||
real_point3d start = *from;
|
||||
real_vector3d vector;
|
||||
real length;
|
||||
short line;
|
||||
|
||||
start.z += 0.3f;
|
||||
vector.i = to->x - from->x;
|
||||
vector.j = to->y - from->y;
|
||||
vector.k = to->z - from->z;
|
||||
return !collision_test_vector(FLAG(_collision_test_structure_bit) | FLAG(_collision_test_front_facing_surfaces_bit),
|
||||
&start, &vector, unit_index, &collision);
|
||||
length = (real)sqrt(vector.i * vector.i + vector.j * vector.j);
|
||||
/* (at its knees and at its chest: a rail with room under it, a slit in
|
||||
a wall, are not ways through; wide, along its shoulders too: a door it
|
||||
would pass the middle of but catch its frame) */
|
||||
for (line = 0; line < (wide && length > 0.001f ? 4 : 2); line++)
|
||||
{
|
||||
real_point3d start = *from;
|
||||
|
||||
start.z += line == 1 ? 1.0f : line ? 0.6f : 0.3f;
|
||||
if (line >= 2)
|
||||
{
|
||||
real side = line == 2 ? 0.35f : -0.35f;
|
||||
|
||||
start.x += -vector.j / length * side;
|
||||
start.y += vector.i / length * side;
|
||||
}
|
||||
if (collision_test_vector(FLAG(_collision_test_structure_bit) | FLAG(_collision_test_front_facing_surfaces_bit),
|
||||
&start, &vector, unit_index, &collision))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static boolean bot_next_point_now(
|
||||
@@ -592,7 +631,7 @@ static boolean bot_next_point_now(
|
||||
real dy = bot->goal.y - position->y;
|
||||
|
||||
if (dx * dx + dy * dy < 3.0f * 3.0f && fabsf(bot->goal.z - position->z) < 0.6f &&
|
||||
bot_clear_walk(unit_index, position, &bot->goal))
|
||||
bot_clear_walk(unit_index, position, &bot->goal, FALSE))
|
||||
{
|
||||
*point = bot->goal;
|
||||
return TRUE;
|
||||
@@ -620,7 +659,7 @@ static boolean bot_next_point_now(
|
||||
|
||||
/* (on its own level only: one above may be in view over a
|
||||
ledge that a ramp leads round to) */
|
||||
if (fabsf(next->z - position->z) < 0.6f && bot_clear_walk(unit_index, position, next))
|
||||
if (fabsf(next->z - position->z) < 0.6f && bot_clear_walk(unit_index, position, next, TRUE))
|
||||
{
|
||||
bot->path.step_index++;
|
||||
if (bot->path.step_index >= bot->path.step_count)
|
||||
@@ -642,17 +681,20 @@ static boolean bot_next_point_now(
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* a goal when none is in sight: the nearest enemy, or somewhere near
|
||||
someone at random */
|
||||
/* a goal when none is in sight: an enemy, or somewhere near someone at
|
||||
random. Not all the nearest: each bot one of the three nearest (by its
|
||||
number), and while it is far, a few metres beside them, so that a team
|
||||
spreads out over the ways there instead of going as one crowd */
|
||||
static void bot_choose_goal(
|
||||
struct bot *bot,
|
||||
struct player_datum const *player,
|
||||
real_point3d const *position)
|
||||
{
|
||||
boolean teams = game_engine_has_teams();
|
||||
real best_distance = 1000000.0f;
|
||||
real best_distance[3] = { 1000000.0f, 1000000.0f, 1000000.0f };
|
||||
real_point3d best_origin[3];
|
||||
short found = 0;
|
||||
short index;
|
||||
boolean found = FALSE;
|
||||
|
||||
for (index = 0; index < MAXIMUM_TRACKED_PLAYERS; index++)
|
||||
{
|
||||
@@ -660,6 +702,7 @@ static void bot_choose_goal(
|
||||
long unit_index;
|
||||
real_point3d origin;
|
||||
real distance;
|
||||
short rank;
|
||||
|
||||
if (!other || other == player || (teams && other->team_index == player->team_index))
|
||||
continue;
|
||||
@@ -668,14 +711,38 @@ static void bot_choose_goal(
|
||||
continue;
|
||||
object_get_origin(unit_index, &origin);
|
||||
distance = distance3d(position, &origin);
|
||||
if (distance < best_distance)
|
||||
/* (kept in order, nearest first) */
|
||||
for (rank = 0; rank < 3 && distance >= best_distance[rank]; rank++)
|
||||
;
|
||||
if (rank == 3)
|
||||
continue;
|
||||
found = (short)MIN(found + 1, 3);
|
||||
{
|
||||
best_distance = distance;
|
||||
bot->goal = origin;
|
||||
found = TRUE;
|
||||
short move;
|
||||
|
||||
for (move = 2; move > rank; move--)
|
||||
{
|
||||
best_distance[move] = best_distance[move - 1];
|
||||
best_origin[move] = best_origin[move - 1];
|
||||
}
|
||||
}
|
||||
best_distance[rank] = distance;
|
||||
best_origin[rank] = origin;
|
||||
}
|
||||
if (found)
|
||||
{
|
||||
short pick = (short)(DATUM_INDEX_TO_ABSOLUTE_INDEX(bot->player_index) % found);
|
||||
|
||||
bot->goal = best_origin[pick];
|
||||
if (best_distance[pick] > 25.0f)
|
||||
{
|
||||
real angle = (real)DATUM_INDEX_TO_ABSOLUTE_INDEX(bot->player_index) * 2.4f;
|
||||
|
||||
bot->goal.x += (real)cos(angle) * 6.0f;
|
||||
bot->goal.y += (real)sin(angle) * 6.0f;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
else
|
||||
{
|
||||
/* (no enemy alive: a few metres off, somewhere) */
|
||||
real angle = bot_random() * 2.0f * _pi;
|
||||
@@ -817,7 +884,59 @@ static real bot_item_worth(
|
||||
}
|
||||
|
||||
/* the thing most worth going for near the bot, or NONE */
|
||||
static boolean bot_item_ignored(
|
||||
struct bot const *bot,
|
||||
long item_index)
|
||||
{
|
||||
short index;
|
||||
|
||||
for (index = 0; index < BOT_IGNORED_ITEMS; index++)
|
||||
{
|
||||
if (bot->ignored_items[index] == item_index &&
|
||||
game_time_get() - bot->ignored_item_times[index] < BOT_IGNORE_ITEM_TICKS &&
|
||||
game_time_get() >= bot->ignored_item_times[index])
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void bot_ignore_item(
|
||||
struct bot *bot,
|
||||
long item_index)
|
||||
{
|
||||
bot->ignored_items[bot->ignored_item_next] = item_index;
|
||||
bot->ignored_item_times[bot->ignored_item_next] = game_time_get();
|
||||
bot->ignored_item_next = (short)((bot->ignored_item_next + 1) % BOT_IGNORED_ITEMS);
|
||||
bot->item_progress_index = NONE;
|
||||
}
|
||||
|
||||
/* going for a thing to pick up: whether to give it up, coming no nearer
|
||||
(half a metre) for two and a half seconds: out of reach, past a rail, up
|
||||
where no way it finds goes, or in a crowd */
|
||||
static boolean bot_item_out_of_reach(
|
||||
struct bot *bot,
|
||||
real distance)
|
||||
{
|
||||
if (bot->item_progress_index != bot->item_index || game_time_get() < bot->item_best_time)
|
||||
{
|
||||
bot->item_progress_index = bot->item_index;
|
||||
bot->item_best_distance = distance;
|
||||
bot->item_best_time = game_time_get();
|
||||
return FALSE;
|
||||
}
|
||||
if (distance < bot->item_best_distance - 0.5f)
|
||||
{
|
||||
bot->item_best_distance = distance;
|
||||
bot->item_best_time = game_time_get();
|
||||
return FALSE;
|
||||
}
|
||||
return game_time_get() - bot->item_best_time > 75;
|
||||
}
|
||||
|
||||
static long bot_find_item(
|
||||
struct bot const *bot,
|
||||
struct unit_datum const *unit,
|
||||
real_point3d const *position,
|
||||
real range,
|
||||
@@ -840,7 +959,7 @@ static long bot_find_item(
|
||||
if (distance > range)
|
||||
continue;
|
||||
worth = bot_item_worth(unit, iterator.index);
|
||||
if (worth <= 0.0f || worth < least_worth)
|
||||
if (worth <= 0.0f || worth < least_worth || bot_item_ignored(bot, iterator.index))
|
||||
continue;
|
||||
/* (the strong ones worth a longer way) */
|
||||
score = worth * worth / (1.0f + distance / 15.0f);
|
||||
@@ -2393,7 +2512,7 @@ static void bot_think(
|
||||
game_time_get() < bot->item_look_time))
|
||||
{
|
||||
bot->item_look_time = game_time_get();
|
||||
bot->item_index = bot_find_item(unit, &position, 10.0f, 4.0f);
|
||||
bot->item_index = bot_find_item(bot, unit, &position, 10.0f, 4.0f);
|
||||
}
|
||||
if (bot->item_index != NONE && !bot->carrying && !bot->has_objective && object_try_and_get(bot->item_index) &&
|
||||
bot_item_worth(unit, bot->item_index) > 0.0f)
|
||||
@@ -2408,6 +2527,11 @@ static void bot_think(
|
||||
bot_walk_to(action, bot->yaw, &position, &point);
|
||||
if (distance3d(&position, &item_position) < 2.0f)
|
||||
bot_take_item(bot, player, unit, action);
|
||||
if (bot_item_out_of_reach(bot, distance3d(&position, &item_position)))
|
||||
{
|
||||
bot_ignore_item(bot, bot->item_index);
|
||||
bot->item_index = NONE;
|
||||
}
|
||||
}
|
||||
/* closes on it along its way, weaving, until near */
|
||||
else if (bot->has_objective && bot->objective_unit_index != bot->target_unit_index)
|
||||
@@ -2497,11 +2621,9 @@ static void bot_think(
|
||||
/* something worth having near: there first */
|
||||
else if (game_time_get() - bot->item_look_time >= ITEM_LOOK_TICKS || game_time_get() < bot->item_look_time)
|
||||
{
|
||||
long item = bot_find_item(unit, &position, BOT_ITEM_RANGE, 0.0f);
|
||||
long item = bot_find_item(bot, unit, &position, BOT_ITEM_RANGE, 0.0f);
|
||||
|
||||
bot->item_look_time = game_time_get();
|
||||
if (item == bot->ignored_item_index && game_time_get() - bot->ignored_item_time < 600)
|
||||
item = NONE;
|
||||
if (item != bot->item_index)
|
||||
{
|
||||
bot->item_index = item;
|
||||
@@ -2519,8 +2641,7 @@ static void bot_think(
|
||||
/* (taken or gone, or no way to it: on) */
|
||||
if (bot->path_failures >= 3)
|
||||
{
|
||||
bot->ignored_item_index = bot->item_index;
|
||||
bot->ignored_item_time = game_time_get();
|
||||
bot_ignore_item(bot, bot->item_index);
|
||||
bot->path_failures = 0;
|
||||
bot->item_index = NONE;
|
||||
bot->has_goal = FALSE;
|
||||
@@ -2536,6 +2657,14 @@ static void bot_think(
|
||||
bot->goal_time = game_time_get();
|
||||
if (distance3d(&position, &bot->goal) < 2.0f)
|
||||
bot_take_item(bot, player, unit, action);
|
||||
/* (no nearer a while: out of reach, as on a ledge, or not
|
||||
offered: given up for a while) */
|
||||
if (bot_item_out_of_reach(bot, distance3d(&position, &bot->goal)))
|
||||
{
|
||||
bot_ignore_item(bot, bot->item_index);
|
||||
bot->item_index = NONE;
|
||||
bot->has_goal = FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* (in a team, every third bot with nothing to do keeps near the
|
||||
@@ -2590,7 +2719,15 @@ static void bot_think(
|
||||
bot->path_failures = 0;
|
||||
}
|
||||
wanted_yaw = bot->wander_yaw;
|
||||
if (bot_next_point(bot, player->unit_index, &position, &point))
|
||||
/* (holding a spot and near it: there, not pressing on into what may
|
||||
stand on it, a pillar, a wall) */
|
||||
if (bot->holding && bot->has_objective &&
|
||||
(bot->goal.x - position.x) * (bot->goal.x - position.x) +
|
||||
(bot->goal.y - position.y) * (bot->goal.y - position.y) < 1.5f * 1.5f)
|
||||
{
|
||||
bot->walking = FALSE;
|
||||
}
|
||||
else if (bot_next_point(bot, player->unit_index, &position, &point))
|
||||
{
|
||||
real dx = point.x - position.x;
|
||||
real dy = point.y - position.y;
|
||||
@@ -2680,7 +2817,8 @@ static void bot_think(
|
||||
boolean progress = distance < bot->walk_distance - 0.015f || moved > 0.06f;
|
||||
|
||||
bot->walk_distance = MIN(bot->walk_distance, distance);
|
||||
if (progress && moved > 0.02f)
|
||||
/* (at the point, as on a thing to pick up: there, not stuck) */
|
||||
if ((progress && moved > 0.02f) || distance < 0.5f)
|
||||
{
|
||||
bot->stuck_ticks = 0;
|
||||
}
|
||||
@@ -2689,8 +2827,27 @@ static void bot_think(
|
||||
bot->stuck_ticks++;
|
||||
if (bot->stuck_ticks == 6)
|
||||
{
|
||||
bot->sidestep = bot_random() < 0.5f ? 1.0f : -1.0f;
|
||||
/* (to the side that is open, as round a pillar; either, at
|
||||
random: left, the strafe's positive, bot_walk_to) */
|
||||
real_point3d left = position, right = position;
|
||||
boolean left_open, right_open;
|
||||
|
||||
left.x += -(real)sin(bot->yaw) * 1.2f;
|
||||
left.y += (real)cos(bot->yaw) * 1.2f;
|
||||
right.x -= -(real)sin(bot->yaw) * 1.2f;
|
||||
right.y -= (real)cos(bot->yaw) * 1.2f;
|
||||
left_open = bot_clear_walk(player->unit_index, &position, &left, FALSE);
|
||||
right_open = bot_clear_walk(player->unit_index, &position, &right, FALSE);
|
||||
bot->sidestep = left_open != right_open ? (left_open ? 1.0f : -1.0f) :
|
||||
bot_random() < 0.5f ? 1.0f : -1.0f;
|
||||
bot->sidestep_ticks = 10;
|
||||
/* (the point a little above and near, up a step or the lip of
|
||||
a ramp: a jump on towards it, not a step aside) */
|
||||
if (bot->walk_point.z - position.z > 0.3f && distance < 3.0f)
|
||||
{
|
||||
bot->sidestep_ticks = 0;
|
||||
SET_FLAG(action->control_flags, _unit_control_jump_bit, TRUE);
|
||||
}
|
||||
/* (near a point of its path it cannot reach, at a corner: the
|
||||
next one) */
|
||||
if (distance < 1.5f && bot->path.valid && bot->path.step_index < bot->path.step_count)
|
||||
@@ -2699,6 +2856,38 @@ static void bot_think(
|
||||
bot->sidestep_ticks = 4;
|
||||
}
|
||||
bots.stucks++;
|
||||
/* (the third time in five seconds: the goal given up, the
|
||||
thing to pick up for a while, and somewhere else, away from
|
||||
where it could not get) */
|
||||
if (game_time_get() - bot->stuck_count_time > 150 || game_time_get() < bot->stuck_count_time)
|
||||
{
|
||||
bot->stuck_count = 0;
|
||||
bot->stuck_count_time = game_time_get();
|
||||
}
|
||||
if (++bot->stuck_count >= 3)
|
||||
{
|
||||
real away = (real)atan2(position.y - bot->walk_point.y, position.x - bot->walk_point.x) +
|
||||
(bot_random() - 0.5f) * 1.5f;
|
||||
|
||||
bot->stuck_count = 0;
|
||||
if (bot->item_index != NONE)
|
||||
{
|
||||
bot_ignore_item(bot, bot->item_index);
|
||||
bot->item_index = NONE;
|
||||
}
|
||||
if (bot->holding)
|
||||
bot->path_failures = 3;
|
||||
else if (!bot->has_objective)
|
||||
{
|
||||
bot->goal = position;
|
||||
bot->goal.x += (real)cos(away) * 10.0f;
|
||||
bot->goal.y += (real)sin(away) * 10.0f;
|
||||
bot->has_goal = TRUE;
|
||||
bot->goal_time = game_time_get();
|
||||
bot->follow_again_time = game_time_get() + 150;
|
||||
}
|
||||
bots.gave_up++;
|
||||
}
|
||||
|
||||
bot->path_time = game_time_get() - PATH_REFRESH_TICKS;
|
||||
bot->walk_distance = 1000000.0f;
|
||||
@@ -2707,6 +2896,10 @@ static void bot_think(
|
||||
SET_FLAG(action->control_flags, _unit_control_jump_bit, TRUE);
|
||||
if (bot->stuck_ticks == 24)
|
||||
bots.long_stucks++;
|
||||
/* (a spot to hold it cannot get to: the place's centre instead,
|
||||
bot_hold_spot) */
|
||||
if (bot->stuck_ticks == 18 && bot->holding)
|
||||
bot->path_failures = 3;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -2735,7 +2928,13 @@ static struct bot *bot_get(
|
||||
free_bot->player_index = player_index;
|
||||
free_bot->target_unit_index = NONE;
|
||||
free_bot->item_index = NONE;
|
||||
free_bot->ignored_item_index = NONE;
|
||||
free_bot->item_progress_index = NONE;
|
||||
{
|
||||
short ignored;
|
||||
|
||||
for (ignored = 0; ignored < BOT_IGNORED_ITEMS; ignored++)
|
||||
free_bot->ignored_items[ignored] = NONE;
|
||||
}
|
||||
free_bot->vehicle_index = NONE;
|
||||
free_bot->ignored_vehicle_index = NONE;
|
||||
free_bot->step_radius = BOT_STEP_RADIUS;
|
||||
@@ -2823,12 +3022,12 @@ void bots_tick(
|
||||
}
|
||||
}
|
||||
bots.logged_time = game_time_get();
|
||||
platform_log("bots: driving %d; paths found %ld, not %ld; weapon swaps asked %ld; grenades %ld; changes %ld; stuck %ld, long %ld (no start %ld, no goal %ld, no way %ld); vehicles: in %ld, out %ld",
|
||||
(int)count, bots.paths_found, bots.paths_failed, bots.pickups, bots.grenades, bots.switches, bots.stucks, bots.long_stucks, bots.fail_start, bots.fail_goal, bots.fail_search,
|
||||
platform_log("bots: driving %d; paths found %ld, not %ld; weapon swaps asked %ld; grenades %ld; changes %ld; stuck %ld, long %ld, gave up %ld (no start %ld, no goal %ld, no way %ld); vehicles: in %ld, out %ld",
|
||||
(int)count, bots.paths_found, bots.paths_failed, bots.pickups, bots.grenades, bots.switches, bots.stucks, bots.long_stucks, bots.gave_up, bots.fail_start, bots.fail_goal, bots.fail_search,
|
||||
bots.vehicle_entries, bots.vehicle_exits);
|
||||
bots.vehicle_entries = bots.vehicle_exits = 0;
|
||||
bots.fail_start = bots.fail_goal = bots.fail_search = 0;
|
||||
bots.paths_found = bots.paths_failed = bots.pickups = bots.grenades = bots.switches = bots.stucks = bots.long_stucks = 0;
|
||||
bots.paths_found = bots.paths_failed = bots.pickups = bots.grenades = bots.switches = bots.stucks = bots.long_stucks = bots.gave_up = 0;
|
||||
for (index = 0; index < MAXIMUM_TRACKED_PLAYERS; index++)
|
||||
{
|
||||
struct player_datum *player = distributed_player(index);
|
||||
|
||||
@@ -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 */
|
||||
@@ -247,6 +250,18 @@ void halo_vr_network_grenade_thrown(short local_player_index, long unit_index, c
|
||||
/* the platform layer's side (vr.c) */
|
||||
void halo_vr_initialize(void);
|
||||
void halo_vr_frame_begin(void);
|
||||
/* vr.log_performance: the end of a part of the frame, for the report: the
|
||||
input and network, the game's ticks (how many), the camera and director,
|
||||
the drawing; the rest is presenting */
|
||||
enum
|
||||
{
|
||||
HALO_VR_PHASE_INPUT,
|
||||
HALO_VR_PHASE_TICKS,
|
||||
HALO_VR_PHASE_CAMERA,
|
||||
HALO_VR_PHASE_DRAW,
|
||||
HALO_VR_PHASE_COUNT
|
||||
};
|
||||
void halo_vr_phase(int phase, int ticks);
|
||||
void halo_vr_present(unsigned int back_buffer_texture);
|
||||
/* the eye image size: the back buffer's size in pixels in VR */
|
||||
int halo_vr_eye_size(int *width, int *height);
|
||||
|
||||
+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,6 +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"
|
||||
|
||||
+166
-15
@@ -69,6 +69,14 @@ static struct
|
||||
unsigned long long wait_us;
|
||||
int report_frames;
|
||||
int report_stereo_frames;
|
||||
/* (the frame's parts, halo_vr_phase: when each ended, and the game's
|
||||
ticks in it; summed apart for frames with ticks and without, with how
|
||||
many took longer than the headset's frame) */
|
||||
unsigned long long phase_end_us[HALO_VR_PHASE_COUNT];
|
||||
int frame_ticks;
|
||||
unsigned long long phase_us[2][HALO_VR_PHASE_COUNT + 1];
|
||||
int phase_frames[2];
|
||||
int phase_late[2];
|
||||
|
||||
/* the view's turning, gathered for halo_vr_look_delta */
|
||||
float look_yaw;
|
||||
@@ -228,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
|
||||
@@ -495,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)
|
||||
@@ -530,10 +559,47 @@ static unsigned long long now_us(void)
|
||||
/* vr.log_performance: every five seconds, the frames the game gave the
|
||||
headset, and where their time went: waiting for the headset (xrWaitFrame),
|
||||
drawing the eyes, and the rest (the game's update, the overlay, the copies) */
|
||||
void halo_vr_phase(int phase, int ticks)
|
||||
{
|
||||
if (!vr.active || !vr.log_performance || phase < 0 || phase >= HALO_VR_PHASE_COUNT)
|
||||
return;
|
||||
vr.phase_end_us[phase] = now_us();
|
||||
if (phase == HALO_VR_PHASE_TICKS)
|
||||
vr.frame_ticks = ticks;
|
||||
}
|
||||
|
||||
/* the frame's parts, from the marks halo_vr_phase left (a part not marked
|
||||
this frame: none) */
|
||||
static void performance_phases(unsigned long long now)
|
||||
{
|
||||
int kind = vr.frame_ticks > 0;
|
||||
unsigned long long last = vr.frame_start_us;
|
||||
int phase;
|
||||
|
||||
for (phase = 0; phase < HALO_VR_PHASE_COUNT; phase++)
|
||||
{
|
||||
unsigned long long end = vr.phase_end_us[phase];
|
||||
|
||||
if (end >= last && end <= now)
|
||||
{
|
||||
vr.phase_us[kind][phase] += end - last;
|
||||
last = end;
|
||||
}
|
||||
}
|
||||
vr.phase_us[kind][HALO_VR_PHASE_COUNT] += now - last;
|
||||
vr.phase_frames[kind]++;
|
||||
/* (late: longer than the headset's frame, less a millisecond) */
|
||||
if (vr.info.refresh_rate > 1.0f && (float)(now - vr.frame_start_us + 1000) > 1e6f / vr.info.refresh_rate)
|
||||
vr.phase_late[kind]++;
|
||||
memset(vr.phase_end_us, 0, sizeof(vr.phase_end_us));
|
||||
vr.frame_ticks = 0;
|
||||
}
|
||||
|
||||
static void performance_report(int stereo)
|
||||
{
|
||||
unsigned long long now = now_us();
|
||||
|
||||
performance_phases(now);
|
||||
vr.work_us += now - vr.frame_start_us;
|
||||
vr.report_frames++;
|
||||
vr.report_stereo_frames += stereo != 0;
|
||||
@@ -548,6 +614,25 @@ static void performance_report(int stereo)
|
||||
platform_log("VR: %.1f fps (%d%% stereo); per frame %.1f ms working (eyes %.1f ms), %.1f ms waiting "
|
||||
"for the headset", frames / seconds, (int)(100.0f * vr.report_stereo_frames / frames),
|
||||
vr.work_us * 1e-3f / frames, vr.eyes_us * 1e-3f / frames, vr.wait_us * 1e-3f / frames);
|
||||
{
|
||||
int kind;
|
||||
|
||||
for (kind = 1; kind >= 0; kind--)
|
||||
{
|
||||
float count = (float)(vr.phase_frames[kind] ? vr.phase_frames[kind] : 1);
|
||||
|
||||
platform_log("VR: %s: %d frames, %d late; ms input %.1f, ticks %.1f, camera %.1f, draw %.1f, "
|
||||
"present %.1f", kind ? "with ticks" : "no ticks", vr.phase_frames[kind], vr.phase_late[kind],
|
||||
vr.phase_us[kind][HALO_VR_PHASE_INPUT] * 1e-3f / count,
|
||||
vr.phase_us[kind][HALO_VR_PHASE_TICKS] * 1e-3f / count,
|
||||
vr.phase_us[kind][HALO_VR_PHASE_CAMERA] * 1e-3f / count,
|
||||
vr.phase_us[kind][HALO_VR_PHASE_DRAW] * 1e-3f / count,
|
||||
vr.phase_us[kind][HALO_VR_PHASE_COUNT] * 1e-3f / count);
|
||||
}
|
||||
}
|
||||
memset(vr.phase_us, 0, sizeof(vr.phase_us));
|
||||
memset(vr.phase_frames, 0, sizeof(vr.phase_frames));
|
||||
memset(vr.phase_late, 0, sizeof(vr.phase_late));
|
||||
}
|
||||
vr.report_start_us = now;
|
||||
vr.report_frames = vr.report_stereo_frames = 0;
|
||||
@@ -1248,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;
|
||||
@@ -1281,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;
|
||||
@@ -1363,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();
|
||||
@@ -1372,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],
|
||||
@@ -1480,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)
|
||||
{
|
||||
@@ -1495,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;
|
||||
@@ -1507,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;
|
||||
@@ -2338,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);
|
||||
}
|
||||
@@ -2448,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)
|
||||
@@ -2466,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);
|
||||
}
|
||||
|
||||
@@ -2503,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;
|
||||
|
||||
@@ -3338,7 +3338,13 @@ void main_loop(
|
||||
network_game_abort();
|
||||
}
|
||||
|
||||
#ifdef HALO_ANDROID
|
||||
halo_vr_phase(HALO_VR_PHASE_INPUT, 0);
|
||||
#endif
|
||||
game_time_update((real)main_globals.halt_time_scale*main_globals.seconds_elapsed);
|
||||
#ifdef HALO_ANDROID
|
||||
halo_vr_phase(HALO_VR_PHASE_TICKS, game_time_get_elapsed());
|
||||
#endif
|
||||
|
||||
render_frame = main_globals.main_menu_scenario_loaded ||
|
||||
(main_globals.halt_time_scale &&
|
||||
@@ -3353,6 +3359,9 @@ void main_loop(
|
||||
observer_update((real)main_globals.halt_time_scale*main_globals.seconds_elapsed);
|
||||
collision_log_end_period();
|
||||
game_engine_update_non_deterministic((real)main_globals.halt_time_scale*main_globals.seconds_elapsed);
|
||||
#ifdef HALO_ANDROID
|
||||
halo_vr_phase(HALO_VR_PHASE_CAMERA, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (main_globals.saving_map)
|
||||
@@ -3367,6 +3376,9 @@ void main_loop(
|
||||
main_game_render((double)main_globals.seconds_elapsed);
|
||||
render_interpolation_frame_end();
|
||||
profile_render_end();
|
||||
#ifdef HALO_ANDROID
|
||||
halo_vr_phase(HALO_VR_PHASE_DRAW, 0);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -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