Files
daveyandCraig 0e3ced6f65 vr: follow the headset through GET_VIDEO_VIEWS_STATUS
A core learns about a headset only from the HMD status bit;
SET_VIDEO_VIEWS returning true means the map was accepted. The scene
walker now enters VR on the first frame with HMD, sends a TRACKED map
at the frontend's recommended eye size and goes flat with an empty map.

A video reinit restarts the headset session, and entering or leaving VR
changes the geometry, which reinits video. A lost headset is waited for
five seconds, repeating the last frame, before the core goes flat.
Input is still polled on those frames.

While VR is tracking, the map is sent every retro_run(), as libretro.h
asks: it covers state loads and run-ahead. The frontend writes the
recommended eye size back on each call, so a new size there is taken as
a resize, and the AV info is sent once for it. A frontend that reports
a headset but refuses the tracked map is not asked again until the
headset drops or the VR option is turned off.

Co-authored-by: Craig <[email protected]>
2026-10-12 05:25:22 -07:00

946 lines
27 KiB
C++

/*
* Libretro 3DEngine
* Copyright (C) 2013-2014 - Hans-Kristian Arntzen
* Copyright (C) 2013-2014 - Daniel De Matteis
*
* Libretro 3DEngine is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* Libretro 3DEngine is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Libretro 3DEngine.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "libretro.h"
#include "program.h"
#include "../engine/mesh.hpp"
#include "../engine/texture.hpp"
#include "../engine/object.hpp"
#include "collision_detection.hpp"
#include "location_math.h"
#ifdef HAVE_VULKAN
#include "../engine/vk_renderer.hpp"
#endif
#include <glsym/glsym.h>
#include <vector>
#include <string.h>
using namespace glm;
static bool first_init = true;
static std::string mesh_path;
static bool discard_hack_enable = false;
static std::vector<std1::shared_ptr<GL::Mesh> > meshes;
static std1::shared_ptr<GL::Texture> blank;
//forward decls
static void scenewalker_reset_mesh_path(void);
static float light_r;
static float light_g;
static float light_b;
static float ambient_light_r;
static float ambient_light_g;
static float ambient_light_b;
static bool update;
static vec3 player_size(0.4f, 0.8f, 0.4f);
enum
{
MODE_MODELVIEWER = 0,
MODE_SCENEWALKER = 1
};
unsigned mode_engine = MODE_MODELVIEWER;
static bool scene_flip_y(void) { return !hw_render.bottom_left_origin; }
static struct retro_vr_frame_state vr_frame;
static float player_view_deg_x;
static float player_view_deg_y;
static vec3 player_pos(0, 2, 0);
extern bool vr_user_enable;
/* Per-frame scene state written by the input code and consumed by whichever
* renderer is active (GL meshes or the Vulkan renderer). */
static mat4 scene_model(1.0f);
static mat4 scene_view(1.0f);
static vec3 scene_eye(0.0f, 0.0f, 0.0f);
#ifdef HAVE_VULKAN
static bool vk_scene_ok = false;
#endif
static vec3 scenewalker_check_input(void)
{
input_poll_cb();
int analog_x = input_state_cb(0, RETRO_DEVICE_ANALOG,
RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X);
int analog_y = input_state_cb(0, RETRO_DEVICE_ANALOG,
RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y);
int analog_ry = input_state_cb(0, RETRO_DEVICE_ANALOG,
RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y);
int analog_rx = input_state_cb(0, RETRO_DEVICE_ANALOG,
RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_X);
static bool old_jump;
bool new_jump = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0,
RETRO_DEVICE_ID_JOYPAD_B);
bool jump = new_jump && !old_jump;
old_jump = new_jump;
bool run_pressed = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0,
RETRO_DEVICE_ID_JOYPAD_Y);
bool mouselook_pressed = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0,
RETRO_DEVICE_ID_JOYPAD_X);
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0,
RETRO_DEVICE_ID_JOYPAD_LEFT))
analog_rx = run_pressed ? -32767 : -16384;
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0,
RETRO_DEVICE_ID_JOYPAD_RIGHT))
analog_rx = run_pressed ? 32767 : 16384;
if (location_camera_control_enable)
{
if (loc_float_greater_than(current_location.longitude, previous_location.longitude))
analog_y = run_pressed ? -32767 : -16384;
else if (loc_float_lesser_than(current_location.longitude, previous_location.longitude))
analog_y = run_pressed ? 32767 : 16384;
}
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0,
RETRO_DEVICE_ID_JOYPAD_UP))
{
if (mouselook_pressed)
analog_ry = run_pressed ? -32767 : -16384;
else
analog_y = run_pressed ? -32767 : -16384;
}
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0,
RETRO_DEVICE_ID_JOYPAD_DOWN))
{
if (mouselook_pressed)
analog_ry = run_pressed ? 32767 : 16384;
else
analog_y = run_pressed ? 32767 : 16384;
}
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0,
RETRO_DEVICE_ID_JOYPAD_L))
analog_x = run_pressed ? -32767 : -16384;
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0,
RETRO_DEVICE_ID_JOYPAD_R))
analog_x = run_pressed ? 32767 : 16384;
if (abs(analog_x) < 10000)
analog_x = 0;
if (abs(analog_y) < 10000)
analog_y = 0;
if (abs(analog_rx) < 10000)
analog_rx = 0;
if (abs(analog_ry) < 10000)
analog_ry = 0;
#if 0
if (log_cb)
{
log_cb(RETRO_LOG_INFO, "analog_x: %d\n", analog_x);
log_cb(RETRO_LOG_INFO, "analog_y: %d\n", analog_y);
log_cb(RETRO_LOG_INFO, "analog_rx: %d\n", analog_rx);
log_cb(RETRO_LOG_INFO, "analog_ry: %d\n", analog_ry);
}
#endif
if (vr_active)
analog_ry = 0; /* head controls pitch */
player_view_deg_y += analog_rx * -0.00008f;
player_view_deg_x += analog_ry * -0.00005f;
player_view_deg_x = clamp(player_view_deg_x, -80.0f, 80.0f);
mat4 rotate_x = rotate(mat4(1.0), player_view_deg_x, vec3(1, 0, 0));
mat4 rotate_y = rotate(mat4(1.0), player_view_deg_y, vec3(0, 1, 0));
mat4 rotate_y_right = rotate(mat4(1.0), player_view_deg_y - 90.0f, vec3(0, 1, 0));
vec3 look_dir = vec3(rotate_y * rotate_x * vec4(0, 0, -1, 1));
vec3 right_walk_dir = vec3(rotate_y_right * vec4(0, 0, -1, 1));
vec3 front_walk_dir = vec3(rotate_y * vec4(0, 0, -1, 1));
if (vr_active)
{
/* walk where the head points, on the ground plane */
front_walk_dir = vec3(rotate_y * vec4(vr_head_forward_xz(vr_frame), 0.0f));
right_walk_dir = cross(front_walk_dir, vec3(0, 1, 0));
}
vec3 velocity = front_walk_dir * vec3(analog_y * -0.000002f) +
right_walk_dir * vec3(analog_x * 0.000002f);
vec3 player_pos_espace = player_pos / player_size;
vec3 velocity_espace = velocity / player_size;
coll_detection(player_pos_espace, velocity_espace);
player_pos_espace += velocity_espace;
coll_wall_hug_detection(player_pos_espace);
static vec3 gravity;
static bool can_jump;
gravity += vec3(0.0f, -0.01f, 0.0f);
if (can_jump && jump)
{
gravity[1] += 0.3f;
can_jump = false;
}
gravity[1] -= gravity[1] * 0.01f;
vec3 old_gravity = gravity;
coll_detection(player_pos_espace, gravity);
if (old_gravity[1] != gravity[1])
{
gravity = vec3(0.0f);
can_jump = true;
}
player_pos_espace += gravity;
coll_wall_hug_detection(player_pos_espace);
player_pos = player_pos_espace * player_size;
mat4 view = lookAt(player_pos, player_pos + look_dir, vec3(0, 1, 0));
bool start_pressed = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START);
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2))
{
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT))
{
if (start_pressed)
light_r -= 0.01;
else
ambient_light_r -= 0.01;
}
else
{
if (start_pressed)
light_r += 0.01;
else
ambient_light_r += 0.01;
}
}
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2))
{
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT))
{
if (start_pressed)
light_g -= 0.01;
else
ambient_light_g -= 0.01;
}
else
{
if (start_pressed)
light_g += 0.01;
else
ambient_light_g += 0.01;
}
}
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3))
{
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT))
{
if (start_pressed)
light_b -= 0.01;
else
ambient_light_b -= 0.01;
}
else
{
if (start_pressed)
light_b += 0.01;
else
ambient_light_b += 0.01;
}
}
scene_view = view;
scene_eye = player_pos;
return player_size;
}
static vec3 modelviewer_check_input(void)
{
if (mode_engine == MODE_SCENEWALKER)
return scenewalker_check_input();
static float model_rotate_y;
static float model_rotate_x;
static float model_scale = 1.0f;
input_poll_cb();
int analog_x = input_state_cb(0, RETRO_DEVICE_ANALOG,
RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X);
int analog_y = input_state_cb(0, RETRO_DEVICE_ANALOG,
RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y);
int analog_ry = input_state_cb(0, RETRO_DEVICE_ANALOG,
RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y);
if (abs(analog_x) < 10000)
analog_x = 0;
if (abs(analog_y) < 10000)
analog_y = 0;
if (abs(analog_ry) < 10000)
analog_ry = 0;
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT))
analog_x -= 30000;
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT))
analog_x += 30000;
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP))
analog_y -= 30000;
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN))
analog_y += 30000;
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B))
analog_ry -= 30000;
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A))
analog_ry += 30000;
bool start_pressed = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START);
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2))
{
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT))
{
if (start_pressed)
light_r -= 0.01;
else
ambient_light_r -= 0.01;
}
else
{
if (start_pressed)
light_r += 0.01;
else
ambient_light_r += 0.01;
}
}
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2))
{
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT))
{
if (start_pressed)
light_g -= 0.01;
else
ambient_light_g -= 0.01;
}
else
{
if (start_pressed)
light_g += 0.01;
else
ambient_light_g += 0.01;
}
}
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3))
{
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT))
{
if (start_pressed)
light_b -= 0.01;
else
ambient_light_b -= 0.01;
}
else
{
if (start_pressed)
light_b += 0.01;
else
ambient_light_b += 0.01;
}
}
model_scale *= 1.0f - analog_ry * 0.000001f;
model_scale = clamp(model_scale, 0.0001f, 100.0f);
model_rotate_x += analog_y * 0.0001f;
model_rotate_y += analog_x * 0.00015f;
mat4 translation = translate(mat4(1.0), vec3(0, 0, -40));
mat4 scaler = scale(mat4(1.0), vec3(model_scale, model_scale, model_scale));
mat4 rotate_x = rotate(mat4(1.0), model_rotate_x, vec3(1, 0, 0));
mat4 rotate_y = rotate(mat4(1.0), model_rotate_y, vec3(0, 1, 0));
scene_model = translation * scaler * rotate_x * rotate_y;
//check_collision_cube();
return player_size;
}
/* Pushes the state computed by the input code into the GL meshes. */
static void apply_scene_state_gl(void)
{
const bool scene = (mode_engine == MODE_SCENEWALKER);
for (unsigned i = 0; i < meshes.size(); i++)
{
if (scene)
{
meshes[i]->set_view(scene_view);
meshes[i]->set_eye(scene_eye);
}
else
meshes[i]->set_model(scene_model);
meshes[i]->set_lighting(light_r, light_g, light_b);
meshes[i]->set_ambient_lighting(ambient_light_r, ambient_light_g, ambient_light_b);
}
}
static void init_lighting(void)
{
if (mode_engine == MODE_SCENEWALKER)
{
light_r = normalize(0);
light_g = normalize(10);
light_b = normalize(0);
}
else
{
light_r = normalize(-1);
light_g = normalize(-1);
light_b = normalize(-1);
}
ambient_light_r = 0.25f;
ambient_light_g = 0.25f;
ambient_light_b = 0.25f;
}
static void init_mesh(const std::string& path)
{
if (log_cb)
log_cb(RETRO_LOG_INFO, "Loading Mesh ...\n");
static const std::string vertex_shader =
"uniform mat4 uModel;\n"
"uniform mat4 uMVP;\n"
"attribute vec4 aVertex;\n"
"attribute vec3 aNormal;\n"
"attribute vec2 aTex;\n"
"varying vec4 vNormal;\n"
"varying vec2 vTex;\n"
"varying vec4 vPos;\n"
"void main() {\n"
" gl_Position = uMVP * aVertex;\n"
" vTex = aTex;\n"
" vPos = uModel * aVertex;\n"
" vNormal = uModel * vec4(aNormal, 0.0);\n"
"}";
static const std::string fragment_shader_avoid_discard_hack =
"#ifdef GL_ES\n"
"precision mediump float;\n"
"#endif\n"
"varying vec2 vTex;\n"
"varying vec4 vNormal;\n"
"varying vec4 vPos;\n"
"uniform sampler2D sDiffuse;\n"
"uniform sampler2D sAmbient;\n"
"uniform vec3 uLightPos;\n"
"uniform vec3 uLightAmbient;\n"
"uniform vec3 uMTLAmbient;\n"
"uniform float uMTLAlphaMod;\n"
"uniform vec3 uMTLDiffuse;\n"
"uniform vec3 uMTLSpecular;\n"
"uniform float uMTLSpecularPower;\n"
"void main() {\n"
" vec4 colorDiffuseFull = texture2D(sDiffuse, vTex);\n"
" vec4 colorAmbientFull = texture2D(sAmbient, vTex);\n"
" if (colorDiffuseFull.a < 0.5)\n"
" discard;\n"
" vec3 colorDiffuse = mix(uMTLDiffuse, colorDiffuseFull.rgb, vec3(colorDiffuseFull.a));\n"
" vec3 colorAmbient = mix(uMTLAmbient, colorAmbientFull.rgb, vec3(colorAmbientFull.a));\n"
" vec3 normal = normalize(vNormal.xyz);\n"
" float directivity = dot(uLightPos, -normal);\n"
" vec3 diffuse = colorDiffuse * clamp(directivity, 0.0, 1.0);\n"
" vec3 ambient = colorAmbient * uLightAmbient;\n"
" vec3 modelToFace = normalize(-vPos.xyz);\n"
" float specularity = pow(clamp(dot(modelToFace, reflect(uLightPos, normal)), 0.0, 1.0), uMTLSpecularPower);\n"
" vec3 specular = uMTLSpecular * specularity;\n"
" gl_FragColor = vec4(diffuse + ambient + specular, uMTLAlphaMod);\n"
"}";
static const std::string fragment_shader =
"#ifdef GL_ES\n"
"precision mediump float;\n"
"#endif\n"
"varying vec2 vTex;\n"
"varying vec4 vNormal;\n"
"varying vec4 vPos;\n"
"uniform sampler2D sDiffuse;\n"
"uniform sampler2D sAmbient;\n"
"uniform vec3 uLightPos;\n"
"uniform vec3 uLightAmbient;\n"
"uniform vec3 uMTLAmbient;\n"
"uniform float uMTLAlphaMod;\n"
"uniform vec3 uMTLDiffuse;\n"
"uniform vec3 uMTLSpecular;\n"
"uniform float uMTLSpecularPower;\n"
"void main() {\n"
" vec4 colorDiffuseFull = texture2D(sDiffuse, vTex);\n"
" vec4 colorAmbientFull = texture2D(sAmbient, vTex);\n"
" vec3 colorDiffuse = mix(uMTLDiffuse, colorDiffuseFull.rgb, vec3(colorDiffuseFull.a));\n"
" vec3 colorAmbient = mix(uMTLAmbient, colorAmbientFull.rgb, vec3(colorAmbientFull.a));\n"
" vec3 normal = normalize(vNormal.xyz);\n"
" float directivity = dot(uLightPos, -normal);\n"
" vec3 diffuse = colorDiffuse * clamp(directivity, 0.0, 1.0);\n"
" vec3 ambient = colorAmbient * uLightAmbient;\n"
" vec3 modelToFace = normalize(-vPos.xyz);\n"
" float specularity = pow(clamp(dot(modelToFace, reflect(uLightPos, normal)), 0.0, 1.0), uMTLSpecularPower);\n"
" vec3 specular = uMTLSpecular * specularity;\n"
" gl_FragColor = vec4(diffuse + ambient + specular, uMTLAlphaMod * colorDiffuseFull.a);\n"
"}";
static const std::string fragment_shader_scene =
"#ifdef GL_ES\n"
"precision mediump float;\n"
"#endif\n"
"varying vec2 vTex;\n"
"varying vec4 vNormal;\n"
"varying vec4 vPos;\n"
"uniform sampler2D sDiffuse;\n"
"uniform sampler2D sAmbient;\n"
"uniform vec3 uLightPos;\n"
"uniform vec3 uLightAmbient;\n"
"uniform vec3 uEyePos;\n"
"uniform vec3 uMTLAmbient;\n"
"uniform float uMTLAlphaMod;\n"
"uniform vec3 uMTLDiffuse;\n"
"uniform vec3 uMTLSpecular;\n"
"uniform float uMTLSpecularPower;\n"
"void main() {\n"
" vec4 colorDiffuseFull = texture2D(sDiffuse, vTex);\n"
" vec4 colorAmbientFull = texture2D(sAmbient, vTex);\n"
" vec3 lightDir = normalize(vPos.xyz - uLightPos);\n"
" vec3 colorDiffuse = mix(uMTLDiffuse, colorDiffuseFull.rgb, vec3(colorDiffuseFull.a));\n"
" vec3 colorAmbient = mix(uMTLAmbient, colorAmbientFull.rgb, vec3(colorAmbientFull.a));\n"
" vec3 normal = normalize(vNormal.xyz);\n"
" float directivity = dot(lightDir, -normal);\n"
" vec3 diffuse = colorDiffuse * clamp(directivity, 0.0, 1.0);\n"
" vec3 ambient = colorAmbient * uLightAmbient;\n"
" vec3 modelToFace = normalize(uEyePos - vPos.xyz);\n"
" float specularity = pow(clamp(dot(modelToFace, reflect(lightDir, normal)), 0.0, 1.0), uMTLSpecularPower);\n"
" vec3 specular = uMTLSpecular * specularity;\n"
" gl_FragColor = vec4(diffuse + ambient + specular, uMTLAlphaMod * colorDiffuseFull.a);\n"
"}";
std1::shared_ptr<GL::Shader> shader(new GL::Shader(vertex_shader, (mode_engine == MODE_SCENEWALKER)
? fragment_shader_scene : ((discard_hack_enable)
? fragment_shader_avoid_discard_hack : fragment_shader)));
meshes = OBJ::load_from_file(path);
mat4 projection;
if (mode_engine == MODE_SCENEWALKER)
{
projection = perspective(45.0f, 640.0f / 480.0f, 1.0f, 100.0f);
if (scene_flip_y())
projection = scale(mat4(1.0), vec3(1, -1, 1)) * projection;
}
else
{
projection = scale(mat4(1.0), vec3(1, -1, 1)) * perspective(45.0f, 4.0f / 3.0f, 0.2f, 100.0f);
}
for (unsigned i = 0; i < meshes.size(); i++)
{
meshes[i]->set_projection(projection);
meshes[i]->set_shader(shader);
meshes[i]->set_blank(blank);
if (mode_engine == MODE_SCENEWALKER)
{
meshes[i]->set_lighting(0, 10, 0);
const std::vector<GL::Vertex>& vertices = *meshes[i]->get_vertex();
for (unsigned v = 0; v < vertices.size(); v += 3)
coll_triangles_push(v, vertices, player_size);
}
}
init_lighting();
}
#ifdef HAVE_VULKAN
/* Vulkan counterpart of init_mesh(): same OBJ parse, same collision data,
* but the GPU objects live in VKR instead of GL::Mesh. */
static bool vulkan_load_scene(void)
{
unsigned i, v;
const bool scene = (mode_engine == MODE_SCENEWALKER);
std::vector<OBJ::Part> parts;
VKR::ShaderMode mode;
if (log_cb)
log_cb(RETRO_LOG_INFO, "Loading Mesh (Vulkan) ...\n");
parts = OBJ::load_parts(mesh_path);
if (scene)
{
for (i = 0; i < parts.size(); i++)
for (v = 0; v + 2 < parts[i].vertices.size(); v += 3)
coll_triangles_push(v, parts[i].vertices, player_size);
mode = VKR::SHADER_SCENE;
}
else
mode = discard_hack_enable ? VKR::SHADER_MODEL_DISCARD : VKR::SHADER_MODEL;
init_lighting();
vk_scene_ok = VKR::load_scene(parts, mode);
return vk_scene_ok;
}
#endif
extern char retro_path_info[1024];
static void modelviewer_context_reset(void)
{
#ifdef HAVE_VULKAN
if (renderer_is_vulkan())
{
vk_scene_ok = false;
if (strstr(retro_path_info, ".mtl") || mode_engine == MODE_SCENEWALKER)
{
coll_triangles_clear();
scenewalker_reset_mesh_path();
mode_engine = MODE_SCENEWALKER;
}
if (!VKR::init(environ_cb))
{
if (log_cb)
log_cb(RETRO_LOG_ERROR, "Vulkan renderer init failed.\n");
return;
}
vulkan_load_scene();
update = true;
return;
}
#endif
renderer_dead_state = true;
meshes.clear();
blank.reset();
renderer_dead_state = false;
if (strstr(retro_path_info, ".mtl") || mode_engine == MODE_SCENEWALKER)
{
coll_triangles_clear();
scenewalker_reset_mesh_path();
mode_engine = MODE_SCENEWALKER;
}
rglgen_resolve_symbols(hw_render.get_proc_address);
blank = GL::Texture::blank();
init_mesh(mesh_path);
update = true;
}
/* Called from the frontend's context_destroy (Vulkan only). */
static void modelviewer_context_destroy(void)
{
#ifdef HAVE_VULKAN
vk_scene_ok = false;
VKR::destroy();
#endif
}
/* Option changes used to be applied by faking a context reset. For Vulkan
* only the scene (pipeline variant + geometry) needs rebuilding. */
static void modelviewer_apply_option_change(void)
{
#ifdef HAVE_VULKAN
if (renderer_is_vulkan())
{
if (!VKR::initialized())
return; /* will be built by the real context_reset */
if (mode_engine == MODE_SCENEWALKER)
coll_triangles_clear();
vulkan_load_scene();
return;
}
#endif
modelviewer_context_reset();
}
static void modelviewer_update_variables(retro_environment_t environ_cb)
{
struct retro_variable var;
var.key = "3dengine-modelviewer-discard-hack";
var.value = NULL;
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
discard_hack_enable = false;
else if (!strcmp(var.value, "enabled"))
discard_hack_enable = true;
if (!first_init)
modelviewer_apply_option_change();
}
var.key = "3dengine-modelviewer-vr-enable";
var.value = NULL;
if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
if (!strcmp(var.value, "disabled"))
vr_user_enable = false;
else if (!strcmp(var.value, "enabled"))
vr_user_enable = true;
if (!first_init)
modelviewer_apply_option_change();
}
}
#ifdef HAVE_VULKAN
static void modelviewer_run_vulkan(unsigned width, unsigned height, bool scene, bool vr)
{
VKR::Frame f;
f.model = scene_model;
f.light = vec3(light_r, light_g, light_b);
f.ambient = vec3(ambient_light_r, ambient_light_g, ambient_light_b);
f.width = width;
f.height = height;
f.num_passes = 0;
if (vr)
{
unsigned eye;
mat4 rig = translate(mat4(1.0), player_pos) *
rotate(mat4(1.0), player_view_deg_y, vec3(0, 1, 0));
for (eye = 0; eye < 2; eye++)
{
const struct retro_vr_eye_state &es = vr_frame.eyes[eye];
mat4 eye_world = rig * vr_eye_pose_matrix(es);
VKR::Pass &p = f.passes[eye];
/* Same matrices as the GL path; VKR applies the GL->Vulkan clip
* correction, so no scene_flip_y() handling here. */
p.view = inverse(eye_world);
p.proj = vr_projection(es.fov_tan, 0.1f, 100.0f);
p.eye_pos = vec3(eye_world[3]);
p.x = eye * vr_eye_width;
p.width = vr_eye_width;
p.height = vr_eye_height;
}
f.num_passes = 2;
}
else
{
VKR::Pass &p = f.passes[0];
p.proj = scene ? perspective(45.0f, 640.0f / 480.0f, 1.0f, 100.0f)
: perspective(45.0f, 4.0f / 3.0f, 0.2f, 100.0f);
p.view = scene ? scene_view : mat4(1.0f);
p.eye_pos = scene_eye;
p.x = 0;
p.width = width;
p.height = height;
f.num_passes = 1;
}
if (!vk_scene_ok || !VKR::render(f, video_cb))
video_cb(NULL, width, height, 0); /* dupe the last frame */
}
#endif
static void modelviewer_run(void)
{
unsigned i, width, height;
const bool scene = (mode_engine == MODE_SCENEWALKER);
bool dupe = false;
bool vr = vr_update(&vr_frame, scene, &dupe); /* before input */
if (dupe)
{
input_poll_cb();
video_cb(NULL, vr_eye_width * 2, vr_eye_height, 0);
return;
}
vec3 look_dir = modelviewer_check_input();
(void)look_dir;
width = vr ? vr_eye_width * 2 : engine_width;
height = vr ? vr_eye_height : engine_height;
#ifdef HAVE_VULKAN
if (renderer_is_vulkan())
{
modelviewer_run_vulkan(width, height, scene, vr);
return;
}
#endif
apply_scene_state_gl();
glBindFramebuffer(GL_FRAMEBUFFER, hw_render.get_current_framebuffer());
glViewport(0, 0, width, height);
glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
/* Only the flipped projection needs CW. Unflipped is normal CCW. */
glFrontFace((scene && !scene_flip_y()) ? GL_CCW : GL_CW);
glEnable(GL_CULL_FACE);
glEnable(GL_BLEND);
if (vr)
{
unsigned eye;
mat4 rig = translate(mat4(1.0), player_pos) *
rotate(mat4(1.0), player_view_deg_y, vec3(0, 1, 0));
glEnable(GL_SCISSOR_TEST);
for (eye = 0; eye < 2; eye++)
{
const struct retro_vr_eye_state &es = vr_frame.eyes[eye];
mat4 eye_world = rig * vr_eye_pose_matrix(es);
mat4 view = inverse(eye_world);
mat4 proj = vr_projection(es.fov_tan, 0.1f, 100.0f);
vec3 eye_pos = vec3(eye_world[3]);
if (scene_flip_y())
proj = scale(mat4(1.0), vec3(1, -1, 1)) * proj;
glViewport(eye * vr_eye_width, 0, vr_eye_width, vr_eye_height);
glScissor (eye * vr_eye_width, 0, vr_eye_width, vr_eye_height);
for (i = 0; i < meshes.size(); i++)
{
meshes[i]->set_projection(proj);
meshes[i]->set_view(view);
meshes[i]->set_eye(eye_pos);
meshes[i]->render();
}
}
glDisable(GL_SCISSOR_TEST);
}
else
for (i = 0; i < meshes.size(); i++)
meshes[i]->render();
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
video_cb(RETRO_HW_FRAME_BUFFER_VALID, width, height, 0);
}
static void scenewalker_reset_mesh_path(void)
{
//always assume matching .obj file is there
size_t extPos = mesh_path.rfind('.');
if (extPos != std::string::npos)
{
// Erase the current extension.
mesh_path.erase(extPos);
// Add the new extension.
mesh_path.append(".obj");
if (log_cb)
log_cb(RETRO_LOG_INFO, "New path: %s\n", mesh_path.c_str());
}
}
static void modelviewer_load_game(const struct retro_game_info *info)
{
mode_engine = MODE_MODELVIEWER;
mesh_path = info->path;
if (strstr(info->path, ".mtl"))
{
mode_engine = MODE_SCENEWALKER;
coll_test_crash_detection();
scenewalker_reset_mesh_path();
}
else
player_size = vec3(0, 0, 0);
first_init = false;
vr_frame_defaults(&vr_frame);
}
const engine_program_t engine_program_modelviewer = {
modelviewer_load_game,
modelviewer_run,
modelviewer_context_reset,
modelviewer_update_variables,
modelviewer_check_input,
modelviewer_context_destroy,
};