yum-mirror/slang
Making it easier to work with shaders
git clone https://git.yummers.dev/yum-mirror/slang
ec7ab914f
master
1// slang-blob-builder.cpp 2#include "slang-blob-builder.h" 3 4namespace Slang 5{ 6 7// 8// BlobBuilder 9// 10 11BlobBuilder ::BlobBuilder () 12 :_arena (4096 ) 13{ 14} 15 16void BlobBuilder ::writeTo (Stream * stream ) 17{ 18// First we scan through all the chunks to set their 19// absolute offsets, which enables us to compute the 20// correct values for relative pointers when we write 21// them out. 22// 23SLANG_MAYBE_UNUSED 24Size sizeComputed = _calcSizeAndSetCachedChunkOffsets (); 25 26// Now we can scan through the chunks again and write 27// the bytes of each of their shards. 28// 29SLANG_MAYBE_UNUSED 30Size sizeWritten = _writeChunksTo (stream ); 31 32SLANG_ASSERT (sizeComputed == sizeWritten ); 33} 34 35Size BlobBuilder ::_calcSizeAndSetCachedChunkOffsets () 36{ 37Size totalSize = 0 ; 38for (auto chunk :_chunks ) 39 { 40auto chunkPrefixSize = chunk -> getPrefixSize (); 41auto chunkContentSize = chunk -> getContentSize (); 42auto chunkAlignment = chunk -> getAlignment (); 43 44// We add the size of the chunk prefix (if any) *before* 45// aligning the current offset. Doing it this way 46// means that sometimes the prefix can fit "for free" 47// in space that would otherwise be padding. 48// 49// For example, if the current `totalSize` is 4, the 50// `chunkAlignment` is 16, and the `chunkPrefixSize` is 8, 51// then the sequence will be: 52// 53// * Add `totalSize += chunkPrefixSize`, resulting in a `totalSize` 54// of 12. 55// 56// * Round the `totalSize` up to the `chunkAlignment`, to compute 57// a `chunkOffset` of 16. 58// 59// The result is that the chunk's content starts at offset 16, 60// and the prefix can occupy the 8 bytes before that (so the 61// prefix is at offset 8). 62// 63// In the best case this approach can save a few bytes here or 64// there when `chunkAlignment` is larger than the `chunkPrefixSize`, 65// and in the worst case it does no harm. 66 67totalSize += chunkPrefixSize ; 68auto chunkOffset = roundUpToAlignment (totalSize ,chunkAlignment ); 69 70chunk -> _setCachedOffset (chunkOffset ); 71 72totalSize = chunkOffset + chunkContentSize ; 73 } 74return totalSize ; 75} 76 77Size BlobBuilder ::_writeChunksTo (Stream * stream ) 78{ 79Size totalSize = 0 ; 80for (auto chunk :_chunks ) 81 { 82auto chunkPrefixSize = chunk -> getPrefixSize (); 83auto chunkContentSize = chunk -> getContentSize (); 84auto chunkOffset = chunk -> _getCachedOffset (); 85 86SLANG_ASSERT ( 87chunkOffset == roundUpToAlignment (totalSize + chunkPrefixSize ,chunk -> getAlignment ())); 88 89auto paddingSize = chunkOffset - totalSize ; 90 91// The "padding" before the chunk's content also 92// includes the space reserved for the chunk's prefix. 93// 94// We can thus subtract the prefix size from the number 95// of pad bytes to write. 96 97SLANG_ASSERT (paddingSize >=chunkPrefixSize ); 98paddingSize -= chunkPrefixSize ; 99 100while (paddingSize -- ) 101 { 102Byte padding = 0 ; 103stream -> write (& padding ,sizeof (padding )); 104 } 105 106// The `ChunkBuilder::_writeTo()` call will write the 107// prefix *and* the chunk content, so it is appropriate 108// to call it here even when the total number of bytes 109// written to the stream is not equal to the `chunkOffset` 110// (because in that case the total bytes written so far 111// should be `chunkOffset - chunkPrefixSize`). 112// 113chunk -> _writeTo (stream ); 114 115totalSize = chunkOffset + chunkContentSize ; 116 } 117 118return totalSize ; 119} 120 121void BlobBuilder ::writeToBlob (ISlangBlob ** outBlob ) 122{ 123OwnedMemoryStream stream (FileAccess ::Write ); 124writeTo (& stream ); 125 126List < uint8_t > data ; 127stream .swapContents (data ); 128 129* outBlob = ListBlob ::moveCreate (data ).detach (); 130} 131 132ChunkBuilder * BlobBuilder ::createUnparentedChunk () 133{ 134auto chunk = new (_arena )ChunkBuilder (this ); 135return chunk ; 136} 137 138void BlobBuilder ::addChunk (ChunkBuilder * chunk ) 139{ 140// TODO(tfoley): it would be good to have a way to assert 141// that the chunk has not already been added. 142 143_chunks .add (chunk ); 144} 145 146ChunkBuilder * BlobBuilder ::addChunk () 147{ 148auto chunk = new (_arena )ChunkBuilder (this ); 149_chunks .add (chunk ); 150return chunk ; 151} 152 153ChunkBuilder * BlobBuilder ::addChunkAfter (ChunkBuilder * existingChunk ) 154{ 155auto newChunk = new (_arena )ChunkBuilder (this ); 156_chunks .insertAfter (existingChunk ,newChunk ); 157return newChunk ; 158} 159 160// 161// ChunkBuilder 162// 163 164void ChunkBuilder ::setAlignmentToAtLeast (Size alignment ) 165{ 166SLANG_ASSERT (alignment > 0 ); 167SLANG_ASSERT (isPowerOfTwo (alignment )); 168 169_contentAlignment = std::max (_contentAlignment ,alignment ); 170} 171 172void ChunkBuilder ::writePaddingToAlignTo (Size alignment ) 173{ 174setAlignmentToAtLeast (alignment ); 175 176auto alignedSize = roundUpToAlignment (_contentSize ,alignment ); 177 178auto requiredPaddingSize = alignedSize - _contentSize ; 179 180while (requiredPaddingSize ) 181 { 182Byte padByte = 0 ; 183writeData (& padByte ,sizeof (padByte )); 184requiredPaddingSize -= sizeof (padByte ); 185 } 186} 187 188ChunkBuilder ::ChunkBuilder (BlobBuilder * parentBlob ) 189 :_parentBlob (parentBlob ) 190{ 191} 192 193Size ChunkBuilder ::getContentSize ()const 194{ 195return _contentSize ; 196} 197 198Size ChunkBuilder ::getPrefixSize ()const 199{ 200if (!_prefixShard ) 201return 0 ; 202 203return _prefixShard -> getSize (); 204} 205 206Size ChunkBuilder ::getAlignment ()const 207{ 208return _contentAlignment ; 209} 210 211 212void ChunkBuilder ::writeData (void const * data ,Size size ) 213{ 214// Adding no data should be a no-op. 215// 216if (size == 0 ) 217return ; 218 219 220// The most interesting implementation detail here 221// is that we will try to detect cases where we 222// can re-use an existing `ShardBuilder` by adding the data 223// to the end of that shard's allocation. 224// 225// This is only possible because of the way that 226// we are using a single `MemoryArena` to allocate 227// everything, which makes it possible that the 228// next address the arena would return for an allocation 229// of `size` bytes is the same as the ending address 230// of the payload for the last shard of this chunk. 231// 232auto & arena = getParentBlob ()-> _getArena (); 233 234// We start by checking if this chunk already has 235// a last shard that we could consider appending to. 236// 237auto lastShard = _childShards .getLast (); 238if (lastShard && lastShard -> _kind == ShardBuilder ::Kind ::Data ) 239 { 240// If there is a last shard, then we can compute 241// the end address of its payload, and see if 242// it is the same as the cursor of the arena 243// we are allocating from. 244// 245auto payload = lastShard -> _data .ptr ; 246auto payloadSize = lastShard -> _size ; 247auto payloadEnd = (Byte * )payload + payloadSize ; 248if (payloadEnd == arena .getCursor ()) 249 { 250// Now that we've confirmed that the shard's 251// payload ends at an address the arena could 252// conceivably allocate from, we need to ask 253// the arena to allocate `size` bytes from 254// the current block it is using, and see if 255// doing so succeeds. 256// 257if (arena .allocateCurrentUnaligned (size )) 258 { 259// At this point, we've confirmed that we 260// are in our special case, and the relevant 261// bytes have been allocated from the arena. 262// 263// Now we can simply write the new data at 264// what used to be the end address for the 265// shard's payload, and adjust its state 266// to account for the new allocation. 267// 268 ::memcpy (payloadEnd ,data ,size ); 269 270lastShard -> _size = payloadSize + size ; 271_contentSize += size ; 272return ; 273 } 274 } 275 } 276 277// If the special case above doesn't apply, we simply 278// allocate a new shard to hold the data that was 279// passed in. 280// 281auto shard = _createDataShard (data ,size ); 282_childShards .add (shard ); 283_contentSize += size ; 284} 285 286void ChunkBuilder ::addContentsOf (ChunkBuilder * otherChunk ) 287{ 288auto otherPrefixShard = otherChunk -> _prefixShard ; 289auto otherChunkSize = otherChunk -> getContentSize (); 290auto otherChunkAlignment = otherChunk -> getAlignment (); 291auto otherChunkShards = otherChunk -> _childShards ; 292 293otherChunk -> _prefixShard = nullptr ; 294otherChunk -> _contentSize = 0 ; 295otherChunk -> _contentAlignment = 1 ; 296otherChunk -> _childShards = InternallyLinkedList < ShardBuilder > (); 297 298if (otherPrefixShard ) 299 { 300// If the other chunk included a prefix, then 301// it only makes sense to append it in the case 302// where *this* chunk is completely empty. 303 304SLANG_ASSERT (!_prefixShard ); 305SLANG_ASSERT (!_childShards .getFirst ()); 306_prefixShard = otherPrefixShard ; 307 } 308 309writePaddingToAlignTo (otherChunkAlignment ); 310_childShards .append (otherChunkShards ); 311_contentSize += otherChunkSize ; 312} 313 314ChunkBuilder * ChunkBuilder ::addChunkAfter () 315{ 316return getParentBlob ()-> addChunkAfter (this ); 317} 318 319void ChunkBuilder ::_writeRelativePtr (ChunkBuilder * targetChunk ,Size ptrSize ) 320{ 321SLANG_ASSERT (ptrSize != 0 ); 322SLANG_ASSERT (ptrSize <=sizeof (UInt64 )); 323 324writePaddingToAlignTo (ptrSize ); 325 326if (!targetChunk ) 327 { 328UInt64 value = 0 ; 329writeData (& value ,ptrSize ); 330return ; 331 } 332 333auto shard = _createRelativePtrShard (targetChunk ,ptrSize ); 334_childShards .add (shard ); 335_contentSize += ptrSize ; 336} 337 338void ChunkBuilder ::_addPrefixRelativePtr (ChunkBuilder * targetChunk ,Size ptrSize ) 339{ 340SLANG_ASSERT (targetChunk != nullptr ); 341SLANG_ASSERT (ptrSize != 0 ); 342 343SLANG_ASSERT (!_prefixShard ); 344 345setAlignmentToAtLeast (ptrSize ); 346 347auto shard = _createRelativePtrShard (targetChunk ,ptrSize ); 348_prefixShard = shard ; 349} 350 351void ChunkBuilder ::addPrefixData (void const * data ,Size size ) 352{ 353SLANG_ASSERT (data != nullptr ); 354SLANG_ASSERT (size != 0 ); 355 356SLANG_ASSERT (!_prefixShard ); 357 358setAlignmentToAtLeast (size ); 359 360auto shard = _createDataShard (data ,size ); 361_prefixShard = shard ; 362} 363 364ShardBuilder * ChunkBuilder ::_createDataShard (void const * data ,Size size ) 365{ 366auto & arena = getParentBlob ()-> _getArena (); 367auto shard = new (arena )ShardBuilder (ShardBuilder ::Kind ::Data ); 368 369auto shardData = arena .allocateUnaligned (size ); 370 ::memcpy (shardData ,data ,size ); 371 372shard -> _data .ptr = shardData ; 373shard -> _size = size ; 374 375return shard ; 376} 377 378ShardBuilder * ChunkBuilder ::_createRelativePtrShard (ChunkBuilder * targetChunk ,Size ptrSize ) 379{ 380auto & arena = getParentBlob ()-> _getArena (); 381auto shard = new (arena )ShardBuilder (ShardBuilder ::Kind ::RelativePtr ); 382 383shard -> _relativePtr .targetChunk = targetChunk ; 384shard -> _size = ptrSize ; 385 386return shard ; 387} 388 389void ChunkBuilder ::_writeTo (Stream * stream ) 390{ 391auto chunkOffset = _getCachedOffset (); 392 393if (_prefixShard ) 394 { 395// Note that the prefix is written *before* the 396// starting offset of the chunk's content, so we 397// compute the appropriate offset to pass down. 398// 399auto prefixSize = _prefixShard -> getSize (); 400auto prefixOffset = chunkOffset - prefixSize ; 401 402_prefixShard -> _writeTo (stream ,prefixOffset ); 403 } 404 405auto shardOffset = chunkOffset ; 406for (auto shard :_childShards ) 407 { 408shard -> _writeTo (stream ,shardOffset ); 409shardOffset += shard -> getSize (); 410 } 411SLANG_ASSERT (shardOffset == chunkOffset + getContentSize ()); 412} 413 414 415// 416// ShardBuilder 417// 418 419ShardBuilder ::ShardBuilder (Kind kind ) 420 :_kind (kind ) 421{ 422} 423 424void ShardBuilder ::_writeTo (Stream * stream ,Size inSelfOffset ) 425{ 426switch (_kind ) 427 { 428case Kind ::Data : 429 { 430stream -> write (_data .ptr ,_size ); 431 } 432break ; 433 434case Kind ::RelativePtr : 435 { 436auto targetChunk = _relativePtr .targetChunk ; 437SLANG_ASSERT (targetChunk ); 438 439auto selfOffset = intptr_t (inSelfOffset ); 440auto targetOffset = intptr_t (targetChunk -> _getCachedOffset ()); 441 442intptr_t relativeOffset = targetOffset - selfOffset ; 443 444switch (_size ) 445 { 446case sizeof (Int32 ): 447 { 448auto value = Int32 (relativeOffset ); 449stream -> write (& value ,sizeof (value )); 450 } 451break ; 452 453case sizeof (Int64 ): 454 { 455auto value = Int64 (relativeOffset ); 456stream -> write (& value ,sizeof (value )); 457 } 458break ; 459 460default : 461SLANG_UNEXPECTED ("unsupported relative pointer size" ); 462break ; 463 } 464 } 465break ; 466 467default : 468SLANG_UNEXPECTED ("unknown Fossil::ShardBuilder::Kind" ); 469break ; 470 } 471} 472 473}// namespace Slang