| /* |
| * Copyright 2022 Google LLC |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #ifndef sktext_gpu_GlyphVector_DEFINED |
| #define sktext_gpu_GlyphVector_DEFINED |
| |
| #include "include/core/SkSpan.h" |
| #include "src/core/SkGlyph.h" |
| #include "src/core/SkStrike.h" // IWYU pragma: keep |
| #include "src/gpu/MaskFormat.h" |
| #include "src/text/gpu/StrikeCache.h" |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <functional> |
| #include <optional> |
| |
| class SkReadBuffer; |
| class SkStrikeClient; |
| class SkWriteBuffer; |
| |
| namespace sktext::gpu { |
| class SubRunAllocator; |
| class StrikeCache; |
| |
| /** |
| * Concepts used to describe backend-specific (Ganesh v. Graphite) data stored in GlyphVector. |
| * The backend stores a type conforming to BackendData in each GlyphVector. This pertains to |
| * the whole vector. The instance of the BackendData type is then used to convert from |
| * SkPackedGlyphID to a backend-specific type conforming to GlyphType. The instances of |
| * the type conforming to GlyphType actually overwrite the packed IDs. However, GlyphType |
| * has a requirement to that it can convert back to packed id. |
| * |
| * Specific requirements for each type (other than size and alignment): |
| * BackendData: |
| * 1) Has a static method FindStrike that returns a sk_sp<T> where T is a subclass of |
| * TextStrikeBase. |
| * 2) Has a member function makeGlyphFromID that takes SkPackedGlyphID and yields |
| * an instance of a type conforming to GlyphType. |
| * GlyphType: |
| * 1) Is trivially destructible. We could relax this by adding calls to the destructor |
| * if needed it proved useful. |
| * 2) Has a method packedID() const that yields (something convertible to) |
| * SkPackedGlyphID. |
| * |
| * We use concepts rather than a base class for BackendData because it allows us to |
| * require the static method on BackendData and it allows us to require that |
| * makeFromGlyphID() yields any type that convert back to packedID() without requiring |
| * a virtual function/vtable for GlyphType. |
| */ |
| namespace GlyphVector_Concepts { |
| |
| // Both backends currently store pointer-sized types for glyphs and 88 is currently sufficient for |
| // Ganesh and Graphite's BackendData. |
| static constexpr size_t kMaxGlyphTypeSize = sizeof(void*); |
| static constexpr size_t kMaxBackendDataSize = 88; |
| |
| template <typename T> |
| concept GlyphType = requires(const T& t) { |
| requires sizeof(T) <= kMaxGlyphTypeSize; |
| // We store these in an array with kMaxGlyphTypeSize per entry. So that alignment must be |
| // sufficient. |
| requires alignof(T) <= kMaxGlyphTypeSize; |
| |
| std::is_trivially_destructible_v<T>; |
| |
| { t.packedID() } -> std::convertible_to<SkPackedGlyphID>; |
| }; |
| |
| template <typename T, template <typename...> class Template> |
| constexpr bool is_specialization_of_v = false; |
| |
| template <template <typename...> class Template, typename... Args> |
| constexpr bool is_specialization_of_v<Template<Args...>, Template> = true; |
| |
| template <typename T> |
| concept BackendData = requires(T& t, |
| StrikeCache* cache, |
| const SkStrikeSpec& spec, |
| SkPackedGlyphID id, |
| skgpu::MaskFormat maskFormat) { |
| requires alignof(T) <= alignof(max_align_t); |
| requires sizeof(T) <= kMaxBackendDataSize; |
| |
| T::FindStrike(cache, spec); |
| requires is_specialization_of_v<decltype(T::FindStrike(cache, spec)), sk_sp>; |
| requires std::derived_from<typename decltype(T::FindStrike(cache, spec))::element_type, TextStrikeBase>; |
| |
| // Must have a makeGlyphFromID that returns a GlyphType |
| { t.makeGlyphFromID(id, maskFormat) } -> GlyphType; |
| }; |
| } // namespace GlyphVector_Concepts |
| |
| // -- GlyphVector ---------------------------------------------------------------------------------- |
| // GlyphVector provides a way to delay the lookup of Glyphs until the code is running on the GPU |
| // in single threaded mode. The GlyphVector is created in a multi-threaded environment, but the |
| // StrikeCache is only single threaded (and must be single threaded because of the atlas). |
| // |
| // Once in the single-threaded GPU environment the glyph packed IDs are converted into a GPU backend |
| // specific entry type from which each glyph's atlas location can be determined. |
| class GlyphVector { |
| using GlyphBytes = std::array<std::byte, GlyphVector_Concepts::kMaxGlyphTypeSize>; |
| using BackendDataBytes = std::array<std::byte, GlyphVector_Concepts::kMaxBackendDataSize>; |
| |
| // Get the specific TextStrikeBase-derived strike class and GlyphType-conforming types yieled |
| // by a BackendData-conforming type. |
| template <GlyphVector_Concepts::BackendData B> |
| using Strike = std::invoke_result_t<decltype(B::FindStrike), StrikeCache*, const SkStrikeSpec&>; |
| |
| template <GlyphVector_Concepts::BackendData B> |
| using Glyph = decltype(std::declval<B>().makeGlyphFromID(SkPackedGlyphID{}, |
| skgpu::MaskFormat::kARGB)); |
| |
| public: |
| static size_t Size(int numGlyphs) { |
| SkASSERT(numGlyphs >= 0); |
| return SkAlignTo(sizeof(GlyphVector), alignof(max_align_t)) + sizeof(GlyphBytes) * numGlyphs; |
| } |
| |
| GlyphVector(SkStrikePromise&& strikePromise, SkSpan<GlyphBytes> glyphs); |
| |
| // To move in to AtlasSubRun constructor |
| GlyphVector(GlyphVector&&); |
| |
| GlyphVector(const GlyphVector&&) = delete; |
| GlyphVector& operator=(const GlyphVector&) = delete; |
| GlyphVector& operator=(GlyphVector&&) = delete; |
| |
| ~GlyphVector(); |
| |
| static GlyphVector Make(SkStrikePromise&& promise, |
| SkSpan<const SkPackedGlyphID> glyphs, |
| SubRunAllocator* alloc); |
| |
| int glyphCount() const { return SkToInt(fGlyphs.size()); } |
| |
| static std::optional<GlyphVector> MakeFromBuffer(SkReadBuffer& buffer, |
| const SkStrikeClient* strikeClient, |
| SubRunAllocator* alloc); |
| void flatten(SkWriteBuffer& buffer) const; |
| |
| // This doesn't need to include sizeof(GlyphVector) because this is embedded in each of |
| // the sub runs. |
| int unflattenSize() const { return Size(fGlyphs.size()); } |
| |
| bool hasBackendData() const { |
| SkASSERT(SkToBool(fBackendDataReleaser) == SkToBool(fGetGlyphID)); |
| return SkToBool(fBackendDataReleaser); |
| } |
| |
| template <GlyphVector_Concepts::BackendData B> const B& accessBackendData() const { |
| SkASSERT(this->hasBackendData()); |
| return *reinterpret_cast<const B*>(fBackendDataBytes.data()); |
| } |
| |
| template <GlyphVector_Concepts::BackendData B> B& accessBackendData() { |
| SkASSERT(this->hasBackendData()); |
| return *reinterpret_cast<B*>(fBackendDataBytes.data()); |
| } |
| |
| template <GlyphVector_Concepts::GlyphType G> SkSpan<const G> accessBackendGlyphs() const { |
| SkASSERT(this->hasBackendData()); |
| auto* first = reinterpret_cast<const G*>(fGlyphs.front().data()); |
| return {first, fGlyphs.size()}; |
| } |
| |
| template <GlyphVector_Concepts::BackendData B> |
| void initBackendData(StrikeCache* cache, skgpu::MaskFormat maskFormat, auto&&... args) |
| requires std::is_constructible_v<B, Strike<B>, decltype(args)...> { |
| SkASSERT(!this->hasBackendData()); |
| SkASSERT(cache); |
| |
| SkStrike* strike = fStrikePromise.strike(); |
| const SkStrikeSpec& spec = strike->strikeSpec(); |
| auto backendStrike = B::FindStrike(cache, spec); |
| |
| auto backendData = new (fBackendDataBytes.data()) B{ |
| std::move(backendStrike), |
| std::forward<decltype(args)>(args)... |
| }; |
| |
| using G = Glyph<B>; |
| |
| for (auto& g : fGlyphs) { |
| auto id = *reinterpret_cast<SkPackedGlyphID*>(g.data()); |
| new (g.data()) G{backendData->makeGlyphFromID(id, maskFormat)}; |
| } |
| |
| fBackendDataReleaser = [](std::byte* p) { reinterpret_cast<B*>(p)->~B(); }; |
| |
| fGetGlyphID = [](const std::byte* b) { |
| auto* g = reinterpret_cast<const G*>(b); |
| return g->packedID(); |
| }; |
| |
| strike->verifyPinnedStrike(); |
| fStrikePromise.resetStrike(); |
| } |
| |
| private: |
| friend class GlyphVectorTestingPeer; |
| |
| using Releaser = void(std::byte*); |
| |
| using GetGlyphID = SkPackedGlyphID(const std::byte*); |
| |
| alignas(max_align_t) BackendDataBytes fBackendDataBytes; |
| |
| Releaser* fBackendDataReleaser = nullptr; |
| GetGlyphID* fGetGlyphID = nullptr; |
| |
| SkStrikePromise fStrikePromise; |
| |
| SkSpan<GlyphBytes> fGlyphs; |
| }; |
| } // namespace sktext::gpu |
| #endif // sktext_gpu_GlyphVector_DEFINED |