Nicolas Fleury

Download Report

Transcript Nicolas Fleury

Feeding the Monster
Advanced Data Packaging for
Consoles
Bruno Champoux
Nicolas Fleury
Outline





Next-Generation Loading Problems
LIP: A Loading Solution
Packaging C++ Objects
Demo: LIP Data Viewer
Questions
Some things never change…
Optical
Disc
RAM
...since 1992!
...since 1992!
Next-Gen Loading Problem
 Processing power up by 10-40X
 Memory size up by 8-16X
 Optical drive performance up by 1-4X
Next-Gen Loading Problem
 Xbox 360
 12X dual-layer DVD drive
 Outer edge speed: 15 MB/s
 Average seek: 115 ms
 PlayStation 3




Blu-ray performance still unknown
1.5X is the most likely choice
CAV drive should give 6 to 16 MB/s
Average seek time might be worse than DVD
Next-Gen Loading Problem
Maximum
Bandwidth
4.4 MB/s
Time to fill
PS2
Memory
Size
32 MB
PS3
192 MB*
16 MB/s
12 s
Xbox
64 MB
6.9 MB/s
9.3 s
Xbox 360
480 MB**
15 MB/s
32 s
7.3 s
Next-Gen Loading Problem
 In order to feed the next-gen data needs,
loading will need to be more frequent
 Hard drives are optional for PS3 and
Xbox 360
 Optical drive performance does not scale
with the memory/CPU power increase
 Conclusion:
 Loading performance must be optimal; any
processing other than raw disc transfers must
be eliminated
Did I Hear “Loading Screen”?




Disruptive
Boring as hell
Non-skippable cutscenes are not better!
Conclusion:
 Loading screens must not survive the current
generation
Background Loading
 Game assets are loaded during
gameplay
 Player immersion is preserved
 Solution:
 Use blocking I/O in a thread or another
processor
Background Loading
 Requirements:
 Cannot be much slower than a loading
screen
 Must have low CPU overhead
 Must not block other streams
 Conclusion:
 Once again, loading performance must be
optimal; any processing other than raw disc
transfers must be eliminated
Proposing A Solution
 Requirements for a next-generation
packaging and loading solution:
 Large amounts of assets must be loaded at
speeds nearing the hardware transfer limit
 Background loading must be possible at little
CPU cost
 Data assets must be streamed in and phased
out without causing memory fragmentation
Understanding Loading Times
 Freeing memory space
 Unloading
 Defragmenting






Seek time
Read time
Allocations
Parsing
Relocation (pointers, hash ID lookups)
Registration (e.g. physics system)
Reducing Loading Times
 Always load compressed files
 Using N:1 compression will load N times
faster
 Double-buffering hides decompression time
 Plenty of processing power available for
decompression on next-gen consoles
Reducing Loading Times
 Compression algorithm choice
 Favor incremental approach
 Use an algorithm based on bytes, not bits
 Lempel-Ziv family
 LZO
Reducing Loading Times
 Take advantage of spatial and game flow
coherence
 Batch related data together in one file to save
seek time
 Place related files next to each other on the
disc to minimize seek time
Reducing Loading Times
 Take advantage of optical disc features
 Store frequently accessed data in the outer
section of the disc
 Store music streams in the middle (prevents
full seek)
 Store single-use data near the center
(videos, cutscenes, engine executable)
 Beware of layer switching (0.1 seconds
penalty)
Reducing Loading Times
 Use the “flyweight” design pattern
 Geometry instancing
 Animation sharing
 Favor procedural techniques
 Parametric surfaces
 Textures (fire, smoke, water)
Reducing Loading Times
 Always prepare data offline
 Eliminate text or intermediate format parsing
in the engine
 Engine time spent converting or interpreting
data is wasted
 Load native hardware and middleware
formats
 Load C++ objects directly
Why Load C++ Objects?
 More natural way to work with data
 Removes any need for parsing or
interpreting assets
 Creation is inexpensive
 Pointer relocation
 Hash ID conversion
 Object registration
Loading C++ Objects
 Requires a very smart packaging system





Member pointers
Virtual tables
Base classes
Alignment issues
Endianness
Loading Non-C++ Objects
 Must be in a format that is ready to use
after being read to memory




