1 /*
2 * Copyright © 2014-2015 Broadcom
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 */
23
24 #include <errno.h>
25 #include <err.h>
26 #include <sys/mman.h>
27 #include <fcntl.h>
28 #include <xf86drm.h>
29 #include <xf86drmMode.h>
30
31 #include "util/u_hash_table.h"
32 #include "util/u_memory.h"
33 #include "util/u_string.h"
34 #include "util/ralloc.h"
35
36 #include "vc4_context.h"
37 #include "vc4_screen.h"
38
39 static bool dump_stats = false;
40
41 static void
42 vc4_bo_cache_free_all(struct vc4_bo_cache *cache);
43
44 void
vc4_bo_debug_describe(char * buf,const struct vc4_bo * ptr)45 vc4_bo_debug_describe(char* buf, const struct vc4_bo *ptr)
46 {
47 sprintf(buf, "vc4_bo<%s,%u,%u>", ptr->name ? ptr->name : "?",
48 ptr->handle, ptr->size);
49 }
50
51 void
vc4_bo_label(struct vc4_screen * screen,struct vc4_bo * bo,const char * fmt,...)52 vc4_bo_label(struct vc4_screen *screen, struct vc4_bo *bo, const char *fmt, ...)
53 {
54 /* Perform BO labeling by default on debug builds (so that you get
55 * whole-system allocation information), or if VC4_DEBUG=surf is set
56 * (for debugging a single app's allocation).
57 */
58 #if !MESA_DEBUG
59 if (!VC4_DBG(SURFACE))
60 return;
61 #endif
62 va_list va;
63 va_start(va, fmt);
64 char *name = ralloc_vasprintf(NULL, fmt, va);
65 va_end(va);
66
67 struct drm_vc4_label_bo label = {
68 .handle = bo->handle,
69 .len = strlen(name),
70 .name = (uintptr_t)name,
71 };
72 vc4_ioctl(screen->fd, DRM_IOCTL_VC4_LABEL_BO, &label);
73
74 ralloc_free(name);
75 }
76
77 static void
vc4_bo_dump_stats(struct vc4_screen * screen)78 vc4_bo_dump_stats(struct vc4_screen *screen)
79 {
80 struct vc4_bo_cache *cache = &screen->bo_cache;
81
82 fprintf(stderr, " BOs allocated: %d\n", screen->bo_count);
83 fprintf(stderr, " BOs size: %dkb\n", screen->bo_size / 1024);
84 fprintf(stderr, " BOs cached: %d\n", cache->bo_count);
85 fprintf(stderr, " BOs cached size: %dkb\n", cache->bo_size / 1024);
86
87 if (!list_is_empty(&cache->time_list)) {
88 struct vc4_bo *first = list_entry(cache->time_list.next,
89 struct vc4_bo,
90 time_list);
91 struct vc4_bo *last = list_entry(cache->time_list.prev,
92 struct vc4_bo,
93 time_list);
94
95 fprintf(stderr, " oldest cache time: %ld\n",
96 (long)first->free_time);
97 fprintf(stderr, " newest cache time: %ld\n",
98 (long)last->free_time);
99
100 struct timespec time;
101 clock_gettime(CLOCK_MONOTONIC, &time);
102 fprintf(stderr, " now: %jd\n",
103 (intmax_t)time.tv_sec);
104 }
105 }
106
107 static void
vc4_bo_remove_from_cache(struct vc4_bo_cache * cache,struct vc4_bo * bo)108 vc4_bo_remove_from_cache(struct vc4_bo_cache *cache, struct vc4_bo *bo)
109 {
110 list_del(&bo->time_list);
111 list_del(&bo->size_list);
112 cache->bo_count--;
113 cache->bo_size -= bo->size;
114 }
115
vc4_bo_purgeable(struct vc4_bo * bo)116 static void vc4_bo_purgeable(struct vc4_bo *bo)
117 {
118 struct drm_vc4_gem_madvise arg = {
119 .handle = bo->handle,
120 .madv = VC4_MADV_DONTNEED,
121 };
122
123 if (bo->screen->has_madvise)
124 vc4_ioctl(bo->screen->fd, DRM_IOCTL_VC4_GEM_MADVISE, &arg);
125 }
126
vc4_bo_unpurgeable(struct vc4_bo * bo)127 static bool vc4_bo_unpurgeable(struct vc4_bo *bo)
128 {
129 struct drm_vc4_gem_madvise arg = {
130 .handle = bo->handle,
131 .madv = VC4_MADV_WILLNEED,
132 };
133
134 if (!bo->screen->has_madvise)
135 return true;
136
137 if (vc4_ioctl(bo->screen->fd, DRM_IOCTL_VC4_GEM_MADVISE, &arg))
138 return false;
139
140 return arg.retained;
141 }
142
143 static void
vc4_bo_free(struct vc4_bo * bo)144 vc4_bo_free(struct vc4_bo *bo)
145 {
146 struct vc4_screen *screen = bo->screen;
147
148 if (bo->map) {
149 #ifdef USE_VC4_SIMULATOR
150 if (bo->name &&
151 strcmp(bo->name, "winsys") == 0) {
152 free(bo->map);
153 } else
154 #endif
155 {
156 munmap(bo->map, bo->size);
157 VG(VALGRIND_FREELIKE_BLOCK(bo->map, 0));
158 }
159 }
160
161 struct drm_gem_close c;
162 memset(&c, 0, sizeof(c));
163 c.handle = bo->handle;
164 int ret = vc4_ioctl(screen->fd, DRM_IOCTL_GEM_CLOSE, &c);
165 if (ret != 0)
166 fprintf(stderr, "close object %d: %s\n", bo->handle, strerror(errno));
167
168 screen->bo_count--;
169 screen->bo_size -= bo->size;
170
171 if (dump_stats) {
172 fprintf(stderr, "Freed %s%s%dkb:\n",
173 bo->name ? bo->name : "",
174 bo->name ? " " : "",
175 bo->size / 1024);
176 vc4_bo_dump_stats(screen);
177 }
178
179 free(bo);
180 }
181
182 static struct vc4_bo *
vc4_bo_from_cache(struct vc4_screen * screen,uint32_t size,const char * name)183 vc4_bo_from_cache(struct vc4_screen *screen, uint32_t size, const char *name)
184 {
185 struct vc4_bo_cache *cache = &screen->bo_cache;
186 uint32_t page_index = size / 4096 - 1;
187 struct vc4_bo *iter, *tmp, *bo = NULL;
188
189 if (cache->size_list_size <= page_index)
190 return NULL;
191
192 mtx_lock(&cache->lock);
193 LIST_FOR_EACH_ENTRY_SAFE(iter, tmp, &cache->size_list[page_index],
194 size_list) {
195 /* Check that the BO has gone idle. If not, then none of the
196 * other BOs (pushed to the list after later rendering) are
197 * likely to be idle, either.
198 */
199 if (!vc4_bo_wait(iter, 0, NULL))
200 break;
201
202 if (!vc4_bo_unpurgeable(iter)) {
203 /* The BO has been purged. Free it and try to find
204 * another one in the cache.
205 */
206 vc4_bo_remove_from_cache(cache, iter);
207 vc4_bo_free(iter);
208 continue;
209 }
210
211 bo = iter;
212 pipe_reference_init(&bo->reference, 1);
213 vc4_bo_remove_from_cache(cache, bo);
214
215 vc4_bo_label(screen, bo, "%s", name);
216 bo->name = name;
217 break;
218 }
219 mtx_unlock(&cache->lock);
220 return bo;
221 }
222
223 struct vc4_bo *
vc4_bo_alloc(struct vc4_screen * screen,uint32_t size,const char * name)224 vc4_bo_alloc(struct vc4_screen *screen, uint32_t size, const char *name)
225 {
226 bool cleared_and_retried = false;
227 struct drm_vc4_create_bo create;
228 struct vc4_bo *bo;
229 int ret;
230
231 size = align(size, 4096);
232
233 bo = vc4_bo_from_cache(screen, size, name);
234 if (bo) {
235 if (dump_stats) {
236 fprintf(stderr, "Allocated %s %dkb from cache:\n",
237 name, size / 1024);
238 vc4_bo_dump_stats(screen);
239 }
240 return bo;
241 }
242
243 bo = CALLOC_STRUCT(vc4_bo);
244 if (!bo)
245 return NULL;
246
247 pipe_reference_init(&bo->reference, 1);
248 bo->screen = screen;
249 bo->size = size;
250 bo->name = name;
251 bo->private = true;
252
253 retry:
254 memset(&create, 0, sizeof(create));
255 create.size = size;
256
257 ret = vc4_ioctl(screen->fd, DRM_IOCTL_VC4_CREATE_BO, &create);
258 bo->handle = create.handle;
259
260 if (ret != 0) {
261 if (!list_is_empty(&screen->bo_cache.time_list) &&
262 !cleared_and_retried) {
263 cleared_and_retried = true;
264 vc4_bo_cache_free_all(&screen->bo_cache);
265 goto retry;
266 }
267
268 free(bo);
269 return NULL;
270 }
271
272 screen->bo_count++;
273 screen->bo_size += bo->size;
274 if (dump_stats) {
275 fprintf(stderr, "Allocated %s %dkb:\n", name, size / 1024);
276 vc4_bo_dump_stats(screen);
277 }
278
279 vc4_bo_label(screen, bo, "%s", name);
280
281 return bo;
282 }
283
284 void
vc4_bo_last_unreference(struct vc4_bo * bo)285 vc4_bo_last_unreference(struct vc4_bo *bo)
286 {
287 struct vc4_screen *screen = bo->screen;
288
289 struct timespec time;
290 clock_gettime(CLOCK_MONOTONIC, &time);
291 mtx_lock(&screen->bo_cache.lock);
292 vc4_bo_last_unreference_locked_timed(bo, time.tv_sec);
293 mtx_unlock(&screen->bo_cache.lock);
294 }
295
296 static void
free_stale_bos(struct vc4_screen * screen,time_t time)297 free_stale_bos(struct vc4_screen *screen, time_t time)
298 {
299 struct vc4_bo_cache *cache = &screen->bo_cache;
300 bool freed_any = false;
301
302 list_for_each_entry_safe(struct vc4_bo, bo, &cache->time_list,
303 time_list) {
304 if (dump_stats && !freed_any) {
305 fprintf(stderr, "Freeing stale BOs:\n");
306 vc4_bo_dump_stats(screen);
307 freed_any = true;
308 }
309
310 /* If it's more than a second old, free it. */
311 if (time - bo->free_time > 2) {
312 vc4_bo_remove_from_cache(cache, bo);
313 vc4_bo_free(bo);
314 } else {
315 break;
316 }
317 }
318
319 if (dump_stats && freed_any) {
320 fprintf(stderr, "Freed stale BOs:\n");
321 vc4_bo_dump_stats(screen);
322 }
323 }
324
325 static void
vc4_bo_cache_free_all(struct vc4_bo_cache * cache)326 vc4_bo_cache_free_all(struct vc4_bo_cache *cache)
327 {
328 mtx_lock(&cache->lock);
329 list_for_each_entry_safe(struct vc4_bo, bo, &cache->time_list,
330 time_list) {
331 vc4_bo_remove_from_cache(cache, bo);
332 vc4_bo_free(bo);
333 }
334 mtx_unlock(&cache->lock);
335 }
336
337 void
vc4_bo_last_unreference_locked_timed(struct vc4_bo * bo,time_t time)338 vc4_bo_last_unreference_locked_timed(struct vc4_bo *bo, time_t time)
339 {
340 struct vc4_screen *screen = bo->screen;
341 struct vc4_bo_cache *cache = &screen->bo_cache;
342 uint32_t page_index = bo->size / 4096 - 1;
343
344 if (!bo->private) {
345 vc4_bo_free(bo);
346 return;
347 }
348
349 if (cache->size_list_size <= page_index) {
350 struct list_head *new_list =
351 ralloc_array(screen, struct list_head, page_index + 1);
352
353 /* Move old list contents over (since the array has moved, and
354 * therefore the pointers to the list heads have to change).
355 */
356 for (int i = 0; i < cache->size_list_size; i++)
357 list_replace(&cache->size_list[i], &new_list[i]);
358 for (int i = cache->size_list_size; i < page_index + 1; i++)
359 list_inithead(&new_list[i]);
360
361 cache->size_list = new_list;
362 cache->size_list_size = page_index + 1;
363 }
364
365 vc4_bo_purgeable(bo);
366 bo->free_time = time;
367 list_addtail(&bo->size_list, &cache->size_list[page_index]);
368 list_addtail(&bo->time_list, &cache->time_list);
369 cache->bo_count++;
370 cache->bo_size += bo->size;
371 if (dump_stats) {
372 fprintf(stderr, "Freed %s %dkb to cache:\n",
373 bo->name, bo->size / 1024);
374 vc4_bo_dump_stats(screen);
375 }
376 bo->name = NULL;
377 vc4_bo_label(screen, bo, "mesa cache");
378
379 free_stale_bos(screen, time);
380 }
381
382 static struct vc4_bo *
vc4_bo_open_handle(struct vc4_screen * screen,uint32_t handle,uint32_t size)383 vc4_bo_open_handle(struct vc4_screen *screen,
384 uint32_t handle, uint32_t size)
385 {
386 struct vc4_bo *bo;
387
388 /* Note: the caller is responsible for locking screen->bo_handles_mutex.
389 * This allows the lock to cover the actual BO import, avoiding a race.
390 */
391
392 assert(size);
393
394 bo = util_hash_table_get(screen->bo_handles, (void*)(uintptr_t)handle);
395 if (bo) {
396 vc4_bo_reference(bo);
397 goto done;
398 }
399
400 bo = CALLOC_STRUCT(vc4_bo);
401 pipe_reference_init(&bo->reference, 1);
402 bo->screen = screen;
403 bo->handle = handle;
404 bo->size = size;
405 bo->name = "winsys";
406 bo->private = false;
407
408 #ifdef USE_VC4_SIMULATOR
409 vc4_simulator_open_from_handle(screen->fd, bo->handle, bo->size);
410 bo->map = malloc(bo->size);
411 #endif
412
413 _mesa_hash_table_insert(screen->bo_handles, (void *)(uintptr_t)handle, bo);
414
415 done:
416 mtx_unlock(&screen->bo_handles_mutex);
417 return bo;
418 }
419
420 struct vc4_bo *
vc4_bo_open_name(struct vc4_screen * screen,uint32_t name)421 vc4_bo_open_name(struct vc4_screen *screen, uint32_t name)
422 {
423 struct drm_gem_open o = {
424 .name = name
425 };
426
427 mtx_lock(&screen->bo_handles_mutex);
428
429 int ret = vc4_ioctl(screen->fd, DRM_IOCTL_GEM_OPEN, &o);
430 if (ret) {
431 fprintf(stderr, "Failed to open bo %d: %s\n",
432 name, strerror(errno));
433 mtx_unlock(&screen->bo_handles_mutex);
434 return NULL;
435 }
436
437 return vc4_bo_open_handle(screen, o.handle, o.size);
438 }
439
440 struct vc4_bo *
vc4_bo_open_dmabuf(struct vc4_screen * screen,int fd)441 vc4_bo_open_dmabuf(struct vc4_screen *screen, int fd)
442 {
443 uint32_t handle;
444
445 mtx_lock(&screen->bo_handles_mutex);
446
447 int ret = drmPrimeFDToHandle(screen->fd, fd, &handle);
448 int size;
449 if (ret) {
450 fprintf(stderr, "Failed to get vc4 handle for dmabuf %d\n", fd);
451 mtx_unlock(&screen->bo_handles_mutex);
452 return NULL;
453 }
454
455 /* Determine the size of the bo we were handed. */
456 size = lseek(fd, 0, SEEK_END);
457 if (size == -1) {
458 fprintf(stderr, "Couldn't get size of dmabuf fd %d.\n", fd);
459 mtx_unlock(&screen->bo_handles_mutex);
460 return NULL;
461 }
462
463 return vc4_bo_open_handle(screen, handle, size);
464 }
465
466 int
vc4_bo_get_dmabuf(struct vc4_bo * bo)467 vc4_bo_get_dmabuf(struct vc4_bo *bo)
468 {
469 int fd;
470 int ret = drmPrimeHandleToFD(bo->screen->fd, bo->handle,
471 O_CLOEXEC, &fd);
472 if (ret != 0) {
473 fprintf(stderr, "Failed to export gem bo %d to dmabuf\n",
474 bo->handle);
475 return -1;
476 }
477
478 mtx_lock(&bo->screen->bo_handles_mutex);
479 bo->private = false;
480 _mesa_hash_table_insert(bo->screen->bo_handles, (void *)(uintptr_t)bo->handle, bo);
481 mtx_unlock(&bo->screen->bo_handles_mutex);
482
483 return fd;
484 }
485
486 struct vc4_bo *
vc4_bo_alloc_shader(struct vc4_screen * screen,const void * data,uint32_t size)487 vc4_bo_alloc_shader(struct vc4_screen *screen, const void *data, uint32_t size)
488 {
489 struct vc4_bo *bo;
490 int ret;
491
492 bo = CALLOC_STRUCT(vc4_bo);
493 if (!bo)
494 return NULL;
495
496 pipe_reference_init(&bo->reference, 1);
497 bo->screen = screen;
498 bo->size = align(size, 4096);
499 bo->name = "code";
500 bo->private = false; /* Make sure it doesn't go back to the cache. */
501
502 struct drm_vc4_create_shader_bo create = {
503 .size = size,
504 .data = (uintptr_t)data,
505 };
506
507 ret = vc4_ioctl(screen->fd, DRM_IOCTL_VC4_CREATE_SHADER_BO,
508 &create);
509 bo->handle = create.handle;
510
511 if (ret != 0) {
512 fprintf(stderr, "create shader ioctl failure\n");
513 abort();
514 }
515
516 screen->bo_count++;
517 screen->bo_size += bo->size;
518 if (dump_stats) {
519 fprintf(stderr, "Allocated shader %dkb:\n", bo->size / 1024);
520 vc4_bo_dump_stats(screen);
521 }
522
523 return bo;
524 }
525
526 bool
vc4_bo_flink(struct vc4_bo * bo,uint32_t * name)527 vc4_bo_flink(struct vc4_bo *bo, uint32_t *name)
528 {
529 struct drm_gem_flink flink = {
530 .handle = bo->handle,
531 };
532 int ret = vc4_ioctl(bo->screen->fd, DRM_IOCTL_GEM_FLINK, &flink);
533 if (ret) {
534 fprintf(stderr, "Failed to flink bo %d: %s\n",
535 bo->handle, strerror(errno));
536 free(bo);
537 return false;
538 }
539
540 bo->private = false;
541 *name = flink.name;
542
543 return true;
544 }
545
vc4_wait_seqno_ioctl(int fd,uint64_t seqno,uint64_t timeout_ns)546 static int vc4_wait_seqno_ioctl(int fd, uint64_t seqno, uint64_t timeout_ns)
547 {
548 struct drm_vc4_wait_seqno wait = {
549 .seqno = seqno,
550 .timeout_ns = timeout_ns,
551 };
552 int ret = vc4_ioctl(fd, DRM_IOCTL_VC4_WAIT_SEQNO, &wait);
553 if (ret == -1)
554 return -errno;
555 else
556 return 0;
557
558 }
559
560 bool
vc4_wait_seqno(struct vc4_screen * screen,uint64_t seqno,uint64_t timeout_ns,const char * reason)561 vc4_wait_seqno(struct vc4_screen *screen, uint64_t seqno, uint64_t timeout_ns,
562 const char *reason)
563 {
564 if (screen->finished_seqno >= seqno)
565 return true;
566
567 if (VC4_DBG(PERF) && timeout_ns && reason) {
568 if (vc4_wait_seqno_ioctl(screen->fd, seqno, 0) == -ETIME) {
569 fprintf(stderr, "Blocking on seqno %lld for %s\n",
570 (long long)seqno, reason);
571 }
572 }
573
574 int ret = vc4_wait_seqno_ioctl(screen->fd, seqno, timeout_ns);
575 if (ret) {
576 if (ret != -ETIME) {
577 fprintf(stderr, "wait failed: %d\n", ret);
578 abort();
579 }
580
581 return false;
582 }
583
584 screen->finished_seqno = seqno;
585 return true;
586 }
587
vc4_wait_bo_ioctl(int fd,uint32_t handle,uint64_t timeout_ns)588 static int vc4_wait_bo_ioctl(int fd, uint32_t handle, uint64_t timeout_ns)
589 {
590 struct drm_vc4_wait_bo wait = {
591 .handle = handle,
592 .timeout_ns = timeout_ns,
593 };
594 int ret = vc4_ioctl(fd, DRM_IOCTL_VC4_WAIT_BO, &wait);
595 if (ret == -1)
596 return -errno;
597 else
598 return 0;
599
600 }
601
602 bool
vc4_bo_wait(struct vc4_bo * bo,uint64_t timeout_ns,const char * reason)603 vc4_bo_wait(struct vc4_bo *bo, uint64_t timeout_ns, const char *reason)
604 {
605 struct vc4_screen *screen = bo->screen;
606
607 if (VC4_DBG(PERF) && timeout_ns && reason) {
608 if (vc4_wait_bo_ioctl(screen->fd, bo->handle, 0) == -ETIME) {
609 fprintf(stderr, "Blocking on %s BO for %s\n",
610 bo->name, reason);
611 }
612 }
613
614 int ret = vc4_wait_bo_ioctl(screen->fd, bo->handle, timeout_ns);
615 if (ret) {
616 if (ret != -ETIME) {
617 fprintf(stderr, "wait failed: %d\n", ret);
618 abort();
619 }
620
621 return false;
622 }
623
624 return true;
625 }
626
627 void *
vc4_bo_map_unsynchronized(struct vc4_bo * bo)628 vc4_bo_map_unsynchronized(struct vc4_bo *bo)
629 {
630 uint64_t offset;
631 int ret;
632
633 if (bo->map)
634 return bo->map;
635
636 struct drm_vc4_mmap_bo map;
637 memset(&map, 0, sizeof(map));
638 map.handle = bo->handle;
639 ret = vc4_ioctl(bo->screen->fd, DRM_IOCTL_VC4_MMAP_BO, &map);
640 offset = map.offset;
641 if (ret != 0) {
642 fprintf(stderr, "map ioctl failure\n");
643 abort();
644 }
645
646 bo->map = mmap(NULL, bo->size, PROT_READ | PROT_WRITE, MAP_SHARED,
647 bo->screen->fd, offset);
648 if (bo->map == MAP_FAILED) {
649 fprintf(stderr, "mmap of bo %d (offset 0x%016llx, size %d) failed\n",
650 bo->handle, (long long)offset, bo->size);
651 abort();
652 }
653 VG(VALGRIND_MALLOCLIKE_BLOCK(bo->map, bo->size, 0, false));
654
655 return bo->map;
656 }
657
658 void *
vc4_bo_map(struct vc4_bo * bo)659 vc4_bo_map(struct vc4_bo *bo)
660 {
661 void *map = vc4_bo_map_unsynchronized(bo);
662
663 bool ok = vc4_bo_wait(bo, OS_TIMEOUT_INFINITE, "bo map");
664 if (!ok) {
665 fprintf(stderr, "BO wait for map failed\n");
666 abort();
667 }
668
669 return map;
670 }
671
672 void
vc4_bufmgr_destroy(struct pipe_screen * pscreen)673 vc4_bufmgr_destroy(struct pipe_screen *pscreen)
674 {
675 struct vc4_screen *screen = vc4_screen(pscreen);
676 struct vc4_bo_cache *cache = &screen->bo_cache;
677
678 vc4_bo_cache_free_all(cache);
679
680 if (dump_stats) {
681 fprintf(stderr, "BO stats after screen destroy:\n");
682 vc4_bo_dump_stats(screen);
683 }
684 }
685