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