//
// Copyright 2018 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://openusd.org/license.
//
#ifndef PXR_IMAGING_HD_RENDER_THREAD_H
#define PXR_IMAGING_HD_RENDER_THREAD_H

#include "pxr/pxr.h"
#include "pxr/imaging/hd/api.h"

#include <atomic>
#include <condition_variable>
#include <functional>
#include <mutex>
#include <thread>

PXR_NAMESPACE_OPEN_SCOPE


/// \class HdRenderThread
///
/// HdRenderThread is a utility that specific render delegates can choose to
/// use depending on their needs.  It provides a system for rendering in a
/// background thread, and synchronizing between hydra (either in the main
/// thread, or the sync threadpool) and the rendering thread.
///
/// \section HdRenderThread_StateMachine State Machine
///
/// The render thread is implemented in terms of a state machine, and hydra
/// requests to the render thread are implemented in terms of transitions on
/// that state machine.
///
/// States:
/// - \em StateInitial - indicates the render thread hasn't been started.
/// - \em StateIdle - indicates the render thread is running, but not rendering.
/// - \em StateRendering - indicates the render thread is rendering.
/// - \em StateTerminated - indicates the render thread is shutting down.
///
/// Transitions:
/// - StartThread():  StateInitial => StateIdle
/// - StartRender():  StateIdle, StateRendering => StateRendering
/// - StopRender():   StateIdle, StateRendering => StateIdle
/// - StopThread():   StateIdle, StateRendering => StateTerminated
/// - StopThread():   StateTerminated => StateInitial
///
/// \section HdRenderThread_Usage Example Usage
///
/// \code{.cpp}
/// class ExampleRenderDelegate : HdRenderDelegate {
///   public:
///     ExampleRenderDelegate() {
///       _renderThread.SetRenderCallback(
///         std::bind(&ExampleRenderDelegate::_RenderCallback, this));
///       _renderThread.StartThread();
///     }
///     ~ExampleRenderDelegate() {
///       _renderThread.StopThread();
///     }
///   private:
///     void _RenderCallback() {
///       bool renderComplete = false;
///       while(!renderComplete) {
///         // Check if we have been asked to pause.
///         while(_renderThread.IsPauseRequested()) {
///             if(_renderThread.IsStopRequested()) {
///                 break;
///             }
///             std::this_thread::sleep_for(std::chrono::milliseconds(10));
///         }
///         if(_renderThread.IsStopRequested()) {
///             break;
///         }
///         // generate N pixels.
///         auto lock = _renderThread.LockFramebuffer();
///         // resolve pixels to shared buffer.
///         // Set renderComplete = true when finished rendering.
///       }
///     }
///     HdRenderThread _renderThread;
/// };
///
/// class ExampleRenderParam : HdRenderParam {
///   public:
///     ExampleRenderParam(HdRenderThread* renderThread, SceneData *scene);
///     SceneData* AcquireSceneForEdit() {
///       _renderThread->StopRender();
///       return _scene;
///     }
/// };
///
/// class ExamplePrim : HdMesh {
///   public:
///     void Sync(...) {
///       SceneData *scene = renderParam->AcquireSceneForEdit();
///       ...
///     }
/// };
///
/// class ExampleRenderPass : HdRenderPass {
///   public:
///     ExampleRenderPass(HdRenderThread *renderThread);
///   protected:
///     void _Execute(...) {
///       _renderThread->StartRendering();
///       auto lock = _renderThread->LockFramebuffer();
///       // blit pixels from shared to application buffer.
///     }
/// };
/// \endcode
///
/// Having a locked and shared framebuffer is important if you want to avoid
/// tearing, or if the rendering API disallows multithreaded access to buffers
/// (for example, if your framebuffers are on a GPU). It might be unnecessary
/// for some renderers.
///
/// Stopping the render only when you're about to make a scene edit means that
/// long-running renders aren't interrupted if the scene is static. Hiding the
/// renderer's scene data handle behind AcquireSceneForEdit helps callers
/// use the synchronization mechanisms correctly.
///
/// The render is restarted at the last possible second, in the render pass,
/// after we know scene edits are done.
///
/// The render callback should use IsStopRequested() as a cancellation
/// mechanism.
///
class HdRenderThread {
public:

    HD_API
    HdRenderThread();

    HD_API
    ~HdRenderThread();

    /// \anchor Management
    /// \name API for thread management
    ///
    /// Methods to configure, start, and stop the render thread. These functions
    /// are not threadsafe.
    ///
    /// @{

    /// Set the rendering callback for the render thread to use.
    HD_API
    void SetRenderCallback(std::function<void()> renderCallback);

    /// Set the shutdown callback for the render thread to use. This will be
    /// called once, right before the render thread exits, regardless of whether
    /// the render callback has been called. This can be used to clean up
    /// thread-specific rendering resources.
    HD_API
    void SetShutdownCallback(std::function<void()> shutdownCallback);

    /// Start the rendering background thread.
    /// Note: it's an error to call this function when the render thread is
    /// already running, but it's acceptable to stop the render thread and then
    /// start it again.
    HD_API
    void StartThread();

    /// Stop the rendering background thread. This function will ask the render
    /// thread to transition to StateTerminated, and then join on the thread,
    /// so it will block.  After this function returns, the rendering state
    /// machine will be back in its initial state, and the render thread can be
    /// started again.
    HD_API
    void StopThread();

    /// Check whether the background thread is running (i.e. StartThread was
    /// called successfully, but StopThread has not been).
    HD_API
    bool IsThreadRunning();

    /// @}

    /// \anchor HydraAPI
    /// \name API for hydra threads
    ///
    /// Methods for hydra to communicate with the render thread. These methods
    /// can be called from the application thread or hydra threadpool threads.
    ///
    /// @{

