/* * 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 #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 { 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); }