diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 247d5a0613..c3ab4fe115 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -160,6 +160,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _lastQueriedViewFrustum(), _lastQueriedTime(usecTimestampNow()), _mirrorViewRect(QRect(MIRROR_VIEW_LEFT_PADDING, MIRROR_VIEW_TOP_PADDING, MIRROR_VIEW_WIDTH, MIRROR_VIEW_HEIGHT)), + _viewTransform(new gpu::Transform()), _scaleMirror(1.0f), _rotateMirror(0.0f), _raiseMirror(0.0f), @@ -188,8 +189,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _lastNackTime(usecTimestampNow()), _lastSendDownstreamAudioStats(usecTimestampNow()), _isVSyncOn(true), - _aboutToQuit(false), - _viewTransform(new gpu::Transform()) + _aboutToQuit(false) { // read the ApplicationInfo.ini file for Name/Version/Domain information diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index f973ad7983..e0feb4e349 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -437,10 +437,15 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(entitiesDebugMenu, MenuOption::DisplayModelElementChildProxies, 0, false); addCheckableActionToQMenuAndActionHash(entitiesDebugMenu, MenuOption::DisableLightEntities, 0, false); + addCheckableActionToQMenuAndActionHash(entitiesDebugMenu, MenuOption::DontReduceMaterialSwitches, 0, false); + addCheckableActionToQMenuAndActionHash(entitiesDebugMenu, MenuOption::RenderEntitiesAsScene, 0, false); + QMenu* entityCullingMenu = entitiesDebugMenu->addMenu("Culling"); addCheckableActionToQMenuAndActionHash(entityCullingMenu, MenuOption::DontCullOutOfViewMeshParts, 0, false); addCheckableActionToQMenuAndActionHash(entityCullingMenu, MenuOption::DontCullTooSmallMeshParts, 0, false); - addCheckableActionToQMenuAndActionHash(entityCullingMenu, MenuOption::DontReduceMaterialSwitches, 0, false); + + + QMenu* voxelOptionsMenu = developerMenu->addMenu("Voxels"); addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::VoxelTextures); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index ee29833d78..b745246780 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -447,6 +447,7 @@ namespace MenuOption { const QString ReloadAllScripts = "Reload All Scripts"; const QString RenderBoundingCollisionShapes = "Show Bounding Collision Shapes"; const QString RenderDualContourSurfaces = "Render Dual Contour Surfaces"; + const QString RenderEntitiesAsScene = "Render Entities as Scene"; const QString RenderFocusIndicator = "Show Eye Focus"; const QString RenderHeadCollisionShapes = "Show Head Collision Shapes"; const QString RenderHeightfields = "Render Heightfields"; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index a5c388e7f2..292cc2fb71 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -39,6 +39,7 @@ #include "Recorder.h" #include "devices/Faceshift.h" #include "devices/OculusManager.h" +#include "renderer/AnimationHandle.h" #include "ui/TextRenderer.h" using namespace std; diff --git a/interface/src/entities/EntityTreeRenderer.cpp b/interface/src/entities/EntityTreeRenderer.cpp index 1876b6c624..99e4916c62 100644 --- a/interface/src/entities/EntityTreeRenderer.cpp +++ b/interface/src/entities/EntityTreeRenderer.cpp @@ -253,7 +253,45 @@ void EntityTreeRenderer::checkEnterLeaveEntities() { } void EntityTreeRenderer::render(RenderArgs::RenderMode renderMode) { - OctreeRenderer::render(renderMode); + bool dontRenderAsScene = !Menu::getInstance()->isOptionChecked(MenuOption::RenderEntitiesAsScene); + + if (dontRenderAsScene) { + OctreeRenderer::render(renderMode); + } else { + if (_tree) { + Model::startScene(); + RenderArgs args = { this, _viewFrustum, getSizeScale(), getBoundaryLevelAdjust(), renderMode, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + _tree->lockForRead(); + _tree->recurseTreeWithOperation(renderOperation, &args); + + Model::RenderMode modelRenderMode = renderMode == RenderArgs::SHADOW_RENDER_MODE + ? Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE; + + // we must call endScene while we still have the tree locked so that no one deletes a model + // on us while rendering the scene + Model::endScene(modelRenderMode, &args); + _tree->unlock(); + + // stats... + _meshesConsidered = args._meshesConsidered; + _meshesRendered = args._meshesRendered; + _meshesOutOfView = args._meshesOutOfView; + _meshesTooSmall = args._meshesTooSmall; + + _elementsTouched = args._elementsTouched; + _itemsRendered = args._itemsRendered; + _itemsOutOfView = args._itemsOutOfView; + _itemsTooSmall = args._itemsTooSmall; + + _materialSwitches = args._materialSwitches; + _trianglesRendered = args._trianglesRendered; + _quadsRendered = args._quadsRendered; + + _translucentMeshPartsRendered = args._translucentMeshPartsRendered; + _opaqueMeshPartsRendered = args._opaqueMeshPartsRendered; + } + } deleteReleasedModels(); // seems like as good as any other place to do some memory cleanup } diff --git a/interface/src/entities/RenderableModelEntityItem.cpp b/interface/src/entities/RenderableModelEntityItem.cpp index 3cee527273..8d932b121d 100644 --- a/interface/src/entities/RenderableModelEntityItem.cpp +++ b/interface/src/entities/RenderableModelEntityItem.cpp @@ -172,7 +172,12 @@ void RenderableModelEntityItem::render(RenderArgs* args) { // TODO: this is the majority of model render time. And rendering of a cube model vs the basic Box render // is significantly more expensive. Is there a way to call this that doesn't cost us as much? PerformanceTimer perfTimer("model->render"); - _model->render(alpha, modelRenderMode, args); + bool dontRenderAsScene = !Menu::getInstance()->isOptionChecked(MenuOption::RenderEntitiesAsScene); + if (dontRenderAsScene) { + _model->render(alpha, modelRenderMode, args); + } else { + _model->renderInScene(alpha, args); + } } else { // if we couldn't get a model, then just draw a cube glColor3ub(getColor()[RED_INDEX],getColor()[GREEN_INDEX],getColor()[BLUE_INDEX]); diff --git a/interface/src/renderer/AnimationHandle.cpp b/interface/src/renderer/AnimationHandle.cpp new file mode 100644 index 0000000000..8ecf5d9699 --- /dev/null +++ b/interface/src/renderer/AnimationHandle.cpp @@ -0,0 +1,176 @@ +// +// AnimationHandle.cpp +// interface/src/renderer +// +// Created by Andrzej Kapolka on 10/18/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "AnimationHandle.h" +#include "Application.h" + +void AnimationHandle::setURL(const QUrl& url) { + if (_url != url) { + _animation = Application::getInstance()->getAnimationCache()->getAnimation(_url = url); + _jointMappings.clear(); + } +} + +static void insertSorted(QList& handles, const AnimationHandlePointer& handle) { + for (QList::iterator it = handles.begin(); it != handles.end(); it++) { + if (handle->getPriority() > (*it)->getPriority()) { + handles.insert(it, handle); + return; + } + } + handles.append(handle); +} + +void AnimationHandle::setPriority(float priority) { + if (_priority == priority) { + return; + } + if (_running) { + _model->_runningAnimations.removeOne(_self); + if (priority < _priority) { + replaceMatchingPriorities(priority); + } + _priority = priority; + insertSorted(_model->_runningAnimations, _self); + + } else { + _priority = priority; + } +} + +void AnimationHandle::setStartAutomatically(bool startAutomatically) { + if ((_startAutomatically = startAutomatically) && !_running) { + start(); + } +} + +void AnimationHandle::setMaskedJoints(const QStringList& maskedJoints) { + _maskedJoints = maskedJoints; + _jointMappings.clear(); +} + +void AnimationHandle::setRunning(bool running) { + if (_running == running) { + if (running) { + // move back to the beginning + _frameIndex = _firstFrame; + } + return; + } + if ((_running = running)) { + if (!_model->_runningAnimations.contains(_self)) { + insertSorted(_model->_runningAnimations, _self); + } + _frameIndex = _firstFrame; + + } else { + _model->_runningAnimations.removeOne(_self); + replaceMatchingPriorities(0.0f); + } + emit runningChanged(_running); +} + +AnimationHandle::AnimationHandle(Model* model) : + QObject(model), + _model(model), + _fps(30.0f), + _priority(1.0f), + _loop(false), + _hold(false), + _startAutomatically(false), + _firstFrame(0.0f), + _lastFrame(FLT_MAX), + _running(false) { +} + +AnimationDetails AnimationHandle::getAnimationDetails() const { + AnimationDetails details(_role, _url, _fps, _priority, _loop, _hold, + _startAutomatically, _firstFrame, _lastFrame, _running, _frameIndex); + return details; +} + + +void AnimationHandle::simulate(float deltaTime) { + _frameIndex += deltaTime * _fps; + + // update the joint mappings if necessary/possible + if (_jointMappings.isEmpty()) { + if (_model->isActive()) { + _jointMappings = _model->getGeometry()->getJointMappings(_animation); + } + if (_jointMappings.isEmpty()) { + return; + } + if (!_maskedJoints.isEmpty()) { + const FBXGeometry& geometry = _model->getGeometry()->getFBXGeometry(); + for (int i = 0; i < _jointMappings.size(); i++) { + int& mapping = _jointMappings[i]; + if (mapping != -1 && _maskedJoints.contains(geometry.joints.at(mapping).name)) { + mapping = -1; + } + } + } + } + + const FBXGeometry& animationGeometry = _animation->getGeometry(); + if (animationGeometry.animationFrames.isEmpty()) { + stop(); + return; + } + float endFrameIndex = qMin(_lastFrame, animationGeometry.animationFrames.size() - (_loop ? 0.0f : 1.0f)); + float startFrameIndex = qMin(_firstFrame, endFrameIndex); + if ((!_loop && (_frameIndex < startFrameIndex || _frameIndex > endFrameIndex)) || startFrameIndex == endFrameIndex) { + // passed the end; apply the last frame + applyFrame(glm::clamp(_frameIndex, startFrameIndex, endFrameIndex)); + if (!_hold) { + stop(); + } + return; + } + // wrap within the the desired range + if (_frameIndex < startFrameIndex) { + _frameIndex = endFrameIndex - glm::mod(endFrameIndex - _frameIndex, endFrameIndex - startFrameIndex); + + } else if (_frameIndex > endFrameIndex) { + _frameIndex = startFrameIndex + glm::mod(_frameIndex - startFrameIndex, endFrameIndex - startFrameIndex); + } + + // blend between the closest two frames + applyFrame(_frameIndex); +} + +void AnimationHandle::applyFrame(float frameIndex) { + const FBXGeometry& animationGeometry = _animation->getGeometry(); + int frameCount = animationGeometry.animationFrames.size(); + const FBXAnimationFrame& floorFrame = animationGeometry.animationFrames.at((int)glm::floor(frameIndex) % frameCount); + const FBXAnimationFrame& ceilFrame = animationGeometry.animationFrames.at((int)glm::ceil(frameIndex) % frameCount); + float frameFraction = glm::fract(frameIndex); + for (int i = 0; i < _jointMappings.size(); i++) { + int mapping = _jointMappings.at(i); + if (mapping != -1) { + JointState& state = _model->_jointStates[mapping]; + state.setRotationInConstrainedFrame(safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction), _priority); + } + } +} + +void AnimationHandle::replaceMatchingPriorities(float newPriority) { + for (int i = 0; i < _jointMappings.size(); i++) { + int mapping = _jointMappings.at(i); + if (mapping != -1) { + JointState& state = _model->_jointStates[mapping]; + if (_priority == state._animationPriority) { + state._animationPriority = newPriority; + } + } + } +} + diff --git a/interface/src/renderer/AnimationHandle.h b/interface/src/renderer/AnimationHandle.h new file mode 100644 index 0000000000..3b736698df --- /dev/null +++ b/interface/src/renderer/AnimationHandle.h @@ -0,0 +1,112 @@ +// +// AnimationHandle.h +// interface/src/renderer +// +// Created by Andrzej Kapolka on 10/18/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AnimationHandle_h +#define hifi_AnimationHandle_h + +#include +#include +#include +#include +#include + +#include + +class AnimationHandle; +class Model; + +typedef QSharedPointer AnimationHandlePointer; +typedef QWeakPointer WeakAnimationHandlePointer; + + +/// Represents a handle to a model animation. +class AnimationHandle : public QObject { + Q_OBJECT + +public: + + void setRole(const QString& role) { _role = role; } + const QString& getRole() const { return _role; } + + void setURL(const QUrl& url); + const QUrl& getURL() const { return _url; } + + void setFPS(float fps) { _fps = fps; } + float getFPS() const { return _fps; } + + void setPriority(float priority); + float getPriority() const { return _priority; } + + void setLoop(bool loop) { _loop = loop; } + bool getLoop() const { return _loop; } + + void setHold(bool hold) { _hold = hold; } + bool getHold() const { return _hold; } + + void setStartAutomatically(bool startAutomatically); + bool getStartAutomatically() const { return _startAutomatically; } + + void setFirstFrame(float firstFrame) { _firstFrame = firstFrame; } + float getFirstFrame() const { return _firstFrame; } + + void setLastFrame(float lastFrame) { _lastFrame = lastFrame; } + float getLastFrame() const { return _lastFrame; } + + void setMaskedJoints(const QStringList& maskedJoints); + const QStringList& getMaskedJoints() const { return _maskedJoints; } + + void setRunning(bool running); + bool isRunning() const { return _running; } + + void setFrameIndex(float frameIndex) { _frameIndex = glm::clamp(_frameIndex, _firstFrame, _lastFrame); } + float getFrameIndex() const { return _frameIndex; } + + AnimationDetails getAnimationDetails() const; + +signals: + + void runningChanged(bool running); + +public slots: + + void start() { setRunning(true); } + void stop() { setRunning(false); } + +private: + + friend class Model; + + AnimationHandle(Model* model); + + void simulate(float deltaTime); + void applyFrame(float frameIndex); + void replaceMatchingPriorities(float newPriority); + + Model* _model; + WeakAnimationHandlePointer _self; + AnimationPointer _animation; + QString _role; + QUrl _url; + float _fps; + float _priority; + bool _loop; + bool _hold; + bool _startAutomatically; + float _firstFrame; + float _lastFrame; + QStringList _maskedJoints; + bool _running; + QVector _jointMappings; + float _frameIndex; +}; + + +#endif // hifi_AnimationHandle_h diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 246d4abfbf..8b3c656ae7 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -23,6 +23,7 @@ #include #include +#include "AnimationHandle.h" #include "Application.h" #include "Model.h" @@ -517,17 +518,7 @@ void Model::recalcuateMeshBoxes() { } } -bool Model::render(float alpha, RenderMode mode, RenderArgs* args) { - PROFILE_RANGE(__FUNCTION__); - - // render the attachments - foreach (Model* attachment, _attachments) { - attachment->render(alpha, mode); - } - if (_meshStates.isEmpty()) { - return false; - } - +void Model::renderSetup(RenderArgs* args) { // if we don't have valid mesh boxes, calculate them now, this only matters in cases // where our caller has passed RenderArgs which will include a view frustum we can cull // against. We cache the results of these calculations so long as the model hasn't been @@ -549,6 +540,25 @@ bool Model::render(float alpha, RenderMode mode, RenderArgs* args) { if (!_meshGroupsKnown) { segregateMeshGroups(); } +} + +bool Model::render(float alpha, RenderMode mode, RenderArgs* args) { + PROFILE_RANGE(__FUNCTION__); + + // render the attachments + foreach (Model* attachment, _attachments) { + attachment->render(alpha, mode); + } + if (_meshStates.isEmpty()) { + return false; + } + + renderSetup(args); + return renderCore(alpha, mode, args); +} + +bool Model::renderCore(float alpha, RenderMode mode, RenderArgs* args) { + PROFILE_RANGE(__FUNCTION__); // Let's introduce a gpu::Batch to capture all the calls to the graphics api _renderBatch.clear(); @@ -1471,6 +1481,279 @@ void Model::deleteGeometry() { _blendedBlendshapeCoefficients.clear(); } +// Scene rendering support +QVector Model::_modelsInScene; +gpu::Batch Model::_sceneRenderBatch; +void Model::startScene() { + _modelsInScene.clear(); +} + +void Model::setupBatchTransform(gpu::Batch& batch) { + GLBATCH(glPushMatrix)(); + + // Capture the view matrix once for the rendering of this model + if (_transforms.empty()) { + _transforms.push_back(gpu::TransformPointer(new gpu::Transform())); + } + (*_transforms[0]) = gpu::Transform((*Application::getInstance()->getViewTransform())); + _transforms[0]->preTranslate(-_translation); + batch.setViewTransform(_transforms[0]); +} + +void Model::endScene(RenderMode mode, RenderArgs* args) { + PROFILE_RANGE(__FUNCTION__); + + // Let's introduce a gpu::Batch to capture all the calls to the graphics api + _sceneRenderBatch.clear(); + gpu::Batch& batch = _sceneRenderBatch; + + GLBATCH(glDisable)(GL_COLOR_MATERIAL); + + if (mode == DIFFUSE_RENDER_MODE || mode == NORMAL_RENDER_MODE) { + GLBATCH(glDisable)(GL_CULL_FACE); + } else { + GLBATCH(glEnable)(GL_CULL_FACE); + if (mode == SHADOW_RENDER_MODE) { + GLBATCH(glCullFace)(GL_FRONT); + } + } + + // render opaque meshes with alpha testing + + GLBATCH(glDisable)(GL_BLEND); + GLBATCH(glEnable)(GL_ALPHA_TEST); + + if (mode == SHADOW_RENDER_MODE) { + GLBATCH(glAlphaFunc)(GL_EQUAL, 0.0f); + } + + + /*Application::getInstance()->getTextureCache()->setPrimaryDrawBuffers( + mode == DEFAULT_RENDER_MODE || mode == DIFFUSE_RENDER_MODE, + mode == DEFAULT_RENDER_MODE || mode == NORMAL_RENDER_MODE, + mode == DEFAULT_RENDER_MODE); + */ + { + GLenum buffers[3]; + int bufferCount = 0; + if (mode == DEFAULT_RENDER_MODE || mode == DIFFUSE_RENDER_MODE) { + buffers[bufferCount++] = GL_COLOR_ATTACHMENT0; + } + if (mode == DEFAULT_RENDER_MODE || mode == NORMAL_RENDER_MODE) { + buffers[bufferCount++] = GL_COLOR_ATTACHMENT1; + } + if (mode == DEFAULT_RENDER_MODE) { + buffers[bufferCount++] = GL_COLOR_ATTACHMENT2; + } + GLBATCH(glDrawBuffers)(bufferCount, buffers); + } + + const float DEFAULT_ALPHA_THRESHOLD = 0.5f; + + int opaqueMeshPartsRendered = 0; + + // now, for each model in the scene, render the mesh portions + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + opaqueMeshPartsRendered += model->renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, false, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + opaqueMeshPartsRendered += model->renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, true, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + opaqueMeshPartsRendered += model->renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, false, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + opaqueMeshPartsRendered += model->renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, true, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + opaqueMeshPartsRendered += model->renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, false, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + opaqueMeshPartsRendered += model->renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, true, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + opaqueMeshPartsRendered += model->renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, true, false, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + opaqueMeshPartsRendered += model->renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, true, args); + GLBATCH(glPopMatrix)(); + } + + // render translucent meshes afterwards + //Application::getInstance()->getTextureCache()->setPrimaryDrawBuffers(false, true, true); + { + GLenum buffers[2]; + int bufferCount = 0; + buffers[bufferCount++] = GL_COLOR_ATTACHMENT1; + buffers[bufferCount++] = GL_COLOR_ATTACHMENT2; + GLBATCH(glDrawBuffers)(bufferCount, buffers); + } + + int translucentParts = 0; + const float MOSTLY_OPAQUE_THRESHOLD = 0.75f; + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, false, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, true, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, false, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, true, false, false, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, true, false, true, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, true, true, false, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, true, false, true, args); + GLBATCH(glPopMatrix)(); + } + + GLBATCH(glDisable)(GL_ALPHA_TEST); + GLBATCH(glEnable)(GL_BLEND); + GLBATCH(glDepthMask)(false); + GLBATCH(glDepthFunc)(GL_LEQUAL); + + //Application::getInstance()->getTextureCache()->setPrimaryDrawBuffers(true); + { + GLenum buffers[1]; + int bufferCount = 0; + buffers[bufferCount++] = GL_COLOR_ATTACHMENT0; + GLBATCH(glDrawBuffers)(bufferCount, buffers); + } + + if (mode == DEFAULT_RENDER_MODE || mode == DIFFUSE_RENDER_MODE) { + const float MOSTLY_TRANSPARENT_THRESHOLD = 0.0f; + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, true, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, true, false, false, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, true, false, true, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, true, true, false, args); + GLBATCH(glPopMatrix)(); + } + foreach(Model* model, _modelsInScene) { + model->setupBatchTransform(batch); + translucentParts += model->renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, true, false, true, args); + GLBATCH(glPopMatrix)(); + } + } + + GLBATCH(glDepthMask)(true); + GLBATCH(glDepthFunc)(GL_LESS); + GLBATCH(glDisable)(GL_CULL_FACE); + + if (mode == SHADOW_RENDER_MODE) { + GLBATCH(glCullFace)(GL_BACK); + } + + // deactivate vertex arrays after drawing + GLBATCH(glDisableClientState)(GL_NORMAL_ARRAY); + GLBATCH(glDisableClientState)(GL_VERTEX_ARRAY); + GLBATCH(glDisableClientState)(GL_TEXTURE_COORD_ARRAY); + GLBATCH(glDisableClientState)(GL_COLOR_ARRAY); + GLBATCH(glDisableVertexAttribArray)(gpu::Stream::TANGENT); + GLBATCH(glDisableVertexAttribArray)(gpu::Stream::SKIN_CLUSTER_INDEX); + GLBATCH(glDisableVertexAttribArray)(gpu::Stream::SKIN_CLUSTER_WEIGHT); + + // bind with 0 to switch back to normal operation + GLBATCH(glBindBuffer)(GL_ARRAY_BUFFER, 0); + GLBATCH(glBindBuffer)(GL_ELEMENT_ARRAY_BUFFER, 0); + GLBATCH(glBindTexture)(GL_TEXTURE_2D, 0); + + // Render! + { + PROFILE_RANGE("render Batch"); + ::gpu::GLBackend::renderBatch(batch); + } + + // restore all the default material settings + Application::getInstance()->setupWorldLight(); + + if (args) { + args->_translucentMeshPartsRendered = translucentParts; + args->_opaqueMeshPartsRendered = opaqueMeshPartsRendered; + } +} + +bool Model::renderInScene(float alpha, RenderArgs* args) { + // render the attachments + foreach (Model* attachment, _attachments) { + attachment->renderInScene(alpha); + } + if (_meshStates.isEmpty()) { + return false; + } + renderSetup(args); + _modelsInScene.push_back(this); + return true; +} + void Model::segregateMeshGroups() { _meshesTranslucentTangents.clear(); _meshesTranslucent.clear(); @@ -1989,166 +2272,3 @@ int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, fl return meshPartsRendered; } - -void AnimationHandle::setURL(const QUrl& url) { - if (_url != url) { - _animation = Application::getInstance()->getAnimationCache()->getAnimation(_url = url); - _jointMappings.clear(); - } -} - -static void insertSorted(QList& handles, const AnimationHandlePointer& handle) { - for (QList::iterator it = handles.begin(); it != handles.end(); it++) { - if (handle->getPriority() > (*it)->getPriority()) { - handles.insert(it, handle); - return; - } - } - handles.append(handle); -} - -void AnimationHandle::setPriority(float priority) { - if (_priority == priority) { - return; - } - if (_running) { - _model->_runningAnimations.removeOne(_self); - if (priority < _priority) { - replaceMatchingPriorities(priority); - } - _priority = priority; - insertSorted(_model->_runningAnimations, _self); - - } else { - _priority = priority; - } -} - -void AnimationHandle::setStartAutomatically(bool startAutomatically) { - if ((_startAutomatically = startAutomatically) && !_running) { - start(); - } -} - -void AnimationHandle::setMaskedJoints(const QStringList& maskedJoints) { - _maskedJoints = maskedJoints; - _jointMappings.clear(); -} - -void AnimationHandle::setRunning(bool running) { - if (_running == running) { - if (running) { - // move back to the beginning - _frameIndex = _firstFrame; - } - return; - } - if ((_running = running)) { - if (!_model->_runningAnimations.contains(_self)) { - insertSorted(_model->_runningAnimations, _self); - } - _frameIndex = _firstFrame; - - } else { - _model->_runningAnimations.removeOne(_self); - replaceMatchingPriorities(0.0f); - } - emit runningChanged(_running); -} - -AnimationHandle::AnimationHandle(Model* model) : - QObject(model), - _model(model), - _fps(30.0f), - _priority(1.0f), - _loop(false), - _hold(false), - _startAutomatically(false), - _firstFrame(0.0f), - _lastFrame(FLT_MAX), - _running(false) { -} - -AnimationDetails AnimationHandle::getAnimationDetails() const { - AnimationDetails details(_role, _url, _fps, _priority, _loop, _hold, - _startAutomatically, _firstFrame, _lastFrame, _running, _frameIndex); - return details; -} - - -void AnimationHandle::simulate(float deltaTime) { - _frameIndex += deltaTime * _fps; - - // update the joint mappings if necessary/possible - if (_jointMappings.isEmpty()) { - if (_model->isActive()) { - _jointMappings = _model->getGeometry()->getJointMappings(_animation); - } - if (_jointMappings.isEmpty()) { - return; - } - if (!_maskedJoints.isEmpty()) { - const FBXGeometry& geometry = _model->getGeometry()->getFBXGeometry(); - for (int i = 0; i < _jointMappings.size(); i++) { - int& mapping = _jointMappings[i]; - if (mapping != -1 && _maskedJoints.contains(geometry.joints.at(mapping).name)) { - mapping = -1; - } - } - } - } - - const FBXGeometry& animationGeometry = _animation->getGeometry(); - if (animationGeometry.animationFrames.isEmpty()) { - stop(); - return; - } - float endFrameIndex = qMin(_lastFrame, animationGeometry.animationFrames.size() - (_loop ? 0.0f : 1.0f)); - float startFrameIndex = qMin(_firstFrame, endFrameIndex); - if ((!_loop && (_frameIndex < startFrameIndex || _frameIndex > endFrameIndex)) || startFrameIndex == endFrameIndex) { - // passed the end; apply the last frame - applyFrame(glm::clamp(_frameIndex, startFrameIndex, endFrameIndex)); - if (!_hold) { - stop(); - } - return; - } - // wrap within the the desired range - if (_frameIndex < startFrameIndex) { - _frameIndex = endFrameIndex - glm::mod(endFrameIndex - _frameIndex, endFrameIndex - startFrameIndex); - - } else if (_frameIndex > endFrameIndex) { - _frameIndex = startFrameIndex + glm::mod(_frameIndex - startFrameIndex, endFrameIndex - startFrameIndex); - } - - // blend between the closest two frames - applyFrame(_frameIndex); -} - -void AnimationHandle::applyFrame(float frameIndex) { - const FBXGeometry& animationGeometry = _animation->getGeometry(); - int frameCount = animationGeometry.animationFrames.size(); - const FBXAnimationFrame& floorFrame = animationGeometry.animationFrames.at((int)glm::floor(frameIndex) % frameCount); - const FBXAnimationFrame& ceilFrame = animationGeometry.animationFrames.at((int)glm::ceil(frameIndex) % frameCount); - float frameFraction = glm::fract(frameIndex); - for (int i = 0; i < _jointMappings.size(); i++) { - int mapping = _jointMappings.at(i); - if (mapping != -1) { - JointState& state = _model->_jointStates[mapping]; - state.setRotationInConstrainedFrame(safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction), _priority); - } - } -} - -void AnimationHandle::replaceMatchingPriorities(float newPriority) { - for (int i = 0; i < _jointMappings.size(); i++) { - int mapping = _jointMappings.at(i); - if (mapping != -1) { - JointState& state = _model->_jointStates[mapping]; - if (_priority == state._animationPriority) { - state._animationPriority = newPriority; - } - } - } -} - diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 29d4f35167..8c74b1a222 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -21,6 +21,7 @@ #include #include +#include "AnimationHandle.h" #include "GeometryCache.h" #include "InterfaceConfig.h" #include "JointState.h" @@ -29,14 +30,10 @@ class QScriptEngine; -class AnimationHandle; class Shape; class RenderArgs; class ViewFrustum; -typedef QSharedPointer AnimationHandlePointer; -typedef QWeakPointer WeakAnimationHandlePointer; - #include "gpu/Stream.h" #include "gpu/Batch.h" @@ -92,6 +89,11 @@ public: enum RenderMode { DEFAULT_RENDER_MODE, SHADOW_RENDER_MODE, DIFFUSE_RENDER_MODE, NORMAL_RENDER_MODE }; bool render(float alpha = 1.0f, RenderMode mode = DEFAULT_RENDER_MODE, RenderArgs* args = NULL); + + // Scene rendering support + static void startScene(); + bool renderInScene(float alpha = 1.0f, RenderArgs* args = NULL); + static void endScene(RenderMode mode = DEFAULT_RENDER_MODE, RenderArgs* args = NULL); /// Sets the URL of the model to render. /// \param fallback the URL of a fallback model to render if the requested model fails to load @@ -258,14 +260,13 @@ protected: /// Computes and returns the extended length of the limb terminating at the specified joint and starting at the joint's /// first free ancestor. float getLimbLength(int jointIndex) const; - + private: friend class AnimationHandle; void applyNextGeometry(); void deleteGeometry(); - int renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args = NULL); QVector createJointStates(const FBXGeometry& geometry); void initJointTransforms(); @@ -283,7 +284,7 @@ private: gpu::Buffers _blendedVertexBuffers; gpu::Transforms _transforms; - gpu::Batch _renderBatch; + gpu::Batch _renderBatch; QVector > > _dilatedTextures; @@ -395,91 +396,25 @@ private: QVector _meshesOpaqueTangentsSpecularSkinned; QVector _meshesOpaqueSpecularSkinned; + // Scene rendering support + static QVector _modelsInScene; + static gpu::Batch _sceneRenderBatch; + + static void endSceneSimple(RenderMode mode = DEFAULT_RENDER_MODE, RenderArgs* args = NULL); + static void endSceneSplitPass(RenderMode mode = DEFAULT_RENDER_MODE, RenderArgs* args = NULL); + + // helper functions used by render() or renderInScene() + void renderSetup(RenderArgs* args); + bool renderCore(float alpha, RenderMode mode, RenderArgs* args); + int renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, + bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args = NULL); + void setupBatchTransform(gpu::Batch& batch); + }; Q_DECLARE_METATYPE(QPointer) Q_DECLARE_METATYPE(QWeakPointer) Q_DECLARE_METATYPE(QVector) -/// Represents a handle to a model animation. -class AnimationHandle : public QObject { - Q_OBJECT - -public: - - void setRole(const QString& role) { _role = role; } - const QString& getRole() const { return _role; } - - void setURL(const QUrl& url); - const QUrl& getURL() const { return _url; } - - void setFPS(float fps) { _fps = fps; } - float getFPS() const { return _fps; } - - void setPriority(float priority); - float getPriority() const { return _priority; } - - void setLoop(bool loop) { _loop = loop; } - bool getLoop() const { return _loop; } - - void setHold(bool hold) { _hold = hold; } - bool getHold() const { return _hold; } - - void setStartAutomatically(bool startAutomatically); - bool getStartAutomatically() const { return _startAutomatically; } - - void setFirstFrame(float firstFrame) { _firstFrame = firstFrame; } - float getFirstFrame() const { return _firstFrame; } - - void setLastFrame(float lastFrame) { _lastFrame = lastFrame; } - float getLastFrame() const { return _lastFrame; } - - void setMaskedJoints(const QStringList& maskedJoints); - const QStringList& getMaskedJoints() const { return _maskedJoints; } - - void setRunning(bool running); - bool isRunning() const { return _running; } - - void setFrameIndex(float frameIndex) { _frameIndex = glm::clamp(_frameIndex, _firstFrame, _lastFrame); } - float getFrameIndex() const { return _frameIndex; } - - AnimationDetails getAnimationDetails() const; - -signals: - - void runningChanged(bool running); - -public slots: - - void start() { setRunning(true); } - void stop() { setRunning(false); } - -private: - - friend class Model; - - AnimationHandle(Model* model); - - void simulate(float deltaTime); - void applyFrame(float frameIndex); - void replaceMatchingPriorities(float newPriority); - - Model* _model; - WeakAnimationHandlePointer _self; - AnimationPointer _animation; - QString _role; - QUrl _url; - float _fps; - float _priority; - bool _loop; - bool _hold; - bool _startAutomatically; - float _firstFrame; - float _lastFrame; - QStringList _maskedJoints; - bool _running; - QVector _jointMappings; - float _frameIndex; -}; #endif // hifi_Model_h