
/*
 * Copyright 2020 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef GrD3DTypes_DEFINED
#define GrD3DTypes_DEFINED

// This file includes d3d12.h, which in turn includes windows.h, which redefines many
// common identifiers such as:
// * interface
// * small
// * near
// * far
// * CreateSemaphore
// * MemoryBarrier
//
// You should only include this header if you need the Direct3D definitions and are
// prepared to rename those identifiers. Otherwise use GrD3DTypesMinimal.h.

#include "include/core/SkRefCnt.h"
#include "include/gpu/GrTypes.h"
#include <d3d12.h>
#include <dxgi1_4.h>

class GrD3DGpu;

 /** Check if the argument is non-null, and if so, call obj->AddRef() and return obj.
  */
template <typename T> static inline T* GrSafeComAddRef(T* obj) {
    if (obj) {
        obj->AddRef();
    }
    return obj;
}

/** Check if the argument is non-null, and if so, call obj->Release()
 */
template <typename T> static inline void GrSafeComRelease(T* obj) {
    if (obj) {
        obj->Release();
    }
}

template <typename T> class gr_cp {
public:
    using element_type = T;

    constexpr gr_cp() : fObject(nullptr) {}
    constexpr gr_cp(std::nullptr_t) : fObject(nullptr) {}

    /**
     *  Shares the underlying object by calling AddRef(), so that both the argument and the newly
     *  created gr_cp both have a reference to it.
     */
    gr_cp(const gr_cp<T>& that) : fObject(GrSafeComAddRef(that.get())) {}

    /**
     *  Move the underlying object from the argument to the newly created gr_cp. Afterwards only
     *  the new gr_cp will have a reference to the object, and the argument will point to null.
     *  No call to AddRef() or Release() will be made.
     */
    gr_cp(gr_cp<T>&& that) : fObject(that.release()) {}

    /**
     *  Adopt the bare object into the newly created gr_cp.
     *  No call to AddRef() or Release() will be made.
     */
    explicit gr_cp(T* obj) {
        fObject = obj;
    }

    /**
     *  Calls Release() on the underlying object pointer.
     */
    ~gr_cp() {
        GrSafeComRelease(fObject);
        SkDEBUGCODE(fObject = nullptr);
    }

    /**
     *  Shares the underlying object referenced by the argument by calling AddRef() on it. If this
     *  gr_cp previously had a reference to an object (i.e. not null) it will call Release()
     *  on that object.
     */
    gr_cp<T>& operator=(const gr_cp<T>& that) {
        if (this != &that) {
            this->reset(GrSafeComAddRef(that.get()));
        }
        return *this;
    }

    /**
     *  Move the underlying object from the argument to the gr_cp. If the gr_cp
     *  previously held a reference to another object, Release() will be called on that object.
     *  No call to AddRef() will be made.
     */
    gr_cp<T>& operator=(gr_cp<T>&& that) {
        this->reset(that.release());
        return *this;
    }

    explicit operator bool() const { return this->get() != nullptr; }

    T* get() const { return fObject; }
    T* operator->() const { return fObject; }
    T** operator&() { return &fObject; }

    /**
     *  Adopt the new object, and call Release() on any previously held object (if not null).
     *  No call to AddRef() will be made.
     */
    void reset(T* object = nullptr) {
        T* oldObject = fObject;
        fObject = object;
        GrSafeComRelease(oldObject);
    }

    /**
     *  Shares the new object by calling AddRef() on it. If this gr_cp previously had a
     *  reference to an object (i.e. not null) it will call Release() on that object.
     */
    void retain(T* object) {
        if (this->fObject != object) {
            this->reset(GrSafeComAddRef(object));
        }
    }

    /**
     *  Return the original object, and set the internal object to nullptr.
     *  The caller must assume ownership of the object, and manage its reference count directly.
     *  No call to Release() will be made.
     */
    T* SK_WARN_UNUSED_RESULT release() {
        T* obj = fObject;
        fObject = nullptr;
        return obj;
    }

private:
    T* fObject;
};

