/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
 *
 * SPDX-License-Identifier: Apache-2.0 */

#pragma once

#include "session/display_driver.h"

#include "util/half.h"
#include "util/thread.h"
#include "util/unique_ptr.h"

CCL_NAMESPACE_BEGIN

class BufferParams;

/* PathTraceDisplay is used for efficient render buffer display.
 *
 * The host applications implements a DisplayDriver, storing a render pass in a GPU-side
 * textures. This texture is continuously updated by the path tracer and drawn by the host
 * application.
 *
 * PathTraceDisplay is a wrapper around the DisplayDriver, adding thread safety, state tracking
 * and error checking. */

class PathTraceDisplay {
 public:
  explicit PathTraceDisplay(unique_ptr<DisplayDriver> driver);
  virtual ~PathTraceDisplay() = default;

  /* Reset the display for the new state of render session. Is called whenever session is reset,
   * which happens on changes like viewport navigation or viewport dimension change.
   *
   * This call will configure parameters for a changed buffer and reset the texture state.
   *
   * When the `reset_rendering` a complete display reset happens. When it is false reset happens
   * for a new state of the buffer parameters which is assumed to correspond to the next tile. */
  void reset(const BufferParams &buffer_params, bool reset_rendering);

  /* --------------------------------------------------------------------
   * Update procedure.
   *
   * These calls indicates a desire of the caller to update content of the displayed texture. */

  /* Returns true when update is ready. Update should be finished with update_end().
   *
   * If false is returned then no update is possible, and no update_end() call is needed.
   *
   * The texture width and height denotes an actual resolution of the underlying render result. */
  bool update_begin(const int texture_width, const int texture_height);

  void update_end();

  /* Get currently configured texture size of the display (as configured by `update_begin()`. */
  int2 get_texture_size() const;

  /* --------------------------------------------------------------------
   * Texture update from CPU buffer.
   *
   * NOTE: The PathTraceDisplay should be marked for an update being in process with
   * `update_begin()`.
   *
   * Most portable implementation, which must be supported by all platforms. Might not be the most
   * efficient one.
   */

  /* Copy buffer of rendered pixels of a given size into a given position of the texture.
   *
   * This function does not acquire a lock. The reason for this is to allow use of this function
   * for partial updates from different devices. In this case the caller will acquire the lock
   * once, update all the slices and release
   * the lock once. This will ensure that draw() will never use partially updated texture. */
  void copy_pixels_to_texture(const half4 *rgba_pixels,
                              const int texture_x,
                              const int texture_y,
                              const int pixels_width,
                              const int pixels_height);

  /* --------------------------------------------------------------------
   * Texture buffer mapping.
   *
   * This functionality is used to update GPU-side texture content without need to maintain CPU
   * side buffer on the caller.
   *
   * NOTE: The PathTraceDisplay should be marked for an update being in process with
   * `update_begin()`.
   *
   * NOTE: Texture buffer can not be mapped while graphics interoperability is active. This means
   * that `map_texture_buffer()` is not allowed between `graphics_interop_begin()` and
   * `graphics_interop_end()` calls.
   */

  /* Map pixels memory form texture to a buffer available for write from CPU. Width and height will
   * define a requested size of the texture to write to.
   * Upon success a non-null pointer is returned and the texture buffer is to be unmapped.
   * If an error happens during mapping, or if mapping is not supported by this GPU display a
   * null pointer is returned and the buffer is NOT to be unmapped.
   *
   * NOTE: Usually the implementation will rely on a GPU context of some sort, and the GPU context
   * is often can not be bound to two threads simultaneously, and can not be released from a
   * different thread. This means that the mapping API should be used from the single thread only,
   */
  half4 *map_texture_buffer();
  void unmap_texture_buffer();

  /* --------------------------------------------------------------------
   * Graphics interoperability.
   *
   * A special code path which allows to update texture content directly from the GPU compute
   * device. Complementary part of DeviceGraphicsInterop.
   *
   * NOTE: Graphics interoperability can not be used while the texture buffer is mapped. This means
   * that `graphics_interop_get_buffer()` is not allowed between `map_texture_buffer()` and
   * `unmap_texture_buffer()` calls. */

  /* Get PathTraceDisplay graphics interoperability information which acts as a destination for the
   * device API. */
  GraphicsInteropDevice graphics_interop_get_device();
  GraphicsInteropBuffer &graphics_interop_get_buffer();

  /* (De)activate GPU display for graphics interoperability outside of regular display update
   * routines. */
  void graphics_interop_activate();
  void graphics_interop_deactivate();

  /* --------------------------------------------------------------------
   * Drawing.
   */

  /* Clear the texture by filling it with all zeroes.
   *
   * This call might happen in parallel with draw, but can never happen in parallel with the
   * update.
   *
   * The actual zeroing can be deferred to a later moment. What is important is that after clear
   * and before pixels update the drawing texture will be fully empty, and that partial update
   * after clear will write new pixel values for an updating area, leaving everything else zeroed.
   *
   * If the GPU display supports graphics interoperability then the zeroing the display is to be
   * delegated to the device via the `GraphicsInterop`. */
  void zero();

  /* Draw the current state of the texture.
   *
   * Returns true if this call did draw an updated state of the texture. */
  bool draw();

  /* Flush outstanding display commands before ending the render loop. */
  void flush();

 private:
  /* Display driver implemented by the host application. */
  unique_ptr<DisplayDriver> driver_;

  /* Current display parameters */
  thread_mutex mutex_;
  DisplayDriver::Params params_;

  /* Mark texture as its content has been updated.
   * Used from places which knows that the texture content has been brought up-to-date, so that the
   * drawing knows whether it can be performed, and whether drawing happened with an up-to-date
   * texture state. */
  void mark_texture_updated();

  /* State of the update process. */
  struct {
    /* True when update is in process, indicated by `update_begin()` / `update_end()`. */
    bool is_active = false;
  } update_state_;

  /* State of the texture, which is needed for an integration with render session and interactive
   * updates and navigation. */
  struct {
    /* Texture is considered outdated after `reset()` until the next call of
     * `copy_pixels_to_texture()`. */
    bool is_outdated = true;

    /* Texture size in pixels. */
    int2 size = make_int2(0, 0);
  } texture_state_;

  /* State of the texture buffer. Is tracked to perform sanity checks. */
  struct {
    /* True when the texture buffer is mapped with `map_texture_buffer()`. */
    bool is_mapped = false;
  } texture_buffer_state_;
};

CCL_NAMESPACE_END
