1#ifndef ENGINE_CLIENT_BACKEND_SDL_H
2#define ENGINE_CLIENT_BACKEND_SDL_H
3
4#include <SDL_video.h>
5
6#include <base/detect.h>
7
8#include <engine/graphics.h>
9
10#include <engine/client/graphics_threaded.h>
11
12#include <engine/client/backend/backend_base.h>
13
14#include <atomic>
15#include <condition_variable>
16#include <cstddef>
17#include <cstdint>
18#include <mutex>
19#include <vector>
20
21#if defined(CONF_PLATFORM_MACOS)
22#include <objc/objc-runtime.h>
23
24class CAutoreleasePool
25{
26private:
27 id m_Pool;
28
29public:
30 CAutoreleasePool()
31 {
32 Class NSAutoreleasePoolClass = (Class)objc_getClass("NSAutoreleasePool");
33 m_Pool = class_createInstance(NSAutoreleasePoolClass, 0);
34 SEL selector = sel_registerName("init");
35 ((id(*)(id, SEL))objc_msgSend)(m_Pool, selector);
36 }
37
38 ~CAutoreleasePool()
39 {
40 SEL selector = sel_registerName("drain");
41 ((id(*)(id, SEL))objc_msgSend)(m_Pool, selector);
42 }
43};
44#endif
45
46// basic threaded backend, abstract, missing init and shutdown functions
47class CGraphicsBackend_Threaded : public IGraphicsBackend
48{
49private:
50 TTranslateFunc m_TranslateFunc;
51 SGfxWarningContainer m_Warning;
52
53public:
54 // constructed on the main thread, the rest of the functions is run on the render thread
55 class ICommandProcessor
56 {
57 public:
58 virtual ~ICommandProcessor() = default;
59 virtual void RunBuffer(CCommandBuffer *pBuffer) = 0;
60
61 virtual const SGfxErrorContainer &GetError() const = 0;
62 virtual void ErroneousCleanup() = 0;
63
64 virtual const SGfxWarningContainer &GetWarning() const = 0;
65 };
66
67 CGraphicsBackend_Threaded(TTranslateFunc &&TranslateFunc);
68
69 void RunBuffer(CCommandBuffer *pBuffer) override;
70 void RunBufferSingleThreadedUnsafe(CCommandBuffer *pBuffer) override;
71 bool IsIdle() const override;
72 void WaitForIdle() override;
73
74 void ProcessError(const SGfxErrorContainer &Error);
75
76protected:
77 void StartProcessor(ICommandProcessor *pProcessor);
78 void StopProcessor();
79
80 bool HasWarning()
81 {
82 return m_Warning.m_WarningType != GFX_WARNING_TYPE_NONE;
83 }
84
85private:
86 ICommandProcessor *m_pProcessor;
87 std::mutex m_BufferSwapMutex;
88 std::condition_variable m_BufferSwapCond;
89 CCommandBuffer *m_pBuffer;
90 std::atomic_bool m_Shutdown;
91 bool m_Started = false;
92 std::atomic_bool m_BufferInProcess;
93 void *m_pThread;
94
95 static void ThreadFunc(void *pUser);
96
97public:
98 bool GetWarning(std::vector<std::string> &WarningStrings) override;
99};
100
101// takes care of implementation independent operations
102class CCommandProcessorFragment_General
103{
104 void Cmd_Nop();
105 void Cmd_Signal(const CCommandBuffer::SCommand_Signal *pCommand);
106
107public:
108 bool RunCommand(const CCommandBuffer::SCommand *pBaseCommand);
109};
110
111struct SBackendCapabilites
112{
113 bool m_TileBuffering;
114 bool m_QuadBuffering;
115 bool m_TextBuffering;
116 bool m_QuadContainerBuffering;
117
118 bool m_MipMapping;
119 bool m_NPOTTextures;
120 bool m_3DTextures;
121 bool m_2DArrayTextures;
122 bool m_2DArrayTexturesAsExtension;
123 bool m_ShaderSupport;
124
125 // use quads as much as possible, even if the user config says otherwise
126 bool m_TrianglesAsQuads;
127
128 int m_ContextMajor;
129 int m_ContextMinor;
130 int m_ContextPatch;
131};
132
133// takes care of sdl related commands
134class CCommandProcessorFragment_SDL
135{
136 // SDL stuff
137 SDL_Window *m_pWindow = nullptr;
138 SDL_GLContext m_GLContext = nullptr;
139
140public:
141 enum
142 {
143 CMD_INIT = CCommandBuffer::CMDGROUP_PLATFORM_SDL,
144 CMD_SHUTDOWN,
145 };
146
147 struct SCommand_Init : public CCommandBuffer::SCommand
148 {
149 SCommand_Init() :
150 SCommand(CMD_INIT) {}
151 SDL_Window *m_pWindow;
152 SDL_GLContext m_GLContext;
153 };
154
155 struct SCommand_Shutdown : public CCommandBuffer::SCommand
156 {
157 SCommand_Shutdown() :
158 SCommand(CMD_SHUTDOWN) {}
159 };
160
161private:
162 void Cmd_Init(const SCommand_Init *pCommand);
163 void Cmd_Shutdown(const SCommand_Shutdown *pCommand);
164 void Cmd_Swap(const CCommandBuffer::SCommand_Swap *pCommand);
165 void Cmd_VSync(const CCommandBuffer::SCommand_VSync *pCommand);
166 void Cmd_WindowCreateNtf(const CCommandBuffer::SCommand_WindowCreateNtf *pCommand);
167 void Cmd_WindowDestroyNtf(const CCommandBuffer::SCommand_WindowDestroyNtf *pCommand);
168
169public:
170 CCommandProcessorFragment_SDL();
171
172 bool RunCommand(const CCommandBuffer::SCommand *pBaseCommand);
173};
174
175// command processor implementation, uses the fragments to combine into one processor
176class CCommandProcessor_SDL_GL : public CGraphicsBackend_Threaded::ICommandProcessor
177{
178 CCommandProcessorFragment_GLBase *m_pGLBackend;
179 CCommandProcessorFragment_SDL m_SDL;
180 CCommandProcessorFragment_General m_General;
181
182 EBackendType m_BackendType;
183
184 SGfxErrorContainer m_Error;
185 SGfxWarningContainer m_Warning;
186
187public:
188 CCommandProcessor_SDL_GL(EBackendType BackendType, int GLMajor, int GLMinor, int GLPatch);
189 virtual ~CCommandProcessor_SDL_GL();
190 void RunBuffer(CCommandBuffer *pBuffer) override;
191
192 const SGfxErrorContainer &GetError() const override;
193 void ErroneousCleanup() override;
194
195 const SGfxWarningContainer &GetWarning() const override;
196
197 void HandleError();
198 void HandleWarning();
199};
200
201static constexpr size_t gs_GpuInfoStringSize = 256;
202
203// graphics backend implemented with SDL and the graphics library @see EBackendType
204class CGraphicsBackend_SDL_GL : public CGraphicsBackend_Threaded
205{
206 SDL_Window *m_pWindow = nullptr;
207 SDL_GLContext m_GLContext = nullptr;
208 ICommandProcessor *m_pProcessor = nullptr;
209 std::atomic<uint64_t> m_TextureMemoryUsage{0};
210 std::atomic<uint64_t> m_BufferMemoryUsage{0};
211 std::atomic<uint64_t> m_StreamMemoryUsage{0};
212 std::atomic<uint64_t> m_StagingMemoryUsage{0};
213
214 TTwGraphicsGpuList m_GpuList;
215
216 TGLBackendReadPresentedImageData m_ReadPresentedImageDataFunc;
217
218 int m_NumScreens;
219
220 SBackendCapabilites m_Capabilites;
221
222 char m_aVendorString[gs_GpuInfoStringSize] = {};
223 char m_aVersionString[gs_GpuInfoStringSize] = {};
224 char m_aRendererString[gs_GpuInfoStringSize] = {};
225
226 EBackendType m_BackendType = BACKEND_TYPE_AUTO;
227
228 char m_aErrorString[256];
229
230 static EBackendType DetectBackend();
231 static void ClampDriverVersion(EBackendType BackendType);
232
233public:
234 CGraphicsBackend_SDL_GL(TTranslateFunc &&TranslateFunc);
235 int Init(const char *pName, int *pScreen, int *pWidth, int *pHeight, int *pRefreshRate, int *pFsaaSamples, int Flags, int *pDesktopWidth, int *pDesktopHeight, int *pCurrentWidth, int *pCurrentHeight, class IStorage *pStorage) override;
236 int Shutdown() override;
237
238 uint64_t TextureMemoryUsage() const override;
239 uint64_t BufferMemoryUsage() const override;
240 uint64_t StreamedMemoryUsage() const override;
241 uint64_t StagingMemoryUsage() const override;
242
243 const TTwGraphicsGpuList &GetGpus() const override;
244
245 int GetNumScreens() const override { return m_NumScreens; }
246 const char *GetScreenName(int Screen) const override;
247
248 void GetVideoModes(CVideoMode *pModes, int MaxModes, int *pNumModes, int HiDPIScale, int MaxWindowWidth, int MaxWindowHeight, int ScreenId) override;
249 void GetCurrentVideoMode(CVideoMode &CurMode, int HiDPIScale, int MaxWindowWidth, int MaxWindowHeight, int ScreenId) override;
250
251 void Minimize() override;
252 void Maximize() override;
253 void SetWindowParams(int FullscreenMode, bool IsBorderless) override;
254 bool SetWindowScreen(int Index) override;
255 bool UpdateDisplayMode(int Index) override;
256 int GetWindowScreen() override;
257 int WindowActive() override;
258 int WindowOpen() override;
259 void SetWindowGrab(bool Grab) override;
260 bool ResizeWindow(int w, int h, int RefreshRate) override;
261 void GetViewportSize(int &w, int &h) override;
262 void NotifyWindow() override;
263
264 void WindowDestroyNtf(uint32_t WindowId) override;
265 void WindowCreateNtf(uint32_t WindowId) override;
266
267 bool GetDriverVersion(EGraphicsDriverAgeType DriverAgeType, int &Major, int &Minor, int &Patch, const char *&pName, EBackendType BackendType) override;
268 bool IsConfigModernAPI() override { return IsModernAPI(BackendType: m_BackendType); }
269 bool UseTrianglesAsQuad() override { return m_Capabilites.m_TrianglesAsQuads; }
270 bool HasTileBuffering() override { return m_Capabilites.m_TileBuffering; }
271 bool HasQuadBuffering() override { return m_Capabilites.m_QuadBuffering; }
272 bool HasTextBuffering() override { return m_Capabilites.m_TextBuffering; }
273 bool HasQuadContainerBuffering() override { return m_Capabilites.m_QuadContainerBuffering; }
274 bool Uses2DTextureArrays() override { return m_Capabilites.m_2DArrayTextures; }
275 bool HasTextureArraysSupport() override { return m_Capabilites.m_2DArrayTextures || m_Capabilites.m_3DTextures; }
276
277 const char *GetErrorString() override
278 {
279 if(m_aErrorString[0] != '\0')
280 return m_aErrorString;
281
282 return NULL;
283 }
284
285 const char *GetVendorString() override
286 {
287 return m_aVendorString;
288 }
289
290 const char *GetVersionString() override
291 {
292 return m_aVersionString;
293 }
294
295 const char *GetRendererString() override
296 {
297 return m_aRendererString;
298 }
299
300 TGLBackendReadPresentedImageData &GetReadPresentedImageDataFuncUnsafe() override;
301
302 bool ShowMessageBox(unsigned Type, const char *pTitle, const char *pMsg) override;
303
304 static bool IsModernAPI(EBackendType BackendType);
305};
306
307#endif // ENGINE_CLIENT_BACKEND_SDL_H
308