blob: 35432127f90c19cb1ffba87a0f180bf89ccedb46 [file] [log] [blame] [edit]
/*
* 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