Compare commits

...
Author SHA1 Message Date
Stefan MaierandClaude Opus 5.5 3aad697ebe VR multiview: a driver self-test, views turned apart drawn one by one, driver variables
On the Steam Frame the eyes drawn at once did not fuse (each alone sharp)
while drawn one by one they did; the corrections agree with the projection
and fuse on the Quest, so the Frame's GL driver (Mesa zink on Turnip) is
the suspect: a right eye given the left one's correction shows there, where
the eyes' views are wider and unlike each other.
- d3d8_gl.c: when the layers are first made, a point drawn into two 2x1
  layers by rows picked with gl_ViewID_OVR, as the game's vertex programs
  pick them, and read back; failed, the eyes are drawn one by one
  (logcat "multiview: self-test passed/FAILED")
- vr.c: a frame whose eyes' views are turned apart (more than 0.05
  degrees, which one camera cannot serve) draws the eyes one by one; the
  views' angle and each eye's edges go to the log as they change
- debug.gl_environment: "NAME=value ..." set before the driver starts
  (TU_DEBUG=nomultipos, ZINK_DEBUG=nobgc, MESA_SHADER_CACHE_DISABLE=true)
SteamVR still draws the eyes one by one unless debug.multiview_steamvr.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:09:42 +02:00
Stefan MaierandClaude Opus 5.5 c04ce7fd00 Steam Frame: each eye drawn apart (no multiview on SteamVR)
With both eyes drawn at once (multiview) the eyes' pictures did not fuse
on the Steam Frame, now and then: each eye alone sharp, both together
doubled as the head turned, in the menus too. With vr.multiview = false
the user saw no doubling, at the same frame time (the Frame's time goes
to the GL driver either way). debug.multiview_steamvr brings multiview
back there for finding the cause; the Quest keeps it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:54:53 +02:00
Stefan MaierandClaude Opus 5.5 3f06b1b9d8 Revert "Steam Frame: the layers' images acquired before the frame waits"
This reverts commit 24e583c0. With the images acquired before xrWaitFrame
the frames came fast (about 8 ms of work) but the picture doubled again
on the Steam Frame as the head turned (181 of 3753 frames reprojected);
without it (673b32b1) it did not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:40:35 +02:00
Stefan MaierandClaude Opus 5.5 1b2d058a04 VR multiview: lens flares placed by their depth in each eye
A lens flare is projected on the CPU (w 1, its depth in z) and drawn
without the depth test, which multiview took for something at infinity,
as the sun's flares are. A weapon's own lights have flares too, at its
markers half a metre from the eyes (the plasma pistol's at its prongs,
the plasma rifle's at its muzzle): each eye saw its flare where infinity
puts it, a green ball at the barrel the eyes could not fuse. While flares
are drawn (rasterizer_lens_flares_draw), each eye's is now placed by the
flare's depth, as its occlusion test already was; the sun's are far
enough for infinity either way. Whether a flare shows at all depends on
its occlusion test, which is why the balls came and went, and showed on
the Frame more than on the Quest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:34:56 +02:00
Stefan MaierandClaude Opus 5.5 b5fa53ed95 Steam Frame: the layers' images acquired before the frame waits
With the copies deferred the wait only moved: acquiring the images took
8-10 ms a frame, the left eye's 6-8 of it. SteamVR's acquire (or its wait
for the image) waits for the GL work queued before it, the eyes' whole
drawing when the left eye's copy asked, the HUD's for the overlay. The
images the last frame filled are now acquired before xrWaitFrame, when
little is queued and the frame would wait for the headset anyway; one a
frame needs that was not acquired ahead is acquired as before. The
performance log shows the time acquiring ahead apart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:34:56 +02:00
Stefan MaierandClaude Opus 5.5 1f2c10a3ca Menus' art: a texture outside the art's range is no menu bitmap
Every texture the renderer binds asks whether it is a menu bitmap
standing for its PNG (menu_art_texture), in a game too, where the PC
menus' bitmaps are loaded for the pause menu's settings: a walk over the
150-odd registered bitmaps for each texture of each draw. Their textures
lie together, so one lowest/highest check answers for the map's own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:11:05 +02:00
Stefan MaierandClaude Opus 5.5 12f1155752 Steam Frame: the headset's layer copies without waits in the frame
The Frame's game thread spent most of each 13.9 ms frame waiting, not
working: the left eye's copy into its swapchain took 4.5-7 ms, the right
one's right after it 0.7, each later copy 0.7-3. Every copy read the GL
state back to restore it (glGet*, glIsEnabled, the source's swizzle) and
released its image at once; with Mesa's GL thread (zink) each read waits
for the thread to run everything queued, each release for the GPU, so the
thread stood still for the eyes' whole drawing mid-frame, and frames came
late enough for SteamVR to reproject 2-8% of them (the doubled picture
when turning the head).

Deferred copies (SteamVR by default; debug.xr_deferred_copies 0 or 1 says
otherwise; the Quest's path is unchanged):
- nothing read back: a copy leaves GL as it drew and returns 2; vr.c has
  the renderer set its state again (d3d8_host_state_changed: the state
  cache invalidated, its vertex arrays bound again)
- no swizzle round trip: copies read render targets, which have none
- the images kept until the frame ends and released together before
  xrEndFrame; the eyes' work flushed to the GPU once they are copied
- the performance log times acquiring and releasing apart

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:11:05 +02:00
Stefan MaierandClaude Opus 5.5 24e4123882 VR performance diagnostics: the frame's parts, the layer copies, Mesa's GL thread switch
- vr.log_performance: frames with game ticks and without, how many late,
  and their time by part (input, ticks, camera, draw, present)
- with XR_META_performance_metrics: per frame, each layer copy's time, the
  waits for swapchain images and xrEndFrame
- debug.gl_thread: Mesa's GL thread on or off (mesa_glthread) for trying

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 21:33:00 +02:00
Stefan MaierandClaude Opus 5.5 8466846d45 Steam Frame: the frames' thread kept on the fast cores
Without XR_EXT_performance_settings (SteamVR in Lepton) the scheduler may
put the game's busy thread on the 2.3 GHz cores; frames then come late to
the compositor and the picture doubles as the head turns, now and then,
at a steady 72 fps. On SteamVR the thread that ends the frames is kept on
the cores no slower than 90% of the fastest (HALO_XR_ANY_CORE: not); the
metrics line says which core it is on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 19:53:47 +02:00
Stefan MaierandClaude Opus 5.5 bc580cf1cf VR: the compositor's performance counters in the log (XR_META_performance_metrics)
Every five seconds, where the runtime offers them (SteamVR on the Steam
Frame): to tell frames the compositor showed late or reprojected from the
game's own pace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 19:46:54 +02:00
Stefan MaierandClaude Opus 5.5 3d575f32ed Bots on foot: ramps, rails, door frames and things out of reach
- a way is clear only at the knees and the chest (a rail with room under
  it, a slit in a wall), and for a shortcut along a path at the shoulders
  too (a door frame)
- stuck below a near point (a step, a ramp's lip): a jump on towards it
- a thing to pick up it comes no nearer to for 2.5 s is given up
- stuck three times in five seconds: the goal given up, somewhere away
- at the point it walks to: arrived, not stuck

Emulator, Blood Gulch, 15 bots: CTF stuck events 264 -> 153, mid-field
26% -> 32%, kills 26 -> 33; team slayer stuck events 860 -> 560.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 19:33:16 +02:00
Stefan MaierandClaude Opus 5.5 727b9ca80d Bots on foot: no crowding at things out of reach, spread over the enemies, hold spots they can reach
- a thing to pick up that it stands by and is not offered (on a ledge,
  out of reach) is given up for 20 s; up to four remembered, in the
  fight too (the crowds jumping at the base's weapons)
- stuck: the side step goes to the open side
- goals: each bot one of the three nearest enemies, and beside them while
  far, so a team spreads out
- holding a spot: stops near it, and one it cannot reach falls back to
  the place's centre

Emulator, Blood Gulch, 15 bots: CTF stuck events 1793 -> 264; team slayer
stuck windows 32% -> 9-15%, time by the bases 85% -> 68-73%.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 18:52:30 +02:00
12 changed files with 949 additions and 67 deletions

No files matched your search

+26
View File
@@ -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.
+3 -1
View File
@@ -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 */
+57
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+15
View File
@@ -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
View File
@@ -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)
{
+12
View File
@@ -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)
+15
View File
@@ -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
View File
@@ -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;
+12
View File
@@ -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
+5
View File
@@ -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)
{