// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGPU_GPU_CANVAS_CONTEXT_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGPU_GPU_CANVAS_CONTEXT_H_

#include "base/containers/heap_array.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_canvas_tone_mapping_mode.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_canvas_alpha_mode.h"
#include "third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h"
#include "third_party/blink/renderer/core/html/canvas/canvas_rendering_context_factory.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
#include "third_party/blink/renderer/platform/bindings/union_base.h"
#include "third_party/blink/renderer/platform/graphics/gpu/webgpu_cpp.h"
#include "third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.h"
#include "third_party/blink/renderer/platform/graphics/predefined_color_space.h"
#include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h"

namespace blink {

class CanvasNon2DResourceProvider;
class ExceptionState;
class ExecutionContext;
class GPUDevice;
class GPUCanvasConfiguration;
class GPUSwapChain;
class GPUTexture;
class WebGPUTextureAlphaClearer;
class V8UnionHTMLCanvasElementOrOffscreenCanvas;

// A GPUCanvasContext does little by itself and basically just binds a canvas
// and a GPUSwapChain together and forwards calls from one to the other.
class GPUCanvasContext : public ScriptWrappable,
                         public CanvasRenderingContext,
                         public WebGPUSwapBufferProvider::Client {
  DEFINE_WRAPPERTYPEINFO();

 public:
  class Factory : public CanvasRenderingContextFactory {
   public:
    Factory() = default;

    Factory(const Factory&) = delete;
    Factory& operator=(const Factory&) = delete;

    ~Factory() override;

    CanvasRenderingContext* Create(
        ExecutionContext*,
        CanvasRenderingContextHost*,
        const CanvasContextCreationAttributesCore&) override;
    CanvasRenderingContext::CanvasRenderingAPI GetRenderingAPI() const override;
  };

  GPUCanvasContext(CanvasRenderingContextHost*,
                   const CanvasContextCreationAttributesCore&);

  GPUCanvasContext(const GPUCanvasContext&) = delete;
  GPUCanvasContext& operator=(const GPUCanvasContext&) = delete;

  ~GPUCanvasContext() override;

  void Trace(Visitor*) const override;

  // CanvasRenderingContext implementation
  V8RenderingContext* AsV8RenderingContext() final;
  V8OffscreenRenderingContext* AsV8OffscreenRenderingContext() final;
  bool IsOpaque() const override;
  base::ByteSize AllocatedBufferSize() const override;
  // Produces a snapshot of the current contents of the swap chain if possible
  // or else a snapshot of the most-recently presented contents.
  scoped_refptr<StaticBitmapImage> GetImage() final;
  scoped_refptr<StaticBitmapImage> PaintRenderingResultsToSnapshot(
      SourceDrawingBuffer source_buffer) override;
  bool CopyRenderingResultsToVideoFrame(
      WebGraphicsContext3DVideoFramePool* frame_pool,
      SourceDrawingBuffer src_buffer,
      const gfx::ColorSpace& dst_color_space,
      VideoFrameCopyCompletedCallback callback) override;
  void PageVisibilityChanged() override {}
  void SizeChanged() override;
  bool isContextLost() const override { return false; }
  bool IsComposited() const final { return true; }
  bool IsPaintable() const final { return true; }
  void Stop() final;
  cc::Layer* CcLayer() const final;
  void Reshape(int width, int height) override;
  void Dispose() override;

  // OffscreenCanvas-specific methods
  scoped_refptr<CanvasResource> GetResourceForPushFrame(
      bool& should_call_push_frame) final;
  // Returns a StaticBitmapImage backed by a texture containing the current
  // contents of the front buffer. This is done without any pixel copies. The
  // texture in the ImageBitmap is from the active ContextProvider on the
  // WebGPUSwapBufferProvider.
  ImageBitmap* TransferToImageBitmap(ScriptState*, ExceptionState&) final;

  bool IsOffscreenCanvas() const {
    if (Host())
      return Host()->IsOffscreenCanvas();
    return false;
  }

  // gpu_canvas_context.idl {{{
  bindings::OptimizedReturnProxy<V8UnionHTMLCanvasElementOrOffscreenCanvas>
  getHTMLOrOffscreenCanvas(ScriptState*) const;
  void configure(const GPUCanvasConfiguration* descriptor, ExceptionState&);
  void unconfigure();
  GPUCanvasConfiguration* getConfiguration();
  GPUTexture* getCurrentTexture(ScriptState*, ExceptionState&);
  // }}} End of WebIDL binding implementation.

  // WebGPUSwapBufferProvider::Client implementation
  void OnTextureTransferred() override;
  void InitializeLayer(cc::Layer* layer) override;
  void SetNeedsCompositingUpdate() override;
  bool IsGPUDeviceDestroyed() override;

 private:
  CanvasNon2DResourceProvider* GetOrCreateCanvasNon2DResourceProvider();
  scoped_refptr<WebGPUMailboxTexture> GetFrontBufferMailboxTexture();
  void DetachSwapBuffers();
  void ReplaceDrawingBuffer(bool destroy_swap_buffers);
  void InitializeAlphaModePipeline(wgpu::TextureFormat format);

  void FinalizeFrame(FlushReason) override;

  scoped_refptr<StaticBitmapImage> SnapshotInternal(
      const wgpu::Texture& texture) const;

  bool CopyTextureToResourceProvider(
      const wgpu::Texture& texture,
      CanvasNon2DResourceProvider* resource_provider) const;

  void CopyToSwapTexture();

  viz::SharedImageFormat GetSharedImageFormat() const;
  gfx::ColorSpace GetColorSpace() const;
  SkAlphaType GetAlphaType() const;

  base::WeakPtr<WebGraphicsContext3DProviderWrapper> GetContextProviderWeakPtr()
      const;

  scoped_refptr<StaticBitmapImage> MakeFallbackStaticBitmapImage(
      V8GPUCanvasAlphaMode::Enum alpha_mode);

  Member<GPUDevice> device_;

  std::unique_ptr<CanvasNon2DResourceProvider> resource_provider_;

  // `did_fail_to_create_resource_provider_` prevents repeated attempts in
  // allocating resources after the first attempt failed.
  bool did_fail_to_create_resource_provider_ = false;

  // If the system doesn't support the requested format but it's one that WebGPU
  // is required to offer, a texture_ will be allocated separately with the
  // desired format and the will be copied to swap_texture_, allocated by the
  // swap buffer provider with the system-supported format, when we're ready to
  // present. Otherwise texture_ and swap_texture_ will point to the same
  // texture, allocated by the swap buffer provider.
  Member<GPUTexture> texture_;
  Member<GPUTexture> swap_texture_;

  PredefinedColorSpace color_space_ = PredefinedColorSpace::kSRGB;
  V8GPUCanvasAlphaMode::Enum alpha_mode_;
  V8CanvasToneMappingMode::Enum tone_mapping_mode_;
  scoped_refptr<WebGPUTextureAlphaClearer> alpha_clearer_;
  scoped_refptr<WebGPUSwapBufferProvider> swap_buffers_;

  bool new_texture_required_ = true;
  bool copy_to_swap_texture_required_ = false;
  bool suppress_preferred_format_warning_ = false;
  bool stopped_ = false;

  // Matches [[configuration]] != null in the WebGPU specification.
  bool configured_ = false;
  // Matches [[texture_descriptor]] in the WebGPU specification except that it
  // never becomes null.
  wgpu::TextureDescriptor texture_descriptor_;

  // The texture descriptor for the swap_texture is tracked separately, since
  // it may have different usage in the case that a copy is required.
  wgpu::TextureDescriptor swap_texture_descriptor_;
  wgpu::DawnTextureInternalUsageDescriptor texture_internal_usage_;
  base::HeapArray<wgpu::TextureFormat> view_formats_;
};

}  // namespace blink

#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGPU_GPU_CANVAS_CONTEXT_H_