Texture/normal maps
Havok structures
Audio
Script bytecode
 Pretty straightforward
Load-In-Place (LIP)
 Our solution for
packaging and loading
game assets
 Framework for defining,
storing and loading
native C++ objects
 Dynamic Storage: a
self-defragmenting
game asset container
Maya
Exporter
Level Editor
LIP
Generator
LIP Packaging
Game
Assets
LIP Loading
Dynamic
Storage
Engine
Load-In-Place: “LIP Item”
 1 LIP item  1 game asset
 1 LIP item  unique hash ID (64-bit)
 32 bits for the type ID and properties
 32 bits for the hashed asset name (CRC-32)
 The smallest unit of data that can be
 queried
 moved by defragmentation
 unloaded
 Supports both C++ objects and binary
blocks
Examples of LIP Items








Joint Animation
Character Model
Environment Model Section
Collision Floor Section
Game Object (hero, enemy, trigger, etc.)
Script
Particle Emitter
Texture
C++-Based LIP Items
 Can be made of any number of C++
objects and arrays
 On the disc, all internal pointers are kept
relative to the LIP item block
 Pointer relocation starts with a placement
new on a “relocation constructor”
 Internal pointers are relocated automatically
through “constructor chaining”
Placement “new” Operator
 Syntax
 new(<address>) <type>;
 Calls the constructor but does not
allocate memory
 Initializes the virtual table
 Called once for each LIP item on the
main class relocation constructor
Relocation Constructors
 Required by all classes and structures that
 can get loaded by the LIP framework
 contain members that require relocation
 3 constructors
 Loading relocation constructor
 Moving relocation constructor (defragmentation)
 Dynamic constructor (optional, can be dummy)
 No default constructor!
Object Members Relocation
 Internal pointer
 Must point within the LIP item block
 Converted into absolute pointer
 External reference (LIP items only)
 Stored as a LIP item hash ID
 Converted into a pointer in the global asset
table entry that points to the referenced LIP
item
 LIP framework provides wrapper classes
with appropriate constructors for all pointer
types
Relocation Example
class GameObject {
public:
GameObject(const LoadContext& ctx);
GameObject(const MoveContext& ctx);
GameObject(HASHID id,
Script* pScript);
protected:
lip::RelocPtr<Transfo> mpLocation;
lip::LipItemPtr<Script> mpScript;
};
Relocation Example (cont’d)
GameObject::GameObject(const LoadContext& ctx) :
mpLocation(ctx),
mpScript(ctx) {}
GameObject::GameObject(const MoveContext& ctx) :
mpLocation(ctx),
mpScript(ctx) {}
GameObject::GameObject(HASHID id,
Script* pScript) :
mpLocation(new Transfo),
mpScript(pScript) { SetHashId(id); }
Relocation Example (cont’d)
template<typename LipItemT>
void PlacementNew(
lip::LoadContext& loadCtx)
{
new(loadCtx.pvBaseAddr)
LipItemT(loadCtx);
}
loadCtx.pvBaseAddr = pvLoadMemory;
PlacementNew<GameObject>(loadCtx);
Relocation Example (cont’d)
Placement new
GameObject
Constructors
RelocPtr<Transfo>
LipItemPtr<Script>
Placement new
hash ID lookup
Transfo
Script
Constructors
Load-In-Place: “Load Unit”
 Group of LIP items
 The smallest unit of data that can be
loaded
 1 load unit  1 load command
 Number of files is minimized
 1 language-independent file
 Models, animations, scripts, environments, …
 N language-dependent files
 Fonts, in-game text, some textures, audio, …
 Load unit files are compressed
Load Unit Table
 Each LIP item has an entry in the table
 Hash ID
 Offset to LIP Item
Table
LIP items
Dynamic Storage
 Loading process
 Load unit files are read and decompressed to
available storage memory
 Load unit table offsets are relocated
 Load unit table entries are merged in the
global asset table
 A placement new is called for each LIP item
 Some LIP item types may require a second
initialization pass (e.g. registration)
Dynamic Storage
 Unloading process
 Each LIP item can be removed individually
 All LIP items of a load unit can be removed
together
 Destructors are called on C++ LIP items
 Dynamic storage algorithm will defragment
