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 */
30 static bool
virgl_resource_cache_entry_is_compatible(struct virgl_resource_cache_entry * entry,struct virgl_resource_params params)31 virgl_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, ¶ms, sizeof(params)) == 0;
46 }
47 }
48
49 static void
virgl_resource_cache_entry_release(struct virgl_resource_cache * cache,struct virgl_resource_cache_entry * entry)50 virgl_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
57 static void
virgl_resource_cache_destroy_expired(struct virgl_resource_cache * cache,int64_t now)58 virgl_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
71 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)72 virgl_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
85 void
virgl_resource_cache_add(struct virgl_resource_cache * cache,struct virgl_resource_cache_entry * entry)86 virgl_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
102 struct virgl_resource_cache_entry *
virgl_resource_cache_remove_compatible(struct virgl_resource_cache * cache,struct virgl_resource_params params)103 virgl_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
148 void
virgl_resource_cache_flush(struct virgl_resource_cache * cache)149 virgl_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