template <typename T> inline bool operator==(const gr_cp<T>& a,
                                             const gr_cp<T>& b) {
    return a.get() == b.get();
}

template <typename T> inline bool operator!=(const gr_cp<T>& a,
                                             const gr_cp<T>& b) {
    return a.get() != b.get();
}

// interface classes for the GPU memory allocator
class GrD3DAlloc : public SkRefCnt {
public:
    ~GrD3DAlloc() override = default;
};

class GrD3DMemoryAllocator : public SkRefCnt {
public:
    virtual gr_cp<ID3D12Resource> createResource(D3D12_HEAP_TYPE, const D3D12_RESOURCE_DESC*,
                                                 D3D12_RESOURCE_STATES initialResourceState,
                                                 sk_sp<GrD3DAlloc>* allocation,
                                                 const D3D12_CLEAR_VALUE*) = 0;
    virtual gr_cp<ID3D12Resource> createAliasingResource(sk_sp<GrD3DAlloc>& allocation,
                                                         uint64_t localOffset,
                                                         const D3D12_RESOURCE_DESC*,
                                                         D3D12_RESOURCE_STATES initialResourceState,
                                                         const D3D12_CLEAR_VALUE*) = 0;
};

// Note: there is no notion of Borrowed or Adopted resources in the D3D backend,
// so Ganesh will ref fResource once it's asked to wrap it.
// Clients are responsible for releasing their own ref to avoid memory leaks.
struct GrD3DTextureResourceInfo {
    gr_cp<ID3D12Resource>    fResource             = nullptr;
    sk_sp<GrD3DAlloc>        fAlloc                = nullptr;
    D3D12_RESOURCE_STATES    fResourceState        = D3D12_RESOURCE_STATE_COMMON;
    DXGI_FORMAT              fFormat               = DXGI_FORMAT_UNKNOWN;
    uint32_t                 fSampleCount          = 1;
    uint32_t                 fLevelCount           = 0;
    unsigned int             fSampleQualityPattern = DXGI_STANDARD_MULTISAMPLE_QUALITY_PATTERN;
    GrProtected              fProtected            = GrProtected::kNo;

    GrD3DTextureResourceInfo() = default;

    GrD3DTextureResourceInfo(ID3D12Resource* resource,
                             const sk_sp<GrD3DAlloc> alloc,
                             D3D12_RESOURCE_STATES resourceState,
                             DXGI_FORMAT format,
                             uint32_t sampleCount,
                             uint32_t levelCount,
                             unsigned int sampleQualityLevel,
                             GrProtected isProtected = GrProtected::kNo)
            : fResource(resource)
            , fAlloc(alloc)
            , fResourceState(resourceState)
            , fFormat(format)
            , fSampleCount(sampleCount)
            , fLevelCount(levelCount)
            , fSampleQualityPattern(sampleQualityLevel)
            , fProtected(isProtected) {}

    GrD3DTextureResourceInfo(const GrD3DTextureResourceInfo& info,
                             D3D12_RESOURCE_STATES resourceState)
            : fResource(info.fResource)
            , fAlloc(info.fAlloc)
            , fResourceState(resourceState)
            , fFormat(info.fFormat)
            , fSampleCount(info.fSampleCount)
            , fLevelCount(info.fLevelCount)
            , fSampleQualityPattern(info.fSampleQualityPattern)
            , fProtected(info.fProtected) {}

#if GR_TEST_UTILS
    bool operator==(const GrD3DTextureResourceInfo& that) const {
        return fResource == that.fResource && fResourceState == that.fResourceState &&
               fFormat == that.fFormat && fSampleCount == that.fSampleCount &&
               fLevelCount == that.fLevelCount &&
               fSampleQualityPattern == that.fSampleQualityPattern && fProtected == that.fProtected;
    }
#endif
};

struct GrD3DFenceInfo {
    GrD3DFenceInfo()
        : fFence(nullptr)
        , fValue(0) {
    }

    gr_cp<ID3D12Fence> fFence;
    uint64_t           fValue;  // signal value for the fence
};

#endif
