https://github.com/shader-slang/slang
Tip revision: 26e25f6e73a9808ad93c98ad34907e3a27b6726c authored by Yong He on 19 March 2024, 00:21:48 UTC
Check for cylic types. (#3790)
Check for cylic types. (#3790)
Tip revision: 26e25f6
slang-riff.h
#ifndef SLANG_RIFF_H
#define SLANG_RIFF_H
#include "slang-basic.h"
#include "slang-stream.h"
#include "slang-memory-arena.h"
#include "slang-writer.h"
#include "slang-semantic-version.h"
namespace Slang
{
// http://fileformats.archiveteam.org/wiki/RIFF
// http://www.fileformat.info/format/riff/egff.htm
typedef uint32_t FourCC;
/* Use of macros to construct and extract from FourCC means the FourCC ordering can be fixed for endian differences. */
#if SLANG_LITTLE_ENDIAN
#define SLANG_FOUR_CC(c0, c1, c2, c3) ((FourCC(c0) << 0) | (FourCC(c1) << 8) | (FourCC(c2) << 16) | (FourCC(c3) << 24))
#define SLANG_FOUR_CC_GET_FIRST_CHAR(x) char((x) & 0xff)
#define SLANG_FOUR_CC_REPLACE_FIRST_CHAR(x, c) (((x) & 0xffffff00) | FourCC(c))
#else
#define SLANG_FOUR_CC(c0, c1, c2, c3) ((FourCC(c0) << 24) | (FourCC(c1) << 16) | (FourCC(c2) << 8) | (FourCC(c3) << 0))
#define SLANG_FOUR_CC_GET_FIRST_CHAR(x) char((x) >> 24)
#define SLANG_FOUR_CC_REPLACE_FIRST_CHAR(x, c) (((x) & 0x00ffffff) | (FourCC(c) << 24))
#endif
enum
{
kRiffPadSize = 2, ///< We only align to 2 bytes
kRiffPadMask = kRiffPadSize - 1,
};
// Uses it's own version of a hash
typedef int RiffHashCode;
struct RiffHeader
{
FourCC type; ///< The FourCC code that identifies this chunk
uint32_t size; ///< Size does *NOT* include the riff chunk size. The size can be byte sized, but on storage it will always be treated as aligned up by 4.
};
struct RiffListHeader
{
RiffHeader chunk;
FourCC subType;
// This is then followed by the contained subchunk/s
};
struct RiffFourCC
{
/// A 'riff' is the high level file container. It is followed by a subtype and then the contained chunks.
static const FourCC kRiff = SLANG_FOUR_CC('R', 'I', 'F', 'F');
/// A list is the same as a 'riff' except can be placed anywhere in hierarchy.
static const FourCC kList = SLANG_FOUR_CC('L', 'I', 'S', 'T');
private:
RiffFourCC() = delete;
};
// Follows semantic version rules
// https://semver.org/
//
// major.minor.patch
// Patch versions indicate a change.
// Minor means a change that is backwards compatible with previous minor versions. A step in minor and/or major zeros patch.
// Major means a non compatible change. A step in major, zeros minor and patch.
struct RiffSemanticVersion
{
typedef RiffSemanticVersion ThisType;
typedef uint32_t RawType;
/// ==
bool operator==(const ThisType& rhs) const { return m_raw == rhs.m_raw; }
bool operator!=(const ThisType& rhs) const { return !(*this == rhs); }
/// A patch change indices a different version but does not change the compatibility of the format
int getPatch() const { return m_raw & 0xff; }
/// A minor change implies a format change that is backwards compatible
int getMinor() const { return (m_raw >> 8) & 0xff; }
/// A major change is binary incompatible by default
int getMajor() const { return (m_raw >> 16); }
SemanticVersion asSemanticVersion() const
{
return SemanticVersion(getMajor(), getMinor(), getPatch());
}
static RawType makeRaw(int major, int minor, int patch)
{
SLANG_ASSERT((major | minor | patch) >= 0);
SLANG_ASSERT(major < 0x10000 && minor < 0x100 && patch < 0x100);
return (RawType(major) << 16) | (RawType(minor) << 8) | RawType(patch);
}
static RiffSemanticVersion makeFromRaw(RawType raw)
{
ThisType version;
version.m_raw = raw;
return version;
}
static RiffSemanticVersion make(int major, int minor, int patch) { return makeFromRaw(makeRaw(major, minor, patch)); }
static RiffSemanticVersion make(const SemanticVersion& in) { return makeFromRaw(makeRaw(in.m_major, in.m_minor, in.m_patch)); }
/// True if the read version is compatible with the current version, based on semantic rules.
static bool areCompatible(const ThisType& currentVersion, const ThisType& readVersion)
{
const RawType currentRaw = currentVersion.m_raw;
const RawType readRaw = readVersion.m_raw;
// Must have same major version.
// For minor version, the read version must be less than or equal.
return ((currentRaw & 0xffff0000) == (readRaw & 0xffff0000)) && ((currentRaw & 0xff00) >= (readRaw & 0xff00));
}
RawType m_raw;
};
/* A helper class that makes reading data from a data block simpler */
class RiffReadHelper
{
public:
template <typename T>
SlangResult read(T& out)
{
if (m_cur + sizeof(T) > m_end)
{
return SLANG_FAIL;
}
// TODO: consider whether this type should enforce alignment.
// SLANG_ASSERT((size_t(m_cur) & (SLANG_ALIGN_OF(T) - 1)) == 0);
::memcpy(&out, m_cur, sizeof(T));
m_cur += sizeof(T);
return SLANG_OK;
}
/// Get the data
const uint8_t* getData() const { return m_cur; }
/// Get the remaining size
size_t getRemainingSize() const { return size_t(m_end - m_cur); }
RiffReadHelper(const uint8_t* data, size_t size):
m_start(data),
m_end(data + size),
m_cur(data)
{
}
SlangResult skip(size_t size)
{
if (m_cur + size > m_end)
{
return SLANG_FAIL;
}
m_cur += size;
return SLANG_OK;
}
protected:
const uint8_t* m_start;
const uint8_t* m_end;
const uint8_t* m_cur;
};
/* A container for data in RIFF format. Holds the contents in memory.
With the data held in memory allows for adding or removing chunks at will.
A future implementation does not necessarily have to be backed by memory when construction,
as data could be written to stream, and the chunk sizes written by seeking back over the file and setting the value.
In normal usage the chunk sizes are calculated during construction. If the structure is changed, the sizes may
need to be recalculated, before serialization.
*/
class RiffContainer
{
public:
// This alignment is only made for arena based allocations.
// For external blocks it's client code to have appropriate alignment.
// This is needed because when reading a RiffContainer, all allocation is arena based, and
// if the payload contains 8 byte aligned data, the overall payload needs to be 8 byte aligned.
static const size_t kPayloadMinAlignment = 8;
enum class Ownership
{
Uninitialized, ///< Doesn't contain anything
NotOwned, ///< It's not owned by the container
Arena, ///< It's owned and allocated on the arena
Owned, ///< It's owned, but wasn't allocated on the arena
};
struct Data
{
/// Get the payload
void* getPayload() { return m_payload; }
/// Get the end pointer
void* getPayloadEnd() { return (void*)((uint8_t*)m_payload + m_size); }
/// Get the size of the payload
size_t getSize() const { return m_size; }
/// Get the ownership of the data held in the payload
Ownership getOwnership() const { return m_ownership; }
void init()
{
m_ownership = Ownership::Uninitialized;
m_size = 0;
m_next = nullptr;
m_payload = nullptr;
}
Ownership m_ownership; ///< Stores the ownership of the payload
size_t m_size; ///< The size of the payload
void* m_payload; ///< The payload
Data* m_next; ///< The next Data block in the list
};
struct Chunk;
struct ListChunk;
struct DataChunk;
typedef SlangResult(*VisitorCallback)(Chunk* chunk, void* data);
class Visitor;
struct Chunk
{
enum class Kind
{
List, ///< Strictly speaking this can be a 'LIST' or a 'RIFF' as they have the same structure
Data,
};
void init(Kind kind, FourCC fourCC)
{
m_kind = kind;
m_fourCC = fourCC;
m_payloadSize = 0;
m_next = nullptr;
m_parent = nullptr;
}
SlangResult visit(Visitor* visitor);
SlangResult visitPostOrder(VisitorCallback callback, void* data);
SlangResult visitPreOrder(VisitorCallback callback, void* data);
/// Returns a single data chunk
Data* getSingleData() const;
/// Calculate the payload size
size_t calcPayloadSize();
Kind m_kind; ///< Kind of chunk
FourCC m_fourCC; ///< The chunk type for data, or the sub type for a List (riff/list)
size_t m_payloadSize; ///< The payload size (ie does NOT include RiffChunk header).
Chunk* m_next; ///< Next chunk in this list
ListChunk* m_parent; ///< The chunk this belongs to
};
struct ListChunk : public Chunk
{
typedef Chunk Super;
SLANG_FORCE_INLINE static bool isType(const Chunk* chunk) { return chunk->m_kind == Kind::List; }
void init(FourCC subType)
{
Super::init(Kind::List, subType);
m_containedChunks = nullptr;
m_endChunk = nullptr;
m_payloadSize = uint32_t(sizeof(RiffListHeader) - sizeof(RiffHeader));
}
/// Finds chunk (list or data) that matches type. For List/Riff, type is the subtype
Chunk* findContained(FourCC type) const;
void* findContainedData(FourCC type, size_t minSize) const;
ListChunk* findContainedList(FourCC type);
/// Finds the contained data. NOTE! Assumes that there is only as single data block, and will return nullptr if there is not
Data* findContainedData(FourCC type) const;
template <typename T>
T* findContainedData(FourCC type) const { return (T*)findContainedData(type, sizeof(T)); }
/// Find all contained that match the type
void findContained(FourCC type, List<ListChunk*>& out);
/// Find all contained that match the type
void findContained(FourCC type, List<DataChunk*>& out);
/// Find the list (including self) that matches subtype recursively
ListChunk* findListRec(FourCC subType);
/// NOTE! Assumes all contained chunks have correct payload sizes
size_t calcPayloadSize();
/// Get the sub type
FourCC getSubType() const { return m_fourCC; }
/// A singly linked list of contained chunks directly contained in this chunk
Chunk* getFirstContainedChunk() const { return m_containedChunks; }
Chunk* m_containedChunks; ///< The contained chunks
Chunk* m_endChunk; ///< The last chunk (only set when pushed, and used when popped)
};
struct DataChunk : public Chunk
{
typedef Chunk Super;
SLANG_FORCE_INLINE static bool isType(const Chunk* chunk) { return chunk->m_kind == Kind::Data; }
/// Calculate a hash (not necessarily very fast)
RiffHashCode calcHash() const;
/// Calculate the payload size
size_t calcPayloadSize() const;
/// Copy the payload to dst. Dst must be at least the payload size.
void getPayload(void* dst) const;
/// True if payloads contents is equal to data
bool isEqual(const void* data, size_t count) const;
/// Get single data payload.
Data* getSingleData() const;
/// Return as read helper
RiffReadHelper asReadHelper() const;
void init(FourCC fourCC)
{
Super::init(Kind::Data, fourCC);
m_dataList = nullptr;
m_endData = nullptr;
}
Data* m_dataList; ///< List of 0 or more data items
Data* m_endData; ///< The last data point
};
class ScopeChunk
{
public:
ScopeChunk(RiffContainer* container, Chunk::Kind kind, FourCC fourCC) :
m_container(container)
{
container->startChunk(kind, fourCC);
}
~ScopeChunk()
{
m_container->endChunk();
}
private:
RiffContainer* m_container;
};
class Visitor
{
public:
virtual SlangResult enterList(ListChunk* list) = 0;
virtual SlangResult handleData(DataChunk* data) = 0;
virtual SlangResult leaveList(ListChunk* list) = 0;
};
/// Add a complete data chunk
void addDataChunk(FourCC dataFourCC, const void* data, size_t dataSizeInBytes);
/// Start a chunk
void startChunk(Chunk::Kind kind, FourCC type);
/// Write data into a chunk (can only be inside a Kind::Data)
void write(const void* data, size_t size);
/// Adds an empty data block
Data* addData();
/// Set the payload on a data. Payload can be passed as nullptr, if it is no memory will be copied.
void setPayload(Data* data, const void* payload, size_t size);
/// Move ownership to.
/// NOTE! The payload *must* be deallocatable via 'free'
void moveOwned(Data* data, void* payload, size_t size);
/// Move unowned. The payload scope must last longer than the RiffContainer
void setUnowned(Data* data, void* payload, size_t size);
/// End a chunk
void endChunk();
/// Get the root
ListChunk* getRoot() const { return m_rootList; }
/// Get the current chunk
Chunk* getCurrentChunk() { return m_dataChunk ? static_cast<Chunk*>(m_dataChunk) : static_cast<Chunk*>(m_listChunk); }
/// Reset the container
void reset();
/// true if has a root container, and nothing remains open
bool isFullyConstructed() { return m_rootList && m_listChunk == nullptr && m_dataChunk == nullptr; }
/// Makes a data chunk contain a single contiguous data block
Data* makeSingleData(DataChunk* dataChunk);
/// Get the memory arena that is backing the storage of data
MemoryArena& getMemoryArena() { return m_arena; }
/// The if the list and sublists appear correct
static bool isChunkOk(Chunk* chunk);
/// Traverses over chunk hierarchy and sets the sizes
static void calcAndSetSize(Chunk* chunk);
/// Ctor
RiffContainer();
protected:
void _addChunk(Chunk* chunk);
ListChunk* _newListChunk(FourCC subType);
DataChunk* _newDataChunk(FourCC type);
ListChunk* m_rootList; ///< Root list
ListChunk* m_listChunk;
DataChunk* m_dataChunk;
MemoryArena m_arena; ///< Can be used to use other owned blocks
};
// -----------------------------------------------------------------------------
template <typename T>
T* as(RiffContainer::Chunk* chunk)
{
return chunk && T::isType(chunk) ? static_cast<T*>(chunk) : nullptr;
}
// -----------------------------------------------------------------------------
template <typename T>
T* as(RiffContainer::Chunk* chunk, FourCC fourCC)
{
return chunk && chunk->m_fourCC == fourCC && T::isType(chunk) ? static_cast<T*>(chunk) : nullptr;
}
struct RiffUtil
{
typedef RiffContainer::Chunk Chunk;
typedef RiffContainer::ListChunk ListChunk;
typedef RiffContainer::DataChunk DataChunk;
static int64_t calcChunkTotalSize(const RiffHeader& chunk);
static SlangResult skip(const RiffHeader& chunk, Stream* stream, int64_t* remainingBytesInOut);
static SlangResult readChunk(Stream* stream, RiffHeader& outChunk);
static SlangResult writeData(const RiffHeader* header, size_t headerSize, const void* payload, size_t payloadSize, Stream* out);
static SlangResult readData(Stream* stream, RiffHeader* outHeader, size_t headerSize, List<uint8_t>& data);
static SlangResult readPayload(Stream* stream, size_t size, void* outData, size_t& outReadSize);
/// Read a header. Handles special case of list/riff types
static SlangResult readHeader(Stream* stream, RiffListHeader& outHeader);
/// True if the type is a container type
static bool isListType(FourCC type) { return type == RiffFourCC::kRiff || type == RiffFourCC::kList; }
/// Dump the chunk structure
static void dump(Chunk* chunk, WriterHelper writer);
/// Get the size taking into account padding
static size_t getPadSize(size_t in) { return (in + kRiffPadMask) & ~size_t(kRiffPadMask); }
/// Write a chunk list and contents to a stream
static SlangResult write(ListChunk* listChunk, bool isRoot, Stream* stream);
/// Write a container to the stream
static SlangResult write(RiffContainer* container, Stream* stream);
/// Read the stream into the container
static SlangResult read(Stream* stream, RiffContainer& outContainer);
};
}
#endif