Files
Mujoco_WASM/src/engine/engine_vis_state.c
T
Nimrod Gileadi 7cb7c87f70 Fix a few issues with the passive viewer.
1. Create an arena for the mjData instance used by the passive viewer visualization.

When using the passive viewer, stuff gets copied from the real mjData into a minimal struct.
That struct didn't have a stack, and now visualization for Flex does stack allocs.

2. Add missing a missing field in scene state for flex visualization.

3. Fix a memory leak where mjvScene wasn't released on exit in the passive viewer.

4. Add some locks in places where the render thread and Simulate::Sync collide.

This fixes #1280.

PiperOrigin-RevId: 591891676
Change-Id: I592f286cab9719c8d9af84e42f9756ea1f9f1971
2023-12-18 07:33:19 -08:00

416 lines
13 KiB
C

// Copyright 2023 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "engine/engine_vis_state.h"
#include <string.h>
#include <mujoco/mjdata.h>
#include <mujoco/mjexport.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjvisualize.h>
#include <mujoco/mjxmacro.h>
#include "engine/engine_core_constraint.h"
#include "engine/engine_plugin.h"
#include "engine/engine_support.h"
#include "engine/engine_util_errmem.h"
#include "engine/engine_vis_init.h"
#include "engine/engine_vis_interact.h"
#include "engine/engine_vis_visualize.h"
#ifdef MEMORY_SANITIZER
#include <sanitizer/msan_interface.h>
#endif
// this source file needs to treat XMJV differently from other X macros
#undef XMJV
// round size up to multiples of 64-byte cache lines
static inline size_t roundUpToCacheLine(size_t n) {
return 64 * ((n / 64) + (n % 64 ? 1 : 0));
}
// set default scene
void mjv_defaultSceneState(mjvSceneState* scnstate) {
memset(scnstate, 0, sizeof(mjvSceneState));
mjv_defaultScene(&scnstate->scratch);
}
// allocate and init scene state
void mjv_makeSceneState(const mjModel* m, const mjData* d, mjvSceneState* scnstate, int maxgeom) {
mjv_freeScene(&scnstate->scratch);
mju_free(scnstate->buffer);
#ifdef MEMORY_SANITIZER
__msan_allocated_memory(scnstate, sizeof(mjvSceneState));
mjv_defaultScene(&scnstate->scratch);
#endif
scnstate->nbuffer = 0;
scnstate->maxgeom = maxgeom;
#define X(var)
#define XMJV(var) scnstate->model.var = m->var;
MJMODEL_INTS
#undef XMJV
#undef X
#define X(dtype, var, dim0, dim1)
#define XMJV(dtype, var, dim0, dim1) \
scnstate->nbuffer += roundUpToCacheLine(sizeof(dtype) * m->dim0 * dim1);
MJMODEL_POINTERS
#undef XMJV
#undef X
#define X(dtype, var, dim0, dim1)
#define XMJV(dtype, var, dim0, dim1) \
scnstate->nbuffer += roundUpToCacheLine(sizeof(dtype) * m->dim0 * dim1);
MJDATA_POINTERS
#undef XMJV
#undef X
// create an arena in the scnstate, to allow visualization code to use the stack.
// TODO: Consider allocating way less than narena, since stack allocations in
// visualization code are much smaller than the arena space required by the model,
// typically.
scnstate->nbuffer += roundUpToCacheLine(m->narena);
// buffer space required for contacts
int condimmax = mj_isPyramidal(m) ? 10 : 6;
scnstate->nbuffer += roundUpToCacheLine(sizeof(*d->contact) * maxgeom);
scnstate->nbuffer += roundUpToCacheLine(sizeof(*d->efc_force) * maxgeom * condimmax);
// buffer space required for islands
scnstate->nbuffer += roundUpToCacheLine(sizeof(*d->island_dofadr) * m->ntree);
scnstate->nbuffer += roundUpToCacheLine(sizeof(*d->island_dofind) * m->nv);
scnstate->nbuffer += roundUpToCacheLine(sizeof(*d->dof_island) * m->nv);
scnstate->nbuffer += roundUpToCacheLine(sizeof(*d->efc_island) * maxgeom * condimmax);
scnstate->nbuffer += roundUpToCacheLine(sizeof(*d->tendon_efcadr) * m->ntendon);
scnstate->buffer = mju_malloc(scnstate->nbuffer);
char* ptr = scnstate->buffer;
#define X(dtype, var, dim0, dim1)
#define XMJV(dtype, var, dim0, dim1) \
scnstate->model.var = (dtype*)ptr; \
ptr += roundUpToCacheLine(sizeof(dtype) * m->dim0 * dim1);
MJMODEL_POINTERS
#undef XMJV
#undef X
#define X(dtype, var, dim0, dim1)
#define XMJV(dtype, var, dim0, dim1) \
scnstate->data.var = (dtype*)ptr; \
ptr += roundUpToCacheLine(sizeof(dtype) * m->dim0 * dim1);
MJDATA_POINTERS
#undef XMJV
#undef X
scnstate->model.narena = m->narena;
scnstate->data.arena = (void*)ptr;
ptr += roundUpToCacheLine(m->narena);
scnstate->data.contact = (mjContact*)ptr;
ptr += roundUpToCacheLine(sizeof(*scnstate->data.contact) * scnstate->maxgeom);
scnstate->data.efc_force = (mjtNum*)ptr;
ptr += roundUpToCacheLine(sizeof(*scnstate->data.efc_force) * scnstate->maxgeom * condimmax);
scnstate->data.island_dofadr = (int*)ptr;
ptr += roundUpToCacheLine(sizeof(*scnstate->data.island_dofadr) * scnstate->model.ntree);
scnstate->data.island_dofind = (int*)ptr;
ptr += roundUpToCacheLine(sizeof(*scnstate->data.island_dofind) * scnstate->model.nv);
scnstate->data.dof_island = (int*)ptr;
ptr += roundUpToCacheLine(sizeof(*scnstate->data.dof_island) * scnstate->model.nv);
scnstate->data.efc_island = (int*)ptr;
ptr += roundUpToCacheLine(sizeof(*scnstate->data.efc_island) * scnstate->maxgeom * condimmax);
scnstate->data.tendon_efcadr = (int*)ptr;
ptr += roundUpToCacheLine(sizeof(*scnstate->data.tendon_efcadr) * m->ntendon);
// should not occur
if (ptr - (char*)scnstate->buffer != scnstate->nbuffer) {
mjERROR("mjvSceneState buffer is not fully used");
}
mjv_makeScene(m, &scnstate->scratch, maxgeom);
}
// free scene state
void mjv_freeSceneState(mjvSceneState* scnstate) {
mjv_freeScene(&scnstate->scratch);
mju_free(scnstate->buffer);
mjv_defaultSceneState(scnstate);
}
// shallow copy scene state into model and data for use with mjv functions
void mjv_assignFromSceneState(const mjvSceneState* scnstate, mjModel* m, mjData* d) {
if (m) {
memset(m, 0, sizeof(mjModel));
#ifdef MEMORY_SANITIZER
// Tell msan to treat the entire buffer as uninitialized
__msan_allocated_memory(m, sizeof(mjModel));
#endif
#define X(var)
#define XMJV(var) m->var = scnstate->model.var;
MJMODEL_INTS
#undef XMJV
#undef X
m->opt = scnstate->model.opt;
m->vis = scnstate->model.vis;
m->stat = scnstate->model.stat;
m->narena = scnstate->model.narena;
#define X(dtype, var, dim0, dim1)
#define XMJV(dtype, var, dim0, dim1) m->var = scnstate->model.var;
MJMODEL_POINTERS
#undef XMJV
#undef X
}
if (d) {
memset(d, 0, sizeof(mjData));
#ifdef MEMORY_SANITIZER
// Tell msan to treat the entire buffer as uninitialized
__msan_allocated_memory(d, sizeof(mjData));
#endif
memcpy(d->warning, scnstate->data.warning, sizeof(d->warning));
d->threadpool = 0;
d->nefc = scnstate->data.nefc;
d->ncon = scnstate->data.ncon;
d->nisland = scnstate->data.nisland;
d->time = scnstate->data.time;
d->narena = scnstate->model.narena;
d->arena = scnstate->data.arena;
d->parena = 0;
d->pbase = 0;
d->pstack = 0;
#define X(dtype, var, dim0, dim1)
#define XMJV(dtype, var, dim0, dim1) d->var = scnstate->data.var;
MJDATA_POINTERS
#undef XMJV
#undef X
d->contact = scnstate->data.contact;
d->efc_force = scnstate->data.efc_force;
d->island_dofadr = scnstate->data.island_dofadr;
d->island_dofind = scnstate->data.island_dofind;
d->dof_island = scnstate->data.dof_island;
d->efc_island = scnstate->data.efc_island;
d->tendon_efcadr = scnstate->data.tendon_efcadr;
}
}
// update entire scene from a scene state, return the number of new mjWARN_VGEOMFULL warnings
int mjv_updateSceneFromState(const mjvSceneState* scnstate, const mjvOption* opt,
const mjvPerturb* pert, mjvCamera* cam, int catmask, mjvScene* scn) {
// shallow-copy scnstate pointers into mjModel and mjData
mjModel m;
mjData d;
mjv_assignFromSceneState(scnstate, &m, &d);
// save the number of mjWARN_VGEOMFULL warnings before the scene update
int warning_start = d.warning[mjWARN_VGEOMFULL].number;
// copy mjvGeoms added by plugins
int nplugingeom = scnstate->scratch.ngeom;
if (nplugingeom > scn->maxgeom) {
mj_warning(&d, mjWARN_VGEOMFULL, scn->maxgeom);
scn->ngeom = scn->maxgeom;
} else {
scn->ngeom = nplugingeom;
}
memcpy(scn->geoms, scnstate->scratch.geoms, sizeof(mjvGeom) * scn->ngeom);
// add all categories
mjv_addGeoms(&m, &d, opt, pert, catmask, scn);
// update camera
mjv_updateCamera(&m, &d, cam, scn);
// add lights
mjv_makeLights(&m, &d, scn);
// update flexes
if (opt->flags[mjVIS_FLEXVERT] || opt->flags[mjVIS_FLEXEDGE] ||
opt->flags[mjVIS_FLEXFACE] || opt->flags[mjVIS_FLEXSKIN]) {
mjv_updateActiveFlex(&m, &d, scn, opt);
}
// update skins
if (opt->flags[mjVIS_SKIN]) {
mjv_updateActiveSkin(&m, &d, scn, opt);
}
// return the number of new mjWARN_VGEOMFULL warnings generated
return d.warning[mjWARN_VGEOMFULL].number - warning_start;
}
// update a scene state from model and data
void mjv_updateSceneState(const mjModel* m, mjData* d, const mjvOption* opt,
mjvSceneState* scnstate) {
// Check that mjModel sizes haven't changed.
#define X(var)
#define XMJV(var) \
if (scnstate->model.var != m->var) { \
mjERROR("m->%s changed: %d vs %d", #var, scnstate->model.var, m->var); \
}
MJMODEL_INTS
#undef XMJV
#undef X
// Update plugin visualization cache.
scnstate->scratch.ngeom = 0;
if (m->nplugin) {
const int nslot = mjp_pluginCount();
// iterate over plugins, call visualize if defined
for (int i=0; i < m->nplugin; i++) {
const int slot = m->plugin[i];
const mjpPlugin* plugin = mjp_getPluginAtSlotUnsafe(slot, nslot);
if (!plugin) {
mjERROR("invalid plugin slot: %d", slot);
}
if (plugin->visualize) {
plugin->visualize(m, d, opt, &scnstate->scratch, i);
}
}
}
// Copy variable-sized arrays in mjModel.
#define X(dtype, var, dim0, dim1)
#define XMJV(dtype, var, dim0, dim1) \
memcpy(scnstate->model.var, m->var, sizeof(dtype) * m->dim0 * dim1);
MJMODEL_POINTERS
#undef XMJV
#undef X
scnstate->model.opt = m->opt;
scnstate->model.vis = m->vis;
scnstate->model.stat = m->stat;
// Copy mjData variables.
memcpy(scnstate->data.warning, d->warning, sizeof(d->warning));
scnstate->data.time = d->time;
// Copy variable-sized arrays in mjData.
#define X(dtype, var, dim0, dim1)
#define XMJV(dtype, var, dim0, dim1) \
memcpy(scnstate->data.var, d->var, sizeof(dtype) * m->dim0 * dim1);
MJDATA_POINTERS
#undef XMJV
#undef X
// Copy contacts.
{
if (d->ncon > scnstate->maxgeom) {
mj_warning(d, mjWARN_VGEOMFULL, scnstate->maxgeom);
scnstate->data.ncon = scnstate->maxgeom;
} else {
scnstate->data.ncon = d->ncon;
}
memcpy(scnstate->data.contact, d->contact, sizeof(*d->contact) * scnstate->data.ncon);
}
// Copy only the entries in efc_force and efc_island that correspond to contacts.
{
scnstate->data.nefc = 0;
for (int i = 0; i < scnstate->data.ncon; ++i) {
const mjContact* con = &d->contact[i];
scnstate->data.nefc += con->dim;
}
scnstate->data.nefc += scnstate->model.ntendon;
int efc_address = 0;
int ispyramid = mj_isPyramidal(m);
for (int i = 0; i < scnstate->data.ncon; ++i) {
mjContact* con = &scnstate->data.contact[i];
int dim = con->dim;
if (ispyramid && dim > 1){
dim = 2*(dim - 1);
}
for (int j = 0; j < dim; ++j) {
scnstate->data.efc_force[efc_address + j] = d->efc_force[con->efc_address + j];
if (d->nisland) {
scnstate->data.efc_island[efc_address + j] = d->efc_island[con->efc_address + j];
}
}
con->efc_address = efc_address;
efc_address += dim;
}
if (d->nisland) {
for (int i = 0; i < scnstate->model.ntendon; ++i) {
int efcadr = d->tendon_efcadr[i];
if (efcadr != -1) {
scnstate->data.efc_island[efcadr] = d->efc_island[efcadr];
}
}
}
}
// Copy island data.
scnstate->data.nisland = d->nisland;
if (d->nisland) {
memcpy(scnstate->data.island_dofadr, d->island_dofadr, sizeof(*d->island_dofadr) * d->nisland);
memcpy(scnstate->data.island_dofind, d->island_dofind, sizeof(*d->island_dofind) * m->nv);
memcpy(scnstate->data.dof_island, d->dof_island, sizeof(*d->dof_island) * m->nv);
memcpy(scnstate->data.tendon_efcadr, d->tendon_efcadr, sizeof(*d->tendon_efcadr) * m->ntendon);
}
}
// move camera with mouse given a scene state; action is mjtMouse
MJAPI void mjv_moveCameraFromState(const mjvSceneState* scnstate, int action,
mjtNum reldx, mjtNum reldy,
const mjvScene* scn, mjvCamera* cam) {
mjModel m;
mjv_assignFromSceneState(scnstate, &m, NULL);
mjv_moveCamera(&m, action, reldx, reldy, scn, cam);
}
// move perturb object with mouse given a scene state; action is mjtMouse
MJAPI void mjv_movePerturbFromState(const mjvSceneState* scnstate, int action,
mjtNum reldx, mjtNum reldy,
const mjvScene* scn, mjvPerturb* pert) {
mjModel m;
mjData d;
mjv_assignFromSceneState(scnstate, &m, &d);
mjv_movePerturb(&m, &d, action, reldx, reldy, scn, pert);
}