the new holes later
 Locking
 LIP items can be locked
 Locked items cannot be moved or unloaded
Platform-Specific Issues
 GameCube
 Special ARAM load unit files
 Animations
 Collision floors
 Small disc  compression
 Xbox/Xbox 360
 Special LIP items for DirectX buffers
 Vertex, index and texture buffers
 4KB-aligned LIP items (binary blocks)
 Buffer headers in separate LIP items (C++ objects)
Load-In-Place: Other Uses
 Network-based asset editing
 LIP items can be transferred from and to our
level editor during gameplay
 Changes in asset sizes do not matter
 Used by Maya exporters to store our
intermediate art assets
 LIP is much more efficient than parsing XML!
Packaging C++ Objects
Nicolas Fleury
Our Previous Python-Based
System
 class MyClass(LipObject):
x = Member(UInt32)
y = Member(UInt32, default=1)
p = Member(makeArrayType(
makePtrType(SomeClass)))
Cool Things with this System




Not too complex to implement.
Python is easy to use.
Introspection support.
A lot of freedom in corresponding C++
classes.
Problems with this System
 Python and C++ structures must be
synchronized.
 Exporters must be written, at least partly,
in Python.
 Validations limited (unless you parse C++
code).
 We just invented a Python/C++ hybrid.
C++-based system
 class MyClass : public MyBaseCls {
...
LIP_DECLARE_REGISTER(MyClass);
uint32 x;
};
 // In .cpp
LIP_DEFINE_REGISTER(MyClass) {
LIP_REGISTER_BASE_CLASS(MyBaseCls);
LIP_REGISTER_MEMBER(x);
}
Consequences
 Exporters are now written in C++.
 Class content written twice, but
synchronization fully validated.
 Dummy engine .DLL must be compiled
(not a working engine, provides only
reflection/introspection).
 Need a good build system.
 We just added reflection/introspection to
C++.
Member Registration
Information




Name
Offset
Type
Special flags (exposed in level editor,
etc.)
(Non Empty) Base Class
Registration Information
 Name
 Type
 Offset; calculated with:
 (size_t)(BaseClassType*)(SubClassType*)1 - 1
Member Type Deduction
 In IntrospectorBase class:
template <
typename TypeT, typename MemberT>
void RegisterMember(
const char* name,
MemberT(TypeT::*memberPtr));
Member Type Deduction
(Arrays)
 In IntrospectorBase class:
template <
typename TypeT,
typename MemberTypeT, int sizeT>
void RegisterMember(
const char* name,
MemberT(TypeT::*memberPtr)[sizeT]);
Needed Information in Tools
 Every class base class (to write their
members too).
 Every class member.
 Every base class offset (to detect missing
base class registration).
 Every member name, size, type and
special flags.
 For every type, the necessary alignment
and if it is polymorphic.
Introspection Classes
(Members)
IntrospectorBase
MemberInfoBase
TypeT
TypeT
Introspector
MemberInfoTypedBase
1
*
TypeT, MemberTypeT, sizeT:int
ArrayMemberInfo
MemberInfo
TypeT, MemberTypeT
Introspection Classes
(Base Classes)
IntrospectorBase
BaseClassInfoBase
*
TypeT
SubClassTypeT
Introspector
BaseClassTypedInfoBase
1
SubClassTypeT, BaseClassTypeT
BaseClassInfo
Result: Member Introspection
 Able to know all types and their
members.
 Can be used for both writing and reading
binary data.
 Same class used in tools to fill the data
as in engine.
LipViewer
 Data of any platform, endianness, pointer
size (binary files have a header with
platform id).
 Both for engine data and tools binary
formats.
 Hexadecimal viewer integration, edition
support.
 Excellent learning and debugging tool.
LipViewer Demo
Restrictions for Simplification
of Implementation
 Polymorphic types must begin with a
vtable pointer (their first non-empty base
class must be polymorphic).
 Can’t inherit twice from same class
indirectly (or offset trick doesn’t work).
 No virtual base classes.
 All padding is explicit.
Explicit Padding
class MyClass {
...
LIP_PADDING(mP1, LIP_PS3(12));
uint16 mSomeMember;
lip::Padding<4> mP2;
uint32 mSomeOtherMember;
LIP_PADDING(mP3,LIP_PC(4) LIP_PS3(8));
...
};
Particular Things to Handle




