| /* |
| * 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 <gtest/gtest.h> |
| |
| #include "virgl_context.h" |
| #include "virgl_resource.h" |
| #include "virgl_screen.h" |
| #include "virgl_staging_mgr.h" |
| #include "virgl_winsys.h" |
| |
| #include "util/u_inlines.h" |
| #include "util/u_memory.h" |
| |
| struct virgl_hw_res { |
| struct pipe_reference reference; |
| uint32_t target; |
| uint32_t bind; |
| uint32_t size; |
| void *data; |
| }; |
| |
| 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) |
| { |
| struct virgl_hw_res *hw_res = CALLOC_STRUCT(virgl_hw_res); |
| |
| pipe_reference_init(&hw_res->reference, 1); |
| |
| hw_res->target = target; |
| hw_res->bind = bind; |
| hw_res->size = size; |
| hw_res->data = CALLOC(size, 1); |
| |
| return hw_res; |
| } |
| |
| static void |
| fake_resource_reference(struct virgl_winsys *vws, |
| struct virgl_hw_res **dres, |
| struct virgl_hw_res *sres) |
| { |
| struct virgl_hw_res *old = *dres; |
| |
| if (pipe_reference(&(*dres)->reference, &sres->reference)) { |
| FREE(old->data); |
| FREE(old); |
| } |
| |
| *dres = sres; |
| } |
| |
| static void * |
| fake_resource_map(struct virgl_winsys *vws, struct virgl_hw_res *hw_res) |
| { |
| return hw_res->data; |
| } |
| |
| static struct pipe_context * |
| fake_virgl_context_create() |
| { |
| struct virgl_context *vctx = CALLOC_STRUCT(virgl_context); |
| struct virgl_screen *vs = CALLOC_STRUCT(virgl_screen); |
| struct virgl_winsys *vws = CALLOC_STRUCT(virgl_winsys); |
| |
| vctx->base.screen = &vs->base; |
| vs->vws = vws; |
| |
| vs->vws->resource_create = fake_resource_create; |
| vs->vws->resource_reference = fake_resource_reference; |
| vs->vws->resource_map = fake_resource_map; |
| |
| return &vctx->base; |
| } |
| |
| static void |
| fake_virgl_context_destroy(struct pipe_context *ctx) |
| { |
| struct virgl_context *vctx = virgl_context(ctx); |
| struct virgl_screen *vs = virgl_screen(ctx->screen); |
| |
| FREE(vs->vws); |
| FREE(vs); |
| FREE(vctx); |
| } |
| |
| static void * |
| resource_map(struct virgl_hw_res *hw_res) |
| { |
| return hw_res->data; |
| } |
| |
| static void |
| release_resources(struct virgl_hw_res *resources[], unsigned len) |
| { |
| for (unsigned i = 0; i < len; ++i) |
| fake_resource_reference(NULL, &resources[i], NULL); |
| } |
| |
| class VirglStagingMgr : public ::testing::Test |
| { |
| protected: |
| VirglStagingMgr() : ctx(fake_virgl_context_create()) |
| { |
| virgl_staging_init(&staging, ctx, staging_size); |
| } |
| |
| ~VirglStagingMgr() |
| { |
| virgl_staging_destroy(&staging); |
| fake_virgl_context_destroy(ctx); |
| } |
| |
| static const unsigned staging_size; |
| struct pipe_context * const ctx; |
| struct virgl_staging_mgr staging; |
| }; |
| |
| const unsigned VirglStagingMgr::staging_size = 4096; |
| |
| class VirglStagingMgrWithAlignment : public VirglStagingMgr, |
| public ::testing::WithParamInterface<unsigned> |
| { |
| protected: |
| VirglStagingMgrWithAlignment() : alignment(GetParam()) {} |
| const unsigned alignment; |
| }; |
| |
| TEST_P(VirglStagingMgrWithAlignment, |
| suballocations_are_non_overlapping_in_same_resource) |
| { |
| const unsigned alloc_sizes[] = {16, 450, 79, 240, 128, 1001}; |
| const unsigned num_resources = sizeof(alloc_sizes) / sizeof(alloc_sizes[0]); |
| struct virgl_hw_res *out_resource[num_resources] = {0}; |
| unsigned expected_offset = 0; |
| unsigned out_offset; |
| void *map_ptr; |
| bool alloc_succeeded; |
| |
| for (unsigned i = 0; i < num_resources; ++i) { |
| alloc_succeeded = |
| virgl_staging_alloc(&staging, alloc_sizes[i], alignment, &out_offset, |
| &out_resource[i], &map_ptr); |
| |
| EXPECT_TRUE(alloc_succeeded); |
| EXPECT_EQ(out_offset, expected_offset); |
| ASSERT_NE(out_resource[i], nullptr); |
| if (i > 0) { |
| EXPECT_EQ(out_resource[i], out_resource[i - 1]); |
| } |
| EXPECT_EQ(map_ptr, |
| (uint8_t*)resource_map(out_resource[i]) + expected_offset); |
| |
| expected_offset += alloc_sizes[i]; |
| expected_offset = align(expected_offset, alignment); |
| } |
| |
| release_resources(out_resource, num_resources); |
| } |
| |
| INSTANTIATE_TEST_CASE_P(WithAlignment, |
| VirglStagingMgrWithAlignment, |
| ::testing::Values(1, 16), |
| testing::PrintToStringParamName()); |
| |
| TEST_F(VirglStagingMgr, |
| non_fitting_allocation_reallocates_resource) |
| { |
| struct virgl_hw_res *out_resource[2] = {0}; |
| unsigned out_offset; |
| void *map_ptr; |
| bool alloc_succeeded; |
| |
| alloc_succeeded = |
| virgl_staging_alloc(&staging, staging_size - 1, 1, &out_offset, |
| &out_resource[0], &map_ptr); |
| |
| EXPECT_TRUE(alloc_succeeded); |
| EXPECT_EQ(out_offset, 0); |
| ASSERT_NE(out_resource[0], nullptr); |
| EXPECT_EQ(map_ptr, resource_map(out_resource[0])); |
| |
| alloc_succeeded = |
| virgl_staging_alloc(&staging, 2, 1, &out_offset, |
| &out_resource[1], &map_ptr); |
| |
| EXPECT_TRUE(alloc_succeeded); |
| EXPECT_EQ(out_offset, 0); |
| ASSERT_NE(out_resource[1], nullptr); |
| EXPECT_EQ(map_ptr, resource_map(out_resource[1])); |
| /* New resource with same size as old resource. */ |
| EXPECT_NE(out_resource[1], out_resource[0]); |
| EXPECT_EQ(out_resource[1]->size, out_resource[0]->size); |
| |
| release_resources(out_resource, 2); |
| } |
| |
| TEST_F(VirglStagingMgr, |
| non_fitting_aligned_allocation_reallocates_resource) |
| { |
| struct virgl_hw_res *out_resource[2] = {0}; |
| unsigned out_offset; |
| void *map_ptr; |
| bool alloc_succeeded; |
| |
| alloc_succeeded = |
| virgl_staging_alloc(&staging, staging_size - 1, 1, &out_offset, |
| &out_resource[0], &map_ptr); |
| |
| EXPECT_TRUE(alloc_succeeded); |
| EXPECT_EQ(out_offset, 0); |
| ASSERT_NE(out_resource[0], nullptr); |
| EXPECT_EQ(map_ptr, resource_map(out_resource[0])); |
| |
| alloc_succeeded = |
| virgl_staging_alloc(&staging, 1, 16, &out_offset, |
| &out_resource[1], &map_ptr); |
| |
| EXPECT_TRUE(alloc_succeeded); |
| EXPECT_EQ(out_offset, 0); |
| ASSERT_NE(out_resource[1], nullptr); |
| EXPECT_EQ(map_ptr, resource_map(out_resource[1])); |
| /* New resource with same size as old resource. */ |
| EXPECT_NE(out_resource[1], out_resource[0]); |
| EXPECT_EQ(out_resource[1]->size, out_resource[0]->size); |
| |
| release_resources(out_resource, 2); |
| } |
| |
| TEST_F(VirglStagingMgr, |
| large_non_fitting_allocation_reallocates_large_resource) |
| { |
| struct virgl_hw_res *out_resource[2] = {0}; |
| unsigned out_offset; |
| void *map_ptr; |
| bool alloc_succeeded; |
| |
| ASSERT_LT(staging_size, 5123); |
| |
| alloc_succeeded = |
| virgl_staging_alloc(&staging, 5123, 1, &out_offset, |
| &out_resource[0], &map_ptr); |
| |
| EXPECT_TRUE(alloc_succeeded); |
| EXPECT_EQ(out_offset, 0); |
| ASSERT_NE(out_resource[0], nullptr); |
| EXPECT_EQ(map_ptr, resource_map(out_resource[0])); |
| EXPECT_GE(out_resource[0]->size, 5123); |
| |
| alloc_succeeded = |
| virgl_staging_alloc(&staging, 19345, 1, &out_offset, |
| &out_resource[1], &map_ptr); |
| |
| EXPECT_TRUE(alloc_succeeded); |
| EXPECT_EQ(out_offset, 0); |
| ASSERT_NE(out_resource[1], nullptr); |
| EXPECT_EQ(map_ptr, resource_map(out_resource[1])); |
| /* New resource */ |
| EXPECT_NE(out_resource[1], out_resource[0]); |
| EXPECT_GE(out_resource[1]->size, 19345); |
| |
| release_resources(out_resource, 2); |
| } |
| |
| TEST_F(VirglStagingMgr, releases_resource_on_destruction) |
| { |
| struct virgl_hw_res *out_resource = NULL; |
| unsigned out_offset; |
| void *map_ptr; |
| bool alloc_succeeded; |
| |
| alloc_succeeded = |
| virgl_staging_alloc(&staging, 128, 1, &out_offset, |
| &out_resource, &map_ptr); |
| |
| EXPECT_TRUE(alloc_succeeded); |
| ASSERT_NE(out_resource, nullptr); |
| /* The resource is referenced both by staging internally, |
| * and out_resource. |
| */ |
| EXPECT_EQ(out_resource->reference.count, 2); |
| |
| /* Destroying staging releases the internal reference. */ |
| virgl_staging_destroy(&staging); |
| EXPECT_EQ(out_resource->reference.count, 1); |
| |
| release_resources(&out_resource, 1); |
| } |
| |
| 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) |
| { |
| return NULL; |
| } |
| |
| TEST_F(VirglStagingMgr, fails_gracefully_if_resource_create_fails) |
| { |
| struct virgl_screen *vs = virgl_screen(ctx->screen); |
| struct virgl_hw_res *out_resource = NULL; |
| unsigned out_offset; |
| void *map_ptr; |
| bool alloc_succeeded; |
| |
| vs->vws->resource_create = failing_resource_create; |
| |
| alloc_succeeded = |
| virgl_staging_alloc(&staging, 128, 1, &out_offset, |
| &out_resource, &map_ptr); |
| |
| EXPECT_FALSE(alloc_succeeded); |
| EXPECT_EQ(out_resource, nullptr); |
| EXPECT_EQ(map_ptr, nullptr); |
| } |
| |
| static void * |
| failing_resource_map(struct virgl_winsys *vws, struct virgl_hw_res *hw_res) |
| { |
| return NULL; |
| } |
| |
| TEST_F(VirglStagingMgr, fails_gracefully_if_map_fails) |
| { |
| struct virgl_screen *vs = virgl_screen(ctx->screen); |
| struct virgl_hw_res *out_resource = NULL; |
| unsigned out_offset; |
| void *map_ptr; |
| bool alloc_succeeded; |
| |
| vs->vws->resource_map = failing_resource_map; |
| |
| alloc_succeeded = |
| virgl_staging_alloc(&staging, 128, 1, &out_offset, |
| &out_resource, &map_ptr); |
| |
| EXPECT_FALSE(alloc_succeeded); |
| EXPECT_EQ(out_resource, nullptr); |
| EXPECT_EQ(map_ptr, nullptr); |
| } |
| |
| TEST_F(VirglStagingMgr, uses_staging_buffer_resource) |
| { |
| struct virgl_hw_res *out_resource = NULL; |
| unsigned out_offset; |
| void *map_ptr; |
| bool alloc_succeeded; |
| |
| alloc_succeeded = |
| virgl_staging_alloc(&staging, 128, 1, &out_offset, |
| &out_resource, &map_ptr); |
| |
| EXPECT_TRUE(alloc_succeeded); |
| ASSERT_NE(out_resource, nullptr); |
| EXPECT_EQ(out_resource->target, PIPE_BUFFER); |
| EXPECT_EQ(out_resource->bind, VIRGL_BIND_STAGING); |
| |
| release_resources(&out_resource, 1); |
| } |