render: skip textures the classic renderer can't upload
KTX textures crash the classic OpenGL renderer and the Python viewer with "Number of channels not supported: 1": mjCTexture::LoadKTX stores the file as an opaque single-channel blob for the Filament renderer, and mjr_uploadTexture aborts via mju_error because the classic path only handles 3- or 4-channel textures. Skip textures the classic renderer can't upload (nchannel not 3 or 4) with a warning instead of aborting, so the model still loads and the other textures render. LoadKTX and the Filament path are unchanged. The guard also covers the cube/skybox path, which previously over-read a single-channel blob as RGB. Add a render_test.py regression test that builds a model with a KTX texture and constructs an MjrContext; it aborts before this change and passes after. Addresses #3343.
This commit is contained in:
@@ -14,6 +14,8 @@
|
||||
# ==============================================================================
|
||||
"""Tests for MuJoCo Python rendering."""
|
||||
|
||||
import os
|
||||
|
||||
from absl.testing import absltest
|
||||
import mujoco
|
||||
import numpy as np
|
||||
@@ -104,6 +106,34 @@ class MuJoCoRenderTest(absltest.TestCase):
|
||||
context.free()
|
||||
context.free()
|
||||
|
||||
def test_ktx_texture_does_not_crash_classic_renderer(self):
|
||||
# KTX textures are stored as opaque single-channel blobs for the Filament
|
||||
# renderer; mjCTexture::LoadKTX does not decode them. The classic renderer
|
||||
# must skip such textures with a warning rather than aborting in
|
||||
# mjr_uploadTexture (https://github.com/google-deepmind/mujoco/issues/3343).
|
||||
ktx_path = os.path.join(self.create_tempdir().full_path, 'texture.ktx')
|
||||
with open(ktx_path, 'wb') as f:
|
||||
# LoadKTX stores the raw payload as a 1-channel blob without decoding, so
|
||||
# the contents do not matter; start with the KTX 2.0 identifier for realism.
|
||||
f.write(b'\xabKTX 20\xbb\r\n\x1a\n' + bytes(64))
|
||||
|
||||
model = mujoco.MjModel.from_xml_string(f"""
|
||||
<mujoco>
|
||||
<asset>
|
||||
<texture name="ktx" type="2d" file="{ktx_path}"/>
|
||||
<material name="mat" texture="ktx"/>
|
||||
</asset>
|
||||
<worldbody>
|
||||
<geom type="plane" size="1 1 .1" material="mat"/>
|
||||
</worldbody>
|
||||
</mujoco>""")
|
||||
self.assertEqual(model.tex_nchannel[0], 1)
|
||||
|
||||
# Building the render context uploads all textures; before the fix this
|
||||
# aborted with "Number of channels not supported: 1". It must not raise now.
|
||||
context = mujoco.MjrContext(model, mujoco.mjtFontScale.mjFONTSCALE_150)
|
||||
context.free()
|
||||
|
||||
def test_mjrrect_repr(self):
|
||||
rect = mujoco.MjrRect(1, 2, 3, 4)
|
||||
rect_repr = repr(rect)
|
||||
|
||||
@@ -1365,6 +1365,16 @@ void mjr_uploadTexture(const mjModel* m, const mjrContext* con, int texid) {
|
||||
int w = m->tex_width[texid];
|
||||
float plane[4];
|
||||
|
||||
// the classic renderer only uploads 3- (RGB) or 4-channel (RGBA) textures. KTX
|
||||
// and other encoded textures are single-channel blobs for the Filament renderer
|
||||
// (see LoadKTX); skip them with a warning instead of aborting (issue #3343).
|
||||
if (m->tex_nchannel[texid] != 3 && m->tex_nchannel[texid] != 4) {
|
||||
mju_warning("texture %d: classic renderer can't upload %d-channel data, "
|
||||
"skipping (KTX/encoded textures need the Filament renderer)",
|
||||
texid, m->tex_nchannel[texid]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2D texture
|
||||
if (m->tex_type[texid] == mjTEXTURE_2D) {
|
||||
// OpenGL settings
|
||||
@@ -1390,11 +1400,9 @@ void mjr_uploadTexture(const mjModel* m, const mjrContext* con, int texid) {
|
||||
if (m->tex_nchannel[texid] == 3) {
|
||||
type = GL_RGB;
|
||||
internaltype = (m->tex_colorspace[texid] == mjCOLORSPACE_SRGB) ? GL_SRGB8_EXT : GL_RGB;
|
||||
} else if (m->tex_nchannel[texid] == 4) {
|
||||
} else { // 4 channels, guaranteed by the channel check at the top of the function
|
||||
type = GL_RGBA;
|
||||
internaltype = (m->tex_colorspace[texid] == mjCOLORSPACE_SRGB) ? GL_SRGB8_ALPHA8_EXT : GL_RGBA;
|
||||
} else {
|
||||
mju_error("Number of channels not supported: %d", m->tex_nchannel[texid]);
|
||||
}
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, internaltype, m->tex_width[texid],
|
||||
|
||||
Reference in New Issue
Block a user