blob: 088975bc169cf94a493827497e8680d96b4300f9 [file] [log] [blame]
commit-bot@chromium.orgc4b21e62014-04-11 18:33:31 +00001/*
2 * Copyright 2014 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +00008#ifndef SkRecord_DEFINED
9#define SkRecord_DEFINED
10
Herb Derby4304d112017-04-18 11:42:49 -040011#include "SkArenaAlloc.h"
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +000012#include "SkRecords.h"
commit-bot@chromium.org08bf86c2014-05-07 18:01:57 +000013#include "SkTLogic.h"
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +000014#include "SkTemplates.h"
15
mtklein29b1afc2015-04-09 07:46:41 -070016// SkRecord represents a sequence of SkCanvas calls, saved for future use.
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +000017// These future uses may include: replay, optimization, serialization, or combinations of those.
18//
19// Though an enterprising user may find calling alloc(), append(), visit(), and mutate() enough to
20// work with SkRecord, you probably want to look at SkRecorder which presents an SkCanvas interface
21// for creating an SkRecord, and SkRecordDraw which plays an SkRecord back into another SkCanvas.
22//
23// SkRecord often looks like it's compatible with any type T, but really it's compatible with any
24// type T which has a static const SkRecords::Type kType. That is to say, SkRecord is compatible
25// only with SkRecords::* structs defined in SkRecords.h. Your compiler will helpfully yell if you
26// get this wrong.
27
Mike Kleincd25df9c2017-04-11 10:50:27 -040028class SkRecord : public SkRefCnt {
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +000029public:
Herb Derby4304d112017-04-18 11:42:49 -040030 SkRecord() = default;
mtkleinf98862c2014-11-24 14:45:47 -080031 ~SkRecord();
commit-bot@chromium.org506db0b2014-04-08 23:31:35 +000032
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +000033 // Returns the number of canvas commands in this SkRecord.
mtkleinc6ad06a2015-08-19 09:51:00 -070034 int count() const { return fCount; }
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +000035
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +000036 // Visit the i-th canvas command with a functor matching this interface:
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +000037 // template <typename T>
commit-bot@chromium.orgc71da1f2014-05-07 21:16:09 +000038 // R operator()(const T& record) { ... }
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +000039 // This operator() must be defined for at least all SkRecords::*.
mtklein343a63d2016-03-22 11:46:53 -070040 template <typename F>
41 auto visit(int i, F&& f) const -> decltype(f(SkRecords::NoOp())) {
42 return fRecords[i].visit(f);
commit-bot@chromium.org506db0b2014-04-08 23:31:35 +000043 }
44
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +000045 // Mutate the i-th canvas command with a functor matching this interface:
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +000046 // template <typename T>
commit-bot@chromium.orgc71da1f2014-05-07 21:16:09 +000047 // R operator()(T* record) { ... }
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +000048 // This operator() must be defined for at least all SkRecords::*.
mtklein343a63d2016-03-22 11:46:53 -070049 template <typename F>
50 auto mutate(int i, F&& f) -> decltype(f((SkRecords::NoOp*)nullptr)) {
51 return fRecords[i].mutate(f);
commit-bot@chromium.org506db0b2014-04-08 23:31:35 +000052 }
mtklein29b1afc2015-04-09 07:46:41 -070053
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +000054 // Allocate contiguous space for count Ts, to be freed when the SkRecord is destroyed.
55 // Here T can be any class, not just those from SkRecords. Throws on failure.
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +000056 template <typename T>
reed2347b622014-08-07 12:19:50 -070057 T* alloc(size_t count = 1) {
Herb Derby4304d112017-04-18 11:42:49 -040058 struct RawBytes {
59 alignas(T) char data[sizeof(T)];
60 };
61 fApproxBytesAllocated += count * sizeof(T) + alignof(T);
62 return (T*)fAlloc.makeArrayDefault<RawBytes>(count);
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +000063 }
64
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +000065 // Add a new command of type T to the end of this SkRecord.
66 // You are expected to placement new an object of type T onto this pointer.
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +000067 template <typename T>
68 T* append() {
69 if (fCount == fReserved) {
mtkleinf98862c2014-11-24 14:45:47 -080070 this->grow();
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +000071 }
commit-bot@chromium.org08bf86c2014-05-07 18:01:57 +000072 return fRecords[fCount++].set(this->allocCommand<T>());
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +000073 }
74
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +000075 // Replace the i-th command with a new command of type T.
76 // You are expected to placement new an object of type T onto this pointer.
commit-bot@chromium.orgf0ae5e42014-04-24 15:33:48 +000077 // References to the original command are invalidated.
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +000078 template <typename T>
mtkleinc6ad06a2015-08-19 09:51:00 -070079 T* replace(int i) {
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +000080 SkASSERT(i < this->count());
commit-bot@chromium.orgf0ae5e42014-04-24 15:33:48 +000081
82 Destroyer destroyer;
mtklein343a63d2016-03-22 11:46:53 -070083 this->mutate(i, destroyer);
commit-bot@chromium.orgf0ae5e42014-04-24 15:33:48 +000084
commit-bot@chromium.org08bf86c2014-05-07 18:01:57 +000085 return fRecords[i].set(this->allocCommand<T>());
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +000086 }
87
commit-bot@chromium.orgf0ae5e42014-04-24 15:33:48 +000088 // Replace the i-th command with a new command of type T.
89 // You are expected to placement new an object of type T onto this pointer.
90 // You must show proof that you've already adopted the existing command.
91 template <typename T, typename Existing>
mtkleinc6ad06a2015-08-19 09:51:00 -070092 T* replace(int i, const SkRecords::Adopted<Existing>& proofOfAdoption) {
commit-bot@chromium.orgf0ae5e42014-04-24 15:33:48 +000093 SkASSERT(i < this->count());
94
mtklein29b1afc2015-04-09 07:46:41 -070095 SkASSERT(Existing::kType == fRecords[i].type());
96 SkASSERT(proofOfAdoption == fRecords[i].ptr());
commit-bot@chromium.orgf0ae5e42014-04-24 15:33:48 +000097
commit-bot@chromium.org08bf86c2014-05-07 18:01:57 +000098 return fRecords[i].set(this->allocCommand<T>());
commit-bot@chromium.orgf0ae5e42014-04-24 15:33:48 +000099 }
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +0000100
tomhudson158fcaa2014-11-19 10:41:14 -0800101 // Does not return the bytes in any pointers embedded in the Records; callers
102 // need to iterate with a visitor to measure those they care for.
mtkleinf98862c2014-11-24 14:45:47 -0800103 size_t bytesUsed() const;
tomhudson158fcaa2014-11-19 10:41:14 -0800104
mtkleinc3c61942015-11-19 07:23:49 -0800105 // Rearrange and resize this record to eliminate any NoOps.
106 // May change count() and the indices of ops, but preserves their order.
107 void defrag();
108
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +0000109private:
mtklein29b1afc2015-04-09 07:46:41 -0700110 // An SkRecord is structured as an array of pointers into a big chunk of memory where
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +0000111 // records representing each canvas draw call are stored:
112 //
113 // fRecords: [*][*][*]...
114 // | | |
115 // | | |
116 // | | +---------------------------------------+
117 // | +-----------------+ |
118 // | | |
119 // v v v
120 // fAlloc: [SkRecords::DrawRect][SkRecords::DrawPosTextH][SkRecords::DrawRect]...
121 //
mtklein29b1afc2015-04-09 07:46:41 -0700122 // We store the types of each of the pointers alongside the pointer.
123 // The cost to append a T to this structure is 8 + sizeof(T) bytes.
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +0000124
commit-bot@chromium.orgf0ae5e42014-04-24 15:33:48 +0000125 // A mutator that can be used with replace to destroy canvas commands.
126 struct Destroyer {
127 template <typename T>
128 void operator()(T* record) { record->~T(); }
129 };
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +0000130
commit-bot@chromium.org08bf86c2014-05-07 18:01:57 +0000131 template <typename T>
bungeman0e101662016-01-08 11:05:09 -0800132 SK_WHEN(std::is_empty<T>::value, T*) allocCommand() {
commit-bot@chromium.org50ca12b2014-05-07 19:56:27 +0000133 static T singleton = {};
commit-bot@chromium.org08bf86c2014-05-07 18:01:57 +0000134 return &singleton;
135 }
136
137 template <typename T>
bungeman0e101662016-01-08 11:05:09 -0800138 SK_WHEN(!std::is_empty<T>::value, T*) allocCommand() { return this->alloc<T>(); }
commit-bot@chromium.org08bf86c2014-05-07 18:01:57 +0000139
mtkleinf98862c2014-11-24 14:45:47 -0800140 void grow();
141
mtklein29b1afc2015-04-09 07:46:41 -0700142 // A typed pointer to some bytes in fAlloc. visit() and mutate() allow polymorphic dispatch.
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +0000143 struct Record {
mtklein29b1afc2015-04-09 07:46:41 -0700144 // On 32-bit machines we store type in 4 bytes, followed by a pointer. Simple.
145 // On 64-bit machines we store a pointer with the type slotted into two top (unused) bytes.
146 // FWIW, SkRecords::Type is tiny. It can easily fit in one byte.
147 uint64_t fTypeAndPtr;
148 static const int kTypeShift = sizeof(void*) == 4 ? 32 : 48;
149
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +0000150 // Point this record to its data in fAlloc. Returns ptr for convenience.
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +0000151 template <typename T>
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +0000152 T* set(T* ptr) {
mtklein29b1afc2015-04-09 07:46:41 -0700153 fTypeAndPtr = ((uint64_t)T::kType) << kTypeShift | (uintptr_t)ptr;
154 SkASSERT(this->ptr() == ptr && this->type() == T::kType);
commit-bot@chromium.org88c3e272014-04-22 16:57:20 +0000155 return ptr;
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +0000156 }
157
mtklein29b1afc2015-04-09 07:46:41 -0700158 SkRecords::Type type() const { return (SkRecords::Type)(fTypeAndPtr >> kTypeShift); }
159 void* ptr() const { return (void*)(fTypeAndPtr & ((1ull<<kTypeShift)-1)); }
commit-bot@chromium.orgf0ae5e42014-04-24 15:33:48 +0000160
mtklein29b1afc2015-04-09 07:46:41 -0700161 // Visit this record with functor F (see public API above).
mtklein343a63d2016-03-22 11:46:53 -0700162 template <typename F>
163 auto visit(F&& f) const -> decltype(f(SkRecords::NoOp())) {
mtklein29b1afc2015-04-09 07:46:41 -0700164 #define CASE(T) case SkRecords::T##_Type: return f(*(const SkRecords::T*)this->ptr());
165 switch(this->type()) { SK_RECORD_TYPES(CASE) }
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +0000166 #undef CASE
commit-bot@chromium.orgc71da1f2014-05-07 21:16:09 +0000167 SkDEBUGFAIL("Unreachable");
Mike Klein9f183502017-06-30 11:04:37 -0400168 static const SkRecords::NoOp noop{};
169 return f(noop);
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +0000170 }
171
mtklein29b1afc2015-04-09 07:46:41 -0700172 // Mutate this record with functor F (see public API above).
mtklein343a63d2016-03-22 11:46:53 -0700173 template <typename F>
174 auto mutate(F&& f) -> decltype(f((SkRecords::NoOp*)nullptr)) {
mtklein29b1afc2015-04-09 07:46:41 -0700175 #define CASE(T) case SkRecords::T##_Type: return f((SkRecords::T*)this->ptr());
176 switch(this->type()) { SK_RECORD_TYPES(CASE) }
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +0000177 #undef CASE
commit-bot@chromium.orgc71da1f2014-05-07 21:16:09 +0000178 SkDEBUGFAIL("Unreachable");
Mike Klein9f183502017-06-30 11:04:37 -0400179 static const SkRecords::NoOp noop{};
180 return f(const_cast<SkRecords::NoOp*>(&noop));
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +0000181 }
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +0000182 };
183
mtklein29b1afc2015-04-09 07:46:41 -0700184 // fRecords needs to be a data structure that can append fixed length data, and need to
185 // support efficient random access and forward iteration. (It doesn't need to be contiguous.)
Herb Derby4304d112017-04-18 11:42:49 -0400186 int fCount{0},
187 fReserved{0};
Mike Klein0fa156f2017-04-11 10:36:48 -0400188 SkAutoTMalloc<Record> fRecords;
mtklein29b1afc2015-04-09 07:46:41 -0700189
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +0000190 // fAlloc needs to be a data structure which can append variable length data in contiguous
191 // chunks, returning a stable handle to that data for later retrieval.
Herb Derby4304d112017-04-18 11:42:49 -0400192 SkArenaAlloc fAlloc{256};
193 size_t fApproxBytesAllocated{0};
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +0000194};
commit-bot@chromium.orge3ff5582014-04-01 16:24:06 +0000195
196#endif//SkRecord_DEFINED