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