From 50ef671fad532f5ebead40ee33643f94b2e2de95 Mon Sep 17 00:00:00 2001 From: Norbert Schulz Date: Mon, 5 Aug 2024 13:13:48 +0200 Subject: [PATCH 1/4] Improve sound support for external controller (#6591) (#6605) * Improve sound support for external controller (#6591) Stream sound data from controller to Webots if the sound file path is accesible by the controller process. This allows external controller to use sounds even if the file path is not accessible from Webots. * Controller: Try to load sound data on controller side and stream it to webots as part of the C_SPEAKER_PLAY_SOUND message. (speaker.c) * Webots: Read controller uploaded sound data in C_SPEAKER_PLAY_SOUND message (WbSpeaker). Use former file based load method if no data was streamed from controller. * Streamed sound data is cached inside WbSpeaker instance as controller provides the data only on first use of a sound file. * Updated speaker.md to explain when wb_speaker_play_sound() streams loads the sound data itself already. * Prevent copy/assignment of WbSoundClip as it now hold a heap allocated member (mDevice). * WbSpeaker.cpp - fix source indentation * Fix comment typo in speaker.c * Address cppcheck issues * Added changelog entry for this PR Fixed ubuntu tester src formatting issue. * Fix formatting issue in WbSpeaker.cpp * Fixed bug radar.py (#6606) * fix bug radar.py Fixed the bug that when using Python to write a controller, using getTargets() could not correctly obtain multiple target data detected by radar * Fixed the bug in obtaining radar detection target information#6606 Fixed the bug that when using Python to write a controller, using getTargets() could not correctly obtain multiple target data detected by radar --------- Co-authored-by: Olivier Michel * Added changelog entry for this PR Fixed ubuntu tester src formatting issue. * Addressed code review comments https://github.com/cyberbotics/webots/pull/6605 * Incorporated review feedback Added improvement provided by CoolSpy3. * Update src/controller/c/speaker.c --------- Co-authored-by: Olivier Michel Co-authored-by: Gabryel Reyes <66941456+gabryelreyes@users.noreply.github.com> Co-authored-by: lonely-poppy <77427326+lonely-poppy@users.noreply.github.com> --- docs/reference/changelog-r2024.md | 1 + docs/reference/speaker.md | 8 +-- src/controller/c/speaker.c | 53 +++++++++++++++++++ src/webots/nodes/WbSpeaker.cpp | 84 ++++++++++++++++++++++--------- src/webots/nodes/WbSpeaker.hpp | 31 +++++++++++- src/webots/sound/WbSoundClip.cpp | 3 +- src/webots/sound/WbSoundClip.hpp | 1 - 7 files changed, 149 insertions(+), 32 deletions(-) diff --git a/docs/reference/changelog-r2024.md b/docs/reference/changelog-r2024.md index 2567e763664..3ffeda84b3c 100644 --- a/docs/reference/changelog-r2024.md +++ b/docs/reference/changelog-r2024.md @@ -17,4 +17,5 @@ Released on December **th, 2023. - Fixed crashes (with some graphics cards) caused by references to unused GLSL uniforms ([#6587](https://github.com/cyberbotics/webots/pull/6587)). - Fixed `Brake`s added to the second axis of a `Hinge2Joint` being applied to the non-transformed axis ([#6584](https://github.com/cyberbotics/webots/pull/6584)). - Fixed invalid absolute sound file path resulted in crash ([#6593](https://github.com/cyberbotics/webots/pull/6593)) + - Fixed Speaker relative sound file path not working with external controller ([#6605](https://github.com/cyberbotics/webots/pull/6605)). - Fixed the bug that when the language is Python, getTargets() cannot correctly obtain the multi-target data detected by the radar ([#6606](https://github.com/cyberbotics/webots/pull/6606)) diff --git a/docs/reference/speaker.md b/docs/reference/speaker.md index 10806318332..06cff9f7bff 100644 --- a/docs/reference/speaker.md +++ b/docs/reference/speaker.md @@ -111,9 +111,11 @@ Finally, the boolean `loop` argument defines if the sound will be played only on It is possible to change the volume, pitch, balance, and loop parameters of a sound currently playing by calling again the `wb_speaker_play_sound` function with the same speakers and `sound` arguments. -> **Note**: The path to the sound file should be defined either absolutely or relatively. -If defined relatively, it will be searched first relatively to the robot controller folder. -If not found there and if the robot is a PROTO, it will be searched relatively to the PROTO folder of the robot. +> **Note**: The path to the sound file can be defined either absolutely or relatively. +If the file path is accessible by the robot controller either absolutely or relative to its working directory, it is loaded by the controller and its contents are streamed to Webots. +If the file is not found by the controller, the given path is passed to Webots. +Webots will first search for the path relative to the robot controller folder. +If the file is not found relative to the controller folder and the robot is a PROTO, Webots will search for the sound file relative to the folder of the PROTO file. --- diff --git a/src/controller/c/speaker.c b/src/controller/c/speaker.c index 306d63bbb25..fdf8472faf9 100644 --- a/src/controller/c/speaker.c +++ b/src/controller/c/speaker.c @@ -40,6 +40,8 @@ struct Sound { bool need_update; bool need_stop; bool is_playing; + unsigned char *upload_data; // sound data for uploading to webots + int upload_size; // size of sound data struct Sound *next; }; @@ -257,6 +259,18 @@ static void speaker_write_request(WbDevice *d, WbRequest *r) { else request_write_char(r, 0); } + + request_write_int32(r, sound->upload_size); + if (sound->upload_size) { + /* Sound data available to stream, send it one time and discard it. + * Webots will cache the data inside the device. + */ + request_write_data(r, sound->upload_data, sound->upload_size); + free(sound->upload_data); + sound->upload_data = NULL; + sound->upload_size = 0; + } + sound->need_update = false; sound = sound->next; } @@ -298,6 +312,43 @@ static void speaker_toggle_remote(WbDevice *d, WbRequest *r) { // nothing to do. } +/* Try to read sound file contents if it is found by given path. */ +static void speaker_try_load_sound_local(Sound *sound, const char *sound_file_name) { + FILE *fp; + + sound->upload_data = NULL; + sound->upload_size = 0; + + if ((fp = fopen(sound_file_name, "rb")) != NULL) { + if (fseek(fp, 0L, SEEK_END) == 0) { + const long file_size = ftell(fp); + if (file_size != -1) { + if (fseek(fp, 0L, SEEK_SET) == 0) { + size_t consumed = 0U; + sound->upload_data = malloc(file_size); + sound->upload_size = (int)file_size; + while (consumed < file_size) { + const size_t read = fread(&sound->upload_data[consumed], sizeof(unsigned char), file_size - consumed, fp); + if (ferror(fp)) { + free(sound->upload_data); + sound->upload_data = NULL; + sound->upload_size = 0; + break; + } + consumed += read; + } + } + } + } + + if (sound->upload_data == NULL) { + fprintf(stderr, "Warning: %s() failed to read sound file '%s' :", __FUNCTION__, sound_file_name); + perror(NULL); + } + fclose(fp); + } +} + static void speaker_play_sound(WbDevice *device, const char *sound_name, double volume, double pitch, double balance, bool loop, int side) { Sound *sound = speaker_get_sound_if_exist(device, sound_name); @@ -309,6 +360,8 @@ static void speaker_play_sound(WbDevice *device, const char *sound_name, double memcpy(sound->sound_file, sound_name, l); sound->next = ((Speaker *)device->pdata)->sound_list; ((Speaker *)device->pdata)->sound_list = sound; + + speaker_try_load_sound_local(sound, sound_name); } sound->volume = volume; sound->pitch = pitch; diff --git a/src/webots/nodes/WbSpeaker.cpp b/src/webots/nodes/WbSpeaker.cpp index 425dfdcfe8a..3f90886949e 100644 --- a/src/webots/nodes/WbSpeaker.cpp +++ b/src/webots/nodes/WbSpeaker.cpp @@ -27,6 +27,7 @@ #include "../../controller/c/messages.h" +#include #include #include @@ -61,6 +62,10 @@ WbSpeaker::~WbSpeaker() { } mSoundSourcesMap.clear(); mPlayingSoundSourcesMap.clear(); + + foreach (QByteArray *buffer, mStreamedSoundDataMap) + delete buffer; + mStreamedSoundDataMap.clear(); } void WbSpeaker::postFinalize() { @@ -79,21 +84,8 @@ void WbSpeaker::handleMessage(QDataStream &stream) { int numberOfSound = 0; stream >> numberOfSound; for (int i = 0; i < numberOfSound; ++i) { - short size; - short side; - double volume; - double pitch; - double balance; - unsigned char loop; - stream >> size; - char soundFile[size]; - stream.readRawData(soundFile, size); - stream >> volume; - stream >> pitch; - stream >> balance; - stream >> side; - stream >> loop; - playSound(soundFile, volume, pitch, balance, (bool)loop, (int)side); + SoundPlayData playData(stream); + playSound(playData); } return; } @@ -194,18 +186,28 @@ void WbSpeaker::playText(const char *text, double volume) { } } -void WbSpeaker::playSound(const char *file, double volume, double pitch, double balance, bool loop, int side) { - QString filename = QString(file); - QString key = filename; - if (side == -1) +void WbSpeaker::playSound(SoundPlayData &playData) { + const QString filename(playData.file()); + QString key(filename); + if (playData.side() == -1) key += "_left"; - else if (side == 1) + else if (playData.side() == 1) key += "_right"; + // Controller streamed sound data available ? + if (playData.rawLength() && !mStreamedSoundDataMap.contains(filename)) + mStreamedSoundDataMap[filename] = new QByteArray(playData.rawData()); + QByteArray *cachedSoundData = NULL; + if (mStreamedSoundDataMap.contains(key)) + cachedSoundData = mStreamedSoundDataMap[key]; + if (!mSoundSourcesMap.contains(key)) { // this sound was never played QString path; + // check if sound data was streamed from controller + if (cachedSoundData) + path = filename; // check if the path is absolute - if (QDir::isAbsolutePath(filename) && QFile::exists(filename)) + if (path.isEmpty() && QDir::isAbsolutePath(filename) && QFile::exists(filename)) path = filename; // check if the path is relative to the controller if (path.isEmpty() && QFile::exists(mControllerDir + filename)) @@ -221,7 +223,18 @@ void WbSpeaker::playSound(const char *file, double volume, double pitch, double WbSoundSource *source = WbSoundEngine::createSource(); updateSoundSource(source); const QString extension = filename.mid(filename.lastIndexOf('.') + 1).toLower(); - const WbSoundClip *soundClip = WbSoundEngine::sound(path, extension, NULL, balance, side); + + // Use controller streamed sound data if available. + QBuffer *device = NULL; + if (cachedSoundData) { + device = new QBuffer(cachedSoundData); + device->open(QBuffer::ReadOnly); + } + + const WbSoundClip *soundClip = WbSoundEngine::sound(path, extension, device, playData.balance(), playData.side()); + delete device; + device = NULL; + if (!soundClip) { this->warn(tr("Impossible to play '%1'. Make sure the file format is supported (8 or 16 bits, mono or stereo wave).\n") .arg(filename)); @@ -237,9 +250,9 @@ void WbSpeaker::playSound(const char *file, double volume, double pitch, double mPlayingSoundSourcesMap[key] = source; if (!source->isPlaying()) // this sound was already played but is over source->play(); - source->setLooping(loop); - source->setGain(volume); - source->setPitch(pitch); + source->setLooping(playData.loop()); + source->setGain(playData.volume()); + source->setPitch(playData.pitch()); if (WbSimulationState::instance()->isPaused() || WbSimulationState::instance()->isStep()) source->pause(); } @@ -280,3 +293,24 @@ void WbSpeaker::updateSoundSource(WbSoundSource *source) { source->setVelocity(linearVelocity()); source->setDirection(rotationMatrix() * WbVector3(0, 1, 0)); } + +WbSpeaker::SoundPlayData::SoundPlayData(QDataStream &stream) : mFile(), mRawData() { + short size; + unsigned char loopByte; + stream >> size; + char soundFile[size]; + stream.readRawData(soundFile, size); + mFile = QString(soundFile); + + stream >> mVolume; + stream >> mPitch; + stream >> mBalance; + stream >> mSide; + stream >> loopByte; + mLoop = (bool)loopByte; + stream >> mRawLength; + if (mRawLength) { + mRawData.resize(mRawLength); + stream.readRawData(mRawData.data(), mRawLength); + } +} diff --git a/src/webots/nodes/WbSpeaker.hpp b/src/webots/nodes/WbSpeaker.hpp index af76aa1695f..cb2076f1303 100644 --- a/src/webots/nodes/WbSpeaker.hpp +++ b/src/webots/nodes/WbSpeaker.hpp @@ -20,6 +20,7 @@ #include class WbSoundSource; +class QDataStream; class WbSpeaker : public WbSolidDevice { Q_OBJECT @@ -39,12 +40,39 @@ class WbSpeaker : public WbSolidDevice { void postPhysicsStep() override; private: + // private data types + + // Data from wb_speaker_play_sound() API + class SoundPlayData { + public: + explicit SoundPlayData(QDataStream &); + + const QString &file() const { return mFile; } + double volume() const { return mVolume; } + double pitch() const { return mPitch; } + double balance() const { return mBalance; } + bool loop() const { return mLoop; } + short side() const { return mSide; } + int rawLength() const { return mRawLength; } + QByteArray &rawData() { return mRawData; } + + private: + QString mFile; + double mVolume; + double mPitch; + double mBalance; + bool mLoop; + short mSide; + int mRawLength; + QByteArray mRawData; + }; + // private functions WbSpeaker &operator=(const WbSpeaker &); // non copyable WbNode *clone() const override { return new WbSpeaker(*this); } void init(); - void playSound(const char *file, double volume, double pitch, double balance, bool loop, int side); + void playSound(SoundPlayData &playData); void playText(const char *text, double volume); void stop(const char *sound); void stopAll(); @@ -53,6 +81,7 @@ class WbSpeaker : public WbSolidDevice { QMap mSoundSourcesMap; QMap mPlayingSoundSourcesMap; + QMap mStreamedSoundDataMap; QString mControllerDir; QString mEngine; diff --git a/src/webots/sound/WbSoundClip.cpp b/src/webots/sound/WbSoundClip.cpp index 2f6f9e95770..867bd9b94d3 100644 --- a/src/webots/sound/WbSoundClip.cpp +++ b/src/webots/sound/WbSoundClip.cpp @@ -28,7 +28,7 @@ #include #endif -WbSoundClip::WbSoundClip() : mDevice(NULL), mBuffer(0), mSide(0), mBalance(0.0) { +WbSoundClip::WbSoundClip() : mBuffer(0), mSide(0), mBalance(0.0) { } WbSoundClip::~WbSoundClip() { @@ -46,7 +46,6 @@ void WbSoundClip::load(const QString &filename, const QString &extension, QIODev mFilename = wave.filename(); mSide = side; mBalance = balance; - mDevice = device; load(&wave); } diff --git a/src/webots/sound/WbSoundClip.hpp b/src/webots/sound/WbSoundClip.hpp index fca133fea05..0869a24066c 100644 --- a/src/webots/sound/WbSoundClip.hpp +++ b/src/webots/sound/WbSoundClip.hpp @@ -38,7 +38,6 @@ class WbSoundClip { protected: QString mFilename; - QIODevice *mDevice; unsigned int mBuffer; int mSide; // 0: both sides, -1: left only, 1: right only double mBalance; From 6b49409db7e4a0e32e376df7dbcd3321125eb8b0 Mon Sep 17 00:00:00 2001 From: Dean Brettle Date: Sun, 11 Aug 2024 11:15:55 -0700 Subject: [PATCH 2/4] Fix unitialized sliding friction when using asymmetric rolling friction. (#6618) * Fix unitialized sliding friction when using asymmetric rolling friction. Also update expected values in test to reflect the result of the change. Previously mu2 was initialized when using asymmetric rolling friction, but ODE will use asymmetric sliding friction if we set `dContactAxisDep` (because `dContactAxisDep == dContactMu2`), so we need to make sure `mu2` is set. The unitialized mu2 was resulting in the rolling_friction test failing under some circumstances. * Update changelog. --- docs/reference/changelog-r2024.md | 1 + src/webots/engine/WbSimulationCluster.cpp | 9 ++++++++- .../rolling_friction_supervisor.c | 10 +++++----- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/docs/reference/changelog-r2024.md b/docs/reference/changelog-r2024.md index 3ffeda84b3c..880c9f724b7 100644 --- a/docs/reference/changelog-r2024.md +++ b/docs/reference/changelog-r2024.md @@ -19,3 +19,4 @@ Released on December **th, 2023. - Fixed invalid absolute sound file path resulted in crash ([#6593](https://github.com/cyberbotics/webots/pull/6593)) - Fixed Speaker relative sound file path not working with external controller ([#6605](https://github.com/cyberbotics/webots/pull/6605)). - Fixed the bug that when the language is Python, getTargets() cannot correctly obtain the multi-target data detected by the radar ([#6606](https://github.com/cyberbotics/webots/pull/6606)) + - Fixed unitialized sliding friction when using asymmetric rolling friction ([#6618](https://github.com/cyberbotics/webots/pull/6618)). diff --git a/src/webots/engine/WbSimulationCluster.cpp b/src/webots/engine/WbSimulationCluster.cpp index eec18e00ef9..2d9abbeeb66 100644 --- a/src/webots/engine/WbSimulationCluster.cpp +++ b/src/webots/engine/WbSimulationCluster.cpp @@ -320,8 +320,15 @@ void WbSimulationCluster::fillSurfaceParameters(const WbContactProperties *cp, c // handle rolling friction if (rho[0] > 0 || rho[1] > 0 || rho[2] > 0) { contact->surface.mode = contact->surface.mode | dContactRolling; - if (rho[1] > 0 || rho[2] > 0) + if (rho[1] > 0 || rho[2] > 0) { + // ODE will use asymmetric sliding friction if we set dContactAxisDep (because dContactAxisDep + // == dContactMu2), so we need to make sure mu2 is set. If it wasn't already set above, we + // want symmetric sliding friction which means setting mu2 = mu (like ODE would do if + // dContactMu2 was not set.) + if (!(contact->surface.mode & dContactMu2)) + contact->surface.mu2 = contact->surface.mu; contact->surface.mode = contact->surface.mode | dContactAxisDep; + } contact->surface.rho = rho[0]; contact->surface.rho2 = rho[1]; diff --git a/tests/physics/controllers/rolling_friction_supervisor/rolling_friction_supervisor.c b/tests/physics/controllers/rolling_friction_supervisor/rolling_friction_supervisor.c index 265c4a8bd90..4bcb50430b0 100644 --- a/tests/physics/controllers/rolling_friction_supervisor/rolling_friction_supervisor.c +++ b/tests/physics/controllers/rolling_friction_supervisor/rolling_friction_supervisor.c @@ -98,9 +98,9 @@ int main(int argc, char **argv) { // pre-registered velocities/positions after 200 timesteps (to ensure that if changes to the contact properties affect the // rolling behavior it will be detected) const double expected_positions[BALL_COUNT][3] = { - {-5.822856, -3.0, 0.498430}, {-14.564553, 0.0, 0.498392}, {-25.145860, 3.0, 1.942235}, - {-16.0, -8.0, 1.498430}, {-14.0, -8.0, 1.498430}, {-12.0, -8.0, 1.498430}, - {-5.822856, -27.511330, 0.498430}, {1.171054, -19.324655, 0.498430}, {1.171054, -13.936790, 0.498430}}; + {-5.822856, -3.0, 0.498430}, {-14.564553, 0.0, 0.498392}, {-25.145860, 3.0, 1.942235}, + {-16.0, -8.0, 1.498430}, {-14.0, -8.0, 1.498430}, {-12.0, -8.0, 1.498430}, + {-5.822856, -27.511330, 0.498430}, {-5.822856, -19.314704, 0.498430}, {-5.822856, -13.936790, 0.498430}}; const double expected_velocities[BALL_COUNT][6] = {{5.957204, 0.0, 0.0, 0.0, 11.951927, 0.0}, {4.164077, 0.0, 0.000304, 0.0, 8.355176, 0.0}, {1.817242, 0.0, -0.368373, 0.0, 3.719816, 0.0}, @@ -108,8 +108,8 @@ int main(int argc, char **argv) { {0.0, 0.0, 0.0, 0.0, 0.0, 6.164800}, {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, {5.957204, 3.686891, 0.0, -7.397003, 11.951927, -3.446233}, - {7.029157, 0.0, 0.0, 0.0, 0.0, -4.455340}, - {7.029157, 0.0, 0.0, 0.0, 0.0, -4.867729}}; + {5.957204, 0.0, 0.0, 0.0, 11.951927, -4.639518}, + {5.957204, 0.0, 0.0, 0.0, 11.951927, -4.867729}}; for (int i = 0; i < BALL_COUNT; ++i) { sprintf(name, "BALL_%d", i + 1); From fe2879ccc95f40db7a57f3395a811093092e68f2 Mon Sep 17 00:00:00 2001 From: CoolSpy3 <55305038+CoolSpy3@users.noreply.github.com> Date: Mon, 12 Aug 2024 13:05:58 -0700 Subject: [PATCH 3/4] Allow Indirect Field Accesses in Proto Templates (#6614) * allow indirectly referencing proto fields * update other regular expression * add test * fix test worlds * update changelog * use spaces for indentation * update proto headers * run clang-format * enable fields variable with a tag * update regex tests * update docs * update changelog * run clang-format * add curly brackets * fix comment indentation * fix proto name * fix controller name * update the changelog * fix indentation Co-authored-by: Olivier Michel --------- Co-authored-by: Olivier Michel --- docs/reference/changelog-r2024.md | 1 + docs/reference/javascript-procedural-proto.md | 10 +++ docs/reference/lua-procedural-proto.md | 10 +++ src/webots/vrml/WbProtoModel.cpp | 64 +++++++++++-------- src/webots/vrml/WbProtoModel.hpp | 3 +- .../template_indirect_field_access/.gitignore | 1 + .../template_indirect_field_access/Makefile | 19 ++++++ .../template_indirect_field_access.c | 19 ++++++ .../protos/TemplateIndirectFieldAccess.proto | 18 ++++++ .../TemplateIndirectFieldAccessLua.proto | 17 +++++ .../worlds/template_indirect_field_access.wbt | 39 +++++++++++ .../template_indirect_field_access_lua.wbt | 39 +++++++++++ 12 files changed, 213 insertions(+), 27 deletions(-) create mode 100644 tests/protos/controllers/template_indirect_field_access/.gitignore create mode 100644 tests/protos/controllers/template_indirect_field_access/Makefile create mode 100644 tests/protos/controllers/template_indirect_field_access/template_indirect_field_access.c create mode 100644 tests/protos/protos/TemplateIndirectFieldAccess.proto create mode 100644 tests/protos/protos/TemplateIndirectFieldAccessLua.proto create mode 100644 tests/protos/worlds/template_indirect_field_access.wbt create mode 100644 tests/protos/worlds/template_indirect_field_access_lua.wbt diff --git a/docs/reference/changelog-r2024.md b/docs/reference/changelog-r2024.md index 880c9f724b7..10f032d6647 100644 --- a/docs/reference/changelog-r2024.md +++ b/docs/reference/changelog-r2024.md @@ -5,6 +5,7 @@ Released on December **th, 2023. - New Features - **Change the name of the web scene format from `X3D` to `W3D` ([#6280](https://github.com/cyberbotics/webots/pull/6280)).** - Removed support for macOS 11 "Big Sur" and added support for macOS 14 "Sonoma" ([#6580](https://github.com/cyberbotics/webots/pull/6580)). + - Added the `indirectFieldAccess` tag to allow the `fields` variable to be used in proto templates without referencing a specific field ([#6614](https://github.com/cyberbotics/webots/pull/6614)). - Enhancements - Improved the image range of the rotating [Lidar](lidar.md) ([#6324](https://github.com/cyberbotics/webots/pull/6324)). - Cleanup diff --git a/docs/reference/javascript-procedural-proto.md b/docs/reference/javascript-procedural-proto.md index d10426701cd..9bf26da048b 100644 --- a/docs/reference/javascript-procedural-proto.md +++ b/docs/reference/javascript-procedural-proto.md @@ -27,6 +27,8 @@ The first represents the effective value of the field (for instance the one defi - As shown in [this table](#vrml97-type-to-javascript-type-conversion), the conversion of a VRML97 node is an object. This object contains the following keys: "node\_name" containing the VRML97 node name and "fields" which is in turn an object containing the JavaScript representation of the VRML97 node fields. This object is equal to `undefined` if the VRML97 node is not defined (`NULL`). +- By default, the parser only detects fields that are accessed directly in the template statements (i.e. `fields.appearance`). +If you would like to access the `fields` object without referencing a specific field, you can add the following line at the beginning of the PROTO file: `# tags: indirectFieldAccess`. - Objects that are part of [ECMA-262](http://www.ecma-international.org/publications/standards/Ecma-262.htm) are built-in and globally accessible, such as `Math`, `Date` and `String`. - The `context` object provides contextual information about the PROTO. Table [this table](#content-of-the-context-object) shows the available information and the corresponding keys. @@ -823,6 +825,14 @@ The location of this path can be retrieved from the `context` field object, see %end +### PROTO Regeneration +When a field used in a template statement is modified, the PROTO node is regenerated. +This means that the template statements are re-evaluated and the PROTO node is reloaded in the world. +For most nodes, this behavior should not affect the simulation. +However, special care should be taken when using PROTOs that have side effects (e.g. writing to a file). +Additionally, Robot nodes will restart their controllers when regenerated. +Using `tags: indirectFieldAccess` in the PROTO file will cause the PROTO to be regenerated whenever any field is modified, even if it is not used in a template statement. + ### Optimization By default, PROTO files are considered to be deterministic. diff --git a/docs/reference/lua-procedural-proto.md b/docs/reference/lua-procedural-proto.md index 04cbf5ed519..2f1f9aea87a 100644 --- a/docs/reference/lua-procedural-proto.md +++ b/docs/reference/lua-procedural-proto.md @@ -24,6 +24,8 @@ The conversion between the VRML97 types and the Lua types is detailed in [this t This dictionary contains the following keys: "node\_name" containing the VRML97 node name and "fields" which is a dictionary containing the Lua representation of the VRML97 node fields. This dictionary is equal to `nil` if the VRML97 node is not defined (`NULL`). For example, in the SimpleStairs example below, the `fields.appearance.node_name` key contains the `'Appearance'` string. +- By default, the parser only detects fields that are accessed directly in the template statements (i.e. `fields.appearance`). +If you would like to access the `fields` object without referencing a specific field, you can add the following line at the beginning of the PROTO file: `# tags: indirectFieldAccess`. - The `context` dictionary provides contextual information about the PROTO. Table [this table](#content-of-the-context-dictionary) shows the available information and its corresponding keys. - The VRML97 comment ("#") prevails over the Lua statements. @@ -88,6 +90,14 @@ The following standard fonts are available to write on the texture: In addition to these fonts, it is possible to add other TrueType fonts file in your `PROJECT_HOME/fonts` directory. +### PROTO Regeneration +When a field used in a template statement is modified, the PROTO node is regenerated. +This means that the template statements are re-evaluated and the PROTO node is reloaded in the world. +For most nodes, this behavior should not affect the simulation. +However, special care should be taken when using PROTOs that have side effects (e.g. writing to a file). +Additionally, Robot nodes will restart their controllers when regenerated. +Using `tags: indirectFieldAccess` in the PROTO file will cause the PROTO to be regenerated whenever any field is modified, even if it is not used in a template statement. + ### Optimization By default, PROTO files are considered to be deterministic. diff --git a/src/webots/vrml/WbProtoModel.cpp b/src/webots/vrml/WbProtoModel.cpp index 4a2a13cccc8..07de98d3812 100644 --- a/src/webots/vrml/WbProtoModel.cpp +++ b/src/webots/vrml/WbProtoModel.cpp @@ -69,6 +69,7 @@ WbProtoModel::WbProtoModel(WbTokenizer *tokenizer, const QString &worldPath, con mDocumentationUrl = tokenizer->documentationUrl(); mTemplateLanguage = tokenizer->templateLanguage(); mIsDeterministic = !mTags.contains("nonDeterministic"); + mHasIndirectFieldAccess = mTags.contains("indirectFieldAccess"); WbParser parser(tokenizer); while (tokenizer->peekWord() == "EXTERNPROTO" || tokenizer->peekWord() == "IMPORTABLE") // consume EXTERNPROTO declarations @@ -209,15 +210,23 @@ WbProtoModel::WbProtoModel(WbTokenizer *tokenizer, const QString &worldPath, con previousToken = token; token = tokenizer->nextToken(); + if (mHasIndirectFieldAccess) { + foreach (WbFieldModel *model, mFieldModels) + model->setTemplateRegenerator(true); + } + if (token->isTemplateStatement()) { mTemplate = true; - foreach (WbFieldModel *model, mFieldModels) { - // condition explanation: if (token contains modelName and not a Lua identifier containing modelName such as - // "my_awesome_modelName") - if (token->word().contains(QRegularExpression( - QString("(^|[^a-zA-Z0-9_])fields\\.%1($|[^a-zA-Z0-9_])").arg(QRegularExpression::escape(model->name()))))) { - model->setTemplateRegenerator(true); + if (!mHasIndirectFieldAccess) { // If the proto has indirect field access, we've already set the fields as template + // regenerators + foreach (WbFieldModel *model, mFieldModels) { + // condition explanation: if (token contains modelName and not a Lua identifier containing modelName such as + // "my_awesome_modelName") or (token contains fields and not a Lua identifier containing fields such as "my_fields") + if (token->word().contains( + QRegularExpression(QString("(^|\\W)fields\\.%1($|\\W)").arg(QRegularExpression::escape(model->name()))))) { + model->setTemplateRegenerator(true); + } } } } else if (readBaseType) { @@ -274,26 +283,29 @@ WbProtoModel::WbProtoModel(WbTokenizer *tokenizer, const QString &worldPath, con throw 0; } } else if (token->isString()) { - // check which parameter need to regenerate the template instance from inside a string - foreach (WbFieldModel *model, mFieldModels) { - // regex test cases: - // "You know nothing, John Snow." => false - // "%{=fields.model->name()}%" => false - // "%{= fields.model->name().value.x }% %{= fields.model->name().value.y }%" => true - // "abc %{= fields.model->name().value.y }% def" => true - // "%{= 17 % fields.model->name().value.y * 88 }%" => true - // "fields.model->name().value.y" => false - // "%{}% fields.model->name().value.y %{}%" => false - // "%{ a = \"fields.model->name().value.y\" }%" => false - // "%{= \"fields.model->name().value.y\" }%" => false - // "%{= fields.model->name().value.y }%" => true - if (token->word().contains(QRegularExpression(QString("%1(?:(?!%2|\").)*fields\\.%3(?:(?!%4|\").)*%5") - .arg(open) - .arg(close) - .arg(QRegularExpression::escape(model->name())) - .arg(close) - .arg(close)))) - model->setTemplateRegenerator(true); + if (!mHasIndirectFieldAccess) { // If the proto has indirect field access, we've already set the fields as template + // regenerators + // check which parameter need to regenerate the template instance from inside a string + foreach (WbFieldModel *model, mFieldModels) { + // regex test cases: + // "You know nothing, John Snow." => false + // "%{=fields.model->name()}%" => true + // "%{= fields.model->name().value.x }% %{= fields.model->name().value.y }%" => true + // "abc %{= fields.model->name().value.y }% def" => true + // "%{= 17 % fields.model->name().value.y * 88 }%" => true + // "fields.model->name().value.y" => false + // "%{}% fields.model->name().value.y %{}%" => false + // "%{ a = \"fields.model->name().value.y\" }%" => false + // "%{= \"fields.model->name().value.y\" }%" => false + // "%{= fields.model->name().value.y }%" => true + if (token->word().contains(QRegularExpression(QString("%1(?:(?!%2|\").)*fields\\.%3(?:(?!%4|\").)*%5") + .arg(open) + .arg(close) + .arg(QRegularExpression::escape(model->name())) + .arg(close) + .arg(close)))) + model->setTemplateRegenerator(true); + } } } diff --git a/src/webots/vrml/WbProtoModel.hpp b/src/webots/vrml/WbProtoModel.hpp index dccf030bd97..49fca7c8971 100644 --- a/src/webots/vrml/WbProtoModel.hpp +++ b/src/webots/vrml/WbProtoModel.hpp @@ -125,7 +125,8 @@ class WbProtoModel : public QObject { WbVersion mFileVersion; QString mName; QString mInfo; - bool mIsDeterministic; // i.e doesn't have the 'nonDeterministic' tag + bool mIsDeterministic; // i.e doesn't have the 'nonDeterministic' tag + bool mHasIndirectFieldAccess; // i.e. has the 'indirectFieldAccess' tag QList mFieldModels; QString mUrl; // how the PROTO is referenced diff --git a/tests/protos/controllers/template_indirect_field_access/.gitignore b/tests/protos/controllers/template_indirect_field_access/.gitignore new file mode 100644 index 00000000000..7abe0a8a413 --- /dev/null +++ b/tests/protos/controllers/template_indirect_field_access/.gitignore @@ -0,0 +1 @@ +/template_indirect_field_access diff --git a/tests/protos/controllers/template_indirect_field_access/Makefile b/tests/protos/controllers/template_indirect_field_access/Makefile new file mode 100644 index 00000000000..9734e04d8e9 --- /dev/null +++ b/tests/protos/controllers/template_indirect_field_access/Makefile @@ -0,0 +1,19 @@ +# Copyright 1996-2023 Cyberbotics Ltd. +# +# 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 +# +# https://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. + +### Do not modify: this includes Webots global Makefile.include +null := +space := $(null) $(null) +WEBOTS_HOME_PATH?=$(subst $(space),\ ,$(strip $(subst \,/,$(WEBOTS_HOME)))) +include $(WEBOTS_HOME_PATH)/resources/Makefile.include diff --git a/tests/protos/controllers/template_indirect_field_access/template_indirect_field_access.c b/tests/protos/controllers/template_indirect_field_access/template_indirect_field_access.c new file mode 100644 index 00000000000..6a5605d5473 --- /dev/null +++ b/tests/protos/controllers/template_indirect_field_access/template_indirect_field_access.c @@ -0,0 +1,19 @@ +#include +#include +#include + +#include "../../../lib/ts_assertion.h" +#include "../../../lib/ts_utils.h" + +#define TIME_STEP 32 + +int main(int argc, char **argv) { + ts_setup(argv[1]); // give the controller args + WbNodeRef proto = wb_supervisor_node_get_from_def("PROTO_template_indirect_field_access"); + WbFieldRef radarCrossSection = wb_supervisor_node_get_proto_field(proto, "radarCrossSection"); + ts_assert_double_in_delta(wb_supervisor_field_get_sf_float(radarCrossSection), 6.0, 0.0001, + "radarCrossSection should be 6.0"); + ts_send_success(); + + return EXIT_SUCCESS; +} diff --git a/tests/protos/protos/TemplateIndirectFieldAccess.proto b/tests/protos/protos/TemplateIndirectFieldAccess.proto new file mode 100644 index 00000000000..b115bed1463 --- /dev/null +++ b/tests/protos/protos/TemplateIndirectFieldAccess.proto @@ -0,0 +1,18 @@ +#VRML_SIM R2024a utf8 +# tags: indirectFieldAccess +# template language: javascript + +PROTO TemplateIndirectFieldAccess [ + unconnectedField SFFloat field1 1 + unconnectedField SFFloat field2 5 +] +{ + Solid { + %< + function sum(fieldsObj) { + return fieldsObj.field1.value + fieldsObj.field2.value; + } + >% + radarCrossSection %<= sum(fields) >% + } +} diff --git a/tests/protos/protos/TemplateIndirectFieldAccessLua.proto b/tests/protos/protos/TemplateIndirectFieldAccessLua.proto new file mode 100644 index 00000000000..d6b9a0c20e3 --- /dev/null +++ b/tests/protos/protos/TemplateIndirectFieldAccessLua.proto @@ -0,0 +1,17 @@ +#VRML_SIM R2024a utf8 +# tags: indirectFieldAccess + +PROTO TemplateIndirectFieldAccessLua [ + unconnectedField SFFloat field1 1 + unconnectedField SFFloat field2 5 +] +{ + Solid { + %{ + function sum(fieldsObj) + return fieldsObj.field1.value + fieldsObj.field2.value; + end + }% + radarCrossSection %{= sum(fields) }% + } +} diff --git a/tests/protos/worlds/template_indirect_field_access.wbt b/tests/protos/worlds/template_indirect_field_access.wbt new file mode 100644 index 00000000000..1d79b67a155 --- /dev/null +++ b/tests/protos/worlds/template_indirect_field_access.wbt @@ -0,0 +1,39 @@ +#VRML_SIM R2024a utf8 + +EXTERNPROTO "webots://tests/protos/protos/TemplateIndirectFieldAccess.proto" +EXTERNPROTO "webots://tests/default/protos/TestSuiteEmitter.proto" +EXTERNPROTO "webots://tests/default/protos/TestSuiteSupervisor.proto" + +WorldInfo { + coordinateSystem "NUE" + lineScale 1 +} +Viewpoint { + orientation -0.8999515417256047 0.42450878383113183 0.0993957493855782 0.509061 + position 0.303563 0.46348 0.936228 +} +Background { + skyColor [ + 0 0.811765 0.992157 + ] +} +DirectionalLight { + direction 0 0.5 -1 +} +Robot { + translation 0.1 0 0.15 + rotation 0 1 0 1.5708 + children [ + DEF PROTO_template_indirect_field_access TemplateIndirectFieldAccess { + } + TestSuiteEmitter { + } + ] + controller "template_indirect_field_access" + controllerArgs [ + "template_indirect_field_access" + ] + supervisor TRUE +} +TestSuiteSupervisor { +} diff --git a/tests/protos/worlds/template_indirect_field_access_lua.wbt b/tests/protos/worlds/template_indirect_field_access_lua.wbt new file mode 100644 index 00000000000..b16b039f496 --- /dev/null +++ b/tests/protos/worlds/template_indirect_field_access_lua.wbt @@ -0,0 +1,39 @@ +#VRML_SIM R2024a utf8 + +EXTERNPROTO "webots://tests/protos/protos/TemplateIndirectFieldAccessLua.proto" +EXTERNPROTO "webots://tests/default/protos/TestSuiteEmitter.proto" +EXTERNPROTO "webots://tests/default/protos/TestSuiteSupervisor.proto" + +WorldInfo { + coordinateSystem "NUE" + lineScale 1 +} +Viewpoint { + orientation -0.8999515417256047 0.42450878383113183 0.0993957493855782 0.509061 + position 0.303563 0.46348 0.936228 +} +Background { + skyColor [ + 0 0.811765 0.992157 + ] +} +DirectionalLight { + direction 0 0.5 -1 +} +Robot { + translation 0.1 0 0.15 + rotation 0 1 0 1.5708 + children [ + DEF PROTO_template_indirect_field_access TemplateIndirectFieldAccessLua { + } + TestSuiteEmitter { + } + ] + controller "template_indirect_field_access" + controllerArgs [ + "template_indirect_field_access (lua)" + ] + supervisor TRUE +} +TestSuiteSupervisor { +} From 0bcfb17c42b23e97645484ca8183817924ad501c Mon Sep 17 00:00:00 2001 From: Dean Brettle Date: Wed, 14 Aug 2024 09:37:29 -0700 Subject: [PATCH 4/4] Fix incomplete loading while minimized under some windowing systems. (#6617) * Fix incomplete loading under some windowing systems. * Add changelog entry. * Apply suggestions from code review Co-authored-by: Olivier Michel * Revert "Apply suggestions from code review" This reverts commit 7804a09cb772339c93687de95e333441e2f7ab43. * Revert "Fix incomplete loading under some windowing systems." This reverts commit 0d9dbb29d6ffb8cdc1dc675c6c26b444a6d94eed. * Rework approach to ensure that only offscreen framebuffers are used for the initial render. --------- Co-authored-by: Olivier Michel --- docs/reference/changelog-r2024.md | 1 + include/wren/scene.h | 5 +++-- src/webots/gui/WbView3D.cpp | 9 +++++---- src/webots/gui/WbView3D.hpp | 2 +- src/webots/gui/WbWrenWindow.cpp | 9 +++++---- src/webots/gui/WbWrenWindow.hpp | 2 +- .../nodes/utils/WbVirtualRealityHeadset.cpp | 2 +- src/webots/wren/WbWrenCamera.cpp | 2 +- src/webots/wren/WbWrenPicker.cpp | 2 +- src/wren/Scene.cpp | 17 +++++++++-------- src/wren/Scene.hpp | 4 ++-- src/wren/Viewport.cpp | 11 ----------- src/wren/demo/default.cpp | 2 +- src/wren/demo/phong.cpp | 2 +- 14 files changed, 32 insertions(+), 38 deletions(-) diff --git a/docs/reference/changelog-r2024.md b/docs/reference/changelog-r2024.md index 10f032d6647..54add51c1ef 100644 --- a/docs/reference/changelog-r2024.md +++ b/docs/reference/changelog-r2024.md @@ -20,4 +20,5 @@ Released on December **th, 2023. - Fixed invalid absolute sound file path resulted in crash ([#6593](https://github.com/cyberbotics/webots/pull/6593)) - Fixed Speaker relative sound file path not working with external controller ([#6605](https://github.com/cyberbotics/webots/pull/6605)). - Fixed the bug that when the language is Python, getTargets() cannot correctly obtain the multi-target data detected by the radar ([#6606](https://github.com/cyberbotics/webots/pull/6606)) + - Fixed incomplete loading while minimized under some windowing systems ([#6617](https://github.com/cyberbotics/webots/pull/6617)). - Fixed unitialized sliding friction when using asymmetric rolling friction ([#6618](https://github.com/cyberbotics/webots/pull/6618)). diff --git a/include/wren/scene.h b/include/wren/scene.h index 9788bbb2935..ea0d7fc1d0d 100644 --- a/include/wren/scene.h +++ b/include/wren/scene.h @@ -42,8 +42,9 @@ void wr_scene_apply_pending_updates(WrScene *scene); /* The 'materialName' parameter is optional (can be set to NULL for default), and if set, force the renderables to use the named material */ -void wr_scene_render(WrScene *scene, const char *material_name, bool culling); -void wr_scene_render_to_viewports(WrScene *scene, int count, WrViewport **viewports, const char *material_name, bool culling); +void wr_scene_render(WrScene *scene, const char *material_name, bool culling, bool offScreen); +void wr_scene_render_to_viewports(WrScene *scene, int count, WrViewport **viewports, const char *material_name, bool culling, + bool offScreen); void wr_scene_set_ambient_light(const float *ambient_light); diff --git a/src/webots/gui/WbView3D.cpp b/src/webots/gui/WbView3D.cpp index f7ce3bb2849..b60cc9bd591 100644 --- a/src/webots/gui/WbView3D.cpp +++ b/src/webots/gui/WbView3D.cpp @@ -1111,8 +1111,9 @@ void WbView3D::setWorld(WbSimulationWorld *w) { WbWrenOpenGlContext::doneWren(); - // first rendering without culling to make sure every meshes/textures are actually loaded on the GPU - renderNow(false); + // first rendering is offscreen without culling to make sure every meshes/textures are actually + // loaded on the GPU + renderNow(false, true); } void WbView3D::restoreOptionalRendering(const QStringList &enabledCenterOfMassNodeNames, @@ -1441,7 +1442,7 @@ void WbView3D::resizeWren(int width, int height) { emit resized(); } -void WbView3D::renderNow(bool culling) { +void WbView3D::renderNow(bool culling, bool offScreen) { if (!wr_gl_state_is_initialized()) initialize(); @@ -1461,7 +1462,7 @@ void WbView3D::renderNow(bool culling) { WbWrenOpenGlContext::doneWren(); } else #endif - WbWrenWindow::renderNow(culling); + WbWrenWindow::renderNow(culling, offScreen); mLastRefreshTimer.start(); emit mainRenderingEnded(mPhysicsRefresh); diff --git a/src/webots/gui/WbView3D.hpp b/src/webots/gui/WbView3D.hpp index d81d292a6c8..be3dec0bf64 100644 --- a/src/webots/gui/WbView3D.hpp +++ b/src/webots/gui/WbView3D.hpp @@ -97,7 +97,7 @@ public slots: protected slots: // cppcheck-suppress virtualCallInConstructor - void renderNow(bool culling = true) override; + void renderNow(bool culling = true, bool offScreen = false) override; protected: void initialize() override; diff --git a/src/webots/gui/WbWrenWindow.cpp b/src/webots/gui/WbWrenWindow.cpp index 0d404cd7131..44960f92495 100644 --- a/src/webots/gui/WbWrenWindow.cpp +++ b/src/webots/gui/WbWrenWindow.cpp @@ -200,8 +200,8 @@ void WbWrenWindow::renderLater() { } } -void WbWrenWindow::renderNow(bool culling) { - if (!isExposed() || !wr_gl_state_is_initialized()) +void WbWrenWindow::renderNow(bool culling, bool offScreen) { + if ((!isExposed() && !offScreen) || !wr_gl_state_is_initialized()) return; static int first = true; @@ -225,9 +225,10 @@ void WbWrenWindow::renderNow(bool culling) { WbWrenOpenGlContext::makeWrenCurrent(); - wr_scene_render(wr_scene_get_instance(), NULL, culling); + wr_scene_render(wr_scene_get_instance(), NULL, culling, offScreen); - WbWrenOpenGlContext::instance()->swapBuffers(this); + if (!offScreen) + WbWrenOpenGlContext::instance()->swapBuffers(this); WbWrenOpenGlContext::doneWren(); if (mVideoStreamingServer && mVideoStreamingServer->isNewFrameNeeded() && !first) diff --git a/src/webots/gui/WbWrenWindow.hpp b/src/webots/gui/WbWrenWindow.hpp index 9a49b17efe4..dee3c098876 100644 --- a/src/webots/gui/WbWrenWindow.hpp +++ b/src/webots/gui/WbWrenWindow.hpp @@ -65,7 +65,7 @@ public slots: protected: virtual void initialize(); - virtual void renderNow(bool culling = true); + virtual void renderNow(bool culling = true, bool offScreen = false); virtual void resizeWren(int width, int height); bool event(QEvent *event) override; diff --git a/src/webots/nodes/utils/WbVirtualRealityHeadset.cpp b/src/webots/nodes/utils/WbVirtualRealityHeadset.cpp index 07f7653fce8..9beeeb7bbad 100644 --- a/src/webots/nodes/utils/WbVirtualRealityHeadset.cpp +++ b/src/webots/nodes/utils/WbVirtualRealityHeadset.cpp @@ -406,7 +406,7 @@ void WbVirtualRealityHeadset::updateOrientationAndPosition() { if (mWrenViewports[0] && mSystem) { WbWrenOpenGlContext::makeWrenCurrent(); - wr_scene_render_to_viewports(wr_scene_get_instance(), 2, mWrenViewports, NULL, true); + wr_scene_render_to_viewports(wr_scene_get_instance(), 2, mWrenViewports, NULL, true, false); WbWrenOpenGlContext::doneWren(); vr::VRCompositor()->Submit(vr::Eye_Left, mTextureReferences[LEFT], mTextureBounds); vr::VRCompositor()->Submit(vr::Eye_Right, mTextureReferences[RIGHT], mTextureBounds); diff --git a/src/webots/wren/WbWrenCamera.cpp b/src/webots/wren/WbWrenCamera.cpp index f507904b2d1..eedf0c47c48 100644 --- a/src/webots/wren/WbWrenCamera.cpp +++ b/src/webots/wren/WbWrenCamera.cpp @@ -452,7 +452,7 @@ void WbWrenCamera::render() { else if (mType == 's') materialName = "segmentation"; wr_scene_enable_depth_reset(wr_scene_get_instance(), false); - wr_scene_render_to_viewports(wr_scene_get_instance(), numActiveViewports, mViewportsToRender, materialName, true); + wr_scene_render_to_viewports(wr_scene_get_instance(), numActiveViewports, mViewportsToRender, materialName, true, false); if (!isPlanarProjection()) applySphericalPostProcessingEffect(); diff --git a/src/webots/wren/WbWrenPicker.cpp b/src/webots/wren/WbWrenPicker.cpp index 2b78ab90c75..742051aecc8 100644 --- a/src/webots/wren/WbWrenPicker.cpp +++ b/src/webots/wren/WbWrenPicker.cpp @@ -134,7 +134,7 @@ bool WbWrenPicker::pick(int x, int y) { wr_viewport_enable_skybox(mViewport, false); wr_scene_enable_translucence(scene, false); wr_scene_enable_depth_reset(scene, false); - wr_scene_render_to_viewports(scene, 1, &mViewport, "picking", true); + wr_scene_render_to_viewports(scene, 1, &mViewport, "picking", true, false); wr_scene_enable_depth_reset(scene, true); wr_viewport_enable_skybox(mViewport, true); wr_scene_enable_translucence(scene, true); diff --git a/src/wren/Scene.cpp b/src/wren/Scene.cpp index 90791423099..87258a3e4dc 100644 --- a/src/wren/Scene.cpp +++ b/src/wren/Scene.cpp @@ -224,7 +224,7 @@ namespace wren { debug::printSceneTree(); } - void Scene::render(bool culling) { + void Scene::render(bool culling, bool offScreen) { assert(glstate::isInitialized()); ++mFrameCounter; @@ -233,10 +233,10 @@ namespace wren { // debug::printCacheContents(); // debug::printSceneTree(); - renderToViewports({mMainViewport}, culling); + renderToViewports({mMainViewport}, culling, offScreen); } - void Scene::renderToViewports(const std::vector &viewports, bool culling) { + void Scene::renderToViewports(const std::vector &viewports, bool culling, bool offScreen) { assert(glstate::isInitialized()); DEBUG("Notify frame listeners..."); @@ -282,7 +282,7 @@ namespace wren { } } else { renderToViewport(culling); - if (mCurrentViewport == mMainViewport && mCurrentViewport->frameBuffer()) { + if (!offScreen && mCurrentViewport == mMainViewport && mCurrentViewport->frameBuffer()) { glstate::bindDrawFrameBuffer(0); mCurrentViewport->frameBuffer()->blit(0, true, false, false, 0, 0, 0, 0, 0, 0, mCurrentViewport->width() * mCurrentViewport->pixelRatio(), @@ -909,22 +909,23 @@ void wr_scene_terminate_frame_capture(WrScene *scene) { reinterpret_cast(scene)->terminateFrameCapture(); } -void wr_scene_render(WrScene *scene, const char *material_name, bool culling) { +void wr_scene_render(WrScene *scene, const char *material_name, bool culling, bool offScreen) { if (material_name) wren::Renderable::setUseMaterial(material_name); - reinterpret_cast(scene)->render(culling); + reinterpret_cast(scene)->render(culling, offScreen); wren::Renderable::setUseMaterial(NULL); } -void wr_scene_render_to_viewports(WrScene *scene, int count, WrViewport **viewports, const char *material_name, bool culling) { +void wr_scene_render_to_viewports(WrScene *scene, int count, WrViewport **viewports, const char *material_name, bool culling, + bool offScreen) { if (material_name) wren::Renderable::setUseMaterial(material_name); wren::Viewport **start = reinterpret_cast(viewports); std::vector viewportsVector(start, start + count); - reinterpret_cast(scene)->renderToViewports(viewportsVector, culling); + reinterpret_cast(scene)->renderToViewports(viewportsVector, culling, offScreen); wren::Renderable::setUseMaterial(NULL); } diff --git a/src/wren/Scene.hpp b/src/wren/Scene.hpp index d865cbd9043..9f68654b77e 100644 --- a/src/wren/Scene.hpp +++ b/src/wren/Scene.hpp @@ -90,8 +90,8 @@ namespace wren { int computeNodeCount() const; static void printSceneTree(); - void render(bool culling); - void renderToViewports(const std::vector &viewports, bool culling); + void render(bool culling, bool offScreen = false); + void renderToViewports(const std::vector &viewports, bool culling, bool offScreen = false); void addFrameListener(void (*listener)()) { mListeners.push_back(listener); } void removeFrameListener(void (*listener)()); diff --git a/src/wren/Viewport.cpp b/src/wren/Viewport.cpp index 1d5e25fdeeb..723477ebb10 100644 --- a/src/wren/Viewport.cpp +++ b/src/wren/Viewport.cpp @@ -180,10 +180,6 @@ namespace wren { } } - glstate::bindDrawFrameBuffer(0); - - clear(); - for (size_t i = 0; i < mPostProcessingEffects.size(); ++i) mPostProcessingEffects[i]->apply(); } @@ -202,9 +198,6 @@ namespace wren { mAmbientOcclusionEffect->setResultFrameBuffer(mFrameBuffer); mAmbientOcclusionEffect->firstPass()->setInputTexture(0, mFrameBuffer->outputTexture(0)); } - glstate::bindDrawFrameBuffer(0); - - clear(); mAmbientOcclusionEffect->apply(); } @@ -224,10 +217,6 @@ namespace wren { mAntiAliasingEffect->firstPass()->setInputTexture(0, mFrameBuffer->outputTexture(0)); } - glstate::bindDrawFrameBuffer(0); - - clear(); - mAntiAliasingEffect->apply(); } } diff --git a/src/wren/demo/default.cpp b/src/wren/demo/default.cpp index fad345389c1..1578317ef93 100644 --- a/src/wren/demo/default.cpp +++ b/src/wren/demo/default.cpp @@ -99,7 +99,7 @@ static void create_wren_scene() { // Render function. static void render() { - wr_scene_render(wr_scene_get_instance(), NULL, true); + wr_scene_render(wr_scene_get_instance(), NULL, true, false); glutSwapBuffers(); } diff --git a/src/wren/demo/phong.cpp b/src/wren/demo/phong.cpp index 7ed9807a5c3..f7facb5ce95 100644 --- a/src/wren/demo/phong.cpp +++ b/src/wren/demo/phong.cpp @@ -166,7 +166,7 @@ static void render() { static int i = 0; printf("\33[2K\rrendering iteration %d", i++); fflush(stdout); - wr_scene_render(wr_scene_get_instance(), NULL, true); + wr_scene_render(wr_scene_get_instance(), NULL, true, false); glutSwapBuffers(); }