Files
libretro-3dengine/program/modelviewer.cpp
T
2026-10-06 09:12:16 +01:00

945 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;
bool modelviewer_vr_capable(void) { return mode_engine == MODE_SCENEWALKER; }
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 vr = false;
if (scene && vr_active)
{
vr_poll_frame(&vr_frame); /* once, before input, per the API doc */
vr = vr_active; /* may drop if the resize renegotiation failed */
}
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,
};