static void DVDThread() { Common::SetCurrentThreadName("DVD thread"); while (true) { s_request_queue_expanded.Wait(); if (s_dvd_thread_exiting.IsSet()) return; ReadRequest request; while (s_request_queue.Pop(request)) { FileMonitor::Log(*s_disc, request.partition, request.dvd_offset); std::vector<u8> buffer(request.length); if (!s_disc->Read(request.dvd_offset, request.length, buffer.data(), request.partition)) buffer.resize(0); request.realtime_done_us = Common::Timer::GetTimeUs(); s_result_queue.Push(ReadResult(std::move(request), std::move(buffer))); s_result_queue_expanded.Set(); if (s_dvd_thread_exiting.IsSet()) return; } } }
u32 VideoBackendHardware::Video_AccessEFB(EFBAccessType type, u32 x, u32 y, u32 InputData) { if (s_BackendInitialized && g_ActiveConfig.bEFBAccessEnable) { s_accessEFBArgs.type = type; s_accessEFBArgs.x = x; s_accessEFBArgs.y = y; s_accessEFBArgs.Data = InputData; s_efbAccessRequested.Set(); if (SConfig::GetInstance().m_LocalCoreStartupParameter.bCPUThread) { s_efbAccessReadyEvent.Reset(); if (s_FifoShuttingDown.IsSet()) return 0; s_efbAccessRequested.Set(); s_efbAccessReadyEvent.Wait(); } else VideoFifo_CheckEFBAccess(); return s_AccessEFBResult; } return 0; }
virtual void MainLoop() { while (s_running.IsSet()) { Core::HostDispatchJobs(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); } }
static void VideoFifo_CheckPerfQueryRequest() { if (s_perfQueryRequested.IsSet()) { g_perf_query->FlushResults(); s_perfQueryRequested.Clear(); s_perfQueryReadyEvent.Set(); } }
void VideoFifo_CheckEFBAccess() { if (s_efbAccessRequested.IsSet()) { s_AccessEFBResult = g_renderer->AccessEFB(s_accessEFBArgs.type, s_accessEFBArgs.x, s_accessEFBArgs.y, s_accessEFBArgs.Data); s_efbAccessRequested.Clear(); s_efbAccessReadyEvent.Set(); } }
// Run from the graphics thread (from Fifo.cpp) static void VideoFifo_CheckSwapRequest() { if (g_ActiveConfig.bUseXFB) { if (s_swapRequested.IsSet()) { EFBRectangle rc; Renderer::Swap(s_beginFieldArgs.xfbAddr, s_beginFieldArgs.fbWidth, s_beginFieldArgs.fbHeight,rc); s_swapRequested.Clear(); } } }
// Run from the graphics thread (from Fifo.cpp) void VideoFifo_CheckSwapRequestAt(u32 xfbAddr, u32 fbWidth, u32 fbHeight) { if (g_ActiveConfig.bUseXFB) { if (s_swapRequested.IsSet()) { u32 aLower = xfbAddr; u32 aUpper = xfbAddr + 2 * fbWidth * fbHeight; u32 bLower = s_beginFieldArgs.xfbAddr; u32 bUpper = s_beginFieldArgs.xfbAddr + 2 * s_beginFieldArgs.fbWidth * s_beginFieldArgs.fbHeight; if (addrRangesOverlap(aLower, aUpper, bLower, bUpper)) VideoFifo_CheckSwapRequest(); } } }
void HiresTexture::Prefetch() { Common::SetCurrentThreadName("Prefetcher"); u32 starttime = Common::Timer::GetTimeMs(); for (const auto& entry : s_textureMap) { const std::string& base_filename = entry.first; std::unique_lock<std::mutex> lk(s_textureCacheMutex); auto iter = s_textureCache.find(base_filename); if (iter == s_textureCache.end()) { lk.unlock(); HiresTexture* ptr = Load(base_filename, [](size_t requested_size) { return new u8[requested_size]; }, true); lk.lock(); if (ptr != nullptr) { size_sum.fetch_add(ptr->m_cached_data_size); iter = s_textureCache.insert(iter, std::make_pair(base_filename, std::shared_ptr<HiresTexture>(ptr))); } } if (s_textureCacheAbortLoading.IsSet()) { return; } if (size_sum.load() > max_mem) { g_Config.bCacheHiresTextures = false; OSD::AddMessage(StringFromFormat("Custom Textures prefetching after %.1f MB aborted, not enough RAM available", size_sum / (1024.0 * 1024.0)), 10000); return; } } u32 stoptime = Common::Timer::GetTimeMs(); OSD::AddMessage(StringFromFormat("Custom Textures loaded, %.1f MB in %.1f s", size_sum / (1024.0 * 1024.0), (stoptime - starttime) / 1000.0), 10000); }
u32 VideoBackendHardware::Video_GetQueryResult(PerfQueryType type) { if (!g_perf_query->ShouldEmulate()) { return 0; } // TODO: Is this check sane? if (!g_perf_query->IsFlushed()) { if (SConfig::GetInstance().m_LocalCoreStartupParameter.bCPUThread) { s_perfQueryReadyEvent.Reset(); if (s_FifoShuttingDown.IsSet()) return 0; s_perfQueryRequested.Set(); s_perfQueryReadyEvent.Wait(); } else g_perf_query->FlushResults(); } return g_perf_query->GetQueryResult(type); }
void HostDispatchJobs() { // WARNING: This should only run on the Host Thread. // NOTE: This function is potentially re-entrant. If a job calls // Core::Stop for instance then we'll enter this a second time. std::unique_lock<std::mutex> guard(s_host_jobs_lock); while (!s_host_jobs_queue.empty()) { HostJob job = std::move(s_host_jobs_queue.front()); s_host_jobs_queue.pop(); // NOTE: Memory ordering is important. The booting flag needs to be // checked first because the state transition is: // CORE_UNINITIALIZED: s_is_booting -> s_hardware_initialized // We need to check variables in the same order as the state // transition, otherwise we race and get transient failures. if (!job.run_after_stop && !s_is_booting.IsSet() && !IsRunning()) continue; guard.unlock(); job.job(); guard.lock(); } }
void HiresTexture::Prefetch() { Common::SetCurrentThreadName("Prefetcher"); const size_t total = s_textureMap.size(); size_t count = 0; size_t notification = 10; u32 starttime = Common::Timer::GetTimeMs(); for (const auto& entry : s_textureMap) { const std::string& base_filename = entry.first; std::unique_lock<std::mutex> lk(s_textureCacheMutex); auto iter = s_textureCache.find(base_filename); if (iter == s_textureCache.end()) { lk.unlock(); HiresTexture* ptr = Load(base_filename, [](size_t requested_size) { return new u8[requested_size]; }, true); lk.lock(); if (ptr != nullptr) { size_sum.fetch_add(ptr->m_cached_data_size); iter = s_textureCache.insert( iter, std::make_pair(base_filename, std::shared_ptr<HiresTexture>(ptr))); } } if (s_textureCacheAbortLoading.IsSet()) { if (g_ActiveConfig.bWaitForCacheHiresTextures) { Host_UpdateProgressDialog("", -1, -1); } return; } if (size_sum.load() > max_mem) { Config::SetCurrent(Config::GFX_HIRES_TEXTURES, false); OSD::AddMessage( StringFromFormat( "Custom Textures prefetching after %.1f MB aborted, not enough RAM available", size_sum / (1024.0 * 1024.0)), 10000); if (g_ActiveConfig.bWaitForCacheHiresTextures) { Host_UpdateProgressDialog("", -1, -1); } return; } count++; size_t percent = (count * 100) / total; if (percent >= notification) { if (g_ActiveConfig.bWaitForCacheHiresTextures) { Host_UpdateProgressDialog(GetStringT("Prefetching Custom Textures...").c_str(), static_cast<int>(count), static_cast<int>(total)); } else { OSD::AddMessage(StringFromFormat("Custom Textures prefetching %.1f MB %zu %% finished", size_sum / (1024.0 * 1024.0), percent), 2000); } notification += 10; } } if (g_ActiveConfig.bWaitForCacheHiresTextures) { Host_UpdateProgressDialog("", -1, -1); } u32 stoptime = Common::Timer::GetTimeMs(); OSD::AddMessage(StringFromFormat("Custom Textures loaded, %.1f MB in %.1f s", size_sum / (1024.0 * 1024.0), (stoptime - starttime) / 1000.0), 10000); }
int main(int argc, char* argv[]) { auto parser = CommandLineParse::CreateParser(CommandLineParse::ParserOptions::OmitGUIOptions); optparse::Values& options = CommandLineParse::ParseArguments(parser.get(), argc, argv); std::vector<std::string> args = parser->args(); std::string boot_filename; if (options.is_set("exec")) { boot_filename = static_cast<const char*>(options.get("exec")); } else if (args.size()) { boot_filename = args.front(); args.erase(args.begin()); } else { parser->print_help(); return 0; } std::string user_directory; if (options.is_set("user")) { user_directory = static_cast<const char*>(options.get("user")); } platform = GetPlatform(); if (!platform) { fprintf(stderr, "No platform found\n"); return 1; } UICommon::SetUserDirectory(user_directory); UICommon::Init(); Core::SetOnStoppedCallback([]() { s_running.Clear(); }); platform->Init(); // Shut down cleanly on SIGINT and SIGTERM struct sigaction sa; sa.sa_handler = signal_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESETHAND; sigaction(SIGINT, &sa, nullptr); sigaction(SIGTERM, &sa, nullptr); DolphinAnalytics::Instance()->ReportDolphinStart("nogui"); if (!BootManager::BootCore(BootParameters::GenerateFromFile(boot_filename))) { fprintf(stderr, "Could not boot %s\n", boot_filename.c_str()); return 1; } while (!Core::IsRunning() && s_running.IsSet()) { Core::HostDispatchJobs(); updateMainFrameEvent.Wait(); } if (s_running.IsSet()) platform->MainLoop(); Core::Stop(); Core::Shutdown(); platform->Shutdown(); UICommon::Shutdown(); delete platform; return 0; }
void MainLoop() override { bool fullscreen = SConfig::GetInstance().bFullscreen; int last_window_width = SConfig::GetInstance().iRenderWindowWidth; int last_window_height = SConfig::GetInstance().iRenderWindowHeight; if (fullscreen) { rendererIsFullscreen = X11Utils::ToggleFullscreen(dpy, win); #if defined(HAVE_XRANDR) && HAVE_XRANDR XRRConfig->ToggleDisplayMode(True); #endif } // The actual loop while (s_running.IsSet()) { if (s_shutdown_requested.TestAndClear()) { const auto ios = IOS::HLE::GetIOS(); const auto stm = ios ? ios->GetDeviceByName("/dev/stm/eventhook") : nullptr; if (!s_tried_graceful_shutdown.IsSet() && stm && std::static_pointer_cast<IOS::HLE::Device::STMEventHook>(stm)->HasHookInstalled()) { ProcessorInterface::PowerButton_Tap(); s_tried_graceful_shutdown.Set(); } else { s_running.Clear(); } } XEvent event; KeySym key; for (int num_events = XPending(dpy); num_events > 0; num_events--) { XNextEvent(dpy, &event); switch (event.type) { case KeyPress: key = XLookupKeysym((XKeyEvent*)&event, 0); if (key == XK_Escape) { if (Core::GetState() == Core::State::Running) { if (SConfig::GetInstance().bHideCursor) XUndefineCursor(dpy, win); Core::SetState(Core::State::Paused); } else { if (SConfig::GetInstance().bHideCursor) XDefineCursor(dpy, win, blankCursor); Core::SetState(Core::State::Running); } } else if ((key == XK_Return) && (event.xkey.state & Mod1Mask)) { fullscreen = !fullscreen; X11Utils::ToggleFullscreen(dpy, win); #if defined(HAVE_XRANDR) && HAVE_XRANDR XRRConfig->ToggleDisplayMode(fullscreen); #endif } else if (key >= XK_F1 && key <= XK_F8) { int slot_number = key - XK_F1 + 1; if (event.xkey.state & ShiftMask) State::Save(slot_number); else State::Load(slot_number); } else if (key == XK_F9) Core::SaveScreenShot(); else if (key == XK_F11) State::LoadLastSaved(); else if (key == XK_F12) { if (event.xkey.state & ShiftMask) State::UndoLoadState(); else State::UndoSaveState(); } break; case FocusIn: rendererHasFocus = true; if (SConfig::GetInstance().bHideCursor && Core::GetState() != Core::State::Paused) XDefineCursor(dpy, win, blankCursor); break; case FocusOut: rendererHasFocus = false; if (SConfig::GetInstance().bHideCursor) XUndefineCursor(dpy, win); break; case ClientMessage: if ((unsigned long)event.xclient.data.l[0] == XInternAtom(dpy, "WM_DELETE_WINDOW", False)) s_shutdown_requested.Set(); break; case ConfigureNotify: { if (last_window_width != event.xconfigure.width || last_window_height != event.xconfigure.height) { last_window_width = event.xconfigure.width; last_window_height = event.xconfigure.height; // We call Renderer::ChangeSurface here to indicate the size has changed, // but pass the same window handle. This is needed for the Vulkan backend, // otherwise it cannot tell that the window has been resized on some drivers. if (g_renderer) g_renderer->ChangeSurface(s_window_handle); } } break; } } if (!fullscreen) { Window winDummy; unsigned int borderDummy, depthDummy; XGetGeometry(dpy, win, &winDummy, &SConfig::GetInstance().iRenderWindowXPos, &SConfig::GetInstance().iRenderWindowYPos, (unsigned int*)&SConfig::GetInstance().iRenderWindowWidth, (unsigned int*)&SConfig::GetInstance().iRenderWindowHeight, &borderDummy, &depthDummy); rendererIsFullscreen = false; } Core::HostDispatchJobs(); usleep(100000); } }
// Description: Main FIFO update loop // Purpose: Keep the Core HW updated about the CPU-GPU distance void RunGpuLoop() { AsyncRequests::GetInstance()->SetEnable(true); AsyncRequests::GetInstance()->SetPassthrough(false); s_gpu_mainloop.Run( [] { const SConfig& param = SConfig::GetInstance(); g_video_backend->PeekMessages(); // Do nothing while paused if (!s_emu_running_state.IsSet()) return; if (s_use_deterministic_gpu_thread) { AsyncRequests::GetInstance()->PullEvents(); // All the fifo/CP stuff is on the CPU. We just need to run the opcode decoder. u8* seen_ptr = s_video_buffer_seen_ptr; u8* write_ptr = s_video_buffer_write_ptr; // See comment in SyncGPU if (write_ptr > seen_ptr) { s_video_buffer_read_ptr = OpcodeDecoder::Run(DataReader(s_video_buffer_read_ptr, write_ptr), nullptr, false); s_video_buffer_seen_ptr = write_ptr; } } else { SCPFifoStruct& fifo = CommandProcessor::fifo; AsyncRequests::GetInstance()->PullEvents(); CommandProcessor::SetCPStatusFromGPU(); // check if we are able to run this buffer while (!CommandProcessor::IsInterruptWaiting() && fifo.bFF_GPReadEnable && fifo.CPReadWriteDistance && !AtBreakpoint()) { if (param.bSyncGPU && s_sync_ticks.load() < param.iSyncGpuMinDistance) break; u32 cyclesExecuted = 0; u32 readPtr = fifo.CPReadPointer; ReadDataFromFifo(readPtr); if (readPtr == fifo.CPEnd) readPtr = fifo.CPBase; else readPtr += 32; _assert_msg_(COMMANDPROCESSOR, (s32)fifo.CPReadWriteDistance - 32 >= 0, "Negative fifo.CPReadWriteDistance = %i in FIFO Loop !\nThat can produce " "instability in the game. Please report it.", fifo.CPReadWriteDistance - 32); u8* write_ptr = s_video_buffer_write_ptr; s_video_buffer_read_ptr = OpcodeDecoder::Run( DataReader(s_video_buffer_read_ptr, write_ptr), &cyclesExecuted, false); Common::AtomicStore(fifo.CPReadPointer, readPtr); Common::AtomicAdd(fifo.CPReadWriteDistance, -32); if ((write_ptr - s_video_buffer_read_ptr) == 0) Common::AtomicStore(fifo.SafeCPReadPointer, fifo.CPReadPointer); CommandProcessor::SetCPStatusFromGPU(); if (param.bSyncGPU) { cyclesExecuted = (int)(cyclesExecuted / param.fSyncGpuOverclock); int old = s_sync_ticks.fetch_sub(cyclesExecuted); if (old >= param.iSyncGpuMaxDistance && old - (int)cyclesExecuted < param.iSyncGpuMaxDistance) s_sync_wakeup_event.Set(); } // This call is pretty important in DualCore mode and must be called in the FIFO Loop. // If we don't, s_swapRequested or s_efbAccessRequested won't be set to false // leading the CPU thread to wait in Video_BeginField or Video_AccessEFB thus slowing // things down. AsyncRequests::GetInstance()->PullEvents(); } // fast skip remaining GPU time if fifo is empty if (s_sync_ticks.load() > 0) { int old = s_sync_ticks.exchange(0); if (old >= param.iSyncGpuMaxDistance) s_sync_wakeup_event.Set(); } // The fifo is empty and it's unlikely we will get any more work in the near future. // Make sure VertexManager finishes drawing any primitives it has stored in it's buffer. g_vertex_manager->Flush(); } }, 100); AsyncRequests::GetInstance()->SetEnable(false); AsyncRequests::GetInstance()->SetPassthrough(true); }
void HiresTexture::Prefetch() { Common::SetCurrentThreadName("Prefetcher"); size_t size_sum = 0; size_t sys_mem = MemPhysical(); size_t recommended_min_mem = 2 * size_t(1024 * 1024 * 1024); // keep 2GB memory for system stability if system RAM is 4GB+ - use half of memory in other cases size_t max_mem = (sys_mem / 2 < recommended_min_mem) ? (sys_mem / 2) : (sys_mem - recommended_min_mem); u32 starttime = Common::Timer::GetTimeMs(); for (const auto& entry : s_textureMap) { const std::string& base_filename = entry.first; if (base_filename.find("_mip") == std::string::npos) { { // try to get this mutex first, so the video thread is allow to get the real mutex faster std::unique_lock<std::mutex> lk(s_textureCacheAquireMutex); } std::unique_lock<std::mutex> lk(s_textureCacheMutex); auto iter = s_textureCache.find(base_filename); if (iter == s_textureCache.end()) { // unlock while loading a texture. This may result in a race condition where we'll load a texture twice, // but it reduces the stuttering a lot. Notice: The loading library _must_ be thread safe now. // But bad luck, SOIL isn't, so TODO: remove SOIL usage here and use libpng directly // Also TODO: remove s_textureCacheAquireMutex afterwards. It won't be needed as the main mutex will be locked rarely //lk.unlock(); std::unique_ptr<HiresTexture> texture = Load(base_filename, 0, 0); //lk.lock(); if (texture) { std::shared_ptr<HiresTexture> ptr(std::move(texture)); iter = s_textureCache.insert(iter, std::make_pair(base_filename, ptr)); } } if (iter != s_textureCache.end()) { for (const Level& l : iter->second->m_levels) { size_sum += l.data_size; } } } if (s_textureCacheAbortLoading.IsSet()) { return; } if (size_sum > max_mem) { g_Config.bCacheHiresTextures = false; OSD::AddMessage(StringFromFormat("Custom Textures prefetching after %.1f MB aborted, not enough RAM available", size_sum / (1024.0 * 1024.0)), 10000); return; } } u32 stoptime = Common::Timer::GetTimeMs(); OSD::AddMessage(StringFromFormat("Custom Textures loaded, %.1f MB in %.1f s", size_sum / (1024.0 * 1024.0), (stoptime - starttime) / 1000.0), 10000); }