1 //-*****************************************************************************
2 //
3 // Copyright (c) 2009-2012,
4 //  Sony Pictures Imageworks Inc. and
5 //  Industrial Light & Magic, a division of Lucasfilm Entertainment Company Ltd.
6 //
7 // All rights reserved.
8 //
9 // Redistribution and use in source and binary forms, with or without
10 // modification, are permitted provided that the following conditions are
11 // met:
12 // *       Redistributions of source code must retain the above copyright
13 // notice, this list of conditions and the following disclaimer.
14 // *       Redistributions in binary form must reproduce the above
15 // copyright notice, this list of conditions and the following disclaimer
16 // in the documentation and/or other materials provided with the
17 // distribution.
18 // *       Neither the name of Sony Pictures Imageworks, nor
19 // Industrial Light & Magic, nor the names of their contributors may be used
20 // to endorse or promote products derived from this software without specific
21 // prior written permission.
22 //
23 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 //
35 //-*****************************************************************************
36 
37 #include <Alembic/AbcCoreHDF5/WriteUtil.h>
38 #include <Alembic/AbcCoreHDF5/DataTypeRegistry.h>
39 #include <Alembic/AbcCoreHDF5/StringWriteUtil.h>
40 #include <Alembic/AbcCoreHDF5/AwImpl.h>
41 #include <Alembic/AbcCoreHDF5/HDF5Util.h>
42 
43 namespace Alembic {
44 namespace AbcCoreHDF5 {
45 namespace ALEMBIC_VERSION_NS {
46 
47 //-*****************************************************************************
48 //-*****************************************************************************
49 //-*****************************************************************************
50 
51 //-*****************************************************************************
52 void
WriteReferences(hid_t iParent,const std::string & iRefName,size_t iNumRefs,const void * iRefs)53 WriteReferences( hid_t iParent,
54                  const std::string& iRefName,
55                  size_t iNumRefs,
56                  const void *iRefs )
57 {
58     hsize_t dims[1];
59     dims[0] = iNumRefs;
60 
61     hid_t dspaceId = H5Screate_simple( 1, dims, NULL );
62     DspaceCloser dspaceCloser( dspaceId );
63 
64     hid_t dsetId = H5Dcreate2( iParent, iRefName.c_str(), H5T_STD_REF_OBJ,
65                                dspaceId, H5P_DEFAULT, H5P_DEFAULT,H5P_DEFAULT);
66     DsetCloser dsetCloser( dsetId );
67 
68     herr_t status = H5Dwrite( dsetId, H5T_STD_REF_OBJ, H5S_ALL, H5S_ALL,
69                        H5P_DEFAULT, iRefs);
70 
71     ABCA_ASSERT( status >= 0, "Couldn't write reference: " << iRefName );
72 }
73 //-*****************************************************************************
74 WrittenArraySampleMap &
GetWrittenArraySampleMap(AbcA::ArchiveWriterPtr iVal)75 GetWrittenArraySampleMap( AbcA::ArchiveWriterPtr iVal )
76 {
77     AwImpl *ptr = dynamic_cast<AwImpl*>( iVal.get() );
78     ABCA_ASSERT( ptr, "NULL Impl Ptr" );
79     return ptr->getWrittenArraySampleMap();
80 }
81 
82 //-*****************************************************************************
83 void
WriteDataToAttr(hid_t iParent,hid_t iDspace,const std::string & iAttrName,hid_t iFileType,hid_t iNativeType,const void * iData)84 WriteDataToAttr( hid_t iParent,
85                  hid_t iDspace,
86                  const std::string &iAttrName,
87                  hid_t iFileType,
88                  hid_t iNativeType,
89                  const void *iData )
90 {
91     hid_t attrId = H5Acreate2( iParent, iAttrName.c_str(),
92                                iFileType, iDspace,
93                                H5P_DEFAULT, H5P_DEFAULT );
94     AttrCloser attrCloser( attrId );
95 
96     herr_t status = H5Awrite( attrId, iNativeType, iData );
97 
98     ABCA_ASSERT( status >= 0, "Couldn't write attribute: " << iAttrName );
99 }
100 
101 //-*****************************************************************************
102 void
WriteScalar(hid_t iParent,const std::string & iAttrName,hid_t iFileType,hid_t iNativeType,const void * iData)103 WriteScalar( hid_t iParent,
104              const std::string &iAttrName,
105              hid_t iFileType,
106              hid_t iNativeType,
107              const void *iData )
108 {
109     hid_t dspaceId = H5Screate( H5S_SCALAR );
110     DspaceCloser dspaceCloser( dspaceId );
111 
112     WriteDataToAttr( iParent, dspaceId, iAttrName, iFileType, iNativeType,
113                      iData );
114 }
115 
116 //-*****************************************************************************
117 void
WriteSmallArray(hid_t iParent,const std::string & iAttrName,hid_t iFileType,hid_t iNativeType,size_t iNumVals,const void * iData)118 WriteSmallArray( hid_t iParent,
119                  const std::string &iAttrName,
120                  hid_t iFileType,
121                  hid_t iNativeType,
122                  size_t iNumVals,
123                  const void *iData )
124 {
125     Dimensions dims( iNumVals );
126     HDimensions hdims( dims );
127     size_t npoints = hdims.numPoints();
128     ABCA_ASSERT( npoints > 0,
129                   "Cannot create degenerate dataspace" );
130 
131     hid_t dspaceId = H5Screate_simple( hdims.rank(), hdims.rootPtr(), NULL );
132     DspaceCloser dspaceCloser( dspaceId );
133 
134     WriteDataToAttr( iParent, dspaceId, iAttrName, iFileType, iNativeType,
135                      iData );
136 }
137 
138 //-*****************************************************************************
139 void
WriteKey(hid_t iHashDset,const std::string & iAttrName,const AbcA::ArraySample::Key & iKey)140 WriteKey( hid_t iHashDset,
141           const std::string &iAttrName,
142           const AbcA::ArraySample::Key &iKey )
143 {
144     // keys are 16 bytes.
145     WriteSmallArray( iHashDset, iAttrName,
146                      H5T_STD_U8LE,
147                      H5T_NATIVE_UINT8,
148                      16,
149                      ( const void * )&iKey.digest );
150 }
151 
152 //-*****************************************************************************
153 //-*****************************************************************************
154 //-*****************************************************************************
155 
156 //-*****************************************************************************
157 // Dimensions aren't a scalar, and thus must be written carefully.
158 void
WriteDimensions(hid_t iParent,const std::string & iAttrName,const Dimensions & iDims)159 WriteDimensions( hid_t iParent,
160                  const std::string &iAttrName,
161                  const Dimensions &iDims )
162 {
163 
164     size_t rank = iDims.rank();
165 
166     // Create temporary storage to write
167     std::vector<uint32_t> dimStorage( rank );
168 
169     // Copy into it.
170     for ( size_t r = 0; r < rank; ++r )
171     {
172         dimStorage[r] = ( uint32_t )iDims[r];
173     }
174 
175     WriteSmallArray( iParent, iAttrName, H5T_STD_U32LE,
176                      H5T_NATIVE_UINT32,
177                      rank,
178                      ( const void * )&dimStorage.front() );
179 }
180 
181 //-*****************************************************************************
182 void
WriteMetaData(hid_t iGroup,const std::string & iName,const AbcA::MetaData & iMetaData)183 WriteMetaData( hid_t iGroup,
184                const std::string &iName,
185                const AbcA::MetaData &iMetaData )
186 {
187     //std::cout << "Being asked to write MetaData named: " << iName
188     //          << std::endl;
189     if ( iMetaData.size() > 0 )
190     {
191         std::string str = iMetaData.serialize();
192         if ( str.length() > 0 && str != "" )
193         {
194             //std::cout << "About to write MetaData string: "
195             //          << str << " to name: " << iName << std::endl;
196             WriteString( iGroup, iName, str );
197         }
198     }
199 }
200 
201 //-*****************************************************************************
202 static void
WriteTimeSamplingType(hid_t iGroup,const std::string & iName,const AbcA::TimeSamplingType & iTimeSamplingType)203 WriteTimeSamplingType( hid_t iGroup,
204                        const std::string &iName,
205                        const AbcA::TimeSamplingType &iTimeSamplingType )
206 {
207     const std::string nameSPC = iName + ".tspc";
208     const std::string nameTPC = iName + ".ttpc";
209 
210     const uint32_t spc = iTimeSamplingType.getNumSamplesPerCycle();
211     const chrono_t tpc = iTimeSamplingType.getTimePerCycle();
212 
213     if ( iTimeSamplingType.isUniform() )
214     {
215         // With uniform, we JUST write the time per sample
216         assert( spc == 1 );
217         WriteScalar( iGroup, nameTPC,
218                      H5T_IEEE_F64LE,
219                      H5T_NATIVE_DOUBLE,
220                      ( const void * )&tpc );
221     }
222     else if ( iTimeSamplingType.isCyclic() )
223     {
224         // Here we have to write SPC, and if TPC is 1.0 we don't
225         // bother writing it.
226         assert( spc > 1 );
227         assert( tpc < AbcA::TimeSamplingType::AcyclicTimePerCycle() );
228         WriteScalar( iGroup, nameSPC,
229                      H5T_STD_U32LE,
230                      H5T_NATIVE_UINT32,
231                      ( const void * )&spc );
232         if ( tpc != 1.0 )
233         {
234             WriteScalar( iGroup, nameTPC,
235                          H5T_IEEE_F64LE,
236                          H5T_NATIVE_DOUBLE,
237                          ( const void * )&tpc );
238         }
239     }
240     else
241     {
242         assert( iTimeSamplingType.isAcyclic() );
243         assert( spc == AbcA::TimeSamplingType::AcyclicNumSamples() );
244         WriteScalar( iGroup, nameSPC,
245                      H5T_STD_U32LE,
246                      H5T_NATIVE_UINT32,
247                      ( const void * )&spc );
248     }
249 }
250 
251 //-*****************************************************************************
252 WrittenArraySampleIDPtr
WriteArray(WrittenArraySampleMap & iMap,hid_t iGroup,const std::string & iName,const AbcA::ArraySample & iSamp,const AbcA::ArraySample::Key & iKey,hid_t iFileType,hid_t iNativeType,int iCompressionLevel)253 WriteArray( WrittenArraySampleMap &iMap,
254             hid_t iGroup,
255             const std::string &iName,
256             const AbcA::ArraySample &iSamp,
257             const AbcA::ArraySample::Key &iKey,
258             hid_t iFileType,
259             hid_t iNativeType,
260             int iCompressionLevel )
261 {
262 
263     // Dispatch to string writing utils.
264     const AbcA::DataType &dataType = iSamp.getDataType();
265     if ( dataType.getPod() == kStringPOD )
266     {
267         return WriteStringArray( iMap, iGroup, iName, iSamp, iKey,
268                                  iCompressionLevel );
269     }
270     else if ( dataType.getPod() == kWstringPOD )
271     {
272         return WriteWstringArray( iMap, iGroup, iName, iSamp, iKey,
273                                   iCompressionLevel );
274     }
275 
276     // write the dimensions as necessary
277     Dimensions dims = iSamp.getDimensions();
278     size_t rank = dims.rank();
279 
280     ABCA_ASSERT( rank > 0, "Cannot have a rank-0 array sample" );
281 
282     // rank 1 is the most common case, and we can easily infer it's size
283     // from the dataspace for non-strings, so don't bother writing it out
284     if (rank > 1)
285     {
286         std::string dimsName = iName + ".dims";
287         WriteDimensions( iGroup, dimsName, dims );
288     }
289 
290     // See whether or not we've already stored this.
291     WrittenArraySampleIDPtr writeID = iMap.find( iKey );
292     if ( writeID )
293     {
294         CopyWrittenArray( iGroup, iName, writeID );
295         return writeID;
296     }
297 
298     // Okay, need to actually store it.
299     // It will be a dataset with an internal attribute for storing
300     // the hash id.
301 
302     bool hasData = dims.numPoints() > 0;
303 
304     hid_t dspaceId = -1;
305     if ( hasData )
306     {
307         hsize_t hdim = dims.numPoints() * dataType.getExtent();
308         dspaceId = H5Screate_simple( 1, &hdim, NULL );
309     }
310     else
311     {
312         dspaceId = H5Screate( H5S_NULL );
313     }
314 
315     ABCA_ASSERT( dspaceId >= 0,
316                  "WriteArray() Failed in dataspace construction" );
317     DspaceCloser dspaceCloser( dspaceId );
318 
319     hid_t dsetId = -1;
320     if ( iCompressionLevel >= 0 && hasData )
321     {
322         // Make a compression plist
323         hid_t zipPlist = DsetGzipCreatePlist( dims,
324             iCompressionLevel > 9 ? 9 : iCompressionLevel );
325         PlistCloser plistCloser( zipPlist );
326 
327         // Make the dataset.
328         dsetId = H5Dcreate2( iGroup, iName.c_str(), iFileType, dspaceId,
329                              H5P_DEFAULT, zipPlist, H5P_DEFAULT );
330     }
331     else
332     {
333         dsetId = H5Dcreate2( iGroup, iName.c_str(),
334                              iFileType, dspaceId,
335                              H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT );
336     }
337     DsetCloser dsetCloser(dsetId);
338 
339     ABCA_ASSERT( dsetId >= 0,
340                  "WriteArray() Failed in dataset constructor" );
341 
342     // Write the data.
343     if ( hasData )
344     {
345         H5Dwrite( dsetId, iNativeType, H5S_ALL, H5S_ALL, H5P_DEFAULT,
346                   iSamp.getData() );
347     }
348 
349     // Write the array sample key.
350     WriteKey( dsetId, "key", iKey );
351 
352     writeID.reset( new WrittenArraySampleID( iKey, dsetId ) );
353     iMap.store( writeID );
354 
355     // Return the reference.
356     return writeID;
357 }
358 
359 //-*****************************************************************************
360 void
CopyWrittenArray(hid_t iGroup,const std::string & iName,WrittenArraySampleIDPtr iRef)361 CopyWrittenArray( hid_t iGroup,
362                   const std::string &iName,
363                   WrittenArraySampleIDPtr iRef )
364 {
365     ABCA_ASSERT( ( bool )iRef,
366                   "CopyWrittenArray() passed a bogus ref" );
367 
368     hid_t fid = H5Iget_file_id(iGroup);
369     ABCA_ASSERT( fid >= 0,
370                 "CopyWrittenArray() Could not get file ID from iGroup" );
371 
372     hid_t did = H5Dopen( fid,
373         iRef->getObjectLocation().c_str(), H5P_DEFAULT );
374     DsetCloser dcloser(did);
375 
376     // We have a reference. Create a link to it.
377     // We are manually getting the source dataset instead of using
378     // fid and iName because of a bug in HDF5 1.8.5 and earlier.
379     // Files written using that approach would sometimes be corrupted.
380     herr_t status = H5Lcreate_hard( did,
381                                     ".",
382                                     iGroup,
383                                     iName.c_str(),
384                                     H5P_DEFAULT,
385                                     H5P_DEFAULT );
386 
387     H5Fclose( fid );
388     ABCA_ASSERT( status >= 0,
389                  "H5Lcreate_hard failed!" << std::endl
390                   << "Dset obj id: " << did << std::endl
391                   << "Link loc id: " << iGroup << std::endl
392                   << "Link name: " << iName );
393 }
394 
395 //-*****************************************************************************
WritePropertyInfo(hid_t iGroup,const AbcA::PropertyHeader & iHeader,bool isScalarLike,uint32_t iTimeSamplingIndex,uint32_t iNumSamples,uint32_t iFirstChangedIndex,uint32_t iLastChangedIndex)396 void WritePropertyInfo( hid_t iGroup,
397                     const AbcA::PropertyHeader &iHeader,
398                     bool isScalarLike,
399                     uint32_t iTimeSamplingIndex,
400                     uint32_t iNumSamples,
401                     uint32_t iFirstChangedIndex,
402                     uint32_t iLastChangedIndex )
403 {
404 
405     uint32_t info[5] = {0, 0, 0, 0, 0};
406     uint32_t numFields = 1;
407 
408     // Our bitmasks look like this:
409     //
410     // Property Type mask (Scalar, Array, or Compound) 0x0003
411     // 0000 0000 0000 0000 0000 0000 0000 0011
412     //
413     // Our Pod type mask 0x003c
414     // 0000 0000 0000 0000 0000 0000 0011 1100
415     //
416     // Has a time sampling index mask 0x0040
417     // 0000 0000 0000 0000 0000 0000 0100 0000
418     //
419     // no repeats mask 0x0080
420     // 0000 0000 0000 0000 0000 0000 1000 0000
421     //
422     // Extent value mask 0xff00
423     // 0000 0000 0000 0000 1111 1111 0000 0000
424 
425     // compounds are treated differently
426     if ( iHeader.getPropertyType() != AbcA::kCompoundProperty )
427     {
428         // Slam the property type in there.
429         info[0] |= 0x0003 & ( uint32_t )iHeader.getPropertyType();
430 
431         // arrays may be scalar like, scalars are already scalar like
432         info[0] |= ( uint32_t ) isScalarLike;
433 
434         uint32_t pod = ( uint32_t )iHeader.getDataType().getPod();
435         info[0] |= 0x003c & ( pod << 2 );
436 
437         if (iTimeSamplingIndex != 0)
438         {
439             info[0] |=  0x0040;
440         }
441 
442         if (iFirstChangedIndex == 1 && iLastChangedIndex == iNumSamples - 1)
443         {
444             info[0] |= 0x0080;
445         }
446 
447         uint32_t extent = ( uint32_t )iHeader.getDataType().getExtent();
448         info[0] |= 0xff00 & ( extent << 8 );
449 
450         ABCA_ASSERT( iFirstChangedIndex <= iNumSamples &&
451             iLastChangedIndex <= iNumSamples &&
452             iFirstChangedIndex <= iLastChangedIndex,
453             "Illegal Sampling!" << std::endl <<
454             "Num Samples: " << iNumSamples << std::endl <<
455             "First Changed Index: " << iFirstChangedIndex << std::endl <<
456             "Last Changed Index: " << iLastChangedIndex << std::endl );
457 
458         // Write the num samples. Only bother writing if
459         // the num samples is greater than 1.  Existence of name.smp0
460         // is used by the reader to determine if 0 or 1 sample.
461         if ( iNumSamples > 1 )
462         {
463             info[1] = iNumSamples;
464             numFields ++;
465             if ( iFirstChangedIndex > 1 || ( iLastChangedIndex != 0 &&
466                 iLastChangedIndex != iNumSamples - 1 ) )
467             {
468                 info[2] = iFirstChangedIndex;
469                 info[3] = iLastChangedIndex;
470                 numFields += 2;
471             }
472         }
473 
474         // finally set time sampling index on the end if necessary
475         if (iTimeSamplingIndex != 0)
476         {
477             info[numFields] = iTimeSamplingIndex;
478             numFields ++;
479         }
480 
481     }
482 
483     WriteSmallArray( iGroup, iHeader.getName() + ".info",
484         H5T_STD_U32LE, H5T_NATIVE_UINT32, numFields,
485         ( const void * ) info );
486 
487     WriteMetaData( iGroup, iHeader.getName() + ".meta", iHeader.getMetaData());
488 }
489 
490 //-*****************************************************************************
WriteTimeSampling(hid_t iGroup,const std::string & iName,const AbcA::TimeSampling & iTsmp)491 void WriteTimeSampling( hid_t iGroup,
492                     const std::string &iName,
493                     const AbcA::TimeSampling &iTsmp )
494 {
495 
496     AbcA::TimeSamplingType tst = iTsmp.getTimeSamplingType();
497     WriteTimeSamplingType( iGroup, iName, tst );
498 
499     //-*************************************************************************
500     // WRITE TIMES.
501     //-*************************************************************************
502     std::string timeSampsName = iName + ".time";
503 
504     const std::vector < chrono_t > & samps = iTsmp.getStoredTimes();
505     ABCA_ASSERT( samps.size() > 0, "No TimeSamples to write!");
506     WriteSmallArray( iGroup, timeSampsName.c_str(), H5T_IEEE_F64LE,
507                      H5T_NATIVE_DOUBLE, samps.size(), &samps.front() );
508 }
509 
510 } // End namespace ALEMBIC_VERSION_NS
511 } // End namespace AbcCoreHDF5
512 } // End namespace Alembic
513