1 // Copyright (c) 2017- PPSSPP Project.
2
3 // This program is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, version 2.0 or later versions.
6
7 // This program is distributed in the hope that it will be useful,
8 // but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // GNU General Public License 2.0 for more details.
11
12 // A copy of the GPL 2.0 should have been included with the program.
13 // If not, see http://www.gnu.org/licenses/
14
15 // Official git repository and contact information can be found at
16 // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18 #ifdef SDL
19
20 #include <cstdio>
21 #include <SDL.h>
22
23 #include "headless/SDLHeadlessHost.h"
24 #include "Common/GPU/OpenGL/GLCommon.h"
25 #include "Common/GPU/OpenGL/GLFeatures.h"
26 #include "Common/GPU/thin3d_create.h"
27 #include "Common/GPU/OpenGL/GLRenderManager.h"
28
29 #include "Common/File/VFS/VFS.h"
30 #include "Common/File/VFS/AssetReader.h"
31 #include "Common/Log.h"
32 #include "Common/File/FileUtil.h"
33 #include "Common/GraphicsContext.h"
34 #include "Common/TimeUtil.h"
35
36 #include "Core/CoreParameter.h"
37 #include "Core/ConfigValues.h"
38 #include "Core/System.h"
39 #include "GPU/Common/GPUDebugInterface.h"
40 #include "GPU/GPUState.h"
41
42 const bool WINDOW_VISIBLE = false;
43 const int WINDOW_WIDTH = 480;
44 const int WINDOW_HEIGHT = 272;
45
CreateHiddenWindow()46 SDL_Window *CreateHiddenWindow() {
47 Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS;
48 if (!WINDOW_VISIBLE) {
49 flags |= SDL_WINDOW_HIDDEN;
50 }
51 return SDL_CreateWindow("PPSSPPHeadless", 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, flags);
52 }
53
54 class GLDummyGraphicsContext : public GraphicsContext {
55 public:
GLDummyGraphicsContext()56 GLDummyGraphicsContext() {
57 }
~GLDummyGraphicsContext()58 ~GLDummyGraphicsContext() { delete draw_; }
59
60 bool InitFromRenderThread(std::string *errorMessage) override;
61
ShutdownFromRenderThread()62 void ShutdownFromRenderThread() override {
63 delete draw_;
64 draw_ = nullptr;
65
66 SDL_GL_DeleteContext(glContext_);
67 glContext_ = nullptr;
68 SDL_DestroyWindow(screen_);
69 screen_ = nullptr;
70 }
71
GetDrawContext()72 Draw::DrawContext *GetDrawContext() override {
73 return draw_;
74 }
75
ThreadStart()76 void ThreadStart() override {
77 renderManager_->ThreadStart(draw_);
78 }
79
ThreadFrame()80 bool ThreadFrame() override {
81 return renderManager_->ThreadFrame();
82 }
83
ThreadEnd()84 void ThreadEnd() override {
85 renderManager_->ThreadEnd();
86 }
87
StopThread()88 void StopThread() override {
89 renderManager_->WaitUntilQueueIdle();
90 renderManager_->StopThread();
91 }
92
Shutdown()93 void Shutdown() override {}
Resize()94 void Resize() override {}
SwapInterval(int interval)95 void SwapInterval(int interval) override {}
SwapBuffers()96 void SwapBuffers() override {}
97
98 private:
99 Draw::DrawContext *draw_;
100 GLRenderManager *renderManager_ = nullptr;
101 SDL_Window *screen_;
102 SDL_GLContext glContext_;
103 };
104
LoadNativeAssets()105 void SDLHeadlessHost::LoadNativeAssets() {
106 VFSRegister("", new DirectoryAssetReader(Path("assets")));
107 VFSRegister("", new DirectoryAssetReader(Path("")));
108 VFSRegister("", new DirectoryAssetReader(Path("..")));
109 }
110
InitFromRenderThread(std::string * errorMessage)111 bool GLDummyGraphicsContext::InitFromRenderThread(std::string *errorMessage) {
112 SDL_Init(SDL_INIT_VIDEO);
113
114 // TODO
115 //SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
116 //SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
117 //SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
118
119 SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
120 SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
121 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
122 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
123 SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
124 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
125
126 screen_ = CreateHiddenWindow();
127 glContext_ = SDL_GL_CreateContext(screen_);
128
129 // Ensure that the swap interval is set after context creation (needed for kmsdrm)
130 SDL_GL_SetSwapInterval(0);
131
132 #ifndef USING_GLES2
133 // Some core profile drivers elide certain extensions from GL_EXTENSIONS/etc.
134 // glewExperimental allows us to force GLEW to search for the pointers anyway.
135 if (gl_extensions.IsCoreContext)
136 glewExperimental = true;
137 if (GLEW_OK != glewInit()) {
138 printf("Failed to initialize glew!\n");
139 return false;
140 }
141 // Unfortunately, glew will generate an invalid enum error, ignore.
142 if (gl_extensions.IsCoreContext)
143 glGetError();
144
145 if (GLEW_VERSION_2_0) {
146 printf("OpenGL 2.0 or higher.\n");
147 } else {
148 printf("Sorry, this program requires OpenGL 2.0.\n");
149 return false;
150 }
151 #endif
152
153 CheckGLExtensions();
154 draw_ = Draw::T3DCreateGLContext();
155 renderManager_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
156 renderManager_->SetInflightFrames(g_Config.iInflightFrames);
157 SetGPUBackend(GPUBackend::OPENGL);
158 bool success = draw_->CreatePresets();
159 _assert_(success);
160 renderManager_->SetSwapFunction([&]() {
161 SDL_GL_SwapWindow(screen_);
162 });
163
164 return success;
165 }
166
InitGraphics(std::string * error_message,GraphicsContext ** ctx)167 bool SDLHeadlessHost::InitGraphics(std::string *error_message, GraphicsContext **ctx) {
168 GraphicsContext *graphicsContext = new GLDummyGraphicsContext();
169 *ctx = graphicsContext;
170 gfx_ = graphicsContext;
171
172 std::thread th([&]{
173 while (threadState_ == RenderThreadState::IDLE)
174 sleep_ms(1);
175 threadState_ = RenderThreadState::STARTING;
176
177 std::string err;
178 if (!gfx_->InitFromRenderThread(&err)) {
179 threadState_ = RenderThreadState::START_FAILED;
180 return;
181 }
182 gfx_->ThreadStart();
183 threadState_ = RenderThreadState::STARTED;
184
185 while (threadState_ != RenderThreadState::STOP_REQUESTED) {
186 if (!gfx_->ThreadFrame()) {
187 break;
188 }
189 gfx_->SwapBuffers();
190 }
191
192 threadState_ = RenderThreadState::STOPPING;
193 gfx_->ThreadEnd();
194 gfx_->ShutdownFromRenderThread();
195 threadState_ = RenderThreadState::STOPPED;
196 });
197 th.detach();
198
199 LoadNativeAssets();
200
201 threadState_ = RenderThreadState::START_REQUESTED;
202 while (threadState_ == RenderThreadState::START_REQUESTED || threadState_ == RenderThreadState::STARTING)
203 sleep_ms(1);
204
205 return threadState_ == RenderThreadState::STARTED;
206 }
207
ShutdownGraphics()208 void SDLHeadlessHost::ShutdownGraphics() {
209 gfx_->StopThread();
210 while (threadState_ != RenderThreadState::STOPPED)
211 sleep_ms(1);
212
213 gfx_->Shutdown();
214 delete gfx_;
215 gfx_ = nullptr;
216 }
217
SwapBuffers()218 void SDLHeadlessHost::SwapBuffers() {
219 gfx_->SwapBuffers();
220 }
221
222 #endif
223