summary refs log tree commit diff
path: root/src/Window_SDL3.c
diff options
context:
space:
mode:
authorWlodekM <[email protected]>2024-06-16 10:35:45 +0300
committerWlodekM <[email protected]>2024-06-16 10:35:45 +0300
commitabef6da56913f1c55528103e60a50451a39628b1 (patch)
treeb3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/Window_SDL3.c
initial commit
Diffstat (limited to 'src/Window_SDL3.c')
-rw-r--r--src/Window_SDL3.c541
1 files changed, 541 insertions, 0 deletions
diff --git a/src/Window_SDL3.c b/src/Window_SDL3.c
new file mode 100644
index 0000000..25f7ea0
--- /dev/null
+++ b/src/Window_SDL3.c
@@ -0,0 +1,541 @@
+#include "Core.h"
+#if CC_WIN_BACKEND == CC_WIN_BACKEND_SDL3
+#include "_WindowBase.h"
+#include "Graphics.h"
+#include "String.h"
+#include "Funcs.h"
+#include "Bitmap.h"
+#include "Errors.h"
+#include <SDL3/SDL.h>
+static SDL_Window* win_handle;
+static Uint32 dlg_event;
+
+static void RefreshWindowBounds(void) {
+	SDL_GetWindowSize(win_handle, &Window_Main.Width, &Window_Main.Height);
+}
+
+static void Window_SDLFail(const char* place) {
+	char strBuffer[256];
+	cc_string str;
+	String_InitArray_NT(str, strBuffer);
+
+	String_Format2(&str, "Error when %c: %c", place, SDL_GetError());
+	str.buffer[str.length] = '\0';
+	Logger_Abort(str.buffer);
+}
+
+void Window_PreInit(void) {
+	SDL_Init(SDL_INIT_VIDEO);
+	#ifdef CC_BUILD_FLATPAK
+	SDL_SetHint(SDL_HINT_APP_ID, "net.classicube.flatpak.client");
+	#endif
+}
+
+void Window_Init(void) {
+	int displayID = SDL_GetPrimaryDisplay();
+	Input.Sources = INPUT_SOURCE_NORMAL;
+	
+	const SDL_DisplayMode* mode = SDL_GetDesktopDisplayMode(displayID);
+	dlg_event = SDL_RegisterEvents(1);
+
+	DisplayInfo.Width  = mode->w;
+	DisplayInfo.Height = mode->h;
+	DisplayInfo.Depth  = SDL_BITSPERPIXEL(mode->format);
+	DisplayInfo.ScaleX = 1;
+	DisplayInfo.ScaleY = 1;
+}
+
+void Window_Free(void) { }
+
+#ifdef CC_BUILD_ICON
+/* See misc/sdl/sdl_icon_gen.cs for how to generate this file */
+#include "../misc/sdl/CCIcon_SDL.h"
+
+static void ApplyIcon(void) {
+	SDL_Surface* surface = SDL_CreateSurfaceFrom(CCIcon_Data, CCIcon_Width, CCIcon_Height, 
+												CCIcon_Pitch, SDL_PIXELFORMAT_BGRA8888);
+	SDL_SetWindowIcon(win_handle, surface);
+}
+#else
+static void ApplyIcon(void) { }
+#endif
+
+static void DoCreateWindow(int width, int height, int flags) {
+	SDL_PropertiesID props = SDL_CreateProperties();
+	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED);
+	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED);
+	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER,  width);
+	SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, height);
+	SDL_SetNumberProperty(props, "flags", flags | SDL_WINDOW_RESIZABLE);
+
+	win_handle = SDL_CreateWindowWithProperties(props);
+	if (!win_handle) Window_SDLFail("creating window");
+	SDL_DestroyProperties(props);
+
+	RefreshWindowBounds();
+	Window_Main.Exists = true;
+	Window_Main.Handle = win_handle;
+	ApplyIcon();
+	/* TODO grab using SDL_SetWindowGrab? seems to be unnecessary on Linux at least */
+}
+
+void Window_Create2D(int width, int height) { DoCreateWindow(width, height, 0); }
+#if (CC_GFX_BACKEND & CC_GFX_BACKEND_GL_MASK)
+void Window_Create3D(int width, int height) { DoCreateWindow(width, height, SDL_WINDOW_OPENGL); }
+#else
+void Window_Create3D(int width, int height) { DoCreateWindow(width, height, 0); }
+#endif
+
+void Window_SetTitle(const cc_string* title) {
+	char str[NATIVE_STR_LEN];
+	String_EncodeUtf8(str, title);
+	SDL_SetWindowTitle(win_handle, str);
+}
+
+void Clipboard_GetText(cc_string* value) {
+	char* ptr = SDL_GetClipboardText();
+	if (!ptr) return;
+
+	int len = String_Length(ptr);
+	String_AppendUtf8(value, ptr, len);
+	SDL_free(ptr);
+}
+
+void Clipboard_SetText(const cc_string* value) {
+	char str[NATIVE_STR_LEN];
+	String_EncodeUtf8(str, value);
+	SDL_SetClipboardText(str);
+}
+
+int Window_GetWindowState(void) {
+	Uint32 flags = SDL_GetWindowFlags(win_handle);
+
+	if (flags & SDL_WINDOW_MINIMIZED)  return WINDOW_STATE_MINIMISED;
+	if (flags & SDL_WINDOW_FULLSCREEN) return WINDOW_STATE_FULLSCREEN;
+	return WINDOW_STATE_NORMAL;
+}
+
+cc_result Window_EnterFullscreen(void) {
+	return SDL_SetWindowFullscreen(win_handle, true);
+}
+cc_result Window_ExitFullscreen(void) { 
+	return SDL_SetWindowFullscreen(win_handle, false);
+}
+
+int Window_IsObscured(void) {
+	Uint32 flags = SDL_GetWindowFlags(win_handle);
+	return flags & SDL_WINDOW_OCCLUDED;
+}
+
+void Window_Show(void) {
+	 SDL_ShowWindow(win_handle); 
+}
+
+void Window_SetSize(int width, int height) {
+	SDL_SetWindowSize(win_handle, width, height);
+}
+
+void Window_RequestClose(void) {
+	SDL_Event e;
+	e.type = SDL_EVENT_QUIT;
+	SDL_PushEvent(&e);
+}
+
+static int MapNativeKey(SDL_Keycode k) {
+	if (k >= SDLK_0   && k <= SDLK_9)   { return '0'       + (k - SDLK_0); }
+	if (k >= SDLK_a   && k <= SDLK_z)   { return 'A'       + (k - SDLK_a); }
+	if (k >= SDLK_F1  && k <= SDLK_F12) { return CCKEY_F1  + (k - SDLK_F1); }
+	if (k >= SDLK_F13 && k <= SDLK_F24) { return CCKEY_F13 + (k - SDLK_F13); }
+	/* SDLK_KP_0 isn't before SDLK_KP_1 */
+	if (k >= SDLK_KP_1 && k <= SDLK_KP_9) { return CCKEY_KP1 + (k - SDLK_KP_1); }
+
+	switch (k) {
+		case SDLK_RETURN: return CCKEY_ENTER;
+		case SDLK_ESCAPE: return CCKEY_ESCAPE;
+		case SDLK_BACKSPACE: return CCKEY_BACKSPACE;
+		case SDLK_TAB:    return CCKEY_TAB;
+		case SDLK_SPACE:  return CCKEY_SPACE;
+		case SDLK_APOSTROPHE: return CCKEY_QUOTE;
+		case SDLK_EQUALS: return CCKEY_EQUALS;
+		case SDLK_COMMA:  return CCKEY_COMMA;
+		case SDLK_MINUS:  return CCKEY_MINUS;
+		case SDLK_PERIOD: return CCKEY_PERIOD;
+		case SDLK_SLASH:  return CCKEY_SLASH;
+		case SDLK_SEMICOLON:    return CCKEY_SEMICOLON;
+		case SDLK_LEFTBRACKET:  return CCKEY_LBRACKET;
+		case SDLK_BACKSLASH:    return CCKEY_BACKSLASH;
+		case SDLK_RIGHTBRACKET: return CCKEY_RBRACKET;
+		case SDLK_GRAVE:        return CCKEY_TILDE;
+		case SDLK_CAPSLOCK:     return CCKEY_CAPSLOCK;
+		case SDLK_PRINTSCREEN: return CCKEY_PRINTSCREEN;
+		case SDLK_SCROLLLOCK:  return CCKEY_SCROLLLOCK;
+		case SDLK_PAUSE:       return CCKEY_PAUSE;
+		case SDLK_INSERT:   return CCKEY_INSERT;
+		case SDLK_HOME:     return CCKEY_HOME;
+		case SDLK_PAGEUP:   return CCKEY_PAGEUP;
+		case SDLK_DELETE:   return CCKEY_DELETE;
+		case SDLK_END:      return CCKEY_END;
+		case SDLK_PAGEDOWN: return CCKEY_PAGEDOWN;
+		case SDLK_RIGHT: return CCKEY_RIGHT;
+		case SDLK_LEFT:  return CCKEY_LEFT;
+		case SDLK_DOWN:  return CCKEY_DOWN;
+		case SDLK_UP:    return CCKEY_UP;
+
+		case SDLK_NUMLOCKCLEAR: return CCKEY_NUMLOCK;
+		case SDLK_KP_DIVIDE: return CCKEY_KP_DIVIDE;
+		case SDLK_KP_MULTIPLY: return CCKEY_KP_MULTIPLY;
+		case SDLK_KP_MINUS: return CCKEY_KP_MINUS;
+		case SDLK_KP_PLUS: return CCKEY_KP_PLUS;
+		case SDLK_KP_ENTER: return CCKEY_KP_ENTER;
+		case SDLK_KP_0: return CCKEY_KP0;
+		case SDLK_KP_PERIOD: return CCKEY_KP_DECIMAL;
+
+		case SDLK_LCTRL: return CCKEY_LCTRL;
+		case SDLK_LSHIFT: return CCKEY_LSHIFT;
+		case SDLK_LALT: return CCKEY_LALT;
+		case SDLK_LGUI: return CCKEY_LWIN;
+		case SDLK_RCTRL: return CCKEY_RCTRL;
+		case SDLK_RSHIFT: return CCKEY_RSHIFT;
+		case SDLK_RALT: return CCKEY_RALT;
+		case SDLK_RGUI: return CCKEY_RWIN;
+		
+		case SDLK_AUDIONEXT: return CCKEY_MEDIA_NEXT;
+		case SDLK_AUDIOPREV: return CCKEY_MEDIA_PREV;
+		case SDLK_AUDIOPLAY: return CCKEY_MEDIA_PLAY;
+		case SDLK_AUDIOSTOP: return CCKEY_MEDIA_STOP;
+		
+		case SDLK_AUDIOMUTE:  return CCKEY_VOLUME_MUTE;
+		case SDLK_VOLUMEDOWN: return CCKEY_VOLUME_DOWN;
+		case SDLK_VOLUMEUP:   return CCKEY_VOLUME_UP;
+	}
+	return INPUT_NONE;
+}
+
+static void OnKeyEvent(const SDL_Event* e) {
+	cc_bool pressed = e->key.state == SDL_PRESSED;
+	int key = MapNativeKey(e->key.keysym.sym);
+	if (key) Input_Set(key, pressed);
+}
+
+static void OnMouseEvent(const SDL_Event* e) {
+	cc_bool pressed = e->button.state == SDL_PRESSED;
+	int btn;
+	switch (e->button.button) {
+		case SDL_BUTTON_LEFT:   btn = CCMOUSE_L; break;
+		case SDL_BUTTON_MIDDLE: btn = CCMOUSE_M; break;
+		case SDL_BUTTON_RIGHT:  btn = CCMOUSE_R; break;
+		case SDL_BUTTON_X1:     btn = CCMOUSE_X1; break;
+		case SDL_BUTTON_X2:     btn = CCMOUSE_X2; break;
+		default: return;
+	}
+	Input_Set(btn, pressed);
+}
+
+static void OnTextEvent(const SDL_Event* e) {
+	cc_codepoint cp;
+	const char* src;
+	int i, len;
+
+	src = e->text.text;
+	len = String_Length(src);
+
+	while (len > 0) {
+		i = Convert_Utf8ToCodepoint(&cp, src, len);
+		if (!i) break;
+
+		Event_RaiseInt(&InputEvents.Press, cp);
+		src += i; len -= i;
+	}
+}
+static void ProcessDialogEvent(SDL_Event* e);
+
+void Window_ProcessEvents(float delta) {
+	SDL_Event e;
+	while (SDL_PollEvent(&e)) {
+		switch (e.type) {
+
+		case SDL_EVENT_KEY_DOWN:
+		case SDL_EVENT_KEY_UP:
+			OnKeyEvent(&e); break;
+		case SDL_EVENT_MOUSE_BUTTON_DOWN:
+		case SDL_EVENT_MOUSE_BUTTON_UP:
+			OnMouseEvent(&e); break;
+		case SDL_EVENT_MOUSE_WHEEL:
+			Mouse_ScrollHWheel(e.wheel.x);
+			Mouse_ScrollVWheel(e.wheel.y);
+			break;
+		case SDL_EVENT_MOUSE_MOTION:
+			Pointer_SetPosition(0, e.motion.x, e.motion.y);
+			if (Input.RawMode) Event_RaiseRawMove(&PointerEvents.RawMoved, e.motion.xrel, e.motion.yrel);
+			break;
+		case SDL_EVENT_TEXT_INPUT:
+			OnTextEvent(&e); break;
+
+		case SDL_EVENT_QUIT:
+			Window_Main.Exists = false;
+			Event_RaiseVoid(&WindowEvents.Closing);
+			SDL_DestroyWindow(win_handle);
+			break;
+
+		case SDL_EVENT_RENDER_DEVICE_RESET:
+			Gfx_LoseContext("SDL device reset event");
+			Gfx_RecreateContext();
+			break;
+			
+			
+		case SDL_EVENT_WINDOW_EXPOSED:
+			Event_RaiseVoid(&WindowEvents.RedrawNeeded);
+			break;
+		case SDL_EVENT_WINDOW_RESIZED: // TODO SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED 
+			RefreshWindowBounds();
+			Event_RaiseVoid(&WindowEvents.Resized);
+			break;
+		case SDL_EVENT_WINDOW_MINIMIZED:
+		case SDL_EVENT_WINDOW_MAXIMIZED:
+		case SDL_EVENT_WINDOW_RESTORED:
+			Event_RaiseVoid(&WindowEvents.StateChanged);
+			break;
+		case SDL_EVENT_WINDOW_FOCUS_GAINED:
+			Window_Main.Focused = true;
+			Event_RaiseVoid(&WindowEvents.FocusChanged);
+			break;
+		case SDL_EVENT_WINDOW_FOCUS_LOST:
+			Window_Main.Focused = false;
+			Event_RaiseVoid(&WindowEvents.FocusChanged);
+			break;
+		case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
+			Window_RequestClose();
+			break;
+		default:
+			if (e.type == dlg_event) ProcessDialogEvent(&e);
+			break;
+		}
+	}
+}
+
+void Window_ProcessGamepads(float delta) { }
+
+static void Cursor_GetRawPos(int* x, int* y) {
+	float xPos, yPos;
+	SDL_GetMouseState(&xPos, &yPos);
+	*x = xPos; *y = yPos;
+}
+void Cursor_SetPosition(int x, int y) {
+	SDL_WarpMouseInWindow(win_handle, x, y);
+}
+
+static void Cursor_DoSetVisible(cc_bool visible) {
+	if (visible) {
+		SDL_ShowCursor();
+	} else {
+		SDL_HideCursor();
+	}
+}
+
+static void ShowDialogCore(const char* title, const char* msg) {
+	SDL_ShowSimpleMessageBox(0, title, msg, win_handle);
+}	
+
+static FileDialogCallback dlgCallback;
+static SDL_DialogFileFilter* save_filters;
+
+static void ProcessDialogEvent(SDL_Event* e) {
+	char* result = e->user.data1;
+	int length   = e->user.code;
+	
+	cc_string path; char pathBuffer[1024];
+	String_InitArray(path, pathBuffer);
+	String_AppendUtf8(&path, result, length);
+	
+	dlgCallback(&path);
+	dlgCallback = NULL;
+	Mem_Free(result);
+}
+
+static void DialogCallback(void *userdata, const char* const* filelist, int filter) {
+	if (!filelist) return; /* Error occurred */
+	const char* result = filelist[0];
+	if (!result) return; /* No file provided */
+	
+	char* path    = Mem_Alloc(NATIVE_STR_LEN, 1, "Dialog path");
+	cc_string str = String_Init(path, 0, NATIVE_STR_LEN);
+	String_AppendUtf8(&str, result, String_Length(result));
+	
+	// May need to add file extension when saving, e.g. on Windows
+	if (save_filters && filter >= 0 && save_filters[filter].pattern)
+		String_Format1(&str, ".%c", save_filters[filter].pattern);
+	
+	// Dialog callback may not be called from the main thread
+	//  (E.g. on windows it is called from a background thread)
+	SDL_Event e = { 0 };
+	e.type = SDL_EVENT_USER;
+	e.user.code  = str.length;
+	e.user.data1 = path;
+	SDL_PushEvent(&e);
+}
+
+cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) {
+	// TODO free memory
+	char* pattern = Mem_Alloc(301, 1, "OpenDialog pattern");
+	SDL_DialogFileFilter* filters = Mem_Alloc(2, sizeof(SDL_DialogFileFilter), "OpenDialog filters");
+	int i;
+	
+	cc_string str = String_Init(pattern, 0, 300);
+	for (i = 0; ; i++)
+	{
+		if (!args->filters[i]) break;
+		if (i) String_Append(&str, ';');
+		String_AppendConst(&str, args->filters[i] + 1);
+	}
+	
+	pattern[str.length] = '\0';
+	filters[0].name     = args->description;
+	filters[0].pattern  = pattern;
+	filters[1].name     = NULL;
+	filters[1].pattern  = NULL;
+	
+	dlgCallback  = args->Callback;
+	save_filters = NULL;
+	SDL_ShowOpenFileDialog(DialogCallback, NULL, win_handle, filters, NULL, false);
+	return 0;
+}
+
+#define MAX_SAVE_DIALOG_FILTERS 10
+cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) {
+	// TODO free memory
+	char* defName = Mem_Alloc(NATIVE_STR_LEN, 1, "SaveDialog default");
+	SDL_DialogFileFilter* filters = Mem_Alloc(MAX_SAVE_DIALOG_FILTERS + 1, sizeof(SDL_DialogFileFilter), "SaveDialog filters");
+	int i;
+	String_EncodeUtf8(defName, &args->defaultName);
+	
+	for (i = 0; i < MAX_SAVE_DIALOG_FILTERS; i++)
+	{
+		if (!args->filters[i]) break;
+		filters[i].name    = args->titles[i];
+		filters[i].pattern = args->filters[i] + 1; // skip .
+	}
+	
+	filters[i].name    = NULL;
+	filters[i].pattern = NULL;
+	
+	dlgCallback  = args->Callback;
+	save_filters = filters;
+	SDL_ShowSaveFileDialog(DialogCallback, NULL, win_handle, filters, defName);
+	return 0;
+}
+
+static SDL_Surface* win_surface;
+static SDL_Surface* blit_surface;
+
+void Window_AllocFramebuffer(struct Bitmap* bmp, int width, int height) {
+	SDL_PixelFormat* fmt;
+	win_surface = SDL_GetWindowSurface(win_handle);
+	if (!win_surface) Window_SDLFail("getting window surface");
+
+	fmt = win_surface->format;
+	if (fmt->bits_per_pixel != 32) {
+		/* Slow path: e.g. 15 or 16 bit pixels */
+		Platform_Log1("Slow color depth: %b bpp", &fmt->bits_per_pixel);
+		blit_surface = SDL_CreateSurface(win_surface->w, win_surface->h, SDL_PIXELFORMAT_RGBA32);
+		if (!blit_surface) Window_SDLFail("creating blit surface");
+
+		SDL_SetSurfaceBlendMode(blit_surface, SDL_BLENDMODE_NONE);
+		bmp->scan0 = blit_surface->pixels;
+	} else {
+		/* Fast path: 32 bit pixels */
+		if (SDL_MUSTLOCK(win_surface)) {
+			int ret = SDL_LockSurface(win_surface);
+			if (ret < 0) Window_SDLFail("locking window surface");
+		}
+		bmp->scan0 = win_surface->pixels;
+	}
+	/* TODO proper stride */
+	bmp->width  = width;
+	bmp->height = height;
+}
+
+void Window_DrawFramebuffer(Rect2D r, struct Bitmap* bmp) {
+	SDL_Rect rect;
+	rect.x = r.x; rect.w = r.width;
+	rect.y = r.y; rect.h = r.height;
+
+	if (blit_surface) SDL_BlitSurface(blit_surface, &rect, win_surface, &rect);
+	SDL_UpdateWindowSurfaceRects(win_handle, &rect, 1);
+}
+
+void Window_FreeFramebuffer(struct Bitmap* bmp) {
+	if (blit_surface) SDL_DestroySurface(blit_surface);
+	blit_surface = NULL;
+
+	/* SDL docs explicitly say to NOT free window surface */
+	/* https://wiki.libsdl.org/SDL_GetWindowSurface */
+	/* TODO: Do we still need to unlock it though? */
+}
+
+void OnscreenKeyboard_Open(struct OpenKeyboardArgs* args) { SDL_StartTextInput(); }
+void OnscreenKeyboard_SetText(const cc_string* text) { }
+void OnscreenKeyboard_Draw2D(Rect2D* r, struct Bitmap* bmp) { }
+void OnscreenKeyboard_Draw3D(void) { }
+void OnscreenKeyboard_Close(void) { SDL_StopTextInput(); }
+
+void Window_EnableRawMouse(void) {
+	RegrabMouse();
+	SDL_SetRelativeMouseMode(true);
+	Input.RawMode = true;
+}
+void Window_UpdateRawMouse(void) { CentreMousePosition(); }
+
+void Window_DisableRawMouse(void) {
+	RegrabMouse();
+	SDL_SetRelativeMouseMode(false);
+	Input.RawMode = false;
+}
+
+
+/*########################################################################################################################*
+*-----------------------------------------------------OpenGL context------------------------------------------------------*
+*#########################################################################################################################*/
+#if (CC_GFX_BACKEND & CC_GFX_BACKEND_GL_MASK) && !defined CC_BUILD_EGL
+static SDL_GLContext win_ctx;
+
+void GLContext_Create(void) {
+	struct GraphicsMode mode;
+	InitGraphicsMode(&mode);
+	SDL_GL_SetAttribute(SDL_GL_RED_SIZE,   mode.R);
+	SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, mode.G);
+	SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,  mode.B);
+	SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, mode.A);
+
+	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,   GLCONTEXT_DEFAULT_DEPTH);
+	SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0);
+	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, true);
+#ifdef CC_BUILD_GLES
+	SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
+#endif
+
+	win_ctx = SDL_GL_CreateContext(win_handle);
+	if (!win_ctx) Window_SDLFail("creating OpenGL context");
+}
+
+void GLContext_Update(void) { }
+cc_bool GLContext_TryRestore(void) { return true; }
+void GLContext_Free(void) {
+	SDL_GL_DeleteContext(win_ctx);
+	win_ctx = NULL;
+}
+
+void* GLContext_GetAddress(const char* function) {
+	return SDL_GL_GetProcAddress(function);
+}
+
+cc_bool GLContext_SwapBuffers(void) {
+	SDL_GL_SwapWindow(win_handle);
+	return true;
+}
+
+void GLContext_SetFpsLimit(cc_bool vsync, float minFrameMs) {
+	SDL_GL_SetSwapInterval(vsync);
+}
+void GLContext_GetApiInfo(cc_string* info) { }
+#endif
+#endif