/* * Copyright (C) 2018-2019 - Vito Caputo - * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3 as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include "stage.h" struct stage_t { stage_t *parent; /* NULL when root stage */ dll_t layer; /* node on parent->layers[layer] when parent != NULL */ dll_t layers[STAGE_LAYERS_MAX]; char name[STAGE_NAME_MAX]; float alpha; /* alpha for the stage */ unsigned active:1; /* stage is active */ unsigned locked:1; /* stage is locked */ unsigned dirty:1; /* stage is dirty (since last render) */ const stage_ops_t *ops; /* ops for operating on this stage's object */ void *object; /* this stage's object */ }; /* minimally initialize a stage to carry an object */ static stage_t * _stage_set_object(stage_t *stage, const char *name, const stage_ops_t *ops, void *object) { static stage_ops_t null_ops = {}; assert(stage); assert(name); if (!ops) ops = &null_ops; strncpy(stage->name, name, sizeof(stage->name)); stage->ops = ops; stage->object = object; return stage; } /* allocate a stage, this purely creates a stage in isolation and assigns its associated name, object and functions */ static stage_t * _stage_new(const char *name, const stage_ops_t *ops, void *object) { stage_t *stage; stage = calloc(1, sizeof(stage_t)); if (!stage) return NULL; dll_init(&stage->layer); for (int i = 0; i < STAGE_LAYERS_MAX; i++) dll_init(&stage->layers[i]); return _stage_set_object(stage, name, ops, object); } /* free a stage, no list manipulation occurs, this is purely cleanup of the stage and its object */ static void _stage_free(stage_t *stage) { assert(stage); if (stage->ops->free_func) stage->ops->free_func(stage, stage->object); free(stage); } /* returns a new stage, attached at the specified layer under parent if supplied */ /* layer has no effect when parent == NULL */ /* if replace is set, conf.stage must be set, its internals are replaced and it's returned as new */ stage_t * stage_new(const stage_conf_t *conf, const stage_ops_t *ops, void *object) { stage_t *stage; assert(conf); assert(conf->parent || !conf->layer); assert(conf->stage || !conf->replace); assert(conf->layer < STAGE_LAYERS_MAX); if (conf->replace) return stage_replace(conf->stage, conf->name, ops, object); stage = _stage_new(conf->name, ops, object); if (!stage) return NULL; if (conf->parent) { stage->parent = conf->parent; (void) dll_add_pre(&stage->parent->layers[conf->layer], &stage->layer); } stage->alpha = conf->alpha; stage->active = conf->active; stage->locked = conf->locked; stage->dirty = conf->dirty; return stage; } /* replaces a given stage's object-related properties but otherwise keeping the existing state */ stage_t * stage_replace(stage_t *stage, const char *name, const stage_ops_t *ops, void *object) { assert(stage); if (stage->ops->free_func) stage->ops->free_func(stage, stage->object); return _stage_set_object(stage, name, ops, object); } static void _stage_free_all(stage_t *stage, int force); /* recursively free the layers of this stage, but not the stage itself */ static void _stage_free_layers(stage_t *stage, int force) { assert(stage); for (int i = 0; i < STAGE_LAYERS_MAX; i++) { stage_t *s, *_s; DLL_FOR_EACH_ENTRY_SAFE(&stage->layers[i], s, _s, stage_t, layer) _stage_free_all(s, force); } } /* free the stage recursively, skipping locked stages if !force */ static void _stage_free_all(stage_t *stage, int force) { assert(stage); if (stage->locked && !force) return; _stage_free_layers(stage, force); dll_del(&stage->layer); _stage_free(stage); } /* frees a given stage and all its descendants, removing from its * parent if not root. */ stage_t * stage_free(stage_t *stage) { if (stage) _stage_free_all(stage, 1); return NULL; } /* replace a given stage's object only - retain render/free hooks and everything else, invalidates cache - DOES NOT FREE OLD OBJECT */ void stage_set_object(stage_t *stage, void *object) { assert(stage); stage->object = object; } void * stage_get_object(const stage_t *stage) { assert(stage); return stage->object; } /* set the alpha on a stage */ void stage_set_alpha(stage_t *stage, float alpha) { assert(stage); stage->alpha = alpha; } /* get the current alpha from a stage */ float stage_get_alpha(const stage_t *stage) { assert(stage); return stage->alpha; } /* set a stage's active state (participates in rendering) */ void stage_set_active(stage_t *stage, int active) { assert(stage); stage->active = active; } /* get a stage's active state */ int stage_get_active(const stage_t *stage) { assert(stage); return stage->active; } /* set a stage's locked state (doesn't get freed by clears) */ void stage_set_locked(stage_t *stage, int locked) { assert(stage); stage->locked = locked; } /* get a stage's locked state */ int stage_get_locked(const stage_t *stage) { assert(stage); return stage->locked; } /* set a stage's layer (must not be root stage) */ /* TODO: maybe support supplying a parent here for switching parents? */ void stage_set_layer(stage_t *stage, int layer) { assert(stage); assert(stage->parent); (void) dll_del(&stage->layer); (void) dll_add_pre(&stage->parent->layers[layer], &stage->layer); } /* free everything in the stage, but keep the stage around */ /* probably good hygiene to clear everything when there's no intention of * reusing stages at the start of a new context, in case something is left * around unintentionally. */ void stage_clear(stage_t *stage) { _stage_free_layers(stage, 0); } static void _prepare_stage(stage_t *stage, float alpha, void *render_ctxt) { float a = alpha * stage->alpha; assert(stage); if (stage->ops->prepare_func) stage->ops->prepare_func(stage, stage->object, a, render_ctxt); for (int i = 0; i < STAGE_LAYERS_MAX; i++) { stage_t *s; DLL_FOR_EACH_ENTRY(&stage->layers[i], s, stage_t, layer) { if (!s->active) continue; _prepare_stage(s, a, render_ctxt); } } } static void _render_stage(const stage_t *stage, float alpha, void *render_ctxt) { float a = alpha * stage->alpha; assert(stage); if (stage->ops->render_func) stage->ops->render_func(stage, stage->object, a, render_ctxt); for (int i = 0; i < STAGE_LAYERS_MAX; i++) { stage_t *s; DLL_FOR_EACH_ENTRY(&stage->layers[i], s, stage_t, layer) { if (!s->active) continue; _render_stage(s, a, render_ctxt); } } } /* recursively render the supplied stage tree if dirty, skipping inactive branches */ /* returns wether the stage was dirty or not, for influence of page flipping etc. */ int stage_render(stage_t *stage, void *render_ctxt) { assert(stage); assert(!stage->parent); /* XXX: Current dirty implementation is global and only * maintains the dirty bit in the root node. So assert * that render is only called on the root to not break * dirty detection. */ /* we must always enter the prepare pass, as some nodes may dirty the stage * in the prepare stage. */ if (stage->active) _prepare_stage(stage, 1.f, render_ctxt); if (stage->dirty) { if (stage->active) _render_stage(stage, 1.f, render_ctxt); stage->dirty = 0; return 1; } return 0; } /* mark a stage as dirty */ /* as the current stage_render() implementation only checks the dirty flag of * the supplied stage, which is presumed to be the root, this currently * walks up the chain of parents if any to update the root flag. * TODO: perhaps stage_render() should assert that it's called on the root. */ void stage_dirty(stage_t *stage) { assert(stage); while (stage->parent) stage = stage->parent; stage->dirty = 1; } /* lookup a stage from a name, returns first hit */ stage_t * stage_lookup_name(stage_t *stage, const char *name) { assert(stage); assert(name); if (!strncmp(stage->name, name, sizeof(stage->name))) return stage; /* FIXME: this should probably search layers in descending order */ for (int i = 0; i < STAGE_LAYERS_MAX; i++) { stage_t *s; DLL_FOR_EACH_ENTRY(&stage->layers[i], s, stage_t, layer) { s = stage_lookup_name(s, name); if (s) return s; } } return NULL; }