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