Endianness.
64 bits pointers (no more!).
VTable padding.
Type alignment.
VTable Padding
 __declspec(align(16)) class Matrix {…};
vtable
vtable
 class MyClass {
x
uint32 x, y, z;
y
Matrix m;
z
x
};
y
m
 32 bytes on PS3,
z
48 bytes on PC.
m
Automatic Versioning
 Create a huge string with member
names/types and member names/types
of pointed classes.
 In the case of polymorphic pointers, all
sub-types must also be included.
 Hash the huge string.
 Can be integrated in tools dependency
tree mechanism.
Needed Information in Engine
 A hash map of objects to do the
placement new of the appropriate type.
 Smart pointers/arrays handle the rest.
Type Ids
 VTable pointers are replaced by a type id.
 LIP_DECLARE_TYPE_ID(MyClass, id) in
.hpp.
 Defines a compile-time mechanism to get id.
 Declares a global object.
 LIP_DEFINE_TYPE_ID(MyClass) in .cpp.
 Defines the global object. Its constructor adds
itself as a hash node to a hash map. This
object class is templated to make operations
with the good type (example: placement new).
Hash Map Overview
TypeManager
+PolymorphicPlacementNew()
+PolymorphicMovePlacementNew()
+GetTypeSize()
+StreamObject()
+AddTypeConstructor()
TypeConstructorBase
1
*
+mVtableId
+mpNext : TypeConstructorBase
+PlacementNew()
+MovePlacementNew()
+GetTypeSize()
+StreamObject()
TypeT
TypeConstructorBase
+PlacementNew()
+MovePlacementNew()
+GetTypeSize()
+StreamObject()
Pointers
 Normal pointers like T* must be set to 0
when exporting an object.
 For relocated pointers, smart pointer
classes must be used.
 Different types of smart pointers/arrays:
 Ownership of pointed data?
 Relocation of pointed data?
Smart Members
 Classes can derive from a
lip::SmartMember class to implement a
custom writing/reading in tools.
 Class only used as a tag, it doesn’t have
any virtual function.
 Classes deriving from lip::SmartMember
are expected to implement a compiletime interface.
 Useful for smart pointers (normal pointers
cannot be load-in-placed).
Smart Members: Full Control
Over Writing and Reading.
class MySmartArray : lip::SmartMember {
public:
void Write(lip::LipWriter&) const;
void WriteExternalData(
lip::LipWriter&)const;
void Stream(lip::LipReader&);
void StreamExternalData(
lip::LipReader&);
};
WeakRelocPtr<Type>
Parent class
Not owned pointed data
mpPtr
RelocPtr<Type>
 Relocation assumes pointed data is not of a
sub-type and does directly a placement new
of Type.
Parent class
mpPtr
RelocPolymorphicPtr<Type>
 Relocation looks in the hash map to do
placement new of the good type (involves a
search and a virtual function call).
Parent class
mpPtr
RelocFixedArray<Type, size>
Parent class
[0]
[1]
[2]
[3]
WeakRelocArray
Parent class
Owned pointed array (not needing relocation)
mpPtr
muiCount
RelocArray<Type>
Parent class
Owned pointed array (with relocation)
mpPtr
muiCount
RelocPolymorphicArray<Type>
Parent class
Owned array of pointers
mppPtr
muiCount
Owned array of objects (can be of different sub-types)
RelocWeakPtrArray<Type>
Parent class
Owned array of pointers
mppPtr
muiCount
Not owned pointed objects (can be of different sub-types)
BinaryBlockPtr<alignment=4>
Parent class
Owned pointed data
mpPtr
Enums
 Concept of exclusivity group masks to
regroup values in mask in a radio button
group in GUI.
 LIP_REGISTER_ENUM(MyEnum) {
LIP_DEFINE_ENUM_VALUE(eNO);
LIP_DEFINE_ENUM_VALUE(eYES);
}
Other Solutions
 Parse debug info files.
 Compile as C++/CLI.
 Parse source code.
Questions?
Links
 Latest slides
 http://www.a2m.com/gdc/
 How to reach us
 [email protected][email protected]