1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com>
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
25 #include <systemd/sd-bus.h>
26 #include <systemd/sd-event.h>
27 #include <systemd/sd-login.h>
29 #include "grdev-internal.h"
31 #include "login-shared.h"
35 static void pipe_enable(grdev_pipe *pipe);
36 static void pipe_disable(grdev_pipe *pipe);
37 static void card_modified(grdev_card *card);
38 static void session_frame(grdev_session *session, grdev_display *display);
44 static inline grdev_tile *tile_leftmost(grdev_tile *tile) {
48 while (tile->type == GRDEV_TILE_NODE && tile->node.child_list)
49 tile = tile->node.child_list;
54 #define TILE_FOREACH(_root, _i) \
55 for (_i = tile_leftmost(_root); _i; _i = tile_leftmost(_i->childs_by_node_next) ? : _i->parent)
57 #define TILE_FOREACH_SAFE(_root, _i, _next) \
58 for (_i = tile_leftmost(_root); _i && ((_next = tile_leftmost(_i->childs_by_node_next) ? : _i->parent), true); _i = _next)
60 static void tile_link(grdev_tile *tile, grdev_tile *parent) {
61 grdev_display *display;
65 assert(!tile->parent);
66 assert(!tile->display);
68 assert(parent->type == GRDEV_TILE_NODE);
70 display = parent->display;
72 assert(!display || !display->enabled);
74 ++parent->node.n_childs;
75 LIST_PREPEND(childs_by_node, parent->node.child_list, tile);
76 tile->parent = parent;
79 display->modified = true;
80 TILE_FOREACH(tile, t) {
82 if (t->type == GRDEV_TILE_LEAF) {
85 pipe_enable(t->leaf.pipe);
91 static void tile_unlink(grdev_tile *tile) {
92 grdev_tile *parent, *t;
93 grdev_display *display;
97 display = tile->display;
98 parent = tile->parent;
104 assert(parent->type == GRDEV_TILE_NODE);
105 assert(parent->display == display);
106 assert(parent->node.n_childs > 0);
108 --parent->node.n_childs;
109 LIST_REMOVE(childs_by_node, parent->node.child_list, tile);
113 display->modified = true;
114 TILE_FOREACH(tile, t) {
116 if (t->type == GRDEV_TILE_LEAF) {
118 t->leaf.pipe->cache = NULL;
119 pipe_disable(t->leaf.pipe);
124 /* Tile trees are driven by leafs. Internal nodes have no owner, thus,
125 * we must take care to not leave them around. Therefore, whenever we
126 * unlink any part of a tree, we also destroy the parent, in case it's
128 * Parents are stale if they have no childs and either have no display
129 * or if they are intermediate nodes (i.e, they have a parent).
130 * This means, you can easily create trees, but you can never partially
131 * move or destruct them so far. They're always reduced to minimal form
132 * if you cut them. This might change later, but so far we didn't need
133 * partial destruction or the ability to move whole trees. */
135 if (parent->node.n_childs < 1 && (parent->parent || !parent->display))
136 grdev_tile_free(parent);
139 static int tile_new(grdev_tile **out) {
140 _cleanup_(grdev_tile_freep) grdev_tile *tile = NULL;
144 tile = new0(grdev_tile, 1);
148 tile->type = (unsigned)-1;
155 int grdev_tile_new_leaf(grdev_tile **out, grdev_pipe *pipe) {
156 _cleanup_(grdev_tile_freep) grdev_tile *tile = NULL;
159 assert_return(pipe, -EINVAL);
160 assert_return(!pipe->tile, -EINVAL);
166 tile->type = GRDEV_TILE_LEAF;
167 tile->leaf.pipe = pipe;
175 int grdev_tile_new_node(grdev_tile **out) {
176 _cleanup_(grdev_tile_freep) grdev_tile *tile = NULL;
179 assert_return(out, -EINVAL);
185 tile->type = GRDEV_TILE_NODE;
192 grdev_tile *grdev_tile_free(grdev_tile *tile) {
198 switch (tile->type) {
199 case GRDEV_TILE_LEAF:
200 assert(!tile->parent);
201 assert(!tile->display);
202 assert(tile->leaf.pipe);
205 case GRDEV_TILE_NODE:
206 assert(!tile->parent);
207 assert(!tile->display);
208 assert(tile->node.n_childs == 0);
218 grdev_display *grdev_find_display(grdev_session *session, const char *name) {
219 assert_return(session, NULL);
220 assert_return(name, NULL);
222 return hashmap_get(session->display_map, name);
225 int grdev_display_new(grdev_display **out, grdev_session *session, const char *name) {
226 _cleanup_(grdev_display_freep) grdev_display *display = NULL;
232 display = new0(grdev_display, 1);
236 display->session = session;
238 display->name = strdup(name);
242 r = grdev_tile_new_node(&display->tile);
246 display->tile->display = display;
248 r = hashmap_put(session->display_map, display->name, display);
258 grdev_display *grdev_display_free(grdev_display *display) {
262 assert(!display->public);
263 assert(!display->enabled);
264 assert(!display->modified);
265 assert(display->n_leafs == 0);
266 assert(display->n_pipes == 0);
269 hashmap_remove_value(display->session->display_map, display->name, display);
272 display->tile->display = NULL;
273 grdev_tile_free(display->tile);
276 free(display->pipes);
283 bool grdev_display_is_enabled(grdev_display *display) {
284 return display && display->enabled;
287 void grdev_display_enable(grdev_display *display) {
292 if (!display->enabled) {
293 display->enabled = true;
294 TILE_FOREACH(display->tile, t)
295 if (t->type == GRDEV_TILE_LEAF)
296 pipe_enable(t->leaf.pipe);
300 void grdev_display_disable(grdev_display *display) {
305 if (display->enabled) {
306 display->enabled = false;
307 TILE_FOREACH(display->tile, t)
308 if (t->type == GRDEV_TILE_LEAF)
309 pipe_disable(t->leaf.pipe);
313 const grdev_display_target *grdev_display_next_target(grdev_display *display, const grdev_display_target *prev, uint64_t minage) {
314 grdev_display_cache *cache;
317 assert_return(display, NULL);
318 assert_return(!display->modified, NULL);
319 assert_return(display->enabled, NULL);
322 cache = container_of(prev, grdev_display_cache, target);
325 assert(cache->pipe->tile->display == display);
326 assert(display->pipes >= cache);
328 idx = (cache - display->pipes) / sizeof(*cache) + 1;
333 for (cache = display->pipes + idx; idx < display->n_pipes; ++idx, ++cache) {
334 grdev_display_target *target;
339 target = &cache->target;
341 if (!pipe->running || !pipe->enabled)
344 /* if front-buffer is up-to-date, there's nothing to do */
345 if (minage > 0 && pipe->front && pipe->front->age >= minage)
348 /* find suitable back-buffer */
349 if (!(fb = pipe->back)) {
350 if (!pipe->vtable->target || !(fb = pipe->vtable->target(pipe)))
354 /* if back-buffer is up-to-date, schedule flip */
355 if (minage > 0 && fb->age >= minage) {
356 grdev_display_flip_target(display, target, fb->age);
360 /* we have an out-of-date back-buffer; return for redraw */
368 void grdev_display_flip_target(grdev_display *display, const grdev_display_target *target, uint64_t age) {
369 grdev_display_cache *cache;
373 assert(!display->modified);
374 assert(display->enabled);
378 cache = container_of(target, grdev_display_cache, target);
381 assert(cache->pipe->tile->display == display);
383 /* reset age of all FB on overflow */
384 if (age < target->fb->age)
385 for (i = 0; i < cache->pipe->max_fbs; ++i)
386 if (cache->pipe->fbs[i])
387 cache->pipe->fbs[i]->age = 0;
389 ((grdev_fb*)target->fb)->age = age;
390 cache->pipe->flip = true;
393 static void display_cache_apply(grdev_display_cache *c, grdev_tile *l) {
394 uint32_t x, y, width, height;
395 grdev_display_target *t;
399 assert(l->cache_w >= c->target.width + c->target.x);
400 assert(l->cache_h >= c->target.height + c->target.y);
406 t->rotate = (t->rotate + l->rotate) & 0x3;
416 case GRDEV_ROTATE_90:
417 t->x = l->cache_h - (height + y);
422 case GRDEV_ROTATE_180:
423 t->x = l->cache_w - (width + x);
424 t->y = l->cache_h - (height + y);
426 case GRDEV_ROTATE_270:
428 t->y = l->cache_w - (width + x);
438 if (l->flip & GRDEV_FLIP_HORIZONTAL)
439 t->x = l->cache_w - (t->width + t->x);
440 if (l->flip & GRDEV_FLIP_VERTICAL)
441 t->y = l->cache_h - (t->height + t->y);
449 static void display_cache_targets(grdev_display *display) {
450 grdev_display_cache *c;
455 /* depth-first with childs before parent */
456 for (tile = tile_leftmost(display->tile);
458 tile = tile_leftmost(tile->childs_by_node_next) ? : tile->parent) {
459 if (tile->type == GRDEV_TILE_LEAF) {
462 /* We're at a leaf and no parent has been cached, yet.
463 * Copy the pipe information into the target cache and
464 * update our global pipe-caches if required. */
466 assert(tile->leaf.pipe);
467 assert(display->n_pipes + 1 <= display->max_pipes);
470 c = &display->pipes[display->n_pipes++];
475 c->target.width = p->width;
476 c->target.height = p->height;
477 tile->cache_w = p->width;
478 tile->cache_h = p->height;
480 /* all new tiles are incomplete due to geometry changes */
481 c->incomplete = true;
483 display_cache_apply(c, tile);
485 grdev_tile *child, *l;
487 /* We're now at a node with all it's childs already
488 * computed (depth-first, child before parent). We
489 * first need to know the size of our tile, then we
490 * recurse into all leafs and update their cache. */
495 LIST_FOREACH(childs_by_node, child, tile->node.child_list) {
496 if (child->x + child->cache_w > tile->cache_w)
497 tile->cache_w = child->x + child->cache_w;
498 if (child->y + child->cache_h > tile->cache_h)
499 tile->cache_h = child->y + child->cache_h;
502 assert(tile->cache_w > 0);
503 assert(tile->cache_h > 0);
505 TILE_FOREACH(tile, l)
506 if (l->type == GRDEV_TILE_LEAF)
507 display_cache_apply(l->leaf.pipe->cache, tile);
512 static bool display_cache(grdev_display *display) {
520 if (!display->modified)
523 display->modified = false;
524 display->framed = false;
525 display->n_pipes = 0;
529 if (display->n_leafs < 1)
532 TILE_FOREACH(display->tile, tile)
533 if (tile->type == GRDEV_TILE_LEAF)
534 tile->leaf.pipe->cache = NULL;
536 if (display->n_leafs > display->max_pipes) {
537 n = ALIGN_POWER2(display->n_leafs);
543 t = realloc_multiply(display->pipes, sizeof(*display->pipes), n);
550 display->max_pipes = n;
553 display_cache_targets(display);
559 log_debug("grdev: %s/%s: cannot cache pipes: %s",
560 display->session->name, display->name, strerror(-r));
568 grdev_pipe *grdev_find_pipe(grdev_card *card, const char *name) {
569 assert_return(card, NULL);
570 assert_return(name, NULL);
572 return hashmap_get(card->pipe_map, name);
575 int grdev_pipe_add(grdev_pipe *pipe, const char *name, size_t n_fbs) {
578 assert_return(pipe, -EINVAL);
579 assert_return(pipe->vtable, -EINVAL);
580 assert_return(pipe->vtable->free, -EINVAL);
581 assert_return(pipe->card, -EINVAL);
582 assert_return(pipe->card->session, -EINVAL);
583 assert_return(!pipe->cache, -EINVAL);
584 assert_return(pipe->width > 0, -EINVAL);
585 assert_return(pipe->height > 0, -EINVAL);
586 assert_return(!pipe->enabled, -EINVAL);
587 assert_return(!pipe->running, -EINVAL);
588 assert_return(name, -EINVAL);
590 pipe->name = strdup(name);
595 pipe->fbs = new0(grdev_fb*, n_fbs);
599 pipe->max_fbs = n_fbs;
602 r = grdev_tile_new_leaf(&pipe->tile, pipe);
606 r = hashmap_put(pipe->card->pipe_map, pipe->name, pipe);
610 card_modified(pipe->card);
614 grdev_pipe *grdev_pipe_free(grdev_pipe *pipe) {
621 assert(pipe->vtable);
622 assert(pipe->vtable->free);
625 hashmap_remove_value(pipe->card->pipe_map, pipe->name, pipe);
627 tile_unlink(pipe->tile);
629 assert(!pipe->cache);
632 pipe->vtable->free(pipe);
634 grdev_tile_free(tmp.tile);
635 card_modified(tmp.card);
642 static void pipe_enable(grdev_pipe *pipe) {
645 if (!pipe->enabled) {
646 pipe->enabled = true;
647 if (pipe->vtable->enable)
648 pipe->vtable->enable(pipe);
652 static void pipe_disable(grdev_pipe *pipe) {
656 pipe->enabled = false;
657 if (pipe->vtable->disable)
658 pipe->vtable->disable(pipe);
662 void grdev_pipe_ready(grdev_pipe *pipe, bool running) {
665 /* grdev_pipe_ready() is used by backends to notify about pipe state
666 * changed. If a pipe is ready, it can be fully used by us (available,
667 * enabled and accessable). Backends can disable pipes at any time
668 * (like for async revocation), but can only enable them from parent
669 * context. Otherwise, we might call user-callbacks recursively. */
671 if (pipe->running == running)
674 pipe->running = running;
676 /* runtime events for unused pipes are not interesting */
678 grdev_display *display = pipe->tile->display;
684 session_frame(display->session, display);
686 pipe->cache->incomplete = true;
691 void grdev_pipe_frame(grdev_pipe *pipe) {
692 grdev_display *display;
696 /* if pipe is unused, ignore any frame events */
700 display = pipe->tile->display;
704 session_frame(display->session, display);
711 grdev_card *grdev_find_card(grdev_session *session, const char *name) {
712 assert_return(session, NULL);
713 assert_return(name, NULL);
715 return hashmap_get(session->card_map, name);
718 int grdev_card_add(grdev_card *card, const char *name) {
721 assert_return(card, -EINVAL);
722 assert_return(card->vtable, -EINVAL);
723 assert_return(card->vtable->free, -EINVAL);
724 assert_return(card->session, -EINVAL);
725 assert_return(name, -EINVAL);
727 card->name = strdup(name);
731 card->pipe_map = hashmap_new(&string_hash_ops);
735 r = hashmap_put(card->session->card_map, card->name, card);
742 grdev_card *grdev_card_free(grdev_card *card) {
748 assert(!card->enabled);
749 assert(card->vtable);
750 assert(card->vtable->free);
753 hashmap_remove_value(card->session->card_map, card->name, card);
756 card->vtable->free(card);
758 assert(hashmap_size(tmp.pipe_map) == 0);
760 hashmap_free(tmp.pipe_map);
766 static void card_modified(grdev_card *card) {
768 assert(card->session->n_pins > 0);
770 card->modified = true;
773 static void grdev_card_enable(grdev_card *card) {
776 if (!card->enabled) {
777 card->enabled = true;
778 if (card->vtable->enable)
779 card->vtable->enable(card);
783 static void grdev_card_disable(grdev_card *card) {
787 card->enabled = false;
788 if (card->vtable->disable)
789 card->vtable->disable(card);
797 static void session_raise(grdev_session *session, grdev_event *event) {
798 session->event_fn(session, session->userdata, event);
801 static void session_raise_display_add(grdev_session *session, grdev_display *display) {
802 grdev_event event = {
803 .type = GRDEV_EVENT_DISPLAY_ADD,
809 session_raise(session, &event);
812 static void session_raise_display_remove(grdev_session *session, grdev_display *display) {
813 grdev_event event = {
814 .type = GRDEV_EVENT_DISPLAY_REMOVE,
820 session_raise(session, &event);
823 static void session_raise_display_change(grdev_session *session, grdev_display *display) {
824 grdev_event event = {
825 .type = GRDEV_EVENT_DISPLAY_CHANGE,
831 session_raise(session, &event);
834 static void session_raise_display_frame(grdev_session *session, grdev_display *display) {
835 grdev_event event = {
836 .type = GRDEV_EVENT_DISPLAY_FRAME,
842 session_raise(session, &event);
845 static void session_add_card(grdev_session *session, grdev_card *card) {
849 log_debug("grdev: %s: add card '%s'", session->name, card->name);
851 /* Cards are not exposed to users, but managed internally. Cards are
852 * enabled if the session is enabled, and will track that state. The
853 * backend can probe the card at any time, but only if enabled. It
854 * will then add pipes according to hardware state.
855 * That is, the card may create pipes as soon as we enable it here. */
857 if (session->enabled)
858 grdev_card_enable(card);
861 static void session_remove_card(grdev_session *session, grdev_card *card) {
865 log_debug("grdev: %s: remove card '%s'", session->name, card->name);
867 /* As cards are not exposed, it can never be accessed by outside
868 * users and we can simply remove it. Disabling the card does not
869 * necessarily drop all pipes of the card. This is usually deferred
870 * to card destruction (as pipes are cached as long as FDs remain
871 * open). Therefore, the card destruction might cause pipes, and thus
872 * visible displays, to be removed. */
874 grdev_card_disable(card);
875 grdev_card_free(card);
878 static void session_add_display(grdev_session *session, grdev_display *display) {
881 assert(!display->enabled);
883 log_debug("grdev: %s: add display '%s'", session->name, display->name);
885 /* Displays are the main entity for public API users. We create them
886 * independent of card backends and they wrap any underlying display
887 * architecture. Displays are public at all times, thus, may be entered
888 * by outside users at any time. */
890 display->public = true;
891 session_raise_display_add(session, display);
894 static void session_remove_display(grdev_session *session, grdev_display *display) {
898 log_debug("grdev: %s: remove display '%s'", session->name, display->name);
900 /* Displays are public, so we have to be careful when removing them.
901 * We first tell users about their removal, disable them and then drop
902 * them. We now, after the notification, no external access will
903 * happen. Therefore, we can release the tiles afterwards safely. */
905 if (display->public) {
906 display->public = false;
907 session_raise_display_remove(session, display);
910 grdev_display_disable(display);
911 grdev_display_free(display);
914 static void session_change_display(grdev_session *session, grdev_display *display) {
920 changed = display_cache(display);
922 if (display->n_leafs == 0)
923 session_remove_display(session, display);
924 else if (!display->public)
925 session_add_display(session, display);
927 session_raise_display_change(session, display);
928 else if (display->framed)
929 session_frame(session, display);
932 static void session_frame(grdev_session *session, grdev_display *display) {
936 display->framed = false;
938 if (!display->enabled || !session->enabled)
941 if (session->n_pins > 0)
942 display->framed = true;
944 session_raise_display_frame(session, display);
947 int grdev_session_new(grdev_session **out,
948 grdev_context *context,
951 grdev_event_fn event_fn,
953 _cleanup_(grdev_session_freep) grdev_session *session = NULL;
960 assert_return(session_id_valid(name) == !(flags & GRDEV_SESSION_CUSTOM), -EINVAL);
961 assert_return(!(flags & GRDEV_SESSION_CUSTOM) || !(flags & GRDEV_SESSION_MANAGED), -EINVAL);
962 assert_return(!(flags & GRDEV_SESSION_MANAGED) || context->sysbus, -EINVAL);
964 session = new0(grdev_session, 1);
968 session->context = grdev_context_ref(context);
969 session->custom = flags & GRDEV_SESSION_CUSTOM;
970 session->managed = flags & GRDEV_SESSION_MANAGED;
971 session->event_fn = event_fn;
972 session->userdata = userdata;
974 session->name = strdup(name);
978 if (session->managed) {
979 r = sd_bus_path_encode("/org/freedesktop/login1/session",
980 session->name, &session->path);
985 session->card_map = hashmap_new(&string_hash_ops);
986 if (!session->card_map)
989 session->display_map = hashmap_new(&string_hash_ops);
990 if (!session->display_map)
993 r = hashmap_put(context->session_map, session->name, session);
1002 grdev_session *grdev_session_free(grdev_session *session) {
1008 grdev_session_disable(session);
1010 while ((card = hashmap_first(session->card_map)))
1011 session_remove_card(session, card);
1013 assert(hashmap_size(session->display_map) == 0);
1016 hashmap_remove_value(session->context->session_map, session->name, session);
1018 hashmap_free(session->display_map);
1019 hashmap_free(session->card_map);
1020 session->context = grdev_context_unref(session->context);
1021 free(session->path);
1022 free(session->name);
1028 bool grdev_session_is_enabled(grdev_session *session) {
1029 return session && session->enabled;
1032 void grdev_session_enable(grdev_session *session) {
1038 if (!session->enabled) {
1039 session->enabled = true;
1040 HASHMAP_FOREACH(card, session->card_map, iter)
1041 grdev_card_enable(card);
1045 void grdev_session_disable(grdev_session *session) {
1051 if (session->enabled) {
1052 session->enabled = false;
1053 HASHMAP_FOREACH(card, session->card_map, iter)
1054 grdev_card_disable(card);
1058 void grdev_session_commit(grdev_session *session) {
1064 if (!session->enabled)
1067 HASHMAP_FOREACH(card, session->card_map, iter)
1068 if (card->vtable->commit)
1069 card->vtable->commit(card);
1072 void grdev_session_restore(grdev_session *session) {
1078 if (!session->enabled)
1081 HASHMAP_FOREACH(card, session->card_map, iter)
1082 if (card->vtable->restore)
1083 card->vtable->restore(card);
1086 static void session_configure(grdev_session *session) {
1087 grdev_display *display;
1097 * Whenever backends add or remove pipes, we set session->modified and
1098 * require them to pin the session while modifying it. On release, we
1099 * reconfigure the device and re-assign displays to all modified pipes.
1101 * So far, we configure each pipe as a separate display. We do not
1102 * support user-configuration, nor have we gotten any reports from
1103 * users with multi-pipe monitors (4k on DP-1.2 MST and so on). Until
1104 * we get reports, we keep the logic to a minimum.
1107 /* create new displays for all unconfigured pipes */
1108 HASHMAP_FOREACH(card, session->card_map, i) {
1109 if (!card->modified)
1112 card->modified = false;
1114 HASHMAP_FOREACH(pipe, card->pipe_map, j) {
1119 assert(!tile->parent);
1121 display = grdev_find_display(session, pipe->name);
1122 if (display && display->tile) {
1123 log_debug("grdev: %s/%s: occupied display for pipe %s",
1124 session->name, card->name, pipe->name);
1126 } else if (!display) {
1127 r = grdev_display_new(&display, session, pipe->name);
1129 log_debug("grdev: %s/%s: cannot create display for pipe %s: %s",
1130 session->name, card->name, pipe->name, strerror(-r));
1135 tile_link(pipe->tile, display->tile);
1139 /* update displays */
1140 HASHMAP_FOREACH(display, session->display_map, i)
1141 session_change_display(session, display);
1144 grdev_session *grdev_session_pin(grdev_session *session) {
1151 grdev_session *grdev_session_unpin(grdev_session *session) {
1155 assert(session->n_pins > 0);
1157 if (--session->n_pins == 0)
1158 session_configure(session);
1167 int grdev_context_new(grdev_context **out, sd_event *event, sd_bus *sysbus) {
1168 _cleanup_(grdev_context_unrefp) grdev_context *context = NULL;
1170 assert_return(out, -EINVAL);
1171 assert_return(event, -EINVAL);
1173 context = new0(grdev_context, 1);
1178 context->event = sd_event_ref(event);
1181 context->sysbus = sd_bus_ref(sysbus);
1183 context->session_map = hashmap_new(&string_hash_ops);
1184 if (!context->session_map)
1192 static void context_cleanup(grdev_context *context) {
1193 assert(hashmap_size(context->session_map) == 0);
1195 hashmap_free(context->session_map);
1196 context->sysbus = sd_bus_unref(context->sysbus);
1197 context->event = sd_event_unref(context->event);
1201 grdev_context *grdev_context_ref(grdev_context *context) {
1202 assert_return(context, NULL);
1203 assert_return(context->ref > 0, NULL);
1209 grdev_context *grdev_context_unref(grdev_context *context) {
1213 assert_return(context->ref > 0, NULL);
1215 if (--context->ref == 0)
1216 context_cleanup(context);