/* * Copyright (c) 2009-2023 Julien Nadeau Carriere * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Driver for OpenGL graphics via the SDL 1.2 library. */ #include #include #include #include #include #include #include #include #include enum ag_sdlgl_out { AG_SDLGL_OUT_NONE, /* No capture */ AG_SDLGL_OUT_JPEG, /* Output JPEG files */ AG_SDLGL_OUT_PNG /* Output PNG files */ }; typedef struct ag_sdlgl_driver { struct ag_driver_sw _inherit; SDL_Surface *_Nullable s; /* Display surface */ AG_GL_Context gl; /* Common OpenGL context data */ Uint8 *_Nullable outBuf; /* Output capture buffer */ char *_Nullable outPath; /* Output capture path */ enum ag_sdlgl_out outMode; /* Output capture mode */ Uint outFrame; /* Capture frame# counter */ Uint outLast; /* Terminate after this many frames */ Uint outJpegQual; /* Quality (%) for jpeg output */ Uint outJpegFlags; /* DCT options */ Uint32 _pad; } AG_DriverSDLGL; static int nDrivers = 0; /* Opened driver instances */ static int initedSDL = 0; /* Used SDL_Init() */ static AG_EventSink *_Nullable sglEventSpinner = NULL; /* Standard event sink */ static AG_EventSink *_Nullable sglEventEpilogue = NULL; /* Standard event epilogue */ static void Init(void *_Nonnull obj) { AG_DriverSDLGL *sgl = obj; sgl->s = NULL; } /* * Generic driver operations */ static int SDLGL_Open(void *_Nonnull obj, const char *_Nullable spec) { AG_Driver *drv = obj; AG_DriverSDLGL *sgl = obj; if (nDrivers != 0) { AG_SetErrorS(_("Multiple SDL displays are not supported")); return (-1); } /* Initialize SDL's video subsystem. */ if (!initedSDL) { Uint32 sdlFlags = SDL_INIT_VIDEO; if (AG_Defined(drv, "joy")) { sdlFlags |= SDL_INIT_JOYSTICK; } if (SDL_Init(sdlFlags) == -1) { AG_SetError(_("SDL_Init() failed: %s"), SDL_GetError()); return (-1); } initedSDL = 1; } #if 0 /* Use SDL's time interface. */ AG_SetTimeOps(&agTimeOps_SDL); AG_DestroyEventSubsystem(); AG_InitEventSubsystem(AG_SOFT_TIMERS); #endif /* Initialize this driver instance. */ if ((drv->mouse = AG_MouseNew(sgl, "SDL Mouse")) == NULL || (drv->kbd = AG_KeyboardNew(sgl, "SDL Keyboard")) == NULL) { goto fail; } sgl->outBuf = NULL; sgl->outPath = NULL; sgl->outMode = AG_SDLGL_OUT_NONE; sgl->outFrame = 0; sgl->outLast = 0; sgl->outJpegQual = 100; sgl->outJpegFlags = 0; /* Configure the window caption */ if (agProgName != NULL) SDL_WM_SetCaption(agProgName, agProgName); /* * TODO where AG_SINK_READ capability and pipes are available, * could we create a separate thread running SDL_WaitEvent() and * sending notifications over a pipe, instead of using a spinner? */ if ((sglEventSpinner = AG_AddEventSpinner(AG_SDL_EventSink, "%p", drv)) == NULL || (sglEventEpilogue = AG_AddEventEpilogue(AG_SDL_EventEpilogue, NULL)) == NULL) goto fail; /* Set up event filters for standard AG_EventLoop(). */ nDrivers = 1; return (0); fail: if (sglEventSpinner != NULL) { AG_DelEventSpinner(sglEventSpinner); sglEventSpinner = NULL; } if (sglEventEpilogue != NULL) { AG_DelEventEpilogue(sglEventEpilogue); sglEventEpilogue = NULL; } if (drv->kbd != NULL) { AG_ObjectDelete(drv->kbd); drv->kbd = NULL; } if (drv->mouse != NULL) { AG_ObjectDelete(drv->mouse); drv->mouse = NULL; } return (-1); } static void SDLGL_Close(void *_Nonnull obj) { AG_Driver *drv = obj; AG_DriverSw *dsw = obj; AG_DriverSDLGL *sgl = obj; AG_DelEventSpinner(sglEventSpinner); sglEventSpinner = NULL; AG_DelEventEpilogue(sglEventEpilogue); sglEventEpilogue = NULL; if (drv->gl != NULL) AG_GL_DestroyContext(sgl); #ifdef AG_DEBUG if (nDrivers != 1) { AG_FatalError("Driver close without open"); } #endif AG_FreeCursors(AGDRIVER(sgl)); if (dsw->flags & AG_DRIVER_SW_FULLSCREEN) { if (SDL_WM_ToggleFullScreen(sgl->s)) dsw->flags &= ~(AG_DRIVER_SW_FULLSCREEN); } AG_ObjectDelete(drv->mouse); drv->mouse = NULL; AG_ObjectDelete(drv->kbd); drv->kbd = NULL; if (sgl->outBuf != NULL) { free(sgl->outBuf); sgl->outBuf = NULL; } nDrivers = 0; } static void SDLGL_BeginRendering(void *_Nonnull obj) { AG_DriverSDLGL *sgl = obj; AG_GL_Context *gl = &sgl->gl; if (AGDRIVERSW(sgl)->flags & AG_DRIVER_SW_OVERLAY) { AG_Rect r; AG_Driver *drv = obj; glPushAttrib(GL_VIEWPORT_BIT | GL_TRANSFORM_BIT | GL_LIGHTING_BIT | GL_ENABLE_BIT); /* Reinitialize Agar's OpenGL context. */ if (drv->gl != NULL) { AG_GL_DestroyContext(drv); } AG_GL_InitContext(drv, gl); r.x = 0; r.y = 0; r.w = AGDRIVERSW(sgl)->w; r.h = AGDRIVERSW(sgl)->h; AG_GL_SetViewport(gl, &r); } else { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } } static void SDLGL_RenderWindow(struct ag_window *_Nonnull win) { AG_WidgetDraw(win); } static void SDLGL_CaptureOutput(AG_DriverSDLGL *_Nonnull sgl) { char path[AG_PATHNAME_MAX]; AG_DriverSw *dsw = (AG_DriverSw *)sgl; int w = dsw->w; int h = dsw->h; AG_Surface *S; Snprintf(path, sizeof(path), sgl->outPath, sgl->outFrame); glReadPixels(0,0, w,h, GL_RGBA, GL_UNSIGNED_BYTE, sgl->outBuf); if (AG_PackedPixelFlip(sgl->outBuf, h,w*4) == -1) { goto fail_disable; } S = AG_SurfaceFromPixelsRGBA(sgl->outBuf, w,h, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0); if (S == NULL) goto fail; switch (sgl->outMode) { case AG_SDLGL_OUT_JPEG: if (AG_SurfaceExportJPEG(S, path, sgl->outJpegQual, sgl->outJpegFlags) == -1) { goto fail; } break; case AG_SDLGL_OUT_PNG: if (AG_SurfaceExportPNG(S, path, 0) == -1) { goto fail; } break; default: break; } if (++sgl->outFrame == sgl->outLast) { Verbose("SDLGL: Reached last frame; terminating\n"); AG_Terminate(0); } AG_SurfaceFree(S); return; fail: AG_SurfaceFree(S); fail_disable: Verbose("SDLGL: %s; disabling capture\n", AG_GetError()); sgl->outMode = AG_SDLGL_OUT_NONE; } static void SDLGL_EndRendering(void *_Nonnull drv) { AG_DriverSDLGL *sgl = drv; if (sgl->outMode != AG_SDLGL_OUT_NONE) /* Capture output */ SDLGL_CaptureOutput(sgl); if (AGDRIVERSW(sgl)->flags & AG_DRIVER_SW_OVERLAY) { glPopAttrib(); AG_GL_DestroyContext(&sgl->gl); /* Restore former state */ } else { SDL_GL_SwapBuffers(); } } /* * Operations specific to single-display drivers. */ static __inline__ void ClearBackground(AG_DriverSw *_Nonnull dsw) { glClearColor(dsw->bgColor.r/AG_COLOR_LASTF, dsw->bgColor.g/AG_COLOR_LASTF, dsw->bgColor.b/AG_COLOR_LASTF, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } static int SDLGL_OpenVideo(void *_Nonnull obj, Uint w, Uint h, int depth, Uint flags) { char buf[256]; AG_Rect rVP; AG_Driver *drv = obj; AG_DriverSw *dsw = obj; AG_DriverSDLGL *sgl = obj; Uint32 sFlags = SDL_OPENGL; int newDepth; /* Set the requested display options. */ if (flags & AG_VIDEO_RESIZABLE) { sFlags |= SDL_RESIZABLE; } if (flags & AG_VIDEO_ANYFORMAT) { sFlags |= SDL_ANYFORMAT; } if (flags & AG_VIDEO_HWPALETTE) { sFlags |= SDL_HWPALETTE; } if (flags & AG_VIDEO_DOUBLEBUF) { sFlags |= SDL_DOUBLEBUF; } if (flags & AG_VIDEO_FULLSCREEN) { sFlags |= SDL_FULLSCREEN; } if (flags & AG_VIDEO_NOFRAME) { sFlags |= SDL_NOFRAME; } if (flags & AG_VIDEO_OVERLAY) dsw->flags |= AG_DRIVER_SW_OVERLAY; if (flags & AG_VIDEO_BGPOPUPMENU) dsw->flags |= AG_DRIVER_SW_BGPOPUP; if (AG_Defined(drv, "out")) { char *ext; AG_GetString(drv, "out", buf, sizeof(buf)); if ((ext = strrchr(buf, '.')) != NULL && ext[1] != '\0') { if (Strcasecmp(&ext[1], "jpeg") == 0 || Strcasecmp(&ext[1], "jpg") == 0) { sgl->outMode = AG_SDLGL_OUT_JPEG; if ((sgl->outPath = TryStrdup(buf)) == NULL) return (-1); } else if (Strcasecmp(&ext[1], "png") == 0) { sgl->outMode = AG_SDLGL_OUT_PNG; if ((sgl->outPath = TryStrdup(buf)) == NULL) return (-1); } else { AG_SetError(_("Invalid out= argument: `%s'"), buf); return (-1); } if (AG_Defined(drv, "outFirst")) { AG_GetString(drv, "outFirst", buf, sizeof(buf)); sgl->outFrame = atoi(buf); } else { sgl->outFrame = 0; } if (AG_Defined(drv, "outLast")) { AG_GetString(drv, "outLast", buf, sizeof(buf)); sgl->outLast = atoi(buf); } } if (AG_Defined(drv, "jpegQual")) { AG_GetString(drv, "jpegQual", buf, sizeof(buf)); sgl->outJpegQual = atoi(buf); } if (AG_Defined(drv, "jpegDCT")) { AG_GetString(drv, "jpegDCT", buf, sizeof(buf)); if (Strcasecmp(buf, "islow")) { sgl->outJpegFlags = AG_EXPORT_JPEG_JDCT_ISLOW; } else if (Strcasecmp(buf, "ifast")) { sgl->outJpegFlags = AG_EXPORT_JPEG_JDCT_IFAST; } else if (Strcasecmp(buf, "float")) { sgl->outJpegFlags = AG_EXPORT_JPEG_JDCT_FLOAT; } } } /* Set the video mode. Force hardware palette in 8bpp. */ AG_SDL_GetPrefDisplaySettings(drv, &w, &h, &depth); Verbose(_("SDLGL: Setting mode %ux%u (%d bpp)\n"), w, h, depth); newDepth = SDL_VideoModeOK(w, h, depth, sFlags); if (newDepth == 8) { Verbose(_("SDLGL: Using hardware palette\n")); sFlags |= SDL_HWPALETTE; } if ((sgl->s = SDL_SetVideoMode((int)w, (int)h, newDepth, sFlags)) == NULL) { AG_SetError("Mode %dx%dx%d: %s", w, h, newDepth, SDL_GetError()); return (-1); } SDL_EnableUNICODE(1); if ((drv->videoFmt = AG_SDL_GetPixelFormat(sgl->s)) == NULL) { goto fail; } dsw->w = sgl->s->w; dsw->h = sgl->s->h; dsw->depth = (Uint)drv->videoFmt->BitsPerPixel; Verbose(_("SDLGL: New display (%dbpp)\n"), (int)drv->videoFmt->BitsPerPixel); /* Create the cursors. */ AG_SDL_InitDefaultCursor(sgl); AG_InitStockCursors(drv); /* Initialize our OpenGL context and viewport. */ AG_GL_InitContext(sgl, &sgl->gl); rVP.x = 0; rVP.y = 0; rVP.w = dsw->w; rVP.h = dsw->h; AG_GL_SetViewport(&sgl->gl, &rVP); if (!(dsw->flags & AG_DRIVER_SW_OVERLAY)) ClearBackground(dsw); /* Initialize the output capture buffer. */ Free(sgl->outBuf); if (sgl->outMode != AG_SDLGL_OUT_NONE) { if ((sgl->outBuf = AG_TryMalloc(dsw->w*dsw->h*4)) == NULL) { Verbose("SDLGL: Out of memory; disabling capture\n"); sgl->outMode = AG_SDLGL_OUT_NONE; } } if (flags & AG_VIDEO_FULLSCREEN) { if (SDL_WM_ToggleFullScreen(sgl->s)) dsw->flags |= AG_DRIVER_SW_FULLSCREEN; } return (0); fail: if (drv->videoFmt) { AG_PixelFormatFree(drv->videoFmt); free(drv->videoFmt); drv->videoFmt = NULL; } return (-1); } static int SDLGL_OpenVideoContext(void *_Nonnull obj, void *_Nonnull ctx, Uint flags) { AG_DriverSDLGL *sgl = obj; AG_DriverSw *dsw = obj; AG_Driver *drv = obj; AG_Rect rVP; SDL_Surface *ctxSu = (SDL_Surface *)ctx; if (!(ctxSu->flags & SDL_OPENGL)) { AG_SetErrorS(_("Context surface is !SDL_OPENGL")); return (-1); } /* Set the requested display options. */ if (flags & AG_VIDEO_OVERLAY) dsw->flags |= AG_DRIVER_SW_OVERLAY; if (flags & AG_VIDEO_BGPOPUPMENU) dsw->flags |= AG_DRIVER_SW_BGPOPUP; /* Use the given display surface. */ sgl->s = (SDL_Surface *)ctx; if ((drv->videoFmt = AG_SDL_GetPixelFormat(sgl->s)) == NULL) { goto fail; } dsw->w = sgl->s->w; dsw->h = sgl->s->h; dsw->depth = (Uint)drv->videoFmt->BitsPerPixel; Verbose(_("SDLGL: Using existing display (%ux%ux%d bpp)\n"), dsw->w, dsw->h, (int)drv->videoFmt->BitsPerPixel); /* Initialize our OpenGL context and viewport. */ AG_GL_InitContext(sgl, &sgl->gl); rVP.x = 0; rVP.y = 0; rVP.w = dsw->w; rVP.h = dsw->h; AG_GL_SetViewport(&sgl->gl, &rVP); /* Create the cursors. */ AG_SDL_InitDefaultCursor(sgl); AG_InitStockCursors(drv); return (0); fail: if (drv->videoFmt) { AG_PixelFormatFree(drv->videoFmt); free(drv->videoFmt); drv->videoFmt = NULL; } return (-1); } static void SDLGL_CloseVideo(void *_Nonnull obj) { /* Nothing to do */ } static int SDLGL_VideoResize(void *_Nonnull obj, Uint w, Uint h) { AG_Driver *drv = obj; AG_DriverSw *dsw = obj; AG_DriverSDLGL *sgl = obj; AG_Rect rVP; Uint32 sFlags; SDL_Surface *S; AG_Window *win; Debug(sgl, "VideoResize event (%u x %u)\n", w,h); sFlags = sgl->s->flags & (SDL_SWSURFACE | SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_ANYFORMAT | SDL_HWPALETTE | SDL_DOUBLEBUF | SDL_FULLSCREEN | SDL_OPENGL | SDL_OPENGLBLIT | SDL_RESIZABLE | SDL_NOFRAME); AG_FOREACH_WINDOW(win, sgl) /* Save mapped textures */ AG_WidgetFreeResourcesGL(win); AG_TextClearGlyphCache(drv); if ((S = SDL_SetVideoMode(w, h, 0, sFlags)) == NULL) { AG_SetError("SDL_SetVideoMode(%ux%u): %s", w,h, SDL_GetError()); return (-1); } sgl->s = S; Debug(sgl, "Resized to: %u x %u x %d-bpp (flags 0x%x)\n", S->w, S->h, S->format->BitsPerPixel, S->flags); dsw->w = S->w; dsw->h = S->h; dsw->depth = (Uint)S->format->BitsPerPixel; if (sgl->outBuf != NULL) { /* Resize output capture buffer */ free(sgl->outBuf); if ((sgl->outBuf = AG_TryMalloc(dsw->w * dsw->h * 4)) == NULL) { Verbose("SDLGL: Out of memory; disabling capture\n"); sgl->outMode = AG_SDLGL_OUT_NONE; } } rVP.x = 0; /* Resize GL viewport */ rVP.y = 0; rVP.w = dsw->w; rVP.h = dsw->h; AG_GL_SetViewport(&sgl->gl, &rVP); AG_FOREACH_WINDOW(win, sgl) { /* Restore mapped textures */ AG_WidgetRegenResourcesGL(win); win->dirty = 1; } if (!(dsw->flags & AG_DRIVER_SW_OVERLAY)) ClearBackground(dsw); return (0); } static AG_Surface * SDLGL_VideoCapture(void *_Nonnull obj) { const AG_DriverSw *dsw = obj; const Uint w = dsw->w; const Uint h = dsw->h; Uint8 *pixels; AG_Surface *S; if ((pixels = AG_TryMalloc((w * h) << 2)) == NULL) { return (NULL); } glReadPixels(0,0, w,h, GL_RGBA, GL_UNSIGNED_BYTE, pixels); AG_PackedPixelFlip(pixels, h, (w << 2)); S = AG_SurfaceFromPixelsRGBA(pixels, w,h, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0); free(pixels); return (S); } static void SDLGL_VideoClear(void *_Nonnull obj, const AG_Color *_Nonnull c) { glClearColor(c->r / AG_COLOR_LASTF, c->g / AG_COLOR_LASTF, c->b / AG_COLOR_LASTF, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } static int SDLGL_SetVideoContext(void *_Nonnull obj, void *_Nonnull pSurface) { AG_DriverSDLGL *sgl = obj; AG_GL_Context *gl = &sgl->gl; AG_DriverSw *dsw = obj; SDL_Surface *S = pSurface; sgl->s = S; dsw->w = S->w; dsw->h = S->h; dsw->depth = (Uint)S->format->BitsPerPixel; if (dsw->flags & AG_DRIVER_SW_OVERLAY) { AG_ClipRect *cr0 = &gl->clipRects[0]; /* Just update clipping rectangle 0. */ cr0->r.w = S->w; cr0->r.h = S->h; } else { AG_Rect rVP; rVP.x = 0; rVP.y = 0; rVP.w = S->w; rVP.h = S->h; AG_GL_SetViewport(gl, &rVP); } return (0); } AG_DriverSwClass agDriverSDLGL = { { { "AG_Driver:AG_DriverSw:AG_DriverSDLGL", sizeof(AG_DriverSDLGL), { 1,7, AGC_DRIVER_SDLGL, 0xE096 }, Init, NULL, /* reset */ NULL, /* destroy */ NULL, /* load */ NULL, /* save */ NULL, /* edit */ }, "sdlgl", AG_VECTOR, AG_WM_SINGLE, AG_DRIVER_SDL | AG_DRIVER_OPENGL | AG_DRIVER_TEXTURES, SDLGL_Open, SDLGL_Close, AG_SDL_GetDisplaySize, AG_SDL_BeginEventProcessing, AG_SDL_PendingEvents, AG_SDL_GetNextEvent, AG_SDL_ProcessEvent, NULL, /* genericEventLoop */ NULL, /* endEventProcessing */ NULL, /* terminate */ SDLGL_BeginRendering, SDLGL_RenderWindow, SDLGL_EndRendering, AG_GL_FillRect, NULL, /* updateRegion */ AG_GL_StdUploadTexture, AG_GL_StdUpdateTexture, AG_GL_StdDeleteTexture, AG_SDL_SetRefreshRate, AG_GL_StdPushClipRect, AG_GL_StdPopClipRect, AG_GL_StdPushBlendingMode, AG_GL_StdPopBlendingMode, AG_SDL_CreateCursor, AG_SDL_FreeCursor, AG_SDL_SetCursor, AG_SDL_UnsetCursor, AG_SDL_GetCursorVisibility, AG_SDL_SetCursorVisibility, AG_GL_BlitSurface, AG_GL_BlitSurfaceFrom, #ifdef HAVE_OPENGL AG_GL_BlitSurfaceGL, AG_GL_BlitSurfaceFromGL, AG_GL_BlitSurfaceFlippedGL, AG_GL_BackupSurfaces, AG_GL_RestoreSurfaces, #else NULL, /* backupSurfaces */ NULL, /* restoreSurfaces */ #endif AG_GL_RenderToSurface, AG_GL_PutPixel, AG_GL_PutPixel32, AG_GL_PutPixelRGB8, #if AG_MODEL == AG_LARGE AG_GL_PutPixel64, AG_GL_PutPixelRGB16, #endif AG_GL_BlendPixel, AG_GL_DrawLine, AG_GL_DrawLineH, AG_GL_DrawLineV, AG_GL_DrawLineBlended, AG_GL_DrawLineW, AG_GL_DrawLineW_Sti16, AG_GL_DrawTriangle, AG_GL_DrawPolygon, AG_GL_DrawPolygon_Sti32, AG_GL_DrawArrow, AG_GL_DrawBoxRounded, AG_GL_DrawBoxRoundedTop, AG_GL_DrawCircle, AG_GL_DrawCircleFilled, AG_GL_DrawRectFilled, AG_GL_DrawRectBlended, AG_GL_DrawRectDithered, AG_GL_UpdateGlyph, AG_GL_DrawGlyph, AG_GL_StdDeleteList, NULL, /* getClipboardText */ NULL, /* setClipboardText */ NULL /* setMouseAutoCapture */ }, 0, SDLGL_OpenVideo, SDLGL_OpenVideoContext, SDLGL_SetVideoContext, SDLGL_CloseVideo, SDLGL_VideoResize, SDLGL_VideoCapture, SDLGL_VideoClear };