Merge feature/multiview: both eyes in one draw

GL_OVR_multiview2 (vr.multiview, on by default): the scene drawn once from
between the eyes into a two-layer target, each view's positions moved to
its eye in the vertex programs; the sky as though infinitely far, what the
game projects itself by its depth, screen reads from a composite of both
eyes. At one spot in 'Halo': both eyes 6.3 ms -> 4.0 ms, the frame's work
7.9 -> 5.8 ms. Also: no VR facing during cinematics, hands held still under
the system menu. Merged with reservations; one-time diagnostics log what
may still be off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Stefan MaierandClaude Opus 5.5 committed 2026-09-30 20:50:35 +02:00
commit 4e6b3f69f6
12 files changed
+673 -17

No files matched your search

+3
View File
@@ -124,6 +124,9 @@ int host_xr_copy_layer_region(int layer, unsigned int texture, int clear_alpha,
/* submits the frame begun by host_xr_begin_frame */
int host_xr_end_frame(const struct halo_xr_submit *submit);
void host_xr_haptic(int hand, float amplitude, float seconds);
/* copies a layer of a texture array (the eyes drawn at once, multiview)
into a layer's image, opaque */
int host_xr_copy_layer_array(int layer, unsigned int texture, int array_layer);
/* darkens the eye images from their edges, strength 0..1 (0: off) */
void host_xr_set_vignette(float strength);
+112 -1
View File
@@ -124,6 +124,13 @@ static struct
GLint vignette_uniform;
GLint tangents_uniform;
float vignette;
/* the same copy from a layer of a texture array (the eyes drawn at
once, multiview): its program and uniforms, and the layer for the copy
under way (host_xr_copy_layer_array; -1: a plain texture) */
GLuint array_program;
GLint array_source_uniform, array_rect_uniform, array_layer_uniform;
GLint array_vignette_uniform, array_tangents_uniform;
int copy_array_layer;
GLuint vertex_array;
GLuint sampler;
} xr;
@@ -248,6 +255,33 @@ static const char copy_fragment_shader[] =
" }\n"
"}\n";
static const char copy_array_fragment_shader[] =
"#version 300 es\n"
"precision highp float;\n"
"uniform highp sampler2DArray source;\n"
"uniform float layer;\n"
"uniform float vignette;\n"
"uniform vec4 tangents;\n"
"in vec2 coordinate;\n"
"in vec2 disc;\n"
"out vec4 color;\n"
"vec3 to_linear(vec3 c)\n"
"{\n"
" return mix(c / 12.92, pow((c + 0.055) / 1.055, vec3(2.4)), step(0.04045, c));\n"
"}\n"
"void main()\n"
"{\n"
" vec4 texel = texture(source, vec3(coordinate, layer));\n"
" color = vec4(to_linear(texel.rgb), texel.a);\n"
" if (vignette > 0.0)\n"
" {\n"
" vec2 corner = disc * 0.5 + 0.5;\n"
" vec2 away = vec2(mix(tangents.x, tangents.y, corner.x), mix(tangents.z, tangents.w, corner.y));\n"
" float edge = mix(1.8, 0.45, vignette);\n"
" color.rgb *= 1.0 - smoothstep(edge - 0.3, edge, length(away));\n"
" }\n"
"}\n";
static GLuint compile_shader(GLenum type, const char *text)
{
GLuint shader = glCreateShader(type);
@@ -293,6 +327,38 @@ static int copy_initialize(void)
xr.cut_disc_uniform = glGetUniformLocation(xr.program, "cut_disc");
xr.vignette_uniform = glGetUniformLocation(xr.program, "vignette");
xr.tangents_uniform = glGetUniformLocation(xr.program, "tangents");
xr.copy_array_layer = -1;
{
GLuint array_vertex = compile_shader(GL_VERTEX_SHADER, copy_vertex_shader);
GLuint array_fragment = compile_shader(GL_FRAGMENT_SHADER, copy_array_fragment_shader);
if (array_vertex && array_fragment)
{
xr.array_program = glCreateProgram();
glAttachShader(xr.array_program, array_vertex);
glAttachShader(xr.array_program, array_fragment);
glLinkProgram(xr.array_program);
glGetProgramiv(xr.array_program, GL_LINK_STATUS, &status);
if (!status)
{
host_logf(HOST_LOG_ERROR, "xr: array copy program does not link");
glDeleteProgram(xr.array_program);
xr.array_program = 0;
}
else
{
xr.array_source_uniform = glGetUniformLocation(xr.array_program, "source");
xr.array_rect_uniform = glGetUniformLocation(xr.array_program, "rect");
xr.array_layer_uniform = glGetUniformLocation(xr.array_program, "layer");
xr.array_vignette_uniform = glGetUniformLocation(xr.array_program, "vignette");
xr.array_tangents_uniform = glGetUniformLocation(xr.array_program, "tangents");
}
}
if (array_vertex)
glDeleteShader(array_vertex);
if (array_fragment)
glDeleteShader(array_fragment);
}
glGenVertexArrays(1, &xr.vertex_array);
glGenSamplers(1, &xr.sampler);
glSamplerParameteri(xr.sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
@@ -1150,6 +1216,20 @@ int host_xr_copy_layer(int layer, uint32_t texture, int clear_alpha)
return host_xr_copy_layer_region(layer, texture, clear_alpha, 0.0f, 0.0f, 1.0f, 1.0f);
}
/* copies a layer of a texture array (the eyes drawn at once) into a
layer's image, opaque */
int host_xr_copy_layer_array(int layer, uint32_t texture, int array_layer)
{
int copied;
if (!xr.array_program)
return 0;
xr.copy_array_layer = array_layer;
copied = host_xr_copy_layer_region(layer, texture, HALO_XR_COPY_OPAQUE, 0.0f, 0.0f, 1.0f, 1.0f);
xr.copy_array_layer = -1;
return copied;
}
/* copies part of a render target (left, top, right, bottom, 0..1 from its
top left) into a layer's image */
int host_xr_copy_layer_region(int layer, uint32_t texture, int clear_alpha, float left, float top, float right,
@@ -1184,7 +1264,38 @@ int host_xr_copy_layer_region(int layer, uint32_t texture, int clear_alpha, floa
glDisable(GL_CULL_FACE);
glDisable(GL_STENCIL_TEST);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
if (texture)
if (texture && xr.copy_array_layer >= 0)
{
/* a layer of the eyes' texture array (multiview) */
glUseProgram(xr.array_program);
glUniform1i(xr.array_source_uniform, 0);
glUniform4f(xr.array_rect_uniform, left, top, right, bottom);
glUniform1f(xr.array_layer_uniform, (float)xr.copy_array_layer);
if ((layer == HALO_XR_LAYER_EYE_LEFT || layer == HALO_XR_LAYER_EYE_RIGHT) && xr.vignette > 0.001f)
{
const XrFovf *fov = &xr.views[layer - HALO_XR_LAYER_EYE_LEFT].fov;
glUniform1f(xr.array_vignette_uniform, xr.vignette);
glUniform4f(xr.array_tangents_uniform, tanf(fov->angleLeft), tanf(fov->angleRight),
tanf(fov->angleDown), tanf(fov->angleUp));
}
else
{
glUniform1f(xr.array_vignette_uniform, 0.0f);
}
glBindVertexArray(xr.vertex_array);
glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
glBindSampler(0, xr.sampler);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
if (clear_alpha == HALO_XR_COPY_OPAQUE)
{
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
}
}
else if (texture)
{
GLint swizzle_r, swizzle_b;
+1
View File
@@ -57,6 +57,7 @@ host_xr_copy_layer_region
host_xr_end_frame
host_xr_haptic
host_xr_set_vignette
host_xr_copy_layer_array
host_android_path
host_errno
+14
View File
@@ -50,6 +50,20 @@ void halo_vr_eye_camera(int eye, const float game_position[3],
/* copies the back buffer, just drawn for an eye, to that eye's image */
void halo_vr_capture_eye(int eye);
/* Both eyes in one draw (vr.multiview, where GL_OVR_multiview2 is): whether
this frame draws so; the camera between the eyes whose view takes in both
(as halo_vr_eye_camera), which also starts drawing into both eyes' layers;
and after the draw, the layers taken for the eyes' images. */
int halo_vr_multiview(void);
void halo_vr_center_camera(const float game_position[3], float position[3], float forward[3], float up[3],
float *vertical_field_of_view, float frustum_bounds[4]);
void halo_vr_capture_multiview(void);
/* the sky, drawn about the camera, is being drawn: infinitely far */
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);
/* clears the back buffer to transparent black for the overlay, after the
eyes; menus_active chooses where the overlay is shown */
void halo_vr_begin_overlay(int menus_active);
+244 -10
View File
@@ -175,8 +175,12 @@ struct vertex_shader_object
struct vertex_element elements[XGPU_VERTEX_ATTRIBUTE_COUNT];
unsigned long element_count;
unsigned long packed_mask;
/* [0] streams per the declaration, [1] immediate mode (all floats) */
GLuint shader[2];
/* [0] streams per the declaration, [1] immediate mode (all floats);
[2] and [3] the same for both eyes at once (multiview) */
GLuint shader[4];
/* its position is a projected one (nv2a_vsh.c's clip capture): known,
and so; and whether a multiview draw with it was looked at (diagnostics) */
BOOL projection_known, projected, multiview_checked;
};
/* ---------- programs */
@@ -224,6 +228,13 @@ struct program_entry
GLint bump_matrix, bump_luminance, texture_scale;
GLint texture_lod_bias;
GLint screen_offset;
/* multiview: each view's correction (nv2a_vsh.c), and multiview.serial
when it was last uploaded; the projection's depth, and whether the draw
tests depth (what the uniform holds) */
GLint mv_rows;
unsigned long mv_serial;
GLint mv_depth;
float mv_depth_value[4];
/* the vertex constants c[0..constant_count) the program uses; with
consecutive locations, a changed range is uploaded by itself */
@@ -370,6 +381,8 @@ struct gl_device
{
unsigned short slot;
unsigned short counter;
/* drawn into both eyes at once: counted twice */
unsigned short views;
} frame_tests[STREAM_BUFFER_RING][VISIBILITY_FRAME_TESTS];
unsigned long frame_test_count[STREAM_BUFFER_RING];
GLuint visibility_latest[VISIBILITY_TEST_SLOTS];
@@ -855,6 +868,162 @@ static GLuint framebuffer_get(GLuint color, GLuint depth)
return entry->framebuffer;
}
#ifdef HALO_ANDROID
/* ---------- multiview (VR: both eyes in one draw, GL_OVR_multiview2)
Between d3d8_multiview_begin and _end, what the game draws into the back
buffer goes into two layers at once, one per eye: every vertex program is
compiled for two views (nv2a_vsh.c) and moves each view's positions to its
eye (rows, from vr.c). The game draws the scene once, from between the eyes.
A texture read of the back buffer meanwhile (a screen effect: the sun's
glow measures the picture about the sun) gets the eyes' pictures, each laid
where it lies in the shared camera's (resolve_rect), the left one over the
right. */
static struct
{
BOOL active;
/* the back buffer's target, which the layers stand in for */
struct render_target_entry *back_buffer;
/* drawn since the back buffer last got the left eye's picture */
BOOL drawn;
GLuint color, depth, framebuffer;
unsigned long width, height;
float rows[4][4];
unsigned long serial;
/* where each eye's picture lies in the shared camera's: left, top,
right, bottom, 0..1 from its top left */
float resolve_rect[2][4];
/* 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];
} multiview;
/* the bound targets are the layers */
static BOOL targets_multiview;
static struct vertex_shader_object *current_program(void);
static GLuint framebuffer_get(GLuint color, GLuint depth);
/* the eyes' pictures into the back buffer's own texture, for a draw that
reads it; the bindings it changes are put back */
static void multiview_resolve(void)
{
static GLuint read_framebuffer;
GLboolean scissor = glIsEnabled(GL_SCISSOR_TEST);
int eye;
if (!multiview.drawn)
return;
multiview.drawn = FALSE;
if (!read_framebuffer)
glGenFramebuffers(1, &read_framebuffer);
glBindFramebuffer(GL_READ_FRAMEBUFFER, read_framebuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer_get(multiview.back_buffer->target.texture, 0));
if (scissor)
glDisable(GL_SCISSOR_TEST);
for (eye = 1; eye >= 0; eye--)
{
const float *rect = multiview.resolve_rect[eye];
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, multiview.color, 0, eye);
/* (rows from the top in both: GL's y 0 is the picture's top) */
glBlitFramebuffer(0, 0, (GLint)multiview.width, (GLint)multiview.height,
(GLint)(rect[0] * multiview.width + 0.5f), (GLint)(rect[1] * multiview.height + 0.5f),
(GLint)(rect[2] * multiview.width + 0.5f), (GLint)(rect[3] * multiview.height + 0.5f),
GL_COLOR_BUFFER_BIT, GL_LINEAR);
}
if (scissor)
glEnable(GL_SCISSOR_TEST);
glBindFramebuffer(GL_FRAMEBUFFER, gl_state.framebuffer != (GLuint)-1 ? gl_state.framebuffer : 0);
}
int d3d8_multiview_supported(void)
{
return device.gl_ready && xgpu_capabilities.multiview;
}
/* 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])
{
struct render_target_entry *back_buffer;
if (!d3d8_multiview_supported() || !(back_buffer = render_target_get(&device.back_buffer)))
return FALSE;
if (!multiview.framebuffer || multiview.width != back_buffer->target.gl_width ||
multiview.height != back_buffer->target.gl_height)
{
static const GLenum draw_buffer = GL_COLOR_ATTACHMENT0;
GLenum status;
if (multiview.framebuffer)
{
glDeleteFramebuffers(1, &multiview.framebuffer);
glDeleteTextures(1, &multiview.color);
glDeleteTextures(1, &multiview.depth);
}
multiview.width = back_buffer->target.gl_width;
multiview.height = back_buffer->target.gl_height;
glGenTextures(1, &multiview.color);
glBindTexture(GL_TEXTURE_2D_ARRAY, multiview.color);
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, (GLsizei)multiview.width, (GLsizei)multiview.height, 2);
glGenTextures(1, &multiview.depth);
glBindTexture(GL_TEXTURE_2D_ARRAY, multiview.depth);
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_DEPTH24_STENCIL8, (GLsizei)multiview.width,
(GLsizei)multiview.height, 2);
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
glGenFramebuffers(1, &multiview.framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, multiview.framebuffer);
glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, multiview.color, 0, 0, 2);
glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, multiview.depth, 0, 0, 2);
glDrawBuffers(1, &draw_buffer);
status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
xgpu_gl_state_invalidate();
platform_log("multiview: %lux%lu layers, framebuffer %s", multiview.width, multiview.height,
status == GL_FRAMEBUFFER_COMPLETE ? "complete" : "INCOMPLETE");
if (status != GL_FRAMEBUFFER_COMPLETE)
{
xgpu_capabilities.multiview = FALSE;
return FALSE;
}
}
memcpy(multiview.rows, rows, sizeof(multiview.rows));
memcpy(multiview.resolve_rect, resolve_rect, sizeof(multiview.resolve_rect));
multiview.serial++;
multiview.back_buffer = back_buffer;
multiview.drawn = FALSE;
multiview.active = TRUE;
return TRUE;
}
/* 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)
{
multiview.projection_depth[0] = a;
multiview.projection_depth[1] = b;
}
/* new rows for the draws that follow (the sky's, vr.c) */
void d3d8_multiview_rows(const float rows[4][4])
{
if (!multiview.active)
return;
memcpy(multiview.rows, rows, sizeof(multiview.rows));
multiview.serial++;
}
/* the eyes' layers (GL_TEXTURE_2D_ARRAY), after d3d8_multiview_end */
unsigned int d3d8_multiview_end(void)
{
multiview.active = FALSE;
targets_multiview = FALSE;
return multiview.color;
}
#endif
/* the pixels per unit of the bound targets (render_target_get) */
static float target_scale[2] = { 1.0f, 1.0f };
@@ -880,6 +1049,18 @@ static BOOL bind_targets(BOOL *has_depth)
/* viewports and clears are in the targets' units (render_target_get) */
target_scale[0] = color ? color->target.scale[0] : depth->target.scale[0];
target_scale[1] = color ? color->target.scale[1] : depth->target.scale[1];
#ifdef HALO_ANDROID
/* VR: the back buffer, while both eyes are drawn at once, is their
layers (d3d8_multiview_begin), with a depth buffer of their own */
targets_multiview = multiview.active && color == multiview.back_buffer;
if (targets_multiview)
{
multiview.drawn = TRUE;
state_framebuffer(multiview.framebuffer);
*has_depth = depth != NULL;
return TRUE;
}
#endif
state_framebuffer(framebuffer_get(color ? color->target.texture : 0, depth ? depth->target.texture : 0));
*has_depth = depth != NULL;
return TRUE;
@@ -917,9 +1098,11 @@ static void gl_initialize(void)
(host_gl_has_extension("GL_EXT_texture_compression_dxt1") &&
host_gl_has_extension("GL_ANGLE_texture_compression_dxt3") &&
host_gl_has_extension("GL_ANGLE_texture_compression_dxt5"));
platform_log("OpenGL ES %d.%d: copy image %d, border clamp %d, anisotropy %d, S3TC %d, sample counting %d",
(int)major, (int)minor, xgpu_capabilities.copy_image, xgpu_capabilities.border_clamp,
xgpu_capabilities.anisotropy, xgpu_capabilities.s3tc, xgpu_capabilities.atomic_counters);
xgpu_capabilities.multiview = host_gl_has_extension("GL_OVR_multiview2");
platform_log("OpenGL ES %d.%d: copy image %d, border clamp %d, anisotropy %d, S3TC %d, sample counting %d, "
"multiview %d", (int)major, (int)minor, xgpu_capabilities.copy_image, xgpu_capabilities.border_clamp,
xgpu_capabilities.anisotropy, xgpu_capabilities.s3tc, xgpu_capabilities.atomic_counters,
xgpu_capabilities.multiview);
}
#else
if (config_boolean("debug.gl_debug"))
@@ -1414,6 +1597,7 @@ HRESULT WINAPI D3DDevice_EndVisibilityTest(DWORD index)
{
device.frame_tests[device.buffer_ring][*count].slot = (unsigned short)index;
device.frame_tests[device.buffer_ring][*count].counter = (unsigned short)device.counter_active;
device.frame_tests[device.buffer_ring][*count].views = (unsigned short)(multiview.active ? 2 : 1);
(*count)++;
}
return S_OK;
@@ -1473,7 +1657,7 @@ static void visibility_results_update(void)
{
const struct visibility_frame_test *entry = &device.frame_tests[ring][test];
device.visibility_latest[entry->slot] = counts[entry->counter];
device.visibility_latest[entry->slot] = counts[entry->counter] / (entry->views ? entry->views : 1);
}
}
device.frame_test_count[ring] = 0;
@@ -1855,16 +2039,21 @@ static unsigned long hash_words(const void *data, unsigned long size)
return hash;
}
static GLuint vertex_shader_get(struct vertex_shader_object *program, BOOL immediate)
static GLuint vertex_shader_get(struct vertex_shader_object *program, BOOL immediate, BOOL multiview_view)
{
int variant = immediate ? 1 : 0;
int variant = (immediate ? 1 : 0) + (multiview_view ? 2 : 0);
if (!program->shader[variant])
{
char *source = nv2a_vertex_shader_to_glsl(program->instructions, program->instruction_count,
immediate ? 0 : device.vertex_shader->packed_mask);
immediate ? 0 : device.vertex_shader->packed_mask, multiview_view);
program->shader[variant] = compile_shader(GL_VERTEX_SHADER, source, "vertex");
if (!program->projection_known)
{
program->projected = strstr(source, "clip_captured = true") != NULL;
program->projection_known = TRUE;
}
if (debug_settings.dump_shaders)
{
char path[512];
@@ -2018,6 +2207,9 @@ static struct program_entry *program_get(GLuint vertex_shader, GLuint fragment_s
entry->texture_scale = glGetUniformLocation(entry->program, "texture_scale");
entry->texture_lod_bias = glGetUniformLocation(entry->program, "texture_lod_bias");
entry->screen_offset = glGetUniformLocation(entry->program, "screen_offset");
entry->mv_rows = glGetUniformLocation(entry->program, "mv_rows");
entry->mv_depth = glGetUniformLocation(entry->program, "mv_depth");
entry->mv_depth_value[0] = entry->mv_depth_value[1] = -1.0e30f;
for (stage = 0; stage < D3DTSS_MAXSTAGES; stage++)
{
char name[8];
@@ -2241,6 +2433,24 @@ static void bind_textures(struct nv2a_pixel_shader_key *key, float texture_scale
if (target)
{
#ifdef HALO_ANDROID
if (multiview.active && multiview.back_buffer && target == &multiview.back_buffer->target)
{
static unsigned long logged[16];
unsigned long id = current_program() ? current_program()->id : 0;
int slot, known = FALSE;
/* (diagnostics: each vertex program that reads the picture) */
for (slot = 0; slot < 16 && logged[slot]; slot++)
known |= logged[slot] == id + 1;
if (!known && slot < 16)
{
logged[slot] = id + 1;
platform_log("multiview: vertex program %lu reads the picture", id);
}
multiview_resolve();
}
#endif
xgpu_texture_describe(texture->Format, texture->Size, &description);
gl_texture = target->texture;
gl_target = GL_TEXTURE_2D;
@@ -2563,7 +2773,7 @@ static struct program_entry *prepare_draw(BOOL immediate)
key.count_samples = device.visibility_test_active && xgpu_capabilities.atomic_counters;
#endif
entry = program_get(vertex_shader_get(program, immediate), fragment_shader_get(&key));
entry = program_get(vertex_shader_get(program, immediate, targets_multiview), fragment_shader_get(&key));
if (!entry)
{
stats.skipped_link++;
@@ -2577,6 +2787,30 @@ static struct program_entry *prepare_draw(BOOL immediate)
stats.draws++;
state_program(entry->program);
#ifdef HALO_ANDROID
if (targets_multiview && entry->mv_rows >= 0 && entry->mv_serial != multiview.serial)
{
glUniform4fv(entry->mv_rows, 4, &multiview.rows[0][0]);
entry->mv_serial = multiview.serial;
}
if (targets_multiview && !program->multiview_checked)
{
/* (diagnostics: a vertex program whose position is not a projected
one, drawn depth tested into both eyes, is something the game
placed in screen space itself, which multiview puts at infinity) */
program->multiview_checked = TRUE;
if (!program->projected && has_depth && D3D__RenderState[D3DRS_ZENABLE])
platform_log("multiview: vertex program %lu draws in screen space, depth tested", program->id);
}
if (targets_multiview && entry->mv_depth >= 0)
{
float depth[4];
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[3] = 0.0f;
uniform_vec4(entry->mv_depth, entry->mv_depth_value, depth, 1);
}
if (key.count_samples)
glBindBufferRange(GL_ATOMIC_COUNTER_BUFFER, 0, device.visibility_counters,
(GLintptr)(device.counter_active * sizeof(GLuint)), sizeof(GLuint));
+8
View File
@@ -128,6 +128,10 @@ this list to generate the guest's entry points */
X(glGenQueries) \
X(glBeginQuery) \
X(glEndQuery) \
X(glTexStorage3D) \
X(glFramebufferTextureLayer) \
X(glFramebufferTextureMultiviewOVR) \
X(glIsEnabled) \
X(glGetQueryObjectuiv)
/* ANDROID_GL_FUNCTIONS_END */
#else
@@ -343,6 +347,10 @@ pointers, sees the declarations without these aliases */
#define glGenQueries halo_glGenQueries
#define glBeginQuery halo_glBeginQuery
#define glEndQuery halo_glEndQuery
#define glTexStorage3D halo_glTexStorage3D
#define glFramebufferTextureLayer halo_glFramebufferTextureLayer
#define glFramebufferTextureMultiviewOVR halo_glFramebufferTextureMultiviewOVR
#define glIsEnabled halo_glIsEnabled
#define glGetQueryObjectuiv halo_glGetQueryObjectuiv
#else
#define glGetString halo_glGetString
+41 -2
View File
@@ -195,13 +195,30 @@ static const char shader_prologue[] =
"}\n";
char *nv2a_vertex_shader_to_glsl(const DWORD *instructions, unsigned long instruction_count,
unsigned long packed_attribute_mask)
unsigned long packed_attribute_mask, int multiview)
{
struct xgpu_text text = { 0 };
unsigned long index;
#ifdef HALO_ANDROID
xgpu_text_append(&text, "#version %s\n", xgpu_capabilities.shading_language);
if (multiview)
{
/* VR: one draw into both eyes' layers. The scene is posed for a
camera between the eyes whose view covers both; each view moves the
position to its eye (half the eyes' distance aside) and onto its
own view's edges. As x' = a x + b w + c and y' likewise, with w the
depth along the view: mv_rows[2 * view] and [2 * view + 1];
what is drawn in screen space goes without c, as though far. */
xgpu_text_append(&text,
"#extension GL_OVR_multiview2 : require\n"
"layout(num_views = 2) in;\n"
"uniform vec4 mv_rows[4];\n"
/* the projection's depth: z = x + y / depth; z: depth tested */
"uniform vec4 mv_depth;\n");
}
#else
(void)multiview;
#endif
xgpu_text_append(&text, "%s", shader_prologue);
for (index = 0; index < XGPU_VERTEX_ATTRIBUTE_COUNT; index++)
@@ -360,6 +377,7 @@ char *nv2a_vertex_shader_to_glsl(const DWORD *instructions, unsigned long instru
GL: rows from the top, depth 0..1 */
"\tgl_Position.y = -gl_Position.y;\n"
"\tgl_Position.z = 2.0 * gl_Position.z - gl_Position.w;\n"
"%s"
#endif
"\tgl_PointSize = oPts.x;\n"
"\txD0 = clamp(oD0, 0.0, 1.0);\n"
@@ -373,7 +391,28 @@ char *nv2a_vertex_shader_to_glsl(const DWORD *instructions, unsigned long instru
"\txFog = oFog.x;\n"
"}\n"
#ifdef HALO_ANDROID
, XGPU_VERTEX_CONSTANT_BIAS - 38, XGPU_VERTEX_CONSTANT_BIAS - 37
, XGPU_VERTEX_CONSTANT_BIAS - 38, XGPU_VERTEX_CONSTANT_BIAS - 37,
/* (what is drawn in screen space, which kept no clip position, is
taken to each view as though infinitely far: a lens flare's sun
lands where the sky's does) */
/* (what the game projects itself, a sprite or a lens flare's
test, comes as a screen position with w 1 and its depth in z: its
eye's offset is by that depth, where it is depth tested) */
multiview ?
"\t{\n"
"\t\tint row = int(gl_ViewID_OVR) * 2;\n"
"\t\tfloat near = 0.0;\n"
"\t\tif (clip_captured && abs(clip_position.w - 1.0) > 1.0e-5)\n"
"\t\t\tnear = 1.0;\n"
"\t\telse if (clip_captured && mv_depth.z > 0.5)\n"
"\t\t{\n"
"\t\t\tfloat depth = mv_depth.y / (clip_position.z - mv_depth.x);\n"
"\t\t\tnear = depth > 0.0 ? 1.0 / depth : 0.0;\n"
"\t\t}\n"
"\t\tgl_Position.xy = vec2(dot(vec3(gl_Position.xw, near), mv_rows[row].xyz),\n"
"\t\t\tdot(vec3(gl_Position.yw, near), mv_rows[row + 1].xyz));\n"
"\t}\n" :
""
#endif
);
return text.buffer;
+3
View File
@@ -97,6 +97,9 @@ static const struct config_setting config_settings[] =
"away from the player's body (walls are not checked)." },
{ "vr.crouch_limit", _config_real, "0.9", "HALO_VR_CROUCH_LIMIT", _environment_value, _platform_android,
"How far, in metres, the view follows your head down." },
{ "vr.multiview", _config_boolean, "true", "HALO_VR_MULTIVIEW", _environment_value, _platform_android,
"Draw both eyes in one pass (GL_OVR_multiview2), from between them: about\n"
"half the drawing's cost on the processor. false: one eye after the other." },
{ "vr.log_performance", _config_boolean, "false", "HALO_VR_LOG_PERFORMANCE", _environment_value, _platform_android,
"Log the frame rate and where the frame time goes every five seconds\n"
"(adb logcat -s halo)." },
+202
View File
@@ -42,6 +42,10 @@ static struct
struct halo_xr_info info;
struct halo_xr_frame frame;
int frame_begun;
/* the hands as last seen with the app in focus (the system menu over
it reports none): held still meanwhile */
struct halo_xr_hand focused_hands[HALO_XR_HAND_COUNT];
int focused_hands_valid;
int eyes_captured; /* bit per eye, this frame */
int overlay_mode; /* HALO_XR_OVERLAY_*, this frame */
int overlay_blend;
@@ -91,6 +95,9 @@ static struct
float seat_frame_yaw;
int seat_frame_valid;
float seat_turn;
int multiview; /* vr.multiview: both eyes in one draw */
int multiview_logged;
int multiview_logged_views;
int vehicle_first_person;
int turret_handles; /* a gun's barrel rises as its handles go down */
float gunner_camera_back; /* metres */
@@ -201,6 +208,11 @@ static struct
/* d3d8_gl.c */
unsigned int d3d8_back_buffer_texture(void);
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);
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);
@@ -423,6 +435,7 @@ void halo_vr_initialize(void)
vr.two_hand_reach = (float)config_real("vr.two_hand_reach");
vr.melee_swing_speed = (float)config_real("vr.melee_swing_speed");
vr.grenade_throw_speed = (float)config_real("vr.grenade_throw_speed");
vr.multiview = config_boolean("vr.multiview");
vr.vehicle_first_person = config_boolean("vr.vehicle_first_person");
vr.vehicle_spawn = config_boolean("debug.vehicle_spawn");
vr.turret_handles = config_boolean("vr.turret_handles");
@@ -982,6 +995,25 @@ void halo_vr_frame_begin(void)
waited = now_us();
if (!host_xr_begin_frame(&vr.frame))
memset(&vr.frame, 0, sizeof(vr.frame));
if (vr.frame.focused)
{
memcpy(vr.focused_hands, vr.frame.hands, sizeof(vr.focused_hands));
vr.focused_hands_valid = 1;
}
else if (vr.focused_hands_valid && vr.frame.session_running)
{
/* the system menu is up: the hands stay where they were, their
buttons let go (the weapon does not fall back to the head) */
int hand;
memcpy(vr.frame.hands, vr.focused_hands, sizeof(vr.frame.hands));
for (hand = 0; hand < HALO_XR_HAND_COUNT; hand++)
{
vr.frame.hands[hand].trigger = vr.frame.hands[hand].squeeze = 0.0f;
vr.frame.hands[hand].thumbstick[0] = vr.frame.hands[hand].thumbstick[1] = 0.0f;
vr.frame.hands[hand].buttons = 0;
}
}
vr.frame_start_us = now_us();
vr.wait_us += vr.frame_start_us - waited;
vr.frame_begun = 1;
@@ -1125,6 +1157,164 @@ void halo_vr_eye_camera(int eye, const float game_position[3],
frustum_bounds[3] = view->tangent_up / half_tangent;
}
/* ---------- both eyes at once (vr.multiview)
The scene is drawn once, from a camera between the eyes whose view takes in
both eyes' (with a margin, for what is near: the eyes stand apart), into two
layers; each layer's positions are moved from that camera's picture to its
eye's (d3d8_multiview_begin, nv2a_vsh.c). With d the depth along the view,
a point's picture across, x, is (2 tx - (L + R)) / (R - L) times d for the
tangent tx = X / d of the view's edges L, R; up, y, likewise, but for rows
from the top. An eye that stands t aside sees X - t: x' = a x + b d + c. */
#define MULTIVIEW_MARGIN 0.1f
static float multiview_bounds[4]; /* the shared view's L, R, D, U */
static int multiview_drawing;
/* the rows for what is drawn in the world, and for the sky, which is drawn
about the camera, as though infinitely far: no eye stands aside of it */
static float multiview_rows[2][4][4];
int halo_vr_multiview(void)
{
int available = vr.active && vr.multiview && d3d8_multiview_supported();
if (vr.active && vr.multiview && !vr.multiview_logged)
{
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();
}
void halo_vr_center_camera(const float game_position[3], float position[3], float forward[3], float up[3],
float *vertical_field_of_view, float frustum_bounds[4])
{
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 const float local_right[3] = { 1.0f, 0.0f, 0.0f };
const struct halo_xr_view *views = vr.frame.views;
float centre[3], offset[3], direction[3], right_local[3], up_local[3], rows[4][4];
float left, right, down, top, half_tangent;
int axis, eye;
vr.eyes_start_us = now_us();
for (axis = 0; axis < 3; axis++)
centre[axis] = (views[0].pose.position[axis] + views[1].pose.position[axis]) * 0.5f;
tracked_offset(centre, offset);
memcpy(vr.game_camera, game_position, sizeof(vr.game_camera));
for (axis = 0; axis < 3; axis++)
position[axis] = game_position[axis] + offset[axis] / METRES_PER_WORLD_UNIT;
/* the eyes look alike (parallel views): the left one's way */
quaternion_rotate(views[0].pose.orientation, local_forward, direction);
local_to_game(direction, vr.body_yaw, forward);
quaternion_rotate(views[0].pose.orientation, local_up, direction);
local_to_game(direction, vr.body_yaw, up);
quaternion_rotate(views[0].pose.orientation, local_right, right_local);
quaternion_rotate(views[0].pose.orientation, local_up, up_local);
left = fminf(views[0].tangent_left, views[1].tangent_left) - MULTIVIEW_MARGIN;
right = fmaxf(views[0].tangent_right, views[1].tangent_right) + MULTIVIEW_MARGIN;
down = fminf(views[0].tangent_down, views[1].tangent_down) - MULTIVIEW_MARGIN;
top = fmaxf(views[0].tangent_up, views[1].tangent_up) + MULTIVIEW_MARGIN;
multiview_bounds[0] = left;
multiview_bounds[1] = right;
multiview_bounds[2] = down;
multiview_bounds[3] = top;
half_tangent = (top - down) * 0.5f;
*vertical_field_of_view = 2.0f * atanf(half_tangent);
frustum_bounds[0] = left / (half_tangent * (4.0f / 3.0f));
frustum_bounds[1] = right / (half_tangent * (4.0f / 3.0f));
frustum_bounds[2] = down / half_tangent;
frustum_bounds[3] = top / half_tangent;
for (eye = 0; eye < HALO_XR_EYE_COUNT; eye++)
{
const struct halo_xr_view *view = &views[eye];
float aside[3], tx, ty;
float eye_width = view->tangent_right - view->tangent_left;
float eye_height = view->tangent_up - view->tangent_down;
/* the eye's place from the shared camera, in its view's axes (world
units) */
for (axis = 0; axis < 3; axis++)
aside[axis] = view->pose.position[axis] - centre[axis];
tx = (aside[0] * right_local[0] + aside[1] * right_local[1] + aside[2] * right_local[2]) /
METRES_PER_WORLD_UNIT;
ty = (aside[0] * up_local[0] + aside[1] * up_local[1] + aside[2] * up_local[2]) / METRES_PER_WORLD_UNIT;
rows[eye * 2][0] = (right - left) / eye_width;
rows[eye * 2][1] = (left + right - view->tangent_left - view->tangent_right) / eye_width;
rows[eye * 2][2] = -2.0f * tx / eye_width;
rows[eye * 2][3] = 0.0f;
/* (rows from the top: y runs against the tangents) */
rows[eye * 2 + 1][0] = (top - down) / eye_height;
rows[eye * 2 + 1][1] = -(down + top - view->tangent_down - view->tangent_up) / eye_height;
rows[eye * 2 + 1][2] = 2.0f * ty / eye_height;
rows[eye * 2 + 1][3] = 0.0f;
}
memcpy(multiview_rows[0], rows, sizeof(rows));
memcpy(multiview_rows[1], rows, sizeof(rows));
for (eye = 0; eye < HALO_XR_EYE_COUNT; eye++)
multiview_rows[1][eye * 2][2] = multiview_rows[1][eye * 2 + 1][2] = 0.0f;
if (!vr.multiview_logged_views)
{
/* (the correction assumes parallel views: say if they are not) */
float other[3], apart[3];
quaternion_rotate(views[1].pose.orientation, local_forward, other);
quaternion_rotate(views[0].pose.orientation, local_forward, direction);
for (axis = 0; axis < 3; axis++)
apart[axis] = views[1].pose.position[axis] - views[0].pose.position[axis];
platform_log("VR multiview: views %.2f degrees apart, eyes %.1f mm apart; shared view %.2f..%.2f, %.2f..%.2f",
acosf(fminf(1.0f, direction[0] * other[0] + direction[1] * other[1] + direction[2] * other[2])) * 180.0f / PI,
sqrtf(apart[0] * apart[0] + apart[1] * apart[1] + apart[2] * apart[2]) * 1000.0f, left, right, down, top);
vr.multiview_logged_views = 1;
}
{
/* each eye's picture's place in the shared one (rows from the top) */
float resolve_rect[2][4];
for (eye = 0; eye < HALO_XR_EYE_COUNT; eye++)
{
resolve_rect[eye][0] = (views[eye].tangent_left - left) / (right - left);
resolve_rect[eye][1] = (top - views[eye].tangent_up) / (top - down);
resolve_rect[eye][2] = (views[eye].tangent_right - left) / (right - left);
resolve_rect[eye][3] = (top - views[eye].tangent_down) / (top - down);
}
multiview_drawing = d3d8_multiview_begin(rows, resolve_rect);
}
}
/* the shared camera's projection's depth (render.c: its [2][2] and [3][2]):
what the game projects itself carries z = -[2][2] + [3][2] / depth */
void halo_vr_multiview_projection(float projection_22, float projection_32)
{
d3d8_multiview_depth(-projection_22, projection_32);
}
/* the sky is being drawn (render.c): as though infinitely far */
void halo_vr_multiview_sky(int sky)
{
if (multiview_drawing)
d3d8_multiview_rows(multiview_rows[sky ? 1 : 0]);
}
/* after the shared draw: its layers become the eyes' images */
void halo_vr_capture_multiview(void)
{
unsigned int layers = d3d8_multiview_end();
if (!multiview_drawing)
return;
multiview_drawing = 0;
if (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))
vr.eyes_captured |= 2;
if (vr.eyes_start_us)
vr.eyes_us += now_us() - vr.eyes_start_us;
}
void halo_vr_capture_eye(int eye)
{
if (!vr.active || eye < 0 || eye >= HALO_XR_EYE_COUNT)
@@ -1872,6 +2062,18 @@ void halo_vr_eye_camera(int eye, const float game_position[3], float position[3]
(void)vertical_field_of_view; (void)frustum_bounds;
}
void halo_vr_capture_eye(int eye) { (void)eye; }
int halo_vr_multiview(void) { return 0; }
void halo_vr_center_camera(const float game_position[3], float position[3], float forward[3], float up[3],
float *vertical_field_of_view, float frustum_bounds[4])
{
(void)game_position; (void)position; (void)forward; (void)up; (void)vertical_field_of_view; (void)frustum_bounds;
}
void halo_vr_capture_multiview(void) {}
void halo_vr_multiview_sky(int sky) { (void)sky; }
void halo_vr_multiview_projection(float projection_22, float projection_32)
{
(void)projection_22; (void)projection_32;
}
void halo_vr_begin_overlay(int menus_active) { (void)menus_active; }
int halo_vr_aim(short local_player_index, float current_yaw, float current_pitch, float *yaw, float *pitch)
{
+5 -1
View File
@@ -27,6 +27,8 @@ struct xgpu_capabilities
BOOL atomic_counters;
/* "300 es" or "310 es" */
const char *shading_language;
/* GL_OVR_multiview2: both eyes in one draw (VR, d3d8_multiview_begin) */
BOOL multiview;
};
extern struct xgpu_capabilities xgpu_capabilities;
@@ -73,8 +75,10 @@ void xgpu_text_append(struct xgpu_text *text, const char *format, ...) __attribu
header). Attributes whose bit is set in packed_attribute_mask are fed as
NORMPACKED3 32-bit integers and unpacked in the shader. Returns a malloc'd
string. */
/* multiview: for a two-view (GL_OVR_multiview2) target, each view's
position corrected from the shared one by mv_rows (d3d8_gl.c, VR) */
char *nv2a_vertex_shader_to_glsl(const DWORD *instructions, unsigned long instruction_count,
unsigned long packed_attribute_mask);
unsigned long packed_attribute_mask, int multiview);
/* ---------- pixel shaders */
+4 -2
View File
@@ -775,11 +775,13 @@ static void handle_one_player_input(
#endif
#ifdef HALO_ANDROID
{
/* VR: the player faces where the head does (vr.c) */
/* VR: the player faces where the head does (vr.c); not in a
cinematic, whose camera the game has (seated, the controller
would turn it on the screen) */
real vr_yaw;
real vr_pitch;
if (halo_vr_aim(
if (!cinematic_in_progress() && halo_vr_aim(
local_player_index,
player->desired_angles.yaw,
player->desired_angles.pitch,
+36 -1
View File
@@ -389,7 +389,13 @@ static void render_window(
if (!bink_playback_in_progress())
{
build_sprite_prepare_for_window();
#ifdef HALO_ANDROID
halo_vr_multiview_sky(TRUE);
#endif
render_sky();
#ifdef HALO_ANDROID
halo_vr_multiview_sky(FALSE);
#endif
first_person_weapon_render_update();
lights_preprocess_scene();
render_objects();
@@ -700,7 +706,36 @@ static void render_vr_player_frame(
if (nodes)
halo_vr_set_seat_frame(window->local_player_index, (float)atan2(nodes[0].forward.j, nodes[0].forward.i));
}
for (eye = 0; eye < 2; eye++)
if (halo_vr_multiview())
{
/* both eyes at once, from between them */
struct render_window eye_window = *window;
float position[3], forward[3], up[3], bounds[4];
float vertical_field_of_view;
real_rectangle2d frustum_bounds;
halo_vr_center_camera(game_position, position, forward, up, &vertical_field_of_view, bounds);
render_vr_camera_set(&eye_window.render_camera, position, forward, up, vertical_field_of_view);
render_vr_camera_set(&eye_window.rasterizer_camera, position, forward, up, vertical_field_of_view);
frustum_bounds.x0 = bounds[0];
frustum_bounds.x1 = bounds[1];
frustum_bounds.y0 = bounds[2];
frustum_bounds.y1 = bounds[3];
{
/* the depth of what the game projects itself (its sprites) */
struct render_frustum frustum;
render_camera_build_frustum(&eye_window.rasterizer_camera, &frustum_bounds, &frustum, TRUE);
halo_vr_multiview_projection(frustum.projection_matrix[2][2], frustum.projection_matrix[3][2]);
}
render_vr_eye_pass = TRUE;
render_vr_frustum_bounds = &frustum_bounds;
render_player_frame(&eye_window, NULL);
render_vr_frustum_bounds = NULL;
render_vr_eye_pass = FALSE;
halo_vr_capture_multiview();
}
else for (eye = 0; eye < 2; eye++)
{
struct render_window eye_window = *window;
float position[3], forward[3], up[3], bounds[4];