    /// Ask the render thread to start rendering.  This call is a no-op if the
    /// render thread is already rendering. Otherwise, it may block briefly.
    /// This is threadsafe against the render thread, but it shouldn't be
    /// called at the same time as StopRender(), and it shouldn't be called
    /// from multiple hydra threads at once.
    HD_API
    void StartRender();

    /// Ask the render thread to stop rendering, and block until the render
    /// thread is idle. This is fully threadsafe, and can be called from
    /// multiple hydra threads at once.
    HD_API
    void StopRender();

    /// Query whether the render thread is currently rendering. This is set by
    /// StartRender() and reset after the render callback exits, or reset by
    /// StopRender() if the render callback never runs. This does not
    /// block, and is fully threadsafe.
    HD_API
    bool IsRendering();

    /// Ask the render thread to pause rendering. The speed at which the
    /// renderer actually enters the pause state depends on the delegate.
    HD_API
    void PauseRender();

    /// Ask the render thread to resume rendering. Pause and Resume calls do
    /// not need to be paired. The last call (to Pause or Resume) decides the
    /// current state.
    HD_API
    void ResumeRender();

    /// @}

    /// \anchor RenderThreadAPI
    /// \name API for hydra threads
    ///
    /// Methods for the render thread to communicate with hydra. These should
    /// only be called from the render thread, from inside the render callback.
    ///
    /// @{

    /// Query whether hydra has asked to interrupt the current frame since
    /// the last time StartRender() was called. The render callback can check
    /// this to determine whether to cancel rendering.
    HD_API
    bool IsStopRequested();

    /// Query whether hydra has asked to pause rendering. This will continue
    /// to return true until a request has been made for rendering to resume.
    /// Remember to check for a stop request while paused.
    HD_API
    bool IsPauseRequested();

    /// Query whether the pause/resume state has changed since the last time
    /// we called IsPauseDirty.
    HD_API
    bool IsPauseDirty();

    /// @}

    /// \anchor CommonAPI
    /// \name API for both hydra and render threads
    ///
    /// Methods for both hydra and the render threads to synchronize access to
    /// other data.
    ///
    /// @{

    /// Return a scoped lock on the render delegate's framebuffer. Hydra and the
    /// render thread can use this to synchronize blits between render-thread
    /// owned resources, and application-owned resources.
    HD_API
    std::unique_lock<std::mutex> LockFramebuffer();

    /// @}

private:
    // _RenderLoop implements the render thread's state machine; see
    // \ref HdRenderThread_StateMachine for details.  It runs in a background
    // thread and manages synchronization with hydra.  To implement rendering,
    // it calls out to the render callback provided via SetRenderCallback.
    void _RenderLoop();

    // _renderCallback is the render-delegate-provided function responsible for
    // actually rendering.  It's called from _RenderLoop.
    std::function<void()> _renderCallback;

    // _shutdownCallback is the render-delegate-provided function responsible
    // for cleaning up thread-specific resources.  It's called once, right
    // before _RenderLoop exits.
    std::function<void()> _shutdownCallback;

    // A placeholder initial value for _renderCallback.
    static void _DefaultRenderCallback();

    // A placeholder initial value for _shutdownCallback.
    static void _DefaultShutdownCallback();

    // The state enumeration of the render thread state machine; see
    // \ref HdRenderThread_StateMachine for details.
    enum State {
        // Initial constructed state. Render thread is not running.
        StateInitial,
        // Render thread is running and ready for scene edits. No rendering
        // is taking place.
        StateIdle,
        // Render thread is running and rendering; no scene edits are allowed.
        StateRendering,
        // Render thread is shutting down.
        StateTerminated,
    };

    // _requestedState is set by hydra to direct the render thread's state
    // machine; see \ref HdRenderThread_StateMachine for details.
    // _requestedState is protected by a mutex/condition variable combination.
    // The render thread holds _requestedStateMutex while rendering; the
    // frequency with which it can give it up is the interruption frequency.
    //
    // StartRender() and StopRender() lock and write to _requestedState.
    // _RenderLoop() locks and reads _requestedState.
    State _requestedState;
    std::mutex _requestedStateMutex;
    std::condition_variable _requestedStateCV;

    // _enableRender provides an out-of-band way for hydra to cancel a
    // render while the render thread is still holding _requestedStateMutex.
    //
    // StartRender() and StopRender() will write true/false to _enableRender.
    // IsStopRequested() will read from _enableRender.
    std::atomic_flag _enableRender;
    // _stopRequested keeps track of whether _enableRender has gone low since
    // the last time the render callback was called (since _enableRender is
    // reset on read).
    bool _stopRequested;

    // _pauseRender provides a properly locked boolean flag that holds the
    // current pause state of the thread. Toggled by calling PauseRender and
    // ResumeRender, tested by IsPauseRequested.
    std::atomic<bool> _pauseRender;

    // _pauseDirty provides a properly locked boolean flag that holds the
    // current dirtyness of the pause state of the thread. Toggled by
    // calling PauseRender and ResumeRender, tested by IsPauseDirty.
    std::atomic<bool> _pauseDirty;

    // _rendering records whether the render thread is currently inside the
    // render callback, or planning to be inside the render callback.
    // It is managed by StartRender(), StopRender(), and _RenderLoop().
    // IsRendering() will read from _rendering.
    std::atomic<bool> _rendering;

    // _renderThread is the background render thread; it runs _RenderLoop().
    std::thread _renderThread;

    // _frameBufferMutex protects access to the render delegate's framebuffer,
    // and provides an optional synchronization point for blits between the
    // render thread's resources and the application's resources.
    std::mutex _frameBufferMutex;
};


PXR_NAMESPACE_CLOSE_SCOPE

#endif // PXR_IMAGING_HD_RENDER_THREAD_H
