diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2019-10-29 08:51:24 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-10-29 08:51:24 -0400 |
| commit | c27b7d91aaf6bc764807a8998a9c885e57c22a1b (patch) | |
| tree | 56dc12c9f326f7d4fe4ddf1bfe5903e8918732bd /source/core/slang-riff.cpp | |
| parent | c886ca811975e91cedca898a561ff65a5663272d (diff) | |
Feature/container format (#1098)
* WIP RiffContainer.
* WIP riff container.
* Testing out RiffContainer.
* * Naming improvements
* Visitor functions
* Ability to dump riffs.
* Renamed RiffChunk to RiffHeader
* Remove m_ prefix on RiffHeader members.
* Riff stream reading writing.
Simple test of riff reading/writing.
* Fix Riff alignment issue.
Make IR serialization use the RiffContainer API.
* Improve documentation.
* Remove SubChunk fuctionality as not needed with RiffContainer.
Diffstat (limited to 'source/core/slang-riff.cpp')
| -rw-r--r-- | source/core/slang-riff.cpp | 772 |
1 files changed, 738 insertions, 34 deletions
diff --git a/source/core/slang-riff.cpp b/source/core/slang-riff.cpp index 0b0519b48..9849cfc26 100644 --- a/source/core/slang-riff.cpp +++ b/source/core/slang-riff.cpp @@ -2,16 +2,18 @@ #include "../../slang-com-helper.h" +#include "slang-hex-dump-util.h" + namespace Slang { -/* static */int64_t RiffUtil::calcChunkTotalSize(const RiffChunk& chunk) +/* static */int64_t RiffUtil::calcChunkTotalSize(const RiffHeader& chunk) { - int64_t size = chunk.m_size + sizeof(RiffChunk); - return (size + 3) & ~int64_t(3); + size_t size = chunk.size + sizeof(RiffHeader); + return getPadSize(size); } -/* static */SlangResult RiffUtil::skip(const RiffChunk& chunk, Stream* stream, int64_t* remainingBytesInOut) +/* static */SlangResult RiffUtil::skip(const RiffHeader& chunk, Stream* stream, int64_t* remainingBytesInOut) { int64_t chunkSize = calcChunkTotalSize(chunk); if (remainingBytesInOut) @@ -20,16 +22,15 @@ namespace Slang } // Skip the payload (we don't need to skip the Chunk because that was already read - stream->seek(SeekOrigin::Current, chunkSize - sizeof(RiffChunk)); + stream->seek(SeekOrigin::Current, chunkSize - sizeof(RiffHeader)); return SLANG_OK; } - -/* static */SlangResult RiffUtil::readChunk(Stream* stream, RiffChunk& outChunk) +/* static */SlangResult RiffUtil::readChunk(Stream* stream, RiffHeader& outChunk) { try { - stream->read(&outChunk, sizeof(RiffChunk)); + stream->read(&outChunk, sizeof(RiffHeader)); } catch (IOException&) { @@ -42,32 +43,38 @@ namespace Slang } -/* static */SlangResult RiffUtil::writeData(const RiffChunk* header, size_t headerSize, const void* payload, size_t payloadSize, Stream* out) +/* static */SlangResult RiffUtil::writeData(const RiffHeader* header, size_t headerSize, const void* payload, size_t payloadSize, Stream* out) { SLANG_ASSERT(uint64_t(payloadSize) <= uint64_t(0xfffffffff)); - SLANG_ASSERT(headerSize >= sizeof(RiffChunk)); - SLANG_ASSERT((headerSize & 3) == 0); + SLANG_ASSERT(headerSize >= sizeof(RiffHeader)); // TODO(JS): Could handle endianness here - RiffChunk chunk; - chunk.m_type = header->m_type; - chunk.m_size = uint32_t(headerSize - sizeof(RiffChunk) + payloadSize); + RiffHeader chunk; + chunk.type = header->type; + chunk.size = uint32_t(headerSize - sizeof(RiffHeader) + payloadSize); try { // The chunk - out->write(&chunk, sizeof(RiffChunk)); - // The rest of the header - out->write(header + 1, headerSize - sizeof(RiffChunk)); + out->write(&chunk, sizeof(RiffHeader)); + + // Remainder of header + if (headerSize > sizeof(RiffHeader)) + { + // The rest of the header + out->write(header + 1, headerSize - sizeof(RiffHeader)); + } + // Write the payload out->write(payload, payloadSize); - size_t remaining = payloadSize & 3; - if (remaining) + // The riff spec requires all chunks are 4 byte aligned (even if size is not) + size_t padSize = getPadSize(payloadSize); + if (padSize - payloadSize) { - uint8_t end[4] = { 0, 0, 0, 0}; - out->write(end, 4 - remaining); + uint8_t end[kRiffPadSize] = { 0 }; + out->write(end, padSize - payloadSize); } } catch (IOException&) @@ -78,11 +85,33 @@ namespace Slang return SLANG_OK; } -/* static */SlangResult RiffUtil::readData(Stream* stream, RiffChunk* outHeader, size_t headerSize, List<uint8_t>& data) +/* static */SlangResult RiffUtil::readPayload(Stream* stream, size_t size, void* outData, size_t& outReadSize) { - RiffChunk chunk; + outReadSize = 0; + try + { + stream->read(outData, size); + const size_t alignedSize = getPadSize(size); + // Skip to the alignment + if (alignedSize > size) + { + stream->seek(SeekOrigin::Current, alignedSize - size); + } + outReadSize = alignedSize; + } + catch (IOException&) + { + return SLANG_FAIL; + } + + return SLANG_OK; +} + +/* static */SlangResult RiffUtil::readData(Stream* stream, RiffHeader* outHeader, size_t headerSize, List<uint8_t>& data) +{ + RiffHeader chunk; SLANG_RETURN_ON_FAIL(readChunk(stream, chunk)); - if (chunk.m_size < headerSize) + if (chunk.size < headerSize) { return SLANG_FAIL; } @@ -92,27 +121,702 @@ namespace Slang try { // Read the header - stream->read(outHeader + 1, headerSize - sizeof(RiffChunk)); + if (headerSize > sizeof(RiffHeader)) + { + stream->read(outHeader + 1, headerSize - sizeof(RiffHeader)); + } + } + catch (IOException&) + { + return SLANG_FAIL; + } - const size_t payloadSize = chunk.m_size - (headerSize - sizeof(RiffChunk)); + const size_t payloadSize = chunk.size - (headerSize - sizeof(RiffHeader)); + size_t readSize; + data.setCount(payloadSize); + return readPayload(stream, payloadSize, data.getBuffer(), readSize); +} - data.setCount(payloadSize); +/* static */SlangResult RiffUtil::readHeader(Stream* stream, RiffListHeader& outHeader) +{ + // Need to read the chunk header + SLANG_RETURN_ON_FAIL(readChunk(stream, outHeader.chunk)); + outHeader.subType = 0; - stream->read(data.getBuffer(), payloadSize); + if (isListType(outHeader.chunk.type)) + { + // Read the sub type + try + { + stream->read(&outHeader.subType, sizeof(RiffListHeader) - sizeof(RiffHeader)); + } + catch (const IOException&) + { + return SLANG_FAIL; + } + } - // Skip to the alignment - uint32_t remaining = payloadSize & 3; - if (remaining) + return SLANG_OK; +} + +namespace { // anonymous + +struct DumpVisitor : public RiffContainer::Visitor +{ + typedef RiffContainer::Chunk Chunk; + typedef RiffContainer::ListChunk ListChunk; + typedef RiffContainer::DataChunk DataChunk; + + + // Visitor + virtual SlangResult enterList(ListChunk* list) SLANG_OVERRIDE + { + _dumpIndent(); + // If it's the root it's 'riff' + _dumpRiffType(list == m_rootChunk ? RiffFourCC::kRiff : RiffFourCC::kList); + m_writer.put(" "); + _dumpRiffType(list->m_subType); + m_writer.put("\n"); + m_indent++; + return SLANG_OK; + } + virtual SlangResult handleData(DataChunk* data) SLANG_OVERRIDE + { + _dumpIndent(); + // Write out the name + _dumpRiffType(data->m_type); + m_writer.put(" "); + + + int hash = data->calcHash(); + + // We don't know in general what the contents is or means... but we can display a hash + HexDumpUtil::dump(uint32_t(hash), m_writer.getWriter()); + m_writer.put(" "); + + m_writer.put("\n"); + return SLANG_OK; + } + virtual SlangResult leaveList(ListChunk* list) SLANG_OVERRIDE + { + SLANG_UNUSED(list); + m_indent--; + return SLANG_OK; + } + + DumpVisitor(WriterHelper writer, Chunk* rootChunk) : + m_writer(writer), + m_indent(0), + m_rootChunk(rootChunk) + { + } + + void _dumpIndent() + { + for (int i = 0; i < m_indent; ++i) { - stream->seek(SeekOrigin::Current, 4 - remaining); + m_writer.put(" "); } } - catch (IOException&) + void _dumpRiffType(FourCC fourCC) { - return SLANG_FAIL; + char c[5]; + for (int i = 0; i < 4; ++i) + { + c[i] = char(fourCC); + fourCC >>= 8; + } + c[4] = 0; + m_writer.put(c); + } + + Chunk* m_rootChunk; + + int m_indent; + WriterHelper m_writer; +}; + +} + +/* static */void RiffUtil::dump(RiffContainer::Chunk* chunk, WriterHelper writer) +{ + DumpVisitor visitor(writer, chunk); + chunk->visit(&visitor); +} + +/* static */SlangResult RiffUtil::write(RiffContainer::ListChunk* list, bool isRoot, Stream* stream) +{ + RiffListHeader listHeader; + + listHeader.chunk.type = isRoot ? RiffFourCC::kRiff : RiffFourCC::kList; + listHeader.chunk.size = uint32_t(list->m_payloadSize); + listHeader.subType = list->m_subType; + + try + { + // Write the header + stream->write(&listHeader, sizeof(listHeader)); + + // Write the contained chunks + Chunk* chunk = list->m_containedChunks; + while (chunk) + { + switch (chunk->m_kind) + { + case Chunk::Kind::List: + { + auto listChunk = static_cast<ListChunk*>(chunk); + // It's a container + SLANG_RETURN_ON_FAIL(write(listChunk, false, stream)); + break; + } + case Chunk::Kind::Data: + { + auto dataChunk = static_cast<DataChunk*>(chunk); + + // Must be a regular chunk with data + RiffHeader chunkHeader; + chunkHeader.type = dataChunk->m_type; + chunkHeader.size = uint32_t(dataChunk->m_payloadSize); + + stream->write(&chunkHeader, sizeof(chunkHeader)); + + RiffContainer::Data* data = dataChunk->m_dataList; + while (data) + { + stream->write(data->getPayload(), data->getSize()); + + // Next but of data + data = data->m_next; + } + + // Need to write for alignment + const size_t remainingSize = getPadSize(dataChunk->m_payloadSize) - dataChunk->m_payloadSize; + + if (remainingSize) + { + static const uint8_t trailing[kRiffPadSize] = { 0 }; + stream->write(trailing, remainingSize); + } + } + default: break; + } + + // Next + chunk = chunk->m_next; + } + } + catch (const IOException&) + { + return SLANG_FAIL; } return SLANG_OK; } +/* static */SlangResult RiffUtil::read(Stream* stream, RiffContainer& outContainer) +{ + typedef RiffContainer::ScopeChunk ScopeChunk; + typedef RiffContainer::ScopeChunk ScopeContainer; + outContainer.reset(); + + size_t remaining; + { + RiffListHeader header; + + SLANG_RETURN_ON_FAIL(readHeader(stream, header)); + if (!isListType(header.chunk.type)) + { + return SLANG_FAIL; + } + + remaining = getPadSize(header.chunk.size) - (sizeof(RiffListHeader) - sizeof(RiffHeader)); + outContainer.startChunk(Chunk::Kind::List, header.subType); + } + + List<size_t> remainingStack; + while (true) + { + // It must be the end + if (remaining == 0) + { + // If it's a container then we pop container + outContainer.endChunk(); + if (remainingStack.getCount() <= 0) + { + break; + } + + remaining = remainingStack.getLast(); + remainingStack.removeLast(); + } + else + { + RiffListHeader header; + SLANG_RETURN_ON_FAIL(readHeader(stream, header)); + + // The amount of data can't be larger than what remains + if (header.chunk.size > remaining) + { + return SLANG_FAIL; + } + + if (header.chunk.type == RiffFourCC::kList) + { + if (header.chunk.size & kRiffPadMask) + { + SLANG_ASSERT(!"A list chunk can only have divisible by 2 size"); + return SLANG_FAIL; + } + + // Work out the pad size + const size_t padSize = getPadSize(header.chunk.size); + + // Subtract the size of this chunk from remaining of the current chunk + remaining -= sizeof(RiffHeader) + padSize; + // Push it, for when we hit the end + remainingStack.add(remaining); + + // Work out how much remains in this container + remaining = padSize - (sizeof(RiffListHeader) - sizeof(RiffHeader)); + + // Start a container + outContainer.startChunk(Chunk::Kind::List, header.subType); + } + else + { + ScopeChunk scopeChunk(&outContainer, Chunk::Kind::Data, header.chunk.type); + RiffContainer::Data* data = outContainer.addData(header.chunk.size); + + size_t readSize; + SLANG_RETURN_ON_FAIL(readPayload(stream, header.chunk.size, data->getPayload(), readSize)); + + // All read sizes must end up aligned + SLANG_ASSERT((readSize & kRiffPadMask) == 0); + + // Correct remaining + remaining -= sizeof(RiffHeader) + readSize; + } + } + } + + return outContainer.isFullyConstructed() ? SLANG_OK : SLANG_FAIL; +} + +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! RiffContainer::Chunk !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +SlangResult RiffContainer::Chunk::visit(Visitor* visitor) +{ + switch (m_kind) + { + case Kind::Data: + { + return visitor->handleData(static_cast<DataChunk*>(this)); + } + case Kind::List: + { + auto list = static_cast<ListChunk*>(this); + SLANG_RETURN_ON_FAIL(visitor->enterList(list)); + + Chunk* chunk = list->m_containedChunks; + while (chunk) + { + SLANG_RETURN_ON_FAIL(chunk->visit(visitor)); + + chunk = chunk->m_next; + } + + SLANG_RETURN_ON_FAIL(visitor->leaveList(list)); + return SLANG_OK; + } + default: return SLANG_FAIL; + } +} + +SlangResult RiffContainer::Chunk::visitPreOrder(VisitorCallback callback, void* data) +{ + switch (m_kind) + { + case Kind::Data: + { + return callback(this, data); + } + case Kind::List: + { + auto list = static_cast<ListChunk*>(this); + // Do this containing node first + SLANG_RETURN_ON_FAIL(callback(this, data)); + + // Do the contents next + Chunk* chunk = list->m_containedChunks; + while (chunk) + { + SLANG_RETURN_ON_FAIL(chunk->visitPreOrder(callback, data)); + chunk = chunk->m_next; + } + return SLANG_OK; + } + default: return SLANG_FAIL; + } +} + +SlangResult RiffContainer::Chunk::visitPostOrder(VisitorCallback callback, void* data) +{ + switch (m_kind) + { + case Kind::Data: + { + return callback(this, data); + } + case Kind::List: + { + auto list = static_cast<ListChunk*>(this); + + // Do the contents first + Chunk* chunk = list->m_containedChunks; + while (chunk) + { + SLANG_RETURN_ON_FAIL(chunk->visitPostOrder(callback, data)); + chunk = chunk->m_next; + } + // Then the list node (so a post order) + SLANG_RETURN_ON_FAIL(callback(this, data)); + return SLANG_OK; + } + default: return SLANG_FAIL; + } +} + +size_t RiffContainer::Chunk::calcPayloadSize() +{ + switch (m_kind) + { + case Kind::Data: return static_cast<DataChunk*>(this)->calcPayloadSize(); + case Kind::List: return static_cast<ListChunk*>(this)->calcPayloadSize(); + default: return 0; + } +} + +RiffContainer::Data* RiffContainer::Chunk::getSingleData() const +{ + return (m_kind == Kind::Data) ? static_cast<const DataChunk*>(this)->getSingleData(): nullptr; +} + +// !!!!!!!!!!!!!!!!!!!!!!!!!!! RiffContainer::ListChunk !!!!!!!!!!!!!!!!!!!!!! + +size_t RiffContainer::ListChunk::calcPayloadSize() +{ + // Have to include the part of the header not taken up by the RiffHeader + size_t size = sizeof(RiffListHeader) - sizeof(RiffHeader); + Chunk* chunk = m_containedChunks; + while (chunk) + { + size_t chunkSize = chunk->m_payloadSize + sizeof(RiffHeader); + // Align the contained chunk size + size += RiffUtil::getPadSize(chunkSize); + + chunk = chunk->m_next; + } + return size; +} + +RiffContainer::Chunk* RiffContainer::ListChunk::findContained(FourCC type) const +{ + Chunk* chunk = m_containedChunks; + while (chunk) + { + const FourCC checkType = (chunk->m_kind == Chunk::Kind::Data) ? + static_cast<RiffContainer::DataChunk*>(chunk)->m_type : + static_cast<RiffContainer::ListChunk*>(chunk)->m_subType; + if (checkType == type) + { + return chunk; + } + chunk = chunk->m_next; + } + return nullptr; +} + +RiffContainer::Data* RiffContainer::ListChunk::findContainedData(FourCC type) const +{ + Chunk* found = findContained(type); + if (found && found->m_kind == Kind::Data) + { + DataChunk* dataChunk = static_cast<DataChunk*>(found); + // Assumes that there is a single data chunk + + Data* data = dataChunk->m_dataList; + if (data && data->m_next == nullptr) + { + return data; + } + } + return nullptr; +} + +void* RiffContainer::ListChunk::findContainedData(FourCC type, size_t minSize) const +{ + Data* data = findContainedData(type); + return (data && data->m_size >= minSize) ? data->getPayload() : nullptr; +} + +static RiffContainer::ListChunk* _findListRec(RiffContainer::ListChunk* list, FourCC subType) +{ + RiffContainer::Chunk* chunk = list->m_containedChunks; + while (chunk) + { + if (auto childList = as<RiffContainer::ListChunk>(chunk)) + { + // Test if the child is the subtype, if so we are done + if (childList->m_subType == subType) + { + return childList; + } + auto found = _findListRec(childList, subType); + if (found) + { + return found; + } + } + chunk = chunk->m_next; + } + return nullptr; +} + +/* static */RiffContainer::ListChunk* RiffContainer::ListChunk::findListRec(FourCC subType) +{ + return (m_subType == subType) ? this : _findListRec(this, subType); +} + +// !!!!!!!!!!!!!!!!!!!!!!!!!!! RiffContainer::DataChunk !!!!!!!!!!!!!!!!!!!!!! + +RiffContainer::Data* RiffContainer::DataChunk::getSingleData() const +{ + if (m_kind == Kind::Data) + { + auto dataChunk = static_cast<const DataChunk*>(this); + Data* data = dataChunk->m_dataList; + if (data && data->m_next == nullptr) + { + return data; + } + } + return nullptr; +} + +int RiffContainer::DataChunk::calcHash() const +{ + int hash = 0; + + Data* data = m_dataList; + while (data) + { + // This is a little contrived (in that we don't use the function GetHashCode), but the + // reason to be careful is we want the same result however many Data blocks there are. + const char* buffer = (const char*)data->getPayload(); + const size_t size = data->getSize(); + + for (size_t i = 0; i < size; ++i) + { + hash = int(buffer[i]) + (hash << 6) + (hash << 16) - hash; + } + + data = data->m_next; + } + + return hash; +} + +size_t RiffContainer::DataChunk::calcPayloadSize() const +{ + size_t size = 0; + Data* data = m_dataList; + while (data) + { + size += data->getSize(); + data = data->m_next; + } + return size; +} + +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! RiffContainer !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +RiffContainer::RiffContainer() : + m_arena(4096) +{ + m_rootList = nullptr; + m_listChunk = nullptr; + m_dataChunk = nullptr; +} + +void RiffContainer::reset() +{ + m_arena.reset(); + + m_rootList = nullptr; + m_listChunk = nullptr; + m_dataChunk = nullptr; +} + +RiffContainer::ListChunk* RiffContainer::_newListChunk(FourCC subType) +{ + SLANG_ASSERT(!RiffUtil::isListType(subType)); + + ListChunk* chunk = (ListChunk*)m_arena.allocate(sizeof(ListChunk)); + chunk->init(subType); + return chunk; +} + +RiffContainer::DataChunk* RiffContainer::_newDataChunk(FourCC type) +{ + SLANG_ASSERT(!RiffUtil::isListType(type)); + + DataChunk* chunk = (DataChunk*)m_arena.allocate(sizeof(DataChunk)); + chunk->init(type); + return chunk; +} + +void RiffContainer::_addChunk(Chunk* chunk) +{ + if (m_listChunk) + { + chunk->m_parent = m_listChunk; + Chunk*& next = m_listChunk->m_endChunk ? m_listChunk->m_endChunk->m_next : m_listChunk->m_containedChunks; + SLANG_ASSERT(next == nullptr); + next = chunk; + m_listChunk->m_endChunk = chunk; + } +} + +void RiffContainer::startChunk(Chunk::Kind kind, FourCC fourCC) +{ + SLANG_ASSERT(m_listChunk || m_rootList == nullptr); + + switch (kind) + { + case Chunk::Kind::Data: + { + // We can only start a data chunk if we are in a container, and we can't already be in data chunk + SLANG_ASSERT(m_listChunk && m_dataChunk == nullptr); + + DataChunk* chunk = _newDataChunk(fourCC); + _addChunk(chunk); + m_dataChunk = chunk; + break; + } + case Chunk::Kind::List: + { + // We can't be in a data chunk + SLANG_ASSERT(m_dataChunk == nullptr); + + ListChunk* list = _newListChunk(fourCC); + + // If this is the first, make it the root + if (!m_rootList) + { + m_rootList = list; + } + + _addChunk(list); + + m_listChunk = list; + break; + } + } +} + +void RiffContainer::endChunk() +{ + size_t chunkPayloadSize; + + // The chunk we are popping + Chunk* chunk = nullptr; + + ListChunk* parent; + if (m_dataChunk) + { + chunk = m_dataChunk; + + parent = m_dataChunk->m_parent; + chunkPayloadSize = m_dataChunk->m_payloadSize; + + m_dataChunk = nullptr; + } + else + { + chunk = m_listChunk; + + SLANG_ASSERT(m_listChunk && m_dataChunk == nullptr); + parent = m_listChunk->m_parent; + chunkPayloadSize = m_listChunk->m_payloadSize; + } + + m_listChunk = parent; + + if (parent) + { + // Fix the size taking into account padding bytes requirement + chunkPayloadSize = RiffUtil::getPadSize(chunkPayloadSize); + // Update the parents size + parent->m_payloadSize += sizeof(RiffHeader) + chunkPayloadSize; + } + + // Check it's size seems ok + SLANG_ASSERT(isChunkOk(chunk)); +} + +RiffContainer::Data* RiffContainer::addData(size_t size) +{ + // We must be in a chunk + SLANG_ASSERT(m_dataChunk); + + // Add current chunks data + m_dataChunk->m_payloadSize += size; + + Data* data = (Data*)m_arena.allocate(sizeof(Data) + size); + + data->m_next = nullptr; + data->m_size = size; + + Data*& next = m_dataChunk->m_endData ? m_dataChunk->m_endData->m_next : m_dataChunk->m_dataList; + SLANG_ASSERT(next == nullptr); + + // Add to linked list + next = data; + // Make this the new end + m_dataChunk->m_endData = data; + return data; +} + +void RiffContainer::write(const void* inData, size_t size) +{ + auto data = addData(size); + ::memcpy(data->getPayload(), inData, size); +} + +static SlangResult _isChunkOk(RiffContainer::Chunk* chunk, void* data) +{ + SLANG_UNUSED(data); + return chunk->calcPayloadSize() == chunk->m_payloadSize ? SLANG_OK : SLANG_FAIL; +} + +/* static */bool RiffContainer::isChunkOk(Chunk* chunk) +{ + return SLANG_SUCCEEDED(chunk->visitPostOrder(&_isChunkOk, nullptr)); +} + +static SlangResult _calcAndSetSize(RiffContainer::Chunk* chunk, void* data) +{ + SLANG_UNUSED(data); + chunk->m_payloadSize = chunk->calcPayloadSize(); + return SLANG_OK; +} + +/* static */void RiffContainer::calcAndSetSize(Chunk* chunk) +{ + chunk->visitPostOrder(&_calcAndSetSize, nullptr); +} + + + } |
