diff --git a/src/xrt/compositor/CMakeLists.txt b/src/xrt/compositor/CMakeLists.txt index 0113e44eb..9040a6d98 100644 --- a/src/xrt/compositor/CMakeLists.txt +++ b/src/xrt/compositor/CMakeLists.txt @@ -277,3 +277,13 @@ target_include_directories(comp_multi PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) if(XRT_FEATURE_COMPOSITOR_MAIN) target_link_libraries(comp_main PRIVATE comp_multi) endif() + +### +# Mock native compositor +add_library(comp_mock STATIC mock/mock_compositor.cpp mock/mock_compositor.h) +target_include_directories(comp_mock PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries( + comp_mock + PUBLIC xrt-interfaces + PRIVATE aux_util + ) diff --git a/src/xrt/compositor/mock/mock_compositor.cpp b/src/xrt/compositor/mock/mock_compositor.cpp new file mode 100644 index 000000000..0221a3d46 --- /dev/null +++ b/src/xrt/compositor/mock/mock_compositor.cpp @@ -0,0 +1,204 @@ +// Copyright 2020-2022, Collabora, Ltd. +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief A mock native compositor to use when testing client compositors. + * @author Ryan Pavlik <ryan.pavlik@collabora.com> + * @author Jakob Bornecrantz <jakob@collabora.com> + */ + +#include "mock_compositor.h" + +#include "util/u_misc.h" +#include "util/u_handles.h" + +static void +mock_compositor_swapchain_destroy(struct xrt_swapchain *xsc) +{ + struct mock_compositor_swapchain *mcsc = mock_compositor_swapchain(xsc); + struct mock_compositor *mc = mcsc->mc; + + if (mc->swapchain_hooks.destroy) { + mc->swapchain_hooks.destroy(mc, mcsc); + } + for (uint32_t i = 0; i < mcsc->base.base.image_count; ++i) { + u_graphics_buffer_unref(&(mcsc->handles[i])); + } + free(xsc); +} + +static xrt_result_t +mock_compositor_swapchain_wait_image(struct xrt_swapchain *xsc, uint64_t timeout, uint32_t index) +{ + struct mock_compositor_swapchain *mcsc = mock_compositor_swapchain(xsc); + struct mock_compositor *mc = mcsc->mc; + + if (mc->swapchain_hooks.wait_image) { + return mc->swapchain_hooks.wait_image(mc, mcsc, timeout, index); + } + mcsc->waited[index] = true; + return XRT_SUCCESS; +} + +static xrt_result_t +mock_compositor_swapchain_acquire_image(struct xrt_swapchain *xsc, uint32_t *out_index) +{ + struct mock_compositor_swapchain *mcsc = mock_compositor_swapchain(xsc); + struct mock_compositor *mc = mcsc->mc; + + if (mc->swapchain_hooks.acquire_image) { + return mc->swapchain_hooks.acquire_image(mc, mcsc, out_index); + } + uint32_t index = mcsc->next_to_acquire; + mcsc->next_to_acquire = (mcsc->next_to_acquire + 1) % mcsc->base.base.image_count; + mcsc->acquired[index] = true; + + return XRT_SUCCESS; +} + +static xrt_result_t +mock_compositor_swapchain_release_image(struct xrt_swapchain *xsc, uint32_t index) +{ + struct mock_compositor_swapchain *mcsc = mock_compositor_swapchain(xsc); + struct mock_compositor *mc = mcsc->mc; + + if (mc->swapchain_hooks.acquire_image) { + return mc->swapchain_hooks.release_image(mc, mcsc, index); + } + mcsc->acquired[index] = false; + mcsc->waited[index] = false; + + return XRT_SUCCESS; +} + + +static xrt_result_t +mock_compositor_swapchain_create(struct xrt_compositor *xc, + const struct xrt_swapchain_create_info *info, + struct xrt_swapchain **out_xsc) +{ + struct mock_compositor *mc = mock_compositor(xc); + // Mini implementation of get_swapchain_create_properties to avoid an actual call causing confusing traces in + // the mock + uint32_t image_count = (0 != (info->create & XRT_SWAPCHAIN_CREATE_STATIC_IMAGE)) ? 1 : 3; + constexpr bool use_dedicated_allocation = false; + + + struct mock_compositor_swapchain *mcsc = U_TYPED_CALLOC(struct mock_compositor_swapchain); + mcsc->base.base.image_count = image_count; + mcsc->base.base.wait_image = mock_compositor_swapchain_wait_image; + mcsc->base.base.acquire_image = mock_compositor_swapchain_acquire_image; + mcsc->base.base.release_image = mock_compositor_swapchain_release_image; + mcsc->base.base.destroy = mock_compositor_swapchain_destroy; + mcsc->base.base.reference.count = 1; + mcsc->mc = mc; + mcsc->id = mc->next_id; + mcsc->info = *info; + mc->next_id++; + + *out_xsc = &mcsc->base.base; + if (mc->compositor_hooks.create_swapchain) { + return mc->compositor_hooks.create_swapchain(mc, mcsc, info, out_xsc); + } + + for (uint32_t i = 0; i < image_count; i++) { + mcsc->base.images[i].handle = XRT_GRAPHICS_BUFFER_HANDLE_INVALID; + mcsc->base.images[i].use_dedicated_allocation = use_dedicated_allocation; + } + + + return XRT_SUCCESS; +} + +static xrt_result_t +mock_compositor_swapchain_import(struct xrt_compositor *xc, + const struct xrt_swapchain_create_info *info, + struct xrt_image_native *native_images, + uint32_t image_count, + struct xrt_swapchain **out_xsc) +{ + struct mock_compositor *mc = mock_compositor(xc); + struct mock_compositor_swapchain *mcsc = U_TYPED_CALLOC(struct mock_compositor_swapchain); + mcsc->base.base.image_count = image_count; + mcsc->base.base.wait_image = mock_compositor_swapchain_wait_image; + mcsc->base.base.acquire_image = mock_compositor_swapchain_acquire_image; + mcsc->base.base.release_image = mock_compositor_swapchain_release_image; + mcsc->base.base.destroy = mock_compositor_swapchain_destroy; + mcsc->base.base.reference.count = 1; + mcsc->imported = true; + mcsc->mc = mc; + mcsc->id = mc->next_id; + mcsc->info = *info; + mc->next_id++; + + *out_xsc = &mcsc->base.base; + if (mc->compositor_hooks.import_swapchain) { + return mc->compositor_hooks.import_swapchain(mc, mcsc, info, native_images, image_count, out_xsc); + } + + for (uint32_t i = 0; i < image_count; i++) { + mcsc->handles[i] = native_images[i].handle; + mcsc->base.images[i] = native_images[i]; + } + + return XRT_SUCCESS; +} + +static xrt_result_t +mock_compositor_get_swapchain_create_properties(struct xrt_compositor *xc, + const struct xrt_swapchain_create_info *info, + struct xrt_swapchain_create_properties *xsccp) +{ + struct mock_compositor *mc = mock_compositor(xc); + + if (mc->compositor_hooks.get_swapchain_create_properties) { + return mc->compositor_hooks.get_swapchain_create_properties(mc, info, xsccp); + } + // default "normal" impl + if (0 != (info->create & XRT_SWAPCHAIN_CREATE_STATIC_IMAGE)) { + xsccp->image_count = 1; + } else { + xsccp->image_count = 3; + } + return XRT_SUCCESS; +} + +static void +mock_compositor_destroy(struct xrt_compositor *xc) +{ + struct mock_compositor *mc = mock_compositor(xc); + + if (mc->compositor_hooks.destroy) { + return mc->compositor_hooks.destroy(mc); + } + free(mc); +} + +struct xrt_compositor_native * +mock_create_native_compositor() +{ + struct mock_compositor *mc = U_TYPED_CALLOC(struct mock_compositor); + mc->base.base.get_swapchain_create_properties = mock_compositor_get_swapchain_create_properties; + mc->base.base.create_swapchain = mock_compositor_swapchain_create; + mc->base.base.import_swapchain = mock_compositor_swapchain_import; + // mc->base.base.create_semaphore = mock_compositor_semaphore_create; + // mc->base.base.begin_session = mock_compositor_begin_session; + // mc->base.base.end_session = mock_compositor_end_session; + // mc->base.base.wait_frame = mock_compositor_wait_frame; + // mc->base.base.begin_frame = mock_compositor_begin_frame; + // mc->base.base.discard_frame = mock_compositor_discard_frame; + // mc->base.base.layer_begin = mock_compositor_layer_begin; + // mc->base.base.layer_stereo_projection = mock_compositor_layer_stereo_projection; + // mc->base.base.layer_stereo_projection_depth = mock_compositor_layer_stereo_projection_depth; + // mc->base.base.layer_quad = mock_compositor_layer_quad; + // mc->base.base.layer_cube = mock_compositor_layer_cube; + // mc->base.base.layer_cylinder = mock_compositor_layer_cylinder; + // mc->base.base.layer_equirect1 = mock_compositor_layer_equirect1; + // mc->base.base.layer_equirect2 = mock_compositor_layer_equirect2; + // mc->base.base.layer_commit = mock_compositor_layer_commit; + // mc->base.base.layer_commit_with_semaphore = mock_compositor_layer_commit_with_semaphore; + // mc->base.base.poll_events = mock_compositor_poll_events; + mc->base.base.destroy = mock_compositor_destroy; + + return &mc->base; +} diff --git a/src/xrt/compositor/mock/mock_compositor.h b/src/xrt/compositor/mock/mock_compositor.h new file mode 100644 index 000000000..516481eda --- /dev/null +++ b/src/xrt/compositor/mock/mock_compositor.h @@ -0,0 +1,360 @@ +// Copyright 2020-2022, Collabora, Ltd. +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief A mock native compositor to use when testing client compositors. + * @author Ryan Pavlik <ryan.pavlik@collabora.com> + * @author Jakob Bornecrantz <jakob@collabora.com> + */ + +#pragma once +#include "xrt/xrt_compositor.h" +#include "xrt/xrt_defines.h" +#include "xrt/xrt_handles.h" + +#ifdef __cplusplus + +#include <type_traits> + +extern "C" { +#endif + +struct mock_compositor_swapchain; +/*! + * Mock implementation of a native compositor + * @implements xrt_compositor_native + */ +struct mock_compositor +{ + struct xrt_compositor_native base; + + //! ID for next swapchain + uint32_t next_id; + + //! Mock users can populate this pointer to use data from hooks + void *userdata; + + /*! + * Optional function pointers you can populate to hook into the behavior of the mock compositor implementation. + * + * Providing a function pointer will disable any built-in functionality in the mock for most of these fields. + * While you can populate these with a lambda, because they're plain function pointers, you can't have any + * captures, so use @ref mock_compositor::userdata to read or write any data from the outside world. + */ + struct + { + /*! + * Optional function pointer for mock compositor, called during + * @ref xrt_comp_get_swapchain_create_properties + */ + xrt_result_t (*get_swapchain_create_properties)(struct mock_compositor *mc, + const struct xrt_swapchain_create_info *info, + struct xrt_swapchain_create_properties *xsccp); + + /*! + * Optional function pointer for mock compositor, called during @ref xrt_comp_create_swapchain + * + * Takes the extra parameter of the typed pointer to the in-progress swapchain @p mcsc , which is + * allocated and has basic values populated for it, even if this function pointer is set. + */ + xrt_result_t (*create_swapchain)(struct mock_compositor *mc, + struct mock_compositor_swapchain *mcsc, + const struct xrt_swapchain_create_info *info, + struct xrt_swapchain **out_xsc); + + /*! + * Optional function pointer for mock compositor, called during @ref xrt_comp_import_swapchain + * + * Takes the extra parameter of the typed pointer to the in-progress swapchain @p mcsc , which is + * allocated and has basic values populated for it, even if this function pointer is set. Does **not** + * release the native images passed in if this function pointer is set, so you will have to do that + * yourself. + */ + xrt_result_t (*import_swapchain)(struct mock_compositor *mc, + struct mock_compositor_swapchain *mcsc, + const struct xrt_swapchain_create_info *info, + struct xrt_image_native *native_images, + uint32_t image_count, + struct xrt_swapchain **out_xsc); + + // Mocks for the following not yet implemented +#if 0 + /*! + * Optional function pointer for mock compositor, called during @ref xrt_comp_import_fence + */ + xrt_result_t (*import_fence)(struct mock_compositor *mc, + xrt_graphics_sync_handle_t handle, + struct xrt_compositor_fence **out_xcf); + + /*! + * Optional function pointer for mock compositor, called during @ref xrt_comp_create_semaphore + */ + xrt_result_t (*create_semaphore)(struct mock_compositor *mc, + xrt_graphics_sync_handle_t *out_handle, + struct xrt_compositor_semaphore **out_xcsem); + + /*! + * Optional function pointer for mock compositor, called during @ref xrt_comp_poll_events + */ + xrt_result_t (*poll_events)(struct mock_compositor *mc, union xrt_compositor_event *out_xce); + + /*! + * Optional function pointer for mock compositor, called during @ref xrt_comp_begin_session + */ + xrt_result_t (*begin_session)(struct mock_compositor *mc, enum xrt_view_type view_type); + + /*! + * Optional function pointer for mock compositor, called during @ref xrt_comp_end_session + */ + xrt_result_t (*end_session)(struct mock_compositor *mc); + + /*! + * Optional function pointer for mock compositor, called during @ref xrt_comp_predict_frame + */ + xrt_result_t (*predict_frame)(struct mock_compositor *mc, + int64_t *out_frame_id, + uint64_t *out_wake_time_ns, + uint64_t *out_predicted_gpu_time_ns, + uint64_t *out_predicted_display_time_ns, + uint64_t *out_predicted_display_period_ns); + + /*! + * + * Optional function pointer for mock compositor, called during @ref xrt_comp_mark_frame + */ + xrt_result_t (*mark_frame)(struct mock_compositor *mc, + int64_t frame_id, + enum xrt_compositor_frame_point point, + uint64_t when_ns); + + /*! + * Optional function pointer for mock compositor, called during @ref xrt_comp_wait_frame + */ + xrt_result_t (*wait_frame)(struct mock_compositor *mc, + int64_t *out_frame_id, + uint64_t *out_predicted_display_time, + uint64_t *out_predicted_display_period); + + /*! + * Optional function pointer for mock compositor, called during @ref xrt_comp_begin_frame + */ + xrt_result_t (*begin_frame)(struct mock_compositor *mc, int64_t frame_id); + + /*! + * Optional function pointer for mock compositor, called during @ref xrt_comp_discard_frame + */ + xrt_result_t (*discard_frame)(struct mock_compositor *mc, int64_t frame_id); + + /*! + * Optional function pointer for mock compositor, called during @ref xrt_comp_layer_begin + */ + xrt_result_t (*layer_begin)(struct mock_compositor *mc, + int64_t frame_id, + uint64_t display_time_ns, + enum xrt_blend_mode env_blend_mode); + + /*! + * Optional function pointer for mock compositor, called during @ref xrt_comp_layer_stereo_projection + */ + xrt_result_t (*layer_stereo_projection)(struct mock_compositor *mc, + struct xrt_device *xdev, + struct xrt_swapchain *l_xsc, + struct xrt_swapchain *r_xsc, + const struct xrt_layer_data *data); + + /*! + * Optional function pointer for mock compositor, called during @ref + * xrt_comp_layer_stereo_projection_depth + */ + xrt_result_t (*layer_stereo_projection_depth)(struct mock_compositor *mc, + struct xrt_device *xdev, + struct xrt_swapchain *l_xsc, + struct xrt_swapchain *r_xsc, + struct xrt_swapchain *l_d_xsc, + struct xrt_swapchain *r_d_xsc, + const struct xrt_layer_data *data); + + /*! + * Optional function pointer for mock compositor, called during @ref xrt_comp_layer_quad + */ + xrt_result_t (*layer_quad)(struct mock_compositor *mc, + struct xrt_device *xdev, + struct xrt_swapchain *xsc, + const struct xrt_layer_data *data); + + /*! + * Optional function pointer for mock compositor, called during @ref xrt_comp_layer_cube + */ + xrt_result_t (*layer_cube)(struct mock_compositor *mc, + struct xrt_device *xdev, + struct xrt_swapchain *xsc, + const struct xrt_layer_data *data); + + /*! + * Optional function pointer for mock compositor, called during @ref xrt_comp_layer_cylinder + */ + xrt_result_t (*layer_cylinder)(struct mock_compositor *mc, + struct xrt_device *xdev, + struct xrt_swapchain *xsc, + const struct xrt_layer_data *data); + + /*! + * Optional function pointer for mock compositor, called during @ref xrt_comp_layer_equirect1 + */ + xrt_result_t (*layer_equirect1)(struct mock_compositor *mc, + struct xrt_device *xdev, + struct xrt_swapchain *xsc, + const struct xrt_layer_data *data); + + + /*! + * Optional function pointer for mock compositor, called during @ref xrt_comp_layer_equirect2 + */ + xrt_result_t (*layer_equirect2)(struct mock_compositor *mc, + struct xrt_device *xdev, + struct xrt_swapchain *xsc, + const struct xrt_layer_data *data); + + /*! + * Optional function pointer for mock compositor, called during @ref xrt_comp_layer_commit + */ + xrt_result_t (*layer_commit)(struct mock_compositor *mc, + int64_t frame_id, + xrt_graphics_sync_handle_t sync_handle); + + /*! + * Optional function pointer for mock compositor, called during @ref + * xrt_comp_layer_commit_with_semaphore + */ + xrt_result_t (*layer_commit_with_semaphore)(struct mock_compositor *mc, + int64_t frame_id, + struct xrt_compositor_semaphore *xcsem, + uint64_t value); +#endif + + /*! + * Optional function pointer for mock compositor, called during @ref xrt_comp_destroy (before actual + * destruction) + * + * The actual destruction is done by the mock implementation whether or not you populate this field. + */ + void (*destroy)(struct mock_compositor *mc); + } compositor_hooks; + + /*! + * Optional function pointers you can populate to hook into the behavior of the mock swapchain implementation. + * + * Providing a function pointer will disable any built-in functionality in the mock for most of these fields. + * While you can populate these with a lambda, because they're plain function pointers, you can't have any + * captures, so use @ref mock_compositor::userdata to read or write any data from the outside world. + */ + struct + { + /*! + * Optional function pointer, called during @ref xrt_swapchain::destroy (before actual + * destruction) + * + * The actual destruction is done by the mock implementation whether or not you populate this field. + */ + void (*destroy)(struct mock_compositor *mc, struct mock_compositor_swapchain *mcsc); + + /*! + * Optional function pointer, called during @ref xrt_swapchain::acquire_image + */ + xrt_result_t (*acquire_image)(struct mock_compositor *mc, + struct mock_compositor_swapchain *mcsc, + uint32_t *out_index); + + /*! + * Optional function pointer, called during @ref xrt_swapchain::wait_image + */ + xrt_result_t (*wait_image)(struct mock_compositor *mc, + struct mock_compositor_swapchain *mcsc, + uint64_t timeout, + uint32_t index); + + /*! + * Optional function pointer, called during @ref xrt_swapchain::release_image + */ + xrt_result_t (*release_image)(struct mock_compositor *mc, + struct mock_compositor_swapchain *mcsc, + uint32_t index); + } swapchain_hooks; +}; + +/*! + * @brief Cast a generic @ref xrt_compositor pointer (that you know externally is a @ref mock_compositor) to a @p + * mock_compositor pointer. + */ +static inline struct mock_compositor * +mock_compositor(xrt_compositor *xc) +{ + return (struct mock_compositor *)(xc); +} + +/*! + * Mock implementation of @ref xrt_swapchain_native + */ +struct mock_compositor_swapchain +{ + struct xrt_swapchain_native base; + + //! A swapchain ID, assigned by create_swapchain/import_swapchain + uint32_t id; + + //! Set if this swapchain was created by import_swapchain + bool imported; + + //! Populated by copying the create info passed to create_swapchain/import_swapchain + xrt_swapchain_create_info info; + /** + * Native handles for images. + * Populated by the import_swapchain mock if not hooked. + * Will be released/unreferenced at destruction by default. + */ + xrt_graphics_buffer_handle_t handles[XRT_MAX_SWAPCHAIN_IMAGES]; + + //! Modified by the default mock implementations of acquire_image and release_image + bool acquired[XRT_MAX_SWAPCHAIN_IMAGES]; + + //! Modified by the default mock implementations of wait_image and release_image + bool waited[XRT_MAX_SWAPCHAIN_IMAGES]; + + /** + * The image ID that will next be acquired. + * + * The default minimal mock implementation just increments this, modulo image count, regardless of + * acquire/wait/release status. + */ + uint32_t next_to_acquire; + + //! non-owning pointer to parent + struct mock_compositor *mc; +}; + +/*! + * Cast a generic @ref xrt_swapchain pointer (that you know externally is a @ref mock_compositor_swapchain) to a @p + * mock_compositor_swapchain pointer. + */ +static inline struct mock_compositor_swapchain * +mock_compositor_swapchain(xrt_swapchain *xsc) +{ + return (struct mock_compositor_swapchain *)(xsc); +} + +/*! + * Create a mock implementation of @ref xrt_compositor_native. + * + * The returned value can be passed to @ref mock_compositor() to use the internals of the mock, e.g. to populate + * hooks to override mock behavior. + */ +struct xrt_compositor_native * +mock_create_native_compositor(); + +#ifdef __cplusplus + +static_assert(std::is_standard_layout<struct mock_compositor>::value); +static_assert(std::is_standard_layout<struct mock_compositor_swapchain>::value); + +} // extern "C" +#endif