yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Theresa FoleyAdd a memory-mappable binary serialization format (#7222)ec7ab914f

master
11.0 KiB352 linesraw
1// slang-blob-builder.h
2#ifndef SLANG_BLOB_BUILDER_H
3#define SLANG_BLOB_BUILDER_H
4
5// This file provides utilities for building "blobs" of data
6// where, for purposes, a blob is a contiguous sequence of
7// bytes where the interpretation *of* those bytes depends
8// only on the bytes themselves, and not other factors like
9// the in-memory address of the blob, or the address/contents
10// of memory not in the blob.
11//
12// Superficially, the task seems simple: just maintain a
13// dynamically-sized array of bytes and append to it until
14// you're done. If that's what you need, you're probably
15// better off just using an `OwnedMemoryStream`.
16//
17// The utilities in this file deal with the case where you
18// want to build some kind of offset-based data structure,
19// so that parts of the blob will store byte offsets to
20// other parts, while also being able to build parts of
21// that structure "out of order", so that the final offset
22// of a particular piece of data in the blob may not be
23// known until everything *before* it has been fully built.
24
25#include "slang-basic.h"
26#include "slang-internally-linked-list.h"
27#include "slang-io.h"
28#include "slang-memory-arena.h"
29
30namespace Slang
31{
32
33inline constexpr bool isPowerOfTwo(Size value)
34{
35    return value > 0 && (value - 1 & value) == 0;
36}
37
38inline constexpr Size roundUpToAlignment(Size size, Size alignment)
39{
40    SLANG_ASSERT(isPowerOfTwo(alignment));
41
42    auto alignmentMask = Size(alignment) - 1;
43    return (size + alignmentMask) & ~alignmentMask;
44}
45
46class ShardBuilder;
47class ChunkBuilder;
48struct BlobBuilder;
49
50
51/// A utility type for composing a binary blob.
52///
53/// A blob builder allows a blob to be composed as a sequence of discrete
54/// chunks, allowing chunks to be added and written to in any order.
55///
56/// Chunks can contain (relative) pointers to one another, with the correct
57/// relative offsets being computed as part of writing the entire blob out.
58///
59struct BlobBuilder
60{
61public:
62    /// Construct an empty blob builder.
63    BlobBuilder();
64
65    /// Write the contents of the blob to the given `stream`.
66    void writeTo(Stream* stream);
67
68    /// Create a copy of the contents of the blob and assign to `outBlob`.
69    void writeToBlob(ISlangBlob** outBlob);
70
71    /// Add a new empty chunk to the end of the blob.
72    ChunkBuilder* addChunk();
73
74    /// Add a new empty chunk after the given `chunk`.
75    ChunkBuilder* addChunkAfter(ChunkBuilder* chunk);
76
77    /// Create a chunk that is not initially part of the blob.
78    ///
79    /// The contents of the returned chunk will only become
80    /// part of the full blob if `addChunk()` is called later,
81    /// *or* if the contents of the new chunk are moved into
82    /// another chunk that gets added.
83    ///
84    ChunkBuilder* createUnparentedChunk();
85
86    /// Add a chunk to the blob that was initially not part of the blob.
87    void addChunk(ChunkBuilder* chunk);
88
89private:
90    InternallyLinkedList<ChunkBuilder> _chunks;
91    MemoryArena _arena;
92
93    friend class ChunkBuilder;
94    friend class ShardBuilder;
95    MemoryArena& _getArena() { return _arena; }
96
97    Size _calcSizeAndSetCachedChunkOffsets();
98    Size _writeChunksTo(Stream* stream);
99};
100
101/// A chunk is a logically contiguous unit, such that data can
102/// only ever be appended to it. As a result, offsets that are
103/// relative to the start of a chunk are meaningful, and can be
104/// used to encode relative offsets for pointers.
105///
106/// Every `ChunkBuilder` is owned by its parent `BlobBuilder`.
107/// A pointer to a `ChunkBuilder` will only be valid during the
108/// lifetime of the parent `BlobBuilder`.
109///
110/// Conceptually, a `ChunkBuilder` has the following:
111///
112/// * A minimum *alignment* in bytes (initially 1)
113///
114/// * Zero or more bytes of *content* data (initially empty)
115///
116/// * An optional *prefix* consisting of zero or more bytes (initially absent)
117///
118/// The content may be a mix of raw data (e.g., added via `writeData()`),
119/// relative pointers to other chunks (`writeRelativePtr()`) and padding
120/// bytes (`writePaddingToAlignTo()`).
121///
122/// The prefix may only be either a single relative pointer, or a
123/// single range of raw data bytes.
124///
125/// When the parent blob written out as a flat buffer of bytes, the
126/// following are guaranteed:
127///
128/// * The content of the chunk will be a contiguous range of bytes
129///   starting at some offset, and will not overlap any other chunks.
130///
131/// * The byte offset where the chunk contents start will be a multiple
132///   of the chunk's minimum alignment.
133///
134/// * The bytes of the prefix (if any) will immediately precede the
135///   bytes of the content.
136///
137class ChunkBuilder : public InternallyLinkedList<ChunkBuilder>::Node
138{
139public:
140    /// Get the blob builder that this chunk belongs to.
141    BlobBuilder* getParentBlob() const { return _parentBlob; }
142
143    /// Get the required alignment of this chunk.
144    ///
145    /// The minimum alignment for a chunk starts at 1,
146    /// and may be increased by operations such as
147    /// `setAlignmentToAtLeast()` and `writePaddingToAlinTo()`.
148    ///
149    Size getAlignment() const;
150
151    /// Potentially increases the required alignment of this chunk.
152    ///
153    /// If the alignment of the chunk is less than `alignment`,
154    /// then it will be increased to `alignment`.
155    ///
156    /// The `alignment` passed in must be a power of two.
157    ///
158    void setAlignmentToAtLeast(Size alignment);
159
160    /// Get the size in bytes of the content of this chunk.
161    ///
162    /// Note that the size is not necessarily a multiple of the
163    /// alignment of the chunk.
164    ///
165    Size getContentSize() const;
166
167    /// Write data into the chunk.
168    ///
169    /// The chunk will retain a copy of the data passed in,
170    /// so the `data` pointer only needs to be valid for
171    /// the duration of the call.
172    ///
173    /// Note that this operation does *not* adjust the
174    /// alignment of the chunk in any way.
175    ///
176    /// The data must only contain types that can be copied
177    /// bit-for-bit, and that do not depend on addresses
178    /// in meory. In particular no pointers (absolute or
179    /// relative) should be written.
180    ///
181    void writeData(void const* data, Size size);
182
183    /// Append padding bytes to this chunk until its content size
184    /// is a multiple of `alignment`.
185    ///
186    /// May also increase the alignment of the chunk, as if
187    /// calling `setAlignmentToAtLeast(alignment)`.
188    ///
189    /// The padding bytes will all be zero.
190    ///
191    void writePaddingToAlignTo(Size alignment);
192
193    /// Write a relative pointer to the given `targetChunk`.
194    ///
195    /// The type parameter `T` is used to determine the size
196    /// of the relative pointer (should be either 4 or 8 bytes).
197    ///
198    /// The bytes that eventually get written will contain
199    /// the computed offset of `targetChunk` minus the computed
200    /// offset of the first byte of the relative pointer itself.
201    ///
202    /// Acts as is `writePaddingToAlignTo(sizeof(T))` were
203    /// called immediately before.
204    ///
205    template<typename T>
206    void writeRelativePtr(ChunkBuilder* targetChunk)
207    {
208        _writeRelativePtr(targetChunk, sizeof(T));
209    }
210
211    /// Append the contents of another chunk to this one.
212    ///
213    /// This *moves* all of the contents of `chunk` into `this`.
214    /// After the operation completes `chunk` will be an empty
215    /// chunk with one-byte alignment.
216    ///
217    /// This operation is useful when accumulating data that
218    /// needs to be appended to the chunk, but where the correct
219    /// alignment for that data is not yet known; a use can
220    /// effectively create a temporary "sub-chunk" and then append
221    /// it to the main chunk once its correct alignment is known.
222    ///
223    void addContentsOf(ChunkBuilder* chunk);
224
225    /// Get the size in bytes of the prefix of this chunk, if any.
226    ///
227    /// The prefix will be written so that the *end* offset of the
228    /// prefix data is the same as the starting offset of the
229    /// chunk's content.
230    ///
231    Size getPrefixSize() const;
232
233    /// Add a prefix to this chunk, consisting of raw data.
234    ///
235    /// This chunk must not already have a prefix.
236    ///
237    void addPrefixData(void const* data, Size size);
238
239    /// Add a prefix to this chunk, consisting of a relative pointer.
240    ///
241    /// This chunk must not already have a prefix.
242    ///
243    /// Updates the alignment of the chunk as if making a call to
244    /// `setAlignmentToAtLeast(sizeof(T))`.
245    ///
246    template<typename T>
247    void addPrefixRelativePtr(ChunkBuilder* targetChunk)
248    {
249        _addPrefixRelativePtr(targetChunk, sizeof(T));
250    }
251
252    /// Insert a new chunk into the blob, immediately after this chunk.
253    ///
254    ChunkBuilder* addChunkAfter();
255
256private:
257    ChunkBuilder() = delete;
258    ChunkBuilder(ChunkBuilder const&) = delete;
259    ChunkBuilder(ChunkBuilder&&) = delete;
260
261    friend struct BlobBuilder;
262    friend class ShardBuilder;
263
264    ChunkBuilder(BlobBuilder* parentBlob);
265
266    Size _contentSize = 0;
267    Size _contentAlignment = 1;
268
269    InternallyLinkedList<ShardBuilder> _childShards;
270    BlobBuilder* _parentBlob = nullptr;
271
272    Size _cachedOffset = ~Size(0);
273    Size _getCachedOffset() { return _cachedOffset; }
274    void _setCachedOffset(Size offset) { _cachedOffset = offset; }
275
276    void _writeRelativePtr(ChunkBuilder* targetChunk, Size ptrSize);
277
278    ShardBuilder* _createDataShard(void const* data, Size size);
279    ShardBuilder* _createRelativePtrShard(ChunkBuilder* targetChunk, Size ptrSize);
280
281    ShardBuilder* _prefixShard = nullptr;
282
283    void _writeTo(Stream* stream);
284
285    void _addPrefixRelativePtr(ChunkBuilder* targetChunk, Size ptrSize);
286};
287
288/// A shard is a unit of contiguously-allocated data that makes
289/// up part of a chunk.
290///
291/// Shards are *not* meant to be directly manipulated by users;
292/// they are an implementation detail of `ChunkBuilder`.
293///
294/// Every `ShardBuilder` is owned by its parent `ChunkBuilder`.
295/// A pointer to a `ShardBuilder` will only be valid during the
296/// lifetime of the parent `ChunkBuilder`.
297///
298class ShardBuilder : public InternallyLinkedList<ShardBuilder>::Node
299{
300public:
301    // There are two kinds of shards that may appear in a chunk:
302    //
303    // * Shards that hold plain data that will be part of the
304    //   serialized chunk.
305    //
306    // * Shards that represent a relative pointer to some chunk,
307    //   which cannot have their exact binary value determined
308    //   until the offsets of chunk/shards have been finalized.
309
310    enum class Kind
311    {
312        Data,
313        RelativePtr,
314    };
315
316    Size getSize() const { return _size; }
317
318private:
319    ShardBuilder() = delete;
320    ShardBuilder(ShardBuilder const&) = delete;
321    ShardBuilder(ShardBuilder&&) = delete;
322
323    friend class ChunkBuilder;
324    ShardBuilder(Kind kind);
325
326    void _writeTo(Stream* stream, Size selfOffset);
327
328    /// Kind of this shard (data or relative pointer)
329    Kind _kind = Kind::Data;
330
331    union
332    {
333        /// Used when `_kind == Kind::Data`
334        struct
335        {
336            void const* ptr;
337        } _data;
338
339        /// Used when `_kind == Kind::RelativePtr`
340        struct
341        {
342            ChunkBuilder* targetChunk;
343        } _relativePtr;
344    };
345
346    // Size of this shard in bytes.
347    Size _size = 0;
348};
349
350} // namespace Slang
351
352#endif