diff --git a/core/deps/ggpo/include/ggponet.h b/core/deps/ggpo/include/ggponet.h index d25516d4d..bd82894ce 100644 --- a/core/deps/ggpo/include/ggponet.h +++ b/core/deps/ggpo/include/ggponet.h @@ -303,6 +303,9 @@ typedef struct GGPONetworkStats { int local_frames_behind; int remote_frames_behind; } timesync; + struct { + int predicted_frames; + } sync; } GGPONetworkStats; /* diff --git a/core/deps/ggpo/lib/ggpo/backends/p2p.cpp b/core/deps/ggpo/lib/ggpo/backends/p2p.cpp index c1c71b3b2..8c366d2cd 100644 --- a/core/deps/ggpo/lib/ggpo/backends/p2p.cpp +++ b/core/deps/ggpo/lib/ggpo/backends/p2p.cpp @@ -529,6 +529,7 @@ Peer2PeerBackend::GetNetworkStats(GGPONetworkStats *stats, GGPOPlayerHandle play memset(stats, 0, sizeof *stats); _endpoints[queue].GetNetworkStats(stats); + stats->sync.predicted_frames = _sync.GetPredictedFrames(); return GGPO_OK; } diff --git a/core/deps/ggpo/lib/ggpo/main.cpp b/core/deps/ggpo/lib/ggpo/main.cpp index 4da54c7e6..88dd4fa7e 100644 --- a/core/deps/ggpo/lib/ggpo/main.cpp +++ b/core/deps/ggpo/lib/ggpo/main.cpp @@ -15,6 +15,10 @@ struct Init { Init() { srand(Platform::GetCurrentTimeMS() + Platform::GetProcessID()); +#ifdef _WIN32 + WSADATA wsaData; + WSAStartup(MAKEWORD(2, 0), &wsaData); +#endif } }; static Init init; diff --git a/core/deps/ggpo/lib/ggpo/platform_linux.h b/core/deps/ggpo/lib/ggpo/platform_linux.h index 9d1c517c8..426b5b473 100644 --- a/core/deps/ggpo/lib/ggpo/platform_linux.h +++ b/core/deps/ggpo/lib/ggpo/platform_linux.h @@ -21,7 +21,11 @@ #include #include #include +#ifdef __SWITCH__ +#include "nswitch.h" +#else #include +#endif #include #include diff --git a/core/deps/ggpo/lib/ggpo/sync.h b/core/deps/ggpo/lib/ggpo/sync.h index 465ee248b..f9523cbd0 100644 --- a/core/deps/ggpo/lib/ggpo/sync.h +++ b/core/deps/ggpo/lib/ggpo/sync.h @@ -57,6 +57,7 @@ public: int GetFrameCount() { return _framecount; } bool InRollback() { return _rollingback; } + int GetPredictedFrames() { return _framecount - _last_confirmed_frame; } bool GetEvent(Event &e); diff --git a/core/hw/pvr/Renderer_if.cpp b/core/hw/pvr/Renderer_if.cpp index af4196da8..12ef4e2fb 100644 --- a/core/hw/pvr/Renderer_if.cpp +++ b/core/hw/pvr/Renderer_if.cpp @@ -27,6 +27,7 @@ cResetEvent rs, re; static bool do_swap; std::mutex swap_mutex; u32 fb_w_cur = 1; +static cResetEvent vramRollback; // direct framebuffer write detection static bool render_called = false; @@ -197,6 +198,7 @@ static bool rend_frame(TA_context* ctx) if (!proc || (!ctx->rend.isRTT && !ctx->rend.isRenderFramebuffer)) // If rendering to texture, continue locking until the frame is rendered re.Set(); + rend_allow_rollback(); return proc && renderer->Render(); } @@ -437,6 +439,7 @@ void rend_cancel_emu_wait() { FinishRender(NULL); re.Set(); + rend_allow_rollback(); } } @@ -470,3 +473,18 @@ void rend_swap_frame(u32 fb_r_sof1) } swap_mutex.unlock(); } + +void rend_disable_rollback() +{ + vramRollback.Reset(); +} + +void rend_allow_rollback() +{ + vramRollback.Set(); +} + +void rend_start_rollback() +{ + vramRollback.Wait(); +} diff --git a/core/hw/pvr/Renderer_if.h b/core/hw/pvr/Renderer_if.h index 86007f5e3..0ffc9b9c5 100644 --- a/core/hw/pvr/Renderer_if.h +++ b/core/hw/pvr/Renderer_if.h @@ -15,6 +15,9 @@ bool rend_single_frame(const bool& enabled); void rend_swap_frame(u32 fb_r_sof1); void rend_set_fb_write_addr(u32 fb_w_sof1); void rend_reset(); +void rend_disable_rollback(); +void rend_start_rollback(); +void rend_allow_rollback(); /////// extern TA_context* _pvrrc; diff --git a/core/hw/pvr/ta_ctx.cpp b/core/hw/pvr/ta_ctx.cpp index 15d7d3e45..9411bb4ca 100644 --- a/core/hw/pvr/ta_ctx.cpp +++ b/core/hw/pvr/ta_ctx.cpp @@ -4,6 +4,7 @@ #if defined(__SWITCH__) #include #endif +#include "Renderer_if.h" extern u32 fskip; extern u32 FrameCount; @@ -101,7 +102,8 @@ bool QueueRender(TA_context* ctx) fskip++; return false; } - + // disable net rollbacks until the render thread has processed the frame + rend_disable_rollback(); frame_finished.Reset(); mtx_rqueue.lock(); TA_context* old = rqueue; diff --git a/core/network/ggpo.cpp b/core/network/ggpo.cpp index ab34411c5..e90fab49d 100644 --- a/core/network/ggpo.cpp +++ b/core/network/ggpo.cpp @@ -30,6 +30,7 @@ #include #include #include +#include "imgui/imgui.h" //#define SYNC_TEST 1 @@ -59,6 +60,8 @@ struct MemPages static std::unordered_map deltaStates; static int lastSavedFrame = -1; +static int timesyncOccurred; + /* * begin_game callback - This callback has been deprecated. You must * implement it, but should ignore the 'game' parameter. @@ -99,6 +102,7 @@ static bool on_event(GGPOEvent *info) break; case GGPO_EVENTCODE_TIMESYNC: INFO_LOG(NETWORK, "Timesync: %d frames ahead", info->u.timesync.frames_ahead); + timesyncOccurred += 5; std::this_thread::sleep_for(std::chrono::milliseconds(1000 * info->u.timesync.frames_ahead / 60)); // FIXME assumes 60 FPS break; case GGPO_EVENTCODE_CONNECTION_INTERRUPTED: @@ -149,6 +153,7 @@ static bool load_game_state(unsigned char *buffer, int len) { INFO_LOG(NETWORK, "load_game_state"); + rend_start_rollback(); // FIXME will invalidate too much stuff: palette/fog textures, maple stuff // FIXME dynarecs int frame = *(u32 *)buffer; @@ -172,6 +177,7 @@ static bool load_game_state(unsigned char *buffer, int len) DEBUG_LOG(NETWORK, "Restored frame %d pages: %d ram, %d vram, %d aica ram", f, (u32)pages.ram.size(), (u32)pages.vram.size(), (u32)pages.aram.size()); } + rend_allow_rollback(); // ggpo might load another state right after this one memwatch::reset(); memwatch::protect(); return true; @@ -468,6 +474,59 @@ std::future startNetwork() }); } +void displayStats() +{ + if (!active()) + return; + GGPONetworkStats stats; + ggpo_get_network_stats(ggpoSession, remotePlayer, &stats); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0); + ImGui::SetNextWindowPos(ImVec2(10, 10)); + ImGui::SetNextWindowSize(ImVec2(95 * scaling, 0)); + ImGui::SetNextWindowBgAlpha(0.7f); + ImGui::Begin("", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration); + + ImGui::Columns(2, "cols", false); + ImGui::SetColumnWidth(0, 55 * scaling); + ImGui::SetColumnWidth(1, 40 * scaling); + ImGui::Text("Send Q"); + ImGui::NextColumn(); + ImGui::Text("%d", stats.network.send_queue_len); + ImGui::NextColumn(); + + ImGui::Text("Ping"); + ImGui::NextColumn(); + ImGui::Text("%d", stats.network.ping); + ImGui::NextColumn(); + + if (stats.sync.predicted_frames == 7) + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 0, 0, 1)); + ImGui::Text("Predicted"); + ImGui::NextColumn(); + ImGui::Text("%d", stats.sync.predicted_frames); + ImGui::NextColumn(); + if (stats.sync.predicted_frames == 7) + ImGui::PopStyleColor(); + + int timesync = timesyncOccurred; + if (timesync > 0) + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 0, 0, 1)); + ImGui::Text("Behind"); + ImGui::NextColumn(); + ImGui::Text("%d", stats.timesync.local_frames_behind); + if (timesync > 0) + { + ImGui::PopStyleColor(); + timesyncOccurred--; + } + ImGui::Columns(1); + + ImGui::End(); + ImGui::PopStyleVar(2); +} + } #else // LIBRETRO @@ -496,5 +555,8 @@ std::future startNetwork() { return std::async(std::launch::deferred, []{ return false; });; } +void displayStats() { +} + } #endif diff --git a/core/network/ggpo.h b/core/network/ggpo.h index c0f30f1da..7fad6ece2 100644 --- a/core/network/ggpo.h +++ b/core/network/ggpo.h @@ -28,5 +28,6 @@ void stopSession(); void getInput(u32 out_kcode[4]); void nextFrame(); bool active(); +void displayStats(); } diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index 711d56567..80720a024 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -2242,7 +2242,7 @@ void gui_display_osd() if (message.empty()) message = getFPSNotification(); - if (!message.empty() || config::FloatVMUs || crosshairsNeeded()) + if (!message.empty() || config::FloatVMUs || crosshairsNeeded() || ggpo::active()) { ImGui_Impl_NewFrame(); ImGui::NewFrame(); @@ -2263,6 +2263,7 @@ void gui_display_osd() if (config::FloatVMUs) display_vmus(); // gui_plot_render_time(screen_width, screen_height); + ggpo::displayStats(); ImGui::Render(); ImGui_impl_RenderDrawData(ImGui::GetDrawData());