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

#ifndef skgpu_graphite_RasterPathAtlas_DEFINED
#define skgpu_graphite_RasterPathAtlas_DEFINED

#include "src/base/SkTInternalLList.h"
#include "src/core/SkTHash.h"
#include "src/gpu/AtlasTypes.h"
#include "src/gpu/ResourceKey.h"
#include "src/gpu/graphite/PathAtlas.h"

namespace skgpu::graphite {

/**
 * PathAtlas class that rasterizes coverage masks on the CPU.
 *
 * When a new shape gets added, its path is rasterized in preparation for upload. These
 * uploads are recorded by `recordUploads()` and subsequently added to an UploadTask.
 *
 * After a successful call to `recordUploads()`, the client is free to call `reset()` and start
 * adding new shapes for a future atlas render.
 * TODO: We should cache Shapes for future frames to avoid the cost of raster pipeline rendering.
 */
class RasterPathAtlas : public PathAtlas {
public:
    RasterPathAtlas();
    ~RasterPathAtlas() override {}
    void recordUploads(DrawContext*, Recorder*);

protected:
    const TextureProxy* onAddShape(Recorder* recorder,
                                   const Shape&,
                                   const Transform& transform,
                                   const SkStrokeRec&,
                                   skvx::half2 maskSize,
                                   skvx::half2* outPos) override;
    const TextureProxy* addRect(Recorder* recorder,
                                skvx::half2 maskSize,
                                SkIPoint16* outPos);

private:
    // TODO: select atlas size dynamically? Take ContextOptions::fMaxTextureAtlasSize into account?
    static constexpr int kDefaultAtlasDim = 4096;

    struct Page {
        bool initializeTextureIfNeeded(Recorder* recorder, uint16_t identifier);

        // A Page lazily requests a texture from the AtlasProvider when the first shape gets added
        // to it and references the same texture for the duration of its lifetime. A reference to
        // this texture is stored here, which is used by CoverageMaskRenderStep when encoding the
        // render pass.
        sk_sp<TextureProxy> fTexture;
        // Tracks placement of paths in a Page
        skgpu::RectanizerSkyline fRectanizer = {kDefaultAtlasDim, kDefaultAtlasDim};
        // Rendered data that gets uploaded
        SkAutoPixmapStorage fPixels;
        // Area that's needed to be uploaded
        SkIRect fDirtyRect;
        // Tracks whether a path is already in this Page, and its location in the atlas
        struct UniqueKeyHash {
            uint32_t operator()(const skgpu::UniqueKey& key) const { return key.hash(); }
        };
        skia_private::THashMap<skgpu::UniqueKey, skvx::half2, UniqueKeyHash> fCachedShapes;
        // Tracks current state relative to last flush
        AtlasToken fLastUse = AtlasToken::InvalidToken();

        uint16_t fIdentifier;

        SK_DECLARE_INTERNAL_LLIST_INTERFACE(Page);
    };

    void makeMRU(Page*, Recorder*);
    // Free up atlas allocations, if necessary. After this call the atlas can be considered
    // available for new shape insertions. However this method does not have any bearing on the
    // contents of any atlas textures themselves, which may be in use by GPU commands that are
    // in-flight or yet to be submitted.
    void reset(Page*);

    // Investigation shows that eight pages helps with some of the more complex skps, and
    // since we're using less complex vertex setups with the RPA, we have more GPU memory
    // to take advantage of.
    static constexpr int kMaxPages = 8;
    typedef SkTInternalLList<Page> PageList;
    // LRU list of Pages (MRU at head - LRU at tail)
    PageList fPageList;
    // Allocated array of pages (backing data for list)
    Page fPageArray[kMaxPages];
};

}  // namespace skgpu::graphite

#endif  // skgpu_graphite_RasterPathAtlas_DEFINED
