/* * Copyright (c) 2006-2023 Julien Nadeau Carriere * * 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. */ /* * High-level texture object. */ #include #include #include #include /* Create a new texture object. */ SG_Texture * SG_TextureNew(void *parent, const char *name) { SG_Texture *tex; tex = Malloc(sizeof(SG_Texture)); AG_ObjectInit(tex, &sgTextureClass); if (name) { AG_ObjectSetNameS(tex, name); } else { OBJECT(tex)->flags |= AG_OBJECT_NAME_ONATTACH; } AG_ObjectAttach(parent, tex); return (tex); } static void Init(void *_Nonnull obj) { SG_Texture *tex = obj; tex->flags = 0; tex->w = 0; tex->h = 0; tex->shininess = 0.0; tex->emissive = M_ColorGray(0.5); tex->ambient = M_ColorGray(0.2); tex->diffuse = M_ColorGray(0.8); tex->specular = M_ColorBlack(); TAILQ_INIT(&tex->progs); tex->nProgs = 0; tex->surface = NULL; tex->nSurfaces = 0; TAILQ_INIT(&tex->surfaces); TAILQ_INIT(&tex->vtex); } static void Reset(void *_Nonnull obj) { SG_Texture *tex = obj; SG_TextureProgram *tp, *tpNext; SG_TextureSurface *tsu, *tsuNext; for (tp = TAILQ_FIRST(&tex->progs); tp != TAILQ_END(&tex->progs); tp = tpNext) { tpNext = TAILQ_NEXT(tp, programs); Free(tp); } TAILQ_INIT(&tex->progs); tex->nProgs = 0; for (tsu = TAILQ_FIRST(&tex->surfaces); tsu != TAILQ_END(&tex->surfaces); tsu = tsuNext) { tsuNext = TAILQ_NEXT(tsu, surfaces); if (!(tsu->flags & SG_TEXTURE_SURFACE_NODUP)) { AG_SurfaceFree(tsu->su); } Free(tsu); } TAILQ_INIT(&tex->surfaces); tex->nSurfaces = 0; AG_SurfaceFree(tex->surface); tex->surface = NULL; } static int Load(void *_Nonnull obj, AG_DataSource *_Nonnull ds, const AG_Version *_Nonnull ver) { SG_Texture *tex = obj; int i, count; tex->flags &= ~(SG_TEXTURE_SAVED); tex->flags |= (AG_ReadUint32(ds) & SG_TEXTURE_SAVED); tex->w = (Uint)AG_ReadUint32(ds); tex->h = (Uint)AG_ReadUint32(ds); tex->shininess = M_ReadReal(ds); tex->emissive = M_ReadColor(ds); tex->ambient = M_ReadColor(ds); tex->diffuse = M_ReadColor(ds); tex->specular = M_ReadColor(ds); /* Load program references. */ if ((count = (int)AG_ReadUint8(ds)) > SG_TEXTURE_PROGS_MAX) { AG_SetError("Bad program count"); return (-1); } for (i = 0; i < count; i++) { SG_TextureProgram *tp; tp = SG_TextureAddProgram(tex, NULL); AG_CopyString(tp->progName, ds, sizeof(tp->progName)); } /* Load surfaces. */ if ((count = (int)AG_ReadUint8(ds)) > SG_TEXTURE_SURFACES_MAX) { AG_SetError("Bad surface count"); return (-1); } for (i = 0; i < count; i++) { AG_Surface *su; SG_TextureSurface *tsu; Uint flags; flags = (Uint)AG_ReadUint8(ds); if ((su = AG_ReadSurface(ds)) == NULL) { AG_SetError("Reading surface %d: %s", i, AG_GetError()); return (-1); } if ((tsu = SG_TextureAddSurface(tex, su)) == NULL) { AG_SurfaceFree(su); return (-1); } AG_SurfaceFree(su); tsu->flags &= ~(SG_TEXTURE_SURFACE_SAVED| SG_TEXTURE_SURFACE_NODUP); tsu->flags |= flags; AG_ReadRect(&tsu->rSrc, ds); AG_ReadRect(&tsu->rDst, ds); } return SG_TextureCompile(tex); } static int Save(void *_Nonnull obj, AG_DataSource *_Nonnull ds) { SG_Texture *tex = obj; SG_TextureProgram *tp; SG_TextureSurface *tsu; AG_WriteUint32(ds, tex->flags & SG_TEXTURE_SAVED); AG_WriteUint32(ds, (Uint32)tex->w); AG_WriteUint32(ds, (Uint32)tex->h); M_WriteReal(ds, tex->shininess); M_WriteColor(ds, &tex->emissive); M_WriteColor(ds, &tex->ambient); M_WriteColor(ds, &tex->diffuse); M_WriteColor(ds, &tex->specular); AG_WriteUint8(ds, (Uint32)tex->nProgs); TAILQ_FOREACH(tp, &tex->progs, programs) { AG_WriteUint8(ds, (Uint8)tp->flags & SG_TEXTURE_PROGRAM_SAVED); AG_WriteString(ds, OBJECT(tp->prog)->name); } AG_WriteUint16(ds, (Uint16)tex->nSurfaces); TAILQ_FOREACH(tsu, &tex->surfaces, surfaces) { AG_WriteUint8(ds, (Uint8)tsu->flags & SG_TEXTURE_SURFACE_SAVED); AG_WriteSurface(ds, tsu->su); AG_WriteRect(ds, &tsu->rSrc); AG_WriteRect(ds, &tsu->rDst); } return (0); } static void SelectColor(AG_Event *_Nonnull event) { AG_HSVPal *pal = AG_HSVPAL_PTR(1); void *color = AG_PTR(2); AG_Mutex *lock = AG_PTR(3); M_BindRealMp(pal, "RGBAv", color, lock); } static void PollSurfaces(AG_Event *_Nonnull event) { AG_Tlist *tl = AG_TLIST_SELF(); SG_Texture *tex = SG_TEXTURE_PTR(1); AG_TlistItem *it; SG_TextureSurface *tsu; int n = 0; AG_TlistClear(tl); AG_ObjectLock(tex); TAILQ_FOREACH(tsu, &tex->surfaces, surfaces) { it = AG_TlistAdd(tl, NULL, "Surface %d\n(%dx%dx%d)", n++, tsu->su->w, tsu->su->h, tsu->su->format.BitsPerPixel); it->p1 = tsu; it->cat = "texture-surface"; AG_TlistSetIcon(tl, it, tsu->su); } AG_ObjectUnlock(tex); AG_TlistRestore(tl); } /* Add a new surface to a texture object. */ SG_TextureSurface * SG_TextureAddSurface(SG_Texture *tex, const AG_Surface *su) { SG_TextureSurface *tsu; if ((tex->nSurfaces+1) > SG_TEXTURE_SURFACES_MAX) { AG_SetError("Too many surfaces"); return (NULL); } if ((tsu = TryMalloc(sizeof(SG_TextureSurface))) == NULL) { return (NULL); } if ((tsu->su = AG_SurfaceNew(agSurfaceFmt, su->w, su->h, 0)) == NULL) { Free(tsu); return (NULL); } AG_SurfaceCopy(tsu->su, su); tsu->flags = 0; tsu->rSrc.x = 0; tsu->rSrc.y = 0; tsu->rSrc.w = su->w; tsu->rSrc.h = su->h; tsu->rDst = tsu->rSrc; if (tex->w == 0 || tex->h == 0) { tex->w = su->w; tex->h = su->h; } AG_ObjectLock(tex); TAILQ_INSERT_TAIL(&tex->surfaces, tsu, surfaces); tex->nSurfaces++; AG_ObjectUnlock(tex); return (tsu); } /* * Add a new surface to a texture object (NODUP variant). * As the surface is referenced directly, it must have been created * with AG_SurfaceStdRGBA() and must remain valid. */ SG_TextureSurface * SG_TextureAddSurfaceNODUP(SG_Texture *tex, AG_Surface *su) { SG_TextureSurface *tsu; if (!(su->flags & AG_SURFACE_GL_TEXTURE)) { AG_SetError("Surface is not a GL texture"); return (NULL); } if ((tex->nSurfaces+1) > SG_TEXTURE_SURFACES_MAX) { AG_SetError("Too many surfaces"); return (NULL); } if ((tsu = TryMalloc(sizeof(SG_TextureSurface))) == NULL) { return (NULL); } tsu->su = su; tsu->flags = SG_TEXTURE_SURFACE_NODUP; tsu->rSrc.x = 0; tsu->rSrc.y = 0; tsu->rSrc.w = su->w; tsu->rSrc.h = su->h; tsu->rDst = tsu->rSrc; AG_ObjectLock(tex); TAILQ_INSERT_TAIL(&tex->surfaces, tsu, surfaces); tex->nSurfaces++; AG_ObjectUnlock(tex); return (tsu); } /* Remove the specified surface from texture object. */ void SG_TextureDelSurface(SG_Texture *tex, SG_TextureSurface *tsu) { AG_ObjectLock(tex); TAILQ_REMOVE(&tex->surfaces, tsu, surfaces); tex->nSurfaces--; AG_ObjectUnlock(tex); AG_SurfaceFree(tsu->su); Free(tsu); } /* Attach a new fragment shader to a texture object. */ SG_TextureProgram * SG_TextureAddProgram(SG_Texture *tex, SG_Program *prog) { SG_TextureProgram *tp; if ((tex->nProgs+1) > SG_TEXTURE_PROGS_MAX) { AG_SetError("Too many programs"); return (NULL); } if ((tp = TryMalloc(sizeof(SG_TextureProgram))) == NULL) { return (NULL); } tp->flags = 0; tp->progName[0] = '\0'; tp->prog = prog; AG_ObjectLock(tex); TAILQ_INSERT_TAIL(&tex->progs, tp, programs); tex->nProgs++; AG_ObjectUnlock(tex); return (tp); } /* Detach a fragment shader from a texture object. */ void SG_TextureDelProgram(SG_Texture *tex, SG_TextureProgram *tp) { AG_ObjectLock(tex); TAILQ_REMOVE(&tex->progs, tp, programs); tex->nProgs--; AG_ObjectUnlock(tex); Free(tp); } /* Import texture surface from file. */ static void ImportSurface(AG_Event *_Nonnull event) { SG_Texture *tex = SG_TEXTURE_PTR(1); const char *path = AG_STRING(2); AG_Surface *s; SG_TextureSurface *tsu; if ((s = AG_SurfaceFromFile(path)) == NULL) { AG_TextMsgFromError(); return; } AG_ObjectLock(tex); if ((tsu = SG_TextureAddSurface(tex, s)) == NULL) { AG_TextMsgFromError(); AG_SurfaceFree(s); } SG_TextureCompile(tex); AG_ObjectUnlock(tex); } static void PreviewTexture(AG_Event *_Nonnull event) { AG_Pixmap *px = AG_PIXMAP_PTR(1); const char *path = AG_STRING(2); AG_Surface *suFile, *suScaled = NULL; if ((suFile = AG_SurfaceFromFile(path)) == NULL) { AG_TextColor(&WCOLOR(px, TEXT_COLOR)); AG_PixmapReplaceSurface(px, px->n, AG_TextRender(AG_GetError())); return; } if ((suScaled = AG_SurfaceScale(suFile, 196, 128, 0)) == NULL) { return; } AG_PixmapReplaceSurface(px, px->n, suScaled); AG_SurfaceFree(suFile); } static void ImportSurfaceDlg(AG_Event *_Nonnull event) { SG_Texture *tex = SG_TEXTURE_PTR(2); AG_Window *win; AG_FileDlg *fd; AG_Box *hBox; AG_Pixmap *px; if ((win = AG_WindowNew(AG_WINDOW_MODAL)) == NULL) { return; } AG_WindowSetCaption(win, _("%s: Import surface"), OBJECT(tex)->name); hBox = AG_BoxNew(win, AG_BOX_HORIZ, AG_BOX_EXPAND); { fd = AG_FileDlgNewMRU(hBox, "sg-textures", AG_FILEDLG_LOAD | AG_FILEDLG_CLOSEWIN | AG_FILEDLG_EXPAND | AG_FILEDLG_MASK_EXT); AG_FileDlgAddType(fd, _("JPEG Image"), "*.jpg,*.jpeg", ImportSurface, "%p", tex); AG_FileDlgAddType(fd, _("Portable Network Graphics"), "*.png", ImportSurface, "%p", tex); AG_FileDlgAddType(fd, _("PC bitmap"), "*.bmp", ImportSurface, "%p", tex); px = AG_PixmapNew(hBox, 0, 196, 128); AG_SetEvent(fd, "file-selected", PreviewTexture, "%p", px); } AG_WindowShow(win); } static void AttachProgram(AG_Event *_Nonnull event) { AG_ObjectSelector *os = AG_OBJECTSELECTOR_PTR(1); SG_Texture *tex = SG_TEXTURE_PTR(2); SG_Program *prog = AG_GetPointer(os,"object"); SG_TextureProgram *tp; AG_ObjectLock(tex); TAILQ_FOREACH(tp, &tex->progs, programs) { if (tp->prog == prog) break; } if (tp == NULL) { SG_TextureAddProgram(tex, prog); } AG_ObjectUnlock(tex); } static void PollPrograms(AG_Event *_Nonnull event) { AG_Tlist *tl = AG_TLIST_SELF(); SG_Texture *tex = SG_TEXTURE_PTR(1); SG_TextureProgram *tp; AG_TlistBegin(tl); AG_ObjectLock(tex); TAILQ_FOREACH(tp, &tex->progs, programs) { AG_TlistAddPtr(tl, sgIconCgProgram.s, OBJECT(tp)->name, tp->prog); } AG_ObjectUnlock(tex); AG_TlistEnd(tl); } static void ExpandPreviewModes(AG_Event *_Nonnull event) { AG_Combo *com = AG_COMBO_SELF(); AG_TlistAdd(com->list, NULL, _(AGSI_IDEOGRAM AGSI_WIREFRAME_CUBE AGSI_RST " Cube")); AG_TlistAdd(com->list, NULL, _(AGSI_IDEOGRAM AGSI_LARGE_SPHERE AGSI_RST "Ball")); } static void *_Nullable Edit(void *_Nonnull obj) { SG_Texture *tex = obj; AG_Mutex *lock = &OBJECT(tex)->lock; AG_Window *win; AG_Pane *paHoriz; AG_Notebook *nb; AG_NotebookTab *ntab; AG_Numerical *num; SG *sgPre; if ((win = AG_WindowNew(AG_WINDOW_MAIN)) == NULL) { return (NULL); } AG_WindowSetCaption(win, _("Texture <%s>"), OBJECT(tex)->name); paHoriz = AG_PaneNewHoriz(win, AG_PANE_EXPAND); { SG_Polybox *cube; /* SG_Ball *ball ; */ SG_Camera *cam; AG_Combo *com; SG_View *svPre; sgPre = SG_New(NULL, "Preview", 0); svPre = SG_ViewNew(paHoriz->div[1], sgPre, SG_VIEW_EXPAND); AG_RedrawOnTick(svPre, 1); cube = SG_PolyboxNew(sgPre->root, "Box"); SG_ObjectSetTexture(cube, tex); // ball = SG_BallNew(sgPre->root, "Ball"); // SG_ObjectSetTexture(ball, tex); if ((cam = sgPre->def.cam) != NULL) { cam->flags |= SG_CAMERA_ROT_I; cam->flags |= SG_CAMERA_ROT_J; cam->flags |= SG_CAMERA_ROT_K; } com = AG_ComboNew(paHoriz->div[1], AG_COMBO_HFILL, _("Preview: ")); AG_SetEvent(com, "combo-expand", ExpandPreviewModes, "%p", tex); } nb = AG_NotebookNew(paHoriz->div[0], AG_NOTEBOOK_HFILL|AG_NOTEBOOK_VFILL); ntab = AG_NotebookAdd(nb, _("Surfaces"), AG_BOX_VERT); { AG_Tlist *tl; AG_MenuItem *mi; tl = AG_TlistNewPolled(ntab, AG_TLIST_EXPAND, PollSurfaces, "%p", tex); AG_TlistSetItemHeight(tl, 64); AG_TlistSetIconWidth(tl, 64); AG_TlistSizeHint(tl, "XXXXXXXXXXXXXXXXXXXXXXXX (00x00)", 4); mi = AG_TlistSetPopup(tl, "texture-surface"); { /* XXX */ AG_MenuAction(mi, _("Edit surface..."), NULL, NULL, "%p,%p,%p", win, tex, tl); AG_MenuSeparator(mi); AG_MenuAction(mi, _("Duplicate surface"), NULL, NULL, "%p,%p", tex, tl); AG_MenuSeparator(mi); AG_MenuAction(mi, _("Delete surface"), agIconTrash.s, NULL, "%p,%p", tex, tl); } AG_ButtonNewFn(ntab, AG_BUTTON_HFILL, _("Import surface..."), ImportSurfaceDlg, "%p,%p", win, tex); } ntab = AG_NotebookAdd(nb, _("Fixed Color"), AG_BOX_VERT); { AG_Toolbar *bar; AG_HSVPal *pal; pal = AG_HSVPalNew(ntab, AG_HSVPAL_EXPAND); M_BindRealMp(pal, "RGBAv", (void *)&tex->ambient, lock); bar = AG_ToolbarNew(ntab, AG_TOOLBAR_HORIZ, 1, AG_TOOLBAR_HOMOGENOUS|AG_TOOLBAR_STICKY); { AG_ToolbarButton(bar, _("Ambient"), 1, SelectColor, "%p,%p,%p", pal, &tex->ambient, lock); AG_ToolbarButton(bar, _("Diffuse"), 0, SelectColor, "%p,%p,%p", pal, &tex->diffuse, lock); AG_ToolbarButton(bar, _("Specular"), 0, SelectColor, "%p,%p,%p", pal, &tex->specular, lock); AG_ToolbarButton(bar, _("Emissive"), 0, SelectColor, "%p,%p,%p", pal, &tex->emissive, lock); } num = AG_NumericalNew(ntab, 0, NULL, _("Shininess: ")); M_BindRealMp(num, "value", &tex->shininess, lock); } ntab = AG_NotebookAdd(nb, _("Shaders"), AG_BOX_VERT); { AG_ObjectSelector *os; AG_Box *hBox; hBox = AG_BoxNewHoriz(ntab, AG_BOX_HFILL); { os = AG_ObjectSelectorNew(hBox, 0, tex, AGOBJECT(tex)->root, _("Attach Shader Program: ")); AG_ObjectSelectorMaskType(os, "SG_Program:*"); AG_ButtonNewFn(hBox, AG_BUTTON_VFILL, _("OK"), AttachProgram, "%p,%p", os, tex); } AG_TlistNewPolled(ntab, AG_TLIST_EXPAND, PollPrograms, "%p", tex); } AG_WindowSetGeometryAlignedPct(win, AG_WINDOW_MC, 40, 40); return (win); } /* Compile the final texture surface from the input surfaces. */ int SG_TextureCompile(SG_Texture *tex) { SG_TextureSurface *tsu; AG_Color c; if (tex->surface != NULL) { AG_SurfaceFree(tex->surface); } if ((tex->surface = AG_SurfaceStdRGBA(tex->w, tex->h)) == NULL) { return (-1); } AG_ColorRGB(&c, (AG_Component)(tex->ambient.r * AG_COLOR_LASTD), (AG_Component)(tex->ambient.g * AG_COLOR_LASTD), (AG_Component)(tex->ambient.b * AG_COLOR_LASTD)); AG_FillRect(tex->surface, NULL, &c); TAILQ_FOREACH(tsu, &tex->surfaces, surfaces) { if (tsu->flags & SG_TEXTURE_SURFACE_SUPPRESS) { continue; } if (tsu->rDst.w != tsu->su->w || tsu->rDst.h != tsu->su->h) { AG_Surface *suScaled = NULL; suScaled = AG_SurfaceScale(tsu->su, tsu->rDst.w, tsu->rDst.h, 0); if (suScaled == NULL) { return (-1); } AG_SurfaceBlit(suScaled, &tsu->rSrc, tex->surface, tsu->rDst.x, tsu->rDst.y); AG_SurfaceFree(suScaled); } else { AG_SurfaceBlit(tsu->su, &tsu->rSrc, tex->surface, tsu->rDst.x, tsu->rDst.y); } } return (0); } /* * Set up the texture for rendering an object. * Must be called from GUI rendering context. */ void SG_TextureBind(SG_Texture *tex, SG_View *view) { AG_Driver *drv = WIDGET(view)->drv; SG_TextureProgram *tp; SG_ViewTexture *vt; AG_ObjectLock(tex); GL_MaterialColorv(GL_FRONT, GL_EMISSION, &tex->emissive); GL_MaterialColorv(GL_FRONT, GL_AMBIENT, &tex->ambient); GL_MaterialColorv(GL_FRONT, GL_DIFFUSE, &tex->diffuse); GL_MaterialColorv(GL_FRONT, GL_SPECULAR, &tex->specular); GL_Material(GL_FRONT, GL_SHININESS, tex->shininess); /* Bind texture surfaces */ if (tex->nSurfaces > 0) { if (tex->surface == NULL && SG_TextureCompile(tex) == -1) { goto fail; } TAILQ_FOREACH(vt, &tex->vtex, textures) { if (vt->sv == view) break; } if (vt == NULL) { vt = Malloc(sizeof(SG_ViewTexture)); vt->sv = view; AG_GL_UploadTexture(drv, &vt->name, tex->surface, NULL); vt->frame = -1; TAILQ_INSERT_TAIL(&tex->vtex, vt, textures); } AGDRIVER_CLASS(drv)->pushBlendingMode(drv, AG_ALPHA_SRC, AG_ALPHA_ONE_MINUS_SRC); glBindTexture(GL_TEXTURE_2D, vt->name); } /* Bind associated fragment shaders. */ TAILQ_FOREACH(tp, &tex->progs, programs) { SG_Program *prog; if (tp->flags & SG_TEXTURE_PROGRAM_SUPPRESS) { continue; } if ((prog = tp->prog) == NULL) { AG_Object *parent; if ((parent = AG_ObjectParent(tex)) != NULL && (prog = AG_ObjectFindChild(parent, tp->progName)) != NULL) { Debug(tex, "Bind program: %s\n", OBJECT(prog)->name); tp->prog = prog; } else { Debug(tex, "No such program: %s; ignoring\n", tp->progName); continue; } } if (prog != NULL && AG_OBJECT_VALID(prog) && SG_PROGRAM_ISA(prog)) SG_ProgramBind(prog, view); } AG_ObjectUnlock(tex); return; fail: Verbose("SG_TextureBind: %s\n", AG_GetError()); AG_ObjectUnlock(tex); } /* * Cleanup after rendering an object. * Must be called from widget draw context. */ void SG_TextureUnbind(SG_Texture *tex, SG_View *view) { AG_Driver *drv = WIDGET(view)->drv; SG_ViewTexture *vt; SG_TextureProgram *tp; AG_ObjectLock(tex); TAILQ_FOREACH(tp, &tex->progs, programs) { SG_Program *prog; if (tp->flags & SG_TEXTURE_PROGRAM_SUPPRESS) { continue; } if ((prog = tp->prog) != NULL && AG_OBJECT_VALID(prog) && SG_PROGRAM_ISA(prog)) SG_ProgramUnbind(prog, view); } if (tex->nSurfaces > 0) { TAILQ_FOREACH(vt, &tex->vtex, textures) { if (vt->sv == view) break; } if (vt != NULL) glBindTexture(GL_TEXTURE_2D, 0); AGDRIVER_CLASS(drv)->popBlendingMode(drv); } AG_ObjectUnlock(tex); } AG_ObjectClass sgTextureClass = { "SG_Texture", sizeof(SG_Texture), { 0,0, AGC_SG_TEXTURE, 0xE06B }, Init, Reset, NULL, /* destroy */ Load, Save, Edit };