diff --git a/common/custom_data/Tfrag3Data.h b/common/custom_data/Tfrag3Data.h index 463e27f9e5..d1d3cea58f 100644 --- a/common/custom_data/Tfrag3Data.h +++ b/common/custom_data/Tfrag3Data.h @@ -11,7 +11,7 @@ namespace tfrag3 { -constexpr int TFRAG3_VERSION = 8; +constexpr int TFRAG3_VERSION = 9; // These vertices should be uploaded to the GPU at load time and don't change struct PreloadedVertex { @@ -42,8 +42,8 @@ struct StripDraw { // to do culling, the above vertex stream is grouped. // by following the visgroups and checking the visibility, you can leave out invisible vertices. struct VisGroup { - u32 num = 0; // number of vertex indices in this group - u32 vis_idx = 0; // the visibility group they belong to + u32 num = 0; // number of vertex indices in this group + u32 vis_idx_in_pc_bvh = 0; // the visibility group they belong to (in BVH) }; std::vector vis_groups; @@ -77,8 +77,9 @@ struct InstancedStripDraw { struct VisNode { math::Vector bsphere; // the bounding sphere, in meters (4096 = 1 game meter). w = rad u16 child_id = 0xffff; // the ID of our first child. - u8 num_kids = 0xff; // number of children. The children are consecutive in memory - u8 flags = 0; // flags. If 1, we have a DrawVisNode child, otherwise a leaf. + u16 my_id = 0xffff; + u8 num_kids = 0xff; // number of children. The children are consecutive in memory + u8 flags = 0; // flags. If 1, we have a DrawVisNode child, otherwise a leaf. }; // The leaf nodes don't actually exist in the vector of VisNodes, but instead they are ID's used diff --git a/common/util/FrameLimiter.h b/common/util/FrameLimiter.h new file mode 100644 index 0000000000..3384149e32 --- /dev/null +++ b/common/util/FrameLimiter.h @@ -0,0 +1,36 @@ +#pragma once + +#include "common/util/Timer.h" + +class FrameLimiter { + public: + void run(double target_fps, bool experimental_accurate_lag, double engine_time) { + double target_seconds; + if (experimental_accurate_lag) { + target_seconds = round_to_nearest_60fps(engine_time); + } else { + target_seconds = 1.f / target_fps; + } + double remaining_time = target_seconds - m_timer.getSeconds(); + while (remaining_time > 0) { + if (remaining_time > 0.003) { + std::this_thread::sleep_for(std::chrono::microseconds(int(remaining_time * 1e6 * 0.5))); + } + remaining_time = target_seconds - m_timer.getSeconds(); + } + + m_timer.start(); + } + + private: + double round_to_nearest_60fps(double current) { + double one_frame = 1.f / 60.f; + int frames_missed = (current / one_frame); // rounds down + if (frames_missed > 4) { + frames_missed = 4; + } + return (frames_missed + 1) * one_frame; + } + + Timer m_timer; +}; \ No newline at end of file diff --git a/decompiler/level_extractor/extract_tfrag.cpp b/decompiler/level_extractor/extract_tfrag.cpp index 7bb0fe7c19..5e2b556909 100644 --- a/decompiler/level_extractor/extract_tfrag.cpp +++ b/decompiler/level_extractor/extract_tfrag.cpp @@ -117,6 +117,7 @@ VisNodeTree extract_vis_data(const level_tools::DrawableTreeTfrag* tree, u16 fir } vis.num_kids = elt.child_count; vis.flags = elt.flags; + vis.my_id = elt.id; assert(vis.flags == expecting_leaves ? 0 : 1); assert(vis.num_kids > 0); assert(vis.num_kids <= 8); @@ -2036,8 +2037,8 @@ void make_tfrag3_data(std::map>& draws, for (auto& strip : draw.strips) { tfrag3::StripDraw::VisGroup vgroup; - vgroup.vis_idx = strip.tfrag_id; // associate with the tfrag for culling - vgroup.num = strip.verts.size() + 1; // one for the primitive restart! + vgroup.vis_idx_in_pc_bvh = strip.tfrag_id; // associate with the tfrag for culling + vgroup.num = strip.verts.size() + 1; // one for the primitive restart! tdraw.num_triangles += strip.verts.size() - 2; for (auto& vert : strip.verts) { @@ -2118,7 +2119,7 @@ void merge_groups(std::vector& grps) { std::vector result; result.push_back(grps.at(0)); for (size_t i = 1; i < grps.size(); i++) { - if (grps[i].vis_idx == result.back().vis_idx) { + if (grps[i].vis_idx_in_pc_bvh == result.back().vis_idx_in_pc_bvh) { result.back().num += grps[i].num; } else { result.push_back(grps[i]); @@ -2196,11 +2197,11 @@ void extract_tfrag(const level_tools::DrawableTreeTfrag* tree, for (auto& draw : this_tree.draws) { for (auto& str : draw.vis_groups) { - auto it = tfrag_parents.find(str.vis_idx); + auto it = tfrag_parents.find(str.vis_idx_in_pc_bvh); if (it == tfrag_parents.end()) { - str.vis_idx = UINT32_MAX; + str.vis_idx_in_pc_bvh = UINT32_MAX; } else { - str.vis_idx = it->second; + str.vis_idx_in_pc_bvh = it->second; } } merge_groups(draw.vis_groups); diff --git a/decompiler/level_extractor/extract_tfrag.h b/decompiler/level_extractor/extract_tfrag.h index 985d43826c..dbe176661b 100644 --- a/decompiler/level_extractor/extract_tfrag.h +++ b/decompiler/level_extractor/extract_tfrag.h @@ -20,15 +20,6 @@ struct VisNodeTree { bool only_children = false; }; -// The final result -struct ExtractedTFragmentTree { - // TFragmentKind kind = TFragmentKind::INVALID; - VisNodeTree vis_nodes; - - u16 num_tfrags = 0; - u16 tfrag_base_idx = 0; -}; - // will pool textures with others already in out. void extract_tfrag(const level_tools::DrawableTreeTfrag* tree, const std::string& debug_name, diff --git a/decompiler/level_extractor/extract_tie.cpp b/decompiler/level_extractor/extract_tie.cpp index ff9017888c..5f67f9ef63 100644 --- a/decompiler/level_extractor/extract_tie.cpp +++ b/decompiler/level_extractor/extract_tie.cpp @@ -119,6 +119,7 @@ void extract_vis_data(const level_tools::DrawableTreeInstanceTie* tree, } vis.num_kids = elt.child_count; vis.flags = elt.flags; + vis.my_id = elt.id; assert(vis.flags == expecting_leaves ? 0 : 1); assert(vis.num_kids > 0); assert(vis.num_kids <= 8); @@ -2182,8 +2183,8 @@ void add_vertices_and_static_draw(tfrag3::TieTree& tree, // now we have a draw, time to add vertices tfrag3::StripDraw::VisGroup vgroup; - vgroup.vis_idx = inst.vis_id; // associate with the instance for culling - vgroup.num = strip.verts.size() + 1; // one for the primitive restart! + vgroup.vis_idx_in_pc_bvh = inst.vis_id; // associate with the instance for culling + vgroup.num = strip.verts.size() + 1; // one for the primitive restart! draw_to_add_to->num_triangles += strip.verts.size() - 2; for (auto& vert : strip.verts) { tfrag3::PreloadedVertex vtx; @@ -2247,7 +2248,7 @@ void merge_groups(std::vector& grps) { std::vector result; result.push_back(grps.at(0)); for (size_t i = 1; i < grps.size(); i++) { - if (grps[i].vis_idx == result.back().vis_idx) { + if (grps[i].vis_idx_in_pc_bvh == result.back().vis_idx_in_pc_bvh) { result.back().num += grps[i].num; } else { result.push_back(grps[i]); @@ -2329,11 +2330,11 @@ void extract_tie(const level_tools::DrawableTreeInstanceTie* tree, // remap vis indices and merge for (auto& draw : this_tree.static_draws) { for (auto& str : draw.vis_groups) { - auto it = instance_parents.find(str.vis_idx); + auto it = instance_parents.find(str.vis_idx_in_pc_bvh); if (it == instance_parents.end()) { - str.vis_idx = UINT32_MAX; + str.vis_idx_in_pc_bvh = UINT32_MAX; } else { - str.vis_idx = it->second; + str.vis_idx_in_pc_bvh = it->second; } } merge_groups(draw.vis_groups); diff --git a/game/graphics/opengl_renderer/BucketRenderer.cpp b/game/graphics/opengl_renderer/BucketRenderer.cpp index b065fb6d89..158ce1005d 100644 --- a/game/graphics/opengl_renderer/BucketRenderer.cpp +++ b/game/graphics/opengl_renderer/BucketRenderer.cpp @@ -55,4 +55,11 @@ void SkipRenderer::render(DmaFollower& dma, while (dma.current_tag_offset() != render_state->next_bucket) { dma.read_and_advance(); } +} + +void SharedRenderState::reset() { + has_camera_planes = false; + for (auto& x : occlusion_vis) { + x.valid = false; + } } \ No newline at end of file diff --git a/game/graphics/opengl_renderer/BucketRenderer.h b/game/graphics/opengl_renderer/BucketRenderer.h index 283f709473..1bb2c5b82f 100644 --- a/game/graphics/opengl_renderer/BucketRenderer.h +++ b/game/graphics/opengl_renderer/BucketRenderer.h @@ -43,6 +43,11 @@ enum class BucketId { MAX_BUCKETS = 69 }; +struct LevelVis { + bool valid = false; + u8 data[2048]; +}; + /*! * The main renderer will contain a single SharedRenderState that's passed to all bucket renderers. * This allows bucket renders to share textures and shaders. @@ -63,8 +68,11 @@ struct SharedRenderState { bool dump_playback = false; bool use_sky_cpu = true; + bool use_occlusion_culling = true; + void reset(); bool has_camera_planes = false; + LevelVis occlusion_vis[2]; math::Vector4f camera_planes[4]; }; diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.cpp b/game/graphics/opengl_renderer/OpenGLRenderer.cpp index 369988fbec..9d28c7ae84 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.cpp +++ b/game/graphics/opengl_renderer/OpenGLRenderer.cpp @@ -72,11 +72,11 @@ void OpenGLRenderer::init_bucket_renderers() { init_bucket_renderer("sky", BucketId::SKY_DRAW); init_bucket_renderer("tfrag-tex-0", BucketId::TFRAG_TEX_LEVEL0); - init_bucket_renderer("tfrag-0", BucketId::TFRAG_LEVEL0, normal_tfrags, false); - init_bucket_renderer("tie-0", BucketId::TIE_LEVEL0); + init_bucket_renderer("tfrag-0", BucketId::TFRAG_LEVEL0, normal_tfrags, false, 0); + init_bucket_renderer("tie-0", BucketId::TIE_LEVEL0, 0); init_bucket_renderer("tfrag-tex-1", BucketId::TFRAG_TEX_LEVEL1); - init_bucket_renderer("tfrag-1", BucketId::TFRAG_LEVEL1, normal_tfrags, false); - init_bucket_renderer("tie-1", BucketId::TIE_LEVEL1); + init_bucket_renderer("tfrag-1", BucketId::TFRAG_LEVEL1, normal_tfrags, false, 1); + init_bucket_renderer("tie-1", BucketId::TIE_LEVEL1, 1); init_bucket_renderer("shrub-tex-0", BucketId::SHRUB_TEX_LEVEL0); init_bucket_renderer("shrub-tex-1", BucketId::SHRUB_TEX_LEVEL1); init_bucket_renderer("alpha-tex-0", BucketId::ALPHA_TEX_LEVEL0); @@ -84,15 +84,17 @@ void OpenGLRenderer::init_bucket_renderers() { auto sky_gpu_blender = std::make_shared(); auto sky_cpu_blender = std::make_shared(); init_bucket_renderer("sky-blend-and-tfrag-trans-0", - BucketId::TFRAG_TRANS0_AND_SKY_BLEND_LEVEL0, + BucketId::TFRAG_TRANS0_AND_SKY_BLEND_LEVEL0, 0, sky_gpu_blender, sky_cpu_blender); - init_bucket_renderer("tfrag-dirt-0", BucketId::TFRAG_DIRT_LEVEL0, dirt_tfrags, false); - init_bucket_renderer("tfrag-ice-0", BucketId::TFRAG_ICE_LEVEL0, ice_tfrags, false); + init_bucket_renderer("tfrag-dirt-0", BucketId::TFRAG_DIRT_LEVEL0, dirt_tfrags, false, + 0); + init_bucket_renderer("tfrag-ice-0", BucketId::TFRAG_ICE_LEVEL0, ice_tfrags, false, 0); init_bucket_renderer("sky-blend-and-tfrag-trans-1", - BucketId::TFRAG_TRANS1_AND_SKY_BLEND_LEVEL1, + BucketId::TFRAG_TRANS1_AND_SKY_BLEND_LEVEL1, 1, sky_gpu_blender, sky_cpu_blender); - init_bucket_renderer("tfrag-dirt-1", BucketId::TFRAG_DIRT_LEVEL1, dirt_tfrags, false); - init_bucket_renderer("tfrag-ice-1", BucketId::TFRAG_ICE_LEVEL1, ice_tfrags, false); + init_bucket_renderer("tfrag-dirt-1", BucketId::TFRAG_DIRT_LEVEL1, dirt_tfrags, false, + 1); + init_bucket_renderer("tfrag-ice-1", BucketId::TFRAG_ICE_LEVEL1, ice_tfrags, false, 1); init_bucket_renderer("pris-tex-0", BucketId::PRIS_TEX_LEVEL0); init_bucket_renderer("pris-tex-1", BucketId::PRIS_TEX_LEVEL1); init_bucket_renderer("water-tex-0", BucketId::WATER_TEX_LEVEL0); @@ -117,6 +119,7 @@ void OpenGLRenderer::init_bucket_renderers() { */ void OpenGLRenderer::render(DmaFollower dma, const RenderOptions& settings) { m_profiler.clear(); + m_render_state.reset(); m_render_state.dump_playback = settings.playing_from_dump; m_render_state.ee_main_memory = settings.playing_from_dump ? nullptr : g_ee_main_mem; m_render_state.offset_of_s7 = offset_of_s7(); @@ -173,6 +176,7 @@ void OpenGLRenderer::draw_renderer_selection_window() { ImGui::Begin("Renderer Debug"); ImGui::Checkbox("Sky CPU", &m_render_state.use_sky_cpu); + ImGui::Checkbox("Occlusion Cull", &m_render_state.use_occlusion_culling); for (size_t i = 0; i < m_bucket_renderers.size(); i++) { auto renderer = m_bucket_renderers[i].get(); diff --git a/game/graphics/opengl_renderer/SkyRenderer.cpp b/game/graphics/opengl_renderer/SkyRenderer.cpp index 0f43b54e33..efad20bd0a 100644 --- a/game/graphics/opengl_renderer/SkyRenderer.cpp +++ b/game/graphics/opengl_renderer/SkyRenderer.cpp @@ -21,6 +21,7 @@ SkyBlendHandler::SkyBlendHandler(const std::string& name, BucketId my_id, + int level_id, std::shared_ptr shared_blender, std::shared_ptr shared_blender_cpu) : BucketRenderer(name, my_id), @@ -29,7 +30,8 @@ SkyBlendHandler::SkyBlendHandler(const std::string& name, m_tfrag_renderer(fmt::format("tfrag-{}", name), my_id, {tfrag3::TFragmentTreeKind::TRANS, tfrag3::TFragmentTreeKind::LOWRES_TRANS}, - true) {} + true, + level_id) {} void SkyBlendHandler::handle_sky_copies(DmaFollower& dma, SharedRenderState* render_state, diff --git a/game/graphics/opengl_renderer/SkyRenderer.h b/game/graphics/opengl_renderer/SkyRenderer.h index e142f64732..4b7542e6a2 100644 --- a/game/graphics/opengl_renderer/SkyRenderer.h +++ b/game/graphics/opengl_renderer/SkyRenderer.h @@ -14,6 +14,7 @@ class SkyBlendHandler : public BucketRenderer { public: SkyBlendHandler(const std::string& name, BucketId my_id, + int level_id, std::shared_ptr shared_gpu_blender, std::shared_ptr shared_cpu_blender); void render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) override; diff --git a/game/graphics/opengl_renderer/debug_gui.cpp b/game/graphics/opengl_renderer/debug_gui.cpp index 3b2f0f143b..5bf047ddcc 100644 --- a/game/graphics/opengl_renderer/debug_gui.cpp +++ b/game/graphics/opengl_renderer/debug_gui.cpp @@ -4,14 +4,17 @@ #include "third-party/imgui/imgui.h" void FrameTimeRecorder::finish_frame() { - m_frame_times[m_idx++] = m_timer.getMs(); + m_frame_times[m_idx++] = m_compute_timer.getMs(); if (m_idx == SIZE) { m_idx = 0; } } void FrameTimeRecorder::start_frame() { - m_timer.start(); + m_compute_timer.start(); + float frame_time = m_fps_timer.getSeconds(); + m_last_frame_time = (0.9 * m_last_frame_time) + (0.1 * frame_time); + m_fps_timer.start(); } void FrameTimeRecorder::draw_window(const DmaStats& dma_stats) { @@ -52,6 +55,8 @@ void FrameTimeRecorder::draw_window(const DmaStats& dma_stats) { } else { ImGui::Text("worst: %.1f", worst); } + ImGui::SameLine(); + ImGui::Text("fps-avg: %.1f", 1.f / m_last_frame_time); ImGui::Separator(); ImGui::PlotLines( @@ -105,6 +110,20 @@ void OpenGlDebugGui::draw(const DmaStats& dma_stats) { ImGui::InputText("Dump", m_dump_save_name, 12); ImGui::EndMenu(); } + + if (ImGui::BeginMenu("Frame Rate")) { + ImGui::MenuItem("Enable V-Sync", nullptr, &m_vsync); + ImGui::MenuItem("Disable V-Sync", nullptr, &m_nosync); + ImGui::Separator(); + ImGui::Checkbox("Framelimiter", &framelimiter); + ImGui::InputFloat("Target FPS", &m_target_fps_text); + if (ImGui::MenuItem("Apply")) { + target_fps = m_target_fps_text; + } + ImGui::Separator(); + ImGui::Checkbox("Accurate Lag Mode", &experimental_accurate_lag); + ImGui::EndMenu(); + } } ImGui::EndMainMenuBar(); diff --git a/game/graphics/opengl_renderer/debug_gui.h b/game/graphics/opengl_renderer/debug_gui.h index baa65a4ffc..666dd4c5d7 100644 --- a/game/graphics/opengl_renderer/debug_gui.h +++ b/game/graphics/opengl_renderer/debug_gui.h @@ -27,8 +27,10 @@ class FrameTimeRecorder { private: float m_frame_times[SIZE] = {0}; + float m_last_frame_time = 0; int m_idx = 0; - Timer m_timer; + Timer m_compute_timer; + Timer m_fps_timer; bool m_open = true; bool m_play = true; @@ -59,6 +61,26 @@ class OpenGlDebugGui { return false; } + bool get_nosync_flag() { + if (m_nosync) { + m_nosync = false; + return true; + } + return false; + } + + bool get_vsync_flag() { + if (m_vsync) { + m_vsync = false; + return true; + } + return false; + } + + bool framelimiter = false; + float target_fps = 60.f; + bool experimental_accurate_lag = false; + private: FrameTimeRecorder m_frame_timer; bool m_draw_frame_time = false; @@ -70,4 +92,7 @@ class OpenGlDebugGui { bool m_want_screenshot = false; char m_dump_save_name[256] = "dump.bin"; char m_screenshot_save_name[256] = "screenshot.png"; + bool m_vsync = false; + bool m_nosync = false; + float m_target_fps_text = 60.0; }; \ No newline at end of file diff --git a/game/graphics/opengl_renderer/tfrag/TFragment.cpp b/game/graphics/opengl_renderer/tfrag/TFragment.cpp index e33bd98788..b6c6611668 100644 --- a/game/graphics/opengl_renderer/tfrag/TFragment.cpp +++ b/game/graphics/opengl_renderer/tfrag/TFragment.cpp @@ -18,12 +18,14 @@ bool looks_like_tfrag_init(const DmaFollower& follow) { TFragment::TFragment(const std::string& name, BucketId my_id, const std::vector& trees, - bool child_mode) + bool child_mode, + int level_id) : BucketRenderer(name, my_id), m_child_mode(child_mode), m_direct_renderer(fmt::format("{}.direct", name), my_id, 1024, DirectRenderer::Mode::NORMAL), m_buffered_renderer(my_id), - m_tree_kinds(trees) { + m_tree_kinds(trees), + m_level_id(level_id) { for (auto& buf : m_buffered_data) { for (auto& x : buf.pad) { x = 0xff; @@ -77,6 +79,37 @@ void TFragment::render(DmaFollower& dma, return; } + if (m_my_id == BucketId::TFRAG_LEVEL0) { + DmaTransfer transfers[2]; + + transfers[0] = dma.read_and_advance(); + auto next0 = dma.read_and_advance(); + assert(next0.size_bytes == 0); + transfers[1] = dma.read_and_advance(); + auto next1 = dma.read_and_advance(); + assert(next1.size_bytes == 0); + + for (int i = 0; i < 2; i++) { + if (transfers[i].size_bytes == 128 * 16) { + if (render_state->use_occlusion_culling) { + render_state->occlusion_vis[i].valid = true; + memcpy(render_state->occlusion_vis[i].data, transfers[i].data, 128 * 16); + } + } else { + assert(transfers[i].size_bytes == 16); + } + } + } + + if (dma.current_tag().kind == DmaTag::Kind::CALL) { + // renderer didn't run, let's just get out of here. + for (int i = 0; i < 4; i++) { + dma.read_and_advance(); + } + assert(dma.current_tag_offset() == render_state->next_bucket); + return; + } + if (m_extra_debug) { ImGui::Begin(fmt::format("{} extra", m_name).c_str()); } @@ -108,6 +141,9 @@ void TFragment::render(DmaFollower& dma, memcpy(settings.math_camera.data(), &m_buffered_data[0].pad[TFragDataMem::TFragMatrix0 * 16], 64); settings.tree_idx = 0; + if (render_state->occlusion_vis[m_level_id].valid) { + settings.occlusion_culling = render_state->occlusion_vis[m_level_id].data; + } for (int i = 0; i < 4; i++) { settings.planes[i] = m_pc_port_data.planes[i]; @@ -190,7 +226,7 @@ void TFragment::render(DmaFollower& dma, m_many_level_render.tfrag_level_renderers[i] = std::make_unique(); } if (!m_many_level_render.tie_level_renderers[i]) { - m_many_level_render.tie_level_renderers[i] = std::make_unique("tie", m_my_id); + m_many_level_render.tie_level_renderers[i] = std::make_unique("tie", m_my_id, 0); } m_many_level_render.tfrag_level_renderers[i]->setup_for_level(all_kinds, level_names[i], render_state); diff --git a/game/graphics/opengl_renderer/tfrag/TFragment.h b/game/graphics/opengl_renderer/tfrag/TFragment.h index 7ebb65e34c..95aeb40de9 100644 --- a/game/graphics/opengl_renderer/tfrag/TFragment.h +++ b/game/graphics/opengl_renderer/tfrag/TFragment.h @@ -44,7 +44,8 @@ class TFragment : public BucketRenderer { TFragment(const std::string& name, BucketId my_id, const std::vector& trees, - bool child_mode); + bool child_mode, + int level_id); void render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) override; void draw_debug_window() override; @@ -293,6 +294,7 @@ class TFragment : public BucketRenderer { BufferedRenderer::Builder m_buffered_renderer; Tfrag3 m_tfrag3; std::vector m_tree_kinds; + int m_level_id; struct HackManyLevels { static constexpr int NUM_LEVELS = 23; diff --git a/game/graphics/opengl_renderer/tfrag/Tfrag3.cpp b/game/graphics/opengl_renderer/tfrag/Tfrag3.cpp index 2c8465191f..29539cd8ef 100644 --- a/game/graphics/opengl_renderer/tfrag/Tfrag3.cpp +++ b/game/graphics/opengl_renderer/tfrag/Tfrag3.cpp @@ -260,7 +260,8 @@ void Tfrag3::render_tree(const TfragRenderSettings& settings, glEnable(GL_PRIMITIVE_RESTART); glPrimitiveRestartIndex(UINT32_MAX); - cull_check_all_slow(settings.planes, tree.vis->vis_nodes, m_cache.vis_temp.data()); + cull_check_all_slow(settings.planes, tree.vis->vis_nodes, settings.occlusion_culling, + m_cache.vis_temp.data()); int idx_buffer_ptr = make_index_list_from_vis_string( m_cache.draw_idx_temp.data(), tree.index_list.data(), *tree.draws, m_cache.vis_temp); diff --git a/game/graphics/opengl_renderer/tfrag/Tie3.cpp b/game/graphics/opengl_renderer/tfrag/Tie3.cpp index 66f7def4cc..0297b48be2 100644 --- a/game/graphics/opengl_renderer/tfrag/Tie3.cpp +++ b/game/graphics/opengl_renderer/tfrag/Tie3.cpp @@ -2,7 +2,8 @@ #include "third-party/imgui/imgui.h" -Tie3::Tie3(const std::string& name, BucketId my_id) : BucketRenderer(name, my_id) { +Tie3::Tie3(const std::string& name, BucketId my_id, int level_id) + : BucketRenderer(name, my_id), m_level_id(level_id) { // regardless of how many we use some fixed max // we won't actually interp or upload to gpu the unused ones, but we need a fixed maximum so // indexing works properly. @@ -427,6 +428,10 @@ void Tie3::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfi memcpy(settings.math_camera.data(), m_pc_port_data.camera[0].data(), 64); settings.tree_idx = 0; + if (render_state->occlusion_vis[m_level_id].valid) { + settings.occlusion_culling = render_state->occlusion_vis[m_level_id].data; + } + for (int i = 0; i < 4; i++) { settings.planes[i] = m_pc_port_data.planes[i]; render_state->camera_planes[i] = m_pc_port_data.planes[i]; @@ -630,7 +635,8 @@ void Tie3::render_tree(int idx, tree.perf.index_upload = sizeof(u32) * idx_buffer_ptr; } else { Timer cull_timer; - cull_check_all_slow(settings.planes, tree.vis->vis_nodes, m_cache.vis_temp.data()); + cull_check_all_slow(settings.planes, tree.vis->vis_nodes, settings.occlusion_culling, + m_cache.vis_temp.data()); tree.perf.cull_time.add(cull_timer.getSeconds()); Timer index_timer; diff --git a/game/graphics/opengl_renderer/tfrag/Tie3.h b/game/graphics/opengl_renderer/tfrag/Tie3.h index f28b25e666..6dcfa88c8f 100644 --- a/game/graphics/opengl_renderer/tfrag/Tie3.h +++ b/game/graphics/opengl_renderer/tfrag/Tie3.h @@ -9,7 +9,7 @@ class Tie3 : public BucketRenderer { public: - Tie3(const std::string& name, BucketId my_id); + Tie3(const std::string& name, BucketId my_id, int level_id); void render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) override; void draw_debug_window() override; ~Tie3(); @@ -105,6 +105,8 @@ class Tie3 : public BucketRenderer { float m_wind_multiplier = 1.f; + int m_level_id; + static_assert(sizeof(WindWork) == 84 * 16); enum State : u32 { diff --git a/game/graphics/opengl_renderer/tfrag/tfrag_common.cpp b/game/graphics/opengl_renderer/tfrag/tfrag_common.cpp index 304e1a6eb8..89b3993465 100644 --- a/game/graphics/opengl_renderer/tfrag/tfrag_common.cpp +++ b/game/graphics/opengl_renderer/tfrag/tfrag_common.cpp @@ -300,9 +300,19 @@ bool sphere_in_view_ref(const math::Vector4f& sphere, const math::Vector4f* plan // this isn't super efficient, but we spend so little time here it's not worth it to go faster. void cull_check_all_slow(const math::Vector4f* planes, const std::vector& nodes, + const u8* level_occlusion_string, u8* out) { - for (size_t i = 0; i < nodes.size(); i++) { - out[i] = sphere_in_view_ref(nodes[i].bsphere, planes); + if (level_occlusion_string) { + for (size_t i = 0; i < nodes.size(); i++) { + u16 my_id = nodes[i].my_id; + bool not_occluded = + my_id != 0xffff && level_occlusion_string[my_id / 8] & (1 << (7 - (my_id & 7))); + out[i] = not_occluded && sphere_in_view_ref(nodes[i].bsphere, planes); + } + } else { + for (size_t i = 0; i < nodes.size(); i++) { + out[i] = sphere_in_view_ref(nodes[i].bsphere, planes); + } } } @@ -337,7 +347,7 @@ u32 make_index_list_from_vis_string(std::pair* group_out, int run_start_out = 0; int run_start_in = 0; for (auto& grp : draw.vis_groups) { - bool vis = grp.vis_idx == 0xffffffff || vis_data[grp.vis_idx]; + bool vis = grp.vis_idx_in_pc_bvh == 0xffffffff || vis_data[grp.vis_idx_in_pc_bvh]; if (building_run) { if (vis) { idx_buffer_ptr += grp.num; diff --git a/game/graphics/opengl_renderer/tfrag/tfrag_common.h b/game/graphics/opengl_renderer/tfrag/tfrag_common.h index fcd333bbfe..7babbe1b3e 100644 --- a/game/graphics/opengl_renderer/tfrag/tfrag_common.h +++ b/game/graphics/opengl_renderer/tfrag/tfrag_common.h @@ -10,8 +10,8 @@ struct TfragRenderSettings { int tree_idx; float time_of_day_weights[8] = {0}; math::Vector4f planes[4]; - bool do_culling = false; bool debug_culling = false; + const u8* occlusion_culling = nullptr; // todo occlusion culling string. }; @@ -43,6 +43,7 @@ void interp_time_of_day_fast(const float weights[8], void cull_check_all_slow(const math::Vector4f* planes, const std::vector& nodes, + const u8* level_occlusion_string, u8* out); bool sphere_in_view_ref(const math::Vector4f& sphere, const math::Vector4f* planes); diff --git a/game/graphics/pipelines/opengl.cpp b/game/graphics/pipelines/opengl.cpp index 52ccf106e5..45b6f4e88c 100644 --- a/game/graphics/pipelines/opengl.cpp +++ b/game/graphics/pipelines/opengl.cpp @@ -27,6 +27,7 @@ #include "game/graphics/opengl_renderer/debug_gui.h" #include "common/util/FileUtil.h" #include "common/util/compress.h" +#include "common/util/FrameLimiter.h" namespace { @@ -53,6 +54,10 @@ struct GraphicsData { Serializer loaded_dump; + FrameLimiter frame_limiter; + Timer engine_timer; + double last_engine_time = 1. / 60.; + void serialize(Serializer& ser) { dma_copier.serialize_last_result(ser); ogl_renderer.serialize(ser); @@ -235,10 +240,9 @@ void render_game_frame(int width, int height, int lbox_width, int lbox_height) { std::unique_lock lock(g_gfx_data->dma_mutex); // note: there's a timeout here. If the engine is messed up and not sending us frames, // we still want to run the glfw loop. - got_chain = g_gfx_data->dma_cv.wait_for(lock, std::chrono::milliseconds(20), + got_chain = g_gfx_data->dma_cv.wait_for(lock, std::chrono::milliseconds(50), [=] { return g_gfx_data->has_data_to_render; }); } - // render that chain. if (got_chain) { // we want to serialize before rendering @@ -272,6 +276,7 @@ void render_game_frame(int width, int height, int lbox_width, int lbox_height) { // should be fine to remove this mutex if the game actually waits for vsync to call // send_chain again. but let's be safe for now. std::unique_lock lock(g_gfx_data->dma_mutex); + g_gfx_data->engine_timer.start(); g_gfx_data->has_data_to_render = false; g_gfx_data->sync_cv.notify_all(); } @@ -383,6 +388,7 @@ static void gl_render_display(GfxDisplay* display) { ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); + // window size int width = Gfx::g_global_settings.lbox_w; int height = Gfx::g_global_settings.lbox_h; int fbuf_w, fbuf_h; @@ -396,6 +402,7 @@ static void gl_render_display(GfxDisplay* display) { int lbox_w = (fbuf_w - width) / 2; int lbox_h = (fbuf_h - height) / 2; + // render game! if (g_gfx_data->debug_gui.want_dump_replay()) { // hack: no letterbox for dump frames, for now. render_dump_frame(fbuf_w, fbuf_h, 0, 0); @@ -412,9 +419,21 @@ static void gl_render_display(GfxDisplay* display) { ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + // switch vsync modes, if requested + if (g_gfx_data->debug_gui.get_nosync_flag()) { + glfwSwapInterval(0); + } else if (g_gfx_data->debug_gui.get_vsync_flag()) { + glfwSwapInterval(1); + } + // actual vsync g_gfx_data->debug_gui.finish_frame(); glfwSwapBuffers(window); + if (g_gfx_data->debug_gui.framelimiter) { + g_gfx_data->frame_limiter.run(g_gfx_data->debug_gui.target_fps, + g_gfx_data->debug_gui.experimental_accurate_lag, + g_gfx_data->last_engine_time); + } g_gfx_data->debug_gui.start_frame(); if (display->fullscreen_pending()) { @@ -425,7 +444,6 @@ static void gl_render_display(GfxDisplay* display) { if (!g_gfx_data->debug_gui.want_dump_replay()) { std::unique_lock lock(g_gfx_data->sync_mutex); g_gfx_data->frame_idx++; - g_gfx_data->sync_cv.notify_all(); } @@ -445,11 +463,9 @@ u32 gl_vsync() { if (!g_gfx_data) { return 0; } - std::unique_lock lock(g_gfx_data->sync_mutex); auto init_frame = g_gfx_data->frame_idx_of_input_data; g_gfx_data->sync_cv.wait(lock, [=] { return MasterExit || g_gfx_data->frame_idx > init_frame; }); - return g_gfx_data->frame_idx & 1; } @@ -458,6 +474,7 @@ u32 gl_sync_path() { return 0; } std::unique_lock lock(g_gfx_data->sync_mutex); + g_gfx_data->last_engine_time = g_gfx_data->engine_timer.getSeconds(); if (!g_gfx_data->has_data_to_render) { return 0; } diff --git a/goal_src/engine/gfx/background.gc b/goal_src/engine/gfx/background.gc index a7bc49783a..66534387d0 100644 --- a/goal_src/engine/gfx/background.gc +++ b/goal_src/engine/gfx/background.gc @@ -86,8 +86,6 @@ ;;(spad-vis (the-as (pointer uint128) (+ #x38b0 #x70000000))) (spad-vis (scratchpad-ptr uint128 :offset VISIBLE_LIST_SCRATCHPAD)) ) - ;; TODO this is a hack. - (quad-copy! (-> arg0 vis-bits) (-> arg2 all-visible-list) (/ (+ (-> arg2 visible-list-length) 15) 16)) (b! (not *artist-flip-visible*) cfg-5 :delay (nop!)) (nop!) @@ -132,6 +130,57 @@ (none) ) +(defun add-pc-port-background-data ((dma-buf dma-buffer)) + ;; loop over levels + (dotimes (lev-idx 2) + (let ((lev (-> *level* level lev-idx)) + (dma-start (-> dma-buf base))) + (cond + ((= (-> lev status) 'active) + ;; the level is active. + (let ((packet (the-as dma-packet (-> dma-buf base)))) + (set! (-> packet dma) (new 'static 'dma-tag :id (dma-tag-id cnt) :qwc 128)) + (set! (-> packet vif0) (new 'static 'vif-tag)) + (set! (-> packet vif1) (new 'static 'vif-tag :cmd (vif-cmd pc-port))) + (set! (-> dma-buf base) (the pointer (&+ packet 16))) + ) + (quad-copy! (-> dma-buf base) (-> lev vis-bits) 128) + (&+! (-> dma-buf base) (* 16 128)) + ) + (else + (let ((packet (the-as dma-packet (-> dma-buf base)))) + (set! (-> packet dma) (new 'static 'dma-tag :id (dma-tag-id cnt) :qwc 1)) + (set! (-> packet vif0) (new 'static 'vif-tag)) + (set! (-> packet vif1) (new 'static 'vif-tag :cmd (vif-cmd pc-port))) + (set! (-> dma-buf base) (the pointer (&+ packet 16))) + ) + (set! (-> (the (pointer uint128) (-> dma-buf base))) (the uint128 0)) + (&+! (-> dma-buf base) (* 16 1)) + ) + ) + + + (let ((a3-3 (-> dma-buf base))) + (let ((v1-38 (the-as object (-> dma-buf base)))) + (set! (-> (the-as dma-packet v1-38) dma) (new 'static 'dma-tag :id (dma-tag-id next))) + (set! (-> (the-as dma-packet v1-38) vif0) (new 'static 'vif-tag)) + (set! (-> (the-as dma-packet v1-38) vif1) (new 'static 'vif-tag)) + (set! (-> dma-buf base) (&+ (the-as pointer v1-38) 16)) + ) + (dma-bucket-insert-tag (-> *display* frames (-> *display* on-screen) frame bucket-group) + + (bucket-id tfrag-0) + + + dma-start + (the-as (pointer dma-tag) a3-3) + ) + ) + + ) + ) + ) + (defun finish-background () "Complete the background drawing. This function should run after the background engine has executed. @@ -140,6 +189,12 @@ ;; set up common VU0 stuff for background. (background-upload-vu0) + (#when PC_PORT + (add-pc-port-background-data + (-> *display* frames (-> *display* on-screen) frame global-buf) + ) + ) + ;;;;;;;;;;;;;;;; ;; shrubbery ;;;;;;;;;;;;;;;;