// Copyright 2018 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_DEVICE_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGPU_GPU_DEVICE_H_

#include <bitset>

#include "base/memory/scoped_refptr.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_property.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_blend_factor.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_texture_format.h"
#include "third_party/blink/renderer/core/dom/events/event_target.h"
#include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
#include "third_party/blink/renderer/modules/webgpu/dawn_object.h"
#include "third_party/blink/renderer/platform/graphics/gpu/webgpu_callback.h"
#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"

namespace blink {

class ExecutionContext;
class ExternalTextureCache;
class GPUAdapter;
class GPUAdapterInfo;
class GPUBuffer;
class GPUBufferDescriptor;
class GPUCommandEncoder;
class GPUCommandEncoderDescriptor;
class GPUBindGroup;
class GPUBindGroupDescriptor;
class GPUBindGroupLayout;
class GPUBindGroupLayoutDescriptor;
class GPUComputePipeline;
class GPUComputePipelineDescriptor;
class GPUDeviceDescriptor;
class GPUDeviceLostInfo;
class GPUError;
class GPUExternalTexture;
class GPUExternalTextureDescriptor;
class GPUPipelineLayout;
class GPUPipelineLayoutDescriptor;
class GPUQuerySet;
class GPUQuerySetDescriptor;
class GPUQueue;
class GPURenderBundleEncoder;
class GPURenderBundleEncoderDescriptor;
class GPURenderPipeline;
class GPURenderPipelineDescriptor;
class GPUResourceTable;
class GPUResourceTableDescriptor;
class GPUSampler;
class GPUSamplerDescriptor;
class GPUShaderModule;
class GPUShaderModuleDescriptor;
class GPUSupportedFeatures;
class GPUSupportedLimits;
class GPUTexture;
class GPUTextureDescriptor;
class ScriptState;
class V8GPUErrorFilter;

// Singleton warnings are messages that can only be raised once per device. They
// should be used for warnings of behavior that is not invalid but may have
// performance issues or side effects that the developer may overlook since a
// regular warning is not raised.
enum class GPUSingletonWarning {
  kNonPreferredFormat,
  kDepthKey,
  kCount,  // Must be last
};

class GPUDevice final : public EventTarget,
                        public ExecutionContextClient,
                        public DawnObject<wgpu::Device> {
  DEFINE_WRAPPERTYPEINFO();
  USING_PRE_FINALIZER(GPUDevice, Dispose);

 public:
  // Creates the device without a handle so that some callbacks can be set up
  // in the wgpu::DeviceDescriptor that will be used to create this device.
  // Initialize is where the handle is given, and the rest of the initialization
  // happens.
  GPUDevice(ExecutionContext* execution_context,
            scoped_refptr<DawnControlClientHolder> dawn_control_client,
            GPUAdapter* adapter,
            const String& label);

  // The second step of the initialization once we have the result from
  // wgpu::Adapter::RequestDevice. The lost_info if non-null will be used to
  // resolve the lost property.
  void Initialize(wgpu::Device handle,
                  const GPUDeviceDescriptor* descriptor,
                  GPUDeviceLostInfo* lost_info);

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

  ~GPUDevice() override;

  void Trace(Visitor* visitor) const override;

  // gpu_device.idl {{{
  GPUAdapter* adapter() const;
  GPUSupportedFeatures* features() const;
  GPUSupportedLimits* limits() const { return limits_.Get(); }
  GPUAdapterInfo* adapterInfo() const;
  ScriptPromise<GPUDeviceLostInfo> lost(ScriptState* script_state);
  // }}} End of WebIDL binding implementation.

  GPUQueue* queue();

  void destroy(v8::Isolate* isolate);

  GPUBuffer* createBuffer(const GPUBufferDescriptor* descriptor,
                          ExceptionState& exception_state);
  GPUTexture* createTexture(const GPUTextureDescriptor* descriptor,
                            ExceptionState& exception_state);
  GPUSampler* createSampler(const GPUSamplerDescriptor* descriptor);

  GPUExternalTexture* importExternalTexture(
      const GPUExternalTextureDescriptor* descriptor,
      ExceptionState& exception_state);

  GPUBindGroup* createBindGroup(const GPUBindGroupDescriptor* descriptor,
                                ExceptionState& exception_state);
  GPUBindGroupLayout* createBindGroupLayout(
      const GPUBindGroupLayoutDescriptor* descriptor,
      ExceptionState& exception_state);
  GPUPipelineLayout* createPipelineLayout(
      const GPUPipelineLayoutDescriptor* descriptor);
  GPUResourceTable* createResourceTable(
      const GPUResourceTableDescriptor* descriptor,
      ExceptionState& exception_state);

  GPUShaderModule* createShaderModule(
      const GPUShaderModuleDescriptor* descriptor);
  GPURenderPipeline* createRenderPipeline(
      ScriptState* script_state,
      const GPURenderPipelineDescriptor* descriptor);
  GPUComputePipeline* createComputePipeline(
      const GPUComputePipelineDescriptor* descriptor,
      ExceptionState& exception_state);
  ScriptPromise<GPURenderPipeline> createRenderPipelineAsync(
      ScriptState* script_state,
      const GPURenderPipelineDescriptor* descriptor,
      ExceptionState&);
  ScriptPromise<GPUComputePipeline> createComputePipelineAsync(
      ScriptState* script_state,
      const GPUComputePipelineDescriptor* descriptor);

  GPUCommandEncoder* createCommandEncoder(
      const GPUCommandEncoderDescriptor* descriptor);
  GPURenderBundleEncoder* createRenderBundleEncoder(
      const GPURenderBundleEncoderDescriptor* descriptor,
      ExceptionState& exception_state);

  GPUQuerySet* createQuerySet(const GPUQuerySetDescriptor* descriptor,
                              ExceptionState& exception_state);

  void pushErrorScope(const V8GPUErrorFilter& filter);
  ScriptPromise<IDLNullable<GPUError>> popErrorScope(ScriptState* script_state);

  DEFINE_ATTRIBUTE_EVENT_LISTENER(uncapturederror, kUncapturederror)

  // EventTarget overrides.
  const AtomicString& InterfaceName() const override;
  ExecutionContext* GetExecutionContext() const override;

  bool IsDestroyed() const;
  String GetFormattedLabel() const;
  void InjectError(wgpu::ErrorType type, const char* message);
  void AddConsoleWarning(const String& message);
  void AddConsoleWarning(const char* message);
  void AddConsoleWarning(wgpu::StringView message);
  void AddSingletonWarning(GPUSingletonWarning type);

  void TrackTextureWithMailbox(GPUTexture* texture);
  void UntrackTextureWithMailbox(GPUTexture* texture);

  void TrackBufferWithMailbox(GPUBuffer* buffer);
  void UntrackBufferWithMailbox(GPUBuffer* buffer);

  bool ValidateTextureFormatUsage(V8GPUTextureFormat format,
                                  ExceptionState& exception_state);
  bool ValidateBlendFactor(V8GPUBlendFactor blend_factor,
                           ExceptionState& exception_state);

  // Store the buffer in a weak hash set so we can unmap it when the
  // device is destroyed.
  void TrackMappableBuffer(GPUBuffer* buffer);
  // Untrack the GPUBuffer. This is called eagerly when the buffer is
  // destroyed.
  void UntrackMappableBuffer(GPUBuffer* buffer);

  // Helper used to set the wgpu::DeviceDescriptor callbacks during the first
  // steps of GPUDevice creation. Note that this helper should only ever be
  // called once per GPUDevice.
  void SetDescriptorCallbacks(wgpu::DeviceDescriptor& dawn_desc);

 private:
  using LostProperty = ScriptPromiseProperty<GPUDeviceLostInfo, IDLUndefined>;

  // Used by USING_PRE_FINALIZER.
  void Dispose();
  void DissociateMailboxes();
  void UnmapAllMappableBuffers(v8::Isolate* isolate);

  // Both the uncaptured error callbacks and the logging callbacks run
  // spontaneously (unlike other callbacks that run via ProcessEvents). When
  // running on the main thread, they can run inline as usual, but when running
  // off the main thread, i.e. on the IO thread, the StringView needs to be
  // copied at the callsite, then proxied over to the main thread to actually
  // run the callbacks. The complexity of the function signatures is a result of
  // the restrictions when using blink's callbacks which implicitly wraps
  // sequence checking. Further explanation of the callbacks are included at the
  // implementation sites.
  void OnUncapturedError(wgpu::ErrorType errorType, const String& message);
  void OnLogging(wgpu::LoggingType loggingType, const String& message);

  void OnDeviceLost(
      std::unique_ptr<
          WGPURepeatingCallback<wgpu::UncapturedErrorCallback<void>>>,
      const wgpu::Device& device,
      wgpu::DeviceLostReason reason,
      wgpu::StringView message);

  void OnPopErrorScopeCallback(
      ScriptPromiseResolver<IDLNullable<GPUError>>* resolver,
      wgpu::PopErrorScopeStatus status,
      wgpu::ErrorType type,
      wgpu::StringView message);

  void OnCreateRenderPipelineAsyncCallback(
      const String& label,
      ScriptPromiseResolver<GPURenderPipeline>* resolver,
      wgpu::CreatePipelineAsyncStatus status,
      wgpu::RenderPipeline render_pipeline,
      wgpu::StringView message);
  void OnCreateComputePipelineAsyncCallback(
      const String& label,
      ScriptPromiseResolver<GPUComputePipeline>* resolver,
      wgpu::CreatePipelineAsyncStatus status,
      wgpu::ComputePipeline compute_pipeline,
      wgpu::StringView message);

  void SetLabelImpl(std::string_view value) override {
    GetHandle().SetLabel(value);
  }

  Member<GPUAdapter> adapter_;
  Member<GPUSupportedFeatures> features_;
  Member<GPUSupportedLimits> limits_;
  Member<GPUAdapterInfo> adapter_info_;
  Member<GPUQueue> queue_;
  Member<LostProperty> lost_property_;
  std::unique_ptr<WGPURepeatingCallback<wgpu::LoggingCallback<void>>>
      logging_callback_;

  static constexpr int kMaxAllowedConsoleWarnings = 500;
  int allowed_console_warnings_remaining_ = kMaxAllowedConsoleWarnings;

  // Textures with mailboxes that should be dissociated before device.destroy().
  HeapHashSet<WeakMember<GPUTexture>> textures_with_mailbox_;

  // Buffers with mailboxes that should be dissociated before device.destroy().
  HeapHashSet<WeakMember<GPUBuffer>> buffers_with_mailbox_;

  HeapHashSet<WeakMember<GPUBuffer>> mappable_buffers_;

  Member<ExternalTextureCache> external_texture_cache_;

  std::bitset<static_cast<size_t>(GPUSingletonWarning::kCount)>
      singleton_warning_fired_;

  // This attribute records that whether GPUDevice is destroyed (via destroy()).
  bool destroyed_ = false;
};

}  // namespace blink

#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGPU_GPU_DEVICE_H_
