Skip to content

Commit b0f206d

Browse files
yuvaltassacopybara-github
authored andcommitted
Release the decoded pixels of a texture once they are copied into mjModel
PiperOrigin-RevId: 1003136927 Change-Id: Icfcb24f9c5fa75d197a5543d1766228016693e48
1 parent e81c8ae commit b0f206d

3 files changed

Lines changed: 78 additions & 0 deletions

File tree

‎doc/changelog.rst‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ Compiler
4848

4949
**Migration:** Test for an error with ``< 0`` rather than ``!= 0``.
5050

51+
- A compiled :ref:`mjSpec` no longer keeps the decoded pixels of its textures between compilations, which lowers the
52+
memory of a loaded model by the size of its textures.
5153
- Added :ref:`mjs_dedupAssets` (``MjSpec.dedup_assets``), which merges the textures and meshes of an :ref:`mjSpec`
5254
that would compile to the same asset, as those of a sub-model attached several times do: the first of each group is
5355
kept and every reference to the others is rewritten to it. Like :ref:`mjs_fuseStatic`, it returns the number of

‎src/user/user_model.cc‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <chrono>
2121
#include <cmath>
2222
#include <csetjmp>
23+
#include <cstddef>
2324
#include <cstdint>
2425
#include <cstdio>
2526
#include <cstdlib>
@@ -3899,6 +3900,9 @@ void mjCModel::CopyObjects(mjModel* m) {
38993900
mjtSize nbytes = static_cast<mjtSize>(ptex->nchannel) * ptex->width * ptex->height;
39003901
memcpy(m->tex_data + data_adr, ptex->data_.data(), nbytes);
39013902

3903+
// the model has the pixels now; the next compile loads them again
3904+
std::vector<std::byte>().swap(ptex->data_);
3905+
39023906
// advance counter
39033907
data_adr += nbytes;
39043908
}

‎test/user/user_objects_test.cc‎

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <cstring>
2121
#include <memory>
2222
#include <string>
23+
#include <vector>
2324

2425
#include <gmock/gmock.h>
2526
#include <gtest/gtest.h>
@@ -1588,6 +1589,77 @@ TEST_F(MjCTextureTest, CubeFromFileMustHaveThreeChannels) {
15881589
mj_deleteVFS(vfs.get());
15891590
}
15901591

1592+
// the spec does not keep the pixels of its textures between compilations: a
1593+
// file, a builtin and a buffer texture compile to the same tex_data every time,
1594+
// with and without the asset cache
1595+
TEST_F(MjCTextureTest, PixelsSameAfterRecompile) {
1596+
// 1 x 1 RGB PNG file
1597+
static constexpr unsigned char pixel[] = {
1598+
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
1599+
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
1600+
0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53, 0xde, 0x00, 0x00, 0x00,
1601+
0x0c, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0xf8, 0xdf, 0xc0, 0x00,
1602+
0x00, 0x04, 0x01, 0x01, 0x80, 0xfb, 0xd7, 0xcb, 0xf1, 0x00, 0x00, 0x00,
1603+
0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82};
1604+
static constexpr std::byte buffer[] = {
1605+
std::byte{1}, std::byte{2}, std::byte{3}, std::byte{11},
1606+
std::byte{12}, std::byte{13}, std::byte{21}, std::byte{22},
1607+
std::byte{23}, std::byte{31}, std::byte{32}, std::byte{33}};
1608+
1609+
auto vfs = std::make_unique<mjVFS>();
1610+
mj_defaultVFS(vfs.get());
1611+
mj_addBufferVFS(vfs.get(), "pixel.png", pixel, sizeof(pixel));
1612+
1613+
mjSpec* spec = mj_makeSpec();
1614+
mjsTexture* file = mjs_addTexture(spec);
1615+
mjs_setName(file->element, "file");
1616+
file->type = mjTEXTURE_2D;
1617+
mjs_setString(file->file, "pixel.png");
1618+
mjsTexture* builtin = mjs_addTexture(spec);
1619+
mjs_setName(builtin->element, "builtin");
1620+
builtin->type = mjTEXTURE_2D;
1621+
builtin->builtin = mjBUILTIN_CHECKER;
1622+
builtin->width = 4;
1623+
builtin->height = 4;
1624+
mjsTexture* buffered = mjs_addTexture(spec);
1625+
mjs_setName(buffered->element, "buffer");
1626+
buffered->type = mjTEXTURE_2D;
1627+
buffered->width = 2;
1628+
buffered->height = 2;
1629+
buffered->nchannel = 3;
1630+
buffered->hflip = 1;
1631+
mjs_setBuffer(buffered->data, buffer, sizeof(buffer));
1632+
1633+
mjModel* m = mj_compile(spec, vfs.get());
1634+
ASSERT_THAT(m, NotNull()) << mjs_getError(spec);
1635+
ASSERT_EQ(m->ntex, 3);
1636+
ASSERT_EQ(m->ntexdata, 3 + 48 + 12);
1637+
std::vector<mjtByte> expected(m->tex_data, m->tex_data + m->ntexdata);
1638+
EXPECT_EQ(expected[0], 255); // the pixel of the file is red
1639+
EXPECT_EQ(expected[m->tex_adr[2]], 11); // the buffer is flipped
1640+
1641+
// recompile with a warm cache, then with a cold one, then compile again
1642+
for (int i = 0; i < 3; i++) {
1643+
if (i == 1) mj_clearCache(mj_getCache());
1644+
if (i < 2) {
1645+
ASSERT_EQ(mj_recompile(spec, vfs.get(), m, nullptr), 0)
1646+
<< mjs_getError(spec);
1647+
} else {
1648+
mj_deleteModel(m);
1649+
m = mj_compile(spec, vfs.get());
1650+
ASSERT_THAT(m, NotNull()) << mjs_getError(spec);
1651+
}
1652+
ASSERT_EQ(m->ntexdata, expected.size()) << i;
1653+
EXPECT_EQ(std::vector<mjtByte>(m->tex_data, m->tex_data + m->ntexdata),
1654+
expected)
1655+
<< i;
1656+
}
1657+
1658+
mj_deleteModel(m);
1659+
mj_deleteSpec(spec);
1660+
mj_deleteVFS(vfs.get());
1661+
}
1662+
15911663
// ------------- test quaternion normalization----------------------------------
15921664

15931665
using QuatNorm = MujocoTest;

0 commit comments

Comments
 (0)
⌂ 换网址