Skip to content

Commit 5cc8860

Browse files
authored
perf(TS): Reduce TS memory by slimming down internal LevelInfo size (#4337)
Reduces the memory footprint of the `LevelInfo` struct. Three changes are here, in order of most to least impactful: - Makes one `ImageSpec` optional, most of the time `spec` and `nativespec` are the same, it appears they are only different when autotile is enabled, this saves about 160 bytes. - Uses `std::unique_ptr<float[]>` (8 bytes) instead of `std::vector<float>` (24 bytes). - Reorders members to remove excess padding. We noticed this memory usage on scenes with per-face textures with multiple mips per face. For a scene with 12.5 million mips, this saves about 2GB. Note, it would probably also be possible to remove the `polecolorcomputed` member, instead relying on the `polecolor` pointer. But since this bool occupies existing padding in the struct it wouldn't save any memory currently. --------- Signed-off-by: Curtis Black <curtis.w.black@gmail.com>
1 parent 736d253 commit 5cc8860

3 files changed

Lines changed: 61 additions & 37 deletions

File tree

src/libtexture/imagecache.cpp

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -216,11 +216,12 @@ ImageCacheStatistics::merge(const ImageCacheStatistics& s)
216216

217217

218218

219-
ImageCacheFile::LevelInfo::LevelInfo(const ImageSpec& spec_,
219+
ImageCacheFile::LevelInfo::LevelInfo(std::unique_ptr<ImageSpec> spec_,
220220
const ImageSpec& nativespec_)
221-
: spec(spec_)
221+
: m_spec(std::move(spec_))
222222
, nativespec(nativespec_)
223223
{
224+
const ImageSpec& spec = m_spec ? *m_spec : nativespec;
224225
full_pixel_range
225226
= (spec.x == spec.full_x && spec.y == spec.full_y
226227
&& spec.z == spec.full_z && spec.width == spec.full_width
@@ -250,16 +251,21 @@ ImageCacheFile::LevelInfo::LevelInfo(const ImageSpec& spec_,
250251

251252

252253
ImageCacheFile::LevelInfo::LevelInfo(const LevelInfo& src)
253-
: spec(src.spec)
254-
, nativespec(src.nativespec)
255-
, full_pixel_range(src.full_pixel_range)
256-
, onetile(src.onetile)
257-
, polecolorcomputed(src.polecolorcomputed)
258-
, polecolor(src.polecolor)
254+
: nativespec(src.nativespec)
259255
, nxtiles(src.nxtiles)
260256
, nytiles(src.nytiles)
261257
, nztiles(src.nztiles)
258+
, full_pixel_range(src.full_pixel_range)
259+
, onetile(src.onetile)
260+
, polecolorcomputed(src.polecolorcomputed)
262261
{
262+
if (src.m_spec)
263+
m_spec = std::make_unique<ImageSpec>(*src.m_spec);
264+
const ImageSpec& spec = m_spec ? *m_spec : nativespec;
265+
if (src.polecolor) {
266+
polecolor.reset(new float[2 * spec.nchannels]);
267+
std::copy_n(src.polecolor.get(), 2 * spec.nchannels, polecolor.get());
268+
}
263269
int nwords = round_to_multiple(nxtiles * nytiles * nztiles, 64) / 64;
264270
tiles_read = new atomic_ll[nwords];
265271
for (int i = 0; i < nwords; ++i)
@@ -563,8 +569,9 @@ ImageCacheFile::open(ImageCachePerThreadInfo* thread_info)
563569
int max_mip_res = imagecache().max_mip_res();
564570
int nmip = 0;
565571
do {
566-
nativespec = inp->spec(nsubimages, nmip);
567-
tempspec = nativespec;
572+
nativespec = inp->spec(nsubimages, nmip);
573+
tempspec = nativespec;
574+
bool tmpspecmodified = false;
568575
if (nmip == 0) {
569576
// Things to do on MIP level 0, i.e. once per subimage
570577
si.init(*this, tempspec, imagecache().forcefloat());
@@ -598,6 +605,7 @@ ImageCacheFile::open(ImageCachePerThreadInfo* thread_info)
598605
tempspec.tile_height = tempspec.height;
599606
tempspec.tile_depth = tempspec.depth;
600607
}
608+
tmpspecmodified = true;
601609
}
602610
// If a request was made for a maximum MIP resolution to use for
603611
// texture lookups and this level is too big, bump up this
@@ -620,8 +628,14 @@ ImageCacheFile::open(ImageCachePerThreadInfo* thread_info)
620628
}
621629
// ImageCache can't store differing formats per channel
622630
tempspec.channelformats.clear();
623-
LevelInfo levelinfo(tempspec, nativespec);
624-
si.levels.push_back(levelinfo);
631+
if (tmpspecmodified) {
632+
LevelInfo levelinfo(std::make_unique<ImageSpec>(tempspec),
633+
nativespec);
634+
si.levels.push_back(levelinfo);
635+
} else {
636+
LevelInfo levelinfo(nativespec);
637+
si.levels.push_back(levelinfo);
638+
}
625639
++nmip;
626640
} while (inp->seek_subimage(nsubimages, nmip));
627641

@@ -663,7 +677,7 @@ ImageCacheFile::open(ImageCachePerThreadInfo* thread_info)
663677
s.tile_depth = d;
664678
}
665679
++nmip;
666-
LevelInfo levelinfo(s, s);
680+
LevelInfo levelinfo(s);
667681
si.levels.push_back(levelinfo);
668682
}
669683
}
@@ -1583,18 +1597,18 @@ ImageCacheTile::read(ImageCachePerThreadInfo* thread_info)
15831597
if (m_valid) {
15841598
ImageCacheFile::LevelInfo& lev(
15851599
file.levelinfo(m_id.subimage(), m_id.miplevel()));
1586-
m_tile_width = lev.spec.tile_width;
1600+
const ImageSpec& spec(lev.spec());
1601+
m_tile_width = spec.tile_width;
15871602
OIIO_DASSERT(m_tile_width > 0);
1588-
int whichtile = ((m_id.x() - lev.spec.x) / lev.spec.tile_width)
1589-
+ ((m_id.y() - lev.spec.y) / lev.spec.tile_height)
1590-
* lev.nxtiles
1591-
+ ((m_id.z() - lev.spec.z) / lev.spec.tile_depth)
1603+
int whichtile = ((m_id.x() - spec.x) / spec.tile_width)
1604+
+ ((m_id.y() - spec.y) / spec.tile_height) * lev.nxtiles
1605+
+ ((m_id.z() - spec.z) / spec.tile_depth)
15921606
* (lev.nxtiles * lev.nytiles);
15931607
int index = whichtile / 64;
15941608
int64_t bitmask = int64_t(1ULL << (whichtile & 63));
15951609
int64_t oldval = lev.tiles_read[index].fetch_or(bitmask);
15961610
if (oldval & bitmask) // Was it previously read?
1597-
file.register_redundant_tile(lev.spec.tile_bytes());
1611+
file.register_redundant_tile(spec.tile_bytes());
15981612
} else {
15991613
// (! m_valid)
16001614
m_used = false; // Don't let it hold mem if invalid
@@ -3586,8 +3600,9 @@ ImageCacheImpl::invalidate_all(bool force)
35863600
if (sub.untiled) {
35873601
for (int m = 0, mend = f->miplevels(s); m < mend; ++m) {
35883602
const ImageCacheFile::LevelInfo& level(f->levelinfo(s, m));
3589-
if (level.spec.tile_width != m_autotile
3590-
|| level.spec.tile_height != m_autotile) {
3603+
const ImageSpec& spec(level.spec());
3604+
if (spec.tile_width != m_autotile
3605+
|| spec.tile_height != m_autotile) {
35913606
all_files.push_back(name);
35923607
break;
35933608
}

src/libtexture/imagecache_pvt.h

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -164,11 +164,11 @@ class OIIO_API ImageCacheFile final : public RefCnt {
164164
}
165165
const ImageSpec& spec(int subimage, int miplevel) const
166166
{
167-
return levelinfo(subimage, miplevel).spec;
167+
return levelinfo(subimage, miplevel).spec();
168168
}
169169
ImageSpec& spec(int subimage, int miplevel)
170170
{
171-
return levelinfo(subimage, miplevel).spec;
171+
return levelinfo(subimage, miplevel).spec();
172172
}
173173
const ImageSpec& nativespec(int subimage, int miplevel) const
174174
{
@@ -250,18 +250,27 @@ class OIIO_API ImageCacheFile final : public RefCnt {
250250
/// Info for each MIP level that isn't in the ImageSpec, or that we
251251
/// precompute.
252252
struct LevelInfo {
253-
ImageSpec spec; ///< ImageSpec for the mip level
254-
ImageSpec nativespec; ///< Native ImageSpec for the mip level
253+
std::unique_ptr<ImageSpec> m_spec; ///< ImageSpec for the mip level,
254+
// only specified if different from nativespec
255+
ImageSpec nativespec; ///< Native ImageSpec for the mip level
256+
mutable std::unique_ptr<float[]> polecolor; ///< Pole colors
257+
atomic_ll* tiles_read; ///< Bitfield for tiles read at least once
258+
int nxtiles, nytiles, nztiles; ///< Number of tiles in each dimension
255259
bool full_pixel_range; ///< pixel data window matches image window
256260
bool onetile; ///< Whole level fits on one tile
257-
mutable bool polecolorcomputed; ///< Pole color was computed
258-
mutable std::vector<float> polecolor; ///< Pole colors
259-
int nxtiles, nytiles, nztiles; ///< Number of tiles in each dimension
260-
atomic_ll* tiles_read; ///< Bitfield for tiles read at least once
261-
LevelInfo(const ImageSpec& spec,
261+
mutable bool polecolorcomputed; ///< Pole color was computed
262+
263+
LevelInfo(std::unique_ptr<ImageSpec> spec,
262264
const ImageSpec& nativespec); ///< Initialize based on spec
263-
LevelInfo(const LevelInfo& src); // needed for vector<LevelInfo>
265+
LevelInfo(const ImageSpec& nativespec)
266+
: LevelInfo(nullptr, nativespec)
267+
{
268+
}
269+
LevelInfo(const LevelInfo& src); // needed for vector<LevelInfo>
264270
~LevelInfo() { delete[] tiles_read; }
271+
272+
ImageSpec& spec() { return m_spec ? *m_spec : nativespec; }
273+
const ImageSpec& spec() const { return m_spec ? *m_spec : nativespec; }
265274
};
266275

267276
/// Info for each subimage
@@ -278,8 +287,8 @@ class OIIO_API ImageCacheFile final : public RefCnt {
278287
bool full_pixel_range = false; ///< data window matches image window
279288
bool is_constant_image = false; ///< Is the image a constant color?
280289
bool has_average_color = false; ///< We have an average color
281-
std::vector<float> average_color; ///< Average color
282290
spin_mutex average_color_mutex; ///< protect average_color
291+
std::vector<float> average_color; ///< Average color
283292
std::unique_ptr<Imath::M44f> Mlocal; ///< shadows/volumes: world-to-local
284293
// The scale/offset accounts for crops or overscans, converting
285294
// 0-1 texture space relative to the "display/full window" into
@@ -294,8 +303,8 @@ class OIIO_API ImageCacheFile final : public RefCnt {
294303
SubimageInfo() {}
295304
void init(ImageCacheFile& icfile, const ImageSpec& spec,
296305
bool forcefloat);
297-
ImageSpec& spec(int m) { return levels[m].spec; }
298-
const ImageSpec& spec(int m) const { return levels[m].spec; }
306+
ImageSpec& spec(int m) { return levels[m].spec(); }
307+
const ImageSpec& spec(int m) const { return levels[m].spec(); }
299308
const ImageSpec& nativespec(int m) const
300309
{
301310
return levels[m].nativespec;

src/libtexture/texturesys.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1981,13 +1981,13 @@ TextureSystemImpl::pole_color(TextureFile& texturefile,
19811981
{
19821982
if (!levelinfo.onetile)
19831983
return NULL; // Only compute color for one-tile MIP levels
1984-
const ImageSpec& spec(levelinfo.spec);
1984+
const ImageSpec& spec(levelinfo.spec());
19851985
if (!levelinfo.polecolorcomputed) {
19861986
static spin_mutex mutex; // Protect everybody's polecolor
19871987
spin_lock lock(mutex);
19881988
if (!levelinfo.polecolorcomputed) {
1989-
OIIO_DASSERT(levelinfo.polecolor.size() == 0);
1990-
levelinfo.polecolor.resize(2 * spec.nchannels);
1989+
OIIO_DASSERT(!levelinfo.polecolor);
1990+
levelinfo.polecolor.reset(new float[2 * spec.nchannels]);
19911991
OIIO_DASSERT(tile->id().nchannels() == spec.nchannels
19921992
&& "pole_color doesn't work for channel subsets");
19931993
int pixelsize = tile->pixelsize();

0 commit comments

Comments
 (0)