// Copyright 2021, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
 * @file
 * @brief  System compositor capable of supporting multiple clients: internal structs.
 * @author Jakob Bornecrantz <jakob@collabora.com>
 * @ingroup comp_multi
 */

#pragma once

#include "xrt/xrt_compiler.h"
#include "xrt/xrt_defines.h"
#include "xrt/xrt_limits.h"
#include "xrt/xrt_compositor.h"

#include "os/os_time.h"
#include "os/os_threading.h"

#include "util/u_pacing.h"

#include "comp_multi_interface.h"


#ifdef __cplusplus
extern "C" {
#endif


/*!
 * Number of max active clients.
 *
 * @todo Move to `xrt_limits.h`, or make dynamic to remove limit.
 * @ingroup comp_multi
 */
#define MULTI_MAX_CLIENTS 64

/*!
 * Number of max active layers per @ref multi_compositor.
 *
 * @todo Move to `xrt_limits.h` and share.
 * @ingroup comp_multi
 */
#define MULTI_MAX_LAYERS XRT_MAX_LAYERS


/*
 *
 * Native compositor.
 *
 */

/*!
 * Data for a single composition layer.
 *
 * Similar in function to @ref comp_layer
 *
 * @ingroup comp_multi
 */
struct multi_layer_entry
{
	/*!
	 * Device to get pose from.
	 */
	struct xrt_device *xdev;

	/*!
	 * Pointers to swapchains.
	 *
	 * How many are actually used depends on the value of @p data.type
	 */
	struct xrt_swapchain *xscs[2 * XRT_MAX_VIEWS];

	/*!
	 * All basic (trivially-serializable) data associated with a layer,
	 * aside from which swapchain(s) are used.
	 */
	struct xrt_layer_data data;
};

/*!
 * Render state for a single client, including all layers.
 *
 * @ingroup comp_multi
 */
struct multi_layer_slot
{
	struct xrt_layer_frame_data data;
	uint32_t layer_count;
	struct multi_layer_entry layers[MULTI_MAX_LAYERS];
	bool active;
};

/*!
 * A single compositor for feeding the layers from one session/app into
 * the multi-client-capable system compositor.
 *
 * An instance (usually an IPC server instance) might have several of
 * these at once, feeding layers to a single multi-client-capable system
 * compositor.
 *
 * @ingroup comp_multi
 * @implements xrt_compositor_native
 */
struct multi_compositor
{
	struct xrt_compositor_native base;

	// Client info.
	struct xrt_session_info xsi;

	//! Where events for this compositor should go.
	struct xrt_session_event_sink *xses;

	//! Owning system compositor.
	struct multi_system_compositor *msc;

	//! Used to implement wait frame, only used for in process.
	struct os_precise_sleeper frame_sleeper;

	//! Used when waiting for the scheduled frame to complete.
	struct os_precise_sleeper scheduled_sleeper;

	struct
	{
		bool visible;
		bool focused;

		int64_t z_order;

		bool session_active;
		bool is_base_session;
	} state;

	struct
	{
		//! Fence to wait for.
		struct xrt_compositor_fence *xcf;

		//! Timeline semaphore to wait for.
		struct xrt_compositor_semaphore *xcsem;

		//! Timeline semaphore value to wait for.
		uint64_t value;

		//! Frame id of frame being waited on.
		int64_t frame_id;

		//! The wait thread itself
		struct os_thread_helper oth;

		//! Have we gotten to the loop?
		bool alive;

		//! Is the thread waiting, if so the client should block.
		bool waiting;

		/*!
		 * Is the client thread blocked?
		 *
		 * Set to true by the client thread,
		 * cleared by the wait thread to release the client thread.
		 */
		bool blocked;
	} wait_thread;

	//! Lock for all of the slots.
	struct os_mutex slot_lock;

	/*!
	 * The next which the next frames to be picked up will be displayed.
	 */
	int64_t slot_next_frame_display;

	/*!
	 * Currently being transferred or waited on.
	 * Not protected by the slot lock as it is only touched by the client thread.
	 */
	struct multi_layer_slot progress;

	//! Scheduled frames for a future timepoint.
	struct multi_layer_slot scheduled;

	/*!
	 * Fully ready to be used.
	 * Not protected by the slot lock as it is only touched by the main render loop thread.
	 */
	struct multi_layer_slot delivered;

	struct u_pacing_app *upa;

	float current_refresh_rate_hz;
};

/*!
 * Small helper go from @ref xrt_compositor to @ref multi_compositor.
 *
 * @ingroup comp_multi
 */
static inline struct multi_compositor *
multi_compositor(struct xrt_compositor *xc)
{
	return (struct multi_compositor *)xc;
}

/*!
 * Create a multi client wrapper compositor.
 *
 * @ingroup comp_multi
 */
xrt_result_t
multi_compositor_create(struct multi_system_compositor *msc,
                        const struct xrt_session_info *xsi,
                        struct xrt_session_event_sink *xses,
                        struct xrt_compositor_native **out_xcn);

/*!
 * Push a event to be delivered to the session that corresponds
 * to the given @ref multi_compositor.
 *
 * @ingroup comp_multi
 * @private @memberof multi_compositor
 */
XRT_CHECK_RESULT xrt_result_t
multi_compositor_push_event(struct multi_compositor *mc, const union xrt_session_event *xse);

/*!
 * Deliver any scheduled frames at that is to be display at or after the given @p display_time_ns. Called by the render
 * thread and copies data from multi_compositor::scheduled to multi_compositor::delivered while holding the slot_lock.
 *
 * @ingroup comp_multi
 * @private @memberof multi_compositor
 */
void
multi_compositor_deliver_any_frames(struct multi_compositor *mc, int64_t display_time_ns);

/*!
 * Makes the current delivered frame as latched, called by the render thread.
 * The list_and_timing_lock is held when this function is called.
 *
 * @ingroup comp_multi
 * @private @memberof multi_compositor
 */
void
multi_compositor_latch_frame_locked(struct multi_compositor *mc, int64_t when_ns, int64_t system_frame_id);

/*!
 * Clears and retires the delivered frame, called by the render thread.
 * The list_and_timing_lock is held when this function is called.
 *
 * @ingroup comp_multi
 * @private @memberof multi_compositor
 */
void
multi_compositor_retire_delivered_locked(struct multi_compositor *mc, int64_t when_ns);


/*
 *
 * Multi-client-capable system compositor
 *
 */

/*!
 * State of the multi-client system compositor. Use to track the calling of native
 * compositor methods @ref xrt_comp_begin_session and @ref xrt_comp_end_session.
 *
 * It is driven by the number of active app sessions.
 *
 * @ingroup comp_multi
 */
enum multi_system_state
{
	/*!
	 * Invalid state, never used.
	 */
	MULTI_SYSTEM_STATE_INVALID,

	/*!
	 * One of the initial states, the multi-client system compositor will
	 * make sure that its @ref xrt_compositor_native submits one frame.
	 *
	 * The session hasn't been started yet.
	 */
	MULTI_SYSTEM_STATE_INIT_WARM_START,

	/*!
	 * One of the initial state and post stopping state.
	 *
	 * The multi-client system compositor has called @ref xrt_comp_end_session
	 * on its @ref xrt_compositor_native.
	 */
	MULTI_SYSTEM_STATE_STOPPED,

	/*!
	 * The main session is running.
	 *
	 * The multi-client system compositor has called @ref xrt_comp_begin_session
	 * on its @ref xrt_compositor_native.
	 */
	MULTI_SYSTEM_STATE_RUNNING,

	/*!
	 * There are no active sessions and the multi-client system compositor is
	 * instructing the native compositor to draw one or more clear frames.
	 *
	 * The multi-client system compositor has not yet called @ref xrt_comp_begin_session
	 * on its @ref xrt_compositor_native.
	 */
	MULTI_SYSTEM_STATE_STOPPING,
};

/*!
 * The multi-client module (aka multi compositor) is  system compositor that
 * multiplexes access to a single @ref xrt_compositor_native, merging layers
 * from one or more client apps/sessions. This object implements the
 * @ref xrt_system_compositor, and gives each session a @ref multi_compositor,
 * which implements @ref xrt_compositor_native.
 *
 * @ingroup comp_multi
 * @implements xrt_system_compositor
 */
struct multi_system_compositor
{
	//! Base interface.
	struct xrt_system_compositor base;

	comp_multi_view_config_callback_func_t get_view_config_callback;

	//! Extra functions to handle multi client.
	struct xrt_multi_compositor_control xmcc;

	/*!
	 * Real native compositor, which this multi client module submits the
	 * combined layers of active @ref multi_compositor objects.
	 */
	struct xrt_compositor_native *xcn;

	/*!
	 * App pacer factory, when a new @ref multi_compositor is created a
	 * pacer is created from this factory.
	 */
	struct u_pacing_app_factory *upaf;

	//! Render loop thread.
	struct os_thread_helper oth;

	struct
	{
		/*!
		 * The state of the multi-client system compositor.
		 * This is updated on the multi_system_compositor::oth
		 * thread, aka multi-client system compositor main thread.
		 * It is driven by the active_count field.
		 */
		enum multi_system_state state;

		//! Number of active sessions, protected by oth.
		uint64_t active_count;
	} sessions;

	/*!
	 * This mutex protects the list of client compositor
	 * and the rendering timings on it.
	 */
	struct os_mutex list_and_timing_lock;

	struct
	{
		int64_t predicted_display_time_ns;
		int64_t predicted_display_period_ns;
		int64_t diff_ns;
	} last_timings;

	//! List of active clients.
	struct multi_compositor *clients[MULTI_MAX_CLIENTS];

	//! Chroma key parameters in HSV space
	struct xrt_layer_chroma_key_data chroma_key;
};

/*!
 * Cast helper
 *
 * @ingroup comp_multi
 * @private @memberof multi_system_compositor
 */
static inline struct multi_system_compositor *
multi_system_compositor(struct xrt_system_compositor *xsc)
{
	return (struct multi_system_compositor *)xsc;
}

/*!
 * The client compositor calls this function to update when its session is
 * started or stopped.
 *
 * @ingroup comp_multi
 * @private @memberof multi_system_compositor
 */
void
multi_system_compositor_update_session_status(struct multi_system_compositor *msc, bool active);


#ifdef __cplusplus
}
#endif
