Merge pull request #21944 from hrydgard/more-headless-fixes

More headless fixes, and an adhoc crash fix
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2026-07-19 02:15:21 +02:00
commit 35a2bcf3df
18 files changed
+104 -85

No files matched your search

+1 -1
View File
@@ -433,7 +433,7 @@ void VulkanRenderManager::StopThreads() {
// Not sure this is a sensible check - should be ok even if not.
// _dbg_assert_(steps_.empty());
_dbg_assert_(renderThread_.joinable());
_dbg_assert_(!useRenderThread_ || renderThread_.joinable());
if (useRenderThread_ && renderThread_.joinable()) {
// Tell the render thread to quit when it's done.
VKRRenderThreadTask *task = new VKRRenderThreadTask(VKRRunType::EXIT);
-1
View File
@@ -59,6 +59,5 @@ public:
sleep_ms(5, "exit poll");
}
}
}
};
+3
View File
@@ -250,6 +250,7 @@ enum SystemProperty {
SYSPROP_INSTALLER_NAME, // Useful on Android to check if we were installed from the play store.
};
// NOTE: Unlike requests or UIMessage, these are synchronous!
enum class SystemNotification {
UI,
MEM_VIEW,
@@ -273,6 +274,8 @@ enum class SystemNotification {
AUDIO_MODE_CHANGED,
APP_SWITCH_MODE_CHANGED,
PAD_STATE_CHANGED,
CONFIG_LOADED,
BEFORE_CONFIG_SAVE_ON_EXIT,
};
// I guess it's not super great architecturally to centralize this, since it's not general - but same with a lot of
+1 -1
View File
@@ -1784,7 +1784,7 @@ static int sceNetAdhocMatchingCreate(int mode, int maxnum, int port, int rxbufle
getLocalMac(&localmac);
// Clear Memory
context = {};
*context = {};
// Allocate Receive Buffer
context->rxbuf = (uint8_t*)malloc(rxbuflen);
+1 -1
View File
@@ -2792,7 +2792,7 @@ bool TextureCacheCommon::PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEnt
}
}
// Check early for the degenerate case from Tactics Ogre.
// Check early for the degenerate case from Tactics Ogre (EN version, japanese is worse!).
if (pure3D && plan.levelsToLoad == 2 && gstate.getTextureAddress(0) == gstate.getTextureAddress(1)) {
// Simply treat it as a regular 2D texture, no fake mipmaps or anything.
// levelsToLoad/Create gets set to 1 on the way out from the surrounding if.
+10 -2
View File
@@ -515,8 +515,12 @@ bool TextureCacheVulkan::RunMultipassCompute(VulkanContext *vulkan, VkCommandBuf
stage.useFinalOutputSize ? dstWidth : srcWidth * stage.dstWidthScale,
stage.useFinalOutputSize ? dstHeight : srcHeight * stage.dstHeightScale,
};
VkPipeline pipeline = computeShaderManager_.GetPipeline(multipassCS_[stage.shaderIndex], "Multipass Compute Shader");
if (!pipeline) {
return false;
}
VkDescriptorSet stageSet = computeShaderManager_.GetDescriptorSet(outputView, inputBuffer, inputOffset, inputRange, VK_NULL_HANDLE, 0, 0, inputImage, textureScaleCBuffer_.Buffer(), textureScaleCBuffer_.Size());
vkCmdBindPipeline(cmdInit, VK_PIPELINE_BIND_POINT_COMPUTE, computeShaderManager_.GetPipeline(multipassCS_[stage.shaderIndex]));
vkCmdBindPipeline(cmdInit, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
vkCmdBindDescriptorSets(cmdInit, VK_PIPELINE_BIND_POINT_COMPUTE, computeShaderManager_.GetPipelineLayout(), 0, 1, &stageSet, 0, nullptr);
vkCmdPushConstants(cmdInit, computeShaderManager_.GetPipelineLayout(), VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(params), &params);
vkCmdDispatch(cmdInit, (params.dstWidth + 7) / 8, (params.dstHeight + 7) / 8, 1);
@@ -542,7 +546,11 @@ bool TextureCacheVulkan::ScaleBufferToImage(VulkanContext *vulkan, VkCommandBuff
}
VkDescriptorSet descSet = computeShaderManager_.GetDescriptorSet(dstView, texBuf, bufferOffset, srcSize, VK_NULL_HANDLE, 0, 0, VK_NULL_HANDLE, textureScaleCBuffer_.Buffer(), textureScaleCBuffer_.Size());
struct Params { int x; int y; } params{ srcWidth, srcHeight };
vkCmdBindPipeline(cmdInit, VK_PIPELINE_BIND_POINT_COMPUTE, computeShaderManager_.GetPipeline(singlePassCS_));
VkPipeline pipeline = computeShaderManager_.GetPipeline(singlePassCS_, "Single Pass Compute Shader");
if (!pipeline) {
return false;
}
vkCmdBindPipeline(cmdInit, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
vkCmdBindDescriptorSets(cmdInit, VK_PIPELINE_BIND_POINT_COMPUTE, computeShaderManager_.GetPipelineLayout(), 0, 1, &descSet, 0, nullptr);
vkCmdPushConstants(cmdInit, computeShaderManager_.GetPipelineLayout(), VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(params), &params);
vkCmdDispatch(cmdInit, (srcWidth + 7) / 8, (srcHeight + 7) / 8, 1);
+6 -2
View File
@@ -262,7 +262,7 @@ VkDescriptorSet VulkanComputeShaderManager::GetDescriptorSet(VkImageView image,
return desc;
}
VkPipeline VulkanComputeShaderManager::GetPipeline(VkShaderModule cs) {
VkPipeline VulkanComputeShaderManager::GetPipeline(VkShaderModule cs, const char *tag) {
PipelineKey key{ cs };
VkPipeline pipeline;
if (pipelines_.Get(key, &pipeline)) {
@@ -278,7 +278,11 @@ VkPipeline VulkanComputeShaderManager::GetPipeline(VkShaderModule cs) {
pci.flags = 0;
VkResult res = vkCreateComputePipelines(vulkan_->GetDevice(), pipelineCache_, 1, &pci, nullptr, &pipeline);
_assert_(res == VK_SUCCESS);
if (res != VK_SUCCESS) {
ERROR_LOG(Log::G3D, "Failed to create compute pipeline from shader module (%s)", tag);
pipelines_.Insert(key, VK_NULL_HANDLE);
}
_dbg_assert_msg_(res == VK_SUCCESS, "Failed to create compute pipeline from shader module (%s)", tag);
pipelines_.Insert(key, pipeline);
return pipeline;
+1 -1
View File
@@ -58,7 +58,7 @@ public:
VkDescriptorSet GetDescriptorSet(VkImageView image, VkBuffer buffer, VkDeviceSize offset, VkDeviceSize range, VkBuffer buffer2, VkDeviceSize offset2, VkDeviceSize range2, VkImageView image2, VkBuffer cbuffer, VkDeviceSize cbufferSize);
// This of course caches though.
VkPipeline GetPipeline(VkShaderModule cs);
VkPipeline GetPipeline(VkShaderModule cs, const char *tag);
VkPipelineLayout GetPipelineLayout() const { return pipelineLayout_; }
void ClearPipelines();
+3
View File
@@ -575,6 +575,7 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch
// Note that if we don't have storage permission here, loading the config will
// fail and it will be set to the default. Later, we load again when we get permission.
g_Config.Load();
System_Notify(SystemNotification::CONFIG_LOADED);
#endif
const char *fileToLog = nullptr;
@@ -1708,6 +1709,8 @@ void NativeShutdown() {
g_screenManager = nullptr;
}
System_Notify(SystemNotification::BEFORE_CONFIG_SAVE_ON_EXIT);
g_Config.Save("NativeShutdown");
g_i18nrepo.LogMissingKeys();
+21 -49
View File
@@ -11,15 +11,12 @@
#include "Common/Log.h"
#include "Common/StringUtils.h"
#include "Common/GraphicsContext.h"
#include "Common/TimeUtil.h"
#include "Common/Thread/ThreadUtil.h"
#include "Windows/EmuThread.h"
#include "Windows/W32Util/Misc.h"
#include "Windows/MainWindow.h"
#include "Windows/resource.h"
#include "Core/Reporting.h"
#include "Core/MemMap.h"
#include "Core/Core.h"
#include "Core/System.h"
#include "Core/Config.h"
@@ -43,23 +40,28 @@ static std::thread emuThread;
static std::atomic<EmuThreadState> g_emuThreadState(EmuThreadState::DISABLED);
static std::thread mainThread;
static bool useEmuThread;
static std::string g_error_message;
static bool g_inLoop;
static const char** g_argv;
static int g_argc;
extern std::vector<std::wstring> GetWideCmdLine();
class GraphicsContext;
static GraphicsContext *g_graphicsContext;
void MainThreadFunc();
void MainThreadFunc(int argc, const char *argv[]);
// On most other platforms, we let the "main" thread become the render thread and
// start a separate emu thread from that, if needed. Should probably switch to that
// to make it the same on all platforms.
void MainThread_Start(bool separateEmuThread) {
useEmuThread = separateEmuThread;
mainThread = std::thread(&MainThreadFunc);
void MainThread_Start(int argc, const char *argv[]) {
// This is just to survive WM_USER_RESTART_EMUTHREAD.
if (g_argc == 0) {
g_argc = argc;
g_argv = argv;
}
mainThread = std::thread([]() {
MainThreadFunc(g_argc, g_argv);
});
}
void MainThread_Stop() {
@@ -70,6 +72,9 @@ void MainThread_Stop() {
}
bool MainThread_Ready() {
if (g_inLoop) {
_dbg_assert_(mainThread.joinable());
}
return g_inLoop;
}
@@ -148,41 +153,15 @@ bool CreateGraphicsBackend(std::string *error_message, GraphicsContext **ctx) {
}
}
void MainThreadFunc() {
// We'll start up a separate thread we'll call Emu
SetCurrentThreadName(useEmuThread ? "RenderThread" : "EmuThread");
void MainThreadFunc(int argc, const char *argv[]) {
const bool useEmuThread = g_Config.iGPUBackend == (int)GPUBackend::OPENGL;
const HWND console = GetConsoleWindow();
if (console && g_Config.iConsoleWindowX != -1 && g_Config.iConsoleWindowY != -1) {
SetWindowPos(console, NULL, g_Config.iConsoleWindowX, g_Config.iConsoleWindowY, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}
SetCurrentThreadName(useEmuThread ? "RenderThread" : "EmuThread");
System_SetWindowTitle("");
// We convert command line arguments to UTF-8 immediately.
std::vector<std::wstring> wideArgs = GetWideCmdLine();
std::vector<std::string> argsUTF8;
for (auto& string : wideArgs) {
argsUTF8.push_back(ConvertWStringToUTF8(string));
}
std::vector<const char *> args;
for (auto& string : argsUTF8) {
args.push_back(string.c_str());
}
bool performingRestart = NativeIsRestarting();
NativeInit(static_cast<int>(args.size()), &args[0], "", "", nullptr);
if (g_Config.iGPUBackend == (int)GPUBackend::OPENGL) {
if (!useEmuThread) {
// Okay, we must've switched to OpenGL. Let's flip the emu thread on.
useEmuThread = true;
SetCurrentThreadName("Render");
}
} else if (useEmuThread) {
// We must've failed over from OpenGL, flip the emu thread off.
useEmuThread = false;
SetCurrentThreadName("EmuThread");
}
const bool performingRestart = NativeIsRestarting();
NativeInit(argc, argv, "", "", nullptr);
if (g_Config.sFailedGPUBackends.find("ALL") != std::string::npos) {
Reporting::ReportMessage("Graphics init error: %s", "ALL");
@@ -313,18 +292,11 @@ void MainThreadFunc() {
g_graphicsContext->ThreadEnd();
g_graphicsContext->ShutdownFromRenderThread();
g_graphicsContext->Shutdown();
delete g_graphicsContext;
g_graphicsContext = nullptr;
RECT rc;
if (console && GetWindowRect(console, &rc) && !IsIconic(console)) {
g_Config.iConsoleWindowX = rc.left;
g_Config.iConsoleWindowY = rc.top;
}
NativeShutdown();
PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_UPDATE_UI, 0, 0);
+3 -1
View File
@@ -17,6 +17,8 @@
#pragma once
void MainThread_Start(bool separateRenderThread);
void MainThread_Start(int argc, const char *argv[]);
void MainThread_Stop();
bool MainThread_Ready();
void MainThreadFunc(int argc, const char *argv[]);
+1 -1
View File
@@ -1102,7 +1102,7 @@ namespace MainWindow {
g_InputManager.StopPolling();
MainThread_Stop();
UpdateUIState(UISTATE_MENU);
MainThread_Start(g_Config.iGPUBackend == (int)GPUBackend::OPENGL);
MainThread_Start(0, nullptr);
g_InputManager.BeginPolling();
break;
+35 -5
View File
@@ -594,6 +594,21 @@ void System_Notify(SystemNotification notification) {
case SystemNotification::APP_SWITCH_MODE_CHANGED:
case SystemNotification::UI_STATE_CHANGED:
break;
case SystemNotification::CONFIG_LOADED:
{
// This is too early to move the console into position - hasn't been allocated yet.
break;
}
case SystemNotification::BEFORE_CONFIG_SAVE_ON_EXIT:
{
RECT rc{};
const HWND console = GetConsoleWindow();
if (console && GetWindowRect(console, &rc) && !IsIconic(console)) {
g_Config.iConsoleWindowX = rc.left;
g_Config.iConsoleWindowY = rc.top;
}
break;
}
}
}
@@ -893,7 +908,6 @@ static bool DetectVulkanInExternalProcess() {
std::vector<std::wstring> GetWideCmdLine() {
wchar_t **wargv;
int wargc = -1;
// This is used for the WM_USER_RESTART_EMUTHREAD path.
if (!restartArgs.empty()) {
std::wstring wargs = ConvertUTF8ToWString("PPSSPP " + restartArgs);
wargv = CommandLineToArgvW(wargs.c_str(), &wargc);
@@ -904,7 +918,6 @@ std::vector<std::wstring> GetWideCmdLine() {
std::vector<std::wstring> wideArgs(wargv, wargv + wargc);
LocalFree(wargv);
return wideArgs;
}
@@ -1008,7 +1021,7 @@ static void WinMainCleanup() {
}
int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int iCmdShow) {
std::vector<std::wstring> wideArgs = GetWideCmdLine();
const std::vector<std::wstring> wideArgs = GetWideCmdLine();
// Check for the Vulkan workaround before any serious init.
for (size_t i = 1; i < wideArgs.size(); ++i) {
@@ -1029,7 +1042,6 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin
SetCurrentThreadName("Main");
TimeInit();
WinMainInit();
#ifndef _DEBUG
@@ -1088,6 +1100,7 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin
// if it's not loaded here first.
g_Config.SetSearchPath(GetSysDirectory(DIRECTORY_SYSTEM));
g_Config.Load(configFilename.c_str(), controlsConfigFilename.c_str());
System_Notify(SystemNotification::CONFIG_LOADED);
bool debugLogLevel = false;
@@ -1170,6 +1183,12 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin
// - It should be possible to log to a file without showing the console.
g_logManager.GetConsoleListener()->Init(showLog, 150, 120);
// Move the console into position.
const HWND console = GetConsoleWindow();
if (console && g_Config.iConsoleWindowX != -1 && g_Config.iConsoleWindowY != -1) {
SetWindowPos(console, NULL, g_Config.iConsoleWindowX, g_Config.iConsoleWindowY, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}
if (debugLogLevel) {
g_logManager.SetAllLogLevels(LogLevel::LDEBUG);
}
@@ -1208,7 +1227,18 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin
// Emu thread (and render thread, if any) is always running!
// Only OpenGL uses an externally managed render thread (due to GL's single-threaded context design). Vulkan
// manages its own render thread.
MainThread_Start(g_Config.iGPUBackend == (int)GPUBackend::OPENGL);
// We convert command line arguments to UTF-8.
std::vector<std::string> argsUTF8;
for (const auto &string : wideArgs) {
argsUTF8.push_back(ConvertWStringToUTF8(string));
}
std::vector<const char *> args;
for (const auto &string : argsUTF8) {
args.push_back(string.c_str());
}
MainThread_Start((int)args.size(), args.data());
g_InputManager.BeginPolling();
+1 -1
View File
@@ -317,7 +317,7 @@ std::vector<u32> TranslateDebugBufferToCompare(const GPUDebugBuffer *buffer, u32
outStride = -outStride;
}
// Skip the bottom of the image in the buffer was smaller. Remember, we're flipped.
// Skip the bottom of the image if the buffer was smaller. Remember, we're flipped.
u32 *dst = &data[0];
if (safeH < h) {
dst += (h - safeH) * stride;
+1 -1
View File
@@ -262,7 +262,7 @@ bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, const
}
if (draw) {
draw->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "Backbuffer");
draw->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "BackBuffer");
// Vulkan may get angry if we don't do a final present.
if (gpu) {
gpu->SetCurFramebufferDirty(true);
+2 -5
View File
@@ -180,11 +180,7 @@ bool SDLHeadlessHost::InitGraphics(std::string *error_message, GraphicsContext *
gfx_->ThreadStart();
threadState_ = RenderThreadState::STARTED;
while (threadState_ != RenderThreadState::STOP_REQUESTED) {
if (!gfx_->ThreadFrame(true)) {
break;
}
}
gfx_->ThreadFrameUntilCondition([this] { return threadState_ == RenderThreadState::STOP_REQUESTED; });
threadState_ = RenderThreadState::STOPPING;
gfx_->ThreadEnd();
@@ -201,6 +197,7 @@ bool SDLHeadlessHost::InitGraphics(std::string *error_message, GraphicsContext *
}
void SDLHeadlessHost::ShutdownGraphics() {
threadState_ = RenderThreadState::STOP_REQUESTED;
while (threadState_ != RenderThreadState::STOPPED && threadState_ != RenderThreadState::START_FAILED)
sleep_ms(1, "sdl-stop-poll");
+12 -11
View File
@@ -29,6 +29,7 @@
#include "Common/Log.h"
#include "Common/File/FileUtil.h"
#include "Common/TimeUtil.h"
#include "Common/Thread/ThreadUtil.h"
#include "Core/CoreParameter.h"
#include "Core/System.h"
@@ -81,13 +82,11 @@ bool WindowsHeadlessHost::InitGraphics(std::string *error_message, GraphicsConte
}
WindowsGraphicsContext *graphicsContext = nullptr;
bool needRenderThread = false;
switch (gpuCore_) {
case GPUCORE_GLES:
#if PPSSPP_API(ANY_GL)
case GPUCORE_SOFTWARE:
graphicsContext = new WindowsGLContext();
needRenderThread = true;
break;
#endif
case GPUCORE_DIRECTX11:
@@ -112,8 +111,11 @@ bool WindowsHeadlessHost::InitGraphics(std::string *error_message, GraphicsConte
return false;
}
bool needRenderThread = gpuCore_ == GPUCORE_GLES;
if (needRenderThread) {
std::thread th([&]{
renderThread_ = std::thread([this]{
SetCurrentThreadName("RenderThread");
while (threadState_ == RenderThreadState::IDLE)
sleep_ms(1, "render-thread-idle-poll");
threadState_ = RenderThreadState::STARTING;
@@ -126,18 +128,13 @@ bool WindowsHeadlessHost::InitGraphics(std::string *error_message, GraphicsConte
gfx_->ThreadStart();
threadState_ = RenderThreadState::STARTED;
while (threadState_ != RenderThreadState::STOP_REQUESTED) {
if (!gfx_->ThreadFrame(true)) {
break;
}
}
gfx_->ThreadFrameUntilCondition([this] { return threadState_ == RenderThreadState::STOP_REQUESTED; });
threadState_ = RenderThreadState::STOPPING;
gfx_->ThreadEnd();
gfx_->ShutdownFromRenderThread();
threadState_ = RenderThreadState::STOPPED;
});
th.detach();
}
if (needRenderThread) {
@@ -152,8 +149,12 @@ bool WindowsHeadlessHost::InitGraphics(std::string *error_message, GraphicsConte
}
void WindowsHeadlessHost::ShutdownGraphics() {
while (threadState_ != RenderThreadState::STOPPED && threadState_ != RenderThreadState::IDLE)
sleep_ms(1, "render-thread-stop-poll");
if (renderThread_.joinable()) {
threadState_ = RenderThreadState::STOP_REQUESTED;
while (threadState_ != RenderThreadState::STOPPED && threadState_ != RenderThreadState::IDLE)
sleep_ms(1, "render-thread-stop-poll");
renderThread_.join();
}
gfx_->Shutdown();
delete gfx_;
+2 -2
View File
@@ -25,8 +25,7 @@
#include "Common/CommonWindows.h"
class WindowsHeadlessHost : public HeadlessHost
{
class WindowsHeadlessHost : public HeadlessHost {
public:
bool InitGraphics(std::string *error_message, GraphicsContext **ctx, GPUCore core) override;
void ShutdownGraphics() override;
@@ -51,4 +50,5 @@ protected:
HDC hDC;
HGLRC hRC;
volatile RenderThreadState threadState_ = RenderThreadState::IDLE;
std::thread renderThread_;
};