/* * Copyright 2019 Collabora Ltd. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * on the rights to use, copy, modify, merge, publish, distribute, sub * license, and/or sell copies of the Software, and to permit persons to whom * the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL * THE AUTHOR(S) AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE * USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "virgl_resource_cache.h" #include "util/os_time.h" /* Checks whether the resource represented by a cache entry is able to hold * data of the specified size, bind and format. */ static bool virgl_resource_cache_entry_is_compatible(struct virgl_resource_cache_entry *entry, uint32_t size, uint32_t bind, uint32_t format, uint32_t flags) { return (entry->bind == bind && entry->format == format && entry->size >= size && entry->flags == flags && /* We don't want to waste space, so don't reuse resource storage to * hold much smaller (< 50%) sizes. */ entry->size <= size * 2); } static void virgl_resource_cache_entry_release(struct virgl_resource_cache *cache, struct virgl_resource_cache_entry *entry) { list_del(&entry->head); cache->entry_release_func(entry, cache->user_data); } static void virgl_resource_cache_destroy_expired(struct virgl_resource_cache *cache, int64_t now) { list_for_each_entry_safe(struct virgl_resource_cache_entry, entry, &cache->resources, head) { /* Entries are in non-decreasing timeout order, so we can stop * at the first entry which hasn't expired. */ if (!os_time_timeout(entry->timeout_start, entry->timeout_end, now)) break; virgl_resource_cache_entry_release(cache, entry); } } void virgl_resource_cache_init(struct virgl_resource_cache *cache, unsigned timeout_usecs, virgl_resource_cache_entry_is_busy_func is_busy_func, virgl_resource_cache_entry_release_func destroy_func, void *user_data) { list_inithead(&cache->resources); cache->timeout_usecs = timeout_usecs; cache->entry_is_busy_func = is_busy_func; cache->entry_release_func = destroy_func; cache->user_data = user_data; } void virgl_resource_cache_add(struct virgl_resource_cache *cache, struct virgl_resource_cache_entry *entry) { const int64_t now = os_time_get(); /* Entry should not already be in the cache. */ assert(entry->head.next == NULL); assert(entry->head.prev == NULL); virgl_resource_cache_destroy_expired(cache, now); entry->timeout_start = now; entry->timeout_end = entry->timeout_start + cache->timeout_usecs; list_addtail(&entry->head, &cache->resources); } struct virgl_resource_cache_entry * virgl_resource_cache_remove_compatible(struct virgl_resource_cache *cache, uint32_t size, uint32_t bind, uint32_t format, uint32_t flags) { const int64_t now = os_time_get(); struct virgl_resource_cache_entry *compat_entry = NULL; bool check_expired = true; /* Iterate through the cache to find a compatible resource, while also * destroying any expired resources we come across. */ list_for_each_entry_safe(struct virgl_resource_cache_entry, entry, &cache->resources, head) { const bool compatible = virgl_resource_cache_entry_is_compatible(entry, size, bind, format, flags); if (compatible) { if (!cache->entry_is_busy_func(entry, cache->user_data)) compat_entry = entry; /* We either have found a compatible resource, in which case we are * done, or the resource is busy, which means resources later in * the cache list will also be busy, so there is no point in * searching further. */ break; } /* If we aren't using this resource, check to see if it has expired. * Once we have found the first non-expired resource, we can stop checking * since the cache holds resources in non-decreasing timeout order. */ if (check_expired) { if (os_time_timeout(entry->timeout_start, entry->timeout_end, now)) virgl_resource_cache_entry_release(cache, entry); else check_expired = false; } } if (compat_entry) list_del(&compat_entry->head); return compat_entry; } void virgl_resource_cache_flush(struct virgl_resource_cache *cache) { list_for_each_entry_safe(struct virgl_resource_cache_entry, entry, &cache->resources, head) { virgl_resource_cache_entry_release(cache, entry); } }