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 <gtest/gtest.h>
25
26 #include "virgl_context.h"
27 #include "virgl_resource.h"
28 #include "virgl_screen.h"
29 #include "virgl_staging_mgr.h"
30 #include "virgl_winsys.h"
31
32 #include "util/u_inlines.h"
33 #include "util/u_memory.h"
34
35 struct virgl_hw_res {
36 struct pipe_reference reference;
37 uint32_t target;
38 uint32_t bind;
39 uint32_t size;
40 void *data;
41 };
42
43 static struct virgl_hw_res *
fake_resource_create(struct virgl_winsys * vws,enum pipe_texture_target target,const void * map_front_private,uint32_t format,uint32_t bind,uint32_t width,uint32_t height,uint32_t depth,uint32_t array_size,uint32_t last_level,uint32_t nr_samples,uint32_t flags,uint32_t size)44 fake_resource_create(struct virgl_winsys *vws,
45 enum pipe_texture_target target,
46 const void *map_front_private,
47 uint32_t format, uint32_t bind,
48 uint32_t width, uint32_t height,
49 uint32_t depth, uint32_t array_size,
50 uint32_t last_level, uint32_t nr_samples,
51 uint32_t flags, uint32_t size)
52 {
53 struct virgl_hw_res *hw_res = CALLOC_STRUCT(virgl_hw_res);
54
55 pipe_reference_init(&hw_res->reference, 1);
56
57 hw_res->target = target;
58 hw_res->bind = bind;
59 hw_res->size = size;
60 hw_res->data = CALLOC(size, 1);
61
62 return hw_res;
63 }
64
65 static void
fake_resource_reference(struct virgl_winsys * vws,struct virgl_hw_res ** dres,struct virgl_hw_res * sres)66 fake_resource_reference(struct virgl_winsys *vws,
67 struct virgl_hw_res **dres,
68 struct virgl_hw_res *sres)
69 {
70 struct virgl_hw_res *old = *dres;
71
72 if (pipe_reference(&(*dres)->reference, &sres->reference)) {
73 FREE(old->data);
74 FREE(old);
75 }
76
77 *dres = sres;
78 }
79
80 static void *
fake_resource_map(struct virgl_winsys * vws,struct virgl_hw_res * hw_res)81 fake_resource_map(struct virgl_winsys *vws, struct virgl_hw_res *hw_res)
82 {
83 return hw_res->data;
84 }
85
86 static struct pipe_context *
fake_virgl_context_create()87 fake_virgl_context_create()
88 {
89 struct virgl_context *vctx = CALLOC_STRUCT(virgl_context);
90 struct virgl_screen *vs = CALLOC_STRUCT(virgl_screen);
91 struct virgl_winsys *vws = CALLOC_STRUCT(virgl_winsys);
92
93 vctx->base.screen = &vs->base;
94 vs->vws = vws;
95
96 vs->vws->resource_create = fake_resource_create;
97 vs->vws->resource_reference = fake_resource_reference;
98 vs->vws->resource_map = fake_resource_map;
99
100 return &vctx->base;
101 }
102
103 static void
fake_virgl_context_destroy(struct pipe_context * ctx)104 fake_virgl_context_destroy(struct pipe_context *ctx)
105 {
106 struct virgl_context *vctx = virgl_context(ctx);
107 struct virgl_screen *vs = virgl_screen(ctx->screen);
108
109 FREE(vs->vws);
110 FREE(vs);
111 FREE(vctx);
112 }
113
114 static void *
resource_map(struct virgl_hw_res * hw_res)115 resource_map(struct virgl_hw_res *hw_res)
116 {
117 return hw_res->data;
118 }
119
120 static void
release_resources(struct virgl_hw_res * resources[],unsigned len)121 release_resources(struct virgl_hw_res *resources[], unsigned len)
122 {
123 for (unsigned i = 0; i < len; ++i)
124 fake_resource_reference(NULL, &resources[i], NULL);
125 }
126
127 class VirglStagingMgr : public ::testing::Test
128 {
129 protected:
VirglStagingMgr()130 VirglStagingMgr() : ctx(fake_virgl_context_create())
131 {
132 virgl_staging_init(&staging, ctx, staging_size);
133 }
134
~VirglStagingMgr()135 ~VirglStagingMgr()
136 {
137 virgl_staging_destroy(&staging);
138 fake_virgl_context_destroy(ctx);
139 }
140
141 static const unsigned staging_size;
142 struct pipe_context * const ctx;
143 struct virgl_staging_mgr staging;
144 };
145
146 const unsigned VirglStagingMgr::staging_size = 4096;
147
148 class VirglStagingMgrWithAlignment : public VirglStagingMgr,
149 public ::testing::WithParamInterface<unsigned>
150 {
151 protected:
VirglStagingMgrWithAlignment()152 VirglStagingMgrWithAlignment() : alignment(GetParam()) {}
153 const unsigned alignment;
154 };
155
TEST_P(VirglStagingMgrWithAlignment,suballocations_are_non_overlapping_in_same_resource)156 TEST_P(VirglStagingMgrWithAlignment,
157 suballocations_are_non_overlapping_in_same_resource)
158 {
159 const unsigned alloc_sizes[] = {16, 450, 79, 240, 128, 1001};
160 const unsigned num_resources = sizeof(alloc_sizes) / sizeof(alloc_sizes[0]);
161 struct virgl_hw_res *out_resource[num_resources] = {0};
162 unsigned expected_offset = 0;
163 unsigned out_offset;
164 void *map_ptr;
165 bool alloc_succeeded;
166
167 for (unsigned i = 0; i < num_resources; ++i) {
168 alloc_succeeded =
169 virgl_staging_alloc(&staging, alloc_sizes[i], alignment, &out_offset,
170 &out_resource[i], &map_ptr);
171
172 EXPECT_TRUE(alloc_succeeded);
173 EXPECT_EQ(out_offset, expected_offset);
174 ASSERT_NE(out_resource[i], nullptr);
175 if (i > 0) {
176 EXPECT_EQ(out_resource[i], out_resource[i - 1]);
177 }
178 EXPECT_EQ(map_ptr,
179 (uint8_t*)resource_map(out_resource[i]) + expected_offset);
180
181 expected_offset += alloc_sizes[i];
182 expected_offset = align(expected_offset, alignment);
183 }
184
185 release_resources(out_resource, num_resources);
186 }
187
188 INSTANTIATE_TEST_CASE_P(WithAlignment,
189 VirglStagingMgrWithAlignment,
190 ::testing::Values(1, 16),
191 testing::PrintToStringParamName());
192
TEST_F(VirglStagingMgr,non_fitting_allocation_reallocates_resource)193 TEST_F(VirglStagingMgr,
194 non_fitting_allocation_reallocates_resource)
195 {
196 struct virgl_hw_res *out_resource[2] = {0};
197 unsigned out_offset;
198 void *map_ptr;
199 bool alloc_succeeded;
200
201 alloc_succeeded =
202 virgl_staging_alloc(&staging, staging_size - 1, 1, &out_offset,
203 &out_resource[0], &map_ptr);
204
205 EXPECT_TRUE(alloc_succeeded);
206 EXPECT_EQ(out_offset, 0);
207 ASSERT_NE(out_resource[0], nullptr);
208 EXPECT_EQ(map_ptr, resource_map(out_resource[0]));
209
210 alloc_succeeded =
211 virgl_staging_alloc(&staging, 2, 1, &out_offset,
212 &out_resource[1], &map_ptr);
213
214 EXPECT_TRUE(alloc_succeeded);
215 EXPECT_EQ(out_offset, 0);
216 ASSERT_NE(out_resource[1], nullptr);
217 EXPECT_EQ(map_ptr, resource_map(out_resource[1]));
218 /* New resource with same size as old resource. */
219 EXPECT_NE(out_resource[1], out_resource[0]);
220 EXPECT_EQ(out_resource[1]->size, out_resource[0]->size);
221
222 release_resources(out_resource, 2);
223 }
224
TEST_F(VirglStagingMgr,non_fitting_aligned_allocation_reallocates_resource)225 TEST_F(VirglStagingMgr,
226 non_fitting_aligned_allocation_reallocates_resource)
227 {
228 struct virgl_hw_res *out_resource[2] = {0};
229 unsigned out_offset;
230 void *map_ptr;
231 bool alloc_succeeded;
232
233 alloc_succeeded =
234 virgl_staging_alloc(&staging, staging_size - 1, 1, &out_offset,
235 &out_resource[0], &map_ptr);
236
237 EXPECT_TRUE(alloc_succeeded);
238 EXPECT_EQ(out_offset, 0);
239 ASSERT_NE(out_resource[0], nullptr);
240 EXPECT_EQ(map_ptr, resource_map(out_resource[0]));
241
242 alloc_succeeded =
243 virgl_staging_alloc(&staging, 1, 16, &out_offset,
244 &out_resource[1], &map_ptr);
245
246 EXPECT_TRUE(alloc_succeeded);
247 EXPECT_EQ(out_offset, 0);
248 ASSERT_NE(out_resource[1], nullptr);
249 EXPECT_EQ(map_ptr, resource_map(out_resource[1]));
250 /* New resource with same size as old resource. */
251 EXPECT_NE(out_resource[1], out_resource[0]);
252 EXPECT_EQ(out_resource[1]->size, out_resource[0]->size);
253
254 release_resources(out_resource, 2);
255 }
256
TEST_F(VirglStagingMgr,large_non_fitting_allocation_reallocates_large_resource)257 TEST_F(VirglStagingMgr,
258 large_non_fitting_allocation_reallocates_large_resource)
259 {
260 struct virgl_hw_res *out_resource[2] = {0};
261 unsigned out_offset;
262 void *map_ptr;
263 bool alloc_succeeded;
264
265 ASSERT_LT(staging_size, 5123);
266
267 alloc_succeeded =
268 virgl_staging_alloc(&staging, 5123, 1, &out_offset,
269 &out_resource[0], &map_ptr);
270
271 EXPECT_TRUE(alloc_succeeded);
272 EXPECT_EQ(out_offset, 0);
273 ASSERT_NE(out_resource[0], nullptr);
274 EXPECT_EQ(map_ptr, resource_map(out_resource[0]));
275 EXPECT_GE(out_resource[0]->size, 5123);
276
277 alloc_succeeded =
278 virgl_staging_alloc(&staging, 19345, 1, &out_offset,
279 &out_resource[1], &map_ptr);
280
281 EXPECT_TRUE(alloc_succeeded);
282 EXPECT_EQ(out_offset, 0);
283 ASSERT_NE(out_resource[1], nullptr);
284 EXPECT_EQ(map_ptr, resource_map(out_resource[1]));
285 /* New resource */
286 EXPECT_NE(out_resource[1], out_resource[0]);
287 EXPECT_GE(out_resource[1]->size, 19345);
288
289 release_resources(out_resource, 2);
290 }
291
TEST_F(VirglStagingMgr,releases_resource_on_destruction)292 TEST_F(VirglStagingMgr, releases_resource_on_destruction)
293 {
294 struct virgl_hw_res *out_resource = NULL;
295 unsigned out_offset;
296 void *map_ptr;
297 bool alloc_succeeded;
298
299 alloc_succeeded =
300 virgl_staging_alloc(&staging, 128, 1, &out_offset,
301 &out_resource, &map_ptr);
302
303 EXPECT_TRUE(alloc_succeeded);
304 ASSERT_NE(out_resource, nullptr);
305 /* The resource is referenced both by staging internally,
306 * and out_resource.
307 */
308 EXPECT_EQ(out_resource->reference.count, 2);
309
310 /* Destroying staging releases the internal reference. */
311 virgl_staging_destroy(&staging);
312 EXPECT_EQ(out_resource->reference.count, 1);
313
314 release_resources(&out_resource, 1);
315 }
316
317 static struct virgl_hw_res *
failing_resource_create(struct virgl_winsys * vws,enum pipe_texture_target target,const void * map_front_private,uint32_t format,uint32_t bind,uint32_t width,uint32_t height,uint32_t depth,uint32_t array_size,uint32_t last_level,uint32_t nr_samples,uint32_t flags,uint32_t size)318 failing_resource_create(struct virgl_winsys *vws,
319 enum pipe_texture_target target,
320 const void *map_front_private,
321 uint32_t format, uint32_t bind,
322 uint32_t width, uint32_t height,
323 uint32_t depth, uint32_t array_size,
324 uint32_t last_level, uint32_t nr_samples,
325 uint32_t flags, uint32_t size)
326 {
327 return NULL;
328 }
329
TEST_F(VirglStagingMgr,fails_gracefully_if_resource_create_fails)330 TEST_F(VirglStagingMgr, fails_gracefully_if_resource_create_fails)
331 {
332 struct virgl_screen *vs = virgl_screen(ctx->screen);
333 struct virgl_hw_res *out_resource = NULL;
334 unsigned out_offset;
335 void *map_ptr;
336 bool alloc_succeeded;
337
338 vs->vws->resource_create = failing_resource_create;
339
340 alloc_succeeded =
341 virgl_staging_alloc(&staging, 128, 1, &out_offset,
342 &out_resource, &map_ptr);
343
344 EXPECT_FALSE(alloc_succeeded);
345 EXPECT_EQ(out_resource, nullptr);
346 EXPECT_EQ(map_ptr, nullptr);
347 }
348
349 static void *
failing_resource_map(struct virgl_winsys * vws,struct virgl_hw_res * hw_res)350 failing_resource_map(struct virgl_winsys *vws, struct virgl_hw_res *hw_res)
351 {
352 return NULL;
353 }
354
TEST_F(VirglStagingMgr,fails_gracefully_if_map_fails)355 TEST_F(VirglStagingMgr, fails_gracefully_if_map_fails)
356 {
357 struct virgl_screen *vs = virgl_screen(ctx->screen);
358 struct virgl_hw_res *out_resource = NULL;
359 unsigned out_offset;
360 void *map_ptr;
361 bool alloc_succeeded;
362
363 vs->vws->resource_map = failing_resource_map;
364
365 alloc_succeeded =
366 virgl_staging_alloc(&staging, 128, 1, &out_offset,
367 &out_resource, &map_ptr);
368
369 EXPECT_FALSE(alloc_succeeded);
370 EXPECT_EQ(out_resource, nullptr);
371 EXPECT_EQ(map_ptr, nullptr);
372 }
373
TEST_F(VirglStagingMgr,uses_staging_buffer_resource)374 TEST_F(VirglStagingMgr, uses_staging_buffer_resource)
375 {
376 struct virgl_hw_res *out_resource = NULL;
377 unsigned out_offset;
378 void *map_ptr;
379 bool alloc_succeeded;
380
381 alloc_succeeded =
382 virgl_staging_alloc(&staging, 128, 1, &out_offset,
383 &out_resource, &map_ptr);
384
385 EXPECT_TRUE(alloc_succeeded);
386 ASSERT_NE(out_resource, nullptr);
387 EXPECT_EQ(out_resource->target, PIPE_BUFFER);
388 EXPECT_EQ(out_resource->bind, VIRGL_BIND_STAGING);
389
390 release_resources(&out_resource, 1);
391 }
392