mirror of
https://github.com/dhewm/dhewm3.git
synced 2024-12-11 13:30:59 +00:00
1b61053c53
The code to decompress OGG files directly on load if they're shorter than s_decompressionLimit seconds (usually 6) accidentally got broken when removing the non-OpenAL soundbackends: `if ( objectInfo.wFormatTag == WAVE_FORMAT_TAG_OGG )` slipped into the `if ( objectInfo.wFormatTag == WAVE_FORMAT_TAG_PCM )` block - obviously both can't be true at the same time so the OGG case was never executed. Now it's in its own block again. I put it into a {} scope so it doesn't have to be re-indented (=> more useful with git blame)
654 lines
16 KiB
C++
654 lines
16 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 GPL Source Code
|
|
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
|
|
|
|
Doom 3 Source Code is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Doom 3 Source Code is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
|
|
|
|
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "sys/platform.h"
|
|
#include "framework/FileSystem.h"
|
|
|
|
#include "sound/snd_local.h"
|
|
|
|
#define USE_SOUND_CACHE_ALLOCATOR
|
|
|
|
#ifdef USE_SOUND_CACHE_ALLOCATOR
|
|
static idDynamicBlockAlloc<byte, 1<<20, 1<<10> soundCacheAllocator;
|
|
#else
|
|
static idDynamicAlloc<byte, 1<<20, 1<<10> soundCacheAllocator;
|
|
#endif
|
|
|
|
/*
|
|
===================
|
|
idSoundCache::idSoundCache()
|
|
===================
|
|
*/
|
|
idSoundCache::idSoundCache() {
|
|
soundCacheAllocator.Init();
|
|
soundCacheAllocator.SetLockMemory( true );
|
|
listCache.AssureSize( 1024, NULL );
|
|
listCache.SetGranularity( 256 );
|
|
insideLevelLoad = false;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundCache::~idSoundCache()
|
|
===================
|
|
*/
|
|
idSoundCache::~idSoundCache() {
|
|
listCache.DeleteContents( true );
|
|
soundCacheAllocator.Shutdown();
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundCache::::GetObject
|
|
|
|
returns a single cached object pointer
|
|
===================
|
|
*/
|
|
const idSoundSample* idSoundCache::GetObject( const int index ) const {
|
|
if (index<0 || index>listCache.Num()) {
|
|
return NULL;
|
|
}
|
|
return listCache[index];
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundCache::FindSound
|
|
|
|
Adds a sound object to the cache and returns a handle for it.
|
|
===================
|
|
*/
|
|
idSoundSample *idSoundCache::FindSound( const idStr& filename, bool loadOnDemandOnly ) {
|
|
idStr fname;
|
|
|
|
fname = filename;
|
|
fname.BackSlashesToSlashes();
|
|
fname.ToLower();
|
|
|
|
declManager->MediaPrint( "%s\n", fname.c_str() );
|
|
|
|
// check to see if object is already in cache
|
|
for( int i = 0; i < listCache.Num(); i++ ) {
|
|
idSoundSample *def = listCache[i];
|
|
if ( def && def->name == fname ) {
|
|
def->levelLoadReferenced = true;
|
|
if ( def->purged && !loadOnDemandOnly ) {
|
|
def->Load();
|
|
}
|
|
return def;
|
|
}
|
|
}
|
|
|
|
// create a new entry
|
|
idSoundSample *def = new idSoundSample;
|
|
|
|
int shandle = listCache.FindNull();
|
|
if ( shandle != -1 ) {
|
|
listCache[shandle] = def;
|
|
} else {
|
|
shandle = listCache.Append( def );
|
|
}
|
|
|
|
def->name = fname;
|
|
def->levelLoadReferenced = true;
|
|
def->onDemand = loadOnDemandOnly;
|
|
def->purged = true;
|
|
|
|
if ( !loadOnDemandOnly ) {
|
|
// this may make it a default sound if it can't be loaded
|
|
def->Load();
|
|
}
|
|
|
|
return def;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundCache::ReloadSounds
|
|
|
|
Completely nukes the current cache
|
|
===================
|
|
*/
|
|
void idSoundCache::ReloadSounds( bool force ) {
|
|
int i;
|
|
|
|
for( i = 0; i < listCache.Num(); i++ ) {
|
|
idSoundSample *def = listCache[i];
|
|
if ( def ) {
|
|
def->Reload( force );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
BeginLevelLoad
|
|
|
|
Mark all file based images as currently unused,
|
|
but don't free anything. Calls to ImageFromFile() will
|
|
either mark the image as used, or create a new image without
|
|
loading the actual data.
|
|
====================
|
|
*/
|
|
void idSoundCache::BeginLevelLoad() {
|
|
insideLevelLoad = true;
|
|
|
|
for ( int i = 0 ; i < listCache.Num() ; i++ ) {
|
|
idSoundSample *sample = listCache[ i ];
|
|
if ( !sample ) {
|
|
continue;
|
|
}
|
|
|
|
if ( com_purgeAll.GetBool() ) {
|
|
sample->PurgeSoundSample();
|
|
}
|
|
|
|
sample->levelLoadReferenced = false;
|
|
}
|
|
|
|
soundCacheAllocator.FreeEmptyBaseBlocks();
|
|
}
|
|
|
|
/*
|
|
====================
|
|
EndLevelLoad
|
|
|
|
Free all samples marked as unused
|
|
====================
|
|
*/
|
|
void idSoundCache::EndLevelLoad() {
|
|
int useCount, purgeCount;
|
|
common->Printf( "----- idSoundCache::EndLevelLoad -----\n" );
|
|
|
|
insideLevelLoad = false;
|
|
|
|
// purge the ones we don't need
|
|
useCount = 0;
|
|
purgeCount = 0;
|
|
for ( int i = 0 ; i < listCache.Num() ; i++ ) {
|
|
idSoundSample *sample = listCache[ i ];
|
|
if ( !sample ) {
|
|
continue;
|
|
}
|
|
if ( sample->purged ) {
|
|
continue;
|
|
}
|
|
if ( !sample->levelLoadReferenced ) {
|
|
// common->Printf( "Purging %s\n", sample->name.c_str() );
|
|
purgeCount += sample->objectMemSize;
|
|
sample->PurgeSoundSample();
|
|
} else {
|
|
useCount += sample->objectMemSize;
|
|
}
|
|
}
|
|
|
|
soundCacheAllocator.FreeEmptyBaseBlocks();
|
|
|
|
common->Printf( "%5ik referenced\n", useCount / 1024 );
|
|
common->Printf( "%5ik purged\n", purgeCount / 1024 );
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundCache::PrintMemInfo
|
|
===================
|
|
*/
|
|
void idSoundCache::PrintMemInfo( MemInfo_t *mi ) {
|
|
int i, j, num = 0, total = 0;
|
|
int *sortIndex;
|
|
idFile *f;
|
|
|
|
f = fileSystem->OpenFileWrite( mi->filebase + "_sounds.txt" );
|
|
if ( !f ) {
|
|
return;
|
|
}
|
|
|
|
// count
|
|
for ( i = 0; i < listCache.Num(); i++, num++ ) {
|
|
if ( !listCache[i] ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// sort first
|
|
sortIndex = new int[num];
|
|
|
|
for ( i = 0; i < num; i++ ) {
|
|
sortIndex[i] = i;
|
|
}
|
|
|
|
for ( i = 0; i < num - 1; i++ ) {
|
|
for ( j = i + 1; j < num; j++ ) {
|
|
if ( listCache[sortIndex[i]]->objectMemSize < listCache[sortIndex[j]]->objectMemSize ) {
|
|
int temp = sortIndex[i];
|
|
sortIndex[i] = sortIndex[j];
|
|
sortIndex[j] = temp;
|
|
}
|
|
}
|
|
}
|
|
|
|
// print next
|
|
for ( i = 0; i < num; i++ ) {
|
|
idSoundSample *sample = listCache[sortIndex[i]];
|
|
|
|
// this is strange
|
|
if ( !sample ) {
|
|
continue;
|
|
}
|
|
|
|
total += sample->objectMemSize;
|
|
f->Printf( "%s %s\n", idStr::FormatNumber( sample->objectMemSize ).c_str(), sample->name.c_str() );
|
|
}
|
|
|
|
mi->soundAssetsTotal = total;
|
|
|
|
f->Printf( "\nTotal sound bytes allocated: %s\n", idStr::FormatNumber( total ).c_str() );
|
|
fileSystem->CloseFile( f );
|
|
delete[] sortIndex;
|
|
}
|
|
|
|
|
|
/*
|
|
==========================================================================
|
|
|
|
idSoundSample
|
|
|
|
==========================================================================
|
|
*/
|
|
|
|
/*
|
|
===================
|
|
idSoundSample::idSoundSample
|
|
===================
|
|
*/
|
|
idSoundSample::idSoundSample() {
|
|
memset( &objectInfo, 0, sizeof(waveformatex_t) );
|
|
objectSize = 0;
|
|
objectMemSize = 0;
|
|
nonCacheData = NULL;
|
|
amplitudeData = NULL;
|
|
openalBuffer = 0;
|
|
hardwareBuffer = false;
|
|
defaultSound = false;
|
|
onDemand = false;
|
|
purged = false;
|
|
levelLoadReferenced = false;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSample::~idSoundSample
|
|
===================
|
|
*/
|
|
idSoundSample::~idSoundSample() {
|
|
PurgeSoundSample();
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSample::LengthIn44kHzSamples
|
|
===================
|
|
*/
|
|
int idSoundSample::LengthIn44kHzSamples( void ) const {
|
|
// objectSize is samples
|
|
if ( objectInfo.nSamplesPerSec == 11025 ) {
|
|
return objectSize << 2;
|
|
} else if ( objectInfo.nSamplesPerSec == 22050 ) {
|
|
return objectSize << 1;
|
|
} else {
|
|
return objectSize << 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSample::MakeDefault
|
|
===================
|
|
*/
|
|
void idSoundSample::MakeDefault( void ) {
|
|
int i;
|
|
float v;
|
|
int sample;
|
|
|
|
memset( &objectInfo, 0, sizeof( objectInfo ) );
|
|
|
|
objectInfo.nChannels = 1;
|
|
objectInfo.wBitsPerSample = 16;
|
|
objectInfo.nSamplesPerSec = 44100;
|
|
|
|
objectSize = MIXBUFFER_SAMPLES * 2;
|
|
objectMemSize = objectSize * sizeof( short );
|
|
|
|
nonCacheData = (byte *)soundCacheAllocator.Alloc( objectMemSize );
|
|
|
|
short *ncd = (short *)nonCacheData;
|
|
|
|
for ( i = 0; i < MIXBUFFER_SAMPLES; i ++ ) {
|
|
v = sin( idMath::PI * 2 * i / 64 );
|
|
sample = v * 0x4000;
|
|
ncd[i*2+0] = sample;
|
|
ncd[i*2+1] = sample;
|
|
}
|
|
|
|
alGetError();
|
|
alGenBuffers( 1, &openalBuffer );
|
|
if ( alGetError() != AL_NO_ERROR ) {
|
|
common->Error( "idSoundCache: error generating OpenAL hardware buffer" );
|
|
}
|
|
|
|
alGetError();
|
|
alBufferData( openalBuffer, objectInfo.nChannels==1?AL_FORMAT_MONO16:AL_FORMAT_STEREO16, nonCacheData, objectMemSize, objectInfo.nSamplesPerSec );
|
|
if ( alGetError() != AL_NO_ERROR ) {
|
|
common->Warning( "idSoundCache: error loading data into OpenAL hardware buffer" );
|
|
hardwareBuffer = false;
|
|
} else {
|
|
hardwareBuffer = true;
|
|
}
|
|
|
|
defaultSound = true;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSample::CheckForDownSample
|
|
===================
|
|
*/
|
|
void idSoundSample::CheckForDownSample( void ) {
|
|
if ( !idSoundSystemLocal::s_force22kHz.GetBool() ) {
|
|
return;
|
|
}
|
|
if ( objectInfo.wFormatTag != WAVE_FORMAT_TAG_PCM || objectInfo.nSamplesPerSec != 44100 ) {
|
|
return;
|
|
}
|
|
int shortSamples = objectSize >> 1;
|
|
short *converted = (short *)soundCacheAllocator.Alloc( shortSamples * sizeof( short ) );
|
|
|
|
if ( objectInfo.nChannels == 1 ) {
|
|
for ( int i = 0; i < shortSamples; i++ ) {
|
|
converted[i] = ((short *)nonCacheData)[i*2];
|
|
}
|
|
} else {
|
|
for ( int i = 0; i < shortSamples; i += 2 ) {
|
|
converted[i+0] = ((short *)nonCacheData)[i*2+0];
|
|
converted[i+1] = ((short *)nonCacheData)[i*2+1];
|
|
}
|
|
}
|
|
soundCacheAllocator.Free( nonCacheData );
|
|
nonCacheData = (byte *)converted;
|
|
objectSize >>= 1;
|
|
objectMemSize >>= 1;
|
|
objectInfo.nAvgBytesPerSec >>= 1;
|
|
objectInfo.nSamplesPerSec >>= 1;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSample::GetNewTimeStamp
|
|
===================
|
|
*/
|
|
ID_TIME_T idSoundSample::GetNewTimeStamp( void ) const {
|
|
ID_TIME_T timestamp;
|
|
|
|
fileSystem->ReadFile( name, NULL, ×tamp );
|
|
if ( timestamp == FILE_NOT_FOUND_TIMESTAMP ) {
|
|
idStr oggName = name;
|
|
oggName.SetFileExtension( ".ogg" );
|
|
fileSystem->ReadFile( oggName, NULL, ×tamp );
|
|
}
|
|
return timestamp;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSample::Load
|
|
|
|
Loads based on name, possibly doing a MakeDefault if necessary
|
|
===================
|
|
*/
|
|
void idSoundSample::Load( void ) {
|
|
defaultSound = false;
|
|
purged = false;
|
|
hardwareBuffer = false;
|
|
|
|
timestamp = GetNewTimeStamp();
|
|
|
|
if ( timestamp == FILE_NOT_FOUND_TIMESTAMP ) {
|
|
common->Warning( "Couldn't load sound '%s' using default", name.c_str() );
|
|
MakeDefault();
|
|
return;
|
|
}
|
|
|
|
// load it
|
|
idWaveFile fh;
|
|
waveformatex_t info;
|
|
|
|
if ( fh.Open( name, &info ) == -1 ) {
|
|
common->Warning( "Couldn't load sound '%s' using default", name.c_str() );
|
|
MakeDefault();
|
|
return;
|
|
}
|
|
|
|
if ( info.nChannels != 1 && info.nChannels != 2 ) {
|
|
common->Warning( "idSoundSample: %s has %i channels, using default", name.c_str(), info.nChannels );
|
|
fh.Close();
|
|
MakeDefault();
|
|
return;
|
|
}
|
|
|
|
if ( info.wBitsPerSample != 16 ) {
|
|
common->Warning( "idSoundSample: %s is %dbits, expected 16bits using default", name.c_str(), info.wBitsPerSample );
|
|
fh.Close();
|
|
MakeDefault();
|
|
return;
|
|
}
|
|
|
|
if ( info.nSamplesPerSec != 44100 && info.nSamplesPerSec != 22050 && info.nSamplesPerSec != 11025 ) {
|
|
common->Warning( "idSoundCache: %s is %dHz, expected 11025, 22050 or 44100 Hz. Using default", name.c_str(), info.nSamplesPerSec );
|
|
fh.Close();
|
|
MakeDefault();
|
|
return;
|
|
}
|
|
|
|
objectInfo = info;
|
|
objectSize = fh.GetOutputSize();
|
|
objectMemSize = fh.GetMemorySize();
|
|
|
|
nonCacheData = (byte *)soundCacheAllocator.Alloc( objectMemSize );
|
|
fh.Read( nonCacheData, objectMemSize, NULL );
|
|
|
|
// optionally convert it to 22kHz to save memory
|
|
CheckForDownSample();
|
|
|
|
// create hardware audio buffers
|
|
// PCM loads directly
|
|
if ( objectInfo.wFormatTag == WAVE_FORMAT_TAG_PCM ) {
|
|
alGetError();
|
|
alGenBuffers( 1, &openalBuffer );
|
|
if ( alGetError() != AL_NO_ERROR )
|
|
common->Error( "idSoundCache: error generating OpenAL hardware buffer" );
|
|
if ( alIsBuffer( openalBuffer ) ) {
|
|
alGetError();
|
|
alBufferData( openalBuffer, objectInfo.nChannels==1?AL_FORMAT_MONO16:AL_FORMAT_STEREO16, nonCacheData, objectMemSize, objectInfo.nSamplesPerSec );
|
|
if ( alGetError() != AL_NO_ERROR ) {
|
|
common->Warning( "idSoundCache: error loading data into OpenAL hardware buffer" );
|
|
hardwareBuffer = false;
|
|
} else {
|
|
hardwareBuffer = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
// OGG decompressed at load time (when smaller than s_decompressionLimit seconds, 6 seconds by default)
|
|
if ( objectInfo.wFormatTag == WAVE_FORMAT_TAG_OGG ) {
|
|
if ( ( objectSize < ( ( int ) objectInfo.nSamplesPerSec * idSoundSystemLocal::s_decompressionLimit.GetInteger() ) ) ) {
|
|
alGetError();
|
|
alGenBuffers( 1, &openalBuffer );
|
|
if ( alGetError() != AL_NO_ERROR )
|
|
common->Error( "idSoundCache: error generating OpenAL hardware buffer" );
|
|
if ( alIsBuffer( openalBuffer ) ) {
|
|
idSampleDecoder *decoder = idSampleDecoder::Alloc();
|
|
float *destData = (float *)soundCacheAllocator.Alloc( ( LengthIn44kHzSamples() + 1 ) * sizeof( float ) );
|
|
|
|
// Decoder *always* outputs 44 kHz data
|
|
decoder->Decode( this, 0, LengthIn44kHzSamples(), destData );
|
|
|
|
// Downsample back to original frequency (save memory)
|
|
if ( objectInfo.nSamplesPerSec == 11025 ) {
|
|
for ( int i = 0; i < objectSize; i++ ) {
|
|
if ( destData[i*4] < -32768.0f )
|
|
((short *)destData)[i] = -32768;
|
|
else if ( destData[i*4] > 32767.0f )
|
|
((short *)destData)[i] = 32767;
|
|
else
|
|
((short *)destData)[i] = idMath::FtoiFast( destData[i*4] );
|
|
}
|
|
} else if ( objectInfo.nSamplesPerSec == 22050 ) {
|
|
for ( int i = 0; i < objectSize; i++ ) {
|
|
if ( destData[i*2] < -32768.0f )
|
|
((short *)destData)[i] = -32768;
|
|
else if ( destData[i*2] > 32767.0f )
|
|
((short *)destData)[i] = 32767;
|
|
else
|
|
((short *)destData)[i] = idMath::FtoiFast( destData[i*2] );
|
|
}
|
|
} else {
|
|
for ( int i = 0; i < objectSize; i++ ) {
|
|
if ( destData[i] < -32768.0f )
|
|
((short *)destData)[i] = -32768;
|
|
else if ( destData[i] > 32767.0f )
|
|
((short *)destData)[i] = 32767;
|
|
else
|
|
((short *)destData)[i] = idMath::FtoiFast( destData[i] );
|
|
}
|
|
}
|
|
|
|
alGetError();
|
|
alBufferData( openalBuffer, objectInfo.nChannels==1?AL_FORMAT_MONO16:AL_FORMAT_STEREO16, destData, objectSize * sizeof( short ), objectInfo.nSamplesPerSec );
|
|
if ( alGetError() != AL_NO_ERROR ) {
|
|
common->Warning( "idSoundCache: error loading data into OpenAL hardware buffer" );
|
|
hardwareBuffer = false;
|
|
} else {
|
|
hardwareBuffer = true;
|
|
}
|
|
|
|
soundCacheAllocator.Free( (byte *)destData );
|
|
idSampleDecoder::Free( decoder );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fh.Close();
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSample::PurgeSoundSample
|
|
===================
|
|
*/
|
|
void idSoundSample::PurgeSoundSample() {
|
|
purged = true;
|
|
|
|
alGetError();
|
|
alDeleteBuffers( 1, &openalBuffer );
|
|
if ( alGetError() != AL_NO_ERROR ) {
|
|
common->Warning( "idSoundCache: error unloading data from OpenAL hardware buffer" );
|
|
}
|
|
|
|
openalBuffer = 0;
|
|
hardwareBuffer = false;
|
|
|
|
if ( amplitudeData ) {
|
|
soundCacheAllocator.Free( amplitudeData );
|
|
amplitudeData = NULL;
|
|
}
|
|
|
|
if ( nonCacheData ) {
|
|
soundCacheAllocator.Free( nonCacheData );
|
|
nonCacheData = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSample::Reload
|
|
===================
|
|
*/
|
|
void idSoundSample::Reload( bool force ) {
|
|
if ( !force ) {
|
|
ID_TIME_T newTimestamp;
|
|
|
|
// check the timestamp
|
|
newTimestamp = GetNewTimeStamp();
|
|
|
|
if ( newTimestamp == FILE_NOT_FOUND_TIMESTAMP ) {
|
|
if ( !defaultSound ) {
|
|
common->Warning( "Couldn't load sound '%s' using default", name.c_str() );
|
|
MakeDefault();
|
|
}
|
|
return;
|
|
}
|
|
if ( newTimestamp == timestamp ) {
|
|
return; // don't need to reload it
|
|
}
|
|
}
|
|
|
|
common->Printf( "reloading %s\n", name.c_str() );
|
|
PurgeSoundSample();
|
|
Load();
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idSoundSample::FetchFromCache
|
|
|
|
Returns true on success.
|
|
===================
|
|
*/
|
|
bool idSoundSample::FetchFromCache( int offset, const byte **output, int *position, int *size, const bool allowIO ) {
|
|
offset &= 0xfffffffe;
|
|
|
|
if ( objectSize == 0 || offset < 0 || offset > objectSize * (int)sizeof( short ) || !nonCacheData ) {
|
|
return false;
|
|
}
|
|
|
|
if ( output ) {
|
|
*output = nonCacheData + offset;
|
|
}
|
|
if ( position ) {
|
|
*position = 0;
|
|
}
|
|
if ( size ) {
|
|
*size = objectSize * sizeof( short ) - offset;
|
|
if ( *size > SCACHE_SIZE ) {
|
|
*size = SCACHE_SIZE;
|
|
}
|
|
}
|
|
return true;
|
|
}
|