OpenGL: Now run GL on a secondary thread. Sync issues remain.

This commit is contained in:
Henrik Rydgård 2018-01-16 14:16:56 +01:00
parent e4752a887f
commit af6431986d
9 changed files with 110 additions and 35 deletions

View File

@ -10,10 +10,7 @@ class GraphicsContext {
public:
virtual ~GraphicsContext() {}
// Threaded backends (that need to do init on the final render thread, like GL)
// call this from the render thread. Init() should block until InitFromThread is done.
// Other backends can ignore this.
virtual bool InitFromThread() { return true; }
virtual bool InitFromRenderThread(std::string *errorMessage) { return true; }
virtual void Shutdown() = 0;
virtual void SwapInterval(int interval) = 0;

View File

@ -213,7 +213,6 @@ void FramebufferManagerGLES::CompilePostShader() {
postShaderProgram_ = nullptr;
usePostShader_ = false;
}
glsl_unbind();
}
void FramebufferManagerGLES::Bind2DShader() {

View File

@ -254,7 +254,7 @@ void EmuScreen::bootComplete() {
#if !PPSSPP_PLATFORM(UWP)
if (GetGPUBackend() == GPUBackend::OPENGL) {
const char *renderer = (const char*)glGetString(GL_RENDERER);
const char *renderer = gl_extensions.model;
if (strstr(renderer, "Chainfire3D") != 0) {
osm.Show(sc->T("Chainfire3DWarning", "WARNING: Chainfire3D detected, may cause problems"), 10.0f, 0xFF30a0FF, -1, true);
} else if (strstr(renderer, "GLTools") != 0) {

View File

@ -33,6 +33,9 @@ static std::thread renderThread;
static std::atomic<int> renderThreadReady;
static bool useRenderThread;
static bool renderThreadFailed;
static bool renderThreadSucceeded;
static std::string g_error_message;
extern std::vector<std::wstring> GetWideCmdLine();
@ -85,15 +88,23 @@ bool EmuThread_Ready() {
void RenderThreadFunc() {
setCurrentThreadName("Render");
renderThreadFailed = false;
renderThreadSucceeded = false;
while (!g_graphicsContext) {
sleep_ms(50);
sleep_ms(10);
continue;
}
g_graphicsContext->InitFromThread();
while (true) {
g_graphicsContext->ThreadFrame();
break;
std::string error_message;
if (!g_graphicsContext->InitFromRenderThread(&error_message)) {
g_error_message = error_message;
renderThreadFailed = true;
return;
} else {
renderThreadSucceeded = true;
}
g_graphicsContext->ThreadFrame();
}
void EmuThreadFunc() {
@ -119,10 +130,25 @@ void EmuThreadFunc() {
host->UpdateUI();
GraphicsContext *graphicsContext = nullptr;
std::string error_string;
if (!host->InitGraphics(&error_string, &graphicsContext)) {
bool success = host->InitGraphics(&error_string, &g_graphicsContext);
if (success) {
if (!useRenderThread) {
// This is also the render thread.
success = g_graphicsContext->InitFromRenderThread(&error_string);
} else {
while (!renderThreadFailed && !renderThreadSucceeded) {
sleep_ms(10);
}
success = renderThreadSucceeded;
if (!success) {
error_string = g_error_message;
}
}
}
if (!success) {
// Before anything: are we restarting right now?
if (performingRestart) {
// Okay, switching graphics didn't work out. Probably a driver bug - fallback to restart.
@ -171,7 +197,7 @@ void EmuThreadFunc() {
exit(1);
}
NativeInitGraphics(graphicsContext);
NativeInitGraphics(g_graphicsContext);
NativeResized();
INFO_LOG(BOOT, "Done.");
@ -194,7 +220,7 @@ void EmuThreadFunc() {
// This way they can load a new game.
if (!Core_IsActive())
UpdateUIState(UISTATE_MENU);
Core_Run(graphicsContext);
Core_Run(g_graphicsContext);
}
shutdown:

View File

@ -21,6 +21,7 @@
#include "gfx/gl_common.h"
#include "gfx/gl_debug_log.h"
#include "gfx_es2/gpu_features.h"
#include "thin3d/GLRenderManager.h"
#include "GL/gl.h"
#include "GL/wglew.h"
#include "Core/Config.h"
@ -34,7 +35,8 @@
#include "Windows/GPU/WindowsGLContext.h"
void WindowsGLContext::SwapBuffers() {
::SwapBuffers(hDC);
renderManager_->Swap();
// Used during fullscreen switching to prevent rendering.
if (pauseRequested) {
SetEvent(pauseEvent);
@ -151,10 +153,16 @@ void DebugCallbackARB(GLenum source, GLenum type, GLuint id, GLenum severity,
}
bool WindowsGLContext::Init(HINSTANCE hInst, HWND window, std::string *error_message) {
hInst_ = hInst;
hWnd_ = window;
*error_message = "ok";
return true;
}
bool WindowsGLContext::InitFromRenderThread(std::string *error_message) {
glslang::InitializeProcess();
*error_message = "ok";
hWnd = window;
GLuint PixelFormat;
// TODO: Change to use WGL_ARB_pixel_format instead
@ -179,7 +187,7 @@ bool WindowsGLContext::Init(HINSTANCE hInst, HWND window, std::string *error_mes
0, 0, 0 // Layer Masks Ignored
};
hDC = GetDC(hWnd);
hDC = GetDC(hWnd_);
if (!hDC) {
*error_message = "Failed to get a device context.";
@ -239,7 +247,7 @@ bool WindowsGLContext::Init(HINSTANCE hInst, HWND window, std::string *error_mes
std::wstring title = ConvertUTF8ToWString(err->T("OpenGLDriverError", "OpenGL driver error"));
std::wstring combined = versionDetected + error;
bool yes = IDYES == MessageBox(hWnd, combined.c_str(), title.c_str(), MB_ICONERROR | MB_YESNO);
bool yes = IDYES == MessageBox(hWnd_, combined.c_str(), title.c_str(), MB_ICONERROR | MB_YESNO);
if (yes) {
// Change the config to D3D and restart.
@ -362,8 +370,10 @@ bool WindowsGLContext::Init(HINSTANCE hInst, HWND window, std::string *error_mes
CheckGLExtensions();
draw_ = Draw::T3DCreateGLContext();
renderManager_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
SetGPUBackend(GPUBackend::OPENGL);
bool success = draw_->CreatePresets(); // if we get this far, there will always be a GLSL compiler capable of compiling these.
renderManager_->SetSwapFunction([&]() {::SwapBuffers(hDC); });
assert(success);
CHECK_GL_ERROR_IF_DEBUG();
return true; // Success
@ -393,16 +403,20 @@ void WindowsGLContext::Shutdown() {
hRC = NULL;
}
if (hDC && !ReleaseDC(hWnd,hDC)) {
if (hDC && !ReleaseDC(hWnd_, hDC)) {
DWORD err = GetLastError();
if (err != ERROR_DC_NOT_FOUND) {
MessageBox(NULL, L"Release device context failed.", L"SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
}
hDC = NULL;
}
hWnd = NULL;
hWnd_ = NULL;
glslang::FinalizeProcess();
}
void WindowsGLContext::Resize() {
}
void WindowsGLContext::ThreadFrame() {
renderManager_->ThreadFunc();
}

View File

@ -7,10 +7,14 @@ namespace Draw {
class DrawContext;
}
class GLRenderManager;
class WindowsGLContext : public WindowsGraphicsContext {
public:
bool Init(HINSTANCE hInst, HWND window, std::string *error_message) override;
bool InitFromRenderThread(std::string *errorMessage) override;
void Shutdown() override;
void SwapInterval(int interval) override;
void SwapBuffers() override;
@ -21,13 +25,18 @@ public:
void Resume() override;
void Resize() override;
void ThreadFrame() override;
Draw::DrawContext *GetDrawContext() override { return draw_; }
private:
bool renderThread_;
Draw::DrawContext *draw_;
GLRenderManager *renderManager_;
HINSTANCE hInst_;
HDC hDC; // Private GDI Device Context
HGLRC hRC; // Permanent Rendering Context
HWND hWnd; // Holds Our Window Handle
HWND hWnd_; // Holds Our Window Handle
volatile bool pauseRequested;
volatile bool resumeRequested;
HANDLE pauseEvent;

View File

@ -530,7 +530,9 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin
}
// Emu thread (and render thread, if any) is always running!
EmuThread_Start(false); // g_Config.iGPUBackend == GPU_BACKEND_VULKAN);
// Only OpenGL uses an externally managed render thread (due to GL's single-threaded context design). Vulkan
// manages its own render thread.
EmuThread_Start(g_Config.iGPUBackend == (int)GPUBackend::OPENGL);
InputDevice::BeginPolling();
HACCEL hAccelTable = LoadAccelerators(_hInstance, (LPCTSTR)IDR_ACCELS);

View File

@ -44,7 +44,8 @@ GLRenderManager::GLRenderManager() {
}
if (!useThread_) {
queueRunner_.CreateDeviceObjects();
// The main thread is also the render thread.
ThreadStartup();
}
}
@ -54,16 +55,22 @@ GLRenderManager::~GLRenderManager() {
}
if (!useThread_) {
queueRunner_.DestroyDeviceObjects();
// The main thread is also the render thread.
ThreadEnd();
}
}
void GLRenderManager::ThreadFunc() {
setCurrentThreadName("RenderMan");
int threadFrame = threadInitFrame_;
bool nextFrame = false;
bool firstFrame = true;
void GLRenderManager::ThreadStartup() {
queueRunner_.CreateDeviceObjects();
}
void GLRenderManager::ThreadEnd() {
queueRunner_.DestroyDeviceObjects();
}
void GLRenderManager::ThreadFunc() {
ThreadStartup();
int threadFrame = threadInitFrame_;
while (true) {
{
if (nextFrame) {
@ -344,7 +351,9 @@ void GLRenderManager::EndSubmitFrame(int frame) {
Submit(frame, true);
if (!frameData.skipSwap) {
// glSwapBuffers();
if (swapFunction_) {
swapFunction_();
}
} else {
frameData.skipSwap = false;
}
@ -540,4 +549,4 @@ size_t GLPushBuffer::GetTotalSize() const {
sum += size_ * (buffers_.size() - 1);
sum += offset_;
return sum;
}
}

View File

@ -3,6 +3,7 @@
#include <thread>
#include <unordered_map>
#include <vector>
#include <functional>
#include <set>
#include <string>
#include <mutex>
@ -622,7 +623,20 @@ public:
frameData_[frame].activePushBuffers.erase(iter);
}
void SetSwapFunction(std::function<void()> swapFunction) {
swapFunction_ = swapFunction;
}
void Swap() {
if (!useThread_ && swapFunction_) {
swapFunction_();
}
}
private:
void ThreadStartup();
void ThreadEnd();
void BeginSubmitFrame(int frame);
void EndSubmitFrame(int frame);
void Submit(int frame, bool triggerFence);
@ -668,17 +682,22 @@ private:
// Execution time state
bool run_ = true;
// Thread is managed elsewhere, and should call ThreadFunc.
// Thread is managed elsewhere, and should call ThreadFrame.
std::mutex mutex_;
int threadInitFrame_ = 0;
GLQueueRunner queueRunner_;
bool nextFrame = false;
bool firstFrame = true;
GLDeleter deleter_;
bool useThread_ = false;
bool useThread_ = true;
int curFrame_ = 0;
std::function<void()> swapFunction_;
int targetWidth_ = 0;
int targetHeight_ = 0;
};