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