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