/* * Copyright (c) 2001-2007 Hypertriton, Inc. * 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. */ /* * Low-level interface between the GUI and video display. */ #include "opengl.h" #include #include #include "window.h" #include "primitive.h" #include "cursors.h" #include "colors.h" #include "menu.h" #include "text.h" #ifdef AG_DEBUG #include "label.h" #include "fixed_plotter.h" #endif #include "gui_math.h" #include #include #include #include #ifdef HAVE_JPEG #undef HAVE_STDLIB_H /* Work around SDL.h retardation */ #include #include #endif /* * Invert the Y-coordinate in OpenGL mode. */ /* #define OPENGL_INVERTED_Y */ AG_Display *agView = NULL; /* Main view */ AG_PixelFormat *agVideoFmt = NULL; /* Current format of display */ AG_PixelFormat *agSurfaceFmt = NULL; /* Preferred format for surfaces */ const SDL_VideoInfo *agVideoInfo; /* Display information */ int agBgPopupMenu = 0; /* Background popup menu */ int agRenderingContext = 0; /* In rendering context (for debug) */ AG_ClipRect *agClipRects = NULL; /* Clipping rectangle stack (first entry always covers whole view) */ int agClipStateGL[4]; /* Saved GL clipping plane states */ Uint agClipRectCount = 0; static int initedGlobals = 0; const char *agBlendFuncNames[] = { N_("Alpha sum"), N_("Source alpha"), N_("Destination alpha"), N_("One minus destination alpha"), N_("One minus source alpha"), NULL }; struct ag_global_key { SDLKey keysym; SDLMod keymod; void (*fn)(void); void (*fn_ev)(AG_Event *); SLIST_ENTRY(ag_global_key) gkeys; }; static SLIST_HEAD(,ag_global_key) agGlobalKeys = SLIST_HEAD_INITIALIZER(&agGlobalKeys); #ifdef AG_THREADS static AG_Mutex agGlobalKeysLock; #endif static void (*agVideoResizeCallback)(Uint w, Uint h) = NULL; #ifdef AG_DEBUG int agEventAvg = 0; /* Number of events in last frame */ int agIdleAvg = 0; /* Measured SDL_Delay() granularity */ AG_Window *agPerfWindow; static AG_FixedPlotter *agPerfGraph; static AG_FixedPlotterItem *agPerfFPS, *agPerfEvnts, *agPerfIdle; #endif int agFullscreenMode = 0; int agAsyncBlits = 0; #ifdef HAVE_OPENGL static void InitGL(void) { Uint8 bR, bG, bB; glViewport(0, 0, agView->w, agView->h); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glOrtho(0, agView->w, agView->h, 0, -1.0, 1.0); AG_GetRGB(AG_COLOR(BG_COLOR), agVideoFmt, &bR, &bG, &bB); glClearColor(bR/255.0, bG/255.0, bB/255.0, 1.0); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glShadeModel(GL_FLAT); glEnable(GL_TEXTURE_2D); glDisable(GL_DEPTH_TEST); glDisable(GL_CULL_FACE); glDisable(GL_DITHER); glDisable(GL_BLEND); } #endif /* HAVE_OPENGL */ void AG_ClearBackground(void) { #ifdef HAVE_OPENGL if (agView->opengl) { Uint8 r, g, b; AG_GetRGB(AG_COLOR(BG_COLOR), agVideoFmt, &r, &g, &b); glClearColor(r/255.0, g/255.0, b/255.0, 1.0); } else #endif { SDL_FillRect(agView->v, NULL, AG_COLOR(BG_COLOR)); SDL_UpdateRect(agView->v, 0, 0, agView->w, agView->h); } } static void InitView(AG_Display *v) { AG_ObjectInit(v, &agDisplayClass); AG_ObjectSetName(v, "_agView"); v->winop = AG_WINOP_NONE; v->ndirty = 0; v->maxdirty = 4; v->dirty = Malloc(v->maxdirty*sizeof(SDL_Rect)); v->opengl = 0; v->winModal = Malloc(sizeof(AG_Window *)); v->nModal = 0; v->winSelected = NULL; v->winToFocus = NULL; v->rNom = 16; v->rCur = 0; TAILQ_INIT(&v->windows); TAILQ_INIT(&v->detach); } static void InitGlobals(void) { if (initedGlobals) { return; } initedGlobals = 1; AG_MutexInitRecursive(&agGlobalKeysLock); AG_RegisterClass(&agDisplayClass); agVideoInfo = SDL_GetVideoInfo(); agGUI = 1; AG_RegisterBuiltinLabelFormats(); } /* Initialize the clipping rectangle stack. */ static void InitClipRects(int wView, int hView) { AG_ClipRect *cr; int i; for (i = 0; i < 4; i++) agClipStateGL[i] = 0; /* Rectangle 0 always covers the whole view. */ agClipRects = Malloc(sizeof(AG_ClipRect)); cr = &agClipRects[0]; cr->r = AG_RECT(0, 0, wView, hView); cr->eqns[0][0] = 1.0; cr->eqns[0][1] = 0.0; cr->eqns[0][2] = 0.0; cr->eqns[0][3] = 0.0; cr->eqns[1][0] = 0.0; cr->eqns[1][1] = 1.0; cr->eqns[1][2] = 0.0; cr->eqns[1][3] = 0.0; cr->eqns[2][0] = -1.0; cr->eqns[2][1] = 0.0; cr->eqns[2][2] = 0.0; cr->eqns[2][3] = (double)wView; cr->eqns[3][0] = 0.0; cr->eqns[3][1] = -1.0; cr->eqns[3][2] = 0.0; cr->eqns[3][3] = (double)hView; agClipRectCount = 1; } /* * Initialize Agar with an existing SDL/OpenGL display surface. If the * surface has SDL_OPENGL or SDL_OPENGLBLIT set, we assume that OpenGL * primitives are to be used in rendering. */ int AG_InitVideoSDL(SDL_Surface *display, Uint flags) { InitGlobals(); agInitedSDL = 0; if (display->w < AG_GetUint16(agConfig,"view.min-w") || display->h < AG_GetUint16(agConfig,"view.min-h")) { AG_SetError(_("The resolution is too small.")); return (-1); } agView = Malloc(sizeof(AG_Display)); InitView(agView); agView->v = display; agView->w = display->w; agView->h = display->h; agView->depth = display->format->BitsPerPixel; agView->rNom = 1000/AG_GetUint(agConfig, "view.nominal-fps"); AG_SetUint8(agConfig, "view.depth", agView->depth); AG_SetUint16(agConfig, "view.w", agView->w); AG_SetUint16(agConfig, "view.h", agView->h); AG_SetBool(agConfig, "view.full-screen", display->flags&SDL_FULLSCREEN); AG_SetBool(agConfig, "view.async-blits", display->flags&SDL_ASYNCBLIT); if (display->flags & (SDL_OPENGL|SDL_OPENGLBLIT)) { AG_SetBool(agConfig, "view.opengl", 1); agView->opengl = 1; } else { if (flags & AG_VIDEO_OPENGL) { AG_SetError("AG_VIDEO_OPENGL flag requested, but " "display surface is missing SDL_OPENGL"); goto fail; } AG_SetBool(agConfig, "view.opengl", 0); agView->opengl = 0; } agView->stmpl = AG_SurfaceRGBA(1,1, 32, 0, #if AG_BYTEORDER == AG_BIG_ENDIAN 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff #else 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 #endif ); if (agView->stmpl == NULL) { return (-1); } agVideoFmt = agView->v->format; agSurfaceFmt = agView->stmpl->format; #ifdef HAVE_OPENGL if (agView->opengl) InitGL(); #endif if (AG_InitGUI(0) == -1) { goto fail; } InitClipRects(agView->w, agView->h); if (flags & AG_VIDEO_BGPOPUPMENU) agBgPopupMenu = 1; if (!(flags & AG_VIDEO_NOBGCLEAR)) AG_ClearBackground(); return (0); fail: Free(agView); agView = NULL; return (-1); } /* Initialize Agar with a new video display. */ int AG_InitVideo(int w, int h, int bpp, Uint flags) { Uint32 screenflags = 0; int depth; InitGlobals(); if (SDL_Init(0) == -1) { AG_SetError("SDL_Init() failed: %s", SDL_GetError()); return (-1); } if (!SDL_WasInit(SDL_INIT_VIDEO) && SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) { AG_SetError("SDL_INIT_VIDEO failed: %s", SDL_GetError()); SDL_Quit(); return (-1); } agInitedSDL = 1; SDL_WM_SetCaption(agProgName, agProgName); if (flags & (AG_VIDEO_OPENGL|AG_VIDEO_OPENGL_OR_SDL)) { #ifdef HAVE_OPENGL AG_SetBool(agConfig, "view.opengl", 1); #else if ((flags & AG_VIDEO_OPENGL_OR_SDL) == 0) AG_FatalError("Agar OpenGL support is not compiled in"); #endif } else { AG_SetBool(agConfig, "view.opengl", 0); } if (flags & AG_VIDEO_HWSURFACE) { screenflags |= SDL_HWSURFACE; } else { screenflags |= SDL_SWSURFACE; } if (flags & AG_VIDEO_RESIZABLE) { screenflags |= SDL_RESIZABLE; } if (flags & AG_VIDEO_ASYNCBLIT) { screenflags |= SDL_ASYNCBLIT; } if (flags & AG_VIDEO_ANYFORMAT) { screenflags |= SDL_ANYFORMAT; } if (flags & AG_VIDEO_HWPALETTE) { screenflags |= SDL_HWPALETTE; } if (flags & AG_VIDEO_DOUBLEBUF) { screenflags |= SDL_DOUBLEBUF; } if (flags & AG_VIDEO_FULLSCREEN) { screenflags |= SDL_FULLSCREEN; } if (flags & AG_VIDEO_NOFRAME) { screenflags |= SDL_NOFRAME; } if (flags & AG_VIDEO_BGPOPUPMENU) { agBgPopupMenu = 1; } agView = Malloc(sizeof(AG_Display)); InitView(agView); agView->rNom = 1000/AG_GetUint(agConfig,"view.nominal-fps"); depth = bpp > 0 ? bpp : AG_GetUint8(agConfig,"view.depth"); agView->w = w > 0 ? w : AG_GetUint16(agConfig,"view.w"); agView->h = h > 0 ? h : AG_GetUint16(agConfig,"view.h"); if (AG_GetBool(agConfig,"view.full-screen")) screenflags |= SDL_FULLSCREEN; if (AG_GetBool(agConfig,"view.async-blits")) screenflags |= SDL_HWSURFACE|SDL_ASYNCBLIT; agView->depth = SDL_VideoModeOK(agView->w, agView->h, depth, screenflags); if (agView->depth == 8) screenflags |= SDL_HWPALETTE; #ifdef HAVE_OPENGL if (AG_GetBool(agConfig,"view.opengl")) { screenflags |= SDL_OPENGL; SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); agView->opengl = 1; } #endif if (agView->w < AG_GetUint16(agConfig,"view.min-w") || agView->h < AG_GetUint16(agConfig,"view.min-h")) { AG_SetError(_("The resolution is too small.")); goto fail; } /* Set the video mode. */ agView->v = SDL_SetVideoMode(agView->w, agView->h, agView->depth, screenflags); if (agView->v == NULL) { AG_SetError("Setting %dx%dx%d mode: %s", agView->w, agView->h, agView->depth, SDL_GetError()); goto fail; } agView->stmpl = AG_SurfaceRGBA(1,1, 32, 0, #if AG_BYTEORDER == AG_BIG_ENDIAN 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff #else 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 #endif ); if (agView->stmpl == NULL) { AG_FatalError(NULL); } agVideoFmt = agView->v->format; agSurfaceFmt = agView->stmpl->format; Verbose(_("Video display is %dbpp (%08x,%08x,%08x)\n"), (int)agVideoFmt->BitsPerPixel, (Uint)agVideoFmt->Rmask, (Uint)agVideoFmt->Gmask, (Uint)agVideoFmt->Bmask); Verbose(_("Reference surface is %dbpp (%08x,%08x,%08x,%08x)\n"), (int)agSurfaceFmt->BitsPerPixel, (Uint)agSurfaceFmt->Rmask, (Uint)agSurfaceFmt->Gmask, (Uint)agSurfaceFmt->Bmask, (Uint)agSurfaceFmt->Amask); #ifdef HAVE_OPENGL if (agView->opengl) { if (agVerbose) { Verbose("\tGL Version: %s\n", glGetString(GL_VERSION)); Verbose("\tGL Vendor: %s\n", glGetString(GL_VENDOR)); Verbose("\tGL Renderer: %s\n", glGetString(GL_RENDERER)); } InitGL(); } else #endif /* HAVE_OPENGL */ { SDL_FillRect(agView->v, NULL, AG_COLOR(BG_COLOR)); SDL_UpdateRect(agView->v, 0, 0, agView->v->w, agView->v->h); } AG_SetUint8(agConfig, "view.depth", agView->depth); AG_SetUint16(agConfig, "view.w", agView->w); AG_SetUint16(agConfig, "view.h", agView->h); if (AG_InitGUI(0) == -1) { goto fail; } InitClipRects(w, h); if (!(flags & AG_VIDEO_NOBGCLEAR)) { AG_ClearBackground(); } return (0); fail: Free(agView); agView = NULL; return (-1); } void AG_DestroyVideo(void) { AG_Window *win, *nwin; if (agView == NULL) return; for (win = TAILQ_FIRST(&agView->detach); win != TAILQ_END(&agView->detach); win = nwin) { nwin = TAILQ_NEXT(win, detach); AG_ObjectDestroy(win); } for (win = TAILQ_FIRST(&agView->windows); win != TAILQ_END(&agView->windows); win = nwin) { nwin = TAILQ_NEXT(win, windows); AG_ObjectDestroy(win); } AG_TextDestroy(); AG_SurfaceFree(agView->stmpl); Free(agView->dirty); Free(agView->winModal); Free(agView); AG_ColorsDestroy(); AG_CursorsDestroy(); AG_ClearGlobalKeys(); AG_MutexDestroy(&agGlobalKeysLock); agView = NULL; } void AG_BindGlobalKey(SDLKey keysym, SDLMod keymod, void (*fn)(void)) { struct ag_global_key *gk; gk = Malloc(sizeof(struct ag_global_key)); gk->keysym = keysym; gk->keymod = keymod; gk->fn = fn; gk->fn_ev = NULL; AG_MutexLock(&agGlobalKeysLock); SLIST_INSERT_HEAD(&agGlobalKeys, gk, gkeys); AG_MutexUnlock(&agGlobalKeysLock); } void AG_BindGlobalKeyEv(SDLKey keysym, SDLMod keymod, void (*fn_ev)(AG_Event *)) { struct ag_global_key *gk; gk = Malloc(sizeof(struct ag_global_key)); gk->keysym = keysym; gk->keymod = keymod; gk->fn = NULL; gk->fn_ev = fn_ev; AG_MutexLock(&agGlobalKeysLock); SLIST_INSERT_HEAD(&agGlobalKeys, gk, gkeys); AG_MutexUnlock(&agGlobalKeysLock); } int AG_UnbindGlobalKey(SDLKey keysym, SDLMod keymod) { struct ag_global_key *gk; AG_MutexLock(&agGlobalKeysLock); SLIST_FOREACH(gk, &agGlobalKeys, gkeys) { if (gk->keysym == keysym && gk->keymod == keymod) { SLIST_REMOVE(&agGlobalKeys, gk, ag_global_key, gkeys); AG_MutexUnlock(&agGlobalKeysLock); Free(gk); return (0); } } AG_MutexUnlock(&agGlobalKeysLock); AG_SetError(_("No such key binding")); return (-1); } void AG_ClearGlobalKeys(void) { struct ag_global_key *gk, *gkNext; AG_MutexLock(&agGlobalKeysLock); for (gk = SLIST_FIRST(&agGlobalKeys); gk != SLIST_END(&agGlobalKeys); gk = gkNext) { gkNext = SLIST_NEXT(gk, gkeys); Free(gk); } SLIST_INIT(&agGlobalKeys); AG_MutexUnlock(&agGlobalKeysLock); } #ifdef HAVE_OPENGL static void FreeWidgetResourcesGL(AG_Widget *wid) { AG_Widget *chld; OBJECT_FOREACH_CHILD(chld, wid, ag_widget) { FreeWidgetResourcesGL(chld); } AG_WidgetFreeResourcesGL(wid); } static void RegenWidgetResourcesGL(AG_Widget *wid) { AG_Widget *chld; OBJECT_FOREACH_CHILD(chld, wid, ag_widget) { RegenWidgetResourcesGL(chld); } AG_WidgetRegenResourcesGL(wid); } #endif /* HAVE_OPENGL */ /* * Respond to a VIDEORESIZE event. Must be called from event context and * agView VFS must be locked. */ int AG_ResizeDisplay(int w, int h) { Uint32 flags = agView->v->flags & (SDL_SWSURFACE|SDL_FULLSCREEN| SDL_HWSURFACE|SDL_ASYNCBLIT| SDL_HWPALETTE|SDL_RESIZABLE| SDL_OPENGL); AG_Window *win; SDL_Surface *su; int ow, oh; AG_ClipRect *cr0; #ifdef HAVE_OPENGL /* * Save all of our GL resources since it is not portable to assume * that a display resize will not destroy them. */ if (agView->opengl) { TAILQ_FOREACH(win, &agView->windows, windows) FreeWidgetResourcesGL(WIDGET(win)); } AG_ClearGlyphCache(); #endif /* Resize the display to the requested geometry. */ /* XXX set a minimum! */ if ((su = SDL_SetVideoMode(w, h, 0, flags)) == NULL) { AG_SetError("Cannot resize display to %ux%u: %s", w, h, SDL_GetError()); goto fail; } ow = agView->w; oh = agView->h; agView->v = su; agView->w = w; agView->h = h; AG_SetUint16(agConfig, "view.w", w); AG_SetUint16(agConfig, "view.h", h); /* Update clipping rectangle 0. */ cr0 = &agClipRects[0]; cr0->r.w = w; cr0->r.h = h; cr0->eqns[2][3] = (double)w; cr0->eqns[3][3] = (double)h; #ifdef HAVE_OPENGL if (agView->opengl) { /* Restore our saved GL resources. */ InitGL(); TAILQ_FOREACH(win, &agView->windows, windows) RegenWidgetResourcesGL(WIDGET(win)); } else #endif { /* Clear the background. */ SDL_FillRect(agView->v, NULL, AG_COLOR(BG_COLOR)); SDL_UpdateRect(agView->v, 0, 0, w, h); } /* Update the Agar window geometries. */ TAILQ_FOREACH(win, &agView->windows, windows) { AG_SizeAlloc a; a.x = WIDGET(win)->x; a.y = WIDGET(win)->y; a.w = WIDGET(win)->w; a.h = WIDGET(win)->h; AG_ObjectLock(win); if (win->flags & AG_WINDOW_MAXIMIZED) { AG_WindowSetGeometryMax(win); } else { if (win->flags & AG_WINDOW_HMAXIMIZE) { a.x = 0; a.w = agView->w; } else { if (a.x+a.w > agView->w) { a.x = agView->w - a.w; if (a.x < 0) { a.x = 0; a.w = agView->w; } } } if (win->flags & AG_WINDOW_VMAXIMIZE) { a.y = 0; a.h = agView->h; } else { if (a.y+a.h > agView->h) { a.y = agView->h - a.h; if (a.y < 0) { a.y = 0; a.h = agView->w; } } } AG_WidgetSizeAlloc(win, &a); AG_WindowUpdate(win); } AG_ObjectUnlock(win); } if (agVideoResizeCallback != NULL) { agVideoResizeCallback(w, h); } return (0); fail: return (-1); } void AG_SetVideoResizeCallback(void (*fn)(Uint w, Uint h)) { agVideoResizeCallback = fn; } /* Enter rendering context. */ void AG_BeginRendering(void) { #ifdef AG_DEBUG agRenderingContext = 1; #endif #ifdef HAVE_OPENGL if (agView->opengl) { glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); agClipStateGL[0] = glIsEnabled(GL_CLIP_PLANE0); glEnable(GL_CLIP_PLANE0); agClipStateGL[1] = glIsEnabled(GL_CLIP_PLANE1); glEnable(GL_CLIP_PLANE1); agClipStateGL[2] = glIsEnabled(GL_CLIP_PLANE2); glEnable(GL_CLIP_PLANE2); agClipStateGL[3] = glIsEnabled(GL_CLIP_PLANE3); glEnable(GL_CLIP_PLANE3); } #endif } /* Leave rendering context. */ void AG_EndRendering(void) { #ifdef AG_DEBUG if (agClipRectCount != 1) AG_FatalError("Inconsistent PushClipRect() / PopClipRect()"); #endif #ifdef HAVE_OPENGL if (agView->opengl) { SDL_GL_SwapBuffers(); if (agClipStateGL[0]) { glEnable(GL_CLIP_PLANE0); } else { glDisable(GL_CLIP_PLANE0); } if (agClipStateGL[1]) { glEnable(GL_CLIP_PLANE1); } else { glDisable(GL_CLIP_PLANE1); } if (agClipStateGL[2]) { glEnable(GL_CLIP_PLANE2); } else { glDisable(GL_CLIP_PLANE2); } if (agClipStateGL[3]) { glEnable(GL_CLIP_PLANE3); } else { glDisable(GL_CLIP_PLANE3); } } else #endif if (agView->ndirty > 0) { SDL_UpdateRects(agView->v, agView->ndirty, agView->dirty); agView->ndirty = 0; } #ifdef AG_DEBUG agRenderingContext = 0; #endif } /* Respond to a VIDEOEXPOSE event by redrawing the windows. */ void AG_ViewVideoExpose(void) { AG_Window *win; AG_LockVFS(agView); AG_BeginRendering(); TAILQ_FOREACH(win, &agView->windows, windows) { AG_ObjectLock(win); AG_WindowDraw(win); AG_ObjectUnlock(win); } AG_EndRendering(); AG_UnlockVFS(agView); } /* Queue a video region for update (for FB graphics modes). */ void AG_ViewUpdateFB(const AG_Rect2 *rp) { AG_Rect2 r; int n; if (agView->opengl) return; /* XXX TODO use IntersectRect() against agView */ r = *rp; if (r.x1 < 0) { r.x1 = 0; } if (r.y1 < 0) { r.y1 = 0; } if (r.x2 > agView->w) { r.x2 = agView->w; r.w = r.x2-r.x1; } if (r.y2 > agView->h) { r.y2 = agView->h; r.h = r.y2-r.y1; } if (r.w < 0) { r.x1 = 0; r.x2 = r.w = agView->w; } if (r.h < 0) { r.y1 = 0; r.y2 = r.h = agView->h; } n = agView->ndirty++; if (n+1 > agView->maxdirty) { agView->maxdirty *= 2; agView->dirty = AG_Realloc(agView->dirty, agView->maxdirty * sizeof(SDL_Rect)); } agView->dirty[n].x = r.x1; agView->dirty[n].y = r.y1; agView->dirty[n].w = r.w; agView->dirty[n].h = r.h; } /* Return the named window or NULL if there is no such window. */ AG_Window * AG_FindWindow(const char *name) { AG_Window *win; AG_LockVFS(agView); TAILQ_FOREACH(win, &agView->windows, windows) { if (strcmp(OBJECT(win)->name, name) == 0) break; } AG_UnlockVFS(agView); return (win); } /* Attach a window to a view. */ void AG_ViewAttach(void *child) { AG_Window *win = child; AG_LockVFS(agView); AG_ObjectAttach(agView, win); if (win->flags & AG_WINDOW_KEEPBELOW) { TAILQ_INSERT_HEAD(&agView->windows, win, windows); } else { TAILQ_INSERT_TAIL(&agView->windows, win, windows); } if (win->flags & AG_WINDOW_FOCUSONATTACH) { AG_WindowFocus(win); } AG_SetStyle(win, agView->style); AG_UnlockVFS(agView); } /* * Hide a window (and its children) and place them on a queue, to be * detached at the end of the current event processing cycle. */ void AG_ViewDetach(AG_Window *win) { AG_Window *subwin, *nsubwin; AG_Window *owin; AG_LockVFS(agView); for (subwin = TAILQ_FIRST(&win->subwins); subwin != TAILQ_END(&win->subwins); subwin = nsubwin) { nsubwin = TAILQ_NEXT(subwin, swins); AG_ViewDetach(subwin); } TAILQ_INIT(&win->subwins); AG_WindowHide(win); AG_PostEvent(agView, win, "detached", NULL); TAILQ_REMOVE(&agView->windows, win, windows); TAILQ_INSERT_TAIL(&agView->detach, win, detach); TAILQ_FOREACH(owin, &agView->windows, windows) { TAILQ_FOREACH(subwin, &owin->subwins, swins) { if (subwin == win) break; } if (subwin != NULL) TAILQ_REMOVE(&owin->subwins, subwin, swins); } AG_UnlockVFS(agView); } /* * Release the windows on the detachment queue. Called at the end of the * current event processing cycle. */ static __inline__ void FreeDetachedWindows(void) { AG_Window *win, *nwin; for (win = TAILQ_FIRST(&agView->detach); win != TAILQ_END(&agView->detach); win = nwin) { nwin = TAILQ_NEXT(win, detach); AG_ObjectDetach(win); AG_ObjectDestroy(win); } TAILQ_INIT(&agView->detach); } /* Return a newly allocated surface containing a copy of ss. */ AG_Surface * AG_DupSurface(AG_Surface *ss) { AG_Surface *rs; rs = (AG_Surface *)SDL_ConvertSurface(ss, ss->format, SDL_SWSURFACE | (ss->flags & (SDL_SRCCOLORKEY|SDL_SRCALPHA|SDL_RLEACCEL))); if (rs == NULL) { AG_SetError("SDL_ConvertSurface: %s", SDL_GetError()); return (NULL); } rs->format->alpha = ss->format->alpha; rs->format->colorkey = ss->format->colorkey; return (rs); } /* * Allocate a new surface containing a pixmap of ss scaled to wxh. * The source surface must not be locked by the calling thread. * * XXX very primitive and inefficient */ int AG_ScaleSurface(AG_Surface *ss, Uint16 w, Uint16 h, AG_Surface **ds) { Uint8 r1, g1, b1, a1; Uint8 *pDst; int x, y; int same_fmt; if (*ds == NULL) { *ds = AG_SurfaceNew(w, h, ss->format, ss->flags & (AG_SWSURFACE|AG_SRCALPHA|AG_SRCCOLORKEY)); if (*ds == NULL) { return (-1); } (*ds)->format->alpha = ss->format->alpha; (*ds)->format->colorkey = ss->format->colorkey; same_fmt = 1; } else { //same_fmt = AG_SamePixelFmt(*ds, ss); same_fmt = 0; } if (ss->w == w && ss->h == h) { AG_SurfaceCopy(*ds, ss); return (0); } AG_SurfaceLock(ss); AG_SurfaceLock(*ds); pDst = (Uint8 *)(*ds)->pixels; for (y = 0; y < (*ds)->h; y++) { for (x = 0; x < (*ds)->w; x++) { Uint8 *pSrc = (Uint8 *)ss->pixels + (y*ss->h/(*ds)->h)*ss->pitch + (x*ss->w/(*ds)->w)*ss->format->BytesPerPixel; Uint32 cSrc = AG_GET_PIXEL(ss, pSrc); Uint32 cDst; if (same_fmt) { cDst = cSrc; } else { AG_GetRGBA(cSrc, ss->format, &r1, &g1, &b1, &a1); cDst = AG_MapRGBA((*ds)->format, r1, g1, b1, a1); } AG_PUT_PIXEL((*ds), pDst, cDst); pDst += (*ds)->format->BytesPerPixel; } } AG_SurfaceUnlock(*ds); AG_SurfaceUnlock(ss); return (0); } /* Set the alpha value of all pixels in a surface where a != 0. */ void AG_SetAlphaPixels(AG_Surface *su, Uint8 alpha) { int x, y; AG_SurfaceLock(su); for (y = 0; y < su->h; y++) { for (x = 0; x < su->w; x++) { Uint8 *dst = (Uint8 *)su->pixels + y*su->pitch + x*su->format->BytesPerPixel; Uint8 r, g, b, a; AG_GetRGBA(*(Uint32 *)dst, su->format, &r, &g, &b, &a); if (a != 0) a = alpha; AG_PUT_PIXEL(su, dst, AG_MapRGBA(su->format, r, g, b, a)); } } AG_SurfaceUnlock(su); } int AG_SetRefreshRate(int fps) { AG_LockVFS(agView); if (fps == -1) { agView->rNom = 1000/AG_GetUint(agConfig,"view.nominal-fps"); agView->rCur = 0; goto out; } AG_SetUint(agConfig, "view.nominal-fps", fps); agView->rNom = 1000/fps; agView->rCur = 0; out: AG_UnlockVFS(agView); return (0); } #ifdef HAVE_OPENGL /* * Update the contents of an existing OpenGL texture from a given * surface. Must be called from rendering context. */ void AG_UpdateTexture(AG_Surface *sourcesu, int texture) { AG_Surface *texsu; int w, h; /* * Convert to the GL_RGBA/GL_UNSIGNED_BYTE format and adjust for * power-of-two dimensions. * TODO check for GL_ARB_texture_non_power_of_two. */ w = PowOf2i(sourcesu->w); h = PowOf2i(sourcesu->h); texsu = AG_SurfaceRGBA(w,h, 32, 0, #if AG_BYTEORDER == AG_BIG_ENDIAN 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff #else 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 #endif ); if (texsu == NULL) { AG_FatalError(NULL); } AG_SurfaceCopy(texsu, sourcesu); /* Upload as an OpenGL texture. */ glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, texsu->pixels); glBindTexture(GL_TEXTURE_2D, 0); AG_SurfaceFree(texsu); } static void CopyColorKeySurface(AG_Surface *suTex, AG_Surface *suSrc) { Uint8 *pSrc; int x, y; pSrc = suSrc->pixels; for (y = 0; y < suSrc->h; y++) { for (x = 0; x < suSrc->w; x++) { Uint32 c = AG_GET_PIXEL(suSrc,pSrc); Uint8 r,g,b; if (c != suSrc->format->colorkey) { AG_GetRGB(c, suSrc->format, &r,&g,&b); AG_PUT_PIXEL2(suTex, x,y, AG_MapRGBA(suTex->format, r,g,b,AG_ALPHA_OPAQUE)); } else { AG_PUT_PIXEL2(suTex, x,y, AG_MapRGBA(suTex->format, 0,0,0,AG_ALPHA_TRANSPARENT)); } pSrc += suSrc->format->BytesPerPixel; } } } /* * Generate an OpenGL texture from an Agar surface. * Returns the texture handle and 4 coordinates into texcoord. * * Must be called from widget rendering context only. */ Uint AG_SurfaceTexture(AG_Surface *suSrc, float *texcoord) { AG_Surface *suTex; int w = PowOf2i(suSrc->w); int h = PowOf2i(suSrc->h); GLuint texture; /* Convert to the GL_RGBA/GL_UNSIGNED_BYTE format. */ if (texcoord != NULL) { texcoord[0] = 0.0f; texcoord[1] = 0.0f; texcoord[2] = (GLfloat)suSrc->w / w; texcoord[3] = (GLfloat)suSrc->h / h; } suTex = AG_SurfaceRGBA(w,h, 32, 0, #if AG_BYTEORDER == AG_BIG_ENDIAN 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff #else 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 #endif ); if (suTex == NULL) { AG_FatalError(NULL); } if (suSrc->flags & AG_SRCCOLORKEY) { CopyColorKeySurface(suTex, suSrc); } else { AG_SurfaceCopy(suTex, suSrc); } /* Upload as an OpenGL texture. */ glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); #if 0 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); #else glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); #endif glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, suTex->pixels); glBindTexture(GL_TEXTURE_2D, 0); AG_SurfaceFree(suTex); return (texture); } /* * Capture the contents of the OpenGL display into a surface. Must be * invoked in rendering context. */ AG_Surface * AG_CaptureGLView(void) { Uint8 *pixels; pixels = Malloc(agView->w*agView->h*3); glReadPixels(0, 0, agView->w, agView->h, GL_RGB, GL_UNSIGNED_BYTE, pixels); AG_FlipSurface(pixels, agView->h, agView->w*3); return AG_SurfaceFromPixelsRGBA(pixels, agView->w, agView->h, 0, 24, agView->w*3, 0x000000ff, 0x0000ff00, 0x00ff0000); } #endif /* HAVE_OPENGL */ /* * Blend the specified components with the pixel at s:[x,y], using the * given alpha function. * * Clipping is not done; the destination surface must be locked. */ void AG_BlendPixelRGBA(AG_Surface *s, Uint8 *pDst, Uint8 sR, Uint8 sG, Uint8 sB, Uint8 sA, AG_BlendFn func) { Uint32 cDst; Uint8 dR, dG, dB, dA; int alpha = 0; cDst = AG_GET_PIXEL(s, pDst); if ((s->flags & AG_SRCCOLORKEY) && (cDst == s->format->colorkey)) { AG_PUT_PIXEL(s, pDst, AG_MapRGBA(s->format, sR,sG,sB,sA)); } else { AG_GetRGBA(cDst, s->format, &dR, &dG, &dB, &dA); switch (func) { case AG_ALPHA_OVERLAY: alpha = dA+sA; break; case AG_ALPHA_SRC: alpha = sA; break; case AG_ALPHA_DST: alpha = dA; break; case AG_ALPHA_ONE_MINUS_DST: alpha = 1-dA; break; case AG_ALPHA_ONE_MINUS_SRC: alpha = 1-sA; break; } alpha = (alpha < 0) ? 0 : (alpha > 255) ? 255 : alpha; AG_PUT_PIXEL(s, pDst, AG_MapRGBA(s->format, (((sR - dR) * sA) >> 8) + dR, (((sG - dG) * sA) >> 8) + dG, (((sB - dB) * sA) >> 8) + dB, (Uint8)alpha)); } } void AG_ViewCapture(void) { char path[AG_PATHNAME_MAX]; if (AG_DumpSurface(agView->v, path) == 0) { AG_TextTmsg(AG_MSG_INFO, 1000, _("Screenshot saved to %s."), path); } else { AG_TextMsg(AG_MSG_ERROR, "%s", AG_GetError()); } } /* Dump a surface to a JPEG image. */ int AG_DumpSurface(AG_Surface *pSu, char *path_save) { #ifdef HAVE_JPEG AG_Surface *su; char path[AG_PATHNAME_MAX]; struct jpeg_error_mgr jerrmgr; struct jpeg_compress_struct jcomp; Uint8 *jcopybuf; FILE *fp; Uint seq = 0; int fd; JSAMPROW row[1]; int x; AG_GetString(agConfig, "save-path", path, sizeof(path)); Strlcat(path, AG_PATHSEP, sizeof(path)); Strlcat(path, "screenshot", sizeof(path)); if (AG_MkDir(path) == -1 && errno != EEXIST) { AG_SetError("mkdir %s: %s", path, strerror(errno)); return (-1); } if (!agView->opengl || pSu != agView->v) { su = pSu; } else { #ifdef HAVE_OPENGL su = AG_CaptureGLView(); #else su = NULL; #endif } for (;;) { char file[AG_PATHNAME_MAX]; Snprintf(file, sizeof(file), "%s/%s%u.jpg", path, agProgName, seq++); if ((fd = open(file, O_WRONLY|O_CREAT|O_EXCL, 0600)) == -1) { if (errno == EEXIST) { continue; } else { AG_SetError("%s: %s", file, strerror(errno)); goto out; } } break; } if (path_save != NULL) Strlcpy(path_save, path, AG_PATHNAME_MAX); if ((fp = fdopen(fd, "wb")) == NULL) { AG_SetError("fdopen: %s", strerror(errno)); return (-1); } jcomp.err = jpeg_std_error(&jerrmgr); jpeg_create_compress(&jcomp); jcomp.image_width = su->w; jcomp.image_height = su->h; jcomp.input_components = 3; jcomp.in_color_space = JCS_RGB; jpeg_set_defaults(&jcomp); jpeg_set_quality(&jcomp, agScreenshotQuality, TRUE); jpeg_stdio_dest(&jcomp, fp); jcopybuf = Malloc(su->w*3); jpeg_start_compress(&jcomp, TRUE); while (jcomp.next_scanline < jcomp.image_height) { Uint8 *pSrc = (Uint8 *)su->pixels + jcomp.next_scanline*su->pitch; Uint8 *pDst = jcopybuf; Uint8 r, g, b; for (x = agView->w; x > 0; x--) { AG_GetRGB(AG_GET_PIXEL(su,pSrc), su->format, &r,&g,&b); *pDst++ = r; *pDst++ = g; *pDst++ = b; pSrc += su->format->BytesPerPixel; } row[0] = jcopybuf; jpeg_write_scanlines(&jcomp, row, 1); } jpeg_finish_compress(&jcomp); jpeg_destroy_compress(&jcomp); fclose(fp); close(fd); Free(jcopybuf); out: #ifdef HAVE_OPENGL if (agView->opengl && su != pSu) AG_SurfaceFree(su); #endif return (0); #else AG_SetError(_("Screenshot feature requires libjpeg")); return (-1); #endif } /* Flip the lines of a frame buffer; useful with glReadPixels(). */ void AG_FlipSurface(Uint8 *src, int h, int pitch) { Uint8 *tmp = Malloc(pitch); int h2 = h >> 1; Uint8 *p1 = &src[0]; Uint8 *p2 = &src[(h-1)*pitch]; int i; for (i = 0; i < h2; i++) { memcpy(tmp, p1, pitch); memcpy(p1, p2, pitch); memcpy(p2, tmp, pitch); p1 += pitch; p2 -= pitch; } Free(tmp); } /* * Obtain the hue/saturation/value of a given RGB triplet. * Note that the hue is lost as saturation approaches 0. */ void AG_RGB2HSV(Uint8 r, Uint8 g, Uint8 b, float *h, float *s, float *v) { float vR, vG, vB; float vMin, vMax, deltaMax; float deltaR, deltaG, deltaB; vR = (float)r/255.0F; vG = (float)g/255.0F; vB = (float)b/255.0F; vMin = MIN3(vR, vG, vB); vMax = MAX3(vR, vG, vB); deltaMax = vMax - vMin; *v = vMax; if (deltaMax == 0.0) { /* This is a gray color (zero hue, no saturation). */ *h = 0.0; *s = 0.0; } else { *s = deltaMax / vMax; deltaR = ((vMax - vR)/6.0F + deltaMax/2.0F) / deltaMax; deltaG = ((vMax - vG)/6.0F + deltaMax/2.0F) / deltaMax; deltaB = ((vMax - vB)/6.0F + deltaMax/2.0F) / deltaMax; if (vR == vMax) { *h = (deltaB - deltaG)*360.0F; } else if (vG == vMax) { *h = 120.0F + (deltaR - deltaB)*360.0F; /* 1/3 */ } else if (vB == vMax) { *h = 240.0F + (deltaG - deltaR)*360.0F; /* 2/3 */ } if (*h < 0.0F) (*h)++; if (*h > 360.0F) (*h)--; } } /* Convert hue/saturation/value to RGB. */ void AG_HSV2RGB(float h, float s, float v, Uint8 *r, Uint8 *g, Uint8 *b) { float var[3]; float vR, vG, vB, hv; int iv; if (s == 0.0) { *r = (Uint8)v*255; *g = (Uint8)v*255; *b = (Uint8)v*255; return; } hv = h/60.0F; iv = Floor(hv); var[0] = v * (1.0F - s); var[1] = v * (1.0F - s*(hv - iv)); var[2] = v * (1.0F - s*(1.0F - (hv - iv))); switch (iv) { case 0: vR = v; vG = var[2]; vB = var[0]; break; case 1: vR = var[1]; vG = v; vB = var[0]; break; case 2: vR = var[0]; vG = v; vB = var[2]; break; case 3: vR = var[0]; vG = var[1]; vB = v; break; case 4: vR = var[2]; vG = var[0]; vB = v; break; default: vR = v; vG = var[0]; vB = var[1]; break; } *r = vR*255; *g = vG*255; *b = vB*255; } #ifdef AG_DEBUG /* * Update the performance counters. * XXX remove this once the graph widget implements polling. */ static __inline__ void PerfMonitorUpdate(void) { static int einc = 0; AG_FixedPlotterDatum(agPerfFPS, agView->rCur); AG_FixedPlotterDatum(agPerfEvnts, agEventAvg * 30 / 10); AG_FixedPlotterDatum(agPerfIdle, agIdleAvg); AG_FixedPlotterScroll(agPerfGraph, 1); if (++einc == 1) { agEventAvg = 0; einc = 0; } } AG_Window * AG_EventShowPerfGraph(void) { AG_WindowShow(agPerfWindow); return (agPerfWindow); } static void PerfMonitorInit(void) { AG_Label *lbl; agPerfWindow = AG_WindowNewNamed(0, "event-fps-counter"); AG_WindowSetCaption(agPerfWindow, _("Performance counters")); AG_WindowSetPosition(agPerfWindow, AG_WINDOW_LOWER_CENTER, 0); lbl = AG_LabelNewPolled(agPerfWindow, AG_LABEL_HFILL, "%dms (nom %dms), %d evnt, %dms idle", &agView->rCur, &agView->rNom, &agEventAvg, &agIdleAvg); AG_LabelSizeHint(lbl, 1, "000ms (nom 000ms), 00 evnt, 000ms idle"); agPerfGraph = AG_FixedPlotterNew(agPerfWindow, AG_FIXED_PLOTTER_LINES, AG_FIXED_PLOTTER_XAXIS| AG_FIXED_PLOTTER_EXPAND); agPerfFPS = AG_FixedPlotterCurve(agPerfGraph, "refresh", 0,160,0, 99); agPerfEvnts = AG_FixedPlotterCurve(agPerfGraph, "event", 0,0,180, 99); agPerfIdle = AG_FixedPlotterCurve(agPerfGraph, "idle", 180,180,180, 99); } #endif /* AG_DEBUG */ /* * Try to ensure a fixed frame rate, and idle as much as possible. * TODO provide MD hooks for finer idling. */ void AG_EventLoop_FixedFPS(void) { SDL_Event ev; AG_Window *win; Uint32 Tr1, Tr2 = 0; #ifdef AG_DEBUG PerfMonitorInit(); #endif Tr1 = SDL_GetTicks(); for (;;) { Tr2 = SDL_GetTicks(); if (Tr2-Tr1 >= agView->rNom) { AG_LockVFS(agView); AG_BeginRendering(); TAILQ_FOREACH(win, &agView->windows, windows) { AG_ObjectLock(win); AG_WindowDraw(win); AG_ObjectUnlock(win); } AG_EndRendering(); AG_UnlockVFS(agView); /* Recalibrate the effective refresh rate. */ Tr1 = SDL_GetTicks(); agView->rCur = agView->rNom - (Tr1-Tr2); #ifdef AG_DEBUG if (agPerfWindow->visible) PerfMonitorUpdate(); #endif if (agView->rCur < 1) { agView->rCur = 1; } } else if (SDL_PollEvent(&ev) != 0) { if (AG_ProcessEvent(&ev) == -1) return; #ifdef AG_DEBUG agEventAvg++; #endif } else if (AG_TIMEOUTS_QUEUED()) { /* Safe */ AG_ProcessTimeouts(Tr2); } else if (agView->rCur > agIdleThresh) { SDL_Delay(agView->rCur - agIdleThresh); #ifdef AG_DEBUG agIdleAvg = SDL_GetTicks() - Tr2; } else { agIdleAvg = 0; } #else } #endif } } static void UnminimizeWindow(AG_Event *event) { AG_Window *win = AG_PTR(1); AG_WindowUnminimize(win); } /* * Process an SDL event. Returns 1 if the event was processed in some * way, -1 if application is exiting. */ int AG_ProcessEvent(SDL_Event *ev) { int rv = 0; AG_LockVFS(agView); switch (ev->type) { case SDL_MOUSEMOTION: #ifdef OPENGL_INVERTED_Y if (agView->opengl) { ev->motion.y = agView->h - ev->motion.y; ev->motion.yrel = -ev->motion.yrel; } #endif rv = AG_WindowEvent(ev); break; case SDL_MOUSEBUTTONUP: case SDL_MOUSEBUTTONDOWN: #ifdef OPENGL_INVERTED_Y if (agView->opengl) ev->button.y = agView->h - ev->button.y; #endif if (AG_WindowEvent(ev) == 0 && agBgPopupMenu && ev->type == SDL_MOUSEBUTTONDOWN && (ev->button.button == SDL_BUTTON_MIDDLE || ev->button.button == SDL_BUTTON_RIGHT)) { AG_Menu *me; AG_MenuItem *mi; AG_Window *win; int x, y; me = AG_MenuNew(NULL, 0); mi = me->itemSel = AG_MenuAddItem(me, NULL); TAILQ_FOREACH_REVERSE(win, &agView->windows, ag_windowq, windows) { if (strcmp(win->caption, "win-popup") == 0) { continue; } AG_MenuAction(mi, win->caption, NULL, UnminimizeWindow, "%p", win); } AG_MouseGetState(&x, &y); AG_MenuExpand(me, mi, x+4, y+4); rv = 1; } break; case SDL_KEYDOWN: #if 0 fprintf(stderr, "[DOWN] Key=%d(%c), Mod=%d\n", (int)ev->key.keysym.sym, (char)ev->key.keysym.sym, (int)ev->key.keysym.mod); #endif { struct ag_global_key *gk; AG_MutexLock(&agGlobalKeysLock); SLIST_FOREACH(gk, &agGlobalKeys, gkeys) { if (gk->keysym == ev->key.keysym.sym && (gk->keymod == KMOD_NONE || ev->key.keysym.mod & gk->keymod)) { if (gk->fn != NULL) { gk->fn(); } else if (gk->fn_ev != NULL) { gk->fn_ev(NULL); } rv = 1; } } AG_MutexUnlock(&agGlobalKeysLock); } /* FALLTHROUGH */ case SDL_KEYUP: #if 0 fprintf(stderr, "[ UP] Key=%d(%c), Mod=%d\n", (int)ev->key.keysym.sym, (char)ev->key.keysym.sym, (int)ev->key.keysym.mod); #endif rv = AG_WindowEvent(ev); break; case SDL_JOYAXISMOTION: case SDL_JOYBUTTONDOWN: case SDL_JOYBUTTONUP: rv = AG_WindowEvent(ev); break; case SDL_VIDEORESIZE: AG_ResizeDisplay(ev->resize.w, ev->resize.h); rv = 1; break; case SDL_VIDEOEXPOSE: AG_ViewVideoExpose(); rv = 1; break; case SDL_QUIT: #if 0 if (!agTerminating && AG_FindEventHandler(agWorld, "quit") != NULL) { AG_PostEvent(NULL, agWorld, "quit", NULL); break; } #endif /* FALLTHROUGH */ case SDL_USEREVENT: AG_UnlockVFS(agView); agTerminating = 1; return (-1); } FreeDetachedWindows(); AG_UnlockVFS(agView); return (rv); } Uint8 AG_MouseGetState(int *x, int *y) { Uint8 rv; rv = SDL_GetMouseState(x, y); #ifdef OPENGL_INVERTED_Y if (agView->opengl && y != NULL) *y = agView->h - *y; #endif return (rv); } #define COMPUTE_SHIFTLOSS(mask, shift, loss) \ shift = 0; \ loss = 8; \ if (mask != 0) { \ for (m = mask ; (m & 0x01) == 0; m >>= 1) { \ shift++; \ } \ while ((m & 0x01) != 0) { \ loss--; \ m >>= 1; \ } \ } /* Specify a packed-pixel format from three 32-bit bitmasks. */ AG_PixelFormat * AG_PixelFormatRGB(int bpp, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask) { AG_PixelFormat *pf; Uint32 m; pf = Malloc(sizeof(AG_PixelFormat)); pf->alpha = AG_ALPHA_OPAQUE; pf->BitsPerPixel = bpp; pf->BytesPerPixel = (bpp+7)/8; pf->palette = NULL; pf->Rmask = Rmask; pf->Gmask = Gmask; pf->Bmask = Bmask; pf->Amask = 0x00000000; COMPUTE_SHIFTLOSS(pf->Rmask, pf->Rshift, pf->Rloss); COMPUTE_SHIFTLOSS(pf->Gmask, pf->Gshift, pf->Gloss); COMPUTE_SHIFTLOSS(pf->Bmask, pf->Bshift, pf->Bloss); return (pf); } /* Specify a packed-pixel format from four 32-bit bitmasks. */ AG_PixelFormat * AG_PixelFormatRGBA(int bpp, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask) { AG_PixelFormat *pf; Uint32 m; pf = Malloc(sizeof(AG_PixelFormat)); pf->alpha = AG_ALPHA_OPAQUE; pf->BitsPerPixel = bpp; pf->BytesPerPixel = (bpp+7)/8; pf->palette = NULL; pf->Rmask = Rmask; pf->Gmask = Gmask; pf->Bmask = Bmask; pf->Amask = Amask; COMPUTE_SHIFTLOSS(pf->Rmask, pf->Rshift, pf->Rloss); COMPUTE_SHIFTLOSS(pf->Gmask, pf->Gshift, pf->Gloss); COMPUTE_SHIFTLOSS(pf->Bmask, pf->Bshift, pf->Bloss); COMPUTE_SHIFTLOSS(pf->Amask, pf->Ashift, pf->Aloss); return (pf); } /* * Specify an indexed pixel format. If bpp=2, the palette is initialized to * [0 = white] and [1 = black], otherwise the palette is initialized to all * black. */ AG_PixelFormat * AG_PixelFormatIndexed(int bpp) { AG_PixelFormat *pf; AG_Palette *pal; pf = Malloc(sizeof(AG_PixelFormat)); pf->alpha = AG_ALPHA_OPAQUE; pf->BitsPerPixel = bpp; pf->BytesPerPixel = (bpp+7)/8; pal = pf->palette = Malloc(sizeof(AG_Palette)); pal->ncolors = 1<colors = Malloc(pal->ncolors*sizeof(AG_Color)); if (bpp == 2) { pal->colors[0].r = 255; pal->colors[0].g = 255; pal->colors[0].b = 255; pal->colors[1].r = 0; pal->colors[1].g = 0; pal->colors[1].b = 0; } else { memset(pal->colors, 0, pal->ncolors*sizeof(AG_Color)); } pf->Rmask = pf->Gmask = pf->Bmask = pf->Amask = 0; pf->Rloss = pf->Gloss = pf->Bloss = pf->Aloss = 8; pf->Rshift = pf->Gshift = pf->Bshift = pf->Ashift = 0; return (pf); } AG_PixelFormat * AG_PixelFormatDup(const AG_PixelFormat *pf) { AG_PixelFormat *pfd; pfd = Malloc(sizeof(AG_PixelFormat)); if (pf->palette != NULL) { pfd->palette = Malloc(pf->palette->ncolors*sizeof(AG_Color)); memcpy(pfd->palette->colors, pf->palette->colors, pf->palette->ncolors*sizeof(AG_Color)); } else { pfd->palette = NULL; } pfd->BitsPerPixel = pf->BitsPerPixel; pfd->BytesPerPixel = pf->BytesPerPixel; pfd->Rloss = pf->Rloss; pfd->Gloss = pf->Gloss; pfd->Bloss = pf->Bloss; pfd->Aloss = pf->Aloss; pfd->Rshift = pf->Rshift; pfd->Gshift = pf->Gshift; pfd->Bshift = pf->Bshift; pfd->Ashift = pf->Ashift; pfd->Rmask = pf->Rmask; pfd->Gmask = pf->Gmask; pfd->Bmask = pf->Bmask; pfd->Amask = pf->Amask; pfd->colorkey = pf->colorkey; pfd->alpha = pf->alpha; return (pfd); } void AG_PixelFormatFree(AG_PixelFormat *fmt) { Free(fmt->palette); free(fmt); } #undef COMPUTE_SHIFTLOSS static __inline__ Uint32 SDLFlags(Uint flags) { Uint32 sdlFlags = SDL_SWSURFACE; if (flags & AG_HWSURFACE) { sdlFlags |= SDL_HWSURFACE; } if (flags & AG_SRCCOLORKEY) { sdlFlags |= SDL_SRCCOLORKEY; } if (flags & AG_SRCALPHA) { sdlFlags |= SDL_SRCALPHA; } return (sdlFlags); } /* Create a new surface of the specified pixel format. */ AG_Surface * AG_SurfaceNew(Uint w, Uint h, AG_PixelFormat *fmt, Uint flags) { AG_Surface *s; s = (AG_Surface *)SDL_CreateRGBSurface(SDLFlags(flags), w, h, fmt->BitsPerPixel, fmt->Rmask, fmt->Gmask, fmt->Bmask, fmt->Amask); if (s == NULL) { AG_SetError("SDL_CreateRGBSurface(%ux%ux%u): %s", w, h, fmt->BitsPerPixel, SDL_GetError()); return (NULL); } if (fmt->palette != NULL) { SDL_SetPalette((SDL_Surface *)s, SDL_LOGPAL, (SDL_Color *)fmt->palette, 0, fmt->palette->ncolors); } return (s); } /* Create an empty surface. */ AG_Surface * AG_SurfaceEmpty(void) { return (AG_Surface *)SDL_CreateRGBSurface(SDL_SWSURFACE, 0,0,8,0,0,0,0); } /* Create a new surface of the specified pixel format. */ AG_Surface * AG_SurfaceIndexed(Uint w, Uint h, int bpp, Uint flags) { AG_Surface *s; s = (AG_Surface *)SDL_CreateRGBSurface(SDLFlags(flags), w,h, bpp, 0,0,0,0); if (s == NULL) { AG_SetError("SDL_CreateRGBSurface(%ux%ux%u): %s", w, h, bpp, SDL_GetError()); return (NULL); } return (s); } /* Create a new surface with the specified RGB pixel-packing format. */ AG_Surface * AG_SurfaceRGB(Uint w, Uint h, int bpp, Uint flags, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask) { AG_Surface *s; s = (AG_Surface *)SDL_CreateRGBSurface(SDLFlags(flags), w,h, bpp, Rmask,Gmask,Bmask,0); if (s == NULL) { AG_SetError("SDL_CreateRGBSurface(%ux%ux%u): %s", w, h, bpp, SDL_GetError()); return (NULL); } return (s); } /* Create a new surface with the specified RGB pixel-packing format. */ AG_Surface * AG_SurfaceRGBA(Uint w, Uint h, int bpp, Uint flags, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask) { AG_Surface *s; s = (AG_Surface *)SDL_CreateRGBSurface(SDLFlags(flags), w,h, bpp, Rmask,Gmask,Bmask,Amask); if (s == NULL) { AG_SetError("SDL_CreateRGBSurface(%ux%ux%u): %s", w, h, bpp, SDL_GetError()); return (NULL); } return (s); } /* Create a new surface from pixel data in the specified packed RGB format. */ AG_Surface * AG_SurfaceFromPixelsRGB(void *pixels, Uint w, Uint h, int bpp, int pitch, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask) { AG_Surface *s; s = (AG_Surface *)SDL_CreateRGBSurfaceFrom(pixels, (int)w, (int)h, bpp, pitch, Rmask, Gmask, Bmask, 0); return (s); } /* Create a new surface from pixel data in the specified packed RGBA format. */ AG_Surface * AG_SurfaceFromPixelsRGBA(void *pixels, Uint w, Uint h, int bpp, int pitch, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask) { AG_Surface *s; s = (AG_Surface *)SDL_CreateRGBSurfaceFrom(pixels, (int)w, (int)h, bpp, pitch, Rmask, Gmask, Bmask, Amask); return (s); } /* Create a new surface from a .bmp file. */ AG_Surface * AG_SurfaceFromBMP(const char *path) { AG_Surface *s; if ((s = (AG_Surface *)SDL_LoadBMP(path)) == NULL) { AG_SetError("SDL_LoadBMP(%s): %s", path, SDL_GetError()); return (NULL); } return (s); } AG_Surface * AG_SurfaceFromSDL(AG_Surface *su) { return AG_DupSurface(su); } SDL_Surface * AG_SurfaceToSDL(AG_Surface *su) { return (SDL_Surface *)AG_DupSurface(su); } AG_Surface * AG_SurfaceFromSurface(AG_Surface *su, AG_PixelFormat *fmt, Uint flags) { return (AG_Surface *)SDL_ConvertSurface(su, fmt, SDLFlags(flags)); } void AG_SurfaceCopy(AG_Surface *ds, AG_Surface *ss) { SDL_Surface *dsSDL = (SDL_Surface *)ds; SDL_Surface *ssSDL = (SDL_Surface *)ss; Uint32 aflagsSave = ssSDL->flags & (AG_SRCALPHA|AG_RLEACCEL); Uint8 alphaSave = ssSDL->format->alpha; Uint32 cflagsSave = ssSDL->flags & (SDL_SRCCOLORKEY|SDL_RLEACCEL); Uint32 ckeySave = ssSDL->format->colorkey; SDL_SetAlpha(ssSDL, 0, 0); SDL_SetColorKey(ssSDL, 0, 0); SDL_BlitSurface(ssSDL, NULL, dsSDL, NULL); SDL_SetAlpha(ssSDL, aflagsSave, alphaSave); SDL_SetColorKey(ssSDL, cflagsSave, ckeySave); } /* Free the specified surface. */ void AG_SurfaceFree(AG_Surface *s) { SDL_FreeSurface((SDL_Surface *)s); } AG_ObjectClass agDisplayClass = { "AG_Display", sizeof(AG_Display), { 0,0 }, NULL, /* init */ NULL, /* free */ NULL, /* destroy */ NULL, /* load */ NULL, /* save */ NULL /* edit */ };