/* * 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 . */ #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 #include #include using namespace glm; static bool first_init = true; static std::string mesh_path; static bool discard_hack_enable = false; static std::vector > meshes; static std1::shared_ptr 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 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& 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 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, };