1/*
2 * Copyright 2019 Collabora Ltd.
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 * on the rights to use, copy, modify, merge, publish, distribute, sub
8 * license, and/or sell copies of the Software, and to permit persons to whom
9 * the 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 NON-INFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHOR(S) AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM,
19 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
20 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
21 * USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23
24#include "virgl_resource_cache.h"
25#include "util/os_time.h"
26
27/* Checks whether the resource represented by a cache entry is able to hold
28 * data of the specified size, bind and format.
29 */
30static bool
31virgl_resource_cache_entry_is_compatible(struct virgl_resource_cache_entry *entry, struct virgl_resource_params params)
32{
33   if (entry->params.target == PIPE_BUFFER) {
34         return (entry->params.bind == params.bind &&
35                 entry->params.format == params.format &&
36                 entry->params.size >= params.size &&
37                 entry->params.flags == params.flags &&
38                 /* We don't want to waste space, so don't reuse resource storage to
39                 * hold much smaller (< 50%) sizes.
40                 */
41                 entry->params.size <= params.size * 2 &&
42                 entry->params.width >= params.width &&
43                 entry->params.target == params.target);
44   } else {
45      return memcmp(&entry->params, &params, sizeof(params)) == 0;
46   }
47}
48
49static void
50virgl_resource_cache_entry_release(struct virgl_resource_cache *cache,
51                                   struct virgl_resource_cache_entry *entry)
52{
53      list_del(&entry->head);
54      cache->entry_release_func(entry, cache->user_data);
55}
56
57static void
58virgl_resource_cache_destroy_expired(struct virgl_resource_cache *cache, int64_t now)
59{
60   list_for_each_entry_safe(struct virgl_resource_cache_entry,
61                            entry, &cache->resources, head) {
62      /* Entries are in non-decreasing timeout order, so we can stop
63       * at the first entry which hasn't expired.
64       */
65      if (!os_time_timeout(entry->timeout_start, entry->timeout_end, now))
66         break;
67      virgl_resource_cache_entry_release(cache, entry);
68   }
69}
70
71void
72virgl_resource_cache_init(struct virgl_resource_cache *cache,
73                          unsigned timeout_usecs,
74                          virgl_resource_cache_entry_is_busy_func is_busy_func,
75                          virgl_resource_cache_entry_release_func destroy_func,
76                          void *user_data)
77{
78   list_inithead(&cache->resources);
79   cache->timeout_usecs = timeout_usecs;
80   cache->entry_is_busy_func = is_busy_func;
81   cache->entry_release_func = destroy_func;
82   cache->user_data = user_data;
83}
84
85void
86virgl_resource_cache_add(struct virgl_resource_cache *cache,
87                         struct virgl_resource_cache_entry *entry)
88{
89   const int64_t now = os_time_get();
90
91   /* Entry should not already be in the cache. */
92   assert(entry->head.next == NULL);
93   assert(entry->head.prev == NULL);
94
95   virgl_resource_cache_destroy_expired(cache, now);
96
97   entry->timeout_start = now;
98   entry->timeout_end = entry->timeout_start + cache->timeout_usecs;
99   list_addtail(&entry->head, &cache->resources);
100}
101
102struct virgl_resource_cache_entry *
103virgl_resource_cache_remove_compatible(struct virgl_resource_cache *cache,
104                                       struct virgl_resource_params params)
105{
106   const int64_t now = os_time_get();
107   struct virgl_resource_cache_entry *compat_entry = NULL;
108   bool check_expired = true;
109
110   /* Iterate through the cache to find a compatible resource, while also
111    * destroying any expired resources we come across.
112    */
113   list_for_each_entry_safe(struct virgl_resource_cache_entry,
114                            entry, &cache->resources, head) {
115      const bool compatible =
116         virgl_resource_cache_entry_is_compatible(entry, params);
117
118      if (compatible) {
119         if (!cache->entry_is_busy_func(entry, cache->user_data))
120            compat_entry = entry;
121
122         /* We either have found a compatible resource, in which case we are
123          * done, or the resource is busy, which means resources later in
124          * the cache list will also be busy, so there is no point in
125          * searching further.
126          */
127         break;
128      }
129
130      /* If we aren't using this resource, check to see if it has expired.
131       * Once we have found the first non-expired resource, we can stop checking
132       * since the cache holds resources in non-decreasing timeout order.
133       */
134      if (check_expired) {
135         if (os_time_timeout(entry->timeout_start, entry->timeout_end, now))
136            virgl_resource_cache_entry_release(cache, entry);
137         else
138            check_expired = false;
139      }
140   }
141
142   if (compat_entry)
143      list_del(&compat_entry->head);
144
145   return compat_entry;
146}
147
148void
149virgl_resource_cache_flush(struct virgl_resource_cache *cache)
150{
151   list_for_each_entry_safe(struct virgl_resource_cache_entry,
152                            entry, &cache->resources, head) {
153      virgl_resource_cache_entry_release(cache, entry);
154   }
155}
156